perf: optimize MXFP4xBF16 & INT4xFP8 and add MXFP4xFP8 CUTLASS MoE backend for SM90 - #3738
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:
📝 WalkthroughWalkthroughAdds Humming-style MXFP4×FP8 mixed-input support across CUTLASS and FlashInfer, including FP4 compatibility aliases, mixed-input scale-mode routing, precomputed scheduler/workspace plumbing, per-token FP8 scaling, preprocessing APIs, JIT generation, and correctness tests. ChangesHumming mixed-input MoE path
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 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 adds support for a Humming-style MXFP4-weight x FP8-activation Hopper path with pre-MMA E8M0 scale fusion, introducing a precomputed tile scheduler, weight scale folding, and per-token FP8 quantization. The review feedback highlights several important issues: a critical bug in cutlass_fused_moe_kernels.cuh where pointer arithmetic for subbyte weight types lacks proper bit-width scaling, leading to out-of-bounds memory access; a performance bottleneck in the activation kernel due to redundant calls to compute_activation; and multiple compilation errors in flashinfer_cutlass_fused_moe_binding.cu where the Fp4Type alias is not fully qualified.
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.
There was a problem hiding this comment.
Actionable comments posted: 17
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
csrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cu (1)
1518-1523: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winValidate the interleave kernel shape preconditions before launch.
All three interleave kernels process rows in 16-row groups and K in 64-element groups. Without
n % 16 == 0andk % 64 == 0checks, small/misaligned inputs can silently skip tails or read past row bounds.Proposed fix
int64_t const num_experts = weight.size(0); int64_t const n = weight.size(1); int64_t const k = weight.size(2) * 2; + TVM_FFI_ICHECK_EQ(n % 16, 0) + << "weight n dimension must be divisible by 16 for SM90 mixed-gemm interleave"; + TVM_FFI_ICHECK_EQ(k % 64, 0) + << "logical K dimension must be divisible by 64 for SM90 mixed-gemm interleave"; int64_t const per_expert_bytes = n * (k / 2);Also applies to: 1535-1537
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@csrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cu` around lines 1518 - 1523, In the interleave kernel launch path, add explicit shape precondition validation before invoking the kernels: `weight.size(1)` must be divisible by 16 and `weight.size(2) * 2` must be divisible by 64. Update the checks near the existing `quant_type` validation in the fused MoE binding so `num_experts`, `n`, and `k` are only used after confirming these alignment requirements, and apply the same guard to the other interleave launch sites referenced by this block.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@csrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cu`:
- Around line 914-922: Keep GEMM2 tactic IDs unambiguous:
getValidTacticsForShape currently returns absolute GEMM2 IDs, but
setRunnerProfiles still interprets any id2 less than mGemm2TacticCount as
relative and applies an extra offset. Update the selection/normalization logic
in setRunnerProfiles (and, if needed, the helper around getValidTacticsForShape)
so GEMM2 IDs are consistently treated as either absolute or relative everywhere,
preventing double-offsetting and incorrect profile selection.
In
`@csrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm_configs.h`:
- Line 120: The mixed-dtype MoE dispatch switch is missing a case for
CutlassTileConfigSM90::CtaShape64x256x128B, so the SM90 heuristic can select a
tile that falls through to the invalid-config default. Update
moe_gemm_template_dispatch_tma_ws_mixed_dtype.h in the
DISPATCH_MIXED_DTYPE_MOE_TILE switch to add the missing CtaShape64x256x128B
branch alongside the other CtaShape64x... entries, matching the existing
dispatch pattern used for the other tile shapes.
In
`@csrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/collective/sm90_mma_array_tma_gmma_rs_warpspecialized_mixed_input_prescale.hpp`:
- Around line 365-368: The `UseInt4ToFP8LookupTable` constexpr check uses an
invalid 4-bit type name, so update the `cute::is_same_v` comparison in this GEMM
collective to use `cutlass::int4b_t` instead of `cutlass::int4_t`. Keep the rest
of the condition unchanged so the lookup-table path still only applies for
`ConversionMode::ConvertAndScale` with `ElementA` as the 4-bit integer type and
`ElementB` as `cutlass::float_e4m3_t`.
In
`@csrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/kernel/sm90_tile_scheduler_group_precomputed.hpp`:
- Around line 97-100: The scheduler’s linear work index is being truncated
because `WorkLinearIdx` is defined as `uint32_t`, while the precomputed map uses
`uint64_t`; update `sm90_tile_scheduler_group_precomputed` so
`current_work_linear_idx_` and `total_grid_size_` use a 64-bit type and keep all
related arithmetic/indexing in `WorkLinearIdx` consistent. Review the affected
logic in `sm90_tile_scheduler_group_precomputed` where the linear index is
advanced and compared, and make sure any helpers or calculations that feed the
map lookup also use the wider type so large routed-token/expert cases cannot
wrap.
- Around line 166-175: The SharedStorage::pipeline accessor is returning
PipelineStorage by value, which can cause callers to work on a copy instead of
the shared-memory synchronization state. Update the pipeline() method in
SharedStorage to return the stored pipeline_ by reference so the scheduler built
from it uses the actual shared storage; keep the existing throttle_pipeline()
and data() accessors unchanged.
In
`@csrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/launchers/moe_gemm_tma_ws_mixed_input_launcher.inl`:
- Around line 244-246: The precomputed scheduler check in
moe_gemm_tma_ws_mixed_input_launcher rejects empty routed batches by requiring
total_routed_tokens > 0; update the validation around total_routed_tokens so
zero is accepted as a no-op case, and ensure the launcher path exits early or
skips work when hopper_inputs.precomputed_scheduler_total_routed_tokens is 0
instead of failing.
In
`@csrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/launchers/moe_gemm_tma_ws_mixed_input_prebuild.h`:
- Around line 174-182: The precomputed work-tile map in make_work_tile_static is
using only gemm_grid_x * gemm_grid_y for worker_id and sentinel sizing, which
leaves gridDim.z inconsistent with the scheduler’s global indexing. Update the
prebuild logic to either incorporate gemm_grid_shape.z / gemm_grid_z
consistently wherever the map is built and partitioned, or add a strict
assertion in the launcher path that gemm_grid_shape.z == 1 before calling this
kernel. Make the same consistency fix in the other affected map-building sites
referenced by the diff so the precomputed sentinels and worker indexing all
agree with the full 3D grid shape.
In
`@csrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_mixed_utils.cu`:
- Line 52: The `moe_gemm_mixed_utils.cu` read of `fp4x2` in the mixed-utils path
uses `reinterpret_cast<uint32_t*>`, which risks strict-aliasing and alignment
UB. Replace that type-pun with an explicit 32-bit assembly of the four bytes
into `fp4x8_raw` in the same scope so the `fp4x2` buffer is accessed safely
regardless of alignment. Keep the fix local to the code that initializes
`fp4x8_raw` and ensure no `uint32_t*` cast remains.
In
`@csrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_template_dispatch_tma_ws_mixed_dtype.h`:
- Around line 86-90: The mixed-dtype MOE GEMM launchers are still hardcoding
KernelTmaWarpSpecialized* when Wfp4Afp8Mode resolves to
MixedInputScaleMode::kPreMmaE8M0, which breaks the ptr-array-only path. Update
the launcher selection logic in moe_gemm_template_dispatch_tma_ws_mixed_dtype.h
to use the ptr-array schedule variants when IsArrayOfPointersGemm is true,
including the pingpong path via KernelPtrArrayTmaWarpSpecializedPingpong, so the
builder’s pre-MMA E8M0 requirement is matched correctly.
- Around line 160-164: The size validation in the FP4 branch is using the wrong
precedence and currently makes TLLM_CHECK ineffective; update the check in
moe_gemm_template_dispatch_tma_ws_mixed_dtype.h so the expected activation size
is computed explicitly instead of relying on the ambiguous ternary expression.
Use the existing symbols WeightType, Fp4Type, and Wfp4Afp8Mode to distinguish
FP4-weight/FP8-activation mode from other FP4 modes, and make the expected
sizeof(T) 1 for Wfp4Afp8Mode and 2 for the other FP4 path. Keep the
non-ENABLE_FP4 branch as a 1-byte check.
In `@flashinfer/fused_moe/core.py`:
- Around line 795-800: The Hopper-only Humming path is still reachable in the
fused MoE wrapper before architecture checks, so add an early guard in the
module-dispatch flow in fused_moe/core.py around the use_wfp4afp8_humming path
to reject unsupported device_arch values before loading the extension. Use the
existing fused_moe wrapper/module selection logic and the use_wfp4afp8_humming
flag to fail fast with a clear unsupported-architecture error on non-Hopper
devices (including SM100/SM120), and apply the same guard in the related
duplicated block referenced by the same dispatch path.
In `@flashinfer/fused_moe/prepare.py`:
- Around line 170-177: The new public tensor APIs decorated with `@flashinfer_api`
are missing TraceTemplate wiring, so add a trace=... argument to the decorator
for preprocess_moe_weights_for_sm90_mixed_gemm_humming and the other affected
public functions in this module. Use the existing trace template pattern used
elsewhere in flashinfer/**/*.py for tensor-in/tensor-out APIs, and keep the
exported API signatures unchanged while updating the `@flashinfer_api` annotations
to enable benchmark-definition JSON generation.
- Around line 268-272: The group_size validation in prepare() should happen
before computing scale_groups_per_k128, since a zero value currently triggers
ZeroDivisionError instead of the intended ValueError. Move the
divisibility/positivity check ahead of the 128 // group_size calculation, and
keep the existing validation message in the same prepare.py logic so invalid
group_size values are rejected cleanly.
- Around line 64-80: `prepare.py` currently allows `max_range` up to 255, but
`prepare()` packs the offset with a 4-bit mask via `offset`, so larger values
can wrap and corrupt the preprocessing. Tighten the validation near the existing
`max_range` check to cap it at the 4-bit representable range used by `offset`
(and keep the non-negative check), and make sure the computed
`scale_range`/`scale_min_new` logic in `prepare()` only operates within that
safe bound.
- Around line 211-227: The preprocessing in the weight-scale path should reject
invalid inputs earlier in prepare() before calling
_preprocess_humming_e8m0_weight_scale and _process_humming_mxfp4_w4a8_payload.
Add explicit validation that the K dimension used in expected_scale_shape is
divisible by 32 instead of relying on floor division, and verify raw_scale and
weight are on the same device (and compatible dtype/device context) before any
tensor indexing or preprocessing. Use the existing prepare() shape check and the
raw_scale/weight arguments to raise a clear ValueError at the API boundary when
these conditions are not met.
In `@flashinfer/jit/gemm/cutlass/generate_kernels.py`:
- Around line 672-694: The FP4 dtype support list in generate_kernels.py is
missing the existing FP16 activation W4A16 combo, which prevents SM90
mixed-input grouped kernels from being generated for that path. Update
supported_dtypes_fp4 to include the FP16 activation variant alongside the
existing BF16 and FP8 entries, and make sure the kernel generation logic that
consumes this list still emits the FP16 x MXFP4 W4A16 cases through the relevant
dtype tuples and any associated filtering in the same generator code.
In `@tests/moe/test_trtllm_cutlass_fused_moe.py`:
- Around line 1790-1799: The W4A8 FC1 reference is still using per-expert
input_scale.squeeze(-1), which does not match the flash path’s max-broadcast
contract. Update the reference setup in the fused MoE test to build FC1 scales
from the same w3_w1_input_scale_max behavior used by _run_w4a8_moe_hopper, and
reuse that max-broadcast scale wherever the FC1 comparison is computed so both
paths compare against the same quantization input.
---
Outside diff comments:
In `@csrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cu`:
- Around line 1518-1523: In the interleave kernel launch path, add explicit
shape precondition validation before invoking the kernels: `weight.size(1)` must
be divisible by 16 and `weight.size(2) * 2` must be divisible by 64. Update the
checks near the existing `quant_type` validation in the fused MoE binding so
`num_experts`, `n`, and `k` are only used after confirming these alignment
requirements, and apply the same guard to the other interleave launch sites
referenced by this block.
🪄 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: cb252d49-4f49-43a8-adcb-8b3c33ac3859
📥 Commits
Reviewing files that changed from the base of the PR and between 647c52d and 96397d18b5801c4bae10931bb258de4a25c64270.
📒 Files selected for processing (45)
csrc/fused_moe/cutlass_backend/cutlass_fused_moe_instantiation.cucsrc/fused_moe/cutlass_backend/cutlass_fused_moe_kernels.cuhcsrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cucsrc/moe_utils_binding.cucsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/detail/collective/mixed_input_utils.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/epilogue/collective/sm90_epilogue_array_tma_warpspecialized_mixed_input.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/epilogue/fusion/sm90_ptr_array_per_token_scale_callbacks_tma_warpspecialized.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/collective/builders/sm90_gmma_builder_mixed_input.inlcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/collective/collective_builder_mixed_input.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/collective/collective_mma_array_mixed_input.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/collective/sm90_mma_array_tma_gmma_rs_warpspecialized_mixed_input_.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/collective/sm90_mma_array_tma_gmma_rs_warpspecialized_mixed_input_prescale.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/kernel/sm90_gemm_array_tma_warpspecialized_cooperative_precomputed.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/kernel/sm90_gemm_array_tma_warpspecialized_pingpong_precomputed.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/kernel/sm90_gemm_array_tma_warpspecialized_precomputed_decl.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/kernel/sm90_tile_scheduler_group_precomputed.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm_configs.hcsrc/nv_internal/tensorrt_llm/kernels/cuteDslKernels/moeUtils.cucsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/cutlass_heuristic.cppcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/cutlass_type_conversion.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/fp4_compat.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/include/moe_gemm_kernels.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/include/moe_kernels.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/include/moe_util_kernels.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/launchers/moe_gemm_tma_ws_launcher.inlcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/launchers/moe_gemm_tma_ws_mixed_input_launcher.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/launchers/moe_gemm_tma_ws_mixed_input_launcher.inlcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/launchers/moe_gemm_tma_ws_mixed_input_prebuild.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_kernels_bf16_fp4.cucsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_kernels_fp16_fp4.cucsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_kernels_fp4_fp4.cucsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_kernels_fp8_fp4.cucsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_mixed_utils.cucsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_mixed_utils.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_template_dispatch.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_template_dispatch_tma_ws.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_template_dispatch_tma_ws_mixed_dtype.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_tma_warp_specialized_input.cucsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_tma_warp_specialized_traits.hflashinfer/fused_moe/__init__.pyflashinfer/fused_moe/core.pyflashinfer/fused_moe/prepare.pyflashinfer/jit/fused_moe.pyflashinfer/jit/gemm/cutlass/generate_kernels.pytests/moe/test_trtllm_cutlass_fused_moe.py
| @@ -154,9 +172,29 @@ class FusedMoeRunner : public tvm::ffi::ModuleObj { | |||
| } | |||
| #endif | |||
| #ifdef ENABLE_FP4 | |||
| if (isWMxfp4AMxfp8Quant() || isWMxfp4AFp8Quant()) { | |||
| #if 0 | |||
There was a problem hiding this comment.
#if 0 removes the legacy FP8×MXFP4 runner selection from the shared SM100/SM120 binding. The existing Blackwell tests still pass FP8 activations, int64-packed MXFP4 weights, and use_mxfp8_act_scaling=true; no remaining constructor branch matches, so module.init throws.
If Blackwell support is intentionally dropped, please remove the tests and document the breaking change; otherwise restrict this disable to Hopper.
There was a problem hiding this comment.
Fixed and varified on B200.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
csrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cu (1)
1253-1258: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winPass the validated
fc2_act_globalinto Humming quant params.This branch validates
fc2_act_globalbut then drops it by passingnullptr, so FC2 activation/global scaling is silently ignored for Humming-style MXFP4×FP8.Proposed fix
return kernels::QuantParams::FP8MXFP4( nullptr, static_cast<TmaWarpSpecializedGroupedGemmInput::ElementSF*>(fc1_weight_block.data_ptr()), - static_cast<float const*>(fc1_token_scale.data_ptr()), nullptr, + static_cast<float const*>(fc1_token_scale.data_ptr()), + static_cast<float const*>(fc2_act_global.data_ptr()), static_cast<TmaWarpSpecializedGroupedGemmInput::ElementSF*>(fc2_weight_block.data_ptr()), - static_cast<float const*>(fc2_token_scale.data_ptr()), false, false); + static_cast<float const*>(fc2_token_scale.data_ptr()), false, fc2_act_global.ndim() == 1);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@csrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cu` around lines 1253 - 1258, The Humming quant params path is validating fc2_act_global but then discarding it by passing a null value, so the FC2 activation/global scale is ignored. Update the FP8MXFP4 construction in the fused MoE binding to pass the validated fc2_act_global through the same slot currently hardcoded as null, and keep the surrounding quant-parameter wiring in sync with the existing fc1_weight_block, fc1_token_scale, fc2_weight_block, and fc2_token_scale handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@csrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/launchers/moe_gemm_tma_ws_mixed_input_prebuild.h`:
- Around line 452-462: When ChunkMajorWorkMap is enabled in
moe_gemm_tma_ws_mixed_input_prebuild.h, the packing logic in the work-tile loop
must use the chunk owner instead of recomputing worker ownership inside
make_work_tile_static. Update the call site in the local_tile/global_tile loop
so the worker index used for packing matches the storage_idx chunk owner derived
from global_tile / tiles_per_worker, and pass that ownership information through
make_work_tile_static or its inputs rather than deriving it from global_tile %
total_grid_size. Keep the chunk-major storage mapping and the packed tile
coordinates consistent in the ChunkMajorWorkMap path.
---
Outside diff comments:
In `@csrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cu`:
- Around line 1253-1258: The Humming quant params path is validating
fc2_act_global but then discarding it by passing a null value, so the FC2
activation/global scale is ignored. Update the FP8MXFP4 construction in the
fused MoE binding to pass the validated fc2_act_global through the same slot
currently hardcoded as null, and keep the surrounding quant-parameter wiring in
sync with the existing fc1_weight_block, fc1_token_scale, fc2_weight_block, and
fc2_token_scale handling.
🪄 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: 279012e6-53d7-4b71-8784-63a90a432c1d
📥 Commits
Reviewing files that changed from the base of the PR and between 96397d18b5801c4bae10931bb258de4a25c64270 and 37bb2aa6d456eb6e9e023379c36c763bacf3d75b.
📒 Files selected for processing (15)
csrc/fused_moe/cutlass_backend/cutlass_fused_moe_kernels.cuhcsrc/fused_moe/cutlass_backend/flashinfer_cutlass_fused_moe_binding.cucsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/epilogue/collective/default_epilogue_array_per_token_scale.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/collective/sm90_mma_array_tma_gmma_rs_warpspecialized_mixed_input_prescale.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/kernel/sm90_gemm_array_tma_single_warpgroup_persistent.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/kernel/sm90_gemm_array_tma_warpspecialized_pingpong_precomputed.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/kernel/sm90_tile_scheduler_group_precomputed.hppcsrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm_configs.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/launchers/moe_gemm_tma_ws_mixed_input_launcher.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/launchers/moe_gemm_tma_ws_mixed_input_launcher.inlcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/launchers/moe_gemm_tma_ws_mixed_input_prebuild.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_template_dispatch.hcsrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_template_dispatch_tma_ws_mixed_dtype.hflashinfer/jit/gemm/cutlass/generate_kernels.pytests/moe/test_trtllm_cutlass_fused_moe.py
🚧 Files skipped from review as they are similar to previous changes (10)
- csrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/launchers/moe_gemm_tma_ws_mixed_input_launcher.h
- csrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/collective/sm90_mma_array_tma_gmma_rs_warpspecialized_mixed_input_prescale.hpp
- csrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/kernel/sm90_tile_scheduler_group_precomputed.hpp
- flashinfer/jit/gemm/cutlass/generate_kernels.py
- csrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/launchers/moe_gemm_tma_ws_mixed_input_launcher.inl
- csrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_template_dispatch_tma_ws_mixed_dtype.h
- csrc/nv_internal/tensorrt_llm/cutlass_extensions/include/cutlass_extensions/gemm/kernel/sm90_gemm_array_tma_warpspecialized_pingpong_precomputed.hpp
- tests/moe/test_trtllm_cutlass_fused_moe.py
- csrc/nv_internal/tensorrt_llm/kernels/cutlass_kernels/moe_gemm/moe_gemm_template_dispatch.h
- csrc/fused_moe/cutlass_backend/cutlass_fused_moe_kernels.cuh
| switch (inputs.gemm_config.mainloop_schedule) { | ||
| case tkc::MainloopScheduleType::COOPERATIVE: | ||
| if constexpr (get<0>(CTAShape{}) < 128) { | ||
| TLLM_THROW("COOPERATIVE is only enabled when tile M >= 128."); | ||
| } else { | ||
| if constexpr ((get<0>(CTAShape{}) == 128) && get<1>(CTAShape{}) == 128) { |
There was a problem hiding this comment.
Just to confirm, why is the special-case path for the 128x128 CTA shape no longer needed here?
There was a problem hiding this comment.
The 128x128xKtile config would cause register overflow in previous versions, resulting in compilation failure. New version does not have this issue.
Signed-off-by: Jiang Shao <91270701+StudyingShao@users.noreply.github.com>
Signed-off-by: Jiang Shao <91270701+StudyingShao@users.noreply.github.com>
Signed-off-by: Jiang Shao <91270701+StudyingShao@users.noreply.github.com>
Signed-off-by: Jiang Shao <91270701+StudyingShao@users.noreply.github.com>
Signed-off-by: Jiang Shao <91270701+StudyingShao@users.noreply.github.com>
#4025) ## 📌 Description PR #3738 moved the following helper APIs from `flashinfer.fused_moe.core` to `flashinfer.fused_moe.prepare`: - `interleave_moe_scales_for_sm90_mixed_gemm` - `interleave_moe_weights_for_sm90_mixed_gemm` That module-path change breaks downstream users that still import these public helpers directly from `flashinfer.fused_moe.core`. This PR restores backward compatibility by re-exporting both helpers from `flashinfer.fused_moe.core`, while keeping their implementations and canonical definitions in `flashinfer.fused_moe.prepare`. It does not change their runtime or CUDA behavior. ## 🔍 Related Issues Fixes #4022. Follow-up to #3738. ## 🚀 Pull Request Checklist Thank you for contributing to FlashInfer! Before we review your pull request, please make sure the following items are complete. ### ✅ Pre-commit Checks - [x] I have installed `pre-commit` by running `pip install pre-commit` (or used my 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. > If you are unsure about how to set up `pre-commit`, see the > [pre-commit documentation](https://pre-commit.com/). ## 🧪 Tests - [x] `pre-commit run --all-files` - [ ] Verify the legacy imports on H20: ```python from flashinfer.fused_moe.core import ( interleave_moe_scales_for_sm90_mixed_gemm, interleave_moe_weights_for_sm90_mixed_gemm, ) ``` - [ ] Run a representative Hopper mixed-input MoE correctness test on H20. ## Reviewer Notes - The explicit same-name aliases are intentional: they expose the imported symbols as public module attributes and satisfy static linting. - `prepare.py` remains the canonical implementation location; this PR only restores the previous import path for compatibility. - `prepare.py` does not import `core.py` during module initialization, so these re-exports do not introduce an import-time circular dependency. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Kept backward compatibility for existing callers of mixture-of-experts weight and scale utilities. * Consolidated where these utilities are sourced so they remain available under the same public names and behave the same for current users. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Jiang Shao <91270701+StudyingShao@users.noreply.github.com>
<!-- .github/pull_request_template.md --> ## 📌 Description The FlashInfer perf-CI on B300 crashes when autotuning the CUTLASS NVFP4 fused-MoE with Assertion failed: `quant_1 && … && quant_6 at cutlass_fused_moe_kernels.cuh:5034.` The failure is confined to the autotuner's tactic-profiler (GemmProfilerBackend), which fabricates its own scratch scale-factor buffers quant_1..6 to time GEMM candidates in isolation — the real MoE forward builds its quant params from the caller's actual tensors and is unaffected. [PR3738](#3738) refactored the profiler's FP4 workspace-sizing, replacing the broad `is_fp4_w_quant` branch (which covered every FP4-weight case) with two narrower branches, `is_native_wfp4afp8_family` and `is_sm90_wfp4afp8_family`, both of which require FP8 activation (`mDType == kFP8`). Plain NVFP4×NVFP4 has FP4/INT64 activation, so it matched neither branch, leaving all six quant_* buffers sized 0 → null pointers → the assertion. The fix restores NVFP4 coverage by adding an `is_nvfp4_quant` predicate (`mSM>=100 && mDType∈{kFP4,kINT64} && mWType∈{kFP4,kINT64}`) and adding an additional sizing branch, whose buffer sizes are byte-for-byte identical to the removed `is_fp4_w_quant` block. Verified on a B300: the previously-crashing repro now completes autotuning (gemm1/gemm2 21/21 each) and benchmarks cleanly. ## 🔍 Related Issues <!-- Link any related issues here --> ## 🚀 Pull Request Checklist Thank you for contributing to FlashInfer! Before we review your pull request, please make sure the following items are complete. ### ✅ Pre-commit Checks - [ ] I have installed `pre-commit` by running `pip install pre-commit` (or used your preferred method). - [ ] I have installed the hooks with `pre-commit install`. - [ ] I have run the hooks manually with `pre-commit run --all-files` and fixed any reported issues. > If you are unsure about how to set up `pre-commit`, see [the pre-commit documentation](https://pre-commit.com/). ## 🧪 Tests - [ ] Tests have been added or updated as needed. - [ ] All tests are passing (`unittest`, etc.). ## Reviewer Notes <!-- Optional: anything you'd like reviewers to focus on, concerns, etc. --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved profiler workspace sizing for NVFP4 (FP4 quantization) workloads by applying the correct FP4/NVFP4-specific scaling layout. * Ensured these updated sizing rules take precedence only for the intended FP4 + INT64 quantization configuration, preventing unintended overrides in other quantization families. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…fc1 SF sizing Follow-up to flashinfer-ai#4080, which restored the NVFP4 quant-workspace sizing branch in getProfilerWorkspaces after the flashinfer-ai#3738 regression (issue flashinfer-ai#4003). Two gaps remain: Replace the ad-hoc is_nvfp4_quant predicate with an isNativeWfp4Afp4Family() helper used by both the allocation site (getProfilerWorkspaces) and the consumption site (prepareQuantParams), so the two cannot drift apart again the way flashinfer-ai#3738 did. This also drops the mSM >= 100 guard that the allocation side had but the consumption side did not. Size quant_2 (fc1 weight scale factors) with fc1_out_size instead of inter_size, fixing a pre-existing under-allocation for gated activations that equally affected the FP8-activation (wfp4afp8) family. Parametrize test_moe_nvfp4 with use_autotune as regression coverage. AI-assisted (Claude Code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#4080) on release-v0.6.17 (#4411) ## 📌 Description Reverts #3738 (`5823159c`) and its two dependents on `release-v0.6.17`, to unblock the release after a **critical crash reported by a vLLM user that escaped QA**. This is a release-unblocking revert, not a judgement on the feature. The proper fix is expected in **0.6.18**. The same revert is being applied to `release-v0.6.16` separately by another engineer — note #3738 shipped in v0.6.16 and v0.6.16.post2, so 0.6.17 is not the first release carrying it. ## Why three commits #3738 cannot be reverted alone — two later commits are built on it: | commit | PR | why it must go first | |---|---|---| | `2475121f` | #4080 | fixes `getProfilerWorkspaces` in code #3738 reworked (the `quant_1 && … && quant_6` assert) | | `c83607a9` | #4025 | exists *only* to re-export the interleave helpers that #3738 moved `core` → `prepare` | | `5823159c` | #3738 | the target | Reverted newest-first. #4080 and #4025 reverted with **zero conflicts**; #3738 conflicted in 3 files. ## Conflict resolution — later work is preserved This reverts #3738 only, **not** the features layered on top of it: - **`core.py`** — kept `profile_ids` / `workspace_buffer` and the CUDA-device guard (#4057 and follow-ups); dropped only `use_wfp4afp8_humming` and its plumbing. - **`__init__.py`** — kept `cutlass_fused_moe_workspace_size` (#4057); restored the two `interleave_moe_*_for_sm90_mixed_gemm` exports, which move back to `core` once #3738 is gone. - **`prepare.py`** — dropped the SM90 Humming docstring and the then-unused `functools` / `struct` imports; kept the `TrtllmBf16Config` wording added after #3738. Net: **47 files, +1701 / -10557** — a near-exact mirror of #3738's +10546 / -1704, the delta being the later features deliberately kept. ## ✅ Verification - No `wfp4afp8_humming` / `Humming` reference remains anywhere in `flashinfer/`, `csrc/` or `tests/`. - **Every** `cutlass_kernels/` and `cutlass_backend/` source matches the pre-#3738 parent **except two files**, and both residuals are fully attributable to later commits that correctly survive: - `flashinfer_cutlass_fused_moe_binding.cu` → #4057 (caller-owned workspace; 16 workspace-API references retained) - one `std::remove_reference_t<…>` line in `moe_gemm_tma_ws_launcher.inl` → SM107 work (#4122 / #4280) - Every relative import in `fused_moe/__init__.py` resolves (AST-checked). - `py_compile` passes on all touched Python files. - `pre-commit` (clang-format, mypy, ruff check, ruff format) passes. **✅ GPU-validated** — see [the validation comment](#4411 (comment)). A/B contrast on **B300 / sm103** with the reporter's repro, same node / container / install, only the commit differing: | | commit | result | |---|---|---| | baseline | `4e1206b3` (branch head, unreverted) | `FAIL_IN_AUTOTUNE` — dies at gemm1 tactic 1/21 | | revert | `57d74695` (this PR) | **`PASS`** — gemm1 21/21, gemm2 21/21 | The `without autotune` control passes on both builds, isolating the autotuned path. **Still not run:** the MoE unit-test suites. CI remains the gate for regression coverage; this validates the specific reported crash only. ## Reviewer notes - Worth a targeted look at the `core.py` conflict resolutions, since that is where #3738 and #4057 interleaved in the same parameter lists. - If the crash turns out to be reproducible on v0.6.16 too, that confirms #3738 as the cause rather than a 0.6.17-specific interaction. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…S MoE backend for SM90 (flashinfer-ai#3738)" This reverts commit 5823159.
#4080) on release-v0.6.16 (#4412) <!-- .github/pull_request_template.md --> ## 📌 Description <!-- What does this PR do? Briefly describe the changes and why they’re needed. --> Revert sequence for * #4080 * #3738 * #4025 ``` git checkout -b revert-3738-release-v0.6.16 upstream/release-v0.6.16 # c498513 git revert --no-edit 2475121 # #4080 (clean) git revert --no-edit 5823159 # #3738 -> conflicts in core.py/__init__.py/prepare.py # resolve keeping #4057's workspace API + profile_ids; strip use_wfp4afp8_humming; # restore interleave helpers to core.py & their exports; drop #3738's functools/struct import git add flashinfer/fused_moe/{core,__init__,prepare}.py && git revert --continue --no-edit git revert --no-edit c83607a # #4025 (clean) — must come after #3738 ``` Note: #4057 is intentionally kept ## 🔍 Related Issues <!-- Link any related issues here --> ## 🚀 Pull Request Checklist Thank you for contributing to FlashInfer! Before we review your pull request, please make sure the following items are complete. ### ✅ Pre-commit Checks - [ ] I have installed `pre-commit` by running `pip install pre-commit` (or used your preferred method). - [ ] I have installed the hooks with `pre-commit install`. - [ ] I have run the hooks manually with `pre-commit run --all-files` and fixed any reported issues. > If you are unsure about how to set up `pre-commit`, see [the pre-commit documentation](https://pre-commit.com/). ## 🧪 Tests - [ ] Tests have been added or updated as needed. - [ ] All tests are passing (`unittest`, etc.). ## Reviewer Notes <!-- Optional: anything you'd like reviewers to focus on, concerns, etc. -->
…IT builds Flags gated on the JIT toolkit version (-DENABLE_FP4 in fp4_quantization, -DENABLE_FP8_BLOCK_SCALE in cutlass fused_moe 89/90 and fp8_blockscale_gemm_90) were dropped silently, so the module built fine and the first gated call failed deep in kernel-runner construction with a misleading dtype-combination error (flashinfer-ai#3951). - Add version_gated_nvcc_flag() which emits a loud warning naming the module, the dropped flag, the toolkit version found, and the 12.8 requirement. - Use it at every version-gated flag drop site: jit/fused_moe.py, jit/fp4_quantization.py, quantization/fp4_quantization.py, and jit/gemm/fp8_blockscale.py. - Demote the warning to info when a prebuilt AOT artifact exists for the module: JitSpecNvcc.try_load() serves such modules from the AOT cache, so the JIT flag list is never compiled and the gated kernels stay available. Without this, the recommended flashinfer-jit-cache install with an older local toolkit warned spuriously, even recommending the wheel already installed. The check mirrors the AOT resolution rule through a new has_prebuilt_aot_module() helper (shared with the capability probe below) instead of moving the warning to build time, which would require threading dropped-flag metadata through JitSpec for a strictly larger diff. AOT wheel builds themselves compile into a separate build dir and never read FLASHINFER_AOT_DIR, so self-builds with an old toolkit still warn loudly. - Make the existing use_deepseek_fp8_block_scale early check name the actual cause and stop rejecting AOT/jit-cache installs, via a new cutlass_fused_moe_fp8_block_scale_supported() capability probe. - Document why SM90 fused_moe keeps -DENABLE_FP4 unconditionally (cutlass::float_e2m1_t path from flashinfer-ai#3738, no <cuda_fp4.h> needed). - Add CPU-only tests that monkeypatch the toolkit-version probe to both sides of 12.8 and assert the flag, warning, and AOT-demotion behavior; tests isolate FLASHINFER_AOT_DIR so host jit-cache installs cannot skew results.
Unlike post1/post2, which only rebuilt the companion wheels, post3 and post4 carry real source changes. post3 reverts the SM90 CUTLASS MoE backend (flashinfer-ai/flashinfer#3738 plus dependents sgl-project#4025 and sgl-project#4080). That work first shipped in 0.6.16 — it is not in 0.6.15.post1 — so the revert returns those kernels to roughly the state this repo already runs against today, rather than regressing anything. It drops six public symbols and one parameter: preprocess_moe_weights_for_sm90_mixed_gemm_humming sm90_mixed_gemm_humming_weight_preprocess_trace_dispatch bits_from_float / float_from_bits / quant_to_fp4_val / dequant_fp4_val use_wfp4afp8_humming= on cutlass_fused_moe / cutlass_fused_moe_workspace_size SGLang uses none of them. `interleave_moe_{scales,weights}_for_sm90_mixed_gemm` move back from `fused_moe/prepare.py` to `fused_moe/core.py` but keep their signatures and stay exported from `flashinfer.fused_moe`, which is where mxfp4.py and mxfp4_flashinfer_cutlass_moe.py import them from. post4 adds `from __future__ import annotations` to `flashinfer/comm/fd_exchange.py`. That module is new in 0.6.16 and annotates `array.array[int]`, which is only subscriptable on Python 3.12+. `flashinfer/comm/mnnvl.py` imports it unconditionally, so on Python 3.10/3.11 — both allowed by our `requires-python = ">=3.10"` — importing `flashinfer.comm.mnnvl` raised TypeError: type 'array.array' is not subscriptable taking out the MNNVL comm-fusion and MoE all-to-all paths. Verified on a real 3.11 interpreter: post2's fd_exchange raises, post4's imports fine. The published Docker image is Python 3.12 and was unaffected; this only bit source installs on older interpreters. Checked against 0.6.15.post1 with the full symbol sweep: no API SGLang uses is removed, and every changed signature is purely additive. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Unlike post1/post2, which only rebuilt the companion wheels, post3 and post4 carry real source changes. post3 reverts the SM90 CUTLASS MoE backend (flashinfer-ai/flashinfer#3738 plus dependents sgl-project#4025 and sgl-project#4080). That work first shipped in 0.6.16 — it is not in 0.6.15.post1 — so the revert returns those kernels to roughly the state this repo already runs against today, rather than regressing anything. It drops six public symbols and one parameter: preprocess_moe_weights_for_sm90_mixed_gemm_humming sm90_mixed_gemm_humming_weight_preprocess_trace_dispatch bits_from_float / float_from_bits / quant_to_fp4_val / dequant_fp4_val use_wfp4afp8_humming= on cutlass_fused_moe / cutlass_fused_moe_workspace_size SGLang uses none of them. `interleave_moe_{scales,weights}_for_sm90_mixed_gemm` move back from `fused_moe/prepare.py` to `fused_moe/core.py` but keep their signatures and stay exported from `flashinfer.fused_moe`, which is where mxfp4.py and mxfp4_flashinfer_cutlass_moe.py import them from. post4 adds `from __future__ import annotations` to `flashinfer/comm/fd_exchange.py`. That module is new in 0.6.16 and annotates `array.array[int]`, which is only subscriptable on Python 3.12+. `flashinfer/comm/mnnvl.py` imports it unconditionally, so on Python 3.10/3.11 — both allowed by our `requires-python = ">=3.10"` — importing `flashinfer.comm.mnnvl` raised TypeError: type 'array.array' is not subscriptable taking out the MNNVL comm-fusion and MoE all-to-all paths. Verified on a real 3.11 interpreter: post2's fd_exchange raises, post4's imports fine. The published Docker image is Python 3.12 and was unaffected; this only bit source installs on older interpreters. Checked against 0.6.15.post1 with the full symbol sweep: no API SGLang uses is removed, and every changed signature is purely additive. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…fc1 SF sizing Follow-up to flashinfer-ai#4080, which restored the NVFP4 quant-workspace sizing branch in getProfilerWorkspaces after the flashinfer-ai#3738 regression (issue flashinfer-ai#4003). Two gaps remain: Replace the ad-hoc is_nvfp4_quant predicate with an isNativeWfp4Afp4Family() helper used by both the allocation site (getProfilerWorkspaces) and the consumption site (prepareQuantParams), so the two cannot drift apart again the way flashinfer-ai#3738 did. This also drops the mSM >= 100 guard that the allocation side had but the consumption side did not. Size quant_2 (fc1 weight scale factors) with fc1_out_size instead of inter_size, fixing a pre-existing under-allocation for gated activations that equally affected the FP8-activation (wfp4afp8) family. Parametrize test_moe_nvfp4 with use_autotune as regression coverage. AI-assisted (Claude Code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fc1 SF sizing Follow-up to #4080, which restored the NVFP4 quant-workspace sizing branch in getProfilerWorkspaces after the #3738 regression (issue #4003). Two gaps remain: Replace the ad-hoc is_nvfp4_quant predicate with an isNativeWfp4Afp4Family() helper used by both the allocation site (getProfilerWorkspaces) and the consumption site (prepareQuantParams), so the two cannot drift apart again the way #3738 did. This also drops the mSM >= 100 guard that the allocation side had but the consumption side did not. Size quant_2 (fc1 weight scale factors) with fc1_out_size instead of inter_size, fixing a pre-existing under-allocation for gated activations that equally affected the FP8-activation (wfp4afp8) family. Parametrize test_moe_nvfp4 with use_autotune as regression coverage. AI-assisted (Claude Code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 9ae8ba3)
…fc1 SF sizing Follow-up to #4080, which restored the NVFP4 quant-workspace sizing branch in getProfilerWorkspaces after the #3738 regression (issue #4003). Two gaps remain: Replace the ad-hoc is_nvfp4_quant predicate with an isNativeWfp4Afp4Family() helper used by both the allocation site (getProfilerWorkspaces) and the consumption site (prepareQuantParams), so the two cannot drift apart again the way #3738 did. This also drops the mSM >= 100 guard that the allocation side had but the consumption side did not. Size quant_2 (fc1 weight scale factors) with fc1_out_size instead of inter_size, fixing a pre-existing under-allocation for gated activations that equally affected the FP8-activation (wfp4afp8) family. Parametrize test_moe_nvfp4 with use_autotune as regression coverage. AI-assisted (Claude Code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 9ae8ba3)
…fc1 SF sizing (#4010) ## 📌 Description Autotuning a native NVFP4 (FP4 activations x FP4 weights) CUTLASS MoE crashed with `Assertion failed: quant_1 && quant_2 && quant_3 && quant_4 && quant_5 && quant_6` (#4003): after #3738, the gemm profiler only allocated its scratch quant-scale buffers for the FP8-activation FP4 flavor, so native NVFP4 got none. #4080 has since fixed that crash on main. This PR is rebased on top of it and adds what's still missing: - Replace the ad-hoc `is_nvfp4_quant` check with an `isNativeWfp4Afp4Family()` helper used by both the allocation site (`getProfilerWorkspaces`) and the consumption site (`prepareQuantParams`), so the two can't drift apart again. This also drops the `mSM >= 100` guard that the allocation side had but the consumption side didn't. - Size the fc1 weight scale-factor buffer (`quant_2`) with `fc1_out_size` instead of `inter_size`. Gated activations make fc1 output 2x `inter_size`, so the old size under-allocated. #4308 has since landed this sizing for the wfp4afp8 flavor; this PR applies it to native NVFP4 and merges the two sizing branches so they cannot diverge. - Add autotune coverage to `test_moe_nvfp4`, the regression test that would have caught #4003. Credit to @eugr for the proposed patch this builds on. ## 🔍 Related Issues Fixes #4003 (crash itself already fixed on main by #4080). Regressed by #3738. ## 🚀 Pull Request Checklist Thank you for contributing to FlashInfer! Before we review your pull request, please make sure the following items are complete. ### ✅ Pre-commit Checks - [x] I have installed `pre-commit` by running `pip install pre-commit` (or used your preferred method). - [x] I have installed the hooks with `pre-commit install`. - [x] I have run the hooks manually with `pre-commit run --all-files` and fixed any reported issues. ## 🧪 Tests - [x] Tests have been added or updated as needed. - [x] All tests are passing (`unittest`, etc.). `test_moe_nvfp4` is now parametrized with `use_autotune`, same approach as #3558. Pre-rebase on a GB10 (SM121): every autotune case died with the assertion without the fix, and all 48 cases passed with it. Post-rebase, re-ran gated and non-gated autotune cases plus a non-autotune case on an RTX 5080 (SM120), all passing. After rebasing onto current main (post-#4308), all 48 cases pass on an RTX 5080 (SM120). ## Reviewer Notes The predicate deliberately has no SM version check, unlike its wfp4afp8 sibling: it must exactly match the consuming branch in `prepareQuantParams`, which checks dtypes only. FP4 x FP4 doesn't exist below SM100 anyway. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved Native NVFP4 fused Mixture-of-Experts (MoE) execution by correcting workspace and scaling-factor handling. * Fixed NVFP4 behavior when autotuning is enabled, preventing related execution failures. * **Tests** * Expanded NVFP4 coverage to validate both autotuned and non-autotuned execution paths. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
📌 Description
This PR updates the Hopper CUTLASS grouped MoE backend for mixed low-precision
inputs. It improves the existing Hopper W4 grouped GEMM paths and adds a new
MXFP4 weight + FP8 activation path:
per-token FP8 activation quantization.
The MXFP4xFP8 path follows the high-level weight preprocessing and
dequantization idea used by Humming:
the MXFP4 weight scale is transformed into a bounded E8M0 exponent offset and
folded into the FP8 operand before MMA. FlashInfer keeps this flow integrated
with its fused MoE runtime, autotune, and CUTLASS extension backend.
The implementation keeps the changes inside FlashInfer's CUTLASS extension
layer rather than modifying vendored CUTLASS. The main changes are:
by using CUTLASS subbyte FP4 types.
scale fusion.
main GEMM kernel.
the profiled
TileK.schedulers.
the pre-MMA E8M0 scale-fusion path.
path.
checked for correctness before being selected.
paths.
🔍 Related Issues
N/A
Performance
Benchmarks were run on NVIDIA H20 and H200 GPUs using MoE workload configurations matching DeepSeek-V3 (256 experts, top-8, hidden size 7168, intermediate size 2048) and DeepSeek-V4-Pro (384 experts, top-6, hidden size 7168, intermediate size 3072).
https://huggingface.co/deepseek-ai/DeepSeek-V3
https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro
The plots below summarize the PR performance results. Raw benchmark tables are intentionally not embedded in this PR text.
INT4xFP8 and MXFP4xBF16 vs Same-Path Baseline
The existing INT4xFP8 and MXFP4xBF16 paths generally improve over the same-path
baseline after the Hopper mixed-input backend changes.
| Current fused MoE speedup vs same-path baseline


Current Paths vs Current FP8 Per-Tensor Baseline
This compares the current fused MoE mixed-input paths against the current FP8
per-tensor path.
| Current fused MoE path speedup vs current FP8 per-tensor


MXFP4xFP8 Pre-MMA Scale Path vs Upstream Humming (2026/06/23 commit f6241bb)
The pre-MMA E8M0 scale-fusion path is compared against upstream Humming GEMM
timings. Positive values mean FlashInfer is faster.
| DS4 FlashInfer Humming GEMM speedup vs upstream Humming


Implementation Notes
CUDA native FP4 as a toolkit-dependent feature instead of a Hopper
requirement.
This avoids doing grouped tile lookup and descriptor updates in the main GEMM
hot path.
selected
TileK, which lets autotune choose among multipleTileKvalueswithout requiring multiple prepacked weight-scale layouts.
WGMMA and applies the activation token scale in the epilogue.
preprocessing/dequantization flow:
https://github.com/inclusionAI/humming
scales while expanding routed MoE rows.
timing and selection.
🚀 Pull Request Checklist
Thank you for contributing to FlashInfer! Before we review your pull request,
please make sure the following items are complete.
✅ Pre-commit Checks
pre-commitby runningpip install pre-commit(or usedyour preferred method).
pre-commit install.pre-commit run --all-filesand fixedany reported issues.
🧪 Tests
were added.
during autotune.
Local validation focused on Hopper mixed-input MoE paths:
Reviewer Notes
csrc/nv_internal/tensorrt_llm/cutlass_extensions/; this PR does not modifyvendored CUTLASS under
3rdparty/.standard post-MMA scale paths because it changes where MXFP4 weight scaling is
applied.
the Humming approach for fusing E8M0 scale offsets into the FP8 MMA operand:
https://github.com/inclusionAI/humming
output before using their timing result for tactic selection.
in each plot title.
Summary by CodeRabbit
Release Notes
use_wfp4afp8_hummingand optionalprofile_idsto steer tactic selection.preprocess_moe_weights_for_sm90_mixed_gemm_humming.