> 没屏蔽的 padding 会自己制造出"表征崩塌"——而那正是你要测的现象
一次训练冒烟运行里,LM loss 用 labels=input_ids 算——也就是把 pad 位也算进去了。 两个后果都很严重:
结论先行¶
一次训练冒烟运行里,LM loss 用 labels=input_ids 算——也就是把 pad 位也算进去了。
两个后果都很严重:
一、探针的 held-out loss 被抬高了:8.40,而真值是 2.90(一个未改动的预训练检查点)。 于是那个本该用来区分"正常锐化"和"真崩塌"的下游探针信号, 根本不是在量困惑度。
二、训练时模型花容量去建模 padding 的结构,而这件事本身就驱动出一个退化表征。 同一个设置下,有效秩的变化:
| 修复前 | 修复后 | |
|---|---|---|
高学习率下 r_eff |
192 → 2.88 | 192 → 84 |
那个戏剧性的"崩塌",大部分是 padding 产物,不是被诱导出来的那个模式。
修法是一个 make_labels():pad 位置填 -100,并且探针批次和训练批次都要用。
修复前的所有数字作废。
为什么¶
这个 bug 的恶性程度来自一个巧合:它制造的伪影,和被研究的现象长得一模一样。
padding 位的隐状态彼此几乎相同(同一个 pad token、相似的位置上下文), 于是它们在表征空间里挤成一团:
- 协方差被一个方向主导 → 有效秩塌向 1;
- 方差比、k_95、RankMe 之类所有基于谱的指标同向移动;
- 而序列越短、pad 越多,效应越强——它还会随数据分布系统性地变化。
所以你不会看到一个"坏掉"的信号,你会看到一个漂亮、单调、可复现的崩塌曲线。 这是最难发现的那一类错误:它不降低结果的质量,它提高结果的说服力。
同一个形状在别处的样子:
- 把
-100忘在某一侧(只在训练里屏蔽、评测里没屏蔽),两边的数不可比; - attention mask 对了但 label mask 没对(这一例);
- 右 padding 与左 padding 混用,导致"最后一个 token"取到 pad 上。
怎么用¶
- 在任何表征/谱类测量之前,先在一个未改动的预训练检查点上量一遍。 如果健康模型就已经接近指标的下界,那你测的不是模型。
- 把 held-out loss 和一个已知基准对齐。 8.40 对 2.90 这个差距足以单独暴露问题—— 一个预训练模型在普通文本上的困惑度量级是已知的,拿它当哨兵。
- mask 要同时检查三处:attention mask、label mask(
-100)、池化时的加权。 三处里漏一处,症状各不相同但都是静默的。 - 用"去掉 pad 之后结果变不变"做一次对照。 把 batch size 设成 1、不 padding 跑一遍,和 padded 批次比。 数字不一致,就是 mask 的问题。
- 伪影修掉之后,明确声明旧数字作废,不要让它们留在同一张表里。
出处¶
- 自己一个项目的训练与探针冒烟运行,2026-08:
labels=input_ids覆盖 pad 位 → held-out loss 8.40 vs 真值 2.90(预训练检查点); 高学习率下r_eff192→2.88(修前)vs 192→84(修后); 修法为make_labels()将 pad 位置为-100,探针批次与训练批次同时应用 - 相关:恒为常数的特征在归一化融合里等于不存在——不报错,还会连累依赖它的阈值、区分"近似漂移"和"真 bug":用发散位置,不用最终指标、 把失败归因给模型之前,先审 verifier——41% 的"模型错误"其实是判分 bug