Cuda 在单个流中使用对等GPU

Cuda 在单个流中使用对等GPU,cuda,multi-gpu,Cuda,Multi Gpu,在我目前的项目中,我使用GPU进行信号处理和可视化。我已经在使用流来允许异步操作。信号在帧中处理,对于每个帧,流中的处理步骤如下所示 memcpy到设备 信号调节 图像处理 形象化 现在这些步骤都是在单个GPU上进行的,但是我的机器有一个多GPU卡(GeForce GTX 690),我想在两个设备之间分配操作。基本上,我希望在设备A上执行步骤1和2,在设备B上执行步骤3和4,而操作1、2、3和4仍然作为单个异步流执行。理想的结果是流式布局,如下所示 Device A Stream a 1 2

在我目前的项目中,我使用GPU进行信号处理和可视化。我已经在使用流来允许异步操作。信号在帧中处理,对于每个帧,流中的处理步骤如下所示

  • memcpy到设备
  • 信号调节
  • 图像处理
  • 形象化
  • 现在这些步骤都是在单个GPU上进行的,但是我的机器有一个多GPU卡(GeForce GTX 690),我想在两个设备之间分配操作。基本上,我希望在设备A上执行步骤1和2,在设备B上执行步骤3和4,而操作1、2、3和4仍然作为单个异步流执行。理想的结果是流式布局,如下所示

    Device A Stream a 1 2       1 2  ...
             Stream b      1 2  ...
    Device B Stream a    3 4       3 4 ...
             Stream b         3 4  ...
    

    如何执行此操作?

    我以前的尝试不正确,因为流与创建它的设备相关联。因此,我认为对你在标题中提出的问题最直接的回答是“这是不可能做到的”。您不能创建单个流并从中向多个GPU发出命令。发件人:

    然而,在研究过程中,我注意到事件是在两个不同设备上同步两个流的建议方法:

    cudaStreamWaitEvent()
    将成功,即使输入流和 事件关联到不同的设备。cudaStreamWaitEvent()可以 因此,可用于使多个设备相互同步

    因此,我创建了以下代码来说明这一点:

    #include <stdio.h>
    #define SIZE 32
    #define K1VAL 5
    #define K3VAL 3
    
    #define cudaCheckErrors(msg) \
        do { \
            cudaError_t __err = cudaGetLastError(); \
            if (__err != cudaSuccess) { \
                fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
                    msg, cudaGetErrorString(__err), \
                    __FILE__, __LINE__); \
                fprintf(stderr, "*** FAILED - ABORTING\n"); \
                exit(1); \
            } \
        } while (0)
    
    
    __global__ void kernel1(int *frame, int size){
    
      int idx = threadIdx.x + (blockDim.x * blockIdx.x);
    
      if (idx == 0){
       int *a = new int[10000]; // just to make this kernel take a while
       for (int i = 0; i<10000; i++)
         a[i] = 0;
       for (int i = 0; i < size; i++)
         frame[i] += K1VAL;
       }
    }
    
    
    __global__ void kernel3(int *frame, int size){
    
      int idx = threadIdx.x + (blockDim.x * blockIdx.x);
      if (idx == 0)
       for (int i = 0; i < size; i++)
         frame[i] -= K3VAL;
    }
    
    void set_device(int dev){
      int ldev;
      cudaSetDevice(dev);
      cudaGetDevice(&ldev);
      cudaCheckErrors("set device error");
      if (ldev != dev){
        printf("set device mismatch error\n");
        exit(1);
        }
    }
    
    
    int main(){
    
      int A=0;
      int B=1;
      int framesize = SIZE*sizeof(int);
      int *h_frame;
      int *d_frame_aA,  *d_frame_bB;
      int numdev = 0;
      cudaGetDeviceCount(&numdev);
      cudaCheckErrors("can't determine number of devices");
      if (numdev < 2){
       printf("not enough devices!\n");
       return 1;
       }
      set_device(A);
      cudaMalloc((void **) &d_frame_aA, framesize);  // stream_a
      cudaMemset(d_frame_aA, 0, framesize);
      set_device(B);
      cudaMalloc((void **) &d_frame_bB, framesize);  // stream_b
      cudaMemset(d_frame_bB, 0, framesize);
      cudaHostAlloc((void **) &h_frame, framesize, cudaHostAllocDefault);
      cudaCheckErrors("allocations failure");
    
      set_device(A);
      cudaStream_t stream_a, stream_b;
      cudaStreamCreate(&stream_a);
      cudaEvent_t absync;
      cudaEventCreate(&absync);
      set_device(B);
      cudaStreamCreate(&stream_b);
      cudaCheckErrors("stream creation failure");
    
    
      for (int i = 0; i < SIZE; i++)
        h_frame[i] = 0;
    
      set_device(A);
      cudaDeviceEnablePeerAccess(B, 0);
      set_device(B);
      cudaDeviceEnablePeerAccess(A, 0);
      cudaCheckErrors("enable peer access fail");
    
    
      set_device(A);
      cudaMemcpyAsync(d_frame_aA, h_frame, framesize, cudaMemcpyHostToDevice, stream_a);
      kernel1<<<1,1,0, stream_a>>>(d_frame_aA, SIZE);
      cudaCheckErrors("kernel1 fail");
      cudaMemcpyPeerAsync(d_frame_bB, B, d_frame_aA, A, framesize, stream_a );
      cudaCheckErrors("memcpypeer fail");
      cudaEventRecord(absync, stream_a);
      set_device(B);
      // comment out the next line to see the failure
      cudaStreamWaitEvent(stream_b, absync, 0);
      kernel3<<<1,1,0, stream_b>>>(d_frame_bB, SIZE);
      cudaCheckErrors("main sequence fail");
    
    
    //  cudaCheckErrors("main sequence failure");
    
      cudaMemcpy(h_frame, d_frame_bB, framesize, cudaMemcpyDeviceToHost);
      cudaCheckErrors("results_a memcpy fail");
      for (int i = 0; i < SIZE; i++)
        if (h_frame[i] != (K1VAL - K3VAL)) {
          printf("results error\n");
          return 1;
          }
      printf("success\n");
      return 0;
    
    }
    
    #包括
    #定义大小32
    #定义k1值5
    #定义k3val3
    #定义cudaCheckErrors(msg)\
    做{\
    cudaError\u t\u err=cudaGetLastError()\
    如果(_err!=cudaSuccess){\
    fprintf(标准,“致命错误:%s(%s位于%s:%d)\n”\
    msg,cudaGetErrorString(_err)\
    __文件(行)\
    fprintf(stderr,“***失败-中止\n”)\
    出口(1)\
    } \
    }而(0)
    __全局无效内核1(整数*帧,整数大小){
    int idx=threadIdx.x+(blockDim.x*blockIdx.x);
    如果(idx==0){
    int*a=newint[10000];//只是为了让这个内核花费一些时间
    for(int i=0;i
    cudaStreamWaitEvent()
    启用GPU间同步,因为您可以在属于另一个设备的CUDA事件上插入等待

    因此,生产者和消费者之间的GPU间同步需要为2个GPU中的每一个分配几个事件(至少2个),然后让生产者
    cudaEventRecord()
    和消费者
    cudaStreamWaitEvent()
    在同一事件上。
    cudaStreamWaitEvent()
    将命令插入当前设备的命令缓冲区,使其暂停执行,直到记录给定事件为止

    下面查看一个代码片段,其中使用
    cudaStreamWaitEvent()
    以这种方式实现对等memcpy。泵启动后,生产者和消费者都应同时进行PCIe传输,每个传输到两个暂存缓冲区(分配在便携式固定内存中)中的一个


    这实际上与我已经尝试过的非常接近,唯一的区别是我没有使用
    cudaMemcpyPeerAsync
    在GPU之间传输数据。我的印象是,通过使用
    cudaEnablePeerAccess
    设备可以直接存储其对等设备,而无需通过副本。不管怎样结果是,根据nvvp,所有操作都是在设备A上执行的;在探查器中运行时,可视化部分被禁用,我只收集有关结果的统计信息。您正确地设置了内核和函数,但可能会执行第五次内核执行,这是一个cudaMemcpy设备,用于宿主以检索步骤/k的结果ernel 3,这样就可以存储起来进行离线分析和可视化。对等访问不是仅在特斯拉系列上可用吗?正如您链接的文本“特斯拉系列2.0及更高计算能力的设备可以相互寻址内存”中所述@ks6g10在回答了这个问题后,我让一位同事轶事般地告诉我,高速对等访问似乎在GTX690上起作用。即使高速对等访问不起作用,
    cudamemcypeerasync()
    调用仍能正常工作,只会以稍慢的速度进行,因为它是通过系统内存进行的。您可能希望阅读我在该答案中链接的相关部分。@ks6g10:根据文档,开普勒也可以使用它。考虑到我是PCI-E带宽,我不介意额外的复制操作无论如何绑定。我下面的答案是错误的,不起作用。您不能让一个流向两个不同的设备发出内核或memcopies。我将很快删除下面的错误答案。@RobertCrovella:谢谢提醒。同一文档还声明,
    cudaStreamWaitEvent
    可用于设备间同步因此,通过使用2*n个流,应该可以异步同步每个设备的流。现在我正在重构有问题的代码,但我会在再次运行时尝试。
    #include <stdio.h>
    #define SIZE 32
    #define K1VAL 5
    #define K3VAL 3
    
    #define cudaCheckErrors(msg) \
        do { \
            cudaError_t __err = cudaGetLastError(); \
            if (__err != cudaSuccess) { \
                fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
                    msg, cudaGetErrorString(__err), \
                    __FILE__, __LINE__); \
                fprintf(stderr, "*** FAILED - ABORTING\n"); \
                exit(1); \
            } \
        } while (0)
    
    
    __global__ void kernel1(int *frame, int size){
    
      int idx = threadIdx.x + (blockDim.x * blockIdx.x);
    
      if (idx == 0){
       int *a = new int[10000]; // just to make this kernel take a while
       for (int i = 0; i<10000; i++)
         a[i] = 0;
       for (int i = 0; i < size; i++)
         frame[i] += K1VAL;
       }
    }
    
    
    __global__ void kernel3(int *frame, int size){
    
      int idx = threadIdx.x + (blockDim.x * blockIdx.x);
      if (idx == 0)
       for (int i = 0; i < size; i++)
         frame[i] -= K3VAL;
    }
    
    void set_device(int dev){
      int ldev;
      cudaSetDevice(dev);
      cudaGetDevice(&ldev);
      cudaCheckErrors("set device error");
      if (ldev != dev){
        printf("set device mismatch error\n");
        exit(1);
        }
    }
    
    
    int main(){
    
      int A=0;
      int B=1;
      int framesize = SIZE*sizeof(int);
      int *h_frame;
      int *d_frame_aA,  *d_frame_bB;
      int numdev = 0;
      cudaGetDeviceCount(&numdev);
      cudaCheckErrors("can't determine number of devices");
      if (numdev < 2){
       printf("not enough devices!\n");
       return 1;
       }
      set_device(A);
      cudaMalloc((void **) &d_frame_aA, framesize);  // stream_a
      cudaMemset(d_frame_aA, 0, framesize);
      set_device(B);
      cudaMalloc((void **) &d_frame_bB, framesize);  // stream_b
      cudaMemset(d_frame_bB, 0, framesize);
      cudaHostAlloc((void **) &h_frame, framesize, cudaHostAllocDefault);
      cudaCheckErrors("allocations failure");
    
      set_device(A);
      cudaStream_t stream_a, stream_b;
      cudaStreamCreate(&stream_a);
      cudaEvent_t absync;
      cudaEventCreate(&absync);
      set_device(B);
      cudaStreamCreate(&stream_b);
      cudaCheckErrors("stream creation failure");
    
    
      for (int i = 0; i < SIZE; i++)
        h_frame[i] = 0;
    
      set_device(A);
      cudaDeviceEnablePeerAccess(B, 0);
      set_device(B);
      cudaDeviceEnablePeerAccess(A, 0);
      cudaCheckErrors("enable peer access fail");
    
    
      set_device(A);
      cudaMemcpyAsync(d_frame_aA, h_frame, framesize, cudaMemcpyHostToDevice, stream_a);
      kernel1<<<1,1,0, stream_a>>>(d_frame_aA, SIZE);
      cudaCheckErrors("kernel1 fail");
      cudaMemcpyPeerAsync(d_frame_bB, B, d_frame_aA, A, framesize, stream_a );
      cudaCheckErrors("memcpypeer fail");
      cudaEventRecord(absync, stream_a);
      set_device(B);
      // comment out the next line to see the failure
      cudaStreamWaitEvent(stream_b, absync, 0);
      kernel3<<<1,1,0, stream_b>>>(d_frame_bB, SIZE);
      cudaCheckErrors("main sequence fail");
    
    
    //  cudaCheckErrors("main sequence failure");
    
      cudaMemcpy(h_frame, d_frame_bB, framesize, cudaMemcpyDeviceToHost);
      cudaCheckErrors("results_a memcpy fail");
      for (int i = 0; i < SIZE; i++)
        if (h_frame[i] != (K1VAL - K3VAL)) {
          printf("results error\n");
          return 1;
          }
      printf("success\n");
      return 0;
    
    }
    
    cudaError_t
    chMemcpyPeerToPeer( 
        void *_dst, int dstDevice, 
        const void *_src, int srcDevice, 
        size_t N ) 
    {
        cudaError_t status;
        char *dst = (char *) _dst;
        const char *src = (const char *) _src;
        int stagingIndex = 0;
        while ( N ) {
            size_t thisCopySize = min( N, STAGING_BUFFER_SIZE );
    
            CUDART_CHECK( cudaSetDevice( srcDevice ) );
            CUDART_CHECK( cudaStreamWaitEvent( NULL, g_events[dstDevice][stagingIndex], 0 ) );
            CUDART_CHECK( cudaMemcpyAsync( g_hostBuffers[stagingIndex], src, thisCopySize, 
                cudaMemcpyDeviceToHost, NULL ) );
            CUDART_CHECK( cudaEventRecord( g_events[srcDevice][stagingIndex] ) );
    
            CUDART_CHECK( cudaSetDevice( dstDevice ) );
            CUDART_CHECK( cudaStreamWaitEvent( NULL, g_events[srcDevice][stagingIndex], 0 ) );
            CUDART_CHECK( cudaMemcpyAsync( dst, g_hostBuffers[stagingIndex], thisCopySize, 
                cudaMemcpyHostToDevice, NULL ) );
            CUDART_CHECK( cudaEventRecord( g_events[dstDevice][stagingIndex] ) );
    
            dst += thisCopySize;
            src += thisCopySize;
            N -= thisCopySize;
            stagingIndex = 1 - stagingIndex;
        }
        // Wait until both devices are done
        CUDART_CHECK( cudaSetDevice( srcDevice ) );
        CUDART_CHECK( cudaDeviceSynchronize() );
    
        CUDART_CHECK( cudaSetDevice( dstDevice ) );
        CUDART_CHECK( cudaDeviceSynchronize() );
    
    Error:
        return status;
    }