Skip to content

fix(gemm): handle linear MXFP8 scales in bmm_mxfp8 cuDNN - #3798

Open
coder-2011 wants to merge 8 commits into
flashinfer-ai:mainfrom
coder-2011:fix-bmm-mxfp8-scale-reordering
Open

coder-2011 wants to merge 8 commits into
flashinfer-ai:mainfrom
coder-2011:fix-bmm-mxfp8-scale-reordering

Conversation

@coder-2011

@coder-2011 coder-2011 commented Jul 2, 2026

Copy link
Copy Markdown

Description

Fixes bmm_mxfp8 cuDNN scale-layout handling for MXFP8 linear scale tensors.

Previously, bmm_mxfp8 always built the cuDNN MXFP8 graph with scale tensors declared as F8_128x4. That was correct for swizzled scales, but incorrect when the inputs were quantized with is_sf_swizzled_layout=False, which produces linear scales.

This PR adds sf_layout argument to bmm_mxfp8 and maps it to the cuDNN scale tensor reordering, where

  • SfLayout.layout_linear -> cudnn.tensor_reordering.NONE
  • SfLayout.layout_128x4 -> cudnn.tensor_reordering.F8_128x4

The existing default remains SfLayout.layout_128x4 to preserve current behavior. CUTLASS BMM continues to require 128x4 swizzled scales.

The BMM MXFP8 test now passes the scale layout used by the test into bmm_mxfp8, while leaving quantization behavior unchanged.

Related Issues

Fixes #3663

Pull Request Checklist

Pre-commit Checks

  • I have installed pre-commit by running pip install pre-commit (or used your preferred method).
  • I have installed the hooks with pre-commit install.
  • I have run the hooks manually with pre-commit run --all-files and fixed any reported issues.

Tests

  • Tests have been added or updated as needed.
  • Relevant tests are passing.

Tested with:

.venv/bin/pre-commit run --all-files
.venv/bin/python -m pytest -q \
  'tests/gemm/test_bmm_mxfp8.py::test_bmm_mxfp8[False-cudnn-res_dtype0-False-input_dtype0-128-512-128-1]' \
  'tests/gemm/test_bmm_mxfp8.py::test_bmm_mxfp8[False-cudnn-res_dtype0-True-input_dtype0-128-512-128-1]' \
  'tests/gemm/test_bmm_mxfp8.py::test_bmm_mxfp8[True-cudnn-res_dtype0-False-input_dtype0-128-512-128-1]' \
  'tests/gemm/test_bmm_mxfp8.py::test_bmm_mxfp8[True-cudnn-res_dtype0-True-input_dtype0-128-512-128-1]'

Random-seed stress for the original failing cuDNN linear-scale shape:

backend=cudnn
input_dtype=torch.bfloat16
res_dtype=torch.bfloat16
is_sf_swizzled_layout=False
sf_layout=SfLayout.layout_linear
sampled seeds=10,000 unique seeds from range(0, 2**31)
minimum cosine=0.94921875

Summary by CodeRabbit

Summary

  • New Features
    • Added runtime-selectable FP8 scale reordering for MXFP8 GEMM/BMM, supporting linear and 128x4 layouts.
    • Extended bmm_mxfp8 with a new keyword-only sf_layout argument to choose the scale format.
  • Bug Fixes
    • Improved cuDNN MXFP8 autotuning/execution so selected scale-layout tactics don’t mix across different reordering layouts.
    • Added stricter validation for supported sf_layout options across backends.
  • Tests
    • Updated MXFP8 BMM tests to cover both supported sf_layout values.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds keyword-only sf_layout handling to bmm_mxfp8, propagates its cuDNN scale-reordering mapping through MXFP8 graph builders and tuner execution, separates autotuner cache entries by layout, and updates coverage for linear and swizzled layouts.

Changes

MXFP8 sf_layout / scale_reordering support

Layer / File(s) Summary
cuDNN MXFP8 graph builders accept scale_reordering
flashinfer/gemm/gemm_base.py
Both graph builders accept scale_reordering and apply it to block descale tensor descriptors, defaulting to F8_128x4.
Execution and tuner runner threading
flashinfer/gemm/gemm_base.py
The cuDNN MXFP8 execution and runner paths propagate scale_reordering, include it in tuner cache keys, and forward it from mxfp8_gemm_sm100.
bmm_mxfp8 layout validation and dispatch
flashinfer/gemm/gemm_base.py
bmm_mxfp8 adds keyword-only sf_layout; cuDNN supports linear and 128x4 layouts, while CUTLASS requires 128x4.
Test coverage for sf_layout
tests/gemm/test_bmm_mxfp8.py
The test derives SfLayout from the swizzled-layout parameter and passes it to bmm_mxfp8.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant bmm_mxfp8
  participant mxfp8_gemm_sm100
  participant cudnn_runner
  participant graph_builder

  Caller->>bmm_mxfp8: call with sf_layout
  bmm_mxfp8->>bmm_mxfp8: validate and map layout
  bmm_mxfp8->>mxfp8_gemm_sm100: pass scale_reordering
  mxfp8_gemm_sm100->>cudnn_runner: provide reordering for tuning
  cudnn_runner->>graph_builder: build graph with reordering
Loading

Possibly related issues

  • Issue 3663: The updated test explicitly exercises scale-layout selection related to the reported MXFP8 cosine-similarity failure.
  • Issue 3841: Directly overlaps the new sf_layout handling and cuDNN MXFP8 scale tensor reordering.

Possibly related PRs

