Skip to content

Commit 362d9e0

Browse files
authored
[Feature] Add float4_e2m1_unpacked dtype (#2271)
* Add float4_e2m1_unpacked dtype * Fix TIR cast for float4_e2m1_unpacked dtype. Route custom[float4_e2m1_unpacked] through Float4E2M1Unpacked FFI so T.float4_e2m1_unpacked(0.0) works in prim_func builders. * Limit float4_e2m1_unpacked vector lanes and fix TMA transaction bytes. Cap unpacked FP4 vector dtypes at x32 and count TMA/mbarrier transactions using 4-bit payload size (0.5 byte per element). * lint
1 parent 693f5e1 commit 362d9e0

4 files changed

Lines changed: 186 additions & 9 deletions

File tree

src/backend/cuda/op/copy.cc

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,17 @@ PrimExpr MakeTmaLeaderCondition(PrimExpr thread_extent) {
4242
return Call(DataType::Bool(), tl_shuffle_elect(), {std::move(thread_extent)});
4343
}
4444

45-
PrimExpr TMABytesFromElements(PrimExpr elements, DataType dtype) {
45+
int TMATransactionElementBits(DataType dtype) {
46+
// float4_e2m1_unpacked stores one logical FP4 value per 8-bit SMEM slot,
47+
// but TMA/mbarrier transaction counts reflect the moved FP4 payload (4b).
48+
if (dtype.is_float4_e2m1_unpacked()) {
49+
return 4;
50+
}
51+
return dtype.bits();
52+
}
53+
54+
PrimExpr TMABytesFromElements(PrimExpr elements, DataType dtype, int bits) {
4655
PrimExpr elements_i64 = cast(DataType::Int(64), elements);
47-
int bits = dtype.bits();
4856
if (bits % 8 == 0) {
4957
return elements_i64 * IntImm(DataType::Int(64), bits / 8);
5058
}
@@ -53,8 +61,26 @@ PrimExpr TMABytesFromElements(PrimExpr elements, DataType dtype) {
5361
IntImm(DataType::Int(64), 8));
5462
}
5563

64+
int64_t TMABytesFromElements(int64_t elements, DataType dtype, int bits) {
65+
return (elements * bits + 7) / 8;
66+
}
67+
68+
PrimExpr TMABytesFromElements(PrimExpr elements, DataType dtype) {
69+
return TMABytesFromElements(elements, dtype, dtype.bits());
70+
}
71+
5672
int64_t TMABytesFromElements(int64_t elements, DataType dtype) {
57-
return (elements * dtype.bits() + 7) / 8;
73+
return TMABytesFromElements(elements, dtype, dtype.bits());
74+
}
75+
76+
PrimExpr TMATransactionBytesFromElements(PrimExpr elements, DataType dtype) {
77+
return TMABytesFromElements(elements, dtype,
78+
TMATransactionElementBits(dtype));
79+
}
80+
81+
int64_t TMATransactionBytesFromElements(int64_t elements, DataType dtype) {
82+
return TMABytesFromElements(elements, dtype,
83+
TMATransactionElementBits(dtype));
5884
}
5985

6086
int64_t TMAElementsForBytes(int64_t bytes, DataType dtype) {
@@ -1656,10 +1682,11 @@ Stmt Copy::LowerBulk(const CopyNode &op, const LowerArgs &T,
16561682
PrimExpr total_bytes;
16571683
if ((*inner_box_dim) != instruction_dim) {
16581684
int loop_extent = (*inner_box_dim) / instruction_dim;
1659-
total_bytes = TMABytesFromElements(total_elements * loop_extent,
1660-
shared_tensor->dtype);
1685+
total_bytes = TMATransactionBytesFromElements(
1686+
total_elements * loop_extent, shared_tensor->dtype);
16611687
} else {
1662-
total_bytes = TMABytesFromElements(total_elements, shared_tensor->dtype);
1688+
total_bytes =
1689+
TMATransactionBytesFromElements(total_elements, shared_tensor->dtype);
16631690
}
16641691

16651692
Stmt barrier_before_tma_stmt;
@@ -1959,7 +1986,8 @@ Stmt Copy::LowerBulk1D(const CopyNode &op, const LowerArgs &T,
19591986
}
19601987

19611988
Stmt tma_copy;
1962-
PrimExpr total_bytes = TMABytesFromElements(elements, shared_tensor->dtype);
1989+
PrimExpr total_bytes =
1990+
TMATransactionBytesFromElements(elements, shared_tensor->dtype);
19631991
if (is_load) {
19641992
PrimExpr mbar_arg = barrier_base_id >= 0 ? mbar_handle : PrimExpr(0);
19651993
tma_copy = Evaluate(Call(DataType::Handle(), tma_load(),
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""Tests for float4_e2m1_unpacked (CUTLASS float_e2m1_unpacksmem_t)."""
2+
3+
import tilelang.language as T
4+
from tilelang import tvm as tvm
5+
6+
7+
def test_float4_e2m1_unpacked_dtype_properties():
8+
dt = T.float4_e2m1_unpacked
9+
assert dt.bits == 8
10+
assert int(dt.type_code) == 131
11+
assert str(dt) == "custom[float4_e2m1_unpacked]8"
12+
assert dt.lanes == 1
13+
14+
15+
def test_float4_e2m1_unpacked_vector_lanes():
16+
dt2 = T.float4_e2m1_unpackedx2
17+
assert dt2.bits == 8
18+
assert int(dt2.type_code) == 131
19+
assert dt2.lanes == 2
20+
21+
22+
def test_float4_e2m1_unpacked_tir_cast():
23+
from tilelang import language as T_lang
24+
25+
@T_lang.prim_func
26+
def main():
27+
T_lang.evaluate(T_lang.float4_e2m1_unpacked(0.0))
28+
29+
assert main.body.value.dtype.is_float4_e2m1_unpacked()
30+
31+
32+
def test_float4_e2m1_unpacked_distinct_from_packed():
33+
packed = T.float4_e2m1fn
34+
unpacked = T.float4_e2m1_unpacked
35+
assert packed.bits == 4
36+
assert unpacked.bits == 8
37+
assert packed != unpacked
38+
39+
40+
def test_float4_e2m1_unpacked_dtype_helpers():
41+
from tilelang.language.dtypes import is_float4, is_float4_e2m1fn, is_float4_e2m1_unpacked
42+
43+
packed = T.float4_e2m1fn
44+
unpacked = T.float4_e2m1_unpacked
45+
assert packed.is_float4_e2m1fn()
46+
assert not packed.is_float4_e2m1_unpacked()
47+
assert packed.is_float4()
48+
assert unpacked.is_float4_e2m1_unpacked()
49+
assert not unpacked.is_float4_e2m1fn()
50+
assert unpacked.is_float4()
51+
assert T.float4_e2m1fnx2.is_float4()
52+
assert T.float4_e2m1_unpackedx4.is_float4()
53+
assert is_float4_e2m1fn(packed)
54+
assert is_float4_e2m1_unpacked(unpacked)
55+
assert is_float4("float4_e2m1fn")
56+
assert is_float4("custom[float4_e2m1_unpacked]8")
57+
assert not is_float4(T.float16)
58+
59+
60+
if __name__ == "__main__":
61+
test_float4_e2m1_unpacked_dtype_properties()
62+
test_float4_e2m1_unpacked_vector_lanes()
63+
test_float4_e2m1_unpacked_tir_cast()
64+
test_float4_e2m1_unpacked_distinct_from_packed()
65+
test_float4_e2m1_unpacked_dtype_helpers()
66+
print("ok")

tilelang/language/copy_op.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,8 +402,12 @@ def tma_gather4_bytes(K_box, dtype: str) -> int:
402402
"""
403403
if dtype not in _TMA_SUPPORTED_DTYPES:
404404
raise ValueError(f"Unsupported dtype: {dtype}")
405-
elem_bytes = tvm.DataType(dtype).bits // 8
406-
return 4 * K_box * elem_bytes
405+
dt = tvm.DataType(dtype)
406+
if dt.is_float4_e2m1_unpacked():
407+
elem_bits = 4
408+
else:
409+
elem_bits = dt.bits
410+
return (4 * K_box * elem_bits + 7) // 8
407411

408412

409413
def tma_scatter4(

tilelang/language/dtypes.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ def bits(self) -> int: ...
1717
@property
1818
def bytes(self) -> int: ...
1919
def as_torch(self) -> torch.dtype: ...
20+
def is_float4_e2m1fn(self) -> bool: ...
21+
def is_float4_e2m1_unpacked(self) -> bool: ...
22+
def is_float4(self) -> bool: ...
2023
else:
2124
dtype = tvm.DataType
2225

@@ -163,6 +166,10 @@ def __dtype_call__(self: dtype, *args, is_size_var: bool = False) -> tirx.Var:
163166
val = "BFloat" + self[6:]
164167
elif self.startswith("tfloat"):
165168
val = "TensorFloat" + self[6:]
169+
elif self.is_float4_e2m1_unpacked():
170+
val = "Float4E2M1Unpacked"
171+
if self.lanes > 1:
172+
val += f"x{self.lanes}"
166173
else:
167174
raise TypeError(f"Invalid type {self}")
168175
if "_" in val:
@@ -246,10 +253,61 @@ def __dtype_bytes__(self: dtype) -> int:
246253
return self.itemsize
247254

248255

256+
_FLOAT4_E2M1FN_BITS = 4
257+
_FLOAT4_E2M1FN_TYPE_CODE = 17
258+
_FLOAT4_E2M1_UNPACKED_BITS = 8
259+
_FLOAT4_E2M1_UNPACKED_TYPE_CODE = 131
260+
261+
262+
def __dtype_is_float4_e2m1fn__(self: dtype) -> bool:
263+
"""Packed 4-bit FP4 E2M1 (CUTLASS float_e2m1_t).
264+
265+
Use for pure FP4 workloads on tcgen05 mxf4 / mxf4nvf4 (packed SMEM, half
266+
the footprint). Global tensors and packed SMEM roundtrips use this dtype.
267+
TMA: ``CU_TENSOR_MAP_DATA_TYPE_16U4_ALIGN8B``.
268+
"""
269+
return self.bits == _FLOAT4_E2M1FN_BITS and int(self.type_code) == _FLOAT4_E2M1FN_TYPE_CODE
270+
271+
272+
def __dtype_is_float4_e2m1_unpacked__(self: dtype) -> bool:
273+
"""8-bit FP4 E2M1 unpacked storage (CUTLASS float_e2m1_unpacksmem_t).
274+
275+
Only for tcgen05 ``kind::f8f6f4`` and block-scaled ``kind::mxf8f6f4``
276+
mixed-precision paths where sub-byte types share 16-byte SMEM/TMEM padding
277+
(one byte per FP4 element). Not for mxf4 / mxf4nvf4 packed FP4 kernels.
278+
Pair with ``T.tma_copy`` from packed global FP4; TMA:
279+
``CU_TENSOR_MAP_DATA_TYPE_16U4_ALIGN16B``.
280+
"""
281+
return self.bits == _FLOAT4_E2M1_UNPACKED_BITS and int(self.type_code) == _FLOAT4_E2M1_UNPACKED_TYPE_CODE
282+
283+
284+
def __dtype_is_float4__(self: dtype) -> bool:
285+
"""Whether this is any FP4 E2M1 logical variant (packed or unpacked storage)."""
286+
return __dtype_is_float4_e2m1fn__(self) or __dtype_is_float4_e2m1_unpacked__(self)
287+
288+
289+
def is_float4_e2m1fn(value: AnyDType) -> bool:
290+
"""Whether *value* is the packed 4-bit FP4 E2M1 variant."""
291+
return get_tvm_dtype(value).is_float4_e2m1fn()
292+
293+
294+
def is_float4_e2m1_unpacked(value: AnyDType) -> bool:
295+
"""Whether *value* is the 8-bit FP4 E2M1 unpacked shared-memory storage variant."""
296+
return get_tvm_dtype(value).is_float4_e2m1_unpacked()
297+
298+
299+
def is_float4(value: AnyDType) -> bool:
300+
"""Whether *value* is any FP4 E2M1 logical variant (packed or unpacked)."""
301+
return get_tvm_dtype(value).is_float4()
302+
303+
249304
dtype.__call__ = __dtype_call__
250305
dtype.__new__ = __dtype_new__
251306
dtype.as_torch = __dtype_as_torch__
252307
dtype.bytes = property(__dtype_bytes__)
308+
dtype.is_float4_e2m1fn = __dtype_is_float4_e2m1fn__
309+
dtype.is_float4_e2m1_unpacked = __dtype_is_float4_e2m1_unpacked__
310+
dtype.is_float4 = __dtype_is_float4__
253311

254312

255313
def get_tvm_dtype(value: AnyDType) -> dtype:
@@ -423,6 +481,12 @@ class float4_e2m1fnx8(dtype): ...
423481
class float4_e2m1fnx16(dtype): ...
424482
class float4_e2m1fnx32(dtype): ...
425483
class float4_e2m1fnx64(dtype): ...
484+
class float4_e2m1_unpacked(dtype): ...
485+
class float4_e2m1_unpackedx2(dtype): ...
486+
class float4_e2m1_unpackedx4(dtype): ...
487+
class float4_e2m1_unpackedx8(dtype): ...
488+
class float4_e2m1_unpackedx16(dtype): ...
489+
class float4_e2m1_unpackedx32(dtype): ...
426490
class bfloat16(dtype): ...
427491
class bfloat16x2(dtype): ...
428492
class tfloat32(dtype): ...
@@ -599,6 +663,12 @@ class tfloat32x64(dtype): ...
599663
float4_e2m1fnx16 = dtype("float4_e2m1fnx16")
600664
float4_e2m1fnx32 = dtype("float4_e2m1fnx32")
601665
float4_e2m1fnx64 = dtype("float4_e2m1fnx64")
666+
float4_e2m1_unpacked = dtype("custom[float4_e2m1_unpacked]8")
667+
float4_e2m1_unpackedx2 = dtype("custom[float4_e2m1_unpacked]8x2")
668+
float4_e2m1_unpackedx4 = dtype("custom[float4_e2m1_unpacked]8x4")
669+
float4_e2m1_unpackedx8 = dtype("custom[float4_e2m1_unpacked]8x8")
670+
float4_e2m1_unpackedx16 = dtype("custom[float4_e2m1_unpacked]8x16")
671+
float4_e2m1_unpackedx32 = dtype("custom[float4_e2m1_unpacked]8x32")
602672
bfloat16 = dtype("bfloat16")
603673
bfloat16x2 = dtype("bfloat16x2")
604674
tfloat32 = dtype("custom[tfloat32]")
@@ -773,6 +843,12 @@ class tfloat32x64(dtype): ...
773843
"float4_e2m1fnx16",
774844
"float4_e2m1fnx32",
775845
"float4_e2m1fnx64",
846+
"float4_e2m1_unpacked",
847+
"float4_e2m1_unpackedx2",
848+
"float4_e2m1_unpackedx4",
849+
"float4_e2m1_unpackedx8",
850+
"float4_e2m1_unpackedx16",
851+
"float4_e2m1_unpackedx32",
776852
"bfloat16",
777853
"bfloat16x2",
778854
"tfloat32",
@@ -788,4 +864,7 @@ class tfloat32x64(dtype): ...
788864
"dtype",
789865
"AnyDType",
790866
"get_tvm_dtype",
867+
"is_float4_e2m1fn",
868+
"is_float4_e2m1_unpacked",
869+
"is_float4",
791870
]

0 commit comments

Comments
 (0)