内核运行时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();
}
我回到了这个问题,并使用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函数可能需要一些额外的代码更改。
一个小型复制器可以改善这个问题:) – OMGtechy
添加了一个简单的代码示例,说明我的问题。 – kusterl
这看起来应该永远不会工作。与OpenMPI一起工作的事实可能比其他任何事情都更为偶然。 – talonmies