# 四足机器狗从训练到实机部署全流程指南

> 本教程基于西安交通大学 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 仓库

| 用途 | 公开仓库 | 分支/版本 | 镜像内目录 | 本次验证 commit |
| --- | --- | --- | --- | --- |
| IsaacLab 源码 | [isaac-sim/IsaacLab](https://github.com/isaac-sim/IsaacLab) | `v2.3.2` | `/opt/IsaacLab` | `37ddf626871758333d6ed89cf64ad702aef127d0` |
| Go1 训练与 IsaacLab 部署 | [ChenYouYuan0413/go2_rl_robotlab](https://github.com/ChenYouYuan0413/go2_rl_robotlab) | `go1` | `/opt/go2_rl_robotlab` | `f40874bc90c72264dbf46989d90e15a9715c1393` |
| Go1 RoboGauge 评估 | [ChenYouYuan0413/RoboGauge](https://github.com/ChenYouYuan0413/RoboGauge) | `go1` | `/opt/RoboGauge` | `a1665511267bca6f66c41224a113595f0abd3051` |
| Go1 MuJoCo、ONNX 与 BPU 部署 | [ChenYouYuan0413/go1_deploy](https://github.com/ChenYouYuan0413/go1_deploy) | `main` | `/opt/go1_deploy/deploy_rl_lab` | `cc7864aa605df0d6f533c463ddf930d2ace8aed8` |

上述三个 Go1 适配仓库均可从 GitHub 匿名拉取，不再依赖内部 Gitea。官方公开 SDK 可参考 [unitree_legged_sdk](https://github.com/unitreerobotics/unitree_legged_sdk)。

当前交付镜像部署目录结构为：

```text
/opt/go1_deploy/
  deploy_rl_lab/
    policy_robotlab_26000.onnx
    deploy_go1_onnx_mujoco_lab.py
    deploy_go1_rlgym_pro_sdk_lab.py
    deploy_go1_rlgym_pro_sdk_lab_fastcpp.py
    bpu_quantization/
    bpu_deploy_x5/
    bpu_deploy_s100/
    terrains/
  logs/
```

本文有两条使用路径：

- **路径 A，直接使用交付镜像**：不需要重新 clone 或重新安装，使用 `/opt` 下的固定目录和解释器
- **路径 B，没有镜像**：按 3.4 节拉取仓库并安装

本文以 26000 轮检查点演示导出、评估、量化和部署流程。示例发布名称如下：

```text
训练检查点: 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
```

训练目录可保留不同课程阶段的 checkpoint 用于比较，但交付目录只保留最终选定的版本。量化脚本使用 `--name policy_robotlab_26000` 时会生成上面的 X5/S100 名称

### 1.2 全流程

```text
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 奖励、评估和部署的职责不同

训练奖励用于产生梯度，并不是最终安全指标。高总奖励可能由速度跟踪项主导，同时仍存在极端关节、力矩尖峰或急停不稳。因此本流程分三层判断：

1. **训练奖励**：判断优化是否收敛、是否发生策略坍塌。
2. **RoboGauge 指标**：在跨地形、跨摩擦、多随机种子下比较跟踪、安全和运动质量。
3. **真机保护与日志**：判断实时观测、动作、关节目标和硬件状态是否处于允许范围

RoboGauge 当前关注的指标包括关节软限位、线/角速度误差、电机功耗、姿态稳定性、力矩平滑度、摩擦裕度和 ZMP 裕度。归一化后均按“越大越好”解释，但任何综合分数都不能替代分项检查

## 3. 两种环境准备方式

### 3.1 硬件与软件前提

- NVIDIA GPU 和匹配的驱动
- Linux 训练环境；图形界面训练不是必须
- Python 3.11
- IsaacLab `2.3.2.post1` 和 Isaac Sim `5.1.0.0`
- 足够的磁盘空间保存检查点、TensorBoard 日志和导出模型

### 3.2 路径 A：直接使用交付镜像

我们在地瓜的robogo的云桌面平台--[d-robotics](https://robogo.d-robotics.cc/cloud-desktop)
提供了配置好所有环境开箱即用的镜像

![image-20260801194631847](E:/race_save/doge/dog_train_deploy_guide_doc/%E5%9B%9B%E8%B6%B3%E6%9C%BA%E5%99%A8%E7%8B%97%E8%AE%AD%E7%BB%83%E5%88%B0%E5%AE%9E%E6%9C%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E6%B5%81%E7%A8%8B%E6%8C%87%E5%8D%97.assets/image-20260801194631847.png)

如上创建桌面，选择DOG-MOE-CTS_V1.1的镜像开始创建，选择12g-16c32g的配置

进入后我们先把量化的dockerg工具链进行挂载：

```text
sudo docker load -i /opt/envs/go2_rl_robotlab/docker-toolchain/images/openexplorer_ai_toolchain_ubuntu_20_x5_cpu_v1.2.8.tar

sudo docker load -i /opt/envs/go2_rl_robotlab/docker-toolchain/images/ai_toolchain_ubuntu_22_s100_s600_cpu_v3.7.0.tar
```

量化时应连接宿主 Docker daemon，先执行 `docker load -i`，再运行第 11 节脚本

镜像内目录固定如下：

```text
/opt/IsaacLab
/opt/go2_rl_robotlab
/opt/RoboGauge
/opt/go1_deploy
/opt/envs/go2_rl_robotlab
/opt/envs/robogauge
```

直接使用镜像时跳过 3.4 节。首次进入容器先执行验收：

```bash
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_deploy
test -d /opt/go1_deploy/deploy_rl_lab
test -f /opt/go1_deploy/deploy_rl_lab/policy_robotlab_26000.onnx
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`

Pip 版 Isaac Sim 首次启动必须接受 EULA。建议把下面变量写入镜像运行环境：

```bash
export OMNI_KIT_ACCEPT_EULA=YES
```

下列目录会在训练或评估时写入：

```text
/opt/go2_rl_robotlab/logs
/opt/go2_rl_robotlab/outputs
/opt/RoboGauge/logs
/opt/RoboGauge/outputs
/opt/go1_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` 只是占位符）：

```dockerfile
RUN install -d -m 0775 /opt/go2_rl_robotlab/logs /opt/go2_rl_robotlab/outputs \
    /opt/RoboGauge/logs /opt/RoboGauge/outputs \
    /opt/go1_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_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
```

RoboGauge 环境应指向 `/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` 为准

### 3.3 镜像训练链路自检

正式训练前先跑 1 轮。发布镜像必须先通过 3.2 节的版本和写权限验收：

```bash
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`：

```bash
find /opt/go2_rl_robotlab/logs/rsl_rl/go1_moe_cts -path '*image_direct_1iter*' -type f -print
```

自检生成的 checkpoint 仅用于确认训练链路可运行，不是可部署策略

### 3.4 路径 B：没有镜像时从零安装

以下命令假设主机已安装 NVIDIA 驱动、Docker（量化时需要）和 Conda：

```bash
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_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
```

从公开 GitHub 仓库拉取固定分支。`go2_rl_robotlab` 和 `RoboGauge` 使用 `go1` 分支，`go1_deploy` 使用 `main` 分支：

```bash
git clone --branch v2.3.2 --depth 1 https://github.com/isaac-sim/IsaacLab.git /opt/IsaacLab
git clone --branch go1 --single-branch https://github.com/ChenYouYuan0413/go2_rl_robotlab.git /opt/go2_rl_robotlab
git clone --branch go1 --single-branch https://github.com/ChenYouYuan0413/RoboGauge.git /opt/RoboGauge
git clone --branch main --single-branch https://github.com/ChenYouYuan0413/go1_deploy.git /opt/go1_deploy

git -C /opt/IsaacLab rev-parse HEAD
git -C /opt/go2_rl_robotlab rev-parse HEAD
git -C /opt/RoboGauge rev-parse HEAD
git -C /opt/go1_deploy rev-parse HEAD

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 等大体积版本：

```bash
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
```

最后由管理员创建运行目录并交给实际运行用户：

```bash
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_deploy/logs
```

记录环境，便于在部署前复现：

```bash
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` 分支已包含 Go1 训练与部署适配。适配不只是替换 URDF，至少包括以下内容：

1. 添加完整的 Go1 URDF、MuJoCo XML 和 mesh
2. 定义 Go1 执行器、默认关节角、PD 参数和力矩限制
3. 新建 `RobotLab-Go1-v0` 的场景、观测、动作、奖励、随机化和课程配置
4. 注册 Go1 任务及 MoE-CTS runner 配置
5. 添加 Go1 的 MuJoCo 配置和部署入口
6. 在所有模块中统一关节顺序与策略输入输出

### 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 帧]
```

## 5. 奖励函数与环境设计

### 5.1 奖励项与判断原则

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 奖励管理器还会按环境步长累计

奖励判断原则：

- 先看 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 轮还会发生第二次、更激进的切换。继续训练到该位置前，应预留新的适应区间并单独标注曲线

课程切换后，应关注回报是否逐步恢复、非法接触是否下降，而不是要求新课程下的绝对回报回到旧课程水平。另两个固定奖励课程更早结束：`lin_vel_z_l2` 在 0 到 1500 轮由 `-2` 变为 0，`base_height_l2` 在 0 到 5000 轮由 `-1` 变为 `-10`，都不是 20000 轮断崖的原因

## 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
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
```

另开终端观察：

```bash
watch -n 1 nvidia-smi
```

选择能稳定运行且留有显存余量的最大环境数。若 OOM，先减小 `num_envs`；只有明确理解 PPO batch 变化后，再修改 `rsl_rl_cfg.py` 中的 `num_steps_per_env`、mini-batch 和 epoch 数

### 6.2 正式训练并保留候选检查点

```bash
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
```

日志默认位于：

```text
/opt/go2_rl_robotlab/logs/rsl_rl/go1_moe_cts/<时间戳>_go1_4096x24/
```

检查点每 500 轮保存一次。不要只保留最后一个文件，至少确认课程切换前后和最终候选都存在：

```bash
find /opt/go2_rl_robotlab/logs/rsl_rl/go1_moe_cts -name 'model_*.pt' -print | sort -V
```

若训练进程被 `Ctrl+C` 中断，以磁盘上最后一个完整 checkpoint 为准，不要把日志中未保存的轮次标为检查点

### 6.3 断点续训

```bash
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 观察训练

训练机执行：

```bash
/opt/envs/go2_rl_robotlab/bin/python -m tensorboard.main --logdir /opt/go2_rl_robotlab/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_Reward/*` | 速度跟踪、功率、力矩和动作平滑等分项奖励 |
| `Episode_Termination/*` | `time_out` 与 `illegal_contact` 的占比 |
| `Curriculum/terrain_levels` | 地形难度是否连续变化 |
| `Metrics/base_velocity/max_command_x` | 是否在 20000/50000 轮发生指令课程切换 |

![屏幕截图 2026-08-01 184526](E:/race_save/doge/dog_train_deploy_guide_doc/%E5%9B%9B%E8%B6%B3%E6%9C%BA%E5%99%A8%E7%8B%97%E8%AE%AD%E7%BB%83%E5%88%B0%E5%AE%9E%E6%9C%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E6%B5%81%E7%A8%8B%E6%8C%87%E5%8D%97.assets/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202026-08-01%20184526.png)
不要跨课程直接比较总回报绝对值，也不要只选平均回报最高或轮次最大的 checkpoint。建议将课程切换前后和训练后期的候选模型同时送入 RoboGauge，比较综合分数和分项指标后再决定

## 8. 评估与导出模型

### 8.1 IsaacLab play 与导出

`play.py` 加载 checkpoint 后，会先在同一 run 的 `exported/` 下生成 `policy.pt` 和 `policy.onnx`，再进入可视化 rollout：

```bash
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
```

输出：

```text
<RUN_DIR>/exported/policy.pt
<RUN_DIR>/exported/policy.onnx
```

同一 run 下再次导出会覆盖这两个通用文件名。必须在切换到下一个 checkpoint 前复制并带上轮次、课程阶段；导出文件完整写入后才能终止 play：

```bash
cp <RUN_DIR>/exported/policy.onnx /opt/go1_deploy/deploy_rl_lab/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`：

```bash
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，待训练停止后离线导出
- `play.py` 导出后仍会进入仿真循环；看到导出文件后用 `Ctrl+C` 结束该独立进程，不会中断训练进程

### 8.3 检查 ONNX 接口

```bash
/opt/envs/go2_rl_robotlab/bin/python -c "import onnx, onnxruntime as ort; p='/opt/go1_deploy/deploy_rl_lab/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`。输入输出不匹配时不要继续 sim2sim 或量化

### 8.4 独立 MuJoCo 快速检查

如果你要做的是“和部署链路一致”的快速检查，直接使用部署仓库里的 ONNX 入口。它读取当前 45 维单帧观测，并在脚本内部维护 10 帧历史，适合在进入完整 RoboGauge 评估前核对观测、动作缩放和地形 XML：

镜像中用绝对 ONNX 路径执行非交互采样。该脚本会在加载模型前切换工作目录，相对 ONNX 路径可能因此失效：

```bash
cd /opt/go1_deploy/deploy_rl_lab
env -u MUJOCO_GL /opt/envs/robogauge/bin/python deploy_go1_onnx_mujoco_lab.py \
  --onnx /opt/go1_deploy/deploy_rl_lab/policy_robotlab_26000.onnx \
  --sample \
  --sample-seconds 1 \
  --sample-log-dir /opt/go1_deploy/logs
```

采样目录名固定以 `mujoco_stairs_sample_` 开头，用于保存独立 MuJoCo ONNX 推理检查日志；PTQ 使用第 11 节的正式 64 样本校验集

楼梯测试同样直接切 terrain：

```bash
env -u MUJOCO_GL /opt/envs/robogauge/bin/python deploy_go1_onnx_mujoco_lab.py \
  --onnx /opt/go1_deploy/deploy_rl_lab/policy_robotlab_26000.onnx \
  --sample-stairs-level 3 \
  --sample-seconds 20 \
  --sample-log-dir /opt/go1_deploy/logs \
  --spawn-x -0.6 \
  --spawn-z 0.34
```

脚本默认 `--clip-actions 100.0`、`--max-target-step 0.0`。如果你要做更保守的起步测试，可以再按日志逐步收紧这些参数

## 9. RoboGauge 自动评估

### 9.1 安装

路径 A 的镜像用户不再安装，先检查解释器、任务注册和模型：

```bash
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 构建：

```bash
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` 中执行。本文需要公开适配仓库 `ChenYouYuan0413/RoboGauge` 的 `go1` 分支，其中包含 `go1_lab.*` 任务；安装后确认当前 commit 与任务注册，输出中必须出现 `go1_lab.flat`、`go1_lab.stairs_fd` 等任务：

```bash
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.*` 任务，应先同步本地 Go1 适配代码

### 9.2 单模型评估

直接通过 `--model-path` 指定 26000 对应的部署版 TorchScript。不要传入训练 checkpoint `model_26000.pt`，它是包含优化器等状态的字典，不能被 `torch.jit.load()`。当前平地配置默认启用最大速度和对角速度；命令仍显式传入 `--goals`，以免配置默认值变化后评测范围静默改变,不加  **--headless** 则为可视化查看：

```bash
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
```

![image-20260801200427939](E:/race_save/doge/dog_train_deploy_guide_doc/%E5%9B%9B%E8%B6%B3%E6%9C%BA%E5%99%A8%E7%8B%97%E8%AE%AD%E7%BB%83%E5%88%B0%E5%AE%9E%E6%9C%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E6%B5%81%E7%A8%8B%E6%8C%87%E5%8D%97.assets/image-20260801200427939.png)
三级楼梯使用目标点任务。`--spawn-type level_eval` 固定评估出生点，避免和搜索最高等级时的出生点混淆：

```bash
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
```

单任务的 `--task-name` 必须是注册表里的完整名称，例如 `go1_lab.flat` 或 `go1_lab.stairs_fd`，不能写成裸的 `go1_lab`。只有下面的 stress pipeline 把 `go1_lab` 当作机器人任务前缀，再拼接具体地形

### 9.3 跨地形压力测试

```bash
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 模型选择原则

候选 checkpoint 应使用相同的任务、命令目标、地形、随机种子和物理参数评估。同时检查跟踪、安全、运动质量、各地形成功情况和多随机种子波动，不能只按最新轮次或单一综合分数选择发布模型。使用前仍需运行多随机种子、摩擦、质量和地形压力测试，并结合真实电机温度、电流与保护日志做最终判断

RoboGauge 的 `dof_power` 是归一化质量分，不是瓦特或电机温度。它可以比较同条件 checkpoint，但不能据此断言真机不会过热

### 9.5 训练期间异步评估

先启动服务端：

```bash
cd /opt/RoboGauge
MPLCONFIGDIR=/tmp/robogauge-matplotlib /opt/envs/robogauge/bin/python robogauge/scripts/server.py --port 9973 --num-processes 8
```

再启动训练：

```bash
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 前必须先改成一个已注册的完整任务名，例如平地筛选使用：

```python
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

这里使用 `/opt/go1_deploy/deploy_rl_lab/deploy_go1_onnx_mujoco_lab.py`

```bash
cd /opt/go1_deploy/deploy_rl_lab

MUJOCO_GL=glfw /opt/envs/robogauge/bin/python deploy_go1_onnx_mujoco_lab.py \
  --onnx /opt/go1_deploy/deploy_rl_lab/policy_robotlab_26000.onnx
```

这是带窗口和键盘的交互命令，需要可用的 X11/桌面。Linux 使用普通 `python`；只有 macOS 才通常需要 `mjpython`。脚本内部会 `chdir` 到自身目录，因此 `--onnx` 使用绝对路径。无图形环境应使用 8.4 节的 `--sample` 命令

楼梯测试：

```bash
MUJOCO_GL=glfw /opt/envs/robogauge/bin/python deploy_go1_onnx_mujoco_lab.py \
  --onnx /opt/go1_deploy/deploy_rl_lab/policy_robotlab_26000.onnx \
  --terrain /opt/go1_deploy/deploy_rl_lab/terrains/stairs/stairs_3.xml \
  --spawn-x -0.6 \
  --spawn-z 0.34
```

![屏幕截图 2026-08-01 161128](E:/race_save/doge/dog_train_deploy_guide_doc/%E5%9B%9B%E8%B6%B3%E6%9C%BA%E5%99%A8%E7%8B%97%E8%AE%AD%E7%BB%83%E5%88%B0%E5%AE%9E%E6%9C%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E6%B5%81%E7%A8%8B%E6%8C%87%E5%8D%97.assets/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202026-08-01%20161128.png)

键盘映射：

| 按键 | 功能 |
| --- | --- |
| `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. 与部署观测构建逻辑一致的 10 帧/450 维 `float32` PTQ 输入样本
3. 使用浮点 ONNX 对这批输入执行推理得到的参考输出
4. 浮点 ONNX、图变换 ONNX、量化模型三者的逐样本输出对比

当前 26000 模型使用 `calibration_data_26000_robotlab_fast64/` 中的 64 个 PTQ 校验样本。这批数据来自 ONNX 推理流程。需要重新生成时，先检查供 `make_calibration_data.py` 使用的 ONNX 推理日志：

```bash
find /opt/go1_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` 后缀，但两者用途完全不同。脚本找不到匹配日志或有效样本不足时会中止，不会绕过数据准备继续编译

路径 A 已随镜像提供 `calibration_data_26000_robotlab_fast64/`。量化脚本检测到目录中恰好有 64 个 `.bin` 时直接复用；路径 B 或样本数量不匹配时，才从上述 ONNX 推理日志重新生成

当前量化脚本会保留 actions 输出、降级 opset、包装为 BPU 需要的 4D 输入，并把 MoE grouped Conv 等价替换为 Gemm，以减少 CPU fallback。中间 norm 节点位于 actor 之前，不能随意删除或移到后处理

量化脚本内部流程如下：

```text
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` |
| `--name` | 输出模型 basename；不填时取 ONNX 文件名 | `policy_robotlab_26000` |
| `--history-len` | 历史帧数，用于重建输入维度 | Lab 固定为 `10` |
| `--flat-dim` | 平铺输入维度；不填时为 `45 * history-len` | Lab 固定为 `450` |
| `--samples` | 校准样本数量，越大越慢但覆盖更稳 | 默认 `64`，必要时 `128` |
| `--min-samples` | 最少有效样本数，不足则中止 | 默认 `32` |
| `--log-prefix` | 从 `logs/` 下筛选 ONNX 推理日志的目录前缀 | `robotlab_go1_deploy` |
| `--cal-tag` | 校准目录标签 | `robotlab` |
| `--compare-limit` | 浮点 ONNX 等价对比样本数 | 默认 `64` |
| `--quant` | 量化策略 | 默认 `int16`，`int8` 仅作对照 |
| `--docker-image` | D-Robotics CPU 工具链镜像 | X5/S100 各自不同 |

量化建议在 Mac 或训练机的 Docker 中完成，不在 X5/S100 板端跑 Docker。本文训练/量化主机的仓库固定为 `/opt/go1_deploy`；板端只负责加载产物做离线测速、数值对齐和实机部署

量化脚本内部直接调用 `docker`。运行 X5/S100 脚本前先验收 daemon：

```bash
sudo /opt/envs/go2_rl_robotlab/docker-toolchain/bin/docker info >/dev/null
```

### 11.2 RDK X5

X5 使用 D-Robotics X5 CPU 工具链镜像：

```bash
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/go1_deploy`。量化机执行：

```bash
cd /opt/go1_deploy

sudo env PATH=/opt/envs/go2_rl_robotlab/docker-toolchain/bin:${PATH} \
  bash deploy_rl_lab/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`。

以下是 **RDK X5 目标板** 命令。板端软件按交付约定安装在 `/root/go1_deploy`；这是硬件系统路径，不是训练/量化镜像路径，也不应复制回 `/opt` 镜像执行：

板端先做完全离线自检，不连接电机控制：

```bash
cd /root/go1_deploy
PYTHONPATH=/root/go1_deploy \
python3 deploy_rl_lab/bpu_deploy_x5/test_bpu_policy.py \
  --bpu-model deploy_rl_lab/bpu_quantization/mapper_output_26000_gemm/policy_robotlab_26000_int16_gemm.bin \
  --input-bin deploy_rl_lab/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
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：

```bash
cd /opt/go1_deploy

sudo env PATH=/opt/envs/go2_rl_robotlab/docker-toolchain/bin:${PATH} \
  bash deploy_rl_lab/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 后，先看模型信息再测速：

```bash
/usr/hobot/bin/hrt_model_exec model_info \
  --model_file deploy_rl_lab/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_rl_lab/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_rl_lab/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 说明

本文只描述公开的控制契约和安全设计，不记录私有协议格式。需要复现时，需要以 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 /opt/unitree_legged_sdk
cmake -S /opt/unitree_legged_sdk -B /opt/unitree_legged_sdk/build
cmake --build /opt/unitree_legged_sdk/build --parallel
```

阅读时重点关注 UDP 收发线程、`LowState` 到策略观测的转换、`Safety` 调用和退出流程，不要照搬其网络地址、增益或速度范围到另一台机器人

### 12.2 硬件、接线与网络

本次部署所使用的是宇树go1
#### 通信控制

内部通信结构如下：

![img](E:/race_save/doge/dog_train_deploy_guide_doc/%E5%9B%9B%E8%B6%B3%E6%9C%BA%E5%99%A8%E7%8B%97%E8%AE%AD%E7%BB%83%E5%88%B0%E5%AE%9E%E6%9C%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E6%B5%81%E7%A8%8B%E6%8C%87%E5%8D%97.assets/NetworkLayout.png)

内部的控制都是通过一台机器狗内部的交换机完成的，通过狗背部暴露出的网口即可完成与它的内部通信（前提是ip需要是统一网段192.168.123.xxx，需要自行修改固定s100和x5的网口的ip，比如配置eth0的ip为192.168.123.222)，检验是否连接到内部：

```
ping 192.168.123.161
```

下图是网线的连接示意图

![image-20260801193808916](E:/race_save/doge/dog_train_deploy_guide_doc/%E5%9B%9B%E8%B6%B3%E6%9C%BA%E5%99%A8%E7%8B%97%E8%AE%AD%E7%BB%83%E5%88%B0%E5%AE%9E%E6%9C%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E6%B5%81%E7%A8%8B%E6%8C%87%E5%8D%97.assets/image-20260801193808916.png)

![image-20260801191848283](E:/race_save/doge/dog_train_deploy_guide_doc/%E5%9B%9B%E8%B6%B3%E6%9C%BA%E5%99%A8%E7%8B%97%E8%AE%AD%E7%BB%83%E5%88%B0%E5%AE%9E%E6%9C%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E6%B5%81%E7%A8%8B%E6%8C%87%E5%8D%97.assets/image-20260801191848283.png)



之后使用官方的sdk：即可完成对底层的lowlevel电机控制

同时建议直接将s100/x5的ssh公钥复制到树莓派（192.168.123.161）上来进行ssh的免密访问

```
# 在机器狗身上的运动控制板树莓派（192.168.123.161）上
mkdir -p ~/.ssh
chmod 700 ~/.ssh
touch ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
vim ~/.ssh/authorized_keys
# 把公钥粘贴到这里面，vim使用: + wq 退出并保存
```

#### 供电

我们使用的s100需要19v的供电，宇树go1的背部有给出对外的接口，如图

![image-20260801192200981](E:/race_save/doge/dog_train_deploy_guide_doc/%E5%9B%9B%E8%B6%B3%E6%9C%BA%E5%99%A8%E7%8B%97%E8%AE%AD%E7%BB%83%E5%88%B0%E5%AE%9E%E6%9C%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E6%B5%81%E7%A8%8B%E6%8C%87%E5%8D%97.assets/image-20260801192200981.png)

是一个24v xt30的接口，我们只需要使用一个24v转19v的降压模块即可（同时注意电流承载大致6A）

建议接入之前先使用可调电源和万用表测试输入输出电压

![屏幕截图 2026-08-01 192536](E:/race_save/doge/dog_train_deploy_guide_doc/%E5%9B%9B%E8%B6%B3%E6%9C%BA%E5%99%A8%E7%8B%97%E8%AE%AD%E7%BB%83%E5%88%B0%E5%AE%9E%E6%9C%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E6%B5%81%E7%A8%8B%E6%8C%87%E5%8D%97.assets/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202026-08-01%20192536.png)

![image-20260801192620807](E:/race_save/doge/dog_train_deploy_guide_doc/%E5%9B%9B%E8%B6%B3%E6%9C%BA%E5%99%A8%E7%8B%97%E8%AE%AD%E7%BB%83%E5%88%B0%E5%AE%9E%E6%9C%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E6%B5%81%E7%A8%8B%E6%8C%87%E5%8D%97.assets/image-20260801192620807.png)

对于s100还需要一根dc的接口

如图接线：

![img_v3_02145_97a765f5-ee43-4437-b264-4555bfb617eg](E:/race_save/doge/dog_train_deploy_guide_doc/%E5%9B%9B%E8%B6%B3%E6%9C%BA%E5%99%A8%E7%8B%97%E8%AE%AD%E7%BB%83%E5%88%B0%E5%AE%9E%E6%9C%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E6%B5%81%E7%A8%8B%E6%8C%87%E5%8D%97.assets/img_v3_02145_97a765f5-ee43-4437-b264-4555bfb617eg.jpg)

左右两边黄色打印机只是支撑的作用（两个小claude）

接上go1就是这样：

![image-20260801193333084](E:/race_save/doge/dog_train_deploy_guide_doc/%E5%9B%9B%E8%B6%B3%E6%9C%BA%E5%99%A8%E7%8B%97%E8%AE%AD%E7%BB%83%E5%88%B0%E5%AE%9E%E6%9C%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E6%B5%81%E7%A8%8B%E6%8C%87%E5%8D%97.assets/image-20260801193333084.png)

此外建议加一根足够强劲的狗绳来在紧急时候减小对小电脑的冲击，起到保护作用


对于x5就很方便，只需要有支持快充的充电宝和typec线即可完成供电，无需额外的降压
#### 实际部署效果

s100部署：


x5部署：

【溜溜狗～】 https://www.bilibili.com/video/BV1AT3B6GEs2/?share_source=copy_web&vd_source=927818b43cb3455737f81900c0cf8608

## 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 和最大推理时间
- 对零输入、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 示例：

```bash
cd /root/go1_deploy
PYTHONPATH=/root/go1_pro_sdk:/root/go1_deploy \
python3 deploy_rl_lab/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_deploy
PYTHONPATH=/root/go1_pro_sdk:/root/go1_deploy \
python3 deploy_rl_lab/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 差

速度合格不代表数值合格。使用同一批 PTQ 校验输入逐帧比较 ONNX/BPU，重点查 featuremap 输入、量化校准分布、输出 reshape、CPU fallback 和 runtime 调用生命周期

### 16.4 TensorBoard 总回报上涨，但 RoboGauge 下降

先检查是否跨过 20000 或 50000 轮的速度指令课程切换。课程变难后总回报下降、非法接触短时上升是预期现象，旧课程和新课程的绝对回报不能直接比较。若命令范围和地形等级都未变化，才进一步排查训练奖励偏向速度跟踪、牺牲力矩/姿态/关节裕度或后期 checkpoint 对训练域过拟合。无论哪种情况，都应比较分项指标和多个 checkpoint，不要只保留最后一轮

### 16.5 真机一进入 RL 就力矩保护

立即停机。依次检查动作方向、目标角、目标单步变化、PD 增益、当前位置偏差、观测缩放和历史初始化。不要把提高 `power_factor` 作为第一反应

## 17. 参考资料

- [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)
- [Go1 训练适配：ChenYouYuan0413/go2_rl_robotlab，go1 分支](https://github.com/ChenYouYuan0413/go2_rl_robotlab/tree/go1)
- [Go1 RoboGauge 适配：ChenYouYuan0413/RoboGauge，go1 分支](https://github.com/ChenYouYuan0413/RoboGauge/tree/go1)
- [Go1 部署：ChenYouYuan0413/go1_deploy，main 分支](https://github.com/ChenYouYuan0413/go1_deploy/tree/main)
- [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)
- [MAVProxyUser/YushuTechUnitreeGo1: 宇树科技 Yushu Technology (Unitree) go1 development notes](https://github.com/MAVProxyUser/YushuTechUnitreeGo1/tree/main)
- [V3.7.0 | RDK S100/S600 DOC](https://developer.d-robotics.cc/rdk_s_doc/Advanced_development/toolchain_development/algorithm_toolchain/overview?v=4.0.5&p=RDK+S100)
- [7.3.1 简介 | RDK X3/X5 DOC](https://developer.d-robotics.cc/rdk_x_doc/Advanced_development/toolchain_development/overview?v=3.5.0&p=RDK+X5)
