Skip to content

fix(moe): MxFp8 weight scales must be 128x4 interleaved after the row shuffle (#4087) - #4093

Open
YangXu1990uiuc wants to merge 1 commit into
mainfrom
fix-4087-mxfp8-sf-interleave
Open

YangXu1990uiuc wants to merge 1 commit into
mainfrom
fix-4087-mxfp8-sf-interleave

Conversation

@YangXu1990uiuc

@YangXu1990uiuc YangXu1990uiuc commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

📌 Description

Fixes the MxFp8 half of #4087. The unified-MoE MxFp8 weight preparation applied only the row permutation to W1/W2 scale factors, but the trtllm-gen kernels consume the legacy shuffle_matrix_sf_a layout: row permutation then 128x4 block_scale_interleave. The runner forces use_shuffled_weight=True for MxFp8 and the C++ launcher hard-requires it, so ~99.8% of scale bytes were read from the wrong interleaved address. Errors stayed sparse and sub-tolerance in existing tests because random weights repeat E8M0 exponents (a misplaced read often lands on an equal exponent and cancels), the original layout unit test hard-coded the permutation-only layout as expected, and the functional test's atol=0.05 dwarfs the ~0.0025 output scale. Root-cause credit: external review pass on the #4087 coordinate-dump forensics.

Changes

  • prepare.py MxFp8 branch: interleave the permuted scales for both W1 and W2, then reshape back to the logical (rows, cols) view the runner validates (the kernel consumes the raw interleaved bytes) — the legacy pipeline's exact idiom.
  • Explicit precondition: the shuffled-weight layout requires hidden_size and 2*intermediate_size divisible by 128 (the same constraint the legacy path carried implicitly); clear ValueError instead of a confusing downstream reshape failure.
  • tests/moe/test_mxfp8_weight_prepare_layout.py: the w2 (non-gated) recipe is compared byte-exact against legacy shuffle_matrix_sf_a using sentinel data where every scale byte is distinct (with random data, misplacements cancel — the sentinel is what makes the test able to fail); a gated-path guard asserts the shipped layout is not permutation-only.

Validation (B200-class SM100, main @ 6104afe)

🔍 Related Issues

Fixes the MxFp8 layout half of #4087. The deepseekfp8 reference calibration and the nvfp4-FL s120 failure remain tracked there.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved MXFP8 weight-scale layout handling for shuffled weight formats.
    • Added validation for supported tensor dimensions to prevent invalid configurations.
    • Ensured scale data is correctly interleaved and reshaped for affected weight paths.
  • Tests

    • Added GPU-specific regression coverage to verify scale ordering, layout correctness, and data preservation.

… shuffle (gh #4087)

The MxFp8 unified-MoE weight preparation applied only the row permutation
to W1/W2 scale factors, but the trtllm-gen kernels consume the legacy
shuffle_matrix_sf_a layout: row permutation THEN 128x4
block_scale_interleave. The runner forces use_shuffled_weight=True for
MxFp8 and the C++ launcher hard-requires it, so ~99.8% of scale bytes were
read from the wrong interleaved address. The corruption stayed sparse and
sub-tolerance in existing tests because random weights repeat E8M0
exponents (misplaced reads often land on an equal exponent), the original
layout unit test hard-coded the permutation-only layout as expected, and
the functional test's atol=0.05 dwarfs the ~0.0025 output scale.

Fix: interleave the permuted scales for both W1 and W2, matching the
legacy pipeline byte-for-byte.

Tests: tests/moe/test_mxfp8_weight_prepare_layout.py — the w2 (non-gated)
recipe is compared byte-exact against legacy shuffle_matrix_sf_a using
sentinel data where every scale byte is distinct (with random data,
misplacements cancel); a gated-path guard asserts the shipped layout is
not permutation-only. Validated on B200: 4/4 previously-failing fuzzer
configs pass with this change (full 16-config sweep in the PR).

AI-assisted (root cause identified via coordinate-dump forensics and an
external review pass; A/B validated on live SM100 hardware).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The MXFP8 weight preparation path now validates scale-layout dimensions and applies block_scale_interleave to per-expert w1_sf and w2_sf tensors. New SM100-gated tests verify interleaving and byte preservation with deterministic sentinel data.

Changes

MXFP8 scale layout

Layer / File(s) Summary
Implement MXFP8 scale interleaving
flashinfer/fused_moe/prepare.py
The MXFP8 path requires hidden_size and 2 * intermediate_size to be divisible by 128, then interleaves and reshapes w1_sf and w2_sf scale tensors.
Validate prepared scale layouts
tests/moe/test_mxfp8_weight_prepare_layout.py
CUDA-gated parametrized tests use sentinel scale bytes to compare the w2 layout with the legacy reference and verify the w3/w1 path is interleaved without byte loss or duplication.

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

Possibly related PRs

Suggested reviewers: aleozlx, yzh119, jiahanc

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely and accurately summarizes the MxFp8 weight-scale interleaving fix and related issue.
Description check ✅ Passed The description follows the template well with a clear summary, related issue, and validation/testing details.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-4087-mxfp8-sf-interleave

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.

@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 `@tests/moe/test_mxfp8_weight_prepare_layout.py`:
- Around line 40-88: Strengthen the regression tests by exercising
prepare_trtllm_fp8_block_weights rather than only composing
block_scale_interleave and permutation helpers directly. In both w2 and gated
w3/w1 cases, compare the API’s two returned scale tensors with the
sentinel-derived expected legacy/interleaved layouts, including shape and
byte-content checks. Add invalid-dimension parametrized cases that assert
prepare_trtllm_fp8_block_weights raises ValueError.
🪄 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: 6b02798c-7b28-4860-b457-6f799922a810

📥 Commits

Reviewing files that changed from the base of the PR and between d25d2d6 and fcc2d4f.

📒 Files selected for processing (2)
  • flashinfer/fused_moe/prepare.py
  • tests/moe/test_mxfp8_weight_prepare_layout.py

Comment on lines +40 to +88
@pytest.mark.parametrize("rows,cols", [(1024, 768), (256, 512), (2048, 1024)])
def test_w2_sf_shuffle_matches_legacy(rows, cols):
"""Non-gated (w2) scale path: permute+interleave == shuffle_matrix_sf_a."""
_skip_unless_sm100()
from flashinfer.fused_moe.core import get_w2_permute_indices_with_cache
from flashinfer.quantization.fp4_quantization import shuffle_matrix_sf_a

dev = torch.device("cuda:0")
sf = _sentinel_sf(rows, cols // 32, dev)

cache = {}
permute_sf = get_w2_permute_indices_with_cache(cache, sf, 128, num_elts_per_sf=32)
got = block_scale_interleave(sf[permute_sf.to(dev)].contiguous())

want = shuffle_matrix_sf_a(sf, 128, num_elts_per_sf=32)

assert got.shape == want.shape, f"{got.shape} vs {want.shape}"
n_diff = int((got != want).sum())
assert n_diff == 0, f"{n_diff}/{want.numel()} scale bytes misplaced"


@pytest.mark.parametrize("rows,cols", [(2 * 768, 1024), (2 * 256, 512)])
def test_w31_sf_is_interleaved(rows, cols):
"""Gated (w3_w1) scale path: the result must be 128x4-interleaved, i.e.
NOT equal to the permutation-only layout #4026 produced (the exact gh
#4087 regression), and elementwise-preserving (a permutation of bytes)."""
_skip_unless_sm100()
from flashinfer.fused_moe.core import _maybe_get_cached_w3_w1_permute_indices

dev = torch.device("cuda:0")
sf = _sentinel_sf(rows, cols // 32, dev)

cache = {}
permute_sf = _maybe_get_cached_w3_w1_permute_indices(
cache, sf, 128, num_elts_per_sf=32, is_gated_act_gemm=True
)
permuted_only = sf[permute_sf.to(dev)].contiguous()
got = block_scale_interleave(permuted_only)

# Regression guard: the shipped layout must NOT be the permutation-only
# layout #4026 produced (the exact gh #4087 regression).
assert not torch.equal(
got.flatten()[: permuted_only.numel()], permuted_only.flatten()
), "interleave was a no-op -- layout regression (gh #4087)"
if got.numel() == permuted_only.numel():
# Pure relayout (no padding): byte multiset must be preserved.
assert torch.equal(
got.flatten().sort().values, permuted_only.flatten().sort().values
), "interleave lost/duplicated scale bytes"

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Exercise prepare_trtllm_fp8_block_weights in these regressions.

Both tests construct got directly from the expected primitives, so they still pass if prepare.py skips interleaving, uses the wrong permutation, or reshapes the returned bytes incorrectly. Invoke the preparation API and compare its two returned scale tensors against the sentinel-derived legacy layouts; also add invalid-dimension cases asserting the new ValueError.

🤖 Prompt for 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.

In `@tests/moe/test_mxfp8_weight_prepare_layout.py` around lines 40 - 88,
Strengthen the regression tests by exercising prepare_trtllm_fp8_block_weights
rather than only composing block_scale_interleave and permutation helpers
directly. In both w2 and gated w3/w1 cases, compare the API’s two returned scale
tensors with the sentinel-derived expected legacy/interleaved layouts, including
shape and byte-content checks. Add invalid-dimension parametrized cases that
assert prepare_trtllm_fp8_block_weights raises ValueError.

raise ValueError(
"MXFP8 requires hidden_size and intermediate_size divisible by 32."
)
if hidden_size % 128 != 0 or (2 * intermediate_size) % 128 != 0:

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.

is (2 * intermediate_size) % 128 != 0 correct? I heard intermediate_size=64 cannot be supported

@jdebache

Copy link
Copy Markdown
Contributor

Related to #3882?

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.

4 participants