剖析 Transformer 核心:解码器层(Decoder Layer)内部结构与生成逻辑
剖析 Transformer 核心:解码器层(Decoder Layer)内部结构与生成逻辑
如果说编码器(Encoder)的目标是提炼并理解源语言序列的全局语义,那么解码器(Decoder)的核心职责就是基于已生成的历史信息与编码器输出的源序列特征,以自回归(Autoregressive)的方式逐词预测未来的内容。
标准 Transformer 解码器由 个结构相同的解码器层(Decoder Layer,论文默认 )垂直堆叠而成。为了兼顾历史信息的因果约束与跨序列的语义融合,解码器层在编码器的基础上进行了关键升级:扩展为三层级联子结构,并引入了因果掩码与交叉注意力机制。
一、 解码器层的宏观结构与数据流
单个解码器层接收两个截然不同的输入流:
- 来自目标序列(Target Sequence)的输入: 包含历史已生成词向量的主干张量 。
- 来自编码器(Encoder)的输出: 包含源语言完整全局特征的记忆张量 。
每个解码器层按顺序串联了三个子层:
解码器输入 X_dec (B, L_tgt, d_model)
│
┌───────────────┴───────────────┐
│ ▼
│ [ 1. 掩码多头自注意力 (Masked MHA) ] <── 因果掩码 (Causal Mask)
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 残差连接 │ │ 子层输出 │
└──────┬──────┘ └──────┬──────┘
│ │
└───────────────► ◄─────────────┘ (Add & Norm)
│
┌───────────────┴───────────────┐
│ ▼
│ [ 2. 交叉注意力 (Cross-Attention) ] <── K, V 来自 Encoder 输出
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 残差连接 │ │ 子层输出 │
└──────┬──────┘ └──────┬──────┘
│ │
└───────────────► ◄─────────────┘ (Add & Norm)
│
┌───────────────┴───────────────┐
│ ▼
│ [ 3. 前馈全连接网络 (FFN) ]
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 残差连接 │ │ 子层输出 │
└──────┬──────┘ └──────┬──────┘
│ │
└───────────────► ◄─────────────┘ (Add & Norm)
│
解码器输出 Y_dec (B, L_tgt, d_model)
每个子层同样被残差连接(Residual Connection)与层归一化(Layer Normalization)严密包裹。
二、 子层一:掩码多头自注意力(Masked Multi-Head Self-Attention)
自注意力机制默认具备全局可见性,但语言生成是一个单向因果过程——在预测当前词 时,模型绝不能提前“偷看”到时刻 及之后的未来词,否则会导致训练时的标签泄露(Data Leakage)。
1. 因果掩码(Causal Mask)的数学构造
为了消除时间机器效应,解码器的自注意力层引入了一个下三角掩码矩阵 :
掩码直接加在未归一化的注意力得分矩阵(Logits)上:
通过将未来位置的值设为负无穷大(在工程实现中通常为 ),经过 Softmax 指数运算后,,使得未来词的注意力权重精确为 0,彻底切断反向信息流。
2. 训练与推理的差异:Teacher Forcing vs 自回归
- 训练阶段(并行加速): 采用 Teacher Forcing 策略。虽然因果掩码阻断了未来信息,但由于真实的目标句子是已知的,整个序列可以一次性批量输入,注意力矩阵在掩码约束下实现全并行计算,无需按时间步循环。
- 推理阶段(自回归串行): 模型没有“未来的答案”。只能将已生成的 Token 逐个作为输入,每次前向传播仅生成一个新 Token,追加到序列末尾后,再进行下一轮迭代。
三、 子层二:交叉注意力(Cross-Attention)
交叉注意力(又称 Encoder-Decoder Attention)是整个架构中源语言与目标语言进行语义对齐的枢纽。
1. 数据的异源交互(Q 来自 Decoder,K/V 来自 Encoder)
交叉注意力层的计算依然遵循缩放点积公式,但其输入张量的来源发生了根本分化:
- (Query)由解码器上一层的输出投影而来:
Query 携带的是当前已经生成的目标语言上下文(例如:“我想要知道……”)。 - (Key)由编码器最终输出投影而来:
Key 充当源语言所有词的特征索引标签。 - (Value)由编码器最终输出投影而来:
Value 包含源语言序列中各词最深层的语义实体。
2. 机制本质与尺寸匹配
在交叉注意力中,相关性矩阵的尺寸为 。矩阵的每一行对应一个正在生成的目标词,每一列对应源句子中的输入词。这意味着:目标端的每一个 Token 都在自适应地打量源端的每一个 Token,以此决定当前应该翻译、总结或引申原文的哪一部分。
注意:交叉注意力不需要因果掩码,因为源语言序列在生成之前就已经全部已知,解码器理应且必须拥有对整篇输入文本的全局检索权限。
四、 子层三:基于位置的前馈全连接网络(FFN)
交叉注意力完成源文本与目标文本的动态融合后,输出张量进入前馈全连接网络。该结构与编码器中的 FFN 完全一致:
每个位置独立进行升维映射(从 升至 )与非线性激活,再降维投影回 。该层负责对融合后的特征进行通道级重组与知识激活,为最后输出词表概率分布准备充分的判别性表征。
五、 推理核心工程:KV Cache 机制
在自回归解码过程中,如果每生成一个新 Token 都将整个历史序列重新输入模型,历史 Token 的 投影以及注意力点积会被重复计算 次,使得生成长度为 的序列总时间复杂度飙升至 (单步从 恶化至 )。
KV Cache(键值缓存) 是现代解码器推理的标配优化技术:
- 核心观察: 预测时刻 的新词时,只有新词自身需要作为 Query 去匹配过往所有词;而历史已生成的词,其 Key 和 Value 向量在后续时间步完全保持不变。
- 缓存机制: 在每次前向传播中,仅将当前最新的单个 Token 输入网络,计算出其对应的 。将 拼接到显存中常驻的历史缓存矩阵 和 中。
- 计算复杂度下降: 每次前向计算的复杂度直接降为 ,使得整句生成的计算量回落至 ,极大提升了推理吞吐。
六、 PyTorch 模块化代码实现
以下代码实现了工业界通用的 Pre-LN 结构解码器层 TransformerDecoderLayer,并完整封装了因果掩码生成与多层堆叠的 TransformerDecoder:
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
class MultiHeadAttention(nn.Module):
def __init__(self, d_model: int, num_heads: int, dropout: float = 0.1):
super().__init__()
assert d_model % num_heads == 0
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads
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: torch.Tensor, k: torch.Tensor, v: torch.Tensor, mask: torch.Tensor = None) -> torch.Tensor:
B, L_q, _ = q.shape
L_k = k.size(1)
# 线性映射并切分多头
Q = self.w_q(q).view(B, L_q, self.num_heads, self.d_k).transpose(1, 2)
K = self.w_k(k).view(B, L_k, self.num_heads, self.d_k).transpose(1, 2)
V = self.w_v(v).view(B, L_k, self.num_heads, self.d_k).transpose(1, 2)
# 缩放点积
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
# 掩码填充
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
attn_weights = self.dropout(torch.softmax(scores, dim=-1))
context = torch.matmul(attn_weights, V)
context = context.transpose(1, 2).contiguous().view(B, L_q, self.d_model)
return self.w_o(context)
class TransformerDecoderLayer(nn.Module):
"""采用 Pre-LN 结构的单层 Decoder"""
def __init__(self, d_model: int, num_heads: int, d_ff: int, dropout: float = 0.1):
super().__init__()
# 1. 掩码自注意力
self.self_attn = MultiHeadAttention(d_model, num_heads, dropout)
self.norm1 = nn.LayerNorm(d_model)
# 2. 交叉注意力
self.cross_attn = MultiHeadAttention(d_model, num_heads, dropout)
self.norm2 = nn.LayerNorm(d_model)
# 3. 前馈网络
self.ffn = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(d_ff, d_model)
)
self.norm3 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x: torch.Tensor, enc_output: torch.Tensor,
tgt_mask: torch.Tensor = None, src_mask: torch.Tensor = None) -> torch.Tensor:
"""
x: 解码器自身特征 (B, L_tgt, d_model)
enc_output: 编码器最终输出特征 (B, L_src, d_model)
tgt_mask: 因果掩码 + 目标 padding 掩码 (B, 1, L_tgt, L_tgt)
src_mask: 源端 padding 掩码 (B, 1, 1, L_src)
"""
# 子层 1:掩码自注意力 (Pre-LN)
norm_x = self.norm1(x)
self_attn_out = self.self_attn(norm_x, norm_x, norm_x, mask=tgt_mask)
x = x + self.dropout(self_attn_out)
# 子层 2:交叉注意力 (Q 来自解码器,K/V 来自编码器)
norm_x = self.norm2(x)
cross_attn_out = self.cross_attn(norm_x, enc_output, enc_output, mask=src_mask)
x = x + self.dropout(cross_attn_out)
# 子层 3:前馈全连接
norm_x = self.norm3(x)
ffn_out = self.ffn(norm_x)
x = x + self.dropout(ffn_out)
return x
class TransformerDecoder(nn.Module):
def __init__(self, num_layers: int, d_model: int, num_heads: int, d_ff: int, dropout: float = 0.1):
super().__init__()
self.layers = nn.ModuleList([
TransformerDecoderLayer(d_model, num_heads, d_ff, dropout)
for _ in range(num_layers)
])
self.final_norm = nn.LayerNorm(d_model)
def forward(self, x: torch.Tensor, enc_output: torch.Tensor,
tgt_mask: torch.Tensor = None, src_mask: torch.Tensor = None) -> torch.Tensor:
for layer in self.layers:
x = layer(x, enc_output, tgt_mask, src_mask)
return self.final_norm(x)
# ---------------- 验证前向传播与数据流 ----------------
if __name__ == "__main__":
B = 2
L_src = 10
L_tgt = 6
d_model = 64
num_heads = 4
d_ff = 256
num_layers = 4
# 1. 模拟输入数据
dummy_tgt = torch.randn(B, L_tgt, d_model) # 解码器目标输入
dummy_enc_out = torch.randn(B, L_src, d_model) # 编码器产出特征
# 2. 构造因果掩码 (下三角矩阵: 1 保留,0 屏蔽)
# shape: (1, 1, L_tgt, L_tgt)
causal_mask = torch.tril(torch.ones(L_tgt, L_tgt)).unsqueeze(0).unsqueeze(0)
# 3. 实例化解码器并执行前向
decoder = TransformerDecoder(num_layers, d_model, num_heads, d_ff)
dec_out = decoder(dummy_tgt, dummy_enc_out, tgt_mask=causal_mask)
print("Decoder 输入尺寸:", dummy_tgt.shape) # torch.Size([2, 6, 64])
print("Encoder 特征尺寸:", dummy_enc_out.shape) # torch.Size([2, 10, 64])
print("Decoder 输出尺寸:", dec_out.shape) # torch.Size([2, 6, 64])
七、 演化思考:为什么当代大模型全面倒向 Decoder-Only?
在 2017 年原生 Transformer 时代,经典的翻译模型采用 Encoder-Decoder;然而在当今大语言模型(GPT-4、LLaMA、DeepSeek 等)的版图中,Decoder-Only(仅解码器)架构占据了绝对主导地位。
| 架构维度 | 传统 Encoder-Decoder(如 T5, BART) | 现代 Decoder-Only(如 GPT, LLaMA) |
|---|---|---|
| 子层组成 | 掩码自注意力 + 交叉注意力 + FFN | 仅保留掩码自注意力 + FFN(完全去除交叉注意力) |
| 参数利用率 | 编码器参数在解码阶段只计算一次,随后进入闲置状态。 | 每一层参数、每一个 Token 在自回归过程中都被充分复用。 |
| 任务泛化性 | 需要定义源输入与目标输出的边界(如翻译、摘要)。 | 将理解、问答、推理统统统一为自回归下一个 Token 预测。 |
| KV Cache 复杂度 | 需同时维护解码器内部与交叉注意力两套 Cache。 | 单一线性自回归,KV Cache 结构统一,极易通过 PagedAttention 等算子加速。 |
现代大模型并非抛弃了解码器,而是将理解能力完全融入到了自回归解码器的因果注意力流中。解码器层凭借其精密的因果约束设计与强大的时序推演能力,不仅构成了原生 Transformer 完成跨语言映射的终点,更成为了当代大语言模型生成整个数字世界的基本计算单元。
- 点赞
- 收藏
- 关注作者
评论(0)