Transformer
论文:Attention Is All You Need(2017)
Transformer 是一种基于**自注意力(Self-Attention)**的序列建模架构,不依赖循环或卷积即可对序列进行编码,成为 BERT、GPT 等大模型的基础。
自注意力(Self-Attention)
区别于传统 RNN 需要按位置逐个传递、串行计算,自注意力允许所有位置同时并行计算,并通过注意力权重直接建模任意位置之间的依赖关系,因此更易捕捉长程依赖,也便于并行加速。
多头注意力(Multi-Head Attention)
将 Q、K、V 在特征维度上拆成多组,每组独立做一次自注意力,最后把多组输出拼接再线性变换。这样模型可以在不同子空间里学习不同类型的依赖关系(如局部语法、长程指代等)。
Q、K、V
自注意力通过三组向量表示每个位置的角色:
- Q(Query):当前位置「想找什么」—— 用于与其它位置的 Key 做匹配。
- K(Key):当前位置「能提供什么」—— 供其它位置的 Query 来检索。
- V(Value):当前位置的「实际内容」—— 匹配后用于加权聚合的信息。
模型通过训练学习 WQ、WK、WV 等投影矩阵,将输入映射为 Q、K、V;运行时可理解为用这些矩阵表达模型学到的「如何查询、如何提供、如何聚合」的知识。
V 不是注意力的产物,而是输入的投影。Q、K、V 三者地位对称,都是 token 自己经过一次线性变换得到的,互不依赖。真正混合上下文信息、流向下一层的,是注意力公式输出的 Z(attention output)—— 它对所有位置的 V 按权重加权求和而成。简记:V 是「素材」,Z 才是「成品」。
计算示例:
为便于直观理解,设序列有 3 个 token,dk = dv = 2。输入经 WQ、WK、WV 投影后得到:
- Q1 = [1, 0],Q2 = [0, 1],Q3 = [1, 1]
- K1 = [1, 0],K2 = [0, 1],K3 = [1, 1]
- V1 = [1, 2],V2 = [3, 4],V3 = [5, 6]
以计算位置 3 的输出为例(即用 Q3 去查询所有位置):
- 点积相似度:Q3 · K1 = 1,Q3 · K2 = 1,Q3 · K3 = 2。
- 缩放:除以 √dk = √2,得 ≈ [0.707, 0.707, 1.414](缩放原因见下文「缩放点积注意力」)。
- softmax 归一化 为注意力权重:≈ [0.25, 0.25, 0.50]。
- 按权重聚合 V:0.25·V1 + 0.25·V2 + 0.50·V3 ≈ [3.5, 4.5]。
可以看到 Q3 与 K3 最相似(同为 [1,1]),因此 V3 在最终输出里占主导,这正符合「用 Q 查询、按 K 的相似度、加权聚合 V」的直觉。每个位置都会执行同样的计算(位置 1 用 Q1、位置 2 用 Q2……),因此整个序列可以一次性并行算完,这也是 Transformer 相比 RNN 的核心优势。
KV Cache
推理阶段是自回归的(逐个生成新 token),例如 "A" → "AB" → "ABC" → "ABCD"。若生成 D 时仍对整段序列重新算一遍注意力,会产生大量冗余计算。
QKV 结构适合做 KV Cache:生成 D 时只需新算 (Q_D)、(K_D)、(V_D),而注意力计算所依赖的 (K_A)、(V_A)、(K_B)、(V_B) 等可直接复用上一轮已算好的结果;当前步只关心「用 D 的 Query 去查前面所有 Key/Value」,因此不需要保留或复用之前的 (Q_A)、(Q_B) 等。
缩放点积注意力(Scaled Dot-Product Attention)
核心计算为:用 Query 与所有 Key 做点积得到相似度,经 softmax 得到注意力权重,再对 Value 加权求和。公式中会对点积结果除以 √(dk)(dk 为 Key 维度),以缓解维度较大时点积数值过大导致 softmax 梯度变小的问题。
前馈层(FFN)
Transformer 每层由两个子模块组成:Self-Attention 负责跨 token 混合上下文,FFN(Feed-Forward Network,前馈网络) 负责对每个 token 的向量做非线性加工。结构上 FFN 就是一个「升维 → 激活 → 降维」的两层 MLP,逐 token 独立计算(不同位置之间互不影响):
FFN(x) = W₂ · σ(W₁ · x + b₁) + b₂
↑ ↑
d_model→4·d_model 4·d_model→d_model
激活函数 σ 早期用 ReLU/GELU,现代 LLaMA 系普遍换成 SwiGLU。FFN 通常占整层约 2/3 的参数量,机制解释里常把 FFN 理解为承载大量事实性知识与非线性变换的部分,而注意力更像「路由」。这也是为什么 MoE(见下文「架构变体」)通常选择替换 FFN——把这个高参数量子层从单个稠密网络换成多专家稀疏激活。
「前馈」的两层含义
「前馈」一词在 Transformer 语境下常指两件不同的事,要分清:
| 含义 | 指什么 |
|---|---|
| 广义前馈网络 | 一类网络结构:信号单向流动、计算图无环(DAG)。Transformer 整体属于此类 |
| 狭义 FFN 子层 | Transformer 每层中那个「两层 MLP + 激活」的小模块,也就是上面的公式 |
与 RNN 的区别
「前馈 vs 循环」是神经网络按信息流动方向最基础的二分:
| 维度 | 前馈网络(FFN-family,含 Transformer) | 循环网络(RNN-family,含 LSTM/GRU) |
|---|---|---|
| 结构 | 计算图无环,信号只往前传 | 隐藏状态会回流到下一时间步,有环 |
| 建模序列的方式 | 一次性看到整段输入,靠位置编码 + 注意力捕捉顺序 | 按时间步逐个吃 token,用隐藏状态 ht 携带历史 |
| 位置维度并行 | ✅ 可并行(详见下文「并行与串行」) | ❌ 串行:ht 必须等 ht-1 算完 |
| 长程依赖 | 注意力可直接连接任意两个位置,路径长度 O(1) | 信息需逐步传递,路径长度 O(N),易梯度消失 |
注意:「前馈」 ≠ 「前向传播」。前者是网络结构类别(相对 RNN),后者是一次推理的计算过程(相对反向传播)。也没有所谓「后馈层」——你可能想表达的要么是 RNN(反馈 / 循环网络),要么是 反向传播(backpropagation,训练时的梯度回传算法)。Transformer 是前馈结构,但训练时一样要做反向传播,两者并不矛盾。
残差连接(Residual Connection)
让某一层的输入"绕过"该层计算,直接加到该层输出上:output = Layer(x) + x,这条直连通路叫 shortcut / skip connection(源自 2015 年 ResNet),是深度网络(几十、几百层)能训练起来的关键:
- 缓解梯度消失:
∂output/∂x = ∂Layer(x)/∂x + 1,那个 +1 让梯度至少能原样传回浅层,不会逐层连乘衰减为 0。 - 缓解退化问题:即使某层学不到东西,至少能学成"恒等映射"(Layer(x)≈0),加深层数不会让训练误差反而上升。
- 直觉:让网络"学增量"而非"学整体"——每层只需在原始信号上加点修正,比从零拟合目标函数容易得多。
在 Transformer 里的位置
每层的 Attention 和 FFN 各包一条残差,配合 LayerNorm 使用。现代 LLM(GPT-2、LLaMA 等)普遍采用 Pre-Norm 形式:
x = x + Attention(LayerNorm(x)) ← 残差 1:绕过 Attention
x = x + FFN(LayerNorm(x)) ← 残差 2:绕过 FFN
所以前文「Attention + FFN 串联」的完整含义是:每层有 2 条残差,96 层 Transformer 就是 192 条 shortcut 织成的网。Pre-Norm 的关键好处是残差通路上没有 Norm,从最后一层到第一层存在一条几乎恒等的"高速公路",梯度可一路畅通(原始 Transformer 用的 Post-Norm Norm(x + Sublayer(x)) 深层时训练不稳,已被主流弃用)。
把所有层的残差通路串起来看,整个 Transformer 是一条贯穿始终的残差流(residual stream):embedding 是初始值,每层 Attention/FFN 都在往这条主干上"读取并写入"增量。这也是机制可解释性研究里把 Transformer 看作"多个子模块在共享黑板上协作"的视角来源。
多层堆叠
实际的 Transformer 由 L 层「Attention + FFN」串联而成(GPT-3 96 层、LLaMA-7B 32 层)。每一层都有自己独立的一套 WQ、WK、WV、WO 与 FFN 参数,相互不共享。因此一个 token 在一次前向中要经历 L 次 QKV 计算,每层的输入(上一层的输出)和投影矩阵都不同。
不同层学到的注意力模式有明显分工:浅层偏重局部语法与邻近依赖,中层捕捉短语结构与指代关系,深层聚焦语义、推理与长程依赖。
能否预先缓存 token × W 的结果?
直觉上似乎可以把「每个 token 经过每层 W 矩阵的结果」打成查找表,避开运行时矩阵乘法。但这条路只对第 1 层理论可行,从第 2 层起原理上不成立:
- 第 1 层:输入是 token embedding 本身,可以预计算
embedding[v] · W^Q_1等查找表。但实务中很少这么做——投影矩阵乘法在整个前向中占比很小,而查找表显存(vocab × d_model)反而比矩阵(d_model × d_model)大几十倍;绝对位置编码还会让查找表进一步爆炸到 vocab × max_pos。 - 第 2 层及以后:每层输入是上一层注意力的输出,已经融合了上下文里所有 token 的信息。同一个 token 在不同句子、不同位置拿到的输入向量都不同——这正是 LLM 能做上下文相关表示的根本,也是它和静态词向量(word2vec)的本质区别。
工程上真正在做的「缓存」是 KV Cache 与 Prefix Cache:缓存的是当前会话或共享前缀的中间结果,是请求级别的,不是 token 级别的全局表。
并行与串行
「自注意力可并行」是相对 RNN 而言的——核心认知是:「依赖其他 token」 ≠ 「必须串行」。把一层 attention 的计算拆成三阶段就一目了然:
阶段 1:投影(每个 token 独立)
Q_i = X_i · W^Q
K_i = X_i · W^K ← 只需 token i 自己的输入
V_i = X_i · W^V
阶段 2:位置编码(每个 token 独立)
对 RoPE,按 token i 自己的位置旋转 Q_i 和 K_i
(V 不旋转)
阶段 3:注意力(跨 token,但仍是并行矩阵乘)
scores = Q · K^T / √d_k ← 一次 N×N 矩阵乘
weights = softmax(scores)
Z = weights · V ← 一次矩阵乘
阶段 1、2 是逐 token 独立的,所有位置一起算;阶段 3 虽然每个 Qi 都要「看」所有 Kj,但本质是一次矩阵乘法——GPU 上 N×N 个分数同时算出,并不串行。「读所有位置」(数据已就绪)和「等某个位置」(必须等计算完成)是完全不同的依赖类型:
| 形式 | 数据依赖 | 能否并行 |
|---|---|---|
| RNN:ht = f(ht-1, xt) | 当前步等前一步算完 | ❌ 串行 |
| Self-Attention:Zi = f(Qi, K1..N, V1..N) | 当前步读所有位置(已就绪) | ✅ 并行 |
下面具体看训练和推理的并行情况。
训练 / 推理 prefill 阶段
输入是一整段已知文本,可一次性塞进模型:
| 维度 | 并行 / 串行 |
|---|---|
| 同层内不同位置的 Q/K/V 投影 | 并行(一次大矩阵乘) |
| 同层内多头之间 | 并行 |
| batch 内多个样本 | 并行 |
| 层与层之间 | 串行(layer 2 必须等 layer 1 输出) |
「位置维度可并行」是 Transformer 相比 RNN 的核心优势——RNN 必须 t-1 算完才能算 t。
推理 decode 阶段
进入自回归生成后,每步只产出一个新 token,并行度被打破一部分:
| 维度 | 并行 / 串行 |
|---|---|
| 新 token 之间 | 串行(必须先有 D 才能算 E) |
| 当前步的多头之间 | 并行 |
| 层与层之间 | 串行 |
| 位置维度 | 退化为 1×N 向量乘(当前步只有 1 个新 Q,K/V 来自缓存) |
这就是为什么推理优化里有大量花活:KV Cache 复用已算的 K/V;Continuous Batching 把不同请求拼到同一 batch,把「用户之间」变成新的并行维度;投机采样用小模型并行验证多个候选 token,把「token 之间」的串行部分变回并行(详见 inference.md)。
Decoder-only 与 Causal Mask
Transformer 原论文同时包含 Encoder 和 Decoder 两部分,但现代 GPT、LLaMA、Qwen、DeepSeek 等主流文本 LLM 大多采用 Decoder-only 架构:只保留一串 Transformer Decoder block,用同一个目标训练和推理——给定前文,预测下一个 token。
常见 Transformer 形态可以粗略分成三类:
| 架构 | 代表模型 | 注意力方式 | 典型用途 |
|---|---|---|---|
| Encoder-only | BERT | 双向注意力:每个 token 可看左右上下文 | 文本理解、分类、抽取 |
| Decoder-only | GPT、LLaMA、Qwen | 单向注意力:每个 token 只能看自己和前文 | 文本生成、对话、代码生成 |
| Encoder-Decoder | T5、原始 Transformer | Encoder 双向理解输入,Decoder 单向生成输出 | 翻译、摘要、序列到序列转换 |
LLM 偏好 Decoder-only,核心原因是它和自回归生成天然一致:训练时不断做「预测下一个 token」,推理时也不断把刚生成的 token 接回上下文继续预测。结构更简单,预训练语料也容易构造——任意一段文本都可以变成很多个「前缀 → 下一个 token」样本。
Causal Mask
如果训练时把整段文本一次性喂给模型,而每个位置又能看到右侧未来 token,模型就会“偷看答案”。例如训练句子:
我 爱 AI
当模型在位置「爱」预测下一个 token 时,如果它已经能看到右边的「AI」,任务就失去了意义。因此 Decoder-only 模型会在注意力分数上加 Causal Mask(因果掩码):位置 i 只能关注位置 ≤ i 的 token,不能关注未来位置。
矩阵上可以理解为一个下三角可见区域:
| 当前 token | 可关注的 token |
|---|---|
| 我 | 我 |
| 爱 | 我、爱 |
| AI | 我、爱、AI |
实现时并不是把未来 token 从输入里删掉,而是在 softmax 之前把未来位置的 attention score 置为 -inf:
scores = Q · K^T / sqrt(d_k)
scores = scores + causal_mask # 未来位置为 -inf,其余为 0
weights = softmax(scores)
Z = weights · V
经过 softmax 后,未来位置的权重变成 0,模型只能从前文聚合信息。
Mask 不等于串行
Causal Mask 限制的是信息可见性,不是计算顺序。训练或 prefill 阶段,整段序列的 Q/K/V 和注意力矩阵仍然可以一次性并行算出,只是矩阵右上角的未来信息被 mask 掉。
这也是 Decoder-only LLM 的一个关键平衡:
- 训练 / prefill:已知完整输入,可并行计算所有位置,但每个位置只能“看见”前文;
- decode 推理:未来 token 尚未生成,只能一步步产生;此时 KV Cache 复用前文的 K/V,每步只为新 token 计算新的 Q/K/V。
所以「自回归生成是串行的」和「Transformer 训练可并行」并不矛盾:前者说的是生成新 token 的时间顺序,后者说的是已知序列内部的矩阵计算方式。
与 BERT 这类 Encoder-only 模型相比,Decoder-only 模型牺牲了“看完整句左右上下文”的能力,换来与生成任务完全对齐的训练目标和推理路径。这也是它成为现代通用 LLM 主流架构的重要原因。
架构变体
标准 Transformer 的每一层都是「Attention + 稠密 FFN」,所有权重在每次前向都参与计算。围绕「用更多参数装下更多知识,但单次推理算力可控」这一目标,业界发展出若干架构变体,最具代表性的就是稀疏激活与 MoE。
稠密模型 vs 稀疏激活模型
| 对比 | 稠密(Dense) | 稀疏激活(常指 MoE 等) |
|---|---|---|
| 在算什么 | 单次前向里,绝大部分(通常全部)权重都会参与当前计算 | 单次前向里,只对当前输入激活一部分子网络;其余权重本次不算,但仍占显存 |
| 直觉 | 参数多,单次计算也跟着「满负荷」 | 总参数量可以很大,单次激活参数量却可控制在较小范围 |
| 实现 | 结构相对单纯 | 需要路由、负载均衡等,工程更复杂 |
易混概念:「权重稀疏」指矩阵里大量元素为 0(压缩 / 剪枝语境),和 MoE 的「每次只激活部分专家」不是一回事,阅读论文或新闻时要分清主语是矩阵结构还是计算路径。
混合专家模型(MoE)
MoE 是稀疏激活的典型实现,作为对标准 Transformer 中 FFN 子层的替换:网络里并列多个专家(小型前馈子层等),对**每个 token(或每块输入)由门控 / 路由器(router)**动态选出 Top-k 个专家参与计算,其余专家不参与这一次前向。
- 为何常用:在相近训练算力下堆出更大总参数,同时让推理或训练时的激活 FLOPs 可控;也常与负载均衡、辅助损失等训练技巧一起出现,避免少数专家被「打爆」、多数闲置。
- 专家在学什么:专家通常没有人工标注的「领域名」;事后分析里有时会看到近似分工,但因模型与数据而异,不宜把某种模式当成 MoE 的定义。
- 与注意力的关系:MoE 通常只替换 FFN,注意力层仍是稠密的;因此前文「多层堆叠」「并行与串行」中关于注意力的讨论对 MoE 同样适用。
复习问题
- 为什么 Transformer 训练 / prefill 阶段可以并行,但 decode 阶段必须逐 token 生成?
- 为什么 KV Cache 主要缓存 K/V,而不是缓存历史 token 的 Q?
- Causal Mask 限制的是信息可见性,还是实际计算顺序?
- 为什么 MoE 通常替换 FFN,而不是替换注意力层?