Repository navigation
Conversation
…p_tokens() On CUDA at TP > 1, replace the per-shard max / index add / casts / stack, the dim=-1 all-gather (+ reshuffle copy) and the argmax / gather / cast after it with one Triton pass over the logits shard, a dim-0 all-gather of [B, 16] fp32 candidates and a small pick kernel. Same result as torch.argmax over the full vocabulary, including ties, inf and NaN. The shard kernel takes row and column strides, so no .contiguous() copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 of 6 tasks
This branch has not been deployed
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.
Overview
On CUDA at TP > 1, compute
LogitsProcessor.get_top_tokens()(theuse_local_argmax_reductionpath) with one Triton pass over the logits shard, a dim-0 all-gather of a[B, 16]fp32 candidate tensor and one small pick kernel, instead of the current sequence of small torch ops. Same result astorch.argmaxover the full vocabulary. Split out of #59727 after review.Claims
torch.argmaxover the concatenated logits for every input, including ties within and across shards,infand NaN (first NaN wins, ties go to the lowest id).Validation
On
ac9126e58(nightly image with this branch'svllm/files overlaid), 1x / 4x B200:pytest -v tests/model_executor/layers/test_vocab_parallel_argmax.py: 27 passed. Simulated TP 2 / 3 / 4 / 8 shards, bf16 / fp16 / fp32, 131072 and 32001 vocab, 1-64 rows, againsttorch.argmax: random values, integer ties, ties across shards, all-inf,+infties, NaN,-0.0/+0.0ties; column-major and strided-column shards; 50 CUDA graph replays.pytest -v tests/distributed/test_vocab_parallel_argmax.py(TP2 / TP4, Ray): 2 passed. RealParallelLMHead+LogitsProcessor.get_top_tokens()across ranks against the full-vocabulary greedy path (argmaxof the gathered logits), with tie-heavy integer weights, 131072 and 32001 (padded) vocab, 1-8 rows, eagerly and from a captured CUDA graph.Time per call relative to the existing reduction:
The existing path in this benchmark also gets 54 / 54 simulated-shard cases (NaN, ties, signed zeros) equal to
torch.argmax, so the change does not alter the selected token.Details
What the existing path does per call (after the head GEMM, soft-cap / scale and padding mask, which are unchanged):
logits.max(dim=-1)(1 reduction kernel),+ vocab_start,.float()on the values and on the indices,torch.stack(4 elementwise kernels): 8 bytes per row;all_gather(dim=-1): NCCL gather along dim 0 plus the copy that moves the rank dimension next to the last dim;argmaxover ranks,gather,.to(int64)(3 kernels).That is 9 small kernels around the collective, each a separate launch.
This PR:
local_argmax_candidatesscans the shard once with 8 programs per row; each writes its best(value, global id)as fp32 (exact for bf16 / fp16 / fp32 values and ids below 2^24, the same assumption as today). The candidates (64 bytes per row and rank) are gathered along dim 0, so no rearranging copy is needed, andpick_from_candidatespicks per row. The kernel takes row and column strides, so non-contiguous logits need no copy. TP = 1 and non-CUDA platforms keep the torch path.Exactness: every comparison uses
torch.argmax's total order with an index tie-break (NaN first, then larger value, then lower id,-0.0 == +0.0). The maximum under such an order over a partition of the vocabulary equals the maximum of the per-part maxima, so the result does not depend on the shard or chunk boundaries.torch.compile: this path is not compiled. For MTP drafters such as Nemotron-H,
@support_torch_compileis applied to the inner model (NemotronHMultiTokenPredictor), so only itsforwardis traced. The speculator'ssample_draft()callsget_top_tokens()after that forward, outside the compiled region, so Inductor never fuses these ops. With FULL CUDA graphs the eager kernels are captured into the drafter's graph and replayed one by one, once per draft position; with PIECEWISE they run eagerly. The kernel count is therefore what runs at decode.Race-free: two kernels plus vLLM's normal TP all-gather, all on the caller's stream; each kernel writes a fresh output; no atomics, no persistent state, no cross-program communication.
Related: #59727 (Nemotron-H MTP
get_top_tokens(), needed to use this path with that drafter), #56995 (generic MTP drafter hook), #57770 (split-vocabulary argmax on already-gathered logits for DSpark; different call site).Duplicate check:
gh pr list --repo vllm-project/vllm --state open --search "get_top_tokens"-> #59727, #56995, #52072, #57770, #47584;--search "vocab parallel argmax"and--search "local argmax reduction"-> the same plus unrelated sampler / diffusion items. None changes the reduction insideLogitsProcessor.get_top_tokens().AI assistance: Claude Code was used to write this change and its tests, run the validation and draft this description. The human submitter reviews every changed line before this leaves draft.
Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR.
Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.
🤖 Generated with Claude Code