Transformer
鉴于Transformer架构已经逐步成为深度学习乃至机器学习的核心架构,我单独开这么一篇笔记来记录学习Transformer架构。在DNN架构介绍中我们已经简单了解了Transformer的一些特点,在本笔记中我们将进一步深入了解Transformer。
从 RNN/CNN 到 Transformer
在 RNN 笔记中我们已经看到,RNN 有两个致命问题:
| 问题 | RNN 的困境 | Transformer 的解决方案 |
|---|---|---|
| 无法并行 | \(h_t\) 必须等 \(h_{t-1}\) 算完,顺序依赖 \(O(n)\) | Self-Attention 一次性处理所有位置,\(O(1)\) 顺序操作 |
| 长距离依赖 | 梯度经过多步连乘后消失,记不住远处信息 | 任意两个位置直接相连,路径长度\(O(1)\) |
LSTM/GRU 缓解了梯度消失但无法解决并行问题。Attention 机制最初是作为 Seq2Seq 的辅助(Bahdanau, 2014),但 2017 年 Vaswani 等人提出了一个激进想法:完全抛弃 RNN,仅用 Attention 构建整个模型。
"Attention is All You Need" — Vaswani et al., 2017
架构总览
完整架构图
下图是 Transformer 的完整架构(图源:"Attention Is All You Need" 原论文):

架构要点:
- 左半边 = Encoder(编码器):处理源语言输入,输出每个词的上下文表示
- 右半边 = Decoder(解码器):利用 Encoder 的输出,自回归生成目标语言
- Nx 表示堆叠 \(N\) 层(原论文 \(N=6\))
- 核心超参数:\(d_{\text{model}}=512\),\(h=8\) 头,\(d_{ff}=2048\)
数据流:一次翻译的完整过程
以翻译 "我 喜欢 猫" → "I like cats" 为例:
- 输入编码:源语言 "我 喜欢 猫" → Token Embedding + Positional Encoding
- Encoder处理:经过6层Encoder,每层都做Self-Attention + FFN,最终输出每个源词的上下文表示
- Decoder自回归生成:
- 输入
<BOS>(句首标记)→ Decoder通过Cross-Attention看Encoder输出 → 预测 "I" - 输入
<BOS> I→ 预测 "like" - 输入
<BOS> I like→ 预测 "cats" - 输入
<BOS> I like cats→ 预测<EOS>(结束标记)
- 输入
输入处理
Token Embedding
将离散的词(token)映射为连续的向量。这与Word2Vec类似,但Transformer的嵌入矩阵是随模型一起训练的。
其中 \(E \in \mathbb{R}^{V \times d_{\text{model}}}\) 是嵌入矩阵,\(V\) 是词表大小。
注意:原论文中对嵌入向量乘以了 \(\sqrt{d_{\text{model}}}\) 进行缩放:
这是为了在与Positional Encoding相加时,保持两者的量级相当。
Positional Encoding(位置编码)
为什么需要位置编码?
Self-Attention是一个集合操作——它对输入的处理与顺序无关。如果不加位置信息,"狗咬人" 和 "人咬狗" 对模型来说完全一样。Positional Encoding给每个位置注入顺序信息。
正弦余弦位置编码:
- \(pos\):词在序列中的位置(0, 1, 2, ...)
- \(i\):维度索引(0, 1, ..., \(d_{\text{model}}/2 - 1\))
- 偶数维用 \(\sin\),奇数维用 \(\cos\)
直觉理解:每个维度对应一个不同频率的"钟摆"。低维度变化快(高频),高维度变化慢(低频)。这就像二进制计数器——个位翻转最快,高位翻转最慢。不同位置的组合是唯一的,就像每个数字的二进制表示是唯一的。
为什么用正弦/余弦而不是简单的线性编码(如1, 2, 3...)?
- 有界性:值域在 \([-1, 1]\),不会随位置增长而爆炸
- 相对位置可学习:对于固定偏移 \(k\),\(PE_{pos+k}\) 可以表示为 \(PE_{pos}\) 的线性函数(利用三角函数的和差化积公式),这使模型能学习相对位置关系:
其中 \(\omega = 1/10000^{2i/d_{\text{model}}}\)。也就是说,位置 \(pos+k\) 的编码可以由位置 \(pos\) 的编码通过一个只依赖于 \(k\)(不依赖于 \(pos\))的线性变换得到。
- 可外推:理论上可以推广到训练中未见过的序列长度
与输入的结合方式:直接相加(不是拼接)
Encoder 详解
下图高亮标注了 Encoder 部分(红色框内):

每个Encoder层包含两个子层:Multi-Head Self-Attention 和 Feed-Forward Network,每个子层外面包裹 残差连接 + Layer Normalization。
子层1:Multi-Head Self-Attention
编码器中的Self-Attention让每个源词都能关注到所有其他源词,构建全局上下文理解。
下图展示了 Scaled Dot-Product Attention 和 Multi-Head Attention 的内部结构(图源:原论文):

Scaled Dot-Product Attention(左图)的计算流程:
- MatMul:\(Q\) 和 \(K^T\) 做矩阵乘法,得到注意力分数矩阵 \(\in \mathbb{R}^{n \times n}\)
- Scale:除以 \(\sqrt{d_k}\),防止点积过大导致 softmax 梯度消失
- Mask(可选):Decoder 中的因果掩码,将未来位置设为 \(-\infty\)
- Softmax:将分数归一化为概率分布(每行加和为1)
- MatMul:用注意力权重对 \(V\) 加权求和,得到输出
Multi-Head Attention(右图)的计算流程:
- \(h=8\) 个头,每个头的维度 \(d_k = d_v = d_{\text{model}} / h = 64\)
- 每个头学习不同的注意力模式(如语法关系、语义关系、位置关系等)
- 最后拼接并通过线性层 \(W^O\) 映射回 \(d_{\text{model}}\) 维
在编码 "我 吃 苹果" 时:
- "苹果"可以关注到"吃",从而理解这里的"苹果"是水果(而非公司)
- "吃"可以关注到"我",理解动作的主语
这回答了待思考的问题: "模型在翻译'苹果'时,是否真的关注到了前面的动词'吃'?" —— 是的。 Self-Attention让"苹果"可以直接(\(O(1)\) 路径长度)关注到"吃",并根据上下文动态调整自己的表示。
子层2:Position-wise Feed-Forward Network (FFN)
对每个位置独立地施加相同的两层全连接网络:
或者写成更清晰的两步:
- \(W_1 \in \mathbb{R}^{d_{\text{model}} \times d_{ff}}\):升维,\(512 \to 2048\)
- \(W_2 \in \mathbb{R}^{d_{ff} \times d_{\text{model}}}\):降维,\(2048 \to 512\)
为什么需要FFN?
Self-Attention是一个线性加权求和操作(Softmax权重 × Value),即使堆叠多层,也只是在做加权组合。FFN引入了非线性(通过ReLU),赋予模型非线性变换能力。
"Position-wise"的含义:FFN对序列中每个位置独立施加,不同位置之间不交互。位置间的信息交换完全由Self-Attention负责。可以将FFN理解为对每个词的"独立思考",而Self-Attention是词与词之间的"交流讨论"。
残差连接与Layer Normalization
每个子层都配有残差连接(Residual Connection)和层归一化(Layer Normalization):
残差连接(He et al., 2015):让梯度可以"跳过"子层直接传播,解决深层网络的梯度消失问题。
如果 \(f(x)\) 学到的变换不好,梯度至少可以通过恒等映射 \(x\) 传回去。
Layer Normalization:对单个样本的特征维度做归一化:
- \(\mu = \frac{1}{d}\sum_{i=1}^d x_i\):特征维度的均值
- \(\sigma^2 = \frac{1}{d}\sum_{i=1}^d (x_i - \mu)^2\):特征维度的方差
- \(\gamma, \beta\):可学习的缩放和偏移参数
- \(\epsilon\):防止除零的小常数
为什么Layer Norm而非Batch Norm?
这回答了待思考问题之一。
Batch Norm:在batch维度上归一化(对同一个特征,跨batch中所有样本计算均值和方差)
Layer Norm:在特征维度上归一化(对同一个样本,跨所有特征计算均值和方差)
Batch Norm Layer Norm
跨样本归一化 跨特征归一化
Batch: ┌ Sample1: [f1 f2 f3 f4] ┐ Sample1: [f1 f2 f3 f4] ← 在这一行归一化
│ Sample2: [f1 f2 f3 f4] │ Sample2: [f1 f2 f3 f4] ← 在这一行归一化
│ Sample3: [f1 f2 f3 f4] │ Sample3: [f1 f2 f3 f4] ← 在这一行归一化
└────────────────────────┘
↑ 在每一列归一化
Layer Norm更适合NLP的三个原因:
- 变长序列:NLP中每个样本的序列长度不同。Batch Norm需要跨样本统计,但不同样本的第10个位置可能有完全不同的语义角色,统计没有意义。
- 小batch问题:Transformer训练常用较小的batch size(GPU显存限制),Batch Norm的统计量不稳定。
- 推理时不依赖batch:Layer Norm只看单个样本自身,推理时行为与训练时一致,不需要维护running mean/variance。
Decoder 详解
下图高亮标注了 Decoder 部分(红色框内):

