news 2026/9/6 10:40:17

RK3588同源多任务调度实战:共享NPU与帧池的高效部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RK3588同源多任务调度实战:共享NPU与帧池的高效部署

做 RK3588 边缘 AI 最头疼的,往往不是单路模型跑不快,而是多路任务一起上时互相打架。这篇是这个系列的第 3 篇,前两篇我们把 RK3588 上的模型部署链路、单路视频的推理加速都过了一遍,这一篇专门聊聊“同源多任务调度”——也就是多路视频、多个检测需求同时存在时,怎么让它们共享一路数据源、共享同一个模型、共享同一块 NPU,还能各跑各的不掉链子。适合正在 RK3588 上做多路视觉检测、工业质检、安防布控类项目的朋友,看完可以直接拿去改到自己的框架里。

先交代一下背景。我手头这套设备是一块 RK3588 核心板,外接 4 路海康 IPC,需要同时跑人脸检测、安全帽未佩戴检测、明火/烟雾识别这几个业务。刚开始的做法非常朴素:每个业务各拉一路 RTSP 流,各加载各的模型,各开各的线程去推。结果一上电就发现,系统资源被迅速吃满,视频卡顿、NPU 占用率忽高忽低,内存也频繁告警。真正的问题不是某一个模型慢,而是多个任务在“同源”场景下没有统一调度,大家抢内存、抢解码器、抢 NPU,最后谁也跑不顺。

1. 先拆清楚“同源多任务”到底卡在哪

很多人一上来就想着怎么优化模型、怎么压帧率,但多任务场景下真正要解决的是资源编排问题。我先说清楚这里的“同源”指的是什么,以及 RK3588 这块芯片到底有哪些资源容易成为瓶颈。

1.1 边缘视觉任务的三种同源形态

我在实际项目里总结出“同源”至少有三层含义,这三层往往是叠加出现的。

第一层是数据源同源:4 路摄像头接入后,人脸检测、安全帽检测、明火检测都要消费这 4 路视频流。如果每个业务都独立拉流、独立解码,同一路视频会被解码好几遍,内存带宽和 CPU 消耗直接翻倍。正确做法是只拉一份码流、只解一次码,把结果放到公共帧池里,所有业务按需取帧。

第二层是模型同源:很多时候多个业务复用的是同一个基础模型。比如用同一个 YOLOv5s 做检测,只是不同业务对输出类别的过滤和后处理不一样。这时候完全没有必要加载 3 份模型、占 3 份内存、排 3 个推理任务,而是加载一次,多个业务共用同一个 RKNN context,靠调度器分配推理时间片。

第三层是资源同源:无论业务怎么拆,最终处理器、内存带宽、NPU 算力、解码器这些硬件资源都是同一套。RK3588 虽然有 3 个 NPU 核心,但依然不是“无限并发”的,更不能让每个任务都觉得自己独占 NPU。资源同源决定了多任务调度是必然要做的,而不是可选项。

1.2 RK3588 的家底够不够用

RK3588 的参数大家都很熟:CPU 是 4 个 A76 大核加 4 个 A55 小核,GPU 是 Mali-G610 MC4,NPU 标称 6 TOPS(INT8),内置 3 个 NPU 核心,还带 VPU,支持 8K 级别的视频硬解。单看数据很唬人,但落到真实项目里要清醒一点:6 TOPS 的算力其实相当有限。

拿最常见的 YOLOv5s 来说,如果输入分辨率是 640x640,INT8 量化后在 RK3588 上推理一次大概 15~25ms,具体取决于模型结构、量化精度和散热状态。这意味着 NPU 满负荷跑,每秒也就处理 40~60 帧。4 路视频如果每路都要求 25 帧,光一个模型就把 NPU 全部吃掉了,更别说其他任务。所以这类项目必须学会做“减法”:缩减输入分辨率、控制检测帧率、共享模型参数,以及最重要的——用调度去保证关键业务的优先级。不要指望硬件堆得越高越好,RK3588 的定义是“够用但必须精打细算”的边缘设备,调度的价值就在这里。

2. 数据通路设计:让一份视频流喂饱所有任务

多任务调度不是从推理才开始,而是从摄像头接入那一刻就要规划好。同一个视频源只有一份,但消费它的业务可能有四五个,这个阶段如果设计不好,后面调什么都白搭。我的做法是“统一接入、集中分发、零拷贝取帧”。

2.1 统一拉流与硬解码

4 路海康 IPC 通过 RTSP 协议接入。项目里不建议每个业务各自拉流,而是用一个单独的接入模块负责所有视频流。这个模块创建 4 个拉流线程,每个线程用 FFmpeg 的 API 去拉一路流,也可以通过命令行ffmpeg -rtsp_transport tcp -i rtsp://user:pass@ip:554/Streaming/Channels/101 -frames 1 out.jpg先验证流的可用性,但正式代码里还是推荐用 libavformat/libavcodec 做库调用,把 RTSP 流解码成 NV12 格式的原始帧。

解码这一步要放在 VPU 上,不要用 CPU 去软解。RK3588 的 VPU 可以并行处理多路 1080p 解码,CPU 软解 4 路 1080p 会直接吃掉好几个核。硬解码出来的帧建议直接落到 NV12 或 RGB 的共享内存里,这样后续做模型前处理时不用再转一次格式,省掉一笔不小的开销。

# 验证单路 RTSP 流是否可用 ffmpeg -rtsp_transport tcp -i rtsp://admin:pass@192.168.1.64:554/Streaming/Channels/101 -frames 1 -pix_fmt nv12 frame.nv12 -y

2.2 帧池:所有业务从同一个地方取帧

视频帧被解出来之后,不能直接丢给各个业务线程,而是先放进一个“帧池”。帧池本质上是一个环形队列加引用计数:每一帧在内存里只有一份拷贝,谁要用就“借”走,用完立即归还或释放。这样能避免同一帧在多个业务线程里被复制好几遍,对内存带宽的压力小很多。

一个比较容易接受的类比是快递柜:快递员(解码线程)把包裹放进柜子,每个取件人(业务线程)凭取件码去拿,拿走之后快递员才能清空格子装下一个包裹。边缘视频任务帧率不需要无限高,帧池深度通常设置为 3~5 帧,超过这个阈值说明消费速度跟不上,应该做丢帧而不是越积越多。

帧池还需要考虑“帧龄”问题。如果某个业务跑得慢,拿到的一直是十几秒前的老帧,那结果没有意义。我的处理方式是在帧结构体里打时间戳,调度器在派发任务时会把过老的帧标记为失效,让后续业务跳过它直接取新帧,宁可少算一帧也不要算一个过期结果。

3. 推理任务的调度:NPU 是公共资源,必须排队

数据通了之后,核心矛盾就转移到推理环节。这里有一个 RKNN Runtime 的硬性规则:同一个 RKNN context 不允许被多个线程同时调用rknn_run,否则轻则结果不稳定,重则直接崩溃或使 NPU 掉算力。所以“多业务共享同一个模型”的前提,是必须有一个串行化的推理调度层,把所有请求排成队,一个一个喂给 NPU。

3.1 为什么不能直接多线程调同一个模型

第一次做多任务时我踩过这个坑:在一个进程里创建了 3 个线程,同时调用同一个rknn_inputs_setrknn_run,结果半小时内 NPU 就出现输出张量错乱,后面模型推理速度肉眼可见地变慢。后来查了 RKNN Runtime 的文档才知道,NPU 的上下文是绑定到单个 context 的,多线程并发读写同一个 context 属于未定义行为。

解决办法有两种:一种是给每个业务创建独立的 RKNN context,相当于每个业务一个私有的推理通道;另一种是只创建一个 context,所有业务通过任务队列串行访问。前一种适合不同业务用不同模型的情况,后一种适合多个业务共用同一个模型的场景。我们这个项目里主要业务都复用一个检测模型,所以采用“单 context + 任务队列”的方案,内存占用小,调度也更可控。

3.2 一个可落地的推理队列实现

推理调度器是我后来反复打磨的核心。它的结构不复杂,但几个细节决定了稳定性。整体思路是:外部业务模块只负责构造推理请求,投递到一个队列里,调度器内部的工作线程逐个取出请求,执行 RKNN 推理,然后把原始输出写回请求对象,由各业务的回调去做后处理。

