Repository navigation
[MoE] Consolidate ungrouped + grouped gate/topk onto one Triton router (#26771) — faster than AOT on B200/H100/H200, at parity with flashinfer - #29771
Merged
Conversation
… router Part of the fused MoE gate/topk kernel consolidation (sgl-project#26771): converge the many per-model ungrouped gate kernels onto the single Triton `moe_fused_gate` router introduced in sgl-project#25835. - Extend the Triton router to support `softmax` scoring (softmax-prob weight, optional tanh softcapping, bias kept) alongside `sigmoid`/`sqrtsoftplus`. - Accept fp32/fp16/bf16 `scores` (the kernel already upcasts on load), so bf16 router logits no longer need a host-side fp32 upcast. - Route the CUDA `fused_topk` softmax/sigmoid paths through the Triton router, gated by `SGLANG_OPT_USE_JIT_KERNEL_FUSED_TOPK` (default on, `_is_cuda` only). AOT `topk_softmax`/`topk_sigmoid` remain the default for non-CUDA backends and the env-off fallback, so this is additive (no kernel deleted yet). Validated on B200: full test_moe_fused_gate.py (1055 cases) passes, including new softmax/sigmoid parity vs AOT sgl_kernel.topk_softmax/topk_sigmoid and a torch reference, fp32+bf16. CUDA-graph (kernel-only) benchmark: Triton is 1.14-1.51x faster than the AOT kernels on the softmax/sigmoid shapes (up to 6.9x where AOT topk_sigmoid degrades at N=512). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
BBuf
requested review from
DarkSharpness,
Edwardf0t1,
Fridge003,
HaiShaw,
HydraQYH,
Ying1123,
celve,
ch-wan,
ispobock,
merrymercy and
yuan-luo
as code owners
June 30, 2026 15:55
Contributor
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
Collaborator
Author
|
/tag-and-rerun-ci |
…Triton router Fold the second ungrouped CUDA sigmoid dispatch point (biased_grouped_topk_gpu's num_expert_group==1 / topk_group==1 branch, previously the jit grouped_topk.cuh kernel) onto the same unified Triton moe_fused_gate router. Validated on B200: matches biased_grouped_topk_impl at fp32-epsilon (fp32+bf16, renorm + scaling). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collaborator
Author
|
Added a second commit folding the |
The jit grouped_topk (grouped_topk.cuh + wrapper + test) was only reachable via the biased_grouped_topk_gpu num_expert_group==1 dispatch branch, which now routes to the unified Triton moe_fused_gate router. With that call site gone the kernel has no remaining importer, so remove the kernel, its Python wrapper, and its test (the ungrouped-sigmoid-vs-biased_grouped_topk_impl coverage is retained by test_moe_fused_gate.py). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r too The single-warp-per-row launch (grid=(M,)) was occupancy-bound at small N on Hopper (H100), losing ~25% to the hand-tuned AOT topk_softmax/topk_sigmoid. Tile a few rows per program (num_warps=1, BLOCK_M = max(1, min(4, 256//BLOCK_N))) so small-N launches stay occupancy-bound while the per-row top-k stays on cheap warp shuffles. Swept BLOCK_M x num_warps on H100/B200 — larger tiles / more warps regress (register pressure); this config wins across shapes. CUDA-graph kernel-only (softmax/sigmoid, topk=8): B200: 1.32-7.66x vs AOT (was 1.19-6.91x) H100: 1.00-6.09x vs AOT (was 0.74-6.27x -> no more small-N regression) Correctness unchanged: test_moe_fused_gate.py 1055 passed on B200 + H100 parity. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collaborator
Author
|
/tag-and-rerun-ci |
Extend the Triton router with grouped top-k (constexpr N_GROUP>1): per-group score = sum of the top-2 biased values, keep TOPK_GROUP groups (lowest group id wins ties), mask dropped groups' experts, then the existing top-k runs. Weight is still the bias-free activated score. N_GROUP==1 skips the block (ungrouped path unchanged). More general than the AOT grouped kernel (no experts/group<=32 cap). Validated on B200: grouped parity vs biased_grouped_topk_impl (fp32+bf16, incl. DeepSeek-V3 256/8/4/8) passes at <=1e-7; matches the AOT grouped moe_fused_gate in fp32 (bf16 differs only on borderline experts because the AOT scores in bf16 while the reference + this kernel upcast to fp32). CUDA-graph kernel-only bench vs the AOT grouped kernel: 1.22-1.75x on DeepSeek-V3 shapes. NOTE: this only adds the capability; the DeepSeek-V3 grouped dispatch is NOT switched to Triton here — that needs on-model e2e accuracy validation first (the bf16 borderline-selection difference vs the current AOT path). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collaborator
Author
|
/rerun-failed-ci |
Closed
4 of 5 tasks
…uter Add SGLANG_OPT_USE_JIT_KERNEL_GROUPED_TOPK (EnvBool, default False). When set, biased_grouped_topk_gpu routes DeepSeek-V3 grouped routing (num_expert_group>1) through the unified Triton moe_fused_gate instead of the flashinfer/AOT grouped kernels, completing the issue sgl-project#26771 consolidation for the grouped path. Off by default: flashinfer fused_topk_deepseek stays the production default. The Triton grouped path is validated bit-exact against it on DeepSeek-V3.2-5layer-bf16 e2e (52 grouped-MoE invocations across prefill+decode, identical output hash) and benchmarks at parity on H200 (0.99-1.05x vs flashinfer), so this is a consolidation escape hatch, not a perf flip. It also lifts the AOT experts/group <= 32 cap. Adds test_grouped_dispatch_flag_matches_default asserting the flag-on Triton path matches the flag-off default path for DeepSeek-V3 shapes (with and without a fused shared expert). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DarkSharpness
approved these changes
Jul 2, 2026
ispobock
approved these changes
Jul 3, 2026
3 tasks done
BBuf
added a commit
to BBuf/sglang
that referenced
this pull request
Jul 3, 2026
…nels Follow-up to the gate/topk consolidation (sgl-project#26771, on top of sgl-project#29771): retire the two remaining AOT gate kernels now that the unified Triton router covers their CUDA paths. (1) moe_fused_gate (moe_fused_gate.cu): its only live CUDA caller was the grouped `experts_per_group <= 32` fallback in biased_grouped_topk_gpu. Reroute that fallback to the Triton router (which has no MAX_VPT=32 cap and handles any experts-per-group), so flashinfer stays the grouped default and Triton is the fallback/opt-in. (2) kimi_k2_moe_fused_gate (kimi_k2_moe_fused_gate.cu): already dead on sgl-project#29771 -- Phase 1 routes the 384/256-expert single-group sigmoid path (Kimi-K2, MiMo-V2 Flash) through the Triton router; only a register_fake stub remained. Removed. The experimental-LoRA bf16 fast path uses a separate JIT kernel (trtllm_lora_temp), which is untouched. Deletes both .cu (+ the MUSA moe_fused_gate .cu, since sglang's MUSA path uses the external `mate` package, not this sgl_kernel op -- no runtime impact), their CUDA and MUSA pybind registrations, header decls, CMake/setup_musa build entries, the sgl_kernel Python wrappers + exports, and their unit tests + benchmarks. ROCm never registered these ops, so it is unaffected. Net -2454 lines. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ch-wan
added a commit
that referenced
this pull request
Jul 4, 2026
The unified Triton router (PR #29771, issue #26771) is on by default for ungrouped softmax/sigmoid MoE (SGLANG_OPT_USE_JIT_KERNEL_FUSED_TOPK=True), so qwen3-moe now routes topk through moe_fused_gate instead of the AOT topk_softmax kernel. On an all-NaN gating row (garbage padding positions during EAGLE3 + DP-attention decode CUDA graph capture) max_val is NaN, so `cur == max_val` is false on every lane and the top-k tie-break falls through to the `N + 1` sentinel. That out-of-range expert id (129 for 128 experts) overflows the moe_align cumsum buffer (size num_experts + 2) and faults count_and_sort_expert_tokens_kernel with CUDBG_EXCEPTION_WARP_ILLEGAL_ADDRESS. The same root cause also faults fused_moe_kernel / DeepEP dispatch and propagates NaN into the sampler logits in other MoE + spec/EP tests, all during graph capture / decode. Fix, entirely within the degenerate-row path (normal rows bit-identical): - Map NaN -> a finite floor before ranking, so degenerate rows select in-range and *distinct* experts 0..K-1 (the finite floor, unlike -inf, lets the winner-removal advance). - Zero NaN weights so degenerate rows don't feed NaN into topk_weights (the weights are computed from the raw scores before the ranking sanitize). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Chronostasys
pushed a commit
to MindLab-Research/sglang
that referenced
this pull request
Aug 24, 2026
sgl-project#26771) — faster than AOT on B200/H100/H200, at parity with flashinfer (sgl-project#29771) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Part of the fused MoE gate/topk kernel consolidation (#26771). SGLang has many per-model gate/topk kernels (
grouped_topk.cuh, AOTtopk_sigmoid/topk_softmax, the CUDA-JITmoe_fused_gate, flashinferfused_topk_deepseek, …). #25835 introduced one Tritonmoe_fused_gaterouter and made it the default for the ungroupedsqrtsoftpluspath.This PR converges the remaining paths onto that single router, in three phases:
softmax/sigmoidtopk and the ungroupedgrouped_topk(num_expert_group==1) branch through the Triton router, and optimize it to beat the hand-tuned AOT kernels on B200 + H100 + H200.n_group/topk_group, noaux_tc) to the same router and wire an opt-in dispatch, validated bit-exact e2e and at parity with flashinfer.Modifications
Triton router (
jit_kernel/moe_fused_gate.py):softmaxscoring (softmax-prob weight, bias kept, optionaltanhsoftcap) alongsidesigmoid/sqrtsoftplus.scores(kernel upcasts on load) — bf16 router logits no longer need a host-side fp32 upcast.N_GROUP>1): per-group score = sum of the top-2 biased values, keepTOPK_GROUPgroups (lowest group id wins ties), mask dropped groups, then the existing top-k runs.N_GROUP==1compiles the block out (ungrouped path unchanged). More general than the AOT grouped kernel — no experts-per-group ≤ 32 cap.num_warps=1,BLOCK_M = max(1, min(4, 256//BLOCK_N))). The old single-warp-per-rowgrid=(M,)was occupancy-bound at small N on Hopper and lost ~25% to AOT; packing a few rows per program only when N is small fixes it (sweptBLOCK_M × num_warpson H100/B200/H200 — this heuristic is the sweep-optimal point; larger tiles / more warps regress via register pressure).Dispatch (
srt/layers/moe/topk.py):fused_topksoftmax/sigmoid branches and thebiased_grouped_topk_gpunum_expert_group==1branch through the Triton router (gated bySGLANG_OPT_USE_JIT_KERNEL_FUSED_TOPK, default on,_is_cuda). AOT stays the default on non-CUDA backends and the env-off fallback.SGLANG_OPT_USE_JIT_KERNEL_GROUPED_TOPK(default off) routes the DeepSeek-V3 grouped path (num_expert_group>1) through the Triton router. Off by default so flashinferfused_topk_deepseekstays the tuned production default; the flag is a consolidation escape hatch (validated bit-exact + at parity, below), not a perf flip.Cleanup (Phase 2): delete the now-dead jit
grouped_topk(kernel + wrapper + test) — its only dispatch site now routes to the Triton router.Accuracy Tests
test/registered/jit/test_moe_fused_gate.py: 1063 passed on GPU (sigmoid/sqrtsoftplus/softmax × experts × topk × shared-experts × renorm × scale, fp32+bf16), including:topk_softmax/topk_sigmoid+ a torch reference (Phase 1);biased_grouped_topk_implfor DeepSeek-V3 shapes (256/8/4/8, 128/8/4/6, 256/4/2/8), fp32+bf16, ≤1e-3 (Phase 3 kernel);biased_grouped_topk_gpuwith the flag on (Triton) vs off (flashinfer/AOT default), with and without a fused shared expert (Phase 3 wiring).moe_fused_gatein fp32; bf16 differs only on borderline experts because AOT scores in bf16 while the reference + this router upcast to fp32.Model accuracy — end-to-end (Phase 3)
Three levels of accuracy evidence for the grouped path:
1. Full-model downstream eval — GSM8K + MMLU on DeepSeek-V3-0324 (671B fp8, the flagship sigmoid-grouped model this dispatch targets;
n_group=8,topk_group=4, 256 experts). Served withsglang.launch_server --tp 8on 8×B200; the two runs use the same build, differing only in theSGLANG_OPT_USE_JIT_KERNEL_GROUPED_TOPKenv var (flag verified live in the server process).sglang.test.run_eval:fused_topk_deepseek)MMLU is identical (2594/3000 both). GSM8K differs by 2 questions — the grouped Triton router and flashinfer diverge only on borderline-expert selection (fp32 vs bf16 scoring rounding), which occasionally flips a mid-CoT token; the effect is noise-level and here Triton scores marginally higher. No accuracy regression.
2. Kernel-level numerical precision (
test_moe_fused_gate.py, on GPU): the grouped Triton router vs the definitionalbiased_grouped_topk_impl— routed-expert selection exact and weights max abs diff ≤ 1e-7 in fp32 (DeepSeek-V3 256/8/4/8, 128/8/4/6, 256/4/2/8). In bf16 selection is identical except on borderline experts, and only because the AOT kernel scores in bf16 while the reference + this router upcast to fp32. Grouped-dispatch parity (flag on vs off, ±fused shared expert) matches to ≤ 1e-3.3. Bit-exact end-to-end output — DeepSeek-V3.2-5layer-bf16 (real DSA + grouped MoE: 256 experts,
n_group=8,topk_group=4, sigmoid), greedy generation on fixed prompts, 1×H200:fused_topk_deepseek)5518026aa2a27596SGLANG_OPT_USE_JIT_KERNEL_GROUPED_TOPK=1(Triton)5518026aa2a27596Every generated token is bit-identical across the two dispatches (a stderr marker confirmed the Triton grouped branch fired 52× across prefill + decode, ruling out a silent no-op).
Speed Tests and Profiling
CUDA-graph, kernel-only replay (production runs the gate under graphs).
Phase 1 — ungrouped, Triton vs AOT (Triton ≥ AOT on every shape on B200 + H100; H200 5/6, see note):
B200 (Blackwell):
H100 (Hopper):
H200 (Hopper):
* The single 0.96x is a bench artifact, not a kernel regression: the Triton wrapper allocates its two output tensors inside the captured graph while the AOT bench pre-allocates them. Kernel-only at that shape the Triton router is 1.13x (
BLOCK_M=2, num_warps=1— the heuristic's choice, confirmed sweep-optimal on H200). Production allocates identically on both paths.Phase 3 — grouped, Triton vs the real defaults (DeepSeek-V3 256/8/4/8, H200; flashinfer
fused_topk_deepseekis the current production default):Grouped Triton is at parity with flashinfer (0.99–1.05x) and generally beats AOT — i.e. no perf loss vs the actual production default. Combined with the bit-exact e2e result, this is why the grouped dispatch is wired as an opt-in flag rather than deferred to a follow-up.
Follow-ups (#26771, separate PRs)
.cuonce XPU/HIP/MUSA/CPU migrate; MUSA decision.Checklist
🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ❌ Run #28581806799
Latest PR Test (Extra): ❌ Run #28581806410