44Double-buffered MXFP4 GEMM with 4-wave and 8-wave configurations.
55Uses get_tagged_mxfp4_gemm (templates) + get_mxfp4_dbuf_schedule (schedules).
66
7+ The --splitk N flag enables split-K with N splits for supported tests.
8+
79Usage:
810 python 7.1_schedule.py --test test_dbuf_4wave_mxfp_gemm
9- python 7.1_schedule.py --test test_dbuf_8wave_mxfp_gemm
10- python 7.1_schedule.py --test test_dbuf_8wave_mxfp_gemm --debug
11+ python 7.1_schedule.py --test test_dbuf_4wave_mxfp_gemm --splitk 2
12+ python 7.1_schedule.py --test test_dbuf_8wave_pingpong_mxfp_gemm
13+ python 7.1_schedule.py --test test_dbuf_8wave_pingpong_mxfp_gemm --splitk 2
14+ python 7.1_schedule.py --test test_splitk_preshuffle_scales_gemm_cpp
1115 python 7.1_schedule.py --list_tests
1216"""
1317
2024 SHARED_ADDRESS_SPACE ,
2125)
2226from wave_lang .kernel .wave .compile import wave_compile
27+ from wave_lang .kernel .wave .constraints import ScaledMMAType
2328from wave_lang .kernel .wave .schedules import (
2429 get_mxfp4_asymmetric_schedule ,
2530 get_mxfp4_dbuf_mixed_pingpong_schedule ,
3540 get_tagged_mxfp4_gemm_preshuffle_b_wide_store ,
3641 get_tagged_mxfp4_gemm_preshuffle_scales ,
3742 get_tagged_mxfp4_gemm_preshuffle_scales_and_B ,
43+ get_tagged_splitk_mxfp4_gemm ,
44+ get_tagged_splitk_mxfp4_gemm_preshuffle_b ,
45+ get_tagged_splitk_mxfp4_gemm_preshuffle_scales ,
3846)
3947from wave_lang .kernel .wave .utils .mxfp_utils import (
4048 b_preshuffle ,
@@ -61,7 +69,14 @@ def _run_mxfp_gemm(gemm, shape):
6169
6270
6371def _run_mxfp_gemm_preshuffle (
64- gemm , shape , all = False , only_scale = False , only_b = False , output_dtype = torch .float32
72+ gemm ,
73+ shape ,
74+ all = False ,
75+ only_scale = False ,
76+ only_b = False ,
77+ output_dtype = torch .float32 ,
78+ atol = None ,
79+ rtol = None ,
6580):
6681 """Run compiled GEMM kernel with preshuffled B and B_scale, verify against reference.
6782
@@ -90,8 +105,13 @@ def _run_mxfp_gemm_preshuffle(
90105
91106 gemm (x , x_scales_ps , w_t_ps , w_scales_ps , out )
92107
108+ tol_kwargs = {}
109+ if atol is not None :
110+ tol_kwargs ["atol" ] = atol
111+ if rtol is not None :
112+ tol_kwargs ["rtol" ] = rtol
93113 torch .testing .assert_close (
94- torch_out , out .cpu (), check_dtype = False , check_device = False
114+ torch_out , out .cpu (), check_dtype = False , check_device = False , ** tol_kwargs
95115 )
96116
97117
@@ -137,10 +157,17 @@ def _get_8wave_shape_from_block(block):
137157
138158
139159def test_dbuf_4wave_mxfp_gemm (
140- is_debug = False , shape = (1024 , 1024 , 8192 ), block = (256 , 256 , 256 )
160+ is_debug = False , shape = (1024 , 1024 , 8192 ), block = (256 , 256 , 256 ), splitk = None
141161):
142162 """Double-buffered MXFP4 GEMM, 4 waves, no stagger."""
143- gemm , options = get_tagged_mxfp4_gemm (shape , block , wave_shape = (2 , 2 ))
163+ if splitk and block == (256 , 256 , 256 ):
164+ block = (128 , 128 , 256 )
165+ if splitk :
166+ gemm , options = get_tagged_splitk_mxfp4_gemm (
167+ shape , num_splits = splitk , block_shape = block , wave_shape = (2 , 2 )
168+ )
169+ else :
170+ gemm , options = get_tagged_mxfp4_gemm (shape , block , wave_shape = (2 , 2 ))
144171 schedule = get_mxfp4_dbuf_schedule (use_stagger = False )
145172
146173 options .print_ir_after = "all" if is_debug else []
@@ -150,11 +177,16 @@ def test_dbuf_4wave_mxfp_gemm(
150177 gemm = wave_compile (options , gemm , schedule )
151178
152179 _run_mxfp_gemm (gemm , shape )
153- print ("MXFP GEMM double-buffer 4-wave test passed!" )
180+ sk = f" split-K({ splitk } )" if splitk else ""
181+ print (f"MXFP GEMM double-buffer 4-wave{ sk } test passed!" )
154182
155183
156184def test_dbuf_8wave_pingpong_mxfp_gemm (
157- is_debug = False , shape = (1024 , 1024 , 8192 ), block = (256 , 256 , 256 ), dynamic = False
185+ is_debug = False ,
186+ shape = (1024 , 1024 , 8192 ),
187+ block = (256 , 256 , 256 ),
188+ dynamic = False ,
189+ splitk = None ,
158190):
159191 """Double-buffered MXFP4 GEMM, 8 waves, ping-pong with stagger.
160192 A&B scales are preshuffled and read from global memory directly to VGPRs.
@@ -163,10 +195,17 @@ def test_dbuf_8wave_pingpong_mxfp_gemm(
163195 Note: for dynamic mode, keep block MxN at or below 128x256 or 256x128
164196 to avoid exceeding shared-memory limits.
165197 """
198+ if splitk and block == (256 , 256 , 256 ):
199+ block = (128 , 256 , 256 )
166200 wave_shape = _get_8wave_shape_from_block (block )
167- gemm , options = get_tagged_mxfp4_gemm_preshuffle_scales (
168- shape , block , wave_shape = wave_shape
169- )
201+ if splitk :
202+ gemm , options = get_tagged_splitk_mxfp4_gemm_preshuffle_scales (
203+ shape , num_splits = splitk , block_shape = block , wave_shape = wave_shape
204+ )
205+ else :
206+ gemm , options = get_tagged_mxfp4_gemm_preshuffle_scales (
207+ shape , block , wave_shape = wave_shape
208+ )
170209 options .specialize = True
171210 options .use_buffer_ops = True
172211 options .minimize_shared_allocs = True
@@ -185,19 +224,28 @@ def test_dbuf_8wave_pingpong_mxfp_gemm(
185224
186225 _run_mxfp_gemm_preshuffle (gemm , shape , only_scale = True )
187226 mode = "dynamic" if dynamic else "static"
227+ sk = f", split-K({ splitk } )" if splitk else ""
188228 print (
189- f"MXFP GEMM double-buffer 8-wave ping pong with scale shuffling ({ mode } ) test passed!"
229+ f"MXFP GEMM double-buffer 8-wave ping pong with scale shuffling ({ mode } { sk } ) test passed!"
190230 )
191231
192232
193233def test_dbuf_8wave_pingpong_mxfp_gemm_Bshuffle (
194- is_debug = False , shape = (1024 , 1024 , 8192 ), block = (256 , 256 , 256 ), dynamic = False
234+ is_debug = False ,
235+ shape = (1024 , 1024 , 8192 ),
236+ block = (256 , 256 , 256 ),
237+ dynamic = False ,
238+ splitk = None ,
195239):
196240 """Double-buffered MXFP4 GEMM, 8 waves, ping-pong with stagger.
197241 A&B scales are preshuffled and read from global memory directly to VGPRs.
198242 Same for B data. However, prefetching shuffled B directly to VGPR consumes too many VGPRs and causes spilling.
199243 A is read from global memory directly to LDS.
200244 """
245+ if splitk :
246+ raise NotImplementedError (
247+ "split-K is not yet supported with B-shuffled ping-pong schedule"
248+ )
201249 wave_shape = _get_8wave_shape_from_block (block )
202250 gemm , options = get_tagged_mxfp4_gemm_preshuffle_scales_and_B (
203251 shape , block , wave_shape = wave_shape
@@ -390,7 +438,7 @@ def test_dbuf_8wave_pingpong_mxfp_gemm_Bshuffle_lds_transposed(
390438
391439
392440def test_dbuf_8wave_mixed_pingpong_mxfp_gemm (
393- is_debug = False , shape = (1024 , 1024 , 8192 ), block = (256 , 256 , 256 )
441+ is_debug = False , shape = (1024 , 1024 , 8192 ), block = (256 , 256 , 256 ), splitk = None
394442):
395443 """Double-buffered MXFP4 GEMM, 8 waves, with stagger.
396444
@@ -411,6 +459,10 @@ def test_dbuf_8wave_mixed_pingpong_mxfp_gemm(
411459 barrier releases, effectively hiding the second barrier's latency behind
412460 the early loads and compute.
413461 """
462+ if splitk :
463+ raise NotImplementedError (
464+ "split-K is not yet supported with the mixed ping-pong schedule"
465+ )
414466 gemm , options = get_tagged_mxfp4_gemm (shape , block , wave_shape = (4 , 2 ))
415467 options .specialize = True
416468 options .use_buffer_ops = True
@@ -426,15 +478,18 @@ def test_dbuf_8wave_mixed_pingpong_mxfp_gemm(
426478
427479
428480def test_dbuf_8wave_mixed_pingpong_shuffle_mxfp_gemm (
429- is_debug = False , shape = (16384 , 16384 , 16384 ), block = (256 , 256 , 256 )
481+ is_debug = False , shape = (16384 , 16384 , 16384 ), block = (256 , 256 , 256 ), splitk = None
430482):
431483 """Like :func:`test_dbuf_8wave_mixed_pingpong_mxfp_gemm` but with A_scale & B_scale
432484 preshuffled and prefetched to VGPRs.
433485
434486 Note: preshuffling B and loading it directly to VGPRs combined with prefetching
435487 consumes too many VGPRs and causes spilling.
436488 """
437-
489+ if splitk :
490+ raise NotImplementedError (
491+ "split-K is not yet supported with the mixed ping-pong shuffle schedule"
492+ )
438493 gemm , options = get_tagged_mxfp4_gemm_preshuffle_scales (
439494 shape , block , wave_shape = (4 , 2 )
440495 )
@@ -453,9 +508,13 @@ def test_dbuf_8wave_mixed_pingpong_shuffle_mxfp_gemm(
453508
454509
455510def test_dbuf_4wave_mxfp_asymmetric_gemm (
456- is_debug = False , shape = (1024 , 1024 , 8192 ), block = (256 , 256 , 256 )
511+ is_debug = False , shape = (1024 , 1024 , 8192 ), block = (256 , 256 , 256 ), splitk = None
457512):
458513 """Asymmetric-prefetch MXFP4 GEMM: A through LDS (2x prefetch), B direct from global."""
514+ if splitk :
515+ raise NotImplementedError (
516+ "split-K is not yet supported with the asymmetric schedule"
517+ )
459518 gemm , options = get_tagged_mxfp4_gemm (
460519 shape , block , wave_shape = (1 , 4 ), b_address_space = GLOBAL_ADDRESS_SPACE
461520 )
@@ -481,9 +540,17 @@ def test_dbuf_4wave_mxfp_preshuffle_b_gemm(
481540 shape = (1024 , 1024 , 8192 ),
482541 block = (128 , 256 , 256 ),
483542 eliminate_epilogue = True ,
543+ splitk = None ,
484544):
485545 """Asymmetric MXFP4 GEMM with preshuffled B data and B scales."""
486- gemm , options = get_tagged_mxfp4_gemm_preshuffle_b (shape , block , wave_shape = (1 , 4 ))
546+ if splitk :
547+ gemm , options = get_tagged_splitk_mxfp4_gemm_preshuffle_b (
548+ shape , num_splits = splitk , block_shape = block , wave_shape = (1 , 4 )
549+ )
550+ else :
551+ gemm , options = get_tagged_mxfp4_gemm_preshuffle_b (
552+ shape , block , wave_shape = (1 , 4 )
553+ )
487554 options .minimize_shared_allocs = True
488555 options .linearize_shared_access = True
489556 options .use_buffer_ops = True
@@ -497,13 +564,18 @@ def test_dbuf_4wave_mxfp_preshuffle_b_gemm(
497564 gemm = wave_compile (options , gemm , schedule )
498565
499566 _run_mxfp_gemm_preshuffle (gemm , shape , all = True )
500- print ("MXFP GEMM preshuffle-B 4-wave test passed!" )
567+ sk = f" split-K({ splitk } )" if splitk else ""
568+ print (f"MXFP GEMM preshuffle-B 4-wave{ sk } test passed!" )
501569
502570
503571def test_dbuf_4wave_mxfp_asymmetric_gemm_cpp (
504- is_debug = False , shape = (1024 , 1024 , 8192 ), block = (128 , 256 , 256 )
572+ is_debug = False , shape = (1024 , 1024 , 8192 ), block = (128 , 256 , 256 ), splitk = None
505573):
506574 """Asymmetric MXFP4 GEMM using C++ WaveASM backend (no preshuffle)."""
575+ if splitk :
576+ raise NotImplementedError (
577+ "split-K is not yet supported with the asymmetric C++ backend"
578+ )
507579 gemm , options = get_tagged_mxfp4_gemm (
508580 shape , block , wave_shape = (1 , 4 ), b_address_space = GLOBAL_ADDRESS_SPACE
509581 )
@@ -524,8 +596,14 @@ def test_dbuf_4wave_mxfp_preshuffle_b_gemm_cpp(
524596 shape = (512 , 1024 , 8192 ), # 4*T0, 4*T1, 8192
525597 block = (128 , 256 , 256 ),
526598 eliminate_epilogue = True ,
599+ splitk = None ,
527600):
528601 """Preshuffle-B MXFP4 GEMM using C++ WaveASM backend."""
602+ if splitk :
603+ raise NotImplementedError (
604+ "split-K with WaveASM backend hits register alignment errors in "
605+ "the assembler; use test_dbuf_4wave_mxfp_preshuffle_b_gemm instead"
606+ )
529607 gemm , options = get_tagged_mxfp4_gemm_preshuffle_b (shape , block , wave_shape = (1 , 4 ))
530608 options .backend = "asm"
531609 options .use_buffer_ops = True
@@ -551,8 +629,13 @@ def test_dbuf_4wave_mxfp_dynamic_preshuffle_b_gemm(
551629 shape = (1024 , 1024 , 8192 ),
552630 block = (128 , 256 , 256 ),
553631 eliminate_epilogue = True ,
632+ splitk = None ,
554633):
555634 """Preshuffle-B MXFP4 GEMM with dynamic M, N, K."""
635+ if splitk :
636+ raise NotImplementedError (
637+ "split-K is not yet supported with the dynamic preshuffle-B schedule"
638+ )
556639 gemm , options = get_tagged_mxfp4_gemm_preshuffle_b (shape , block , wave_shape = (1 , 4 ))
557640 # Make M, N, K dynamic so the compiler does not specialize on problem size.
558641 dynamic_symbols = [tkl .sym .M , tkl .sym .N , tkl .sym .K ]
@@ -647,6 +730,33 @@ def test_dbuf_4wave_mxfp_dynamic_preshuffle_b_gemm_asm(
647730 )
648731
649732
733+ def test_splitk_preshuffle_scales_gemm_cpp (
734+ is_debug = False ,
735+ shape = (1024 , 1024 , 8192 ),
736+ block = (128 , 128 , 256 ),
737+ splitk = None ,
738+ ):
739+ """Split-K MXFP4 GEMM using C++ WaveASM backend (preshuffled scales)."""
740+ num_splits = splitk if splitk else 2
741+ splitk_fn , options = get_tagged_splitk_mxfp4_gemm_preshuffle_scales (
742+ shape ,
743+ num_splits = num_splits ,
744+ mfma_variant = ScaledMMAType .F32_16x16x128_F8F6F4 ,
745+ block_shape = block ,
746+ wave_shape = (2 , 2 ),
747+ output_type = tkl .f32 ,
748+ )
749+ options .backend = "asm"
750+ options .wave_runtime = True
751+ options .use_wave_asm_backend = True
752+ options .print_ir_after = "all" if is_debug else []
753+ options = set_default_run_config (options )
754+ gemm = wave_compile (options , splitk_fn )
755+
756+ _run_mxfp_gemm_preshuffle (gemm , shape , only_scale = True )
757+ print ("Split-K MXFP4 GEMM (preshuffled scales, WaveASM backend) test passed!" )
758+
759+
650760if __name__ == "__main__" :
651761 args = parse_args ()
652762
@@ -667,5 +777,6 @@ def test_dbuf_4wave_mxfp_dynamic_preshuffle_b_gemm_asm(
667777 args .shape ,
668778 args .block ,
669779 args .eliminate_epilogue ,
780+ args .splitk ,
670781 )
671782 exit (0 if success else 1 )
0 commit comments