2017 年 Google 那篇《Attention Is All You Need》之后,Transformer 几乎重写了整个 NLP 行业。ChatGPT、Claude、GPT-4、Gemini、Llama、Qwen 全是 Transformer 家族。但很多人对它的理解停在"自注意力机制"五个字,问起 QKV 怎么算、为什么要多头、位置编码为什么需要,就支支吾吾。这篇文章不堆数学公式,而是把 Transformer 拆成你能在白板上画出来的几个模块,所有关键步骤都配代码。
问题的起点:RNN 为什么不够
Transformer 出现之前,序列建模主要靠 RNN / LSTM。它的核心思想是"把序列一个个字喂进去,内部维护一个隐状态记着前文":
# RNN 伪代码
h = init_hidden()
for token in sequence:
h = update(h, token) # 每一步依赖上一步
问题:
- 必须串行:第 N 步要等第 N-1 步算完,GPU 并行能力发挥不出来。
- 长距离依赖弱:信息在隐状态里逐步传递,几百步外的内容容易丢。
- 梯度消失/爆炸:虽然 LSTM/GRU 缓解了一些,但根本机制还是依赖逐步传递。
Transformer 的答案极其大胆:不要逐步传递,让每个位置直接"看到"所有其他位置。这就是自注意力(Self-Attention)。
自注意力:Q、K、V 是什么
自注意力的核心问题是:"对当前这个词,序列里其他每个词有多重要?" 用三个向量表达:
- Query(Q):当前词的"提问向量" —— 我想找什么。
- Key(K):每个词的"标签向量" —— 我能提供什么。
- Value(V):每个词的"内容向量" —— 我具体的信息。
把每个 Token 的 embedding 各乘三个学习到的矩阵 W_Q、W_K、W_V,就得到这三个向量。然后做注意力计算:
# 简化版自注意力(PyTorch 风格)
import torch
import torch.nn.functional as F
def self_attention(X, W_Q, W_K, W_V):
# X: [seq_len, d_model] 输入序列的 embedding
Q = X @ W_Q # [seq_len, d_k]
K = X @ W_K # [seq_len, d_k]
V = X @ W_V # [seq_len, d_v]
# 计算每个 token 和其他 token 的相似度(QK^T)
scores = Q @ K.T # [seq_len, seq_len]
# 缩放:防止 d_k 大时点积过大导致 softmax 饱和
d_k = K.shape[-1]
scores = scores / (d_k ** 0.5)
# softmax 归一化成"注意力权重"
attn = F.softmax(scores, dim=-1) # [seq_len, seq_len]
# 加权求和 V
output = attn @ V # [seq_len, d_v]
return output, attn
这就是公式 Attention(Q, K, V) = softmax(QK^T / √d_k) V 的代码版。一句话理解:每个 token 用自己的 Q 去和所有 token 的 K 算相似度,得到权重,然后把所有 token 的 V 按权重加起来作为新表示。
多头注意力:几个视角同时看
一个注意力头只能学一种"看待序列的方式"。Transformer 用多头 —— 让模型从多个子空间并行学习不同的关系:
# d_model 是总维度,h 是头数,d_k = d_model / h
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, h):
super().__init__()
self.d_model = d_model
self.h = h
self.d_k = d_model // h
# 用一个大矩阵代替 h 个小矩阵
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)
def forward(self, X):
B, T, _ = X.shape
Q = self.W_Q(X).view(B, T, self.h, self.d_k).transpose(1, 2)
K = self.W_K(X).view(B, T, self.h, self.d_k).transpose(1, 2)
V = self.W_V(X).view(B, T, self.h, self.d_k).transpose(1, 2)
# 现在形状:[B, h, T, d_k]
scores = Q @ K.transpose(-2, -1) / (self.d_k ** 0.5)
attn = scores.softmax(dim=-1)
out = attn @ V # [B, h, T, d_k]
# 拼回 d_model 并最后线性变换
out = out.transpose(1, 2).contiguous().view(B, T, self.d_model)
return self.W_O(out)
不同的头会学到不同的关注模式:有的头学"代词指代",有的头学"主谓关系",有的头学"长距离依赖"。论文里有可视化图,每个头的注意力分布形态完全不同。
位置编码:让模型知道顺序
自注意力本身没有顺序概念 —— 你把 "我吃苹果" 和 "苹果吃我" 输入,自注意力的输出在数学上是一样的(只是注意力权重重排)。但这两句话意思完全不同!
解决方法:在 token embedding 上加一个"位置编码",告诉模型每个 token 在序列里的位置。原始 Transformer 用正弦余弦:
def positional_encoding(seq_len, d_model):
pe = torch.zeros(seq_len, d_model)
pos = torch.arange(seq_len).unsqueeze(1).float() # [seq_len, 1]
div = torch.exp(torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(pos * div)
pe[:, 1::2] = torch.cos(pos * div)
return pe
# 使用
X = token_embedding(tokens) + positional_encoding(seq_len, d_model)
用正余弦的好处:相对位置可以用线性变换表达(PE(pos+k) 是 PE(pos) 的线性函数)。后来 GPT 用了可学习的位置编码,LLaMA 用了 RoPE(旋转位置编码),Llama 3 / Qwen2 还在用 RoPE 的扩展版本来支持长上下文。
前馈网络与残差连接
注意力层后面通常接一个简单的两层前馈网络(FFN),给每个位置独立做非线性变换:
class FFN(nn.Module):
def __init__(self, d_model, d_ff):
super().__init__()
self.linear1 = nn.Linear(d_model, d_ff) # 通常 d_ff = 4 * d_model
self.linear2 = nn.Linear(d_ff, d_model)
self.activation = nn.GELU() # 或 ReLU / SwiGLU
def forward(self, x):
return self.linear2(self.activation(self.linear1(x)))
整个 Transformer block 还有残差连接 + LayerNorm:
class TransformerBlock(nn.Module):
def __init__(self, d_model, h, d_ff):
super().__init__()
self.attn = MultiHeadAttention(d_model, h)
self.ffn = FFN(d_model, d_ff)
self.ln1 = nn.LayerNorm(d_model)
self.ln2 = nn.LayerNorm(d_model)
def forward(self, x):
x = x + self.attn(self.ln1(x)) # 残差 + 注意力
x = x + self.ffn(self.ln2(x)) # 残差 + FFN
return x
残差连接让梯度能直接流过深层网络,是训练 100+ 层模型的关键。LayerNorm 稳定训练。这两个细节没它们,深度 Transformer 训练不起来。
Encoder vs Decoder vs Decoder-Only
原始 Transformer 是 Encoder-Decoder 架构(用于翻译)。但现代 LLM 大多是 Decoder-Only:
- Encoder-Only(BERT):双向注意力,看全句上下文,擅长理解任务(分类、NER、问答)。
- Decoder-Only(GPT / Claude / Llama):单向注意力 + 因果 mask(只能看前面),擅长生成任务。
- Encoder-Decoder(T5 / BART):同时做理解和生成,翻译、摘要常用。
Decoder-Only 的关键是因果 mask:
# 上三角设为 -inf,softmax 后变成 0,等于看不到未来位置
mask = torch.triu(torch.ones(T, T), diagonal=1).bool()
scores = scores.masked_fill(mask, float('-inf'))
attn = scores.softmax(dim=-1)
有了这个 mask,token i 只能看到 token 0 到 i-1,正好符合"自回归生成"的假设:基于前文预测下一个词。
训练:从 next-token prediction 到 SFT 再到 RLHF
LLM 训练通常三阶段:
1. 预训练(Pretraining)
在海量文本(几 TB)上做"预测下一个词":
# 数据:[T1, T2, T3, T4, T5]
# 任务:已知 [T1..Ti],预测 T_{i+1}
# 损失:交叉熵(预测分布 vs 真实 token 的 one-hot)
logits = model(tokens[:-1]) # [B, T-1, vocab_size]
loss = F.cross_entropy(logits.view(-1, vocab_size), tokens[1:].view(-1))
2. 监督微调(SFT)
在"指令 → 回答"的格式化数据上继续训练,让模型学会按人类期望回答:
# 数据格式
{
"instruction": "总结这段文字的要点",
"input": "...",
"output": "..."
}
3. RLHF(强化学习人类反馈)
训练一个"奖励模型"(给回答打分),然后用 PPO 等算法让 LLM 生成"高分回答"。ChatGPT、Claude 的"有帮助、无害"主要靠这一步实现。DPO(Direct Preference Optimization)是 2024 年起的简化替代,直接用偏好数据优化,不需要单独训奖励模型。
推理:KV Cache 与生成策略
训练时一次性看全序列,推理时一个 token 一个 token 地生成。如果每次都重算所有历史 token 的 K、V,极其浪费。KV Cache 缓存历史 K/V,只算新 token 的:
class GenerationCache:
def __init__(self):
self.k_cache = [] # 每层一个张量
self.v_cache = []
# 第 t 步生成,只需要计算 token t 的 Q、K、V,
# 把 K、V 追加到 cache,然后用 Q 和整个 cache 算注意力
这把推理从 O(T²) 优化到 O(T)。但 KV Cache 占显存(每层每个头 [T, d_k]),长上下文模型显存爆炸 —— 这是为什么"支持 128k / 1M 上下文"这件事比看起来贵得多。优化方向:Group-Query Attention(多个 Q 头共享 K/V)、Sliding Window Attention、KV 量化。
生成策略:
# Greedy:每次取概率最大的 token,无聊但确定
next_token = logits.argmax(-1)
# Sampling:按概率分布采样,有创意但可能跑偏
probs = F.softmax(logits / temperature, dim=-1)
next_token = torch.multinomial(probs, 1)
# Top-k:只在概率最高的 k 个里采样
top_logits, top_idx = logits.topk(k)
probs = F.softmax(top_logits, dim=-1)
# Top-p (nucleus):累积概率达到 p 的最小集合
sorted_logits, sorted_idx = logits.sort(descending=True)
cumulative = sorted_logits.softmax(dim=-1).cumsum(dim=-1)
mask = cumulative > p
# ...
实际项目里 top-p (0.9 左右) + temperature (0.7-1.0) 最常用,平衡创造性和准确性。
缩放定律:为什么模型越大越好
2020 年 OpenAI 的 scaling laws 论文揭示:模型表现随参数量、数据量、算力呈幂律提升。这是过去几年大家拼命堆参数(7B → 70B → 405B)的理论基础。但 2022 年 DeepMind 的 Chinchilla 论文修正:参数和数据要同比例增加,大多数模型是"数据欠拟合"的。
这就是为什么现在 Llama 3 405B 用 15T tokens 训,Qwen2 72B 用 7T tokens —— 远超早期的"数据量 ≈ 参数量"配比。
常见误解
误解 1:Transformer 理解语言。 它学的是"统计上下一个 token 是什么的分布",并不"理解"。但当训练数据足够多,这种统计模式能涌现出令人惊讶的能力(推理、代码、翻译)—— 这是"涌现能力"现象。
误解 2:更大就一定更好。 7B 模型在某些专精任务上(代码补全、特定领域)可以超过 70B 通用模型。架构、数据质量、微调方式都重要。
误解 3:Transformer 是终极架构。 Mamba、RWKV、Hyena 等线性复杂度架构在长序列上有潜力。Transformer 的 O(T²) 自注意力是它的根本限制。
Mixture of Experts(MoE):稀疏激活的现代选择
2024 年起,Mixtral / DeepSeek / Qwen2-MoE / Llama 4 等模型采用 MoE 架构 —— 把 FFN 拆成多个"专家",每次只激活其中 2-4 个。总参数量很大(几百 B),但实际计算量只用一小部分,推理速度接近一个小模型。
# MoE 简化版
class MoELayer(nn.Module):
def __init__(self, d_model, n_experts, top_k=2):
super().__init__()
self.experts = nn.ModuleList([FFN(d_model, 4*d_model) for _ in range(n_experts)])
self.router = nn.Linear(d_model, n_experts)
self.top_k = top_k
def forward(self, x):
# 每个 token 选 top_k 个专家
logits = self.router(x) # [B, T, n_experts]
scores, indices = logits.topk(self.top_k, dim=-1)
weights = scores.softmax(dim=-1)
out = torch.zeros_like(x)
for k in range(self.top_k):
expert_idx = indices[..., k]
for i, exp in enumerate(self.experts):
mask = (expert_idx == i)
if mask.any():
out[mask] += weights[..., k][mask].unsqueeze(-1) * exp(x[mask])
return out
MoE 的核心权衡:训练复杂(负载均衡 + 通信开销),推理快(每 token 只激活一小部分参数)。也是 GPT-4 / Claude 3 Opus / Gemini 1.5 Ultra 的核心架构猜测。
长上下文的工程挑战
Transformer 标准自注意力是 O(T²),T 是序列长度。1k token 没事,1M token 直接爆。一系列工程优化让长上下文成为可能:
- FlashAttention(2022):重新组织注意力计算,显存从 O(T²) 降到 O(T),速度也提升 2-4 倍。现在所有主流推理引擎(vLLM、TGI)默认开。
- Sliding Window Attention:每个 token 只看附近 K 个,长度无限但全局信息丢一些。Mistral / Longformer 用。
- Sparse Attention:只在重要位置算注意力。GPT-3 早期就用过。
- 位置编码外推:RoPE 的 NTK 缩放、YaRN 等技巧,让训练时 4k 上下文的模型推理时跑 32k+。
支持 1M 上下文的 Claude 3 / Gemini 1.5 / GPT-4 turbo,实测仍存在"lost in the middle"现象:中间位置的信息比开头结尾更容易被忽略。Prompt 设计时把关键信息放头尾,准确率明显提升。
写在最后
Transformer 的核心思想可以浓缩成两句话:"用注意力让每个位置直接看到所有其他位置"(消除 RNN 的串行依赖);"用因果 mask 让训练目标变成预测下一个 token"(自回归生成)。这两个设计加上"参数堆叠 + 数据堆叠"的工程实践,造就了今天的 LLM 浪潮。
给学习者的路径:先看可视化(The Illustrated Transformer 那篇文章),再看代码(nanoGPT 用 300 行实现 GPT),最后看论文(Attention Is All You Need + GPT-3 + LLaMA)。这个顺序比一上来啃论文友好得多。理解了 Transformer,你看 Claude / GPT-4 的更新公告就不再是"魔法",而是知道每一个改进点(MoE、long context、multi-modal)在 Transformer 框架里具体在改什么。
—— 别看了 · 2026