Skip to content

Optimize cache-aware PD routing with a compressed radix tree - #1626

Open
sufubao wants to merge 3 commits into
ModelTC:mainfrom
sufubao:fix/pd-cache-long-prompt
Open

sufubao wants to merge 3 commits into
ModelTC:mainfrom
sufubao:fix/pd-cache-long-prompt

Conversation

@sufubao

@sufubao sufubao commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

长 prompt 的采样路径超过 Python 递归上限时,cache-aware PD 选点的插入和匹配会抛出 RecursionError。本次改为压缩 radix tree,同时消除选点与命中信息查询之间的重复匹配。

长边整体存储与比较,在分叉或插入终点拆分;整条路径刷新后合并不再需要的单子边,避免多轮聊天追加内容形成长节点链。容量仍按采样字符数计算,保留字符级叶到根 LRU 顺序,驱逐时可以裁剪叶边。每次插入只取一次时间戳。选点返回 worker 和同一次匹配生成的命中信息,负载均衡改派后仍返回零命中率,不共享跨请求的匹配状态。

单字符边直接复用 children 查找的匹配结果,跳过通用前缀比较;输入 key 长度在循环外计算,避免高分支密度下压缩树退化为单字符链时的额外开销。

最新提交 b88fc7c3a 的正确性复测(两个 sub-agent 独立执行正确性与性能验证):

  • 22 个 cache-aware 单测通过,覆盖超递归深度、ASCII/Unicode 分叉、内部前缀、时间戳、容量裁边、LRU、多轮追加和单次匹配。
  • 独立 oracle 穷举 15,309 个操作序列:45,927 次插入、1,423,737 次查询、15,309 次显式驱逐,全部一致。
  • upstream 差分:43,200 次插入、216,000 次查询,比较展开后的逐字符 worker、时间戳、LRU、容量及驱逐结果;每次插入固定时钟,验证单次取时语义。另有 1,800 次多轮增长/内部前缀/完整刷新插入通过。
  • 8 线程、7 组容量配置,共 23,200 次插入、46,400 次查询、23,200 次显式驱逐;按树锁内顺序在 upstream 回放一致。
  • 1,746 个有效随机策略用例选点与缓存信息一致。匹配后强制并发改写并驱逐缓存,仍返回同一个完整快照且只匹配一次;历史 worker 下线回退通过。
  • Black、Flake8 按仓库配置通过。此前扩大 PD 回归为 111 通过、6 个日志捕获断言失败;未修改的 upstream 同环境也复现这 6 项失败,此次未重复扩大该套件。

单字符快速路径 CPU A/B/C(b88fc7c3a):

基线为 d6a34eb,涉及的三个 selector 文件与 upstream/main 3d9415f44 完全一致;修复前为 3d902a7。Intel Xeon Platinum 8568Y+、Python 3.12.9、默认 sample_stride=512,固定同一 CPU,3 轮预热后交错测量 7 轮取中位数;通过 exp 保存源码哈希、补丁和结果。使用真实 select_p_d_node()、4 个模拟 prefill worker,保留日志格式化但关闭输出。

以下通过公开插入接口构造每个采样点都有分叉的边界树形;恰好一个 worker 空闲,旧版和新版都只匹配一次:

Prompt 字符数 upstream 修复前 PR 单字符快速路径 相对 upstream 提速
65,536 17.02 µs 27.34 µs 13.06 µs 1.30×
524,288 145.31 µs 203.85 µs 78.56 µs 1.85×
1,536,000 449.93 µs 599.69 µs 233.15 µs 1.93×

该场景相对 upstream 的性能退化已消除。高分支密度和第三采样字符失配的 16 个完整 selector 场景,选中 worker 与估算命中率在三组实现中一致。另有 16 个完整 selector 场景覆盖单压缩边完整匹配和根失配。36 个树微基准同时覆盖短串、长压缩边、根失配、浅失配、深失配、碎片化树和重插入。

快速路径判断在部分长压缩边匹配微基准中增加约 0.03–0.10 µs;原有浅失配相对 upstream 的小幅开销仍存在,不能据此声称所有树形和请求均更快。

以下常规路径数据采集于 3d902a7,保留作为压缩 radix tree 与单次匹配复用的初始测量;本次快速路径的三组对照见上表。

速度基线为上述 upstream/main 3d9415f44,不是 PR 原来的迭代版本。Intel Xeon Platinum 8568Y+、Python 3.12.9、sample_stride=512,固定 CPU,3 轮预热后交错测量 9 轮取中位数;所有实验通过 exp 记录。

完整 CPU 选点使用真实 LoadBalancedCacheAwareSelector.select_p_d_node 和 4 个模拟 prefill worker,包含 decode 加权抽样与命中信息生成。两边关闭日志输出,保留字符串格式化成本。下表为 ASCII prompt 的 upstream/本次实现耗时倍率:

Prompt 字符数 空闲节点 全忙节点 命中节点过载改派 未命中
4,096 1.35× 1.32× 1.33× 1.16×
65,536 5.97× 4.96× 5.10× 1.18×
524,288 43.29× 37.73× 38.39× 1.30×
1,536,000 93.80× 74.06× 85.63× 1.37×

1,536,000 字符空闲场景从 878.30 µs 降至 9.36 µs,全忙场景从 881.88 µs 降至 11.91 µs。

单路径前缀树微基准,524,288 字符 / 1,024 个采样字符:

操作 upstream 本次实现 提速
新建插入 1.194835 ms 0.003382 ms 353×
重插入 0.540738 ms 0.003139 ms 172×
完整匹配 0.138462 ms 0.002353 ms 58.8×
插入并驱逐 3.069226 ms 0.006222 ms 493×

64 条随机分支、共享前 16 个采样字符、每条深度 1,024 时,新分支插入快 104×,重插入快 79.9×,完整匹配快 25×,半深度匹配快 8.6×。逻辑采样字符数同为 64,528,实体节点从 64,528 降至 65;tracemalloc 测得树的保留内存从 20,654,896 B 降至 160,049 B,约减少 129×,输入文本不计入。

256 轮追加式聊天,每轮追加 4 个采样字符并执行插入和匹配,末轮为 524,288 字符:总耗时从 87.640 ms 降至 2.303 ms,快 38.1×,最终保持一个压缩边。5,000 个采样字符的路径在 upstream 默认递归上限 4,000 下插入失败,本次实现插入与匹配成功。

仅命中第一个采样字符的浅匹配仍有约 0.14–0.25 µs 回退,根未命中基本持平。微基准计时不含建树准备,计时期间关闭 GC;较大的倍率来自消除逐字符 Python 节点操作,不能换算为服务吞吐或 GPU 推理提速。

@sufubao sufubao changed the title Fix cache-aware PD routing recursion for long prompts Optimize cache-aware PD routing with a compressed radix tree Oct 14, 2026
@shihaobai
shihaobai requested a review from hiworldwzj October 14, 2026 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant