RK3566 NPU部署强化学习四足机器人策略:从训练到实机的完整链路
2026/9/6 9:58:56 网站建设 项目流程

先说个结论:这块板子真能跑强化学习策略,但不是“装上就能跑”那种能跑。我在 RK3566 上把 Microduck 的推理延迟压到了 7ms 左右,端到端控制频率稳定在 100Hz,四足姿态和步态都能跟着仿真走,但中间踩的坑,足够写满一本小册子。

Microduck 是一台 25 厘米级别的四足机器人,整机轻、关节多、成本可控,非常适合拿来做强化学习运动控制的教学和验证。它不像宇树 Go2 那样自带全套仿真和部署工具链,反而让我把“从训练到部署”这条链路从头到尾走了一遍:英伟达 GPU 上训练策略、导出模型、量化、再迁到 RK3566 的 NPU 上推理,最后驱动底层舵机完成闭环。

这篇手记就围绕这条链路展开。无论你是想复现一个完整的足式 RL 项目,还是准备把强化学习策略部署到 RK3566、RK3588 这类嵌入式平台,都可以参考这里面的方案和教训。

1. 项目全貌:为什么是 Microduck + RK3566 的组合

1.1 Microduck 的硬件定位与技术特点

Microduck 其实是一个开源的四足机器人项目,重点在“小型化”和“可复现”。整机 25 厘米级别的身长,通常由 12 个关节组成——每条腿 3 个自由度,髋关节两个、膝关节一个。这种自由度配置是四足机器人最经典的结构,也是强化学习策略最常训练的动作空间。

质量大概在 1 到 1.5 公斤之间,关节执行器常见方案有两类:一类是舵机驱动,便宜、装配简单,适合教学演示和步态验证;另一类是带减速箱的小型直流无刷电机,响应快、力矩控制更精确,适合做更接近工业级的行为。我用的版本是舵机方案,这也直接影响了我后续对控制频率和动作平滑的处理方式。

还有一个容易忽略的点:Microduck 的机身体积小,意味着 IMU 和主控板都高度集成。常见的配置是主控跑 Linux 系统(比如 RK3566 开发板),通过串口或者 I2C 与底层舵机驱动板通信,IMU 则挂在主控的 I2C 或 SPI 上。这样的架构决定了一件事:强化学习策略的推理结果是“关节目标角度”还是“关节力矩指令”,取决于你用的是舵机还是电机,而这两者在部署逻辑上差别非常大。

1.2 为什么选 RK3566:算力、成本与功耗的平衡

很多人在做足式机器人时纠结选树莓派、Jetson 还是 RK3566。我直接说结论:如果你只是跑一个 12 维动作输出的 MLP 策略网络,RK3566 的 NPU 性能完全够用,而且功耗、成本和开发难度都更可控。

RK3566 是一颗四核 Cortex-A55 的 SoC,内置 0.8 TOPS 算力的 NPU(INT8)。这个数字放到大模型时代确实不够看,但 RL 运动控制策略的网络结构通常非常小——两层或三层 MLP,每层 256 或 512 个神经元,输入维度 40 到 60,输出维度 12。这种规模的网络在 NPU 上推理一次,耗时基本在几毫秒量级,比 CPU 快得多。

对比选择:

  • 树莓派 4B:CPU 强,但没有 NPU,纯跑 PyTorch 推理在 20 到 40ms 级别,控制频率上不去,而且 CPU 占用率飙高,容易被底层调度干扰。
  • Jetson Orin Nano:性能碾压,但是价格高、功耗大,对 Microduck 这种 25 厘米的小机器人来说,电池和散热都是问题。
  • RK3566:开发板几十到一百多块,功耗 2 到 5W,自带 NPU,官方提供了从模型转换到运行时推理的完整工具链。

RK3566 还有一个隐性优势:国产芯片的文档和工具链做得比较用心,rknn-toolkit2 持续在更新,遇到问题在社区里能搜到不少案例。对于部署链路的研究来说,“有人踩过坑”比“理论性能高”重要得多。

1.3 整条技术链路的设计蓝图

整个项目的链路可以拆成三段:训练、迁移、部署。

训练端在英伟达 GPU 上进行。使用 Isaac Lab(或者老一些的 Isaac Gym 版本)搭建四足机器人仿真环境,导入 Microduck 的 URDF 模型,用 PPO 算法训练策略网络。训练完成后把 PyTorch 模型导出成 ONNX 格式。

迁移端在 x86 主机上完成。用 Rockchip 官方的 rknn-toolkit2 工具,把 ONNX 模型转换成 RKNN 格式,做 INT8 量化以减少模型体积和推理延迟,同时尽量保持策略精度不退化。

部署端在 RK3566 上完成。板子加载 RKNN 模型,通过 NPU 进行推理,读取 IMU 和关节反馈,拼接观测向量,推理输出目标关节动作,再通过底层 PD 控制转换成舵机或电机的指令,形成完整闭环。

这三段各自有各自的深坑,模型转换和 sim-to-real 迁移是最容易翻车的环节,后面我用大篇幅来写。

2. 训练端:如何在英伟达 GPU 上训出能部署的策略

2.1 仿真环境搭建:Isaac Lab 与 Microduck 的 URDF 导入

训练四足机器人强化学习策略,目前主流方案是 NVIDIA 的 Isaac Lab 或 Isaac Gym。Isaac Gym 的早期版本已经停止维护,新项目建议直接用 Isaac Lab,API 更现代,社区也更活跃。

安装 Isaac Lab 的基本流程:

git clone https://github.com/isaac-sim/IsaacLab.git cd IsaacLab ./isaaclab.sh --install ./isaaclab.sh --python scripts/train_rl.py --task ...

关键一步是把 Microduck 的 URDF 模型导入 Isaac Lab。URDF 文件可以从 Microduck 官方 GitHub 仓库找到,里面包含了每个 link 的质量、惯性参数和关节的限位信息。这里我建议不要省事,务必把惯性参数核一遍——很多小四足项目的 URDF 惯性数据是从其他机型抄过来的,这会导致仿真里的动力学行为和真机差距很大,策略在仿真里跑得再好看,到了真机也是摔。

导入 URDF 之后,还需要做两件事:配置观测空间和动作空间。观测空间我用了最常见的组合:IMU 的三轴角速度、机身姿态(用四元数或欧拉角表示)、12 个关节的位置和速度、上一时刻的动作,再加一个相位变量用于步态生成。这个向量大概在 50 维左右。动作空间就是 12 个关节的目标位置。

2.2 PPO 训练参数与算力占用实录

训练 RL 策略,PPO 依然是四足运动控制里的默认选择,稳定、调参空间大。我的训练配置如下:

  • 并行环境数:4096
  • 学习率:3e-4,训练中后期降到 1e-4
  • clip 范围:0.2
  • GAE lambda:0.95
  • 每次迭代的 transition 数:24
  • 训练步数:2000 万到 3000 万步

在 RTX 4090 上,4096 个并行环境占用显存大约 8 到 12GB,训练 2000 万步大约需要 4 到 6 个小时。如果用的是消费级显卡(比如 RTX 3060),并行环境数降到 2048,显存也够,只是训练时间会拉长一倍左右。

这里有个经验:训练时长不是越长越好。我在实践里发现,四足策略训练到一定阶段后,仿真里的表现会趋于稳定,但继续训练可能会导致策略“过度适应仿真环境”,反而加重 sim-to-real gap。建议训练过程中每隔一段时间保存一个 checkpoint,后期通过真机测试来挑选最适合部署的版本。

2.3 从 checkpoint 到 ONNX:固定维度是重中之重

训练完成后,导出 ONNX 是整个迁移链路的第一步,也是最容易被忽略的一步。

用 PyTorch 导出 ONNX 的核心思路:

import torch import torch.onnx policy.eval() dummy_obs = torch.randn(1, obs_dim).to(device) torch.onnx.export( policy, dummy_obs, "policy.onnx", export_params=True, opset_version=12, input_names=["obs"], output_names=["action"], dynamic_axes=None )

这里最关键的参数是dynamic_axes=None,即固定输入输出的维度。很多人在这一步不设置 dynamic_axes,导致导出的 ONNX 模型输入维度是动态的。RKNN-Toolkit 对动态维度的支持比较有限,转换时容易报错或者产生性能很差的图。我的做法是固定 batch size 为 1,推理时永远只处理一个观测向量。

另外,opset_version 建议设置在 11 到 13 之间。太新的 opset(比如 17、18)里有些算子 rknn-toolkit2 还不支持,转换时会报“Unsupported op”。如果你发现某一层的算子不兼容,可以用torch.onnx.export(..., opset_version=12)之类的版本回退来规避,大多数情况下能解决。

2.4 顺带聊聊“基于强化学习的 PID 控制”

很多人在搜“基于强化学习的 PID 控制”,这里我想单独说一下。四足机器人运动控制里,RL 和 PID 不是对立关系,而是分工关系。

对于 Microduck 这种舵机驱动的机器人,底层通常是 PD 控制器:给定目标关节角度,PD 计算输出力矩或者舵机指令。RL 策略的作用是“决定目标关节角度是什么”,也就是生成期望运动轨迹和动作;PD 负责“执行这个目标”。

另外一种思路是让 RL 直接学习 PID 参数,比如把 PID 增益作为策略输出的一部分。但在高频运动控制场景下,这种做法并不常用,因为 RL 策略输出 PID 参数意味着控制律完全由神经网络生成,安全性和稳定性都很难保证。主流的足式机器人 RL 方案(比如宇树的开源工作、国内外实验室的 locomotion 研究)基本都是“RL 生成目标 + PD 执行”的两级架构。

如果你是想做 RL 和 PID 结合的学习项目,建议先把“RL 输出关节目标位置,PD 跟踪”这条路线吃透,再考虑更激进的控制架构。

3. 模型迁移:PyTorch 到 RKNN 的完整链路与避坑

3.1 rknn-toolkit2 环境准备:版本匹配决定成败

模型转换需要在 x86 主机上安装 Rockchip 官方的 rknn-toolkit2。安装过程不复杂,但版本匹配是个大坑。板子上的 RKNN Runtime 版本必须和转换工具的版本对应,否则轻则推理结果错误,重则模型根本加载不了。

我的操作流程:

# 用 conda 创建独立环境 conda create -n rknn python=3.10 conda activate rknn # 安装 rknn-toolkit2 pip install rknn-toolkit2-x.x.x-cp310-cp310-linux_x86_64.whl

安装完成后,先跑一遍官方自带的 demo 做验证,确保环境没问题,再转换自己的模型。这一步能帮你区分“环境问题”和“模型问题”。

版本对应关系是门玄学,我在实际中吃过亏:rknn-toolkit2 的某个新版本在转换时正常,但生成的 RKNN 模型加载到板子上的旧版本 Runtime 里直接崩了。我的建议是先去板子的系统镜像里查一下自带的 rknn runtime 版本,然后按这个版本去选择对应版本的转换工具。不要盲目追新。

3.2 ONNX 检查与输入输出对齐

拿到了 ONNX 模型之后,不要急着做转换。先花 10 分钟确认模型的输入输出和你的预期一致。我用的是一个很简单的办法,在 x86 上用 onnxruntime 加载模型做一次推理:

import onnxruntime as ort import numpy as np sess = ort.InferenceSession("policy.onnx") input_name = sess.get_inputs()[0].name output_name = sess.get_outputs()[0].name obs = np.random.randn(1, obs_dim).astype(np.float32) action = sess.run([output_name], {input_name: obs}) print(action)

这一步能验证两件事:模型输入输出维度是否正确、数值范围是否合理。如果在这里动作输出就是 NaN 或者数值爆炸,那说明网络导出本身出了问题,不要带着问题往下走。

同时要注意 ONNX 模型里是否包含大量的IdentityGather等冗余算子。RKNN 转换器对这类算子的支持时好时坏,我遇到过一次因为Gather算子导致转换失败的情况,最终的解决方案是在导出 ONNX 时启用torch.onnx.export的优化参数,或者用 onnx-simplifier 做一次简化:

python -m onnxsim policy.onnx policy_sim.onnx

简化后的模型结构更干净,RKNN 转换的成功率更高。

3.3 INT8 量化:量化数据集决定策略的生死

RK3566 的 NPU 在 INT8 下算力是 0.8 TOPS,FP16 下性能会明显下降。为了把推理延迟压到 10ms 以内,我最终选择了 INT8 量化。

量化不是直接拿模型转换就行,你需要准备一个“量化数据集”——一组有代表性的输入数据,工具会根据这些数据统计每一层的数值范围,从而决定 INT8 量化的缩放因子。

