Skip to content

fix: make the cutlass MoE gemm profiler MXFP8-aware (autotune crash on MXFP8xMXFP8) - #3614

Merged
kahyunnam merged 4 commits into
flashinfer-ai:mainfrom
waynehacking8:wayne/fix-3558-mxfp8-autotune-profiler
Jul 9, 2026
Merged

kahyunnam merged 4 commits into
flashinfer-ai:mainfrom
waynehacking8:wayne/fix-3558-mxfp8-autotune-profiler

Conversation

@waynehacking8

@waynehacking8 waynehacking8 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes the original-report half of #3558: the autotuner tuning pass crashes for cutlass_fused_moe with MXFP8 activation scaling (CUDA illegal instruction / null TMA SF buffer on the reporter's SM121 build; !use_block_scaling kernel assert on current main).

Root cause (one level deeper than my scoping comment on the issue — input_sf is not even a tuning input on this path; the bug is entirely in the C++ gemm profiler): MXFP8×MXFP8 stores activations and weights as plain FP8, so the profiler cannot tell it apart from per-tensor FP8 by dtypes and prepared wrong tuning state in three coordinated places:

  1. GemmProfilerBackend::init derived mScalingType from dtypes alone → NONEgetOffsetActivationSF sized the activation-SF workspace to zero → the TMA warp-specialized MXFP8 kernels were launched with a null SF pointer ("gmem address 0 / null TMA buffer" in the report).
  2. getProfilerWorkspaces reserved only per-tensor float scalars for FP8 weights; MXFP8 needs per-expert weight block SFs + global scales.
  3. prepareQuantParams fell into per-tensor QuantParams::FP8; MXFP8 needs QuantParams::MXFP8MXFP8.

