Integrate MXFP4 hipblaslt GEMM support - #697
Conversation
There was a problem hiding this comment.
Pull request overview
Adds opt-in native MXFP4 GEMM support through hipBLASLt while retaining AITER as the default backend.
Changes:
- Adds MXFP4 datatype, scaling, capability gating, and dispatch.
- Introduces backend-aware quantization layouts.
- Adds native and reference GEMM tests.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
transformer_engine/pytorch/quantization.py |
Selects MXFP4 layouts by backend. |
transformer_engine/pytorch/cpp_extensions/gemm.py |
Routes opted-in MXFP4 GEMMs to hipBLASLt. |
transformer_engine/common/recipe/__init__.py |
Adds swizzled-scale configuration. |
transformer_engine/common/gemm/rocm_gemm.cu |
Implements native MXFP4 hipBLASLt support. |
tests/pytorch/mxfp4/test_mxfp4_gemm_exact.py |
Tests Python MXFP4 backend results. |
tests/cpp/operator/test_cublaslt_gemm.cu |
Tests native MXFP4 GEMMs. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Out of curiosity, why is it disabled by default?
Also, for visibility, #675 does a few of the same things as this PR, but I don't see any conflict.
| # the 32x8 tile order that hipBLASLt's BLK32_UE8M0_32_8_EXT (mode 1001) reads in place | ||
| # -- opt in via the recipe's use_swizzled_scales flag. The AITER a4w4 backend always | ||
| # needs the 16x16 weight shuffle and swizzled scales. | ||
| use_hipblaslt = os.environ.get("NVTE_ROCM_USE_HIPBLASLT_MXFP4", "0") == "1" |
There was a problem hiding this comment.
This could perhaps be
| use_hipblaslt = os.environ.get("NVTE_ROCM_USE_HIPBLASLT_MXFP4", "0") == "1" | |
| use_hipblaslt = bool(int(os.environ.get("NVTE_ROCM_USE_HIPBLASLT_MXFP4", "0"))) |
so that other values besides "1" are also accepted.
| } | ||
| } | ||
| } else if (is_mxfp_scaling(B.scaling_mode)) { | ||
| // MXFP8 |
There was a problem hiding this comment.
I'd suggest just repeating the adjusted comment you have above, instead of deleting it.
| accumulate=accumulate, | ||
| ) | ||
| return result, None, None, None | ||
| if os.environ.get("NVTE_ROCM_USE_HIPBLASLT_MXFP4", "0") != "1": |
There was a problem hiding this comment.
Same recommendation as below regarding the bool change.
| static const float kHostFP4E2M1Table[16] = { | ||
| 0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 6.0f, | ||
| -0.0f,-0.5f,-1.0f,-1.5f,-2.0f,-3.0f,-4.0f,-6.0f}; |
There was a problem hiding this comment.
We have a similar table in rocm_gemm.cu, perhaps this can be factored out (e.g., into a header file).
| } | ||
|
|
||
| // TE MXFP4 path supports only BF16/FP32 output (FP16 F4H kernel exists in hipBLASLt but TE's | ||
| // descriptor finds "no suitable algorithms" for it -- see §3.5/§10.1) and no bias/GELU epilogue. |
There was a problem hiding this comment.
What does "§3.5/§10.1" refer to ?
There was a problem hiding this comment.
Oo, it referred to my design doc. Let me correct it.
| INSTANTIATE_TEST_SUITE_P(OperatorTestMXFP4, Mxfp4GEMMTestSuite, | ||
| ::testing::Combine(::testing::ValuesIn(test_case_sizes_mxfp4), | ||
| ::testing::Values(false), // bias unsupported | ||
| ::testing::Values(kTN), // TN only |
There was a problem hiding this comment.
Is support going to extend soon with bias and non TN?
There was a problem hiding this comment.
Removed the bias, and added support for all layouts TN,NN,NT,TT and corresponding tests in both cpp and torch
|
|
||
| # MXFP4 GEMM: route to AITER a4w4 ASM kernels | ||
| # MXFP4 GEMM: route to AITER a4w4 ASM kernels, unless the hipBLASLt backend is | ||
| # opted in via NVTE_ROCM_USE_HIPBLASLT_MXFP4 |
There was a problem hiding this comment.
We already have NVTE_ROCM_USE_HIPBLASLT_MXFP8. To limit env vars better to combine them to NVTE_ROCM_FORCE_HIPBLASLT. Might be separate PR though
There was a problem hiding this comment.
Will keep this, and make this change in a separate PR.
| // MXFP4 can additionally use hipBLASLt's pre-swizzled UE8M0 scale mode | ||
| // (BLK32_UE8M0_32_8_EXT, "1001"). | ||
| const bool mxfp4_swizzled = use_mxfp4 && inputA->with_gemm_swizzled_scales && | ||
| inputB->with_gemm_swizzled_scales; |
There was a problem hiding this comment.
nit: inputB checking is redundant, as they asserted to be equal
I am currently doing performance runs and see which to enable by default. |
| // writing dst[r, c] = CW[c, r] yields the logical [R, C] operand -- i.e. what nvte_dequantize | ||
| // produces for MXFP8. Used for non-TN layouts, where CanonicalizeGemmInput consumes the | ||
| // column-wise buffer for the non-transposed operand. | ||
| static void dequantize_mxfp4_columnwise_to_bf16(test::Tensor &src_fp4, test::Tensor &dst_bf16, |
There was a problem hiding this comment.
This function is very similar to dequantize_mxfp4_rowwise_to_bf16. The only real difference is which axis is packed/scaled. I think we could have a single parameterized helper
| } | ||
| #endif // __HIP_PLATFORM_AMD__ | ||
|
|
||
| #ifdef __HIP_PLATFORM_AMD__ |
There was a problem hiding this comment.
nit: back to back #ifdef HIP_PLATFORM_AMD can be merged
| (void)cudaGetDeviceProperties(&prop, 0); | ||
|
|
||
| #if !((HIPBLASLT_VERSION_MAJOR > 1) || (HIPBLASLT_VERSION_MAJOR == 1 && HIPBLASLT_VERSION_MINOR >= 3)) | ||
| GTEST_SKIP() << "MXFP4 GEMM requires hipBLASLt >= 1.3"; |
There was a problem hiding this comment.
It is compile time condition so you can guard the test instantiation instead of unconditionally instantiate it and then skip
| if (!(prop.major == 9 && prop.minor == 5)) { | ||
| GTEST_SKIP() << "MXFP4 GEMM is only supported on gfx950"; | ||
| } | ||
| if (params.m % 32 || params.n % 32 || params.k % 256) { |
There was a problem hiding this comment.
it is called with test_case_sizes_mxfp4 so they should already have only supported sizes
Description
Adds a native MXFP4 GEMM path through hipBLASLt (F4F4 kernels) on gfx950 / ROCm ≥ 7.13 /
hipBLASLt ≥ 1.3, alongside the existing AITER
a4w4backend. Until now an MXFP4 GEMM alwaysrouted to AITER and never reached
rocm_gemm.cu; hipBLASLt 1.3 now ships FP4×FP4 + UE8M0block-32 kernels, so this wires MXFP4 into the hipBLASLt path (mirroring the MXFP8 native path)
behind an opt-in toggle, enabling A/B benchmarking against AITER.
The new path is opt-in and regression-safe: with
NVTE_ROCM_USE_HIPBLASLT_MXFP4unset,MXFP4 still routes to AITER with the existing shuffled quantization, so current behavior and
tests are unchanged.
Fixes # (N/A — internal ROCm enablement)
Type of change
Changes
Please list the changes introduced in this PR:
transformer_engine/common/gemm/rocm_gemm.cu):DType::kFloat4E2M1 → HIP_R_4F_E2M1inget_hipblaslt_dtype()and the algo-cachetype_name_map.is_mxfp_scalingcanonicalization and the existingVEC32_UE8M0block-scale arm (wires A/B scale pointers/modes); no separate branch needed.use_nvfp4) so NVFP4 keeps thefallback while MXFP4 stays native.
cublas_gemm: compile-time hipBLASLt ≥ 1.3, runtimegfx950,
K % 256,M/N % 32, BF16/FP32 output only, no bias/GELU,beta == 0.pytorch/cpp_extensions/gemm.py): route MXFP4 to hipBLASLt whenNVTE_ROCM_USE_HIPBLASLT_MXFP4=1, otherwise AITERa4w4(default).pytorch/quantization.py):MXFP4BlockScalingRecipeState.make_quantizersemitsplain (un-shuffled) FP4 data + plain UE8M0 scales when the toggle is on, so operand layout
matches the GEMM backend by construction (AITER-shuffled otherwise).
tests/cpp/operator/test_cublaslt_gemm.cu):OperatorTestMXFP4compares thenative MXFP4 GEMM against a BF16 reference built by CPU-dequantizing the same operands (TN,
BF16/FP32 output, K%256; no MXFP4
nvte_dequantizeexists on ROCm).tests/pytorch/mxfp4/test_mxfp4_gemm_exact.py): parametrized over bothbackends (routed automatically via
monkeypatch, no env var required) vsMXFP4QuantizerRef,plus a direct hipBLASLt-vs-AITER cross-check.
MXFP4 GEMM Performance: hipBLASLt vs AITER
Speedup = aiter_ms / hipblaslt_ms (> 1.0 means the hipBLASLt variant is faster than AITER).
Summary (hipBLASLt vs AITER)
hipblaslt_plain
hipblaslt_swizzled
Takeaways
NVTE_ROCM_USE_HIPBLASLT_MXFP4unset) is justified; hipBLASLt (swizzled) is a competitive alternative that additionally offers native BF16/FP32 output and the full four-layout set including TT.Figures
Geometric-mean speedup vs AITER by pass (dashed line = AITER parity).

Per-config hipBLASLt-swizzled speedup vs AITER across contraction sizes (K); most configs land within ~10% of AITER, with several at or above parity.

Script:
benchmark_mxfp4_hipblaslt_vs_aiter.py