完整 Transformer:编码器 + 解码器


文档摘要

完整 Transformer:编码器 + 解码器 本节摘要:注意力是主角,其余一切——残差连接、层归一化、前馈网络、交叉注意力——都是让你能把注意力块堆深的脚手架。单层注意力只是个特征提取器,一次矩阵乘法装不下语言所需的表达容量,你需要深度,而深度没有正确的「管道」就会崩。2017 年的 Vaswani 论文用六个设计决策把一层注意力打包成一个可堆叠的块:嵌入与位置信号、自注意力、前馈网络(FFN)、残差连接、层归一化、交叉注意力(仅解码器)。此后每一个 Transformer——编码器型(BERT)、解码器型(GPT)、编码器-解码器型(T5)——都继承同一副骨架。到 2026 年,这些块被进一步打磨(RMSNorm、SwiGLU、pre-norm、RoPE),但骨架分毫未改。

完整 Transformer:编码器 + 解码器

本节摘要:注意力是主角,其余一切——残差连接、层归一化、前馈网络、交叉注意力——都是让你能把注意力块堆深的脚手架。单层注意力只是个特征提取器,一次矩阵乘法装不下语言所需的表达容量,你需要深度,而深度没有正确的「管道」就会崩。2017 年的 Vaswani 论文用六个设计决策把一层注意力打包成一个可堆叠的块:嵌入与位置信号、自注意力、前馈网络(FFN)、残差连接、层归一化、交叉注意力(仅解码器)。此后每一个 Transformer——编码器型(BERT)、解码器型(GPT)、编码器-解码器型(T5)——都继承同一副骨架。到 2026 年,这些块被进一步打磨(RMSNorm、SwiGLU、pre-norm、RoPE),但骨架分毫未改。本节就是这副骨架:带你把六块积木拼成一个 2 层编码器 + 2 层解码器,看一个向量如何流过它,再用现代组件把 2017 版块升级成 2026 版。读完本节,你能说清为什么深度需要残差、为什么 pre-norm 干掉了 post-norm、以及怎么按任务选编码器/解码器/编码器-解码器。

学习目标

阅读完本节,你应当能够:

  1. 说出构成 Transformer 块的六个组件:嵌入+位置、自注意力、FFN、残差、层归一化、交叉注意力,以及各自解决什么问题。
  2. 说清残差连接为什么是深度的前提——没有它梯度在约 6 层之后就消失。
  3. 区分 pre-norm 与 post-norm,解释为什么 2026 年的默认是 pre-norm(Llama、Qwen、GPT-3+、Mistral 全用它)。
  4. RMSNorm 替换 LayerNorm、SwiGLU 替换 ReLU,把 2017 版块升级成 2026 版块,且只改函数不动骨架。
  5. 按任务需求选择架构:分类/嵌入用编码器型、生成用解码器型、结构化输入→结构化输出用编码器-解码器型。

一、问题与直觉

单个注意力层是一个特征提取器,不是一个模型。每层一次矩阵乘不足以承载语言的表达容量。你需要深度,而深度没有正确的管道就会崩。

Vaswani 2017 用六个设计决策把一层注意力变成可堆叠的块。本节就是这副骨架,06 节把它特化成编码器,07 节特化成解码器,08 节特化成编码器-解码器。

六块积木

  1. 嵌入 + 位置信号。 token → 向量,位置靠 RoPE(现代)或正弦(经典)注入。
  2. 自注意力。 每个位置关注所有其他位置;解码器里要加掩码。
  3. 前馈网络(FFN)。 位置独立的两层 MLP:W_2 · activation(W_1 · x),默认扩展比 4 倍。
  4. 残差连接。 x + sublayer(x),没有它,约 6 层之后梯度消失。
  5. 层归一化。 LayerNormRMSNorm(现代),稳定残差流。
  6. 交叉注意力(仅解码器)。 查询来自解码器,键和值来自编码器输出。

看一个向量流过一个块:注意力跨位置混合,残差把它带向前,FFN 再变换它,归一化保持流稳定。

编码器块 vs 解码器块

编码器块(双向,无掩码): x → LN → MHA(自注意力) → + → LN → FFN → + → out ^ ^ └─── 残差 ─────┘ 解码器块(自回归 + 看编码器): x → LN → MHA(掩码自注意力) → + → LN → MHA(交叉到编码器) → + → LN → FFN → + → out

