Files
dog_train_deploy_guide_doc/四足机器狗训练到实机部署全流程指南.md
2026-07-31 16:46:58 +08:00

1044 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 四足机器狗从训练到实机部署全流程指南
> 本教程基于西安交通大学 RoboGauge 团队的研究成果 [Toward Reliable Sim-to-Real Predictability for MoE-based Robust Quadrupedal Locomotion](https://robogauge.github.io/static/files/arxiv.pdf) 及其[项目主页](https://robogauge.github.io/complete/),将原始 Go2 训练任务适配为 Unitree Go1并串联 IsaacLab 训练、RoboGauge 评估、MuJoCo sim2sim、ONNX 导出、RDK X5/S100 BPU 量化和 sim2real。
>
> 本文依据当前本地仓库代码整理。编写文档的计算机不是训练机或部署机,因此下面的安装、训练、量化和实机命令**尚未在本文编写环境中执行验证**。实际操作时应逐条执行、逐条记录,不要整段复制后无人值守运行。
## 1. 教程范围与版本约定
### 1.1 仓库
| 用途 | 仓库或目录 |
| --- | --- |
| IsaacLab 训练与自带 MuJoCo 部署 | `go2_rl_robotlab``go1` 分支 |
| 自动化 sim2sim 评估 | `RoboGauge``go1` 分支 |
| 独立 MuJoCo、ONNX、X5/S100 BPU 部署 | `go1_pro_deploy/deploy_45dim_rl_gym` |
| 官方公开 SDK | [unitree_legged_sdk](https://github.com/unitreerobotics/unitree_legged_sdk) |
| 私有 Go1 PRO SDK | 仅在内部部署机使用,本文不公开协议、密钥和专有接口 |
本教程统一推荐 **26000 轮检查点**,示例名称如下:
```text
训练检查点: model_26000.pt
浮点模型: policy_robotlab_26000.onnx
X5 模型: policy_robotlab_26000_int16_gemm.bin
S100 模型: policy_robotlab_26000_s100_int16_gemm.hbm
```
26000 只是当前推荐基线,不表示轮数越大一定越好。最终模型仍应根据 RoboGauge、MuJoCo 回放和真机日志共同选择。
### 1.2 全流程
```text
Go1 模型与任务适配
-> IsaacLab + MoE-CTS 训练
-> TensorBoard 观察收敛
-> 导出 TorchScript / ONNX
-> IsaacLab play
-> RoboGauge 自动评估
-> 独立 MuJoCo sim2sim
-> ONNX 实机只读和悬空测试
-> 用真实日志制作 BPU 校准集
-> X5/S100 量化与离线对齐
-> BPU 悬空对照
-> 低速地面测试
-> 复杂地形测试
```
任何阶段不通过,都应退回上一阶段定位问题,不能用放宽真机保护阈值来掩盖观测、关节顺序或模型接口错误。
## 2. 研究方法解读
### 2.1 为什么使用 MoE-CTS
该流程的核心不是只追求仿真中的高回报,而是提高策略在未知动力学和地形上的泛化能力,并用标准化指标提前判断 sim2real 风险。
- **CTSConcurrent Teacher-Student**teacher 可使用地形高度、接触力、关节力矩等特权信息student 仅使用真机可获得的本体感知历史。两者在训练期间并行优化,使 student 学会从历史观测中估计隐含环境信息。
- **MoEMixture of Experts**:策略包含多个 expert由门控网络根据隐变量组合不同 expert。不同 expert 可以分别覆盖平地、坡面、台阶、低摩擦和动力学扰动等状态。
- **历史观测**:单帧传感信息不能直接给出摩擦、延迟和外部扰动,连续历史为 student 提供了估计这些量的依据。
- **域随机化与课程学习**:训练中改变地形、摩擦、质量、执行器延迟和外力,并随能力提升逐渐增加地形和指令难度。
### 2.2 奖励、评估和部署的职责不同
训练奖励用于产生梯度,并不是最终安全指标。高总奖励可能由速度跟踪项主导,同时仍存在极端关节、力矩尖峰或急停不稳。因此本流程分三层判断:
1. **训练奖励**:判断优化是否收敛、是否发生策略坍塌。
2. **RoboGauge 指标**:在跨地形、跨摩擦、多随机种子下比较跟踪、安全和运动质量。
3. **真机保护与日志**:判断实时观测、动作、关节目标和硬件状态是否处于允许范围。
RoboGauge 当前关注的指标包括关节软限位、线/角速度误差、电机功耗、姿态稳定性、力矩平滑度、摩擦裕度和 ZMP 裕度。归一化后均按“越大越好”解释,但任何综合分数都不能替代分项检查。
## 3. 训练环境
### 3.1 硬件与软件前提
- NVIDIA GPU 和匹配的驱动。
- Linux 训练环境;图形界面训练不是必须。
- Python 3.11。
- IsaacLab/Isaac Sim 2.3.2 对应依赖。
- 足够的磁盘空间保存检查点、TensorBoard 日志和导出模型。
### 3.2 RoboGo 镜像
此处由维护者补充平台镜像信息:
```text
TODO维护者填写
RoboGo 镜像名称:
镜像拉取命令:
容器启动参数:
训练数据持久化目录:
```
若镜像已包含 IsaacLab、项目仓库和依赖可跳过下一节的手工安装但仍需核对代码分支和版本。
### 3.3 手工安装
```bash
conda create -n go2_rl_robotlab python=3.11 -y
conda activate 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
```
拉取 Go1 分支并安装项目内定制包:
```bash
git clone --branch go1 --single-branch https://github.com/wertyuilife2/go2_rl_robotlab.git
cd go2_rl_robotlab
python -m pip install -e source/robot_lab
python -m pip install -e source/rsl_rl
python -m pip install mujoco pygame onnx onnxruntime tensorboard
```
记录环境,便于在部署前复现:
```bash
git rev-parse HEAD
python --version
python -m pip freeze > requirements-lock.txt
nvidia-smi
```
## 4. 从 Go2 适配到 Go1
本地 `go1` 分支的关键提交为 `0d2afbf feat: add Go1 training and MuJoCo deployment`。适配不只是替换 URDF至少包括以下内容
1. 添加完整的 Go1 URDF、MuJoCo XML 和 mesh。
2. 定义 Go1 执行器、默认关节角、PD 参数和力矩限制。
3. 新建 `RobotLab-Go1-v0` 的场景、观测、动作、奖励、随机化和课程配置。
4. 注册 Go1 任务及 MoE-CTS runner 配置。
5. 添加 Go1 的 MuJoCo 配置和部署入口。
6. 在所有模块中统一关节顺序与策略输入输出。
可用下面的命令查看完整改动:
```bash
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 维单帧观测顺序为:
```text
机身角速度(3)
+ 投影重力(3)
+ 速度指令 vx/vy/wz(3)
+ 相对默认角度的关节位置(12)
+ 关节速度(12)
+ 上一时刻动作(12)
= 45
```
RobotLab ONNX 的历史按“观测项分组后堆叠”,不是简单地将 10 个 45 维帧首尾拼接:
```text
[角速度 10 帧, 重力 10 帧, 指令 10 帧,
关节位置 10 帧, 关节速度 10 帧, 上一动作 10 帧]
```
不要把旧 RL-Gym 的 5 帧/225 维 ONNX、RobotLab 的 10 帧/450 维 ONNX、内部维护历史的 45 维 TorchScript 混用。即使程序没有立即报错,错误的历史排列也会让输出失去意义。
## 5. 奖励函数与环境设计
Go1 奖励配置位于:
```text
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 部署中的延迟参数按策略步计量,两者单位不同。
## 6. 开始训练
### 6.1 默认参数的含义
当前默认配置:
```text
num_envs = 4096
num_steps_per_env = 24
save_interval = 500
max_iterations = 300000通常通过命令行覆盖
```
每次 PPO 更新的 rollout 数量约为:
```text
4096 * 24 = 98304 个 transition
```
`num_envs` 主要影响仿真吞吐、显存和每轮采样量;`num_steps_per_env` 同时影响 rollout 时间跨度、buffer 显存和更新统计。优先保持 `num_steps_per_env=24`,通过 `--num_envs` 适配 GPU。
建议从小到大试探:
```bash
python scripts/rsl_rl/train.py --task=RobotLab-Go1-v0 --num_envs=512 --max_iterations=20 --headless
python scripts/rsl_rl/train.py --task=RobotLab-Go1-v0 --num_envs=1024 --max_iterations=20 --headless
python scripts/rsl_rl/train.py --task=RobotLab-Go1-v0 --num_envs=2048 --max_iterations=20 --headless
python scripts/rsl_rl/train.py --task=RobotLab-Go1-v0 --num_envs=4096 --max_iterations=20 --headless
```
另开终端观察:
```bash
watch -n 1 nvidia-smi
```
选择能稳定运行且留有显存余量的最大环境数。若 OOM先减小 `num_envs`;只有明确理解 PPO batch 变化后,再修改 `rsl_rl_cfg.py` 中的 `num_steps_per_env`、mini-batch 和 epoch 数。
### 6.2 正式训练到 26000 轮
```bash
cd go2_rl_robotlab
conda activate go2_rl_robotlab
python scripts/rsl_rl/train.py \
--task=RobotLab-Go1-v0 \
--num_envs=4096 \
--max_iterations=26000 \
--run_name=go1_26000 \
--logger=tensorboard \
--headless
```
日志默认位于:
```text
logs/rsl_rl/go1_moe_cts/<时间戳>_go1_26000/
```
检查点每 500 轮保存一次。确认 26000 轮文件存在:
```bash
find logs/rsl_rl/go1_moe_cts -name 'model_26000.pt' -print
```
### 6.3 断点续训
```bash
python 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 观察训练
训练机执行:
```bash
tensorboard --logdir logs/rsl_rl --port 6006
```
从本机通过 SSH 查看远端 TensorBoard
```bash
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/*` | 各奖励项、课程难度和终止统计 |
不要只选平均回报最高的 checkpoint。建议将 15000、20000、23000、26000 等候选同时送入 RoboGauge比较综合分数和分项指标后再决定。
## 8. 评估与导出模型
### 8.1 IsaacLab play 与导出
`play.py` 加载 checkpoint 后,会先在同一 run 的 `exported/` 下生成 `policy.pt``policy.onnx`,再进入可视化 rollout
```bash
python scripts/rsl_rl/play.py \
--task=RobotLab-Go1-v0 \
--num_envs=64 \
--checkpoint=/absolute/path/to/model_26000.pt
```
输出:
```text
<RUN_DIR>/exported/policy.pt
<RUN_DIR>/exported/policy.onnx
```
导出文件出现后才能终止 play。为了后续名称统一
```bash
cp <RUN_DIR>/exported/policy.onnx /path/to/go1_pro_deploy/deploy_45dim_rl_gym/policy_robotlab_26000.onnx
cp <RUN_DIR>/exported/policy.pt /path/to/RoboGauge/resources/models/go1/policy_robotlab_26000.pt
```
### 8.2 不打断训练地导出
训练 runner 保存的是可恢复训练的 checkpoint不会默认在每次保存时生成 ONNX。要保持主训练进程运行可在另一块 GPU、另一台机器或训练显存允许时启动独立 `play.py`
```bash
CUDA_VISIBLE_DEVICES=1 python scripts/rsl_rl/play.py \
--task=RobotLab-Go1-v0 \
--num_envs=1 \
--checkpoint=/absolute/path/to/model_26000.pt \
--headless
```
注意:
- 只处理已经完整写完的 checkpoint不要复制正在写入的文件。
- 同一 GPU 显存不足时会影响训练,应在第二块 GPU 或离线机器导出。
- `play.py` 导出后仍会进入仿真循环;看到导出文件后用 `Ctrl+C` 结束该独立进程,不会中断训练进程。
### 8.3 检查 ONNX 接口
```bash
python -c "import onnxruntime as ort; s=ort.InferenceSession('policy_robotlab_26000.onnx'); 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()])"
```
预期输入为 450 个 `float32`,输出为 12 个动作。输入输出不匹配时不要继续 sim2sim 或量化。
### 8.4 独立 MuJoCo 快速检查
如果你要做的是“和部署链路一致”的快速检查,直接使用部署仓库里的 ONNX 入口。它读取当前 45 维单帧观测,并在脚本内部维护 10 帧历史,适合在进入完整 RoboGauge 评估前核对观测、动作缩放和地形 XML
```bash
cd /path/to/go1_pro_deploy
MUJOCO_GL=glfw mjpython deploy_45dim_rl_gym/deploy_go1_onnx_mujoco_lab.py \
--onnx deploy_45dim_rl_gym/policy_robotlab_26000.onnx
```
楼梯测试同样直接切 terrain
```bash
MUJOCO_GL=glfw mjpython deploy_45dim_rl_gym/deploy_go1_onnx_mujoco_lab.py \
--onnx deploy_45dim_rl_gym/policy_robotlab_26000.onnx \
--terrain deploy_45dim_rl_gym/terrains/stairs/stairs_3.xml \
--spawn-x -0.6 \
--spawn-z 0.34
```
脚本默认 `--clip-actions 100.0``--max-target-step 0.0`。如果你要做更保守的起步测试,可以再按日志逐步收紧这些参数;不要把旧版本的示例值当成固定默认。
## 9. RoboGauge 自动评估
### 9.1 安装
建议使用独立环境,因为当前 RoboGauge 固定了 `mujoco==3.2.3``dm_control==1.0.23``numpy<2`
```bash
conda create -n robogauge python=3.10 -y
conda activate robogauge
git clone https://github.com/wty-yy/RoboGauge.git
cd RoboGauge
python -m pip install -e .
```
公开仓库作为基线使用。本文所需的 `go1_lab.*` 任务来自本地修改版的 `go1` 分支;在新评估机上还必须同步该分支或应用对应提交。安装后执行下面的命令确认任务注册,输出中必须出现 `go1_lab.flat``go1_lab.stairs_fd` 等任务:
```bash
git branch --show-current
python -c "import robogauge.tasks; from robogauge.utils.task_register import task_register; print('\\n'.join(sorted(task_register.pipeline_classes)))"
```
若不存在 `go1_lab` 任务,不要用 `go2_lab` 代替,应先同步本地 Go1 适配代码。
### 9.2 单模型与压力测试
直接通过 `--model-path` 指定 26000 对应的 TorchScript
```bash
python robogauge/scripts/run.py \
--task-name go1_lab.flat \
--model-path resources/models/go1/policy_robotlab_26000.pt \
--experiment-name go1_robotlab_26000_flat \
--headless
```
跨地形压力测试:
```bash
python robogauge/scripts/run.py \
--task-name go1_lab \
--model-path resources/models/go1/policy_robotlab_26000.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 32 \
--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.3 训练期间异步评估
先启动服务端:
```bash
conda activate robogauge
cd RoboGauge
python robogauge/scripts/server.py --port 9973 --num-processes 32
```
再启动训练:
```bash
conda activate go2_rl_robotlab
cd go2_rl_robotlab
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 模型并提交评估,但本地版本中 `task_name` 曾硬编码为 `go2_lab`。训练 Go1 前必须确认该处为:
```python
task_name="go1_lab"
```
检查文件为 `source/rsl_rl/rsl_rl/runners/on_policy_runner_cts.py`。否则会用错误的机器人任务评估 Go1 模型。异步评估生成的是 `jit_models/policy_jit_<ITER>.pt`ONNX 仍需通过 `play.py` 导出。
### 9.4 如何选择 26000 模型
至少比较:
- 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 接口一致。
```bash
cd /path/to/go1_pro_deploy
conda activate <DEPLOY_ENV>
MUJOCO_GL=glfw mjpython deploy_45dim_rl_gym/deploy_go1_onnx_mujoco_lab.py \
--onnx deploy_45dim_rl_gym/policy_robotlab_26000.onnx
```
楼梯测试:
```bash
MUJOCO_GL=glfw mjpython deploy_45dim_rl_gym/deploy_go1_onnx_mujoco_lab.py \
--onnx deploy_45dim_rl_gym/policy_robotlab_26000.onnx \
--terrain 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 改一个后缀。至少需要:
1. 已通过接口检查的 `policy_robotlab_26000.onnx`
2. 来自相同观测构建逻辑的真实部署日志。
3. 从 RL 状态提取的 10 帧/450 维 `float32` 校准样本。
4. 浮点 ONNX、图变换 ONNX、量化模型三者的逐样本输出对比。
当前量化脚本会保留 actions 输出、降级 opset、包装为 BPU 需要的 4D 输入,并把 MoE grouped Conv 等价替换为 Gemm以减少 CPU fallback。中间 norm 节点位于 actor 之前,不能随意删除或移到后处理。
量化脚本内部流程如下:
```text
真实日志 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/` 下筛选部署日志的目录前缀 | `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。板端只负责加载产物做离线测速、数值对齐和实机部署。
### 11.2 RDK X5
X5 使用 D-Robotics X5 CPU 工具链镜像:
```bash
docker pull openexplorer/ai_toolchain_ubuntu_20_x5_cpu:v1.2.8
```
脚本默认会用 `docker run --rm --platform linux/amd64` 启动这个镜像,并把当前仓库挂载到 `/workspace/deploy_go1_pro`。量化机执行:
```bash
cd /path/to/go1_pro_deploy
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 \
--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`
预期产物:
```text
deploy_45dim_rl_gym/bpu_quantization/mapper_output_26000_gemm/
policy_robotlab_26000_int16_gemm.bin
```
如果要量化旧 Gym 5 帧模型,必须同步修改历史长度、日志前缀和标签:
```bash
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
```
板端先做完全离线自检,不连接电机控制:
```bash
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 工具链镜像:
```bash
docker pull registry.d-robotics.cc/deliver/ai_toolchain_ubuntu_22_s100_s600_cpu:v3.7.0
```
脚本同样在量化机 Docker 中运行,不在 S100 板端运行 Docker
```bash
cd /path/to/go1_pro_deploy
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 \
--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不要直接改实机脚本。
预期产物:
```text
deploy_45dim_rl_gym/bpu_quantization/mapper_output_26000_s100_gemm/
policy_robotlab_26000_s100_int16_gemm.hbm
```
产物同步到 S100 后,先看模型信息再测速:
```bash
/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
```
板端测速:
```bash
/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、结构体字段和安全调用方式但不要直接运行带位置目标的官方示例。先只编译
```bash
git clone https://github.com/unitreerobotics/unitree_legged_sdk.git
cd unitree_legged_sdk
mkdir build
cd build
cmake ..
cmake --build . --parallel
```
本地 `walk-these-ways/go1_gym_deploy/unitree_legged_sdk_bin/lcm_position.cpp` 可作为官方 SDK 接入策略控制环的公开参考。阅读时重点关注 UDP 收发线程、`LowState` 到策略观测的转换、`Safety` 调用和退出流程,不要照搬其网络地址、增益或速度范围到另一台机器人。
### 12.2 硬件、接线与网络
> 本节按维护者要求留空,由实际设备负责人填写。部署前不得跳过。
```text
TODO维护者填写
1. Go1 具体型号与固件版本:
2. S100/RDK X5 型号、系统镜像与供电:
3. 机器人、主控、交换机/网卡接线图:
4. 网口名称、静态 IP、子网掩码和路由
5. 遥控器型号及按键映射:
6. 吊架/保护绳安装方式和承重:
7. 物理急停、断电和现场人员分工:
8. 启停 sport mode 的设备专用步骤:
```
## 13. 软件安全策略
### 13.1 控制权互斥
低层控制前必须确认原厂 sport 进程已停止,避免两个控制源同时发送命令。不要在机器人站立或行走时直接停止 sport mode先让机器人处于受支撑或安全趴卧状态。
检查示例:
```bash
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 停机路径
至少准备三条独立路径:
1. 遥控器软急停:当前状态机使用 `L2` 回到 `IDLE damping`
2. `Ctrl+C`:捕获后连续发送多帧阻尼命令再退出。
3. 物理断电:现场人员随时可以执行,且不依赖已被停止的原厂进程。
急停触发后不得自动恢复到 RL。必须重新检查故障原因从校准或保持状态重新开始。
## 14. 分级实机测试流程
以下步骤必须按顺序通过。任何异常都应保存日志并停止,不要连续试错。
### 14.1 第 0 级:不上电机的离线检查
- 用相同 450 维输入比较 PyTorch、ONNX 和 BPU 输出。
- 检查 12 维动作的顺序、符号、范围和有限性。
- 测量平均、P99 和最大推理时间。
- 对零输入、真实日志输入和边界输入做回归。
### 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 示例:
```bash
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 示例:
```bash
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`,验证状态机:
```text
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. 日志与验收
每次实机运行至少保存:
```text
metadata.json
steps.jsonl
```
部署脚本默认在 `--log-dir` 下创建带时间戳的目录,例如:
```text
logs/robotlab_go1_deploy_YYYYMMDD_HHMMSS/
```
不要关闭日志来“省性能”。实机调试阶段宁可降低打印频率,也要保留 JSONL 日志后续量化校准、ONNX/BPU 对齐、力矩保护分析和动作异常定位都依赖这些记录。
`metadata.json` 用来复现实验条件。当前部署脚本会自动写入:
```text
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` 是逐周期日志。当前脚本每条记录至少包含:
```text
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 差
速度合格不代表数值合格。使用同一批真实 450 维输入逐帧比较 ONNX/BPU重点查 featuremap 输入、量化校准分布、输出 reshape、CPU fallback 和 runtime 调用生命周期。
### 16.4 TensorBoard 总回报上涨,但 RoboGauge 下降
可能是训练奖励偏向速度跟踪,牺牲了力矩、姿态或关节裕度,也可能是后期 checkpoint 对训练域过拟合。比较分项指标和多个 checkpoint不要只保留最后一轮。
### 16.5 真机一进入 RL 就力矩保护
立即停机。依次检查动作方向、目标角、目标单步变化、PD 增益、当前位置偏差、观测缩放和历史初始化。不要把提高 `power_factor` 作为第一反应。
## 17. 发布前检查清单
- [ ] RoboGo 镜像和容器启动信息已补全。
- [ ] 硬件、接线、网络和急停章节已由设备负责人补全。
- [ ] 所有仓库 commit 和 Python 依赖已记录。
- [ ] 26000 checkpoint 已完成 TensorBoard、IsaacLab play 和导出检查。
- [ ] ONNX 输入 450、输出 12关节顺序为 `FR, FL, RR, RL`
- [ ] RoboGauge 使用 `go1_lab` 而非 `go2_lab`
- [ ] MuJoCo 平地、转向、侧移、急停和台阶测试通过。
- [ ] 真实日志校准集与模型版本一致。
- [ ] X5/S100 量化模型通过离线精度和延迟验收。
- [ ] 私有 SDK 的协议、密钥和专有实现未写入公开文档。
- [ ] 实机测试严格完成只读、观测、推理、状态机、悬空、地面分级流程。
- [ ] 每条停机路径均已实际演练。
## 18. 参考资料
- [RoboGauge 项目主页](https://robogauge.github.io/complete/)
- [Toward Reliable Sim-to-Real Predictability for MoE-based Robust Quadrupedal Locomotion](https://robogauge.github.io/static/files/arxiv.pdf)
- [RoboGauge](https://github.com/wty-yy/RoboGauge)
- [go2_rl_robotlab](https://github.com/wertyuilife2/go2_rl_robotlab)
- [IsaacLab 2.3.2 安装文档](https://isaac-sim.github.io/IsaacLab/v2.3.2/source/setup/installation/isaaclab_pip_installation.html)
- [Unitree unitree_legged_sdk](https://github.com/unitreerobotics/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`