一篇文章搞懂什么是Transformer

ChatGPT、Claude、通义千问、DeepSeek——底层骨架几乎都叫 Transformer。名字像变形金刚,机制却朴素:用注意力让任意两个位置直接对话,再堆深、训壮。

一句话:Transformer = 自注意力(谁该看谁)+ 前馈网络(各自变换)+ 残差与归一化(堆得住)。GPT 一脉再加因果掩码:生成时只能看左边。

  • 入门:第 1–3、5、9 节
  • 读论文 / 源码:第 4–8、10、12、14 节
  • 推理 / 平台:第 11、13、15 节

目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.  它解决了什么问题
2. 鸟瞰:整体结构
3. 输入:分词、Embedding、位置
4. 核心:自注意力
5. 动手算一遍
6. 多头注意力
7. 掩码:谁能看见谁
8. 一层 Block
9. 完整数据流:从提示到下一个词
10. 三条岔路
11. 从论文到 ChatGPT
12. 对照配置文件
13. 工程:复杂度、KV Cache、FlashAttention
14. 最小可读代码
15. 一次对话如何映射回结构
16. 常见误解与问题
17. 收束

1. 它解决了什么问题

1.1 RNN 的两记硬伤

Transformer 之前,序列主力是 RNN / LSTM / GRU:

1
词1 → h₁ → 词2 → h₂ → … → 词 n → hₙ
硬伤 后果
难并行 第 100 步必须等第 99 步,GPU 吃不饱
长程靠状态链 第 1 个词影响第 100 个,要穿过 99 次隐状态;信息淡、梯度弱

The animal didn’t cross the street because it was too tired.

it 该绑 animal。RNN 只能把信息塞进隐状态再一跳一跳传过去。LSTM 缓解遗忘,但路径仍是长链,训练仍难并行。

1.2 Attention 不是 2017 才有的

  1. Word2Vec(2013):词变向量,但静态、无上下文
  2. Seq2Seq(2014):整句压成一个向量再解码;一长就挤爆
  3. Bahdanau Attention(2014/15):解码时按相关性回看源句——仍是 RNN + Attention

Google 再问:若 Attention 已能建模依赖,还要 RNN 干什么?《Attention Is All You Need》——注意力就够了。

1.3 换了什么假设

把「时间上的接力」换成「空间上的开会」:

  • 每个位置同时看见(被允许看见的)所有位置
  • 训练时整句可并行算矩阵
  • 任意两词一步相连——用 (O(n^2)) 换短路径与可规模化

RNN 像传话游戏;Transformer 像圆桌会议,声调高低就是注意力权重。

结构允许大力出奇迹,所以撑得起后来的千亿参数训练。论文没证明「注意力=意识」,也没说平方复杂度不是问题;它证明的是更窄的一件事:翻译等任务上,去掉循环也能训得又快又好。

2. 鸟瞰:整体结构

原版为机器翻译而设计:左右两塔,各堆 (N) 层(论文 (N=6))。数据自下而上;每一层里都是 先 Attention,再 FFN。

侧 干什么 一层里有什么
Encoder 读懂源句 Self-Attn(双向)→ FFN
Decoder 写出目标句 Masked Self-Attn(因果)→ Cross-Attn(查编码器)→ FFN

外加残差与 Norm。顶上 Linear + Softmax 得到词表分布。

模块 Q K/V 权限
Encoder Self-Attn 源句 源句 双向
Decoder Self-Attn 已生成前缀 已生成前缀 因果
Cross-Attn 解码器当前层 编码器输出 解码看编码
1
2
3
输入:Token → Embedding → 位置
骨干:N × (Attention + FFN + 残差/Norm)
输出:LM Head → 下一词分布

今天聊天模型多半只留 Decoder:系统提示、检索、工具结果直接拼进前缀,交叉注意力塔被省掉——不是它不重要,而是条件改成了自注意力可见的上下文。

3. 输入:分词、Embedding、位置

1
文本 → Tokenizer → Embedding 查表 → 注入位置 → 第一层