解码器每块三个子层。中间那个——交叉注意力——是信息从编码器流向解码器的唯一通道。在纯解码器架构(GPT)里,交叉注意力被省略,只剩掩码自注意力 + FFN。

编码器是双向的,无掩码,所有位置互相可见;解码器是自回归的,未来位置必须被遮住(因果掩码,见第 02 节)。

Pre-norm vs Post-norm

原论文用 post-norm:LN(x + sublayer(x)),归一化在残差相加之后。2019 年左右它失宠——不精心 warmup 就难训深。Pre-norm(x + sublayer(LN(x)),归一化在子层之前)是 2026 年的默认:Llama、Qwen、GPT-3+、Mistral 全用 pre-norm。

差别看似微小,后果巨大:post-norm 把残差流也归一化了,深堆叠时主干信号会被反复重塑,激活容易爆炸或塌缩;pre-norm 让残差主干保持原样,梯度可以一路无阻碍地传到底层。

2026 年的现代块

Vaswani 2017 出货时是 LayerNorm + ReLU,生产块已经把两者都换了:

组件 2017 2026
归一化 LayerNorm RMSNorm
FFN 激活 ReLU SwiGLU
FFN 扩展比 2.6×(SwiGLU 用三矩阵,总参数匹配)
位置 绝对正弦 RoPE
注意力 完整 MHA GQA(或 MLA)
偏置项

RMSNorm 去掉了 LayerNorm 的去中心化步骤(少一次减法),省算力且经验上至少同等稳定。SwiGLU(Swish(W1·x) ⊙ (W3·x) 再过 W2)在 Llama、PaLM、Qwen 论文里一致地比 ReLU/GELU FFN 低约 0.5 点困惑度。

💡 SwiGLU 的本质:门控线性单元(GLU)。Swish(W1·x) 当门,决定 W3·x 的哪些维度被放过——让 FFN 学到「条件性」的非线性,而非对每个输入一刀切。

参数计数

对一块 d_model = d、FFN 扩展比 r:

  • MHA:4·d²(Q、K、V、O 四个投影)
  • FFN(SwiGLU):3·d·(r·d)3r·d²
  • 归一化层:可忽略

d=4096, r=2.6, 层数=32(约等于 Llama 3 8B),总参数 32·(4·4096² + 3·2.6·4096²) ≈ 32·(16 + 32)M ≈ 1.5B/层 × 32 ≈ 7B(再加嵌入和输出头),与公布计数吻合。

二、从零实现

完整接线见原课程 phases/07-transformers-deep-dive/05-full-transformer/code/main.py。关键函数:

Step 1:积木函数

def layer_norm(x, eps=1e-5): mean = sum(x) / len(x) var = sum((xi - mean) ** 2 for xi in x) / len(x) return [(xi - mean) / (var + eps) ** 0.5 for xi in x] def rms_norm(x, eps=1e-6): rms = (sum(xi ** 2 for xi in x) / len(x) + eps) ** 0.5 return [xi / rms for xi in x] # 不去中心,省一次减法 def ffn_swiglu(x, W1, W2, W3): gate = [silu(sum(W1[i][j] * x[j] for j in range(len(x)))) for i in range(len(W1[0]))] value = [sum(W3[i][j] * x[j] for j in range(len(x))) for i in range(len(W3[0]))] hidden = [gate[i] * value[i] for i in range(len(gate))] # GLU 门控 return [sum(W2[i][j] * hidden[j] for j in range(len(hidden))) for i in range(len(W2[0]))]

设计要点:silu(x) = x · sigmoid(x)(Swish)。门控 gate ⊙ value 让 FFN 学到「这层该激活哪些维度」,比 ReLU 的硬阈值灵活。

Step 2:编码器块与解码器块

把上面六块拼起来,每个子层包一个残差:

def encoder_block(x, p): h = layer_norm(x) h = multi_head_attention(h, h, h) # 自注意力,Q=K=V x = [x[i] + h[i] for i in range(len(x))] # 残差 h = layer_norm(x) h = ffn_swiglu(h, p.W1, p.W2, p.W3) return [x[i] + h[i] for i in range(len(x))]# 残差 def decode(target_tokens, encoder_out, params): x = embed(target_tokens, params.emb) x = [x[i] + pe[i] for i in range(len(x))] # 加位置 for block in params.decoder_blocks: x = decoder_block(x, encoder_out, block) # 内含掩码自注意力 + 交叉注意力 + FFN return x

