-
Notifications
You must be signed in to change notification settings - Fork 42
Description
vLLM prefill 阶段可以使用 CUDA Graph 吗?
prefill 阶段可以使用 CUDA Graph。Piecewise CUDA Graph 都支持(Prefill 部分优化),Attention 保持 eager,其他部分(Layer Norm, FFN 等)用 graph;
Full CUDA Graph(Prefill 完全优化) 要看具体的 attention backend 是否支持,比如 FlashAttention v3 和 TritonAttention 支持。
https://github.com/vllm-project/vllm/blob/main/docs/design/cuda_graphs.md#cudagraphmodes
https://www.zhihu.com/question/7987565201/answer/1976955378840405519
CUDA Graph 是一种 NVIDIA 提供的底层机制,允许开发者将一系列 CUDA kernel 调用及其依赖关系静态捕捉下来,形成一个“执行图”,在后续迭代中直接重放(replay)这个图,从而避免重复的 CPU-GPU 同步、kernel launch 开销,甚至能提升 GPU 调度器的效率。它的最大优势在于执行路径高度确定、输入模式稳定的场景。而 decode 阶段正好符合这个特征。
在 decode 阶段,每个 step 的输入通常只有一个 token (batch size 动态可变但每个 request 的 input_len=1),attention mask 是完全可预测的因果 mask,KV cache append 方式是固定偏移写入。因此,vLLM 能够基于固定的 batch size(例如动态 batching后) 提前录制 decode step 的 CUDA Graph,在后续推理中直接重放,获得显著性能提升。
对于 prefill,使用 CUDA Graph 的收益有限且牺牲灵活性。prefill 需要一次性处理整个 prompt,输入长度变化剧烈 -- 从几个 token 到几万个 token 都有可能。这种极端的动态性和不可预测性 直接导致几个关键问题。
- kernel launch pattern 高度动态。 prefill 的 attention 计算需要根据具体 prompt length 构建对应的 causal mask,这通常会触发不同的 kernel specialization(例如 FlashAttention 内部会根据 seqlen 选择不同的 block size 和 shared memory layout)。更关键的是,KV cache 的初始化长度是 variable 的,这导致 memory allocation、reshape、copy 等操作无法被静态图覆盖。
- 内存访问模式不固定。prefill 的最大瓶颈往往不是计算密度,而是HBM 带宽。由于不同请求的 prompt 长度差异大,即使 batching 在一起,也会出现严重的 padding 或碎片化问题。CUDA Graph 无法有效处理这种动态 memory footprint,反而可能因图录制失败或 fallback 到 eager mode 而引入更多 overhead。
- batching 策略的不确定性。vLLM 在 prefill 阶段采用动态批处理(continuous batching),请求随时可能进入系统。这就意味着每次 prefill 执行的 batch size、每个请求的 sequence length、 总 token 数都可能完全不同。而 CUDA Graph 要求执行图完全一致才能重用,这就导致除非为每种 (batch_size, lengths)组合都录制一张图,否则无法复用。考虑到组合爆炸问题(例如 128 个 可能长度 x 32种 batch size),这种方案在工程上不可行。
- 此外还有一个更底层的问题:prefill 通常只执行一次,而 decode 会执行几十甚至上百次。CUDA Graph 的录制(capture)本身是有开销的(通常几十到上百毫秒),只有在图能被多次重放时才能摊薄这个成本。prefil无法重复执行,图录制开销几乎无法回收,得不偿失。