KV Cache 如何显著降低大模型推理成本
大模型推理早就不是“算力够不够”的单一问题了。到了生产环境,真正卡住系统的,往往是延迟、吞吐、显存占用和成本。尤其在上下文窗口越来越长的情况下,单纯依赖更强的 GPU 并不能解决核心矛盾,因为解码阶段常常是内存带宽受限,而不是纯计算受限。
KV Cache 是这一阶段最关键的优化之一。它通过缓存注意力层中已经计算过的 Key 和 Value,避免模型在生成每一个新 token 时都把历史 token 的 K/V 重算一遍。结果很直接:重复计算显著减少,延迟下降,吞吐提升,但代价是显存压力转移到了缓存管理本身。
下面按工程视角拆开这件事:KV Cache 到底在缓存什么,为什么它能省成本,什么时候收益最大,什么时候反而没那么划算,以及现代推理引擎是怎么围绕它设计整套内存管理策略的。
KV Cache 到底解决了什么问题?
结论先说:KV Cache 解决的是自回归解码阶段的大量重复计算问题,适用于 Transformer 解码过程,但不会消除缓存本身带来的显存成本。
在 Transformer 中,每个输出 token 都可以通过多头注意力访问此前的所有 token。每一步解码都要计算 Query、Key、Value。问题在于,如果不做缓存,那么为了生成第 N 个 token,模型需要再次为前面所有 token 计算 K 和 V。生成 N 个 token 时,这种重复会累积得非常夸张。
不使用 KV Cache 时发生了什么?
对于已经出现过的历史 token,它们的 K/V 在此前步骤里其实已经算过了。但如果没有缓存,模型在每一步解码时仍要重新计算这些历史 token 的 K/V。
这会带来两个问题:
- 计算被重复执行
- 序列越长,代价增长越快
从注意力机制的角度看,这类开销会随着序列长度呈平方级增长。
使用 KV Cache 后发生了什么?
KV Cache 的做法很直接:
- 在 prompt 的 prefill 阶段,先为每个 token 计算各层、各注意力头的 K/V
- 把这些 K/V 存起来
- 后续每一步解码时,只需要为新 token 计算 Query,并读取历史缓存中的 K/V 参与注意力计算
- 当前新 token 对应的 K/V 再追加进缓存,供下一步继续使用
也就是说,历史 token 的 K/V 只算一次,之后反复复用。
一个简化流程
- Prefill:模型完整处理输入 prompt,生成并缓存每层每头的 K/V
- Decode:每生成一个新 token,只计算这个 token 的 Query,并与缓存中的 K/V 做注意力
- Cache Update:把新 token 的 K/V 追加到缓存里
这也是为什么 KV Cache 主要作用在单个请求内部的解码阶段。它不是跨请求复用,而是让同一个请求的后续生成不必重复劳动。
为什么大模型推理成本会在规模化时迅速上升?
结论是:推理成本的核心不是只有 FLOPs,很多场景下瓶颈来自内存带宽、缓存增长和批处理效率。上下文越长、并发越高,KV Cache 越容易变成主要显存消费者。
为什么解码阶段经常是内存带宽受限?
直觉上很容易以为“换一张算力更强的 GPU 就会更快”,但大模型解码不是这样。
在解码阶段,模型一次只生成一个 token。为了生成这一个 token,系统要做的事情是:
- 读取模型权重
- 读取已有 KV Cache
- 进行相对有限的计算
这里的关键是算术强度(arithmetic intensity):每读取一个字节数据,实际做了多少计算。
- Prefill 阶段:算术强度高,通常更偏计算受限
- Decode 阶段:算术强度低,更偏内存受限
所以很多线上推理系统的表现是:
- 显存流量很高
- 计算单元利用率却不高
- 升级更高 FLOPs 的 GPU 收益有限,因为瓶颈在内存 I/O
为什么上下文一长,KV Cache 就会变成显存大户?
每增加一个 token,注意力层都要为它存储对应的 K/V。这个增长是线性的,并且会乘上:
- batch size
- sequence length
- layer 数
- KV head 数
- head dimension
- 缓存精度位数
KV Cache 显存占用可以近似写成:
KV Cache Memory ≈ 2 × B × S × L × Hkv × D × Q
其中:
B:batch sizeS:sequence lengthL:layer 数Hkv:key/value head 数D:head dimensionQ:每个缓存元素的 bit 数- 前面的
2表示同时存 Key 和 Value
这个公式最重要的结论不是推导本身,而是两个工程事实:
- KV Cache 随 batch size 线性增长
- KV Cache 随序列长度线性增长
因此,一旦进入长上下文、高并发场景,缓存很容易与模型权重同量级,甚至超过模型权重。
一个具体量级
以 Llama-2-7B 为例,半精度下有估算显示,KV Cache 大约需要 0.5 MB / token。
那么如果系统里总共有 28,000 个活跃 token,KV Cache 大约就是:
28,000 × 0.5 MB ≈ 14,000 MB ≈ 14 GB
14 GB 已经和很多 7B 模型的 FP16 权重处在同一量级。也就是说,评估一个推理工作负载能不能放进 GPU,不能只盯着参数量,KV Cache 同样是主预算项。
KV Cache 到底能带来多大收益?
结论是:收益通常很大,尤其在长上下文和持续生成场景;但收益大小依赖请求结构、前缀复用程度、内存管理方式以及引擎实现。
几个具有代表性的结果如下:
| 优化项 | 结果 |
|---|---|
| 纯 KV Cache 加速生成 | 在 T4 GPU 的一组基准中,生成速度提升 5.21×,从 1 分 1 秒 缩短到 11.7 秒 |
| 提前复用 KV Cache | 某些共享系统提示词场景下,TTFT 最多可提升 5× |
| H100 + CPU offload 复用缓存 | TTFT 最多可提升 14× |
| 持续批处理 + vLLM 内存优化 | 吞吐最高可提升 23× |
| PagedAttention | 在相近延迟下,吞吐可提升 2–4× |
| NVFP4 KV Cache 量化 | 相比 FP8,KV Cache 显存占用最多降低 50%,某些基准中精度损失 <1% |
这些数字说明了一件事:KV Cache 本身很重要,但真正把收益放大的,往往是围绕 KV Cache 的整套调度与内存管理设计,比如分页、量化、持续批处理、前缀复用和分层卸载。
KV Cache 和 Prompt Cache 有什么区别?
结论:KV Cache 面向单个请求内部的解码复用;Prompt Cache(或 Prefix Cache)面向多个请求之间的公共前缀复用。两者不是替代关系,而是作用在不同层面。
KV Cache:单请求内部复用
在一次请求的生成过程中,历史 token 的 K/V 被重复使用。这是标准的解码缓存机制。
如果没有它,每生成一个 token,都要把历史 token 的 K/V 再算一遍。
Prompt Cache:跨请求复用固定前缀
Prompt Cache 复用的是静态前缀的 KV 状态,比如:
- system prompt
- 工具定义
- 固定参考文档
- 一段所有请求共享的模板上下文
如果多个请求前缀完全一致,那么后续请求可以跳过这部分 prefill,直接从已经缓存好的前缀状态继续计算。
限制条件非常严格
Prompt Cache 的前提是前缀完全一致。哪怕只差一个字符,也会 cache miss。
所以它的收益很依赖业务形态:
- 共享系统提示词明显的客服、助手类场景,效果通常很好
- 前缀动态变化频繁的场景,命中率会很低
满足条件时,Prompt Cache 对延迟和计算量的优化可能达到一个数量级。
为什么 KV Cache 会带来新的工程复杂度?
结论:KV Cache 节省了重复计算,但把问题转移成了显存管理问题。真正难的不是“开缓存”,而是“怎么让缓存不把系统拖死”。
1. 为什么内存碎片会成为大问题?
如果给每个序列分配一块连续显存区域,实际运行中很容易出现:
- 预留过多,浪费显存
- 请求长度差异大,导致碎片严重
- 序列结束后释放不整齐,难以高效复用
在长上下文和大 batch 下,这类浪费会很明显。传统连续分配方式的主要问题不是不能用,而是资源利用率太差。
2. 为什么需要缓存淘汰策略?
显存总是有限的。缓存不断增长时,服务引擎必须决定:
- 哪些 token 的缓存继续保留
- 哪些 token 的缓存可以丢弃
- 是否只保留近期 token
- 是否按注意力分数、层级重要性或熵等指标分配缓存预算
已有一些方法表明,只保留最近且重要的 token,在某些任务上可以把 KV Cache 占用降低约 2–5×,而质量损失很小甚至没有明显损失。典型思路包括:
- sliding-window attention
- attention-score-based eviction
- layer-aware / entropy-guided allocation
- H2O
- Scissorhands
但这类方法都不是零代价:一旦淘汰策略不合适,就可能影响输出质量。
3. 为什么“显存够”不代表“吞吐就高”?
即便容量足够,带宽也可能先成为瓶颈。
每一步解码都需要读取:
- 模型权重
- 对应的 KV Cache block
因此生成速度经常取决于 memory I/O,而不是理论算力上限。
一些推理架构会把 prefill 和 decode 拆到不同 worker 上:
- prefill 更偏计算密集
- decode 更偏内存密集
两者之间通过高速 RDMA 传输 KV Cache,以减轻单个 worker 的综合压力。
4. 为什么闲置会话也会吃掉大量资源?
聊天、RAG、Agent 任务都有一个常见特点:用户会停顿。
用户暂停期间,请求虽然不在持续生成,但历史 KV Cache 往往还留在 GPU 上。大量闲置会话堆积后,会明显侵占活跃请求的容量。
因此才有 KV Cache offloading:
- 把闲置或可复用的 KV block 移到 CPU 内存
- 或写入磁盘
- 或放入远端存储
- 需要时再加载回来
多层缓存系统在多轮问答和 RAG 中可带来 3×–10× 的延迟节省,但前提是回载速度比重算更划算。否则就不是优化,而是额外延迟。
5. 为什么更细粒度的块可以提升复用,但也会增加复杂度?
如果缓存块比较小,那么部分重叠前缀更容易共享缓存。某些实现会把 block size 从 64 tokens 降到 8 tokens,在 LLAMA70B 上报告过 最高 7% 的 TTFT 改善。
但细粒度也会带来:
- 更多元数据
- 更复杂的 block table
- 更高的调度和管理成本
这类优化适合追求极致吞吐和前缀复用的大规模服务,不一定适合所有部署。
vLLM、PagedAttention 和持续批处理为什么重要?
结论:现代推理系统要把 KV Cache 用好,通常至少要同时优化三件事:内存布局、批处理方式、面向硬件的执行内核。vLLM 的代表性价值就在这里。
vLLM 的 PagedAttention 解决了什么?
PagedAttention 的思路很像操作系统的虚拟内存。
它不再为每个序列预留一整块连续显存,而是把 KV Cache 切成固定大小的 page 或 block,例如 16 tokens 一块。然后通过 block table 把逻辑 token 位置映射到 GPU 中的物理位置。
这样做有几个直接效果:
- 按需分配,而不是提前整块预留
- 序列结束后可以及时释放 block
- 显著减少显存碎片
- 提高缓存利用率
有数据表明,vLLM 的 KV Cache 内存浪费可以控制在 4% 以下,这比传统连续分配方式好得多。
代价是实现复杂度上升:
- 需要 block table
- 需要缓存管理器
- 需要更复杂的调度逻辑
如果只是做简单实验,这一套可能显得重;但如果要做高吞吐服务,它基本已经是主流方向。
持续批处理为什么比静态批处理更适合 LLM 服务?
静态批处理的问题很明显:一批请求一起进 GPU,但它们生成长度不同。
假设一个 batch 里:
- 有些请求很快就结束
- 有些请求还要继续生成很长时间
那么短请求结束后留下的计算槽位就空了,但系统通常得等最长的那个请求完成,才能开始下一批。结果就是 GPU 容量被浪费。
持续批处理,也叫 iteration-level scheduling,每一步解码都可以更新 batch:
- 某个请求完成,就立刻塞入新请求
- 不必等整个 batch 全部结束
- GPU 利用率更高
- 空转时间更少
这也是很多高吞吐引擎能把性能做上去的关键原因之一。
哪些场景下 KV Cache 的收益没那么明显?
结论:KV Cache 几乎总是有帮助,但不是任何场景都能得到同等量级的收益。短序列、小模型、显存紧张、前缀动态变化大或本身使用局部注意力的模型,收益都会打折。
短序列或小模型
当序列很短、batch 很小、模型也不大时,被避免的重复计算本来就有限。
这种情况下,KV Cache 仍然可能有用,但提升通常不如长上下文生成那样显著。
显存非常紧张
在小显存 GPU 上,KV Cache 要和模型权重、运行时缓冲区、其他请求争资源。
一旦出现:
- 高频淘汰
- 频繁重算
- 频繁 offload / reload
缓存带来的收益就会被抵消。量化和卸载能缓解问题,但不能让 KV Cache 预算凭空消失。
Prompt 变化高度动态
这主要影响的是 Prompt/Prefix Cache,而不是单请求内部的 KV Cache。
如果请求的开头部分频繁变化,跨请求复用的命中率就会很低,前缀缓存的收益自然有限。
使用局部注意力或滑动窗口注意力的模型
像 Mistral-7B 这类使用 local/sliding-window attention 的模型,更依赖滚动缓存。
当注意力窗口之外的老 token 不再直接参与当前计算时,把很久以前的全部 K/V 都长期保留,收益就未必高。虽然早期 token 仍可能通过更深层的表示间接影响后续层,但直接缓存收益会下降。
卸载目标存储层太慢
Offloading 只有在“取回缓存”比“重新计算”更快或更便宜时才成立。
如果 CPU、磁盘或远端存储已经很慢,或者带宽饱和,那么 offload 反而会放大延迟。
怎么判断是否应该重点投入 KV Cache 优化?
可以直接看下面这张判断表。
| 场景 | 是否值得重点优化 KV Cache | 原因 |
|---|---|---|
| 长上下文对话 | 是 | 历史 token 多,重复计算可观 |
| RAG 多轮问答 | 是 | 常有长前缀和会话状态保留需求 |
| Agent 工作流 | 是 | 多轮调用、暂停恢复频繁,缓存和卸载都重要 |
| 共享 system prompt 的 SaaS 助手 | 是 | Prefix Cache 命中率可能很高 |
| 小模型、短输出、低并发 | 一般 | 重复计算不多,收益可能有限 |
| 显存极小且无法量化/卸载 | 谨慎 | 缓存和权重争抢资源,可能得不偿失 |
| 局部注意力模型 | 视实现而定 | 全量历史缓存未必总有高收益 |
设计推理系统时,应该优先关注哪些点?
如果目标是规模化部署,通常要同时关注下面几件事:
-
区分 prefill 和 decode 的瓶颈
- prefill 多半偏计算
- decode 多半偏内存带宽
-
把 KV Cache 视为一等资源
- 不要只算模型权重显存
- 要按 batch、上下文、并发会话估算缓存增长
-
优先采用分页式缓存管理
- 尤其在长序列和动态 batch 场景下
-
用持续批处理提高 GPU 利用率
- 静态批处理对生成长度差异非常敏感
-
对公共前缀开启 Prefix Cache
- 但要先确认业务前缀是否真的稳定
-
为显存约束场景准备量化、淘汰和卸载方案
- 这些不是附加功能,而是线上系统常用的保命手段
小结
大模型推理正在越来越像一个内存工程问题,而不只是算力问题。KV Cache 的价值在于,它用额外显存换掉了解码阶段的大量重复计算,这是当前大模型服务里最划算的一笔交换之一。
但工程上不能只看到“缓存能加速”,还得同时看到另一面:
- 缓存会线性增长
- 可能与模型权重同量级
- 会暴露碎片、淘汰、带宽、卸载、复用粒度等一整套系统问题
因此,真正高效的推理服务通常不会只停留在“开启 KV Cache”,而是进一步结合:
- PagedAttention
- continuous batching
- prefix caching
- cache quantization
- cache eviction
- cache offloading
如果系统面对的是长上下文、RAG、多轮对话或 Agent 场景,那么 KV Cache 不是可选优化,而是基础设施的一部分。区别只在于:是粗糙地用,还是把它当成核心调度对象认真设计。
关于
关注我获取更多资讯