Skip to content

Commit e28e5bf

Browse files
authored
[TLE][MTHREADS] Support barrier/wgmma/pipe/ws primitives (#944)
* [TLE][MTHREADS] Add memdesc slot binding with constant bounds checks * [TLE][MTHREADS] Add backend-local TLE barrier support * [TLE][MTHREADS] Support completion barriers for TME copies * [TLE][MTHREADS] Add explicit warp-specialize container support * [TLE][MTHREADS] Add warp-specialize frontend diagnostics * [TLE][MTHREADS] Add explicit warp-specialize integration coverage * [TLE][MTHREADS] Lower barrier allocations to hardware IDs * [TLE][MTHREADS] Synchronize barrier initialization for all kernels * [TLE][MTHREADS] Lower explicit TME completion transactions * [TLE][MTHREADS] Complete barrier consumer protocol * [TLE][MTHREADS] Lower WGMMA operations to SQMMA * [TLE][MTHREADS] Add single-field pipe lowering * [TLE][MTHREADS] Add static warp-specialize lowering * [TLE][MTHREADS] Support three-stage buffered pipelines * [TLE][MTHREADS] Enable automatic SQMMA shared layouts
1 parent d9f55cc commit e28e5bf

49 files changed

Lines changed: 8407 additions & 66 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

python/triton/experimental/tle/language/gpu/core.py

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424
from typing import Optional, Sequence
2525
from enum import Enum
2626
from . import types as tle
27+
from .mthreads import common as mthreads_common
28+
from .mthreads import buffer as mthreads_buffer
2729
from .mthreads import copy as mthreads_copy
30+
from .mthreads import warp_specialize as mthreads_warp_specialize
31+
from .mthreads import wgmma as mthreads_wgmma
2832
from .iluvatar import copy as iluvatar_copy
2933
from triton.compiler.code_generator import flatten_values_to_ir, unflatten_ir_values
3034

@@ -45,6 +49,9 @@
4549
def _mark_wgmma_user_promise(_semantic, _generator):
4650
if _generator is None or _semantic is None:
4751
return
52+
# This module attribute is NVIDIA-specific and must not leak into mthreads IR.
53+
if mthreads_common.enabled():
54+
return
4855
_generator.module.set_attr(
4956
_WGMMA_PIPELINE_MODE_ATTR,
5057
_semantic.builder.get_string_attr(_WGMMA_PIPELINE_MODE_USER_PROMISE),
@@ -186,8 +193,12 @@ def warp_specialize(functions_and_args, worker_num_warps, worker_num_regs, _sema
186193
if _generator is None:
187194
raise ValueError("warp_specialize requires a Triton code generator")
188195
functions_and_args = tl._unwrap_if_constexpr(functions_and_args)
189-
worker_num_warps = [tl._unwrap_if_constexpr(w) for w in tl._unwrap_if_constexpr(worker_num_warps)]
190-
worker_num_regs = [tl._unwrap_if_constexpr(r) for r in tl._unwrap_if_constexpr(worker_num_regs)]
196+
mthreads_enabled = mthreads_common.enabled()
197+
if mthreads_enabled:
198+
worker_num_warps, worker_num_regs = mthreads_warp_specialize.normalize_config(worker_num_warps, worker_num_regs)
199+
else:
200+
worker_num_warps = [tl._unwrap_if_constexpr(w) for w in tl._unwrap_if_constexpr(worker_num_warps)]
201+
worker_num_regs = [tl._unwrap_if_constexpr(r) for r in tl._unwrap_if_constexpr(worker_num_regs)]
191202
if len(functions_and_args) < 1:
192203
raise ValueError("warp_specialize requires at least a default partition function")
193204
num_partitions = len(functions_and_args) - 1
@@ -200,8 +211,9 @@ def warp_specialize(functions_and_args, worker_num_warps, worker_num_regs, _sema
200211

201212
builder = _semantic.builder
202213
insert_pt = builder.get_insertion_point()
203-
inline_user_promise = _is_wgmma_user_promise_marked(_generator)
204-
if inline_user_promise:
214+
if mthreads_enabled:
215+
call_jit_function = mthreads_warp_specialize.partition_function_caller(_generator)
216+
elif _is_wgmma_user_promise_marked(_generator):
205217
call_jit_function = _generator.inline_JitFunction
206218
else:
207219
call_jit_function = _generator.call_JitFunction
@@ -225,7 +237,10 @@ def warp_specialize(functions_and_args, worker_num_warps, worker_num_regs, _sema
225237
worker_arg_handles, worker_items = _deduplicate_warp_specialize_captures(worker_items)
226238

227239
builder.restore_insertion_point(insert_pt)
228-
ws_op = builder.create_warp_specialize(result_types, worker_arg_handles, worker_num_warps)
240+
if mthreads_enabled:
241+
ws_op = mthreads_warp_specialize.create_op(builder, result_types, worker_num_warps)
242+
else:
243+
ws_op = builder.create_warp_specialize(result_types, worker_arg_handles, worker_num_warps)
229244
real_default_block = builder.create_block_with_parent(ws_op.get_default_region(), [])
230245
default_block.merge_block_before(real_default_block)
231246
default_block = real_default_block
@@ -234,7 +249,10 @@ def warp_specialize(functions_and_args, worker_num_warps, worker_num_regs, _sema
234249
ws_op.set_requested_registers(worker_num_regs)
235250

236251
builder.create_block_with_parent(ws_op.get_partition_op_holder(), [])
237-
partitions_op = builder.create_warp_specialize_partitions(num_partitions)
252+
if mthreads_enabled:
253+
partitions_op = mthreads_warp_specialize.create_partitions(builder, worker_arg_handles, num_partitions)
254+
else:
255+
partitions_op = builder.create_warp_specialize_partitions(num_partitions)
238256
partition_arg_types = [arg.get_type() for arg in worker_arg_handles]
239257
for idx, (worker_fn, worker_args, flattened, remapped) in enumerate(worker_items):
240258
block = builder.create_block_with_parent(partitions_op.get_region(idx), partition_arg_types)
@@ -288,6 +306,9 @@ def alloc(
288306
dtype: Data type
289307
layout: Memory layout encoding (optional)
290308
scope: Storage type (default to shared memory)
309+
nv_mma_shared_layout: Select an MMA-consumer-defined shared layout when
310+
``layout`` is None. On mthreads this is materialized by the SQMMA
311+
lowering rather than as an NVIDIA encoding.
291312
_semantic: Semantic analyzer (internal use)
292313
293314
Returns:
@@ -331,16 +352,22 @@ def alloc(
331352

332353
# Map scope to storage (backward compatibility)
333354
storage = scope
355+
mthreads_auto_sqmma_shared_layout = (mthreads_common.enabled() and storage == tle.smem
356+
and mthreads_wgmma.use_auto_shared_layout(layout, nv_mma_shared_layout))
334357

335358
try:
336359
unwrapped_shape = [tl._unwrap_if_constexpr(dim) for dim in shape]
337360
full_shape = unwrapped_shape
361+
use_mthreads_buffer = (mthreads_common.enabled() and mthreads_buffer.needs_non_power_of_two_leading_dim(
362+
_semantic.builder, unwrapped_shape))
363+
if use_mthreads_buffer:
364+
mthreads_buffer.validate_shape(unwrapped_shape)
338365
dtype = tl._unwrap_if_constexpr(dtype)
339366
elem_type = dtype.to_ir(_semantic.builder)
340367

341368
if layout is None:
342369
if storage == tle.smem:
343-
if not nv_mma_shared_layout:
370+
if mthreads_auto_sqmma_shared_layout or not nv_mma_shared_layout:
344371
layout = tle.swizzled_shared_layout.make_default(rank=len(shape))
345372
layout_handle = _semantic.builder.make_swizzled_shared_encoding_attr(
346373
layout.vectorSize,
@@ -383,9 +410,20 @@ def alloc(
383410
tensor_handle = _semantic.builder.create_local_alloc(mutable_ty, init_value.handle)
384411
else:
385412
tensor_handle = _semantic.builder.create_local_alloc(full_shape, elem_type, layout_handle)
413+
if mthreads_auto_sqmma_shared_layout:
414+
mthreads_wgmma.mark_auto_shared_layout(_semantic.builder, tensor_handle)
386415
else:
387416
raise ValueError(f"Storage type {storage} not yet supported")
388417

418+
if use_mthreads_buffer:
419+
return mthreads_buffer.create_buffered_tensor(
420+
tensor_handle,
421+
dtype,
422+
unwrapped_shape,
423+
storage,
424+
layout,
425+
_semantic,
426+
)
389427
return tle.buffered_tensor(tensor_handle, dtype, unwrapped_shape, storage, layout, _semantic)
390428

391429
except Exception as e:
@@ -773,16 +811,23 @@ def wgmma(
773811
"""
774812
trans_a = _require_wgmma_bool(trans_a, "trans_a")
775813
trans_b = _require_wgmma_bool(trans_b, "trans_b")
776-
a, b = _canonicalize_wgmma_operands(a, b, trans_a, trans_b, _semantic)
814+
mthreads_enabled = mthreads_common.enabled()
815+
if mthreads_enabled:
816+
mthreads_wgmma.validate_operands(a, b, acc, trans_a, trans_b)
817+
else:
818+
a, b = _canonicalize_wgmma_operands(a, b, trans_a, trans_b, _semantic)
777819

778820
m, k = [int(tl._unwrap_if_constexpr(dim)) for dim in a.type.shape]
779821
k_b, n = [int(tl._unwrap_if_constexpr(dim)) for dim in b.type.shape]
780822
if k != k_b:
781823
raise ValueError(f"wgmma shape mismatch: a is {a.type.shape}, b is {b.type.shape}")
782-
if m < 64 or m % 64 != 0:
783-
raise ValueError("wgmma result M dimension must be divisible by 64")
784-
if n < 8 or n % 8 != 0:
785-
raise ValueError("wgmma result N dimension must be divisible by 8")
824+
if mthreads_enabled:
825+
mthreads_wgmma.validate_dimensions(m, n)
826+
else:
827+
if m < 64 or m % 64 != 0:
828+
raise ValueError("wgmma result M dimension must be divisible by 64")
829+
if n < 8 or n % 8 != 0:
830+
raise ValueError("wgmma result N dimension must be divisible by 8")
786831
if k < 16:
787832
raise ValueError("wgmma K dimension must be at least 16")
788833

@@ -813,6 +858,9 @@ def wgmma(
813858
if max_num_imprecise_acc < 0:
814859
raise ValueError("max_num_imprecise_acc must be non-negative")
815860

861+
if mthreads_enabled:
862+
mthreads_wgmma.validate_options(max_num_imprecise_acc, out_dtype)
863+
816864
ret_scalar_ty = _wgmma_ret_scalar_ty(a.dtype, out_dtype)
817865
ret_ty = tl.block_type(ret_scalar_ty, [m, n])
818866
builder = _semantic.builder
@@ -851,6 +899,8 @@ def wgmma_wait(pendings, acc=None, _semantic=None, _generator=None) -> tl.tensor
851899
pendings = _require_wgmma_int(pendings, "pendings")
852900
if pendings < 0:
853901
raise ValueError("wgmma_wait pendings must be non-negative")
902+
if mthreads_common.enabled():
903+
mthreads_wgmma.validate_wait_pendings(pendings)
854904
if not isinstance(acc, tl.tensor):
855905
raise ValueError(f"wgmma_wait acc must be a tl.tensor, got {type(acc).__name__}")
856906
result = _semantic.builder.create_tle_wgmma_wait(acc.handle, pendings)
@@ -921,7 +971,7 @@ def copy(
921971
tle.copy(tma_desc, local_buf, [64, 64], [x_offset, y_offset], barrier=bar)
922972
tle.gpu.barrier_wait(bar, phaseIdx=0)
923973
"""
924-
mthreads_enabled = mthreads_copy.enabled()
974+
mthreads_enabled = mthreads_common.enabled()
925975
iluvatar_enabled = iluvatar_copy.enabled()
926976

927977
def normcopy(
@@ -1078,9 +1128,12 @@ def tmacopy(
10781128
raise ValueError("copy barrier is only supported for TMA global-to-shared copy")
10791129
return normcopy(src, dst, shape, direction, _semantic)
10801130
if mthreads_enabled:
1131+
barrier_slot = None
10811132
if barrier is not None:
1082-
raise ValueError("TMA copy barrier is only supported on NVIDIA backend")
1083-
return mthreads_copy.tmacopy(src, dst, direction, shape, offsets, _semantic)
1133+
if direction != CopyDirection.GM_TO_LOCAL:
1134+
raise ValueError("TMA copy barrier is only supported for global-to-shared TMA copy")
1135+
barrier_slot = _tma_completion_barrier_slot(barrier, _semantic)
1136+
return mthreads_copy.tmacopy(src, dst, direction, shape, offsets, barrier_slot, _semantic)
10841137
else:
10851138
return tmacopy(src, dst, direction, shape, offsets, barrier, _semantic)
10861139

python/triton/experimental/tle/language/gpu/mthreads/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1919
# SOFTWARE.
2020

21-
from . import copy
21+
from . import buffer, common, copy, pipe, warp_specialize, wgmma
2222

23-
__all__ = ["copy"]
23+
__all__ = ["buffer", "common", "copy", "pipe", "warp_specialize", "wgmma"]
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Copyright 2025- FlagOS Contributors
2+
#
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy
4+
# of this software and associated documentation files (the "Software"), to deal
5+
# in the Software without restriction, including without limitation the rights
6+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
# copies of the Software, and to permit persons to whom the Software is
8+
# furnished to do so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in all
11+
# copies or substantial portions of the Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
# SOFTWARE.
20+
21+
from math import prod
22+
23+
from triton._utils import TRITON_MAX_TENSOR_NUMEL, is_power_of_two
24+
25+
from .. import types as gpu_types
26+
27+
28+
def is_backend_builder(builder) -> bool:
29+
return hasattr(builder, "mark_musa_tle_auto_shared_layout")
30+
31+
32+
def needs_non_power_of_two_leading_dim(builder, shape) -> bool:
33+
if not is_backend_builder(builder) or not shape:
34+
return False
35+
return isinstance(shape[0], int) and shape[0] > 0 and not is_power_of_two(shape[0])
36+
37+
38+
def validate_shape(shape) -> tuple[int, ...]:
39+
shape = tuple(shape)
40+
if len(shape) < 2:
41+
raise ValueError("mthreads TLE non-power-of-two buffered tensor requires rank >= 2")
42+
for index, dim in enumerate(shape):
43+
if not isinstance(dim, int):
44+
raise TypeError(f"Shape element {index} must have type `constexpr[int]`, got `constexpr[{type(dim)}]")
45+
if dim <= 0:
46+
raise ValueError(f"Shape element {index} must be positive")
47+
if index > 0 and not is_power_of_two(dim):
48+
raise ValueError(f"Shape element {index} must be a power of 2")
49+
numel = prod(shape)
50+
if numel > TRITON_MAX_TENSOR_NUMEL:
51+
raise ValueError(f"numel ({numel}) exceeds triton maximum tensor numel ({TRITON_MAX_TENSOR_NUMEL})")
52+
return shape
53+
54+
55+
class buffered_tensor_type(gpu_types.buffered_tensor_type):
56+
57+
def __init__(self, element_ty, shape, storage, layout=None, semantic=None, alloc_shape=None):
58+
shape = validate_shape(shape)
59+
self.element_ty = element_ty
60+
self.shape = shape
61+
self.numel = prod(shape)
62+
self.name = f"<{self.shape}, {self.element_ty}>"
63+
self.storage = storage
64+
self.layout = layout
65+
self.alloc_shape = list(shape if alloc_shape is None else alloc_shape)
66+
assert semantic, "buffered_tensor array must be created with a builder"
67+
self.semantic = semantic
68+
69+
def _unflatten_ir(self, handles, cursor):
70+
value = buffered_tensor(
71+
handles[cursor],
72+
self.scalar,
73+
self.shape,
74+
self.storage,
75+
self.layout,
76+
self.semantic,
77+
alloc_shape=self.alloc_shape,
78+
)
79+
if hasattr(self, "_tle_remote_shard_id"):
80+
shard_id = getattr(self, "_tle_remote_shard_id")
81+
scope = getattr(self, "_tle_remote_scope", None)
82+
setattr(value, "_tle_remote_shard_id", shard_id)
83+
setattr(value, "_tle_remote_scope", scope)
84+
setattr(value.type, "_tle_remote_shard_id", shard_id)
85+
setattr(value.type, "_tle_remote_scope", scope)
86+
return value, cursor + 1
87+
88+
def with_element_ty(self, scalar_ty):
89+
return buffered_tensor_type(
90+
scalar_ty,
91+
self.shape,
92+
self.storage,
93+
self.layout,
94+
self.semantic,
95+
alloc_shape=self.alloc_shape,
96+
)
97+
98+
99+
class buffered_tensor(gpu_types.buffered_tensor):
100+
101+
def __init__(self, handle, element_ty, shape, storage, layout=None, semantic=None, alloc_shape=None):
102+
self.handle = handle
103+
self.shape = list(shape)
104+
self.type = buffered_tensor_type(
105+
element_ty,
106+
shape,
107+
storage,
108+
layout,
109+
semantic,
110+
alloc_shape=alloc_shape,
111+
)
112+
self.dtype = element_ty
113+
114+
115+
def create_buffered_tensor(handle, element_ty, shape, storage, layout, semantic, alloc_shape=None):
116+
return buffered_tensor(
117+
handle,
118+
element_ty,
119+
shape,
120+
storage,
121+
layout,
122+
semantic,
123+
alloc_shape=alloc_shape,
124+
)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2025- FlagOS Contributors
2+
#
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy
4+
# of this software and associated documentation files (the "Software"), to deal
5+
# in the Software without restriction, including without limitation the rights
6+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
# copies of the Software, and to permit persons to whom the Software is
8+
# furnished to do so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in all
11+
# copies or substantial portions of the Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
# SOFTWARE.
20+
21+
import os
22+
23+
try:
24+
from triton._flagtree_backend import FLAGTREE_BACKEND
25+
except ModuleNotFoundError:
26+
FLAGTREE_BACKEND = os.environ.get("FLAGTREE_BACKEND", "")
27+
28+
29+
def _has_mthreads_libtriton() -> bool:
30+
try:
31+
from triton._C import libtriton
32+
except ImportError:
33+
return False
34+
return hasattr(libtriton, "mthreads")
35+
36+
37+
def enabled() -> bool:
38+
return FLAGTREE_BACKEND == "mthreads" or _has_mthreads_libtriton()

0 commit comments

Comments
 (0)