// 简化版推理任务结构 struct InferTask { int task_type; // 0: 人脸检测, 1: 安全帽, 2: 明火 int frame_id; // 当前帧号 uint8_t* input_ptr; // 指向帧池中的共享帧 uint32_t input_size; int priority; // 优先级, 数字越大越先执行 std::function<void(const rknn_output*, int)> callback; }; class InferScheduler { public: void push(InferTask&& task) { std::lock_guard<std::mutex> lock(mtx_); queue_.push(std::move(task)); // 按优先级重排(完整实现可用优先队列) } private: std::mutex mtx_; std::queue<InferTask> queue_; };

调度器构造函数里调用rknn_init加载模型,rknn_query查询输入输出维度,然后启动一个常驻的工作线程。工作线程的伪代码就是不断从队列取任务、调用rknn_run、输出结果,再执行回调。这里有两个关键点:输入张量的内存最好提前分配好,不要在每次推理时重新申请;推理后处理的回调不要放在工作线程里做耗时操作,否则会把后续任务全部堵住,应该把后处理丢到业务自己的线程池。

3.3 多模型场景下的资源切分

如果业务确实需要多个不同的模型,比如除了 YOLOv5s 之外还要跑一个人脸关键点模型,那建议创建多个 context,并在调度器里做“模型分组排队”。每个 context 有自己的队列和工作线程,但全局共享同一份“NPU 权限管理”。RK3588 的 3 个 NPU 核心在驱动层是自动调度的,但我们不能依赖它自动均衡,还是要手动控制每个模型的时间片。

我的习惯是:N 个模型就创建 N 个工作线程,但每个线程默认执行不超过设定好的时间片(比如 100ms),时间片用完就切到下一个模型,防止某个模型长期霸占 NPU 导致其他任务饿死。整体上这是一个非常朴素的“时间片轮转 + 优先级提升”思路,但实测在边缘设备上已经够用了,不需要引入复杂度太高的实时调度算法。

4. 调度策略选型与权重计算

队列和线程只是骨架,真正体现“调度”的是策略。项目一开始我用的是最简单的先来先服务,结果发现一旦明火检测偶尔丢帧,人脸抓拍就把 NPU 排队通道占满,高优任务被低优任务堵死。因此我在调度器里加入了“优先级 + 权重轮询”的组合策略。

4.1 三种常见调度策略的取舍

策略核心思路优点缺点适用场景
先来先服务按请求到达顺序依次执行实现最简单,公平性可接受低优任务可能阻塞高优任务单任务或任务相互独立
优先级抢占高优先级请求插队紧急任务及时响应低优任务可能被饿死明火、急停等强实时业务
加权轮询按预设权重分配推理时间片兼顾延迟和公平权重设置依赖经验多路视频多业务并存的常态

这个项目最终采用的是“加权轮询为主,优先级为兜底”。说白了就是平时大家都按权重排队,但如果监控业务出现了超过 N 次连续未处理的高优先级请求,调度器会临时把它前面的任务往后压,确保关键业务尽快执行。

4.2 权重是怎么算出来的

权重不能拍脑袋定,我有一套基于“目标帧率”的计算方式。假设当前模型单次推理耗时 T(比如 20ms),业务 A 的目标帧率是 25 FPS,业务 B 是 10 FPS,业务 C 是 5 FPS。不考虑其他开销时,每秒钟用于推理的总时间大约是25*20ms + 10*20ms + 5*20ms = 800ms,还不到 1000ms,说明算力是够的,这时候按目标帧率归一化就能得到权重比例 25:10:5,即 5:2:1。

真正执行轮询时,调度器按权重分配“连续推理次数”。权重为 5 的任务一轮最多连续推理 5 次,权重为 2 的 2 次,权重为 1 的 1 次,一轮结束后循环。这样既能保证高帧率业务拿到更多的时间片,又不至于让低帧率业务完全饿着。要注意的是,这个计算要在模型推理耗时稳定的前提下才有意义,所以调度器还要动态统计每次rknn_run的实际耗时,如果某段时间耗时明显上升(比如 NPU 降频了),就要及时调整分配次数,避免时间片被浪费。

4.3 帧率自适应的兜底策略

