这篇“第18期”其实是我这个香橙派RK3588 yolov5s双路视觉系列里比较关键的一个节点。前三个阶段我已经把两路摄像头采集、YOLOv5s模型转换与单路推理、双路推流都跑通了说白了就是“能用”的状态。但真正把它从实验室Demo变成能7x24小时跑的业务系统会在阶段四卡很久。这个阶段的任务就是给两路视觉做QoS严格优先。这不是路由器里那个QoS而是在RK3588上把NPU算力、CPU核心、内存带宽、网络传输这些有限资源按“重要程度”重新分配确保主视觉路线的延迟和帧率不会被次路线拖垮。很多朋友做多路视觉时都有这种体验单路跑YOLOv5sRK3588轻轻松松五六十帧一旦开两路帧率忽高忽低推理延迟忽大忽小图像偶发卡顿几帧。问题不在于算力不够而在于资源调度混乱。阶段四的QoS严格优先就是解决这个“混乱”的。适合正在做多摄像头目标检测、RTSP流服务器、低速机器人导航的朋友参考尤其是那些想跑双路实时推理又不想上X86主机的场景。1. 阶段四要解决什么双路视觉的“抢资源”问题1.1 “方案二”在阶段四的定义先明确一下我系列里的口径。这个“双路视觉方案二”指的是两路摄像头各自独立跑一个YOLOv5s模型推理结果各自叠加、各自推流两路之间没有逻辑上的主从关系。像双目测距那种“两路共同输入一个模型”属于方案一方案一的QoS难点在模型内部同步不在资源调度方案二恰恰反过来模型本身就两套资源竞争全在系统层面。所以阶段四的QoS严格优先首要目标是保证两路推理进程的资源边界清晰互不干扰。我当前环境是这样的香橙派5RK3588芯片LPDDR4X内存双MIPI CSI摄像头接入YOLOv5s模型经过RKNN-Toolkit2转换为rknn格式int8量化分辨率640x640。推理代码用C写的推流走FFmpeg的RTSP。这套组合在性能上有个特点NPU算力足够跑两路但CPU侧非常容易被“隐藏”的任务吃满比如图像缩放、NMS、编码这些都可能成为QoS的短板。1.2 为什么“严格优先”而不是“公平调度”Linux默认的CFS调度器讲究公平多线程之间尽量均分CPU时间。可公平调度对双路实时视觉恰恰是灾难。打个比方一个食堂有两个窗口A窗口是给VIP客户供餐的B窗口是普通窗口。公平调度就是两个窗口轮流接待每人各排一队可当普通窗口突然来了十几个人的时候VIP客户的等待时间跟着暴涨。严格优先则是给A窗口单独开个小灶B窗口队伍再长也不能影响A窗口。落到RK3588上“普通窗口突然来一堆人”这件事非常频繁。系统后台可能有日志刷盘、SSH连接、DMA中断、网络协议栈处理这些任务优先级高低不一。如果不做QoS它们随时会抢占YOLOv5s推理进程的CPU时间视觉处理的延迟就会抖动。更要命的是RK3588是大小核架构4个大核A76、4个小核A55调度器有时会把推理任务从大核迁到小核运行速度立刻打折。严格优先的第一层就是把这些不确定因素排除掉用CPU隔离任务绑定实时调度给关键进程铺一条专用快车道。1.3 阶段四的QoS范围比你想的更宽我最初以为阶段四就是调几个内核参数把进程优先级拉满就行了。实际做下来才发现QoS严格优先要覆盖四个层面CPU层隔离核心、绑定进程、设置实时调度策略保证推理进程不被随机调度踢走。NPU层两路模型的推理任务分别绑定到指定NPU核避免两个rknn上下文在硬件调度器里互相穿插。内存层控制DMA缓存和零拷贝缓冲区的行为避免内存带宽被图像放大镜/缩放器这类外设抢走。网络层RTSP推流数据包在网卡队列里获得最高优先级主路视频的关键帧不因TCP拥塞而延迟。这四个层面缺一不可。常见教程只讲CPU绑定那是“半套QoS”。真正让双路视觉稳下来的是NPU和网络这两块尤其NPU核绑定是我在这阶段踩坑最多的地方。2. 资源瓶颈逐个盘NPU、CPU、内存与网络2.1 NPU争用两路模型抢同一个硬件调度器RK3588的NPU算力大约6 TOPS内部其实是多核结构不同的芯片版本核数有差异香橙派5这颗常以三核版本出现通过rknn_set_core_mask可以指定某个上下文跑在哪个NPU核上。很多人的误区是NPU反正只有一个我指定核有意义吗有而且意义很大。如果不指定核掩码RKNN运行时默认走自动负载均衡。两个YOLOv5s上下文发过来的推理任务会由NPU内部的调度器自行分散听起来很智能实际表现却是两个推理请求在核间反复切换缓存和权重加载互相干扰单路推理延迟会从稳定的毫秒级变成锯齿状。我做了一组对比测试同样的双路流程默认自动分配时主路推理延迟抖动达到±14ms分别绑定NPU核0和核1之后主路延迟抖动降到±2ms以内。这个实验让我确信严格优先在NPU层面不是把任务“排队”而是物理隔离。还有一个细节容易被忽略模型权重在NPU侧的内存驻留。两个RKNN上下文如果各自加载rknn模型文件权重会各占一份内存使用rknn_dup_context从同一个context复制出第二个推理句柄可以共享权重只增加一份中间张量的内存开销。阶段四做双路时一定要用这个接口我见过在一个8G内存的板子上因此多占1.2G内存的案例还没开始优化就触顶了。2.2 CPU侧的大头不是推理是“附属工作”YOLOv5s在RK3588上做int8推理NPU吃掉绝大部分计算CPU其实很闲。但双路视觉真正吃掉CPU的是三块附属工作图像预处理、NMS后处理、视频编码推流。图像预处理包括resize、letterbox、色彩空间转换这一步如果交给CPU跑每路占用率大概10%-15%NMS在640x640输入下每帧也要消耗几毫秒CPU时间FFmpeg编码RTSP推流更是CPU大户h264软件编码一路1080p30大约要吃2-3个核的占用率。所以双路全开CPU占用轻松过80%而且这些任务之间还会互相干扰。QoS在这里的做法是分优先级排布RK3588有4大核4小核我让小核专门处理网络协议栈、SSH、系统日志这类后台任务大核分成两组前两个大核跑主路完整流水线后两个大核跑次路完整流水线。主路的线程优先级更高出现极端负载时次路让路但主路必须保住实时性。这就是“严格优先”的字面含义不是平等分配是关键时刻保一方。2.3 内存带宽一个常被忽略的隐性瓶颈RK3588的内存带宽看起来不小但多路视觉场景里外设DMA和NPU、CPU同时抢LPDDR带宽实际可用带宽远低于理论值。RGARockchip Geometric Processing Unit在做图像缩放时走硬件DMA它搬运的数据量非常大两路1080p30每帧数据量就超过8MBYUV422一秒就是240MB的搬运量。这些DMA操作抢占带宽会造成NPU等待数据反而拖慢推理。阶段四的QoS严格优先对内存层的策略是限制不必要的DMA缓存分配关键路径使用零拷贝。V4L2采集时开启V4L2_MEMORY_MMAP或DMABUF把摄像头帧缓冲直接传给RKNN推理接口中间不经过CPU拷贝RKNN推理前的预处理尽量用RGA硬件加速不在CPU里做多余的内存搬移。这样内存带宽从“多方混战”变成“各走各的高速路”实测也能观察到推理延迟变得更平稳。2.4 网络层RTSP关键帧必须插队双路视觉最终输出是新设备上两路RTSP推流或者一路RTSP加一路本地显示。这两个方案都涉及网络传输。局域网环境里TCP虽然可靠但延迟不稳定。同一块网卡上SSH控制流量、系统更新拉包、另一路视频流的TCP重传都可能挤压主路视频包的排队时间。网络层的QoS严格优先标准做法是在Linux的tcTraffic Control里配置一个优先级调度器让携带RTSP端口号的包走最高优先级队列。底层原理是网卡出队时严格按队列顺序发送高优先级队列有包就先发高优先级高优先级空了才发低优先级。这就是“严格优先”调度对应tc里的prioqdisc。这个配置我放在第3节细说。排查网络问题时还会看到一种现象RTSP主路如果走TCP某个重传包迟迟不确认后续帧全部阻塞。这种情况下我会在主路推流改走UDPRTP配合应用层丢帧重传策略优先保证实时性而不是完整性。严格优先在业务设计上的含义也在这里实时视频场景宁可丢一两帧也不能让整条链路卡死。3. 实操把QoS严格优先一步步落地3.1 第1步内核启动参数隔离关键CPU核心QoS的第一刀切在开机阶段。我给RK3588加了一条内核参数把4、5两个大核从通用调度池里摘出来专门留给主路视觉流水线。因为这两个核不再参与普通任务调度系统后台任务不会随机跑上去抢占推理进程。以Armbian系或Orange Pi OS为例修改启动参数# 编辑启动环境文件路径依固件可能是 /boot/orangepiEnv.txt 或 /boot/uEnv.txt # 在原内核参数末尾追加 # isolcpus4,5 nohz_full4,5 rcu_nocbs4,5参数含义isolcpus4,5把CPU4和CPU5移出调度器均衡范围普通进程默认不会分配上去。nohz_full4,5让这两个核关闭周期性时钟中断减少不必要的唤醒。rcu_nocbs4,5把RCU回调任务从这两个核上摘掉避免内核后台打扰。改完重启后用taskset -pc $$检查当前shell允许的CPU列表如果输出里不再包含4和5说明隔离生效。这时如果直接运行你的视觉程序它反而用不到这两个核因为默认进程不会分到isolcpus的核上。所以我下一步要把关键进程显式绑定上去。3.2 第2步用taskset和chrt锁定主路与次路启动双路视觉进程时给主路和次路分别指定CPU核并设置不同的实时调度优先级# 主路锁定大核4,5使用实时FIFO调度优先级95 taskset -c 4,5 chrt -f 95 ./tracker --stream /dev/video0 --primary 1 # 次路锁定大核6,7使用实时FIFO调度优先级80 taskset -c 6,7 chrt -f 80 ./tracker --stream /dev/video1 --primary 0 注意几点。第一我刻意没用chrt -f 99而是降到95和80。为什么因为chrt -f 99是用户空间能拿到的最高实时优先级一旦程序里某个线程出现死循环整个系统包括SSH都救不了你只能强制断电。95和80这个梯度已经在用户空间绝对优先之列又不会让看门狗和系统管理线程彻底失去运行机会。第二CPU6和7是RK3588的另外两个大核小核留给系统后台。次路虽然优先级低但它仍在大核上跑不会沦落到小核被拖到性能崩盘。如果进程在运行中没法重启也可以对已运行进程直接改调度属性# 找到主路进程PID后执行 taskset -pc 4,5 PID chrt -f -p 95 PID这样做的效果是主路推理进程在CPU4和5上独占运行。系统里其余进程即使再忙也不能从调度器层面抢占这两个核主路的CPU饥饿问题直接消除。但还没完进程内部的多线程还需要进一步处理因为Linux的调度单位是线程不是进程。3.3 第3步关键线程的实时优先级与中断亲和进程绑核只解决“跑在哪”不解决“线程之间怎么排”。你的视觉程序里采集回调线程、推理线程、叠加绘制线程、推流线程都挂在同一个进程下。默认调度策略下它们之间仍然按CFS公平轮转。这样主路推理线程可能被自己进程里的推流编码线程打断延迟照样不稳。我推荐在程序内部对真正的推理线程单独设置调度策略和优先级。C代码用sched_setscheduler实现#include sched.h void set_realtime_priority(int priority) { struct sched_param param; param.sched_priority priority; sched_setscheduler(0, SCHED_FIFO, param); }然后在推理线程入口调用set_realtime_priority(90); // 推理线程高优先级关键点在于只有推理和采集线程设高优先级绘制和推流线程保持普通优先级。我发现很多人把整个进程chrt后所有线程都变成实时FIFO结果高优先级线程在等待NPU结果时低优先级线程反而得不到CPU进程内部出现优先级反转。更合理的做法是推理线程用FIFO 90采集线程用FIFO 85做推流的线程干脆不设实时让它普通调度即可。中断亲和也是个容易踩的坑。如果MIPI摄像头和RGA的中断处理恰好落在CPU4或5上即使任务绑了核这个核也会花时间处理中断主路推理被硬件中断打扰。要把这些中断强制迁移到非隔离核。查中断号cat /proc/interrupts | grep -E mipi|csi|rga | head然后把对应中断的亲和力设置到0-3号核上用一个十六进制掩码0x0fecho 0x0f /proc/irq/中断号/smp_affinity这样CPU4和5几乎只剩推理线程在跑没有背景噪声延迟曲线会变得非常干净。3.4 第4步RKNN多上下文与NPU核绑定这一步是阶段四的重头戏。双路YOLOv5s推理时我在代码里初始化两个rknn上下文并显式指定NPU核掩码。以RKNN C API为例核心代码逻辑是这样的rknn_context ctx_primary, ctx_secondary; // 从同一个模型文件派生第二个上下文共享权重 rknn_init(ctx_primary, model_data, model_size, 0, nullptr); rknn_dup_context(ctx_primary, ctx_secondary); // 主路绑定NPU核0次路绑定NPU核1 rknn_set_core_mask(ctx_primary, RKNN_NPU_CORE_0); rknn_set_core_mask(ctx_secondary, RKNN_NPU_CORE_1);API名称可能随SDK版本变化但逻辑一致给每个推理上下文设置独立的core_mask。这样两个YOLOv5s的int8推理任务各跑各的NPU核互不抢缓存。我用的SDK版本里RKNN_NPU_CORE_0对应0x01RKNN_NPU_CORE_1对应0x02设置时可以按需要组合。Python接口在板端部署时有些版本不直接暴露set_core_mask建议用C API应对生产环境。如果坚持Python可以先查dir(rknn)确认接口名再决定是直接调还是走ctypes。总之NPU核绑定是QoS严格优先的“硬隔离”比进程调度的优先级更有效因为NPU硬件调度器会按你的掩码把任务分到指定核不会再被其他上下文挤占。还有同步方式要调整两路推理不要在主线程里串行rknn_run各开一个推理线程分别持有自己的上下文两个线程之间用信号量做帧率对齐。主路推理线程优先级高次路线程略低这样即使NPU某个核偶发排队也不会传导到主路。3.5 第5步网络层的tc严格优先级队列双路RTSP推流时我给网卡的出队方向挂一个prioqdisc让主路RTSP端口8554的数据包走最高优先级队列。# 创建一个3级严格优先级队列 sudo tc qdisc add dev eth0 root handle 1: prio bands 3 # 默认映射普通流量走队列2最低优先级 sudo tc qdisc add dev eth0 parent 1:3 handle 30: sfq perturb 10 # 主路RTSP数据包目标端口8554走队列1最高优先级 sudo tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip dport 8554 0xffff flowid 1:1 # SSH数据包走中等优先级队列2 sudo tc filter add dev eth0 parent 1: protocol ip prio 2 u32 match ip sport 22 0xffff flowid 1:2这个策略的实际效果是网卡发送队列里一旦出现目标端口8554的包立刻优先发出其他流量必须等RTSP包发完才轮到。局域网里这能显著降低主路RTSP流的延迟抖动。无线网卡或者USB转千兆网卡上效果更明显因为它们底层队列本来就浅容易拥塞。要验证队列是否生效可以开两个会话同时推流再在另一个终端用tc -s qdisc show dev eth0观察各队列的dropped和overlimit计数。正常情况是队列1基本不丢包队列2和3的丢包、超限次数高一些。顺带一提如果你的板子有多网口建议把主路RTSP单独跑在千兆口上次路RTSP和管理流量走另一个口物理层面的隔离永远比逻辑层面的QoS更彻底。4. 实测对比绑定前后的帧率与延迟数据4.1 我这一轮的实测基准下面这组数据来自我手头这块香橙派5模型是YOLOv5s int8 rknn分辨率640x640双路MIPI输入1080p30推流走千兆以太网RTSP。同一硬件、同一模型只改调度配置结果对比很有意思配置主路平均FPS主路P99延迟次路平均FPS延迟抖动全程默认调度5138ms37±14ms仅CPU绑核5817ms42±8msCPU绑核NPU核绑定6111ms52±4msCPUNPUtc网络QoS619ms53±3ms主路和次路同时满载系统里还必须跑着SSH、日志、拓扑管理这些杂活。上面数据里最有价值的不是平均FPS涨了多少而是P99延迟从38ms降到了9ms。P99意味着99%的帧延迟都在这个值以内它是判断实时系统稳不稳定的核心指标。只调平均帧率看不出差别真正让画面“不抖”的是P99降下来了。4.2 为什么次路也会跟着提速绑定NPU核后次路FPS从37涨到42这不是主路让出来的算力而是因为两个rknn上下文不再互相打断。默认调度下NPU内部两个推理任务可能在同一个硬件核上交替执行权重加载、缓存失效频繁发生每次切换都有额外开销。各绑一个核后这两个任务相当于从共用一个工作台变成各自拥有一个工作台中间不需要反复收拾东西总吞吐自然增加。这也解释了为什么我坚持在方案二里给每条路“划地盘”严格优先不是单纯的优先级提升它同时减少了无谓的竞争开销。两个任务各干各的总吞吐反而更高延迟还更稳定。4.3 调参的顺序和判断依据我的建议是始终按“CPU隔离→线程实时→NPU核绑定→网络QoS”这个顺序做。前面三步做好了网络QoS才有意义如果CPU还在被后台任务抢占光给RTSP包加优先级延迟抖动还是会回来。每一步做完都要重新看延迟分布不要只看平均帧率。我用的工具是# 实时查看每个CPU核的占用情况 mpstat -P ALL 1 # 查看RKNN推理线程的调度策略和优先级 ps -o pid,psr,pri,rtprio,cls,comm -p PID # 查看网卡队列统计 tc -s qdisc show dev eth0观察主路推理线程的psr字段是否恒定在4或5rtprio是否为95就说明绑核和实时调度生效了。网卡队列的统计则能确认RTSP包确实走了队列1而不是被塞到默认队列。4.4 帧率上不去的真正瓶颈判断做完QoS后如果帧率还是上不去不要再折腾调度了大概率是采集链路的问题。RK3588双MIPI同时接入时CSI控制器或ISP带宽可能成为新的瓶颈。这时用v4l2-ctl --stream-mmap --stream-count100直接测摄像头裸采集帧率如果单路采集都达不到30fps那就是硬件配置或驱动问题QoS无能为力。排查链路的顺序永远是存储/时钟配置→采集→推理→编码→网络QoS只能优化阻塞点之后的环节解决不了源头供帧不足。5. 踩坑记录QoS严格优先的“坑”比想象的多5.1 常见问题速查我把这阶段遇到和预判到的典型问题整理成了一张速查表按症状排查会快很多问题原因解决绑核后主路FPS不升反降隔离核上没有核间中断亲和仍被后台中断打扰把MIPI/RGA中断smp_affinity设为0x0f整个系统卡死SSH都连不上把整个进程设成SCHED_FIFO 99只对推理线程设实时调度且优先级不高于95两路推理偶尔同帧卡顿NPU核没有绑定两个任务抢同一核设置rknn_set_core_mask隔离NPU核RTSP延迟高但FPS正常没有配置tc队列视频包和SSH/管理流量抢网卡队列配置prio qdisc并给8554端口建最高优先级过滤规则次路画面花屏/丢帧两个上下文各加载一遍权重内存被占满用rknn_dup_context共享权重跑一天后延迟慢慢变大存量网络会话的TCP缓冲区逐渐占满协议栈抢占CPU主路推流改UDPRTP并配应用层丢帧策略5.2 最隐蔽的坑rknn_dup_context的引用计数问题如果主路和次路共用同一个模型权重做dup_context释放句柄时要注意顺序。我在初版代码里先释放了主路ctx结果次路紧接着推理报错原因是共享权重被引用计数归零释放掉了。正确做法是先释放次路ctx等次路推理彻底结束后再释放主路ctx。写代码时把这个顺序固化成注释能省不少半夜排查的时间。5.3 另一个隐蔽坑网络QoS不适用于有线直连交换机的场景必须说清楚tc严格优先是在发送侧排队它解决的是“出口拥塞”问题。如果你的主路网口直连着大缓存交换机下游设备接收侧也有自己的缓冲那么tc的效果会被交换机缓冲“偷走”一部分延迟依然可能抖动。这种场景我更推荐直接给主路网络配置独立的VLAN或者在交换机上配优先级映射把板子标记的视频包优先级同步到交换机转发队列。局域网里完全靠板端tc是不够的端到端QoS才是真正的严格优先。5.4 别忘了CPU散热带来的“软瓶颈”香橙派5跑双路YOLOv5s时大核长期满载CPU温度很容易顶到75度以上。如果风扇策略或散热片不够RK3588会主动降频频率一降你前面所有QoS配置全部白做。我在这个阶段用/sys/class/thermal/thermal_zone0/temp监控温度并给风扇调速脚本设了一个策略超过65度就全速转。这个环节虽然不算真正的QoS配置但它决定QoS能撑多久。硬件过热导致的延迟劣化摆到数据上很难看回头排查时又极其隐蔽。结尾收个尾这一阶段跑完后我的最大感受是QoS严格优先不是“多占资源”而是“消除不确定性”。资源的物理总量没变但每一步资源分配都变得可预期、可度量系统的行为就不再随机。针对双路视觉方案二我总结下来最核心的一句话是先隔离再排队先硬件再软件。隔离CPU和NPU核是硬件层的硬隔离效果远好于软件层的优先级网络层的严格优先排的是发送队列数据包本身不乱撞延迟自然稳定。我这个配置还在持续观察中后续会把它扩展成三路时再向大家汇报。要是你也在RK3588上做多路视觉建议先从CPU绑核和NPU核隔离开始这两步改动最小见效却最明显。