Skip to content

verl 整体架构:HybridFlow、single-controller、RayWorkerGroup 和 DataProto 边界

verl 是面向 LLM post-training 的 RL 训练框架。它最值得学习的地方,不只是 PPO/GRPO 的公式,而是怎样把复杂 RL 控制流、分布式训练 worker、高吞吐 rollout engine、reward 计算、配置系统和 checkpoint/weight sync 放进同一条可扩展的数据流里。

先验知识

读架构前,需要先建立几个概念:

  1. Ray 可以把 Python class 变成远端 actor。driver 调用 actor.method.remote(),真正执行发生在远端进程。
  2. LLM 训练 worker 往往不是一个普通 Python 对象,而是一组 GPU 进程,内部还有 data parallel、tensor parallel、pipeline parallel。
  3. RL post-training 的控制流比 SFT 更复杂。一次 step 包含生成、打分、logprob、value、advantage、更新和权重同步。
  4. verl 选择让一个 driver 写“算法控制流”,把重计算派发给远端 worker。这就是 single-controller 的直觉。
  5. DataProto 是 driver 和 worker 之间的主要数据边界。worker 内部可转成 TensorDict 或更底层结构。

本页原先不适合小白的地方

原说明给了架构大图,但还缺这些小白入口:

  • 没有解释“不懂 Ray 也能读”的最低模型。初学者需要先知道 worker group 是“远端多 worker 的本地代理”。
  • HybridFlow 容易被误解成某个具体类。它更像一种设计思想:单进程表达 RL 控制流,多进程执行模型计算。
  • @register、dispatch、collect、RayWorkerGroup 的链条需要连到 trainer 的 actor_rollout_wg.compute_log_prob(batch)
  • DataProto 边界需要明确:driver 用 DataProto 组织样本,worker 内部常用 TensorDict 和 engine-specific batch。

一句话架构

一个 Python driver 负责写训练逻辑;single_controller 把 driver 上的 wg.method(batch) 转成多 Ray worker 的远端调用;actor/critic/ref/reward/rollout 角色由 worker group 承载;DataProto 负责跨模块传 batch;checkpoint engine 负责把训练后的 actor 权重同步给 rollout server。

text
Hydra config
  -> main_ppo.py TaskRunner
  -> RayPPOTrainer
  -> RayWorkerGroup / RewardLoopManager / LLMServerManager
  -> DataProto flows through rollout, reward, logprob, value, update
  -> CheckpointEngineManager syncs actor weights to rollout

HybridFlow 的直觉

docs/hybrid_flow.rst 把 RL 训练拆成两层 flow:

  • control flow:先 rollout,再 reward,再 advantage,再 update。这是 RL 算法逻辑。
  • computation flow:每个阶段内部的模型 forward/backward/optimizer step。这通常是多 GPU 程序。

verl 的选择是:control flow 留在一个 driver 进程里表达,computation flow 交给分布式 worker 执行。

这带来的好处:

  • 新算法可以主要改 trainer loop,不必重写 FSDP/Megatron/vLLM/SGLang 的内部。
  • 同一套 trainer 控制流可以复用不同模型训练后端。
  • 调试时可以在 driver 上看 batch 字段如何一步步长出来。

代价:

  • driver 和 worker 之间要搬数据。
  • 每个 worker 方法都要声明 dispatch/collect 规则。
  • DataProto 字段必须严格对齐,否则跨进程问题很难查。

不懂 Ray 时怎么理解 RayWorkerGroup

你可以先把 RayWorkerGroup 理解成:

它是 driver 本地的代理对象,背后管理 N 个远端 Ray actor。你调用一次 wg.method(batch),它负责把 batch 切成 N 份,发给 N 个 worker,等结果回来再拼成一个 batch。

真实链路是:

text
trainer 调用 actor_rollout_wg.compute_log_prob(batch_td)
  -> RayWorkerGroup 上动态绑定的 compute_log_prob
  -> dispatch_fn 切分参数
  -> execute_fn 调用每个 Ray actor 的远端方法
  -> collect_fn 拼接返回结果
  -> trainer 得到 TensorDict/DataProto 结果

@register 做了什么

worker 方法上会有装饰器:

python
@register(dispatch_mode=make_nd_compute_dataproto_dispatch_fn(mesh_name="actor"))
def compute_log_prob(self, data: TensorDict) -> TensorDict:
    ...

@register 本身不执行远端调用。它只是给方法挂上元数据:

python
{
    "dispatch_mode": ...,
    "execute_mode": ...,
    "blocking": ...
}

WorkerGroup._bind_worker_method() 扫描 worker class,发现带 MAGIC_ATTR 的方法,就把它绑定成 worker group 上的同名方法。真正执行远端调用的是 RayWorkerGroup.func_generator() 生成的函数。

dispatch/execute/collect 三步

Dispatch.DP_COMPUTE_PROTO 为例:

  1. dispatch:DataProto.chunk(world_size),把全局 batch 切给每个 worker。
  2. execute:RayWorkerGroup.execute_all(),对每个 Ray actor 调用远端方法。
  3. collect:DataProto.concat(output),把 worker 输出拼回全局 batch。

