🔄 sim2real 部署步骤拆解
从"仿真里会走"到"真机上能走" —— 十步完整流程,每步给出目标、做法、坑点与验收标准
域随机化
策略导出
真机部署
数据闭环
十步流程
🎯 本页学习目标
1. 能说清"仿真策略直接上真机为什么会失败"(sim2real gap 的 5 个来源)
2. 能按十步流程把训练好的策略部署到真机,并说出每步的验收标准
3. 理解域随机化、延迟注入、策略导出(ONNX/C++)、数据闭环各自的作用
4. 与
04-04 仿真与 RL 配合:那页讲"怎么训",本页讲"怎么上真机"
建议用时:约 30 分钟
1 什么是 sim2real gap?
在仿真里训练出一个"满分策略",直接拷到真机往往立刻摔倒或根本站不起来。原因是仿真和真机之间存在系统性差异,这些差异被统称为 sim2real gap。gap 越大,策略迁移越容易失败;而 sim2real 的全部工作,就是把 gap 缩小到策略能容忍的范围。
| 差异来源 | 仿真里常被简化成 | 真机上的真实情况 |
| 物理精度 | 理想刚体、库伦摩擦常数、零背隙 | 柔性、变摩擦、减速器背隙、接触非线性 |
| 电机/执行器 | 理想力矩源、无延迟 | 力矩带宽有限、响应延迟、饱和、发热降额 |
| 通信/控制延迟 | 同步无延迟 | 控制周期 1-5ms、通信抖动、量化 |
| 传感器噪声 | 真值直接给 | 编码器/IMU 噪声、漂移、遮挡 |
| 算力 | 桌面 GPU 无限制 | 机载算力受限,策略推理要实时(常见 50Hz) |
💡 一句话原则:策略能容忍的"随机性"越大,迁移成功率越高。所以 sim2real 的核心手段不是"把仿真做得多逼真",而是"把策略练得多鲁棒"(域随机化),再配合真机侧的工程化对齐(延迟、频率、保护)。
2 十步部署流程
以下十步按顺序执行;每一步都有"验收标准",达标再进下一步。可对照 04-04 的训练流水线一起看。
1
真机建模(URDF / MJCF)
目标:在仿真里搭一个与真机结构一致的模型。做法:量测真实质量/惯量/连杆长度/关节限位,写进 URDF;不要用"默认参数"糊弄。坑:惯量张量(尤其转子和末端)误差大。验收:仿真里自由摆动/摔倒的姿态与真机肉眼一致。
2
参数辨识(电机 / 摩擦 / 阻尼)
目标:把电机 KV、相电阻、库伦+粘滞摩擦、减速器效率实测出来。做法:开环阶跃/斜坡实验 + 最小二乘拟合(或用 SimpleFOC/ODrive 的辨识例程)。坑:摩擦随温度/速度变化,取"工作区间"的均值。验收:同一指令下仿真与真机的电流/转速曲线趋势一致。
3
域随机化(让策略"见过世面")
目标:训练时随机扰动物理参数,让策略对参数变化不敏感。做法:每个 episode 随机采样质量(±10-20%)、摩擦、力矩缩放、地面摩擦/坡度、推搡力等。坑:范围太小=没效果,范围太大=学不会;先小后大逐步加。验收:策略在随机化后的环境里仍能稳定完成任务(成功率 ≥ 90%)。
4
动作延迟注入
目标:让策略适应真实控制延迟。做法:训练时把动作延迟 1-3 个控制周期(如 20-60ms)再作用到环境。坑:只延迟观测不延迟动作、或延迟随机化缺失,都会让策略上真机"手抖"。验收:策略在 40ms 延迟下不掉分(或掉分 < 5%)。
5
观测噪声注入
目标:让策略适应传感器噪声。做法:给角度/角速度/线速度/IMU 加高斯噪声(幅值取真机实测的 1-1.5 倍)。坑:角度直接用"真值"会让策略依赖不存在的信号。验收:策略在噪声下仍稳定,且不发生高频抖动。
6
奖励塑形与防 hack
目标:设计让策略"真的会走"而非"钻空子"的奖励。做法:速度跟踪 + 能量惩罚 + 姿态惩罚 + 存活奖励的组合;加入"课程"(先站立→再行走)。坑:常见 hack:腿高频抽动、原地滑步、靠"摔倒前一刻"刷分。验收:回放 rollout 视频,确认动作自然、无异常抖动。
7
PPO 训练(与 04-04 衔接)
目标:训练出收敛策略。做法:Isaac Lab / legged-gym 跑 PPO;观察回报曲线与"提前终止"占比。坑:奖励曲线涨但实测表现差 → 奖励有问题。验收:训练收敛、rollout 成功率达标、策略确定性(无随机动作方差发散)。
8
策略导出(ONNX / TorchScript → C++ / Rust)
目标:把 PyTorch 策略转成真机能跑的推理。做法:导出 ONNX,用 onnxruntime(CPU/GPU)或转 C++ 在机载电脑推理;固定输入/输出张量形状。坑:忘记把"随机动作的均值"当最终输出(推理时不要采样);数值精度(float32→float16)可能抖动。验收:同一输入下,ONNX 输出与 PyTorch 输出误差 < 1e-3。
配套代码 1 — Isaac Lab 域随机化配置(对应第 3 步,建议在训练前就配好):
# Isaac Lab:用 EventTerm 声明域随机化(ManagerBasedRLEnv 的 events 配置)
import isaaclab.envs.mdp.events as mdp_events
from isaaclab.envs import ManagerBasedRLEnvCfg
from isaaclab.managers import EventTermCfg as EventTerm
from isaaclab.utils import configclass
@configclass
class MyEnvCfg(ManagerBasedRLEnvCfg):
events = ManagerBasedRLEnvCfg.events # 在原有 events 上追加以下随机化项
events.randomize_link_mass = EventTerm(
func=mdp_events.randomize_rigid_body_mass, # 随机化连杆质量
mode="startup", # 每次环境 reset 前抽样一次
params={
"asset_cfg": SceneEntityCfg("robot"),
"mass_distribution_params": (0.8, 1.2), # 质量缩放区间:标称值 ±20%
"operation": "scale",
},
)
events.randomize_friction = EventTerm(
func=mdp_events.randomize_rigid_body_material, # 随机化地面摩擦
mode="startup",
params={
"asset_cfg": SceneEntityCfg("robot"),
"static_friction_range": (0.4, 1.2), # 静摩擦区间(覆盖瓷砖/草地/地毯)
"dynamic_friction_range": (0.3, 1.0), # 动摩擦区间
"restitution_range": (0.0, 0.0),
},
)
events.randomize_push = EventTerm(
func=mdp_events.push_by_setting_velocity, # 随机推力扰动(抗扰训练)
mode="interval", # 每隔 interval_s 秒推一次
interval_range_s=(5.0, 10.0),
params={
"velocity_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5)}, # 推力等效速度 ±20% 量级
},
)
配套代码 2 — ONNX 导出与数值校验(对应第 8 步的验收项):
import torch, numpy as np, onnxruntime as ort
actor.eval() # 推理导出:确定性前向,取均值不采样
obs = torch.randn(1, obs_dim) # 固定形状的示例输入
torch.onnx.export(
actor, obs, "policy.onnx",
input_names=["obs"], output_names=["action"],
opset_version=17, dynamo=False,
)
sess = ort.InferenceSession("policy.onnx", providers=["CPUExecutionProvider"])
onnx_out = sess.run(["action"], {"obs": obs.numpy()})[0]
pt_out = actor(obs).detach().numpy()
diff = np.abs(pt_out - onnx_out).max()
assert diff < 1e-3, f"ONNX 与 PyTorch 输出偏差过大: {diff}"
print(f"max|Δ| = {diff:.2e} ✓ 校验通过")
9
真机部署(接口 / 频率 / 保护)
目标:策略推理 → 关节指令 的实时闭环。做法:上位机 50-100Hz 推理,输出目标关节角度/力矩 → 底层 FOC 环(见 04-02)执行;对齐频率、加死区、加力矩/速度限幅。坑:频率不齐(推理 50Hz vs 电机 10kHz)要经插值;缺限幅会烧电机/损坏关节。验收:上电后先用"悬吊/支撑"模式小力矩试运行,确认动作方向、限位、急停都正确。
10
数据闭环(真机数据 → 修正 → 再训练)
目标:用真机数据持续改进。做法:记录真机状态/动作/失败片段 → 回灌到仿真(做"重放"或修正参数)→ 针对性再训练。坑:只收集成功数据会掩盖失败模式,摔倒/异常数据更珍贵。验收:形成一个"仿真训练→真机试跑→数据回传→改进"的可重复闭环。
3 三个最关键的工程细节
① 控制频率对齐:策略(如 50Hz)与电机控制环(如 10kHz)频率不同。策略输出的目标要经插值/平滑后再下发;否则真机会"一格一格"抖动。
② 力矩限制与急停:真机第一原则是"不伤人、不摔坏"。务必加力矩上限、速度上限、倒地自动断电/抱闸,并设物理急停。
③ 先软后硬、先支撑后自由:首次上真机一定在"悬吊支架"或"人工扶住"下小力矩试跑,确认无误再逐步放开 —— 这是无数人形/四足项目的标准安全流程。
4 常见误区
✗ "仿真跑分高 = 真机能跑":仿真 100 分,真机可能 0 分 —— gap 没缩小前,分数没有迁移意义。
✗ "跳过域随机化":不做域随机化的策略,几乎必然在真机上因一点点参数偏差而失败。
✗ "推理时还在采样动作":训练时动作是"高斯采样",推理时必须取均值;继续采样 = 真机乱抖。
5 本节自测
1. 训练策略时,让仿真器随机扰动质量/摩擦/地面参数,目的是什么?
💡 这就是"域随机化"的核心:让策略适应参数不确定性,从而在真机(参数未知/有偏差)上也能工作。
2. 策略从 PyTorch 部署到真机时,推理阶段应该输出什么?
💡 训练时动作来自"均值+噪声"的高斯分布;推理(部署)时必须取均值(确定性),否则真机动作会随机抖动。
3. 首次把策略上真机时,最安全的做法是什么?
💡 安全第一:先软后硬、先支撑后自由,配合力矩/速度限幅与急停,是行业标准流程。
4. 下面哪项是"sim2real gap"的来源?
💡 电机不是理想力矩源,有延迟、带宽、饱和、发热降额 —— 这是仿真常忽略、真机必存在的 gap 来源之一。
5. 在第 10 步「数据闭环」中,真机试跑产生的数据里,哪种最值得回灌到仿真?
💡 见第 2 节第 10 步:只收集成功数据会掩盖失败模式,摔倒/异常数据更珍贵,它们能暴露 sim2real gap 的真实分布。
6. 关于「仿真跑分高 = 真机能跑」这个想法,正确的理解是?
💡 见第 4 节常见误区:仿真 100 分、真机可能 0 分。关键是缩小 sim2real gap,而不是盲目追求仿真分数或画面逼真。
7. 策略推理是 50Hz,而电机 FOC 控制环是 10kHz,正确做法是?
💡 见第 3 节工程细节①:策略(如 50Hz)与电机环(如 10kHz)频率不同,策略输出的目标要经插值/平滑后再下发,否则真机会「一格一格」抖动。
📌 本节要点速查:①sim2real gap 来自物理、执行器、延迟、噪声、算力五处差异;②十步流程:建模→辨识→域随机化→延迟注入→噪声注入→奖励塑形→PPO 训练→导出(ONNX/C++)→真机部署→数据闭环;③推理时取动作均值而非采样,导出后校验输出误差小于 1e-3;④三个工程铁律:频率对齐(插值)、限幅与急停、先支撑后自由。
📌 本节小结:sim2real gap 来自物理、执行器、延迟、噪声、算力五处差异;十步流程(建模→辨识→域随机化→延迟注入→噪声注入→奖励塑形→训练→导出→部署→数据闭环)把 gap 逐步缩小;三个工程铁律是"频率对齐、限幅急停、先支撑后自由"。
🤔 思考题:
1. 如果你的策略在仿真里能走、上真机却"高频抖腿",最可能漏了哪一步?
2. 域随机化范围设得太大或太小,分别会发生什么?
3. 真机上只有 50Hz 的推理能力,你会怎么设计观测/动作与控制频率?
6 参考来源