学习 Transformer 时,我最开始能记住 Attention 的公式,却说不清公式里的每个张量究竟来自哪里,也不清楚“多头”为什么在代码里经常只是一次大矩阵乘法。继续往下看 RoPE 和 Decoder Block 后,新的问题又出现了:一个 token 为什么要旋转多组特征?相对位置到底发生在哪两个对象之间?多个 head 拼接以后为什么还要经过输出投影?
后来我发现,这些问题其实属于同一条数据流:一段文本先变成 token 序列,每个 token 被表示成隐藏向量;Attention 负责让 token 之间交换信息,FFN 负责让每个 token 独立加工信息,RoPE、Norm 和残差连接则保证顺序信息与训练稳定性。
1
2
3
4
5
6
7
8
9
10
11
文本
↓ Tokenizer 与 Embedding
[B, S, H]
↓ RMSNorm
Masked Multi-Head Self-Attention
↓ 残差连接
[B, S, H]
↓ RMSNorm
SwiGLU FFN
↓ 残差连接
[B, S, H]
本文从张量形状出发,把 Self-Attention、多头工程实现、MHA/GQA/MQA、RoPE 和现代 Decoder Block 串成一条完整链路。
1. 先统一符号
Transformer 中同一个字母有时会被不同资料赋予不同含义。为了避免把序列长度、层数、Value 和词表混在一起,本文统一使用下面的符号:
| 符号 | 含义 |
|---|---|
| $B$ | batch size,一次处理的序列数量 |
| $S$ | sequence length,一条序列中的 token 数量 |
| $H$ | hidden size,也写作 $d_{model}$,每个 token 的隐藏向量维度 |
| $N_h$ | Query head 的数量 |
| $N_{kv}$ | Key/Value head 的数量 |
| $D_h$ | 每个 Attention head 的维度,通常 $D_h=H/N_h$ |
| $H_{ff}$ | FFN 的中间维度 |
| $L$ | Decoder Block 的层数 |
| $\lvert\mathcal V\rvert$ | tokenizer 的词表大小 |
| $V_{val}$ | Attention 中的 Value 张量,避免与词表混淆 |
这里最容易混淆的是 $S$、$H$ 和 $\lvert\mathcal V\rvert$:
1
2
3
S:当前这段输入实际有多少个 token
H:模型用多少个浮点数表示一个 token
|V|:整个词表中一共有多少种 token
它们描述的是三个不同的问题。
2. 从文本到 Transformer 输入
模型的输入最初是一段文本,例如:
1
我喜欢学习 AI Infra
Tokenizer 会把它转换成一串 token ID:
1
2
3
文本
↓ Tokenizer
[id₀, id₁, id₂, ..., idₛ₋₁]
如果一共得到 $S$ 个 token,那么 token ID 张量的形状是:
$\text{token_ids}\in\mathbb{N}^{B\times S}$
模型内部维护一个 Embedding 参数矩阵:
$E\in\mathbb{R}^{\lvert\mathcal V\rvert\times H}$
其中每一行保存一个词表 token 的 $H$ 维表示。对于第 $s$ 个输入 token,查表得到:
$x_s=E[\text{id}_s]\in\mathbb{R}^{H}$
把当前输入中的 $S$ 个 token 向量按顺序排列起来,得到:
$X\in\mathbb{R}^{B\times S\times H}$
所以 Transformer 的输入是 $[B,S,H]$,而不是 $[B,\lvert\mathcal V\rvert,H]$。$\lvert\mathcal V\rvert$ 只表示整个 Embedding 表有多少行;当前句子只从中查出了 $S$ 行。
例如:
1
2
3
4
5
6
7
8
词表大小 |V| = 32000
当前序列长度 S = 10
隐藏维度 H = 4096
batch size B = 1
Embedding 参数: [32000, 4096]
Token IDs: [1, 10]
查表后的 X: [1, 10, 4096]
3. Transformer 的三种常见架构
原始 Transformer 由 Encoder 和 Decoder 两部分组成:
1
2
3
源序列 → Encoder → 上下文表示
↓
目标序列 → Decoder + Cross-Attention → 输出
后来逐渐形成三类常见架构:
| 架构 | 注意力方式 | 代表模型 | 常见任务 |
|---|---|---|---|
| Encoder-only | 双向 Self-Attention | BERT | 分类、表示学习、信息抽取 |
| Encoder-Decoder | Encoder 双向,Decoder 因果注意力并包含 Cross-Attention | T5、原始 Transformer | 翻译、摘要、Seq2Seq |
| Decoder-only | 因果 Self-Attention | GPT、LLaMA、Qwen | 自回归文本生成 |
当前大语言模型通常采用 Decoder-only:删除原始 Decoder 中连接 Encoder 的 Cross-Attention,只保留因果 Self-Attention 和 FFN,并堆叠很多层相同形状的 Block。
需要注意,Decoder-only 是否使用 Pre-Norm,是另一个独立的架构选择。删除 Cross-Attention 和调整 Norm 位置不是同一件事。
本文后面主要讨论现代大模型常见的 Decoder-only、Pre-Norm 结构。
4. Self-Attention 在做什么?
Self-Attention 的目标是:让序列中的每个 token 根据相关程度,从其他可见 token 中汇总信息。
可以把 Q、K、V 理解成一次检索:
- Query:当前 token 想查什么;
- Key:每个 token 用什么特征接受匹配;
- Value:匹配后真正取回的内容。
这只是帮助理解的比喻。从计算上看,Q、K、V 是同一个输入经过三组可学习线性投影得到的结果。
4.1 线性投影生成 Q、K、V
暂时忽略多头细节,输入为:
$X\in\mathbb{R}^{B\times S\times H}$
经过三组参数矩阵:
$Q=XW_Q,\qquad K=XW_K,\qquad V_{val}=XW_V$
在标准多头注意力中,三个大投影的输出宽度通常都是 $H$:
$W_Q,W_K,W_V\in\mathbb{R}^{H\times H}$
于是:
$Q,K,V_{val}\in\mathbb{R}^{B\times S\times H}$
这里并没有让 token 数量发生变化。每个 token 仍然对应一行,只是从原来的隐藏表示变成了用于查询、匹配和传递内容的三种表示。
4.2 计算匹配分数
对于单个 head,Q 和 K 的形状是:
$Q_i,K_i\in\mathbb{R}^{B\times S\times D_h}$
计算:
$S_i=Q_iK_i^\top$
在 batch 中,每个样本独立计算,形状变化是:
1
[B, S, Dh] @ [B, Dh, S] -> [B, S, S]
分数矩阵中的元素 $S_i[b,m,n]$ 表示:
第 $b$ 条序列中,位置 $m$ 的 Query 与位置 $n$ 的 Key 在第 $i$ 个 head 中有多匹配。
因此每一行对应一个 Query token,每一列对应一个 Key token。
4.3 为什么要除以 $\sqrt{D_h}$?
缩放后的分数是:
$\widetilde S_i=\dfrac{Q_iK_i^\top}{\sqrt{D_h}}$
一个 Q 和 K 的点积包含 $D_h$ 个乘积的累加:
$q^\top k=\sum_{r=1}^{D_h}q_rk_r$
如果各分量大致独立、均值为 0、方差约为 1,那么这个和的方差大约随 $D_h$ 增长,标准差大约是 $\sqrt{D_h}$。维度越大,未经缩放的分数越容易出现较大的绝对值。
Softmax 中包含指数运算。分数差距过大时,概率会过早接近 one-hot,很多位置的梯度变得很小。除以 $\sqrt{D_h}$ 后,不同 head dimension 下的分数尺度更稳定。
这里除的是每个 head 的维度 $D_h$,而不是整个隐藏维度 $H$。
4.4 因果 Mask:不能看到未来答案
Decoder-only 模型按顺序预测下一个 token,所以位置 $m$ 不能访问位置 $m$ 之后的 token。
例如:
1
2
3
4
5
6
输入:我 喜欢 学习 AI
“我” 只能看:我
“喜欢” 可以看:我、喜欢
“学习” 可以看:我、喜欢、学习
“AI” 可以看:我、喜欢、学习、AI
将未来位置的分数设为负无穷:
$M_{m,n}=-\infty,\qquad n>m$
再做 Softmax:
$A_i=\operatorname{softmax}\left(\dfrac{Q_iK_i^\top}{\sqrt{D_h}}+M\right)$
Softmax 沿 Key 的位置维,也就是最后一维执行。由于 $e^{-\infty}=0$,未来位置的注意力权重变成 0。
4.5 用权重汇总 Value
得到注意力权重后:
$O_i=A_iV_i$
形状变化是:
1
[B, S, S] @ [B, S, Dh] -> [B, S, Dh]
对于第 $m$ 个 Query token:
$o_m=\sum_{n=1}^{S}A_{m,n}v_n$
它得到的不是某一个 Value,而是所有可见 Value 的加权和。这就是“每个 token 聚合上下文信息”的具体含义。
一个 head 的完整公式是:
$\operatorname{head}_i=\operatorname{softmax}\left(\dfrac{Q_iK_i^\top}{\sqrt{D_h}}+M\right)V_i$
5. 多头注意力到底“多”在哪里?
假设:
1
2
3
H = 4096
Nh = 32
Dh = 128
它们满足:
$H=N_hD_h=32\times128=4096$
同一个 token 在 32 个 head 中各有一份 128 维的 Q、K、V 表示。所有 head 都处理完整序列,拆分的是特征维度,不是把 token 分成 32 组。
5.1 每个 head 都有独立的投影参数吗?
从数学上看,第 $i$ 个 Query head 对应一块参数:
$W_Q^{(i)}\in\mathbb{R}^{H\times D_h}$
它计算:
$Q_i=XW_Q^{(i)}$
32 个 head 可以写成 32 次独立投影。但工程上不会为了这个表达真的启动 32 次小矩阵乘法,而是把这些参数沿输出列方向拼起来:
$W_Q=[W_Q^{(1)},W_Q^{(2)},\ldots,W_Q^{(32)}]\in\mathbb{R}^{4096\times4096}$
一次大矩阵乘法即可得到:
$Q=XW_Q=[Q_1,Q_2,\ldots,Q_{32}]$
然后 reshape:
1
2
3
4
5
[B, S, 4096]
↓ reshape
[B, S, 32, 128]
↓ 调整逻辑维度顺序
[B, 32, S, 128]
所以“每个 head 有独立参数”和“实现中维护一个大矩阵”并不冲突。大矩阵的不同列块就是不同 head 的参数。
5.2 为什么这仍然是真正的多头?
如果只有一个 4096 维的大 head,那么每对 token 只产生一套注意力分布:
$A=\operatorname{softmax}\left(\dfrac{QK^\top}{\sqrt{4096}}\right)$
而 32 个 head 会分别产生 32 套注意力分布:
$A_1,A_2,\ldots,A_{32}\in\mathbb{R}^{B\times S\times S}$
每个 head 的 Q、K 参数不同,Softmax 也独立执行,因此同一个 Query token 可以在不同特征子空间中采用不同的关注模式。
多头的本质不在于参数是否保存成 32 个 Python 对象,而在于:
特征被分成多个子空间,每个子空间独立计算 $Q_iK_i^\top$ 和 Softmax,产生独立的注意力分布。
5.3 Q、K、V 还可以融合成一次 GEMM
GEMM 是 General Matrix-Matrix Multiplication,即通用矩阵乘法。Transformer 中的线性层主要由 GEMM 实现。
逻辑上,Q、K、V 是三次投影:
$Q=XW_Q,\qquad K=XW_K,\qquad V_{val}=XW_V$
在标准 MHA 中,还可以把三组权重继续拼接:
$W_{QKV}=[W_Q,W_K,W_V]\in\mathbb{R}^{H\times3H}$
然后执行一次更大的 GEMM:
$[Q,K,V_{val}]=XW_{QKV}$
形状是:
1
[B×S, H] @ [H, 3H] -> [B×S, 3H]
之后再 split 出 Q、K、V,并 reshape 成多个 head。不同框架也可能保留三个独立 Linear,因此更准确的说法是:
数学上有三组线性投影,工程上可以根据模型和 Kernel 实现为一次或多次 GEMM。
5.4 多头拼接后为什么还要输出投影?
每个 head 的输出是:
$O_i\in\mathbb{R}^{B\times S\times D_h}$
32 个 head 沿特征维拼接:
$O_{cat}=\operatorname{Concat}(O_1,\ldots,O_{32})\in\mathbb{R}^{B\times S\times4096}$
此时每个 token 的向量可以看成:
1
[head 1 的 128 维 | head 2 的 128 维 | ... | head 32 的 128 维]
Concat 只是把各个 head 放在一起,并没有主动混合它们。接下来使用输出投影:
$Y=O_{cat}W_O$
其中:
$W_O\in\mathbb{R}^{(N_hD_h)\times H}$
常见情况下 $N_hD_h=H$,因此 $W_O$ 是 $H\times H$ 方阵。它有两个主要作用:
- 对不同 head 的输出通道进行可学习的混合;
- 把结果映射回残差流要求的隐藏维度 $H$。
它不会在不同 token 之间再次交换信息,只在每个 token 自己的特征维上进行线性变换。token 之间的信息聚合已经发生在前面的 Attention 中。
这里的 $W_O$ 也不是模型最后把 hidden state 映射到词表 logits 的 LM Head。二者形状和职责不同:
1
2
Attention 输出投影: [B, S, H] -> [B, S, H]
LM Head: [B, S, H] -> [B, S, |V|]
6. MHA、GQA 和 MQA
推理时,每一层都需要缓存历史 token 的 K 和 V。标准 MHA 为每个 Query head 保存一组 K/V,KV Cache 很快会占用大量显存。
MQA 和 GQA 的思路是:保留多个 Query head,但减少 K/V head 数量,让多个 Query head 共享 K/V。
6.1 MHA:一对一
标准 MHA 中:
$N_{kv}=N_h$
假设有 32 个 Query head:
1
2
3
4
Q head 1 ↔ K/V head 1
Q head 2 ↔ K/V head 2
...
Q head 32 ↔ K/V head 32
张量形状为:
$Q,K,V_{val}\in\mathbb{R}^{B\times S\times32\times128}$
6.2 MQA:全部 Query 共享一组 K/V
Multi-Query Attention 保留 32 个 Query head,但只有 1 个 KV head:
$N_{kv}=1$
1
2
3
4
Q head 1 ─┐
Q head 2 ─┤
... ├─ 共享 K/V head 1
Q head 32 ─┘
形状变为:
$Q\in\mathbb{R}^{B\times S\times32\times128}$
$K,V_{val}\in\mathbb{R}^{B\times S\times1\times128}$
共享 K/V 不表示 32 个 head 的输出相同。每个 $Q_i$ 仍然不同,因此 $Q_iK^\top$ 得到的注意力分布也可以不同。
6.3 GQA:若干 Query 共享一组 K/V
Grouped-Query Attention 位于 MHA 和 MQA 之间。假设有 32 个 Query head、8 个 KV head,那么每 4 个 Query head 共享一组 K/V:
1
2
3
4
Q head 1 ~ 4 -> K/V head 1
Q head 5 ~ 8 -> K/V head 2
...
Q head 29 ~ 32 -> K/V head 8
形状为:
$Q\in\mathbb{R}^{B\times S\times32\times128}$
$K,V_{val}\in\mathbb{R}^{B\times S\times8\times128}$
每组包含的 Query head 数量是:
$G=\dfrac{N_h}{N_{kv}}=\dfrac{32}{8}=4$
三种机制可以统一理解为:
| 机制 | Query head 数 | KV head 数 | 共享方式 |
|---|---|---|---|
| MHA | 32 | 32 | 一个 Q 对应一组 K/V |
| GQA | 32 | 8 | 每 4 个 Q 共享一组 K/V |
| MQA | 32 | 1 | 所有 Q 共享一组 K/V |
GQA 不会把 K/V 在显存中真的复制成 32 份。高效 Attention Kernel 会在逻辑上把同一个 KV head 提供给对应的多个 Query head,否则会抵消缓存和带宽收益。
6.4 为什么它能减少 KV Cache?
KV Cache 的大小与 KV head 数 $N_{kv}$ 成正比。忽略额外管理开销,其字节数约为:
$M_{KV}=2LBSN_{kv}D_h\cdot s$
其中:
- $2$ 表示 K 和 V;
- $L$ 是层数;
- $B$ 是 batch size;
- $S$ 是缓存的 token 数;
- $s$ 是每个元素占用的字节数。
当 Query head 数为 32 时:
1
2
3
MHA:Nkv = 32
GQA:Nkv = 8 -> KV Cache 约为 MHA 的 1/4
MQA:Nkv = 1 -> KV Cache 约为 MHA 的 1/32
GQA 在模型表达能力和推理效率之间提供了折中,因此被很多现代大模型采用。
7. 为什么 Attention 需要位置信息?
先考虑没有位置编码、也没有因果 Mask 的 Self-Attention。
输入:
1
[猫, 追, 狗]
如果重新排列成:
1
[狗, 猫, 追]
Q、K、V 的行会跟着 token 一起重新排列。对于“猫”来说,它仍然用相同的 Query 去匹配同一组“猫、追、狗”的 Key;对应权重和 Value 同步换了顺序,加权和的内容不会因此改变。
原来:
$o_{猫}=0.2v_{猫}+0.3v_{追}+0.5v_{狗}$
重新排列后可能写成:
$o'_{猫}=0.5v_{狗}+0.2v_{猫}+0.3v_{追}$
加法项只是换了顺序,因此结果仍然相同。整体表现为:
1
2
[猫, 追, 狗] -> [o猫, o追, o狗]
[狗, 猫, 追] -> [o狗, o猫, o追]
输入怎样排列,输出就跟着怎样排列,这叫排列等变,不是排列不变。
如果用排列矩阵 $P$ 表示换序,在无位置编码、无位置相关 Mask 的条件下:
$\operatorname{Attn}(PX)=P\operatorname{Attn}(X)$
这说明 Attention 自身不会知道一个 token 原来处于第几个位置。
需要补充:Decoder 的因果 Mask 本身已经规定了“过去”和“未来”,所以加入固定因果 Mask 后,不再对任意排列满足上面的等变关系。但 Mask 主要限制可见范围,模型仍需要位置编码来表达更丰富的绝对位置、相对方向和距离关系。
8. RoPE:在 Q、K 内部旋转位置
原始 Transformer 把位置向量直接加到 token embedding 上。现代 Decoder-only 大模型更常使用 RoPE,也就是 Rotary Position Embedding。
它不增加新的 token,也不改变 Q、K 的形状,而是在每个 head 内部,把 Q 和 K 的特征两两组成二维坐标,再按 token 位置进行旋转。
8.1 一个 token 到底旋转了什么?
假设某个 token 在一个 head 中的 Q 是 8 维:
$q=(q_0,q_1,q_2,q_3,q_4,q_5,q_6,q_7)$
为了直观说明,可以把它分成四个二维组:
1
2
3
4
第 0 组:(q₀, q₁)
第 1 组:(q₂, q₃)
第 2 组:(q₄, q₅)
第 3 组:(q₆, q₇)
每个二维组旋转一次,旋转后再组合回一个 8 维向量:
1
2
3
4
5
一个 8 维 Q
↓ 分成 4 个二维组
每组按自己的角度旋转一次
↓ 重新组合
一个新的 8 维 Q
因此,一个 token 不会因为 RoPE 变成多份 token 或多个完整 Q。它仍然只有一个 Q 向量,只是向量内部的二维特征组分别发生了数值变换。
真实模型中,如果 $D_h=128$,就可以形成 64 个二维组。不同实现可能采用相邻维度配对,也可能采用前后半区配对;只要训练和推理采用一致约定,数学本质都是二维子空间中的旋转。
8.2 每组的旋转速度不同
第 $i$ 个二维组使用频率:
$\theta_i=\dfrac{1}{10000^{2i/D_r}}$
其中 $D_r$ 是参与 RoPE 的 rotary dimension,很多模型中等于 $D_h$,也有模型只旋转 head dimension 的一部分。
位置为 $m$ 的 token,第 $i$ 组旋转角度是:
$\alpha_{m,i}=m\theta_i$
同一组中的两个元素使用同一个角度,不同组的 $ heta_i$ 不同。所以:
- 前面的高频组每移动一个位置转得更多,对局部位置变化更敏感;
- 后面的低频组转得更慢,周期更长,能在更长范围内保留位置差异。
可以把它类比成钟表中的秒针、分针和时针:单个指针都存在周期重复,但多个速度联合起来,可以表示多个尺度的位置关系。
8.3 二维旋转公式
对第 $i$ 个二维组,旋转角度记为 $\alpha=m\theta_i$:
$q'_{2i}=q_{2i}\cos\alpha-q_{2i+1}\sin\alpha$
$q'_{2i+1}=q_{2i}\sin\alpha+q_{2i+1}\cos\alpha$
也可以使用旋转矩阵表示:
$R(\alpha)=\begin{bmatrix}\cos\alpha&-\sin\alpha\\\sin\alpha&\cos\alpha\end{bmatrix}$
位置 $m$ 上的 Query 和位置 $n$ 上的 Key 分别是:
$q'_{m,i}=R(m\theta_i)q_{m,i},\qquad k'_{n,i}=R(n\theta_i)k_{n,i}$
8.4 相对位置为什么会出现在点积中?
旋转后的 Q、K 做点积:
$(q'_{m,i})^\top k'_{n,i}=q_{m,i}^\top R(m\theta_i)^\top R(n\theta_i)k_{n,i}$
旋转矩阵满足:
$R(m\theta_i)^\top R(n\theta_i)=R((n-m)\theta_i)$
所以:
$(q'_{m,i})^\top k'_{n,i}=q_{m,i}^\top R((n-m)\theta_i)k_{n,i}$
这说明位置相关部分只通过相对位置 $n-m$ 进入点积。
例如:
1
2
Query 在位置 3,Key 在位置 8:相对距离 8 - 3 = 5
Query 在位置 13,Key 在位置 18:相对距离 18 - 13 = 5
两组 token 的绝对位置不同,但 RoPE 引入的相对旋转相同。
更准确地说,最终注意力分数仍然同时依赖:
- Q、K 的内容;
- 两个 token 的相对位置。
不能把它理解成“距离相同,注意力分数就一定相同”。只有位置造成的那一部分变化依赖相对距离。
8.5 为什么只旋转 Q 和 K?
Q、K 的点积决定“应该关注谁”:
$A=\operatorname{softmax}\left(\dfrac{QK^\top}{\sqrt{D_h}}+M\right)$
因此对 Q、K 注入位置,可以直接影响注意力权重。V 主要承载权重确定后要取回的内容:
$O=AV_{val}$
所以 RoPE 通常旋转 Q 和 K,而不旋转 V。
9. FFN:每个 token 独立加工信息
Attention 负责不同 token 之间的信息交换,FFN 则对每个 token 独立执行同一套非线性变换。
标准 FFN 可以写成:
$\operatorname{FFN}(x)=\operatorname{Activation}(xW_1)W_2$
它先升维,再降维:
1
2
3
4
5
6
7
[B, S, H]
↓ W₁
[B, S, Hff]
↓ 激活函数
[B, S, Hff]
↓ W₂
[B, S, H]
如果 $H=4096$,标准 FFN 的 $H_{ff}$ 常取接近 $4H$ 的量级。
9.1 从 ReLU、GELU 到 SwiGLU
ReLU:
$\operatorname{ReLU}(x)=\max(0,x)$
GELU:
$\operatorname{GELU}(x)=x\Phi(x)$
现代 LLaMA 风格模型常使用 SwiGLU:
$\operatorname{SwiGLU}(x)=\left[\operatorname{SiLU}(xW_{gate})\odot(xW_{up})\right]W_{down}$
其中:
1
2
3
4
x @ Wgate:生成门控分支
x @ Wup: 生成候选信息
SiLU(gate) ⊙ up:逐元素决定信息通过多少
结果 @ Wdown:映射回 H 维
以 $H=4096$、$H_{ff}=11008$ 为例:
1
2
3
4
5
x: [B, S, 4096]
gate: [B, S, 11008]
up: [B, S, 11008]
mid: [B, S, 11008]
down: [B, S, 4096]
FFN 不改变 token 数量。每个 token 独立经过同一组参数,所以它只改变每个 token 内部的特征表示。
10. Norm 与残差连接
现代大模型通常使用 Pre-Norm:先归一化,再进入 Attention 或 FFN,最后与残差分支相加。
10.1 LayerNorm 与 RMSNorm
LayerNorm 会减均值、除以标准差,再进行可学习的缩放和平移:
$\operatorname{LayerNorm}(x)=\gamma\odot\dfrac{x-\mu}{\sqrt{\sigma^2+\epsilon}}+\beta$
LLaMA 使用 RMSNorm,它不减均值,而是按均方根缩放:
$\operatorname{RMSNorm}(x)=\gamma\odot\dfrac{x}{\sqrt{\operatorname{mean}(x^2)+\epsilon}}$
两者通常都沿每个 token 的隐藏维度进行计算:
1
每个 token 的 H 个特征 -> 单独归一化
形状始终不变:
$[B,S,H]\rightarrow[B,S,H]$
10.2 残差连接
残差连接的形式是:
$y=x+F(x)$
它让子层主要学习“在当前表示上增加什么”,并提供一条更直接的信息和梯度通路。
对 Transformer 来说,残差相加要求两个分支形状一致:
1
2
3
原始输入: [B, S, H]
子层输出: [B, S, H]
逐元素相加: [B, S, H]
这也是每个 Decoder Block 都保持隐藏维度 $H$ 的重要原因。
10.3 Pre-Norm 与 Post-Norm
原始 Transformer 常写成 Post-Norm:
$y=\operatorname{Norm}(x+\operatorname{Sublayer}(x))$
现代大模型常使用 Pre-Norm:
$y=x+\operatorname{Sublayer}(\operatorname{Norm}(x))$
Pre-Norm 中,残差主路径不穿过 Norm,通常更容易稳定训练很深的网络。但这不是说它在所有模型和训练配方中一定更好,而是它已经成为大模型中的常见工程选择。
11. 组装完整的 Transformer Decoder Block
把前面的模块拼起来,现代 Pre-Norm Decoder Block 可以写成两个公式:
$U_l=X_l+\operatorname{Attention}(\operatorname{RMSNorm}(X_l))$
$X_{l+1}=U_l+\operatorname{SwiGLU}(\operatorname{RMSNorm}(U_l))$
完整数据流是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
X_l [B, S, H]
│
├──────────────── 残差 ────────────────┐
↓ │
RMSNorm │
↓ │
QKV 投影 │
↓ │
Q/K 应用 RoPE │
↓ │
Causal Multi-Head Attention │
↓ │
输出投影 W_O │
↓ │
加法 ←───────────────────────────────────┘
↓
U_l [B, S, H]
│
├──────────────── 残差 ────────────────┐
↓ │
RMSNorm │
↓ │
SwiGLU FFN │
↓ │
加法 ←───────────────────────────────────┘
↓
X_{l+1} [B, S, H]
11.1 用 LLaMA-2-7B 的形状走一遍
取下面的配置:
1
2
3
4
5
6
7
B = 1
S = 2048
H = 4096
Nh = 32
Nkv = 32
Dh = 128
Hff = 11008
Attention 子层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Block 输入: [1, 2048, 4096]
RMSNorm: [1, 2048, 4096]
融合 QKV 投影:
[1×2048, 4096] @ [4096, 12288]
-> [1×2048, 12288]
拆分并 reshape:
Q、K、V -> [1, 32, 2048, 128]
Q、K 应用 RoPE: 形状不变
每个 head 计算 QKᵀ:
[2048, 128] @ [128, 2048] -> [2048, 2048]
32 个 head 的分数: [1, 32, 2048, 2048]
Mask + Softmax: [1, 32, 2048, 2048]
权重乘 V:
[2048, 2048] @ [2048, 128] -> [2048, 128]
拼接 32 个 head: [1, 2048, 4096]
输出投影 W_O: [1, 2048, 4096]
第一次残差相加: [1, 2048, 4096]
FFN 子层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RMSNorm: [1, 2048, 4096]
gate 投影:
[1×2048, 4096] @ [4096, 11008]
-> [1×2048, 11008]
up 投影: [1, 2048, 11008]
SiLU(gate) ⊙ up: [1, 2048, 11008]
down 投影:
[1×2048, 11008] @ [11008, 4096]
-> [1×2048, 4096]
第二次残差相加: [1, 2048, 4096]
Block 输出: [1, 2048, 4096]
从头到尾,残差流都保持 $[B,S,H]$。因此多个 Block 可以连续堆叠:
1
2
3
4
5
6
7
8
9
Embedding 输出 [B, S, H]
↓
Decoder Block 1 [B, S, H]
↓
Decoder Block 2 [B, S, H]
↓
...
↓
Decoder Block L [B, S, H]
每一层形状相同,但参数并不共享,token 表示会在层与层之间不断变化。
11.2 一个 Block 的参数主要在哪里?
仍以 $H=4096$、$H_{ff}=11008$ 的标准 MHA 为例。
Attention 的四个矩阵为:
$W_Q,W_K,W_V,W_O\in\mathbb{R}^{4096\times4096}$
参数量:
$4\times4096^2=67,108,864$
SwiGLU 的三个矩阵为:
$W_{gate},W_{up}\in\mathbb{R}^{4096\times11008},\qquad W_{down}\in\mathbb{R}^{11008\times4096}$
参数量:
$3\times4096\times11008=135,266,304$
两个 RMSNorm 各有一个长度为 4096 的缩放参数,因此单个 Block 总计约:
$67,108,864+135,266,304+8192=202,383,360$
也就是约 202M 参数。可以看出,FFN 约占单个 Block 参数量的三分之二,Attention 约占三分之一。
这直接影响后续的 AI Infra 优化:
- Attention 是长序列二次复杂度和 KV Cache 的核心;
- FFN 拥有大量权重和 GEMM 计算,是张量并行与量化的重要对象。
12. 从最后一个 Block 到词表 logits
经过 $L$ 个 Decoder Block 后,模型通常还会执行一次 Final RMSNorm,得到:
$X_{final}\in\mathbb{R}^{B\times S\times H}$
然后通过 LM Head 映射到整个词表:
$W_{lm}\in\mathbb{R}^{H\times\lvert\mathcal V\rvert}$
$\text{logits}=X_{final}W_{lm}$
形状变化是:
1
[B, S, H] @ [H, |V|] -> [B, S, |V|]
每个位置都得到对词表中所有候选 token 的打分。训练时可以用这些 logits 计算下一个 token 的交叉熵;生成时通常只取当前最后一个位置的 logits,经过采样得到下一个 token。
13. 自回归生成、Prefill 与 Decode
Decoder-only 模型一次预测一个新 token:
1
2
3
4
5
6
7
输入:“人工智能的”
↓
预测:“核”
↓ 把“核”加入输入
预测:“心”
↓
继续生成……
推理可以分成 Prefill 和 Decode 两个阶段。
13.1 Prefill
Prefill 一次处理完整 prompt。假设 prompt 长度为 $P$:
1
2
Query 长度:P
Key/Value 长度:P
所有 prompt token 可以并行完成 QKV 投影和 Attention,并把每一层产生的 K、V 写入 KV Cache。
在常见配置中,Prefill 有较大的矩阵乘法,通常更容易利用 GPU 算力;但是否严格属于 Compute Bound,仍取决于 prompt 长度、batch、模型、数据类型和 Kernel 实现。
13.2 Decode
进入 Decode 后,每一步只有一个新 token:
1
2
新 Query 长度:1
历史 Key/Value 长度:P + 已生成 token 数
新 token 只需要生成自己的 Q、K、V:
- 新 Q 与所有历史 K 计算注意力分数;
- 根据权重汇总历史 V;
- 把新的 K、V 追加到 KV Cache;
- 经过其余层后预测下一个 token。
历史 Q 不需要缓存,因为未来步骤不会再次用历史 token 充当 Query;未来每一步只需要当前新 token 的 Query。
Decode 的矩阵在单请求下很“瘦”,同时每一步都要读取模型权重和历史 KV Cache,因此常常受到显存带宽限制。但连续批处理增大 batch 后,它也可能重新形成更适合 GPU 的矩阵计算。
13.3 为什么 KV Cache 很重要?
如果没有 KV Cache,每生成一个 token 都要为整个历史序列重复计算 K、V。缓存后,历史 K、V 可以直接复用,每一步只新增当前 token 的 K、V。
代价是显存随以下因素线性增长:
$M_{KV}=2LBSN_{kv}D_h\cdot s$
这也解释了为什么 GQA、MQA、PagedAttention 和 KV Cache 量化是推理优化中的核心技术。
14. 从 AI Infra 角度重新看 Transformer
理解完整 Decoder Block 后,后续技术不再是孤立名词:
| Transformer 组件 | 主要计算或资源 | AI Infra 关联 |
|---|---|---|
| QKV 投影 | 大型 GEMM | Tensor Core、融合 QKV、张量并行 |
| $QK^\top$ 与 $AV$ | Batched GEMM | FlashAttention、长序列优化 |
| Softmax | 行归约、指数、归一化 | Stable/Online Softmax、算子融合 |
| RoPE | 高频逐元素运算 | 融合进 Attention Kernel |
| 输出投影 $W_O$ | GEMM、跨 head 混合 | 张量并行后的通信与归并 |
| RMSNorm | 归约、逐元素缩放 | Welford、Residual + Norm 融合 |
| SwiGLU FFN | 三个大型权重矩阵 | GEMM、量化、张量并行、MoE |
| 多层 Block | 层级堆叠 | 流水线并行、激活重计算 |
| K/V | 随序列增长的缓存 | GQA/MQA、PagedAttention、KV 量化 |
| 自回归生成 | 逐 token 串行 | Continuous Batching、Speculative Decoding |
FlashAttention 不会改变精确 Attention 的算术复杂度。它的核心是通过分块和在线维护 Softmax 状态,避免把完整的 $S\times S$ 分数或概率矩阵写回 HBM,从而减少中间结果物化和显存读写。
15. 把完整链路串起来
现在可以把 Transformer Decoder-only 模型压缩成下面这条数据流:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
文本
↓ Tokenizer
Token IDs [B, S]
↓ Embedding 查表
Hidden States [B, S, H]
↓
重复 L 次 Decoder Block:
RMSNorm
↓
QKV 投影(逻辑上三组投影,工程上可融合为大 GEMM)
↓
拆成多个 Query/KV head
↓
对 Q、K 应用 RoPE
↓
QKᵀ / sqrt(Dh) + causal mask
↓
Softmax
↓
权重乘 V
↓
拼接 Query heads + 输出投影 W_O
↓
第一次残差连接
↓
RMSNorm
↓
SwiGLU FFN
↓
第二次残差连接
↓
Final RMSNorm
↓ LM Head
Logits [B, S, |V|]
↓
预测下一个 token
这次最重要的几个理解是:
- $S$ 是当前输入的 token 数,$H$ 是每个 token 的隐藏维度,$\lvert\mathcal V\rvert$ 是整个词表大小;
- Q、K、V 是同一输入经过不同参数投影得到的三种表示;
- 多头的投影可以合并成一次大 GEMM,但每个 head 仍会独立计算注意力分数和 Softmax;
- 输出投影 $W_O$ 用来混合多个 head,并让结果回到残差流的 $H$ 维;
- MHA、GQA、MQA 的主要差异是 KV head 数量,以及每组 K/V 被多少个 Query head 共享;
- RoPE 旋转的是每个 token 的 Q、K 向量内部的二维特征组,token 之间的相对位置在 Q、K 点积时出现;
- Attention 负责 token 之间交换信息,FFN 负责每个 token 独立加工信息;
- 一个现代 Decoder Block 可以概括为两次
Norm → 子层 → 残差相加。
Transformer 架构不是后续 AI Infra 学习之外的一章,而是所有算子优化、并行训练与推理系统共同作用的对象。只有先把张量怎样流过 Decoder Block 看清楚,后续的 FlashAttention、张量并行、KV Cache 和量化才有明确的落点。