很多人在第一次接触大语言模型推理时,都会被这些概念绕晕:
- Q、K、V 到底分别是什么
- O 又是什么
- Prefill 和 Decode 到底有什么区别
- 为什么只缓存 KV,不缓存 Q
- 为什么 Prefill 要整段算,Decode 却只算最后一个 token
- 推理过程中,内存里到底存了什么,哪些会保留,哪些会被丢掉
这篇文章不追求"数学最完整",而追求"工程直觉最清楚"。只用一个非常简单的例子:
I love you
来把整个过程从头讲明白。
一、先说结论:推理其实分成两个阶段
当用户输入:
I love you
模型要做的不是"重新预测 love 和 you",而是:
给定 I love you,预测下一个 token
整个推理通常分成两个阶段:
1. Prefill
把用户已经给出的 prompt,也就是 I love you,一次性送进模型,完整跑一遍前向传播。
2. Decode
模型根据 Prefill 的结果,生成第一个新 token。如果模型生成了一个新词,比如 too,接下来要继续预测下一个词,这时进入逐 token 的 Decode 阶段。
一句话概括:
- Prefill:处理"用户已经给的内容"
- Decode:处理"模型自己接着生成的内容"
二、Q、K、V、O 分别是什么
先把几个最核心的符号说清楚。
Q:Query
当前这个位置"拿什么去查询上下文"。
K:Key
每个历史 token 的"索引"或者"可匹配表示"。
V:Value
每个历史 token 真正携带的信息内容。
O:Output
当前这个位置做完 attention 之后得到的新表示。
如果用一句非常直白的话说:
- Q 像"我现在的问题"
- K 像"历史信息的标签"
- V 像"历史信息的内容"
- O 像"我把历史看完后,形成的新理解"
在 Transformer 的一层里,通常是:
输入 hidden states
-> 线性变换得到 Q/K/V
-> attention(Q, K, V)
-> 得到 O
-> O 再经过后续计算,作为下一层输入
注意一点:O 不是最终输出给用户的词,而是这一层 attention 的输出表示。真正预测词时,还要把最后一个位置的表示再投影到词表上。
三、为什么是多层网络,这一点决定了 Prefill 不能只算最后一个位置
这是很多人最容易误解的地方。
如果模型只有一层 attention,那么从纯数学上说,如果你的目标只是"预测下一个词",你似乎只需要最后一个位置的 Query 就够了。
但真实的大语言模型不是一层,而是很多层。这意味着:
- 第 1 层会得到一组输出表示
- 第 2 层的输入,就是第 1 层的输出
- 第 2 层会重新算自己的 Q/K/V
- 第 3 层再接着来,一直到最后一层
所以,前面位置的输出不是"没用",而是会成为下一层的输入。也就是说:
第 L 层的输出,会用于构造第 L+1 层的 Q/K/V。
这件事特别重要,因为它解释了:
- 为什么 Prefill 要整段跑
- 为什么不能只算最后一个位置
- 为什么多层模型里历史表示必须逐层建立起来
四、以 I love you 为例:Prefill 到底在算什么
现在正式进入例子。
用户输入 I love you,模型首先会把这三个 token 变成 embedding。这时内存中,最初有的是:
输入侧内存
token ids:I, love, you
embedding 向量
position 信息(位置编码或位置相关机制)
这时还没有 KV cache。现在开始 Prefill。
五、Prefill 的第 1 层:内存里会出现什么
进入第 1 层后,模型会基于这三个位置的输入 hidden states,一次性算出:
Q1, Q2, Q3
K1, K2, K3
V1, V2, V3
注意,这里是对整个 prompt 一次性并行计算的,因为用户给的 I love you 已经完整已知。
接下来会计算 attention,带 causal mask:
- 第 1 个位置只能看见自己
- 第 2 个位置可以看见前两个
- 第 3 个位置可以看见前三个
最终,第 1 层会输出:
O1:看完 I 之后,第 1 个位置的新表示
O2:看完 I love 之后,第 2 个位置的新表示
O3:看完 I love you 之后,第 3 个位置的新表示
此时,O1/O2/O3 会成为第 2 层的输入。
六、Prefill 的第 2 层以及后续层:为什么前面位置也必须算
进入第 2 层。第 2 层的输入不是原始 embedding 了,而是上一层的输出表示:
第 2 层输入位置 1:上一层的 O1
第 2 层输入位置 2:上一层的 O2
第 2 层输入位置 3:上一层的 O3
然后第 2 层再算自己的 Q/K/V,再做 attention,再得到 O1²、O2²、O3²。后面每一层都重复这个过程。
所以在 Prefill 期间,模型做的其实是:
整段 prompt
-> 第1层完整前向
-> 第2层完整前向
-> 第3层完整前向
-> ...
-> 最后一层完整前向
到最后一层时,我们会拿最后一个位置的表示去预测下一个 token。
这就是为什么:
- 从最终预测角度,确实只用最后一个位置
- 但从整个网络的计算角度,前面所有位置都必须参与
因为它们是构造"最后那个位置的深层上下文表示"的必要中间过程。
七、Prefill 的最后一步:怎么预测下一个词
假设模型有很多层,全部跑完以后,最后一层给出三个位置的最终表示。和"下一个词预测"相关的是:
最后一个位置的最终 hidden state
为什么只有它有用?因为你当前的任务是:
给定 I love you,预测下一个 token
而最后一个位置代表的是:完整看完 I love you 后形成的表示。
接下来,模型会把这个最后位置的表示送入输出层:
最后位置 hidden state
-> 词表投影(lm_head)
-> logits
-> softmax
-> 得到下一个 token 的概率分布
假设这一步模型预测出了 too。到这里,Prefill 就完成了。
八、Prefill 完成后,内存里到底保留什么
这是最核心的工程问题之一。
会保留的:KV Cache
对于每一层,模型会把这次 prompt 对应的历史 K 和 V 保留下来。如果模型有 N 层,那么 Prefill 结束后,内存里会保留:
第 1 层的 K1, K2, K3 和 V1, V2, V3
第 2 层的 K1, K2, K3 和 V1, V2, V3
...
第 N 层的 K1, K2, K3 和 V1, V2, V3
这就是 KV cache。注意:KV cache 不是一份全局 K/V,而是每一层各有一份。
不会长期保留的:Q
Q 只是当前 step 用来做一次 attention 计算的查询。用完以后,通常就没有复用价值了。
一般也不会长期保留的:中间 O
每一层的 O 主要用于继续流向下一层。一旦整个前向传播结束,它通常也不是后续 decode 反复复用的对象。
一句话总结:
Prefill 结束后,长期有价值的是"历史 token 在每一层上的 K/V",因为它们会在之后的 Decode 中被反复使用。
九、为什么只缓存 KV,不缓存 Q
K/V 会被未来反复使用
假设已经有了 I love you,然后模型生成了 too,接下来还会生成更多 token。未来每一步新的 token,都要去"看"前面的历史:
- 历史的 K 会被未来每一步新的 Query 反复匹配
- 历史的 V 会被未来每一步 attention 反复加权读取
所以历史 K/V 是典型的"会反复被访问的数据"。
Q 只在当前这一步用一次
第 4 个 token 的 Q4,只会用于这一次:Q4 与所有历史 K 做 attention。等这一步结束,下一步来的已经是 Q5 了。Q4 不会再被后面的 token 复用。
所以 Q 的生命周期非常短:生成出来 → 做一次 attention → 用完就可以扔掉。
因此,从工程上看:
- K/V 是值得缓存的长期资产
- Q 是一次性临时变量
十、进入 Decode:为什么这时只算新 token
Prefill 已经结束,模型预测出了第 4 个 token too。现在序列变成:
I love you too
接下来模型要预测第 5 个 token。这时已经不是"完整 prompt 的第一次处理"了,而是进入 Decode。
Decode 的核心特征是:每次只新增一个 token。
和 Prefill 最大的不同:
- Prefill:整个 prompt 一次性并行进入模型
- Decode:每次只新增一个位置
十一、Decode 时每一层到底怎么计算
假设现在要处理新 token too。
第 1 层
只需要对这个新 token 计算 Q4¹、K4¹、V4¹,然后做 attention:
Q4¹ 去和 [K1¹, K2¹, K3¹, K4¹] 做匹配
其中 K1¹、K2¹、K3¹ 来自 Prefill 留下的 cache,K4¹ 是当前这个新 token 刚刚算出来的。做完 attention 后,得到第 1 层的新输出 O4¹。
第 2 层
把 O4¹ 送到第 2 层,算 Q4²、K4²、V4²,再用:
Q4² 去和 [K1², K2², K3², K4²] 做 attention
历史 K/V 都来自 cache,只需要新算当前 token 对应的那一份。后面所有层都如此。
十二、Decode 每一步结束后,内存会发生什么变化
这一步算完以后,会有两类数据:
要丢掉的临时数据:
- 当前 step 的各层 Q4
- 当前 step 的很多中间 attention 临时张量
要追加到 cache 的长期数据:
- 每一层的 K4
- 每一层的 V4
于是 KV cache 会从历史长度 3 变成历史长度 4。
然后模型再拿最后一层第 4 个位置的表示去预测第 5 个 token。假设第 5 个 token 是 much,接下来进入下一轮 decode,重复同样的过程:只算新 token 的 Q5/K5/V5,历史 KV 全复用,Q5 用完就丢,K5/V5 追加进 cache。
十三、把整个推理流程用时间线梳理一遍
T0:用户输入到达
内存里有 token ids(I, love, you)、embedding、位置信息。此时还没有 KV cache。
T1:Prefill 开始
模型按层处理整个 prompt:每层都计算完整的 Q/K/V,得到完整的 O,上一层 O 作为下一层输入。
T2:Prefill 结束
内存里有每层关于历史三个 token 的 K/V cache。Q 和各层中间 O 通常不长期保留。真正保留的是所有层的历史 K/V。假设预测结果是 too。
T3:第一个 Decode step
模型处理新 token too:每层只为这个新 token 计算 Q4/K4/V4,用 Q4 读取历史 K cache,得到新输出,最后预测第 5 个 token。
T4:第一个 Decode step 结束
KV cache 长度从 3 变成 4,本 step 的 Q4 可以丢弃。
T5 及之后:
对每个新 token 重复同样过程,KV cache 持续增长。
十四、为什么 Prefill 和 Decode 的内存特征差别很大
Prefill 的特点:
- 一次性处理整个 prompt
- 计算量大,并行度高
- 会瞬间产生大量中间计算张量
- 更像"吞大段输入"
Decode 的特点:
- 一次只处理一个新 token
- 每一步计算量小很多,但步数会很多
- 核心瓶颈常常转向 KV cache 读取和显存带宽
工程上可以粗略理解为:
- Prefill 更偏"计算密集"
- Decode 更偏"内存访问密集"
这也是为什么很多推理系统会分别优化这两个阶段。
十五、为什么 KV cache 会越来越占内存
因为每生成一个新 token,KV cache 都会在每一层追加一份新的 K/V,cache 大小随已生成序列长度线性增长。
如果模型层数很多、注意力头很多、hidden size 很大、上下文很长,每一层都要存一长串历史 K/V,累计起来就会很可观。
你可以把它想成:
模型在每一层都维护了一份"历史记忆库",历史越长,记忆库越大。
十六、这套机制的本质直觉
如果把整个注意力过程类比成"查数据库":
| 概念 | 类比 |
|---|---|
| Query | 当前一步发起的查询请求 |
| Key | 历史记录的索引 |
| Value | 历史记录的内容 |
| KV cache | 已经建好的、可复用的历史数据库 |
| Decode | 每来一个新 token,就发起一次新的查询 |
大模型推理本质上很像:用当前 token 的 Query,去查询历史 token 在每一层上积累起来的 KV 记忆。
这也是为什么历史 K/V 要缓存,当前 Q 没必要缓存。
十七、推理过程中的内存曲线:从 Prefill 峰值到 Decode 线性增长
在实际的大模型推理过程中,内存占用并不是简单"逐步增长"的,而是呈现出一个非常典型的三阶段变化曲线:
0
→ 100(Prefill 峰值,包含大量临时 tensor)
→ 70(释放临时 tensor,仅保留 KV cache)
→ 71
→ 72
→ 73
→ ...
1. Prefill 阶段:瞬时内存峰值
当模型接收到完整的 prompt 并进入 Prefill 时,会进行一次完整的前向传播。这一阶段的特点是:
- 会同时计算所有位置的 Q / K / V
- 会构建完整的 attention 计算(例如 n × n 的 attention score)
- 会产生大量中间计算结果(softmax buffer、临时输出 O、各层中间 tensor 等)
这些中间数据体积很大,但生命周期很短,仅在当前 forward 过程中存在。因此,内存占用会快速攀升到一个峰值:
0 → 100
这个"100"代表的是:KV cache + 所有临时计算 tensor 的叠加。
2. Prefill 结束:回落到稳定基线
当 Prefill 完成并成功预测出第一个 token 后,这一整轮前向传播结束。此时:
- 所有中间计算 tensor(attention matrix、softmax buffer、临时 Q、O 等)都会被释放
- 只有每一层的 K/V(KV cache)会被保留下来
因此内存会出现一个明显回落:
100 → 70
这个"70"可以理解为:当前 prompt 对应的 KV cache 所占用的内存。此时内存的"长期占用部分"基本已经确定。
3. Decode 阶段:线性缓慢增长
进入 Decode 阶段后,模型开始逐 token 生成输出。每生成一个新 token,会发生两件事:
- 产生少量临时计算(当前 token 的 Q、一次 attention 等),这些会很快释放
- 为每一层新增一份 K/V,并追加到 KV cache 中
因此,内存变化变成:
70 → 71 → 72 → 73 → ...
随着生成 token 数量的增加,KV cache 按序列长度线性增长。
小结
整个推理过程的内存行为可以总结为:
- Prefill:计算密集 + 内存峰值(包含大量临时 tensor)
- Prefill 结束:释放临时数据,仅保留 KV cache(进入稳定基线)
- Decode:每步追加 KV cache,内存线性增长
理解这一点,对于分析推理性能、显存瓶颈以及 KV cache 优化(例如 paged KV cache)至关重要。
十八、进阶:真实推理系统中的 KV Cache 管理与显存计算
在前面的分析中,我们把 KV cache 理解为"随着 token 增长逐步累加的一段内存"。这个理解在逻辑上是正确的,但在真实工程系统中,还有两个非常关键的细节。
1. KV Cache 并不是"每步动态申请"的
在理想化模型里,我们会这样想:
每生成一个 token:
新申请一段内存 → 存 K/V
但在真实系统中(例如 vLLM、TensorRT-LLM),通常不会这样做。原因很简单:
- GPU 上频繁申请/释放内存非常昂贵
- 会导致碎片化和性能抖动
- 无法支持高并发推理
实际做法:预分配(Pre-allocation)
真实系统通常会在推理一开始,就预先分配一整块 KV cache 内存池:
先申请一大块连续显存
然后在里面"按需填充"
这就是为什么你可能会看到:显存一开始就突然占了一大块(例如直接到 80%),然后随着生成进行,显存几乎不再增长,只是"被逐渐填满"。
Paged KV Cache(核心优化)
以 vLLM 为例,它引入了类似操作系统的"分页"机制:
- KV cache 被拆成很多固定大小的 block(类似内存页)
- 每个序列按需申请 block,支持动态扩展和回收
- 多个请求之间可以高效复用内存
可以把它理解为:KV cache ≈ GPU 上的"虚拟内存系统"
2. KV Cache 显存到底怎么算(面试高频)
这是面试中非常经典的一题:
"一个 7B 模型,context 长度 1000,大概需要多少 KV cache 显存?"
核心公式
KV cache 大小 ≈ 层数 × 序列长度 × hidden_size × 2 × 数据类型字节数
- 层数(num_layers):每一层都有 KV cache
- 序列长度(seq_len):历史 token 数量
- hidden_size:每个 token 的向量维度
- × 2:因为要存 K 和 V
- dtype size:比如 fp16 是 2 bytes
举个典型例子(7B 模型)
假设:hidden_size = 4096,层数 = 32,seq_len = 1000,dtype = fp16(2 bytes)
4096 × 2 × 2 = 16384 bytes ≈ 16 KB / token / layer
16 KB × 1000 tokens = 16 MB / layer
16 MB × 32 层 ≈ 512 MB
结论:一个 7B 模型,1000 token,大约需要 500MB KV cache。
重要补充(面试加分)
- batch 会线性放大:batch = 8 时,500 MB × 8 = 4 GB
- context 长度是线性影响:seq_len 从 1k → 8k,500 MB → 4 GB
- KV cache 通常比模型权重更"吃显存":在长上下文 / 高并发场景下,KV cache 才是主要瓶颈,而不是模型权重本身
3. 为什么这是推理优化的核心
现在你可以理解为什么:
- Prefill 是计算瓶颈
- Decode 是内存瓶颈
- KV cache 是推理系统设计的核心资源
在真实推理系统中,KV cache 通常通过预分配或分页机制进行管理,而不是逐步动态申请;其显存占用与层数、序列长度、hidden size 和 batch 成线性关系,在长上下文或高并发场景下往往成为主要瓶颈。
十九、最终总结
1. Prefill 是什么
把用户已经给出的 prompt 一次性送进模型,完整跑过所有层,建立起历史 token 的深层表示,并生成每一层的 KV cache。
2. Decode 是什么
在模型已经有历史 KV cache 的基础上,每次只对一个新 token 计算新的 Q/K/V,然后用新的 Q 去读取历史 KV,逐步生成后续 token。
3. O 是什么
某一层 attention 的输出表示,也就是该层中某个位置在融合上下文后的新 hidden state。
4. 为什么 Prefill 不能只算最后一个位置
因为真实模型是多层的,前面位置的输出会作为下一层的输入,用于构造下一层的 Q/K/V,所以整个序列都必须参与前向传播。
5. 为什么只缓存 KV,不缓存 Q
因为历史 K/V 会被未来每一步反复使用,而 Query 只在当前 step 使用一次,用完就失去复用价值。
6. KV cache 缓存的是哪一层
不是某一层,而是每一层都有自己的 KV cache。
一个可以直接记住的脑图:
用户输入 prompt
↓
Prefill:整段并行跑过所有层
↓
得到最后位置表示,预测第一个新 token
↓
同时保留所有层的历史 KV cache
↓
进入 Decode
↓
每一步只算新 token 的 Q/K/V
↓
Q 读取所有历史 KV
↓
预测下一个 token
↓
Q 丢弃,K/V 追加到 cache
↓
重复