DeepCoder: A Fully Open-Source 14B Coder at O3-mini Level

作者/机构: Michael Luo, Sijun Tan, Roy Huang, Ameen Patel, Alpay Ariyak, Qingyang Wu, Xiaoxiang Shi, Rachel Xin, Colin Cai, Maurice Weber, Ce Zhang, Li Erran Li, Raluca Ada Popa, Ion Stoica (Agentica团队与Together AI合作)

A1 主要贡献

本文由Agentica团队与Together AI合作,发布了DeepCoder-14B-Preview,这是一个通过分布式强化学习(RL)从Deepseek-R1-Distilled-Qwen-14B微调而来的代码推理模型。

核心问题: 近几个月来,通过强化学习在数学领域的推理模型扩展取得了显著进展(如DeepScaleR, AReaL, Light-R1, DAPO)。然而,编码领域的进展相对滞后,主要挑战在于构建具有可靠、可验证奖励的高质量数据集。

研究目标与成果: 本文旨在普及使用强化学习将小型模型训练成与o3-mini相媲美的强大竞争性编码器的方法。主要成果是DeepCoder-14B-Preview模型,该模型在32个H100 GPU上花费2.5周,利用2.4万个可验证的编码问题进行训练。它在LiveCodeBench上取得了60.6%的Pass@1准确率(提升了8%),性能与o3-mini-2025-01-031 (Low)和o1-2024-12-17相当,而参数量仅为14B。

主要贡献:
1. 发布DeepCoder-14B-Preview模型: 一个14B参数量的代码模型,在多个编码基准测试中表现出色,性能可与OpenAI的o3-mini匹敌甚至超越。
2. 开源完整技术栈: 全面开源了用于训练的数据集、代码、训练日志以及系统优化,旨在推动社区利用RL扩展和加速智能。
3. 开源verl-pipe系统: 发布了verl-pipe,它是verl后训练系统的一个扩展,包含多项系统优化,可将端到端训练速度提高2倍。

图1:DeepCoder的LiveCodeBench (LCB) 分数随训练进展的变化。在第180步,上下文长度扩展到32K。最佳的32K检查点被用于推理时扩展到64K,实现了60.6%的LCB分数——与o3-mini的性能相匹配。
图1:DeepCoder的LiveCodeBench (LCB) 分数随训练进展的变化。在第180步,上下文长度扩展到32K。最佳的32K检查点被用于推理时扩展到64K,实现了60.6%的LCB分数——与o3-mini的性能相匹配。

A3 背景知识与设计原则

数据集管理

编码领域高质量数据的稀缺性
先前在数学领域的工作表明,带有可验证奖励的强化学习可以显著增强模型的推理能力。然而,与数学领域不同——互联网上随时可以获得大量高质量、可验证的数据——编码领域相对缺乏此类数据。

现有数据集的局限性
在早期的实验中,我们评估了几个流行的编码数据集,包括APPS、TACO、CodeContests、KodCode和LeetCode。我们发现其中一些对于我们的模型来说太简单(例如KodCode、LeetCode),而另一些则存在噪声或包含有缺陷或缺失测试用例的不可验证问题。这些问题常常产生无效或误导性的奖励信号,最终破坏了RL训练的稳定性。

高质量训练集的构建
为了克服这些限制,我们策划了一个高质量的训练集,包括:
* TACO验证过的问题。
* 来自PrimeIntellect的SYNTHETIC-1数据集中经过验证的问题。
* 2023年5月1日至2024年7月31日期间提交的LiveCodeBench问题。

严格的数据过滤流程
为确保RL训练的数据质量,我们实施了严格的过滤流程:
1. 程序化验证: 每个问题都使用外部的官方解决方案进行自动验证。我们筛选数据集,只包含那些官方解决方案能通过所有单元测试的问题。这个过程在tests/rewards/test_code_batch.py中自动化实现。
2. 测试用例过滤: 每个问题必须包含至少5个单元测试。我们发现,测试用例较少的问题容易鼓励奖励 hacking,即模型学会通过识别常见的测试用例来直接打印出记忆的答案。
3. 去重: 我们移除了跨数据集的重复问题以避免污染。我们对三个训练数据集(Taco Verified, PrimeIntellect SYNTHETIC-1, 和 LCB (05/01/23-07/31/24))执行了此操作。然后,我们验证了测试数据集——LCB (08/01/24-02/01/25)和来自Codeforces的57个竞赛——中没有污染。

最终数据集构成
经过筛选,我们得到了2.4万个高质量的编码问题用于我们的RL训练,其中7.5千个问题来自TACO Verified,1.6万个来自PrimeIntellect的SYNTHETIC-1,以及600个来自LiveCodeBench。

代码沙箱环境

沙箱环境的必要性
为了计算代码RL训练的奖励,我们必须在代码沙箱中对模型生成的代码运行单元测试。在每个RL迭代中,我们的训练批次会在1024个问题上进行评估——每个问题都有多个单元测试(≥ 5个)。这个要求严苛的工作负载需要并行运行100多个代码沙箱,以确保LLM生成的代码在合理的时间内得到准确验证。目前,我们分别使用了两种沙箱:Together代码解释器和本地代码沙箱。

Together 代码解释器

一个快速高效的云环境
这是一个快速、高效的环境,与我们的RL训练直接兼容,每个问题仅花费3美分。我们一直致力于将Together代码解释器可靠地扩展到100多个并发沙箱和每分钟1000多次沙箱执行。这些沙箱暴露了stdout、stdin和最后一行代码输出的评估,同时安全地限制执行并将代码与主机系统隔离。Together代码解释器目前处于测试阶段;详细信息可在Together代码解释器文档中找到,集成示例代码可在我们的代码仓库中找到。

本地代码沙箱

与官方基准一致的本地环境
它会启动一个本地沙箱作为一个独立的、有防护的Python子进程,通过stdin接收测试用例输入,并将答案打印到stdout。我们的本地沙箱遵循官方LiveCodeBench仓库中相同的评估代码,确保我们的结果与现有排行榜保持一致。

奖励函数

采用稀疏结果奖励模型(ORM)
我们的奖励函数采用稀疏的结果奖励模型(Outcome Reward Model, ORM)。我们避免分配部分奖励,例如思维链惩罚或在N个测试中通过K个时给予K/N的奖励,因为这可能导致奖励 hacking,即LLM学会直接打印出公开测试的答案或错误地收敛于通过简单的边界情况。

二元奖励机制
1. 奖励为1:生成的代码必须通过所有抽样的单元测试。由于某些问题包含数百个测试,使得全面验证不切实际,我们为每个问题抽样了15个最具挑战性的测试,这些测试通过其输入字符串的长度来识别。
2. 奖励为0:如果LLM的代码在至少一个测试用例上失败,或者答案格式不正确(即缺少python [CODE] 标签),我们则不分配任何奖励。每个测试用例的超时时间设置为6-12秒。

A2 方法细节

GRPO+: 一个稳定版的GRPO



图2:我们16K运行中GRPO+和GRPO的平均训练奖励。GRPO的奖励曲线最终崩溃。由于Clip High机制,GRPO+的曲线是稳定的。
图3:由于过长过滤,GRPO+的响应长度随时间稳定增长。

图4:Clip High和无熵损失确保GRPO+的token级熵不会崩溃,并鼓励充分的探索。

GRPO算法的增强
我们通过整合来自DAPO的见解来增强原始的GRPO算法,以实现更稳定的训练:

  • 无熵损失 (No Entropy Loss): 我们观察到,包含熵损失项通常会导致不稳定,熵会呈指数级增长并最终导致训练崩溃。为了缓解这个问题,我们完全取消了熵损失。
  • 无KL损失 (No KL Loss, 来自DAPO): 取消KL损失可以防止LLM被约束在原始SFT模型的信任区域内。这一移除也避免了为参考策略计算对数概率的需要,从而加速了训练。
  • 过长过滤 (Overlong Filtering, 来自DAPO): 为了保留长上下文推理能力,我们对被截断的序列的损失进行掩码。这项技术使得DeepCoder即使在32K上下文长度下训练,也能泛化到64K上下文的推理。如图3所示,这种过滤方法使得响应长度能够自然增长,而不会因截断而受到惩罚。
  • 高位裁剪 (Clip High, 来自DAPO): 通过提高GRPO/PPO代理损失中的上限,我们鼓励更多的探索并稳定熵。图4表明,这一调整既带来了更稳定的训练,也提高了模型性能。

迭代式上下文长度扩展:开箱即用的泛化能力

迭代式上下文扩展技术的背景
在我们最初的DeepScaleR博文中,我们介绍了迭代式上下文长度扩展,这是一种训练技术,使语言模型能够首先在较短的上下文长度下学习如何有效思考,然后泛化到更长的上下文。这种方法帮助我们的1.5B模型在我们将上下文窗口从8K扩展到16K再到24K时,下游性能稳步提升,在AIME上的准确率从33%提升到38%再到43%,最终达到了O1-preview的性能。

14B模型面临的新挑战
然而,当我们将这项技术应用于14B模型时,遇到了新的挑战:
1. 14B模型已经具备比1.5B模型强大得多的推理能力,这意味着进一步提升需要解决更难的问题。
2. 这些更难的问题自然需要比用于较小模型的8K起始点更长的上下文窗口。

短上下文起始点的不利影响
从短上下文开始并惩罚模型超出该窗口的做法产生了负面效果——它导致了初始性能的下降、响应变短,以及模型在长上下文上推理能力的退化。

结合过长过滤的解决方案
为了在实现高效训练的同时保留长上下文推理能力,我们引入了来自DAPO的过长过滤技术。该技术在训练期间掩盖了被截断的序列,这样模型就不会因为生成超出当前上下文限制的、深思熟虑但冗长的输出而受到惩罚。因此,模型在较短的上下文上训练时仍然可以进行“长思考”。

DeepCoder-14B-Preview的应用与结果
我们将迭代式上下文长度扩展应用于DeepCoder-14B-Preview,将上下文窗口从16K扩展到32K。在LiveCodeBench上,该模型分别在16K和32K上下文下实现了54%到58%的准确率,并且在64K上下文下评估时达到了60.6%,展示了超越其训练上下文的强大泛化能力。

与基础模型的性能对比
这种泛化能力与像DeepSeek-R1-DistillQwen-14B这样的基础蒸馏模型形成对比,后者在超出其训练上下文长度后性能会停滞不前:

性能分析
尽管DeepCoder的原始16K性能较低,这是由于其平均响应长度较长,导致截断和分数惩罚,但由于其在更长上下文中的推理能力,它最终在64K下优于其他模型。


图5:DeepCoder的平均响应长度和训练奖励随训练进展的变化。平均响应长度从8K增加到17.5K上下文长度。

成功归因
DeepCoder的成功是迭代式上下文长度扩展与过长过滤相结合的直接结果。如图5所示,模型的平均响应长度在训练过程中从8K稳步增长到17.5K,而平均奖励从0.6提高到0.7——这清晰地表明模型正在学习更具可扩展性和连贯性的思维模式。

宝贝,没有足够高的山峰。
没有足够长的上下文。——灵感来自Marvin Gaye & Tammi Terrell

后训练的系统优化

长上下文RL训练的耗时问题
使用长上下文强化学习训练大语言模型非常耗时,需要反复在长上下文中进行采样和训练。如果没有系统级优化,完整的训练过程可能需要数周甚至数月——我们的14B编码模型每步需要1200-2500秒,总训练时间为2.5周!

引入verl-pipeline
我们介绍并开源了verl-pipeline,这是一个verl(一个开源RLHF库)的优化扩展,它应用了多项系统级改进来加速端到端的RL训练。verl-pipeline相比基线verl实现,速度提升高达2.5倍。我们应用这些新的系统优化来训练DeepCoder-1.5B-Preview,它达到了25%的LCB分数,比Deepseek-R1-Distill-Qwen-1.5B提高了8%。

社区合作的邀请
我们邀请社区,包括verl团队和其他新兴项目,采纳并基于这些优化进行构建。

采样器是瓶颈


图7:Verl的PPO/GRPO训练流水线。每个RL迭代都循环经历采样、奖励函数计算和训练。采样是瓶颈;训练速度受限于生成长序列的掉队采样器。

瓶颈分析
后训练系统通常受限于采样时间——即使用vLLM和SGLang等推理引擎生成长序列(最多32K tokens)的延迟。图7(原文为Figure 4,但根据上下文应为Figure 7)展示了Verl的PPO/GRPO流水线,其中响应长度的异质性导致一些采样器成为掉队者(stragglers)。这些掉队者延迟了训练,而已完成的采样器则处于空闲状态,导致GPU利用率低下。


图8:小批量流水线(Minibatch Pipelining)。采样器和训练器在不同的工作组中运行。当采样器完成并释放小批量数据(用于PPO/GRPO)时,训练器工作节点会异步处理它们。在迭代结束时,训练器将其权重广播给采样器。

