Transformer原理详解

注意力就是一切的架构 — Attention Is All You Need
Transformer Self-Attention Multi-Head Attention Positional Encoding Encoder-Decoder LayerNorm FFN

一、Transformer架构总览

2017年,Google在论文《Attention Is All You Need》中提出了Transformer架构,彻底改变了自然语言处理(NLP)领域的格局。Transformer摒弃了传统的循环神经网络(RNN)和卷积神经网络(CNN),完全基于注意力机制(Attention Mechanism)来建模序列数据。其核心思想在于:序列中任意两个位置之间的距离都是常数,这从根本上解决了RNN难以捕获长程依赖的痛点。

Transformer的原始架构采用了编码器-解码器(Encoder-Decoder)结构。编码器由N个完全相同的层(Layer)堆叠而成(原论文中N=6),每一层包含两个子层:多头自注意力子层(Multi-Head Self-Attention)和逐位置前馈神经网络子层(Position-wise Feed-Forward Network)。每个子层周围都使用了残差连接(Residual Connection)层归一化(Layer Normalization),公式表达为:LayerNorm(x + Sublayer(x))。解码器同样由N个层堆叠,每个解码器层包含三个子层:掩码多头自注意力(Masked Multi-Head Self-Attention)、编码器-解码器注意力(Encoder-Decoder Attention)和前馈网络。

架构核心特点

  • 完全基于注意力: 没有任何循环或卷积操作,全部依赖注意力机制捕获依赖关系
  • 并行计算: 输入序列的所有位置可以同时进行计算,训练效率远高于RNN
  • 全局感受野: 每个位置可以直接关注到整个序列的所有其他位置
  • 多层堆叠: 通过N层堆叠逐步构建从低级到高级的语义表示
  • 残差连接: 缓解深层网络中的梯度消失问题,使信息流动更加顺畅

1.1 编码器结构

编码器(Encoder)负责将输入序列映射为连续的表示序列。每个编码器层包含两个子层:第一个子层是多头自注意力机制,允许每个位置关注序列中的所有其他位置;第二个子层是逐位置前馈网络,对每个位置的表示独立地进行非线性变换。两个子层均有残差连接和层归一化。输入序列首先经过词嵌入(Token Embedding)位置编码(Positional Encoding)后送入编码器。

1.2 解码器结构

解码器(Decoder)以编码器的输出和已生成的目标序列为输入,逐步生成输出序列。每个解码器层包含三个子层:第一个子层是掩码多头自注意力,确保在预测当前位置时只能看到之前的位置(因果约束);第二个子层是编码器-解码器注意力,以编码器输出作为Key和Value,以解码器自注意力输出作为Query,实现源序列到目标序列的信息传递;第三个子层是前馈网络。

# Transformer编码器层 PyTorch实现框架 class TransformerEncoderLayer(nn.Module): def __init__(self, d_model, nhead, dim_feedforward, dropout=0.1): super().__init__() # 多头自注意力子层 self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout) # 前馈网络子层 self.feed_forward = nn.Sequential( nn.Linear(d_model, dim_feedforward), nn.ReLU(), nn.Dropout(dropout), nn.Linear(dim_feedforward, d_model) ) # 层归一化 self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) # Dropout self.dropout1 = nn.Dropout(dropout) self.dropout2 = nn.Dropout(dropout) def forward(self, x, mask=None): # 子层1: 多头自注意力 + 残差连接 + LayerNorm attn_out = self.self_attn(x, x, x, attn_mask=mask)[0] x = self.norm1(x + self.dropout1(attn_out)) # 子层2: 前馈网络 + 残差连接 + LayerNorm ff_out = self.feed_forward(x) x = self.norm2(x + self.dropout2(ff_out)) return x

二、自注意力机制 Self-Attention

自注意力机制(Self-Attention)是Transformer最核心的组件。其基本思想是:对于输入序列中的每个元素,通过计算该元素与序列中所有其他元素之间的注意力权重,来聚合全局信息。这种机制使得模型能够动态地关注输入序列中最相关的部分。

2.1 QKV矩阵推导

自注意力机制的核心操作涉及三个矩阵:Query(查询)Key(键)Value(值)。对于输入序列 X ∈ R^(n×d_model),通过三个不同的权重矩阵 W_Q、W_K、W_V 进行线性变换得到 Q、K、V:

Q = X · W_Q W_Q ∈ R^(d_model × d_k) K = X · W_K W_K ∈ R^(d_model × d_k) V = X · W_V W_V ∈ R^(d_model × d_v)

其中 d_k = d_v = d_model / h,h 为注意力头的数量。Query 表示当前位置"想要查找什么",Key 表示其他位置"拥有什么信息",Value 表示其他位置"提供什么信息"。注意力得分通过 Query 与 Key 的点积计算,然后经过 Softmax 归一化得到权重,最后对 Value 进行加权求和。

2.2 缩放点积注意力 Scaled Dot-Product Attention

注意力计算的核心公式为:

Attention(Q, K, V) = softmax(Q · K^T / √d_k) · V

其中 Q·K^T 计算所有位置对之间的相似度得分,除以 √d_k 进行缩放(Scale)以防止点积值过大导致Softmax梯度饱和。缩放因子 √d_k 的选取基于以下考量:当 d_k 较大时,点积的方差也会变大(约为 d_k),导致Softmax函数的输入落入梯度极小的区域。通过除以 √d_k 可以将方差控制为1,保持梯度的稳定性。

缩放因子的数学解释

假设 q 和 k 是均值为0、方差为1的独立随机向量,则点积 q·k = Σ(q_i·k_i) 的均值为0,方差为 d_k。这意味着点积值的标准差为 √d_k。通过除以 √d_k,使得注意力得分的方差恢复为1,避免Softmax进入饱和区。

# 缩放点积注意力的PyTorch实现 import torch import torch.nn as nn import torch.nn.functional as F def scaled_dot_product_attention(Q, K, V, mask=None): """ Q: (batch_size, ..., seq_len_q, d_k) K: (batch_size, ..., seq_len_k, d_k) V: (batch_size, ..., seq_len_k, d_v) mask: (batch_size, ..., seq_len_q, seq_len_k) """ d_k = Q.size(-1) # 计算QK^T / sqrt(d_k) scores = torch.matmul(Q, K.transpose(-2, -1)) / (d_k ** 0.5) # 掩码处理(将需要屏蔽的位置设为负无穷) if mask is not None: scores = scores.masked_fill(mask == 0, float('-inf')) # Softmax归一化得到注意力权重 attn_weights = F.softmax(scores, dim=-1) # 加权求和得到输出 output = torch.matmul(attn_weights, V) return output, attn_weights

2.3 多头注意力 Multi-Head Attention

多头注意力机制(Multi-Head Attention)是自注意力的扩展。它将模型分为 h 个独立的注意力头,每个头在不同的表示子空间中并行地执行缩放点积注意力。这样模型可以从不同的角度关注序列中的不同特征,捕捉更加丰富的关系。

MultiHead(Q, K, V) = Concat(head_1, head_2, ..., head_h) · W_O 其中 head_i = Attention(Q · W_Q_i, K · W_K_i, V · W_V_i)

每个注意力头的维度为 d_k = d_v = d_model / h。各头的输出拼接后通过输出投影矩阵 W_O 映射回 d_model 维度。原论文中 d_model=512,h=8,因此每个头的维度 d_k=64。

# 多头注意力机制的完整PyTorch实现 class MultiHeadAttention(nn.Module): def __init__(self, d_model, nhead, dropout=0.1): super().__init__() assert d_model % nhead == 0 self.d_model = d_model self.nhead = nhead self.d_k = d_model // nhead # 定义QKV线性变换矩阵 self.W_Q = nn.Linear(d_model, d_model) # 整体变换,之后再分割 self.W_K = nn.Linear(d_model, d_model) self.W_V = nn.Linear(d_model, d_model) self.W_O = nn.Linear(d_model, d_model) # 输出投影 self.dropout = nn.Dropout(dropout) def forward(self, Q, K, V, mask=None): batch_size = Q.size(0) # 1. 线性变换并分割成多个头 # Q: (batch, seq_len, d_model) -> (batch, seq_len, nhead, d_k) -> (batch, nhead, seq_len, d_k) Q = self.W_Q(Q).view(batch_size, -1, self.nhead, self.d_k).transpose(1, 2) K = self.W_K(K).view(batch_size, -1, self.nhead, self.d_k).transpose(1, 2) V = self.W_V(V).view(batch_size, -1, self.nhead, self.d_k).transpose(1, 2) # 2. 缩放点积注意力 scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_k ** 0.5) if mask is not None: scores = scores.masked_fill(mask == 0, float('-inf')) attn_weights = F.softmax(scores, dim=-1) attn_weights = self.dropout(attn_weights) # 3. 加权求和 attn_out = torch.matmul(attn_weights, V) # (batch, nhead, seq_len, d_k) # 4. 拼接所有头并投影 attn_out = attn_out.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model) output = self.W_O(attn_out) return output, attn_weights

