[MiniMax-M3] Decode: paged K/V tile loads and a tiny GEMM for the router projection - #38841
Draft
thanhhao98 wants to merge 2 commits into
Draft
thanhhao98 wants to merge 2 commits into
thanhhao98 wants to merge 2 commits into
Conversation
added 2 commits
September 10, 2026 15:52
… GEMM The router projection is N=128 (num_local_experts) by K=6144 (hidden). cuBLAS gets too few output tiles to fill the device on that shape and splits K, so the projection runs as an nvjet split-K plus a splitKreduce: two launches per layer. sgl-kernel already ships tiny_gemm_bf16 for exactly this regime and two other models call it. Route M3's gate through it for decode-sized batches, keeping torch.mm for larger M and for deterministic inference. nsys, MiniMax-M3-NVFP4 on GB300 TP4, cc=1 decode: 2.00 -> 1.00 launches per layer, 6.20 -> 1.81 us per layer.
_gqa_share_sparse_decode_kernel resolved each selected block by loading 128 slot ids from req_to_token and addressing K/V by those ids, so every block cost two dependent global round trips and scattered addressing. A selected block starts at a multiple of block_size, and PagedTokenToKVPoolAllocator lays slots out as page * page_size + offset, so when page_size >= block_size and page_size % block_size == 0 the block lies inside one page and its slots are contiguous. Load the block's first slot and walk it; K/V addressing becomes affine. Gated on page_size, which the backend already tracks; page_size == 0 keeps the gather, as do the existing callers, so nothing else changes behaviour.
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.
Two independent decode-path changes for MiniMax-M3, both measured on GB300 (sm_103), TP4,
MiniMax-M3-NVFP4, ISL 8192 / OSL 1024,--context-length 1048576, backends auto-resolved.1. Sparse-decode K/V tiles by page
_gqa_share_sparse_decode_kernelresolved each selected block by loading 128 slot ids fromreq_to_tokenand addressing K/V by those ids — two dependent global round trips per block, scattered addressing.A selected block starts at a multiple of
block_size, andPagedTokenToKVPoolAllocatorlays slots out aspage * page_size + offset(mem_cache/allocator/paged.py). So whenpage_size >= block_size and page_size % block_size == 0the block lies inside one page and its slots are contiguous: load the block's first slot, walk it, and K/V addressing becomes affine.Gated on
page_size, which the backend already tracks.page_size == 0keeps the gather, as do the existing callers, so nothing outside this configuration changes behaviour. The invariant is already load-bearing elsewhere —metadata.pydoesreq_to_token[pool_idx, ::page_size] // page_size, and the M3 page-table top-k kernel doesreq_to_token[...] / page_size.nsys, cc=1 decode:
_gqa_share_sparse_decode_kernel8.61 → 5.27 µs/launch (−39%), 57 launches/step, unchanged grid.2. Router projection on the in-tree tiny GEMM
The router projection is N=128 (
num_local_experts) × K=6144 (hidden). cuBLAS splits K on that shape and runs it as two launches. The in-treetiny_gemm_bf16(python/sglang/kernels/ops/gemm/tiny_gemm.py) does it in one, and DeepSeek-V2 and Dots3 already dispatch to it through the sametiny_router_gemm_max_tokenshelper.Dispatch order is gfx95
router_gemv(#36557) → NPU → tiny GEMM →torch.mm; the paths are device-disjoint. Guarded off underenable_deterministic_inference, since the row-count threshold makes a token's logits depend on the batch it lands in.nsys, cc=1 decode: router projection 2.00 → 1.00 launches/layer, 6.20 → 1.81 µs/layer.
Accuracy
GSM8K 1319 q against a matched unpatched control on the same image: 0.856 vs 0.856. bs=1 canary and an EOS/termination check pass on both. The sparse-decode change is additionally verified bitwise-equal to the gather path on the allocator's layout.
Benchmarking
Median TPOT (ms), cumulative over the two changes plus #38615:
Checklist
CI States
Latest PR Test (Base): ❌ Run #34471493086
Latest PR Test (Extra): ❌ Run #34471492937
Latest PR Test (AMD ROCm 10): ❌ Run #34471493321