Skip to content

框架与工具怎么选

本页速览 Stable-Baselines3、Ray RLlib、Tianshou、CleanRL、JAX 系(Brax、PureJaxRL)、自研:六个选项的适用场景对比表;"先抄 CleanRL 再改"的工程建议。

框架与工具怎么选 ​

一句话定位:这一页帮你决定"我的 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,再改:以可读参考实现为骨架,逐步替换成你需要的部分。全程自己写代码 = 把两三个月花在别人已经解决的工程细节上。这条建议是本页最重要的产出,详见第五节。

三、六个选项横向对比表 ​

维度SB3RLlibTianshouCleanRLBrax/PureJaxRL自研
语言/后端PyTorchPyTorch/TFPyTorchPyTorchJAX任意
算法覆盖广且稳最广广且新主流算法PPO/SAC 为主由你
开箱即用★★★★★★★★★★★★★★★★★★★★
灵活性★★★★★★★★★★★★★★★★★★★★★★★
分布式/并行单机向量多机大规模单机单机GPU 大规模由你
学习成本低高中低高最高
文档/社区最优好中优秀(每文件一篇文档)中—
适合人群业务/入门大规模团队研究者学习者/自研起点规模化研究者极客/特殊需求

四、实战建议:按场景选 ​

场景推荐理由
学习原理、第一次写 RLCleanRL + SB3CleanRL 读懂每个数字,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 / 自写评估脚本                       │
└──────────────────────────────────────────────────────────┘

选框架时要把上下两层一起想:

  1. 环境兼容:你的环境是不是 Gymnasium 接口?Brax 环境在 gymnasium.make 里可用(brax 提供了 Gymnasium 包装);Atari/MuJoCo 都有 Gymnasium 集成。非 Gymnasium 接口的环境(某些工业仿真)会瞬间砍掉大部分框架选项。
  2. 观测预处理:像素观测(Atari)要不要 frame_stack/灰度?SB3 内置 AtariPreprocessing,CleanRL 有现成参数——这两家在图像任务上都省心。
  3. 与工具链的配合: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 项目的流水线自己搭。

延伸阅读 ​

参考资料 ​