RKNN 模型转换详解:从 PyTorch 到 RKNN 全流程
RK3588 上跑模型,必须转成 RKNN 格式才能用 NPU 加速。这篇讲完整的转换流程:PyTorch → ONNX → 简化 → RKNN,以及量化配置、常见算子问题、转换失败排查。
大家好,我是黒漂技术佬。
RK3588 的 NPU 算力很强,但不是随便拿个模型就能跑的——必须转换成瑞芯微自家的 RKNN 格式。转换过程看着简单,实际上坑不少:算子不支持、量化精度掉、转换报错、推理结果不对……
这篇把 RKNN 模型转换的完整流程讲清楚,从 PyTorch 训练好的模型到能在 RK3588 上跑的 RKNN 文件,一步一步来。
一、整体流程
PyTorch 模型 (.pt) ↓ 导出 ONNX 模型 (.onnx) ↓ 简化(可选但推荐) 简化后的 ONNX ↓ RKNN Toolkit 转换 + 量化 RKNN 模型 (.rknn) ↓ 推送到设备 RK3588 NPU 推理三步:导出 ONNX → ONNX 优化 → 转 RKNN + 量化。
二、第一步:PyTorch 导出 ONNX
基本导出代码
importtorchfromultralyticsimportYOLO# 加载模型model=YOLO('yolov8n.pt')model.eval()# 导出 ONNXmodel.export(format='onnx',imgsz=640,opset=12,simplify=True)Ultralytics 自带导出,很方便。
手动导出(自定义模型)
importtorchimporttorch.onnx model=YourModel()model.load_state_dict(torch.load('model.pth'))model.eval()# 虚拟输入dummy_input=torch.randn(1,3,640,640)# 导出torch.onnx.export(model,dummy_input,'model.onnx',opset_version=12,# RKNN 推荐 11/12input_names=['images'],output_names=['output'],dynamic_axes=None# RKNN 不支持动态 shape,固定尺寸)导出注意事项
- opset 版本:RKNN 推荐 11 或 12,太高的版本有些算子不支持
- 固定尺寸:RKNN 不支持动态 shape,输入尺寸必须固定
- 训练模式关掉:一定要 model.eval(),不然 dropout、BN 这些推理模式不对
- 不要有控制流:if/for 这些动态逻辑 ONNX 导出可能有问题
验证 ONNX 正确性
导出完先在 PC 上验证 ONNX 的输出和 PyTorch 一致:
importonnxruntimeasortimportnumpyasnp# PyTorch 输出torch_out=model(dummy_input).detach().numpy()# ONNX 输出session=ort.InferenceSession('model.onnx')onnx_out=session.run(None,{'images':dummy_input.numpy()})[0]# 对比误差print(np.max(np.abs(torch_out-onnx_out)))# 误差应该很小(1e-5 级别)误差大说明导出有问题,先解决再往下走。
三、第二步:ONNX 简化(onnx-simplifier)
导出的 ONNX 里有很多冗余算子(identity、常量折叠等),简化一下:
安装
pipinstallonnx-simplifier使用
onnxsim model.onnx model_simplified.onnx或者 Python 代码:
fromonnxsimimportsimplifyimportonnx model=onnx.load('model.onnx')model_simp,check=simplify(model)assertcheck,"简化失败"onnx.save(model_simp,'model_simplified.onnx')简化的好处
- 去掉冗余算子,模型更小
- 减少 RKNN 不支持的算子(比如一些奇怪的 reshape 组合)
- 量化更准确,计算更快
强烈建议每次都做,没什么坏处。
四、第三步:转 RKNN + 量化
用 RKNN Toolkit2 来转换。
环境准备
RKNN Toolkit2 只能在 x86 Ubuntu 上跑(或者 WSL),Windows 原生不支持。
pipinstallrknn-toolkit2完整转换代码
fromrknn.apiimportRKNN# 1. 创建 RKNN 对象rknn=RKNN(verbose=True)# 2. 配置rknn.config(mean_values=[[0,0,0]],# 归一化均值std_values=[[255,255,255]],# 归一化标准差target_platform='rk3588',# 目标平台quant_img_RGB2BGR=False,# 输入是否 BGRoptimization_level=3,# 优化等级 0-3single_core_mode=False,# 单核心模式(默认三核))# 3. 加载 ONNXrknn.load_onnx(model='model_simplified.onnx')# 4. 构建(编译 + 量化)ret=rknn.build(do_quantization=True,# 是否量化dataset='dataset.txt',# 校准图片列表rknn_batch_size=1,# batch size)ifret!=0:print('构建失败')exit(ret)# 5. 保存 RKNN 模型rknn.export_rknn('model.rknn')# 6. 释放rknn.release()dataset.txt 格式
校准数据集的图片路径列表,每行一个:
data/calib/0001.jpg data/calib/0002.jpg data/calib/0003.jpg ...一般 200-1000 张就够了,要有代表性,覆盖各种场景。
五、config 关键参数详解
mean_values / std_values
预处理的归一化参数。RKNN 会把归一化融合到模型里,推理时不用手动做。
YOLO 的归一化是除以 255,所以:
- mean = [0, 0, 0]
- std = [255, 255, 255]
如果你的预处理是(img - mean) / std,对应填进去就行。
target_platform
目标芯片型号:
rk3588:RK3588rk3568:RK3568rk3566:RK3566
optimization_level
优化等级 0-3,越高优化越激进:
- 0:不优化
- 3:最高优化(默认)
一般用 3 就行。
quantized_dtype
量化精度:
asymmetric_quantized-8:非对称 INT8(默认,推荐)dynamic_fixed_point-8:对称 INT8w8a16:权重 INT8,激活 FP16(精度高,速度慢一点)
do_quantization
- True:INT8 量化(推荐,速度快)
- False:FP16,不量化(精度高,速度慢)
先试量化版,精度不够再考虑 FP16。
六、常见算子支持问题
RKNN 不是所有 ONNX 算子都支持,遇到不支持的算子会回退到 CPU 跑,巨慢。
支持良好的算子
- Conv、ConvTranspose
- BatchNormalization(会被融合)
- ReLU、ReLU6、LeakyReLU、SiLU
- MaxPool、AvgPool
- Add、Mul、Concat
- Resize(最近邻、双线性)
- Sigmoid、Tanh
- Reshape、Transpose、Flatten
可能有问题的算子
- 复杂的 Upsample 模式
- Group Normalization
- 各种花式注意力(SE、CBAM 等)
- 大 kernel 的 Depthwise Conv
- 动态 shape 相关的算子
遇到不支持的算子怎么办?
- 替换算子:把不支持的换成支持的等价实现
- 后处理移到 CPU:检测头的 NMS 这些放 CPU 做,别塞进模型里
- 自定义算子:RKNN 支持自定义算子,开发成本高
- 换模型结构:选 NPU 友好的结构
💡 经验:YOLOv5/v8 的标准结构 RKNN 支持都很好,基本不用改。自己设计的模型要注意尽量用标准算子。
七、转换后精度验证
转完一定要验精度,别直接拿到设备上用。
方法 1:RKNN Toolkit 自带精度分析
# 量化误差分析rknn.accuracy_analysis(inputs=['test_image.jpg'],output_dir='./analysis',target='rk3588')会输出每层的量化误差,找出误差大的层。
方法 2:PC 端模拟推理对比
RKNN Toolkit 可以在 PC 上模拟 NPU 推理,对比 ONNX 和 RKNN 的输出差异:
# ONNX 输出(FP32 基准)importonnxruntime session=onnxruntime.InferenceSession('model.onnx')onnx_out=session.run(None,{'images':input_data})[0]# RKNN 模拟输出(模拟 INT8 结果)rknn.load_rknn('model.rknn')rknn.init_runtime(target=None)# None 就是 PC 模拟rknn_out=rknn.inference(inputs=[input_data])[0]# 对比importnumpyasnp diff=np.abs(onnx_out-rknn_out).mean()print(f"平均误差:{diff}")误差在可接受范围内再上板。
方法 3:上板实测 mAP
最终还是要在真实设备上跑测试集,算 mAP,和 PC 端对比。
掉点 1-2 个点正常,掉太多就要调量化了。
八、转换常见的坑
坑 1:输出结果完全不对
原因通常是预处理没对齐:
- mean/std 设错了
- RGB/BGR 搞反了
- 图像缩放方式不一样(letterbox 还是直接 resize)
解决:逐个对比预处理步骤,确保和训练时完全一致。
坑 2:转换成功但推理特别慢
原因:有算子不支持,回退到 CPU 跑了。
解决:看转换日志,搜fallback、CPU关键字,找到不支持的算子替换掉。
坑 3:量化后精度掉很多
- 校准数据不具代表性 → 换校准集
- 校准方法不合适 → 调量化参数
- 某些层误差大 → 那层指定不量化
- 模型本身不适合量化 → 考虑 FP16 或 QAT
坑 4:YOLO 检测头输出乱
原因:YOLO 的后处理(解码、NMS)不能直接转,要拿原始输出自己在 CPU 做后处理。
解决:导出时不要把 NMS 塞进模型,拿 raw output,推理完自己写后处理。
坑 5:动态 shape 报错
RKNN 不支持动态输入尺寸。导出 ONNX 时固定好尺寸,不要 dynamic_axes。
九、RKNN 文件推送到设备
转好的 .rknn 文件拷到 RK3588 开发板上,用 RKNN Runtime 推理。
C++ 推理代码大致结构
#include<rknn_api.h>// 1. 初始化rknn_context ctx;rknn_init(&ctx,model_data,model_size,0,NULL);// 2. 设置输入rknn_input inputs[1];inputs[0].index=0;inputs[0].buf=image_data;inputs[0].size=size;rknn_inputs_set(ctx,1,inputs);// 3. 推理rknn_run(ctx,NULL);// 4. 拿输出rknn_output outputs[3];rknn_outputs_get(ctx,3,outputs,NULL);// 5. 后处理(NMS 等)// ...// 6. 释放rknn_outputs_release(ctx,3,outputs);rknn_destroy(ctx);Python 也有对应的 rknnlite 库,用法类似。
十、本篇小结
- 转换三步:导出 ONNX → 简化 → 转 RKNN + 量化
- ONNX 导出注意:opset 12、固定尺寸、eval 模式
- onnx-simplifier 必做,减少冗余算子
- RKNN 配置:mean/std、目标平台、量化开关
- 校准集 200-1000 张,要有代表性
- 转换后验证精度:先 PC 模拟对比,再上板测 mAP
- 常见问题:算子不支持回退 CPU、预处理不对齐、量化掉点
下一篇讲RKNN 量化实战调优:量化精度不够怎么办,怎么分析误差、怎么调参数。
我是黒漂技术佬。