Files
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

289 lines
12 KiB
Python

"""DFLASH spec-v2 overlap scheduling data structures."""
import contextlib
from dataclasses import dataclass
from typing import Optional, Tuple
import torch
from sglang.srt.environ import envs
from sglang.srt.managers.schedule_batch import ScheduleBatch
from sglang.srt.mem_cache.common import (
alloc_paged_token_slots_extend,
alloc_token_slots,
get_last_loc,
)
from sglang.srt.runtime_context import get_server_args
from sglang.srt.speculative.spec_info import SpecInput, SpecInputType
from sglang.srt.speculative.spec_utils import assign_req_to_token_pool_func
from sglang.srt.utils.common import is_pin_memory_available
_OVERLAP_PLAN_STREAMS: dict[str, torch.cuda.Stream] = {}
def _get_overlap_plan_stream(
device: torch.device | str,
) -> tuple[Optional[torch.cuda.Stream], contextlib.AbstractContextManager]:
"""Return an optional plan stream/context for overlap scheduling prep kernels."""
if not envs.SGLANG_ENABLE_OVERLAP_PLAN_STREAM.get():
return None, contextlib.nullcontext()
device_str = str(device)
stream = _OVERLAP_PLAN_STREAMS.get(device_str)
if stream is None:
stream = torch.get_device_module(device_str).Stream()
_OVERLAP_PLAN_STREAMS[device_str] = stream
return stream, torch.get_device_module(device_str).stream(stream)
@dataclass
class DFlashDraftInputV2(SpecInput):
"""Draft-side state carried across overlap iterations (spec-v2)."""
# Legacy Eagle-shaped fields; DFLASH relays via FutureMap so these are unused.
topk_p: torch.Tensor
topk_index: torch.Tensor
bonus_tokens: torch.Tensor
new_seq_lens: torch.Tensor
hidden_states: torch.Tensor
max_top_k: int = 1
uniform_top_k_value: Optional[int] = None
reserved_seq_lens_cpu: Optional[torch.Tensor] = None
reserved_seq_lens_sum: Optional[int] = None
_prepare_batch_seq_lens_cpu_buf: Optional[torch.Tensor] = None
_prepare_cur_kv_lens_cpu_buf: Optional[torch.Tensor] = None
_prepare_nxt_kv_lens_cpu_buf: Optional[torch.Tensor] = None
_prepare_cur_kv_lens_gpu_buf: Optional[torch.Tensor] = None
_prepare_nxt_kv_lens_gpu_buf: Optional[torch.Tensor] = None
# Filled by scheduler after dispatch.
future_indices: Optional[torch.Tensor] = None
verify_token_budget: Optional[int] = None
def __post_init__(self):
super().__init__(spec_input_type=SpecInputType.DFLASH_DRAFT)
def get_spec_adjust_token_coefficient(self) -> Tuple[int, int]:
# Spec v2 draft state itself does not change token accounting.
return (1, 1)
def _ensure_prepare_length_buffers(
self, bs: int, device: torch.device | str
) -> None:
pin_memory = is_pin_memory_available(device)
def needs_cpu_alloc(buf: Optional[torch.Tensor]) -> bool:
return buf is None or buf.numel() < bs
def needs_gpu_alloc(buf: Optional[torch.Tensor]) -> bool:
return buf is None or buf.numel() < bs or str(buf.device) != str(device)
def grown_capacity(buf: Optional[torch.Tensor]) -> int:
current = 0 if buf is None else int(buf.numel())
return max(bs, 32, current * 2 if current > 0 else 0)
# The three CPU scratch buffers grow together; capacity is the only
# invariant (batch is int64 non-pinned, cur/nxt are int32 pinned).
if needs_cpu_alloc(self._prepare_batch_seq_lens_cpu_buf):
capacity = grown_capacity(self._prepare_batch_seq_lens_cpu_buf)
self._prepare_batch_seq_lens_cpu_buf = torch.empty(
(capacity,), dtype=torch.int64, device="cpu"
)
self._prepare_cur_kv_lens_cpu_buf = torch.empty(
(capacity,), dtype=torch.int32, device="cpu", pin_memory=pin_memory
)
self._prepare_nxt_kv_lens_cpu_buf = torch.empty(
(capacity,), dtype=torch.int32, device="cpu", pin_memory=pin_memory
)
if needs_gpu_alloc(self._prepare_cur_kv_lens_gpu_buf):
capacity = grown_capacity(self._prepare_cur_kv_lens_gpu_buf)
self._prepare_cur_kv_lens_gpu_buf = torch.empty(
(capacity,), dtype=torch.int32, device=device
)
self._prepare_nxt_kv_lens_gpu_buf = torch.empty(
(capacity,), dtype=torch.int32, device=device
)
@classmethod
def create_idle_input(cls, device: torch.device) -> "DFlashDraftInputV2":
return cls(
topk_p=torch.empty((0, 0), device=device, dtype=torch.float32),
topk_index=torch.empty((0, 0), device=device, dtype=torch.int64),
bonus_tokens=torch.empty((0,), device=device, dtype=torch.int64),
new_seq_lens=torch.empty((0,), device=device, dtype=torch.int64),
hidden_states=torch.empty((0, 0), device=device, dtype=torch.float16),
)
def prepare_for_decode(self, batch: ScheduleBatch):
"""Allocate headroom in the shared req_to_token pool for the next DFLASH step.
DFLASH spec-v2 uses overlap scheduling's "over-allocation" approach: we reserve
future KV slots ahead of time so the worker can gather `out_cache_loc` directly
from `req_to_token` without allocator backup/restore. CPU metadata intentionally
lags by one iteration; keep it separate from the reserved upper bound that backs
the overallocated mapping.
"""
plan_stream, plan_stream_ctx = _get_overlap_plan_stream(batch.device)
bs = batch.batch_size()
if bs == 0:
return
self._ensure_prepare_length_buffers(bs, batch.device)
assert self._prepare_batch_seq_lens_cpu_buf is not None
assert self._prepare_cur_kv_lens_cpu_buf is not None
assert self._prepare_nxt_kv_lens_cpu_buf is not None
assert self._prepare_cur_kv_lens_gpu_buf is not None
assert self._prepare_nxt_kv_lens_gpu_buf is not None
batch_seq_lens_cpu_t = self._prepare_batch_seq_lens_cpu_buf[:bs]
cur_kv_lens_cpu_t = self._prepare_cur_kv_lens_cpu_buf[:bs]
# For DFLASH, each decode step needs a fixed-size verify block.
block_size = int(get_server_args().speculative_num_draft_tokens)
if block_size <= 0:
raise ValueError(
f"DFLASH invalid speculative_num_draft_tokens={block_size}."
)
page_size = batch.token_to_kv_pool_allocator.page_size
nxt_kv_lens_cpu_t = self._prepare_nxt_kv_lens_cpu_buf[:bs]
committed_seq_lens_sum = 0
reserved_seq_lens_sum = 0
num_needed_tokens = 0
max_top_k = 1
uniform_top_k_value = None
uniform_top_k = True
for i, req in enumerate(batch.reqs):
committed_len = int(req.kv_committed_len)
# Read the allocation watermark from the req object like EAGLE.
cur_alloc_len = int(req.kv_allocated_len)
reserved_len = max(cur_alloc_len, committed_len + 2 * block_size)
top_k = int(req.sampling_params.top_k)
batch_seq_lens_cpu_t[i] = committed_len
cur_kv_lens_cpu_t[i] = cur_alloc_len
nxt_kv_lens_cpu_t[i] = reserved_len
committed_seq_lens_sum += committed_len
reserved_seq_lens_sum += reserved_len
num_needed_tokens += reserved_len - cur_alloc_len
if top_k > max_top_k:
max_top_k = top_k
if i == 0:
uniform_top_k_value = top_k
elif uniform_top_k and top_k != uniform_top_k_value:
uniform_top_k = False
self.max_top_k = max(max_top_k, 1)
self.uniform_top_k_value = uniform_top_k_value if uniform_top_k else None
caller_stream = None
if plan_stream is not None:
caller_stream = torch.get_device_module(batch.device).current_stream()
with plan_stream_ctx:
if plan_stream is not None and caller_stream is not None:
# `batch.seq_lens`, `batch.req_pool_indices`, and related tensors may
# have just been rebuilt on the scheduler stream by filter/merge ops.
# The plan stream must wait for those writes before reading them.
plan_stream.wait_stream(caller_stream)
cur_kv_lens = self._prepare_cur_kv_lens_gpu_buf[:bs]
nxt_kv_lens = self._prepare_nxt_kv_lens_gpu_buf[:bs]
cur_kv_lens.copy_(cur_kv_lens_cpu_t, non_blocking=True)
nxt_kv_lens.copy_(nxt_kv_lens_cpu_t, non_blocking=True)
if num_needed_tokens > 0:
if page_size == 1:
out_cache_loc = alloc_token_slots(
batch.tree_cache, num_needed_tokens
)
else:
last_loc = get_last_loc(
batch.req_to_token_pool.req_to_token,
batch.req_pool_indices,
cur_kv_lens,
)
out_cache_loc = alloc_paged_token_slots_extend(
batch.tree_cache,
cur_kv_lens,
cur_kv_lens_cpu_t,
nxt_kv_lens,
nxt_kv_lens_cpu_t,
last_loc,
num_needed_tokens,
)
# Updating req_to_token is a write to a shared tensor: it must not overlap
# with the previous batch's forward, which also reads req_to_token.
assign_req_to_token_pool_func(
batch.req_pool_indices,
batch.req_to_token_pool.req_to_token,
cur_kv_lens,
nxt_kv_lens,
out_cache_loc,
bs,
)
if caller_stream is not None:
# Enqueue the dependency on the caller's stream, not inside the
# plan-stream context, so forward work cannot observe partially
# prepared req_to_token / KV allocation state.
caller_stream.wait_stream(plan_stream)
# This request-side high-water mark is what release_kv_cache() uses to
# reclaim any DFLASH over-allocation if the request finishes later.
for i, req in enumerate(batch.reqs):
req.kv_allocated_len = max(req.kv_allocated_len, int(nxt_kv_lens_cpu_t[i]))
# Seed committed; overlap's resolve overwrites it with the published value.
batch.seq_lens_cpu = batch_seq_lens_cpu_t
batch.seq_lens_sum = committed_seq_lens_sum
self.reserved_seq_lens_cpu = nxt_kv_lens_cpu_t
self.reserved_seq_lens_sum = reserved_seq_lens_sum
def filter_batch(self, new_indices: torch.Tensor, has_been_filtered: bool = True):
if self.reserved_seq_lens_cpu is not None:
self.reserved_seq_lens_cpu = self.reserved_seq_lens_cpu[new_indices.cpu()]
self.reserved_seq_lens_sum = int(self.reserved_seq_lens_cpu.sum().item())
if self.future_indices is not None:
self.future_indices = self.future_indices[new_indices]
return
self.topk_p = self.topk_p[new_indices]
self.topk_index = self.topk_index[new_indices]
self.bonus_tokens = self.bonus_tokens[new_indices]
self.new_seq_lens = self.new_seq_lens[new_indices]
self.hidden_states = self.hidden_states[new_indices]
def merge_batch(self, spec_info: "DFlashDraftInputV2"):
if self.reserved_seq_lens_cpu is not None:
assert spec_info.reserved_seq_lens_cpu is not None
self.reserved_seq_lens_cpu = torch.cat(
[self.reserved_seq_lens_cpu, spec_info.reserved_seq_lens_cpu]
)
self.reserved_seq_lens_sum = int(self.reserved_seq_lens_cpu.sum().item())
elif spec_info.reserved_seq_lens_cpu is not None:
self.reserved_seq_lens_cpu = spec_info.reserved_seq_lens_cpu
self.reserved_seq_lens_sum = spec_info.reserved_seq_lens_sum
if self.future_indices is not None:
assert spec_info.future_indices is not None
self.future_indices = torch.cat(
[self.future_indices, spec_info.future_indices]
)
return
self.topk_p = torch.cat([self.topk_p, spec_info.topk_p], dim=0)
self.topk_index = torch.cat([self.topk_index, spec_info.topk_index], dim=0)
self.bonus_tokens = torch.cat(
[self.bonus_tokens, spec_info.bonus_tokens], dim=0
)
self.new_seq_lens = torch.cat(
[self.new_seq_lens, spec_info.new_seq_lens], dim=0
)
self.hidden_states = torch.cat(
[self.hidden_states, spec_info.hidden_states], dim=0
)