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

356 lines
14 KiB
Python

from __future__ import annotations
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any
import torch
from sglang.srt.utils import is_cpu
_is_cpu = is_cpu()
if _is_cpu:
from sgl_kernel import assign_draft_cache_locs_contiguous_cpu
if TYPE_CHECKING:
from sglang.srt.managers.schedule_batch import ScheduleBatch
from sglang.srt.managers.tp_worker import TpModelWorker
from sglang.srt.mem_cache.memory_pool import ReqToTokenPool
from sglang.srt.model_executor.model_runner import ModelRunner
from sglang.srt.speculative.eagle_draft_cuda_graph_runner import (
EAGLEDraftCudaGraphRunner,
)
from sglang.srt.speculative.eagle_info import (
EagleDraftExtendInput,
EagleDraftInput,
)
def duplicate_prefix_tail_to_draft_branches(
token_to_kv_pool,
rows: torch.Tensor,
prefix_base: torch.Tensor,
last_page: torch.Tensor,
num_new_pages: torch.Tensor,
topk: int,
page_size: int,
) -> None:
"""Copy the prefix partial-tail page into each branch's first-page holes (page>1 + topk>1).
The draft-decode expand pass reads each branch's own draft page by block id
(cache_loc // page_size), so branch b>=1's hole slots [0, last_page) must hold the
real prefix tail (branch 0's first page already is it). Mirrors V1 #7725.
"""
if topk <= 1:
return
bs = rows.shape[0]
page_off = torch.arange(page_size, device=rows.device, dtype=torch.int64)
branches = torch.arange(1, topk, device=rows.device, dtype=torch.int64).view(
1, topk - 1, 1
)
# Source: the prefix tail page [prefix_base, prefix_base + page_size), one per branch.
src_pos = (prefix_base.view(bs, 1, 1) + page_off.view(1, 1, page_size)).expand(
bs, topk - 1, page_size
)
# Target: branch b's first page [prefix_base + b*num_new_pages*page, + page_size).
tgt_pos = (
prefix_base.view(bs, 1, 1)
+ branches * (num_new_pages.view(bs, 1, 1) * page_size)
+ page_off.view(1, 1, page_size)
)
# Only [0, last_page) holds real prefix KV; [last_page, page_size) are the branch's
# own draft slots and must not be overwritten.
vmask = (page_off.view(1, 1, page_size) < last_page.view(bs, 1, 1)).expand(
bs, topk - 1, page_size
)
src_slots = torch.gather(rows, 1, src_pos.reshape(bs, -1)).reshape(
bs, topk - 1, page_size
)[vmask]
tgt_slots = torch.gather(rows, 1, tgt_pos.reshape(bs, -1)).reshape(
bs, topk - 1, page_size
)[vmask]
if src_slots.numel() > 0:
token_to_kv_pool.move_kv_cache(tgt_slots, src_slots)
class EagleDraftWorkerBase(ABC):
@abstractmethod
def draft():
pass
@abstractmethod
def draft_extend():
pass
def alloc_memory_pool(self, **kwargs):
pass
def init_attention_backends(self):
"""Subclasses wrap this with their context managers (draft_tp_context,
speculative_moe_backend_context, etc.) rather than reimplementing it."""
self.draft_worker.init_attention_backends()
self.init_attention_backend()
def init_cuda_graphs(self):
"""Capture draft graphs (decode disabled on the draft TpModelWorker)."""
self.draft_worker.init_cuda_graphs(capture_decode_cuda_graph=False)
self._capture_cuda_graphs()
def prepare_for_draft_extend(
self,
draft_extend_input: EagleDraftExtendInput,
batch: ScheduleBatch,
predict: torch.Tensor,
num_draft_tokens: int,
draft_model_runner: Any,
cuda_graph_runner: Any,
):
from sglang.srt.model_executor.forward_batch_info import (
CaptureHiddenMode,
ForwardBatch,
ForwardMode,
)
from sglang.srt.utils.async_probe import maybe_detect_oob
from sglang.srt.utils.common import is_npu
bs = len(batch.seq_lens)
extend_num_tokens = bs * num_draft_tokens
# When seq_lens_cpu is absent, stay on GPU-only path -- no .tolist()/.cpu().
gpu_only = batch.seq_lens_cpu is None
batch.spec_info = draft_extend_input
# Do NOT cast predict dtype here. The caller (e.g., _draft_extend_for_decode)
# may run this under a plan stream; casting inside the plan stream creates a
# cross-stream dependency that can lead to data races and break MTP acceptance.
# The caller should cast to int64 before entering the plan stream context.
batch.input_ids = predict
maybe_detect_oob(
batch.input_ids,
0,
batch.model_config.vocab_size,
"v2 prepare_for_draft_extend input_ids",
)
# init_new requires both list or both Tensor;
# gpu_only emits device tensors to skip H2D.
if gpu_only:
batch.prefix_lens = batch.seq_lens.to(torch.int32)
batch.extend_lens = torch.full(
(bs,), num_draft_tokens, dtype=torch.int32, device=batch.seq_lens.device
)
else:
batch.prefix_lens = batch.seq_lens_cpu.tolist()
batch.extend_lens = [num_draft_tokens] * bs
batch.extend_num_tokens = extend_num_tokens
capture_mode = (
CaptureHiddenMode.NULL
if draft_model_runner.spec_algorithm.is_standalone()
else CaptureHiddenMode.FULL
)
batch.forward_mode = (
ForwardMode.IDLE
if batch.forward_mode.is_idle()
else ForwardMode.DRAFT_EXTEND_V2
)
batch.capture_hidden_mode = capture_mode
forward_batch = ForwardBatch.init_new(batch, draft_model_runner)
# Forward sees post-write length (draft extend writes num_draft_tokens
# slots); mutation stays on forward_batch to preserve SB.seq_lens.
forward_batch.seq_lens = forward_batch.seq_lens + num_draft_tokens
if not gpu_only:
forward_batch.seq_lens_cpu = forward_batch.seq_lens_cpu + num_draft_tokens
forward_batch.seq_lens_sum = int(forward_batch.seq_lens_cpu.sum())
else:
# Supply CPU mirror (extend_seq_lens are all num_draft_tokens) so
# backend max() reads from list without a per-iter D2H sync.
forward_batch.extend_seq_lens_cpu = [num_draft_tokens] * bs
can_cuda_graph = cuda_graph_runner and cuda_graph_runner.can_run_graph(
forward_batch
)
if not batch.forward_mode.is_idle() and not can_cuda_graph:
draft_model_runner.attn_backend.init_forward_metadata(forward_batch)
# 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
# (the `_batch_size == batch_size` assertion, see #27091); the
# marked pre-pad metadata is used as-is, matching the proven
# skip_attn_backend_init=True behavior.
# On NPU with --disable-cuda-graph, block_table shape won't match
# after prepare_mlp_sync_batch padding; defer re-init to
# forward_extend (post-pad) instead.
if not is_npu() or can_cuda_graph:
forward_batch.mark_forward_metadata_ready()
return forward_batch
def prepare_for_draft(
self,
draft_input: EagleDraftInput,
req_to_token_pool: ReqToTokenPool,
batch: ScheduleBatch,
cuda_graph_runner: EAGLEDraftCudaGraphRunner,
draft_model_runner: ModelRunner,
topk: int,
num_steps: int,
):
from sglang.kernels.ops.speculative.cache_locs import (
assign_draft_cache_locs_contiguous,
)
from sglang.srt.model_executor.forward_batch_info import (
CaptureHiddenMode,
ForwardBatch,
)
if not batch.forward_mode.is_idle():
bs = len(batch.seq_lens)
# Assign cache locations (draft-write targets).
page_size = batch.token_to_kv_pool_allocator.page_size
if page_size == 1 or topk == 1:
batch.out_cache_loc = torch.empty(
(bs * topk * num_steps,),
dtype=torch.int64,
device=batch.device,
)
if _is_cpu:
assign_draft_cache_locs_contiguous_cpu(
batch.req_pool_indices,
req_to_token_pool.req_to_token,
batch.seq_lens,
batch.out_cache_loc,
req_to_token_pool.req_to_token.shape[1],
topk,
num_steps,
)
else:
# FIXME(lsyin): align with the default code path
assign_draft_cache_locs_contiguous[(bs,)](
batch.req_pool_indices,
req_to_token_pool.req_to_token,
batch.seq_lens,
batch.out_cache_loc,
req_to_token_pool.req_to_token.shape[1],
topk,
num_steps,
)
else:
# page_size > 1 + topk > 1: per-branch page-aligned draft pages.
# Reduce out_cache_loc from the page-aligned tree region down to the
# dense draft slots (skip each branch's duplicated prefix-tail slots
# and trailing padding), matching generate_draft_decode_kv_indices'
# paged read formula: prefix_base + t*num_new_pages*page + last_page + s.
# base is batch.seq_lens (== KV-ready committed prefix at draft time;
# the bonus is the tree root written by verify, not part of [0:seq_lens]).
rows = req_to_token_pool.req_to_token[batch.req_pool_indices.long()]
seq_lens = batch.seq_lens.to(torch.int64)
last_page = seq_lens % page_size
prefix_base = seq_lens - last_page
num_new_pages = (last_page + num_steps + page_size - 1) // page_size
topk_ids = torch.arange(
topk, device=rows.device, dtype=torch.int64
).view(1, topk)
starts = (
prefix_base.view(bs, 1)
+ topk_ids * (num_new_pages.view(bs, 1) * page_size)
+ last_page.view(bs, 1)
)
steps = torch.arange(
num_steps, device=rows.device, dtype=torch.int64
).view(1, 1, num_steps)
pos = (starts.view(bs, topk, 1) + steps).reshape(bs, topk * num_steps)
batch.out_cache_loc = (
torch.gather(rows, 1, pos).reshape(-1).contiguous()
)
# Each branch's page-aligned region starts with `last_page` hole slots
# overlapping the prefix tail page; duplicate the real prefix-tail KV
# into them so whole-page reads stay coherent (see helper docstring).
duplicate_prefix_tail_to_draft_branches(
draft_model_runner.token_to_kv_pool,
rows,
prefix_base,
last_page,
num_new_pages,
topk,
page_size,
)
# Get a forward batch
draft_input.num_tokens_per_req = topk
draft_input.num_tokens_for_logprob_per_req = topk
capture_mode = (
CaptureHiddenMode.NULL
if draft_model_runner.spec_algorithm.is_standalone()
else CaptureHiddenMode.LAST
)
draft_input.positions = batch.seq_lens.repeat_interleave(topk, dim=0)
batch.capture_hidden_mode = capture_mode
forward_batch = ForwardBatch.init_new(batch, draft_model_runner)
can_cuda_graph = cuda_graph_runner and cuda_graph_runner.can_run_graph(
forward_batch
)
return forward_batch, can_cuda_graph
class BaseSpecWorker(ABC):
@property
@abstractmethod
def target_worker(self) -> TpModelWorker:
pass
@property
@abstractmethod
def draft_worker(self) -> EagleDraftWorkerBase:
pass
@property
def war_fastpath_runner(self):
# The runner that runs the step's LAST shared-buffer-reading phase --
# it owns the read-done event the scheduler's WAR barrier waits on.
# Default is the target runner; override if the last phase runs
# elsewhere (eagle's draft_extend runs on the draft runner).
return self.target_worker.model_runner
@property
def spec_v2_attn_backends(self) -> tuple:
"""Attn backends touched by spec_v2 forward; OR-ed by decide_needs_cpu_seq_lens.
Default returns target only; subclasses extend with draft backends."""
return (self.target_worker.model_runner.attn_backend,)
@abstractmethod
def clear_cache_pool(self):
# TODO: move this abstract method to BaseTpWorker and call through self.model_runner
pass
def alloc_memory_pool(self, **kwargs):
pass
def init_attention_backends(self):
pass
def init_cuda_graphs(self):
pass
def on_verify_complete_cpu(
self, num_correct_drafts_per_req: list[int], batch_size: int = 0
) -> None:
"""Hook called after verify finishes and accept counts are on CPU.
Default no-op. Adaptive-aware workers override this to feed the
controller without forcing a GPU→CPU sync in the worker hot path.
"""
pass
def note_request_finished(self, *, rid: str, natural_stop: bool) -> None:
"""Hook called by the batch-result processor when a request finishes.
Default no-op. DSpark overrides this to settle / censor its
block-accept estimator state for the finished request.
"""
pass
def activate_step_by_batch(self, batch_size: int) -> None:
"""Activate the optimal adaptive step for the current batch size.
Default no-op. Adaptive-aware workers override this to switch
the runtime state before each draft round.
"""
pass