一、npu固件调度的监控方法
NPU的调度如何去优化,一般场景下聊的是上层调度的策略,比如如何规避权重搬运带宽墙,如何拆分计算图等等。
而实际上,NPU固件层的调度效率这个底层的核心瓶颈也很重要,任务下发到流以后,固件内部能否快速高效地执行很关键,固件是直接掌管硬件资源的,这些资源就包括:npu core、dma引擎等等,这些资源是否空闲,固件能直接"看"得见。
比如说,一个3核的npu,固件会不会把任务自动分发到空闲的核心,不要经常出现core0跑满100%, 而core1、core2完全闲置的情况。
原则是,尽量让Npu的core核心有事情可做,不要空转。
那么如何监控这种性能瓶颈就变得很重要了------Npu固件是一个跑在NPU内部微控制器上的实时程序,一般会跑一个简单的RTOS操作系统,那么就存在中断响应、任务切换、资源等待等等行为,那么是否有一个工具可以可视化地监控这些行为呢?答案是有的,笔者推荐一款工具,就是segger提供的sysview这个工具。
SEGGER SystemView 是一款面向嵌入式系统的实时运行行为记录与可视化分析工具,由SEGGER官方推出,能深度还原系统真实运行状态,解决传统断点调试无法捕捉的多线程、中断交互类问题。
核心功能
- 可视化调度时间线:以图形化方式完整展示所有任务、中断、软件定时器的执行顺序,精确到微秒级时间戳,直观呈现谁占用了CPU、谁被抢占、谁在等待资源。
- 系统性能统计:自动生成CPU负载分布、各任务执行时长占比、中断触发频率等统计数据,帮你快速定位系统性能瓶颈,评估峰值负载下的硬件余量。
- 深度事件追溯:完整记录信号量获取/释放、队列读写、任务挂起/恢复等所有RTOS内核事件,精准还原多任务间的交互细节,定位资源冲突、优先级翻转等隐蔽问题。
- 离线回放存档:支持将完整的事件记录保存为.svdat文件,后续可随时离线回溯分析,也能作为系统性能文档存档,方便对比优化前后的效果。
典型应用场景
- 排查偶发系统卡顿、任务饿死、中断响应超时等传统调试手段难以定位的问题。
- 验证实时系统硬实时性指标,测量从中断触发到ISR实际执行的延迟,找到拖慢响应的长临界区。
- 评估系统CPU负载余量,在新增功能前确认硬件资源是否足够支撑,辅助硬件选型决策。
- 优化NPU、MCU等嵌入式固件的调度逻辑,像之前我们聊到的NPU固件调度优化,它就是核心分析工具。
基础运行架构
一套完整的SystemView链路由三部分组成:
- 目标端:将SEGGER_SYSVIEW库代码编译进固件,在系统事件发生时自动写入带时间戳的记录到RAM缓冲区。
- 传输层:通过J-Link的RTT通道(最常用)、UART等方式,把缓冲区数据实时上传到PC。
- PC端上位机:接收数据后解析、渲染成可视化时间线和统计报表,供开发者分析。
二、如何移植
移植sysview是需要花费一些功夫的,不过原理上不难,首先需要去segger官网下载最新版本的代码,然后把segger_sysview.c,segger_rtt.c等代码拷贝到工程就可以了。
然后,我们需要理清楚我们关心的观测点是啥:
-
任务调度与上下文切换追踪 (这里的任务不是指的npu的任务,而是rtos的task)
-
中断进入/退出与 ISR 时间统计
-
门铃(doorbell)/DMA/围栏(fence)/kernel launch等自定义事件,这个就是我们最关心的npu任务执行的部分
-
Mailbox等host和固件通信的 IPC 延迟分析
2.1 操作系统部分
首先针对前两点,任务调度和中断进出部分,sysview的官方代码已经包含了主流操作系统的钩子函数,只需要把segger_sysview_freertos.c拷贝到我们的工程里就可以了。然后,对freertos进行一个配置,主要在freertosconfig.h对一些宏进行打开或者关闭,这里有一些微调的部分,比如说,进入idle task非常频繁,而对此我们并不关心,所以在这里对task的name做一个删选,如果是Idle task进出,就不要产生打印了。

然后,进出中断也是比较频繁的行为,我们并不关心,可以关闭:

2.2 自定义部分
Marker是sysview里比较适合来观测某些事件的持续时间的方法,使用的时候是定义一些自己关心的一些事件,在在初始化的时候,用SEGGER_SYSVIEW_NameMarker做一个声明。
比如,在本例,我们针对doorbell、dma、kernel launch、ipc等消息,我们设置了如下这些namemarker:

然后比如我们要监控kernel launch从开始到结束的整个时间,那么在开始的时候,调用SEGGER_SYSVIEW_MarkStart

在结束的时候,调用SEGGER_SYSVIEW_MarkStop

最后生成的效果就是这样的

三、实战案例
3.1 某次graph kernel测试用例不过
一个简单的case:一个job,包含了三个简单的command buffer,一个是h2d的memcpy的dma命令,一个是kernel launch的命令,一个是d2h的memcpy的dma命令,这个job下发到固件的流以后,由固件执行完返回host,但是host读取返回结果的时候发现与预期不符。
抓取sysview时序以后,很容易就可以分析到问题所在:

从上图可以看到,job done作为固件返回host的一条ipc消息,其发送的时间点过早,(期待中的时间点是三个command buffer都执行完毕才能发送),导致host去读内存中的数据的时候,由于d2h的memcpy还没有完成,数据是不对的。
修改代码中的bug后,再次抓取sysview,看到正确的时序,可以清晰地看到一次包含了两个dma copy和一个kernel launch的job的生命周期::

3.2 kernel launch中断支持
在kernel launch没有支持中断以前,我们的固件是在monitor task中检查完成情况的,所以每次kernel launch都会调度出doorbell task,在以1ms为节拍的monitor task检查到kernel launch完成后,才会继续下一个kernel launch,效率低下,如下图,一个包含8个kernel launch的job就会消耗大概8ms的时间:

而支持了中断以后,一个包含了8个kernel launch的job,总的时间才330us。原因是不再需要调度到monitor task,而由中断直接去唤醒doorbell task的信号量就可以了,这样就很快了。

PS:以上案例只是举个例子,各家NPU的固件代码不是开源的,实现方式也是前差万别,因为固件和你的硬件以及mcu是强相关的,所以会有很大的不同。但是,用sysview这种工具作为分析手段的方法,应该是很有效的。