想象你是一位交响乐指挥家,面对的是一支技艺精湛的乐团(GPU)。传统的工作方式是:每演奏一个小节,你都要放下指挥棒、转过身、对着某位乐手喊出一个指令——“长笛,进!”“小提琴,强!”,等这位乐手回应后,你再转向下一位。一首曲子有上万个音符,你就得上万次地转身喊话,嗓子喊哑了,乐手也被反复打断,整首曲子的节奏支离破碎。 CUDA Graph 改变了这个荒唐的场面。它让你先在排练厅把整首曲子完完整整地走一遍、录成一张“总谱”(capture),然后正式演出时,你只需挥动一次指挥棒,所有乐手就照着总谱自己配合着把整首曲子一气呵成奏完——你不再需要逐小节指挥。这张“总谱”就是计算图(Graph),它把成千上万个 GPU 内核(kernel)的启动指令预先编排成一张依赖关系图,之后每次只需一次“开始”信号就能整体重放。这就是 CUDA Graph 的核心思想:把逐条发号的调度过程,变成一张可以反复使用的乐谱,从而消除 CPU 与 GPU 之间频繁通信带来的巨大开销。
职业价值:从事 CUDA 开发或推理引擎优化的工程师,掌握 CUDA Graph 能把 GPU kernel 调度开销降低一个数量级,是 LLM 推理、强化学习等高频小 kernel 场景必备的性能优化技能。
目录
可视化演示
通过捕获重放机制(多kernel录制为图→单次重放)、启动开销对比(传统逐个启动vs图启动)与图结构优化(节点依赖+条件分支+实例化缓存)三个模式,展示CUDA Graph如何消除kernel调度开销提升推理性能。
本节配有交互动效(进入视口后自动加载,约 3KB)。
CUDA Graph捕获与重放 录制阶段:逐个kernel启动被捕获为图节点构建完整计算图 重放阶段:整图作为单次提交执行消除逐kernel CPU开销 capture → graph → replay (1 launch) 一次录制多次重放是核心机制
启动开销:指挥家喊话的代价
想象指挥家每指挥一个音符都要转身向乐手喊话——即使乐手们动作已经很快,这“转身—喊话—等待回应”的过程依然需要时间。在 GPU 编程里,CPU 调用一次 cudaLaunchKernel 就好比喊一次话:CPU 要把 kernel 的参数打包、写入命令缓冲区、通知 GPU 驱动,这被称为启动开销(launch overhead)。一个典型 kernel 实际在 GPU 上执行只需要几微秒,而 CPU 端的启动开销却可能高达 5~10 微秒——相当于喊话时间比演奏时间还长。
传统编程用 流(Stream) 做异步调度,把多个 kernel 像流水线一样排在同一股“河道”里。流式异步让 CPU 不必等 GPU 执行完再发下一个命令,但每次下发仍然要单独走一遍驱动协议,就像指挥家虽然不用等乐手演完,但每喊一句话都得备一次嗓子。当你的程序里有上百个短小 kernel 时,这种“逐句发令”的累计成本就非常可观。
批量提交的思路由此而生:能不能把一百句话合并成一次喊话?这正是 CUDA Graph 的出发点。就像把乐手各自的碎片化音符先排练成一张总谱,而不是演出时逐个提醒。传统方式的总耗时可以写成一个简单的模型:
回到指挥家的比喻:每次喊话都是一次 T_launch,乐手演奏是 T_kernel。喊一百次话,嗓子费的力气不比演奏少。CUDA Graph 的使命,就是把这个“喊话次数”从 N 次压缩到接近 1 次,让指挥家只起手一次。
T_launch 与 T_kernel 同量级时,启动开销几乎让 GPU 空转了一半时间,这正是优化空间所在。- 内核个数
N代表程序中 kernel 的调用次数,比如一个神经网络推理循环里几十个算子。 - 单次启动开销
T_{\text{launch}}是 CPU 提交一次 kernel 所需的时间,包括参数解析、命令入队、驱动通信。 - 内核执行时间
T_{\text{kernel}}是 kernel 在 GPU 上真正计算所需的时间。 - 累加求和
\times N表示总共 N 次调用,每次都重复付出启动开销。
捕获机制:把排练录成总谱
CUDA Graph 最神奇的地方在于,它不需要你手动画一张图,而是通过流捕获(Stream Capture) 自动“录”出来。你可以把它想象成录音棚:指挥家指挥乐团完整地排练一遍,录音师把整个过程录下来。途中每一位乐手在何时开始、何时结束、与别人如何衔接,全都被记录在案。CUDA 的 cudaStreamBeginCapture 和 cudaStreamEndCapture 就是这两个“录制开始/结束”的按钮。
录制过程中,GPU 会为每一次 kernel 调用记录一个节点(node),包括 kernel 的参数、所在流、依赖的前后事件(event)。如果 kernel A 的结果被 kernel B 读取,B 的节点就会自动挂到 A 节点的后面,形成依赖边(dependency edge)。这就像录音师在乐谱上标注:“第二小节的定音鼓必须等第一小节的圆号吹完才能进入”——依赖关系是自动推导出来的,不需要你手动连线。
当录制完成,调用 cudaGraphInstantiate 时,CUDA 会把这个图结构“雕琢”成一个可执行的 executable graph,类似录音师把原始录音混音成正式母带。这张母带就是 cudaGraphExec_t,之后每次要执行整批 kernel,只需调用 cudaGraphLaunch 这一个 API,GPU 就会照着依赖边自动调度成千上万个节点。整体的时间模型变成了:
所以,指挥家的策略变成了:演出前花一次时间排练录谱(capture + instantiate 的成本),正式演出时只需一个起手动作,整首曲子自动演奏。只要这张谱子会被反复使用——比如推理服务每次请求都走同一批算子——前期的排练成本就能被摊薄到忽略不计。
T_launch^graph 的大幅降低——因为 CPU 不再逐条解析参数,GPU 驱动可以直接按图索骥。- 录制成本
T_{\text{capture}}是流捕获阶段的时间,相当于乐队完整排练一遍的投入。 - 实例化成本
T_{\text{instantiate}}是把捕获的图“编译”成可执行图的时间,类似录音的后期混音。 - 单次图启动开销
T^{\text{graph}}_{\text{launch}}是cudaGraphLaunch一次的开销,比传统启动小一个数量级。 - 重放执行
N \times (...)表示每次重放时 N 个 kernel 按图依次执行,但 CPU 只需付一次启动。
图优化器:编曲家的神来之笔
总谱录好之后,CUDA 并不满足于“原样重放”。它内置了一个图优化器(Graph Optimizer),相当于一位编曲家,拿到原始总谱后开始大刀阔斧地修改。第一招是节点融合(fusion):如果相邻两个 kernel 之间没有其他依赖,且数据只在中间暂存,优化器会把它们合成一个 kernel,省掉一次中间数据的写入和读取。这就像编曲家把两个紧凑的乐句合并成一段连续演奏,中间的停顿直接剪掉。
第二招是依赖剪枝(dependency pruning)。原谱是录音师自动标注依赖的,难免有“过度谨慎”的边——比如两个 kernel 其实互不影响,却被标成了先后关系。编曲家仔细比对后发现它们其实可以同时演奏,于是删掉这条多余的依赖边。GPU 的调度器因此获得更大的并行空间,多个互不依赖的 kernel 可以真正并发执行。
第三招是内存复用(memory reuse)。传统流模式下,每个 kernel 都想自己独占一块中间缓冲区,内存需求像乐章里每个乐手都要求单独一间休息室。编曲家发现:第一乐章的乐手和第二乐章的乐手根本不会同时在场,于是让它们共用同一间休息室。图优化器会分析每个节点的生命周期,把不在交集内的中间缓冲重叠到同一块显存地址,显存峰值占用显著下降。这三招叠加之后,实际加速往往比单纯省掉启动开销还要可观:
编曲家的工作台就是 GPU 上的硬件调度器,它拿到优化后的图谱,能同时看到整首曲子的全局,而不是像传统方式那样“只见一个音符”。这正是图模式的核心优势:CPU 看到的是整张谱,GPU 调度器才能提前布局、穿插执行。
- 传统启动开销
T^{\text{stream}}_{\text{launch}}是普通cudaLaunchKernel的 CPU 端耗时。 - 图启动开销
T^{\text{graph}}_{\text{launch}}是cudaGraphLaunch的 CPU 端耗时。 - 求比值
/表示两项相除,得到一个无量纲倍数,反映调度效率的提升幅度。
条件执行:乐谱里的跳跃记号
传统乐谱总是一路演奏到底,但许多真实场景需要动态分支:比如推理时如果某个 batch 很短,可以跳过部分冗余计算。在传统流模式下,每次分支都需要 CPU 参与判断后再决定启动哪个 kernel——CPU 和 GPU 之间又多了一次往返。CUDA Graph 从 CUDA 11.0 开始支持条件节点(Conditional Node),相当于乐谱里的跳跃记号"DA CAPO AL FINE":乐手读到记号时自己判断是否跳回开头,不需要指挥家临时喊话。
cudaGraphConditionalHandle 可以让图的某个区域变成“可选段落”。当你启动这个图时,GPU 端会传入一个条件值——它可能来自前一个 kernel 的计算结果——条件节点自动选择执行 A 分支还是 B 分支。更妙的是循环结构也能入图:一个包含条件节点的子图可以构成循环体,让 GPU 在图上自动迭代,CPU 全程不必介入。这正是动态控制流的全部含义:让“决策”也从 CPU 下沉到 GPU,连 if/while 都变成音符的一部分。
用期望成本来理解条件执行的好处:假设分支 A 以概率 p 被选中,分支 B 以概率 1-p 被选中,传统方式每次都要 CPU 判断(一次启动开销),而图模式在 GPU 内部完成判断:
乐手们最头疼的就是指挥家反复打断:“注意!这里跳过去!”动态控制流入图之后,分段记号直接印在总谱上,所有跳转都是 GPU 对自己说“该跳了”,整个乐团的演奏行云流水,CPU 彻底解放出来去处理别的任务。
- 分支概率
p表示运行时会走路径 A 的统计概率,来自真实数据分布。 - 路径 A 耗时
T_{\text{pathA}}是条件为真时子图执行的 GPU 耗时。 - 路径 B 耗时
T_{\text{pathB}}是条件为假时另一条子图的耗时。 - 加权期望
p \times A + (1-p) \times B是标准的期望值计算,反映多次运行的平均成本。
流对比分析:总谱与即兴演奏的取舍
既然 CUDA Graph 这么好,是不是所有场景都该弃用传统 Stream?答案藏在“排练成本”里。传统流异步(traditional Stream asynchrony) 本质上是“即兴演奏”的指挥方式,CPU 下发指令后不等结果立即返回,利用异步性让 CPU 和 GPU 并行工作。它的优点是灵活:每次调用都是独立的,参数随时可以改,启动零前置成本。缺点也明显:每个 kernel 启动都要走一遍完整驱动协议。
CUDA Graph 的优势在于“一次捕获、反复重放”,特别适合 kernel 序列固定且反复执行的场景,比如神经网络的推理阶段——每个请求都走完全相同的算子序列,只是数据不同。但如果你只跑一次少量 kernel,捕获和实例化的成本反而会盖过收益,就像花一整天排练一首只在婚礼上演奏一次的曲子,不划算。适用场景的判断标准是调用次数:图模式额外付出的构建成本,需要被足够多次的重放摊薄。
性能对比可以形式化为一个收益条件。令图构建总成本为 T_build,那么使用图模式的前提是节省的启动开销总和要大于构建成本:
回到总谱的比喻:录音棚不是随便进的,进门就要花排练费。但如果你的乐队每天要演同一首曲子演一百遍,这首排练费早就值回票价。传统 Stream 是即兴演奏,Graph 是录音重放——选哪种,先问自己同一首曲子要演多少遍。
- 单次节省
T^{\text{stream}}_{\text{launch}} - T^{\text{graph}}_{\text{launch}}是每个 kernel 从传统切换为图模式后省下的 CPU 时间。 - 总节省
N \times (...)乘以调用次数 N,得到整个程序节省的总时间。 - 构建成本
T_{\text{capture}} + T_{\text{instantiate}}是录制排练和混音母带的投入。 - 正收益条件
\Delta > 0表示节省大于投入,采用图模式才算明智的投资。
实践要点:舞台监督的演出手册
真正的演出团队不会只有一张谱子。一个 GPU 程序可能同时服务多个业务路径,就像乐团会准备多首曲目——推理引擎往往要维护多个图(multi-graph),按需切换。CUDA 支持多个 cudaGraphExec_t 实例并存,你可以为不同输入形状分别捕获一张图,然后根据运行时请求选择合适的图来重放。要注意的是,图切换本身也有开销,合理的做法是给高频路径长期驻留图,低频路径才临时构建。
内存规划是实践中最容易翻车的环节。图捕获时会把 kernel 用到的指针地址“固化”进图里,如果后续数据换了显存地址,重放就会崩溃或出错。解决办法是预先规划好输入输出缓冲的生命周期,用固定的内存池(比如 CUDA 的虚拟内存管理)保证每次重放时地址稳定。这就像总谱上写死了“第三圆号手用 2 号休息室”,演出前必须确认这位乐手真的在 2 号休息室。
Nsight Systems / Compute 是舞台监督的回放录像,用来验证优化效果。你可以在捕获图之前和重放图之后各打一个时间戳,对比两者在 Nsight 时间线上的调度间隙。如果发现 GPU 利用率仍然有空洞,说明图内仍有依赖可以剪枝,或者 kernel 本身太短小不值得入图。优秀的实践流程是:先用 Nsight 测量传统模式的启动开销占比 → 决定是否值得图化 → 捕获 → 实例化 → 重放 → 再用 Nsight 验证收益。多图的平均开销可以视为各图构建与重放成本之和:
舞台监督的智慧在于:排练花掉的每一分钟,都要从正式演出的顺畅里赚回来。CUDA Graph 的工程实践本质就是用一次有计划的“排练”,换取无数次高效率的“演出”——前提是你把总谱(内存)、曲目(多图)、回放记录(Nsight)都打理得井井有条。
- 图的数量
M表示同时维护的 CUDA Graph 实例个数。 - 单图构建
T_{\text{build}}^{(i)}是第 i 张图的捕获与实例化总成本。 - 重放次数
R_i表示第 i 张图在程序生命周期内被启动的次数。 - 求和汇总
\sum把 M 张图的成本加总,用于估算整体开销预算。
AI 应用场景
大模型推理引擎加速
阶段:推理部署与性能优化
解决的问题:LLM 推理的 prefill 阶段包含数十个不同形状的 GEMM 算子,传统方式逐个启动,CPU 调度开销在 batch 较小时占到端到端延迟的 30% 以上。CUDA Graph 将这些算子捕获成一张图,每次生成 token 只需一次图启动,推理吞吐显著提升。
具体案例:vLLM、TensorRT-LLM 等主流推理框架都内置了 CUDA Graph 支持。当一个请求到达时,框架先按当前 batch 形状捕获一张图,后续迭代直接重放同一张图,把 prefill 阶段耗时降低 40%~60%,token 生成过程获得稳定的低延迟。
强化学习批量 Rollout 并行推理
阶段:训练数据采集与仿真
解决的问题:强化学习训练中,agent 需要与环境交互采样,每一步都要跑同一个策略网络的前向推理。大量并行环境会启动成百上千个小 kernel,逐条调度的开销迅速成为瓶颈。CUDA Graph 对固定结构的策略网络进行图化,配合多图轮换支持不同 batch 大小,让采样吞吐翻倍。
具体案例:某机器人仿真平台在 PPO 训练中,使用 CUDA Graph 重放策略网络的多层 MLP 算子,将每轮 rollout 的采集时间从 15 ms 压缩到 6 ms,训练迭代速度提升 2.3 倍,且 CPU 占用率显著下降。
Python 演示
# -*- coding: utf-8 -*-"""模拟传统 Stream 调度与 CUDA Graph 调度的性能差异通过一个简化的时间模型,对比逐次启动与图重放的累计开销。""" class GpuScheduler: def __init__(self, launch_us=10.0, kernel_us=100.0): self.launch_us = launch_us self.kernel_us = kernel_us self.graph_build_us = 15.0 self.graph_launch_us = launch_us * 0.1 def stream_total(self, kernels, rounds): """传统流式:每轮每个 kernel 都付一次完整启动开销""" return rounds * kernels * (self.launch_us + self.kernel_us) def graph_total(self, kernels, rounds): """CUDA Graph:只构建一次,之后每轮重放付一次图启动""" build_cost = self.graph_build_us + self.graph_launch_us replay_cost = rounds * (self.graph_launch_us + kernels * self.kernel_us) return build_cost + replay_cost def report(self, kernels=20, rounds=100): stream_time = self.stream_total(kernels, rounds) graph_time = self.graph_total(kernels, rounds) print(f"kernel 数: {kernels}, 重放轮数: {rounds}") print(f"传统流式总耗时: {stream_time:.1f} us") print(f"CUDA Graph 总耗时: {graph_time:.1f} us") print(f"加速比: {stream_time / graph_time:.2f}x") if __name__ == "__main__": GpuScheduler().report()
class GpuScheduler:定义一个模拟 GPU 调度器的类,封装两种调度方式的时间模型。self.graph_launch_us = launch_us * 0.1图调度的单次启动开销仅为传统方式的 10%,这是 CUDA Graph 收益的核心来源。def stream_total(...):传统模式下每轮每个 kernel 都独立计算launch + kernel耗时。build_cost = self.graph_build_us + self.graph_launch_us图模式额外付出一次构建成本(捕获+实例化+首次启动)。replay_cost = rounds * (self.graph_launch_us + kernels * self.kernel_us)之后每轮重放只需一次图启动,kernel 耗时保持不变。if __name__ == "__main__":保证模块被导入时不会自动执行,只有直接运行时才输出报告。GpuScheduler().report()用默认参数跑一次对比,展示加速比结果。
常见误区
误区“我明白了,CUDA Graph 就是把十个 kernel 打包成一个,少调用几次 `cudaLaunchKernel`,性能提升主要来自省掉那几次函数调用的时间。”
正解打包只是表象。真正的收益来自三层:一是 CPU 启动路径大幅缩短(驱动层免去了逐参数解析和命令入队);二是图优化器做节点融合和依赖剪枝,减少了 GPU 上的冗余执行和等待;三是调度器能看到全局依赖,让互不依赖的 kernel 真正并发执行。省掉的远不止“几次函数调用”,而是整个 CPU-GPU 同步协议的开销。就像总谱的价值不只是“不用逐句喊话”,更在于让整个乐团看到全局而自动协奏。
为何常见:因为初学者从 API 层面观察,只看到了“一次启动代替多次启动”的表象,看不到驱动内部协议和调度器全局视野的深层优化。
误区“Graph 捕获一次后就是一个万能模板,以后不管数据是什么、显存地址怎么变,直接 `cudaGraphLaunch` 就能跑,永远正确。”
正解图在捕获时会固化 kernel 参数中的指针值。如果下一次重放时输入张量的显存地址变了,图仍然会访问旧地址,轻则读脏数据,重则 segfault。安全做法是预先分配固定地址的内存池,或者每次重放前调用 cudaGraphExecKernelNodeSetParams 更新相关节点的参数。好比总谱上写死了“演奏者在 3 号站位”,换了站位必须先在总谱上改过来才能演出。
为何常见:因为官方文档强调“replay“(重放),字面意思容易让人误解为”一劳永逸”,忽略了图是参数化模板而非全自动适配器。
误区“CUDA Graph 是一种需要手动画的图结构,Stream Capture 是另一个选项,两者选一就行。”
正解Stream Capture 正是创建 CUDA Graph 的主要 API 途径——cudaStreamBeginCapture 开始录制流里的 kernel 调用,结束时得到一个 cudaGraph_t,这就是图本身。它们是“录制手段”与“产物”的关系,而非二选一的并列技术。当然你也可以用 cudaGraphAddKernelNode 手动建图,但 Stream Capture 是最主流、最不容易出错的构建方式。这就像“排练录音”和“总谱”的关系:录音是生成总谱的方式,而不是独立于总谱的另一件事。
为何常见:因为两者出现在不同 API 章节里,初学者把它们各自当成独立功能,没有意识到调用链上的包含关系。
误区“CUDA Graph 是官方优化技术,用了肯定快,我把所有 kernel 都塞进图里就对了。”
正解图模式有构建成本(capture + instantiate)。如果你的程序只跑一次少量 kernel,或者传统流调度本来就已经很高效(比如用 CUDA Graph 之前已经做了 kernel 融合),构建成本会超过节省的启动开销,表现为负优化。另外,图里的 kernel 地址被固化,若内存地址频繁变化需要反复更新参数,也会侵蚀收益。判断标准回到收益公式:只有当“减少的启动开销 × 重放次数”大于“构建成本”时才划算。就像录制一张专辑,如果只有一个人买,唱片公司肯定亏本。
为何常见:因为教程和案例都聚焦于收益惊人的场景,很少展示负优化的边界条件,让人产生了“银弹”错觉。
面试题
Q1. CUDA Graph 的核心思想是什么?
- A.把多个 kernel 线程合并成一个线程
- B.将 kernel 调度流程编译成图并整体重放← 正确答案
- C.用 CPU 模拟 GPU 的计算过程
- D.将显存中的数据压缩后传输
答案:B。CUDA Graph 把多次 kernel 启动捕获成一张依赖图,之后整体重放,从而消除逐次启动的 CPU 开销。A 是把线程合并,不是图思想;C、D 与 CUDA Graph 无关。就像用总谱代替逐句指挥,而不是改变乐手本身。
Q2. 使用哪个 API 可以启动一个已实例化的 CUDA Graph?
- A.
cudaLaunchKernel - B.
cudaStreamSynchronize - C.
cudaGraphLaunch← 正确答案 - D.
cudaMemcpyAsync
答案:C。cudaGraphLaunch 是图模式专用的启动入口,接收 cudaGraphExec_t 参数。cudaLaunchKernel 是传统逐 kernel 启动,cudaStreamSynchronize 是同步等待,cudaMemcpyAsync 是异步拷贝,都与图启动无关。
Q3. 关于 Stream Capture 与 CUDA Graph 的关系,正确的是?
- A.两者是并列的两种独立优化技术
- B.Stream Capture 是 CUDA Graph 的调试工具
- C.Stream Capture 是构建 CUDA Graph 的录制手段← 正确答案
- D.CUDA Graph 是 Stream Capture 的硬件加速器
答案:C。cudaStreamBeginCapture 录制流中的 kernel 调用,结束时返回一个 cudaGraph_t,所以 Stream Capture 是构建图的主要途径,是“手段”与“产物”的关系,而非并列技术。
Q4. 为什么短小的 kernel 序列不适合用 CUDA Graph?
- A.短小 kernel 的启动开销反而更小
- B.捕获和实例化的构建成本会超过节省的开销← 正确答案
- C.短小 kernel 无法被捕获进图
- D.图模式只支持单个 kernel 执行
答案:B。图模式需要先付 capture + instantiate 的成本,如果 kernel 序列短且只执行一次,构建成本会大于节省的启动开销,出现负收益。短小 kernel 同样可入图,C、D 是错误说法。这与“排练成本大于演出收益”的道理一致。
Q5. 图条件节点(Conditional Node)的主要作用是什么?
- A.在内核中执行 if-else 分支计算
- B.让图内控制流决策在 GPU 端自动完成← 正确答案
- C.控制 GPU 的冷却风扇转速
- D.在 CPU 和 GPU 间同步共享内存
答案:B。条件节点将分支/循环决策下沉到 GPU 执行,重放图时 GPU 根据条件自动选择路径,CPU 无需中途介入。A 是普通 kernel 内的分支,与图级控制流不同;C、D 完全不相关。
Q6. 关于图优化器的“内存复用”,下列说法正确的是?
- A.用更小的数据类型替换 kernel 参数← 正确答案
- B.将互不重叠生命周期的中间缓冲映射到同一块显存
- C.把显存数据统一搬到 CPU 内存以减少显存占用
- D.在每个 kernel 之间自动插入内存拷贝指令
- E.390
- F.440
- G.450
- H.500
答案:A。净收益 = 传统流总开销 - (图构建成本 + 图启动总开销) = 500 - (60 + 50) = 390 微秒。计算时注意 60 和 50 都要算进图模式的成本,很多同学只减了构建成本导致误选 B。
Q7. 哪些场景适合使用 CUDA Graph?(多选)
- A.推理服务反复执行固定算子序列← 正确答案
- B.强化学习批量环境策略网络推理← 正确答案
- C.只执行一次的少量 kernel 启动
- D.训练循环中每步重放同一组算子← 正确答案
答案:ABD。A、B、D 都是“同一批 kernel 被反复重放”的场景,构建成本能被摊薄。C 是一次性场景,构建成本盖过收益,不适合。判断标准就是看“同一首曲子要演多少遍”。
Q8. 图优化器可能对图执行哪些优化操作?(多选)
- A.将相邻无依赖的 kernel 节点合并← 正确答案
- B.删除多余的依赖边← 正确答案
- C.把条件节点改成无条件节点
- D.复用生命周期不重叠的中间缓冲← 正确答案
答案:ABD。图优化器做节点融合、依赖剪枝、内存复用三类典型优化。C 会破坏语义,优化器不可能删除用户显式声明的条件分支。
Q9. 关于 CUDA Graph 的内存地址固化问题,正确的处理方式有?(多选)
- A.每次重放前更新节点参数← 正确答案
- B.使用固定地址的内存池← 正确答案
- C.捕获一次后永远不修改指针
- D.每次重放前重新捕获整个图
答案:AB。图捕获会固化指针地址,若数据地址变化,需要用 cudaGraphExecKernelNodeSetParams 更新节点参数(A),或预先规划固定地址池(B)。C 不现实,数据总会变;D 若多次重建图则失去重放收益。就像总谱写好站位后,换人必须改谱或约定固定站位。