spec: size the speculative buffers from the bags, not the startup record (#35024)
This commit is contained in:
@@ -88,13 +88,9 @@ patches:
|
||||
- target: sglang.srt.mem_cache.allocation_sizing.get_req_to_token_extra_context_len
|
||||
edits:
|
||||
- match: |
|
||||
if (
|
||||
server_args.speculative_algorithm is not None
|
||||
and server_args.page_size > 1
|
||||
and (server_args.speculative_eagle_topk or 1) > 1
|
||||
):
|
||||
extra = max(extra, get_alloc_reserve_per_decode(server_args))
|
||||
replacement: ""
|
||||
extra = max(extra, get_alloc_reserve_per_decode() + page_size - 1)
|
||||
replacement: |
|
||||
pass
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -2335,7 +2335,7 @@ class SchedulerDisaggregationDecodeMixin:
|
||||
|
||||
# construct fake completed prefill
|
||||
new_batch.prepare_for_prebuilt()
|
||||
new_batch.process_prebuilt(self.server_args, self.future_map)
|
||||
new_batch.process_prebuilt(self.future_map)
|
||||
|
||||
return new_batch
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ logger = logging.getLogger(__name__)
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.managers.overlap_utils import FutureMap
|
||||
from sglang.srt.managers.schedule_batch import ScheduleBatch
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
|
||||
|
||||
class ScheduleBatchDisaggregationDecodeMixin:
|
||||
@@ -113,7 +112,6 @@ class ScheduleBatchDisaggregationDecodeMixin:
|
||||
|
||||
def process_prebuilt(
|
||||
self: ScheduleBatch,
|
||||
server_args: ServerArgs,
|
||||
future_map: FutureMap,
|
||||
):
|
||||
"""Assign the buffered last input id to schedule batch"""
|
||||
@@ -146,7 +144,6 @@ class ScheduleBatchDisaggregationDecodeMixin:
|
||||
|
||||
spec_info = self.spec_algorithm.build_disagg_draft_input(
|
||||
self,
|
||||
server_args,
|
||||
last_tokens_tensor,
|
||||
future_map,
|
||||
)
|
||||
|
||||
@@ -13,6 +13,8 @@ from sglang.srt.model_executor.runner_backend_utils.tc_piecewise_cuda_graph impo
|
||||
is_in_tc_piecewise_cuda_graph,
|
||||
)
|
||||
from sglang.srt.runtime_context import (
|
||||
get_disagg,
|
||||
get_memory,
|
||||
get_parallel,
|
||||
process_model_config,
|
||||
)
|
||||
@@ -67,27 +69,24 @@ INDEXER_K_CACHE_PRESHUFFLE_TILE = 16
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
|
||||
|
||||
def compute_dsa_seqlens(original_seq_lens, dsa_index_topk: int):
|
||||
return original_seq_lens.clamp(max=dsa_index_topk)
|
||||
|
||||
|
||||
def should_remap_pd_dsa_seed_to_local_slots(server_args: "ServerArgs") -> bool:
|
||||
def should_remap_pd_dsa_seed_to_local_slots() -> bool:
|
||||
"""Whether a PD seed should enter the allocator-local fused TopK domain."""
|
||||
return (
|
||||
is_cuda()
|
||||
and envs.SGLANG_DSA_FUSE_TOPK.get()
|
||||
and server_args.disaggregation_mode == "decode"
|
||||
and not server_args.enable_hisparse
|
||||
and get_disagg().disaggregation_mode == "decode"
|
||||
and not get_memory().enable_hisparse
|
||||
and not get_parallel().dcp_enabled
|
||||
)
|
||||
|
||||
|
||||
def should_use_dsa_fused_topk(
|
||||
server_args: "ServerArgs", seed_dsa_topk_from_draft_extend: bool
|
||||
) -> bool:
|
||||
def should_use_dsa_fused_topk(seed_dsa_topk_from_draft_extend: bool) -> bool:
|
||||
"""Select fused TopK for PD IndexShare.
|
||||
|
||||
PD Prefill worker:
|
||||
@@ -98,10 +97,10 @@ def should_use_dsa_fused_topk(
|
||||
- Draft decode / target verify / draft extend: fused TopK enabled.
|
||||
"""
|
||||
pd_index_share_seed = (
|
||||
server_args.disaggregation_mode != "null" and seed_dsa_topk_from_draft_extend
|
||||
get_disagg().disaggregation_mode != "null" and seed_dsa_topk_from_draft_extend
|
||||
)
|
||||
return envs.SGLANG_DSA_FUSE_TOPK.get() and (
|
||||
not pd_index_share_seed or should_remap_pd_dsa_seed_to_local_slots(server_args)
|
||||
not pd_index_share_seed or should_remap_pd_dsa_seed_to_local_slots()
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -393,9 +393,7 @@ class DeepseekSparseAttnBackend(
|
||||
self.speculative_num_steps = speculative_num_steps
|
||||
self.speculative_num_draft_tokens = get_spec().speculative_num_draft_tokens
|
||||
self.speculative_step_id = speculative_step_id
|
||||
self.use_fused_topk = should_use_dsa_fused_topk(
|
||||
model_runner.server_args, seed_dsa_topk_from_draft_extend
|
||||
)
|
||||
self.use_fused_topk = should_use_dsa_fused_topk(seed_dsa_topk_from_draft_extend)
|
||||
if envs.SGLANG_DSA_FUSE_TOPK.get() and not self.use_fused_topk:
|
||||
print_warning_once(
|
||||
"Disabling fused DSA top-k for IndexShare under PD disaggregation."
|
||||
|
||||
@@ -219,7 +219,6 @@ class FlashAttentionBackend(AttentionBackend):
|
||||
# Static verify width; NOTE: overwrites the config-named attr in place.
|
||||
self.speculative_num_draft_tokens = resolve_num_tokens_per_req(
|
||||
phase="target_verify",
|
||||
server_args=model_runner.server_args,
|
||||
spec_algorithm=SpeculativeAlgorithm.from_string(
|
||||
get_spec().speculative_algorithm
|
||||
),
|
||||
|
||||
@@ -726,9 +726,7 @@ class RustServer:
|
||||
server_args["version"] = __version__
|
||||
# Not a `server_args` field: `TokenizerManager` derives it, and the rust
|
||||
# ingress needs the same number for its total-token check.
|
||||
server_args["num_reserved_tokens"] = compute_num_reserved_tokens(
|
||||
scheduler.server_args
|
||||
)
|
||||
server_args["num_reserved_tokens"] = compute_num_reserved_tokens()
|
||||
server_args["max_total_num_tokens"] = scheduler.max_total_num_tokens
|
||||
|
||||
return msgspec.json.encode(server_args, enc_hook=str).decode("utf-8")
|
||||
|
||||
@@ -462,7 +462,7 @@ class TokenizerManager(TokenizerControlMixin, TokenizerManagerScoreMixin):
|
||||
self.max_req_input_len = None # Will be set later in engine.py
|
||||
self.enable_priority_scheduling = server_args.enable_priority_scheduling
|
||||
self.default_priority_value = server_args.default_priority_value
|
||||
self.num_reserved_tokens = compute_num_reserved_tokens(server_args)
|
||||
self.num_reserved_tokens = compute_num_reserved_tokens()
|
||||
self.validate_total_tokens = True
|
||||
|
||||
def init_tokenizer_and_processor(self):
|
||||
|
||||
@@ -15,12 +15,12 @@ from sglang.srt.layers.logits_processor import LogitsProcessorOutput
|
||||
from sglang.srt.managers import io_struct
|
||||
from sglang.srt.managers.schedule_batch import Req
|
||||
from sglang.srt.model_executor.forward_batch_info import PPProxyTensors
|
||||
from sglang.srt.runtime_context import get_spec, max_speculative_num_draft_tokens
|
||||
from sglang.srt.speculative.spec_info import SpeculativeAlgorithm
|
||||
from sglang.srt.state_capturer.base import TopkCaptureOutput
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.managers.scheduler import GenerationBatchResult
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
from sglang.srt.speculative.eagle_info import EagleDraftInput
|
||||
|
||||
|
||||
@@ -377,19 +377,23 @@ def msgpack_decode_explained(data: bytes) -> Any:
|
||||
raise MsgpackDecodeError(rid, msg) from e
|
||||
|
||||
|
||||
def compute_num_reserved_tokens(server_args: ServerArgs) -> int:
|
||||
def compute_num_reserved_tokens() -> int:
|
||||
"""Output token slots reserved per request, on top of its input.
|
||||
|
||||
The current eagle implementation stores draft tokens in the output token
|
||||
slots, so the context budget has to account for them; every other algorithm
|
||||
reserves nothing. Shared by `TokenizerManager` and the rust server's
|
||||
`server_args` blob (`RustServer._build_server_args`), which needs the same
|
||||
number to run the total-token check in Rust.
|
||||
number to run the total-token check in Rust. Both stamp the number once at
|
||||
launch, so it has to cover every step an adaptive-spec run may switch to:
|
||||
it reads the bags for the candidate-table ceiling and the current
|
||||
`topk * steps`, not the untouched startup record.
|
||||
"""
|
||||
algorithm = SpeculativeAlgorithm.from_string(server_args.speculative_algorithm)
|
||||
spec = get_spec()
|
||||
algorithm = SpeculativeAlgorithm.from_string(spec.speculative_algorithm)
|
||||
if not algorithm.is_eagle():
|
||||
return 0
|
||||
return max(
|
||||
server_args.speculative_eagle_topk * server_args.speculative_num_steps,
|
||||
server_args.max_speculative_num_draft_tokens,
|
||||
spec.speculative_eagle_topk * spec.speculative_num_steps,
|
||||
max_speculative_num_draft_tokens(),
|
||||
)
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from sglang.srt.runtime_context import get_server_args
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
from sglang.srt.runtime_context import (
|
||||
get_schedule,
|
||||
get_spec,
|
||||
max_speculative_num_draft_tokens,
|
||||
)
|
||||
|
||||
|
||||
def get_alloc_len_per_decode(
|
||||
server_args: ServerArgs, *, max_draft_tokens: Optional[int] = None
|
||||
) -> int:
|
||||
"""``max_draft_tokens`` lets a caller that already resolved the draft-token
|
||||
bound (the KV-cache configurator reads it off the bags) size with that same
|
||||
value; the default is the handed instance's own member, never the global."""
|
||||
if server_args.speculative_algorithm is None:
|
||||
def get_alloc_len_per_decode() -> int:
|
||||
"""KV length one request may allocate in a single decode step.
|
||||
|
||||
Reads the bags: adaptive speculative decoding moves the step count and the
|
||||
draft-token bound after publish, and this runs per decode batch.
|
||||
"""
|
||||
spec = get_spec()
|
||||
if spec.speculative_algorithm is None:
|
||||
return 1
|
||||
|
||||
# Spec decoding allocates max(topk * num_steps, num_draft_tokens) per decode step.
|
||||
spec_steps = server_args.speculative_num_steps or 1
|
||||
spec_topk = server_args.speculative_eagle_topk or 1
|
||||
spec_tokens = (
|
||||
max_draft_tokens
|
||||
if max_draft_tokens is not None
|
||||
else server_args.max_speculative_num_draft_tokens
|
||||
)
|
||||
page_size = server_args.page_size
|
||||
spec_steps = spec.speculative_num_steps or 1
|
||||
spec_topk = spec.speculative_eagle_topk or 1
|
||||
spec_tokens = max_speculative_num_draft_tokens()
|
||||
page_size = get_schedule().page_size
|
||||
|
||||
from sglang.srt.speculative.spec_info import SpeculativeAlgorithm
|
||||
|
||||
spec_algo = SpeculativeAlgorithm.from_string(server_args.speculative_algorithm)
|
||||
spec_algo = SpeculativeAlgorithm.from_string(spec.speculative_algorithm)
|
||||
if page_size == 1 or spec_topk == 1 or not spec_algo.has_draft_kv():
|
||||
return max(spec_steps * spec_topk, spec_tokens)
|
||||
else:
|
||||
@@ -40,51 +38,29 @@ def get_alloc_len_per_decode(
|
||||
return max(num_new_pages_per_topk * page_size * spec_topk, spec_tokens)
|
||||
|
||||
|
||||
def get_alloc_reserve_per_decode(
|
||||
server_args: Optional[ServerArgs] = None,
|
||||
*,
|
||||
max_draft_tokens: Optional[int] = None,
|
||||
) -> int:
|
||||
def get_alloc_reserve_per_decode() -> int:
|
||||
"""KV length reserved per request at each decode step.
|
||||
|
||||
The 2x is a double-buffer that absorbs the kv_committed_len lag in overlap
|
||||
mode; see eagle_utils.eagle_prepare_for_decode.
|
||||
|
||||
Callers on a request path have no config in hand, so this is the module's
|
||||
single "which config" decision point: everything below it takes the
|
||||
instance explicitly.
|
||||
"""
|
||||
if server_args is None:
|
||||
server_args = get_server_args()
|
||||
return 2 * get_alloc_len_per_decode(server_args, max_draft_tokens=max_draft_tokens)
|
||||
return 2 * get_alloc_len_per_decode()
|
||||
|
||||
|
||||
def get_req_to_token_extra_context_len(
|
||||
server_args: ServerArgs, *, max_draft_tokens: Optional[int] = None
|
||||
) -> int:
|
||||
def get_req_to_token_extra_context_len() -> int:
|
||||
"""req_to_token row headroom beyond the model context length.
|
||||
|
||||
Sized to hold the decode over-allocation; the spec v2 page>1 topk>1 holey
|
||||
draft footprint can outgrow the default num_draft_tokens headroom.
|
||||
|
||||
``max_draft_tokens`` keeps this row headroom and the caller's other
|
||||
draft-token-sized buffers on ONE resolved value: the KV-cache configurator
|
||||
passes the bag-derived bound it also hands the pools, so the two cannot
|
||||
disagree after a post-publish override. The default stays the handed
|
||||
instance's member for callers sizing against a specific config object.
|
||||
draft footprint can outgrow the default num_draft_tokens headroom. The row
|
||||
headroom and the pools it sits next to derive from the same bag leaves, so
|
||||
they cannot disagree after a post-publish override.
|
||||
"""
|
||||
if max_draft_tokens is None:
|
||||
max_draft_tokens = server_args.max_speculative_num_draft_tokens
|
||||
# FIXME(lsyin): temporary fix for the context length issue under spec decoding
|
||||
extra = 4 + (max_draft_tokens or 0)
|
||||
if server_args.speculative_algorithm is not None and server_args.page_size > 1:
|
||||
extra = 4 + (max_speculative_num_draft_tokens() or 0)
|
||||
page_size = get_schedule().page_size
|
||||
if get_spec().speculative_algorithm is not None and page_size > 1:
|
||||
# kv_allocated_len is page-aligned (eagle_prepare_for_decode), so near
|
||||
# the context limit the aligned reserve can overshoot by page_size - 1;
|
||||
# without the headroom the row write silently lands in the neighbor row.
|
||||
extra = max(
|
||||
extra,
|
||||
get_alloc_reserve_per_decode(server_args, max_draft_tokens=max_draft_tokens)
|
||||
+ server_args.page_size
|
||||
- 1,
|
||||
)
|
||||
extra = max(extra, get_alloc_reserve_per_decode() + page_size - 1)
|
||||
return extra
|
||||
|
||||
@@ -759,13 +759,7 @@ class KVCacheConfigurator:
|
||||
)
|
||||
|
||||
def _build_req_to_token_pool(self, *, max_num_reqs: int) -> ReqToTokenPool:
|
||||
# The same bag-derived bound the pools below receive, so the row
|
||||
# headroom and the speculative buffers cannot disagree after a
|
||||
# post-publish override.
|
||||
extra_max_context_len = get_req_to_token_extra_context_len(
|
||||
self.server_args,
|
||||
max_draft_tokens=max_speculative_num_draft_tokens(),
|
||||
)
|
||||
extra_max_context_len = get_req_to_token_extra_context_len()
|
||||
|
||||
if get_disagg().disaggregation_mode == "decode":
|
||||
# Extra slots for pre-allocated requests
|
||||
|
||||
@@ -784,7 +784,6 @@ class ModelRunner:
|
||||
if self.spec_algorithm.is_speculative():
|
||||
return resolve_num_tokens_per_req(
|
||||
phase="target_verify",
|
||||
server_args=self.server_args,
|
||||
spec_algorithm=self.spec_algorithm,
|
||||
is_draft_worker=self.is_draft_worker,
|
||||
num_draft_tokens=num_draft_tokens,
|
||||
|
||||
@@ -43,7 +43,6 @@ from sglang.srt.runtime_context import (
|
||||
get_parallel,
|
||||
get_schedule,
|
||||
get_spec,
|
||||
max_speculative_num_draft_tokens,
|
||||
)
|
||||
from sglang.srt.utils.common import (
|
||||
ceil_align,
|
||||
@@ -633,10 +632,7 @@ class SWAChunkCapPoolConfigurator(HybridSWAPoolConfigurator):
|
||||
else:
|
||||
# spec-v2: the overlap allocator keeps 2 * alloc_len outstanding
|
||||
# (eagle_utils.eagle_prepare_for_decode: kv_committed_len + 2 * alloc_len).
|
||||
decode_alloc = 2 * get_alloc_len_per_decode(
|
||||
kvc.server_args,
|
||||
max_draft_tokens=max_speculative_num_draft_tokens(),
|
||||
)
|
||||
decode_alloc = 2 * get_alloc_len_per_decode()
|
||||
per_request = trailing_tokens + decode_alloc
|
||||
|
||||
num_reqs = get_schedule().max_running_requests // kvc.ps.attn_dp_size
|
||||
|
||||
@@ -554,7 +554,6 @@ class BaseRunner(ABC):
|
||||
# Speculative metadata and hidden-state capture mode.
|
||||
spec_info = create_dummy_verify_input(
|
||||
mr.spec_algorithm,
|
||||
mr.server_args,
|
||||
buffers.custom_mask,
|
||||
num_tokens_per_req,
|
||||
mr.is_draft_worker,
|
||||
|
||||
@@ -10,13 +10,11 @@ from sglang.srt.speculative.dspark_components.dspark_draft import make_next_draf
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.managers.overlap_utils import FutureMap
|
||||
from sglang.srt.managers.schedule_batch import ScheduleBatch
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
from sglang.srt.speculative.spec_info import SpecInput
|
||||
|
||||
|
||||
def build_dspark_disagg_draft_input(
|
||||
batch: ScheduleBatch,
|
||||
server_args: ServerArgs,
|
||||
last_tokens_tensor: torch.Tensor,
|
||||
future_map: FutureMap,
|
||||
) -> SpecInput:
|
||||
|
||||
@@ -9,23 +9,25 @@ from sglang.srt.layers.attention.dsa.utils import (
|
||||
)
|
||||
from sglang.srt.managers.overlap_utils import RelayPayload
|
||||
from sglang.srt.model_executor.forward_batch_info import CaptureHiddenMode
|
||||
from sglang.srt.runtime_context import get_spec
|
||||
from sglang.srt.speculative.eagle_info import EagleDraftInput
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.managers.overlap_utils import FutureMap
|
||||
from sglang.srt.managers.schedule_batch import ScheduleBatch
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
|
||||
|
||||
def build_eagle_disagg_draft_input(
|
||||
batch: ScheduleBatch,
|
||||
server_args: ServerArgs,
|
||||
last_tokens_tensor: torch.Tensor,
|
||||
future_map: FutureMap,
|
||||
) -> EagleDraftInput:
|
||||
num_states = server_args.speculative_eagle_topk
|
||||
if server_args.enable_multi_layer_eagle:
|
||||
num_states *= server_args.speculative_num_steps
|
||||
# Adaptive spec moves the step count after publish, and this runs once per
|
||||
# prebuilt batch.
|
||||
spec = get_spec()
|
||||
num_states = spec.speculative_eagle_topk
|
||||
if spec.enable_multi_layer_eagle:
|
||||
num_states *= spec.speculative_num_steps
|
||||
|
||||
topk_p = torch.stack(
|
||||
[
|
||||
@@ -58,7 +60,7 @@ def build_eagle_disagg_draft_input(
|
||||
dsa_indices_list = [req.output_dsa_topk_indices for req in batch.reqs]
|
||||
if dsa_indices_list and all(t is not None for t in dsa_indices_list):
|
||||
dsa_topk_indices = torch.stack(dsa_indices_list, dim=0).to(batch.device)
|
||||
if should_remap_pd_dsa_seed_to_local_slots(server_args):
|
||||
if should_remap_pd_dsa_seed_to_local_slots():
|
||||
# PD sends request-relative positions; fused TopK consumes
|
||||
# decode-local physical slots. Remap once before the draft loop/graph.
|
||||
req_to_token = batch.req_to_token_pool.req_to_token
|
||||
|
||||
@@ -147,9 +147,7 @@ class EAGLEDraftCudaGraphRunner(DecodeCudaGraphRunner):
|
||||
# Bucket sizes
|
||||
self.capture_bs, _ = get_batch_sizes_to_capture(model_runner)
|
||||
# Static capture width.
|
||||
self.captured_req_width = resolve_num_tokens_per_req(
|
||||
phase="draft_decode", server_args=model_runner.server_args
|
||||
)
|
||||
self.captured_req_width = resolve_num_tokens_per_req(phase="draft_decode")
|
||||
self.max_bs = max(self.capture_bs)
|
||||
self.max_num_token = self.max_bs * self.captured_req_width
|
||||
|
||||
|
||||
@@ -130,9 +130,7 @@ class EAGLEDraftExtendCudaGraphRunner(DecodeCudaGraphRunner):
|
||||
|
||||
# Static capture width: full tree width (num_draft_tokens), not
|
||||
# num_steps + 1 -- topk > 1 draft-extend overflows the buffers.
|
||||
self.captured_req_width = resolve_num_tokens_per_req(
|
||||
phase="draft_extend", server_args=model_runner.server_args
|
||||
)
|
||||
self.captured_req_width = resolve_num_tokens_per_req(phase="draft_extend")
|
||||
self.max_bs = max(self.capture_bs)
|
||||
self.max_num_token = self.max_bs * self.captured_req_width
|
||||
|
||||
|
||||
@@ -116,9 +116,7 @@ class FrozenKVMTPCudaGraphRunner(DecodeCudaGraphRunner):
|
||||
self.capture_hidden_mode = CaptureHiddenMode.LAST
|
||||
|
||||
# Static capture width.
|
||||
self.captured_req_width = resolve_num_tokens_per_req(
|
||||
phase="draft_decode", server_args=model_runner.server_args
|
||||
)
|
||||
self.captured_req_width = resolve_num_tokens_per_req(phase="draft_decode")
|
||||
self.capture_bs, _ = get_batch_sizes_to_capture(
|
||||
model_runner, self.captured_req_width
|
||||
)
|
||||
|
||||
@@ -189,10 +189,7 @@ class MultiLayerEagleDraftExtendCudaGraphRunner(DecodeCudaGraphRunner):
|
||||
# tokens, which lets all steps share one buffer set.
|
||||
self.num_front_tokens = eagle_worker.draft_extend_num_front_tokens
|
||||
self.captured_req_width = (
|
||||
resolve_num_tokens_per_req(
|
||||
phase="draft_extend", server_args=model_runner.server_args
|
||||
)
|
||||
+ self.num_front_tokens
|
||||
resolve_num_tokens_per_req(phase="draft_extend") + self.num_front_tokens
|
||||
)
|
||||
self.max_bs = max(self.capture_bs)
|
||||
self.max_num_token = self.max_bs * self.captured_req_width
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Callable, List, Optional, Tuple, Type, Union
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.runtime_context import get_spec as get_spec_config
|
||||
from sglang.srt.speculative.spec_registry import (
|
||||
CustomSpecAlgo,
|
||||
ServerArgsValidator,
|
||||
@@ -172,7 +173,6 @@ class SpeculativeAlgorithm(Enum):
|
||||
def build_disagg_draft_input(
|
||||
self,
|
||||
batch: ScheduleBatch,
|
||||
server_args: ServerArgs,
|
||||
last_tokens_tensor: torch.Tensor,
|
||||
future_map: FutureMap,
|
||||
) -> Optional[SpecInput]:
|
||||
@@ -181,16 +181,14 @@ class SpeculativeAlgorithm(Enum):
|
||||
build_eagle_disagg_draft_input,
|
||||
)
|
||||
|
||||
return build_eagle_disagg_draft_input(
|
||||
batch, server_args, last_tokens_tensor, future_map
|
||||
)
|
||||
return build_eagle_disagg_draft_input(batch, last_tokens_tensor, future_map)
|
||||
if self.is_dspark():
|
||||
from sglang.srt.speculative.dspark_disaggregation import (
|
||||
build_dspark_disagg_draft_input,
|
||||
)
|
||||
|
||||
return build_dspark_disagg_draft_input(
|
||||
batch, server_args, last_tokens_tensor, future_map
|
||||
batch, last_tokens_tensor, future_map
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -386,14 +384,20 @@ def spec_scale_global_num_tokens(
|
||||
|
||||
def create_dummy_verify_input(
|
||||
spec_algorithm: SpeculativeAlgorithm,
|
||||
server_args: ServerArgs,
|
||||
custom_mask: torch.Tensor,
|
||||
num_tokens_per_req: int,
|
||||
is_draft_worker: bool,
|
||||
) -> Optional[SpecInput]:
|
||||
"""Dummy verify ``SpecInput`` for CUDA-graph capture (per-algorithm dispatch)."""
|
||||
"""Dummy verify ``SpecInput`` for CUDA-graph capture (per-algorithm dispatch).
|
||||
|
||||
The tree shape comes from the bags so the dummy matches the step config
|
||||
being captured; adaptive spec captures each candidate with that config's
|
||||
leaves overridden.
|
||||
"""
|
||||
from sglang.srt.model_executor.forward_batch_info import CaptureHiddenMode
|
||||
|
||||
spec = get_spec_config()
|
||||
|
||||
spec_info = None
|
||||
if spec_algorithm.is_eagle() or spec_algorithm.is_standalone():
|
||||
from sglang.srt.speculative.eagle_info import EagleVerifyInput
|
||||
@@ -409,9 +413,9 @@ def create_dummy_verify_input(
|
||||
retrieve_next_token=None,
|
||||
retrieve_next_sibling=None,
|
||||
retrieve_cum_len=None,
|
||||
spec_steps=server_args.speculative_num_steps,
|
||||
topk=server_args.speculative_eagle_topk,
|
||||
draft_token_num=server_args.speculative_num_draft_tokens,
|
||||
spec_steps=spec.speculative_num_steps,
|
||||
topk=spec.speculative_eagle_topk,
|
||||
draft_token_num=spec.speculative_num_draft_tokens,
|
||||
capture_hidden_mode=CaptureHiddenMode.FULL,
|
||||
seq_lens_sum=None,
|
||||
seq_lens_cpu=None,
|
||||
@@ -423,7 +427,7 @@ def create_dummy_verify_input(
|
||||
spec_info = DFlashVerifyInput(
|
||||
draft_token=None,
|
||||
positions=None,
|
||||
draft_token_num=server_args.speculative_num_draft_tokens,
|
||||
draft_token_num=spec.speculative_num_draft_tokens,
|
||||
custom_mask=None,
|
||||
capture_hidden_mode=(
|
||||
CaptureHiddenMode.NULL if is_draft_worker else CaptureHiddenMode.FULL
|
||||
|
||||
@@ -152,10 +152,14 @@ class CustomSpecAlgo:
|
||||
def build_disagg_draft_input(
|
||||
self,
|
||||
batch: ScheduleBatch,
|
||||
server_args: ServerArgs,
|
||||
last_tokens_tensor: torch.Tensor,
|
||||
future_map: FutureMap,
|
||||
) -> Optional[SpecInput]:
|
||||
"""Build the disaggregation draft input for ``batch``, or ``None``.
|
||||
|
||||
The speculative config comes from ``runtime_context.get_spec()``, which
|
||||
follows a runtime override where the startup record does not.
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -78,7 +78,6 @@ if TYPE_CHECKING:
|
||||
from sglang.srt.managers.tp_worker import TpModelWorker
|
||||
from sglang.srt.mem_cache.allocator import BaseTokenToKVPoolAllocator
|
||||
from sglang.srt.sampling.sampling_batch_info import SamplingBatchInfo
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
|
||||
|
||||
if _is_cuda:
|
||||
@@ -98,7 +97,6 @@ logger = logging.getLogger(__name__)
|
||||
def resolve_num_tokens_per_req(
|
||||
*,
|
||||
phase: Literal["draft_decode", "draft_extend", "target_verify"],
|
||||
server_args: ServerArgs,
|
||||
spec_algorithm=None,
|
||||
is_draft_worker: bool = False,
|
||||
num_draft_tokens: Optional[int] = None,
|
||||
@@ -107,14 +105,19 @@ def resolve_num_tokens_per_req(
|
||||
width (sizes capture shapes / buffers); the per-forward dynamic width
|
||||
lives on ``SpecInput.num_tokens_per_req``. Draft phases are
|
||||
EAGLE-family-only; "target_verify" is algorithm-generic via the hook.
|
||||
|
||||
The widths come from the bags: adaptive spec captures each candidate step
|
||||
config with that config's leaves overridden, so the buffers being sized
|
||||
must follow the override rather than the startup values.
|
||||
"""
|
||||
spec = get_spec()
|
||||
if phase == "draft_decode":
|
||||
return server_args.speculative_eagle_topk
|
||||
return spec.speculative_eagle_topk
|
||||
if phase == "draft_extend":
|
||||
return server_args.speculative_num_draft_tokens
|
||||
return spec.speculative_num_draft_tokens
|
||||
if phase == "target_verify":
|
||||
if num_draft_tokens is None:
|
||||
num_draft_tokens = server_args.speculative_num_draft_tokens
|
||||
num_draft_tokens = spec.speculative_num_draft_tokens
|
||||
return spec_algorithm.get_num_tokens_per_req_for_target_verify(
|
||||
num_draft_tokens, is_draft_worker
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user