2.4 注意力头数的选择

注意力头数 h 是Transformer中重要的超参数。原论文中 d_model=512,h=8。实际应用中的常见配置如下:

模型 d_model nhead d_k 参数量(单层)
Transformer Base 512 8 64 ~1.05M
Transformer Big 1024 16 64 ~4.19M
BERT-Base 768 12 64 ~2.36M
BERT-Large 1024 16 64 ~4.19M
GPT-2 768 12 64 ~2.36M
GPT-3 12288 96 128 ~453M

选择 h 的核心原则是保持 d_k = d_model / h 为合理值(通常为64或128)。过小的 d_k 会限制每个头的表达能力,过大的 d_k 会使缩放点积注意力中 Softmax 梯度易于饱和。

三、位置编码 Positional Encoding

由于自注意力机制本身不具备序列顺序感知能力(它对输入序列的排列是置换等变的),必须显式地在输入中注入位置信息。Transformer 原论文提出了正弦余弦位置编码(Sinusoidal Positional Encoding),这是一种不需要学习的确定性编码方式。

PE_(pos, 2i) = sin(pos / 10000^(2i / d_model)) PE_(pos, 2i+1) = cos(pos / 10000^(2i / d_model))

其中 pos 表示位置索引,i 表示维度索引(0 ≤ i < d_model/2)。这种编码方式的精妙之处在于:对于任意固定偏移量 k,PE(pos+k) 可以表示为 PE(pos) 的线性函数,这使得模型能够轻易地学习到相对位置关系。

# 正弦余弦位置编码的PyTorch实现 import torch import math def sinusoidal_positional_encoding(max_len, d_model): """生成正弦余弦位置编码 Args: max_len: 最大序列长度 d_model: 模型维度 Returns: pe: (max_len, d_model) """ pe = torch.zeros(max_len, d_model) # 位置索引: (max_len, 1) position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) # 频率项: 1/10000^(2i/d_model) div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) # 偶数维度使用sin,奇数维度使用cos pe[:, 0::2] = torch.sin(position * div_term) # 2i pe[:, 1::2] = torch.cos(position * div_term) # 2i+1 return pe # 使用示例 max_len = 512 d_model = 512 pe = sinusoidal_positional_encoding(max_len, d_model) # pe[pos] 可直接与token embedding相加

3.1 可学习位置编码

除了正弦余弦编码,可学习位置编码(Learned Positional Encoding)是另一种常见方案。BERT和GPT系列模型均采用此方法。它将位置编码视为可训练的参数矩阵,通过反向传播学习适合特定任务的位置表示。可学习编码的优点是灵活性高,能自适应地学习位置模式,缺点是无法处理超过训练时最大长度的序列。

# 可学习位置编码 class LearnedPositionalEncoding(nn.Module): def __init__(self, max_len, d_model, dropout=0.1): super().__init__() self.dropout = nn.Dropout(dropout) # 位置编码参数: (1, max_len, d_model) self.position_embeddings = nn.Parameter( torch.randn(1, max_len, d_model) ) def forward(self, x): # x: (batch, seq_len, d_model) seq_len = x.size(1) # 添加位置编码 x = x + self.position_embeddings[:, :seq_len, :] return self.dropout(x)

3.2 相对位置编码

绝对位置编码(无论是正弦余弦还是可学习)存在一个局限:它难以捕捉位置之间的相对关系。例如,模型需要理解"单词A在单词B左边第3个位置"这类相对信息。为此,Shaw等人提出了相对位置编码(Relative Positional Encoding),该编码方式在注意力得分计算中引入位置偏差:

score(i, j) = (Q_i · K_j) + (Q_i · a_(i-j)) 其中 a_(i-j) 是位置i和j之间的相对位置嵌入

相对位置编码的关键优势:平移不变性——无论"动词-名词"关系出现在句子的开头还是结尾,相对位置模式都是一致的。Transformer-XL、T5等模型广泛采用了相对位置编码。XLNet和Transformer-XL更进一步,将相对位置信息同时融入注意力得分计算和值映射中。

