P2P复制中,cudaMemcpy()和cudaMemcpyPeer()有什么区别?

4
我希望能够直接从GPU0-DDR复制数据到GPU1-DDR,无需通过CPU-RAM进行中转。
正如在第15页上所述:http://people.maths.ox.ac.uk/gilesm/cuda/MultiGPU_Programming.pdf
Peer-to-Peer Memcpy
 Direct copy from pointer on GPU A to pointer on GPU B

 With UVA, just use cudaMemcpy(…, cudaMemcpyDefault)
     Or cudaMemcpyAsync(…, cudaMemcpyDefault)

 Also non-UVA explicit P2P copies:
     cudaError_t cudaMemcpyPeer( void * dst, int dstDevice, const void* src, 
        int srcDevice, size_t count )
     cudaError_t cudaMemcpyPeerAsync( void * dst, int dstDevice,
        const void* src, int srcDevice, size_t count, cuda_stream_t stream = 0 )
  1. 如果我使用cudaMemcpy(),那么我必须首先设置一个标志cudaSetDeviceFlags(cudaDeviceMapHost)吗?
  2. 我必须使用从函数cudaHostGetDevicePointer(&uva_ptr, ptr, 0)获得的指针来使用cudaMemcpy()吗?
  3. 函数cudaMemcpyPeer()有什么优势,如果没有任何优势,为什么需要它?
1个回答

10

统一虚拟寻址(Unified Virtual Addressing,UVA)使得所有CPU和GPU内存可以使用一个地址空间,因为它可以从指针值确定物理内存位置。

使用UVA进行点对点memcpy*

当UVA可行时,可以使用cudaMemcpy进行设备之间的点对点memcpy,因为CUDA能够推断哪个设备“拥有”哪个内存。执行点对点memcpy所需的指令通常如下:

//Check for peer access between participating GPUs: 
cudaDeviceCanAccessPeer(&can_access_peer_0_1, gpuid_0, gpuid_1);
cudaDeviceCanAccessPeer(&can_access_peer_1_0, gpuid_1, gpuid_0);

//Enable peer access between participating GPUs:
cudaSetDevice(gpuid_0);
cudaDeviceEnablePeerAccess(gpuid_1, 0);
cudaSetDevice(gpuid_1);
cudaDeviceEnablePeerAccess(gpuid_0, 0);

//UVA memory copy:
cudaMemcpy(gpu0_buf, gpu1_buf, buf_size, cudaMemcpyDefault);

无UVA的点对点内存拷贝

当不可能使用UVA时,可以通过cudaMemcpyPeer进行点对点内存拷贝。以下是一个示例:

// Set device 0 as current
cudaSetDevice(0); 
float* p0;
size_t size = 1024 * sizeof(float);
// Allocate memory on device 0
cudaMalloc(&p0, size); 
// Set device 1 as current
cudaSetDevice(1); 
float* p1;
// Allocate memory on device 1
cudaMalloc(&p1, size); 
// Set device 0 as current
cudaSetDevice(0);
// Launch kernel on device 0
MyKernel<<<1000, 128>>>(p0); 
// Set device 1 as current
cudaSetDevice(1); 
// Copy p0 to p1
cudaMemcpyPeer(p1, 1, p0, 0, size); 
// Launch kernel on device 1
MyKernel<<<1000, 128>>>(p1);

如您所见,在前一种情况下(可能使用UVA),您不需要指定不同指针所引用的设备,而在后一种情况下(不可能使用UVA),您必须明确说明指针所引用的设备。

该指令

cudaSetDeviceFlags(cudaDeviceMapHost);

该函数用于启用主机映射到设备内存,这是一种不同的事情,并涉及主机<->设备内存移动,而不是对等内存移动,这是您帖子的主题。

总之,回答您的问题:

  1. 不可以;
  2. 不可以;
  3. 尽可能启用UVA并使用cudaMemcpy(您不需要指定设备);否则,请使用cudaMemcpyPeer(您需要指定设备)。

谢谢!但是在调用cudaMemcpyPeer(p1, 1, p0, 0, size);之前,我必须在哪个上下文(setCudaDevice(0 or 1);)中,是0还是1? - Alex
1
@Alex 我认为这并不相关,因为您在调用cudaMemcpyPeer时指定了源设备和目标设备。 - Vitality
谢谢。但是如果我使用cudaMemcpyPeerAsync(,,,,stream);,那么我必须使用setCudaDevice();设置创建此流的上下文吗? - Alex
1
@Alex 我认为在这种情况下你不需要指定设备。从 How to define destination device stream in cudaMemcpyPeerAsync()? 的答案中可以明确地看到,_cudaMemcpyPeerAsync 调用将显示在分配给它的流(和设备)中_,特别是源设备。请参见 Multi-GPU Programming 第 20 页上的示例。 - Vitality
1
@Alex 你必须换个角度看问题。你必须确保作为cudaMemcpyPeerAsync参数的stream已经在点对点异步内存传输的源设备上创建。 - Vitality
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接