最近在搞一个 25 厘米级别的四足机器人 Microduck,核心目标是把手头在英伟达 GPU 上训好的强化学习策略,真的搬到一个 RK3566 的开发板上让它自己走起来。整个流程走下来,最大的感受是:"训模型"和"上实机"完全是两件事。这篇手记就是把从 GPU 训练、模型导出、RK3566 端侧推理,到最后实机联调的完整过程做一个记录,目标是让同样想把强化学习机器人跑通实机的朋友,少走几个我踩进去又爬出来的坑。
适合谁来读?如果你已经在仿真环境里训过强化学习策略,或者正准备把一个仿真模型部署到 Jetson、RK 系列、树莓派这类边缘设备上,这篇内容会比较对口。我会把训练侧要注意的、部署侧容易翻车的点,以及实机联调时的排查思路都摊开来讲。
1. 项目全貌:Microduck 到底是个什么东西?为什么要走这一趟"落地的旅行"?
1.1 Microduck 硬件底子
Microduck 不是那种几十公斤的大狗,它是一只能放上桌面的小型四足机器人,体长 25 厘米左右,整机重量很轻,拿在手里就能带走。每条腿有 3 个自由度,一共 12 个关节,用的是小型伺服电机加减速器的方案,关节带编码器反馈,主控板上跑 Linux 系统。这类硬件形态在开源社区中有不少类似项目,它最大的价值是:便宜、体积小、适合在室内做强化学习算法的快速验证。
但"适合验证算法"不代表"适合跑重型神经网络"。它的主控是瑞芯微 RK3566,4 核 Cortex-A55,主频最高大概 1.8GHz,NPU 算力 0.8TOPS,内存看配置,一般是 2GB 起步。这块芯片最大的特点是功耗低、外设接口全、成本友好,在边缘设备里是典型"够用但不算强"的存在。跑个传统 C 语言控制循环绰绰有余,但你要是想塞一个大模型进去,那就另当别论了。
1.2 为什么偏偏选 RK3566
很多朋友看到这儿会问:真要部署机器人,直接上 Jetson Orin 不香吗?香是香,但贵。Orin 的算力确实强,跑视觉大模型都没压力,可它的功耗和价格,注定是"开发原型"或者"演示 Demo"级别才舍得用。而 RK3566 这档的芯片,才是很多批量产品里真实会用的算力平台。对一个要做落地产品的团队来说,用 RK3566 跑通强化学习策略这件事,本身就很有参考价值。
这里我多说一句。很多人觉得边缘设备跑 RL 策略很难,实际上强化学习策略网络本身通常都很小。比如四足机器人最常用的 MLP 策略,输入层几十维到一百多维,中间两层 256 或 512 的隐藏层,输出十几个维度的关节动作指令,整个网络参数不到 10 万个,计算量也就几百万次乘加运算。这种网络在 RK3566 上无论是 CPU 推理还是 NPU 推理,都有不小的余量。难点从来不在"跑得动跑不动",而在"能不能稳定实时地跑"以及"模型精度在端侧会不会缩水"。
1.3 这套流程解决的核心问题
我这次的目标很明确:把 Isaac Gym 里训好的强化学习策略,完整地迁移到 RK3566 实机,用实机传感器数据作为输入,让策略实时输出 12 个关节的目标角度,机器人能正常行走、转弯、抗扰动。训练端用的是英伟达 GPU,依赖 Isaac Gym 的并行仿真能力来采样,训练策略本身用 PPO 这类强化学习算法;部署端是 RK3566,需要把 PyTorch 模型转换、量化、然后集成到实时控制循环里。
这条路走通之后,后续想在端侧微调策略、换不同的奖励函数、加视觉感知模块,都只需要动单个环节,整体链路是不变的。这对想在校项目里做机器人,或者想在公司产品里验证 RL 控制可行性的朋友,都是一个可以复用的样板。我接下来会按照"训练侧决策—模型导出—端侧推理—实机联调—踩坑记录"这个顺序来写,尽量把每一步背后的"为什么"讲透。
2. 训练侧的关键决策:在 GPU 上跑出的策略,要预先想到端侧的一切
2.1 仿真与算法选型
训练强化学习机器人策略,仿真环境是绕不开的。纯在实机上跑强化学习,数据采集效率极低,而且机器人摔几次硬件就废了。我这边用的是 Isaac Gym,它在英伟达 GPU 上做并行物理仿真,几千个环境同时跑,配合 PPO 类算法,一张中端显卡就能在几小时内训出一个比较能看的跆拳道策略。这类"大规模并行仿真+PPO"的组合,已经是当前足式机器人强化学习的事实标准。
为什么选 PPO 而不是其他算法?简单说,PPO 在连续动作空间、并行环境条件下的稳定性和调参友好度都比较好。它不是样本效率最高的算法,但胜在训练曲线可控,超参数鲁棒性不错,对四足机器人这种策略容易评估的场景,PPO 的信任域约束能有效避免策略更新步长太大导致奖励崩掉。如果是为了样本效率,SAC 可以考虑,但实际项目里 PPO 的工程积累更多,遇到问题更容易找到参考。
这里有一个很重要的认知:强化学习训练出的策略,本质是一个"从观测到动作"的函数。它不是在存一大堆规则,而是用一个神经网络把"此刻状态"映射成"下一步该给关节什么指令"。这个理解贯穿后续所有部署过程——训练和部署之间,唯一的交接物就是这个神经网络结构和权重。
2.2 观测空间和动作空间的设计
观测空间的设计直接决定部署时的传感器方案。我这次用的是比较标准的四足机器人 RL 观测组合:机体姿态(IMU 解算出来的 roll、pitch 或者四元数)、机体角速度、12 个关节的角度和角速度、上一时刻的动作输出,以及目标线速度和目标转向角速度。总共大概 40 维输入。之所以保留"上一时刻动作"作为观测,是为了让策略的动作输出更平滑,避免相邻时间步出现跳变,这在部署到实机时尤其重要,因为物理电机的加速度是有限的。
动作空间是 12 维,对应 12 个关节的目标角度。注意,策略输出的是目标角度,不是力矩。实际的力矩闭环是交给底层 MCU 里的 PD 控制器去做的。这样设计的好处是:高频率的力矩控制(1kHz 以上)可以在 MCU 层保证,而强化学习策略只需要以较慢的频率(比如 100Hz 到 500Hz)输出目标角度,大幅降低了对端侧推理实时性的压力。这个分层控制结构是四足机器人部署的经典范式,既能发挥 RL 在高层决策上的优势,又能复用传统控制在高频伺服上的可靠性。
2.3 奖励函数和域随机化的隐性约束
训练效果好不好,奖励函数是命根子。我这次的核心奖励是"跟目标速度的匹配度",加上一大堆惩罚项:能耗惩罚、关节动作变化率惩罚、机体倾斜惩罚、关节限位惩罚、摔倒惩罚等等。很多人低估了惩罚项的重要性。单纯奖励前进速度,策略会学到用尽一切夸张动作去挪动,这在仿真里也许能拿高分,但一旦上实机,电机根本承受不了那种暴力动作。所以惩罚项不是为了"罚"而罚,而是为了让策略学到平稳、自然的运动模式,这种模式才有 sim-to-real 的可能。
另外必须提域随机化。仿真和实机之间永远有差距:仿真里的摩擦系数是固定的,实机的每个脚底磨损程度不一样;仿真里的电机响应是理想的,实机的电机在低电压下会有明显延迟;仿真里的传感器是干净的,实机的 IMU 有噪声和温漂。我的做法是:在训练时,对质量、摩擦力、重心偏移、电机强度、观测噪声、控制延迟这些参数都加入随机扰动范围。这样策略在仿真里就被迫学到"不依赖精确参数也能走"的鲁棒行为。实测下来,域随机化是直接决定实机能否走起来的第一要素,比任何部署技巧都重要。
3. 部署链路的硬仗:从 PyTorch 权重到 RK3566 上能跑的推理
3.1 导出、转换、量化三板斧
训练结束,手里拿到的是一个 PyTorch 权重文件,它是浮点数的网络参数。RK3566 要跑这个网络,第一步是把 PyTorch 模型转成端侧引擎能加载的格式。我这里的选择是用 ONNX 作为中间格式,然后把 ONNX 转成 RKNN 格式(瑞芯微的官方推理格式)。RKNN 是瑞芯微 NPU 的私有格式,转完之后可以直接加载到 NPU 上执行,也可以回退到 CPU 执行。
导出 ONNX 这一步看起来简单,但也有讲究。你要确保模型输入输出的维度、名字、数据类型都是可控的。我踩过的坑是:PyTorch 模型里如果有某些动态维度或者 Python 控制流,ONNX 导出会失败或者导出的计算图带了多余的节点。解决办法很简单:尽量用纯 Tensor 操作,不要用 Python 的 if、for 去控制张量逻辑。大多数 MLP 网络都是纯 Tensor 运算,导出很顺利。
转换成 RKNN 时,官方工具链提供了校准量化功能,它会把浮点权重和激活值转成 INT8,这样可以大幅减小模型体积并提升 NPU 推理速度。但量化必然带来精度损失。这个损失在小型 MLP 策略上通常小到可以忽略,但也不是绝对的。我的建议是:先不量化,用 FP16 或者 FP32 跑一次看效果,如果实机表现正常,再尝试 INT8 量化和混合量化。量化不是必须的,尤其在推理频率要求不高、CPU 也能满足延迟预算的情况下,完全可以跳过量化以保精度。
3.2 CPU 还是 NPU?先算算延迟预算
跑强化学习策略,最关键的指标不是算力峰值,而是端到端延迟。控制回路是有时间窗的。假设我在实机上用 200Hz 跑策略推理,那每个控制周期是 5 毫秒。在这 5 毫秒里,要完成 IMU 数据读取、状态估计、策略前向推理、生成 12 个关节命令,再传给底层电机控制器。留给推理的时间,最多只有 1 到 2 毫秒。如果推理超过控制周期,就会出现"策略跟不上执行"的尴尬:电机必须不断等待新命令,整个控制环会抖动甚至发散。
来看 RK3566 的实际数据。A55 大核跑一个 256x256 的 MLP 前向推理,大约需要 1 到 3 毫秒,取决于实现方式和线程调度。如果用 NPU 跑 INT8 量化模型,可以压缩到 1 毫秒以内。看起来 NPU 优势明显,但要注意一个附加成本:数据在内存和 NPU 之间来回拷贝也是要时间的,如果控制循环里每一步都要把传感器数据从 CPU 侧拷贝给 NPU、再从 NPU 拿结果,这个开销可能抵消 NPU 的算力优势。
我的实际方案是:优先用 CPU 跑 ONNX Runtime,因为控制循环天然是在 CPU 上,省去数据搬运。当 CPU 推理时间逼近控制周期时,再评估 NPU 的可行性。对 Microduck 这种小型 MLP 策略,CPU 推理已经足够了。这算是一个经常被忽略的经验:边缘部署不是"有 NPU 就一定用 NPU",而是要算"全链路延迟"这笔账。
3.3 把推理服务集成到实时控制循环里
模型转换只是第一步,真正让机器人动起来,需要把模型推理嵌入到控制循环里。控制循环的主干逻辑是:读取编码器和 IMU 数据,经过状态估计得到当前状态,把状态整理成策略需要的观测向量,推进策略推理,得到 12 个目标角度,然后通过串口或总线发给底层电机驱动板,由驱动板上几百微秒级的中断伺服循环执行 PD 计算。
有一个工程细节特别重要:控制循环的频率要稳定,不能有大抖动。Linux 系统不是实时操作系统,调度延迟不稳定,线程可能被其他任务抢占。为了减少这个影响,我把控制线程设置为最高优先级,并且用循环定时器驱动,尽量不依赖阻塞型 sleep。同时为了保险,实机上还加了"看门狗"逻辑:如果控制循环超过设定时间没有输出,底层电机板会自动锁死,防止机器人处于无指令状态乱动。
观测向量的拼装顺序必须和训练时完全一致。这个我在下一节细讲,因为它是实机调试中最容易翻车、也最隐蔽的问题。
4. 实机联调现场记录:从"动起来"到"走得稳"
4.1 第一件事:频率和时延的对账
实机第一次上电启动控制程序时,别急着让它走,先把频率对账这件事做完。我这里说的频率对账,包含两层意思:一是策略推理频率和底层伺服频率是否匹配,二是各环节延迟加起来有没有超过控制周期预算。
我实际跑出来的数据是:底层电机 PD 控制在 1kHz(由电机驱动 MCU 完成),策略外环在 200Hz(每 5 毫秒出一个新指令)。把 IMU 读取、编码器读取、策略推理、串口发送的时间加起来,一个循环大概是 3.5 毫秒,留了 1.5 毫秒的余量。这个余量看起来很充裕,但实际运行中,Linux 的系统调度可能会有 1 到 2 毫秒的抖动,所以真正安全的设计应该把负载压到 50% 以内。如果频率对不上,机器人会表现出诡异的抖动:看起来在快步,但其实每个关节的命令都是"过期的",整体步态完全没有协调性。
一个有效工具是日志系统。我在控制循环里给每个环节打上时间戳,记录下来一轮循环的耗时分布。这样一眼就能看出是哪一环拖了后腿。如果没有这层日志,你会在实机上看到各种莫名其妙的问题,来回调参也找不到根因。
4.2 观测对齐:最容易翻车的地方
我来重点说这个。训练时,观测向量的每个维度是有固定顺序的:前几位是 IMU 姿态角,接着是角速度,然后是 12 个关节角度、12 个关节角速度、上一时刻的动作,还有目标速度指令。这个顺序一旦在部署时拼错,哪怕只是一维放错了位置,策略的输出就会完全乱套。
实机上的坑在于,你读取 IMU 得到的数的单位、坐标系定义,和仿真里不完全一样。比如仿真里四元数可能是 w、x、y、z(最后一个维度),但实机 SDK 输出的可能是 x、y、z、w,不转换就喂给模型,模型输入分布和训练时对不上,表现自然会崩。关节角度的正负号也一样,不同电机的编码器零位和旋转方向定义可能和仿真相反,如果不在部署代码里做符号映射,策略指令会让每个关节往错误方向用力,机器人本能地"铲地"。
我的做法是写了一个"观测对齐脚本"。实机上采集一段静止和缓慢摆动的数据,保存成 CSV,然后在电脑上用同样的策略模型做前向推理,和仿真同输入时的输出做对比,确认观测拼装顺序和数值范围正确。这个步骤能在不上电的情况下发现绝大多数观测对齐问题,强烈建议做这一步,别直接拿实机当试验品。
4.3 实机表现与 sim-to-real gap 的补救
当第一版策略真正让 Microduck 站起来的时候,我的心情是复杂的:它确实站住了,也能往前走,但步态很不自然,走起来像喝醉了一样歪歪扭扭,还偶尔莫名抖一下。这是非常典型的 sim-to-real gap 表现。好消息是,这些问题大多有迹可循。
最常见的问题是输入延迟造成的行为抖动。策略在仿真里假设观测是零延迟的,但实机从传感器到决策有 2 到 3 毫秒延迟,相当于每步动作都"慢了半拍"。尤其在高频步态下,半拍就足以让步态相位错乱。解决方法有两个方向:第一个是加观测延迟补偿,把上一拍或者前几拍的动作信息做线性外推,抵消延迟;第二个是回到训练侧,把域随机化里的控制延迟范围调高一些,重新训练一个对延迟更鲁棒的策略。我个人更推荐第二种,因为它从源头解决了问题,实机端不需要做太多补偿逻辑。
另外,电机发热也是个不可忽视的问题。强化学习策略往往学到的是"高频小幅抖动"这种动作模式,仿真里不消耗能量,实机上却会让电机持续发热,甚至有异味。我在部署代码里加了动作低通滤波和动作变化率限制,相当于给策略输出加了一道"物理护栏",让电机不会频繁做反向加速。这个操作牺牲了一点点动作灵活性,但大幅提升了实机运行的稳定性和续航。
5. 踩坑实录与排查速查表
5.1 我印象最深的几个坑
第一个坑是 RKNN 量化后策略完全失效。当时转换成 INT8 之后,模型加载和推理都正常,控制循环也跑得顺畅,但机器人就是不会走了,四条腿只会在原地轻微抽搐,偶尔还能自己绊倒。排查半天,问题出在量化校准集上。校准集必须覆盖部署时会遇到的数据分布,我偷懒用了几百张随机噪声图(因为最初是拿视觉模型做测试的流程改的,没换成机器人状态数据),这导致量化后的权重对机器人观测分布严重失真。后来换成实机录制的一万条状态数据做校准,量化后的模型表现和浮点版本基本一致了。
第二个坑是 Linux 系统调度导致的控制抖动。有段时间机器人走起来总是平均 2 秒左右突然顿一下,像被人按了暂停键。查日志发现是系统里某个后台服务周期性唤醒,占用了 CPU,导致控制循环偶尔超时。我把控制线程绑到指定 CPU 核、设置实时调度优先级、关掉无用的后台服务之后,问题就消失了。在 Linux 上做实时控制,这类系统级干扰的排查要留足预算。
第三个坑比较蠢,但也值得写出来。我在实机端拼观测向量时,忘记把"目标速度指令"这一维从初始值改成正弦变化的期望速度,导致机器人只会在原地小碎步。因为仿真里策略接收的目标速度一直是变化的,而实机端如果给了一个恒定目标速度,策略会认为"已经到了目标速度,不需要努力"——理由很合理,但你不看代码根本想不到是这种低级问题。
5.2 问题排查速查表
我把这次遇到的高频问题整理成表格,方便实机调试时快速定位。
| 故障现象 | 常见原因 | 排查思路 |
|---|---|---|
| 机器人完全不动 | 控制循环没启动 / 电机使能没开 / 观测向量全为零 | 先在仿真里跑同一份模型,确认输出非零;再检查电机驱动板状态灯 |
| 原地抽搐,无法稳定站立 | 观测向量拼装顺序错误 / 关节角度正负号反了 | 跑观测对齐脚本,对比浮点模型输出;核对电机方向映射表 |
| 前进姿态奇怪,歪歪扭扭 | 延迟过大 / 域随机化不足 | 查日志看单轮循环耗时;增加训练时的延迟随机范围 |
| 偶尔突然不受控地猛冲 | 控制循环超时 / 策略输出未限幅 | 检查线程调度优先级;给策略输出加饱和限幅和变化率限制 |
| INT8 模型表现远差于浮点 | 校准集不合适 / 量化位宽冲突 | 用实机数据重新校准;尝试混合量化保留关键层为 FP16 |
| CPU 推理太慢 | 模型过大 / 线程未绑定大核 / 未开 NEON 优化 | 用 ONNX Runtime 的线程配置绑定 CPU;确认编译时开了 NEON 加速 |
这张表不能代替具体分析,但能帮你快速圈定问题范围。整个联调过程,本质上就是在"仿真—模型—端侧—实机"这四层之间来回找差异,找到差异所在,离解决就不远了。
我个人在实际操作中的体会是,强化学习机器人项目里,真正消耗时间的地方往往不在模型训练,而在部署环节那些极其琐碎的细节上。一个小的单位错误、一次坐标系没对齐、一个延迟没补偿,都可能让好端端的策略在实机上表现成一堆"智力障碍"的动作。所以,如果想做这类部署,建议提前把工具链铺好:日志、观测对齐脚本、调试平台、底层电机保护机制都准备齐全,再上电实测,能省下大量精力。接下来我会继续把重心放在端侧策略微调和 NPU 量化上,希望之后能把这套流程跑得更省心,也欢迎同样在做足式机器人部署的朋友多交流。