内核运行时CUDA内存上的MVAPICH死锁

问题描述:

我试图获得一个与MVAPICH CUDA8一起工作的MPI-CUDA程序。我之前用openMPI成功运行过该程序,但是我想测试一下如果使用MVAPICH获得更好的性能。不幸的是,如果一个CUDA内核在使用MVAPICH的同时运行,那么程序会停留在MPI_Isend中。内核运行时CUDA内存上的MVAPICH死锁

我下载MVAPICH2-2.2并建立它与配置标志

--enable-CUDA - 禁用MCAST

,使MPI CUDA的内存调用源。 mcast被禁用,因为我无法在没有标志的情况下编译它。当在同一时间没有CUDA内核运行

export MV2_USE_CUDA=1 
export MV2_GPUDIRECT_GDRCOPY_LIB=/path/to/gdrcopy/ 
export MV2_USE_GPUDIRECT=1 

MPI_Isend/recv的做工精细:

我用下面的标志运行应用程序之前。但是在我的程序中,MPI在内核运行时发送和接收来自GPU内存的数据并且接收GPU内存的数据是非常重要的。

我想出了两种可能的原因。首先,由于某些原因,MVAPICH尝试运行自己的CUDA内核以从GPU内存发送数据,并且该内核未被调度,因为GPU已经被充分利用。第二种可能性:MVAPICH在某处使用cudaMemcpy(而不是异步版本),这会阻塞,直到内核完成执行。

有人可以确认我的一个假设吗? MVAPICH中有一个标志解决了我不知道的这个问题吗?

编辑:

这里是 “simpel” 的代码,说明我的问题。在使用openMPI执行代码时,它会正确执行并终止。有了mvapich2,它会在标记为MPI_Send函数时发生死锁。

#include <stdio.h> 
#include <stdlib.h> 
#include <cuda.h> 
#include <mpi.h> 


__global__ void kernel(double * buffer, int rank) 
{ 
    volatile double *buf = buffer; 
    if(rank == 0){ 
     while(buf[0] != 3){} 
    } else { 
     while(buf[0] != 2){} 
    } 
} 


int main(int argc, char **argv) 
{ 
    double host_buffer[1]; 
    MPI_Init(&argc, &argv); 
    int world_size, world_rank; 
    MPI_Comm_size(MPI_COMM_WORLD, &world_size); 
    MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); 

    printf("Im rank %d\n", world_rank); 
    cudaSetDevice(world_rank); 

    double * dev_buffer; 
    cudaError_t err = cudaMalloc(&dev_buffer, sizeof(double)); 
    if(world_rank == 0){ 
     host_buffer[0] = 1; 
     cudaError_t err = cudaMemcpy(dev_buffer, host_buffer, sizeof(double), cudaMemcpyHostToDevice); 
     MPI_Send(dev_buffer, 1, MPI_DOUBLE, 1, 0, MPI_COMM_WORLD); 
     printf("[%d]First send does not deadlock\n", world_rank); 
    }else { 
     MPI_Recv(dev_buffer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); 
     printf("[%d]Received first message\n", world_rank); 
    } 

    cudaStream_t stream, kernel_stream; 
    cudaStreamCreate(&stream); 
    cudaStreamCreate(&kernel_stream); 

    printf("[%d]launching kernel\n", world_rank); 
    kernel<<<208, 128, 0, kernel_stream>>>(dev_buffer, world_rank); 

    if(world_rank == 0){ 
     //rank 0 
     host_buffer[0] = 2; 
     cudaMemcpyAsync(
      dev_buffer, host_buffer, sizeof(double), 
      cudaMemcpyHostToDevice, 
      stream 
     ); 
     cudaStreamSynchronize(stream); 

     printf("[%d]Send message\n", world_rank); 
     MPI_Send(dev_buffer, 1, MPI_DOUBLE, 1, 0, MPI_COMM_WORLD); //mvapich2 deadlocks here 
     printf("[%d]Message sent\n", world_rank); 

     printf("[%d]Receive message\n", world_rank); 
     MPI_Recv(dev_buffer, 1, MPI_DOUBLE, 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); 
     printf("[%d]Message received\n", world_rank); 

     cudaStreamSynchronize(kernel_stream); 
     printf("[%d]kernel finished\n", world_rank); 

    } else { 
     //rank 1 
     printf("[%d]Receive message\n", world_rank); 
     MPI_Recv(dev_buffer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); 
     printf("[%d]Message received\n", world_rank); 

     cudaStreamSynchronize(kernel_stream); 
     printf("[%d]kernel finished\n", world_rank); 

     host_buffer[0] = 3; 
     cudaMemcpyAsync(
      dev_buffer, host_buffer, sizeof(double), 
      cudaMemcpyHostToDevice, 
      stream 
     ); 
     cudaStreamSynchronize(stream); 

     printf("[%d]Send message\n", world_rank); 
     MPI_Send(dev_buffer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD); 
     printf("[%d]Message sent\n", world_rank); 

    } 
    printf("[%d]Stopped execution\n", world_rank); 
    MPI_Finalize(); 
} 
+1

一个小型复制器可以改善这个问题:) – OMGtechy

+0

添加了一个简单的代码示例,说明我的问题。 – kusterl

+0

这看起来应该永远不会工作。与OpenMPI一起工作的事实可能比其他任何事情都更为偶然。 – talonmies

我回到了这个问题,并使用gdb来调试代码。

显然,问题是在src/mpid/ch3/channels/mrail/src/gen2/ibv_send.c中实现的MVAPICH2的热切协议。 eager协议使用不带异步的cuda_memcpy,直到内核执行完成为止。

通过将MV2_IBA_EAGER_THRESHOLD 1传递给mpirun,问题中发布的程序正常运行。这可以防止MPI使用渴望的协议并使用rendez-vous协议。

修补MVAPICH2源代码的确也解决了这个问题。我改变了同步cudaMemcpys到cudaMemcpyAsync中的文件

  • SRC/MPID/CH3 /信道/ mrail/SRC /第二代/ ibv_send.c
  • SRC/MPID/CH3 /信道/ mrail/SRC /第二代/ ibv_recv.c
  • SRC/MPID/CH3/SRC/ch3u_request.c

在第三文件中的改变时,才需要对MPI_Isend/MPI_Irecv。其他MPI函数可能需要一些额外的代码更改。