Skip to content

feat(moe): MXFP8 x MXFP4 CuTe-DSL fused MoE for SM100, plus large-batch routing locality - #4440

Merged
jiahanc merged 7 commits into
flashinfer-ai:mainfrom
vitamin-chaos:feat/cutedsl-mxfp8-mxfp4-fused-moe
Aug 13, 2026
Merged

jiahanc merged 7 commits into
flashinfer-ai:mainfrom
vitamin-chaos:feat/cutedsl-mxfp8-mxfp4-fused-moe

Conversation

@vitamin-chaos

@vitamin-chaos vitamin-chaos commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

📌 Description

Two related changes to the Blackwell CuTe-DSL MoE path.

1. MXFP8 x MXFP4 fused MoE (SM100/SM103)e3bc88b3

A mixed-precision path: MXFP8 (E4M3 + linear block-32 E8M0) activations against
packed MXFP4 (E2M1 + MMA-layout block-32 E8M0) weights, with an MXFP8 FC1
intermediate and BF16 finalized output.

  • Both grouped-GEMM kernels take separate a_dtype/b_dtype, size SMEM from
    smem_alloc_*_dtype, and load the narrow operand via TMA UNPACK_U8.
  • The FC1 epilogue emits the same exact UE8M0 codes as mxfp8_quantize and
    scalar-stores them as bytes, avoiding the unsupported vector<2xE8M0>
    conversion/store lowering.
  • New public APIs cute_dsl_fused_moe_mxfp8_mxfp4 and
    CuteDslMxfp8Mxfp4MoEWrapper, with their own tactic space and autotune cache
    namespace so NVFP4 results and cache entries are unaffected.
  • GEMM2 can_implement no longer requires N to divide the MMA N tile: the
    finalize epilogue already clamps its bulk-reduce to valid_columns, which
    tests/moe/test_cute_dsl_moe_can_implement.py documents. A partial-N-tile
    case is added to the grouped-GEMM test to cover it.

2. Contiguous per-CTA route windows in moe_sort at large batches5e0f9e64

The cooperative routing kernel assigns each CTA a grid stride of expanded
indices, and an expert's rows land in CTA-arrival order, so one expert's rows
are drawn from the whole batch. A grouped-GEMM tile then gathers across the
entire activation tensor, and the footprint grows with the batch: measured on
B200 (hidden 6144, 256 experts, top_k 8, 32 local, tile 256) a GEMM1 tile
touches 94 distinct 2 MiB pages at 64K tokens, 190 at 128K and 228 at 256K,
which outgrows the uTLB.

With mUseContiguousRouteWindows each CTA owns one contiguous span instead, so
a tile gathers from a few narrow token windows and the footprint stays flat at
~28-38 pages. moe_sort enables it from 65536 tokens:

tokens GEMM1 full MoE pipeline 2 MiB pages / tile
65536 1.00-1.10x 0.99-1.03x 94 -> 38
131072 1.40-1.44x 1.17-1.18x 190 -> 32
262144 1.43-1.47x 1.24-1.28x 228 -> 29

The flag defaults to off, so every other caller of the routing kernels,
including the TRT-LLM MoE backend, keeps its current ordering bit for bit.

🔍 Related Issues

🚀 Pull Request Checklist

✅ Pre-commit Checks

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

🧪 Tests

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

New tests: tests/moe/test_cute_dsl_mxfp8_mxfp4_grouped_gemm.py,
tests/moe/test_cute_dsl_mxfp8_mxfp4_fused_moe.py,
tests/moe/test_moe_sort_route_windows.py.

Run on B200 (SM100):

  • tests/moe/test_cute_dsl_fused_moe.py plus the three MXFP8 x MXFP4 /
    can_implement files — 564 passed
  • tests/moe/test_moe_sort_route_windows.py plus MXFP8 x MXFP4 end to end — 12 passed
  • tests/trace/test_fi_trace_template_consistency.py,
    tests/trace/test_template_init.py — 818 passed, 167 skipped

Reviewer Notes

  • The routing change touches RoutingKernel.cuh, shared with the TRT-LLM MoE
    backend. It is gated behind a DataBase flag that only moe_sort sets, so
    TRT-LLM paths are unchanged; that separation is the main thing worth checking.
  • Scope of the new ordering: all three CuTe-DSL MoE entry points share
    moe_sort (cute_dsl_fused_moe_nvfp4, cute_dsl_fused_moe_mxfp8_mxfp4 and
    the W4A16 path), so all three pick it up above the threshold. NVFP4 uses the same gather kernel so the
    mechanism applies, but its packed-FP4 activations are half the bytes per
    token, so its benefit is not that much.
  • Only the cooperative routing path honours the flag, so it applies while that
    path is selected (roughly 65536 to ~570K tokens at 256 experts / top_k 8 on a
    148-SM B200). Above that the multi-kernel path runs and the flag is ignored.
  • This is a locality change, not an ordering guarantee: rows within an expert
    still land in atomic-arrival order and the permutation stays
    non-deterministic run to run, exactly as before. Only each CTA's source range
    becomes contiguous.
  • Two subtleties in the coop kernel are load-bearing: the per-CTA span is sized
    to keep every CTA busy rather than fixed at the per-thread maximum, and each
    CTA must stop at its own span end. Without the latter the trailing iterations
    run into the next CTA's span — still below expandedIdxSize, so the
    batch-size check alone does not stop them — and those routes get counted and
    permuted twice. test_moe_sort_route_windows.py asserts per-expert row counts
    against the routing histogram, which is what catches this class of bug; a
    bijection check alone does not, since a duplicated permutation is still
    self-consistent.