量化数据集怎么构造?最理想的方式是在仿真环境里采集策略运行时的真实观测数据。我在仿真里让策略随机走各种地形、各种速度,录制了大约 2000 帧观测向量,存成 npy 文件。注意,量化数据集要覆盖到策略在真机上可能遇到的各种观测范围,比如姿态倾斜较大、关节角速度较高的情况。如果量化数据只包含“正常行走”时的观测,模型在面对异常姿态时可能输出严重失真。

rknn-toolkit2 的量化配置核心代码如下:

from rknn.api import RKNN rknn = RKNN() rknn.config( mean_values=[[mean_0, mean_1, ...]], std_values=[[std_0, std_1, ...]], target_platform='rk3566', quantized_dtype='w8a8' ) rknn.load_onnx(model='policy_sim.onnx') rknn.build(do_quantization=True, dataset='dataset.txt')

这里有一个重要细节:mean_valuesstd_values不要和训练时的观测归一化参数混为一谈。RKNN 的 mean/std 是输入数据的预处理参数,量化时会把输入数据按(x - mean) / std进行缩放。我建议在这里直接填 0 和 1,也就是不做预处理,把归一化逻辑留在部署代码里手动实现。这么做的原因很简单:训练时的观测归一化参数非常多(每个观测维度有自己的 mean/std),如果全部塞给 RKNN,会显著增加调试复杂度,而且一旦某个值填错,很难排查。

3.4 转换报错与量化损失的排查思路

模型转换过程中最常见的报错就是某个算子不支持。我的处理思路是这样的:

  • 看日志里具体是哪个算子报错,去 rknn-toolkit2 的 docs 目录里查算子支持列表。
  • 如果算子不在支持列表里,优先检查 ONNX 算子版本是否过高,尝试降低 opset 重新导出。
  • 如果降低 opset 仍然不行,尝试用 onnx-simplifier 简化图结构。
  • 实在不行,就把这个算子所在的子模块在 PyTorch 里改写成更基础的算子组合,再重新导出。

量化完成后,先用仿真数据评估量化损失。我的评估方法是对比原始 ONNX 模型和量化后的 RKNN 模型在相同输入下的输出差异。如果动作输出的差异超过 0.1 弧度(对于关节目标角度来说),说明某些层对量化过于敏感,这时候需要做“混合量化”:把敏感层保持 FP16,其余层用 INT8。

混合量化的做法是在 RKNN 配置中指定custom_quantize_layers,例如:

rknn.config( ... custom_quantize_layers=[['layer_name', 'fp16']] )

这个操作能救回大部分量化性能损失,代价是推理延迟会略微增加,但通常比全 FP16 快很多。我最终部署到板子上的模型就是这样:绝大多数层走 INT8,一两个敏感层走 FP16,推理延迟 7ms 左右,动作输出和原始模型的误差在可接受范围内。

4. 实机部署:RK3566 上的推理与控制闭环

4.1 RKNN Runtime 接入:从 Python 到 C 的迁移

RK3566 上加载和运行模型,官方提供 Python API(rknn-toolkit-lite2)和 C/C++ API(librknnmrt)。对于控制频率要求较高的场景,我最终用 C API 实现了推理环节,Python 版本在接口调用上更简单,但几次实测下来,Python 的 GC 和解释器开销会导致控制周期的抖动。

使用 C API 的一个最小示例骨架:

#include "rknn_api.h" rknn_context ctx; rknn_init(&ctx, "policy.rknn", 0, 0, NULL); rknn_input inputs[1]; inputs[0].index = 0; inputs[0].pass_through = 0; inputs[0].type = RKNN_TENSOR_FLOAT32; inputs[0].size = obs_dim * sizeof(float); inputs[0].fmt = RKNN_TENSOR_NCHW; rknn_output outputs[1]; outputs[0].want_float = 1; while (running) { // 1. 拼接观测向量 obs memcpy(inputs[0].buf, obs.data(), obs_dim * sizeof(float)); // 2. 设置输入 rknn_inputs_set(ctx, 1, inputs); // 3. 前向推理 rknn_run(ctx, NULL); // 4. 获取输出 rknn_outputs_get(ctx, 1, outputs, NULL); memcpy(action.data(), outputs[0].buf, 12 * sizeof(float)); rknn_outputs_release(ctx, 1, outputs); // 5. 将action送到底层控制 }