三种位置编码对比

  • 正弦余弦编码: 无需学习,可外推到任意长度,但表达能力固定
  • 可学习编码: 灵活性强,但无法处理超长序列,需大量数据学习
  • 相对位置编码: 捕获相对位置关系,具有平移不变性,计算复杂度略高

四、前馈神经网络 FFN

Transformer中的逐位置前馈神经网络(Position-wise Feed-Forward Network)对每个位置的表示独立地应用相同的非线性变换。它本质上是一个两层的多层感知机(MLP),包含一个隐藏层扩展和一个输出层压缩:

FFN(x) = W_2 · GELU(W_1 · x + b_1) + b_2

其中 W_1 ∈ R^(d_ff × d_model)、W_2 ∈ R^(d_model × d_ff)。隐藏层维度 d_ff 通常设置为 d_model 的4倍(原论文中 d_model=512,d_ff=2048)。这种"先扩展后压缩"的设计使得模型能够在高维空间中学习更丰富的特征表示。

# 标准FFN实现(GELU激活) class FeedForward(nn.Module): def __init__(self, d_model, d_ff, dropout=0.1): super().__init__() self.linear1 = nn.Linear(d_model, d_ff) self.linear2 = nn.Linear(d_ff, d_model) self.dropout = nn.Dropout(dropout) def forward(self, x): # 激活函数使用GELU x = F.gelu(self.linear1(x)) x = self.dropout(x) x = self.linear2(x) return x # 实例化:d_model=512, d_ff=2048 ffn = FeedForward(d_model=512, d_ff=2048, dropout=0.1)

4.1 激活函数的选择

Transformer原论文使用ReLU激活函数。后续研究发现,GELU(Gaussian Error Linear Unit)在许多任务上表现更好,已成为BERT和GPT系列模型的事实标准。GELU定义为:

GELU(x) = x · Φ(x) = x · 0.5 · [1 + erf(x / √2)]

其中 Φ(x) 是标准高斯分布的累积分布函数。GELU可以看作ReLU的平滑版本,在输入较小时有非零梯度,有利于训练稳定性。

4.2 SwiGLU变体

近年来的研究表明,将FFN中的激活函数替换为SwiGLU(Swish-Gated Linear Unit)可以显著提升模型性能。SwiGLU由Google在PaLM模型中推广,它引入了一个门控机制(Gating Mechanism):

SwiGLU(x) = (Swish(x · W_gate) ⊙ (x · W_up)) · W_down

其中 Swish(x) = x · sigmoid(βx),⊙ 表示逐元素相乘。SwiGLU将单个线性层拆分为门控分支和上投影分支,通过门控机制控制信息流动。需要注意的是,使用SwiGLU时,隐藏层维度通常从 4×d_model 调整为 8/3×d_model 以保持参数量一致(因为SwiGLU有三个权重矩阵而非两个)。LLaMA、GPT-J等现代大语言模型广泛采用了SwiGLU。

# SwiGLU实现 class SwiGLU(nn.Module): def __init__(self, d_model, d_ff, dropout=0.1): super().__init__() # SwiGLU需要三个权重矩阵 self.W_gate = nn.Linear(d_model, d_ff) # 门控分支 self.W_up = nn.Linear(d_model, d_ff) # 上投影分支 self.W_down = nn.Linear(d_ff, d_model) # 下投影 self.dropout = nn.Dropout(dropout) def forward(self, x): gate = F.silu(self.W_gate(x)) # SiLU = Swish(β=1) up = self.W_up(x) x = gate * up # 门控乘法 x = self.dropout(x) x = self.W_down(x) return x # LLaMA风格配置: d_ff = int(8/3 * d_model) llama_ffn = SwiGLU(d_model=4096, d_ff=int(8/3 * 4096)) # ≈ 10923

五、层归一化 Layer Normalization

层归一化(Layer Normalization, LayerNorm)是Transformer中稳定训练的关键技术。与批归一化(Batch Normalization)沿着批次维度计算统计量不同,LayerNorm沿着特征维度计算均值和方差,因此不受批次大小的影响,特别适合序列模型。

LayerNorm(x) = γ · (x - μ) / √(σ² + ε) + β 其中 μ = (1/d) · Σx_i, σ² = (1/d) · Σ(x_i - μ)²

