Skip to content

Commit 864e88b

Browse files
committed
Complete side-channel runtime Stage A gates
1 parent 1be9b60 commit 864e88b

42 files changed

Lines changed: 1399 additions & 449 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cppmega_mlx/nn/_cuda_kernel_bridge.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@
3838
``_cute_bridge.cute_dsl_source_to_tilelang_prim`` can recover cppmega's
3939
smallest ``SingleGemmWGMMA``, ``MaskedLKQApplyWGMMA``, one-chunk
4040
``StateApplyConsumersWGMMA``, and fixed-nchunk
41-
``MultiChunkStateApplyConsumersWGMMA`` external kernels as TileLang ``T``
42-
ops. More complex external ``@cute.kernel`` objects still loud-fail via
43-
``CuteBridgeUnsupported`` until they have dedicated importers.
41+
``MultiChunkStateApplyConsumersWGMMA``, Phase-4 ``FusedBwdBwdP4``, and
42+
FA4 ``FA4PatternFused3Gemm`` / ``FA4PatternFused3GemmV2`` external
43+
kernels as TileLang ``T`` ops. Other external ``@cute.kernel`` objects
44+
still loud-fail via ``CuteBridgeUnsupported`` until they have dedicated
45+
importers.
4446
* Unknown SM (e.g. future ``sm_130``) emits a ``RuntimeWarning`` and
4547
*falls back to sm_120 dispatch*. This keeps newer client GPUs usable for
4648
Triton/MXFP8 paths until we cut a release with explicit support.
@@ -107,10 +109,8 @@ def detect_cuda_arch() -> str:
107109
108110
Returns one of :data:`KNOWN_ARCHES` -- including ``"cpu"`` when no
109111
CUDA device is visible. The result is cached for the lifetime of the
110-
process via ``functools.lru_cache``; tests that monkeypatch the
111-
detection should patch the *symbol* (e.g.
112-
``monkeypatch.setattr(_cuda_kernel_bridge, 'detect_cuda_arch',
113-
lambda: 'sm_130')``) rather than relying on the cache being cleared.
112+
process via ``functools.lru_cache``; callers that need a specific
113+
target should pass ``arch=...`` to :func:`dispatch_kernel_bridge`.
114114
115115
Hopper sub-variant note: ``torch.cuda.get_device_capability()`` cannot
116116
distinguish sm_90 from sm_90a (the latter being the arch-flag form

cppmega_mlx/nn/_cute_bridge.py

Lines changed: 207 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444
functions — it only emits CuTeDSL from TileLang IR. For cppmega's
4545
smallest smoke kernels, ``SingleGemmWGMMA``, ``MaskedLKQApplyWGMMA``,
4646
one-chunk ``StateApplyConsumersWGMMA``, fixed-nchunk
47-
``MultiChunkStateApplyConsumersWGMMA``, and FA4's
47+
``MultiChunkStateApplyConsumersWGMMA``, Phase-4 ``FusedBwdBwdP4``,
48+
and FA4's
4849
``FA4PatternFused3Gemm`` / ``FA4PatternFused3GemmV2`` chains, we can
4950
recover the tile contract and
5051
re-express it as TileLang ``T`` ops. More complex kernels still require
@@ -93,19 +94,19 @@
9394
"TileLang's CuTeDSL bridge emits CuTeDSL Python from a TileLang T.prim_func; "
9495
"it does not generally consume hand-written @cute.kernel modules. cppmega's "
9596
"SingleGemmWGMMA, MaskedLKQApplyWGMMA, one-chunk "
96-
"StateApplyConsumersWGMMA, and fixed-nchunk "
97-
"MultiChunkStateApplyConsumersWGMMA plus FA4PatternFused3Gemm and "
98-
"FA4PatternFused3GemmV2 can be "
97+
"StateApplyConsumersWGMMA, fixed-nchunk "
98+
"MultiChunkStateApplyConsumersWGMMA, Phase-4 FusedBwdBwdP4, and "
99+
"FA4PatternFused3Gemm / FA4PatternFused3GemmV2 can be "
99100
"pattern-imported by re-expressing the recovered contracts in TileLang "
100-
"T-ops; complex cute_dsl_mimo kernels (full FA4 backward, fused 10-GEMM "
101-
"bwd_bwd_sm90_p4) still need dedicated TileLang rewrites or a real "
102-
"CuTeDSL frontend.",
101+
"T-ops; complex cute_dsl_mimo kernels outside this recovered contract "
102+
"set still need dedicated TileLang rewrites or a real CuTeDSL frontend.",
103103
# Hopper-specific feature gaps — even for the supported direction.
104104
"TileLang's CuTeDSL emitter (sm_90 path) does not expose all WGMMA / TMA "
105105
"knobs that cppmega's hand-written kernels rely on (e.g. quack.sm90_utils, "
106106
"warpgroup.OperandSource selection, custom SmemAllocator layouts, "
107-
"StMatrix epilogues). Re-expressing fused_bwd_bwd_sm90_p4 in TileLang IR "
108-
"would lose those optimizations.",
107+
"StMatrix epilogues). The FusedBwdBwdP4 importer preserves the public "
108+
"10-GEMM dataflow contract, but not those Hopper-specific scheduling "
109+
"optimizations.",
109110
# Environment gating.
110111
"tilelang.compile(target='cutedsl') requires nvidia-cutlass-dsl>=4.3.1 "
111112
"(excluding 4.3.4) AND a CUDA host. Mac/MLX hosts cannot exercise this "
@@ -211,7 +212,8 @@ def cute_dsl_source_to_tilelang_prim(
211212
works on Mac/MLX hosts where ``cutlass`` and ``quack`` are not installed.
212213
Supported source patterns are cppmega's ``SingleGemmWGMMA``,
213214
``MaskedLKQApplyWGMMA``, one-chunk ``StateApplyConsumersWGMMA``,
214-
fixed-nchunk ``MultiChunkStateApplyConsumersWGMMA``, and
215+
fixed-nchunk ``MultiChunkStateApplyConsumersWGMMA``, Phase-4
216+
``FusedBwdBwdP4``, and
215217
``FA4PatternFused3Gemm`` / ``FA4PatternFused3GemmV2`` classes with
216218
``@cute.kernel`` methods and
217219
literal constructor defaults for their tile-contract dimensions.
@@ -268,6 +270,28 @@ def cute_dsl_source_to_tilelang_prim(
268270
dtype,
269271
nchunks_value,
270272
)
273+
if cls.name == "FusedBwdBwdP4":
274+
if nchunks is None:
275+
raise CuteBridgeUnsupported(
276+
"FusedBwdBwdP4 source import requires explicit nchunks "
277+
"because the CuTe kernel specializes it from the runtime "
278+
"tensor shape."
279+
)
280+
nchunks_value = _positive_int_value(nchunks, "nchunks")
281+
if nchunks_value not in (2, 4, 8):
282+
raise CuteBridgeUnsupported(
283+
"FusedBwdBwdP4 source import supports nchunks in (2, 4, 8); "
284+
f"got {nchunks_value}."
285+
)
286+
n, p, r, chunk_size, dtype = _fused_bwd_bwd_p4_defaults_from_class(cls)
287+
return _build_fused_bwd_bwd_p4_tilelang_prim(
288+
n,
289+
p,
290+
r,
291+
chunk_size,
292+
dtype,
293+
nchunks_value,
294+
)
271295
if cls.name == "FA4PatternFused3Gemm":
272296
dim, dtype = _fa4_fused3_gemm_defaults_from_class(cls)
273297
return _build_fa4_v1_fused3_gemm_tilelang_prim(dim, dtype)
@@ -278,7 +302,7 @@ def cute_dsl_source_to_tilelang_prim(
278302
"source-level CuTeDSL import currently supports only SingleGemmWGMMA, "
279303
"MaskedLKQApplyWGMMA, one-chunk StateApplyConsumersWGMMA, and "
280304
"fixed-nchunk MultiChunkStateApplyConsumersWGMMA plus "
281-
"FA4PatternFused3Gemm / FA4PatternFused3GemmV2; "
305+
"FusedBwdBwdP4 and FA4PatternFused3Gemm / FA4PatternFused3GemmV2; "
282306
f"got {cls.name!r} from {path}."
283307
)
284308

@@ -299,7 +323,7 @@ def _find_cute_kernel_class(
299323
*,
300324
class_name: str | None,
301325
) -> ast.ClassDef:
302-
"""Find the requested or sole ``@cute.kernel`` class in a source AST."""
326+
"""Find the requested or sole CuTeDSL class in a source AST."""
303327

304328
classes = [node for node in tree.body if isinstance(node, ast.ClassDef)]
305329
if class_name is not None:
@@ -309,25 +333,25 @@ def _find_cute_kernel_class(
309333
f"CuTeDSL source does not define class {class_name!r}."
310334
)
311335
candidate = matches[0]
312-
if not _class_has_cute_kernel(candidate):
336+
if not _class_has_cute_entrypoint(candidate):
313337
raise CuteBridgeUnsupported(
314-
f"CuTeDSL source class {class_name!r} has no @cute.kernel method."
338+
f"CuTeDSL source class {class_name!r} has no @cute.kernel or @cute.jit method."
315339
)
316340
return candidate
317341

318342
named = [node for node in classes if node.name == "SingleGemmWGMMA"]
319-
if named and _class_has_cute_kernel(named[0]):
343+
if named and _class_has_cute_entrypoint(named[0]):
320344
return named[0]
321345

322-
cute_classes = [node for node in classes if _class_has_cute_kernel(node)]
346+
cute_classes = [node for node in classes if _class_has_cute_entrypoint(node)]
323347
if len(cute_classes) == 1:
324348
return cute_classes[0]
325349
if not cute_classes:
326350
raise CuteBridgeUnsupported(
327-
"CuTeDSL source does not define a class with a @cute.kernel method."
351+
"CuTeDSL source does not define a class with a @cute.kernel or @cute.jit method."
328352
)
329353
raise CuteBridgeUnsupported(
330-
"CuTeDSL source defines multiple @cute.kernel classes; pass class_name."
354+
"CuTeDSL source defines multiple @cute.kernel/@cute.jit classes; pass class_name."
331355
)
332356

333357

@@ -338,12 +362,13 @@ def _find_class_by_name(tree: ast.Module, class_name: str) -> ast.ClassDef:
338362
raise CuteBridgeUnsupported(f"CuTeDSL source does not define class {class_name!r}.")
339363

340364

341-
def _class_has_cute_kernel(cls: ast.ClassDef) -> bool:
365+
def _class_has_cute_entrypoint(cls: ast.ClassDef) -> bool:
342366
for item in cls.body:
343367
if not isinstance(item, ast.FunctionDef):
344368
continue
345369
if any(
346370
_is_cute_decorator(decorator, "kernel")
371+
or _is_cute_decorator(decorator, "jit")
347372
for decorator in item.decorator_list
348373
):
349374
return True
@@ -479,6 +504,41 @@ def _fa4_fused3_gemm_defaults_from_class(cls: ast.ClassDef) -> tuple[int, str]:
479504
return dim, dtype
480505

481506

507+
def _fused_bwd_bwd_p4_defaults_from_class(
508+
cls: ast.ClassDef,
509+
) -> tuple[int, int, int, int, str]:
510+
init = next(
511+
(
512+
item
513+
for item in cls.body
514+
if isinstance(item, ast.FunctionDef) and item.name == "__init__"
515+
),
516+
None,
517+
)
518+
if init is None:
519+
raise CuteBridgeUnsupported("FusedBwdBwdP4 source has no __init__ method.")
520+
521+
defaults_by_name = _function_defaults_by_name(init)
522+
try:
523+
n = _literal_positive_int(defaults_by_name["N"], "N")
524+
p = _literal_positive_int(defaults_by_name["P"], "P")
525+
r = _literal_positive_int(defaults_by_name["R"], "R")
526+
chunk_size = _literal_positive_int(
527+
defaults_by_name["chunk_size"],
528+
"chunk_size",
529+
)
530+
except KeyError as exc:
531+
raise CuteBridgeUnsupported(
532+
"FusedBwdBwdP4 source __init__ must default N, P, R, "
533+
"and chunk_size."
534+
) from exc
535+
dtype_node = defaults_by_name.get("dtype")
536+
dtype = _normalize_cute_dtype(
537+
_ast_default_to_name(dtype_node or ast.Constant("bfloat16"))
538+
)
539+
return n, p, r, chunk_size, dtype
540+
541+
482542
def _fa4_v2_defaults_from_class(cls: ast.ClassDef) -> tuple[int, str]:
483543
return _fa4_fused3_gemm_defaults_from_class(cls)
484544

@@ -911,6 +971,134 @@ def multi_chunk_state_apply_consumers_wgmma_imported(
911971
return multi_chunk_state_apply_consumers_wgmma_imported
912972

913973

974+
def _build_fused_bwd_bwd_p4_tilelang_prim(
975+
n: int,
976+
p: int,
977+
r: int,
978+
chunk_size: int,
979+
dtype: str,
980+
nchunks: int,
981+
) -> Any:
982+
"""Build TileLang IR for cppmega's Phase-4 10-GEMM CuTe tile contract."""
983+
984+
try:
985+
import tilelang.language as T
986+
except Exception as exc: # pragma: no cover - host/environment dependent
987+
raise CuteBridgeUnsupported(
988+
"FusedBwdBwdP4 import requires a working tilelang import; "
989+
f"got {exc.__class__.__name__}: {exc}."
990+
) from exc
991+
992+
fcs = chunk_size * r
993+
if not (n == p == fcs):
994+
raise CuteBridgeUnsupported(
995+
"FusedBwdBwdP4 source import currently supports the source's "
996+
"single tiled_mma contract N == P == chunk_size * R; got "
997+
f"N={n}, P={p}, chunk_size={chunk_size}, R={r}."
998+
)
999+
1000+
threads = 128
1001+
1002+
@T.prim_func
1003+
def fused_bwd_bwd_p4_imported(
1004+
K: T.Tensor((nchunks, fcs, n), dtype),
1005+
K_T: T.Tensor((nchunks, n, fcs), dtype),
1006+
Q: T.Tensor((nchunks, fcs, n), dtype),
1007+
Q_T: T.Tensor((nchunks, n, fcs), dtype),
1008+
Dst: T.Tensor((nchunks, n, p), dtype),
1009+
DPh: T.Tensor((nchunks, fcs, p), dtype),
1010+
DPh_T: T.Tensor((nchunks, p, fcs), dtype),
1011+
Psi: T.Tensor((nchunks, fcs, p), dtype),
1012+
Sts: T.Tensor((nchunks, n, p), dtype),
1013+
DPsiV: T.Tensor((nchunks, fcs, p), dtype),
1014+
DK: T.Tensor((nchunks, fcs, n), dtype),
1015+
DQ: T.Tensor((nchunks, fcs, n), dtype),
1016+
Dqkd: T.Tensor((nchunks, fcs, fcs), dtype),
1017+
DstatesOut: T.Tensor((n, p), dtype),
1018+
):
1019+
with T.Kernel(1, 1, threads=threads) as (_bx, _by):
1020+
K_sh = T.alloc_shared((fcs, n), dtype)
1021+
K_T_sh = T.alloc_shared((n, fcs), dtype)
1022+
Q_sh = T.alloc_shared((fcs, n), dtype)
1023+
Q_T_sh = T.alloc_shared((n, fcs), dtype)
1024+
Dst_sh = T.alloc_shared((n, p), dtype)
1025+
DPh_sh = T.alloc_shared((fcs, p), dtype)
1026+
DPh_T_sh = T.alloc_shared((p, fcs), dtype)
1027+
Psi_sh = T.alloc_shared((fcs, p), dtype)
1028+
Sts_sh = T.alloc_shared((n, p), dtype)
1029+
LKQ_sh = T.alloc_shared((fcs, fcs), dtype)
1030+
DKI_sh = T.alloc_shared((fcs, fcs), dtype)
1031+
DKI_T_sh = T.alloc_shared((fcs, fcs), dtype)
1032+
1033+
acc_dstates = T.alloc_fragment((n, p), "float32")
1034+
acc_dPsiV = T.alloc_fragment((fcs, p), "float32")
1035+
acc_lkq = T.alloc_fragment((fcs, fcs), "float32")
1036+
acc_dqkd = T.alloc_fragment((fcs, fcs), "float32")
1037+
acc_dk = T.alloc_fragment((fcs, n), "float32")
1038+
acc_dki = T.alloc_fragment((fcs, fcs), "float32")
1039+
acc_dki_T = T.alloc_fragment((fcs, fcs), "float32")
1040+
acc_dq = T.alloc_fragment((fcs, n), "float32")
1041+
1042+
T.clear(acc_dstates)
1043+
for chunk_rev in T.serial(0, nchunks):
1044+
chunk_idx = nchunks - 1 - chunk_rev
1045+
T.copy(K[chunk_idx, 0, 0], K_sh)
1046+
T.copy(K_T[chunk_idx, 0, 0], K_T_sh)
1047+
T.copy(Q[chunk_idx, 0, 0], Q_sh)
1048+
T.copy(Q_T[chunk_idx, 0, 0], Q_T_sh)
1049+
T.copy(Dst[chunk_idx, 0, 0], Dst_sh)
1050+
T.copy(DPh[chunk_idx, 0, 0], DPh_sh)
1051+
T.copy(DPh_T[chunk_idx, 0, 0], DPh_T_sh)
1052+
T.copy(Psi[chunk_idx, 0, 0], Psi_sh)
1053+
T.copy(Sts[chunk_idx, 0, 0], Sts_sh)
1054+
1055+
T.clear(acc_dPsiV)
1056+
T.clear(acc_lkq)
1057+
T.clear(acc_dqkd)
1058+
T.clear(acc_dk)
1059+
T.clear(acc_dki)
1060+
T.clear(acc_dki_T)
1061+
T.clear(acc_dq)
1062+
1063+
# GEMM1: dPsiV = K @ Dst.T
1064+
T.gemm(K_sh, Dst_sh, acc_dPsiV, False, True)
1065+
# GEMM2: lkq = K @ Q.T
1066+
T.gemm(K_sh, Q_sh, acc_lkq, False, True)
1067+
# GEMM4: dqkd = DPh @ Psi.T
1068+
T.gemm(DPh_sh, Psi_sh, acc_dqkd, False, True)
1069+
# GEMM5: dk = Psi @ Dst.T
1070+
T.gemm(Psi_sh, Dst_sh, acc_dk, False, True)
1071+
# GEMM6: dki = Psi @ DPh.T
1072+
T.gemm(Psi_sh, DPh_sh, acc_dki, False, True)
1073+
# GEMM6': dki_T = DPh @ Psi.T
1074+
T.gemm(DPh_sh, Psi_sh, acc_dki_T, False, True)
1075+
# GEMM8: dq = DPh @ Sts.T
1076+
T.gemm(DPh_sh, Sts_sh, acc_dq, False, True)
1077+
# GEMM10: loop-carried dstates += Q.T @ DPh
1078+
T.gemm(Q_T_sh, DPh_T_sh, acc_dstates, False, True)
1079+
1080+
for i, j in T.Parallel(fcs, fcs):
1081+
LKQ_sh[i, j] = T.cast(acc_lkq[i, j], dtype)
1082+
DKI_sh[i, j] = T.cast(acc_dki[i, j], dtype)
1083+
DKI_T_sh[i, j] = T.cast(acc_dki_T[i, j], dtype)
1084+
1085+
# GEMM3: dPsiV += lkq @ DPh.T
1086+
T.gemm(LKQ_sh, DPh_sh, acc_dPsiV, False, True)
1087+
# GEMM7: dk += dki @ Q
1088+
T.gemm(DKI_sh, Q_T_sh, acc_dk, False, True)
1089+
# GEMM9: dq += dki.T @ K
1090+
T.gemm(DKI_T_sh, K_T_sh, acc_dq, False, True)
1091+
1092+
T.copy(acc_dPsiV, DPsiV[chunk_idx, 0, 0])
1093+
T.copy(acc_dk, DK[chunk_idx, 0, 0])
1094+
T.copy(acc_dq, DQ[chunk_idx, 0, 0])
1095+
T.copy(acc_dqkd, Dqkd[chunk_idx, 0, 0])
1096+
1097+
T.copy(acc_dstates, DstatesOut[0, 0])
1098+
1099+
return fused_bwd_bwd_p4_imported
1100+
1101+
9141102
def _build_fa4_v1_fused3_gemm_tilelang_prim(
9151103
dim: int,
9161104
dtype: str,

cppmega_mlx/nn/_tilelang/_engine_dispatch.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,19 @@ def _engine_compile(
9595
compile_target = _as_metal_target(target)
9696

9797
pass_context = _with_pass_context(pass_configs)
98-
if pass_context is None:
99-
artifact = tilelang.compile(prim_func, target=compile_target, out_idx=None)
100-
else:
101-
with pass_context:
98+
try:
99+
if pass_context is None:
102100
artifact = tilelang.compile(prim_func, target=compile_target, out_idx=None)
101+
else:
102+
with pass_context:
103+
artifact = tilelang.compile(prim_func, target=compile_target, out_idx=None)
104+
except ValueError as exc:
105+
message = str(exc)
106+
if "Cannot find global function target.build.tilelang_" in message:
107+
raise RuntimeError(
108+
f"TileLang backend for target {target!r} is unavailable: {message}"
109+
) from exc
110+
raise
103111
try:
104112
setattr(artifact, "_tilelang_engine_target", target)
105113
except (AttributeError, TypeError):

0 commit comments

Comments
 (0)