GPT Transformer 如何工作:因果自注意力机制解析

GPT 仅解码器 Transformer 的详细技术架构图,展示了因果掩码和自回归 token 生成

近年来,生成式预训练 Transformer(GPT)彻底改变了人工智能。从代码助手到对话智能体,基于 GPT 的模型正在为当今最先进的生成式应用提供动力。但是这项技术究竟是如何工作的呢?

虽然像 BERT 这样的模型使用 Transformer 的**编码器(Encoder)部分来双向理解文本,但 GPT 采用的是仅解码器(Decoder-only)**的架构,专为自回归的下一 token 预测而设计。在本文中,我们将揭秘 GPT Transformer 的工作原理,深入探讨因果自注意力(Causal Self-Attention)机制,并在代码中对其进行实现。


1. 自回归生成循环

在核心层面,GPT 是一个自回归模型。这意味着要生成一段文本序列,它会一个接一个地预测下一个 token,并使用已生成的 token 作为下一次预测的上下文。

其工作流程遵循以下步骤:

  1. 输入: 模型接收一个提示词(prompt):"Deep learning is"
  2. 预测: 模型处理该提示,并在其整个词汇表上输出一个概率分布。它对下一个 token 进行采样:"awesome"
  3. 循环: 新生成的 token 被追加到输入中,使其变为:"Deep learning is awesome"。这个新序列成为下一步的输入。
  4. 终止: 重复该过程,直到模型输出特殊的序列结束符([EOS])或达到预设的长度限制。

2. 因果掩码:解码器的核心

在像 BERT 这样仅包含编码器的模型中,每个 token 都可以关注所有其他 token,同时回顾过去并展望未来。然而,对于预测下一个 token 的生成式模型来说,在训练期间看到未来的 token 属于“作弊”。

为了防止模型看到未来的 token, GPT 使用了因果自注意力机制(或称掩码自注意力机制)。

因果掩码矩阵

在自注意力计算期间,我们通过计算查询(Query, $Q$)和键(Key, $K$)的点积来计算 token 之间的相似度得分:

$$\text{Scores} = QK^T$$

为了强制执行因果关系,我们引入一个掩码矩阵 $M$,其中对角线以上的所有值都设为 $-\infty$(负无穷大),对角线及以下的值设为 0。在应用 softmax 函数之前,我们将此掩码加到得分矩阵中:

$$\text{Masked Scores} = \frac{QK^T}{\sqrt{d_k}} + M$$

$$M = \begin{pmatrix} 0 & -\infty & -\infty & \dots & -\infty \\ 0 & 0 & -\infty & \dots & -\infty \\ 0 & 0 & 0 & \dots & -\infty \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & 0 & \dots & 0 \end{pmatrix}$$

当我们应用 softmax 函数时,$e^{-\infty}$ 会变为 $0$。因此,任何未来位置的注意力权重都会变为精确的 0,从而使未来的 token 对当前的 token 不可见。


3. GPT 层的核心架构模块

GPT 模型由多个堆叠的 Transformer 解码器层组成。每一层包含以下几个关键部分:

A. 输入嵌入与位置编码

  • 分词(Tokenization): 原始文本使用字节对编码(Byte-Pair Encoding, BPE)被拆分为子词 token。
  • Token 嵌入: 每个 token 被映射到一个高维向量。
  • 可学习的位置嵌入: 由于自注意力本身不包含顺序信息,GPT 将可学习的位置嵌入向量加到 token 嵌入中,以便模型知道每个 token 在序列中的位置。

B. 前置层归一化(Pre-Layer Normalization / Pre-LN)

原始 Transformer 架构在残差连接加法之后应用层归一化(Post-LN),而现代 GPT 架构则在注意力层和前馈网络层之前应用层归一化:

$$x_{l+1} = x_l + \text{Attention}(\text{LayerNorm}(x_l))$$

Pre-LN 稳定了训练期间的梯度流,使我们能够稳定地训练具有数千亿参数的深层网络。

