Repository navigation
Conversation
--speculative-token-map loads global vocab ids, but the shared target lm_head (ParallelLMHead) is vocab-sharded under TP: rank r holds only about vocab_size / tp_size rows. Gathering those rows by global ids reads out of bounds, which surfaces as a device-side assert from vectorized_gather_kernel inside eagle_worker_v2.init_lm_head rather than an actionable error. Validate the combination in _handle_eagle_family so the run fails at arg resolution and says why. EAGLE3 is excluded: it ignores the flag and uses the checkpoint's own reduced head, which is sharded consistently. The NPU token-map test asserted this configuration at --tp-size 4, which cannot work either -- Llama-3-8B shards to 32064 rows per rank while the FR-Spec freq_32768 map holds ids up to 128255. Move that case to --tp-size 1; the EAGLE3 case keeps --tp-size 4. Refs: sgl-project#42397
Review of the previous commit found the tp_size test too blunt in one direction and too narrow in another. The target lm_head is not always sharded by tp_size. Models that pass use_attn_tp_group=enable_dp_lm_head (56 of 136 model files, e.g. llama.py) shard it over attn_tp_size instead, which is 1 under full attention DP -- so the head is replicated and global hot ids address it fine. The rest (including qwen3_5_mtp.py) always shard by tp_size. The width is therefore a per-model property that server args cannot derive. Check the row count where the head is actually in hand: validate_hot_token_ids_fit in init_lm_head is exact for either layout. The arg-time guard now skips attention-DP configs and defers to it, so no working deployment is rejected up front. STANDALONE also needs rejecting at every tp_size, not just tp_size > 1: StandaloneDraftWorker.init_lm_head is a no-op, so the map reduces no head while the proposal still indexes it by full-vocab ids. The previous message told those users to set --tp-size 1, which would still fault. Also state the constraint in the flag's help text, which is the source for the generated table in docs/advanced_features/server_arguments.mdx. Refs: sgl-project#42397
atty57
requested review from
JustinTong0323,
Qiaolin-Yu,
Ying1123,
hnyls2002,
merrymercy,
ping1jing2,
sogalin,
wisclmy0611 and
zijiexia
as code owners
October 3, 2026 19:55
Open
5 tasks done
Open
4 of 5 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.
Motivation
--speculative-token-map(FR-Spec) crashes attp_size > 1with a device-side assert during speculative worker init, reported in #42397:The map holds global vocab ids, but the shared target
lm_headis aParallelLMHeadwithenable_tp=Trueby default (vocab_parallel_embedding.py:561), so each rank holds only aboutvocab_size / tp_sizerows.head.data[self.hot_token_id]ininit_lm_headthen indexes past the end of the local shard, and ids that do land in range select the wrong rows on rank >= 1.Nothing in the speculative path all-gathers the head, and
draft_tp_contextkeeps the target's TP placement, so the head stays sharded. Running the real shard math throughVocabParallelEmbedding._get_indices:124064 matches the reporter's "about 124K of 248,077".
Modifications
The shard width of the target
lm_headis a per-model property, so server args cannot derive it: 56 of 136 model files passuse_attn_tp_group=get_parallel().enable_dp_lm_head(e.g.llama.py:560) and shard overattn_tp_size, which is 1 under full attention DP - a replicated head that global hot ids index fine. The remaining models, includingqwen3_5_mtp.pyfrom the issue report, always shard bytp_size. So the check is split:speculative/spec_utils.py-validate_hot_token_ids_fit(hot_token_id, num_head_rows): the exact test, raising if any hot id lands at or past the head's row count. Called fromeagle_worker_v2.init_lm_headright before the gather, where the real head is in hand, so it is correct for either layout and for a quantized head.arg_groups/speculative_hook.py- fail fast for the unambiguous case (tp_size > 1and no attention DP), and rejectSTANDALONE+ token map at everytp_size:StandaloneDraftWorker.init_lm_headis a no-op (standalone_worker_v2.py:118), so the map reduces no head whileinit_token_mapstill loads it and the proposal indexes it by full-vocab ids (eagle_worker_v2.py:690,:826). Attention-DP configs pass through to the exact row check rather than being rejected up front, so no working deployment is blocked.init_token_mapalready ignores the flag for EAGLE3 and uses the checkpoint's own reduced head, which is sharded consistently.arg_groups/fields/spec.py- state the constraint in the flag's help text, the source for the generated table indocs/advanced_features/server_arguments.mdx:1610.test/registered/unit/spec/test_spec_token_map_tp.py- 8 cases: TP>1 rejected, TP=1 allowed, EAGLE3+TP>1 allowed, STANDALONE rejected at TP=1, attention DP deferred, plus the row-check boundary.--tp-size 4, which cannot work per the table above; moved to--tp-size 1. The EAGLE3 case keeps--tp-size 4.docs/.../speculative_decoding.mdx- record the requirement beside the existing EAGLE-3 note.This takes the second option the issue offers (fail fast). Supporting a sharded head properly needs per-rank hot-id shards of unequal size, a padded vocab all-gather in
LogitsProcessor(which currently assumes equal partitions), andvocab_sizebookkeeping ineagle_draft_extend_cuda_graph_runner.py:243- and separately,head.data[hot_token_id]is not meaningful on the quantized head in the reporter's NVFP4 setup. That is a feature, left as a TODO on the guard.Accuracy Tests
No accuracy impact: this only rejects a configuration that previously aborted during init.
Speed Tests and Profiling
No performance impact — arg validation only. FR-Spec at
tp_size 1is unchanged.Checklist
isort7.0.0 andruff0.15.1 (check+format) are clean on all five changed Python files.test/registered/unit/spec/test_spec_token_map_tp.py, registered tobase-a-test-cpu. Verified red before the fix (AssertionError: ValueError not raised) and green after. The 6 neighbouring spec arg-hook test files andtest_server_args.py(280/280) also pass, on Linux CPU with Python 3.12 / torch 2.14.1+cpu.speculative_decoding.mdxand in the flag's help text, which is the source for the generatedserver_arguments.mdxtable.Open items for reviewers
full-4-npu-a3runs only on a manualworkflow_dispatchof Full Test (NPU) (full-test-npu.yml'spull_requesttrigger is commented out and no workflow calls it), so it needs an Ascend runner to confirm Llama-3-8B fp16 + EAGLE draft still fits at--tp-size 1with--mem-fraction-static 0.7. On paper it is comfortable, but it has not been run.est_time=300is now low, since decode attp=1is roughly 4x slower. It only affects partition load-balancing (the stage caps per-file at 3600s), so nothing breaks, but the suite owner should pick the number.CI States
Latest PR Test (Base): ❌ Run #37149629635
Latest PR Test (Extra): ❌ Run #37149629362
Latest PR Test (AMD ROCm 10): ❌ Run #37149629693