这些 Prefix 本来就只出现一次;扩容量无效。
缓存容量问题
缓存结果不是由容量单独决定。访问负载决定访问什么,策略决定留下什么,放置 / 路由决定去哪里找,成本决定一次 Hit 到底值多少钱。
| 因子 | 需要观察什么 | 它会改变什么 |
|---|---|---|
| Workload(访问负载) | 请求速率、Prompt 长度、一次性 Prefix、复用分布、突发与租户构成 | 理论可复用上限和工作集大小 |
| Policy(策略) | 准入、淘汰、保护 / 占用(Pin / Lock)、降层与迁移规则 | 相同容量下谁留下、谁被赶走 |
| Capacity(容量) | HBM / DRAM 的 Block、Byte 与局部容量分布 | 能保留多深的复用历史 |
| Placement(放置 / 路由) | 全局索引、路由结果、目标 Engine 与本地 Trace | 已有 KV 是否出现在真正执行请求的位置 |
| Cost(代价) | Recomputed Token、Prefill 时间、传输、排队与 TTFT | 一个 Hit 或 Miss 的实际价值 |
| Transition(状态切换) | Cache ReadyTime、预热、迁移与路由切换 | 新容量何时真正能够承接请求 |
| Sharing(共享) | 多租户竞争、扫描污染与容量分区 | 谁占用了容量,以及谁驱逐了谁 |
先固定几个词:Prefix 是请求开头可被后续请求复用的 Token 序列;Block 是引擎存放 KV 的管理粒度;Engine 是实际执行推理的实例;Prefill 是模型读取输入并生成 KV 的计算阶段;TTFT 是从请求开始到产生首个输出 Token 的时间;Cache ReadyTime 是触发扩容到新容量已经带着可用 Prefix 接流量的时间。
两个不同决策:Admission(准入)决定 Miss 后是否把新 KV 写入;Eviction(淘汰)决定容量满时删谁。扩容量只改变可用空间,不会自动修正错误的准入或淘汰策略。
缓存性能恶化时,先分清四类原因
仍会复用的 block 被提前淘汰;扩容量可能有效。
KV 在另一 Engine,请求却被送到没有它的 Engine,只能重算。
命中落在 DRAM 或远端,但搬运与排队成本已经过高。
论文提供的 insight:现有工作分别解决容量测量、调度 / 流控和多租户,但没有一篇把三者连成当前系统的完整答案。论文 → Takeaway
系统模型与开源基线
先固定对象、层级、调度与真实命中的边界。
讨论边界:本页不展开模型版本、LoRA、Tokenizer 兼容、权重显存竞争,以及正在生成中的请求 KV 与 Prefix KV 的竞争;后文默认这些条件不变。
开源系统为什么实现不同
| 系统 | 管理层级 / 对象 | 默认候选与约束 | 为什么不同 |
|---|---|---|---|
| vLLM | HBM 固定 Block | 只回收 ref_cnt = 0、即没有请求占用的 Block,按 LRU;同时间优先更深后缀 | 活跃 Block 不能回收,固定 Block 可直接维护空闲顺序 |
| SGLang | HBM Radix Tree(前缀树) | 只从未锁定叶子按 LRU 淘汰 | 共享祖先不能先于子节点删除,树结构约束高于纯 LRU 顺序 |
| RTP-LLM | HBM Prefix Block | 动态 Block 按 LRU,Resident(常驻)Block 不参与淘汰 | 常驻与活跃 Block 需要从候选集合排除 |
| LMCache | CPU、Disk 或外部层级 | 可配置 LRU 等策略,在 Engine 外保存与加载 KV | 管理跨层 KV,不只是在 HBM 内选择淘汰对象 |
| Mooncake | 跨节点 KV Store | 先过滤被保护或仍在租约内的对象(Pin / Lease),再回收 | 分布式所有权、传输和租约决定对象能否回收 |
| kvcached | Engine 下方物理页 | 沿用 vLLM / SGLang 的候选顺序,再执行物理页回收 | 解决物理容量管理,不重新定义访问模式感知的优先级 |
这不是一张优劣榜。vLLM、SGLang、RTP-LLM 处理 Engine 内部的逻辑候选;LMCache、Mooncake、kvcached 处理外部层级、跨节点存储或物理页。对象结构和“哪些 Block 可以被回收”不同,实现自然不同。
论文提供的 insight:推理 KV 章节把 Engine 内 Prefix 淘汰、外部 KV 层和物理容量管理拆开,避免把“扩物理内存”误认为新的淘汰策略。论文 → 推理 KV
常见指标、故障形态与联合诊断
先回答现在发生了什么,再判断其中哪部分能被容量变化挽回。
3.1 常见状态指标
| 指标 | 定义 / 口径 | 能说明什么 | 不能直接推出什么 |
|---|---|---|---|
| Request Hit Rate | 整个目标 Prefix 是否命中 | 完整复用请求的比例 | 看不到只命中一部分 Prefix 的情况(Partial Hit) |
| Block Hit Rate | Hit Blocks / Requested Blocks | 固定 Block 下的复用比例 | 比例稳定时,总 Prefill 仍可随 QPS / Prompt 变大 |
| Token Reuse Rate | Reused Tokens / Input Tokens | 与实际输入长度对齐 | 没有区分不同位置 Token 的计算成本 |
| Recomputed Token Rate | 每秒需要重新 Prefill 的 Token | 当前 Prefill 计算压力 | 不是端到端延迟,还受批处理、队列和硬件影响 |
| Eviction Rate | 每秒因容量不足被删除的 Block / Byte | 当前替换周转速度 | 缓存满后持续淘汰可能完全正常 |
| Occupancy | 已用容量 / 可用容量 | 当前空间状态 | LRU 缓存常态接近 100%,不能直接触发扩容 |
| Admission Rate | Miss 后真正写入的 Block / Byte | 新数据进入缓存的速度 | 写入多不代表这些数据会复用 |
| Tier Hit / Remote Load | HBM、Local DRAM、Remote DRAM 命中与搬运量 | Hit 落在哪层、付出多少传输 | 汇总 Hit Rate 会掩盖层级成本 |
| Prefill Queue / TTFT | 等待重算的队列与首 Token 延迟 | 用户结果和过载程度 | 它们是结果指标,不能单独证明容量不足 |
3.2 状态指标与决策指标
状态指标描述当前命中、淘汰和重算;决策指标要回答增加或减少 10% 容量会改变多少 Prefill、搬运与租户收益。后者必须来自 Ghost、Shadow、MRC 或真实小幅扰动,不能由当前阈值直接推断。
3.3 先给 Miss 归因
| Miss 来源 | 发生了什么 | 扩容量通常是否有效 |
|---|---|---|
| Cold / One-hit Miss | Prefix 首次出现,或只出现一次 | 无效;它决定可复用上限 |
| Admission Miss | 策略判断新 KV 不值得写入 | 先检查准入;加空间不保证它会进入 |
| Capacity / Eviction Miss | 仍会复用的 Block 因空间不足被淘汰 | 可能有效;Ghost、Shadow 与 MRC 用来估计收益 |
| Placement Miss | KV 存在,但没有出现在实际执行请求的 Engine | 先修路由或放置;全局扩容未必有效 |
| Slow-tier Hit | KV 在 DRAM 或远端,能复用但搬运太慢 | 不是 Miss;应比较加载与重算成本 |
3.4 常见故障形态
| 形态 | 发生了什么 | 典型信号 | 容量判断 |
|---|---|---|---|
| 扫描污染 | 大量一次性 Block 被准入,驱逐稳定热点 | Admission / Eviction 高,Ghost 重访低,其他租户 Hit 下降 | 单纯扩容只延后问题;优先收紧准入或隔离租户 |
| 穿透型流量 | 大量 Prefix 只访问一次,持续穿过缓存 | Miss 高、Ghost / Shadow 收益低、只访问一次(one-hit)的比例高 | 扩容无效 |
| 热点击穿 | 高价值 Prefix 丢失后,大量请求同时重算 | 少量 Prefix 对 Recomputed Token / Queue 贡献突增 | 容量可能有帮助,还需合并重算、Pin 或预热 |
| 缓存雪崩 | 缩容、迁移或冷启动让大量 Prefix 同时不可用 | Tier Hit 同时下降,Recompute 与 Queue 快速上升 | 必须提前准备并分批切换,事后扩容可能来不及 |
| Hit 稳定但性能崩溃 | QPS、Prompt 长度、Remote Load 或排队上升 | Hit 比例平稳,Prefill Token/s、Transfer 或 TTFT 上升 | 取决于容量曲线与净收益 |
3.5 联合诊断
| 观察 | 更可能的解释 | 还缺什么 |
|---|---|---|
| 淘汰高,被淘汰 Block 很少再访问 | 正常周转或低复用扫描 | 扩容量是否真的能减少 Miss |
| 被淘汰 Block 频繁再次访问 | 当前容量可能赶走了近期复用 | 能挽回多少 Prefill 成本 |
| Hit Rate 稳定,Recompute 上升 | 请求速率、Prompt 长度或租户构成变化 | 平均输入 Token 与每秒 Prefill Token |
| DRAM / Remote Hit 上升,TTFT 仍恶化 | 搬运队列或加载成本上升 | 加载与重算谁更便宜 |
论文提供的 insight:FLOWS@EuroSys'24、Tail-Optimized@NeurIPS'25、RobinHood@OSDI'18、MDK@OSDI'26 分别从 Byte、Prefill、尾延迟与 SLO 说明不同 Miss 并不同价,所以 Hit Rate 不能独立决定容量。论文 → Takeaway · 论文 → 容量决策
替换策略与经典容量建模
先固定谁能进入、谁会被淘汰,再讨论同一策略下容量变化带来的结果。
4.1 替换与准入策略
| 基础策略 | 使用的信号 | 为什么有效 | 主要失败模式 |
|---|---|---|---|
| FIFO | 进入缓存的先后顺序 | Hit 路径不维护顺序,成本低 | 完全看不到复用,老热点也会按进入时间被删 |
| LRU | 距离上次访问的远近 | 时间局部性强时,近期访问能近似未来复用 | 扫描与一次性访问污染;每次 Hit 都要更新顺序 |
| LFU | 历史访问频次 | 长期热点稳定时能保住高频对象 | 旧热点计数难衰减,新热点升温慢 |
| 现代策略 | 解决的问题 | 核心设计 | 适合什么 | 主要边界 |
|---|---|---|---|---|
| ARC(FAST 2003) | 近期性(Recency)与频率(Frequency)谁更重要会变化 | 两组真实队列加两组只存被淘汰 ID 的 Ghost 队列,根据 Ghost Hit 自动调节容量 | 近期复用与长期热点会切换 | 状态和维护成本高于简单队列 |
| TinyLFU | 一次性对象进入后污染缓存 | 近似比较新对象与待淘汰对象的频率,新对象更有价值才准入 | 扫描多、热点偏斜明显 | 主要解决 Admission,不单独定义 Eviction 顺序 |
| S3-FIFO(SOSP 2023) | 只访问一次的对象污染与 LRU 命中重排开销 | 小 FIFO 试用、主 FIFO 保护、Ghost 识别再次出现 | 一次性对象多、并发高 | 队列比例与晋升规则仍需适配访问负载 |
| SIEVE(NSDI 2024) | LRU 每次 Hit 搬链表导致锁竞争 | Hit 只设置访问位(visited bit),淘汰时用游标扫描 | 高并发、希望命中路径极轻 | 一个访问位表达不了更细的频率和成本差异 |
Admission 与 Eviction 是两个决策:TinyLFU 主要判断新对象是否值得写入;LRU、FIFO、SIEVE 主要判断空间满时从已有对象里删谁。Prefix KV 还有共享祖先、活跃 Block 与保护 / 占用约束,所以推理引擎实际采用的基本都是带结构约束的 LRU 变种。
论文提供的 insight:LHD@NSDI'18 用单位容量收益选淘汰对象;S3-FIFO@SOSP'23 处理一次访问污染;SIEVE@NSDI'24 降低 Hit 路径维护成本。它们共同说明缓存策略会改变同一容量下的曲线,比较容量时必须固定策略。论文 → 通用替换
4.2 Working Set(Denning,1968):近期到底有多少数据在活动
定义
在最近 T 秒或最近 N 次访问中出现过的不同 Block / Byte 集合,记作 WSS(T)。它是“近期活跃数据量”,不是缓存当前占用量。
怎样读
容量长期低于近期 Working Set 时,同一批 Block 会反复淘汰和重算;但一次扫描也会把 WSS 暂时推高,所以窗口长度必须写清。
4.3 Reuse Interval 与 Reuse Distance:时间和容量不是一回事
| 概念 | 定义 | 能说明什么 | 不能直接说明什么 |
|---|---|---|---|
| Reuse Interval | 同一 Block 两次访问相隔的时间或请求数 | 复用发生得快不快,访问模式是否发生漂移 | 这段时间里来了多少其他 Block,因此不能直接换算容量 |
| Reuse Distance | 同一 Block 两次访问之间出现的不同 Block 数;变长对象时可按 Byte 计 | 在 LRU 顺序中,这个 Block 被推到了多深的位置 | 非 LRU 策略、分层成本与路由变化 |
对 LRU,容量 C 大于 Reuse Distance 时才会 Hit。Interval 相同但到达速率更高时,Distance 会变大;这也是突发流量下时间窗口容易误判、Distance 更适合容量分析的原因。
4.4 Mattson(1970)与 MRC:从一条 Trace 得到“容量 → Miss”
LRU 优先保留最近访问的 Block。Mattson 读取访问 Trace(按时间记录的 Block 请求序列),在每次访问时记录该 Block 的 Reuse Distance:这次访问会在所有 C ≤ Distance 的缓存中 Miss,在所有 C > Distance 的缓存中 Hit。
怎样看曲线
当前 C 右侧下降很陡:扩一点能救回很多 Miss;左侧很陡:缩一点会立刻丢掉工作集;两侧都平:容量变化收益小。
Mattson 的边界
它依赖 LRU 的包含性:容量变大只会增加 Hit。首次访问是 Cold Miss,在所有容量下都无法被曲线救回;完整维护访问栈也可能太贵。
4.5 Ghost、Shadow、Miniature 与 SHARDS:怎样在线估计
| 方法 | 保存什么 | 得到什么 | 代价与边界 |
|---|---|---|---|
| Ghost Cache | 被淘汰 Block 的 ID、大小和时间,不保存 KV | Ghost Hit 表示“如果容量稍大,刚才的淘汰可能被避免” | 只观察当前容量附近;Bloom Filter 只是压缩元数据的实现 |
| Shadow Cache | 一套不服务真实请求的影子缓存状态 | 直接回答某个更大或更小容量下会 Hit 还是 Miss | 必须复现真实准入、淘汰、Block 匹配与路由 |
| Miniature Simulation(ATC 2017) | 采样 Trace 上的多个缩小版影子缓存 | 同时得到若干容量点,也可覆盖 FIFO 等无法由 Reuse Distance 直接推导的策略 | 容量点越多越重,采样与缩放会带来误差 |
| SHARDS(FAST 2015) | 按 Key 哈希采样后的 Reuse Distance | 以较低开销逼近完整 LRU MRC | 仍依赖 LRU 类包含性,且必须校正采样尺度 |
论文提供的 insight:SHARDS@FAST'15 用抽样 Reuse Distance 降低完整 LRU MRC 的开销;Miniature Simulations@ATC'17 直接模拟多个容量并覆盖非栈策略;eMRC@FAST'21、Kosmo@FAST'24、LAShards@IEEE TC'25 分别扩展到多层容量或更低的在线开销。论文 → 测量 / MRC
从命中率到缓存价值
一个 Block Hit 到底省了多少工作。
命中了多少请求 block。
还需要重新计算多少输入。
这些命中实际避免多少 GPU 计算时间。
再扣除从 DRAM 或远端加载 KV 的成本。
“剩余 Prefill”是什么:对一个请求,Engine 能复用到的最长连续 Prefix 之后那段输入,才需要重新计算;不是把每个 Miss Block 当成互不相关的任务分别计费。
为什么原始 MRC 还不够:它把每个 Miss 都记作 1;但 Prefix KV 中,Miss 一个短后缀和重算一个长前缀的代价不同。需要把 MRC 的纵轴从“Miss 次数”换成“剩余 Prefill 成本”。
于是扩 ΔC 的直接收益是 MissCost(C) − MissCost(C + ΔC);它回答多一份容量能少算多少,而不是只多命中多少 Block。
Token 数只能作为第一阶近似:同样 1,000 个未复用 Token,在不同前缀长度、批处理(Batch)和并发状态下,真实 Prefill 成本可能不同。
论文提供的 insight:LHD@NSDI'18 用单位容量收益选择淘汰对象,FLOWS@EuroSys'24 说明对象数曲线会忽略 Byte 差异,Tail-Optimized@NeurIPS'25 把 KV Miss 与 TTFT 风险连接起来。它们分别提供单位空间价值、曲线权重和 SLO 价值,但没有组成完整扩缩容控制器。论文 → 通用替换 · 论文 → 测量 / MRC · 论文 → 推理 KV
架构边界
当前架构里,三个口径不能混在一起。
这里只要求测量时把预计位置与实际命中、命中层级、租户归因分别记录,不在本页展开求解。
论文提供的 insight:eMRC@FAST'21 提供多层容量建模参照,LMetric@OSDI'26 说明调度会改写本地 Trace,HARE@FAST'26 说明缩缓存可能把压力推向下游。论文 → 测量 / MRC · 论文 → 调度器 · 论文 → 容量决策
多租户容量
共享容量里,下一份应该给谁。
典型外部影响:扫描租户把大量只访问一次的 Block 写入共享 LRU。它自己的收益很低,却会驱逐其他租户的高价值 Prefix;全局 Hit Rate 可能只缓慢下降,受损租户的 Prefill 与 TTFT 已经明显上升。
潜在控制方向:先在共享池内把容量移向边际收益更高的租户;可调整余量用完后,整体收益仍高于容量成本,再扩整个池。
论文提供的 insight:Memshare@ATC'17 给出最低保障加共享余量;Cliffhanger@NSDI'16、OSCA@ATC'20、Cuki@ATC'23 用局部梯度或在线需求估计分配容量;RobinHood@OSDI'18、HARE@FAST'26、MDK@OSDI'26 把尾延迟、跨资源压力和 SLO 边界带入回收决策。论文 → 容量决策
方法与指标总表
这些方法分别能回答什么,不能回答什么。
| 方法 | 能回答什么 | 不能回答什么 | 对当前系统的价值 |
|---|---|---|---|
| Hit Rate | 当前有多少访问命中 | 扩容量后会怎样;每次 Hit 省了多少 | 基础健康指标,必须与 Token 量和成本联看 |
| Eviction Rate | 当前替换有多频繁 | 被淘汰 Block 是否会再次访问 | 描述周转压力,不能单独触发扩容 |
| Occupancy | 当前用了多少容量 | 已有对象是否值得保留;扩容收益 | 判断空间状态,不能把接近 100% 直接当异常 |
| Admission Rate | Miss 后写入多少新 KV | 新 KV 是否会复用 | 识别扫描与写入压力,需与一次访问比例 / Ghost 联看 |
| Recomputed Token / Prefill Time | 当前因 Miss 重算多少输入和 GPU 时间 | 哪部分能被加容量挽回 | 比 Miss 次数更接近真实压力 |
| Tier Hit / Remote Load | 命中发生在哪层、搬运多少 KV | 改容量后的层级迁移 | 识别“Hit 不变但慢层负载上升” |
| Working Set | 给定窗口内的活跃数据量 | 精确的容量收益;扫描是否值得保留 | 识别容量需求随时间漂移 |
| Reuse Interval | 复用发生得快不快 | 相同时间内有多少不同 Block 到达 | 识别访问节奏、突发与过时窗口 |
| Reuse Distance / Mattson | LRU 下每个访问需要多大容量;完整 MRC | 非 LRU、多层代价和路由反馈 | 构造 Block / Byte 级容量曲线 |
| Ghost Cache | 当前容量附近,扩一点能救回多少近期淘汰 | 完整 MRC 和较远容量点 | 低成本在线估计右侧局部收益 |
| Shadow / Miniature | 若干候选容量或策略下会怎样 | 真实系统全部排队、传输和调度反馈 | 在线估计容量变化,也能覆盖非 LRU |
| MRC | 容量 C 对应多少 Miss | 不同 Miss 的 Prefill 价值 | 扩缩容的容量骨架 |
| Saved Prefill | 缓存实际少算了多少 Token / GPU 时间 | 加载、迁移与容量本身的成本 | 把 Hit 转成算力价值 |
| 分层净收益 | HBM、Local DRAM、Remote DRAM 命中的净价值 | 未来访问负载与控制器稳定性 | 形成 HBM / DRAM 共享池的统一决策量 |
| Cache ReadyTime | 扩容触发到可带着 Prefix 接流量要多久 | 应该扩多少容量 | 决定预测窗口和最晚触发时间 |
论文提供的 insight:测量 / MRC 章节回答“容量变化会怎样”,容量决策章节回答“拿到收益曲线后如何分配与安全回收”;两类工作不能互相替代。论文 → 测量 / MRC · 论文 → 容量决策
迁移条件
经典方法迁到 Prefix KV 前,要验证什么。
| 方法 | 必须验证的前提 | 前提不成立会怎样 | 验证方式 |
|---|---|---|---|
| Reuse Distance / MRC | 真实淘汰接近 LRU;Block 粒度明确;Trace 的 Engine / 租户作用域固定 | 离线曲线无法代表扩容后的真实命中 | 用小幅容量扰动对照预测与实际变化 |
| Ghost Cache | 能区分删除(Eviction)、降到慢层(Demotion)和迁移节点(Migration);Ghost Hit 确实表示更大容量可能保留 | 把层级迁移或路由变化误判成容量不足 | 保留淘汰原因、原层级、目标位置与重访结果 |
| Shadow / Miniature | 复现真实 Prefix 匹配、准入、淘汰、HBM / DRAM 与调度选择 | 影子 Hit 与 Engine 真命中持续偏离 | 按阶段比较全局索引、Engine、命中层级与重算结果 |
| Saved Prefill | Prefill 成本可按 Token 位置、批处理、并发和硬件状态估计 | 同样的 Missed Token 被赋予错误价值 | 用真实 Prefill 时间校准 Token 近似 |
| 多租户收益 | 每次访问、淘汰、传输与重算都能归因到租户 | 全局收益看似正常,但受益者和受损者不可见 | 按租户回放与容量扰动,检查外部影响 |
最重要的验证:让 Shadow 预测一次小幅扩容或缩容,再与真实 Saved Prefill、Tier Load 和 TTFT 变化对照。预测误差没有收敛前,不把它接入自动控制。
论文提供的 insight:SHARDS@FAST'15 依赖 LRU 的栈性质,Miniature Simulations@ATC'17 用模拟覆盖非栈策略,eMRC@FAST'21 / FLOWS@EuroSys'24 说明多层容量与 Byte 权重会改写曲线;这些假设都要在真实 Trace 上验证。论文 → 测量 / MRC
KV Cache 扩缩容
测容量收益,也测准备时间。
不能等当前指标越线才扩容;需要预测新增容量真正可用时,缓存会是什么状态。
控制循环
- Observe:归因可被容量挽回的 Miss
- Estimate:更新算力加权 MRC
- Forecast:预测 t + ReadyTime 的容量收益与过载风险
- Prepare:扩容先准备 Prefix,缩容先迁移
- Verify:用 Saved Prefill 与 SLO 验证动作
需要记录的最小事件链
重要的指标
它们怎样变成缓存动作
| 判断 | 含义 | 缓存动作 |
|---|---|---|
| 预测 t + ReadyTime 时,当前容量右侧收益很高 | 多一份容量能避免大量高成本 Prefill | 现在触发扩容并开始准备 Prefix |
| 当前容量左侧 MRC 很平 | 少一份容量只增加少量低价值 Miss | 先迁移 / 预热,再分批缩容 |
| 预计容量就绪前先撞上 Prefill 过载线 | 扩容已经来不及覆盖风险窗口 | 停止缩容、收紧低收益准入,优先保留或迁移高价值 Prefix |
论文提供的 insight:SHARDS@FAST'15 / Miniature Simulations@ATC'17 / Kosmo@FAST'24 / LAShards@IEEE TC'25 负责测容量收益,Cliffhanger@NSDI'16 / Cuki@ATC'23 负责找容量拐点,FLOWS@EuroSys'24 / Tail-Optimized@NeurIPS'25 负责给 Miss 加价值,HARE@FAST'26 / LMetric@OSDI'26 分别提醒下游压力与调度器对缓存局部性的影响。它们尚未组合成带 Cache ReadyTime 的 KV Cache 扩缩容控制器。论文 → Takeaway