小批量流水线方法
为了减少后训练中的空闲时间,我们对采样和训练进行流水线化——允许训练器在采样器继续生成下一个小批量数据时,开始对早期的小批量数据进行更新。这种重叠有助于掩盖采样延迟。

小批量流水线的局限性
然而,这种方法有三个关键的局限性:
1. 首先,小批量数据的平均序列长度会随时间增长,增加了后续小批量数据的训练时间。结果,最后几个小批量数据通常在采样完成后才会处理完,限制了流水线化的好处。
2. 其次,流水线化需要在采样器和训练器之间分配GPU,减少了可用的采样器数量。与Verl在同一GPU池中动态切换采样器和训练器不同,这种静态分配由于采样器较少,可能会减慢端到端的采样时间。
3. 最后,奖励函数的计算可能需要很长时间,特别是对于编码相关任务,每个RL迭代需要运行数千个单元测试。默认情况下,Verl在采样完成后在主节点上计算奖励。

实现说明
尽管有这些限制,我们在代码库的ray_trainer_pipeline.py中实现了小批量流水线,并指出通过微批次处理(microbatching)可以进一步改进流水线。


图9:一次性流水线(One-Off Pipelining)。采样器提前一个迭代生成批次数据,而训练器使用前一个迭代的数据更新梯度。其次,奖励函数计算与采样交错进行。这种方法不会向GRPO/PPO的在线策略算法中引入异步的离策略样本。

一次性流水线方法
为了完全将训练、奖励计算和采样流水线化,我们引入了一次性流水线(one-off pipelining)。其思想很简单:牺牲第一个RL迭代仅用于采样,然后在下一个迭代中使用该批次数据进行训练。这使得采样和训练能够并行进行,消除了采样后训练器的空闲时间。

奖励计算的优化
其次,奖励计算与采样交错进行。一旦一个请求完成,其奖励立即被计算——减少了奖励评估的开销,特别是对于像编码任务中测试用例执行这样的计算密集型任务。

实现说明
我们在我们的verl分支的ray_trainer_async.py中实现了一次性流水线。

端到端性能


图10:一次性流水线完全掩盖了训练器和奖励计算时间,使数学任务的训练时间减少了1.4倍,编码任务减少了2倍。

评估设置
在图10(原文为Figure 7)中,我们针对数学和编码两个工作负载,评估了verl、微批次流水线和一次性流水线。为公平起见,所有基线都通过Python线程池并行计算奖励;verl官方对每个样本串行计算奖励,这对于编码任务来说时间长得难以接受。

硬件平台
我们在8个A100 GPU上评估了Deepcoder-1.5B-Preview,并调整了采样器与训练器的比例,以更好地平衡训练器和采样器的时间。

数学任务结果
对于数学任务,一次性流水线将每个RL迭代的时间减少了1.4倍。我们注意到数学任务的奖励计算时间接近于零,因为它只包含基本的sympy检查。特别地,一次性流水线完全掩盖了训练器的时间,而不像小批量流水线那样,最后一个小批量会溢出。

编码任务结果
对于编码任务,计算奖励每个RL迭代需要运行数千个测试,这是一个耗时的过程。一次性流水线掩盖了训练器和奖励计算的时间,将端到端的训练时间减少了2倍。

有效性与可扩展性
最重要的是,一次性流水线是有效的,并且可以扩展到困难的编码任务。我们使用ray_trainer_async.py训练了DeepCoder-1.5B-Preview,其LCB分数相比基础蒸馏模型提高了8%。

A4 实验

实验环境

  • 模型架构:
    • DeepCoder-14B-Preview: 基于Deepseek-R1-Distilled-Qwen-14B进行微调,参数量为14B。
    • DeepCoder-1.5B-Preview: 基于Deepseek-R1-Distill-Qwen-1.5B进行微调,参数量为1.5B。
  • 数据集:
    • 训练集: 包含2.4万个高质量编码问题,来源为TACO Verified (7.5k), PrimeIntellect’s SYNTHETIC-1 (16k), 和LiveCodeBench (600)。
    • 评估集: LiveCodeBench (LCB), Codeforces (使用Qwen CodeElo基准), HumanEval+, AIME2024。
  • 硬件配置:
    • 14B模型训练:32个H100 GPU。
    • 1.5B模型训练及系统评测:8个A100 GPU。
  • 软件配置:
    • 代码实现基于verl(一个开源RLHF库)及其优化扩展verl-pipeline
    • 推理引擎使用vLLM和SGLang。

实验结果

我们在多个编码和数学基准上对Deepcoder-14B-Preview进行了评估。
* 综合性能: 14B参数量的模型在所有编码基准上均表现出强大的性能。
* LiveCodeBench (LCB): 取得了60.6%的Pass@1准确率,与o3-mini (low)和o1的性能相当。
* Codeforces: 获得了1936的Elo评分,同样与o3-mini (low)和o1相当。
* HumanEval+: 取得了85.3%的Pass@1准确率。
* 数学推理泛化能力 (AIME2024): 尽管模型未针对数学任务进行专门训练,但其从编码任务中获得的推理能力很好地泛化到了数学领域,在AIME2024上取得了73.8%的分数,相比基础模型提升了4.1%。


由于Deepseek和OpenAI在内部评估Codeforces,我们参考附录A以获取Codeforces评估的更多细节。
*非推理模型。


图6:LiveCodeBench Pass@1准确率 vs 模型大小。DeepCoder仅用14B参数就达到了前沿推理模型o1和o3-mini (low)的水平。

A5 结论

本文介绍了Deepcoder-14B-Preview,一个14B参数的模型,在LiveCodeBench上以60.6%的Pass@1准确率达到了o3-mini的性能水平。为实现这一目标,我们精心筛选了高质量、可验证的编码数据,并为高效的RL训练引入了算法和系统上的优化。

我们的目标是普及大语言模型的RL训练。Deepcoder-14B-Preview代表了朝这个方向迈出的第二个重要里程碑,它建立在我们第一个专注于数学推理的模型DeepScaleR-1.5B-Preview所奠定的基础之上。通过完全分享我们的数据集、代码和训练方法,我们赋能社区复现我们的工作,并使RL训练对所有人开放。

我们相信,推动RL扩展是一项集体的、由社区驱动的努力,我们欢迎开源贡献和赞助。让我们共同努力,推动RL在LLM推理及更广阔领域的边界!

A6 附录

A — Codeforces

基准测试说明
我们的Codeforces评估使用了Qwen CodeElo基准,该基准包含来自57个竞赛的408个问题,难度从Div. 4到Div. 1不等。目前缺乏一个统一的Codeforces评估基准,OpenAI和Deepseek使用了不同的方法。这是一项尝试标准化这个被广泛使用的特定基准的举措。

计分

遵循官方计分方法
我们遵循Codeforces的官方计分方法,每个问题的最高可得分数为??分。每次提交错误,分数??减少50,直到因足够多的错误提交而降至0。在我们的评估中,我们让每个模型为每个问题生成8个回答,并收集每次提交的成功/失败情况,以计算模型在该问题上获得的分数。每个竞赛的分数随后被加总,成为该模型在该竞赛的总分。

Elo等级分计算

采用独立竞赛评估方法
Elo等级分的计算方法与Codeforces官方采用的方法非常相似。主要区别在于,Codeforces会跨多个竞赛持续更新参与者的等级分,而我们的方法则将每个竞赛视为独立的。这简化了计算,并提高了对单次表现评估的准确性。

计算公式
具体来说,我们使用Elo & Sloan, 1978的以下公式来估计每个模型的期望等级分:


在这个场景中,一个竞赛有n名人类参与者,每人都有已知的等级分$R_i$(其中$i = 1, 2, ..., n$)。模型在这些参与者中排名为$k$。利用这些信息,我们计算出模型$E_R$在特定竞赛中的期望等级分。

数据来源
我们通过Codeforces API获取特定竞赛的人类参与者分数,以反映我们模型非常真实的Elo等级分。

最终等级分
模型在所有57个竞赛中的总体估计Elo等级分是通过对各个等级分求平均得到的。

等价性证明

数学等价性参考
关于等价性的详细证明,请参考CodeElo论文的附录C,其中他们证明了这种Elo等级分计算方法与官方Codeforces方法在数学上的等价性。

百分位计算

基于公开用户数据
每个等级分的百分位是基于Codeforces平台上的公开用户评分。数据来自2024年,包含了89352名有评分的用户。我们选择2024年是为了保持一致性,因为CodeElo基准中使用的竞赛范围都在2024年内。非常感谢Codeforces用户123gjweq2提供此数据。