You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CUDA Graph运行结果不符合预期,求技术解答

CUDA Graph 核函数参数异常问题解析

问题描述

我正在用以下代码学习CUDA Graph,设置NSTEP=1000、NKERNEL=20。核函数shortKernel执行简单计算,但实际运行结果完全不符合预期。

原代码

#include <cuda_runtime.h>
#include <iostream>

#define N 131072 // tuned such that kernel takes a few microseconds
#define NSTEP 1000
#define NKERNEL 20
#define BLOCKS 256
#define THREADS 512

#define CHECK(call)                                                         \
    do {                                                                    \
        const cudaError_t error_code = call;                                \
        if (error_code != cudaSuccess) {                                    \
            printf("CUDA Error\n");                                         \
            printf("    File:   %s\n", __FILE__);                           \
            printf("    Line:   %d\n", __LINE__);                           \
            printf("    Error code: %d\n", error_code);                     \
            printf("    Error text: %s\n", cudaGetErrorString(error_code)); \
            exit(1);                                                        \
        }                                                                   \
    } while (0)

__global__ void shortKernel(float * out_d, float * in_d, int i){
      int idx=blockIdx.x*blockDim.x+threadIdx.x;
        if(idx<N) out_d[idx]=1.23*in_d[idx] + i;
        
}

void test2() {
  cudaStream_t stream;
  cudaStreamCreate(&stream);
  cudaSetDevice(0);

  float x_host[N], y_host[N];
  // initialize x and y arrays on the host
  for (int i = 0; i < N; i++) {
    x_host[i] = 2.0f;
    y_host[i] = 2.0f;
  }
  float *x, *y, *z;
  CHECK(cudaMalloc((void**)&x, N*sizeof(float)));
  CHECK(cudaMalloc((void**)&y, N*sizeof(float)));
  CHECK(cudaMalloc((void**)&z, N*sizeof(float)));
  cudaMemcpy(x, x_host, sizeof(float) * N, cudaMemcpyHostToDevice);

  cudaEvent_t begin, end;
  CHECK(cudaEventCreate(&begin));
  CHECK(cudaEventCreate(&end));
  // start recording
  cudaEventRecord(begin, stream);
  bool graphCreated=false;
  cudaGraph_t graph;
  cudaGraphExec_t instance;
  // Run graphs
  for(int istep=0; istep<NSTEP; istep++){
    if(!graphCreated){
      cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal);
      for(int ikrnl=0; ikrnl<NKERNEL; ikrnl++){
        shortKernel<<<BLOCKS, THREADS, 0, stream>>>(y, x, ikrnl);
      }
      cudaStreamEndCapture(stream, &graph);
      cudaGraphNode_t* nodes = NULL;
      size_t num_nodes = 0;
      CHECK(cudaGraphGetNodes(graph, nodes, &num_nodes));
      std::cout << "Num of nodes in the graph: " << num_nodes
                << std::endl;
      CHECK(cudaGraphInstantiate(&instance, graph, NULL, NULL, 0));
      graphCreated=true;
    }
    CHECK(cudaGraphLaunch(instance, stream));
    cudaStreamSynchronize(stream);
  }  // End run graphs
  cudaEventRecord(end, stream);
  cudaEventSynchronize(end);
  float time_ms = 0;
  cudaEventElapsedTime(&time_ms, begin, end);
  std::cout << "CUDA Graph - CUDA Kernel overall time: " << time_ms << " ms" << std::endl;

  cudaMemcpy(y_host, y, sizeof(float) * N, cudaMemcpyDeviceToHost);
  for(int i = 0; i < N; i++) {
    std::cout << "res " << y_host[i] << std::endl;
  }
  // Free memory
  cudaFree(x);
  cudaFree(y);

}

int main() {
    test2();
    std::cout << "end" << std::endl;
    return 0;
}

预期结果

每个核函数依次执行后,y数组的值应该逐步递增:

res 2.46
res 3.46
res 4.46
res 5.46
res 6.46
...

实际结果

所有结果都是最后一个核函数的计算值(1.23*2 +19=21.46):

res 21.46
res 21.46
res 21.46
res 21.46
res 21.46
...

我尝试手动展开前几个核函数的参数,结果还是一样:

