Prhub

#23606 [HiSparse & PD] Support hisparse memory pool host page > 1

原始 PR 作者 huangtingwei9988 合并时间 2026-05-19 16:29 文件变更 7 提交数 17 评论 20 代码增减 +253 / -147

执行摘要

支持 HiSparse 主机池页大小大于 1

Currently, the host pool in hisparse has a hardcoded configuration of page=1. Consequently, page scheduling is interrupted at this point; when hisparse is integrated with hicache—particularly regarding data transfer performance between storage and the host—performance degrades significantly.

建议合并并关注性能基准测试结果,特别是 PD 模式下的吞吐量。设计上的 Mixin 抽象值得后续参考。

讨论亮点
  • ShangmingCai 询问 dst 索引是否直接在 hisparse 模块中处理,认为放弃了异质页大小的灵活性,建议未来考虑。作者确认当前统一。
  • hzh0425 询问预填充端是否需要更新 send_kvcache_hisparse,结论是该函数被移除。
  • xiezhq-hermann 建议将 ensure_host_slots 逻辑移入 memory_pool_host,最终抽象为 HiSparseHostPoolMixin。
  • hzh0425 在整体评论中强调需要优化 GPU-CPU 同步,并建议进行 PD 模式基准测试。

实现拆解

  1. memory_pool_host.py 中引入 HiSparseHostPoolMixin,提供 alloc_paged_token_slotsallocated_host_indices 等页级别分配方法。
  2. 修改 MLATokenToKVPoolHost 继承该 Mixin,并将 page_size 参数从硬编码1改为从设备池获取。
  3. 修改 hisparse_coordinator.py 中的 __init__admit_request_into_staging_eager_backup_previous_token,使用新的分配接口。
  4. 修改 decode.py 中的 _init_kv_manager_pre_alloc,移除特判分支,统一使用 alloc_paged_token_slots
  5. 移除 mooncake/conn.py 中的 send_kvcache_hisparse 方法及 enable_hisparse 字段,因为页大小统一后不再需要特殊处理。
  6. 移除 common/conn.py 中针对 hiSparse 的页大小不匹配绕过逻辑。
  7. 新增单元测试 test_single_node_staging_allocates_paged_host_slotstest_pd_decode_prealloc_hisparse_host_slots,验证页级分配正确性。
文件 模块 状态 重要度
python/sglang/srt/mem_cache/memory_pool_host.py 主机池 modified 8.37
python/sglang/srt/disaggregation/mooncake/conn.py 传输层 modified 7.47
python/sglang/srt/managers/hisparse_coordinator.py 协调器 modified 7.08
python/sglang/srt/disaggregation/decode.py 解码器 modified 6.85
test/registered/unit/managers/test_hisparse_unit.py 单元测试 modified 6.74
python/sglang/srt/disaggregation/common/conn.py 通用连接 modified 6.4
python/sglang/srt/mem_cache/hisparse_memory_pool.py 稀疏内存池 modified 6.29

关键符号

HiSparseHostPoolMixin._round_up_to_page_size HiSparseHostPoolMixin.alloc_page HiSparseHostPoolMixin.alloc_paged_token_slots HiSparseHostPoolMixin.allocated_host_indices MLATokenToKVPoolHost.__init__ DeepSeekV4SingleKVPoolHost.__init__ HiSparseCoordinator.admit_request_into_staging HiSparseCoordinator._eager_backup_previous_token Decode._init_kv_manager Decode._pre_alloc

关键源码片段

python/sglang/srt/mem_cache/memory_pool_host.py dependency-wiring

核心变更文件,新增 HiSparseHostPoolMixin 实现页级主机分配,并修改 MLATokenToKVPoolHost 继承该混入类。

class HiSparseHostPoolMixin:
    def _round_up_to_page_size(self, size: int) -> int:
        # 将 size 向上对齐到 page_size 的整数倍
        return (size + self.page_size - 1) // self.page_size * self.page_size
​
    def alloc_page(self, num_pages: int) -> Optional[torch.Tensor]:
        # 按页数分配连续主机缓存
        return self.alloc(num_pages * self.page_size)
