Lite3 强化学习策略迁移:训练迁移 + Sim-to-Sim + ONNX 部署(面向开发者的可执行流程)
本文目标:把“Lite3 强化学习策略从源仓迁移到 OrcaGym 并在 gRPC 仿真里稳定运行”的工作,整理成可按步骤执行的开发者流程。重点是“怎么开始迁移、怎么推进到可验收”,而不是展开每个实现细节。
术语约定(全文统一用法):
- 源仓:已有 Lite3 策略与部署链路的参考实现(文中也会称“源实现”)。
- 迁移端:在 OrcaGym 侧复刻/对齐这条链路的实现。
- 数据契约:obs/action 的维度、顺序、缩放、默认位、频率与 PD 参数等“必须一致”的约定。
相关主仓(用于读者快速定位项目背景):openverse-orca/OrcaGym
1) 选材:为什么选择这套 Lite3 仓库来做 RL 迁移入门
适合做“入门迁移教学”的仓库,通常要满足以下条件:
- 产物齐全:已有可运行的策略模型(例如 ONNX),并且能在仿真里实时推理。
- 接口明确:观测/动作/控制层有稳定的数据契约(维度、顺序、缩放、PD 参数、频率)。
- 迁移素材齐全:同时包含“源实现”和“迁移后示例/对比文档”,便于定位差异并建立检查清单。
本例的选材结构正好满足:
- 源仓侧包含策略产物(ONNX)、部署工程化(状态机/接口)、以及迁移桥接实现(训练框架侧的 Lite3 环境骨架)。
- OrcaGym 侧包含“最小可跑推理闭环”与大量对比说明,能够把迁移工作拆成可验收的阶段。
对初级开发者的建议:不要从“重新训练”开始,而是从“跑通已有模型并保持行为稳定”开始。能稳定跑通,才能判断“需要调参还是需要再训练”。
2) 如何在仓库里定位“强化学习在哪里”
仓库再大,强化学习相关内容通常都围绕同一条链路组织:
命令(cmd) → 观测(obs) → 策略推理(policy) → 动作(action) → 控制(PD/伺服) → 仿真步进(sim)
定位步骤建议按“从产物到接口”的顺序推进:
2.1 先找策略产物(最快定位 RL 的入口)
检查是否存在以下任意一种“策略产物”:
policy.onnx(部署推理常见).pt/.pth(训练权重常见)onnxruntime/torch/ppo/runner等关键字
只要定位到“模型文件 + 加载方式”,基本就能反推观测维度、动作维度、推理频率以及控制层接口。
2.2 再找观测与动作“数据契约”(迁移最关键)
数据契约通常会明确写出:
- 观测维度(本例为 45)
- 动作维度(本例为 12)
- 观测拼接顺序与缩放(例如角速度缩放、关节速度缩放、命令缩放)
- 动作缩放与默认关节位(动作是“相对默认位的偏移”还是“直接目标位”)
迁移成败高度依赖这里:只要观测/动作契约不一致,策略行为往往会呈现“能跑但像随机抖动 / 一动就倒 / 方向反了”。
2.3 最后找“控制层”:策略输出如何变成关节力矩
腿式机器人策略多数不是直接输出力矩,而是输出“目标关节位置偏移”,再用 PD 控制生成力矩。定位点包括:
kp/kd(PD 参数)- “Position Control / PD 控制”字样
- “目标关节位置 + 默认关节位 + 动作缩放”的描述
这一步能把“策略在训练时假设了什么控制接口”固定下来,也就确定了迁移端必须复现什么接口。
3) 迁移成功率:先定义“成功”的档位,再估计可预期程度
“迁移成功率”建议按可验收的行为档位定义。不同档位对应不同工作量与可预期程度。
3.1 迁移成功的 4 个档位(从易到难)
- S0:推理可用
- ONNX/权重可加载,输入输出维度匹配,推理稳定运行。
- S1:闭环可控(入门成功)
- 机器人能稳定站立,能响应前后/左右/转向命令,方向正确,不卡死、不持续发散。
- S2:行为接近源仓(常见目标)
- 同一组命令下,步态与速度响应与源实现接近;差异主要来自动力学/接触而非“数据契约错误”。
- S3:跨域鲁棒(高难目标)
- 更换地形/摩擦/质量等仍能保持稳定;通常需要训练端加入域随机或重新训练。
3.2 可预期程度(即“成功率”表达方式)
在不改策略权重的前提下:
-
达到 S1:预期较高
条件是:观测/动作/PD/频率/命令归一化对齐,初始高度与地形干扰较小。 -
达到 S2:预期中等
在 S1 的基础上,还需要逐步收敛动力学差异(接触、摩擦、阻尼、初始姿态)。属于典型 sim-to-sim 工作量。 -
达到 S3:预期较低
若训练时没有覆盖迁移端的动力学分布,S3 往往需要重新训练或显著增强训练随机化。
这套分档的意义是:迁移推进时,先把目标锁定在 S1(可验收、可迭代),避免一开始就以 S3 的标准评估,从而误判“迁移失败”。
4) 迁移流程(主体):按阶段推进,每阶段都有明确产出与验收标准
以下流程把迁移拆成 4 个阶段。每一阶段都有“输入、产出、验收、常见卡点”,以便初级开发者按清单推进。
4.1 阶段 A:把数据契约固化成“迁移合同”
输入:
- 策略模型文件(例如 ONNX)
- 源仓侧对观测/动作/控制层的描述(来自既有文档/实现)
产出(可整理为单页“迁移合同”):
- 观测:维度、顺序、各分量缩放系数
- 动作:维度、顺序、动作缩放、默认关节位
- 控制:PD 参数(kp/kd)、控制类型(目标关节位置+PD)
- 频率:物理步长、控制频率、策略推理频率(以及 skip/decimation 的等价关系)
- 命令:前后/左右/转向范围、归一化方式、进入观测前的缩放方式
验收标准:
- 迁移合同可直接用于做 S0/S1 排错:一旦出现异常,能明确“是哪一条合同没满足”。
常见卡点:
- 关节顺序不确定:需要明确“policy order vs robot order”,并准备映射策略。
- 默认关节位混淆:区分“用于 PD 的 neutral”与“用于策略相对量的 default”。
4.2 阶段 B:闭环跑起来(S1):离线推理 → gRPC 仿真闭环
输入:
- 阶段 A 的迁移合同
- OrcaGym 侧的推理运行入口(键盘命令 + 推理 + 控制 + 仿真)
产出:
- S0:推理稳定运行(维度/类型正确)
- S1:闭环可控(站立稳定、命令响应正确)
验收标准(可固定为三步):
- 站立稳定:在无移动命令下不发散、不自激振荡。
- 方向正确:前后/左右/转向的符号与坐标系一致。
- 不易卡死:命令切换时不出现长期停滞或持续发散。
常见卡点:
- “能加载但一动就倒”:通常是动作缩放/默认位/PD 参数不一致。
- “完全不响应命令”:通常是命令未进入观测或被缩放到接近 0。
- “左右/转向反了”:通常是坐标系或按键映射符号不一致。
4.2.1 代码怎么写:最小可跑闭环的 5 个模块(可按该结构落地)
迁移实现本质上是在新工程里复刻一条固定的数据链路:
命令(cmd) → 观测(obs, 45) → 策略推理(policy, 12) → 动作适配(action) → 环境 step/render
建议把代码拆成 5 个模块,每个模块只做一件事,便于调试与复用。
模块 1:配置(Config)
职责:把“迁移合同”落成可读的配置对象/字典,至少包含:
- 关节顺序(12 个)
- 执行器顺序(12 个)
- neutral 站立角(用于环境 reset 与动作 offset 基准)
- policy default 关节位(用于 obs 相对量与策略动作反解)
kps/kds(控制层参数)omega_scale/dof_vel_scale/max_cmd_vel(观测缩放)action_scale(动作缩放)
关键点:关节顺序必须与 XML 定义一致;policy default 与 neutral 不是同一组角度,不能混用。
模块 2:观测构建(Obs Builder)
职责:从环境/agent 的状态拼出 45 维观测,保持顺序与缩放一致。
实现建议:
- 单独写一个
compute_lite3_obs(...)函数,输入是“已抽取好的结构化量”(角速度、重力投影、命令、关节 pos/vel、上一动作),输出是 45 维向量。 - 环境绑定部分单独写一个
compute_lite3_observation_from_env(env, agent_name),只负责“从 env/agent 抽取字段”,不在这里做策略相关逻辑。
模块 3:策略推理封装(Policy Wrapper)
职责:屏蔽 onnxruntime 的输入输出细节,提供一个可调用对象 policy(obs)->action12。
实现建议:
- 在封装里强制
float32、强制 shape(单样本与批量都支持) - 如果模型不支持动态 batch,封装内部做逐条推理并拼回结果
模块 4:动作适配(Action Adapter)
职责:把策略输出的 12 维动作转换成环境可接受的 action(通常是归一化后的 [-1, 1])。
典型计算链路:
policy_action(12)是相对dof_pos_default_policy的偏移target_dof_pos = policy_action * action_scale + dof_pos_default_policy- 环境 action 常以 neutral 为基准:
action_offset = target_dof_pos - neutral_joint_values - 归一化:
normalized_action = action_offset / action_scale(并 clip 到 [-1, 1]) last_action保存策略原始输出(不是环境 action),用于下一步观测拼接
模块 5:主循环(Runner Loop)
职责:串起 cmd/obs/policy/action/step/render,并控制运行频率。
实现建议:
- reset 后先 step 一次零动作,确保 agent 内部缓存初始化完毕
- 非 RL 模式下只发零动作维持站立;进入 RL 模式后才执行推理
- 每次循环都按“设置命令 → 计算 obs → 推理 → 动作适配 → env.step → env.render”的顺序
- 用固定的 realtime 步长控制策略频率(例如 50Hz),避免帧率飘动导致行为变化
4.3 阶段 C:sim-to-sim 对齐(S2):先数据 → 再时序 → 再动力学
本阶段的关键是“不要同时改多项”。推荐按三层顺序推进:
-
C1:数据层:观测/动作顺序与缩放、默认位、关节映射
目标:排除“看起来像策略退化、实际是数据错”的问题。 -
C2:时序层:物理步长、控制频率、策略推理频率、命令平滑
目标:排除“频率不一致导致的抖动/迟滞/节拍异常”。 -
C3:动力学层:地形、摩擦、阻尼、初始高度/初始姿态、接触参数
目标:把差异收敛到“合理的 sim 差异”,从而达到 S2。
验收标准:
- 相同命令下,速度响应、稳定性、转向/侧移行为接近源仓;残余差异能被动力学差异解释,而非契约差异。
常见卡点:
- 抖动:多由 kd 或频率不一致触发。
- 步态明显偏:多由接触/摩擦/关节阻尼差异触发。
4.4 阶段 D:训练迁移(面向 S3 或定制化需求):重新训练并回灌部署(持续迭代)
当目标从“跑通已有策略”升级到“适配新场景/新动力学/更强鲁棒性”,才进入训练迁移。
输入:
- 阶段 A 的迁移合同(必须固化到训练端)
- 训练框架侧的 Lite3 环境骨架(保证观测/动作/控制层契约一致)
产出:
- 新策略权重(训练产物)
- 可部署策略(例如 ONNX)
- 一份“训练元数据记录”(观测/动作/PD/频率/命令范围/随机化配置)
验收标准:
- 新策略在训练端表现满足目标任务
- 新策略在 OrcaGym 侧按 S0→S1→S2 的流程回归通过
常见卡点:
- 训练端契约与部署端契约不一致:会造成“训练好、部署差”的典型问题。
- 训练随机化不足:导致 S3 目标难以达成。
5) 附录:
5.1 当前对齐状态(示例:迁移端相对源实现的对齐/差异说明)
已对齐(迁移端与源实现一致或已明确修复):
- 左右移动对称性:A 键与 D 键使用完整速度(±0.3 m/s),归一化后为 ±1.0
kd:已对齐为 1.0- 初始高度:已上调约 10cm(示例:0.101 m)
- 前进速度:0.5 m/s
- 转向速度:±0.785 rad/s
保留差异(有意为之;需明确差异原因与预期影响):
- 后退速度:示例中使用 50% 速度(-0.25 m/s)以改善策略对负值命令的响应(如需严格复现,可还原为 -0.5 m/s)
- 命令平滑:示例中加入指数移动平均(α=0.3)降低命令突变导致的抖动(进行 sim-to-sim 对齐时,可先关闭平滑,稳定后再逐步打开)
action_scale:示例中统一为 0.25(源实现可能对不同关节采用不同 scale;如出现明显行为差异,可优先回到“关节级 action_scale”对齐)
5.2 频率与控制参数对比(复制文本)
仿真频率对比(节选)
| 参数 | 原项目 | 当前版本 | 说明 |
|---|---|---|---|
| 物理时间步长 | 0.001s (1ms) | 0.001s (1ms) | ✅ 一致 |
| 物理仿真频率 | 1000 Hz | 1000 Hz | ✅ 一致 |
| FRAME_SKIP | 5 | 5 | ✅ 一致 |
| ACTION_SKIP | 4 | 4 | ✅ 一致 |
| 策略更新频率 | 50 Hz | 50 Hz | ✅ 一致 |
| 策略更新时间步 | 0.02s (20ms) | 0.02s (20ms) | ✅ 一致 |
控制循环频率(节选)
| 参数 | 原项目 | 当前版本 | 说明 |
|---|---|---|---|
| PD控制频率 | 200 Hz | 200 Hz | FRAME_SKIP=5, 1000/5=200Hz |
| 策略推理频率 | 50 Hz | 50 Hz | ACTION_SKIP=4, 200/4=50Hz |
| 实时控制频率 | 50 Hz | 50 Hz | 使用 REALTIME_STEP 控制 |
键盘控制速度(节选)
| 控制类型 | 原项目配置 | 当前配置 | 实际速度值 | 归一化后 | 状态 |
|---|---|---|---|---|---|
| 前进 (W) | 0.5 m/s | 0.5 m/s | 0.5 m/s | 1.0 | ✅ 一致 |
| 后退 (S) | -0.5 m/s | -0.5 m/s | -0.25 m/s | -0.5 | ⚠️ 降低50% |
| 左移 (A) | 0.3 m/s | 0.3 m/s | 0.3 m/s | 1.0 | ✅ 一致 |
| 右移 (D) | -0.3 m/s | -0.3 m/s | -0.3 m/s | -1.0 | ✅ 一致 |
| 转向速度 (Q/E) | ±0.785 rad/s | ±0.785 rad/s | ±0.785 rad/s | ±1.0 | ✅ 一致 |
| Turbo倍数 | 3.0x | 3.0x | 3.0x | - | ✅ 一致 |
保留的差异(设计选择,节选)
| 差异项 | 原项目 | 当前版本 | 影响 |
|---|---|---|---|
| 后退速度缩放 | -0.5 m/s | -0.25 m/s(50%) | 降低后退速度 |
| action_scale | 不同关节不同 | 所有关节相同(0.25) | 简化处理 |
| 地形 | terrain_test_usda | terrain_ellipsoid_low_usda | 无障碍物 |
| 命令平滑 | 无 | 指数移动平均(α=0.3) | 减少命令突变 |
5.3 迁移检查清单与架构对比
核心架构对比(节选)
当前架构:
Python仿真 (mujoco_simulation.py) ↕ UDP (端口20001/30010) C++控制器 (rl_deploy) → StateMachine → RLControlStateONNX → PolicyRunner (ONNX Runtime)
目标架构:
Isaac Gym环境 (Lite3类) → compute_observations() (45维) → ONNX Policy / PyTorch Policy → pre_physics_step() (PD控制)
迁移检查清单(节选)
- 模型转换
- MJCF → URDF转换
- URDF放置在 legged_gym/resources/robots/Lite3/
- 关节命名和顺序验证
- 观测空间
- 维度:45维
- 缩放参数:omega_scale=0.25, dof_vel_scale=0.05
- 顺序:[ang_vel, gravity, cmd, pos, vel, last_action]
- 动作空间
- 维度:12维
- 缩放:action_scale
- 默认位置:default_dof_pos
- PD控制器
- kp = 30.0
- kd = 1.0
- 控制公式:τ = kp(q_d-q) + kd(dq_d-dq)
- 策略模型
- ONNX模型路径正确
- 输入维度:45维
- 输出维度:12维
- 推理结果验证
- 控制频率
- 物理步长:1ms(DT=0.001)
- PD 控制频率:( f_{pd} = 1 / (DT \times FRAME_SKIP) )
- 策略推理频率:( f_{policy} = f_{pd} / ACTION_SKIP )
- 建议:把这些频率写进“迁移合同”,并在代码里打印出来做自检,避免“表里写 50Hz,实际跑成别的频率”