From a10bd785bedad1a443be9b7ad13ffb499896cfce Mon Sep 17 00:00:00 2001 From: Leon Gao Date: Thu, 4 Jun 2026 00:10:16 -0700 Subject: [PATCH] Reduce mamba prefill allocation overhead (#25000) --- python/sglang/srt/managers/scheduler.py | 6 +++++ python/sglang/srt/mem_cache/memory_pool.py | 30 +++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/python/sglang/srt/managers/scheduler.py b/python/sglang/srt/managers/scheduler.py index ea515de96..7c1e4ec32 100644 --- a/python/sglang/srt/managers/scheduler.py +++ b/python/sglang/srt/managers/scheduler.py @@ -2642,6 +2642,9 @@ class Scheduler( self.running_batch.reqs, ) + mamba_pool = getattr(self.req_to_token_pool, "mamba_pool", None) + if mamba_pool is not None: + mamba_pool.alloc_group_begin(len(self.waiting_queue)) # Get requests from the waiting queue to a new prefill batch for req in self.waiting_queue: if self.enable_lora and not self._can_schedule_lora_req(req, running_loras): @@ -2708,6 +2711,9 @@ class Scheduler( req.mamba_pool_idx = None break + if mamba_pool is not None: + mamba_pool.alloc_group_end() + # Update waiting queue can_run_list: List[Req] = adder.can_run_list if len(can_run_list) == 0: diff --git a/python/sglang/srt/mem_cache/memory_pool.py b/python/sglang/srt/mem_cache/memory_pool.py index 69edbb12b..1f619132c 100644 --- a/python/sglang/srt/mem_cache/memory_pool.py +++ b/python/sglang/srt/mem_cache/memory_pool.py @@ -27,7 +27,7 @@ import dataclasses import logging from contextlib import contextmanager, nullcontext from dataclasses import dataclass, fields -from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Iterator, List, Optional, Tuple, Union import numpy as np import torch @@ -367,6 +367,12 @@ class MambaPool: self.mem_usage = self.mamba_cache.mem_usage_bytes() / GB self.num_mamba_layers = num_mamba_layers + # Active preallocated batch for `alloc_group_begin` / `alloc_group_end`. + # When non-None, `alloc(1)` consumes the next slot from this iterator + # instead of calling `_do_alloc(1)` per request. Reset to None outside + # a group window so `alloc` falls through to the per-call path. + self._alloc_iter: Optional[Iterator] = None + def get_speculative_mamba2_params_all_layers(self) -> SpeculativeState: assert isinstance(self.mamba_cache, self.SpeculativeState) return self.mamba_cache @@ -377,7 +383,29 @@ class MambaPool: def available_size(self): return len(self.free_slots) + # -- Batched alloc for match_prefix -- + def alloc_group_begin(self, num_reqs: int): + self._alloc_iter = None + if num_reqs > 0: + result = self._do_alloc(num_reqs) + if result is not None: + self._alloc_iter = iter(result.split(1)) + + def alloc_group_end(self): + if self._alloc_iter is not None: + remaining = list(self._alloc_iter) + if remaining: + self.free(torch.cat(remaining)) + self._alloc_iter = None + def alloc(self, need_size: int) -> Optional[torch.Tensor]: + if self._alloc_iter is not None and need_size == 1: + slot = next(self._alloc_iter, None) + if slot is not None: + return slot + return self._do_alloc(need_size) + + def _do_alloc(self, need_size: int) -> Optional[torch.Tensor]: if need_size > len(self.free_slots): return None