[AMD] Support DeepSeek V4 DSpark on AMD HIP platform (#30964)

This commit is contained in:
Wang, FangYuan
2026-08-08 15:22:14 -07:00
committed by GitHub
parent a59bb931c6
commit ba7abd4f92
9 changed files with 586 additions and 52 deletions
@@ -110,6 +110,57 @@ def store_swa_into_unified(
)
@triton.jit
def _scatter_loc_kernel(
kv_ptr, # [T, D] bf16
loc_ptr, # [T] int (unified row index; <0 => skip)
unified_ptr, # [pages, D] bf16
n_rows,
D: tl.constexpr,
BLOCK_D: tl.constexpr,
):
row = tl.program_id(0)
if row >= n_rows:
return
loc = tl.load(loc_ptr + row).to(tl.int64)
if loc < 0:
return
offs = tl.arange(0, BLOCK_D)
mask = offs < D
vals = tl.load(kv_ptr + row * D + offs, mask=mask, other=0.0)
tl.store(unified_ptr + loc * D + offs, vals, mask=mask)
def scatter_bf16_into_unified(
*,
kv: torch.Tensor, # [T, head_dim] bf16 (already norm+rope'd)
loc: torch.Tensor, # [T] int32/int64 unified ring row; <0 => skip
unified_kv: torch.Tensor, # [pages, head_dim] bf16
) -> None:
"""Scatter already-norm+rope'd bf16 K into ``unified_kv[loc]`` (skip loc < 0).
Companion to ``store_swa_into_unified`` for callers that already hold the
precomputed ring row index (the DSpark draft: ``get_unified_swa_loc`` for the
draft forward, or the commit-inject layout for target-hidden injection) and
need per-row commit masking expressed as ``loc == -1``.
"""
n_rows, D = kv.shape
if n_rows == 0:
return
assert kv.is_contiguous() and kv.dtype == unified_kv.dtype
assert loc.is_contiguous()
assert unified_kv.is_contiguous()
_scatter_loc_kernel[(n_rows,)](
kv,
loc,
unified_kv,
n_rows,
D=D,
BLOCK_D=triton.next_power_of_2(D),
num_warps=8,
)
# ---------------------------------------------------------------------------
# Ragged indptr helper (shared by the decode streams + prefill builders)
# ---------------------------------------------------------------------------
@@ -604,6 +604,37 @@ class CommitInjectLayoutResult(msgspec.Struct):
positions: torch.Tensor
def build_unified_commit_inject_layout(
*,
req_pool_indices: torch.Tensor,
prefix_lens: torch.Tensor,
block_pos_offsets: torch.Tensor,
commit_lens: torch.Tensor,
stride: int,
ring_stride: int,
) -> CommitInjectLayoutResult:
"""unified_kv counterpart of build_commit_inject_layout.
Non-unified injection translates the verify tokens' full cache locs through
``full_to_swa_mapping``; under unified_kv the SWA K lives in a ring addressed
directly by ``state_slot * ring_stride + pos % ring_stride``, so compute the
ring row here instead. Uncommitted tokens (col >= commit_len) get loc = -1 and
are skipped by the scatter. All ops are static-shape (CUDA-graph safe).
"""
bs = req_pool_indices.shape[0]
device = req_pool_indices.device
positions_2d = prefix_lens.unsqueeze(1) + block_pos_offsets[:stride]
positions = positions_2d.reshape(-1).to(torch.int64)
state_slot = (
req_pool_indices.to(torch.int64).view(-1, 1).expand(bs, stride).reshape(-1)
)
loc = state_slot * ring_stride + positions % ring_stride
col = torch.arange(stride, device=device).view(1, -1)
committed = (col < commit_lens.to(torch.long).view(-1, 1)).reshape(-1)
swa_loc = torch.where(committed, loc, torch.full_like(loc, -1)).to(torch.int32)
return CommitInjectLayoutResult(swa_loc=swa_loc, positions=positions)
class BuildCommitInjectLayout:
@classmethod
def execute(cls, *args, **kwargs) -> CommitInjectLayoutResult:
@@ -414,6 +414,7 @@ class DeepseekV4HipRadixBackend(
# both children and leaks ROCm HSA resources (HSA_STATUS_ERROR_OUT_OF_RESOURCES).
# TboAttnBackend reads this to skip children in the *_graph paths only.
tbo_supports_cuda_graph = False
supports_ragged_verify_graph: bool = True
def __init__(
self,
@@ -456,6 +457,18 @@ class DeepseekV4HipRadixBackend(
self.mtp_enabled = self.topk > 0
self.speculative_num_steps = speculative_num_steps
self.speculative_num_draft_tokens: int = get_spec().speculative_num_draft_tokens
self.is_dspark_draft = (
getattr(model_runner, "is_draft_worker", False)
and model_runner.spec_algorithm.is_dspark()
)
self.target_verify_num_draft_tokens = self.speculative_num_draft_tokens
if self.is_dspark_draft:
assert self.speculative_num_draft_tokens is not None
assert self.speculative_num_draft_tokens > 1
# DSpark draft workers verify gamma rows. The server arg keeps the
# CUDA-side convention gamma + 1, so use an explicit effective value
# instead of mutating speculative_num_draft_tokens in place.
self.target_verify_num_draft_tokens = self.speculative_num_draft_tokens - 1
self.speculative_step_id = speculative_step_id
self.forward_metadata: Union[
DSV4Metadata,
@@ -532,14 +545,34 @@ class DeepseekV4HipRadixBackend(
extend_seq_lens_cpu: List[int],
need_compress: bool = True,
use_prefill_cuda_graph: bool = False,
compress_gpu_plan: bool = False,
extend_start_loc: Optional[torch.Tensor] = 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],
)
if extend_start_loc is not None:
from sglang.kernels.ops.attention.dsv4_attn_metadata_kernels import (
ExpandPrefillCausally,
)
_expanded = 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=out_cache_loc.shape[0],
)
seq_lens_casual = _expanded.seq_lens_casual
req_pool_indices_repeated = _expanded.req_pool_indices_repeated
else:
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],
)
core_attn_metadata = self.make_core_attn_metadata(
req_to_token=self.req_to_token,
req_pool_indices_repeated=req_pool_indices_repeated,
@@ -559,6 +592,20 @@ class DeepseekV4HipRadixBackend(
)
if not need_compress:
create = _create_dummy_paged_compress_data
elif compress_gpu_plan:
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,
seq_lens_cpu=None,
extend_lens=extend_seq_lens,
extend_lens_cpu=None,
num_q_tokens=num_tokens,
use_prefill_cuda_graph=use_prefill_cuda_graph,
)
else:
create = functools.partial(
create_paged_compressor_data,
@@ -588,6 +635,7 @@ class DeepseekV4HipRadixBackend(
extend_seq_lens: Optional[torch.Tensor] = None,
use_prefill_cuda_graph: bool = False,
seq_lens_cpu: Optional[List[int]] = None,
ragged_layout=None,
) -> Union[DSV4Metadata, DSV4RawVerifyMetadata]:
# HIP path: build target-verify metadata eagerly even when
# SGLANG_PREP_IN_CUDA_GRAPH is enabled. The raw/lazy-upgrade route can
@@ -601,6 +649,7 @@ class DeepseekV4HipRadixBackend(
seq_lens_cpu=seq_lens_cpu,
out_cache_loc=out_cache_loc,
use_prefill_cuda_graph=use_prefill_cuda_graph,
ragged_layout=ragged_layout,
)
def init_forward_metadata_target_verify_old(
@@ -611,13 +660,38 @@ class DeepseekV4HipRadixBackend(
seq_lens_cpu: Optional[List[int]] = None,
out_cache_loc: Optional[torch.Tensor] = None,
use_prefill_cuda_graph: bool = False,
ragged_layout=None,
) -> DSV4Metadata:
batch_size = len(seq_lens)
seq_lens = seq_lens + self.speculative_num_draft_tokens
seq_lens_cpu = [x + self.speculative_num_draft_tokens for x in seq_lens_cpu]
extend_seq_lens_cpu = [self.speculative_num_draft_tokens] * batch_size
extend_seq_lens = self._move_to_device(extend_seq_lens_cpu)
num_tokens = self.speculative_num_draft_tokens * batch_size
extend_start_loc = None
if ragged_layout is not None:
verify_lens_dev = ragged_layout.verify_lens.to(
device=seq_lens.device, dtype=torch.int32
)
extend_start_loc = ragged_layout.extend_start_loc.to(
device=seq_lens.device, dtype=torch.int32
)
extend_seq_lens = verify_lens_dev
seq_lens = seq_lens + verify_lens_dev.to(seq_lens.dtype)
# Total verify tokens to expand. For the graph path the padded layout
# sets total_verify_tokens == graph_num_tokens (tier); the eager path
# resolves a device-assembled layout whose total_verify_tokens is None,
# so fall back to sum(verify_lens) (== real total; padded == tier).
num_tokens = ragged_layout.total_verify_tokens
if num_tokens is None:
num_tokens = int(verify_lens_dev.sum().item())
else:
num_tokens = int(num_tokens)
extend_seq_lens_cpu = None
seq_lens_cpu = None
else:
seq_lens = seq_lens + self.target_verify_num_draft_tokens
seq_lens_cpu = [
x + self.target_verify_num_draft_tokens for x in seq_lens_cpu
]
extend_seq_lens_cpu = [self.target_verify_num_draft_tokens] * batch_size
num_tokens = self.target_verify_num_draft_tokens * batch_size
extend_seq_lens = self._move_to_device(extend_seq_lens_cpu)
if out_cache_loc is None:
out_cache_loc = seq_lens.new_zeros(num_tokens)
return self.init_forward_metadata_prefill(
@@ -631,6 +705,8 @@ class DeepseekV4HipRadixBackend(
extend_seq_lens_cpu=extend_seq_lens_cpu,
need_compress=True,
use_prefill_cuda_graph=use_prefill_cuda_graph,
compress_gpu_plan=ragged_layout is not None,
extend_start_loc=extend_start_loc,
)
def make_forward_metadata_from_raw_verify(
@@ -640,7 +716,7 @@ class DeepseekV4HipRadixBackend(
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
bs, num_draft_tokens = len(seq_lens), self.target_verify_num_draft_tokens
seq_lens = seq_lens + num_draft_tokens
extend_seq_lens = raw_metadata.extend_seq_lens
if extend_seq_lens is None or extend_seq_lens.numel() != bs:
@@ -846,6 +922,8 @@ class DeepseekV4HipRadixBackend(
chosen_max_seq_len = self.MAX_SEQ_LEN_FOR_CAPTURE
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=}"
@@ -862,14 +940,14 @@ class DeepseekV4HipRadixBackend(
out_cache_loc=out_cache_loc_padded,
)
elif bucket == _GraphBucket.TARGET_VERIFY:
if resolve_ragged_verify_layout(forward_batch) is not None:
raise NotImplementedError(
"DSV4 ragged verify is not supported on the HIP backend "
"(DeepseekV4HipRadixBackend) cuda-graph path; disable "
"SGLANG_RAGGED_VERIFY_MODE or use a CUDA device."
)
assert out_cache_loc is not None
num_tokens_v = self.speculative_num_draft_tokens * bs
ragged_layout = resolve_ragged_verify_layout(forward_batch)
if ragged_layout is not None:
ragged_layout = ragged_layout.padded_to_bucket(padded_bs=bs)
num_tokens_v = ragged_layout.graph_num_tokens
graph_key = num_tokens_v
else:
num_tokens_v = self.target_verify_num_draft_tokens * bs
out_cache_loc_padded = torch.nn.functional.pad(
out_cache_loc,
pad=(0, num_tokens_v - len(out_cache_loc)),
@@ -885,6 +963,7 @@ class DeepseekV4HipRadixBackend(
# CPU mirror already available here (== seq_lens, no D2H);
# pass it so target_verify skips the per-iter seq_lens.tolist() sync.
seq_lens_cpu=seq_lens_cpu.tolist(),
ragged_layout=ragged_layout,
)
elif bucket == _GraphBucket.DRAFT_EXTEND:
num_tokens_per_req = self.draft_extend_num_tokens_per_req
@@ -910,7 +989,7 @@ class DeepseekV4HipRadixBackend(
raise NotImplementedError
self.replay_cuda_graph_metadata_from(
bs=bs, temp_metadata=temp_metadata, bucket=bucket
bs=graph_key, temp_metadata=temp_metadata, bucket=bucket
)
if in_capture:
@@ -955,12 +1034,7 @@ class DeepseekV4HipRadixBackend(
out_cache_loc=out_cache_loc,
)
elif forward_batch.forward_mode.is_target_verify():
if resolve_ragged_verify_layout(forward_batch) is not None:
raise NotImplementedError(
"DSV4 ragged verify is not supported on the HIP backend "
"(DeepseekV4HipRadixBackend); disable SGLANG_RAGGED_VERIFY_MODE "
"or use a CUDA device."
)
ragged_layout = resolve_ragged_verify_layout(forward_batch)
metadata = self.init_forward_metadata_target_verify(
max_seq_len=max_seq_len,
req_pool_indices=req_pool_indices,
@@ -970,6 +1044,7 @@ class DeepseekV4HipRadixBackend(
seq_lens_cpu=(
seq_lens_cpu.tolist() if seq_lens_cpu is not None else None
),
ragged_layout=ragged_layout,
)
elif forward_batch.forward_mode.is_prefill(include_draft_extend_v2=True):
extend_seq_lens_cpu = forward_batch.extend_seq_lens_cpu
@@ -1190,6 +1190,34 @@ class DeepSeekV4TokenToKVPool(BaseSWAKVPool):
page_size=self.swa_kv_pool.page_size,
)
def set_unified_key_buffer_radix_fused_norm_rope(
self,
layer_id: int,
swa_loc: torch.Tensor,
kv: torch.Tensor,
kv_weight: torch.Tensor,
eps: float,
freqs_cis: torch.Tensor,
positions: torch.Tensor,
) -> None:
"""unified_kv counterpart of set_swa_key_buffer_radix_fused_norm_rope.
Under unified_kv the (fp8, paged) swa_kv_pool is None -- SWA K lives in
the shared bf16 unified_kv ring instead. Norm+RoPE the draft KV in place
(the same freqs_cis path the main model uses via _compute_kv_bf16) and
scatter it into ``unified_kv[swa_loc]``. Rows with swa_loc < 0
(uncommitted verify tokens) are skipped by the scatter.
"""
from sglang.kernels.ops.attention.dsv4 import fused_norm_rope_inplace
from sglang.kernels.ops.attention.dsv4.unified_kv_kernels import runtime
fused_norm_rope_inplace(kv, kv_weight, eps, freqs_cis, positions)
runtime.scatter_bf16_into_unified(
kv=kv,
loc=swa_loc,
unified_kv=self.get_unified_kv(layer_id),
)
def set_extra_key_buffer_fused(
self,
layer_id: int,
+27 -1
View File
@@ -9,6 +9,9 @@ import torch.nn.functional as F
from torch import nn
from sglang.kernels.ops.attention.dsv4 import fused_q_norm_rope, fused_rope_inplace
from sglang.kernels.ops.attention.dsv4.unified_kv_kernels.env_gate import (
is_unified_kv_triton,
)
from sglang.kernels.ops.speculative.dspark.dspark_draft_model import (
BuildStepLocal,
CommitKvProj,
@@ -135,6 +138,20 @@ class DSparkAttention(MqaAttentionBase):
attn_backend,
pool: DeepSeekV4TokenToKVPool,
) -> None:
if is_unified_kv_triton():
# unified_kv: SWA K lives in the shared bf16 ring (swa_kv_pool is
# None). Use the unified ring write target -- get_unified_swa_loc
# recomputes it from live positions for multi-step draft decode.
pool.set_unified_key_buffer_radix_fused_norm_rope(
layer_id=self.layer_id,
swa_loc=attn_backend.get_unified_swa_loc(forward_batch),
kv=kv,
kv_weight=self.kv_norm.weight.data,
eps=self.eps,
freqs_cis=self.freqs_cis,
positions=positions,
)
return
pool.set_swa_key_buffer_radix_fused_norm_rope(
layer_id=self.layer_id,
swa_loc=attn_backend.get_swa_out_cache_loc(forward_batch),
@@ -660,9 +677,18 @@ class DeepseekV4ForCausalLMDSpark(nn.Module):
main_x=main_x,
wkv_linears=[stage.self_attn.wkv for stage in self.stages],
)
# Under unified_kv the swa_kv_pool is None; the caller passes a unified
# ring loc (state_slot * ring + pos % ring, -1 for uncommitted) so the
# store just needs to target the bf16 ring instead of the fp8 flashmla
# buffer. Same swa_loc/positions contract either way.
store_kv = (
pool.set_unified_key_buffer_radix_fused_norm_rope
if is_unified_kv_triton()
else pool.set_swa_key_buffer_radix_fused_norm_rope
)
for stage, kv in zip(self.stages, kvs):
attn = stage.self_attn
pool.set_swa_key_buffer_radix_fused_norm_rope(
store_kv(
layer_id=attn.layer_id,
swa_loc=swa_loc,
kv=kv,
@@ -2,9 +2,13 @@ from typing import Optional
import torch
from sglang.kernels.ops.attention.dsv4.unified_kv_kernels.env_gate import (
is_unified_kv_triton,
)
from sglang.kernels.ops.speculative.cache_locs import assign_extend_cache_locs_func
from sglang.kernels.ops.speculative.dspark.dspark_verify_window import (
BuildCommitInjectLayout,
build_unified_commit_inject_layout,
)
from sglang.srt.managers.schedule_batch import ScheduleBatch
from sglang.srt.speculative.ragged_verify import RaggedVerifyLayout
@@ -36,6 +40,8 @@ class TargetHiddenKvInjector:
positions: torch.Tensor,
cache_loc_2d: Optional[torch.Tensor] = None,
commit_lens: Optional[torch.Tensor] = None,
state_slot: Optional[torch.Tensor] = None,
final_pos: Optional[torch.Tensor] = None,
) -> None:
if target_hidden is None or target_hidden.numel() == 0:
return
@@ -54,6 +60,14 @@ class TargetHiddenKvInjector:
commit_lens = commit_lens.to(
device=device, dtype=torch.int32, non_blocking=True
)
if state_slot is not None:
state_slot = state_slot.to(
device=device, dtype=torch.int64, non_blocking=True
)
if final_pos is not None:
final_pos = final_pos.to(
device=device, dtype=torch.int64, non_blocking=True
)
pool = self.draft_model_runner.token_to_kv_pool
if hasattr(pool, "set_swa_key_buffer_radix_fused_norm_rope"):
@@ -64,6 +78,8 @@ class TargetHiddenKvInjector:
positions=positions,
cache_loc_2d=cache_loc_2d,
commit_lens=commit_lens,
state_slot=state_slot,
final_pos=final_pos,
)
return
@@ -86,13 +102,29 @@ class TargetHiddenKvInjector:
positions: torch.Tensor,
cache_loc_2d: Optional[torch.Tensor],
commit_lens: Optional[torch.Tensor],
state_slot: Optional[torch.Tensor] = None,
final_pos: Optional[torch.Tensor] = None,
) -> None:
swa_loc = pool.translate_loc_from_full_to_swa(cache_loc).to(torch.int32)
if commit_lens is not None and cache_loc_2d is not None:
bs, verify_len = cache_loc_2d.shape
col = torch.arange(verify_len, device=cache_loc.device).view(1, -1)
committed_mask = (col < commit_lens.to(torch.long).view(-1, 1)).reshape(-1)
swa_loc = torch.where(committed_mask, swa_loc, torch.full_like(swa_loc, -1))
if is_unified_kv_triton():
swa_loc = self._unified_inject_loc(
pool=pool,
positions=positions,
cache_loc_2d=cache_loc_2d,
commit_lens=commit_lens,
state_slot=state_slot,
final_pos=final_pos,
)
else:
swa_loc = pool.translate_loc_from_full_to_swa(cache_loc).to(torch.int32)
if commit_lens is not None and cache_loc_2d is not None:
bs, verify_len = cache_loc_2d.shape
col = torch.arange(verify_len, device=cache_loc.device).view(1, -1)
committed_mask = (col < commit_lens.to(torch.long).view(-1, 1)).reshape(
-1
)
swa_loc = torch.where(
committed_mask, swa_loc, torch.full_like(swa_loc, -1)
)
with torch.inference_mode():
self.draft_model.write_target_hidden_kv(
@@ -102,6 +134,43 @@ class TargetHiddenKvInjector:
pool=pool,
)
def _unified_inject_loc(
self,
*,
pool,
positions: torch.Tensor,
cache_loc_2d: Optional[torch.Tensor],
commit_lens: Optional[torch.Tensor],
state_slot: Optional[torch.Tensor],
final_pos: Optional[torch.Tensor],
) -> torch.Tensor:
"""Ring row for target-hidden injection under unified_kv.
loc = state_slot * ring + pos % ring, with two skip (-1) rules:
* SWA window: only the last ``win`` tokens per req land in the ring;
older tokens share a ring slot (pos % ring) and would race, so drop
them (needed for long prefill chunks).
* commit gate: uncommitted verify tokens (col >= commit_len) are dropped.
"""
if state_slot is None:
raise RuntimeError(
"unified_kv target-hidden injection requires state_slot "
"(per-token draft req_pool_indices)."
)
ring = pool.unified_swa_ring_size
win = pool.unified_swa_window
pos = positions.to(torch.int64)
loc = state_slot.to(torch.int64) * ring + pos % ring
if final_pos is not None:
keep = pos > (final_pos.to(torch.int64) - win)
loc = torch.where(keep, loc, torch.full_like(loc, -1))
if commit_lens is not None and cache_loc_2d is not None:
bs, verify_len = cache_loc_2d.shape
col = torch.arange(verify_len, device=positions.device).view(1, -1)
committed = (col < commit_lens.to(torch.long).view(-1, 1)).reshape(-1)
loc = torch.where(committed, loc, torch.full_like(loc, -1))
return loc.to(torch.int32)
def inject_ragged(
self,
*,
@@ -119,15 +188,25 @@ class TargetHiddenKvInjector:
if hasattr(pool, "set_swa_key_buffer_radix_fused_norm_rope"):
if hidden_strided.numel() == 0:
return
inject_layout = BuildCommitInjectLayout.execute(
req_pool_indices=batch.req_pool_indices,
req_to_token=self.model_runner.req_to_token_pool.req_to_token,
prefix_lens=prefix_lens,
block_pos_offsets=self._block_pos_offsets[:stride],
full_to_swa_mapping=pool.full_to_swa_index_mapping,
commit_lens=commit_lens,
stride=stride,
)
if is_unified_kv_triton():
inject_layout = build_unified_commit_inject_layout(
req_pool_indices=batch.req_pool_indices,
prefix_lens=prefix_lens,
block_pos_offsets=self._block_pos_offsets[:stride],
commit_lens=commit_lens,
stride=stride,
ring_stride=pool.unified_swa_ring_size,
)
else:
inject_layout = BuildCommitInjectLayout.execute(
req_pool_indices=batch.req_pool_indices,
req_to_token=self.model_runner.req_to_token_pool.req_to_token,
prefix_lens=prefix_lens,
block_pos_offsets=self._block_pos_offsets[:stride],
full_to_swa_mapping=pool.full_to_swa_index_mapping,
commit_lens=commit_lens,
stride=stride,
)
with torch.inference_mode():
self.draft_model.write_target_hidden_kv(
main_hidden=hidden.reshape(-1, hidden.shape[-1]),
@@ -5,6 +5,9 @@ from typing import Optional
import msgspec
import torch
from sglang.kernels.ops.attention.dsv4.unified_kv_kernels.env_gate import (
is_unified_kv_triton,
)
from sglang.kernels.ops.speculative.dspark.dspark_accept import (
AcceptGreedy,
AcceptSampling,
@@ -20,6 +23,7 @@ from sglang.kernels.ops.speculative.dspark.dspark_verify_window import (
BuildRaggedVerifyWindow,
RaggedVerifyWindow,
ScatterCompactToStrided,
build_unified_commit_inject_layout,
scatter_compact_to_strided_into,
)
from sglang.srt.layers.logits_processor import LogitsProcessorOutput
@@ -317,12 +321,23 @@ class TargetVerifyExecutor:
if hidden is None:
raise RuntimeError("DSpark verify requires target hidden states, got None.")
hidden = hidden.view(bs, self.verify_num_draft_tokens, -1)
state_slot = None
if is_unified_kv_triton():
# unified_kv needs the per-token draft req slot to address the SWA ring
# (state_slot * ring + pos % ring). Verify tokens are the latest in each
# req so they always fall in the window; the commit gate (via commit_lens
# + cache_loc_2d) drops rejected tokens, so no final_pos skip is needed.
vlen = verify_window.verify_cache_loc_2d.shape[1]
state_slot = (
batch.req_pool_indices[:bs].view(-1, 1).expand(bs, vlen).reshape(-1)
)
self.kv_injector.inject_target_hidden(
target_hidden=hidden.reshape(-1, hidden.shape[-1]),
cache_loc=verify_window.verify_cache_loc,
cache_loc_2d=verify_window.verify_cache_loc_2d,
positions=verify_window.positions_2d.reshape(-1),
commit_lens=commit_lens,
state_slot=state_slot,
)
def _run_ragged(
@@ -645,15 +660,25 @@ class DsparkVerifyEpilogue:
torch.minimum(commit_lens, verify_lens.to(torch.int32))
* self.inject_gate_buf
)
inject_layout = BuildCommitInjectLayout.execute(
req_pool_indices=req_pool_indices,
req_to_token=ctx.resolve_req_to_token(),
prefix_lens=seq_lens[:bs],
block_pos_offsets=ctx.block_pos_offsets[: self.stride],
full_to_swa_mapping=pool.full_to_swa_index_mapping,
commit_lens=gated_commit_lens,
stride=self.stride,
)
if is_unified_kv_triton():
inject_layout = build_unified_commit_inject_layout(
req_pool_indices=req_pool_indices,
prefix_lens=seq_lens[:bs],
block_pos_offsets=ctx.block_pos_offsets[: self.stride],
commit_lens=gated_commit_lens,
stride=self.stride,
ring_stride=pool.unified_swa_ring_size,
)
else:
inject_layout = BuildCommitInjectLayout.execute(
req_pool_indices=req_pool_indices,
req_to_token=ctx.resolve_req_to_token(),
prefix_lens=seq_lens[:bs],
block_pos_offsets=ctx.block_pos_offsets[: self.stride],
full_to_swa_mapping=pool.full_to_swa_index_mapping,
commit_lens=gated_commit_lens,
stride=self.stride,
)
with torch.inference_mode():
ctx.draft_model.write_target_hidden_kv(
main_hidden=self.strided_hidden[: bs * self.stride],
@@ -5,6 +5,9 @@ from typing import Optional
import torch
from sglang.kernels.ops.attention.dsv4.unified_kv_kernels.env_gate import (
is_unified_kv_triton,
)
from sglang.srt.configs.hybrid_arch import mambaish_config
from sglang.srt.distributed.parallel_state_wrapper import ParallelState
from sglang.srt.environ import envs
@@ -449,10 +452,25 @@ class DSparkWorkerV2(BaseSpecWorker):
ctx_lens,
int(sum(batch.extend_lens)),
)
# unified_kv injects into the SWA ring keyed by (draft req slot, position);
# thread the per-token state_slot + the req's final position so the
# injector keeps only the last SWA window (older prefill tokens share a
# ring slot and would race). Cheap; only consumed under unified_kv.
state_slot = final_pos = None
if is_unified_kv_triton():
repeats = ctx_lens.to(torch.int64)
state_slot = torch.repeat_interleave(
batch.req_pool_indices.to(device=device, dtype=torch.int64), repeats
)
final_pos = torch.repeat_interleave(
(draft_seq_lens + ctx_lens - 1).to(torch.int64), repeats
)
self._kv_injector.inject_target_hidden(
target_hidden=logits_output.hidden_states,
cache_loc=batch.out_cache_loc,
positions=positions,
state_slot=state_slot,
final_pos=final_pos,
)
# Avoid copying large hidden-state buffers to CPU in overlap scheduling.
logits_output.hidden_states = None