第七章 Transformer 架构
一、注意力机制
1.1 自注意力
自注意力(Self-Attention)是 Transformer 的核心机制。它让序列中每个位置都能「看到」其他所有位置,并通过学习的权重决定关注程度。
直觉来源 。在 RNN 中,信息通过隐藏状态按时间步传递,长距离依赖需要穿越多层;CNN 通过多层堆叠扩大感受野,但仍受限于「层数 × 卷积核尺寸」;自注意力则直接计算任意两个位置之间的关联,无论距离多远都只需一次计算。
历史动机:Seq2Seq 的瓶颈 。自注意力不是凭空出现的。2014 年的 Seq2Seq(编码器-解码器 RNN)做机器翻译时,编码器把整个源句子压缩成一个固定长度向量 交给解码器------句子一长,这个向量就成了信息瓶颈,翻译质量骤降。Bahdanau et al.(2015)的解法:让解码器在生成每个词时,回头「查看」编码器的全部隐状态 并加权取用------这个「查看并加权」的操作就是最早用于神经机器翻译的注意力机制。但彼时的注意力仍架在顺序计算的 RNN 之上;Transformer(2017)则更进一步问:如果只用注意力、把 RNN 整个去掉呢?------这构成了本章的起点。
Scaled Dot-Product Attention(缩放点积注意力)是 Transformer 使用的标准形式。给定三个矩阵:
- Q∈Rn×dkQ \in \mathbb{R}^{n \times d_k}Q∈Rn×dk(Query,查询)
- K∈Rn×dkK \in \mathbb{R}^{n \times d_k}K∈Rn×dk(Key,键)
- V∈Rn×dvV \in \mathbb{R}^{n \times d_v}V∈Rn×dv(Value,值)
注意力输出为:
Attention(Q,K,V)=softmax (QK⊤dk)V \text{Attention}(Q, K, V) = \text{softmax}\!\left(\frac{Q K^\top}{\sqrt{d_k}}\right) V Attention(Q,K,V)=softmax(dk QK⊤)V
计算流程:
- 计算 QK⊤Q K^\topQK⊤,得到 n×nn \times nn×n 的「相似度矩阵」(每对位置的点积)。
- 除以 dk\sqrt{d_k}dk 做缩放(见下)。
- 沿行做 Softmax,得到注意力权重(每行是一个概率分布,表示该位置对所有位置的注意力分布)。
- 用权重对 VVV 加权求和,得到输出。
为什么除以 dk\sqrt{d_k}dk 。假设 QQQ、KKK 的每个分量独立同分布,均值为 0、方差为 1,则 q⋅k=∑i=1dkqikiq \cdot k = \sum_{i=1}^{d_k} q_i k_iq⋅k=∑i=1dkqiki 的方差为 dkd_kdk。当 dkd_kdk 较大时,点积绝对值会很大,Softmax 进入饱和区(几乎所有概率集中在一个位置),梯度极小。除以 dk\sqrt{d_k}dk 把方差拉回 1,保证 Softmax 处于有效梯度区域。这是 Transformer 训练稳定的关键细节。
自注意力 vs 一般的 Attention 。「自注意力」指 Q、K、V 都来自同一个输入(对自身做注意力);一般的 Attention(如 Seq2Seq 中的)Q 来自解码器、K/V 来自编码器,实现「跨序列关注」。
复杂度分析:
- 时间复杂度 :O(n2⋅d)O(n^2 \cdot d)O(n2⋅d)。QK⊤Q K^\topQK⊤ 是 n×nn \times nn×n 矩阵,需要 O(n2d)O(n^2 d)O(n2d)。
- 空间复杂度 :O(n2+nd)O(n^2 + nd)O(n2+nd)。注意力矩阵 n×nn \times nn×n 是主要开销。
- 对比 :
- RNN: O(n⋅d2)O(n \cdot d^2)O(n⋅d2),但顺序执行,无法并行。
- CNN: O(k⋅n⋅d2)O(k \cdot n \cdot d^2)O(k⋅n⋅d2)(k 为卷积核),受限于感受野。
- 自注意力: O(n2⋅d)O(n^2 \cdot d)O(n2⋅d),可完全并行,任意位置 O(1) 距离。
优点:
- 任意两个位置直接相连,长距离依赖无障碍。
- 完全并行(无顺序依赖),训练速度远超 RNN。
- 可解释性较好(注意力权重可视化展示模型关注位置)。
缺点:
- 二次复杂度,长序列(>8K token)显存爆炸。
- 注意力矩阵可能不聚焦(冗余信息)。
- 对位置信息不敏感(默认把序列当作「集合」),需要位置编码补充。
实现细节:
python
import torch
import torch.nn.functional as F
def scaled_dot_product_attention(q, k, v, mask=None):
"""
q: (B, n, d_k)
k: (B, n, d_k)
v: (B, n, d_v)
mask: (B, n, n) 或 (n, n),True 表示「屏蔽」的位置
"""
d_k = q.size(-1)
scores = torch.matmul(q, k.transpose(-2, -1)) / (d_k ** 0.5) # (B, n, n)
if mask is not None:
scores = scores.masked_fill(mask == True, float('-inf'))
attn = F.softmax(scores, dim=-1) # (B, n, n)
return torch.matmul(attn, v) # (B, n, d_v)
PyTorch 2.0+ 提供 torch.nn.functional.scaled_dot_product_attention,内部用 FlashAttention、Memory-Efficient 等优化内核,生产代码优先使用:
python
from torch.nn.functional import scaled_dot_product_attention
out = scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=0.1)
注意 attn_mask 的语义差异 。上面的手写函数约定 True = 屏蔽;而 F.scaled_dot_product_attention 的布尔 attn_mask 语义相反------**True = 参与注意力**。把因果掩码(上三角为 True)直接传入 SDPA,会把「允许看的位置」全部屏蔽掉,模型学不到任何东西且不报错。正确做法是传 ~mask`(取反),或改用浮点掩码(允许处填 0、禁止处填 −∞-\infty−∞)。这是 PyTorch 实现因果掩码时最常见的静默错误。
掩码(Mask) 。解码器训练时需要「因果掩码」:每个位置只能看到自己及之前的输入,不能看到未来。构造上三角矩阵(右上为 True),在 Softmax 前把对应位置填充为 −∞-\infty−∞。
1.2 多头注意力
多头注意力 (Multi-Head Attention, MHA)的核心思想:与其让一个注意力在 d_model 维空间里同时关注所有模式,不如把 d_model 维拆成 h 个子空间,每个子空间独立做一次注意力。
数学定义:
MultiHead(Q,K,V)=Concat(head1,...,headh)WO \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h) W^O MultiHead(Q,K,V)=Concat(head1,...,headh)WO
headi=Attention(QWiQ,KWiK,VWiV) \text{head}_i = \text{Attention}(Q W_i^Q, K W_i^K, V W_i^V) headi=Attention(QWiQ,KWiK,VWiV)
其中 WiQ,WiK∈Rdmodel×dkW_i^Q, W_i^K \in \mathbb{R}^{d_{\text{model}} \times d_k}WiQ,WiK∈Rdmodel×dk, WiV∈Rdmodel×dvW_i^V \in \mathbb{R}^{d_{\text{model}} \times d_v}WiV∈Rdmodel×dv, WO∈Rhdv×dmodelW^O \in \mathbb{R}^{h d_v \times d_{\text{model}}}WO∈Rhdv×dmodel。
典型配置 :h=8h = 8h=8,dk=dv=dmodel/h=64d_k = d_v = d_{\text{model}} / h = 64dk=dv=dmodel/h=64(当 dmodel=512d_{\text{model}}=512dmodel=512)。每个 head 维度更小,但能并行 h 个,等价于让模型在 h 个不同子空间「独立观察」输入。
为什么有效。不同 head 可以学到不同类型的关联:
- 某个 head 关注相邻 token(局部语法)
- 另一个 head 关注远距离依赖(语义)
- 还有 head 关注句法结构(主谓关系)
- 等等
经验上,可视化不同 head 的注意力图,可以看到清晰的模式分化。这是单头无法做到的------单头被迫把所有信息「平均」到一个注意力分布里。
参数量与计算量。MHA 的总参数与单头相当:
- 单头:3×dmodel23 \times d_{\text{model}}^23×dmodel2(Q、K、V 投影)+ 1 × dmodel2d_{\text{model}}^2dmodel2(输出投影)= 4dmodel24 d_{\text{model}}^24dmodel2
- 多头:h×3×(dmodel×dk)+dmodel×hdv=4dmodel2h \times 3 \times (d_{\text{model}} \times d_k) + d_{\text{model}} \times h d_v = 4 d_{\text{model}}^2h×3×(dmodel×dk)+dmodel×hdv=4dmodel2(当 dk=dv=dmodel/hd_k = d_v = d_{\text{model}}/hdk=dv=dmodel/h)
总计算量也不增加 (单头 n2⋅dmodeln^2 \cdot d_{\text{model}}n2⋅dmodel,多头 h⋅n2⋅(dmodel/h)=n2⋅dmodelh \cdot n^2 \cdot (d_{\text{model}}/h) = n^2 \cdot d_{\text{model}}h⋅n2⋅(dmodel/h)=n2⋅dmodel)。
变体:
- MQA(Multi-Query Attention,Shazeer 2019):所有 head 共享同一组 K、V,只有 Q 分头。在 LLM 推理中显著减少 KV cache 显存。
- GQA(Grouped-Query Attention, Ainslie et al. 2023):将 head 分组,组内共享 K、V,是 MHA 与 MQA 的折中,Llama 2 等模型采用。
- MLA(Multi-Latent Attention, DeepSeek-V2 2024):把 K、V 压缩到低维潜空间,进一步减少 KV cache。
PyTorch 实现:
python
import torch.nn as nn
import torch.nn.functional as F
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads, dropout=0.1):
super().__init__()
assert d_model % num_heads == 0
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask=None):
B, L, _ = x.shape
# 线性投影后分头
q = self.W_q(x).view(B, L, self.num_heads, self.d_k).transpose(1, 2)
k = self.W_k(x).view(B, L, self.num_heads, self.d_k).transpose(1, 2)
v = self.W_v(x).view(B, L, self.num_heads, self.d_k).transpose(1, 2)
# q, k, v: (B, num_heads, L, d_k)
attn = F.scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=self.dropout.p)
# attn: (B, num_heads, L, d_k)
attn = attn.transpose(1, 2).contiguous().view(B, L, self.d_model)
return self.W_o(attn)
nn.MultiheadAttention(d_model, num_heads) 是 PyTorch 内置版本,生产代码可直接使用。
二、架构设计
2.1 编码器与解码器
原始 Transformer(Vaswani et al., 2017, "Attention Is All You Need")是为机器翻译设计的,采用编码器-解码器(Encoder-Decoder)架构。后续 BERT、GPT 等模型只用了其中一部分。
Transformer 完整结构(以翻译为例):
text
Encoder (N=6) Decoder (N=6)
┌─────────────────┐ ┌─────────────────┐
│ Multi-Head │ │ Masked Multi-Head│
│ Self-Attention │ │ Self-Attention │
│ (Q=K=V=input) │ │ (只看已生成) │
└────────┬────────┘ └────────┬────────┘
│ │
┌────────▼────────┐ ┌────────▼────────┐
│ Add & Norm │ │ Add & Norm │
└────────┬────────┘ └────────┬────────┘
│ │
┌─────────────────┐ ┌────────▼────────┐
│ Feed-Forward │ │ Multi-Head │
│ (Position-wise) │ │ Cross-Attention │
│ │ │ (Q=decoder, │
│ │ │ K/V=encoder) │
└────────┬────────┘ └────────┬────────┘
│ │
┌────────▼────────┐ ┌────────▼────────┐
│ Add & Norm │ │ Add & Norm │
└─────────────────┘ │ │
│ Feed-Forward │
│ │
└────────┬────────┘
│
┌────────▼────────┐
│ Add & Norm │
└─────────────────┘
编码器的核心组件(每个子层):
- Multi-Head Self-Attention:Q、K、V 都来自编码器上一层的输出,捕获输入序列内部关联。
- Add & Norm :残差连接 + LayerNorm。公式 LayerNorm(x+Sublayer(x))\text{LayerNorm}(x + \text{Sublayer}(x))LayerNorm(x+Sublayer(x))。这是 ResNet 公式的直接继承(见第五章)。
- Feed-Forward Network(FFN) :逐位置(position-wise)的两层全连接。FFN(x)=max(0,xW1+b1)W2+b2\text{FFN}(x) = \max(0, x W_1 + b_1) W_2 + b_2FFN(x)=max(0,xW1+b1)W2+b2。中间维度通常 4×dmodel4 \times d_{\text{model}}4×dmodel。
解码器的核心组件(比编码器多一个子层):
- Masked Multi-Head Self-Attention :Q、K、V 都来自解码器,但用因果掩码阻止位置 ttt 看到 ttt 之后的信息。
- Add & Norm:同上。
- Multi-Head Cross-Attention :Q 来自解码器上一子层输出,K、V 来自编码器最终输出。这让解码器在生成每个位置时「关注」输入序列的相关部分。
- Add & Norm:同上。
- Feed-Forward Network:同上。
- Add & Norm:同上。
三种注意力的角色:
| 类型 | Q 来源 | K、V 来源 | 作用 |
|---|---|---|---|
| 编码器自注意力 | 编码器输入 | 编码器输入 | 编码器内部关联 |
| 解码器自注意力(掩码) | 解码器输入 | 解码器输入 | 保持自回归特性 |
| 解码器-编码器注意力 | 解码器 | 编码器 | 让解码器读输入 |
为何要用 Add & Norm 而不是 Post-LN 。原 Transformer 用的是 Post-LN(LayerNorm(x+Sublayer(x))\text{LayerNorm}(x + \text{Sublayer}(x))LayerNorm(x+Sublayer(x)))。实践中发现:
- Post-LN 训练时,深层 Transformer 容易不稳定(需要 Warmup,且 Warmup 比例较大)。
- 后续工作(XLM-R、GPT-2 等)采用 Pre-LN (x+Sublayer(LayerNorm(x))x + \text{Sublayer}(\text{LayerNorm}(x))x+Sublayer(LayerNorm(x))),训练更稳定;Pre-LN 与 Post-LN 在最终模型质量上的差异随训练数据/任务而异(参见 XLM-R 论文、On Layer Normalization in the Transformer Architecture 等消融研究)。
- 折中方案:Post-LN + 精心调参 或 Pre-LN + 适当缩放。
现代大模型(LLM)几乎一致选择 Pre-LN。
参数规模(原论文):
- Base model: dmodel=512d_{\text{model}}=512dmodel=512, h=8h=8h=8, 6 encoder + 6 decoder, 约 65M 参数。
- Big model: dmodel=1024d_{\text{model}}=1024dmodel=1024, h=16h=16h=16, 6+6 层, 约 213M 参数。
WMT 2014 翻译成绩(Big 模型):EN-DE 28.4 BLEU、EN-FR 41.0 BLEU(checkpoint 平均后 41.8 BLEU),均创当时 SOTA。Base 模型对应 EN-DE 27.3 BLEU、EN-FR 38.1 BLEU。
2.2 位置编码
为什么需要位置编码 。自注意力是「置换不变」(permutation equivariant)的:把输入序列打乱顺序,只要内容相同,自注意力的输出也会被同步打乱,但「集合意义」上的输出不变。这意味着没有位置编码,Transformer 看不到词序------「猫吃鱼」和「鱼吃猫」会得到相同的隐藏表示。
位置编码(Positional Encoding, PE)给每个位置加一个独特的向量,使模型能区分不同位置的同 token。
正弦位置编码(Sinusoidal, 原 Transformer 使用):
PE(pos,2i)=sin(pos/100002i/dmodel)PE(pos,2i+1)=cos(pos/100002i/dmodel) \begin{aligned} PE_{(pos, 2i)} &= \sin(pos / 10000^{2i / d_{\text{model}}}) \\ PE_{(pos, 2i+1)} &= \cos(pos / 10000^{2i / d_{\text{model}}}) \end{aligned} PE(pos,2i)PE(pos,2i+1)=sin(pos/100002i/dmodel)=cos(pos/100002i/dmodel)
其中 pospospos 是位置, iii 是维度索引。每个维度对应不同频率的正弦/余弦。
优点:
- 理论可外推到训练时未见过的序列长度 (因为是固定函数,任意 pospospos 都能计算)。
- 不增加可学习参数。
- 不同位置之间有「相对位置」关系(由三角恒等式保证):PEpos+kPE_{pos+k}PEpos+k 可以表示为 PEposPE_{pos}PEpos 的线性函数。
学习位置编码 (Learned Positional Embedding)。直接把每个位置作为可学习向量。BERT、GPT-2 等采用。简单有效,但外推到训练时未见过的长度困难(需要特殊处理或重新训练)。
相对位置编码(Relative Positional Encoding)。把「两个位置之间的相对距离」编码到注意力分数中,而非给每个绝对位置加偏置。代表工作:
- T5(Raffel et al. 2020):用「桶化」相对距离 + 学习偏置。
- ALiBi (Press et al. 2022):对注意力分数加上一个与距离成线性关系的负偏置 (
-m·|i-j|,m 为与头相关的斜率),等价于注意力随距离线性衰减,简单但显著提升外推能力。 - RoPE (Rotary Position Embedding, Su et al. 2021):用「旋转」矩阵把位置信息编码到 Query 和 Key 中,保持相对距离的内积关系。Llama、Qwen、Mistral 等主流开源 LLM 一致采用。
RoPE 的直观理解 。把每个 token 的 Q、K 向量视为复数,按位置做角度为 pos⋅θipos \cdot \theta_ipos⋅θi 的旋转。这样:
- 注意力分数 q⊤kq^\top kq⊤k 中天然包含「相对位置差」信息。
- 相对距离越远,内积越倾向于 0,符合「近处 token 更相关」的直觉。
- 长度外推性比绝对位置编码好。
现代位置编码方案对比:
| 方案 | 代表模型 | 长度外推 | 计算开销 |
|---|---|---|---|
| 绝对正弦 | 原始 Transformer | 较好 | 低 |
| 绝对学习 | BERT、GPT-2 | 差 | 低 |
| RoPE | Llama、Qwen | 较好 | 极低(只需修改 Q/K) |
| ALiBi | BLOOM | 极好 | 极低(加偏置即可) |
最大序列长度的工程经验:
- 训练时:512-4096 token 是常见。
- 推理时:通过 RoPE 缩放、YaRN、PI 等技术可外推到 32K-128K 甚至更长。
- 长上下文(Long Context)是当前 LLM 的重要竞争维度。
现代大模型的常见做法:训练时用 4K-8K 上下文,推理时通过位置编码插值(Position Interpolation, Chen et al. 2023)或 YaRN 等技术扩展到 32K-200K。
三、预训练范式
3.1 BERT 与 GPT 路线
Transformer 架构发布后(2017),自然语言处理迅速分化出两条主流预训练路线:BERT 路线(Encoder-only,双向) 与 GPT 路线(Decoder-only,自回归)。它们共享 Transformer 主体,但在「预训练目标」「可见性」「下游任务适配」上截然不同。
先决问题:文本如何变成 token id 。神经网络的输入是整数序列,原始文本必须先经过分词(tokenization) 。现代 LLM 用 BPE(Byte Pair Encoding) 及其变体(WordPiece、SentencePiece):从字符级词表开始,迭代合并语料中最高频的相邻片段,直到词表达到目标大小(3 万--20 万)。效果:常见词是单个 token,罕见词被拆成子词,任意新词都能表示(退化为字符序列)。tiktoken(GPT 系)与 Hugging Face tokenizers 是常用实现。粗略估算:英文约 4 字符/token,中文约 1--1.5 字/token------预算上下文长度时按此折算。
BERT 路线(Encoder-only,双向)。Devlin et al., 2018, "BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding"。
架构:只用 Transformer 编码器,堆叠多层(基线 12 层,大模型 24 层)。每个位置可以「看到」所有其他位置(无掩码)。
预训练目标:
- MLM (Masked Language Modeling):随机遮盖输入中 15% 的 token,让模型预测被遮盖的 token。其中 80% 替换为
[MASK],10% 替换为随机 token,10% 保持原 token(避免预训练-微调不一致)。 - NSP(Next Sentence Prediction):判断两个句子是否相邻(二分类),帮助模型学到句间关系。后续工作证明其收益有限:RoBERTa 去掉 NSP 无损,ALBERT 把它换成更难的 SOP(句子顺序预测);NSP 在后续主流模型中被废弃。
特性:
- 双向(Bidirectional):每个位置能看完整上下文,适合理解类任务(分类、QA、NER)。
- 典型应用:文本分类、命名实体识别、问答(答案抽取)、句子相似度。
- 微调方式 :在
[CLS]位置接分类头,或对每个 token 接 token-level 分类头。 - 代表模型:BERT(2018)、RoBERTa(2019, 去掉 NSP,更大数据,更大 batch)、ALBERT(2019, 参数共享)、DeBERTa(2021, 解耦相对位置)、ELECTRA(2020, 替换 token 检测)。
GPT 路线(Decoder-only,自回归)。Radford et al., 2018-2020, "Improving Language Understanding by Generative Pre-Training"、"Language Models are Unsupervised Multitask Learners"、"Language Models are Few-Shot Learners"(GPT-3)。
架构 :只用 Transformer 解码器,但去掉了 Cross-Attention(没有编码器输入)。保留 Masked Self-Attention,使每个位置只能看到自己及之前的 token(自回归)。堆叠数十到上百层。
预训练目标:
- CLM / AR (Causal Language Modeling):给定前 ttt 个 token,预测第 t+1t+1t+1 个 token 的概率。损失是负对数似然的平均。
- L=−∑t=1TlogP(xt∣x<t)L = -\sum_{t=1}^{T} \log P(x_t | x_{<t})L=−∑t=1TlogP(xt∣x<t)
特性:
- 单向 / 自回归:每个位置只能看历史,适合生成类任务(续写、对话、翻译、代码)。
- 典型应用:文本生成、对话、续写、代码补全、零样本/少样本推理。
- 推理范式:给定 prompt,逐 token 生成,可结合搜索(beam search)、采样(top-k、top-p)、温度缩放。
自回归解码策略(生成质量与多样性的旋钮):
- Greedy(贪心):每步取概率最高的 token。确定、快,但输出容易重复、呆板。
- Beam Search :维护 kkk 条最优候选序列并行扩展,取整体概率最高者。适合翻译等"有标准答案"的任务;在对话/创作中产出千篇一律。
- Temperature(温度) :把 logits 除以 TTT 再做 softmax。T<1T<1T<1 分布变尖(更保守), T>1T>1T>1 变平(更随机)。
- Top-k :只在概率最高的 kkk 个 token 中采样,截断长尾。
- Top-p / nucleus :在累计概率达到 ppp(如 0.9)的最小 token 集合中采样------候选数随分布形状自适应,通常优于固定 kkk。
- 实践组合:对话/创作常用 temperature 0.7--1.0 + top-p 0.9;代码/事实问答用更低温度。
- 代表模型:GPT-1/2/3/4(OpenAI)、Llama 1/2/3(Meta)、Mistral、Qwen(阿里)、DeepSeek、Claude(Anthropic) 等。
- 规模效应:模型能力随参数、数据、算力呈幂律提升(GPT-3 论文),175B 参数时涌现零样本能力。
关键对比:
| 维度 | BERT(Encoder) | GPT(Decoder) |
|---|---|---|
| 注意力方向 | 双向(全可见) | 单向(只看历史) |
| 预训练目标 | MLM(填空) | CLM(自回归) |
| 上下文建模 | 完整双向 | 严格因果 |
| 适合任务 | 理解(NLU) | 生成(NLG) |
| 推理范式 | 单次前向 | 自回归逐 token |
| 上下文长度 | 有限(512-4096) | 较长(4K-200K) |
| 工程重点 | 微调(Fine-tuning) | In-context Learning、RLHF |
T5 路线(Encoder-Decoder) 。Raffel et al., 2019, "Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer"。同时用编码器和解码器,把所有任务统一为「文本到文本」(分类任务把类别名作为目标文本)。在多个 NLU 任务上表现优异,但架构比 BERT/GPT 复杂,工程上不如这两者流行。
对比学习路线(CLIP,概念) 。除 MLM/CLM 外还有第三种预训练信号:对比学习 。CLIP(Radford et al., 2021)用 4 亿图文对,把图像与文本分别编码后,用 InfoNCE 损失拉近匹配的图文对、推开错配对------预训练目标即"判断哪段文本配哪张图"。学到的对齐空间支持零样本分类(把类别名当文本输入比相似度),也是文生图的条件入口(第八章 Stable Diffusion 的文本编码器即 CLIP)。对比学习是第一章 §3.1 所述「自监督预训练」家族在多模态上的代表。
最小可运行示例:decoder-only 语言模型。把本章(以及第六章的 Embedding)的零件拼成整机------一个能做 next-token 训练的微型 GPT:
python
import torch
import torch.nn as nn
import torch.nn.functional as F
class Block(nn.Module): # 解码器层 = §2.1 的组件
def __init__(self, d_model, num_heads):
super().__init__()
self.attn = nn.MultiheadAttention(d_model, num_heads, batch_first=True)
self.ln1 = nn.LayerNorm(d_model) # Pre-LN(现代大模型惯例)
self.ln2 = nn.LayerNorm(d_model)
self.ffn = nn.Sequential(nn.Linear(d_model, 4 * d_model), nn.GELU(),
nn.Linear(4 * d_model, d_model))
def forward(self, x, causal_mask):
h = self.ln1(x)
x = x + self.attn(h, h, h, attn_mask=causal_mask)[0] # 掩码自注意力 + 残差
x = x + self.ffn(self.ln2(x)) # FFN + 残差
return x
class MiniGPT(nn.Module):
def __init__(self, vocab_size, d_model=256, num_heads=8, num_layers=4, max_len=256):
super().__init__()
self.tok_emb = nn.Embedding(vocab_size, d_model) # token 嵌入(第六章)
self.pos_emb = nn.Embedding(max_len, d_model) # 学习位置编码(§2.2)
self.blocks = nn.ModuleList(Block(d_model, num_heads) for _ in range(num_layers))
self.ln_f = nn.LayerNorm(d_model)
self.head = nn.Linear(d_model, vocab_size) # LM head
mask = torch.triu(torch.full((max_len, max_len), float('-inf')), diagonal=1)
self.register_buffer('causal_mask', mask) # 因果掩码(§1.1)
def forward(self, ids): # ids: (B, L) 整数 token id
B, L = ids.shape
pos = torch.arange(L, device=ids.device)
x = self.tok_emb(ids) + self.pos_emb(pos)[None, :, :]
for blk in self.blocks:
x = blk(x, self.causal_mask[:L, :L])
return self.head(self.ln_f(x)) # (B, L, vocab):每个位置预测下一个 token
model = MiniGPT(vocab_size=8000)
ids = torch.randint(0, 8000, (4, 64))
logits = model(ids)
# 训练目标:位置 t 的输出应预测 ids[t+1] ------ 整体右移一位做交叉熵
loss = F.cross_entropy(logits[:, :-1].reshape(-1, 8000), ids[:, 1:].reshape(-1))
注意三处细节:因果掩码保证位置 ttt 只能看到 <t<t<t(§1.1);Pre-LN + 残差的组合(§2.1、第四章);目标是把整体序列右移一位做交叉熵。这三点就是 GPT 式预训练的全部骨架------真实 LLM 与它的差别只在规模、数据与工程细节。
为什么 GPT 路线胜出成为主流。在 2020-2024 年,Decoder-only 路线逐渐成为 LLM 的事实标准(BERT 的辉煌在 2020 年后被 GPT-3/ChatGPT 取代)。原因:
- 规模效应:GPT 路线在超大规模下表现出的「涌现能力」(in-context learning、推理、代码)是 BERT 路线没有的。
- 通用性:同一模型既可做对话、续写、翻译、代码,无需为不同任务设计不同头。
- Scaling Law:Kaplan et al. 2020、Hoffmann et al. 2022(Chinchilla)系统研究了 loss vs. 模型/数据规模,给 Decoder-only 训练提供了清晰路线图。
- RLHF 对齐:Decoder-only 天然适合 RLHF(用语言模型生成的输出作为动作空间,人类反馈作为奖励)。
统一视角。可以把所有 Transformer 模型视为:
- 目标函数 × 架构 的组合
- 目标:CLM、MLM、Seq2Seq、对比学习等
- 架构:Encoder-only、Decoder-only、Encoder-Decoder
- 主流:CLM + Decoder-only(LLM 时代)
预训练 vs 微调 vs 提示学习:
- 预训练(Pre-training):在海量无标注语料上自监督学习(MLM/CLM)。代价高,通常一次性。
- 微调(Fine-tuning):在下游任务的小数据集上继续训练,代价中等,适合垂直领域适配。
- 提示学习(Prompting / In-context Learning):不修改参数,只通过 prompt 引导模型行为。LLM 时代主流。
延伸阅读。现代 Transformer 的演进方向(MoE 架构、长上下文扩展、多模态、State Space Model 如 Mamba 等)不在本教程范围内,可参考综述论文与对应前沿工作深入。