4、算子调度日志:识别算子启动、执行、同步三个阶段,定位哪个阶段最耗时
算子调度日志,说白了就是GPU干活时的流水账。
它把每个算子的执行过程拆成了三段:启动、执行、同步。我刚开始调性能时,总盯着执行时间看,觉得那才是算力核心。后来发现,很多时候瓶颈根本不在执行,而在启动和同步上。
4.1 三个阶段到底在干什么?
咱们先捋清楚每个阶段的意义。你想想看,GPU不是一上来就闷头算的,它得先准备一下。
- 启动(Launch):CPU把kernel指令发出去,告诉GPU“嘿,该干活了”。这个阶段主要花在指令下发和参数传递上。
- 执行(Execute):GPU真正开始算。矩阵乘、卷积、激活函数,都在这里跑。
- 同步(Sync):CPU等GPU算完,或者等数据从显存拷回来。说白了就是“你好了没?好了我就继续”。
我在项目中遇到过一种情况:一个算子的执行时间只有0.2ms,但启动花了0.8ms,同步又花了1.5ms。整个算子耗时2.5ms,执行只占8%。你说这算力利用率能高吗?
4.2 怎么从日志里抓出这三个阶段?
大多数深度学习框架都会输出调度日志。以PyTorch为例,开启TORCH_SHOW_CPP_STACKTRACES=1或者用profiler,就能看到类似下面的信息:
# 典型的算子调度日志片段
[2025-03-15 10:23:45.123] [launch] kernel: aten::mm, stream: 7, grid: (128,128,1), block: (256,1,1)
[2025-03-15 10:23:45.124] [execute] kernel: aten::mm, device: cuda:0, duration: 0.45ms
[2025-03-15 10:23:45.126] [sync] kernel: aten::mm, stream: 7, sync_type: cudaDeviceSynchronize, duration: 1.23ms
嗯,这里要注意:日志里的时间戳是连续的。启动和同步的时间差,就是执行时间。但很多工具已经帮你算好了,直接看三列就行。
我个人习惯用NVIDIA Nsight Systems来抓调度日志。它会把每个算子的三个阶段用不同颜色标出来,一目了然。比如:
| 算子名称 | 启动耗时 (μs) | 执行耗时 (μs) | 同步耗时 (μs) | 总耗时 (μs) |
|---|---|---|---|---|
| aten::mm | 820 | 450 | 1230 | 2500 |
| aten::conv2d | 150 | 3200 | 80 | 3430 |
| aten::relu | 60 | 120 | 50 | 230 |
看到没?aten::mm的同步时间比执行时间还长。这就是典型的“同步瓶颈”。
4.3 怎么定位哪个阶段最耗时?
方法其实很简单:算占比。
对于每个算子,计算三个阶段各自占总耗时的百分比。然后排序,看哪个阶段占比最高。
我一般这么干:
- 先看同步占比。如果超过30%,说明CPU和GPU之间在频繁等待。常见原因是:
cudaDeviceSynchronize调用过多,或者数据拷贝没做好异步。 - 再看启动占比。如果超过20%,说明kernel太小了。启动开销摊不平执行时间。我遇到过一个小算子,启动花了1ms,执行才0.1ms,这种就该考虑算子融合。
- 最后看执行占比。如果执行占比很高(比如80%以上),那说明算力用满了。这时候瓶颈在计算本身,得从算法层面优化。
核心判断逻辑:
- 同步占比高 → 减少同步点,用异步流
- 启动占比高 → 算子融合,减少kernel数量
- 执行占比高 → 优化算法,或者换更快的硬件
4.4 避坑指南:我曾经踩过的坑
我曾经在优化一个BERT模型时,发现同步占比高达60%。我以为是数据拷贝的问题,折腾了半天异步传输,结果一点用没有。
后来仔细看日志才发现,问题出在Python侧的GIL上。框架在每次forward之后都隐式调了torch.cuda.synchronize()。说白了,框架自己加的同步点,不是我能控制的。
注意:有些框架的调度日志不会显式打印同步阶段,而是把同步时间算进了启动阶段。这时候你得看cudaEventSynchronize或者cudaStreamSynchronize的调用次数。次数越多,同步开销越大。
另一个坑是:启动时间会被CPU调度影响。如果你的机器上CPU负载很高,启动时间会显著增加。我建议在抓日志时,先确保CPU负载低于50%,否则数据不准。
4.5 知识体系:算子调度三阶段的核心逻辑
下面这张图,是我自己总结的算子调度流程。你看一眼就明白了。
我的小技巧:抓调度日志时,别只看单个算子。把整个模型的算子按阶段汇总,算一下三个阶段的总耗时占比。很多时候,一个模型里90%的同步时间都集中在几个大算子上。抓住它们,优化就成功了一半。
说白了,算子调度日志就是一张“时间账单”。启动、执行、同步,三笔账。哪笔账最大,就从哪下手。别一上来就想着改算法,先看看是不是同步在拖后腿。
想要更多资料,联系可可