// Run graphs
for(int istep=0; istep<NSTEP; istep++){
  if(!graphCreated){
    cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal);
    for(int ikrnl=0; ikrnl<NKERNEL; ikrnl++){
      if(ikrnl == 0)
        shortKernel<<<BLOCKS, THREADS, 0, stream>>>(y, x, 0);
      else if(ikrnl == 1)
        shortKernel<<<BLOCKS, THREADS, 0, stream>>>(y, x, 1);
      else if(ikrnl == 2)
        shortKernel<<<BLOCKS, THREADS, 0, stream>>>(y, x, 2);
      else
        shortKernel<<<BLOCKS, THREADS, 0, stream>>>(y, x, ikrnl);
    }
    cudaStreamEndCapture(stream, &graph);
    cudaGraphNode_t* nodes = NULL;
    size_t num_nodes = 0;
    CHECK(cudaGraphGetNodes(graph, nodes, &num_nodes));
    std::cout << "Num of nodes in the graph: " << num_nodes
              << std::endl;
    CHECK(cudaGraphInstantiate(&instance, graph, NULL, NULL, 0));
    graphCreated=true;
  }
  CHECK(cudaGraphLaunch(instance, stream));
  cudaStreamSynchronize(stream);
}  // End run graphs

问题原因

这是CUDA Graph的写后读依赖优化导致的结果。

CUDA Graph在捕获流中操作时,会自动分析数据依赖关系。你的代码中,所有核函数都对同一个设备数组y执行写操作,且没有中间的读操作或同步点。CUDA优化器会判定:前面的核函数对y的写操作会被后面的核函数完全覆盖,因此可以直接跳过前面所有核函数,只保留最后一个写操作的核函数。这就是为什么最终结果都是最后一个核函数的计算值。

手动展开参数后结果不变,是因为优化逻辑和参数无关,只和数据的读写依赖有关——只要所有核函数都是覆盖写入同一个数组,优化器就会合并这些操作。

解决方法

要让所有核函数都被执行,需要打破这种写覆盖的依赖关系,让CUDA Graph认为每个核函数的输出都是必要的。常见的方法有两种:

方法1:使用不同的输出数组

每个核函数写入不同的设备数组,避免覆盖:

// 预先分配多个输出数组
float *y_arr[NKERNEL];
for(int i=0; i<NKERNEL; i++){
    CHECK(cudaMalloc((void**)&y_arr[i], N*sizeof(float)));
}

// 捕获图时,每个核函数写入不同的数组
cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal);
for(int ikrnl=0; ikrnl<NKERNEL; ikrnl++){
    shortKernel<<<BLOCKS, THREADS, 0, stream>>>(y_arr[ikrnl], x, ikrnl);
}
cudaStreamEndCapture(stream, &graph);

方法2:添加显式依赖关系

在每个核函数之间添加同步标记,或者手动为图节点添加依赖,强制CUDA按顺序执行所有核函数:

cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal);
cudaEvent_t prev_event;
CHECK(cudaEventCreate(&prev_event));
for(int ikrnl=0; ikrnl<NKERNEL; ikrnl++){
    shortKernel<<<BLOCKS, THREADS, 0, stream>>>(y, x, ikrnl);
    cudaEventRecord(prev_event, stream);
    // 让下一个核函数等待当前核函数完成
    cudaStreamWaitEvent(stream, prev_event, 0);
}
cudaStreamEndCapture(stream, &graph);
cudaEventDestroy(prev_event);

或者在捕获图后手动添加节点依赖:

cudaStreamEndCapture(stream, &graph);
// 获取所有节点
size_t num_nodes;
CHECK(cudaGraphGetNodes(graph, NULL, &num_nodes));
cudaGraphNode_t* nodes = new cudaGraphNode_t[num_nodes];
CHECK(cudaGraphGetNodes(graph, nodes, &num_nodes));
// 依次添加依赖,让每个节点依赖前一个节点
for(int i=1; i<num_nodes; i++){
    CHECK(cudaGraphAddDependencies(graph, &nodes[i-1], &nodes[i], 1));
}
// 实例化图
CHECK(cudaGraphInstantiate(&instance, graph, NULL, NULL, 0));
delete[] nodes;

这样CUDA Graph就会按顺序执行所有核函数,不会优化掉前面的操作。


内容的提问来源于stack exchange,提问作者Jackson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 07:40:33