
最近在做瑞芯微RV1106的模型部署把训练好的目标检测模型从ONNX一路搬到这颗小芯片的NPU上跑通。RV1106这块芯片主打安防IPC、智能门锁这类边缘场景芯片单价低、功耗控制好内部集成了一颗0.5 TOPS算力的NPU能在设备本地完成AI推理不用把视频流全部丢回服务器。项目标题看着就两个词实际做下来发现里面包含模型转换、INT8量化、交叉编译、板端C接口推理、性能调优一整套流程。这篇文章就把我从0到1跑通RV1106模型部署的完整过程写下来包括每一步的关键操作、参数设置和踩过的坑适合正在做边缘AI落地、或者刚拿到RV1106开发板不知道怎么下手的开发者参考。1. 项目概述搞清RV1106到底能跑什么样的模型1.1 芯片能力与典型应用场景RV1106是Rockchip面向视觉场景推出的高性价比SoC核心配置是单核Cortex-A7 CPU加一颗0.5 TOPS算力的NPU同时集成了ISP、视频编解码单元支持最高4K分辨率的视频输入。这套组合决定了它的定位不是给服务器做大规模训练的而是给摄像头、门锁、猫眼、家用机器人这类终端设备做本地智能分析。0.5 TOPS是什么概念以INT8算力计算它适合跑轻量级网络比如MobileNet系列、YOLOv5n、YOLOv6n、NanoDet、PP-LCNet这类几MB到十几MB的小模型。目标检测、图像分类、人脸关键点、简单的人体姿态估计都能覆盖。如果你想着跑大语言模型或者超大目标检测网络那确实是想多了这颗芯片不合适。部署到RV1106上解决的痛点很典型摄像头画面有隐私问题不能全部上传云端或者业务要求毫秒级响应云端来回延迟不可控再或者产品BOM成本卡得很死用不了RK3588那种大算力芯片。RV1106就是卡在中间的那颗料——本地推理、响应快、成本低虽然算力不大但把业务需要的模型精心裁剪优化后完全够用。1.2 模型部署的完整链路与核心诉求把模型部署到RV1106上不是一个单步操作而是一条完整链路训练/选择模型导出ONNX格式用RKNN-Toolkit2把ONNX转成RKNN格式做INT8量化用SDK交叉编译出板端可执行程序在板上加载RKNN模型并推理很多人拿着训练好的模型直接往工具链里一丢失败了就不知道往哪查。我完整走了一遍之后总结出三个核心诉求第一模型适配。模型结构和算子必须在RKNN工具链支持范围内或者通过等价替换能转过去。自定义算子最好不要有RV1106的NPU不是万能的。第二输入固定。RV1106对动态shape支持很一般务必把模型输入尺寸固定成一张图比如320x320、416x416。第三精度保持。转成INT8后精度多少会有损失关键是要把损失控制在业务可接受范围内。这三件事是所有边缘NPU部署的通用课题。路走通之后你会发现RV1106工具链的套路和RK3588、RV1126那套工具链高度相似只是算力和算子支持范围不同而已。2. 整体方案设计与工具链选型为什么是ONNX加RKNN-Toolkit22.1 工具链选型逻辑瑞芯微的NPU部署有一个强制前提必须使用瑞芯微官方工具链RKNN-Toolkit2。TensorRT是英伟达的OpenVINO是英特尔的它们只认自家硬件RV1106的NPU只认RKNN这一套生态。中间格式我强烈建议用ONNX而不是直接把PyTorch模型喂给RKNN-Toolkit2。PyTorch模型直接转换时解析器偶尔会对某些层处理不好比如aten命名空间的算子、某些版本的F.interpolate。先通过torch.onnx.export导出成ONNX再用RKNN加载整个转换链路的稳定性会好很多。我实测下来ONNX中间格式的出错率明显低于直接转PyTorch遇到算子报错时排查范围也更小。整体方案定为训练框架导出ONNX再由RKNN-Toolkit2转成RKNN格式。后续所有环节都围绕这条主线展开。2.2 SDK版本与配套组件瑞芯微的SDK迭代速度很快RKNN-Toolkit2也一直有版本更新。这个环节如果不注意后面会吃大亏PC端工具链版本和板端runtime版本不匹配模型转换没问题但一放到板子上加载就报版本不匹配或者直接初始化失败。RV1106开发要用官方配套的RV1106/RV1103 Linux SDKSDK里自带RKNN-Toolkit2以及对应版本的runtime库librknnmrt.so。我在项目里用的组合是RKNN-Toolkit2 1.6.0左右版本用SDK里配套的那一版别用更老或更新的RV1106 SDK release版镜像板端runtime使用SDK自带librknnmrt.so有个低级错误必须提醒网上有不少教程讲的是旧版RKNN-Toolkit它针对的是RK3399Pro、RK1126那批芯片命令格式和接口都不一样不能用在RV1106上。下载工具链时先看清楚是不是带“2”的版本名字一样很容易搞混。2.3 为什么先在PC模拟评估再上板RKNN-Toolkit2支持在x86 PC上模拟推理RKNN模型这一步在调试阶段价值巨大。我的习惯是先转换出rknn文件在PC上跑通模拟确认输出shape正确、数值范围正常、后处理能解出结果再拷到板级实测。板级调试成本比PC高一个量级每次都要烧录、跑日志、检查串口输出反复循环很费时间。先在PC上把模型本身的问题解决掉上板后只专注平台相关的问题效率能提升不少。不过也要清楚模拟器的边界它只能验证数值逻辑不能代表板端真实推理速度因为PC是x86架构NPU只是被模拟出来的。需要用耗时数据做决策的话以板端实测为准。调试顺序建议是先模拟再上板最后做性能调优。3. 模型部署实操全流程从ONNX到板端跑通3.1 环境准备与工具链安装强烈建议在Ubuntu 18.04或20.04 x86机器上进行转换操作。Windows上RKNN-Toolkit2只能用Docker方式跑多一层容器封装调用摄像头或USB设备时比较麻烦不如Linux原生环境干净。安装RKNN-Toolkit2时建议先建一个干净的Python虚拟环境免得和系统里的其他包打架# 创建虚拟环境 python3 -m venv rknn_env source rknn_env/bin/activate # 安装rknn-toolkit2 pip install rknn-toolkit2 -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后验证一下from rknn.api import RKNN print(rknn toolkit ok)下一步是从SDK里找到板端runtime库、交叉编译工具链和参考demo。RV1106 SDK解压后目录里通常会有rknpu2、examples/rknn_yolov5_demo、交叉编译工具链等几个关键部分。交叉编译RV1106常用的工具链是arm-rockchip830-linux-uclibcgnueabihf具体以你SDK提供为准。3.2 导出ONNX模型的注意事项以一个训练好的YOLOv5检测模型为例导出ONNX的核心代码如下import torch model torch.load(best.pt, map_locationcpu)[model].float() model.eval() # 固定输入尺寸RV1106不支持动态shape dummy_input torch.randn(1, 3, 320, 320) torch.onnx.export( model, dummy_input, best.onnx, opset_version12, input_names[images], output_names[output], dynamic_axesNone # 一定不要设置动态轴 ) print(onnx export done)导出时有几个细节直接影响后续转换成败第一固定输入尺寸。RKNN的NPU调度器对动态shape支持非常有限一旦设了动态轴可能出现转换报错、推理耗时不稳、甚至输出错乱的情况。模型训练时如果用了动态输入导出前务必固定下来。第二opset版本控制。我试过opset 12比较稳opset 15、17偶尔会遇到RKNN解析不了的算子编码到时候还得回退。如果你的模型用了比较新的算子优先考虑改结构而不是强行用高版本opset。第三归一化处理的放置位置。通常YOLO训练时会在前处理里除255这个操作可以放模型里面也可以放模型外面。我的做法是把归一化融进模型输入也就是导出前给模型加一层缩放这样板端只做resize和通道转换就行。好处是板端代码简单不用额外传mean和std参数代价是模型输入变成浮点如果你的模型层对浮点输入支持不佳还得另想办法。这个选择没有绝对标准关键是训练和部署的预处理逻辑必须完全一致。3.3 RKNN模型转换与INT8量化实操转换过程分四步走配置参数、加载ONNX、量化构建、导出RKNN。参考代码如下from rknn.api import RKNN rknn RKNN() # 1. 配置量化参数 rknn.config( mean_values[[0, 0, 0]], # 与训练预处理保持一致 std_values[[1, 1, 1]], target_platformrv1106, quantized_dtypeasymmetric_quantized-8 ) # 2. 加载ONNX ret rknn.load_onnx(modelbest.onnx) assert ret 0, load onnx failed # 3. 量化构建dataset.txt保存量化图片路径 ret rknn.build(do_quantizationTrue, dataset./dataset.txt) assert ret 0, build failed # 4. 导出rknn ret rknn.export_rknn(./best.rknn) assert ret 0, export failed关于INT8量化我用大白话解释一下量化就是把原来用浮点数表示的权重和激活值压缩到8bit整数域。模型体积缩小到四分之一推理速度变快但会引入精度损失。打个比方一张色彩丰富的照片从1600万色压成256色肉眼看可能没什么区别但如果是细腻渐变的天空压缩后就会出现明显的色块。量化造成的精度损失与此类似关键看模型权重分布是否集中、动态范围是否合理。量化图片集是决定量化效果的关键因素。dataset.txt格式很简单每行是一张图片路径/path/to/train_0001.jpg /path/to/train_0002.jpg /path/to/train_0003.jpg我一般从训练集或验证集抽取100到200张图片覆盖不同光照、不同远近、不同背景。量化图片太少或太相似统计出来的激活动态范围就不准量化后精度大概率崩。需要注意的是量化图片必须是模型输入尺寸工具链会自动resize不用自己提前处理。mean_values和std_values怎么填这又是一个高频翻车点。必须问自己训练时输入图像是怎么进网络的如果你训练时直接输入[0,1]的浮点图那mean填0、std填1如果你按ImageNet那种方式减均值除标准差就按实际值填。我发现很多人模型训练没问题卡在部署端几个月最后查出来居然是部署预处理和训练预处理对不上。3.4 PC模拟验证与输出检查转换完先别急着上板在PC上模拟推理一遍import cv2 # 初始化runtimetargetrv1106进行模拟 ret rknn.init_runtime(targetrv1106) assert ret 0, init runtime failed # 加载图像并预处理 img cv2.imread(test.jpg) input_data cv2.resize(img, (320, 320)) # 推理 outputs rknn.inference(inputs[input_data]) print(output shape:, outputs[0].shape) print(output sample:, outputs[0].flatten()[:20])如果输出shape和训练时的推理输出一致数值范围合理说明模型转换成功。这一步能提前发现八成的问题比如通道顺序错了、NCHW和NHWC混了、输出结构不对等。等PC模拟跑通了再往板上搬。3.5 板端部署与C接口推理代码RV1106板端推理用C/C API是正式做法。Python在板端虽然能跑但多一层解释器开销依赖管理也麻烦不适合产品交付。SDK里的examples/rknn_yolov5_demo是个很好的起点我在此基础上按项目需求改的。核心代码骨架#include rknn_api.h // 1. 初始化NPU上下文 rknn_context ctx; ret rknn_init(ctx, model_path, 0, 0, NULL); if (ret 0) { printf(rknn_init failed: %d\n, ret); return -1; } // 2. 查询输入输出信息 rknn_input_output_num io_num; rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, io_num, sizeof(io_num)); rknn_tensor_attr input_attrs[io_num.n_input]; for (int i 0; i io_num.n_input; i) { input_attrs[i].index i; rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, input_attrs[i], sizeof(input_attrs[i])); } // 3. 准备输入缓冲区 rknn_input inputs[1]; inputs[0].index 0; inputs[0].type RKNN_TENSOR_UINT8; inputs[0].size width * height * channels; inputs[0].fmt RKNN_TENSOR_NHWC; inputs[0].buf image_buf; // 必须是连续内存 // 4. 推理 rknn_run(ctx, NULL); // 5. 获取输出 rknn_output outputs[io_num.n_output]; outputs[0].want_float 1; rknn_outputs_get(ctx, 1, outputs, NULL); // 6. 解析输出、后处理 process_outputs(outputs, ...); // 7. 释放 rknn_outputs_release(ctx, 1, outputs); rknn_destroy(ctx);这块有3个坑值得一提输入缓冲区必须连续。很多摄像头回传的帧用OpenCV的Mat存放时因为图像宽度不是按4字节对齐的Mat的每行实际内存长度可能大于width乘以channels也就是有padding。直接把这样的Mat的data指针传给rknn模型读到的是错位的数据。正确做法是先resize到模型输入尺寸再按行把数据拷到一块连续的缓冲区里。更高效的做法是用RGA硬件去做resize和格式转换RGA输出本身就是连续内存SDK里有现成接口。颜色通道顺序不能想当然。ONNX模型里的张量通常是RGB顺序但摄像头采集到的帧默认是BGROpenCV读图也是BGR。如果训练时用的是RGB图板端就必须在送进模型前做一次BGR转RGB漏掉这一步模型精度直接崩掉。输出解析要看decode在模型内还是模型外。如果模型把anchor decode写进了网络内部输出直接是坐标和置信度省事如果decode留在外部你就得自己实现完整的解码逻辑包括anchor生成、坐标变换、NMS。这一步有一个坐标算错出来的结果全是乱的。我建议训练导出时尽量把轻量的decode逻辑放进模型里减少板端的工作量但这个要结合算子支持情况来权衡。3.6 性能验证与调优方向跑通之后要盯三个指标单帧推理耗时、内存占用、CPU占用。推理耗时要在rknn_run前后插时间戳测量。类似YOLOv5n这种规模输入320x320、INT8量化后在RV1106上做实时分析是够用的但具体数值和模型复杂度强相关得拿实际模型测。CPU占用高多半是图像前处理和后处理耗的资源。resize、颜色转换、坐标解码这些操作如果用CPU硬算小芯片很容易跑满。优先用RGA做resize用NEON指令优化后处理循环把CPU留给业务逻辑。内存方面RV1106板端一般内存不大模型、图像、系统进程都在抢。模型转换阶段能压就压能量化就量化输入输出缓冲区提前申请一次并复用不要在采集循环里反复malloc和free跑完推理及时调用rknn_outputs_release释放输出。我见过不少运行到一半OOM崩溃的基本都是对内存使用没有概念。4. 常见问题与排查技巧实录4.1 版本不匹配导致模型加载失败典型报错信息包括E RKNN: RKNN_ERR_PARAM_INVALID E RKNN: rknn_init fail! ret-3出现这类问题优先级最高的怀疑对象就是runtime版本不匹配。处理办法是确认PC端RKNN-Toolkit2版本和板端librknnmrt.so版本一致或者直接用SDK内置的一整套配套组件别混搭。把SDK里对应的runtime库通过adb或ssh推送到板上替换/lib下旧文件再重跑。这个坑我印象很深换了版本之后就一切正常了。4.2 转换时报算子不支持RKNN-Toolkit2加载ONNX时遇到不支持的算子会直接报错告诉你哪个op有问题。常见的“钉子户”包括某些Gather算子、部分Resize算子在特定参数下的实现、以及一些PyTorch自定义算子。我的排查顺序是调低opset版本从高版本降到11或12有些算子在不同opset下编码完全不同。改模型结构把不支持的层改写成等价支持的算子组合。比如某些检测头里的特殊操作可以用卷积、拼接、reshape组合来代替。拆模型把模型拆成两段一部分在NPU跑一部分在CPU跑代码里串联。这属于急救方案确实能救火但会抬高CPU负载非必要不用。还有一点RKNN工具链有个特性某些不支持的算子它会静默放到CPU上执行而不是直接报错。这种情况模型能加载、能跑但整体性能被CPU算子拖垮速度远不如预期。后面讲性能排查时会说到。4.3 量化后精度骤降如果模型转了INTS后预测结果惨不忍睹按这个顺序排查先确认是不是量化的锅。把do_quantization设为False先转一个浮点模型上板跑一遍如果精度正常那问题就在量化环节如果浮点模型精度就不对说明是转换或者预处理的问题别急着怪量化。量化环节里最常见的元凶依次是量化图片集不具代表性、归一化参数填错、某些敏感层被量化后误差被放大。量化图片集方面多抽一些覆盖全场景的图最好几百张代价只是转换时间变长。归一化参数方面反复核对训练代码里到底做了什么预处理。如果普通量化还是不行可以尝试混合量化。把对精度影响最大的层保留浮点其他层用INT8RKNN-Toolkit2支持按层配置量化方式。当然这会牺牲一部分推理速度和模型体积属于精度和性能之间的妥协。4.4 推理速度不达预期这个问题的核心思路是确认瓶颈在哪而不是盲目优化。看CPU占用率和日志。如果发现某一个算子被回退到CPU执行模型整体的真实性能就会很差。RV1106上的CPU算力和NPU差好几个数量级只要有一个关键层回退整条链路就会被拖住。对CPU回退的算子参考第4.2节的方法改结构或拆模型。排查完算子回退如果还是慢再考虑分级调优降低输入分辨率。320x320和640x640的计算量差4倍对实时性要求高的场景分辨率是最大杠杆。精简模型结构。把backbone换成更轻量级的选择或者砍掉最后几层大通道卷积。优化前后处理。用RGA做缩放、用NEON加速解码和NMS前处理和后处理在摄像头场景中经常被忽略实际占了不少CPU时间。RV1106这类芯片的理念是用最少的钱干够用的活别追求把算力吃满而是把单帧耗时控制在业务可接受的范围内。4.5 内存不足与运行时崩溃板端内存小这个问题几乎一定会遇到。常见表现是rknn_init报错、跑到一半进程被杀、或者在循环推理时内存占用持续上涨。排查重点模型本身太大试着用更小的模型结构或者更激进的量化。输入输出缓冲区有没有在循环里反复分配。复用缓冲区是基本操作。图像处理链路是否同时存了多份大图。比如原始1080p帧、resize后的中间图、模型输入图同时存在内存就是翻着倍在涨。尽量流水线化处理一个环节处理完就释放。推理输出的内存有没有及时释放。rknn_outputs_get拿到的输出需要调用rknn_outputs_release释放不能只get不release。在板子上调试时一边跑程序一边看free输出能直观看到内存峰值出现在哪个环节。5. 写在最后个人经验与建议这个项目做下来我最大的体会是RV1106模型部署本质上不是模型训练问题而是模型适配问题。训练端的一切都建立在PyTorch或TensorFlow上资源丰富但要把模型落实到这颗0.5 TOPS的小芯片上后面一半的工作其实是在做减法——裁剪、量化、算子替换、预处理对齐。新手建议不要一上来就挑战大模型先用MobileNet分类或YOLO-Fastest检测跑通全流程再逐步加复杂度。最后分享一个很实用的小技巧把整套部署流程脚本化。ONNX导出、RKNN转换、量化验证、文件拷贝到板子、上板自动跑测试这些步骤全部写成一个脚本每次换模型只改输入路径和配置参数一键跑完。我后来换了好几个模型基本都在半小时内完成从新模型到板端验证的整个循环。这个投资非常值。RV1106这套工具链的套路和瑞芯微其他芯片是相通的跑通这一颗芯片以后遇到RK3588、RV1126你会发现整个部署思路都是一样的只是算子支持和算力规模不同而已。