Plus tactic hygiene at the source: CutlassMoeFCRunner::getTactics now filters out non-TMA (SM80/SM89-style) fallback configs for MXFP8 instantiations — those paths hard-assert !use_block_scaling, so offering them as tuning tactics guarantees a crash on any arch where they are enumerated. This composes with future SM12x enablement (#3463-style): once isValidSM120MOESpecialisation admits fp8×fp8, the SM120 TMA configs flow through this filter untouched.

The binding passes mUseMxfp8ActScaling into GemmProfilerBackend::init (new defaulted parameter — other call sites unaffected).

Validation

This box is SM120 (RTX PRO 6000), where MXFP8×MXFP8 is not an enabled arch on main (test_moe_mxfp8_mxfp8 is SM100-gated), so the honest validation matrix is:

  • Compile: all changes JIT-compile (CUDA 13.0, sm_120).
  • No-regression (pure FP8, the dtype-twin path): cutlass_fused_moe FP8 per-tensor with autotune(True) runs the full tuning pass and produces bit-identical outputs across {stock main, this PR} × {autotune on, off} (cos identical to 4+ digits on a 128×1024, 8-expert config). test_moe_fp8 passes.
  • Fail-fast improvement on SM120: MXFP8 + autotune now fails at init with No valid tactics available for fused moe op... instead of the deep Assertion failed: !use_block_scaling (cutlass_fused_moe_kernels.cuh:3153) mid-tuning.
  • E2E regression coverage on SM100: added use_autotune parametrization to the SM100-gated test_moe_mxfp8_mxfp8 — on a pre-fix tree the True cases crash the tuning pass; with this PR they should pass on SM100 CI.

@tgmerritt — if you can ride this onto your #3463-based branch on the GB10, your original repro (vLLM --moe-backend flashinfer_cutlass, autotune enabled) is the definitive e2e check for the IMA half. The kErrorInternal tactic-init failures from your follow-up remain #3568's territory.

Tests

  • Tests have been added or updated as needed (use_autotune parametrization on test_moe_mxfp8_mxfp8)
  • All tests are passing locally (pure-FP8 autotune regression + test_moe_fp8 on SM120; MXFP8 cases are SM100-gated)

Disclosure

AI-assisted (Claude); reviewed, compiled, and validated by the submitter.

Summary by CodeRabbit

  • New Features

    • Added MXFP8×MXFP8 quantization support for profiler workspace and parameter initialization.
    • Added an option to enable MXFP8 activation scaling that influences GEMM tactic selection.
    • Ensured deterministic activation block-scale values during profiler runs.
  • Tests

    • Extended MXFP8×MXFP8 tests to run both with and without autotuning to cover tuning and non-tuning paths.

The autotuner tuning pass crashed for cutlass_fused_moe with MXFP8
activation scaling (illegal instruction / null TMA SF buffer on the
reporter's SM121; !use_block_scaling kernel assert on current main).
MXFP8xMXFP8 stores activations and weights as plain FP8, so the gemm
profiler could not tell it apart from per-tensor FP8 and prepared the
wrong tuning state in three coordinated places:

- GemmProfilerBackend::init derived mScalingType from dtypes alone ->
  NONE -> getOffsetActivationSF sized the activation-SF workspace to
  zero -> the TMA warp-specialized MXFP8 kernels got a null SF pointer.
- getProfilerWorkspaces only reserved per-tensor float scalars for FP8
  weights; MXFP8 needs per-expert weight block SFs + global scales.
- prepareQuantParams fell into the per-tensor QuantParams::FP8 branch;
  MXFP8 needs QuantParams::MXFP8MXFP8.

Additionally CutlassMoeFCRunner::getTactics now filters out non-TMA
(SM80/SM89-style) fallback configs for MXFP8 instantiations: those
paths hard-assert !use_block_scaling, so offering them as tactics
guarantees a tuning-pass crash on any arch where they are enumerated.

Adds use_autotune parametrization to test_moe_mxfp8_mxfp8 (SM100-gated)
as the regression test.

Validated on SM120 (RTX PRO 6000): all changes JIT-compile; pure-FP8
cutlass_fused_moe with autotune produces bit-identical outputs pre/post
change (the filter is a no-op for non-MXFP8); MXFP8 on SM120 (not an
enabled arch on main) now fails fast at init with 'No valid tactics
available' instead of the deep kernel assert. MXFP8 e2e runs on the
SM100-gated CI test added here.

Fixes the original-report half of flashinfer-ai#3558 (the kErrorInternal tactic
failures from the follow-up comment are tracked by flashinfer-ai#3568).

AI-assisted (Claude), reviewed and validated by the submitter.
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds MXFP8×MXFP8 profiler support: new init flag to enable MXFPX scaling, tactic filtering for MXFP8, profiler workspace and QuantParams for per-expert MXFPX scales, runtime wiring of the flag, and tests parametrized to exercise autotuning.

Changes

MXFP8x MXFP8 Fused MoE Quantization

Layer / File(s) Summary
Profiler contract, tactic filtering, and init flag
csrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/include/moe_kernels.h
Adds <algorithm>; getTactics routes configs through filterMxfp8Tactics; GemmProfilerBackend::init gains use_mxfp8_act_scaling and sets mScalingType = MXFPX for FP8/FP8 when enabled.
Workspace sizing and QuantParams for MXFPX
csrc/fused_moe/cutlass_backend/cutlass_fused_moe_kernels.cuh
Adds MXFP8×MXFP8 workspace sizing: zeroes quant_1/quant_4, allocates per-expert weight-block SFs (quant_2/quant_5) and per-expert float global scales (quant_3/quant_6); prepareQuantParams builds QuantParams::MXFP8MXFP8 under USING_OSS_CUTLASS_MOE_GEMM, cudaMemsetAsync-inits weight-SF buffers to 0x7F, and backfills dequant pointers; prepareTmaWsInputs memset-inits activation block-SF when present.
Runtime wiring of MXFPX flag
csrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cu
runGemmProfile passes mUseMxfp8ActScaling into mProfiler->init(...) in the OSS Cutlass path.
Test parametrization for autotune
tests/moe/test_trtllm_cutlass_fused_moe.py
test_moe_mxfp8_mxfp8 parametrized with use_autotune=[False, True]; fused MoE invocation wrapped in autotune(True) or nullcontext() accordingly.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Suggested labels

run-ci

Suggested reviewers

  • yzh119
  • samuellees
  • nv-yunzheq
  • bkryu
  • IwakuraRein
  • aleozlx

Poem

🐇 I hop through bytes where MXFP8s gleam,
Per-expert scales wake from a memset dream,
Tactics pruned and flags in place,
Autotune runs to find its pace,
The profiler hums — a tiny beam.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely and specifically describes the main fix: making the CUTLASS MoE GEMM profiler MXFP8-aware to resolve an autotuner crash on MXFP8×MXFP8 configurations.
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.
Description check ✅ Passed The PR description covers purpose, root cause, validation, and tests, but it omits the template's explicit Related Issues and checklist sections.
✨ 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 addresses issue #3558 by correctly supporting MXFP8xMXFP8 block-scaled quantization in the GEMM profiler, preventing crashes during the autotuning pass. The changes include calculating the correct workspace sizes for MXFP8, preparing the appropriate quantization parameters, filtering out incompatible non-TMA tactics for MXFP8, and adding regression test coverage. The review feedback suggests improving type safety by using MXFPXElementSF instead of ElementSF when calculating quantization sizes, and explicitly including the <algorithm> header to ensure compilation stability when using std::remove_if.

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 on lines +4461 to +4471
if (is_mxfp8_quant) {
quant_1_size = 0;
quant_2_size =
getOffsetWeightSF(num_experts_per_node, fc1_out_size, hidden_size, mScalingType) *
sizeof(TmaWarpSpecializedGroupedGemmInput::ElementSF);
quant_3_size = num_experts_per_node * sizeof(float);
quant_4_size = 0;
quant_5_size = getOffsetWeightSF(num_experts_per_node, hidden_size, inter_size, mScalingType) *
sizeof(TmaWarpSpecializedGroupedGemmInput::ElementSF);
quant_6_size = num_experts_per_node * sizeof(float);
}

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.

medium

To ensure type safety and consistency with prepareQuantParams (where quant_2 and quant_5 are cast to TmaWarpSpecializedGroupedGemmInput::MXFPXElementSF const*), it is safer to use sizeof(TmaWarpSpecializedGroupedGemmInput::MXFPXElementSF) instead of sizeof(TmaWarpSpecializedGroupedGemmInput::ElementSF) when calculating quant_2_size and quant_5_size.

  if (is_mxfp8_quant) {
    quant_1_size = 0;
    quant_2_size =
        getOffsetWeightSF(num_experts_per_node, fc1_out_size, hidden_size, mScalingType) *
        sizeof(TmaWarpSpecializedGroupedGemmInput::MXFPXElementSF);
    quant_3_size = num_experts_per_node * sizeof(float);
    quant_4_size = 0;
    quant_5_size = getOffsetWeightSF(num_experts_per_node, hidden_size, inter_size, mScalingType) *
                   sizeof(TmaWarpSpecializedGroupedGemmInput::MXFPXElementSF);
    quant_6_size = num_experts_per_node * sizeof(float);
  }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in ddeb61d — both sizeofs now use MXFPXElementSF for consistency with the prepareQuantParams casts (currently an alias of ElementSF, but this keeps the branch type-coherent if they ever diverge). Recompiled + reran the FP8 no-regression check, bit-identical.

static std::vector<cutlass_extensions::CutlassGemmConfig> filterMxfp8Tactics(
std::vector<cutlass_extensions::CutlassGemmConfig> configs) {
if constexpr (use_mxfp8) {
configs.erase(std::remove_if(configs.begin(), configs.end(),

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.

medium

The use of std::remove_if requires the <algorithm> header. While it might be transitively included by other headers in some compilation units, it is highly recommended to explicitly include <algorithm> at the top of moe_kernels.h to prevent potential compilation failures on other toolchains or standard library implementations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in ddeb61d — explicit #include <algorithm> added (it was indeed only transitively available).

@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: 2

🧹 Nitpick comments (1)
csrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cu (1)

676-690: 💤 Low value

OSS vs non-OSS profiler initialization divergence.

The OSS Cutlass path (line 682) now receives mUseMxfp8ActScaling, while the non-OSS path (lines 684-689) does not. This divergence is intentional per the PR scope, which targets OSS Cutlass MXFP8 support. However, a brief inline comment explaining why only the OSS path receives this parameter would improve maintainability.

📝 Suggested comment
       int64_t const unpadded_hidden_size_profiler = hidden_size;  // HACK no padding by default
 `#ifdef` USING_OSS_CUTLASS_MOE_GEMM
+      // OSS Cutlass profiler needs mUseMxfp8ActScaling to allocate MXFP8-specific workspace
       mProfiler->init(
           *mKernelRunner.get(), mProfiler->mGemmToProfile, DtypeUtils::dataType(activation_dtype),
           DtypeUtils::dataType(mWeightDtype), DtypeUtils::dataType(mOutputDtype), num_experts,
           static_cast<int>(top_k), hidden_size, unpadded_hidden_size_profiler, inter_size,
           group_size, activation_type, USE_BIAS, USE_LORA, min_latency_mode,
           /*need_weights*/ false, parallelism_config, enable_alltoall, mUseMxfp8ActScaling);
 `#else`
🤖 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 `@csrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cu`
around lines 676 - 690, Add a brief inline comment near the conditional that
calls mProfiler->init under the USING_OSS_CUTLASS_MOE_GEMM branch explaining why
mUseMxfp8ActScaling is passed only in the OSS path (e.g., "MXFP8 activation
scaling is supported/enabled only in the OSS Cutlass build, so this flag is not
applicable to the non-OSS profiler init"), referencing the conditional macro
USING_OSS_CUTLASS_MOE_GEMM and the mUseMxfp8ActScaling parameter so future
maintainers understand the intentional divergence in the mProfiler->init calls.
🤖 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 `@csrc/fused_moe/cutlass_backend/cutlass_fused_moe_kernels.cuh`:
- Around line 4677-4691: When constructing mQuantParams via
QuantParams::MXFP8MXFP8(...) inside the MXFP8MXFP8 branch, also populate the
common FP8 dequantation aliases mQuantParams.fp8.dequant_fc1 and
mQuantParams.fp8.dequant_fc2 (the same pointers used by runProfiler(),
prepareTmaWsInputs(), and the TMA/GEMM paths) by remapping the MXFP8-specific
element scales/float pointers into those alias fields so downstream code
(runProfiler, prepareTmaWsInputs, runMoe) sees valid dequant pointers; make this
change in the MXFP8 x MXFP8 branch alongside the existing
QuantParams::MXFP8MXFP8(...) construction.

In `@csrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cu`:
- Around line 677-682: The mUseMxfp8ActScaling boolean must be passed as the
parameter immediately following enable_alltoall to match
GemmProfilerBackend::init(..., bool const enable_alltoall, bool
use_mxfp8_act_scaling = false); confirm the call to mProfiler->init(...) uses
mUseMxfp8ActScaling in that position (as it currently does), and if any
reorderings were made ensure the argument order is restored so the final two
arguments are enable_alltoall then mUseMxfp8ActScaling (referencing
mProfiler->init, enable_alltoall, and mUseMxfp8ActScaling).

---

Nitpick comments:
In `@csrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cu`:
- Around line 676-690: Add a brief inline comment near the conditional that
calls mProfiler->init under the USING_OSS_CUTLASS_MOE_GEMM branch explaining why
mUseMxfp8ActScaling is passed only in the OSS path (e.g., "MXFP8 activation
scaling is supported/enabled only in the OSS Cutlass build, so this flag is not
applicable to the non-OSS profiler init"), referencing the conditional macro
USING_OSS_CUTLASS_MOE_GEMM and the mUseMxfp8ActScaling parameter so future
maintainers understand the intentional divergence in the mProfiler->init calls.
🪄 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: 539cffb8-5164-4051-b7e1-f10b91c7570b

📥 Commits

Reviewing files that changed from the base of the PR and between ac554d5 and 6dfbdb2.

📒 Files selected for processing (4)
  • csrc/fused_moe/cutlass_backend/cutlass_fused_moe_kernels.cuh
  • csrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cu
  • csrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/include/moe_kernels.h
  • tests/moe/test_trtllm_cutlass_fused_moe.py

Comment thread csrc/fused_moe/cutlass_backend/cutlass_fused_moe_kernels.cuh
… branch

prepareTmaWsInputs() reads mQuantParams.fp8.dequant_fc1/fc2 directly for
the TMA strides dispatch; mirror the WMXFP8AMXFP8 remap that runMoe()
performs so the profiler hands real per-expert global scales instead of
null pointers.

AI-assisted (Claude).
@tgmerritt

Copy link
Copy Markdown

Confirming the split from the GB10 side: it matches. Our #3568 only touches the dense MXFP8 GEMM path (gemm_base.py get_valid_tactics probe) — it never enters cutlass_fused_moe's profiler, so the fused-MoE IMA is fully independent of it. The two failure signatures we reported on #3558 were on different ops (IMA = fused-MoE tuning pass; kErrorInternal skips = dense linear-layer autotune), which is exactly your Bug A / Bug B split.

Diff read-through from our side: the mScalingType=MXFPX derivation in GemmProfilerBackend::init is the keystone — once that's set, the existing getOffsetActivationSF sizing stops returning zero, which removes the null TMA SF descriptor we captured ("gmem address 0"). The prepareQuantParams MXFP8 branch ordering before the generic FP8 branch and the fp8.dequant_fc1/fc2 alias backfill both mirror what runMoe() does on the working non-autotune path, so the profiler and runtime states are now consistent.

One non-blocking question: the profiler's dummy activation SF content is still random bits — valid allocation now, but garbage E8M0 exponents. For pure timing that should be fine (the TMA descriptor is valid; decoding garbage scales can't fault), but worth confirming it's intentional vs. filling a neutral scale (all-ones exponent), in case any tactic's runtime is data-dependent on scale magnitude.

Running the definitive e2e check now on the GB10: #3463 + #3568 + this PR, vLLM --moe-backend flashinfer_cutlass with autotune enabled, Gemma4-26B MXFP8 W8A8. Will report results here.

@tgmerritt

Copy link
Copy Markdown

GB10 (SM121) validation results — PASS.

Setup: feat-wmxfp8-on-main (#3463) + #3568 + this PR, merged clean; FlashInfer JIT-built on the box (CUDA 13.0, torch 2.11, SM121). Test: cutlass_fused_moe MXFP8×MXFP8 with autotune(True) on gemma4-26B-A4B-like MoE shapes (hidden=2816, inter=768, 128 experts, top_k=8), M ∈ {1, 8, 64, 512, 2048}, against the non-autotuned path as reference.

M=    1: PASS — tuning pass completed, cos=0.999989
M=    8: PASS — tuning pass completed, cos=0.999999
M=   64: PASS — tuning pass completed, cos=0.999999
M=  512: PASS — tuning pass completed, cos=1.000000
M= 2048: PASS — tuning pass completed, cos=1.000000
  • The tuning pass ran the full 21-tactic profile loop per gemm with no illegal instruction — this exact path crashed with the null-TMA-SF IMA pre-fix (it's why our production config and sanity benches had autotune hard-disabled).
  • Skipped 28 unsupported tactic(s) logged per profile pass — the non-TMA fallback filtering doing its job silently instead of crashing.
  • Autotuned vs non-autotuned outputs agree (cos ≥ 0.999989 at every M; small diffs are just different valid tactic selections).

This was the kernel-level repro of the original #3558 crash on the affected hardware. We'll fold the fix into our next production vLLM image rebuild (--moe-backend flashinfer_cutlass, autotune enabled) and can report back if anything surfaces at that layer, but as far as the reported IMA goes: confirmed fixed on SM121. Thanks for the clean root-cause work — the dtype-twin ambiguity explanation matches everything we captured in the original forensics.

@tgmerritt

Copy link
Copy Markdown

Addendum — full in-vLLM data from the GB10 (SM121), autotune now enabled end-to-end.

Beyond the kernel-level validation above, we ran the complete vLLM A/B (gemma4-26B-A4B MXFP8 W8A8, --moe-backend flashinfer_cutlass, dev50-era image + #3463 + #3568 + this PR). Two findings:

1. The crash fix holds in vLLM. Boot-time autotuning runs to completion, serves correct output. No IMA, no asserts — the original #3558 repro is gone at the serving layer too.

2. But the tactics the autotuner selects regress high-batch throughput. Concurrency sweep (200 tok/req, ignore_eos), same image/flags, only the autotune flag differs:

conc autotune ON (tok/s) autotune OFF ratio
1 37.2 30.5 1.22
2 69.1 76.6 0.90
4 110.7 141.2 0.78
8 195.1 256.0 0.76
16 288.1 475.0 0.61
32 496.3 824.5 0.60

Autotune ON helps bs=1 (+22%) but selects tactics that are up to 40% slower at high concurrency. (With MTP speculative decoding at the bs=1 serving regime the picture is the same direction: 96.9 tok/s autotuned vs 75.1 with default tactics — a +29% win there.)

This pattern is consistent with the dummy-input question above: if the profiler's dummy activations/SFs aren't representative (random E8M0 exponents, and/or the M-bucketing used for tuning), the measured timings can rank tactics differently than real traffic does, and the selection generalizes badly to large M. Happy to re-run the sweep against any revision — the harness is a one-command run on this box.

(For completeness: this doesn't change our production choice — MARLIN W8A16 still wins our interactive MTP regime 102.6 vs 96.9 tok/s on identical same-day config — but it makes the autotuner usable, which is the point of this PR. The tactic-quality question is a separate, smaller follow-up.)

The profiler sizes the weight/activation block-SF workspaces but never
fills them, so the timed kernels read uninitialized E8M0 exponents —
a nondeterministic scale pattern that can skew tactic rankings vs real
traffic (observed as high-batch tactic regressions in GB10 validation).
Fill all three SF regions with biased exponent 0x7F (= 1.0) on the
MXFP8 path.

AI-assisted (Claude).
@waynehacking8

Copy link
Copy Markdown
Contributor Author

@tgmerritt Outstanding validation — thank you. The A/B table is exactly the data this needed, and your read of the mechanism matches mine. Three responses:

1. One concrete cause of unrepresentative dummies is now fixed in 054ec99. You guessed right with "random E8M0 exponents": the profiler sized the SF workspaces (this PR's earlier commits) but never filled them — the timed kernels were reading uninitialized exponent bytes. That's worse than random: it's nondeterministic across allocations, so two boots can rank tactics differently. The new commit memsets all three SF regions (fc1/fc2 weight block-SFs + the activation SF buffer) to biased exponent 0x7F (= 1.0) on the MXFP8 path. Whether SF content moves kernel timing enough to explain a 0.6× ratio is an open question — but it's now a controlled variable. Your one-command sweep against this revision would tell us how much of the regression it eats (heads-up: this revision is compile-verified only by inspection on my side, since MXFP8 won't JIT on my SM120 main checkout — your rig is the real check).

2. The remaining suspects are profiler-structural and probably the bigger half: (a) routing distribution — the profiler benchmarks under NUM_ROUTING_SAMPLES=16 uniform-random expert assignments, while real gemma4 traffic is skewed; tile-config rankings at high M are sensitive to per-expert token counts; (b) M-bucketing on the vLLM side — which bucket M values boot-time tuning ran at, and how runtime M maps onto them, is outside this PR entirely. Both deserve a dedicated issue rather than riding this crash fix — I'll file it with your table as the evidence base once we see the post-054ec99 numbers, unless you'd rather file from the GB10 side with the harness details.

3. Agreed on scope: this PR's job is "autotune doesn't crash and doesn't lie nondeterministically"; tactic quality is the follow-up. Your bs=1 (+22%) and MTP (+29%) wins suggest the autotuner is already worth having at interactive regimes even before the quality work.

@tgmerritt

Copy link
Copy Markdown

Re-ran the sweep on 054ec99 (GB10/SM121): the SF-init fix compiles, serves correctly — but does not move the high-concurrency regression.

Same image/flags/harness as the table above, only the kernel header updated to 054ec99 (incremental JIT rebuild, 49s):

conc autotune ON @ 054ec99 autotune ON (pre-054) autotune OFF
1 36.5 37.2 30.5
2 64.9 69.1 76.6
4 109.3 110.7 141.2
8 190.1 195.1 256.0
16 315.4 288.1 475.0
32 499.8 496.3 824.5

All deltas vs pre-054 are within run-to-run noise (the C=16 +9% is the largest and that band is noisy on this box). Correctness intact ("Paris" / "42" smoke on chat completions).

So: the uninitialized-SF read was real and worth fixing for determinism alone, but SF content is not what's mispricing the tactics — the bs=1-good/high-M-bad selection survives a clean 1.0-exponent fill. That leaves your structural suspects holding the bag: the uniform-random routing distribution (NUM_ROUTING_SAMPLES=16) and the vLLM-side M-bucketing. Both consistent with what we see: the selected tactics behave like they were ranked at small effective per-expert M.

Please go ahead and file the follow-up issue from your side — you have the mechanism scoped better than we do. Feel free to embed both tables; harness details for the repro: gemma4-26B-A4B MXFP8 W8A8 checkpoint, vLLM dev50-era build + #3463 + #3568 + this PR, --moe-backend flashinfer_cutlass, concurrency sweep at 200 tok/req with ignore_eos (script is ~50 lines of stdlib urllib, happy to paste it into the issue once it exists). We can turn around validation runs on the GB10 same-day.

@waynehacking8

Copy link
Copy Markdown
Contributor Author

@tgmerritt Thanks for the fast re-run — clean experimental result: SF content ruled out, determinism fix kept. Follow-up filed as #3622 with both your tables, the per-expert-M arithmetic behind your "ranked at small effective per-expert M" read (uniform routing with E=128/top_k=8 gives every expert ≈ M/16 — balanced and small, exactly the regime your selected tactics look tuned for), and the M-bucketing half flagged as possibly needing a vLLM-side companion. Please paste the harness script there when you get a chance — your GB10 same-day loop + this SM120 box gives the follow-up the same validation setup that worked here.

use_mxfp8_act_scaling=True,
output=flash_output,
)
with autotune(True) if use_autotune else nullcontext():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

skip_ops is also available for this purpose

skip_ops: Optional set of ``custom_op`` names to exclude from

cc @qiching

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good pointer, thanks - kept the parametrized context here since the crash lived in the profiling path itself, so the test wants autotune fully on/off per case, but skip_ops is the right tool when only a specific op should stay on heuristics.

@aleozlx aleozlx left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm

@aleozlx aleozlx added the run-ci label Jul 8, 2026
@aleozlx

aleozlx commented Jul 8, 2026

Copy link
Copy Markdown
Member

/bot run tests/moe

@aleozlx aleozlx self-assigned this Jul 8, 2026
@flashinfer-bot

Copy link
Copy Markdown
Collaborator

GitLab MR !908 has been created, and the CI pipeline #57191307 is currently running. I'll report back once the pipeline job completes.

@flashinfer-bot

Copy link
Copy Markdown
Collaborator

[FAILED] Pipeline #57191307: 12/20 passed

@kahyunnam
kahyunnam merged commit 59b1c4d into flashinfer-ai:main Jul 9, 2026
47 of 48 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants