unitree_rl_gym 拆解:机器人靠强化学习学会走路的完整链路
- 走通 Train → Play → Sim2Sim → Sim2Real 这条完整流水线,知道每一步在防什么
- 看懂 legged_gym 的环境继承结构,明白新增一个机器人型号要写哪些文件
- 理解「策略网络」在部署时其实只是一个几百 KB 的文件,以及它两端各接着什么
传统上让一台四足机器人走路,是这么干的:建立动力学模型,设计步态,写状态机管理支撑相和摆动相,再用 MPC 之类的方法在线求解每条腿该出多大力。这条路走得通,但每一步都需要人来设计,而且换个地形、换个负载,往往就得重新调。
强化学习给的是另一个答案:别设计了,让它自己练。
在仿真里放几千个机器人同时摔跤,摔对了给奖励,摔错了给惩罚,练上几亿步,最后得到一个神经网络。你把这个网络塞进真机,它就会走路了。
听起来像魔法,但拆开看全是工程。宇树把这条链路完整开源了,就是 unitree_rl_gym 这个仓库。这篇我们把它从头读一遍——不是教你怎么跑(那份操作步骤官方 README 写得很清楚),而是搞明白每一步为什么存在。
照例说明边界:本文基于该仓库的公开文档与代码结构。我没有真机可以部署,所以不会有任何「实测效果如何」的描述,涉及运行结果的地方都以官方文档的说法为准。
四步流水线:这个仓库真正的骨架
README 里有一行字,是理解整个仓库的钥匙:
Train → Play → Sim2Sim → Sim2Real
训练 → 回放 → 仿真到仿真 → 仿真到真机。
第一次看到这条链路,多数人的疑问集中在第三步:为什么训练完不直接上真机,中间要再换一个仿真器跑一遍?
这一步恰恰是整条链路里最能体现工程成熟度的设计,我们放到后面细说。先按顺序走一遍。
第一步 Train:在仿真里练
训练入口是 legged_gym/scripts/train.py,跑起来长这样:
python legged_gym/scripts/train.py --task=xxx
--task 是必填参数,官方文档给出的可选值是 go2、g1、h1、h1_2——一个四足加三个人形。
其他几个参数里,有两个特别值得说:
--num_envs:并行环境数量。 这是强化学习能在机器人上落地的物理基础。传统的强化学习在真实世界里几乎没法用,因为样本效率太低——一个策略可能需要几亿步交互才能收敛,真机跑几亿步,机器人早散架了。Isaac Gym 这类 GPU 加速仿真器的价值就在这:它能在一块显卡上同时跑几千个机器人实例,几亿步交互被压缩到可以接受的时间里。
--headless:无图形界面模式。 官方文档明确建议训练时用它,因为渲染画面会拖慢效率。这也提示了一个心态上的转变:训练阶段不是给人看的。你不需要盯着几千个机器人在屏幕上乱抖,你只需要看奖励曲线。
训练产物按这个规则存放:
logs/<experiment_name>/<日期时间>_<run_name>/model_<迭代次数>.pt
每隔一定迭代存一个检查点,配合 --resume 可以断点续训。这套目录约定在做长时间训练时很实用——你会需要回到某个中间版本,因为强化学习的训练曲线并不总是单调变好。
第二步 Play:把练出来的东西调出来看
python legged_gym/scripts/play.py --task=xxx
Play 默认加载最近一次实验的最新模型,在仿真器里可视化跑给你看。参数和 Train 基本一致。
Play 除了「看一眼」之外,还干了一件关键的事:导出网络。
官方文档写得很明确,Play 会把 Actor 网络导出到 logs/{experiment_name}/exported/policies 下:普通的 MLP 网络导出为 policy_1.pt,RNN 网络导出为 policy_lstm_1.pt。
这里有个概念要掰扯清楚。强化学习训练时,你手上通常有两个网络:Actor(演员)负责根据当前状态输出动作,Critic(评论家)负责评估这个状态有多好。Critic 只在训练时有用,部署时完全不需要。 所以导出只导 Actor。
这件事的意义比它看起来大:训练完成后,真正要搬到机器人身上的,就是这么一个文件。几百 KB 到几 MB。机器人身上跑的推理代码,本质就是「读传感器 → 拼成一个向量 → 喂给这个文件加载出来的网络 → 拿到输出 → 发给关节」。
第三步 Sim2Sim:换个仿真器再跑一遍
python deploy/deploy_mujoco/deploy_mujoco.py g1.yaml
现在回到最初那个问题:训练已经成功了,为什么还要在 MuJoCo 里再跑一遍?
官方文档给的理由是:确保策略没有过度依赖 Gym 的特性。
这句话背后是强化学习最经典的坑。神经网络是个彻底的机会主义者,你给它什么奖励,它就想尽办法把奖励拿满——包括利用仿真器的 bug。这在圈内有个专门的说法叫 reward hacking。仿真器的接触力学模型有偏差、摩擦计算有近似、数值积分有误差,这些都是可以被策略「薅」的漏洞。练出来的机器人在 Isaac Gym 里走得漂亮极了,一到真机上原地摔倒——因为它学会的不是走路,是利用某个仿真器的特定误差。
Isaac Gym 和 MuJoCo 是两套独立实现的物理引擎,接触模型、求解器都不一样。一个策略如果只在 Isaac Gym 里能用,换到 MuJoCo 就废,那它多半也上不了真机。
所以 Sim2Sim 是一道成本极低的过滤网:不用碰真机、不冒摔坏硬件的风险,就能筛掉一大批「假装学会了」的策略。这一步没有炫技的成分,纯粹是工程上的保险。
仓库里 deploy/deploy_mujoco/configs/ 下放着 g1、h1、h1_2 三个 YAML 配置。配置文件里的 policy_path 指向要加载的模型——默认指向仓库自带的预训练模型,你想验证自己训的,就把这个路径改成 Play 导出的那个 .pt 文件。
第四步 Sim2Real:上真机
python deploy/deploy_real/deploy_real.py {net_interface} {config_name}
第一个参数是网卡名,比如 enp3s0。
这个参数很能说明问题:你的电脑是通过网线跟机器人通信的,走的就是 unitree_sdk2 那套 DDS 通信。仓库的致谢里也确认了这条依赖——物理部署的硬件通信接口用的是 unitree_sdk2_python。
deploy/deploy_real/common/ 下的三个文件,勾勒出真机部署要额外处理的事:
command_helper.py —— 指令构造
remote_controller.py —— 遥控器接入
rotation_helper.py —— 姿态旋转换算
remote_controller 的存在特别值得注意。在仿真里没人管你,但真机上必须有一个人握着遥控器——出事的时候能立刻切断策略输出。官方文档也强调了部署前要确保机器人处于调试模式。这是安全底线,不是可选项。
rotation_helper 则对应另一类麻烦:四元数、欧拉角、旋转矩阵之间的换算,以及仿真和真机之间坐标系定义的对齐。这类换算不难但极易出错,而且错了往往不报错,只是机器人动作诡异——单独抽成一个文件是明智的。
仓库还提供了一份 C++ 部署实现,在 deploy/deploy_real/cpp_g1/ 下,依赖 LibTorch。Python 适合开发调试,但生产环境里 C++ 在实时性和确定性上更可控。看这个目录的构成也能读出真机部署的关注点:
Controller.cpp/h —— 控制主体
DataBuffer.h —— 数据缓冲
AtomicLock.h —— 原子锁
joystick.h —— 手柄
utilities.cpp/h
AtomicLock 和 DataBuffer 一起出现,说明这是个多线程程序:一个线程按固定周期收状态,另一个线程跑推理,两者之间用带锁的缓冲区交换数据。控制程序里线程安全出问题,代价是硬件损坏,不是抛个异常那么简单。
代码是怎么组织的
流水线讲完,看代码结构。仓库主体分三大块:
legged_gym/ —— 训练侧
deploy/ —— 部署侧
resources/ —— 机器人模型资产
训练侧:一套清晰的继承体系
legged_gym/envs/base/
base_config.py
base_task.py
legged_robot.py
legged_robot_config.py
legged_gym/envs/go2/ go2_config.py
legged_gym/envs/g1/ g1_config.py g1_env.py
legged_gym/envs/h1/ h1_config.py h1_env.py
legged_gym/envs/h1_2/ h1_2_config.py h1_2_env.py
base/ 里是通用基类:base_task 是任务框架,legged_robot 是足式机器人的通用环境实现,两个 config 文件是配置基类。
然后每个型号一个目录。这里有个细节很有意思:go2 目录下只有 go2_config.py,没有 go2_env.py;而三个人形型号都各带一个 env.py。
从代码结构直接能读出的结论是:go2 完全复用了基类 legged_robot 的环境实现,只需要改配置;而人形型号需要覆写环境逻辑。这符合直觉——四足机器人的通用环境本来就是照着四足写的,人形在观测量构成、终止条件、奖励设计上都有额外需求,配置改不动,得写代码。
这个结构也回答了「我想加一个新机器人要做什么」:先试试只写一个 config,不够再写 env。
legged_gym/utils/ 下还有几个值得一提的文件:
task_registry.py:任务注册表。这是--task=g1这个命令行参数能生效的机制——每个型号把自己的环境类和配置类注册进去,脚本按名字取用。想加新型号,注册一下就接进来了。terrain.py:地形生成。训练时机器人不能只在平地上练,斜坡、台阶、崎岖地面都要有,否则策略一出实验室就废。math.py、isaacgym_utils.py、helpers.py、logger.py:数学工具、仿真器适配、辅助函数与日志。
资产侧:URDF 和网格模型
resources/robots/ 占了仓库文件数量的大头,装的是各型号的描述文件。以 g1_description/ 为例,里面有一组 URDF 文件和对应的 XML,区分不同配置——是否锁定腰部关节、是否带手,等等;meshes/ 下是每个连杆的 STL 网格。
URDF 是机器人的「身体定义」:有哪些连杆、通过什么类型的关节连接、每个关节能转到什么范围、质量和惯量是多少。仿真器靠它把机器人搭出来。同一台机器人提供多个 URDF 变体,是因为训练不同任务时可能需要固定掉一部分关节——比如只训下肢行走时,把腰和手臂锁死能显著降低问题维度。
站在别人的肩膀上
仓库的致谢部分把上游依赖列得很清楚:
- legged_gym(苏黎世联邦理工 Robotic Systems Lab):训练与运行代码的基础
- rsl_rl(同一实验室):强化学习算法实现
- MuJoCo(Google DeepMind):仿真能力
- unitree_sdk2_python:真机通信接口
这个依赖链条很能说明足式机器人强化学习这个领域的现状:学术界提供算法与训练框架,厂商在上面接自己的机器人模型和硬件通信。 legged_gym 和 rsl_rl 是这个方向被广泛使用的开源基础设施,宇树做的是把自家型号接进这套体系,并补上从仿真到真机的最后一段。
许可证是 BSD 3-Clause,仓库里明确列了三条:保留原始版权声明、不得用项目或组织名做推广、修改需公开说明。基于它做二次开发时记得遵守。
读完这个仓库,你应该记住三件事
第一,强化学习控制的产物就是一个文件。 训练过程再复杂,最后交付到机器人身上的只是一个几百 KB 的策略网络。它的输入是拼接好的状态向量,输出是关节指令。理解了这一点,「AI 控制机器人」这件事就从玄学变成了工程。
第二,Sim2Sim 那一步是整条链路里最值得学的设计。 它体现的思路可以推广到任何领域:在进入高成本验证之前,先用一个廉价的、独立的环境做交叉验证。 换个仿真器跑一遍几乎不花钱,但能挡掉一大批会在真机上摔跤的策略。你在做其他项目时也可以问自己:我有没有这样一道低成本的过滤网?
第三,代码结构会告诉你设计者的取舍。 go2 没有 env.py 而人形有,这一个细节就说明了四足和人形在环境建模上的真实差异。读仓库时留意这类「不对称」,比读十页文档更能理解设计。
顺着这一卷往下,可以看unitree_guide 那套不用强化学习的传统控制实现,两条路线对着看,各自的代价和好处会清楚很多。想先补基础的话,机器人与具身智能卷里有从 ESP32 起步的完整路径,具身智能是什么那篇可以先读。