Skip to content

[TRTLLM-12214][perf] DeepGemmFusedMoE: fuse masked gather + finalize-scale into one Triton kernel - #14592

Merged
xwang233 merged 3 commits into
NVIDIA:mainfrom
xwang233:pr/trtllm-12214-moe-fused-gather-finalize
Jun 4, 2026
Merged

[TRTLLM-12214][perf] DeepGemmFusedMoE: fuse masked gather + finalize-scale into one Triton kernel#14592
xwang233 merged 3 commits into
NVIDIA:mainfrom
xwang233:pr/trtllm-12214-moe-fused-gather-finalize

Conversation

@xwang233

@xwang233 xwang233 commented May 26, 2026

Copy link
Copy Markdown
Collaborator

[TRTLLM-12214][perf] DeepGemmFusedMoE: fuse masked gather + finalize-scale into one Triton kernel

Summary

The post-GEMM pipeline in DeepGemmFusedMoE was:

triton_masked_index_gather    (writes permuted_data buffer: 25.6 MB at bs=32 × topk=8 × hidden=7168 × bf16)
  → torch.ops.trtllm.moe_finalize_scale_op
        (reads permuted_data, applies routing weights, accumulates to output)

This PR fuses both steps into one Triton kernel triton_fused_gather_finalize that reads h3 (the GEMM output) directly via the reverse-permute map:

permuted_row     = unpermuted_to_permuted[token + k * num_rows]
(expert_idx, col_idx) = (token_to_expert_map[permuted_row],
                         permuted_row − expert_first_token_offset[expert_idx])

applies token_final_scales in the same k = 0..topk-1 order per token, and accumulates in fp32 (matching the C++ finalizeMoeRoutingKernel precision used by moe_finalize_scale_op).

The 25.6 MB intermediate buffer is eliminated; total HBM traffic for this pair drops from ~80 MB to ~28.8 MB (−64%) at the model anchor.

Microbenchmark (B200, sm_100)

triton.testing.do_bench(warmup=50, rep=300) over 40 cells: num_source_tokens ∈ {1, 4, 16, 32, 64} × topK ∈ {4, 8} × experts ∈ {64, 128} × hidden ∈ {4096, 7168}.

Baseline is triton_masked_index_gather + moe_finalize_scale_op (sum of the two timed individually). Patched is the new fused kernel.

Shape Baseline (µs) Fused (µs) Δ
N=1, K=4, E=64, H=4096 20.29 10.27 −49.4%
anchor N=32, K=8, E=128, H=4096 27.71 16.38 −40.9%
anchor N=32, K=8, E=128, H=7168 38.94 16.38 −57.9%
N=64, K=8, E=128, H=7168 41.79 16.38 −60.8%
N=1, K=8, E=128, H=7168 (best) 36.80 12.35 −66.4%
Aggregate (40 cells) Count
Regress (>+2%) 0 / 40
Win (<−2%) 40 / 40
Neutral 0 / 40
Median Δ −54.9%
Best Δ −66.4%
Worst Δ −40.9%

Zero regressing cells across the full sweep. The anchor measures −40.9% at H=4096 and −57.9% at H=7168.

Full-bench context

Qwen3-235B-A22B-FP8 + EAGLE3 dyntree, 4×GB200 NVL72, TP=4, EP=1, bs=32, mtbench-32, output_tokens=512, --ignore_eos, all PR-1..PR-3 stacked:

Baseline Patched Δ
Replay median (µs) 35 887 32 587 −9.2%
End-to-end TPS 1 447 1 583 +9.4%
Acceptance rate 0.555 +0.0038 neutral
Output length 1465 −0.0031 neutral

This is the largest single contribution of the three-PR series (~93 invocations per replay × ~16 µs saved per call).

Risk callouts

  • moe_finalize_scale_op also handles enable_alltoall=True and other paths the new kernel does not. DeepGemmFusedMoE.forward passes enable_alltoall=False inline today; if alltoall is ever wired onto the DeepGemm path, this fusion must back out or fork.
  • Grid shape: 2D grid (num_rows, ceil_div(unpadded_hidden, 1024)) redundantly loads topk metadata once per hidden-chunk program (7× at hidden=7168 / BLOCK_SIZE=1024); L1 is expected to absorb this. A 1D-per-row grid is the obvious fallback if NCU shows poor L1 hit rate.
  • Bit-exact by static equivalence: same h3 addresses (proven by the reverse-permute math above), same token_final_scales applied in the same k order, same fp32 accumulator precision as the C++ finalizeMoeRoutingKernel. No new index math hazards.

