直觉理解

Transformer 是 2017 年在 Attention Is All You Need 中提出的神经网络架构,如今几乎所有大语言模型都基于它。

在它之前,处理序列主要靠循环神经网络(RNN、LSTM):按顺序一个词一个词地读,把信息存在一个隐藏状态里往后传。这有两个问题:一是必须串行计算,难以利用 GPU 的并行能力;二是距离远的两个词之间要经过很多步传递,信息容易丢失。

Transformer 完全抛弃了循环结构,只用注意力机制让每个位置直接与所有位置交互。整个模型就是同一种「块」的反复堆叠,每个块做两件事:

  1. 注意力:让各个位置互相交换信息(token 之间的混合)。
  2. 前馈网络:对每个位置单独做非线性变换(每个 token 内部的加工)。

再加上残差连接和归一化保证深层网络能稳定训练,以及位置编码告诉模型词的顺序。

结构

整体流程

以仅解码器的语言模型(GPT 风格)为例,一次前向计算是:

  1. 文本经过分词器变成 token id 序列。
  2. 嵌入层把每个 id 变成 维向量,加上(或在注意力中注入)位置信息。
  3. 依次经过 个 Transformer 块,每个块的输入输出形状都是 (序列长度, )。
  4. 最后做一次归一化,再用线性层映射到词表大小,得到每个位置上「下一个 token」的分数(logits)。
flowchart TB
    A[token id] --> B[词嵌入 + 位置编码]
    B --> C["Transformer 块 × N"]
    C --> D[最终归一化]
    D --> E[线性层:映射到词表]
    E --> F[softmax:下一个 token 的概率]

Transformer 块

每个块由两个子层组成,每个子层外面都包着残差连接和归一化。现在主流的 Pre-LN 写法是:

多头注意力的细节见注意力机制。解码器中使用因果掩码,保证每个位置只能看到自己和之前的位置。

前馈网络(FFN)是一个作用在每个位置上的两层 MLP,各位置共享参数:

普通两层 FFN 的隐藏维度常取 ,激活函数可用 ReLU 或 GELU。SwiGLU 则是带两条输入投影分支的门控结构,不能只把上式的 换个名字,详见激活函数。在标准多头注意力加 FFN 的解码器块中,FFN 约占块内参数的三分之二,这个比例不适用于所有模型或整个网络。知识编辑研究在特定模型的 FFN 中发现了可干预的事实关联,但不能把知识存储简单归结为单一组件。把前馈层换成多个专家、每次只激活其中几个,是常见的混合专家模型设计。

残差连接让子层只需要学习对输入的「修正量」,梯度也可以沿残差通路直接传回前面的层,这是几十上百层的网络能训练起来的关键。

Post-LN 与 Pre-LN

  • Post-LN:原始论文的写法,。归一化在残差相加之后,层数多了以后靠近输出的层梯度偏大,训练不稳定,需要仔细的学习率预热。
  • Pre-LN:,归一化挪到子层之前,残差通路上不再有归一化,梯度更稳定。GPT-2 起的大语言模型基本都采用这种写法,并在所有块之后再加一个最终归一化。

LLaMA 等模型进一步把层归一化换成更简单的 RMSNorm。

位置编码

不带位置编码、位置偏置或顺序相关掩码的全连接自注意力具有置换等变性:重排输入,输出只会跟着重排。标准 Transformer 因而显式加入位置信息。因果掩码本身也带有顺序结构,不能把上述性质不加条件地套到因果注意力上。

正弦位置编码(原始 Transformer):给位置 构造一个固定向量,加到词嵌入上:

不同维度对应不同频率的正弦波,就像用一组快慢不同的时钟共同表示一个时刻。它不需要训练。

可学习的位置编码(BERT、GPT-2):直接为每个位置学一个向量,与词嵌入相加。未经扩展的位置嵌入表不能索引超过表长的位置;可以扩表、插值并继续训练,但超出已训练位置后的效果没有保证。

旋转位置编码(RoPE,2021 年由苏剑林等人提出):在计算注意力前,把查询和键的成对维度按位置旋转。若 表示位置 的旋转,则 。显式的位置因子只依赖相对距离,但点积仍取决于查询和键的内容,不能说得分只由距离决定。RoPE 被 LLaMA 等模型采用,很多扩窗方法通过调整旋转频率实现,见长上下文。

三种变体

原始 Transformer 是为机器翻译设计的编码器-解码器结构。后来的模型只保留其中一部分,形成三大家族:

变体注意力方式训练目标代表模型擅长
仅编码器双向自注意力,每个位置能看到全部上下文掩码语言模型:随机遮住一些词再预测BERT(2018)理解类任务:分类、抽取、检索
仅解码器带因果掩码的自注意力,只看左边预测下一个 tokenGPT 系列(2018 起)生成,是当今大语言模型的主流
编码器-解码器编码器双向;解码器因果,并通过交叉注意力读取编码器输出序列到序列,如 T5 把所有任务统一成「文本到文本」原始 Transformer、T5(2019)翻译、摘要等输入输出分明的任务

仅解码器结构便于用统一的续写目标训练,并通过提示适配任务,见 GPT-3。KV 缓存并非它独有:自回归编码器-解码器模型同样可以缓存解码器的键值和交叉注意力的键值。表中的训练目标是代表做法,架构本身并不唯一决定训练目标。

计算复杂度与参数量

  • 自注意力的计算量随序列长度 平方增长,每层约 ;前馈层约 。序列较短时前馈层占主导,序列很长时注意力占主导。
  • 每个块的参数约 :注意力的 4 个投影矩阵约 ,隐藏层为 的前馈层约 。例如 GPT-3 的 、96 层, 亿,与它 1750 亿的参数量基本吻合,剩下的主要是嵌入矩阵。

原始论文的基础模型取 、8 个头、前馈层 2048 维、编码器和解码器各 6 层。

代码示例

一个最小的 GPT 风格语言模型:Pre-LN 结构、因果掩码、可学习位置编码。

import torch
import torch.nn as nn
 
class Block(nn.Module):
    """Pre-LN 的仅解码器 Transformer 块(GPT 风格)"""
    def __init__(self, d_model, n_heads, d_ff):
        super().__init__()
        self.ln1 = nn.LayerNorm(d_model)
        self.attn = nn.MultiheadAttention(d_model, n_heads, batch_first=True)
        self.ln2 = nn.LayerNorm(d_model)
        self.ffn = nn.Sequential(nn.Linear(d_model, d_ff), nn.GELU(), nn.Linear(d_ff, d_model))
 
    def forward(self, x):
        n = x.size(1)
        # nn.MultiheadAttention 的布尔掩码中 True 表示「不允许看」
        mask = torch.triu(torch.ones(n, n, dtype=torch.bool, device=x.device), diagonal=1)
        h = self.ln1(x)
        x = x + self.attn(h, h, h, attn_mask=mask, need_weights=False)[0]   # 残差连接
        x = x + self.ffn(self.ln2(x))                                        # 残差连接
        return x
 
class TinyGPT(nn.Module):
    def __init__(self, vocab_size=100, max_len=64, d_model=32, n_heads=4, n_layers=2):
        super().__init__()
        self.tok = nn.Embedding(vocab_size, d_model)
        self.pos = nn.Embedding(max_len, d_model)    # 可学习的位置编码
        self.blocks = nn.ModuleList(Block(d_model, n_heads, 4 * d_model) for _ in range(n_layers))
        self.ln_f = nn.LayerNorm(d_model)
        self.head = nn.Linear(d_model, vocab_size, bias=False)
 
    def forward(self, ids):
        pos = torch.arange(ids.size(1), device=ids.device)
        x = self.tok(ids) + self.pos(pos)
        for block in self.blocks:
            x = block(x)
        return self.head(self.ln_f(x))    # 每个位置输出下一个 token 的 logits
 
ids = torch.randint(0, 100, (2, 10))
print(TinyGPT()(ids).shape)               # torch.Size([2, 10, 100])

用交叉熵训练这个模型预测下一个 token 的方法,见语言模型。

在 AI 中的作用

Transformer 广泛用于语言、视觉、语音和多模态任务。公开架构的 GPT-2、GPT-3、LLaMA 等采用仅解码器结构;ViT 把图像切成块输入编码器。不能仅凭闭源模型的产品名称推断其未公开的架构。稠密注意力在训练和预填充时的平方计算量,以及全局自回归注意力不断增长的 KV 缓存,是许多后续优化研究的出发点。

参考资料

相关