Skip to content

Commit 464cbd5

Browse files
committed
feat(fp8): native fp8-input e4m3 MMA prim for sm_121 (lever r2-native-fp8mma)
Replace the R2 decode-to-fp16 + fp16-MMA CUDA path with a REAL fp8-input e4m3 tensor-core MMA on gb10/sm_121, added as a NEW prim/variant (the fp16-decode twin + the Metal cooperative route stay byte-identical, A/B-able). cppmega_mlx/nn/_tilelang/fp8_matmul_path_c.py: - _fp8_scaled_matmul2d_cuda_native_kernel_template: e4m3 A_shared/B_shared fed straight into T.gemm(transpose_B=True) with an fp32 C register fragment. No T.cast decode, no float16 buffers -> the CUDA backend dispatches the SM120 fp8 MMA atom (SM120_16x8x32_TN -> mma.sync...kind::f8f6f4.m16n8k32.row.col. f32.e4m3.e4m3.f32) i.e. a real fp8-INPUT MMA, fp32 accumulate (res_r2mma GO, proven on-hardware maxabs-err 0.0229). - _native_fp8_tile_for: fp8-MMA tile selector (K%32==0 HARD floor, m16n8 divisibility); prod shapes -> 128x128x64/128t, SSD F2 -> 64x64x64. K%32!=0 returns None (caller RAISES; RULE #1 no silent partial-tile). - _make_scaled_matmul2d_cuda_native_kernel + public alias fp8_scaled_matmul_path_c_cuda_native_prim (validated BM/BN/BK/threads override for a tile sweep; illegal -> RAISE). - _cuda_native_fp8_enabled / CPPMEGA_FP8_PATH_C_CUDA_NATIVE: native is the DEFAULT sm_121 path; =0 opts to the fp16-decode twin for an explicit A/B. Native compile/dispatch failure still RAISES (no silent fp16/bf16 fallback). scratch/fp8_gemm_microbench.py: - run_tilelang_fp8_native (R2_native_fp8mma) + --no-tl-native: A/Bs the native fp8-MMA prim against the R2 fp16-decode route over the same e4m3 operands, in one run; rel_err over all elements; failure RECORDED with where+what. Local checks: py_compile + ast.parse both files; AST cross-symbol (e4m3 operands, no cast, 1 TN T.gemm, fp32 frag, K%32 guard, __all__ exports, microbench wiring); pure-logic tile selection over prod shapes. NO GPU here; the GB10 phase MEASURES TFLOPs + verifies the emitted kFloat8_e4m3 MMA dtype.
1 parent 02da98f commit 464cbd5

2 files changed

Lines changed: 474 additions & 14 deletions

File tree

cppmega_mlx/nn/_tilelang/fp8_matmul_path_c.py

Lines changed: 330 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,62 @@ def _coop_tile_for(M: int, N: int, K: int) -> tuple[int, int, int, int] | None:
9393
return None
9494

9595

96+
def _native_fp8_tile_for(M: int, N: int, K: int) -> tuple[int, int, int, int] | None:
97+
"""Pick a tile (BM, BN, BK, threads) for the NATIVE fp8-input e4m3 MMA path.
98+
99+
Unlike the Metal cooperative ``matmul2d`` selector (:func:`_coop_tile_for`),
100+
this targets the gb10/sm_121 CUDA ``T.gemm`` SM120 fp8 MMA dispatch
101+
(``SM120_16x8x32_TN`` -> ``mma.sync.aligned.kind::f8f6f4.m16n8k32...
102+
.e4m3.e4m3.f32``). The hardware atom is m16n8k32, so the gating constraints
103+
are (proven on hardware, res_r2mma.feasibility):
104+
105+
* TN layout (transpose_B=True) -- handled by the kernel, not the tile.
106+
* K % 32 == 0 (the fp8 MMA is K=32-deep). HARD FLOOR; BK must be a
107+
multiple of 32 and K a multiple of BK.
108+
* BM % 16 == 0 and BN % 8 == 0 (the m16n8 output fragment shape); we use
109+
the larger, warp-tile-friendly BM % 16, BN % 16 to map cleanly onto
110+
128/256-thread warp grids.
111+
112+
The candidate tiles are ordered best-first per the research recommendation:
113+
start from the proven tilelang fp8 example geometry (128x128x64, 128 threads)
114+
which divides every prod GEMM (M=16384; N in {3584,10752,37888}; K in
115+
{3584,18944}); fall back to smaller tiles for the narrow SSD F2 shape and any
116+
residual sub-128 dim. Threadgroup shared memory is the e4m3 operands (1 byte)
117+
plus the fp32 C staging -- far under budget, so the dominant constraint is
118+
divisibility, not shmem.
119+
120+
Returns ``None`` when no legal native-fp8 tile divides ``M/N/K`` (e.g. a shape
121+
whose K is not a multiple of 32). RULE #1: the caller RAISES on ``None`` (no
122+
silent partial-tile / no fp16-decode fallback) -- a K%32!=0 shape simply
123+
cannot run the native fp8 MMA and must surface that, not silently degrade.
124+
"""
125+
if K % 32 != 0:
126+
# The fp8 m16n8k32 MMA hard-requires K (and thus BK) a multiple of 32.
127+
# Surface the violation to the caller (it RAISES) rather than picking an
128+
# illegal tile that would mis-tile the K reduction (RULE #1).
129+
return None
130+
candidates = (
131+
(128, 128, 64, 128), # research-recommended fp8 example geometry
132+
(128, 128, 32, 128), # smaller BK (K=32-deep amortized less)
133+
(128, 64, 64, 128), # narrower N
134+
(64, 64, 64, 128), # SSD F2 tile (64x64x64) and small dims
135+
(64, 64, 32, 128),
136+
(32, 64, 32, 128), # current untuned R2 tile (kept as a sweep point)
137+
(16, 32, 32, 64), # smallest legal native-fp8 tile (m16n8 fragment)
138+
)
139+
for bm, bn, bk, threads in candidates:
140+
if bk % 32 != 0:
141+
continue
142+
if M % bm or N % bn or K % bk:
143+
continue
144+
if bm % 16 or bn % 8:
145+
continue
146+
if (M // bm) <= 0 or (N // bn) <= 0:
147+
continue
148+
return bm, bn, bk, threads
149+
return None
150+
151+
96152
@dataclass(frozen=True)
97153
class FP8MatmulPathCStatus:
98154
available: bool
@@ -297,6 +353,93 @@ def _fp8_scaled_matmul2d_cuda_kernel_template(
297353
T.copy(C_shared, C[by * _FP8_MM_BM, bx * _FP8_MM_BN], disable_tma=True) # type: ignore[name-defined] # noqa: F821
298354

299355

356+
def _fp8_scaled_matmul2d_cuda_native_kernel_template(
357+
A_fp8: T.Tensor((_FP8_MM_M, _FP8_MM_K), "float8_e4m3"), # type: ignore[name-defined] # noqa: F821
358+
A_scale: T.Tensor((1,), "float32"), # type: ignore[name-defined] # noqa: F821
359+
B_fp8: T.Tensor((_FP8_MM_N, _FP8_MM_K), "float8_e4m3"), # type: ignore[name-defined] # noqa: F821
360+
B_scale: T.Tensor((1,), "float32"), # type: ignore[name-defined] # noqa: F821
361+
C: T.Tensor((_FP8_MM_M, _FP8_MM_N), _FP8_MM_C_DTYPE), # type: ignore[name-defined] # noqa: F821
362+
):
363+
"""NATIVE fp8-input e4m3 tensor-core MMA on gb10 / sm_121 (lever r2-native-fp8mma).
364+
365+
This is the lever-1 rewrite of :func:`_fp8_scaled_matmul2d_cuda_kernel_template`.
366+
The slow twin DECODES each e4m3 byte to ``float16`` in register fragments and
367+
runs ``tl.mma_sync<kFloat16,kFloat16,kFloat32,16,8,16>`` -- an fp16-MMA on
368+
decoded values (the 0.18-0.42x memory-lever). THIS template instead keeps the
369+
e4m3 operands as ``float8_e4m3`` all the way into ``T.gemm``, so TileLang's
370+
CUDA GEMM backend dispatches the SM120 fp8 MMA atom
371+
(``src/backend/cuda/op/gemm.cc`` lines 60/74 accept fp8-input GEMM ->
372+
``gemm_mma.h`` ``#if __CUDA_ARCH_LIST__ >= 1200`` ->
373+
``TL_DISPATCH_MMA_TEMPLATE(fp8_e4_t, fp8_e4_t, float, SM120_16x8x32_TN)`` ->
374+
CuTe ``SM120_16x8x32_TN`` -> the real Blackwell PTX
375+
``mma.sync.aligned.kind::f8f6f4.m16n8k32.row.col.f32.e4m3.e4m3.f32``). The
376+
generated CUDA emits ``tl::mma_sync<kFloat8_e4m3,kFloat8_e4m3,kFloat32,16,8,32>``
377+
over ``fp8_e4_t`` register operands -- a REAL fp8-input MMA, fp32 accumulate,
378+
ZERO fp16 decode (PROVEN on the live gb10 sm_121 GPU, res_r2mma.feasibility=GO;
379+
on-hardware compile+run gave maxabs-err 0.0229 vs an fp16 reference = honest
380+
fp8 quantization noise, RULE #1 clean).
381+
382+
Deltas vs the fp16-decode twin (the ONLY changes -- everything else identical):
383+
384+
* ``A_shared``/``B_shared`` are ``float8_e4m3`` (NOT ``float16``); the e4m3
385+
bytes are copied straight from global into the e4m3 shared buffers
386+
(``disable_tma=True``), with NO ``A_fp8_local``/``B_fp8_local`` fragment
387+
stage and NO ``T.cast(e4m3 -> float16)`` Parallel loops.
388+
* ``T.gemm(A_shared_e4m3, B_shared_e4m3, C_frag_fp32, transpose_B=True)``
389+
now dispatches the native fp8 MMA (the fp8+fp8+fp32 branch of gemm.cc)
390+
instead of the fp16 MMA.
391+
392+
Unchanged: the fp32 ``C_frag`` register accumulator (the fp8 MMA accumulates
393+
in fp32 -- do NOT switch to fp16 accumulate; it loses range), the post-K
394+
``sa*sb`` scale epilogue, the ``C_shared`` copy-out staging, and the
395+
``disable_tma=True`` on every copy (sm_121 TMA tensormap mis-aligns at these
396+
tile dims -> "Invalid TMA descriptor arguments"; the cp.async path is correct).
397+
The compile site (:func:`_fp8_matmul_tvm_ffi_kernel_for`) keeps the same
398+
``tl.disable_tma_lower`` / ``tl.disable_warp_specialized`` pass_configs.
399+
400+
HARD constraints (gemm.cc CheckWgmma fp8 branch + the m16n8k32 MMA shape),
401+
enforced by :func:`_native_fp8_tile_for` (tile=None -> caller RAISES, RULE #1):
402+
transpose_B=True (TN) and K % 32 == 0. All prod shapes satisfy them.
403+
"""
404+
with T.Kernel( # type: ignore[name-defined] # noqa: F821
405+
T.ceildiv(_FP8_MM_N, _FP8_MM_BN), # type: ignore[name-defined] # noqa: F821
406+
T.ceildiv(_FP8_MM_M, _FP8_MM_BM), # type: ignore[name-defined] # noqa: F821
407+
threads=_FP8_MM_THREADS,
408+
) as (bx, by):
409+
# NATIVE-fp8: the shared operands stay e4m3 (no float16 decode buffers).
410+
A_shared = T.alloc_shared((_FP8_MM_BM, _FP8_MM_BK), "float8_e4m3", scope="shared") # type: ignore[name-defined] # noqa: F821
411+
B_shared = T.alloc_shared((_FP8_MM_BN, _FP8_MM_BK), "float8_e4m3", scope="shared") # type: ignore[name-defined] # noqa: F821
412+
C_frag = T.alloc_fragment((_FP8_MM_BM, _FP8_MM_BN), _FP8_MM_C_DTYPE) # type: ignore[name-defined] # noqa: F821
413+
C_shared = T.alloc_shared((_FP8_MM_BM, _FP8_MM_BN), _FP8_MM_C_DTYPE, scope="shared") # type: ignore[name-defined] # noqa: F821
414+
T.clear(C_frag)
415+
for ko in T.serial(T.ceildiv(_FP8_MM_K, _FP8_MM_BK)): # type: ignore[name-defined] # noqa: F821
416+
# Copy the e4m3 bytes global->shared DIRECTLY (disable_tma on sm_121).
417+
# NO fp16 decode: the e4m3 operands feed T.gemm unchanged so the SM120
418+
# fp8 MMA atom fires. This is the whole point of the lever (RULE #1: a
419+
# real fp8-input MMA, verified by the kFloat8_e4m3 dtype in the
420+
# generated CUDA, not a relabeled fp16 path).
421+
T.copy( # type: ignore[name-defined] # noqa: F821
422+
A_fp8[by * _FP8_MM_BM : by * _FP8_MM_BM + _FP8_MM_BM,
423+
ko * _FP8_MM_BK : ko * _FP8_MM_BK + _FP8_MM_BK],
424+
A_shared,
425+
disable_tma=True,
426+
)
427+
T.copy( # type: ignore[name-defined] # noqa: F821
428+
B_fp8[bx * _FP8_MM_BN : bx * _FP8_MM_BN + _FP8_MM_BN,
429+
ko * _FP8_MM_BK : ko * _FP8_MM_BK + _FP8_MM_BK],
430+
B_shared,
431+
disable_tma=True,
432+
)
433+
# transpose_B=True keeps the TN layout the SM120 fp8 MMA requires.
434+
T.gemm(A_shared, B_shared, C_frag, transpose_B=True) # type: ignore[name-defined] # noqa: F821
435+
sa = A_scale[0]
436+
sb = B_scale[0]
437+
for i, j in T.Parallel(_FP8_MM_BM, _FP8_MM_BN): # type: ignore[name-defined] # noqa: F821
438+
C_frag[i, j] = C_frag[i, j] * sa * sb
439+
T.copy(C_frag, C_shared) # type: ignore[name-defined] # noqa: F821
440+
T.copy(C_shared, C[by * _FP8_MM_BM, bx * _FP8_MM_BN], disable_tma=True) # type: ignore[name-defined] # noqa: F821
441+
442+
300443
def _make_scaled_matmul_kernel(
301444
*,
302445
M: int,
@@ -417,6 +560,84 @@ def _make_scaled_matmul2d_cuda_kernel(
417560
return tilelang.language.prim_func(_fp8_scaled_matmul2d_cuda_kernel_template)
418561

419562

563+
def _make_scaled_matmul2d_cuda_native_kernel(
564+
*,
565+
M: int,
566+
N: int,
567+
K: int,
568+
num_stages: int = 0,
569+
c_dtype: str = "float32",
570+
BM: int | None = None,
571+
BN: int | None = None,
572+
BK: int | None = None,
573+
threads: int | None = None,
574+
) -> Any:
575+
"""Build the gb10 (sm_121) NATIVE fp8-input e4m3 ``T.gemm`` owner-output prim.
576+
577+
Binds :func:`_fp8_scaled_matmul2d_cuda_native_kernel_template` (e4m3 shared
578+
operands fed straight into ``T.gemm`` -> SM120 fp8 MMA). Tile selection uses
579+
:func:`_native_fp8_tile_for` (K%32==0 + m16n8 divisibility), NOT the Metal
580+
cooperative selector -- the fp8 MMA wants larger BM/BN than the narrow Metal
581+
matmul2d micro-tile. An explicit (BM,BN,BK,threads) override is accepted so
582+
the microbench / a tile sweep can probe a specific geometry; the override is
583+
VALIDATED (K%32==0, divisibility, m16n8) and RAISES on an illegal tile rather
584+
than silently mis-tiling (RULE #1).
585+
586+
Returns ``None`` when no legal native-fp8 tile divides ``M/N/K`` (e.g. K not a
587+
multiple of 32). RULE #1: the caller RAISES on ``None`` -- the native fp8 MMA
588+
is the ONE path when selected; it never falls back to the fp16-decode twin or
589+
to bf16. (For an explicit override, an illegal tile RAISES here directly.)
590+
"""
591+
import tilelang
592+
from tilelang import language as T
593+
from tvm.target import Target
594+
595+
override = (BM, BN, BK, threads)
596+
if any(v is not None for v in override):
597+
if any(v is None for v in override):
598+
raise ValueError(
599+
"fp8_scaled_matmul_path_c (cuda native): a tile override must "
600+
"specify all of BM, BN, BK, threads together (RULE #1: no "
601+
f"half-specified tile); got BM={BM} BN={BN} BK={BK} threads={threads}"
602+
)
603+
bm, bn, bk, thr = int(BM), int(BN), int(BK), int(threads)
604+
# Validate the override against the fp8 MMA hard constraints (the same
605+
# gates _native_fp8_tile_for enforces). An illegal override RAISES.
606+
if bk % 32 != 0 or K % bk:
607+
raise ValueError(
608+
"fp8_scaled_matmul_path_c (cuda native): illegal BK override "
609+
f"BK={bk} for K={K}; the e4m3 m16n8k32 MMA requires BK%32==0 and "
610+
"K%BK==0 (RULE #1: K%32 is a hard floor, no silent re-tile)."
611+
)
612+
if bm % 16 or bn % 8 or M % bm or N % bn:
613+
raise ValueError(
614+
"fp8_scaled_matmul_path_c (cuda native): illegal BM/BN override "
615+
f"BM={bm} BN={bn} for M={M} N={N}; require BM%16==0, BN%8==0, "
616+
"M%BM==0, N%BN==0 (RULE #1: no wrong partial-tile)."
617+
)
618+
tile = (bm, bn, bk, thr)
619+
else:
620+
tile = _native_fp8_tile_for(int(M), int(N), int(K))
621+
if tile is None:
622+
return None
623+
bm, bn, bk, thr = tile
624+
625+
globals().update(
626+
T=T,
627+
Target=Target,
628+
_FP8_MM_M=int(M),
629+
_FP8_MM_N=int(N),
630+
_FP8_MM_K=int(K),
631+
_FP8_MM_BM=int(bm),
632+
_FP8_MM_BN=int(bn),
633+
_FP8_MM_BK=int(bk),
634+
_FP8_MM_THREADS=int(thr),
635+
_FP8_MM_NUM_STAGES=int(num_stages),
636+
_FP8_MM_C_DTYPE=str(c_dtype),
637+
)
638+
return tilelang.language.prim_func(_fp8_scaled_matmul2d_cuda_native_kernel_template)
639+
640+
420641
def _resolve_fp8_compile_target(target: Any) -> Any:
421642
"""Resolve the ``target`` kwarg threaded through the FP8 matmul builders.
422643
@@ -453,6 +674,34 @@ def _resolve_fp8_compile_target(target: Any) -> Any:
453674

454675
FP8_PATH_C_MATMUL2D_ENV = "CPPMEGA_FP8_PATH_C_MATMUL2D"
455676

677+
# Lever r2-native-fp8mma: route the gb10 / sm_121 CUDA fp8 GEMM through the
678+
# NATIVE fp8-input e4m3 MMA prim (_fp8_scaled_matmul2d_cuda_native_kernel_template)
679+
# instead of the fp16-decode twin (_fp8_scaled_matmul2d_cuda_kernel_template).
680+
# DEFAULT: native (the real fp8-input MMA is the intended clear path on sm_121).
681+
# Set CPPMEGA_FP8_PATH_C_CUDA_NATIVE=0 to A/B against the original correct-but-slow
682+
# fp16-decode prim (e.g. to compare TFLOPs). This is an explicit A/B opt-out, NOT
683+
# a silent fallback: a native-prim compile/dispatch FAILURE still RAISES (RULE #1);
684+
# the env only chooses which CUDA prim is built, never papers over a broken one.
685+
FP8_PATH_C_CUDA_NATIVE_ENV = "CPPMEGA_FP8_PATH_C_CUDA_NATIVE"
686+
687+
688+
def _cuda_native_fp8_enabled() -> bool:
689+
"""Whether the gb10 CUDA branch builds the NATIVE fp8-input MMA prim.
690+
691+
Native (real e4m3 tensor-core MMA, SM120_16x8x32_TN) is the DEFAULT clear
692+
path on sm_121 (res_r2mma.feasibility=GO, proven on-hardware). The env
693+
``CPPMEGA_FP8_PATH_C_CUDA_NATIVE`` is an explicit A/B opt-OUT only: set it to
694+
``0``/``false``/``off`` to build the original fp16-decode twin instead (for a
695+
side-by-side TFLOPs comparison). RULE #1: this never silently degrades -- a
696+
native-prim failure RAISES; the env merely selects which prim is compiled.
697+
"""
698+
import os
699+
700+
val = os.environ.get(FP8_PATH_C_CUDA_NATIVE_ENV, "").strip().lower()
701+
if val in {"0", "false", "no", "off"}:
702+
return False
703+
return True
704+
456705

457706
def _matmul2d_owner_output_enabled() -> bool:
458707
"""Whether to route the owner-output GEMM through Metal-4 ``matmul2d``.
@@ -535,19 +784,40 @@ def _fp8_matmul_tvm_ffi_kernel_for(
535784
# RAISES (caught + re-raised as FP8MatmulPathCDirectError by the caller); it
536785
# does NOT silently fall back to dot4, to bf16, or to a degraded precision.
537786
if "cuda" in kind:
538-
cuda_prim = _make_scaled_matmul2d_cuda_kernel(
539-
M=M,
540-
N=N,
541-
K=K,
542-
num_stages=num_stages,
543-
c_dtype=c_dtype,
544-
)
787+
# Lever r2-native-fp8mma: DEFAULT to the native fp8-input e4m3 MMA prim
788+
# (real SM120 tensor-core fp8 MMA). CPPMEGA_FP8_PATH_C_CUDA_NATIVE=0 opts
789+
# back to the fp16-decode twin for an explicit A/B. RULE #1: either prim's
790+
# compile/dispatch failure RAISES (re-raised as FP8MatmulPathCDirectError
791+
# by the caller); the env only selects which CUDA prim is built.
792+
if _cuda_native_fp8_enabled():
793+
cuda_prim = _make_scaled_matmul2d_cuda_native_kernel(
794+
M=M,
795+
N=N,
796+
K=K,
797+
num_stages=num_stages,
798+
c_dtype=c_dtype,
799+
)
800+
cuda_prim_kind = "native fp8-input e4m3 MMA (SM120_16x8x32_TN)"
801+
no_tile_detail = (
802+
"no legal native-fp8 tile divides it (the e4m3 m16n8k32 MMA "
803+
"requires K%32==0 + m16n8 divisibility)"
804+
)
805+
else:
806+
cuda_prim = _make_scaled_matmul2d_cuda_kernel(
807+
M=M,
808+
N=N,
809+
K=K,
810+
num_stages=num_stages,
811+
c_dtype=c_dtype,
812+
)
813+
cuda_prim_kind = "fp16-decode dequant->T.gemm twin (A/B opt-out)"
814+
no_tile_detail = "no legal cooperative tile divides it"
545815
if cuda_prim is None:
546816
raise FP8MatmulPathCDirectError(
547-
f"fp8_scaled_matmul_path_c (cuda): no legal cooperative tile "
548-
f"divides M={M} N={N} K={K}; the CUDA dequant->T.gemm kernel "
549-
"cannot tile this shape and there is no Metal-only dot4 sibling "
550-
"on CUDA (RULE #1: no silent partial-tile / fallback)."
817+
f"fp8_scaled_matmul_path_c (cuda, {cuda_prim_kind}): "
818+
f"{no_tile_detail} for M={M} N={N} K={K}; the CUDA fp8 T.gemm "
819+
"kernel cannot tile this shape and there is no Metal-only dot4 "
820+
"sibling on CUDA (RULE #1: no silent partial-tile / fallback)."
551821
)
552822
# sm_121 (Blackwell): disable the TMA + warp-specialized lowering so the
553823
# GEMM operand copies use plain cp.async/vectorized loads (the same
@@ -937,3 +1207,52 @@ def fp8_scaled_matmul_path_c_cuda_prim(
9371207

9381208

9391209
__all__.append("fp8_scaled_matmul_path_c_cuda_prim")
1210+
1211+
1212+
def fp8_scaled_matmul_path_c_cuda_native_prim(
1213+
*,
1214+
M: int,
1215+
N: int,
1216+
K: int,
1217+
num_stages: int = 0,
1218+
c_dtype: str = "float32",
1219+
BM: int | None = None,
1220+
BN: int | None = None,
1221+
BK: int | None = None,
1222+
threads: int | None = None,
1223+
) -> Any:
1224+
"""Public alias: build the gb10 (sm_121) NATIVE fp8-input e4m3 ``T.gemm`` prim.
1225+
1226+
Thin wrapper over :func:`_make_scaled_matmul2d_cuda_native_kernel` -- the
1227+
lever-r2-native-fp8mma kernel whose e4m3 shared operands feed ``T.gemm``
1228+
directly, so the CUDA backend dispatches the SM120 fp8 MMA atom
1229+
(``SM120_16x8x32_TN`` -> ``mma.sync...kind::f8f6f4.m16n8k32...e4m3.e4m3.f32``)
1230+
-- a REAL fp8-input tensor-core MMA, fp32 accumulate, NO fp16 decode.
1231+
1232+
Returns the compiled-ready ``@T.prim_func``; ``None`` when no legal native-fp8
1233+
tile divides ``M/N/K`` (the caller RAISES on a CUDA host -- RULE #1). Optional
1234+
``BM/BN/BK/threads`` pin a specific tile for a sweep (validated; illegal ->
1235+
RAISE). Compile it via :func:`tilelang.compile` with
1236+
``target=_as_cuda_target("cuda")`` and ``pass_configs={"tl.disable_tma_lower":
1237+
True, "tl.disable_warp_specialized": True}`` -- exactly what
1238+
:func:`_fp8_matmul_tvm_ffi_kernel_for` does on the cuda native branch.
1239+
1240+
VERIFY-GATE (RULE #1): the GB10 phase must confirm the generated CUDA emits
1241+
``tl::mma_sync<kFloat8_e4m3,kFloat8_e4m3,kFloat32,16,8,32>`` (NOT kFloat16) --
1242+
that proves it is a real fp8-input MMA, not a relabeled fp16 path.
1243+
"""
1244+
return _make_scaled_matmul2d_cuda_native_kernel(
1245+
M=M,
1246+
N=N,
1247+
K=K,
1248+
num_stages=num_stages,
1249+
c_dtype=c_dtype,
1250+
BM=BM,
1251+
BN=BN,
1252+
BK=BK,
1253+
threads=threads,
1254+
)
1255+
1256+
1257+
__all__.append("fp8_scaled_matmul_path_c_cuda_native_prim")
1258+
__all__.append("FP8_PATH_C_CUDA_NATIVE_ENV")

0 commit comments

Comments
 (0)