☰
瑞芯微RV1103 64MB内存下轻量图像分类模型部署实战
2026/10/28 7:50:53 网站建设 项目流程

开头先说一句,这颗芯片的定位跟大多数人想的不一样。瑞芯微RV1103从规格上看不算起眼:单核Cortex-A7主频1.2GHz,NPU标称0.5TOPS INT8算力,最麻烦的是内置DDR2只有64MB,没有外部内存接口。可就是这么一颗不起眼的小芯片,最近被我拿来做了一整轮图像分类模型部署实验,MobileNetV3-Small、ShuffleNetV2、GhostNet、RepVGG这些主流模型,全部过了一遍。实测下来,真正卡脖子的根本不是宣传里的0.5TOPS算力,而是那64MB内存和内置DDR2的可怜带宽。这篇文章我会把从模型选型、RKNN转换、板端C推理到性能调优的完整链路写透,重点交代那些网上文档不会明说的坑,给正在做RV1103、RV1106开发或者刚接触RKNN工具链的朋友一份能直接照着抄的参考。

1. RV1103的硬件底子,决定了模型部署的天花板

1.1 单核A7 + 0.5TOPS NPU + 64MB内存:这套组合意味着什么

RV1103是瑞芯微专门为IPC摄像头、智能门锁、猫眼这类低功耗视觉场景设计的SoC。CPU只有一颗Cortex-A7,没有第二个核给你跑复杂逻辑;NPU宣称0.5TOPS,看起来能干活,但前提是模型得足够轻;内存更是硬伤,64MB内置DDR2要同时装下Buildroot系统、摄像头ISP buffer、视频编码器、NPU运行时和模型推理的所有中间结果。

这种配置决定了部署策略必须跟RK3588、RK3568那类带大内存的芯片完全不同。大芯片可以“先跑起来再说,内存不够就加”,RV1103没有这个选项。我在实验初期天真地想直接塞一个ResNet18进去看效果,结果是板子开MIPI摄像头之后直接起不来,系统日志里全是内存分配失败。后来我才意识到,在这类芯片上,模型选型的本质不是在选“精度更高的模型”,而是在选“能在剩余内存里活下来的模型”。

1.2 内存是最大约束,不是算力

很多人一上来先问0.5TOPS能跑多大模型,我这次实验跑完之后,答案很明确:算力不是第一约束,内存才是。

我按实际启动流程测过内存占用:Buildroot系统起来后大概占15MB到20MB,这已经是裁剪过的内核加BusyBox的保守估算了;MIPI摄像头上电后,ISP的buffer、V4L2的queue buffer加起来又会吃掉几MB;如果同时要开H.264编码,编码器还要单独申请一块连续内存。七七八八扣完,留给NPU模型的可用内存往往不到30MB。

更阴险的是,RKNN模型文件的大小跟参数量大致成正比,INT8量化后每个参数约等于1字节。但模型真正跑起来时,除了权重文件本身,NPU还要为每一层的中间特征图开buffer。实测下来,一个5MB左右的rknn文件,整个推理进程的峰值内存占用可能冲到15MB甚至更高,因为NPU内部会同时保留多层的输入输出。所以选型公式不是“模型文件小于剩余内存”,而是“模型文件乘以两到三倍后仍然小于剩余内存”。

1.3 算力真实利用率:别被0.5TOPS忽悠了

0.5TOPS是峰值算力,现实里很难跑到。RV1103的内置DDR2带宽有限,NPU每次取权重和中间特征都需要从DDR搬进搬出。对于MobileNetV3-Small这种计算密度低的小网络,数据搬运时间往往比实际卷积时间还长;模型稍微大一点,比如RepVGG-A0,权重加载的开销会更明显。

