纸翼 · 加载中
1878 words
9 minutes
RK3588 端侧 AI 部署(三):ONNX 转 RKNN 与推理验证
所属合集 RK3588 端侧 AI 部署 第 3 / 6 篇
  1. 1 RK3588 端侧 AI 部署(一):平台、NPU 与 RKNN 工具链
  2. 2 RK3588 端侧 AI 部署(二):主机与板端环境搭建
  3. 3 RK3588 端侧 AI 部署(三):ONNX 转 RKNN 与推理验证 正在阅读
  4. 4 RK3588 端侧 AI 部署(四):量化、评估与模型优化
  5. 5 RK3588 端侧 AI 部署(五):Lite2、C API 与零拷贝工程化
  6. 6 RK3588 端侧 AI 部署(六):MobileNet、YOLO 与 RKLLM 实战

RK3588 端侧 AI 部署(三):ONNX 转 RKNN 与推理验证#

这一篇处理端侧部署中最关键、也最容易被低估的环节:把训练模型可靠地变成 RKNN,并证明转换前后做的是同一件事。

上一篇:主机与板端环境搭建

一、不要把“文件生成成功”当成转换成功#

正确的验证链路应该是:

PyTorch/TensorFlow 原模型
→ ONNX
→ ONNX Runtime 输出正确
→ RKNN FP16/非量化模型输出正确
→ RKNN INT8 模型精度可接受
→ RK3588 真机输出正确

每一步都要使用相同输入保存中间输出。这样如果最终结果错误,可以准确判断是 ONNX 导出、RKNN 图优化、量化、Runtime,还是 C++ 前后处理出了问题。

二、先定义“模型输入输出契约”#

在写转换脚本前,把以下信息整理成表:

项目示例必须从哪里确认
输入名称imagesONNX 图、导出脚本
输入 shape[1,3,640,640]训练/导出配置
内存布局NCHW 或 NHWC模型和 Runtime 查询结果
数据类型FP32、UINT8、INT8模型输入和 RKNN 配置
通道顺序RGB 或 BGR训练预处理
resize 方法直接缩放或 letterbox训练/验证代码
像素范围0~2550~1训练预处理
mean/std每通道数值训练预处理
输出名称/数量分类一个、检测多路ONNX 图和后处理
输出语义logits、框回归、类别分数模型实现

这张表就是 Python、RKNN 和 C++ 之间的接口文档。后续任何一层都不能凭经验猜。

三、导出和检查 ONNX#

不同训练框架的导出方法不同,但导出后至少要完成三类检查。

1. ONNX 合法性和 shape 推导#

Terminal window
pip install onnx onnxruntime
import onnx
from onnx import shape_inference
model_path = "model.onnx"
model = onnx.load(model_path)
# 检查图结构是否合法
onnx.checker.check_model(model, full_check=True)
# 补全可以静态推导的中间张量 shape
inferred = shape_inference.infer_shapes(model, strict_mode=True)
onnx.save(inferred, "model_inferred.onnx")
print("ONNX check passed")

官方说明可参考 onnx.checkershape inference。动态 Reshape 等结构可能无法完整推导 shape,这不一定代表模型错误。

2. 用 Netron 看图#

Terminal window
pip install netron
netron model.onnx

重点检查:

  • 输入输出名称、数量、shape 和 dtype;
  • 是否把 NMS、解码等不适合量化的后处理留在图中;
  • 是否出现大量 TransposeReshape
  • 动态维是否符合预期;
  • 检测模型多尺度输出的顺序。

Netron 是结构查看器,不是精度验证器。图能打开不代表推理结果正确。

3. 与原框架比较输出#

同一输入经过完全相同预处理后,分别运行原模型和 ONNX Runtime,比较:

  • 输出数量和 shape;
  • 最大绝对误差、平均绝对误差;
  • 余弦相似度;
  • 最终任务结果,例如 Top-1、检测框或 mAP。

不要只比较最终类别。两个模型可能恰好得到同一 Top-1,但中间数值已经明显偏移。

四、理解 mean、std 和像素范围#

RKNN 配置常见:

rknn.config(
mean_values=[[123.675, 116.28, 103.53]],
std_values=[[58.395, 58.395, 58.395]],
target_platform="rk3588",
)

其核心语义是复现:

x' = (x - mean) / std

但参数是否正确还取决于输入是 0~255 还是 0~1。例如训练代码常见:

transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225],
)

如果 RKNN 输入直接使用 0~255 的 UINT8 图片,对应参数常写成:

mean = [0.485, 0.456, 0.406] × 255
std = [0.229, 0.224, 0.225] × 255

还要确认顺序是 RGB,而 OpenCV imread() 默认返回 BGR。

最常见的重复预处理错误#

如果 mean_values/std_values 已固化进 RKNN 模型,C++ 又手动执行同一遍归一化,输入会被处理两次。相反,如果转换时没有配置,应用也没处理,模型收到的数值范围也会错误。

最稳妥的方法是把预处理拆成明确步骤并逐项记录:

读取 BGR
→ BGR 转 RGB
→ resize/letterbox
→ 是否除以 255
→ 是否减 mean/除 std
→ HWC/NHWC/NCHW
→ dtype

五、编写可复现的 RKNN 转换脚本#

下面是一个适合继续扩展的 ONNX 转 RKNN 骨架:

from pathlib import Path
from rknn.api import RKNN
ONNX_MODEL = Path("model.onnx")
RKNN_MODEL = Path("model-rk3588-int8.rknn")
DATASET = Path("dataset.txt")
rknn = RKNN(verbose=True, verbose_file="convert.log")
try:
ret = rknn.config(
target_platform="rk3588",
mean_values=[[123.675, 116.28, 103.53]],
std_values=[[58.395, 58.395, 58.395]],
)
if ret != 0:
raise RuntimeError(f"config failed: {ret}")
ret = rknn.load_onnx(model=str(ONNX_MODEL))
if ret != 0:
raise RuntimeError(f"load_onnx failed: {ret}")
ret = rknn.build(
do_quantization=True,
dataset=str(DATASET),
)
if ret != 0:
raise RuntimeError(f"build failed: {ret}")
ret = rknn.export_rknn(str(RKNN_MODEL))
if ret != 0:
raise RuntimeError(f"export_rknn failed: {ret}")
print(f"saved: {RKNN_MODEL}")
finally:
rknn.release()

config() 的参数、支持的量化方法和 API 返回值会随版本变化。使用前应打开当前 release 的 Toolkit2 API 文档,并以本机接口为准。

量化校准集 dataset.txt#

文本中每行通常写一个校准输入路径:

./calibration/0001.jpg
./calibration/0002.jpg
./calibration/0003.jpg

校准集不是训练集随便抽几张,而应覆盖真实部署中的:

  • 光照、背景、距离和目标大小;
  • 不同摄像头或图像来源;
  • 正常、边界和困难样本;
  • 各类别和常见负样本。

样本数量要结合模型和量化算法测试。代表性通常比机械追求数量更重要。

六、命令行转换工具 rknn_convert#

标准模型可使用 YAML 配置配合命令行:

Terminal window
python -m rknn.api.rknn_convert -h
python -m rknn.api.rknn_convert \
-t rk3588 \
-i ./model_config.yml \
-o ./output

它内部仍执行 config → load → build → export。批量模型和统一流水线适合 YAML;需要自定义检查、自动对比输出时,Python 脚本更灵活。不同版本参数可能变化,所以先执行 -h,不要盲抄旧命令。

七、模拟器和真实 RK3588 推理#

1. PC 模拟器#

from rknn.api import RKNN
rknn = RKNN(verbose=True)
rknn.load_rknn("model-rk3588-int8.rknn")
rknn.init_runtime()
outputs = rknn.inference(inputs=[input_tensor], data_format=["nhwc"])
rknn.release()

模拟器适合快速检查输入、输出和后处理,但不代表真实 NPU 的性能,也不能替代板端精度验证。

2. Toolkit2 连板调用 NPU#

rknn.init_runtime(
target="rk3588",
# device_id="...", # 多设备时指定
)
outputs = rknn.inference(inputs=[input_tensor], data_format=["nhwc"])

需要 ADB/rknn_server、Runtime 和驱动正常。日志中要核对:目标平台、Toolkit、Runtime 和驱动版本。

3. 板端 Lite2#

from rknnlite.api import RKNNLite
runtime = RKNNLite(verbose=True)
runtime.load_rknn("model-rk3588-int8.rknn")
runtime.init_runtime(core_mask=RKNNLite.NPU_CORE_AUTO)
outputs = runtime.inference(inputs=[input_tensor], data_format=["nhwc"])
runtime.release()

Lite2 适合把 Python 验证脚本直接搬到板端。最终高性能项目通常仍会进入 C/C++。

八、怎样比较不同阶段的输出#

import numpy as np
def cosine_similarity(a, b):
a = np.asarray(a, dtype=np.float64).reshape(-1)
b = np.asarray(b, dtype=np.float64).reshape(-1)
denom = np.linalg.norm(a) * np.linalg.norm(b)
return float(np.dot(a, b) / denom) if denom else 0.0
for i, (golden, test) in enumerate(zip(golden_outputs, rknn_outputs)):
print(
i,
"shape:", np.shape(test),
"max_abs:", np.max(np.abs(golden - test)),
"mean_abs:", np.mean(np.abs(golden - test)),
"cosine:", cosine_similarity(golden, test),
)

余弦相似度只反映张量方向是否接近,不等同于 Accuracy/mAP。最终仍要跑完整验证集和真实业务数据。

九、转换日志应该看什么#

保留 convert.log,重点关注:

  • 不支持或回退 CPU 的算子;
  • 输入 dtype/layout 的自动转换;
  • 算子融合、常量折叠和图优化;
  • 量化异常、离群值和精度警告;
  • 输出节点顺序变化;
  • 目标平台与模型版本。

遇到错误时按以下顺序排查:

ONNX 是否合法
→ 输入输出契约是否正确
→ 算子是否被当前 Toolkit 支持
→ 预处理是否重复/遗漏
→ 校准集是否有效
→ Toolkit/Runtime/驱动是否匹配
→ C++ 后处理是否假设了错误的输出顺序

十、本篇实践验收#

  • 能写出模型输入输出契约;
  • ONNX 通过 checker,并能用 Netron 和 ONNX Runtime 验证;
  • 能转换 FP16/非量化和 INT8 RKNN;
  • 能在模拟器和 RK3588 真机使用同一输入对比输出;
  • 保存了脚本、模型哈希、转换日志、量化集清单和版本信息。

下一篇:RK3588 端侧 AI 部署(四):量化、评估与模型优化

RK3588 端侧 AI 部署(三):ONNX 转 RKNN 与推理验证
https://blog.huangzy.xyz/posts/rk3588-端侧-ai-部署三/
Author
纸翼
Published at
2026-07-21
License
CC BY-NC-SA 4.0

Some information may be outdated