Transformer 完全指南:从注意力机制到 GPT 的工作原理

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
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

解释器模式完全指南:从规则引擎到 SpEL 与 mini 语言设计

2026-5-15 15:39:35

技术教程

Embedding 与向量数据库完全指南:语义搜索的工程实现

2026-5-15 15:54:03

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索