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

2116 lines
84 KiB
Python

from __future__ import annotations
import enum
import functools
import logging
from dataclasses import dataclass, field
from typing import (
TYPE_CHECKING,
Dict,
List,
Literal,
Optional,
Tuple,
TypeVar,
Union,
)
import torch
import torch.nn.functional as F
from sglang.jit_kernel.dsv4.online_c128_mtp import OnlineC128MTPController
from sglang.srt.environ import envs
from sglang.srt.layers.attention.base_attn_backend import AttentionBackend
from sglang.srt.layers.attention.dsv4.attn_metadata_kernels import (
BuildCausalSwaPageIndices,
BuildPageTablePositions,
ExpandPrefillCausally,
)
from sglang.srt.layers.attention.dsv4.compressor_v2 import (
CompressorBackendMixin,
FusedCompressMetadata,
create_paged_compressor_data,
)
from sglang.srt.layers.attention.dsv4.dequant_k_cache import (
dequantize_k_cache_paged,
)
from sglang.srt.layers.attention.dsv4.indexer import C4IndexerBackendMixin
from sglang.srt.layers.attention.dsv4.metadata import (
_LARGE_INDEXER_QUERY_THRESHOLD,
PagedIndexerMetadata,
copy_metadata,
maybe_copy_inplace,
)
from sglang.srt.layers.attention.dsv4.metadata_kernel import (
init_compression_metadata as _init_compression_metadata_triton,
)
from sglang.srt.layers.attention.dsv4.quant_k_cache import (
quant_to_nope_fp8_rope_bf16_pack_triton,
)
from sglang.srt.layers.attention.dsv4.sparse_prefill_utils import (
SparsePrefillChunkCache,
SparsePrefillWorkspace,
)
from sglang.srt.mem_cache.deepseek_v4_memory_pool import DeepSeekV4TokenToKVPool
from sglang.srt.model_executor.forward_batch_info import ForwardBatch, ForwardMode
from sglang.srt.runtime_context import get_parallel
from sglang.srt.speculative.dspark_components.kernels.dspark_attn_metadata import (
BuildBlockSeqLensCausal,
BuildDsparkSwaPageIndices,
ComputeDsparkWindowGather,
)
from sglang.srt.speculative.eagle_utils import per_step_draft_out_cache_loc
from sglang.srt.speculative.ragged_verify import (
RaggedVerifyMode,
compute_ragged_extend_lengths,
compute_target_verify_graph_key,
compute_uniform_extend_lengths,
read_ragged_verify_mode,
resolve_ragged_verify_layout,
)
from sglang.srt.utils import ceil_align, is_xpu
from sglang.srt.utils.common import is_sm120_supported
if TYPE_CHECKING:
from sgl_kernel.flash_mla import FlashMLASchedMeta
from sglang.srt.layers.radix_attention import RadixAttention
from sglang.srt.model_executor.model_runner import ModelRunner
from sglang.srt.speculative.ragged_verify import RaggedVerifyLayout
_is_sm120 = is_sm120_supported()
_is_xpu = is_xpu()
logger = logging.getLogger(__name__)
SWA_WINDOW = 128
C4_TOPK = 512
PAGE_INDEX_ALIGNED_SIZE = 64
def _get_logical_forward_mode(forward_batch: ForwardBatch) -> ForwardMode:
# IDLE is a real per-DP-rank mode. Do not let a stale _original_forward_mode
# from a reused/padded ForwardBatch turn an empty rank into TARGET_VERIFY.
if forward_batch.forward_mode.is_idle():
return forward_batch.forward_mode
return (
getattr(forward_batch, "_original_forward_mode", None)
or forward_batch.forward_mode
)
def _get_target_verify_bs(forward_batch: ForwardBatch) -> int:
actual_forward_mode = getattr(
forward_batch, "actual_forward_mode", forward_batch.forward_mode
)
if actual_forward_mode.is_idle():
return 0
spec_info = getattr(forward_batch, "spec_info", None)
draft_token_num = getattr(spec_info, "draft_token_num", 0)
draft_token = getattr(spec_info, "draft_token", None)
if draft_token is None:
return forward_batch.batch_size
if draft_token_num <= 0:
return 0
draft_count = len(draft_token)
if draft_count % draft_token_num != 0:
return 0
return draft_count // draft_token_num
T = TypeVar("T", bound=Optional[torch.Tensor])
def _pad_last_dim(x: T, multiples_of: int = PAGE_INDEX_ALIGNED_SIZE) -> T:
if x is None:
return None
curr_size = x.shape[-1]
target_size = ceil_align(curr_size, multiples_of)
return F.pad(x, pad=(0, target_size - curr_size), mode="constant", value=-1)
def _create_flashmla_metadata():
if _is_sm120 or _is_xpu:
return None
import sgl_kernel.flash_mla as flash_mla
return flash_mla.get_mla_metadata()[0]
def _create_dummy_paged_compress_data(compress_ratio: int):
return None
def _copy_or_replace(dst, src):
if dst is not None and src is not None:
dst.copy_(src)
return dst
return src
@dataclass
class DSV4AttnMetadata:
page_size: int
page_table: torch.Tensor
raw_out_loc: torch.Tensor
cuda_int32_kwargs: dict
seq_lens_casual: torch.Tensor
positions_casual: torch.Tensor
swa_page_indices: torch.Tensor
swa_topk_lengths: torch.Tensor
c4_sparse_topk: int
# SWA KV-store write target (out_cache_loc translated to SWA space), computed
# once per iteration in make_core_attn_metadata and read by the store path.
swa_out_cache_loc: Optional[torch.Tensor] = None
c4_out_loc: Optional[torch.Tensor] = None
c4_topk_lengths_raw: Optional[torch.Tensor] = None
c4_topk_lengths_clamp1: Optional[torch.Tensor] = None
c4_sparse_topk_lengths: torch.Tensor = field(init=False)
c4_sparse_page_indices: torch.Tensor = field(init=False)
c4_sparse_raw_indices: Optional[torch.Tensor] = field(init=False, default=None)
c128_out_loc: Optional[torch.Tensor] = None
c128_page_indices: Optional[torch.Tensor] = None
c128_topk_lengths_clamp1: Optional[torch.Tensor] = None
c1_flashmla_metadata: FlashMLASchedMeta = field(init=False, repr=False)
c4_flashmla_metadata: FlashMLASchedMeta = field(init=False, repr=False)
c128_flashmla_metadata: FlashMLASchedMeta = field(init=False, repr=False)
@property
def positions(self) -> torch.Tensor:
return self.positions_casual
def get_flashmla_metadata(self, compress_ratio: Literal[0, 4, 128]):
if compress_ratio == 0:
return self.c1_flashmla_metadata
elif compress_ratio == 4:
return self.c4_flashmla_metadata
elif compress_ratio == 128:
return self.c128_flashmla_metadata
else:
raise ValueError(f"invalid {compress_ratio=}")
def copy_(self, other: DSV4AttnMetadata) -> None:
copy_metadata(
src=other,
dst=self,
check_eq_fields=[
"c4_sparse_topk",
"page_size",
"cuda_int32_kwargs",
],
copy_fields=[
"raw_out_loc",
"seq_lens_casual",
"positions_casual",
"c4_out_loc",
"c128_out_loc",
"page_table",
"swa_page_indices",
"swa_topk_lengths",
"c128_page_indices",
"c128_topk_lengths_clamp1",
"c4_topk_lengths_raw",
"c4_topk_lengths_clamp1",
"c4_sparse_topk_lengths",
"c4_sparse_page_indices",
"c4_sparse_raw_indices",
],
assign_fields=[
# Recomputed by the recorded init_forward_metadata_in_graph op
# each forward; not copied across replays.
"swa_out_cache_loc",
"c1_flashmla_metadata",
"c4_flashmla_metadata",
"c128_flashmla_metadata",
],
)
def refresh_for_breakable_cuda_graph_replay_(self, other: DSV4AttnMetadata) -> None:
assert self.c4_sparse_topk == other.c4_sparse_topk
assert self.page_size == other.page_size
assert self.cuda_int32_kwargs == other.cuda_int32_kwargs
tensor_copy_fields = [
"raw_out_loc",
"seq_lens_casual",
"positions_casual",
"c4_out_loc",
"c128_out_loc",
"c4_topk_lengths_raw",
"c4_topk_lengths_clamp1",
"c4_sparse_topk_lengths",
]
reference_assign_fields = [
"page_table",
"swa_page_indices",
"swa_topk_lengths",
"c128_page_indices",
"c128_topk_lengths_clamp1",
"c1_flashmla_metadata",
"c4_flashmla_metadata",
"c128_flashmla_metadata",
]
# Keep graph-captured tensor objects alive for fields that captured
# kernels read by address; overwrite only their contents.
for field_name in tensor_copy_fields:
src_val = getattr(other, field_name)
dst_val = getattr(self, field_name)
if src_val is None and dst_val is None:
continue
assert dst_val is not None, f"{field_name=} {src_val=} {dst_val=}"
dst_val.copy_(src_val)
# These fields are safe to replace because captured kernels only need
# the current per-replay objects, or the field is produced inside the
# captured graph before the attention graph break consumes it.
for field_name in reference_assign_fields:
setattr(self, field_name, getattr(other, field_name))
def init_compression_metadata(self):
assert self.page_table.dim() == 2
assert (
self.raw_out_loc.shape == self.seq_lens_casual.shape
), f"{self.raw_out_loc.shape=}, {self.seq_lens_casual.shape=}"
(
self.c4_out_loc,
_,
self.c4_topk_lengths_raw,
self.c4_topk_lengths_clamp1,
self.c128_out_loc,
_,
_,
self.c128_topk_lengths_clamp1,
self.c128_page_indices,
) = _init_compression_metadata_triton(
self.seq_lens_casual,
self.positions_casual,
self.raw_out_loc,
self.page_table,
self.page_size,
compute_page_indices=True,
)
self.c128_page_indices = _pad_last_dim(self.c128_page_indices)
self.swa_page_indices = _pad_last_dim(self.swa_page_indices)
_CP_REINDEX_FIELDS = [
"seq_lens_casual",
"positions_casual",
"swa_page_indices",
"swa_topk_lengths",
"page_table",
"c4_topk_lengths_raw",
"c4_topk_lengths_clamp1",
"c128_page_indices",
"c128_topk_lengths_clamp1",
]
_CP_GLOBAL_FIELDS = [
"raw_out_loc",
"swa_out_cache_loc",
"c4_out_loc",
"c128_out_loc",
]
def apply_cp_reindex(self) -> None:
cp_rank = get_parallel().attn_cp_rank
cp_size = get_parallel().attn_cp_size
idx = slice(cp_rank, None, cp_size)
pre_global_len = self.seq_lens_casual.shape[0]
assert pre_global_len % cp_size == 0, (
f"apply_cp_reindex: global token count {pre_global_len} is not divisible by cp_size={cp_size}. "
"CP round-robin requires padding to ensure divisibility."
)
expected_local_len = pre_global_len // cp_size
for field_name in self._CP_REINDEX_FIELDS:
val = getattr(self, field_name, None)
assert isinstance(
val, torch.Tensor
), f"CP reindex: {field_name} is {type(val)}, expected Tensor"
setattr(self, field_name, val[idx].contiguous())
for field_name in self._CP_REINDEX_FIELDS:
val = getattr(self, field_name)
assert val.shape[0] == expected_local_len, (
f"apply_cp_reindex post-condition: {field_name}.shape[0]={val.shape[0]} "
f"!= expected_local_len={expected_local_len} (cp_size={cp_size})"
)
for field_name in self._CP_GLOBAL_FIELDS:
val = getattr(self, field_name, None)
if val is None:
continue
assert val.shape[0] == pre_global_len, (
f"apply_cp_reindex post-condition: global field {field_name}.shape[0]={val.shape[0]} "
f"!= pre_global_len={pre_global_len} (must remain global for compressor write path)"
)
def init_flashmla_related(self, is_prefill: bool = False):
# c4_sparse_topk is set from model_config.index_topk per-model
# (small model: 512, large model: 1024).
assert self.c4_sparse_topk in (512, 1024), (
f"unexpected c4_sparse_topk={self.c4_sparse_topk}; "
"supported: 512 (small) or 1024 (large)"
)
assert self.c4_topk_lengths_clamp1 is not None
self.c4_sparse_topk_lengths = torch.clamp(
self.c4_topk_lengths_clamp1, max=self.c4_sparse_topk
)
self.c4_sparse_page_indices = torch.full(
(self.c4_topk_lengths_clamp1.size(0), self.c4_sparse_topk),
-1,
dtype=torch.int32,
device=self.c4_topk_lengths_clamp1.device,
)
self.c4_sparse_page_indices = _pad_last_dim(self.c4_sparse_page_indices)
if is_prefill:
self.c4_sparse_raw_indices = torch.empty_like(self.c4_sparse_page_indices)
self.c1_flashmla_metadata = _create_flashmla_metadata()
self.c4_flashmla_metadata = _create_flashmla_metadata()
self.c128_flashmla_metadata = _create_flashmla_metadata()
@dataclass
class DSV4Metadata:
core_attn_metadata: DSV4AttnMetadata
indexer_metadata: Optional[PagedIndexerMetadata]
c4_compress_metadata: Optional[FusedCompressMetadata] = None
c128_compress_metadata: Optional[FusedCompressMetadata] = None
# Lazily populated on the first call to ``_forward_prefill_sparse`` and
# reused across every layer in the chunk. Reset to ``None`` when graph
# metadata is refreshed so replay rebuilds it from the live batch.
sparse_prefill_cache: Optional[SparsePrefillChunkCache] = None
@property
def core_metadata(self) -> DSV4AttnMetadata:
return self.core_attn_metadata
def copy_(self, other: DSV4Metadata):
self.core_attn_metadata.copy_(other.core_attn_metadata)
maybe_copy_inplace(self.indexer_metadata, src=other.indexer_metadata)
maybe_copy_inplace(self.c4_compress_metadata, src=other.c4_compress_metadata)
maybe_copy_inplace(
self.c128_compress_metadata, src=other.c128_compress_metadata
)
self.sparse_prefill_cache = None
def refresh_for_breakable_cuda_graph_replay_(self, static_metadata: DSV4Metadata):
self.core_attn_metadata.refresh_for_breakable_cuda_graph_replay_(
static_metadata.core_attn_metadata
)
maybe_copy_inplace(self.indexer_metadata, src=static_metadata.indexer_metadata)
maybe_copy_inplace(
self.c4_compress_metadata, src=static_metadata.c4_compress_metadata
)
if envs.SGLANG_OPT_USE_ONLINE_COMPRESS.get():
# Online c128 prefill metadata may carry Python-side planner state,
# so assign the freshly built per-replay object.
self.c128_compress_metadata = static_metadata.c128_compress_metadata
else:
maybe_copy_inplace(
self.c128_compress_metadata,
src=static_metadata.c128_compress_metadata,
)
self.sparse_prefill_cache = None
@dataclass
class DSV4RawVerifyMetadata:
req_pool_indices: torch.Tensor
seq_lens: torch.Tensor
out_cache_loc: torch.Tensor
extend_seq_lens: Optional[torch.Tensor] = None
seq_lens_cpu: Optional[List[int]] = None
c128_compress_metadata: Optional[FusedCompressMetadata] = None
extend_start_loc: Optional[torch.Tensor] = None
verify_lens: Optional[torch.Tensor] = None
total_verify_tokens: int = 0
def copy_(self, other: DSV4RawVerifyMetadata):
self.req_pool_indices.copy_(other.req_pool_indices)
self.seq_lens.copy_(other.seq_lens)
self.out_cache_loc.copy_(other.out_cache_loc)
self.extend_seq_lens = other.extend_seq_lens
self.seq_lens_cpu = other.seq_lens_cpu
self.c128_compress_metadata = _copy_or_replace(
self.c128_compress_metadata, other.c128_compress_metadata
)
self.extend_start_loc = other.extend_start_loc
self.verify_lens = other.verify_lens
self.total_verify_tokens = other.total_verify_tokens
@dataclass
class DSV4RawDecodeMetadata:
req_pool_indices: torch.Tensor
seq_lens: torch.Tensor
out_cache_loc: torch.Tensor
def copy_(self, other: DSV4RawDecodeMetadata):
self.req_pool_indices.copy_(other.req_pool_indices)
self.seq_lens.copy_(other.seq_lens)
self.out_cache_loc.copy_(other.out_cache_loc)
class _GraphBucket(enum.Enum):
DECODE_OR_IDLE = "decode_or_idle"
TARGET_VERIFY = "target_verify"
DRAFT_EXTEND = "draft_extend"
@classmethod
def of(cls, forward_mode: ForwardMode) -> _GraphBucket:
if forward_mode.is_decode_or_idle():
return cls.DECODE_OR_IDLE
if forward_mode.is_target_verify():
return cls.TARGET_VERIFY
if forward_mode.is_draft_extend_v2():
return cls.DRAFT_EXTEND
raise NotImplementedError(f"unsupported {forward_mode=}")
class DeepseekV4AttnBackend(
AttentionBackend, C4IndexerBackendMixin, CompressorBackendMixin
):
use_captured_forward_metadata_for_breakable_cuda_graph: bool = True
supports_ragged_verify_graph: bool = True
needs_cpu_seq_lens: bool = False
def __init__(
self,
model_runner: ModelRunner,
skip_prefill: bool = False,
speculative_step_id=0,
topk=0,
speculative_num_steps=0,
):
super().__init__()
self.model_runner = model_runner
self.device = torch.device(model_runner.device)
head_dim = model_runner.model_config.head_dim
assert (
head_dim == 512
), "DSV4 MQA head_dim = qk_nope_head_dim(448) + qk_rope_head_dim(64) = 512"
self.softmax_scale: float = head_dim**-0.5
self.head_dim_v: int = model_runner.model_config.v_head_dim
self.cuda_int32_kwargs = {"device": self.device, "dtype": torch.int32}
self.swa_page_size = 128
assert model_runner.page_size is not None
assert model_runner.req_to_token_pool is not None
self.page_size = model_runner.page_size
assert self.page_size == 256, "the system hardcodes page_size=256"
self.req_to_token_pool = model_runner.req_to_token_pool
self.token_to_kv_pool: DeepSeekV4TokenToKVPool = model_runner.token_to_kv_pool
self.hisparse_coordinator = model_runner.hisparse_coordinator
self.req_to_token = model_runner.req_to_token_pool.req_to_token
self.MAX_SEQ_LEN_FOR_CAPTURE = self.req_to_token.shape[1]
assert isinstance(self.token_to_kv_pool, DeepSeekV4TokenToKVPool)
self.c4_topk = getattr(
model_runner.model_config.hf_text_config, "index_topk", C4_TOPK
)
self.enable_deepseek_v4_fp4_indexer: bool = (
model_runner.server_args.enable_deepseek_v4_fp4_indexer
)
self.topk = model_runner.server_args.speculative_eagle_topk or 0
assert self.topk in [0, 1], "MTP Topk > 1 not supported for DeepSeek V4"
self.mtp_enabled = self.topk > 0
self.speculative_num_steps = speculative_num_steps
self.speculative_num_draft_tokens: int = (
model_runner.server_args.speculative_num_draft_tokens
)
if self.speculative_num_draft_tokens is not None:
# Persistent target-verify metadata buffers. Allocated here (not
# lazily) so they are ordinary tensors: the first touch of a lazy
# buffer would inherit the caller's context, and a creation inside
# an inference_mode forward would forbid the in-place updates the
# graph-capture path performs outside inference mode.
num_reqs = self.req_to_token.shape[0]
self.extend_seq_lens_buffer = torch.full(
(num_reqs,),
self.speculative_num_draft_tokens,
**self.cuda_int32_kwargs,
)
self.extend_start_loc_buffer = torch.zeros(
num_reqs, **self.cuda_int32_kwargs
)
self.speculative_step_id = speculative_step_id
self.forward_metadata: Union[
DSV4Metadata,
DSV4RawVerifyMetadata,
DSV4RawDecodeMetadata,
] = None
self.online_c128_mtp = OnlineC128MTPController(self)
# Draft-extend and online-c128 verify metadata are host-planned, so
# spec runs keep the relay publish (the mirror only exists under
# spec-v2; without spec the flag has no consumer either way).
# DSPARK is the exception: its draft path carries its own host lens
# (reserved_seq_lens_cpu) and its verify prep is device-side.
spec_alg = model_runner.spec_algorithm
if not spec_alg.is_none() and not spec_alg.is_dspark():
self.needs_cpu_seq_lens = True
self.sparse_prefill_workspace = SparsePrefillWorkspace(self.device)
self.is_dspark_draft = model_runner.is_draft_worker and spec_alg.is_dspark()
def _move_to_device(self, x: List[int]) -> torch.Tensor:
pin_tensor = torch.tensor(x, dtype=torch.int32, pin_memory=True)
return pin_tensor.to(self.device, non_blocking=True)
def _resolve_verify_layout(
self,
forward_batch: ForwardBatch,
bs: int,
) -> Optional[RaggedVerifyLayout]:
layout = resolve_ragged_verify_layout(forward_batch)
if layout is None:
return None
if read_ragged_verify_mode() is not RaggedVerifyMode.COMPACT:
return None
if get_parallel().attn_cp_size > 1:
raise NotImplementedError(
"DSV4 ragged verify does not support context parallel (CP); "
"set SGLANG_RAGGED_VERIFY_MODE off for CP runs."
)
if self.online_c128_mtp.enabled():
raise NotImplementedError(
"DSV4 ragged verify does not support online c128 MTP; "
"set SGLANG_RAGGED_VERIFY_MODE off or disable online compress."
)
# Layout invariants (verify_lens >= 1, total == sum) are enforced in
# RaggedVerifyLayout.__post_init__; don't re-check the device tensor
# here -- that would D2H-sync the host-free verify prep path.
layout = layout.padded_to_bucket(padded_bs=bs)
return layout
def _target_verify_graph_key(
self,
bs: int,
ragged_layout: Optional[RaggedVerifyLayout],
) -> Tuple[int, int]:
return compute_target_verify_graph_key(
bs=bs,
num_draft_tokens=self.speculative_num_draft_tokens,
ragged_layout=ragged_layout,
)
def _make_target_verify_c128_metadata(
self,
req_pool_indices: torch.Tensor,
seq_lens: torch.Tensor,
seq_lens_cpu: List[int],
extend_seq_lens: torch.Tensor,
use_prefill_cuda_graph: bool,
online_c128_state_slot_offset: int,
) -> Optional[FusedCompressMetadata]:
if not self.online_c128_mtp.enabled():
return None
num_draft_tokens = self.speculative_num_draft_tokens
seq_lens_cpu = [int(x) + num_draft_tokens for x in seq_lens_cpu]
extend_lens_cpu = [num_draft_tokens] * len(seq_lens_cpu)
return create_paged_compressor_data(
compress_ratio=128,
is_prefill=True,
token_to_kv_pool=self.token_to_kv_pool,
req_to_token=self.req_to_token,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens + self.speculative_num_draft_tokens,
seq_lens_cpu=seq_lens_cpu,
extend_lens=extend_seq_lens,
extend_lens_cpu=extend_lens_cpu,
use_prefill_cuda_graph=use_prefill_cuda_graph,
online_state_slot_offset=online_c128_state_slot_offset,
)
def init_forward_metadata_indexer(
self,
core_attn_metadata: DSV4AttnMetadata,
*,
use_prefill_cuda_graph: bool = False,
):
return PagedIndexerMetadata(
page_size=self.page_size,
page_table=core_attn_metadata.page_table,
c4_seq_lens=core_attn_metadata.c4_topk_lengths_raw,
use_prefill_cuda_graph=use_prefill_cuda_graph,
)
def init_forward_metadata_decode(
self,
max_seq_len: int,
req_pool_indices: torch.Tensor,
seq_lens: torch.Tensor,
out_cache_loc: torch.Tensor,
) -> Union[DSV4Metadata, DSV4RawDecodeMetadata]:
assert (
req_pool_indices.shape[0] == seq_lens.shape[0] == out_cache_loc.shape[0]
), f"{req_pool_indices.shape=} {seq_lens.shape=} {out_cache_loc.shape=}"
if envs.SGLANG_PREP_IN_CUDA_GRAPH.get():
return DSV4RawDecodeMetadata(
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
out_cache_loc=out_cache_loc,
)
core_attn_metadata = self.make_core_attn_metadata(
req_to_token=self.req_to_token,
req_pool_indices_repeated=req_pool_indices,
seq_lens_casual=seq_lens,
max_seq_len=max_seq_len,
out_loc=out_cache_loc,
need_compress=True,
)
indexer_metadata = self.init_forward_metadata_indexer(core_attn_metadata)
create = functools.partial(
create_paged_compressor_data,
is_prefill=False,
token_to_kv_pool=self.token_to_kv_pool,
req_to_token=self.req_to_token,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
)
return DSV4Metadata(
core_attn_metadata,
indexer_metadata,
c4_compress_metadata=create(compress_ratio=4),
c128_compress_metadata=create(compress_ratio=128),
)
def init_forward_metadata_prefill(
self,
max_seq_len: int,
req_pool_indices: torch.Tensor,
seq_lens: torch.Tensor,
seq_lens_cpu: List[int],
out_cache_loc: torch.Tensor,
num_tokens: int,
extend_seq_lens: torch.Tensor,
extend_seq_lens_cpu: List[int],
extend_start_loc: Optional[torch.Tensor] = None,
need_compress: bool = True,
use_prefill_cuda_graph: bool = False,
online_c128_state_slot_offset: int = 0,
dspark_block_size: Optional[int] = None,
) -> DSV4Metadata:
seq_lens_casual, req_pool_indices_repeated = self.expand_prefill_casually(
num_tokens=num_tokens,
seq_lens=seq_lens_cpu,
extend_seq_lens=extend_seq_lens_cpu,
req_pool_indices=req_pool_indices,
padded_num_tokens=out_cache_loc.shape[0],
seq_lens_tensor=seq_lens,
extend_seq_lens_tensor=extend_seq_lens,
extend_start_loc=extend_start_loc,
)
core_attn_metadata = self.make_core_attn_metadata(
req_to_token=self.req_to_token,
req_pool_indices_repeated=req_pool_indices_repeated,
seq_lens_casual=seq_lens_casual,
max_seq_len=max_seq_len,
out_loc=out_cache_loc,
need_compress=need_compress,
is_prefill=True,
dspark_block_size=dspark_block_size,
)
indexer_metadata = (
self.init_forward_metadata_indexer(
core_attn_metadata,
use_prefill_cuda_graph=use_prefill_cuda_graph,
)
if need_compress
else None
)
if not need_compress:
create = _create_dummy_paged_compress_data
else:
def create(compress_ratio: Literal[4, 128]):
# Online c128 uses a different planner that cannot be created in
# prefill cuda-graph mode. Keep c4 graph-friendly while matching
# c128's existing online path.
use_graph_plan = use_prefill_cuda_graph and not (
compress_ratio == 128 and envs.SGLANG_OPT_USE_ONLINE_COMPRESS.get()
)
if use_graph_plan:
return create_paged_compressor_data(
compress_ratio=compress_ratio,
is_prefill=True,
token_to_kv_pool=self.token_to_kv_pool,
req_to_token=self.req_to_token,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
seq_lens_cpu=None,
extend_lens=extend_seq_lens,
extend_lens_cpu=None,
use_prefill_cuda_graph=True,
num_q_tokens=out_cache_loc.shape[0],
online_state_slot_offset=online_c128_state_slot_offset,
)
return create_paged_compressor_data(
compress_ratio=compress_ratio,
is_prefill=True,
token_to_kv_pool=self.token_to_kv_pool,
req_to_token=self.req_to_token,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
seq_lens_cpu=seq_lens_cpu,
extend_lens=extend_seq_lens,
extend_lens_cpu=extend_seq_lens_cpu,
use_prefill_cuda_graph=use_graph_plan,
online_state_slot_offset=online_c128_state_slot_offset,
)
c4_compress_metadata = create(compress_ratio=4)
c128_compress_metadata = create(compress_ratio=128)
return DSV4Metadata(
core_attn_metadata,
indexer_metadata,
c4_compress_metadata=c4_compress_metadata,
c128_compress_metadata=c128_compress_metadata,
)
def init_forward_metadata_target_verify(
self,
max_seq_len: int,
req_pool_indices: torch.Tensor,
seq_lens: torch.Tensor,
seq_lens_cpu: Optional[torch.Tensor] = None,
out_cache_loc: Optional[torch.Tensor] = None,
use_prefill_cuda_graph: bool = False,
online_c128_state_slot_offset: int = 0,
ragged_layout: Optional[RaggedVerifyLayout] = None,
) -> Union[DSV4Metadata, DSV4RawVerifyMetadata]:
if envs.SGLANG_PREP_IN_CUDA_GRAPH.get():
assert out_cache_loc is not None
bs = len(seq_lens)
seq_lens_cpu_list = (
seq_lens_cpu.tolist() if seq_lens_cpu is not None else None
)
if ragged_layout is None:
self.extend_seq_lens_buffer[:bs].fill_(
self.speculative_num_draft_tokens
)
extend_seq_lens = self.extend_seq_lens_buffer[:bs]
extend_start_loc = None
verify_lens = None
total_verify_tokens = self.speculative_num_draft_tokens * bs
else:
self.extend_seq_lens_buffer[:bs].copy_(ragged_layout.verify_lens)
self.extend_start_loc_buffer[:bs].copy_(ragged_layout.extend_start_loc)
extend_seq_lens = self.extend_seq_lens_buffer[:bs]
extend_start_loc = self.extend_start_loc_buffer[:bs]
verify_lens = self.extend_seq_lens_buffer[:bs]
total_verify_tokens = ragged_layout.graph_num_tokens
return DSV4RawVerifyMetadata(
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
out_cache_loc=out_cache_loc,
extend_seq_lens=extend_seq_lens,
seq_lens_cpu=seq_lens_cpu_list,
c128_compress_metadata=self._make_target_verify_c128_metadata(
req_pool_indices,
seq_lens,
seq_lens_cpu_list,
extend_seq_lens,
use_prefill_cuda_graph,
online_c128_state_slot_offset,
),
extend_start_loc=extend_start_loc,
verify_lens=verify_lens,
total_verify_tokens=total_verify_tokens,
)
else:
seq_lens_cpu = seq_lens.tolist()
return self.init_forward_metadata_target_verify_old(
max_seq_len=max_seq_len,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
seq_lens_cpu=seq_lens_cpu,
out_cache_loc=out_cache_loc,
use_prefill_cuda_graph=use_prefill_cuda_graph,
online_c128_state_slot_offset=online_c128_state_slot_offset,
ragged_layout=ragged_layout,
)
def init_forward_metadata_target_verify_old(
self,
max_seq_len: int,
req_pool_indices: torch.Tensor,
seq_lens: torch.Tensor,
seq_lens_cpu: Optional[List[int]] = None,
out_cache_loc: Optional[torch.Tensor] = None,
use_prefill_cuda_graph: bool = False,
online_c128_state_slot_offset: int = 0,
ragged_layout: Optional[RaggedVerifyLayout] = None,
) -> DSV4Metadata:
if ragged_layout is None:
lengths = compute_uniform_extend_lengths(
seq_lens=seq_lens,
seq_lens_cpu=seq_lens_cpu,
extend_len=self.speculative_num_draft_tokens,
)
extend_seq_lens = self._move_to_device(lengths.extend_seq_lens_cpu)
else:
lengths = compute_ragged_extend_lengths(
seq_lens=seq_lens,
seq_lens_cpu=seq_lens_cpu,
ragged_layout=ragged_layout,
)
extend_seq_lens = ragged_layout.verify_lens
seq_lens = lengths.seq_lens_extended
seq_lens_cpu = lengths.seq_lens_cpu_extended
extend_seq_lens_cpu = lengths.extend_seq_lens_cpu
num_tokens = lengths.num_tokens
extend_start_loc = lengths.extend_start_loc
if out_cache_loc is None:
out_cache_loc = seq_lens.new_zeros(num_tokens)
return self.init_forward_metadata_prefill(
max_seq_len=max_seq_len,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
seq_lens_cpu=seq_lens_cpu,
out_cache_loc=out_cache_loc,
num_tokens=num_tokens,
extend_seq_lens=extend_seq_lens,
extend_seq_lens_cpu=extend_seq_lens_cpu,
extend_start_loc=extend_start_loc,
need_compress=True,
use_prefill_cuda_graph=use_prefill_cuda_graph,
online_c128_state_slot_offset=online_c128_state_slot_offset,
)
def init_forward_metadata_dspark_draft_block(
self,
max_seq_len: int,
req_pool_indices: torch.Tensor,
seq_lens: torch.Tensor,
seq_lens_cpu: Optional[torch.Tensor],
out_cache_loc: torch.Tensor,
block_size: int,
) -> DSV4Metadata:
if seq_lens_cpu is None:
seq_lens_cpu_list = seq_lens.tolist()
else:
seq_lens_cpu_list = [int(x) for x in seq_lens_cpu.tolist()]
lengths = compute_uniform_extend_lengths(
seq_lens=seq_lens,
seq_lens_cpu=seq_lens_cpu_list,
extend_len=block_size,
)
extend_seq_lens = self._move_to_device(lengths.extend_seq_lens_cpu)
return self.init_forward_metadata_prefill(
max_seq_len=max_seq_len,
req_pool_indices=req_pool_indices,
seq_lens=lengths.seq_lens_extended,
seq_lens_cpu=lengths.seq_lens_cpu_extended,
out_cache_loc=out_cache_loc,
num_tokens=lengths.num_tokens,
extend_seq_lens=extend_seq_lens,
extend_seq_lens_cpu=lengths.extend_seq_lens_cpu,
extend_start_loc=lengths.extend_start_loc,
need_compress=False,
use_prefill_cuda_graph=False,
dspark_block_size=block_size,
)
def make_forward_metadata_from_raw_verify(
self,
raw_metadata: DSV4RawVerifyMetadata,
online_c128_state_slot_offset: int = 0,
) -> DSV4Metadata:
req_pool_indices = raw_metadata.req_pool_indices
seq_lens = raw_metadata.seq_lens
out_cache_loc = raw_metadata.out_cache_loc
bs, num_draft_tokens = len(seq_lens), self.speculative_num_draft_tokens
extend_seq_lens = raw_metadata.extend_seq_lens
assert extend_seq_lens is not None
is_ragged = raw_metadata.verify_lens is not None
if is_ragged:
seq_lens = seq_lens + extend_seq_lens
num_q_tokens = raw_metadata.total_verify_tokens
assert num_q_tokens > 0, "ragged verify raw metadata is stale/empty"
seq_lens_casual, req_pool_indices_repeated = (
self._expand_prefill_casually_vectorized(
num_tokens=num_q_tokens,
seq_lens=seq_lens,
extend_seq_lens=extend_seq_lens,
extend_start_loc=raw_metadata.extend_start_loc,
req_pool_indices=req_pool_indices,
padded_num_tokens=out_cache_loc.shape[0],
)
)
else:
seq_lens = seq_lens + self.speculative_num_draft_tokens
num_q_tokens = num_draft_tokens * bs
seq_lens_casual, req_pool_indices_repeated = (
self.expand_extend_with_same_length(
bs=bs,
qo_len=num_draft_tokens,
seq_lens=seq_lens,
req_pool_indices=req_pool_indices,
)
)
core_attn_metadata = self.make_core_attn_metadata(
req_to_token=self.req_to_token,
req_pool_indices_repeated=req_pool_indices_repeated,
seq_lens_casual=seq_lens_casual,
max_seq_len=self.MAX_SEQ_LEN_FOR_CAPTURE,
out_loc=out_cache_loc,
need_compress=True,
)
indexer_metadata = self.init_forward_metadata_indexer(core_attn_metadata)
create = functools.partial(
create_paged_compressor_data,
is_prefill=True,
token_to_kv_pool=self.token_to_kv_pool,
req_to_token=self.req_to_token,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
extend_lens=extend_seq_lens,
seq_lens_cpu=None,
extend_lens_cpu=None,
use_prefill_cuda_graph=True,
num_q_tokens=num_q_tokens,
online_state_slot_offset=online_c128_state_slot_offset,
)
c128_compress_metadata = raw_metadata.c128_compress_metadata
if c128_compress_metadata is None:
c128_compress_metadata = create(compress_ratio=128)
return DSV4Metadata(
core_attn_metadata,
indexer_metadata,
c4_compress_metadata=create(compress_ratio=4),
c128_compress_metadata=c128_compress_metadata,
)
def make_forward_metadata_from_raw_decode(
self,
raw_metadata: DSV4RawDecodeMetadata,
) -> DSV4Metadata:
req_pool_indices = raw_metadata.req_pool_indices
seq_lens = raw_metadata.seq_lens
out_cache_loc = raw_metadata.out_cache_loc
core_attn_metadata = self.make_core_attn_metadata(
req_to_token=self.req_to_token,
req_pool_indices_repeated=req_pool_indices,
seq_lens_casual=seq_lens,
max_seq_len=self.MAX_SEQ_LEN_FOR_CAPTURE,
out_loc=out_cache_loc,
need_compress=True,
)
indexer_metadata = self.init_forward_metadata_indexer(core_attn_metadata)
create = functools.partial(
create_paged_compressor_data,
is_prefill=False,
token_to_kv_pool=self.token_to_kv_pool,
req_to_token=self.req_to_token,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
)
return DSV4Metadata(
core_attn_metadata,
indexer_metadata,
c4_compress_metadata=create(compress_ratio=4),
c128_compress_metadata=create(compress_ratio=128),
)
def init_forward_metadata_draft_extend(
self,
max_seq_len: int,
req_pool_indices: torch.Tensor,
seq_lens: torch.Tensor,
seq_lens_cpu: List[int],
num_tokens_per_bs: int,
out_cache_loc: Optional[torch.Tensor] = None,
use_prefill_cuda_graph: bool = False,
) -> DSV4Metadata:
batch_size = len(seq_lens)
extend_seq_lens_cpu = [num_tokens_per_bs] * batch_size
extend_seq_lens = self._move_to_device(extend_seq_lens_cpu)
num_tokens = num_tokens_per_bs * batch_size
if out_cache_loc is None:
out_cache_loc = seq_lens.new_zeros(num_tokens)
return self.init_forward_metadata_prefill(
seq_lens=seq_lens,
max_seq_len=max_seq_len,
req_pool_indices=req_pool_indices,
seq_lens_cpu=seq_lens_cpu,
out_cache_loc=out_cache_loc,
num_tokens=num_tokens,
extend_seq_lens=extend_seq_lens,
extend_seq_lens_cpu=extend_seq_lens_cpu,
extend_start_loc=None,
need_compress=False,
use_prefill_cuda_graph=use_prefill_cuda_graph,
)
def init_forward_metadata_in_graph(self, forward_batch: ForwardBatch) -> None:
# Upgrade Raw->Full so the c4/c128 compress + core_attn + indexer
# materialization is recorded inside the cuda graph; a no-op (Full
# already) when PREP_IN_CUDA_GRAPH=0.
if isinstance(self.forward_metadata, DSV4RawVerifyMetadata):
self.forward_metadata = self.make_forward_metadata_from_raw_verify(
raw_metadata=self.forward_metadata,
online_c128_state_slot_offset=self.online_c128_mtp.state_slot_offset(),
)
elif isinstance(self.forward_metadata, DSV4RawDecodeMetadata):
self.forward_metadata = self.make_forward_metadata_from_raw_decode(
raw_metadata=self.forward_metadata,
)
# Compute the SWA KV-store write target once per forward and cache it on
# the metadata for every layer's store. This is recorded inside the cuda
# graph, so replay re-reads the live out_cache_loc buffer (spec-v2 and DP
# padding rebind out_cache_loc after out-graph metadata prep). flash_mla
# kernels require int32 indices.
metadata = self.forward_metadata
if (
isinstance(metadata, DSV4Metadata)
and forward_batch.out_cache_loc is not None
):
out_cache_loc = forward_batch.out_cache_loc
if (
forward_batch.forward_mode.is_decode_or_idle()
and self.topk > 0
and self.speculative_num_steps > 1
):
# Multi-step draft decode shares one out_cache_loc buffer across
# steps; mirror the eager init's per-step slice.
out_cache_loc = per_step_draft_out_cache_loc(
out_cache_loc,
forward_batch.batch_size,
self.topk,
self.speculative_num_steps,
)[self.speculative_step_id]
metadata.core_attn_metadata.swa_out_cache_loc = (
self.token_to_kv_pool.translate_loc_from_full_to_swa(out_cache_loc).to(
torch.int32
)
)
if self.is_dspark_draft and forward_batch.forward_mode.is_target_verify():
block_size = int(forward_batch.spec_info.draft_token_num)
seq_lens_casual = self._dspark_seq_lens_casual(
seq_lens=forward_batch.seq_lens, block_size=block_size
)
req_pool_indices_repeated = (
forward_batch.req_pool_indices.repeat_interleave(block_size)
)
(
swa_page_indices,
swa_topk_lengths,
) = self.get_dspark_swa_page_indices(
seq_lens_casual=seq_lens_casual,
req_pool_indices_repeated=req_pool_indices_repeated,
out_loc=out_cache_loc,
block_size=block_size,
)
metadata.core_attn_metadata.swa_page_indices = swa_page_indices
metadata.core_attn_metadata.swa_topk_lengths = swa_topk_lengths
def _dspark_seq_lens_casual(
self, *, seq_lens: torch.Tensor, block_size: int
) -> torch.Tensor:
return BuildBlockSeqLensCausal.execute(
seq_lens=seq_lens,
block_size=block_size,
device=self.cuda_int32_kwargs["device"],
)
def init_forward_metadata_out_graph(
self,
forward_batch: ForwardBatch,
in_capture: bool = False,
) -> None:
bucket = _GraphBucket.of(forward_batch.forward_mode)
bs = forward_batch.batch_size
req_pool_indices = forward_batch.req_pool_indices
seq_lens = forward_batch.seq_lens
if in_capture:
# Captured graph does no real cache writes, so synthesize a dummy
# out_cache_loc per bucket (replay supplies the real value).
assert req_pool_indices.size(0) == bs
assert seq_lens.size(0) == bs
num_tokens = forward_batch.positions.numel()
if bucket == _GraphBucket.DECODE_OR_IDLE:
out_cache_loc = torch.zeros_like(seq_lens)
elif bucket == _GraphBucket.TARGET_VERIFY:
out_cache_loc = torch.zeros(num_tokens, **self.cuda_int32_kwargs)
else:
out_cache_loc = None
actual_forward_mode = forward_batch.forward_mode
seq_lens_sum = int(seq_lens.sum().item())
seq_lens_cpu = seq_lens.cpu()
else:
out_cache_loc = forward_batch.out_cache_loc
actual_forward_mode = getattr(
forward_batch, "actual_forward_mode", forward_batch.forward_mode
)
seq_lens_sum = forward_batch.seq_lens_sum
seq_lens_cpu = forward_batch.seq_lens_cpu
if actual_forward_mode == ForwardMode.IDLE:
logger.debug(
f"[IDLE replay] bs={bs}, "
f"local_seq_lens_len={len(seq_lens)}, "
f"has_graph={bs in self.cuda_graph_metadata_of_bucket_and_bs[_GraphBucket.DECODE_OR_IDLE]}"
)
device = seq_lens.device
seq_lens = torch.ones(bs, dtype=seq_lens.dtype, device=device)
seq_lens_cpu = torch.ones(bs, dtype=torch.int64)
seq_lens_sum = bs
req_pool_indices = torch.zeros(
bs, dtype=req_pool_indices.dtype, device=device
)
out_cache_loc = torch.zeros(bs, dtype=torch.int64, device=device)
seq_lens = seq_lens[:bs]
req_pool_indices = req_pool_indices[:bs]
chosen_max_seq_len = self.MAX_SEQ_LEN_FOR_CAPTURE
if seq_lens_cpu is not None:
seq_lens_cpu = seq_lens_cpu[:bs]
actual_max_seq_len = seq_lens_cpu.max().item()
assert actual_max_seq_len <= chosen_max_seq_len
graph_key = bs
if bucket == _GraphBucket.DECODE_OR_IDLE:
assert out_cache_loc is not None
assert len(out_cache_loc.shape) == 1, f"{out_cache_loc.shape=}"
self.online_c128_mtp.prepare_forward(
actual_forward_mode,
req_pool_indices,
seq_lens,
)
out_cache_loc_padded = torch.nn.functional.pad(
out_cache_loc,
pad=(0, bs - len(out_cache_loc)),
mode="constant",
value=0,
)
temp_metadata = self.init_forward_metadata_decode(
max_seq_len=chosen_max_seq_len,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
out_cache_loc=out_cache_loc_padded,
)
elif bucket == _GraphBucket.TARGET_VERIFY and self.is_dspark_draft:
block_size = self.speculative_num_draft_tokens - 1
num_tokens_block = block_size * bs
assert out_cache_loc is not None
out_cache_loc_padded = torch.nn.functional.pad(
out_cache_loc,
pad=(0, num_tokens_block - len(out_cache_loc)),
mode="constant",
value=0,
)
self.online_c128_mtp.prepare_forward(
actual_forward_mode,
req_pool_indices,
seq_lens,
)
temp_metadata = self.init_forward_metadata_dspark_draft_block(
max_seq_len=chosen_max_seq_len,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
seq_lens_cpu=seq_lens_cpu,
out_cache_loc=out_cache_loc_padded,
block_size=block_size,
)
elif bucket == _GraphBucket.TARGET_VERIFY:
verify_bs = _get_target_verify_bs(forward_batch)
ragged_layout = self._resolve_verify_layout(forward_batch, bs=bs)
graph_key, num_tokens_v = self._target_verify_graph_key(
bs=bs, ragged_layout=ragged_layout
)
if self.online_c128_mtp.enabled() and verify_bs == 0:
self.online_c128_mtp.clear()
self.forward_metadata = self.cuda_graph_metadata_of_bucket_and_bs[
bucket
][graph_key]
return
assert out_cache_loc is not None
assert num_tokens_v >= len(out_cache_loc), (
f"ragged verify token-keyed graph requires the decode cuda-graph "
f"runner to supply out_cache_loc sized to graph_num_tokens "
f"({num_tokens_v}), got {len(out_cache_loc)}; the decode graph "
"runner does not yet route token-keyed ragged captures."
)
out_cache_loc_padded = torch.nn.functional.pad(
out_cache_loc,
pad=(0, num_tokens_v - len(out_cache_loc)),
mode="constant",
value=0,
)
online_c128_state_slot_offset = self.online_c128_mtp.prepare_forward(
actual_forward_mode,
req_pool_indices,
seq_lens,
verify_bs=verify_bs,
)
temp_metadata = self.init_forward_metadata_target_verify(
max_seq_len=chosen_max_seq_len,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
seq_lens_cpu=seq_lens_cpu,
out_cache_loc=out_cache_loc_padded,
use_prefill_cuda_graph=True,
online_c128_state_slot_offset=online_c128_state_slot_offset,
ragged_layout=ragged_layout,
)
elif bucket == _GraphBucket.DRAFT_EXTEND:
self.online_c128_mtp.prepare_forward(
actual_forward_mode,
req_pool_indices,
seq_lens,
)
num_tokens_per_bs = self.draft_extend_num_tokens_per_bs
if out_cache_loc is not None:
# Pad the real write locations to the captured token count so
# raw_out_loc reflects the actual replay out_cache_loc.
out_cache_loc = torch.nn.functional.pad(
out_cache_loc,
pad=(0, num_tokens_per_bs * bs - len(out_cache_loc)),
mode="constant",
value=0,
)
draft_extend_seq_lens_cpu = (
seq_lens_cpu.tolist() if seq_lens_cpu is not None else seq_lens.tolist()
)
temp_metadata = self.init_forward_metadata_draft_extend(
max_seq_len=chosen_max_seq_len,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
seq_lens_cpu=draft_extend_seq_lens_cpu,
num_tokens_per_bs=num_tokens_per_bs,
out_cache_loc=out_cache_loc,
use_prefill_cuda_graph=True,
)
else:
self.online_c128_mtp.clear()
raise NotImplementedError
self.replay_cuda_graph_metadata_from(
bs=graph_key, temp_metadata=temp_metadata, bucket=bucket
)
if in_capture:
# Preserve _current_capture_raw for on_after_cuda_graph_warmup
metadata = self.forward_metadata
self._current_capture_raw = (
metadata
if isinstance(
metadata,
(DSV4RawDecodeMetadata, DSV4RawVerifyMetadata),
)
else None
)
def init_forward_metadata(self, forward_batch: ForwardBatch) -> None:
logical_forward_mode = _get_logical_forward_mode(forward_batch)
if self.mtp_enabled and logical_forward_mode.is_idle():
self.online_c128_mtp.clear()
return
self.forward_metadata = self._build_forward_metadata(forward_batch)
self.init_forward_metadata_in_graph(forward_batch)
def _build_forward_metadata(
self,
forward_batch: ForwardBatch,
*,
max_seq_len_override: Optional[int] = None,
use_prefill_cuda_graph: bool = False,
):
logical_forward_mode = _get_logical_forward_mode(forward_batch)
req_pool_indices = forward_batch.req_pool_indices
seq_lens = forward_batch.seq_lens.to(torch.int32)
seq_lens_cpu = forward_batch.seq_lens_cpu
assert self.req_to_token_pool.req_to_token is self.req_to_token
assert self.swa_page_size % SWA_WINDOW == 0 and self.page_size % 128 == 0
if max_seq_len_override is None:
max_seq_len_override = getattr(forward_batch, "max_seq_len_override", None)
if max_seq_len_override is not None:
max_seq_len = max_seq_len_override
elif seq_lens_cpu is not None:
max_seq_len = int(seq_lens_cpu.max().item())
else:
max_seq_len = int(seq_lens.max().item())
verify_bs = _get_target_verify_bs(forward_batch)
online_c128_state_slot_offset = self.online_c128_mtp.prepare_forward(
logical_forward_mode,
req_pool_indices,
seq_lens,
verify_bs=verify_bs,
)
if logical_forward_mode.is_decode_or_idle():
# DSv4 bakes this step's KV write target (c4/c128) into metadata,
# so slice the shared multi-step out_cache_loc now, not at forward time.
out_cache_loc = forward_batch.out_cache_loc
if self.topk > 0 and self.speculative_num_steps > 1:
out_cache_loc = per_step_draft_out_cache_loc(
out_cache_loc,
forward_batch.batch_size,
self.topk,
self.speculative_num_steps,
)[self.speculative_step_id]
metadata = self.init_forward_metadata_decode(
max_seq_len=max_seq_len,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
out_cache_loc=out_cache_loc,
)
elif self.is_dspark_draft and logical_forward_mode.is_target_verify():
block_size = int(forward_batch.spec_info.draft_token_num)
metadata = self.init_forward_metadata_dspark_draft_block(
max_seq_len=max_seq_len,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
seq_lens_cpu=seq_lens_cpu,
out_cache_loc=forward_batch.out_cache_loc,
block_size=block_size,
)
elif logical_forward_mode.is_target_verify():
ragged_layout = self._resolve_verify_layout(forward_batch, bs=len(seq_lens))
metadata = self.init_forward_metadata_target_verify(
max_seq_len=max_seq_len,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
seq_lens_cpu=seq_lens_cpu,
out_cache_loc=forward_batch.out_cache_loc,
online_c128_state_slot_offset=online_c128_state_slot_offset,
ragged_layout=ragged_layout,
)
elif logical_forward_mode.is_prefill(include_draft_extend_v2=True):
extend_seq_lens_cpu = forward_batch.extend_seq_lens_cpu
extend_seq_lens = forward_batch.extend_seq_lens
assert (
seq_lens is not None
and extend_seq_lens is not None
and extend_seq_lens_cpu is not None
)
is_draft = forward_batch.forward_mode.is_draft_extend_v2()
prefill_seq_lens_cpu = (
seq_lens_cpu.tolist() if seq_lens_cpu is not None else seq_lens.tolist()
)
metadata = self.init_forward_metadata_prefill(
max_seq_len=max_seq_len,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
seq_lens_cpu=prefill_seq_lens_cpu,
out_cache_loc=forward_batch.out_cache_loc,
num_tokens=sum(extend_seq_lens_cpu),
extend_seq_lens=extend_seq_lens,
extend_seq_lens_cpu=extend_seq_lens_cpu,
extend_start_loc=forward_batch.extend_start_loc,
need_compress=not is_draft,
use_prefill_cuda_graph=use_prefill_cuda_graph,
)
else:
raise NotImplementedError(f"unsupported mode {forward_batch.forward_mode=}")
return metadata
def init_forward_metadata_for_breakable_cuda_graph_capture(
self, forward_batch: ForwardBatch
):
self.forward_metadata = self._build_forward_metadata(
forward_batch,
max_seq_len_override=self.MAX_SEQ_LEN_FOR_CAPTURE,
use_prefill_cuda_graph=True,
)
return self.forward_metadata
def prepare_forward_metadata_for_breakable_cuda_graph_replay(
self,
capture_metadata,
forward_batch: ForwardBatch,
*,
static_forward_batch: Optional[ForwardBatch] = None,
) -> None:
# Build graph-compatible metadata against the padded static batch. The
# batch still carries live seq/extend lens, so the online c128 prefill
# plan remains batch-specific without constructing a second metadata set.
static_metadata = self._build_forward_metadata(
static_forward_batch if static_forward_batch is not None else forward_batch,
max_seq_len_override=self.MAX_SEQ_LEN_FOR_CAPTURE,
use_prefill_cuda_graph=True,
)
assert isinstance(capture_metadata, DSV4Metadata)
capture_metadata.refresh_for_breakable_cuda_graph_replay_(static_metadata)
self.forward_metadata = capture_metadata
def init_cuda_graph_state(self, max_bs: int, max_num_tokens: int) -> None:
self.cuda_graph_metadata_of_bucket_and_bs: Dict[
_GraphBucket,
Dict[
int,
Union[
DSV4Metadata,
DSV4RawDecodeMetadata,
DSV4RawVerifyMetadata,
],
],
] = {bucket: {} for bucket in _GraphBucket}
self.draft_extend_num_tokens_per_bs = (
max_num_tokens // max_bs if max_bs > 0 else 1
)
def replay_cuda_graph_metadata_from(
self,
bs: int,
temp_metadata: Union[
DSV4Metadata,
DSV4RawVerifyMetadata,
DSV4RawDecodeMetadata,
],
bucket: _GraphBucket,
) -> None:
bucket_metadata = self.cuda_graph_metadata_of_bucket_and_bs[bucket]
chosen_metadata = bucket_metadata.get(bs)
if chosen_metadata is None:
bucket_metadata[bs] = temp_metadata
self.forward_metadata = temp_metadata
return
chosen_metadata.copy_(temp_metadata)
self.forward_metadata = chosen_metadata
def get_cuda_graph_seq_len_fill_value(self):
return 1
def on_after_cuda_graph_warmup(self):
metadata = self.forward_metadata
if isinstance(metadata, DSV4Metadata) and isinstance(
metadata.core_attn_metadata, DSV4AttnMetadata
):
core = metadata.core_attn_metadata
core.c1_flashmla_metadata = _create_flashmla_metadata()
core.c4_flashmla_metadata = _create_flashmla_metadata()
core.c128_flashmla_metadata = _create_flashmla_metadata()
# PREP_IN_CUDA_GRAPH=True: warmup upgraded raw->full on the host;
# restore raw so capture re-runs the upgrade inside the graph.
current_raw = getattr(self, "_current_capture_raw", None)
if current_raw is not None:
self.forward_metadata = current_raw
def get_swa_out_cache_loc(self, forward_batch: ForwardBatch) -> torch.Tensor:
"""Resolve the SWA KV-store write target for the current forward.
Fast path: the per-forward value cached by init_forward_metadata_in_graph
(recorded inside cuda graphs, so replay re-reads live buffers). Fallback:
translate at store time, matching the pre-cache behavior, for paths that
never run the in-graph init — eager idle (forward_idle skips attn init),
runners that only run the out-graph prep (e.g.
EAGLEDraftExtendCudaGraphRunner) — or whose batch was re-padded after
init (shape mismatch). Idle always falls back: its metadata is absent or
left over from a previous forward, and translating the zero-padded
out_cache_loc writes to the dummy slot.
"""
out_cache_loc = forward_batch.out_cache_loc
core = getattr(self.forward_metadata, "core_attn_metadata", None)
cached = core.swa_out_cache_loc if core is not None else None
if (
cached is not None
and not forward_batch.forward_mode.is_idle()
and cached.shape[0] == out_cache_loc.shape[0]
):
return cached
return self.token_to_kv_pool.translate_loc_from_full_to_swa(out_cache_loc).to(
torch.int32
)
def store_cache(
self, layer_id: int, swa_k: torch.Tensor, forward_batch: ForwardBatch
) -> None:
swa_loc = self.get_swa_out_cache_loc(forward_batch)
if envs.SGLANG_OPT_USE_FUSED_STORE_CACHE.get():
self.token_to_kv_pool.set_swa_key_buffer_radix_fused(
layer_id=layer_id,
swa_loc=swa_loc,
cache_k=swa_k,
)
else:
swa_k_pack = quant_to_nope_fp8_rope_bf16_pack_triton(swa_k)
self.token_to_kv_pool.set_swa_key_buffer_radix(
layer_id=layer_id,
swa_loc=swa_loc,
cache_nope_fp8_rope_bf16_pack=swa_k_pack,
)
def forward(
self,
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
layer: RadixAttention,
forward_batch: ForwardBatch,
compress_ratio: Literal[0, 4, 128],
save_kv_cache: bool = True,
attn_sink: Optional[torch.Tensor] = None,
**_,
) -> torch.Tensor:
if self.mtp_enabled and forward_batch.forward_mode.is_idle():
return q.new_empty(q.shape[0], q.shape[1], layer.v_head_dim)
assert k is v, "DeepseekV4 shares k and v"
swa_k = k
layer_id = layer.layer_id
metadata = self.forward_metadata
core_attn_metadata = metadata.core_attn_metadata
token_to_kv_pool = self.token_to_kv_pool
assert isinstance(token_to_kv_pool, DeepSeekV4TokenToKVPool)
if isinstance(core_attn_metadata, DSV4AttnMetadata):
if save_kv_cache:
self.store_cache(layer_id, swa_k, forward_batch)
swa_k_cache = token_to_kv_pool.get_swa_key_buffer_radix(layer_id)
extra_k_cache, extra_indices, extra_topk_lengths = None, None, None
if compress_ratio == 4:
extra_k_cache = token_to_kv_pool.get_extra_key_buffer(layer_id)
extra_indices = core_attn_metadata.c4_sparse_page_indices
extra_topk_lengths = core_attn_metadata.c4_sparse_topk_lengths
elif compress_ratio == 128:
extra_k_cache = token_to_kv_pool.get_extra_key_buffer(layer_id)
extra_indices = core_attn_metadata.c128_page_indices
extra_topk_lengths = core_attn_metadata.c128_topk_lengths_clamp1
swa_window_size = token_to_kv_pool.swa_window_size
assert swa_k_cache.ndim == 2
k_cache_total_dim = token_to_kv_pool.swa_kv_pool.kv_cache_total_dim
swa_k_cache = swa_k_cache[:, : swa_window_size * k_cache_total_dim].view(
swa_k_cache.shape[0], swa_window_size, 1, k_cache_total_dim
)
if extra_k_cache is not None:
page_sizes = {
4: token_to_kv_pool.page_size // 4,
128: token_to_kv_pool.page_size // 128,
}
extra_k_cache = extra_k_cache[
:, : page_sizes[compress_ratio] * k_cache_total_dim
].view(
extra_k_cache.shape[0],
page_sizes[compress_ratio],
1,
k_cache_total_dim,
)
swa_page_indices = core_attn_metadata.swa_page_indices
swa_topk_lengths = core_attn_metadata.swa_topk_lengths
def match_num_queries(x, value):
if x is None or x.shape[0] == q.shape[0]:
return x
if x.shape[0] > q.shape[0]:
return x[: q.shape[0]]
return _pad_tensor_to_size(x, q.shape[0], value=value)
swa_page_indices = match_num_queries(swa_page_indices, value=0)
swa_topk_lengths = match_num_queries(swa_topk_lengths, value=1)
extra_indices = match_num_queries(extra_indices, value=-1)
extra_topk_lengths = match_num_queries(extra_topk_lengths, value=1)
if q.ndim == 3:
q = q.unsqueeze(1)
if swa_page_indices.ndim == 2:
swa_page_indices = swa_page_indices.unsqueeze(1)
if extra_indices is not None and extra_indices.ndim == 2:
extra_indices = extra_indices.unsqueeze(1)
assert attn_sink is not None
flashmla_metadata = core_attn_metadata.get_flashmla_metadata(compress_ratio)
assert (
swa_page_indices.shape[-1] % 64 == 0
), f"{swa_page_indices.shape=}'s last dimension is not aligned to 64"
if extra_indices is not None:
assert (
extra_indices.shape[-1] % 64 == 0
), f"{extra_indices.shape=}'s last dimension is not aligned to 64"
if forward_batch.forward_mode.is_extend_without_speculative() and (
q.shape[0] > _LARGE_INDEXER_QUERY_THRESHOLD
or envs.SGLANG_OPT_FLASHMLA_SPARSE_PREFILL.get()
):
return self._forward_prefill_sparse(
q=q,
layer_id=layer_id,
compress_ratio=compress_ratio,
forward_batch=forward_batch,
token_to_kv_pool=token_to_kv_pool,
core_attn_metadata=core_attn_metadata,
attn_sink=attn_sink,
)
if _is_sm120:
from sglang.srt.layers.attention.flash_mla_sm120 import (
flash_mla_with_kvcache_sm120,
)
o = flash_mla_with_kvcache_sm120(
q=q,
k_cache=swa_k_cache,
head_dim_v=self.head_dim_v,
softmax_scale=self.softmax_scale,
indices=swa_page_indices,
topk_length=swa_topk_lengths,
attn_sink=attn_sink,
extra_k_cache=extra_k_cache,
extra_indices_in_kvcache=extra_indices,
extra_topk_length=extra_topk_lengths,
)[0]
else:
if _is_xpu:
from sgl_kernel import flash_mla_with_kvcache
else:
from sgl_kernel.flash_mla import flash_mla_with_kvcache
o = flash_mla_with_kvcache(
q=q,
k_cache=swa_k_cache,
head_dim_v=self.head_dim_v,
block_table=None,
cache_seqlens=None,
tile_scheduler_metadata=flashmla_metadata,
softmax_scale=self.softmax_scale,
is_fp8_kvcache=True,
indices=swa_page_indices,
topk_length=swa_topk_lengths,
attn_sink=attn_sink,
extra_k_cache=extra_k_cache,
extra_indices_in_kvcache=extra_indices,
extra_topk_length=extra_topk_lengths,
)[0]
o = o.squeeze(1)
return o
raise NotImplementedError("ragged attention")
def _forward_prefill_sparse(
self,
q: torch.Tensor,
layer_id: int,
compress_ratio: Literal[0, 4, 128],
forward_batch: ForwardBatch,
token_to_kv_pool: DeepSeekV4TokenToKVPool,
core_attn_metadata: DSV4AttnMetadata,
attn_sink: torch.Tensor,
) -> torch.Tensor:
"""Unified prefill via flash_mla_sparse_fwd. Replaces the
flash_mla_with_kvcache call on the extend path. Per request,
positionally gathers the SWA window (always) and the compressed
cache (c4/c128) into a flat bf16 workspace, then lets
flash_mla_sparse_fwd consume the workspace via per-query rebased
indices. Chunk-invariant scaffolding lives in
``self.forward_metadata.sparse_prefill_cache``.
"""
from sgl_kernel.flash_mla import flash_mla_sparse_fwd
# q is (b, 1, h_q, d_qk); flash_mla_sparse_fwd takes (s_q, h_q, d_qk).
q_flat = q.squeeze(1)
cache = self.forward_metadata.sparse_prefill_cache
if cache is None:
seq_lens_cpu = forward_batch.seq_lens_cpu
assert seq_lens_cpu is not None
# ``swa_window_size`` on the pool is its storage page size, not
# the model's SWA window — pass both explicitly.
cache = SparsePrefillChunkCache.build(
seq_lens=forward_batch.seq_lens.to(torch.int32),
extend_seq_lens=forward_batch.extend_seq_lens.to(torch.int32),
req_pool_indices=forward_batch.req_pool_indices.to(torch.int32),
req_to_token=self.req_to_token,
full_to_swa=token_to_kv_pool.full_to_swa_index_mapping,
swa_window_size=SWA_WINDOW,
swa_page_size=token_to_kv_pool.swa_window_size,
num_qo_tokens=q_flat.shape[0],
max_seq_len=int(seq_lens_cpu.max().item()),
)
self.forward_metadata.sparse_prefill_cache = cache
# Resolve the workspace + indices for this ratio, then dequant
# SWA + compressed regions directly into the workspace (no torch.cat).
compressed_slice = None
extra_k_cache = None
extra_page_size = None
flat_token_ids = None
if compress_ratio == 0:
workspace = self.sparse_prefill_workspace.get(cache.swa_token_ids.shape[0])
combined_indices = cache.c0_combined_indices
combined_lens = cache.c0_combined_lens
swa_slice = workspace
else:
extra_page_size = token_to_kv_pool.get_extra_key_page_size(layer_id)
extra_k_cache = token_to_kv_pool.get_extra_key_buffer(layer_id)
if compress_ratio == 128:
assert core_attn_metadata.c128_page_indices is not None
cache.ensure_c128(core_attn_metadata.c128_page_indices)
flat_token_ids = cache.c128_flat_token_ids
combined_indices = cache.c128_combined_indices
combined_lens = cache.c128_combined_lens
else:
assert core_attn_metadata.c4_sparse_raw_indices is not None, (
"sparse-prefill c4 path requires c4_sparse_raw_indices "
"(allocated in init_flashmla_related when is_prefill=True)"
)
cache.ensure_c4(core_attn_metadata.page_table, extra_page_size)
flat_token_ids = cache.c4_flat_token_ids
combined_indices, combined_lens = cache.combine_c4_layer(
c4_sparse_raw_indices=core_attn_metadata.c4_sparse_raw_indices[
: cache.num_qo_tokens
],
)
n_compressed = flat_token_ids.shape[0]
workspace = self.sparse_prefill_workspace.get(
n_compressed + cache.swa_token_ids.shape[0]
)
compressed_slice = workspace[:n_compressed]
swa_slice = workspace[n_compressed:]
if compressed_slice is not None:
dequantize_k_cache_paged(
extra_k_cache,
flat_token_ids,
page_size=extra_page_size,
out=compressed_slice,
)
dequantize_k_cache_paged(
token_to_kv_pool.get_swa_key_buffer_radix(layer_id),
cache.swa_token_ids,
page_size=cache.swa_page_size,
out=swa_slice,
)
kv = workspace
o, _, _ = flash_mla_sparse_fwd(
q=q_flat,
kv=kv,
indices=combined_indices.unsqueeze(1),
sm_scale=self.softmax_scale,
d_v=self.head_dim_v,
attn_sink=attn_sink,
topk_length=combined_lens,
)
return o
def expand_prefill_casually(
self,
num_tokens: int,
seq_lens: List[int],
extend_seq_lens: List[int],
req_pool_indices: torch.Tensor,
padded_num_tokens: Optional[int],
seq_lens_tensor: Optional[torch.Tensor] = None,
extend_seq_lens_tensor: Optional[torch.Tensor] = None,
extend_start_loc: Optional[torch.Tensor] = None,
) -> Tuple[torch.Tensor, torch.Tensor]:
assert seq_lens_tensor is not None and extend_seq_lens_tensor is not None
result = ExpandPrefillCausally.execute(
req_pool_indices=req_pool_indices,
seq_lens=seq_lens_tensor,
extend_seq_lens=extend_seq_lens_tensor,
extend_start_loc=extend_start_loc,
seq_lens_cpu=seq_lens,
extend_seq_lens_cpu=extend_seq_lens,
num_tokens=num_tokens,
padded_num_tokens=padded_num_tokens,
)
return result.seq_lens_casual, result.req_pool_indices_repeated
def _expand_prefill_casually_vectorized(
self,
num_tokens: int,
seq_lens: torch.Tensor,
extend_seq_lens: torch.Tensor,
extend_start_loc: torch.Tensor,
req_pool_indices: torch.Tensor,
padded_num_tokens: Optional[int],
) -> Tuple[torch.Tensor, torch.Tensor]:
result = ExpandPrefillCausally.execute(
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
extend_seq_lens=extend_seq_lens,
extend_start_loc=extend_start_loc,
seq_lens_cpu=None,
extend_seq_lens_cpu=None,
num_tokens=num_tokens,
padded_num_tokens=padded_num_tokens,
)
return result.seq_lens_casual, result.req_pool_indices_repeated
def expand_extend_with_same_length(
self,
*,
bs: int,
qo_len: int,
seq_lens: torch.Tensor,
req_pool_indices: torch.Tensor,
):
seq_lens_casual = seq_lens[:, None] + torch.arange(
-qo_len + 1, 1, **self.cuda_int32_kwargs
)
seq_lens_casual = seq_lens_casual.flatten()
idx_to_req_repeated = torch.arange(
bs, **self.cuda_int32_kwargs
).repeat_interleave(qo_len)
req_pool_indices_repeated = req_pool_indices[idx_to_req_repeated]
return seq_lens_casual, req_pool_indices_repeated
def make_core_attn_metadata(
self,
req_to_token: torch.Tensor,
req_pool_indices_repeated: torch.Tensor,
seq_lens_casual: torch.Tensor,
max_seq_len: int,
out_loc: torch.Tensor,
need_compress: bool = True,
is_prefill: bool = False,
dspark_block_size: Optional[int] = None,
) -> DSV4AttnMetadata:
assert self.swa_page_size == SWA_WINDOW
prep = BuildPageTablePositions.execute(
req_to_token=req_to_token,
req_pool_indices_repeated=req_pool_indices_repeated,
seq_lens_casual=seq_lens_casual,
max_seq_len=max_seq_len,
page_size=self.page_size,
swa_window=SWA_WINDOW,
)
seq_lens_casual = prep.seq_lens_casual
raw_positions = prep.positions_casual
if dspark_block_size is not None:
assert (
self.is_dspark_draft
and dspark_block_size == self.speculative_num_draft_tokens - 1
), (
f"dspark_block_size={dspark_block_size} must equal gamma = "
f"speculative_num_draft_tokens-1={self.speculative_num_draft_tokens - 1} "
f"and is only valid on the DSpark draft backend "
f"(is_dspark_draft={self.is_dspark_draft})."
)
swa_page_indices, swa_topk_lengths = self.get_dspark_swa_page_indices(
seq_lens_casual=seq_lens_casual,
req_pool_indices_repeated=req_pool_indices_repeated,
out_loc=out_loc,
block_size=dspark_block_size,
)
else:
swa_page_indices = BuildCausalSwaPageIndices.execute(
req_to_token=self.req_to_token,
full_to_swa_mapping=self.token_to_kv_pool.full_to_swa_index_mapping,
req_pool_indices_repeated=req_pool_indices_repeated,
seq_lens_casual=seq_lens_casual,
swa_window=SWA_WINDOW,
page_index_aligned_size=PAGE_INDEX_ALIGNED_SIZE,
)
swa_topk_lengths = prep.swa_topk_lengths
page_table = prep.page_table
core_attn_metadata = DSV4AttnMetadata(
page_size=self.page_size,
raw_out_loc=out_loc,
seq_lens_casual=seq_lens_casual,
cuda_int32_kwargs=self.cuda_int32_kwargs,
positions_casual=raw_positions,
page_table=page_table,
swa_page_indices=swa_page_indices,
swa_topk_lengths=swa_topk_lengths,
c4_sparse_topk=self.c4_topk,
)
if need_compress:
core_attn_metadata.init_compression_metadata()
core_attn_metadata.init_flashmla_related(is_prefill=is_prefill)
else:
core_attn_metadata.c4_sparse_topk_lengths = None
core_attn_metadata.c4_sparse_page_indices = None
core_attn_metadata.c4_sparse_raw_indices = None
core_attn_metadata.c1_flashmla_metadata = _create_flashmla_metadata()
core_attn_metadata.c4_flashmla_metadata = None
core_attn_metadata.c128_flashmla_metadata = None
return core_attn_metadata
def get_dspark_swa_page_indices(
self,
*,
seq_lens_casual: torch.Tensor,
req_pool_indices_repeated: torch.Tensor,
out_loc: torch.Tensor,
block_size: int,
) -> Tuple[torch.Tensor, torch.Tensor]:
gather = ComputeDsparkWindowGather.execute(
seq_lens_casual=seq_lens_casual,
req_pool_indices_repeated=req_pool_indices_repeated,
block_size=block_size,
swa_window=SWA_WINDOW,
)
swa_page_indices, swa_topk_lengths = BuildDsparkSwaPageIndices.execute(
req_to_token=self.req_to_token,
full_to_swa_mapping=self.token_to_kv_pool.full_to_swa_index_mapping,
req_pool_indices_per_request=gather.req_pool_indices_per_request,
offsets=gather.offsets,
invalid=gather.invalid,
out_loc=out_loc[: gather.num_q],
context_lens=gather.context_lens,
block_size=block_size,
swa_window=SWA_WINDOW,
page_index_aligned_size=PAGE_INDEX_ALIGNED_SIZE,
)
return swa_page_indices, swa_topk_lengths
class DeepseekV4MultiStepBackend(DeepseekV4AttnBackend):
def __init__(
self, model_runner: ModelRunner, topk: int, speculative_num_steps: int
):
super().__init__(model_runner)
self.model_runner = model_runner
self.topk = topk
self.speculative_num_steps = speculative_num_steps
self.attn_backends: List[DeepseekV4AttnBackend] = []
for i in range(self.speculative_num_steps):
self.attn_backends.append(
DeepseekV4AttnBackend(
model_runner,
speculative_step_id=i,
topk=self.topk,
speculative_num_steps=self.speculative_num_steps,
)
)
def init_forward_metadata_in_graph(self, forward_batch: ForwardBatch) -> None:
for attn_backend in self.attn_backends:
attn_backend.init_forward_metadata_in_graph(forward_batch)
def init_forward_metadata_out_graph(
self,
forward_batch: ForwardBatch,
in_capture: bool = False,
):
from types import SimpleNamespace
inner_fb = SimpleNamespace(
batch_size=forward_batch.batch_size,
forward_mode=ForwardMode.DECODE,
# Propagate the real runtime mode so inner backends can detect IDLE
# and apply their idle substitution.
actual_forward_mode=getattr(
forward_batch, "actual_forward_mode", forward_batch.forward_mode
),
input_ids=getattr(forward_batch, "input_ids", None),
positions=getattr(forward_batch, "positions", None),
req_pool_indices=forward_batch.req_pool_indices,
seq_lens=forward_batch.seq_lens,
seq_lens_sum=forward_batch.seq_lens_sum,
seq_lens_cpu=forward_batch.seq_lens_cpu,
encoder_lens=None,
out_cache_loc=getattr(forward_batch, "out_cache_loc", None),
spec_info=forward_batch.spec_info,
)
if in_capture:
for i in range(self.speculative_num_steps):
self.attn_backends[i].init_forward_metadata_out_graph(
inner_fb, in_capture=True
)
else:
if self.speculative_num_steps == 1:
return
self.attn_backends[0].init_forward_metadata_out_graph(inner_fb)
temp_metadata = self.attn_backends[0].forward_metadata
for i in range(1, self.speculative_num_steps - 1):
self.attn_backends[i].replay_cuda_graph_metadata_from(
bs=forward_batch.batch_size,
temp_metadata=temp_metadata,
bucket=_GraphBucket.DECODE_OR_IDLE,
)
def init_forward_metadata(self, forward_batch: ForwardBatch):
for i in range(self.speculative_num_steps - 1):
self.attn_backends[i].init_forward_metadata(forward_batch)
def init_forward_metadata_for_breakable_cuda_graph_capture(
self, forward_batch: ForwardBatch
):
ret = []
for i in range(self.speculative_num_steps - 1):
ret.append(
self.attn_backends[
i
].init_forward_metadata_for_breakable_cuda_graph_capture(forward_batch)
)
return ret
def prepare_forward_metadata_for_breakable_cuda_graph_replay(
self,
capture_metadata,
forward_batch: ForwardBatch,
*,
static_forward_batch: Optional[ForwardBatch] = None,
) -> None:
assert len(capture_metadata) == self.speculative_num_steps - 1
for i in range(self.speculative_num_steps - 1):
self.attn_backends[
i
].prepare_forward_metadata_for_breakable_cuda_graph_replay(
capture_metadata[i],
forward_batch,
static_forward_batch=static_forward_batch,
)
def init_cuda_graph_state(self, max_bs: int, max_num_tokens: int):
for i in range(self.speculative_num_steps):
self.attn_backends[i].init_cuda_graph_state(max_bs, max_num_tokens)
def on_after_cuda_graph_warmup(self):
for backend in self.attn_backends:
backend.on_after_cuda_graph_warmup()
def _pad_tensor_to_size(tensor: torch.Tensor, size: int, *, value: int = 0):
if value == 0:
return torch.cat(
[tensor, tensor.new_zeros(size - tensor.shape[0], *tensor.shape[1:])],
dim=0,
)
else:
return torch.cat(
[
tensor,
tensor.new_full((size - tensor.shape[0], *tensor.shape[1:]), value),
],
dim=0,
)