1. KV-Cache 这一个核心概念
    1. 1. 1. 先回忆 LLM 是怎么”生成”文字的
    2. 2. 2. 不用 KV-Cache 会怎样——重复计算的大坑
      1. 2.1. 用一个具体例子看
    3. 3. 3. KV-Cache 的核心思想——“算过一次就记下来”
      1. 3.1. 有了 KV-Cache 之后的流程
      2. 3.2. 打个比方
    4. 4. 4. KV-Cache 里具体存什么
      1. 4.1. 为什么只存 K, V,不存 Q
      2. 4.2. KV-Cache 的形状
    5. 5. 5. KV-Cache 到底多大(痛点来了)
      1. 5.1. Qwen-7B 的例子
      2. 5.2. 对比权重大小
    6. 6. 6. 为什么 KV-Cache 是端侧 LLM 的命门
      1. 6.1. 6.1 内存命门
      2. 6.2. 6.2 带宽命门(更关键)
      3. 6.3. 6.3 具体感受一下速度上限
    7. 7. 7. KV-Cache 衍生的优化技术
      1. 7.1. 7.1 减小 KV-Cache 本身
      2. 7.2. 7.2 聪明地管理 KV-Cache
      3. 7.3. 7.3 读 KV 时少读一点
    8. 8. 8. 一句话总结

KV-Cache 这一个核心概念

本文由 AI 生成,内容可能存在错误或不准确之处,请结合可靠来源核验后再作参考。

NOTE

阅读目标:这篇文章讲清楚 KV-Cache 这一个核心概念:

  • 为什么要有 KV-Cache
  • 它到底存什么
  • 它有多大(痛点)
  • 为什么它是端侧 LLM 的命门
  • 围绕它的优化技术

搞清楚 KV-Cache,端侧 LLM 优化一大半技术(PagedAttention / KV 量化 / StreamingLLM / GQA / MLA)就都懂了。


1. 先回忆 LLM 是怎么”生成”文字的

LLM 生成文字不是一次性吐出整句话,而是一个字一个字地生成。每次生成一个字(token)都要做一遍完整推理。

比如用户问:”今天天气“,模型要回答:”真好“:

1
2
3
4
步骤 1: 输入"今天天气"      → 模型吐出"真"     → 输出:"今天天气真"
步骤 2: 输入"今天天气真" → 模型吐出"好" → 输出:"今天天气真好"
步骤 3: 输入"今天天气真好" → 模型吐出"!" → 输出:"今天天气真好!"
步骤 4: 输入"今天天气真好!" → 模型吐出 <END> → 生成结束

每一步都要把”整串历史”重新喂进去跑一遍。这就是 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
2
3
4
5
6
7
8
9
10
KV-Cache 结构:
├── 层 1
│ ├── Head 0: [K: N 个 token × d 维向量, V: N 个 token × d 维向量]
│ ├── Head 1: [K: ..., V: ...]
│ ├── ...
│ └── Head H-1
├── 层 2
│ ├── ...
├── ...
└── 层 L-1

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 瓶颈(量化 + 分页 + 共享 + 驱逐)