ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从 GPU 到 RK3566:Microduck 强化学习机器人部署实战手记

从 GPU 到 RK3566:Microduck 强化学习机器人部署实战手记 从英伟达 GPU 到 RK3566 实机Microduck 25 厘米强化学习机器人部署手记前阵子把一台 25 厘米足的 Microduck 从 Simulink 里的仿真环境搬到了 RK3566 实机上跑整个过程比我想象中要折腾但也特别有意思。这个项目本质上是把强化学习训练出来的运动控制策略部署到一块几百块钱的嵌入式开发板上让机器人在真实世界里走路、转向并且保持稳定。训练用的环境是老黄的 RTX 显卡推理端则完全跑在一颗四核 A55 的 ARM 芯片上这两个平台之间的鸿沟比想象中大得多。这篇手记就当作自己踩坑过程的记录也希望给正在做类似“训练在 GPU、部署在边缘设备”的强化学习机器人项目的朋友一点参考。内容会覆盖整体设计思路、训练环境搭建、模型转换、RK3566 实机部署以及我遇到的几个典型问题全程干货没有废话。1. 项目整体设计与硬件选型复盘1.1 为什么选择 25 厘米这个尺寸以及 Microduck 形态Microduck 本质上是一只四足机器人整机高度大约 20 厘米体长带尾巴大约 25 厘米。这个尺寸级别有几个优点首先是桌面级测试非常方便不需要像大型机械狗那样必须去空旷场地其次是硬件成本可控整机物料加控制板加起来在 1500 元上下比动辄几万块的科研级机器人便宜太多最后是安全风险小就算强化学习策略在实机上跑飞了也就是翻个跟头不会造成什么人身伤害。四足形态本身也是一个很好的强化学习验证平台。相比双足四足天然具有更大的静稳定域控制难度相对低一些但又要处理接触、摩擦、协调等问题非常适合用来训练鲁棒步态策略。我之前跑过单足跳跃和双足平衡的实验最后经验是四足是性价比最高的入门载体既有足够的动力学复杂度又不会让你在调试上耗尽精力。选型时的另一个考虑是整机的执行器件。25 厘米级别对应的是一般称为“中尺寸”的舵机比如串行总线舵机峰值扭矩大概在 5-8 kg·cm 左右。每条腿三个自由度总共十二个自由度这种配置既能保证动作的灵活性又不会让批量力控伺服驱动的成本太高。我们可以利用强化学习在仿真里直接输出十二个关节的目标角度或力矩然后通过总线协议下发到舵机整个控制链路非常直接。1.2 RK3566 才是真正落地的主角在很多强化学习项目里大家习惯把目光放在 GPU 训练上但真实落地的时候你会发现推理端往往比训练端更考验功力。这次我选的边缘设备是 RK3566 开发板一颗瑞芯微的 SoC四核 Cortex-A55 处理器主频最高 2.0 GHz集成 Mali-G52 GPU 和 0.8 TOPS 算力的 NPU支持 INT8/INT16 量化推理。为啥选它不选树莓派树莓派的实际算力也不错但 RK3566 有几个明显优势首先是 NPU 的存在让 INT8 推理效率比纯 CPU 高不少其次是编解码能力强虽然做机器人控制用不上最关键的是价格——某宝上带 2GB 内存的开发板一百多块就能拿下整机成本控制真的很重要。但这里得说清楚一个现实0.8 TOPS 的 NPU 放在 2025 年看并不算强跑大模型是完全不行的但跑我们的强化学习控制策略绰绰有余。如果策略网络是 MLP多层感知机输入维度一般在 30~50 之间网络结构三层各 256 个神经元单次前向推理的计算量大概在几百万次乘加这个规模即便在纯 CPU 上也能在 10 毫秒内跑完。所以 NPU 只是加速手段不是必需品真正的瓶颈在于整个控制回路的延迟和稳定性。1.3 强化学习与 PID 控制的关系不是互斥而是互补很多刚开始接触机器人控制的朋友会问既然 PID 就能让机器人站起来走路为什么还要用强化学习我的理解是传统的 PID 控制需要你手动设计控制律对于四足这种强非线性、变拓扑的动力学系统来说调参难度非常大而且很难在复杂地形下保持最优表现。强化学习的思路是让策略网络直接从状态映射到动作绕过人工建模的过程。但这不代表 PID 就完全没用了。我们在 Microduck 上采用的是“底层 PID 上层强化学习策略”的组合强化学习策略输出的是关节目标角度或目标力矩底层的舵机控制器再用 PID 去跟踪这个目标。这样做的原因是舵机的物理特性决定了它不能直接接受“力矩”这种输入它自身有位置环、速度环的闭环控制逻辑。所以更准确的说法是强化学习负责决策PID 负责执行两者各司其职。这种分层架构还有一个额外的好处就算上层策略出了问题底层的 PID 限幅和舵机保护机制还能兜底不至于把机器人摔坏。我在实机测试中遇到过策略输出跳变的情况如果直接对舵机下指令瞬时电流会很大但经过底层 PID 的平滑处理后冲击明显被缓解了。这一点特别重要后面我会详细讲。2. 训练侧从零到能跑的策略2.1 训练环境的搭建从 PyTorch 到 Isaac Gym既然是强化学习项目训练环境是大头。我用的框架是 NVIDIA Isaac Gym这是英伟达出的一个基于 GPU 的仿真平台可以并行跑数千个环境实例把训练时间从几天压缩到几小时。Isaac Gym 的安装有特定要求它依赖于特定版本的 PyTorch 和 CUDA这里我踩了不少坑值得单独说一下。我当时的训练机配置是 i9-13900K RTX 3090 64GB 内存系统是 Ubuntu 22.04。NVIDIA 驱动版本是 535CUDA 版本 11.8。PyTorch 装的是 1.13.1因为 Isaac Gym 1.0 这个版本要求 PyTorch 和 CUDA 版本有严格的对应关系哪怕差一个小版本都可能导致环境创建时报错。具体安装命令大概是先装 CUDA 11.8 的 PyTorch再装 Isaac Gym 的 pip 包。整个过程如果顺利的话半小时能搞定但如果你用的是新版本 CUDA 12.x大概率会遇到兼容性问题。注意Isaac Gym 对 Python 版本的容忍度也很低建议用 Python 3.8 或 3.9不要用 3.10 以上的版本否则 import 阶段就可能报错。训练本身用到的算法是 PPO近端策略优化。开源社区里基于 Isaac Gym 的四足机器人训练代码非常多MIT 的 Cheetah 项目和 ETH 的 legged_robot 项目都是很好的模板。我这次用的是 legged_robot 的改版因为它支持自定义机器人 URDF并且内置了地形生成器和奖励函数模板改起来特别快。第一个版本我直接把 Microduck 在 SolidWorks 里的模型导出成 URDF导入 Isaac Gym。这里有一点要注意URDF 里每个关节的阻尼、摩擦系数、电机扭矩限制都会直接影响到训练出来的策略能否在真实机器人上迁移。比如把关节阻尼设得太低仿真里的机器人会像一个散架的骨架训练出来的动作在实机上会因为摩擦力不够而跑不起来。2.2 用 IQL 离线强化学习为什么能省一只机器人很多做强化学习机器人部署的朋友第一步想到的就是在仿真里在线训练策略然后直接迁移到实机。但存在一个现实问题从仿真到实机的 domain gap 很可能导致策略在真实环境中直接失败。要缩小这个差距要么用 domain randomization 把策略做得很鲁棒要么用 system identification 把仿真参数调到和实机接近。前者训练调参的工作量很大后者需要做频繁的实机数据采集。这次我采用的是一种折中的方案——先训练一个在线策略采集数据集再用 IQL离线强化学习从数据中学习出一个新的鲁棒策略。这个思路在足式机器人领域越来越流行因为它能降低 domain gap 的影响离线学习的策略学会的是“在给定状态分布下做出合理动作”而不是“对仿真状态映射到动作”因此对仿真参数的变化不那么敏感。具体流程是先在 Isaac Gym 里用 PPO 训练一个基础策略让机器人学会平稳行走。用这个基础策略在仿真里大规模采样加入随机扰动存储 state-action-reward 数据。使用 IQL 算法重新在离线数据上训练策略但这次的训练不对仿真环境做在线交互。将 IQL 训练好的策略直接导出到实机大幅减少实机调试时间。我采用的 IQL 实现是基于 JAX 的纯离线强化学习框架训练时在同一个 GPU 上同时跑环境和学习器。实测下来整个流程跑一遍大约需要 4~6 小时视网络大小和数据量而定。相比在线训练加 domain randomization 动辄一两天的调参周期我爱这个方案。2.3 奖励函数设计与训练参数复盘奖励函数是整个训练成败的关键。这次我用的奖励函数包含以下几个部分前进速度奖励机器人骨盆的实际前进速度与指令速度的差距差距越小奖励越高。姿态稳定奖励身体姿态角与目标姿态角的差距这里限制了 body pitch 和 body roll但 yaw 不限制因为转向靠 yaw。关节限位惩罚如果舵机角度超过设定的安全限位给出惩罚。能耗惩罚所有关节力矩的平方和防止策略“暴力”控制也降低实机关节过热风险。动作平滑惩罚相邻两步动作差值的惩罚防止高频抖动。训练参数的设置我直接用了一套比较保守但可靠的组合每个训练批次 4096 个并行环境学习率 3e-4GAE lambda 0.95clip range 0.2训练步数为 5000 万步。在 3090 上大约跑 6~8 小时收敛观察训练曲线上平均奖励和平均前进速度都稳定后就可以导出了。注意不要只盯着总奖励曲线要把速度奖励、姿态奖励分开看。有一次总奖励一直在涨但我一检查发现速度奖励是负的——策略学会了“摔倒然后爬起来”来刷奖励这种策略在实机上完全不可用。细看每项奖励分量非常重要。3. 部署链路从 GPU 到 RK3566 的完整管线3.1 模型格式转换的第一个坎PyTorch 到 ONNX训练好的策略保存在 PyTorch 的 .pt 文件里要部署到 RK3566第一步是把模型转成通用的 ONNX 格式。这一步骤本身原理简单但实际执行时我会踩一些细节坑。PyTorch 导出 ONNX 的核心是torch.onnx.export函数需要指定模型输入输出的维度信息。我们的策略网络输入是状态向量包括关节角度、角速度、身体姿态、线速度、角速度和指令速度等信息总维度大约 30~50。输出是十二个关节的目标位置或力矩维度是 12。转换的代码如下import torch import numpy as np # 加载训练好的策略 model torch.load(microduck_policy.pt, map_locationcpu) model.eval() # 定义输入维度构造一个随机输入用于追踪网络结构 dummy_input torch.randn(1, obs_dim, dtypetorch.float32) # 导出 ONNX torch.onnx.export( model, dummy_input, microduck_policy.onnx, export_paramsTrue, opset_version11, # 注意 opset 版本RKNN 对高版本支持不好 do_constant_foldingTrue, input_names[obs], output_names[action], dynamic_axes{obs: {0: batch_size}, action: {0: batch_size}}, )这里有三个容易出错的地方。 第一个是dynamic_axes。我一开始设置了动态 batch导出的模型在 RK3566 上跑的时候总是报维度错误后来发现 RK3566 的 RKNN-Toolkit2 对动态维度的支持有限要么用固定的 1 维 batch要么转成 RKNN 前用固定 shape 导出 ONNX。最后我的做法是导出一个固定输入 shape 的版本用于 RKNN 转换另外再导出一个动态 shape 的版本用于调试。 第二个是 opset 版本。RKNN-Toolkit2 目前对 opset 11~13 的支持最好如果你用了默认的 opset 17 甚至更高转换时很容易遇到不支持的算子。 第三个是do_constant_foldingTrue。很多教程会跳过这个参数但它能帮你把 batch normalization 的均值和方差提前融合进前面的卷积或全连接层的权重里。如果你的策略网络用了 BatchNorm这个参数不加的话模型部署后精度会出现明显的漂移。3.2 RKNN 转换从 ONNX 到 NPU 能跑的格式RK3566 的 NPU 不能直接跑 ONNX 格式需要用瑞芯微官方的 RKNN-Toolkit2 工具链转换。这一点官方文档写得比较零散流程大致是# 在 x86 机器上安装 RKNN-Toolkit2 git clone https://github.com/airockchip/rknn-toolkit2.git cd rknn-toolkit2 pip install -r requirements_cp38-1.5.0.txt pip install rknn_toolkit2-1.5.0-cp38-cp38-linux_x86_64.whl转换脚本的核心逻辑如下from rknn.api import RKNN rknn RKNN() # 配置量化参数 rknn.config( mean_values[[0, 0, 0]], std_values[[1, 1, 1]], target_platformrk3566, quantized_dtypew8a8, # 权重量化为 INT8激活也量化 quantized_algorithmnormal, ) # 载入 ONNX 模型 ret rknn.load_onnx(modelmicroduck_policy.onnx) assert ret 0 # 构建 RKNN 模型 ret rknn.build(do_quantizationTrue, datasetdataset.txt) assert ret 0 # 导出 RKNN 文件 ret rknn.export_rknn(microduck_policy.rknn) assert ret 0dataset.txt里面是用于校准量化的输入样本文件路径列表每行一个 npy 文件。这里要特别注意量化校准数据集必须覆盖到机器人真实运行时可能遇到的状态范围比如你训练时有各种各样的姿态、速度、地面情况校准数据也要包含这些。如果只用一组很接近的数据去校准量化后的模型在极端输入下会出现很大的误差实机上表现为突然摔倒。我试过用 500 组随机状态做校准精度损失大约在 1% 以内实机表现完全没问题。但如果只用 50 组全是很平稳的状态去做测试时发现一旦机器人遇到小台阶或者被推了一下策略的输出就会明显异常。3.3 在 RK3566 上跑的推理接口RKNN 模型转换完成后在 RK3566 上部署就比较舒服了。瑞芯微提供了一个轻量的 Python 推理库rknn-toolkit-lite2安装后在开发板上直接 import 即可。from rknnlite.api import RKNNLite # 初始化 rknn_lite RKNNLite() ret rknn_lite.load_rknn(microduck_policy.rknn) assert ret 0 # 初始化 NPU 核心 ret rknn_lite.init_runtime(core_maskRKNNLite.NPU_CORE_0_1_2) assert ret 0 # 推理 obs np.array([...], dtypenp.float32).reshape(1, obs_dim) action rknn_lite.inference(inputs[obs])在实际使用时我发现inference函数会有一定的调用开销如果控制环路频率设定在 50Hz每 20 毫秒推理一次这个开销占整个周期比例很小对性能影响不大。但如果想跑到 100Hz建议在 C 层直接调用 rknn 的接口Python 层的延迟会成为瓶颈。关于 NPU 核心的分配我试过只使用一个核心NPU_CORE_0和三个核心NPU_CORE_0_1_2两种情况。由于我们的网络很小一个核心的推理时间大约是 1~2 毫秒三个核心并行约 0.5 毫秒差别不大。所以如果你没有其他任务要跑用单核心就行省得跟别的线程争抢资源。4. 实机调试与常见问题排查4.1 步态频率和指令周期的匹配问题训练完成、模型部署好到了最激动人心的实机测试环节。第一次下电的时候我满怀期待地让机器人走起来结果它抖成一团像癫痫发作一样完全站不起来。排查了半天发现最核心的问题是仿真里的控制频率和实机的控制频率不一样。在 Isaac Gym 里我的控制频率设的是 50Hz20 毫秒周期但实机上舵机控制总线的刷新率也是 50Hz。理论上应该匹配但实际执行时策略推理结果下发到舵机舵机执行到实际位置变化这个反馈链路存在延迟导致稳定性变差。解决方法是引入“反馈-前馈”的思路策略输出的目标角度不直接作为舵机目标而是通过一个低通滤波器平滑后再下发同时对关节实际角度与目标角度的误差做比例补偿。这一步本质上就是把底层 PID 控制加回来但比例系数不能太大否则会因为反馈延迟引发振荡。另一个问题是步态频率。仿真里的步频是 2Hz每 0.5 秒一个完整步态周期对应的行走速度大约是 0.5m/s。但是实机上因为舵机的响应速度有限2Hz 的步频会让腿摆不过来动作看起来特别僵硬。我把策略输出的频率固定住了但增加了一个“动作频率缩放”参数实机上把步频降到 1.5Hz速度降到 0.35m/s机器人立刻就正常走路了。4.2 浮动基座坐标不一致导致的动作异常这是最有意思的一个坑。训练好的策略在仿真里走得很好但一到实机机器人的动作就完全不对不是单纯地抖动而是整个姿态都扭曲了。我花了整整一天才定位到问题——坐标系的定义不同。Isaac Gym 里机器人浮动基座骨盆的坐标原点在仿真里默认是机器人的初始位置坐标轴方向是固定的。但实机上我的代码读取 IMU惯性测量单元数据时把 gravity vector 当成了某些方式偏向于 body frame 的方向。这就导致策略输入的状态和训练时的分布完全对不上相当于你用一套斤不是为这套环境准备的策略去控制一个从未见过的机器人行为自然完全错乱。排查思路是把策略网络的输入打印出来和仿真里的输入做对比观察每个维度的量纲和符号是否一致。我当时打印后发现 body angular velocity 的 z 分量符号反了——因为 IMU 的轴定义和仿真里的坐标轴定义不同。修正后机器人立刻恢复正常。所以在这里强烈建议做仿真到实机迁移时第一步先把状态向量的每个维度逐一验证而不是直接让机器人走路。4.3 实测采样频率与调参技巧经过几轮修改后我的实机控制回路工作正常了。实测数据大概是这样的CPU 占用率RK3566 上运行完整的控制栈姿态解算 策略推理 舵机控制四核 A55 大约占用 15%。NPU 占用率单次推理 1.2ms完全不是瓶颈。控制频率实测稳定运行在 48~50Hz偶尔会有调度抖动但没影响机器人稳定性。机器人行走速度最高 0.45m/s连续行走 10 分钟电池温度在 45℃ 左右舵机温度在 55℃ 左右。如果你也遇到策略在实机表现不如仿真我建议按顺序排查这几项先看传感器预处理有没有问题IMU 的滤波参数、角速度的单位换算弧度/度、关节编码器的零点校准。再看控制频率是否匹配策略输出的频率和控制指令下发频率是否一致不一致会造成“动作输出被重复执行两次”的问题。最后看模型量化是否过度如果 RKNN 转换时校准数据太少可以重新生成校准数据再转换一次。我在实机测试中还发现一个操作层面的技巧先在“悬空”模式下测试。把机器人的四条腿垫高让它悬空然后跑控制程序观察关节运动是否协调。如果悬空模式下动作都乱套那大概率是策略输入的问题而不是地面交互的问题。等悬空模式稳定后再放到地面上测试会省很多事。注意实机测试时一定给机器人装上一个带提手的背带或挂架。一旦机器人摔倒一是能防止它继续在失控状态下摩擦地面二是可以直接提起来断电避免舵机长时间堵转过热烧毁。别问我怎么知道的我的第一台 Microduck 舵机就是这么烧掉的。4.4 常见问题排查速查表为了方便对照排查我把这次实机部署过程中遇到的问题整理成了一个速查表现象可能原因解决方法机器人站不起来腿部没有反应策略输入的状态向量全是 0或者数据不一致检查 IMU 数据是否正常初始化关节编码器读数是否变化机器人站起来但剧烈抖动控制频率过高导致反馈延迟底层 PID 增益过大降低控制频率到 30-50Hz减小 PID 比例系数机器人可以站立但不会前进步态频率与舵机响应速度不匹配指令速度输入始终为 0调整步态频率缩放确认指令速度接口有数据输入策略在仿真好但在实机完全乱走坐标系的定义不一致或传感器数据的轴方向反了逐维度打印策略输入和仿真数据的分布进行对比RKNN 量化后策略输出异常校准数据集覆盖不足采集覆盖面更广的校准数据重新做量化舵机发热严重策略输出高频抖动底层 PID 限幅设置过松增加动作平滑惩罚在实机代码中加入低通滤波器电池电压掉太快舵机堵转电流过大检查机器人是否被卡住调整策略的能耗惩罚权重5. 从仿真到实机还有一些值得深挖的方向这次部署算是一次完整的“仿真训练-模型压缩-边缘部署”的流程验证但坦白说目前这个方案还有很大的优化空间。比如我这次完全用的是 MLP 策略网络没有用到 Transformer 或者基于扩散模型的策略理论上更强的模型能给机器人带来更复杂的运动模式。但受限于 RK3566 的算力这类大模型估计要等后续带更强 NPU 的 SoC 才能跑起来。另外我这次用的是 IQL 离线强化学习来提高鲁棒性但说实话真正让策略在实机上站稳的是整个控制系统的工程细节——频率同步、数据校验、反馈滤波、保护机制。强化学习只是其中的一个环节甚至在系统和策略层面的努力占比是七三开。做这个领域的人一定要有全栈思维不能只盯着训练曲线。在部署过程中我不断意识到的一个核心观点是仿真到实机的迁移起决定作用的往往不是训练技巧而是工程耐心。如果你也正在做一个类似的机器人部署项目建议从小处着手先让机器人在悬空模式下动起来再让它在地面上站住最后再去探索各种行走、转向、爬坡的能力。每一步都有很多细节但每一步也都有实际的解决方案。希望这篇手记能帮你绕开我踩过的这些坑让你把更多精力放在机器人的运动能力本身而不是和传感器数据和模型格式死磕。
返回列表