OpenAI 的 o1/o3-mini 级别代码推理模型被抢先进行了开源!UC 伯克利与 Together AI 联合推出的 DeepCoder-14B-Preview,仅仅 14B 的参数就能够与 o3-mini 相媲美,并且开源的代码以及数据集都很齐全,还可以免费使用。
OpenAI 的 o1/o3-mini 级推理模型,竟然被抢先进行了开源操作?
来自 UC 伯克利和 Together AI 的联合团队刚刚重磅推出了一款模型,这款模型是完全开源的代码推理模型,名为 DeepCoder - 14B - Preview。
现在,只需 14B 就能够拥有一个与 o3-mini 相媲美的本地模型,并且这个模型是完全免费的。
DeepCoder-14B-Preview 是由分布式 RL 从 Deepseek-R1-Distilled-Qwen-14B 进行微调而得到的。
在 LiveCodeBench 基准测试里,它单次的通过率达到了 60.6%,提升的幅度达到了 8%。
至此,又见证了强化学习的胜利。
DeepCoder 在训练过程中涉及 LiveCodeBench(LCB)。训练到第 180 步时,其上下文长度被扩展至 32K。以 32K 时的最佳检查点进行推理,随后又将上下文扩展到 64K,在此情况下,LCB 得分能达到 60.6%,该得分与 o3-mini 的性能相当。
团队开源了 verl-pipe ,它是 verl 后训练系统的扩展,集成了多项系统优化,能够使端到端的训练速度提升 2 倍。
网友们对此称赞,这是开源的完全胜利,不仅模型是开放的,数据集、代码以及训练日志也都开放。
值得一提的是,DeepCoder-14B-Preview 是基于 24K 个能够被验证的编程问题来进行训练的。它在 32 个 H100 GPU 上进行了训练,训练时间长达 2.5 周。
数据集构建
数学领域的研究表明,强化学习若具备可验证的奖励机制,就能够大幅提升模型的推理能力。
在数学领域,网上能够找到许多高质量且可验证的数据。而在编程方面,与之相比则相对较为稀缺。
团队在早期实验中评估了一些常见的代码数据集,其中包括 APPS、TACO、CodeContests、KodCode 和 LeetCode。
发现有些数据集对模型而言较为简单,例如 KodCode 和 LeetCode;同时还有些数据集存在噪声,亦或是其中的测试用例存在缺陷、不够完整且无法验证。这些情况会给出错误的奖励信号,致使 RL 训练无法稳定地进行。
为解决这些问题,研究者整理出一个高质量的训练集,包括:
TACO里已验证过的问题。
PrimeIntellect 的 SYNTHETIC - 1 数据集中的问题是经过验证的。
2024 年 7 月 31 日提交的 LiveCodeBench 问题。
为了确保数据质量,以便让 RL 训练能够顺利地进行,存在着一套较为严格的过滤流程。
程序化验证:对于每个问题,会利用外部的官方解法进行自动检查。只留下那些官方解法能够通过所有单元测试的问题。这个检查过程是在 tests/rewards/test_code_batch.py 脚本中自动完成的。
测试过滤的要求是每个问题至少要有 5 个单元测试。如果测试用例少,问题就容易让模型钻空子,因为模型能通过识别常见测试用例,学会简单地输出记忆的答案,这就是所谓的“奖励黑客”。
去重:研究者会把数据集中重复的问题都去掉,防止互相干扰。他们检查了 Codeforces 的 57 个竞赛数据集。
过滤之后,获得了 24K 个高质量的编程问题,这些问题将用于 RL 训练。其中,有 7.5K 个编程问题来自 TACO Verified,16K 个来自 PrimeIntellect SYNTHETIC-1,还有 600 个来自 LiveCodeBench。
代码沙盒环境
为了计算代码 RL 训练的奖励,需要在代码沙盒中进行操作。具体来说,就是要对模型生成的代码进行单元测试。
在每个 RL 迭代过程中,会使用 1024 个问题去评估训练的效果,并且每个问题都至少配备有 5 个单元测试。
这么多测试任务,需要 100 多个代码沙盒一起并行运行,这样才能在合理时间内准确验证模型生成的代码。
目前,研究者使用了两种沙盒,一种是 Together 代码解释器,另一种是本地代码沙盒。
Together代码解释器
这个环境速度较快且效率较高,能够直接应用于 RL 训练。每个问题的成本仅仅是 3 美分。
Together 代码解释器具备这样的能力,它可以支持 100 多个沙盒同时进行运行。并且,它在每分钟内能够执行 1000 多次的沙盒操作。
这些沙盒还能够评估代码最后一行输出的结果。
同时,它能把代码运行的环境和主机系统隔离开,保证安全。
本地代码沙盒
本地代码沙盒通过启动一个独立且有防护的 Python 子进程来进行运行。它会从标准输入(stdin)那里接收测试用例的输入,接着把答案输出到标准输入(stdout)。
本地沙盒采用的是 LiveCodeBench 官方代码库里的评估代码,这样能保证测试结果与现有排行榜保持一致。
奖励函数
有些奖励方式会容易导致模型作弊。例如,对思维链(CoT)进行惩罚,或者在 N 个测试中只要有 K 个通过就给予 K/N 的奖励。
奖励函数选用稀疏结果奖励模型(ORM),其具体的奖励规则如下:
奖励为「1」:生成的代码需要通过所有被抽选出来的单元测试。由于有些问题存在着几百个测试用例,要全部进行验证是不太现实的。因此,会依据输入字符串的长度,从每个问题中挑选出 15 个最为困难的测试(通过输入字符串的长度来进行判断)。
奖励为「0」:如果模型生成的代码存在一个测试用例未通过的情况,或者答案格式不正确,像缺少 python[CODE]标记等,那么就不会有奖励。并且每个测试用例都有 6 至 12 秒的时间限制。
训练方法 GRPO+
研究者借鉴了 DAPO 的关键思路,从而对 GRPO 算法进行了改进,使得训练过程变得更加稳定。
GRPO 以及 GRPO 在 16K 上下文训练时的平均训练奖励情况如下:GRPO 的奖励曲线最终会出现崩溃现象,而 GRPO+由于具备 Clip High 机制,其奖励曲线能够保持稳定。
加上熵损失项后,很容易使训练变得不稳定,因为熵值会呈指数级增长,从而导致训练崩溃。所以就移除了熵损失项。
无 KL 损失(源自 DAPO):若去掉 KL 散度损失,LLM 便不会被限制在原来监督微调(SFT)模型的置信区域内。并且能够省去为参考策略计算对数概率这一环节,从而使训练速度更快。
超长过滤(源自 DAPO):为了保留长上下文的推理能力,对于超出长度而被截断的序列进行了特殊的处理。这项技术使得 DeepCoder 即便在 32K 的上下文环境中进行训练,在 64K 的上下文环境下也能够进行推理。这种过滤方式允许响应的长度自然地增长,不会因为被截断而受到惩罚。
GRPO+采用了超长过滤机制,所以它的响应长度会随着训练时间的推进而稳步增长。
Clip High(源自 DAPO):它通过提升 GRPO/PPO 代理损失的上限,从而促使模型去尝试更多不同的可能性,并且使得熵值变得更加稳定。在进行这样的调整之后,训练过程会更加稳定,同时模型的性能也能够得到提升。
Clip High机制以及不存在熵损失,这能够确保 GRPO+的 token 级熵不会出现崩溃的情况,并且会鼓励模型进行充分的探索。
迭代式上下文扩展
在关于 DeepScaleR 的介绍里,提及了迭代式上下文扩展技术。此技术可使模型先于较长的上下文在较短的上下文中学会有效思考,接着再将其应用到更长的上下文当中。
这个方法使得 1.5B 参数模型的下游任务性能逐步提升。在上下文窗口从 8K 扩大到 16K 之后,又扩大到 24K 的过程中,其在 AIME 测试中的准确率先是从 33%提升到 38%,接着提升到 43%,最终达到了 o1-preview 的水平。
不过,将这个技术用在14B参数模型的时候,遇到了新问题:
语言风格:保持原文风格,去除序号,把长句拆成一个个简短的分句。
这些更难的问题通常需要比 8K 更长的上下文窗口。之前小模型训练的起始上下文长度是 8K。
如果一开始以短上下文来进行训练,当模型输出超出此长度时就对其进行惩罚,这种做法的效果不佳。模型的初始性能会降低,其输出的内容会变短,并且长上下文的推理能力也会变弱。
研究者为了在保证训练效率的同时让模型能处理长上下文推理,引入了 DAPO 的超长过滤技术。在训练过程中,会对那些因为太长而被截断的序列予以忽略,如此一来,即便模型生成的内容稍长一些,也不会受到惩罚。
因此,模型即使在较短的上下文中训练,也能「想得长远」。
研究者将迭代上下文扩展应用于 DeepCoder - 14B - Preview ,把训练的上下文窗口由 16K 扩充至 32K 。在 LiveCodeBench 基准测试中,模型呈现出如下表现:
在16K和32K上下文长度下,准确率从54%提升至58%。
在64K上下文长度评估时,达到了60.6%。
这表明模型的泛化能力较为强大。即便超出了在训练时所限定的上下文范围,它依然能够展现出良好的表现。
DeepSeek-R1-Distill-Qwen-14B 是一种基础蒸馏模型,与之相比,DeepCoder-14B-Preview 的泛化能力更为突出。
基础蒸馏模型一旦超出训练时的上下文长度,性能就很难提升了。
DeepCoder 平均响应长度较长,所以在 16K 上下文长度下原始性能低一些,会因截断和格式问题被扣分。然而,它长上下文的推理能力很强,最终在 64K 上下文长度的评估中超越了其他模型。
DeepCoder 在训练过程中,其平均响应长度有变化。一开始平均响应长度为 8K,之后增长到了 17.5K。
DeepCoder 获得了成功,它是将迭代上下文进行了扩展,并且把超长过滤技术也结合在了一起。
从图中能够看到,在训练期间,模型的平均响应长度由 8K 提升至 17.5K,同时平均奖励从 0.6 增加到了 0.7。这表明随着时间的不断推进,模型学会了更为厉害且更有条理的思考方式。
关键技术改进
Deepcoder-14B-Preview 在多种编程基准上进行了评估,这些编程基准包括 LiveCodeBench (LCB)、Codeforces、Humaneval+以及 AIME2024 数学竞赛。
模型凭借 14B 的参数量,在所有编程基准上都展现出强劲性能。在 LiveCodeBench 上,它实现了 60.6%的 Pass@1 准确率。在 Codeforces 上,它获得了 1936 的评分。它的表现能够与 o3-mini (low) 和 o1 模型相媲美。
训练耗时太长?系统优化来帮忙
使用长上下文对 LLM 进行强化学习(RL)训练是很耗费时间的,并且需要在长上下文的环境里不断地进行采样以及训练。
如果没有系统层面的优化,完整的训练流程可能需要耗费数周甚至数月的时间。对于 14B 参数编程模型的训练,每一步就需要花费 1200 到 2500 秒,而总训练时长达到了 2.5 周。
团队引入了 verl-pipeline 并且将其开源。verl-pipeline 是开源 RLHF 库 verl 的一个优化版本,采用了多项系统级的改进措施,其目的是加速端到端的 RL 训练过程。
Verl 基准实现与 verl-pipeline 实现相比,后者实现了速度提升,提升幅度高达 2.5 倍。
运用这些新的系统优化对 DeepCoder-1.5B-Preview 模型进行训练,此模型在 LiveCodeBench 上的准确率达到了 25%,并且相较于 Deepseek-R1-Distill-Qwen-1.5B 提升了 8%。
采样器是瓶颈
在后训练过程中,采样通常是会拖慢整体进度的一个关键因素。原因在于,当使用 vLLM 和 SGLang 这类推理引擎来生成 32K token 的长序列时,会出现延迟现象。
Verl 的 PPO/GRPO 训练流程如下:每次进行 RL 迭代时,都包含采样这一阶段、奖励函数计算这一阶段以及训练这一阶段。在这些阶段中,采样是整个训练流程的瓶颈所在,并且训练速度会受到那些生成较长序列的掉队采样器(straggler samplers)的限制。
RL 训练系统一般会受到采样时间的限制。上图展示了 Verl 的 PPO/GRPO 流水线,在这个流水线中,响应长度存在不一致性,这种不一致性使得部分采样器成为掉队者。
这些掉队的会拖慢训练的进度,先完成任务的采样器处于空闲状态,这样就会导致 GPU 的利用率低下。
朴素解决方案:小批流水线化
研究者将采样和训练过程流水线化(Minibatch Pipelining),目的是减少 RL 训练过程中的空闲时间。
训练器会在采样器持续生成后续数据批次之时开始利用较早抵达的小批数据来进行模型更新,并且这种重叠执行的方式有助于降低采样所导致的延迟。
小批流水线的情况如下:采样器在一个工作机组中运行,训练器在另一个工作机组中运行;完成采样并释放出小批量数据(用于 PPO/GRPO 训练)后,训练器会异步处理这些数据;在一次迭代结束的时候,训练器会把更新后的权重给到采样器。
然而,这种方法存在三个关键的局限性:
小批数据的平均序列长度通常会随着训练的推进而变长。这样一来,处理后续小批数据的训练时间就增加了。其结果是,最后几个小批数据往往要在采样阶段结束之后才能处理完。正因如此,流水线化所带来的实际效益受到了限制。
流水线化需在采样器与训练器间静态划分 GPU 资源,这致使可用采样器数量减少。Verl 能在同一 GPU 池中动态切换采样器和训练器角色,而这种静态划分由于采样器数量减少,或许会导致端到端的总采样时间延长。
奖励函数的计算耗时可能会比较长,尤其是对于编程类任务,每个强化学习(RL)迭代都需要运行数千个单元测试。在 Verl 的默认设置里,奖励计算是在所有采样任务都完成之后,于头节点(head node)上集中展开的。
团队在代码库的 ray_trainer_pipeline.py 文件中实现了小批流水线化,尽管存在一些约束。同时需要指出的是,这种流水线技术能够通过引入微批处理来进一步优化。
DeepCoder的解决方案:一次性流水线化
研究者为了实现训练、奖励计算和采样的完全流水线化,引入了一次性流水线化。即通过这种方式,能够让训练、奖励计算和采样等过程实现流水线化,达到完全流水线化的效果。
采样器会提前一个迭代周期生成一批数据,训练器使用上一次迭代的数据来更新梯度。奖励函数的计算与采样过程是交错进行的。这种一次性流水线的方法不会为 GRPO/PPO 的策略算法引入异步离策略样本。
其思路较为简单:首先牺牲掉第一个 RL 迭代,仅仅去执行采样任务。接着,利用通过这个采样所得到的数据批次,在接下来的一个迭代中进行训练。
采样可以并行处理,训练也可以并行处理,这样就彻底消除了采样完成后训练器的等待空闲时间。
其次,奖励计算被嵌入到采样流程中,与之交错执行。
某个采样请求一旦完成,其对应的奖励就会立刻被计算出来。这样做有效减少了奖励评估环节的开销,尤其在计算密集型任务(像编程任务中的测试用例执行)时,效果更为显著。
团队在代码库的 verl 分支的 fork 中,于 ray_trainer_async.py 文件里实现了一次性的流水线化。
端到端性能
一次性流水线将训练器和奖励计算的时间完全掩盖了。数学任务的训练时间缩短了 1.4 倍,编程任务的训练时间缩短了 2 倍。
上图展示了关于 verl 以及小批流水线化和一次性流水线化在数学和编程这两种工作负载下的评估结果。
为保证公平性,所有基准方法都借助 Python 线程池来进行并行计算奖励。而 verl 官方的实现则是逐个串行地计算每个样本的奖励,这种方式在编程任务中花费的时间过长,所以难以在实际中应用。
这样做的目的是更好地平衡两者所需的时间开销。
对于数学任务,一次性流水线化使得每次 RL 迭代所需的时间缩短了。具体来说,缩短的幅度为 1.4 倍。值得一提的是,数学任务的奖励计算时间几乎可以忽略不计,因为它仅仅涉及基础的 Sympy 检查。特别之处在于,一次性流水线化可以将训练器所需的时间完全掩盖起来。而在小批流水线化中,最后一个小批会出现“溢出”的情况,从而导致延迟,这与一次性流水线化形成了对比。
对于编程任务而言,在每次 RL 迭代中要运行数千个单元测试来计算奖励,这是一个耗费时间极长的过程。一次性流水线化具有这样的作用,它可以同时将训练器时间和奖励计算时间进行掩盖,进而使得端到端的训练总时长得以缩短 2 倍。
关键在于,一次性流水线化不但切实可行,还能够成功地应用于复杂的编程任务。
DeepCoder 使用 ray_trainer_async.py 进行训练,该训练采用一次性流水线化方式。通过这种方式训练出了 DeepCoder - 1.5B - Preview 。在 LiveCodeBench (LCB) 上,它的得分相较于基础的蒸馏模型提升了 8% 。
作者介绍
Sijun Tan(谭嗣俊)
谭嗣俊在 UC 伯克利攻读计算机科学专业,目前是三年级博士生。他的导师是 Raluca Ada Popa。他隶属于伯克利的 Sky Computing Lab。
他之前在弗吉尼亚大学拿到了计算机科学和数学的双学士学位,他的导师是 David Wu 和 Yuan Tian。
他有过在 Facebook AI Research(FAIR)实习的经历,且在那段时间里待了一段时间。他还在蚂蚁集团担任过高级算法工程师这一职务。
他的研究领域包含机器学习,也包含计算机安全,还包含应用密码学。当下,他的研究重点在于提升通用型 AI 智能体的能力,同时提升其鲁棒性。
Michael Luo
Michael Luo 现在是 UC 伯克利的博士生,所属院系为电气工程与计算机科学系(EECS),他的导师是 Ion Stoica 教授。
他此前获得了学位。其一为 UC 伯克利的电气工程与计算机科学硕士学位。其二为工商管理学士学位。这两个学位是双学位。
他的研究兴趣主要集中于人工智能和系统这两个领域。当下,他的研究主要是给机器学习从业者搭建能够扩展的系统,目的是实现 Sky Computing 的愿景。
Roy Huang
Roy Huang 现在是 UC 伯克利的学生,处于计算机科学专业的大四阶段。他对 CV 领域的研究感兴趣,同时也对 NLP 领域的研究感兴趣。
参考资料:
本文来源于微信公众号“新智元”,作者是新智元,36 氪获得了发布的授权。