外观
框架与工具怎么选
一句话定位:这一页帮你决定"我的 RL 代码到底该用哪个框架搭"——Stable-Baselines3、Ray RLlib、Tianshou、CleanRL、Brax 与自研六个选项的横向对比,以及一条被反复验证的工程建议:"先抄 CleanRL 再改"。读完你会有明确的选型结论,而不是在六个 README 之间来回横跳。
先泼一盆冷水:框架选型远没有你想的重要。 RL 项目的成败在环境、奖励、评估与超参,不在库的选择(RL 设计原则里"简单优先"那条说的就是这个)。真正决定你效率的,是对原理的理解——而理解的最佳载体是干净可读的参考实现。所以本页的结论会偏向"教学用 CleanRL、产品用 SB3、大规模用 RLlib/Brax",而不是给你一个"唯一正确"的答案。
一、选择框架的三个维度
任何框架都在这三个维度上权衡,先想清楚你要什么:
维度一 成熟度 (算法实现全不全、坑修没修、社区大不大)
维度二 灵活度 (能不能改内部逻辑、能不能上自定义算法)
维度三 并行度 (单机单卡 → 多机分布式,能撑多大规模)
选型 = 在这三者的帕累托前沿上找你的位置| 维度 | 提问 | 影响 |
|---|---|---|
| 成熟度 | 有多少生产案例?issue 库活跃吗?算法实现被社区验证过吗? | 越成熟越省心,但越"黑盒" |
| 灵活度 | 我要不要改网络结构、自定义损失、发明新算法? | 越灵活越费心,但越可塑 |
| 并行度 | 单进程跑不动吗?要多少环境并行?要上 GPU/多机吗? | 越并行越复杂 |
绝大多数个人项目和中小业务项目,并行度需求是"1 块 GPU 够用"——这就把选项砍掉了大半,剩下的其实只有"SB3 还是 CleanRL"。
二、六个选项逐个介绍
1. Stable-Baselines3(SB3)——生产级开箱即用
- 一句话:PyTorch 生态里最成熟、文档最全、最"傻瓜式"的 RL 库。
- 特点:统一 API(
PPO(env).learn()),内置算法多(A2C/DQN/PPO/SAC/TD3/DDPG),文档与教程质量高,与 Gymnasium 无缝。 - 适合:业务方需要一个"能跑就行"的可靠算法;教学对照;作为你所有项目的默认起点。
- 代价:灵活性差——改网络、改 loss 都要 hack 内部;并行靠
SubprocVecEnv,规模有限。
python
from stable_baselines3 import PPO
from stable_baselines3.common.env_util import make_vec_env
env = make_vec_env("CartPole-v1", n_envs=4)
model = PPO("MlpPolicy", env, learning_rate=3e-4, seed=0, verbose=1)
model.learn(total_timesteps=50_000)
model.save("ppo_cartpole") # 存模型2. Ray RLlib——分布式老兵
- 一句话:为"大规模分布式训练"而生的工业级框架。
- 特点:基于 Ray 的分布式调度,支持多机多 GPU、超大规模环境并行、
Tune自动调参;算法覆盖广。 - 适合:大规模环境采样、多机集群、需要和 Ray 生态(
Tune、Serve、Datasets)协同的团队。 - 代价:学习曲线陡峭、抽象层级深、小项目杀鸡用牛刀;API 演变快,网上教程容易过时。
3. Tianshou——学术灵活的"瑞士军刀"
- 一句话:华人社区主导的 PyTorch 高阶 RL 库,灵活度接近手写。
- 特点:模块化设计(策略/收集器/环境/训练器可自由组合),对新手科研极为友好,内置算法全(包括较新的 QRDQN、IQN 等)。
- 适合:做研究、跑消融、快速验证新想法的学术界/实验室场景。
- 代价:灵活 = 需要理解它的抽象,文档与社区规模不如 SB3。
4. CleanRL——可读性第一的教学与工程蓝本
- 一句话:把 PPO 的完整实现压缩进一个文件、一份文档里,代码即论文。
- 特点:单一 Python 文件、零抽象、
argparse控制超参;每个算法配一份与论文对齐的解释文档;完整覆盖 PPO/SAC/DQN/DDPG 等。 - 适合:学习原理(强烈推荐)、需要"可读、可改、可审计"的自研起点、复现论文。
- 代价:它是"参考实现",不是"通用库"——没有分布式、没有调度、需要自己接日志。
python
# CleanRL 的 PPO:单文件、注释详尽(节选骨架)
# python cleanrl/ppo.py --env-id CartPole-v1 --total-timesteps 50000
class Agent(nn.Module):
def __init__(self, envs):
super().__init__()
self.critic = nn.Sequential(...)
self.actor = nn.Sequential(...)
def get_value(self, x): ...
def get_action_and_value(self, x, action=None): ...
# 主循环:rollout → GAE → 更新 → 日志,全部在一个文件里平铺5. Brax / PureJaxRL——GPU 并行与 JAX 生态
- 一句话:把环境写在可微分的 JAX 里,整批环境直接跑在 GPU/TPU 上,采样速度碾压 CPU。
- 特点:Brax 提供 JAX 版物理环境(Ant、Humanoid、HalfCheetah 等);PureJaxRL 在其上提供纯 JAX 的 PPO/SAC 极简实现,几千行内跑完大规模并行训练。
- 适合:样本预算紧、追求极致吞吐、想要"环境+算法全在 GPU"的研究/前沿工程;以及真正想把训练规模化的人。
- 代价:JAX 学习曲线陡、调试体验差(报错信息抽象)、生态仍在高速演变。
6. 自研——最后的手段,也是最好的老师
- 一句话:完全自己写训练循环、算法、回放、日志。
- 适合:学习(每个 RL 从业者该亲手写一遍,见渐进式教程);或现有框架确实满足不了的需求(自定义环境协议、特殊并行、可微分环境)。
- 代价:所有坑都自己踩一遍——bug 排查成本极高,且你很可能在重新实现一个没人维护的 SB3。
"自研"的正确打开方式
自研 ≠ 从零造轮子。先抄 CleanRL,再改:以可读参考实现为骨架,逐步替换成你需要的部分。全程自己写代码 = 把两三个月花在别人已经解决的工程细节上。这条建议是本页最重要的产出,详见第五节。
三、六个选项横向对比表
| 维度 | SB3 | RLlib | Tianshou | CleanRL | Brax/PureJaxRL | 自研 |
|---|---|---|---|---|---|---|
| 语言/后端 | PyTorch | PyTorch/TF | PyTorch | PyTorch | JAX | 任意 |
| 算法覆盖 | 广且稳 | 最广 | 广且新 | 主流算法 | PPO/SAC 为主 | 由你 |
| 开箱即用 | ★★★★★ | ★★★★ | ★★★★ | ★★★ | ★★★ | ★ |
| 灵活性 | ★★ | ★★★ | ★★★★★ | ★★★★ | ★★★★ | ★★★★★ |
| 分布式/并行 | 单机向量 | 多机大规模 | 单机 | 单机 | GPU 大规模 | 由你 |
| 学习成本 | 低 | 高 | 中 | 低 | 高 | 最高 |
| 文档/社区 | 最优 | 好 | 中 | 优秀(每文件一篇文档) | 中 | — |
| 适合人群 | 业务/入门 | 大规模团队 | 研究者 | 学习者/自研起点 | 规模化研究者 | 极客/特殊需求 |
四、实战建议:按场景选
| 场景 | 推荐 | 理由 |
|---|---|---|
| 学习原理、第一次写 RL | CleanRL + SB3 | CleanRL 读懂每个数字,SB3 快速对照 |
| 业务方要一个"能跑、可靠"的策略 | SB3 | 文档全、坑少、维护省心 |
| 需要在 SB3 基础上改网络/奖励形状 | SB3 + 自定义 MlpExtractor / callbacks | 大部分改动 SB3 的扩展点就够 |
| 需要大规模并行/多机训练 | RLlib(CPU 集群)或 Brax(GPU) | 按硬件选 |
| 做研究、跑消融、试新想法 | Tianshou | 模块化最顺手 |
| 深度定制(可微分环境、自定义协议) | 自研 + 抄 CleanRL | 先抄后改 |
| 复现某篇论文 | 该论文的官方实现 | 官方代码比任何框架都接近原文 |
一个常被忽略的选项:直接用论文官方实现
想复现 PPO/DQN/RLHF,第一选择永远是论文仓库(CleanRL 往往也维护了对应复刻)。框架实现经过封装,很多细节(奖励归一化、advantage 计算方式)可能与原文不同——这正是调参与超参数优化里"复现先查实现差异"的来源。
五、与自研的边界:"先抄 CleanRL 再改"
这条建议值得单独一节。它包含三层递进的意思:
text
第 1 层 抄:Clone 一份 CleanRL 的 ppo.py,读懂每一个字段
第 2 层 改:替换成你的环境、你的网络、你的奖励、你的日志
第 3 层 长:需要时再引入 SB3/RLlib 的组件(回调、向量化、日志)或整体迁移什么时候该从"抄 CleanRL"走向"换大框架"?
| 触发信号 | 行动 |
|---|---|
| 环境采样成了瓶颈(单进程 CPU 太慢) | 先加 gymnasium.vector / SubprocVecEnv,不行再上 RLlib/Brax |
| 需要多机训练 | 直接 RLlib 或 Brax,别在自研上挣扎 |
| 需要和公司现有调度/存储体系集成 | SB3 起步(生态好对接),或直接评估 RLlib |
| 团队要维护这个代码 2 年以上 | 优先 SB3(社区会替你维护) |
| 只是验证想法、不长期维护 | CleanRL/自研即可 |
自研的真成本
自研最大的隐藏成本不是开发,而是维护与复现:半年后你的"快速原型"长成了没人懂的技术债,此时 SB3 社区已经帮你修好了几百个边界 bug。除非你有明确理由(学习、特殊协议、性能),否则"先抄 CleanRL 再改、能换 SB3 就换"。
扩展点示例:在 SB3 上做"受控的自研"
很多人以为选 SB3 = 失去控制权,其实 SB3 的扩展点足够覆盖大多数"想改一点"的需求。从轻到重四个层次:
| 层 | 需求 | SB3 的入口 |
|---|---|---|
| 1 | 改网络结构 | 自定义 MlpExtractor / features_extractor_class |
| 2 | 改训练过程行为 | callbacks(保存、评估、学习率调度、自定义日志) |
| 3 | 改奖励/观测 | Gymnasium wrapper(RewardWrapper/ObservationWrapper) |
| 4 | 改算法核心逻辑 | 继承 PPO/SAC 覆写方法(此时已接近"先抄再改") |
第 2 层的一个典型示例——训练中动态调学习率并定期评估:
python
from stable_baselines3 import PPO
from stable_baselines3.common.callbacks import BaseCallback
class TrainCallback(BaseCallback):
"""每个 rollout 后:线性衰减学习率 + 定期打印评估回报。"""
def _on_step(self):
if self.n_calls % self.model.n_steps == 0:
# 学习率线性衰减到 1/10
progress = self.num_timesteps / self.model.total_timesteps
new_lr = 3e-4 * (1.0 - 0.9 * progress)
self.model.lr_schedule = lambda _: new_lr
return True
model = PPO("MlpPolicy", "CartPole-v1", learning_rate=3e-4, verbose=0)
model.learn(total_timesteps=100_000, callback=TrainCallback())第 1 层的一个典型示例——给策略加一个特征提取器(把观测先过一层自定义网络):
python
import torch.nn as nn
from stable_baselines3 import PPO
from stable_baselines3.common.torch_layers import BaseFeaturesExtractor
class ObsNormExtractor(BaseFeaturesExtractor):
def __init__(self, observation_space, features_dim=128):
super().__init__(observation_space, features_dim)
self.net = nn.Sequential(
nn.Linear(observation_space.shape[0], 128), nn.ReLU(),
)
def forward(self, observations):
return self.net(observations)
model = PPO("MlpPolicy", "CartPole-v1",
policy_kwargs={"features_extractor_class": ObsNormExtractor})什么时候"扩展 SB3"会变成"必须自研"
SB3 扩展点解决 90% 的需求,剩下 10% 集中在:需要自定义训练循环(非标准更新节奏)、需要可微分环境(环境参与反向传播)、需要多机自定义调度。出现这些需求时再启动"先抄 CleanRL 再改"也不迟。
六、环境与工具链的配合
框架永远只是中间层。完整工具链长这样:
text
┌──────────────────────────────────────────────────────────┐
│ 环境层 Gymnasium(统一接口) │
│ ├─ MuJoCo(连续控制) ├─ Atari(像素观测) │
│ ├─ Brax(JAX 环境) ├─ 自建环境 │
├──────────────────────────────────────────────────────────┤
│ 采样层 SubprocVecEnv / gymnasium.vector / 多机 │
├──────────────────────────────────────────────────────────┤
│ 算法层 SB3 / RLlib / Tianshou / CleanRL / 自研 │
├──────────────────────────────────────────────────────────┤
│ 配置层 Hydra(config 管理) │
│ 日志层 W&B / TensorBoard / CSV │
│ 调参层 Optuna(贝叶斯优化) │
│ 评估层 rliable / 自写评估脚本 │
└──────────────────────────────────────────────────────────┘选框架时要把上下两层一起想:
- 环境兼容:你的环境是不是 Gymnasium 接口?Brax 环境在
gymnasium.make里可用(brax提供了 Gymnasium 包装);Atari/MuJoCo 都有 Gymnasium 集成。非 Gymnasium 接口的环境(某些工业仿真)会瞬间砍掉大部分框架选项。 - 观测预处理:像素观测(Atari)要不要
frame_stack/灰度?SB3 内置AtariPreprocessing,CleanRL 有现成参数——这两家在图像任务上都省心。 - 与工具链的配合:SB3 有官方
EvalCallback、tensorboard集成;CleanRL 用argparse+ 手动日志,可接 W&B;RLlib 内置Tune调参与Serve部署。
选型决策速查
text
开始
├─ 第一次写 RL?───────────────────→ CleanRL 教程 + SB3 对照
├─ 业务要稳定策略?────────────────→ SB3
├─ 单机并行不够?──────────────────→ RLlib 或 Brax(按 CPU/GPU)
├─ 做研究/消融?───────────────────→ Tianshou
└─ 要完全掌控一切?────────────────→ 先抄 CleanRL,再决定要不要自研七、给团队的一句话总结
教学用 CleanRL,产品用 SB3,大规模用 RLlib/Brax,特殊需求先抄 CleanRL 再改。
框架是脚手架,不是作品。你的作品是"环境 + 奖励 + 评估 + 调参"这一整套实验系统——那部分框架帮不了你,需要按从零构建一个 RL 项目的流水线自己搭。
延伸阅读
- 渐进式教程:Gymnasium 三版跑起来 —— 用最小骨架理解框架在帮你做什么
- 从零构建一个 RL 项目 —— 框架之外的项目工程:环境、评估、部署、监控
- 数据集与工具档案 —— 环境库(Gymnasium/MuJoCo/Brax)与训练工具链(Hydra/Optuna/W&B)的完整索引
- Actor-Critic 家族 —— 框架内置算法的原理底牌:PPO/SAC/DDPG 谱系
- 调参与超参数优化 —— 选好框架后,怎么批量跑实验、怎么调参
参考资料
- Stable-Baselines3 官方文档:stable-baselines3.readthedocs.io;源码:github.com/DLR-RM/stable-baselines3
- Ray RLlib 官方文档:docs.ray.io/en/latest/rllib/
- Tianshou 官方文档:tianshou.org;源码:github.com/thu-ml/tianshou
- CleanRL 官方文档:docs.cleanrl.dev;源码:github.com/vwxyzjn/cleanrl
- Brax(Google,JAX 物理引擎):github.com/google/brax
- PureJaxRL(JAX 版 RL 极简实现):github.com/luchris429/purejaxrl
- Gymnasium(Farama Foundation):gymnasium.farama.org;MuJoCo:mujoco.org
- Hydra:hydra.cc;Optuna:optuna.org;Weights & Biases:wandb.ai