其中 γ 和 β 是可学习的缩放参数和偏移参数,ε 是为数值稳定性添加的小常数。LayerNorm确保每一层的输出具有稳定的均值和方差,缓解了内部协变量偏移问题。

5.1 Pre-Norm vs Post-Norm

LayerNorm在Transformer中的放置位置经历了重要的演进,形成了两种主流方案:

对比维度 Post-Norm(原始Transformer) Pre-Norm(现代主流方案) 计算顺序 残差连接 → 子层 → LayerNorm LayerNorm → 子层 → 残差连接 公式表达 output = LayerNorm(x + Sublayer(x)) output = x + Sublayer(LayerNorm(x)) 训练稳定性 深层训练不稳定,需warmup 深层训练稳定,可无warmup 典型模型 Transformer原论文、BERT GPT系列、LLaMA、ViT 梯度流动 经过LayerNorm反向传播 残差捷径直达,梯度畅通

Post-Norm(原始论文方案):将LayerNorm放在残差连接之后。表达式为 LayerNorm(x + Sublayer(x))。这种方案下,梯度需要流经LayerNorm层,在深层网络中可能导致梯度消失。因此Post-Norm通常需要学习率预热(warmup)和更细致的调参。

Pre-Norm(现代主流方案):将LayerNorm放在子层之前。表达式为 x + Sublayer(LayerNorm(x))。优势在于残差连接提供了无损的梯度捷径,模型可以在没有warmup的情况下直接训练深层网络。当前绝大多数Transformer模型(GPT、LLaMA、ViT等)都采用Pre-Norm方案。

# Post-Norm vs Pre-Norm对比实现 class EncoderLayer_PostNorm(nn.Module): """Post-Norm方案(原始Transformer)""" def __init__(self, d_model, nhead, d_ff, dropout=0.1): super().__init__() self.self_attn = MultiHeadAttention(d_model, nhead, dropout) self.ffn = FeedForward(d_model, d_ff, dropout) self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) self.dropout1 = nn.Dropout(dropout) self.dropout2 = nn.Dropout(dropout) def forward(self, x, mask=None): # Post-Norm: 先残差,后归一化 x = self.norm1(x + self.dropout1(self.self_attn(x, x, x, mask)[0])) x = self.norm2(x + self.dropout2(self.ffn(x))) return x class EncoderLayer_PreNorm(nn.Module): """Pre-Norm方案(现代主流)""" def __init__(self, d_model, nhead, d_ff, dropout=0.1): super().__init__() self.self_attn = MultiHeadAttention(d_model, nhead, dropout) self.ffn = FeedForward(d_model, d_ff, dropout) self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) self.dropout1 = nn.Dropout(dropout) self.dropout2 = nn.Dropout(dropout) def forward(self, x, mask=None): # Pre-Norm: 先归一化,后残差 x = x + self.dropout1(self.self_attn(self.norm1(x), self.norm1(x), self.norm1(x), mask)[0]) x = x + self.dropout2(self.ffn(self.norm2(x))) return x

六、掩码机制 Masking

Transformer中使用不同类型的掩码来约束注意力机制的行为。主要分为两大类:填充掩码(Padding Mask)因果掩码(Causal Mask / Look-Ahead Mask)

6.1 填充掩码 Padding Mask

在批处理中,不等长的序列需要填充(Padding)到相同长度。填充位置的Token不应参与注意力计算,因为它们是人为添加的无意义标记。填充掩码将填充位置的注意力得分设为负无穷(-∞),使得Softmax后对应位置的注意力权重趋近于0。

# 填充掩码生成 def create_padding_mask(seq, pad_token_id=0): """ seq: (batch_size, seq_len) return: (batch_size, 1, 1, seq_len) """ mask = (seq != pad_token_id).float() return mask.unsqueeze(1).unsqueeze(2)

6.2 因果掩码 Causal Mask

因果掩码(也称为前瞻掩码 Look-Ahead Mask)用于解码器的自注意力层,确保在生成位置i的预测时,只能看到位置 < i 的已生成Token,而看不到未来位置的Token。如果不加此掩码,解码器会"作弊"地看到正确答案。

因果掩码是一个上三角矩阵,上三角部分(未来位置)填充为负无穷,下三角部分(当前和历史位置)为0:

