Skip to content

[SM90] Add W4A8 MegaMoE support - #6343

Merged
yyihuang merged 18 commits into
flashinfer-ai:mainfrom
AichenF:megamoe_w4a8_split
Oct 13, 2026
Merged

yyihuang merged 18 commits into
flashinfer-ai:mainfrom
AichenF:megamoe_w4a8_split

Conversation

@ouershua2299

@ouershua2299 ouershua2299 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

📌 Description

Co-authors: @AichenF, @jychen21, @YijiaZhao.

Submitted by: xuechengw.

flashinfer.megamoe runs expert-parallel MoE on NVLink-connected Hopper GPUs,
using NVFP4/MXFP4 weights, FP8 E4M3 activations and BF16 outputs. The kernels
perform token dispatch, two expert GEMMs, clamped SwiGLU and top-k reduction.

Weights Family Launches
NVFP4 fused 1
NVFP4 split 2: L1, then L2
MXFP4 fused 1

Usage

Allocate MegaMoESymmBuffer collectively before graph capture. Prepare weights
with transform_nvfp4_weights_for_mega_moe or
transform_mxfp4_weights_for_mega_moe; all ranks stage inputs and call matching
kernels in matching order.

from flashinfer import megamoe

buf.stage(x_fp8, x_sf, topk_idx, topk_weights)
megamoe.nvfp4_mega_moe(y, nv_w1, nv_w2, buf, kernel_family="auto")
# Or, with MXFP4 weights:
megamoe.mxfp4_mega_moe(y, mx_w1, mx_w2, buf)

NVFP4 supports 256 or 384 total experts. The process group selects EP4 or EP8;
no separate execution API is needed. EP4 uses the generic fused schedule;
EP8 retains its measured tuning policies. Auto selection uses fused through
256 tokens per rank, then split.
Input scales are FP32 per 128 channels; routing uses INT64 expert indices
(-1 masks a route) and FP32 weights. Hidden width is a multiple of 256 and
intermediate width a multiple of 128. Raw weight scales use UINT8 E4M3 per
16 values for NVFP4 or E8M0 per 32 values for MXFP4.

Performance

Cold L2 before each call; one MoE call per graph; CUDA events; maximum time
across ranks per call, then the median of 50 samples. Split timing includes
both launches. Packing, compilation and cache clearing are outside timing.
M is tokens per rank; capacity is the allocated maximum tokens per rank.

