Day9_用sysfs实测NPU饱和度与DVFS调频
系列上一篇Day8多 batch 推理实测RK3568 的 NPU 真被喂满了吗硬件RK3568正点原子 EVB1 DDR4 V10 板1 核 NPU模型yolov5s 640×640 INT8runtimelibrknnrt 1.5.0一、引子昨天我下了一个错误结论Day8 结尾我写“batch4 只比单帧快 6%说明 NPU 已经饱和。”这句话有一个我没验证的假设——NPU 负载真的到 100% 了吗嵌入式工程师的本能应该是别猜去问硬件。RK3568 的 NPU 驱动在 sysfs 里暴露了负载计数器cat/sys/kernel/debug/rknpu/load# NPU load: 0% - 空载于是我写了个采样线程边推理边读这个节点。结果推翻了昨天的结论还顺藤摸瓜挖出了三个更深的发现。这一天没有写一行 C没有碰一次交叉编译——全部用板端 Pythonrknnlite完成这也是本文的第二个主题不依赖工具链的板端基准测试方法。二、发现一RK3568 的 NPU 是单核一个流传很广的误区起因是我一开始想测NPU 多核调度给 rknnlite 传了 core_maskfromrknnlite.apiimportRKNNLite rknnRKNNLite()rknn.load_rknn(yolov5s.rknn)rknn.init_runtime(core_mask0x07)# 想用 3 个核runtime 直接报错E The core_mask is only supported by RK3588. Please do not set this parameter on other platforms.core_mask 多核绑定是 RK3588 独有的。RK3588 有 3 个 NPU 核心可以 1/2/3 核灵活组合而 RK3568 只有一颗 NPU 核设备树节点是fde40000.npu。网上不少资料把RK35xx 系列 NPU混着说“3 核 NPU”多核并行的说法很多。看芯片手册 实测报错比看博客靠谱。这是本文的第一个坑坑25RK3568 单核 NPUcore_mask参数仅 RK3588 支持在 RK3568 上设置会 init 失败ret-1。三、发现二NPU 负载从来没超过 78%把负载采样线程跑起来50ms 间隔同时用 1/2/3/4 个线程并发跑推理并发实例单次推理(ms)总吞吐NPU load 平均/峰值190.48.1 FPS23.9% / 54%2130.310.5 FPS47.4% / 73%3187.110.7 FPS56.0% / 78%4251.27.3 FPS ⬇53.1% / 77%两个信息NPU 负载峰值只有 78%从来就没有饱和。Day8 的结论要修正。多实例并发到 2-3 个时总吞吐有 1.3 倍收益到 4 个反而退化——说明并发填的是提交间隙单线程时 CPU/NPU 互相等待的空档而不是在压榨剩余算力。顺带说明Python rknnlite 单实例 90ms vs C API 的 60ms差值主要是 Python 侧输出张量拷贝和封装开销。本文的结论看的是相对关系不受绝对值影响。四、发现三600MHz 是谁钉死的——DVFS 调频实验上面所有测试里有个奇怪现象NPU 频率从头到尾钉在 600MHz 不动。查 devfreqcat/sys/class/devfreq/fde40000.npu/available_frequencies# 200000000 297000000 400000000 600000000 700000000 800000000 900000000cat/sys/class/devfreq/fde40000.npu/governor# rknpu_ondemand7 个档位最高900MHz出厂 governor 是rknpu_ondemand——按负载升频。但负载计数器峰值才 78%governor 的升频阈值估计在更高处所以永远够不到 900MHz。写了个 A/B 脚本基线跑一遍 → 切performance强制 900MHz 再跑一遍 → 自动恢复原设置。Python间歇负载baseline (rknpu_ondemand, 600MHz) avg 82.91 ms 12.1 FPS performance (900MHz) avg 69.74 ms 14.3 FPS 提升 15.9%C连续负载有意思的来了——C 版在默认 governor 下自己就升到了 900MHzC baseline (rknpu_ondemand→900MHz) NPU avg 50.69 ms 19.7 FPS C performance (900MHz) NPU avg 51.06 ms 19.6 FPS为什么 C 能升频而 Python 不能因为 C 的推理是背靠背连续的负载计数器读数高Python 每次推理之间有 ~28ms 的宿主侧空隙负载计数被稀释——governor 看到的是半闲的 NPU于是不给最高频。这里藏着一个对做基准测试的人很重要的教训坑26DVFS 设备上做 benchmark必须先echo performance .../governor钉死频率。否则你测的不是算法/硬件的性能而是调度器觉得你配用多少频率。同一份代码负载形态不同连续 vs 间歇实测性能差 16%。另外顺手验证了/sys/kernel/debug/rknpu/load这个计数器本身也不可靠900MHz 满速推理时它平均只报 9.7%跑完空闲时反而报 75%。采样的负载值只能当参考别当证据证据是墙钟时间。五、终极实验频率 x 并发的完整矩阵最后把 performance 档下的多实例并发也跑了凑齐 2×4 矩阵并发实例600MHz 单次/总吞吐900MHz 单次/总吞吐190.4ms / 8.166.2ms / 9.22130.3ms /10.5(1.30x)112.7ms /10.6(1.15x)3187.1ms /10.7(1.32x)161.7ms /10.5(1.14x)4251.2ms / 7.3 ⬇205.8ms / 8.6 ⬇这张表里最有价值的是加粗的那一列总吞吐的天花板在 600MHz 和 900MHz 下都是 ~10.5 FPS纹丝不动。把频率提高 50%单实例延迟明显改善90→66ms但总吞吐一动不动——这说明多实例并发的瓶颈根本不在 NPU 计算单元而在 DDR 带宽或驱动层的串行化。yolov5s 每次推理要在 DDR 里搬运约 30MB 的权重激活值多实例并发时大家抢的是同一条内存总线。六、结论修正Day8 那句话应该怎么说Day8 的说法错Day9 修正后对单帧 60ms 的本质“NPU 饱和了”这是本 BSP 上该管线的延迟地板900MHz、C 连续负载与 NPU 利用率无关batch4 只 6%“NPU 没有摊销空间”batch 摊销掉的是提交间隙但带宽压力不降所以收益有限正确的优化方向—单路追延迟锁频连续提交多路追吞吐上限 ~10.5 FPS 等效撞的是内存墙这套用实验修正自己昨天结论的过程比结论本身更值钱。面试时它对应的是一类高频问题——你怎么定位性能瓶颈标准答案不是背名词而是这样的方法论“先找到硬件的观测接口sysfs 计数器、devfreq、trace让数据说话发现矛盾load 78% 但性能不再提升就设计对照实验锁频 A/B最后用’改 X 不改 YY 没动’的方式归因频率50% 吞吐不动 → 带宽瓶颈。”七、复现方法板端直接跑无需交叉编译三个脚本都在仓库 python/ 目录scp 上板即可python3 bench_npu_core.py /root/yolov5s.rknn20# 单实例延迟统计p50/p95/min/maxpython3 bench_npu_satur.py /root/yolov5s.rknn15# 1-4 实例并发 load/freq 采样python3 bench_npu_gov.py30# governor A/B 对比自动恢复原设置bench_npu_gov.py的安全设计值得抄跑之前先读出原 governor/min_freq/max_freq 存起来实验完无条件写回异常退出也不至于把板子留在 performance 档白耗电。八、30 秒面试话术「我在 RK3568 上做过一次 NPU 饱和度的系统排查最初多 batch 只提升 6%我判断是 NPU 饱和后来用 sysfs 负载采样发现峰值只有 78%继续查 devfreq 发现出厂 governor 把频率钉在 600MHz最高 900MHz。锁 performance 档后单实例提速 16%但多实例总吞吐在两个频点下都卡在同一个值——说明并发瓶颈是 DDR 带宽不是 NPU 算力。这件事让我形成了一个习惯DVFS 设备上做基准测试先锁频性能归因必须用对照实验不能拿一个反常数据直接下结论。」写在最后明天后计划做 RKNN 多路视频流的工程化既然并发吞吐上限是 10.5 FPS 等效多路场景就要认真做跳帧/降分辨率权衡了。本系列所有代码与数据都在 GitHub 仓库欢迎取用。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →