11 · CUDA Graph:降低 Decode 的启动开销
Eager 模式的隐藏成本
普通 PyTorch 每轮 Decode 都会从 CPU 发起许多 GPU kernel。单个 kernel 很快时,Python、dispatcher 与 kernel launch 的固定开销会变得显著。
CUDA Graph 允许把一段固定 GPU 工作流捕获下来,之后只更新预分配输入缓冲区并 replay,减少 CPU 发射次数。
nano-vLLM 如何捕获
ModelRunner 会为一组 batch size 预先捕获 graph,例如小 batch 的 1、2、4、8,以及更大的固定间隔。捕获过程:
- 创建足够大的静态
input_ids、positions、slot_mapping、context_lens、block_tables与输出缓冲区。 - 设置对应 batch size 的 Context。
- 先 warmup 一次,触发必要初始化。
- 在
torch.cuda.graph(...)中再次执行模型,完成捕获。 - 保存 graph 和共享 graph pool。
为什么形状必须稳定
Graph 记录的是固定内存地址和操作拓扑。若每次输入 shape 都变化,就无法直接 replay。因此运行时会把当前 batch 映射到某个已捕获 batch size,并在静态缓冲区里填充数据。
enforce_eager 的价值
True:不捕获 graph,适合调试、验证新功能、观察真实堆栈。False:使用 graph,适合稳定 Decode 路径和性能测量。
任何涉及动态控制流、不同 shape 或新的内存分配的修改,都应先在 Eager 模式验证,再确认可被 graph 捕获。
CUDA Graph 不是免费加速
它需要额外 warmup、图池内存和固定缓冲区;捕获多个 batch size 也增加初始化时间。对长 Prefill 或大矩阵计算,launch 开销占比小,收益可能不如 Decode 明显。
HTML INTERACTIVE LAB11 · CUDA Graph:降低 Decode 的启动开销
单独打开 ↗课后习题等待完成
为什么 CUDA Graph 通常需要静态输入缓冲区?
动手任务
在 Eager 模式和 Graph 模式各画一条 5-step Decode 时间线,标出 CPU launch 次数的概念差异。
下一节:综合项目