Skip to content

Pipeline parallelism x speculative decoding (EAGLE/MTP) compatibility - #30775

Open
AliceChenyy wants to merge 43 commits into
sgl-project:mainfrom
AliceChenyy:feat/pp-spec-compat
Open

AliceChenyy wants to merge 43 commits into
sgl-project:mainfrom
AliceChenyy:feat/pp-spec-compat

Conversation

@AliceChenyy

@AliceChenyy AliceChenyy commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Motivation

PP and speculative decoding are currently mutually exclusive (server_args.py asserts). On PCIe-only multi-GPU boxes (no NVLink), TP-only scaling is bottlenecked by per-layer all-reduces, and PP is the right scaling axis. Combining the two requires PP x spec compatibility, which vLLM has (vllm-project/vllm#16568) and SGLang doesn't.

This PR implements it behind SGLANG_ENABLE_PP_SPEC=1; default behavior is unchanged.

Dependencies: none outstanding. #26928 (SM120 GLM DSA) merged on 2026-07-28 and is in main. The former companion PRs #30774 (proxy buffer fixes) and #30994 (dense MLA kwargs) are closed — #30774's fixes are folded into this diff, #30994 is no longer needed on the sparse path.

Design

Key observation: non-spec PP decode already relies on a last->first output-dict relay (next sampled token becomes the next round's input, and every stage forwards the dict). Spec decoding is structurally identical — the relay payload just grows to {accept_lens, new_seq_lens, bonus_token, next draft tree}.

  1. Draft worker only on the last stage (needs final hidden states + lm_head).
  2. Tail-drafting: the last stage drafts for round r+1 at the end of round r (verify -> draft_extend -> draft). Round r+1's verify tokens must exist before stage 0 runs its half, so the single-stage head-of-iteration drafting cannot work under PP.
  3. The relayed tree is a first-class SpecInput: PPSpecRelayInput carries the proposed tokens plus the topology they were arranged by (parent_list / top_scores_index), rides on ScheduleBatch.spec_info, and implements the filter_batch / merge_batch hooks, so the existing recomposition path carries it — a request that finishes, is retracted, or merges in from a just-finished prefill takes its row along. Every stage rebuilds an identical EagleVerifyInput from it via build_tree_kernel_efficient, so no draft model is needed off the last stage. The type is deliberately the algorithm-agnostic half: another speculative algorithm supplies only its own rebuild.
  4. Composition-safe by rid: the relayed tensors are sized by the composition that ran the forward, which the live microbatch can have moved past by the time the result comes back around the ring, so rows are folded in by rid rather than by position.
  5. KV bookkeeping is free: kv_committed_len advances in the shared result processor from the relayed CPU accept_lens; verify slots stay within eagle_prepare_for_decode's 2x reserve (same mechanism the overlap path uses).

A subtle but load-bearing detail: non-last stages must run eagle_prepare_for_verify inside scheduler._forward_isolation — it mutates ScheduleBatch.forward_mode to TARGET_VERIFY, and without the restore the next get_next_batch_to_run treats the decode batch as extend and re-merges it (duplicate requests).

Two hazards that only appear once the relay carries several GPU tensors, both fixed here:

  • PP ring ordering. Device P2P work stays ordered on the stream, so enqueueing several sends before any recv on every rank can form a ring wait even on CUDA, where the existing comment assumes send-first is always safe. Adjacent stages are paired by pp_rank parity when the spec relay is active; non-spec PP keeps the existing behavior. Credit to [PP&Spec] enable speculative decoding (eagle_worker_v2) under PP #31139 for finding this.
  • Chunked prefill middle chunks. Only the chunk that finishes the prompt samples a real token; a middle chunk's relayed next_token_ids is a placeholder, and storing it seeded the next verify round with a token the model never emitted.

Validation

4u8g-gen-0210, 8x RTX PRO 6000 Blackwell Server (SM120, PCIe, no NVLink), driver 595.58.03, image lmsysorg/sglang:nightly-dev-cu13-20260826-41e7612d, torch 2.13.0+cu130 / sgl-kernel 0.4.6.post1 / flashinfer 0.6.17 / sgl-deep-gemm 0.1.5.post3. GLM-5.2-NVFP4, FP8 KV, EAGLE (2 steps / topk 1 / 3 draft tokens). Plain NCCL all-reduce on both configs.

GSM8K 200 questions, 8-shot, greedy, two runs per config — see the caveat below on why one run is not enough.

TP4xPP2+MTP (gate on) TP8+MTP (gate off)
GSM8K x2 0.940 / 0.935 0.955 / 0.940
Invalid 0.000 0.000
accept_length 2.79 - 3.00 2.68 - 3.00
8K bs1 TTFT / TPOT 1721 / 9.87 1888 / 8.73
8K bs8 TTFT / TPOT / tok/s 5521 / 30.28 / 154.5 11032 / 30.55 / 108.7
8K bs32 TTFT / TPOT / tok/s 13189 / 95.3 / 218.0 35897 / 105.2 / 120.1
KV pool 389,184 241,728

Accuracy and accept length match; PP wins TTFT (2.0x at bs8, 2.7x at bs32), throughput (+42% / +81%) and KV capacity (1.61x).

Correction to the earlier revision of this description. It claimed PP beating TP8 by 3-6x across TTFT/TPOT/throughput. That was measured in July; re-running the same benchmark on today's main, TP8+MTP is now 13% faster than PP at bs1 TPOT (8.73 vs 9.87 ms) — TP8's bs1 TPOT improved 1.85x and its bs32 throughput 2.6x over that period, while the PP numbers barely moved. The cause looks structural: PP+spec forces --disable-overlap-schedule, and the overlap path is where much of that improvement landed, so PP cannot pick it up. Making PP+spec work under the overlap scheduler is the obvious follow-up and probably matters more than anything else in this PR. PP's remaining advantages — TTFT, throughput at batch, KV capacity — all grow with concurrency.

Measurement caveat. GSM8K on this stack has +-2-3% run-to-run variance: the same build gave 0.940 and 0.965 on consecutive 200-question runs, because batch composition follows arrival timing and the reductions follow batch composition. Greedy output is not token-reproducible here either. So single runs cannot resolve differences of this size, and the accuracy claim above is "no detectable regression", not bit-identical behavior.

Also validated on DeepSeek-V4-Flash FP8, TP2xPP2 (4x RTX 6000D): GSM8K 0.95-1.00, accept_length 2.44-2.72 — the PP+spec layer carried over with only two portability fixes (checkpoint embed tensor name, backend-agnostic context-len bound).

Tests

test/registered/pp/test_pp_spec.py:

  • TestPPSpecConsistency runs GSM8K under plain spec and under PP2+spec and requires the PP run to match on both accuracy and accept length. Accuracy alone cannot distinguish a working relay from a broken one: if the relayed tree is lost, the bonus token is still force-accepted and the rest rejected, so the output stays correct and only acceptance collapses. Both a topk=1 chain and a topk=2 tree are covered.
  • TestPPSpecGate pins that the gate is off by default (the existing PP+spec ban still fires) and that the combinations the relay cannot reproduce identically on every stage are rejected. It resolves argument state only, so it stays off the GPU.

Known gaps

  • Non-overlap schedule only (PP itself requires that today). Per the correction above, this is now the main thing holding PP back at low batch, not a cosmetic limitation.
  • PP + DP attention is rejected at startup: DP attention partitions the batch per DP rank, so the stages would no longer rebuild the same verify tree. [PP&Spec] enable speculative decoding (eagle_worker_v2) under PP #31139 handles this case; this PR does not yet.
  • Adaptive spec (--speculative-adaptive), --speculative-token-map, and EAGLE3 drafts are rejected at startup (the relay/head-wiring paths don't cover them yet).
  • topk>1 trees are implemented (the drafted topology is relayed) but are exercised only by the unit test above — the SM120 model family available to us runs the DSA backend, where upstream rejects topk>1 with page_size>1, and DSA forces page_size=64.
  • One D2H sync per round in tail-drafting (optimizable).

Relationship to the other PP x spec PRs

#31139 (PP + EAGLE) reached essentially the same design independently — draft on the last stage, tail-draft, relay the raw tree, rebuild per stage, proxy buffers sized by token count. That convergence is probably the most useful signal here: the shape of the solution is not in dispute. Differences worth a maintainer's attention:

Whichever base the maintainers pick, happy to contribute the pieces that are specific to this one — the model-agnostic embed loading, the ring-ordering fix, the tests, and the SM120 / PCIe validation.

🤖 Generated with Claude Code


CI States

Latest PR Test (Base): ⏳ Run #35013704529
Latest PR Test (Extra): 🚫 Run #35013704124
Latest PR Test (AMD ROCm 10): ❌ Run #35013704196

AliceChenyy and others added 2 commits July 10, 2026 04:00
Four related fixes to the pipeline-parallel proxy tensor plumbing that only
manifest when PP is combined with speculative decoding (multi-token verify);
plain decode (num_tokens_per_bs == 1) is bit-identical:

1. _allocate_decode_buffers / DecodeInputBuffers.create: hidden_states and
   residual proxy buffers were sized (max_bs, hidden) while every other
   token-axis buffer (input_ids, positions, and topk_indices in the same
   dict) uses max_num_token = max_bs * num_tokens_per_bs. Under TARGET_VERIFY
   the [:num_tokens] slice silently returned fewer rows, crashing rotary with
   mismatched query/positions during warmup.

2. DecodeCudaGraphRunner.load_batch: the pre-planned early-return path (taken
   when eagle_prepare_for_verify already ran load_batch) copied input_ids and
   positions but never refreshed the pp_proxy_tensors input buffers, so the
   last PP stage replayed verify graphs against stale hidden states.

3. DecodeCudaGraphRunner.execute: the PPProxyTensors output was sliced with
   [:self.bs] (request rows) instead of [:self.bs * self.num_tokens_per_bs]
   (token rows). With verify (3 tokens/request) only the first bs rows were
   forwarded downstream, corrupting every request after the first in a
   microbatch while single-request runs looked healthy.

4. cuda_graph_buffer_registry: the pp-proxy slot source indexed
   ppx.tensors[key] unconditionally; an entry can legitimately be absent
   (e.g. topk_indices when a DSA model runs a dense attention backend).
   Use .get() so the established None-skips-copy contract applies.

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

Enables PP + spec-v2 (EAGLE/MTP, topk=1 chains) behind SGLANG_ALLOW_PP_SPEC=1.
Default behavior is unchanged; the existing PP+spec assert stays in place
unless the flag is set.

Architecture (mirrors vLLM sgl-project#16568's drafter-on-last-stage):
- The draft worker exists only on the last PP stage (needs final hidden
  states and lm_head). Non-last stages run the verify-shaped target chunk
  through the plain TpModelWorker, which is already PP-aware.
- The last stage drafts at the TAIL of its iteration (verify -> draft_extend
  -> draft-for-next-round): round r's verify tokens must exist before
  stage 0 runs its half of round r, so drafting cannot stay at the head of
  the iteration as in the single-stage flow.
- Spec state rides the existing last->first output-dict relay (which every
  stage already forwards): accept_lens, new_seq_lens, bonus token, and the
  tail-drafted chain for the next round. No new communication channel.
- Chains are stored per-request (rid-keyed) and every stage rebuilds an
  identical EagleVerifyInput at compose time via build_tree_kernel_efficient
  with the topk=1 chain constants -- no draft model needed off the last
  stage, and the design is robust to microbatch recomposition
  (finish/retract/merge) between rounds.
- Batch results are processed against a launch-time ScheduleBatch.copy()
  snapshot (PPBatchMetadata.fwd_batch); the live microbatch object can be
  merged/filtered in place before its relayed result arrives. seq_lens is
  scatter-updated per rid.
- KV bookkeeping needs no new plumbing: kv_committed_len advances in the
  shared batch-result processor from the relayed CPU accept_lens, and the
  verify slots stay within eagle_prepare_for_decode's 2x reserve (same
  mechanism the overlap path relies on).

Validated on 8x RTX PRO 6000 (SM120, PCIe), GLM-5.2-NVFP4, TP4xPP2 +
EAGLE(2 steps / topk 1 / 3 draft tokens):
- greedy decode identical to non-spec; GSM8K 20q 0.900 (= TP8 baseline)
- concurrent/staggered workloads correct (relay verified token-exact)

Known limitations (hence RFC):
- topk=1 chains only; tree speculation needs rebuild-logic extensions
- non-overlap schedule only (PP itself requires that today)
- draft-model weight loading on the last stage must not apply PP filtering
  (the NextN/MTP embed_tokens is shared with the target model and lives in
  the first-stage partition; loading the draft with the target's pp_rank
  leaves its embedding randomly initialized and collapses acceptance
  length) -- follow-up in progress
- depends on the PP proxy buffer fixes in the companion PR

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

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces support for Pipeline Parallelism combined with Speculative Decoding (PP+spec) in SGLang, ensuring draft models run only on the last PP stage and verify inputs are correctly rebuilt and relayed across stages. Feedback on the changes highlights several critical improvements: preventing a GPU memory leak and fragmentation by storing draft chains on the CPU, overriding PP parameters for the draft worker's ModelRunner to prevent embedding weights from being filtered out, avoiding potential KeyError and AttributeError crashes with safer attribute and dictionary accesses, and using get_embed_and_head() instead of fragile hardcoded attribute paths.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +1255 to +1270
if chain_tokens is not None:
rows = chain_tokens.to(torch.int64).reshape(
len(batch.reqs), num_draft_tokens
)
else:
rows = torch.zeros(
(len(batch.reqs), num_draft_tokens),
dtype=torch.int64,
device=bonus_tokens.device,
)
rows[:, 0] = bonus_tokens.to(torch.int64)
for i, req in enumerate(batch.reqs):
if req.finished():
self._pp_spec_chain_by_rid.pop(req.rid, None)
else:
self._pp_spec_chain_by_rid[req.rid] = rows[i]

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.

high

Storing slice views of the rows tensor (i.e., rows[i]) in the persistent dictionary self._pp_spec_chain_by_rid keeps the entire rows tensor's underlying storage alive in GPU memory. Since requests finish at different times, this will cause a severe GPU memory leak and memory fragmentation over time.

Additionally, storing these very small 1D tensors (size 4-16) on the GPU introduces unnecessary CUDA allocation overhead and fragmentation. Moving the tensors to CPU and cloning the slice completely resolves both issues.

Suggested change
if chain_tokens is not None:
rows = chain_tokens.to(torch.int64).reshape(
len(batch.reqs), num_draft_tokens
)
else:
rows = torch.zeros(
(len(batch.reqs), num_draft_tokens),
dtype=torch.int64,
device=bonus_tokens.device,
)
rows[:, 0] = bonus_tokens.to(torch.int64)
for i, req in enumerate(batch.reqs):
if req.finished():
self._pp_spec_chain_by_rid.pop(req.rid, None)
else:
self._pp_spec_chain_by_rid[req.rid] = rows[i]
if chain_tokens is not None:
rows = chain_tokens.to(device="cpu", dtype=torch.int64).reshape(
len(batch.reqs), num_draft_tokens
)
else:
rows = torch.zeros(
(len(batch.reqs), num_draft_tokens),
dtype=torch.int64,
device="cpu",
)
rows[:, 0] = bonus_tokens.to(device="cpu", dtype=torch.int64)
for i, req in enumerate(batch.reqs):
if req.finished():
self._pp_spec_chain_by_rid.pop(req.rid, None)
else:
self._pp_spec_chain_by_rid[req.rid] = rows[i].clone()

Comment on lines +605 to +610
if self.pp_size > 1:
assert (
self.support_pp
), "Pipeline Parallel is not compatible with this model."
import os as _os
if not (_os.environ.get("SGLANG_ALLOW_PP_SPEC") and self.is_draft_worker):
assert (
self.support_pp
), "Pipeline Parallel is not compatible with this model."

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.

high

The draft worker's ModelRunner is initialized with pp_size > 1 and pp_rank = pp_size - 1. This causes the weight loader to filter out the draft model's embedding weights (thinking they belong to stage 0), leaving them randomly initialized.

Since the draft worker runs entirely locally on the last stage and does not participate in pipeline parallel partitioning, we can elegantly solve this "known gap" by overriding self.pp_size = 1 and self.pp_rank = 0 early in ModelRunner.__init__ when self.is_draft_worker is True. This completely bypasses PP filtering for the draft worker.

Suggested change
if self.pp_size > 1:
assert (
self.support_pp
), "Pipeline Parallel is not compatible with this model."
import os as _os
if not (_os.environ.get("SGLANG_ALLOW_PP_SPEC") and self.is_draft_worker):
assert (
self.support_pp
), "Pipeline Parallel is not compatible with this model."
import os as _os
if getattr(self, "is_draft_worker", False) and _os.environ.get("SGLANG_ALLOW_PP_SPEC"):
self.pp_size = 1
self.pp_rank = 0
if self.pp_size > 1:
assert (
self.support_pp
), "Pipeline Parallel is not compatible with this model."

Comment on lines +1301 to +1303
chain_rows = torch.stack(
[self._pp_spec_chain_by_rid[req.rid] for req in batch.reqs]
).to(device=device, dtype=torch.int64)

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.

medium

If a request is missing from self._pp_spec_chain_by_rid (e.g., due to preemption, rescheduling, or other state inconsistencies), accessing self._pp_spec_chain_by_rid[req.rid] directly will raise a KeyError and crash the scheduler. Using .get() with a safe default row of zeros prevents this crash.

Suggested change
chain_rows = torch.stack(
[self._pp_spec_chain_by_rid[req.rid] for req in batch.reqs]
).to(device=device, dtype=torch.int64)
default_row = torch.zeros(num_draft_tokens, dtype=torch.int64, device="cpu")
chain_rows = torch.stack(
[self._pp_spec_chain_by_rid.get(req.rid, default_row) for req in batch.reqs]
).to(device=device, dtype=torch.int64)

Comment thread python/sglang/srt/model_executor/runner/decode_cuda_graph_runner.py
Comment on lines +345 to +347
embed = self.draft_runner.model.model.embed_tokens.weight
head = self.target_worker.model_runner.model.lm_head.weight
self.draft_runner.model.set_embed_and_head(embed, head)

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.

medium

Hardcoding self.draft_runner.model.model.embed_tokens.weight is fragile and can break across different model architectures (e.g., those using different embedding attribute names). Using the standard get_embed_and_head() method is much more robust and maintainable.

Suggested change
embed = self.draft_runner.model.model.embed_tokens.weight
head = self.target_worker.model_runner.model.lm_head.weight
self.draft_runner.model.set_embed_and_head(embed, head)
embed, _ = self.draft_runner.model.get_embed_and_head()
head = self.target_worker.model_runner.model.lm_head.weight
self.draft_runner.model.set_embed_and_head(embed, head)

@enternal111

enternal111 commented Jul 12, 2026

Copy link
Copy Markdown

Also need PP with speculative decoding. Willing to test if it is ready to merge.

AliceChenyy and others added 4 commits July 13, 2026 02:24
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NextN/MTP draft layers carry no embedding of their own in the
checkpoint, and under PP the target's embed_tokens lives on the first
stage (PPMissingLayer on the last stage where the draft runs), so the
draft embedding stayed randomly initialized and accept_length collapsed
to ~1 (correct output, zero speculative gain). Load the target's
embed_tokens from the checkpoint safetensors directly for the draft,
routed through VocabParallelEmbedding.weight_loader for TP sharding.
Skipped under --load-format dummy.

Also fix a porting slip in _pp_launch_batch: self.cur_batch -> the
cur_batch local (the attribute does not exist on Scheduler in the PP
event loop).

Validated on 8x RTX 6000D, GLM-5.2-NVFP4, TP4xPP2 + EAGLE(2s/topk1/3d):
accept_length 2.71-2.91 during GSM8K and serving bench (TP8+MTP
baseline on the same host: 2.60-3.00; before the fix: ~1.07), GSM8K
20q 0.900, bs=1 median TPOT 13.5ms vs 25.5ms without MTP.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Validated the PP+spec path on a second model family (DeepSeek-V4-Flash,
DeepseekV4ForCausalLM) and fixed the two portability gaps it surfaced:

- The draft embedding checkpoint tensor is looked up from a candidate
  list: DeepSeek-V4 checkpoints store it as embed.weight, not the
  GLM/DeepSeek-V3 style model.embed_tokens.weight.
- The verify-input rebuild fallback bound now uses
  model_config.context_len instead of attn_backend.max_context_len,
  which only some backends expose (DeepseekV4AttnBackend does not; on
  FlashInfer backends the two values are identical).

Validation on 4x RTX 6000D, DeepSeek-V4-Flash (FP8), TP2xPP2 +
EAGLE(2s/topk1/3d): accept_length 2.44-2.72 sustained under GSM8K and
serving load, GSM8K 20q 0.95-1.00, greedy output correct; bench at
concurrency 1/4/8/16 completes end to end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes from an adversarial review pass over the branch diff:

Ungated default-path fixes (gate off must be behavior-identical):
- Non-overlap spec path passed pp_proxy_tensors= to every spec worker;
  only the V2 EAGLE worker accepts it (NGRAM/DFLASH/etc. would crash).
  Pass it only when pp_size > 1, mirroring the non-spec branch.
- on_verify_complete_cpu: replace the hasattr guard with a no-op hook
  on BaseTpWorker (repo convention: no defensive hasattr).

Gate hardening:
- Register SGLANG_ENABLE_PP_SPEC as a typed EnvBool in environ.py
  (renamed from SGLANG_ALLOW_PP_SPEC per naming conventions) and use
  envs.*.get() at all sites — raw string truthiness treated =0/=false
  as enabled. Removes the scattered function-local os imports.
- Assert PP+spec is incompatible with --speculative-adaptive: the
  relay slices results with the configured num_draft_tokens, which
  adaptive spec changes at runtime.
- init_lm_head PP branch fails fast on --speculative-token-map and
  EAGLE3 drafts (their head wiring is skipped by this branch).
- _load_checkpoint_tensor asserts the model path is a local directory
  (HF hub ids resolve into the loader's cache, not model_path).

PP+spec path fixes:
- _pp_spec_chain_by_rid leaked one GPU chain row per completed request
  (stored before finish flags are set, and finished reqs never re-enter
  a batch). Initialize it in init_pp_loop_state and drop finished rids
  after process_batch_result.
- Draft-worker seed sync: broadcast within the stage's TP group
  (exactly the ranks holding a draft worker) instead of adopting the
  node-local server_args seed, which diverges across nodes when
  --random-seed is unset. The caller's global rank comes from
  tp_group.ranks (the draft worker is constructed with pp_rank=0).
- Deduplicate the draft-local memory-profiling predicate into
  ModelRunner.pp_spec_draft_local computed once in __init__.

Validated on 8x RTX 6000D: GLM-5.2 TP4xPP2+MTP and DSv4-Flash
TP2xPP2+MTP dummy smokes pass with the gate on; DSv4 TP4+MTP with
--disable-overlap-schedule passes with the gate off (the ungated
regression case); GLM-5.2 TP4xPP2+MTP real weights: GSM8K 20q 0.900,
accept_length 2.67-2.80 (unchanged from before this commit).

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

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

AliceChenyy and others added 3 commits July 13, 2026 19:10
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Conflicts resolved:
- decode_cuda_graph_runner.py: kept the PP-proxy input-buffer refresh,
  adopted upstream's is_dflash_family()/is_ragged condition (sgl-project#30261),
  and renamed num_tokens_per_bs -> num_tokens_per_req in the PP proxy
  output slice (sgl-project#30977).
- eagle_worker_v2.py: dropped the local _get_plan_stream (extracted to
  a shared get_plan_stream upstream, sgl-project#31008/sgl-project#30857) and re-applied the
  pp_proxy_tensors parameter on the deduplicated
  forward_batch_generation signature.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Zero-semantic cleanups from the file-by-file pass: give the gated
non-overlap assert a message, make the fwd_batch fallback an explicit
None check, and drop blank lines left behind by the os-import removal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AliceChenyy and others added 2 commits July 16, 2026 10:21
# Conflicts:
#	python/sglang/srt/managers/scheduler_pp_mixin.py
#	python/sglang/srt/managers/tp_worker.py
#	python/sglang/srt/model_executor/model_runner.py
#	python/sglang/srt/model_executor/model_runner_kv_cache_mixin.py
#	python/sglang/srt/speculative/eagle_worker_v2.py
rows[i] kept the whole rows storage alive per entry, and when
chain_tokens is already int64 the .to() is a no-op view of the relay
buffer -- a later relay reusing that buffer would corrupt stored chains
(the row root is force-accepted by verify). Addresses the
gemini-code-assist finding on _pp_spec_store_bonus.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread python/sglang/srt/speculative/eagle_worker_v2.py Outdated
YAMY1234 and others added 10 commits September 2, 2026 19:53
# Conflicts:
#	python/sglang/srt/arg_groups/validation_hook.py
#	python/sglang/srt/model_executor/model_runner.py
The embedding read hardcoded model.model.embed_tokens, which misses draft
families that hang the embedding elsewhere (Bailing MTP word_embeddings,
Mistral consolidated tok_embeddings). Scan for the one VocabParallelEmbedding
that is not the ParallelLMHead, extend the checkpoint-name table with both
spellings (verified against the published Ling-2.0 and Mistral-Large-3
indexes), and pin the scan on real Bailing/Mistral draft classes in a unit
test.

Validated on 8x RTX PRO 6000: GLM-5.2 TP4xPP2+MTP GSM8K-100 0.950 / accept
2.78-2.84; DeepSeek-V4-Flash TP2xPP2+MTP 0.960 / accept 2.55-2.70.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	python/sglang/srt/speculative/spec_info.py
# Conflicts:
#	python/sglang/srt/speculative/eagle_worker_v2.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants