ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Transformer Block核心原理与PyTorch实现:从注意力机制到代码实战

2026/8/10 15:33:49 拓冰建站 浏览量
Transformer Block核心原理与PyTorch实现:从注意力机制到代码实战 1. 项目概述从“黑盒”到“白盒”的Transformer Block拆解之旅如果你已经对CNN、RNN这些经典神经网络结构有了基本了解甚至动手实现过几个简单的分类或序列预测模型那么当你第一次听说Transformer时大概率会感到一种“降维打击”般的震撼。它不像CNN那样依赖局部感受野的滑动也不像RNN那样受制于序列的串行计算。2017年那篇著名的《Attention Is All You Need》论文用一个名为Transformer的架构彻底改变了自然语言处理乃至整个深度学习领域的游戏规则。但说实话对于很多学习者尤其是从经典模型过渡过来的朋友初次接触Transformer时很容易被其复杂的结构图吓退——多头注意力、前馈网络、残差连接、层归一化各种模块堆叠在一起像个精密但难以理解的“黑盒”。今天我们不谈整个庞大的Transformer模型而是聚焦于其最核心、最可复用的基本单元Transformer Block。你可以把它想象成乐高积木中最基础的那一块。无论是用于机器翻译的Encoder-Decoder架构还是后来横扫各大榜单的BERT、GPT等预训练模型其内部都是由多个这样的Block堆叠而成。理解了一个Block你就掌握了Transformer这座大厦的基石。本文将带你深入这个“黑盒”内部逐层拆解从数学公式到代码实现从设计动机到实操细节目标是让你不仅能看懂论文里的结构图更能亲手搭建并理解每一个运算背后的“为什么”。无论你是希望夯实理论基础的学生还是准备在项目中应用Transformer的工程师这篇针对单个Block的深度解析都将是你进阶路上关键的一步。2. Transformer Block的整体设计与核心思路在深入细节之前我们首先要建立对Transformer Block的宏观认知。它的设计哲学深刻体现了深度学习近年来的一些核心思想并行化、模块化和深层网络优化。2.1 为什么是“Attention Is All You Need”在Transformer出现之前处理序列数据尤其是长序列的王者是RNN及其变体LSTM、GRU。它们通过循环机制传递隐藏状态理论上能处理任意长度的序列。但问题也很明显序列计算的顺序依赖性导致了极差的并行能力。你必须等第t-1步算完才能算第t步这在GPU时代是巨大的性能瓶颈。其次RNN在传递长程依赖时容易遭遇梯度消失或爆炸问题尽管LSTM有所缓解但并未根除。Transformer的答案非常激进完全抛弃循环仅依赖注意力机制来建立序列中任意两个位置之间的联系。注意力机制的核心是“查询-键-值”Query-Key-Value模型它允许模型在计算某个位置的表示时“注意”到序列中所有其他位置的信息并且这种注意力的计算是完全可并行的因为对于序列中所有位置的查询、键、值矩阵运算可以同时进行。一个Transformer Block就是实现这种强大注意力机制并使其稳定、可训练的一个完整封装。它的标准结构通常包含两个主要子层每个子层都包裹着相同的“配方”子层计算 - 残差连接 - 层归一化。2.2 Block的经典结构一个双层的“三明治”一个最经典的Transformer Block通常指Transformer论文中的Encoder Block结构如下我们可以将其视为一个精心设计的“三明治”第一层注意力层核心多头自注意力机制Multi-Head Self-Attention MHSA。包装LayerNorm(x SubLayer(x))即残差连接Add和层归一化Norm。论文中称之为“Add Norm”。第二层前馈层核心位置式前馈网络Position-wise Feed-Forward Network FFN。包装同样的LayerNorm(x SubLayer(x))结构。用公式可以简洁地表示为设输入为 x 子层1输出z LayerNorm(x MultiHeadAttention(x)) 子层2输出output LayerNorm(z FeedForward(z))这个“子层 - Add - Norm”的排列顺序是原始论文的做法。需要注意的是在一些后续的知名实现如Tensor2Tensor库、以及许多讲解中和模型如GPT里采用了“Pre-Norm”的结构即x SubLayer(LayerNorm(x))。这种结构通常被认为在训练非常深的网络时更稳定。本文将以原始论文的“Post-Norm”Add Norm在子层后结构为主进行讲解因为它是理解设计初衷的起点同时也会对比说明Pre-Norm的区别与影响。注意这里的“层归一化”LayerNorm和“残差连接”Add是确保深层网络能够成功训练的关键技术。残差连接使得梯度可以直接回流缓解了梯度消失层归一化对单个样本的所有特征维度进行归一化稳定了每一层的输入分布加速了训练收敛。这两者与注意力机制的结合是Transformer能够堆叠数十甚至上百层的基础。3. 核心细节一多头自注意力机制MHSA深度解析这是Transformer Block的灵魂也是最需要吃透的部分。我们将其拆解为几个步骤。3.1 自注意力Self-Attention的基本计算自注意力的目的是为序列中的每个单词或更一般地每个“token”计算一个新的表示这个表示是所有其他token表示的加权和。权重由token之间的相关性注意力分数决定。第一步生成Q, K, V假设输入是一个序列的表示矩阵X形状为[序列长度, 模型维度]。我们通过三个不同的线性变换权重矩阵W^Q,W^K,W^V将其投影到三个空间Q X * W^Q # Query K X * W^K # Key V X * W^V # ValueQ,K,V的形状通常与X相同。这里的“自”体现在Q, K, V都来源于同一个输入X。第二步计算注意力分数Attention Scores注意力分数衡量了查询Query和键Key的匹配程度。最常用的方法是缩放点积注意力Scaled Dot-Product Attention注意力分数 softmax( (Q * K^T) / sqrt(d_k) )Q * K^T计算查询和所有键的点积得到一个[序列长度, 序列长度]的矩阵。位置(i, j)的值表示第i个查询与第j个键的关联度。sqrt(d_k)缩放因子。d_k是键向量的维度。点积的结果会随着维度的增大而增大经过softmax后梯度会变得非常小。除以sqrt(d_k)可以稳定梯度是论文中的一个重要技巧。softmax对每一行对应一个查询进行归一化使得该查询对所有键的注意力权重之和为1。第三步加权求和得到输出将上一步得到的注意力权重矩阵与值Value矩阵V相乘输出 注意力分数 * V最终输出的形状与V相同。对于序列中的每个位置其新的表示都是所有位置原始值的加权和权重由该位置与所有位置的关联度决定。3.2 为什么要“多头”Multi-Head单头注意力已经很强大了但它只进行一次“全局关联”的学习。类比一下人在理解一句话时可能会同时关注其语法结构、语义重点、情感色彩等多个方面。多头注意力机制模拟了这一过程。操作流程将模型维度d_model平均分成h个头head。例如d_model512,h8则每个头的维度d_k d_v d_model / h 64。对每个头i使用不同的线性变换矩阵W_i^Q, W_i^K, W_i^V将输入X投影到对应的Q_i, K_i, V_i。这意味着每个头学习不同的投影子空间从而可以关注输入信息的不同侧面。每个头独立进行上一节描述的缩放点积注意力计算得到h个输出矩阵head_i形状为[序列长度, d_v]。将h个头的输出在特征维度上拼接Concat起来形状变回[序列长度, d_model]。最后通过一个线性变换W^O进行融合和投影得到多头注意力的最终输出。公式表示MultiHead(Q, K, V) Concat(head_1, ..., head_h) * W^O 其中 head_i Attention(Q * W_i^Q, K * W_i^K, V * W_i^V)多头机制的好处是显而易见的它扩展了模型在不同位置共同关注来自不同表示子空间信息的能力。单一注意力头可能会被当前词本身或非常近的词主导而多头则允许模型同时关注序列中不同距离、不同方面的依赖关系。3.3 注意力机制中的掩码Masking在自注意力中序列中的每个位置都能看到整个序列这在Encoder中是合理的。但在Decoder中为了保持自回归特性预测下一个词时只能看到前面的词需要引入掩码。解码器掩码Decoder Mask通常是一个上三角矩阵对角线及以下为0以上为负无穷-inf。在计算Q*K^T后加上这个掩码矩阵再经过softmax负无穷的位置会变成0。这样就确保了第i个位置只能关注到1到i的位置实现了单向注意力。填充掩码Padding Mask在处理变长序列时我们会用特殊符号如[PAD]将批次内的序列填充到相同长度。在计算注意力时需要忽略这些填充位置。具体做法是在注意力分数矩阵中将对应[PAD]位置的值设为一个很大的负数如-1e9这样在softmax后其权重就接近于0。实操心得在实现时尤其是使用深度学习框架如PyTorch时掩码的处理需要格外小心。PyTorch的nn.MultiheadAttention模块提供了attn_mask和key_padding_mask参数来分别处理这两种掩码。理解它们的区别和用法是正确实现Transformer Decoder的关键。一个常见的坑是混淆了两种掩码的维度和作用时机。4. 核心细节二前馈网络FFN与残差归一化经过注意力层后数据已经融合了全局信息。前馈网络的作用是对每个位置的表示进行独立的、非线性的变换和增强。4.1 位置式前馈网络Position-wise FFN“位置式”意味着这个前馈网络独立地应用于序列中的每一个位置。对于形状为[序列长度, d_model]的输入FFN对每一行即每个位置的向量进行相同的操作。它的结构非常简单就是一个两层神经网络中间包含一个非线性激活函数FFN(x) max(0, x * W_1 b_1) * W_2 b_2W_1形状为[d_model, d_ff]将维度从d_model映射到一个更大的中间维度d_ff通常为4*d_model。ReLU最常用的激活函数提供非线性。有些变体使用GELU效果可能更好。W_2形状为[d_ff, d_model]将维度投影回d_model以便与残差连接相加。为什么需要这个“扩大再缩小”的过程可以将其理解为一个“特征处理器”。注意力机制负责融合信息而FFN负责基于融合后的信息在每个位置上进行更复杂的特征变换和抽象增加模型的表达能力。4.2 残差连接Add与层归一化Norm的协同这是保证Transformer能够堆叠得很深的关键技术组合。残差连接Residual Connection 其操作就是简单的加法输出 子层输出 输入。作用它创建了一条从输入直接到输出的“捷径”shortcut。在反向传播时梯度可以通过这条捷径直接回流极大地缓解了深层网络中的梯度消失问题。这使得训练数十层的网络成为可能。直观理解它让每一层的学习目标变成了“学习输入与期望输出之间的残差差值”这通常比直接学习完整的映射要容易得多。层归一化Layer Normalization 与批归一化BatchNorm对一批数据中每个特征通道进行归一化不同层归一化是对单个样本的所有特征维度进行归一化。对于一个形状为[序列长度, d_model]的输入LayerNorm 对每一行即每个token的向量独立计算均值和方差然后进行标准化和缩放平移。# 对于输入向量 x (形状为 [d_model]) mean mean(x) # 计算该向量的均值 var variance(x) # 计算方差 x_norm (x - mean) / sqrt(var eps) # 标准化eps为防止除零的小常数 output gamma * x_norm beta # 可学习的缩放和平移参数作用稳定每一层网络的输入分布减少内部协变量偏移Internal Covariate Shift从而加速训练收敛并允许使用更大的学习率。与残差连接的顺序原始论文采用“Post-Norm”LayerNorm(x Sublayer(x))。后来“Pre-Norm”x Sublayer(LayerNorm(x))变得流行。Pre-Norm将归一化置于子层计算之前相当于让每个子层的输入都是归一化的在实践中通常能让训练更稳定尤其对于极深的模型。但Post-Norm在理论上可能具有更强的表达能力。选择哪种需要根据具体任务和模型深度权衡。注意事项在实现时Add Norm通常被实现为一个独立的模块。确保加法操作的两个张量形状完全相同。LayerNorm的eps参数通常设置为1e-5或1e-6。对于gamma和beta的初始化通常gamma初始化为1beta初始化为0。5. Transformer Block的完整实现与代码剖析理论讲得再多不如一行代码。下面我们将用PyTorch实现一个完整的、标准的Transformer Encoder Block。我们将遵循模块化设计并加上详细的注释。5.1 构建缩放点积注意力模块这是最基础的积木。import torch import torch.nn as nn import torch.nn.functional as F import math class ScaledDotProductAttention(nn.Module): 缩放点积注意力 def __init__(self, dropout0.1): super().__init__() self.dropout nn.Dropout(dropout) def forward(self, q, k, v, maskNone): # q, k, v 形状: [batch_size, seq_len, d_k] 或 [batch_size, num_heads, seq_len, d_k] # mask 形状: [batch_size, 1, 1, seq_len] (key_padding_mask) 或 [1, seq_len, seq_len] (attn_mask) d_k k.size(-1) # 获取键向量的维度 # 计算注意力分数: [..., seq_len_q, seq_len_k] scores torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k) # 应用掩码如果提供 if mask is not None: # 将mask中为True的位置需要被掩盖替换为一个极小的负数 scores scores.masked_fill(mask 0, -1e9) # 对最后一维key的维度进行softmax得到注意力权重 attn_weights F.softmax(scores, dim-1) # 应用Dropout一种正则化在训练时随机丢弃一部分注意力权重 attn_weights self.dropout(attn_weights) # 加权求和得到输出: [..., seq_len_q, d_v] output torch.matmul(attn_weights, v) return output, attn_weights # 返回输出和注意力权重可用于可视化5.2 构建多头注意力模块将多个“头”组装起来。class MultiHeadAttention(nn.Module): 多头自注意力机制 def __init__(self, d_model, num_heads, dropout0.1): super().__init__() assert d_model % num_heads 0, d_model 必须能被 num_heads 整除 self.d_model d_model self.num_heads num_heads self.d_k d_model // num_heads # 每个头的维度 # 定义四个线性变换层W^Q, W^K, W^V, W^O 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.attention ScaledDotProductAttention(dropout) self.dropout nn.Dropout(dropout) self.layer_norm nn.LayerNorm(d_model) # 这里先定义但注意原始论文顺序 def forward(self, query, key, value, maskNone): batch_size query.size(0) # 1. 线性投影并分头 # 线性变换后形状: [batch_size, seq_len, d_model] Q self.w_q(query) K self.w_k(key) V self.w_v(value) # 分头: 将 d_model 维度拆分为 num_heads * d_k # 重塑形状为: [batch_size, seq_len, num_heads, d_k] # 然后转置为: [batch_size, num_heads, seq_len, d_k] 以便并行计算每个头 Q Q.view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) K K.view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) V V.view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) # 2. 应用缩放点积注意力每个头独立计算 # 如果mask不为None需要扩展维度以匹配多头 [batch_size, 1, 1, seq_len] - [batch_size, num_heads, seq_len, seq_len] if mask is not None: mask mask.unsqueeze(1) # 为head维度添加一个维度便于广播 x, attn_weights self.attention(Q, K, V, maskmask) # x形状: [batch_size, num_heads, seq_len_q, d_k] # 3. 合并多头 # 转置回来: [batch_size, seq_len_q, num_heads, d_k] x x.transpose(1, 2).contiguous() # 合并: [batch_size, seq_len_q, d_model] x x.view(batch_size, -1, self.d_model) # 4. 最终线性投影 W^O output self.w_o(x) return output, attn_weights5.3 构建前馈网络模块class PositionwiseFeedForward(nn.Module): 位置式前馈网络 def __init__(self, d_model, d_ff, dropout0.1): super().__init__() self.linear1 nn.Linear(d_model, d_ff) # 扩展维度 self.linear2 nn.Linear(d_ff, d_model) # 投影回原维度 self.dropout nn.Dropout(dropout) self.activation nn.GELU() # 原始论文用ReLU这里用更流行的GELU def forward(self, x): # x形状: [batch_size, seq_len, d_model] return self.linear2(self.dropout(self.activation(self.linear1(x))))5.4 组装完整的Transformer Encoder Block现在我们将所有部件组装起来采用原始论文的Post-Norm结构。class TransformerEncoderBlock(nn.Module): Transformer编码器块 (Post-Norm结构) def __init__(self, d_model, num_heads, d_ff, dropout0.1): super().__init__() self.self_attn MultiHeadAttention(d_model, num_heads, dropout) self.feed_forward PositionwiseFeedForward(d_model, d_ff, dropout) # 两个Add Norm层 self.norm1 nn.LayerNorm(d_model) self.norm2 nn.LayerNorm(d_model) self.dropout nn.Dropout(dropout) def forward(self, src, src_maskNone): src: 输入序列 [batch_size, src_len, d_model] src_mask: 源序列掩码 [batch_size, 1, src_len] (用于padding mask) # 第一个子层多头自注意力 Add Norm attn_output, _ self.self_attn(src, src, src, masksrc_mask) # 自注意力Q,K,V均来自src src self.norm1(src self.dropout(attn_output)) # Post-Norm: Norm(x Sublayer(x)) # 第二个子层前馈网络 Add Norm ff_output self.feed_forward(src) output self.norm2(src self.dropout(ff_output)) return output如果要实现Pre-Norm结构只需要调整forward函数中的顺序# Pre-Norm 结构示例 def forward(self, src, src_maskNone): # 第一个子层 src_norm self.norm1(src) # 先Norm attn_output, _ self.self_attn(src_norm, src_norm, src_norm, masksrc_mask) src src self.dropout(attn_output) # 后Add # 第二个子层 src_norm self.norm2(src) ff_output self.feed_forward(src_norm) output src self.dropout(ff_output) return output实操心得在编写代码时张量形状的变换view, transpose, permute是最容易出错的地方。务必在每一步都打印或调试确认张量的形状。特别是分头和合并头的操作transpose和contiguous()的配合使用需要理解其内存布局的影响。另外mask的维度和类型bool还是float需要与你使用的框架和注意力计算函数匹配。6. 关键参数解析与配置经验一个Transformer Block的性能和效率很大程度上取决于其超参数的设置。理解这些参数的意义和常见的配置经验至关重要。6.1 核心维度参数d_model(模型维度)这是Block输入和输出的特征维度也是整个Transformer模型的基准宽度。它决定了每个位置表示向量的“容量”。常见值512原始论文、768BERT-base、1024BERT-large、1280GPT-3部分层。影响更大的d_model通常意味着更强的表示能力但也会显著增加计算量和参数量特别是注意力机制中的QKV投影矩阵参数量与d_model的平方成正比。num_heads(注意力头数)多头注意力的头数。常见值通常设置为8或16。一个经验法则是d_model必须能被num_heads整除。影响更多的头可以让模型同时关注更多样化的信息模式。但并非头数越多越好当d_model固定时增加头数意味着每个头的维度d_k变小可能会限制每个头的表达能力。通常d_kd_model/num_heads不小于64。d_ff(前馈网络中间维度)FFN层第一个线性变换的输出维度。常见值通常是d_model的4倍即4 * d_model。这是原始论文的设置被广泛沿用。影响决定了FFN层的非线性变换能力。增大它可以提升模型容量但也会增加参数量和计算量FFN的参数量主要来自W_1其大小为d_model * d_ff。6.2 正则化与稳定化参数dropout(丢弃率)在注意力权重和FFN层激活后应用的随机丢弃比例。常见值0.1原始论文范围通常在0.1到0.3之间。作用防止过拟合。在注意力中应用Dropout可以随机“忽略”一些注意力连接在FFN中应用可以随机“关闭”一些神经元增强模型的泛化能力。layer_norm_eps(层归一化epsilon)LayerNorm中分母方差项添加的小常数防止除零。常见值1e-5或1e-6。注意这个值虽然小但在混合精度训练如FP16时如果设置得太小可能会导致数值不稳定。6.3 初始化策略参数的初始化对训练深度Transformer模型至关重要。常见的策略包括线性层权重通常使用Xavier均匀初始化或KaimingHe初始化。在Transformer的原始实现中作者使用了sqrt(d_model)为方差的正态分布初始化。线性层偏置初始化为0。LayerNorm的gamma和betagamma缩放参数初始化为1beta平移参数初始化为0。这保证了初始化时LayerNorm是一个恒等变换。配置经验对于大多数NLP任务从BERT-base的配置d_model768, num_heads12, d_ff3072开始微调是一个安全且有效的起点。如果你想从头开始训练一个较小的模型可以考虑d_model512, num_heads8, d_ff2048。一个重要的原则是保持d_kd_model/num_heads在一个合理的范围如64-128这被认为是注意力机制有效工作的关键。7. 常见问题、调试技巧与性能优化在实际实现和训练Transformer Block时你会遇到各种各样的问题。下面记录了一些典型问题和解决思路。7.1 训练不稳定与梯度问题问题表现损失出现NaNNot a Number或者训练初期梯度爆炸。检查初始化确认所有参数特别是线性层和LayerNorm已正确初始化。可以尝试更保守的初始化如缩小初始化权重的标准差。检查LayerNorm的eps在混合精度训练中尝试将eps从1e-12增大到1e-5或1e-6。梯度裁剪Gradient Clipping这是训练Transformer的标配。在反向传播后、优化器更新前对梯度向量的全局范数进行裁剪例如设置最大范数为1.0或5.0防止梯度爆炸。torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)学习率预热Learning Rate Warmup在训练开始时使用一个非常小的学习率然后线性或余弦增加到预设值。这给了模型一个“热身”阶段让参数稳定下来。对于Transformer预热几百到几千步非常常见。尝试Pre-Norm如果使用Post-Norm遇到严重的不稳定切换到Pre-Norm结构往往能立刻改善情况。7.2 注意力权重可视化与解释理解模型在“看”哪里是调试和解释模型行为的重要手段。获取权重在我们实现的ScaledDotProductAttention.forward中我们返回了attn_weights。对于多头注意力这个张量的形状是[batch_size, num_heads, seq_len_q, seq_len_k]。可视化可以选择一个样本batch_idx0一个特定的头head_idx用热力图heatmap画出attn_weights[0, head_idx]这个矩阵。你会看到模型在处理不同词语时各个注意力头关注的重点区域。例如有些头可能关注下一个词有些头可能关注句法相关的词有些头可能关注语义相似的词。7.3 计算效率与内存优化Transformer Block尤其是注意力机制在长序列上计算和内存开销巨大复杂度为O(序列长度^2)。Flash Attention如果使用PyTorch 2.x及以上版本可以利用内置的优化注意力实现torch.nn.functional.scaled_dot_product_attention。它经过高度优化通常比手动实现的注意力更快、更省内存。# 替代自定义的注意力计算 attn_output F.scaled_dot_product_attention(q, k, v, attn_maskmask, dropout_pself.dropout_p)检查点技术Gradient Checkpointing对于极深的模型可以通过在反向传播时重新计算前向传播的中间结果来节省显存以时间换空间。PyTorch提供了torch.utils.checkpoint模块。混合精度训练AMP使用Automatic Mixed Precision自动混合精度训练将部分计算转换为FP16可以显著减少显存占用并加速训练。但需注意数值稳定性适当调整layer_norm_eps和损失缩放loss scaling。7.4 一个简单的测试脚本在完成Block编写后务必用一个简单的例子进行前向传播测试确保形状匹配没有错误。def test_transformer_block(): batch_size 4 seq_len 10 d_model 512 num_heads 8 d_ff 2048 # 创建模型和随机输入 block TransformerEncoderBlock(d_model, num_heads, d_ff) x torch.randn(batch_size, seq_len, d_model) # 创建一个模拟的padding mask假设后3个位置是padding mask torch.ones(batch_size, 1, seq_len) mask[:, :, -3:] 0 # 将最后3个位置置为0需要被掩盖 print(f输入形状: {x.shape}) print(f掩码形状: {mask.shape}) # 前向传播 output block(x, src_maskmask) print(f输出形状: {output.shape}) print(测试通过输出形状与输入一致。) # 检查参数数量 total_params sum(p.numel() for p in block.parameters() if p.requires_grad) print(fBlock总参数量: {total_params:,}) if __name__ __main__: test_transformer_block()运行这个脚本你应该看到输入和输出的形状都是[4, 10, 512]并且得到一个具体的参数量。这能快速验证你的实现基本正确。8. 从Block到模型扩展与应用思考理解单个Transformer Block是第一步它的真正威力在于堆叠和组合。编码器Encoder将N个完全相同的Encoder Block堆叠起来就构成了Transformer的编码器。每个Block接收上一层的输出逐层提取和抽象特征。BERT就是一个典型的深度Transformer编码器堆叠。解码器Decoder解码器Block比编码器Block多了一个“编码器-解码器注意力”子层又称交叉注意力。它接收两个输入来自上一解码层的输出作为Q和编码器的最终输出作为K, V。其结构通常是自注意力带掩码 - Add Norm - 交叉注意力 - Add Norm - FFN - Add Norm。GPT系列模型就是堆叠的解码器Block只使用掩码自注意力没有交叉注意力。变体与改进围绕Transformer Block的改进层出不穷。例如Swin Transformer引入了窗口注意力Window Attention和移位窗口Shifted Window来降低视觉任务中长序列的计算复杂度Reformer引入了局部敏感哈希LSH注意力来近似全局注意力降低复杂度Performer使用线性注意力来替代softmax注意力。这些改进的核心大多是在注意力机制或FFN上做文章但其模块化Block的设计思想一脉相承。亲手实现并理解了这个基础的Transformer Block你就拥有了打开现代深度学习特别是大语言模型LLM世界大门的钥匙。后续无论遇到多么复杂的Transformer变体你都可以尝试将其拆解看看它在这个基本Block之上做了哪些修改和优化。这种自底向上的理解方式远比死记硬背模型结构要扎实和有用得多。