声明这篇笔记仅为记录学习 Transformer 生成过程时的理解和实现纯为技术分享不含任何商业推广或培训导向。1. 自回归解码的基本流程无论是机器翻译中使用的编码器-解码器 Transformer还是 GPT 这类纯解码器结构文本生成的核心都是自回归将已生成的序列当作新的输入模型据此预测下一个词拼接到尾部如此循环。步骤可以概括为提供初始输入比如sos起始符机器翻译还需要源语言句子。模型基于当前序列计算所有可能下一个词的概率分布。从分布中选择一个词贪心、采样等附加到序列末尾。重复第 2 步直到出现eos或达到预设的最大长度。每一步都能看到之前所有生成的内容因此上下文是完整的。2. 注意力机制及其实现2.1 缩放点积注意力注意力层的计算可以表述为Attention(Q,K,V)softmax(QKTdk)V \text{Attention}(Q, K, V) \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) VAttention(Q,K,V)softmax(dk​​QKT​)V除以dk\sqrt{d_k}dk​​是为了防止点积数值过大让 softmax 的输出保持合理的梯度。对应的 PyTorch 实现importtorchimporttorch.nn.functionalasFimportmathdefscaled_dot_product_attention(Q,K,V,maskNone):d_kQ.size(-1)scorestorch.matmul(Q,K.transpose(-2,-1))/math.sqrt(d_k)ifmaskisnotNone:scoresscores.masked_fill(mask0,float(-inf))attn_weightsF.softmax(scores,dim-1)returntorch.matmul(attn_weights,V),attn_weights2.2 多头注意力多头注意力将 Q、K、V 线性映射到多个子空间各自做注意力运算再将结果拼接、投影回原维度。classMultiHeadAttention(nn.Module):def__init__(self,d_model,num_heads):super().__init__()self.num_headsnum_heads self.d_kd_model//num_heads self.W_qnn.Linear(d_model,d_model)self.W_knn.Linear(d_model,d_model)self.W_vnn.Linear(d_model,d_model)self.W_onn.Linear(d_model,d_model)defsplit_heads(self,x):# x: (batch, seq_len, d_model) - (batch, heads, seq_len, d_k)b,s,_x.shapereturnx.view(b,s,self.num_heads,self.d_k).transpose(1,2)defcombine_heads(self,x):b,_,s,_x.shapereturnx.transpose(1,2).contiguous().view(b,s,-1)defforward(self,Q,K,V,maskNone):Qself.split_heads(self.W_q(Q))Kself.split_heads(self.W_k(K))Vself.split_heads(self.W_v(V))attn_out,_scaled_dot_product_attention(Q,K,V,mask)returnself.W_o(self.combine_heads(attn_out))3. 位置编码与前馈网络Transformer 本身不包含循环或卷积需要借助位置编码注入序列顺序信息。这里使用正弦-余弦固定编码classPositionalEncoding(nn.Module):def__init__(self,d_model,max_len5000):super().__init__()petorch.zeros(max_len,d_model)positiontorch.arange(0,max_len).unsqueeze(1).float()div_termtorch.exp(torch.arange(0,d_model,2).float()*-(math.log(10000.0)/d_model))pe[:,0::2]torch.sin(position*div_term)pe[:,1::2]torch.cos(position*div_term)pepe.unsqueeze(0)# (1, max_len, d_model)self.register_buffer(pe,pe)defforward(self,x):returnxself.pe[:,:x.size(1),:]每个注意力子层后面都有一个前馈网络本质上是一个两层的全连接网络中间用 ReLU 激活classFeedForward(nn.Module):def__init__(self,d_model,d_ff):super().__init__()self.linear1nn.Linear(d_model,d_ff)self.linear2nn.Linear(d_ff,d_model)defforward(self,x):returnself.linear2(F.relu(self.linear1(x)))4. 编码器层与解码器层4.1 编码器层编码器层包含多头自注意力 前馈网络每个子层后面有残差连接和层归一化。classEncoderLayer(nn.Module):def__init__(self,d_model,num_heads,d_ff,dropout0.1):super().__init__()self.self_attnMultiHeadAttention(d_model,num_heads)self.ffFeedForward(d_model,d_ff)self.norm1nn.LayerNorm(d_model)self.norm2nn.LayerNorm(d_model)self.dropoutnn.Dropout(dropout)defforward(self,x,maskNone):aself.self_attn(x,x,x,mask)xself.norm1(xself.dropout(a))fself.ff(x)xself.norm2(xself.dropout(f))returnx4.2 解码器层解码器层在自注意力之后还插入了一个交叉注意力它用解码器的表示去查询编码器的输出。同时自注意力部分必须使用因果掩码防止当前词“偷看”后面内容。classDecoderLayer(nn.Module):def__init__(self,d_model,num_heads,d_ff,dropout0.1):super().__init__()self.self_attnMultiHeadAttention(d_model,num_heads)self.cross_attnMultiHeadAttention(d_model,num_heads)self.ffFeedForward(d_model,d_ff)self.norm1nn.LayerNorm(d_model)self.norm2nn.LayerNorm(d_model)self.norm3nn.LayerNorm(d_model)self.dropoutnn.Dropout(dropout)defforward(self,x,enc_output,src_maskNone,tgt_maskNone):# 带掩码的自注意力sself.self_attn(x,x,x,tgt_mask)xself.norm1(xself.dropout(s))# 交叉注意力 (query来自解码器, key/value来自编码器)cself.cross_attn(x,enc_output,enc_output,src_mask)xself.norm2(xself.dropout(c))# 前馈fself.ff(x)xself.norm3(xself.dropout(f))returnx5. 因果掩码与推理循环因果掩码是一个下三角矩阵保证位置 i 只能关注 0 到 i 的信息。实现方式很简单defcreate_causal_mask(sz):masktorch.tril(torch.ones(sz,sz))# 下三角为1上三角为0returnmask.unsqueeze(0).unsqueeze(0)# (1, 1, sz, sz)在推理阶段源序列经过编码器生成记忆memory仅计算一次然后解码器逐词生成defgreedy_decode(model,src,src_mask,max_len,start_sym,end_sym):model.eval()memorymodel.encode(src,src_mask)ystorch.ones(1,1).fill_(start_sym).long()# 初始序列仅含sosfor_inrange(max_len-1):tgt_maskcreate_causal_mask(ys.size(1)).to(src.device)outmodel.decode(memory,src_mask,ys,tgt_mask)# (1, seq_len, vocab_size)probout[:,-1,:]# 取最后一步的 logits_,next_wordtorch.max(prob,dim1)next_wordnext_word.item()ystorch.cat([ys,torch.ones(1,1).fill_(next_word).long()],dim1)ifnext_wordend_sym:breakreturnys实践中这里会加入KV 缓存以提升效率但核心逻辑不变。6. 完整模型与生成方法将以上模块组合起来得到一个标准的 Transformer 模型。下面展示encode、decode和generate三个核心方法省略完整的__init__以节省篇幅结构就是堆叠若干编码器层和解码器层并加上词嵌入和位置编码。classTransformer(nn.Module):# __init__ 中定义:# encoder_embed, decoder_embed, pos_encoder, pos_decoder,# encoder_layers (ModuleList), decoder_layers (ModuleList),# output_proj (nn.Linear)# 细节见前文各个组件。defencode(self,src,src_maskNone):xself.encoder_embed(src)*math.sqrt(self.d_model)xself.pos_encoder(x)forlayerinself.encoder_layers:xlayer(x,src_mask)returnxdefdecode(self,memory,src_mask,tgt,tgt_maskNone):xself.decoder_embed(tgt)*math.sqrt(self.d_model)xself.pos_decoder(x)forlayerinself.decoder_layers:xlayer(x,memory,src_mask,tgt_mask)returnself.output_proj(x)torch.no_grad()defgenerate(self,src,src_mask,max_len,start_sym,end_sym,temperature1.0,top_k0):self.eval()memoryself.encode(src,src_mask)ystorch.ones(src.size(0),1).fill_(start_sym).long().to(src.device)for_inrange(max_len-1):tgt_maskcreate_causal_mask(ys.size(1)).to(src.device)logitsself.decode(memory,src_mask,ys,tgt_mask)logitslogits[:,-1,:]/temperature# 温度缩放iftop_k0:v,_torch.topk(logits,top_k)logits[logitsv[:,-1:]]float(-inf)probsF.softmax(logits,dim-1)next_wordtorch.multinomial(probs,num_samples1)# 随机采样# 贪心解码可替换为: next_word torch.argmax(probs, dim-1, keepdimTrue)ystorch.cat([ys,next_word],dim1)if(next_wordend_sym).all():breakreturnys该generate方法集成了温度和Top-K 采样可以控制生成的多样性和质量。7. 总结Transformer 的生成本质上是因果掩码下的自回归过程编码器将源序列处理为固定长度的上下文表示。解码器每一步以已生成的输出作为输入利用因果掩码确保只依赖历史信息。最后一步的 logits 转换为概率选定下一个词后拼接到序列上循环至终止。对于纯解码器模型只需去掉编码器和交叉注意力部分完全依靠堆叠的自注意力实现相同的自回归逻辑。理解了这些基本机制就能在此基础上研究更实际的优化如KV 缓存、Beam Search和重复惩罚等而整个生成框架的骨架并不会改变。全文完。这篇笔记仅用作技术记录所有代码均可自由使用和修改。