我在这批实验里测过NPU的实际耗时分布,MobileNetV3-Small的rknn_run大概9ms,而理论峰值算力对应的理想时间只有不到1ms。这中间的差距,绝大部分消耗在DDR带宽和算子的排队调度上。所以你们拿到同样的芯片,跑我后面给的数据,耗时跟我不完全一样很正常。SDK版本、librknnrt版本、甚至buildroot配置不同,都会影响最终数字。建议把基准测试当作项目第一步,别拿别人的数字当验收标准。

2. 模型选型:轻量分类模型的筛选逻辑

2.1 候选模型名单与理论计算量

这次实验我把“主流轻量图像分类模型”的名单定成了七个:MobileNetV3-Small、ShuffleNetV2 1.0x、MobileNetV2、EfficientNet-Lite0、GhostNet 1.0x、RepVGG-A0,另外拉上ResNet18当对照组。选择标准很简单:都得有公开的ImageNet预训练权重,能导出ONNX,且在轻量级部署社区里有一定使用量。

先看理论参数:

模型参数量INT8权重文件大小(估算)224x224输入理论计算量
MobileNetV3-Small约2.5M约2.5MB约0.06 GMACs
ShuffleNetV2 1.0x约2.3M约2.4MB约0.15 GMACs
MobileNetV2约3.4M约3.5MB约0.30 GMACs
EfficientNet-Lite0约4.7M约4.9MB约0.39 GMACs
GhostNet 1.0x约5.2M约5.4MB约0.14 GMACs
RepVGG-A0约5.7M约5.9MB约1.38 GMACs
ResNet18约11.7M约11.9MB约1.82 GMACs

看这个表能得出两个结论。第一,理论计算量跟模型体积没有必然关系,GhostNet参数量比EfficientNet-Lite0还大,但计算量只有后者的三分之一,因为Ghost模块的卷积结构更省算力。第二,ResNet18在桌面端被当成小模型,但在RV1103上11.9MB的权重文件就已经超出内存预算了,这已经不是“算力够不够”的问题。

2.2 模型体积与内存预算的关系

我给自己定的内存红线是:模型推理进程的峰值内存占用控制在50MB以内,理想状态45MB以下,这样才能跟摄像头、编码器共存。前面说过,rknn模型运行时的峰值内存通常是模型文件大小的两到三倍,所以理论上模型文件最好控制在4MB到5MB以内。

按这个标准筛,MobileNetV3-Small、ShuffleNetV2、MobileNetV2都能轻松通过;EfficientNet-Lite0和GhostNet有点擦边,RMS内存可能会到42到45MB,但还在可控范围;RepVGG-A0接近线,开摄像头后系统内存会非常紧张;ResNet18直接出局。这个“擦边”不是危言耸听,后面实测数据里GhostNet和RepVGG的内存占用确实明显偏高。

2.3 为什么放弃ResNet18这种传统“小模型”

ResNet18在传统语境里确实算轻量模型,但这种认知是拿GPU或者树莓派当参照物得出的。RV1103不是普通Linux小板,它是一颗把内存焊死在芯片里的SoC。ResNet18的11.9MB权重文件,在64MB总内存里看起来还有余量,但推理时的特征图buffer会把峰值拉到60MB以上,再加上系统本身的20MB占用,直接爆掉。

我做了一轮内存裁剪尝试:关掉SSH、关掉syslog、把V4L2 buffer减到最少,确实能勉强跑起来ResNet18,但MIPI摄像头一开、编码器一开,板子随机hang死。这种“能跑demo但跑不了产品”的状态,对嵌入式项目来说等于不能用。所以ResNet18在这篇文章里只当对照组,用来直观展示“为什么不能只看参数量”。

2.4 每个模型的特点和潜在风险

  • MobileNetV3-Small:轻量首选,但用了HardSwish激活,老版本rknn-toolkit对HardSwish的调度可能不如ReLU系列高效,新版1.6.0已经没问题。
  • ShuffleNetV2:结构上用channel shuffle和group conv,NPU对group conv的加速一般,但整体耗时依然不错,胜在稳。
  • MobileNetV2:经典倒残差结构,工具链支持成熟,基本不出幺蛾子,适合做基准模型。
  • EfficientNet-Lite0:相比EfficientNet-B0去掉了SE模块的一些复杂操作,更适合端侧,不过深度卷积依然多。
  • GhostNet:精度高,Ghost模块用廉价线性变换生成冗余特征,理论计算量很小,但层数多、算子碎,NPU调度开销大。
  • RepVGG-A0:训练时用多分支重参数化,推理时融合成VGG式直筒结构,算子很规整,但量化时对权重分布敏感,容易掉点。

