Repository navigation
perf: optimize prefill q8kv8 indexer for rubin - #17
Merged
NVIDIA-JerryChen merged 1 commit intoOct 5, 2026
Merged
NVIDIA-JerryChen merged 1 commit into
NVIDIA-JerryChen merged 1 commit into
Conversation
Author
Collaborator
|
lgtm |
caitlinw-nvidia
added a commit
to caitlinw-nvidia/MSA
that referenced
this pull request
Oct 11, 2026
…x-AI#17) SM107 only; SM100/SM103 code paths unchanged: - SM107MmaFP8Op (K=64) for the FP8 UMMA - TMEM load-and-reduce epilogue on SM107 - 168 registers for every warp role - K loader prefetches the next page-table entry - two Q stages - planner caps the page chunk (8..128) to fill the clusters Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Caitlin Wang <caitlinw@nvidia.com>
caitlinw-nvidia
added a commit
to caitlinw-nvidia/MSA
that referenced
this pull request
Oct 11, 2026
…x-AI#17) SM107-specific changes: - SM107MmaFP8Op (K=64) for the FP8 UMMA - TMEM load-and-reduce epilogue on SM107 - 168 registers for every warp role - K loader prefetches the next page-table entry - two Q stages - planner caps the page chunk (8..128) to fill the clusters Changes on all architectures: - define the physical page index before the dynamic K-load loop on the non-SM107 path (port of MiniMax-AI/MSA bc5f83a) - keep the four persistent task-bucket loops rolled (cutlass.range(..., unroll=1)) instead of unrolling them at trace time, which mainly shrinks the generated code Rubin (SM107), full 128-case prefill-indexer suite, score + top-k per call, weighted mean versus dev e441976: - H=1: 54.71 -> 49.34 us (about 1.11x); short queries (<512) about -22% - H=2: 88.04 -> 82.15 us (about 1.07x) Top-k output is bitwise identical in all 128 cases. The prefill and top-k tests in test_q8_indexer.py pass on SM107; on SM100 (B200) they passed before the loop-rolling change. Suggested-by: Fan Shi <fans@nvidia.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Caitlin Wang <caitlinw@nvidia.com>
caitlinw-nvidia
added a commit
to caitlinw-nvidia/MSA
that referenced
this pull request
Oct 11, 2026
…x-AI#17) SM107-specific changes: - SM107MmaFP8Op (K=64) for the FP8 UMMA - TMEM load-and-reduce epilogue on SM107 - 168 registers for every warp role - K loader prefetches the next page-table entry - two Q stages - planner caps the page chunk (8..128) to fill the clusters Changes on all architectures: - define the physical page index before the dynamic K-load loop on the non-SM107 path (port of MiniMax-AI/MSA bc5f83a) - keep the four persistent task-bucket loops rolled (cutlass.range(..., unroll=1)) instead of unrolling them at trace time, which mainly shrinks the generated code Rubin (SM107), full 128-case prefill-indexer suite, score + top-k per call, weighted mean versus dev e441976: - H=1: 54.71 -> 49.34 us (about 1.11x); short queries (<512) about -22% - H=2: 88.04 -> 82.15 us (about 1.07x) Top-k output is bitwise identical in all 128 cases. The prefill and top-k tests in test_q8_indexer.py pass on SM107; on SM100 (B200) they passed before the loop-rolling change. Suggested-by: Fan Shi <fans@nvidia.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Caitlin Wang <caitlinw@nvidia.com>
zyongye
pushed a commit
to vllm-project/MSA
that referenced
this pull request
Oct 13, 2026
* [Perf][Attention] Run every sparse-prefill combine on the v1 combine Fold the nv_dev v1 combine (src/blackwell_prefill/combine.py) into src/sm100/fwd/combine.py and make it the public combine() for all callers: the Blackwell port route, the original FP8/BF16 path, the NVFP4 KV path and the Q8KV4 prefill. src/sm100/fwd/combine.py now has two sections, one per kernel: - Original combine: SparseAttentionForwardCombineF32F8 and its launcher combine_f32f8, unchanged apart from the names. It implements the FP32 and FP8 E4M3 STG.128 fake layouts as well as BF16/FP16. - v1 combine: SparseAttentionForwardCombine (moved verbatim) and its launcher combine(), the default. It takes BF16/FP16 partials only: the v1 kernel implements just that STG.128 fake layout. interface.py picks the launcher by partial dtype (_select_combine) on the two paths that accept a caller-chosen partial_dtype, the original FP8/BF16 path and the NVFP4 KV path. The Blackwell port route and Q8KV4 always write BF16 partials and call combine() directly. vLLM always writes BF16 partials, so production runs the v1 kernel. Other changes: - v1 launch: min_blocks_per_mp=0 (was 3 with output_scale + PDL), and a 3-stage O_partial ring on SM107 (2 elsewhere). - The Blackwell port route now accepts output_scale and passes it to the combine instead of falling back to the original path. - Delete src/blackwell_prefill/combine.py; drop the per-caller combine selection in interface.py and prefill_q8kv4/interface.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(indexer): port Rubin Q8KV8 prefill indexer optimizations (MiniMax-AI#17) SM107-specific changes: - SM107MmaFP8Op (K=64) for the FP8 UMMA - TMEM load-and-reduce epilogue on SM107 - 168 registers for every warp role - K loader prefetches the next page-table entry - two Q stages - planner caps the page chunk (8..128) to fill the clusters Changes on all architectures: - define the physical page index before the dynamic K-load loop on the non-SM107 path (port of MiniMax-AI/MSA bc5f83a) - keep the four persistent task-bucket loops rolled (cutlass.range(..., unroll=1)) instead of unrolling them at trace time, which mainly shrinks the generated code Rubin (SM107), full 128-case prefill-indexer suite, score + top-k per call, weighted mean versus dev e441976: - H=1: 54.71 -> 49.34 us (about 1.11x); short queries (<512) about -22% - H=2: 88.04 -> 82.15 us (about 1.07x) Top-k output is bitwise identical in all 128 cases. The prefill and top-k tests in test_q8_indexer.py pass on SM107; on SM100 (B200) they passed before the loop-rolling change. Suggested-by: Fan Shi <fans@nvidia.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Caitlin Wang <caitlinw@nvidia.com> * feat(indexer): let Q8KV8 prefill callers own the score buffer BatchPrefillIndexerQ8KV8Wrapper always allocated its own score buffer and its only public call ran the TopK as well. A caller that selects pages itself (vLLM's shared TopK over decode and prefill rows) needs the scores written into its own buffer, without the wrapper's TopK. - plan(..., score_out=None): optional caller-owned fp32 buffer of shape [total_q * num_heads, >= max_pages] with contiguous pages per row; a wider row pitch is accepted, so a slice of a larger buffer works. The kernel layout is already dynamic, so no recompile and no kernel change. - run_scores(q, k_cache): public scores-only call; run() is unchanged. Tests: score_out matches the wrapper-owned scores bitwise at an 8192-page pitch, leaves other entries untouched, runs no TopK, and run() TopK is unchanged; invalid score_out buffers are rejected. Prefill and TopK tests in test_q8_indexer.py pass on SM107 (41 passed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Caitlin Wang <caitlinw@nvidia.com> --------- Signed-off-by: Caitlin Wang <caitlinw@nvidia.com> Co-authored-by: Claude Opus 5.5 <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.
Perf: optimize prefill q8kv8 indexer for rubin
Adds Rubin (SM107) support to the Q8KV8 paged prefill indexer.
Changes
indexer_gemm.pyuse_tmem_load_reduce.plan.py,interface.pyPerformance (Rubin / SM107)
Metric: the repository's formal benchmark,
benchmarks/.../indexer/prefill/q8kv8/benchmark.py --suite full --verify: 128 production cases, CUDA Graph, cold cache, 5 warmup + 20 timed replays, median per case, production-weighted mean (66 weighted cases, 14,805 represented calls). Baseline = nv_dev (dbd31ee) compiled directly for SM107 and run on same Rubin GPU.Performance (Rubin / SM107)
nv_devCorrectness (Rubin)
tests/.../indexer/prefill/q8kv8: api, manifest, production, runtime, plan-reuse, CUDA Graph (H=1/2/4) +tests/.../indexer/_common/topk_selecttest_real_cases.py, full tier, H=1/2/4test_all_real_cases.py(all 10,562 prefill shapes)--verify, full suite H=1 (two runs)For all 128 production cases, the outputs were also bitwise-identical to original baseline.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.