[BCG][3/N] Enable bcg on dsa & deepep a2a backend (#31987)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yuwei An
2026-07-31 16:45:18 -07:00
committed by GitHub
co-authored by Claude Fable 5
parent 3c5f115741
commit 3e0f7c3f30
13 changed files with 319 additions and 116 deletions
+1
View File
@@ -536,6 +536,7 @@ def _maybe_prepare_mlp_sync_batch(batch: ScheduleBatch, model_runner):
if require_mlp_sync(model_runner.server_args): if require_mlp_sync(model_runner.server_args):
prepare_mlp_sync_batch_raw( prepare_mlp_sync_batch_raw(
batch, batch,
model_runner=model_runner,
dp_size=model_runner.server_args.dp_size, dp_size=model_runner.server_args.dp_size,
attn_tp_size=get_parallel().attn_tp_size, attn_tp_size=get_parallel().attn_tp_size,
attn_cp_size=model_runner.ps.attn_cp_size, attn_cp_size=model_runner.ps.attn_cp_size,
+66 -1
View File
@@ -8,6 +8,10 @@ import torch
from sglang.kernels.ops.quantization.fp8_kernel import is_fp8_fnuz from sglang.kernels.ops.quantization.fp8_kernel import is_fp8_fnuz
from sglang.srt.environ import envs from sglang.srt.environ import envs
from sglang.srt.layers import deep_gemm_wrapper from sglang.srt.layers import deep_gemm_wrapper
from sglang.srt.layers.dp_attention import (
get_is_extend_in_batch,
set_is_extend_in_batch,
)
from sglang.srt.layers.moe import ( from sglang.srt.layers.moe import (
get_deepep_mode, get_deepep_mode,
get_moe_a2a_backend, get_moe_a2a_backend,
@@ -21,10 +25,20 @@ from sglang.srt.layers.moe.token_dispatcher.deepep import (
DeepEPLLCombineInput, DeepEPLLCombineInput,
DeepEPNormalCombineInput, DeepEPNormalCombineInput,
) )
from sglang.srt.layers.moe.topk import TopKOutput, TopKOutputChecker from sglang.srt.layers.moe.topk import (
StandardTopKOutput,
TopKOutput,
TopKOutputChecker,
)
from sglang.srt.layers.quantization.base_config import QuantizationConfig from sglang.srt.layers.quantization.base_config import QuantizationConfig
from sglang.srt.layers.quantization.fp8 import Fp8Config from sglang.srt.layers.quantization.fp8 import Fp8Config
from sglang.srt.layers.quantization.w4afp8 import W4AFp8Config, W4AFp8MoEMethod from sglang.srt.layers.quantization.w4afp8 import W4AFp8Config, W4AFp8MoEMethod
from sglang.srt.model_executor.runner_backend_utils.breakable_cuda_graph import (
eager_on_graph,
)
from sglang.srt.model_executor.runner_backend_utils.breakable_cuda_graph.context import (
is_in_breakable_cuda_graph,
)
from sglang.srt.model_executor.runner_backend_utils.tc_piecewise_cuda_graph import ( from sglang.srt.model_executor.runner_backend_utils.tc_piecewise_cuda_graph import (
is_in_tc_piecewise_cuda_graph, is_in_tc_piecewise_cuda_graph,
) )
@@ -155,11 +169,62 @@ class DeepEPMoE(FusedMoE):
deep_gemm_wrapper.ENABLE_JIT_DEEPGEMM deep_gemm_wrapper.ENABLE_JIT_DEEPGEMM
), f"DeepEP {self.deepep_mode} mode requires deep_gemm" ), f"DeepEP {self.deepep_mode} mode requires deep_gemm"
def _a2a_forward_with_output_impl(
self,
hidden_states: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor,
router_logits: torch.Tensor,
output: torch.Tensor,
) -> None:
# eager run under breakable cuda graph
saved_is_extend_in_batch = get_is_extend_in_batch()
set_is_extend_in_batch(True)
try:
output.copy_(
self.forward_impl(
hidden_states,
StandardTopKOutput(topk_weights, topk_ids, router_logits),
)
)
finally:
set_is_extend_in_batch(saved_is_extend_in_batch)
def _a2a_forward_capture_stub(
self,
hidden_states: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor,
router_logits: torch.Tensor,
output: torch.Tensor,
) -> None:
# Capture pass only: record the buffer address, skip the
# rank-coupled a2a. Warmup and replay run the real body.
output.zero_()
a2a_forward_with_output = eager_on_graph(
True, capture_stub=_a2a_forward_capture_stub
)(_a2a_forward_with_output_impl)
def forward( def forward(
self, self,
hidden_states: torch.Tensor, hidden_states: torch.Tensor,
topk_output: TopKOutput, topk_output: TopKOutput,
): ):
# DeepEP NORMAL mode is not capturable; run it as an eager node.
if is_in_breakable_cuda_graph():
assert TopKOutputChecker.format_is_standard(
topk_output
), "Only standard topk output is supported for breakable cuda graph"
output = torch.empty_like(hidden_states)
self.a2a_forward_with_output(
hidden_states,
topk_output.topk_weights,
topk_output.topk_ids,
topk_output.router_logits,
output,
)
return output
if is_in_tc_piecewise_cuda_graph(): if is_in_tc_piecewise_cuda_graph():
assert TopKOutputChecker.format_is_standard( assert TopKOutputChecker.format_is_standard(
topk_output topk_output
+9
View File
@@ -269,6 +269,7 @@ from sglang.srt.sampling.sampling_batch_info import SamplingBatchInfo
from sglang.srt.sampling.sampling_params import TOP_K_ALL from sglang.srt.sampling.sampling_params import TOP_K_ALL
from sglang.srt.server_args import PortArgs, ServerArgs from sglang.srt.server_args import PortArgs, ServerArgs
from sglang.srt.session.session_controller import SessionController from sglang.srt.session.session_controller import SessionController
from sglang.srt.speculative.base_spec_worker import BaseSpecWorker
from sglang.srt.speculative.dflash_utils import validate_dflash_request from sglang.srt.speculative.dflash_utils import validate_dflash_request
from sglang.srt.speculative.eagle_utils import get_draft_recurrent_hidden_state_spec from sglang.srt.speculative.eagle_utils import get_draft_recurrent_hidden_state_spec
from sglang.srt.speculative.spec_info import SpeculativeAlgorithm from sglang.srt.speculative.spec_info import SpeculativeAlgorithm
@@ -1882,7 +1883,15 @@ class Scheduler(
) )
def init_dp_attn_adapter(self) -> None: def init_dp_attn_adapter(self) -> None:
# Spec workers have no .model_runner of their own; the prefill graph
# runner that votes belongs to the target model.
target_worker = (
self.tp_worker.target_worker
if isinstance(self.tp_worker, BaseSpecWorker)
else self.tp_worker
)
self.dp_attn_adapter = SchedulerDPAttnAdapter( self.dp_attn_adapter = SchedulerDPAttnAdapter(
model_runner=target_worker.model_runner,
tp_group=self.tp_group, tp_group=self.tp_group,
req_to_token_pool=self.req_to_token_pool, req_to_token_pool=self.req_to_token_pool,
token_to_kv_pool_allocator=self.token_to_kv_pool_allocator, token_to_kv_pool_allocator=self.token_to_kv_pool_allocator,
@@ -33,6 +33,7 @@ from sglang.srt.utils.common import require_mlp_tp_gather
if TYPE_CHECKING: if TYPE_CHECKING:
from sglang.srt.distributed.parallel_state import GroupCoordinator from sglang.srt.distributed.parallel_state import GroupCoordinator
from sglang.srt.model_executor.model_runner import ModelRunner
_ENABLE_METRICS_DP_ATTENTION = envs.SGLANG_ENABLE_METRICS_DP_ATTENTION.get() _ENABLE_METRICS_DP_ATTENTION = envs.SGLANG_ENABLE_METRICS_DP_ATTENTION.get()
@@ -83,11 +84,11 @@ class MLPSyncBatchInfo:
num_tokens: int num_tokens: int
num_tokens_for_logprob: int num_tokens_for_logprob: int
can_cuda_graph: bool can_run_decode_cuda_graph: bool
can_run_prefill_cuda_graph: bool
is_extend_in_batch: bool is_extend_in_batch: bool
local_can_run_tbo: bool local_can_run_tbo: bool
local_forward_mode: int local_forward_mode: int
can_run_breakable_cuda_graph: bool
# some gathered elements # some gathered elements
tp0_info: torch.Tensor = None tp0_info: torch.Tensor = None
@@ -102,11 +103,11 @@ class MLPSyncBatchInfo:
[ [
self.num_tokens, self.num_tokens,
self.num_tokens_for_logprob, self.num_tokens_for_logprob,
int(self.can_cuda_graph), int(self.can_run_decode_cuda_graph),
int(self.is_extend_in_batch), int(self.is_extend_in_batch),
int(self.local_can_run_tbo), int(self.local_can_run_tbo),
self.local_forward_mode, self.local_forward_mode,
int(self.can_run_breakable_cuda_graph), int(self.can_run_prefill_cuda_graph),
], ],
device=device, device=device,
dtype=dtype, dtype=dtype,
@@ -117,11 +118,11 @@ class MLPSyncBatchInfo:
[ [
0, # num_tokens 0, # num_tokens
0, # num_tokens_for_logprob 0, # num_tokens_for_logprob
1, # can_cuda_graph 1, # can_run_decode_cuda_graph
0, # is_extend_in_batch 0, # is_extend_in_batch
1, # local_can_run_tbo 1, # local_can_run_tbo
ForwardMode.IDLE.value, # local_forward_mode ForwardMode.IDLE.value, # local_forward_mode
0, # can_run_breakable_cuda_graph 0, # can_run_prefill_cuda_graph
], ],
device=device, device=device,
dtype=dtype, dtype=dtype,
@@ -184,9 +185,9 @@ class MLPSyncBatchInfo:
cpu_data = tp0_info[:, :2].cpu() cpu_data = tp0_info[:, :2].cpu()
self.global_num_tokens = cpu_data[:, 0].tolist() self.global_num_tokens = cpu_data[:, 0].tolist()
self.global_num_tokens_for_logprob = cpu_data[:, 1].tolist() self.global_num_tokens_for_logprob = cpu_data[:, 1].tolist()
self.can_cuda_graph = bool(tp0_info[:, 2].min().item()) self.can_run_decode_cuda_graph = bool(tp0_info[:, 2].min().item())
self.is_extend_in_batch = bool(tp0_info[:, 3].max().item()) self.is_extend_in_batch = bool(tp0_info[:, 3].max().item())
self.can_run_breakable_cuda_graph = bool(tp0_info[:, 6].min().item()) self.can_run_prefill_cuda_graph = bool(tp0_info[:, 6].min().item())
if _ENABLE_METRICS_DP_ATTENTION: if _ENABLE_METRICS_DP_ATTENTION:
self.dp_cooperation_info = DPCooperationInfo.create(tp0_info[:, 5].tolist()) self.dp_cooperation_info = DPCooperationInfo.create(tp0_info[:, 5].tolist())
@@ -212,12 +213,13 @@ def _update_gather_batch(
batch.global_forward_mode = mlp_sync_info.global_forward_mode batch.global_forward_mode = mlp_sync_info.global_forward_mode
# Check forward mode for cuda graph # Check forward mode for cuda graph
batch.can_run_dp_cuda_graph = mlp_sync_info.can_cuda_graph batch.can_run_dp_cuda_graph = mlp_sync_info.can_run_decode_cuda_graph
batch.can_run_dp_breakable_cuda_graph = mlp_sync_info.can_run_breakable_cuda_graph batch.can_run_dp_breakable_cuda_graph = mlp_sync_info.can_run_prefill_cuda_graph
def prepare_mlp_sync_batch_raw( def prepare_mlp_sync_batch_raw(
local_batch: ScheduleBatch, local_batch: ScheduleBatch,
model_runner: ModelRunner,
dp_size: int, dp_size: int,
attn_tp_size: int, attn_tp_size: int,
attn_cp_size: int, attn_cp_size: int,
@@ -256,19 +258,38 @@ def prepare_mlp_sync_batch_raw(
) )
skip_all_gather = envs.SGLANG_SCHEDULER_SKIP_ALL_GATHER.get() skip_all_gather = envs.SGLANG_SCHEDULER_SKIP_ALL_GATHER.get()
can_cuda_graph = ( can_run_decode_cuda_graph = (
local_batch is None local_batch is None
or local_batch.forward_mode.is_decode_or_idle() or local_batch.forward_mode.is_decode_or_idle()
or local_batch.forward_mode.is_prebuilt() or local_batch.forward_mode.is_prebuilt()
) and not disable_cuda_graph ) and not disable_cuda_graph
# Idle/None ranks are permissive (like can_cuda_graph): the all-gather breakable_prefill = check_cuda_graph_backend(Phase.PREFILL, Backend.BREAKABLE)
# min()-reduces this across DP ranks, so a prefill batch with idle ranks prefill_graph_runner = (
# still resolves to True (idle ranks become a padded dummy extend). model_runner.prefill_cuda_graph_runner if breakable_prefill else None
can_run_breakable_cuda_graph = ( )
can_run_prefill_cuda_graph = (
local_batch is None local_batch is None
or local_batch.forward_mode.is_idle() or local_batch.forward_mode.is_idle()
or local_batch.forward_mode in (ForwardMode.EXTEND, ForwardMode.MIXED) # Breakable Cuda Graph Backend Check.
) and check_cuda_graph_backend(Phase.PREFILL, Backend.BREAKABLE) or (
local_batch.forward_mode in (ForwardMode.EXTEND, ForwardMode.MIXED)
and (
prefill_graph_runner is None
or prefill_graph_runner.can_replay_locally(
batch_size=local_batch.batch_size(),
num_tokens=local_batch.extend_num_tokens,
input_embeds=local_batch.input_embeds,
replace_embeds=None,
prefix_lens=local_batch.prefix_lens,
is_target_verify=local_batch.forward_mode.is_target_verify(),
capture_hidden_mode=None,
return_logprob=local_batch.return_logprob,
lora_ineligible=prefill_graph_runner.enable_lora,
)
)
and breakable_prefill
)
)
is_extend_in_batch = local_batch.forward_mode.is_extend() if local_batch else False is_extend_in_batch = local_batch.forward_mode.is_extend() if local_batch else False
if local_batch is not None: if local_batch is not None:
@@ -307,11 +328,11 @@ def prepare_mlp_sync_batch_raw(
cp_size=attn_cp_size, cp_size=attn_cp_size,
num_tokens=num_tokens, num_tokens=num_tokens,
num_tokens_for_logprob=num_tokens_for_logprob, num_tokens_for_logprob=num_tokens_for_logprob,
can_cuda_graph=can_cuda_graph, can_run_decode_cuda_graph=can_run_decode_cuda_graph,
can_run_prefill_cuda_graph=can_run_prefill_cuda_graph,
is_extend_in_batch=is_extend_in_batch, is_extend_in_batch=is_extend_in_batch,
local_can_run_tbo=local_can_run_tbo, local_can_run_tbo=local_can_run_tbo,
local_forward_mode=local_forward_mode, local_forward_mode=local_forward_mode,
can_run_breakable_cuda_graph=can_run_breakable_cuda_graph,
) )
if not skip_all_gather: if not skip_all_gather:
@@ -364,6 +385,7 @@ def prepare_mlp_sync_batch_raw(
@dataclass(kw_only=True, slots=True, frozen=True) @dataclass(kw_only=True, slots=True, frozen=True)
class SchedulerDPAttnAdapter: class SchedulerDPAttnAdapter:
model_runner: ModelRunner
tp_group: GroupCoordinator tp_group: GroupCoordinator
req_to_token_pool: ReqToTokenPool req_to_token_pool: ReqToTokenPool
token_to_kv_pool_allocator: BaseTokenToKVPoolAllocator token_to_kv_pool_allocator: BaseTokenToKVPoolAllocator
@@ -379,6 +401,7 @@ class SchedulerDPAttnAdapter:
def prepare_mlp_sync_batch(self, local_batch: ScheduleBatch): def prepare_mlp_sync_batch(self, local_batch: ScheduleBatch):
return prepare_mlp_sync_batch_raw( return prepare_mlp_sync_batch_raw(
local_batch, local_batch,
model_runner=self.model_runner,
dp_size=self.server_args.dp_size, dp_size=self.server_args.dp_size,
attn_tp_size=self.ps.attn_tp_size, attn_tp_size=self.ps.attn_tp_size,
attn_cp_size=self.ps.attn_cp_size, attn_cp_size=self.ps.attn_cp_size,
@@ -50,6 +50,7 @@ import tqdm
from sglang.kernels.ops.kvcache.kv_indices import ( from sglang.kernels.ops.kvcache.kv_indices import (
create_chunked_prefix_cache_kv_indices, create_chunked_prefix_cache_kv_indices,
) )
from sglang.srt.configs.model_config import is_deepseek_dsa
from sglang.srt.distributed.parallel_state import graph_capture from sglang.srt.distributed.parallel_state import graph_capture
from sglang.srt.layers.attention.dsa.utils import is_dsa_enable_prefill_cp from sglang.srt.layers.attention.dsa.utils import is_dsa_enable_prefill_cp
from sglang.srt.layers.dp_attention import ( from sglang.srt.layers.dp_attention import (
@@ -236,6 +237,11 @@ class PrefillCudaGraphRunner(BaseCudaGraphRunner):
buffer population, attention metadata init, and output slicing. buffer population, attention metadata init, and output slicing.
""" """
# DSA forces use_mha=False in BCG capture/replay, so the sparse path
# serves any prefix and the MHA-prefix ban does not apply. Class
# default keeps __new__-built test instances on the ban.
dsa_sparse_prefill_forced: bool = False
def __init__(self, model_runner: ModelRunner): def __init__(self, model_runner: ModelRunner):
super().__init__(model_runner) super().__init__(model_runner)
# --- model flags ---------------------------------------------- # --- model flags ----------------------------------------------
@@ -316,6 +322,10 @@ class PrefillCudaGraphRunner(BaseCudaGraphRunner):
source=self.buffers, source=self.buffers,
) )
self.dsa_sparse_prefill_forced = is_deepseek_dsa(
self.model_runner.model_config.hf_config
)
self.attention_layers = self.model_runner.attention_layers self.attention_layers = self.model_runner.attention_layers
self.mha_companion_layers = self.model_runner.mha_companion_layers self.mha_companion_layers = self.model_runner.mha_companion_layers
self.has_mha_companion_layers = any( self.has_mha_companion_layers = any(
@@ -986,14 +996,6 @@ class PrefillCudaGraphRunner(BaseCudaGraphRunner):
static_forward_batch=static_forward_batch, static_forward_batch=static_forward_batch,
) )
def _has_unsupported_mha_prefix(self, forward_batch: ForwardBatch) -> bool:
return (
self.prefill_backend_name == Backend.BREAKABLE
and self.has_mha_companion_layers
and forward_batch.extend_prefix_lens_cpu is not None
and any(forward_batch.extend_prefix_lens_cpu)
)
@staticmethod @staticmethod
def _restore_mha_capture_state(forward_batch: ForwardBatch) -> None: def _restore_mha_capture_state(forward_batch: ForwardBatch) -> None:
"""Restore Python state omitted from breakable graph segments.""" """Restore Python state omitted from breakable graph segments."""
@@ -1001,59 +1003,113 @@ class PrefillCudaGraphRunner(BaseCudaGraphRunner):
forward_batch.mha_return_lse = False forward_batch.mha_return_lse = False
forward_batch.set_attn_attend_prefix_cache(False) forward_batch.set_attn_attend_prefix_cache(False)
def can_run_graph(self, forward_batch: ForwardBatch) -> bool: def can_replay_locally(
if self._is_full_backend and forward_batch.batch_size > self._capture_req_slots: self,
*,
batch_size: int,
num_tokens: Optional[int],
input_embeds,
replace_embeds,
prefix_lens,
is_target_verify: bool,
capture_hidden_mode,
return_logprob: bool,
lora_ineligible: bool = False,
chunked_prefix_uncapturable: bool = False,
) -> bool:
"""Rank-local replay eligibility: the single source of truth for
``can_run_graph`` (ForwardBatch, forward time) and the dp mlp-sync
vote (ScheduleBatch, schedule time) — all dp ranks must reach the
same replay-vs-eager decision or their collectives mismatch. Pass
``capture_hidden_mode=None`` when unknown at the call site (it is
rank-uniform; forward-time-only checking cannot split the group).
"""
if self._is_full_backend and batch_size > self._capture_req_slots:
return False return False
# LoRA batches may only replay the graph when prepare_lora_batch put # LoRA replays need prepare_lora_batch's static metadata. lora_manager
# their metadata in the static buffers (same predicate); keyed off # keeps LoRA prefill eager on every rank under dp attention, so the
# enable_lora, not lora_ids, which is non-None even without LoRA. # schedule-time vote derives this from enable_lora alone.
if self.enable_lora and not ( if lora_ineligible:
self._capture_lora return False
and self.model_runner.lora_manager.can_use_prefill_cuda_graph(forward_batch) if input_embeds is not None:
return False
if replace_embeds is not None:
return False
# A prefix forces the MHA companion path, whose captured state is
# frozen prefix-free; DSA models are exempt (capture/replay force
# the sparse path, which takes any prefix via device metadata).
if (
self.prefill_backend_name == Backend.BREAKABLE
and self.has_mha_companion_layers
and not self.dsa_sparse_prefill_forced
and prefix_lens is not None
and any(prefix_lens)
): ):
return False return False
if forward_batch.input_embeds is not None: # FullCG's chunked-prefix topology covers a bounded prefix. The flag
return False # gating it is FULL-backend-only, so this is inert for the breakable
if forward_batch.replace_embeds is not None: # vote path.
return False if chunked_prefix_uncapturable:
if self._has_unsupported_mha_prefix(forward_batch):
return False return False
# tc_piecewise captures with ForwardMode.EXTEND and spec_info=None. # tc_piecewise captures with ForwardMode.EXTEND and spec_info=None.
if forward_batch.forward_mode.is_target_verify(): if is_target_verify:
return False return False
if forward_batch.capture_hidden_mode != self.capture_hidden_mode: if (
capture_hidden_mode is not None
and capture_hidden_mode != self.capture_hidden_mode
):
return False return False
# BCG-with-captured-metadata under DP attention: every rank must if return_logprob and not self._uses_eager_prefill_tail():
# have local tokens, and the batch must declare itself replayable.
# These gates are no-ops for non-DP / non-opt-in paths because
# global_num_tokens_cpu stays None.
if self._has_inactive_dp_rank(forward_batch):
return False return False
if num_tokens is None:
return True
if num_tokens > self.max_num_tokens:
return False
# No exact-shape check: load_batch bucket-pads; only reject
# disproportionate padding waste.
padded_num_tokens = self._pad_to_bucket(num_tokens, self.capture_num_tokens)
if padded_num_tokens > num_tokens * _MAX_PREFILL_CUDA_GRAPH_PADDING_FACTOR:
return False
return True
def can_run_graph(self, forward_batch: ForwardBatch) -> bool:
# DP check: group verdict from the schedule-time all-gather
# (min-reduced votes; also requires every rank to hold tokens).
if ( if (
forward_batch.global_num_tokens_cpu is not None forward_batch.global_num_tokens_cpu is not None
and not forward_batch.can_run_dp_breakable_cuda_graph and not forward_batch.can_run_dp_breakable_cuda_graph
): ):
return False return False
num_tokens = len(forward_batch.input_ids)
if forward_batch.return_logprob and not self._uses_eager_prefill_tail(): # Every dp rank must hold tokens this forward (reads the synced
# table post dp-padding; idle ranks vote permissively upstream).
if self._has_inactive_dp_rank(forward_batch):
return False return False
if num_tokens > self.max_num_tokens:
return False # Non-DP local check (sole decision for tp-only).
padded_num_tokens = self._pad_to_bucket(num_tokens, self.capture_num_tokens) if not self.can_replay_locally(
if padded_num_tokens > num_tokens * _MAX_PREFILL_CUDA_GRAPH_PADDING_FACTOR: batch_size=forward_batch.batch_size,
return False num_tokens=len(forward_batch.input_ids),
# Other backends and non-MLA FullCG keep using their normal graph with input_embeds=forward_batch.input_embeds,
# replay-refreshed metadata; only this extra topology has a prefix cap. replace_embeds=forward_batch.replace_embeds,
if ( prefix_lens=forward_batch.extend_prefix_lens_cpu,
is_target_verify=forward_batch.forward_mode.is_target_verify(),
capture_hidden_mode=forward_batch.capture_hidden_mode,
return_logprob=forward_batch.return_logprob,
lora_ineligible=self.enable_lora
and not (
self._capture_lora
and self.model_runner.lora_manager.can_use_prefill_cuda_graph(
forward_batch
)
),
chunked_prefix_uncapturable=(
self._capture_chunked_prefix self._capture_chunked_prefix
and self._has_prefix_hit(forward_batch) and self._has_prefix_hit(forward_batch)
and self._select_prefix_capture_chunks(forward_batch) is None and self._select_prefix_capture_chunks(forward_batch) is None
),
): ):
return False return False
# load_batch bucket-pads to the nearest captured shape. The factor
# above rejects replays whose padded model work is disproportionate
# to the useful token count.
#
# Multi-req replay is supported by body-capture backends via the # Multi-req replay is supported by body-capture backends via the
# layer_model.forward monkey-patch in replay(): the captured graph runs # layer_model.forward monkey-patch in replay(): the captured graph runs
# the transformer stack, then the outer model.forward runs # the transformer stack, then the outer model.forward runs
@@ -130,6 +130,7 @@ class BreakableCudaGraphBackend(DedupedCudaGraphMixin, BaseCudaGraphBackend):
cuda_graph=graph, cuda_graph=graph,
pool=self._pool, pool=self._pool,
stream=self._capture_stream, stream=self._capture_stream,
barrier_fn=self._tp_group.barrier,
): ):
out = captured_fn() out = captured_fn()
out_rows = self._output_rows(out, size) out_rows = self._output_rows(out, size)
@@ -25,7 +25,7 @@ buffers to keep break-point tensors at stable addresses.
import logging import logging
import threading import threading
from contextvars import ContextVar from contextvars import ContextVar
from typing import Any, Callable from typing import Any, Callable, Optional
import torch import torch
@@ -216,7 +216,7 @@ def _copy_output(dst: Any, src: Any) -> Any:
return src return src
def eager_on_graph(enable: bool): def eager_on_graph(enable: bool, capture_stub: Optional[Callable] = None):
def decorator(inner: Callable): def decorator(inner: Callable):
if not enable: if not enable:
return inner return inner
@@ -231,8 +231,20 @@ def eager_on_graph(enable: bool):
# End the segment that captured up to this break point. # End the segment that captured up to this break point.
capture._end_current_segment() capture._end_current_segment()
# Run the eager function once so it allocates its outputs and # Re-sync ranks after segment teardown (the slow, variable
# writes real data into them. # step) before break fns with rank-coupled collectives and hard
# timeouts (DeepEP NORMAL: 100s). Capture-only; replay bypasses
# this wrapper.
if capture._barrier_fn is not None:
capture._barrier_fn()
# Run the break once so its outputs are allocated and their
# addresses recorded. A capture_stub replaces the body during
# capture (contents are never consumed; warmup and replay run
# the real inner), letting rank-coupled bodies skip the work.
if capture_stub is not None:
output = capture_stub(*args, **kwargs)
else:
output = inner(*args, **kwargs) output = inner(*args, **kwargs)
# Weak-ref captured inputs produced by graph segments. Their storage # Weak-ref captured inputs produced by graph segments. Their storage
@@ -308,6 +320,7 @@ class BreakableCUDAGraphCapture:
pool=None, pool=None,
stream: torch.Stream | None = None, stream: torch.Stream | None = None,
capture_error_mode: str = "global", capture_error_mode: str = "global",
barrier_fn: Callable[[], None] | None = None,
): ):
assert isinstance( assert isinstance(
cuda_graph, BreakableCUDAGraph cuda_graph, BreakableCUDAGraph
@@ -316,6 +329,7 @@ class BreakableCUDAGraphCapture:
self._pool = pool if pool is not None else (0, 0) self._pool = pool if pool is not None else (0, 0)
self._stream = stream self._stream = stream
self._capture_error_mode = capture_error_mode self._capture_error_mode = capture_error_mode
self._barrier_fn = barrier_fn
self._stream_ctx = None self._stream_ctx = None
self._capture_token = None self._capture_token = None
self._stream_token = None self._stream_token = None
+8
View File
@@ -1270,6 +1270,13 @@ class DeepseekV2MoE(nn.Module):
shared_output = self._forward_shared_experts(hidden_states) shared_output = self._forward_shared_experts(hidden_states)
shared_output.record_stream(self.alt_stream) shared_output.record_stream(self.alt_stream)
shared_event = self.alt_stream.record_event() shared_event = self.alt_stream.record_event()
if is_in_breakable_cuda_graph():
# The MoE call below is an eager break, so record
# and wait must share one capture; joining here means
# the shared experts overlap nothing. The alt stream
# is kept for record_stream: without that marking the
# allocator recycles shared_output across the break.
torch.cuda.current_stream().wait_event(shared_event)
else: else:
shared_output = self._forward_shared_experts(hidden_states) shared_output = self._forward_shared_experts(hidden_states)
topk_kwargs = ( topk_kwargs = (
@@ -1453,6 +1460,7 @@ class DeepseekV2MoE(nn.Module):
and not sbo_enabled_flag and not sbo_enabled_flag
and self.num_fused_shared_experts == 0 and self.num_fused_shared_experts == 0
and self.alt_stream is not None and self.alt_stream is not None
and not is_in_breakable_cuda_graph()
): ):
torch.cuda.current_stream().wait_event(shared_event) torch.cuda.current_stream().wait_event(shared_event)
+53 -28
View File
@@ -41,6 +41,7 @@ from sglang.srt.arg_groups.argparse_actions import (
DeprecatedStoreTrueAction, DeprecatedStoreTrueAction,
LoRAPathAction, LoRAPathAction,
) )
from sglang.srt.arg_groups.overrides import resolved_view
from sglang.srt.configs.embedding_model_spec import BCGPrefillPolicy from sglang.srt.configs.embedding_model_spec import BCGPrefillPolicy
from sglang.srt.configs.linear_attn_model_registry import get_linear_attn_spec_by_arch from sglang.srt.configs.linear_attn_model_registry import get_linear_attn_spec_by_arch
from sglang.srt.connector import ConnectorType from sglang.srt.connector import ConnectorType
@@ -4207,6 +4208,7 @@ class ServerArgs:
def _handle_cuda_graph_config(self): def _handle_cuda_graph_config(self):
self._parse_cuda_graph_config() self._parse_cuda_graph_config()
self._apply_cuda_graph_compatibility() self._apply_cuda_graph_compatibility()
self._apply_deepep_adjustments()
self._apply_cuda_graph_disaggregation_roles() self._apply_cuda_graph_disaggregation_roles()
self._validate_cuda_graph_config() self._validate_cuda_graph_config()
# Warn on the final resolved config (not inside the compat cascade — # Warn on the final resolved config (not inside the compat cascade —
@@ -4218,6 +4220,30 @@ class ServerArgs:
"Use breakable or tc_piecewise for production workloads." "Use breakable or tc_piecewise for production workloads."
) )
def _apply_deepep_adjustments(self):
"""Config adjustments required by the DeepEP a2a backend."""
if resolved_view(self).moe_a2a_backend != "deepep":
return
# Non-multiple-of-8 prefill buckets can hang DeepEP a2a capture under
# breakable CUDA graph
if self.cuda_graph_config.prefill.backend == Backend.BREAKABLE:
bs = self.cuda_graph_config.prefill.bs
if bs is None:
# 2048 = documented prefill default; max_bs unresolved here.
max_bs = self.cuda_graph_config.prefill.max_bs or 2048
bs = self._generate_prefill_cuda_graph_batch_sizes(max_bs)
aligned = sorted({((b + 7) // 8) * 8 for b in bs})
if aligned != sorted(bs):
logger.info(
"Breakable prefill CUDA graph with DeepEP requires bucket "
"sizes divisible by 8; aligning %s -> %s.",
sorted(bs),
aligned,
)
self.cuda_graph_config.prefill.bs = aligned
self.cuda_graph_config.prefill.max_bs = aligned[-1]
def _parse_cuda_graph_config(self): def _parse_cuda_graph_config(self):
"""Resolve cuda_graph_config from explicit JSON, per-phase """Resolve cuda_graph_config from explicit JSON, per-phase
convenience flags, legacy global flags, and defaults. convenience flags, legacy global flags, and defaults.
@@ -4321,8 +4347,6 @@ class ServerArgs:
self.cuda_graph_config.prefill.backend = Backend.DISABLED self.cuda_graph_config.prefill.backend = Backend.DISABLED
def _disable_tc_piecewise_cudagraph_if_incompatible(self): def _disable_tc_piecewise_cudagraph_if_incompatible(self):
from sglang.srt.arg_groups.overrides import resolved_view as _resolved_view
"""TcPiecewise (torch.compile + piecewise) is incompatible with """TcPiecewise (torch.compile + piecewise) is incompatible with
these configurations. Most are torch.compile / dynamo limitations. these configurations. Most are torch.compile / dynamo limitations.
""" """
@@ -4346,7 +4370,7 @@ class ServerArgs:
), ),
( (
"MoE A2A backend", "MoE A2A backend",
lambda: _resolved_view(self).moe_a2a_backend != "none", lambda: resolved_view(self).moe_a2a_backend != "none",
), ),
# Dynamo blocks LoRA under tc_piecewise (per-batch LoRABatchInfo # Dynamo blocks LoRA under tc_piecewise (per-batch LoRABatchInfo
# rebinds break guards); breakable/full support LoRA. # rebinds break guards); breakable/full support LoRA.
@@ -4359,7 +4383,7 @@ class ServerArgs:
( (
"GGUF quantization", "GGUF quantization",
lambda: self.load_format == "gguf" lambda: self.load_format == "gguf"
or _resolved_view(self).quantization == "gguf" or resolved_view(self).quantization == "gguf"
or check_gguf_file(self.model_path), or check_gguf_file(self.model_path),
), ),
("DLLM (diffusion LLM)", lambda: self.dllm_algorithm is not None), ("DLLM (diffusion LLM)", lambda: self.dllm_algorithm is not None),
@@ -4398,17 +4422,21 @@ class ServerArgs:
self.cuda_graph_config.prefill.backend = Backend.DISABLED self.cuda_graph_config.prefill.backend = Backend.DISABLED
def _disable_breakable_cudagraph_if_incompatible(self): def _disable_breakable_cudagraph_if_incompatible(self):
from sglang.srt.arg_groups.overrides import resolved_view as _resolved_view
"""Breakable (segmented capture, no torch.compile). Breakable enforces """Breakable (segmented capture, no torch.compile). Breakable enforces
memory-saver rejection in its own __init__; config-time rules can be memory-saver rejection in its own __init__; config-time rules can be
added here as they're discovered. added here as they're discovered.
""" """
from sglang.srt.configs.model_config import is_deepseek_v4 from sglang.srt.configs.model_config import is_deepseek_dsa, is_deepseek_v4
rules = [ rules = [
# MLA prefill takes a different attn-forward path under BCG. # MLA prefill under BCG takes forward_mha, which has no eager
("MLA attention", lambda: self.use_mla_backend()), # breaks. DSA is exempt: BCG forces the sparse path, whose
# indexer already splits eagerly.
(
"MLA attention (non-DSA)",
lambda: self.use_mla_backend()
and not is_deepseek_dsa(self.get_model_config().hf_config),
),
# DSV4 is BCG-compatible but introduces heavy memory pressure: the # DSV4 is BCG-compatible but introduces heavy memory pressure: the
# c4 indexer scratch is pinned in the capture pool and OOMs. Disable. # c4 indexer scratch is pinned in the capture pool and OOMs. Disable.
( (
@@ -4425,10 +4453,15 @@ class ServerArgs:
"decode context parallel (dcp_size > 1)", "decode context parallel (dcp_size > 1)",
lambda: self.dcp_size > 1, lambda: self.dcp_size > 1,
), ),
# BCG bucket sizes exceed FlashInfer MoE A2A's dispatch cap. # TBO capture is unsupported.
( (
"MoE A2A backend", "two-batch overlap",
lambda: _resolved_view(self).moe_a2a_backend != "none", lambda: self.enable_two_batch_overlap,
),
# Only DeepEP's a2a is validated under BCG.
(
"non-DeepEP a2a backend",
lambda: resolved_view(self).moe_a2a_backend not in ("none", "deepep"),
), ),
# Multimodal prefill replay faults under BCG; allowlisted archs opt back in. # Multimodal prefill replay faults under BCG; allowlisted archs opt back in.
( (
@@ -4826,12 +4859,19 @@ class ServerArgs:
# MLA backend overhead is much higher than expected with fa3. # MLA backend overhead is much higher than expected with fa3.
reserved_mem += 1.5 * 1024 reserved_mem += 1.5 * 1024
if (
prefill_cuda_graph_config.backend == Backend.BREAKABLE
and resolved_view(self).moe_a2a_backend == "deepep"
):
# Prefill-BCG DeepEP delta (bridge pool + NVL first-touch
# during capture); decode-side DeepEP is a baseline cost.
reserved_mem += 1 * 1024
return reserved_mem return reserved_mem
def reserve_for_deepep_a2a_mb(self) -> float: def reserve_for_deepep_a2a_mb(self) -> float:
# DeepEP all-to-all buffers captured in the decode graph are real extra # DeepEP all-to-all buffers captured in the decode graph are real extra
# allocations, reserved on top of the floor. # allocations, reserved on top of the floor.
from sglang.srt.arg_groups.overrides import resolved_view
decode_cuda_graph_config = self.cuda_graph_config.decode decode_cuda_graph_config = self.cuda_graph_config.decode
if ( if (
@@ -4991,7 +5031,6 @@ class ServerArgs:
# flags tier. # flags tier.
from sglang.srt.arg_groups.overrides import ( from sglang.srt.arg_groups.overrides import (
collect_model_override_declarations, collect_model_override_declarations,
resolved_view,
validate_declarations, validate_declarations,
) )
@@ -5524,7 +5563,6 @@ class ServerArgs:
from sglang.srt.arg_groups.overrides import ( from sglang.srt.arg_groups.overrides import (
_mamba_radix_cache_resolution, _mamba_radix_cache_resolution,
mamba_extra_buffer_of, mamba_extra_buffer_of,
resolved_view,
run_post_process_pass, run_post_process_pass,
) )
@@ -5574,7 +5612,6 @@ class ServerArgs:
if not use_mla_backend: if not use_mla_backend:
# MHA architecture # MHA architecture
from sglang.srt.arg_groups.overrides import resolved_view
if is_hopper_with_cuda_12_3() and is_no_spec_infer_or_topk_one( if is_hopper_with_cuda_12_3() and is_no_spec_infer_or_topk_one(
resolved_view(self) resolved_view(self)
@@ -5637,7 +5674,6 @@ class ServerArgs:
_fa4_page_constraint, _fa4_page_constraint,
_intel_xpu_page_constraint, _intel_xpu_page_constraint,
_mla_backend_page_constraints, _mla_backend_page_constraints,
resolved_view,
run_post_process_pass, run_post_process_pass,
) )
@@ -5748,7 +5784,6 @@ class ServerArgs:
def _handle_kv4_compatibility(self): def _handle_kv4_compatibility(self):
"""Check FP4 KV cache compatibility with the attention backend""" """Check FP4 KV cache compatibility with the attention backend"""
from sglang.srt.arg_groups.overrides import resolved_view
if self.kv_cache_dtype not in ("nvfp4", "fp4_mx_block16"): if self.kv_cache_dtype not in ("nvfp4", "fp4_mx_block16"):
return return
@@ -6014,7 +6049,6 @@ class ServerArgs:
) )
from sglang.srt.arg_groups.overrides import ( from sglang.srt.arg_groups.overrides import (
mamba_extra_buffer_of, mamba_extra_buffer_of,
resolved_view,
) )
if mamba_extra_buffer_of(resolved_view(self)): if mamba_extra_buffer_of(resolved_view(self)):
@@ -6374,7 +6408,6 @@ class ServerArgs:
_cutlass_moe_env_override, _cutlass_moe_env_override,
_moe_runner_backend_quant_constraints, _moe_runner_backend_quant_constraints,
_moe_runner_fusion_disable, _moe_runner_fusion_disable,
resolved_view,
run_post_process_pass, run_post_process_pass,
) )
@@ -6493,7 +6526,6 @@ class ServerArgs:
"""Fail fast if the FlashInfer A2A dispatcher workspace cannot cover the """Fail fast if the FlashInfer A2A dispatcher workspace cannot cover the
largest CuteDSL MoE forward. Runs after speculative decoding is resolved largest CuteDSL MoE forward. Runs after speculative decoding is resolved
so cutedsl_moe_max_num_tokens() sees the final num_tokens_per_req.""" so cutedsl_moe_max_num_tokens() sees the final num_tokens_per_req."""
from sglang.srt.arg_groups.overrides import resolved_view
view = resolved_view(self) view = resolved_view(self)
if not ( if not (
@@ -6536,7 +6568,6 @@ class ServerArgs:
_a2a_backend_overrides, _a2a_backend_overrides,
_a2a_ep_size, _a2a_ep_size,
_a2a_fusion_adjustments, _a2a_fusion_adjustments,
resolved_view,
run_post_process_pass, run_post_process_pass,
) )
@@ -7022,7 +7053,6 @@ class ServerArgs:
still None, backends haven't settled yet and the resolved (prefill, still None, backends haven't settled yet and the resolved (prefill,
decode) pair would be a stale (None, None). decode) pair would be a stale (None, None).
""" """
from sglang.srt.arg_groups.overrides import resolved_view
if not self.prefill_only_disable_kv_cache: if not self.prefill_only_disable_kv_cache:
return return
@@ -7595,7 +7625,6 @@ class ServerArgs:
from sglang.srt.arg_groups.overrides import ( from sglang.srt.arg_groups.overrides import (
_deterministic_attention_backend, _deterministic_attention_backend,
_deterministic_sampling_backend, _deterministic_sampling_backend,
resolved_view,
run_post_process_pass, run_post_process_pass,
) )
@@ -7841,7 +7870,6 @@ class ServerArgs:
) )
def _handle_other_validations(self): def _handle_other_validations(self):
from sglang.srt.arg_groups.overrides import resolved_view
# Handle optimistic prefill validation # Handle optimistic prefill validation
if ( if (
@@ -8276,7 +8304,6 @@ class ServerArgs:
def _resolved(self): def _resolved(self):
"""Read-only view of the resolving configuration: declared fields """Read-only view of the resolving configuration: declared fields
resolve from the declaration stash.""" resolve from the declaration stash."""
from sglang.srt.arg_groups.overrides import resolved_view
return resolved_view(self) return resolved_view(self)
@@ -8338,7 +8365,6 @@ class ServerArgs:
view so declared fields resolve from the declaration stash.""" view so declared fields resolve from the declaration stash."""
from sglang.srt.arg_groups.overrides import ( from sglang.srt.arg_groups.overrides import (
attention_backends_of, attention_backends_of,
resolved_view,
) )
return attention_backends_of(resolved_view(self)) return attention_backends_of(resolved_view(self))
@@ -8406,7 +8432,6 @@ class ServerArgs:
# (or mamba_chunk_size if it is defined in the model's config) and page_size. # (or mamba_chunk_size if it is defined in the model's config) and page_size.
# It is used to determine the caching point in a sequence during prefill. # It is used to determine the caching point in a sequence during prefill.
if not hasattr(self, "_mamba_cache_chunk_size"): if not hasattr(self, "_mamba_cache_chunk_size"):
from sglang.srt.arg_groups.overrides import resolved_view
hf_config = self.get_model_config().hf_config hf_config = self.get_model_config().hf_config
chunk_size = getattr(hf_config, "mamba_chunk_size", FLA_CHUNK_SIZE) chunk_size = getattr(hf_config, "mamba_chunk_size", FLA_CHUNK_SIZE)
@@ -191,10 +191,10 @@ def prepare_for_draft_extend(
# Supply CPU mirror (extend_seq_lens are all num_window_tokens) so # Supply CPU mirror (extend_seq_lens are all num_window_tokens) so
# backend max() reads from list without a per-iter D2H sync. # backend max() reads from list without a per-iter D2H sync.
forward_batch.extend_seq_lens_cpu = [num_window_tokens] * bs forward_batch.extend_seq_lens_cpu = [num_window_tokens] * bs
can_cuda_graph = cuda_graph_runner and cuda_graph_runner.can_run_graph( can_run_decode_cuda_graph = cuda_graph_runner and cuda_graph_runner.can_run_graph(
forward_batch forward_batch
) )
if not batch.forward_mode.is_idle() and not can_cuda_graph: if not batch.forward_mode.is_idle() and not can_run_decode_cuda_graph:
draft_model_runner.attn_backend.init_forward_metadata(forward_batch) draft_model_runner.attn_backend.init_forward_metadata(forward_batch)
# Planned pre-pad; do NOT opt into post-pad re-plan. DSA's indexer # Planned pre-pad; do NOT opt into post-pad re-plan. DSA's indexer
# cannot rebuild its deep_gemm schedule_meta on a DP-padded batch # cannot rebuild its deep_gemm schedule_meta on a DP-padded batch
@@ -204,7 +204,7 @@ def prepare_for_draft_extend(
# On NPU with --disable-cuda-graph, block_table shape won't match # On NPU with --disable-cuda-graph, block_table shape won't match
# after prepare_mlp_sync_batch padding; defer re-init to # after prepare_mlp_sync_batch padding; defer re-init to
# forward_extend (post-pad) instead. # forward_extend (post-pad) instead.
if not is_npu() or can_cuda_graph: if not is_npu() or can_run_decode_cuda_graph:
forward_batch.mark_forward_metadata_ready() forward_batch.mark_forward_metadata_ready()
return forward_batch return forward_batch
@@ -307,10 +307,10 @@ def prepare_for_draft(
capture_hidden_mode=capture_mode, capture_hidden_mode=capture_mode,
return_hidden_states_before_norm=False, return_hidden_states_before_norm=False,
) )
can_cuda_graph = cuda_graph_runner and cuda_graph_runner.can_run_graph( can_run_decode_cuda_graph = cuda_graph_runner and cuda_graph_runner.can_run_graph(
forward_batch forward_batch
) )
return forward_batch, can_cuda_graph return forward_batch, can_run_decode_cuda_graph
def build_eagle_verify_input( def build_eagle_verify_input(
@@ -468,7 +468,7 @@ class EagleDraftWorker(EagleDraftWorkerBase):
def draft(self, batch: ScheduleBatch): def draft(self, batch: ScheduleBatch):
draft_input: EagleDraftInput = batch.spec_info draft_input: EagleDraftInput = batch.spec_info
forward_batch, can_cuda_graph = prepare_for_draft( forward_batch, can_run_decode_cuda_graph = prepare_for_draft(
draft_input, draft_input,
self.req_to_token_pool, self.req_to_token_pool,
batch, batch,
@@ -478,12 +478,12 @@ class EagleDraftWorker(EagleDraftWorkerBase):
self.speculative_num_steps, self.speculative_num_steps,
) )
if ( if (
can_cuda_graph can_run_decode_cuda_graph
and not forward_batch.forward_mode.is_idle() and not forward_batch.forward_mode.is_idle()
and self.seed_dsa_topk_from_draft_extend and self.seed_dsa_topk_from_draft_extend
and draft_input.dsa_topk_indices is None and draft_input.dsa_topk_indices is None
): ):
can_cuda_graph = False can_run_decode_cuda_graph = False
n_inner = self.speculative_num_steps - 1 n_inner = self.speculative_num_steps - 1
canary_outside_ctx = ( canary_outside_ctx = (
@@ -497,7 +497,7 @@ class EagleDraftWorker(EagleDraftWorkerBase):
with canary_outside_ctx: with canary_outside_ctx:
# Run draft # Run draft
if can_cuda_graph: if can_run_decode_cuda_graph:
parent_list, top_scores_index, draft_tokens, draft_probs = ( parent_list, top_scores_index, draft_tokens, draft_probs = (
self.cuda_graph_runner.execute(forward_batch) self.cuda_graph_runner.execute(forward_batch)
) )
@@ -882,14 +882,14 @@ class EagleDraftWorker(EagleDraftWorkerBase):
) )
# Run draft extend batch in the main compute stream # Run draft extend batch in the main compute stream
can_cuda_graph = ( can_run_decode_cuda_graph = (
self.cuda_graph_runner_for_draft_extend self.cuda_graph_runner_for_draft_extend
and self.cuda_graph_runner_for_draft_extend.can_run_graph(forward_batch) and self.cuda_graph_runner_for_draft_extend.can_run_graph(forward_batch)
) )
# Eager path publishes the indexer top-k into a worker buffer (the graph # Eager path publishes the indexer top-k into a worker buffer (the graph
# path uses the runner's static buffer). Gathered at select_index below. # path uses the runner's static buffer). Gathered at select_index below.
if self.seed_dsa_topk_from_draft_extend and not can_cuda_graph: if self.seed_dsa_topk_from_draft_extend and not can_run_decode_cuda_graph:
forward_batch.spec_info.dsa_seed_topk_capture = ( forward_batch.spec_info.dsa_seed_topk_capture = (
self._get_dsa_extend_topk_buf(forward_batch.input_ids.shape[0]) self._get_dsa_extend_topk_buf(forward_batch.input_ids.shape[0])
) )
@@ -906,7 +906,7 @@ class EagleDraftWorker(EagleDraftWorkerBase):
else contextlib.nullcontext() else contextlib.nullcontext()
) )
with canary_ctx: with canary_ctx:
if can_cuda_graph: if can_run_decode_cuda_graph:
draft_logits_output = self.cuda_graph_runner_for_draft_extend.execute( draft_logits_output = self.cuda_graph_runner_for_draft_extend.execute(
forward_batch forward_batch
) )
@@ -917,18 +917,18 @@ class EagleDraftWorker(EagleDraftWorkerBase):
maybe_detect_nan( maybe_detect_nan(
draft_logits_output.next_token_logits, draft_logits_output.next_token_logits,
f"draft_extend_for_decode (cuda_graph={can_cuda_graph})", f"draft_extend_for_decode (cuda_graph={can_run_decode_cuda_graph})",
) )
maybe_detect_inf( maybe_detect_inf(
draft_logits_output.next_token_logits, draft_logits_output.next_token_logits,
f"draft_extend_for_decode (cuda_graph={can_cuda_graph})", f"draft_extend_for_decode (cuda_graph={can_run_decode_cuda_graph})",
) )
# Gather the per-request last-position indexer top-k as the next loop's # Gather the per-request last-position indexer top-k as the next loop's
# seed (select_index already picks the last accepted position per req). # seed (select_index already picks the last accepted position per req).
dsa_seed_topk_indices = None dsa_seed_topk_indices = None
if self.seed_dsa_topk_from_draft_extend: if self.seed_dsa_topk_from_draft_extend:
if can_cuda_graph: if can_run_decode_cuda_graph:
dsa_extend_topk_capture = ( dsa_extend_topk_capture = (
self.cuda_graph_runner_for_draft_extend.buffers.dsa_seed_topk_capture self.cuda_graph_runner_for_draft_extend.buffers.dsa_seed_topk_capture
) )
@@ -406,7 +406,7 @@ class MultiLayerEagleDraftWorker(EagleDraftWorkerBase):
def draft(self, batch: ScheduleBatch): def draft(self, batch: ScheduleBatch):
draft_input: EagleDraftInput = batch.spec_info draft_input: EagleDraftInput = batch.spec_info
forward_batch, can_cuda_graph = prepare_for_draft( forward_batch, can_run_decode_cuda_graph = prepare_for_draft(
draft_input, draft_input,
self.req_to_token_pool, self.req_to_token_pool,
batch, batch,
@@ -732,7 +732,7 @@ class MultiLayerEagleDraftWorker(EagleDraftWorkerBase):
forward_batch.spec_info.num_accept_tokens = batch_result.accept_lens forward_batch.spec_info.num_accept_tokens = batch_result.accept_lens
# Run draft extend batch in the main compute stream # Run draft extend batch in the main compute stream
can_cuda_graph = ( can_run_decode_cuda_graph = (
self.cuda_graph_runner_for_draft_extend self.cuda_graph_runner_for_draft_extend
and self.cuda_graph_runner_for_draft_extend.can_run_graph(forward_batch) and self.cuda_graph_runner_for_draft_extend.can_run_graph(forward_batch)
) )
@@ -742,7 +742,7 @@ class MultiLayerEagleDraftWorker(EagleDraftWorkerBase):
ret_draft_probs = None ret_draft_probs = None
next_token_ids_backup = batch_result.next_token_ids.clone() next_token_ids_backup = batch_result.next_token_ids.clone()
if can_cuda_graph: if can_run_decode_cuda_graph:
# Graph replay bypasses ModelRunner.forward, which emits the # Graph replay bypasses ModelRunner.forward, which emits the
# step[...] trace span for every other phase; emit it here. # step[...] trace span for every other phase; emit it here.
with profile_range(build_step_span_name(forward_batch)): with profile_range(build_step_span_name(forward_batch)):
@@ -39,6 +39,7 @@ class TestPrefillCudaGraphPadding(CustomTestCase):
global_num_tokens_cpu=None, global_num_tokens_cpu=None,
return_logprob=False, return_logprob=False,
input_ids=list(range(num_tokens)), input_ids=list(range(num_tokens)),
extend_prefix_lens_cpu=[0],
) )
def test_rejects_more_than_two_x_token_padding(self): def test_rejects_more_than_two_x_token_padding(self):