PyTorch Profiling 入门:用最简单的例子学会读懂 profiler trace
PyTorch 训练和推理慢得像玄学,问题很可能出在你根本不知道时间花在了哪里。这篇文章直击这个痛点——profiler 的学习曲线太陡峭,trace 图看起来像一堵彩色的墙,大多数教程默认你已经是专家了。作者决定从最基础的 matmul + add 操作开始,手把手教你看懂 torch.profiler 到底返回了什么。
文章用 64×64 和 4096×4096 两组矩阵做对比实验,发现小矩阵时 CPU 时间是毫秒级而 GPU 只有微秒级,GPU 闲置超过 99%——典型的 overhead-bound。换成大矩阵后两者都变成毫秒级,才算进入 compute-bound。更硬核的洞察是:cudaOccupancyMaxActiveBlocksPerMultiprocessor 这个 planning call 只出现在 matmul 前,因为 cuBLAS 有几百种 kernel 变体需要运行时决定用哪个,而 add 操作寄存器固定、不需要查询。torch.compile 后的”fusion”其实是 dispatcher 层面的 aten::addmm,背后仍然跑了两次 kernel:一次 Memcpy DtoD 复制 bias,一次 GEMM epilogue 写入结果,并没有真正合并成一个 kernel。更反直觉的是,compile 后每一步的 CPU 开销反而是 eager 模式的 2 倍,因为 Dynamo→AOTAutograd→Inductor 的调用链本身就要花钱。
最容易被忽略的一个观察:同一个 matmul kernel 在 20 次迭代中运行时间从 500 微秒到 1500 微秒不等,GPU 温度、时钟频率、电源管理都在影响结果。只看 profiler table 的平均值会严重误导优化方向。读完这篇文章,你得到的不只是工具使用方法,而是一套读 trace 的思维框架:先分清 overhead-bound 还是 compute-bound,再顺着 dispatch chain 找到真正的热点,最后用 cheat sheet 做快速诊断。


