尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

RK3588 NPU推理加速实战:RKNN INT8量化调优全指南

RK3588 NPU推理加速实战:RKNN INT8量化调优全指南 刚开始把模型搬到RK3588上跑的时候我心里其实挺爽的。毕竟这颗芯片自带6 TOPS算力的NPU宣传册上写着支持INT8/FP16混合量化怎么看都比纯CPU硬扛要强。可等我把Pytorch模型转成ONNX再直接塞进NPU里一跑结果傻眼了推理时延跟Jetson Nano上跑FP16差不多甚至有些模型还不如纯CPU优化后的表现。问题出在哪我后来才明白不是RK3588的NPU不行是我压根没做量化就让它跑等于开着一辆能跑200码的车在市区里用一挡慢慢挪。这篇文章我就把自己在RK3588上做RKNN模型量化的完整过程写出来。从为什么量化能提速到PC端怎么转出INT8和FP16的RKNN模型再到板端部署和实测性能对比最后把量化过程中踩过的坑全部列出来。目标是让同样被NPU性能折磨的人看完就能直接操作少走弯路。1. 为什么RK3588的NPU需要量化算力闲置的真相1.1 一块闲置的6 TOPS到底意味着什么RK3588的NPU有三颗核心官方标称INT8精度下整颗芯片算力是6 TOPS但很多人忽略了一件事FP16精度下它的理论算力只有INT8的一半左右也就是3 TOPS。这其实很好理解NPU里的乘累加单元对低比特数据的并行度天然更高INT8能用更小的硬件开销塞下更多计算单元所以单看峰值算力INT8是FP16的两倍。但理论算力和实际吞吐量是两回事。很多模型转成RKNN之后如果不指定量化默认用FP16精度存储权重NPU依然在算只是没有发挥出全部潜力。尤其是一些卷积层特别多的模型比如YOLOv8s、ResNet50之类INT8量化后往往能把推理时延直接砍掉一半左右。这个“翻倍”的效果不是玄学是NPU设计时对INT8算力的天然倾斜。还有一点容易被忽视量化不只是提速还能降内存带宽占用。RK3588的NPU要频繁读写DDR模型体积缩小之后权重的搬运量也跟着变小。我实测过一个3MB左右的FP16模型INT8之后体积只有1.5MB左右虽然现在内存动不动几个G但在共享带宽的场景下少搬运一点数据CPU和GPU都能跟着受益。1.2 RKNN量化的底层逻辑INT8和FP16到底改了啥RKNN是Rockchip自家NPU的模型格式和推理框架。流程很简单先把PyTorch/TensorFlow/ONNX模型通过PC端的rknn-toolkit2转成.rknn文件再拷贝到板子上用rknn-toolkit-lite2或者C API加载推理。转换过程中能选择量化策略量化就是改变权重和激活值的“存储宽度”。FP16量化很直白就是把FP32的权重和激活值截断成半精度浮点数。它不改变数值的动态范围只是小数精度降了一半大多数模型几乎无感所以某些容忍度低的模型用FP16最稳。INT8量化就要复杂一些。它需要收集真实数据分布算出每个tensor的缩放因子scale和零点zero point然后把浮点数映射到[-128, 127]或[0, 255]这个范围。这里分两种做法一种是训练后量化PTQ板端部署主流选这个另一种是量化感知训练QAT精度更高但需要重新训练模型嵌入式项目里一般先PTQ实在不行再上QAT。RKNN Toolkit2在做INT8量化时会通过你提供的校准图像数据集去统计分析每一层激活值的范围然后选择合适的scale尽量把信息都保留下来。这也是为什么校准集的质量直接决定量化后的精度后面我会仔细讲。2. 动手前先备好工具链PC端与板端环境搭建2.1 版本匹配是第一大坑RKNN的工具链迭代非常快最大的坑就是版本不匹配。PC端rknn-toolkit2的版本、板端rknn-toolkit-lite2的版本、固件里NPU驱动版本这三者必须对齐。我见过很多人装了最新的rknn-toolkit2转头扔到一个老固件的板子上推理时报错“driver version mismatch”然后又回来翻版本号。建议直接按官方Release页面的版本对照表来别追新。比如我目前用的组合是rknn-toolkit2 1.6.0 rknn-toolkit-lite2 1.6.0 Ubuntu 20.04固件跑得很稳。安装环境时Python版本最好用3.8到3.10之间低版本可能装不上一些依赖高版本可能和onnx版本冲突。装rknn-toolkit2之前建议先用conda建一个独立环境conda create -n rknn python3.8 conda activate rknn pip install rknn-toolkit2-1.6.0-cp38-cp38-linux_x86_64.whl pip install onnxruntime onnx1.13.1这里是重点onnx版本别装太新。RKNN转换器对onnx的算子兼容比较敏感onnx 1.15以上有些算子结构会解析失败。我习惯固定onnx版本避免下次拉新依赖时莫名其妙挂掉。2.2 导出ONNX时的三条军规从PyTorch导出ONNX是整个流程最容易出错的一环。这里说三条我踩过之后总结的军规。第一固定输入尺寸。NPU最讨厌动态shape虽然RKNN工具链理论上支持动态shape但每次推理都会重新做一些内存规划性能和稳定性都受影响。导出时直接把dummy input的shape固定下来比如torch.onnx.export(model, dummy, model.onnx, opset_version12, input_names[images], output_names[output0])这样做出来的RKNN最稳。第二尽量简化模型。导出完ONNX之后用onnx-simplifier处理一下很多冗余算子会被合并或删除。特别是模型带了一些自定义前处理逻辑、reshape、transpose套娃简化后转RKNN的出错率会低很多。python -m onnxsim model.onnx model_sim.onnx第三确认算子在RKNN支持列表里。RKNN对常见Conv、BN、ReLU、Add、Concat、Sigmoid这些都是支持的但一些冷门算子比如ScatterND、InstanceNorm之类需要提前确认。最简单的方式是转的时候直接试报错会告诉你哪个算子不支持再想办法替换或拆开。3. INT8量化实操校准集、config、build三步走3.1 校准集别随便拿张图就去量化INT8量化最关键的不是代码而是校准集。校准集的目的是让工具统计模型每一层激活值的真实分布如果给的数据和真正部署场景差太远量化后精度崩了你都不知道去哪哭。我一般从部署现场的测试视频里抽帧选200张左右覆盖不同光照、不同目标大小、不同背景。比如你做个行人检测的IPC就不能只拿大白天正对着人拍的图去校准得把夜间、逆光、雨天远距离的情况都放进校准集。200张不是硬标准我试过80张也能用但200张更稳而且不要多于1000张因为校准过程对每张图都要过一遍网络太多图反而拖慢转换速度。校准图片可以是jpg、png、bmp等常见格式然后在一个dataset.txt文件里按行写好路径。路径要写绝对路径或者相对执行rknn转换脚本时的路径我就不小心用过相对路径结果读不到文件报一个很隐晦的Error。还有一个容易翻车的地方预处理必须和训练时一致。你的PyTorch模型如果用的是mean[0.485, 0.456, 0.406]、std[0.229, 0.224, 0.225]那么通用做法是先归一化到0~1再减mean除std。如果模型里已经自带归一化层那校准图就不要重复处理。RKNN Toolkit2的config里有mean_values和std_values它的逻辑是(input / 255 - mean) / std所以如果模型需要caffe风格的scale要相应调整这些参数。3.2 量化脚本逐行拆解下面这个脚本是我长期在用的模板PC端Linux下执行能将ONNX模型转成INT8 RKNN模型。注意先写dataset.txt再跑脚本。from rknn.api import RKNN model yolov8s_sim.onnx rknn RKNN() # 关键配置 rknn.config( mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588, optimization_level3 ) # 加载ONNX模型 rknn.load_onnx(modelmodel) # 构建RKNN模型do_quantizationTrue启动INT8量化 rknn.build( do_quantizationTrue, dataset./dataset.txt ) # 导出 rknn.export_rknn(./yolov8s_int8.rknn) rknn.release()target_platform一定不能写错。网上很多教程用的是rk3568你直接抄过来转换虽然成功但板端NPU可能执行兼容层性能打折。如果你专门给RK3588优化就写rk3588。optimization_level3是能开的最激进优化会做算子融合和计算图重写。如果模型转换后出现精度问题可以先降到2或1做对比看是不是优化太过导致的。mean_values和std_values要跟模型本身的预处理对齐。我用YOLOv8官方模型时通常让模型自己处理归一化所以这边用0和255效果很好。如果你的模型输入已经是归一化过的数据这里就要按训练时的mean/std填。3.3 我一定改的几个参数除了上面这3个还有几个参数会在特殊场景下用到这里记录一下。第一个是do_quantization它控制转INT8还是FP16。设成False的时候生成的就是FP16模型这一步很灵活我经常在同一个项目里同时导出INT8和FP16两版然后在板端做AB测试。第二个是混合量化相关的quantized_dtype字段。RKNN Toolkit2在新版config中支持更细粒度的量化类型设置比如quantized_dtypew8a16表示权重用INT8、激活用FP16。这个设置是个调试利器当INT8整体精度掉太多时可以先试试权重量化、激活保持高精度很多情况下精度损失会明显缩小。第三个是dataset文件的路径。如果转换脚本是在工程根目录跑的dataset.txt里面的图片路径最好写相对根目录的路径。我不止一次因为在子目录里执行脚本导致图片路径对不上而转换失败。转完后RKNN工具会打印一个量化前后每层的dequantize误差报告。不要扫一眼就关掉重点看最后输出的整体量化精度评估如果误差过大比如超过5%建议马上去检查校准集分布是否和真实场景一致或者考虑下一节说的FP16方案。4. FP16量化不损失精度的“加速”备选方案4.1 FP16和INT8的取舍很多人一听量化上来就只做INT8结果精度一掉整个项目卡住。我的习惯是永远先导一版FP16出来因为FP16基本是无损的还能利用NPU的半精度算力比CPU快很多。当FP16跑通整个部署链路后再做INT8这样即使INT8方案挂了项目也不至于完全没用。FP16和INT8的取舍我用一张表说清楚对比项FP16INT8理论算力约3 TOPS约6 TOPS模型体积约FP32一半约FP32四分之一精度损失几乎可忽略与校准集强相关算子支持更全面偶尔有不支持或精度敏感算子典型性能提升1.2~1.5倍1.6~2.5倍如果模型有大量敏感层比如分割模型里的上采样、检测头里的几个回归分支INT8经常掉点。这时候FP16就是很好的兜底方案。RKNN里转FP16非常简单把build里的do_quantization改成False就行。工具会默认把所有共享权重和激活转成FP16存储推理时NPU按半精度计算。还有一个经验FP16模型在RK3588上经常能跑到一个不错的基准值比如我用YOLOv8s 640x640FP16大约20msINT8能到11ms左右。如果你们项目只需要1.5倍加速那FP16完全够用也省去了处理INT8精度损失的精力。4.2 混合量化思路INT8为主、FP16兜底有些模型整体用INT8现象很好但就是有那么一两个层量化后输出分布完全变形导致最终检测不到目标。这时候先别急着放弃INT8试试混合量化大部分层保持INT8个别敏感层用FP16。RKNN Toolkit2在较新版本里支持通过模型转换配置指定某些算子的量化精度。具体做法是根据转换日志里报出的高误差层名或者自己通过netron查看模型结构找出输出定义很敏感的层通常紧跟输入的层、最后的输出层、以及一些concat和upsample附近然后单独设置这些层不做量化。因为每个版本API会有细微差异我建议直接看rknn.api里的docstring搜“quantized_dtype”或“hybrid”关键词。如果工具链暂时不支持细粒度层配置也可以从模型侧想办法把敏感层从计算图中拆出来先让前面卷积层INT8量化敏感层放到后处理里用CPU的浮点算子处理。这个方法看起来土但很多时候能救急。混合量化的思路是“能不花的精度不花能省的资源尽量省”在实际项目中比单纯追INT8极限更有价值。5. 板上部署与性能实测从15ms到8ms的实践记录5.1 Python快速验证模型转好之后拿到板子上用RKNNLite做验证。我建议先用Python脚本把整条链路跑通再去考虑C/C优化。代码其实不长import time import numpy as np from rknnlite.api import RKNNLite rknn_lite RKNNLite() rknn_lite.load_rknn(./yolov8s_int8.rknn) rknn_lite.init_runtime(core_maskRKNNLite.NPU_CORE_AUTO) # 读图和预处理 img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 预热 for _ in range(10): outputs rknn_lite.inference(inputs[img]) # 计时 t0 time.perf_counter() N 100 for _ in range(N): outputs rknn_lite.inference(inputs[img]) cost (time.perf_counter() - t0) / N * 1000 print(faverage inference time: {cost:.2f} ms)core_mask参数可以控制使用几核NPU。默认NPU_CORE_AUTO会让驱动自动调度三个核心一般这个就够。如果你做的是多路视频流可以分别创建多个RKNNLite实例并手动指定NPU_CORE_0/1/2这样每路单独占一个核心互不干扰。5.2 性能对比表与瓶颈定位我以YOLOv8s 640x640在RK3588上的实测结果为例给大家一个直观感受模型版本单帧推理耗时帧率ONNX CPU FP32150ms左右约6 FPSRKNN FP1620ms左右约50 FPSRKNN INT811ms左右约90 FPSCPU到FP16是7倍多提升FP16到INT8又快了将近一倍。标题说“速度翻倍”在INT8对比FP16这个点上是真实存在的。这还是在Python接口下有部分采数开销的情况下测出来的如果改成C API并做zero-copy还能再挤出一些。性能没到预期时第一件事不是怪NPU而是检查预处理和后处理有没有拖后腿。我见过有人贪图方便在板端Python脚本里用OpenCV的imdecode逐帧转BGR、再resize、再转RGB结果CPU把NPU节省的时间全吃回去了。解决办法是把预处理放到模型内部用卷积或opencv算子做resize或者用板端硬件RGA做图像缩放和格式转换把CPU解放出来。内存拷贝同样是大坑。C API部署时尽量使用rknn_create_mem和rknn_set_io_mem实现zero-copy输入输出避免每一帧都做一次CPU与NPU之间的数据搬运。Python接口对这些操作的封装有限所以高性能场景还得上C/C或者ARM计算库。6. 量化路上的坑与排查实录6.1 精度掉成狗的排查方向INT8量化后最典型的现象就是AP/AUC断崖式下跌。我遇到过一次YOLOv8转完INT8后检测框全部偏移仔细排查发现校准集图片的分辨率和实际推理视频不一致换算到模型输入尺寸后每个目标的相对大小变了导致校准分布出现偏差。换了一批跟实际场景同源、同采样策略的图片后精度立刻恢复。如果校准集没问题就看一下配置里的mean_values和std_values。有些模型的预处理是在PyTorch的DataLoader里写的而不是在模型内部转RKNN时忘了跟训练对齐输入分布从0~1的区域变成-2~2量化后的激活范围找不准自然掉点。还有一种情况是模型某些层对数值分布特别敏感比如检测Head里的回归分支。这种情况用per-channel量化或者在config里降低优化级别往往能救回来。6.2 节点算子不支持怎么办转换时报“Unsupported Op”是第二大坑。最简单的办法是去手写一个等价结构把不支持的算子拆成几个支持算子的组合。比如某些模型里的HardSwish用ReLU6(x3)/6替换LeakyReLU也可以表示为max(x, 0) alpha * min(x, 0)但这里要注意算子的数值一致性。还有一招是利用RKNN Toolkit2自带的模型转换工具链它其实集成了很多自定义插件的支持比如可以在转ONNX之前用onnx_graphsurgeon把不支持的子图改写成通用算子组合。如果是在板端运行才报错那要检查.rknn模型版本和rknn-toolkit-lite版本是否匹配。新版模型放到旧版runtime上偶尔会出现算子实现找不到的问题优先统一版本。6.3 性能没起飞怎么办转完INT8后如果性能提升不到30%先别急着怀疑NPU排查顺序如下第一确认target_platform写的是rk3588如果误写成rk3566或rk3568模型可能会以兼容模式运行跑不出完整算力。第二检查日志里是否出现“fallback to CPU”或“CPU operator”的字样。RKNN转换时遇到不支持的算子有时不会直接报错而是悄悄塞进CPU执行。那样模型虽然在NPU上加载了但部分层在CPU跑整体时延自然下不来。解决方式就是继续精简单模型结构或者用混合量化方式让更多层完整运行在NPU上。第三看看是不是单张图的维度喂错了。如果板端推理时输入尺寸和转换时配置不一致RKNN会隐式重新布局带来额外开销。尽量保证板端输入尺寸与模型构建时一致不做动态变换。第四如果用的是Python接口多测几次取中位数或平均值。大部分板子第一帧推理慢后续才稳定我习惯预热10次后再开始计时这个前面代码里也写了。最后很多人在PC上把模型转换好后板端加载时报“failed to load rknn”大概率是PC端rknn-toolkit2版本比板端runtime新太多或者板端固件驱动太老。这时候直接升级固件或者降级PC端工具链确保两边版本落在同一个release配套方案里。我自己的体会是RK3588的NPU潜力很大但吃透它的关键在于对量化这件事有耐心。先FP16跑通流程再INT8慢慢调参校准集和预处理一定要较真才有可能把推理时间从十几毫秒压到个位数。真到那一步你回头看之前裸奔FP16的版本就知道这30块的板卡到底能挖出多少性能了。
返回列表