RK3588 边缘 AI 盒子多模型部署实战:单板卡同时跑入侵烟火垃圾分类
2026/9/8 13:08:37 网站建设 项目流程

设备还没到货,需求文档已经压过来了。施工工地出入口要人形入侵告警,废品回收站要看烟火和垃圾混投分类,甲方点名要一套能在单板边缘盒子上离线跑完所有算法的方案。我当时看了一眼预算和算力表,脑子里第一个念头是"RK3588 那颗 6 TOPS 的 NPU 能不能扛住三路模型同时推理"。后来整套方案落地,实测数据比我预想的好不少,也踩了足够多的坑,值得单独写一篇记录。

这篇内容不是 RKNN 官方文档的复读,也不是跑个 mnist demo 的玩具贴。我会从算力盘算、模型选型、模型转换、多模型并发调度、性能调优,到最后排障清单,把"单块 RK3588 同时跑三个检测模型"这件事完整拆开。适合正在做边缘 AI 盒子、多路视频分析、RK3588 部署的朋友参考。

1. 需求拆解与算力评估:一块 RK3588 到底够不够用

1.1 三个检测任务一起上的真实业务场景

先别急着谈模型和帧率,先搞清楚这类需求通常出现在什么现场。人员入侵检测一般放在工地周界、园区围墙、配电房门口,目标是检测人形目标进入预设区域,报警延迟要求高,通常希望从画面上出现人到产生告警不超过 1 秒。烟火检测放在仓库、垃圾场、山林监测点,目标是火焰和烟雾,这类目标形状变化大、纹理不清晰,模型召回率不能太低。垃圾分类则是在垃圾投放点或者传送带上方安装摄像头,识别瓶子、纸张、易拉罐、电池、厨余等常见类别,它对单帧分类的准确性要求高,但实时性要求相对宽松。

这三个任务有一个共同特点:全是视觉检测,都要跑深度神经网络,而且都要长期在线运行。如果每个任务买一块 Jetson 或者配一台 x86 工控机,成本直接翻三倍,功耗和体积也完全不适合边缘场景。所以"单块 RK3588 全包"不是甲方异想天开,是整个行业在内卷成本之后必然会提出的要求。

这里有一个很关键的认知要先纠正:RK3588 是一颗 SoC,不是单纯的一块 NPU 加速卡。它内部有 4 个 Cortex-A76 大核、4 个 Cortex-A55 小核、Mali-G610 GPU,以及一颗算力 6 TOPS 的 NPU。这意味着你不仅要考虑 NPU 能不能算得过来,还要考虑 CPU 能不能把图像数据预处理、模型调度、结果后处理、日志上报这些事情全部扛住。

1.2 RK3588 NPU 算力指标与真实可利用算力

RK3588 的 NPU 标称 6 TOPS,这指的是 INT8 精度下的峰值算力。它内部有 3 个 NPU 核心,理论最高支持同时处理多路模型。但是"峰值算力"这个东西,只存在于 datasheet 里。真实部署的时候,NPU 的利用率能到 60% 到 80% 就已经算调得很不错了,原因有三点:一是模型算子不一定完全被 NPU 加速,遇到不支持的算子会回退到 CPU 跑;二是大量中间张量的搬移会占用总线带宽,特别是当输入分辨率比较大时,DDR 带宽会成为瓶颈;三是多模型并发时,NPU 核心之间的任务分配不可能是绝对均匀的。

对于 6 TOPS 的算力上限,做一个直观换算。以 YOLOv5s 为例,INT8 量化后,640x640 输入,在 RK3588 上的纯 NPU 推理时间实测通常在 20 毫秒到 40 毫秒之间,换算成算力占用大约 2 TOPS 到 3 TOPS。也就是说,单颗 YOLOv5s 大约会吃掉 NPU 一半左右的峰值能力。如果三个模型都是 640x640 的 YOLOv5s,理论上会卡到 60 到 90 毫秒一帧,实际跑起来会非常吃力。

所以要回答"够不够用",必须把模型规格做小,把输入分辨率降下来,这需要在算法精度和算力占用之间找平衡点。以我这次的项目为例,最终决定使用"一大两小"的配置:人员入侵用 640x640 输入以保证远距离小目标召回率,烟火检测和垃圾分类用 320x320 输入以压缩计算量。这个组合在算力上留出了充足冗余,也为后续接入更多路视频流留了余地。

1.3 先算总账再动手:每路视频的算力预算

动手写代码之前,我习惯先列一张"算力账单"。比如目标帧率是 10 FPS,三个任务的输入分别为 640x640、320x320、320x320。用 RKNN 提供的 profiling 工具粗略估算,640x640 的 YOLOv5s 单帧约 25 ms,320x320 的 YOLOv5n 单帧约 8 ms,三个模型叠加的理论耗时约 41 ms。这意味着如果理想情况下三路模型完全并行,10 FPS 没有问题,甚至能跑到 20 FPS 左右。如果三路模型需要串行排队推理,那一帧的处理时间就接近 40 毫秒,对应 25 FPS 上限,实际还要叠加上 CPU 预处理和后处理的时间。

这里我建议每个人都在项目早期做这个简单的数学题:需要的目标帧率 x 每个模型的单帧延迟 = 总 NPU 负载。用这个数去和 6 TOPS 做对比,立刻就知道方案是否可行,而不是等代码写完跑起来才发现卡成 PPT。

2. 模型选型与 RKNN 转换:把 PyTorch 模型塞进 NPU

2.1 三个模型怎么选:精度、算力与板载内存的三角博弈

模型选型是整个项目里最影响最终效果的一步。很多人一上来就选 YOLOv8m 甚至 YOLOv8l,理由是精度高、效果好。但在边缘设备上,模型参数量直接决定显存占用和推理延迟,稍微不注意就会把 NPU 吃满。

以我这次的三个任务为例:

人员入侵检测选的是 YOLOv5s。原因很朴素:YOLOv5s 在 RK3588 上的生态最成熟,RKNN-Toolkit2 对它的算子支持最完整,量化后精度掉得少,而且 C3 结构的计算密度在 NPU 上发挥得不错。同时人员检测只需要一个类别,我可以把模型的类别数从 COCO 的 80 类裁剪到 1 类,输出张量大幅减小,后处理时间也跟着下降。

烟火检测选的是 YOLOv8n。火焰和烟雾的形态与常规物体差异很大,YOLOv8 的 anchor-free 设计对这类无固定形状目标更友好。考虑算力预算,我用 n 版本而不是 s 版本,把参数量压在 3.2M 左右。

垃圾分类选的是 YOLOv5n 再加一个分类头。严格来说垃圾投放点的识别不需要检测框,只要判断当前画面中垃圾属于哪一类,但实际业务中往往需要把垃圾位置也标记出来,所以我仍然采用检测模型,类别设置为 6 类。320x320 输入下用 YOLOv5n,单帧 NPU 推理可以做到 8 毫秒以内。

建议大家在选型时记住一个口诀:能用 tiny 不用 small,能用 small 不用 medium;输入分辨率从 640 起步往下砍;类别数能少就少。边缘设备上,精度差两三个点远没有帧率掉一半致命。

2.2 RKNN-Toolkit2 环境搭建与模型转换全流程

模型转换需要用到 Rockchip 官方提供的 RKNN-Toolkit2 工具包。它运行在 x86 主机上,可以把 PyTorch、ONNX、TensorFlow 等格式的模型转换成 RK3588 NPU 可以加载的 .rknn 文件。

先交代环境版本,这一步非常关键,因为 RKNN-Toolkit2 的版本兼容性问题极其突出。我使用的是 rknn-toolkit2 1.6.0 版本,对应 rknpu2 运行时库 1.6.0。注意:主机端转换工具的版本必须和板端 runtime 版本匹配,否则加载模型时会报版本不兼容错误。

转换环境建议用官方 docker 镜像,或者直接在 conda 虚拟环境里安装。rknn-toolkit2 依赖的 Python 版本和库很多,直接 pip 安装很容易把宿主机环境搞坏。我个人习惯是建一个独立的 conda 环境,Python 3.8,然后按官方 requirements 安装。