Decoder比Encoder多一个子层,共三个子层:
子层1:Masked Multi-Head Self-Attention
与Encoder的Self-Attention几乎相同,唯一区别是加了因果掩码(Causal Mask):
为什么需要掩码?
在训练时,Decoder看到的是完整的目标序列(如 "I like cats"),但为了模拟自回归生成的过程,生成第 \(t\) 个词时不能"偷看"第 \(t+1, t+2, \ldots\) 个词。
目标序列: <BOS> I like cats <EOS>
生成 "I" 时,只能看到 <BOS>
生成 "like" 时,只能看到 <BOS> I
生成 "cats" 时,只能看到 <BOS> I like
生成 <EOS> 时,只能看到 <BOS> I like cats
掩码矩阵(0=可见,-∞=遮蔽):
<BOS> I like cats <EOS>
<BOS> [ 0 -∞ -∞ -∞ -∞ ]
I [ 0 0 -∞ -∞ -∞ ]
like [ 0 0 0 -∞ -∞ ]
cats [ 0 0 0 0 -∞ ]
<EOS> [ 0 0 0 0 0 ]
这个掩码加在Softmax之前的注意力分数上。\(-\infty\) 经Softmax后变为0,实现了信息屏蔽。
训练效率的关键:有了掩码,Decoder可以一次性并行处理整个目标序列(而不是像RNN那样逐步生成),因为掩码保证了每个位置只看到它该看到的历史信息。这就是所谓的 Teacher Forcing。
子层2:Cross-Attention(编码器-解码器注意力)
这是连接Encoder和Decoder的桥梁:
- Query来自Decoder当前层的输出("我要翻译什么?")
- Key和Value来自Encoder的最终输出("源语言说了什么?")
这让Decoder在生成每个目标词时,可以关注源语言中最相关的部分。例如生成 "cats" 时,Cross-Attention会高权重关注 "猫" 对应的Encoder输出。
子层3:Feed-Forward Network
与Encoder中的FFN完全相同。
输出层
Decoder最顶层的输出通过一个线性层 + Softmax,得到词表上的概率分布:
\(W_{\text{vocab}} \in \mathbb{R}^{V \times d_{\text{model}}}\),实践中通常与Token Embedding矩阵权重共享(Weight Tying)以减少参数。
前向传播:完整的数值示例
参考 RNN 和 CNN 笔记的风格,我们用一个具体的数值示例走完 Transformer 的整个前向传播过程。
为了简化,使用一个极小的 Transformer:\(d_{\text{model}}=4\),\(h=2\)(2个头),\(d_k = d_v = 2\),\(d_{ff}=8\),\(N=1\)(1层),词表大小 \(V=6\)。
翻译任务:"我 猫" → "I cat"
第1步:输入 Embedding + Positional Encoding
Token Embedding(从嵌入矩阵 \(E\) 查表):
Positional Encoding(用正弦余弦公式计算):
相加得到 Encoder 输入:
输入矩阵 \(X \in \mathbb{R}^{2 \times 4}\)(2个词,每个词4维):
第2步:Encoder Self-Attention
生成 Q, K, V(通过线性变换 \(W^Q, W^K, W^V \in \mathbb{R}^{4 \times 4}\)):
拆分为2个头(每个头 \(d_k = 2\)):
其中 \(Q_1, K_1, V_1 \in \mathbb{R}^{2 \times 2}\)(取前2列),\(Q_2, K_2, V_2 \in \mathbb{R}^{2 \times 2}\)(取后2列)。
Head 1 的 Attention 计算:
(a) 注意力分数:
假设计算结果为:
(b) Softmax(对每一行做归一化):
如何理解注意力权重
第1行 \([0.60, 0.40]\) 表示"我"对自己关注60%,对"猫"关注40%。 第2行 \([0.35, 0.65]\) 表示"猫"对"我"关注35%,对自己关注65%。
(c) 加权求和:
Head 2 同理计算,得到 \(\text{head}_2 \in \mathbb{R}^{2 \times 2}\)。
拼接 + 线性变换:
第3步:残差连接 + Layer Norm
对每个词向量(每一行):计算均值 \(\mu\) 和方差 \(\sigma^2\),然后归一化。
假设"我"经过 Attention 后的值为 \([0.3, 0.7, 0.5, 1.1]\),残差相加后:
第4步:Feed-Forward Network
对每个词独立施加两层全连接:
这就是两次普通的矩阵乘法,和 MLP 的前向传播完全一样。
第5步:再次残差 + Layer Norm
至此,Encoder 处理完毕。EncoderOutput 中的每个词向量都融合了全局上下文信息。
Encoder 的所有中间值都保存在内存中
与 RNN 一样(见 RNN 笔记),训练时前向传播的所有中间结果(\(Q, K, V\), attention 权重, FFN 中间层等)都需要保存,因为反向传播要用。这就是 Transformer 训练显存开销大的直接原因。
第6步:Decoder 输入
假设当前已生成 <BOS>,要预测下一个词 "I"。
Decoder 的输入同样经过 Embedding + Positional Encoding:
第7步:Decoder Masked Self-Attention
与 Encoder 的 Self-Attention 相同,但加上因果掩码。由于当前只有一个词 <BOS>,掩码矩阵为 \(1 \times 1\),没有需要遮蔽的位置。
第8步:Decoder Cross-Attention
这一步是 Encoder 和 Decoder 的连接点:
- \(Q \in \mathbb{R}^{1 \times 4}\)(Decoder 的1个词)
- \(K, V \in \mathbb{R}^{2 \times 4}\)(Encoder 的2个词)
注意力分数 \(\in \mathbb{R}^{1 \times 2}\):模型决定生成当前目标词时应该"看"源语言的哪个词。
这表示生成 "I" 时,模型 35% 关注"我",65% 关注"猫"。(实际中应该更关注"我",这里只是示意数值。)
第9步:Decoder FFN + 输出
经过 FFN → 残差 + LayerNorm 后,Decoder 输出 \(h_1^{\text{dec}} \in \mathbb{R}^{4}\)。
线性层 + Softmax:
取概率最大的位置(index 2)→ 对应词表中的 "I"。
前向传播总结
Transformer 的前向传播本质上就是矩阵乘法的堆叠,和 MLP/CNN/RNN 没有本质区别:
| 组件 | 核心操作 |
|---|---|
| Embedding | 查表(矩阵行选取) |
| Self-Attention | 三次线性变换 + 一次矩阵乘法(\(QK^T\))+ Softmax + 一次矩阵乘法(\(\alpha V\)) |
| FFN | 两次矩阵乘法 + ReLU |
| LayerNorm | 逐元素归一化 |
| Output | 一次矩阵乘法 + Softmax |
反向传播
损失函数
训练时使用交叉熵损失,对每个位置的预测计算 loss 后取平均:
梯度如何流动
Transformer 的反向传播与普通深度网络的反向传播完全相同——都是链式法则。但由于 Transformer 的计算图比 RNN 复杂(有多头注意力、残差连接、Layer Norm 等),我们需要理解梯度经过每个模块时的行为。
梯度流动路径(从 Loss 往回看):
Loss
↓
Softmax + Linear (∂L/∂W_vocab)
↓
Decoder Layer N → ... → Decoder Layer 1
↓ (每层内部:FFN ← Cross-Attn ← Masked Self-Attn)
↓
Decoder Embedding
↓
同时,Cross-Attention 的 K,V 分支将梯度传回:
↓
Encoder Layer N → ... → Encoder Layer 1
↓ (每层内部:FFN ← Self-Attn)
↓
Encoder Embedding
各模块的梯度计算
1. Softmax + 交叉熵
即预测概率减去 one-hot 标签,这是一个非常简洁的梯度。
2. 线性层
和 MLP 的全连接层反向传播完全一样。
3. Self-Attention 的梯度
这是 Transformer 反向传播中最复杂的部分。设 \(A = \text{softmax}(QK^T / \sqrt{d_k})\):
梯度需要流过三条路径:
- 对 \(V\) 的梯度:\(\frac{\partial \mathcal{L}}{\partial V} = A^T \cdot \frac{\partial \mathcal{L}}{\partial \text{Output}}\)
- 对 \(A\) 的梯度:\(\frac{\partial \mathcal{L}}{\partial A} = \frac{\partial \mathcal{L}}{\partial \text{Output}} \cdot V^T\)
- 对 \(Q\) 和 \(K\) 的梯度:需要经过 Softmax 的雅可比矩阵和缩放点积
最终梯度会传播到 \(W^Q, W^K, W^V\) 的参数上。
4. 残差连接的梯度——关键优势
残差连接为什么能解决梯度消失
梯度中始终有一个 +1 项。即使 \(\frac{\partial f}{\partial x}\) 趋近于 0,梯度仍然可以通过恒等映射(shortcut)传回去。这就是为什么 Transformer 可以堆叠几十甚至上百层而不会出现 RNN 那样的梯度消失。
对比 RNN 的 BPTT(见 RNN 笔记):RNN 的梯度是连乘 \(\prod W_{hh}\),一项小于1就指数衰减;而 Transformer 的梯度是连加 \(1 + \frac{\partial f}{\partial x}\),始终有通路。
5. Layer Normalization 的梯度
LayerNorm 的反向传播需要计算雅可比矩阵,涉及均值和方差对输入的依赖。核心公式:
这个公式看起来复杂,但本质上就是归一化操作的逆——把梯度从归一化后的空间映射回原始空间。
Transformer vs RNN 的反向传播对比
| 特性 | RNN (BPTT) | Transformer |
|---|---|---|
| 梯度路径 | 沿时间轴顺序回传,\(O(n)\) 步 | 通过 Attention直接连接任意位置,\(O(1)\) |
| 梯度消失 | 连乘\(\prod_{i} W_{hh} \cdot \text{diag}(\tanh')\) → 指数衰减 | 残差连接保证梯度通路,不会消失 |
| 参数更新 | 所有时间步共享\(U, V, W\),梯度累加 | 每层有独立参数,各层梯度独立计算 |
| 计算并行性 | 反向传播也必须顺序,\(O(n)\) | 反向传播可并行,\(O(1)\) 顺序操作 |
| 显存占用 | 存储\(h_0, h_1, \ldots, h_T\),\(O(n \cdot d_h)\) | 存储所有层的\(Q, K, V\), Attention 矩阵等,\(O(N \cdot n^2 + N \cdot n \cdot d)\) |
Transformer 的显存瓶颈
虽然 Transformer 解决了梯度消失和并行性问题,但它引入了新的瓶颈:\(O(n^2)\) 的注意力矩阵。对于长度 \(n\) 的序列,每层每个头都要存储 \(n \times n\) 的注意力权重矩阵。这就是为什么处理超长文本非常吃显存——序列长度翻倍,显存翻4倍。
训练
Teacher Forcing
训练时,Decoder的输入是真实的目标序列(而非模型自己的预测),这叫Teacher Forcing:
- 输入:
<BOS> I like cats(目标序列右移一位) - 标签:
I like cats <EOS>(目标序列) - 损失函数:对每个位置计算交叉熵损失,然后取平均
Label Smoothing
原论文使用了 \(\epsilon_{\text{ls}} = 0.1\) 的标签平滑。不使用硬标签(one-hot),而是将正确类别的概率从1降为 \(1 - \epsilon\),剩余概率均分给其他类别:
这防止模型过度自信,提升泛化能力。
学习率调度(Warm-up + Decay)
原论文使用了一个特殊的学习率策略:
- 前 \(\text{warmup\_steps}\)(论文中4000步):学习率线性增长
- 之后:学习率按步数的平方根倒数衰减
lr ↑
│ ╱╲
│ ╱ ╲
│ ╱ ╲
│╱ ╲───────────────
└──────────────────────────→ step
0 4000
warmup decay
为什么需要warmup? 训练初期,模型参数是随机初始化的,梯度方向不可靠。如果一开始就用大学习率,模型可能"跑偏"到难以恢复的参数区域。先用小学习率"热身",让模型找到一个合理的参数区域后再加速。
参数量分析
以原论文的 Transformer Base 配置为例(\(d_{\text{model}}=512, h=8, d_{ff}=2048, N=6\)):
| 组件 | 参数量公式 | 数量 |
|---|---|---|
| Token Embedding | \(V \times d_{\text{model}}\) | \(37000 \times 512 \approx 19M\) |
| 每层Self-Attention | \(4 \times d_{\text{model}}^2\) | \(4 \times 512^2 = 1.05M\) |
| 每层FFN | \(2 \times d_{\text{model}} \times d_{ff}\) | \(2 \times 512 \times 2048 = 2.10M\) |
| 每层LayerNorm × 2 | \(2 \times 2 \times d_{\text{model}}\) | \(2048\) |
| Encoder(6层) | \(6 \times (1.05M + 2.10M)\) | \(\approx 18.9M\) |
| Decoder(6层,含Cross-Attn) | \(6 \times (1.05M + 1.05M + 2.10M)\) | \(\approx 25.2M\) |
| 总计 | \(\approx 63M\) |
思考:现代大模型还是"预测下一个token"吗?
一个有意思的问题:当 ChatGPT 写出 "an" 的时候,它知道下一个词是 "apple" 吗?
短回答
是的,现代大模型(GPT-4、Claude 等)在架构层面依然是自回归的 next-token prediction。 写出 "an" 的时候,模型并不"知道"下一个一定是 "apple"——它只是在词表上算出一个概率分布,"apple" 的概率很高而已。模型没有一个"草稿本"提前把整句话写好再逐字输出。
但"只是预测下一个token"≠"没有规划能力"
虽然输出机制是逐 token 的,但模型的内部表示(隐藏层状态)可能已经隐式编码了对后续多个 token 的"预期"。研究表明,Transformer 中间层的隐状态中可以探测到未来 token 的信息。也就是说:
- 输出层面:一次只产生一个 token
- 内部层面:隐藏状态中可能已经"规划"了接下来好几步要说什么
这就像一个人说话时,嘴巴一次只能说一个字,但大脑里可能已经组织好了整句话。
超越 next-token prediction 的新技术
虽然底层架构没变,但训练方法和推理策略已经有了重大演进:
| 技术 | 原理 | 是否改变了架构 |
|---|---|---|
| Chain-of-Thought (CoT) | 让模型输出中间推理步骤,用"自言自语"辅助思考 | 否,仍是 next-token,但用输出 token 充当"工作记忆" |
| RLHF / RLAIF | 用人类反馈训练模型偏好,优化目标从"预测准确"变为"回答有用" | 否,改变了训练目标,但推理仍是 next-token |
| Reasoning Models(如 OpenAI o1/o3) | 生成大量隐藏的"思考 token",对答案进行搜索和验证 | 否,本质还是 next-token,但推理时分配了大量计算用于"思考"——类似在脑子里打草稿 |
| Multi-Token Prediction | 训练时同时预测未来\(k\) 个 token(Meta, 2024) | 是,修改了训练目标。每一步预测 4-8 个未来 token,使模型学到更长远的规划 |
| Speculative Decoding | 小模型先"猜"多个 token,大模型一次性验证 | 否,但推理速度提升 2-3 倍 |
| Diffusion LM | 非自回归,像图像扩散模型一样同时生成所有 token | 是,完全不同的范式,但目前效果尚不及自回归模型 |
核心洞察
截至 2025 年,主流大模型的架构底座仍然是 Transformer + 自回归。真正改变的是:
1.训练方法:从单纯的语言建模(predict next token)→ RLHF/DPO → 推理强化学习 2. 推理策略:从贪心/采样生成 → Chain-of-Thought → 搜索 + 验证(test-time compute scaling) 3. 规模:从 63M(原论文)→ 数千亿参数
"predict next token" 看似简单,但当模型足够大、数据足够多、训练方法足够好时,这个简单的目标函数能涌现出令人惊讶的能力。
与 CNN、RNN 的全面对比
| 特性 | CNN | RNN | Transformer |
|---|---|---|---|
| 核心操作 | 卷积(局部窗口) | 循环(逐步处理) | 自注意力(全局交互) |
| 归纳偏置 | 局部性 + 平移不变性 | 时间依赖性 + 时间平移不变性 | 几乎无(靠数据学习) |
| 并行性 | 高(不同位置独立卷积) | 低(\(h_t\) 依赖 \(h_{t-1}\)) | 高(所有位置同时计算注意力) |
| 长距离依赖 | 需要多层堆叠,\(O(\log n)\) 或 \(O(n/k)\) | 理论上可以,但梯度消失 | \(O(1)\),任意两个位置直接相连 |
| 计算复杂度(每层) | \(O(k \cdot n \cdot d^2)\) | \(O(n \cdot d^2)\) | \(O(n^2 \cdot d)\) |
| 适用场景 | 图像、局部模式 | 短序列、流式数据 | 长序列、需要全局理解的任务 |
| 参数量与序列长度 | 无关 | 无关 | 无关(但注意力矩阵占显存\(O(n^2)\)) |
计算复杂度的权衡
当 \(n < d\)(序列长度小于特征维度)时,Self-Attention 比 RNN 更快;当 \(n > d\) 时(超长序列),Self-Attention 的 \(O(n^2)\) 成为瓶颈。这催生了各种高效 Attention 变体(Linear Attention、Sparse Attention 等)。
大模型
Scaling Law
Scaling Laws 的发现(最早由 OpenAI 在 2020 年系统提出)指出:模型的最终性能(通常用 Loss 衡量)主要取决于三个要素: 参数量 (\(N\)) 、训练数据量 (\(D\)) 和 计算量 (\(C\)) 。当这三个要素不受限制时,Loss 会随着这三个维度的增加而呈 幂律下降 :
- \(L\) :模型在测试集上的交叉熵损失(可以理解为预测的"准确度")。
- \(N\) :模型有效参数量(雕刻刀的精细度)。
- \(D\) :训练数据集的大小(见识的广度)。
- \(\alpha_N, \alpha_D\) :幂律指数。在 Transformer 架构下,这些系数表现出惊人的稳定性。
- 计算量关系 :通常 \(C \approx 6ND\)(前向传播 + 反向传播的总浮点运算量)。
三大要素的底层原理:
| 维度 | 原理理解 | 对应你的"空间折叠"理论 |
|---|---|---|
| 参数量 (\(N\)) | 空间分辨率 | 参数量越多,意味着在高维空间里用于"对折"的关节越多,能够雕刻出的分割面(Decision Boundary)越精细。 |
| 数据量 (\(D\)) | 流形覆盖度 | 数据量决定了模型对高维知识空间(Manifold)的覆盖率。没有足够的数据,再细的雕刻刀也只能在局部区域反复折叠,导致过拟合。 |
| 计算量 (\(C\)) | 折叠的动力 | 算力是执行梯度下降、寻找最优折叠方案的能源。算力保证了模型能从混乱状态(随机初始化)抵达那个万能逼近预言的"完美参数点"。 |
Scaling Laws带来的三大工程启示:
- 架构不重要,规模才重要。只要是基于 Transformer 的前馈结构,具体的微调(比如头的个数、残差的细节)对性能的影响远小于规模增加带来的收益。
- 算力分配的最佳比例 (Chinchilla Optimality):如果你的算力翻倍,你应该同比例地增加参数量和数据量。而不是只加参数(会导致模型"虚胖",见多识广但没记牢)。
- 性能的可预测性:你可以在模型还没训练出来之前,先在小模型上试一下,然后精准预测出投 1 亿美元训练出来的大模型 Loss 到底是多少。
Scaling Laws 描述的是拟合精度的提升,而非逻辑本质的突变。虽然增加 \(N\) 和 \(D\) 可以无限逼近数据的分布(万能逼近定理),但如果数据的分布本身存在质量上限(比如互联网上充斥着垃圾信息),模型也只能学会'平庸的规律'。此外,Scaling Laws 目前主要是在语言建模(Next Token Prediction)上表现稳定,在复杂的推理链条(Reasoning)和自我纠错上,我们可能还需要在架构上引入类似强化学习(RLHF/Search)的新维度,而不仅仅是堆规模。
自回归
KV-Cache
KV-cache 是专门为了服务于大语言模型的自回归(Auto-regressive)推理过程而诞生的技术,是大语言模型(如 GPT、LLaMA)在推理(Inference)阶段用来加速生成速度、减少重复计算的一种核心技术手段。
简单来说,它是模型为了"不把算过的东西重算一遍"而建立的一个临时存储区。
在 Transformer 的注意力机制中,每个词都会生成三个向量:
- Q (Query) :当前的查询(我是谁?我要找什么?)。
- K (Key) :索引标签(我的特征是什么?)。
- V (Value) :实际内容(我的含义是什么?)。
比如模型要生成句子 "我 爱 学习" :
- 第1步 :输入 "我",模型算出 "爱"。
- 第2步 :模型把 "我" 和 "爱" 放在一起看,算出 "学习"。
- 第3步 :模型把 "我"、"爱"、"学习" 放在一起看,算出下一个词(比如句号)。
如果没有 KV-cache,第3步会发生什么?
- 模型拿到 "我、爱、学习"。
- 模型重新计算 "我" 的 K 和 V。
- 模型重新计算 "爱" 的 K 和 V。
- 模型计算新词 "学习" 的 K、V 和 Q。
- 然后大家一起做注意力运算。
问题: "我" 和 "爱" 的 K 和 V,在第1步和第2步早就又算过一遍了!它们是固定的, 重复计算它们是在浪费时间(GPU 算力) 。
KV-cache 的逻辑是:算过的就存起来(Cache),下次直接用。带 KV-cache 的流程:
- 第1步(生成"爱"):
- 计算 "我" 的 \(K_{1}, V_{1}\)。
- 【存储】 把 \(K_{1}, V_{1}\) 存入显存(这就是 KV-cache)。
- 输出 "爱"。
- 第2步(生成"学习"):
- 输入新词 "爱"。
- 只计算 "爱" 的 \(K_{2}, V_{2}\) 和 \(Q_{2}\)。
- 【读取】 从显存里把 "我" 的 \(K_{1}, V_{1}\) 拿出来。
- 【拼接】 把旧的 \(K, V\) 和新的 \(K, V\) 拼在一起。
- 做注意力运算,输出 "学习"。
- 【存储】 把 \(K_{2}, V_{2}\) 也存入 KV-cache。
- 第3步(生成"。"):
- 输入新词 "学习"。
- 只计算 "学习" 的 \(K_{3}, V_{3}\) 和 \(Q_{3}\)。
- 【读取】 拿之前的 \(K_{1}, V_{1}, K_{2}, V_{2}\)。
- ...以此类推。
模型不需要重新"阅读"原文,只要拿着这些 KV 向量,就拥有了对过去所有对话的"记忆"。因此KV-Cache实际上是一种"工作时记忆"。在具身智能笔记中介绍世界模型的时候我们提到过,KV-Cache的局限性在于线性增长和显存爆炸:1000个词的对话,就要存1000组 KV;10万个词,就要存10万组。