Model Hidden Intermediate Experts Top-k
Flash 4096 2048 256 6
MiMo 6144 2048 384 8
Pro 7168 3072 384 6
All 82 workloads: H20 and H200 latency
Kernel Model / EP M / rank Capacity H20 (us) H200 (us)
NVFP4 fused Flash / 8 1 384 90.960 81.280
NVFP4 fused Flash / 8 24 384 254.016 215.440
NVFP4 fused Flash / 8 32 384 258.624 218.944
NVFP4 fused Flash / 8 64 384 288.208 232.496
NVFP4 fused Flash / 8 96 384 327.056 227.824
NVFP4 fused Mimo / 8 1 384 151.376 136.560
NVFP4 fused Mimo / 8 24 384 509.616 422.048
NVFP4 fused Mimo / 8 32 384 519.904 426.096
NVFP4 fused Mimo / 8 64 384 579.584 458.912
NVFP4 fused Mimo / 8 96 384 629.312 456.368
NVFP4 fused Pro / 8 1 384 169.152 161.856
NVFP4 fused Pro / 8 24 384 849.856 703.008
NVFP4 fused Pro / 8 32 384 890.464 706.304
NVFP4 fused Pro / 8 64 384 930.048 727.424
NVFP4 fused Pro / 8 96 384 972.128 746.960
NVFP4 split Flash / 8 384 4224 891.280 339.296
NVFP4 split Flash / 8 1024 4224 1,588.000 601.840
NVFP4 split Flash / 8 4096 4224 5,223.680 1,824.592
NVFP4 split Mimo / 8 384 4224 1,637.664 635.584
NVFP4 split Mimo / 8 1024 4224 3,027.472 1,207.888
NVFP4 split Mimo / 8 4096 4224 10,236.048 3,887.568
NVFP4 split Pro / 8 384 4224 2,026.080 949.056
NVFP4 split Pro / 8 1024 4224 4,358.480 1,622.128
NVFP4 split Pro / 8 4096 4224 13,342.016 6,017.504
MXFP4 fused Flash / 8 1 4224 98.144 97.296
MXFP4 fused Flash / 8 24 4224 271.648 197.600
MXFP4 fused Flash / 8 32 4224 270.912 192.016
MXFP4 fused Flash / 8 64 4224 289.104 202.672
MXFP4 fused Flash / 8 96 4224 310.480 220.752
MXFP4 fused Flash / 8 384 4224 801.184 393.040
MXFP4 fused Flash / 8 1024 4224 1,504.720 704.352
MXFP4 fused Flash / 8 4096 4224 5,195.056 2,406.736
MXFP4 fused Mimo / 8 1 4224 131.008 127.344
MXFP4 fused Mimo / 8 24 4224 501.568 339.296
MXFP4 fused Mimo / 8 32 4224 561.024 360.208
MXFP4 fused Mimo / 8 64 4224 582.112 392.960
MXFP4 fused Mimo / 8 96 4224 626.848 434.160
MXFP4 fused Mimo / 8 384 4224 1,436.624 647.120
MXFP4 fused Mimo / 8 1024 4224 2,857.840 1,355.680
MXFP4 fused Mimo / 8 4096 4224 10,136.752 Pending*
MXFP4 fused Pro / 8 1 4224 221.840 157.376
MXFP4 fused Pro / 8 24 4224 787.984 519.264
MXFP4 fused Pro / 8 32 4224 836.160 550.400
MXFP4 fused Pro / 8 64 4224 935.568 614.960
MXFP4 fused Pro / 8 96 4224 975.072 637.424
MXFP4 fused Pro / 8 384 4224 1,708.752 750.672
MXFP4 fused Pro / 8 1024 4224 4,049.184 1,810.048
MXFP4 fused Pro / 8 4096 4224 13,173.744 29,917.920
MXFP4 fused Flash / 8 8 384 214.080 171.856
MXFP4 fused Flash / 8 16 384 238.896 182.560
MXFP4 fused Flash / 8 32 384 271.248 193.280
MXFP4 fused Flash / 8 48 384 280.800 197.504
MXFP4 fused Flash / 8 64 384 289.040 202.880
MXFP4 fused Flash / 8 96 384 307.504 220.672
MXFP4 fused Flash / 8 112 384 318.624 221.040
MXFP4 fused Flash / 8 128 384 351.120 221.904
MXFP4 fused Flash / 8 160 384 438.320 236.144
MXFP4 fused Flash / 8 192 384 440.304 229.760
MXFP4 fused Flash / 8 256 384 457.088 1,060.400
MXFP4 fused Flash / 8 384 384 814.176 1,155.168
MXFP4 fused Flash / 4 8 768 261.088 211.152
MXFP4 fused Flash / 4 16 768 380.096 267.680
MXFP4 fused Flash / 4 32 768 438.016 286.512
MXFP4 fused Flash / 4 48 768 451.552 306.160
MXFP4 fused Flash / 4 64 768 496.848 330.000
MXFP4 fused Flash / 4 96 768 518.560 340.400
MXFP4 fused Flash / 4 128 768 535.520 351.376
MXFP4 fused Flash / 4 160 768 555.216 384.320
MXFP4 fused Flash / 4 192 768 573.584 386.256
MXFP4 fused Flash / 4 224 768 589.936 386.784
MXFP4 fused Flash / 4 256 768 614.192 388.768
MXFP4 fused Flash / 4 384 768 857.952 394.448
MXFP4 fused Flash / 4 512 768 866.240 406.992
MXFP4 fused Flash / 4 768 768 1,519.440 694.912
MXFP4 fused Mimo / 4 8 384 527.168 361.936
MXFP4 fused Mimo / 4 32 384 854.064 549.344
MXFP4 fused Mimo / 4 64 384 1,008.064 647.968
MXFP4 fused Mimo / 4 128 384 1,101.408 711.840
MXFP4 fused Mimo / 4 192 384 1,216.400 791.616
MXFP4 fused Mimo / 4 240 384 1,204.288 795.072
MXFP4 fused Mimo / 4 288 384 1,304.784 797.056
MXFP4 fused Mimo / 4 384 384 1,804.144 803.552

H200 EP8 regression against the original NVFP4 kernels covers 12 points
(Flash/MiMo/Pro, M=1/32/96/384, capacity 384): latency changes range from
-1.07% to +0.64%. Flash M96 is 227.808 us before and
227.440 us after.

Validation

pytest tests/moe/test_megamoe_fp4_ep.py tests/moe/test_megamoe_fp4_weights.py tests/moe/test_megamoe_fp4_sm90.py

CPU plan/cache and weight-layout checks pass. EP4 numerical, receive-count,
workspace-reuse and CUDA graph checks pass on H20 and H200, including Flash,
MiMo and Pro shapes and both push-dispatch barrier modes. H200 EP8 regression
outputs match the original NVFP4 fused/split kernels bitwise.

The benchmark defaults to warm-cache repeated calls; pass --cold-l2 and
--repeats 1 for one cold-L2 invocation per sample.

🔍 Related Issues

None linked.

🚀 Pull Request Checklist

Thank you for contributing to FlashInfer! Before we review your pull request, please make sure the following items are complete.

✅ 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.

If you are unsure about how to set up pre-commit, see the pre-commit documentation.

🧪 Tests

  • Tests have been added or updated as needed.
  • All tests are passing (unittest, etc.).

CPU targets: 32 passed. GPU targets: 8 passed on H200 (EP4/EP8),
6 passed on H20 (EP4). Tests cover dense-reference numerics, receive counts,
workspace reuse, graph replay, production shapes and push-dispatch barrier modes.
H200 NVFP4 EP8 source comparisons are bitwise exact.

🔬 Experimental Track

  • This PR is experimental: it adds or changes code under flashinfer/experimental/ and/or an @flashinfer_experimental_api. Tracking issue: #
    • The tracking issue names an owner, the reason for the experimental path, and a graduation plan with a target release.
    • Core changes are limited to a thin entry point (signature, shared validation, feature-gate check, backend selection, handoff).
    • Tests live in tests/experimental/ and were validated on the intended hardware; a runnable example is included.
    • Nothing is registered in flashinfer/aot.py, and no experimental backend is reachable from backend="auto" without FLASHINFER_ALLOW_EXPERIMENTAL_AUTO_BACKENDS=1. (Calling an @flashinfer_experimental_api or naming a backend explicitly is itself the opt-in and needs no environment variable.)
    • Test scope declared below. The experimental CI lane runs exactly these targets, so keep them as narrow as the change allows.
# One target per line: a directory or a file. (A pytest ::selector is not
# supported -- the sharding runner cannot consume one.) Must be under
# tests/experimental/ and must exist. Delete these comment lines and add yours, e.g.
#
#   tests/experimental/test_my_backend.py
#   tests/experimental/my_backend/
#
# Declaring the whole tree (tests/experimental/) is allowed but means every
# experimental PR pays for every other feature's tests, in every matrix cell.

Reviewer Notes

Reviewer Notes

The test checkbox covers PR-related targets; upstream multi-architecture CI
requires authorization. EP4 uses the generic fused schedule and has not been
tuned independently. This run remeasured EP8 performance on H200; H20 had four
free GPUs and was used for EP4 correctness verification. The previously accepted
H20 NVFP4 hot-routing M1 deviation matches the original EP8 output bitwise.
The H200 timing point marked Pending is not a settled performance result.
Root README is unchanged.

Summary by CodeRabbit

  • New Features

    • Added SM90 MegaMoE support for NVFP4 and MXFP4, including fused execution for both formats and split execution for NVFP4.
    • Added APIs for managing shared buffers, preparing weights, and selecting NVFP4 split plans.
    • Added guidance for using and benchmarking the new functionality.
  • Tests

    • Added correctness coverage for distributed execution, weight preparation, and supported rank configurations.

AichenF and others added 14 commits October 4, 2026 09:10
Framework-agnostic kernel headers for the SM90 (Hopper) NVFP4 MegaMoE fused
family -- a single persistent kernel that fuses the cross-rank token dispatch,
both FFN GEMMs (W4A8: NVFP4 weights, FP8 activations), SwiGLU and the combine
reduction over an 8-rank NVLink symmetric buffer.

This is part 1 of 2. It covers the BN256 fused family, which the host policy
selects for per-rank M <= family_threshold; the BN128 split family for larger M
follows in part 2 and reuses every support header added here.

Contents (include/flashinfer/megamoe/, no torch dependency per the framework
separation rule):
  sm90_nvfp4_mega_moe_fused.cuh / _body.inl   the fused kernel
  scheduler.cuh                               dev-m dynamic task scheduler
  layout.cuh                                  workspace / token-pool layout
  nvfp4_dequant.cuh                           NVFP4 -> FP8 dequant
  barrier.cuh, sym_buffer.cuh                 NVLink barriers, symmetric buffer
  detail/                                     ptx, sm90 mma, tma, shared helpers

Dispatch synchronisation uses counter-based push dispatch (the source rank
writes rows straight into the destination pool and release-adds a DONE count)
with a parity-rotated pool, gated by an M-range policy; outside those ranges it
falls back to the NVLink barrier protocol.

Requires -std=c++20 (the kernels take a float non-type template parameter for
the activation clamp).

Verified: 8 template instantiations build clean on sm_90a -- push+rotated,
barrier-only, push-only, static scheduler, BLOCK_M=24, and the Flash / Pro /
MiMo geometries, plus the phase-stamp diagnostic arm.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wires the SM90 NVFP4 MegaMoE fused kernel into FlashInfer's JIT pipeline.

