使用事件时,CUDA并发内核会序列化

时间:2012-05-07 18:48:40

标签: cuda

我遇到了CUDA内核中的序列化问题,其中需要并发执行。我使用cudaEvents作为标记来跟踪内核执行。

在我对具有多个流的并发内核的实验中,我们发现在各自的流上使用事件会导致并发内核被序列化。

以下代码演示了此问题。我在两个具有下面列出的并发内核执行功能的不同设备上测试了这个:

  1. Tesla C2070,驱动程序版本4.10,运行时版本4.10,CUDA功能2.0
  2. Tesla M2090,驱动程序版本4.10,运行时版本4.10,CUDA功能2.0
  3. 您可以通过更改USE_EVENTS宏来运行带有和没有事件的程序,您将观察由于并发执行与串行执行之间的差异。

    #include<cuda.h>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<stdint.h>
    
    #define CUDA_SAFE_CALL( call) do {                                        \
    cudaError_t err = call;                                                    \
    if( cudaSuccess != err) {                                                \
    fprintf(stderr, "Cuda error in call at file '%s' in line %i : %s.\n", \
    __FILE__, __LINE__, cudaGetErrorString( err) );              \
    exit(-1);                                                     \
    } } while (0)
    
    
    
    // Device code
    __global__ void VecAdd(uint64_t len)
    {
        volatile int a;
        for(uint64_t n = 0 ; n < len ; n ++)
            a++; 
        return ;
    }
    
    #define USE_EVENTS
    
    int
    main(int argc, char *argv[])
    {
    
        cudaStream_t stream[2];
        for(int i = 0 ; i < 2 ; i++) 
            CUDA_SAFE_CALL(cudaStreamCreate(&stream[i]));
    
    #ifdef USE_EVENTS
        cudaEvent_t e[4];
        CUDA_SAFE_CALL(cudaEventCreate(&e[0]));
        CUDA_SAFE_CALL(cudaEventCreate(&e[1]));
        CUDA_SAFE_CALL(cudaEventRecord(e[0],stream[0]));
    #endif
        VecAdd<<<1, 32, 0, stream[0]>>>(0xfffffff);
    
    #ifdef USE_EVENTS
        CUDA_SAFE_CALL(cudaEventRecord(e[1],stream[0]));
    #endif
    
    #ifdef USE_EVENTS
        CUDA_SAFE_CALL(cudaEventCreate(&e[2]));
        CUDA_SAFE_CALL(cudaEventCreate(&e[3]));
        CUDA_SAFE_CALL(cudaEventRecord(e[2],stream[1]));
    #endif
        VecAdd<<<1, 32, 0, stream[1]>>>(0xfffffff);
    
    #ifdef USE_EVENTS
        CUDA_SAFE_CALL(cudaEventRecord(e[3],stream[1]));
    #endif
        CUDA_SAFE_CALL(cudaDeviceSynchronize());
    
        for(int i = 0 ; i < 2 ; i++) 
            CUDA_SAFE_CALL(cudaStreamDestroy(stream[i]));
    
        return 0;
    
    }
    

    为什么会发生这种情况以及如何绕过这种序列化的任何建议都会很有用。

2 个答案:

答案 0 :(得分:2)

以上示例问题按以下顺序进行:

1 event record on stream A
2 launch on stream A
3 event record on Stream A
4 event record on stream B
5 launch on stream B
6 event record on stream B

同一个流上的CUDA操作按发布顺序执行。 不同流中的CUDA操作可以同时运行。

通过编程模型定义应该有并发性。但是,在当前设备上,此工作通过单个推送缓冲区发布到GPU。这导致GPU在发出操作3之前等待操作2完成,并且在发出5之前等待操作4完成,...如果事件记录被移除则操作是

1 launch on stream A
2 launch on stream B

操作1和2在不同的流上,因此GPU可以同时执行这两个操作。

Parallel Nsight和CUDA命令行分析器(v4.2)可用于对并发操作进行计时。命令行分析器选项是“conckerneltrace”。此功能应出现在NVIDIA Visual Profiler的未来版本中。

答案 1 :(得分:1)

我从根本上调试了同样的问题。格雷格的回答非常有帮助,但解释似乎并不完整。真正的问题是,当发布4时,op 3正在等待2。即使4位于不同的流中,如果已在发布队列中等待内核/事件,则无法发出。这类似于每个流连续发出多个内核的情况。这可以通过延迟流结束事件来解决,如下所示:

  1. 流A上的事件记录(启动计时器)
  2. 在A流上发布
  3. 流B上的事件记录(启动计时器)
  4. 在B流上发布
  5. 流A上的事件记录(结束计时器)
  6. 流B上的事件记录(结束计时器)
  7. 由于启动是异步的,因此流末尾事件将一直等到该流中先前的内核启动完成,并且已启动所有其他流的内核问题。显然,如果有更多的流可以在给定的硬件上同时发布,这将导致终止时间发布得太晚。