这里有一个性能关键点:rknn_inputs_setrknn_run之间会有数据拷贝,如果每次推理都重新分配内存,开销会非常大。我的做法是在主循环外一次性分配好输入输出的内存 buffer,循环内只做 memcpy,不涉及任何动态内存分配。

4.2 控制频率与线程架构:先想清楚再动手

Microduck 用舵机方案时,控制频率不要贪高。舵机的物理响应速度通常在 50Hz 到 100Hz 之间,即使 RK3566 的推理能跑到 1000Hz,舵机也跟不上。我把整个控制周期定为 10ms(100Hz),也就是每 10ms 完成一次“采集观测 -> 推理 -> 输出动作”的闭环。

这样的频率下,线程架构可以不用搞得太复杂。我的方案是双线程:

  • 主线程:负责控制闭环,按定时器中断(10ms)触发,读取 IMU 和关节反馈,执行推理,输出动作指令。
  • 辅助线程:负责日志记录和外部通信(比如和上位机交互、参数调整)。

主线程里绝对不进行任何可能阻塞的操作,比如不要直接写日志到 SD 卡、不要开 socket 等待、不要 printf 到串口。我在实际调试中经常遇到一个问题:加了调试打印之后,控制频率波动明显,机器人走路变得一瘸一拐。这不是策略变了,是调度抖动导致控制周期不稳定。RL 策略是在固定控制频率下训练的,实际运行频率的波动会直接影响行为表现。

4.3 观测归一化:部署端最容易错的一步

RK3566 上做推理时,观测向量必须和训练时保持完全相同的预处理流程。训练时一般用 running mean 和 running std 对每个观测维度做标准化,部署时需要把这些参数固化下来。

我的做法是在训练完成后,把每个观测维度的 mean 和 std 保存成一个 C 头文件:

static const float OBS_MEAN[OBS_DIM] = { ... }; static const float OBS_STD[OBS_DIM] = { ... }; void normalize_obs(float *obs) { for (int i = 0; i < OBS_DIM; i++) { obs[i] = (obs[i] - OBS_MEAN[i]) / OBS_STD[i]; } }

这里特别提醒:归一化时的 clip 范围也要一致。训练时我通常把归一化后的观测 clip 在 [-5, 5] 之间,部署端也要做同样的 clip。不要小看这一步,观测里如果出现一个 20 以上的离群值,策略输出可能会被带偏,导致机器人动作异常。

还有一点,输出动作也可能在训练时做了缩放。比如 RL 策略输出的是 [-1, 1] 之间的归一化动作,需要映射到实际的关节角度范围。这些缩放系数同样要固化到部署代码里,不能遗漏。

4.4 底层控制执行:PD 控制器与舵机指令生成

策略输出的是一组目标关节角度,但舵机最终执行的也是目标角度。对于舵机版 Microduck,PD 控制其实可以简化成“直接让舵机跟踪目标角度”。不过为了让动作更平滑,我仍然在中间加了一层位置-速度控制:

// 舵机目标位置 float target_pos = action[i] * POS_SCALE + POS_OFFSET; // PD 输出速度指令(底层舵机支持速度控制时) float pos_error = target_pos - current_pos[i]; float cmd_speed = Kp * pos_error; cmd_speed = clamp(cmd_speed, -MAX_SPEED, MAX_SPEED);

在把指令发给舵机之前,我会再做一次限幅:动作变化量不能超过单步最大值。比如 100Hz 控制频率下,关节角度每步变化不超过 0.05 弧度。这个限幅能有效防止策略偶尔输出的跳变导致舵机瞬间过流或者机械撞击。

4.5 如果 RK3566 不够用,下一步往哪走

如果后续想跑更复杂的感知模块或者多机协同,RK3566 的接口和算力会显得局促。可以考虑 RK3588:8 核 CPU、6 TOPS NPU,性能是 RK3566 的 7 到 8 倍,而且外设接口丰富很多。预算允许的情况下,RK3588 是一个明显的性能扩展方向。

另一个方向是考虑 Jetson Orin Nano,CUDA 生态成熟,可以无缝跑 PyTorch 模型,省去模型转换的环节。但要注意功耗和散热,25 厘米级别的 Microduck 比较难背着 Orin Nano 跑起来,电池和结构都得动。

我在这个项目里坚持用 RK3566,核心原因是“把链路跑通”比“追求性能”更重要——部署方案越接近量产约束,整个研究的技术含金量就越高。

