news 2026/4/29 5:27:18

vLLM推理引擎教程7-CUDA Graph

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
vLLM推理引擎教程7-CUDA Graph

1、概念

在vLLM的decode阶段使用了CUDA Graph技术来提升性能。

CUDA Graph概念:它是NVIDIA提供的一种GPU计算优化技术,它的作用是:可以减少kernel launch等的开销,在某些场景下,如有大量的kernel的实际运行时间很短,甚至超过了kernel launch的时间,这时候有一定的性能收益。

CUDA Graph操作:

  • 录制(capture):运行一次真实计算,记录所有GPU操作到图中
  • 重放(replay):直接提交整个图给GPU、跳过CPU调度

优势:减少CPU-GPU同步、减少驱动层开销、提升GPU利用率。把每次都要CPU指挥GPU做一堆操作变成录一次播多次,从而减少开销、提升性能。适用于输入结构固定、反复执行的推理任务。

PyTorch使用前提:

  • 输入、输出张量形状固定
  • 计算流程无动态控制流(如if/else依赖GPU数据)
  • 无CPU-GPU同步操作(如.item()、.cpu())
  • 张量地址固定,避免每次new memory

注意事项:

  • 只适用于结构固定的计算(如decode阶段)
  • prefill阶段通常不用,因为prompt长度变化大
  • 多batch size场景需要预录多个图(如batch_size=1,2,4,...,512)

2、实践Demo

(1)python代码

import torch import torch.nn as nn D_in = 32 D_out = 32 torch.manual_seed(1) class CUDAGraphRunner(): def __init__(self, model): self.model = model self.cuda_graph = None self.graph_input = {} self.graph_output = {} def capture(self, x, y, z): assert self.cuda_graph is None self.cuda_graph = torch.cuda.CUDAGraph() self.cuda_graph.enable_debug_mode() with torch.cuda.graph(self.cuda_graph): out = self.model(x, y, z) torch.cuda.synchronize() self.cuda_graph.debug_dump("graph.dot") # 定义graph 输入placeholder self.graph_input['x'] = x self.graph_input['y'] = y self.graph_input['z'] = z # 定义graph 输出placeholder self.graph_output['output'] = out def forward(self, x, y, z): self.graph_input['x'].copy_(x) self.graph_input['y'].copy_(y) self.graph_input['z'].copy_(z) self.cuda_graph.replay() return self.graph_output['output'] def __call__(self, *args, **kwargs): return self.forward(*args, **kwargs) # 创建模型和输入数据 class simple_model(nn.Module): def __init__(self): super().__init__() num_layer = 10000 self.blocks = torch.nn.ModuleList([nn.Linear(D_in, D_out) for _ in range(num_layer)]) def forward(self, x, y, z): a = torch.matmul(x, y) b = torch.matmul(x, z) c = torch.add(a, b) for block in self.blocks: c = block(c) return c def timed(fn, *args, **kwargs): start = torch.cuda.Event(enable_timing=True) end = torch.cuda.Event(enable_timing=True) repeat = 10 start.record() for _ in range(repeat): result = fn(*args, **kwargs) end.record() torch.cuda.synchronize() return result, start.elapsed_time(end) / repeat model = simple_model().cuda() inp = torch.randn(32, D_in).cuda() model.eval() model(x=inp, y=inp, z=inp) # warm up, 触发一些 gpu 资源的初始化 graph_runner = CUDAGraphRunner(model) inputs = {"x":inp, "y":inp, "z":inp} graph_runner.capture(**inputs) graph_runner(**inputs) # cuda_graph_runner warm up input = torch.randn(32, D_in).cuda() output, cuda_graph_elasped_time = timed(graph_runner, **inputs) output_ref, ori_infernce_elasped_time = timed(model.forward, **inputs) torch.cuda.synchronize() torch.testing.assert_close(output_ref, output, rtol=1e-03, atol=1e-03) print(f"cuda_graph_elasped_time: {cuda_graph_elasped_time} ms, ori_infernce_elasped_time: {ori_infernce_elasped_time} ms")

代码执行命令:

nsys profile --trace=cuda,nvtx,osrt --output=cuda_graph_trace --force-overwrite true python cuda_graph.py

执行结果:

(vllm_python312) [work@iZuf6hp1dkg31metmko4pbZ code]$ nsys profile --trace=cuda,nvtx,osrt --output=cuda_graph_trace --force-overwrite true python cuda_graph.py Collecting data... /data/xiehao/conda_workspace/envs/vllm_python312/lib/python3.12/site-packages/torch/cuda/graphs.py:167: UserWarning: DEBUG: calling debug_dump() (Triggered internally at /pytorch/aten/src/ATen/cuda/CUDAGraph.cpp:232.) return super().debug_dump(debug_path) cuda_graph_elasped_time: 47.78128662109375 ms, ori_infernce_elasped_time: 236.9718994140625 ms Generating '/tmp/nsys-report-93a0.qdstrm' [1/1] [========================100%] cuda_graph_trace.nsys-rep Generated: /data/xiehao/workspace/code/cuda_graph_trace.nsys-rep

原始模型执行236ms,通过图优化后执行47ms,提升明显。

(2)CUDAGraphRunner初始化

class CUDAGraphRunner(): def __init__(self, model): self.model = model self.cuda_graph = None self.graph_input = {} self.graph_output = {}

self.model:要加速的原始PyTorch模型

self.cuda_graph:存储录制好的CUDA Graph对象,初始化为None

self.graph_input / self.graph_output:字典,用于保存静态张量(static tensors),即GPU显存地址固定的输入/输出缓冲区。

静态张量不是指值不变,而是指内存地址不变。后续通过.copy_()更新内容,但地址始终不变,这是CUDAGraph正确工作的前提。

(3)录制方法capture

def capture(self, x, y, z): assert self.cuda_graph is None # 确保只录制一次

确保只录制一次,防止重复录制。

self.cuda_graph = torch.cuda.CUDAGraph() self.cuda_graph.enable_debug_mode() # 启用调试模式(可选)

enable_debug_mode():开启后可生成 .dot 图用于可视化(方便调试)

with torch.cuda.graph(self.cuda_graph): out = self.model(x, y, z)

在 with 上下文中执行模型前向,PyTorch 会自动录制所有 GPU 操作(kernel 启动、内存拷贝等)到 cuda_graph 中。

torch.cuda.synchronize() self.cuda_graph.debug_dump("graph.dot") # 保存计算图为 graph.dot(可选)

synchronize():确保录制完成;

debug_dump():将图导出为 graph.dot 文件,可用 Graphviz 可视化。

# 保存静态输入/输出张量(关键!) self.graph_input['x'] = x self.graph_input['y'] = y self.graph_input['z'] = z self.graph_output['output'] = out

重点! 这里保存的是 x, y, z, out 的引用(即它们的 GPU 内存地址);

后续重放时,CUDA Graph 会直接从这些地址读写数据。

⚠️ 注意:这些张量必须在录制后保持存活,不能被释放或重新分配!

(4)前向方法forward

def forward(self, x, y, z): self.graph_input['x'].copy_(x) self.graph_input['y'].copy_(y) self.graph_input['z'].copy_(z)

将新的输入数据拷贝到静态张量的内存地址中;

使用 .copy_() 是为了不改变地址,只更新内容。

self.cuda_graph.replay() return self.graph_output['output']

replay():一键重放整个 GPU 计算流程,跳过 CPU 调度开销;

返回录制时的输出张量(其内容已被更新)。

(5)定义测试模型simple_model

class simple_model(nn.Module): def __init__(self): super().__init__() num_layer = 10000 self.blocks = torch.nn.ModuleList([nn.Linear(D_in, D_out) for _ in range(num_layer)])

构造一个很深的模型(10000 层 Linear),目的是:

  • 增加 GPU 计算量;
  • 使 CPU 调度开销相对更明显,从而凸显 CUDA Graph 的加速效果。
def forward(self, x, y, z): a = torch.matmul(x, y) b = torch.matmul(x, z) c = torch.add(a, b) for block in self.blocks: c = block(c) return c

输入三个矩阵 x, y, z;

先做两个矩阵乘法 + 加法;

再经过 10000 层线性变换;

整个计算流程是静态的(无 if/else、无 CPU 同步),符合 CUDA Graph 要求。

(6)定义计时函数timed

def timed(fn, *args, **kwargs): start = torch.cuda.Event(enable_timing=True) end = torch.cuda.Event(enable_timing=True) repeat = 10 start.record() for _ in range(repeat): result = fn(*args, **kwargs) end.record() torch.cuda.synchronize() return result, start.elapsed_time(end) / repeat

使用torch.cuda.Event精确测量GPU执行时间。

Event的本质:它是一个GPU时间戳标记。当event.record()被调用时,该事件被插入到当前CUDA stream的队列末尾。GPU执行到这个位置时,才会打时间戳。即:record()不是立刻记录当前时间,而是在GPU执行流中预约一个时间点。

start.record():在stream队列中插入start事件

end.record():在stream队列末尾插入end事件

torch.cuda.synchronize():等待GPU执行完所有操作,包括end事件

start.elapsed_time(end):自动计算两个事件的时间差

(7)主程序

model = simple_model().cuda() inp = torch.randn(32, D_in).cuda() model.eval()

将模型和输入移到 GPU;

model.eval():关闭 dropout/batchnorm 等训练特性。

model(x=inp, y=inp, z=inp) # warm up

先跑一次普通推理,触发 CUDA context 初始化、cudnn benchmark 等,避免首次运行慢影响计时。

graph_runner = CUDAGraphRunner(model) inputs = {"x": inp, "y": inp, "z": inp} graph_runner.capture(**inputs)

用 inp 作为典型输入进行录制;

要求:后续所有输入必须和 inp 形状一致(32×32)。

graph_runner(**inputs) # cuda_graph_runner warm up

再跑一次 Graph 推理,确保图已加载到 GPU,避免首次 replay 慢。

torch.testing.assert_close(output_ref, output, rtol=1e-03, atol=1e-03)

验证两种方式的输出是否一致(允许微小浮点误差);

如果不一致,说明 CUDA Graph 使用有误!

output_ref:期望值(expected),通常是普通推理结果;

output:实际值(actual),通常是 CUDA Graph 推理结果;

rtol=1e-3:相对容差(relative tolerance);

atol=1e-3:绝对容差(absolute tolerance)。

对应的数学公式:

|a - e| ≤ atol + rtol * |e|

# ❌ 危险!浮点数不要用 == assert torch.equal(output_ref, output) # ❌ 不够灵活 assert (output_ref - output).abs().max() < 1e-3 # ✅ 推荐:使用 assert_close(语义清晰,容差合理) torch.testing.assert_close(output_ref, output, rtol=1e-3, atol=1e-3)

3、vLLM的简单实现

import torch import torch.nn as nn D_in = 1024 D_out = 2048 class ModelRunner(): def __init__(self, model): self.model = model self.graph_runners = {} # (int, CUDAGraphRunner) @torch.inference_mode() def capture_model(self): for batch in [1, 2, 3, 4]: # 提前设置一批 batch input = torch.randn(batch, D_in).cuda() graph_runner = CUDAGraphRunner(self.model) graph_runner.capture(input) self.graph_runners[batch] = graph_runner @torch.inference_mode() def execute_model(self, x): batch = x.size(0) if batch in self.graph_runners: model_executable = self.graph_runners[batch] # 根据输入找到对应的 graph_runner else: print(f"warning, no cudagraph_runner, back to origin model") model_executable = self.model # 回退到原始的 model return model_executable(x) class CUDAGraphRunner(): def __init__(self, model): self.model = model self.cuda_graph = None self.graph_input = None self.graph_output = None def capture(self, x): assert self.cuda_graph is None self.cuda_graph = torch.cuda.CUDAGraph() with torch.cuda.graph(self.cuda_graph): out = self.model(x) torch.cuda.synchronize() self.graph_input = x # 定义 graph 输入 placeholder self.graph_output = out # 定义 graph 输出 def forward(self, x): self.graph_input.copy_(x) self.cuda_graph.replay() return self.graph_output def __call__(self, *args, **kwargs): return self.forward(*args, **kwargs) # 创建模型和输入数据 model = nn.Linear(D_in, D_out).cuda() model.eval() input = torch.randn(4, D_in).cuda() output_ref = model(input) model_runner = ModelRunner(model) model_runner.capture_model() # model_runner 构造cuda graph output = model_runner.execute_model(input) # 执行 torch.testing.assert_close(output_ref, output, rtol=1e-03, atol=1e-03)

在vllm中,设置的capture的batch为:

设置得越多,构建cudagraph时耗费得显存资源也越多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 15:44:34

GPT-5.2 上线后差评如潮,其功能表现有哪些退步或不足?

一般&#xff0c;他们的发布都有后手。不会全盘推出。至少是成熟了的&#xff0c;才能面向用户。 3年来&#xff0c;GPT每次都能压着谷歌大。提前一天发布模型。 这一次是第一次被动了。而谷歌的后手&#xff0c;还有个语音模型&#xff0c;也很不错。 图片的nano-banana-pr…

作者头像 李华
网站建设 2026/4/22 20:20:17

从2025年来看,AI 泡沫是否会在一两年内破灭

英伟达&#xff0c;OpenAI&#xff0c;微软&#xff0c;谷歌都可能破灭&#xff0c;股价暴跌。 但是AI不会。 Agent会&#xff0c;manus 会&#xff0c;豆包手机会&#xff0c; 但是AI不会 宇树科技 &#xff0c;波斯顿动力 &#xff0c;figure可能会&#xff0c; 但是…

作者头像 李华
网站建设 2026/4/26 10:35:04

露,大鼠活动记录仪 小动物活动记录仪

适用对象涵盖小鼠、大鼠、豚鼠及兔&#xff0c;具备多场景应用能力与宽泛的监测范围。其核心特性为无需为实验动物配置专用盛放装置&#xff0c;可在动物原有生活环境中开展实时监测&#xff0c;能够客观、真实地捕捉动物长期活动的相关数据。安徽&#xff0c;正华&#xff0c;…

作者头像 李华
网站建设 2026/4/29 0:39:20

微软确认:Windows 11 AI 智能体访问用户文件前会先请求许可

微软证实&#xff0c;在允许人工智能智能体访问存储于六个常用文件夹&#xff08;包括桌面、文档、下载、音乐、图片和视频&#xff09;的文件前&#xff0c;Windows 11 会先征求用户的同意。用户也可为每个智能体单独自定义文件访问权限。12 月 17 日消息&#xff0c;微软证实…

作者头像 李华
网站建设 2026/4/26 0:06:19

Shopee 验证码解决方案

ight Data 的验证码解决方案是 抓取浏览器 和 网络解锁器 的内置功能&#xff0c;为应对最复杂的验证码挑战提供完整解决方案。功能特点快速识别与解决&#xff1a;可高准确率且迅速地自动解决 Shopee 验证码。IP 轮换&#xff1a;利用自动重试和动态 IP 调整&#xff0c;防止被…

作者头像 李华