CUDA Graph 是什么?
launch overhead 怎么来的,怎么去掉?

英文问法What is a CUDA Graph and what problem does it solve? · What does the CPU do when it launches a kernel? · How do you use CUDA Graphs with dynamic shapes? Can they make things slower?

考过别家面经原题:BAAI AI 编译器开发实习一面 2026-06-22「CUDA Graph 的 Capture、Replay 和 Fallback 流程是什么?」「哪些动态情况会导致 CUDA Graph 无法复用?」(牛客)· 华为 AI Infra 社招一面 2026-09-08「如果输入形状变化频繁,用了 CUDA Graph 反而会更慢吗?为什么?」(牛客)· 小鹏 AI Infra 一面 2026-08-22「CUDA Graph 适合什么场景?动态输入下如何使用」(牛客)· 沐曦 AI 工程师一面 2026-09-02「一个 cuda kernel 在 launch 的时候 cpu 都做了些什么事?」(牛客)· Unity 一侧没有直接依据,调研笔记里 FlashVector 和 JD 都没有提 CUDA Graph · 没有 Unity 自己的面经原题

先弄清楚:每个概念是什么

Launch
CPU 让 GPU 跑一个 kernel 的那次调用,不等 kernel 跑完就返回。
Launch overhead
每次 launch 花在 CPU 上的时间,微秒级,和 kernel 处理多少数据无关。
Launch-bound
kernel 又小又多,GPU 跑完一个要等 CPU 发下一个。
CUDA Graph
把一整串 kernel 和它们的参数录下来(capture),之后一次调用重放整串(replay)。

看图:逐个 launch 和一次 replay

逐个 launch:GPU 跑完一个,要等 CPU 发下一个 CPU GPU launch launch launch launch launch launch 空等 空等 共 32 µs GPU 忙 12 µs CUDA Graph:一次 replay 提交整串,kernel 连着跑 CPU GPU replay 共 17 µs,每个 kernel 还是 2 µs
图 1:launch 5 µs、kernel 2 µs、replay 5 µs 都是示意。逐个 launch 时 6 个 kernel 共 32 µs,GPU 只忙 12 µs。一次 replay 之后 kernel 连着跑,共 17 µs。

30 秒回答

Every kernel launch costs the CPU a few microseconds to set up arguments and submit the work. With many tiny kernels, that overhead dominates and the GPU sits idle waiting for the CPU. A CUDA Graph captures the whole sequence of kernels and their arguments once, then replays it with a single call. It removes launch overhead but doesn't make any kernel faster. It needs fixed shapes and memory addresses, with no CPU sync or data-dependent branches. For dynamic shapes, I capture one graph per shape bucket, pad inputs up to the bucket, and fall back to eager when nothing matches.

什么时候有用:手算一遍

按图里的数字放大到 100 个小 kernel。逐个 launch 约 500 µs,其中 GPU 只忙 200 µs。CUDA Graph 是一次 replay 加 100 × 2 µs,约 205 µs,快约 2.4 倍。

反过来,每个 kernel 要跑 5 ms 时,5 µs 的 launch 只占 0.1%,用了也看不出差别(benchmark 调大单次运行是同一个道理,见 kernel benchmark)。

NVIDIA 博客在 V100 上实测 2.9 µs 的 kernel,连续 launch 时平均每个 3.8 µs,用 CUDA Graph 是 3.4 µs。

流程和限制

  1. Warmup:先用 eager 跑几次
  2. Capture:再跑一次,kernel 不真的执行,只把 kernel 和参数记进 graph
  3. Replay:之后每次调用一次,整串 kernel 按录下的参数重跑
  4. Fallback:输入的 shape 对不上任何 graph,就退回 eager
限制原因
shape 固定每次 replay 里每个 tensor 的 size 和 layout 都不能变
内存地址固定每次 replay 读写同一批地址,新输入要先 copy 进 capture 时用的那块 tensor
不能有 CPU 同步.item() 这类要等 GPU 结果的 op 不能在 capture 里
不能有依赖数据的分支录下的是固定的 kernel 序列,分支在 capture 时就定死了

动态 shape 按 batch size 分桶,每个桶 capture 一个 graph,输入 padding 到桶的大小。分 8、16、32 三个桶时(示意),batch 11 padding 到 16,有 5/16 ≈ 31% 的计算是白做的,batch 40 对不上任何桶就 fallback。

必答点

追问

launch 的时候 CPU 做了什么?

准备 kernel 的参数,把命令提交给 GPU,立刻返回。PyTorch eager 里每个 op 在这之前还要过 Python 和 C++ 的 dispatch,这几层 replay 都跳过。

怎么知道自己是 launch-bound?

Nsight Systems 的 timeline 上 kernel 之间有密集的小空隙,GPU 在等 CPU(见 Nsight profiling 和 训练慢怎么查)。

它和 kernel fusion 有什么区别?

CUDA Graph 不改 kernel,kernel 数和读写 HBM 的量都不变。Kernel fusion 把 launch 次数和 memory traffic 一起降。两个可以叠加,torch.compile 的 reduce-overhead 模式就是这样做的。

为什么用了 CUDA Graph 显存占得更多?

PyTorch 默认给每次 capture 一个 private memory pool,graph 和 capture 时创建的 tensor 还活着它就不释放,桶越多 pool 越多。小马智行实习面问过,出处是 AIInfraGuide 的二手整理。

来源PyTorch CUDA semantics:CUDA Graphs(capture 不执行、Constraints、private pool、warmup)· NVIDIA blog:Getting Started with CUDA Graphs(V100、CUDA 10.1 的实测)· ml-八股 08 第 13 题 · 面经出处见 面试题与 JD 3.5 节和 NVIDIA 题库笔记 1.7、1.9 节