Summary by CodeRabbit

  • New Features

    • Added MXFP8 activation × MXFP4 weight support for fused Mixture-of-Experts workloads on compatible GPUs.
    • Added public APIs, reusable wrappers, autotuning, tracing, and documentation for mixed-precision fused MoE operations.
    • Improved large-batch routing with contiguous route windows for more localized processing.
  • Bug Fixes

    • Improved validation, scaling, alignment, memory handling, and output quantization.
    • Preserved compatibility with existing NVFP4 APIs.
  • Tests

    • Added functional, integration, routing, and numerical correctness coverage.

Adds a mixed-precision path to the Blackwell CuTe-DSL MoE kernels: MXFP8
(E4M3 + linear block-32 E8M0) activations against packed MXFP4 (E2M1 +
MMA-layout block-32 E8M0) weights, an MXFP8 FC1 intermediate, and a BF16
finalized output.

- Both grouped-GEMM kernels now take separate a_dtype/b_dtype, size SMEM
  from smem_alloc_*_dtype, and load the narrow operand via TMA UNPACK_U8.
- The FC1 epilogue emits the same exact UE8M0 codes as mxfp8_quantize and
  scalar-stores them as bytes, avoiding the unsupported vector<2xE8M0>
  conversion/store lowering.
- New public APIs cute_dsl_fused_moe_mxfp8_mxfp4 and
  CuteDslMxfp8Mxfp4MoEWrapper, with their own tactic space and autotune
  cache namespace so NVFP4 results and cache entries are unaffected.
- GEMM2 can_implement no longer requires N to divide the MMA N tile: the
  finalize epilogue already clamps its bulk-reduce to valid_columns, and
  a partial-tile case is covered by the new grouped-GEMM test.

AI-assisted with Claude Code.

Signed-off-by: Tiekai Bi <tiekaib@nvidia.com>
…tches

The cooperative routing kernel assigns each CTA a grid stride of expanded
indices, and an expert's rows land in CTA-arrival order, so the rows of one
expert are drawn from the whole batch. A grouped-GEMM tile then gathers across
the entire activation tensor, and the footprint grows with the batch: measured
on B200 (hidden 6144, 256 experts, top_k 8, 32 local, tile 256) a GEMM1 tile
touches 94 distinct 2 MiB pages at 64K tokens, 190 at 128K and 228 at 256K,
which outgrows the uTLB.

Add an opt-in ordering where each CTA owns one contiguous span instead, so a
tile gathers from a few narrow token windows and the footprint stays flat at
~28-38 pages. moe_sort enables it from 65536 tokens, where the gather is large
enough for the locality to pay for itself:

  M        GEMM1        full MoE pipeline
  65536    1.00-1.10x   0.99-1.03x
  131072   1.40-1.44x   1.17-1.18x
  262144   1.43-1.47x   1.24-1.28x

The span is sized to keep every CTA busy rather than fixed at the per-thread
maximum, and each CTA stops at its own span end -- the batch-size check alone
would let the trailing iterations run into the next CTA's span and permute
those routes twice.

The flag defaults to off, so every other caller of the routing kernels,
including the TRT-LLM MoE backend, keeps its current ordering bit for bit.

AI-assisted with Claude Code.

Signed-off-by: Tiekai Bi <tiekaib@nvidia.com>
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds CuTeDSL MXFP8-activation/MXFP4-weight fused MoE support for Blackwell GPUs. It adds mixed-width grouped GEMM kernels, public APIs, autotuning, traces, tests, and large-batch contiguous routing windows.

Changes

MXFP8/MXFP4 fused MoE

