笔记日期: 2026-08-22 作者: Zhongzhu Zhou 论文标题: CacheRoute: Planned Prefix-Affinity Routing for Large-Scale LLM Serving 论文作者: Huang Cheng (Meta) arXiv: 2608.19677 类型/状态: Preprint (cs.DC),2026 年 8 月
0. 这篇笔记要讲什么、读完能带走什么
这篇笔记会按下面这条主线展开:先建立理解这篇论文所需的两块前置知识(前缀缓存到底是什么、为什么朴素负载均衡在数学上就注定会破坏它),然后逐步拆解 CacheRoute 具体怎么构建路由计划(准入、定容、放置三步,附完整伪代码),再仔细走一遍论文的六组实验(旗舰结果、泛化性检验、组件消融、负面结果、模型驻留预测的失败尝试、突发性鲁棒性),最后给出这篇论文的设计取舍、局限,以及一份独立的批判性评估,并专门拿出一节篇幅梳理论文附录里几个容易被忽略、但信息量很大的补充实验。读完之后,你应该能回答三个问题:CacheRoute 具体解决了什么问题(以及为什么这个问题不是靠”聪明的反应式路由”就能解决的);它的核心机制(准入 + LPT 放置)具体是怎么运作的;以及——这是本文认为最重要的一点——在什么样的工作负载特征下,这套机制反而会让情况变得更糟,而不是更好。
1. 这篇论文到底在纠结什么
如果你负责一套线上 LLM serving 集群,负载均衡这件事听起来应该是最无聊的一环:来了一个新请求,该把它送到 台副本里的哪一台?教科书答案是”谁闲就给谁”——尽量把负载摊平,把尾延迟压下去。这个答案对无状态服务是对的,但对 LLM 推理引擎是错的,因为现代 serving 引擎其实不是无状态的。vLLM 的 PagedAttention、SGLang 的 RadixAttention 这些系统都会把对话前缀算出来的 KV(key/value)张量缓存下来,这样同一个前缀再次出现时(多轮对话的下一轮、重复的 system prompt、反复被检索出来的上下文块),引擎就可以跳过重新计算,直接复用——这是实打实的延迟和算力节省,业内叫”前缀缓存”(prefix caching)。
问题在于:这份缓存的 KV 状态是留在某一台具体机器上的。如果负载均衡器把同一个对话的连续请求撒到不同的副本上——而”哪个空闲就发给谁”这种再自然不过的分散策略恰好就会这么干——那么任何一台机器上的缓存基本上就废了,因为下一次同样前缀的请求大概率不会再落到这台机器。结果就是缓存命中率低、重复的 prefill 计算,即使你整个集群理论上的显存总量足够缓存所有东西。
显而易见的补救办法是”亲和性”:把某个对话(或者某个业务、某个租户)固定路由到同一台机器,这样它的缓存能稳定地预热并保持热。但纯亲和性也有一个镜像的失败模式。如果不同对话之间的请求速率是倾斜的——现实里几乎永远是倾斜的——那么简单粗暴地固定路由,就是把这种速率倾斜直接映射成了服务器队列的倾斜。最忙的那个对话决定了它所在机器队列的长度,而这台机器的尾延迟就变成了整个集群的尾延迟,不管另外 29 台机器有多闲。
于是你被夹在两种看似相反、根源却相同的失败模式之间——两种策略都没法同时感知”缓存局部性”和”负载”:
- 对缓存无感的均衡(比如 power-of-two-choices):负载摊平了,局部性没了,已经付出的 prefill 计算白白扔掉。
- 朴素的固定亲和(比如普通一致性哈希):局部性保住了,但单个热点 key 就能压垮它被固定绑定的那台机器,把整个集群的 p99 拖下水。
CacheRoute 的做法出乎意料地朴素:不要试图在请求级别、实时地解决这个问题。而是周期性地根据测量到的每个 key 的请求速率,离线算出一份路由计划——这份计划同时决定 (a) 哪些 key 的请求量”够热”,值得给一个稳定的、对缓存友好的固定分配,以及 (b) 在发出任何请求之前,就把这些分配摆放得让各台机器的期望负载保持均衡。因为规划环节完全不在请求路径上,它可以负担一个实时路由器负担不起的精确账本式计算。
这篇论文真正值得细读的地方,不在于单个技术点有多新——论文自己就明说,top-rate 准入和 LPT (longest-processing-time) 调度都是教科书级别的老算法。真正有价值的是它的测量态度:一个 60 卡 GPU、70B 参数规模的、贴近生产环境的大规模评测,一个专门用来验证结果是否能泛化的独立第二工作负载,以及——对系统论文来说非常罕见——一份明确量化的、“这套方法在哪些场景下会反而输”的清单。第 5.3 节里那两个 32B 工作负载上亲和性反而拖累容量的实验,是全文最有价值的一个贡献,本笔记会花不少篇幅认真讨论它。
2. 前置知识
2.1 前缀缓存到底省了什么,又为什么脆弱
自回归 Transformer 解码器处理一个请求分两个阶段:prefill(把整段输入 prompt 并行处理一遍,产出第一个输出 token,同时顺带算出所有输入 token 在每一层的 KV 张量),以及decode(逐个生成输出 token,每个新 token 通过 Transformer 架构里的因果自注意力机制,回看之前所有已经算好的 KV 张量)。Prefill 的注意力计算代价随 prompt 长度的平方增长,前馈层部分则是线性增长;对于长度在几千 token 量级的 prompt(多轮对话加 system prompt 加不断增长的历史很容易到这个量级),prefill 是总服务成本里不可忽视的一大块。
前缀缓存(vLLM 的 PagedAttention 率先在工程上把这件事落地,SGLang 的 RadixAttention 进一步完善)利用了一个观察:如果两个请求共享一段完全相同的 token 前缀,那么这段共享前缀对应的 KV 张量完全一样,因为 Transformer 对第 个 token 的注意力计算只依赖 这些 token。所以引擎与其每次都重新计算共享前缀的 KV 张量,不如把它们缓存起来(通常以分块的”页”结构组织),直接复用,只对新增的后缀 token 重新算 KV。对于一个每轮都在稳定历史后面追加新内容的多轮对话,这把一个随全部上下文长度 增长的 prefill,变成了一个只对新增 token 做 的 prefill——当 时,这是巨大的实际收益。
脆弱之处在于:这份缓存只对算出它的那台机器本地有效。没有什么免费的机制能让 KV 状态在整个集群范围内自动可用——跨机器 KV 传输本身就是一个活跃的系统研究方向(下文相关工作部分会提到 Mooncake、MemServe 这类系统),即便这类系统存在,跨网络传输几个 GB 的 KV 张量也不是零成本的。绝大多数生产环境的默认假设是:如果你想复用某个前缀的缓存,携带这个前缀的请求就必须回到当初算出它的那台机器上。这篇论文的全部前提就建立在这一点上。
2.2 为什么朴素的负载均衡会打败前缀缓存:一个粗略的量级估算
考虑一个”业务”(论文里对路由 key 的称呼——同样可以是一个租户、一个对话线程、一个应用)以恒定速率 请求/秒到达,并被 power-of-two-choices 或类似策略均匀地负载均衡到 台机器上。因为在某种分散策略下,这个业务的每个请求(近似)等概率地落到 台机器中的任意一台,那么同一台机器连续两次被这个 key 访问之间的平均时间间隔近似满足:
这是一个简单的更新过程(renewal process)论证:如果请求以速率 近似泊松到达,且被独立地以概率 分配到某台机器,那么落到某一台特定机器上的子流也近似是速率为 的泊松过程,于是该机器上连续两次到达之间的期望间隔就是 。
这个结论反直觉但很关键:扩大集群规模(增大 )来应对更多负载,反而会主动让前缀缓存变得更差,因为它拉长了某个前缀的缓存条目被再次访问之前的期望等待时间。如果这个重访时间超过了引擎实际的 KV 缓存驱逐时间(由内存压力和大多数引擎采用的类 LRU 驱逐策略决定),缓存条目在被复用之前就已经被清掉了——不管你的集群总缓存容量有多大,每个请求都要付全额 prefill 的代价。图 1 把这个关系画了出来。

这是论文第 2.2 节的核心论证,也是撑起后面一切的概念内核:一个纯反应式、对缓存无感的均衡器在结构上就无法在规模化下保持复用,不管它的负载均衡启发式设计得多聪明,因为问题的本质不是”每个请求挑一个最好的目的地”,而是”任何一台机器被重新访问的统计频率”。
2.3 LPT(最长处理时间优先)调度——CacheRoute 借用的经典算法
LPT list scheduling(Graham, 1969)是调度理论里最古老的结果之一。场景设定:你有一批已知处理时长的任务,以及 台相同的机器;目标是把任务分配到机器上,让最晚完成的那台机器的完成时间(makespan)尽量小。贪心的 LPT 启发式做法是:按任务大小从大到小排序,依次把每个任务分给当前总负载最轻的那台机器。Graham 的经典结果是一个最坏情况近似比:LPT 的 makespan 永远不会超过最优解的 倍——对于一个近乎朴素的贪心算法来说,这是一个很强的理论保证。
CacheRoute 完全借用了这条贪心规则,但论文非常谨慎地标注(第 3.1 节以及表格上下文)Graham 的近似比并不能原样套用到 CacheRoute 的场景,因为 CacheRoute 里的”任务”不是原子的:一个热点 key 可以被拆分到多个目的地(即所谓的复制),而且分配还要满足”不同目的地”约束(同一个 key 的多个副本必须落在不同机器上)。这是一处诚实且容易被忽略的说明——很多系统论文借用一个经典算法的名字,并隐含地借用了它的理论保证;这篇论文明确声明理论保证不适用,同时保留了机制本身,这在你没法验证证明是否能迁移的时候,是学术上更正确的做法。
2.4 把三种策略放在同一张坐标图里理解
在正式进入方法之前,先用一张对照表把第 1 节提到的三类策略——对缓存无感的均衡、朴素固定亲和、以及 CacheRoute 试图做到的联合规划——放在一起,明确它们各自在”局部性”和”负载均衡”这两个维度上分别做出了什么取舍:
| 策略类型 | 局部性(缓存命中) | 负载均衡 | 典型失效场景 |
|---|---|---|---|
| 对缓存无感的均衡(如 Flat-LB) | 差——请求被打散,前缀很少能命中同一台机器的缓存 | 好——天然把负载摊平 | 工作负载里有大量可复用前缀时,白白浪费缓存能力 |
| 朴素固定亲和(如 Sticky) | 好——同一个 key 稳定落在同一台机器 | 差——速率倾斜直接映射成队列倾斜 | 存在少数几个远超平均速率的热点 key 时,尾延迟被这些热点支配 |
| 联合规划(CacheRoute) | 好——已准入 key 稳定路由 | 好——LPT 主动平衡已准入 key 的期望负载 | 可恢复的前缀工作本身很少时(第 5.4 节的 32B 负面案例),联合规划也无力回天 |
这张表最后一行是全篇论文最容易被忽视、却最重要的一点:CacheRoute 解决的是”如何在局部性和均衡之间找到一个联合最优点”这个问题,但它没有、也不可能解决”这个工作负载里到底有没有值得保留的局部性”这个更上游的问题——如果答案是”没有”,那么无论路由算法设计得多精巧,都不会有实质性收益,这也是为什么论文把大量篇幅花在了”如何提前测出这个答案”(也就是第 5.5 节的影子回放建议)上,而不是一味地宣传算法本身。
3. 方法:CacheRoute 到底怎么构建路由表
3.1 规划目标
CacheRoute 的任务是构建一张表 ,把每个业务 key 映射到 台可用机器中的一个或多个,并周期性地重新计算(每个”控制周期”算一次,不是每个请求算一次)。构建过程分三步,本节会把每一步的推导和直觉都展开,再给出完整的伪代码。
第一步——按负载计算副本数。 对于一个测量速率为 的业务,以及一个经过标定的单机容量上限 (通过实测单台机器延迟/负载拐点得到——也就是这台机器的尾延迟开始爆掉时的 QPS),key 应该被复制到的目的地数量 为:
这里的直觉是直接的容量核算:如果 ,一台机器就够了(),整个 都落在这一台机器上,不比这台机器自己的容量上限差。如果 超出了单机能处理的量, 就恰好增大到——把 平均拆到 台机器上之后,每台机器分到的份额重新回到 以下。要特别注意这个公式不是什么:它不是一个驱逐时间模型,也没有对”下次请求到来时缓存是否真的还在”做任何承诺——它纯粹是一条负载控制规则。论文明确区分了这一点(第 3.1 节),这个区分很重要,因为这正是不那么严谨的论文容易含糊带过、暗示某种缓存驻留保证的地方,而这个方法其实并不提供这种保证。
第二步——预热槽位的准入(admission)。 不是每个业务 key 都能拿到一个稳定槽位;如果你试图给论文主要工作负载里观测到的 128,824 个 key 每个都分配一个独立预热槽位,会大大超出实际缓存容量。所以规划器把整个集群的准入预算记为 个”预热前缀槽位”(其中 是单机槽位预算,根据实测缓存容量标定),把所有 key 按 从大到小排序,贪心地从最热的开始准入,直到累计 即将超过 为止:
这里 指”按排序顺序,速率不低于 的那些 key”。这正是论文贡献清单里说的”top-rate admission”,概念上等价于按价值密度往背包里放东西——只不过这里每个单位的大小(一个预热槽位)都相等,只有价值(速率)在变化,这也是为什么按 简单排序的贪心策略对这个简化版准入子问题来说是最优的(不过论文很谨慎地在第 3.1 节说明,这个”等槽位”假设对每个 key 预热前缀大小不均匀的工作负载并不成立——这是一个真实的局限,下文第 7 节会进一步讨论)。
第三步——LPT 放置。 对每个已准入的 key ,规划器创建 个”任务”,每个任务承担 的期望负载(即把这个 key 的总速率平均拆到它被分配到的几个目的地上)。然后完全照搬第 2.3 节的 LPT 启发式:按速率从大到小依次处理已准入的 key,对每个 key 的 个任务,贪心地分配给当前负载最轻的 台符合条件的目的地(受”不同目的地”约束——同一个 key 自己的副本不能挤在一台机器上),边分配边更新每台被选中机器的累计负载。目的地一开始会带一个”预期冷尾负载”的初始基线,用于估计它们通过第四步的兜底路由要额外承担的未准入流量。
第四步——分发。 一旦表 在这个控制周期内被固定下来,请求时的路由就变成一次廉价的查表:一个已准入 key 的请求会被送到它自己固定集合 里当前最轻的那个成员(一个预先算好的小候选集合,不是全集群搜索);任何未准入的 key 走朴素的、对缓存无感的、全集群范围的 power-of-two-choices 兜底路由。
下面是完整的编号伪代码,对应论文的 Algorithm 1,并把每一行的意图都展开写清楚:
算法:周期性路由表构建
输入: 各 key 的测量速率 {λ_b}
目的地数量 R
单机容量上限 q_cap
总预热槽位预算 C = R · W
1. 对每个 key b:
2. k_b ← max(1, ceil(λ_b / q_cap)) # 公式 2:b 需要几台目的地
3. 结束循环
4. 按 λ_b 从大到小排序所有 key
5. 已准入集合 ← {};累计计数 ← 0
6. 对排序后的每个 key b:
7. 若 累计计数 + k_b ≤ C:
8. 已准入集合 ← 已准入集合 ∪ {b}
9. 累计计数 ← 累计计数 + k_b
10. 否则:
11. 跳过 b(分发时走兜底的 power-of-two-choices)
12. 结束判断
13. 结束循环
14. 初始化 L[r] ← 目的地 r 的预期冷尾负载,对每个目的地 r ∈ [1, R]
15. 按 λ_b 从大到小,对每个已准入 key b:
16. T(b) ← 当前 L[r] 值最小的 k_b 个目的地,
约束条件:这 k_b 个目的地必须互不相同
17. 对 T(b) 里的每个目的地 r:
18. L[r] ← L[r] + λ_b / k_b # 把拆分后的负载记到 r 头上
19. 结束循环
20. 结束循环
21. 输出 T(在这个控制周期内保持固定);已准入 key 在 T(b) 内路由;
未准入 key 在分发时走兜底的 power-of-two-choices。
论文报告了这整套流程的实测构建成本:在 台目的地、128,824 个业务 key 的规模下,构建耗时 345 毫秒,渐进复杂度为 ——主要开销来自初始排序和对每个 key 搜索最轻符合条件目的地的过程(可以用一个覆盖 台目的地的小顶堆实现)。因为这个规划过程是离线运行的,不在真实服务请求的路径上,所以完全负担得起这个成本——尽管这套成本在每个请求上重算一遍会太慢。
3.2 设计选择深挖:为什么是”周期性、固定不变”,而不是”实时响应、每请求重算”?
这是这篇论文里最关键的一个设计决策,值得按”为什么可行/显然的替代方案/边界在哪里”这个框架仔细展开。
为什么可行: 一个实时反应式的路由器(比如下文第 4 节会讲到的 Preble 或 DualMap)只能利用当下这一瞬间能拿到的信息——当前的缓存状态估计、当前的队列长度——来做路由决策,而且这个决策必须足够快,能挂在热请求路径上。这就迫使反应式路由器只能做局部的、贪心的决策,没法同时兼顾全局的所有 key 速率分布。CacheRoute 把这个组合难题里最难的部分(哪些 key 值得拿到稳定分配、如何平衡它们合起来的期望负载)整个挪到请求路径之外,可以负担得起一次 的排序和一次对全体观测速率分布的 LPT 扫描。服务时的开销就退化成一次查表加一个小范围的局部比较——足够便宜,可以放在热路径上而不会带来明显的额外延迟。
显而易见的替代方案: 像 Preble 和 DualMap 那样,基于实时状态持续(或每个请求都)重新计算分配。这种做法对流量模式的突然变化反应更快——一个周期性的计划在重新计算的间隔之间可能会变得过时。事实也的确如此(第 5.4 节的实测结果):一份滞后一个控制周期的计划,在一次模拟的速率漂移实验里,最坏的一次转变损失了 3.0 个百分点的 KV 命中率和 858 毫秒的 p99 延迟。所以”周期性规划为什么可行”这个问题的答案,不是”反应式方法一定更差”——而是一个权衡上的赌注:全局一致、离线优化带来的收益,超过了在重新计算间隔之间稍微过时所付出的代价——前提是重新计算的间隔相对于底层速率分布实际变化的速度足够短。
在哪里失效/边界在哪: 论文在这一点上出奇地坦诚。第 5.4 节的速率漂移实验显示,从头重新计算 LPT 计划会改变 94.5% 的 key-到-目的地分配,即使只有 1.1% 的 key 实际改变了自己的复制数量 ——也就是说,LPT 放置这一步对输入的微小扰动相当敏感,一次不大的速率变化就能导致几乎全量的计划抖动。装上一份新算出来的计划之后,还会在”重新预热”期间出现瞬时的 13.6 个百分点的 KV 命中率下跌,因为原本预热好的目的地失去了分配,新目的地又要从冷启动开始。论文自己的结论(明确写出来的,不是隐含的)是:一次部署应该保留旧计划,直到实测的”过时惩罚”超过安装新计划的”重新预热惩罚”为止——而且他们并没有做一个能缓解这个问题的、感知抖动的重规划器(一个诚实标注出来的缺口,本笔记第 8 节会进一步讨论)。
3.3 为什么”贪心排序”对这个准入子问题恰好是最优的——一个简短的证明直觉
第 3.1 节说”按 从大到小贪心排序、依次准入”这个策略对等槽位版本的准入问题是最优的,这里给出为什么。设总槽位预算是 ,每个 key 需要 个槽位、贡献”价值” (准入它能保留的期望请求速率)。如果所有 都相等(比如 CacheRoute 主工作负载里几乎全部 的情形),这就退化成经典的”分数背包”问题的一个特例——每件物品大小相同,只有价值不同,那么在固定总容量下,贪心地先装价值最高的物品,一定能装出价值总和最大的组合;因为对于任意两个都被选中或都未被选中的物品对调顺序不会影响总容量占用,而对于一个”选了价值低的却漏掉了价值高的”的次优解,总能通过替换成同容量的更高价值物品来严格改进,这就是贪心最优性的标准交换论证(exchange argument)。
但要注意这个最优性结论只在 全部相等时精确成立。一旦引入不同 key 之间 不同的情况(比如某些超热 key 的 ),这就变成了一般的 0/1 背包问题的变体——每个物品的”大小”()不同,贪心按价值排序不再保证全局最优,只能给出一个近似解。论文对此没有做近似比的理论分析,而是完全依赖第 5.2、5.3 节的实测结果来验证这个贪心近似在实践中的效果——这和 LPT 调度那里”用经典算法、但不套用其经典理论保证”的处理方式是一致的态度。
4. CacheRoute 到底在跟谁比
论文对比了五种基线路由策略,分别代表了”亲和 vs 均衡”这条谱线上的不同点,而且全部在同一套评测框架下重新实现以保证公平(论文明确说明这些不是对原始已发表系统的字面复现,只是在匹配的硬件条件下做了行为对齐的重实现):
- Flat-LB —— power-of-two-choices,对缓存完全无感的基线,代表”只管均衡负载,不管缓存”。
- Sticky —— 普通一致性哈希(Karger et al. 1997):纯亲和,完全不感知负载。
- CHWBL —— 带负载上限的一致性哈希(Mirrokni et al. 2018):亲和加一个显式的溢出上限,过热的 key 会把多出来的请求溢出到别处。
- DualMap —— 一个双候选、同时感知缓存和负载的反应式策略(Yuan et al. 2026),代表较新的”聪明”反应式路由。
- Preble —— 一个感知前缀历史和实时负载的反应式策略(Srivatsa et al. 2024),代表最强的反应式对手。
图 2 把从测量遥测数据到每请求分发的完整流水线画了出来,把算法和架构图对应起来:
flowchart LR
subgraph offline["离线,周期性(每个控制周期一次)"]
A["聚合遥测数据:<br/>每个 key 的速率 λ_b"] --> B["Top-rate 准入<br/>(公式 3,贪心背包)"]
B --> C["负载定容<br/>k_b = ceil(λ_b/q_cap)<br/>(公式 2)"]
C --> D["LPT 放置<br/>基于总期望负载"]
D --> E["稳定路由表 T: b → 目的地集合"]
end
subgraph online["在线,逐请求"]
F["携带 key b 的请求"] --> G{"b 是否<br/>已准入 T?"}
G -- "是" --> H["固定 T(b) 中<br/>当前最轻的成员"]
G -- "否" --> I["冷尾流量:<br/>在全集群范围内<br/>做 power-of-two-choices"]
H --> J["模型目的地<br/>(引擎原生前缀 KV 缓存)"]
I --> J
end
E -.->|"查表"| G
图 2(对应论文 Fig. 1,重新绘制):两个时间尺度的架构——一个慢速的、全局优化的离线计划,驱动一个快速的、逐请求的分发决策。 这种”规划一次,分发廉价”的关注点分离设计,正是让后面所有测量结果成立的结构性想法。
5. 实验与结果拆解
5.1 旗舰结果:70B 模型在 60 张 H100 上
主评测在 30 台张量并行度为 2 的目的地(总计 60 张 H100 GPU)上以 fp8 精度服务 Llama-3.3-70B,回放一份贴合真实多租户对话助手流量模式的半合成工作负载:128,824 个匿名业务 key,基尼系数 0.756(高度倾斜——约 4% 的 key 产生了 47% 的请求量,但没有任何单个 key 超过总流量的 0.3%,排除了”直接把最热的那一个 key 特殊处理”这种取巧办法)。
在主要的 3.5 秒 p99 SLO 阈值下,五个配对随机种子的结果:
| 策略 | 服务端 KV 命中率 | SLO 容量 @3.5s(QPS) | p99 @ 100 QPS |
|---|---|---|---|
| Flat-LB | 64.1 ± 1.3% | 42 ± 20 | 5.7 s |
| Sticky | 87.3 ± 2.4% | 30 | 8.5 s |
| CHWBL | 75.6 ± 0.8% | 64 ± 11 | 3.8 s |
| DualMap | 88.7 ± 1.9% | 58 ± 22 | 5.3 s |
| Preble | 72.0 ± 0.7% | 76 ± 11 | 3.8 s |
| CacheRoute | 93.2 ± 0.5% | 176 ± 11 | 1.8 s |
CacheRoute 达到了最强基线(Preble)2.3 倍的 SLO 容量,以及对缓存无感的 Flat-LB 基线 4.2 倍的容量。图 3 展示了容量对比,图 4 展示了共同负载点下的延迟对比(对应论文 Figure 2 的 (a)(b) 两个面板)。


第 5.1 节里对这些数字背后的机制讲得很仔细,值得原原本本讲清楚,因为它把”为什么赢”和”是不是真的赢了”两件事区分开了:Sticky 和 DualMap 都能达到很高的缓存命中率(87.3% 和 88.7%,和 CacheRoute 的 93.2% 相当)——所以单靠缓存局部性并不是决定性因素。但它们在 100 QPS 时的 p99 延迟分别是 8.5 秒和 5.3 秒,远比 CacheRoute 的 1.8 秒差,因为负载一上来,它们的队列失衡就主导了尾延迟。反过来,Preble 和 CHWBL 把队列保持得相对均衡(p99 都是 3.8 秒,还不错),但它们的缓存命中率掉到了 72.0% 和 75.6%——局部性差意味着更多的 prefill 工作,即便队列均衡也会推高延迟。CacheRoute 是对比中唯一同时做到高命中率和低失衡的策略,而这正是它”准入+放置”联合优化目标设计的初衷。
在更宽松的 5 秒 SLO 下,CacheRoute 相对 Preble 的优势收窄到 1.3 倍(180 对 140 QPS);在更严苛的 2 秒 SLO 下,CacheRoute 是唯一能在任何测试负载点通过的策略——五个基线全部在 30 QPS 这个最低测试负载下就已经左删失(left-censored)。这种”优势随 SLO 收紧而变大”的模式很有信息量:CacheRoute 的优势恰好在最重要的地方(严苛、贴近生产的 SLO)最大,并随着门槛放宽而缩小——这种模式更能让读者相信这是一个真实机制在起作用,而不是挑了一个刻意有利的运行点。
5.2 泛化性检验:一份独立收集的第二工作负载
论文没有止步于一个结果,而是在同一套硬件上,用一份独立收集的业务 key 速率分布重复了整个实验。在 top-K128(即只有速率排名前 128 的 key 在真正驱动负载,大致对应主工作负载的有效准入规模)这个活跃集规模下,三个感知缓存的策略(CacheRoute、DualMap、CHWBL)全部打平在 100 QPS——CacheRoute 的优势在这里没有体现出来。但把活跃集扩大到 top-K256,差距就明显拉开了:CacheRoute 维持 160 QPS,而最好的基线(DualMap)只有 100 QPS。论文自己对此的表述很精确、也很难得:这个对比之所以有价值,恰恰因为它没有复现第一张表的每一格数据——优势的出现,具体对应着活跃集规模超出预热槽位分配的那个临界点,也就是准入压力真正开始起作用的时候。相比单纯宣称”我们在第二个数据集上复现了优势”,这种把”正面结果”和”负面结果”配对着讲的方式信息量大得多,因为它告诉你优势出现的条件,而不只是断言优势存在。
5.3 组件消融:把亲和性和放置策略拆开看
论文里机制层面最有说服力的实验,是在更小的 Llama-3.1-8B-Instruct 测试床(30 张单卡 H100)上,注入人为的”鲸鱼”key(专门设计的超热点 key,用来压力测试机制),逐个添加 CacheRoute 的组件:
| 配置 | KV 命中率 | 负载失衡(max/mean) | SLO 容量 |
|---|---|---|---|
| Flat-LB | 56 ± 1.9% | 1.00× | 240 |
| + 仅加亲和性 | 88 ± 1.5% | 3.46× | 240 |
| + 加复制 | 88 ± 1.6% | 2.60× | 240 |
| + 加 LPT(完整 CacheRoute) | 90 ± 1.0% | 1.24× | 500(触顶) |

