# 四足机器狗从训练到实机部署全流程指南 > 本教程基于西安交通大学 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。 > > 本文依据当前本地仓库代码和本次 Go1 训练记录整理。IsaacLab 训练、模型导出以及 RoboGauge 单模型评估命令已在训练机上验证;全新环境安装、BPU 量化和实机命令仍需在目标设备上逐条复核。不要整段复制后无人值守运行。 ## 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 | 仅在内部部署机使用,本文不公开协议、密钥和专有接口 | 本次训练在约 33540 轮手动停止,最后一个完整保存的检查点是 `model_33500.pt`。综合当前单 seed RoboGauge 结果,本文仍用 **26000 轮检查点**作为平地与三级楼梯之间较均衡的部署基线;33500 是最新候选,而不是无条件替代 26000。示例发布名称如下: ```text 训练检查点: model_26000.pt 浮点模型: policy_robotlab_26000.onnx X5 模型: policy_robotlab_26000_int16_gemm.bin S100 模型: policy_robotlab_26000_s100_int16_gemm.hbm ``` 训练目录中的导出文件会保留课程阶段,例如 `policy_robotlab_26000_post_command_curriculum.onnx`;确定发布版本后可再复制为上面的短名称。26000 只是当前均衡基线,不表示轮数越大一定越好。若任务以上楼梯为主,应同时保留 15000 和 26000;若以平地高速或下楼梯为主,可继续比较 33500。最终模型仍应根据多 seed 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 风险。 - **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/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. 奖励函数与环境设计 ### 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 奖励管理器还会按环境步长累计,因此不要根据权重直接猜测 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 默认参数的含义 当前默认配置: ```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 正式训练并保留候选检查点 ```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=33500 \ --run_name=go1_4096x24 \ --logger=tensorboard \ --headless ``` 日志默认位于: ```text logs/rsl_rl/go1_moe_cts/<时间戳>_go1_4096x24/ ``` 检查点每 500 轮保存一次。不要只保留最后一个文件,至少确认课程切换前后和最终候选都存在: ```bash find 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 断点续训 ```bash python scripts/rsl_rl/train.py \ --task=RobotLab-Go1-v0 \ --num_envs=4096 \ --max_iterations=3000 \ --resume \ --load_run= \ --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 @ ``` 浏览器打开 `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: ```bash python scripts/rsl_rl/play.py \ --task=RobotLab-Go1-v0 \ --num_envs=64 \ --checkpoint=/absolute/path/to/model_26000.pt ``` 输出: ```text /exported/policy.pt /exported/policy.onnx ``` 同一 run 下再次导出会覆盖这两个通用文件名。必须在切换到下一个 checkpoint 前复制并带上轮次、课程阶段;导出文件完整写入后才能终止 play: ```bash cp /exported/policy.onnx /path/to/go1_pro_deploy/deploy_45dim_rl_gym/policy_robotlab_26000.onnx cp /exported/policy.pt /path/to/RoboGauge/resources/models/go1/policy_robotlab_26000.pt ``` RoboGauge 当前通过 `torch.jit.load()` 使用 TorchScript `.pt`;ONNX 用于独立 MuJoCo、量化和实机部署。两者不能只靠改后缀互换。 ### 8.2 不打断训练的导出原则 训练 runner 保存的是可恢复训练的 checkpoint,不会默认在每次保存时生成 ONNX。`play.py` 即使使用 `--headless` 也会启动 Isaac Sim 和一个环境,不是纯模型导出器。要保持主训练进程运行,只应在另一块 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,不要复制正在写入的文件。 - 12 GB 级显卡以 4096 环境训练时通常没有余量再启动 Isaac Sim;不要为了导出挤占训练进程。 - 若没有第二块 GPU,先复制 checkpoint,待训练停止后离线导出。仓库当前没有正式的 `export-only` 命令,不要把临时 Python 片段写成通用教程入口。 - `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()])" ``` 预期 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: ```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 # go1 分支当前维护在私有派生仓库;有权限时再执行 git remote add go1-private git fetch go1-private go1 git switch -c go1 --track go1-private/go1 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(*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`,否则不会得到有效的速度评估: ```bash MUJOCO_GL=glfw 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 \ --run-name flat_velocity \ --goals max_velocity diagonal_velocity \ --headless ``` 三级楼梯使用目标点任务。`--spawn-type level_eval` 固定评估出生点,避免和搜索最高等级时的出生点混淆: ```bash MUJOCO_GL=glfw python robogauge/scripts/run.py \ --task-name go1_lab.stairs_fd \ --model-path resources/models/go1/policy_robotlab_26000.pt \ --experiment-name go1_robotlab_26000_stairs_fd \ --run-name stairs_fd_l3 \ --level 3 \ --spawn-type level_eval \ --headless MUJOCO_GL=glfw python robogauge/scripts/run.py \ --task-name go1_lab.stairs_bd \ --model-path resources/models/go1/policy_robotlab_26000.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`。 单任务的 `--task-name` 必须是注册表里的完整名称,例如 `go1_lab.flat` 或 `go1_lab.stairs_fd`,不能写成裸的 `go1_lab`。只有下面的 stress pipeline 把 `go1_lab` 当作机器人任务前缀,再拼接具体地形。 ### 9.3 跨地形压力测试 ```bash MUJOCO_GL=glfw 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 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 训练期间异步评估 先启动服务端: ```bash conda activate robogauge cd RoboGauge python robogauge/scripts/server.py --port 9973 --num-processes 8 ``` 再启动训练: ```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 模型并提交评估,但当前 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_.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 接口一致。 ```bash cd /path/to/go1_pro_deploy conda activate 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 下降 先检查是否跨过 20000 或 50000 轮的速度指令课程切换。课程变难后总回报下降、非法接触短时上升是预期现象,旧课程和新课程的绝对回报不能直接比较。若命令范围和地形等级都未变化,才进一步排查训练奖励偏向速度跟踪、牺牲力矩/姿态/关节裕度或后期 checkpoint 对训练域过拟合。无论哪种情况,都应比较分项指标和多个 checkpoint,不要只保留最后一轮。 ### 16.5 真机一进入 RL 就力矩保护 立即停机。依次检查动作方向、目标角、目标单步变化、PD 增益、当前位置偏差、观测缩放和历史初始化。不要把提高 `power_factor` 作为第一反应。 ## 17. 发布前检查清单 - [ ] RoboGo 镜像和容器启动信息已补全。 - [ ] 硬件、接线、网络和急停章节已由设备负责人补全。 - [ ] 所有仓库 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 平地、转向、侧移、急停和台阶测试通过。 - [ ] 真实日志校准集与模型版本一致。 - [ ] 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`