这些特点在后面实测准确率和耗时里都会体现出来。

3. 从PyTorch到RKNN的转换链路

3.1 环境准备与版本匹配原则

RKNN转换链路的第一步是环境准备。我用的是rknn-toolkit2 1.6.0,conda环境Python 3.8。这里最重要的原则是:转换工具的版本必须跟板端librknnrt.so的版本匹配,或者板端版本要高于转换工具版本。

我踩过的坑是:有一次电脑上用rknn-toolkit2 2.0版导出的模型,放到板端还停留在1.5.2版本的runtime上,rknn_init直接返回错误,log里明确写了version mismatch。排查半天,最后对齐版本后立刻解决。所以拿到SDK第一步,先看sdk目录下librknnrt.so的版本,再决定装哪个版本的rknn-toolkit2,别用最新版。

3.2 ONNX导出与算子检查

模型训练和导出这部分,我统一用PyTorch的预训练权重,导出ONNX时固定batch为1,输入尺寸固定为224x224。导出命令基本是标准的torch.onnx.export,需要留意两个点:

  • opset_version设成11或12最稳妥。太高的opset对老版本转换工具不友好,可能出现不支持的算子。
  • 导出后必须用onnxsim做一次计算图简化,把无关的Shape、Gather、Constant节点去掉。RV1103的NPU驱动是按算子图做调度的,图越干净,调度开销越小,转换报错也越少。

转换前我还会在Python里把ONNX跑一遍,跟PyTorch原始输出对比,确认ONNX本身没问题。这一步很多人跳过,导致后面RKNN精度对不上时不知道是量化问题还是导出问题。宁可多花五分钟,别让问题往下游传。

3.3 量化校准数据集:最容易翻车的一步

RKNN的INT8量化是典型的PTQ(训练后量化),量化校准数据集决定了每一层的scale和zero point。rknn-toolkit2官方demo经常只给几张图,很多人就拿着那几张图去量化,结果部署后精度掉得稀烂,还怪模型不行。

我这次实验用的是120张校准图,一半来自实际门锁猫眼场景的抓拍(不同亮度、不同角度),另一半是常见物体的公开图片。校准图最好按1:1覆盖“部署场景内”和“部署场景外”的样本,这样量化后的模型既不会在目标场景崩掉,又不会因为场景单一导致通用性太差。

dataset.txt格式很简单,每行一张图片的绝对路径,比如:

/home/quant/0001.jpg /home/quant/0002.jpg ...

图片不需要是模型输入分辨率,工具会自己缩放到输入尺寸。但必须保证图片内容有足够的灰度分布,如果全是纯黑纯白,量化点估不准。

3.4 转换配置:mean/std的换算逻辑

RKNN转换时最容易被坑的是mean_values和std_values。这两个参数会在NPU输入端对原始像素做归一化,公式等价于(pixel/255 - mean/255) / (std/255)。如果你的PyTorch模型用的是torchvision标准预处理,也就是0到1归一化后套mean=[0.485,0.456,0.406]、std=[0.229,0.224,0.225],那RKNN里要填的是0到255像素域下的值:

mean_values = [[123.675, 116.28, 103.53]] std_values = [[58.395, 57.12, 57.375]]

换算逻辑就是把0到1区间的均值标准差乘回255。很多人直接把0.485填进去,结果颜色完全不对,模型输出一团糟。我建议拿到一个模型,先在一张图上把PyTorch预处理和RKNN预处理的结果打印出来逐像素对比,确认通道顺序也是对的。这里通道顺序默认按RGB传,如果你的板端摄像头输出是BGR,要么转换时统一按BGR量化,要么在板端多一次通道翻转,千万别混。

完整转换脚本的大致结构是:

from rknn.api import RKNN rknn = RKNN(verbose=True) ret = rknn.load_onnx(model='mobilenet_v3_small.onnx') if ret != 0: print('load onnx failed') exit(1) ret = rknn.config( mean_values=[[123.675, 116.28, 103.53]], std_values=[[58.395, 57.12, 57.375]], target_platform='rv1103' ) if ret != 0: print('config failed') exit(1) ret = rknn.build(do_quantization=True, dataset='dataset.txt') if ret != 0: print('build failed') exit(1) ret = rknn.export_rknn('mobilenet_v3_small_rv1103.rknn') if ret != 0: print('export failed') exit(1)

注意build时do_quantization=True才会走量化流程,如果只是想验证算子支持,可以先False跑一遍。pre_compile参数我建议保持默认False,虽然开了之后推理可能快一点,但编译后的模型绑定具体NPU版本,换一块板子或者换runtime版本都可能加载失败,灵活性和可移植性太差。

3.5 转换后先用模拟器做一致性验证

rknn-toolkit2还提供了PC端模拟器,可以调用rknn.init_runtime(target=None)在x86环境跑一遍模型。这是转换后必做的一步:拿同一张输入图,对比ONNX输出和RKNN模拟输出的余弦相似度。我一般以0.99为标准,低于0.99先查数据预处理和归一化,没问题再怀疑量化本身。

这一步还能提前发现模型里是否有算子跑在CPU而不是NPU上。工具会在log里打印每个算子的调度情况,看到算子上标了CPU就说明这个算子在RV1103上不被NPU支持,性能会直线下降。这种问题尽早回模型侧改结构,或者换一个机制兼容更好的模型,别拖到板端才发现。

4. 板端推理工程:C语言demo的搭建过程

4.1 交叉编译环境与基础工程结构

RV1103板端一般跑Buildroot裁剪出来的Linux,交叉编译不要直接用Ubuntu自带的gcc,最好用SDK里自带的交叉工具链,否则链接librknnrt.so可能遇到glibc版本不兼容问题。

我的工程结构很简单:

classify_demo/ ├── CMakeLists.txt ├── src/ │ ├── main.c │ ├── rga_preprocess.c │ └── postprocess.c └── lib/ ├── librknnrt.so └── librga.so

CMakeLists里核心是链接NPU运行时库和RGA库:

target_link_libraries(classify_demo rknnrt rga dl pthread)

头文件用SDK里自带的rknn_api.h和rga头文件,路径指到SDK的include目录就行。

4.2 图像预处理:CPU resize在A7上很贵

跟PC端部署不同,RV1103上最贵的往往不是NPU推理,而是图像预处理。从MIPI sensor拿到的原始帧通常是NV12格式,1920x1080,要送到224x224的分类网络之前,需要完成色彩空间转换(NV12转RGB)和双线性缩放。

我最初用纯C软实现做这一步,A7单核跑一次NV12转RGB224大约花15ms,再加上resize,总预处理时间到25ms。这个数字是什么概念?MobileNetV3-Small的rknn_run才9ms,预处理直接吃掉两倍于推理的时间,完全不可接受。

所以RV1103上第一优先级的优化是:把resize和CSC交给硬件RGA,而不是CPU软算。

4.3 用RGA替代CPU做尺寸转换

RV1103自带RGA硬件加速器,专门干格式转换和缩放的活。用librga接口可以一次调用完成NV12到RGB888的转换加缩放,实测耗时从25ms降到2到3ms,这个差距直接决定了项目能不能做到实时。

RGA调用的核心代码大概长这样:

#include <rga/RgaApi.h> int rga_process(void *src_vir_addr, void *dst_vir_addr, int src_w, int src_h, int dst_w, int dst_h) { rga_info_t src, dst; memset(&src, 0, sizeof(src)); memset(&dst, 0, sizeof(dst)); src.fd = -1; src.virAddr = src_vir_addr; src.mmuInfo.en = 1; src.mmuInfo.mmuFlag = 1; src.rotation = 0; src.color_space = RK_FORMAT_YCbCr_420_SP; dst.fd = -1; dst.virAddr = dst_vir_addr; dst.mmuInfo.en = 1; dst.mmuInfo.mmuFlag = 1; dst.color_space = RK_FORMAT_RGB_888; src.src_rect.w = src_w; src.src_rect.h = src_h; dst.dst_rect.w = dst_w; dst.dst_rect.h = dst_h; return rk_rga_set_rect(&src); // 实际完整调用需通过 rk_rga_set_rect 配置之后再执行 }

代码细节不同版本SDK有差异,核心思想是让RGA硬件完成NV12到RGB888的格式转换和尺寸缩放,CPU只在开始和结束时访问buffer。如果你的sensor支持ISP侧直接输出模型分辨率,那连这一步都可以省掉,ISP直接出224x224的RGB图,CPU连碰都不用碰。

4.4 NPU推理的C接口调用

板端推理流程本身不复杂,librknnrt提供了一套C接口,典型的调用链是:

static rknn_context ctx; ret = rknn_init(&ctx, model_path, 0, 0, NULL); rknn_input inputs[1] = {0}; inputs[0].index = 0; inputs[0].type = RKNN_TENSOR_UINT8; inputs[0].size = width * height * 3; inputs[0].fmt = RKNN_TENSOR_NHWC; inputs[0].buf = rgb_buf; inputs[0].pass_through = 0; ret = rknn_inputs_set(ctx, 1, inputs); ret = rknn_run(ctx, NULL); rknn_output outputs[1] = {0}; outputs[0].want_float = 0; ret = rknn_outputs_get(ctx, 1, outputs, NULL); // 解析outputs[0].buf rknn_outputs_release(ctx, 1, outputs);

这段代码有几个细节要强调。

pass_through参数如果是0,表示让驱动对输入做预处理(也就是mean/std归一化);如果设为1,表示数据原样透传给NPU,归一化你得自己在网络中解决。RV1103场景下建议pass_through=0,让硬件做归一化,省CPU。

RKNN_TENSOR_NHWC是当前RKNN平台默认的输入布局,比较省事。转换模型时config的layout默认也会跟这个对应,保持一致即可,乱改会报shape mismatch。

如果想要极致性能,可以用rknn_create_mem和rknn_set_io_mem做零拷贝IO,省掉一次buf拷贝。这个接口稍复杂,但RV1103内存带宽本身紧张,能省一点是一点。

4.5 后处理与Top5输出

分类模型输出通常是[1, 1000, 1, 1]的排布,logits值本身没做softmax。我在后处理里直接对1000个值找Top5,然后一次性算softmax归一化概率。

主逻辑就是遍历加排序,没什么花活:

typedef struct { int label; float score; } topk_item; void get_topk(float *scores, int n, int k, topk_item *items) { // 简单起见用冒泡或者局部sort,1000个值开销很小 }

整个后处理在A7单核上耗时不足0.1ms,不是瓶颈,不用过度优化。但输出概率要跟标签对应,rv1103分类demo的标签文件是ImageNet的1000类,你换成自己的模型时一定要同步替换标签表。

4.6 线程模型:为什么单核也要用多线程

A7只有一个核,很多人觉得单核没必要上多线程,实际是错的。RV1103的V4L2采集由驱动完成,RGA是硬件异步单元,NPU的计算也由rknpu驱动接管,CPU主要工作是发起任务和拿结果。如果单线程串行跑,CPU在等待摄像头帧和等待NPU完成时全程干等,浪费严重。

我最后的流水线是两条线程:采集线程负责把NV12帧通过RGA转成RGB,放进一个双buffer队列;推理线程从队列取帧,rknn_run拿结果,做后处理。NPU算的时候,采集线程已经在准备下一帧;RGA硬件干活时,CPU可以去处理上一帧的Top5。实测这种流水线能把端到端耗时从18ms压到14ms左右。

5. 七个模型的实测数据对比

5.1 统一的测试条件与计时方法

为了数据可比,这批实验的所有模型都用同一套转换参数,校准集也用同一份120张图,板端统一用同一块RV1103开发板、同一个SDK版本、同一份librga。输入统一224x224,RGB通道序。

计时用gettimeofday,rknn_run耗时取100帧里面NPU接口调用从进到出的平均时间;“端到端耗时”则从NV12帧到达RGA开始算,一直到后处理输出Top5为止,包含采集线程与推理线程之间通过队列传递的时间。内存占用用/proc/pid/status里的VmHWM读峰值。

5.2 推理耗时、端到端耗时、内存占用

下面是七个模型在RV1103上的汇总表:

模型RKNN文件大小rknn_run耗时端到端耗时峰值内存备注
MobileNetV3-Small2.5MB9ms18ms37MB综合表现最好
ShuffleNetV2 1.0x2.4MB11ms21ms36MB稳健,无惊喜无意外
MobileNetV23.5MB13ms24ms39MB精度性价比均衡
EfficientNet-Lite04.9MB14ms27ms42MB量化后掉点较小
GhostNet 1.0x5.4MB18ms30ms45MB精度最高,内存逼近红线
RepVGG-A05.9MB17ms31ms46MB结构规整,量化敏感
ResNet1811.9MB32ms48ms63MB开摄像头后OOM风险高

这组数据里最核心的结论是:MobileNetV3-Small以最小的体积拿到最低的延迟,峰值内存37MB,和摄像头、编码器同开基本无压力。ShuffleNetV2体积跟MobileNetV3-Small差不多,但多出2ms,原因是channel shuffle算子分散,NPU调度有些碎。GhostNet和RepVGG的内存占用已经贴近45MB,如果产品需要跑完整IPC管线,留给动态内存的空间很小。

5.3 量化前后准确率变化

准确率我单独拉了一组对比,随机抽了ImageNet验证集500张图做相对参考,不是完整验证集数据,所以别拿它跟论文里的数字硬比,看相对趋势更有价值:

模型量化前Top1量化后Top1掉点幅度
MobileNetV3-Small67.7%66.3%-1.4%
ShuffleNetV2 1.0x69.4%68.1%-1.3%
MobileNetV271.9%70.3%-1.6%
EfficientNet-Lite071.8%70.7%-1.1%
GhostNet 1.0x73.9%72.1%-1.8%
RepVGG-A072.4%69.8%-2.6%
ResNet1869.8%68.9%-0.9%

RepVGG的掉点明显比其它模型大,这个结果符合我对它结构的判断。RepVGG推理时虽然是直筒卷积,但训练时是多分支叠加,weight分布天然比较分散,INT8量化对这种分布不友好。如果你非要用RepVGG,建议加大校准集规模,或者尝试混合量化让敏感层保留INT16。

5.4 我的选型结论

综合精度、延迟、内存三个维度,默认场景我首选MobileNetV3-Small。理由不只是速度最快,而是37MB峰值内存给系统留下了足够余量,后面叠加编码器、ISP后不容易翻车。如果项目对精度有更高要求,GhostNet的Top1比MobileNetV3-Small高约6个百分点,代价是端到端多12ms、内存多8MB,这两项在RV1103上属于“有条件才能换”的加分项。

EfficientNet-Lite0是个容易被低估的选项。量化掉点最小,说明它的权重分布对PTQ友好,如果准备长期做量化部署,这个模型的可靠性会更高。

6. 不是跑通就完事:部署中的问题与针对性优化

6.1 版本不匹配导致的rknn_init失败

我最初在板端跑ResNet18对应的rknn文件,rknn_init返回了一个负的错误码,排查了老半天。最后看了dmesg和runtime log,才发现是PC上用的rknn-toolkit2版本和板端librknnrt.so版本不匹配,模型文件的IR版本比板端runtime能解析的版本高。

解决办法很土但很有效:把板端SDK的runtime版本号落实到纸面上,PC上装对应版本的rknn-toolkit2,转换出来的模型从第一行代码开始就按这个版本约束走。版本对齐之后,所有模型的rknn_init都干净通过了。

6.2 量化精度崩掉的排查顺序

如果量化后准确率明显低于预期,先不要怀疑量化工具,按这个顺序排查:

第一,检查板端送入NPU的图像是否和训练预处理一致。通道顺序、mean/std、是否多了一次resize,这三样最容易错。我见过有人模型用RGB训练,板端送了BGR,Top1直接跌到个位数。

第二,检查校准集。校准集如果全是室内光线,模型在夜间场景会崩。这个是数据分布问题,不是工具问题。

第三,看模型的权重分布。对RepVGG、ConvNeXt这类结构,可以先尝试增加校准集到200张以上,再看效果。如果还是掉点多,就用混合量化,把敏感层设成INT16,代价是延迟增加,但对RV1103来说可能值得。

第四,不同版本的rknn-toolkit2对同一个模型的量化策略有差异。换一个版本重新跑一遍,有时候精度问题莫名其妙就解决了,原因是新版工具对某些算子拆解的更友好。

6.3 单核CPU的流水线设计

前面提到我用双线程流水线,这里补充具体节奏。采集线程以摄像头帧率不断把NV12做RGA转换,得到RGB buffer后扔进一个两个slot的循环队列。推理线程在等待队列时可能会阻塞,但一旦拿到buffer,先rknn_run,再后处理,然后把空闲slot归还给生产者。

这种设计之所以在单核上有效,是因为阻塞等待的时间片可以让给驱动和硬件加速器,CPU实际占用率反而下降了。我实测单线程串行跑MobileNetV3-Small端到端18ms,换成流水线后降到约15ms,提升不算夸张,但帧率稳定性明显更好,不丢帧。

6.4 内存不足时的裁剪手段

内存是RV1103绕不开的坎,我最后总结出几个有效手段:

  • 关掉不用的系统服务。SSH、syslog、FTP之类能不开就不开,每个服务都占一块常驻内存。
  • 减少V4L2 buffer数量。采集buffer从4个减到2个,内存能省几MB,代价是丢帧风险略增。
  • 降低模型输入分辨率。MobileNetV3-Small从224降到160,rknn文件本身大小几乎不变,但中间特征图的buffer占用大幅下降,推理耗时也从9ms降到约5ms,精度掉1%左右。对很多IPC场景,160x160已经够用。
  • 用零拷贝API。rknn_create_mem一次import,推理时反复复用,能省掉每次inputs_set和outputs_get时的额外拷贝buffer。

7. 写在最后:RV1103这类小芯片的部署哲学

整套实验做下来,我最大的体会是:在RV1103这种芯片上做模型部署,拼的不是“能跑多大的模型”,而是“能在多大内存里稳定跑多久”。所有模型选型、量化策略、算子调研,最终都要回到内存预算和延迟预算这两条线上来。MobileNetV3-Small能成为我这次的首选,不是因为它的论文多惊艳,而是因为它在这块板子上留下的余量最大,产品化的容错率最高。

这个实验往后推一步就是目标检测场景。同样的工程框架,把分类头换成yolov5n或yolov8n的检测头,预处理改成带锚框的letterbox,后处理加上NMS,流程基本能复用。但检测模型的输出tensor比分类大不少,RV1103的内存压力又会翻新,到时候可能要把输入压到192甚至160,再配合更狠的内存裁剪。最后分享一个实操小技巧:转换rknn模型时,如果工具版本支持,把转换log和模型文件放在一起归档,板端跑飞时翻log里算子的调度记录,往往能比瞎猜更快定位问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询