feat(moe_ep): add SM90 push FP8 mega-MoE backend for Hopper - #4069
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds a new SM90 push-style FP8 MegaMoE backend (protocol, GEMM, tests, benchmarks) and vendors two large CuTeDSL kernel source drops: an SM100 tree (MXFP8 and NVFP4 fused kernels) and an SM90 pull-style tree (FP8 GLU and NVFP4 kernels). Shared mega backends, docs, and packaging are updated to reference the new source layout. ChangesSM90 Push FP8 MegaMoE Backend
SM100 CuTeDSL MegaMoE Kernel Drop
SM90 Pull-Style CuTeDSL MegaMoE Kernel Drop
Estimated code review effort: 5 (Critical) | ~180 minutes Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces the 'sm90_push_fp8' MegaMoE backend, which provides an optimized FP8 communication and computation path for Hopper (SM90) GPUs. The changes include new kernel sources, Python shims for JIT compilation, and comprehensive correctness tests. I have reviewed the code and identified two issues: a potential cross-device link failure in the JIT cache renaming logic and a strict aliasing violation in the tag increment kernel. Both issues have actionable suggestions provided in the comments.
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.
| try { | ||
| std::filesystem::rename(tmp_cubin_path, cubin_path); | ||
| } catch (std::filesystem::filesystem_error const&) { | ||
| if (!std::filesystem::exists(cubin_path)) { | ||
| std::filesystem::remove_all(tmp_path); | ||
| throw; | ||
| } | ||
| } |
There was a problem hiding this comment.
In containerized or multi-partition environments (such as Docker or cluster nodes), the temporary directory (e.g., /tmp) and the cache directory (e.g., ~/.tensorrt_llm/cache) are often located on different filesystems. In such cases, std::filesystem::rename will fail with EXDEV (Invalid cross-device link), causing the compilation to fail completely. To make this robust, add a fallback to std::filesystem::copy_file and then remove the temporary directory if rename fails.
try {
std::filesystem::rename(tmp_cubin_path, cubin_path);
} catch (std::filesystem::filesystem_error const&) {
if (!std::filesystem::exists(cubin_path)) {
try {
std::filesystem::copy_file(tmp_cubin_path, cubin_path, std::filesystem::copy_options::overwrite_existing);
} catch (...) {
std::filesystem::remove_all(tmp_path);
throw;
}
}
}
There was a problem hiding this comment.
Both directories are under the same cache root, so this does not cross filesystems. The atomic rename is intentional to avoid exposing a partially written cubin to another rank.
| __global__ void bump_tag_kernel(int32_t* round_ctr) { | ||
| if (threadIdx.x == 0 && blockIdx.x == 0) { | ||
| auto* u = reinterpret_cast<uint32_t*>(round_ctr); | ||
| *u = *u + 1u; | ||
| } | ||
| } |
There was a problem hiding this comment.
Reinterpreting int32_t* as uint32_t* and dereferencing it violates the strict aliasing rule in C++, which is undefined behavior (UB). This can lead to compiler optimizations that misbehave or omit the write entirely. Instead of using reinterpret_cast, you can safely perform the increment by casting the dereferenced value to uint32_t, incrementing it, and casting it back to int32_t before writing it back.
if (threadIdx.x == 0 && blockIdx.x == 0) {
*round_ctr = static_cast<int32_t>(static_cast<uint32_t>(*round_ctr) + 1u);
}
There was a problem hiding this comment.
Access through the matching unsigned type is valid C++. We use it intentionally so the tag wraps modulo 2^32 without signed-overflow UB.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
tests/moe_ep/run_tests.sh (1)
111-127: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOptional:
run_sm90_pushis gated behindrequire_nccl_epinsiderun_multirank.
run_multirankreturns early whenrequire_nccl_epfails (Line 112), so the SM90 push suite—which uses the push backend, notnccl_ep—won't run in the multirank flow on boxes withoutnccl_ep. The standalonesm90_pushtarget still covers it, but consider runningrun_sm90_pushbefore thenccl_epgate if you want it exercised inall/multirankregardless.🤖 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_ep/run_tests.sh` around lines 111 - 127, Move the run_sm90_push invocation in run_multirank before the require_nccl_ep early-return gate so the SM90 push suite runs regardless of NCCL EP availability. Preserve the existing return-code aggregation by setting rc on failure, then run the NCCL-dependent suites only after the gate succeeds.flashinfer/moe_ep/kernel_src/sm90_push_megamoe/__init__.py (1)
19-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOptional:
__all__is not sorted (Ruff RUF022). Both new re-export modules declare__all__in insertion order; apply isort-style sorting to satisfy the lint rule and keep the two surfaces consistent.
flashinfer/moe_ep/kernel_src/sm90_push_megamoe/__init__.py#L19-L33: sort the__all__entries alphabetically.flashinfer/moe_ep/kernel_src/sm90_push_megamoe/shim/__init__.py#L17-L31: sort the__all__entries alphabetically.🤖 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 `@flashinfer/moe_ep/kernel_src/sm90_push_megamoe/__init__.py` around lines 19 - 33, Sort the __all__ entries alphabetically in both flashinfer/moe_ep/kernel_src/sm90_push_megamoe/__init__.py (lines 19-33) and flashinfer/moe_ep/kernel_src/sm90_push_megamoe/shim/__init__.py (lines 17-31), preserving all existing exports and applying the same isort-style ordering to both modules.Source: Linters/SAST tools
🤖 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_ep/_sm90_push_fp8_baseline.py`:
- Around line 28-63: Update quant_weights so each _weight_cache entry retains
the source w13 and w2 tensors alongside the quantized result, preventing
data_ptr reuse from returning stale weights. On cache hits, return the cached
result at the entry’s first element, and store the source tensors with the
result when populating the cache.
In `@tests/moe_ep/test_sm90_push_fp8_backend.py`:
- Around line 307-325: Update the capacity assertion in
test_public_ep1_forward_validation_and_capacity to match the token_capacity
wording emitted by validate_forward, replacing the max_tokens_per_rank
expectation while leaving the dtype checks unchanged.
---
Nitpick comments:
In `@flashinfer/moe_ep/kernel_src/sm90_push_megamoe/__init__.py`:
- Around line 19-33: Sort the __all__ entries alphabetically in both
flashinfer/moe_ep/kernel_src/sm90_push_megamoe/__init__.py (lines 19-33) and
flashinfer/moe_ep/kernel_src/sm90_push_megamoe/shim/__init__.py (lines 17-31),
preserving all existing exports and applying the same isort-style ordering to
both modules.
In `@tests/moe_ep/run_tests.sh`:
- Around line 111-127: Move the run_sm90_push invocation in run_multirank before
the require_nccl_ep early-return gate so the SM90 push suite runs regardless of
NCCL EP availability. Preserve the existing return-code aggregation by setting
rc on failure, then run the NCCL-dependent suites only after the gate succeeds.
🪄 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: 3c1c788f-1baf-46c3-8f7c-ba053d6f9bed
📥 Commits
Reviewing files that changed from the base of the PR and between c83607a and 0e01918ffb2b4189baf26cbb4deda5016d6987e7.
📒 Files selected for processing (47)
benchmarks/bench_sm90_push_megamoe.pybenchmarks/sm90_push_megamoe_baseline.pybenchmarks/sm90_push_megamoe_reference.pydocs/design_docs/moe_ep_architecture.mddocs/design_docs/moe_ep_runbook.mdflashinfer/aot.pyflashinfer/comm/mnnvl.pyflashinfer/moe_ep/__init__.pyflashinfer/moe_ep/backends/mega/kernel/__init__.pyflashinfer/moe_ep/backends/mega/kernel/deep_gemm_mega/backend.pyflashinfer/moe_ep/backends/mega/kernel/mxfp8_cutedsl/backend.pyflashinfer/moe_ep/backends/mega/kernel/nvfp4_cutedsl/backend.pyflashinfer/moe_ep/backends/mega/kernel/sm90_push_fp8/__init__.pyflashinfer/moe_ep/backends/mega/kernel/sm90_push_fp8/backend.pyflashinfer/moe_ep/backends/mega/kernel/sm90_push_fp8/config.pyflashinfer/moe_ep/backends/mega/kernel/sm90_push_fp8/staging.pyflashinfer/moe_ep/backends/mega/kernel/sm90_push_fp8/weights.pyflashinfer/moe_ep/core/kernel/base.pyflashinfer/moe_ep/kernel_src/sm90_push_megamoe/ACKNOWLEDGEMENT.mdflashinfer/moe_ep/kernel_src/sm90_push_megamoe/__init__.pyflashinfer/moe_ep/kernel_src/sm90_push_megamoe/shim/__init__.pyflashinfer/moe_ep/kernel_src/sm90_push_megamoe/shim/gemm.pyflashinfer/moe_ep/kernel_src/sm90_push_megamoe/shim/jit.pyflashinfer/moe_ep/kernel_src/sm90_push_megamoe/shim/protocol.pyflashinfer/moe_ep/kernel_src/sm90_push_megamoe/shim/runner.pyflashinfer/moe_ep/kernel_src/sm90_push_megamoe/shim/weights.pyflashinfer/moe_ep/kernel_src/sm90_push_megamoe/src/a2a/sm90_push_a2a.cuhflashinfer/moe_ep/kernel_src/sm90_push_megamoe/src/a2a/sm90_push_a2a_ops.cuflashinfer/moe_ep/kernel_src/sm90_push_megamoe/src/fp8_gemm/fp8_moe_binding.cuflashinfer/moe_ep/kernel_src/sm90_push_megamoe/src/fp8_gemm/fp8_moe_fc1_fused.cuhflashinfer/moe_ep/kernel_src/sm90_push_megamoe/src/fp8_gemm/fp8_moe_jit.cuhflashinfer/moe_ep/kernel_src/sm90_push_megamoe/src/fp8_gemm/fp8_moe_launcher.cuhflashinfer/moe_ep/kernel_src/sm90_push_megamoe/src/fp8_gemm/fp8_moe_scheduler.cuhflashinfer/moe_ep/modes/mega_layer.pypyproject.tomltests/conftest.pytests/gemm/test_sm90_moe_gemm.pytests/gemm/test_sm90_moe_gemm_contract.pytests/moe_ep/_sm90_push_fp8_baseline.pytests/moe_ep/_sm90_push_fp8_reference.pytests/moe_ep/run_tests.shtests/moe_ep/test_mega_layer_validation.pytests/moe_ep/test_sm90_push_fp8_backend.pytests/moe_ep/test_sm90_push_fp8_backend_cpu.pytests/moe_ep/test_sm90_push_fp8_kernel.pytests/moe_ep/test_sm90_push_fp8_orchestrator.pytests/moe_ep/test_sm90_push_fp8_packaging.py
|
@leonardHONG Thank you for the PR! let me run it on my end and get back to you! |
|
@leonardHONG My apologies for the delay! I am still restructuring moe_ep to adapt new dtypes and arch (+creating vLLM PR of fi moe_ep). Tentatively will process your PR and this #4113 by mid-next week |
|
@leonardHONG I was able to reproduce your perf on my end. The numbers look great! Can you resolve the conflicts, and rebase it against this PR: #4113? That way we can cleanly merge it |
51ba0e9 to
6586c79
Compare
1a9d124 to
79e60a6
Compare
|
Done, rebased onto the latest main and cleaned up the remaining coderabbit comments. Thanks! |
mhoqueanik
left a comment
There was a problem hiding this comment.
Overall looks good to me. We can proceed with the merge once the LICENSE/ACKNOWLEDGE.md issue is clear.
|
/bot run tests/moe_ep |
|
[SUCCESS] Pipeline #62029339: 18/18 executed test jobs passed |
…flashinfer-ai#4069) Ports flashinfer-ai#4069 (head 301f8ce, PR still open) onto the taxonomy/provenance layout, per the incorporation plan: - kernel_src/sm90/push_style_megamoe/: verbatim byte-for-byte drop from the PR head (src/{a2a,fp8_gemm} CUDA sources, shim/, ACKNOWLEDGEMENT.md; no {$nv-internal-release} markers at this SHA) + VENDOR.md provenance record. Re-diff against the merged SHA when the PR lands. - backends/mega/kernel/sm90/fp8_fp8_bf16_push_cuda/: the 5 wrapper files relocated from upstream's flat kernel/sm90_push_fp8/, import depths fixed for the extra package level, config renamed to Sm90_Fp8_Fp8_Bf16_PushCuda_MegaMoeConfig with kernel_name "sm90_fp8_fp8_bf16_push_cuda", registered with deprecated_aliases=("sm90_push_fp8",). - Package wiring: moe_ep/__init__.py taxonomy import + Sm90PushFp8MegaMoeConfig deprecated alias + preprocess_sm90_push_fp8_mega_weights; kernel/sm90 re-exports; alias row in test_deprecated_aliases.py. - Core deltas from the PR: mega_layer.py allocates the output before stage_inputs; pyproject package-data ships the drop's .cu/.cuh for non-editable installs; conftest isolated_deep_gemm_cache fixture; the mega-layer allocation-order regression test. - Tests: the 9 sm90_push_fp8 test files (names kept for re-sync friction) with imports/config names rewritten to the taxonomy. Deviation from upstream: run_tests.sh exposes sm90_push as its own 2-GPU Hopper target instead of folding it into multirank — on non-Hopper nodes the arch-marked files collect 0 tests and torchrun turns pytest exit 5 into a failure. - Docs: runbook + architecture doc gain the new drop and backend. Also hardens the unit target against the long-known suite-accumulated heap corruption: with every test passing, the process aborted either at the first heavy import burst (the isolated nvfp4 warmup test) or — new signature, job 2388315 — in CPython teardown after the pytest summary ("malloc(): unaligned tcache chunk detected"). Both unit pytest invocations now exit via os._exit(pytest_rc), skipping interpreter finalization; rationale in the runbook. Root cause still open (needs ASAN). Validated on B200 (jobs 2388315/2388326): registry/alias smoke, deprecated aliases, unit x3 green (396 passed / 72 skipped; push cpu/packaging/contract tests run, Hopper-marked kernel tests skip). Kernel/orchestrator/GEMM tests need an H100 node — tracked as a follow-up. AI-assisted (Claude Code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Hi @leonardHONG, Thanks for the great work! Is there a benchmark comparing the pull- and push-style backends under the same setup? Which workloads currently favor each style? |
yep, i posted the 8× h100 comparison [above]. pull is a little better for small token counts, while push wins on larger workloads. push currently needs 128-aligned dimensions and top-k ≤ 8, but i may support more shapes later. |
Aligns the branch with flashinfer TOT (13 commits), resolving the conflicts GitHub flagged on PR flashinfer-ai#4449. The bulk of the collision is the squash-merge of flashinfer-ai#4069 (f9b13ef) — the SM90 push-style FP8 backend this branch already carries in taxonomy form (7984140, vendored drop verified byte-identical to the merged SHA): - moe_ep/__init__.py, backends/mega/kernel/__init__.py: keep the taxonomy spellings (upstream's side is the pre-restructure flat imports of the same content, nothing new). - tests/moe_ep/test_sm90_push_fp8_{backend,backend_cpu,packaging}.py add/add: keep ours (taxonomy imports; verified a strict superset of upstream's copies). - pyproject.toml: keep the package-data comment for the push drop. - Upstream's flat backends/mega/kernel/sm90_push_fp8/ wrapper (5 files, auto-merged as new) removed — ours lives at sm90/fp8_fp8_bf16_push_cuda/. - run_tests.sh: restored to our version — the auto-merge duplicated run_sm90_push and re-folded it into run_multirank (upstream's shape, deliberately rejected in 7984140: on non-Hopper nodes the arch-marked files collect 0 tests and torchrun turns pytest exit 5 into a failure). Upstream's delta to this file is sm90_push wiring only. No other moe_ep deltas in this upstream range. AI-assisted (Claude Code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lean vs f9b13ef PR flashinfer-ai#4069 merged upstream 2026-08-12 as squash f9b13ef. Re-diffed the vendored kernel_src/sm90/push_style_megamoe tree against the merged SHA: byte-for-byte identical (no post-review deltas between the vendored PR head 301f8ce and the merge). Future syncs diff against main. AI-assisted (Claude Code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ush-style FP8 backend; sync CuTe-DSL 4.7 quant-staging fix (#4449) ## Summary Three things: a layout/naming refactor of `flashinfer.moe_ep`'s mega-kernel layer, the incorporation of the SM90 push-style FP8 backend (#4069, since merged upstream) as the first new backend added in the restructured shape, and one vendored-kernel sync that fixes the fused activation-quant staging crash on CuTe-DSL 4.7 — un-blocking 4.7.x and lifting the temporary `==4.6.1` pin. The branch is merged up to upstream/main (2febce5, past the v0.6.17 line and the #4069 squash). The refactor organizes the layer around two orthogonal views: 1. **Taxonomy (user view)** — backends move to `backends/mega/kernel/sm<arch>/<act_dtype>_<weight_dtype>_<out_dtype>_<kernel_style>/`, and registry `kernel_name` strings plus config classes carry the same fully-qualified names. One glance at a name now tells you the architecture, the activation/weight/output dtypes, and the kernel style: | old kernel_name | new kernel_name | new config class | |---|---|---| | `deep_gemm_mega` | `sm100_fp8_fp4_bf16_deepgemm` | `Sm100_Fp8_Fp4_Bf16_Deepgemm_MegaMoeConfig` | | `nvfp4_cutedsl` | `sm100_nvfp4_nvfp4_bf16_cutedsl` | `Sm100_Nvfp4_Nvfp4_Bf16_Cutedsl_MegaMoeConfig` | | `mxfp8_cutedsl` | `sm100_mxfp8_mxfp8_bf16_cutedsl` | `Sm100_Mxfp8_Mxfp8_Bf16_Cutedsl_MegaMoeConfig` | | `sm90_pull_fp8` | `sm90_fp8_fp8_bf16_pull_cutedsl` | `Sm90_Fp8_Fp8_Bf16_PullCutedsl_MegaMoeConfig` | | `sm90_push_fp8` (new, from #4069) | `sm90_fp8_fp8_bf16_push_cuda` | `Sm90_Fp8_Fp8_Bf16_PushCuda_MegaMoeConfig` | Naming conventions: deep_gemm dtypes are plain `fp8`/`fp4`, matching upstream `deep_gemm.fp8_fp4_mega_moe`; the mx/nv prefixes are reserved for the cutedsl kernels' block-scaled formats. Output dtype is always bf16 — nvfp4's `combine_dtype` is comm-wire compression, not an output format. 2. **Provenance (kernel-dev view)** — vendored kernel sources in `kernel_src/` are keyed by upstream repo snapshot, not by architecture: `kernel_src/sm100/cutedsl_megamoe` moves to `kernel_src/cutedsl_megamoe` (the mother repo ships kernels for multiple arches, so an smXX level misrepresents it). Each drop mirrors the vendor repo layout — `src/` byte-for-byte upstream, all adaptation in `shim/` — and gains a `VENDOR.md` recording upstream repo/commit/sync state and pending local diffs. A new `kernel_src/README.md` states the contract explicitly: **no edits to `src/` of any kind — including docstrings, comments, and lint fixes**; tool warnings against vendored files (docstring-coverage gates, review bots) are handled by excluding the path, never by editing the file. The sm90 fork trees (`kernel_src/sm90/pull_style_cutedsl_megakernel` from #4113, `kernel_src/sm90/push_style_megamoe` from #4069) intentionally stay separate snapshots — one kernel_src dir = one upstream commit — and fold into the mother drop if/when upstream merges them. **Why:** verbatim snapshots must stay diffable against one upstream commit, and splitting vendored trees per-dtype or per-arch breaks re-sync; meanwhile users navigate by architecture and dtype, not by which vendor repo a kernel came from. Putting each concern where its audience looks resolves the tension. The layout rule is documented in `docs/design_docs/moe_ep_architecture.md`, and it is what makes new backend families routine — demonstrated in this very PR by the SM90 push-style incorporation below, and next by the follow-up backend-family PRs (SM100 BF16 #4386, SM120 MXFP8). ## Directories affected All changes live under `flashinfer/moe_ep/` plus its tests and docs: - `backends/mega/kernel/sm100/{fp8_fp4_bf16_deepgemm,nvfp4_nvfp4_bf16_cutedsl,mxfp8_mxfp8_bf16_cutedsl}/` and `backends/mega/kernel/sm90/{fp8_fp8_bf16_pull_cutedsl,fp8_fp8_bf16_push_cuda}/` — taxonomy backend wrappers (moved/renamed; push_cuda is new). - `kernel_src/cutedsl_megamoe/` (moved from `kernel_src/sm100/cutedsl_megamoe/`), `kernel_src/sm90/pull_style_cutedsl_megakernel/`, `kernel_src/sm90/push_style_megamoe/` (new) — provenance-keyed vendored drops, each with `VENDOR.md`; new `kernel_src/README.md` states the no-edits contract. - `backends/mega/kernel/tuning.py` + per-backend `tuner.py` files — tuning machinery moved out of `tune.py` (now a CLI shim). - `core/kernel/registry.py`, `moe_ep/__init__.py` — deprecated-alias resolution and re-exports. - `tests/moe_ep/`, `docs/design_docs/moe_ep_{architecture,runbook}.md`, `pyproject.toml`/`.pre-commit-config.yaml` excludes, `run_tests.sh` (new 2-GPU `sm90_push` target). ## Test results - **Full `run_tests.sh` matrix — all 12 targets green** on 4xH100 (job 2389821, 2026-08-13), including the new `sm90_push` Hopper target and the fault-tolerance suites after the deadlock fixes. - **B200** (jobs 2388315/2388326): registry/alias smoke, deprecated aliases, unit x3 green — 396 passed / 72 skipped (push cpu/packaging/contract tests run; Hopper-marked kernel tests skip). - **Unit target re-validated green** after the second upstream merge (job 2389880) and again after the round-2 CodeRabbit fixes (job 2389916), same 396/72 counts, B200. - **8x B200** (jobs 2384640/2384641/2384650): quant-staging sync matrix fully green on both dsl 4.6.1 and 4.7.0 (details in the vendored-sync section below). - **GB200 + B200**: mxfp8/nvfp4 multirank oracle suites with the per-cell tolerance band. - Microbenchmark re-run: no regressions vs pre-restructure reference numbers (deep_gemm parity; cutedsl kernels at or above their previous points). - `pre-commit run -a` fully green at the branch head (e9f791a). ## SM90 push-style FP8 backend (incorporates #4069) Ports #4069 (head 301f8ce; since merged to main as f9b13ef — re-diffed, byte-identical, no post-review deltas) onto the taxonomy/provenance layout, serving as the first proof of the "one taxonomy backend dir + one provenance-keyed kernel drop" recipe: - **`kernel_src/sm90/push_style_megamoe/`** — verbatim byte-for-byte drop from the PR head (`src/{a2a,fp8_gemm}` CUDA sources, `shim/`, ACKNOWLEDGEMENT.md) plus a `VENDOR.md` provenance record. - **`backends/mega/kernel/sm90/fp8_fp8_bf16_push_cuda/`** — the five wrapper files relocated from upstream's flat `kernel/sm90_push_fp8/`, config renamed to `Sm90_Fp8_Fp8_Bf16_PushCuda_MegaMoeConfig`, registered with `deprecated_aliases=("sm90_push_fp8",)`. - **Core deltas carried from the PR:** `mega_layer.py` allocates the output before `stage_inputs`; pyproject package-data ships the drop's `.cu`/`.cuh` for non-editable installs; the `isolated_deep_gemm_cache` conftest fixture; the mega-layer allocation-order regression test. - **Tests:** the nine sm90_push_fp8 test files (names kept to minimize re-sync friction) rewritten to the taxonomy. Deviation from upstream: `run_tests.sh` exposes `sm90_push` as its own 2-GPU Hopper target instead of folding it into multirank — on non-Hopper nodes the arch-marked files collect 0 tests and torchrun turns pytest exit 5 into a failure. ## CuTe-DSL 4.7 quant-staging fix (vendored sync) The `CUDA_ERROR_MISALIGNED_ADDRESS` crash on cutlass-dsl 4.7.0 — which presented as a deep_gemm mega multirank failure — was root-caused to the **fused bf16→quantized activation staging** (`DataPreprocess` in the vendored cutedsl_megamoe tree), which every mega staging path shares, deep_gemm's included. The kernel team's fix is synced in as a single-file partial re-sync per the vendoring policy: - `kernel_src/cutedsl_megamoe/src/src/inputs_process.py` + `src/common/host_utils.py` taken **verbatim** from upstream `bangyus/cutedsl_megamoe @ 50117315d`, recorded in `VENDOR.md` under pending-diffs (resolves at the next full re-sync). The mxfp8 quant kernel is reworked so each lane owns one contiguous 16-byte fp8 store (adjacent lanes reduce the 32-element block amax via `shuffle_sync_bfly`, even lane writes the E8M0 scale), and `__init__` gains a hidden-size row-alignment guard. - Also fixes a stale pre-commit exclude left by the directory move (`kernel_src/sm100/cutedsl_megamoe` → `kernel_src/cutedsl_megamoe`) so hooks stop reformatting the verbatim `src/` tree. Validated on 8x B200 (jobs 2384640/2384641/2384650), full matrix green on **both** DSL versions: | section | dsl 4.6.1 | dsl 4.7.0 | |---|---|---| | drop's own harness (`python -m src.inputs_process`: bit-exact scales + SNR vs reference, nvfp4 offline/online + mxfp8) | 3/3 | 3/3 | | `test_fused_quant_stage.py` | 11/11 | 11/11 | | mega multirank x4 ranks (deep_gemm + nvfp4 + mxfp8) | 20/rank | 20/rank | | single-rank kernel-vs-reference oracles | 6/6 | 6/6 | The deep_gemm multirank suite previously crashed deterministically on 4.7.0; it now passes there. On the strength of this, the runbook's temporary `==4.6.1` pin is lifted (see the DSL guidance bullet below). ## Also in this PR - **Per-backend tuners.** `flashinfer/moe_ep/tune.py` becomes a pure CLI shim (surface unchanged: `python -m flashinfer.moe_ep.tune`); dtype-specific tuning moves into the backends (`sm100/{nvfp4,mxfp8}.../tuner.py`), shared sweep machinery (dist lifecycle, skewed restage, schedule grid, timed sweep tail) into `backends/mega/kernel/tuning.py`. - **CUTLASS DSL guidance updated (pin lifted).** The test-container recipe briefly carried a hard `nvidia-cutlass-dsl==4.6.1` pin because 4.7.0 crashed the mega multirank path; with the crash root-caused and fixed above, the runbook now allows `-U` installs again. 4.6.1 remains the perf-validated reference (pin it when producing numbers meant to compare against the TUNING.md tables); 4.7.0 is correctness-validated. The library's supported floor remains 4.5.2 (the MR!27 WAR already in main). - **Per-cell bf16 term-magnitude tolerance band** for the mxfp8 multirank oracle compares — a principled per-cell bound derived from the bf16 accumulation term magnitudes, replacing the global rtol that produced rare single-cell false failures. Validated on GB200 and B200. - **One-direction import layering rules** codified in the architecture doc, with all `cutedsl_megamoe` access routed through the drop's `__init__` rather than deep-path imports. ## Merge with upstream/main and follow-up fixes The branch is merged up to upstream/main in two steps. First to aaf97df (95 commits, incl. the v0.6.17 release line): conflict resolution keeps the restructure spellings everywhere; upstream's one real kernel advance in the moved tree — the 4fbac49 singleton-expert TMA-modes fix (#4296) — is ported onto the renamed paths and recorded in `VENDOR.md`. Notable upstream picks now in-tree: `BootstrapConfig.device` (#4348) and the E_local=1 nvfp4 oracle regression test. Second merge to 2febce5 (13 commits), resolving the conflicts created when #4069 itself squash-merged upstream (f9b13ef) with the same moe_ep files in the pre-restructure flat layout. Every conflict resolves to the taxonomy spellings (upstream's side is the flat spelling of content this branch already carries); upstream's flat `backends/mega/kernel/sm90_push_fp8/` wrapper and its re-folding of `sm90_push` into the multirank target are dropped in favor of this branch's layout. The vendored push drop was re-diffed against the merged SHA: byte-for-byte identical, no post-review deltas (recorded in `VENDOR.md`). Post-merge hardening found and fixed by full-suite runs: - **Merge fallout:** auto-merged regions had re-introduced pre-restructure `kernel_src.sm100.cutedsl_megamoe` spellings in 12 files, silently skipping entire GPU test files via `importorskip`; restored, and upstream's re-added flat `sm90_pull_fp8/` wrapper removed. - **FT test deadlocks (4xH100):** the fault-tolerance multirank test's evicted victim ran a collective `destroy()` against the survivors' barrier sequence, deadlocking until the NCCL watchdog — the victim tail now mirrors the survivors' barrier→destroy→barrier shape. The FT smoke's survivors now keep forwarding past the kill window so they actually observe the fault, and `run_tests.sh` judges the smoke by counting `SMOKE_RESULT` markers (torchrun interleaves lines). - **Unit-suite crasher isolation:** the long-known in-suite-only interpreter abort (heap corruption accumulating over the ~200-test single-process run, firing during a plain module import or in CPython teardown) is worked around by running the trigger test in its own pytest process and exiting the unit invocations via `os._exit(pytest_rc)`; rationale in the runbook, root cause tracked (needs ASAN). All tests pass — this is process-teardown hygiene, not a kernel bug. **CodeRabbit review responses.** Two rounds of actionable findings are fixed in-branch (640b75f, 57926a9) — highlights from round 2: the push packaging test's import-boundary gate was building the pre-taxonomy flat backend path and passing vacuously (fixed, now validates all 5 wrapper files); the test baseline's weight cache gains weakref eviction; `cutedsl_megamoe/shim/__main__.py` added so the documented `python -m ...shim` commands resolve; the cutedsl_megamoe `VENDOR.md` provenance TODOs are filled. Findings inside verbatim-vendored `kernel_src/**/src/` trees are deliberately not patched locally — they route upstream per the vendoring policy in `kernel_src/README.md`. **Lint.** `pre-commit run -a` is fully green (clang-format, mypy, ruff check/format, whitespace hooks). The final e9f791a is a pure ruff-format pass over 13 moe_ep files — line wraps where the longer taxonomy class names pushed calls past the limit. The vendored `src/` trees are untouched by hooks (the exclude set holds). ## Backward compatibility External callers keep working unchanged — both the old config-class names and the old kernel_name strings remain as deprecated aliases: - **Config classes**: `DeepGemmMegaMoeConfig`, `Nvfp4CutedslMegaMoeConfig`, `Mxfp8CutedslMegaMoeConfig`, `Sm90PullFp8MegaMoeConfig`, and `Sm90PushFp8MegaMoeConfig` are plain aliases of the new `Sm<arch>_..._MegaMoeConfig` classes, defined (with a removal note) in `flashinfer/moe_ep/__init__.py` right below the taxonomy imports, and still exported via `__all__`. - **Registry kernel_name strings**: `deep_gemm_mega`, `nvfp4_cutedsl`, `mxfp8_cutedsl`, `sm90_pull_fp8`, and `sm90_push_fp8` resolve to the taxonomy backends through the `deprecated_aliases=` parameter of each backend's `@register_mega_kernel(...)` decoration; the resolution machinery lives in `flashinfer/moe_ep/core/kernel/registry.py`. Using one emits a `DeprecationWarning`, and aliases are excluded from the available-kernels listing. - Both alias families WILL BE REMOVED in a future release (noted at both locations above). ## Testing - Directory moves and renames are behavior-preserving by construction; registry tests exercise both the taxonomy names and the deprecated aliases (alias use warns; the kernel listing shows taxonomy names only). - Full `run_tests.sh` matrix (all 12 targets) green on 4xH100 (job 2389821); B200 unit/registry/alias validation (jobs 2388315/2388326) — see Test results above. - The quant-staging sync validated on both dsl 4.6.1 and 4.7.0 (matrix above); mxfp8/nvfp4 multirank oracle suites validated on GB200 and B200. - The standalone MoE-EP microbenchmark was re-run against this branch with no regressions vs the pre-restructure reference numbers (deep_gemm parity; cutedsl kernels at or above their previous points). ## Relation to other PRs Re-layering on top of #4113 (SM90 pull-style FP8 backend, merged) and incorporating #4069 (SM90 push-style FP8 backend, merged upstream 2026-08-12; the vendored drop was re-diffed against the merged SHA f9b13ef and is byte-identical). This is the base branch for the upcoming backend-family PRs — SM100 BF16 (#4386) and SM120 MXFP8 — each of which adds one taxonomy backend directory plus one provenance-keyed kernel drop in the shape this restructure establishes. Both follow-up branches are already rebased onto this branch's head (unit target green on each), so they apply as exactly their backend-specific commits once this merges. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Md Anik <mhoqueanik@cw-dfw-cs-001-login-01.cm.cluster> Co-authored-by: Md Saidul Hoque Anik <mhoqueanik@login-preos01.a51.clusters.nvidia.com>
…er-ai#4069) ## 📌 Description This PR registers `sm90_push_fp8`, a whole-layer expert-parallel MoE backend for SM90 GPUs connected through single-node NVLink, as a fourth mega backend behind the existing `register_mega_kernel` plugin surface. The public entry point is `MoEEpLayer` with `Sm90PushFp8MegaMoeConfig`. Existing backend behavior is unchanged. The steady-state forward path covers dispatch, FP8 block-scale expert FFN, and combine without host-side synchronization, and supports CUDA Graph capture after initialization. The diagram below shows the path with all three optional optimizations enabled: ```text bump_tag -> wait_acks -> deduplicated dispatch -> wait_prefix -> compact -> FC1 with fused SwiGLU + 1x128 quantization -> FC2 -> grouped combine with owner-side FP32 route reduction -> BF16 reduction of received owner rows -> ack ``` The backend provides three independently configurable optimizations. Validation and performance results below use all three unless stated otherwise. * **Deduplicated dispatch** stores one payload row per token and destination rank instead of one per route. In the tested configurations, with all other options fixed, its output matched the per-route path under `torch.equal`. For `K=6`, `EP=4`, and random routing, it reduces dispatch payload bytes by approximately 43%. * **Grouped combine** performs FP32 route reduction on the owner rank before pushing one quantized row per token. Its correctness gate compares the final output against the same reference used by the per-route path because the quantization point moves. * **Fused FC1 epilogue** performs SwiGLU and 1x128 activation quantization inside the FC1 DeepGEMM epilogue, eliminating an approximately 1 GiB intermediate BF16 activation buffer per rank at the DSV3 EP8 shape. In the tested configurations, with all other options fixed, its output matched the unfused path under `torch.equal`. The unfused path remains the default and maintained reference; the fused path is opt-in. ## Motivation The existing Hopper EP stack composes NCCL all-to-all with a local fused-MoE operator, paying for additional launches, intermediate buffers, and quantization boundaries. `sm90_push_fp8` writes quantized payloads directly into peer-mapped symmetric memory and feeds received rows directly into the expert GEMM pipeline. ## Contracts introduced by this PR * `capacity_factor` bounds the GEMM, TMA, and scale buffers, rather than only the protocol window. The workspace query takes explicit `expected_m` and `max_rows`, so block-M tactics and cubin keys remain independent of the memory bound. * Building the grouped GEMM requires CUDA Toolkit 12.8 or newer. The generator explicitly rejects older toolkits, and AOT skips this module below CUDA 12.8, allowing JIT-cache wheels to continue building on CUDA 12.6. Loading and running the generated module also requires a CUDA runtime version 12.8 or newer. * Runner initialization elects one NVCC-capable rank per cache and tactic group to compile cold DeepGEMM cubins. Other ranks load the resulting cubins from disk. * A warm cache works without NVCC. * A cold cache without NVCC fails during initialization with an error explaining how to select the fallback. * `TRTLLM_DG_ENABLED=0` selects the fixed-tactic CUTLASS fallback on the unfused path. * Wait kernels use a `%globaltimer` deadline and publish a shared abort marker before trapping. Peers polling the same protocol window observe the marker and fail. Because a trap leaves a sticky CUDA error, the process launcher must terminate the full rank group from the CPU. * This backend is a stateful variant of the mega-kernel contract: * construction binds transformed static weights; * `stage_inputs` pre-binds the caller's output tensor. The architecture and runbook documentation are updated accordingly, including narrowing the two-entry wording to buffer-oriented kernels. The `MegaKernelBackend` lifecycle itself is unchanged. ## Correctness Full kernel acceptance ran on an 8× H800 NVLink node: | Suite | Scale | Result | | --------------------------------------------------------- | --------------: | ---------------------------- | | Single-GPU suite, 105 configurations | 1 GPU | all passed | | Distributed suites | EP2 / EP4 / EP8 | all passed | | Uneven token distributions, zero-token ranks, launch skew | EP2–EP8 | all passed | | CUDA Graph capture and replay | tested configurations | all passed | | 200-round oracle-checked soaks | every tested configuration | all passed | | Eight-GPU acceptance matrix at DSV3 shapes | EP8 | all passed, no skipped cases | The final PR tree was then revalidated across three Hopper variants: | Suite | Hardware | Result | | ------------------------------------------------------------------------------ | -------------- | -------------------: | | Single-GPU suite, all configurations | 1× H20 | 130 passed, 0 failed | | `torchrun` EP2 | 2× H100 NVLink | 15 passed, 0 failed | | `torchrun` EP4 | 4× H20 NVLink | 15 passed, 0 failed | | `compute-sanitizer` memcheck / racecheck / initcheck, excluding trap and soaks | 1× H20 | Clean | Additional hardware-verified properties on the final PR tree: * In the tested configurations, the fused FC1 output matched the unfused reference under `torch.equal`. * Leader deduplication was verified by counting NVCC invocations against a shared cold cache: exactly one compilation per cubin across four ranks. * Warm-cache execution without NVCC was exercised end to end. * Cold-cache fail-fast behavior was exercised end to end. * The CUTLASS fallback was exercised end to end. ## Performance Performance numbers were collected during kernel acceptance on the 8× H800 node. The final PR tree was functionally revalidated as listed above. ### Setup * H800 NVLink * CUDA 12.9 * PyTorch 2.8 * barrier-aligned timing * maximum latency across ranks * autotuned baseline tactics * identical correctness gates on both sides * `T` denotes live tokens per rank The tables report `baseline latency / sm90_push_fp8 latency`; values greater than `1×` favor this backend. `H`, `E`, and `K` denote hidden size, expert count, and top-k routes. `random` distributes routes across experts, while `hot1` concentrates routes on one expert. ### Versus NCCL all-to-all with the same FP8 block-scale compute | Configuration | Decode T64 | Throughput T2048 | | ----------------------- | ---------: | ---------------: | | EP4 SMALL, H4096 E32 K6 | 1.1–1.5× | 2.0–2.3× | | EP4 DSV3, H7168 E256 K8 | 1.01× | 2.0–2.3× | Against a wire-only NCCL transport reference that excludes compaction and combine reduction, the push transport measured **1.9–3.2×** faster. This number should be interpreted only as a comparison against that specific measured reference. It is not a claim against third-party EP libraries. ### Versus NCCL + `cutlass_fused_moe` FP8 | Configuration | Decode T64 | T2048 random | T2048 hot1 | | ------------- | ---------: | -----------: | --------------: | | EP4 DSV3 | 1.06× | 2.3× | 2.0× | | EP8 DSV3 | 1.10× | 2.2–2.4× | 1.6× | | EP4 SMALL | 3.1× | 2.1× | See limitations | At EP1 and equal FP8 precision, `sm90_push_fp8` measured **2.3–3.4×** faster than `cutlass_fused_moe` over `T64–T2048`. ### EP8 scaling Configuration: ```text DSV3 capacity = 2048 all three optimizations enabled ``` Results: ```text T64: 0.758 ms T2048: 3.238 ms ``` With capacity fixed at 2048, a 32× increase in live token count increased latency by 4.27×. Additional observations: * no scheduling cliff near `T128`; * all-remote routing adds approximately 5%; * running 64 live tokens with buffers sized for 2048 adds approximately 0.7%. ## Limitations * Supports: * SM90; * single-node, peer-accessible NVLink; * protocol limit `ep_size <= 32`, with hardware validation in this PR covering up to EP8; * `top_k` in `{1, 2, 4, 6, 8}`; * DeepSeek-style FP8 block scaling; * SwiGLU. Unsupported configurations raise an explicit error rather than silently falling back. * The unfused FC1 path supports `intermediate_size <= 16384`; larger configurations require `fuse_fc1_epilogue=True`. * At DSV3 decode shapes, the advantage over NCCL + `cutlass_fused_moe` is **1.06–1.10×**, because computation dominates. * In the artificial SMALL `T2048` case where all `K` routes target one expert, the backend is 25% slower than the autotuned CUTLASS stack. Realistic `hot1` routing remains faster at EP4 and EP8. * The fused FC1 epilogue does not yet have a small-M `swapAB` variant and can be disabled independently below its break-even point. * Round tags use `uint32`. Reuse after `2^32` forwards on one pipe is a documented limit. * Custom raw-stream execution through `bootstrap.stream` is rejected explicitly rather than silently ignored. ## How to run ### Single-GPU and host-only suites ```bash pytest \ tests/moe_ep/test_sm90_push_fp8_kernel.py \ tests/moe_ep/test_sm90_push_fp8_backend.py \ tests/gemm/test_sm90_moe_gemm.py \ tests/gemm/test_sm90_moe_gemm_contract.py ``` ### Multi-rank tests Requires at least two SM90 GPUs and uses `torch.distributed` only. ```bash bash tests/moe_ep/run_tests.sh sm90_push ``` ### Soak tests ```bash SM90_PUSH_SOAK_ROUNDS=200 \ bash tests/moe_ep/run_tests.sh sm90_push ``` ### Benchmark ```bash torchrun \ --standalone \ --nproc-per-node=8 \ benchmarks/bench_sm90_push_megamoe.py \ --config DSV3 \ --dedup \ --grouped-combine \ --fuse-fc1 \ --assert-cos-min 0.997 ``` ## 🔍 Related issues This PR contributes the Hopper FP8 block-scale milestone under flashinfer-ai#3692 and the SM90 sub-issue flashinfer-ai#3780, and follows the whole-layer integration direction described in flashinfer-ai#3704. ### ✅ 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`. - [ ] I have run the hooks manually with `pre-commit run --all-files` and fixed any reported issues. ## 🧪 Tests * [x] Tests added and updated. * [x] Single-GPU suite passes. * [x] EP2 distributed suite passes. * [x] EP4 distributed suite passes. * [x] CUDA Graph tests pass. * [x] Soak tests pass on the listed hardware. ## Reviewer notes Native NVFP4 and MXFP8 execution are out of scope because SM90 does not provide native block-scaled tensor-core instructions for those formats. A follow-up PR stacked on this one adds NVFP4 checkpoint support for SM90 on top of this backend, through online W4A8 decode kernels and a one-time requantization path that rides this FP8 backend unchanged. The highest-value review areas are: Entry points for the highest-risk areas: * **Symmetric-window ordering and the acknowledgement protocol:** `kernel_src/sm90_push_megamoe/src/a2a/sm90_push_a2a_ops.cu` * **Grouped-combine numerical behavior, where the quantization point moves:** The combine kernels in `kernel_src/sm90_push_megamoe/src/a2a/sm90_push_a2a_ops.cu`, gated by the reference comparison in `tests/moe_ep/test_sm90_push_fp8_kernel.py` * **DeepGEMM fused-epilogue and workspace contracts:** `src/fp8_gemm/fp8_moe_fc1_fused.cuh``shim/gemm.py` * **CUDA 12.8 gating and the AOT skip:** `shim/gemm.py``flashinfer/aot.py` * **Collective initialization failure handling:** `shim/protocol.py` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added SM90 FP8 MegaMoE support with pull- and push-style execution paths. * Added configurable FP8 formats, scaling modes, routing, reduction, CUDA graph, and distributed execution options. * Added weight preprocessing and public configuration interfaces. * Added benchmark tools for correctness, performance, token sweeps, and peer-to-peer bandwidth. * **Documentation** * Expanded architecture guidance, setup instructions, tuning information, and reproducibility runbooks. * **Tests** * Added broad correctness, validation, packaging, lifecycle, distributed, and CUDA graph coverage. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…ush-style FP8 backend; sync CuTe-DSL 4.7 quant-staging fix (flashinfer-ai#4449) ## Summary Three things: a layout/naming refactor of `flashinfer.moe_ep`'s mega-kernel layer, the incorporation of the SM90 push-style FP8 backend (flashinfer-ai#4069, since merged upstream) as the first new backend added in the restructured shape, and one vendored-kernel sync that fixes the fused activation-quant staging crash on CuTe-DSL 4.7 — un-blocking 4.7.x and lifting the temporary `==4.6.1` pin. The branch is merged up to upstream/main (2febce5, past the v0.6.17 line and the flashinfer-ai#4069 squash). The refactor organizes the layer around two orthogonal views: 1. **Taxonomy (user view)** — backends move to `backends/mega/kernel/sm<arch>/<act_dtype>_<weight_dtype>_<out_dtype>_<kernel_style>/`, and registry `kernel_name` strings plus config classes carry the same fully-qualified names. One glance at a name now tells you the architecture, the activation/weight/output dtypes, and the kernel style: | old kernel_name | new kernel_name | new config class | |---|---|---| | `deep_gemm_mega` | `sm100_fp8_fp4_bf16_deepgemm` | `Sm100_Fp8_Fp4_Bf16_Deepgemm_MegaMoeConfig` | | `nvfp4_cutedsl` | `sm100_nvfp4_nvfp4_bf16_cutedsl` | `Sm100_Nvfp4_Nvfp4_Bf16_Cutedsl_MegaMoeConfig` | | `mxfp8_cutedsl` | `sm100_mxfp8_mxfp8_bf16_cutedsl` | `Sm100_Mxfp8_Mxfp8_Bf16_Cutedsl_MegaMoeConfig` | | `sm90_pull_fp8` | `sm90_fp8_fp8_bf16_pull_cutedsl` | `Sm90_Fp8_Fp8_Bf16_PullCutedsl_MegaMoeConfig` | | `sm90_push_fp8` (new, from flashinfer-ai#4069) | `sm90_fp8_fp8_bf16_push_cuda` | `Sm90_Fp8_Fp8_Bf16_PushCuda_MegaMoeConfig` | Naming conventions: deep_gemm dtypes are plain `fp8`/`fp4`, matching upstream `deep_gemm.fp8_fp4_mega_moe`; the mx/nv prefixes are reserved for the cutedsl kernels' block-scaled formats. Output dtype is always bf16 — nvfp4's `combine_dtype` is comm-wire compression, not an output format. 2. **Provenance (kernel-dev view)** — vendored kernel sources in `kernel_src/` are keyed by upstream repo snapshot, not by architecture: `kernel_src/sm100/cutedsl_megamoe` moves to `kernel_src/cutedsl_megamoe` (the mother repo ships kernels for multiple arches, so an smXX level misrepresents it). Each drop mirrors the vendor repo layout — `src/` byte-for-byte upstream, all adaptation in `shim/` — and gains a `VENDOR.md` recording upstream repo/commit/sync state and pending local diffs. A new `kernel_src/README.md` states the contract explicitly: **no edits to `src/` of any kind — including docstrings, comments, and lint fixes**; tool warnings against vendored files (docstring-coverage gates, review bots) are handled by excluding the path, never by editing the file. The sm90 fork trees (`kernel_src/sm90/pull_style_cutedsl_megakernel` from flashinfer-ai#4113, `kernel_src/sm90/push_style_megamoe` from flashinfer-ai#4069) intentionally stay separate snapshots — one kernel_src dir = one upstream commit — and fold into the mother drop if/when upstream merges them. **Why:** verbatim snapshots must stay diffable against one upstream commit, and splitting vendored trees per-dtype or per-arch breaks re-sync; meanwhile users navigate by architecture and dtype, not by which vendor repo a kernel came from. Putting each concern where its audience looks resolves the tension. The layout rule is documented in `docs/design_docs/moe_ep_architecture.md`, and it is what makes new backend families routine — demonstrated in this very PR by the SM90 push-style incorporation below, and next by the follow-up backend-family PRs (SM100 BF16 flashinfer-ai#4386, SM120 MXFP8). ## Directories affected All changes live under `flashinfer/moe_ep/` plus its tests and docs: - `backends/mega/kernel/sm100/{fp8_fp4_bf16_deepgemm,nvfp4_nvfp4_bf16_cutedsl,mxfp8_mxfp8_bf16_cutedsl}/` and `backends/mega/kernel/sm90/{fp8_fp8_bf16_pull_cutedsl,fp8_fp8_bf16_push_cuda}/` — taxonomy backend wrappers (moved/renamed; push_cuda is new). - `kernel_src/cutedsl_megamoe/` (moved from `kernel_src/sm100/cutedsl_megamoe/`), `kernel_src/sm90/pull_style_cutedsl_megakernel/`, `kernel_src/sm90/push_style_megamoe/` (new) — provenance-keyed vendored drops, each with `VENDOR.md`; new `kernel_src/README.md` states the no-edits contract. - `backends/mega/kernel/tuning.py` + per-backend `tuner.py` files — tuning machinery moved out of `tune.py` (now a CLI shim). - `core/kernel/registry.py`, `moe_ep/__init__.py` — deprecated-alias resolution and re-exports. - `tests/moe_ep/`, `docs/design_docs/moe_ep_{architecture,runbook}.md`, `pyproject.toml`/`.pre-commit-config.yaml` excludes, `run_tests.sh` (new 2-GPU `sm90_push` target). ## Test results - **Full `run_tests.sh` matrix — all 12 targets green** on 4xH100 (job 2389821, 2026-08-13), including the new `sm90_push` Hopper target and the fault-tolerance suites after the deadlock fixes. - **B200** (jobs 2388315/2388326): registry/alias smoke, deprecated aliases, unit x3 green — 396 passed / 72 skipped (push cpu/packaging/contract tests run; Hopper-marked kernel tests skip). - **Unit target re-validated green** after the second upstream merge (job 2389880) and again after the round-2 CodeRabbit fixes (job 2389916), same 396/72 counts, B200. - **8x B200** (jobs 2384640/2384641/2384650): quant-staging sync matrix fully green on both dsl 4.6.1 and 4.7.0 (details in the vendored-sync section below). - **GB200 + B200**: mxfp8/nvfp4 multirank oracle suites with the per-cell tolerance band. - Microbenchmark re-run: no regressions vs pre-restructure reference numbers (deep_gemm parity; cutedsl kernels at or above their previous points). - `pre-commit run -a` fully green at the branch head (e9f791a). ## SM90 push-style FP8 backend (incorporates flashinfer-ai#4069) Ports flashinfer-ai#4069 (head 301f8ce; since merged to main as f9b13ef — re-diffed, byte-identical, no post-review deltas) onto the taxonomy/provenance layout, serving as the first proof of the "one taxonomy backend dir + one provenance-keyed kernel drop" recipe: - **`kernel_src/sm90/push_style_megamoe/`** — verbatim byte-for-byte drop from the PR head (`src/{a2a,fp8_gemm}` CUDA sources, `shim/`, ACKNOWLEDGEMENT.md) plus a `VENDOR.md` provenance record. - **`backends/mega/kernel/sm90/fp8_fp8_bf16_push_cuda/`** — the five wrapper files relocated from upstream's flat `kernel/sm90_push_fp8/`, config renamed to `Sm90_Fp8_Fp8_Bf16_PushCuda_MegaMoeConfig`, registered with `deprecated_aliases=("sm90_push_fp8",)`. - **Core deltas carried from the PR:** `mega_layer.py` allocates the output before `stage_inputs`; pyproject package-data ships the drop's `.cu`/`.cuh` for non-editable installs; the `isolated_deep_gemm_cache` conftest fixture; the mega-layer allocation-order regression test. - **Tests:** the nine sm90_push_fp8 test files (names kept to minimize re-sync friction) rewritten to the taxonomy. Deviation from upstream: `run_tests.sh` exposes `sm90_push` as its own 2-GPU Hopper target instead of folding it into multirank — on non-Hopper nodes the arch-marked files collect 0 tests and torchrun turns pytest exit 5 into a failure. ## CuTe-DSL 4.7 quant-staging fix (vendored sync) The `CUDA_ERROR_MISALIGNED_ADDRESS` crash on cutlass-dsl 4.7.0 — which presented as a deep_gemm mega multirank failure — was root-caused to the **fused bf16→quantized activation staging** (`DataPreprocess` in the vendored cutedsl_megamoe tree), which every mega staging path shares, deep_gemm's included. The kernel team's fix is synced in as a single-file partial re-sync per the vendoring policy: - `kernel_src/cutedsl_megamoe/src/src/inputs_process.py` + `src/common/host_utils.py` taken **verbatim** from upstream `bangyus/cutedsl_megamoe @ 50117315d`, recorded in `VENDOR.md` under pending-diffs (resolves at the next full re-sync). The mxfp8 quant kernel is reworked so each lane owns one contiguous 16-byte fp8 store (adjacent lanes reduce the 32-element block amax via `shuffle_sync_bfly`, even lane writes the E8M0 scale), and `__init__` gains a hidden-size row-alignment guard. - Also fixes a stale pre-commit exclude left by the directory move (`kernel_src/sm100/cutedsl_megamoe` → `kernel_src/cutedsl_megamoe`) so hooks stop reformatting the verbatim `src/` tree. Validated on 8x B200 (jobs 2384640/2384641/2384650), full matrix green on **both** DSL versions: | section | dsl 4.6.1 | dsl 4.7.0 | |---|---|---| | drop's own harness (`python -m src.inputs_process`: bit-exact scales + SNR vs reference, nvfp4 offline/online + mxfp8) | 3/3 | 3/3 | | `test_fused_quant_stage.py` | 11/11 | 11/11 | | mega multirank x4 ranks (deep_gemm + nvfp4 + mxfp8) | 20/rank | 20/rank | | single-rank kernel-vs-reference oracles | 6/6 | 6/6 | The deep_gemm multirank suite previously crashed deterministically on 4.7.0; it now passes there. On the strength of this, the runbook's temporary `==4.6.1` pin is lifted (see the DSL guidance bullet below). ## Also in this PR - **Per-backend tuners.** `flashinfer/moe_ep/tune.py` becomes a pure CLI shim (surface unchanged: `python -m flashinfer.moe_ep.tune`); dtype-specific tuning moves into the backends (`sm100/{nvfp4,mxfp8}.../tuner.py`), shared sweep machinery (dist lifecycle, skewed restage, schedule grid, timed sweep tail) into `backends/mega/kernel/tuning.py`. - **CUTLASS DSL guidance updated (pin lifted).** The test-container recipe briefly carried a hard `nvidia-cutlass-dsl==4.6.1` pin because 4.7.0 crashed the mega multirank path; with the crash root-caused and fixed above, the runbook now allows `-U` installs again. 4.6.1 remains the perf-validated reference (pin it when producing numbers meant to compare against the TUNING.md tables); 4.7.0 is correctness-validated. The library's supported floor remains 4.5.2 (the MR!27 WAR already in main). - **Per-cell bf16 term-magnitude tolerance band** for the mxfp8 multirank oracle compares — a principled per-cell bound derived from the bf16 accumulation term magnitudes, replacing the global rtol that produced rare single-cell false failures. Validated on GB200 and B200. - **One-direction import layering rules** codified in the architecture doc, with all `cutedsl_megamoe` access routed through the drop's `__init__` rather than deep-path imports. ## Merge with upstream/main and follow-up fixes The branch is merged up to upstream/main in two steps. First to aaf97df (95 commits, incl. the v0.6.17 release line): conflict resolution keeps the restructure spellings everywhere; upstream's one real kernel advance in the moved tree — the 4fbac49 singleton-expert TMA-modes fix (flashinfer-ai#4296) — is ported onto the renamed paths and recorded in `VENDOR.md`. Notable upstream picks now in-tree: `BootstrapConfig.device` (flashinfer-ai#4348) and the E_local=1 nvfp4 oracle regression test. Second merge to 2febce5 (13 commits), resolving the conflicts created when flashinfer-ai#4069 itself squash-merged upstream (f9b13ef) with the same moe_ep files in the pre-restructure flat layout. Every conflict resolves to the taxonomy spellings (upstream's side is the flat spelling of content this branch already carries); upstream's flat `backends/mega/kernel/sm90_push_fp8/` wrapper and its re-folding of `sm90_push` into the multirank target are dropped in favor of this branch's layout. The vendored push drop was re-diffed against the merged SHA: byte-for-byte identical, no post-review deltas (recorded in `VENDOR.md`). Post-merge hardening found and fixed by full-suite runs: - **Merge fallout:** auto-merged regions had re-introduced pre-restructure `kernel_src.sm100.cutedsl_megamoe` spellings in 12 files, silently skipping entire GPU test files via `importorskip`; restored, and upstream's re-added flat `sm90_pull_fp8/` wrapper removed. - **FT test deadlocks (4xH100):** the fault-tolerance multirank test's evicted victim ran a collective `destroy()` against the survivors' barrier sequence, deadlocking until the NCCL watchdog — the victim tail now mirrors the survivors' barrier→destroy→barrier shape. The FT smoke's survivors now keep forwarding past the kill window so they actually observe the fault, and `run_tests.sh` judges the smoke by counting `SMOKE_RESULT` markers (torchrun interleaves lines). - **Unit-suite crasher isolation:** the long-known in-suite-only interpreter abort (heap corruption accumulating over the ~200-test single-process run, firing during a plain module import or in CPython teardown) is worked around by running the trigger test in its own pytest process and exiting the unit invocations via `os._exit(pytest_rc)`; rationale in the runbook, root cause tracked (needs ASAN). All tests pass — this is process-teardown hygiene, not a kernel bug. **CodeRabbit review responses.** Two rounds of actionable findings are fixed in-branch (640b75f, 57926a9) — highlights from round 2: the push packaging test's import-boundary gate was building the pre-taxonomy flat backend path and passing vacuously (fixed, now validates all 5 wrapper files); the test baseline's weight cache gains weakref eviction; `cutedsl_megamoe/shim/__main__.py` added so the documented `python -m ...shim` commands resolve; the cutedsl_megamoe `VENDOR.md` provenance TODOs are filled. Findings inside verbatim-vendored `kernel_src/**/src/` trees are deliberately not patched locally — they route upstream per the vendoring policy in `kernel_src/README.md`. **Lint.** `pre-commit run -a` is fully green (clang-format, mypy, ruff check/format, whitespace hooks). The final e9f791a is a pure ruff-format pass over 13 moe_ep files — line wraps where the longer taxonomy class names pushed calls past the limit. The vendored `src/` trees are untouched by hooks (the exclude set holds). ## Backward compatibility External callers keep working unchanged — both the old config-class names and the old kernel_name strings remain as deprecated aliases: - **Config classes**: `DeepGemmMegaMoeConfig`, `Nvfp4CutedslMegaMoeConfig`, `Mxfp8CutedslMegaMoeConfig`, `Sm90PullFp8MegaMoeConfig`, and `Sm90PushFp8MegaMoeConfig` are plain aliases of the new `Sm<arch>_..._MegaMoeConfig` classes, defined (with a removal note) in `flashinfer/moe_ep/__init__.py` right below the taxonomy imports, and still exported via `__all__`. - **Registry kernel_name strings**: `deep_gemm_mega`, `nvfp4_cutedsl`, `mxfp8_cutedsl`, `sm90_pull_fp8`, and `sm90_push_fp8` resolve to the taxonomy backends through the `deprecated_aliases=` parameter of each backend's `@register_mega_kernel(...)` decoration; the resolution machinery lives in `flashinfer/moe_ep/core/kernel/registry.py`. Using one emits a `DeprecationWarning`, and aliases are excluded from the available-kernels listing. - Both alias families WILL BE REMOVED in a future release (noted at both locations above). ## Testing - Directory moves and renames are behavior-preserving by construction; registry tests exercise both the taxonomy names and the deprecated aliases (alias use warns; the kernel listing shows taxonomy names only). - Full `run_tests.sh` matrix (all 12 targets) green on 4xH100 (job 2389821); B200 unit/registry/alias validation (jobs 2388315/2388326) — see Test results above. - The quant-staging sync validated on both dsl 4.6.1 and 4.7.0 (matrix above); mxfp8/nvfp4 multirank oracle suites validated on GB200 and B200. - The standalone MoE-EP microbenchmark was re-run against this branch with no regressions vs the pre-restructure reference numbers (deep_gemm parity; cutedsl kernels at or above their previous points). ## Relation to other PRs Re-layering on top of flashinfer-ai#4113 (SM90 pull-style FP8 backend, merged) and incorporating flashinfer-ai#4069 (SM90 push-style FP8 backend, merged upstream 2026-08-12; the vendored drop was re-diffed against the merged SHA f9b13ef and is byte-identical). This is the base branch for the upcoming backend-family PRs — SM100 BF16 (flashinfer-ai#4386) and SM120 MXFP8 — each of which adds one taxonomy backend directory plus one provenance-keyed kernel drop in the shape this restructure establishes. Both follow-up branches are already rebased onto this branch's head (unit target green on each), so they apply as exactly their backend-specific commits once this merges. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Md Anik <mhoqueanik@cw-dfw-cs-001-login-01.cm.cluster> Co-authored-by: Md Saidul Hoque Anik <mhoqueanik@login-preos01.a51.clusters.nvidia.com>
📌 Description
This PR registers
sm90_push_fp8, a whole-layer expert-parallel MoE backend for SM90 GPUs connected through single-node NVLink, as a fourth mega backend behind the existingregister_mega_kernelplugin surface.The public entry point is
MoEEpLayerwithSm90PushFp8MegaMoeConfig. Existing backend behavior is unchanged.The steady-state forward path covers dispatch, FP8 block-scale expert FFN, and combine without host-side synchronization, and supports CUDA Graph capture after initialization. The diagram below shows the path with all three optional optimizations enabled:
The backend provides three independently configurable optimizations. Validation and performance results below use all three unless stated otherwise.
torch.equal. ForK=6,EP=4, and random routing, it reduces dispatch payload bytes by approximately 43%.torch.equal. The unfused path remains the default and maintained reference; the fused path is opt-in.Motivation
The existing Hopper EP stack composes NCCL all-to-all with a local fused-MoE operator, paying for additional launches, intermediate buffers, and quantization boundaries.
sm90_push_fp8writes quantized payloads directly into peer-mapped symmetric memory and feeds received rows directly into the expert GEMM pipeline.Contracts introduced by this PR
capacity_factorbounds the GEMM, TMA, and scale buffers, rather than only the protocol window. The workspace query takes explicitexpected_mandmax_rows, so block-M tactics and cubin keys remain independent of the memory bound.Building the grouped GEMM requires CUDA Toolkit 12.8 or newer. The generator explicitly rejects older toolkits, and AOT skips this module below CUDA 12.8, allowing JIT-cache wheels to continue building on CUDA 12.6. Loading and running the generated module also requires a CUDA runtime version 12.8 or newer.
Runner initialization elects one NVCC-capable rank per cache and tactic group to compile cold DeepGEMM cubins. Other ranks load the resulting cubins from disk.
TRTLLM_DG_ENABLED=0selects the fixed-tactic CUTLASS fallback on the unfused path.Wait kernels use a
%globaltimerdeadline and publish a shared abort marker before trapping. Peers polling the same protocol window observe the marker and fail. Because a trap leaves a sticky CUDA error, the process launcher must terminate the full rank group from the CPU.This backend is a stateful variant of the mega-kernel contract:
stage_inputspre-binds the caller's output tensor.The architecture and runbook documentation are updated accordingly, including narrowing the two-entry wording to buffer-oriented kernels. The
MegaKernelBackendlifecycle itself is unchanged.Correctness
Full kernel acceptance ran on an 8× H800 NVLink node:
The final PR tree was then revalidated across three Hopper variants:
torchrunEP2torchrunEP4compute-sanitizermemcheck / racecheck / initcheck, excluding trap and soaksAdditional hardware-verified properties on the final PR tree:
torch.equal.Performance
Performance numbers were collected during kernel acceptance on the 8× H800 node. The final PR tree was functionally revalidated as listed above.
Setup
Tdenotes live tokens per rankThe tables report
baseline latency / sm90_push_fp8 latency; values greater than1×favor this backend.H,E, andKdenote hidden size, expert count, and top-k routes.randomdistributes routes across experts, whilehot1concentrates routes on one expert.Versus NCCL all-to-all with the same FP8 block-scale compute
Against a wire-only NCCL transport reference that excludes compaction and combine reduction, the push transport measured 1.9–3.2× faster.
This number should be interpreted only as a comparison against that specific measured reference. It is not a claim against third-party EP libraries.
Versus NCCL +
cutlass_fused_moeFP8At EP1 and equal FP8 precision,
sm90_push_fp8measured 2.3–3.4× faster thancutlass_fused_moeoverT64–T2048.EP8 scaling
Configuration:
Results:
With capacity fixed at 2048, a 32× increase in live token count increased latency by 4.27×.
Additional observations:
T128;Comparison with Pull Style
[From comment]
Ran the six suggested shapes on 8× H100 80GB SXM. These are back-to-back steady-state measurements with 20 warmups and 100 samples per path. The table reports p50 latency in milliseconds; the last column is Pull / Push, so values above 1 mean Push is faster.
Push is about 4–5% slower at the smallest token counts, but becomes consistently faster from T512 onward, with gains of roughly 1.25–1.80×.
Correctness also passed across all measured cases (minimum cosine similarity 0.99816, maximum NRMSE 0.06063).
GPT-OSS 120B is not supported yet because its hidden dimensions are not 128-aligned, and Qwen3.5 397B uses top-k 10 while the current limit is 8.
Limitations
Supports:
ep_size <= 32, with hardware validation in this PR covering up to EP8;top_kin{1, 2, 4, 6, 8};Unsupported configurations raise an explicit error rather than silently falling back.
The unfused FC1 path supports
intermediate_size <= 16384; larger configurations requirefuse_fc1_epilogue=True.At DSV3 decode shapes, the advantage over NCCL +
cutlass_fused_moeis 1.06–1.10×, because computation dominates.In the artificial SMALL
T2048case where allKroutes target one expert, the backend is 25% slower than the autotuned CUTLASS stack. Realistichot1routing remains faster at EP4 and EP8.The fused FC1 epilogue does not yet have a small-M
swapABvariant and can be disabled independently below its break-even point.Round tags use
uint32. Reuse after2^32forwards on one pipe is a documented limit.Custom raw-stream execution through
bootstrap.streamis rejected explicitly rather than silently ignored.How to run
Single-GPU and host-only suites
Multi-rank tests
Requires at least two SM90 GPUs and uses
torch.distributedonly.Soak tests
Benchmark
🔍 Related issues
This PR contributes the Hopper FP8 block-scale milestone under #3692 and the SM90 sub-issue #3780, and follows the whole-layer integration direction described in #3704.
✅ Pre-commit Checks
pre-commitby runningpip install pre-commit(or used your preferred method).pre-commit install.pre-commit run --all-filesand fixed any reported issues.🧪 Tests
Reviewer notes
Native NVFP4 and MXFP8 execution are out of scope because SM90 does not provide native block-scaled tensor-core instructions for those formats. A follow-up PR stacked on this one adds NVFP4 checkpoint support for SM90 on top of this backend, through online W4A8 decode kernels and a one-time requantization path that rides this FP8 backend unchanged.
The highest-value review areas are:
Entry points for the highest-risk areas:
Symmetric-window ordering and the acknowledgement protocol:
kernel_src/sm90_push_megamoe/src/a2a/sm90_push_a2a_ops.cuGrouped-combine numerical behavior, where the quantization point moves:
The combine kernels in
kernel_src/sm90_push_megamoe/src/a2a/sm90_push_a2a_ops.cu, gated by the reference comparison intests/moe_ep/test_sm90_push_fp8_kernel.pyDeepGEMM fused-epilogue and workspace contracts:
src/fp8_gemm/fp8_moe_fc1_fused.cuh``shim/gemm.pyCUDA 12.8 gating and the AOT skip:
shim/gemm.py``flashinfer/aot.pyCollective initialization failure handling:
shim/protocol.pySummary by CodeRabbit
New Features
Documentation
Tests