KV Cache 如何显著降低大模型推理成本

讲清 KV Cache 在大模型推理中的工作机制、性能收益与工程代价,帮助判断何时该用缓存、如何控制显存增长,以及如何结合分页注意力与持续批处理优化吞吐。

阅读时长: 11 分钟
共 5179字
作者: eimoon.com

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。

这会带来两个问题:

  1. 计算被重复执行
  2. 序列越长,代价增长越快

从注意力机制的角度看,这类开销会随着序列长度呈平方级增长。

使用 KV Cache 后发生了什么?

KV Cache 的做法很直接:

  • 在 prompt 的 prefill 阶段,先为每个 token 计算各层、各注意力头的 K/V
  • 把这些 K/V 存起来
  • 后续每一步解码时,只需要为新 token 计算 Query,并读取历史缓存中的 K/V 参与注意力计算
  • 当前新 token 对应的 K/V 再追加进缓存,供下一步继续使用

也就是说,历史 token 的 K/V 只算一次,之后反复复用。

一个简化流程

  1. Prefill:模型完整处理输入 prompt,生成并缓存每层每头的 K/V
  2. Decode:每生成一个新 token,只计算这个 token 的 Query,并与缓存中的 K/V 做注意力
  3. 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 size
  • S:sequence length
  • L:layer 数
  • Hkv:key/value head 数
  • D:head dimension
  • Q:每个缓存元素的 bit 数
  • 前面的 2 表示同时存 Key 和 Value

这个公式最重要的结论不是推导本身,而是两个工程事实:

  1. KV Cache 随 batch size 线性增长
  2. 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 最多可提升
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 命中率可能很高
小模型、短输出、低并发 一般 重复计算不多,收益可能有限
显存极小且无法量化/卸载 谨慎 缓存和权重争抢资源,可能得不偿失
局部注意力模型 视实现而定 全量历史缓存未必总有高收益

设计推理系统时,应该优先关注哪些点?

如果目标是规模化部署,通常要同时关注下面几件事:

  1. 区分 prefill 和 decode 的瓶颈

    • prefill 多半偏计算
    • decode 多半偏内存带宽
  2. 把 KV Cache 视为一等资源

    • 不要只算模型权重显存
    • 要按 batch、上下文、并发会话估算缓存增长
  3. 优先采用分页式缓存管理

    • 尤其在长序列和动态 batch 场景下
  4. 用持续批处理提高 GPU 利用率

    • 静态批处理对生成长度差异非常敏感
  5. 对公共前缀开启 Prefix Cache

    • 但要先确认业务前缀是否真的稳定
  6. 为显存约束场景准备量化、淘汰和卸载方案

    • 这些不是附加功能,而是线上系统常用的保命手段

小结

大模型推理正在越来越像一个内存工程问题,而不只是算力问题。KV Cache 的价值在于,它用额外显存换掉了解码阶段的大量重复计算,这是当前大模型服务里最划算的一笔交换之一。

但工程上不能只看到“缓存能加速”,还得同时看到另一面:

  • 缓存会线性增长
  • 可能与模型权重同量级
  • 会暴露碎片、淘汰、带宽、卸载、复用粒度等一整套系统问题

因此,真正高效的推理服务通常不会只停留在“开启 KV Cache”,而是进一步结合:

  • PagedAttention
  • continuous batching
  • prefix caching
  • cache quantization
  • cache eviction
  • cache offloading

如果系统面对的是长上下文、RAG、多轮对话或 Agent 场景,那么 KV Cache 不是可选优化,而是基础设施的一部分。区别只在于:是粗糙地用,还是把它当成核心调度对象认真设计。

关于

关注我获取更多资讯

月球基地博客公众号二维码,扫码关注获取更多 AI 与编程资讯
📢 公众号
月球基地博客作者个人微信二维码,扫码交流 AI 与编程话题
💬 个人号
使用 Hugo 构建
主题 StackJimmy 设计