Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
6b232c1
Fix PP proxy buffer sizing and refresh for speculative verify
AliceChenyy Jul 10, 2026
c79d133
[RFC] Pipeline parallelism x speculative decoding (EAGLE/MTP) compati…
AliceChenyy Jul 10, 2026
682d323
Fix lint: apply black/isort formatting
AliceChenyy Jul 13, 2026
5718cc6
Fix PP+spec draft embedding loading; accept_length recovers to parity
AliceChenyy Jul 13, 2026
46fee6c
Generalize PP+spec beyond GLM: portable embed name and context-len bound
AliceChenyy Jul 13, 2026
3686cd9
Address self-review: harden the PP+spec gate and fix ungated regressions
AliceChenyy Jul 13, 2026
32e3751
Revert batch_result_processor.py to upstream (comment-only net diff)
AliceChenyy Jul 14, 2026
ec9c1d0
Merge upstream/main into feat/pp-spec-compat
AliceChenyy Jul 14, 2026
5b51ecb
Review polish: assert message, explicit None checks, stray blank lines
AliceChenyy Jul 14, 2026
038a4ca
Merge remote-tracking branch 'upstream/main' into feat/pp-spec-compat
AliceChenyy Jul 16, 2026
635cf59
Own the stored chain row instead of a slice view into the relay tensor
AliceChenyy Jul 16, 2026
aa737e6
Lint: black formatting for bootstrap.py
AliceChenyy Jul 17, 2026
8caeda8
Merge upstream/main into feat/pp-spec-compat
AliceChenyy Jul 27, 2026
598fa41
Fix stale attribute after merge: num_tokens_per_req -> captured_req_w…
AliceChenyy Jul 27, 2026
13a5d3d
Merge upstream/main into feat/pp-spec-compat
AliceChenyy Aug 17, 2026
06285ec
Adapt the PP+spec verify rebuild to upstream's verify-mask API
AliceChenyy Aug 17, 2026
ffd716a
Harden the PP+spec relay: ring ordering, chunked prefill, batch invar…
AliceChenyy Aug 17, 2026
090c391
Relay the drafted tree topology so PP+spec is not limited to topk=1 c…
AliceChenyy Aug 17, 2026
dc440b9
Add PP x spec tests; drop the mixed-chunk assert as unreachable
AliceChenyy Aug 17, 2026
d41e097
Carry the PP relay state as a SpecInput instead of a scheduler side t…
AliceChenyy Aug 17, 2026
2fdb5af
Merge upstream/main into feat/pp-spec-compat
AliceChenyy Aug 26, 2026
29e3244
Merge upstream/main into feat/pp-spec-compat
AliceChenyy Aug 27, 2026
b48e4d9
Merge upstream/main into feat/pp-spec-compat
AliceChenyy Aug 31, 2026
6a0ec0e
Lint: isort/black over the merge
AliceChenyy Aug 31, 2026
08ae55c
Gate the PP+spec relay reads mock-safely
AliceChenyy Sep 1, 2026
2684031
Address review: config reads, algorithm whitelist, PD gate, embed loa…
AliceChenyy Sep 1, 2026
8d78574
Reuse the target runner's load config for the draft embedding read
AliceChenyy Sep 2, 2026
cf9f9bc
Merge remote-tracking branch 'origin/main' into HEAD
YAMY1234 Sep 2, 2026
35ca910
Merge remote-tracking branch 'refs/remotes/origin/main' into HEAD
YAMY1234 Sep 3, 2026
751cb35
Merge remote-tracking branch 'refs/remotes/origin/main' into HEAD
YAMY1234 Sep 3, 2026
506d647
style: format PP speculative decoding changes
YAMY1234 Sep 3, 2026
ac3eae0
Find the draft input embedding by type instead of attribute path
AliceChenyy Sep 3, 2026
237d7fd
Merge remote-tracking branch 'origin/feat/pp-spec-compat' into feat/p…
AliceChenyy Sep 3, 2026
5171bd8
Merge remote-tracking branch 'upstream/main' into feat/pp-spec-compat
AliceChenyy Sep 8, 2026
56a9767
test: move PP speculative tests under e2e taxonomy
YAMY1234 Sep 8, 2026
4ec2d04
Merge upstream/main into feat/pp-spec-compat
YAMY1234 Sep 8, 2026
3f51320
Merge commit 'refs/guardian/main-52fecfdf' into HEAD
YAMY1234 Sep 8, 2026
96626f2
Merge branch 'main' into feat/pp-spec-compat
YAMY1234 Sep 9, 2026
5a0eaed
Merge upstream/main to include MXFP4 JIT CI fix
YAMY1234 Sep 14, 2026
7d8cf63
test: use Scheduler defaults in sampling-mask PP round trip
YAMY1234 Sep 14, 2026
9715ef9
Merge commit '03ea13a54557de52da5faab2c422da07c3727407' into codex/pr…
YAMY1234 Sep 15, 2026
f5c1ad0
Fix PP speculative output exchange ordering before tree planning
YAMY1234 Sep 15, 2026
dfbe017
Merge main to refresh upstream scheduler and auxiliary outputs
YAMY1234 Sep 15, 2026
9141098
Fix PP speculative fallback topology row strides for trees
YAMY1234 Sep 15, 2026
2bd1200
Format PP speculative topology dimensions
YAMY1234 Sep 15, 2026
f65a9f7
Merge remote-tracking branch 'origin/main' into codex/pr30775-clean-s…
YAMY1234 Sep 15, 2026
75c07c8
Compact the accepted tree path into every PP stage's KV, not just the…
AliceChenyy Sep 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions python/sglang/srt/arg_groups/validation_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,36 @@ def check_server_args(server_args: Any):
"NPU PP + speculative decoding (MTP) is only supported "
"on prefill nodes (disaggregation-mode=prefill)"
)
elif envs.SGLANG_ENABLE_PP_SPEC.get():
assert cfg.disable_overlap_schedule, (
"SGLANG_ENABLE_PP_SPEC requires --disable-overlap-schedule"
)
# The relay carries an EAGLE-shaped tree and only EAGLEWorkerV2
# tail-drafts; every other algorithm would be mis-rebuilt.
assert (
cfg.speculative_algorithm == "EAGLE"
and not cfg.enable_multi_layer_eagle
), (
"SGLANG_ENABLE_PP_SPEC supports single-layer EAGLE/MTP only, "
f"got {cfg.speculative_algorithm}"
)
# PD prefill relays topk_p / topk_index / hidden states through
# RelayPayload; the gated flow replaces that relay with its own
# and does not carry those fields.
assert cfg.disaggregation_mode == "null", (
"SGLANG_ENABLE_PP_SPEC is not compatible with --disaggregation-mode"
)
# The PP relay slices spec results with the configured
# num_draft_tokens; adaptive spec changes it at runtime.
assert not cfg.speculative_adaptive, (
"SGLANG_ENABLE_PP_SPEC is not compatible with --speculative-adaptive"
)
# Every stage rebuilds the same verify input from the relayed
# per-request state, so all stages must see the same batch.
# DP attention partitions it per DP rank.
assert not cfg.enable_dp_attention, (
"SGLANG_ENABLE_PP_SPEC is not compatible with --enable-dp-attention"
)
else:
# Non-NPU: PP + speculative decoding is not supported
assert cfg.disable_overlap_schedule and cfg.speculative_algorithm is None, (
Expand Down
4 changes: 4 additions & 0 deletions python/sglang/srt/environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -1300,6 +1300,10 @@ class Envs:
# Speculative decoding
# ===================================================================
SGLANG_ENABLE_OVERLAP_PLAN_STREAM = EnvBool(False)
# Experimental: allow pipeline parallelism x speculative decoding
# (EAGLE/MTP). Off by default; see the PP+spec RFC for constraints
# (non-overlap schedule, no DP attention).
SGLANG_ENABLE_PP_SPEC = EnvBool(False)
# Capture the per-replay attention-metadata prep (init_forward_metadata_out_graph)
# into a small CUDA graph, collapsing its host dispatch cost to one launch.
# Experimental; auto-falls back to eager if the backend's prep is not capturable.
Expand Down
131 changes: 106 additions & 25 deletions python/sglang/srt/managers/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,17 @@ def maybe_init_draft_worker(self):
self.external_corpus_manager = None
return

if (
envs.SGLANG_ENABLE_PP_SPEC.get()
and self.ps.pp_size > 1
and self.ps.pp_rank != self.ps.pp_size - 1
):
# PP+spec: the draft model (MTP layer) needs final hidden states and
# the lm_head, both of which live on the last PP stage only.
self.draft_worker = None
self.external_corpus_manager = None
return

# Launch a draft worker for speculative decoding. It builds its draft
# from this process's own config: what differs for the draft — the
# target's context length, the draft load format, its attention backend
Expand Down Expand Up @@ -1155,7 +1166,9 @@ def init_model_worker(self):
model_runner.post_capture_elastic_ep_recover()

# Dispatch the model worker
if self.spec_algorithm.is_none():
if self.spec_algorithm.is_none() or self.draft_worker is None:
# PP+spec: non-last stages have no draft worker; they run the
# verify-shaped target forward through the plain tp_worker.
self.model_worker = self.tp_worker
else:
self.model_worker = self.draft_worker
Expand Down Expand Up @@ -4391,31 +4404,99 @@ def run_batch(
batch.input_ids = None
self._copy_auxiliary_output_to_cpu(batch, batch_result)
elif not batch.spec_algorithm.is_none():
# Non-overlap: drive the V2 worker synchronously (no
# future_map relay / on_publish).
resolve_forward_inputs(batch, self.future_map)
with self._forward_isolation(batch, overlap=False):
batch_result = self.model_worker.forward_batch_generation(
batch, pp_proxy_tensors=pp_proxy_tensors
)
# The isolation restore reverted the worker's in-forward SB edits;
# re-apply what must carry to the next iter.
batch.spec_info = batch_result.next_draft_input
if batch_result.new_seq_lens is not None:
batch.seq_lens = batch_result.new_seq_lens
if batch.seq_lens_cpu is not None:
batch.seq_lens_cpu = batch_result.new_seq_lens.to("cpu")
batch.seq_lens_sum = int(batch.seq_lens_cpu.sum())
batch.input_ids = None # rebuilt next iter from draft_token
self.update_cache_from_scheduler(batch, batch_result)
# Only the last PP rank owns real results requiring D2H; other ranks
# consume device tensors rebuilt from the output ring.
batch_result.copy_done = self.device_module.Event()
if batch_result.has_sampled_token_ids and self.ps.pp_size == 1:
batch_result.copy_to_cpu(
return_logprob=batch.return_logprob,
return_hidden_states=batch.return_hidden_states,
is_verify_round = self.ps.pp_size > 1 and not (
batch.forward_mode.is_extend() or batch.is_extend_in_batch
)
# The relayed tree is what the requests carry between rounds;
# the rebuild below swaps it for this round's verify input, so
# hold on to it and put it back once the forward is done.
relay_input = batch.spec_info if is_verify_round else None
if is_verify_round:
# PP+spec decode: every stage rebuilds the same verify
# input from relayed per-req state (draft lives on the
# last stage only).
self._pp_spec_rebuild_verify_input(batch)
if not self.pp_group.is_last_rank:
# PP+spec: non-last stages run only their model chunk on
# the verify-shaped batch; sampling, accept and draft all
# live on the last stage. The plain tp_worker path already
# returns pp_hidden_states_proxy_tensors for relay.
resolve_forward_inputs(batch, self.future_map)
if is_verify_round:
from sglang.srt.speculative.eagle_utils import (
eagle_prepare_for_verify,
)

# Isolation is load-bearing: eagle_prepare_for_verify
# mutates SB fields (forward_mode -> TARGET_VERIFY,
# input_ids, out_cache_loc); without the restore the
# next get_next_batch_to_run treats this decode batch
# as extend and re-merges it (duplicate reqs).
with self._forward_isolation(batch, overlap=False):
verify_forward_batch, can_run_cuda_graph = (
eagle_prepare_for_verify(
batch.spec_info,
self.req_to_token_pool,
batch,
self.tp_worker,
)
)
batch_result = self.tp_worker.forward_batch_generation(
batch=None,
forward_batch=verify_forward_batch,
pp_proxy_tensors=pp_proxy_tensors,
is_verify=True,
)
batch_result.can_run_cuda_graph = can_run_cuda_graph
# The isolation above restores batch.out_cache_loc, but
# this stage still has to compact its own accepted-path
# KV once the last stage relays which nodes it kept, so
# the verify slots have to outlive the forward.
batch_result.spec_verify_out_cache_loc = (
verify_forward_batch.out_cache_loc
)
else:
batch_result = self.model_worker.forward_batch_generation(
batch, pp_proxy_tensors=pp_proxy_tensors
)
batch.input_ids = None
# The verify input is per-round; between iterations
# spec_info carries the relayed tree, which is
# merge/filter-safe.
batch.spec_info = relay_input
else:
# Non-overlap: drive the V2 worker synchronously (no
# future_map relay / on_publish).
resolve_forward_inputs(batch, self.future_map)
with self._forward_isolation(batch, overlap=False):
batch_result = self.model_worker.forward_batch_generation(
batch, pp_proxy_tensors=pp_proxy_tensors
)
# The isolation restore reverted the worker's in-forward SB edits;
# re-apply what must carry to the next iter. Under PP the
# tail draft already consumed the draft input in-round, and
# the next round's tree comes from the relay, so the last
# stage carries the same relayed tree as the others.
batch.spec_info = (
relay_input
if is_verify_round
else batch_result.next_draft_input
)
if batch_result.new_seq_lens is not None:
batch.seq_lens = batch_result.new_seq_lens
if batch.seq_lens_cpu is not None:
batch.seq_lens_cpu = batch_result.new_seq_lens.to("cpu")
batch.seq_lens_sum = int(batch.seq_lens_cpu.sum())
batch.input_ids = None # rebuilt next iter from draft_token
self.update_cache_from_scheduler(batch, batch_result)
# Only the last PP rank owns real results requiring D2H; other ranks
# consume device tensors rebuilt from the output ring.
batch_result.copy_done = self.device_module.Event()
if batch_result.has_sampled_token_ids and self.ps.pp_size == 1:
batch_result.copy_to_cpu(
return_logprob=batch.return_logprob,
return_hidden_states=batch.return_hidden_states,
)
else:
kwargs = (
{"pp_proxy_tensors": pp_proxy_tensors}
Expand Down
Loading
Loading