原创教学案例 · 最近核验:2026-10。算例使用预先给定的 token loss;不是模型训练结果。

“训练没报错,loss 也降了”只说明程序在优化某个目标。这个目标是否等于我们想教的行为,还得把一条样本打开看。

先分组,再划分训练集和测试集

“报名最晚到哪天?”和“这次报名的截止日期是什么?”可能来自同一个原始问题。若逐行随机划分,一种问法进训练,另一种进测试,模型可能靠见过的内容答对,而不是学会处理新问题。因此,先给这些改写分配同一个 group_id,再按组划分数据;中英文改写也放在一起。

分组方式也取决于你想测什么。想知道模型能否处理新文档,就留出整份文档或一组相关文档;想知道它能否处理未来请求,就按时间切分,再检查前后有没有重复内容。先明确评估问题,再选划分方法。

字段 为什么保留
样本 ID、问题族 ID 回溯、去重、避免改写泄漏
输入证据的版本 不拿今天的答案配昨天的材料
示范来源与审阅状态 区分人工校正、模型生成和未审阅样本
可回答 / 缺证据 / 冲突类型 检查是否只教会“总是作答”
拆分与预处理版本 重建同一批训练、开发和测试样本

teacher 生成的答案可以是候选(candidate)示范,不自动变成真值。若训练与评估都照抄同一个 teacher 的判断,我们可能只是让学生更像 teacher,而不是让任务做得更好。保留独立人工核查的切片(slice),尤其检查缺证据和来源冲突。

loss mask:哪些 token 参与训练目标?

以 decoder-only 模型的 SFT 为例,模型仍然要读问题和证据,但计算 loss 时,可以只计算 assistant 回答对应的目标 token。这里的 loss mask 决定哪些位置计入训练目标,不负责移除输入上下文,也不同于 attention mask。

为了看清区别,假设前 2 个目标位置属于提示,后 2 个属于回答,逐 token loss 已经算好:

token loss:  4.0   3.0   0.8   0.4
loss mask:    0     0     1     1
有效均值: (0.8 + 0.4) / 2 = 0.6

只算回答时,均值是 0.6;四个位置都算,均值变成 2.05。后者也在训练模型预测提示内容。哪种方式适合当前任务要另外比较,但它们确实是在优化不同的目标。

真实实现还要检查 next-token shift:每个位置的输出是否在预测下一个 token,mask 是否与移位后的目标对齐。padding、结束标记(EOS)和工具消息也要检查。本例的 loss 和 mask 已经对齐,不重复做移位。

被 mask 的提示位置不直接贡献预测损失,但它们仍是回答的上下文。回答产生的梯度可以经过这些上下文的计算路径;所以“提示不计 loss”不等于“提示相关的计算完全没有梯度”。

TRL v0.29.0 的 SFTTrainer提供 assistant_only_loss,但聊天模板必须支持返回 assistant mask。配置写了 True 不够,要打印一条实际 token/label 对照,确认模板确实产生了想要的边界。

按 token 平均,和按回答平均

样本 A 只有一个有效 token,loss 为 0.2;样本 B 有三个,均为 1.0:

$$ L_{\text{token}}=\frac{0.2+1+1+1}{4}=0.8,\qquad L_{\text{sample}}=\frac{0.2+1}{2}=0.6. $$

按 token 平均时,A 占 1/4 的权重,B 占 3/4;先算每条回答的均值、再按样本平均时,A、B 各占 1/2。后一种相当于给每条回答相同权重,前一种给每个有效 token 相同权重。长回答并不是一定会主导实际梯度,但它在这个平均公式中占的份额更大。

多卡训练也会遇到类似问题:各卡有效 token 数不同时,直接平均各卡的均值,并不等于全局 token 均值。还要结合框架的梯度缩放检查实际实现。

这里先要求每个样本至少有一个有效 token。全被截掉的回答不应该悄悄变成零 loss;报错并统计这种样本,才能看出是不是长度设置把训练目标切没了。

训练前,拿一个 batch 手算

参考实现提供 masked_objective,同时返回 token 平均和样本平均。它拒绝长度不匹配、非二值 mask、非有限 loss 和没有有效目标的样本。

python3 practice/post-training/code/experiment_checks.py
python3 -m unittest discover -s site/tests -p 'test_practice_projects.py'

运行时会看到 0.8 与 0.6,对应上面的两种平均方式。再把某条样本的 mask 全改成 0,检查程序是否报错。接上真正的训练器(trainer)后,也拿同一个 batch 对照逐位置的 loss;一条平滑下降的总 loss 曲线,未必能暴露这里的问题。

需要进一步看损失如何产生,接着读 PyTorch 训练循环。这里检查分母和数据约定,不演示优化器更新。

下一篇:多卡怎么选,先看卡在哪里 · 直接看实验比较

本页术语 3
候选 candidate
召回阶段捞出来、等待精排挑选的内容
切片 slice
按内容或用户属性划分的子群体,用来检查平均值掩盖了什么
训练器 trainer
负责计算 loss、反向传播并更新模型参数的系统