KV-Cache 这一个核心概念
本文由 AI 生成,内容可能存在错误或不准确之处,请结合可靠来源核验后再作参考。
NOTE
阅读目标:这篇文章讲清楚 KV-Cache 这一个核心概念:
- 为什么要有 KV-Cache
- 它到底存什么
- 它有多大(痛点)
- 为什么它是端侧 LLM 的命门
- 围绕它的优化技术
搞清楚 KV-Cache,端侧 LLM 优化一大半技术(PagedAttention / KV 量化 / StreamingLLM / GQA / MLA)就都懂了。
1. 先回忆 LLM 是怎么”生成”文字的
LLM 生成文字不是一次性吐出整句话,而是一个字一个字地生成。每次生成一个字(token)都要做一遍完整推理。
比如用户问:”今天天气“,模型要回答:”真好“:
1 | 步骤 1: 输入"今天天气" → 模型吐出"真" → 输出:"今天天气真" |
每一步都要把”整串历史”重新喂进去跑一遍。这就是 LLM 的”自回归生成”。
2. 不用 KV-Cache 会怎样——重复计算的大坑
Attention 机制里有三个矩阵 Q、K、V:
$$
\begin{aligned}
Q &= XW_Q, \\
K &= XW_K, \\
V &= XW_V.
\end{aligned}
$$
关键事实:在因果注意力中,历史 token 在每一层对应的 $K$ 和 $V$ 一旦计算完成,后续追加的新 token 就不会再改变它们。也就是说:
IMPORTANT
第 5 个 token 的 $K$ 和 $V$,从它生成的那一刻开始,到整个序列结束都不会改变。
但如果不用 KV-Cache,会发生什么?
用一个具体例子看
假设我们生成一句 10 个字的话:
| 生成第几字 | 输入 | 要算什么 K/V |
|---|---|---|
| 第 1 字 | $[1]$ | 1 个 token 的 K, V |
| 第 2 字 | $[1,2]$ | 重新算 1, 2 两个 token 的 K, V |
| 第 3 字 | $[1,2,3]$ | 重新算 1, 2, 3 三个 token 的 K, V |
| 第 4 字 | $[1,2,3,4]$ | 重新算 1-4 四个 token 的 K, V |
| … | … | … |
| 第 10 字 | $[1,2,\ldots,10]$ | 重新算 10 个 token 的 K, V |
总计算量为:
$$
1+2+3+\cdots+10=\frac{10\times11}{2}=55.
$$
但这 55 次里,token 1 的 K/V 被算了 10 遍,token 2 被算了 9 遍,……全都是一模一样的重复劳动。
生成 $N$ 个 token 时,总计算复杂度为 $O(N^2)$。生成 1000 个 token,相当于白白多算约 50 万次。
WARNING
同一个 token 的 $K$、$V$ 被反复重算几十甚至上百次,而结果完全相同。这是巨大的浪费。
3. KV-Cache 的核心思想——“算过一次就记下来”
既然 K 和 V 一旦算出来就不变,那把它们存起来,下次要用时直接读,就不用重算了。
这就是 KV-Cache:一个专门存历史 K 和 V 的内存区域。
有了 KV-Cache 之后的流程
| 生成第几字 | 对新 token 做什么 | 对历史 token 做什么 |
|---|---|---|
| 第 1 字 | 算它的 K, V,存进 KV-Cache | 无 |
| 第 2 字 | 算新 token 的 K, V,追加进 KV-Cache | 从 KV-Cache 读前面的 K, V |
| 第 3 字 | 算新 token 的 K, V,追加进 KV-Cache | 从 KV-Cache 读前面的 K, V |
| … | … | … |
| 第 N 字 | 算新 token 的 K, V,追加 | 从 KV-Cache 读前面 N-1 个 |
总计算量变为:
$$
\underbrace{1+1+\cdots+1}_{N\text{ 项}}=N.
$$
计算复杂度从 $O(N^2)$ 降到 $O(N)$。生成 1000 个 token 时,只需计算 1000 次 $K/V$,而不是约 50 万次。
打个比方
TIP
不用 KV-Cache,就像每写一个字都把前面所有字重新抄一遍,然后才写新字。
使用 KV-Cache,则可以直接查看稿纸上已经写好的内容,只需专心写下一个字。
4. KV-Cache 里具体存什么
很多人会疑惑:”为什么叫 KV-Cache 不叫 QKV-Cache?为什么不存 Q?”
为什么只存 K, V,不存 Q
回头看 Attention 公式:
$$
\operatorname{Attention}_i
=\operatorname{softmax}\left(\frac{Q_iK^\mathsf{T}}{\sqrt{d}}\right)V.
$$
- Q:只当前这个字的 Q 要参与计算。下一个字会有自己的 Q,旧的 Q 扔了就扔了,没人用
- K, V:所有历史 token 的 K 和 V 都要参与当前字的 Attention 计算
所以只需要 cache K 和 V。
KV-Cache 的形状
每一层、每一个head(注意力头)都要存:
1 | KV-Cache 结构: |
KV-Cache 总大小的公式为:
$$
M_{\mathrm{KV}}=2LHNd\cdot b.
$$
其中:
- $2$ 表示 $K$ 和 $V$ 两份缓存
- $L$ 表示 Transformer 层数
- $H$ 表示 KV 注意力头数
- $N$ 表示当前序列长度
- $d$ 表示每个 head 的向量维度
- $b$ 表示每个元素占用的字节数
5. KV-Cache 到底多大(痛点来了)
Qwen-7B 的例子
参数为 $L=32$ 层、$H=32$ 个 KV head、$d=128$ 维,并使用 FP16($b=2$ 字节)。
| 序列长度 N | KV-Cache 大小 |
|---|---|
| 512 | 256 MB |
| 2,048 | 1 GB |
| 8,192 | 4 GB |
| 32,768 | 16 GB |
| 131,072 | 64 GB |
WARNING
**这是个惊人的数字。**Qwen-7B 权重经 W4 量化后约为 3.5 GB,而 KV-Cache 在长上下文下甚至比权重还大。手机通常只有 8–16 GB 内存,还需要为 OS 和 App 预留空间。
对比权重大小
| 模型 | 权重大小(W4) | KV-Cache 大小(8K 上下文,FP16) |
|---|---|---|
| Qwen-3B | ~1.5 GB | ~2 GB |
| Qwen-7B | ~3.5 GB | ~4 GB |
| Qwen-14B | ~7 GB | ~8 GB |
两者基本是 $1:1$ 的规模,因此端侧 LLM 的实际内存需求近似为:
$$
M_{\mathrm{total}}\approx M_{\mathrm{weights}}+M_{\mathrm{KV}}.
$$
6. 为什么 KV-Cache 是端侧 LLM 的命门
6.1 内存命门
手机内存本来就小(8-16GB,还要和 OS 抢),KV-Cache 一涨,很容易 OOM(out of memory)。
6.2 带宽命门(更关键)
Decode 阶段每吐一个 token,要做什么?
- 读完整权重一次:~ 3.5GB(W4)
- 读完整 KV-Cache 一次:~ 4GB(8K 上下文)
- 做几亿次乘加(其实很少)
- 写回 1 个 token 的新 K/V 到 KV-Cache(几十 KB)
核心观察:
- 计算量极少(一个 token 的 Attention 而已)
- 数据搬运量巨大(~ 7.5GB)
- 典型的 memory-bound
也就是说,decode 的速度完全由”每 token 要读多少字节”决定。KV-Cache 占的读带宽几乎和权重一样多。
6.3 具体感受一下速度上限
假设手机 DDR 带宽是 50 GB/s:
- 每个 token 要读 7.5 GB 的数据
- 理论最大速度约为 $50/7.5\approx6.7\ \text{tok/s}$
就算你的 NPU 算力无穷强,DDR 读 KV 这一步就把你限死在 6-7 tok/s。想更快,必须想办法少读点数据。这就是为什么 KV-Cache 优化技术如此多。
7. KV-Cache 衍生的优化技术
理解了 KV-Cache 的重要性,下面这些技术就都好懂了——全部是围绕”怎么让 KV-Cache 小一点、快一点、共享一点”。
7.1 减小 KV-Cache 本身
| 技术 | 思路 |
|---|---|
| KV 量化(INT8 / INT4) | 把 K 和 V 压缩,FP16 → INT4 直接省 4 倍 |
| GQA(Grouped Query Attention) | 多个 Q head 共享 K/V head,H 减 4-8 倍 |
| MQA(Multi-Query Attention) | 所有 Q 共享一组 KV,极限压缩 |
| MLA(Multi-head Latent Attention) | DeepSeek 发明,把 KV 压到潜空间,再解压 |
7.2 聪明地管理 KV-Cache
| 技术 | 思路 |
|---|---|
| PagedAttention | 把 KV 切成”页”,按需分配,避免预留浪费 |
| Prefix Caching | 多个对话的公共开头(system prompt)共用一份 KV |
| StreamingLLM / H2O | 长对话时丢掉不重要的老 KV,只保留最近和极少数关键 KV |
| KV Eviction | 内存紧张时智能驱逐某些对话的 KV |
7.3 读 KV 时少读一点
| 技术 | 思路 |
|---|---|
| Sliding Window Attention | 每次只读最近 N 个 token 的 KV |
| Sparse Attention | 大部分 KV 不读,只读重要的 |
8. 一句话总结
IMPORTANT
KV-Cache 是 LLM 生成过程中保存下来的历史 $K$ 和 $V$。
为什么要有:LLM 每生成一个 token 都要回顾所有历史 token 的 $K$ 和 $V$。不使用缓存就要反复重算,复杂度为 $O(N^2)$;使用缓存后只需要 $O(N)$。
有什么用:让 LLM 生成速度从”几乎跑不起来”变成”可用”。
副作用:KV-Cache 本身会涨到几 GB,成为端侧 LLM 的第二大内存消耗和第二大带宽瓶颈(第一是权重)。
端侧 LLM 优化 = 解决权重瓶颈(量化)+ 解决 KV 瓶颈(量化 + 分页 + 共享 + 驱逐)。