跳转至

0x00 基本结构

原始 Transformer 的结构

完整的 Transformer 包含三种注意力类型

Transformer.png

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 等

image.png

仅解码器模型逐渐主导了LLM的发展。在LLM开发的早期阶段,仅解码器模型不如仅编码器和编码器-解码器模型流行。然而,在2021年之后,随着改变游戏规则的LLM-GPT-3的引入,仅解码器模型经历了显著的繁荣。与此同时,在BERT带来的最初爆炸性增长之后,仅编码器的模型逐渐开始逐渐消失。

Decoder-Only 架构又可以细分为两种架构:分别是 因果解码器 (Casual Decoder),前缀解码器 (Prefix-Decoder),其注意力的掩码如下图所示:

image.png

分析一下上述两种架构:

  • Causal Decoder: 因果解码仅包含单向注意力掩码,即每个 token 只能注意到自己和之前出现过的 token,代表的模型如 GPT 一系列的生成式大模型。
  • Prefix Decoder: 前缀解码包含了一部分的双向注意力,在输入部分,采用了双向注意力,而在输出部分,对生成的 token 使用了因果注意力,也就是生成的 token 仅能关注到自身和之前的内容。代表的模型包括早期的 GLM 系列。