Prhub

#28085 [PD] Optimize SWA allocation

原始 PR 作者 cctry 合并时间 2026-06-16 02:01 文件变更 3 提交数 4 评论 2 代码增减 +34 / -22

执行摘要

优化 PD decode 场景 SWA 分配路径与内存

在 PD(Prefill/Decode)分离部署的解码路径中,SWA 混合分配器存在两处可优化点:一是容量检查与索引映射代码重复,二是解码预分配时未跟踪窗口外 KV 长度,导致无法释放窗口外内存。PR body 明确指出优化目标为“Allocation-path cleanup + a memory optimization for the SWA hybrid KV allocator”。

本 PR 属于中等重要度的重构+小优化,代码结构清晰,改动量小,推荐相关维护者精读 swa.py 中的分配重构逻辑,可作为同类代码清理的参考模式。

讨论亮点

PR 仅有 0 条 review 评论和 2 条低信号 issue 评论(插件配额告警和 CI rerun 指令),无技术讨论。merrymercy 直接批准,表明变更逻辑清晰、风险低。

实现拆解

  1. 提取公用方法 new_pages_available:在 python/sglang/srt/mem_cache/allocator/swa.py 中新增 new_pages_available(num_full_pages, num_swa_pages) 方法,统一封装 full 与 SWA 子分配器的可用容量检查,替代 alloc_extendalloc_extend_swa_tail 中原有的两段重复判断。
  2. 统 一映射写入入口:将 allocalloc_extendalloc_extend_swa_tail 中分散的平台分支(NPU 与非 NPU)都替换为调用已有的 set_full_to_swa_mapping 方法,减少代码重复,也与 alloc 方法保持一致。
  3. 传递 num_new_pages 给 SWA 子分配器alloc_extend_swa_tail 原本未向子 alloc_extend 传递 num_new_pages,本 PR 将 num_full_pagesnum_swa_pages 分别传入 full/SWA 子分配器,使行为与 alloc_extend 一致,避免子分配器重复计算。
  4. 跟踪 swa_evicted_seqlen:在 python/sglang/srt/disaggregation/decode.py 的预分配路径中,当使用 alloc_extend_swa_tail 分配后,设置 req.swa_evicted_seqlen = fill_len - swa_tail_len,记录窗口外被丢弃的序列长度,使得后续解码可释放这部分 KV 内存。
  5. 更新测试桩:在 test/registered/unit/mem_cache/test_swa_alloc_extend_page_estimation.py_make_self 桩中添加了 new_pages_availableset_full_to_swa_mapping 方法,使测试能正确构造 allocator,保持现有回归测试通过。
文件 模块 状态 重要度
python/sglang/srt/mem_cache/allocator/swa.py 内存分配器 modified 7.03
test/registered/unit/mem_cache/test_swa_alloc_extend_page_estimation.py 测试 modified 5.47
python/sglang/srt/disaggregation/decode.py PD 解码 modified 4.35

关键符号

new_pages_available set_full_to_swa_mapping alloc_extend alloc_extend_swa_tail _pre_alloc

关键源码片段

python/sglang/srt/mem_cache/allocator/swa.py core-logic

核心重构文件,提取 `new_pages_available` 方法,统一映射写入入口,传递 `num_new_pages` 参数,是行为保持的清理和优化基础。

    # 新增:统一容量检查方法,避免重复代码
    def new_pages_available(self, num_full_pages: int, num_swa_pages: int) -> bool:
        # 同时校验 full 和 SWA 子分配器是否有足够页面
        return (
            num_full_pages
            <= self.full_attn_allocator.available_size() // self.page_size
            and num_swa_pages
            <= self.swa_attn_allocator.available_size() // self.page_size
        )
​
    def alloc_extend(self, ...):
        # ...
        num_new_pages = get_num_new_pages(...)
        # 替换原来的两段式检查
        if not self.new_pages_available(num_new_pages, num_new_pages):
            return None
        # ...
        # 统一使用 set_full_to_swa_mapping
        self.set_full_to_swa_mapping(alloc_full_indices, alloc_swa_indices)
        return alloc_full_indices
​
    def alloc_extend_swa_tail(self, ...):
        # ...
        # 传递 num_new_pages 参数,与 alloc_extend 对齐
        alloc_full_indices = self.full_attn_allocator.alloc_extend(
            ...,
            num_new_pages=num_full_pages,
        )
        # ...
        alloc_swa_indices = self.swa_attn_allocator.alloc_extend(
            ...,
            num_new_pages=num_swa_pages,
        )
        # 统一使用 set_full_to_swa_mapping
        self.set_full_to_swa_mapping(
            alloc_full_indices[-swa_tail_len:], alloc_swa_indices
        )
test/registered/unit/mem_cache/test_swa_alloc_extend_page_estimation.py test-coverage

测试配套更新,为桩对象添加 `new_pages_available` 和 `set_full_to_swa_mapping` 方法,确保重构后回归测试通过。

def _make_self(*, page_size: int, full_available: int, swa_available: int):
    full_indices = torch.tensor([10, 11], dtype=torch.int64)
    swa_indices = torch.tensor([20, 21], dtype=torch.int64)
    full_to_swa_index_mapping = torch.zeros(64, dtype=torch.int64)
​
    # 新增:模拟 new_pages_available 方法,行为与生产一致
    def new_pages_available(num_full_pages: int, num_swa_pages: int) -> bool:
        return (
            num_full_pages <= full_available // page_size
            and num_swa_pages <= swa_available // page_size
        )
​
    # 新增:模拟 set_full_to_swa_mapping 方法
    def set_full_to_swa_mapping(
        full_indices: torch.Tensor, swa_indices: torch.Tensor
    ) -> None:
        full_to_swa_index_mapping[full_indices] = swa_indices
​
    return SimpleNamespace(
        # ...
        new_pages_available=new_pages_available,
        set_full_to_swa_mapping=set_full_to_swa_mapping,
        full_to_swa_index_mapping=full_to_swa_index_mapping,
    )
python/sglang/srt/disaggregation/decode.py core-logic

实际内存优化落地处,添加一条记录 `swa_evicted_seqlen` 的赋值语句,使解码路径可知窗口外可释放的 KV 长度。

            # 在 alloc_extend_swa_tail 成功后记录窗口外被丢弃的序列长度
            if self._uses_swa_tail_prealloc() and prefix_len == 0:
                kv_loc = self.token_to_kv_pool_allocator.alloc_extend_swa_tail(...)
                # 新增:记录 evicted seqlen,供后续释放使用
                req.swa_evicted_seqlen = fill_len - self._swa_tail_len(fill_len)
            else:
                # ...

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

  1. 回归风险低:重构部分(new_pages_availableset_full_to_swa_mapping)行为等价,且有单元测试覆盖;新增 swa_evicted_seqlen 字段仅增加跟踪,不影响现有分配逻辑。
  2. 性能风险alloc_extend_swa_tail 新增 num_new_pages 参数传递,但子分配器已处理该参数,无额外开销。
  3. 兼容性:NPU 路径内部映射方式未变,_is_npu 分支被统一到 set_full_to_swa_mapping,该函数内部已处理平台差异,无影响。

影响范围集中在 PD 分离部署的解码路径中 SWA KV 分配器,核心收益是滑动窗口外 KV 可被及时释放,从而提升内存利用率。对非 PD 场景无影响,NPU decode 路径不受影响。测试文件改动确保回归覆盖。

重构行为等价 少量新增字段

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论