Open R1 发布 CodeForces-CoTs 数据集、IOI 基准与 OlympicCoder 代码模型
Open R1: Update #3
Open R1 发布第三期更新,包括从 DeepSeek-R1 蒸馏的近 10 万条 C++ 与 Python 竞赛编程思维链数据集 CodeForces-CoTs、基于 2024 国际信息学奥林匹克(IOI)的新基准,以及微调的 7B 和 32B 模型 OlympicCoder。
Open R1 公布了从 R1 蒸馏的代码推理数据集、IOI 基准与两个开源模型,可看蒸馏训练的具体做法与教训。
在过去几周里,我们集中精力复现了 DeepSeek-R1 方案中竞技编程(代码推理)相关的部分。
在这篇文章中,我们很高兴分享:
- CodeForces-CoTs 的构建:一个包含近 10 万条高质量样本的数据集,这些样本从 R1 蒸馏而来,用于生成 C++ 和 Python 的解题方案。
- IOI 基准:一个包含 2024 年国际信息学奥林匹克竞赛(IOI)中高难度问题的新基准。
- OlympicCoder:两个经过微调的 7B 和 32B 代码模型,在 IOI 问题上表现超越了 Claude 3.7 Sonnet 等闭源前沿模型。
以下是 OlympicCoder 模型与其他指令微调模型和推理模型的对比概览。我们发现,在 CodeForces-CoTs 上训练模型能够达到顶级性能,其中 OlympicCoder-32B 在我们测试的所有开放权重模型中表现最佳,包括一些规模超过其 100 倍的模型 🤯。
继续阅读,了解我们如何构建数据集、基准和模型!
关键链接
CodeForces
- 问题数据集:
open-r1/codeforces - DeepSeek-R1 思维链数据集:
open-r1/codeforces-cots
国际信息学奥林匹克竞赛(IOI)
- 问题陈述数据集(IOI'2020 - IOI'2024):
open-r1/ioi - 测试用例:
open-r1/ioi-test-cases - 官方(标准答案)解答:
open-r1/ioi-sample-solutions - DeepSeek-R1 思维链数据集(IOI'2020-IOI'2023):
open-r1/ioi-cots - 40 多个领先模型在 IOI'2024 上的评估数据:
open-r1/ioi-2024-model-solutions - 运行生成和评估的代码
OlympicCoder
CodeForces-CoTs 数据集
CodeForces 是竞技程序员中最受欢迎的网站之一,定期举办比赛,参赛者必须解决具有挑战性的算法优化问题。这些问题的挑战性使其成为提升和测试模型代码推理能力的有趣数据集。
虽然此前已有 DeepMind 的 CodeContests 数据集 等努力汇集了大量 CodeForces 问题,今天我们发布了自己的 open-r1/codeforces 数据集,包含超过 1 万个问题,涵盖从最早的比赛一直到 2025 年,其中 约 3 千个 问题未包含在 DeepMind 的数据集中。此外,对于大约 60% 的问题,我们 包含了题解, 这是由比赛组织者撰写的解释,说明了正确的解法。你还会找到每个问题从官方网站提取的 3 个正确解答。
此外,我们还发布了 open-r1/codeforces-cots,其中包含 DeepSeek-R1 在这些问题上生成的思维链,我们要求模型生成 C++(竞技编程中使用的主要语言)和 Python 的解答,总计近 10 万 条样本。
我们在此数据集上微调了 Qwen2.5 Coder Instruct 7B 和 32B,得到了我们的 OlympicCoder-7B 和 OlympicCoder-32B 模型。你将在博客文章的后续部分找到有关这些模型的更多细节。
代码可验证性危机
尽管像 DeepMind 的竞赛数据集以及其他包含竞赛编程问题的数据集都包含测试用例并声称可验证,但这些测试用例通常只是竞赛网站上所用完整测试集的一小部分。尤其是 CodeForces,其显示的测试用例上限约为 500 个字符,这意味着这些数据集只包含符合此限制的较短、较简单的测试用例。
举个例子,我们选取了 7 道 R1 生成的解决方案通过了所有公开测试用例的题目,并尝试将它们提交到 CodeForces 平台:
尽管它们通过了较短的测试,但这些解决方案中的每一个都在完整测试集上失败了。这凸显了对新的完全可验证的竞赛编程数据集的需求。虽然我们计划尝试基于模型的解决方案来生成和验证可能在未来添加到我们的 CodeForces 数据集中的额外有挑战性的测试,但目前我们转向其他地方寻找完全可用的题目数据。
国际信息学奥林匹克竞赛 (IOI)
国际信息学奥林匹克竞赛 (IOI) 是五项国际科学奥林匹克竞赛之一(如果你熟悉 AIME,IOI 相当于编程领域的 IMO,参加 AIME 的最优秀学生会受邀参加 IMO),它测试一组经过严格选拔的高中生(每个国家 4 人)解决复杂算法问题的能力。
这些题目极具挑战性,且完整的测试集在宽松的 (CC-BY) 许可下可用并已发布。这意味着 IOI 是测试模型代码推理能力的完美数据集。
在 IOI 中,每道题都有若干子任务,每个子任务有不同的输入约束。要解决一个子任务,提交的代码需要在(严格的)时间限制内通过其所有测试用例。虽然最终的子任务通常是“完整题目”,但有些子任务实际上描述了一个容易得多(约束更多)的问题,参赛者常常针对特定的子任务来获得部分分数,而不是仅仅试图解决完整题目(满分数相对罕见)。
继最近一篇 OpenAI 论文之后,o1 在 IOI'2024(最近一届)上现场参赛,我们同样处理了 IOI'2024(以及直到 2020 年的历届 IOI)的所有题目,并将它们拆分为子任务,使得每个提示都要求模型解决一个特定的子任务。我们在 open-r1/ioi 和 open-r1/ioi-test-cases 中发布了处理过的题目陈述,以及运行它们所需的所有评分/检查文件和测试用例。
我们创建了自定义代码来运行解决方案(许多题目设置复杂,需要一个“管理器”进程与运行用户提交的多个进程以及特殊检查器进行通信以验证解决方案),并根据 IOI 规则进行评分,代码可在 https://github.com/huggingface/ioi 获取,并且在 IOI'2024 上评估了 40 多个领先的推理模型。
在 IOI 中,参赛者每道题有 50 次提交限制。我们为每个子任务生成了 50 次提交,然后应用了与 OpenAI 所用类似的选择策略,以在竞赛条件下获得每道题的分数。结果如下所示,其中水平线代表来自 真实竞赛数据的铜牌、银牌和金牌模型的阈值。虽然 o1 非常接近铜牌,但最终没有模型达到奖牌阈值(参赛者的第 50 百分位)。
我们的 OlympicCoder 模型(红色)与其他前沿模型相比表现相当不错,甚至超越了某些闭源模型(金色),如 claude-3.7-sonnet-thinking,而 OlympicCoder-32B 在 50 次提交限制的设置下,甚至超过了 o1-mini 和 DeepSeek-R1——我们从中蒸馏的模型。
提交策略
需要注意的一点是,我们的提交策略可能会对非推理模型不利,例如 Qwen-2.5-Coder-32B-Instruct,即 OlympicCoder-32B 的基础模型。为了模拟真实的竞赛条件——即提交的得分只有在实际提交之后才能知道——我们采用了类似于 OpenAI 为 o1-ioi 所采用的 轮转提交策略:我们首先提交针对问题最后一个子任务的解答,然后针对倒数第二个子任务,依此类推,仅在解答被选中提交时才对其进行评估。我们跳过针对已被先前所选提交解决的子任务的提交,并且在每个目标子任务中,我们更倾向于来自更长生成内容的提交——这一标准对推理模型合理,但对其他模型则不太适用。
如果我们取消 50 次提交限制(这将使我们脱离竞赛条件),并评估我们生成的所有提交(每个子任务 50 次),我们得到以下结果:
在 R1 轨迹上训练代码模型的经验教训
在创建 OlympicCoder 模型的过程中,我们进行了大量 SFT 实验,以理解应用于 CodeForces 数据集的各种过滤器的作用。我们发现以下 open-r1/codeforces-cots 子集给出了最佳整体性能:
solutions:给定问题陈述后 R1 生成的解答。solutions_w_editorials:给定问题陈述和解释正确解法的说明后 R1 生成的解答。
请注意,我们仅关注 C++ 解答,但混入 Python 解答很可能会进一步提升性能。
我们使用 LiveCodeBench 作为模型的测试平台,然后将表现最佳的检查点通过难度更高的 IOI 基准进行测试。我们测试了多种超参数配置来训练模型,并最终确定如下:
- 模型:Qwen2.5 Coder Instruct 7B 和 32B
- 轮数:10
- 有效批量大小:128
- 学习率:4e-5
- 调度器:余弦衰减至峰值学习率的 10%
- 上下文大小:7B 为 32,768 个 token,32B 为 22,528 个 token
以下是我们从基于 R1 推理轨迹调优 Qwen2.5 Coder 模型中学到的一些经验教训。
经验 1:样本打包会损害推理性能
样本打包是一种广泛使用的方法,用于高效处理可变长度序列并加速训练。如下图所示,其工作原理是将训练样本(彩色)拼接成等大小的块,从而无需在批次间使用填充 token(灰色):
使用打包时,样本可能会跨越每个块的边界重叠,但在实践中,如果大多数样本远小于块大小,这并不太重要。
然而,对于我们从 R1 蒸馏出的推理轨迹,我们想知道打包是否会损害性能,因为许多轨迹很长,被截断答案的比例可能很高。这意味着可能难以训练模型关注长上下文信息,尤其是当问题和答案被打包到不同的块中时。
如下图所示,我们发现打包会显著损害模型性能:使用打包时,模型几乎无法解决 LiveCodeBench 中的问题,而不使用打包时,性能在多个 epoch 内持续提升,然后趋于平稳:
我们怀疑这种显著差异被以下事实所放大:我们的训练集仅包含 C++ 解法,而 LiveCodeBench 仅在 Python 上评估性能。尽管如此,我们发现在所有消融实验的数据集上,打包始终表现更差。
经验 2:使用大学习率以获得最佳性能
对于我们使用 Qwen 模型进行的大多数 SFT 实验,2e-5 的学习率通常足以获得强劲的性能。然而,当将带有推理数据的 SFT 应用于现有的 instruct 模型时,我们发现使用更大的 4e-5 学习率可以获得显著的性能提升。如下图所示,学习率每翻一倍,LiveCodeBench 上的性能差异就几乎达到 10 分!
经验 3:加入题解并不能提升性能
当我们创建 open-r1/codeforces-cots 的 solutions_w_editorials 子集时,我们的直觉是:通过向 R1 提供题目和解法,我们能够获得更好的推理轨迹。令人惊讶的是,这一直觉在评估中并未得到验证,而从题目描述进行朴素采样在训练过程中带来了适度但持续的提升:
经验 4:使用 <think> 预填充以持续启用长 CoT
在对我们的一些模型进行 vibe testing 时,我们遇到了一个奇怪的现象:如果使用领域内查询(如竞赛编程)进行提示,模型会在 <think> 和 </think> 个 token 内输出预期的长 CoT,随后给出最终答案。然而,对于领域外查询,如“瑞士的首都是哪里”,模型会退回到原始 instruct 模型的行为!
以下是一个简单查询未能返回长 CoT 的示例:
用户:1+1 等于多少?
助手:2。这是最简单的数学加法问题之一。1 + 1 等于 2。这个问题常被用作谜语或智力题,特别是在求职面试或在线测试中,用来判断候选人快速解决问题的能力。虽然对大多数人来说答案显而易见,但它对雇主评估批判性思维和问题解决能力来说可能是一个有用的问题。此外,它是数学中的一个重要概念,构成了更复杂算术运算的基础。所以下次有人问你 1+1 等于多少时,你可以自信地说答案是 2!
# Define a function to calculate sum of two numbers def add_numbers(a, b): return a + b # Example usage of the function result = add_numbers(1, 1) print("The sum of 1 + 1 is:", result)在这个解法中,我们定义了一个函数
add_numbers,它接收两个参数a和b,并返回它们的和。然后我们用 1 和 1 调用这个函数,将结果存储在变量result中,随后将其打印出来。这展示了计算并显示两个整数之和的一种直接方式。这个例子简单而有效地说明了 Python 中的基本算术运算。
然而,如果我们用 <think> 预填充助手回复,期望的行为就会出现:
用户:1+1 等于多少?
Assistant: <think> 好的,我需要回答“1+1等于几?”这个问题。让我想想。哦对,1加1等于2。但等等,也许还有更多内容。比如,如果有人用不同的方式提问呢?或者考虑其他进制?比如在十进制中是2,但在二进制中是10。嗯,但这个问题很直接。用户大概期望简单的答案。所以这里的正确答案是2。我应该直接说出来吗? </think> 是的,1 + 1 等于 2。关于这个计算,你还有其他想了解的吗?
这表明,当把推理轨迹蒸馏到已有的后训练模型中时,应当在最终模型的聊天模板中默认强制预填充。顺便一提,这也是 DeepSeek 对其蒸馏模型所做的,而上述行为可能正是原因所在。(猜测一下流行聊天 UI 上点击“think”按钮是否只是对助手回复进行预填充,也很有意思 🙂)。
结合所有这些经验,我们推出了 OlympicCoder-7B,其性能与 DeepSeek 的蒸馏模型相当,并显著优于基础 Qwen2.5-Coder 模型:
经验 5:使用 8 位优化器在长上下文下扩展大模型
在 OlympicCoder-7B 的整个训练过程中,我们发现 DeepSpeed ZeRO-3 足以在单节点 8 x H100 上以 32k 上下文训练每个模型。然而,当我们尝试将配方扩展到 32B 时,遇到了大量内存问题。特别是,一旦上下文扩展到超过 20k token,我们的运行就会 OOM,即使在 16 个节点上也是如此 😢。这并不理想,因为 20% 的 CodeForces-CoTs 轨迹大于 20k token,这意味着它们会在训练期间被截断。
问题的根源在于 transformers 和 trl 尚不支持上下文并行,不过可以关注这个 issue 来跟踪进展。
与此同时,我们探索了多种节省内存的技术,发现将 FSDP 与 paged_adamw_8bit 优化器结合使用,可以将上下文扩展到 22,528 个 token:仍不理想,但只有 9% 的数据被截断。
更新
GRPO 更新
TRL 中 GRPO 的实现最近取得了进展,带来了进一步增强效率、可扩展性和资源利用率的改进。以下是自上次更新以来最重要变化的摘要:
生成复用
GRPO 的主要瓶颈之一与任何在线方法相同:生成需要时间。使 GRPO 更具样本效率的一个关键方法是在优化过程中多次复用生成的样本,而不是在一次使用后将其丢弃。这项技术实际上很久以前就随 PPO 引入了。
对于 GRPO,样本被复用的次数记为 μ。
现在可以多次复用生成的样本,显著加快进程。
from trl import GRPOConfig
training_args = GRPOConfig(..., num_iterations=...)
然而,要谨慎——如果 μ 太大,可能会对学习产生负面影响。根据我们的经验,2 到 4 之间是一个良好的平衡。
奖励加权
在训练模型时,并非所有奖励都同等重要。例如,我们可能希望模型优先考虑正确性而非格式,而不是平等对待两个方面。
为了解决这个问题,现在可以为不同的奖励分配不同的权重,从而对优化过程进行更精细的控制。通过调整这些权重,我们可以引导模型更多地关注对特定任务最重要的方面。
from trl import GRPOConfig, GRPOTrainer
def very_important_reward(completions, **kwargs):
...
def less_important_reward(completions, **kwargs):
...
training_args = GRPOConfig(
...,
reward_weights=[0.9, 0.1],
)
trainer = GRPOTrainer(
...,
reward_funcs=[very_important_reward, less_important_reward],
args=training_args,
)
其他增强
对 GRPO 进行了几项较小但影响深远的改进:
- PEFT + vLLM 集成 – 现在可以同时使用 PEFT(参数高效微调)和 vLLM,将高效微调与优化推理相结合,以实现更好的可扩展性。
- 梯度检查点 – 已添加此功能,通过重新计算某些激活值而不是存储它们来减少训练期间的内存消耗,从而能够训练更大的模型。
- 优化的选择性 Log Softmax 计算 – 引入了一种计算 log softmax 的新方法,减少了训练期间的内存峰值。
后续步骤和进行中的工作
当前的重点在两个关键领域:
- 提高生成速度 – 正在探索进一步的优化(如静态缓存),以使生成过程更快。
- 将 GRPO 扩展到多节点设置 – 正在努力使 GRPO 能够跨多个节点扩展,从而能够训练更大的模型。
Open R1 数学数据集更新
我们进一步丰富了之前发布的 OpenR1-Math-Raw 数据集,添加了新的元数据,以便在过滤和验证过程中做出更明智的决策。具体来说,我们添加了以下列:
reparsed_answers:我们观察到answer列中的许多条目要么 LaTeX 格式不正确,要么只包含部分答案。此外,由于有些问题是多选题,正确答案本身和对应的字母都应被视为有效回答。为了解决这个问题,我们使用 Llama-3.3-70B-Instruct 模型从solution列重新提取了所有答案,以确保reparsed_answers既包含正确答案,在多选题的情况下也包含对应的字母。我们相信这一添加将对社区非常有价值,能够提高列评分准确性和 GRPO 期间的验证过程。correctness:当依赖于基于模型的方法时,运行答案验证可能会消耗大量资源。因此,我们使用 Llama-3.3-70B-Instruct 作为评判者,同时用math_verify对answer和reparsed_answers列进行评估,对所有解决方案进行了评估。
实验细节
为了帮助社区理解基于验证的过滤对 SFT 蒸馏数学数据集的影响,我们进行了几项消融实验。从随机选择的 200k 样本池开始,我们根据以下过滤规则创建了六个不同的 SFT 数据集:
no_restrictions(200k) - 未应用过滤。llama_verification(124k) - 被 LLAMA-70B 验证评为正确的样本。math_verify_answer(88.7k) - 在answer列上被math_verify验证为正确的样本。math_verify_reparsed(101k) - 在reparsed_answers列上被math_verify验证为正确的样本。llama_verification_or_math_verify_reparsed(LorMV) (154k) - 数据集 2 和 4 的并集。llama_verification_and_math_verify_reparsed(LandMV) (71.2k) - 数据集 2 和 4 的交集。
训练和评估
在数据受限的环境中进行过滤时,精确率和召回率都是重要的考量因素。因此,我们没有用相同的 token 预算来运行每个实验,而是在所有数据上训练单个 epoch。对于模型,我们选择了 Qwen7B-Instruct,并用 RoPE 扩展将其微调到 32k 上下文长度和余弦调度。为了跟踪性能进展,我们使用 lighteval 在第 40 步评估了 AIME-24、AIME-25 和 MATH-500 上的模型。结果总结如下:
关键观察
- 验证显著影响早期性能。 过滤在前 40 步中尤为重要。在 MATH-500 数据集上,更严格的验证方法在早期阶段带来了显著的性能提升(例如,
no_restrictions得分为 0.61,而LandMV为 0.72)。然而,随着训练进展,这种性能差距缩小,拥有更多样本被证明是有益的——即使其中一些包含错误。 - 训练损失在不同数据集之间差异显著。 使用
math_verify过滤的数据集始终表现出更低的训练损失。我们假设math_verify有效地识别了特定的任务子集(主要是数值任务),而基于 Llama 的验证或未过滤的数据集保持了更广泛的数据多样性。 - 令人惊讶的是,未过滤的数据集并没有出现严重的退化。 尽管包含错误样本,
no_restrictions数据集在长时间训练运行中仍保持了有竞争力的性能。
建议
根据我们的发现,最佳过滤方法在很大程度上取决于训练 token 预算。对于较短的运行,更严格的验证方法具有显著优势。作为一般建议,我们推荐将 llama 验证与 math_verify 结合使用——正如在 LorMV 数据集中所做的那样。
推理课程
Hugging Face 学习团队正在制作关于强化学习、GRPO 以及使用 TRL 训练推理模型的可访问材料。它包括来自 Maxime Labonne、Unsloth 和 Marimo 的教程和演示。如果你正在寻找进入这个快速发展的领域的好起点,请查看 推理课程!
社区亮点
过去几周,人们继续在各种任务中探索 GRPO,同时出现了几个新的推理数据集,针对比数学和代码更广泛的领域。以下是我们发现特别令人兴奋的一些发布:
GRPO 探索
- Unsloth 的优化巫师们进一步减少了使用 LoRA 训练 GRPO 模型所需的 VRAM 量,现在 1.5B 模型只需 5GB 🧙
- Kalomaze 写了一篇优秀的博客文章,关于为 GRPO 选择好的超参数。他们还观察到,小于 7B 的模型往往收敛得慢得多,这意味着你应该在这些规模上探索新想法,然后再断定它们不起作用。
- Hrishbh Dalal 展示了你可以使用 GRPO 教 LLM 解决数独谜题!
- Yutai Li 和合著者发表了一篇非常好的论文,表明对于小型模型,最好从更强大的教师中蒸馏长和短 CoT 数据的混合。
推理数据集
- KodKode 发布了一个非常大的数据集,包含约 50 万个编程样本。这看起来是 CodeForces-CoTs 的极好补充,我们很兴奋能在其上训练一些新模型!
- GeneralReasoning 团队已开始发布高质量且多样化的数据集,如
GeneralReasoning/GeneralThought-323K,其涵盖的领域和模型均比公开可用的更多。他们还有一个不错的 网站,可让你探索这些数据,以及
下一步是什么?
通过这次更新,我们现在已具备完成复现计划第 1 步和第 2 步的主要部分:
在接下来的几周里,我们计划重点聚焦于:
- 完善蒸馏数据集混合方案,以训练通用推理模型。
- 将 GRPO 扩展至像
Qwen/Qwen2.5-Coder-32B-Instruct这样的更大模型,以便我们能衍生出 R1-Zero 变体。 - 结合来自数学和代码等多个领域的奖励信号,并引入奖励模型来为非推理数据评分。
来源:Hugging Face Blog · hf-mirror.com













