From df0dc44931433b8a3488fbd4c6489e08cdee8703 Mon Sep 17 00:00:00 2001 From: Mohammad Miadh Angkad <176301910+mmangkad@users.noreply.github.com> Date: Sun, 20 Sep 2026 03:27:51 +0000 Subject: [PATCH] [Fix] Forward SWA prealloc reclaim through the DSV4 HiSparse allocator (#40354) Co-authored-by: Mohammad Angkad --- .../srt/mem_cache/allocator/hisparse.py | 8 ++++++++ .../unit/mem_cache/test_hisparse_allocator.py | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/python/sglang/srt/mem_cache/allocator/hisparse.py b/python/sglang/srt/mem_cache/allocator/hisparse.py index 706df84bf..9508aa150 100644 --- a/python/sglang/srt/mem_cache/allocator/hisparse.py +++ b/python/sglang/srt/mem_cache/allocator/hisparse.py @@ -376,6 +376,14 @@ class DeepSeekV4HiSparseTokenToKVPoolAllocator(BaseTokenToKVPoolAllocator): def swa_available_size(self): return self.logical_attn_allocator.swa_available_size() + def reclaim_for_prealloc( + self, tree_cache, full_tokens: int, swa_tokens: int + ) -> str | None: + # C4 needs no reclaim here: full_available_size prices it into the budget. + return self.logical_attn_allocator.reclaim_for_prealloc( + tree_cache, full_tokens, swa_tokens + ) + def free_swa(self, free_indices: torch.Tensor): self.logical_attn_allocator.free_swa(free_indices) diff --git a/test/registered/unit/mem_cache/test_hisparse_allocator.py b/test/registered/unit/mem_cache/test_hisparse_allocator.py index d0fc86156..f2049b8d3 100644 --- a/test/registered/unit/mem_cache/test_hisparse_allocator.py +++ b/test/registered/unit/mem_cache/test_hisparse_allocator.py @@ -60,6 +60,25 @@ class TestDeepSeekV4HiSparseAllocator(CustomTestCase): self.assertEqual(kwargs["extend_num_tokens"], 512) self.assertEqual(kwargs["swa_tail_len"], 128) + def test_forwards_prealloc_reclaim_to_logical_allocator(self): + """PD decode preallocation must not crash on the HiSparse composite.""" + allocator = object.__new__(DeepSeekV4HiSparseTokenToKVPoolAllocator) + logical_allocator = MagicMock(spec=["reclaim_for_prealloc"]) + allocator.logical_attn_allocator = logical_allocator + logical_allocator.reclaim_for_prealloc.return_value = None + + tree_cache = object() + self.assertIsNone(allocator.reclaim_for_prealloc(tree_cache, 512, 256)) + logical_allocator.reclaim_for_prealloc.assert_called_once_with( + tree_cache, 512, 256 + ) + + logical_allocator.reclaim_for_prealloc.return_value = "SWA eviction short" + self.assertEqual( + allocator.reclaim_for_prealloc(tree_cache, 512, 256), + "SWA eviction short", + ) + def test_hisparse_budget_uses_full_logical_capacity_for_swa_tail(self): from sglang.srt.disaggregation.decode import DecodePreallocQueue