From 25798de2fe244763e5235bfa5d914516898dcbc8 Mon Sep 17 00:00:00 2001 From: Ivan Butygin Date: Wed, 1 Apr 2026 21:51:21 +0200 Subject: [PATCH 1/7] Add reads_merged flag to track scale read merge status in E2E tests Adds a reads_merged parameter to _dbuf_mxfp4_helper and the 4-wave MXFP4 preshuffle E2E tests. The flag records whether merge_contiguous_reads successfully merges all vector<1xi8> scale loads into wider vectors. Tests assert that the actual merge status matches the declared flag, catching both regressions (merged->unmerged) and improvements (unmerged->merged) that need flag updates. Currently only 128x256x256 with wave_shape (1,4) merges; all other configs have unmerged scale reads. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ivan Butygin --- tests/kernel/wave/asm/test_waveasm_e2e.py | 40 ++++++++++++++++++----- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/tests/kernel/wave/asm/test_waveasm_e2e.py b/tests/kernel/wave/asm/test_waveasm_e2e.py index f3d173b8fb..fc0ac8cccf 100644 --- a/tests/kernel/wave/asm/test_waveasm_e2e.py +++ b/tests/kernel/wave/asm/test_waveasm_e2e.py @@ -1143,6 +1143,7 @@ def _dbuf_mxfp4_helper( reorder_workgroups=None, eliminate_epilogue=False, linearize_reads=True, + reads_merged=None, ): """Shared helper for double-buffered MXFP4 scheduled GEMM tests. @@ -1258,10 +1259,18 @@ def _dbuf_mxfp4_helper( options, gemm, schedule=schedule, dynamic_values=dynamic_values ) - # Verify MLIR contains scaled_mfma operation + # Verify MLIR contains scaled_mfma operation. assert ( "amdgpu.scaled_mfma" in kernel_info.mlir_text ), "Expected amdgpu.scaled_mfma operation in MLIR" + # Check whether scale reads were merged by merge_contiguous_reads. + if reads_merged is not None: + actually_merged = "vector<1xi8>" not in kernel_info.mlir_text + assert actually_merged == reads_merged, ( + f"Scale read merge status changed: expected " + f"{'merged' if reads_merged else 'unmerged'}, " + f"got {'merged' if actually_merged else 'unmerged'}" + ) if dynamic_dims: expected_idx = r"function_type = \([^)]*index, index, index\) -> \(\)" expected_msg = "M, N, and K" @@ -1342,20 +1351,33 @@ def _dbuf_mxfp4_helper( @pytest.mark.parametrize("eliminate_epilogue", [True, False], ids=["ee", "no_ee"]) @pytest.mark.parametrize("output_dtype", ["f32", "bf16"]) @pytest.mark.parametrize( - "shape,block,wave_shape", + "shape,block,wave_shape,reads_merged", [ - pytest.param((1024, 1024, 8192), (128, 256, 256), (1, 4), id="128x256x256"), - pytest.param((1024, 1024, 8192), (128, 32, 256), (2, 2), id="128x32x256"), - pytest.param((896, 640, 8192), (224, 160, 256), (2, 2), id="224x160x256"), - pytest.param((1024, 768, 8192), (256, 192, 256), (1, 4), id="256x192x256"), - pytest.param((1024, 640, 8192), (256, 160, 256), (2, 2), id="256x160x256"), - pytest.param((1024, 896, 8192), (256, 224, 256), (2, 2), id="256x224x256"), + pytest.param( + (1024, 1024, 8192), (128, 256, 256), (1, 4), True, id="128x256x256" + ), + pytest.param( + (1024, 1024, 8192), (128, 32, 256), (2, 2), False, id="128x32x256" + ), + pytest.param( + (896, 640, 8192), (224, 160, 256), (2, 2), False, id="224x160x256" + ), + pytest.param( + (1024, 768, 8192), (256, 192, 256), (1, 4), False, id="256x192x256" + ), + pytest.param( + (1024, 640, 8192), (256, 160, 256), (2, 2), False, id="256x160x256" + ), + pytest.param( + (1024, 896, 8192), (256, 224, 256), (2, 2), False, id="256x224x256" + ), ], ) def test_dbuf_4wave_mxfp4_gemm_cpp_backend( shape, block, wave_shape, + reads_merged, dynamic_dims, use_buffer_ops, use_schedule, @@ -1499,6 +1521,7 @@ def test_dbuf_4wave_mxfp4_gemm_cpp_backend( wave_shape=wave_shape, eliminate_epilogue=eliminate_epilogue, linearize_reads=not skip_linearize, + reads_merged=reads_merged, ) @@ -1534,6 +1557,7 @@ def test_dbuf_4wave_mxfp4_dynamic_mn_reorder_cpp_backend( use_schedule=True, wave_shape=wave_shape, reorder_workgroups=True, + reads_merged=True, ) From 6cdf648ac78512fcf2e957db8e966c0951049410 Mon Sep 17 00:00:00 2001 From: Ivan Butygin Date: Wed, 1 Apr 2026 22:42:02 +0200 Subject: [PATCH 2/7] Extract _mxfp4_config helper for pytest.param generation Auto-generates test IDs from block dimensions instead of duplicating them manually in each pytest.param call. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ivan Butygin --- tests/kernel/wave/asm/test_waveasm_e2e.py | 30 +++++++++-------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/tests/kernel/wave/asm/test_waveasm_e2e.py b/tests/kernel/wave/asm/test_waveasm_e2e.py index fc0ac8cccf..215d208a90 100644 --- a/tests/kernel/wave/asm/test_waveasm_e2e.py +++ b/tests/kernel/wave/asm/test_waveasm_e2e.py @@ -1345,6 +1345,12 @@ def _dbuf_mxfp4_helper( ) +def _mxfp4_config(shape, block, wave_shape, reads_merged=False): + """Build a pytest.param for an MXFP4 preshuffle config with auto-generated id.""" + block_id = f"{block[0]}x{block[1]}x{block[2]}" + return pytest.param(shape, block, wave_shape, reads_merged, id=block_id) + + @param_bool("dynamic_dims", "dyn") @param_bool("use_buffer_ops", "bufops") @param_bool("use_schedule", "sched") @@ -1353,24 +1359,12 @@ def _dbuf_mxfp4_helper( @pytest.mark.parametrize( "shape,block,wave_shape,reads_merged", [ - pytest.param( - (1024, 1024, 8192), (128, 256, 256), (1, 4), True, id="128x256x256" - ), - pytest.param( - (1024, 1024, 8192), (128, 32, 256), (2, 2), False, id="128x32x256" - ), - pytest.param( - (896, 640, 8192), (224, 160, 256), (2, 2), False, id="224x160x256" - ), - pytest.param( - (1024, 768, 8192), (256, 192, 256), (1, 4), False, id="256x192x256" - ), - pytest.param( - (1024, 640, 8192), (256, 160, 256), (2, 2), False, id="256x160x256" - ), - pytest.param( - (1024, 896, 8192), (256, 224, 256), (2, 2), False, id="256x224x256" - ), + _mxfp4_config((1024, 1024, 8192), (128, 256, 256), (1, 4), reads_merged=True), + _mxfp4_config((1024, 1024, 8192), (128, 32, 256), (2, 2)), + _mxfp4_config((896, 640, 8192), (224, 160, 256), (2, 2)), + _mxfp4_config((1024, 768, 8192), (256, 192, 256), (1, 4)), + _mxfp4_config((1024, 640, 8192), (256, 160, 256), (2, 2)), + _mxfp4_config((1024, 896, 8192), (256, 224, 256), (2, 2)), ], ) def test_dbuf_4wave_mxfp4_gemm_cpp_backend( From b005dfdecd3405c03a7a351a8067dc23a4ac49ef Mon Sep 17 00:00:00 2001 From: Ivan Butygin Date: Wed, 1 Apr 2026 22:45:47 +0200 Subject: [PATCH 3/7] Add wave_shape (4,1) and (2,2) configs per PR review Add 128x256x256 with wave_shape (4,1) and 256x192x256 with wave_shape (2,2) to MXFP4 preshuffle E2E tests. Both configs merge scale reads successfully. Include wave_shape in auto-generated test IDs to distinguish configs sharing the same block dimensions. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ivan Butygin --- tests/kernel/wave/asm/test_waveasm_e2e.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/kernel/wave/asm/test_waveasm_e2e.py b/tests/kernel/wave/asm/test_waveasm_e2e.py index 215d208a90..d55b9b4a1f 100644 --- a/tests/kernel/wave/asm/test_waveasm_e2e.py +++ b/tests/kernel/wave/asm/test_waveasm_e2e.py @@ -1347,7 +1347,7 @@ def _dbuf_mxfp4_helper( def _mxfp4_config(shape, block, wave_shape, reads_merged=False): """Build a pytest.param for an MXFP4 preshuffle config with auto-generated id.""" - block_id = f"{block[0]}x{block[1]}x{block[2]}" + block_id = f"{block[0]}x{block[1]}x{block[2]}_w{'x'.join(map(str, wave_shape))}" return pytest.param(shape, block, wave_shape, reads_merged, id=block_id) @@ -1360,9 +1360,11 @@ def _mxfp4_config(shape, block, wave_shape, reads_merged=False): "shape,block,wave_shape,reads_merged", [ _mxfp4_config((1024, 1024, 8192), (128, 256, 256), (1, 4), reads_merged=True), + _mxfp4_config((1024, 1024, 8192), (128, 256, 256), (4, 1), reads_merged=True), _mxfp4_config((1024, 1024, 8192), (128, 32, 256), (2, 2)), _mxfp4_config((896, 640, 8192), (224, 160, 256), (2, 2)), _mxfp4_config((1024, 768, 8192), (256, 192, 256), (1, 4)), + _mxfp4_config((1024, 768, 8192), (256, 192, 256), (2, 2), reads_merged=True), _mxfp4_config((1024, 640, 8192), (256, 160, 256), (2, 2)), _mxfp4_config((1024, 896, 8192), (256, 224, 256), (2, 2)), ], From 765e9d7c1a5b9a75b1785f5678f3952909ce0db1 Mon Sep 17 00:00:00 2001 From: Ivan Butygin Date: Thu, 2 Apr 2026 15:58:39 +0200 Subject: [PATCH 4/7] Replace pytest.xfail with strict xfail markers to detect silent fixes Imperative pytest.xfail() stops the test immediately and never notices when the underlying bug gets fixed. Switch to request.node.add_marker with strict=True so XPASS is reported as a failure, forcing the xfail guard to be removed. Also add VGPR overflow xfail for 128x256x256 with (4,1) wave shape + scheduled pipeline (341 VGPRs needed). Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ivan Butygin --- tests/kernel/wave/asm/test_waveasm_e2e.py | 41 +++++++++++++++-------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/tests/kernel/wave/asm/test_waveasm_e2e.py b/tests/kernel/wave/asm/test_waveasm_e2e.py index d55b9b4a1f..17f01e8125 100644 --- a/tests/kernel/wave/asm/test_waveasm_e2e.py +++ b/tests/kernel/wave/asm/test_waveasm_e2e.py @@ -1381,6 +1381,7 @@ def test_dbuf_4wave_mxfp4_gemm_cpp_backend( output_dtype, compiler, dump_asm, + request, ): """End-to-end test for asymmetric MXFP4 GEMM with 4 waves. @@ -1400,27 +1401,31 @@ def test_dbuf_4wave_mxfp4_gemm_cpp_backend( "not yet supported" ) + def expect_fail(reason): + """Mark test as expected failure; XPASS (strict) catches silent fixes.""" + request.node.add_marker(pytest.mark.xfail(reason=reason, strict=True)) + # VGPR overflow: 256x224x256 scheduled pipeline exceeds 256 VGPR limit. if block_id == "256x224x256" and use_schedule: - pytest.xfail("C++ ASM backend exceeds VGPR limit with scheduled pipeline") + expect_fail("C++ ASM backend exceeds VGPR limit with scheduled pipeline") # VGPR overflow: 224x160x256 scheduled pipeline exceeds 256 VGPR limit # without epilogue elimination; with ee=True it fits for static dims # but dynamic dims adds enough extra VGPRs to overflow again. if block_id == "224x160x256" and use_schedule: if not eliminate_epilogue: - pytest.xfail( + expect_fail( "C++ ASM backend exceeds VGPR limit with scheduled pipeline " "(ee=False) for 224x160x256" ) elif dynamic_dims: - pytest.xfail( + expect_fail( "C++ ASM backend exceeds VGPR limit with ee=True + dynamic " "dims for 224x160x256" ) else: # TODO (Gaurav/Sanket): should be passing after all the cherry-picks - pytest.xfail( + expect_fail( "VGPR overflow: 224x160x256 ee + scheduled pipeline + static " "dims exceeds register limit (register index is out of range)" ) @@ -1429,18 +1434,18 @@ def test_dbuf_4wave_mxfp4_gemm_cpp_backend( # but dynamic dims adds enough extra VGPRs to overflow again. if block_id == "256x160x256" and use_schedule: if not eliminate_epilogue: - pytest.xfail( + expect_fail( "C++ ASM backend exceeds VGPR limit with scheduled pipeline " "(ee=False) for 256x160x256" ) elif dynamic_dims: - pytest.xfail( + expect_fail( "C++ ASM backend exceeds VGPR limit with ee=True + dynamic " "dims for 256x160x256" ) else: # TODO (Gaurav/Sanket): should be passing after all the cherry-picks - pytest.xfail( + expect_fail( "VGPR overflow: 256x160x256 ee + scheduled pipeline + static " "dims exceeds register limit (register index is out of range)" ) @@ -1449,18 +1454,18 @@ def test_dbuf_4wave_mxfp4_gemm_cpp_backend( # enough to pass with static dims; ee=False and dynamic dims still overflow. if block_id == "256x192x256" and use_schedule: if not eliminate_epilogue: - pytest.xfail( + expect_fail( "C++ ASM backend exceeds VGPR limit with scheduled pipeline " "(ee=False); ee=True resolves this for 256x192x256" ) elif dynamic_dims: - pytest.xfail( + expect_fail( "C++ ASM backend exceeds VGPR limit with ee=True + dynamic " "dims for 256x192x256" ) else: # TODO (Gaurav/Sanket): should be passing after all the cherry-picks - pytest.xfail( + expect_fail( "VGPR overflow: 256x192x256 ee + scheduled pipeline + static " "dims exceeds register limit (register index is out of range)" ) @@ -1469,20 +1474,28 @@ def test_dbuf_4wave_mxfp4_gemm_cpp_backend( # dynamic dims when ee=False; dynamic-dims-only when ee=True). if block_id == "128x32x256" and use_schedule: if not eliminate_epilogue: - pytest.xfail( + expect_fail( "Numerical mismatch on (2,2) wave shape with scheduled " "pipeline (ee=False)" ) elif dynamic_dims: - pytest.xfail("Numerical mismatch with dynamic dims on (2,2) wave shape") + expect_fail("Numerical mismatch with dynamic dims on (2,2) wave shape") else: # TODO (Gaurav/Sanket): should be passing after all the cherry-picks - pytest.xfail( + expect_fail( "128x32x256 (2,2) wave shape ee + scheduled pipeline + " "static dims: numerical mismatch (no_bufops) or SGPR " "overflow s103 not available (bufops)" ) + # VGPR overflow: 128x256x256 with (4,1) wave shape and scheduled pipeline + # requires 341 VGPRs but only 256 are available. + if block_id == "128x256x256" and wave_shape == (4, 1) and use_schedule: + expect_fail( + "C++ ASM backend exceeds VGPR limit (341 needed) for " + "128x256x256 (4,1) with scheduled pipeline" + ) + # SGPR overflow: 128x256x256 with ee + scheduled pipeline + buffer ops # exceeds the 102 SGPR limit (static: s103 not available; dynamic: SRD # allocation assertion). @@ -1493,7 +1506,7 @@ def test_dbuf_4wave_mxfp4_gemm_cpp_backend( and use_schedule and use_buffer_ops ): - pytest.xfail( + expect_fail( "C++ ASM backend exceeds SGPR limit for 128x256x256 with " "ee + scheduled pipeline + buffer ops" ) From 89d1625b580619dc35054583a9c5b8dbb636acca Mon Sep 17 00:00:00 2001 From: Ivan Butygin Date: Thu, 2 Apr 2026 16:42:52 +0200 Subject: [PATCH 5/7] skip crashing tests Signed-off-by: Ivan Butygin --- tests/kernel/wave/asm/test_waveasm_e2e.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/kernel/wave/asm/test_waveasm_e2e.py b/tests/kernel/wave/asm/test_waveasm_e2e.py index 17f01e8125..d296a3ff6f 100644 --- a/tests/kernel/wave/asm/test_waveasm_e2e.py +++ b/tests/kernel/wave/asm/test_waveasm_e2e.py @@ -1401,8 +1401,11 @@ def test_dbuf_4wave_mxfp4_gemm_cpp_backend( "not yet supported" ) - def expect_fail(reason): + def expect_fail(reason, is_crashing=False): """Mark test as expected failure; XPASS (strict) catches silent fixes.""" + if is_crashing: + # TODO: Some of those tests segfault, crashing the test runner. + pytest.xfail(reason) request.node.add_marker(pytest.mark.xfail(reason=reason, strict=True)) # VGPR overflow: 256x224x256 scheduled pipeline exceeds 256 VGPR limit. @@ -1447,7 +1450,8 @@ def expect_fail(reason): # TODO (Gaurav/Sanket): should be passing after all the cherry-picks expect_fail( "VGPR overflow: 256x160x256 ee + scheduled pipeline + static " - "dims exceeds register limit (register index is out of range)" + "dims exceeds register limit (register index is out of range)", + is_crashing=True, ) # VGPR overflow for 256x192x256: ee=True reduces register pressure @@ -1485,7 +1489,8 @@ def expect_fail(reason): expect_fail( "128x32x256 (2,2) wave shape ee + scheduled pipeline + " "static dims: numerical mismatch (no_bufops) or SGPR " - "overflow s103 not available (bufops)" + "overflow s103 not available (bufops)", + is_crashing=True, ) # VGPR overflow: 128x256x256 with (4,1) wave shape and scheduled pipeline @@ -1493,7 +1498,8 @@ def expect_fail(reason): if block_id == "128x256x256" and wave_shape == (4, 1) and use_schedule: expect_fail( "C++ ASM backend exceeds VGPR limit (341 needed) for " - "128x256x256 (4,1) with scheduled pipeline" + "128x256x256 (4,1) with scheduled pipeline", + is_crashing=True, ) # SGPR overflow: 128x256x256 with ee + scheduled pipeline + buffer ops From 0ce6ca55f3fa1b0ea335c8ac90133b431dd05df5 Mon Sep 17 00:00:00 2001 From: Ivan Butygin Date: Thu, 2 Apr 2026 17:16:52 +0200 Subject: [PATCH 6/7] xpass Signed-off-by: Ivan Butygin --- tests/kernel/wave/asm/test_waveasm_e2e.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/tests/kernel/wave/asm/test_waveasm_e2e.py b/tests/kernel/wave/asm/test_waveasm_e2e.py index d296a3ff6f..f7d9770558 100644 --- a/tests/kernel/wave/asm/test_waveasm_e2e.py +++ b/tests/kernel/wave/asm/test_waveasm_e2e.py @@ -1502,26 +1502,12 @@ def expect_fail(reason, is_crashing=False): is_crashing=True, ) - # SGPR overflow: 128x256x256 with ee + scheduled pipeline + buffer ops - # exceeds the 102 SGPR limit (static: s103 not available; dynamic: SRD - # allocation assertion). - # TODO (Gaurav/Sanket): should be passing after all the cherry-picks - if ( - block_id == "128x256x256" - and eliminate_epilogue - and use_schedule - and use_buffer_ops - ): - expect_fail( - "C++ ASM backend exceeds SGPR limit for 128x256x256 with " - "ee + scheduled pipeline + buffer ops" - ) - # Linearized reads with dynamic dims produce complex floor/Mod # expressions that cause VGPR overflow or numerical mismatches # across all block configurations in the MXFP4 preshuffle pipeline. skip_linearize = dynamic_dims + _dbuf_mxfp4_helper( shape=shape, block=block, From e07deda4b94cb16527287176e58bd21601ed3da6 Mon Sep 17 00:00:00 2001 From: Ivan Butygin Date: Thu, 2 Apr 2026 17:20:04 +0200 Subject: [PATCH 7/7] crashing Signed-off-by: Ivan Butygin --- tests/kernel/wave/asm/test_waveasm_e2e.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/kernel/wave/asm/test_waveasm_e2e.py b/tests/kernel/wave/asm/test_waveasm_e2e.py index f7d9770558..ad61bf6729 100644 --- a/tests/kernel/wave/asm/test_waveasm_e2e.py +++ b/tests/kernel/wave/asm/test_waveasm_e2e.py @@ -1499,7 +1499,6 @@ def expect_fail(reason, is_crashing=False): expect_fail( "C++ ASM backend exceeds VGPR limit (341 needed) for " "128x256x256 (4,1) with scheduled pipeline", - is_crashing=True, ) # Linearized reads with dynamic dims produce complex floor/Mod @@ -1507,7 +1506,6 @@ def expect_fail(reason, is_crashing=False): # across all block configurations in the MXFP4 preshuffle pipeline. skip_linearize = dynamic_dims - _dbuf_mxfp4_helper( shape=shape, block=block,