') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Integrate MXFP4 hipblaslt GEMM support by VeeraRajasekhar · Pull Request #697 · ROCm/TransformerEngine · GitHub
Skip to content

Integrate MXFP4 hipblaslt GEMM support - #697

Open
VeeraRajasekhar wants to merge 10 commits into
devfrom
veergopu/maxfp4-hipblaslt-integration
Open

Integrate MXFP4 hipblaslt GEMM support#697
VeeraRajasekhar wants to merge 10 commits into
devfrom
veergopu/maxfp4-hipblaslt-integration

Conversation

@VeeraRajasekhar

@VeeraRajasekharVeeraRajasekhar commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a native MXFP4 GEMM path through hipBLASLt (F4F4 kernels) on gfx950 / ROCm ≥ 7.13 /
hipBLASLt ≥ 1.3, alongside the existing AITER a4w4 backend. Until now an MXFP4 GEMM always
routed to AITER and never reached rocm_gemm.cu; hipBLASLt 1.3 now ships FP4×FP4 + UE8M0
block-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_MXFP4 unset,
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

  • Documentation change (change only to the documentation, either a fix or a new content)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Infra/Build change
  • Code refactoring

Changes

Please list the changes introduced in this PR:

  • C++ (transformer_engine/common/gemm/rocm_gemm.cu):
    • Map DType::kFloat4E2M1 → HIP_R_4F_E2M1 in get_hipblaslt_dtype() and the algo-cache
      type_name_map.
    • Route MXFP4 through the shared is_mxfp_scaling canonicalization and the existing
      VEC32_UE8M0 block-scale arm (wires A/B scale pointers/modes); no separate branch needed.
    • Gate the FP4 dequant→BF16 fallback on the scaling mode (use_nvfp4) so NVFP4 keeps the
      fallback while MXFP4 stays native.
    • Add an MXFP4 capability gate in cublas_gemm: compile-time hipBLASLt ≥ 1.3, runtime
      gfx950, K % 256, M/N % 32, BF16/FP32 output only, no bias/GELU, beta == 0.
  • Python (pytorch/cpp_extensions/gemm.py): route MXFP4 to hipBLASLt when
    NVTE_ROCM_USE_HIPBLASLT_MXFP4=1, otherwise AITER a4w4 (default).
  • Python (pytorch/quantization.py): MXFP4BlockScalingRecipeState.make_quantizers emits
    plain (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 (C++, tests/cpp/operator/test_cublaslt_gemm.cu): OperatorTestMXFP4 compares the
    native MXFP4 GEMM against a BF16 reference built by CPU-dequantizing the same operands (TN,
    BF16/FP32 output, K%256; no MXFP4 nvte_dequantize exists on ROCm).
  • Tests (pytest, tests/pytorch/mxfp4/test_mxfp4_gemm_exact.py): parametrized over both
    backends (routed automatically via monkeypatch, no env var required) vs MXFP4QuantizerRef,
    plus a direct hipBLASLt-vs-AITER cross-check.

MXFP4 GEMM Performance: hipBLASLt vs AITER

  • Device: AMD Instinct MI355X
  • Backends: AITER a4w4 (shuffled weights + swizzled scales) vs hipBLASLt F4F4 in both plain (VEC32_UE8M0) and pre-swizzled (BLK32_UE8M0_32_8_EXT) scale modes. MXFP4, BF16 output.
  • Passes: forward (TN), dgrad (NN), wgrad (NT) -- true per-pass shapes (fwd contracts hidden, dgrad contracts out, wgrad contracts tokens); same FLOPs, different M/N/K.
  • Timing: CUDA events + leading kernel, 15 warmup / 50 iters; TFLOPS = 2·m·n·k / t
  • seqlen: 2048, mbs: [1, 2, 4]

Speedup = aiter_ms / hipblaslt_ms (> 1.0 means the hipBLASLt variant is faster than AITER).

Summary (hipBLASLt vs AITER)

hipblaslt_plain

PassConfigsMedian speedupGeomean speeduphipBLASLt wins
forward (TN)300.68x0.60x0/30
dgrad (NN)300.68x0.62x0/30
wgrad (NT)300.66x0.62x0/30
Overall900.67x0.61x0/90

hipblaslt_swizzled

PassConfigsMedian speedupGeomean speeduphipBLASLt wins
forward (TN)300.95x0.96x7/30
dgrad (NN)300.95x0.95x5/30
wgrad (NT)300.91x0.92x2/30
Overall900.94x0.94x14/90

Takeaways

  • hipBLASLt with pre-swizzled scales (mode 1001) is ~0.94x AITER (geomean) -- effectively on par with the hand-tuned a4w4 ASM kernels.
  • hipBLASLt with plain UE8M0 scales is ~0.61x AITER (geomean); the pre-swizzled scale path is essential for competitive performance.
  • AITER remains marginally faster on aggregate, so keeping it the default (NVTE_ROCM_USE_HIPBLASLT_MXFP4 unset) 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).