Files changed

  • tensorrt_llm/_torch/modules/fused_moe/fused_moe_deepgemm.py (single file)
    • New fused_gather_finalize_kernel JIT
    • New triton_fused_gather_finalize launcher
    • Replace the triton_masked_index_gather(...) + torch.ops.trtllm.moe_finalize_scale_op(...) call pair with a single call to the new launcher

No C++ touch. Independent of PR-1 and PR-2 at the code level.

Test plan

  • DeepGemm MoE unit tests for the new fused kernel (bit-exact vs old pair across the 40-cell shape sweep)
  • Qwen3-235B-A22B-FP8 + DeepSeek-V3 integration accuracy check (no regression on AR or OSL)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added mixture-of-experts computation path combining gather and finalize operations into a single kernel.
  • Tests

    • Added comprehensive test suite for the mixture-of-experts computation validating consistency across H100, B200, and B300 GPU architectures, ensuring results match the previous implementation path.

…scale into one Triton kernel

The post-GEMM pipeline in DeepGemmFusedMoE was:
    triton_masked_index_gather (writes permuted_data buffer, 25.6 MB at
                                bs=32 x topk=8 x hidden=7168 x bf16)
      -> torch.ops.trtllm.moe_finalize_scale_op (reads permuted_data,
                                                  applies routing weights,
                                                  accumulates to output)

This commit fuses both steps into a single Triton kernel
triton_fused_gather_finalize that:
  * reads h3 (the GEMM output) directly via the reverse-permute map
    (permuted_row = unpermuted_to_permuted[token + k*num_rows], then
     (expert_idx, col_idx) = (token_to_expert_map[permuted_row],
                              permuted_row - expert_first_token_offset[expert_idx]))
  * applies token_final_scales in the same k=0..topk-1 order per token,
  * accumulates in fp32 (matching the C++ finalizeMoeRoutingKernel
    precision used by moe_finalize_scale_op).

The 25.6 MB intermediate buffer is eliminated; total HBM traffic for
this pair drops from ~80 MB to ~28.8 MB (-64%) at the model anchor.

Microbench on B200 (sm_100, do_bench warmup=50 rep=300), 40 cells
(num_source_tokens in {1,4,16,32,64} x topK in {4,8} x experts in
{64,128} x hidden in {4096,7168}):

  anchor (N=32, K=8, E=128, H=4096): 27.71 us -> 16.38 us (-40.9%)
  anchor (N=32, K=8, E=128, H=7168): 38.94 us -> 16.38 us (-57.9%)
  small  (N=1,  K=4, E=64,  H=4096): 20.29 us -> 10.27 us (-49.4%)
  large  (N=64, K=8, E=128, H=7168): 41.79 us -> 16.38 us (-60.8%)

  aggregate: 40/40 win, median -54.9%, best -66.4%, worst -40.9%.
  Zero regressing cells across the full sweep.

Full-bench context (Qwen3-235B-A22B-FP8 + EAGLE3 dyntree, 4xGB200 NVL72,
TP=4, bs=32, mtbench-32): replay median 35887 us -> 32587 us (-9.2%),
throughput 1447 -> 1583 tok/s (+9.4%), accuracy neutral
(acceptance-rate +0.0038, output-length -0.0031), all PR-1..PR-3 stacked.

Risk callouts for reviewers:
* moe_finalize_scale_op also handles enable_alltoall=True and other
  paths the new kernel does not. DeepGemmFusedMoE.forward passes
  enable_alltoall=False inline today; if alltoall is ever wired on
  the DeepGemm path, this fusion must back out or fork.
* The 2D grid (num_rows, ceil_div(unpadded_hidden, 1024)) redundantly
  loads topk metadata once per hidden-chunk program (7x at hidden=7168
  / BLOCK_SIZE=1024); L1 is expected to absorb this. A 1D-per-row
  grid is the obvious fallback if NCU shows poor L1 hit rate.
* Single-file change, no C++ touch. Independent of PR-1 and PR-2 at
  the code level.

Signed-off-by: Xiao Wang <24860335+xwang233@users.noreply.github.com>
@xwang233

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #50371 [ run ] triggered by Bot. Commit: eec674a Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #50371 [ run ] completed with state SUCCESS. Commit: eec674a
/LLM/main/L0_MergeRequest_PR pipeline #39898 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

…nalize

Adds a differential correctness test that feeds the same self-consistent
permutation maps (built by the real moe_permute_op) to both the old
post-GEMM pair (triton_masked_index_gather + moe_finalize_scale_op) and the
new fused triton_fused_gather_finalize kernel, then asserts the outputs match.

