ProRL Agent: Rollout-as-a-Service for RL Training of Multi-Turn LLM Agents
Paper: arXiv:2603.18815 Code: NVIDIA-NeMo/ProRL-Agent-Server Code reference:
training_code@7bdfbde(2026-03-20)
1. Motivation (研究动机)
这篇论文解决的不是“再发明一个 RL 算法”,而是多轮 LLM agent 做 RL 时最容易被低估的系统瓶颈:rollout 本身已经变成一个复杂、异步、I/O 密集、带沙箱生命周期管理的服务,而很多训练框架仍把它塞在 trainer 内部。
多轮 agent 任务和单轮 RLVR 的差异很大。单轮任务通常只需要给模型一个 prompt,采样回答,计算 reward;而软件工程、网页浏览、OS 操作、代码执行等 agent 任务会产生几十轮工具调用和上万 token 的轨迹。每个样本都可能包括容器启动、文件读写、shell/IPython 执行、LLM 多次推理、测试运行、最终 reward 计算。论文认为 rollout generation 会随着任务复杂度变成主要瓶颈,尤其是 SWE 任务中每次环境交互的延迟都高度不稳定。
现有 agentic RL 框架的关键问题是 rollout 和训练栈耦合过紧:
- 系统需求冲突:rollout 是 I/O 密集型,包含 sandbox 创建、长期工具会话、跨数百实例的异步协调;policy training 是 GPU 密集型,核心是前后向、梯度同步和 checkpoint 更新。把二者放在同一个进程/框架里,容易让 rollout 的慢 I/O 拖住 GPU,或让 trainer 逻辑被环境细节污染。
- 迁移和维护成本高:如果 rollout 逻辑嵌在 veRL、NeMo RL、SkyRL 等某个 trainer 内,切换训练后端就需要重写 agent 执行栈;反过来,想支持新任务、新 runtime、新工具,也会牵动训练代码。
- agent 环境对 HPC 不友好:许多 sandbox 方案默认 Docker daemon 或 root-like 权限,但共享 Slurm/HPC 集群通常不允许用户开 Docker daemon。大规模训练时,环境隔离、端口管理、容器清理、工具后端延迟都会直接影响吞吐。
论文借鉴 inference-as-a-service,把 agent rollout 也服务化:trainer 不直接管理环境、工具和评测,只向独立 HTTP 服务提交任务实例,拿回 token-level trajectory、reward 和状态。这个设计的价值在于把 I/O 密集的 rollout 节点和 GPU 密集的训练节点拆开,让两边可以分别扩展、分别优化、分别替换。
Figure 1 解读:左侧是传统 coupled design:agent 生命周期、环境执行、工具调用、evaluation 都嵌在 RL trainer 里;右侧是 ProRL Agent 的 decoupled design:trainer 只通过 HTTP 发 POST /process 请求并接收轨迹与 reward,rollout server 负责 sandbox、工具、LLM backend 路由、评测和取消任务。这张图是全文的核心动机:把 rollout 当成独立基础设施,而不是某个 trainer 的内部函数。
2. Idea (核心思想)
核心 insight:agent RL 的 rollout 生命周期已经足够复杂,应该像 LLM inference 一样被服务化;trainer 只消费标准化的 trajectory/reward,不应该拥有环境生命周期。 ProRL Agent 因此提出 Rollout-as-a-Service,把 sandbox execution、multi-turn agent loop、reward evaluation、LLM backend routing 和 job cancellation 封装在独立 HTTP 服务中。
与 SkyRL-Agent、VeRL-Tool、rLLM、Agent Lightning、GEM 等方法相比,ProRL Agent 的本质差异不是多一个任务 wrapper,而是系统边界不同:这些框架多把 rollout orchestration 作为 trainer 进程内库来调度;ProRL Agent 则让任意 trainer(论文中提到 veRL 和 NeMo RL)通过统一 API 访问 rollout 服务。这样新任务只需要实现服务侧 AgentHandler,训练侧不必知道任务如何启动容器、如何跑工具、如何打分。
论文的主要创新可以概括为四点:
- RaaS 架构:完整 agentic rollout 生命周期由 HTTP server 提供,trainer 只发任务和接收结果。
- HPC-native sandbox:用
SingularityRuntime和.sif镜像支持 rootless、Slurm 友好的大规模 agent 执行。 - 高吞吐 server 机制:INIT/RUN/EVAL 三阶段异步 pipeline、LLM backend min-heap 负载均衡、phase-aware timeout、stale job cancellation。
- 训练稳定性细节:token-in/token-out 避免多轮轨迹重分词漂移;高效 DAPO 用异步补充、早停和跨 iteration 保留减少无效 rollout。
3. Method (方法)
3.1 总体框架:把 rollout 生命周期切成服务边界
ProRL Agent 有三个层级:Sandbox Environment、ProRL Agent Server、RL Trainer。Sandbox 负责真实执行任务;Server 负责任务排队、阶段调度、LLM backend 管理、取消和状态;Trainer 只作为 client,通过 HTTP 控制 rollout 服务并用返回轨迹更新 policy。
Figure 2 解读:图中最重要的是三条边界。第一,Sandbox Environment 里每个任务都在 SingularityRuntime 中运行,由 AgentHandler 定义 init()、run()、eval()。第二,ProRL Agent Server 内部把一个 job 拆为 INIT、RUN、EVAL 三个阶段,并维护 LLM backend pool。第三,RL Trainer 不再包含 agent loop,而是通过 /process、/add_llm_server、/cancel 等 API 与服务交互;完成的 trajectories 和 rewards 再回到 trainer 做 policy update。
直觉上,这相当于把 agent rollout 从“训练代码里的 for-loop”改成“可扩容的流水线工厂”。容器初始化、agent 执行、测试评测这三类工作耗时和资源需求不同;如果同一个 worker 串行做完三步,大量时间会花在等待慢阶段。拆成流水线后,某个 job 在 EVAL 时,另一个 job 可以在 RUN,第三个 job 可以在 INIT,从而让不同资源同时工作。
3.2 Sandbox Environment:任务插件 + rootless runtime
AgentHandler 抽象
论文把任务特定逻辑压缩为一个 handler 接口:
init(job_details):为任务准备 sandbox、metadata 和 OpenHands 配置,例如拉起 runtime、配置工具、写入初始文件。run(job_details):在准备好的环境中执行多轮 agent loop,收集 action-observation trajectory 和任务产物。eval(job_details):对 agent 输出打分,返回标量 reward 或 resolved 状态。init_exception/run_exception/eval_exception/final_result:保证失败时也能返回结构化结果,而不是让 trainer 卡死。
released code 中这个接口对应 openhands/nvidia/registry.py 的 AgentHandler 和 register_agent_handler();具体任务实现包括 SweAgentHandler、STEMHandler、MathHandler、CodeHandler 等。README 也明确说明新增任务只需要实现 name、init、run、eval 并注册,使 instance["data_source"] == name 能路由到对应 handler。
SingularityRuntime 与镜像构建
ProRL Agent 面向 HPC/Slurm 场景,因此不用依赖 Docker daemon。SingularityRuntime 以普通用户进程启动容器,镜像打包成 .sif 文件;SingularityRuntimeBuilder 用 Jinja2 模板构建镜像,并支持 Scratch、Versioned、Lock 三种缓存模式。这个设计让不同任务可以定义不同 sandbox,同时保留统一 server 生命周期管理。
工具后端优化
论文特别强调 rollout 的工具 latency 会累积成系统瓶颈,优化了三类工具后端:
- Efficient Bash:用
ptyprocess直接提供 pseudo-terminal,替代传统 tmux session,减少 shell round-trip 开销;released code 中相关实现位于openhands/runtime/utils/efficient_bash.py。 - IPython:不通过 Jupyter kernel gateway 做额外网络跳转,而直接连接 kernel in-process API,适合数学和代码 agent 在多步中复用变量、import 和 helper 函数。
- UDS communication:容器内 action execution server 与外部 client 用 Unix domain socket,而不是 TCP loopback;这样避免端口冲突并降低 IPC 开销。相关 UDS 路径散布在
openhands/runtime/action_execution_server.py、openhands/runtime/impl/action_execution/action_execution_client.py、openhands/runtime/impl/singularity/singularity_runtime.py。
3.3 ProRL Agent Server:异步流水线、后端管理与取消
Server 的基本 API 包括:
POST /add_llm_server {"address": "http://host:port/v1"}:注册 LLM backend。POST /clear_llm_server:清空旧 backend,用于 checkpoint swap。POST /process {"instance": {...}, "sampling_params": {...}}:提交 rollout job。POST /cancel {"job_id": "..."}:取消正在运行的 job。POST /start、POST /stop、GET /status:服务生命周期和队列状态。
论文中的简化逻辑是三阶段 worker pool:
STAGES = [INIT, RUN, EVAL]
queues = {stage: Queue() for stage in STAGES}
pools = {stage: ThreadPool(num_workers[stage]) for stage in STAGES}
while running:
job = queues[stage].get()
if job.id in discarded:
continue
with job.timer.phase(stage):
try:
result = handler[stage](job)
except Exception as exc:
result = handler[f"{stage}_exception"](job, exc)
job.store(stage, result)
if stage == RUN:
cleanup(job.runtime)
if stage != EVAL:
queues[next_stage(stage)].put(job)
else:
job.done.set()released code 的实现是同一思想的 process/semaphore 版本:scripts/start_server.py 暴露 FastAPI endpoint,子进程持有 OpenHandsServer;openhands/nvidia/async_server_process.py 里 ConcurrencyControl 用 init/run/eval 三个 mp.Semaphore 限制各阶段并发,Worker.run_step() 用 PausableTimer 记录 phase 时间并执行 handler。也就是说,论文里的 queue/pool 是抽象示意,代码用多进程和 semaphore 落地。
LLM backend 负载均衡与 checkpoint swap
ProRL Agent 让 trainer 动态注册和清空 LLM server。一次 policy checkpoint 更新后,trainer 可以 clear_llm_server,再注册加载了新权重的 vLLM endpoint;后续 rollout 自动使用新 backend,不需要重启 rollout server。
负载均衡使用 min-heap。若第 个 backend 的已分配计数为 ,每次选:
论文说明这个计数按 task 而不是按单次 LLM call 更新,因此同一任务内的多次调用会被固定路由到同一个 backend,尽量复用 prefix cache。released code 中 OpenHandsServer.weighted_addresses 是 [weight, address] 的 heap,get_llm_server_addresses() 取堆顶、计数加一并 heapreplace()。
Token-in / Token-out
多轮 agent 训练的一个隐蔽问题是 re-tokenization drift:如果 rollout 过程中产生的是文本,而 trainer 重新 tokenize,最终 token 序列可能与模型采样时的 token 不一致,导致 off-policy 偏差。ProRL Agent 以 token IDs 作为 canonical representation:rollout worker 发送 prompt_ids,接收 response_ids 和 per-token logprobs;消息保留 input_ids、output_ids、logprobs。多轮对话中,历史 assistant token 直接拼接,只有新的环境 observation 需要 tokenize。
released code 中 token-level 路径主要在 trainer_integration/verl/verl_custom/nvidia/rollout/async_server.py、vllm_async_server.py、openhands/llm/nvidia/qwen3.py 和相关 reward manager 中;launch script 通过 +actor_rollout_ref.rollout.token_level_generation=True、+actor_rollout_ref.rollout.logprobs_mode=processed_logprobs、actor_rollout_ref.rollout.calculate_log_probs=True 打开这一路径。
Job lifecycle、timeout 与 stale cleanup
每个 job 有 phase-aware timeout:PausableTimer 只统计 INIT/RUN/EVAL 真正执行的时间,不把排队等待算进去。这样 timeout 更接近真实执行成本,而不是 server 负载造成的等待成本。
POST /cancel 会把 job 标记为 discarded,取消当前 async task,关闭容器 runtime,释放资源并唤醒等待中的 HTTP handler。released code 中 start_server.py 的 control message 会调用 server.cancel_job(job_id);async_server_process.py 用 JobState.force_terminate() 清理 job process 和子进程,并通过 dead-process monitor 防止 semaphore/lock 泄漏。
3.4 Efficient DAPO:为长耗时 agent rollout 改造动态采样
论文默认采用 DAPO(Dynamic Sampling Policy Optimization)。DAPO 会过滤 Zero-Variance Prompts:如果一个 prompt 的 条 rollout 全对或全错,则 reward 方差为 0,对 policy gradient 基本没有训练信号。可以写成: 但 agent rollout 很慢、异步、昂贵,朴素 batch-by-batch DAPO 会浪费大量时间:先请求 个 prompt,过滤无信息样本;如果不足目标 batch size,再发下一批;上一批中未完成或多余的 rollout 可能被丢弃。
ProRL Agent 的改造有三点:
- Continuous Throughput:job queue 一空就补充新任务,不让 worker 闲置。
- Early Termination:一旦收集到目标数量的 informative prompts,就取消剩余活跃 jobs。
- Cross-Iteration Persistence:未完成 jobs 不直接丢弃,而是保留到下一 iteration,减少数据浪费。
Figure 3 解读:朴素 DAPO 是一批结束后再决定是否补采样,中间会产生等待空洞;ProRL Agent 的实现把采样、过滤、补充和取消做成连续流程。released code 中 AsyncLLMServerManagerDAPO.request_from_openhands_dapo() 会维护 job_queue、available_servers_queue、active_job_tasks,在完成 requested_batch_size 后取消 dispatcher 和活跃任务;filter_easy_hard_instance() 明确过滤 resolved_count == num_trajectories 或 resolved_count == 0 的 prompt;push_remaining_train_data_to_job_queue() 把剩余未完成训练数据重新放回队列。
3.5 训练与代码配置中的一致性/差异
论文公式与 released code 实现差异:论文实验设置写的是 batch size 32、mini-batch size 8、每 instance 8 条 rollout、KL coefficient 、learning rate 、32 NVIDIA H100 GPUs;released trainer_integration/verl/verl_custom/nvidia/scripts/run_proagent_qwn3_4B_instruct.sh 是 paper-era 4B 训练脚本,设置 BATCH_SIZE=4、NUM_TRAJ=4、KL_LOSS_COEF=0.001、actor_rollout_ref.actor.optim.lr=1e-6、trainer.n_gpus_per_node=8、NNODES=1,并注释“Assumes a h200 node / For 2xH100…”。README 的 4B current result 又写使用 32 A100 GPUs、293 个 SWE-GYM 训练样本、约 66 steps 从 14.8% 到 21.2%。因此笔记中把论文实验数字和 released launch-script 数字分开记录,不把脚本示例当成论文 Table 2 的完整复现实验配置。
Code reference:
training_code@7bdfbde(2026-03-20)
| Paper Concept | Source File | Key Class/Function |
|---|---|---|
| 任务插件抽象 | openhands/nvidia/registry.py | AgentHandler, register_agent_handler, JobDetails |
| SWE rollout handler | openhands/nvidia/swe_agent/swe_agent_handler.py | SweAgentHandler.init/run/eval/final_result |
| STEM/math/code handlers | openhands/nvidia/stem_agent/stem_handler.py, openhands/nvidia/math_coder/math_code_handler.py | STEMHandler, MathHandler, CodeHandler |
| FastAPI 管理面 | scripts/start_server.py | /start, /process, /cancel, /add_llm_server, /clear_llm_server, /status |
| Process-based rollout server | openhands/nvidia/async_server_process.py | OpenHandsServer, Worker, ConcurrencyControl, JobState |
| Phase timer | openhands/nvidia/timer.py | PausableTimer, phase_context, timeout helpers |
| Singularity runtime | openhands/runtime/impl/singularity/singularity_runtime.py, openhands/runtime/utils/singularity_runtime_build.py | runtime launch/cleanup, image build helpers |
| Efficient Bash | openhands/runtime/utils/efficient_bash.py | ptyprocess-backed shell backend |
| veRL rollout manager | trainer_integration/verl/verl_custom/nvidia/rollout/async_server.py | AsyncLLMServerManager, token-level conversion, backend assignment |
| Efficient DAPO | trainer_integration/verl/verl_custom/nvidia/rollout/async_server_dapo.py | AsyncLLMServerManagerDAPO, request_from_openhands_dapo, filter_easy_hard_instance |
| SWE reward manager | trainer_integration/verl/verl_custom/nvidia/reward_manager/swebench.py | reward conversion for SWE-Bench-style resolved signals |
| 4B launch script | trainer_integration/verl/verl_custom/nvidia/scripts/run_proagent_qwn3_4B_instruct.sh | GRPO/DAPO-style filtered rollout config, token-level generation flags |
4. Experimental Setup (实验设置)
4.1 任务和数据
论文覆盖四类 agent 训练场景:
- Software Engineering:主要实验。训练 Qwen3-4B-Instruct-2507、Qwen3-8B、Qwen3-14B,数据是 SkyRL-v0 使用的 SWE-Gym 293-instance subset;评测是 SWE-Bench Verified。Qwen3-8B 和 Qwen3-14B 在训练时启用 thinking mode。
- STEM Agent:面向 science/technology/engineering/math 的复杂问答;主工具是 web search,后端用 Tavily,同时使用 Bash 和 IPython;训练数据跟随 ProRL recipe,使用 SCP-116K。
- Math Agent:跟随 ProRL 使用 DeepScaleR 数据;主工具是 IPython execution,预装 NumPy、SciPy、SymPy,并加入
thinktool 做显式规划和自我验证;评测曲线用 AMC Pass@1。 - Code Agent:使用 Eurus-2-RL-Data 训练,评测 Codeforces testing split;主工具是
str_replace_editor修改/workspace/solution.py,并用 Bash/IPython 运行测试和调试;reward 用 hidden test cases 评估最终 solution。
4.2 训练配置
论文默认配置:DAPO 过滤过易/过难实例;batch size 32;mini-batch size 8;每个 instance 生成 8 条 rollouts;带错误的 rollouts 不进 gradient;KL coefficient ;learning rate ;所有 RL 训练使用 32 NVIDIA H100 GPUs。
released code 的 paper-era 4B launch script 记录了一个不同的可运行配置:BATCH_SIZE=4、NUM_TRAJ=4、MAX_NUM_ITERS=30、OPENHANDS_NUM_WORKERS=70、actor_rollout_ref.actor.optim.lr=1e-6、KL_LOSS_COEF=0.001、actor_rollout_ref.rollout.temperature=1.4、top_p=0.95、openhands_timeout=1000、token_level_generation=True、rollout.calculate_log_probs=True、reward_manager.type="swebench"、trainer.n_gpus_per_node=8、trainer.total_epochs=100。这说明公开代码提供了具体集成路径,但不完全等同于论文实验段落的统一默认超参。
4.3 Baselines 和 metrics
主要 baseline/对照包括:
- SWE-Bench Verified:Qwen3 base/instruct 模型、SkyRL-Agent-8B-v0、SkyRL-Agent-14B-v0。
- 系统对比图:SkyRL-Agent、Agent Lightning、VeRL-Tool、rLLM、GEM,用于说明 rollout 与 trainer 的耦合方式差异。
- 系统消融:Load Balancing、Efficient Bash、Stale Job Cleanup 三个组件逐个移除。
Metrics:SWE-Bench Verified 用 Pass@1 / resolved performance;STEM 用 mean reward;Math 用 AMC Pass@1;Code 用 Codeforces Pass@1;系统分析用 rollout throughput(instances/sec)、GPU utilization、shell command action time。
5. Experimental Results (实验结果)
5.1 SWE-Bench Verified 主结果
| Size | Model | Reproduced | Reported |
|---|---|---|---|
| 4B | Qwen3-4B-Instruct-2507 | 14.8 | — |
| 4B | ProRL Agent-4B (RL) | 21.2 | — |
| 8B | Qwen3-8B | 9.6 | — |
| 8B | SkyRL-Agent-8B-v0 | — | 9.4 |
| 8B | ProRL Agent-8B (RL) | 18.0 | — |
| 14B | Qwen3-14B | 15.4 | — |
| 14B | SkyRL-Agent-14B-v0 | — | 21.6 |
| 14B | ProRL Agent-14B (RL) | 23.6 | — |
核心结论:ProRL Agent 在三个模型尺寸上都提升 SWE-Bench Verified。4B 从 14.8 到 21.2;8B 从 9.6 到 18.0,相比 SkyRL-Agent-8B-v0 reported 9.4 接近 2 倍;14B 从 15.4 到 23.6,并超过 SkyRL-Agent-14B-v0 reported 21.6。论文把这些结果解读为:更稳定、解耦的 rollout infrastructure 能让 agent RL 训练更有效,而不仅是提高系统吞吐。
5.2 跨任务泛化:STEM / Math / Code
Figure 4a–4c 解读:三条曲线分别展示 STEM mean reward、Math AMC Pass@1、Codeforces Pass@1。STEM 从约 0.2 上升到约 0.65(60 steps);Math AMC Pass@1 从 0.4 上升到约 0.9;Codeforces Pass@1 从 0.23 上升到约 0.42。三者工具和 reward 都不同,说明 ProRL Agent 的贡献更偏“统一 rollout 基础设施”,而不是只为 SWE-Bench 写了一个特殊训练 loop。
5.3 系统吞吐与 scaling

