Wink Pings

每个 GPU 操作都没变快,LLM 推理却更快了

CUDA Graphs 让 GPU 不再空等 CPU 提交操作。kernel 没变,计算没变,变的是操作到达 GPU 的方式。

Avi Chawla 提出了一个关于 LLM 推理的反直觉观点:GPU 上每个操作耗时完全不变,推理也能变快。改进不来自 kernel 本身,而来自操作之间的空隙。

LLM 的 forward pass(前向计算)看起来是一次计算。在 GPU 上,它被拆成 attention(注意力)、矩阵乘法、归一化、采样、数据移动等一系列小操作。CPU 逐个准备和提交这些操作。单个启动开销很小,但 token-by-token(逐 token)解码时,每个 token 都重复相似的序列。有些 kernel(GPU 计算函数)只需几微秒,GPU 在 CPU 提交下一个操作之前就完成了当前操作。留下的间隙里没有模型计算,profiler 显示 GPU 在等待下一个任务。

CUDA Graphs 就是为这种情况设计的。它保存一个包含 GPU 操作序列和依赖关系的执行计划。应用先跑一次预热,处理一次性初始化,确定形状、缓冲区、kernel 和内存地址。运行时把操作序列捕获为图。后续迭代通过一次启动重放整个图。

重放不是复用旧输出。新输入被放入预期缓冲区,GPU 重新运行捕获的操作,产生新结果。计算几乎没有变化,变的是计算到达 GPU 的方式。

上面这张信息图展示了预热、捕获、重放如何消除重复的启动路径。

代价是灵活性。捕获的图要求兼容的操作、张量形状、控制流和内存地址。真实推理流量很少只有一种形状。批大小会变,请求进出,服务器在 prefill(预填充)和 decoding(解码)之间切换。服务引擎通常为常见形状捕获多个图,其他请求补齐到捕获大小,或走 eager(即时)执行。

捕获的形状越多,覆盖率越高,但 GPU 内存消耗和启动开销也增加。

有网友指出,PyTorch 里 torch.compile(mode="reduce-overhead") 做的就是这件事,自动捕获 CUDA Graphs。批大小的影响也很明显:batch 1 时 decode 的性能主要受操作启动次数限制,CUDA Graphs 收益很大;大 batch 时 kernel 运行时间长,空隙的影响就小了。这也是 vLLM 为一组固定批大小捕获图的原因。

CUDA Graph 重放的价值在于优化了 kernel 基准测试很容易隐藏的东西:反复请求 GPU 开始下一项工作的成本。这也是 LLM 推理需要专用的推理服务引擎,而不是标准的预测 API 的原因之一。Avi 还写了另一篇文章,专门讲传统 ML 推理和 LLM 推理的五个架构差异,包括 continuous batching、prefill-decode disaggregation、KV cache、prefix-aware routing 和 model sharding。

发布时间: 2026-10-26 13:11