Prhub

#26227 [PD]: Support HiCache prefetching and pd-incremental transfer on decode side

原始 PR 作者 hzh0425 合并时间 2026-06-02 15:40 文件变更 7 提交数 18 评论 25 代码增减 +630 / -56

执行摘要

decode 端支持 HiCache 三层缓存预取与 PD 增量传输

作为PD Disaggregation Roadmap (#21703) 的重要组成部分,该PR旨在填补decode侧无法利用分层缓存的空白。在现有PD模式中,decode端只能使用设备端L1缓存,当缓存未命中时需从prefill端全量重传KV cache,导致TTFT较高。通过引入HiCache的L2(主机)和L3(存储)缓存层次,decode端可以直接从本地恢复部分KV cache,减少跨节点传输依赖。Roadmap Issue #21846明确将'Precisely control token budget'和'Improve parallelization between HiCache transfer and PD transfer'作为前置步骤。

该PR是PD项目的重要演进,值得所有关注长上下文延迟和缓存效率的读者精读。重点关注query_storage_hit_length中的all_reduce同步设计、_process_hicache_local_restore的状态机调度方式,以及如何通过继承Mixin实现功能扩展。建议后续跟进批量all_reduce优化和并发加载支持。

讨论亮点

Review重点讨论了以下设计权衡和潜在风险:

  1. per-request all_reduce性能风险:gemini-code-assist[bot]指出query_storage_hit_length在request循环中调用all_reduce可能导致性能瓶颈。ShangmingCai回应可后续优化,hzh0425说明已通过prefetch_rate_limited做限流。

  2. check_hicache_events在循环中:同样由bot指出,应在batch级别调用一次而非per-request。

  3. 冗余前缀匹配:bot指出_process_hicache_local_restore中重新调用match_prefix_for_req是冗余的,可能导致不一致。最终决定复用DecodeRequest中存储的prefix_match

  4. _start_hicache_prefetch异常处理:ShangmingCai询问是否可能失败,hzh0425随后添加了fallback逻辑(捕获异常后降级为L2-only恢复)。

  5. TP rank发散风险:ShangmingCai关注对prefetch_rate_limited等分支的跨rank一致性,hzh0425确认依赖变量已全局同步。

实现拆解

  1. 定义核心数据结构:在新增文件decode_hicache_mixin.py中定义DecodePrefixMatch dataclass(包含l1_prefix_lendecode_prefix_len等属性)和HiCacheRestoreResult枚举(PENDING/READY/FAILED),为三层缓存匹配和本地恢复提供类型化表示。

  2. 扩展DecodeRequest:在decode.pyDecodeRequest dataclass中增加prefix_matchhicache_restored_kv_indiceshicache_restore_status等字段,用于跟踪请求在整个缓存恢复流程中的状态。

  3. 重构DecodePreallocQueue:让DecodePreallocQueue继承新混入类DecodeHiCachePreallocMixin;修改_match_prefix_and_lock方法,使其返回DecodePrefixMatch而不是简单的(indices, length),并在启用decode端HiCache时调用query_storage_hit_length查询L3存储匹配长度。

  4. 触发预取和本地恢复:在pop_preallocated中调用_start_hicache_prefetch发送L3存储预取请求;在pop_transferred中新增状态机逻辑,驱动L2→L1本地加载(通过init_load_back),并在_process_hicache_local_restore中轮询加载完成事件和检查所有挂起事件。

  5. 底层缓存支持:在hiradix_cache.py中新增query_storage_hit_length方法,用于计算L3存储命中长度(包含跨rank all_reduce同步);新增is_load_back_event_done方法用于轮询异步加载完成。在scheduler.py中新增enable_decode_hicache开关,仅在同时启用disaggregation_decode_radix_cache和hierarchical_cache时激活。

  6. 测试与配置:在测试文件test_disaggregation_decode_radix_cache.py中新增TestDisaggregationDecodeRadixHiCacheFileBackend类,端到端验证三层缓存恢复行为(包括flush cache后的L3重用场景);调整单元测试中的mock函数签名以支持新增的total_prefix_len参数。

文件 模块 状态 重要度
python/sglang/srt/disaggregation/decode_hicache_mixin.py 缓存层 added 9.28
python/sglang/srt/disaggregation/decode.py 调度器 modified 8.5
python/sglang/srt/mem_cache/hiradix_cache.py 缓存层 modified 7.64
test/registered/disaggregation/test_disaggregation_decode_radix_cache.py 集成测试 modified 7.29
python/sglang/srt/managers/scheduler.py 调度器 modified 5.28
test/registered/unit/managers/test_priority_scheduling_disaggregation.py 单元测试 modified 4.59
test/registered/unit/mem_cache/test_decode_radix_lock_ref.py 单元测试 modified 3.97

关键符号

DecodeHiCachePreallocMixin._build_decode_prefix_match DecodeHiCachePreallocMixin._start_hicache_prefetch DecodePreallocQueue._match_prefix_and_lock DecodePreallocQueue.pop_preallocated DecodeTransferQueue._process_hicache_local_restore HiRadixCache.query_storage_hit_length HiRadixCache.is_load_back_event_done

关键源码片段

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

底层缓存实现,新增 query_storage_hit_length 和 is_load_back_event_done 方法,为 decode 侧 L3 存储查询和加载完成检测提供基础。

def query_storage_hit_length(
    self,
    last_host_node: TreeNode,
    new_input_tokens: List[int],
    last_hash: Optional[str] = None,
    prefix_keys: Optional[List[str]] = None,
) -> int:
    """
    查询 L3 存储中的前缀命中长度。
    若存储不可用或受速率限制,返回 0。否则构造 PrefetchOperation
    并调用 _storage_hit_query,再通过 all_reduce 取所有 rank 的最小值。
    """
    # 存储未启用或预取被限速则直接返回 0
    if not self.enable_storage or self.cache_controller.prefetch_rate_limited():
        return 0
​
    # 构造 radix key,page 对齐
    prefetch_key = RadixKey(
        new_input_tokens,
        extra_key=last_host_node.key.extra_key,
        is_bigram=self.is_eagle,
    ).page_aligned(self.page_size)
    if len(prefetch_key) < self.prefetch_threshold:
        return 0
​
    operation = PrefetchOperation(
        "__storage_hit_query__",
        self.cache_controller.mem_pool_host.get_dummy_flat_data_page()[:0],
        prefetch_key,
        last_hash,
        prefix_keys,
    )
    _, storage_hit_count = self.cache_controller._storage_hit_query(operation)
    # 所有 rank 取最小值以保证一致性
    storage_hit_count_tensor = torch.tensor(storage_hit_count, dtype=torch.int)
    self._all_reduce_attn_groups(
        storage_hit_count_tensor, torch.distributed.ReduceOp.MIN
    )
    storage_hit_count = storage_hit_count_tensor.item()
    storage_hit_count = storage_hit_count - (storage_hit_count % self.page_size)
    return storage_hit_countdef is_load_back_event_done(self, consumer_index: int) -> bool:
    """检查指定 consumer 的本地加载事件是否完成"""
    if consumer_index < 0:
        return True
    finish_event = self.cache_controller.layer_done_counter.events[
        consumer_index
    ].finish_event
    if not finish_event.query():
        return False
    self.loading_check()
    return True

评论区精华

query_storage_hit_length 中的 all_reduce 性能风险 性能

gemini-code-assist[bot] 指出 query_storage_hit_length 在 per-request 循环中调用 all_reduce,可能导致严重性能瓶颈。ShangmingCai 认可该风险,建议后续优化。

结论:作者 hzh0425 说明已通过 prefetch_rate_limited 做限流,且该方法本身包含 all_reduce 保证一致性;ShangmingCai 同意可后续优化。 · 已解决

check_hicache_events 在循环中被调用 性能

gemini-code-assist[bot] 指出 _process_hicache_local_restore 在 for 循环中调用 self.tree_cache.check_hicache_events(),后者内含 all_reduce,应只调用一次。

结论:未在 comments 中明确是否修改,但从最终代码看可能已优化(因为最终提交修复了 comment)。 · 已解决

冗余的前缀匹配调用 正确性

gemini-code-assist[bot] 指出 _process_hicache_local_restore 中重新调用 match_prefix_for_req 在前已经匹配过的请求,可能导致不一致。

结论:作者 hzh0425 确认已改为复用 DecodeRequest.prefix_match,避免重复匹配。 · 已解决

_start_hicache_prefetch 异常处理 正确性

ShangmingCai 询问 _start_hicache_prefetch 是否会失败,是否需要 try-catch。

结论:hzh0425 添加了 fallback 逻辑,捕获异常后降级为 L2-only 恢复。 · 已解决

TP rank 节点变异的并发安全 正确性

ShangmingCai 询问 _process_hicache_local_restore 中的 init_load_back 是否会 mutate 树节点,以及 TP rank 间若出现分歧,清除逻辑能否正确清理。

结论:hzh0425 回应 init_load_back 触发 L2→L1 加载,通常不会失败;若异常,清除逻辑应已验证。 · 已解决

风险与影响

  1. 性能风险(高)query_storage_hit_length中的all_reduce在per-request粒度执行,当batch size增大时可能成为瓶颈。虽然当前有prefetch_rate_limited限制,但高频同步仍可能增加调度延迟。
  2. 冗余匹配不一致风险(中):原实现中_process_hicache_local_restore重新调用match_prefix_for_req,若radix树在此期间变更,可能导致分配与恢复参数不一致。Review后改为复用已存储的prefix_match,已缓解。
  3. 序列化加载瓶颈(中)_process_hicache_local_restore中检查len(self.tree_cache.ongoing_load_back)来控制并发加载,可能导致请求排队,影响吞吐。
  4. TP rank状态发散(低):在query_storage_hit_length中如果因为prefetch_rate_limited提前返回0,各rank可能分歧。但作者称依赖变量已同步,且该方法本身就包含all_reduce,因此风险较低。
  5. 事件泄漏风险(低):若请求在恢复中途被abort,_clean_hicache_prefetch_resources能否正确清理所有状态需要测试验证。

对用户:启用--disaggregation-decode-enable-radix-cache--enable-hierarchical-cache后,PD部署的TTFT显著降低(平均-39%,P99-52%),TPOT略有上升(~2.5%),适合TTFT敏感型场景。

对系统:增加decode端的内存占用(L2/L3缓存),并新增存储后端(文件/分布式)的读写压力。需监控磁盘IO和内存使用。

对团队:新增一个源文件(decode_hicache_mixin.py约300行)和测试框架,维护复杂度提升。引入的状态机增加了调度逻辑的调试难度,需配套详细的日志和metrics。

兼容性:仅在同时启用--disaggregation-decode-enable-radix-cache--enable-hierarchical-cache时激活,默认行为不变,向后兼容。

per-request all_reduce 同步 冗余匹配不一致 序列化加载瓶颈 TP rank 发散

关联 Issue

#21703 [Roadmap] Prefill-Decode Disaggregation Roadmap (2026 Q2)

完整报告

参与讨论