
TransformerAI时代的基石伟大的架构革命【NLP系列第四篇】1. 从RNN注意力到只有注意力前三篇我们走了一条很清晰的路线词向量 → RNN/LSTM/GRU → Seq2Seq 注意力机制。上一篇讲到注意力机制时它还是寄生在 RNN 上的——用 RNN 算 Q 和 KV注意力只是给 Decoder 加了个探照灯。但 2017 年 Google 的一篇论文直接掀了桌子Attention Is All You Need。意思是不需要 RNN 了注意力自己就能干所有事。这就是 Transformer。它的核心思想就一句话彻底抛弃循环结构完全基于注意力机制实现全并行计算。这篇博客会带着你把 Transformer 的架构从头拆到尾位置编码、多头注意力、残差连接、层归一化、掩码机制……每个零件都讲清楚为什么需要、怎么工作最后用 PyTorch 把它跑起来。2. 核心概念回顾自注意力与多头注意力在上一篇中我们已经详细讲过自注意力这里快速回顾几个关键点。2.1 缩放点积注意力自注意力的计算公式A t t e n t i o n ( Q , K , V ) softmax ( Q K T d k ) V Attention(Q, K, V) \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)VAttention(Q,K,V)softmax(dkQKT)V几个要点QQuery当前位置的需求想找什么KKey各个位置的索引有什么信息可以提供VValue各个位置的内容具体提供什么信息除以d k \sqrt{d_k}dk维度越高点积值越大softmax 后梯度越容易消失缩放着保证数值稳定矩阵形式一次算出所有位置两两之间的注意力这是 Transformer 能全并行的根本原因。2.2 多头注意力单头自注意力有一个局限所有注意力共享同一组 QKV 投影。但一句话里往往同时包含多种语义关系——句法、词义、指代等。多头注意力就是把 QKV 投影到多个子空间多个头每个头独立计算注意力最后拼接起来某些头关注句法依赖主谓宾关系某些头关注共指关系“it指向animal”某些头关注长距离语义关联多头注意力的参数量和单头一样总维度不变但表达能力更强。3. Transformer 架构逐层解剖3.1 总体结构Transformer 延续了 Seq2Seq 的 Encoder-Decoder 架构编码器Encoder负责理解输入序列解码器Decoder负责生成目标序列。标准配置是6 层 Encoder 6 层 Decoder每层结构相同但参数独立。层数越多模型能提取的语义越深。3.2 Encoder 层拆解每个 Encoder 层由四个核心组件构成位置编码自注意力本身是无序的——我爱你和你爱我对它来说是一样的。所以 Transformer 需要额外注入位置信息。原始 Transformer 使用正弦余弦位置编码P E ( p o s , 2 i ) sin ( p o s 10000 2 i / d m o d e l ) PE(pos, 2i) \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right)PE(pos,2i)sin(100002i/dmodelpos)P E ( p o s , 2 i 1 ) cos ( p o s 10000 2 i / d m o d e l ) PE(pos, 2i1) \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right)PE(pos,2i1)cos(100002i/dmodelpos)其中p o s pospos是位置i ii是维度索引。每个位置得到一个唯一的编码向量加到词 Embedding 上。多头自注意力每个 token 关注序列中所有其他 token包括自己通过注意力权重聚合上下文信息。这是 Encoder 理解序列语义的核心。残差连接 LayerNorm残差连接子层的输入和输出相加形成一条高速公路y x SubLayer ( x ) y x \text{SubLayer}(x)yxSubLayer(x)反向传播时梯度可以直接跳过子层回传解决深层网络的梯度消失问题。层归一化LayerNorm对每个 token 的特征做标准化均值为 0方差为 1加速收敛。和 BatchNorm 的区别在于——BatchNorm 跨样本归一化LayerNorm 跨特征维度归一化更适合变长序列。Post-LN vs Pre-LN原始 Transformer 用的是 Post-LN先计算再归一化但实践中 Pre-LN先归一化再计算训练更稳定GPT 系列用的就是 Pre-LN。前馈神经网络FFN对每个位置的表示做逐位置、非线性变换提升表达能力FFN ( x ) Linear ( ReLU ( Linear ( x ) ) ) \text{FFN}(x) \text{Linear}( \text{ReLU}( \text{Linear}(x) ) )FFN(x)Linear(ReLU(Linear(x)))两层线性变换中间夹一个 ReLU 激活输入输出维度相同d m o d e l 512 d_{model}512dmodel512中间隐藏层维度更大d f f 2048 d_{ff}2048dff2048。Encoder 层的完整流程输入x xx→ 位置编码 → 多头自注意力 → 残差连接 LayerNorm → FFN → 残差连接 LayerNorm → 输出3.3 Decoder 层拆解Decoder 比 Encoder 多了一个交叉注意力层而且要加掩码Masked 自注意力Decoder 生成时当前位置不能看到未来的词——否则就是作弊。实现方式是在自注意力分数矩阵上用一个下三角掩码把未来位置设为− ∞ -\infty−∞softmax 后权重就是 0。举个例子生成第 3 个词时只能看到第 1、2 个词和自己不能看第 4、5 个# 下三角掩码seq_len5这里1代表能看也可以用上三角也就是1代表遮挡不能看 [ 1 0 0 0 0 ← 位置 1 只能看自己 1 1 0 0 0 ← 位置 2 能看 1、2 1 1 1 0 0 ← 位置 3 能看 1、2、3 1 1 1 1 0 ← 位置 4 能看 1~4 1 1 1 1 1 ] ← 位置 5 能看全部交叉注意力Encoder-Decoder Attention这就是上一篇讲的注意力机制——Q 来自 Decoder 当前步的隐状态KV 来自 Encoder 的输出。作用是从源句中找出当前位置最该关注的信息。前馈神经网络和 Encoder 中的 FFN 完全一样。Decoder 层的完整流程输入y yy→ 位置编码 →Masked 自注意力只能看过去→ 残差 LayerNorm →交叉注意力Q 来自 DecoderKV 来自 Encoder→ 残差 LayerNorm → FFN → 残差 LayerNorm → 输出3.4 训练 vs 推理这是面试高频考点也是新手最容易搞混的地方。训练阶段使用Teacher Forcing——把完整目标序列一次性喂给 Decoder通过掩码保证因果顺序。所有位置同时计算充分利用 GPU 并行能力。# 训练时一次性输入并行计算outputtransformer(src_emb,tgt_emb,tgt_masktgt_mask)推理阶段自回归生成必须逐个词生成。每一步输入已生成的全部词取最后一个位置的输出作为当前步的预测拼到输入中重复直到生成eos。# 推理时循环生成foriinrange(max_len):outputtransformer(src_emb,generated_seq)next_tokenoutput[:,-1,:]# 取最后一个位置generated_seqconcat(generated_seq,next_token)训练能并行推理不能并行——这是自回归生成模型的天然限制。4. 代码实战用 PyTorch 实现 TransformerPyTorch 提供了完整的nn.Transformer模块下面演示从 token ids 到前向输出的完整流程。4.1 使用 nn.Transformer 官方 APIimporttorchimporttorch.nnasnn# 超参数 d_model512# 模型特征维度nhead8# 多头注意力头数num_encoder_layers6# Encoder 层数num_decoder_layers6# Decoder 层数dim_feedforward2048# FFN 隐藏层维度dropout0.1# Dropout 概率max_len100# 最大序列长度src_vocab_size10000# 源语言词表大小tgt_vocab_size10000# 目标语言词表大小batch_size2src_len10# 源句长度tgt_len8# 目标句长度# 1. 构建 Transformer transformernn.Transformer(d_modeld_model,nheadnhead,num_encoder_layersnum_encoder_layers,num_decoder_layersnum_decoder_layers,dim_feedforwarddim_feedforward,dropoutdropout,activationrelu,batch_firstTrue,# ⭐ 输入形状: (batch, seq_len, d_model)norm_firstFalse,# FalsePost-LN, TruePre-LN)# 2. 词嵌入层 src_embednn.Embedding(src_vocab_size,d_model)tgt_embednn.Embedding(tgt_vocab_size,d_model)# 3. 模拟数据随机生成 token ids srctorch.randint(0,src_vocab_size,(batch_size,src_len))# (2, 10)tgttorch.randint(0,tgt_vocab_size,(batch_size,tgt_len))# (2, 8)# 4. Embedding实验中忽略位置编码实际需要加上src_embsrc_embed(src)# (2, 10, 512)tgt_embtgt_embed(tgt)# (2, 8, 512)# 5. ⭐ 生成因果掩码Decoder 用防止看到未来词# generate_square_subsequent_mask 生成下三角掩码矩阵tgt_masknn.Transformer.generate_square_subsequent_mask(tgt_len)# tgt_mask shape: (8, 8) — 下三角为 0上三角为 -inf# 6. 前向传播 outputtransformer(src_emb,# 源序列 embeddingtgt_emb,# 目标序列 embeddingtgt_masktgt_mask,# 因果掩码)print(f源序列:{src.shape})# torch.Size([2, 10])print(f目标序列:{tgt.shape})# torch.Size([2, 8])print(f输出:{output.shape})# torch.Size([2, 8, 512])# output 的最后一维通过线性层 softmax 映射到词表大小得到每个位置的预测词4.2 手写简化版自注意力如果想理解底层原理这是自注意力最简实现importtorchimporttorch.nnasnnimporttorch.nn.functionalasFclassSelfAttention(nn.Module):def__init__(self,embed_dim):super().__init__()## Q、K、V 三个投影矩阵self.W_qnn.Linear(embed_dim,embed_dim)self.W_knn.Linear(embed_dim,embed_dim)self.W_vnn.Linear(embed_dim,embed_dim)defforward(self,x):batch_size,seq_len,embed_dimx.shape# 1. 投影得到 Q、K、Vqself.W_q(x)# (batch, seq_len, embed_dim)kself.W_k(x)# (batch, seq_len, embed_dim)vself.W_v(x)# (batch, seq_len, embed_dim)# 2. ⭐ 计算缩放点积分数# (batch, seq_len, seq_len)scorestorch.bmm(q,k.transpose(1,2))/torch.sqrt(torch.tensor(embed_dim,dtypetorch.float32))# 3. softmax 得到注意力权重attn_weightsF.softmax(scores,dim-1)# (batch, seq_len, seq_len)# 4. 加权求和得到输出outputtorch.bmm(attn_weights,v)# (batch, seq_len, embed_dim)returnoutput,attn_weights## 使用示例self_attnSelfAttention(embed_dim128)xtorch.randn(2,5,128)output,attnself_attn(x)print(f输入:{x.shape}→ 输出:{output.shape}, 权重:{attn.shape})# 输入: torch.Size([2, 5, 128]) → 输出: torch.Size([2, 5, 128]), 权重: torch.Size([2, 5, 5])5. 避坑指南5.1 batch_first 默认是 False这是新手踩得最狠的坑。nn.Transformer的batch_first默认是False要求的输入形状是(seq_len, batch, d_model)而大部分人的习惯是(batch, seq_len, d_model)。建议始终显式设置batch_firstTrue否则 shape 全乱。5.2 训练时别忘了 tgt_mask# ❌ 不加 mask — 模型能看到未来词等价于作弊outputtransformer(src_emb,tgt_emb)# ✅ 加因果掩码tgt_masknn.Transformer.generate_square_subsequent_mask(tgt_len)outputtransformer(src_emb,tgt_emb,tgt_masktgt_mask)不加tgt_mask训练时的 loss 会异常低但推理时效果极差——因为训练时模型偷看了答案。5.3 训练和推理的代码结构完全不同阶段输入方式计算方式掩码训练完整目标序列一次输入并行计算因果掩码推理逐个词拼接到已生成序列顺序循环下三角掩码自动保证训练是一步到位推理是循环生成——不要用推理的代码写训练逻辑也别用训练的逻辑做推理。5.4 Pre-LN vs Post-LN原始 Transformer 用Post-LN先计算再归一化但深层网络训练不稳定需要 warmup。实践中Pre-LN先归一化再计算更稳定GPT、BERT 现代实现大多用 Pre-LN。# Post-LN原始版子层 → 残差 → LayerNorm# Pre-LN稳定版LayerNorm → 子层 → 残差encoder_layernn.TransformerEncoderLayer(d_model512,nhead8,norm_firstTrue,# True Pre-LN)5.5 位置编码不要漏nn.Transformer内部不包含位置编码需要手动叠加。漏掉位置编码相当于让模型在无序的状态下处理序列长序列效果会严重下降。# 注意nn.Transformer 不内置位置编码需要自己加src_embsrc_embed(src)positional_encoding[:src_len]tgt_embtgt_embed(tgt)positional_encoding[:tgt_len]6. 总结Encoder vs Decoder 对比对比维度EncoderDecoder注意力类型自注意力双向掩码自注意力 交叉注意力掩码无有因果掩码下三角输入源序列目标序列已生成部分可见范围序列中所有位置只看当前位置之前含自己Q 来源自身自注意力自身 / 交叉注意力DecoderKV 来源自身自注意力自身 / 交叉注意力Encoder输出源序列的上下文表示目标序列的预测结果Transformer 的五大核心创新完全基于注意力摆脱 RNN实现全并行训练多头注意力多个子空间并行学习不同类型的依赖关系位置编码给无序的自注意力注入位置信息残差连接 LayerNorm支撑深层网络稳定训练自回归生成 因果掩码训练时并行推理时逐词生成参考链接原始论文Attention Is All You NeedPyTorch nn.Transformer 官方文档PyTorch TransformerEncoderLayer 官方文档The Annotated Transformer下篇预告Transformer 讲完了下一篇终于轮到真正的大模型了——BERT 和 GPT 系列。它们各自在 Transformer 的基础上做了什么改进为什么 BERT 适合理解任务、GPT 适合生成任务敬请期待。