7. MPI通讯协议

MPI(Message Passive Interface,消息传递接口)是一个为并行计算设计的通讯协议标准。 该标准定义了一组具有可移植性的接口。MPI的目标是高性能、大规模、可移植。MPI是非常流行的、高性能计算的主要模型之一,具有很好的可移植性、扩展性,并拥有完备的异步通讯功能。

7.1. 常用API

表 7.1 MPI常用API

函数

功能

MPI_Comm_rank

获取当前进程的秩

MPI_Finalize

终止MPI,释放MPI的资源

MPI_Init

环境初始化

MPI_Comm_size

获取MPI进程数量

MPI_Send/MPI_Recv

阻塞发送/接收

MPI_Isend/MPI_Irecv

非阻塞发送/接收,是MPI异步消息

MPI_Bcast/MPI_Ibcast

阻塞广播/非阻塞广播

MPI_Gather/MPI_Allgather

收集/全收集

MPI_Scatter

发散

MPI_Reduce/MPI_Allreduce

归约/全归约

MPI_Alltoall

所有进程互换数据

MPI_Barrier

栅栏函数

7.2. 使用示例

用户通常需要根据使用环境,选择合适的并行计算模型。在单机多设备环境中,可以使用MPI、OpenMP、MCCL编程模型,进行并行程序设计。 在多台主机,每台主机只有1个设备的环境中,适合使用MPI、MCCL编程模型,进行并行程序设计。 在多机多设备环境中,可以使用MPI(每个MPI进程控制1个设备)、MPI+OpenMP(每个MPI进程控制1台主机,通过OpenMP调用这台主机上的多个设备)、MCCL编程模型,进行并行程序设计。 如图 7.1 所示,MPI通用传输模型描述了数据是如何在不同进程中进行运算和传输的。

../_images/image7.png

图 7.1 MPI通用传输模型

MPI异步传输代码示例如下,该示例演示了在满足硬件条件时,MPI支持直接传输设备显存。

int myRank, nRanks;
int dev = 0, nDevs;
mcGetDeviceCount(&ndevs);
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myRank);
MPI_Comm_size(MPI_COMM_WORLD, &nRanks);

dev = myRank % nDevs; // Each process's device id.

// picking a GPU based on localRank, allocate device buffers
mcSetDevice(dev);
mcMalloc((void **)&d_sendbuff, size * sizeof(int));
mcMalloc((void **)&d_recvbuff, size * sizeof(int));
mcStreamCreate(&s);

int *sendbuff = d_sendbuff;
int *recvbuff = d_recvbuff;

#ifndef GPU_AWARE_MPI
// malloc host data
int *h_sendbuff = (int *)malloc(size * sizeof(int));
int *h_recvbuff = (int *)malloc(size * sizeof(int));
sendbuff = h_sendbuff;
recvbuff = h_recvbuff;
#endif

// prepare send buff
Kernel_gather<<<grid, block, 0, s>>>(d_sendbuff[, other_args]);
#ifndef GPU_AWARE_MPI
mcMemcpyAsync(sendbuff, d_sendbuff, size * sizeof(int), mcMemcpyDeviceToHost, s);
#endif
// exchange data
MPI_Irecv(recvbuff, nrecv, MPI_INT, neighbor, tag, MPI_COMM_WORLD, recv_request);
// stream synchronize before MPI_Isend
mcStreamSynchronize(s);
MPI_Isend(sendbuff, nsend, MPI_INT, neighbor, tag, MPI_COMM_WORLD, send_request);
// obtain recvbuff
MPI_Waitall(neighbors, recv_request, MPI_STATUS_IGNORE);
MPI_Waitall(neighbors, send_request, MPI_STATUS_IGNORE);
#ifndef GPU_AWARE_MPI
mcMemcpyAsync(d_recvbuff, recvbuff, size * sizeof(int), mcMemcpyHostToDevice, s);
#endif

myKenel<<<grid, block, 0, s>>>(d_recvbuff[, result]);

// cleanup
#ifndef GPU_AWARE_MPI
Free(h_sendbuff);
Free(h_recvbuff);
#endif
mcFree(d_sendbuff);
mcFree(d_recvbuff);

// mpi finalize
MPI_Finalize();

编译MPI程序

MPI程序的编译,通常只需要编译器在链接阶段加上 -lompi 等编译选项即可。

$ mxcc a.maca -lompi

运行MPI程序

$ mpirun -np 2 ./a.out

或在Slurm集群环境下,可使用srun运行程序,参见 2.3 基于Slurm集群的容器化应用

MPI程序性能分析

使用GPU加速计算的MPI程序,可以通过观察MPI数据传输与GPU内核计算是否重合,来推断程序性能是否有优化空间。 如下图所示,(a)中在进行核函数计算时,也同时进行MPI数据的收发;(b)中的MPI通讯与核函数计算在时间上并没有重叠。通讯与计算重叠后,程序运行效率大大提高。

../_images/image8.png

图 7.2 MPI传输与GPU计算