资讯详情

资讯详情

RK3588 NPU多模型视觉部署实战:从YOLO转换到并发调度

1. 项目概述为什么要把三个视觉任务塞进同一块 RK3588近几年边缘计算设备在安防和物联网场景里越来越“卷”单颗芯片往往要同时承担多路视频分析。这次的项目背景很简单一台 RK3588 工控机接入一路RTSP摄像头画面要在 NPU 上同时完成人员入侵检测、烟火检测、垃圾分类三个视觉任务而且不能明显掉帧。听起来像是在给算法工程师出难题但实际做下来RK3588 的 6 TOPS NPU 应付这三个模型是有余量的关键在于任务划分和调度设计。先说结论单块 RK3588 跑三个检测模型完全可行实测稳定在 25~30 FPSCPU 占用率控制在 30% 以内。这个成绩的前提是——模型要选对、量化要做足、多路推理要串行复用同一块 NPU而不是简单粗暴地开多个进程抢算力。如果你正打算在 RK3588或者 RK3568、RK3576上部署多模型视觉任务这篇文章可以帮你少踩不少坑。我会从模型选型、RKNN 转换、NPU 并发调度、内存带宽优化这几个维度完整走一遍最后附上调试过程中记录的问题和排查思路。2. 整体设计与算力预估算2.1 三个任务的共性特征分析人员入侵检测、烟火检测烟雾和火焰、垃圾分类这三个任务看起来跨度很大但从视觉算法角度有非常多的共性都是基于目标检测框架不需要实例分割或关键点检测这类重输出头输入分辨率要求都不高640x640 已经是“顶配”实际部署甚至可以降到 416 或 320都属于可离线推理的任务对延迟敏感度中等200ms 以内可接受后处理逻辑相似都是 NMS 类别过滤。这些共性决定了我们的技术路线统一采用 YOLO 系列检测模型通过 RKNN Toolkit 转换成 RK3588 NPU 能识别的 rknn 格式然后在一个应用进程里串行调用三个模型实例。2.2 为什么选 RK3588 而不是外挂 GPU 或多芯片RK3588 的 NPU 算力标称 6 TOPSINT8单看数字不算夸张但它在端侧设备里的优势非常明显——四核 A76 四核 A55 CPU、自带 8K 视频解码单元、支持多路 MIPI/RGB 输入、功耗控制在 5~12W这些能力让它天然适合做“一台设备八个功能”的集中式方案。对比其他方案方案优势劣势RK3588 NPU低功耗、低成本、集成度高单卡算力有限不适合超大模型外接 GPU如 Jetson Orin算力强、生态成熟功耗高、成本翻倍多芯片拼接多个 RK3568单芯片压力小同步难、开发量大、硬件复杂综合评估下来RK3588 是“单板多任务”场景下性价比最高的选择。关键是 RKNN 工具链经过几个版本迭代现在对 YOLO 系列的支持已经相当完善从 PyTorch 到 rknn 几乎一条龙。2.3 算力预算与任务拆解的数学基础在做具体开发前先把账算清楚假设三个模型都采用 YOLOv5s 结构输入 640x640INT8 量化后的单次推理耗时在 RK3588 NPU 上大约是 45~55ms。如果三个任务串行跑一帧的总耗时约为 150ms对应帧率只有 6~7 FPS显然不行。所以算力预算是这样拆的人员入侵检测模型输入降到 320x320单次推理约 25ms烟火检测保持 640x640 提高小目标召回单次推理约 50ms垃圾分类输入 320x320类别数多单次推理约 35ms如果完全串行一轮总耗时 110ms约 9 FPS。但因为三个任务的输入源都是同一路视频流完全没必要“逐帧串行”而是可以用多线程并发提交不同的帧给 NPU让 NPU 的 MAC 阵列始终保持忙碌这样整条管线可以达到 25~30 FPS。这个思路后面会详细展开。3. 模型选型与 RKNN 转换实操3.1 模型选型的取舍RK3588 的 NPU 虽然支持主流的 CNN 结构但不同模型的算子拆解效率差很多。我的实际经验是YOLOv5s是 RKNN 工具链优化最好的检测模型之一算子全部映射到 NPU几乎不会回退到 CPUYOLOv8s在 RKNN 上也能跑但 DFL 解码头的后处理如果处理不当耗时会有明显增加YOLOX的解耦头在 NPU 上稍逊需要跑 RKNN Toolkit 时额外配置PP-YOLO系列不建议部分算子不支持 INT8 量化会掉精度。这次项目里人员入侵和垃圾分类我选了 YOLOv5s烟火检测选了 YOLOv8s因为烟火目标形状不规则YOLOv8 的 anchor-free 设计召回更好。3.2 RKNN Toolkit 环境搭建与转换完整流程如果你用的 RKNN-Toolkit2 是 1.6 版本转换过程大概是这样# 安装依赖Python 3.8 宿主机建议 Ubuntu 20.04 pip install rknn-toolkit21.6.0 # 转换脚本核心部分 from rknn.api import RKNN rknn RKNN() # 配置模型输入这里用 onnx 作为中间格式 rknn.config(mean_values[[0,0,0]], std_values[[255,255,255]], target_platformrk3588) # onnx 模型加载 rknn.load_onnx(model./yolov5s_320.onnx) # 量化数据集推荐用 100~200 张真实场景图 rknn.build(do_quantizationTrue, dataset./dataset.txt) # 导出 rknn 文件 rknn.export_rknn(./yolov5s_320.rknn) rknn.release()几个值得注意的参数target_platform必须指定rk3588否则工具链会默认按 rk3568 优化某些算子的排布会差一截mean_values和std_values要和训练时保持一致。如果训练用的是归一化到 0~1 的流程配置应该是mean_values[[0,0,0]], std_values[[1,1,1]]量化数据集不要只用公开图片最好从实际部署环境的摄像头里抽帧。烟火这类带颜色特征的目标对量化敏感数据集里必须包含火灾、烟雾的样张否则量化后掉点特别明显。3.3 混精度量化烟火检测不掉点的关键INT8 量化默认是全量化遇到烟火模型会导致小目标漏检率上升 15% 以上。解决办法是混精度量化——让部分对量化敏感的层保持 FP16。在 RKNN Toolkit 2 里可以通过rknn.config的quantized_dtype参数做逐层指定或者在rknn.build之后调用rknn.quantize配合quantized_algorithm来设置。实际操作中我列出了烟火模型的敏感层清单主要是浅层输出通道少的卷积层只对这几个层用 FP16其余 INT8模型体积只增大 15%但小目标的召回率基本恢复到浮点水平。注意不要整个模型都用 FP16那会失去 NPU 的 INT8 加速优势推理速度直接翻倍下降。混精度的目标是“代价可控、精度可控”。4. NPU 调度与多任务并发设计4.1 RK3588 NPU 的真实工作模式很多人在 RK3588 上做多路推理时第一个想法是“开三个线程每个线程各自调用 rknn_run”。这其实是常见的误解——单块 RK3588 的 NPU 不能同时并行执行三个独立模型它的计算核心两个 NPU core共享一组 MAC 阵列资源任务调度由 NPU 驱动内部的硬件调度器完成。多个线程同时调用 rknn_run 时NPU 实际是把指令流排队执行而每个模型切换时需要重新配置权重和激活缓冲区切换开销如果控制不好效率反而比串行还低。正确的做法是串行提交、异步查询、帧流水线并行。4.2 代码实现固定大小输入 单独句柄我用的 RKNN Python 接口版本是 1.6.0核心代码如下import numpy as np import cv2 from rknnlite.api import RKNNLite # 初始化三个 RKNNLite 实例 rknn_person RKNNLite() rknn_fire RKNNLite() rknn_garbage RKNNLite() rknn_person.load_rknn(./yolov5s_320.rknn) rknn_fire.load_rknn(./yolov8s_640.rknn) rknn_garbage.load_rknn(./yolov5s_320.rknn) rknn_person.init_runtime(core_maskRKNNLite.NPU_CORE_0_1) rknn_fire.init_runtime(core_maskRKNNLite.NPU_CORE_0_1) rknn_garbage.init_runtime(core_maskRKNNLite.NPU_CORE_0_1)注意我在初始化时指定了core_maskRKNNLite.NPU_CORE_0_1即使用两个 NPU core。实测三个模型都开到双核心时NPU 利用率最高而且不容易死锁。推理阶段不要直接连续调用三个rknn.inference因为那是同步阻塞的。我的做法是# 用三个线程分别从同一个视频帧队列取帧各自推理 def infer_loop(model, input_queue, output_queue): while True: frame input_queue.get() img cv2.resize(frame, (model.input_size, model.input_size)) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) outputs model.inference(inputs[img]) output_queue.put(outputs) # 视频采集线程 def capture_loop(cap, person_q, fire_q, garbage_q): while True: ret, frame cap.read() if not ret: break person_q.put(frame) fire_q.put(frame) garbage_q.put(frame)这样采集帧是同一个 Frame 对象三个推理线程各自 resize 和预处理NPU 底层通过 RKNNLite 的排队机制串行跑但因为是三路流水线采集一帧的同时另外两个模型可能正在跑上一帧或上上帧整体 FPS 就上来了。4.3 帧队列深度和丢帧策略队列长度建议固定为 3不要用无界队列。否则当 NPU 处理不过来时内存里堆积大量待处理帧延迟越来越高最终画面卡顿甚至 OOM。丢帧策略我选择“队列满直接丢弃旧帧保留最新帧”。这个策略对检测类任务尤其重要——宁可偶尔跳一帧也不能把延迟拉大。因为入侵检测、烟火检测本质上都是做“告警”时间响应比连续检测更重要。4.4 是否建议使用多进程我的答案是同一个应用里用多线程就够了不要多进程。多进程会带来三个问题每个进程都要 Load 一次 rknn 模型RK3588 的 NPU 内存占用会翻倍进程间通信传递视频帧需要用共享内存或者编码传输效率低RKNNLite 的底层驱动对多进程并发支持不太友好有时会出现莫名的推理失败或 NPU 卡死。所以正确姿势是单进程 多线程 RKNNLite 句柄隔离。5. 内存带宽与预处理优化5.1 RK3588 内存架构对多模型的影响RK3588 是统一内存架构NPU 和 CPU 共享同一片 LPDDR4X/5 内存。三个模型同时加载后权重和激活值总共占用的 NPU 内存大约在 1.5~2GB 左右取决于模型精度配置如果用 8GB 版本剩余空间还能跑系统和其他应用但如果是 4GB 版本就必须做好内存预算。你可以在代码里通过rknn.get_sdk_version()和rknn.get_mem_info()查看 NPU 内存分配实时调整模型尺寸。5.2 图像缩放和色彩转换的加速每帧要做 640x640 和 320x320 两种 resize加上 RGB 转换如果用 Python 的cv2.resizecv2.cvtColor实时做CPU 占用会飙到 50% 以上。这块的优化空间非常大。两个方案方案 A直接在采集线程把一帧 RGB 图像转成 640x640 的 blob再用cv2.resize从 640 resize 到 320省去一次解码和色彩转换方案 B用 NPU 的RKNNLite.inference里自带的预处理参数inputs传一个带layoutnhwc的 numpy 数组让 RKNN 库内部做 resize 和量化但这个方式灵活性差、输出尺寸限制死。我最终采用的是方案 A视频帧先解码为 BGRcvtColor转一次 RGB 后基于 640 尺寸做一次双线性插值320 的输入直接从 640 缩放过来。这样三个模型共享一次色彩转换CPU 占用显著下降。5.3 后处理逻辑解耦三个模型的后处理NMS、坐标还原不要在 NPU 推理线程里做。正确做法是推理线程只负责inference拿到原始输出然后丢给另一个线程做 NMS 和业务逻辑。因为 NPU 推理是重负载后处理是轻负载混在一起会导致推理队列阻塞时间增加整体吞吐下降。6. 常见问题与排查技巧实录6.1 打开/dev/rknpu报错或初始化失败这是 RK3588 新手最容易遇到的坑。三个RKNNLite.init_runtime()同时执行时偶发会出现ERROR: failed to open device /dev/rknpu: Device or resource busy。原因RKNNLite 默认会占用全部 NPU 资源多个实例同时 init 时驱动内部会发生资源竞争。解决办法确保内核驱动版本是 rknn_server 1.6 以上RKNPU2 的固件都会带初始化时统一用core_maskRKNNLite.NPU_CORE_0_1避免某个模型单独占用单核如果仍然偶发在 init 前加一个 500ms 的随机延时实测能显著降低冲突概率。import time time.sleep(0.5) # 在 init 前加延时6.2 NPU 推理速度突然变成 CPU 速度模型转换时如果算子没有被 NPU 完全支持RKNN Toolkit 会自动把部分算子回退到 CPU。表现就是单次推理 cpu 占用高、耗时翻好几倍。排查方法# 在 convert 阶段打印 rknn.build(do_quantizationTrue, dataset./dataset.txt) rknn.list_support_ops()如果发现某些操作不在支持列表里优先换模型结构。比如用 YOLOv5s 而不是 YOLOv5m后者的某些大卷积可能回退到 CPU。另外注意 RKNN Toolkit2 对 PyTorch 模型要求先导出为 ONNX且 ONNX opset 版本最好用 11~12版本太高容易生成 NPU 不支持的算子。6.3 三个模型同时跑导致 NPU 崩溃或驱动死锁这个问题在多线程并发时容易遇到通常不是代码逻辑问题而是 RKNNLite 的驱动调度 bug 或者内存越界。我遇到的情况是连续跑 24 小时后inference突然超时接着rknn_run返回错误码 -1最终整个线程异常退出。排查思路第一步抓 dmesgdmesg | grep rknpu看有没有reset或timeout日志第二步检查是不是某个模型的输入尺寸不固定。RKNN 模型一旦 load 后inference的输入张量尺寸必须保持严格对齐如果 resize 时出现边界情况导致尺寸不匹配驱动底层会认为调用非法第三步确认三个模型每个都创建了独立的 RKNNLite 实例后init_runtime的core_mask不要混用统一NPU_CORE_0_1稳定些。最后我加了看门狗线程如果连续 5 秒没有新的输出就重建整个 NPU 相关实例——虽然听起来很糙但端侧设备上非常实用。6.4 后处理卡顿导致整体延迟升高三个模型的后处理如果都在 Python 里用纯 for 循环跑 NMS当画面上目标数量多时会突然卡几百毫秒。我的做法是把后处理改为向量化操作numpy 操作替代 for 循环利用多线程并行处理三路后处理结果如果追求极限性能后处理代码可以拆出来用 Cython 或 C 写绑定到 Python。实测前后对比最坏情况下后处理耗时从 180ms 降到 30ms体感非常明显。6.5 RK3588 温度过高导致性能下降这是最后一个容易被忽略的坑。RK3588 在满载 NPU 的场景下SoC 温度很快能到 80°C 以上这时 NPU 会主动降频推理速度从 50ms 飙升到 80ms。解决方法是散热如果使用开发板至少加装主动散热风扇如果是嵌入式整机确认外壳有足够散热开孔系统层面开启温度监控当 NPU 温度超过 85°C 时主动丢弃部分非紧急推理帧比如垃圾检测可以降低采样率到每 3 帧一次。7. 实测数据与后续改进空间用同一路 1080p RTSP 视频流做最终验证完整管线解码、三路推理、后处理稳定运行 72 小时无重启关键指标如下项目数值人员入侵检测延迟约 60ms含解码和排队烟火检测延迟约 90ms垃圾分类延迟约 70ms整体处理帧率25~30 FPSCPU 占用25%~35%NPU 利用率约 85%内存占用2.8GB8GB 版本这个结果已经满足项目现场要求。如果想进一步压榨性能可以考虑三个方向优化输入尺寸烟火检测在 640 下效果更好但人员入侵和垃圾分类降到 256 甚至 224可以显著减少 NPU 占用用 C 重写整个管线Python 版本在解码和后处理上还有提升空间对后处理结果做时间戳合并把三个模型的检测结果统一到同一个时间帧上避免出现同一时刻不同推理时间戳导致的上层业务混乱。最后分享一个我踩过多次坑之后的体会在 RK3588 这类端侧 NPU 上做多任务部署真正困难的不是把单个模型跑起来而是如何把三路推理的节奏调理清楚。很多时候你盯着单模型性能看每一项都合格一并发就出各种问题根源往往是 NPU 资源竞争的细节没有处理好。建议新上手的同学先把单模型跑通再用串行方式跑通三模型最后才上并发优化一步一步来少走弯路。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →