← 返回笔记列表
Transformer 第 7 / 8 篇

Transformer(七):训练、损失与自回归推理

查看“Transformer”专题目录(7/8)
本文目录 29 个小节

学习目标

学完本文后,应该能够:

  • 构造源输入、目标输入和目标标签;
  • 解释“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>Iamadog
监督标签Iamadog<eos>

模型学习的是:给定左侧前缀,预测当前位置的下一个 token。

2. 为什么训练时能一次预测所有位置

训练数据提供了完整目标句,因此可一次把 tgt_input 输入 Decoder。Causal Mask 保证:

  • <bos> 位置只能看到 <bos>;
  • I 位置只能看到 <bos>, I;
  • am 位置只能看到 <bos>, I, am;
  • 后续位置同理。

所有位置的矩阵运算可以并行,但每个位置的可见范围仍与自回归推理一致。

训练时可以把源输入、右移后的目标输入和逐位置损失看成三条配合的数据流:源序列提供条件,目标前缀提供已知上下文,标签负责监督下一个 token。

3. Teacher Forcing

训练时,第 tt 个位置接收真实的前缀 token,而不是模型自己刚刚预测的 token,这称为 Teacher Forcing。

优点:

  • 可以并行训练全部位置;
  • 训练信号稳定,不会因早期错误让后续输入完全偏离。

局限:

  • 推理时只能使用模型自己的历史输出;
  • 训练和推理的前缀分布不同,可能产生暴露偏差。

4. 从 Decoder 输出到词表 logits

Decoder 输出:

hidden: [B, T, D]

词表投影:

Z=HWvocab+bZ=HW_{vocab}+b
W_vocab: [D, V]
logits:  [B, T, V]

训练交叉熵直接接收 logits,不要先手动 softmax。框架内部会使用数值稳定的 log_softmax 计算。

5. Token 级交叉熵

对 batch 中所有非填充目标 token 求平均:

L=−1Nvalid∑b,t:yb,t≠padlog⁡p(yb,t∣yb,<t,xb)\mathcal{L} = -\frac{1}{N_{valid}} \sum_{b,t:y_{b,t}\ne pad} \log p(y_{b,t}\mid y_{b,<t},x_b)

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:β1=0.9\beta_1=0.9、β2=0.98\beta_2=0.98、ϵ=10−9\epsilon=10^{-9};
  • 先 warmup、再按步数平方根倒数衰减的学习率;
  • label smoothing,系数为 0.1;
  • dropout,Base 模型为 0.1;
  • 目标 embedding 与 softmax 前线性变换共享权重。

这些设计不是 Transformer 公式的必要组成部分,却对复现论文结果非常重要。

8.1 学习率日程

论文中的学习率与模型维度、训练步数有关:

lr=D−1/2min⁡(step−1/2,step⋅warmup−3/2)lr=D^{-1/2}\min(step^{-1/2},step\cdot warmup^{-3/2})

warmup 阶段线性上升,之后按 step−1/2step^{-1/2} 下降。

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 保存之前位置的 K,VK,V,新一步只计算当前 token 的投影,再与缓存拼接。

KV Cache:

  • 减少重复投影和注意力前缀计算;
  • 增加随序列长度增长的缓存显存;
  • 不会取消“下一 token 依赖上一个 token”的串行关系。

原始 Encoder-Decoder 模型中,Encoder memory 只需计算一次;Decoder 自注意力的 KV 可逐步缓存,交叉注意力的 Encoder K/V 也可以预计算。

12. 解码策略

模型输出的是概率分布,如何选择下一个 token 是独立的解码决策:

  • 贪心:每步取概率最大;
  • Beam Search:保留多个高分序列,翻译任务常用;
  • Temperature:控制分布尖锐程度;
  • Top-k:只在概率最高的 kk 个 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:减少重复计算,不取消串行依赖。

自测问题

  1. 为什么目标输入必须右移一位?
  2. <eos> 应该出现在哪个监督位置?
  3. 为什么 Causal Mask 与 ignore_index 都需要?
  4. Teacher Forcing 的优点和训练—推理差异是什么?
  5. KV Cache 缓存了什么,又没有解决什么?

参考与推荐阅读

继续学习本专题