ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

部署UniRL奖励服务:FastAPI+Ray多奖励模型面板的架构与扩展完全指南

部署UniRL奖励服务:FastAPI+Ray多奖励模型面板的架构与扩展完全指南

【免费下载链接】UniRL

UniRL is a Framework for Unified Multimodal Model Reinforcement Learning

项目地址:https://gitcode.com/gh_mirrors/uni/UniRL
点击查看免费下载

UniRL 是一个统一多模态模型强化学习框架,其子项目unirl-reward-service是一个基于FastAPI + Ray的多奖励模型面板:一个 HTTP 网关背后挂着若干奖励模型(HPSv2、CLIP、PickScore、ImageReward、GenEval2 等),每个模型独占若干 GPU,一次POST /score调用即可并行拿到多个奖励分。本文带你完整走一遍:理解架构 → 单机 3 步部署 → 多机 Ray 集群扩容 → 训练任务对接 → 扩展新打分器,新手也能照着跑起来。

📌 什么是 UniRL 奖励服务?

在强化学习训练中,奖励模型负责把生成结果(图片/视频/文本)变成一个标量信号。UniRL 提供两种取用方式:

  • local(进程内):每个后端只挂一个打分器,简单直接;
  • remote(HTTP 面板):RewardService通过POST /score调用远端服务,请求里用required_rewards声明需要哪几个模型,服务端支持weighted_sum / mean / min / max加权聚合成一个奖励,并返回每个模型的明细。

把奖励模型集中到独立机器的面板服务上,好处很明显:训练机的 GPU 全部留给 rollout 和训练,奖励模型可以跨团队复用、独立扩缩容、独立升级依赖。

🏗️ 架构拆解:FastAPI 网关 + Ray WorkerGroup

整条调用链非常简洁:

Client ──HTTP──▶ FastAPI Gateway ──Ray actor call──▶ WorkerGroup(reward=X) ├── actor_0 (独占 GPU k) └── actor_1 (独占 GPU k+1)

每个奖励对应一个WorkerGroup,组内是若干可配置的 Ray actor(replicas);路由按required_rewards把请求分桶(bucket)后,对每个奖励只发起一次批量 Ray 调用,组内轮询(round-robin)选 actor。

组件源码位置职责
HTTP 网关server.py/score、/health、/rewards端点,分桶/派发/聚合
配置解析config.pyYAML → dataclass,校验num_replicas≥1、num_gpus≥0、名字唯一
WorkerPoolpool.pyRay 运行时生命周期 + 组注册 + 按名派发
WorkerGroupgroup.pyN 个 actor + 轮询调度
ScorerActoractor.py@ray.remote薄壳,在 GPU 进程内构建模型
BaseScorerbase.py抽象契约:score(items) -> list[dict[str, float]]
Registryregistry.pyregister(name, cls)打分器注册表

服务对外承诺三重隔离(详见 docs/ARCHITECTURE.md):

  1. 资源隔离:GPU 以整数资源分配(num_gpus=N),同一时刻一块卡只属于一个奖励组;
  2. 错误隔离:某个奖励 OOM/超时/崩溃,只把异常写进errors[i][reward],同批其他奖励照常返回,不会整批 500;
  3. 依赖隔离:每个打分器有独立的envs/<scorer>.txt,Rayruntime_env首次启动时自动建隔离 venv——imagereward 和 vLLM 可以使用不同版本的 transformers 互不冲突。

🚀 单机部署:3 步拉起奖励面板

第一步:准备基础环境并安装

前置条件:已激活Python 3.12环境,且预装了 torch + nccl(这些包太大、强绑定硬件,官方不代装)。然后在 unirl-reward-service/ 目录下执行:

./install.sh # 只装基础层:ray、fastapi、uvicorn、pillow 等

注意:各打分器的依赖(transformers、vllm、hpsv2……)不在这一步安装,它们由 Ray 在 actor 首次启动时按envs/*.txt自动装进隔离 venv,所以首次启动较慢(vLLM 类约 10 分钟),之后复用缓存几秒即可就绪。

第二步:编辑部署配置

cp configs/service.example.yaml configs/service.yaml

打开 configs/service.example.yaml,核心就改三处:

  • 打开/注释你需要的奖励条目(默认启用 8 个,共 10 张 GPU 预算);
  • 把每个条目的weights_path填成你本地的权重目录;
  • 按机器卡数调整num_gpus/num_replicas。总预算 = Σ(num_gpus × num_replicas),不要超过本机 GPU 数,超预算时ray.init()能成功,但 actor 会卡在资源分配上,可从/health看到某组 ping 不回来。

常用字段速查:max_concurrency(单 actor 并发度,纯前向的 transformers 打分器可放心调大,vLLM 打分器保持 1)、score_timeout_s(单奖励超时,默认 120s)、vLLM 类参数(tensor_parallel_size、gpu_memory_utilization、dtype等直接透传给vllm.LLM)。

第三步:启动并做健康检查

python -m reward_service --config configs/service.yaml # 等价入口:unirl-reward-service --config configs/service.yaml

启动后先打两个只读端点确认状态:

端点方法说明
/healthGET{"status": "ok", "rewards": {名字: [各副本状态]}}
/rewardsGET已就绪的奖励列表
/scorePOST批量打分主入口

📞 调用面板:批量打分与错误隔离

最推荐的调用方式是 Python SDK(client.py),请求列表想多长就多长:

from reward_service.client import RewardClient, RewardRequest client = RewardClient("http://localhost:8080") # 跨机时换成服务 IP scores = client.score([ RewardRequest(history=[("a cute dog", Image.open("dog.png"))], required_rewards=["hpsv2", "clip"]), RewardRequest(history=[("a cute cat", Image.open("cat.png"))], required_rewards=["hpsv2", "pickscore"]), ]) # -> [{"hpsv2": {"hpsv2": 0.27}, "clip": {"clip": 0.31}}, ...]

RLHF 里最常见的模式是"一个 prompt × N 张候选图":N 个请求放进一次client.score([...]),服务端会把需要同一奖励的 N 个请求打包成单次 actor 调用,效率远高于逐条请求。

响应中results[i]与errors[i]和requests[i]一一对应:

{ "results": [ {"hpsv2": {"hpsv2": 0.27}} ], "errors": [ {"clip": "RayActorError(...)"} ] }

即clip失败了,只在该请求里记录异常,hpsv2照常出分——客户端要同时读两个字段,否则失败的奖励会被静默漏掉。跨机调用只需把 URL 换成http://<server-ip>:8080,也可以不装 SDK 直接用裸requests手写 HTTP(参考 scripts/remote_client_zero_deps.py)。

🌍 多机扩容:加入 Ray 集群只需改一行 YAML

单机的 GPU 预算不够时,可以把多个主机组成 Ray 集群,服务代码零改动,参考 configs/service.cluster.example.yaml(2 节点 × 8 GPU = 16 卡预算):

export NODE_IP_LIST="10.0.0.1:8 10.0.0.2:8" # 第一个节点是 head bash scripts/ray_start.sh # pdsh 拉起多机 Ray 集群 python -m reward_service --config configs/service.cluster.example.yaml # 拆集群: bash scripts/ray_stop.sh

scripts/ray_start.sh 启动完成会打印 head 地址,把它填进 YAML 即可:

cluster: ray_address: <head-ip>:6379 # 服务本身跑在 head 节点上时用 auto

多机部署的两个调度要点(配置里都有注释):

  • scheduling: pack:vLLM 张量并行(如 GenEval2 的 TP=2)必须两卡落在同一节点,跨以太网做 TP 会毁掉吞吐;
  • scheduling: spread:轻量的 1 卡奖励开num_replicas: 2+ spread,让副本摊到不同节点,兼顾负载均衡与故障隔离。

另外注意 YAML 中奖励条目的书写顺序:多卡 actor 放前面,先占好连续卡槽,避免被 1 卡副本把集群切碎。

🔌 对接 UniRL 训练任务:一行环境变量搞定

远程面板最大的价值是让训练机的 GPU 全部留给 rollout 与训练。训练侧由 unirl/reward/remote.py 的RemoteRewardBackend负责对接,官方示例 recipe 见 examples/diffusion/sd3/sd3_nft_reward_service.yaml:

reward: _target_: unirl.reward.service.RewardService backend: _target_: unirl.reward.remote.RemoteRewardBackend config: _target_: unirl.reward.remote.RemoteRewardSpec base_url: ${oc.env:REWARD_SERVICE_URL,http://localhost:8080} required_rewards: [hpsv2, pickscore, unified_reward, imagereward, hpsv3] reward_weights: {hpsv2: 0.2, pickscore: 0.2, unified_reward: 0.2, imagereward: 0.2, hpsv3: 0.2} batch_size: 8 aggregation_method: weighted_sum # 支持 weighted_sum / mean / min / max

训练时只需:

REWARD_SERVICE_URL=http://10.0.0.5:8080 python -m unirl.train_diffusion \ --config-dir examples/diffusion/sd3 --config-name sd3_nft_reward_service

一次 HTTP 往返就把 5 个奖励模型的分全拿回来,按权重聚合成训练用的单一奖励。

🧩 扩展指南:4 步接入一个新打分器

框架的扩展点设计得很克制:网关、workers、schemas完全不用动,新打分器会被注册表自动发现。以新增my_reward为例:

  1. 在 reward_service/scorers/ 下新建my_scorer.py,实现BaseScorer的score(items) -> list[dict[str, float]],模块末尾register("my_reward", MyScorer);重依赖一律放在__init__/方法内,不要在模块顶层 import;
  2. 在 registry.py 的SCORER_MODULES映射里加一行"my_reward": "reward_service.scorers.my_scorer";
  3. 写一份envs/my_reward.txt声明该打分器的 pip 依赖(版本冲突的隔离层);
  4. 在configs/service.example.yaml加对应条目(name/scorer/runtime_env/num_gpus/num_replicas/params),并补一个测试。

已有子指标想扩新指标?只需扩sub_metric_names元组并在score()返回的 dict 里加键——响应 schema 是 `dict[str, float],天然多键,协议不变。

⚠️一个硬边界:Rayruntime_env的隔离只到Python 包级,不能换 Python 解释器或 torch 底座(worker 必须与集群同 Python 小版本)。像geneval(mmdet/mmcv 栈需要 Python 3.10)这种跑不进本集群(py3.12+)的打分器,官方给了旁路方案:在独立 Python 3.10 主机上用 configs/geneval_service.yaml 单独起一份服务再互调。

⚡ 进阶技巧:MPS 共享 GPU 与压测

  • CUDA MPS 省卡:CLIP、PickScore、HPSv2、ImageReward 这类轻量打分器可以各占半张卡(num_gpus: 0.5+mps:块),服务会在 Ray 初始化前自动拉起本地 MPS 守护进程,示例见 configs/service.mps.example.yaml:
- name: clip num_gpus: 0.5 mps: active_thread_percentage: 100 # H20 + 535 驱动下 fp16/bf16 打分器保持 100 device_memory_limit: 20GiB
  • 并发压测:服务起来后用 scripts/bench_concurrent.py 输出 p50/p90/p99 延迟与吞吐;--sweep一次跑多个并发档,--per-reward-isolated单独测每个奖励的独立延迟(混批时 HTTP 只等最慢的那个返回):
python3 scripts/bench_concurrent.py --url http://10.0.0.5:8080 \ --concurrency 200 --total 1000 --rewards clip
  • 依赖自检:python scripts/check_venvs.py --standalone可核查各打分器 venv 的安装与隔离状态。

📚 关键文件速查表

资料相对路径
服务总 README(安装/启动/API 全表)unirl-reward-service/README.md
架构与数据流设计文档unirl-reward-service/docs/ARCHITECTURE.md
单机示例配置(8 奖励 10 卡)unirl-reward-service/configs/service.example.yaml
多机集群示例配置(2×8 卡)unirl-reward-service/configs/service.cluster.example.yaml
MPS 共享 GPU 示例配置unirl-reward-service/configs/service.mps.example.yaml
打分器注册表unirl-reward-service/reward_service/scorers/registry.py
Python SDK(RewardClient)unirl-reward-service/reward_service/client.py
训练侧远程后端unirl/reward/remote.py
远程奖励训练 recipe 示例examples/diffusion/sd3/sd3_nft_reward_service.yaml
多机 Ray 启停脚本unirl-reward-service/scripts/ray_start.sh

✅ 小结

UniRL 奖励服务的精髓是"一个 FastAPI 网关 + N 个独占 GPU 的 Ray 奖励组 + 三重隔离":单机三步即可上线一个多奖励面板,多机扩容只靠一行ray_address配置;训练侧一个REWARD_SERVICE_URL环境变量就能接入。想加新打分器,只需写一个模块 + 一行注册 + 一份 env 清单,网关与调度层自动生效。建议按本文顺序先跑通service.example.yaml,再逐步尝试 MPS 省卡与多机集群,最后扩展自己的奖励模型。

【免费下载链接】UniRL

UniRL is a Framework for Unified Multimodal Model Reinforcement Learning

项目地址:https://gitcode.com/gh_mirrors/uni/UniRL
点击查看免费下载
上一篇:Mirah类型系统深度解析:静态类型与类型推断的巧妙设计
下一篇:AWS SDK for Java 2.x 操作 Amazon CloudFront 实战指南:分发、签名 URL/Cookie 与多租户场景

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进