5. Sim-to-Real:策略从仿真到真机,最重要的临门一脚

5.1 训练时的域随机化:为部署铺路

很多人在仿真里把策略训练得很好,放到真机上却完全不行,根本原因就是过度拟合仿真环境。解决这个问题的手段是域随机化(Domain Randomization),也就是在训练时随机改变仿真环境的参数,让策略学会在多种条件下工作。

我在训练时随机化了几组参数:

  • 关节摩擦系数:从 0.4 到 1.2 之间随机
  • 机身质量和重心位置:质量加减 20%,重心在机身前后来回偏移
  • 电机力矩响应:增加随机延迟,模拟真实舵机的滞后
  • 观测噪声:每个观测维度加上一定比例的高斯噪声
  • 地面摩擦力:在不同地形上随机变化
  • 额外推力扰动:随机在机身上施加 10N 以内的瞬时外力,模拟碰撞和冲击

还有一个容易被忽略的参数:控制频率。真实部署时控制频率会有抖动,不是每一次都是严格的 10ms。我在仿真里把控制周期设置为随机量,比如在 8ms 到 12ms 之间随机,策略对控制频率的敏感性明显降低,迁移到真机后的适应性好了很多。

5.2 真机校准:关节方向、零点与 IMU 朝向

sim-to-real 调试的第一步不是调策略,而是校准硬件。我踩过最深的坑是关节方向反了。

仿真里的关节正方向是 URDF 里定义的,但真机的舵机正方向可能和仿真相反。如果某个关节的正方向反了,策略在真机上就会表现得非常怪异——比如某个腿一直在往错误的方向踢。排查方法是逐关节测试:在部署端写一个测试程序,依次给每个关节发送正方向角度指令,观察真机关节的实际转动方向,并和仿真里的正方向定义逐一比对。

IMU 的朝向和轴方向同样需要校验。仿真里 IMU 的坐标轴跟随机体坐标系,但真机上 IMU 的安装方向可能不同。如果 IMU 的某个轴装反了,策略读取到的姿态和角速度就是错误的,直接导致平衡失效。我在校准 IMU 时是用一个已知姿态的基准面,分别验证俯仰、滚转、偏航的数据显示是否符合预期。

还有一个很容易忽视的是关节零点。舵机在装配时不一定处于 0 度位置,需要先跑一个“归零程序”,把每个关节调整到机械零点,再开始部署策略。我就是因为省了这一步,第一次真机调试时机器人站起来时姿态歪得离谱。

5.3 策略动作平滑:低通滤波器与动作限幅

RL 策略在高频推理下输出的动作会有一个特点:相邻时刻的动作存在高频抖动。这在仿真里看不出来,因为仿真环境没有物理噪声,但在真机上会导致舵机频繁抖动、发热、甚至损坏。

我的处理方式有两层:

第一层是动作低通滤波。在部署代码里对策略输出做一阶低通滤波:

smooth_action = alpha * raw_action + (1.0 - alpha) * last_action;

alpha 的值在 0.3 到 0.6 之间比较合适,太小会让动作滞后明显,太大则滤波效果差。这个参数需要在真机上反复调,我最终用的是 0.4。

第二层是单步限幅。限制每一步动作相对上一时刻的变化量,比如最大变化不超过 0.05 弧度。这个限幅和滤波配合使用,能有效抑制策略输出的异常抖动。

滤波和限幅的本质是给策略输出加一个“物理可执行性”的约束,因为策略是在理想仿真里训练的,它生成的轨迹在真机上可能需要更保守的执行方式。

5.4 安全落地:状态机与急停逻辑

真机调试时如果策略行为异常,最大的风险是机械损坏。我的安全设计分三层:

  • 第三层:策略输出限幅,限制动作范围,防止关节打到机械限位。
  • 第二层:控制模式切换,手动开关控制策略是否生效,关闭时机器人进入“人工遥控”模式。
  • 第一层:物理急停,控制板断电或者舵机驱动板断电。

我建议在部署代码里实现一个简单的状态机,包括IDLESTANDWALKERROR四种状态。初始状态是IDLE,此时策略推理不生效,舵机不输出力矩。手动切换后进入STAND,让机器人尝试站立,确认姿态稳定后再切换WALK。任何异常(比如 IMU 数据超出合理范围、推理结果异常)都直接进入ERROR,关闭所有输出。

