如何为含依赖流与动态参数的CUDA程序应用CUDA Graphs
基于CUDA Graph重构多流依赖的动态参数程序
你的核心需求是把多流依赖、带动态参数的循环迭代转换成CUDA Graph执行,降低启动开销。先指出你现有尝试中的几个关键问题:
- 手动添加kernel节点完全多余:流捕获已经会自动把
kernel<<<>>>、cudaStreamWaitEvent、cudaEventRecord这些操作转换成图节点,重复添加会导致逻辑混乱。 - 动态参数处理错误:
cudaKernelNodeParams.kernelParams需要是指向参数数组的指针,而非直接指向单个参数;迭代变量i作为动态参数,需要和其他参数一起放入数组中更新。 - 外部事件与图的冲突:原代码中使用的
event1、event2是外部事件,在图捕获时应该让CUDA自动生成图内的依赖关系,而非复用外部事件。
正确实现步骤
- 初始化迭代:第一次迭代单独执行,完成初始数据准备并记录事件,为后续图迭代建立初始依赖。
- 捕获图结构:使用全局捕获模式,执行一次
else分支的完整逻辑,让CUDA自动把两个流的操作、流间依赖(通过cudaStreamWaitEvent和cudaEventRecord)捕获到同一个图中。 - 动态参数管理:把所有动态参数(
i、矩阵指针、数组指针等)放入一个参数数组,每次迭代更新数组内容,再通过cudaGraphExecKernelNodeSetParams更新图中对应kernel节点的参数。 - 图执行与资源清理:每次迭代更新参数后启动图执行,循环结束后清理所有CUDA资源。
修正后的完整代码
#include <cuda_runtime.h> #include <vector> __global__ void kernel1(int i, int* dynamic_matrix, int* persistent_array) { // 核函数实现 } __global__ void kernel2(int i, int* dynamic_matrix, int* persistent_array) { // 核函数实现 } __global__ void kernel3(int i, int* persistent_array) { // 核函数实现 } void execute_with_cuda_graphs(int* d_matrix, int* d_array, int iter_count) { cudaStream_t stream1, stream2; cudaEvent_t event1, event2; cudaGraph_t graph; cudaGraphExec_t graph_exec; std::vector<cudaGraphNode_t> kernel_nodes; // 存储图中的kernel节点,用于后续更新参数 // 初始化CUDA资源 cudaStreamCreate(&stream1); cudaStreamCreate(&stream2); cudaEventCreate(&event1); cudaEventCreate(&event2); // 第一次迭代:初始化 kernel1<<<1, 512, 0, stream1>>>(0, d_matrix, d_array); kernel2<<<1, 512, 0, stream2>>>(0, d_matrix, d_array); kernel3<<<1, 512, 0, stream1>>>(0, d_array); cudaEventRecord(event1, stream2); cudaEventRecord(event2, stream1); cudaStreamSynchronize(stream1); cudaStreamSynchronize(stream2); // 捕获图结构(执行一次else分支逻辑) cudaStreamBeginCapture(stream1, cudaStreamCaptureModeGlobal); { // 流1等待stream2的event1 cudaStreamWaitEvent(stream1, event1, 0); kernel1<<<1, 512, 0, stream1>>>(1, d_matrix, d_array); // 用临时i=1捕获结构 // 流2等待stream1的event2 cudaStreamWaitEvent(stream2, event2, 0); kernel2<<<1, 512, 0, stream2>>>(1, d_matrix, d_array); cudaEventRecord(event1, stream2); kernel3<<<1, 512, 0, stream1>>>(1, d_array); cudaEventRecord(event2, stream1); } cudaStreamEndCapture(stream1, &graph); // 遍历图节点,收集所有kernel节点 size_t node_count; cudaGraphGetNodes(graph, nullptr, &node_count); std::vector<cudaGraphNode_t> all_nodes(node_count); cudaGraphGetNodes(graph, all_nodes.data(), &node_count); for (auto node : all_nodes) { cudaGraphNodeType type; cudaGraphNodeGetType(node, &type); if (type == cudaGraphNodeTypeKernel) { kernel_nodes.push_back(node); } } // 实例化可执行图 cudaGraphInstantiate(&graph_exec, graph, nullptr, nullptr, 0); // 后续迭代:更新参数并执行图 for (int i = 1; i < iter_count; ++i) { // 准备动态参数数组:每个kernel的参数顺序要和核函数定义一致 // kernel1参数:i, d_matrix, d_array void* params_kernel1[] = {&i, &d_matrix, &d_array}; cudaKernelNodeParams params1 = { .func = (void*)kernel1, .gridDim = dim3(1), .blockDim = dim3(512), .sharedMemBytes = 0, .kernelParams = params_kernel1, .extra = nullptr }; cudaGraphExecKernelNodeSetParams(graph_exec, kernel_nodes[0], ¶ms1); // kernel2参数:i, d_matrix, d_array void* params_kernel2[] = {&i, &d_matrix, &d_array}; cudaKernelNodeParams params2 = { .func = (void*)kernel2, .gridDim = dim3(1), .blockDim = dim3(512), .sharedMemBytes = 0, .kernelParams = params_kernel2, .extra = nullptr }; cudaGraphExecKernelNodeSetParams(graph_exec, kernel_nodes[1], ¶ms2); // kernel3参数:i, d_array void* params_kernel3[] = {&i, &d_array}; cudaKernelNodeParams params3 = { .func = (void*)kernel3, .gridDim = dim3(1), .blockDim = dim3(512), .sharedMemBytes = 0, .kernelParams = params_kernel3, .extra = nullptr }; cudaGraphExecKernelNodeSetParams(graph_exec, kernel_nodes[2], ¶ms3); // 执行图(用stream1启动,图会自动调度两个流的操作) cudaGraphLaunch(graph_exec, stream1); } // 等待所有操作完成 cudaStreamSynchronize(stream1); cudaStreamSynchronize(stream2); // 清理资源 cudaStreamDestroy(stream1); cudaStreamDestroy(stream2); cudaEventDestroy(event1); cudaEventDestroy(event2); cudaGraphDestroy(graph); cudaGraphExecDestroy(graph_exec); }
关键细节说明
- 全局捕获模式:
cudaStreamCaptureModeGlobal允许捕获多个流的操作,并自动将流间的依赖(通过cudaStreamWaitEvent和cudaEventRecord)转换成图节点间的依赖关系,无需手动处理。 - 动态参数更新:每次迭代更新参数数组的内容,再调用
cudaGraphExecKernelNodeSetParams更新对应kernel节点的参数,确保每次图执行时使用最新的i、矩阵和数组指针。 - 节点收集:通过
cudaGraphGetNodes遍历图中的所有节点,筛选出kernel节点,后续更新参数时直接操作这些节点即可。
内容的提问来源于stack exchange,提问作者Photos
相关产品推荐
相关产品推荐

