Repository navigation
Conversation
The decode publish ran the histogram TopK with one 512-thread CTA per (token, head) row. A decode row has at most a few thousand local columns and keeps 16, so each row spent most of its time in block barriers and merge sorts, and with two or three rows resident per SM a step of thousands of rows ran in waves. One warp now ranks a row exactly in the merge's order: score descending with forced pages as +inf, NaN as -inf and -0 as +0, then the lower column, so exact cutoff ties no longer depend on shared-memory atomic order. At least 16 keys reach the 16th largest of the lanes' maxima; up to 64 such survivors are ranked against each other in shared memory, and a larger set falls back to 16 exact rounds over the row. Up to 768 columns of a row load into registers while its valid-page count is in flight, and the receive slot and generation load up front too. The published rows, PDL, index_scale/index_offset, forced blocks and the tagged and legacy protocols are unchanged. tests/smoke/test_sparse_topk_publish.py checks published rows exactly against a reference in tagged and legacy modes, across cutoff ties, signed zeros, infinities and NaN, scale/offset, forced pages, the fallback path, slot reuse and PDL. Signed-off-by: mdavis36 <michael.l.davis@hotmail.co.uk>
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.
Summary
This replaces the histogram TopK behind the decode
sparse_topk_select_publishwith a top-16 that ranks each (token, head) row in one warp, in the candidate merge's order. It is one commit on f380d0e, the head of vllm-project#46 when this was opened, and targets that PR's branch, so merging it adds the commit to that PR.In indexer context parallelism (ICP), each rank selects a local top-16 per (token, head) row and publishes it to the row's owner rank, which merges the candidates in vLLM. On f380d0e, publish takes longer than the merge. The histogram kernel runs one 512-thread CTA per row, but a decode row has one local column per page of context (1,344 at 168K tokens with P128 pages) and keeps 16. Its threads hold only a few columns each, while every row still pays for block-wide barriers, a block scan and merge sorts. With only two or three of those CTAs per SM, a decode step of thousands of rows runs in waves.
The API, row format, tagged/legacy protocol, PDL,
index_scale/index_offsetand which pages are forced (force_begin/force_end) are unchanged, so callers need no changes. Published rows differ from f380d0e only in the tie and special-value cases below. Prefill (sparse_topk_select_prefill) keeps the histogram kernel.Changes
SparseTopKPublishWarpKernelranks one row per warp, 4 rows per 128-thread CTA. Each column gets a 64-bit key: its order-preserving score bits above its inverted column index. Keys are distinct and sort in the candidate merge's order: score descending, forced pages as +inf, NaN as -inf, -0 as +0, then the lower column (so the lower page id). As ec980f2 does for prefill, ranking in the merge's order lets independently selected rank candidates compose exactly: each rank's top-16 holds every candidate the merge would take from it.kCached) are now unused. Removing them is left for a follow-up to keep this diff small.Behaviour changes vs f380d0e
Not changed: prefill
sparse_topk_select_prefillkeeps the histogram kernel. Its docstring promises cutoff ties to the lower id, matching the merge, and since ec980f2 its final candidates are ranked with a stable key on rows of any length. It still departs from the merge's order in three cases:Performance
No timings are included here, and GB200 and GB300 have not been timed. Publish kernel time (nsys) and the decode chain of score, publish and merge (CUDA events) were each measured back to back against f380d0e:
Publish kernel time drops in every configuration, by more at 64 requests than at 16, and the chain is faster by about the time saved in publish. A separate run at 512K tokens with P256 (2,048-column rows), which also used the warp merge under Related at TP2, saved about as much per layer. Longer rows have not been timed: 4,096 columns at 512K tokens with P128 or at 1M with P256, and 8,192 at 1M with P128. Since columns past 768 are re-read on every pass, those are the shapes to check.
Testing
tests/smoke/test_sparse_topk_publish.pyis new: 552 cases that check published rows exactly against a reference in tagged and legacy modes. Score buffers of 17 to 5,000 columns cover the 8-, 16- and 24-column register paths and rows streamed past 768 columns, with valid counts from 0 to past the buffer. Cases cover cutoff ties, ±0, ±inf/NaN, scale/offset, forced pages, the fallback, slot reuse and PDL. Buffers wider than 5,000 columns (the op accepts up to 12,287) are not tested.pytest tests/smoke/test_sparse_topk_publish.py \ tests/smoke/test_sparse_topk_prefill.py # 634 passed on GB200 and on GB300test_sparse_topk_forced.py,test_sparse_topk_large_boundary_bin.pyandtest_index_score_sharded.py: 725 passed.vllm/third_party/fmha_sm100replaced by this branch'spython/fmha_sm100, since the MSA that vLLM pins predatessparse_topk_select_publish:pytest tests/kernels/attention/test_minimax_m3.py -k 'warp_matches_legacy or candidate_merge or select_publish or sharded or q8kv4'gives 370 passed and 8 failed on GB200, GB300 and SM107.tests/distributed/test_minimax_m3_indexer_sharding.pypasses at TP2 and TP4 on SM107.test_sharded_candidate_merge_deduplicates_and_forces("indices should be either on cpu or on the same device"). It is a merge test that never calls the publish kernel, and it fails the same way without this change: since wolfcomos/vllm@dc68fa728, it indexes a CPUscorestensor with the CUDAnonemptymask.Alternatives considered
Related
A one-warp-per-row Triton candidate merge for vLLM is on
mdavis36/vllm:md/icp-merge-warp, on top of vllm-project/vllm#64631 and independent of this PR. Itsautosetting uses it everywhere except TP4 decode, where the current merge is faster.