别把 CUDA Graph 当成万能加速:它在 LLM Serving 里的真实收益边界与工程落地
很多团队一看到CUDA Graph第一反应都是“减少 kernel launch 开销吞吐和延迟都会更好”。这句话不算错但如果把它直接搬到 LLM Serving往往会高估收益、低估代价。因为大模型推理不是单个 kernel 的 benchmark而是一个同时受Prefill/Decode、动态 shape、continuous batching、KV Cache、显存水位、调度公平性影响的在线系统。真正的问题不是“CUDA Graph 有没有用”而是它到底优化了哪一段开销在什么负载下收益明显为什么有的场景能稳定降延迟有的场景几乎没变化甚至因为显存预留、shape 爆炸和调试复杂度把系统搞得更重这篇文章从 AI Infra 和 LLM Serving 的工程视角系统拆解 CUDA Graph 在推理系统中的位置先讲清楚 launch overhead 到底是什么再讲它和 Prefill/Decode 的关系、和 vLLM / TensorRT-LLM 这类 runtime 的关系、和 continuous batching / 动态 batch / 动态长度之间的冲突最后给出一套更实用的落地判断框架、监控指标和面试表达方式。导读前几篇系列文章我们已经分别拆过Chunked Prefill如何缓解长 prompt 对 TTFT 和 decode 连续性的冲击Admission Control / Goodput如何保护线上系统不被异质请求拖进过载区MoE 路由抖动、KV Cache 页管理、SGLang vs vLLM、CUDA Roofline等不同层面的工程问题如果说这些文章主要回答的是请求怎么调度KV 怎么管理框架怎么选瓶颈怎么分析那么这一篇要回答的是另一个经常被误用的问题当你已经知道系统瓶颈不只是算子本身而是“运行时执行路径”时CUDA Graph 到底值不值得上它属于 AI Infra 体系里一个很典型的话题既靠近 CUDA 和 runtime又必须放回 LLM Serving 的整体系统里理解。目录先讲清楚CUDA Graph 到底在解决什么问题为什么很多人会高估它在 LLM Serving 里的收益Kernel launch overhead 到底是什么CUDA Graph 的基本工作方式它在训练和推理里的价值为什么不一样放到 LLM Serving 里先把 Prefill 和 Decode 分开看为什么 Decode 往往比 Prefill 更适合 Graph 化动态 shape 是 CUDA Graph 最大的工程约束之一Continuous Batching 为什么会和 Graph 产生天然张力它到底能优化哪些开销不能优化哪些开销为什么有的压测收益明显线上却不稳定常见落地方式一固定 shape 池 多图复用常见落地方式二Prefill 保守Decode 激进常见落地方式三和 TensorRT-LLM / 编译型 runtime 结合显存为什么会变重Graph 不是白送优化监控和压测时应该重点看什么线上最容易踩的几个坑面试里怎么把 CUDA Graph 讲得像真做过总结1. 先讲清楚CUDA Graph 到底在解决什么问题很多文章一上来就说它可以减少 CPU 到 GPU 的 launch 开销它可以把一串 GPU 操作打包后重复执行它可以降低延迟、提升吞吐这些都没问题但还不够工程化。更准确地说CUDA Graph 解决的是当一段 GPU 执行路径重复出现时能不能把“每次都重新发射这串工作”的 CPU 侧运行时开销压到更低。它关注的不是模型数学上变了没有单个 kernel 算法更快了没有HBM 带宽凭空更高了没有它关注的是这一轮会发多少个 kernel每个 kernel 之间有多少依赖这些 launch 和同步动作是不是每次都在重复CPU runtime 是否成了显式成本从这个角度看CUDA Graph 更像一种执行路径缓存而不是单纯的算子优化。2. 为什么很多人会高估它在 LLM Serving 里的收益原因很简单大家容易把它想成“任何 GPU 程序都能稳定加速”。但 LLM Serving 和传统离线 benchmark 很不一样它的执行路径有几个特殊点请求长度高度动态batch 形态持续变化Prefill 和 Decode 的计算特征完全不同continuous batching 会让每一轮参与执行的请求集合不断变化KV Cache 水位、分页状态、prefix 复用、量化分支都会影响实际路径这意味着如果执行图不稳定Graph 的可复用性就会很差。于是你会看到两种完全不同的结论在固定 batch、固定序列长度的压测里CUDA Graph 收益很漂亮在线上异质流量里收益变得不稳定甚至维护成本高于实际价值这不是 Graph 无效而是系统条件变了。3. Kernel launch overhead 到底是什么想判断 CUDA Graph 有没有价值先得理解 launch overhead 是什么。很多工程师会把 GPU 延迟理解成“kernel 执行时间”但端到端执行通常还包括CPU 侧准备 launch 参数调用 runtime / driver 发射 kernel建立 stream 上的依赖关系插入同步点或事件多个小 kernel 串起来形成一次完整前向如果单个 kernel 很大这些开销可能不显眼。但如果一次推理步骤里包含很多细碎 kernel而且每个 kernel 执行时间不长那么CPU launchruntime bookkeeping调度和同步就可能逐渐变成显性成本。这在以下场景里尤其明显小 batch小 shape高频重复执行一个 token 一步一步生成的 decode这也是为什么很多人一聊 CUDA Graph都会自然想到推理阶段尤其是 decode。4. CUDA Graph 的基本工作方式它的核心思路可以粗略理解为两步4.1 先捕获一段执行路径把一串稳定的 GPU 操作记录成一个图里面包含哪些 kernel 会被执行执行顺序是什么依赖关系是什么相关内存地址和参数布局如何组织4.2 后续重复回放下一次遇到“同一类执行路径”时不再逐个 launch而是直接 replay 这张图。这样能减少多次 runtime 调用多次 CPU 发射动作一部分调度与依赖建立成本但它有个前提你回放的执行路径必须足够稳定。如果 shape、地址、kernel 序列、控制流分支经常变图就很难复用。5. 它在训练和推理里的价值为什么不一样这是一个很容易被忽略的点。5.1 训练阶段训练往往有更稳定的 mini-batch 形态固定 batch size固定 sequence length固定前向/反向结构如果数据整理得比较规整Graph 复用条件更容易满足。5.2 推理阶段在线推理的异质性强得多prompt 长度不一样输出长度不一样请求到达时间不一样batch 是动态拼出来的Prefill 和 Decode 混在一起时路径差异更大所以推理里不是“能不能用 CUDA Graph”而是要在多大程度上牺牲动态性去换取图复用。这本质上是 runtime 设计 trade-off。6. 放到 LLM Serving 里先把 Prefill 和 Decode 分开看这是理解收益边界的关键。6.1 PrefillPrefill 的特点是一次处理大量输入 token更偏大矩阵计算计算规模随 prompt 长度变化很大shape 波动通常更明显它更像“每次进来一段规模变化很大的工作”。6.2 DecodeDecode 的特点是每步通常只处理每个请求的少量新 token操作反复执行kernel 数量多、步数多单步执行形态相对更稳定它更像“同一段小工作重复很多次”。从 Graph 友好性看后者通常更适合。7. 为什么 Decode 往往比 Prefill 更适合 Graph 化这背后有三个核心原因。7.1 重复度更高Decode 是 token-by-token 的迭代过程。如果每一步执行路径相似那么 graph replay 的复用价值会被多次放大。7.2 CPU launch 成本更容易显性化因为 decode 单步通常更小、更碎。当真正的 GPU 计算时间没那么长时CPU launch 占比更容易上升。7.3 shape 更容易被约束尽管 decode 也有 batch 波动但相比长 prompt prefill单步 token 数更小更容易做 bucket 化更容易通过固定 slot / token budget 设计出较稳定执行形态所以很多系统会优先在 decode 路径上做更激进的 graph 化而对 prefill 更保守。8. 动态 shape 是 CUDA Graph 最大的工程约束之一如果你只记一个限制记这个就够了。CUDA Graph 最怕的不是“模型大”而是执行形态不稳定。在 LLM Serving 中导致 shape 不稳定的来源很多输入 prompt 长度变化每轮 active sequence 数变化每轮 decode 参与请求数变化continuous batching 不断插入和移除请求不同请求的 KV 长度不同不同采样策略或功能分支带来不同路径这会直接引出一个现实问题8.1 不能指望一张图吃掉所有流量真正落地时通常不是“一张万能图”而是多个 shape bucket每个 bucket 一组图不命中 bucket 时 fallback 到 eager也就是说Graph 本身往往不是替代普通执行而是和普通执行并存。8.2 bucket 设计本身就是工程题bucket 太细图数量暴涨捕获成本高显存和管理复杂度上升bucket 太粗padding 和空转增加复用看起来多了真实效率可能下降这不是单纯 CUDA 技术问题而是 runtime 选型问题。9. Continuous Batching 为什么会和 Graph 产生天然张力continuous batching 的目标是让 GPU 尽量持续吃到工作提升吞吐和资源利用率。但它的副作用是每一轮 batch 形态都可能变化请求会动态进出 batchactive sequence 数持续波动这和 CUDA Graph 希望看到的“稳定重复路径”天然不完全一致。所以它们之间并不是简单叠加关系而是存在张力9.1 batching 越灵活graph 越难稳定你越想动态地插入新请求、越想追求极致调度自由度执行路径就越不稳定。9.2 graph 越激进batching 自由度越可能下降如果为了复用图而强行把 shape 固定得更死调度器的自由度会下降请求异质性处理会变差可能损害公平性或长尾表现所以成熟系统通常会折中在一部分稳定形态上 Graph 化在另一部分动态形态上保持 eager10. 它到底能优化哪些开销不能优化哪些开销这部分最容易被讲错。10.1 它更可能优化的CPU 侧 launch overhead多 kernel 串联的运行时管理开销一部分小 shape 场景下的端到端延迟重复执行路径上的稳定性10.2 它不能直接解决的HBM 带宽不够TP / EP 通信过慢KV Cache 容量不足PagedAttention block 访问模式本身的访存瓶颈长 prompt 导致的 prefill 计算量爆炸调度策略不合理造成的 TTFT / TPOT 恶化如果系统真正瓶颈是memory-bound decodeKV page miss / block 管理多卡同步长 prompt 压住 decode那么上 CUDA Graph 的收益可能远没有想象中大。换句话说它优化的是“怎么发射这段工作”不是“这段工作本身为什么慢”。11. 为什么有的压测收益明显线上却不稳定因为很多压测默认做了三件对 Graph 很友好的事固定输入长度固定输出长度固定 batch 形态这等于人为构造了一个高度稳定的执行路径。在线上则完全不是这样请求长短分布离散高峰时 active sequence 波动很大有人很快结束有人持续长输出某些请求还带 prefix reuse、工具调用或结构化输出约束于是你会看到实验室里 latency 降了很多线上平均值也许有提升但 P95 / P99 不一定同步变好fallback 比例一高收益就被吃掉所以评估 CUDA Graph 不能只看平均 tokens/s 或平均 latency必须看graph hit ratefallback rate不同请求类型的收益分布长尾延迟是否改善12. 常见落地方式一固定 shape 池 多图复用这是最典型也最现实的方案。12.1 基本思路把常见执行形态离散成少量 bucket例如按batch size 区间active sequence 数token 数上限某些关键维度的 shape为每个 bucket 预热或懒加载对应图。12.2 优点实现路径清晰对热门 shape 能拿到稳定收益不需要把所有请求都硬塞进统一图12.3 代价需要管理多张图预热时间更长显存与元数据开销更高长尾 shape 很可能仍要 eager fallback很多时候这个方案的真实收益不取决于 Graph 本身而取决于你的线上流量是不是足够“有主形态”。13. 常见落地方式二Prefill 保守Decode 激进这是 LLM Serving 里很常见的一种思路。13.1 Prefill 更保守因为 prefillshape 波动大长 prompt 差异大经常伴随 chunking、prefix reuse、长上下文场景这使得它 graph 化的复用率未必高。13.2 Decode 更激进因为 decode单步重复多CPU launch 更容易成为成本更容易按 active token / batch slot 做稳定化设计于是常见做法是prefill 主要保持 eager 或有限 bucket graphdecode 路径重点 graph 化这种分层思路比“全链路一把梭”更符合工程现实。14. 常见落地方式三和 TensorRT-LLM / 编译型 runtime 结合如果说 vLLM 这类 runtime 更强调动态调度、KV 管理和通用服务能力那么 TensorRT-LLM 这类方案通常更强调编译优化静态或半静态执行路径kernel / engine 层面更激进的优化这类系统和 CUDA Graph 的关系往往更自然因为它们本来就在追求更稳定的执行计划更强的 shape 约束更低的运行时调度开销但代价也很明确灵活性下降形态管理更复杂对动态请求分布的包容度可能不如更通用的 runtime所以这不是“谁更先进”而是你的业务更偏稳定高吞吐还是更偏异质在线交互。15. 显存为什么会变重Graph 不是白送优化很多人第一次用 Graph都会疑惑为什么显存变大了。原因通常包括需要为图捕获保留稳定的内存布局可能要维护多组 shape bucket 图某些中间 buffer 生命周期会被拉长图相关元数据和预留空间会增加放到 LLM Serving 里这个问题更敏感因为显存本来就被三类东西挤压模型权重KV Cache运行时 bufferGraph 一旦再吃掉一部分显存系统可能会出现新的 trade-off图是复用了launch overhead 是降了但 KV Cache 预算变小了最终并发能力或长上下文容量反而下降这就是典型的局部优化和系统优化冲突。所以判断值不值必须看端到端TTFTTPOT总吞吐Goodput可容纳活跃会话数而不是只看某段执行更快了。16. 监控和压测时应该重点看什么如果你真的在做这件事建议至少看下面几组指标。16.1 Graph 本身的命中情况graph replay hit rateeager fallback rate不同 bucket 的命中分布新图捕获频率16.2 用户体验指标TTFT P50 / P95 / P99TPOT 或 per-token latency流式输出卡顿比例请求超时率、取消率16.3 系统资源指标GPU 利用率CPU 利用率runtime 线程占用显存总占用KV Cache 可用容量16.4 结构化收益指标短请求 vs 长请求收益差异Prefill vs Decode 收益差异低并发 vs 高并发收益差异命中 graph 的请求 vs fallback 请求差异如果只看平均 latency很容易被掩盖。17. 线上最容易踩的几个坑17.1 只在理想 benchmark 上评估固定 shape 跑出来很漂亮不代表线上就稳定收益。17.2 图 bucket 设计过细结果是图太多捕获复杂预热慢显存爆炸17.3 图 bucket 设计过粗结果是padding 浪费严重graph 命中了但真实效率不高batch 里无效工作增多17.4 忽略 fallback 的系统影响fallback 不是简单“性能稍差一点”而是可能导致延迟分布双峰化某些请求类型体验明显不稳定线上尾延迟变差17.5 只盯 GPU不看 CPUCUDA Graph 的一部分价值恰恰在 CPU runtime 路径。如果不看 CPU 开销和 launch 路径很难解释为什么它有时有效、有时无效。17.6 只看单请求不看 Goodput有时单步更快但因为显存更紧、图管理更重系统整体可承载会话数下降最终 Goodput 不升反降。18. 面试里怎么把 CUDA Graph 讲得像真做过这是个很适合拉开差距的话题因为它天然带系统味道。18.1 不要只背定义如果你只说CUDA Graph 可以减少 kernel launch overhead这太浅了。18.2 更像实战的表达方式你可以这样讲我理解 CUDA Graph 在 LLM Serving 里不是“默认必开”的通用优化而是一个对执行路径稳定性要求很高的 runtime 优化手段。它主要减少的是 CPU 侧 launch 和一串 kernel 的重复发射成本所以最适合那种重复度高、单步较碎、shape 相对稳定的路径典型就是 decode。但线上推理有 continuous batching、动态长度、KV Cache 管理、prefix 复用等因素执行形态并不稳定所以很难一张图吃掉所有请求。实际落地一般会做 bucket 化、多图复用并保留 eager fallback。评估它值不值不能只看固定 shape benchmark 的平均延迟还要看 graph hit rate、fallback rate、显存额外占用以及 TTFT / TPOT / Goodput 在真实异质流量下是否真的变好。很多时候它优化的是运行时发射路径而不是 memory-bound decode、通信或 KV 容量这些更根本的瓶颈。18.3 面试官继续追问时能展开什么可以继续讲为什么 decode 比 prefill 更适合为什么 dynamic shape 会限制图复用为什么显存可能上升为什么线上收益和离线 benchmark 不一致为什么要和调度器、bucket、fallback 一起设计只要能把这些因果链讲顺基本就不像只看过概念。19. 总结CUDA Graph 是个很典型的 AI Infra 话题它既不是纯 CUDA 面试题也不是简单开关项而是一个必须放进 LLM Serving 系统上下文里理解的 runtime 优化点。如果只看局部它的故事很简单减少 launch overhead提高重复路径执行效率但如果放进真实线上系统它马上会变成一组 trade-off复用率 vs 动态性launch 开销 vs bucket 复杂度局部延迟优化 vs 显存预算图命中收益 vs fallback 抖动runtime 优化 vs 系统 Goodput所以更准确的结论不是CUDA Graph 能不能让 LLM Serving 变快而是当你的执行路径足够稳定、CPU launch 成本已经显性化、并且你愿意用一部分动态性和显存去换运行时效率时CUDA Graph 才会在 LLM Serving 里体现出稳定价值。一句话总结这篇文章CUDA Graph 优化的是“同一段 GPU 工作怎样更省地重复发射”而 LLM Serving 真正难的是“哪些工作值得这样发射、哪些路径根本不稳定、哪些系统瓶颈并不在这里”。如果你正在做 AI Infra / LLM Serving这个话题很值得掌握因为它刚好站在 CUDA、runtime、调度和线上落地的交叉点上。也正因为如此它很适合用来区分“知道概念”和“理解系统”的差别。