Skip to content

[CUDA] Add fused decode routing, single-pass finalize, and PDL overlap for NVFP4 QMoE - #33366

Merged
Tianlei Wu (tianleiwu) merged 5 commits into
mainfrom
tlwu/20261014/nvfp4-qmoe-decode-route-pdl
Oct 20, 2026
Merged

Tianlei Wu (tianleiwu) merged 5 commits into
mainfrom
tlwu/20261014/nvfp4-qmoe-decode-route-pdl

Conversation

@tianleiwu

Copy link
Copy Markdown
Contributor

Stacked on #33354 (and #33094). The base branch is tlwu/20261013/nvfp4-qmoe-decode-gemv, so the diff below is this change only. Once #33094 and #33354 merge, this PR will be retargeted to main. Opened as a draft until then.

Description

Optimizes the non-GEMV kernels and inter-kernel launch serialization along the offline K-packed (weights_prepacked=1) NVFP4 QMoE decode path on SM90+:

  1. Single-CTA fused decode routing (FusedSoftmaxTopKMapsKernel): Replaces LaunchSoftmaxTopK + fusedBuildExpertMapsSortFirstToken in a single CTA launch for num_rows <= 4, num_experts == 512, and k in {1, 2, 4, 6, 8, 10}. One warp per row reproduces the SoftmaxTopKMergeKernel<T, 128, 4> reduction trees operation for operation and emits the (expert, token)-sorted expert maps bit-identically (ORT_FP4_GEMV_FUSED_ROUTE=0 disables).
  2. Single-pass finalizeMoeRoutingKernel: Sizes the block to one thread per 128-bit column vector (up to 1024 threads instead of a fixed 256) so hidden dimensions up to 4096 (such as Qwen3.8's hidden=2560) reduce in a single pass, and issues batched loads of up to 16 expert rows together while preserving top-k accumulation order (7.7 us -> 4.0 us per layer on H200).
  3. Programmatic dependent launch (PDL) overlap of FC1 -> FC2 -> finalize: Launches FC2 and finalizeMoeRoutingKernel with cudaLaunchAttributeProgrammaticStreamSerialization (ORT_FP4_GEMV_PDL=0 disables; ORT_FP4_GEMV_FC1_PDL=1 optionally enables PDL for FC1 behind fused routing). FC1 triggers dependents at start, FC2 prefetches its first batch of weights/scales before waiting on FC1's output, and finalizeMoeRoutingKernel stages routing metadata into shared memory before waiting on FC2's output (~2.2 us saved per layer on H200, preserved under CUDA Graph capture).

All three optimizations preserve exact floating-point evaluation order and produce bit-identical outputs.

Summary of Changes

File Change
onnxruntime/contrib_ops/cuda/moe/qmoe_fused_route.cuh New FusedSoftmaxTopKMapsKernel single-CTA fused softmax + top-k + expert-map kernel (E=512, rows <= 4, k in {1,2,4,6,8,10})
onnxruntime/contrib_ops/cuda/moe/qmoe_kernels.h / .cu LaunchFusedRouteMaps declarations and launcher
onnxruntime/contrib_ops/cuda/llm/moe_gemm/moe_kernels.cu / moe_util_kernels.h Single-pass finalizeMoeRoutingKernel (blockDim.x up to 1024, FINALIZE_LOAD_BATCH=16) and early_routing_loads PDL support
onnxruntime/contrib_ops/cuda/llm/moe_gemm/moe_gemv_fp4.h / .cu griddepcontrol.wait / griddepcontrol.launch_dependents in MoeGemvFp4DecodeKernel and programmatic_dependent_launch attribute in LaunchMoeGemvFp4RawNPacked (ORT_FP4_GEMV_PDL)
onnxruntime/contrib_ops/cuda/moe/moe_quantization.cc Wire LaunchFusedRouteMaps and fc2_finalize_pdl into QMoE::ComputeInternal
docs/contrib_ops/cuda/moe_qmoe.md Document ORT_FP4_GEMV_FUSED_ROUTE, ORT_FP4_GEMV_PDL, and ORT_FP4_GEMV_FC1_PDL

Testing

Validated on NVIDIA H200 (sm_90a, CUDA 13.0):

  • python -m pytest onnxruntime/test/python/transformers/test_qmoe_nvfp4_cuda.py: 178 passed, 8 skipped, 0 failed (including test_nvfp4_offline_row_major_decode_gemv_bit_exact for FP16 and BF16).
  • End-to-end Qwen3.8-Flash-Next NVFP4 (v12b 3-session CUDA-graph + MTP engine, 48 MoE layers + 1 MTP layer):
    • Bit-exactness: engine_gate.py --prefill steps passes 6/6 with 100% identical token sequences (256/256 tokens on text2048 and prose2048) compared against the baseline without fused routing / PDL.
    • End-to-end decode speed (bench_engine_mtp.py, 256 generated tokens, median of 3 runs):
Context Before (#33354 only) ms/cycle With this PR ms/cycle Cycle speedup Decode throughput (tok/s)
2,048 8.274 ms 7.657 ms -0.617 ms (-7.5%) 244.98 tok/s (was 227.64)
8,192 8.164 ms 7.836 ms -0.328 ms (-4.0%) 232.49 tok/s (was 225.81)
32,768 8.352 ms 8.027 ms -0.325 ms (-3.9%) 237.25 tok/s (was 229.97)
  • lintrunner on all touched files: no issues.

@tianleiwu
Tianlei Wu (tianleiwu) force-pushed the tlwu/20261013/nvfp4-qmoe-decode-gemv branch from 4b4e16e to 60cd1fb Compare October 16, 2026 05:32
@tianleiwu
Tianlei Wu (tianleiwu) force-pushed the tlwu/20261014/nvfp4-qmoe-decode-route-pdl branch from 3aa261a to a35a504 Compare October 16, 2026 21:51
@tianleiwu
Tianlei Wu (tianleiwu) force-pushed the tlwu/20261013/nvfp4-qmoe-decode-gemv branch from 99626e6 to a20b5c5 Compare October 19, 2026 06:07
Base automatically changed from tlwu/20261013/nvfp4-qmoe-decode-gemv to main October 19, 2026 08:15
@tianleiwu
Tianlei Wu (tianleiwu) force-pushed the tlwu/20261014/nvfp4-qmoe-decode-route-pdl branch from 5e08544 to 26ab25a Compare October 19, 2026 08:17
…p for NVFP4 QMoE

For the offline K-packed (weights_prepacked=1) NVFP4 QMoE decode path on SM90+:
- Fused softmax + top-k + expert-map kernel (FusedSoftmaxTopKMapsKernel) for E=512, num_rows <= 4, k in {1, 2, 4, 6, 8, 10}, reproducing LaunchSoftmaxTopK + fusedBuildExpertMapsSortFirstToken bit-exactly in a single CTA launch (ORT_FP4_GEMV_FUSED_ROUTE=0 disables).
- Single-pass finalizeMoeRoutingKernel with one thread per 128-bit column vector (up to 1024 threads) and batched loads of up to 16 expert rows while preserving top-k accumulation order.
- Programmatic dependent launch (PDL) overlap of FC1 -> FC2 -> finalize so FC2 prefetches weights/scales and finalize loads routing metadata before waiting on the producer grid (ORT_FP4_GEMV_PDL=0 disables; ORT_FP4_GEMV_FC1_PDL=1 optionally enables PDL for FC1 behind fused routing).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Guard FusedSoftmaxTopKMapsKernel inline PTX instructions only for __CUDA_ARCH__ >= 900 (with a warp-shuffle fallback for __reduce_max_sync below SM80) and verify compiled SM90 PTX/SASS via cudaFuncGetAttributes in LaunchFusedRouteMapsT.
- Gate fc2_finalize_pdl on sm_ >= 90 (and disable PDL inside the autotune timing loop), and verify SM90 PTX/SASS in finalizeMoeRoutingKernelLauncher before enabling programmaticStreamSerializationAllowed.
- Clamp finalizeMoeRoutingKernel blockDim to the compiled specialization's register-limited maxThreadsPerBlock and check cudaLaunchKernelEx return status.
- Extend test_nvfp4_offline_row_major_decode_gemv_bit_exact to cover 512-expert fused routing, PDL opt-out, and wide hidden_size=5120 finalize.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tianleiwu
Tianlei Wu (tianleiwu) force-pushed the tlwu/20261014/nvfp4-qmoe-decode-route-pdl branch from 26ab25a to c9e3dcc Compare October 19, 2026 08:58
@tianleiwu
Tianlei Wu (tianleiwu) marked this pull request as ready for review October 19, 2026 17:52
Copilot AI balanced review requested due to automatic review settings October 19, 2026 17:52

Copilot AI 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.

🟡 Changes recommended

Incorrect native-scale test expectations and host-only code entering the standalone CUDA benchmark source cause regressions.

3 open issues
What changed in this PR

Optimizes ONNX Runtime’s offline K-packed NVFP4 QMoE decode path with fused routing and overlapping CUDA execution.

Changes:

  • Combines softmax, top-k selection, and expert-map construction.
  • Batches finalize loads and adds programmatic dependent launch overlap.
  • Extends parity cases and documents optimization controls.
File Description
onnxruntime/​test/​python/​transformers/​test_qmoe_nvfp4_cuda.py Extends parity cases and kernel profiling.
onnxruntime/​contrib_ops/​cuda/​moe/​qmoe_kernels.h Declares fused-routing launchers.
onnxruntime/​contrib_ops/​cuda/​moe/​qmoe_kernels.cu Implements fused-routing dispatch.
onnxruntime/​contrib_ops/​cuda/​moe/​qmoe_fused_route.cuh Adds the fused routing kernel.
onnxruntime/​contrib_ops/​cuda/​moe/​moe_quantization.cc Integrates routing and launch overlap.
onnxruntime/​contrib_ops/​cuda/​llm/​moe_gemm/​moe_util_kernels.h Extends the finalize launch interface.
onnxruntime/​contrib_ops/​cuda/​llm/​moe_gemm/​moe_kernels.cu Optimizes finalize loads and block sizing.
onnxruntime/​contrib_ops/​cuda/​llm/​moe_gemm/​moe_gemv_fp4.h Exposes dependent-launch controls.
onnxruntime/​contrib_ops/​cuda/​llm/​moe_gemm/​moe_gemv_fp4.cu Adds decode synchronization and launch attributes.
docs/​contrib_ops/​cuda/​moe_qmoe.md Documents routing and overlap switches.

Review effort: Balanced

- Explicitly guard griddepcontrol PTX instructions inside MoeGemvFp4DecodeKernel with __CUDA_ARCH__ >= 900 and add memory clobber to finalizeMoeRoutingKernel griddepcontrol.wait.
- Check SM90+ device capability, CUDA driver >= 12.0, and ptxVersion >= 90 && binaryVersion >= 90 in QuerySm90DecodeKernelCaps, IsFusedRouteKernelSm90Available, and getFinalizeKernelLaunchCaps while clearing sticky CUDA errors on query failure.
- Avoid attaching cudaLaunchAttributeProgrammaticStreamSerialization when PDL is disabled, fall back to a regular launch if cudaLaunchKernelEx rejects the attribute, and gate FC2/finalize PDL on fc1_used_decode/fc2_used_decode.
- Move Fp4MoeGemvUsePdl outside the standalone NVFP4 decode device-code slice and extend test_benchmark_qwen38_mtp_kernels.py.
- Fix FP16 native-scale FC1 dispatch expectation for hidden_size <= 1024 and add 512-expert FC1 PDL + CUDA Graph test coverage in test_qmoe_nvfp4_cuda.py.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…uild dir

Resolve onnxruntime source root across parent directories, CI workspace environment variables (GITHUB_WORKSPACE/BUILD_SOURCESDIRECTORY), and cwd ancestors when benchmark_qwen38_mtp_kernels.py is copied into the CMake build tree.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI 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.

🔵 Needs a closer look

Default-on CUDA synchronization changes warrant maintainer review, and direct fused-routing parity coverage remains incomplete.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Add fused/unfused routing parity tests for both normalization modes

onnxruntime/​contrib_ops/​cuda/​moe/​qmoe_fused_route.cuh:178

Please add direct fused-versus-unfused routing parity tests. The NVFP4 model builder hardcodes normalize_routing_weights=1 (test_qmoe_nvfp4_cuda.py:221), and the standalone routing tests call only LaunchSoftmaxTopK. Thus normalize_scales=false is uncovered, and the end-to-end FP16/BF16 comparisons can hide differences in FP32 routing scales through final rounding. Extend softmax_topk_kernel_test.cc to compare FP32 scales bitwise, selected indices, both row maps, expert IDs, and expert offsets against LaunchSoftmaxTopK plus the existing map builder. Cover both normalization modes, FP16/BF16 inputs, supported row/k combinations (including four rows), and tied logits.

3 resolved since last review

Review effort: Balanced

@tianleiwu
Tianlei Wu (tianleiwu) merged commit 942356f into main Oct 20, 2026
@tianleiwu
Tianlei Wu (tianleiwu) deleted the tlwu/20261014/nvfp4-qmoe-decode-route-pdl branch October 20, 2026 23:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants