MiniMax-M3: Triton split-K router GEMV with in-kernel fixup - #36557
Merged
Merged
Conversation
zcnrex
requested review from
BBuf,
DarkSharpness,
HaiShaw,
HydraQYH,
celve and
yuan-luo
as code owners
August 26, 2026 19:41
The MoE router gate is a skinny GEMV: [M, K] bf16 x [N, K] bf16 -> [M, N] fp32 with N ~ 128 experts and M <= 64 at decode. hipblaslt's solutions for this shape run far off roofline on gfx950, so route it to a Triton split-K kernel that reads the gate weight once at near-bandwidth. Per-M-bucket configs (BLOCK_K 512, BLOCK_N chosen by batch size) cover M <= 8 / 16 / 32 / 64. The split-K reduction runs inside the same kernel as a last-CTA fixup with a self-cleaning counter, so neither a zero-init nor a separate reduce launch is needed -- each extra launch costs ~2us on gfx950. Split-K reassociates the fp32 accumulation, so the router logits are NOT bit-identical to the torch.mm path; an accuracy gate is required. The call site is guarded by router_gemv_supported(), which falls back to the existing torch.mm path for any unsupported dtype, stride, or shape. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The split-K configs are gfx950 tunings and the kernel was only validated there; router_gemv_supported() previously matched any CUDA-type device, NVIDIA included. Guard the model-side import so platforms without Triton never import the module. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GbK4exGWvsmPdMf3kmBYW4
kevin-mii
force-pushed
the
m3-router-gemv-splitk
branch
from
August 31, 2026 20:10
f743cf5 to
f45f716
Compare
zcnrex
enabled auto-merge (squash)
September 3, 2026 17:47
auto-merge was automatically disabled
September 4, 2026 10:03
Branch protection rule check failed
2 tasks
kevin-mii
pushed a commit
to zcnrex/sglang
that referenced
this pull request
Sep 10, 2026
sgl-project#36557 landed `_is_gfx95_supported = _is_hip and is_gfx95_supported()`. Took main's guarded form and gave the new gfx942 constant the same shape, so both arch probes short-circuit off HIP identically instead of one guarding and one not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mqhc2020
pushed a commit
to mqhc2020/sglang
that referenced
this pull request
Sep 15, 2026
…ect#36557) Co-authored-by: Kevin Mi <45493463+kevin-mii@users.noreply.github.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.
MiniMax-M3: Triton split-K router GEMV with in-kernel split-K fixup
The MiniMax-M3 MoE router gate is a skinny GEMV —
[M, K]bf16 x[N, K]bf16 ->[M, N]fp32, withN ~ 128experts andM <= 64at decode — and the vendor BLAS solutions picked for that shape run far off the memory roofline on gfx950 (~0.1 TB/s for a ~1.6MB weight read atM<=8,N=128). This PR addspython/sglang/kernels/ops/gemm/router_gemv.py, a Triton split-K kernel that reads the gate weight once at near-bandwidth, and routesMiniMaxM3MoE._compute_router_logitsthrough it whenrouter_gemv_supported()says the shape qualifies. Configs are per-M-bucket (BLOCK_K512 throughout,BLOCK_NandSPLIT_Kchosen by batch size, with a dedicatedBLOCK_M == 1GEMV path that usestl.suminstead oftl.dot) coveringM <= 8 / 16 / 32 / 64. The split-K reduction is done inside the same kernel: the last CTA to finish a(n-block, row-tile)pair reduces allSPLIT_Kpartials and resets its counter slot to zero on the way out, so the scratch buffer stays reusable across launches without either a zero-init launch or a separate reduce launch — each extra launch costs ~2us on gfx950, which is a meaningful fraction of the kernel itself at these sizes. Anything the kernel does not cover — wrong dtype, non-contiguous last dim,Nnot divisible byBLOCK_N,Knot divisible byBLOCK_K, orM > 64— falls throughrouter_gemv_supported()to the existingtorch.mmpath unchanged.Performance
Measured on 8x MI350X (gfx950), MiniMax-M3-MXFP8, TP8, 80k input / 600 output.
Two bench reps per boot; the warm (second) rep is reported and the two agreed
within 0.1%. Noise floor is +/-0.4%. Tables are the harness output verbatim,
trimmed to the first five columns. Baseline is
upstream/main.Baseline (
upstream/main)With this PR
Output throughput +1.07%..+3.27%; input throughput +0.33%..+0.99%.
Accuracy
gsm8k, 512 examples,
max_tokens 2048,temperature 0,seed 0, against thesame server boot as the perf run.
Baseline
With this PR
Correctness
Not bit-identical. Split-K reassociates the fp32 accumulation in the router GEMV, so the router logits differ in the last bits from the
torch.mmpath. That is why this carries an accuracy gate rather than a bit-identity claim.gsm8k is 97.27% against a 97.07% baseline (+0.20 pp),
truncated_rate1.95% vs 1.56%,error_rate0.00% on both. On a fixed 512-question greedy run +0.20 pp is one answer flipping -- read this as "no measurable accuracy cost", not as an improvement.Scope is wider than ROCm.
SGLANG_OPT_USE_TRITON_ROUTER_GEMVno longer exists; the path is selected byrouter_gemv_supported(), which testsx.device.type == "cuda"-- true for ROCm torch builds as well as NVIDIA.SGLANG_OPT_USE_BF16_ROUTER_GEMMalready defaults true onmain. So this is on by default on NVIDIA too, not an opt-in ROCm path, and reviewers should judge it on that basis. If you would rather it ship behind a flag, that is a code change and I am happy to make it.The kernel fires roughly 57 launches per decode step, and its configs are per-M-bucket, so the win is batch-size dependent by construction -- hence the full bs 1-32 sweep rather than a single point.
Reproduction
Server (identical on both sides):
Benchmark:
Accuracy gate:
Two bench reps per boot, warm rep reported; noise floor +/-0.4%, established by a separate PR measuring flat under a config where its code cannot fire.
Note on the launch command
--moe-runner-backend aiteris not honoured on currentmainfor mxfp8. Arg resolution logs:and falls back to triton. Both sides of this A/B therefore ran the Triton MoE runner, so the comparison is apples-to-apples and the deltas stand — but
AITER_CONFIG_FMOEis inert under this configuration and the aiter MoE path is not what was measured. The flag is kept in the command above only because it matches the invocation actually used; anyone reproducing will get triton and should see the same numbers.CI States
Latest PR Test (Base): 🚫 Run #34190762630
Latest PR Test (Extra): ❌ Run #34190762501
Latest PR Test (AMD ROCm 7.2): ❌ Run #34190762684