Skip to content

[Kernel] Optimize CuTeDSL DCP top-k merge - #47348

Draft
LucasWilkinson wants to merge 2 commits into
vllm-project:mainfrom
LucasWilkinson:codex/cutedsl-topk-merge-improvements
Draft

LucasWilkinson wants to merge 2 commits into
vllm-project:mainfrom
LucasWilkinson:codex/cutedsl-topk-merge-improvements

Conversation

@LucasWilkinson

@LucasWilkinson LucasWilkinson commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two commits optimizing the DCP sparse-indexer top-k merge (each rank holds the local top-K of its 1/DCP KV shard; the merge selects the global top-K from the gathered candidates).

Commit 1 — CuTeDSL selector optimization. StableTopKFromGatheredCandidatesKernel runs a radix pass over 32-bit score keys first and only runs the token-id tie-break pass when the score threshold actually ties, instead of always sorting 64-bit score<<32 | ~id keys. Selection semantics are unchanged and deterministic: score descending, then lowest global token id.

Commit 2 — symmetric-memory candidate exchange. Replaces the NCCL all-gather of packed (score, global_id) candidates with a one-shot all-gather over torch.distributed._symmetric_memory: each rank packs its candidates into its slice of every peer's symmetric "inbox" with a Triton remote-write kernel, synchronized by device-side write/read sequence flags (sys-scope acquire/release atomics). No NCCL launch, no host branches, fully CUDA-graph capturable; the selector then runs on the local inbox unchanged. get_dcp_topk_symm_mem_workspace returns None on unsupported configurations (no device group, unaligned candidate counts, rendezvous failure) and the code falls back to the existing all-gather path.

Duplicate-work check

gh pr list --repo vllm-project/vllm --state open --search "CuTeDSL DCP top-k merge" --limit 20
gh pr list --repo vllm-project/vllm --state open --search "sparse indexer topk cutedsl" --limit 20
gh pr list --repo vllm-project/vllm --state open --search "DCP topk symmetric memory" --limit 20

Related DCP/sparse-MLA PRs (#46514, #45426) exist but none touch the CuTeDSL gathered-candidate selector or the candidate exchange transport. The side-stream indexer overlap work is separate in #47355.

Benchmarks

Selector microbenchmark (stable_topk_from_gathered_candidates_cutedsl, median µs, 7×200 launches, JIT warmup excluded):

case main this PR speedup
rows=16, candidates=4096, random 6.922 6.721 1.03x
rows=16, candidates=8192, random 13.040 11.478 1.14x
rows=16, candidates=16384, random 20.936 20.684 1.01x
rows=64, candidates=8192, random 13.029 11.696 1.11x
rows=256, candidates=8192, random 17.705 15.459 1.15x
rows=16, candidates=8192, tie-heavy 21.371 20.396 1.05x

The symm-mem exchange replaces an NCCL all-gather launch + copy with two flag kernels and one push kernel on the capture stream; on GLM-5.1-NVFP4 B200 TP=4/DCP=4 decode it was worth ~4% end-to-end output throughput measured back-to-back against the NCCL merge path.

Tests

.venv/bin/python -m pytest tests/v1/attention/test_indexer_dcp_localize.py -q       # 38 passed
CUDA_VISIBLE_DEVICES=0,1 .venv/bin/python -m pytest tests/distributed/test_dcp_topk_symm_mem.py -q   # passed (2- and 4-rank)
pre-commit run --files <changed files>   # all hooks passed

tests/distributed/test_dcp_topk_symm_mem.py is new: multi-rank exactness of the symm-mem merge against the all-gather reference (including tie-heavy scores and empty shards) and replay correctness under CUDA graph capture.

AI assistance

AI assistance (Claude) was used to implement, benchmark, and prepare this PR. The human submitter has reviewed the changed lines and run the tests above.

LucasWilkinson and others added 2 commits July 9, 2026 19:58
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Lucas Wilkinson <lwilkins@redhat.com>
Replace the NCCL all-gather in the DCP sparse-indexer top-k merge with a
one-shot all-gather over torch symmetric memory: each rank packs its
(score, global_id) candidates into its slice of every rank's peer-mapped
inbox, a device-side flag handshake orders the exchange, and the existing
CuTeDSL selector then runs unchanged on the (now local, contiguous)
gathered layout. Selection semantics are identical - each rank still
exchanges its full local top-K, so the merge remains exact - and the
selector kernel is untouched.

The exchange is five small kernels (wait-writable, pack, push, publish-
and-wait, ack) with sequence flags and sys-scope acquire/release; there
are no host branches, so the chain is FULL-cudagraph capturable and
replaces the per-layer NCCL RING_LL all-gather launch (~19us at decode
sizes, latency-bound). Falls back to the all-gather path automatically
when symmetric memory is unavailable.

On the development branch this transport measured +3.8-4.0% output tok/s
(GLM-5.1 NVFP4, TP4/DCP4, c64 ISL8192/OSL1024, B300, with a reduced
candidate configuration), halving per-layer all-gathers. GSM8K-64 smoke
on that branch: accuracy 0.9531, invalid 0.

Tests: tests/distributed/test_dcp_topk_symm_mem.py asserts the fused
exchange selects identical sets to the all-gather reference at 2 and 4
ranks across varying row counts, including under CUDA-graph capture and
replay. The existing localize suite passes unchanged (38/38).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: Lucas Wilkinson <lwilkins@redhat.com>
if tid == Int32(0):
committed_count_smem.store(Int32(0))
prefix_smem.store(Uint64(0))
prefix_smem.store(Uint32(0))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing sync_threads() between score_threshold = prefix_smem.load() above and this store(0) — warps that haven't loaded yet capture score_threshold = 0, so the phase-2 key == score_threshold predicate drops every tied candidate. Adding one barrier before this store makes 20/20 heavy-tie trials match the 64-bit reference bit-exactly.

@mergify

mergify Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @LucasWilkinson.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants