Skip to content

Commit d817794

Browse files
Support non-power-of-two TLE pipe capacities
1 parent df824d7 commit d817794

5 files changed

Lines changed: 137 additions & 10 deletions

File tree

python/test/tle/unit/test_tle.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,40 @@ def test_buffered_tensor_type_attributes(self):
275275
assert hasattr(tle.gpu.buffered_tensor, 'make_permute')
276276
assert hasattr(tle.gpu.buffered_tensor, 'slot')
277277

278+
def test_memory_descriptor_shape_is_not_a_register_block_shape(self):
279+
"""Memory descriptors accept pipeline capacities that register blocks reject."""
280+
buffer, _ = self._make_buffer([6, 1, 64, 128])
281+
282+
assert buffer.type.shape == (6, 1, 64, 128)
283+
assert buffer.type.numel == 6 * 64 * 128
284+
assert not buffer.type.is_block()
285+
with pytest.raises(ValueError):
286+
tl.block_type(tl.float16, [6, 1, 64, 128])
287+
288+
def test_memory_descriptor_shape_still_rejects_invalid_dimensions(self):
289+
with pytest.raises(ValueError, match="must be positive"):
290+
self._make_buffer([0, 16])
291+
with pytest.raises(TypeError, match="must be an integer"):
292+
self._make_buffer(["6", 16])
293+
294+
def test_buffered_tensor_type_equality_includes_dtype_and_storage(self):
295+
buffer, semantic = self._make_buffer([6, 16])
296+
layout = buffer.type.layout
297+
fp32_type = tle.gpu.buffered_tensor_type(tl.float32, [6, 16], tle.gpu.smem, layout, semantic)
298+
tmem_type = tle.gpu.buffered_tensor_type(tl.float16, [6, 16], tle.gpu.tmem, layout, semantic)
299+
300+
assert buffer.type != fp32_type
301+
assert buffer.type != tmem_type
302+
303+
def test_barrier_descriptor_accepts_non_power_of_two_capacity(self):
304+
semantic = self._FakeSemantic()
305+
layout = tle.gpu.swizzled_shared_layout.make_default(2)
306+
barrier_type = tle.gpu.barrier_type(6, 1, "all", None, layout, semantic)
307+
308+
assert barrier_type.shape == (6, 1)
309+
assert barrier_type.numel == 6
310+
assert not barrier_type.is_block()
311+
278312
def test_buffered_tensor_slot_indexes_leading_dimension(self):
279313
"""slot(stage) returns a typed view with the leading stage dimension removed."""
280314
buffer, semantic = self._make_buffer([4, 16, 32])

python/test/tle/unit/test_tle_gpu_slot.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ def _slot_local_ptr_store_kernel(out_ptr, BLOCK: tl.constexpr):
4141
tl.store(out_ptr + idx, vals)
4242

4343

44+
@triton.jit
45+
def _non_power_of_two_stage_slot_kernel(out_ptr, BLOCK: tl.constexpr):
46+
idx = tl.arange(0, BLOCK)
47+
smem = tle.gpu.alloc([6, BLOCK], dtype=tl.int32, layout=None, scope=tle.gpu.smem, nv_mma_shared_layout=False)
48+
slot = smem.slot(5)
49+
ptrs = tle.gpu.local_ptr(slot, (idx, ))
50+
tl.store(ptrs, idx + 11)
51+
tl.store(out_ptr + idx, tl.load(ptrs))
52+
53+
4454
def test_buffered_tensor_slot_lowers_to_memdesc_index_and_executes():
4555
block = 64
4656
out = torch.empty((block, ), device="cuda", dtype=torch.int32)
@@ -54,3 +64,17 @@ def test_buffered_tensor_slot_lowers_to_memdesc_index_and_executes():
5464
_slot_local_ptr_store_kernel[(1, )](out, BLOCK=block, num_warps=4)
5565
expected = torch.arange(0, block, device="cuda", dtype=torch.int32) + 7
5666
torch.testing.assert_close(out, expected, atol=0, rtol=0)
67+
68+
69+
def test_non_power_of_two_stage_descriptor_lowers_and_executes():
70+
block = 64
71+
out = torch.empty((block, ), device="cuda", dtype=torch.int32)
72+
73+
compiled = _non_power_of_two_stage_slot_kernel.warmup(out, BLOCK=block, grid=(1, ), num_warps=4)
74+
ttgir = compiled.asm["ttgir"]
75+
assert "ttg.memdesc_index" in ttgir
76+
assert "!ttg.memdesc<6x64xi32" in ttgir
77+
78+
_non_power_of_two_stage_slot_kernel[(1, )](out, BLOCK=block, num_warps=4)
79+
expected = torch.arange(0, block, device="cuda", dtype=torch.int32) + 11
80+
torch.testing.assert_close(out, expected, atol=0, rtol=0)

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

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,50 @@ def to_ir(self, builder: ir.builder) -> None:
4949
READY = "ready"
5050

