基于RK3566的强化学习四足机器人实机部署全记录

基于RK3566的强化学习四足机器人实机部署全记录 最近一直在折腾一件事把在英伟达 GPU 上训练好的 Microduck 强化学习行走策略从一个“仿真里能跑”的模型变成一块 RK3566 开发板上真正能控制 25 厘米小机器人在瓷砖上、地毯上、甚至有点坡度的地面上稳稳走起来的实机系统。整个过程踩了不少坑也搞清楚了很多之前光看文档根本理解不了的问题。今天把整条链路完整记录下来从训练端的仿真环境和 PPO 超参到 PyTorch 转 ONNX 再转 RKNN 的每一步细节再到实机上的高频推理、串口通信和电源调度希望能给同样在玩 Microduck 或者类似小型四足、想做强化学习边端落地的朋友一条可以直接参考的路径。1. 这套组合到底解决了什么问题1.1 25 厘米四足机器人和它的控制需求Microduck 是一类体长约 25 厘米的小型四足机器人平台整体大小比一只成年猫小一圈重量通常在 1 公斤上下关节由串行总线舵机驱动板载主控是一颗 RK3566 核心板外加 IMU 惯导模块。这种体积的机器人有个很尴尬的处境传统基于运动学逆解和手工整定的步态算法在平整地面上能走但一旦遇到不规则的表面、轻微的推进力扰动或者地面摩擦变化固定步态就开始僵硬甚至翻车。这正是我选择强化学习方案的原因——策略不是人为写死的“正弦波相位表”而是通过大量仿真交互学出来的一个端到端映射从传感器观测直接到关节指令。机器人的控制频率一般设定在 100Hz 到 500Hz 之间。对于这种四足机器人我最终采用了 200Hz也就是每 5 毫秒要完成一轮“读取 IMU 和关节反馈 - 推理策略网络 - 输出 12 个关节的目标位置 - 通过串口下发到舵机驱动板”的闭环。5 毫秒这个窗口听起来宽裕但在 RK3566 这种以低功耗为目标、四核 Cortex-A55 的 SoC 上如果推理框架没选好、推理线程和通讯线程没做好隔离很容易就超时。1.2 为什么偏偏是 RK3566选择 RK3566 不是因为它算力强而是因为它在“功耗、成本、外设接口、软件生态”这几件事上找到了一个平衡点。RK3566 集成了 0.8 TOPS 算力的 NPU虽然不能和 GPU 比但对于一个隐藏层几百个节点的策略 MLP 来说完全够用。它自带多路串口、以太网、 USB 和 GPIO接舵机控制板、IMU、调试网络都可以直接走原生接口不需要额外转接。在决定用 RK3566 的 NPU 之前我做过一个快速评估策略网络是一个三层 MLP输入维度 48输出 12中间层宽度分别是 256 和 128。这个网络如果跑在 CPU 上单次推理大约 1.5 到 2 毫秒按说也能勉强支撑 200Hz 循环但实际运行中 CPU 还要处理 IMU 读取、电平转换、日志输出和可能的 Linux 系统调度抖动CPU 推理会带来不小的延迟不确定性。在 RK3566 的 NPU 上同样是这个网络单次推理大概 0.4 到 0.7 毫秒而且因为是独立 NPU 单元推理过程不抢占 CPU 资源留给控制循环的时间裕量大了很多。后文的部署方案也主要是基于 NPU 推理来展开。1.3 从训练到实机的完整链路先把整条链路画个蓝图后面每一节都是沿着这条线往下走的GPU 训练端使用仿真环境Isaac Gym / MuJoCo 类平台训练强化学习策略得到 PyTorch 权重文件。模型导出把训练好的 actor 网络从 PyTorch 导出为 ONNX固定输入输出节点。格式转换在 x86 主机上用 RKNN-Toolkit2 把 ONNX 转换成 RK3566 的 NPU 能直接加载的 rknn 格式。板端集成在 RK3566 上编写推理与闭环控制程序通过串口与舵机驱动板通信。实机联调调试控制频率、策略表现、各关节响应延迟解决抖动和漂移问题。这套链路和普通的深度学习模型部署本质上是同一件事但机器人控制场景有一个最大的特殊性模型推理的实时性要求远高于图像分类、文本生成而且推理结果直接作用于物理世界任何一个延迟尖峰都可能转换成一次踉跄或摔倒。理解了这一点整篇部署手记的核心也就抓住了。2. GPU 训练端把策略练到可以走路2.1 仿真环境、观测空间与动作空间我使用的训练框架是经典的 legged_gym 体系仿真平台基于 Isaac Gym它可以直接在英伟达 GPU 上并行跑成百上千个环境训练速度比传统 CPU 仿真快很多。Microduck 在仿真环境里被抽象成 12 个可控关节的刚体模型每条腿有 3 个自由度髋关节外摆、髋关节前摆、膝关节。每条腿的关节角度、角速度、力矩限制都会尽量往实机参数上靠例如关节角速度限制在每秒钟 10 到 12 弧度力矩限制按对应舵机的输出能力设置。观测空间我最终采用了 48 维大致结构如下机体坐标系下的角速度3 维来自 IMU 陀螺仪。重力方向在机体坐标系的投影3 维用来表达机器人当前姿态。12 个关节当前角度12 维。12 个关节当前角速度12 维。上一时刻动作12 维即上一轮下发的目标关节位置增量。期望速度指令6 维包括前向速度、横向速度、转向角速度的设定值及其低通滤波后的值。动作空间则是 12 维每个维度代表对应关节的目标位置相对于“默认姿态位置”的增量。策略输出增量后实际关节位置通过 PD 控制器转换为力矩再施加到仿真模型上。在实机上也是同样的逻辑策略输出的增量会叠加到当前关节位置或者叠加到默认站立姿态位置形成舵机目标角度舵机内部的闭环会去追踪这个目标。2.2 PPO 训练的核心超参与训练策略训练算法选择的是 PPOProximal Policy Optimization这是当前四足机器人强化学习里最常用的算法稳定、对超参不太敏感。训练时关键的超参设置如下每个 batch 包含 4096 个环境每个环境 rollout 长度 24 步共约 98304 个样本用于一次更新。学习率 0.001使用 Adam 优化器。PPO clip 参数设为 0.2熵系数在前 5000 步设为 0.01之后线性衰减到 0。奖励函数由多部分加权组合主要包括前进速度奖励、方向奖励、姿态稳定奖励、关节力矩惩罚、动作变化率惩罚。训练过程中最影响实机表现的两个关节点一个是奖励里的“动作平滑项”如果惩罚系数设得太低策略在仿真里会高频抖动实机上舵机跟着抖发热严重甚至触发舵机过流保护另一个是仿真里的各类随机化范围随机化开得越大迁移到实机越鲁棒但训练收敛越慢。我这里地面摩擦系数在 0.4 到 1.5 之间随机机体质量和重心位置在一个范围内扰动电机力矩上限也做了正态采样这套 domain randomization 是实机表现的关键。训练到第 5000 到 9000 个迭代时策略基本可以在仿真里稳定前进、转弯抗推力扰动能力也符合预期。此时并不急着导出模型先运行一批随机初始朝向、随机目标速度的仿真测试记录平均稳定时间和平均前进速度确认没有明显退化再进入导出阶段。2.3 导出前必须做好的三件事第一件事确认 actor 和 critic 的分离。强化学习训练时常用 actor-critic 结构但部署只需要 actor 网络critic 是训练阶段的辅助网络务必只导出 actor 部分。很多第一次做部署的人会误把整个模型结构导出导致转换时参数多出一倍推理时间严重超标。第二件事固定输入 shape。PyTorch 模型在导出 ONNX 时默认很容易带出动态维度比如 beam_size 或者 batch 维度被当成动态轴。RKNN-Toolkit2 对动态输入的支持有限强行转换容易报错或者生成性能很差的模型。这里我直接把输入固定为(1, 48)的 batch size 1既符合实机推理的输入形态又能减少转换的麻烦。第三件事导出前做一次“彻底冻结”的推理测试。先加载训练好的权重关闭 dropout、batch norm 的 training 状态输入一组已知观测记录输出数值。这个输出在后续 RKNN 转换并加载到板端后要能对上是验证转换链路是否正确的关键锚点。很多转换问题最后排查不出来就是因为少了这个基准测试无法判断是网络转换丢了精度还是部署代码引入的 bug。3. 模型转换PyTorch 到 RKNN 的关键关卡3.1 固定输入 shape让模型失去“灵活性”模型导出这一步必须把输入节点和输出节点的名字、形状都固定下来。我导出的命令逻辑如下。先把 actor 模型加载出来设为 eval 模式然后构造一个随机输入维度是(1, 48)这个输入只用于追踪数据流不会影响权重。导出 ONNX 时要显式指定dynamic_axesNone并设置 opset 版本为 11 或者 12太低的 opset 不支持某些算子太高则可能超出 RKNN 转换器的解析范围。import torch from policy_network import Actor model Actor(obs_dim48, act_dim12) model.load_state_dict(torch.load(checkpoints/best_actor.pt, map_locationcpu)) model.eval() dummy_input torch.randn(1, 48, dtypetorch.float32) torch.onnx.export( model, dummy_input, policy.onnx, input_names[obs], output_names[action], opset_version11, dynamic_axesNone, )导出的 ONNX 文件建议先用一个简单的可视化工具查看一下网络结构确认输入输出节点符合预期中间没有奇怪的动态 shape 情况。因为策略网络是纯 MLP没有卷积、注意力这些复杂结构所以这一阶段的成功率比较高主要问题往往出在算子兼容性上。如果 RKNN 转换时报出不支持的算子优先检查网络里是否有clamp的导数相关节点、是否有gelu这类激活函数必要时把激活函数替换成 ReLU 再重新导出。我的经验是尽量用基础算子组合例如把softmax拆出来手动实现或者把atan2等三角函数换成近似计算会减少很多麻烦。3.2 RKNN-Toolkit2 环境与 rknn 模型生成RKNN-Toolkit2 建议安装在与板端系统匹配的 Python 版本环境中。我使用的是 1.60 左右的版本配套 Python 3.8。安装完成后转换的核心代码非常简短但有两个细节必须注意。第一target_platform必须填写rk3566如果填成rk3588生成的模型在 RK3566 上要么跑不起来要么性能异常。第二量化策略的选择。RKNN 支持 fp16 和 int8 两种主要模型类型这个选择直接关系到实机的精度和速度。from rknn.api import RKNN rknn RKNN() rknn.config(target_platformrk3566, quantized_dtypefp16) rknn.load_onnx(modelpolicy.onnx) rknn.build(do_quantizationFalse) rknn.export_rknn(policy_fp16.rknn)这段代码先 load ONNX再 build最后导出 rknn 文件。如果开启量化则需要在 build 前提供一个量化的校准数据集。量化校准数据应该尽量贴近实机运行时的真实观测分布我在实践中的做法是把训练时记录的一批仿真观测数据直接导出成 npy 文件喂给转换器而不是用随机数据这样得到模型的精度损失会更小。3.3 量化选择FP16 还是 INT8这是整个转换环节里最值得花时间对比的部分。INT8 模型能把单次推理压缩到 0.3 毫秒以下但量化过程会引入精度损失哪怕做了校准MLP 的输出也可能产生微小偏差。这些偏差作用到关节目标位置增量上经过 PD 控制器放大后往往表现为机器人站立时的持续抖动——就是这个“站着也会抖”的现象让你很难排查原因是控制器问题还是量化噪声问题。FP16 模型体积比 INT8 大一些推理时间在 RK3566 上大约 0.4 到 0.7 毫秒对于 200Hz 的控制周期来说依然充裕而且精度损失几乎可以忽略。我的建议是除非你的控制频率需要跑到 500Hz 以上且 CPU 资源极度紧张否则优先选择 FP16。毕竟四足机器人控制的瓶颈往往不是那 0.2 到 0.3 毫秒的推理时间差而是实机上的通信延迟和舵机响应延迟。如果确实要上 INT8需要在实机上做一轮“站定-踏步-前进”的对比测试我后面在常见问题部分会展开说明。3.4 转换踩坑记录转换过程中我遇到过两个印象很深的问题。第一个是 RKNN-Toolkit2 在解析 ONNX 时对 batch 维度非常敏感。第一次导模型时dynamic_axes没有显式设为None转换时提示维度不确定的错误后来固定输入 shape 才解决。第二个问题是精度异常用 FPGA 调试口读出的板端模型输出和 PyTorch 输出对不上最后发现是忘了设置do_quantizationFalse默认开启了 int8 量化而量化里程碑的精度偏差被放大了。排查方法很简单在板端加载 rknn 模型后输入一组固定观测和 PyTorch 的导出基准输出对比误差超过 1e-2 就要检查转换参数。4. RK3566 实机部署从 NPU 推理到关节控制4.1 实机软件栈与 NPU 推理初始化RK3566 板端运行的是一个精简的 Linux 系统。部署前先确认板端已经安装了 RKNPU runtime 库这通常是通过官方驱动包自带的也可以在系统镜像里预装。Python 环境下使用rknnlite加载模型接口非常简洁。from rknnlite.api import RKNNLite rknn RKNNLite() ret rknn.load_rknn(policy_fp16.rknn) ret rknn.init_runtime(targetrk3566)这里有一个启动时间上的坑RK3566 的 NPU 在首次 init 时会有一段模型解析和权重加载的过程耗时可能到几百毫秒甚至一秒钟。这意味着一定不能把 NPU 模型的加载写在第一个控制周期内否则机器人开机后要等半天才开始响应。我的做法是在程序启动阶段先完成模型加载再进入控制循环模型加载完成后打一个时间戳便于后续排查启动慢的问题。NPU 推理接口调用也很直接输入一个(1, 48)或(48,)的浮点数组输出(1, 12)数组。这里要注意输入数据的 dtype 和归一化方式必须与训练时一致。强化学习策略在训练时通常不做过多的手工归一化而是靠网络自己学习输入的均值和方差所以实机上喂给网络的观测值也要是“原始传感器读数”对应的尺度不要画蛇添足地自己归一化。4.2 200Hz 控制循环和串口通信控制循环是整个部署的核心。我用一个高优先级线程来跑控制循环循环体里按顺序执行以下操作读取当前时间戳计算距离上一轮循环的间隔。从 IMU 读取角速度、重力方向估计。从串口/内部总线上读取 12 个关节当前角度和速度取决于舵机驱动板是否回传。组装观测向量构造(1, 48)输入。调用 NPU 推理拿到(1, 12)的动作增量。将增量叠加到默认站立姿态或当前状态得到 12 个目标关节角度。将目标角度编码成舵机控制帧通过串口发送给舵机驱动板。记录本轮耗时如果超过 5 毫秒做标记并统计超时次数。串口通信这部分是整个实机环节最考验工程细节的地方。Microduck 这类小型机器人通常使用半双工串行总线舵机例如 LX-16A 或类似协议波特率常见 115200 或者更高。一条指令帧包含舵机 ID、目标角度、速度、力矩等字段舵机执行后可以选择回传状态但也因此会占用通信时间。我的经验是高频控制循环里绝对不要每一轮都查询全部舵机的角度和速度。查询-响应式通信会显著增加延迟让控制周期不稳定。正确做法是每 5 到 10 个控制周期才查询一次完整关节状态其他周期只下发指令。IMU 数据则可以通过 SPI/I2C 接口读取延迟远低于串口。如果你发现控制频率上不去优先怀疑串口上的查询风暴而不是 NPU 推理速度。4.3 电源、散热与调度优化RK3566 在持续推理时核心温度会上升虽然策略网络负载不大但整机紧凑散热空间有限。我最初部署时没做好散热运行几分钟后 NPU 降频推理时间从 0.5 毫秒涨到 1.5 毫秒直接导致控制周期抖动机器人开始走路踉跄。后来加了一块很小的铝制散热片并在系统层面把 NPU 频率锁到固定档位问题才解决。嵌入式平台上性能优化不一定意味着超频更多时候是“稳定在一个可靠的水平”这一点和 GPU 训练环境的心态完全不同。调度优化方面建议给控制线程设置实时优先级。Linux 系统默认的 CFS 调度器会把控制线程和后台日志、网络服务混在一起调度偶尔出现一个 10 毫秒的调度延迟机器人就会明显晃一下。把控制线程设置为SCHED_FIFO或至少提高 nice 值同时把日志写入操作放到单独的低优先级线程能显著减少抖动。我实测下来仅调度这一项改动控制周期超时率就从每 10 秒一次降到十几分钟一次。5. 常见问题与排查实录5.1 实机抖动和策略异常这是部署阶段最常遇到的状况。机器人站定时抖、走起来像醉汉一般从三个方向排查。第一个是观测值尺度问题。训练时仿真里的角速度是弧度每秒为单位关节角度是弧度但实机上从 IMU 读到的角速度可能是度每秒关节角度的回传值可能是舵机内部的位置码。如果不在组装观测向量前把单位换算成和仿真一致策略看到的就是“分布错位”的输入输出自然乱七八糟。我的做法是先在 PC 端把传感器数据记录下来和仿真数据的均值、方差做一次对比确认量纲一致再上板。第二个是动作增量叠加方式不对。训练时策略输出的是相对默认姿态的位置增量实机上某些实现会错误地把它叠加到“当前关节的真实位置”上这会导致一个正反馈循环动作增量让关节移动下一次观测里当前角度变了策略又叠加新的增量关节越走越偏。正确的叠加方式是固定参考默认姿态或者明确训练时接受的是“目标关节绝对位置”两种方式不能混用。第三个是 PD 增益不匹配。仿真里的 PD 增益和实机上舵机的闭环增益是完全两回事。如果你发现机器人动作正确但无力、站不稳先确认舵机工作在位置控制模式并且位置跟踪的响应速度足够。某些舵机需要单独配置响应速度上限默认值可能太慢跟不上 200Hz 控制周期下连续变化的指令。5.2 延迟与通讯问题实机表现延迟感明显时优先测出整条链路的延迟分布。我用一个 GPIO 引脚来打点控制循环进到推理前拉高推理完成后拉低再用逻辑分析仪或示波器看高电平宽度这就是纯 NPU 推理时间。然后测量串口发送到舵机响应之间的延迟这一步通常占大头。很多舵机内部的位置环更新频率只有几十赫兹即使你以 200Hz 下发指令舵机实际每 20 毫秒才真正追踪一次目标中间指令会被丢弃或覆盖。如果遇到指令丢帧的情况检查舵机协议是半双工还是全双工连续下发的帧间隔是否符合协议要求。有的舵机在收到一条指令后需要一定时间释放总线立即发送下一条会触发帧冲突。解决方法是控制周期设置为舵机协议容许的范围或者换成总线占用时间更短的指令格式。5.3 我的最后几个建议这套部署流程走完之后我觉得有几个经验值得单独拿出来说。第一不要迷信 NPU 在机器人控制里的作用。对于 Microduck 这种小网络CPU 也能勉强跑NPU 的主要价值是释放 CPU 资源而不是不可替代。如果你的项目里后续还要跑更复杂的感知模型比如视觉避障、目标检测那时候 NPU 的价值才会真正体现。第二所有传感器数据的清洗和单位换算一定要在 PC 端写好并验证不要边写板端代码边猜。第三养成记录版本的习惯。训练权重、ONNX 文件、rknn 模型、板端部署代码四者要保持严格的版本对应改任何一环都要重新做整链路测试不然出了问题很难定位。我目前已经在这套基础上开始尝试加入简单的视觉观测输入让策略能根据前方障碍物调整步频和转向。RK3566 的 NPU 还有不少算力余量扩展空间比一开始预想的要大。如果你也在做类似的小型强化学习机器人项目希望这篇手记能帮你少踩几个坑尤其是那些“从仿真到实机”才暴露出来的问题早一步注意到就能省下好几个通宵。