Both paths accumulate k=0..topk-1 in order in an fp32 accumulator with the same
round-to-nearest fp32->bf16 final cast, so the result is bit-identical modulo
FMA-contraction differences between the Triton and nvcc backends. The committed
gate is a tight tolerance (robust across GPU arch); the test also reports
whether the result is bitwise equal.

Covers the model anchor (32x8x4096x128), hidden=7168, single-token decode,
medium-batch, and a top_k=4 shape. Gated to SM90+ (no deep_gemm GEMM is
invoked, only the permute/gather/finalize kernels).

Signed-off-by: Xiao Wang <24860335+xwang233@users.noreply.github.com>
@xwang233
xwang233 force-pushed the pr/trtllm-12214-moe-fused-gather-finalize branch from 81e8dcf to c5840a8 Compare May 28, 2026 22:18
@xwang233

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #50900 [ run ] triggered by Bot. Commit: c5840a8 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #50900 [ run ] completed with state SUCCESS. Commit: c5840a8
/LLM/main/L0_MergeRequest_PR pipeline #40365 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@xwang233

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #51105 [ run ] triggered by Bot. Commit: c5840a8 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #51105 [ run ] completed with state SUCCESS. Commit: c5840a8
/LLM/main/L0_MergeRequest_PR pipeline #40543 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

…est lists

The new differential-correctness test
tests/unittest/_torch/modules/fused_moe/test_deepgemm_fused_gather_finalize.py
was added in this PR but not picked up by CI because it lives in a new
file not referenced by any test-db list. Register it in the SM90+
single-GPU lists (l0_h100, l0_b200, l0_b300) alongside the other MoE
component tests so the bit-exactness check actually runs in pre-merge.

Signed-off-by: Xiao Wang <24860335+xwang233@users.noreply.github.com>
@xwang233

xwang233 commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #51398 [ run ] triggered by Bot. Commit: a327746 Link to invocation

@xwang233
xwang233 marked this pull request as ready for review June 1, 2026 18:20
@xwang233
xwang233 requested a review from a team as a code owner June 1, 2026 18:20
@xwang233
xwang233 requested review from mikeiovine and xxi-nv June 1, 2026 18:20
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a fused Triton kernel that combines MoE gather and finalize operations into a single operation. The kernel reads expert GEMM output directly, applies per-token routing weights, and writes final hidden states. The prior separate gather and finalize steps are removed from run_moe. A comprehensive test suite validates the fused path against the unfused baseline and is integrated across B200, B300, and H100 architectures.

Changes

Fused MoE gather-finalize

Layer / File(s) Summary
Fused gather-finalize kernel and run_moe integration
tensorrt_llm/_torch/modules/fused_moe/fused_moe_deepgemm.py
New Triton kernel fused_gather_finalize_kernel and Python wrapper perform combined gather and finalize in a single operation, reverse-mapping from routing metadata to expert outputs and accumulating weighted results. DeepGemmFusedMoE.run_moe now calls the fused kernel directly instead of the separate gather and finalize path.
Correctness validation test suite
tests/unittest/_torch/modules/fused_moe/test_deepgemm_fused_gather_finalize.py
New CUDA SM90+-gated test module compares fused kernel output against unfused baseline (gather + moe_finalize_scale_op). Test generates consistent top-k routing with per-token scales, synthesizes expert outputs, constructs permutation maps, and validates shape/dtype and numeric agreement within rtol=1e-2/atol=1e-2.
Test integration across GPU architectures
tests/integration/test_lists/test-db/l0_b200.yml, l0_b300.yml, l0_h100.yml
New test test_deepgemm_fused_gather_finalize.py added to pre-merge test suites for B200, B300, and H100 GPUs under existing stage, backend, and hardware constraints.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% 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 and specifically describes the main optimization: fusing two Triton kernels into one for DeepGemmFusedMoE, with the perf tag indicating performance focus.
Description check ✅ Passed The description comprehensively covers the problem statement, solution design, performance metrics, test coverage, and risk callouts; all template sections are adequately addressed.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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

🧹 Nitpick comments (2)
tests/integration/test_lists/test-db/l0_h100.yml (1)

38-38: QA test-list follow-up is unnecessary for this change.

This PR wires a unit test into existing pre-merge test-db suites; no tests/integration/defs/ integration-test addition was made, so tests/integration/test_lists/qa/ updates are optional/unnecessary here.