这张表是论文核心论点最清晰的证据——单靠亲和性或单靠均衡都不够。加上朴素的亲和性,KV 命中率几乎翻了三倍(56% → 88%),但同时把负载失衡推到了 3.46×——而且关键在于,这一步容量根本没有任何改善(仍然卡在 240 QPS),因为朴素亲和性带来的失衡恰好抵消了更高命中率带来的延迟收益。加上按负载比例的复制(把热点 key 拆分到多台目的地)让失衡有所缓解(降到 2.60×),但容量还是没动——没有配合聪明的放置,单纯做复制也帮不上忙。只有加上 LPT 放置——主动均衡这些被拆分出来的子负载该落在哪——失衡才降到了可控的 1.24×,容量也一路跳到了 500 QPS 的测试上限(这是一个右删失结果,意味着真实容量可能更高,只是实验没有把负载推得更远去找到真正的拐点)。这个消融实验值得被称为论文里最重要的机制证据:它明确分离出——起作用的是亲和性和均衡放置的组合,而不是任何单一成分。
5.4 负面结果:亲和性反而帮倒忙的时候
这一节最能体现这篇论文和典型系统论文的报喜不报忧风格的不同,值得完整读一遍。下表(重新画成图 6)报告了三种”运行区间”:
| 区间 | Flat-LB 命中率 | 亲和路由命中率 | 容量倍数 | 结果 |
|---|---|---|---|---|
| 8B 合成鲸鱼工作负载 | 9.3% | 77.0% | 2–6× | 赢 |
| 聚合工作负载 A – 32B | 1.1% | 11.8% | 0.50–0.67× | 输 |
| 聚合工作负载 B – 32B | 0.8% | 8.5% | 1.0× | 打平 |

在两份(去标识化的、半合成的)真实 32B 工作负载上,启用亲和性只能小幅改善 KV 命中率(从大约 1% 提到 8.5%-11.8% 之间),而对于聚合工作负载 A 来说,这点小幅的命中率提升根本不足以抵消亲和性带来的负载失衡——容量实际上下降到了无感均衡基线的 0.50-0.67 倍。这恰好就是论文第 2.2 节的理论论证所预言的失效模式:如果一个工作负载里大部分请求实际上并没有值得保留的可复用前缀(即使在完美亲和的条件下命中率也很低),那么亲和性在缓存层面几乎买不到什么好处,却还是会把负载集中到特定的目的地上——结果严格地比单纯做负载均衡更差。
论文从这里得出的结论毫不含糊,也难得地没有被软化处理:模型规模本身并不能区分这些结果——70B 和 32B 在不同工作负载上都出现过正面结果,而单独 32B 也同时出现了负面和打平的结果,所以”模型越大亲和性效果越好”这种推断是从这份数据里得不出来的,论文特意花笔墨提前排除了这个错误推断。
5.5 为什么论文明确拒绝构建一个解析式的驻留预测模型
第 5.3 节里有一段(原文标题是”Why we do not predict residency analytically”)报告了一个作者做过、但选择不继续深入的实验:他们试图针对实际的 70B fp8 引擎,测试一个单一特征时间的占用模型(本质上是想用速率和缓存大小,解析地预测有多少比例的请求能命中还没被驱逐的前缀缓存),在仔细给这个引擎打点仪表、通过七项独立的隔离性检验确认仪表数据可信之后,这个模型在实际观测到的服务端命中率上,中位数误差达到 14.3 个百分点,90 分位误差更是达到了 44.7 个百分点——而且实测的命中率曲线形状(先急剧下降、再进入一个平台期)完全落在这个解析模型能表示的曲线族之外。
这是一个相当坦诚的负面结果,而且直接支撑了论文推荐的部署实践:与其试图用一个封闭形式的模型来预测结果,不如用经验性的”影子回放”(在真实流量上试跑候选路由计划,但不把结果返回给用户,只测量真实的命中率和延迟)。大多数论文会直接省略这种负面建模结果,而这篇论文把它报告出来,是一个真实的优点——它告诉未来的实践者,不要在一个作者们已经尝试过、发现不够用的方向上浪费时间,而且它坦诚地说明了为什么会失败(曲线形状对不上),而不是只说一句”没成功”。
5.6 突发性和陈旧性敏感度
两项进一步的鲁棒性检验补齐了实验部分。第一,把平滑到达过程换成一个匹配工作负载变异系数(CV = 1.9,意味着相当突发)的伽马分布到达过程,会让 CacheRoute 在 3.5 秒 SLO 下的容量恰好下降一个梯度台阶,从 180 降到 160 QPS,而 Flat-LB 则始终钉在 30 QPS 不变——说明即使绝对数字有所下降,CacheRoute 的优势在真实的突发性下依然存在。另一项检验用移动块自助法重采样了 22,639 个真实测得的到达间隔时间(保留了纯伽马过程会丢失的短程相关结构),结果 CacheRoute 基本不受影响,维持在 180 QPS / 93% 命中率,而 Flat-LB 在其 30 QPS 的最低测试负载下就已经左删失——证明 CacheRoute 的优势不是建立在”到达过程被假设得过于平滑”这个不现实的前提之上的。
第二,前面第 3.2 节讨论过的速率漂移实验(对主要速率向量做六个控制周期的对数正态随机游走扰动,)量化了一份过时计划的确切代价,以及重新安装新计划本身要付出的瞬时重预热代价——对于任何要决定生产环境里多久重新算一次计划的人来说,这是一条真正有用的运营层面的发现。
5.7 论文给出的落地部署清单
论文在正文快结束的地方给出了一份非常具体的运营清单,这份清单本身就是对全文方法论的一次很好的总结,值得完整摘录并逐条解释其背后的道理:
- 从聚合的每 key 速率数据出发,再单独测一次单目的地的容量上限 。 这两个输入是公式 (2)、公式 (3) 全部计算的起点,必须先有,而且必须是实测得到的,而不是理论估算的——因为 本质上依赖于具体模型、具体精度、具体硬件下”延迟开始爆炸”的那个负载拐点,这个拐点没有一个通用公式能算出来,只能靠在真实引擎上加压测出来。
- 选一个保守的预热槽位分配。 “保守”的意思是宁可少准入一些 key,也不要让准入的 key 总量超出缓存实际能稳定容纳的规模——过度激进的槽位分配会导致准入的 key 之间互相驱逐彼此的缓存,表面上”准入”了却实际上从未真正预热成功,这比干脆不准入还要糟糕,因为你还多付出了亲和路由的负载集中代价。
- 影子回放候选计划和 Flat-LB,在匹配的负载点下对比。 这是整个部署流程里最关键的一步,也是第 5.5 节”拒绝解析预测模型”这个决定的直接后果——既然没有一个可信的解析模型能提前告诉你会不会赢,那唯一负责任的做法就是拿真实流量(但不把结果返回给用户)先跑一遍,测出真实的服务端命中率、各机器负载、p99 延迟。
- 只有回放证明计划更优时,才放金丝雀流量。 命中率单独提升不算数——论文反复强调,命中率提升必须同时观察 p99 或容量是否真的改善,这正是第 5.4 节”聚合工作负载 A”那个反例想说明的问题:命中率涨了 10 个百分点,但容量反而跌了,如果只看命中率这一个指标就会被误导着上线一个实际更差的方案。
- 当活跃集或实测 p99 越出这个已验证的范围时,回退到 Flat-LB。 这一条承认了一个朴素但重要的事实:influence 计划是针对某个特定的流量特征标定出来的,流量特征一旦发生实质性漂移(不只是第 5.4 节讨论的速率漂移,还包括活跃集规模本身的变化),这份计划的有效性保证就不再成立,必须有一个安全的兜底路径。
- 模型、精度、上下文长度、批处理策略、缓存分配、活跃集分布任何一项发生变化后,都要重新走一遍这个门槛检验。 这一条呼应了本笔记第 9.1 节的发现——单是从 fp8 换到 fp16,整个容量结论就会归零,所以任何一个会影响 、缓存容量或流量分布的系统性变化,都必须把这份”影子回放门槛”重新走一遍,不能假设一次标定能一直用下去。
论文特意强调,这整套清单不需要请求内容本身——只需要聚合的 key 速率、到达时间间隔和缓存命中计数器,这也是为什么整篇论文的工作负载描述里反复强调”不含原始请求文本、用户内容、业务标识符”。这个设计选择让 CacheRoute 更容易在有隐私合规要求的多租户环境里落地,因为路由决策完全建立在流量的统计特征之上,而不需要窥探请求本身携带的内容。
6. 设计选择再审视:替代方案与边界
等槽位准入(把每个 key 的预热前缀占用量都当作一样大)。 为什么可行: 它让准入子问题保持成一个简单的贪心排序——不需要额外测量或估计每个 key 的前缀字节大小,否则会把数学问题变成一个真正的背包问题(一般情况下是 NP 难的,尽管有不错的近似算法)。显而易见的替代方案: 一个感知字节大小的准入方案,按每个 key 实际可复用前缀大小除以速率来加权(一个按价值密度排序的背包),可能会以牺牲高速率、大前缀的 key 为代价,多准入一些低速率、小前缀的 key。在哪里失效: 论文明确指出(第 3.1 节和第 6 节都提到)这个假设”不适用于可复用前缀大小不均匀的工作负载”——如果你的部署里有些租户的 system prompt 只有 200 个 token,另一些租户带着两万 token 的检索文档上下文,等槽位准入就会错误地分配预热槽位预算,大概率相对于实际价值过度准入小占用的 key、准入不足大占用的 key,或者反过来,具体取决于 是怎么标定的。
不做 KV 缓存迁移或预留(CacheRoute 只改变请求去哪里,从不触碰缓存内容)。 为什么可行: 这让 CacheRoute 和 serving 引擎的内部实现完全解耦——它可以配合引擎本来就有的原生驱逐策略(通常是类 LRU)工作,不需要对引擎做任何改动,使得这个技术能广泛移植到 vLLM、SGLang 或任何其他带前缀缓存的引擎上。显而易见的替代方案: 主动迁移或预热 KV 缓存块到目的地机器上,再把请求路由过去(下文相关工作里提到的 Mooncake 就是这个思路),理论上可以完全消除新装表项的冷启动。在哪里失效: 正因为 CacheRoute 不预留缓存空间,一个已准入 key 的预热前缀依然可能被无关的冷尾流量竞争同一份缓存而挤掉——论文在第 3.2 节明确指出了这一点,这也正是为什么全文都在用实测的”服务端”命中率,而不是假设某种保证。
整个控制周期内表保持固定,而不是持续动态调整。 已经在上面第 3.2 节详细分析过了——离线全局优化与陈旧性代价之间的核心权衡。
6.1 相关工作:CacheRoute 在系统谱系里的位置
把 CacheRoute 放到已有工作的坐标系里,能更清楚地看出它选择了哪条路。PagedAttention/vLLM 和 SGLang/RadixAttention 解决的是”单个 serving 引擎内部如何高效复用前缀 KV”这个问题——它们让缓存本身变得快、变得省显存,但完全不管一个多副本集群里”请求该往哪个副本送”这件事。CacheRoute 恰恰站在这些系统”之上”的一层:它假设每个引擎实例已经有了高效的本地前缀缓存,自己只负责决定入口流量怎么在这些实例之间分配。
Preble 和 DualMap 走的是另一条路——实时地对每个请求打分,综合考虑”这个前缀历史上在哪出现过""当前各个目的地的负载是多少”,然后当场决策。这类方法的好处是天然能适应流量突变,缺点是决策必须挂在请求路径上,只能看到局部、瞬时的信息,没法像 CacheRoute 那样对全体 key 的速率分布做一次全局优化。一致性哈希及其变种(CHWBL)提供了另一种思路——用哈希函数天然保证亲和性,再叠加一个显式的负载上限来防止某个哈希桶过热,这本质上是”先保证局部性、再局部修补失衡”,和 CacheRoute”先规划好全局负载分布、再谈局部性”的顺序刚好相反。
论文还专门用一段篇幅撇清了自己和 Mooncake、MemServe 这类系统的关系——那些系统解决的是”如何在服务器之间搬运或池化 KV 状态本身”,而不是”通过入口路由来保留局部性,从而根本不需要搬运”。这两条路径并不互斥:一个部署了 KV 状态搬运能力的引擎集群,依然可以在其上叠加一层入口路由计划,来减少需要临时搬运的 KV 数据量,论文原文最后一句相关工作的话说得很直接——“这些系统依然可以用一个入口层的规划,来避免路由本身本来会重复触发的 prefill”。类似地,Llumnix 靠迁移正在运行的请求来修复失衡,DistServe 和 Splitwise 把 prefill 和 decode 拆到不同的机器上执行,这些都是和 CacheRoute 正交的优化维度——一个成熟的生产系统完全可能同时用上”入口路由亲和""KV 状态迁移""prefill/decode 分离”这三层机制,它们各自解决的是流水线里不同的一环。
7. 论文自己说的局限,以及一些被低估的地方
论文自己的第 6 节(“Discussion and Limitations”)按系统论文的标准来说已经相当彻底,明确列出了:等槽位准入假设;缺少针对不均匀前缀长度的字节感知价值函数;由于什么都不预留,冷尾流量依然可能驱逐已准入的前缀;基线重实现(Preble、DualMap、CHWBL)是共用框架下的重实现,不是原始发表的代码库,所以绝对数字不应被当作复现结果来读;8B 测试床上好几个容量拐点在 500 QPS 的扫描上限处被右删失;三种子的次要研究置信区间比五种子的旗舰结果宽得多。
对一篇系统论文来说,这已经是相当完整的自我审视了。不过仍有几点被低估或没有被充分检验:
- 预热槽位预算 和容量上限 被当作预先标定好的输入,但论文从没探究过它们的敏感度。 整个方法的行为——多少 key 会被准入、LPT 放置有多紧凑——都取决于这两个数字,但论文从没做过一次扫描,展示如果 或 被标错了(比如高估或低估 20%),结果会退化到什么程度。考虑到实际部署里运营方几乎不可避免地会把这些参数标定得不够精确(流量在变、硬件在变、模型在变),一次敏感度分析要比在现有实验上再多跑一两个种子有价值得多。
- 对三个反应式基线(Preble、DualMap、CHWBL)的重实现,是论文自己标注了、但没有完全化解的一个真实混淆因素。 “在统一接口下重新实现”对于公平的相对比较来说是好的科学实践,但这也意味着这些基线的绝对数字很可能比原论文作者用调优过的、生产级实现能达到的效果要差——而论文没有提供任何独立验证(比如把重实现的行为跟原论文在同一个基准上报告的数字做对比),来界定这个差距里有多少是实现质量造成的,有多少是算法本身的差异。
- 那两个负面结果的工作负载(聚合 A、B,32B)只在很高的层面上被描述(“去标识化的半合成聚合数据,配了不同的模型配置”)——除了”可恢复的 KV 工作量很小”这个笼统说法,论文完全没有讨论这两份工作负载的速率分布具体有什么特征(基尼系数的形状、前缀复用统计)会导致它们落入”亲和性会输”的区间。哪怕是一个粗糙的”工作负载特征→结果”映射(比如”当超过 X% 的请求在最近 Y 秒内和某个请求共享完全相同的前缀时,亲和性有帮助;低于这个比例就没有”),都能把一个逸事性的负面结果变成一个可操作的诊断工具,让运营方在部署前就能在自己的流量上跑一下,而不必每次都要跑一次完整的影子回放实验才能知道结果。
- 完全没有讨论跨租户的公平性/隔离问题。 因为 CacheRoute 会刻意把一个高速率业务的流量集中到一小撮目的地上来保持缓存局部性,理论上一个非常热门的业务反而可能因此获得系统性更好的尾延迟(或者反过来,如果它被分配到的目的地恰好也承担了大量无关的冷尾溢出流量)。对于一个真正的多租户部署——恰恰是论文自己设定的应用场景——每个租户之间的公平性保证会非常重要,而论文的评测指标(集群整体的聚合 SLO 容量、聚合 KV 命中率)完全没有把这个问题暴露出来。
7.1 一个值得单独展开的追问:控制周期该设多长?
论文虽然量化了”陈旧计划”和”新鲜计划”各自的代价(第 3.2 节、第 5.4 节讨论过),却始终没有给出一个具体的、可操作的公式或经验法则,来回答”控制周期到底应该设多长”这个几乎每个想落地这套方法的团队都会立刻问出来的问题。从论文提供的数字倒推一下能得到一些直觉:重新安装一份新计划,瞬时命中率会跌 13.6 个百分点,这个”重预热惩罚”是一次性的、固定的;而陈旧计划的命中率损失(0-3 个百分点不等)会随着漂移时间的推移逐渐累积。如果把重预热惩罚看作一次性成本、陈旧损失看作按时间线性增长的持续成本,那么理论上存在一个”盈亏平衡点”——当累积的陈旧损失超过一次性的重预热损失时,就应该触发重新规划。但论文没有把这个直觉落实成具体的触发策略,只是定性地建议”当实测的陈旧惩罚超过实测的预热惩罚时”重新规划,把这个判断完全交给运营方自己在生产环境里持续监控和决策。对于一个原本以”可操作的部署清单”为卖点的论文来说,这是清单里唯一一处还需要使用者自己补全细节、而不是直接照做就行的地方。
8. 批判性分析
这篇论文本身的局限和瑕疵。 单一作者、隶属 Meta,再加上内部、去标识化的”半合成”工作负载方法论,意味着外部读者无法独立验证驱动headline结果的工作负载具体特征——我们只被告知了基尼系数(0.756)和寥寥几个概括性统计量,但没有,比如说,key 间速率分布的具体形状,而这对”top-rate 准入这一步在这份具体数据上的具体表现,是否能推广到另一个部署的流量上”这件事至关重要。论文报告的容量倍数随着 SLO 松紧变化很大(严苛 SLO 下 4.2 倍,宽松 SLO 下 1.3 倍,在其他工作负载上有的打平有的甚至输),却没有给出一个统一的量,让读者能从自己工作负载的基尼系数和可恢复命中率上限,粗略估计出大致能期待多少容量倍数——论文交给你的是机制本身和部署清单,而不是一个预测模型,这是可以理解的(第 5.5 节解释了为什么一个解析模型失败了),但确实让读者除了自己跑一遍完整的影子回放流程之外,没有更快的办法判断这套方法对自己的场景到底适不适用。
作者低估或忽略的局限。 除了第 7 节已经提到的那几点(标定敏感度、基线重实现混淆、缺少工作负载特征诊断、租户公平性)之外,还有一处更隐蔽的疏漏:论文报告的构建时间复杂度分析(,在 、128,824 个 key 的规模下实测 345 毫秒),是针对单个控制周期快照报告的,但论文从没讨论过随着 (观测到的不同 key 数量)在真实部署里随时间增长——在一个真正的多租户系统里可能达到百万级而不是十万级——这个成本会怎么变化。在那个规模下,即使是”周期性”重新计算的 排序,也会开始影响控制周期能设得多短这件事,而这直接和第 5.4 节量化的”陈旧 vs 新鲜”权衡相互作用。论文也完全没有讨论目的地故障的情况——如果一个已准入 key 的表 里某个目的地在控制周期中途挂掉了,是有一条快速的兜底路径,还是这个 key 的流量就一直报错,直到下一个规划周期?考虑到生产环境的 serving 集群会经历常规的节点故障,对于一篇整体读起来非常面向生产环境的论文来说,这是一个有实际意义的运营层面的空白。
具体的改进建议。 第一,公开(或作为可用资料发布)实际的速率分布形状——哪怕完全匿名化,一份直方图或者一个拟合出来的分布族(比如 Zipf 指数),都能让其他实践者从自己流量拟合出来的分布出发,在跑一次完整部署试验之前,粗略估计自己更可能落在”赢”、“打平”还是”输”这个区间。第二,明确地对 和 做标定敏感度扫描——哪怕是一个适度的三点扫描(比如 ±10%、±25% 的标定误差),放到已有的 70B 测试床上,相对于现有的五种子评测来说增加的算力成本不大,却能大幅加强实际的部署指导意义。第三,给负面结果分析补上一个轻量级的诊断统计量——某种能在几分钟内从原始请求日志算出来的东西,比如”在实测缓存驱逐窗口内,重新出现完全相同前缀的请求所占比例”——让运营方可以在自己的流量上算一下,低成本地预判自己更可能落在哪个区间(赢/打平/输),而不必先跑一次完整的影子回放部署周期才知道结果。第四,直接补上目的地故障这个空白,因为这对任何真正考虑在生产环境部署这套方案的团队来说,都是一个第一优先级的运营层面的顾虑。
8.1 和这篇笔记之前读过的几篇 serving 系统论文放在一起看
把 CacheRoute 放到本系列之前读过的几篇 serving 系统论文旁边比较,能看出一个有意思的分工模式。Mooncake 关心的是”prefill 和 decode 该不该拆到不同机器上执行、KV 状态怎么在它们之间高效搬运”;Llumnix 关心的是”运行中的请求怎么被迁移,来动态修复已经出现的负载失衡”;SGLang 关心的是”单个引擎内部,前缀树结构怎么让缓存复用和淘汰变得高效”。CacheRoute 关心的问题和这三者都不一样——它假设这些机制已经各自把自己那一层做好了,自己只解决”入口流量的路由决策”这一个更上层、更早发生的问题。这种分层意味着 CacheRoute 的收益和这些系统的收益理论上是可以叠加的:一个同时用了 SGLang 的高效前缀树、又跑了 Llumnix 迁移能力的集群,依然可以在入口再叠加一层 CacheRoute 式的规划,用来减少一开始就需要触发迁移或跨机搬运的情况——把问题尽量消灭在源头,而不是依赖下游机制去补救。这也是为什么论文的相关工作部分反复强调”这些系统依然可以用一个入口层的规划”,而不是把自己包装成这些系统的替代品。
9. 附录里值得细读的补充证据
论文的附录篇幅和严谨程度对一篇系统论文来说都相当罕见,里面有几个结果值得专门拿出来讲,因为它们从侧面加强了(有时候也是收窄了)正文的结论。
9.1 精度对结果的影响,比正文暗示的更大
附录 Table 8 把旗舰级的 70B、60 张 H100 对比,在其他条件全部不变的情况下换成 fp16 精度重跑了一遍,结果是一个有用的现实检验:在 fp16 下,没有任何策略能达到 fp8 研究里的 3.5 秒 SLO——所有策略(Flat-LB、Sticky、CacheRoute)的容量都被记为零。因为 fp16 激活值的内存占用是 fp8 的两倍,同样 60 张 H100 的集群在 KV 缓存上能分到的空间成比例地变少,推理本身每 token 的耗时也变长了。CacheRoute 在缓存命中率上依然有明显的相对改善(top-K128 时 76.4% 对 Flat-LB 的 19.5%,更宽的 top-K256 设定下大约 77% 对 10%)——说明这套机制在 fp16 下依然起作用——但论文很谨慎地说明,“这个对比衡量的是规模扩大时分散效应的影响;它没有展示相对于 sticky 路由的缓存命中优势”,因为 Sticky 在这个精度下也能达到相当的 77.3% 命中率。这张附录表悄悄地补了一件正文没做的事:它说明 CacheRoute 正文里那个 2.3× 的容量倍数是特定于 fp8 精度、显存相对宽裕这个配置的,在显存更紧张的精度设置下,容量收益可能完全消失,即便缓存命中收益依然存在——这个区分对任何想把 fp8 的数字外推到自己(可能是 fp16 或 bf16)部署上的人来说都非常重要。
9.2 集群规模和收益之间是非单调关系——印证了第 2.2 节的机制
附录 Table 14(“合成鲸鱼工作负载的集群规模扫描”)在保持绝对提供负载固定的前提下,变化 ,发现了一个真正非单调的结果:在 时完全没有能通过的容量点(集群本身太小,不管用什么路由策略都服务不了这个负载),在 时 CacheRoute 达到了很强的 4.44 倍优势(70% 对 14% 命中率),而在 ——一个更大的集群——反而”没有优势”了,因为即使是被亲和路由固定下来的目标前缀,也开始触发上文第 2.2 节公式 (1) 里的重访时间机制而变冷。这是正文没有充分强调的一个真正重要的细节:CacheRoute 的收益不是简单的”机器越多越好”——它隐含地假设集群规模和流量的速率分布是匹配的,而一个独立于提供负载来扩容集群的部署(比如为了留余量而过度扩容)可能会无意间把自己推入亲和性不再起作用的区间,恰恰是因为论文自己第 (1) 式的机制,同样会作用在已准入、走亲和路由的流量上,不只是未准入的冷尾流量。
9.3 标定敏感度——正文里恰好被本笔记第 7 节点名批评的那个空白,论文其实有部分补上
本笔记第 7 节曾经批评正文没有对 标定参数做敏感度扫描——公道地说,附录 Table 13 恰好在 8B 测试床上做了这件事,扫描了 QPS。结果是令人安心的:CacheRoute 的缓存命中率(89%-91%)和容量倍数(1.50×)在这个 10 倍范围的标定参数扫描里基本保持不变,因为在基础(非鲸鱼)工作负载下,每个 key 的速率 都低于测试范围里最小的 ,所以整个扫描过程中都是 ,和 具体设在哪里无关。论文很谨慎地把这标注为”一次合理性检验,而不是证明复制机制对标定参数不敏感的证据”——也就是说,这个扫描只证明了在”没有任何 key 真正需要复制”的区间里对 误标的鲁棒性,并没有告诉我们复制机制本身( 的情形)对 误标有多敏感,因为同一张表里的注入偏斜(5%/15%/25%/45% 头部占比)行,是固定 、只变化偏斜程度。一个真正测试”复制路径对 敏感度”的实验——同时扫描两者——依然是缺失的,所以本笔记第 7 节的这条批评,只被这张附录表部分回应了。
9.4 “论点-证据映射表”是一个值得学习的科研沟通范例
附录 Table 7 以及配套的”论点-证据映射表”(下方按精神重新整理),坦白说是更多系统论文应该学习的做法:一张明确的表格,把每一条 headline 论点,对应到具体支撑它的实验、种子数量、硬件/仿真状态,再配一句说明”这个结果没有证明什么”的告诫。
| 论点 | 支撑证据 | 明确的告诫 |
|---|---|---|
| 2.3 倍容量的 headline 结果 | 70B fp8,60 张 H100,top-K128,5 个种子 | ”不是一个普适的倍数” |
| 更宽活跃集的结果 | 独立的 8 种子确认实验 | 优势更小,3.5s 下只有 1.33× |
| 第二工作负载的结果 | 3 个种子,K∈{128,256} | K128 打平,只有 K256 才到 1.6× |
| 亲和性/放置分解 | 8B 消融,3 个种子 | 只有合成鲸鱼场景真正用到了复制 |
| 突发性鲁棒性 | 两策略、单活跃集的敏感度实验 | 没有外推到另外四个基线 |
| 32B 输/平区间 | 匹配硬件条件下的实测 | 建立了一道部署门槛,不是普适的失效模式 |
这张表或许比正文里任何一个具体数字都更有方法论价值——它强迫作者(以及读者)明确区分哪些论点是可以泛化的,哪些只是狭窄的、用来分离机制的验证性实验。未来的 LLM serving 系统论文,如果能有更多这样的表格,对读者和审稿人来说都会更友好。
9.5 我自己对这份附录的一点补充思考
读完整份附录之后,我个人觉得最值得记在心里的一条经验,是 9.1 节里 fp8 vs fp16 那个对比——它提醒我们,任何一篇 serving 系统论文报告的”倍数”结果,几乎总是绑定在一个具体的软硬件配置组合上的,精度、模型规模、batch 策略、KV 块大小任何一个维度变了,数字都可能整个归零或者反转。这不是这篇论文特有的问题,而是几乎所有 serving benchmark 论文共同的局限——但这篇论文难得的地方在于它自己在附录里主动做了这个检验,而不是等着读者或者审稿人去质疑。如果我自己要把 CacheRoute 这类方法用到实际项目里,我会把”先在自己的精度/模型/硬件组合上重新跑一遍论文附录里的核心敏感性检验(至少是 fp8/fp16、集群规模、 标定这三项)“,作为影子回放之前的一个前置步骤,而不是直接信任论文报告的 2.3 倍这个数字能原样迁移过来。
10. 写在最后
CacheRoute 之所以是一篇成功的论文,与其说是因为它的具体算法有多新颖(论文自己都说没有,它借用的都是教科书级别的老技术),不如说是因为它测量了什么、以及报告结果时有多诚实。核心洞察——缓存局部性和负载均衡需要联合规划,而且只要把规划环节挪出热请求路径,这件事就是可以做到的——在一个贴近真实场景的 70B 模型、60 张 GPU 的部署上,产出了实打实的巨大收益(2.3 倍 SLO 容量,93.2% 服务端 KV 命中率)。但这篇论文对整个领域最有价值的贡献,可能反而是第 5.3 节里那份量化过的、“同一套技术什么时候会输给朴素的对缓存无感基线”的清单:这是一个提醒——不是摘要里一句轻飘飘的免责声明,而是靠真实测量支撑的提醒——前缀亲和路由本质上是在赌你的工作负载里,有多大比例的请求速率对应着真正可恢复的缓存收益,而这个赌注需要在你自己的流量上去测量(按论文部署清单里说的,用影子回放去测),而不能假设它能从别人的基准测试上直接迁移过来。
常见误读辨析
写完这篇笔记,我想特别提醒几个容易被读错的地方,都是我自己第一遍读论文时差点理解错的细节:
- “93.2% 命中率”不是说所有请求里 93.2% 命中缓存,而是”服务端、按 token 加权”的命中率。 论文明确说明这是 served and token-weighted,意味着这个百分比是按每个请求实际用到的 KV token 数量加权平均出来的,而不是简单的”命中请求数 / 总请求数”。对于前缀长度差异很大的工作负载,这两种统计口径会给出明显不同的数字,读论文报告的命中率数字时一定要留意统计口径。
- “2.3 倍”这个倍数是相对于最强基线(Preble)算出来的,不是相对于所有基线的平均值。 如果拿 CacheRoute 和最弱的基线 Sticky 比(30 QPS),倍数会显得夸张得多(接近 6 倍),但这不是论文强调的对比方式,论文自己选择用最强基线做分母,这是更严格、更保守的报告方式,值得肯定,但读者引用这个数字时也要注意说清楚”是相对于哪个基线”。
- 第 5.3 节的”输”和”平”不是 CacheRoute 这个系统本身设计得不好,而是那两份工作负载里”可恢复的缓存工作量”本来就很小。 换句话说,如果一个工作负载里大部分请求根本没有值得复用的前缀,那么不管用什么路由算法,亲和性都不可能有实质性收益,这时候唯一正确的选择是干脆不要启用亲和路由,而不是去调参数试图让 CacheRoute”表现得更好”——问题出在流量特征上,不在算法实现上。
术语速查
- 前缀缓存(Prefix caching): 在多个共享 token 前缀的请求之间复用之前算好的 KV 张量,避免重复的 prefill 计算。
- KV 缓存: 自注意力层里被缓存下来的 key/value 张量,方便后续 token(或共享前缀的请求)复用,而不用重新计算。
- Prefill / Decode: LLM 推理的两个阶段——并行处理输入 prompt(prefill)与逐个生成输出 token(decode)。
- Power-of-two-choices: 一种负载均衡启发式,随机抽两台候选机器,选负载较轻的那台,是”精确最轻路由”的一种廉价近似。
- LPT(最长处理时间优先)调度: 一种经典的贪心调度启发式——按任务大小从大到小排序,依次分配给当前负载最轻的机器。
- SLO(服务水平目标)容量: 系统在仍然满足目标尾延迟约束的前提下,能持续承受的最高请求速率(QPS)。
- 右删失(Right-censored): 一种实验结果状态,测得的拐点恰好落在最高测试负载点上或超出它,意味着真实值可能更高,但没有被实际测出来。
- 控制周期(Control interval): 一份计算好的路由表在被重新计算之前保持不变的固定时间窗口。