performance_summary

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

Script:
benchmark_mxfp4_hipblaslt_vs_aiter.py

@VeeraRajasekharVeeraRajasekhar self-assigned this Aug 7, 2026
@VeeraRajasekhar
VeeraRajasekhar marked this pull request as ready for review August 9, 2026 17:02
@VeeraRajasekharVeeraRajasekhar added the ci-level 3 CI test level 3 label Aug 10, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
FileDescription
transformer_engine/pytorch/quantization.pySelects MXFP4 layouts by backend.
transformer_engine/pytorch/cpp_extensions/gemm.pyRoutes opted-in MXFP4 GEMMs to hipBLASLt.
transformer_engine/common/recipe/__init__.pyAdds swizzled-scale configuration.
transformer_engine/common/gemm/rocm_gemm.cuImplements native MXFP4 hipBLASLt support.
tests/pytorch/mxfp4/test_mxfp4_gemm_exact.pyTests Python MXFP4 backend results.
tests/cpp/operator/test_cublaslt_gemm.cuTests native MXFP4 GEMMs.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment threadtests/cpp/operator/test_cublaslt_gemm.cu Outdated
Comment threadtests/pytorch/mxfp4/test_mxfp4_gemm_exact.py Outdated
Comment threadtransformer_engine/common/gemm/rocm_gemm.cu Outdated

@matthiasdienermatthiasdiener left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadtransformer_engine/pytorch/quantization.py Outdated
}
}
} else if (is_mxfp_scaling(B.scaling_mode)) {
// MXFP8

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest just repeating the adjusted comment you have above, instead of deleting it.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment threadtransformer_engine/pytorch/cpp_extensions/gemm.py Outdated
Comment threadtests/cpp/operator/test_cublaslt_gemm.cu Outdated
Comment threadtests/cpp/operator/test_cublaslt_gemm.cu Outdated
Comment threadtests/cpp/operator/test_cublaslt_gemm.cu Outdated

# 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will keep this, and make this change in a separate PR.

Comment threadtransformer_engine/common/gemm/rocm_gemm.cu Outdated
Comment threadtransformer_engine/common/gemm/rocm_gemm.cu Outdated
Comment threadtransformer_engine/pytorch/cpp_extensions/gemm.py Outdated
@VeeraRajasekhar

Copy link
Copy Markdown
ContributorAuthor

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.

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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment threadtests/cpp/operator/test_cublaslt_gemm.cu Outdated
Comment threadtests/cpp/operator/test_cublaslt_gemm.cu Outdated
Comment threadtests/cpp/operator/test_cublaslt_gemm.cu Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

transformer_engine/pytorch/quantization.py:1765

  • The new backend-dependent quantizer selection is not exercised by the added GEMM tests: those tests instantiate MXFP4Quantizer directly with explicit shuffle flags, so they would still pass if this environment/recipe mapping regressed. Add a recipe-state or module-level test that toggles NVTE_ROCM_USE_HIPBLASLT_MXFP4 and verifies both plain and use_swizzled_scales=True quantizers (including forward weight/activation and backward slots).
 use_hipblaslt = bool(int(os.environ.get("NVTE_ROCM_USE_HIPBLASLT_MXFP4", "0")))
use_swizzled = use_hipblaslt and self.recipe.use_swizzled_scales
# AITER path swizzles scales; hipBLASLt path swizzles only when the recipe opts in.
# FP4 data shuffle stays off on the hipBLASLt path regardless
swizzled_scales = use_swizzled if use_hipblaslt else True

Comment threadtransformer_engine/common/gemm/rocm_gemm.cu
Comment threadtransformer_engine/common/gemm/rocm_fp4_e2m1_table.h
@VeeraRajasekhar

Copy link
Copy Markdown
ContributorAuthor

Planning to rebase and merge after the approvals. Thanks

void *pre_gelu_out = outputPreGelu->data.dptr;
const bool gelu = pre_gelu_out != nullptr;
const bool use_fp8 = is_fp8_dtype(param.Atype) || is_fp8_dtype(param.Btype);
const bool use_mxfp4 = is_mxfp4_scaling(inputA->scaling_mode);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should this also check for the B type (like the fp8 above)?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they are asserted to be equal in line 471

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-level 3CI test level 3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@VeeraRajasekhar@matthiasdiener@aris134@ipanfilo