65 KiB
四足机器狗从训练到实机部署全流程指南
本教程基于西安交通大学 RoboGauge 团队的研究成果 Toward Reliable Sim-to-Real Predictability for MoE-based Robust Quadrupedal Locomotion 及其项目主页,将原始 Go2 训练任务适配为 Unitree Go1,并串联 IsaacLab 训练、RoboGauge 评估、MuJoCo sim2sim、ONNX 导出、RDK X5/S100 BPU 量化和 sim2real。
本文依据当前本地仓库代码和本次 Go1 训练记录整理。已验证 1 轮 IsaacLab 训练、ONNX 接口与推理、MuJoCo 采样、RoboGauge 单任务评估,以及使用 ONNX 推理采样数据完成 X5/S100 PTQ 编译的链路。板端数值、延迟和硬件命令仍需在对应设备上逐条复核。不要整段复制后无人值守运行。
1. 教程范围与版本约定
1.1 仓库
| 用途 | 镜像内目录 | 本次验证版本 |
|---|---|---|
| IsaacLab 源码 | /opt/IsaacLab |
v2.3.2 |
| IsaacLab 训练与自带 MuJoCo 部署 | /opt/go2_rl_robotlab 的 go1 分支 |
f40874bc90c72264dbf46989d90e15a9715c1393 |
| 自动化 sim2sim 评估 | /opt/RoboGauge 的 go1 分支 |
66d0eb9010dabc501c52a8b9e5ffb2149dbcb82b |
| 独立 MuJoCo、ONNX、X5/S100 BPU 部署 | /opt/go1_pro_deploy/deploy_45dim_rl_gym |
22a13d6659c59e2793b08c767ab69d8fcc0a406b |
| 官方公开 SDK | unitree_legged_sdk | |
| 私有 Go1 PRO SDK | 仅在内部部署机使用,本文不公开协议、密钥和专有接口 |
本文有两条使用路径:
- 路径 A,直接使用交付镜像:不要重新 clone 或重新安装,使用
/opt下的固定目录和解释器。 - 路径 B,没有镜像:按 3.4 节拉取仓库并安装,但仓库、虚拟环境、日志和产物仍全部放在
/opt。
除第 11 至 14 节明确标注的 X5/S100 板端命令外,文中的 /root/... 不适用于训练镜像。训练镜像不得把环境或项目放到用户 home 目录。
本次训练在约 33540 轮手动停止,最后一个完整保存的检查点是 model_33500.pt。综合当前单 seed RoboGauge 结果,本文仍用 26000 轮检查点作为平地与三级楼梯之间较均衡的部署基线;33500 是最新候选,而不是无条件替代 26000。示例发布名称如下:
训练检查点: model_26000.pt
TorchScript: policy_robotlab_26000_post_command_curriculum.pt
浮点模型: policy_robotlab_26000_post_command_curriculum.onnx
X5 模型: policy_robotlab_26000_int16_gemm.bin
S100 模型: policy_robotlab_26000_s100_int16_gemm.hbm
训练目录中的导出文件会保留课程阶段。量化脚本使用 --name policy_robotlab_26000 时会生成上面的 X5/S100 短名称;TorchScript/ONNX 是否另存为不带课程后缀的别名必须在发布记录中明确。26000 只是当前均衡基线,不表示轮数越大一定越好。若任务以上楼梯为主,应同时保留 15000 和 26000;若以平地高速或下楼梯为主,可继续比较 33500。最终模型仍应根据多 seed RoboGauge、MuJoCo 回放和真机日志共同选择。
1.2 全流程
Go1 模型与任务适配
-> IsaacLab + MoE-CTS 训练
-> TensorBoard 观察收敛
-> 导出 TorchScript / ONNX
-> IsaacLab play
-> RoboGauge 自动评估
-> 独立 MuJoCo sim2sim
-> ONNX 实机只读和悬空测试
-> 用 ONNX 推理生成 PTQ 校验基准
-> X5/S100 量化与离线对齐
-> BPU 悬空对照
-> 低速地面测试
-> 复杂地形测试
任何阶段不通过,都应退回上一阶段定位问题,不能用放宽真机保护阈值来掩盖观测、关节顺序或模型接口错误。
2. 研究方法解读
2.1 为什么使用 MoE-CTS
该流程的核心不是只追求仿真中的高回报,而是提高策略在未知动力学和地形上的泛化能力,并用标准化指标提前判断 sim2real 风险。
- CTS(Concurrent Teacher-Student):teacher 可使用地形高度、接触力、关节力矩等特权信息;student 仅使用真机可获得的本体感知历史。两者在训练期间并行优化,使 student 学会从历史观测中估计隐含环境信息。
- MoE(Mixture of Experts):策略包含多个 expert,由门控网络根据隐变量组合不同 expert。不同 expert 可以分别覆盖平地、坡面、台阶、低摩擦和动力学扰动等状态。
- 历史观测:单帧传感信息不能直接给出摩擦、延迟和外部扰动,连续历史为 student 提供了估计这些量的依据。
- 域随机化与课程学习:训练中改变地形、摩擦、质量、执行器延迟和外力,并随能力提升逐渐增加地形和指令难度。
2.2 奖励、评估和部署的职责不同
训练奖励用于产生梯度,并不是最终安全指标。高总奖励可能由速度跟踪项主导,同时仍存在极端关节、力矩尖峰或急停不稳。因此本流程分三层判断:
- 训练奖励:判断优化是否收敛、是否发生策略坍塌。
- RoboGauge 指标:在跨地形、跨摩擦、多随机种子下比较跟踪、安全和运动质量。
- 真机保护与日志:判断实时观测、动作、关节目标和硬件状态是否处于允许范围。
RoboGauge 当前关注的指标包括关节软限位、线/角速度误差、电机功耗、姿态稳定性、力矩平滑度、摩擦裕度和 ZMP 裕度。归一化后均按“越大越好”解释,但任何综合分数都不能替代分项检查。
3. 两种环境准备方式
3.1 硬件与软件前提
- NVIDIA GPU 和匹配的驱动。
- Linux 训练环境;图形界面训练不是必须。
- Python 3.11。
- IsaacLab
2.3.2.post1和 Isaac Sim5.1.0.0。 - 足够的磁盘空间保存检查点、TensorBoard 日志和导出模型。
3.2 路径 A:直接使用交付镜像
以下信息在当前工作区无法从文件和 Docker 本地缓存推断,属于交付前必须填写的镜像元数据:
交付阻断项(维护者填写)
RoboGo 镜像名称:
镜像拉取命令:
容器启动参数:
训练数据持久化目录:
量化工具链不依赖交付镜像本身的 CUDA 环境,已单独归档到 /opt:
/opt/envs/go2_rl_robotlab/docker-toolchain/images/openexplorer_ai_toolchain_ubuntu_20_x5_cpu_v1.2.8.tar
/opt/envs/go2_rl_robotlab/docker-toolchain/images/ai_toolchain_ubuntu_22_s100_s600_cpu_v3.7.0.tar
归档内只有 Docker CLI 和工具链镜像,不包含也不应在交付镜像内自行启动 dockerd。量化时应连接宿主 Docker daemon,先执行 docker load -i <归档>,再运行第 11 节脚本。本次已实际加载并验证两个归档。
如果交付镜像本身运行在 Docker 中,需要由宿主在创建容器时挂载 Docker socket:
# 以下在宿主执行,放入实际的 docker run 参数中
docker run ... \
-v /var/run/docker.sock:/var/run/docker.sock \
<RoboGo镜像>
进入容器后验收 CLI 和 daemon:
sudo /opt/envs/go2_rl_robotlab/docker-toolchain/bin/docker info
sudo /opt/envs/go2_rl_robotlab/docker-toolchain/bin/docker image inspect openexplorer/ai_toolchain_ubuntu_20_x5_cpu:v1.2.8
sudo /opt/envs/go2_rl_robotlab/docker-toolchain/bin/docker image inspect registry.d-robotics.cc/deliver/ai_toolchain_ubuntu_22_s100_s600_cpu:v3.7.0
本次验证 daemon 的 DockerRootDir 是宿主 /var/lib/docker。交付所需的 CLI、配置和可重新加载的镜像归档都在 /opt/envs/go2_rl_robotlab/docker-toolchain;docker load 之后的展开层属于宿主 daemon 缓存,不会被保存进 RoboGo 交付镜像。如果专用量化主机要求 Docker 展开层也位于 /opt,应由主机管理员在首次 docker load 之前设置 daemon,不要在共享 Docker 主机上盲目更改:
{
"data-root": "/opt/envs/go2_rl_robotlab/docker-toolchain/docker-data"
}
上述内容应合并到宿主 /etc/docker/daemon.json,然后由管理员重启 Docker 并用 docker info --format '{{.DockerRootDir}}' 验收。修改数据根目录会影响该 daemon 管理的所有镜像和容器,不是普通容器内步骤。
镜像内目录必须固定如下:
/opt/IsaacLab
/opt/go2_rl_robotlab
/opt/RoboGauge
/opt/go1_pro_deploy
/opt/envs/go2_rl_robotlab
/opt/envs/robogauge
直接使用镜像时跳过 3.4 节。首次进入容器先执行验收,不要看到 /opt/IsaacLab 存在就默认版本正确:
export OMNI_KIT_ACCEPT_EULA=YES
export MPLCONFIGDIR=/tmp/robogauge-matplotlib
test -d /opt/IsaacLab
test -d /opt/go2_rl_robotlab
test -d /opt/RoboGauge
test -d /opt/go1_pro_deploy
test -x /opt/envs/go2_rl_robotlab/bin/python
test -x /opt/envs/robogauge/bin/python
nvidia-smi
git --git-dir=/opt/IsaacLab/.git --work-tree=/opt/IsaacLab describe --tags --always
/opt/envs/go2_rl_robotlab/bin/python -c "import isaaclab, torch; import importlib.metadata as m; print('isaaclab', m.version('isaaclab')); print('isaacsim', m.version('isaacsim')); print('torch', torch.__version__)"
预期为 IsaacLab v2.3.2/2.3.2.post1、Isaac Sim 5.1.0.0、PyTorch 2.7.0。IsaacLab v2.2.0 搭配 Isaac Sim 5.0.0 与当前 go1 分支不兼容,不能作为交付镜像发布。本次检查发现原始 /opt/IsaacLab 正是该不兼容组合;镜像维护者必须在发布层将 /opt/IsaacLab 替换为 v2.3.2,不能只在另一个临时目录安装新版后继续交付旧路径。
Pip 版 Isaac Sim 首次启动必须接受 EULA。建议把下面变量写入镜像运行环境,而不是只写在某个用户的 shell 历史中:
export OMNI_KIT_ACCEPT_EULA=YES
下列目录会在训练或评估时写入,镜像构建阶段必须由 root 预建并授予实际容器用户写权限:
/opt/go2_rl_robotlab/logs
/opt/go2_rl_robotlab/outputs
/opt/RoboGauge/logs
/opt/RoboGauge/outputs
/opt/go1_pro_deploy/logs
/opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim/kit/cache
/opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim/kit/data
/opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim/kit/logs
/opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim/kit/data/Kit/Isaac-Sim/5.1/user.config.json
Dockerfile 中应使用交付镜像的实际运行 UID/GID(下面的 APP_UID/APP_GID 只是占位符):
RUN install -d -m 0775 /opt/go2_rl_robotlab/logs /opt/go2_rl_robotlab/outputs \
/opt/RoboGauge/logs /opt/RoboGauge/outputs \
/opt/go1_pro_deploy/logs \
/opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim/kit/cache \
/opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim/kit/data \
/opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim/kit/logs \
&& chown -R ${APP_UID}:${APP_GID} /opt/go2_rl_robotlab/logs /opt/go2_rl_robotlab/outputs \
/opt/RoboGauge/logs /opt/RoboGauge/outputs \
/opt/go1_pro_deploy/logs \
/opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim/kit/cache \
/opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim/kit/data \
/opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim/kit/logs
运行用户验收:
test -w /opt/go2_rl_robotlab/logs
test -w /opt/go2_rl_robotlab/outputs
test -w /opt/RoboGauge/logs
test -w /opt/RoboGauge/outputs
test -w /opt/go1_pro_deploy/logs
test -w /opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim/kit/cache
test -w /opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim/kit/data
test -w /opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim/kit/logs
test -w /opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim/kit/data/Kit/Isaac-Sim/5.1/user.config.json
旧 /opt/IsaacSim 清理后,RoboGauge 环境的 bin/python3 不得继续指向旧路径;应指向 /opt/envs/go2_rl_robotlab/python-runtime,并把 /opt/RoboGauge 和 RoboGauge 自己的 site-packages 放入 PYTHONPATH。否则第 9 节会在导入前直接报 No such file or directory。
本流程以 /opt/envs/go2_rl_robotlab/lib/python3.11/site-packages/isaacsim 中的 pip 版 Isaac Sim 5.1.0.0 为准。旧的独立 /opt/IsaacSim 5.0.0 已从运行环境移除;不要重新挂载它的 PYTHONPATH 或 python.sh。
若仓库由镜像构建用户创建、运行用户不同,普通 git status 可能报 dubious ownership。镜像应优先统一目录属主;只读检查也可使用 git --git-dir=/opt/<REPO>/.git --work-tree=/opt/<REPO> ...,不要为省事把所有系统目录加入 Git safe.directory。
3.3 镜像训练链路自检
正式训练前先跑 1 轮。以下命令已在目标镜像运行环境中以 16 个环境验证,完成 384 个 transition,用时约 5.23 秒;发布镜像仍必须先通过 3.2 节的版本和写权限验收:
cd /opt/go2_rl_robotlab
OMNI_KIT_ACCEPT_EULA=YES /opt/envs/go2_rl_robotlab/bin/python scripts/rsl_rl/train.py \
--task=RobotLab-Go1-v0 \
--num_envs=16 \
--max_iterations=1 \
--run_name=image_direct_1iter \
--logger=tensorboard \
--headless
确认新目录中至少有 model_0.pt、train.log、TensorBoard event、params/env.yaml 和 params/agent.yaml:
find /opt/go2_rl_robotlab/logs/rsl_rl/go1_moe_cts -path '*image_direct_1iter*' -type f -print
本次自检 checkpoint 的 SHA-256 为 14589a131b09680bbbd10427ad6d5f90dd4cf3a0cc57b6f516c4de2ef1b0cbe3。该文件仅证明训练链路可运行,不是可部署策略。
3.4 路径 B:没有镜像时从零安装
以下命令假设主机已安装 NVIDIA 驱动、Docker(量化时需要)和 Conda;Conda/Miniforge 本身也应安装在 /opt。即使没有镜像,也统一把项目与 Conda prefix 放到 /opt:
sudo install -d -o "$USER" -g "$(id -gn)" /opt/envs /opt/IsaacLab
sudo install -d -o "$USER" -g "$(id -gn)" /opt/go2_rl_robotlab /opt/RoboGauge /opt/go1_pro_deploy
conda create --prefix /opt/envs/go2_rl_robotlab python=3.11 -y
conda activate /opt/envs/go2_rl_robotlab
python -m pip install --upgrade pip
python -m pip install "isaaclab[isaacsim,all]==2.3.2.post1" --extra-index-url https://pypi.nvidia.com
python -m pip install torch==2.7.0 torchvision==0.22.0 --index-url https://download.pytorch.org/whl/cu128
拉取固定分支。内部 Gitea 地址需要相应读取权限;没有权限时应向交付方获取源码归档及 commit 校验,不能退回公开仓库的不同分支假装等价:
git clone --branch v2.3.2 --depth 1 https://github.com/isaac-sim/IsaacLab.git /opt/IsaacLab
git clone --branch go1 --single-branch https://gitea.qingruxu.xyz/cyy/go2_rl_robotlab.git /opt/go2_rl_robotlab
git clone --branch go1 --single-branch https://gitea.qingruxu.xyz/cyy/RoboGauge.git /opt/RoboGauge
git clone --branch main --single-branch https://gitea.qingruxu.xyz/cyy/go1_pro_deploy.git /opt/go1_pro_deploy
cd /opt/go2_rl_robotlab
python -m pip install -e source/robot_lab
python -m pip install -e source/rsl_rl
python -m pip install tensordict onnx onnxscript onnxruntime mujoco pygame tensorboard
RoboGauge 使用独立环境,避免其 mujoco==3.2.3、dm_control==1.0.23 和 numpy<2 约束影响 IsaacLab。必须先固定 PyTorch,防止无上限的 torch 依赖在未来解析成 CUDA 13 等大体积版本:
conda create --prefix /opt/envs/robogauge python=3.11 -y
conda activate /opt/envs/robogauge
python -m pip install torch==2.7.0 --index-url https://download.pytorch.org/whl/cpu
cd /opt/RoboGauge
python -m pip install -e /opt/RoboGauge
最后由管理员创建运行目录并交给实际运行用户:
sudo install -d -o "$USER" -g "$(id -gn)" /opt/go2_rl_robotlab/logs /opt/go2_rl_robotlab/outputs
sudo install -d -o "$USER" -g "$(id -gn)" /opt/RoboGauge/logs /opt/RoboGauge/outputs
sudo install -d -o "$USER" -g "$(id -gn)" /opt/go1_pro_deploy/logs
记录环境,便于在部署前复现:
git --git-dir=/opt/go2_rl_robotlab/.git --work-tree=/opt/go2_rl_robotlab rev-parse HEAD
/opt/envs/go2_rl_robotlab/bin/python --version
/opt/envs/go2_rl_robotlab/bin/python -m pip freeze > /opt/go2_rl_robotlab/requirements-lock.txt
nvidia-smi
4. 从 Go2 适配到 Go1
本地 go1 分支的关键提交为 0d2afbf feat: add Go1 training and MuJoCo deployment。适配不只是替换 URDF,至少包括以下内容:
- 添加完整的 Go1 URDF、MuJoCo XML 和 mesh。
- 定义 Go1 执行器、默认关节角、PD 参数和力矩限制。
- 新建
RobotLab-Go1-v0的场景、观测、动作、奖励、随机化和课程配置。 - 注册 Go1 任务及 MoE-CTS runner 配置。
- 添加 Go1 的 MuJoCo 配置和部署入口。
- 在所有模块中统一关节顺序与策略输入输出。
可用下面的命令查看完整改动:
git show --stat 0d2afbf
git show 0d2afbf -- source/robot_lab/robot_lab/tasks/go1
git show 0d2afbf -- source/robot_lab/robot_lab/assets/unitree.py
git show 0d2afbf -- source/robot_lab/robot_lab/assets/unitree_actuator.py
git show 0d2afbf -- deploy/deploy_mujoco
4.1 必须固定的策略接口
| 项目 | RobotLab Go1 约定 |
|---|---|
| 关节顺序 | FR, FL, RR, RL,每条腿依次为 hip、thigh、calf |
| 单帧观测 | 45 维 |
| 历史长度 | 10 帧 |
| ONNX 输入 | 450 维,由部署端维护历史 |
| TorchScript 输入 | 当前 45 维,模型内部维护历史 |
| 动作 | 12 维归一化关节位置偏移 |
| 动作缩放 | 0.25 rad,再加默认关节角 |
| 物理步长 | 0.005 s |
| decimation | 4 |
| 策略周期 | 0.02 s,即 50 Hz |
| 训练 PD | Kp=28、Kd=0.7 |
| 仿真力矩上限 | 33.5 Nm |
45 维单帧观测顺序为:
机身角速度(3)
+ 投影重力(3)
+ 速度指令 vx/vy/wz(3)
+ 相对默认角度的关节位置(12)
+ 关节速度(12)
+ 上一时刻动作(12)
= 45
RobotLab ONNX 的历史按“观测项分组后堆叠”,不是简单地将 10 个 45 维帧首尾拼接:
[角速度 10 帧, 重力 10 帧, 指令 10 帧,
关节位置 10 帧, 关节速度 10 帧, 上一动作 10 帧]
不要把旧 RL-Gym 的 5 帧/225 维 ONNX、RobotLab 的 10 帧/450 维 ONNX、内部维护历史的 45 维 TorchScript 混用。即使程序没有立即报错,错误的历史排列也会让输出失去意义。
5. 奖励函数与环境设计
5.1 奖励项与判断原则
Go1 奖励配置位于:
source/robot_lab/robot_lab/tasks/go1/env_cfg.py
当前主要奖励项如下:
| 奖励项 | 权重 | 目的 |
|---|---|---|
track_lin_vel_xy_exp |
2.0 |
跟踪平面线速度 |
track_ang_vel_z_exp |
1.0 |
跟踪 yaw 角速度 |
lin_vel_z_l2 |
-2.0,课程中趋近 0 |
抑制竖直跳动 |
ang_vel_xy_l2 |
-0.05 |
抑制 roll/pitch 角速度 |
joint_acc_l2 |
-1e-7 |
抑制关节加速度尖峰 |
joint_power |
-2e-5 |
降低机械功率 |
joint_torques_l2 |
-1e-4 |
降低力矩 |
base_height_l2 |
-1.0,课程中到 -10.0 |
保持约 0.30 m 机身高度 |
action_rate_l2 |
-0.01 |
抑制相邻动作突变 |
action_smoothness_l2 |
-0.01 |
抑制动作二阶不平滑 |
undesired_contacts |
-1.0 |
惩罚大腿、小腿触地 |
joint_pos_limits |
-2.0 |
避免关节接近或越过限制 |
feet_regulation |
-0.05 |
约束足端运动 |
hip_pos_penalty_l1 |
-0.05 |
约束髋关节偏移 |
joint_pos_penalty_l1 |
-0.01 |
约束大腿和小腿关节偏移 |
线速度和角速度跟踪项采用指数形式,误差为零时单项接近 1,再乘权重;其余多数为非负代价乘负权重。IsaacLab 奖励管理器还会按环境步长累计,因此不要根据权重直接猜测 TensorBoard 总回报的绝对值。
奖励判断原则:
- 先看 teacher 和 student 的平均回报是否持续上升并进入平台期。
- student 长期显著低于 teacher,通常表示历史编码器未学到足够的隐变量信息。
- 总回报上升时,还要检查关节限制、力矩、功率、动作平滑和 episode length。
- 单项奖励尺度相差较大,不能要求每条曲线数值接近。
- 奖励短时波动是 on-policy 训练的正常现象;连续大幅下降、episode length 同时缩短才更值得警惕。
- 修改奖励后必须新建 run,不能把不同配置的曲线当作同一实验连续比较。
环境通过随机初始状态、摩擦/质量等动力学变化、外力扰动、执行器延迟和连续地形难度降低过拟合。Go1 训练中的执行器延迟为 0 到 4 个物理步,即 0 到 20 ms;独立 MuJoCo 部署中的延迟参数按策略步计量,两者单位不同。
5.2 必须单独识别的课程切换
Go1 当前复用了 Go2RLGymCommandCfg。速度指令不是全程保持不变,也不是连续缓慢扩大,而是在固定轮次发生硬切换:
| 训练区间 | lin_vel_x |
lin_vel_y |
ang_vel_yaw |
|---|---|---|---|
| 0 到 19999 | [-0.5, 0.5] m/s |
[-0.5, 0.5] m/s |
[-1.0, 1.0] rad/s |
| 20000 到 49999 | [-1.0, 1.0] m/s |
[-1.0, 1.0] m/s |
[-1.5, 1.5] rad/s |
| 50000 起 | [-2.0, 2.0] m/s |
[-1.0, 1.0] m/s |
[-2.0, 2.0] rad/s |
配置位于 source/robot_lab/robot_lab/tasks/go2/mdp/commands.py。命令生成器会在环境重新采样指令时应用切换,并在日志打印 Command range updated at iter ...。因此:
- 20000 轮后的奖励断崖首先应按“任务分布突然变难”解释,不能直接判定策略坍塌。
model_20000.pt已处于切换边界;当前每 500 轮保存一次,明确属于切换前的最后一个检查点是model_19500.pt。- 50000 轮还会发生第二次、更激进的切换。继续训练到该位置前,应预留新的适应区间并单独标注曲线。
本次训练在 19900 到 20100 附近的代表性数据如下:
| 指标 | 19900 | 20100 |
|---|---|---|
| Teacher reward | 55.64 | 38.35 |
| Student reward | 54.08 | 36.33 |
| 非法接触终止比例 | 6.64% | 18.05% |
| 平均地形等级 | 5.687 | 5.735 |
| 最大 X 速度指令 | 0.5 | 1.0 |
地形等级在切换点附近没有同步跳变,奖励下降与速度指令范围扩大相吻合。后续判断重点是回报是否逐步恢复、非法接触是否下降,而不是要求新课程下的绝对回报回到旧课程水平。另两个固定奖励课程更早结束:lin_vel_z_l2 在 0 到 1500 轮由 -2 变为 0,base_height_l2 在 0 到 5000 轮由 -1 变为 -10,都不是 20000 轮断崖的原因。
6. 开始训练
6.1 默认参数的含义
当前默认配置:
num_envs = 4096
num_steps_per_env = 24
save_interval = 500
max_iterations = 300000(通常通过命令行覆盖)
每次 PPO 更新的 rollout 数量约为:
4096 * 24 = 98304 个 transition
num_envs 主要影响仿真吞吐、显存和每轮采样量;num_steps_per_env 同时影响 rollout 时间跨度、buffer 显存和更新统计。优先保持 num_steps_per_env=24,通过 --num_envs 适配 GPU。
建议从小到大试探:
cd /opt/go2_rl_robotlab
export OMNI_KIT_ACCEPT_EULA=YES
/opt/envs/go2_rl_robotlab/bin/python scripts/rsl_rl/train.py --task=RobotLab-Go1-v0 --num_envs=512 --max_iterations=20 --headless
/opt/envs/go2_rl_robotlab/bin/python scripts/rsl_rl/train.py --task=RobotLab-Go1-v0 --num_envs=1024 --max_iterations=20 --headless
/opt/envs/go2_rl_robotlab/bin/python scripts/rsl_rl/train.py --task=RobotLab-Go1-v0 --num_envs=2048 --max_iterations=20 --headless
/opt/envs/go2_rl_robotlab/bin/python scripts/rsl_rl/train.py --task=RobotLab-Go1-v0 --num_envs=4096 --max_iterations=20 --headless
另开终端观察:
watch -n 1 nvidia-smi
选择能稳定运行且留有显存余量的最大环境数。若 OOM,先减小 num_envs;只有明确理解 PPO batch 变化后,再修改 rsl_rl_cfg.py 中的 num_steps_per_env、mini-batch 和 epoch 数。
6.2 正式训练并保留候选检查点
cd /opt/go2_rl_robotlab
export OMNI_KIT_ACCEPT_EULA=YES
/opt/envs/go2_rl_robotlab/bin/python scripts/rsl_rl/train.py \
--task=RobotLab-Go1-v0 \
--num_envs=4096 \
--max_iterations=33500 \
--run_name=go1_4096x24 \
--logger=tensorboard \
--headless
日志默认位于:
/opt/go2_rl_robotlab/logs/rsl_rl/go1_moe_cts/<时间戳>_go1_4096x24/
检查点每 500 轮保存一次。不要只保留最后一个文件,至少确认课程切换前后和最终候选都存在:
find /opt/go2_rl_robotlab/logs/rsl_rl/go1_moe_cts -name 'model_*.pt' -print | sort -V
本次实际 run 保留了 model_15000.pt、model_19500.pt、model_26000.pt 和 model_33500.pt。若训练进程被 Ctrl+C 中断,以磁盘上最后一个完整 checkpoint 为准;本次停止日志到 33540 左右,但 33540 没有保存,不能把 33500 标成 33540。
6.3 断点续训
OMNI_KIT_ACCEPT_EULA=YES /opt/envs/go2_rl_robotlab/bin/python /opt/go2_rl_robotlab/scripts/rsl_rl/train.py \
--task=RobotLab-Go1-v0 \
--num_envs=4096 \
--max_iterations=3000 \
--resume \
--load_run=<RUN_DIRECTORY_NAME> \
--checkpoint=model_23000.pt \
--headless
当前 runner 在恢复训练时将 --max_iterations 解释为额外训练轮数。例如从 23000 继续到 26000,应填写 26000 - 23000 = 3000,若仍填写 26000 会继续训练到约 49000。恢复前先确认 params/env.yaml 和 params/agent.yaml 与预期一致。加载旧优化器状态后改变网络结构、观测维数或 expert 数量通常不可行。
7. 使用 TensorBoard 观察训练
训练机执行:
/opt/envs/go2_rl_robotlab/bin/python -m tensorboard.main --logdir /opt/go2_rl_robotlab/logs/rsl_rl --port 6006
从本机通过 SSH 查看远端 TensorBoard:
ssh -L 6006:127.0.0.1:6006 <USER>@<TRAIN_HOST>
浏览器打开 http://127.0.0.1:6006。重点观察:
| 曲线 | 判断方式 |
|---|---|
Train/mean_teacher_reward |
teacher 是否稳定学习并进入平台期 |
Train/mean_student_reward |
student 是否跟随 teacher,部署时主要关心 student |
Train/*episode_length |
是否频繁因机身触地提前终止 |
Loss/* |
是否出现 NaN、爆炸或长期异常震荡 |
Policy/mean_noise_std |
探索强度是否异常塌缩或持续过大 |
Perf/total_fps |
并行环境设置是否有效利用硬件 |
Episode_Reward/* |
速度跟踪、功率、力矩和动作平滑等分项奖励 |
Episode_Termination/* |
time_out 与 illegal_contact 的占比 |
Curriculum/terrain_levels |
地形难度是否连续变化 |
Metrics/base_velocity/max_command_x |
是否在 20000/50000 轮发生指令课程切换 |
不要跨课程直接比较总回报绝对值,也不要只选平均回报最高或轮次最大的 checkpoint。建议将 15000、19500、26000、33500 等候选同时送入 RoboGauge,比较综合分数和分项指标后再决定。
8. 评估与导出模型
8.1 IsaacLab play 与导出
play.py 加载 checkpoint 后,会先在同一 run 的 exported/ 下生成 policy.pt 和 policy.onnx,再进入可视化 rollout:
cd /opt/go2_rl_robotlab
OMNI_KIT_ACCEPT_EULA=YES /opt/envs/go2_rl_robotlab/bin/python scripts/rsl_rl/play.py \
--task=RobotLab-Go1-v0 \
--num_envs=64 \
--checkpoint=/opt/go2_rl_robotlab/logs/rsl_rl/go1_moe_cts/<RUN_NAME>/model_26000.pt
输出:
<RUN_DIR>/exported/policy.pt
<RUN_DIR>/exported/policy.onnx
同一 run 下再次导出会覆盖这两个通用文件名。必须在切换到下一个 checkpoint 前复制并带上轮次、课程阶段;导出文件完整写入后才能终止 play:
cp <RUN_DIR>/exported/policy.onnx /opt/go1_pro_deploy/deploy_45dim_rl_gym/policy_robotlab_26000.onnx
cp <RUN_DIR>/exported/policy.onnx /opt/RoboGauge/resources/models/go1/policy_robotlab_26000_post_command_curriculum.onnx
cp <RUN_DIR>/exported/policy.pt /opt/RoboGauge/resources/models/go1/policy_robotlab_26000_post_command_curriculum.pt
RoboGauge 当前通过 torch.jit.load() 使用 TorchScript .pt;ONNX 用于独立 MuJoCo、量化和实机部署。两者不能只靠改后缀互换。
8.2 不打断训练的导出原则
训练 runner 保存的是可恢复训练的 checkpoint,不会默认在每次保存时生成 ONNX。play.py 即使使用 --headless 也会启动 Isaac Sim 和一个环境,不是纯模型导出器。要保持主训练进程运行,只应在另一块 GPU、另一台机器或确认剩余显存足够时启动独立 play.py:
cd /opt/go2_rl_robotlab
OMNI_KIT_ACCEPT_EULA=YES CUDA_VISIBLE_DEVICES=1 /opt/envs/go2_rl_robotlab/bin/python scripts/rsl_rl/play.py \
--task=RobotLab-Go1-v0 \
--num_envs=1 \
--checkpoint=/opt/go2_rl_robotlab/logs/rsl_rl/go1_moe_cts/<RUN_NAME>/model_26000.pt \
--headless
注意:
- 只处理已经完整写完的 checkpoint,不要复制正在写入的文件。
- 12 GB 级显卡以 4096 环境训练时通常没有余量再启动 Isaac Sim;不要为了导出挤占训练进程。
- 若没有第二块 GPU,先复制 checkpoint,待训练停止后离线导出。仓库当前没有正式的
export-only命令,不要把临时 Python 片段写成通用教程入口。 play.py导出后仍会进入仿真循环;看到导出文件后用Ctrl+C结束该独立进程,不会中断训练进程。
8.3 检查 ONNX 接口
/opt/envs/go2_rl_robotlab/bin/python -c "import onnx, onnxruntime as ort; p='/opt/go1_pro_deploy/deploy_45dim_rl_gym/policy_robotlab_26000.onnx'; onnx.checker.check_model(onnx.load(p)); s=ort.InferenceSession(p, providers=['CPUExecutionProvider']); print([(x.name,x.shape,x.type) for x in s.get_inputs()]); print([(x.name,x.shape,x.type) for x in s.get_outputs()])"
预期 ONNX 输入为 obs: [1, 450] float32,输出为 actions: [1, 12] float32。本次导出的 19500、26000 和 33500 ONNX 均已通过 onnx.checker.check_model()。输入输出不匹配时不要继续 sim2sim 或量化。
8.4 独立 MuJoCo 快速检查
如果你要做的是“和部署链路一致”的快速检查,直接使用部署仓库里的 ONNX 入口。它读取当前 45 维单帧观测,并在脚本内部维护 10 帧历史,适合在进入完整 RoboGauge 评估前核对观测、动作缩放和地形 XML:
Linux 不需要 mjpython;它主要用于 macOS 的 MuJoCo 图形启动。镜像中用绝对 ONNX 路径执行非交互采样。该脚本会在加载模型前切换工作目录,相对 ONNX 路径可能因此失效:
cd /opt/go1_pro_deploy/deploy_45dim_rl_gym
env -u MUJOCO_GL /opt/envs/robogauge/bin/python deploy_go1_onnx_mujoco_lab.py \
--onnx /opt/go1_pro_deploy/deploy_45dim_rl_gym/policy_robotlab_26000.onnx \
--sample \
--sample-seconds 1 \
--sample-log-dir /opt/go1_pro_deploy/logs
采样目录名固定以 mujoco_stairs_sample_ 开头,用于保存独立 MuJoCo ONNX 推理检查日志;PTQ 使用第 11 节的正式 64 样本校验集。
楼梯测试同样直接切 terrain:
env -u MUJOCO_GL /opt/envs/robogauge/bin/python deploy_go1_onnx_mujoco_lab.py \
--onnx /opt/go1_pro_deploy/deploy_45dim_rl_gym/policy_robotlab_26000.onnx \
--sample-stairs-level 3 \
--sample-seconds 20 \
--sample-log-dir /opt/go1_pro_deploy/logs \
--spawn-x -0.6 \
--spawn-z 0.34
脚本默认 --clip-actions 100.0、--max-target-step 0.0。如果你要做更保守的起步测试,可以再按日志逐步收紧这些参数;不要把旧版本的示例值当成固定默认。
9. RoboGauge 自动评估
9.1 安装
路径 A 的镜像用户不再安装,先检查解释器、任务注册和模型:
test -x /opt/envs/robogauge/bin/python
test -f /opt/RoboGauge/resources/models/go1/policy_robotlab_26000_post_command_curriculum.pt
test -w /opt/RoboGauge/logs
cd /opt/RoboGauge
/opt/envs/robogauge/bin/python -c "import torch, mujoco, numpy; print('torch', torch.__version__); print('mujoco', mujoco.__version__); print('numpy', numpy.__version__)"
/opt/envs/robogauge/bin/python -c "import robogauge.tasks; from robogauge.utils.task_register import task_register; print(*sorted(task_register.pipeline_classes), sep='\n')" | grep '^go1_lab\.'
路径 B 的无镜像用户按 3.4 节 clone 后,单独创建环境。当前 RoboGauge 固定了 mujoco==3.2.3、dm_control==1.0.23 和 numpy<2;先安装固定的 CPU PyTorch,再安装仓库,避免 setup.py 中无上限的 torch 自动选中未来的大体积 CUDA 构建:
conda create --prefix /opt/envs/robogauge python=3.11 -y
conda activate /opt/envs/robogauge
python -m pip install torch==2.7.0 --index-url https://download.pytorch.org/whl/cpu
cd /opt/RoboGauge
python -m pip install -e .
上面的最后一条命令必须在 /opt/RoboGauge 中执行。本文需要 Gitea go1 分支中的 go1_lab.* 任务;公开上游仓库只能作为参考,不能用 go2_lab.* 代替。安装后确认当前 commit 与任务注册,输出中必须出现 go1_lab.flat、go1_lab.stairs_fd 等任务:
git --git-dir=/opt/RoboGauge/.git --work-tree=/opt/RoboGauge rev-parse HEAD
/opt/envs/robogauge/bin/python -c "import robogauge.tasks; from robogauge.utils.task_register import task_register; print(*sorted(task_register.pipeline_classes), sep='\n')"
若不存在 go1_lab.* 任务,不要用 go2_lab.* 代替,应先同步本地 Go1 适配代码。
9.2 单模型评估
直接通过 --model-path 指定 26000 对应的部署版 TorchScript。不要传入训练 checkpoint model_26000.pt,它是包含优化器等状态的字典,不能被 torch.jit.load()。当前平地配置默认启用最大速度和对角速度;命令仍显式传入 --goals,以免配置默认值变化后评测范围静默改变:
cd /opt/RoboGauge
MUJOCO_GL=glfw MPLCONFIGDIR=/tmp/robogauge-matplotlib /opt/envs/robogauge/bin/python robogauge/scripts/run.py \
--task-name go1_lab.flat \
--model-path /opt/RoboGauge/resources/models/go1/policy_robotlab_26000_post_command_curriculum.pt \
--experiment-name go1_robotlab_26000_flat \
--run-name flat_velocity \
--goals max_velocity diagonal_velocity \
--headless
三级楼梯使用目标点任务。--spawn-type level_eval 固定评估出生点,避免和搜索最高等级时的出生点混淆:
MUJOCO_GL=glfw MPLCONFIGDIR=/tmp/robogauge-matplotlib /opt/envs/robogauge/bin/python robogauge/scripts/run.py \
--task-name go1_lab.stairs_fd \
--model-path /opt/RoboGauge/resources/models/go1/policy_robotlab_26000_post_command_curriculum.pt \
--experiment-name go1_robotlab_26000_stairs_fd \
--run-name stairs_fd_l3 \
--level 3 \
--spawn-type level_eval \
--headless
MUJOCO_GL=glfw MPLCONFIGDIR=/tmp/robogauge-matplotlib /opt/envs/robogauge/bin/python robogauge/scripts/run.py \
--task-name go1_lab.stairs_bd \
--model-path /opt/RoboGauge/resources/models/go1/policy_robotlab_26000_post_command_curriculum.pt \
--experiment-name go1_robotlab_26000_stairs_bd \
--run-name stairs_bd_l3 \
--level 3 \
--spawn-type level_eval \
--headless
run.py --headless 在 Linux 默认选择 EGL。本次评估机的 EGL 驱动不支持 PLATFORM_DEVICE,所以命令显式设置 MUJOCO_GL=glfw;GLFW 可能打印无法打开 X11 的 warning,但无窗口仿真仍可完成。如果目标机 EGL 工作正常,可改回 MUJOCO_GL=egl。
本次镜像自检只启用 --goals max_velocity,6 个速度子任务约 12 秒完成,quality_score.mean=0.8402,证明 TorchScript 加载、Go1 XML、MuJoCo 和指标落盘链路可用。该数值不是发布验收阈值,也不能替代下文完整的多目标、多 seed 评估。
单任务的 --task-name 必须是注册表里的完整名称,例如 go1_lab.flat 或 go1_lab.stairs_fd,不能写成裸的 go1_lab。只有下面的 stress pipeline 把 go1_lab 当作机器人任务前缀,再拼接具体地形。
9.3 跨地形压力测试
cd /opt/RoboGauge
MUJOCO_GL=glfw MPLCONFIGDIR=/tmp/robogauge-matplotlib /opt/envs/robogauge/bin/python robogauge/scripts/run.py \
--task-name go1_lab \
--model-path /opt/RoboGauge/resources/models/go1/policy_robotlab_26000_post_command_curriculum.pt \
--experiment-name go1_robotlab_26000_stress \
--stress-benchmark \
--stress-terrain-names flat slope_fd slope_bd stairs_fd stairs_bd wave obstacle \
--num-processes 8 \
--seeds 0 1 2 \
--search-seeds 0 1 2 3 4 \
--frictions 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 \
--compress-logs \
--headless
--num-processes 应根据 CPU 核数和内存调整,不宜盲目设为最大逻辑核心数。
9.4 本次同条件评估结果
下表来自同一 Go1 MuJoCo 模型、go1_lab.* 配置、seed 42 和上述目标参数。所有分数都已归一化为越大越好;楼梯的 success 均为 1。15000 的平地对角速度序列曾发生一次翻倒,pipeline 恢复后继续了其余目标。
| Checkpoint | 平地综合 | 对角速度 | 最大速度 | 三级楼梯前向 | 三级楼梯后向 |
|---|---|---|---|---|---|
| 15000 | 0.7072 | 0.5878 | 0.8265 | 0.6477 | 0.6536 |
| 19500,20k 课程前最后一版 | 0.7246 | 0.6209 | 0.8283 | 0.6311 | 0.5774 |
| 26000 | 0.7933 | 0.7540 | 0.8326 | 0.6410 | 0.6904 |
| 33500,最新完整保存版 | 0.8030 | 0.7558 | 0.8502 | 0.6135 | 0.7009 |
这组结果说明:
- 26000 比 19500 更适应扩大后的速度指令,平地和后向楼梯均明显恢复,整体最均衡。
- 33500 的平地最大速度和后向楼梯最好,但前向上楼梯继续下降,不适合只按最新轮次替换 26000。
- 15000 的前向上楼梯分数最高,但存在一次平地对角命令翻倒,不能仅凭楼梯单项直接上机。
- 这些是单 seed 筛选结果,只用于缩小候选范围。发布前仍需运行多 seed、摩擦、质量和地形压力测试,并结合真实电机温度、电流与保护日志。
RoboGauge 的 dof_power 是归一化质量分,不是瓦特或电机温度。它可以比较同条件 checkpoint,但不能据此断言真机不会过热。
9.5 训练期间异步评估
先启动服务端:
cd /opt/RoboGauge
MPLCONFIGDIR=/tmp/robogauge-matplotlib /opt/envs/robogauge/bin/python robogauge/scripts/server.py --port 9973 --num-processes 8
再启动训练:
cd /opt/go2_rl_robotlab
OMNI_KIT_ACCEPT_EULA=YES /opt/envs/go2_rl_robotlab/bin/python scripts/rsl_rl/train.py \
--task=RobotLab-Go1-v0 \
--num_envs=4096 \
--max_iterations=26000 \
--robogauge \
--robogauge_port=9973 \
--headless
当前 OnPolicyRunnerCTS.update_robogauge() 每 500 轮自动导出一个 JIT 模型并提交评估,但当前 Go1 分支仍将 task_name 硬编码为 go2_lab。训练 Go1 前必须先改成一个已注册的完整任务名,例如平地筛选使用:
task_name="go1_lab.flat"
检查文件为 source/rsl_rl/rsl_rl/runners/on_policy_runner_cts.py。裸的 go1_lab 不是单任务注册名,go2_lab 也会评估错误的机器人。更合理的长期修复是把评估任务写入训练配置,而不是继续硬编码。完成修复前不要启用 --robogauge。异步评估生成的是 jit_models/policy_jit_<ITER>.pt;ONNX 仍需通过 play.py 导出。
9.6 如何选择发布模型
至少比较:
- Tracking:线速度、角速度是否准确。
- Safety:关节范围、功耗、力矩平滑度是否合理。
- Quality:姿态、急停、运动质量是否稳定。
- Level:坡面、台阶、波浪和障碍的最高稳定难度。
- 多 seed 方差:均值高但方差很大的策略不适合直接上机。
10. 独立 MuJoCo sim2sim
这里推荐使用 go1_pro_deploy/deploy_45dim_rl_gym/deploy_go1_onnx_mujoco_lab.py,因为它与 RobotLab 的 10 帧/450 维 ONNX 接口一致。
cd /opt/go1_pro_deploy/deploy_45dim_rl_gym
MUJOCO_GL=glfw /opt/envs/robogauge/bin/python deploy_go1_onnx_mujoco_lab.py \
--onnx /opt/go1_pro_deploy/deploy_45dim_rl_gym/policy_robotlab_26000.onnx
这是带窗口和键盘的交互命令,需要可用的 X11/桌面。Linux 使用普通 python;只有 macOS 才通常需要 mjpython。脚本内部会 chdir 到自身目录,因此 --onnx 使用绝对路径。无图形环境应使用 8.4 节的 --sample 命令。
楼梯测试:
MUJOCO_GL=glfw /opt/envs/robogauge/bin/python deploy_go1_onnx_mujoco_lab.py \
--onnx /opt/go1_pro_deploy/deploy_45dim_rl_gym/policy_robotlab_26000.onnx \
--terrain /opt/go1_pro_deploy/deploy_45dim_rl_gym/terrains/stairs/stairs_3.xml \
--spawn-x -0.6 \
--spawn-z 0.34
键盘映射:
| 按键 | 功能 |
|---|---|
Up / Down |
前进 / 后退 |
Left / Right |
左转 / 右转,即 yaw |
, / . |
左移 / 右移,即 vy |
K |
速度指令清零 |
R |
重置机器人 |
Esc |
退出 |
sim2sim 重点检查:
- 初始零历史时是否平稳进入站姿。
- 四条腿是否对应正确,左右/前后没有交换。
- 正负
vx、vy、wz的方向是否正确。 - 50 Hz 策略周期、PD 参数、默认角和动作缩放是否与训练一致。
- 平地、急停、转向、侧移、斜坡和台阶是否均无明显动作尖峰。
- action、关节速度、机身 roll/pitch 是否出现持续增长。
11. BPU 量化
11.1 量化前提
BPU 量化不是把 ONNX 改一个后缀。至少需要:
- 已通过接口检查的
policy_robotlab_26000.onnx。 - 与部署观测构建逻辑一致的 10 帧/450 维
float32PTQ 输入样本。 - 使用浮点 ONNX 对这批输入执行推理得到的参考输出。
- 浮点 ONNX、图变换 ONNX、量化模型三者的逐样本输出对比。
当前 26000 模型使用 calibration_data_26000_robotlab_fast64/ 中的 64 个 PTQ 校验样本。这批数据来自 ONNX 推理流程,不是 X5 量化产生的模型 .bin。需要重新生成时,先检查供 make_calibration_data.py 使用的 ONNX 推理日志:
find /opt/go1_pro_deploy/logs \
-path '*/robotlab_go1_deploy_*/steps.jsonl' \
-type f -print
make_calibration_data.py 读取日志中的 mode: RL 和 45 维 obs_single,按部署顺序重建 10 帧/450 维历史,并写出工具链所需的 raw float32 校准输入 .bin。这里的 .bin 是 PTQ 输入文件;X5 编译完成后生成的模型也使用 .bin 后缀,但两者用途完全不同。脚本找不到匹配日志或有效样本不足时会中止,不会绕过数据准备继续编译。
当前量化脚本会保留 actions 输出、降级 opset、包装为 BPU 需要的 4D 输入,并把 MoE grouped Conv 等价替换为 Gemm,以减少 CPU fallback。中间 norm 节点位于 actor 之前,不能随意删除或移到后处理。
量化脚本内部流程如下:
ONNX 推理流程日志 steps.jsonl
-> make_calibration_data.py 生成 float32 featuremap 校准样本
-> keep_actions_output.py 只保留 actions 输出
-> downgrade_policy_to_opset11.py 降级到工具链可接受的 opset
-> make_bpu_4d_onnx.py 把 [1, D] 输入包装成 [1, 1, 1, D]
-> replace_group_conv_with_gemm.py 把 MoE grouped Conv 替换为等价 Gemm
-> compare_4d_onnx.py 检查浮点图变换等价
-> hb_mapper / hb_compile 生成板端模型
脚本中的主要可调变量含义:
| 参数 | 含义 | 常用设置 |
|---|---|---|
--policy |
原始 ONNX 路径,必须在部署仓库内 | ../policy_robotlab_26000.onnx |
--round |
输出目录和校准目录里的轮次标签,不参与计算 | 26000、35k |
--name |
输出模型 basename;不填时取 ONNX 文件名 | policy_robotlab_26000 |
--history-len |
历史帧数,用于重建输入维度 | RobotLab 为 10,Gym 为 5 |
--flat-dim |
平铺输入维度;不填时为 45 * history-len |
RobotLab 为 450,Gym 为 225 |
--samples |
校准样本数量,越大越慢但覆盖更稳 | 默认 64,必要时 128 |
--min-samples |
最少有效样本数,不足则中止 | 默认 32 |
--log-prefix |
从 logs/ 下筛选 ONNX 推理日志的目录前缀 |
robotlab_go1_deploy |
--cal-tag |
校准目录标签,区分 RobotLab/Gym | robotlab、gym |
--compare-limit |
浮点 ONNX 等价对比样本数 | 默认 64 |
--quant |
量化策略 | 默认 int16,int8 仅作对照 |
--docker-image |
D-Robotics CPU 工具链镜像 | X5/S100 各自不同 |
量化建议在 Mac 或训练机的 Docker 中完成,不在 X5/S100 板端跑 Docker。本文训练/量化主机的仓库固定为 /opt/go1_pro_deploy;板端只负责加载产物做离线测速、数值对齐和实机部署。
量化脚本内部直接调用 docker。运行 X5/S100 脚本前先验收 daemon:
sudo /opt/envs/go2_rl_robotlab/docker-toolchain/bin/docker info >/dev/null
11.2 RDK X5
X5 使用 D-Robotics X5 CPU 工具链镜像:
sudo /opt/envs/go2_rl_robotlab/docker-toolchain/bin/docker load \
-i /opt/envs/go2_rl_robotlab/docker-toolchain/images/openexplorer_ai_toolchain_ubuntu_20_x5_cpu_v1.2.8.tar
# 或在可联网的 Docker 主机直接拉取:
# sudo /opt/envs/go2_rl_robotlab/docker-toolchain/bin/docker pull openexplorer/ai_toolchain_ubuntu_20_x5_cpu:v1.2.8
脚本默认会用 docker run --rm --platform linux/amd64 启动这个镜像,并把当前仓库挂载到 /workspace/deploy_go1_pro。量化机执行:
cd /opt/go1_pro_deploy
sudo env PATH=/opt/envs/go2_rl_robotlab/docker-toolchain/bin:${PATH} \
bash deploy_45dim_rl_gym/bpu_quantization/quantize_policy_x5.sh \
--policy ../policy_robotlab_26000.onnx \
--round 26000 \
--name policy_robotlab_26000 \
--history-len 10 \
--samples 64 \
--min-samples 32 \
--log-prefix robotlab_go1_deploy \
--cal-tag robotlab \
--compare-limit 64 \
--docker-image openexplorer/ai_toolchain_ubuntu_20_x5_cpu:v1.2.8 \
--quant int16
X5 关键配置:
march固定为bayes-e。- 输入类型为
featuremap,运行时输入 shape 是obs_4d [1, 1, 1, 450]。 - int16 时脚本在 YAML 中写入
optimization: "set_all_nodes_int16"。 hb_mapper checker默认会先运行;明确知道模型已检查过时才使用--skip-checker。
如果要量化旧 Gym 5 帧模型,必须同步修改历史长度、日志前缀和标签:
sudo env PATH=/opt/envs/go2_rl_robotlab/docker-toolchain/bin:${PATH} \
bash deploy_45dim_rl_gym/bpu_quantization/quantize_policy_x5.sh \
--policy ../policy_35k.onnx \
--round 35k \
--history-len 5 \
--log-prefix rlgym_go1_deploy \
--cal-tag gym \
--quant int16
以下是 RDK X5 目标板 命令。板端软件按交付约定安装在 /root/go1_pro_deploy;这是硬件系统路径,不是训练/量化镜像路径,也不应复制回 /opt 镜像执行:
板端先做完全离线自检,不连接电机控制:
cd /root/go1_pro_deploy
PYTHONPATH=/root/go1_pro_deploy \
python3 deploy_45dim_rl_gym/bpu_deploy_x5/test_bpu_policy.py \
--bpu-model deploy_45dim_rl_gym/bpu_quantization/mapper_output_26000_gemm/policy_robotlab_26000_int16_gemm.bin \
--input-bin deploy_45dim_rl_gym/bpu_quantization/calibration_data_26000_robotlab_fast64/00000.bin \
--repeat 1000
X5 的 featuremap 模型不要直接改用 hobot_dnn.pyeasy_dnn.forward()。当前项目默认通过 C++ DNN API,并按 infer、wait、release 的完整生命周期执行,以保证输出和 hrt_model_exec infer 对齐。
11.3 S100
S100 使用新版 S100/S600 CPU 工具链镜像:
sudo /opt/envs/go2_rl_robotlab/docker-toolchain/bin/docker load \
-i /opt/envs/go2_rl_robotlab/docker-toolchain/images/ai_toolchain_ubuntu_22_s100_s600_cpu_v3.7.0.tar
# 或在已登录 registry 的 Docker 主机直接拉取:
# sudo /opt/envs/go2_rl_robotlab/docker-toolchain/bin/docker pull registry.d-robotics.cc/deliver/ai_toolchain_ubuntu_22_s100_s600_cpu:v3.7.0
脚本同样在量化机 Docker 中运行,不在 S100 板端运行 Docker:
cd /opt/go1_pro_deploy
sudo env PATH=/opt/envs/go2_rl_robotlab/docker-toolchain/bin:${PATH} \
bash deploy_45dim_rl_gym/bpu_quantization/quantize_policy_s100.sh \
--policy ../policy_robotlab_26000.onnx \
--round 26000 \
--name policy_robotlab_26000 \
--history-len 10 \
--samples 64 \
--min-samples 32 \
--log-prefix robotlab_go1_deploy \
--cal-tag robotlab \
--compare-limit 64 \
--docker-image registry.d-robotics.cc/deliver/ai_toolchain_ubuntu_22_s100_s600_cpu:v3.7.0 \
--march nash-e \
--quant int16
S100 关键配置:
--march nash-e对应当前 S100 平台;不要沿用 X5 的bayes-e。- 输出是
.hbm,不是 X5 的.bin。 - int16 时脚本在 YAML 中写入
quant_config.model_config.all_node_type: int16。 compiler_parameters.core_num当前为1,优先保证单次策略推理低延迟;需要多核吞吐测试时应另做离线 benchmark,不要直接改实机脚本。
产物同步到 S100 后,先看模型信息再测速:
/usr/hobot/bin/hrt_model_exec model_info \
--model_file deploy_45dim_rl_gym/bpu_quantization/mapper_output_26000_s100_gemm/policy_robotlab_26000_s100_int16_gemm.hbm
板端测速:
/usr/hobot/bin/hrt_model_exec perf \
--model_file deploy_45dim_rl_gym/bpu_quantization/mapper_output_26000_s100_gemm/policy_robotlab_26000_s100_int16_gemm.hbm \
--model_name policy_robotlab_26000_s100_int16_gemm \
--input_file deploy_45dim_rl_gym/bpu_quantization/calibration_data_26000_robotlab_fast64/00000.bin \
--frame_count 1000 \
--thread_num 1
量化验收不能只看平均延迟,还要记录:
- 输入/输出名称、shape 和 dtype。
- held-out 样本的最大绝对误差、平均绝对误差和余弦相似度。
- 最大延迟和抖动是否满足 20 ms 控制周期。
- 是否存在 CPU fallback 或频繁 BPU/CPU 子图切换。
- 连续推理是否有资源泄漏、错误日志或偶发异常动作。
12. 实机部署边界
12.1 SDK 保密说明
实际部署使用私有 Go1 PRO SDK。本文只描述公开的控制契约和安全设计,不记录私有协议格式、加密材料、逆向过程或专有实现。需要公开复现时,应以 Unitree 官方 unitree_legged_sdk 的 UDP、LowState、LowCmd 和 Safety 接口为基线重新实现适配层。
无论 SDK 如何实现,策略层与硬件层之间必须显式完成:
- 状态包有效性、时间戳和丢包检查。
- 电机顺序到
FR, FL, RR, RL策略顺序的映射。 - IMU 四元数、角速度坐标系和单位转换。
- 关节位置、速度、上一动作和指令的缩放。
- ONNX 历史缓存的初始化、更新和复位。
- 动作缩放、默认角叠加、目标限幅和变化率限制。
- PD 控制、安全检查、发送周期和退出阻尼。
公开 SDK 可用于核对通用 API、结构体字段和安全调用方式,但不要直接运行带位置目标的官方示例。先只编译:
git clone https://github.com/unitreerobotics/unitree_legged_sdk.git /opt/unitree_legged_sdk
cmake -S /opt/unitree_legged_sdk -B /opt/unitree_legged_sdk/build
cmake --build /opt/unitree_legged_sdk/build --parallel
本地 walk-these-ways/go1_gym_deploy/unitree_legged_sdk_bin/lcm_position.cpp 可作为官方 SDK 接入策略控制环的公开参考。阅读时重点关注 UDP 收发线程、LowState 到策略观测的转换、Safety 调用和退出流程,不要照搬其网络地址、增益或速度范围到另一台机器人。
12.2 硬件、接线与网络
本节按维护者要求留空,由实际设备负责人填写。部署前不得跳过。
TODO(维护者填写)
1. Go1 具体型号与固件版本:
2. S100/RDK X5 型号、系统镜像与供电:
3. 机器人、主控、交换机/网卡接线图:
4. 网口名称、静态 IP、子网掩码和路由:
5. 遥控器型号及按键映射:
6. 吊架/保护绳安装方式和承重:
7. 物理急停、断电和现场人员分工:
8. 启停 sport mode 的设备专用步骤:
13. 软件安全策略
13.1 控制权互斥
低层控制前必须确认原厂 sport 进程已停止,避免两个控制源同时发送命令。不要在机器人站立或行走时直接停止 sport mode,先让机器人处于受支撑或安全趴卧状态。
检查示例:
pgrep -fl 'keep_sport_alive|Legged_sport|appTransit'
恢复原厂控制前,先结束自定义低层进程并确认已经发送阻尼停机,再恢复 sport mode。
13.2 启动守卫
- 默认模式只能读取状态,不能发送 RL 关节目标。
- 必须有显式
--enable-rl才允许进入 RL 状态。 - 收到第一帧有效、完整且时间连续的 LowState 前,只发送停止哨兵或保持阻尼。
- 检查电机顺序、模型 shape、NaN/Inf、历史长度和控制频率。
- RL 刚启用时先预热历史;预热期间保持默认站姿。
- 从默认站姿向 RL 目标渐变,限制每周期目标变化量。
13.3 每周期保护
每个 20 ms 策略周期至少检查:
- 状态包是否超时、丢失或校验失败。
- 关节位置是否超硬限位或安全软限位。
- 关节速度是否超过部署阈值。
- 估计力矩和命令力矩是否超过电机限制。
- action 是否有限、是否超过软/硬跳闸阈值。
- 新关节目标与当前目标、实测位置的差值是否过大。
- IMU 角速度以及 roll/pitch 是否超过阈值。
- 推理时间和整个控制循环是否超过 20 ms deadline。
- 电量、温度、通信状态和错误码是否正常。
任一关键检查失败应进入 FAULT 或 IDLE damping,清零历史和上一动作。禁止捕获异常后静默继续运行。
13.4 力矩、速度与位置限制
训练中的 33.5 Nm 是仿真执行器上限,不等于真机调试时应直接允许的命令。首次测试应从官方安全接口允许的保守比例开始,并根据日志逐级放开。
位置控制命令即使命令前馈力矩为零,也会通过 PD 误差产生实际力矩,因此:
- 增大
Kp、目标跳变量或目标与实测位置差都可能造成力矩尖峰。 power_factor不能替代实测tauEst保护。- 触发力矩保护时,应优先减小目标变化、速度指令或 PD 增益,不能只提高保护阈值。
- 关节硬限位、位置偏差保护、速度保护和力矩保护必须同时存在。
13.5 停机路径
至少准备三条独立路径:
- 遥控器软急停:当前状态机使用
L2回到IDLE damping。 Ctrl+C:捕获后连续发送多帧阻尼命令再退出。- 物理断电:现场人员随时可以执行,且不依赖已被停止的原厂进程。
急停触发后不得自动恢复到 RL。必须重新检查故障原因,从校准或保持状态重新开始。
14. 分级实机测试流程
以下步骤必须按顺序通过。任何异常都应保存日志并停止,不要连续试错。
14.1 第 0 级:不上电机的离线检查
- 用相同 450 维输入比较 PyTorch、ONNX 和 BPU 输出。
- 检查 12 维动作的顺序、符号、范围和有限性。
- 测量平均、P99 和最大推理时间。
- 对零输入、PTQ 校验输入和边界输入做回归。
14.2 第 1 级:只读 LowState
只连接 MCU 并记录 IMU、12 个关节位置/速度/力矩、电量、错误码和遥控器,不发送电机目标。手动缓慢移动悬空的腿,确认索引、符号和单位。
14.3 第 2 级:只构建观测
构建 45 维单帧和 450 维历史,但不运行策略。逐项核对:
- 静止水平时投影重力方向正确。
- 手动旋转机身时角速度轴和符号正确。
- 手动移动单腿时仅对应三个关节变化。
- 零速度命令为
[0, 0, 0]。 - 历史从零初始化,并按正确顺序滑动更新。
14.4 第 3 级:只推理不发命令
运行 ONNX/BPU 推理并记录 action,仍不发送电机命令。检查 action 无 NaN/Inf、无持续饱和、静止状态不发散,ONNX 和 BPU 的动作差在离线验收范围内。
从这一级开始,命令必须显式保留 --log-dir logs;只要要判断板端实时性,就同时加 --log-timing。日志目录会在启动时自动创建,终端退出时会打印 Log saved: 路径。
X5 示例:
cd /root/go1_pro_deploy
PYTHONPATH=/root/go1_pro_sdk:/root/go1_pro_deploy \
python3 deploy_45dim_rl_gym/bpu_deploy_x5/deploy_go1_robotlab_bpu_x5_fastcpp.py \
--infer-check \
--log-dir logs \
--print-every 50 \
--max-steps 500 \
--log-timing
S100 示例:
cd /root/go1_pro_deploy
PYTHONPATH=/root/go1_pro_sdk:/root/go1_pro_deploy \
python3 deploy_45dim_rl_gym/bpu_deploy_s100/deploy_go1_robotlab_bpu_s100_fastcpp.py \
--infer-check \
--log-dir logs \
--print-every 50 \
--max-steps 500 \
--log-timing
14.5 第 4 级:悬空状态机,不启用 RL
机器人必须可靠悬挂,腿下和侧面无人。先不加 --enable-rl,验证状态机:
IDLE -> CALIBRATE -> HOLD -> OBS_TEST -> INFER_TEST
R2每次只前进一层。L2在任意激活状态回到IDLE damping。- 未加
--enable-rl时,INFER_TEST -> RL必须被守卫拒绝。 Ctrl+C必须进入阻尼后退出。
14.6 第 5 级:悬空 RL,零速度命令
只有前四级通过后才能增加 --enable-rl。第一轮禁用摇杆速度输入,只验证站姿、动作方向、关节速度、力矩和急停。观察 5 到 10 秒后主动回到 HOLD,不要长时间连续运行。
14.7 第 6 级:悬空 RL,小幅指令
一次只测试一个方向:小 vx、小 vy、小 wz。每次测试后回零,检查正负方向和动作对称性。逐个参数变化,不要同时修改 PD、action clip、目标变化率和遥控比例。
14.8 第 7 级:平整地面低速
- 使用保护绳,现场一人操控、一人负责断电。
- 从零指令站立开始,再给短时小速度。
- 首轮只测试前进、停止和软急停。
- 之后再测试后退、侧移和转向。
- 每轮结束检查电机温度、日志和机械结构。
14.9 第 8 级:ONNX/BPU 对照
先用已经通过地面测试的 ONNX 版本确定基线,再在相同软件保护、相同指令、相同场地上测试 BPU。两次测试只替换推理后端。若 BPU 行为异常,回到离线输入逐帧对比,不能通过放宽关节或姿态保护继续测试。
对照日志至少要能证明三件事:同一段动作里 commands 接近一致,action_raw/action_safe/joint_targets 的差异在离线验收范围内,policy_ms/loop_dt_ms/state_age_ms 没有让 BPU 版本比 ONNX 版本产生额外控制延迟。若 BPU 端 policy_ms 很低但 loop_dt_ms 仍不稳,应继续查 SDK 收包、构包和系统调度,而不是只看 BPU 占用率。
14.10 第 9 级:坡面、台阶和扰动
复杂地形是最后阶段。先从低难度、低速度和正向通过开始,再逐步测试侧向、反向、急停和摩擦变化。每次增加难度前必须确认前一级多次可复现。
15. 日志与验收
每次实机运行至少保存:
metadata.json
steps.jsonl
部署脚本默认在 --log-dir 下创建带时间戳的目录,例如:
logs/robotlab_go1_deploy_YYYYMMDD_HHMMSS/
不要关闭日志来“省性能”。实机调试阶段宁可降低打印频率,也要保留 JSONL 日志;后续量化校准、ONNX/BPU 对齐、力矩保护分析和动作异常定位都依赖这些记录。
metadata.json 用来复现实验条件。当前部署脚本会自动写入:
created_at
num_obs / history_len / onnx_input_dim
action_scale / default_dof_pos
joint_names_sdk / joint_names_policy / joint_order_policy
lowcmd_backend 或 BPU backend
history_expected_span_ms
命令行传入的所有标量参数
发布或多人复现实验时,还应额外记录 Git commit、模型哈希、板端系统版本、BPU runtime 版本、设备型号、测试人员和场景。只给一条 Log saved: 路径而没有对应模型与参数,后续无法判断问题来自策略、量化、SDK 还是现场操作。
steps.jsonl 是逐周期日志。当前脚本每条记录至少包含:
step / time_wall / mode / state_reason
commands_raw / commands / rc_lx / rc_ly / rc_rx / rc_ry / rc_buttons
obs_single / history_fill / history_zero_pad
action_raw / action_safe
joint_targets
dof_pos / dof_vel / tau_est / motor_mode / motor_temperature / motor_reserve
imu_rpy_deg / imu_quat / base_ang_vel / projected_gravity
battery_soc
loop_dt_ms / recv_ms / policy_ms / work_ms / state_fresh / state_age_ms
这些字段的用途:
mode和state_reason:判断处于MONITOR、OBS_CHECK、INFER_CHECK、RAMP、RL还是故障/阻尼路径;保护触发时首先看这里。commands_raw和commands:区分遥控器原始输入、deadzone/比例/交换后的策略输入,避免把遥控映射问题误判成模型问题。obs_single、history_fill、history_zero_pad:检查 45 维观测和历史缓存是否正确预热;刚进 RL 时历史未填满属于高风险段。action_raw和action_safe:区分模型原始输出与 clip、smooth、trip 检查后的实际动作;若二者差异大,说明部署端限制正在影响行为。joint_targets、dof_pos、dof_vel、tau_est:分析目标角、实际角、速度和力矩保护之间的因果关系。motor_mode、motor_reserve、motor_temperature:定位电机是否掉出 servo、是否有错误码或过热。loop_dt_ms、recv_ms、policy_ms、work_ms、state_age_ms:判断慢在推理、收包、构包发送还是系统调度。
分析日志时不要只看最后一帧。应从 state_reason 第一次不为 ok、action_safe 第一次明显被 clip、motor_mode 第一次异常、tau_est 第一次接近阈值、loop_dt_ms 第一次超过 20 ms 的位置向前回看至少 1 到 2 秒。
一次测试通过的最低条件:
- 无 NaN/Inf、状态超时和控制周期持续超期。
- 电机顺序、坐标系和指令方向全部正确。
- 无硬限位、速度、力矩、姿态或动作异常跳闸。
L2、Ctrl+C和物理断电路径均已由现场演练确认。- ONNX/BPU 输出差异与离线结果一致。
- 行为可在相同条件下重复,而不是偶然成功一次。
16. 常见问题
16.1 策略在 IsaacLab 正常,MuJoCo 立即摔倒
优先检查关节顺序、默认关节角、观测历史排列、IMU 四元数格式、动作缩放、PD 参数和策略周期。不要先调奖励或增加力矩。
16.2 ONNX 输出维度正确但动作异常
检查输入是 450 维按观测项分组的历史,而不是 10 个单帧直接拼接;同时确认输入已经应用训练时的缩放,上一动作使用的是实际送入策略链路的动作定义。
16.3 BPU 很快,但真机表现比 ONNX 差
速度合格不代表数值合格。使用同一批 PTQ 校验输入逐帧比较 ONNX/BPU,重点查 featuremap 输入、量化校准分布、输出 reshape、CPU fallback 和 runtime 调用生命周期。
16.4 TensorBoard 总回报上涨,但 RoboGauge 下降
先检查是否跨过 20000 或 50000 轮的速度指令课程切换。课程变难后总回报下降、非法接触短时上升是预期现象,旧课程和新课程的绝对回报不能直接比较。若命令范围和地形等级都未变化,才进一步排查训练奖励偏向速度跟踪、牺牲力矩/姿态/关节裕度或后期 checkpoint 对训练域过拟合。无论哪种情况,都应比较分项指标和多个 checkpoint,不要只保留最后一轮。
16.5 真机一进入 RL 就力矩保护
立即停机。依次检查动作方向、目标角、目标单步变化、PD 增益、当前位置偏差、观测缩放和历史初始化。不要把提高 power_factor 作为第一反应。
17. 发布前检查清单
- RoboGo 镜像名称、拉取命令、容器启动参数和持久化目录已补全;当前文档中的阻断项不再是 TODO。
/opt/IsaacLab为v2.3.2,pip 环境为 IsaacLab2.3.2.post1、Isaac Sim5.1.0.0、PyTorch2.7.0,且没有重新挂载旧/opt/IsaacSim。- 运行用户可写训练、RoboGauge、MuJoCo 和 Isaac Sim runtime 目录,且
OMNI_KIT_ACCEPT_EULA=YES已注入。 - 镜像 1 轮训练自检通过,并保存
model_0.pt、日志和参数文件。 - 硬件、接线、网络和急停章节已由设备负责人补全。
- 所有仓库 commit 和 Python 依赖已记录。
- 15000、19500、26000、33500 候选已注明课程阶段,并完成 TensorBoard 与 RoboGauge 对比。
- 最终选定 checkpoint 已完成 IsaacLab play、TorchScript/ONNX 导出及哈希记录。
- ONNX 输入 450、输出 12,关节顺序为
FR, FL, RR, RL。 - RoboGauge 单任务使用完整的
go1_lab.*注册名,而非裸go1_lab或go2_lab。 - MuJoCo 平地、转向、侧移、急停和台阶测试通过。
- PTQ 输入、ONNX 参考输出与模型版本一致。
- X5/S100 量化模型通过离线精度和延迟验收。
- X5/S100 工具链归档已通过 SHA-256 校验,并在具备 Docker daemon 的主机完成
docker load。 - 私有 SDK 的协议、密钥和专有实现未写入公开文档。
- 实机测试严格完成只读、观测、推理、状态机、悬空、地面分级流程。
- 每条停机路径均已实际演练。
18. 参考资料
- RoboGauge 项目主页
- Toward Reliable Sim-to-Real Predictability for MoE-based Robust Quadrupedal Locomotion
- RoboGauge
- go2_rl_robotlab
- IsaacLab 2.3.2 安装文档
- Unitree unitree_legged_sdk
- 本地
go2_rl_robotlab/docs/go1.md - 本地
RoboGauge/assets/docs/go1_policy_io_zh.md - 本地
go1_pro_deploy/deploy_45dim_rl_gym/README.md - 本地
go1_pro_deploy/deploy_45dim_rl_gym/bpu_quantization/README.md - 本地
go1_pro_deploy/deploy_45dim_rl_gym/bpu_deploy_x5/README.md - 本地
go1_pro_deploy/deploy_45dim_rl_gym/bpu_deploy_s100/README.md