我刚开始实际调试时,省掉了这个状态机,直接上电就运行策略,结果机器人原地弹跳了几秒钟,舵机过流保护。从那以后,状态机就成了我所有足式机器人部署的标配。

6. 实测经验与问题排查速查

6.1 推理延迟高怎么办

如果你发现 RK3566 上单次推理超过 15ms,按下面的顺序排查:

第一,确认模型是否真的在跑 NPU。rknn-toolkit2 生成的模型默认走 NPU,但如果你在rknn_init时指定了错误的后端,或者模型里存在某些算子导致图切割失败,部分算子会走 CPU 回退。一条快速判断方法是看板子的 CPU 占用率,推理期间 CPU 占用率飙升,说明模型在图切割时产生了大量 CPU 算子。

第二,检查输入数据拷贝次数。每次推理前如果rknn_inputs_set里的 buffer 是新分配的,会引入不小的开销。建议在主循环外部一次性分配,循环内只做 memcpy。

第三,确认没有开调试日志。RK3566 的 kernel 日志和 rknn 运行日志在调试时很有用,但生产运行时会把推理时间拉长。我把日志级别调到 ERROR 之后,推理时间肉眼可见地缩短了几毫秒。

第四,检查 CPU 调频策略。RK3566 默认可能有省电模式,把 CPU 频率调到 performance 模式,不仅能提升推理速度,还能减少调度抖动:

echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

6.2 部署后机器人姿态不对、行为异常

这类问题 90% 出在观测向量上。按顺序检查:

  • 关节顺序是否和训练一致。训练时策略接收的 12 个关节位置是按照 URDF 的 joint 顺序排列的,部署端如果拼接顺序不一致,策略看到的就是“乱序”的观测。
  • 关节正负方向是否一致。负号这个坑我已经说过了,务必逐关节校验。
  • 归一化参数是否用全了。遗漏任何一个维度的 mean/std 都会导致观测偏移。
  • IMU 数据和仿真里的定义是否一致。坐标系方向、角速度符号、姿态角范围都要逐一验证。
  • 动作缩放是否反向。训练时的 action scale 和 offset 在部署端要还原,不然机器人动作幅度会整体偏小或偏大。

6.3 量化后策略退化严重

如果 INT8 量化导致策略在真机上表现明显变差,先不要急着放弃 INT8,按下面的步骤排查:

  • 检查量化数据集是否覆盖了实际运行的观测范围。我的经验是量化数据集至少 1000 帧,而且要包含异常姿态、剧烈动作的样本。
  • 用仿真数据对比量化前后的输出差异,找出输出误差最大的那几层,设为 FP16。
  • 如果误差仍然明显,可以尝试quantized_dtype='w8a16'混合精度配置,权重用 INT8、激活用 FP16,精度比全 INT8 好,性能损失不大。

6.4 给正在复现的人的几条建议

最后分享几条我在整个项目过程中沉淀下来的经验,都是踩过坑才记住的。

首先,搭建新环境时,先把官方 demo 跑通,再做自己的模型。rknn 工具链的报错不够直观,如果环境下有问题,你会在模型转换阶段浪费大量时间。

其次,真机上调试时,宁慢勿快、宁稳勿炫。先用极低的速度测试行走,确认站立和平衡稳定后再提高速度。我见过很多人在第一步就性能拉满,结果是舵机疯狂抖动,问题分析无从下手。

第三,日志记录要做到“不干扰控制”,建议把控制日志写到内存 buffer,控制周期结束之后再周期性地异步刷盘,而不是每次控制周期都直接写文件。

另外一个我自己很受用的小技巧:在部署代码里留一个远程参数接口。这样调速、调滤波参数、调限幅参数都不用重新编译程序,通过串口或者 UDP 在上位机直接改参数,能省掉大量反复刷机的调试时间。

做完整条链路之后,我最大的感受是:真正让 RL 策略在真机上跑起来,困难点不在算法,也不在推理框架,而是那些“仿真里没有、真机才存在”的细节——关节方向、IMU 安装、控制周期抖动、量化数值误差。这些细节一个不注意,前面几小时的训练、几个小时的模型转换就全部白费了。把这些链路细节记录下来,正是我写这篇手记的初衷。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询