Prhub

#33348 [DCP] Match the replicated draft KV pool's page granularity to its allocator

原始 PR 作者 kpham-sgl 合并时间 2026-08-06 02:40 文件变更 1 提交数 6 评论 3 代码增减 +34 / -22

执行摘要

DCP 下 draft KV 池页粒度对齐分配器,修复越界写

PR body 明确说明:Under DCP the shared allocator pages the virtual loc space in page_size * dcp_size units. #32828 sized the replicated draft KV pool for that space but left its page_size at the per-rank value, so the allocator's last page is only partly backed by pool rows and writes into its tail go out of bounds. 即分配器按 page_size * dcp_size 分页,而 pool 页太小,导致分配器最后一页的尾部写入越界,可能造成内存破坏或偶发崩溃。

值得精读。它展示了分布式推理中 replicated pool 与 shared allocator 之间页粒度对齐的关键细节,以及如何用属性访问器把分散的条件逻辑收敛为单一事实源。关注 loc_space_scale / pool_page_size 的语义,可以避免再次引入同类 bug。

讨论亮点

Review 流程极简:hnyls2002 直接 APPROVED,无公开评论。作者曾通过 /rerun-failed-ci 重跑过一次 CI(extra 任务曾失败,最终通过)。没有出现设计或正确性争议。

实现拆解

  1. 在 KVCacheConfigurator 中新增 loc_space_scale 属性:当为 draft worker 且 dcp_size > 1 时返回 dcp_size,否则返回 1,将分散在 _derive_pool_sizes 中的条件逻辑收敛为一处。
  2. 新增 pool_page_size 属性,返回 get_schedule().page_size * loc_space_scale,定义"虚拟 loc 空间下的页粒度"。
  3. 重构 _derive_pool_sizes:用 loc_scale = self.loc_space_scale 统一缩放 max_total_num_tokens、full_max_total_num_tokens、swa_max_total_num_tokens,替换原先的 if 判断。
  4. 将全部 build*_kv_pool 系列方法(DSV4、OOT DSA/MLA/MHA、Ascend SWA/MLA/MHA、DSA、MLA FP4、MLA、混合 SWA、MiniMax sparse、混合 linear、MHA FP4、MHA 等)中的 page_size=get_schedule().page_size 统一替换为 page_size=self.pool_page_size,保证每个 pool 的物理页大小与共享分配器的虚拟页对齐。
  5. 无新增测试;依赖现有 CI 与 draft validation 覆盖。注意 swa_page_size 等其他参数保持独立,不受影响。
文件 模块 状态 重要度
python/sglang/srt/mem_cache/kv_cache_configurator.py KV 缓存 modified 7.68

关键符号

loc_space_scale pool_page_size _derive_pool_sizes

关键源码片段

python/sglang/srt/mem_cache/kv_cache_configurator.py core-logic

核心文件:新增 loc_space_scale 与 pool_page_size 访问器,并将所有 KV pool 构造处的 page_size 统一切换,修复 DCP 下 draft pool 越界写风险。

# python/sglang/srt/mem_cache/kv_cache_configurator.py# 关键设计:DCP 下 replicated draft pool 索引的是共享分配器的虚拟 loc 空间,
# 该空间按 page_size * dcp_size 分页;draft pool 必须使用同样的页粒度,
# 否则分配器最后一个页的尾部写入会越过 pool 边界。
@property
def loc_space_scale(self) -> int:
    dcp_size = self.server_args.dcp_size
    # draft worker 的 pool 是复制品而非分片,必须按整个虚拟空间缩放;
    # 非 draft worker 或单 rank 场景保持 1。
    return dcp_size if (self.is_draft_worker and dcp_size > 1) else 1@property
def pool_page_size(self) -> int:
    # 虚拟 loc 空间下的页粒度:基础页大小乘以 loc_space_scale。
    return get_schedule().page_size * self.loc_space_scaledef _derive_pool_sizes(self, *, config: MemoryPoolConfig) -> _PoolSizes:
    # ...(省略无关部分)
    loc_scale = self.loc_space_scale
    max_total_num_tokens *= loc_scale
    if full_max_total_num_tokens is not None:
        full_max_total_num_tokens *= loc_scale
    if swa_max_total_num_tokens is not None:
        swa_max_total_num_tokens *= loc_scale
    # ...(后续逻辑不变)# 所有 KV pool 构造统一使用 self.pool_page_size,取代原来的 get_schedule().page_size:
token_to_kv_pool = pool_cls(
    max_total_num_tokens,
    page_size=self.pool_page_size, # 与共享分配器的虚拟页粒度保持一致
    dtype=self.kv_cache_dtype,
    # ...
)

评论区精华

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

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

风险与影响

影响面限定在 draft worker 且 dcp_size > 1 的 DCP 部署:pool_page_size 变为原 page_size 的 dcp_size 倍,每页内存增大,页表项减少,尾部对齐可能产生少量内存浪费,但总容量不变。所有 pool 构造路径(包括 NPU 的 Ascend 路径)都已切换,需确认 NPU 后端对 page_size 的假设是否兼容;目前没有专门针对该组合的单元测试,回归依赖 e2e 测试。另外,若未来共享分配器的分页逻辑再次变化,需同步维护 pool_page_size 访问器。

对使用 DCP 多 rank + draft worker(如 DeepSeek 系列)的用户,修复了潜在的非确定性越界写入,避免偶发的内存腐蚀、崩溃或错误结果。对单 rank 或非 draft worker 完全无行为变化。对团队来说,这是一个小而重要的正确性修复,代码可读性也因访问器收敛而提升。

核心内存池路径变更 缺少直接测试覆盖

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论