The kernel resolves its shared-memory layout, task schedule and dispatch
protocol at compile time, so the plan is selected in Python and rendered into
the config header; one module is built per (shape, plan) pair.

  flashinfer/megamoe/heuristics.py   plan selection (tuning tables, all-M arm,
                                     counter-dispatch policy, pool sizing)
  flashinfer/megamoe/__init__.py     MegaMoESymmBuffer + nvfp4_mega_moe
  flashinfer/jit/megamoe.py          Jinja render + JitSpec
  csrc/megamoe/*.cu, *.jinja         launcher, TMA descriptors, TVM-FFI binding
  tests/moe/test_megamoe_nvfp4_sm90.py

The symmetric-buffer layout is computed in C++ and returned as byte offsets so
Python slices exactly what the kernel addresses. The scale-factor region is
sized for the largest BLOCK_M candidate, since it is allocated once while the
plan varies per call -- sizing it from the current plan silently corrupts every
BLOCK_M != 8 schedule.

Needs -std=c++20 (float non-type template parameter for the activation clamp)
and builds against the pinned CUTLASS 4.4.

Verified on 8x H20-3e (EP8) against the DeepGEMM reference in the same process:
  - correctness: 27/27 bit-identical over Flash / Pro / MiMo at
    M = 1, 2, 4, 8, 16, 32, 64, 128, 256
  - performance: same kineto kernel timer, max over 8 ranks -- 18 of 27 points
    within +-2%, all within +-8%; the spread is measurement noise on a shared
    host (at large M, where the signal dominates, every point is within 2%)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The workspace sizing helpers are CUTLASS_HOST_DEVICE -- shared between the
launcher and the kernel -- so their assertion failure path is also compiled for
the host, where `asm("trap;")` is not a valid instruction. It only stayed hidden
because the fused launcher happens to reach those helpers through constant
evaluation.

Route both paths through FI_MEGAMOE_TRAP(): `trap;` under __CUDA_ARCH__,
__builtin_trap() otherwise. Device code is unchanged -- the coverage build's
cubin is byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ast_math is set

gen_jit_spec() adds -use_fast_math to every module unless told otherwise.
For this kernel that overrides the fast_math argument: kFastMath=false picks
expf() and a true division for SwiGLU in the source, but -use_fast_math makes
nvcc lower expf() to the __expf() intrinsic and turns on --ftz anyway, so
fast_math=False still compiled to the fast-math kernel. The DeepGEMM reference
passes --use_fast_math to this family only when fast_math is set, and
fast_math=False disagreed with it on 1-2 bf16 ULPs (M=64, 256 on Flash).

Pass use_fast_math=fast_math so the compiler flag follows the template
parameter. fast_math=True, the default, builds exactly as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pass/fail verdict was reduced across all ranks, but bit-identical, finite,
max_abs and the mismatch count were rank 0's local values. When another rank
diverged the test printed contradictory lines such as
"FAIL ... bit-identical=True finite=True".

Reduce each field as well (MIN for the flags, MAX for max_abs, SUM for the
count) so the printed line describes all ranks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…files

Formatting from clang-format, ruff format and trailing-whitespace, plus the
fixes ruff and mypy asked for:
  - heuristics.py: the tuning rows are typed tuples instead of mixed
    int/bool lists, so the bool fields keep their type through FusedPlan
  - jit/megamoe.py: read the copied sources under a context manager
  - test: drop an unused import

No functional change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test spawns one worker per GPU itself (torch.multiprocessing.spawn), so
launching it under torchrun as the docstring said would start 8 copies of the
8-process job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the BN128 split family, which the host policy selects for per-rank
M > family_threshold, where a single fused persistent kernel no longer fits the
working set. L1 runs dispatch-dequant with half-stream mode4 and the calibrated
physical-warp remap; L2 is the warp-private contiguous scatter.

Stacked on part 1 -- every support header (scheduler, layout, barriers,
symmetric buffer, ptx/mma/tma helpers) is reused as-is. The only change to an
existing file is restoring ptx::st_shared_cluster_async, which is used solely by
the split L1 body.

New files:
  sm90_nvfp4_mega_moe.cuh                     split entry (L1 + L2)
  sm90_nvfp4_mega_moe_split_l1_body.inl       L1: dispatch-dequant + GEMM
  sm90_nvfp4_mega_moe_split_l2_body.inl       L2: GEMM + scatter
  sm90_nvfp4_mega_moe_mode2_dequant.cuh       Mode2 braided-sign dequant

With both parts in, the host selector covers the full M range: fused below the
threshold, split above it.

Verified: the part-1 instantiations still build unchanged (identical cubin), and
split L1 (with the two production defines the host always emits) and L2 build
clean on sm_90a.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the BN128 split path to the Python API. `nvfp4_mega_moe` now dispatches on
tokens-per-rank: the fused kernel at or below `family_threshold` (256), the
split pair above it, where a single persistent kernel no longer fits the
working set.

The split family runs two kernels per call -- L1 (dispatch-dequant + first GEMM,
2-CTA cluster) then L2 (second GEMM + scatter) -- each with its own pipeline
depth and shared-memory budget. Those are template parameters, so they have to
be known before the module is built. Rather than paraphrasing ~100 lines of
shared-memory arithmetic in Python, the reference sizing code is carried over as
`megamoe_sm90_nvfp4_split_plan.cuh` and compiled into a small template-free plan
module that Python queries first; the kernel module is then rendered from its
answer. Mis-sizing that pipeline would be silent, so it is worth not
re-deriving.

  csrc/megamoe/megamoe_sm90_nvfp4_split_plan.{cuh,cu}   plan query
  csrc/megamoe/megamoe_sm90_nvfp4_split.cu              two-phase launcher
  csrc/megamoe/megamoe_sm90_nvfp4_split_binding.cu      TVM-FFI binding
  csrc/megamoe/megamoe_sm90_nvfp4_split_config.jinja
  flashinfer/jit/megamoe.py, flashinfer/megamoe/__init__.py

Two descriptor details differ from the fused path and are easy to get wrong:
the L1 output box is 64x64 (each of the two M warpgroups stores its own
post-SwiGLU tile, so it is not (BLOCK_N/2, BLOCK_M)), and the L2 activation
scales are per-128 like L1's, not per-BLOCK_N/2.

Verified on 8x H20-3e (EP8) against the DeepGEMM reference in the same process:
  - correctness: 6/6 bit-identical over Flash / Pro / MiMo at M = 512, 1024
  - performance: same kineto timer summed over both phases, max over 8 ranks --
    all six points within +-0.5%

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gen_jit_spec() adds -use_fast_math to every module by default, which implies
--ftz=true and lowers expf()/division to approximate intrinsics. The DeepGEMM
reference builds the split L1/L2 kernels with no fast-math flag at all, for
either value of fast_math: kFastMath alone picks __expf/fast_rcp or
expf/division in the SwiGLU epilogue. With the default flag, our split kernels
flushed denormal intermediates where the reference keeps them, and
fast_math=False still ran the intrinsic path (1-2 bf16 ULP mismatches at
M = 512, 1024, 2048 on Flash). A SASS diff showed the extra .FTZ suffixes.

Pass use_fast_math=False. Appending --ftz=false --prec-div=true
--prec-sqrt=true is not enough, because it does not undo the expf() lowering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kFastMath is a kernel template parameter, so it has to match on both the
FlashInfer and DeepGEMM side for the bit-identical comparison to mean
anything. The test hardcoded fast_math=True on the reference call and relied
on the MegaMoESymmBuffer default for ours, so the fast_math=False path had no
coverage at all -- which is how the -use_fast_math mismatch in the JIT specs
went unnoticed.

Thread a --no-fast-math flag through both sides and report it in the result
line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The prepacked NVFP4 scales are tile-major (E, N/block_n, K/128, block_n, 8),
so the weight layout belongs to the kernel family: the fused kernel is BN256
and split L1/L2 is BN128 (kBlockN is hardcoded to 128 in the split config).
The test transformed once with block_n=256 and then let nvfp4_mega_moe
auto-dispatch, so every M above the threshold handed BN256 weights to the
BN128 split kernel. Nothing above M=256 was ever covered correctly.

Transform per family on first use, and pin kernel_family explicitly on both
the FlashInfer and the DeepGEMM call instead of relying on the two "auto"
rules agreeing -- a silent disagreement would compare two different kernels
and still report a pass. The family is now printed in the result line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ruff (SIM115) flags the bare open().read() calls that copy the split plan and
kernel sources into the generated directory. No functional change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Provide FP8-activation, FP4-weight expert-parallel execution with persistent fused kernels and the two-launch NVFP4 split path. Synchronize SS L2 math warps before decoded-weight reuse, validate packed layouts and distributed outputs, and document the collective APIs.

Include H20/H200 workload tables in the kernel guide. Mark the H200 MiMo EP8 M4096 latency pending verification while retaining all observed batches.

Validation: repository pre-commit hooks; CPU weight-layout tests; eight-rank H20 and H200 correctness and CUDA graph replay tests.
@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 41bc9d66-f678-41db-a2bc-e12786236fe9
📥 Commits

Reviewing files that changed from the base of the PR and between 57ab64b and ccfbe8e.

📒 Files selected for processing (2)
  • CLAUDE.md
  • docs/api/megamoe.rst
💤 Files with no reviewable changes (1)
  • docs/api/megamoe.rst
🚧 Files skipped from review as they are similar to previous changes (1)
  • CLAUDE.md

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.

📝 Walkthrough

Adds SM90 NVFP4 fused and split MegaMoE kernels and an MXFP4 fused kernel. Adds Python APIs for symmetric buffers, weight preparation, execution, and plan selection. Adds correctness tests, a benchmark, and API documentation.

Changes: SM90 MegaMoE FP4

Layer / File(s) Summary
Shared layouts, synchronization, and CUDA primitives
include/flashinfer/megamoe/*, include/flashinfer/megamoe/detail/*, csrc/megamoe/megamoe_sm90_common.cuh, csrc/megamoe/megamoe_sm90_layout.cu
Adds workspace and symmetric-buffer layouts, synchronization and scheduling helpers, PTX/WGMMA/TMA utilities, shared FP4 decoding, epilogue and combine code, and host-side TMA descriptor and layout helpers.
NVFP4 planning and fused/split execution
csrc/megamoe/megamoe_sm90_nvfp4_*, csrc/megamoe/megamoe_sm90_nvfp4_split_*, include/flashinfer/megamoe/sm90_nvfp4*
Adds split-plan selection and NVFP4 fused and split kernel paths, including launch bindings, dequantization, dispatch, and output-combine behavior.
MXFP4 fused execution
csrc/megamoe/megamoe_sm90_mxfp4_*, include/flashinfer/megamoe/mxfp4_dequant.cuh, include/flashinfer/megamoe/sm90_mxfp4*
Adds MXFP4 scale decoding and the configured fused-kernel launch path.
Python API, planning, and weight preparation
flashinfer/megamoe/*, flashinfer/jit/megamoe.py, flashinfer/__init__.py, docs/api/megamoe.rst, docs/index.rst
Adds symmetric-buffer allocation and staging, NVFP4/MXFP4 execution, fused and split planning, JIT module generation, packed-weight transformations, and API reference entries.
Validation and benchmark support
tests/moe/test_megamoe*, benchmarks/bench_megamoe_fp4_sm90.py, CLAUDE.md
Adds weight-layout tests, distributed correctness coverage, planner tests, and an SM90 benchmark that can compare results with a reference runner.

Priority: ➖ Normal

Estimated code review effort: 5 (Critical) | ~120 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant MegaMoESymmBuffer
  participant nvfp4_mega_moe
  participant JITandNativeKernel
  Caller->>MegaMoESymmBuffer: stage activations and routing tensors
  Caller->>nvfp4_mega_moe: submit output, weights, buffer, and token count
  nvfp4_mega_moe->>JITandNativeKernel: select family and dispatch kernel
  JITandNativeKernel->>MegaMoESymmBuffer: read symmetric-buffer inputs
  JITandNativeKernel->>Caller: write BF16 output
Loading
Merge Risk: 🟡 Moderate · up to ccfbe

Some supported MegaMoE configurations can fail to compile, and the MXFP4 and buffer-alignment paths retain execution risks. Resolve these issues before merging unless their affected configurations are explicitly excluded.

Pre-merge checks · Passed 4 · Failed 1

Failed Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 26.56% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 64 functions across 15 files. (2 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks (4 passed)
Check name Status Explanation
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.
Title check Passed The title clearly and concisely identifies the main change: adding SM90 W4A8 MegaMoE support.
Description check Passed The description follows the repository template and provides the implementation scope, usage, performance data, validation results, related-issue status, checklist completion, experimental-track statu…
Full details: Docstring Coverage

Explanation

Docstring coverage is 26.56% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 64 functions across 15 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@yzh119

yzh119 commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator

@flashinfer-bot run

@yzh119

yzh119 commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator

/bot run tests/moe/test_megamoe_fp4_sm90.py tests/moe/test_megamoe_fp4_weights.py tests/moe/test_megamoe_nvfp4_sm90.py

@flashinfer-bot

Copy link
Copy Markdown
Collaborator

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

Parameterize fused rank count and host layouts, isolate EP-specific JIT modules, and preserve EP8 tuning. Validate EP4 fused/split and push barrier modes on H20/H200, and record standard cold-L2 timing in the kernel guide.
Remove the standalone SM90 Markdown guide and clean up its references. Retain the API documentation and original root README.
@yyihuang

Copy link
Copy Markdown
Collaborator

@flashinfer-bot run

@yyihuang
yyihuang enabled auto-merge (squash) October 13, 2026 20:58
@yyihuang
yyihuang merged commit a12ddeb into flashinfer-ai:main Oct 13, 2026
27 of 28 checks passed
ouershua2299 added a commit to ouershua2299/sglang that referenced this pull request Oct 19, 2026
…4 FP4 checkpoints

On SM90, --moe-a2a-backend megamoe with the original DeepSeek-V4 FP4
checkpoints converts the experts losslessly to NVFP4 and runs
flashinfer.megamoe.nvfp4_mega_moe (W4A8; fused for M <= 256 tokens per rank,
split above). Needs flashinfer-ai/flashinfer#6343 and sgl-project#6517; without them the
load fails with an explicit error.

Adds the SGLANG_FLASHINFER_MEGAMOE_SM90_{PREWARM,KERNEL_FAMILY} env vars, a
DeepSeek-V4 cookbook section, a CPU conversion unit test, and a label-gated
8x H200 e2e test that skips when FlashInfer lacks flashinfer.megamoe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
yyihuang pushed a commit that referenced this pull request Oct 21, 2026
…support other models (#6517)

<!-- .github/pull_request_template.md -->

## 📌 Description

Follow-up to #6343 (SM90 FP4 MegaMoE):

1. **Cleanup fix:** with `single_active_dispatch_warp`, the inactive
dispatch warp cleaned the workspace with empty expert counts and left
pool blocks ≥ 32 of hot experts stale, so the next launch failed (H20,
97–128 tokens per rank, e.g. CUDA-graph capture); it now fetches the
counts first (outputs bitwise unchanged, timing within noise).
2. **H200 push dispatch:** add measured H200 EP8 buckets that enable
push for M ≤ 32 on DeepSeek-V4-Flash and MiMo-V2.5-Pro, and M ≤ 64 on
DeepSeek-V4-Pro (rotated pool at M 33–64), saving about 10 µs per call
(1.03–1.16x kernel time, bitwise-identical outputs; H20 and EP4 plans
unchanged).
3. **Other models:** accept any expert count that divides across ranks,
keep the selected plans compilable for untuned geometries (H20 buckets
match the full geometry; the planner checks the full fused shared-memory
layout, which grows with the expert count, and falls back to the next
legal smaller tile when the chosen one does not fit; unsupported shapes
raise `ValueError`), and allow push only on validated plans, so Kimi-K2,
DeepSeek-V3, GLM-4.x and Qwen3 shapes now run correctly while plans of
the three tuned geometries are unchanged.
4. **Usage docs:** the weight-preparation docs now state what callers
must convert before `transform_{nvfp4,mxfp4}_weights_for_mega_moe`:
Marlin byte order (checkpoints are usually low-nibble-first), scales
inside the decoder range (NVFP4 `6 * scale <= 448`; MXFP4 `2^-5`–`2^6`
for groups with nonzero codes, otherwise outputs silently saturate or
flush), and the excess moved into per-expert global scales passed at run
time; the module example shows the full flow.
5. **Framework hooks:** add `MegaMoESymmBuffer.prewarm()`, which builds
every NVFP4 module `nvfp4_mega_moe` can select up to capacity (sharded
across ranks, before CUDA graph capture), and `commit_staged()`, which
accepts rows a producer wrote directly into the now-documented staging
views (`x`, `x_sf`, `topk_idx`, `topk_weights`), so integrations no
longer reach into JIT internals or buffer fields.

**How to use:** FlashInfer only provides the kernels and the layout
transforms; frameworks convert checkpoint tensors to the format above at
load time, call `prewarm()` once, and stage inputs with `stage()` or in
place plus `commit_staged()` (the SGLang integration does this for
`nvidia/DeepSeek-V4-Flash-NVFP4`, where about 51% of blocks would
saturate without rebalancing).

## 🔍 Related Issues

Follow-up to #6343.

## 🚀 Pull Request Checklist

### ✅ Pre-commit Checks

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

## 🧪 Tests

- [x] Tests have been added or updated as needed.
- [x] All tests are passing (`unittest`, etc.).

- `test_hot_expert_workspace_cleanup` reproduces fix 1: it fails before
the fix on 8×H20 and passes after.
- New CPU tests cover the H200 buckets, the counter-dispatch limits and
plan legality for other geometries, including 2048 and 8192 experts
(every plan fits shared memory); every fused and split plan selected for
12 geometries (the three tuned models, six common shapes, 1024 / 2048 /
8192 experts) compiles on SM90, and plans of the tuned and previously
validated geometries are unchanged.
- `test_nvfp4_prewarm_and_direct_staging` checks that after `prewarm()`
no fused or split call compiles a new module, that in-place staging plus
`commit_staged()` gives correct results under CUDA graphs, and that
out-of-range counts are rejected.
- `tests/moe/test_megamoe_fp4_{sm90,ep,weights}.py`: 73 passed on
8×H200.

## Reviewer Notes


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **New Features**
* Added optional JIT prewarming for NVFP4 workloads across supported
token counts.
* Added direct staging support for efficiently preparing dispatch
inputs.
* Added support for transformed NVFP4 weights with per-expert global
scales.
* Improved dispatch planning across supported hardware configurations
and workload shapes.

* **Bug Fixes**
* Fixed workspace cleanup for inactive dispatch paths, improving
repeated CUDA Graph execution reliability.
  * Improved expert-count handling during dispatch cleanup.

* **Documentation**
* Clarified supported weight formats, scale requirements, dispatch
behavior, and configuration limits.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: ouershua2299 <180947746+ouershua2299@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
ouershua2299 added a commit to ouershua2299/sglang that referenced this pull request Oct 21, 2026
…4 NVFP4 checkpoints

On SM90, --moe-a2a-backend megamoe with the ModelOpt NVFP4 checkpoints
(nvidia/DeepSeek-V4-Flash-NVFP4) repacks the expert tensors at load into the
input of flashinfer.megamoe.transform_nvfp4_weights_for_mega_moe (Marlin byte
order, local scales rebalanced into the decoder range, one global scale per
expert) and runs flashinfer.megamoe.nvfp4_mega_moe (W4A8; fused for small
batches, split above). Needs flashinfer-ai/flashinfer#6343 and sgl-project#6517; without
them the load fails with an explicit error. EPLB is rejected on this path.

Adds the SGLANG_FLASHINFER_MEGAMOE_SM90_{PREWARM,KERNEL_FAMILY} env vars, a
DeepSeek-V4 cookbook section, a CPU unit test for the conversion, and a
label-gated 8x H200 e2e test that skips when FlashInfer lacks
flashinfer.megamoe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ouershua2299 added a commit to ouershua2299/sglang that referenced this pull request Oct 25, 2026
…4 NVFP4 checkpoints

On SM90, --moe-a2a-backend megamoe with the ModelOpt NVFP4 checkpoints
(nvidia/DeepSeek-V4-Flash-NVFP4) repacks the expert tensors at load into the
input of flashinfer.megamoe.transform_nvfp4_weights_for_mega_moe (Marlin byte
order, local scales rebalanced into the decoder range, one global scale per
expert) and runs flashinfer.megamoe.nvfp4_mega_moe (W4A8; fused for small
batches, split above). Needs flashinfer-ai/flashinfer#6343 and sgl-project#6517; without
them the load fails with an explicit error. EPLB is rejected on this path.

Adds the SGLANG_FLASHINFER_MEGAMOE_SM90_{PREWARM,KERNEL_FAMILY} env vars, a
DeepSeek-V4 cookbook section, a CPU unit test for the conversion, and a
label-gated 8x H200 e2e test that skips when FlashInfer lacks
flashinfer.megamoe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jiahanc pushed a commit that referenced this pull request Oct 27, 2026
<!-- .github/pull_request_template.md -->

## 📌 Description

This adds CuTe DSL NVFP4×FP8 to unified `moe_ep.MoEEpLayer`;
[#6343](#6343) provides
the separate `flashinfer.megamoe` CUDA API.

NV inherits MXFP4 frontend/kernel classes, overriding weight-format,
conversion and scheduling behavior with separate cache/workspace
identities. The default is `nvfp4_kperm=64`.

For H7168/I3072/E384/top-6/EP4, 13 capacities span 8–32768 tokens/rank.
Other capacities use the next larger entry, capped at 32768; other
shapes use heuristics. MX gains optional `compact_pull_buffer=True`,
disabled by default; MX auto/candidates are unchanged.

### Performance and reproduction

We compare NV defaults with MX main defaults with manual compact=True.
Positive means NV is slower.

| T/rank | NV µs | MX µs | Paired NV/MX Δ |
|---:|---:|---:|---:|
| 8 | 452.4 | 430.1 | +5.13% |
| 16 | 642.8 | 643.2 | -0.03% |
| 32 | 807.2 | 777.6 | +3.73% |
| 64 | 908.8 | 878.3 | +3.51% |
| 128 | 938.8 | 910.9 | +3.16% |
| 256 | 1014.7 | 990.7 | +2.42% |
| 512 | 1227.9 | 1191.2 | +3.08% |
| 1024 | 1607.8 | 1608.8 | -0.13% |
| 2048 | 2733.0 | 2868.6 | -4.92% |
| 4096 | 5020.7 | 5229.2 | -4.18% |
| 8192 | 9530.0 | 9845.9 | -3.26% |
| 16384 | 18507.6 | 19150.0 | -3.34% |
| 32768 | 36690.8 | 38506.6 | -4.80% |

We measured MegaMoE plus top-k reduction on 4×H200. Each block takes the
maximum of four rank medians. Paired ratios are summarized across three
rounds and three fresh processes. SM clocks were set to 1830 MHz.
Software power-cap events were recorded, so a constant clock in every
timed block is not guaranteed.

NV stores 5.9% more weight bytes, adding traffic. T512 observations were
retained: rounds ranged +2.71–13.08%, while process medians ranged
+2.89–3.67%.

```bash
nv_bench_root="$(mktemp -d -p . sm90-pr1-current-XXXXXX)"
for fresh in 1 2 3; do
  FLASHINFER_MOE_EP_KNOB_CACHE="$(pwd)/${nv_bench_root}/knob-${fresh}.json" \
  torchrun --standalone --nproc_per_node=4 benchmarks/bench_moe_ep_sm90_nvfp4.py \
    --tokens 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 \
    --hidden 7168 --intermediate 3072 --experts 384 --top-k 6 --seed 1234 \
    --nv-kperm 64 --nv-overlap auto --mx-compact \
    --rounds 3 --warmup 10 --iters 50 --padding-us 300 --cooldown-seconds 5 \
    --output "${nv_bench_root}/fresh-${fresh}.json" || exit $?
done
```

## 🔍 Related Issues

Stacked follow-up: ruoqianguo#1.

## 🚀 Pull Request Checklist

### ✅ Pre-commit Checks

- [x] 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`.
- [x] I have run the hooks manually with `pre-commit run --all-files`
and fixed any reported issues.

## 🧪 Tests

- [x] Tests have been added or updated as needed.
- [ ] All tests are passing (`unittest`, etc.).

The existing NCCL-EP high-throughput test
(tests/moe_ep/test_moe_ep_cudagraph_multirank.py, `high_throughput`)
also fails on unmodified main (eed63cd) in our 4×H200 environment; it
is unrelated to this PR.

Default MXFP4/FP8/BF16 cubins match tested main byte-for-byte. The
four-format EP4 end-to-end, host/unit and registration tests passed. NV
requires relative L2<2.5% against FP32 with promoted operands. All 55
public auto candidates compiled and ran; a new layer reused the cache.
This was functional smoke, not per-candidate FP32/performance
qualification.

Regression:

```bash
for dtype in nvfp4 mxfp4 fp8 bf16; do
  torchrun --standalone --nproc_per_node=4 -m pytest \
    "tests/moe_ep/test_moe_ep_sm90_pull_${dtype}_mega_multirank.py" \
    -vv -ra -rs --tb=short --full -m arch_hopper || exit $?
done
```

## Reviewer Notes

Please review weight preprocessing and the `nvfp4_kperm` layout tags,
NV's inheritance from the MX classes and the hooks added to the MX/FP8
base classes, the default tactic table, and MX's optional
`compact_pull_buffer`. The sf2 scale ring is covered by the stacked
[PR2](ruoqianguo#1).

## 🔬 Experimental Track

<!-- Only for PRs submitted under the experimental policy
(CONTRIBUTING.md → "Experimental APIs and Backends").
     Leave this section untouched for normal PRs. -->

- [ ] This PR is **experimental**: it adds or changes code under
`flashinfer/experimental/` and/or an `@flashinfer_experimental_api`.
Tracking issue: #
- [ ] The tracking issue names an owner, the reason for the experimental
path, and a graduation plan with a target release.
- [ ] Core changes are limited to a thin entry point (signature, shared
validation, feature-gate check, backend selection, handoff).
- [ ] Tests live in `tests/experimental/` and were validated on the
intended hardware; a runnable example is included.
- [ ] Nothing is registered in `flashinfer/aot.py`, and no experimental
backend is reachable from `backend="auto"` without
`FLASHINFER_ALLOW_EXPERIMENTAL_AUTO_BACKENDS=1`. (Calling an
`@flashinfer_experimental_api` or naming a backend explicitly is itself
the opt-in and needs no environment variable.)
- [ ] **Test scope declared below.** The experimental CI lane runs
exactly these targets, so keep them as narrow as the change allows.

<!-- Required for experimental PRs. Replace the commented lines below
with your targets.
Do not delete the fence or change its `experimental-tests` tag — the
experimental-track
watcher reads it verbatim to decide which targets to ask CI for. -->

```experimental-tests
# One target per line: a directory or a file. (A pytest ::selector is not
# supported -- the sharding runner cannot consume one.) Must be under
# tests/experimental/ and must exist. Delete these comment lines and add yours, e.g.
#
#   tests/experimental/test_my_backend.py
#   tests/experimental/my_backend/
#
# Declaring the whole tree (tests/experimental/) is allowed but means every
# experimental PR pays for every other feature's tests, in every matrix cell.
```



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **New Features**
* Added SM90 NVFP4 support for expert-parallel MegaMoE, including weight
preprocessing, configurable layouts, and automatic or custom tuning.
* Added options for K-permuted weights and FC1 scheduling, with
validation for incompatible configurations.
  * Added a distributed benchmark for measuring NVFP4 performance.
* **Documentation**
* Documented the backend’s numerical behavior, weight requirements,
configuration, tuning, and benchmark methodology.
* **Tests**
* Added multirank coverage for output accuracy, repeatability, layout
validation, and CUDA graph scheduling.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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