← 返回文章库

unitree_rl_gym 拆解:机器人靠强化学习学会走路的完整链路

最后更新 2026-08-23
⏱ 约 15 分钟 🟡 涉接线/强电
你将学到
  • 走通 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

AtomicLockDataBuffer 一起出现,说明这是个多线程程序:一个线程按固定周期收状态,另一个线程跑推理,两者之间用带锁的缓冲区交换数据。控制程序里线程安全出问题,代价是硬件损坏,不是抛个异常那么简单。

代码是怎么组织的

流水线讲完,看代码结构。仓库主体分三大块:

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.pyisaacgym_utils.pyhelpers.pylogger.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 起步的完整路径,具身智能是什么那篇可以先读。

📄 来源 / 自校链接

本文为公开资料整理,非亲测。关键参数与代码请结合实物与下列官方来源验证。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为公开资料的学习整理,非亲测。涉接线/花钱/合规的步骤请结合实物与官方最新资料验证,风险自负。见免责声明