# 因果掩码生成 def create_causal_mask(seq_len): """ return: (seq_len, seq_len) 下三角为0(可见),上三角为-inf(不可见) """ mask = torch.triu(torch.ones(seq_len, seq_len) * float('-inf'), diagonal=1) return mask # 示例: seq_len=4 的因果掩码 # tensor([[0., -inf, -inf, -inf], # [0., 0., -inf, -inf], # [0., 0., 0., -inf], # [0., 0., 0., 0.]])

6.3 三种注意力类型的对比

注意力类型 使用位置 Q、K、V来源 掩码类型 作用
Self-Attention
(自注意力)
编码器 Q=K=V=编码器输入 Padding Mask 捕获输入序列内部的长程依赖关系
Masked Self-Attention
(掩码自注意力)
解码器(第一子层) Q=K=V=解码器输入 Causal Mask + Padding Mask 确保自回归生成中只看历史位置
Cross-Attention
(交叉注意力)
解码器(第二子层) Q=解码器输出
K=V=编码器输出
Padding Mask(基于编码器) 将编码器的语义信息传递给解码器

理解Encoder-Decoder Attention

在编码器-解码器注意力中,Query来自解码器的前一子层(掩码自注意力输出),Key和Value来自编码器的最终输出。这样,解码器的每个位置都可以"关注"编码器输出的所有位置,从源序列中获取相关信息。这种设计使得解码器在生成目标序列时,能够参考源序列的完整语义信息。例如,在机器翻译中,解码器生成英文单词时,可以关注源语言句子中的所有单词。

七、Transformer的并行训练优势

Transformer相比RNN的最显著优势在于并行计算能力。RNN的每个时间步依赖前一个时间步的隐状态,必须顺序执行,无法并行。而Transformer的自注意力机制在计算每个位置的表示时,可以同时计算所有位置,充分利用GPU的大规模并行计算能力。

7.1 vs RNN的序贯计算

对比维度 Transformer RNN / LSTM
计算方式 完全并行 顺序串行
时间复杂度 O(n²·d) (可并行化) O(n·d²) (不可并行)
训练速度(n=512) ~快10-100倍 慢(单步依赖)
最大路径长度 O(1) — 任意位置直接相连 O(n) — 需逐时间步传递
长程依赖捕获 优秀(全局注意力) 困难(梯度消失/爆炸)
位置感知 需显式位置编码 天然具备(时间步序贯)
硬件利用率 极高(充分利用GPU矩阵计算) 低(矩阵-向量运算)

7.2 长程依赖捕获

在RNN中,两个位置之间的信息传递路径长度等于它们之间的距离。对于长序列,信息需要经过多个时间步的传递,极易受到梯度消失或梯度爆炸的影响。而在Transformer中,自注意力机制将任意两个位置之间的路径长度缩短为O(1)——无论两个词相距多远,注意力计算都直接建立连接。这种特性使得Transformer在捕获长程依赖关系方面具有天然优势,也是其在文本、代码、DNA序列等长程依赖任务中表现优异的重要原因。

计算复杂度分析

自注意力的时间复杂度为 O(n²·d),其中 n 为序列长度,d 为隐层维度。当 n 较大时,二次复杂度成为瓶颈。针对这一问题,后续研究提出了多种高效注意力变体:

  • 稀疏注意力(Sparse Attention): 限制每个位置只关注局部和少数全局位置(如Longformer、BigBird)
  • 线性注意力(Linear Attention): 将Softmax注意力近似为核函数,复杂度降至 O(n)(如Performer、Linear Transformer)
  • 闪存注意力(Flash Attention): 通过分块计算和内存优化,在不降低精度的情况下加速注意力计算
# 训练速度的直观对比(伪代码示意) import time import torch.nn as nn # RNN: 必须顺序执行 def train_rnn(model, x): # x: (seq_len, batch, d_model) h = torch.zeros(batch, d_model) for t in range(seq_len): h = model.rnn_cell(x[t], h) # 第t步必须等t-1步完成 return h # 总时间 ≈ seq_len × 单步时间 # Transformer: 完全并行 def train_transformer(model, x): # x: (batch, seq_len, d_model) h = model.transformer(x) # 所有位置同时计算 return h # 总时间 ≈ 单次前向传播时间(与seq_len弱相关)

八、完整Transformer模型实现

下面整合前述所有组件,构建一个完整的Transformer模型。

