Prhub

#37151 [Unified Cache Linker][3/N]: Add backend-independent linker core

原始 PR 作者 hzh0425 合并时间 2026-08-31 14:07 文件变更 4 提交数 1 评论 7 代码增减 +1071 / -1

执行摘要

新增后端无关外部缓存链路核心并接入缓存树

PR body 明确说明这是从 #35687 拆出的第三块,目标是 "Add the backend-independent external linker interface and tree-side wrapper",并把 "direct load/offload lifecycle hooks" 集成进 UnifiedRadixCache。动机层面:给 UnifiedRadixCache 提供直连外部 KV 存储(L3)的能力——不经过 host tier,直接从设备池对接远端 KV 存储,以支持更大规模的 KV 复用与跨节点缓存;同时通过把巨型 PR 拆成多块(#37091、#37098 与本 PR)降低评审与回滚风险,让契约与后端实现解耦。

值得精读。重点看三处设计:① _sync_restorable_prefix 用 0/1 mask + MIN all-reduce 求跨 rank 稀疏集合交集,避免“取局部最大值”落点不可恢复;② UnifiedCacheLinkerWrapper 的「排队 + 异步完成」传输协议与锁 pin/unpin 机制,兼顾按层加载与异常回滚;③ check_hicache_events 的双轨事件轮询与卸载结果二次归约(任一 rank 失败整体回滚)。建议后续 backend PR 合入前补充一次真实多卡端到端验证(含 DMA 失败注入)。

讨论亮点

本 PR 没有 review 评论,reviewer huangtingwei9988 直接 APPROVED,说明接口与树侧接入方案在内部评审中无争议。实际讨论集中在 issue 评论区的 CI 复跑:

  • 作者两次 /rerun-testtest_unified_cache_linker.pytest_hiradix_pp_sync_drain.py 在 ubuntu-latest 均通过。
  • 作者一次 /rerun-group radix_cache/unified_radix_tree:在 4-gpu-h100、4-gpu-b200、8-gpu-h200 上共 8 组多卡 unified radix cache 用例全部通过,验证了跨 rank MIN 归约在真实多卡环境下的正确性。
  • 未决事项:PR body 中 AMD ROCm 7.2 CI 运行失败(Run #33325476754),评论中没有任何说明,无法确认是否与本次变更相关。

实现拆解

  1. 新增传输契约与树侧包装层(unified_cache_linker.py,+571 行)
    - UnifiedCacheLinker(ABC) 定义 11 个抽象方法:lookuploadstart_layer_wise_loadingcancel_queued_loadnum_completed_loadspop_completed_loadoffloadnum_completed_offloadspop_completed_offloadresetclose
    - 契约采用「排队 + 异步完成」模型:load 只入队,真正的传输由下一次 start_layer_wise_loading 触发并返回 layer-counter 消费者索引,这是为配合按层推进的 CUDA graph 或流水线约束而设计。
    - UnifiedCacheLinkerWrapper 是树侧驱动,持有 hit_markerspending_loadspending_offloads 三个状态容器;构造时把 tree_core.enable_external_cache_linker 置 True、write_through_threshold 置 1,完成「挂载即切模式」。

  2. 生命周期接入(unified_radix_cache.py,+52/-1)
    - 新增 self.linker: Optional[UnifiedCacheLinkerWrapper] = None 属性与 init_cache_linker() 入口。
    - reset()release_host_resources() 分别补充 linker.reset()linker.close() 调用,保证后端先静默传输、再释放资源。

  3. 热路径守卫钩子
    - match_prefix() 在常规树匹配与 finalizer 之后追加 linker.match(),把外部可恢复长度合并进 host_hit_length 并暂存 hit_markers
    - init_load_back() 开头用 linker.has_hit(rid) 分流,命中则走 linker.load_back() 直接回灌并插入树。
    - _apply_cache_action()BackupKV 分支在挂载 linker 时改由 linker.offload_nodes() 异步持久化;ReplaceWriteThroughOnNodeSplit 分支同步把在途卸载重定向到拆分后的节点。
    - release_aborted_request() 增加 linker.release_request(rid) 清理排队加载并解锁。

  4. 事件轮询分流
    - check_hicache_events() 在挂载 linker 时走独立分支:先 MIN all-reduce 归约各 rank 的 load/offload 完成数,再 drain_loads()commit_completed_offloads();卸载成功位图二次 all-reduce,任一 rank 失败即整体回滚。该分支直接 return,绕开原有 HiCache 控制队列。
    - ready_to_load_host_cache() 把启动权交给 linker.start_layer_wise_loading()

  5. 测试配套
    - 新增 test_unified_cache_linker.py(+447 行):_FakeLinker 用内存态实现全部抽象方法,覆盖挂载开关、稀疏集合交集、异步加载/卸载的锁 pin/unpin、失败卸载回滚拆分碎片、split action 重定向、reset 静默顺序、cancel 排队加载等 9 个用例。
    - test_hiradix_pp_sync_drain.py_make_cache 夹具补 cache.linker = None,适配新属性。

文件 模块 状态 重要度
python/sglang/srt/mem_cache/unified_cache/unified_cache_linker.py 缓存链路 added 9.08
python/sglang/srt/mem_cache/unified_radix_cache.py 缓存核心 modified 7.15
test/registered/unit/mem_cache/test_unified_cache_linker.py 链路测试 added 7.48
test/registered/unit/mem_cache/test_hiradix_pp_sync_drain.py PP 同步 modified 2.88

关键符号

UnifiedCacheLinker.lookup UnifiedCacheLinker.load UnifiedCacheLinker.start_layer_wise_loading UnifiedCacheLinker.offload UnifiedCacheLinkerWrapper.match UnifiedCacheLinkerWrapper._sync_restorable_prefix UnifiedCacheLinkerWrapper.load_back UnifiedCacheLinkerWrapper.offload_nodes UnifiedCacheLinkerWrapper.release_request UnifiedRadixCache.init_cache_linker UnifiedRadixCache.check_hicache_events

关键源码片段

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

外部缓存链路与既有统一缓存树的唯一接入点:新增 linker 属性、init_cache_linker 入口,并在 match_prefix、init_load_back、_apply_cache_action(BackupKV / ReplaceWriteThroughOnNodeSplit)、release_aborted_request、check_hicache_events、ready_to_load_host_cache 六处热路径挂载守卫钩子。改动虽小但影响面广,决定外部缓存何时接管写穿与事件轮询。

# unified_radix_cache.py -- 外部缓存链路的树侧接入点
# 原则:所有 linker 钩子都以 self.linker is not None 守卫,
# 默认(未挂载)路径与原先完全一致,避免影响现有 HiCache 行为。def init_cache_linker(self, cache_linker: UnifiedCacheLinker) -> None:
    """把外部 KV 存储直连到设备池上。"""
    # 树侧只保存包装层,主树文件不感知具体后端;
    # 包装层构造时会自动打开树侧开关并压低写穿阈值。
    self.linker = UnifiedCacheLinkerWrapper(self, cache_linker)def match_prefix(self, params: MatchPrefixParams) -> MatchResult:
    result = self.tree_core.match_prefix(params)
    self._apply_cache_actions(result.cache_actions)
    for component in self._components_tuple:
        result = component.finalize_match_result_in_cache(params, result)
    # 常规 match 结束后,再交给 linker 探测外部存储的可恢复尾部
    if self.linker is not None and params.req is not None:
        result = self.linker.match(params.key, params.req, result)
    return resultdef _apply_cache_action(self, action: CacheAction | ComponentAction) -> None:
    # ... 其余 action 分支保持不变 ...
    elif isinstance(action, BackupKV):
        # 挂载 linker 后,写穿语义由外部存储异步接管;
        # 未挂载时退回原有 HiCache D→H backup 路径
        if self.linker is not None:
            self.linker.offload_nodes(action.node_ids)
        else:
            self._execute_and_commit_kv_backup(action)def check_hicache_events(self) -> None:
    """每个 scheduler step 轮询异步事件;linker 路径在此分流。"""
    if self.linker is not None:
        # 先用 MIN 归约各 rank 的完成数,保证所有 rank 消费一致的
        # 批次数量,避免部分 rank 提前消费造成跨 rank 不一致
        finish_counts = torch.tensor(
            [
                self.linker.num_completed_loads(),
                self.linker.num_completed_offloads(),
            ],
            dtype=torch.int,
            device="cpu",
        )
        self._all_reduce_attn_groups(finish_counts, torch.distributed.ReduceOp.MIN)
        load_count, offload_count = map(int, finish_counts.tolist())
        self.linker.drain_loads(load_count)
        local_successes = self.linker.take_completed_offloads(offload_count)
        if local_successes:
            # 卸载结果再归约一次:只要有一个 rank 失败就整体回滚
            successes = torch.tensor(local_successes, dtype=torch.int, device="cpu")
            self._all_reduce_attn_groups(successes, torch.distributed.ReduceOp.MIN)
            self.linker.commit_completed_offloads(
                [bool(success) for success in successes.tolist()]
            )
        return # linker 路径完成后直接返回,不再走 HiCache 控制队列
    # ... 原有 HiCache 事件处理逻辑 ...

评论区精华

CI 复跑验证单测与多卡回归 测试

作者在 issue 评论区用 /rerun-test 复跑 test_unified_cache_linker.py 与 test_hiradix_pp_sync_drain.py;用 /rerun-group radix_cache/unified_radix_tree 复跑 4-gpu-h100、4-gpu-b200、8-gpu-h200 上共 8 组多卡 unified radix cache 用例。

结论:ubuntu-latest 单测与多卡 H100/B200/H200 回归全部通过,跨 rank MIN 归约逻辑在真实多卡环境验证正确。 · 已解决

AMD ROCm 7.2 CI 失败未澄清 other

PR body 的 CI 状态显示 AMD ROCm 7.2 运行失败(Run #33325476754),issue 评论与 review 均未涉及。

结论:无法确认是否与本次变更相关,建议后续 PR 跟进澄清。 · unresolved

Review 直接通过 other

huangtingwei9988 对本 PR 给出 APPROVED,无任何 review 评论,说明接口设计与树侧接入方案在内部评审中无争议。

结论:已合入,无遗留 review 意见。 · 已解决

风险与影响

  1. 热路径分支增加:match_prefixinit_load_backcheck_hicache_events_apply_cache_actionrelease_aborted_request 都新增了守卫分支。缺省 linker=None 时零开销,但 check_hicache_events 的 linker 分支直接 return,意味着挂载后原有 HiCache 事件轮询(ack_write_queue、ack_load_queue)整体停用,未来若出现「外部存储 + host tier」共存需求需显式校验互斥。
  2. 锁生命周期依赖异常处理:pending_loadspending_offloads 通过 inc_lock_refdec_lock_ref 机制 pin 住节点,任何异常路径漏调 dec_lock_ref 都会泄漏锁;测试覆盖了 cancel 与失败回滚,但真实后端 DMA 失败路径尚无验证。
  3. 跨 rank 一致性假设:_sync_restorable_prefixcheck_hicache_events 依赖 _all_reduce_attn_groups 的 MIN 归约并要求各 rank 同步调用,PP 长流水线或 CP 组合下的额外同步等待需后续性能评估。
  4. 全局副作用:UnifiedCacheLinkerWrapper.__init__ 无条件把 write_through_threshold 改为 1,是对共享 cache 对象的全局改写。
  5. CI 缺口:AMD ROCm 7.2 运行失败未澄清;本 PR 无真实后端接入,接口稳定性有待后续 backend PR 验证。

对用户:无直接可见功能变化——linker 缺省为 None,所有钩子短路,现有 HiCache 路径行为不变。对系统:UnifiedRadixCache 核心热路径新增 8 处守卫分支,后续所有 cache 相关修改都需同时考虑“挂载/未挂载”两种模式;check_hicache_events 形成双轨事件轮询。对团队:这是外部缓存链路系列的关键基座,为后续真实后端落地提供稳定接口,_FakeLinker + 多卡回归构成可靠安全网,可支撑后续并行开发。影响程度:中,属于基础设施分层改动,非线上行为变更。

热路径新增守卫分支 check_hicache_events 双轨互斥 锁生命周期依赖异常处理 跨 rank 一致性依赖 all-reduce AMD ROCm CI 失败未澄清 缺真实后端端到端验证

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论