15 KiB
Transformer 架构详解
2017 年,"Attention Is All You Need" 论文用自注意力取代了 RNN/LSTM,开启了现代大语言模型时代。
一、Transformer 之前:RNN 的困境
RNN 的处理方式(串行):
Token₁ → Token₂ → Token₃ → ... → Tokenₙ
5ms 5ms 5ms 5ms = 5×n ms(无法并行)
Transformer 的处理方式(并行):
Token₁ ↘
Token₂ → 所有 Token 同时互相"看到" → 并行计算
Token₃ ↗ ≈ 5ms(与 n 无关)
| 问题 | RNN/LSTM | Transformer |
|---|---|---|
| 并行性 | 串行,无法并行训练 | 完全并行,GPU 友好 |
| 长距离依赖 | 梯度消失/爆炸,远距离信息丢失 | 每个 Token 直接关注任意位置的 Token |
| 训练速度 | 慢(序列越长越慢) | 快得多 |
| 可解释性 | 难以解释隐藏状态 | 可直接可视化注意力权重 |
二、整体架构:编码器-解码器
原始 Transformer 用于机器翻译,包含编码器和解码器两部分。
┌─────────────────────────────────┐
│ 编码器(Encoder) │
│ ┌───────────────────────────┐ │
"我 爱 北京" → │ │ Encoder Block × 6 │ │
│ │ Self-Attn + FFN │ │
│ └───────────────────────────┘ │
└──────────────┬──────────────────┘
│ 编码后的表示(K, V)
▼
┌─────────────────────────────────┐
│ 解码器(Decoder) │
│ ┌───────────────────────────┐ │
│ │ Decoder Block × 6 │ │
│ │ ① Masked Self-Attn │ │ → "I love Beijing"
│ │ ② Cross-Attn (用 K,V) │ │
│ │ ③ FFN │ │
│ └───────────────────────────┘ │
└─────────────────────────────────┘
一个 Block 内部(原始设计)
输入 x
│
├──→ Multi-Head Self-Attention ──→ ⊕ ──→ LayerNorm ──→
│ ↑ ↑
│ 残差连接 x 输出
│
└──→ Feed-Forward Network ──→ ⊕ ──→ LayerNorm ──→ 输出
↑ ↑
残差连接 传给下一层
两种主流归一化位置
| 方式 | 归一化位置 | 代表模型 | 特点 |
|---|---|---|---|
| Post-LN | 残差之后 | 原始 Transformer | 训练不稳定,需要 warmup |
| Pre-LN | 子层之前 | GPT 系列、LLaMA | 训练更稳定,无需大量 warmup |
Post-LN:x + Attn(LayerNorm(x)) ← 原始论文
Pre-LN: x + Attn(LayerNorm(x)) 错 — 实际上是:
Pre-LN:LayerNorm(x + Attn(x)) 错 — Pre-LN 是:
Pre-LN 实际:x + Attn(LayerNorm(x)) — 等等,让我用图表示清楚:
Post-LN: x → SubLayer → Norm → + → 输出
Pre-LN: x → Norm → SubLayer → + → 输出
Pre-LN 先将输入归一化再送入子层,然后做残差。GPT/LLaMA 全系使用 Pre-LN(或 RMSNorm + Pre-LN 变体)。
三、自注意力机制:完整推演
3.1 从"查字典"理解 Q/K/V
| 投影 | 类比 | 数学 | 含义 |
|---|---|---|---|
| Q(Query) | 你的搜索关键词 | X · W_Q |
"我在找什么信息?" |
| K(Key) | 所有条目的索引 | X · W_K |
"每个词身上有什么标签?" |
| V(Value) | 条目的实际内容 | X · W_V |
"每个词的实际信息是什么?" |
3.2 逐步计算示例
假设输入句子 "我 爱 NLP",模型维度 d=4,头维度 d_k=2:
Step 1 — 输入嵌入
我: [0.1, 0.3, 0.5, 0.2]
爱: [0.4, 0.1, 0.3, 0.6]
NLP: [0.2, 0.5, 0.1, 0.3]
Step 2 — 计算 K 和 Q(只演示一个头的投影)
K = X · W_K (3×4)×(4×2) = 3×2 矩阵
K_我 = [0.8, 0.2]
K_爱 = [0.3, 0.9]
K_NLP = [0.5, 0.4]
Q_爱 = [0.6, 0.3] ("爱"这个位置的 Query)
Step 3 — 计算注意力分数(Q_爱 × K^T)
Q_爱 · K_我 = 0.6×0.8 + 0.3×0.2 = 0.48 + 0.06 = 0.54
Q_爱 · K_爱 = 0.6×0.3 + 0.3×0.9 = 0.18 + 0.27 = 0.45
Q_爱 · K_NLP = 0.6×0.5 + 0.3×0.4 = 0.30 + 0.12 = 0.42
scores = [0.54, 0.45, 0.42]
Step 4 — 缩放 + Softmax
scaled = scores / √2 = [0.38, 0.32, 0.30]
weights = softmax(scaled) = [0.35, 0.33, 0.32]
Step 5 — 加权求和 V
V_我 = [0.7, 0.1]
V_爱 = [0.2, 0.8]
V_NLP = [0.4, 0.5]
output_爱 = 0.35×[0.7,0.1] + 0.33×[0.2,0.8] + 0.32×[0.4,0.5]
= [0.245, 0.035] + [0.066, 0.264] + [0.128, 0.160]
= [0.439, 0.459]
位置"爱"的输出融合了"我"(35%)、"NLP"(32%)和自身(33%)的信息——这正是"理解上下文"的本质。
3.3 矩阵形式(实际实现)
\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V
- QK^T:N×N 的注意力矩阵,每对 Token 一个分数
- ÷ √d_k:缩放因子。d_k 越大点积值越大 → softmax 趋向 one-hot → 梯度消失。除以 √d_k 保持方差 ≈ 1
- Softmax:沿最后维(Key 维度)归一化,每行和为 1
- × V:用注意力权重对 Value 加权求和
四、多头注意力:并行多个"视角"
4.1 为什么需要多头?
单头可能只学到一种注意力模式:
"我" → 关注了 "爱"
"爱" → 关注了 "北京"
所有头都只关注相邻词 ← 浪费了注意力的表达能力
多头允许并行学习多种模式:
头 1:语法结构(主语→谓语)
头 2:长距离共指("它"→ 前文的"北京")
头 3:语义关联("医生"→ "医院"→ "病人")
头 4:位置模式(局部上下文窗口)
4.2 数学公式
\text{head}_i = \text{Attention}(Q W_i^Q, K W_i^K, V W_i^V)
\text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, ..., \text{head}_h) W^O
每个头有自己独立的投影矩阵 W_Q、W_K、W_V,维度为 d_model × d_k(其中 d_k = d_model / h)。
4.3 计算量并不是 h 倍!
| 单头 (d_model=512) | 8头 (每头 d_k=64) | |
|---|---|---|
| Q投影参数 | 512×512 = 262K | 8×512×64 = 262K |
| K投影参数 | 同上 | 同上 |
| V投影参数 | 同上 | 同上 |
| 总参数 | ≈ 786K | ≈ 786K + W_O(512×512) ≈ 1M |
计算量大致相同,只是把工作分给多个头并行做,每个头在更小的空间(d_k)中关注不同的模式。
4.4 生产实践中的头数配置
| 模型 | d_model | 头数 | d_k |
|---|---|---|---|
| 原始 Transformer | 512 | 8 | 64 |
| GPT-3 | 12288 | 96 | 128 |
| LLaMA 70B | 8192 | 64 | 128 |
| DeepSeek-V3 | — | — | 使用 MLA 替代标准多头 |
五、位置编码:告诉模型"顺序"
注意力机制本身是置换不变的——打乱输入顺序,注意力分数矩阵只是行列交换,输出值不变。所以必须显式注入位置信息。
方案对比
| 方案 | 原理 | 优点 | 缺点 | 代表模型 |
|---|---|---|---|---|
| Sinusoidal(正弦) | 不同频率的正弦/余弦波叠加 | 可外推、无参 | 固定模式 | 原始 Transformer |
| Learned | 可学习的 pos_embed 表 | 灵活 | 不能超出训练长度 | BERT、ViT |
| RoPE(旋转位置编码) | 用旋转矩阵编码相对位置 | 天然编码相对位置、可外推 | 长序列扩展需技巧 | LLaMA、Qwen、DeepSeek |
| ALiBi | 给注意力分数加线性偏置 | 简单、外推最强 | 表达能力有限 | BLOOM |
RoPE 核心思想
不是往 embedding 里加位置向量,而是把 Q 和 K "旋转"一个角度,角度大小取决于位置。
位置 m 的 Q:旋转 m×θ
位置 n 的 K:旋转 n×θ
Q·K = (rotated_Q_m)·(rotated_K_n) = 关于 (m-n) 的函数
↑
只依赖相对位置!
RoPE 扩展:
- NTK-aware scaling:调高基频(如 10000→500000),让旋转更慢,支持更长上下文
- YaRN:NTK + 额外微调,将 Llama 从 4K 扩展到 128K
六、三种架构变体:选哪种?
6.1 编码器-only(如 BERT)
输入:"我 [MASK] 北京" → 编码器 → 每个位置的上下文表示 → 预测 [MASK] = "爱"
特点:
- 双向注意力(能看到整个句子)
- 适合:分类、实体识别、语义搜索
- 不能做生成(没有解码器)
6.2 解码器-only(如 GPT、LLaMA、DeepSeek V4)
输入:"今天天气" → 因果注意力(只看左边)→ 预测下一个 Token "真"
"今天天气真" → ... → 预测 "好"
特点:
- 因果掩码(Causal Mask):每个 Token 只能看自己和左边
- 适合:对话、代码生成、创意写作
- 自回归生成,输入=输出(训练时位移一位即可)
- 现代 LLM 几乎全部使用此方案
因果掩码示意:
注意力分数矩阵(× = 被 mask 为 -∞):
我 爱 北京 天安门
我 ✓ × × × ← "我"只能看到自己
爱 ✓ ✓ × × ← "爱"能看到"我"和自己
北京 ✓ ✓ ✓ × ← ...
天安门✓ ✓ ✓ ✓
6.3 编码器-解码器(如 T5)
编码器(双向):处理源语言 → 生成 K、V
解码器(因果 + 交叉注意力):用 K、V 辅助生成目标语言
交叉注意力(Cross-Attention):
解码器的 Q ← 来自解码器自身("这句话接下来该说什么?")
解码器的 K,V ← 来自编码器("原文说了什么?")
| 架构 | 自注意力方式 | 能否生成 | 代表 | 适合任务 |
|---|---|---|---|---|
| 编码器-only | 双向 | ❌ | BERT | 理解/分类 |
| 解码器-only | 因果(单向) | ✅ | GPT, LLaMA | 生成/对话 |
| 编码器-解码器 | 双向 + 交叉 | ✅ | T5, BART | 翻译/摘要 |
七、训练与推理
7.1 训练:Teacher Forcing
输入: "今天 天气 真" 目标: "天气 真 好"
↑
模型被训练来预测每个位置的下一个 Token
实际一次性计算:
输入 [今天, 天气, 真]
→ 因果掩码确保"今天"看不到后面
→ 输出 [P(天气), P(真), P(好)]
→ Loss = CrossEntropy(预测, 实际目标)
训练时一次前向算出所有位置的预测,因为目标已知、且有因果掩码保护。
7.2 推理:自回归生成
训练时可以并行(目标已知),推理必须串行(目标未知):
Step 1: "今天" → 预测 "天气"
Step 2: "今天天气" → 预测 "真"
Step 3: "今天天气真" → 预测 "好"
Step 4: "今天天气真好" → 预测 <eos>
每次都要重新计算全序列?→ 不,用 KV Cache!
7.3 KV Cache:推理加速的关键
未使用 KV Cache:
Step 1:计算 "今天" 的 Q、K、V ──────────── 1个Token
Step 2:计算 "今天天气" 的 Q、K、V ──────── 2个Token ← 重新算了"今天"!
Step 3:计算 "今天天气真" 的 Q、K、V ────── 3个Token ← 又重算了!
...
使用 KV Cache:
Step 1:计算 "今天" → 缓存 K₁、V₁
Step 2:只算 "天气" 的 Q、K、V + 用缓存的 K₁、V₁ → 缓存 K₁K₂、V₁V₂
Step 3:只算 "真" 的 Q、K、V + 用缓存的 K₁K₂、V₁V₂ → ...
...
每个 Step 只算 1 个新 Token!开销从 O(n²) 降到 O(n)(单步)。
KV Cache 的代价:显存占用随序列长度线性增长。MLA(多头潜在注意力)通过低秩压缩解决此问题——详见 MoE 混合专家架构#MLA(Multi-head Latent Attention,多头潜在注意力)。
八、高效注意力变体
标准自注意力是 O(n²) 复杂度(n = 序列长度)。对长序列需优化:
8.1 FlashAttention(事实标准)
IO 感知的精确注意力算法——数学结果完全等价于标准注意力,但通过算子融合大幅减少显存读写。
标准计算: FlashAttention:
QK^T → 写回 HBM 将 Q、K、V 分块(tile)
Softmax → 写回 HBM 每个块在 SRAM 中完成全部计算
乘 V → 写回 HBM 只有最终结果写回 HBM
↑ 3次 HBM 读写 ↑ 约 1/n 的 HBM 读写
实测效果:相比朴素实现,吞吐量提升 ~32%(A100),现为 PyTorch 2.0+ 默认后端。
8.2 稀疏/线性注意力(长序列场景)
| 方案 | 策略 | 复杂度 | 代表 |
|---|---|---|---|
| Longformer | 局部滑动窗口 + 全局 Token | O(n) | 文档级 NLP |
| BigBird | 局部 + 全局 + 随机 | O(n) | — |
| Performer | 随机特征近似 softmax | O(n) | — |
| Mamba/SSM | 状态空间模型替代注意力 | O(n) | 超长序列 |
经验法则:序列 < 4K Token → 标准注意力 + FlashAttention 即可。长文档/代码库场景才需稀疏注意力或 SSM。
九、现代生产优化总览
| 优化手段 | 效果 | 代价 |
|---|---|---|
| 混合精度(bf16/fp16) | 训练/推理速度翻倍 | 质量损失可忽略 |
| 梯度检查点 | 显存降低 50%+ | 训练慢 20% |
| FlashAttention | 推理加速 ~30%,显存降低 | 无(数学等价) |
| 算子融合(RMSNorm, xFormers) | 减少 kernel launch 开销 | 无 |
| 序列分桶 | 减少 padding 浪费 | 实现复杂度增加 |
| 量化(INT8/INT4) | 显存降低 4-8× | 轻微质量下降 |
十、总结
Transformer 的核心思想就三条:
1. 自注意力(Self-Attention)
Q·K 算关联 → Softmax 归一化 → ×V 合成上下文
每个 Token 直接"看到"所有 Token
2. 多头(Multi-Head)
并行跑多个注意力,每个头学不同的关系模式
总计算量不变,表达能力翻倍
3. 位置编码(Positional Encoding)
注意力不感知顺序 → 注入位置信息
RoPE 是当前主流:Q/K 旋转角度 = 位置函数
Transformer 不只是一篇论文——
它是整个现代大语言模型时代的起点。
相关链接
- 大模型架构基础 — 大模型的各组件总览
- FFN 前馈网络详解 — Transformer 中参数量最大的组件
- MoE 混合专家架构 — 如何用稀疏专家替代 FFN
- 多模态大模型 — Transformer 如何扩展到图像/音频
- DeepSeek V4 系列 — Transformer + MoE + MLA 的实际应用
最后更新:2026-08-05