Figure 5 解读:rollout throughput 随 compute nodes 增加近似线性增长。这个结果支持 RaaS 的系统假设:rollout 服务可以独立扩展,而不是被单个 trainer 进程限制。对 agent RL 来说,rollout generation 经常是主瓶颈;如果 rollout server 可以水平扩展,GPU trainer 更容易持续拿到有效轨迹。
5.4 组件消融
| Load Balancing | Efficient Bash | Stale Job Cleanup | Action Time (s) | GPU Util (%) | Throughput (instance/sec) |
|---|---|---|---|---|---|
| ✓ | ✓ | ✓ | 0.42 | 78 | 0.37 |
| ✓ | ✓ | 0.42 | 42 | 0.25 | |
| ✓ | ✓ | 0.78 | 68 | 0.29 | |
| ✓ | ✓ | 0.42 | 65 | 0.30 |
消融结论很直接:Load Balancing 和 Stale Job Cleanup 主要提升 GPU utilization;Efficient Bash 主要降低 action time(0.78 s → 0.42 s)。三者同时打开时吞吐最高,0.37 instances/sec。这个表也说明 ProRL Agent 的收益不是单点优化,而是 rollout-as-service 架构、工具后端和 DAPO stale cleanup 协同产生的。
5.5 Appendix 中的框架对比图

Appendix ProRL Agent 图解读:这张图从环境管理到 reward computation 展示完整 rollout lifecycle 被移出 GPU-intensive training。它补充 Figure 2:ProRL Agent 不是只把 inference endpoint 外置,而是把 container lifecycle、tool execution、trajectory collection、evaluation 也作为服务侧责任。

SkyRL-Agent 图解读:论文用它代表 trainer 内部直接管理 rollout 的框架。它支持 real-world long-horizon agent 训练,但 rollout orchestration 仍与训练框架绑定,迁移 trainer 或替换环境栈的成本较高。

Agent Lightning 图解读:Agent Lightning 更关注把 agent execution trace 转成训练样本和 credit assignment,但在论文的分类里,它仍没有像 ProRL Agent 一样把完整 rollout lifecycle 作为独立 HTTP 服务暴露给任意 trainer。

VeRL-Tool 图解读:VeRL-Tool 体现了 tool-use RL 与 veRL 训练栈结合的路径;ProRL Agent 与它的区别在于服务边界更硬:trainer 不拥有环境执行 loop,只通过 API 消费 rollout 结果。

rLLM 图解读:rLLM 图中 rollout 更像 monolithic training driver 的一部分;ProRL Agent 则把 job lifecycle、LLM backend、tool execution 和 reward scoring 全部放到 rollout server 侧。

GEM 图解读:GEM 提供 agentic LLM gym/environment 抽象;ProRL Agent 不否认环境抽象的重要性,但强调训练时的系统问题:rollout service 要能在 HPC 上独立扩容、独立管理 backend、独立处理取消和失败。
5.6 局限与未来方向
论文没有展开长篇 limitations,但 conclusion 明确把“richer environments”和“improved cluster-scale robustness”留给未来工作。结合方法和 released code,可以补充三类实际局限:
- 系统复杂度上移:trainer 简化了,但 rollout server 要负责容器、socket、任务取消、LLM backend、reward server、进程清理,部署复杂度不低。
- 代码与论文配置存在漂移:公开
training_code分支和 README 能复现核心系统设计,但实验配置数字与论文段落不完全一致;复现实验需要确认最终内部 launch config。 - 有效性依赖 task handler 和 reward:RaaS 只解决 rollout 基础设施;STEM/math/code 的提升仍依赖数据、工具、rubric/reward 设计和 agent prompt。