0x00 基本结构
原始 Transformer 的结构
完整的 Transformer 包含三种注意力类型
1. 全局自注意力 (Self-Attention)
全局注意力在编码器中,负责处理整个输入序列。序列中的每个元素都可以直接访问序列中的其它元素,从而与序列中的其他元素建立动态的关联,这样可以使模型更好地捕捉序列中的重要信息。
对于全局自注意力来说,Q、K、V有如下可能:
- Q、K、V都是输入序列。
- Q、K、V都来自编码器中前一层的输出。编码器中的每个位置都可以关注编码器前一层输出的所有位置。
更具体的说,Q是序列中当前位置的词向量,K和V是序列中的所有位置的词向量。
2. 掩码自注意力/因果自注意力 (Causal Attention)
掩码自注意力层或者说因果自注意力层(Causal attention layer)可以在解码阶段捕获当前词与已经解码的词之间的关联。它是对解码器的输入序列执行类似全局自注意力层的工作,但是又有不同之处。
Transformer Decoder 是自回归解码器,它逐个生成文本,然后将当前输出文本附加到之前输入上变成新的输入,后续的输出依赖于前面的输出词,因此具备因果关系。这种串行操作会极大影响训练模型的时间。为了并行提速,人们引入了掩码,这样在计算注意力时,通过掩码可以确保后面的词不会参与前面词的计算。
对于掩码自注意力来说,Q、K、V有如下可能:
- Q、K、V都是解码器的输入序列。
- Q、K、V都来自解码器中前一层的输出。解码器中的每个位置都可以关注解码器前一层的所有位置。
更具体的说,Q是序列中当前位置的词向量,K和V是序列中的所有位置的词向量。
3. 交叉注意力 (Cross-Attention)
交叉注意力层位于解码器中,但是其连接了编码器和解码器,可以刻画输入序列和输出序列之间的全局依赖关系,完成输入和输出序列之间的对齐。因此它需要将目标序列作为Q,将上下文序列作为K和V。
对于交叉注意力来说,Q、K、V 来自如下:
- Q来自前一个解码器层,是因果注意力层的输出向量。
- K和V来自编码器输出的注意力向量。
这使得解码器中的每个位置都能关注输入序列中的所有位置。另外,编码器并非只传递最后一步的隐状态,而是把所有时刻(对应每个位置)产生的所有隐状态都传给解码器,这就解决了中间语义编码上下文的长度是固定的问题。
3.1 编码器 (Encoder)
编码器的输入是 word embedding 序列。Transformer的编码器是由多个相同的 Transformer Encoder Layer 堆叠而成。而每个 Transformer Encoder Layer 又由以下几个部分构成:
| 顺序 | 名称 | 英文/缩写 |
|---|---|---|
| 1 | 多头自注意力 | Multi-head Self-Attention, MHA |
| 2 | 第一个残差连接 | Residual Connection |
| 3 | 第一个层归一化 | Layer Normalization, LN |
| 4 | 前馈神经网络 | Feed Forward Network, FFN |
| 5 | 第二个残差连接 | Residual Connection |
| 6 | 第二个层归一化 | Layer Normalization, LN |
编码器的输入和输出如下:
- 输入:Word Embedding + Positional Encoding 之后的 Token Embedding
- 输出:经过多层 Encoder Layer 之后的隐向量输出就是编码器的输出
值得一提的是,Encoder Layer 在实现经过残差连接和归一化的方式时,有 Post-LN 和 Pre-LN 两种。原始 Transformer 采用的是 Post-LN,也就是图中介绍的方式,而现在大多数模型均采用 Pre-LN。两种方式具体的代码分别如下:
###########
# Post-LN #
###########
class PostLNTransformerBlock(nn.Module):
def __init__(self, d_model, nhead):
super().__init__()
self.self_attn = nn.MultiheadAttention(d_model, nhead)
self.ffn = FeedForward(d_model)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
def forward(self, x):
# Self-Attention + Residual,然后 LayerNorm
attn_out, _ = self.self_attn(x, x, x)
x = self.norm1(x + attn_out) # ⬅️ LayerNorm 在残差之后
# FFN + Residual,然后 LayerNorm
ffn_out = self.ffn(x)
x = self.norm2(x + ffn_out) # ⬅️ LayerNorm 在残差之后
return x
###########
# Pre-LN #
###########
class PreLNTransformerBlock(nn.Module):
def __init__(self, d_model, nhead):
super().__init__()
self.self_attn = nn.MultiheadAttention(d_model, nhead)
self.ffn = FeedForward(d_model)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
def forward(self, x):
# 先 LayerNorm,然后 Self-Attention + Residual
attn_out, _ = self.self_attn(self.norm1(x), self.norm1(x), self.norm1(x))
x = x + attn_out # ⬅️ LayerNorm 在子层之前
# 先 LayerNorm,然后 FFN + Residual
ffn_out = self.ffn(self.norm2(x))
x = x + ffn_out # ⬅️ LayerNorm 在子层之前
return x
也就是说,两种公式分别为:
- Post-LN:
output = LayerNorm(x + Sublayer(x)) - Pre-LN:
output = x + Sublayer(LayerNorm(x))
3.2 解码器 (Decoder)
解码器的输入有两个,一个为解码器之前的预测输出(如果是第一个,则有特殊字符 bos 来代替),这一部分直接输入到解码器的因果注意力部分;另外一个为编码器的输出,作为交叉注意力的 Key 和 Value 部分。
而解码器也是有多层解码层(Decoder Layer)堆叠而成,每个 Decoder Layer 都由固定的模块组成,分别为:
| 顺序 | 名称 | 英文 |
|---|---|---|
| 1 | 因果自注意力 | Causal Attention |
| 2 | 残差和层归一化 | Residual Connection and Layer Norm |
| 3 | 交叉注意力 | Cross Attention |
| 4 | 残差和归一化 | Residual Connection and Layer Norm |
| 5 | 前馈神经网络 | Feed Forward Network |
| 6 | 残差和归一化 | Residual Connection and Layer Norm |
其中,因果自注意力和编码器的自注意力的不同之处,在于训练和推理的行为不同:
- 训练过程中,输入的是每个时间步下的全部序列,
- 推理过程中,每个时间不的输入是直到当前时间步下,所得到/生成的整个输出序列,也就是说推理的本质就是自回归的,解码器在预测时候的行为就是自回归生成的
为了在训练过程中最大化解码器因果自注意力的效率,会使用 teacher forcing机制(需要结合掩码机制),这样可以在解码器对同一序列中的不同位置的 token 同时预测。为了让预测 t 位置的时候,只能看到 t-1 及之前的内容,就需要引入掩码技术。换言之,在训练时,每个位置的 token 只能看到之前的词,而看不到未来的词。
解码器的输入和输出
- 解码器的输入有两部分:
- 解码器之前预测全部输出结果的拼接
- 编码器的输出,即第一个解码器层中交叉注意力的K、V均来自编码器的输出(编码器最后一层的 hidden states)
- 解码器的输出为一个向量,而这个向量在 NLP 任务中,会转换为分类/内积操作,从词表中找到一个最接近的词汇,作为预测结果
其他架构
Transformer 架构最早应用在机器翻译任务,以编码器-解码器的方式使用。在后续的工作中,一些研究也进入仅包含编码器和解码器的架构,如下图所示:
- (a) 仅包含编码器的架构 (Encoder-Only):如 Bert、ViT 等
- (b) 仅包含解码器的架构 (Decoder-Only):如 GPT 等
- (c) 编码器-解码器架构 (Encoder-Decoder):如 T5 等
仅解码器模型逐渐主导了LLM的发展。在LLM开发的早期阶段,仅解码器模型不如仅编码器和编码器-解码器模型流行。然而,在2021年之后,随着改变游戏规则的LLM-GPT-3的引入,仅解码器模型经历了显著的繁荣。与此同时,在BERT带来的最初爆炸性增长之后,仅编码器的模型逐渐开始逐渐消失。
Decoder-Only 架构又可以细分为两种架构:分别是 因果解码器 (Casual Decoder),前缀解码器 (Prefix-Decoder),其注意力的掩码如下图所示:
分析一下上述两种架构:
- Causal Decoder: 因果解码仅包含单向注意力掩码,即每个 token 只能注意到自己和之前出现过的 token,代表的模型如 GPT 一系列的生成式大模型。
- Prefix Decoder: 前缀解码包含了一部分的双向注意力,在输入部分,采用了双向注意力,而在输出部分,对生成的 token 使用了因果注意力,也就是生成的 token 仅能关注到自身和之前的内容。代表的模型包括早期的 GLM 系列。