边缘设备的负载不是恒定的,高温降频、多路视频码流波动都会影响推理耗时。我在调度器里加了一个最简单的自适应逻辑:每隔 5 秒统计一次各业务的平均端到端延迟(从取帧到回调结束),如果某个业务延迟超过目标帧率对应的周期(比如目标 25 帧,周期 40ms),就把它的权重下调一个档位,同时把富余的时间片补给延迟较低的快速业务。这套逻辑不需要复杂的控制理论,就是一个比例积分式的调整,在实测中效果很直观,能让整体视频流畅度稳定在可接受范围内。

5. 实战调优与踩坑记录

调度框架搭好之后,真正的战斗才开始。下面把我在 RK3588 上跑同源多任务时遇到的高频问题、排查思路和最终方案整理出来,这些内容在官方文档里往往找不到,但对做项目的人来说比算法原理更救命。

5.1 NPU 降频与风扇散热的联动问题

RK3588 满载跑 NPU 时发热非常猛,尤其是塞在密闭工业机箱里的设备,不一会儿就触发热限。RK3588 的 NPU 频率受 devfreq 控制,不同板卡路径可能不同,可以用ls /sys/class/devfreq/查看,常见的是类似fdab0000.npu的节点。跑推理时可以监控它的 cur_freq,如果发现频率从 1.0GHz 掉到 500MHz 以下,功耗降了、帧率也必然崩。

# 查看当前 NPU 频率和可用频率 cat /sys/class/devfreq/fdab0000.npu/cur_freq cat /sys/class/devfreq/fdab0000.npu/available_frequencies

我的做法是两层联动:第一层在 dts 里配置好 pwm-fan 策略,让风扇在 NPU 温度超过 60 度时提前介入,而不是等到 85 度才全速转;第二层在应用层把 NPU 负载反馈给调度器,如果检测到 NPU 频率长时间低于最高档位,就要主动降低非关键业务的帧率权重。优先保明火检测和实时预览,牺牲那些可以做慢速分析的统计业务,这样用户体验不会明显下降。另外,读取风扇转速可以看/sys/class/thermal/cooling_device*/cur_state,或者查驱动里暴露的 fan 节点,排查时先确认风扇策略真的生效,否则散热问题会直接表现为推理变慢,很容易被误判成算法问题。

5.2 内存带宽为什么比 CPU/GPU 更早成为瓶颈

多路视频硬解码非常吃内存带宽。4 路 1080p 解码后,每帧 NV12 数据大约 3MB,按 25 帧算每秒就是 300MB 的数据吞吐,这还没算模型输入缩放、输出后处理以及图形叠加的拷贝。当调度器把所有业务都跑起来后,我发现 CPU 占用并不高,NPU 也没跑满,但视频硬解码偶尔会丢帧,问题就出在内存带宽被抢了。

解决思路是把“不必要的数据搬运”砍掉。帧池里保留 NV12 原始帧,模型前处理直接在帧数据上做缩放和归一化,不要先把 NV12 转成 RGB 再缩放一遍;所有业务的后处理输出只保留小尺寸的结果图(比如 320x320),不要为了画框而保存整帧。另外,帧池深度控制在 3 以内,避免解码线程预解码过多帧把内存占住。

5.3 典型问题速查表

现象可能原因排查命令 / 手段解决方案
多路视频画面周期性卡顿内存带宽饱和htop看 CPU 不高但硬解线程丢帧压缩帧池深度,减少格式转换
NPU 推理越来越慢芯片温度过高导致降频cat /sys/class/devfreq/fdab0000.npu/cur_freq观察频率提前开启 PWM 风扇,降非关键业务权重
同模型多线程调用输出错乱共享同一 RKNN context 并发调用日志中出现rknn_run异常改为单工作线程 + 推理队列
某业务长期拿不到推理机会优先级或权重设置失衡打印每个任务的执行次数和等待时长引入最小执行时间片,用加权轮询替代完全抢占
拉流一段时间后 RTSP 断流网络抖动或解码线程阻塞ping网关看延迟,dmesg看驱动错误拉流线程加自动重连,解码和推理彻底解耦
内存持续增长最终 OOM帧池没有正确释放/引用计数错误free -m,持续监控进程 VmRSS检查帧借用和归还路径,设置最大帧数