新源码里很多训练方法用的是:

python
make_nd_compute_dataproto_dispatch_fn(mesh_name="actor")
make_nd_compute_dataproto_dispatch_fn(mesh_name="ref")
make_nd_compute_dataproto_dispatch_fn(mesh_name="train")

这比简单按 world_size 切更细:worker 先注册自己在某个 mesh 下的 data parallel rank,dispatch 时按 mesh 的 DP rank mapping 切数据,collect 时只从应该产出结果的 rank 收集。

worker role

RayPPOTrainer 里常见角色:

role职责
ActorRollout / ActorRolloutRefactor 训练、rollout 权重源、可选 reference
Criticvalue model 训练和推理
RefPolicy冻结 reference policy,某些配置下单独存在
RewardModel模型 reward 的资源池或 worker
TeacherModeldistillation 路径的 teacher

当前统一模型引擎里:

  • ActorRolloutRefWorker 可以组合 actor、rollout、ref、checkpoint engine。
  • TrainingWorker 承载 critic 和 reward model 一类训练/推理 worker。
  • RewardLoopManager 负责 reward 计算流程,可能使用规则函数、模型或远端/沙盒逻辑。
  • LLMServerManagerAgentLoopManager 负责 async rollout 生成。

colocated hybrid worker

init_workers() 里会把同一 resource pool 上的多个 role 通过 create_colocated_worker_cls(class_dict=...) 组合,再 wg_dict.spawn(prefix_set=...) 拆出不同 role 的 worker group 代理。

直觉上:

text
同一批 Ray actor 进程里可以暴露 actor、rollout、ref 等多种方法
driver 仍然拿到 actor_rollout_wg / ref_policy_wg 等不同代理

这减少了重复 CUDA/distributed context,也方便 actor 到 rollout 的快速权重同步。但它也带来显存生命周期复杂度,所以你会在 trainer 里看到:

python
self.checkpoint_manager.sleep_replicas()
self.checkpoint_manager.update_weights(global_steps)

这些动作本质上是在训练阶段和 rollout 阶段之间协调显存和权重。

DataProto 边界

verl 的数据边界可以这样看:

text
driver / trainer:
  DataProto

worker group dispatch:
  DataProto 或 TensorDict 被 chunk 到 worker

worker 内部:
  TensorDict
  no-padding/padding 转换
  engine-specific batch

worker 返回:
  TensorDict 或 DataProto
  collect 后回到 driver

ray_trainer.py_compute_old_log_prob() 是最好的例子:

python
batch_td = batch.to_tensordict()
batch_td = left_right_2_no_padding(batch_td)
output = self.actor_rollout_wg.compute_log_prob(batch_td)
log_probs = no_padding_2_padding(tu.get(output, "log_probs"), batch_td)
old_log_prob = DataProto.from_tensordict({"old_log_probs": log_probs})

所以不要把 DataProto 当成模型内部格式。它更像跨模块协议。

和普通 PyTorch 训练的区别

普通 SFT:

text
dataloader -> model forward -> loss -> backward -> optimizer step

verl PPO/GRPO 主路径:

text
dataloader
  -> rollout generate
  -> reward
  -> old/ref logprob
  -> value
  -> advantage
  -> critic update
  -> actor update
  -> rollout weight sync

多出来的每一步都可能跨进程、跨 GPU、跨推理服务。verl 的架构就是为这些边界服务的。

源码实现怎么读

推荐路线:

  1. 先读 docs/hybrid_flow.rst,理解 control flow 和 computation flow 为什么拆开。
  2. docs/single_controller.rst,用 generate_sequences 的例子理解 @register -> bind -> dispatch -> execute -> collect
  3. verl/single_controller/base/decorator.py,看 Dispatchregister()make_nd_compute_dataproto_dispatch_fn()
  4. verl/single_controller/base/worker_group.py,看 _bind_worker_method()
  5. verl/single_controller/ray/base.py,看 RayWorkerGroup._init_with_resource_pool()spawn()execute_all_async()func_generator()
  6. verl/workers/engine_workers.py,看 worker 如何注册 actorreftrain mesh,并暴露 compute_log_prob()update_actor()train_mini_batch()
  7. 回到 verl/trainer/ppo/ray_trainer.py,把 init_workers()fit() 串起来。

本节参考与延伸阅读

  • 源码:verl/trainer/ppo/ray_trainer.py,重点读 RayPPOTrainer.init_workers()fit()
  • 源码:verl/single_controller/base/decorator.pyworker_group.pyworker.py
  • 源码:verl/single_controller/ray/base.py
  • 源码:verl/workers/engine_workers.pyverl/protocol.py
  • 官方 docs:docs/hybrid_flow.rstdocs/single_controller.rstdocs/examples/ppo_code_architecture.rstdocs/workers/ray_trainer.rst
  • 论文/网页:HybridFlow: A Flexible and Efficient RLHF Framework, arXiv:2409.19256;Ray 官方文档中 actor 和 placement group 相关章节。

面向源码阅读的 verl 学习文档。