Layer / File(s) Summary
Mixed-width Blackwell GEMM kernels
flashinfer/fused_moe/cute_dsl/blackwell/*
Grouped GEMM kernels now support independent A/B dtypes, FP4 TMA unpacking, E8M0 scales, mixed-width alignment checks, and MXFP8 output quantization.
Grouped GEMM wrappers and compatibility APIs
flashinfer/fused_moe/cute_dsl/blockscaled_*
Python wrappers now validate and dispatch MXFP8/MXFP4 operations while preserving NVFP4 compatibility.
Fused MoE execution and autotuning
flashinfer/fused_moe/cute_dsl/fused_moe_mxfp8_mxfp4.py, flashinfer/fused_moe/cute_dsl/mixed_tuner.py
Added functional and reusable-wrapper APIs with routing, workspace management, tactic selection, autotuning, BF16 output, and stream safeguards.
Public exports, traces, and documentation
flashinfer/__init__.py, flashinfer/fused_moe/__init__.py, flashinfer/fused_moe/cute_dsl/__init__.py, flashinfer/trace/templates/moe.py, docs/api/fused_moe.rst
The new API, wrapper, trace templates, and documentation are exposed conditionally when CuTeDSL is available.
Mixed MoE validation
tests/moe/test_cute_dsl_mxfp8_mxfp4_*, tests/moe/test_cute_dsl_moe_can_implement.py, flashinfer/fused_moe/cute_dsl/tuner.py
Tests cover contracts, kernel execution, numerical results, wrapper reuse, tactics, and separate A/B dtype validation.

Contiguous route windows

Layer / File(s) Summary
Large-batch route-window routing
csrc/moe_utils_binding.cu, include/flashinfer/trtllm/fused_moe/RoutingKernel.*
moe_sort enables contiguous CTA route windows at 65,536 tokens. Routing kernels use bounded contiguous spans with grid-stride fallback.
Route-window validation
tests/moe/test_moe_sort_route_windows.py
SM100 tests validate permutation integrity, route accounting, expert ownership, threshold behavior, and gather locality.

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

Mergeability Score: 🔵 Low · up to 2cbad

The new MXFP8×MXFP4 wrapper may use the ambient CUDA device instead of its configured device when launching work or recording events, which can fail or run on the wrong GPU in multi-device use. This is a localized, mergeable risk requiring explicit owner follow-up.

Possibly related PRs

Suggested labels: op: gemm

Suggested reviewers: aleozlx, yzh119, jiahanc, iwakurarein, samuellees

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant CuteDslMxfp8Mxfp4MoEWrapper
  participant RoutingKernel
  participant GroupedGEMM
  Caller->>CuteDslMxfp8Mxfp4MoEWrapper: submit MXFP8 activations and MXFP4 weights
  CuteDslMxfp8Mxfp4MoEWrapper->>RoutingKernel: sort routed tokens
  RoutingKernel->>GroupedGEMM: provide routed tensors and scales
  GroupedGEMM->>CuteDslMxfp8Mxfp4MoEWrapper: return BF16 output
  CuteDslMxfp8Mxfp4MoEWrapper->>Caller: return MoE result
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes both primary changes: MXFP8 × MXFP4 CuTe-DSL fused MoE support and large-batch routing locality.
Description check ✅ Passed The description covers the changes, testing, checklist, performance data, scope, and reviewer notes; the empty related-issues section is non-critical.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (7)
tests/moe/test_cute_dsl_mxfp8_mxfp4_fused_moe.py (2)

81-110: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Share the quantization helpers with the grouped-GEMM test.

_interleave_linear_and_gate and _quantize_mxfp4_grouped are also defined in tests/moe/test_cute_dsl_mxfp8_mxfp4_grouped_gemm.py at Lines 44-54 and 115-150. The two copies differ: the grouped-GEMM version asserts rows % (2 * group_size) == 0 and also returns a dequantized reference. Move one implementation into tests/moe/utils.py so the interleaving contract stays defined in one place.

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

In `@tests/moe/test_cute_dsl_mxfp8_mxfp4_fused_moe.py` around lines 81 - 110, Move
_interleave_linear_and_gate and _quantize_mxfp4_grouped into tests/moe/utils.py,
using the grouped-GEMM implementations as the canonical versions, including the
rows divisibility assertion and dequantized reference return. Remove the
duplicate local definitions from both test modules and import the shared helpers
instead.

37-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the exact NVFP4 tactic count.

Line 39 asserts len(ALL_MOE_TACTICS) == 16. That couples this mixed-path test to the NVFP4 tactic space. An unrelated change to the NVFP4 tactics breaks this test without indicating a mixed-path regression. Lines 40 and 47 already prove namespace separation.

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

In `@tests/moe/test_cute_dsl_mxfp8_mxfp4_fused_moe.py` around lines 37 - 47,
Remove the exact count assertion for ALL_MOE_TACTICS from the test, while
preserving the mixed-path tactic count, uniqueness, runner separation, and
tile-size assertions.
flashinfer/fused_moe/cute_dsl/fused_moe_mxfp8_mxfp4.py (2)

785-786: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify that this method returns the unfiltered tactic space.

CuteDslFusedMoEMxfp8Mxfp4Runner.get_valid_tactics filters tactics against can_implement for both kernels. This wrapper method returns every entry in ALL_MXFP8_MXFP4_MOE_TACTICS, so some returned tactics fail at launch for the wrapper's configured hidden_size and intermediate_size. Add a docstring that states the method lists the tactic space rather than the shape-valid subset, or rename it to get_all_tactics.

🤖 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/fused_moe/cute_dsl/fused_moe_mxfp8_mxfp4.py` around lines 785 -
786, Clarify the contract of CuteDslFusedMoEMxfp8Mxfp4Runner.get_valid_tactics
by adding a docstring stating that it returns the complete
ALL_MXFP8_MXFP4_MOE_TACTICS space without filtering for can_implement or the
configured shapes; keep the existing return behavior unchanged.

591-643: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider bucketing capacities when max_num_tokens is unset.

Without max_num_tokens, _capacity_for returns the exact num_tokens. Each distinct token count then allocates a new output tensor, a new _ScratchBuffers pair, and two sort buffers. Nothing is ever evicted. A serving workload with variable batch sizes grows VRAM until every observed token count is cached.

The class docstring documents this, and max_num_tokens avoids it. If you want a safer default, round num_tokens up to a power-of-two capacity so the cache stays bounded.

🤖 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/fused_moe/cute_dsl/fused_moe_mxfp8_mxfp4.py` around lines 591 -
643, Update _capacity_for to bucket token counts when max_num_tokens is unset,
rounding num_tokens up to a power-of-two capacity before _get_output calls
_allocate_capacity. Preserve the existing max_num_tokens validation and return
behavior, and ensure the bucket is at least num_tokens so returned output
slicing remains correct.
flashinfer/trace/templates/moe.py (1)

3692-3698: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Correct the comment about top_k.

The comment states that no tensor on run() carries a num_experts or top_k dim. token_selected_experts and token_final_scales stay in _cute_dsl_mxfp8_mxfp4_wrapper_inputs, and both declare dim_names of ["num_tokens", "top_k"]. The claim holds for num_experts only.

📝 Proposed comment fix
 _cute_dsl_mxfp8_mxfp4_wrapper_axes = dict(cute_dsl_fused_moe_mxfp8_mxfp4_trace.axes)
-# No tensor on run() carries a num_experts or top_k dim once the scalars move
-# to __init__, so both axes have to be free variables here.
+# num_experts has no tensor source once its scalar moves to __init__, so it
+# has to be a free variable. top_k is still carried by token_selected_experts
+# and token_final_scales; it is declared Var for symmetry with num_experts.
 _cute_dsl_mxfp8_mxfp4_wrapper_axes["num_experts"] = Var(
🤖 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/trace/templates/moe.py` around lines 3692 - 3698, Update the
comment above _cute_dsl_mxfp8_mxfp4_wrapper_axes to state that no run() tensor
carries a num_experts dimension, while token_selected_experts and
token_final_scales still carry top_k; preserve the explanation that num_experts
must remain a free variable and remove the incorrect claim about top_k.
flashinfer/fused_moe/cute_dsl/blackwell/blockscaled_contiguous_grouped_gemm_finalize_fusion.py (1)

2758-2773: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

These two unpack checks are unreachable for this kernel.

can_implement rejects any configuration where a_major != "k" or b_major != "k" at Line 2864. The guards here require a_major == "m" or b_major == "n", so they never evaluate to False on a path that otherwise succeeds. Keep them only if you intend them as forward-compatible guards, and add a short comment saying so.

🤖 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/fused_moe/cute_dsl/blackwell/blockscaled_contiguous_grouped_gemm_finalize_fusion.py`
around lines 2758 - 2773, Remove the unreachable unpack checks in can_implement
around use_2cta_instrs, or explicitly retain them only as forward-compatible
guards with a short explanatory comment. Preserve the existing validation that
rejects configurations where a_major is not "k" or b_major is not "k".
flashinfer/fused_moe/cute_dsl/blackwell/blockscaled_contiguous_gather_grouped_gemm_act_fusion.py (1)

3863-3892: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the comment with the symmetric check.

The comment states that B is the only unpacked operand in this gather kernel, but the code applies check_contiguous_128_elements to A and B. The code is correct and defensive. Reword the comment so it does not contradict the applied check.

📝 Proposed comment change
-        # UNPACK_U8 requires the contiguous dimension of a sub-byte operand to
-        # be a multiple of 128 elements.  In this gather kernel B is the only
-        # unpacked operand, but keep the check symmetric for clarity.
+        # UNPACK_U8 requires the contiguous dimension of a sub-byte operand to
+        # be a multiple of 128 elements.  Only B is unpacked today; the check
+        # is applied to both operands so a future A-side FP4 path stays valid.
🤖 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/fused_moe/cute_dsl/blackwell/blockscaled_contiguous_gather_grouped_gemm_act_fusion.py`
around lines 3863 - 3892, Update the comment above the symmetric check in
can_implement to avoid claiming that B is the only unpacked operand; describe
that the 128-element contiguous-dimension validation is applied to both A and B
defensively, while preserving the existing check_contiguous_128_elements logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@flashinfer/fused_moe/cute_dsl/fused_moe_mxfp8_mxfp4.py`:
- Around line 66-81: Make the resources returned by
_get_mixed_functional_resources thread-local per device so concurrent calls to
cute_dsl_fused_moe_mxfp8_mxfp4 never share main_event, memset_event, or
aux_stream. Preserve the existing resource initialization and device
association, ensuring each host thread’s _moe_core_impl_mxfp8_mxfp4 record/wait
sequence uses its own objects.

In `@flashinfer/fused_moe/cute_dsl/mixed_tuner.py`:
- Around line 369-379: The fallback at
flashinfer/fused_moe/cute_dsl/mixed_tuner.py:369-379 must raise a ValueError
naming the unsupported tokens, hidden, intermediate, and expert shape instead of
returning DEFAULT_MXFP8_MXFP4_MOE_TACTIC. At
flashinfer/fused_moe/cute_dsl/mixed_tuner.py:386-391, validate _fallback_tactic
with both BlockScaledContiguousGatherGroupedGemmKernel.can_implement and
Sm100BlockScaledContiguousGroupedGemmFinalizeFusionKernel.can_implement before
calling _extract_tactic_params, and raise if either predicate rejects it.

In `@tests/moe/test_cute_dsl_mxfp8_mxfp4_fused_moe.py`:
- Around line 11-22: Replace the hand-rolled _is_sm100_family checks with
flashinfer.utils.is_sm100a_supported(device), preserving the existing
sm100_required skip marker. Apply this change in
tests/moe/test_cute_dsl_mxfp8_mxfp4_fused_moe.py lines 11-22 and
tests/moe/test_cute_dsl_mxfp8_mxfp4_grouped_gemm.py lines 30-41; both sites
require the same capability check, including supported SM100-family variants and
CUDA version validation.

In `@tests/moe/test_cute_dsl_mxfp8_mxfp4_grouped_gemm.py`:
- Around line 267-271: Update the comment above the n=192 assertion to describe
the E8M0 scale layout’s requirement that the N/column extent align to complete
128-column groups, rather than referring to 128-row groups. Keep the assertions
and the separate explanation of partial trailing N tiles unchanged.

In `@tests/moe/test_moe_sort_route_windows.py`:
- Around line 45-48: Update the sm100_required skip condition to use
flashinfer.utils.is_sm100a_supported(torch.device("cuda")) instead of checking
CUDA availability and compute capability directly; import the utility and torch
as needed, while preserving the existing skip reason.

---

Nitpick comments:
In
`@flashinfer/fused_moe/cute_dsl/blackwell/blockscaled_contiguous_gather_grouped_gemm_act_fusion.py`:
- Around line 3863-3892: Update the comment above the symmetric check in
can_implement to avoid claiming that B is the only unpacked operand; describe
that the 128-element contiguous-dimension validation is applied to both A and B
defensively, while preserving the existing check_contiguous_128_elements logic.

In
`@flashinfer/fused_moe/cute_dsl/blackwell/blockscaled_contiguous_grouped_gemm_finalize_fusion.py`:
- Around line 2758-2773: Remove the unreachable unpack checks in can_implement
around use_2cta_instrs, or explicitly retain them only as forward-compatible
guards with a short explanatory comment. Preserve the existing validation that
rejects configurations where a_major is not "k" or b_major is not "k".

In `@flashinfer/fused_moe/cute_dsl/fused_moe_mxfp8_mxfp4.py`:
- Around line 785-786: Clarify the contract of
CuteDslFusedMoEMxfp8Mxfp4Runner.get_valid_tactics by adding a docstring stating
that it returns the complete ALL_MXFP8_MXFP4_MOE_TACTICS space without filtering
for can_implement or the configured shapes; keep the existing return behavior
unchanged.
- Around line 591-643: Update _capacity_for to bucket token counts when
max_num_tokens is unset, rounding num_tokens up to a power-of-two capacity
before _get_output calls _allocate_capacity. Preserve the existing
max_num_tokens validation and return behavior, and ensure the bucket is at least
num_tokens so returned output slicing remains correct.

In `@flashinfer/trace/templates/moe.py`:
- Around line 3692-3698: Update the comment above
_cute_dsl_mxfp8_mxfp4_wrapper_axes to state that no run() tensor carries a
num_experts dimension, while token_selected_experts and token_final_scales still
carry top_k; preserve the explanation that num_experts must remain a free
variable and remove the incorrect claim about top_k.

In `@tests/moe/test_cute_dsl_mxfp8_mxfp4_fused_moe.py`:
- Around line 81-110: Move _interleave_linear_and_gate and
_quantize_mxfp4_grouped into tests/moe/utils.py, using the grouped-GEMM
implementations as the canonical versions, including the rows divisibility
assertion and dequantized reference return. Remove the duplicate local
definitions from both test modules and import the shared helpers instead.
- Around line 37-47: Remove the exact count assertion for ALL_MOE_TACTICS from
the test, while preserving the mixed-path tactic count, uniqueness, runner
separation, and tile-size assertions.
🪄 Autofix

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 Plus

Run ID: 6c235d1b-429c-45f1-940f-2f3de8e63144

📥 Commits

Reviewing files that changed from the base of the PR and between 2ab910c and 5e0f9e6.

📒 Files selected for processing (19)
  • csrc/moe_utils_binding.cu
  • docs/api/fused_moe.rst
  • flashinfer/__init__.py
  • flashinfer/fused_moe/__init__.py
  • flashinfer/fused_moe/cute_dsl/__init__.py
  • flashinfer/fused_moe/cute_dsl/blackwell/blockscaled_contiguous_gather_grouped_gemm_act_fusion.py
  • flashinfer/fused_moe/cute_dsl/blackwell/blockscaled_contiguous_grouped_gemm_finalize_fusion.py
  • flashinfer/fused_moe/cute_dsl/blockscaled_contiguous_gather_grouped_gemm_act_fusion.py
  • flashinfer/fused_moe/cute_dsl/blockscaled_contiguous_grouped_gemm_finalize_fusion.py
  • flashinfer/fused_moe/cute_dsl/fused_moe_mxfp8_mxfp4.py
  • flashinfer/fused_moe/cute_dsl/mixed_tuner.py
  • flashinfer/fused_moe/cute_dsl/tuner.py
  • flashinfer/trace/templates/moe.py
  • include/flashinfer/trtllm/fused_moe/RoutingKernel.cuh
  • include/flashinfer/trtllm/fused_moe/RoutingKernel.h
  • tests/moe/test_cute_dsl_moe_can_implement.py
  • tests/moe/test_cute_dsl_mxfp8_mxfp4_fused_moe.py
  • tests/moe/test_cute_dsl_mxfp8_mxfp4_grouped_gemm.py
  • tests/moe/test_moe_sort_route_windows.py

Comment thread flashinfer/fused_moe/cute_dsl/fused_moe_mxfp8_mxfp4.py Outdated
Comment thread flashinfer/fused_moe/cute_dsl/mixed_tuner.py
Comment thread tests/moe/test_cute_dsl_mxfp8_mxfp4_fused_moe.py Outdated
Comment thread tests/moe/test_cute_dsl_mxfp8_mxfp4_grouped_gemm.py Outdated
Comment thread tests/moe/test_moe_sort_route_windows.py
- Make the functional path's async-memset event/stream resources thread-local,
  so one thread's memset_event.wait() can no longer be satisfied by another
  thread's record and start GEMM2 over a buffer that was never zeroed.
- Stop returning DEFAULT_MXFP8_MXFP4_MOE_TACTIC when no tactic passes
  can_implement, matching the NVFP4 runner's gh flashinfer-ai#3957 behaviour of returning an
  empty list so the autotuner skips a bucket it cannot profile.
- Gate the MXFP8 x MXFP4 and route-window tests on
  flashinfer.utils.is_sm100a_supported so they honour the CUDA 12.8+
  requirement instead of only checking the compute-capability major version.
- Clarify that the finalize kernel's n % 128 guard groups the N extent rather
  than rows, so it no longer reads as a different dimension from the adjacent
  partial-N-tile note.

AI-assisted with Claude Code.

Signed-off-by: Tiekai Bi <tiekaib@nvidia.com>
The no-autotune fallback matched one exact problem shape and returned
hand-measured tactics for it. The measured edge was under 1% on the full
pipeline, no test guarded the entries against kernel changes, and the sibling
NVFP4 runner has no equivalent table, so the fallback now returns
DEFAULT_MXFP8_MXFP4_MOE_TACTIC like CuteDslFusedMoENvfp4Runner.forward does.
Autotuning is unaffected.

Also drop the concrete problem dimensions from the mUseContiguousRouteWindows
comment; the speedups it cites stand on their own.

AI-assisted with Claude Code.

Signed-off-by: Tiekai Bi <tiekaib@nvidia.com>
Follows the NVFP4 wrapper (flashinfer-ai#3404): CUDA graph capture records allocations
from its private pool, so pre-sizing workspace for a maximum batch never
helped capture and only cost memory. Drop the output, routing, intermediate
and scale buffers and keep just the persistent stream and events, which do
have to exist before capture.

max_num_tokens is now accepted but ignored, matching CuteDslMoEWrapper. This
also removes the per-batch-size buffer caches, which grew without bound when
max_num_tokens was unset, and makes run() return a caller-owned tensor
instead of a view of wrapper storage that the next call overwrites.

AI-assisted with Claude Code.

Signed-off-by: Tiekai Bi <tiekaib@nvidia.com>
@jiahanc

jiahanc commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

/bot run tests/moe

@flashinfer-bot

Copy link
Copy Markdown
Collaborator

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

@jiahanc jiahanc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, thanks for the work!

@jiahanc jiahanc added the run-ci label Aug 12, 2026
@flashinfer-bot

Copy link
Copy Markdown
Collaborator

[SUCCESS] Pipeline #62264435: 18/18 executed test jobs passed

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
flashinfer/fused_moe/cute_dsl/mixed_tuner.py (3)

347-358: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Annotate tactic as Optional.

The parameter defaults to None but is annotated Tuple[Any, ...]. PEP 484 does not allow an implicit Optional, and Ruff reports RUF013 here. Line 354 already handles None, so the correct annotation is Optional[Tuple[Any, ...]]. That change also removes the need for the type: ignore[assignment] comment.

Optional is already imported through typing in this module's import list? Check line 26: it imports Any, Callable, Dict, List, Tuple only, so add Optional.

♻️ Proposed change
-from typing import Any, Callable, Dict, List, Tuple
+from typing import Any, Callable, Dict, List, Optional, Tuple
     def forward(  # type: ignore[override]
         self,
         inputs: List[torch.Tensor],
-        tactic: Tuple[Any, ...] = None,  # type: ignore[assignment]
+        tactic: Optional[Tuple[Any, ...]] = None,
         do_preparation: bool = False,
         **kwargs: Any,
     ) -> torch.Tensor:
🤖 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/fused_moe/cute_dsl/mixed_tuner.py` around lines 347 - 358, Update
the forward method’s tactic parameter annotation to Optional[Tuple[Any, ...]],
add Optional to the typing imports, and remove the now-unnecessary type:
ignore[assignment] comment while preserving the existing None handling.

Source: Linters/SAST tools


269-286: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use one source for num_local_experts.

Line 274 derives num_local_experts from w1_weight.shape[0]. Line 284 passes self.num_local_experts to get_max_num_permuted_tokens. The public entry points validate that the two agree, so the current behavior is correct. Reading the local variable in both places would make that invariant explicit and remove the question for a future reader.

♻️ Proposed change
-            permuted_m = get_max_num_permuted_tokens(
-                num_tokens,
-                self.top_k,
-                self.num_local_experts,
-                params["tile_size"],
-            )
+            permuted_m = get_max_num_permuted_tokens(
+                num_tokens,
+                self.top_k,
+                num_local_experts,
+                params["tile_size"],
+            )
🤖 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/fused_moe/cute_dsl/mixed_tuner.py` around lines 269 - 286, Update
the tactic loop in the relevant tuning method to pass the locally derived
num_local_experts value to get_max_num_permuted_tokens instead of
self.num_local_experts, using one consistent source for the expert count.

239-254: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Drop device.index from the MXFP8/MXFP4 autotune cache key.

compute_capability already separates entries by GPU architecture. device.index repeats the 32-tactic sweep for each GPU ordinal and makes persisted configs ordinal-specific. The sibling NVFP4 runners omit device.index; keep the cache-key behavior consistent.

♻️ Proposed change
         return (
             "mxfp8_mxfp4_v2",
             self.enable_pdl,
             device.type,
-            device.index,
             compute_capability,
             int(self.activation_type),
🤖 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/fused_moe/cute_dsl/mixed_tuner.py` around lines 239 - 254, Update
get_cache_key_extras in the MXFP8/MXFP4 tuner to remove device.index from the
returned cache-key tuple. Preserve compute_capability and all other existing key
components so cached configurations remain separated by GPU architecture rather
than device ordinal.
flashinfer/fused_moe/cute_dsl/fused_moe_mxfp8_mxfp4.py (1)

679-680: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document that this enumeration is unfiltered.

CuteDslFusedMoEMxfp8Mxfp4Runner.get_valid_tactics filters ALL_MXFP8_MXFP4_MOE_TACTICS through both kernels' can_implement predicates for the current shapes. This wrapper method returns the full list with no filtering, because it has no inputs or profile argument. A caller can read the name and then pass a tactic that the kernels reject for its shapes.

A short docstring that states the list is shape-independent would prevent that reading.

♻️ Proposed docstring
     def get_valid_tactics(self) -> list:
+        """Return every MXFP8 x MXFP4 tactic, without shape filtering.
+
+        The returned tactics are not validated against the kernels'
+        ``can_implement`` predicates for a specific problem shape. Pass
+        ``tactic=None`` to ``run`` to let the autotuner select a tactic that
+        both grouped GEMMs accept.
+        """
         return list(ALL_MXFP8_MXFP4_MOE_TACTICS)
🤖 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/fused_moe/cute_dsl/fused_moe_mxfp8_mxfp4.py` around lines 679 -
680, Add a concise docstring to
CuteDslFusedMoEMxfp8Mxfp4Runner.get_valid_tactics stating that it returns the
complete, unfiltered ALL_MXFP8_MXFP4_MOE_TACTICS list and is independent of
input shapes because no kernel capability checks are performed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@flashinfer/fused_moe/cute_dsl/fused_moe_mxfp8_mxfp4.py`:
- Around line 500-540: Update the wrapper’s run method to execute its entire
body inside with torch.cuda.device(self.device), ensuring current-stream, GEMM
launches, and event operations use the wrapper’s configured CUDA device. Locate
the run method associated with the constructor above and preserve its existing
logic within the device context.

---

Nitpick comments:
In `@flashinfer/fused_moe/cute_dsl/fused_moe_mxfp8_mxfp4.py`:
- Around line 679-680: Add a concise docstring to
CuteDslFusedMoEMxfp8Mxfp4Runner.get_valid_tactics stating that it returns the
complete, unfiltered ALL_MXFP8_MXFP4_MOE_TACTICS list and is independent of
input shapes because no kernel capability checks are performed.

In `@flashinfer/fused_moe/cute_dsl/mixed_tuner.py`:
- Around line 347-358: Update the forward method’s tactic parameter annotation
to Optional[Tuple[Any, ...]], add Optional to the typing imports, and remove the
now-unnecessary type: ignore[assignment] comment while preserving the existing
None handling.
- Around line 269-286: Update the tactic loop in the relevant tuning method to
pass the locally derived num_local_experts value to get_max_num_permuted_tokens
instead of self.num_local_experts, using one consistent source for the expert
count.
- Around line 239-254: Update get_cache_key_extras in the MXFP8/MXFP4 tuner to
remove device.index from the returned cache-key tuple. Preserve
compute_capability and all other existing key components so cached
configurations remain separated by GPU architecture rather than device ordinal.
🪄 Autofix

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 Plus

Run ID: 556ee918-deff-4547-b3d6-1150e7faee07

📥 Commits

Reviewing files that changed from the base of the PR and between fcf2604 and 2cbad0c.

📒 Files selected for processing (20)
  • csrc/moe_utils_binding.cu
  • docs/api/fused_moe.rst
  • flashinfer/__init__.py
  • flashinfer/fused_moe/__init__.py
  • flashinfer/fused_moe/cute_dsl/__init__.py
  • flashinfer/fused_moe/cute_dsl/blackwell/blockscaled_contiguous_gather_grouped_gemm_act_fusion.py
  • flashinfer/fused_moe/cute_dsl/blackwell/blockscaled_contiguous_grouped_gemm_finalize_fusion.py
  • flashinfer/fused_moe/cute_dsl/blockscaled_contiguous_gather_grouped_gemm_act_fusion.py
  • flashinfer/fused_moe/cute_dsl/blockscaled_contiguous_grouped_gemm_finalize_fusion.py
  • flashinfer/fused_moe/cute_dsl/fused_moe_mxfp8_mxfp4.py
  • flashinfer/fused_moe/cute_dsl/mixed_tuner.py
  • flashinfer/fused_moe/cute_dsl/tuner.py
  • flashinfer/trace/templates/moe.py
  • include/flashinfer/trtllm/fused_moe/RoutingKernel.cuh
  • include/flashinfer/trtllm/fused_moe/RoutingKernel.h
  • tests/moe/test_cute_dsl_moe_can_implement.py
  • tests/moe/test_cute_dsl_mxfp8_mxfp4_fused_moe.py
  • tests/moe/test_cute_dsl_mxfp8_mxfp4_grouped_gemm.py
  • tests/moe/test_moe_sort_route_windows.py
  • tests/trace/template_registry.py
🚧 Files skipped from review as they are similar to previous changes (17)
  • flashinfer/fused_moe/cute_dsl/tuner.py
  • flashinfer/fused_moe/init.py
  • include/flashinfer/trtllm/fused_moe/RoutingKernel.cuh
  • csrc/moe_utils_binding.cu
  • docs/api/fused_moe.rst
  • flashinfer/fused_moe/cute_dsl/init.py
  • flashinfer/init.py
  • tests/moe/test_cute_dsl_mxfp8_mxfp4_fused_moe.py
  • include/flashinfer/trtllm/fused_moe/RoutingKernel.h
  • tests/moe/test_moe_sort_route_windows.py
  • tests/moe/test_cute_dsl_mxfp8_mxfp4_grouped_gemm.py
  • flashinfer/trace/templates/moe.py
  • tests/moe/test_cute_dsl_moe_can_implement.py
  • flashinfer/fused_moe/cute_dsl/blockscaled_contiguous_gather_grouped_gemm_act_fusion.py
  • flashinfer/fused_moe/cute_dsl/blockscaled_contiguous_grouped_gemm_finalize_fusion.py
  • flashinfer/fused_moe/cute_dsl/blackwell/blockscaled_contiguous_grouped_gemm_finalize_fusion.py
  • flashinfer/fused_moe/cute_dsl/blackwell/blockscaled_contiguous_gather_grouped_gemm_act_fusion.py

Comment thread flashinfer/fused_moe/cute_dsl/fused_moe_mxfp8_mxfp4.py
@jiahanc jiahanc added run-ci and removed run-ci labels Aug 13, 2026
@jiahanc
jiahanc merged commit 0d25b18 into flashinfer-ai:main Aug 13, 2026
28 of 29 checks passed
jefby pushed a commit to jefby/flashinfer that referenced this pull request Aug 19, 2026
…ch routing locality (flashinfer-ai#4440)

## 📌 Description

  Two related changes to the Blackwell CuTe-DSL MoE path.

  **1. MXFP8 x MXFP4 fused MoE (SM100/SM103)** — `e3bc88b3`

A mixed-precision path: MXFP8 (E4M3 + linear block-32 E8M0) activations
against
packed MXFP4 (E2M1 + MMA-layout block-32 E8M0) weights, with an MXFP8
FC1
  intermediate and BF16 finalized output.

- Both grouped-GEMM kernels take separate `a_dtype`/`b_dtype`, size SMEM
from
`smem_alloc_*_dtype`, and load the narrow operand via TMA `UNPACK_U8`.
- The FC1 epilogue emits the same exact UE8M0 codes as `mxfp8_quantize`
and
scalar-stores them as bytes, avoiding the unsupported `vector<2xE8M0>`
    conversion/store lowering.
  - New public APIs `cute_dsl_fused_moe_mxfp8_mxfp4` and
`CuteDslMxfp8Mxfp4MoEWrapper`, with their own tactic space and autotune
cache
    namespace so NVFP4 results and cache entries are unaffected.
- GEMM2 `can_implement` no longer requires N to divide the MMA N tile:
the
finalize epilogue already clamps its bulk-reduce to `valid_columns`,
which
`tests/moe/test_cute_dsl_moe_can_implement.py` documents. A
partial-N-tile
    case is added to the grouped-GEMM test to cover it.

**2. Contiguous per-CTA route windows in `moe_sort` at large batches** —
`5e0f9e64`

The cooperative routing kernel assigns each CTA a grid stride of
expanded
indices, and an expert's rows land in CTA-arrival order, so one expert's
rows
are drawn from the whole batch. A grouped-GEMM tile then gathers across
the
entire activation tensor, and the footprint grows with the batch:
measured on
B200 (hidden 6144, 256 experts, top_k 8, 32 local, tile 256) a GEMM1
tile
touches 94 distinct 2 MiB pages at 64K tokens, 190 at 128K and 228 at
256K,
  which outgrows the uTLB.

With `mUseContiguousRouteWindows` each CTA owns one contiguous span
instead, so
a tile gathers from a few narrow token windows and the footprint stays
flat at
  ~28-38 pages. `moe_sort` enables it from 65536 tokens:

  | tokens | GEMM1 | full MoE pipeline | 2 MiB pages / tile |
  |---:|:--|:--|:--|
  | 65536 | 1.00-1.10x | 0.99-1.03x | 94 -> 38 |
  | 131072 | 1.40-1.44x | **1.17-1.18x** | 190 -> 32 |
  | 262144 | 1.43-1.47x | **1.24-1.28x** | 228 -> 29 |

The flag defaults to off, so every other caller of the routing kernels,
including the TRT-LLM MoE backend, keeps its current ordering bit for
bit.

  ## 🔍 Related Issues

  <!-- link related issues here -->

  ## 🚀 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.).

  New tests: `tests/moe/test_cute_dsl_mxfp8_mxfp4_grouped_gemm.py`,
  `tests/moe/test_cute_dsl_mxfp8_mxfp4_fused_moe.py`,
  `tests/moe/test_moe_sort_route_windows.py`.

  Run on B200 (SM100):

- `tests/moe/test_cute_dsl_fused_moe.py` plus the three MXFP8 x MXFP4 /
    `can_implement` files — 564 passed
- `tests/moe/test_moe_sort_route_windows.py` plus MXFP8 x MXFP4 end to
end — 12 passed
  - `tests/trace/test_fi_trace_template_consistency.py`,
    `tests/trace/test_template_init.py` — 818 passed, 167 skipped

  ## Reviewer Notes

- The routing change touches `RoutingKernel.cuh`, shared with the
TRT-LLM MoE
backend. It is gated behind a `DataBase` flag that only `moe_sort` sets,
so
TRT-LLM paths are unchanged; that separation is the main thing worth
checking.
  - Scope of the new ordering: all three CuTe-DSL MoE entry points share
`moe_sort` (`cute_dsl_fused_moe_nvfp4`, `cute_dsl_fused_moe_mxfp8_mxfp4`
and
the W4A16 path), so all three pick it up above the threshold. NVFP4 uses
the same gather kernel so the
mechanism applies, but its packed-FP4 activations are half the bytes per
    token, so its benefit is not that much.
- Only the cooperative routing path honours the flag, so it applies
while that
path is selected (roughly 65536 to ~570K tokens at 256 experts / top_k 8
on a
148-SM B200). Above that the multi-kernel path runs and the flag is
ignored.
- This is a locality change, not an ordering guarantee: rows within an
expert
    still land in atomic-arrival order and the permutation stays
non-deterministic run to run, exactly as before. Only each CTA's source
range
    becomes contiguous.
- Two subtleties in the coop kernel are load-bearing: the per-CTA span
is sized
to keep every CTA busy rather than fixed at the per-thread maximum, and
each
CTA must stop at its own span end. Without the latter the trailing
iterations
    run into the next CTA's span — still below `expandedIdxSize`, so the
batch-size check alone does not stop them — and those routes get counted
and
permuted twice. `test_moe_sort_route_windows.py` asserts per-expert row
counts
against the routing histogram, which is what catches this class of bug;
a
bijection check alone does not, since a duplicated permutation is still
    self-consistent.


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

* **New Features**
* Added MXFP8 activation × MXFP4 weight support for fused
Mixture-of-Experts workloads on compatible GPUs.
* Added public APIs, reusable wrappers, autotuning, tracing, and
documentation for mixed-precision fused MoE operations.
* Improved large-batch routing with contiguous route windows for more
localized processing.

* **Bug Fixes**
* Improved validation, scaling, alignment, memory handling, and output
quantization.
  * Preserved compatibility with existing NVFP4 APIs.

* **Tests**
* Added functional, integration, routing, and numerical correctness
coverage.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Tiekai Bi <tiekaib@nvidia.com>
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.

3 participants