推理服务的缓存与调度:前缀、多级存储、集群路由

推理服务的缓存与调度:前缀、多级存储、集群路由

💡 原文中文,约2500字,阅读约需6分钟。
📝

内容提要

本文讨论长上下文推理服务的缓存与调度优化。核心原则是缓存未命中比命中贵几个数量级,因此设计围绕复用展开:单实例内通过块哈希实现前缀缓存;KV池采用write-back策略在GPU、DRAM、SSD间分层存储;集群层面用一致性哈希实现缓存亲和,并按请求类别分配预算以应对长请求突发。

🔎

延伸解读

缓存命中率是长上下文服务的生命线

长上下文场景下,输入远长于输出,一次请求可能携带几十万 token 的历史,而新增只有几千 token。如果前缀缓存未命中,prefill 计算量将增加两个数量级,直接拖垮服务效率。因此,从单实例的块哈希缓存到集群路由,所有设计都应优先保障缓存命中率,即使需要付出其他代价。理解这一点,有助于把握后续优化策略的内在逻辑。

write-back 策略的权衡与前提

在 GPU、DRAM、SSD 三层存储中,write-back 策略只在块被驱逐时才写回 DRAM,相比 write-through 能节省大量空间和带宽,尤其适合活跃块比例高、周转快的长上下文服务。但该策略依赖预取机制来抵消驱逐时的拷贝延迟,且要求多种 cache 组件(如 KV 块与线性注意力状态)必须协同搬移,否则部分命中等于未命中,实际部署时需仔细设计。

缓存亲和与故障转移的平衡

集群层面通过一致性哈希实现缓存亲和,确保同一会话的请求路由到持有其缓存的实例,避免负载均衡器导致的缓存未命中。但亲和性也带来绑定风险:主集群故障时,备集群接管需重新 prefill。一致性哈希将不同会话的备集群均匀分布,使重 prefill 负载分散,既保持常态下的缓存局部性,又限制了单点故障的影响范围。

按类别预算应对长请求突发

生产流量中请求长度差异巨大,短请求几千 token,长请求可达上百万,若按平均请求做容量规划,长请求突发会耗尽算力,导致短请求延迟恶化。将准入控制改为按请求类别分配预算,每类只能消耗自己的份额,可隔离长上下文突发对短请求的影响,类似网络流量整形。这要求服务具备精细的请求分类和资源配额管理能力。

Q&A

为什么长上下文推理服务中缓存未命中比命中贵几个数量级?

因为 prefill 阶段的计算量与输入长度成正比。例如,一个前缀为 400K token、增量为 4K token 的请求,命中缓存时只需计算 4K token,而未命中时需计算 404K token,相差两个数量级。

前缀缓存是如何工作的?vLLM 和 SGLang 的实现有何不同?

前缀缓存按块进行:KV cache 按固定大小的物理块(如 16-64 token)分页,每个块计算一个哈希,覆盖该块及之前所有块的 token。新请求逐块查哈希,最长连续命中即为可跳过的前缀。vLLM 使用自动前缀缓存,SGLang 的 RadixAttention 将哈希表换成 token 树。

KV 池中 write-back 策略相比 write-through 有何优势?为什么长上下文场景下更划算?

write-through 在每个块生成时同步写一份到 DRAM,简单但为所有块付 DRAM 空间和带宽,包括活跃在 GPU 上的块。write-back 只在块被驱逐时写回 DRAM,并为复用预取,只为真正离开 GPU 的块付账。长上下文服务中活跃块比例高、周转快,write-back 更划算。

集群层面如何实现缓存亲和?为什么负载均衡器会破坏缓存复用?

负载均衡器轮询或按负载分发会把同一会话的请求送到不同机器,导致缓存 miss。缓存亲和通过一致性哈希实现:每个会话哈希到环上,顺时针第一个集群为主,第二个为备。主集群服务流量,故障时备集群接管,备集群需重新 prefill,但一致性哈希将重 prefill 负载分散到所有集群。

长请求突发如何影响服务?按类别做预算如何解决?

长请求(上百万 token)与短请求(几千 token)的代价相差三个数量级,按平均请求做容量规划会失效,长请求突发会占满算力,导致短请求延迟恶化。按类别做预算将准入控制从“数请求”改为“分预算”,每类请求有独立资源份额,长上下文突发只能消耗自己的份额,不影响短请求延迟。

Kimi K3 在缓存与调度方面有哪些实践?

Kimi K3 是三层缓存设计的一个真实例子:第 7 篇讲了前缀缓存,第 10 篇讲了 KV 池与集群调度。具体包括将哈希粒度与物理块粒度解耦,以及多级存储和集群路由的实现。

🏷️

标签

➡️

继续阅读