​
    def alloc_paged_token_slots(
        self,
        req_to_host_pool: torch.Tensor,
        req_to_host_pool_allocated_len: torch.Tensor,
        req_pool_idx: int,
        start_pos: int,
        num_tokens: int,
    ) -> torch.Tensor:
        """Allocate request host slots by page and return token-granular slots.
           以页为单位分配请求主机槽位,返回 token 粒度的索引。"""
        device = req_to_host_pool.device
        if num_tokens <= 0:
            return torch.empty((0,), dtype=torch.int64, device=device)
​
        allocated_len = int(req_to_host_pool_allocated_len[req_pool_idx])
        end_pos = start_pos + num_tokens
        page_end = self._round_up_to_page_size(end_pos)
        assert start_pos <= allocated_len
​
        if page_end > allocated_len:
            num_new_pages = (page_end - allocated_len) // self.page_size
            host_locs = self.alloc_page(num_new_pages)
            if host_locs is None:
                logger.error(
                    "HiSparse: host mem pool alloc failed for %d host pages "
                    "(req_pool_idx=%d, start_pos=%d, num_tokens=%d)",
                    num_new_pages,
                    req_pool_idx,
                    start_pos,
                    num_tokens,
                )
                raise RuntimeError(
                    f"HiSparse host mem pool alloc failed for {num_new_pages} pages"
                )
​
            req_to_host_pool[req_pool_idx, allocated_len:page_end] = host_locs.to(
                device=device, non_blocking=True
            )
            req_to_host_pool_allocated_len[req_pool_idx] = page_end
​
        return req_to_host_pool[req_pool_idx, start_pos:end_pos]
​
    def allocated_host_indices(
        self,
        req_to_host_pool: torch.Tensor,
        req_pool_idx: int,
        allocated_len: int,
    ) -> torch.Tensor:
        # 返回已分配且有效的主机索引(过滤掉 -1 填充)
        allocated_len = int(allocated_len)
        host_len = min(
            self._round_up_to_page_size(allocated_len),
            req_to_host_pool.shape[1],
        )
        host_indices = req_to_host_pool[req_pool_idx, :host_len]
        return host_indices[host_indices >= 0]

评论区精华

异质页大小设计权衡 设计

ShangmingCai 询问是否放弃了异质页大小的灵活性,建议未来考虑。作者回复当前统一,异质可通过后处理实现。

结论:当前 PR 统一了页大小,未来可通过后处理支持异质。 · 已解决

P 端是否需要更新 send_kvcache_hisparse 正确性

hzh0425 询问预填充端是否也需要相应更新,因为 send_kvcache_hisparse 在 D 端移除。

结论:send_kvcache_hisparse 被移除,P 端直接使用统一接口。 · 已解决

ensure_host_slots 实现位置 设计

xiezhq-hermann 建议将 ensure_host_slots 逻辑移入 memory_pool_host,而不是留在 hisparse_coordinator。

结论:抽象出 HiSparseHostPoolMixin 并集成到 memory_pool_host。 · 已解决

GPU-CPU 同步优化和基准测试 性能

hzh0425 指出 PR 包含许多 GPU-CPU 同步,需要优化;并建议在 PD 模式下做基准对比。

结论:作者接受意见,待后续优化和测试。 · 待处理

风险与影响

  • 性能风险:引入新的分配路径和 GPU-CPU 同步点,可能在 PD 模式下有性能回归,需要基准测试验证。
  • 兼容性风险:统一页大小后,原有的异质大小布局不再支持,如果未来需要不同设备页大小则需要额外改造。
  • 正确性风险:分配逻辑变更涉及边界条件,如向上取整和 assert,需确保无溢出或越界。
  • 对用户:在启用 HiSparse 时,主机 KV 缓存分配现在以页粒度进行,与设备池一致,提升零拷贝传输效率。
  • 对系统:PD 模式下预填充阶段不再需要特化扩展,传输路径简化。
  • 对团队:代码可维护性提高,主机池分配逻辑集中到 Mixin。
核心路径变更 性能回归风险 GPU-CPU 同步增加

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论