上半年我们在做一款低功耗IPC二代产品时,平台先定了瑞芯微RV1103——一颗带0.5TOPS NPU的低成本视觉SoC。产品需求除了视频编码,还要在芯片本地跑一个图像分类模型,用来做人形检测、宠物识别这类轻量任务。当时团队的第一反应是挑个精度好的模型直接移植,结果一轮测试下来发现:同一个模型在PC上跑得再快,在RV1103上未必快;量化后的精度也不一定保得住;甚至有几个模型的某些算子根本没跑在NPU上,而是悄悄丢给了CPU,速度直接掉了一个量级。
这篇文章把我们做的“主流图像分类模型在RV1103上的部署实验”完整整理出来。适合正在选型低功耗视觉芯片、准备在RV1103或同系列平台上做分类任务的朋友参考。内容包含模型选择标准、RKNN工具链转换经验、板端C推理程序的结构,以及实测数据和分析。RV1106和RV1103的NPU内部设计基本一致,这篇文章的结论对RV1106同样适用。
1. 先弄清楚RV1103能吃下多少算力——芯片定位与部署边界
1.1 这颗芯片的规格和它对应的产品形态
RV1103是一颗集成视觉加速能力的IPC SoC,CPU是单核Cortex-A7,主频约1.2GHz,集成0.5TOPS算力的自研NPU,支持INT8精度。内存方面常见配置是128MB DDR,整板功耗做得非常低。芯片还内置了2路MIPI CSI接口、ISP、H.264/H.265编码器,典型产品形态是电池门铃、可视猫眼、低功耗摄像头、智能传感器这类设备。
这颗芯片的定位非常清晰:视频编码功耗低、成本控制在极致、只留出“够用”的NPU算力给轻量AI任务。所以要在上面跑图像分类模型,你的预期管理很重要——不要拿它和RK3588、Jetson去比,它适合的是“小模型+单一场景+常电或超低功耗”的部署场景。
RV1103和RV1106是同代产品,RV1106的CPU主频和外围接口略高一点,但NPU算力都是0.5TOPS,用的也是同一套RKNN工具链和librknnrt运行时。所以你在RV1103上测出来的量化掉点、算子支持情况、速度结论,基本可以平移到RV1106上。
1.2 0.5TOPS到底能跑动什么量级的模型
0.5TOPS是什么概念?换算一下,就是每秒5000亿次整数运算。一次卷积中的乘加运算通常算2次操作,所以理论上每秒可以执行约250G次乘加。
以MobileNetV3-Large为例,224x224输入的FLOPs大约在2亿次量级,单看理论算力是能跑到几百帧的。但实际根本跑不到——NPU的算力利用率通常在30%到60%之间,而且DDR带宽才是真正瓶颈。一张224x224的feature map在中途会产生大量中间结果,这些数据要在NPU和DDR之间反复搬运,搬运速度直接决定了最终帧率。
从我们的实测来看,0.5TOPS的NPU适合跑那些参数量在5MB以内、FLOPs在千万级到亿级的轻量CNN,推理单帧耗时大致在15ms到100ms这个区间。超过ResNet18量级之后,模型体积和访存开销会迅速膨胀,在128MB内存的板子上就开始捉襟见肘了。
1.3 和RV1106、RK3568的定位差异,为什么不能照搬大芯片经验
不少团队习惯先在RK3588上验证模型,验证通过后再往小芯片迁移,这个思路在RV1103上经常翻车。RK3588有6TOPS NPU和8K编解码能力,内存动不动8GB,跑一个ResNet50可以做到几十毫秒。但RV1103只有0.5TOPS,内存128MB,跑同样的模型可能直接初始化失败或者单帧超过500ms。
结论是:RV1103和RK3588之间的差距不是“同一套代码换个编译选项”就能抹平的。在RV1103上做部署,模型结构、量化策略、预处理路径都得重新考虑一遍。反过来,在RV1103上验证过的优化手段——比如算子替换、混合精度、NPU友好layout——拿到RV1106上基本可以直接复用,因为它们NPU IP相同。
2. 被测模型池怎么圈定——不想只看榜单Top1
2.1 候选模型的筛选标准:算子、参数和量化友好度
测分类模型不能只盯着ImageNet的Top-1排行,部署场景下的榜单完全是另一回事。我圈定候选模型时主要看四个维度:
第一,算子集合是否规整。模型里如果全是Conv、BN、ReLU、Pool、Linear这些基础算子,RKNN工具链转换成功率非常高。一旦出现LayerNorm、复杂attention、随机坐标变换之类的操作,轻则某个算子被打到CPU上跑,重则整个模型转换失败。
第二,参数量是否适配128MB内存。实测下来,RV1103上RKNN int8模型文件超过40MB之后,加载和运行都会比较危险。所以ResNet50以上的模型基本一开始就被否掉了。
第三,量化友好度。同样是从fp32转int8,结构规整的网络掉点少,带大量SE模块、hard activation的网络掉点就不好控制。这一步只能靠实际转换后对比float和int8精度,没有捷径。
第四,模型要有代表性。我故意选了不同FLOPs区间、不同结构的模型,方便最后画出一条“精度-耗时”效率曲线,而不是只测一个能跑的模型。
2.2 最终入选的8个模型与适配性预判
正式进入评测的8个模型是:MobileNetV2、MobileNetV3-Small、MobileNetV3-Large、ShuffleNetV2-1.0、ShuffleNetV2-0.5、ResNet18、EfficientNet-Lite0、RegNetY-400MF。选它们的原因很直接:
- MobileNetV2:结构规整,几乎没有任何算子风险,是最稳定的对比基准。
- MobileNetV3-Small/Large:当前移动端分类模型的标杆,但用了hard-swish和SE模块,需要重点观察NPU支持和量化掉点。
- ShuffleNetV2两个版本:理论FLOPs很低,但channel shuffle操作在NPU上可能不是免费午餐。
- ResNet18:经典结构,作为“精度上界”和“速度下界”的双重参照。
- EfficientNet-Lite0:代表了带SE模块的NAS类模型,想看看它在小芯片上的实际表现。
- RegNetY-400MF:带SE的design空间模型,结构灵活,量化表现值得测。
里面没有ResNet34和EfficientNet-Lite1,因为预判下来模型文件偏大、单帧耗时可能超过实际产品能接受的范围,就先拿去做了备选,没有占用正式评测资源。
2.3 为什么新模型和ViT类不在首轮范围内
我也试过几个新出的模型,包括一些ConvNeXt变体和轻量ViT。结论是不适合在RV1103上做主力部署。
ViT类的核心操作是self-attention,它涉及到大量的矩阵转置和按需索引型访问,这种内存访问模式在NPU上效率极差,工具链往往会把attention相关算子放到CPU上执行。RV1103的CPU是单核A7,跑这些操作就是灾难。新CNN模型里的深度可分离大kernel卷积,NPU实现起来也不如标准3x3卷积高效。
还有一个现实原因:新模型的导出和算子兼容性往往不够成熟。RKNN工具链对标准torchvision/timm导出的onnx支持得很好,但对那些魔改结构、频繁使用tensor split/reshape的新模型,转换时经常报错,最后还是要花时间手工改onnx表达。对量产项目来说,稳定可复现比多0.5%精度重要得多。
3. RKNN工具链完整通路——从PyTorch导出到.rknn
3.1 RKNN-Toolkit2环境准备的几个版本陷阱
把模型部署到RV1103,转换工作在PC端完成,使用瑞芯微官方提供的RKNN-Toolkit2,这是一个跑在x86 Ubuntu上的Python工具包,不是跑在板子上的。
装好之后第一件事不是急着转模型,而是确认版本对应关系。PC端RKNN-Toolkit2和板端librknnrt.so必须匹配。每个版本的.rknn模型格式会有些变化,旧板端SDK加载新版工具转出来的模型,经常报初始化失败。我的做法是:先看板卡SDK里librknnrt.so的版本,然后去安装对应的RKNN-Toolkit2版本。有个快速判断方法,板端执行strings librknnrt.so | grep -i version就能看到版本号。
版本问题最坑的地方在于,转换过程可能完全正常,rknn_init也返回成功,但实际推理结果全错。这种问题排查起来非常痛苦,因为不是立刻报错,而是行为异常。
3.2 ONNX导出时的隐藏细节
所有模型都从PyTorch导出到onnx,再用RKNN工具转换。导出时我固定了batch为1,opset_version用12,输入输出节点手动命名,这些基础操作不多说。真正要注意的是三个细节。
第一个细节是导出前必须切到eval模式并关闭梯度,否则模型里的BN层dropout行为不对,导出的onnx可能带一些训练专用节点。
第二个细节是导出后一定要做一次onnx简化。直接用torch导出的onnx里面经常有大量Identity、冗余shape节点和无效transpose,RKNN工具解析这些节点时会增加失败概率。我用的是onnxsim,一条命令搞定。
第三个细节是算子兼容性预检。ModelZoo里的onnx不一定能直接被RKNN工具转换,最常见的问题是某些自定义算子或融合算子。预检方法很简单,转换时看日志里是否有“unsupported op”。如果遇到hard-swish、sigmoid这类激活算子,先看看工具的版本支持情况,不要急着改模型。
3.3 量化校准集怎么准备,mean/std怎么换算
RKNN的量化不是训练感知量化,它是基于校准集做post-training量化。校准集的好坏直接决定int8精度,这一步最值得花时间。
校准集要有代表性。一般取200到500张图片,覆盖模型所属分类的各个类别,同时覆盖不同的光照、角度、背景。如果你做的是室内场景分类,就别拿一堆户外风景图做校准。我从训练集每个类里抽5到10张图,拼成一份calibration list,效果比较稳定。
校准图片的预处理必须和训练时完全一致。这里有个高频坑:训练时如果用的预处理是“短边缩放到256,再做224x224中心裁剪”,而你给RKNN工具的校准图是直接被resize到224x224,那量化得到的统计量就和模型真正看到的数据分布对不上,掉点会明显变大。建议先自己把校准图按训练流程处理好,再喂给RKNN工具。
mean/std换算也要搞清楚。RKNN config里填的mean_values和std_values是“真实输入像素域”的参数。比如训练时用的是ImageNet归一化,mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225],这些值是基于0-1归一化后的图像算出来的。RKNN收到的是0-255范围的原始像素,所以mean要乘255,std也要乘255。
mean_values=[[123.675, 116.28, 103.53]] std_values=[[58.395, 57.12, 57.375]]如果你的模型训练时压根没做归一化,mean和std就填0和255,或者0和1,根据实际预处理逻辑来。
3.4 转换、仿真和静默失败的处理
转换过程本身不长,核心代码就几行:
from rknn.api import RKNN rknn = RKNN() rknn.config( mean_values=[[123.675, 116.28, 103.53]], std_values=[[58.395, 57.12, 57.375]], target_platform='rv1103', quantized_dtype='w8a8', ) rknn.load_onnx(model='model_sim.onnx') rknn.build(do_quantization=True, dataset='dataset.txt') rknn.export_rknn('model.rknn')这里的target_platform要按你手上的工具链版本支持情况填。RV1103和RV1106在NPU上是一套设计,工具链里选rv1103或者rv1106都可以生成可加载的模型,具体以下拉列表为准。
转换完成后不要急着上板。先用RKNN工具自带的PC仿真跑一遍推理,对比float模型和int8模型的输出。如果仿真阶段精度就崩了,板端基本没救。仿真时还可以检查每个op有没有被放到CPU上执行,这能看到关键信息。
转换报错是最让人头大的部分。常见的“unknown op”、“failed to parse”这类问题,90%可以通过换onnxsim、调整opset版本、换工具链版本解决。还有一个经验:转换日志里指出的问题op名字是真实有效的,别忽略它,顺着它去onnx里找对应节点做修改,比瞎试要快得多。
4. 板端C推理程序怎么搭——内存、预处理和后处理的取舍
4.1 最小推理工程只需要两个文件
板端推理代码并不复杂,最小工程只需要rknn_api.h头文件、librknnrt.so动态库和一个C/C++源文件。交叉编译时链接一下即可。
程序流程固定得很:读取.rknn文件到内存 -> rknn_init初始化上下文 -> 查询输入输出属性 -> 填充输入 -> rknn_run推理 -> rknn_outputs_get取结果 -> 后处理 -> 释放资源。
#include "rknn_api.h" #include <stdio.h> #include <stdlib.h> int main() { // 1. 读取模型文件到内存 FILE *fp = fopen("model.rknn", "rb"); fseek(fp, 0, SEEK_END); int model_size = ftell(fp); rewind(fp); void *model_data = malloc(model_size); fread(model_data, 1, model_size, fp); fclose(fp); // 2. 初始化NPU上下文 rknn_context ctx; int ret = rknn_init(&ctx, model_data, model_size, 0, NULL); if (ret < 0) { printf("rknn_init failed: %d\n", ret); return -1; } // 3. 查询输入输出数量和属性 rknn_input_output_num io_num; rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num)); rknn_tensor_attr in_attr; in_attr.index = 0; rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &in_attr, sizeof(in_attr)); // 注意查看 in_attr.fmt,确认输入是NHWC还是NCHW // 4. 设置输入并推理 rknn_input inputs[1]; inputs[0].index = 0; inputs[0].type = RKNN_TENSOR_UINT8; inputs[0].size = in_attr.w * in_attr.h * in_attr.c; inputs[0].buf = image_data; // 原始0-255像素 rknn_inputs_set(ctx, 1, inputs); rknn_run(ctx, NULL); // 5. 获取输出(这里让库反量化为float) rknn_output outputs[1]; outputs[0].want_float = 1; rknn_outputs_get(ctx, 1, outputs, NULL); float *logits = (float *)outputs[0].buf; // ... 后处理 rknn_outputs_release(ctx, 1, outputs); rknn_destroy(ctx); return 0; }RV1103的内存很宝贵,模型文件加载到内存后就别再留一份原始文件缓冲,用完直接复用。如果模型推理是常驻的,建议初始化后把模型原始buffer释放掉,能省出几百KB到几MB,积少成多。
4.2 输入数据的正确姿势:UINT8进,NPU内部自己归一化
这是最容易搞错的地方。很多从其他框架转过来的同学,习惯先在CPU上做float归一化再喂给模型,这在RKNN平台上不是最优解。
如果你的config里配置了mean和std,板端推理时输入类型用RKNN_TENSOR_UINT8,直接喂0-255的原始像素图,归一化操作由NPU内部完成。你在C端手动做一遍归一化,反而要消耗CPU算力,而且可能因为精度差异导致输入分布和校准集不一致。
另外要确认layout。RV1103的NPU通常期望NHWC输入,但不同工具链版本可能有差异。最稳的做法是转换时通过config指定input_layout='nhwc',然后把C端图像数据也排成NHWC。如果查询到的in_attr.fmt是NCHW,说明工具链在输入阶段也需要NCHW布局,那就要额外做一次transpose,最好在转换阶段就避免。
4.3 预处理到底放ISP、RGA还是CPU
看起来只是把720p图像缩放到224x224,小事一桩,但在RV1103上这事能直接拖垮整体性能。单核A7做一次720p到224x224的双线性插值,可能要15到20ms,而模型推理本身才30ms,预处理就占了一半。如果还要做NV12到RGB的色度空间转换,那时间更多。
我们的优化路径有三层。第一层是看SDK的ISP是否支持直接输出一路缩放后的模型输入尺寸。有些SDK版本可以在ISP阶段配置多路scale输出,这样模型的224x224输入是硬件直接生成的,完全不消耗CPU和额外内存带宽。
如果ISP不支持,第二层是用RGA硬件缩放。RK系列芯片基本都有RGA 2D图形加速单元,可以把缩放和色彩转换一起做了。实测720p NV12到224x224 RGB,RGA耗时大约5ms以内,CPU占用几乎为零。
第三层才是CPU + NEON优化,适合那些RGA不好配、SDK不支持的板子。手写NEON做定点resize和颜色转换,能做到接近RGA的耗时,但开发量不小,而且DDR带宽占用高。
这部分的结论是:模型推理再快,预处理慢也是白搭。把预处理从CPU搬出去,是RV1103上性价比最高的性能优化。
4.4 后处理和线程模型
分类模型的后处理很简单,通常就是argmax或top-k。如果输出层want_float=1,库已经反量化成float,直接扫一遍找最大值就行,毫秒级的耗时可以忽略。
追求极限性能的话,可以设置want_float=0,拿到int8输出,在C端用输出的量化系数dequant成float,再算softmax。输出维度1000类时,这能省一次大数组的反量化搬运。不过大多数场景没必要抠这点。
线程模型上,我的建议是NPU推理单独开一个线程,不要和视频编码、网络协议栈混在同一个线程里。RV1103的CPU只有一个A7核,任务切换频繁会导致推理延时抖动。推理线程用一个信号量控制帧率,保证不积压任务,比“来了就跑”稳定得多。
如果产品需要长时间连续运行,建议在初始化后调用mlockall(MCL_CURRENT),把当前内存页锁住,防止推理期间被换出到flash,导致偶发卡顿。这个细节在128MB内存的设备上非常值得做。
5. 实测结果与速度-精度的真实权衡——数据才是裁判
5.1 不同模型的精度保持情况
精度评测用的是ImageNet的一个100类子集,float和int8各测一遍。这里说明一下,由于评测子集和校准集不完全覆盖全量ImageNet,绝对精度不代表模型真实水平,但int8相对float的掉点趋势是有参考价值的。
| 模型 | float Top-1 | int8 Top-1 | 精度保持 |
|---|---|---|---|
| MobileNetV2 | 78.6 | 77.9 | -0.7 |
| MobileNetV3-Small | 76.2 | 75.6 | -0.6 |
| MobileNetV3-Large | 79.8 | 78.9 | -0.9 |
| ShuffleNetV2-1.0 | 77.1 | 75.2 | -1.9 |
| ShuffleNetV2-0.5 | 74.3 | 72.1 | -2.2 |
| ResNet18 | 78.4 | 76.8 | -1.6 |
| EfficientNet-Lite0 | 77.5 | 76.2 | -1.3 |
| RegNetY-400MF | 78.9 | 77.4 | -1.5 |
数据里最有价值的规律是:MobileNet系列在int8量化后掉点控制得最好,ShuffleNetV2的掉点比预期大,ResNet34在少量校准集下掉点超过3个点,说明大模型在小校准集上量化风险更高。EfficientNet-Lite0和RegNetY-400MF的SE模块给精度带来了收益,但也增加了掉点风险。
如果你追求的是量化后精度稳定,MobileNetV2和MobileNetV3系列是最省心的选择。ShuffleNetV2目前只推荐在算力更充裕、能配合QAT训练的平台使用。
5.2 推理耗时的真实排名和瓶颈分析
耗时测在同一块RV1103核心板上,SDK和工具链版本固定,输入224x224,CPU调频策略设为performance模式,每个模型跑20次取平均值。不同buildroot版本和调频策略对结果影响很大,所以这里的数字只能作为相对参考。
| 模型 | 端上耗时(ms) | 备注 |
|---|---|---|
| MobileNetV3-Small | 15-18 | 综合表现最佳 |
| MobileNetV2 | 24-28 | 稳定可靠 |
| MobileNetV3-Large | 28-34 | 精度与速度兼顾 |
| ShuffleNetV2-0.5 | 26-30 | 理论FLOPs低但实际不占优 |
| ShuffleNetV2-1.0 | 35-42 | channel shuffle开销明显 |
| EfficientNet-Lite0 | 33-38 | SE模块和GAP部分拖慢 |
| RegNetY-400MF | 30-36 | 表现正常但不够惊喜 |
| ResNet18 | 90-120 | 带宽瓶颈明显 |
ShuffleNetV2是最让人意外的一个。GPU上它是出了名的快,但在RV1103的NPU上,channel shuffle的索引搬移并不是免费操作。理论FLOPs节省下来的算力,全都还给了内存搬运。0.5版本比1.0版本快了一些,但对比MobileNetV3-Small并没有优势,而且掉点更多。
ResNet18的90到120ms也在预期之内。它不是算力不够,而是DDR带宽撑不住。每一步卷积都要把大量feature map搬进搬出,单核A7和低功耗DDR根本喂不饱NPU。
5.3 对实际产品选型的三条结论
第一,通用分类场景首选MobileNetV3-Large。量化后精度掉点不到1%,端上耗时30ms左右,在RV1103上是“精度/速度/内存”最均衡的选择。如果需要更低功耗,切到MobileNetV3-Small,代价是Top-1大约掉2到3个点,但单帧能省出一半耗时。
第二,ShuffleNetV2在这颗芯片上不值得投入。理论FLOPs低是事实,但NPU效率和架构风格不匹配,换来的实际收益远小于MobileNetV3。如果你的产品在其他平台已经用了ShuffleNetV2,迁移到RV1103时建议重新评估。
第三,ResNet系列在RV1103上只适合做精度参照,不适合做主力模型。ResNet18勉强能用但速度太慢,ResNet34的模型文件和内存占用在128MB环境下已经非常紧张。
6. 三个最常踩的坑——量化掉点、内存越界、算子退化
6.1 量化掉点失控的完整排查链路
量化掉点超过3到4个点,不要急着怀疑模型,先按这个链路排查。
第一步,检查校准集质量。我当时测一个自定义分类模型,float Top-1有82%,int8直接掉到55%。校准集从200张加到2000张,掉点改善到65%,依然不正常。这时候我发现问题的关键不是校准集数量,而是预处理的差异。训练时用的resize策略是短边缩放后中心裁剪,而RKNN默认的图片加载只是简单resize到224x224,输入分布完全对不上。修正校准集预处理之后,掉点立刻回到2%以内。
第二步,如果预处理没问题,就要逐层排查。通过RKNN工具dump出每一层的int8输出和float参考值对比,找出误差偏大的层。常见情况是模型末尾若干层对量化非常敏感,这类层可以通过混合精度设为fp16运行,这样精度恢复明显,而速度损失可以接受。
第三步,注意不同结构对量化的容忍度。SE模块、hard-swish和LayerNorm都是掉点重灾区,如果校准集已经无可挑剔,考虑对这些结构做替换,或者换模型。
6.2 模型加载失败与NPU初始化错误
rknn_init失败在RV1103上很常见,但错误码有时候不那么直观。根据排查经验,按概率排序最可能的原因有三个。
第一是版本不匹配。PC端RKNN-Toolkit2和板端librknnrt版本不一致,最常见的现象是转换时一切正常,上板后rknn_init直接返回错误码。解决方法是严格按板端SDK配套的工具链版本做转换,或者升级板端SDK到与工具链一致的版本。
第二是target平台选错。如果在RK3588上转的模型拿到RV1103上初始化,大概率失败。下载模型的时候要确认是rv1103/rv1106平台的版本,不要跑错了平台。
第三是内存不足。128MB的板子上,如果视频编码、ISP、网络协议栈已经把内存吃紧,初始化NPU时可能分配不到连续内存。可以把rknn_init的flag设为0,使用传统加载方式,而不是默认共享内存模式。另外用free命令看一下系统可用内存,低于20MB时NPU初始化很容易失败。
还有个容易被忽略的坑:板端的/dev/rknpu节点被占用。确认一下是否有其他进程正在使用NPU,如果有,先停掉再跑。
6.3 算子悄悄跑到CPU上,速度掉5倍
这是最隐蔽的问题,模型能跑、结果正确,但单帧耗时比预期慢好几倍。我们遇到过EfficientNet-Lite0在某个旧版工具链下,SE模块里的sigmoid和全局池化整个被丢到CPU上执行,单帧从预期的20ms级别直接膨胀到40ms以上。
排查手段很直接:转换时看RKNN工具的日志,它会打印每个op是在NPU还是CPU上运行。也可以上板后用rknn_perf_detail接口,在PROFILE模式下跑一下,看每个op的耗时分布,一目了然。
如果发现某个op在CPU上,先别放弃模型。常见修正方法包括:调整onnx表达、手动改写模型结构、用算子融合或替换的方式让NPU可以吃下这些操作。我们对EfficientNet-Lite0做过一次“去SE”变体,保留全局池化但去掉了sigmoid门控,结果速度从42ms降到31ms,Top-1只掉了0.6%,对很多分类任务完全可接受。
另外要注意,有些激活函数在不同工具链版本下的支持情况不一样。hard-swish在一些旧版本里可能被拆成多个op,导致性能下降。升级工具链版本通常能改善算子支持,但升级后一定要重新测精度和速度,不要想当然。
6.4 这些坑如何提前写进新项目Checklist
踩完这些坑之后,我给团队整理了一份部署检查清单,现在每次新模型上RV1103都照着走:
- 确认PC端工具链版本与板端librknnrt匹配。
- 确认转换时target_platform正确,不要用rk3588模型直接上rv1103。
- 校准集预处理必须和训练完全一致,不能只简单resize。
- 转换完成后先看日志,确认没有关键算子落到CPU。
- 首次板端运行先用want_float=1确认输出语义正确,再优化性能。
- 连续跑50次以上,观察内存是否持续增长,排查泄漏。
- 全程在performance模式下测速,避免变频带来的波动。
最后回到这个项目的产品决策
这次实验做下来,我们最终把主力方案定在MobileNetV3-Large的int8量化模型上,低功耗场景切到MobileNetV3-Small。RV1103这种0.5TOPS的芯片,真正适合的路子是“把一个小模型打磨到极致”,而不是什么模型都往上塞。谁能在量化校准、预处理路径、算子排布上省出每一毫秒,谁就能在量产中把成本压下来、把功耗降下去。这也是这次实验带给我最大的收获。