Facade DSA index-cache: MTP topk-reuse state + index-K storage (#28609)
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING, Iterator, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import torch
|
||||
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||
|
||||
|
||||
class IndexTopKShareState:
|
||||
def __init__(
|
||||
self,
|
||||
forward_batch: ForwardBatch,
|
||||
topk_indices: Optional[torch.Tensor],
|
||||
):
|
||||
self._forward_batch = forward_batch
|
||||
self._topk_indices = topk_indices
|
||||
|
||||
@classmethod
|
||||
def from_mtp_carry(cls, forward_batch: ForwardBatch) -> IndexTopKShareState:
|
||||
topk_indices = (
|
||||
forward_batch.spec_info.dsa_topk_indices
|
||||
if forward_batch.reuse_dsa_topk_indices
|
||||
else None
|
||||
)
|
||||
return cls(forward_batch, topk_indices)
|
||||
|
||||
@property
|
||||
def enabled(self) -> bool:
|
||||
return bool(self._forward_batch.reuse_dsa_topk_indices)
|
||||
|
||||
@property
|
||||
def _seed_buf(self) -> Optional[torch.Tensor]:
|
||||
if self._forward_batch.forward_mode.is_extend(include_draft_extend_v2=True):
|
||||
return self._forward_batch.spec_info.dsa_seed_topk_capture
|
||||
return None
|
||||
|
||||
@property
|
||||
def should_publish(self) -> bool:
|
||||
return self.enabled or self._seed_buf is not None
|
||||
|
||||
@property
|
||||
def topk_indices(self) -> Optional[torch.Tensor]:
|
||||
return self._topk_indices
|
||||
|
||||
def update(self, topk_indices: Optional[torch.Tensor]) -> None:
|
||||
self._topk_indices = topk_indices
|
||||
|
||||
def publish(self) -> None:
|
||||
if self._topk_indices is None or not self.should_publish:
|
||||
return
|
||||
if self.enabled:
|
||||
self._forward_batch.spec_info.dsa_topk_indices = self._topk_indices
|
||||
seed_buf = self._seed_buf
|
||||
if seed_buf is not None:
|
||||
sel = self._forward_batch.spec_info.dsa_seed_topk_select
|
||||
src = (
|
||||
self._topk_indices[: seed_buf.shape[0]]
|
||||
if sel is None
|
||||
else self._topk_indices[sel]
|
||||
)
|
||||
seed_buf[: src.shape[0]].copy_(src)
|
||||
|
||||
@classmethod
|
||||
@contextmanager
|
||||
def mtp_iteration(
|
||||
cls,
|
||||
forward_batch: ForwardBatch,
|
||||
enabled: bool = True,
|
||||
keep_carry_seed: bool = False,
|
||||
) -> Iterator[Optional[IndexTopKShareState]]:
|
||||
if not enabled:
|
||||
yield None
|
||||
return
|
||||
spec_info = forward_batch.spec_info
|
||||
forward_batch.reuse_dsa_topk_indices = True
|
||||
# Keep the draft-extend seed so step 0 reuses it; else recompute it.
|
||||
if not (keep_carry_seed and spec_info.dsa_topk_indices is not None):
|
||||
spec_info.dsa_topk_indices = None
|
||||
try:
|
||||
yield cls.from_mtp_carry(forward_batch)
|
||||
finally:
|
||||
spec_info.dsa_topk_indices = None
|
||||
forward_batch.reuse_dsa_topk_indices = False
|
||||
@@ -37,6 +37,7 @@ import torch
|
||||
|
||||
from sglang.kernels.ops.attention.dsa import index_buf_accessor
|
||||
from sglang.srt.layers.cp.utils import get_layer_owner, get_layer_shard_range
|
||||
from sglang.srt.mem_cache.index_key_cache import IndexKeyCache
|
||||
from sglang.srt.mem_cache.memory_pool import (
|
||||
GPU_MEMORY_TYPE_KV_CACHE,
|
||||
DSATokenToKVPool,
|
||||
@@ -53,6 +54,141 @@ if TYPE_CHECKING:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LayerSplitIndexKeyCache(IndexKeyCache):
|
||||
def __init__(self, pool: LayerSplitDSATokenToKVPool, index_buf_size: int):
|
||||
super().__init__(pool, index_buf_size)
|
||||
num_pages = (index_buf_size + pool.page_size + 1) // pool.page_size
|
||||
with (
|
||||
torch.cuda.use_mem_pool(pool.custom_mem_pool)
|
||||
if pool.custom_mem_pool
|
||||
else nullcontext()
|
||||
):
|
||||
self.remote_buffer = torch.empty(
|
||||
self._buffer_shape(num_pages),
|
||||
dtype=pool.index_k_with_scale_buffer_dtype,
|
||||
device=pool.device,
|
||||
)
|
||||
self.remote_layer_id: Optional[int] = None
|
||||
|
||||
def _layer_num_pages(self, layer_idx: int, num_pages: int) -> int:
|
||||
layer_id = self.pool.start_layer + layer_idx
|
||||
return num_pages if self.pool._is_layer_owned(layer_id) else 0
|
||||
|
||||
def clear(self) -> None:
|
||||
super().clear()
|
||||
del self.remote_buffer
|
||||
|
||||
def move(self, tgt_loc: torch.Tensor, src_loc: torch.Tensor) -> None:
|
||||
if tgt_loc.numel() == 0:
|
||||
return
|
||||
tgt_loc_flat = tgt_loc.view(-1).long()
|
||||
src_loc_flat = src_loc.view(-1).long()
|
||||
for index_k in self.buffer:
|
||||
if index_k.shape[0] != 0:
|
||||
index_k[tgt_loc_flat] = index_k[src_loc_flat]
|
||||
|
||||
def get_buffer(self, layer_id: int) -> torch.Tensor:
|
||||
if self.pool.layer_transfer_counter is not None:
|
||||
self.pool.layer_transfer_counter.wait_until(
|
||||
layer_id - self.pool.start_layer
|
||||
)
|
||||
return self.get_broadcastable_buffer(layer_id)
|
||||
|
||||
def get_k_and_scale(
|
||||
self,
|
||||
layer_id: int,
|
||||
seq_len_tensor: torch.Tensor,
|
||||
page_indices: torch.Tensor,
|
||||
seq_len_sum: int,
|
||||
max_seq_len: int,
|
||||
):
|
||||
buf = self.get_buffer(layer_id)
|
||||
self.pool.prefetch_kv_buffer(layer_id)
|
||||
return index_buf_accessor.GetKAndS.execute(
|
||||
self.pool,
|
||||
buf,
|
||||
page_indices=page_indices,
|
||||
seq_len_tensor=seq_len_tensor,
|
||||
seq_len_sum=seq_len_sum,
|
||||
max_seq_len=max_seq_len,
|
||||
)
|
||||
|
||||
def store_quantized(
|
||||
self,
|
||||
layer_id: int,
|
||||
loc: torch.Tensor,
|
||||
index_k: torch.Tensor,
|
||||
index_k_scale: torch.Tensor,
|
||||
) -> None:
|
||||
self.invalidate(layer_id)
|
||||
if self.pool._is_layer_owned(layer_id):
|
||||
super().store_quantized(layer_id, loc, index_k, index_k_scale)
|
||||
|
||||
def invalidate(self, layer_id: int) -> None:
|
||||
if self.remote_layer_id == layer_id:
|
||||
self.remote_layer_id = None
|
||||
|
||||
def get_broadcastable_buffer(self, layer_id: int) -> torch.Tensor:
|
||||
if self.remote_layer_id != layer_id:
|
||||
local_idx = layer_id - self.pool.start_layer
|
||||
src_tensor = (
|
||||
self.buffer[local_idx] if self.pool._is_layer_owned(layer_id) else None
|
||||
)
|
||||
self.pool._broadcast_tensor_from_owner(
|
||||
self.remote_buffer,
|
||||
layer_id,
|
||||
src_tensor=src_tensor,
|
||||
)
|
||||
self.remote_layer_id = layer_id
|
||||
return self.remote_buffer
|
||||
|
||||
def state_buf_infos(self):
|
||||
owned_layer_ids = [
|
||||
i
|
||||
for i in range(self.pool.layer_num)
|
||||
if self.pool._is_layer_owned(self.pool.start_layer + i)
|
||||
]
|
||||
data_ptrs = [self.buffer[i].data_ptr() for i in owned_layer_ids]
|
||||
data_lens = [self.buffer[i].nbytes for i in owned_layer_ids]
|
||||
item_lens = [self.buffer[i][0].nbytes for i in owned_layer_ids]
|
||||
return data_ptrs, data_lens, item_lens
|
||||
|
||||
def cpu_copy(self, indices):
|
||||
page_indices = indices[:: self.pool.page_size] // self.pool.page_size
|
||||
torch.cuda.synchronize()
|
||||
index_k_cpu = []
|
||||
chunk_size = self.pool.cpu_offloading_chunk_size
|
||||
page_chunk_size = max(1, chunk_size // self.pool.page_size)
|
||||
for layer_id in range(self.pool.layer_num):
|
||||
index_k_cpu.append([])
|
||||
if self.buffer[layer_id].shape[0] == 0:
|
||||
continue
|
||||
for i in range(0, len(page_indices), page_chunk_size):
|
||||
chunk_page_indices = page_indices[i : i + page_chunk_size]
|
||||
idx_cpu = self.buffer[layer_id][chunk_page_indices].to(
|
||||
"cpu", non_blocking=True
|
||||
)
|
||||
index_k_cpu[-1].append(idx_cpu)
|
||||
torch.cuda.synchronize()
|
||||
return index_k_cpu
|
||||
|
||||
def load_cpu_copy(self, index_k_cpu, indices) -> None:
|
||||
page_indices = indices[:: self.pool.page_size] // self.pool.page_size
|
||||
torch.cuda.synchronize()
|
||||
chunk_size = self.pool.cpu_offloading_chunk_size
|
||||
page_chunk_size = max(1, chunk_size // self.pool.page_size)
|
||||
for layer_id in range(self.pool.layer_num):
|
||||
if self.buffer[layer_id].shape[0] == 0:
|
||||
continue
|
||||
for i in range(0, len(page_indices), page_chunk_size):
|
||||
chunk_page_indices = page_indices[i : i + page_chunk_size]
|
||||
idx_cpu = index_k_cpu[layer_id][i // page_chunk_size]
|
||||
assert idx_cpu.shape[0] == len(chunk_page_indices)
|
||||
idx_chunk = idx_cpu.to(self.buffer[layer_id].device, non_blocking=True)
|
||||
self.buffer[layer_id][chunk_page_indices] = idx_chunk
|
||||
torch.cuda.synchronize()
|
||||
|
||||
|
||||
class LayerSplitDSATokenToKVPool(DSATokenToKVPool):
|
||||
"""DSA KV pool that shards layers across CP ranks with owner-broadcast reads."""
|
||||
|
||||
@@ -203,35 +339,13 @@ class LayerSplitDSATokenToKVPool(DSATokenToKVPool):
|
||||
self.pending_remote_kv_broadcast = False
|
||||
self._init_layer_broadcast_comm()
|
||||
|
||||
def _create_index_buffers(self):
|
||||
num_pages = (self.index_buf_size + self.page_size + 1) // self.page_size
|
||||
with (
|
||||
torch.cuda.use_mem_pool(self.custom_mem_pool)
|
||||
if self.custom_mem_pool
|
||||
else nullcontext()
|
||||
):
|
||||
self.index_k_with_scale_buffer = [
|
||||
torch.zeros(
|
||||
self._index_buffer_shape(
|
||||
num_pages if self._is_layer_owned(self.start_layer + i) else 0
|
||||
),
|
||||
dtype=self.index_k_with_scale_buffer_dtype,
|
||||
device=self.device,
|
||||
)
|
||||
for i in range(self.layer_num)
|
||||
]
|
||||
self.remote_index_k_with_scale_buffer = torch.empty(
|
||||
self._index_buffer_shape(num_pages),
|
||||
dtype=self.index_k_with_scale_buffer_dtype,
|
||||
device=self.device,
|
||||
)
|
||||
self.remote_index_layer_id: Optional[int] = None
|
||||
def _create_index_key_cache(self) -> IndexKeyCache:
|
||||
return LayerSplitIndexKeyCache(self, self.index_buf_size)
|
||||
|
||||
def _clear_buffers(self):
|
||||
del self.kv_buffer
|
||||
del self.remote_kv_buffer
|
||||
del self.remote_index_k_with_scale_buffer
|
||||
del self.index_k_with_scale_buffer
|
||||
self.index_key_cache.clear()
|
||||
|
||||
# ---- MLA latent KV: owned-only writes, owner-broadcast reads ----------
|
||||
|
||||
@@ -418,96 +532,20 @@ class LayerSplitDSATokenToKVPool(DSATokenToKVPool):
|
||||
if kv_cache.shape[0] == 0:
|
||||
continue
|
||||
kv_cache[tgt_loc_flat] = kv_cache[src_loc_flat]
|
||||
for index_k in self.index_k_with_scale_buffer:
|
||||
if index_k.shape[0] == 0:
|
||||
continue
|
||||
index_k[tgt_loc_flat] = index_k[src_loc_flat]
|
||||
self.index_key_cache.move(tgt_loc, src_loc)
|
||||
|
||||
# ---- DSA indexer buffer: owned-only writes, owner-broadcast reads -----
|
||||
|
||||
def get_broadcastable_index_k_with_scale_buffer(
|
||||
self, layer_id: int
|
||||
) -> torch.Tensor:
|
||||
if self.layer_transfer_counter is not None:
|
||||
self.layer_transfer_counter.wait_until(layer_id - self.start_layer)
|
||||
return self._get_broadcastable_index_buffer(layer_id)
|
||||
|
||||
def get_index_k_continuous(self, layer_id, seq_len, page_indices):
|
||||
if self.layer_transfer_counter is not None:
|
||||
self.layer_transfer_counter.wait_until(layer_id - self.start_layer)
|
||||
buf = self._get_broadcastable_index_buffer(layer_id)
|
||||
return index_buf_accessor.GetK.execute(
|
||||
self, buf, seq_len=seq_len, page_indices=page_indices
|
||||
)
|
||||
|
||||
def get_index_k_scale_continuous(self, layer_id, seq_len, page_indices):
|
||||
if self.layer_transfer_counter is not None:
|
||||
self.layer_transfer_counter.wait_until(layer_id - self.start_layer)
|
||||
buf = self._get_broadcastable_index_buffer(layer_id)
|
||||
return index_buf_accessor.GetS.execute(
|
||||
self, buf, seq_len=seq_len, page_indices=page_indices
|
||||
)
|
||||
|
||||
def get_index_k_scale_buffer(
|
||||
self, layer_id, seq_len_tensor, page_indices, seq_len_sum, max_seq_len
|
||||
):
|
||||
if self.layer_transfer_counter is not None:
|
||||
self.layer_transfer_counter.wait_until(layer_id - self.start_layer)
|
||||
buf = self._get_broadcastable_index_buffer(layer_id)
|
||||
# Overlap the latent-KV owner-broadcast with the indexer read.
|
||||
self.prefetch_kv_buffer(layer_id)
|
||||
return index_buf_accessor.GetKAndS.execute(
|
||||
self,
|
||||
buf,
|
||||
page_indices=page_indices,
|
||||
seq_len_tensor=seq_len_tensor,
|
||||
seq_len_sum=seq_len_sum,
|
||||
max_seq_len=max_seq_len,
|
||||
)
|
||||
|
||||
def set_index_k_scale_buffer(self, layer_id, loc, index_k, index_k_scale) -> None:
|
||||
self.invalidate_index_buffer_for_layer(layer_id)
|
||||
if not self._is_layer_owned(layer_id):
|
||||
return
|
||||
buf = self.index_k_with_scale_buffer[layer_id - self.start_layer]
|
||||
index_buf_accessor.SetKAndS.execute(
|
||||
pool=self, buf=buf, loc=loc, index_k=index_k, index_k_scale=index_k_scale
|
||||
)
|
||||
return self.index_key_cache.get_buffer(layer_id)
|
||||
|
||||
def invalidate_index_buffer_for_layer(self, layer_id: int) -> None:
|
||||
if self.remote_index_layer_id == layer_id:
|
||||
self.remote_index_layer_id = None
|
||||
self.index_key_cache.invalidate(layer_id)
|
||||
|
||||
def _get_broadcastable_index_buffer(self, layer_id: int) -> torch.Tensor:
|
||||
if self.remote_index_layer_id != layer_id:
|
||||
local_idx = self._local_layer_idx(layer_id)
|
||||
src_tensor = (
|
||||
self.index_k_with_scale_buffer[local_idx]
|
||||
if self._is_layer_owned(layer_id)
|
||||
else None
|
||||
)
|
||||
self._broadcast_tensor_from_owner(
|
||||
self.remote_index_k_with_scale_buffer,
|
||||
layer_id,
|
||||
src_tensor=src_tensor,
|
||||
)
|
||||
self.remote_index_layer_id = layer_id
|
||||
return self.remote_index_k_with_scale_buffer
|
||||
|
||||
def get_state_buf_infos(self):
|
||||
owned_layer_ids = [
|
||||
i
|
||||
for i in range(self.layer_num)
|
||||
if self._is_layer_owned(self.start_layer + i)
|
||||
]
|
||||
data_ptrs = [
|
||||
self.index_k_with_scale_buffer[i].data_ptr() for i in owned_layer_ids
|
||||
]
|
||||
data_lens = [self.index_k_with_scale_buffer[i].nbytes for i in owned_layer_ids]
|
||||
item_lens = [
|
||||
self.index_k_with_scale_buffer[i][0].nbytes for i in owned_layer_ids
|
||||
]
|
||||
return data_ptrs, data_lens, item_lens
|
||||
return self.index_key_cache.get_broadcastable_buffer(layer_id)
|
||||
|
||||
# ---- HiCache CPU offload: skip empty (non-owned) layers ---------------
|
||||
|
||||
@@ -529,22 +567,7 @@ class LayerSplitDSATokenToKVPool(DSATokenToKVPool):
|
||||
kv_cache_cpu[-1].append(kv_cpu)
|
||||
current_platform.synchronize()
|
||||
|
||||
page_indices = indices[:: self.page_size] // self.page_size
|
||||
torch.cuda.synchronize()
|
||||
index_k_cpu = []
|
||||
page_chunk_size = max(1, chunk_size // self.page_size)
|
||||
for layer_id in range(self.layer_num):
|
||||
index_k_cpu.append([])
|
||||
if self.index_k_with_scale_buffer[layer_id].shape[0] == 0:
|
||||
continue
|
||||
for i in range(0, len(page_indices), page_chunk_size):
|
||||
chunk_page_indices = page_indices[i : i + page_chunk_size]
|
||||
idx_cpu = self.index_k_with_scale_buffer[layer_id][
|
||||
chunk_page_indices
|
||||
].to("cpu", non_blocking=True)
|
||||
index_k_cpu[-1].append(idx_cpu)
|
||||
torch.cuda.synchronize()
|
||||
return {"kv": kv_cache_cpu, "index_k": index_k_cpu}
|
||||
return {"kv": kv_cache_cpu, "index_k": self.index_key_cache.cpu_copy(indices)}
|
||||
|
||||
def load_cpu_copy(self, kv_cache_cpu_dict, indices, mamba_indices=None):
|
||||
from sglang.srt.utils import current_platform
|
||||
@@ -563,19 +586,4 @@ class LayerSplitDSATokenToKVPool(DSATokenToKVPool):
|
||||
self.kv_buffer[layer_id][chunk_indices] = kv_chunk
|
||||
current_platform.synchronize()
|
||||
|
||||
page_indices = indices[:: self.page_size] // self.page_size
|
||||
index_k_cpu = kv_cache_cpu_dict["index_k"]
|
||||
torch.cuda.synchronize()
|
||||
page_chunk_size = max(1, chunk_size // self.page_size)
|
||||
for layer_id in range(self.layer_num):
|
||||
if self.index_k_with_scale_buffer[layer_id].shape[0] == 0:
|
||||
continue
|
||||
for i in range(0, len(page_indices), page_chunk_size):
|
||||
chunk_page_indices = page_indices[i : i + page_chunk_size]
|
||||
idx_cpu = index_k_cpu[layer_id][i // page_chunk_size]
|
||||
assert idx_cpu.shape[0] == len(chunk_page_indices)
|
||||
idx_chunk = idx_cpu.to(
|
||||
self.index_k_with_scale_buffer[layer_id].device, non_blocking=True
|
||||
)
|
||||
self.index_k_with_scale_buffer[layer_id][chunk_page_indices] = idx_chunk
|
||||
torch.cuda.synchronize()
|
||||
self.index_key_cache.load_cpu_copy(kv_cache_cpu_dict["index_k"], indices)
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import nullcontext
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.kernels.ops.attention.dsa import index_buf_accessor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.mem_cache.memory_pool import DSATokenToKVPool
|
||||
|
||||
|
||||
class IndexKeyCache:
|
||||
def __init__(self, pool: DSATokenToKVPool, index_buf_size: int):
|
||||
self.pool = pool
|
||||
num_pages = (index_buf_size + pool.page_size + 1) // pool.page_size
|
||||
with (
|
||||
torch.cuda.use_mem_pool(pool.custom_mem_pool)
|
||||
if pool.custom_mem_pool
|
||||
else nullcontext()
|
||||
):
|
||||
self.buffer = [
|
||||
torch.zeros(
|
||||
self._buffer_shape(self._layer_num_pages(i, num_pages)),
|
||||
dtype=pool.index_k_with_scale_buffer_dtype,
|
||||
device=pool.device,
|
||||
)
|
||||
for i in range(pool.layer_num)
|
||||
]
|
||||
|
||||
def _buffer_shape(self, num_pages: int) -> tuple[int, int]:
|
||||
pool = self.pool
|
||||
return (
|
||||
num_pages,
|
||||
pool.page_size
|
||||
* (pool.index_head_dim + pool.index_head_dim // pool.quant_block_size * 4),
|
||||
)
|
||||
|
||||
def _layer_num_pages(self, layer_idx: int, num_pages: int) -> int:
|
||||
return num_pages
|
||||
|
||||
def clear(self) -> None:
|
||||
del self.buffer
|
||||
|
||||
def move(self, tgt_loc: torch.Tensor, src_loc: torch.Tensor) -> None:
|
||||
if tgt_loc.numel() == 0:
|
||||
return
|
||||
tgt_loc_flat = tgt_loc.view(-1).long()
|
||||
src_loc_flat = src_loc.view(-1).long()
|
||||
for index_k in self.buffer:
|
||||
index_k[tgt_loc_flat] = index_k[src_loc_flat]
|
||||
|
||||
def get_local_buffer(self, layer_id: int) -> torch.Tensor:
|
||||
if self.pool.layer_transfer_counter is not None:
|
||||
self.pool.layer_transfer_counter.wait_until(
|
||||
layer_id - self.pool.start_layer
|
||||
)
|
||||
return self.buffer[layer_id - self.pool.start_layer]
|
||||
|
||||
def get_buffer(self, layer_id: int) -> torch.Tensor:
|
||||
return self.get_local_buffer(layer_id)
|
||||
|
||||
def get_k_continuous(self, layer_id: int, seq_len: int, page_indices: torch.Tensor):
|
||||
buf = self.get_buffer(layer_id)
|
||||
return index_buf_accessor.GetK.execute(
|
||||
self.pool, buf, seq_len=seq_len, page_indices=page_indices
|
||||
)
|
||||
|
||||
def get_k_scale_continuous(
|
||||
self, layer_id: int, seq_len: int, page_indices: torch.Tensor
|
||||
):
|
||||
buf = self.get_buffer(layer_id)
|
||||
return index_buf_accessor.GetS.execute(
|
||||
self.pool, buf, seq_len=seq_len, page_indices=page_indices
|
||||
)
|
||||
|
||||
def get_k_and_scale(
|
||||
self,
|
||||
layer_id: int,
|
||||
seq_len_tensor: torch.Tensor,
|
||||
page_indices: torch.Tensor,
|
||||
seq_len_sum: int,
|
||||
max_seq_len: int,
|
||||
):
|
||||
buf = self.get_buffer(layer_id)
|
||||
return index_buf_accessor.GetKAndS.execute(
|
||||
self.pool,
|
||||
buf,
|
||||
page_indices=page_indices,
|
||||
seq_len_tensor=seq_len_tensor,
|
||||
seq_len_sum=seq_len_sum,
|
||||
max_seq_len=max_seq_len,
|
||||
)
|
||||
|
||||
def store_quantized(
|
||||
self,
|
||||
layer_id: int,
|
||||
loc: torch.Tensor,
|
||||
index_k: torch.Tensor,
|
||||
index_k_scale: torch.Tensor,
|
||||
) -> None:
|
||||
buf = self.buffer[layer_id - self.pool.start_layer]
|
||||
index_buf_accessor.SetKAndS.execute(
|
||||
pool=self.pool,
|
||||
buf=buf,
|
||||
loc=loc,
|
||||
index_k=index_k,
|
||||
index_k_scale=index_k_scale,
|
||||
)
|
||||
|
||||
def cpu_copy(self, indices):
|
||||
# Retracted pages may be reused before resume, so offload index-K with KV.
|
||||
page_indices = indices[:: self.pool.page_size] // self.pool.page_size
|
||||
torch.cuda.synchronize()
|
||||
index_k_cpu = []
|
||||
chunk_size = self.pool.cpu_offloading_chunk_size
|
||||
page_chunk_size = max(1, chunk_size // self.pool.page_size)
|
||||
for layer_id in range(self.pool.layer_num):
|
||||
index_k_cpu.append([])
|
||||
for i in range(0, len(page_indices), page_chunk_size):
|
||||
chunk_page_indices = page_indices[i : i + page_chunk_size]
|
||||
idx_cpu = self.buffer[layer_id][chunk_page_indices].to(
|
||||
"cpu", non_blocking=True
|
||||
)
|
||||
index_k_cpu[-1].append(idx_cpu)
|
||||
torch.cuda.synchronize()
|
||||
return index_k_cpu
|
||||
|
||||
def load_cpu_copy(self, index_k_cpu, indices) -> None:
|
||||
page_indices = indices[:: self.pool.page_size] // self.pool.page_size
|
||||
torch.cuda.synchronize()
|
||||
chunk_size = self.pool.cpu_offloading_chunk_size
|
||||
page_chunk_size = max(1, chunk_size // self.pool.page_size)
|
||||
for layer_id in range(self.pool.layer_num):
|
||||
for i in range(0, len(page_indices), page_chunk_size):
|
||||
chunk_page_indices = page_indices[i : i + page_chunk_size]
|
||||
idx_cpu = index_k_cpu[layer_id][i // page_chunk_size]
|
||||
assert idx_cpu.shape[0] == len(chunk_page_indices)
|
||||
idx_chunk = idx_cpu.to(self.buffer[0].device, non_blocking=True)
|
||||
self.buffer[layer_id][chunk_page_indices] = idx_chunk
|
||||
torch.cuda.synchronize()
|
||||
|
||||
def state_buf_infos(self):
|
||||
layer_num = self.pool.layer_num
|
||||
data_ptrs = [self.buffer[i].data_ptr() for i in range(layer_num)]
|
||||
data_lens = [self.buffer[i].nbytes for i in range(layer_num)]
|
||||
item_lens = [self.buffer[i][0].nbytes for i in range(layer_num)]
|
||||
return data_ptrs, data_lens, item_lens
|
||||
@@ -38,7 +38,6 @@ import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
from sglang.kernels.ops.attention.dsa import index_buf_accessor
|
||||
from sglang.kernels.ops.attention.dsa.quant_k_cache import (
|
||||
quantize_k_cache,
|
||||
quantize_k_cache_separate,
|
||||
@@ -59,6 +58,7 @@ from sglang.srt.layers.quantization.fp4_kv_cache_quant_method import (
|
||||
)
|
||||
from sglang.srt.layers.radix_attention import RadixAttention
|
||||
from sglang.srt.mem_cache.allocator.mamba import MambaSlotAllocator
|
||||
from sglang.srt.mem_cache.index_key_cache import IndexKeyCache
|
||||
from sglang.srt.mem_cache.kv_vmm_backing import KvVmmBufferOwner
|
||||
from sglang.srt.mem_cache.layout.page_major import (
|
||||
build_page_major_mamba_views,
|
||||
@@ -4378,58 +4378,28 @@ class DSATokenToKVPool(MLATokenToKVPool):
|
||||
), f"HIP legacy DSA path requires page_size == 1, got {self.page_size}"
|
||||
else:
|
||||
assert self.page_size == 64
|
||||
self._create_index_buffers()
|
||||
self.index_key_cache = self._create_index_key_cache()
|
||||
self._finalize_allocation_log(size)
|
||||
|
||||
def _index_buffer_shape(self, num_pages: int) -> tuple[int, int]:
|
||||
return (
|
||||
num_pages,
|
||||
self.page_size
|
||||
* (self.index_head_dim + self.index_head_dim // self.quant_block_size * 4),
|
||||
)
|
||||
def _create_index_key_cache(self) -> IndexKeyCache:
|
||||
return IndexKeyCache(self, self.index_buf_size)
|
||||
|
||||
def _create_index_buffers(self):
|
||||
num_pages = (self.index_buf_size + self.page_size + 1) // self.page_size
|
||||
with (
|
||||
torch.cuda.use_mem_pool(self.custom_mem_pool)
|
||||
if self.custom_mem_pool
|
||||
else nullcontext()
|
||||
):
|
||||
self.index_k_with_scale_buffer = [
|
||||
torch.zeros(
|
||||
# Layout:
|
||||
# ref: test_attention.py :: kv_cache_cast_to_fp8
|
||||
# shape: (num_pages, page_size 64 * head_dim 128 + page_size 64 * fp32_nbytes 4)
|
||||
# data: for page i,
|
||||
# * buf[i, :page_size * head_dim] for fp8 data
|
||||
# * buf[i, page_size * head_dim:].view(float32) for scale
|
||||
self._index_buffer_shape(num_pages),
|
||||
dtype=self.index_k_with_scale_buffer_dtype,
|
||||
device=self.device,
|
||||
)
|
||||
for _ in range(self.layer_num)
|
||||
]
|
||||
@property
|
||||
def index_k_with_scale_buffer(self):
|
||||
# Preserve direct HiCache access while storage lives behind the facade.
|
||||
return self.index_key_cache.buffer
|
||||
|
||||
def _clear_buffers(self):
|
||||
super()._clear_buffers()
|
||||
del self.index_k_with_scale_buffer
|
||||
self.index_key_cache.clear()
|
||||
|
||||
def move_kv_cache(self, tgt_loc: torch.Tensor, src_loc: torch.Tensor):
|
||||
"""Move latent KV and the DSA indexer cache (key + scale) in lockstep."""
|
||||
super().move_kv_cache(tgt_loc, src_loc)
|
||||
|
||||
if tgt_loc.numel() == 0:
|
||||
return
|
||||
|
||||
tgt_loc_flat = tgt_loc.view(-1).long()
|
||||
src_loc_flat = src_loc.view(-1).long()
|
||||
for index_k in self.index_k_with_scale_buffer:
|
||||
index_k[tgt_loc_flat] = index_k[src_loc_flat]
|
||||
self.index_key_cache.move(tgt_loc, src_loc)
|
||||
|
||||
def get_index_k_with_scale_buffer(self, layer_id: int) -> torch.Tensor:
|
||||
if self.layer_transfer_counter is not None:
|
||||
self.layer_transfer_counter.wait_until(layer_id - self.start_layer)
|
||||
return self.index_k_with_scale_buffer[layer_id - self.start_layer]
|
||||
return self.index_key_cache.get_local_buffer(layer_id)
|
||||
|
||||
def get_index_k_continuous(
|
||||
self,
|
||||
@@ -4437,12 +4407,7 @@ class DSATokenToKVPool(MLATokenToKVPool):
|
||||
seq_len: int,
|
||||
page_indices: torch.Tensor,
|
||||
):
|
||||
if self.layer_transfer_counter is not None:
|
||||
self.layer_transfer_counter.wait_until(layer_id - self.start_layer)
|
||||
buf = self.index_k_with_scale_buffer[layer_id - self.start_layer]
|
||||
return index_buf_accessor.GetK.execute(
|
||||
self, buf, seq_len=seq_len, page_indices=page_indices
|
||||
)
|
||||
return self.index_key_cache.get_k_continuous(layer_id, seq_len, page_indices)
|
||||
|
||||
def get_index_k_scale_continuous(
|
||||
self,
|
||||
@@ -4450,11 +4415,8 @@ class DSATokenToKVPool(MLATokenToKVPool):
|
||||
seq_len: int,
|
||||
page_indices: torch.Tensor,
|
||||
):
|
||||
if self.layer_transfer_counter is not None:
|
||||
self.layer_transfer_counter.wait_until(layer_id - self.start_layer)
|
||||
buf = self.index_k_with_scale_buffer[layer_id - self.start_layer]
|
||||
return index_buf_accessor.GetS.execute(
|
||||
self, buf, seq_len=seq_len, page_indices=page_indices
|
||||
return self.index_key_cache.get_k_scale_continuous(
|
||||
layer_id, seq_len, page_indices
|
||||
)
|
||||
|
||||
def get_index_k_scale_buffer(
|
||||
@@ -4465,27 +4427,8 @@ class DSATokenToKVPool(MLATokenToKVPool):
|
||||
seq_len_sum: int,
|
||||
max_seq_len: int,
|
||||
):
|
||||
"""
|
||||
Fused method to get both index K and scale data in a single call using Triton.
|
||||
More efficient than calling get_index_k_continuous and get_index_k_scale_continuous separately.
|
||||
|
||||
:param layer_id: Layer index
|
||||
:param seq_len: Sequence length
|
||||
:param page_indices: Page indices tensor
|
||||
:return: tuple of (k_fp8, k_scale) where
|
||||
k_fp8: (seq_len, index_head_dim), uint8
|
||||
k_scale: (seq_len, 4), uint8
|
||||
"""
|
||||
if self.layer_transfer_counter is not None:
|
||||
self.layer_transfer_counter.wait_until(layer_id - self.start_layer)
|
||||
buf = self.index_k_with_scale_buffer[layer_id - self.start_layer]
|
||||
return index_buf_accessor.GetKAndS.execute(
|
||||
self,
|
||||
buf,
|
||||
page_indices=page_indices,
|
||||
seq_len_tensor=seq_len_tensor,
|
||||
seq_len_sum=seq_len_sum,
|
||||
max_seq_len=max_seq_len,
|
||||
return self.index_key_cache.get_k_and_scale(
|
||||
layer_id, seq_len_tensor, page_indices, seq_len_sum, max_seq_len
|
||||
)
|
||||
|
||||
def set_index_k_scale_buffer(
|
||||
@@ -4495,68 +4438,20 @@ class DSATokenToKVPool(MLATokenToKVPool):
|
||||
index_k: torch.Tensor,
|
||||
index_k_scale: torch.Tensor,
|
||||
) -> None:
|
||||
buf = self.index_k_with_scale_buffer[layer_id - self.start_layer]
|
||||
index_buf_accessor.SetKAndS.execute(
|
||||
pool=self, buf=buf, loc=loc, index_k=index_k, index_k_scale=index_k_scale
|
||||
)
|
||||
self.index_key_cache.store_quantized(layer_id, loc, index_k, index_k_scale)
|
||||
|
||||
def get_cpu_copy(self, indices, mamba_indices=None):
|
||||
# DSA keeps a page-indexed index_k_with_scale_buffer alongside kv_buffer.
|
||||
# Retract frees the slots/pages and they get reused by other reqs'
|
||||
# set_index_k_scale_buffer, so we must offload it here too -- otherwise
|
||||
# resume restores kv_buffer but leaves foreign index/scale in place and
|
||||
# DSA attention reads garbage at those token positions.
|
||||
kv_cache_cpu = super().get_cpu_copy(indices, mamba_indices=mamba_indices)
|
||||
|
||||
page_indices = indices[:: self.page_size] // self.page_size
|
||||
torch.cuda.synchronize()
|
||||
index_k_cpu = []
|
||||
chunk_size = self.cpu_offloading_chunk_size
|
||||
page_chunk_size = max(1, chunk_size // self.page_size)
|
||||
for layer_id in range(self.layer_num):
|
||||
index_k_cpu.append([])
|
||||
for i in range(0, len(page_indices), page_chunk_size):
|
||||
chunk_page_indices = page_indices[i : i + page_chunk_size]
|
||||
idx_cpu = self.index_k_with_scale_buffer[layer_id][
|
||||
chunk_page_indices
|
||||
].to("cpu", non_blocking=True)
|
||||
index_k_cpu[-1].append(idx_cpu)
|
||||
torch.cuda.synchronize()
|
||||
|
||||
return {"kv": kv_cache_cpu, "index_k": index_k_cpu}
|
||||
return {"kv": kv_cache_cpu, "index_k": self.index_key_cache.cpu_copy(indices)}
|
||||
|
||||
def load_cpu_copy(self, kv_cache_cpu_dict, indices, mamba_indices=None):
|
||||
super().load_cpu_copy(
|
||||
kv_cache_cpu_dict["kv"], indices, mamba_indices=mamba_indices
|
||||
)
|
||||
|
||||
page_indices = indices[:: self.page_size] // self.page_size
|
||||
index_k_cpu = kv_cache_cpu_dict["index_k"]
|
||||
torch.cuda.synchronize()
|
||||
chunk_size = self.cpu_offloading_chunk_size
|
||||
page_chunk_size = max(1, chunk_size // self.page_size)
|
||||
for layer_id in range(self.layer_num):
|
||||
for i in range(0, len(page_indices), page_chunk_size):
|
||||
chunk_page_indices = page_indices[i : i + page_chunk_size]
|
||||
idx_cpu = index_k_cpu[layer_id][i // page_chunk_size]
|
||||
assert idx_cpu.shape[0] == len(chunk_page_indices)
|
||||
idx_chunk = idx_cpu.to(
|
||||
self.index_k_with_scale_buffer[0].device, non_blocking=True
|
||||
)
|
||||
self.index_k_with_scale_buffer[layer_id][chunk_page_indices] = idx_chunk
|
||||
torch.cuda.synchronize()
|
||||
self.index_key_cache.load_cpu_copy(kv_cache_cpu_dict["index_k"], indices)
|
||||
|
||||
def get_state_buf_infos(self):
|
||||
data_ptrs = [
|
||||
self.index_k_with_scale_buffer[i].data_ptr() for i in range(self.layer_num)
|
||||
]
|
||||
data_lens = [
|
||||
self.index_k_with_scale_buffer[i].nbytes for i in range(self.layer_num)
|
||||
]
|
||||
item_lens = [
|
||||
self.index_k_with_scale_buffer[i][0].nbytes for i in range(self.layer_num)
|
||||
]
|
||||
return data_ptrs, data_lens, item_lens
|
||||
return self.index_key_cache.state_buf_infos()
|
||||
|
||||
def get_kv_size_bytes(self):
|
||||
kv_size_bytes = super().get_kv_size_bytes()
|
||||
|
||||
@@ -35,6 +35,7 @@ from sglang.srt.layers.attention.dsa.utils import (
|
||||
is_dsa_enable_prefill_cp,
|
||||
is_dsa_prefill_cp_round_robin_split,
|
||||
)
|
||||
from sglang.srt.layers.attention.index_topk_share import IndexTopKShareState
|
||||
from sglang.srt.layers.cp.utils import cp_gather_after_forward, is_cp_v2_active
|
||||
from sglang.srt.layers.layernorm import RMSNorm
|
||||
from sglang.srt.layers.linear import ReplicatedLinear
|
||||
@@ -261,14 +262,7 @@ class DeepseekModelNextN(nn.Module):
|
||||
hidden_states = cp_split_and_rebuild_data(forward_batch, hidden_states)
|
||||
positions = cp_split_and_rebuild_position(forward_batch, positions)
|
||||
residual = None
|
||||
seed_buf = (
|
||||
forward_batch.spec_info.dsa_seed_topk_capture
|
||||
if forward_batch.forward_mode.is_extend(include_draft_extend_v2=True)
|
||||
else None
|
||||
)
|
||||
should_update_dsa_topk_indices = (
|
||||
forward_batch.reuse_dsa_topk_indices or seed_buf is not None
|
||||
)
|
||||
index_topk_share = IndexTopKShareState.from_mtp_carry(forward_batch)
|
||||
with get_global_expert_distribution_recorder().disable_this_region():
|
||||
hidden_states, residual, topk_indices = self.decoder(
|
||||
positions,
|
||||
@@ -276,11 +270,7 @@ class DeepseekModelNextN(nn.Module):
|
||||
forward_batch,
|
||||
residual,
|
||||
zero_allocator,
|
||||
prev_topk_indices=(
|
||||
forward_batch.spec_info.dsa_topk_indices
|
||||
if forward_batch.reuse_dsa_topk_indices
|
||||
else None
|
||||
),
|
||||
prev_topk_indices=index_topk_share.topk_indices,
|
||||
)
|
||||
if not forward_batch.forward_mode.is_idle():
|
||||
if residual is not None:
|
||||
@@ -296,7 +286,7 @@ class DeepseekModelNextN(nn.Module):
|
||||
forward_batch,
|
||||
torch.cuda.current_stream(),
|
||||
)
|
||||
if should_update_dsa_topk_indices and topk_indices is not None:
|
||||
if index_topk_share.should_publish and topk_indices is not None:
|
||||
topk_indices = _gather_dsa_topk_indices_for_cp(
|
||||
topk_indices,
|
||||
local_num_tokens,
|
||||
@@ -306,21 +296,12 @@ class DeepseekModelNextN(nn.Module):
|
||||
)
|
||||
elif (
|
||||
cp_v2_active
|
||||
and should_update_dsa_topk_indices
|
||||
and index_topk_share.should_publish
|
||||
and topk_indices is not None
|
||||
):
|
||||
topk_indices = cp_gather_after_forward(topk_indices, forward_batch)
|
||||
if should_update_dsa_topk_indices and topk_indices is not None:
|
||||
if forward_batch.reuse_dsa_topk_indices:
|
||||
forward_batch.spec_info.dsa_topk_indices = topk_indices
|
||||
if seed_buf is not None:
|
||||
sel = forward_batch.spec_info.dsa_seed_topk_select
|
||||
src = (
|
||||
topk_indices[: seed_buf.shape[0]]
|
||||
if sel is None
|
||||
else topk_indices[sel]
|
||||
)
|
||||
seed_buf[: src.shape[0]].copy_(src)
|
||||
index_topk_share.update(topk_indices)
|
||||
index_topk_share.publish()
|
||||
finally:
|
||||
exit_stack.close()
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ from sglang.srt.layers.attention.dsa.utils import (
|
||||
dsa_use_prefill_cp,
|
||||
is_dsa_enable_prefill_cp,
|
||||
)
|
||||
from sglang.srt.layers.attention.index_topk_share import IndexTopKShareState
|
||||
from sglang.srt.layers.aux_hidden_states import (
|
||||
AuxHiddenStateAccumulator,
|
||||
AuxHiddenStatePacker,
|
||||
@@ -2729,18 +2730,21 @@ class DeepseekV2Model(nn.Module):
|
||||
else:
|
||||
hidden_states = input_embeds
|
||||
residual = None
|
||||
initial_topk_indices = None
|
||||
else:
|
||||
assert pp_proxy_tensors is not None
|
||||
hidden_states = pp_proxy_tensors["hidden_states"]
|
||||
residual = pp_proxy_tensors["residual"]
|
||||
topk_indices = pp_proxy_tensors.tensors.get("topk_indices")
|
||||
initial_topk_indices = pp_proxy_tensors.tensors.get("topk_indices")
|
||||
index_topk_share = IndexTopKShareState(forward_batch, initial_topk_indices)
|
||||
if not self.pp_group.is_first_rank:
|
||||
assert not (
|
||||
not forward_batch.forward_mode.is_idle()
|
||||
and hidden_states.shape[0] != 0
|
||||
and self.use_dsa
|
||||
and dsa_forward_uses_topk
|
||||
and dsa_layer_skips_topk(self.config, self.start_layer)
|
||||
and topk_indices is None
|
||||
and index_topk_share.topk_indices is None
|
||||
), (
|
||||
f"PP stage starting at layer {self.start_layer} requires DSA "
|
||||
"topk_indices from the previous stage."
|
||||
@@ -2802,8 +2806,6 @@ class DeepseekV2Model(nn.Module):
|
||||
normal_end_layer = normal_start_layer = 0
|
||||
# Append-compatible, so the shared capture path below is unchanged.
|
||||
aux_hidden_states = AuxHiddenStatePacker(len(self.layers_to_capture))
|
||||
if self.pp_group.is_first_rank:
|
||||
topk_indices = None
|
||||
for i in range(normal_start_layer, normal_end_layer):
|
||||
# NOTE: torch dynamo does not support graph break in context manager
|
||||
ctx = (
|
||||
@@ -2821,7 +2823,7 @@ class DeepseekV2Model(nn.Module):
|
||||
zero_allocator,
|
||||
gemm_output_zero_allocator,
|
||||
llama_4_scaling,
|
||||
prev_topk_indices=topk_indices,
|
||||
prev_topk_indices=index_topk_share.topk_indices,
|
||||
captured_last_layer_outputs=(
|
||||
aux_hidden_states if i in self.layers_to_capture else None
|
||||
),
|
||||
@@ -2829,6 +2831,7 @@ class DeepseekV2Model(nn.Module):
|
||||
i
|
||||
),
|
||||
)
|
||||
index_topk_share.update(topk_indices)
|
||||
|
||||
if normal_end_layer != self.end_layer:
|
||||
hidden_states, residual = model_forward_maybe_tbo(
|
||||
@@ -2855,6 +2858,7 @@ class DeepseekV2Model(nn.Module):
|
||||
and self.end_layer < self.config.num_hidden_layers
|
||||
and dsa_layer_skips_topk(self.config, self.end_layer)
|
||||
):
|
||||
topk_indices = index_topk_share.topk_indices
|
||||
if (
|
||||
not forward_batch.forward_mode.is_idle()
|
||||
and hidden_states.shape[0] != 0
|
||||
|
||||
@@ -18,6 +18,7 @@ from sglang.srt.hardware_backend.npu.graph_runner.eagle_draft_npu_graph_runner i
|
||||
from sglang.srt.hardware_backend.npu.graph_runner.npu_graph_runner import NPUGraphRunner
|
||||
from sglang.srt.kv_canary.runner.canary_manager import context_tuple
|
||||
from sglang.srt.layers.attention.flashinfer_backend import FlashInferAttnBackend
|
||||
from sglang.srt.layers.attention.index_topk_share import IndexTopKShareState
|
||||
from sglang.srt.layers.attention.tokenspeed_mla_backend import TokenspeedMLABackend
|
||||
from sglang.srt.layers.attention.triton_backend import TritonAttnBackend
|
||||
from sglang.srt.layers.attention.trtllm_mha_backend import TRTLLMHAAttnBackend
|
||||
@@ -599,103 +600,96 @@ class EagleDraftWorker(EagleDraftWorkerBase):
|
||||
|
||||
# Forward multiple steps
|
||||
scores = None
|
||||
if self.index_share_for_mtp_iteration:
|
||||
forward_batch.reuse_dsa_topk_indices = True
|
||||
# Keep the draft-extend seed so step 0 reuses it; else recompute it.
|
||||
if not (
|
||||
self.seed_dsa_topk_from_draft_extend
|
||||
and spec_info.dsa_topk_indices is not None
|
||||
):
|
||||
spec_info.dsa_topk_indices = None
|
||||
for i in range(self.speculative_num_steps):
|
||||
if draft_tokens_topk1 is not None:
|
||||
input_ids = topk_index.flatten()
|
||||
else:
|
||||
input_ids, hidden_states, scores, tree_info = select_top_k_tokens(
|
||||
i, topk_p, topk_index, hidden_states, scores, self.topk
|
||||
)
|
||||
score_list.append(tree_info[0])
|
||||
token_list.append(tree_info[1])
|
||||
parents_list.append(tree_info[2])
|
||||
|
||||
# We don't need to run the last forward. we get 1 token from draft prefill and (#spec steps - 1) tokens here
|
||||
if i == self.speculative_num_steps - 1:
|
||||
break
|
||||
|
||||
# Set inputs
|
||||
forward_batch.input_ids = input_ids
|
||||
# Qwen3-MoE MTP uses a fused RoPE + KV-store path whose cache_loc
|
||||
# argument must be contiguous.
|
||||
if (
|
||||
self.draft_runner.model_config.hf_config.architectures[0]
|
||||
== "Qwen3MoeForCausalLMMTP"
|
||||
):
|
||||
out_cache_loc = out_cache_loc.contiguous()
|
||||
forward_batch.out_cache_loc = out_cache_loc[i]
|
||||
spec_info.hidden_states = hidden_states
|
||||
|
||||
# Run forward under a per-step ForwardContext so the model layer
|
||||
# reads attn_backends[i] for the i-th draft step, plus a canary
|
||||
# index context so canary tracks which draft step is active.
|
||||
canary_index_ctx = (
|
||||
c.with_active_single_forward_manager(i)
|
||||
if (c := self.draft_runner.canary_manager) is not None
|
||||
else contextlib.nullcontext()
|
||||
)
|
||||
with (
|
||||
forward_context(
|
||||
ForwardContext(
|
||||
attn_backend=self.draft_attn_backend.attn_backends[i]
|
||||
)
|
||||
),
|
||||
canary_index_ctx,
|
||||
):
|
||||
logits_output = self.draft_runner.forward(forward_batch).logits_output
|
||||
maybe_detect_nan(logits_output.next_token_logits, f"draft_forward step {i}")
|
||||
maybe_detect_inf(logits_output.next_token_logits, f"draft_forward step {i}")
|
||||
if get_spec().speculative_use_rejection_sampling:
|
||||
probs, topk_p, topk_index = sample_draft_proposal(
|
||||
logits_output.next_token_logits,
|
||||
forward_batch.sampling_info.temperatures,
|
||||
)
|
||||
draft_probs_list.append(probs)
|
||||
forward_batch.positions.add_(1)
|
||||
elif self.topk == 1 and not _is_hip:
|
||||
if _is_cuda:
|
||||
# The positions advance is fused into the kernel.
|
||||
topk_p, topk_index = draft_topk1_postprocess(
|
||||
logits_output.next_token_logits,
|
||||
forward_batch.positions,
|
||||
draft_tokens_topk1,
|
||||
i + 1,
|
||||
)
|
||||
with IndexTopKShareState.mtp_iteration(
|
||||
forward_batch,
|
||||
enabled=self.index_share_for_mtp_iteration,
|
||||
keep_carry_seed=self.seed_dsa_topk_from_draft_extend,
|
||||
):
|
||||
for i in range(self.speculative_num_steps):
|
||||
if draft_tokens_topk1 is not None:
|
||||
input_ids = topk_index.flatten()
|
||||
else:
|
||||
topk_index = torch.argmax(
|
||||
logits_output.next_token_logits, dim=-1, keepdim=True
|
||||
input_ids, hidden_states, scores, tree_info = select_top_k_tokens(
|
||||
i, topk_p, topk_index, hidden_states, scores, self.topk
|
||||
)
|
||||
topk_p = torch.ones_like(topk_index, dtype=torch.float32)
|
||||
forward_batch.positions.add_(1)
|
||||
else:
|
||||
probs = renorm_draft_probs(
|
||||
logits_output.next_token_logits,
|
||||
forward_batch.sampling_info,
|
||||
get_spec().speculative_use_rejection_sampling,
|
||||
)
|
||||
topk_p, topk_index = fast_topk(probs, self.topk, dim=-1)
|
||||
forward_batch.positions.add_(1)
|
||||
maybe_detect_oob(
|
||||
topk_index,
|
||||
0,
|
||||
logits_output.next_token_logits.shape[-1],
|
||||
f"draft_forward step {i}: topk_index OOB vs vocab_size={logits_output.next_token_logits.shape[-1]}",
|
||||
)
|
||||
if self.hot_token_id is not None:
|
||||
topk_index = self.hot_token_id[topk_index]
|
||||
hidden_states = logits_output.hidden_states
|
||||
score_list.append(tree_info[0])
|
||||
token_list.append(tree_info[1])
|
||||
parents_list.append(tree_info[2])
|
||||
|
||||
if self.index_share_for_mtp_iteration:
|
||||
spec_info.dsa_topk_indices = None
|
||||
forward_batch.reuse_dsa_topk_indices = False
|
||||
if i == self.speculative_num_steps - 1:
|
||||
break
|
||||
|
||||
forward_batch.input_ids = input_ids
|
||||
# Qwen3-MoE MTP uses a fused RoPE + KV-store path whose cache_loc
|
||||
# argument must be contiguous.
|
||||
if (
|
||||
self.draft_runner.model_config.hf_config.architectures[0]
|
||||
== "Qwen3MoeForCausalLMMTP"
|
||||
):
|
||||
out_cache_loc = out_cache_loc.contiguous()
|
||||
forward_batch.out_cache_loc = out_cache_loc[i]
|
||||
spec_info.hidden_states = hidden_states
|
||||
|
||||
canary_index_ctx = (
|
||||
c.with_active_single_forward_manager(i)
|
||||
if (c := self.draft_runner.canary_manager) is not None
|
||||
else contextlib.nullcontext()
|
||||
)
|
||||
with (
|
||||
forward_context(
|
||||
ForwardContext(
|
||||
attn_backend=self.draft_attn_backend.attn_backends[i]
|
||||
)
|
||||
),
|
||||
canary_index_ctx,
|
||||
):
|
||||
logits_output = self.draft_runner.forward(
|
||||
forward_batch
|
||||
).logits_output
|
||||
maybe_detect_nan(
|
||||
logits_output.next_token_logits, f"draft_forward step {i}"
|
||||
)
|
||||
maybe_detect_inf(
|
||||
logits_output.next_token_logits, f"draft_forward step {i}"
|
||||
)
|
||||
if get_spec().speculative_use_rejection_sampling:
|
||||
probs, topk_p, topk_index = sample_draft_proposal(
|
||||
logits_output.next_token_logits,
|
||||
forward_batch.sampling_info.temperatures,
|
||||
)
|
||||
draft_probs_list.append(probs)
|
||||
forward_batch.positions.add_(1)
|
||||
elif self.topk == 1 and not _is_hip:
|
||||
if _is_cuda:
|
||||
topk_p, topk_index = draft_topk1_postprocess(
|
||||
logits_output.next_token_logits,
|
||||
forward_batch.positions,
|
||||
draft_tokens_topk1,
|
||||
i + 1,
|
||||
)
|
||||
else:
|
||||
topk_index = torch.argmax(
|
||||
logits_output.next_token_logits, dim=-1, keepdim=True
|
||||
)
|
||||
topk_p = torch.ones_like(topk_index, dtype=torch.float32)
|
||||
forward_batch.positions.add_(1)
|
||||
else:
|
||||
probs = renorm_draft_probs(
|
||||
logits_output.next_token_logits,
|
||||
forward_batch.sampling_info,
|
||||
get_spec().speculative_use_rejection_sampling,
|
||||
)
|
||||
topk_p, topk_index = fast_topk(probs, self.topk, dim=-1)
|
||||
forward_batch.positions.add_(1)
|
||||
maybe_detect_oob(
|
||||
topk_index,
|
||||
0,
|
||||
logits_output.next_token_logits.shape[-1],
|
||||
f"draft_forward step {i}: topk_index OOB vs vocab_size={logits_output.next_token_logits.shape[-1]}",
|
||||
)
|
||||
if self.hot_token_id is not None:
|
||||
topk_index = self.hot_token_id[topk_index]
|
||||
hidden_states = logits_output.hidden_states
|
||||
|
||||
draft_probs = (
|
||||
torch.stack(draft_probs_list, dim=1)
|
||||
|
||||
Reference in New Issue
Block a user