Skip to content

Commit 3c03e97

Browse files
[Dialect] Add FP8 support to GFX120X WMMA atom (#1074)
* [Dialect] Add FP8 support to GFX120X WMMA atom * [Dialect] Support BF8 combinations in GFX120X WMMA Signed-off-by: big_yellow_duck <83417790+big-yellow-duck@users.noreply.github.com> --------- Signed-off-by: big_yellow_duck <83417790+big-yellow-duck@users.noreply.github.com>
1 parent 15553b3 commit 3c03e97

9 files changed

Lines changed: 157 additions & 52 deletions

File tree

docs/api/dsl.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ AMD-specific operations for ROCm:
192192
- **fx.rocdl.make_buffer_tensor(tensor)** -- create buffer resource from tensor (CDNA buffer copy)
193193
- **fx.rocdl.BufferCopy32b** / **BufferCopy128b** -- buffer copy instruction atoms
194194
- **fx.rocdl.MFMA(m, n, k, elem_ty_ab, elem_ty_acc=None)** -- MFMA instruction atom constructor (CDNA3/CDNA4; 4th arg is the A/B element type; accumulator defaults to f32)
195-
- **fx.rocdl.WMMA(m, n, k, elem_ty_ab, elem_ty_acc=None, \*\*kwargs)** -- WMMA MMA atom constructor (arch-dispatched: gfx11 / gfx120x / gfx1250). gfx1250 supports f32(K4), f16/bf16(K32), fp8/bf8(K64/128), i8(K64), i4(K32); integer paths take ``sign_a`` / ``sign_b`` / ``clamp``. gfx120x (RDNA4) supports 16x16x16 f16/bf16 only, on the v8 operand ABI
195+
- **fx.rocdl.WMMA(m, n, k, elem_ty_ab, elem_ty_acc=None, \*\*kwargs)** -- WMMA MMA atom constructor (arch-dispatched: gfx11 / gfx120x / gfx1250). ``elem_ty_b`` optionally selects a different B operand type. gfx1250 supports f32(K4), f16/bf16(K32), fp8/bf8(K64/128), i8(K64), i4(K32); integer paths take ``sign_a`` / ``sign_b`` / ``clamp``. gfx120x (RDNA4) supports 16x16x16 f16/bf16 and every fp8(E4M3FN)/bf8(E5M2) A/B combination to f32, on the v8 operand ABI
196196
- **fx.rocdl.WMMAScale(m, n, k, elem_ty_a, elem_ty_b=None, elem_ty_acc=None, \*, opsel_a=0, opsel_b=0, mod_c=0, reuse_a=False, reuse_b=False, block_size=32)** -- gfx1250 MX-scaled WMMA (E8M0 block scale, f8/f6/f4; ``16x16x128`` or ``32x16x128`` fp4-only). Per-operand scales are atom state (``scale_a`` / ``scale_b``)
197197
- **fx.rocdl.make_tdm_atom(tensor, tensor_extents, strides=None, \*, num_warps, ...)** -- build a gfx1250 TDM (Tensor Data Mover) async Global↔LDS whole-tile copy atom (rank 1-5); the global base comes from the ``copy_atom_call`` operand pointer, while the per-dim extent (OOB), stride, ``imm_offset``, and MCAST ``workgroup_mask`` are atom state. ``fx.rocdl.TDM(rank, num_warps, ...)`` builds the atom type only. Advance the K-loop tile with ``fx.copy(atom, gt, dst, imm_offset=...)``
198198
- **fx.rocdl.sched_mfma(cnt)** -- insert MFMA scheduling barrier

docs/kernel_authoring_guide.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -338,13 +338,19 @@ mma = fx.make_mma_atom(rocdl.WMMA(16, 16, 32, T.i4, T.i32, sign_a=True, sign_b=T
338338
```
339339

340340
On RDNA4 (`gfx1200` / `gfx1201`) the same factory builds `MmaOpGFX120X_WMMAType`
341-
instead. RDNA4 shares the v8 register ABI but keeps the gfx11 instruction
342-
shapes, so the only valid form is `16x16x16` f16/bf16 → f32 — the 16x16x32 and
343-
fp8 K=64/128 shapes above are gfx1250-only and are rejected by the atom's
344-
verifier. See `kernels/gemm/rdna_f16_gemm.py` for a full pipelined example.
341+
instead. RDNA4 shares the v8 register ABI, while its supported floating-point
342+
forms use `16x16x16`: f16/bf16 and every fp8(E4M3FN)/bf8(E5M2) A/B combination
343+
accumulating to f32. Pass a different B type with the keyword-only `elem_ty_b`;
344+
it defaults to the A type. The 16x16x32 BF16 and fp8 K=64/128 forms above are
345+
gfx1250-only and are rejected by the atom's verifier. See
346+
`kernels/gemm/rdna_f16_gemm.py` for a full pipelined f16 example and
347+
`kernels/gemm/rdna4_fp8_blockscale.py` for raw FP8 operands.
345348

346349
```python
347350
mma = fx.make_mma_atom(rocdl.WMMA(16, 16, 16, fx.BFloat16, fx.Float32)) # RDNA4
351+
mma = fx.make_mma_atom(rocdl.WMMA(16, 16, 16, fx.Float8E4M3FN, fx.Float32)) # RDNA4 FP8
352+
mma = fx.make_mma_atom(rocdl.WMMA(16, 16, 16, fx.Float8E4M3FN, fx.Float32,
353+
elem_ty_b=fx.Float8E5M2)) # RDNA4 FP8 x BF8
348354
```
349355

350356
**MX-scaled WMMA** — `rocdl.WMMAScale(m, n, k, elem_ty_a, elem_ty_b=None,

include/flydsl/Dialect/FlyROCDL/IR/MmaAtom.td

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,11 @@ def FlyROCDL_MmaOpGFX11_WMMA : FlyROCDL_MmaOp<"MmaOpGFX11_WMMA", "gfx11.wmma", [
216216
//===----------------------------------------------------------------------===//
217217
//
218218
// RDNA4 shares the v8-operand WMMA register ABI with gfx1250 (each lane holds
219-
// K/2 A/B elements, accumulator rows blocked 8-per-lane-half) but keeps the
220-
// gfx11 16x16x16 instruction shapes and the 3-operand ROCDL intrinsic form.
221-
// gfx1250 is a different target: it has 16x16x32 and the mods/reuse operand
222-
// form, so it keeps its own `gfx1250.wmma` atom. Hence `gfx120x` rather than
223-
// `gfx12` in the name — this atom covers gfx1200 / gfx1201 only.
219+
// K/2 A/B elements, accumulator rows blocked 8-per-lane-half) but its supported
220+
// floating-point forms use 16x16x16 and the 3-operand ROCDL intrinsic form.
221+
// gfx1250 uses different floating-point shapes and the mods/reuse operand form,
222+
// so it keeps its own `gfx1250.wmma` atom. Hence `gfx120x` rather than `gfx12`
223+
// in the name — this atom covers gfx1200 / gfx1201 only.
224224

225225
def FlyROCDL_MmaOpGFX120X_WMMA : FlyROCDL_MmaOp<"MmaOpGFX120X_WMMA", "gfx120x.wmma", []> {
226226
let parameters = (ins
@@ -231,7 +231,7 @@ def FlyROCDL_MmaOpGFX120X_WMMA : FlyROCDL_MmaOp<"MmaOpGFX120X_WMMA", "gfx120x.wm
231231
"Type":$elemTyB,
232232
"Type":$elemTyAcc,
233233
// Integer-WMMA controls, forwarded to the ROCDL iu8/iu4 intrinsic. Ignored
234-
// (and required to be false) on the fp16/bf16 paths. Always printed.
234+
// (and required to be false) on the floating-point paths. Always printed.
235235
"bool":$signA,
236236
"bool":$signB,
237237
"bool":$clamp

lib/Bindings/Python/FlyROCDLExtension.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ struct PyMmaOpGFX120X_WMMAType : PyConcreteType<PyMmaOpGFX120X_WMMAType> {
144144
"sign_a"_a = false, "sign_b"_a = false, "clamp"_a = false, "context"_a = nb::none(),
145145
"Create a MmaOpGFX120X_WMMAType with m, n, k dimensions and element types "
146146
"(RDNA4 gfx1200 / gfx1201 wave32 WMMA, 16x16x16 with the v8 operand ABI). "
147-
"sign_a/sign_b/clamp must be false: only the fp16/bf16 paths are supported.");
147+
"sign_a/sign_b/clamp must be false: fp16, bf16, and every fp8(E4M3FN)/"
148+
"bf8(E5M2) A/B combination are supported.");
148149
}
149150
};
150151

lib/Dialect/FlyROCDL/GFX120X/MmaAtom.cpp

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ namespace mlir::fly_rocdl {
2020
// GFX120X (RDNA4: gfx1200, gfx1201) WMMA wave32.
2121
//
2222
// RDNA4 sits between the two existing WMMA atoms:
23-
// * Instruction shapes are the gfx11 ones (16x16x16 fp16/bf16/iu8), and the
23+
// * Floating-point instruction shapes are the gfx11 ones (16x16x16
24+
// fp16/bf16/fp8/bf8), and the
2425
// ROCDL intrinsics take the plain 3-operand {a, b, c} form — unlike
2526
// gfx1250, whose 16x16x32 ops carry mods/reuse operands.
2627
// * The register ABI is the gfx1250 "v8" one — each lane holds K/2 = 8 A/B
@@ -64,26 +65,29 @@ Attribute MmaOpGFX120X_WMMAType::getThrValLayoutC() const {
6465
return gfx1250::getThrValLayoutCD(getContext(), getElemTyAcc());
6566
}
6667

68+
static bool isFp8OrBf8(Type elemTy) { return isa<Float8E4M3FNType, Float8E5M2Type>(elemTy); }
69+
6770
LogicalResult MmaOpGFX120X_WMMAType::verify(function_ref<InFlightDiagnostic()> emitError, int32_t m,
6871
int32_t n, int32_t k, Type elemTyA, Type elemTyB,
6972
Type elemTyAcc, bool signA, bool signB, bool clamp) {
7073
if (m != 16 || n != 16 || k != 16) {
71-
return emitError() << "GFX120X WMMA requires M=N=K=16, got " << m << "x" << n << "x" << k
72-
<< " (the 16x16x32 shapes are gfx1250-only; use gfx1250.wmma there)";
74+
return emitError() << "GFX120X WMMA floating-point forms require M=N=K=16, got " << m << "x"
75+
<< n << "x" << k;
7376
}
7477

78+
const bool isFp8 = isFp8OrBf8(elemTyA) && isFp8OrBf8(elemTyB) && elemTyAcc.isF32();
7579
const bool isFp = (elemTyA.isF16() && elemTyB.isF16() && elemTyAcc.isF32()) ||
76-
(elemTyA.isBF16() && elemTyB.isBF16() && elemTyAcc.isF32());
80+
(elemTyA.isBF16() && elemTyB.isBF16() && elemTyAcc.isF32()) || isFp8;
7781

7882
if (!isFp) {
7983
return emitError() << "unsupported GFX120X WMMA configuration: " << m << "x" << n << "x" << k
8084
<< " with A=" << elemTyA << ", B=" << elemTyB << ", Acc=" << elemTyAcc;
8185
}
8286

83-
// The fp16/bf16 intrinsics have no sign/clamp operands; refuse to build an
87+
// The floating-point intrinsics have no sign/clamp operands; refuse to build an
8488
// atom promising something codegen cannot deliver.
8589
if (signA || signB || clamp) {
86-
return emitError() << "GFX120X WMMA fp16/bf16 path does not accept signA/signB/clamp "
90+
return emitError() << "GFX120X WMMA floating-point path does not accept signA/signB/clamp "
8791
"(the ROCDL fp WMMA intrinsics have no such operands); got signA="
8892
<< signA << ", signB=" << signB << ", clamp=" << clamp;
8993
}
@@ -99,7 +103,10 @@ LogicalResult MmaOpGFX120X_WMMAType::verify(function_ref<InFlightDiagnostic()> e
99103
// per lane.
100104
// fp16 -> vector<8xf16>
101105
// bf16 -> vector<8xi16> (the bf16 WMMA intrinsic takes integer operands)
106+
// fp8/bf8 -> vector<2xi32> (8 packed 8-bit values)
102107
static Type getWmmaABType(MLIRContext *ctx, Type elemTy) {
108+
if (isFp8OrBf8(elemTy))
109+
return VectorType::get({2}, IntegerType::get(ctx, 32));
103110
if (elemTy.isBF16())
104111
return VectorType::get({8}, IntegerType::get(ctx, 16));
105112
if (elemTy.isF16())
@@ -148,6 +155,14 @@ FailureOr<Value> MmaOpGFX120X_WMMAType::emitAtomCallSSA(OpBuilder &builder, Loca
148155
return ROCDL::wmma_f32_16x16x16_f16::create(builder, loc, rawAccTy, a, b, c).getResult();
149156
if (elemTyA.isBF16() && elemTyB.isBF16())
150157
return ROCDL::wmma_f32_16x16x16_bf16::create(builder, loc, rawAccTy, a, b, c).getResult();
158+
if (isa<Float8E4M3FNType>(elemTyA) && isa<Float8E4M3FNType>(elemTyB))
159+
return ROCDL::wmma_f32_16x16x16_fp8_fp8::create(builder, loc, rawAccTy, a, b, c).getResult();
160+
if (isa<Float8E4M3FNType>(elemTyA) && isa<Float8E5M2Type>(elemTyB))
161+
return ROCDL::wmma_f32_16x16x16_fp8_bf8::create(builder, loc, rawAccTy, a, b, c).getResult();
162+
if (isa<Float8E5M2Type>(elemTyA) && isa<Float8E4M3FNType>(elemTyB))
163+
return ROCDL::wmma_f32_16x16x16_bf8_fp8::create(builder, loc, rawAccTy, a, b, c).getResult();
164+
if (isa<Float8E5M2Type>(elemTyA) && isa<Float8E5M2Type>(elemTyB))
165+
return ROCDL::wmma_f32_16x16x16_bf8_bf8::create(builder, loc, rawAccTy, a, b, c).getResult();
151166

152167
return failure();
153168
}

python/flydsl/expr/rocdl/universal.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,10 @@ def MFMA(m, n, k, elem_ty_ab, elem_ty_acc=None):
165165
def WMMA(m, n, k, elem_ty_ab, elem_ty_acc=None, **kwargs):
166166
"""Create an arch-appropriate WMMA atom.
167167
168-
Supported kwargs (integer paths only — iu8 / iu4):
168+
Supported kwargs:
169+
elem_ty_b: optional B operand type for mixed-type instructions; defaults
170+
to ``elem_ty_ab``. RDNA4 accepts every FP8(E4M3FN)/BF8(E5M2)
171+
combination.
169172
sign_a (bool, default False): treat A operand as signed.
170173
sign_b (bool, default False): treat B operand as signed.
171174
clamp (bool, default False): saturate integer accumulator.
@@ -175,7 +178,9 @@ def WMMA(m, n, k, elem_ty_ab, elem_ty_acc=None, **kwargs):
175178
intrinsic has no such operands. Future WMMA ops for new architectures
176179
should extend kwargs here rather than growing the positional signature.
177180
"""
178-
ty_ab = elem_ty_ab.ir_type if hasattr(elem_ty_ab, "ir_type") else elem_ty_ab
181+
ty_a = elem_ty_ab.ir_type if hasattr(elem_ty_ab, "ir_type") else elem_ty_ab
182+
elem_ty_b = kwargs.pop("elem_ty_b", None)
183+
ty_b = ty_a if elem_ty_b is None else (elem_ty_b.ir_type if hasattr(elem_ty_b, "ir_type") else elem_ty_b)
179184
if elem_ty_acc is None:
180185
ty_acc = ir.F32Type.get()
181186
else:
@@ -191,14 +196,14 @@ def WMMA(m, n, k, elem_ty_ab, elem_ty_acc=None, **kwargs):
191196

192197
arch = get_rocm_arch() or ""
193198
if arch.startswith("gfx11"):
194-
return MmaOpGFX11_WMMAType.get(m, n, k, ty_ab, ty_ab, ty_acc, **kwargs)
199+
return MmaOpGFX11_WMMAType.get(m, n, k, ty_a, ty_b, ty_acc, **kwargs)
195200
if arch.startswith("gfx1250"):
196201
return MmaOpGFX1250_WMMAType.get(
197202
m,
198203
n,
199204
k,
200-
ty_ab,
201-
ty_ab,
205+
ty_a,
206+
ty_b,
202207
ty_acc,
203208
sign_a=bool(kwargs.get("sign_a", False)),
204209
sign_b=bool(kwargs.get("sign_b", False)),
@@ -209,8 +214,8 @@ def WMMA(m, n, k, elem_ty_ab, elem_ty_acc=None, **kwargs):
209214
m,
210215
n,
211216
k,
212-
ty_ab,
213-
ty_ab,
217+
ty_a,
218+
ty_b,
214219
ty_acc,
215220
sign_a=bool(kwargs.get("sign_a", False)),
216221
sign_b=bool(kwargs.get("sign_b", False)),

tests/kernels/test_rdna4_wmma_atom.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
M = N = K = 16
4646

4747

48-
def _compile_single_wmma(elem_cls):
48+
def _compile_single_wmma(elem_cls_a, elem_cls_b):
4949
"""One wave, one atom: C[16,16] = A[16,16] @ B[16,16].T."""
5050
f32 = fx.Float32
5151

@@ -57,22 +57,23 @@ def wmma_kernel(A: fx.Tensor, B: fx.Tensor, C: fx.Tensor):
5757
bB = fx.make_view(fx.get_iter(fx.rocdl.make_buffer_tensor(B)), fx.make_layout((N, K), (K, 1)))
5858
bC = fx.make_view(fx.get_iter(fx.rocdl.make_buffer_tensor(C)), fx.make_layout((M, N), (N, 1)))
5959

60-
mma_atom = fx.make_mma_atom(fx.rocdl.WMMA(M, N, K, elem_cls, f32))
60+
mma_atom = fx.make_mma_atom(fx.rocdl.WMMA(M, N, K, elem_cls_a, f32, elem_ty_b=elem_cls_b))
6161
tiled_mma = fx.make_tiled_mma(mma_atom, fx.make_layout((1, 1, 1), (0, 0, 0)))
6262
thr_mma = tiled_mma.thr_slice(tid)
6363

6464
frag_A = thr_mma.make_fragment_A(bA)
6565
frag_B = thr_mma.make_fragment_B(bB)
6666
frag_C = thr_mma.make_fragment_C(bC)
6767

68-
copy_ab = fx.make_copy_atom(fx.rocdl.BufferCopy(elem_cls.width), elem_cls)
68+
copy_a = fx.make_copy_atom(fx.rocdl.BufferCopy(elem_cls_a.width), elem_cls_a)
69+
copy_b = fx.make_copy_atom(fx.rocdl.BufferCopy(elem_cls_b.width), elem_cls_b)
6970
copy_c = fx.make_copy_atom(fx.rocdl.BufferCopy(f32.width), f32)
70-
thr_copy_A = fx.make_tiled_copy_A(copy_ab, tiled_mma).get_slice(tid)
71-
thr_copy_B = fx.make_tiled_copy_B(copy_ab, tiled_mma).get_slice(tid)
71+
thr_copy_A = fx.make_tiled_copy_A(copy_a, tiled_mma).get_slice(tid)
72+
thr_copy_B = fx.make_tiled_copy_B(copy_b, tiled_mma).get_slice(tid)
7273
thr_copy_C = fx.make_tiled_copy_C(copy_c, tiled_mma).get_slice(tid)
7374

74-
fx.copy(copy_ab, thr_copy_A.partition_S(bA), thr_copy_A.retile(frag_A))
75-
fx.copy(copy_ab, thr_copy_B.partition_S(bB), thr_copy_B.retile(frag_B))
75+
fx.copy(copy_a, thr_copy_A.partition_S(bA), thr_copy_A.retile(frag_A))
76+
fx.copy(copy_b, thr_copy_B.partition_S(bB), thr_copy_B.retile(frag_B))
7677

7778
frag_C.fill(0)
7879
fx.gemm(mma_atom, frag_C, frag_A, frag_B, frag_C)
@@ -86,25 +87,29 @@ def launch(A: fx.Tensor, B: fx.Tensor, C: fx.Tensor, stream: fx.Stream = fx.Stre
8687

8788

8889
@pytest.mark.parametrize(
89-
"elem_cls, torch_dtype",
90+
"elem_cls_a, elem_cls_b, torch_dtype_a, torch_dtype_b",
9091
[
91-
(fx.BFloat16, torch.bfloat16),
92-
(fx.Float16, torch.float16),
92+
(fx.BFloat16, fx.BFloat16, torch.bfloat16, torch.bfloat16),
93+
(fx.Float16, fx.Float16, torch.float16, torch.float16),
94+
(fx.Float8E4M3FN, fx.Float8E4M3FN, torch.float8_e4m3fn, torch.float8_e4m3fn),
95+
(fx.Float8E4M3FN, fx.Float8E5M2, torch.float8_e4m3fn, torch.float8_e5m2),
96+
(fx.Float8E5M2, fx.Float8E4M3FN, torch.float8_e5m2, torch.float8_e4m3fn),
97+
(fx.Float8E5M2, fx.Float8E5M2, torch.float8_e5m2, torch.float8_e5m2),
9398
],
94-
ids=["bf16", "f16"],
99+
ids=["bf16", "f16", "fp8_fp8", "fp8_bf8", "bf8_fp8", "bf8_bf8"],
95100
)
96-
def test_single_wmma_atom(elem_cls, torch_dtype):
101+
def test_single_wmma_atom(elem_cls_a, elem_cls_b, torch_dtype_a, torch_dtype_b):
97102
"""A single gfx120x.wmma atom call must match A @ B.T exactly for integers.
98103
99104
Integer-valued inputs keep the result exactly representable, so any
100105
mismatch is a fragment-layout bug rather than rounding.
101106
"""
102107
torch.manual_seed(0)
103-
a = (torch.randn(M, K, device="cuda") * 4).round().to(torch_dtype)
104-
b = (torch.randn(N, K, device="cuda") * 4).round().to(torch_dtype)
108+
a = (torch.randn(M, K, device="cuda") * 4).round().to(torch_dtype_a)
109+
b = (torch.randn(N, K, device="cuda") * 4).round().to(torch_dtype_b)
105110
c = torch.zeros(M, N, dtype=torch.float32, device="cuda")
106111

107-
launch = _compile_single_wmma(elem_cls)
112+
launch = _compile_single_wmma(elem_cls_a, elem_cls_b)
108113
launch(a, b, c, stream=torch.cuda.current_stream())
109114
torch.cuda.synchronize()
110115

0 commit comments

Comments
 (0)