ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Microduck强化学习机器人部署:从GPU训练到RK3566实机落地手记

2026/9/8 18:43:00 拓冰建站 浏览量
Microduck强化学习机器人部署:从GPU训练到RK3566实机落地手记 从英伟达 GPU 到 RK3566 实机Microduck 25 厘米强化学习机器人部署手记我一直觉得强化学习落地最容易翻车的不是算法不是训练时间而是模型在仿真里明明跑得好好的一上真机就原地抽搐这一道坎。这次我把 Microduck 这个 25 厘米的小机器人从英伟达 GPU 上的训练环境一路搬到 RK3566 实机整个过程踩了不少坑也把之前很多模糊的概念彻底捋清楚了。这篇手记适合两类人一是刚接触强化学习机器人、想把 sim2real 真正跑通一遍的新手二是已经在做嵌入式部署、想搞清楚 RK3566 这类板子到底能不能扛住轻量策略推理的人。我会把完整链路拆开讲包括训练阶段的 reward 设计、模型导出、板端系统适配、ADB 设备识别、分区调整、开机自启以及真机调试时那些文档里不会告诉你的细节。1. 为什么是 25 厘米的 Microduck小尺寸反而把问题暴露得更彻底1.1 尺寸决定了成本也决定了策略的容错率先说结论25 厘米这个尺寸不是一个随意的数字它直接决定了整个硬件成本、传感器配置和实验环境的空间需求。Microduck 这类的小型机器人机身小关节力矩天然有限电机响应快但扭矩余量不足。如果你在仿真里给策略设置了一个比较大的加速度上限真机上一跑电机直接进入过流保护机器人还没站稳就先趴下了。小尺寸机器人最大的特点是“误差会被放大”关节间隙、舵机死区、重心偏移这些在 50 厘米级别机器人上可能不明显的问题在 25 厘米级别上会直接反应在步态上。所以选这个尺寸来做强化学习部署实验反而比大机器人更有价值。它逼着你把状态估计、控制频率、输出平滑这些基本功做扎实不能靠大扭矩硬扛。我自己在部署过程中最直观的感受是策略网络输出的动作如果不做限幅和滤波Microduck 的关节几乎立刻就会出现高频抖动那种感觉很像 PID 参数调得过猛时的振荡但频率更高听着就像小电机在“哒哒哒”打齿。1.2 RK3566 做主控不是为了跑大模型而是为了让部署路径接近真实产品我一直觉得做机器人实验选主控不能只看算力要看“最终产品会用什么样的处理器”。RK3566 是一颗非常典型的低成本边缘 SoC四核 Cortex-A55主频最高大概 2.0 GHz带 NPU支持 4K 解码功耗控制也还行。它不像英伟达 Jetson 那样有 CUDA 加持跑不了大网络的重训练但做强化学习策略推理完全够用因为真正部署到量产机器人上的策略网络往往不会很大通常就是一个多层感知机或者轻量循环网络。我最初也犹豫过要不要直接用 Jetson Orin Nano省事又熟悉。但后来想明白一件事Microduck 这套系统面向的是低成本、可复现、能走进课堂和开源社区的场景Jetson 的成本已经超过机器人本体好几倍了这个方向就不对。RK3566 虽然推理需要做模型转换或者在 CPU 上直接跑但它能让你提前面对“资源受限”的真实约束。很多做强化学习的人习惯了 GPU 上的浮点自由到 RK3566 上才发现原来一次推理的延迟、内存占用、线程调度全都是问题。早一点遇到这些比晚一点遇到要好得多。2. GPU 上的训练阶段把仿真里会跑变成实机也能走的关键取舍2.1 仿真器选型和训练框架有一点必须提前确认这次训练我是在英伟达 GPU 上做的。强化学习训练最常用的路线是 Isaac Lab 或者 MuJoCo 搭配 PPO 这类在线强化学习算法Microduck 这类机器人如果做足式运动控制MuJoCo 的仿真速度很有优势Isaac Lab 则胜在 domain randomization 和视觉感知相关的管线更完善。我的实际选择是把两者都用上先用 MuJoCo 做原型验证确定 reward 和动作空间的设计没有大问题再切到 Isaac Lab 做大规模并行采样。这样做的原因很直接MuJoCo 启动快、依赖简单、调试 reward 的时候一次迭代只要几分钟而 Isaac Lab 虽然并行采样效率高但环境配置和依赖处理会消耗不少时间不适合频繁改 reward。有一点我必须单独拎出来说仿真器里的机器人 URDF/MJCF 模型必须和你刷进实机的关节配置完全一致。这不是废话Microduck 这类开源项目经常会有多个版本的模型文件可能舵机安装方向不一样或者关节的 polarity 反了。如果你在仿真里用的是“正方向是抬起”实机上同一个关节却是“正方向是压下”那训练出来的策略在真机上就会表现为完全相反的动作而且非常难排查因为它不是随机乱动是“有规律地反着动”。2.2 reward 设计才是训练阶段最大的坑错误奖励不会杀死训练但会耗尽你的耐心很多新手训练强化学习机器人遇到不收敛第一反应是改网络结构或者调 PPO 超参数。但以我的经验80% 的情况问题出在 reward 上。Microduck 的行走任务最经典的 reward 设计是前进速度给正向奖励姿态倾斜给负向奖励能源消耗给一个很小的惩罚项。看起来很简单对不对但实际跑起来你会发现策略会找到你意想不到的漏洞。我这次遇到的一个典型情况就是“原地转圈刷前进速度”机器人通过快速转向让身体坐标系里的前向速度传感器读出一个虚高的数值但实际上根本没有向前移动。这就是典型的 reward hacking。为了解决这个问题我不得不加上全局坐标系下的位移约束也就是说只在 x 方向上的净位移才给奖励任何形式的旋转造成的速度分量都不计算在内。还有一类问题属于“错误奖励”就是你的 reward 数值本身写错了比如把速度阈值比较的符号写反或者把角速度惩罚项加到了线速度上。这类错误最坑的地方在于训练初期 loss 和 reward 曲线看起来都正常策略也在缓慢提升但学到一定程度就卡住。因为机器人已经在用错误的方式满足错误的目标你给的反馈和它实际的行为根本对不上。我这次在调试代码时发现一个很小的问题速度项用的是x_velocity而不是base_linear_velocity_x在 MuJoCo 里这两个值的坐标系不一样导致策略认为机器人一直“没有前进”于是加大输出幅度实机上一跑就是高频振荡。所以要养成一个好习惯训练起步前拿一个随机策略跑几百步把观测值、奖励分项全部打印出来确认数值范围和图解是对的再开始长训。2.3 导出策略模型你要的不是“权重”而是包含推理约定的完整模型包在 GPU 训练完成、策略在仿真里已经可以稳定行走之后很多人以为工作就结束了直接保存一个 .pt 文件就准备部署。这是最容易在实机上翻车的一步。因为训练是 PyTorch 环境部署环境是 RK3566你根本没法保证板子上有 PyTorch、有 CUDA、有和训练时一模一样的 Python 版本。正确的做法是把策略网络导出成 ONNX 格式或者 TorchScript然后把推理代码和模型文件一起打包形成一个“模型包”。我这次采用的方式是 ONNX 导出。下面是伪代码级别的导出流程真实场景里还需替换成你自己的网络包装方式import torch policy load_trained_policy(microduck_policy.pt) policy.eval() dummy_obs torch.randn(1, obs_dim, dtypetorch.float32) with torch.no_grad(): torch.onnx.export( policy, dummy_obs, microduck_policy.onnx, input_names[obs], output_names[action], dynamic_axes{obs: {0: batch}, action: {0: batch}}, opset_version11 )导出时最容易忽略的是 normalization 参数。很多训练代码会在网络外面对观测做 running mean 和 running std 归一化这些参数如果你在导出时没有固化进模型里部署端又不知道要除以多少那么实机输入分布直接错位策略输出就会全乱。我在第一次部署时就栽在这里仿真里走得好好的导出到 RK3566 上跑机器人像喝醉了一样东倒西歪。一开始我以为是延迟问题后来才发现是均值方差没有固化。所以导出前一定要确认是把归一化层放进了网络内部还是把归一化参数作为额外常量输入到 ONNX 图里。如果要对比不同部署方式的优劣可以参考下面的思路部署方式优点缺点适合场景直接跑 PyTorch调试方便和训练环境一致依赖太重启动慢占用内存大原型验证不推荐上实机长时间跑ONNX Runtime CPU跨平台依赖可控启动快需要转换和校验RK3566 这类资源受限设备的首选RKNN 转换到 NPU能利用 NPU 加速算子支持有限调试难度大有实时性压力且网络已经足够精简之后TensorRT英伟达平台上很快RK3566 用不了Jetson 平台对于 Microduck 这种小机器人策略网络如果只是一个 256x256 左右的 MLP在 RK3566 上用 ONNX Runtime 的 CPU 推理已经足够单次推理大概在 1~3 毫秒远小于控制周期要求。这种情况下我不建议一开始就上 RKNN 转 NPU因为 RKNN 的工具链对算子支持有限你要花额外时间去做算子替换和精度验证性价比不高。3. RK3566 实机端从被识别成 ADB 设备到稳定运行的全部细节3.1 刷机顺序和镜像选择先确认系统版本再谈适配RK3566 常见的开发板系统是 Debian 或者 BuildrootMicroduck 这类机器人一般会用比较精简的 Linux 系统因为需要控制实时性跑完整桌面系统反而浪费资源。我这次拿到板子之后第一件事就是确认系统镜像版本。RK3566 的启动过程比较复杂芯片内部 BootROM 会先加载 loader然后 loader 再去读分区表最后把 kernel 和 rootfs 加载起来。刷机过程很容易遇到的一个情况是RK3566 被电脑识别成了一个 ADB 设备而不是烧录工具期望的 rockusb/loader 设备。出现这个现象通常是因为板子已经正常启动了 Android 或者带 ADB 的 Linux 系统ADB 守护进程接管了 USB 连接。这时候如果你直接打开烧录工具想烧镜像工具会一直提示“没有发现设备”。解决思路很简单先让板子进入 loader 模式。大部分 RK 板卡在烧录时都需要按住板子上的 recovery/maskrom 按键再上电或者执行adb reboot loader让系统重启到烧录模式。如果你发现adb devices能看到设备但烧录工具不认先用下面这两条命令手动切到 loaderadb devices adb reboot loader执行完第二条命令后设备会短暂断开再重新枚举此时lsusb应该能看到 Rockchip 相关的 vendor ID烧录工具也就能识别了。这个“被识别成 ADB 设备”本身不是坏事它恰恰说明系统最起码已经正常启动了后续调试 ADB 通道也能用得上。3.2 RK3566 的 root 权限和新分区规划别在系统盘里裸奔拿到一个能跑的系统之后紧接着就是 root 权限。Microduck 这类机器人项目部署时需要在板子上装运行库、写 systemd 服务、调整网络配置很多操作没有 root 权限做不了。RK3566 的 Linux 系统一般有两种 root 方式一种是镜像本身就默认以 root 登录另一种是普通用户加 sudo。如果你用的官方 Debian 镜像通常有现成的 sudo 用户但如果遇到那种精简版 Buildroot 系统可能连 sudo 都没有只能通过修改内核 cmdline 里的init/bin/sh进入单用户模式来重置密码或修改配置。我在这次部署中做的一个重要操作是“给系统增加一个新的分区”。RK3566 上的 eMMC 存储天然是分区化的loader 分区、uboot 分区、boot 分区、rootfs 分区、userdata 分区。默认分区表里userdata 往往占了一大块但你是跑机器人程序的根本不需要那么多 Android 风格的 userdata反而需要为算法模型、日志和虚拟内存预留更多空间。修改分区表的过程是这样的先在开发包中找到对应的 parameter.txt 文件它定义了每个分区的起始位置和大小然后用 RKDevTool 或者 upgrade_tool 把新分区表烧进去。注意这个操作会清空数据所以必须提前备份。下面是一个简化过的 parameter.txt 里新增一个 “policy” 分区的写法示例CMDLINE: mtdpartsrk29xxnand:0x000020000x00004000(uboot),0x000020000x00006000(misc),0x000100000x00008000(boot),0x000400000x00018000(rootfs),0x000100000x00058000(policy),-0x00068000(userdata)新增的 policy 分区可以用来存放 ONNX 模型文件跟 rootfs 分开以后升级系统镜像时不会误覆盖模型也能防止模型文件刷坏导致整个系统无法启动。实际改动时一定要按照你所用板卡开发包里的模板格式来不同 SDK 的 parameter 格式会略有差异。3.3 开机自启策略服务systemd 远比赛车脚本靠谱如果只是手工测试你可以在终端里手动启动 Python 推理脚本。但想要让 Microduck 在野外场景里作为独立机器人运行你必须有一个可靠的自启动机制。很多新手会写一个/etc/rc.local或者往.bashrc里塞启动命令这在有桌面的系统里偶尔有效但一旦网络服务、USB 枚举或者文件系统挂载没有准备好脚本就会启动失败而且失败原因极难排查。我在 RK3566 上推荐用 systemd service 来管理强化学习推理进程。它的好处是自带依赖管理、自动重启、日志收集。下面是我这次在 Microduck 上用的一个 service 文件示例[Unit] DescriptionMicroduck RL Policy Service Afternetwork.target Wantsnetwork.target [Service] Typesimple WorkingDirectory/opt/microduck ExecStartPre/usr/bin/sleep 5 ExecStart/usr/bin/python3 /opt/microduck/run_policy.py Restartalways RestartSec3 EnvironmentPYTHONUNBUFFERED1 [Install] WantedBymulti-user.target这里有几个关键细节。ExecStartPre里的 sleep 5 看起来是 hack但非常实用RK3566 上电后 USB 设备枚举和串口初始化需要时间如果推理进程启动太早它打开串口时会失败。与其在 Python 里写一堆重试逻辑不如让 systemd 在上层先等一下。Restartalways保证策略进程万一崩了能在三秒后自动拉起来。EnvironmentPYTHONUNBUFFERED1则是为了让 print 日志能够实时写进 systemd journal不然程序崩溃时你看不到崩溃前的最后输出。3.4 RK3566 上的推理依赖部署做一个干净的运行环境RK3566 的 CPU 是 ARM 架构不能直接用 x86 上 pip 装好的那些 wheel 包。Microduck 项目里我需要的核心依赖不多ONNX Runtime、NumPy、PyYAML再加一个串口库。这里有一个操作建议不要在系统全局 Python 环境里 pip install否则哪天你想升级 NumPy 或者装别的包很容易把系统依赖搞坏。RK3566 资源有限用虚拟环境比在 x86 上更刚需因为很多精简系统里 Python 是被系统服务依赖的比如某些系统的python3被用来跑一些底层脚本你把 site-packages 弄乱了系统启动可能直接挂掉。我最后采用的是 venv 方式python3 -m venv /opt/microduck/venv source /opt/microduck/venv/bin/activate pip install --upgrade pip pip install onnxruntime numpy pyserial装完之后运行脚本时直接使用/opt/microduck/venv/bin/python3来启动配合上面 systemd 服务的 ExecStart 路径确保进程使用的一直是这个干净环境。4. 实机调试那些事策略推理进电机控制闭环之前你还差几步4.1 从张量到关节指令别跳过中间层直接连电机在仿真里策略网络输出的 action 通常被解释成目标关节角度、目标关节速度或者归一化力矩。但在真机上Microduck 的关节电机一般是由串行总线舵机或者直流减速电机加驱动器组成的它们期待的指令格式各不相同。如果你在仿真里输出的是归一化角度实机上必须做一步逆归一化把 [-1, 1] 映射到实际关节范围。这一步看起来简单但太容易出问题。比如仿真里关节角度范围是 [-0.8, 0.8] rad实机舵机范围可能是 [-1.2, 1.2] rad如果你的动作映射直接用原始网络输出传给舵机策略在仿真里学会的“小幅调整”变成了实机上的大幅摆动。我建议在推理脚本里明确写一个动作缩放层并且先在开环模式下逐个关节验证方向再上闭环策略。下面是我这次用的一个简化版本的控制循环骨架import onnxruntime as ort import numpy as np import serial sess ort.InferenceSession(/opt/microduck/models/microduck_policy.onnx) ser serial.Serial(/dev/ttyS4, 1000000, timeout0.01) def get_observation(): # 从 IMU 和关节编码器读取数据并做与训练时相同的归一化 obs np.zeros((1, obs_dim), dtypenp.float32) return obs while True: obs get_observation() action sess.run(None, {obs: obs})[0][0] action np.clip(action, -1.0, 1.0) * joint_scale joint_cmds action_to_motor_command(action) # 角度转 PWM/串行指令 ser.write(joint_cmds)这里我特别想说np.clip和joint_scale这两个东西。策略网络在没有约束的情况下可能输出很大的值尤其是状态分布稍微偏移时输出会迅速饱和。如果不做 clip实机舵机会瞬间收到一个超过机械范围的角度指令轻则异响重则扫齿。把动作 clip 到一个安全范围是实机部署的最后一道保险丝。4.2 强化学习策略和底层 PID 的关系不要互相抢戏关于“基于强化学习的 PID 控制”这个热搜词我想多说一句。在 Microduck 这种运动控制机器人上强化学习策略通常不会去替代底层电机的位置环/速度环 PID。策略负责的是“上层决策”它根据身体姿态和目标速度输出一个期望的关节角度。而关节电机内部仍然有一个 PD/PID 控制器在努力让实际角度跟上这个期望值。二者是层级关系不是替代关系。如果你想让强化学习去学 PID 增益那就变成了另一个任务形态策略输出 Kp、Ki、Kd 三个参数底层控制环再根据这些参数计算力矩。这种方案可行但实机稳定性的调试难度会高不少因为增益对噪声和延迟非常敏感。Microduck 这种项目的合理路线是先固定底层电机 PID把 RL 策略的观测空间和动作空间限定在更抽象层面不要一上来就搞端到端控制。我在实机调试时还踩过一个和反馈频率有关的坑RK3566 上跑 Python 的推理循环如果只是简单地while True: run(), 实际执行频率很不稳定波动范围可能到 100 Hz ~ 350 Hz。这对强化学习策略是致命的因为训练时的控制频率是固定的实机频率如果忽高忽低相当于你在给策略喂一种它从未见过的分布偏移。解决办法是使用实时循环或者至少用time.perf_counter()做帧率锁定把控制频率尽量稳定在一个值附近哪怕频率不高也要稳定。下面是一个频率锁定的周期控制骨架import time control_freq 200 # Hz period 1.0 / control_freq next_time time.perf_counter() while True: obs get_observation() action sess.run(None, {obs: obs})[0][0] send_action(action) next_time period sleep_time next_time - time.perf_counter() if sleep_time 0: time.sleep(sleep_time) else: # 超时了说明这一帧耗时过长需要记录 next_time time.perf_counter()不要小看这个简单的频率控制。在后面的整机联调里我发现只要把频率稳定在 200 HzMicroduck 的姿态稳定性和仿真里已经比较接近而一旦循环频率掉到 100 Hz 以下机器人明显变得僵硬反应迟钝仿佛策略在“等数据”一样。4.3 真机整机联调时最大的意外低频共振和关节热度策略在仿真里跑得再稳实机上也会暴露机械共振问题。25 厘米的 Microduck 结构紧凑腿部连杆比较短关节刚度相对较高如果策略输出里带有某一频段的周期分量整个机身会出现肉眼可见的低频共振。在实验室木地板上测试时共振表现尤其明显。解决思路有两个方向。第一个方向是给动作输出做低通滤波对策略输出的动作序列做一次指数滑动平均例如new_action 0.6 * last_action 0.4 * target_action把高频抖动的能量滤掉一部分。第二个方向是直接去训练的 domain randomization 里加执行器延迟模拟让策略在训练时就知道关节无法瞬时响应从而自主学会输出平滑的动作轨迹。我在最终版本里两个方向都做了实机表现才真正稳定下来。还有一个很容易被忽视的点是关节温度。小机器人用的舵机没有主动散热长时间高频率大幅度运动后电机温度会明显上升导致输出力矩下降。Microduck 在强化学习策略控制下运动比传统的正弦步态要“狂野”得多因为策略为了追求速度会频繁输出大幅度动作。如果你发现连续跑几分钟后机器人姿态开始变差优先摸一下各个关节电机温度很可能不是策略退化而是热衰减。5. 如果你想复刻这条路线建议顺序和值得你留意的资源5.1 先从最短路径走通再考虑深度优化很多人都问 Microduck 到底怎么训练、怎么部署我的回答是不要一上来就追求在仿真里跑出完美步态。正确顺序是先让机器人动起来再让机器人“按策略动起来”最后才谈优化。具体建议顺序是第一步先在 RK3566 上手工控制关节确认每个电机通讯正常、方向正确、角度反馈准确第二步写一个简单的正弦步态让机器人能在实机上缓慢走起来这一步的目的是建立坐标系统和控制频率的基本盘第三步跑一个预训练好的策略模型不看训练曲线只看是否能用 ONNX 格式成功推理并输出关节指令第四步重新走一遍完整的强化学习训练流程包括 reward 设计、domain randomization、导出和上机测试。这个顺序的核心逻辑是你要先把“部署链路”中除了训练之外的所有环节都打通再来回头做强化学习迭代。否则每次训练一个策略你都不知道效果不好到底是策略问题还是电机反馈问题还是归一化问题所有问题混在一起会让你完全失去判断力。5.2 训练时值得多看的几个扩展方向Microduck 的训练如果只做 PPO你可以很快跑通但会遇到一个常见的现实问题实机采样成本高在线策略学习效率低。如果你想进一步压低实机调试时间可以了解一下离线强化学习和基于模型的强化学习这两条路线。离线强化学习用的是预先采集好的数据集训练时可以完全不碰实机适合 Microduck 这种长时间跑容易发热的平台。另外如果你想让 Microduck 学会更复杂的操作技能比如目标追踪、避障或者在陌生地形上保持平衡Domain Randomization 是你的必修课。我的做法是在训练时随机化机器人的重心位置、关节摩擦系数、电机力矩常数以及控制延迟模拟真实硬件个体差异。这样同一个策略不用改任何参数就能在不同 25 厘米 Microduck 硬件上直接运行。有一说一这批“随机化出来的策略”在实机上的表现比我在仿真里精调出来的单一环境策略稳定得多。5.3 如果你准备复刻这几个关键词可以先查一查Microduck 的 GitHub 仓库是一个非常不错的起点搜索 microduck 就能找到。仓库里一般会包括机械结构文件、固件源码、训练脚本和部署教程但因为硬件版本和主控方案会不断变化你拿到的版本未必和我的完全一样。学习的时候不要只盯着“训练代码”看要多留意 issue 区里跟实机部署、舵机映射、ADB 识别相关的问题那里面藏着的才是真正绕过弯路的信息。博文最后额外说一个小技巧。RK3566 这类板子在跑强化学习控制进程时如果你用手触摸芯片散热片感觉很烫不要只想着加风扇。先查一下是不是推理频率过高或者 Python 进程里有隐式内存回收导致 CPU 频繁停顿。我最后把控制进程的 CPU 亲和性绑定到了两个 A55 大核上并且用nice提升了进程优先级温度和控制稳定性都有明显改善。从英伟达 GPU 上的虚拟环境到 25 厘米 Microduck 真正迈出第一步中间隔的不是算力而是那些被省略的细节。一个 obs 的归一化、一个输出的 clip、一个 systemd 的 Restart都可能成为压垮整个项目的最后一根稻草。把自己的实践过程和踩坑记录写下来比单纯跑通项目本身有价值得多。希望这篇手记能让你在复刻 Microduck 或者部署其他强化学习机器人时少走几步弯路。