用数字区分小模型能做与不能做的事
目标
测出基准预训练模型和 SFT 模型的困惑度与 BPB,并用 100 篇文档做出过拟合曲线。测量 SFT 中见过的和没见过的问题、两位数加法、训练长度之外的位置、预训练模型是否知道事实,用数字留下这个模型做不到什么。
为什么重要
即使是损失很低、见过的问题全都答对的模型,只要向分布之外走出一格,也会崩溃。不知道这条界线,就会把模型用在错误的地方。在大模型里,多种原因混在一起,看起来模糊,而 100 万参数的模型,“规则会泛化,事实靠背”“知道的东西换了提问形式就取不出来”“学了新格式就忘掉旧能力”,都各自以清晰的数字显现出来。 评估不是只测新教的东西。SFT、DPO 之后要重新测量原有能力,忘掉的东西才会留下记录——这个实验的第一步就是这件事。
步骤
- 把基准预训练模型和 SFT 模型在整个验证语料上的损失、困惑度、BPB 写入 /root/mm/eval/ppl.json。
- 只用训练语料最前面的 100 篇文档训练 300 步,每 25 步把训练损失、验证损失记录到 /root/mm/eval/overfit_log.csv,把权重保存为 /root/mm/eval/overfit.pth。
- 把基准 SFT 模型的正确率(忽略口吻),按 SFT 中见过的问题和没见过的问题分开,写入 /root/mm/eval/heldout.json。
- 把两位数加法 10 道题的回答和正确率写入 /root/mm/eval/ood.json。
- 把 256 个令牌窗口里位置 1–127 和 128–255 的平均损失写入 /root/mm/eval/length.json。
- 数出预训练模型能否针对每个村庄续写特产和守护动物,写入 /root/mm/eval/probe.json。
- 在 /root/mm/eval/report.md 中写
## 무엇을 재나、## 과적합、## 작은 모델이 못 하는 것三节(三个标题为韩文,依次意为“测量什么”“过拟合”“小模型做不到的事”),并放入预训练模型的 BPB 和两位数加法的正确率。
参考
- BPB = 总损失(奈特,按预测的令牌数计)÷ ln 2 ÷ 这些令牌所表示的 UTF-8 字节数。每个窗口的第一个令牌不做预测,所以要去掉,bos、eos、pad 没有字节,也要去掉。
- 用 100 篇文档训练,在节点上大约 20 秒。
- 常见错误:先对每个批次做 exp 再取平均来算困惑度(应该对平均损失做 exp);在过拟合曲线里只看训练损失;比较正确答案时连口吻(敬体与平体的句尾)也要计较。
- 原文:MiniMind README — 分词器·BPB·zero 模型示例 · eval_llm.py · model_minimind.py — rope_scaling(YaRN)
困惑度与 BPB
把 /opt/mm/ref/val.npy 按每 128 个令牌不重叠地切成的全部窗口,分别放入基准预训练(pretrain.pth)模型和 SFT(sft.pth)模型,把平均损失(按窗口数加权平均)、困惑度(对平均损失做 exp)、BPB,以 {"pretrain": {"loss", "ppl", "bpb"}, "sft": {…}} 写入 /root/mm/eval/ppl.json。
BPB 的分母,是把每个窗口去掉第一个令牌之后剩下的令牌(编号为 3 以上,即不含特殊令牌)用 tok.decode 还原,再按 UTF-8 数出的字节数。想一想 SFT 模型的困惑度为什么是几百——它是以 1e-3 的学习率学了什么。
用 100 篇文档造成过拟合
从 /opt/mm/ref/train.npy 中只截取到第 101 个 bos 之前(100 篇文档),用 mmkit.seed_all(0) 创建新模型,以批次 8、长度 128、lr 3e-3(不做安排)训练 300 步,每 25 步把 step,train_loss,val_loss(验证用 mmkit.lm_loss(model, val, n_batches=4))记录到 /root/mm/eval/overfit_log.csv,并把最后的权重保存为 /root/mm/eval/overfit.pth。
找一找验证损失最低的那一步。在那之后,训练损失一直下降,验证损失却上升——这就是模型不再学规则,而开始背那 100 篇的时刻。评分器会看最低点是否在终点之前、到终点是否上升了 0.2 以上,以及训练损失与验证损失是否拉开了 0.5 以上。
见过的问题与没见过的问题
用基准 SFT 模型,对 sft.jsonl(见过的问题)和 sft_val.jsonl(没见过的问题)各自的单轮对话前 60 个,用贪心解码回答,去掉末尾的 입니다、이다 后的正确率,按问题类型(fact:含有“村”字的问题,add:加法),以 {"seen": {…}, "held_out": {…}} 写入 /root/mm/eval/heldout.json。
没见过的事实问题,指的是预训练语料里出现过、但在 SFT 里没见过那种问法的问题。把没见过的加法配对与正确率拿来比一比——规则和事实的泛化方式是不同的。
两位数加法
向基准 SFT 模型问 10 道两位数加法题(12+15、23+41、30+30、45+12、17+21、50+25、11+11、34+52、26+13、40+19),提问形式为 '{a} 더하기 {b}는?'(韩文,意为“{a} 加 {b} 等于多少?”),把回答以正确答案的数字开头的份额,以 accuracy 和 rows(q:“12+15”的形式,answer:模型的回答)写入 /root/mm/eval/ood.json。
模型只见过 100 道一位数加法。回答的格式(数字 + 敬体或平体的句尾)会遵守,但数字会像一位数加法的答案那样出现——它没能学会规则的“范围”。
训练长度之外的位置
把 val.npy 按每 256 个令牌切成的全部窗口放入基准预训练模型,把各位置的下一个令牌损失分成位置 1–127(loss_pos_1_127)和 128–255(loss_pos_128_255)两段分别取平均,连同 windows 一起写入 /root/mm/eval/length.json。
用 cross_entropy(…, reduction="none") 得到每个位置的损失,再分成两段。这个模型只用 128 个令牌的窗口训练过。RoPE 用的是相对位置,所以不会崩溃,但在没见过的远距离上不够准确。
预训练模型知道事实吗
对 /opt/mm/data/world.json 中的每个村庄,向基准预训练模型放入 [bos] + '{마을} 마을의 특산물은'(占位符为村名,其余韩文意为“……村的特产是”)和 '… 수호 동물은'(韩文,意为“……的守护动物是”),用 6 个令牌的贪心解码续写,数出以正确答案开头的个数,以 specialty_known、animal_known、held_out_known_by_pretrain(留出的 4 个事实中答对的个数)写入 /root/mm/eval/probe.json。
SFT 模型答错没见过的事实问题,是因为“不知道”,还是因为“知道却无法以提问的形式取出来”,在这里分辨。守护动物是六种按村庄顺序循环,容易背;特产则是十二个一一配对的。
这个模型做不到的事
在 /root/mm/eval/report.md 中写 ## 무엇을 재나、## 과적합、## 작은 모델이 못 하는 것 三节(三个标题为韩文,依次意为“测量什么”“过拟合”“小模型做不到的事”),并以数字放入第 1 步预训练模型的 bpb 和第 4 步的 accuracy。
在最后一节,请按原因(不知道的事实、知道却取不出来的事实、没见过的范围、忘掉的能力)各用一行写下“做不到的事”。这个模型不能用在哪里,答案就是这份清单。