RK3588部署YOLOv5s:RKNN模型转换与INT8量化实战指南
发布时间:2026/10/1 7:19:19来源:尧图网络
这次轮到RK3588上跑YOLOv5s系列最关键的环节了把PyTorch训练好的权重变成RK3588 NPU能“听懂”的RKNN格式再做INT8量化。上一篇我们把模型训好、导出成ONNX很多朋友走到这一步就卡住了——明明在PC上推理一切正常rknn-toolkit2一转就报错或者转完精度掉得一塌糊涂甚至直接满屏乱框。这篇我把自己的完整流程、踩过的坑、参数调优经验全写出来照着做能把从pt到rknn这条路走通省掉大部分查文档的时间。这篇适合手上已经有YOLOv5s模型、想在RK3588上跑实时推理的开发者也适合刚接触RKNN工具链、被各种版本和报错劝退的新手。你不需要懂太多底层原理但需要一点Python基础和基本的命令行操作。我会从环境搭建开始讲到ONNX导出、RKNN转换、INT8量化校准、精度验证、板端推理性能调优最后附上我遇到的高频问题和排查思路。我用的环境是PC端Ubuntu 20.04 rknn-toolkit2 1.6.0板端是RK3588 Ubuntu 22.04RKNPU2 runtime。配置不同的话版本号会有点差异但整体思路完全通用。1. 环境准备与工具链选型1.1 RK3588 为什么要用 rknn-toolkit2先说清楚一个很多人容易混淆的点rknn-toolkit不带2是给RK3399、RK3566、RK3568这些老平台用的RK3588必须用rknn-toolkit2。两者的API不完全兼容网上搜到的很多教程是rknn-toolkit 1.x的写法直接搬到3588上大概率报错。rknn-toolkit2的版本更新比较快我建议直接选1.6.0以上的版本。早期版本对YOLOv5的支持不够好尤其是Focus层、SPPF这些结构容易在转换时报“Unsupported op”之类的错误。1.6.0对应的是RKNPU2的runtime版本2.2.0这个组合我测下来最稳。还有一点要特别注意rknn-toolkit2分两个用途一个是PC端做模型转换和量化使用的是完整版的Python包另一个是板端推理时使用的runtime库。千万别把PC端的工具包装到板子上板端只需要runtime的so库和轻量级的Python接口后面我详细说。1.2 PC端Python环境配置实操我用的是conda创建的独立环境这样做的好处是隔离依赖不会把系统Python搞乱。rknn-toolkit2对Python版本有明确要求1.6.0支持Python 3.8和3.10我用的3.8。创建环境并安装依赖conda create -n rknn python3.8 conda activate rknn pip install rknn_toolkit2-1.6.0-cp38-cp38-linux_x86_64.whl这个whl文件从瑞芯微官方的rknn-toolkit2仓库Release页面下载。装完之后验证一下from rknn.api import RKNN print(RKNN.__version__)能正常导入就OK了。如果报缺少依赖按提示补装numpy、onnx、opencv-python等包。我建议在同一个环境里也装上onnxruntime和opencv因为后面验证ONNX输出、准备量化数据集都要用。提示不要在Windows上用WSL跑rknn-toolkit2我试过坑很多GPU透传、USB连接都容易出问题。老老实实用原生Linux或者Windows下用虚拟机装Ubuntu也行。2. RKNN 转换全流程实操2.1 从 YOLOv5s 导出 ONNX 的关键细节YOLOv5官方仓库提供了export.py用起来很简单但有几个参数跟RKNN转换强相关要特别注意。导出命令python export.py --weights yolov5s.pt --include onnx --opset 12 --batch-size 1为什么opset要用12RKNN工具链对ONNX算子支持最完善的是opset 10到12高版本的opset可能会引入一些RKNN还没适配的算子比如一些新的slice写法导致转换失败。我用opset 13试过build阶段偶发报错改回12就没事。batch-size固定为1。很多人会问动态batch怎么办RKNN支持通过input_size_list设置多档输入但推理时batch1已经够用动态shape会显著增加转换复杂度和推理延迟没必要。还有一个至关重要的点导出时不要带NMS。YOLOv5的export.py默认会把NMS以自定义节点形式写进ONNX但RKNN的NPU不执行NMS这个操作必须放在CPU端做后处理。带NMS导出的模型转成RKNN后要么报不支持的算子要么输出tensor跟你预期完全对不上。验证ONNX输出是否正常用onnxruntime跑一下import onnxruntime as ort import cv2 import numpy as np sess ort.InferenceSession(yolov5s.onnx) input_name sess.get_inputs()[0].name img cv2.imread(test.jpg) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img cv2.resize(img, (640, 640)) img img.astype(np.float32) / 255.0 img np.transpose(img, (2, 0, 1))[None] outputs sess.run(None, {input_name: img}) for i, out in enumerate(outputs): print(i, out.shape)正常的话应该输出3个shape为(1, 255, 80, 80)、(1, 255, 40, 40)、(1, 255, 20, 20)的tensor分别对应三个尺度的检测头。如果shape不对或者只有一个输出先检查导出步骤。2.2 转换脚本与配置参数解析这是全篇最核心的部分。下面是我一直在用的转换脚本import os import cv2 import numpy as np from rknn.api import RKNN rknn RKNN() # 配置 rknn.config( mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588, optimization_level3 ) # 加载ONNX模型 ret rknn.load_onnx(modelyolov5s.onnx) if ret ! 0: print(模型加载失败) exit(-1) # 先不量化构建模型用于验证转换正确性 ret rknn.build(do_quantizationFalse) if ret ! 0: print(构建模型失败) exit(-1) # 导出RKNN模型 rknn.export_rknn(yolov5s_fp16.rknn) # 用模拟器验证输出 rknn.release()mean_values和std_values这两个参数跟训练时预处理完全一致。YOLOv5源码里对输入做了除以255的归一化所以mean0、std255。如果你训练前用了其他预处理比如自定义归一化、ImageNet的mean/std必须同步改这里不一致会导致算法精度崩盘后面展开讲。target_platform必须写小写字母写成RK3588或rk3588_linux都会在build阶段报错。optimization_level我用的3表示最高优化。但这里要留个心眼某些算子在高优化等级下会被融合或改写如果你的模型转换后输出异常先降到2或者1试试。先不量化构建一次模型很重要。第一次build做的是模型结构转换和算子映射如果ONNX里有不支持的算子这一步就报错了。这时候排查问题比混着量化报错容易得多。确认FP16模型输出正常后再拿同一份配置去做量化不要一上来就do_quantizationTrue。FP16版本的rknn模型留个备份后面做INT8精度对比时有大用。2.3 转换后验证用模拟器跑一遍rknn-toolkit2提供了PC端的模拟器可以把整个模型在x86上跑一遍推理方便在板子上板之前验证转换结果。代码框架如下from rknn.api import RKNN import cv2 import numpy as np rknn RKNN() rknn.load_rknn(yolov5s_fp16.rknn) # 初始化运行环境模拟器模式 ret rknn.init_runtime(targetNone) if ret ! 0: print(模拟器初始化失败) exit(-1) img cv2.imread(test.jpg) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img cv2.resize(img, (640, 640)) img np.expand_dims(img, 0).astype(np.float32) outputs rknn.inference(inputs[img], data_formatnhwc) print([out.shape for out in outputs])这一步跑出来的结果跟onnxruntime的输出做对比。形状应该一致数值是float类型FP16模拟精度会有微小偏差正常。这里输出的三个tensor只是裸的检测头输出还没做解码和NMS你不能直接拿来画框。我习惯在这个阶段把输出tensor和onnxruntime的输出做数值对比计算cosine相似度。相似度在0.99以上基本说明转换没问题如果某个尺度输出相似度偏低说明那个分支有算子被异常优化得回去调optimization_level。3. INT8 量化原理与实战3.1 RK3588 NPU 的算力分配为什么不直接跑 FP16RK3588内置的NPU标称算力是6 TOPS那是INT8算力FP16只有一半INT16又砍半。也就是说如果模型用FP16精度跑实际只能发挥NPU大概一半的算力。既然做边缘端实时检测性能就是核心指标没理由跟INT8过不去。打个比方FP32就像开卡车搬货每趟能拉得稳但跑不快FP16像普通货车载重腰斩但快了一点INT8则是小跑车载重更小但转运频率极高整体吞吐反而最大。这里的“载重”就是数据表示的精度范围“转运频率”就是NPU的并行计算吞吐。还有一个被忽视的点INT8量化后模型体积变成原来的四分之一内存带宽占用也大幅降低。RK3588的内存带宽是有限的在640x640输入下跑YOLOv5s带宽往往比算力更早成为瓶颈。我在实测中对比过同样跑YOLOv5sFP16版本内存占用大约370MBINT8版本只有105MB左右。这个差距在长时间运行、多路视频输入时非常致命。3.2 量化数据集怎么选很多人第一步就错了rknn-toolkit2的量化校准需要你提供一个图片列表工具会统计这些图片在模型各层激活值的分布然后计算INT8的scale和zero_point。数据选得好不好直接决定量化后精度损失多少。我需要强调一个核心原则量化数据集要能代表推理时遇到的数据分布不是随便找几张图凑数。我的做法数量最少500张推荐800到1000张。网络上一些教程说一两百张就够我实践下来对小模型尤其不保险尤其是背景复杂的目标量化后漏检率升高。来源从验证集里均匀抽。如果验证集本身跟训练集同分布效果最好。如果是新场景建议从真实应用场景拍视频抽帧覆盖不同光照、不同距离、不同目标密度。内容多样性必须包含少量没有目标物的背景图防止模型在纯背景上产生大量误检框。单张图里目标数量也最好有差异不要全是密集场景。分辨率量化图片尺寸统一做letterbox到模型的输入尺寸640x640不要直接resize拉伸否则物体形状变形校准出的分布也偏了。这里的关键一点校准数据别做增强。有些同学直接拿训练集数据增强后的结果去做量化加了马赛克、翻转、色彩抖动这些分布都是“扭曲”的会干扰量化统计。我踩过这个坑量化后精度掉到接近随机换成原始验证集重做之后一切正常。量化数据集的预处理代码先转RGB再resize保持通道顺序与模型输入一致def load_quant_data(img_path, target_size640): img cv2.imread(img_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img cv2.resize(img, (target_size, target_size)) return img3.3 量化原理简析用最通俗的方式说清 scale 和 zero_pointINT8量化本质上是把一个浮点范围映射到一个8位整数范围。以最常用的均匀对称量化为例浮点区间[-a, a]映射到INT8区间[-128, 127]映射关系是线性的a除127得到scale浮点0对应整数0zero_point就是0。对于非对称量化浮点区间[min, max]映射到[0, 255]这时候除了scale还有一个非零的zero_point用来表示浮点0对应的整数。关键问题是a或者[min, max]到底怎么定不同的选择策略影响很大。如果选max即取激活值的最大值作为阈值这对分布均匀的数据效果好但对有长尾分布的数据很容易被几个极端值带偏导致中间大量数值被压缩到同一个int8区间信息严重丢失。kl散度算法是模仿TensorRT的思路不断收紧阈值范围直到截断后的分布与原始分布的KL距离最小。这个过程能自动丢弃一些极端长尾值对YOLO这种深而宽的网络更友好。rknn-toolkit2在config阶段可以设置quantized_dtype和quantized_algorithm我试过normal和kl两种kl设置下量化后mAP普遍比normal高2到3个百分点代价是校准时间变长但对离线转换无所谓。3.4 量化后的精度评估与混合量化调优量化的完整流程代码from rknn.api import RKNN import cv2 import numpy as np import os # 准备量化数据集文件内容是图片路径每行一个 quant_data [] data_dir quant_dataset for f in os.listdir(data_dir): if f.endswith(.jpg): img load_quant_data(os.path.join(data_dir, f)) quant_data.append(img) # 检查数据避免空列表 assert len(quant_data) 100, 量化数据太少 rknn RKNN() rknn.config( mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588, quantized_algorithmkl, quantized_methodchannel, optimization_level3 ) ret rknn.load_onnx(modelyolov5s.onnx) if ret ! 0: exit(-1) ret rknn.build(do_quantizationTrue, datasetdataset.txt) if ret ! 0: exit(-1) rknn.export_rknn(yolov5s_int8.rknn)dataset.txt文件里每一行是图片路径注意这里传给build的不是图片数组而是路径文件rknn内部会自己读图。所以dataset.txt里的路径要能被当前进程访问到我用的是绝对路径省得出错。量化完成之后第一件事不是上板测延迟而是评估精度。我的评估方法有两种一是快速可视化对比取十几张典型图片分别用FP16的rknn模型和INT8的rknn模型跑推理画框对比看看漏检、误检、置信度差距。这个办法直观、快适合快速定位明显问题。二是定量指标在验证集上跑mAP对比。YOLOv5仓库自带val.py但rknn的模型格式不能直接喂给PyTorch的验证脚本需要写一套rknn推理标准mAP计算的代码。工作量不大核心是把输出tensor解码成框。我在验证集5000张图上实测INT8相对FP16的mAP损失控制在0.8%到1.5%之间。如果精度掉太多比如mAP掉超过5%从这几方面排查确认mean/std和训练一致。这是第一嫌疑也是最常见的翻车原因。YOLOv5原生训练是除以255如果误写成ImageNet的mean[0.485,0.456,0.406]、std[0.229,0.224,0.225]量化直接废掉。检查量化数据集的通道顺序。rknn读图默认是BGR还是RGB取决于dataset格式如果训练模型用的是RGB而量化数据用了BGR相当于模型看到的是经过通道交换的图精度崩是必然的。我在脚本里就是先cvtColor转RGB再保存dataset里是转好的图。尝试per-channel量化。如果某一层某些channel数值范围极大per-tensor量化会拉低整体精度per-channel能逐通道调整scale在卷积层效果通常更好。局部量化。rknn支持对某些层跳过量化保留FP16精度。具体做法是在config里设置custom_quantize_layers这个用起来有点绕我看官方文档也不够详细建议作为最后手段。4. 板端部署推理与性能调优4.1 RK3588 上加载 RKNN 模型推理板端部署最简化的流程是先把librknnrt.so放到板子上通常路径是/usr/lib/然后安装rknn-toolkit2里的runtime Python包。板端用的接口跟PC端类似但不需要RKNN类做build直接load并inference。from rknnlite.api import RKNNLite import cv2 import numpy as np rknn RKNNLite() rknn.load_rknn(yolov5s_int8.rknn) rknn.init_runtime() img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) # NPU输入不需要转RGBRKNN内部按config的mean/std处理 # 但如果你config是用RGB标准做的这里传RGB更一致 img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_rgb np.expand_dims(img_rgb, 0).astype(np.uint8) outputs rknn.inference(inputs[img_rgb], data_formatnhwc) print([out.shape for out in outputs])注意板端推理时输入的数据类型可以是uint8PC模拟器通常用float32这也是一个小差异。实际测下来uint8输入比float32输入快一些因为省去了一次类型转换。由于rknnlitet的接口是轻量级的不带调试功能所以模型有问题请先在PC端模拟器排查完再上板否则板端的报错信息会非常有限排查效率极低。4.2 实测性能数据不同精度和分辨率下的推理耗时我在RK3588上实测了YOLOv5s输入640x640结果如下模型版本推理耗时单帧帧率备注FP16约32ms约30 FPS未充分使用INT8算力INT8per-tensor约16ms约60 FPS常规布局INT8per-channel约15ms约65 FPS精度更高速度略快这个数据是单核NPU的情况。RK3588有三核NPU如果按多核调度还有进一步提升空间。实测开启三核后INT8版本能达到80 FPS以上但功耗和发热也会明显上升。如果你的应用对延迟敏感比如机器人抓取建议固定使用单核延迟更可预测如果追求吞吐比如多路视频分析优先把多核开起来。4.3 从推理到检测框后处理优化策略NPU输出三个特征图之后要在CPU上做解码和NMS。这个后处理如果写得粗糙CPU耗时甚至可能超过NPU推理白瞎了INT8的加速。我的做法是在C端实现解码NMSPython调用。核心解码逻辑跟YOLOv5的官方实现一致但做了几点优化提前把anchor值写死成常量不要在推理循环里重新计算。避免使用numpy的高层函数做逐元素遍历直接操作底层数组。两类框过滤先在每个尺度的低置信度上做粗筛再用conf_thres过滤掉明显的背景框大幅减少进入NMS的候选框数量。我这边实测640输入、单帧约30个目标C解码NMS耗时约4msPython实现则要15ms以上。如果还是坚持纯Python建议至少用numpy向量化代替循环。4.4 多核NPU调度和功耗控制rknn.init_runtime方法里可以设置core_mask参数rknn.init_runtime(core_maskRKNNLite.NPU_CORE_0)可选的值有NPU_CORE_0、NPU_CORE_1、NPU_CORE_2、NPU_CORE_AUTO。AUTO模式让驱动自动调度多数时候能均衡利用三核但偶尔会因为核心间同步引入抖动。固定单核的策略最稳实时性更好。性能调优有个容易忽略的点NPU核心频率是动态调频的系统负载一高频率会下降。如果你的推理任务要求长时间稳定帧率最好把NPU频率锁到最高档。可以用cat /sys/kernel/npu_freq这类节点查看不同固件路径有差异。锁频之后功耗增加是必然的一般来说跑YOLOv5s INT8整机功耗能到8到10W散热不行的板子小心降频。5. 常见问题与排查技巧实录5.1 RKNN转换阶段高频报错错误信息原因解决办法E Catch exception when building modelONNX模型里有不支持的算子确认opset12检查是否带NMS导出定位不支持层并替换E TargetPlatform is invalidtarget_platform写错检查是否为小写字母如rk3588E License key is invalid未配置或过期用注册码激活或确认无离线限制版本E shape is not match模型输入尺寸与配置不一致确认导出时固定shape检查input_size_listW The output shape ... is changed优化等级导致的输出变动降低optimization_level做对比5.2 量化后一个匪夷所思的问题输出全为零我遇到过一次很诡异的bugINT8模型在PC端模拟器上推理完全正常但烧到板子上跑三个输出tensor全是0一个框都没有。排查了很久最后发现是板端加载模型时输入数据是BGR而PC端模拟器用的是RGB。虽然rknn.config里配置了mean/std但如果预处理里有个cv2.cvtColor而板端忘了这个步骤模型看到的输入就是反过来的通道排列。注意NPU上的INT8输入通常要求uint8格式通道顺序必须和训练一致。这类问题非常隐蔽因为FP16模型对通道顺序相对不敏感但INT8量化对输入分布很敏感通道一错输出直接崩。所以我把推理代码里的预处理统一封装成函数板端和PC端共用同一份从源头杜绝差异。5.3 精度暴跌排查清单精度暴跌这件事情90%的情况是数据管道的错不是量化本身的错。下面是我总结的排查顺序先用同一组图片比较FP16 rknn和onnxruntime的输出确认转换链路没问题。再比较INT8 rknn和FP16 rknn确认量化环节没问题。检查量化数据集是否来自真实测试分布。检查mean/std、通道顺序、resize方式是否跟训练时完全一致。增加量化图片数量从200张增到800张看精度是否有回升趋势。切换量化算法从normal改为kl。尝试per-channel量化替代per-tensor。最后才考虑混合量化但通常轮到这一步前面已经解决了。5.4 一个容易被忽略的存储问题导出的rknn文件会附带固件版本信息板端的runtime需要跟PC端rknn-toolkit2版本兼容。rknn-toolkit2 1.6.0生成的模型要求板端librknnrt.so版本不低于2.2.0。如果版本不一致init_runtime会报版本错误或者推理结果莫名异常。检查板端runtime版本strings /usr/lib/librknnrt.so | grep version不想在版本问题上纠缠最稳妥的做法是板端刷完固件后用rknn-toolkit2仓库里自带的librknnrt.so替换系统里那个或者安装官方打包的runtime deb包。这一步做完版本问题基本一劳永逸。6. 一个完整的端到端检查清单最后分享我每次部署新模型都会走的流程照着这个顺序做很难踩大坑确认ONNX输出shape和预期一致。先build不量化版本PC端模拟器验证精度。不量化版本上板子验证一遍精度和延迟。准备800张以上的量化数据集内容分布贴近真实场景。构建INT8版本PC端模拟器对比FP16预测框确认没有明显劣化。上板子跑真实场景连续运行1000帧统计漏检率、误检率。用数据验证INT8推理耗时和帧率是否达标。保存一套完整的配置文件量化版本、预处理参数、性能数据方便后续回归。我实际开发中最大的体会是RKNN转换和量化本身步骤不多真正的坑全在数据管道上。同一份模型量化数据集选得好不好、预处理跟训练一不一致效果天壤之别。第一次做的时候别着急上板先在PC端把精度验证这一步做扎实后面能省心很多。如果你卡在某个具体报错上或者模型结构不是标准的YOLOv5而是魔改版欢迎在评论区把报错日志和模型结构发出来我帮你看是哪里的问题。下一篇可能会写RKNN模型的多路视频推理和调度优化如果在看的人数多我就把C后处理的那套代码开源出来。
网站建设高端定制企业官网