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

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

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

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

  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 构建
主题 Stack 由 Jimmy 设计