RLforge
完整主线 路线17 / 27 · L2 手锻退出路线
实验预计 60 分钟

把分数再往上推:三轮迭代实验

同一套代码,只改超参和 reward,做三轮对照实验,把 GSM8K 分数推到你能达到的最高点。

学完这节你能做到

  • 设计一组只改一个变量的对照实验
  • 用固定测试集比较三轮结果,并解释差异来源
  • 产出一份能给别人看的实验报告

从「跑通了」到「调出来」

L2 前四节让你跑出了 57% 的正确率。这一节不写新代码,只做一件事:用同一套代码做三轮对照实验,把分数再往上推,并且知道是什么推上去的。

后半句才是重点。分数涨了但不知道为什么涨,下次换个任务你还是从零开始猜。

实验纪律

这四条不是形式主义,每一条都对应一种会让你白烧几小时卡时的错误。

一次只改一个变量

❌ 学习率 1e-6 → 3e-6,同时 G 8 → 16,同时加长度惩罚
   → 分数涨了 4 个点,你不知道是哪个起的作用

✅ 只把 G 从 8 改成 16,其余全部不动
   → 分数涨了 4 个点,你知道是 G 的功劳

RL 实验一次两小时,你没有本钱做「改三个参数然后逐个消融」。一次一个。

固定随机种子

import random, numpy as np, torch
SEED = 0
random.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)
# 采样侧也要固定
params = SamplingParams(..., seed=SEED)

不固定种子,两次运行的差异里混着「改动的效果」和「运气」,你分不开。

!但也别只信一个种子

固定种子让实验可复现,不代表结论可靠。同一个配置换三个种子跑,分数可能差 2~3 个百分点。

务实的做法:主实验用固定种子对比;最终结论想确认时,把胜出的那组用 2~3 个种子重跑。

这也意味着:改动带来的提升小于 3 个百分点时,先别下结论。 加上 L0 测出的判分器误差(约 2%),你的实际分辨率在 3~4 个百分点。

每轮都在 held-out 测试集上评

训练日志里的 acc 是训练集上、temperature=1.0 采样的,不能当成绩。正式分数走 L0 那套:

python scripts/eval_gsm8k.py --model out/run-XX/final --split test --greedy

先跑 20 步冒烟

改完参数别直接跑 200 步。20 步(约 12 分钟)能确认:不 OOM、ratio ≈ 1、adv_std 没塌、reward 方向对。这 12 分钟能拦住大部分配置错误。

基准配置

三轮实验都以这组为对照组(就是 L2 跑出来的那次):

模型            Qwen3-0.6B 全参
G               8
每步 prompt 数   8
学习率           1e-6   (AdamW, betas 0.9/0.98)
KL 系数 β        0
clip            0.2 / 0.28
max_tokens      512
temperature     1.0
归一化           序列平权
reward          正确性 1.0 + 格式 0.2 − 截断 0.5
步数             200
────────────────────────────────
基线(训练前)    20.85%
对照组(run-01)  57.9%

第一轮:调 G

假设:G 越大,组内基线估计越准、退化组越少,但采样成本线性上升。

实验G采样量预期
run-0240.5×更快,但优势估计更噪
run-0181×对照组
run-03162×更稳,但慢一倍
✓改 G 时要不要同时改步数

这里有个陷阱:G=16 时每步的采样量翻倍,如果步数不变,模型总共见到的数据也翻倍了。那你比较的就不是「G 的效果」,而是「G + 数据量」的合成效果。

两种公平的比法:

  1. 固定步数(200 步)—— 比的是「同样多的更新次数下,哪个 G 更有效」。这更接近实际关心的问题,但耗时不同。
  2. 固定总采样量 —— G=16 只跑 100 步,G=4 跑 400 步。比的是「同样的卡时预算下哪个划算」。

推荐第 2 种,因为你的真实约束是卡时而不是步数。下面的结果按第 2 种记。

rl@forge目标 0/3
  1. 1.跑 G=4(步数加倍以保持等采样量)
  2. 2.跑 G=16(步数减半以保持等采样量)
  3. 3.对比三组,得出关于 G 的结论
第一轮:G = 4 / 8 / 16,固定总采样量(等卡时预算)。
goals 看目标,hint 要提示。
[rl@forge ~/rlforge]$
help 查看用法 · goals 看目标 · hint 要提示 · ↑↓ 翻历史

第二轮:调 KL 系数

假设:β=0 完全不约束,训练自由但可能漂移;β 太大则把模型拴死在初始分布上,学不动。

实验β预期
run-010对照组(现代配方的常见选择)
run-040.001轻度约束
run-050.01中度约束
▣这一轮要把参考模型加回来

L2 的脚本为了跑通把参考模型省了(BETA=0 且传 None)。要跑 β>0 就必须常驻一份参考模型——0.6B 上 1.1 GiB,显存足够。

顺便强烈建议:即使最终选 β=0,也把参考模型留着。 这样 KL 会被记录下来(只是不进损失),你才能看到模型漂了多远。L4 那份 KL 跑飞的日志就是「省掉参考模型」的代价。

rl@forge目标 0/3
  1. 1.跑 β=0.001
  2. 2.跑 β=0.01,看强约束的代价
  3. 3.对比三组,得出关于 β 的结论
第二轮:β = 0 / 0.001 / 0.01。参考模型已加回,KL 全程记录。
goals 看目标,hint 要提示。
[rl@forge ~/rlforge]$
help 查看用法 · goals 看目标 · hint 要提示 · ↑↓ 翻历史

第三轮:改 reward

前两轮改的都是超参,收益有限(最好的一组也只 +0.8)。这一轮改 reward——按 L1「reward 从哪来」的说法,这是影响最大的一项。

三个候选改动,各自针对一个已观察到的现象:

实验改动针对的现象
run-06格式分 0.2 → 0.05检查格式分有没有在偷分
run-07加长度惩罚(超 300 token 线性扣分)len 涨到 152,想看能不能压住
run-08动态采样:过滤 std=0 的组退化组占 9.1%,想把这部分捞回来
rl@forge目标 0/4
  1. 1.把格式分压到 0.05,检验它有没有在偷分
  2. 2.加长度惩罚,看能不能压住 len 又不伤 acc
  3. 3.上动态采样,过滤退化组
  4. 4.对比四组,选出最终配置
第三轮:改 reward 与采样策略。这一轮的收益比调超参大得多。
goals 看目标,hint 要提示。
[rl@forge ~/rlforge]$
help 查看用法 · goals 看目标 · hint 要提示 · ↑↓ 翻历史

实验报告模板

三轮做完,把结果整理成一份别人(和两周后的你)能看懂的报告:

# GRPO on GSM8K / Qwen3-0.6B / 单卡 5090

## 结论
基线 20.85% → 63.2%(greedy pass@1, test 1319 题)
起决定作用的单项改动是动态采样(+5.3),超参调整贡献很小。

## 实验分辨率
判分器误差 ≈ 2.0%(50 条人工核对),种子间波动 ≈ 3%
⇒ 差异小于 4 个百分点视为无差异

## 三轮对照

| run | 改动 | test | Δ | 判定 |
| --- | --- | --- | --- | --- |
| 01 | 对照组 | 57.9% | — | — |
| 02 | G 8→4 | 53.6% | −4.3 | 确定更差 |
| 03 | G 8→16 | 58.7% | +0.8 | 无差异 |
| 04 | β 0→0.001 | 58.3% | +0.4 | 无差异 |
| 05 | β 0→0.01 | 46.1% | −11.8 | 确定更差 |
| 06 | 格式分 0.2→0.05 | 58.4% | +0.5 | 无差异 |
| 07 | 加长度惩罚 | 53.9% | −4.0 | 确定更差 |
| 08 | 动态采样 1.5× | 63.2% | +5.3 | 确定更好 |

## 我的解释

1. G 的收益递减点在 8 附近。G=4 时 std=0 的组占 18.4%,
   近五分之一采样白跑;G=16 把这个数降到 6.2%,但等卡时下
   步数减半抵消了收益。

2. β=0.01 拖后腿的机制是 KL 不区分有益/有害漂移。
   len 只涨到 108(对照组 152),连"多写几步推理"这个
   有益变化都被压住了。⇒ 保留 β=0,但留参考模型做监控。

3. 长度惩罚在这个任务上有害,因为 len 上涨伴随 acc 上涨
   (正常变长)。⇒ 长度惩罚只在 len 涨而 acc 不涨时才用。

4. 动态采样是唯一确定有效的改动。看 adv_std 就明白:
   全程 1.0 以上 vs 对照组降到 0.664。它直接消除了
   "退化组白跑"这个效率损失。

## 下一步
- 换更难的数据子集(当前 adv_std 后期仍在降)
- 试 pass@8 看上限(训练前 48.3%,现在多少?)
- 换 1.7B + LoRA,看结论是否迁移
✓报告里最值钱的是「我的解释」那一节

表格是机器产出的,解释只有你能写。

而且注意上面每条解释都引用了一个指标作为机制证据(std=0 占比、len 终值、adv_std 走势),不是「我觉得」。这是把实验做成知识而不是做成一堆数字的关键。

Checkpoint单选

要比较 G=8 和 G=16 的效果,为什么推荐「固定总采样量」而不是「固定步数」?

Checkpoint单选

β=0.01 让 test 分数从 57.9% 掉到 46.1%,同时 len 只涨到 108(对照组 152)。机制是什么?

Checkpoint单选

加长度惩罚后 len 从 152 压到 104,但 acc 掉了 4 个点。这验证了什么判据?

Checkpoint单选

动态采样(过采 1.5 倍、丢掉 std=0 的组)带来 +5.3 个百分点。从哪个指标能看出它的作用机制?

这节课的落点

  • 四条纪律:一次一个变量、固定种子、held-out 评测、先跑 20 步冒烟
  • 实际分辨率 = 判分误差(约 2%)+ 种子波动(约 3%)≈ 4 个百分点,小于它别下结论
  • 比较 G 时用等总采样量(等卡时),否则「G」和「数据量」两个变量同时变了
  • G 的收益递减点在 8 附近:G=4 退化组占 18%,G=16 收益被步数减半抵消
  • β=0.01 有害的机制是 KL 不区分有益漂移;保留 β=0 但留参考模型做监控
  • 长度惩罚只在「len 涨而 acc 不涨」时用;本任务属于正常变长,惩罚它伤 acc
  • 唯一确定有效的改动是动态采样(+5.3),机制看 adv_std 全程维持 1.0 以上
  • 超参调整收益有限,改 reward 与采样策略的收益大一个量级
  • 报告里最值钱的是「我的解释」,且每条解释要引用一个指标作为机制证据