# 完整Transformer模型实现 class Transformer(nn.Module): def __init__(self, vocab_size, d_model=512, nhead=8, num_layers=6, d_ff=2048, max_len=512, dropout=0.1): super().__init__() # 词嵌入 + 位置编码 self.token_embedding = nn.Embedding(vocab_size, d_model) self.pos_encoding = sinusoidal_positional_encoding(max_len, d_model) self.dropout = nn.Dropout(dropout) # 编码器:N层堆叠 self.encoder_layers = nn.ModuleList([ EncoderLayer_PreNorm(d_model, nhead, d_ff, dropout) for _ in range(num_layers) ]) # 解码器:N层堆叠 self.decoder_layers = nn.ModuleList([ DecoderLayer_PreNorm(d_model, nhead, d_ff, dropout) for _ in range(num_layers) ]) # 输出投影 self.output_projection = nn.Linear(d_model, vocab_size) self.d_model = d_model def forward(self, src, tgt, src_mask=None, tgt_mask=None): # src, tgt: (batch, seq_len) # 编码器 src_emb = self.token_embedding(src) * math.sqrt(self.d_model) src_emb = src_emb + self.pos_encoding[:src.size(1), :].to(src.device) src_emb = self.dropout(src_emb) enc_output = src_emb for layer in self.encoder_layers: enc_output = layer(enc_output, src_mask) # 解码器 tgt_emb = self.token_embedding(tgt) * math.sqrt(self.d_model) tgt_emb = tgt_emb + self.pos_encoding[:tgt.size(1), :].to(tgt.device) tgt_emb = self.dropout(tgt_emb) dec_output = tgt_emb for layer in self.decoder_layers: dec_output = layer(dec_output, enc_output, src_mask, tgt_mask) # 输出投影 logits = self.output_projection(dec_output) return logits class DecoderLayer_PreNorm(nn.Module): """解码器层(Pre-Norm方案)""" def __init__(self, d_model, nhead, d_ff, dropout=0.1): super().__init__() self.masked_attn = MultiHeadAttention(d_model, nhead, dropout) self.cross_attn = MultiHeadAttention(d_model, nhead, dropout) self.ffn = FeedForward(d_model, d_ff, dropout) self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) self.norm3 = nn.LayerNorm(d_model) self.dropout1 = nn.Dropout(dropout) self.dropout2 = nn.Dropout(dropout) self.dropout3 = nn.Dropout(dropout) def forward(self, x, enc_output, src_mask=None, tgt_mask=None): # 子层1: 掩码自注意力 x = x + self.dropout1( self.masked_attn(self.norm1(x), self.norm1(x), self.norm1(x), tgt_mask)[0] ) # 子层2: 编码器-解码器交叉注意力 x = x + self.dropout2( self.cross_attn(self.norm2(x), enc_output, enc_output, src_mask)[0] ) # 子层3: 前馈网络 x = x + self.dropout3(self.ffn(self.norm3(x))) return x

九、核心要点总结

十、进一步思考

Transformer架构自2017年提出以来,已经走过了近九年的发展历程。期间涌现了大量改进和变体:从BERT的预训练-微调范式,到GPT系列的大规模自回归语言模型,再到ViT将Transformer应用于计算机视觉,以及最近的混合架构(如Mamba、RWKV等状态空间模型)。Transformer的影响力已经远远超出了NLP领域,成为深度学习中最具影响力的架构之一。

当前研究热点与未来方向

  • 高效注意力: Flash Attention、多查询注意力(MQA)、分组查询注意力(GQA)等降低注意力计算和内存开销的技术
  • 长上下文建模: 通过RoPE、ALiBi、Ring Attention等技术将Transformer的上下文窗口扩展到百万级Token
  • 混合架构: Transformer与状态空间模型(State Space Models)的融合,在保持并行训练优势的同时降低二次复杂度
  • 多模态Transformer: 统一处理文本、图像、视频、音频等多模态数据的Transformer架构
  • 推理优化: 投机解码(Speculative Decoding)、KV-Cache优化等技术提升Transformer推理速度

"We propose a new simple network architecture, the Transformer, based solely on attention mechanisms, dispensing with recurrence and convolutions entirely." — Vaswani et al., 2017

理解Transformer的原理是掌握现代深度学习的基石。从注意力机制的数学推导到实际代码实现,从Encoder-Decoder的完整结构到各种改进变体,每一步都体现了深度学习研究者的智慧。随着大语言模型的持续演进,Transformer架构也在不断进化,但其核心思想——注意力就是一切——始终没有改变。