Skip to content

Commit 2430afa

Browse files
committed
lever(fp8-mma-tune): pipelined tunable native fp8 MMA prim + autotune harness
Add a CLOSURE-parameterized, PIPELINED native fp8-input e4m3 MMA prim builder (_make_scaled_matmul2d_cuda_native_tunable + public alias fp8_scaled_matmul_path_c_cuda_native_tunable_prim) to fp8_matmul_path_c.py: the K loop is T.Pipelined(num_stages) (the default prim's T.serial makes num_stages inert), threads parameterize T.Kernel, and an optional T.use_swizzle L2-rasterization hint — keeping the verified SM120 e4m3 -> T.gemm MMA (kFloat8_e4m3, SM120_16x8x32_TN), fp32 accumulate, disable_tma on every copy. The change is ADDITIVE ONLY (189 insertions, 0 deletions): the production-default native template + the Metal cooperative route stay byte-identical. Illegal tiles RAISE in the builder (RULE #1). Add scratch/fp8_mma_autotune.py: a gb10-only harness that sweeps the tile params (block_M/block_N/block_K/num_stages/threads/rasterize) per prod GEMM shape via tilelang's built-in AutoTuner (parallel compile, fp8-parity validate, benchmark, disk-cache). Seeded coordinate-descent: Stage A (12 CUTLASS-4.5.x SM120-seeded anchor tiles) -> Stage B (num_stages x threads refine) -> Stage C (rasterize probe). Reports best TFLOPs vs bf16 + fp8 parity per config and prints BEST_CONFIG_JSON. RULE #1: it crowns the FASTEST PARITY-PASSING tile and RAISES NoTileBeatsFloorError if none clears the documented floor (--floor, default 1.0x bf16) — no silent slow-tile, no fp16-decode/bf16 fallback; a parity miss (>0.10 rel_err = bf16/garbage guard) is rejected, not crowned.
1 parent 32a3631 commit 2430afa

2 files changed

Lines changed: 1002 additions & 0 deletions

File tree

cppmega_mlx/nn/_tilelang/fp8_matmul_path_c.py

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,145 @@ def _make_scaled_matmul2d_cuda_native_kernel(
638638
return tilelang.language.prim_func(_fp8_scaled_matmul2d_cuda_native_kernel_template)
639639

640640

641+
# Lever fp8-mma-tune: a CLOSURE-parameterized, PIPELINED native-fp8 prim builder
642+
# the autotune harness drives. It is a STRICT SUPERSET of
643+
# :func:`_make_scaled_matmul2d_cuda_native_kernel` whose params (BM,BN,BK,threads,
644+
# num_stages,enable_rasteration) are bound as Python closure locals (NOT
645+
# ``globals().update`` mutation of the module template), so the tuner can vary the
646+
# tile per call without racing the shared module globals. The KERNEL BODY is the
647+
# verified SM120 fp8-input e4m3 MMA: e4m3 shared operands feed ``T.gemm`` directly
648+
# (-> ``tl::mma_sync<kFloat8_e4m3,kFloat8_e4m3,kFloat32,16,8,32>``), fp32 ``C_frag``
649+
# accumulate, the ``sa*sb`` epilogue, and ``disable_tma=True`` on every copy
650+
# (sm_121 TMA tensormap mis-aligns -> "Invalid TMA descriptor arguments").
651+
#
652+
# DELTAS vs the (still-default) module template
653+
# (:func:`_fp8_scaled_matmul2d_cuda_native_kernel_template`) -- which stays
654+
# BYTE-IDENTICAL so the production default + the Metal route are untouched:
655+
# * the K loop is ``T.Pipelined(..., num_stages=num_stages)`` instead of
656+
# ``T.serial(...)`` -- so ``num_stages`` is now LIVE (the module template's
657+
# ``T.serial`` makes num_stages a NO-OP); num_stages=0 lowers to an
658+
# un-pipelined single-buffer loop (parity with the current default).
659+
# * ``threads`` parameterizes ``T.Kernel`` (the module template hard-binds
660+
# ``_FP8_MM_THREADS``).
661+
# * an OPTIONAL ``T.use_swizzle(panel_size=10, enable=enable_rasteration)``
662+
# L2 rasterization hint (off by default -> emission parity with the module
663+
# template when enable_rasteration=False).
664+
# RULE #1: this is the ONE tunable path; an illegal tile RAISES in the builder
665+
# (validated below, same gates as :func:`_native_fp8_tile_for`); a pipelining/
666+
# swizzle codegen failure RAISES at compile -- it never silently degrades to the
667+
# un-pipelined template or to bf16.
668+
669+
670+
def _make_scaled_matmul2d_cuda_native_tunable(
671+
*,
672+
M: int,
673+
N: int,
674+
K: int,
675+
block_M: int,
676+
block_N: int,
677+
block_K: int,
678+
num_stages: int,
679+
threads: int,
680+
c_dtype: str = "float32",
681+
enable_rasteration: bool = False,
682+
) -> Any:
683+
"""Build a PIPELINED, closure-parameterized native fp8-input e4m3 ``T.gemm`` prim.
684+
685+
All of ``block_M/block_N/block_K``, ``num_stages``, ``threads`` and
686+
``enable_rasteration`` are required (the autotuner supplies every axis); they
687+
are captured as Python closure locals and validated against the fp8 m16n8k32
688+
MMA hard constraints (BK%32==0, K%BK==0, BM%16==0, BN%8==0, M%BM==0, N%BN==0)
689+
-- an illegal tile RAISES here (RULE #1: no silent re-tile / partial-tile).
690+
691+
The emitted body is identical math to the verified native template; only the
692+
K loop (``T.Pipelined``), the ``T.Kernel`` ``threads``, and the optional
693+
``T.use_swizzle`` differ (see the module comment above). Returns a
694+
``@T.prim_func`` ready for :func:`tilelang.compile` with the same
695+
``tl.disable_tma_lower`` / ``tl.disable_warp_specialized`` pass_configs.
696+
"""
697+
import tilelang # noqa: F401
698+
from tilelang import language as T
699+
700+
bm, bn, bk = int(block_M), int(block_N), int(block_K)
701+
thr, ns = int(threads), int(num_stages)
702+
M_i, N_i, K_i = int(M), int(N), int(K)
703+
c_dt = str(c_dtype)
704+
rasterize = bool(enable_rasteration)
705+
706+
# Validate the tile against the e4m3 m16n8k32 MMA gates (RULE #1: RAISE, the
707+
# autotuner is expected to filter illegal configs out BEFORE calling this, so
708+
# reaching here with an illegal tile is a real bug, not a sweep miss).
709+
if bk % 32 != 0 or K_i % bk:
710+
raise ValueError(
711+
"fp8_scaled_matmul_path_c (cuda native tunable): illegal BK "
712+
f"block_K={bk} for K={K_i}; the e4m3 m16n8k32 MMA requires "
713+
"block_K%32==0 and K%block_K==0 (RULE #1: K%32 is a hard floor)."
714+
)
715+
if bm % 16 or bn % 8 or M_i % bm or N_i % bn:
716+
raise ValueError(
717+
"fp8_scaled_matmul_path_c (cuda native tunable): illegal BM/BN "
718+
f"block_M={bm} block_N={bn} for M={M_i} N={N_i}; require "
719+
"block_M%16==0, block_N%8==0, M%block_M==0, N%block_N==0 "
720+
"(RULE #1: no wrong partial-tile)."
721+
)
722+
if thr <= 0 or thr % 32 != 0:
723+
raise ValueError(
724+
"fp8_scaled_matmul_path_c (cuda native tunable): illegal threads="
725+
f"{thr}; require a positive multiple of 32 (warp granularity)."
726+
)
727+
if ns < 0:
728+
raise ValueError(
729+
"fp8_scaled_matmul_path_c (cuda native tunable): illegal "
730+
f"num_stages={ns}; require >= 0 (0 = un-pipelined single buffer)."
731+
)
732+
733+
@T.prim_func
734+
def _native_fp8_tunable(
735+
A_fp8: T.Tensor((M_i, K_i), "float8_e4m3"),
736+
A_scale: T.Tensor((1,), "float32"),
737+
B_fp8: T.Tensor((N_i, K_i), "float8_e4m3"),
738+
B_scale: T.Tensor((1,), "float32"),
739+
C: T.Tensor((M_i, N_i), c_dt),
740+
):
741+
with T.Kernel(
742+
T.ceildiv(N_i, bn),
743+
T.ceildiv(M_i, bm),
744+
threads=thr,
745+
) as (bx, by):
746+
# NATIVE-fp8: shared operands stay e4m3 (no float16 decode buffers) so
747+
# T.gemm dispatches the SM120 fp8 MMA atom (verified kFloat8_e4m3).
748+
A_shared = T.alloc_shared((bm, bk), "float8_e4m3", scope="shared")
749+
B_shared = T.alloc_shared((bn, bk), "float8_e4m3", scope="shared")
750+
C_frag = T.alloc_fragment((bm, bn), c_dt)
751+
C_shared = T.alloc_shared((bm, bn), c_dt, scope="shared")
752+
T.clear(C_frag)
753+
# Optional L2 rasterization hint (off by default -> emission parity).
754+
T.use_swizzle(panel_size=10, enable=rasterize)
755+
# PIPELINED K loop: num_stages is LIVE here (the default module
756+
# template uses T.serial, which makes num_stages inert).
757+
for ko in T.Pipelined(T.ceildiv(K_i, bk), num_stages=ns):
758+
T.copy(
759+
A_fp8[by * bm : by * bm + bm, ko * bk : ko * bk + bk],
760+
A_shared,
761+
disable_tma=True,
762+
)
763+
T.copy(
764+
B_fp8[bx * bn : bx * bn + bn, ko * bk : ko * bk + bk],
765+
B_shared,
766+
disable_tma=True,
767+
)
768+
# transpose_B=True keeps the TN layout the SM120 fp8 MMA requires.
769+
T.gemm(A_shared, B_shared, C_frag, transpose_B=True)
770+
sa = A_scale[0]
771+
sb = B_scale[0]
772+
for i, j in T.Parallel(bm, bn):
773+
C_frag[i, j] = C_frag[i, j] * sa * sb
774+
T.copy(C_frag, C_shared)
775+
T.copy(C_shared, C[by * bm, bx * bn], disable_tma=True)
776+
777+
return _native_fp8_tunable
778+
779+
641780
def _resolve_fp8_compile_target(target: Any) -> Any:
642781
"""Resolve the ``target`` kwarg threaded through the FP8 matmul builders.
643782
@@ -1256,3 +1395,53 @@ def fp8_scaled_matmul_path_c_cuda_native_prim(
12561395

12571396
__all__.append("fp8_scaled_matmul_path_c_cuda_native_prim")
12581397
__all__.append("FP8_PATH_C_CUDA_NATIVE_ENV")
1398+
1399+
1400+
def fp8_scaled_matmul_path_c_cuda_native_tunable_prim(
1401+
*,
1402+
M: int,
1403+
N: int,
1404+
K: int,
1405+
block_M: int,
1406+
block_N: int,
1407+
block_K: int,
1408+
num_stages: int,
1409+
threads: int,
1410+
c_dtype: str = "float32",
1411+
enable_rasteration: bool = False,
1412+
) -> Any:
1413+
"""Public alias: build the PIPELINED, closure-parameterized native fp8 prim.
1414+
1415+
Thin wrapper over :func:`_make_scaled_matmul2d_cuda_native_tunable` -- the
1416+
lever-fp8-mma-tune kernel the autotune harness (``scratch/fp8_mma_autotune.py``)
1417+
sweeps. Every tile axis (``block_M/block_N/block_K``, ``num_stages``,
1418+
``threads``, ``enable_rasteration``) is required and validated; an illegal tile
1419+
RAISES (RULE #1). The kernel body is the verified SM120 fp8-input e4m3 MMA
1420+
(``SM120_16x8x32_TN`` -> ``mma.sync...kind::f8f6f4.m16n8k32...e4m3.e4m3.f32``),
1421+
fp32 accumulate, but with a LIVE ``T.Pipelined(num_stages)`` K loop and a
1422+
parameterized ``threads`` (the default
1423+
:func:`fp8_scaled_matmul_path_c_cuda_native_prim` uses ``T.serial`` so its
1424+
num_stages is inert). Compile via :func:`tilelang.compile` with
1425+
``target=_as_cuda_target("cuda")`` and ``pass_configs={"tl.disable_tma_lower":
1426+
True, "tl.disable_warp_specialized": True}``.
1427+
1428+
VERIFY-GATE (RULE #1): the GB10 phase must confirm the generated CUDA still
1429+
emits ``tl::mma_sync<kFloat8_e4m3,kFloat8_e4m3,kFloat32,16,8,32>`` (NOT
1430+
kFloat16) after pipelining -- that proves the pipelined loop did not perturb
1431+
the SM120 fp8 MMA dispatch back to an fp16 path.
1432+
"""
1433+
return _make_scaled_matmul2d_cuda_native_tunable(
1434+
M=M,
1435+
N=N,
1436+
K=K,
1437+
block_M=block_M,
1438+
block_N=block_N,
1439+
block_K=block_K,
1440+
num_stages=num_stages,
1441+
threads=threads,
1442+
c_dtype=c_dtype,
1443+
enable_rasteration=enable_rasteration,
1444+
)
1445+
1446+
1447+
__all__.append("fp8_scaled_matmul_path_c_cuda_native_tunable_prim")

0 commit comments

Comments
 (0)