5.4 调优后的一组参考数据

在我这套 4 路海康 IPC + YOLOv5s 同源多任务的工程里,调优前后的对比比较明显。最开始各业务独立处理时,4 路视频平均只有 12~15 帧,NPU 使用率忽高忽低,CPU 经常冲到 80%,内存 8GB 被吃掉 6.5GB。改成统一拉流、共享帧池、单模型推理队列和加权轮询调度之后,4 路视频稳定在 22~25 帧,NPU 使用率平缓地维持在 80% 左右,CPU 降到 40% 附近,内存占用控制在 4GB 上下。这些数字不同项目之间会有差异,但资源利用率提升的方向是一致的。

写在最后的一点经验

同源多任务调度这件事,做完之后回头看,真正重要的不是调度算法写得有多花哨,而是数据通路和资源边界从一开始就定了规矩。先保证同一路视频只解一次码,同一个模型只加载一份,再谈调度策略。调度器的代码可以很简单,一个队列加一个工作线程就能解决 80% 的问题,剩下的 20% 靠的是在对 NPU、VPU、内存带宽有了充分了解之后做的清醒取舍。

最后分享两个小技巧。第一,所有业务的推理请求一定要收敛到同一个调度器入口,后续要加新业务时只需注册一个任务类型和权重参数,框架不用动。第二,日志里一定要记录每个任务从入队到出队的等待时间,这个指标比帧率更能反映调度健康状况。排查问题时先看等待时间,再去看 NPU 频率和内存占用,基本能快速定位到瓶颈环节。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 10:40:08

[极客大挑战 2019]Upload的个人WP

个人声明&#xff1a; 本人纯小白&#xff0c;对于专业词汇可能表达不清&#xff0c;本文章仅展示个人思路&#xff0c;如有雷同&#xff0c;纯属巧合&#xff08;若有借鉴思路的&#xff0c;会说明&#xff09;。若有错漏&#xff0c;麻烦指正&#xff0c;谢谢。 题目来源&a…

作者头像 李华
网站建设 2026/9/6 10:40:00

Python在嵌入式开发中的真实边界:从MCU到Linux的实践指南

1. 先把“嵌入式开发”拆开看&#xff1a;三个层次&#xff0c;三种答案这个问题如果只给一句“能”或者“不能”&#xff0c;其实都是在误导人。因为“嵌入式开发”这四个字&#xff0c;覆盖的范围实在太宽了&#xff1a;从几毛钱一颗的8位单片机&#xff0c;到跑着完整Linux系…

作者头像 李华
网站建设 2026/9/6 10:38:52

GCOM组合式AI模型:技术原理、实测表现与落地实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 10:38:09

从核心板到量产方案:成都站新品释放嵌入式三大风向标信号

从核心板到量产方案&#xff1a;成都站新品的三个风向标信号做嵌入式这些年&#xff0c;我对厂商巡回技术日的态度一直是"又期待又审慎"。期待的是能一次性摸到最新的核心板平台、看到真实的系统方案&#xff1b;审慎的是有些场次PPT讲完就散场&#xff0c;真正有信息…

作者头像 李华
网站建设 2026/9/6 10:36:32

CMSIS-DSP源码级测评:架构解析、算法审计与工业落地优化

做工业控制或者音频算法这些年&#xff0c;我一直有个习惯&#xff1a;任何要写进固件里的库&#xff0c;不管名气多大&#xff0c;都要把源码翻一遍再决定怎么用。CMSIS-DSP 就是这么一套绕不开的库——ARM 官方出品&#xff0c;Cortex-M 平台上几乎是信号处理的事实标准。FFT…

作者头像 李华
网站建设 2026/9/6 10:36:03

飞凌嵌入式技术创新日成都站前瞻:AI与Linux趋势下的干货解析

飞凌嵌入式技术创新日成都站前瞻&#xff1a;别只盯着奖品&#xff0c;这几波干货才是真正的重头戏 作为一个常年跟嵌入式打交道的老工程师&#xff0c;我平时最怕参加那种“PPT念完、茶歇吃完、袋子提走”的伪技术活动。但飞凌嵌入式这次技术创新日成都站&#xff0c;从流出来…

作者头像 李华