转换的核心流程如下:

from rknn.api import RKNN rknn = RKNN() # 1. 配置预编译、量化等参数 rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform='rk3588') # 2. 加载 ONNX 模型 rknn.load_onnx(model='yolov5s.onnx') # 3. 量化校准 rknn.build(do_quantization=True, dataset='dataset.txt') # 4. 导出 RKNN 模型 rknn.export_rknn('yolov5s_rk3588.rknn') # 5. 在 PC 端模拟推理验证 rknn.init_runtime() outputs = rknn.inference(inputs=[img])

这段代码里最值得注意的就是rknn.config中的mean_valuesstd_values。这两个参数必须和你训练模型时使用的预处理方式完全一致。如果你的训练代码里用了 ImageNet 的 mean(0.485, 0.456, 0.406)和 std(0.229, 0.224, 0.225),但转换配置里却填了 0 和 255,那推理结果会完全乱套。这也是 RKNN 新手最容易犯的错。

另外dataset.txt是量化校准图片列表文件,每一行是一张图片的路径,建议使用 50 到 200 张具有真实场景代表性的图片。量化校准图片不要用训练集里的图,最好从实际部署场景中截取,这样量化后的模型对真实数据的分布拟合更好。

2.3 模型转换中那些不改就会掉精度的细节

模型转换看似是脚本一把梭,但有几个细节很影响最终精度和 NPU 运行效率。

第一个是开启预编译。rknn.config中可以加一个optimization_level=3,这个参数会在模型转换时将部分算子在 NPU 上做算子融合。比如 Conv+BatchNorm+ReLU 的固定组合会被融合成一个算子,大幅减少中间张量的内存读写。实测下来,同样的模型开启 optimization_level=3 之后,推理时间可以缩短 15% 到 30%。但要注意,这个优化在模型输出层要小心,某些模型中融合之后会影响输出张量的排布方式,导致后处理解码时坐标错位,所以在导出前一定要在 PC 上用 rknn.inference 对比原始 ONNX 的输出,确认输出张量一致性。

第二个是片外内存布局。RKNN 默认的输出张量可能是 NHWC 格式,而 PyTorch 后处理代码通常按 NCHW 写。转换工具不会自动帮你做数据排布转换,你需要在后处理代码中显式处理。最好的方式是转换前就在 onnx 模型里用 Transpose 节点把输出转换为预期布局,或者在代码里做output.reshape((1, 84, 8400)).transpose((0, 2, 1))这类操作。

第三个是量化精度补偿。INT8 量化对检测框回归的影响通常不大,但对分类概率的影响比较明显,尤其是垃圾这种类别间形态相似的场景。如果发现量化后某类别的召回率下降,优先调整量化校准图片的类别分布。比如垃圾分类的六类垃圾桶中,如果电池样本在校准集里太少,量化后大概率会把电池误判为其他类别,需要补充对应图片重新校准。

3. 单 NPU 多模型并发:真正决定项目成败的调度方案

3.1 先搞清楚 RK3588 NPU 的并发机制到底支持什么

就算模型选好、转换成功,单块 RK3588 同时跑三路模型也依然有门槛。很多人的第一反应是"把三个模型塞进一个 .rknn 文件",这个思路是错的。RKNN 没有提供类似 TensorRT 的多模型串联图优化能力,也不建议把多个模型合并成一个输入。合理的做法是运行时创建多个 RKNN 上下文,每个模型对应一个独立的 RKNN 对象,然后在多线程里分别调用inference接口。

这里需要明确一个关键认知:RKNN 接口本身不是线程安全的,多线程并发调用同一个 RKNN 对象的inference方法会导致崩溃。正确做法是为每个模型创建一个独立的 RKNN 实例。每个实例内部拥有独立的上下文和内存空间,NPU 硬件会自动在多个核心之间分配任务,不需要应用层手动指定哪个模型跑哪个核心。

实测下来,同时初始化三个 RKNN 实例是可行且稳定的。这也是 Rockchip 官方推荐的"多模型多实例"方案。

