Skip to content

[PP&Sepc] enable dspark under pipeline parallelism (deepseek-v4 & kimi-k3) - #32281

Open
liuqi-1 wants to merge 13 commits into
sgl-project:mainfrom
antgroup:sglang-community-main-pp-dspark
Open

liuqi-1 wants to merge 13 commits into
sgl-project:mainfrom
antgroup:sglang-community-main-pp-dspark

Conversation

@liuqi-1

@liuqi-1 liuqi-1 commented Jul 24, 2026 •

Copy link
Copy Markdown

Motivation

roadmap: #11857, #30344, #32607
Our previous PR (#31139) enabled PP + EAGLE support. Testing showed DSpark speculation performs far better than EAGLE, so this PR builds on it to add DSpark + PP compatibility. This PR depends on the previous one and should be merged afterwards.

DSpark's verify path is structurally different from EAGLE's (per-layer aux hidden capture, linear block verify, confidence-driven budget), so it could not reuse the EAGLE PP machinery verbatim. This PR lifts that ban and makes DSpark run under PP, targeting DeepSeek-V4-Flash + the bundled DSpark draft checkpoint.

Modifications

The core idea mirrors the EAGLE PP design — the draft model lives only on the last PP rank, every rank runs its own target verify forward, and the draft proposal is relayed across stages so each rank rebuilds an identical verify context. The DSpark-specific work is making the per-layer aux-hidden capture and the linear block verify cross the PP boundary correctly.

  1. Per-rank draft proposal carrier (DSparkPPVerifyInputRaw). A new spec input type carries the last rank's draft / confidence / accounting fields (bonus tokens, draft tokens, new seq lens, confidence, accept/cap-trim/verify lens) across the PP ring. Every rank rebuilds an identical verify layout from the same source each iteration, so all PP ranks produce byte-identical verify windows. It mixes in the shared DFlashDecodePrepareMixin so the non-last rank's verify forward (which reads verify_cache_loc from req_to_token) pre-allocates KV headroom exactly like the last rank. Linear verify reuses the formal req_to_token slots, so accept_index is always None and the token-move step in batch_result_processor is skipped automatically.

  2. Per-layer aux hidden capture across PP stages (deepseek_v4.py). DSpark's draft needs per-layer aux hidden states (dspark_aux_hidden_states), but each PP rank only executes layers in its own [start_layer, end_layer) range. The aux list is now stacked as [L, num_tokens, hidden], relayed downstream under the "dspark_aux" key in PPProxyTensors, and rebuilt in global layer order on the last rank (upstream layers first, then local captures). set_dspark_layers_to_capture no longer early-returns on non-last ranks, so every rank registers its own capture range.

  3. PP-aware verify forward. pp_proxy_tensors is threaded through TargetVerifyExecutor.run / run_non_compact so the target verify forward relay hidden states along the PP ring on every rank. TargetVerifyResult carries pp_hidden_states_proxy_tensors back to the scheduler. Logits adjustments (penalizer / vocab mask / logit bias) are now gated on result.logits_output is not None, since non-last ranks never sample and their logits output is None.

  4. Embed / weight loading split (deepseek_v4_dspark.py). The DSpark draft used to borrow embed_tokens from the target. Under PP the last rank builds its own VocabParallelEmbedding and only lm_head is shared from the target; attach_shared_modules now takes embed_tokens: Optional so the PP path passes None and the draft keeps its locally-built embedding. Weight-name remapping keeps embed_tokens.weight on the last PP rank.

  5. PP proxy buffer sizing (base_runner.py). The static pp_proxy_tensors buffers are allocated by max token count, not batch size, so the dummy / capture buffer fits the verify-token-expanded shape [num_tokens, *hidden] produced under speculative decoding.

  6. Planner / draft fixes for the relayed layout. alloc_verify_window and DraftBlockProposer derive prefix_lens from the verify window's anchor position instead of batch.seq_lens, because the last rank's re-propose pass builds the next window from post-iter seq lens while batch.seq_lens is still the pre-iter value. The planner forces RAGGED_VERIFY_MODE=STATIC and non-compact (eager) verify under PP, since the confidence-driven ragged scheduler and the cuda-graph compact path only run on the last rank and cannot keep cross-rank layout identity.

Supporting changes: draft_worker_common.build_draft_tp_worker accepts a pp_global_random_seed so the draft sampler on the last rank is seeded consistently with the target; speculative_hook replaces the blanket pp_size != 1 ban with a narrower "PP + DP-attention unsupported" check (DP-attention partitions the batch, which conflicts with the PP requirement that every rank see an identical batch).

Limitations asserted now

  • PP + DP-attention is rejected. Only --dp-size 1 is permitted under PP.
  • PP forces static verify and eager (non-cuda-graph) verify

Accuracy Tests

Model: DeepSeek-V4-Flash-FP8

GSM8K accuracy (200 examples, 5-shot, temperature=0):

Configuration GSM8K Accuracy (%)
PP2-TP4 (no spec) 98.00
PP2-TP4 + DSpark (this PR) 98.50

Speed Tests

Setup: DeepSeek-V4-Flash-FP8, single 8*H20 node; three PP2+TP4 configs:

  • PP2-TP4 — PP2+TP4, no spec (baseline)
  • PP2-TP4-MTP314 — PP2+TP4 + EAGLE (speculative-num-steps=3, speculative-eagle-topk=1, speculative-num-draft-tokens=4)
  • PP2-TP4-DSpark — PP2+TP4 + DSpark (this PR)

bench_serving: --dataset-name random with local ShareGPT path (--dataset-path /datasets/ShareGPT_V3_unfiltered_cleaned_split.json), --random-range-ratio 1.0, --num-prompts 5, --max-concurrency 1, --random-output-len 1024, --seed 42; 3 rounds per point, reporting the min.

TPOT (ms)

Configuration 16k 32k 64k
PP2-TP4 (no spec) 11.11 11.14 11.13
PP2-TP4 + MTP314 5.39 5.43 5.65
PP2-TP4 + DSpark (this PR) 2.30 2.34 2.38

Average accepted length (tokens / iter)

Configuration 16k 32k 64k
PP2-TP4 + MTP314 2.75 2.90 2.83
PP2-TP4 + DSpark (this PR) 4.91 4.85 4.87

Checklist

Review and Merge Process

  1. Ping Merge Oncalls to start the process. See the PR Merge Process.
  2. Get approvals from CODEOWNERS and other reviewers.
  3. Trigger CI tests with comments or contact authorized users to do so.
    • Common commands include /tag-and-rerun-ci, /tag-run-ci-label, /rerun-failed-ci
  4. After green CI and required approvals, ask Merge Oncalls or people with Write permission to merge the PR.

CI States

Latest PR Test (Base): ❌ Missing run-ci label -- add it to run CI tests.
Latest PR Test (Extra): ❌ Blocked -- run-ci is required first.
Latest PR Test (AMD ROCm 7.2): ➖ No AMD PR run found for this commit.

Copilot AI review requested due to automatic review settings July 24, 2026 04:07
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@liuqi-1 liuqi-1 changed the title [PP&Sepc] enanble dspark under pipeline parallelism [PP&Sepc] enanble dspark under pipeline parallelism (deepseek_v4 & kimi-k3) Aug 17, 2026
@liuqi-1 liuqi-1 changed the title [PP&Sepc] enanble dspark under pipeline parallelism (deepseek_v4 & kimi-k3) [PP&Sepc] enanble dspark under pipeline parallelism (deepseek-v4 & kimi-k3) Aug 17, 2026
liuqi-1 and others added 11 commits August 18, 2026 14:06
Under PP + DP attention, an idle rank (no local requests) on a global
prefill step must follow the prefill path, not the verify path. Routing
it through verify emits speculative_num_steps extra draft-decode
collectives that desync the MoE cross-DP all-gather and hang. Gate the
prefill branch on is_extend OR (pp_enabled and idle and is_extend_in_batch),
mirroring the scheduler's is_extend_in_batch lockstep. _draft_extend_for_prefill
already short-circuits idle batches, so the target+single-draft_extend
collective sequence stays identical across DP ranks.
need_topk gated only on spec_algo would still take the topk stash path
when the payload carries no topk_p (topk_p is None), crashing at
payload.topk_p[0]. Add the payload.topk_p is not None guard, matching the
existing payload.hidden_states check for need_hidden_states.
…ommits

Organically squash-merged from the kimi_k3_rebase_731 work (cd71a396,
b6da9197, 3c04aff8, 12904266) and adapted to this branch's PP&MTP +
GPU-relay baseline:

- DSpark under PP: the last rank relays DSparkPPVerifyInputRaw around the
  PP ring so every rank rebuilds an identical verify context each iter;
  the draft model is built only on the last rank while non-last ranks
  relay the target verify forward; DSpark aux hidden states ride the PP
  proxy buffers (token-major dspark_aux).
- Kimi-K3 hybrid-mamba state commit after PP DSpark verify: non-last
  ranks commit accepted mamba states via commit_pp_mamba_states_after_
  verify, passing explicit state / conv source indices through the fused
  scatter kernel (src_indices_tensor), bypassing the conv multi-launch
  fast path only when scratch rows are explicit.
- Mamba state commit for PP MTP (Eagle): commit_mamba_states_after_verify
  gains state_indices_tensor / scratch_source_indices_tensor and supports
  linear-chain verify (accept_index=None) via per-req step positions;
  Eagle keeps the GPU-tensor relay path and the module-level
  move_accept_tokens_to_target_kvcache.
- Fix ScheduleBatch NameError in eagle_info.py under PP2+DSPARK via a
  TYPE_CHECKING import.
Co-Authored-By: Claude <noreply@anthropic.com>
…ll ranks

Extracted from f2fb9f5e7b (speculative: support separate draft KV cache dtype).

- _profile_available_bytes: query free memory with distributed=False so each
  rank budgets from its own local free memory instead of an all-reduced
  cluster-wide figure.
- _apply_token_constraints: sync the resolved token capacity across every model
  rank (world group), not only PP ranks, so pure TP/DP workers also settle on
  one shared capacity.
Adapted from 3928e9b93 to this branch's DSparkPPVerifyInputRaw.

DSparkPPVerifyInputRaw now carries GPU tensors (bonus_tokens [bs],
draft_tokens [bs, gamma], accept_lens [bs], confidence [bs] f32,
cap_trim_lens [bs] i32, verify_lens [bs] i64) so the PP ring transmits
them over the GPU channel via the nested-dict flattening already provided
by _split_tensor_dict (a747ca5). new_seq_lens stays a CPU list because
it feeds only the last rank's sampling cache keyed by Python ints.

- DSparkPPVerifyInputRaw: tensor fields; build_dummy_for_decode /
  filter_batch / merge_batch operate on tensors (torch.cat / tensor index).
- DSparkWorkerV2: build the relay raw from accept/proposal tensors instead
  of .tolist(); rebuild verify candidates from pp_raw tensors directly.
- batch_result_processor / scheduler_pp_mixin: accept_lens handled as a
  tensor; DSpark and Eagle now share the same tensor path.
The DSpark PP check used bare enable_dp_attention, which is forced on
by DeepSeek-V4 prefill context parallel (dp_size==1) as an execution
flag reuse. That topology has no attention data-parallel ranks, so it
should not be rejected. Align the check with the DP-only restriction
(enable_dp_attention and dp_size>1) already used earlier in _handle_dspark.
@liuqi-1
liuqi-1 force-pushed the sglang-community-main-pp-dspark branch from 379f1b6 to 4b63351 Compare August 18, 2026 07:21
…k_draft_sampler

dspark_worker_v2 imported resolve_greedy_mask from dspark_draft_sampler,
but the function is defined in dspark_draft. Split the import: keep
maybe_build_draft_sampler from dspark_draft_sampler and take
resolve_greedy_mask from dspark_draft, matching the pp_spec branch layout.
This fixes ImportError at draft worker init under DSpark.
@liuqi-1 liuqi-1 changed the title [PP&Sepc] enanble dspark under pipeline parallelism (deepseek-v4 & kimi-k3) [PP&Sepc] enable dspark under pipeline parallelism (deepseek-v4 & kimi-k3) Aug 18, 2026
@enternal111

Copy link
Copy Markdown
Contributor

is this pr conflict with 33614? cause 33614 solve #33289

@enternal111

Copy link
Copy Markdown
Contributor

I would like to test it on H200 with dsv4-0731, but i can not patch both this pr and 33614 into nightly image. Without 33614 crash will happen. Could u deal with it so i can test @liuqi-1

…k) only

The unconditional even-rank-sends-first ordering was introduced for
PP+spec, where the spec worker's verify state relays multiple GPU
tensors in the output dict and every rank posting send first forms a
ring wait of device P2P ops that deadlocks on CUDA.

Keep the parity ordering only for EAGLE and DSPARK, the algorithms the
PP support in eagle_worker_v2.py / dspark_worker_v2.py was adapted and
verified for. Everything else stays on the upstream default: on CUDA
every rank sends first, on XPU adjacent stages are paired by parity.
@liuqi-1

liuqi-1 commented Aug 20, 2026

Copy link
Copy Markdown
Author

I would like to test it on H200 with dsv4-0731, but i can not patch both this pr and 33614 into nightly image. Without 33614 crash will happen. Could u deal with it so i can test @liuqi-1

I will update this PR when #33614 merged. Thanks~

This branch has not been deployed

No deployments
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.

6 participants