5151

52+
class memory_descriptor_type(tl.base_type):
53+
"""Base type for memory-backed descriptors with an arbitrary static shape.
54+
55+
TLE memory descriptors are not register-resident Triton blocks. In
56+
particular, pipeline capacities such as 3 or 6 are valid leading
57+
dimensions, and descriptor allocation sizes are governed by the target
58+
memory space rather than ``TRITON_MAX_TENSOR_NUMEL``.
59+
"""
60+
61+
def __init__(self, element_ty: tl.dtype, shape: List):
62+
if not isinstance(element_ty, tl.dtype):
63+
raise TypeError(f"element_ty has type `{type(element_ty).__name__}`; expected `dtype`.")
64+
if not isinstance(shape, (list, tuple)) or not shape:
65+
raise TypeError("memory descriptor shape must be a non-empty list or tuple")
66+
67+
normalized_shape = []
68+
for index, dim in enumerate(shape):
69+
dim = tl._unwrap_if_constexpr(dim)
70+
if not isinstance(dim, int):
71+
raise TypeError(f"Shape element {index} must be an integer, got {type(dim).__name__}")
72+
if dim <= 0:
73+
raise ValueError(f"Shape element {index} must be positive, got {dim}")
74+
normalized_shape.append(dim)
75+
76+
self.element_ty = element_ty
77+
self.shape = tuple(normalized_shape)
78+
self.numel = 1
79+
for dim in self.shape:
80+
self.numel *= dim
81+
self.name = f'<{self.shape}, {self.element_ty}>'
82+
83+
@staticmethod
84+
def is_block():
85+
return False
86+
87+
@property
88+
def scalar(self):
89+
return self.element_ty
90+
91+
@property
92+
def nbytes(self):
93+
return self.numel * (self.element_ty.primitive_bitwidth // 8)
94+
95+
5296
def _storage_to_memdesc_space(storage: scope) -> str:
5397
if storage is smem:
5498
return "smem"
@@ -356,7 +400,7 @@ def make_permute(self, handle, dims):
356400
)
357401

358402

359-
class buffered_tensor_type(tl.block_type):
403+
class buffered_tensor_type(memory_descriptor_type):
360404

361405
def __init__(self, element_ty: tl.dtype, shape: List, storage: scope, layout: Optional[shared_layout] = None,
362406
semantic: TritonSemantic = None, alloc_shape: List = None):
@@ -401,8 +445,13 @@ def mangle(self) -> str:
401445
def __str__(self) -> str:
402446
return f"buffered_tensor_<{self.element_ty}, {self.shape}, {self.layout}, {self.alloc_shape}, >"
403447

448+
def with_element_ty(self, scalar_ty: tl.dtype):
449+
return buffered_tensor_type(scalar_ty, self.shape, self.storage, self.layout, self.semantic,
450+
alloc_shape=self.alloc_shape)
451+
404452
def __eq__(self, other) -> bool:
405-
if not (type(self) is type(other) and self.shape == other.shape and self.layout == other.layout
453+
if not (type(self) is type(other) and self.element_ty == other.element_ty and self.shape == other.shape
454+
and self.storage is other.storage and self.layout == other.layout
406455
and self.alloc_shape == other.alloc_shape):
407456
return False
408457
self_shard = getattr(self, "_tle_remote_shard_id", None)
@@ -515,7 +564,7 @@ def __getitem__(self, index, _semantic=None):
515564
allocation_key=self.allocation_key)
516565

