[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:
co-authored by
Claude Fable 5
parent
3c5f115741
commit
3e0f7c3f30
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+18
-4
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user