180、NPU的编译器开发:火焰图与调用图分析
上周五晚上十一点,我在调试一个NPU编译器的性能问题。模型是MobileNetV3,在目标芯片上推理延迟比预期高了40%。直觉告诉我问题出在算子调度上,但具体是哪个环节拖了后腿,光靠看日志根本抓不住重点。
我打开perf记录了一轮推理的采样数据,生成火焰图的那一刻,一个异常宽大的“栈顶”赫然在目——tile_scheduler_assign函数占了将近35%的CPU时间。这个函数负责将计算任务切分到NPU的各个计算单元,按理说不该这么重。顺着调用链往下挖,发现每次调度都在重复计算内存对齐参数,而这些参数在模型编译阶段就已经确定了。
这就是我今天想聊的——在NPU编译器开发中,火焰图和调用图到底该怎么用,才能从“看起来有道理”变成“真能解决问题”。
火焰图不是用来“看”的,是用来“扎”的
很多工程师拿到火焰图,习惯性扫一眼最宽的色块,然后说“哦,这里最耗时”。这跟看热闹没区别。火焰图的真正价值在于定位异常宽度的调用路径,而不是确认“哪个函数跑得久”。
NPU编译器跟通用编译器有个本质区别:它的后端包含大量硬件相关的代码——内存搬运、DMA配置、寄存器设置、微码生成。这些代码在通用CPU上跑的时候,性能特征跟普通应用完全不同。比如一个看起来简单的memcpy,在NPU编译器里可能因为对齐检查而膨胀成几百行的条件分支。
我踩过的一个坑:某个NPU驱动库的dma_config函数,在火焰图上宽度正常,但展开它的子调用后发现,一个叫