
从英伟达 GPU 到 RK3566 实机Microduck 25 厘米强化学习机器人部署手记刚拿到 Microduck 这台 25 厘米的小四足时我的第一反应是这东西确实能跑起来但真正让它“自己学会跑”需要一条完整链路先在英伟达 GPU 上用强化学习训出一套控制策略再把策略压缩、转换、部署到 RK3566 这块边缘开发板上最后让机器人在真实地面迈出稳定的步子。这个过程中踩的坑比我想象中多得多。这篇文章就围绕“强化学习训练 边缘端部署”这条主线来写。适合正在做机器人强化学习、想把仿真策略搬到低成本硬件上的朋友尤其是那些手里有类似小四足、却卡在 Sim-to-Real 和模型落地环节的人。我会把训练阶段的方案选择、模型导出与格式转换、RK3566 上的推理优化、以及实机调试中的问题排查都摊开来讲尽量给出可以直接复用的参数和步骤。1. 硬件选型与整体方案拆解1.1 为什么边缘端选择 RK3566 而不是树莓派在实机部署之前我先确认了一件事Microduck 这套 25 厘米级别的四足平台控制用的策略网络其实很小。以常见的 MLP 策略为例输入大概是 20 到 30 维两层隐层各 128 到 256 个神经元输出 8 到 12 个关节位置指令整个模型参数量通常在 10 万级以下。这种规模的网络在当今任何一块开发板上都算不上负担真正的瓶颈从来都不是算力而是推理延迟稳定性、接口时序和整机功耗。自然树莓派在机器人圈子里用得很多生态也热闹。但 RK3566 对这类项目有一个树莓派比不了的优势NPU。它的 NPU 算力在 0.8 TOPS 到 1 TOPS 级别的 INT8 推理能力对于几十 KB 到几百 KB 的小网络单次推理在几毫秒内就能完成。另一个是工具链瑞芯微的 RKNN-Toolkit2 支持从 PyTorch、ONNX 直接转换模型部署时把策略模型量化成 INT8 之后内存占用和延迟都低到可以忽略。第三个是价格RK3566 的开发板在成本上确实更友好对于个人项目和开源硬件爱好者来说整机 BOM 能省出来的钱可以拿去多买几个备件。但这不意味着 RK3566 是唯一解。如果你的机器人关节电机数量更多、IMU 数据频率更高、还打算跑视觉感知那我会建议直接上 RK3588 或者带有更强 CPU 的板子。Microduck 这种只用关节编码器和 IMU 作为感知来源的小型四足RK3566 的定位刚刚好。1.2 Microduck 平台特征与控制需求Microduck 是典型的 25 厘米级小型四足整体结构和常见的 12 自由度大狗不同它一般做成 8 自由度每条腿两个关节分别是髋关节和膝关节。这样的设计简化了运动学模型也让强化学习策略的动作空间从 12 维降到了 8 维训练难度降低不少。实机上关节通常由串行总线舵机或带减速箱的直流电机驱动位置闭环由电机本身完成这直接决定了强化学习策略的输出形式必须是关节期望位置而不是关节力矩。这一点非常关键。很多做仿真出身的人一开始会把动作空间设计成关节力矩假设底层有完美的力矩控制结果到了真机上发现电机带宽不够、力矩控制质量也很差。Microduck 这种小型平台串行舵机内部自带位置环你给它角度目标它自己会去跟那么强化学习的任务就变成了根据机体状态和用户指令输出一系列合理的关节角度目标让身体完成前进、转向、姿态调整等动作。说直接一点强化学习在 Microduck 上扮演的是“上层决策者”真正的物理执行还是靠舵机内部那套位置闭环所以我一直觉得与其说强化学习替代了 PID不如说强化学习成了 PID 的指挥官。1.3 一条完整的部署链路从训练到真机运行我的流程可以拆成四个阶段。仿真是第一步在英伟达 GPU 上用 Isaac Gym 这类并行仿真环境训练策略得到 PyTorch 权重第二步是模型导出把训练好的 PyTorch 模型转成 ONNX这一步主要是为了跨平台和格式统一第三步是边缘端转换在 RK3566 上用 RKNN-Toolkit2 把 ONNX 转换成 RKNN 格式或者直接保留 ONNX 用 CPU 推理第四步才是实机联调把推理结果通过串口或总线发给舵机同时读回关节状态。阶段输入输出主要工具关键风险仿真训练机器人模型、奖励函数PyTorch 权重Isaac Gym / PPO训练不收敛、仿真与真机差异大模型导出PyTorch 权重ONNX 模型torch.onnx.export算子不支持、动态维度问题边缘端转换ONNX 模型RKNN 模型RKNN-Toolkit2INT8 量化掉精度、算子不兼容实机联调RKNN/ONNX 模型稳定步态RKNN API / 串口总线延迟过高、零位偏移、抖动发散这个链路里最容易翻车的往往不是训练阶段而是最后两公里。模型在仿真里跑得再好一旦导出格式有问题、量化精度掉得离谱、实机推理延迟不稳定之前的工作全部白费。所以我强烈建议从项目一开始就要把“部署约束”考虑进去比如训练时就固定网络结构、控制输出频率、规定动作变化率上限这样后面做模型转换时会少掉很多头发。2. 仿真训练从 GPU 到策略权重2.1 训练环境与强化学习算法选型Microduck 这类四足的强化学习训练社区里最主流的方案是用 Isaac Gym 或它的后续版本 Isaac Lab配合 PPO 算法。我参考的是 Microduck 仓库主页给的主线流程本身就用这套技术栈。之所以不用传统仿真器加 RL 库这种组合是因为 Isaac Gym 可以在单张 GPU 上并行几千个环境4096 个环境同时跑一轮训练迭代只需要几秒钟这让整个训练周期从几天缩短到几个小时。对于个人开发者来说这个效率差距是决定性的。算法方面PPO 依然是四足运动控制的首选。它不是理论上限最高的算法但胜在稳定、超参数敏感度低、社区案例多。我在训练 Microduck 时用的就是 PPO最直观的感受是只要奖励函数设计不出大问题策略基本能在一个晚上内收敛出像样的步态。相比之下SAC 这类 off-policy 算法虽然样本效率更高但调参过程会更折腾。如果只是想快速让机器人走起来不需要在算法研究上花太多时间PPO 就是最稳妥的答案。硬件上也不必追求极端配置我实测单张 RTX 4090 就能开 4096 个环境跑完整训练流程8 卡 A100 那种配置通常是给大规模并行搜索用的这个项目完全用不上。2.2 动作空间、观测空间与奖励设计Microduck 的动作空间是 8 维每个维度对应一条腿的关节期望位置。这个设计让强化学习策略的输出能直接对接舵机的位置环指令省去了力矩到位置的转换过程大幅降低了部署复杂度。观测空间我分成了四类总计大约 29 维。第一类是本体感知包括 8 个关节当前角度和 8 个关节角速度第二类是 IMU 数据包括三轴角速度和三轴加速度第三类是上一时刻的动作也就是上一帧输出的 8 个关节目标这能让策略的动作曲线更平滑防止抖动第四类是外部指令包括前进速度指令和转向角速度指令。把上一帧动作加入观测是我觉得训练稳定性和实机表现提升最大的一步没有这个输入策略很容易在相邻两个控制周期之间输出突变导致舵机来回抽动。奖励函数的设计可以直接决定步态风格。Micrduck 的奖励主体包含几个部分速度跟踪用 exp 形式做惩罚、关节加速度过大做惩罚、身体姿态偏转会惩罚、动作变化过快也会惩罚。数值上速度跟踪权重给 1.0姿态保持给 0.5能量消耗和动作平滑权重适中这样既不会让机器人为了节能而原地不动也不会让它为了刷速度而做出夸张又危险的动作。2.3 训练关键参数与收敛判断训练参数我直接给出当时能稳定收敛的一组。环境并行数设为 4096学习率 3e-4PPO clip 范围 0.2GAE 的 lambda 设为 0.95折扣因子 gamma 为 0.99每次迭代的 mini-batch 大小可以按显存去设我一般控制在 4096。训练迭代次数不固定但走到 3000 到 5000 次迭代后基本已经能看到稳定步态。判断训练是否收敛不要只盯着一行 reward 曲线看。我习惯同步做三件事一是看速度跟踪误差的均值是否降到期望范围二是每隔几百次迭代把策略搬到可视化环境里跑一段用眼睛确认步态是否自然三是做一次简单的鲁棒性测试在仿真里推一下机器人身体看它能不能在几步内恢复平衡。如果这三项都过了再考虑导出模型。若训练不收敛我踩过的经验是先检查奖励尺度把各项奖励的量纲对齐而不是焦虑地调网络宽度。很多次不收敛问题都出在某个奖励项数值过大导致策略只顾着优化那一个目标别的细节全丢。3. Sim-to-Real 桥接策略能跑的关键3.1 频率、延迟与控制周期设计仿真里策略的控制频率通常设置在 500 Hz也就是每 2 毫秒输出一次动作。但到了 RK3566 真机上直接跑 500 Hz 是不现实的推理本身不慢慢的是串口通信、舵机总线应答、状态读取这些外围流程。如果每个控制周期都要读取 8 路关节角度加 IMU 数据再推理、再下发整体时间大概率会超过 5 毫秒。我的做法是拆分控制频率。策略推理跑在 100 Hz也就是每 10 毫秒算一次动作底层舵机的位置环跑在 500 Hz两个频率之间用线性插值补齐中间帧。这样做的好处是策略模型的推理压力大幅降低底层看到的关节指令依然平滑不会因为控制间隙产生阶梯感。实际测试下来RK3566 上 100 Hz 的推理频率配合插值下发Microduck 的步态稳定性是够的。如果你的舵机总线速度更快可以把推理频率提到 200 Hz但前提是整条链路的时间预算容得下。3.2 关节零位标定与 IMU 坐标系对齐仿真里机器人的所有关节角度都有准确零位但真机装配时舵机的机械零位和角度传感器零位几乎不可能完全一致。这个问题如果忽略就会出现一种经典的故障机器人四条腿在平地站立时明明你发送的是对称关节角度机身却歪向一侧走起来会划出明显的弧线。解决方式是做零位标定。把 Microduck 放在完全水平的台面上手动调整每条腿到标准站姿然后记录此时舵机反馈回来的角度值作为每个关节的零位偏移量。部署代码里在把强化学习输出的关节目标下发给舵机之前先加上这个偏移量。这个偏移量不是一个固定值就够的每次重新上电、拆装腿之后都要重新标定一次。IMU 坐标系也要对齐重点是确认三轴方向与机器人本体的对应关系尤其是重力方向如果 z 轴方向反了强化学习策略里那些依赖重力投影的观测就会全乱机器人甚至会朝着地面猛蹬。3.3 输出限幅、变化率限制与安全机制强化学习策略在仿真里可以随便输出但真机上必须有安全护栏。我加了三个保护。第一个是关节目标位置限幅每个关节的角度范围是机械结构决定的超出范围必须截断否则舵机憋住就会发热甚至扫齿。第二个是动作变化率限制每帧输出的关节目标和上一帧的差值不能超过某个阈值比如每 10 毫秒最多变化 2 度这能直接避免策略在异常状态下输出剧烈跳变。第三个是看门狗机制如果串口连续 500 毫秒没有收到策略推理的新指令舵机就原地保持当前位置同时停止运动。这三层保护其实花不了多少代码量但在实机调试时能救命的。尤其是第一次把策略从仿真搬到真机、控制频率还没调稳的时候输出限幅和变化率限制能防止机器人直接弹飞撞墙。4. RK3566 上的模型转换与推理优化4.1 模型导出从 PyTorch 到 ONNX训练好的 PyTorch 模型不能直接拿到 RK3566 上跑我先把权重导出成 ONNX。导出时锁定模型到 eval 模式并把输入维度固定成 (1, 观测维度)。这里的 1 是 batch size在部署环境里我们永远单次推理一个样本不需要动态 batch动态维度会带来很多兼容性麻烦。PyTorch 模型导出时最常见的坑有两个一是模型里用了算子 ONNX 不支持比如某些自定义的循环结构或非常规的张量操作二是导出的 ONNX 里有动态维度标记导致在后续转换器里报维度不匹配。我的建议是从训练一开始就用最简单的 torch.nn.Module 组合不要用花哨的 torch.jit.script控制流越简单导出越顺。下面是一段当时使用的导出脚本改动很小但很实用。import torch import onnx policy torch.load(policy.pt, map_locationcpu)[model] policy.eval() obs_dim 29 dummy_input torch.randn(1, obs_dim) torch.onnx.export( policy, dummy_input, policy.onnx, input_names[obs], output_names[action], dynamic_axesNone, opset_version12, ) onnx_model onnx.load(policy.onnx) onnx.checker.check_model(onnx_model) print(onnx export ok,, onnx_model.graph.node[0].op_type)导出之后我习惯先用 onnxruntime 在 PC 上推理一个随机输入确认输出结果和 PyTorch 的原始输出误差在 1e-4 级别再继续后面的转换。这一步可以提前筛掉大量模型不兼容问题。4.2 ONNX 转 RKNNINT8 量化与精度监控RK3566 上的模型转换官方工具链叫 RKNN-Toolkit2它支持把 ONNX 转成 RKNN 格式并选择是否做 INT8 量化。转换流程不算复杂但有几件事必须注意。第一件事target_platform 要指定为 rk3566不同芯片的 NPU 指令集有差异不能随便填。第二件事如果要做 INT8 量化必须准备一个校准数据集通常是几百条从仿真里随机采样的观测数据。这里有一个很多人会踩的坑校准数据集不能随机数要用任务分布里的真实数据。我的做法是加载训练好的策略在仿真环境里随机跑几百条轨迹每帧保存观测向量做成一个 txt 文件列表给 RKNN 工具读取。这样量化后的模型在推理时精度损失能控制在可接受范围内。转换完之后一定要在 PC 上先把 RKNN 模型的推理结果和 ONNX 模型对比一次看看最大误差有多大。我实测一个两层 MLP 策略INT8 量化后动作输出的最大误差在 1 度左右。对于舵机位置控制来说这个误差可以接受但如果你的模型更复杂、对精度更敏感建议直接保留 FP16 或 FP32 在 CPU 上跑不要为了省那几毫秒把策略搞废。转换代码大致如下核心逻辑就是加载模型、配置量化参数、构建、导出。from rknn.api import RKNN rknn RKNN() rknn.config( target_platformrk3566, mean_valuesNone, std_valuesNone, quantized_dtypew8a8, ) rknn.load_onnx(modelpolicy.onnx) rknn.build(do_quantizationTrue, datasetcalib_dataset.txt) rknn.export_rknn(policy.rknn)如果后面在板子上加载 RKNN 模型时发现推理结果离谱第一时间要怀疑量化可以关掉 do_quantization 重新导出 FP16 的 RKNN 模型做对比这个排查思路下面还会再提。4.3 推理主循环与延迟预算分配RK3566 上的部署主循环结构其实不复杂核心是控制好时序。伪代码如下import time import numpy as np from rknnlite.api import RKNNLite rknn RKNNLite() rknn.load_rknn(policy.rknn) rknn.init_runtime() last_action np.zeros(8, dtypenp.float32) last_obs np.zeros(29, dtypenp.float32) while True: start time.perf_counter() joint_pos read_joint_positions() joint_vel read_joint_velocities() imu read_imu() cmd read_command() obs build_observation(joint_pos, joint_vel, imu, last_action, cmd) outputs rknn.inference(inputs[obs]) action np.clip(outputs[0].flatten(), -1.0, 1.0) action limit_rate(action, last_action, max_delta2.0) send_joint_targets(action joint_offset) last_action action elapsed time.perf_counter() - start sleep_time max(0, 0.01 - elapsed) time.sleep(sleep_time)链路的时间预算要提前算好。RK3566 的 CPU 跑一个 29 维输入、两层 128 节点隐层的 MLPONNX Runtime 单帧推理大约 5 到 8 毫秒换成 RKNN 量化模型在 NPU 上跑单帧推理能压缩到 1 到 3 毫秒。再算上读取 8 路关节数据、IMU 数据、串口下发舵机指令的时间整帧控制在 10 毫秒以内是完全够的。如果预算不够优先压缩的是推理时间因为串口通信和舵机总线时间很难省。我也实测过一个对比同一份策略在 RK3566 CPU 上用 ONNX Runtime 推理100 Hz 控制周期下的 CPU 占用率在 40% 左右切到 RKNN 量化模型之后CPU 占用率降到 5% 以下NPU 占用也不高剩余算力可以留给任务调度和可视化调试整体可靠性提升明显。如果你的策略模型非常小、任务又简单CPU 路线其实完全够用NPU 更多是提升余量。5. 常见问题与排查实录5.1 RKNN 转换阶段的报错与解法转换阶段最常见的报错集中在算子不支持这个环节。我们的策略网络是纯 MLP理论上只有 Linear、ReLU、LayerNorm 这类基础算子但有些版本的 PyTorch 会把 LayerNorm 导出一个复杂组合ONNX 转换时没问题到 RKNN 就会因为某个算子不支持而中途挂掉。遇到这种问题我的建议是尽量不要去改算子实现而是简化网络结构。LayerNorm 对四足运动控制策略来说不是必需品换成 BatchNorm 或者直接去掉归一化层经过训练后效果差别不大但转换过程会顺畅很多。如果一定要保留某些复杂算子可以先在 PC 上用 rknn.build(do_quantizationFalse) 尝试转换确认问题是否和量化有关然后再针对性处理。INT8 量化掉精度则是另一类高频问题。表现是转换成功但真机策略表现远不如仿真步态发飘甚至原地打转。定位方法也很简单把同一个观测向量分别输入 ONNX 模型和 RKNN 模型比较输出的最大误差误差超过 2 度就要考虑换校准数据或改成 FP16。5.2 真机抖动、步态不对称和低频振荡第一次把策略布到 Microduck 上最常见的现象是四条腿乱抖或者机身扭动幅度大。这类问题的原因往往不是策略本身而是部署链路里的时序和协议问题。我遇到过三次类似情况每次原因都不一样第一次是串口下发指令时没有加帧间隔舵机总线拥堵导致数据错位第二次是控制频率实际只有 40 Hz策略在仿真里没遇到过这么低的推理频率动作变得顿挫第三次是零位偏移没标定好左右腿的初始姿态不对称导致策略尝试用不对称的输出去纠正结果越纠越偏。排查顺序我建议从频率开始。先确认实际控制周期在目标值的 20% 误差范围内再用串口工具逐帧查看舵机收到的角度指令是否平滑最后检查零位偏移。抖动问题九成出在这三个位置策略网络本身反而不太可能背这个锅。5.3 仿真表现优秀但真机无法起立如果说抖动是时序问题那“仿真里能跑真机根本站不起来”就是更让人头疼的领域差异问题。我遇到过一次只有仿真速度指令为 0 时站姿正常一旦给前进速度指令机器人就会直接弯腰趴下。查下来发现问题出在域随机化不够充分。训练时我没有对机身质量、质心位置和舵机力矩能力做足够范围的随机化策略在仿真里找到了一个“依赖精确质量模型”的捷径到了真机上质量分布稍有偏差策略就失效了。解决办法是对比真机反馈的关节角度和策略期望值如果偏差持续增大说明策略依赖了不存在的物理特性要回到训练阶段增加域随机化范围。另一个常见原因是舵机的速度上限在仿真里被理想化了真机跟不上策略要求的角速度也会出现趴窝现象。5.4 我整理的一份关键问题速查表现象可能原因处理方式模型导出后精度损失大量化校准数据分布不准用仿真实际状态数据做校准集真机关节抖动推理频率过低或串口拥堵降低推理频率至 100 Hz加插值左右步态不对称关节零位偏移重新做零位标定站姿正常但无法前进域随机化不足增大质量、摩擦力、延迟随机化范围RKNN 转换算子报错模型使用了不兼容算子简化网络结构、去掉复杂归一化层CPU 推理负载过高模型未量化导出 INT8 RKNN 模型舵机保护性锁死关节位置指令超范围加输出限幅和看门狗我个人在实际操作中的体会是强化学习四足机器人的部署真正的难点从来不在于“训练一个网络”而在于把仿真里的理想世界一点点贴近真机的物理约束。Microduck 这样的小平台反而是练手的最佳选择它成本低、够灵活、迭代快部署过程中遇到的每一个坑换到大尺寸四足或人形机器人上都会遇到而且更贵、更大、更危险。如果你准备复现这套流程建议按照我给的顺序一步一步走先确认 PC 端训练能收敛再检查 ONNX 导出与 RKNN 转换的精度最后才上真机。不要急着跳过中间的验证步骤部署链路里省掉的每一步验证最后都会以实机翻车的方式还回来。等 Microduck 能稳定走起来之后再去尝试转向、爬坡、抗扰动这些进阶功能整个强化学习控制项目的乐趣才刚刚开始。