Step 3:在玩具样例上前向

喂 6 个源 token、5 个目标 token,验证输出形状是 (5, vocab)。本节不训练——它讲的是架构,不是损失。

Step 4:换上 RMSNorm + SwiGLU

layer_norm 换成 rms_norm、ReLU-FFN 换成 ffn_swiglu,确认形状不变。这就是 2026 年的现代化:一次函数替换,骨架不动。

三、框架对比:PyTorch 标准块与生产实现

PyTorch 提供 nn.TransformerEncoderLayernn.TransformerDecoderLayer 作参考。但 2026 年大多数生产代码自己写块,因为:

  • Flash Attention 在注意力内部调用,不走 nn.MultiheadAttention
  • GQA / MLA 不在标准库参考里。
  • RoPE、RMSNorm、SwiGLU 不是 PyTorch 默认。

HuggingFace transformers 有干净的参考块值得读一遍:modeling_llama.py 是 2026 年标准的解码器块,约 500 行,值得通读一次。

编码器 / 解码器 / 编码器-解码器——何时选哪个

需求 例子
分类、嵌入、文本问答 编码器型 BERT、DeBERTa、ModernBERT
文本生成、聊天、代码、推理 解码器型 GPT、Llama、Claude、Qwen
结构化输入→结构化输出(翻译、摘要) 编码器-解码器型 T5、BART、Whisper

解码器型赢了语言,因为它 scale 得最干净,且同时处理理解和生成。编码器-解码器型在输入有明确「源序列」身份时仍是最佳——翻译、语音识别、结构化任务。

四、可复用产物

原课程产出 outputs/skill-transformer-block-reviewer.md:一个代码审查 Skill,把一个新的 Transformer 块实现对照 2026 默认配置打分,标出缺失项(pre-norm、RoPE、RMSNorm、GQA、FFN 扩展比)。喂给它任何一份块代码,它产出一份「现代化差距清单」。

五、练习

  1. (Easy) 数出你的 encoder_blockd_model=512, n_heads=8, ffn_expansion=4, swiglu=True 下的参数量,用 sum(p.numel() for p in block.parameters()) 验证。
  2. (Medium) 把 post-norm 改成 pre-norm。两种都初始化,在随机输入上堆 12 层后测激活范数。post-norm 的激活应爆炸,pre-norm 应保持有界。
  3. (Hard) 在玩具复制任务(把 x 反转输出)上实现一个 4 层编码器-解码器,训 100 步报告损失。再换上 RMSNorm + SwiGLU + RoPE,损失降了吗?

本节要点回顾

  1. 六块积木:嵌入+位置、自注意力、FFN、残差、归一化、交叉注意力(仅解码器)——自 2017 不变。
  2. 单层注意力只是特征提取器:表达容量靠深度,深度靠残差+归一化撑起来。
  3. 残差是深度的前提:x + sublayer(x) 让梯度绕过非线性一路传到底层,没有它约 6 层后梯度消失。
  4. Pre-norm 干掉 post-norm:归一化在子层前,残差主干保持原样;2026 年 Llama、Qwen、GPT-3+、Mistral 全用 pre-norm。
  5. 2026 现代化三件套:RMSNorm(去中心化)、SwiGLU(门控 FFN,低 0.5 点困惑度)、GQA(注意力头分组),骨架不动只换函数。
  6. FFN 扩展比:经典 4×,SwiGLU 用 2.6×(三矩阵总参数匹配)。
  7. 参数大头:MHA 4d² + FFN 3r·d²,归一化可忽略——Llama 3 8B 的计数就是这么算出来的。
  8. 架构选择:分类/嵌入选编码器型,生成选解码器型,结构化输入→输出选编码器-解码器型;解码器型靠 scale 干净赢了语言。

下一节,我们将把这副骨架特化成编码器,实现 BERT 的掩码语言建模——把 15% 的 token 盖住,让模型猜回来,从而学到双向上下文表示。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U