Improve CUDA graph and speculative execution output handling (#37329)

Co-authored-by: jiayisuse <jiayisuse@fb.com>
Co-authored-by: Yinghai Lu <yinghai@meta.com>
Co-authored-by: Hao Zhang <zhisbug@users.noreply.github.com>
Co-authored-by: Yichao Fu <yichaofu@meta.com>
This commit is contained in:
Lianmin Zheng
2026-09-02 17:25:27 -07:00
committed by GitHub
co-authored by jiayisuse Yinghai Lu Hao Zhang Yichao Fu
parent db1eb48651
commit 87d60a2229
8 changed files with 173 additions and 14 deletions
@@ -42,9 +42,10 @@ class AuxHiddenStatePacker:
def finalize(self) -> torch.Tensor:
"""Return the packed buffer; callers guard the empty case on ``len()``."""
assert (
self._buffer is not None and self._idx == self._num_captures
), f"captured {self._idx} of {self._num_captures} aux hidden states"
if self._buffer is None or self._idx != self._num_captures:
raise RuntimeError(
f"captured {self._idx} of {self._num_captures} aux hidden states"
)
return self._buffer
@@ -55,4 +56,6 @@ AuxHiddenStateAccumulator = Union[List[torch.Tensor], AuxHiddenStatePacker]
def pack_aux_hidden_states(aux_hidden_states: AuxHiddenStates) -> torch.Tensor:
if isinstance(aux_hidden_states, torch.Tensor):
return aux_hidden_states
if len(aux_hidden_states) == 1:
return aux_hidden_states[0]
return torch.cat(aux_hidden_states, dim=-1)
@@ -124,8 +124,10 @@ from sglang.srt.model_executor.model_runner_components.kv_pool_runtime import (
is_post_capture_kv_active,
)
from sglang.srt.model_executor.model_runner_components.layer_setup import (
AttentionAndMoeLayers,
ModelLayerInfo,
adjust_hybrid_swa_layer_ids,
compute_attention_and_moe_layers,
resolve_layer_indices,
)
from sglang.srt.model_executor.model_runner_components.load_model_utils import (
@@ -1435,6 +1437,10 @@ class ModelRunner:
return DecodeCudaGraphRunner
def get_cuda_graph_layers(self, layer_model) -> AttentionAndMoeLayers:
"""Return the model layers used by prefill CUDA graph execution."""
return compute_attention_and_moe_layers(layer_model)
def init_decode_cuda_graph(self):
self.decode_cuda_graph_runner = None
capture = capture_decode_graph(model_runner=self)
@@ -31,9 +31,6 @@ from sglang.srt.model_executor.graph_memory_usage import (
)
from sglang.srt.model_executor.graph_shared_output import GraphSharedOutput
from sglang.srt.model_executor.hook_manager import register_forward_hooks
from sglang.srt.model_executor.model_runner_components.layer_setup import (
compute_attention_and_moe_layers,
)
from sglang.srt.model_executor.runner import (
EagerRunner,
PrefillCudaGraphRunner,
@@ -444,7 +441,7 @@ def capture_prefill_graph(
model_runner.moe_fusions,
model_runner.dsa_indexers,
model_runner.mha_companion_layers,
) = compute_attention_and_moe_layers(layer_model)
) = model_runner.get_cuda_graph_layers(layer_model)
(
model_runner.attention_layers,
model_runner.mha_companion_layers,
+8 -1
View File
@@ -484,6 +484,8 @@ _DSPARK_SKIPPED_WEIGHT_PREFIXES = ("lm_head.", "rotary_emb.")
class DSparkDraftMixin:
supports_pre_gather_target_hidden_projection = True
def __init__(self, config, quant_config=None, prefix: str = "") -> None:
super().__init__(config=config, quant_config=quant_config, prefix=prefix)
self._fused_kv_write_cache = None
@@ -737,8 +739,13 @@ class DSparkDraftMixin:
cache_loc: torch.Tensor,
cache_loc_2d: Optional[torch.Tensor] = None,
commit_lens: Optional[torch.Tensor] = None,
target_hidden_is_projected: bool = False,
) -> None:
ctx_hidden = self.project_target_hidden(target_hidden)
ctx_hidden = (
target_hidden
if target_hidden_is_projected
else self.project_target_hidden(target_hidden)
)
bundle = self._fused_kv_write_bundle(pool)
if bundle is not None:
@@ -42,6 +42,7 @@ class TargetHiddenKvInjector:
commit_lens: Optional[torch.Tensor] = None,
state_slot: Optional[torch.Tensor] = None,
final_pos: Optional[torch.Tensor] = None,
target_hidden_is_projected: bool = False,
) -> None:
if target_hidden is None or target_hidden.numel() == 0:
return
@@ -71,6 +72,11 @@ class TargetHiddenKvInjector:
pool = self.draft_model_runner.token_to_kv_pool
if hasattr(pool, "set_swa_key_buffer_radix_fused_norm_rope"):
if target_hidden_is_projected:
raise RuntimeError(
"Pre-gather target-hidden projection is not supported by the "
"DSpark MLA KV injection path."
)
self._inject_mla(
pool=pool,
target_hidden=target_hidden,
@@ -91,6 +97,7 @@ class TargetHiddenKvInjector:
cache_loc=cache_loc,
cache_loc_2d=cache_loc_2d,
commit_lens=commit_lens,
target_hidden_is_projected=target_hidden_is_projected,
)
def _inject_mla(
@@ -1,7 +1,7 @@
import logging
from contextlib import nullcontext
from dataclasses import replace
from typing import Optional
from typing import Callable, Optional, Protocol, runtime_checkable
import torch
@@ -19,6 +19,7 @@ from sglang.srt.managers.tp_worker import TpModelWorker
from sglang.srt.model_executor.cuda_graph_config import Backend
from sglang.srt.model_executor.forward_batch_info import (
CaptureHiddenMode,
ForwardMode,
compute_position,
)
from sglang.srt.runtime_context import (
@@ -88,6 +89,41 @@ logger = logging.getLogger(__name__)
_is_npu = is_npu()
@runtime_checkable
class _SupportsDSparkTargetHiddenProjection(Protocol):
def set_dspark_target_hidden_projector(
self,
projector: Callable[[torch.Tensor], torch.Tensor],
*,
num_context_features: int,
) -> bool: ...
def should_project_dspark_target_hidden(
self,
*,
forward_mode: ForwardMode,
capture_hidden_mode: CaptureHiddenMode,
) -> bool: ...
def _configure_target_hidden_projection(
*, target_model, draft_model, is_deepseek_v4_draft: bool
) -> bool:
"""Install the optional token-major projection before the target SP gather."""
if is_deepseek_v4_draft:
return False
if not draft_model.supports_pre_gather_target_hidden_projection:
return False
if not isinstance(target_model, _SupportsDSparkTargetHiddenProjection):
return False
return bool(
target_model.set_dspark_target_hidden_projector(
draft_model.project_target_hidden,
num_context_features=int(draft_model.num_context_features),
)
)
class DSparkWorkerV2(BaseSpecWorker):
def __init__(
@@ -227,6 +263,7 @@ class DSparkWorkerV2(BaseSpecWorker):
),
lm_head=lm_head,
)
self._target_hidden_projection_enabled = False
self._verify_planner = DSparkVerifyPlanner(
draft_model=self.draft_model,
@@ -377,6 +414,16 @@ class DSparkWorkerV2(BaseSpecWorker):
def init_attention_backends(self):
with self._draft_context():
self._draft_worker.init_attention_backends()
self._target_hidden_projection_enabled = _configure_target_hidden_projection(
target_model=self.target_worker.model_runner.model,
draft_model=self.draft_model,
is_deepseek_v4_draft=self._draft_is_moe,
)
if self._target_hidden_projection_enabled and self.ps.tp_rank == 0:
logger.info(
"DSpark prefill target-hidden projection runs before "
"sequence-parallel gather."
)
self._need_mamba_verify_commit = mambaish_config(
self.model_runner.model_config
) is not None and hasattr(
@@ -487,6 +534,14 @@ class DSparkWorkerV2(BaseSpecWorker):
batch_output = self.target_worker.forward_batch_generation(
batch, capture_hidden_mode=CaptureHiddenMode.FULL
)
# BCG replay skips model-side Python, so re-evaluate the same pure predicate.
target_hidden_is_projected = (
self._target_hidden_projection_enabled
and self.target_worker.model_runner.model.should_project_dspark_target_hidden(
forward_mode=batch.forward_mode,
capture_hidden_mode=CaptureHiddenMode.FULL,
)
)
logits_output = batch_output.logits_output
next_token_ids = batch_output.next_token_ids
self._tp_sync.sync(SpecTpSyncSite.DSPARK_TARGET, next_token_ids)
@@ -542,6 +597,7 @@ class DSparkWorkerV2(BaseSpecWorker):
positions=positions,
state_slot=state_slot,
final_pos=final_pos,
target_hidden_is_projected=target_hidden_is_projected,
)
# Avoid copying large hidden-state buffers to CPU in overlap scheduling.
logits_output.hidden_states = None