学习目标
学完本文后,应该能够:
- 构造源输入、目标输入和目标标签;
- 解释“Outputs shifted right”真正表示什么;
- 写出 token 级交叉熵并忽略
<pad>; - 区分训练阶段的并行前向与推理阶段的自回归循环;
- 说清 Causal Mask、Padding Mask 和 KV Cache 的作用。
先修知识
代码主线说明
本文的数据构造、整体 Transformer 前向和单步 logits 主要对应本地 Loss.ipynb,采样策略与分词部分分别对应 Sampling.ipynb 和 Tokenizer.ipynb。在 Notebook 基础上补充了 ignore_index=pad_id、Padding Mask 与完整自回归外层循环,使示例能够自然扩展到变长的真实批数据。
1. 训练样本如何构造
假设源句为:
我 是 一 条 狗
目标句为:
I am a dog
加入特殊 token 后:
src_input: <bos> 我 是 一 条 狗 <eos>
tgt_input: <bos> I am a dog
tgt_label: I am a dog <eos>
目标输入和目标标签只相差一个位置,这就是架构图中的 Outputs shifted right。
| Decoder 输入位置 | <bos> | I | am | a | dog |
|---|---|---|---|---|---|
| 监督标签 | I | am | a | dog | <eos> |
模型学习的是:给定左侧前缀,预测当前位置的下一个 token。
2. 为什么训练时能一次预测所有位置
训练数据提供了完整目标句,因此可一次把 tgt_input 输入 Decoder。Causal Mask 保证:
<bos>位置只能看到<bos>;I位置只能看到<bos>, I;am位置只能看到<bos>, I, am;- 后续位置同理。
所有位置的矩阵运算可以并行,但每个位置的可见范围仍与自回归推理一致。
训练时可以把源输入、右移后的目标输入和逐位置损失看成三条配合的数据流:源序列提供条件,目标前缀提供已知上下文,标签负责监督下一个 token。
3. Teacher Forcing
训练时,第 个位置接收真实的前缀 token,而不是模型自己刚刚预测的 token,这称为 Teacher Forcing。
优点:
- 可以并行训练全部位置;
- 训练信号稳定,不会因早期错误让后续输入完全偏离。
局限:
- 推理时只能使用模型自己的历史输出;
- 训练和推理的前缀分布不同,可能产生暴露偏差。
4. 从 Decoder 输出到词表 logits
Decoder 输出:
hidden: [B, T, D]
词表投影:
W_vocab: [D, V]
logits: [B, T, V]
训练交叉熵直接接收 logits,不要先手动 softmax。框架内部会使用数值稳定的 log_softmax 计算。
5. Token 级交叉熵
对 batch 中所有非填充目标 token 求平均:
PyTorch 写法:
import torch.nn.functional as F
def language_model_loss(logits, labels, pad_id):
# logits: [B, T, V]
# labels: [B, T]
if not labels.ne(pad_id).any():
raise ValueError("至少需要一个非填充标签")
return F.cross_entropy(
logits.reshape(-1, logits.size(-1)),
labels.reshape(-1),
ignore_index=pad_id,
)
Loss.ipynb 的简化代码没有设置 ignore_index。当真实数据经过 padding 后,应忽略 <pad> 标签,否则模型会浪费能力学习预测填充符,较长 padding 的样本还会在损失中占据不合理权重。
6. 三类 Mask 如何进入训练
6.1 源端 Padding Mask
src_is_pad: [B, S]
用于 Encoder 自注意力,也用于 Decoder 交叉注意力对 Encoder memory 的读取。
6.2 目标端 Padding Mask
tgt_is_pad: [B, T]
用于 Decoder 自注意力忽略目标端 <pad>,也用于最终损失忽略无效标签。
6.3 目标端 Causal Mask
causal: [T, T]
用于防止每个位置读取未来 token。
这三者不能因为都叫 Mask 就混成一个概念。
7. 一个 batch 的目标右移代码
若原始目标序列已经是:
<bos> I am a dog <eos> <pad>
可以直接切片:
target_input = target_ids[:, :-1]
target_label = target_ids[:, 1:]
assert target_input.shape == target_label.shape
不要把 <eos> 同时从输入和标签中都删除,否则模型学不到何时停止生成。
8. 原论文的重要训练设置
原始 Transformer 还使用了以下方法:
- Adam:、、;
- 先 warmup、再按步数平方根倒数衰减的学习率;
- label smoothing,系数为 0.1;
- dropout,Base 模型为 0.1;
- 目标 embedding 与 softmax 前线性变换共享权重。
这些设计不是 Transformer 公式的必要组成部分,却对复现论文结果非常重要。
8.1 学习率日程
论文中的学习率与模型维度、训练步数有关:
warmup 阶段线性上升,之后按 下降。
9. 推理为什么必须循环
推理开始时,只有 <bos>:
第 1 步:<bos> → 预测 I
第 2 步:<bos> I → 预测 am
第 3 步:<bos> I am → 预测 a
第 4 步:<bos> I am a → 预测 dog
第 5 步:<bos> I am a dog → 预测 <eos>
新 token 会成为下一步的输入,所以 token 维度上的生成不可完全并行。
10. 最小贪心解码流程
本节使用下一篇组装的 Transformer 接口,即 model(src_ids, generated) 返回最后位置 logits。完整 greedy_decode 函数见手写组装与代码调试,避免额外假设模型存在未定义的 encode/decode 方法。
批量解码必须逐样本维护 finished,到达 <eos> 后冻结该样本,并按位置表限制生成步数。torch.no_grad() 关闭梯度记录,model.eval() 才会关闭训练态 dropout,二者都需要。
11. KV Cache 在做什么
朴素推理每一步都会重新计算整个目标前缀的 Key 和 Value。KV Cache 保存之前位置的 ,新一步只计算当前 token 的投影,再与缓存拼接。
KV Cache:
- 减少重复投影和注意力前缀计算;
- 增加随序列长度增长的缓存显存;
- 不会取消“下一 token 依赖上一个 token”的串行关系。
原始 Encoder-Decoder 模型中,Encoder memory 只需计算一次;Decoder 自注意力的 KV 可逐步缓存,交叉注意力的 Encoder K/V 也可以预计算。
12. 解码策略
模型输出的是概率分布,如何选择下一个 token 是独立的解码决策:
- 贪心:每步取概率最大;
- Beam Search:保留多个高分序列,翻译任务常用;
- Temperature:控制分布尖锐程度;
- Top-k:只在概率最高的 个 token 中采样;
- Top-p:在累计概率达到阈值的最小集合中采样。
详细内容见语言模型采样策略。
13. 训练与推理对照
| 对比 | 训练 | 推理 |
|---|---|---|
| 目标端输入 | 真实目标右移 | 模型已生成前缀 |
| 位置计算 | 所有目标位置并行 | 每次新增一个位置 |
| Causal Mask | 必须 | 朴素整段解码仍需要;增量缓存按构造保证因果 |
| Dropout | 开启 | 关闭 |
| 损失 | 计算交叉熵 | 不计算 |
| 终止 | 固定标签长度 | <eos> 或最大长度 |
14. 常见误区
误区一:目标右移是在 embedding 后移动
通常先在 token id 序列上构造 tgt_input 和 tgt_label,再分别进入模型与损失。
误区二:训练时完整输入目标句会泄漏答案
正确的 Causal Mask 会阻断未来信息。
误区三:交叉熵前需要手动 softmax
F.cross_entropy 直接接收 logits。
误区四:所有 <pad> 都会被注意力 Mask 自动处理
注意力 Mask 和损失 ignore_index 是两个位置,二者都要处理。
一句话总结
Transformer 训练的本质是:用真实目标右移作为 Decoder 输入,在因果约束下并行预测所有下一个 token;推理时则把自身预测不断接回输入,逐 token 生成直到 <eos>。
复习卡片
tgt_input:去掉最后一个 token;tgt_label:去掉第一个 token;- logits:
[B,T,V]; - 损失:交叉熵并忽略
<pad>; - 训练:Teacher Forcing、位置并行;
- 推理:自回归循环;
- KV Cache:减少重复计算,不取消串行依赖。
自测问题
- 为什么目标输入必须右移一位?
<eos>应该出现在哪个监督位置?- 为什么 Causal Mask 与
ignore_index都需要? - Teacher Forcing 的优点和训练—推理差异是什么?
- KV Cache 缓存了什么,又没有解决什么?
参考与推荐阅读
- Attention Is All You Need:3.1、5.3 和 5.4 节。
- 利用 Transformer 实现翻译模型:适合对照数据准备、padding 与训练循环。
- PyTorch
nn.Transformer:核对各种 Mask 参数。 - 本地学习代码:
Loss.ipynb、Sampling.ipynb、Tokenizer.ipynb。