4. Transformer架构解析:Encoder-Decoder结构、位置编码(绝对与相对)、残差连接与层归一化

Transformer 这个架构,说实话,已经成了现代 AI 的基石。无论是 NLP 还是 CV,甚至多模态,你都能看到它的影子。今天我们就来拆解一下它的核心组件。

我个人习惯把 Transformer 比作一个「翻译工厂」。Encoder 是理解原文的部门,Decoder 是生成译文的部门。中间通过注意力机制传递信息。嗯,这个比喻虽然简单,但很贴切。

4.1 Encoder-Decoder 结构:工厂的两条流水线

先看整体结构。Transformer 不是单一体,而是由 Encoder 和 Decoder 堆叠而成。

  • Encoder(编码器):由 N 个相同的层堆叠。每层包含两个子层:多头自注意力机制 + 前馈神经网络。每个子层外面都包着残差连接和层归一化。
  • Decoder(解码器):也是 N 个相同层。但每层有三个子层:掩码多头自注意力 + 交叉注意力 + 前馈神经网络。同样有残差和归一化。

我在项目中遇到过一个问题:刚开始做机器翻译时,我直接把 Encoder 的输出喂给 Decoder,结果发现 Decoder 在生成第一个词时就能「偷看」到后面的词。这显然不对。所以 Decoder 里必须加掩码,让当前位置只能看到它之前的词。

核心区别:Encoder 是双向的,能看到整个序列。Decoder 是单向的,只能从左到右生成。

你想想看,如果 Decoder 能看到未来的词,那训练时它直接复制答案就行了,根本学不会真正的生成能力。这就是掩码存在的意义。

4.2 位置编码:给序列注入「顺序感」

Transformer 本身没有循环或卷积,它不知道词与词之间的先后顺序。所以我们需要手动告诉模型:「'我' 在 '爱' 前面,'爱' 在 '你' 前面」。

位置编码有两种主流方案:绝对位置编码和相对位置编码。

4.2.1 绝对位置编码

这是原始 Transformer 用的方法。用正弦和余弦函数生成固定位置的编码向量。

# 绝对位置编码公式
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

为什么用三角函数?因为这样编码出来的向量有很好的性质:不同位置之间的相对关系可以通过线性变换表达。说白了,就是模型能通过数学运算知道「位置 5 和位置 3 的距离是 2」。

我曾经在部署时踩过一个坑:位置编码的精度问题。在 FP16 推理时,三角函数计算出来的值精度损失严重,导致长序列的编码出现偏差。后来我改用查表法,提前算好所有位置的编码存起来,推理时直接查表,又快又准。

4.2.2 相对位置编码

绝对位置编码有个问题:它只关心「这个词在第几个位置」,不关心「这个词和另一个词的距离是多少」。对于很多任务来说,相对距离比绝对位置更重要。

相对位置编码的思路是:在注意力计算时,把位置信息加到 Query 和 Key 的交互中。比如,让模型知道「这个词离我近,那个词离我远」。

我的建议:如果你的任务涉及长序列(比如文档理解、长文本生成),优先考虑相对位置编码。它在处理超出训练长度的序列时表现更好。

举个例子,BERT 用的是绝对位置编码,但后来很多改进模型(如 T5、DeBERTa)都转向了相对位置编码。为什么?因为绝对位置编码在序列长度超过训练长度时,会直接崩溃——它没见过那么远的位置。

4.3 残差连接:让梯度「抄近道」

Transformer 堆了那么多层,如果没有残差连接,梯度早就消失了。残差连接的思想很简单:把输入直接加到输出上。

# 残差连接的数学表达
output = LayerNorm(x + Sublayer(x))

这个「x + Sublayer(x)」就是残差。它让梯度在反向传播时有一条「高速公路」,可以直接从顶层流到底层。

我记得刚开始训练 Transformer 时,去掉残差连接试了一次。结果模型根本训不动,loss 降不下去。加上残差后,训练立马稳定了。说白了,残差连接就是深度网络的「救命稻草」。

注意:残差连接要求输入和输出的维度一致。如果 Sublayer 改变了维度(比如前馈网络有时会升维再降维),需要用一个线性投影把输入映射到输出维度。

4.4 层归一化:稳定训练的「定海神针」

层归一化(Layer Normalization)和批归一化(Batch Normalization)不同。它是对每个样本的所有特征做归一化,而不是对 batch 维度。

为什么 Transformer 用 LayerNorm 而不是 BatchNorm?因为 NLP 任务中序列长度变化很大,BatchNorm 在 batch 维度上统计均值和方差会很不稳定。LayerNorm 只针对单个样本,不受 batch 大小和序列长度影响。

# LayerNorm 的计算
mean = mean(x, axis=-1)
var = var(x, axis=-1)
output = (x - mean) / sqrt(var + epsilon) * gamma + beta

我在部署时发现一个细节:LayerNorm 中的 epsilon 不能太小。在 FP16 推理时,如果 epsilon 设成 1e-12,除以方差时很容易溢出。我一般设成 1e-5 或 1e-6,既保证数值稳定,又不影响精度。

4.5 整体架构图

下面这张图展示了 Transformer 的核心结构。我特意把 Encoder 和 Decoder 的对比画了出来,方便你理解。

Transformer 架构核心组件 Encoder × N 多头自注意力机制 Multi-Head Self-Attention 残差连接 + 层归一化 前馈神经网络 Feed-Forward Network 残差连接 + 层归一化 位置编码(绝对/相对) 输入 Embedding Decoder × N 掩码多头自注意力 Masked Self-Attention 残差连接 + 层归一化 交叉注意力机制 Cross-Attention 残差连接 + 层归一化 前馈神经网络 Feed-Forward Network 残差连接 + 层归一化 Key, Value 残差连接 + 层归一化 位置编码 输入层

4.6 避坑指南:部署时的常见问题

最后分享几个我在硬件部署时踩过的坑,希望能帮你少走弯路。

坑一:LayerNorm 的数值精度

我曾经在部署到边缘设备时,发现推理结果和 GPU 上不一致。排查了半天,发现是 LayerNorm 的 epsilon 在 FP16 下太小,导致除零错误。解决方案:epsilon 设成 1e-5,并且在归一化前先 clamp 一下方差,防止极端值。

坑二:位置编码的缓存

如果你用绝对位置编码,建议提前算好所有位置的编码,存成常量表。不要在推理时实时计算 sin/cos,那会浪费大量算力。我见过有人每次推理都重新算位置编码,结果推理速度慢了 3 倍。

坑三:残差连接的数值溢出

在 INT8 量化部署时,残差连接后的数值范围可能超出量化范围。我的做法是在残差连接后加一个 clip 操作,把数值限制在 [-128, 127] 之间。虽然损失了一点精度,但换来了稳定性。

好了,Transformer 的核心架构就讲到这里。Encoder-Decoder 结构、位置编码、残差连接和层归一化,这四个组件缺一不可。理解它们,你就掌握了 Transformer 的精髓。


专注资料整理