🤖 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/integration/test_lists/test-db/l0_h100.yml` at line 38, The QA
test-list follow-up is not needed because this change only wires an existing
unit test
(unittest/_torch/modules/fused_moe/test_deepgemm_fused_gather_finalize.py) into
the pre-merge test-db suite; remove any added or modified entries under
tests/integration/test_lists/qa/ (or avoid creating a new QA integration-test
def) and keep only the test-db list update (e.g., in
tests/integration/test_lists/test-db/l0_h100.yml) so no qa/defs changes are
committed.
tests/unittest/_torch/modules/fused_moe/test_deepgemm_fused_gather_finalize.py (1)

71-85: ⚡ Quick win

Add an explicit return type to _make_routing.

Please annotate the helper return type (e.g., tuple[torch.Tensor, torch.Tensor]) to satisfy the repo’s typing rule for functions.

As per coding guidelines: "Static type checking with mypy is opt-in by submodule; always annotate functions with return types (use None if function does not return)".

🤖 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/unittest/_torch/modules/fused_moe/test_deepgemm_fused_gather_finalize.py`
around lines 71 - 85, Add an explicit return type annotation to the helper
function _make_routing: change its signature to declare it returns
tuple[torch.Tensor, torch.Tensor] (or Tuple[torch.Tensor, torch.Tensor] if you
prefer typing.Tuple), so the signature references torch.Tensor for both
elements; no functional changes required inside token_selected_experts and
token_final_scales — just annotate the def _make_routing(...) ->
tuple[torch.Tensor, torch.Tensor] (and add an import from typing if using
Tuple).
🤖 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.

Nitpick comments:
In `@tests/integration/test_lists/test-db/l0_h100.yml`:
- Line 38: The QA test-list follow-up is not needed because this change only
wires an existing unit test
(unittest/_torch/modules/fused_moe/test_deepgemm_fused_gather_finalize.py) into
the pre-merge test-db suite; remove any added or modified entries under
tests/integration/test_lists/qa/ (or avoid creating a new QA integration-test
def) and keep only the test-db list update (e.g., in
tests/integration/test_lists/test-db/l0_h100.yml) so no qa/defs changes are
committed.

In
`@tests/unittest/_torch/modules/fused_moe/test_deepgemm_fused_gather_finalize.py`:
- Around line 71-85: Add an explicit return type annotation to the helper
function _make_routing: change its signature to declare it returns
tuple[torch.Tensor, torch.Tensor] (or Tuple[torch.Tensor, torch.Tensor] if you
prefer typing.Tuple), so the signature references torch.Tensor for both
elements; no functional changes required inside token_selected_experts and
token_final_scales — just annotate the def _make_routing(...) ->
tuple[torch.Tensor, torch.Tensor] (and add an import from typing if using
Tuple).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c564588f-ff41-4919-9832-39033e4d75b7

📥 Commits

Reviewing files that changed from the base of the PR and between f6ba936 and a327746.

📒 Files selected for processing (5)
  • tensorrt_llm/_torch/modules/fused_moe/fused_moe_deepgemm.py
  • tests/integration/test_lists/test-db/l0_b200.yml
  • tests/integration/test_lists/test-db/l0_b300.yml
  • tests/integration/test_lists/test-db/l0_h100.yml
  • tests/unittest/_torch/modules/fused_moe/test_deepgemm_fused_gather_finalize.py

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #51398 [ run ] completed with state FAILURE. Commit: a327746
/LLM/main/L0_MergeRequest_PR pipeline #40808 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@xwang233

xwang233 commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #51442 [ run ] triggered by Bot. Commit: a327746 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #51442 [ run ] completed with state SUCCESS. Commit: a327746
/LLM/main/L0_MergeRequest_PR pipeline #40851 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@xxi-nv
xxi-nv requested review from leslie-fang25 and removed request for xxi-nv June 3, 2026 06:19

@leslie-fang25 leslie-fang25 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

@xwang233

xwang233 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator Author

/bot skip --comment "All failures in pipeline 40851 are irrelevant. This PR's new test passed."

@xwang233
xwang233 enabled auto-merge (squash) June 4, 2026 16:43
@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #52110 Bot args parsing error: Failed to parse bot args

Link to invocation

@xwang233

xwang233 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator Author

/bot skip --comment "All failures in pipeline 40851 are irrelevant. This PRs new test passed."

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #52114 [ skip ] triggered by Bot. Commit: a327746 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #52114 [ skip ] completed with state SUCCESS. Commit: a327746
Skipping testing for commit a327746

Link to invocation

@xwang233
xwang233 merged commit 33b0a32 into NVIDIA:main Jun 4, 2026
12 checks passed
2ez4bz pushed a commit to 2ez4bz/TensorRT-LLM that referenced this pull request Jun 8, 2026
…scale into one Triton kernel (NVIDIA#14592)

Signed-off-by: Xiao Wang <24860335+xwang233@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants