Repository navigation
[Bugfix][Spec Decode] Ship the speculative round state to non-last PP ranks - #662
Merged
Merged
Conversation
DSparkDeepseekV4ForCausalLM has no embedding of its own in the checkpoint (has_own_embed_tokens = False): with PP=1 the proposer shares the target's embed_tokens. Under pipeline parallelism it does not (_maybe_share_embeddings: "will be loaded separately"), because the target embedding lives on the first stage and the drafter on the last; but _remap_dspark_name drops every key outside mtp.*, embed.weight included, so the drafter's VocabParallelEmbedding kept its random initialisation. The boot succeeds and acceptance collapses to a few percent. Map embed.weight onto the drafter's table (with PP=1 the shared target embedding replaces it afterwards, as before) and fail loudly under PP when the checkpoint did not supply it. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Peuqui <peuqui@github.com>
… ranks With async scheduling, pipeline parallelism and a drafter, the non-last ranks never learn what the last rank sampled: the [num_reqs, 1] broadcast asserts on the wider speculative matrix, and the draft token ids the next step scatters into input_ids exist on the last rank only. The last rank now broadcasts the sampled matrix, padded with -1 to the static shape [num_reqs, num_spec_tokens + 1], and this step's draft token ids. A non-last rank derives the next token ids and the accepted counts with _count_contiguous_spec_tokens and runs the hybrid-state update on the scheduler output it stashed for this step. Both payloads go over the gloo cpu_group. An NCCL broadcast on the device_group shares the communicator with the pipeline's send/recv; with five stages the two interleave and the first request hangs. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Peuqui <peuqui@github.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.
Purpose
Follow-up to #511 (context: #439). With pipeline parallelism, a drafter and
async scheduling, the non-last ranks never learn what the last rank sampled.
_pp_broadcast_prev_sampled_token_idsasserts a[num_reqs, 1]tensor, whichthe speculative sampler does not produce, and the draft token ids that the next
step scatters into
input_idsexist on the last rank only. Onmainwith #574and #636 applied (Qwen3.8 MTP needs both to get this far under PP) the first
speculative step ends on rank 0 with
and the request hangs.
This change ships the round state:
shape
[num_reqs, num_spec_tokens + 1](the sampler emits fewer columns inrounds with fewer or no scheduled drafts), and this step's draft token ids
[num_reqs, num_spec_tokens]. List-form drafts (ngram) travel as zeros; thescheduler schedules no GPU-resident spec slots from those, so they are never
read.
matrix with
_count_contiguous_spec_tokens, hands them to_copy_valid_sampled_token_count, and runs_update_states_after_model_executeon thescheduler_outputit stashedwhen it returned its intermediate tensors. A missing stash raises instead of
silently skipping the hybrid-state update.
cpu_group. An NCCL broadcast on thedevice_groupshares the communicator with the pipeline's send/recv. On afive-stage pipeline the two interleaved and the first request hung, ranks
0-2 in the broadcast and ranks 3-4 in
irecv. A CPU rendezvous has nostream ordering to violate, and the payloads are a few dozen int32. The
non-speculative
[num_reqs, 1]path is unchanged and stays on thedevice_group.input_batch.num_reqs, thenumber the receiver sizes its buffer from, so a divergence raises instead of
hanging every rank in an unmatched collective.
The second commit lets the DSpark drafter load its own embedding table from
embed.weight. With PP=1 the proposer replaces it by the shared targetembedding afterwards. Under pipeline parallelism the target embedding lives on
the first stage and the drafter on the last, so the drafter ran on an
uninitialized table; a checkpoint without
embed.weightnow fails loudly underPP.
Not covered: #539 stops earlier, in
custom_all_reduce.cuhduring the memoryprofile run, which this does not touch. Qwen3.5-family MTP under PP also needs
#636, and #574 trims the optimistic tokens on every rank; both are independent
of this change and merge cleanly with it.
Test Plan
The new test runs two CPU processes over gloo as the last and a non-last rank
of a PP=2 deployment: a speculative round with a narrower sampler output, a
round without a stashed
scheduler_output(must raise after both broadcastswere consumed, otherwise the last rank would hang on the next collective),
list-form drafts, and the plain
[num_reqs, 1]path.Server run: Qwen3.8-27B-NVFP4 with MTP (
num_speculative_tokens=3), pipelineparallel over two Tesla V100-PCIE-32GB,
--enforce-eager, async scheduling on(the default), this branch merged with #574 and #636.
Test Result
Tesla V100-PCIE-32GB,
CUDA_DEVICE_ORDER=PCI_BUS_ID:53 passed, 2 skipped.The new test against the unmodified runner of
main(b711d53):1 failed.pre-commit: all hooks passed; mypy-3.10 manual stage passed.
Server run with this change: four greedy fact prompts correct, a 320-token
greedy generation at 39.9 tok/s, every greedy prompt repeated token for token,
mean acceptance length 2.9 to 3.2 of 4, and 90 short requests, three in
flight, greedy and temperature 1.0 mixed, without an error. The same stack
with this change reverted: the first request hangs with the RuntimeError
quoted above on
Worker_PP0.A fork of this repository has carried the same transport since early
September on a five-stage pipeline (two RTX 8000, three V100) with
DeepSeek-V4-Flash and DSpark,
num_speculative_tokens=5; that is where theNCCL hang was seen and the gloo path came from.
Not a duplicate
The hits are my own #574, #636 and #639 (different defects, see above) and
#625, which rejects out-of-range sampled ids in the async output path and does
not move data between ranks.
AI assistance
AI assistance (Claude) was used to port the change from the fork, to write the
test and to draft this text. I have read every changed line and ran the tests
and the server runs above on my own hardware.
Essential Elements of an Effective PR Description Checklist
supported_models.mdandexamplesfor a new model.