KV Cache 完全解析:大模型推理提速的第一功臣
KV Cache 完全解析:大模型推理提速的第一功臣
上一篇《一次 LLM 推理的完整旅程》里反复出现一个词:KV Cache。Prefill 阶段产出它,Decode 阶段每一步都要读它,显存被它吃掉大半,PagedAttention、GQA、前缀缓存全都围着它转。
这一篇就把它彻底讲透:它缓存的到底是什么?为什么只缓存 K 和 V、不缓存 Q?它凭什么把推理速度提升一个数量级,又为什么成了显存的头号杀手?
一、问题的起点:自回归生成的重复计算
Transformer 大模型生成文字是逐字预测的:
- 输入一句话 → 输出第 1 个 token;
- 把原文 + 刚输出的第 1 个 token 再喂进去 → 输出第 2 个 token;
- 循环直到结束。
每一轮都要执行自注意力(Self-Attention):对所有历史 token 生成 Query、Key、Value 三组向量,用 Q 和所有 K 做点积打分,再用分数加权 V 得到输出。
问题来了:每生成一个新字,都要把全部历史 token 的 K、V 从头重算一遍。生成第 100 个 token 时重算前 99 个的 K/V,生成第 500 个时重算前 499 个——而这些值和上一轮算出来的一模一样。纯粹的重复劳动,越往后越卡,整段生成的注意力计算量随长度平方级增长。
KV Cache 的思路简单到近乎理所当然:算过的就存下来,别再算了。 每个新 token 只计算自己的 K、V,追加进显存里的缓存;历史 token 的 K、V 直接复用。一次乘法,终身受益。
就这一个改动,推理速度提升几倍到十几倍,成为所有线上大模型服务的必备优化——聊天机器人、代码补全、本地部署 LLM,只要是自回归生成,无一例外。
二、先补底层:Q、K、V 到底是什么
要理解"为什么只缓存 K 和 V",得先弄清这三个向量各自的分工。
从文字到向量
每个 token 先被转换成固定维度的词嵌入向量(embedding)——一个代表语义的数字数组。模型内置三组独立的可训练权重矩阵 WQW_QWQ、WKW_KWK、WVW_VWV,把嵌入矩阵 XXX(全部历史 token 打包)分别投影成三套向量:
Q=XWQ,K=XWK,V=XWV Q = X W_Q,\quad K = X W_K,\quad V = X W_V Q=XWQ,K=XWK,V=XWV
一次矩阵乘法,每个 token 就有了自己的一组 (Q, K, V)。
三者的分工:一次数据库检索
最直观的类比是数据库查询:
- Q(Query,查询向量):你的搜索关键词——代表当前正在计算的 token:“我是谁,我要找什么样的上下文”;
- K(Key,键向量):数据库里每条记录的索引标签——每个历史 token 的特征摘要,专门用来被 Q 匹配;
- V(Value,值向量):每条记录的完整正文——历史 token 携带的实际内容信息。
检索流程:拿关键词(Q)和所有索引(K)做内积打分 → 分数过 softmax 归一化成权重 → 按权重加权求和所有正文(V)→ 得到当前 token 的输出特征。
一个具体例子:上下文是「猫咪在窗边睡觉」,当前要处理的词是"它"。"它"的 Q 和"猫咪"的 K 点积分数最高,softmax 后"猫咪"的 V 占最大权重——模型由此知道"它"指代猫。K 负责打分匹配,V 负责提供内容,缺一不可、分工明确。
走一遍完整流程
上下文:[我, 今天, 吃, 了, 草莓],预测下一个字:
- 5 个 token 转成嵌入矩阵 X;
- X 分别乘 WQW_QWQ、WKW_KWK、WVW_VWV → 各得 5 行 Q、K、V 向量;
- 取最后一个 token「草莓」的 Q,和 5 个 K 逐一算相似度;
- 相似度加权融合 5 个 V → 输出特征 → 采样出下一个字(比如「很甜」)。
注意第 3 步:真正被用到的 Q 只有最后一个 token 的。这是全文最关键的伏笔。
三、为什么只缓存 K、V,不缓存 Q?
自回归生成时,我们只需要预测下一个 token。查询的主体永远是"最新的那一个 token"——它的 Q 去匹配全部历史的 K 和 V。
而历史 token 的 Q 呢?它们在各自"当过一次新 token"时用过一次,之后再也不会被任何计算引用。缓存它们没有任何意义。
反观 K 和 V:每个历史 token 的 K/V 在之后的每一步都要被新 token 的 Q 匹配、加权——它们才是被反复读取的"资产"。
一句话:Q 是一次性的查询请求,K/V 是被反复查询的数据库。 所以缓存叫 KV Cache,而不是 QKV Cache。
四、KV Cache 的工作流程
把缓存机制套进生成过程,对比一下:
没有 KV Cache(以「我今天吃了草莓」→ 续写「很甜」为例):
- 生成「很」:把 5 个历史 token 全部重新过一遍 WKW_KWK、WVW_VWV,再算注意力;
- 生成「甜」:把 6 个 token(含刚生成的「很」)又全部重算一遍 K/V;
- 每一步都从头来,上下文越长每步越慢。
有 KV Cache:
- Prefill 阶段:5 个输入 token 一次并行算出 5 组 K/V,存入显存缓存;
- 生成「很」:只算「很」自己的 1 组 K/V,追加进缓存;注意力直接用缓存里的 6 组 K/V;
- 生成「甜」:只算「甜」的 1 组,复用前面 7 组;
- 历史 token 的 K/V 全程零重算。
每一步的计算量从"正比于上下文长度"降到"一个 token 的常数量",整段生成的注意力计算从平方级降回线性级。
补充:多头注意力下也一样
实际大模型用的是多头注意力(Multi-Head Attention):把 WQ/WK/WVW_Q/W_K/W_VWQ/WK/WV 拆成多组独立的头,每头各自生成一套 QKV、单独做匹配,最后拼接融合——相当于多路并行检索,捕捉更细粒度的语义关联。KV Cache 的逻辑完全不变,只是每层要为每个头都存一份 K/V。这个"每层 × 每头"的乘法,正是下一节显存账单的来源。
五、天下没有免费的午餐:显存账单
KV Cache 的本质是用显存换算力。省下的计算实实在在,付出的显存也实实在在。算一笔账:
每个 token 需要缓存的字节数 =
2×层数×KV头数×每头维度×每元素字节数 2 \times \text{层数} \times \text{KV头数} \times \text{每头维度} \times \text{每元素字节数} 2×层数×KV头数×每头维度×每元素字节数
(开头的 2 = K 和 V 各一份。)
以 Llama-3-70B 为例:80 层 × 8 个 KV 头 × 128 维 × 2 字节(FP16),乘上 K 和 V 两份:
2×80×8×128×2≈327,680 字节≈320KB / token 2 \times 80 \times 8 \times 128 \times 2 \approx 327{,}680 \text{ 字节} \approx 320\text{KB / token} 2×80×8×128×2≈327,680 字节≈320KB / token
- 一条 8K token 的对话 → 约 2.6 GB 显存,只为这一条请求的缓存;
- 128K 长上下文 → 约 40 GB——比很多整卡显存还大;
- 线上服务同时挂几十条并发,KV Cache 轻松超过模型权重本身的占用。
由此得出 KV Cache 的两大缺点:
- 显存占用随长度线性暴涨:万字长文、超长对话很容易把显存顶爆(OOM),并发量直接受限于缓存能塞下多少;
- 越长越慢:decode 每一步都要把整个缓存从显存读出来参与注意力计算,上下文越长,每步搬运的数据越多——这正是上一篇说的 memory-bound 瓶颈的一部分,长对话"越聊越慢"的直接原因。
六、围绕 KV Cache 的优化生态
正因为 KV Cache 同时是速度的功臣和显存的杀手,围绕它长出了一整套优化技术。按思路分四类:
1. 管得更好:PagedAttention
传统做法为每条请求预留一整段连续显存(按最大长度预留),实际用不满,碎片浪费惊人。PagedAttention 借鉴操作系统的内存分页:把 KV Cache 切成固定大小的小块(block),按需分配、离散存放,用"页表"记录逻辑顺序。显存碎片几乎归零,同一张卡能塞下数倍的并发请求。这是 vLLM 的成名作,如今 SGLang、TensorRT-LLM 等主流框架均已标配。
2. 存得更小:量化与结构压缩
- KV Cache 量化(INT8/INT4):K/V 张量从 FP16 降到低精度存储,显存减半甚至减至 1/4,精度损失轻微;
- MQA / GQA / MLA:从模型结构上砍 KV 头数——MQA 所有头共用一组 K/V,GQA 分组共用(Llama 系的选择,70B 从 64 个 Q 头压到 8 个 KV 头,缓存直接缩到 1/8),DeepSeek 的 MLA 更进一步把 K/V 压成低秩隐向量。头数少了,上面公式里的乘数就小了。
3. 丢得聪明:滑动窗口
超长对话只保留最近 N 个 token 的 K/V,久远历史直接丢弃,把显存占用钉在一个固定上限内(Mistral 等模型采用)。代价是模型"记不住"窗口之外的内容,适合只依赖近期上下文的场景。
4. 用得更值:前缀缓存与投机解码
- Prefix Caching(前缀缓存):请求结束后不立即释放缓存,相同前缀(system prompt、多轮对话历史)的下一条请求直接复用,跳过大部分 prefill——API 厂商"缓存命中的输入便宜 10 倍"就是它;
- 投机解码(Speculative Decoding):小模型先猜几个 token、大模型一次并行验证,验证过程同样离不开 KV Cache 的支撑,进一步压低单 token 的生成耗时。
七、总结
把整篇压缩成五句话:
- 自回归生成会反复重算历史 token 的 K/V,KV Cache 用"算过就存"把平方级计算降回线性;
- 只缓存 K/V 不缓存 Q,因为 Q 是一次性的查询、K/V 是被每一步反复读取的数据库;
- 代价是显存随上下文线性暴涨(70B 级模型约每 token 数百 KB),长对话越聊越慢、越聊越占;
- PagedAttention 管碎片、量化和 GQA/MLA 压体积、滑动窗口设上限、前缀缓存复用成果——现代推理引擎的半壁江山都在伺候这块缓存;
- 理解了 KV Cache,就理解了大模型推理为什么快、为什么贵、以及优化的主战场在哪里。
它可能是整个大模型推理栈里性价比最高的一个 idea:一行"存下来别重算"的朴素直觉,撑起了今天所有线上 LLM 服务的可用性。
更多推荐




所有评论(0)