闯关:五种炸法
熵崩塌、reward hacking、长度爆炸、KL 跑飞、OOM。每一种给现场证据,你来判断和处置。
学完这节你能做到
- 看到日志和曲线就能认出是哪一种失效
- 对每种失效给出第一步处置动作,而不是把所有参数都动一遍
- 知道哪些失效是必须重启训练的,哪些可以边跑边救
五种炸法
RL 训练失败的方式远多于成功的方式。好消息是:常见的就那五种,而且每种都有特征指纹。
这一节给你现场证据,你来判断和处置。原则贯穿全节:
先看曲线,再改代码。 不要一次动三个参数。
先把 L2 那八个指标的诊断价值列成一张表——这是全站最该记住的一张表:
| 指标 | 健康 | 异常时指向 |
|---|---|---|
reward | 缓升 | 平 = 没学;暴涨 = 可能在刷分 |
acc(只看答案对错) | 与 reward 同向 | 与 reward 背离 = reward hacking |
entropy | 缓慢下降 | 趋近 0 = 熵崩塌 |
kl | 缓慢上行 | 陡增 = 跑飞 |
adv_std | 0.5 ~ 1.2 | 趋近 0 = 大量组退化,白跑 |
clip_frac | 几个百分点 | > 20% = 单步太猛 |
ratio_mean | ≈ 1.0 | 偏离 = 权重没同步 |
len | 稳定或与 acc 同向 | 单调暴涨而 acc 不动 = 长度作弊 |
熵是「输出分布有多分散」,直接反映探索能力。它是唯一能提前警告熵崩塌的指标。
# 在算 logprob 的地方顺手算
probs = torch.softmax(logits.float(), dim=-1)
entropy = -(probs * torch.log(probs + 1e-10)).sum(-1) # [B, T]
ent = (entropy * mask).sum() / mask.sum()炸法一:熵崩塌
症状:熵单调下降到接近 0。所有回答变得几乎一样,reward 卡在某个值不动。
机制:RL 是正反馈系统。某个模式偶然拿了高分 → 概率被提高 → 更容易被采到 → 又拿高分 → 概率更高……最后模型只会输出这一种模式,探索完全停止。
一旦熵塌了,组内没有差异,adv_std 也跟着趋近 0,训练实质上停了。而且不可逆——概率分布已经收缩,你降学习率也回不来。
处置(按顺序试):
| 动作 | 说明 |
|---|---|
| 提高 temperature | 最直接。1.0 → 1.1/1.2 |
| 加熵奖励 | 损失里加 - entropy_coef × entropy,从 0.001 起 |
| 降学习率 | 减慢正反馈的速度 |
| 从更早的 checkpoint 重启 | 熵已经塌了就救不回来了,只能回滚 |
所以 entropy 必须记进日志,而且要在它掉到 0.3 以下时就干预,不要等它到 0。
slime 有 --entropy-coef 参数(默认 0.00)就是为这个准备的。
炸法二:reward hacking
症状:reward 稳步上升,acc 不动甚至下降。
机制:你的 reward 有一条不解决问题就能拿分的捷径,模型找到了。
L2 那节我们主动找过一遍作弊路径,但真实任务里的捷径往往没那么显眼。常见形态:
| 形态 | 具体行为 |
|---|---|
| 刷格式分 | 套标签但答案是空的或随便填 |
| 赌答案 | 列举多个候选,赌抽取器取到对的那个 |
| 复述题目 | 把题目抄一遍凑长度和关键词 |
| 钻抽取器 | 利用「取最后一个数字」之类的兜底规则 |
| 讨好判分器 | 用 LLM-as-judge 时,输出判分模型偏好的风格而非更好的内容 |
处置:
- 先定位:把 reward 拆成分项记录(
reward_correct、reward_format分开),看是哪一项在涨 - 抽样看原文:打印 reward 最高的 10 条回答,肉眼看它在干什么
- 堵漏:收紧抽取规则、降低作弊项的权重、加针对性惩罚
调学习率、调 KL 都救不了它。唯一的修法是改 reward。
而且它会反复出现:你堵了一个洞,模型会找下一个。这就是 RL 工程里最耗时的部分——某种程度上,RL 调试就是和自己的 reward 函数博弈。
炸法三:长度爆炸
症状:len 单调上涨直到全部顶到 max_tokens,trunc 比例飙升,acc 不涨或下降。
机制:有三个可能来源,要分清:
- per-token 归一化(L2 讲过):长回答的 token 多,对损失贡献大,变相奖励写长
- 长回答恰好更容易对:模型学到「多写几步更可能算对」——这是真实的正相关,不算作弊
- 纯粹的作弊:写一堆无关内容凑长度,钻长度相关的 reward
看 acc 和 len 的关系:
len涨、acc也涨 → 正常。写得更详细确实带来了更高正确率(L2 那次训练就是这样:len 78→152,acc 21%→57%)len涨、acc平 → 有问题。多写的部分没有价值len涨、acc降 → 确定是问题。而且可能因为大量截断导致答案缺失
只看 len 判断不了,必须两个一起看。
处置:
| 动作 | 说明 |
|---|---|
| 换成序列平权归一化 | 治来源 1,最干净 |
| 加长度惩罚 | 超过某长度线性扣分。但要小心:惩罚太重会压制正常的推理 |
提高 max_tokens | 如果截断率高,先排除「被截断导致误判」 |
| 检查截断样本的 reward | 截断的样本要不要给负分?给多少? |
炸法四:KL 跑飞
症状:kl 陡增(不是缓升),输出开始出现胡话、重复、乱码、混语言。
机制:模型为了刷 reward,把「说人话」的能力训丢了。KL 度量的正是「离训练前那个模型有多远」。
单卡上很常见的一个诱因:beta=0 且不带参考模型——完全没有约束,也完全看不见漂移。
处置:
- 确认参考模型加载对了(KL 应该恒 ≥ 0,如果出现负值是 bug,不是跑飞)
- 把
beta从 0 提到 0.001 ~ 0.01 - 降学习率
- 回滚到 KL 陡增之前的 checkpoint
现代配方确实常把 KL 系数设 0(slime 的入门例子就是 --kl-loss-coef 0.00),但它同时开着 --use-kl-loss——KL 不进损失,但作为监控指标记录。
单卡上如果你为了省 1.1 GiB 而完全不要参考模型,就失去了这个预警。建议留着它。
炸法五:OOM
症状:不用说。
机制与处置已经在 L0 显存账本和 L3 colocate 那两节讲透了。这里只放一张速查表:
traceback 在 vLLM 栈 → 采样侧不够 → 降 max_model_len / 降并发 / 提 util
traceback 在 transformers 栈 → 训练侧不够 → 降 micro batch / 开检查点 / 降 util
这种情况尤其恼人,原因通常是:某一批采样出了特别长的回答,峰值显存超了。
对策:
- 按
max_completion_length而不是平均长度做显存预算 - 留 10%~15% 余量
- 加 checkpoint,这样至少不用从头再来
闯关
下面是六份现场。先自己判断,再敲命令看诊断。
- 1.认出熵崩塌
- 2.认出 reward hacking
- 3.认出权重同步失效
- 4.认出长度爆炸
- 5.认出 KL 跑飞
- 6.认出健康的训练,并说出它唯一需要留意的地方
六份训练日志,每份是一种失效(有一份是健康的)。 用 cat 打开,自己判断是哪一种炸法,再用 diagnose 对答案。 输入 goals 看目标,hint 要提示。
通用处置流程
不管遇到哪种,按这个顺序:
1. 别改代码。先把八个指标画成图看一遍
2. 确定是哪一种失效(用上面的指纹表)
3. 找拐点:从第几步开始不对的?
4. 从那一步之前的 checkpoint 重启
5. 只改一个参数
6. 跑 20 步冒烟,确认方向对了再放长
「降学习率 + 提 temperature + 加 KL」一起上,然后训练好了——你不知道是哪个起了作用,下次遇到还是要重来。
一次一个。RL 实验本来就贵,别把它变得更贵。
entropy 单调降到 0.01,8 条采样完全一样,reward 和 acc 都冻结。这是什么,怎么办?
`len` 从 78 涨到 152,`acc` 从 21% 涨到 57%。这是长度爆炸吗?
`ratio_mean` 从 1.0 单调跌到 0.54,`clip_frac` 涨到 66%,但 `adv_std` 正常。指向什么?
遇到失效时,下面哪些是正确的处置原则?(多选)
这节课的落点
- 八个指标的诊断表是全站最该记住的一张表
- 熵崩塌:entropy → 0,输出同质,不可逆,要在 < 0.3 时干预
- reward hacking:reward 涨而 acc 不涨;必须拆分项记录;只能改 reward
- 长度爆炸:len 涨且 acc 不涨;trunc > 15% 要处理;根因常是 per-token 归一化
- KL 跑飞:KL 陡增 + entropy 上升 + 输出变噪声;与熵崩塌方向相反
- OOM:看 traceback 在哪一侧;按 max 长度而非平均长度做预算
- 用正常的指标做排除(adv_std 正常 → 排除数据侧)
- 处置纪律:先看图 → 定失效 → 找拐点 → 回滚 → 只改一个参数 → 冒烟 20 步