Prhub

#19746 [P/D disagg] - support decode side radix cache

原始 PR 作者 ishandhanani 合并时间 2026-05-01 21:55 文件变更 24 提交数 95 评论 207 代码增减 +993 / -138

执行摘要

PD 拆分 decode 端支持 radix 缓存,减少 KV 传输

在PD拆分中,decode节点每轮都要接收完整的KV前缀,导致带宽浪费和TTFT居高不下。通过在decode侧启用radix缓存,共享前缀可被本地缓存,decode只请求新增KV页面,显著降低传输延迟和内存占用。

建议所有从事PD拆分和KV缓存优化的工程师仔细阅读该PR。关键设计决策包括:(1)lock_ref平衡策略及其失败路径处理;(2)将decode prefix handoff迁入kv_manager state以避免调度线程遍历transfer_infos;(3)页面对齐和单调游标维护以防协议错误。该PR为后续扩展(Mooncake支持、批处理eviction、retraction支持)奠定了良好基础。

讨论亮点
  • Transfer失败路径lock_ref平衡(@yudian0504):总结四种场景的lock_ref变化,要求验证所有路径。作者通过单元测试覆盖成功和失败路径,确认无泄漏。(已解决)
  • 非NIXL后端兼容性(@ShangmingCai):询问Mooncake等其他后端是否支持。作者暂时仅允许NIXL并添加ERROR,后续PR添加支持。(已解决)
  • Retraction与radix缓存交互(@cctry):指出retraction需要正确处理前缀匹配,目前未实现。作者添加TODO,计划后续PR添加。(待处理)
  • Warmup垃圾KV处理(@cctry):warmup请求使用伪造bootstrap地址,产生的KV应该被丢弃。作者在warmup结束后flush radix缓存解决。(已解决)

实现拆解

  1. decode调度器前缀匹配与锁定:在pop_preallocated中调用新增的_match_prefix_and_lock,通过match_prefix_for_req匹配decode侧radix树并锁定匹配节点,防止驱逐。获取prefix_len后用于预分配预算计算,并传给_pre_alloc初始化extend输入。

  2. prefill端增量传输:修改pop_bootstrapped,通过KV管理器获取decode_prefix_len,计算待发送页数(总页数-前缀页数)。在send_kv_chunk中保持发送游标单调递增,当游标超过分块边界时发送空块。add_transfer_requestkv_indices为空时跳过RDMA传输,但辅助数据仍然发送。TransferInfo新增decode_prefix_len字段,修正is_dummy方法以区分全命中与CP非权威rank。

  3. 通用工具函数:在common.py新增kv_to_page_indiceskv_to_page_numpage_align_floor,将原本在disaggregation/utils.py中的函数通用化。新增maybe_cache_unfinished_req包装cache_unfinished_req,支持skip_radix_cache_insert标记。调整release_kv_cache在cache finished时使用该标记。

  4. CLI与兼容性:在server_args.py新增--disaggregation-decode-enable-radix-cache布尔标志。decode模式下启用该标志会禁止disable_radix_cache,当前仅允许与nixl后端组合,并对speculative decoding、Mamba/SSM/SWA模型添加断言阻止。

  5. 测试覆盖:新增单元测试test_decode_radix_lock_ref.py验证四种transfer场景下lock_ref平衡;新增分布式集成测试test_disaggregation_decode_radix_cache.py在8-GPU H20 CI上多轮缓存命中测试;更新CI脚本安装NIXL。

文件 模块 状态 重要度
python/sglang/srt/disaggregation/decode.py 解码调度 modified 8.62
python/sglang/srt/mem_cache/common.py 缓存工具 modified 7.91
python/sglang/srt/disaggregation/nixl/conn.py 传输协议 modified 7.91
python/sglang/srt/server_args.py 配置管理 modified 6.92
python/sglang/srt/managers/schedule_policy.py 调度策略 modified 7.15
test/registered/unit/mem_cache/test_decode_radix_lock_ref.py 单元测试 added 7.76

关键符号

_match_prefix_and_lock _pre_alloc _required_alloc_tokens match_prefix_for_req kv_to_page_indices kv_to_page_num page_align_floor maybe_cache_unfinished_req TransferInfo.is_dummy TransferInfo.from_zmq pop_decode_prefix_len should_send_kv_chunk

关键源码片段

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

新增通用工具函数(kv_to_page_indices、page_align_floor 等)并调整 release_kv_cache 以支持 skip_radix_cache_insert 标记。

def kv_to_page_indices(kv_indices: np.ndarray, page_size: int):
    # 页面除最后一块外保证是完整的
    if page_size == 1:
        return kv_indices
    return kv_indices[::page_size] // page_size
​
​
def kv_to_page_num(num_kv_indices: int, page_size: int):
    return (num_kv_indices + page_size - 1) // page_size
​
​
def page_align_floor(length: int, page_size: int) -> int:
    return (length // page_size) * page_size
​
​
def maybe_cache_unfinished_req(req: Req, tree_cache: BasePrefixCache, **kwargs):
    # 当标记为跳过 radix 缓存插入时(例如 decode radix cache 场景),直接返回
    if getattr(req, "skip_radix_cache_insert", False):
        return
    tree_cache.cache_unfinished_req(req, **kwargs)
python/sglang/srt/disaggregation/nixl/conn.py core-logic

传输协议扩展:TransferInfo 添加 decode_prefix_len,add_transfer_request 支持空 KV 传输跳过,修正 is_dummy 方法。

@dataclasses.dataclass
class TransferInfo:
    """传输信息,包含从 decode 发送到 prefill 的索引和参数。"""
    room: int
    endpoint: str
    dst_port: int
    agent_name: str
    dst_kv_indices: npt.NDArray[np.int32]
    dst_aux_index: int
    required_dst_info_num: int
    dst_state_indices: List[int]
    decode_prefix_len: Optional[int] = None # 新增:decode 侧已缓存的前缀长度
​
    def is_dummy(self):
        # CP 非权威 rank 的传输为 dummy;
        # 但当 kv_indices 为空且 decode_prefix_len > 0(全命中)时不是 dummy
        if self.dst_kv_indices.size == 0 and self.decode_prefix_len:
            return False
        return self.dst_kv_indices.size == 0
​
    @classmethod
    def from_zmq(cls, msg: List[bytes]):
        # 解析 state_indices
        dst_state_indices = []
        if len(msg) > 7 and msg[7] != b"":
            dst_state_indices = list(np.frombuffer(msg[7], dtype=np.int32))
        return cls(
            room=int(msg[0].decode("ascii")),
            endpoint=msg[1].decode("ascii"),
            dst_port=int(msg[2].decode("ascii")),
            agent_name=msg[3].decode("ascii"),
            dst_kv_indices=np.frombuffer(msg[4], dtype=np.int32),
            dst_aux_index=int(msg[5].decode("ascii")),
            required_dst_info_num=int(msg[6].decode("ascii")),
            dst_state_indices=dst_state_indices,
            decode_prefix_len=(
                int(msg[8].decode("ascii")) if len(msg) > 8 and msg[8] != b"" else None
            ),
        )

评论区精华

Transfer 失败路径 lock_ref 平衡 正确性

yudian0504 总结了四种 transfer 场景的 lock_ref 变化,要求验证所有路径的正确性,包括增量 / 全量 transfer 的成功和失败情况。

结论:作者通过新增单元测试 `test_decode_radix_lock_ref.py` 覆盖四种场景,确认 inc/dec_lock_ref 平衡且无泄漏。 · 已解决

非 NIXL 后端兼容性 设计

ShangmingCai 询问 Mooncake 等其他后端支持计划,作者表示暂时仅支持 NIXL,并添加 ERROR 禁止其他后端。

结论:当前仅允许 NIXL 后端;Mooncake 支持将在后续 PR 中添加。 · 已解决

Retraction 与 radix 缓存交互 正确性

cctry 指出 retracted 请求重新调度时未匹配前缀,可能导致 KV 泄漏或错误。

结论:作者确认未实现,并添加 TODO 注释,计划后续 PR 处理。 · acknowledged

Warmup 垃圾 KV 处理 正确性

cctry 问 warmup 请求使用伪造 bootstrap 地址,其 KV 不合法,不应插入 radix 树。

结论:作者通过 warmup 结束后执行 flush_cache 清除所有 garbage KV(commit a6b6e8b)。 · 已解决

风险与影响

  • lock_ref泄漏风险:如果transfer失败后lock_ref未正确递减,将导致缓存行永久锁定,最终耗尽内存。已通过单元测试覆盖四种场景,但实际部署中仍可能遇到未预见的失败路径。
  • 页对齐不一致:decode和prefill对前缀长度进行页对齐时若算法不同,可能导致传输内容错位。代码在pop_preallocated中使用page_align_floor对齐,并在kv_to_page_indices中处理。
  • 非NIXL后端:当前仅支持NIXL,若其他后端被启用,将因decode_prefix_len参数缺失而崩溃。已在server_args.py中禁止其他后端,但后续添加Mooncake时需额外工作。
  • DP attention命中率:在DP模式下,如果同一对话被路由到不同rank,缓存命中率将大幅下降。需要外部路由器支持prefix-aware路由。
  • eviction未批处理:当前每个请求独立执行eviction,可能在高负载下成为性能瓶颈,计划后续批处理优化。
  • 用户影响:新增CLI选项,用户可在PD拆分decode节点上启用radix缓存,获得显著的TTFT降低和吞吐提升(实测TTFT P50降低8.1倍,吞吐提升1.32倍),但ITL可能因batch变大而退化。
  • 系统影响:decode节点内存利用率提高(更多请求可同时运行),但增加的batch size可能增加解码延迟。对prefill节点影响有限,仅减少发送的KV页面数。
  • 团队影响:核心团队需为Mooncake等后端适配同样功能;调度团队需考虑prefix-aware路由支持;测试团队需维护新增的分布式测试。
核心路径变更 lock_ref 泄漏风险 非 NIXL 后端未支持 eviction 未批处理

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论