ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RV1106部署YOLOv8:RKNN工具链实战与INT8量化避坑

RV1106部署YOLOv8:RKNN工具链实战与INT8量化避坑 先说明一下这篇文章不是讨好搜索引擎的合集而是我从零开始把模型搬到瑞芯微 RV1106 上跑的完整记录。如果你手里正好有一块 Luckfox Pico 或者别的 RV1106 开发板想在上面部署 YOLOv8 这类轻量 AI 模型又不想被 RKNN 的工具链和各种算子兼容性问题劝退那这文章应该能省你不少时间。芯片本身定位就是入门级 IPC 摄像头 SoC0.5TOPS 的 NPU 听起来不大但做做目标检测、简单分类、人脸识别足够了关键是部署流程藏着不少经验价值我尽量把这几个步骤说透。1. 为什么偏偏是 RV1106芯片定位与部署思路1.1 RV1106 的硬件底子先把这个芯片的老底翻一翻。RV1106 是瑞芯微针对摄像头、门锁、扫地机这类场景设计的 SoCCPU 部分是一个单核 Cortex-A7主频 1.2GHz 左右内置了 0.5TOPS 算力的 NPU同时集成了 ISP、H.264/H.265 编码器支持 MIPI-CSI 输入很多模组还会外挂 DDR常见的是 128MB 或 256MB 内存。这个配置放在手机 SoC 面前肯定不够看但在几块钱成本的芯片里能跑神经网络推理已经是很多方案的刚需了。很多人第一次拿到 RV1106 开发板会很困惑觉得这个板子怎么什么都干不了。它本身就不是给你跑通用 Linux 应用的而是让你把计算任务放到视频链路里比如摄像头实时检测到人再触发录像或者识别到特定物体后上报。0.5TOPS 的 INT8 算力换算下来差不多每秒能做 5000 亿次整数运算跑一个 YOLOv8n 级别的模型绰绰有余但你要是拿它去跑大尺寸 Transformer 或者分割模型那就有点难为人了。NPU 并不是独立的存在它只是矩阵乘法和卷积的加速器。你训练出来的 PyTorch 模型最终还是要靠 CPU 调度、喂数据、收结果后处理比如 NMS 也都在 CPU 上跑。所以 RV1106 的部署必须把 CPU、NPU、内存带宽、ISP 输出格式全部考虑进去单纯盯着 TOPS 数字没有任何意义。我通常先把整条链路想清楚再决定网络结构。1.2 确定模型部署目标实时视频还是单张图片部署之前先问自己一个问题这个模型是要逐帧跑视频还是只处理抓拍回来的图片这两个目标的方案完全不同。逐帧跑视频的话输入通常是 ISP 直接输出的 YUV 或者 RGB 帧分辨率取决于摄像头你还得考虑帧率要求比如 25fps 算力够不够模型前处理会不会把 A7 单核 CPU 拖垮。只处理抓拍图片的话对实时性要求低很多甚至可以接受几秒钟出一次结果模型可以适当选大一点。我见过不少第一次上手的人上来就想跑 1080P 实时检测结果发现 USB 摄像头在 RV1106 上根本没法用因为芯片的 ISP 接口是 MIPI-CSI不是给你接 USB 摄像头的。就算你搞定了视频源1080P 缩放到 640x640 再做归一化在单核 A7 上要花不少时间NPU 推理本身可能只要几十毫秒CPU 前处理反而成了瓶颈。所以我的建议是第一版部署不要想太多先用一张图片或者一个视频文件验证模型能跑通再去做实时视频流这样排查问题会清晰很多。选模型的时候也要克制。YOLOv8n、YOLOv5n、MobileDet 这种 lightweight 模型是 RV1106 的主力参数量在 3M 到 10M 之间输入分辨率能用 320x320 就绝不选 640x640。模型越大NPU 延迟增加还是小事内存容易爆才是大问题。尤其板载 128MB 的版本一个 640x640 x 3 的输入张量加中间特征图再加上系统本身的消耗很容易 OOM。所以这个阶段你要做的不是炫技而是搞清楚资源的边界在哪里。1.3 为什么一定要理解 RKNN 这套格式RV1106 的 NPU 不认识 PyTorch 的 .pt 文件也不认识 ONNX 的 .onnx。瑞芯微给自家 NPU 定义了一套统一模型格式叫 RKNN板端的 RKNN Runtime 只认这个格式。所以模型部署的整个核心工作就是把你手上的模型从训练框架转换成 .rknn 文件然后写代码加载它、喂输入、拿输出再自己做后处理。这里面涉及两个工具链PC 端的 rknn-toolkit2 用于转换和验证板端的 librknnmrt 动态库负责推理执行。很多人以为转换就是一条命令的事情实际上稍不留神就掉坑里。模型里只要有一个算子 NPU 不支持转换过程就会报错或者即便转换成功生成的文件在板子上跑起来结果也是乱的。RKNN 工具链对算子的支持是不断完善的版本很关键哪怕你用的是官方最新版也不能保证所有 ONNX 算子都被支持。所以我的经验是模型结构尽量用经典的、瑞芯微官方 demo 验证过的结构比如 YOLOv5、YOLOv8、RetinaFace 这些不要自己魔改一些冷门算子除非你真的有能力调底层。2. 模型部署的第一步搞清楚 RKNN 这套东西2.1 从训练模型到 RKNN 的转换链路先捋一下完整的转换链路。你训练好的模型可能是 PyTorch 的 .pt也可能是 TensorFlow 的 .pb但 RKNN 工具链最喜欢的统一格式是 ONNX。PyTorch 模型要先用torch.onnx.export导出成 ONNXTensorFlow 模型用tf2onnx转成 ONNX然后把 ONNX 喂给 rknn-toolkit2。工具链会做算子解析、优化、内存规划最后生成一个带模型结构的 .rknn 文件这个文件既包含权重也包含 NPU 可执行的指令序列。为什么中间一定要过 ONNX因为 RKNN 工具链的算子映射表是建立在 ONNX 算子集上的直接转 .pt 没有通用性。实际执行转换的时候你还要设置输入张量的形状、通道顺序、归一化参数这些参数会固化到 .rknn 文件里板上推理时就不再单独做归一化了。也就是说你在 PC 端转换时怎么设置 mean/std板上推理拿到手的输入就是经过同样归一化的张量这个一致性必须保证否则模型推理结果会偏移。导出 ONNX 的时候有几个注意点。第一模型必须先设为 eval 模式把 dropout 和 BN 的 running stats 固定住。第二输入尺寸要固定比如1x3x640x640不要用动态 shapeRKNN 工具链虽然支持动态输入但 RV1106 这种小内存设备上没必要给自己找麻烦。第三opset 版本尽量选 12 到 16 之间的稳定版本太新的算子集容易有解析问题。我习惯导出后再用 ONNX Runtime 跑一遍确认 ONNX 文件本身是通的再去转 RKNN这样可以区分到底是模型导出问题还是 RKNN 转换问题。2.2 INT8 量化是 RV1106 的命根子RV1106 的 NPU 算力是按 INT8 计算的也就是说真正能跑得快的推理格式是 INT8 定点运算。如果你在转换 RKNN 时选择不量化直接保留 FP16那要么 NPU 不支持纯 FP16 模型要么只能落到 CPU 上慢慢算性能会很难看。所以部署这个芯片几乎避不开 INT8 量化。量化的基本思路是把浮点模型的权重和激活值从 float32 映射到 int8 范围映射关系由一个 scale缩放系数和 zero point零点决定。但这个映射不是随便拍脑袋定的而是需要一批有代表性的数据喂给模型做统计计算出每层激活值的动态范围这个环节叫 calibration也就是校准过程。rknn-toolkit2 的 build 阶段会要求你提供一个校准图片列表或 dataset 文件这些图片应该尽量贴近真实使用场景而不是随便拿风景图。我见过太多人量化后检测率掉得厉害基本都是校准集没做好。校准集不需要很多几十张就够了但一定要包含模型实际场景里的典型目标、典型光照、典型尺寸。比如你做室内人形检测那就多找室内人形图片别用一堆纯风景图。另外如果模型某些层对精度特别敏感可以在 RKNN 的量化配置里对这些层设置不量化比如custom_quantize指定一些层保留 FP16但这样做会占用更多内存和算力属于精细调优不建议一开始就用。2.3 版本匹配rknn-toolkit2 与板端运行时这块是我踩过最多坑的地方也是很多新手最容易忽略的。rknn-toolkit2 的 PC 端版本和板端 RKNN Runtime 的版本必须严格对应。比如你在 PC 上用了 1.6.0 的 toolkit 生成 model.rknn那板子上加载这个文件的 librknnmrt 也最好来自 1.6.0 的 SDK跨版本很可能出现模型加载失败、输出异常甚至直接段错误。瑞芯微官方文档里有一张版本兼容表但我怀疑真没几个人仔细看过。实际操作中的经验是整套工具链尽量成套下载。如果你用的是 Luckfox 出的 SDK它里面通常会自带匹配板端系统的 rknn-toolkit2 和 runtime你直接拿去用就好不要自己去 pip 装一个最新版然后跟 SDK 里的 runtime 混搭。用最新版并不代表兼容性最好反而坑最多。我在一个项目里就吃过亏PC 端用 2.0 转换成功板子端 SDK 还是 1.6结果程序启动就报错花了我整个下午排查版本最后老老实实换成 SDK 自带的工具链十分钟跑通。3. 实操流程从 ONNX 到板端跑起来3.1 搭建 PC 端转换环境我不推荐在 Windows 上直接搞 RKNN 工具链虽然官方说支持但各种依赖问题会让你想砸电脑。最好的方式是准备一台 Ubuntu 18.04 或 20.04 的机器或者用虚拟机和 Docker。下面是我习惯的安装方式以 rknn-toolkit2 1.6.0 为例# 创建虚拟环境RKNN Toolkit2 官方常用 Python 3.8 conda create -n rknn python3.8 conda activate rknn # 安装 rknn-toolkit2 的 wheel 包 pip install rknn_toolkit2-1.6.01fab100-cp38-cp38-linux_x86_64.whl注意这个 wheel 包通常在你下载的 SDK 或者官方发布的 rknn-toolkit2 仓库里面直接pip install rknn-toolkit2不一定能拉到正确的包。装完后验证一下能不能导入python -c from rknn.api import RKNN; print(import ok)如果 import 报错大概率是缺依赖比如numpy、onnx、onnxruntime按提示补装就行。这些安装动作都需要联网但如果你在公司内网环境下只能手动下载 wheel 和依赖包再离线安装整个过程也会更繁琐一些。3.2 编写模型转换脚本转换脚本是整个部署流程里最重要的一环。我用 YOLOv8n 举例假设你已经导出了yolov8n.onnx输入是1x3x640x640。下面这个脚本可以直接改改路径用import os import numpy as np from rknn.api import RKNN MODEL_PATH yolov8n.onnx DATASET_PATH dataset.txt # 每行一张校准图片路径 OUTPUT_PATH yolov8n.rknn rknn RKNN() # 配置预处理参数顺序是 RGB注意 mean/std 要跟训练时一致 rknn.config( mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrv1106 ) print(-- Loading ONNX model) ret rknn.load_onnx(modelMODEL_PATH) if ret ! 0: print(load onnx failed) exit(-1) print(-- Building RKNN model) ret rknn.build(do_quantizationTrue, datasetDATASET_PATH) if ret ! 0: print(build failed) exit(-1) print(-- Export RKNN model) ret rknn.export_rknn(OUTPUT_PATH) if ret ! 0: print(export failed) exit(-1) print(done)这里有几个细节值得展开。第一是target_platform一定要设置成rv1106如果你用默认值工具链可能按别的芯片优化生成的 RKNN 文件在 RV1106 上可能无法使用。第二是mean_values和std_valuesYOLOv8 官方训练时通常用 0 到 1 的归一化也就是 mean0、std255如果你训练时用的是 ImageNet 那种 mean/std记得改成你自己的值否则模型输出完全不对。第三是do_quantizationTrue这就是我们上面说的 INT8 量化同时提供dataset.txt里面每行是一个图像路径。构建成功后你会在目录下看到yolov8n.rknn文件。我建议先不要着急拷到板子上在 PC 上直接用 rknn-toolkit2 的模拟器跑一下确认输出跟我们预期一致。这样可以省掉反复烧写板子的时间import numpy as np from rknn.api import RKNN from rknn.api import RKNN # 重复导入不重新组织模拟器相关的 API 其实就在同一套rknn.api里面你可以加载刚生成 .rknn 文件再用rknn.inference(inputs[img])看输出。把输出在一个 Python 脚本里和 ONNX Runtime 的输出对比一下如果输出比较接近误差在 1% 以内就可以安心下一步了。这个步骤是我强烈推荐的很多人直接跳过去板子上测一旦结果不对根本分不清是转换问题还是板端代码问题。3.3 在板端编译运行 C 推理 DemoRV1106 板端跑推理我推荐直接用 C 写别用 Python。虽然 Luckfox 的 SDK 里可能带了 Python 解释器但单核 A7 上跑 Python 做实时推理性能和内存都是灾难。C 生态下的典型流程是用rknn_init初始化模型rknn_query查询输入输出属性rknn_inputs_set设置输入rknn_run触发推理rknn_outputs_get获取输出最后自行解析 YOLO 的输出。我以 Luckfox Pico 的 SDK 为例一般你在 SDK 的 examples 目录下能找到 rknn_demo 工程。编译流程大致是这样的cd examples/rknn_demo ./build.sh编译好的可执行文件通常是rknn_demo它会默认加载某个 .rknn 模型并对图片做推理。如果你是替换自己的模型需要注意输出解析部分YOLOv8 的输出是[1, 84, 8400]的结构也就是每个检测候选框的 4 个坐标加 80 个类别得分而 YOLOv5 的输出可能是三组不同尺度的[1, 255, 80, 80]等结构。这些差异会影响你在 C 代码里如何遍历输出、如何做 NMS不要拿着通用 YOLOv5 demo 去解析 YOLOv8 的结果不然你会发现问题很诡异。核心的推理代码骨架可以简化成下面这样#include rknn_api.h // 加载模型文件 FILE *fp fopen(yolov8n.rknn, rb); fseek(fp, 0, SEEK_END); int model_len ftell(fp); unsigned char *model_data malloc(model_len); fread(model_data, 1, model_len, fp); rknn_context ctx; rknn_init(ctx, model_data, model_len, 0, NULL); rknn_input inputs[1]; rknn_output outputs[3]; // 根据模型实际输出数量 // 设置输入图像数据假设 image 是 640x640 RGB inputs[0].index 0; inputs[0].type RKNN_TENSOR_UINT8; inputs[0].size 640 * 640 * 3; inputs[0].fmt RKNN_TENSOR_NHWC; inputs[0].buf image; inputs[0].pass_through 0; rknn_inputs_set(ctx, 1, inputs); // 推理 rknn_run(ctx, NULL); // 获取输出 rknn_outputs_get(ctx, output_count, outputs, NULL); // 在这里解析检测框做阈值过滤和 NMS rknn_outputs_release(ctx, output_count, outputs);这段代码里inputs[0].fmt要跟你在转换脚本里配置的输入格式一致如果你在 PC 端转换时用的是RGB且预处理已经写进 RKNN那么这里喂进去的原始 RGB 图像就不用再减均值除方差了直接用 uint8 即可。图像尺寸必须是 640x640如果不是你要先把原始图像缩放到 640x640这一步可以用 OpenCV也可以用 SDK 里自带的图像缩放函数。把编译好的可执行文件、.rknn 模型和测试图片通过 adb 或 scp 拷贝到板子上运行就能看到效果。如果是局域网里推流可以直接在板子上用 NFS 挂载共享目录省得每次都要拷贝文件。3.4 实时视频流部署要点做完了单图推理再往实时视频流走一步。RV1106 的核心应用场景是摄像头SDK 里通常会提供基于 V4L2 或自研 API 的摄像头采集示例。你需要把 ISP 出来的帧先缩放到模型输入尺寸再做颜色空间转换。这里我强烈建议用 SDK 里自带的 RGA 硬件加速模块来做缩放和格式转换不要用 CPU 软缩放。A7 单核 CPU 用普通 resize 跑 1080P 到 640x640一帧就得几十毫秒直接吃掉大半个帧周期而 RGA 是硬件加速基本不占 CPU。还有个细节是线程模型。如果你把采集、缩放、推理、后处理全写成串行帧率会非常低。比较稳妥的做法是至少开两个线程一个线程负责采集和缩放往环形缓冲区丢帧另一个线程阻塞等待新帧然后做推理和后处理这样可以把采集等待时间和 NPU 推理时间重叠起来。更进阶的做法是跑多级流水线但 RV1106 内存有限缓冲区不能开太大两个 buffer 轮转通常就够用了。实时视频流常见的问题是内存带宽和拷贝开销。尽量在同一块内存里完成缩放和格式转换避免多次 memcpy。SDK 提供的 demo 往往已经把这部分调好了你只需要改模型文件和后处理代码。实测下来YOLOv8n 输入 640x640 在 RV1106 上大概能做到 20 到 30ms 一次的推理加上前后处理720P 的摄像头跑 15fps 左右是可以实现的但如果还想更高帧率就得降低输入分辨率或者换更轻的模型了。4. 常见问题与排查技巧实录4.1 典型问题速查表我整理了一张快查表基本覆盖了第一次部署 RV1106 模型时最容易踩的坑你可以直接对照排查问题现象可能原因解决办法转换 ONNX 时直接报错算子不支持或 ONNX 版本太新用 opset 12换经典模型结构查 rknn-toolkit2 支持算子列表转换成功但模拟器输出全乱输入预处理参数不一致检查 mean/std、通道顺序、输入尺寸是否与训练一致板子加载模型崩溃PC 端 toolkit 和板端 runtime 版本不匹配统一使用 SDK 内置工具链版本推理时间异常高模型没有量化到 INT8跑到了 CPU fallback确认 build 时 do_quantizationTruetarget_platform 设置正确输出有检测框但框不准量化精度损失大校准集代表性差调整校准集或对敏感层关闭量化换更简化模型板子内存不够运行被 OOM kill模型太大或缓冲区太多降低输入分辨率减小模型结构减少推理缓冲线程数量实时流帧率过低CPU 前处理耗时过高用 RGA 硬件缩放和转换采集线程与推理线程并行化这些问题的排查顺序很重要。我在实际项目里一旦模型跑出来结果不对先把链路拆成三段PC 模拟器、板端推理、后处理解析。PC 模拟器输出对说明模型转换没问题问题在板端代码板端推理拿到了原始输出检查输出数值是否正常后处理再单独和 Python 端 NMS 结果对比。这样一段一段缩小范围比瞎试快得多。4.2 模拟器没问题的模型为什么上板就崩这个问题几乎每个人都遇到过。最典型的情况是 PC 端模拟器运行正常板子一执行就段错误或者返回-1。原因多半是模型转换时设置的输入输出内存规划不适合板端实际内存布局或者板端 librknnmrt 版本跟 PC 端不匹配。另外RV1106 的 NPU 驱动内存分配比较敏感如果你在程序里用了比较多的连续大内存分配也可能造成内存碎片。遇到上板崩溃我先建议你用 SDK 自带的最简 demo 跑一遍官方 rknn 模型如果官方 demo 能跑通而你的模型崩那问题基本在模型本身回 PC 端优化。如果官方 demo 也崩那要考虑板端系统本身有没有问题比如驱动没加载、设备节点权限不对、内存不足。可以在板子上执行dmesg | grep rknpu看看 NPU 驱动是否加载成功。很多时候是换了一个 SDK 版本驱动和 runtime 内核模块没对应上就出现这种诡异问题。4.3 量化导致精度掉太多怎么调量化后精度掉得离谱这是做边缘端部署逃不掉的考题。你首先要判断是不是校准集的问题换一批更贴近实际场景的校准图试试。校准集图片数量不用多我一般用 50 张左右但保证目标种类、位置、光照都覆盖到位。如果换校准集没用再考虑混合量化让敏感层保留 FP16。RKNN 工具链里支持按层设置量化方式常见的是先用默认量化跑完再用rknn.quantize相关的接口或配置手动指定某些层的量化参数。但混合量化会导致模型变大推理速度降低不是万灵药。实际项目中我更推荐反过来调整训练策略在训练时加入量化感知训练让模型适应低比特误差或者直接从模型结构下手把网络里那些对数值特别敏感的层替换成更简单的结构比如把 sigmoid 改成 ReLU虽然理论上精度会掉但量化后整体反而更稳。另一个容易忽略的点是输出后处理阈值的调整。量化后模型的输出概率分布会轻微偏移原来用 0.5 置信度阈值现在可能 0.45 更合适NMS 的 IoU 阈值也可以调。不要一上来就认定模型坏了先写个小脚本扫一遍阈值。有些项目里调整后处理参数带来的效果提升比折腾量化配置还明显。4.4 一点个人的避坑建议最后分享几条我用 RV1106 做模型部署攒下来的经验。第一条先完整跑通官方 demo再替换成自己的模型。官方 demo 会把编译、烧录、运行整个链路给你走一遍很多环境问题在跑官方 demo 时就会暴露而不是你自己开发到一半才遇到。第二条版本管理一定要做干净PC 端 toolkit、板端 SDK、NPU 驱动、librknnmrt 这四者的版本号要记录在项目文档里不然你过一个月回头再维护想死的心都有。第三条不要一开始就追求完美性能先把 640x640 输入、YOLOv8n、单图推理跑通再逐步优化到 320x320、实时视频流、帧率提升。每一步的结果都是可控的出了问题也好排查。第四条如果遇到奇怪的算子不支持不要硬刚能改网络结构就改网络结构边缘端部署本来就是用硬件特性约束模型设计的过程模型结构越贴近硬件能力后面越省心。我在这个项目里最大的感受就是模型部署这件事70% 的时间其实花在环境和数据流的调试上真正写推理代码的时间反而很少。你越是把转换、量化、版本这些看不见的细节抠清楚后面跑起来就越顺。希望这篇文章能帮你少踩几个坑早一点看到它在你自己的板子上跑出框来。
返回列表