3.2 相同优先级并发:三个模型同时跑的实现

我的调度架构可以简化为"三线程 + 独立 RKNN 实例"模式。伪代码如下:

// 每个模型一个线程,线程内循环执行推理 void infer_person(void* ctx) { auto rknn = create_rknn("person_rk3588.rknn"); while (running) { cv::Mat frame = capture_frame(0); // 从对应通道取帧 rknn.inference(frame); // NPU 推理 post_process_person(output); // 解码、画框、告警逻辑 } } void infer_fire(void* ctx) { auto rknn = create_rknn("fire_rk3588.rknn"); while (running) { cv::Mat frame = capture_frame(1); rknn.inference(frame); post_process_fire(output); } } void infer_garbage(void* ctx) { auto rknn = create_rknn("garbage_rk3588.rknn"); while (running) { cv::Mat frame = capture_frame(2); rknn.inference(frame); post_process_garbage(output); } }

这里面每个线程都是独立的消费者,互不阻塞。NPU 硬件内部会按照实际提交的任务负载自动分配算力。如果某一时刻三个模型同时提交推理请求,NPU 的调度器会排队处理,前面的请求会稍稍阻塞后面的请求。实测中,三路模型并发时的总吞吐量大约比串行低 8% 到 15%,这是可接受的代价。

这个架构的好处在于逻辑简单,每个线程只关心自己的模型和感知结果,不涉及复杂的锁和共享状态,后续如果要把单路摄像头扩展成多路,只需要在线程内部加一个摄像头通道轮询即可。

3.3 内存铺排与 Debug:跑起来之后怎么办

三路模型跑起来之后,内存占用是一个非常容易忽视的问题。RKNN 推理时的内存开销主要分三块:模型参数内存、输入输出张量内存、运行时临时缓冲区。以我的三个模型为例,模型文件总大小约 25MB,但运行时每个模型会额外分配约 100MB 到 200MB 的临时内存。三个模型同时存在,内存占用轻松超过 600MB。

如果开发板的系统内存低于 4GB,建议在分配 RKNN 实例时使用RKNN_FLAG_MEM_ALLOC_OUTSIDE标志,把输出张量内存分配到应用层管理的内存池中,这样能减少反复申请释放内存带来的碎片问题。同时要注意,创建第二个 RKNN 实例时,如果上一个模型的临时内存没有释放干净,会出现 coredump,排查方式是在创建新实例之前调用一次强制内存回收,并观察/proc/meminfo中的 MemAvailable 数值变化。

另外强烈建议在开发阶段给每个线程的推理函数加一个耗时统计,打印最近 500 帧的平均推理延迟。这样做的好处是能快速发现某个时段的性能劣化,比如垃圾模型开始时延异常升高,通常不是因为 NPU 算力不够,而是 SD 卡读写或者视频解码线程抢占 CPU 造成的。

4. 实际性能测试与调优记录:帧率、负载和延时到底怎么样

4.1 详解一下我的推理耗时分解

我把三个模型放进完整的应用流程里,开启 RKNN 的 profiling 功能,拿到的真实数据是这样的:

模型输入尺寸单帧 NPU 推理耗时CPU 预处理耗时后处理解码耗时实际帧率
人员入侵 YOLOv5s640x64022.6 ms3.8 ms1.2 ms约 30 FPS
烟火检测 YOLOv8n-pose320x3207.4 ms2.1 ms0.8 ms约 45 FPS
垃圾分类 YOLOv5n320x3206.9 ms2.3 ms1.0 ms约 45 FPS

注意这个数据是三路模型同时跑、系统压力最大的情况下的数据。NPU 的总体利用率大约在 75% 左右,已经逼近合理上限。如果把人员入侵模型的输入提高到 960x960,单帧推理会跳到接近 50 ms,帧率会被砍半,整体系统也会开始出现视频丢帧。

从这里可以得出一个很重要的结论:三路模型的帧率叠加起来完全足够覆盖业务需求。人员入侵只需要 5 FPS 就能保证告警及时性,烟火检测和垃圾分类 3 FPS 就够,所以我实际把三路模型统一配置为 10 FPS,给系统留出非常充裕的冗余。

