• 欢迎访问少将全栈,学会感恩,乐于付出,珍惜缘份,成就彼此、推荐使用最新版火狐浏览器和Chrome浏览器访问本网站。
  • 吐槽,投稿,删稿,交个朋友
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏少将全栈吧

Microduck 的 ONNX 策略是怎么下放到真机的:50Hz 控制循环里的推理艺术

Build in Public admin 5小时前 7次浏览 已收录 扫描二维码

# Microduck 的 ONNX 策略是怎么下放到真机的:50Hz 控制循环里的推理艺术

Meta Description: 用 RL 训出来的鸭子策略,最后要变成一张 ONNX 图塞进 RK3566 里跑。拆解 export.py 如何把观测归一化烙进图里、robotd 如何在 50Hz 循环里做推理、以及 61 维观测契约怎么让策略能热切换。


很多人复刻 Microduck,训到一张能看的策略就收工了。可真正让它”活过来”的是后半段:把那张 .pt 权重变成一张 .onnx,塞进一块 RK3566,让它在一根 50Hz 的控制循环里稳稳跑起来。

这一步看着简单,坑全埋在细节里:观测要不要归一化、推理占多少时间预算、多张策略怎么换、非实时系统上循环会不会漂。这篇就拆这个——从 export.py 到 robotd 的推理路径。

第一件事:把”归一化”烙进 ONNX 图里

训练时策略看到的是归一化后的观测,这几乎没人会有异议。可一旦导出成图,这个细节最容易翻车。

microduck_rl 的 scripts/export.py 用的是 mjlab 1.3.0 的 runner.export_policy_to_onnx()。这里最值钱的设计是:观测归一化(EmpiricalNormalization)是作为 MLPModel 的一个子模块被导出去的。配置里 obs_normalization=True,导出来的图就是 actor(normalizer(obs))——归一化器和你那几层 MLP 焊死在同一个图里。

这意味着两件事。第一,部署方拿到的 ONNX 自带了归一化,runtime 不用记一套 running mean/std 自己在运行时做。第二,反过来也成立:千万别自己拿 .pt 手工转 ONNX,否则图里没有归一化层,真机上策略看到的全是”没洗过”的观测,行为直接崩。官方 README 把这条写得非常死:永远部署 export.py 产出的 ONNX,绝不手工转换 checkpoint。

50Hz 循环:一张图在每 20ms 里怎么走

策略到了真机,活在 robotd 那个 50Hz 的控制循环里。这个 loop 是 tokio 里一个独占 runtime 的任务,周期 20ms,每拍做五件事:

  1. 一条 sync_read 把 15 个舵机加 IMU 板的数据一次读回来
  2. 拼 61 维观测 Observation::build
  3. Policy::infer 跑一遍 ONNX,得到 14 维动作(嘴部关节被排除在外,不算)
  4. 叠上 home pose 和缩放,低通滤波
  5. safety.apply 钳制后 sync_write 写回目标位置

推理这一步落在第 3 步。因为动作只有 14 维、观测 61 维、策略是个中小规模 MLP,在 RK3566 上跑 ONNX Runtime 的单次推理本来就很轻——真正要命的是别让推理拖垮整个 20ms 预算。robotd 把一个关键参数焊得很死,就是控制循环永远不阻塞在别的服务上,所有跨服务读取都是本地缓存、last-value-wins。

有个细节特别能说明这个系统的性格:MissedTickBehavior::Skip。control loop 用 tokio::time::interval 实现,但特意选了 Skip 而不是 Burst 或 Delay。

原因写得很直白:Burst 会把积压的 tick 连发,等于把电机命令叠在一起;Delay 更阴——它在每次 tick 之后按”现在+周期”排下一次,等于把每次唤醒的延迟都加进周期里,循环会越跑越慢、比配置的 50Hz 漂移。Skip 则保持原始调度、丢掉落下的 tick——这才是控制循环想要的语义。把感知挪到 mediad 之后,跟循环抢资源的对手也少了大半。

非实时系统的诚实:不为 50Hz 上 RTOS

复刻的人容易犯一个洁癖:50Hz 控制是不是该上实时系统?microduck 明确没干这事。robotd-design 里原话是:循环保持为一个 tokio task,不把它做成实时系统。

这背后是取舍的清醒:RK3566 跑 Linux,单次推理几毫秒、总线 sync_read 毫秒级,20ms 预算足够宽裕。与其为”硬实时”付出巨大代价(RTOS 迁移、驱动重写、生态归零),不如把功夫花在让循环不被阻塞上——推理要够快、感知挪到别的进程、客户端永远不能挡在循环前面。这是个工程判断,不是技术炫技。

61 维契约:让策略能”热切换”

为什么非要统一成 61 维观测?因为 runtime 要热切换策略。真机上 robotd 会按优先级在多个策略间切换:roulade > kick > ground pick > sit/rise > stand(按 |twist| 决定或强制)> walk。

切换的前提是每个策略读同一套 61 维契约:48 维本体感受 + 命令槽 [twist(3), head_pose(4), body_pose(6)]。某个任务不用某个命令槽,就补零,而不是删掉维度。这样一来,换策略只是换一张图、喂同一套观测,输入输出维度完全不变,runtime 不用做任何适配。就连前面拆过的背隙建模,也是在这个统一契约上加机械真实,不破坏规矩。

导出侧也有对应的排练场:scripts/infer_policy.py 可以在 CPU MuJoCo 里把 walk/stand/sitstand/roulade 几张图一起加载,用键盘做速度命令、G 摸地、Y 坐站、R 翻滚,提前验证”热切换”在真机前的表现。这是 sim2real 里被低估的一环——部署先排练,别直接上真机赌。

复刻者能抄走的三条

第一,归一化永远烙进图里。让 runtime 别碰归一化,部署时出问题先怀疑”这张图是不是 export.py 出的”。

第二,控制循环的调度选 Skip,别用 Burst 和 Delay。前者叠命令,后者让循环越跑越慢漂移。非实时 Linux 上做到”不阻塞”,比上 RTOS 更值。

第三,统一观测契约是热切换的地基。想复刻”能跑能坐能翻身”的鸭子,先想清楚你的多策略怎么共用一套接口,再谈训练。

训练让鸭子会走路,部署让鸭子真正站起来。前者是科学,后者是工程——microduck 把这套工程做成了一份能抄的答案,省掉的是复刻者最贵的那部分试错。


参考来源:

  • Pollen Robotics. microduck_rl 仓库:scripts/export.py(runner.export_policy_to_onnx、归一化烙进图、attach_metadata)、scripts/infer_policy.py(部署排练)、README(61 维观测契约、热切换、backlash 不改导出)。GitHub.
  • Pollen Robotics. microduck 仓库:docs/design/robotd-design.md(50Hz tick、Policy::infer、MissedTickBehavior::Skip、非实时判断、热切换优先级)、README(RK3566、50Hz、15 舵机)。GitHub.
  • 复刻项目进度记录: duck.whatled.com.
喜欢 (0)赏
[🍬谢谢你请我吃糖果🍬🍬~]
分享 (0)
关于作者:
少将,关注Web全栈开发、项目管理,持续不断的学习、努力成为一个更棒的开发,做最好的自己,让世界因你不同。