AB
AiBoss
Wiki

什么是 KV 缓存(KV Cache)?

KV 缓存(KV Cache)是自回归生成模型在逐 token 解码时复用键值向量计算结果的一种机制。Hugging Face Transformers 官方文档指出,它通过避免重复计算来降低计算开销、提升生成速度,并提供了多种 Cache 类以在显存占用与生成速度之间取舍。

KV 缓存(KV Cache)是自回归(autoregressive)生成模型在推理时使用的一种缓存机制:它把注意力计算中已经算出的键(key)向量与值(value)向量保存下来,供后续步骤直接复用,而不必每一步都重新计算。它要解决的问题是——自回归模型一次只预测一个 token,而每个新 token 的预测都依赖此前所有 token,如果每次都从头重算,就会产生大量重复计算。

为什么重要

要理解 KV 缓存的价值,需要先看清自回归生成的计算模式。按照 Hugging Face Transformers 官方文档的描述,键值(key-value,简称 KV)向量被用来计算注意力分数(attention scores)。对于自回归模型而言,KV 分数在每一次预测时都会被计算,因为模型一次只预测一个 token;而每一次预测又都依赖之前的 token,这就意味着模型在反复执行相同的计算。

这种重复在长序列生成时尤其昂贵。生成第 N 个 token 时,模型需要此前全部 token 的键和值;生成第 N+1 个 token 时,前 N 个 token 的键和值其实并没有变化,但如果不做缓存,它们仍会被重新算一遍。序列越长,这种浪费越大,直接体现为计算时间变长、响应速度下降。

KV 缓存的做法正是把「已经算过的东西」存起来。官方文档的表述是:KV 缓存存储这些计算结果,使它们可以被复用而无需重新计算;高效的缓存对优化模型性能至关重要,因为它减少了计算时间并改善了响应速度。换句话说,KV 缓存是一种用显存(或内存)换计算时间的权衡手段。

工作机制

从官方文档的描述出发,KV 缓存的核心逻辑可以拆成几个要点。

  • 缓存的对象是键和值。 注意力机制中,每个 token 会投影出查询(query)、键(key)、值(value)三组向量。查询用于「提问」,键用于「被匹配」,值用于「被加权求和」。在自回归解码中,历史 token 的键和值一旦算出就不再改变,因此它们是天然的缓存对象。
  • 缓存随生成过程增长。 每生成一个新 token,就把它对应的键和值追加进缓存;下一步计算注意力时,直接读取缓存中的历史键值,与新 token 的查询做运算即可,无需重算历史部分。
  • 缓存可以关闭。 官方文档说明,可以通过在 generate() 中配置 use_cache=False 来禁用缓存。这通常用于调试或对照,代价是失去复用带来的加速。
  • 缓存可以预先初始化。 文档指出,Cache 类也可以先被初始化,再传给模型的 past_key_values 参数。这样做便于更细粒度的控制,或用于上下文缓存(context caching)等更高级的用法。

官方文档还给出了若干 Cache 类的对比,用于帮助使用者按场景选择。文档中的对比表包含以下维度与结论:

缓存类型支持滑动层支持卸载(offloading)支持 torch.compile()预期显存占用
Dynamic Cache中等
Static Cache
Quantized Cache

这张表本身说明了 KV 缓存并非「一种」实现,而是一族策略:有的偏向节省显存,有的偏向最大化生成速度。官方文档明确表示,其中一些 Cache 类针对节省内存做了优化,另一些则旨在最大化生成速度。

默认缓存的行为

官方文档指出,DynamicCache 是所有模型的默认缓存类。它允许缓存大小动态增长,以便随着生成推进存储越来越多的键和值。

文档同时给出了一个重要的例外:对于使用滑动窗口注意力(sliding window attention)的模型(文档举例 Mistral、Gemma2)或分块注意力(chunked attention)的模型(文档举例 Llama4),当采用这些注意力类型的层达到其最大尺寸(即滑动窗口大小或分块大小)后,缓存将停止增长。这一点说明「缓存无限增长」并不是普遍规律,而是取决于注意力模式。

典型例子

官方文档给出的示例使用 meta-llama/Llama-2-7b-chat-hf 这一模型标识,配合 AutoTokenizerAutoModelForCausalLM 加载,并以半精度浮点(torch.float16)与 device_map="auto" 配置模型。示例的输入文本是 "I like rock music because",随后调用 model.generate(),参数中包含 do_sample=Falsemax_new_tokens=20 以及 use_cache=False

这个例子值得注意的地方在于:它演示的是关闭缓存的写法。也就是说,官方文档在介绍缓存策略的同一篇指南里,把 use_cache=False 作为一个可配置项展示出来,说明缓存是生成流程中一个显式可控的开关,而不是不可绕过的隐含行为。

文档还提到,Cache 类可以先初始化再传入模型的 past_key_values 参数,并指出这种做法在需要更细粒度控制、或需要实现上下文缓存等更高级用法时有用。这属于文档描述的能力与适用场景,而非某个具体性能数字。

边界与常见误解

第一,KV 缓存不是「免费加速」。它把重复计算换成了存储开销,因此显存占用会随生成序列变长而上升。官方文档的对比表也印证了这一点:不同缓存类型的预期显存占用被明确区分为低、中、高三档,其中 Quantized Cache 显存占用低,Static Cache 显存占用高。选择哪种缓存,本质上是在显存与速度之间做取舍。

第二,缓存并非在所有模型上都无限增长。如前所述,官方文档指出,对使用滑动窗口注意力或分块注意力的模型,相关层达到最大尺寸后缓存会停止增长。把「KV 缓存一定随序列线性增长」当作普遍结论是不准确的。

第三,缓存能力与编译优化、卸载等特性并不总是兼容。官方文档的对比表显示,Quantized Cache 不支持滑动层、不支持卸载、也不支持 torch.compile();Dynamic Cache 支持滑动层与卸载,但不支持 torch.compile();Static Cache 三者皆支持,代价是预期显存占用最高。这些是官方文档列出的能力矩阵,不应被简化为「某种缓存全面更优」。

第四,容易把 KV 缓存与「模型参数缓存」「提示缓存」混为一谈。就官方文档的语境而言,KV 缓存特指注意力计算中键值向量的复用;文档提到的上下文缓存(context caching)是借助预先初始化 Cache 类并传入 past_key_values 来实现的一种更高级用法,二者相关但不等同。

第五,需要区分「文档所述」与「普遍共识」。上述关于默认缓存类、对比表结论、滑动窗口与分块注意力的行为、以及示例代码,均来自 Hugging Face Transformers 官方文档的 Cache strategies 页面;它们描述的是该库的实现与建议,不代表所有推理框架的缓存设计都遵循同一套分类与默认值。

参考资料