Suggested reviewers: aleozlx, yzh119, saltyminty

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the MXFP8 cuDNN scale-layout fix in bmm_mxfp8.
Description check ✅ Passed The description matches the template and includes the change summary, related issue, checklist, and test notes.
Linked Issues check ✅ Passed The changes address #3663 by mapping linear MXFP8 scales to cuDNN NONE while preserving 128x4 behavior and updating the test.
Out of Scope Changes check ✅ Passed The extra API and tuner updates are supporting the MXFP8 scale-layout fix and do not appear unrelated.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces support for configurable scale layouts (sf_layout) in MXFP8 GEMM operations, allowing the cuDNN backend to handle both linear and 128x4 swizzled layouts, while restricting the CUTLASS backend to 128x4 swizzled layouts. Feedback suggests updating _cudnn_bmm_mxfp8_requirement to return False for unsupported layouts to ensure correct backend routing.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread flashinfer/gemm/gemm_base.py
@coder-2011
coder-2011 marked this pull request as ready for review July 2, 2026 02:00

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@flashinfer/gemm/gemm_base.py`:
- Around line 8723-8728: The bmm_mxfp8 backend gating in the cuDNN suitability
check only validates sf_layout and cuDNN availability, so add a
compute-capability guard in this path to reject non-SM10 devices. Update the
backend selection logic around the sf_layout check to inspect the current device
capability before calling _cudnn_available_or_raise_for_backend, and return
False for SM12/other unsupported devices so backend="auto" cannot fall through
to an unsupported cuDNN path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 569088b7-d049-490e-b3b1-ebcbf2345920

📥 Commits

Reviewing files that changed from the base of the PR and between 651d877 and f44f84c.

📒 Files selected for processing (2)
  • flashinfer/gemm/gemm_base.py
  • tests/gemm/test_bmm_mxfp8.py

Comment thread flashinfer/gemm/gemm_base.py
@coder-2011

coder-2011 commented Jul 3, 2026

Copy link
Copy Markdown
Author

ping @aleozlx @yzh119

@waynehacking8

Copy link
Copy Markdown
Contributor

Ran this on an RTX PRO 6000 (SM120, CUDA 13, cudnn-frontend 1.25.0 / cudnn 9.19), since the cudnn path is admitted there at runtime (_cudnn_bmm_mxfp8_requirement allows [100, 103, 110, 120, 121]) even though the test file skips cudnn for anything but SM10x. The fix checks out: with linear scales on the cudnn backend, main produces scrambled output against a reference computed from the dequantized fp8 operands (median rel err ~0.99, cos 0.37) while this branch is exact modulo output rounding (max rel err 0.4%, cos 0.999999). Swizzled is unchanged between main and this branch (cos 0.9993 vs the pre-quant reference) and agrees with the cutlass backend, so no regression there.

One thing I had to work around to measure this, worth noting while you're in the test file: plain randn inputs can't see this bug -- every 32-block quantizes to nearly the same e8m0 scale, so main's broken linear path still scores cos 0.93 on my box and clears the test's 0.9 bar (with a median per-element rel error of 31%). I only got discrimination by scaling each 32-element block by a 2^-4..2^4 ladder and checking per-element error against the dequantized-operand reference.

That ladder input also exposed an issue in the existing test construction that your PR inherits: the test quantizes mat2 as contiguous [b, k, n], which blocks scales along n, while the graph declares b_descale as [b, k/32, n] with K-major strides (and the bmm_mxfp8 docstring says to pass B column-major). With block-varying magnitudes even the intended swizzled config drops to cos 0.756 using the test's construction, vs 0.9993 when B is quantized as [b, n, k] (blocks along k) and passed transposed -- on both cudnn and cutlass, main and PR alike. So as written the test can't catch scale-layout bugs, which is presumably how the linear-layout one shipped silently. Happy to share the probe script if useful.

@coder-2011

Copy link
Copy Markdown
Author

Thx, I think the B-axis construction bug can be solved in a future PR, probably by me.

@coder-2011

Copy link
Copy Markdown
Author

@dhiraj113 @yzh119 @aleozlx @bkryu

I woud appreciate if someone can take a look a this!

@aleozlx

aleozlx commented Jul 12, 2026

Copy link
Copy Markdown
Member

let me help you escalate.

adding @YangXu1990uiuc

_check_cudnn_override_shape_availability()
if policy is None:
policy = cudnn.build_plan_policy.HEURISTICS_CHOICE
if scale_reordering is None:

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.

Can you do this through the function default argument instead.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I might be mistaken but, cudnn.* objects are never function defaults in the codebase, bc flashinfer would crash if we dont have cudnn imported.

ex.

def build_cudnn_gemm_fp4_graph(
    ...,
    policy=None,
):
    if policy is None:
        policy = cudnn.build_plan_policy.HEURISTICS_CHOICE

agent said that there arent any cudnn.* function defaults in flashinfer

block_size,
o_type, # cudnn.data_type, BF16 or FP16
device,
scale_reordering=None,

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.

Same here. Make the default value explicit in the function args.

out: Optional[torch.Tensor] = None,
workspace_buffer: torch.Tensor = None,
tactic: int = -1,
scale_reordering=None,

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.

here too.

Comment thread flashinfer/gemm/gemm_base.py Outdated


def _cudnn_gemm_mxfp8_runner():
def _cudnn_gemm_mxfp8_runner(scale_reordering):

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.

This looks incorrect. Why do you need pass scale_reordering this way when other things are not being passed like this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed!

@dhiraj113 dhiraj113 Jul 14, 2026

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.

follow the style of def mm_fp4.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed that too

Comment thread flashinfer/gemm/gemm_base.py Outdated
dtype: torch.dtype,
out: Optional[torch.Tensor] = None,
backend: Literal["cudnn", "cutlass", "auto"] = "auto",
*,

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.

API shouldn't be extended like this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed!

Comment thread flashinfer/gemm/gemm_base.py
@dhiraj113

Copy link
Copy Markdown
Collaborator

Also, the test in #3663 doesn't pass with your change.

@dhiraj113

Copy link
Copy Markdown
Collaborator

Here's the code review from Claude. See if it helps -

Code Review: PR #3798 — fix(gemm): handle linear MXFP8 scales in bmm_mxfp8 cuDNN
What it does: Threads a scale_reordering parameter through the cuDNN MXFP8 GEMM graph builders/runners and adds a keyword-only sf_layout argument to bmm_mxfp8, mapping SfLayout.layout_linear → cudnn.tensor_reordering.NONE and layout_128x4 → F8_128x4. Previously the reordering was hardcoded to F8_128x4, so linearly-arranged scales (from mxfp8_quantize(..., is_sf_swizzled_layout=False)) were misinterpreted. The core mapping logic, the str(scale_reordering) cache-key addition, and the sf_layout forwarding through @backend_requirement are all correct — with a correctly-constructed B I measured cos_sim=1.0000 for both layouts.

I set up git (added coder-2011 remote, checked out fix-bmm-mxfp8-scale-reordering) and verified findings by running on the branch. Findings ranked most-severe first:

  1. tests/gemm/test_bmm_mxfp8.py:40-44 — The PR does not make its target test pass; the cuDNN/SM100 case still errors out.
    The PR claims to fix [Bug] test_bmm_mxfp8 Cosine similarity 0.8984 is too low (expected > 0.9) on B300 #3663 (B300 = SM100, cuDNN backend) and edits this exact test, but running the target case on the branch still fails:

ValueError: cuDNN mxfp8 GEMM requires B to be column-major [batch, k, n]
(stride [k*n, 1, k] …); got b.shape=(16, 128, 512), b.stride()=(65536, 512, 1).
The test builds mat2 = randn([b,n,k]).transpose(-2,-1).contiguous(), and .contiguous() makes B row-major — rejected by the cuDNN column-major guard (added in #3489). This hits both linear and swizzled cuDNN cases. The PR adds sf_layout= plumbing but never fixes the mat2 construction, so on the very hardware the issue is about, the test errors before the kernel runs. Fix: quantize the [b,n,k] weight and transpose the quantized result without .contiguous() (exactly what the error message prescribes). I confirmed that with correct B construction the fix yields cos_sim=1.0.

  1. flashinfer/gemm/gemm_base.py:8559-8571 — Linear-scale path reuses the 128x4-padded descriptor → silently wrong results for non-aligned shapes.
    _calculate_block_scale_dims always pads m→⌈m/128⌉·128, n→⌈n/128⌉·128, k/32→⌈·/4⌉·4. The graph builder uses these padded dims for the scale-tensor descriptor regardless of layout. But linear scales are unpadded (numel = b·m·⌈k/32⌉), so when m, n, or k/32 isn't 128x4-aligned, cuDNN reads with a wrong-shaped/strided descriptor. Empirically, k=96 (k/32=3): linear → cos_sim=0.7031 (wrong), swizzled → 1.0000. The test grid only uses aligned shapes (m,n ∈ {128,256,512}, k ∈ {128,256,512,1024}), so it never catches this. For the linear path the descriptor should use the unpadded dims [b, m, ⌈k/32⌉] / [b, ⌈k/32⌉, n], not the padded ones. At minimum, the linear layout should be validated/rejected for non-aligned shapes and the docstring should state the alignment restriction.

  2. flashinfer/gemm/gemm_base.py:8794-8808 — mxfp8_gemm_sm100 adds scale_reordering as a required positional parameter (no default), inconsistent with every other touched function.
    Every other function in this PR uses scale_reordering=None. Here it's required-positional. Only one caller exists so it doesn't break today, but it's a gratuitous API-shape inconsistency and a latent break for any future caller. Low severity — give it a default (= None) for consistency.

Bottom line: The fix's logic is sound and works for the aligned shapes the test uses, but (1) the shipped test still fails on the issue's own hardware because the unrelated mat2 row-major bug isn't fixed, and (2) the linear path is only correct for 128x4-aligned shapes — a real latent correctness bug the test can't see. I'd ask the author to fix the test's B construction (so the test actually exercises and validates the fix) and either generalize the descriptor for linear layout or explicitly guard/document the alignment restriction. The branch is checked out locally if you want to iterate on it.

@coder-2011

coder-2011 commented Jul 14, 2026

Copy link
Copy Markdown
Author

Tests do pass on my end, on cuDNN 9.10.2. There was a failure bc of other unrelated issues.

Claude explicitly said:

This hits both linear and swizzled cuDNN cases. The PR adds sf_layout= plumbing but never fixes the mat2 construction, so on the very hardware the issue is about, the test errors before the kernel runs.

My tests say the same thing. I can fix this in a follow up, or now if u think thats better.

I appreciate the thorough review, I learned alot! lmk if I made any stupid mistakes, and I will fix them. I would love to contribute to flashinfer alot more in the future

@dhiraj113

@coder-2011
coder-2011 requested a review from dhiraj113 July 14, 2026 05:27
@coder-2011
coder-2011 force-pushed the fix-bmm-mxfp8-scale-reordering branch from 9e98766 to 3737f1b Compare July 18, 2026 07:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] test_bmm_mxfp8 Cosine similarity 0.8984 is too low (expected > 0.9) on B300

5 participants