[srt] Reuse batched Mamba boundary mask (#33477)

This commit is contained in:
Leon Gao
2026-08-09 16:47:05 +08:00
committed by GitHub
parent 51470b376f
commit 78cd60b4e3
3 changed files with 275 additions and 8 deletions
+31 -4
View File
@@ -2073,6 +2073,9 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
mamba_track_indices: torch.Tensor = None # shape: [b], int64
mamba_track_mask: torch.Tensor = None # shape: [b], bool
mamba_track_seqlens: torch.Tensor = None # shape: [b], int64
mamba_track_mask_cpu: Optional[List[bool]] = None # shape: [b]
mamba_track_mask_next_cpu: Optional[List[bool]] = None # shape: [b]
mamba_decode_batch_idx_cpu: Optional[List[int]] = None # shape: [b]
# Lazy + spec: this iteration's per-req scatter positions
# (see mamba_lazy_spec_prepare).
mamba_lazy_spec_track_positions_cpu: Optional[List[int]] = None # shape: [b]
@@ -3013,6 +3016,9 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
# Spec decoding owns decode preparation (allocation, seq-lens bookkeeping).
from sglang.srt.speculative.spec_utils import spec_prepare_for_decode
self.mamba_track_mask_cpu = None
self.mamba_track_mask_next_cpu = None
self.mamba_decode_batch_idx_cpu = None
spec_prepare_for_decode(self)
return
@@ -3063,11 +3069,23 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
self.mamba_lazy_prealloc_at_boundary(mamba_track_interval)
set_mamba_track_indices_from_reqs(self)
track_remainders_cpu = self.seq_lens_cpu % mamba_track_interval
track_mask_cpu = track_remainders_cpu == 0
self.mamba_track_mask_cpu = track_mask_cpu.tolist()
self.mamba_track_mask_next_cpu = (
(track_remainders_cpu == mamba_track_interval - 1).tolist()
if self.enable_overlap
else None
)
# ScheduleBatch.copy() snapshots the list of requests, but the Req
# objects remain shared. The next overlapped decode can therefore
# advance their counters before this batch's result is processed.
self.mamba_decode_batch_idx_cpu = [
req.decode_batch_idx for req in self.reqs
]
# async H2D
self.mamba_track_mask = (
(self.seq_lens_cpu % mamba_track_interval == 0)
.pin_memory()
.to(device=self.device, non_blocking=True)
self.mamba_track_mask = track_mask_cpu.pin_memory().to(
device=self.device, non_blocking=True
)
def filter_batch(
@@ -3129,6 +3147,9 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
self.mamba_track_indices = None
self.mamba_track_mask = None
self.mamba_track_seqlens = None
self.mamba_track_mask_cpu = None
self.mamba_track_mask_next_cpu = None
self.mamba_decode_batch_idx_cpu = None
self.mamba_lazy_spec_track_positions_cpu = None
self.mamba_cow_src_indices = None
self.mamba_cow_dst_indices = None
@@ -3189,6 +3210,9 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
self.mamba_track_indices = None
self.mamba_track_mask = None
self.mamba_track_seqlens = None
self.mamba_track_mask_cpu = None
self.mamba_track_mask_next_cpu = None
self.mamba_decode_batch_idx_cpu = None
self.mamba_lazy_spec_track_positions_cpu = None
if self.return_logprob and other.return_logprob:
self.top_logprobs_nums = self.top_logprobs_nums + other.top_logprobs_nums
@@ -3247,6 +3271,9 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
mamba_track_indices=self.mamba_track_indices,
mamba_track_mask=self.mamba_track_mask,
mamba_track_seqlens=self.mamba_track_seqlens,
mamba_track_mask_cpu=self.mamba_track_mask_cpu,
mamba_track_mask_next_cpu=self.mamba_track_mask_next_cpu,
mamba_decode_batch_idx_cpu=self.mamba_decode_batch_idx_cpu,
mamba_lazy_spec_track_positions_cpu=self.mamba_lazy_spec_track_positions_cpu,
dp_cooperation_info=self.dp_cooperation_info,
prefill_stats=self.prefill_stats,
@@ -1015,9 +1015,28 @@ class SchedulerBatchResultProcessor:
i: int,
logits_output: LogitsProcessorOutput,
):
known_mamba_boundary = None
if batch.mamba_track_mask_cpu is not None:
lookahead = req.decode_batch_idx - batch.mamba_decode_batch_idx_cpu[i]
assert lookahead in (0, 1), (
f"mamba result lookahead={lookahead} for req {req.rid}; "
"overlap advanced more than one decode batch"
)
if lookahead == 0:
known_mamba_boundary = bool(batch.mamba_track_mask_cpu[i])
else:
known_mamba_boundary = bool(batch.mamba_track_mask_next_cpu[i])
# Called here (after update_finish_state) so req.finished() is valid
# for mamba_lazy_post_decode_at_boundary inside.
self._mamba_prefix_cache_update(req, batch, result, i)
if known_mamba_boundary is None or known_mamba_boundary:
self._mamba_prefix_cache_update(
req,
batch,
result,
i,
known_boundary=known_mamba_boundary is True,
)
if (
get_disagg().disaggregation_decode_enable_offload_kvcache
@@ -1078,6 +1097,7 @@ class SchedulerBatchResultProcessor:
batch: ScheduleBatch,
result: GenerationBatchResult,
i: int,
known_boundary: bool = False,
) -> None:
"""Update mamba track state at ping-pong boundaries.
@@ -1090,9 +1110,15 @@ class SchedulerBatchResultProcessor:
return
lazy = get_server_args().enable_mamba_extra_buffer_lazy()
at_boundary, track_seqlen = self._mamba_check_track_boundary(
req, batch, result, i
)
if known_boundary:
self._mamba_assert_committed_len_lookahead(req)
track_seqlen = req.kv_committed_len
assert track_seqlen % get_exec().mamba.mamba_track_interval == 0
at_boundary = True
else:
at_boundary, track_seqlen = self._mamba_check_track_boundary(
req, batch, result, i
)
if lazy and not batch.spec_algorithm.is_none():
# For spec, at_boundary means this step actually crossed an interval.
@@ -1169,6 +1195,20 @@ class SchedulerBatchResultProcessor:
# keep holds the track_seqlen state either way.
req.mamba_last_track_seqlen = track_seqlen
@staticmethod
def _mamba_assert_committed_len_lookahead(req: Req) -> None:
"""Alarm if overlap advances beyond the scheduler's modeled window."""
assert req.output_ids, (
"mamba track boundary reached before a decode token was appended "
f"(req {req.rid}); output_ids is empty"
)
token_seq_len = len(req.origin_input_ids) + len(req.output_ids) - 1
assert (req.kv_committed_len - token_seq_len) in (0, 1), (
f"mamba track boundary: kv_committed_len={req.kv_committed_len} "
f"leads seq_len={token_seq_len} by more than one (req {req.rid}); "
"overlap lookahead wider than assumed"
)
def _mamba_check_track_boundary(self, req, batch, result, i):
"""Check if this decode step crosses a mamba track interval boundary.
@@ -1186,6 +1226,7 @@ class SchedulerBatchResultProcessor:
interval = get_exec().mamba.mamba_track_interval
if batch.spec_algorithm.is_none():
self._mamba_assert_committed_len_lookahead(req)
if req.kv_committed_len % interval == 0:
return True, req.kv_committed_len
elif result.num_correct_drafts_per_req_cpu is not None: