9.4_多GPU间同步

9.4 多GPU间同步

CUDA事件可使用cu(da)StreamWaitEvent()函数对多个GPU进行同步。如果在两个GPU之间存在生产者/消费者的关系,应用程序可以让生产者GPU记录一个事件,然后让消费者GPU在它的命令流中插入一个流等待事件。当消费者GPU遇到流等待,它将停止处理命令,直到生产者GPU越过了cu(da)EventRecord()被调用时的执行点。

注意 在CUDA 5.0中,如7.5节中描述,设备运行时不支持任何GPU之间的同步。在未来的版本中,这个限制可能会适当放宽。

代码清单9-1给出了chMemcpyPeerToPeer()函数的具体实现[1]。当GPU之间不存在直接映射时,该段实现点对点内存复制操作的代码使用可分享内存和GPU间同步达到类似于CUDA提供的内存复制操作的功能。该函数的工作方式类似于代码清单6-2中的chMemcpyHtoD()所执行主机到设备的内存复制操作:在主机内存中分配一个中转缓冲区,复制操作先把源GPU的数据复制到中转缓冲区并记录事件。但是,与主机到设备的内存复制操作不同之处在于,这里不需要CPU执行同步,因为所有的同步均在GPU端完成。因为内存复制操作和事件记录是异步的,在发出初始内存复制与事件记录之后,CPU即可请求目标GPU等待事件的发生并发出对同一缓冲区的一个内存复制操作。设置两个中转缓冲

区和两个CUDA事件是必要的,因为两个GPU可以并发的执行复制到和复制出缓冲区的操作,就如同在主机到设备的内存复制过程中CPU与GPU并行操纵两个中转缓冲区。CPU在输入缓冲区和输出缓冲区之间循环,发出内存复制和事件记录命令并穿梭于中转缓冲区,直到它已完成全部数据的复制请求,剩下要做的事情就是等待两个GPU来完成任务的处理。

注意 跟英伟达提供的CUDA实现一样,我们的点对点内存复写步的。

代码清单9-1 chMemcpyPeerToPeer()

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); CUDASetDevice(srcDevice); CUDAStreamWaitEvent(0, g_events[dstDevice][stagingIndex], 0); CUDAMemcpyAsync(g_hostBuffers[stagingIndex], src, thisCopySize, CUDAMemcpyDeviceToHost, NULL); CUDAEventRecord(g_events[srcDevice][stagingIndex]); CUDASetDevice.dstDevice); CUDAStreamWaitEvent(0, g_events[srcDevice][stagingIndex], 0); CUDAMemcpyAsync dst, g_hostBuffers[stagingIndex], thisCopySize, CUDAMemcpyHostToDevice, NULL); CUDAEventRecord(g_events[srcDevice][stagingIndex]); dst += thisCopySize; src += thisCopySize; N -= thisCopySize; stagingIndex = 1 - stagingIndex; } // Wait until both devices are done CUDASetDevice(srcDevice); CUDADeviceSynchronize(); CUDASetDevice.dstDevice); CUDADeviceSynchronize(); Error: return status; }

[1] 为清楚起见,CUDART_CHECK错误处理代码被移除。