1. 算力预算先算明白:6 TOPS 是怎么被三个任务分光的
先说结论:单块 RK3588 的 NPU 标称 6 TOPS 算力,但这是 INT8 下的理论峰值,实际能稳定使用的算力大约在 4.5 TOPS 到 5.2 TOPS 之间。别被宣传页上的数字骗了,跑真实模型时,访存带宽、算子调度开销、中间层张量搬运都会吃掉一部分算力。我第一次用 RKNN-Toolkit2 做性能评估时,本以为 YOLOv8s 加两个分类模型绰绰有余,结果一跑起来直接卡成幻灯片,后来才意识到问题出在算力分配策略上,而不是模型本身。
先给三路任务做一个资源画像。人员入侵检测属于连续视频流分析场景,要求高帧率、低延迟,模型不能太小否则误报漏报都会很严重;烟火检测同样是视频流场景,但对帧率的要求可以适当放宽,毕竟火苗和烟雾是持续变化的状态,每秒分析三帧到五帧足够覆盖大多数情况;垃圾分类属于静态图像识别场景,没有连续帧的概念,通常是有人把垃圾放到摄像头下方才触发识别,所以它对实时性的要求反而是最低的。
RK3588 的 NPU 实际上由三个独立的核心组成,每个核心支持异步任务提交,这意味着你可以把三个模型分别绑定到不同的 NPU 核心上,让它们物理隔离地并行执行,而不是抢同一个核心的调度时间片——这是单块 RK3588 能够同时跑三个任务的前提条件。用官方文档的话说,NPU 支持多模型并发,但前提是内存和调度策略得当,否则三个模型提交到同一个核心上,算力再足也会因为上下文切换产生大量性能损耗。
我最终确定的算力分配方案是这样的:人员入侵使用 YOLOv8s,输入分辨率 640x640,INT8 量化后单帧推理约 35ms 到 45ms,分配给 NPU 核心 0;烟火检测使用 YOLOv8s 但降采样到 480x480 输入,单帧推理约 22ms 到 30ms,分配给 NPU 核心 1;垃圾分类使用轻量化分类网络 MobileNetV3-Large 或 RepVGG-A0,输入分辨率 224x224,单帧推理大约 5ms 到 8ms,几乎不占多少算力,分配给 NPU 核心 2。这样分配下来,三个核心各有明确分工,互不干扰。
这里的关键点是,RKNN-Toolkit2 生成的模型默认不做核心绑定,你需要显式指定模型运行在哪个核心上。这个操作在 rknn.config 里有一个 npu_core_id 参数,可以传入 0、1、2 或者 RKNN_NPU_CORE_AUTO。很多人忽略了这一点,所有模型都采用 AUTO 模式,结果三个模型被依次调度到同一个核心上排队执行,白白浪费了另外两个核心的算力。我踩过这个坑,后来在命令行工具里加了 --npu_core_id 参数才把调度问题解决,实测并发推理吞吐量提升了将近三倍。
还有一点值得注意,NPU 核心绑定的粒度是推理会话级别,不是请求级别。也就是说,你需要在初始化阶段就把 RKNN 上下文和核心 ID 绑定好,推理阶段直接调用即可。如果每个请求都去创建和销毁 RKNN 上下文,不仅核心绑定会失效,还会因为反复加载模型产生不可忽略的延迟。
2. 三路视频接入与硬解码:NPU 再快也怕数据排队
算力规划做完之后,紧接着要解决一个很容易被忽视的问题:三路任务的数据从哪来,以什么格式进来。RK3588 有强大的 VPU 硬解码能力,但如果你只是简单地从 OpenCV 的 VideoCapture 读 RTSP 流,默认是走软解,ARM 核会被拖得动弹不得,而 NPU 却在旁边闲等数据。这类视频监控系统往往从 IP Camera 拉流,协议以 RTSP 为主,我们需要做的是把视频流与 VPU 硬解通道对接起来。
RK3588 上的硬解码能力很出色:H.264 和 H.265 都能应对,最高支持 8K30 或者 4K120 的解码能力,常规场景下同时解三路 1080p30 流可以说是毫无压力。但问题是,OpenCV 默认的 FFmpeg 后端通常在 ARM 平台上走软解,不会主动调用硬件解码器。我见过很多开发者在 RK3588 上跑视频 AI,CPU 占用率直接冲到 80% 以上,结果 NPU 占用率还不到 20%,查了半天最后发现是视频解码环节吃光了所有资源。
解决思路有两个方向。第一个方向是用 rockit,也就是 Rockchip 官方多媒体框架,它直接对接 VPU 硬件解码器,通过绑定帧缓冲的方式把解码后的数据零拷贝送入 NPU 的输入张量。第二个方向是使用 GStreamer 的 rkmpp 插件,配合 opencv-python 的 gstreamer 后端来拉流,也能借助硬件解码能力。这两个方案实测下来都能把 CPU 占用率压到 15% 以下,但 rockit 的集成成本更低,而且与 RKNN 的 buffer 共享机制更顺畅。
如果你选择 rockit,流程大概是这样:初始化 rockit 系统,创建视频输入通道绑定 RTSP 源,设置输出格式为 NV12,然后从通道获取帧数据。需要特别注意的是,rockit 的输入源不带内置的 RTSP 拉流能力,你需要先通过 FFmpeg 库把 RTSP 流转成 raw H.264 或 H.265 码流,再喂给 rockit 的码流输入通道。这一步我建议直接用 FFmpeg 的命令行工具配合 FIFO 命名管道来做,简单可靠。
数据格式转换是整个 pipeline 里最容易被低估的环节。VPU 解码输出的是 NV12,而 RKNN 推理需要的输入格式取决于你在转换模型时指定的量化校准格式,一般是 RGB 或 BGR。如果模型中配置了输入格式为 RGB,那么在喂数据之前就要完成 NV12 到 RGB 的色彩空间转换。这不只是格式名称的差别,涉及色域转换和内存重排,如果处理不当,识别的颜色会偏到离谱——尤其烟火检测对颜色非常敏感,火焰的橙红色一旦被误转成偏绿的色块,结果就没法看了。
为了减少格式转换的开销,我在两个环节做了优化。一个是尽量让模型输入格式统一为 NV12,但这个前提是 RKNN-Toolkit2 里的目标检测模型支持 NV12 输入,实测部分模型支持得不好,所以我最后选择在解码后做一次短的 DMA 搬移加格式转换,在 C 语言层面用 RGA 硬件加速器完成,这部分消耗非常小。另一个优化是将多路视频流的采集与推理线程解耦,采帧线程只负责把解码后的帧放入环形缓冲区,推理线程从缓冲区取帧,避免因为 NPU 推理耗时导致丢帧,也避免采集阻塞影响后续帧的实时性。
实用建议:如果你刚开始跑通链路,先用单路流把全流程验证清楚,再扩展到三路。直接上三路时,环形缓冲区的大小一定不能太小,我用了每路 30 帧的容量,取流线程和推理线程之间用信号量做同步,避免忙等造成 CPU 空转。摄像头掉线、RTSP 断连这类异常情况也必须考虑,rockit 的通道在码流中断后不会自动恢复,需要在上层加一个链路健康监测线程,检测到超时未收到帧就重启对应通道。
3. 模型选择与量化策略:同样的 YOLOv8s,被我用出了三种用法
三个任务的输入数据格式都确定之后,接下来的核心工作是模型选型和量化。RK3588 的 NPU 对浮点模型支持有限,必须要转换为 RKNN 格式,而且为了发挥最大性能,通常需要使用 INT8 量化。量化是有精度损失的,损失多少取决于校准数据集的选择和量化策略,这里面有不少门道。
先说说我为三个任务分别选模型型号的原因。人员入侵检测的核心诉求是稳定检测到人形目标,室内外都有可能,夏季穿浅色衣服和背景融为一体的情况最容易漏检,所以我选了 YOLOv8s,COCO 预训练权重对行人这一类别的召回率表现均衡,量化后 MAP 损失控制在 2% 以内可以接受。烟火检测比较特殊,COCO 本身没有 smoke 和 fire 这两个类别,需要自己收集数据微调。我用 YOLOv8s 在自建的烟火数据集上微调了 80 个 epoch,数据集大概 1.2 万张图片,包含白天、夜晚、室内、室外、远距离、近距离等各种光照和尺度情况。垃圾分类则不需要目标检测的定位能力,直接使用 MobileNetV3-Large 在华为的 garbage-classify 数据集上微调,30 个类别的分类准确率可以做到 95% 左右,关键是模型非常小,推理快,不占用太多 NPU 资源。
量化校准集的选择是精度保障的重中之重。校准集不需要很大,但要足够有代表性。我见过不少朋友直接把训练集的验证集拿来量化,结果验证集和真实场景数据分布差异大,量化后精度崩得厉害。我在做烟火检测模型的量化时,校准集专门选了一段 50 帧的合成视频,包含火焰、烟雾、树木阴影、日落天空和红色建筑物等易混淆场景,每一帧都覆盖了模型会遇到的典型颜色分布。量化后实测对比,正检率从 91% 下降到 88%,漏报率基本没有上升,在可接受范围之内。
量化为 INT8 之后,还有一个容易被跳过但很关键的步骤:检查每层激活函数的数值范围。RKNN-Toolkit2 会在量化报告中给出每一层的量化参数,包括 scale 和 zero_point,如果某一层的激活值范围分布过于集中,比如全部集中在 0 附近,那么量化后的信息损失会很大。遇到这种情况,解决办法是在该层之后插入一个 Clip 操作,把激活值范围压缩到合理区间,量化效果会明显改善。这个方法来自我在调试垃圾分类模型时的实测,一个 224x224 输入的分类网络在量化后 KL 散度量化的精度损失高于预期,通过检查各层量化参数发现倒数第二层全连接层的激活值分布严重不均匀,手动加了一个 clip 之后精度回升了 1.8 个百分点。
用 RKNN-Toolkit2 转换模型时,有几个参数值得认真对待。target_platform 要指定为 rk3588,不能再用 rk3568,虽然 RKNN 工具链向下兼容,但编译时针对不同芯片的算子优化差异很大。optimization_level 建议设置为 1,这一级会做算子融合和权重重排,性能提升明显,但不会引入对精度有影响的激进优化。quantized_dtype 使用默认的 asymmetric_quantized-8,这是 RK3588 NPU 最擅长处理的量化格式,比 symmetric 的精度普遍要好。
在实际部署前,我强烈建议做一次 batch 为 1 的单帧推理延迟测试,确认每个模型在给定输入尺寸下的推理耗时是否和预期一致。测试结果如果和我的经验值偏差超过 30%,建议检查是否存在数据格式转换瓶颈,或者模型在转换过程中有没有退化到 CPU 上执行——这种情况通常是因为模型中包含了一些 NPU 不支持的算子,比如某些版本的 Silu 激活函数或者动态 Resize 操作。当模型被部分或全部回退到 CPU 执行时,推理延迟会直接比纯 NPU 慢五到十倍。
4. 多模型并发调度的核心机制:rknn_api 里那些决定成败的函数调用
理论模型选好、量化做完之后,进入实际编码阶段。这一节讲 RKNN 多模型并发调度的底层机制,也是单块 RK3588 能不能真正同时跑三个任务的关键所在。我重点讲 rknn_api 库的调用方式,因为官方 RKNN-Toolkit2 的 Python 接口在多线程并发场景下使用体验一般,性能损耗也比较明显,C API 才是更接近底层的选择。
先明确一个概念:RKNN 上下文(rknn_context)与模型是一一对应的。你要同时跑三个模型,就需要创建三个独立的 rknn_context,不能在一个 context 里重复加载多个模型。创建上下文的函数是 rknn_init,你需要传入一个 context 指针、模型路径、flag 以及回调信息。在这里有个细节:flag 参数中有一个 RKNN_FLAG_ASYNC_MODE,这个标志位决定了推理是异步还是同步。在三个模型并发执行的场景下,必须开启异步模式,否则即使你创建三个线程分别调用推理接口,最终也会被内部的同步锁串行化,达不到并行效果。
开启异步模式之后,核心的执行路径变成了:rknn_run 提交推理任务,然后立刻返回,任务在 NPU 内部排队执行;之后调用 rknn_outputs_get 获取输出结果,这个函数会阻塞等待 NPU 完成推理。在实际使用中,我维护了一个两级的缓冲池,每一路视频流都有一个输入缓冲和输出缓冲,推理线程循环执行"从输入缓冲取最新帧 → 填充 input 结构体 → rknn_run 异步提交 → 处理上一次提交的 rknn_outputs_get 结果"。这样做的目的是让 NPU 和 CPU 的流水线尽量重叠,CPU 处理上一帧结果的同时,NPU 已经在算当前帧了。
这里有一个非常容易被踩的坑:rknn_outputs_get 并不是纯粹阻塞等待,它有一个超时时间参数。如果在超时时间内 NPU 没有完成推理,函数会返回错误。实际使用中,我把超时设置成单模型推理耗时的三倍,比如烟火检测模型推理耗时约 30ms,那么超时就是 90ms,这样即使偶尔调度抖动,也不会误判为推理失败。如果你把超时设成 -1 表示无限等待,表面看稳妥,但一旦 NPU 核心卡住(比如模型加载不完整),整个线程就永久阻塞,其他路任务也跟着报废。
还有一个并发场景下容易忽略的问题:共享内存。三个模型的输入输出数据可能占用的内存总量相当可观,尤其在同时处理 640x640x3 的输入时,一个输入张量就有 1.2MB。三个模型加上中间层的运行内存,峰值内存占用可能达到 300MB 以上。在 RK3588 的开发板上,内存容量从 4GB 到 16GB 不等,如果运行的是 4GB 版本的系统,同时还跑着其他业务进程,内存就非常紧张了。我实际测量过,三个模型同时静态加载后,占用的内存大概是 450MB,其中权重和中间张量是大头。建议在启动阶段把所有模型加载完成、内存分配完毕后再开始拉流推理,避免运行中动态分配内存引发不可预测的失败。
内存分配还有一个细节:rknn_init 加载模型时会自动申请模型权重和中间张量所需的内存,这部分内存是连续的物理内存,不可以被系统回收或者 swap,所以内存压力会直接传递到系统层。因此在部署到实际板子之前,务必用 free 命令观察启动前、模型加载后、推理运行中三个阶段的内存和使用量变化,确保余量充足。我遇到过模型加载成功后一跑推理就 OOM 的情况,最后发现是板子上其他服务占用了太多匿名内存,把 NPU 需要的连续内存挤爆了。
5. 线程拓扑与显式调度:让三路任务真正各走各的
模型并发机制清楚了,代码也写到了能跑通的程度,但这时候整体性能往往并不理想。原因很简单:三路任务各自有独立的推理线程和采集线程,如果线程数太多、优先级设置不合理、CPU 绑核策略不对,ARM 端的调度开销会抵消掉 NPU 并行带来的收益。我们做个大致的计算:每个模型需要采集线程和推理线程,两个线程之间还有一个后处理线程,用于解码坐标框、执行 NMS、映射分类结果,加起来就是至少九个线程在抢 CPU 核心,不梳理线程拓扑的话,光是线程切换就能让有效算力下降四成。
我在项目里采用的线程方案是:三路任务各自独立,包括采集线程、解码线程、推理线程和后处理线程,总共十二个线程,外加一个主控线程负责调度和状态汇报。RK3588 的 CPU 是八核架构,由四个 Cortex-A76 大核和四个 Cortex-A55 小核组成。大核性能强,适合放推理线程和后处理线程;小核省电,适合放采集线程和格式转换线程。
具体绑核策略我可以直接给参考:人员入侵和烟火检测的推理线程各绑定一个大核,用 pthread_setaffinity_np 指定 CPU 0 和 CPU 1;垃圾分类的推理线程绑定到 CPU 2,因为这个模型的推理耗时本来就短,不需要独占最强核心;采集和格式转换线程放在小核 CPU 4 到 CPU 7 上;主控线程放在 CPU 3 上,不参与具体计算,只做任务协调和状态上报。还有一个隐藏得很深的点:每个推理线程的优先级建议设置为 SCHED_FIFO 实时调度策略,优先级设为 50 左右,这样当多个线程同时就绪时,NPU 提交请求的顺序会按照线程优先级来,避免低优先级的烟火检测抢在人员入侵之前提交,导致人员入侵的延迟升高。这里需要特别说明,实时调度策略有风险,如果不小心把某个线程的优先级调得过高,且该线程内部出现死循环或者长时间阻塞,可能饿死其他线程甚至整个系统卡死。所以上线之前必须做压力测试,确保每个线程的循环逻辑都能正常退出。
线程之间的数据传递方式也值得统一设计。我采用无锁环形队列(ring buffer)作为帧数据的跨线程传递媒介,队列元素是一个结构体,包含帧的指针、时间戳、帧序号和宽高信息。环形队列的大小按前文说的设为每路 30,生产者和消费者之间用原子变量维护头尾索引,避免加锁带来的额外开销。这个方法在单生产者单消费者的场景下是安全且高效的,实测一趟完整的帧传递延迟不超过 0.2ms,远低于 CV 系统中常见的互斥锁加条件变量方案。
还有一点需要说明:不要用全局变量在多个线程之间传递推理结果。不同任务的后处理线程各自维护自己的结果链表,主控线程通过一个轻量的发布订阅机制获取三路结果,然后统一上报到业务平台。这样做的原因是后处理的耗时和逻辑自由度较大,如果用共享变量,主控线程不得不频繁轮询,CPU 占用会显著上升。我最初就是用了共享变量轮询,CPU 占用到了 30% 以上,改成发布订阅之后直接降到 8%。
6. 实测数据与瓶颈定位:跑通不代表跑稳,压测之后才算数
所有代码都写完、模型都加载成功、三路任务都能出框之后,我并没有急着上线,而是做了一轮完整的性能压测。压测的目标不是"能跑",而是要搞清楚三路同时运行时的真实帧率、延迟分布、CPU 占用和内存水位,以及哪些环节会是瓶颈。这轮压测帮我发现了一个在单路测试时完全不会暴露的问题:三路并发时,人员入侵的推理延迟从单路的 35ms 突然增加到 90ms。
最初以为模型被人为串行化了,于是查 NPU 核心绑定状态,确认三个模型分别绑定了不同的核心。后来又查了线程优先级,发现优先级设置被上层多线程库覆盖了。然后我做了系统级的性能采样,用 perf 工具查看每个线程的状态,发现人员入侵的推理线程大部分时间处于阻塞等待状态,而不是在计算中。顺着这个线索查下去,发现原因是:rknn_outputs_get 的等待逻辑里,我传入的超时值被误写成了 10ms,而不是设计时的 90ms,导致当 NPU 调度出现瞬时波动时,推理线程频繁超时退出,然后重新提交推理,反而加剧了 NPU 的排队压力。这就是典型的"参数错误掩盖了真实性能"问题,修正超时参数后,延迟恢复正常。
压测数据我记录了一套完整的基线,分享出来供参考。人员入侵检测,640x640 输入,帧率约 22 FPS,推理延迟均值 38ms,P95 延迟 51ms;烟火检测,480x480 输入,帧率约 30 FPS,推理延迟均值 28ms,P95 延迟 36ms;垃圾分类检测,224x224 输入,触发式请求,推理延迟均值 7ms,P95 延迟 9ms。此时 CPU 总占用率约 42%,内存占用 1.1GB 左右(系统加应用程序),NPU 负载因子约 55%。这套参数意味着三路任务同时运行还有相当余量,如果后续要增加一路人脸识别或者车牌识别任务,还有一定扩展空间。
瓶颈定位方法论也总结一下。发现性能不达标时,按照以下顺序排查可以少走弯路:先看 NPU 占用率和推理延迟,如果不高说明问题在数据准备环节;再看 CPU 占用,如果采集或者格式转换线程 CPU 占用过高,问题在视频处理环节;再用 perf top 看内核热点,如果 sys 占比高往往说明内存拷贝过多,要考虑零拷贝方案;最后看内存水位和 swap 情况,内存压力大会导致匿名内存回收频繁触发,间接拉高推理延迟。按照这个顺序排查,我遇到的性能问题大概有一半出在数据格式转换,两成出在超时参数配置,剩下三成是内存和调度问题。
7. 散热与稳定性:双风扇设计、温控降频和七天七夜跑测
系统的软件部分跑通了,但还有最后一个关卡,也是硬件部署中最容易翻车的环节——散热。RK3588 的 NPU 在进行三个模型同时并发推理时,性能功耗相当可观,芯片结温可能轻松冲破 80 摄氏度。如果散热设计不到位,RK3588 会在高温阈值处触发降频,NPU 的推理速度会明显下降。我首次做七小时连续压测时,就碰到了典型的降频现象:前两小时帧率稳定,第三小时开始人员入侵检测的推理延迟从 38ms 缓慢爬升到 60ms 甚至 70ms。
检查系统日志发现"thermal throttle"信息,CPU 和 NPU 都受到了温控策略限制。这时候我才意识到,高算力并发的场景下,散热是整个系统稳定性的前提。我最终采用的是双风扇主动散热方案,一个朝向 NPU/SoC 区域吹风,一个负责机箱排风,配合一块大面积铝制散热片直接贴敷在芯片上,涂覆导热硅脂。实测改造后,连续满载运行两小时,芯片结温稳定在 65 到 72 摄氏度之间,没有再触发降频。
如果你要自行组装配件,建议关注 PCIe 或 USB 接口的可调速风扇,这样可以通过 sysfs 接口读取温度,根据温度动态调节转速。RK3588 读取 Soc 温度的标准路径是 /sys/class/thermal/thermal_zone0/temp,读取出来的值除以 1000 就是摄氏度。PID 温控策略我直接给出参考设定:目标温度 60 摄氏度,温度低于 55 度时风扇最低转速;55 到 70 度之间转速线性上升;超过 70 度转速拉满且触发告警。转速范围的设定需要结合风扇的实际 PWM 范围,我用的风扇是 0 到 255 的 PWM 值,最低转速设为 80,避免过低导致风扇停转。
七天七夜的稳定性测试我是按这个标准来做的:三路视频流持续输入,同时模拟随机中断再恢复,每两小时重启一次垃圾分类检测服务,每六小时轮换一次 RTSP 流的输入源,验证不同 IP Camera 之间切换时系统是否稳定。整个测试期间,人员入侵检测没有出现超过 500ms 的延迟尖刺,烟火检测没有出现超过 2 秒的检测空白,垃圾分类服务的重启恢复时间在 3 秒以内。这期间还特别观察了内存泄漏情况,用 smem 工具每小时记录一次内存水位,确认 RSS 没有持续增长,整体内存曲线平稳。
稳定性测试中还遇到一个有意思的问题:部署现场出现了偶发的模型推理结果错乱,也就是某一帧的输出张量内容莫名变成了另一帧的内容。排查了很久,最后发现问题出在环形缓冲区头部索引和尾部索引的更新顺序上,生产者先更新尾部索引、后写入帧数据,导致消费者读取到半写的帧。解决方案很简单,交换更新顺序即可,也就是保证数据完整写入后再更新索引。这类问题在长时间运行系统中尤其隐蔽,又是那种"跑一小时没事、跑一天必现"的类型,建议做压测时一定要加入这种长时间稳定性测试。
8. 还可以再压榨的余量:下一步优化方向与经验收束
当上述整个系统进入稳定运行状态之后,我开始思考还能从哪些方向继续压榨性能。这块 RK3588 的算力余量实测还有大约 45%,这意味着如果你有新的业务需求,比如再增加一路流或者接一个轻量级的模型,不需要动现有架构,只要按我前面说的方式新增一个模型上下文并绑定到空闲的 NPU 核心上即可。但要注意的是,新增任务的数据获取和预处理开销同样会占用 CPU,所以在 CPU 占用率已经接近 60% 到 70% 的情况下,新增任务前需要重新评估 CPU 侧的负载。
模型层面的优化也可以继续深挖。我目前用的 INT8 量化是比较常规的做法,如果你想进一步提升推理速度,可以考虑使用混合量化,也就是把部分对精度不敏感的层用 INT4 量化,推理速度可能再提升 20% 到 30%。但混合量化对模型精度的影响更难控制,需要做更细致的逐层验证,不适合精度要求极高的烟火检测这类安全相关任务,但垃圾分类这种对模型精度变化不那么敏感的任务可以尝试。另一个方向是模型结构本身的轻量化,把 YOLOv8s 的主干网络换成更轻的骨架,比如 GhostNet 或者 ShuffleNetV2,在检测精度损失可接受的范围内能够有效降低 NPU 计算量。不过这一步需要重新训练和调参,成本不低,建议在业务需求明确、线上数据积累足够的阶段再来考虑。
系统层面还有一个容易被忽略的优化点:把三路视频流的预处理全部交给 RGA 硬件加速器,比如缩放、裁剪、颜色格式转换这些操作都由 RGA 完成,完全不占 CPU。我在最初的实现中把颜色转换放在了 CPU 上,后来迁移到 RGA 之后,CPU 占用率又下降了大约 8 个百分点。如果你手头有多个 RK3588 需要组成集群,还可以考虑通过 PCIe 或者千兆网口把多块开发板组成分布式的推理集群,用负载均衡策略把不同场景的视频流分配到不同板卡上,实现更大规模的视频 AI 分析系统。
最后讲一个项目管理层面的经验。这类多任务并发的嵌入式 AI 项目,最忌讳的就是"单路先跑通再改并发"和"直接上全功能大集成"两种极端。建议按照"单路单模型 → 单路三模型 → 三路单模型 → 三路三模型"这样的渐进路径推进,每一步都做好性能记录和基线比对,确认没有性能回退后再进入下一步。我在这个项目里因为跳过了"单路三模型"这一步,直接从单模型跳到三模型,结果并发隔离的问题排查了整整两天,如果按部就班来,这个坑是可以提前暴露的。
在实际开发过程中,我还有一个习惯:所有与性能相关的参数,包括超时时间、缓冲区大小、线程优先级、NPU 核心 ID、温度阈值,全部集中在一个 JSON 配置文件里,不在代码里写死。这样可以避免上线之后为了调参还要重新编译整个工程的尴尬。毕竟对嵌入式设备而言,每次重新编译、打包、刷机、部署至少半小时起步,而配置文件的修改可以做到秒级生效。把这些细节一点点抠完,整个系统才算是真正达到了可以交付的状态。