4.2 调度周期与帧率的最优配置

很多人在部署时习惯"能跑多快跑多快",把所有模型的推理都调到最高帧率。实际上在边缘设备上,高帧率带来三个问题:CPU 占用暴涨、NPU 发热加剧、系统不稳定。我后来统一改成了定时推理模式,用 Linux 的 timerfd 每隔 100 毫秒唤醒一次各个推理线程,从摄像头取最新一帧来处理。取帧的方式很重要,不要用阻塞式的cv2.VideoCapture.read(),因为如果摄像头帧率只有 15 FPS,read()会一直阻塞等待,线程的其他清理工作就全被堵住了。而是先看缓冲区状态,用grab()retrieve()的方式取最新帧,或者直接轮询相机 API 的帧回调。

通过这种固定节拍的方式,三路模型的总算力负载被控制在一个稳定的水平,不会再出现偶发的连续多帧同时提交导致的 NPU 排队震荡。从系统稳定角度看,固定 10 FPS 比拼命跑 30 FPS 时要稳得多。

4.3 稳定性测试:7×24 小时烧机下的 NPU 温度与内存抖动

边缘盒子最怕的不是性能不够,而是跑一个月后随机死机。我在交付前做了一轮 7×24 小时压力测试,监控三项指标:NPU 温度、系统内存、推理延迟曲线。

先说温度。RK3588 的 NPU 满载运行时,核心温度大约稳定在 65℃ 到 75℃。我的测试环境是密闭铝合金壳体外加一个 5V 风扇,温升在合理范围内。如果没有主动散热,温度会一路飙到 90℃ 以上,触发 SoC 的降频保护,推理延迟会突然翻倍。所以如果你用的是无风扇的被动散热方案,建议把三路模型的帧率配置改成 5 FPS 加上跳过帧策略,即每两帧处理一帧。

内存抖动是另一个坑。最初版本我每次推理后显式释放输入输出内存,但长期运行后内存碎片越来越严重,MemAvailable 慢慢从 2GB 掉到 1GB 以下。后来改成内存池预分配方案,在创建 RKNN 实例时就把输入输出内存一次性分配完成,推理过程中只复用不释放,内存曲线变得非常平稳。

一个小技巧是定时检查/sys/class/thermal/thermal_zone0/temp(温度)和/proc/meminfo(可用内存),超过阈值就在业务日志里打告警,提前发现问题,而不是等客户反馈设备挂了才去现场看。

5. 多模型并存的进阶玩法:任务优先级抢占与资源隔离

5.1 紧急任务优先:入侵检测 20ms 响应如何保住

三路模型同样重要只是理想情况,实际业务里人员入侵告警的紧急程度远高于垃圾分类。如果入侵检测线程恰好被其他模型的推理任务排在了后面,告警延时就会变得不可控。所以生产级方案还需要"优先级抢占"机制。

在 Linux 线程层面,可以使用pthread_setschedparam给入侵检测线程设置SCHED_FIFO实时调度策略和较高优先级,让它能够优先抢占 CPU 时间片。这保证图像预处理、后处理等 CPU 环节不会被其他线程拖累。但注意:NPU 的调度并不完全由 CPU 线程优先级控制,无法保证 NPU 内部的任务抢占。更可靠的方式是在应用层实现一个"低优先级模型跳帧"逻辑:当入侵检测线程的高帧率任务已经提交并开始推理时,其他检测线程在提交推理前先检查一下 NPU 队列的占用情况,如果发现有紧急任务正在运行,就果断跳过当前帧,等下一个节拍再处理。

这个策略在我实际测试中非常有效。正常配置下,即使垃圾模型和烟火模型刚好同时提交,入侵检测的端到端告警延迟也能稳定保持在 200 毫秒以内,完全满足工地周界告警对"秒级响应"的要求。

5.2 利用核心隔离和线程优先级给业务留出后路

