你有没有想过,训练一个能自己写代码、自己搭建整套软件项目的AI,需要花多长时间?

答案可能会让你吃惊:一次任务执行,能跑上好几个小时,模型和环境要来回交互几百次,处理的文字量接近一百万个token。这不是夸张的说法,这是阿里通义团队在训练他们的旗舰模型时,真实遇到的场景。


(相关资料图)

问题是,这么长的任务一旦开始训练强化学习,整个系统就开始出现一个诡异的现象:一大堆昂贵的GPU,在很多时候什么都没干,就那么空转着。

这篇论文要讲的,就是这个"GPU睡大觉"背后的原因,以及阿里团队怎么设计了一整套叫做QWENGYRE的框架来解决它。

从改一个bug到重建一整个项目,任务变得有多长

先说说背景。

早期的AI编程智能体,做的事情相对简单。比如著名的SWE-bench测试,让AI去修复一个已有代码库里的具体bug,通常几十次交互就能搞定。这时候用强化学习去训练模型,效果已经相当不错了。

但现在的野心大了很多。业界开始尝试让AI从零搭建整个软件仓库,重新实现一整套系统,甚至对一个大型代码库做全局重构。这些任务被论文称为"XLong"(极限长)horizon任务。

一次执行动辄几个小时,涉及几百次模型和环境的交互,处理的token量逼近百万级别。这已经不是"写代码"了,这是"做一个项目"。

这种规模的变化,带来了两个根本性的麻烦。

第一个麻烦是资源调度的问题。

传统的强化学习训练,通常采用"同步"(Colocate)模式,也就是让GPU在"跑任务"(rollout)和"训练模型"(training)这两个角色之间来回切换。

> 强化学习*:一种让AI通过不断尝试、犯错、获得反馈来学习的训练方式,类似于让一个人反复练习某项技能,每次根据结果调整策略。

> Rollout*:指AI智能体在环境里实际执行一次任务的完整过程,包括它和工具、代码库之间的所有交互。

这套逻辑在任务时间比较短、比较一致的时候没什么问题。但当任务变成动辄几个小时,而且每次任务花的时间差异巨大的时候,麻烦就来了:训练必须等所有正在跑的任务都结束才能开始,可总有那么几个任务跑得特别慢,拖着后腿。

想象一下你和几个朋友约好一起吃饭,规定"所有人到齐才开始点菜"。如果九个人已经到了,就剩一个人堵在路上迟迟不到,那剩下九个人只能干坐着等。如果不设置"所有人到齐"这个规则,先到的人可以先聊聊天、看看菜单做准备,效率显然更高。这就是同步模式的代价:大部分GPU都提前"到齐"了,却因为规则问题被迫陪着最慢的那个任务一起空转。

另一种做法叫"异步"(Async),干脆把GPU资源分成两块,一块专门跑任务,一块专门搞训练,两边各干各的。这样确实避免了互相等待,但问题是这个划分是固定的。

> 异步调度*:把计算资源提前切割成固定的两部分,一部分专门负责生成任务轨迹,一部分专门负责模型训练,两者同时运行,互不干扰。

固定划分意味着,当跑任务那边突然空闲下来(比如一批任务集中到期结束了),训练那边就算忙不过来也借不到这些闲置的资源;反过来,当训练那边已经做完这批数据,要等下一批任务生成时,分给训练的那部分资源也只能干等着,不能临时去帮忙跑任务。

这就好比一个餐厅永远死板地分配"三个厨师负责炒菜,两个服务员负责端菜"。哪怕某天客人特别多需要更多人炒菜,那两个服务员也不能去帮忙下厨,即使他们此刻正好没事干。灵活性的丧失,才是异步模式的真正代价。

第二个麻烦,来自任务本身变得越来越"不直"了。

早期的智能体循环是很简单的一条直线:提问、回答、调用工具、拿到结果、继续下一轮。但现在支撑几个小时执行的黑盒智能体框架(比如Claude Code这类工具),会做很多复杂的事情:上下文窗口满了就"压缩"历史记录,复杂任务会拆分给子智能体分别处理,失败的路径还会重试。

> 黑盒智能体框架*:像Claude Code这样的商业智能体工具,内部逻辑不对外公开也无法随意修改,研究者只能通过接口观察它的输入输出,而不能改动它的运行方式。

结果就是,一次任务执行不再产出一条干净的轨迹,而是变成一张枝枝蔓蔓的图。有的分支是主智能体在推进任务,有的分支是子智能体在处理被委派的子任务,还有的分支是压缩历史后产生的"摘要"版本。这些分支之间共享着大量重复的上下文内容。

如果把所有这些分支原样展开,当成独立的训练样本一股脑喂给模型,会发生什么?训练成本会爆炸式增长,因为大量重复的前缀内容被反复计算了好几遍。这就好比复印一份需要签字的十页合同,你没必要把前五页共同的条款每次都重新打印一遍再签字,共享的部分留一份就够了,只有真正独立的签字页才需要单独处理。

理解了这两个麻烦,你就能明白QWENGYRE到底要解决什么问题了:它得让GPU资源能灵活地在跑任务和训练之间流动,同时又不能打断正在进行的任务;它还得从这些乱糟糟的分支轨迹里,提炼出干净、不重复、有效的训练数据。

弹性调度器:让GPU资源像水一样自由流动

QWENGYRE给出的第一个方案,叫做弹性调度器(Elastic Scheduler)。

它的核心思路是把整个GPU集群切分成很多个小的"单元"(cell),每个单元既可以拿去跑任务,也可以拿去做训练,角色可以随时切换。

这里有个关键设定:切换角色的时候,正在运行的智能体任务不会被打断。

具体怎么做到的?论文里描述了一个叫做"水位"(waterlevel)的概念,用来追踪当前还有多少任务正在排队和运行。调度器会持续监测这个水位:如果水位下降(意味着越来越多任务已经跑完),说明剩余的跑任务需求变少了,这时候就可以把一部分单元释放出来去做训练。

> 弹性单元(elastic cell)*:一组共享同样并行配置的GPU,可以整体切换角色,既能承担模型推理(跑任务),也能承担参数更新(训练),内部通信组保持不变,只是加载的内容不同。

这个设计里有个特别聪明的地方,叫做"核心单元"(core cell)和"卫星单元"(satellite cell)的分工。核心单元一直持有权威的模型参数和优化器状态,它是训练的"锚点"。而卫星单元可以在训练进行到一半的时候,随时拉取核心单元当前的参数快照,加入到正在进行的这一批训练里,不会打断核心单元原本的工作节奏。

这就好比一场接力跑步比赛正在进行,通常你以为所有跑步者必须同时起跑。但QWENGYRE允许后面的跑者在比赛已经开始一段时间后,直接从旁边插入进来,跟上当前的节奏一起跑完剩下的路程,而不需要整场比赛暂停等他准备好。如果不允许这种"中途加入",那意味着必须等所有单元都准备好、同时按下开始键才能训练,这正是同步模式效率低下的根源。QWENGYRE把这个"必须同时开始"的硬性约束打破了。

论文里给出的实际测量数据很说明问题:单元从跑任务切换到训练平均只要8.52秒,从训练切回跑任务平均只要3.46秒。相比动辄几个小时的任务执行时间,这个切换成本几乎可以忽略不计。

![Elastic Scheduler示意图]

在这套弹性调度背后,还有一个叫做"边界分发"(boundary dispatch)的机制在管理任务的进出节奏,论文用一个专门的数学量"调度陈旧度"(scheduling staleness)来衡量一批训练数据用的是多老的模型版本采集来的。

> 调度陈旧度*:指某批任务被派发出去时用的模型版本,和它最终被拿来训练时的模型版本之间,差了几个训练步。数值越小,说明训练用的数据越"新鲜"。

论文对这套机制做了严谨的数学证明,推导出不同调度策略下陈旧度的精确公式。这部分内容偏理论,简单说结论就是:通过调整"额外派发量"和"每次权重发布前的训练步数"这两个参数,可以让QWENGYRE、异步模式、同步模式在同一个陈旧度水平上公平比较速度。

实验结果显示,在NL2RepoBench这个基准上,用Qwen 3.6 122B模型训练,QWENGYRE相比异步模式实现了1.42到1.53倍的加速,相比同步模式实现了1.36到1.47倍的加速,而且训练分数几乎没有差别。

规模再放大一点,换成阿里的旗舰模型Qwen 3.8 2.4T,单次rollout要处理70万token,QWENGYRE跑完48个训练步只用了75.42小时,而异步模式要134.55小时,同步模式要91.47小时。加速比分别达到了1.78倍和1.21倍。同时,这个模型在NL2RepoBench上的通过率也从52.5%提升到了58.5%。

轨迹处理器:把一团乱麻理成能用的训练数据

解决了GPU资源怎么流动的问题,接下来要面对的是那团枝枝蔓蔓的执行轨迹。

QWENGYRE的解法,是引入了一套叫做轨迹处理器(Trajectory Processor)的机制,核心思想可以概括成三步:先完整记录、再评估打分、最后按优先级挑选。

第一步叫TITO,全称是token-in、token-out。

> TITO*:一种记录方式,精确保存每一次模型调用的输入token和输出token的原始内容,以及模型输出这些token时对应的概率值,不做任何后续加工或篡改。

为什么要这么死板地保存原始token?因为智能体框架经常会对历史记录做"压缩"、"重写"这类操作,如果只保存框架处理后的最终版本,你就无法追溯某个输出当初到底是基于什么样的上下文生成的。TITO保证了即便原始历史后来被压缩掉了,每个输出的"当初语境"依然完整保留着。

这就好比你在写日记的时候,后来觉得某一页写得太啰嗦,把它撕掉换成一句总结。但如果你想复盘当初为什么会做出某个决定,光看那句总结是不够的,你需要那张被撕掉的原始页面。TITO做的事情,就是在撕掉之前先拍照存档,而不是任由原始信息永久消失。

有了这些原始记录,QWENGYRE把它们组织成一种叫做"轨迹树"(trajectory tree)的结构。

> 轨迹树*:把一次任务执行里所有产生分支的上下文路径,组织成一棵树,共享的前缀部分只存一份,不同的分叉才单独记录成新的节点。

这样做的好处很直接:相同的前缀内容不会被重复存储,大大压缩了数据规模。论文里案例研究部分给出了一个真实例子,一次执行产生了1347个独立的消息节点,如果把这10条候选轨迹全部原样展开,会产生1716次节点重复出现,多出的369次重复完全是可以避免的浪费。

树建好之后,接下来是打分环节。这一步要处理一个很现实的麻烦:任务经常没跑完就超时了,这时候该怎么算分?

论文的处理原则是,评估当前已经完成的部分工作,给出一个能反映"部分进度"的分数,而不是粗暴地把所有超时任务都打成零分。这一点很重要,因为一个已经完成80%工作量却因超时被判定失败的任务,和一个几乎什么都没做就崩溃的任务,理应得到完全不同的反馈信号。如果不区分,模型会学不到哪些行为其实是在朝正确方向努力,只会学到"反正超时就是零分,不如乱试"。

打完分,最后一步是从每次执行产生的所有候选轨迹里,挑出一部分拿来真正训练用。这里设定了一个上限值,论文里默认设为5条。

挑选不是随便挑的,而是按照角色优先级来。主智能体推进任务的轨迹优先级最高,其次是主智能体的摘要轨迹,再往后是子智能体的任务轨迹,最后才是子智能体的摘要轨迹。这个排序背后的道理是:最终的任务奖励,最直接反映的是主智能体做了什么,子智能体只是在完成被委派的局部工作,贡献是间接的。

共享的部分只算一次:损失函数怎么公平分配权重

挑出轨迹之后,还有一个容易被忽略但至关重要的细节:怎么计算损失(loss),才能不让训练结果被扭曲。

试想一下,如果一次执行产生了5条被选中的轨迹,其中4条都源自子智能体的短暂对话,只有1条是主智能体推进任务的长轨迹。如果简单粗暴地对5条轨迹的损失做平均,那4条子智能体的短轨迹加起来的权重,会远远超过那1条真正代表任务主线的长轨迹。这显然不合理,任务奖励评估的明明是整个任务的完成情况,不应该因为子智能体产生的碎片轨迹数量多,就让它们在训练里占据不成比例的话语权。

QWENGYRE的解决方式是,先在同一次执行内部,对所有被选中且互不重复的目标token做平均,得到这次执行的一个损失值,然后再在批次内对所有执行做平均。这样无论一次执行贡献了1条轨迹还是5条轨迹,它在整个批次里占的权重始终是一样的。

另外还有一个细节,共享的目标token只计算一次损失。

论文的案例研究里有个很具体的例子:主智能体在做任务压缩(compaction)之前,产生了一段任务轨迹和一段摘要轨迹,这两段轨迹分享同一段前缀内容。如果这两条轨迹都被选中训练,那共享的前缀部分的损失绝不能被算两遍,否则等于人为放大了这部分内容对梯度更新的影响。QWENGYRE通过"逐个抽取轨迹后就把对应路径标记为已用"的机制,确保每个token的训练目标只被固定一次,后续抽取会自动避开已经用过的部分。

论文做了一个专门的消融实验来验证这套挑选机制的价值:如果每次执行只保留1条主轨迹,训练分数明显偏低,梯度范数也偏大不稳定;把上限提高到5条,训练分数就能追平"不设上限、全部保留"的效果,同时前向反向传播的时间只是不设上限情况下的74.8%,省下了整整四分之一的计算时间。这说明5条这个上限,是一个性价比很高的折中点。

消融实验揭示的三条演化路径

论文里有一组特别有意思的消融实验,展示了从传统的异步模式和同步模式,一步步演化到QWENGYRE的完整路径,这部分内容值得细看,因为它清楚地回答了"每一步改进到底带来了什么"这个问题。

|方案|说明|相对QWENGYRE耗时倍数|

|原始异步(A0)|固定训练/跑任务节点分配|1.556|

|异步+流式训练(A3)|开始训练不用等完整批次|1.376|

|原始同步(C0)|整个池子整体切换角色|1.442|

|同步+独立跑任务节点(C1)|加入少量专用跑任务节点|1.400|

|同步+流式+角色切换(C2)|结合上述两项改进|1.193|

|**QWENGYRE(E0)**|**细粒度弹性分配+流式训练**|**1.000**|

从这张表能看出几条清晰的规律。

先看异步这条线。研究者试过把节点从训练重新分配给跑任务(A0变成A1),结果耗时反而增加了7.7%。原因是训练节点变少之后,消耗掉一批完成的任务反而更慢了,拖慢了权重发布的节奏。加长训练时段(A2)同样效果不好,增加了29.9%的耗时,因为固定的跑任务资源池根本供应不上这么高的训练需求。真正有效的改进是引入流式训练(A3),让训练可以在一个批次的所有数据都齐备之前就开始处理已完成的部分,这一步减少了11.6%的耗时。但即便有了流式训练,异步模式固定分割资源的根本限制依然没有解决,它无法把空闲的GPU动态调配给对方。

再看同步这条线。给同步模式加上少量独立的跑任务节点(C0变成C1),只带来了2.9%的微小改善,因为整个同步池子还是要整体一起切换,能省下的时间有限。接着叠加流式训练(C2),耗时降低了14.8%,进步明显,但28个节点仍然必须一起切换角色,流式训练只能在这个批次接近末尾时才真正发挥作用。如果试图进一步增加独立跑任务节点的数量来缩小需要整体切换的池子(C3),反而会让训练阶段变长,拖慢了下一轮的派发节奏,耗时相对C2又增加了7.6%。这说明单纯调整比例解决不了根本问题,问题出在"切换粒度太粗"这件事本身。

真正的突破发生在把C2的固定切分,换成8个可以独立切换的小单元(也就是E0,也就是QWENGYRE本身)。这样一来,训练可以先从一部分单元开始,数据源源不断到来时再逐步扩展到更多单元,其他单元继续跑任务不受影响。这一步相对C2带来了1.19倍的加速。

那么,单元切得越细是不是就越好?论文专门测试了这一点:把8个4节点的单元合并成4个8节点的单元(E2),结果只比E0慢了2.2%。这个数字告诉我们,在这个具体的32节点规模下,细粒度调度的收益已经趋于饱和,四个单元基本就能捕获大部分好处了,继续切得更细边际收益已经很小。这也提示了QWENGYRE这套设计的一个现实边界:单元数量的选择需要结合具体的资源规模来权衡,不是无限细分越好。

写在后面

读完这篇论文,最让我意外的一点,是那个关于"共享部分只算一次损失"的案例研究。论文里给出的f3ad38e5这个真实执行案例特别生动:一次任务里,主智能体先尝试了一次FDW模块的构建,中途被打断,又重新发起了第二次尝试,而且中间还夹杂着子智能体因为并发限制被拒绝调用的记录。这种混乱程度,远比我最初想象的"轨迹分叉"复杂得多。它让我意识到,所谓的"黑盒智能体"训练,处理的从来不是干净的实验室数据,而是真实世界里那些失败、重试、被打断、又恢复的凌乱记录。

另一个值得单独说的细节,是论文附录里那套关于调度陈旧度的数学推导。研究者没有停留在"我们观察到加速了"这个层面,而是给出了严格的公式,证明在什么条件下异步模式和边界分发模式的训练时间应该趋于一致。这种愿意把直觉转化成可以证明的数学关系的态度,在系统类论文里其实并不常见,大部分工程论文满足于实验数字对比,很少往这个方向深入。

还有一点让我反复琢磨:论文提到Qwen 3.8 2.4T在NL2RepoBench上的超时率,比小模型Qwen 3.6 122B更高,达到61.1%的查询要跑满四个小时。这似乎在说,模型越强、能力越大,反而越容易把任务拖向时间的极限,因为它敢于尝试更复杂、更完整的解决方案。这是不是意味着,随着智能体能力继续增长,"超长horizon"这个问题只会越来越突出,而不会随着模型变强而自然缓解?这个问题,论文没有直接回答,但留下了值得继续追问的空间。

Q&A

Q1:QWENGYRE是什么?

A:QWENGYRE是阿里通义团队提出的一套端到端框架,用于在超长horizon智能体任务上做在线强化学习训练,核心是弹性调度器和轨迹处理器两部分,能大幅提升GPU利用率和训练效率。

Q2:QWENGYRE相比传统的同步和异步训练方式能提速多少?

A:论文实验显示,QWENGYRE相比异步模式最高可实现1.78倍加速,相比同步模式最高可实现1.85倍加速,同时训练分数和基线方法基本持平。

Q3:QWENGYRE怎么解决智能体任务分支多、训练数据冗余的问题?

A:它把每次执行的记录组织成共享前缀的轨迹树,只保留有限数量的关键轨迹,并确保共享的目标token在损失计算中只被计算一次,避免重复内容拖慢训练、扭曲权重分配。

推荐内容