【免费下载链接】UniRL
UniRL is a Framework for Unified Multimodal Model Reinforcement Learning
UniRL(A Framework for Unified Multimodal Model Reinforcement Learning)是一个面向统一多模态模型的强化学习训练框架。本文用最直白的方式,带你一图一文搞懂 UniRL 的训练循环:每一步在做什么、由哪个模块负责、新手如何跑通第一个 RL 训练任务。无需读懂全部源码,10 分钟即可建立完整的架构认知。
🎯 一句话总结:rollout 生成样本 → reward 打分 → advantage 算优势 → train 更新策略 → sync 回灌权重,如此循环往复,模型逐轮变强。
UniRL 是什么:先看整体架构
UniRL 的核心理念是"一个 RL 后训练循环,跑遍所有多模态模型家族"——无论是 LLM(Qwen3)、VLM(Qwen-VL)、图像扩散(SD3.5、FLUX.2-Klein)、视频扩散(WAN 2.1/2.2、HunyuanVideo),还是统一 AR+Diffusion 模型(BAGEL、HunyuanImage3),用的都是同一套闭环。
从架构上看,系统自顶向下分为四层:
- 入口层(Entrypoints):
train_diffusion、train_ar、train_pe等,负责加载 Hydra 配方并创建 Trainer; - 编排层(Trainers):
DiffusionTrainer/ARTrainer等"总指挥",决定每个组件放在哪块 GPU 上,并驱动整个训练循环; - 组件层:rollout 引擎、reward 服务、算法(GRPO/FlowDPPO/DRPO…)、训练栈;
- 基础设施层:Ray DevicePool、FSDP、张量传输与权重同步。
下面这张图展示的是"入口如何把一份 YAML 配方变成一支 Worker 舰队",可以帮你快速定位每个组件的代码归属:
组件与源码位置的对应关系一目了然:
| 组件 | 职责 | 代码位置 |
|---|---|---|
| Trainer | 编排五步闭环 | unirl/trainer/ |
| Rollout 引擎 | 生成样本 | unirl/rollout/ |
| Reward 服务 | 打分数奖励 | unirl/reward/ |
| 算法库 | 定义损失函数 | unirl/algorithms/ |
| 训练栈 | 优化器 + FSDP | unirl/train/ |
| 权重同步 | 回灌新权重 | unirl/distributed/weight_sync/ |
一图看懂:UniRL 训练循环每一步的数据流动
在进入分步详解之前,先看全景。下面这张图是一次 train_step 内所有数据的完整流动,强烈建议放大对照阅读:
整个循环的骨架其实非常短。以参考实现 DiffusionTrainer 为例,循环主体(train_step)本质上就是这几行顺序调用:
rollout.generate(sample) # ① 生成 reward.score_and_attach(sample) # ② 打分 part.compute_advantages(...) # ③ 算优势 stack.train_track(...) # ④ 训练 weight_sync.sync() # ⑤ 回灌权重官方模块文档 对"运行时数据流"的官方描述同样是这五步。下面我们逐步拆解。
第一步 Rollout:模型"做题"生成候选样本
Rollout(采样/生成)是整个循环的起点:把一批 prompt 喂给当前策略模型,让它"答一题",产出图像/文本/视频等候选样本。
这一步在 UniRL 中分四个小动作:
- 取数据:数据源(
data_source)从数据集里取一批 prompt(batch_size个); - 构建请求 Sample:Trainer 把每个 prompt 按
samples_per_prompt派生(fork)出 N 个"分支",并携带采样参数(步数、guidance scale、σ 调度、初始噪声配方等)。同一 prompt 的一组 N 个样本就是后面算优势用的"GRPO 组"; - 引擎生成:rollout 引擎执行
text_embed → diffuse → vae_decode之类的流水线,把空壳 Sample 填上真实的生成结果; - 回传:填好的 Sample 回到驱动端,等待打分。
一个容易被忽略的细节:σ 校验边界
注意图中红框"THE σ-VERIFICATION BOUNDARY":UniRL 会把采样时使用的 σ(噪声)调度钉死在生成的 Sample 上,并要求每个引擎回传校验。为什么这么较真?因为强化学习训练时要重放 rollout 重新计算对数概率,如果引擎和训练端走的轨迹哪怕有微小数值漂移,GRPO 的重要性比率就会悄悄偏离 1,训练目标就错了。所以"不一致就大声报错"比"悄悄训错"好得多。
三种部署模式,同一个 generate 接口
rollout 引擎由配方中的_target_选择,常见有:
- trainside:训练进程内直接采样,采样权重就是训练权重本身(无需 sync);
- SGLang / SGLang-Diffusion:独立推理服务器,生成速度更快;
- vLLM-Omni:面向 BAGEL、HunyuanImage3、SD3.5 等多模态/过程模型的专用后端;
- composed:串联"AR 改写 + 扩散生成"的编排器(Prompt-Enhancer 场景)。
无论选哪种,接口都是同一个BaseRolloutEngine.generate(sample)(见 unirl/rollout/engine/base.py),对上层 Trainer 完全透明——这就是"可插拔"的含义。
第二步 Reward:把生成结果变成每样本分数
样本生成了,怎么知道好坏?Reward(奖励)服务负责把"图/文/视频"变成一个标量分数,这是 RL 的信号来源。
如上图,RewardService.score_and_attach只走一条后端,两种可选:
- local(进程内):一个后端一个打分器,比如 PickScore、CLIP、HPSv2、OCR、GenEval2、ImageReward、VideoAlign 等,全部实现在 unirl/reward/local/;
- remote(HTTP 服务):调一个远程 reward 面板,多个奖励模型加权聚合(weighted_sum / mean / min / max),返回一个总奖励 + 每个模型的明细。配套服务在仓库的 unirl-reward-service/ 目录。
打分结果直接挂回 Sample 上(part.rewards,外加可选的component_rewards分项明细)。图中底部那句总结值得记住:reward 是信号,advantage 才是真正用来训练的。
第三步 Advantage:GRPO 组内 z-score 把分数变成"优势"
有了每样本分数,还不能直接训练——分数有偏置(这个任务本来就难,大家分都低)。GRPO 的做法是组内相对评价:同一个 prompt 生成的 N 个样本为一组,各自减去组均值、除以组标准差:
advantage = (reward - group_mean) / (group_std + eps)也就是说:比同组兄弟平均好 → 正优势,鼓励这个方向;比兄弟差 → 负优势,抑制。这一步由 Part.compute_advantages 完成,支持两种作用域:
scope="group"(默认):每个 prompt 组内归一化,是标准 GRPO 语义;scope="global":整批归一化,可通过adv_use_global_std微调。
💡 这也解释了为什么第二步的 fork 必须"同组样本连续排列"——组内 z-score 依赖样本的父子血缘关系(sample id 里就编码了 prompt 谱系)。
优势算完,交给哪个损失函数?
UniRL 算法层的设计很巧妙:算法 = 一个损失函数 + 几个声明式"旋钮"。四个损失家族可在同一训练栈上互换:
| 算法 | 核心思想 | 源码 |
|---|---|---|
| GRPO | PPO 裁剪比率 + 组优势 | unirl/algorithms/grpo.py |
| Flow-DPPO | 面向 Flow Matching 的散度信任域掩码 | unirl/algorithms/flowdppo.py |
| DRPO | token 级平滑二次正则 | unirl/algorithms/drpo.py |
| DiffusionNFT | 双适配器重构 MSE(无需重放) | unirl/algorithms/diffusionnft.py |
图中上半部分还标出了训练时的关键输入:重放(replay)用的冻结锚点 π_old + 每样本优势,输出就是标准的策略梯度损失 → 反向传播 →optimizer.step()。这些"旋钮"(如requires_ema_rollout、supports_multi_update)由训练栈在启动时读取,决定循环如何被重配——新手只需知道:换算法基本等于换配方里的一个_target_。
第四步 Train:FSDP 分片下的 mini-batch 更新
拿到带优势的 Sample,进入唯一的"动权重"环节:训练栈TrainStack把生成的 Part 通过DP_SCATTER切分到各训练 worker,每个 worker 持有同一模型的 FSDP 分片、一个优化器和一个损失算法。
这步的三个关键点:
- 重放对齐:
stack.train_track(...)会先prepare_segment(冻结 π_old 锚点)再前向,保证重放轨迹与 rollout 时逐位一致——呼应第一步说的 σ 校验; - mini-batch 多次更新:
stack.num_updates_per_batch = N会把本批 rollout 切成 N 个不相交的 mini-batch 各跑一次 optimizer step(不是跑 N 个 epoch),用于提高样本效率; - 跨 rollout 累积:
accumulate_rollouts可让 M 次 rollout 只走一次 optimizer step(面向多域任务循环的 MOPD 场景)。
训练完成后,wandb_logger.log_rollout_step会记录损失、奖励均值、步耗时等指标,maybe_save_checkpoint按save_interval落盘检查点(支持断点续训)。
第五步 Sync:把新权重推回 rollout 引擎
如果 rollout 用的是独立推理引擎,训练完的"新大脑"必须装回引擎,否则下一轮还是在用旧权重生成。这一步就是虚线"回边"(见架构总览图),也是分布式上最精细的一步。
上图浓缩了三个必知事实:
- gather + push 两阶段:FSDP 下读取完整权重是全体 rank 参与的集合通信(all-gather),所以
sync()在每个训练 rank 上都会执行(BROADCAST 分发);实际"搬运"只有 rank 0 负责,其余 rank 聚合后丢弃。这也是为什么"只在 rank 0 上 sync"会死锁; - 五种 handler 按 [推什么 × 推去哪] 组合:LoRA 只推小适配器(
LocalLoraWeightSync/RemoteLoraWeightSync),Full 推完整权重(NCCLWeightSync跨节点、TensorWeightSync/IPCWeightSync同卡进程内); - trainside 模式没有这一步:它本来就在训练进程内采样,第 0 次更新时比率恒为 1,权重天然新鲜。
同步频率由weight_sync_interval控制(每隔几个 rollout 推一次;async 模式下则结合版本滞后预算自动发布)。
部署模式与分布式拓扑:三种 GPU 摆法
五步闭环的"第⑤步存在与否"取决于 GPU 布局。UniRL 提供三种拓扑,一图看懂:
| 模式 | GPU 布局 | 需要 sync? | 典型场景 |
|---|---|---|---|
| trainside(direct) | rollout 与 train 同一进程 | ❌ 不需要 | 上手最省事 |
| colocate | 独立引擎与训练共享 GPU,靠 offload/onload 腾挪显存 | ✅ 需要 | 单卡显存紧张时 |
| separate | rollout 与 train 各占一块独立 GPU slab | ✅ 需要(跨 slab NCCL 握手) | 吞吐最优 |
由顶层layout(colocate/separate)+train_fraction/reward_fraction(GPU 切分比例)控制。还有一个新手少踩的坑:重张量永远 worker→worker 直传,绝不经由 driver,所以跨节点跑时记得把save_dir、模型路径放在所有节点都可见的存储上。
新手速查:训练循环关键配置项
结合 examples/README.md 的启动指南,以下是循环层面最常用的旋钮(配方示例见 examples/diffusion/sd3/sd3_trainside.yaml):
| 配置项 | 含义 | 对应循环步骤 |
|---|---|---|
batch_size | 每轮 prompt 数 | rollout |
sampling.samples_per_prompt | 每个 prompt 的组大小(GRPO 组) | rollout / advantage |
num_rollouts | 总训练轮数(总预算) | 全局 |
reward:/reward_stack: | 打分器或远程奖励面板 | reward |
eval_interval | 周期性评测(含生成图预览上传) | 循环外 |
stack.num_updates_per_batch | 每批 rollout 的 optimizer step 数 | train |
weight_sync_interval | 每几个 rollout 向引擎推一次权重 | sync |
save_interval/save_dir | 检查点节奏与目录 | train |
train_resident/rollout_resident | colocate 下各角色权重是否常驻显存 | 部署 |
单节点最简启动方式:
# 先干跑校验配方,再真正启动 python -m unirl.train_diffusion --config-name=diffusion/sd3/sd3_trainside --cfg job --resolve bash examples/run_experiment_single_node.sh diffusion/sd3/sd3_trainside多节点启动与更多入口(AR、agentic、async 等)详见 examples/README.md 与 examples/run_experiment_multinode.sh。
写在最后:回到那 10 行循环
学完这五个环节,回头看 unirl/trainer/ 的总结会发现一切豁然开朗:每个模块都故意"看不见"其他模块(rollout 不知道 reward 是谁、算法不知道引擎是谁),唯一知道全局、负责把五步串起来并决定"谁跑在哪块卡上"的就是 Trainer——这正是循环主体能保持约 10 行、而每个组件都能靠_target_自由替换的原因。
记住这张心智地图,UniRL 的任一新特性(async 训练、agentic 多轮、PE 双栈、检查点续训)都只是在这五步闭环上做重排与解耦,不会再让你迷路 🚀
| 步骤 | 一句话 | 关键入口 |
|---|---|---|
| ① rollout | 模型做题,产出 Sample | rollout.generate |
| ② reward | 把输出变成每样本分数 | reward.score_and_attach |
| ③ advantage | 组内 z-score 出优势 | part.compute_advantages |
| ④ train | FSDP mini-batch 更新 | stack.train_track |
| ⑤ sync | 新权重推回引擎 | weight_sync.sync |
【免费下载链接】UniRL
UniRL is a Framework for Unified Multimodal Model Reinforcement Learning
相关推荐
革命性AI工具PretzelAI:为什么它正在取代Jupyter Notebook
革命性AI工具PretzelAI:为什么它正在取代Jupyter Notebook PretzelAI作为Jupyter Notebook的现代替代品,正在数据
彻底搞懂p5.js核心循环:从静态绘图到流畅动画的钥匙
彻底搞懂p5.js核心循环:从静态绘图到流畅动画的钥匙 你是否曾疑惑为什么有些代码能画出会动的图形,而有些只能显示静止画面?为什么同样的绘图代码放在不同位置,效
前端图形学3D渲染Medium Editor Markdown:终极指南 - 如何为富文本编辑器添加Markdown支持
Medium Editor Markdown:终极指南 如何为富文本编辑器添加Markdown支持 Medium Editor Markdown 是一个功能强大
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考