RK3588 是 4 大核 + 4 小核的架构。推理线程的 CPU 亲和性设置也值得讲究。我的分配策略是:

  • 视频采集、解码、预处理线程绑定到大核 0-3 中的两个核心,保证图像处理不被卡住。
  • 三路模型的 NPU 推理线程绑定在剩下的两个大核上,它们的主要任务是提交 NPU 任务和执行后处理。
  • 网络上报、日志记录、MQTT 推送等后台任务绑定到小核 A55 上,这些任务对时延不敏感,没必要抢占大核资源。

使用sched_setaffinity可以将线程绑定到指定核心。这层优化做完之后,即使后台有大量日志写入或者网络抖动,三路模型的推理延迟也不会出现明显毛刺。

还有一个容易忽略的优化点是内存带宽隔离。当三路模型同时运行时,DDR 带宽是共享资源。如果视频解码同时从摄像头拉取多路 1080P 流,带宽占用会非常大。一个务实的做法是把摄像头的视频流分辨率设置为 1280x720,而不是 1920x1080,在清晰度损失可以接受的范围内大幅降低带宽压力。实测 1080P 三路解码 + 三模型推理时,总内存带宽占用约为 80%,降到 720P 后占用降为 55%,系统响应平滑很多。

6. 踩坑实录:从模型转换到多路视频流的十二个常见坑

最后把这几个月里踩过的坑做成了清单,每一条都是真金白银换来的教训。放在最后给要动手的朋友当挡箭牌。

模型转换相关:

  1. rknn.config中的 mean/std 值与训练预处理不一致,导致输出概率全部偏移。解决办法是训模时保存一份预处理参数,转换时原样复制。
  2. RKNN-Toolkit2 版本与板端 librknnrt.so 版本不匹配,模型加载直接失败。拿到固件后先查看板端 runtime 版本,再决定主机端工具版本。
  3. load_onnx时遇到不支持的算子层,通常出现在自定义网络层。解决办法是先转 ONNX 再打开 simplify 开关,特例情况需要重写算子树。
  4. 量化校准图片用了训练集图片,导致部署场景掉点严重。重新截取现场真实画面做校准集后,mAP 提升明显。

多线程并发相关:

  1. 多个线程共享同一个 RKNN 实例会随机崩。必须一模型一实例。
  2. 频繁创建和销毁 RKNN 实例会导致内存碎片。启动时一次性初始化全部模型,进程生命周期内不释放。
  3. 没有给推理线程设 CPU 亲和性,线程在不同核心间来回调度,延迟抖动高达 30%。绑定核心后抖动降到 5% 以内。
  4. 三路视频解码没有用硬件解码,全用 OpenCV 软解,CPU 占用直接飙到 300% 以上。RK3588 有专门的视频解码单元,务必走硬编解码通道。

部署运行相关:

  1. 风扇转速没有读取和监控,夏季高温时 NPU 突然降频,帧率掉一半。读取风扇转速的方法可以借用 i2c 或者 pwm 控制器,实时监控并自动告警。
  2. 网络断线时 MQTT 推送线程一直阻塞重连,占用大量 CPU。改成带超时和退避策略的非阻塞推送,同时把缓冲队列大小设成固定值。
  3. 推理线程与 UI 线程共用同一个视频帧缓存,出现画面撕裂。用环形缓冲池替代单一缓存变量。
  4. 日志写得太频繁,同时写 SD 卡和串口,导致 IO 阻塞。把日志级别在生产环境调到 ERROR,同时把日志文件写到内存盘而不是 SD 卡。

这十二个坑,每一个都对应过线上问题。尤其是第二和第七条,属于"不定位到进程级别根本发现不了"的问题,排查起来非常耗费时间。建议所有准备做 RK3588 多模型部署的人,把这些检查项当成启动前的标准自检流程,而不是出了问题再补救。

回到标题里那个问题:单块 RK3588 的 NPU 能不能同时跑人员入侵、烟火检测、垃圾分类?答案是可以,而且稳定跑 10 FPS 三路并发毫无压力。前提是模型要选小、输入要控制、并发要用多实例、调度要做节拍控制,做到这几点,这块 6 TOPS 的小芯片能给你的惊喜比你预想的多得多。

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

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

立即咨询