[Mamba] Fix extra_buffer overlap schedule races (#24954)
This commit is contained in:
@@ -140,6 +140,7 @@ class MambaAttnBackendBase(AttentionBackend):
|
|||||||
self.pad_slot_id = PAD_SLOT_ID
|
self.pad_slot_id = PAD_SLOT_ID
|
||||||
self.device = model_runner.device
|
self.device = model_runner.device
|
||||||
self.topk = model_runner.server_args.speculative_eagle_topk or 0
|
self.topk = model_runner.server_args.speculative_eagle_topk or 0
|
||||||
|
self.is_draft_worker = model_runner.is_draft_worker
|
||||||
self.req_to_token_pool: HybridReqToTokenPool = model_runner.req_to_token_pool
|
self.req_to_token_pool: HybridReqToTokenPool = model_runner.req_to_token_pool
|
||||||
self.forward_metadata: ForwardMetadata = None
|
self.forward_metadata: ForwardMetadata = None
|
||||||
self.state_indices_list = []
|
self.state_indices_list = []
|
||||||
@@ -151,6 +152,28 @@ class MambaAttnBackendBase(AttentionBackend):
|
|||||||
self.cached_cuda_graph_verify_query_start_loc: torch.Tensor = None
|
self.cached_cuda_graph_verify_query_start_loc: torch.Tensor = None
|
||||||
self.conv_states_shape: tuple[int, int] = None
|
self.conv_states_shape: tuple[int, int] = None
|
||||||
|
|
||||||
|
def _execute_deferred_mamba_cow_and_clear(self, forward_batch: ForwardBatch):
|
||||||
|
"""Run deferred clear/COW ops on the forward stream to avoid races."""
|
||||||
|
if not forward_batch.forward_mode.is_extend() or self.is_draft_worker:
|
||||||
|
return
|
||||||
|
if (
|
||||||
|
forward_batch.mamba_clear_indices is not None
|
||||||
|
and len(forward_batch.mamba_clear_indices) > 0
|
||||||
|
):
|
||||||
|
self.req_to_token_pool.mamba_pool.clear_slots(
|
||||||
|
forward_batch.mamba_clear_indices
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
forward_batch.mamba_cow_src_indices is not None
|
||||||
|
and len(forward_batch.mamba_cow_src_indices) > 0
|
||||||
|
):
|
||||||
|
self.req_to_token_pool.mamba_pool.copy_from(
|
||||||
|
forward_batch.mamba_cow_src_indices, forward_batch.mamba_cow_dst_indices
|
||||||
|
)
|
||||||
|
forward_batch.mamba_clear_indices = None
|
||||||
|
forward_batch.mamba_cow_src_indices = None
|
||||||
|
forward_batch.mamba_cow_dst_indices = None
|
||||||
|
|
||||||
def _forward_metadata(self, forward_batch: ForwardBatch):
|
def _forward_metadata(self, forward_batch: ForwardBatch):
|
||||||
bs = forward_batch.batch_size
|
bs = forward_batch.batch_size
|
||||||
|
|
||||||
@@ -240,6 +263,7 @@ class MambaAttnBackendBase(AttentionBackend):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def init_forward_metadata(self, forward_batch: ForwardBatch):
|
def init_forward_metadata(self, forward_batch: ForwardBatch):
|
||||||
|
self._execute_deferred_mamba_cow_and_clear(forward_batch)
|
||||||
self.forward_metadata = self._forward_metadata(forward_batch)
|
self.forward_metadata = self._forward_metadata(forward_batch)
|
||||||
|
|
||||||
def _init_track_conv_indices(
|
def _init_track_conv_indices(
|
||||||
@@ -645,6 +669,7 @@ class Mamba2AttnBackend(MambaAttnBackendBase):
|
|||||||
self.mamba_chunk_size = config.mamba_chunk_size
|
self.mamba_chunk_size = config.mamba_chunk_size
|
||||||
|
|
||||||
def init_forward_metadata(self, forward_batch: ForwardBatch):
|
def init_forward_metadata(self, forward_batch: ForwardBatch):
|
||||||
|
self._execute_deferred_mamba_cow_and_clear(forward_batch)
|
||||||
metadata = self._forward_metadata(forward_batch)
|
metadata = self._forward_metadata(forward_batch)
|
||||||
self.forward_metadata = Mamba2Metadata.prepare_mixed(
|
self.forward_metadata = Mamba2Metadata.prepare_mixed(
|
||||||
metadata,
|
metadata,
|
||||||
|
|||||||
@@ -726,6 +726,10 @@ class Req(ReqDllmMixin):
|
|||||||
# the branching point seqlen to track mamba state. If set, given by prefix match,
|
# the branching point seqlen to track mamba state. If set, given by prefix match,
|
||||||
# it will be the tracked seqlen in the ping pong buffer for the right prefill pass.
|
# it will be the tracked seqlen in the ping pong buffer for the right prefill pass.
|
||||||
self.mamba_branching_seqlen: Optional[int] = None
|
self.mamba_branching_seqlen: Optional[int] = None
|
||||||
|
# Deferred COW: source mamba pool index from radix cache node (copy on forward stream)
|
||||||
|
self.mamba_cow_src_index: Optional[torch.Tensor] = None
|
||||||
|
# Deferred clear: newly allocated mamba slot needs zeroing on forward stream
|
||||||
|
self.mamba_needs_clear: bool = False
|
||||||
|
|
||||||
# Check finish
|
# Check finish
|
||||||
self.tokenizer = None
|
self.tokenizer = None
|
||||||
@@ -1282,6 +1286,8 @@ class Req(ReqDllmMixin):
|
|||||||
self.mamba_next_track_idx = None
|
self.mamba_next_track_idx = None
|
||||||
self.mamba_last_track_seqlen = None
|
self.mamba_last_track_seqlen = None
|
||||||
self.mamba_branching_seqlen = None
|
self.mamba_branching_seqlen = None
|
||||||
|
self.mamba_cow_src_index = None
|
||||||
|
self.mamba_needs_clear = False
|
||||||
self.already_computed = 0
|
self.already_computed = 0
|
||||||
self.kv_allocated_len = 0
|
self.kv_allocated_len = 0
|
||||||
self.kv_committed_len = 0
|
self.kv_committed_len = 0
|
||||||
@@ -1398,6 +1404,26 @@ class _MambaRadixCacheV2TrackEntry(NamedTuple):
|
|||||||
track_seqlen: int
|
track_seqlen: int
|
||||||
|
|
||||||
|
|
||||||
|
def set_mamba_track_indices_from_reqs(batch):
|
||||||
|
"""Build mamba_track_indices from req objects (authoritative source)."""
|
||||||
|
req_to_token_pool = batch.req_to_token_pool
|
||||||
|
all_buffers = req_to_token_pool.req_index_to_mamba_ping_pong_track_buffer_mapping[
|
||||||
|
batch.req_pool_indices
|
||||||
|
] # (bs, ping_pong_size), int64, on device
|
||||||
|
idx = (
|
||||||
|
torch.tensor(
|
||||||
|
[req.mamba_next_track_idx for req in batch.reqs],
|
||||||
|
dtype=torch.int64,
|
||||||
|
pin_memory=True,
|
||||||
|
)
|
||||||
|
.unsqueeze(1)
|
||||||
|
.to(device=all_buffers.device, non_blocking=True)
|
||||||
|
)
|
||||||
|
batch.mamba_track_indices = (
|
||||||
|
torch.gather(all_buffers, 1, idx).squeeze(1).to(torch.int64)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
||||||
"""Store all information of a batch on the scheduler."""
|
"""Store all information of a batch on the scheduler."""
|
||||||
@@ -1443,6 +1469,10 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
|||||||
mamba_track_indices: torch.Tensor = None # shape: [b], int64
|
mamba_track_indices: torch.Tensor = None # shape: [b], int64
|
||||||
mamba_track_mask: torch.Tensor = None # shape: [b], bool
|
mamba_track_mask: torch.Tensor = None # shape: [b], bool
|
||||||
mamba_track_seqlens: torch.Tensor = None # shape: [b], int64
|
mamba_track_seqlens: torch.Tensor = None # shape: [b], int64
|
||||||
|
# Deferred mamba init ops: COW pairs and clear indices (performed on forward stream)
|
||||||
|
mamba_cow_src_indices: torch.Tensor = None
|
||||||
|
mamba_cow_dst_indices: torch.Tensor = None
|
||||||
|
mamba_clear_indices: torch.Tensor = None
|
||||||
|
|
||||||
# For multimodal inputs
|
# For multimodal inputs
|
||||||
multimodal_inputs: Optional[List] = None
|
multimodal_inputs: Optional[List] = None
|
||||||
@@ -2003,6 +2033,10 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
|||||||
device=self.device,
|
device=self.device,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Collect mamba init info for deferred ops on forward stream
|
||||||
|
if any(req.mamba_pool_idx is not None for req in reqs):
|
||||||
|
self._collect_deferred_mamba_cow_and_clear(reqs)
|
||||||
|
|
||||||
if self.model_config.is_encoder_decoder:
|
if self.model_config.is_encoder_decoder:
|
||||||
self.prepare_encoder_info_extend(input_ids, seq_lens)
|
self.prepare_encoder_info_extend(input_ids, seq_lens)
|
||||||
|
|
||||||
@@ -2084,12 +2118,40 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
|||||||
mamba_track_seqlen = _force_track_h(req.mamba_branching_seqlen)
|
mamba_track_seqlen = _force_track_h(req.mamba_branching_seqlen)
|
||||||
mamba_track_seqlen_aligned = req.mamba_branching_seqlen
|
mamba_track_seqlen_aligned = req.mamba_branching_seqlen
|
||||||
req.mamba_last_track_seqlen = mamba_track_seqlen_aligned
|
req.mamba_last_track_seqlen = mamba_track_seqlen_aligned
|
||||||
|
|
||||||
return _MambaRadixCacheV2TrackEntry(
|
return _MambaRadixCacheV2TrackEntry(
|
||||||
track_mask=mask,
|
track_mask=mask,
|
||||||
track_index=track_index,
|
track_index=track_index,
|
||||||
track_seqlen=mamba_track_seqlen,
|
track_seqlen=mamba_track_seqlen,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _collect_deferred_mamba_cow_and_clear(self, reqs):
|
||||||
|
"""Collect deferred COW/clear info from requests."""
|
||||||
|
cow_src_tensors = []
|
||||||
|
cow_dst_tensors = []
|
||||||
|
clear_tensors = []
|
||||||
|
for req in reqs:
|
||||||
|
if req.mamba_cow_src_index is not None:
|
||||||
|
cow_src_tensors.append(req.mamba_cow_src_index)
|
||||||
|
cow_dst_tensors.append(req.mamba_pool_idx.unsqueeze(0))
|
||||||
|
req.mamba_cow_src_index = None
|
||||||
|
req.mamba_needs_clear = False
|
||||||
|
elif req.mamba_needs_clear:
|
||||||
|
clear_tensors.append(req.mamba_pool_idx.unsqueeze(0))
|
||||||
|
req.mamba_needs_clear = False
|
||||||
|
self.mamba_cow_src_indices = (
|
||||||
|
torch.cat(cow_src_tensors) if cow_src_tensors else None
|
||||||
|
)
|
||||||
|
self.mamba_cow_dst_indices = (
|
||||||
|
torch.cat(cow_dst_tensors) if cow_dst_tensors else None
|
||||||
|
)
|
||||||
|
self.mamba_clear_indices = torch.cat(clear_tensors) if clear_tensors else None
|
||||||
|
|
||||||
|
def prepare_for_split_prefill(self):
|
||||||
|
self.prepare_for_extend()
|
||||||
|
# For split prefill, we need to set the forward mode to SPLIT_PREFILL
|
||||||
|
self.forward_mode = ForwardMode.SPLIT_PREFILL
|
||||||
|
|
||||||
def mix_with_running(self, running_batch: "ScheduleBatch"):
|
def mix_with_running(self, running_batch: "ScheduleBatch"):
|
||||||
self.forward_mode = ForwardMode.MIXED
|
self.forward_mode = ForwardMode.MIXED
|
||||||
running_bs = running_batch.batch_size()
|
running_bs = running_batch.batch_size()
|
||||||
@@ -2379,22 +2441,7 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
|||||||
(0,), dtype=torch.int64, device=self.device
|
(0,), dtype=torch.int64, device=self.device
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# already on device
|
set_mamba_track_indices_from_reqs(self)
|
||||||
all_buffers = torch.stack(
|
|
||||||
[req.mamba_ping_pong_track_buffer for req in self.reqs]
|
|
||||||
)
|
|
||||||
idx = (
|
|
||||||
torch.tensor(
|
|
||||||
[req.mamba_next_track_idx for req in self.reqs],
|
|
||||||
dtype=torch.int64,
|
|
||||||
pin_memory=True,
|
|
||||||
)
|
|
||||||
.unsqueeze(1)
|
|
||||||
.to(device=all_buffers.device, non_blocking=True)
|
|
||||||
)
|
|
||||||
self.mamba_track_indices = (
|
|
||||||
torch.gather(all_buffers, 1, idx).squeeze(1).to(torch.int64)
|
|
||||||
)
|
|
||||||
|
|
||||||
# async H2D
|
# async H2D
|
||||||
self.mamba_track_mask = (
|
self.mamba_track_mask = (
|
||||||
@@ -2467,6 +2514,9 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
|||||||
self.mamba_track_indices = None
|
self.mamba_track_indices = None
|
||||||
self.mamba_track_mask = None
|
self.mamba_track_mask = None
|
||||||
self.mamba_track_seqlens = None
|
self.mamba_track_seqlens = None
|
||||||
|
self.mamba_cow_src_indices = None
|
||||||
|
self.mamba_cow_dst_indices = None
|
||||||
|
self.mamba_clear_indices = None
|
||||||
self.return_logprob = any(req.return_logprob for req in self.reqs)
|
self.return_logprob = any(req.return_logprob for req in self.reqs)
|
||||||
if self.return_logprob:
|
if self.return_logprob:
|
||||||
self.top_logprobs_nums = [self.top_logprobs_nums[i] for i in keep_indices]
|
self.top_logprobs_nums = [self.top_logprobs_nums[i] for i in keep_indices]
|
||||||
|
|||||||
@@ -1049,13 +1049,9 @@ class HiMambaRadixCache(MambaRadixCache):
|
|||||||
lock_node=mamba_node,
|
lock_node=mamba_node,
|
||||||
error_message="Can not alloc mamba cache",
|
error_message="Can not alloc mamba cache",
|
||||||
)
|
)
|
||||||
src_index = mamba_node.mamba_value
|
|
||||||
self.req_to_token_pool.mamba_pool.copy_from(src_index, dst_index)
|
|
||||||
req.mamba_pool_idx = dst_index[0]
|
req.mamba_pool_idx = dst_index[0]
|
||||||
else:
|
req.mamba_cow_src_index = mamba_node.mamba_value
|
||||||
src_index = mamba_node.mamba_value
|
req.mamba_needs_clear = False
|
||||||
dst_index = req.mamba_pool_idx.unsqueeze(0)
|
|
||||||
self.req_to_token_pool.mamba_pool.copy_from(src_index, dst_index)
|
|
||||||
|
|
||||||
value = value[:best_value_len]
|
value = value[:best_value_len]
|
||||||
if value:
|
if value:
|
||||||
|
|||||||
@@ -639,45 +639,44 @@ class MambaRadixCache(KVCacheEventMixin, BasePrefixCache):
|
|||||||
|
|
||||||
page_aligned_token_ids = token_ids[:page_aligned_len]
|
page_aligned_token_ids = token_ids[:page_aligned_len]
|
||||||
|
|
||||||
|
# Donate the mamba index to the radix cache instead of copying.
|
||||||
|
# This avoids a data copy that would race with the forward stream.
|
||||||
if self.enable_mamba_extra_buffer:
|
if self.enable_mamba_extra_buffer:
|
||||||
# copy from the ping pong track buffer
|
|
||||||
mamba_ping_pong_track_buffer_to_keep = (
|
mamba_ping_pong_track_buffer_to_keep = (
|
||||||
self.req_to_token_pool.get_mamba_ping_pong_other_idx(
|
self.req_to_token_pool.get_mamba_ping_pong_other_idx(
|
||||||
req.mamba_next_track_idx
|
req.mamba_next_track_idx
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mamba_value = (
|
mamba_value_donated = (
|
||||||
req.mamba_ping_pong_track_buffer[mamba_ping_pong_track_buffer_to_keep]
|
req.mamba_ping_pong_track_buffer[mamba_ping_pong_track_buffer_to_keep]
|
||||||
.unsqueeze(-1)
|
.unsqueeze(-1)
|
||||||
.clone()
|
.clone()
|
||||||
)
|
)
|
||||||
else:
|
new_slot = self._alloc_mamba_slot()
|
||||||
mamba_value = self.req_to_token_pool.get_mamba_indices(
|
req.mamba_ping_pong_track_buffer[mamba_ping_pong_track_buffer_to_keep] = (
|
||||||
req.req_pool_idx
|
new_slot[0]
|
||||||
).unsqueeze(-1)
|
|
||||||
# radix tree mamba value is forked from req space
|
|
||||||
mamba_value_forked = self.req_to_token_pool.mamba_pool.fork_from(mamba_value)
|
|
||||||
|
|
||||||
# if alloc mamba cache failed, do evict and alloc again
|
|
||||||
if mamba_value_forked is None:
|
|
||||||
self.evict(EvictParams(num_tokens=0, mamba_num=1))
|
|
||||||
mamba_value_forked = self.req_to_token_pool.mamba_pool.fork_from(
|
|
||||||
mamba_value
|
|
||||||
)
|
)
|
||||||
assert mamba_value_forked is not None, "Can not alloc mamba cache"
|
self.req_to_token_pool.req_index_to_mamba_ping_pong_track_buffer_mapping[
|
||||||
|
req.req_pool_idx
|
||||||
|
] = req.mamba_ping_pong_track_buffer
|
||||||
|
else:
|
||||||
|
mamba_value_donated = self._alloc_mamba_slot()
|
||||||
|
self.req_to_token_pool.mamba_pool.copy_from(
|
||||||
|
req.mamba_pool_idx.unsqueeze(0), mamba_value_donated
|
||||||
|
)
|
||||||
|
|
||||||
result = self.insert(
|
result = self.insert(
|
||||||
InsertParams(
|
InsertParams(
|
||||||
key=RadixKey(page_aligned_token_ids, req.extra_key),
|
key=RadixKey(page_aligned_token_ids, req.extra_key),
|
||||||
value=page_aligned_kv_indices,
|
value=page_aligned_kv_indices,
|
||||||
mamba_value=mamba_value_forked,
|
mamba_value=mamba_value_donated,
|
||||||
prev_prefix_len=req.cache_protected_len,
|
prev_prefix_len=req.cache_protected_len,
|
||||||
chunked=chunked,
|
chunked=chunked,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
new_prefix_len, mamba_exist = result.prefix_len, result.mamba_exist
|
new_prefix_len, mamba_exist = result.prefix_len, result.mamba_exist
|
||||||
# there is a mamba cache in radix cache, release it
|
|
||||||
if mamba_exist:
|
if mamba_exist:
|
||||||
self.req_to_token_pool.mamba_pool.free(mamba_value_forked)
|
self.req_to_token_pool.mamba_pool.free(mamba_value_donated)
|
||||||
|
|
||||||
# The prefix indices could be updated, reuse it
|
# The prefix indices could be updated, reuse it
|
||||||
match_result = self.match_prefix(
|
match_result = self.match_prefix(
|
||||||
@@ -689,7 +688,7 @@ class MambaRadixCache(KVCacheEventMixin, BasePrefixCache):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not mamba_exist:
|
if not mamba_exist:
|
||||||
assert torch.equal(new_last_node.mamba_value, mamba_value_forked)
|
assert torch.equal(new_last_node.mamba_value, mamba_value_donated)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
req.cache_protected_len <= len(new_indices) + self.page_size - 1
|
req.cache_protected_len <= len(new_indices) + self.page_size - 1
|
||||||
@@ -951,6 +950,15 @@ class MambaRadixCache(KVCacheEventMixin, BasePrefixCache):
|
|||||||
|
|
||||||
##### Internal Helper Functions #####
|
##### Internal Helper Functions #####
|
||||||
|
|
||||||
|
def _alloc_mamba_slot(self) -> torch.Tensor:
|
||||||
|
"""Allocate one mamba pool slot, evicting if necessary."""
|
||||||
|
slot = self.req_to_token_pool.mamba_pool.alloc(1)
|
||||||
|
if slot is None:
|
||||||
|
self.evict(EvictParams(num_tokens=0, mamba_num=1))
|
||||||
|
slot = self.req_to_token_pool.mamba_pool.alloc(1)
|
||||||
|
assert slot is not None, "Can not alloc mamba cache"
|
||||||
|
return slot
|
||||||
|
|
||||||
def _match_prefix_helper(
|
def _match_prefix_helper(
|
||||||
self, key: RadixKey
|
self, key: RadixKey
|
||||||
) -> Tuple[List[torch.Tensor], TreeNode, int]:
|
) -> Tuple[List[torch.Tensor], TreeNode, int]:
|
||||||
@@ -1043,25 +1051,19 @@ class MambaRadixCache(KVCacheEventMixin, BasePrefixCache):
|
|||||||
else:
|
else:
|
||||||
mamba_branching_seqlen = None
|
mamba_branching_seqlen = None
|
||||||
|
|
||||||
# Copy mamba state to req local space if cow is true
|
# Defer COW to forward stream: record source index, allocate destination
|
||||||
if cow_mamba and last_node.mamba_value is not None:
|
if cow_mamba and last_node.mamba_value is not None:
|
||||||
# for reqs without mamba cache
|
|
||||||
if req.mamba_pool_idx is None:
|
if req.mamba_pool_idx is None:
|
||||||
dst_index = self.req_to_token_pool.mamba_pool.alloc(1)
|
dst_index = self.req_to_token_pool.mamba_pool.alloc(1)
|
||||||
# try to alloc again, protect last_node from eviction
|
|
||||||
if dst_index is None:
|
if dst_index is None:
|
||||||
self.inc_lock_ref(last_node)
|
self.inc_lock_ref(last_node)
|
||||||
self.evict(EvictParams(num_tokens=0, mamba_num=1))
|
self.evict(EvictParams(num_tokens=0, mamba_num=1))
|
||||||
dst_index = self.req_to_token_pool.mamba_pool.alloc(1)
|
dst_index = self.req_to_token_pool.mamba_pool.alloc(1)
|
||||||
self.dec_lock_ref(last_node)
|
self.dec_lock_ref(last_node)
|
||||||
assert dst_index is not None, "Can not alloc mamba cache"
|
assert dst_index is not None, "Can not alloc mamba cache"
|
||||||
src_index = last_node.mamba_value
|
|
||||||
self.req_to_token_pool.mamba_pool.copy_from(src_index, dst_index)
|
|
||||||
req.mamba_pool_idx = dst_index[0]
|
req.mamba_pool_idx = dst_index[0]
|
||||||
else:
|
req.mamba_cow_src_index = last_node.mamba_value
|
||||||
src_index = last_node.mamba_value
|
req.mamba_needs_clear = False
|
||||||
dst_index = req.mamba_pool_idx.unsqueeze(0)
|
|
||||||
self.req_to_token_pool.mamba_pool.copy_from(src_index, dst_index)
|
|
||||||
|
|
||||||
value = value[:best_value_len]
|
value = value[:best_value_len]
|
||||||
if value:
|
if value:
|
||||||
|
|||||||
@@ -364,20 +364,22 @@ class MambaPool:
|
|||||||
|
|
||||||
select_index = self.free_slots[:need_size]
|
select_index = self.free_slots[:need_size]
|
||||||
self.free_slots = self.free_slots[need_size:]
|
self.free_slots = self.free_slots[need_size:]
|
||||||
# clear at alloc time — expand a scalar GPU zero to the right shape, no CPU-GPU sync
|
return select_index
|
||||||
|
|
||||||
|
def clear_slots(self, indices: torch.Tensor):
|
||||||
|
"""Zero out mamba state at the given pool indices. Must run on forward stream."""
|
||||||
|
need_size = len(indices)
|
||||||
for i in range(len(self.mamba_cache.conv)):
|
for i in range(len(self.mamba_cache.conv)):
|
||||||
t = self.mamba_cache.conv[i]
|
t = self.mamba_cache.conv[i]
|
||||||
z = torch.zeros(1, dtype=t.dtype, device=t.device).expand(
|
z = torch.zeros(1, dtype=t.dtype, device=t.device).expand(
|
||||||
t.shape[0], need_size, *t.shape[2:]
|
t.shape[0], need_size, *t.shape[2:]
|
||||||
)
|
)
|
||||||
t[:, select_index] = z
|
t[:, indices] = z
|
||||||
t = self.mamba_cache.temporal
|
t = self.mamba_cache.temporal
|
||||||
z = torch.zeros(1, dtype=t.dtype, device=t.device).expand(
|
z = torch.zeros(1, dtype=t.dtype, device=t.device).expand(
|
||||||
t.shape[0], need_size, *t.shape[2:]
|
t.shape[0], need_size, *t.shape[2:]
|
||||||
)
|
)
|
||||||
t[:, select_index] = z
|
t[:, indices] = z
|
||||||
|
|
||||||
return select_index
|
|
||||||
|
|
||||||
def free(self, free_index: torch.Tensor):
|
def free(self, free_index: torch.Tensor):
|
||||||
if free_index.numel() == 0:
|
if free_index.numel() == 0:
|
||||||
@@ -389,22 +391,14 @@ class MambaPool:
|
|||||||
1, self.size + 1, dtype=torch.int64, device=self.device
|
1, self.size + 1, dtype=torch.int64, device=self.device
|
||||||
)
|
)
|
||||||
|
|
||||||
def copy_from(self, src_index: torch.Tensor, dst_index: torch.Tensor):
|
def copy_from(self, src_indices: torch.Tensor, dst_indices: torch.Tensor):
|
||||||
for i in range(len(self.mamba_cache.conv)):
|
for i in range(len(self.mamba_cache.conv)):
|
||||||
self.mamba_cache.conv[i][:, dst_index] = self.mamba_cache.conv[i][
|
self.mamba_cache.conv[i][:, dst_indices] = self.mamba_cache.conv[i][
|
||||||
:, src_index
|
:, src_indices
|
||||||
]
|
]
|
||||||
self.mamba_cache.temporal[:, dst_index] = self.mamba_cache.temporal[
|
self.mamba_cache.temporal[:, dst_indices] = self.mamba_cache.temporal[
|
||||||
:, src_index
|
:, src_indices
|
||||||
]
|
]
|
||||||
return
|
|
||||||
|
|
||||||
def fork_from(self, src_index: torch.Tensor) -> Optional[torch.Tensor]:
|
|
||||||
dst_index = self.alloc(1)
|
|
||||||
if dst_index is None:
|
|
||||||
return None
|
|
||||||
self.copy_from(src_index, dst_index)
|
|
||||||
return dst_index
|
|
||||||
|
|
||||||
def get_cpu_copy(self, indices):
|
def get_cpu_copy(self, indices):
|
||||||
current_platform.synchronize()
|
current_platform.synchronize()
|
||||||
@@ -548,8 +542,6 @@ class HybridReqToTokenPool(ReqToTokenPool):
|
|||||||
self.mamba_map = {layer_id: i for i, layer_id in enumerate(mamba_layer_ids)}
|
self.mamba_map = {layer_id: i for i, layer_id in enumerate(mamba_layer_ids)}
|
||||||
|
|
||||||
self.device = device
|
self.device = device
|
||||||
# Indexed by req_pool_idx, so size from the req pool buffer
|
|
||||||
# (self.req_to_token.shape[0]), not from the mamba state pool size.
|
|
||||||
req_pool_size = self.req_to_token.shape[0]
|
req_pool_size = self.req_to_token.shape[0]
|
||||||
self.req_index_to_mamba_index_mapping: torch.Tensor = torch.zeros(
|
self.req_index_to_mamba_index_mapping: torch.Tensor = torch.zeros(
|
||||||
req_pool_size, dtype=torch.int32, device=self.device
|
req_pool_size, dtype=torch.int32, device=self.device
|
||||||
@@ -558,7 +550,7 @@ class HybridReqToTokenPool(ReqToTokenPool):
|
|||||||
self.req_index_to_mamba_ping_pong_track_buffer_mapping: torch.Tensor = (
|
self.req_index_to_mamba_ping_pong_track_buffer_mapping: torch.Tensor = (
|
||||||
torch.zeros(
|
torch.zeros(
|
||||||
(req_pool_size, self.mamba_ping_pong_track_buffer_size),
|
(req_pool_size, self.mamba_ping_pong_track_buffer_size),
|
||||||
dtype=torch.int32,
|
dtype=torch.int64,
|
||||||
device=self.device,
|
device=self.device,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -578,17 +570,16 @@ class HybridReqToTokenPool(ReqToTokenPool):
|
|||||||
mamba_indices: list[torch.Tensor] = []
|
mamba_indices: list[torch.Tensor] = []
|
||||||
mamba_ping_pong_track_buffers: list[torch.Tensor] = []
|
mamba_ping_pong_track_buffers: list[torch.Tensor] = []
|
||||||
for req in reqs:
|
for req in reqs:
|
||||||
mid = None
|
if req.mamba_pool_idx is not None: # for radix cache / continuing chunked
|
||||||
if req.mamba_pool_idx is not None: # for radix cache
|
pass
|
||||||
mid = req.mamba_pool_idx
|
|
||||||
else:
|
else:
|
||||||
mid = self.mamba_pool.alloc(1)
|
mid = self.mamba_pool.alloc(1)
|
||||||
assert (
|
assert (
|
||||||
mid is not None
|
mid is not None
|
||||||
), f"Not enough space for mamba cache, try to increase --mamba-full-memory-ratio or --max-mamba-cache-size. {mid=}, {self.mamba_pool.size=}, {self.mamba_pool.available_size()=}, {len(reqs)=}"
|
), f"Not enough space for mamba cache, try to increase --mamba-full-memory-ratio or --max-mamba-cache-size. {mid=}, {self.mamba_pool.size=}, {self.mamba_pool.available_size()=}, {len(reqs)=}"
|
||||||
mid = mid[0]
|
req.mamba_pool_idx = mid[0]
|
||||||
req.mamba_pool_idx = mid
|
req.mamba_needs_clear = True
|
||||||
mamba_indices.append(mid)
|
mamba_indices.append(req.mamba_pool_idx)
|
||||||
if self.enable_mamba_extra_buffer:
|
if self.enable_mamba_extra_buffer:
|
||||||
if req.mamba_ping_pong_track_buffer is None:
|
if req.mamba_ping_pong_track_buffer is None:
|
||||||
req.mamba_ping_pong_track_buffer = self.mamba_pool.alloc(
|
req.mamba_ping_pong_track_buffer = self.mamba_pool.alloc(
|
||||||
@@ -609,9 +600,7 @@ class HybridReqToTokenPool(ReqToTokenPool):
|
|||||||
mamba_index_tensor = torch.stack(mamba_indices).to(dtype=torch.int32)
|
mamba_index_tensor = torch.stack(mamba_indices).to(dtype=torch.int32)
|
||||||
self.req_index_to_mamba_index_mapping[select_index] = mamba_index_tensor
|
self.req_index_to_mamba_index_mapping[select_index] = mamba_index_tensor
|
||||||
if self.enable_mamba_extra_buffer:
|
if self.enable_mamba_extra_buffer:
|
||||||
ping_pong_tensor = torch.stack(mamba_ping_pong_track_buffers).to(
|
ping_pong_tensor = torch.stack(mamba_ping_pong_track_buffers)
|
||||||
dtype=torch.int32
|
|
||||||
)
|
|
||||||
self.req_index_to_mamba_ping_pong_track_buffer_mapping[select_index] = (
|
self.req_index_to_mamba_ping_pong_track_buffer_mapping[select_index] = (
|
||||||
ping_pong_tensor
|
ping_pong_tensor
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -97,15 +97,9 @@ class MambaComponent(TreeComponent):
|
|||||||
dst_index = self.cache.req_to_token_pool.mamba_pool.alloc(1)
|
dst_index = self.cache.req_to_token_pool.mamba_pool.alloc(1)
|
||||||
self.cache.dec_lock_ref(last_node)
|
self.cache.dec_lock_ref(last_node)
|
||||||
assert dst_index is not None, "Can not alloc mamba cache"
|
assert dst_index is not None, "Can not alloc mamba cache"
|
||||||
self.cache.req_to_token_pool.mamba_pool.copy_from(
|
|
||||||
mamba_value, dst_index
|
|
||||||
)
|
|
||||||
req.mamba_pool_idx = dst_index[0]
|
req.mamba_pool_idx = dst_index[0]
|
||||||
else:
|
req.mamba_cow_src_index = mamba_value
|
||||||
dst_index = req.mamba_pool_idx.unsqueeze(0)
|
req.mamba_needs_clear = False
|
||||||
self.cache.req_to_token_pool.mamba_pool.copy_from(
|
|
||||||
mamba_value, dst_index
|
|
||||||
)
|
|
||||||
|
|
||||||
# HiCache: if mamba was evicted from device but has host backup,
|
# HiCache: if mamba was evicted from device but has host backup,
|
||||||
# ensure host_hit_length >= 1 so load_back is triggered.
|
# ensure host_hit_length >= 1 so load_back is triggered.
|
||||||
@@ -253,6 +247,15 @@ class MambaComponent(TreeComponent):
|
|||||||
self.cache.component_protected_size_[ct] -= vlen
|
self.cache.component_protected_size_[ct] -= vlen
|
||||||
cd.lock_ref -= 1
|
cd.lock_ref -= 1
|
||||||
|
|
||||||
|
def _alloc_mamba_slot(self) -> torch.Tensor:
|
||||||
|
"""Allocate one mamba pool slot, evicting if necessary."""
|
||||||
|
slot = self.cache.req_to_token_pool.mamba_pool.alloc(1)
|
||||||
|
if slot is None:
|
||||||
|
self.cache.evict(EvictParams(num_tokens=0, mamba_num=1))
|
||||||
|
slot = self.cache.req_to_token_pool.mamba_pool.alloc(1)
|
||||||
|
assert slot is not None, "Can not alloc mamba cache"
|
||||||
|
return slot
|
||||||
|
|
||||||
def prepare_for_caching_req(
|
def prepare_for_caching_req(
|
||||||
self,
|
self,
|
||||||
req: Req,
|
req: Req,
|
||||||
@@ -282,27 +285,24 @@ class MambaComponent(TreeComponent):
|
|||||||
else:
|
else:
|
||||||
if cache_len is None:
|
if cache_len is None:
|
||||||
return 0
|
return 0
|
||||||
|
# Donate the mamba index to the radix cache instead of copying.
|
||||||
if self.enable_mamba_extra_buffer:
|
if self.enable_mamba_extra_buffer:
|
||||||
keep_idx = self.cache.req_to_token_pool.get_mamba_ping_pong_other_idx(
|
keep_idx = self.cache.req_to_token_pool.get_mamba_ping_pong_other_idx(
|
||||||
req.mamba_next_track_idx
|
req.mamba_next_track_idx
|
||||||
)
|
)
|
||||||
mamba_value = (
|
mamba_value_donated = (
|
||||||
req.mamba_ping_pong_track_buffer[keep_idx].unsqueeze(-1).clone()
|
req.mamba_ping_pong_track_buffer[keep_idx].unsqueeze(-1).clone()
|
||||||
)
|
)
|
||||||
else:
|
req.mamba_ping_pong_track_buffer[keep_idx] = self._alloc_mamba_slot()[0]
|
||||||
mamba_value = self.cache.req_to_token_pool.get_mamba_indices(
|
self.cache.req_to_token_pool.req_index_to_mamba_ping_pong_track_buffer_mapping[
|
||||||
req.req_pool_idx
|
req.req_pool_idx
|
||||||
).unsqueeze(-1)
|
] = req.mamba_ping_pong_track_buffer
|
||||||
mamba_value_forked = self.cache.req_to_token_pool.mamba_pool.fork_from(
|
else:
|
||||||
mamba_value
|
mamba_value_donated = self._alloc_mamba_slot()
|
||||||
|
self.cache.req_to_token_pool.mamba_pool.copy_from(
|
||||||
|
req.mamba_pool_idx.unsqueeze(0), mamba_value_donated
|
||||||
)
|
)
|
||||||
if mamba_value_forked is None:
|
insert_params.mamba_value = mamba_value_donated
|
||||||
self.cache.evict(EvictParams(num_tokens=0, mamba_num=1))
|
|
||||||
mamba_value_forked = self.cache.req_to_token_pool.mamba_pool.fork_from(
|
|
||||||
mamba_value
|
|
||||||
)
|
|
||||||
assert mamba_value_forked is not None, "Can not alloc mamba cache"
|
|
||||||
insert_params.mamba_value = mamba_value_forked
|
|
||||||
return cache_len
|
return cache_len
|
||||||
|
|
||||||
def cleanup_after_caching_req(
|
def cleanup_after_caching_req(
|
||||||
|
|||||||
@@ -304,6 +304,10 @@ class ForwardBatch(ForwardBatchDeepSeekMHAMixin):
|
|||||||
mamba_track_mask: Optional[torch.Tensor] = None # shape: [b], bool
|
mamba_track_mask: Optional[torch.Tensor] = None # shape: [b], bool
|
||||||
# The seqlens to track mamba state if masked, prefill only.
|
# The seqlens to track mamba state if masked, prefill only.
|
||||||
mamba_track_seqlens: Optional[torch.Tensor] = None # shape: [b], int64
|
mamba_track_seqlens: Optional[torch.Tensor] = None # shape: [b], int64
|
||||||
|
# Deferred mamba init ops: COW pairs and clear indices (performed on forward stream)
|
||||||
|
mamba_cow_src_indices: Optional[torch.Tensor] = None
|
||||||
|
mamba_cow_dst_indices: Optional[torch.Tensor] = None
|
||||||
|
mamba_clear_indices: Optional[torch.Tensor] = None
|
||||||
|
|
||||||
# Optional seq_lens on cpu
|
# Optional seq_lens on cpu
|
||||||
seq_lens_cpu: Optional[torch.Tensor] = None
|
seq_lens_cpu: Optional[torch.Tensor] = None
|
||||||
@@ -509,6 +513,9 @@ class ForwardBatch(ForwardBatchDeepSeekMHAMixin):
|
|||||||
mamba_track_indices=batch.mamba_track_indices,
|
mamba_track_indices=batch.mamba_track_indices,
|
||||||
mamba_track_mask=batch.mamba_track_mask,
|
mamba_track_mask=batch.mamba_track_mask,
|
||||||
mamba_track_seqlens=batch.mamba_track_seqlens,
|
mamba_track_seqlens=batch.mamba_track_seqlens,
|
||||||
|
mamba_cow_src_indices=batch.mamba_cow_src_indices,
|
||||||
|
mamba_cow_dst_indices=batch.mamba_cow_dst_indices,
|
||||||
|
mamba_clear_indices=batch.mamba_clear_indices,
|
||||||
mm_inputs=batch.multimodal_inputs,
|
mm_inputs=batch.multimodal_inputs,
|
||||||
encoder_cached=batch.encoder_cached,
|
encoder_cached=batch.encoder_cached,
|
||||||
encoder_lens=batch.encoder_lens,
|
encoder_lens=batch.encoder_lens,
|
||||||
@@ -544,6 +551,7 @@ class ForwardBatch(ForwardBatchDeepSeekMHAMixin):
|
|||||||
return_hidden_states_before_norm=return_hidden_states_before_norm,
|
return_hidden_states_before_norm=return_hidden_states_before_norm,
|
||||||
rids=[req.rid for req in batch.reqs],
|
rids=[req.rid for req in batch.reqs],
|
||||||
)
|
)
|
||||||
|
|
||||||
device = model_runner.device
|
device = model_runner.device
|
||||||
|
|
||||||
if batch.extend_input_logprob_token_ids is not None:
|
if batch.extend_input_logprob_token_ids is not None:
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ from sglang.srt.layers.dp_attention import (
|
|||||||
is_dp_attention_enabled,
|
is_dp_attention_enabled,
|
||||||
)
|
)
|
||||||
from sglang.srt.layers.logits_processor import LogitsProcessorOutput
|
from sglang.srt.layers.logits_processor import LogitsProcessorOutput
|
||||||
from sglang.srt.managers.schedule_batch import ScheduleBatch
|
from sglang.srt.managers.schedule_batch import (
|
||||||
|
ScheduleBatch,
|
||||||
|
set_mamba_track_indices_from_reqs,
|
||||||
|
)
|
||||||
from sglang.srt.managers.utils import get_alloc_len_per_decode
|
from sglang.srt.managers.utils import get_alloc_len_per_decode
|
||||||
from sglang.srt.mem_cache.common import (
|
from sglang.srt.mem_cache.common import (
|
||||||
alloc_paged_token_slots_extend,
|
alloc_paged_token_slots_extend,
|
||||||
@@ -277,22 +280,8 @@ class EagleVerifyInputV2Mixin:
|
|||||||
device=device,
|
device=device,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set mamba_track_indices for mamba prefix-cache state tracking
|
|
||||||
if get_global_server_args().enable_mamba_extra_buffer():
|
if get_global_server_args().enable_mamba_extra_buffer():
|
||||||
mapping = (
|
set_mamba_track_indices_from_reqs(batch)
|
||||||
req_to_token_pool.req_index_to_mamba_ping_pong_track_buffer_mapping
|
|
||||||
)
|
|
||||||
req_pool_idx_tensor = batch.req_pool_indices.to(
|
|
||||||
device=mapping.device, dtype=torch.int64
|
|
||||||
)
|
|
||||||
track_col_idx = torch.tensor(
|
|
||||||
[req.mamba_next_track_idx for req in batch.reqs],
|
|
||||||
dtype=torch.int64,
|
|
||||||
pin_memory=True,
|
|
||||||
).to(mapping.device, non_blocking=True)
|
|
||||||
batch.mamba_track_indices = mapping[
|
|
||||||
req_pool_idx_tensor, track_col_idx
|
|
||||||
].to(dtype=torch.int64)
|
|
||||||
batch.mamba_track_mask = None
|
batch.mamba_track_mask = None
|
||||||
batch.mamba_track_seqlens = None
|
batch.mamba_track_seqlens = None
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user