C. 前馈网络(Feed-Forward Network / FFN)

在注意力模块之后,每个 token 的表示通过一个多层感知机(MLP),该感知机由两次线性变换和一个激活函数(通常是 GeLU)组成:

$$\text{FFN}(x) = \max(0, x W_1 + b_1) W_2 + b_2$$


4. 采样机制(从 Logits 到 Token)

最后的解码器模块输出每个位置上原始得分的向量,称为 Logits。我们使用 softmax 函数将这些 logits 转化为概率。为了控制生成文本的随机性,我们在采样时应用以下参数:

  • 温度(Temperature, $T$): 在 softmax 之前对 logits 进行缩放。较低的温度(例如 $T = 0.2$)使模型输出更具确定性且更集中,而较高的温度(例如 $T = 0.8$)则会增加创造力和多样性。
  • Top-K: 限制下一步选择为概率最高的前 $K$ 个 token。
  • Top-P(核采样 / Nucleus Sampling): 累加概率分布,并从累加概率超过 $P$(例如 $P = 0.9$)的最小 token 集合中进行选择。

5. 因果自注意力的 PyTorch 实现

下面是一个自包含的 PyTorch 实现,演示了带有因果掩码的因果自注意力机制:

import torch
import torch.nn as nn
import torch.nn.functional as F

class CausalSelfAttention(nn.Module):
    def __init__(self, d_model, n_heads):
        super().__init__()
        assert d_model % n_heads == 0
        self.d_model = d_model
        self.n_heads = n_heads
        self.d_k = d_model // n_heads
        
        # 查询、键和值的映射层
        self.q_proj = nn.Linear(d_model, d_model)
        self.k_proj = nn.Linear(d_model, d_model)
        self.v_proj = nn.Linear(d_model, d_model)
        self.out_proj = nn.Linear(d_model, d_model)
        
    def forward(self, x):
        batch_size, seq_len, d_model = x.size()
        
        # 1. 将输入映射到 Q, K, V
        Q = self.q_proj(x).view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
        K = self.k_proj(x).view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
        V = self.v_proj(x).view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
        
        # 2. 计算原始注意力得分
        scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_k ** 0.5)
        
        # 3. 创建并应用因果掩码
        # 用负无穷大填充的上三角掩码
        mask = torch.triu(torch.ones(seq_len, seq_len, device=x.device), diagonal=1).bool()
        scores = scores.masked_fill(mask, float('-inf'))
        
        # 4. Softmax 将 -inf 转化为 0 概率
        attn_weights = F.softmax(scores, dim=-1)
        
        # 5. 计算值的加权和并输出
        context = torch.matmul(attn_weights, V)
        context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, d_model)
        
        return self.out_proj(context)

# 快速验证运行
if __name__ == "__main__":
    # 批大小 = 1, 序列长度 = 4, 模型维度 = 8, 2个注意力头
    x = torch.randn(1, 4, 8)
    attention_layer = CausalSelfAttention(d_model=8, n_heads=2)
    output = attention_layer(x)
    print("输入特征维度:", x.shape)
    print("输出特征维度:", output.shape)

6. 架构对比

特征 BERT(仅编码器) GPT(仅解码器) 原始 Transformer
主要任务 理解 / 信息抽取 生成 / 文本合成 翻译 / 序列到序列
注意力类型 双向自注意力 因果掩码自注意力 双向与因果交叉注意力
掩码机制 掩码 token ([MASK]) 因果三角掩码 解码器中的因果掩码
处理方式 一次性处理整个序列 自回归 token 生成 编码器处理一次,解码器迭代生成

结论

通过舍弃编码器并完全专注于因果掩码自注意力,GPT 开启了生成式模型规模扩展的道路。预测下一个 token 的简单规则,结合大规模的并行训练,使得 GPT 模型能够捕捉逻辑、代码和语言的丰富内部表示,构成了现代认知智能的基石。


在 Ghaznix 博客上探索更多技术见解 →