闯关:读第一次训练曲线
给你六组真实形状的训练曲线,判断哪些是在学、哪些是在作弊、哪些已经废了。
学完这节你能做到
- 从 reward、KL、长度、熵四条线的组合形状判断训练状态
- 看到异常曲线时,说出下一步该改哪个参数
- 建立「先看曲线再改代码」的习惯
先把曲线画出来
你的训练脚本现在往 stdout 打一行行数字。光看数字看不出形状——趋势、拐点、两条线的相关性,全都要画出来才看得见。
二十行代码,从日志画出八张图:
# scripts/plot_log.py
import re, sys
import matplotlib.pyplot as plt
KEYS = ["reward", "acc", "entropy", "kl", "adv_std", "clip", "ratio", "len"]
def parse(path):
rows = []
for line in open(path):
m = re.match(r"\[\s*(\d+)\]", line)
if not m:
continue
row = {"step": int(m.group(1))}
for k in KEYS:
v = re.search(rf"\b{k}=([\d.]+)%?", line)
if v:
row[k] = float(v.group(1))
rows.append(row)
return rows
rows = parse(sys.argv[1])
steps = [r["step"] for r in rows]
fig, axes = plt.subplots(2, 4, figsize=(18, 7))
for ax, k in zip(axes.flat, KEYS):
ys = [r.get(k) for r in rows]
ax.plot(steps, ys, lw=1.4)
ax.set_title(k)
ax.grid(alpha=0.3)
# ratio 的参考线画在 1.0,一眼看出有没有偏离
if k == "ratio":
ax.axhline(1.0, color="red", ls="--", lw=0.8)
plt.tight_layout()
plt.savefig("logs/curves.png", dpi=120)
print("→ logs/curves.png")
python scripts/plot_log.py logs/run-01.log
-
ratio那张图画一条 y=1.0 的红色参考线。 权重同步失效的特征是 ratio 单调偏离 1,有参考线时一眼就看出来;没有的话你得盯着 y 轴刻度读数。 -
reward和acc画在一起(或者上下相邻)。 这两条线的相对关系比各自的绝对值重要得多——它们背离就是 reward hacking。相邻摆放能让你不用来回切图。
健康的形状长什么样
先建立参照。这是 L2 那次成功训练的八条曲线(200 步):
| 指标 | 起点 → 终点 | 形状 | 为什么是健康的 |
|---|---|---|---|
reward | 0.412 → 0.847 | 缓慢上升,后期趋平 | 在学,且收益递减是正常的 |
acc | 21.1% → 57.4% | 与 reward 同向 | 涨的是真能力,不是格式分 |
entropy | 1.842 → 1.498 | 缓慢下降,远离 0 | 在收敛但仍有探索空间 |
kl | 0.002 → 0.045 | 缓慢上行 | 有漂移但量级安全 |
adv_std | 1.031 → 0.664 | 缓慢下降 | ⚠️ 唯一要留意的(见下) |
clip_frac | 0.000 → 0.041 | 从 0 缓升到几个百分点 | 更新幅度健康 |
ratio_mean | 恒 ≈ 1.0000 | 平的 | 权重同步正常 |
len | 78 → 152 | 上升,且与 acc 同向 | 写详细带来了更高正确率 |
判断曲线用的词汇很小,就四个:
- 缓升 / 缓降:健康的变化都是渐进的
- 平掉:不再变化 —— 可能是收敛,也可能是卡住
- 陡增 / 暴涨:几步内跳一个数量级 —— 几乎总是坏消息
- 单调偏离:本该稳定的量持续往一个方向走 —— 通常是 bug 而不是现象
八条曲线里,只有 ratio_mean 属于「本该稳定」那一类。其余七条都在变化,你要判断的是变化的方向和速率。
adv_std 下降:唯一一个「正常但要留意」的信号
adv_std 从 1.03 降到 0.66,这条值得单独说,因为它既不是故障也不能忽略。
机制是这样的:
模型变强
→ 简单题开始大面积全对
→ 那些组的 reward 全是 1,std = 0
→ 整组优势归零,这批白跑
→ 平均下来 adv_std 就降了
所以它其实是在告诉你:可采信息在减少,边际收益在下降。
判读标准:
| adv_std | 含义 | 处置 |
|---|---|---|
| 0.8 ~ 1.2 | 健康,组内差异充足 | 继续 |
| 0.5 ~ 0.8 | 后期正常,收益开始递减 | 留意 |
| 0.3 ~ 0.5 | 大量组退化,效率明显下降 | 换难题 / 上动态采样 |
| < 0.2 | 训练实质已停 | 停下来,别烧卡时 |
三个对策(L3 读 slime 时会看到工业框架怎么做):
- 换更难的数据子集 —— 最直接
- 动态采样:多采一些 prompt,丢掉
std=0的组,补到目标 batch(DAPO 的做法) - 加连续的格式分 —— 让分数不再只有 0/1,全对的组也能有差异
两者都会让 adv_std 降,但原因相反,处置也相反:
| adv_std 因题目太简单而降 | adv_std 因熵崩塌而降 | |
|---|---|---|
entropy | 正常(1.4 左右) | 单调趋近 0 |
| 8 条采样 | 内容不同,只是都对了 | 几乎完全一样 |
acc | 高(比如 57%) | 卡在某个中等值 |
| 处置 | 换难题 | 提 temperature / 回滚 |
区分方法:看 entropy 和采样原文。 这是本节最实用的一条辨别。
辨认训练:容易混的四组
L4「五种炸法」会给五种命名失效的完整诊断与处置。这一节练的是更基础的一件事:看到相似的现象,能不能分辨出不同的原因。
- 1.区分「熵崩塌导致的平」与「同步失效导致的平」
- 2.区分「题目变简单」与「熵崩塌」造成的 adv_std 下降
- 3.区分「正常变长」与「长度爆炸」
- 4.区分「batch 太小的正常噪声」与「学习率过大」
四组配对。每组两份日志表面现象相似,原因完全不同。 用 pair 看一组,diff 对答案。先自己判断再看。 goals 看目标,hint 要提示。
一张判读表
把八条曲线的诊断价值压成一张表。这是全站最该记住的一张表,L4 会反复用到:
| 指标 | 健康 | 异常时指向 |
|---|---|---|
reward | 缓升 | 平 = 没学;暴涨 = 可能在刷分 |
acc | 与 reward 同向 | 与 reward 背离 = reward hacking |
entropy | 缓降,远离 0 | 趋近 0 = 熵崩塌;上升 = 可能 KL 跑飞 |
kl | 缓升 | 陡增 = 跑飞 |
adv_std | 0.8 ~ 1.2 | 趋近 0 = 组内没差异(成因看 entropy) |
clip_frac | 几个百分点 | > 20% = 单步太猛,降学习率 |
ratio_mean | 恒 ≈ 1.0 | 单调偏离 = 权重没同步 |
len | 稳定或与 acc 同向 | 涨而 acc 不涨 = 长度作弊(配合 trunc 看) |
四组配对里反复出现同一个手法:
- A2 的
adv_std正常 → 排除数据侧 → 问题在同步 - B1 的
entropy正常 → 排除熵崩塌 → 是模型真变强了 - D1 的
clip和gnorm正常 → 排除更新震荡 → 只是观测噪声
症状告诉你「不对」,正常的指标告诉你「不对在哪」。 所以八条要记全——少记一条,就少一个排除工具。
两份日志的 reward 都平掉了。一份 entropy=0.009、ratio=1.0000;另一份 entropy=1.774、ratio=0.5407。分别是什么?
`adv_std` 降到 0.24。怎么判断是「模型变强了」还是「熵崩塌」?
两份日志的曲线都剧烈抖动。一份 clip=0.011 / gnorm=0.8 / 有效 batch 8;另一份 clip=0.419 / gnorm=6 / 有效 batch 64。哪个要修,修什么?
这节课的落点
- 先把八条曲线画出来;
ratio图加一条 y=1.0 参考线,reward和acc相邻摆放 - 四个形状词:缓升缓降(健康)、平掉、陡增、单调偏离(通常是 bug)
- 八条里只有
ratio_mean属于「本该稳定」,其余都在变,判断的是方向与速率 adv_std下降是「正常但要留意」:成因看entropy和采样原文的多样性- 「reward 平掉」「adv_std 低」「len 涨」「曲线抖」这四种症状都对应两种以上原因
- 诊断套路:症状告诉你不对,正常的指标告诉你不对在哪 —— 所以八条要记全
- 五种命名失效的完整诊断与处置在 L4「五种炸法」