架构图看懂了,代码里却还是一堆 labels、mask、backward。这一篇只追踪一个 batch:哪些 token 提供上下文,哪些 token 被计分,最后什么参数变了。
前置是 Decoder-only。理论上的训练与生成差别,在语言模型目标里展开。
先把预测位置对齐
用虚构序列 [BOS, 问题, SEP, 答案, EOS, PAD],这里只把每个词当成一个教学 token。位置 2 的 SEP 处输出预测位置 3 的 答案,不是预测它自己。
| 输入位置 | 当前 token | 应预测的下一个 token | answer-only 计分 |
|---|---|---|---|
| 0 | BOS | 问题 | 否 |
| 1 | 问题 | SEP | 否 |
| 2 | SEP | 答案 | 是 |
| 3 | 答案 | EOS | 是 |
| 4 | EOS | PAD | 否 |
所以手写 loss 时,logits[:, :-1] 对齐 input_ids[:, 1:]。loss mask 按 token 位置标出哪些 token 是计分目标(本例为 [0, 0, 0, 1, 1, 0],即 答案 和 EOS),同样取 [:, 1:],得到 [0, 0, 1, 1, 0],正好对上表中各行。如果框架的模型已经在内部 shift,就不要再 shift 一次;先查实际实现。Hugging Face 的 causal LM 教程展示了一种由模型负责对齐的用法。
三种 mask 不做同一件事
- Causal mask:每个位置只能看自己和前面的 token,不能看后面的。
- Padding mask:无效的补齐位置不应作为有效上下文。
- Loss mask:哪些目标位置计入训练目标。
问题不计 loss,不代表模型看不到问题。答案的预测仍然依赖它;共享参数也仍可能通过这条计算路径得到梯度。
将独立样本 pack 在同一序列里时,还要决定是否隔离样本间的 attention。EOS 是一个 token,本身不等于隔离墙;如果要求样本互不影响,必须显式设置边界。
一个 batch 经过的 5 步
flowchart TD
A["Token IDs + attention / loss masks"] --> B["Forward:B × T × V logits"]
B --> C["对齐下一位置,计算有效 token 的 CE"]
C --> D["Backward:累积参数梯度"]
D --> E["Optimizer step:更新可训练参数"]
E --> F["清理梯度,进入下一步"]
Cross-entropy 接收 logits,而不是先手动 softmax 的概率。有效目标数是 $M$ 时,常见的 token 平均 loss 是:
$$L=\frac{1}{M}\sum_{b,t}m_{b,t}\left[-\log p_\theta(x_{b,t}\mid x_{b,<t})\right],\qquad M=\sum_{b,t}m_{b,t}.$$
这里 $m_{b,t}$ 标的是目标位置。全是无效目标的 batch 不能除以 0;应明确跳过或报错。框架的 ignore_index 和 reduction 语义见 PyTorch CrossEntropyLoss。
Batch 变大,究竟哪里变了
假设两个 microbatch 分别有 2 和 8 个有效 token,平均 loss 分别是 1 和 3。直接平均两个数得 2;按全部 token 平均是 $(2\times1+8\times3)/10=2.6$。它们不是同一个训练目标。
因此 gradient accumulation 不只是在循环里多调几次 backward。如果想等价于大 batch 的 token 平均,要按有效 token 总数归一化;框架或分布式实现还可能做额外的平均,需要一起核对。backward() 累加梯度,optimizer.step() 才更新参数,不要把前者次数当成更新次数。
同一条计算链,怎样区分 pretraining 和 SFT
| 设置 | 数据与计分方式 | 不能直接推出 |
|---|---|---|
| Causal pretraining | 从文本序列预测后续有效 token | 模型自然就会遵循指令 |
| Answer-only SFT | 问题作为上下文,通常只对助手答案计分 | 所有 SFT 工具都用相同 mask |
| 视觉指令 SFT | 再加入视觉条件,对目标答案计分 | 加了图片就保证模型使用它 |
比较的是训练设置,不是三种互斥的网络结构。SFT 也可以选择 full-sequence loss,关键是把实际目标写清楚。
先做这些小检查
配套实验文件实现了纯 Python 的 masked next-token loss;测试会检查 padding 不改变结果、shift 是否正确,以及可选的 PyTorch 结果一致性。
python -m unittest discover -s site/tests -p 'test_multimodal_math.py'
此外,可以让一个真实的小模型先过拟合(overfitting)极小 batch,检查梯度是否有限、预期模块是否更新;再在不重叠的验证数据上看 loss 和任务表现。小 batch 能记住只能证明训练链基本通了,不能证明泛化。
想看这条训练链怎样接上图片输入,可以读视觉语言模型,区分“加了哪个输入”和“改变了哪个目标”;训练路线的下一篇是多 token 预测。
本页术语 1
- 过拟合 overfitting
- 训练集越来越好,没见过的数据反而变差