Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0da6147
fix(bcg): copy Qwen3-VL input_deepstack_embeds into a stable replay slot
bowenwan6 Aug 5, 2026
58a978b
test(bcg): unit tests for DeepStack BCG replay-slot contract
bowenwan6 Aug 5, 2026
29db2fe
refactor(bcg): shrink DeepStack replay-slot patch after adversarial r…
bowenwan6 Aug 5, 2026
a8197da
style(bcg): apply black auto-formatting for DeepStack replay-slot patch
bowenwan6 Aug 5, 2026
b1fd27a
refactor(bcg): trim DeepStack replay comments, warn on slot mismatch
JustinTong0323 Aug 6, 2026
4b6af41
Merge branch 'main' into fix/bcg-deepstack-replay-slot
JustinTong0323 Aug 7, 2026
9de2ca7
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Aug 11, 2026
85275c5
Merge branch 'main' into fix/bcg-deepstack-replay-slot
JustinTong0323 Aug 13, 2026
f2596d5
fix(bcg): fail closed on malformed DeepStack replay input
bowenwan6 Aug 15, 2026
ecf3edb
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Aug 15, 2026
7ffa726
test(bcg): prune DeepStack replay slot tests to what the code pins
JustinTong0323 Aug 15, 2026
ec15d8d
style(bcg): trim DeepStack replay slot comments
JustinTong0323 Aug 15, 2026
5f56637
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Aug 15, 2026
a687f92
feat(bcg): allowlist Qwen3-VL for breakable prefill CUDA graph
JustinTong0323 Aug 15, 2026
639cef9
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Aug 16, 2026
800e907
Merge branch 'main' into fix/bcg-deepstack-replay-slot
JustinTong0323 Aug 17, 2026
8d0e460
Merge remote-tracking branch 'upstream/main' into fix/bcg-deepstack-r…
JustinTong0323 Aug 19, 2026
72b3fba
Merge branch 'main' into fix/bcg-deepstack-replay-slot
JustinTong0323 Aug 24, 2026
74234e4
Merge branch 'main' into fix/bcg-deepstack-replay-slot
JustinTong0323 Aug 25, 2026
c31e6fe
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Aug 29, 2026
d1cd8c5
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Sep 2, 2026
32dbab0
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Sep 3, 2026
2fc7119
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Sep 8, 2026
e605c74
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Oct 5, 2026
c65396e
test(bcg): scope the MTP draft replay stub to the input_embeds slot
bowenwan6 Oct 5, 2026
6f1b06f
style(bcg): apply ruff-format to DeepStack replay-slot tests
bowenwan6 Oct 5, 2026
0d2e0a2
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Oct 5, 2026
533bbb9
fix(bcg): gate DeepStack slot to eager-tail backends, harden refresh …
JustinTong0323 Oct 9, 2026
6fc13d5
Merge remote-tracking branch 'upstream/main' into fix/bcg-deepstack-r…
JustinTong0323 Oct 9, 2026
ce3127e
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Oct 9, 2026
4d7d9cc
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Oct 11, 2026
5e4322f
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Oct 12, 2026
d73b050
refactor(bcg): declare DeepStack replay support through a protocol
bowenwan6 Oct 13, 2026
841be30
test(bcg): import the model classes at module scope
bowenwan6 Oct 13, 2026
6fcaecd
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Oct 16, 2026
b58855b
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Oct 17, 2026
ccfda8d
Merge branch 'main' into fix/bcg-deepstack-replay-slot
JustinTong0323 Oct 17, 2026
920874d
Merge branch 'main' into fix/bcg-deepstack-replay-slot
bowenwan6 Oct 21, 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
2 changes: 2 additions & 0 deletions python/sglang/srt/configs/model_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2401,6 +2401,8 @@ def is_generation_model(model_architectures: List[str], is_embedding: bool = Fal
"Qwen3_5MoeForConditionalGeneration",
"Qwen4ExpForConditionalGeneration",
"MuseGlimmerForConditionalGeneration",
"Qwen3VLForConditionalGeneration",
"Qwen3VLMoeForConditionalGeneration",
"MuseSparkForConditionalGeneration",
"KimiK3ForConditionalGeneration",
"KimiK25ForConditionalGeneration",
Expand Down
12 changes: 12 additions & 0 deletions python/sglang/srt/model_executor/cuda_graph_buffer_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,7 @@ def build_prefill_registry(
is_multimodal: bool = False,
hidden_size: int = 0,
embed_dtype: Optional[torch.dtype] = None,
deepstack_replay_width: int = 0,
enable_mamba_track: bool = False,
enable_num_token_non_padded: bool = False,
require_gathered_buffer: bool = False,
Expand Down Expand Up @@ -925,6 +926,17 @@ def _bs(bs: int, _mt: int) -> Tuple[int, ...]:
copy_from_fb=False,
)
)
if is_multimodal and deepstack_replay_width > 0:
slots.append(
GraphSlot(
"input_deepstack_embeds",
lambda _bs2, mt: (mt, deepstack_replay_width),
embed_dtype,
axis="tokens",
padding_policy=PaddingPolicy.ZERO,
copy_from_fb=False,
)
)
if enable_mamba_track:
slots.append(GraphSlot("mamba_track_indices", _bs, torch.int64, axis="bs"))
slots.append(GraphSlot("mamba_track_mask", _bs, torch.bool, axis="bs"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,15 @@
from collections.abc import Sequence
from contextlib import contextmanager, nullcontext
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from typing import (
TYPE_CHECKING,
Any,
Dict,
Optional,
Protocol,
Union,
runtime_checkable,
)

import torch
import tqdm
Expand Down Expand Up @@ -241,6 +249,57 @@ def _slice_output_rows(output: Any, num_tokens: int) -> Any:
raise TypeError(f"Unsupported full prefill CUDA graph output: {type(output)}")


@runtime_checkable
class _SupportsDeepStackReplay(Protocol):
def get_num_deepstack_embeddings(self) -> int: ...


def _resolve_deepstack_replay_width(
*, model: Any, hidden_size: int, uses_eager_prefill_tail: bool
) -> int:
# Only the eager-tail backends (BREAKABLE/FULL) bind and refresh this slot;
# tc_piecewise captures the outer forward and never reads it.
if not uses_eager_prefill_tail:
return 0
if isinstance(model, _SupportsDeepStackReplay):
return hidden_size * model.get_num_deepstack_embeddings()
return 0


def _refresh_deepstack_replay_slot(
slot: torch.Tensor, deepstack_embeds: Optional[torch.Tensor]
) -> None:
"""Sync the captured DeepStack slot for this replay.

Absence zeroes the slot so previous rows cannot bleed through the LM's
``add_``; a non-empty tensor that does not fit raises instead — replaying
with zeros would silently serve the request without its visual part.
"""
if deepstack_embeds is None or deepstack_embeds.numel() == 0:
slot.zero_()
return

num_tokens = slot.shape[0]
if (
deepstack_embeds.ndim != slot.ndim
or deepstack_embeds.shape[0] > num_tokens
or deepstack_embeds.shape[1:] != slot.shape[1:]
or deepstack_embeds.dtype != slot.dtype
or deepstack_embeds.device != slot.device
):
raise RuntimeError(
"input_deepstack_embeds does not fit the captured BCG replay slot: "
f"got shape={tuple(deepstack_embeds.shape)}, "
f"dtype={deepstack_embeds.dtype}, device={deepstack_embeds.device}; "
f"slot holds up to {num_tokens} rows of {tuple(slot.shape[1:])} "
f"{slot.dtype} on {slot.device}."
)

num_rows = deepstack_embeds.shape[0]
slot[:num_rows].copy_(deepstack_embeds)
slot[num_rows:].zero_()


@dataclass(frozen=True)
class _ChunkedPrefixCaptureBuffers:
"""Runner-owned tensors shared by every prefix and token-bucket variant.
Expand Down Expand Up @@ -430,6 +489,12 @@ def __init__(self, model_runner: ModelRunner):

self.mamba_track_enabled = self._is_mamba_track_enabled()

deepstack_replay_width = _resolve_deepstack_replay_width(
model=self.model_runner.model,
hidden_size=self.model_runner.model_config.hidden_size,
uses_eager_prefill_tail=self._uses_eager_prefill_tail(),
)

# --- buffers ---------------------------------------------------
# `hidden_size` here sizes only the multimodal `input_embeds` buffer,
# which `general_mm_embed_routine` copies the merged text+media
Expand All @@ -446,6 +511,7 @@ def __init__(self, model_runner: ModelRunner):
hidden_size=input_embeds_hidden_size,
dtype=self.model_runner.dtype,
enable_mamba_track=self.mamba_track_enabled,
deepstack_replay_width=deepstack_replay_width,
enable_input_embeds=(self.is_multimodal or self._use_draft_input_embeds),
pp_size=self.pp_size,
is_first_pp_rank=self.model_runner.pp_group.is_first_rank,
Expand All @@ -471,6 +537,7 @@ def __init__(self, model_runner: ModelRunner):
is_multimodal=self.is_multimodal,
hidden_size=input_embeds_hidden_size,
embed_dtype=self.model_runner.dtype,
deepstack_replay_width=deepstack_replay_width,
enable_mamba_track=self.mamba_track_enabled,
# FullCG always pads to a capture bucket. Models that mask padded
# hidden rows need the live boundary even without expert parallelism.
Expand Down Expand Up @@ -874,6 +941,13 @@ def _run_forward(self, forward_batch: ForwardBatch, num_tokens: int):
kwargs = _build_layer_model_forward_kwargs(
self.layer_model, forward_batch, pp_proxy_tensors
)
# DeepStack replay slot: bound by name like the other optional
# transformer inputs above, but sourced from the runner's
# capture buffer rather than the forward batch.
if self.buffer_registry.has_slot("input_deepstack_embeds"):
kwargs["input_deepstack_embeds"] = self.buffer_registry.get_slot(
"input_deepstack_embeds"
).slice_for(1, num_tokens)
if pp_proxy_tensors is not None:
input_ids = None
for embeds_name in ("input_embeds", "inputs_embeds"):
Expand Down Expand Up @@ -2183,6 +2257,13 @@ def replay_layer_forward(*args, **layer_kwargs):
and self.buffer_registry.has_slot("input_embeds")
):
self._fill_input_embeds_slot(args, layer_kwargs, static_num_tokens)
if self.buffer_registry.has_slot("input_deepstack_embeds"):
_refresh_deepstack_replay_slot(
slot=self.buffer_registry.get_slot(
"input_deepstack_embeds"
).slice_for(1, static_num_tokens),
deepstack_embeds=layer_kwargs.get("input_deepstack_embeds"),
)
hs = self.backend.replay(shape_key, static_forward_batch, **kwargs)
hs = self._restore_qwen_bcg_hc_output(hs)
return _slice_output_rows(hs, raw_num_tokens) if full_path else hs
Expand Down
10 changes: 10 additions & 0 deletions python/sglang/srt/model_executor/runner_utils/buffers.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ class PrefillInputBuffers(ForwardInputBuffers):
positions: torch.Tensor
input_embeds: Optional[torch.Tensor]
mrope_positions: Optional[torch.Tensor]
input_deepstack_embeds: Optional[torch.Tensor]
pp_proxy_tensors: Optional[Dict[str, torch.Tensor]]

@classmethod
Expand All @@ -294,6 +295,7 @@ def create(
hidden_size: int,
dtype: torch.dtype,
enable_mamba_track: bool,
deepstack_replay_width: int = 0,
enable_input_embeds: Optional[bool] = None,
pp_size: int = 1,
is_first_pp_rank: bool = False,
Expand Down Expand Up @@ -331,8 +333,15 @@ def create(

if is_multimodal:
mrope_positions = torch.zeros((3, max_num_tokens), dtype=torch.int64)
if deepstack_replay_width > 0:
input_deepstack_embeds = torch.zeros(
(max_num_tokens, deepstack_replay_width), dtype=dtype
)
else:
input_deepstack_embeds = None
else:
mrope_positions = None
input_deepstack_embeds = None

pp_proxy_tensors = (
allocate_pp_proxy_tensors(
Expand All @@ -359,5 +368,6 @@ def create(
positions=positions,
input_embeds=input_embeds,
mrope_positions=mrope_positions,
input_deepstack_embeds=input_deepstack_embeds,
pp_proxy_tensors=pp_proxy_tensors,
)
4 changes: 4 additions & 0 deletions python/sglang/srt/models/qwen3_vl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1411,6 +1411,10 @@ def __init__(
# For EAGLE3 support
self.capture_aux_hidden_states = False

# Opt-in for the prefill graph runner's DeepStack replay slot; 0 means no slot.
def get_num_deepstack_embeddings(self) -> int:
return self.num_deepstack_embeddings

def separate_deepstack_embeds(self, embedding):
assert embedding.shape[-1] % (1 + self.num_deepstack_embeddings) == 0, (
f"hidden_state of {embedding.shape} should be divisible by ({1 + self.num_deepstack_embeddings})"
Expand Down
Loading
Loading