KV Cache 驱逐要不要上 Radix Tree?以 Mooncake模拟一下

最近在学习KV Cache,看到Mooncake 里一个所见略同的关于索引结构的 RFC:要不要在 Store 侧维护 radix 辅助索引,让缓存驱逐满足这个约束:

ini 复制代码
children alive => parent alive

翻译成人话就是:如果某个子前缀还在缓存里,那它的父前缀也应该还在。

这个约束听起来很合理。prefix cache 本来就依赖"连续前缀"。如果父 block 已经被驱逐,只剩下后面的 suffix block,那这些 suffix KV 很可能用不上。缓存还占着,但实际复用不了。

但问题在于合理,并不代表值得做。

维护 radix index 有成本。驱逐时可能要扫更多候选,Store 侧还会有锁、元数据内存、HA replay、prefix query 这些问题。于是我用真实数据集跑了一轮模拟,想先回答一个更窄的问题:

只看 eviction policy,radix-aware eviction 到底能换回多少收益?

先说结论:

  • radix_lru 不建议上,toolagent trace 上比 LRU 更差
  • radix_cost_aware 有价值,但更适合作为 workload-gated policy
  • 默认策略仍然应该是 approximate LRU
  • 真正进生产前,还要单独测 Store 侧锁、内存和 prefix-query 成本

背景:为什么 orphaned suffix 是个问题

Prefix cache 的核心不是"某个 block 在不在缓存里",而是"从开头开始,连续命中了多长"。

假设一个请求前缀是:

rust 复制代码
A -> B -> C -> D

如果缓存里有 CD,但 AB 已经没了,那普通 prefix reuse 场景下基本不能直接从 C 开始复用。除非系统支持更复杂的 selective recomputation,否则后面的 suffix KV 不能算真正命中。

这类状态我在报告里叫 orphaned suffix

普通 LRU 很容易产生这个问题。它只看对象粒度的 recency,不知道 prefix tree 的父子关系。Radix-aware eviction 想解决的就是这个:驱逐时尽量不要留下"子还活着,父没了"的结构。

策略的完善

一开始我没有直接跑数据。先修了本地模拟器里一个会影响结论的问题。

之前的 radix index 在 block eviction 后没有 unregister。也就是说,block 已经不在 cache 里了,但 radix tree 里还留着路径。这样跑出来的 radix_lruradix_cost_aware 会带着陈旧索引做决策,结果不能信。

这次主要修了几类东西:

  • eviction 后从 radix tree 删除路径
  • 增加 orphaned_suffix_ratio
  • cache_hit_depth 改成严格 prefix hit
  • 同时间戳事件按调度顺序处理
  • eviction tie-break 固定,保证 trace replay 可复现

其中最重要的是 strict-prefix hit。

旧口径容易把"这个 block 还在缓存里"算成命中。新口径只认最长连续前缀。父 block 缺了,后面的 block 不再算可复用 KV。这个口径更接近 vLLM、SGLang、Mooncake 这类 prefix cache 系统实际关心的东西。

实验验证

实验采用 Mooncake FAST'25 的三份 trace:

  • conversation
  • toolagent
  • synthetic

它们都有 hash_ids,可以保留 prefix/cache block identity。这个很关键。只有 token 长度的 trace,比如 Azure Splitwise 或 BurstGPT,可以看流量形态,但不能直接拿来判断 prefix reuse。

对比三个策略:

策略 含义
lru baseline,近似 LRU
radix_lru 保持 radix 父子约束的 leaf-first LRU
radix_cost_aware 在 radix 结构上考虑共享度和复用价值

注意,这不是生产 Store benchmark。

这轮模拟不测:

  • Master lock contention
  • metadata memory
  • HA replay
  • GetKeysByPrefix latency

它只看 eviction policy 在模拟器里的行为。

结果 1:radix 约束确实能消掉 orphaned suffix

这个结果比较符合预期。

在三份 FAST'25 trace 上,两个 radix 策略都把 orphaned_suffix_ratio 打到了 0。普通 LRU 会留下大约 0.015 到 0.022 的 orphaned suffix。

主表里几行数据是这样的:

workload policy strict hit orphaned_suffix_ratio eviction-induced prefill
conversation lru 0.205 0.022 1178
conversation radix_lru 0.204 0.000 335
conversation radix_cost_aware 0.204 0.000 335
toolagent lru 0.611 0.015 1200
toolagent radix_lru 0.587 0.000 1673
toolagent radix_cost_aware 0.616 0.000 969

conversation trace 上,radix 策略把 eviction-induced prefill 从 1178 降到 335。这个收益很明显。

但注意,它没有提高 strict hit。conversation 上三个策略的 strict hit 都在 0.204 到 0.205 附近。也就是说,radix 主要减少了 eviction 带来的 recompute damage,不是直接把 hit rate 拉上去了。

结果 2:radix_lru 在 toolagent 上劣化

toolagent trace的结果令人意外。一开始我以为 radix_lru 至少会是个"保守但稳定"的方案。结果不是。

在 toolagent full trace 上:

指标 LRU radix_lru
strict hit 0.611 0.587
eviction-induced prefill 1200 1673
TTFT p95 17.14 ms 22.37 ms

radix_lru 确实消除了 orphaned suffix,但它让 hit 更差,recompute 更多,TTFT 也更差。

toolagent 流量的 prefix reuse 更集中,哪些 prefix 值得保护很重要。只按 leaf-first 的方式维护树结构,可能会保护错东西。结构正确,不等于策略正确。

这点挺关键。很多时候我们会把数据结构的"正确性"误读成策略收益。但在这里不成立。

结果 3:radix_cost_aware 存在收益

radix_cost_aware 在 toolagent 上就好不少:

指标 LRU radix_cost_aware
strict hit 0.611 0.616
eviction-induced prefill 1200 969
TTFT p95 17.14 ms 19.09 ms
radix_nodes_avg 1664 370

cost-aware的意义在于:保护共享度高、复用价值更高的 prefix,而不是机械地按 leaf-first 做 LRU。

然而,toolagent 上,radix_cost_aware 的 TTFT p95 还是比 LRU 差。overhead proxy 也不低:大约 591 次 candidate scan 换 1 次 saved eviction-induced prefill。

这提醒我们:Store 侧真做 radix eviction,不能只看 cache hit,还要看驱逐路径本身是不是太重。

容量 sweep:收益只在某些压力区间明显

我又对 toolagent 跑了一组容量 sweep:

bash 复制代码
10, 40, 160, 640, 2560 blocks/node

结果挺有意思。

blocks/node policy strict hit TTFT p95 eviction-induced
40 lru 0.611 17.14 1200
40 radix_lru 0.587 22.37 1673
40 radix_cost_aware 0.616 19.09 969
160 lru 0.546 39.26 1403
160 radix_cost_aware 0.550 39.21 1309
2560 lru 0.559 64.28 921
2560 radix_cost_aware 0.561 63.45 985

40 和 160 blocks/node 下,radix_cost_aware 比较有价值。到了 2560 blocks/node,它的 strict hit 和 TTFT 略好一点,但 eviction-induced prefill 反而更高:985 vs 921。

所以这不是一个"容量越大越好"的故事。更准确地说,它在中等压力、prefix reuse 结构明显的时候比较有用。压力太低,收益变小。压力太极端,也可能选错保护对象。

还有一个 caveat:不同 policy 下 admission 会有轻微差异,所以 TTFT 和 hit depth 是在各自 completed requests 上算的。小幅 delta 不能过度解读。要更严格的话,后面应该加 matched-request 或 all-arrivals 分析。

结论

这轮模拟之后,初步的判断是:

radix_lru 不值得做默认策略。它解决了 orphaned suffix,但在 toolagent 上把更重要的指标搞差了。

radix_cost_aware 可以继续推进,但它应该是 gated policy。系统要先判断当前 workload 是否有足够强的 prefix reuse,再决定是否启用。直接替换 LRU 风险太大。

LRU 继续做默认值。它不完美,会留下 orphaned suffix,但简单、高效、稳定。

至于 Store 侧 radix auxiliary index,要不要进生产,还缺模拟器给不了的东西:

  • prefix query 延迟
  • Master / Store 锁竞争
  • 元数据内存
  • HA replay 成本

这几个必须用更贴近 Store 的 benchmark 测。Mooncake-Sim 能帮我们排除一些坏方向,比如 plain radix_lru。但它不能替生产系统做决定。

本次最大的收获是:不要把"数据结构上更正确"直接等同于"调度策略上更好"。先让策略变聪明,再决定数据结构值不值得进主路径。

相关推荐
阿牛哥_GX1 小时前
Angular 页面自动化进阶:Select2 组件与表单提交
人工智能
橘子星1 小时前
🤖 从零造一个AI程序员:30行核心代码拆解Cursor背后的Agent原理
人工智能
大鱼>1 小时前
AI+货物追踪:智能快递柜追踪系统
人工智能·深度学习·算法·机器学习
程序喵大人1 小时前
【AI专栏】图解Transformer - 第04章:LLM生成
人工智能·深度学习·llm·transformer
1234567890@world1 小时前
知识管理 | 数字化 | APQC
大数据·数据库·人工智能
千维百策6661 小时前
AI 编码智能体平台如何提升研发效能:从 CI 修复到工程工作流自动化
人工智能·ci/cd·自动化
以和为贵1 小时前
🔥前端也能搞懂流式输出:从 SSE 到打字机效果
前端·人工智能·架构
sunywz2 小时前
【AI RAG知识库】06.【导入】【Web服务集成】
人工智能
zeng_jun_yv2 小时前
测试 Agent 评测体系详细方案
人工智能