数据截至 (上游 commit 1416fa0cf215)
03 · 评测管线与数据卫生
这一章讲什么: 训练配方之外,「数字可信」需要的两套基础设施——评测侧(lighteval 任务注册、训练中途自动评测、pass@1 的采样方差)和数据侧(8-gram 去污染、pass rate 过滤)。这些是不性感但决定复现成败的部分。
1. 它要解决的小问题
复现 R1 的 benchmark 数字,撞上三个脏问题:
- 方差:AIME 2024 只有 30 道题,单次采样的 pass@1 波动极大——论文数字到底是多少次采样的平均,DeepSeek 没写(README.md:529-539);
- 污染:从网上爬的训练题很可能混着 benchmark 原题,分数虚高;
- 难度失配:喂给 GRPO 的题如果全对或全错,组内奖励无差异,梯度为零——白训。
这一章的三个机制各管一个。
2. 评测编排:把「跑 benchmark」做成一键作业
2.1 任务注册表
src/open_r1/utils/evaluation.py:52-59 用一张字典固定了 R1 复现的评测清单:
| 注册名 | lighteval 任务 | 套件 |
|---|---|---|
math_500 | math_500 | lighteval 内置 |
aime24 / aime25 | 同名 | lighteval 内置 |
gpqa | gpqa:diamond | lighteval 内置 |
lcb / lcb_v4 | lcb:codegeneration(_v4) | extended(自定义) |
register_lighteval_task(evaluation.py:27-49)把任务名拼成 lighteval 的 suite|task|fewshot|0 格式。真正的评测引擎是 lighteval + vLLM 后端,本仓库只做编排;lighteval 本身见 lighteval teardown。
2.2 两个「土但鲁棒」的资源推断
提交评测作业前要决定用几张卡、要不要张量并行。实现里有两个启发式:
- 参数量:先读 Hub 上 safetensors 元数据;读不到就用正则从 repo id 里抠
7b、8x7b这种字样换算(src/open_r1/utils/hub.py:89-118,get_param_count_from_repo_id)。≥30B 就开 TP(evaluation.py:79-83)。 - GPU 数:vLLM 要求「注意力头数能被 GPU 数整除、且 64 能被 GPU 数整除」,
get_gpu_count_for_vllm(hub.py:121-132)从 8 往下递减直到两个整除都成立。
evaluation.py:82 还有一行诚实的注释 num_gpus = 2 # Hack while cluster is full——复现工程的真实面貌。
2.3 训练中途自动评测:回调闭环
这是评测管线最妙的一环,时序如下:
trainer 到 save 点
│
▼
PushToHubRevisionCallback.on_save (callbacks.py:47)
│ ① 造 dummy_config:分支名 = {revision}-step-{N:09d}(callbacks.py:59-64)
│ ② push_to_hub_revision 异步上传(run_as_future=True,hub.py:57-64)
│ —— 训练不等上传
▼
上传完成回调(仅当有 Slurm,callbacks.py:70-77)
run_benchmark_jobs → 每个 benchmark sbatch 一个 lighteval 作业
→ 结果自动传回 open-r1/open-r1-eval-leaderboard
也就是说:训练还在跑,每个 checkpoint 的 AIME/MATH 分数已经自动排队在算了。 配方行不行,不用等训完才知道。注意一个细节:callbacks.py:57-58 的注释警告不能用 dataclasses.replace 造配置——会破坏 accelerator 的分布式状态,所以才手写 DummyConfig 逐字段拷贝。这是踩过坑的代码。
2.4 system prompt 过墙的笨办法
训练配置里的 system_prompt 含换行和特殊字符,直接塞进 sbatch 命令行会碎。解法是 base64 编码后作为第 7 个参数传入(evaluation.py:97-101),在 slurm/evaluate.slurm:57 里 --system-prompt "$(echo "$7" | base64 --decode)" 解回。不优雅,但对「穿过 sbatch 命令行」这个场景是对症的最简方案。
3. pass@1 方差:响应数按 benchmark 配
DeepSeek 论文用每题 4~64 次采样估 pass@1,但没写每个 benchmark 用几次。Open R1 的约定(README.md:531-536):
| Benchmark | 每题响应数 | 原因 |
|---|---|---|
| AIME 2024 | 64 | 只有 30 题,方差最大 |
| MATH-500 | 4 | 500 题,题量本身压方差 |
| GPQA Diamond | 8 | — |
| LiveCodeBench | 16 | — |
采样参数也全局固定:max_new_tokens:32768, temperature:0.6, top_p:0.95(如 slurm/evaluate.slurm:38 的 MODEL_ARGS)。README 明说(README.md:539):响应数的选择差异,大概率就是自家数字和 DeepSeek 报告值之间那 1-3 个标准差的来源之一。评测协议本身(采样数、温度、上下文长度)是结果的一部分——这是推理模型评测和普通 LLM 评测最大的不同。