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
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+
482542def _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+
9141102def _build_fa4_v1_fused3_gemm_tilelang_prim (
9151103 dim : int ,
9161104 dtype : str ,
0 commit comments