517566

518-
class barrier_type(tl.block_type):
567+
class barrier_type(memory_descriptor_type):
519568

520569
def __init__(
521570
self,

third_party/tle/dialect/lib/Transforms/TleLowerPipeToNvws.cpp

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,14 +1237,22 @@ static PipeState createPipeState(PipeCreateOp op) {
12371237
ttg::MemDescType::get({1}, builder.getI32Type(), closeTagSlotEncoding,
12381238
sharedMemorySpace, /*mutableMemory=*/true);
12391239

1240-
RankedTensorType closeTagArrayTensorType =
1241-
getCloseTagTensorType(op, builder, {capacity, 1});
1242-
Value initialCloseTags =
1243-
createCloseTagTensor(builder, loc, closeTagArrayTensorType,
1244-
/*value=*/false);
1245-
closeTags = ttg::LocalAllocOp::create(builder, loc, closeTagArrayType,
1246-
initialCloseTags);
12471240
closeTagTensorType = getCloseTagTensorType(op, builder, {1});
1241+
closeTags =
1242+
ttg::LocalAllocOp::create(builder, loc, closeTagArrayType, Value());
1243+
// A pipe capacity describes shared-memory slots, not a distributed
1244+
// register tensor. Initializing the whole ring through a
1245+
// tensor<capacity x 1> would therefore reintroduce Triton's power-of-two
1246+
// register-block restriction for otherwise valid capacities such as 3 or
1247+
// 6. Initialize one scalar memdesc slot at a time instead.
1248+
for (int64_t stage = 0; stage < capacity; ++stage) {
1249+
Value stageValue = arith::ConstantIntOp::create(builder, loc, stage, 32);
1250+
Value slot = ttg::MemDescIndexOp::create(builder, loc, closeTagSlotType,
1251+
closeTags, stageValue);
1252+
Value tag = createCloseTagTensor(builder, loc, closeTagTensorType,
1253+
/*value=*/false);
1254+
ttg::LocalStoreOp::create(builder, loc, tag, slot);
1255+
}
12481256
}
12491257
Value token = ttnvws::CreateTokenOp::create(
12501258
builder, loc, static_cast<uint32_t>(capacity),

third_party/tle/test/GPU/test_tle_lower_pipe_to_nvws.mlir

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@
2525
#smem = #ttg.shared_memory
2626

2727
module attributes {"ttg.num-ctas" = 1 : i32, "ttg.num-warps" = 4 : i32, "ttg.threads-per-warp" = 32 : i32} {
28+
// CHECK-LABEL: tt.func @lower_non_power_of_two_pipe_capacity
29+
tt.func @lower_non_power_of_two_pipe_capacity(%a: !ttg.memdesc<6x16xf16, #shared, #smem, mutable>) {
30+
// CHECK: %[[TAGS:.*]] = ttg.local_alloc
31+
// CHECK-SAME: !ttg.memdesc<6x1xi32
32+
// CHECK-NOT: tensor<6x1xi32
33+
// CHECK-COUNT-6: ttg.local_store
34+
// CHECK: nvws.create_token
35+
// CHECK-SAME: numBuffers = 6
36+
tle.pipe.create %a {capacity = 6 : i32, pipe_name = "six_stage", field_names = ["a"], scope = "cta"} : !ttg.memdesc<6x16xf16, #shared, #smem, mutable>
37+
tt.return
38+
}
39+
2840
// CHECK-LABEL: tt.func @lower_pipe_to_nvws
2941
tt.func @lower_pipe_to_nvws(%a: !ttg.memdesc<2x16xf16, #shared, #smem, mutable>) {
3042
%c0 = arith.constant 0 : i32

0 commit comments

Comments
 (0)