Token:英文常见 BPE / WordPiece(playing → play + ##ing);中文常见字词混合或纯 BPE。词表固定,未知词靠子词拼。模型看见的是 id,不是「词」的日常含义。

Embedding:表大小 (V\times d_{\text{model}})。查表得到无上下文起点。bank 在「河岸 / 银行」里是同一行,要靠后面的 Attention 才分化。

1
2
ids:       (batch, seq_len)
embedding: (batch, seq_len, d_model)

位置:纯 Attention 对词序置换近似不敏感。

  • 正弦/余弦(论文):sin/cos 加到 embedding 上
  • RoPE(现代主流):在 Q·K 时把相对位置编进旋转角;LLaMA、Qwen 等基本走这条

Embedding:「我是什么」。位置:「我在哪」。

「128k 上下文」通常是位置方案 + 长序列训练/微调 + 推理优化。RoPE 常再配插值 / YaRN 做外推。

4. 核心:自注意力

4.1 Q、K、V

每个位置 (x_i) 经三个线性投影:

人话 搜索类比
(Q) 我在找什么 搜索词
(K) 我能被怎样检索 标题 / 标签
(V) 我贡献什么 正文

同一序列、三套投影,所以叫 Self。

[
\operatorname{Attention}(Q,K,V)=\operatorname{softmax}!\left(\frac{QK^{\top}}{\sqrt{d_k}}\right)V
]

1
打分 QKᵀ → 缩放 /√d_k → softmax 成权重 → 加权混合 V

(\sqrt{d_k}) 防止高维点积过大、softmax 塌成 one-hot。输出是 Value 的凸组合。

4.2 热力图与动画

玩具句:The cat sat on mat。第 3 行 sat 对 cat 很亮——像动词找主语。颜色是配方,不是真理;别把每一格讲成人类故事。

  • Self-Attention:Q、K、V 同一序列
  • Cross-Attention:Q 来自解码器,K/V 来自编码器

换一个形容词,路由就会改:

… it was too tired → 更常绑 animal
… it was too wide → 更常绑 street

不是 if-else,是统计上有利于猜对后面的词。

长度 (n)、维度 (d):形成 QKV 约 (O(nd^2)),(QK^{\top}) 与乘 V 约 (O(n^2d))。(n) 大时 (n^2) 会压过 (d^2)。FlashAttention 不改渐进复杂度量级,但大幅降显存流量。

5. 动手算一遍

1
2
3
4
位置:   我      爱      这      模型
Q: (1,0) (1,1) (0,1) (1,0)
K: (1,0) (1,1) (0,1) (0,1)
V: (1,0) (0,1) (1,1) (0,0)

「爱」的 Query=((1,1)),点积得分(忽略缩放):[1, 2, 1, 1]。
(\operatorname{softmax}\approx[0.17,\ 0.48,\ 0.17,\ 0.17]),
输出 (\approx(0.34,\ 0.65))——混进了邻居,自己仍占大头。

因果掩码下,「爱」不能看「这 / 模型」,后两格变 (-\infty),权重归零。否则训练等于偷看未来。

权重若永远均匀,Attention 退化成全句平均;可学之处在于权重依赖内容。

6. 多头注意力

多组投影各自算 Attention,拼接后再经 (W^O) 映回。

[
\operatorname{MultiHead}=\operatorname{Concat}(\mathrm{head}_1,\ldots,\mathrm{head}_h)W^O
]

「有的头偏语法」只是方便记忆。真实训练里分工常很乱,还有闲置头。工程价值是表达力 + 并行。

GQA / MQA:Query 仍多头,KV 头更少,减小 KV Cache。配置项 num_key_value_heads。

7. 掩码:谁能看见谁

掩码 谁能看见谁 典型
双向 全句 BERT、多数 Embedding
因果 自己及左边 GPT、LLaMA、Qwen
Padding 忽略填充位 训练 batch 对齐
1
2
3
scores = Q @ K.T / sqrt(d_k)
scores = scores.masked_fill(禁看, -inf)
weights = softmax(scores)

Padding 与因果常叠加。整行被掩光时 softmax 会 NaN,需保护。

「只能看左边」仍能写长文:续写策略在权重里,未来在一步步采样里展开——边写边定。高质量长输出往往还要外层提纲、检索或多轮改写。

8. 一层 Block

现代 Decoder-only 多为 Pre-Norm:

1
2
3
4
5
6
7
x
├─ Norm → Masked Attn ─┐
│ ⊕ →
└──────────────────────┘
├─ Norm → FFN ─────────┐
│ ⊕ → 输出
└──────────────────────┘

残差实际是:

[
x + \operatorname{Sublayer}(\operatorname{Norm}(x))
]

(论文原版偏 Post-Norm:(x+\mathrm{Sublayer}) 后再 Norm。)

RMSNorm / LayerNorm 稳住尺度。FFN 中间宽常约 (4\times d_{\text{model}}),激活从 ReLU → GELU → SwiGLU。

Attention = 路由;FFN = 变换。参数量很大一块在 FFN。
非线性细节:激活函数的前世今生。

一层 = 开会 + 写纪要。堆 N 层是反复打磨:浅层更局部,深层更抽象——经验观察,不是定理。

9. 完整数据流:从提示到下一个词

前缀 今天天气,Decoder-only 生成下一个词:

  1. 分词 → ids
  2. Embedding + 位置 → (X)
  3. (N) 层 Masked Attn + FFN
  4. 最后位置 × LM Head(有的与 embedding 绑权,有的不绑)
  5. logits → 采样 / 贪心
  6. 追加 token,回到 2

训练用教师强制:整句右移当标签,一次并行算所有位置损失。目标是:

[
\mathcal{L}=-\sum_{t}\log P(x_t\mid x_{<t})
]

推理阶段 做什么
Prefill 整段提示一次前向,写出 KV
Decode 逐 token 生成,读 KV Cache

首字慢或长提示 TTFT 高,多半是 Prefill。Temperature / top-k / top-p 只改采样性格。

能力来自「为猜对词被迫学到的结构」;幻觉来自「似然 ≠ 求真」;对齐试图把续写拧成助手。

10. 三条岔路

类型 注意力 擅长 代表
Encoder-only 双向 理解、检索向量 BERT、多数 Embedding
Decoder-only 因果 生成、对话、代码 GPT、LLaMA、Qwen、DeepSeek
Enc–Dec 双向编码 + 因果解码 + 交叉注意 翻译、摘要 原版、T5、BART

聊天一边倒 Decoder-only:万物可改写成「前缀→续写」,规模化与部署经验最多。Enc–Dec 没死。检索要整段摘要,双向仍然更合适。

11. 从论文到 ChatGPT

节点 叠加
2017 可并行、可堆叠的翻译骨架
2018 GPT-1 / BERT;预训练成默认范式
2020 GPT-3 ≈175B;提示即接口;Scaling Law
2022+ SFT + RLHF/DPO;对齐成产品
开源浪潮 LLaMA / Qwen / DeepSeek… 配方收敛
1
Decoder-only + Pre-Norm/RMSNorm + RoPE + SwiGLU + GQA

差异多在数据、训练日程、分词器与推理优化。「涌现」有争议;工程事实:大力有效,Transformer 让大力变得可行。

12. 对照配置文件

配置项 含义 见
hidden_size / d_model 宽度 第 3 节
num_hidden_layers 层数 第 8 节
num_attention_heads 多头 第 6 节
num_key_value_heads KV 头(GQA) 第 6、13 节
intermediate_size FFN 中间宽 第 8 节
vocab_size 词表 第 3 节
max_position_embeddings 位置上限 第 3 节
rope_theta / rope_scaling RoPE 第 3 节
tie_word_embeddings 是否绑 LM Head 第 9 节
1
2
3
4
5
layers.i.self_attn.{q,k,v,o}_proj   → 注意力线性层
layers.i.mlp.{gate,up,down}_proj → SwiGLU:门 / 内容 / 压回
layers.i.input_layernorm → Attn 前 Norm
layers.i.post_attention_layernorm → FFN 前 Norm
embed_tokens / lm_head → 进出词表

13. 工程:复杂度、KV Cache、FlashAttention

两两打分 → (n\times n)。常见对策:FlashAttention(少物化大矩阵)、GQA/MQA(少存 KV)、滑动窗口/稀疏、YaRN 等外推、以及 SSM 等另一类混合。

KV Cache:历史 K、V 不变,新 token 只算自己的 Q。用显存换时间;长对话显存上涨主因常在这里。Continuous batching、PagedAttention 都是在管 KV。

训练 推理
并行 序列内可并行 自回归;靠 batch 挤吞吐
显存大户 激活、优化器、梯度 权重 + KV Cache
1
2
权重 ∝ 参数量 × 字节/参数
KV ∝ batch × 层 × 序列长 × KV头 × 头维 × 2 × 字节

权重装不下 → 量化 / 张量并行;KV 撑爆 → 分页缓存 / 短窗口 / GQA。别听成「换个注意力变体就解决一切」。

14. 最小可读代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np

def softmax(x, axis=-1):
x = x - x.max(axis=axis, keepdims=True)
e = np.exp(x)
return e / e.sum(axis=axis, keepdims=True)

rng = np.random.default_rng(0)
X = rng.normal(size=(4, 8))
Wq = rng.normal(size=(8, 8)) * 0.1
Wk = rng.normal(size=(8, 8)) * 0.1
Wv = rng.normal(size=(8, 8)) * 0.1

Q, K, V = X @ Wq, X @ Wk, X @ Wv
scale = 1.0 / np.sqrt(Q.shape[-1])
attn = softmax(Q @ K.T * scale)
out = attn @ V

因果掩码:

1
2
mask = np.triu(np.ones((4, 4)), k=1).astype(bool)
attn = softmax(np.where(mask, -1e9, Q @ K.T * scale))
张量 形状((d=H d_h))
(X) (B\times S\times d)
Q/K/V (B\times H\times S\times d_h)
分数 (B\times H\times S\times S)
输出 (B\times S\times d)

有 KV Cache 时:历史走缓存,当前步常 (S=1)(prefill 除外)。

15. 一次对话如何映射回结构

  1. 系统提示 + 历史 + 用户句 → 前缀
  2. Tokenizer → ids
  3. Prefill 写 KV
  4. Decode 自回归吐词
  5. 结束符 / 长度上限停下
  6. 可选:工具调用 → 结果写回前缀 → 再跑

Agent / RAG / 多轮没有换掉 Transformer;它们改桌上的材料。提示词工程有用,是因为推理时改不了权重,但改得了 Attention 能看见什么。检索条文拼进提示、工具 JSON 追加回去——都是同一座 Decoder,前缀变长而已。

16. 常见误解与问题

  • 注意力权重 ≠ 可解释的「想法」
  • 下一词预测 ≠ 理解世界;幻觉与似然目标同源
  • 参数大 ≠ 一定更强
  • BERT 做生成过时,做检索/编码仍香
  • 没有 RNN ≠ 没有顺序(位置编码 + 因果掩码)
  • 多头未必美丽分工
  • (O(n^2)) 是真约束,但硬件 + 算法 + 产品窗口把可用区推得很远

Transformer vs GPT:架构族 vs 其中一条 Decoder-only + 语言建模 + 对齐的产品线。
还是 2017 那张图吗:积木语法还在;塔形、位置、Norm、激活、对齐与系统层已迭代多轮。
下一步:nanoGPT 级实现,或打开一份 config.json 对照第 12 节。
MoE / Mamba:前者多半是 Transformer + 专家 FFN;后者是另一类序列混合。先懂 QKV。

17. 收束

  1. 开会代替传话 → 可并行、短路径
  2. QKV → 找什么、怎么被找到、贡献什么
  3. 掩码 → 能否看未来
  4. Attention 路由,FFN 变换;残差与 Norm 让深度成立
  5. 大模型 = 同骨架 × 规模 × 数据 × 对齐
1
2
3
文本 → Token / 向量 / 位置
→ 反复:Attention + FFN
→ 末位置 → 词表概率 → 下一词 → 追加

延伸阅读