ROSE / OPD 蒸馏实验的完整实现与训练框架
三个 domain(RLVE 单轮推理、SciWorld 多轮 agent、DAPO-Math)上对比三种蒸馏方式 (teacher-SFT / on-policy distillation / online ROSE)的全部代码。
配套的 checkpoint 与数据在: OPD-dq · ROSE-dq · SciWorld-dq · data-dq
目录
frameworks/
verl_rose/ ROSE 全部线用的 verl(含 recipe/rose/rose_agent_loop.py)
verl_opd/ OPD 全部线用的 verl —— 与上游 thunlp/OPD 的计算路径逐字节等价
verl_sft/ teacher-SFT 用的 verl
agentgym_rl/ AgentGym-RL —— 本项目里只用于 SciWorld 评测, 未用于训练
trinity/ Trinity —— SciWorld 的 ROSE/OPD 八条线是它训的
sciworld/
impl/ 自写的多轮 ROSE/OPD 实现(不依赖上面任何框架)
chains/ 编排脚本
trinity_configs/ Trinity 侧的 workflow 与 yaml
agentgym_eval/ AgentGym 口径的 200 题评测
launchers/
rlve/ dapo/ olmo/
eval/ AIME25 / HMMT25 / RLVE 评测
docs/ ENV_NOTES.md —— 跨家族 ROSE 的环境硬约束
tools/ 归档上传用的脚本
三种方法
teacher-SFT 教师采轨迹, 学生离线模仿。
OPD(on-policy distillation)教师对学生自己采出来的 token 打分,
advantages = teacher_logprobs - student_logprobs, adv_estimator=token_reward_direct,
不做归一化。
ROSE 学生写 K 个 token 的前缀(mask=0), 教师从那里接着写 t 个 token,
CE 只打在教师 token 上(mask=1)。学生学的是"我走到这个状态之后, 老师会怎么继续"。
recipe/rose/rose_agent_loop.py 里有完整说明。
SciWorld 有两套并行实现
| 实现 | 位置 | 产物 |
|---|---|---|
| Trinity | frameworks/trinity + sciworld/trinity_configs |
SCIENCEWORLD_{OPD,ROSE}/ 八条线 |
| 自写多轮 | sciworld/impl/online_{rose,opd}_sciworld.py |
老格式 log.jsonl + step_N/(checkpoint 已清, 脚本保留) |
sciworld/impl/ 那套不依赖 verl 也不依赖 Trinity, 是从头写的:
学生走 N 轮 → 按 turn 边界构造 loss mask → 教师接管后续轮次 → 只在教师 turn 上算 CE。
多轮的 mask 构造见 online_rose_sciworld.py; OPD 侧的 forward_kl_topk 见 online_opd_sciworld.py。
AgentGym-RL 只用于评测(sciworld_agentgym_eval.py, 200 题 / ≤20 轮 / greedy / nothink),
本项目没有用它训练过。
结果
RLVE (16k, n=8)
| line | avg@8 | pass@8 | 解出 |
|---|---|---|---|
| 基线 未训练 | 0.0333 | 0.1111 | 20 |
| official OPD bf16 (140步) | 0.0458 | 0.1444 | 26 |
| OPD bf16 mini16 (140步) | 0.0556 | 0.1722 | 31 |
| OPD fp32 (105步) | 0.0694 | 0.1833 | 33 |
| OPD fp32 (132步) | 0.0667 | 0.1833 | 33 |
| official ROSE (140步) | 0.0701 | 0.1778 | 32 |
| official ROSE seed1 | 0.0729 | 0.1889 | 34 |
| teacher-SFT n4 bs256 | 0.0556 | 0.1500 | 27 |
fp32 那条与 official bf16 只差 dtype(train/mini/n、lr、resp、更新次数全同)。
上游 on_policy_distillation.sh 默认就是 fp32, bf16 是早期的偏离 ——
lr 1e-6 × advantage 10⁻² 的每步相对更新量低于 bf16 的分辨率, 会被舍入吞掉。
SciWorld (AgentGym 口径, 200 题, ≤20 轮, greedy, nothink)
| Score | Success | Pass(>0) | |
|---|---|---|---|
| Qwen3-1.7B 基座 | 0.0479 | 0.0000 | 0.1650 |
| OPD lr1e-6 | 0.0421 | — | — |
| OPD lr1e-5 | 0.1931 | — | — |
| teacher-SFT | 0.2077 | 0.0450 | 0.4100 |
| ROSE | 0.3127 | — | — |
| Qwen3-32B 教师 | 0.4346 | 0.1100 | 0.9350 |
ROSE 在 SciWorld 上的优势(+0.105 over SFT, +0.12 over OPD)远大于 RLVE 上的(+0.016 / +0.002)。
DAPO-Math 上的 ROSE 是负结果
AIME25 / HMMT25 avg@16, thinking, temp0.7 / top_p0.95 / max_new 31744:
| AIME25 | HMMT25 | |
|---|---|---|
| Qwen3-32B 教师 | 0.7125 | 0.5042 |
| Qwen3-4B 未训练 | 0.6521 | — |
| ROSE←32B 30步 (4B) | 0.6125 | — |
| Olmo-3-7B 基线 | 0.5917 | 0.4167 |
| ROSE←32B 30步 (Olmo-7B) | 0.5750 | 0.3854 |
| ROSE←32B 220步 (Olmo-7B) | 0.5583 | 0.3688 |
四组独立观测同向, 且 Olmo 从 30 步到 220 步(跑满一个 epoch)继续下降 —— 排除欠训。 训练本身也早停滞: ce_loss 从 31 步起斜率 −0.00012, grad_norm 降到 0.18。
诊断: TEACHER_MAX_TOKENS=1024, 而实测 ce_target_tokens 达 960-1016
(占预算 94-99%)—— 教师几乎每条都是写到预算耗尽被截断, 从不自己收尾。
配合学生 16000-18600 token 的推理链, 监督只覆盖 token 1024-2048, 约 6%。
学生学到的是"在思考中段接着往下写", 从没被教过怎么收敛到答案。
表现是输出方差变大而能力边界未动: pass@16 靠多蒙中一题涨(两个 benchmark 各 +1 题,
其中一题是 0/16 → 1/16), 而 avg@16 与 pass@1 都下降, 截断率上升。
要检验这个诊断, 应该把 K 提到 4096-8192、teacher 预算给到 2048-4096, 让切点落到推理链中后段。
跨家族 ROSE(Olmo-3-7B ← Qwen3-32B)
学生 vocab 100278 / 教师 151669, 完全不同的词表。rose_agent_loop 有现成通路
(TEACHER_TOKENIZER 非 null 即启用): 学生前缀 ids → decode 成文本 →
套教师的 chat template → 教师续写 → 取回 text → 用学生的 tokenizer 重新 encode。
实测两边对拼接缝文本的 round-trip 都无损, 教师续写语义连贯且不会重复吐 <think>。
环境上的硬约束(vLLM 版本被夹死在 0.11.0、NCCL 只能关 NVLS、verl 序列打包硬依赖
flash_attn 等)见 docs/ENV_NOTES.md —— 每一条都对应一次失败的启动。