NCCL 和 MPI¶
API¶
NCCL API 和用法与 MPI 类似,但存在许多细微差异。以下列表总结了这些差异
每个进程使用多个设备¶
与 MPI 端点的概念类似,NCCL 不要求 rank 与进程 1:1 映射。一个 NCCL 通信器可能有很多 rank(因此,多个设备)与单个进程关联。因此,如果与 MPI 一起使用,单个 MPI rank(一个 NCCL 进程)可能具有多个与之关联的设备。
ReduceScatter 操作¶
ncclReduceScatter 操作类似于 MPI_Reduce_scatter_block 操作,而不是 MPI_Reduce_scatter 操作。MPI_Reduce_scatter 函数本质上是一个“向量”函数,而 MPI_Reduce_scatter_block(稍后定义以填充缺失的语义)提供的常规计数类似于镜像函数 MPI_Allgather。这是 MPI 的一个怪癖,出于合理的向后兼容性原因尚未修复,NCCL 不遵循该怪癖。
发送和接收计数¶
在许多集体操作中,MPI 允许不同的发送和接收计数及类型,只要 sendcount*sizeof(sendtype) == recvcount*sizeof(recvtype)。NCCL 不允许这样做,而是定义单个计数和单个数据类型。
对于 AllGather 和 ReduceScatter 操作,计数等于每个 rank 的大小,这是最小的大小;另一个计数等于 nranks*count。函数原型清楚地表明提供了哪个计数。ncclAllGather 以 sendcount 作为参数,而 ncclReduceScatter 以 recvcount 作为参数。
注意:当使用 ReduceScatter 和 AllGather 的组合执行或比较 AllReduce 操作时,将 sendcount 和 recvcount 定义为总计数除以 rank 数,如果它不是 rank 数的完美倍数,则正确计数向上舍入。
其他集体操作和点对点操作¶
NCCL 没有为 sendrecv、gather、gatherv、scatter、scatterv、alltoall、alltoallv、alltoallw 或邻居集体定义特定的动词。所有这些操作都可以简单地使用 ncclSend、ncclRecv 和 ncclGroupStart/ncclGroupEnd 的组合来表示,类似于它们如何可以用 MPI_Isend、MPI_Irecv 和 MPI_Waitall 表示。
ncclRecv 不支持 MPI_ANY_SOURCE 的等效项;必须始终提供特定的源 rank。同样,提供的接收计数必须与发送计数匹配。此外,没有消息标签的概念。
在 MPI 程序中使用 NCCL¶
NCCL 可以很容易地与 MPI 结合使用。NCCL 集体操作类似于 MPI 集体操作,因此,从 MPI 通信器创建 NCCL 通信器非常简单。因此,很容易使用 MPI 进行 CPU 到 CPU 通信,使用 NCCL 进行 GPU 到 GPU 通信。
但是,MPI 中的一些实现细节可能会在使用 MPI 程序内部的 NCCL 时导致问题。
MPI 进度¶
MPI 定义了一个进度的概念,这意味着 MPI 操作需要程序调用 MPI 函数(可能多次)才能取得进展并最终完成。
在某些实现中,一个 rank 上的进度可能需要在另一个 rank 上调用 MPI。虽然这通常对性能不利,但可以说这是一个有效的 MPI 实现。
因此,阻塞在 NCCL 集体操作上,例如调用 cudaStreamSynchronize,在某些情况下可能会造成死锁,因为在一个 rank 上不调用 MPI 可能会阻塞其他 rank,阻止它们到达将解除第一个 rank 上的 NCCL 集体操作阻塞的 NCCL 调用。
在这种情况下,cudaStreamSynchronize 调用应替换为如下循环
cudaError_t err = cudaErrorNotReady;
int flag;
while (err == cudaErrorNotReady) {
err = cudaStreamQuery(args->streams[i]);
MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &flag, MPI_STATUS_IGNORE);
}
使用 CUDA 感知 MPI 的 GPU 间通信¶
使用 NCCL 与 CUDA 感知 MPI 并发执行 GPU 间通信可能会造成死锁。
NCCL 创建设备间依赖关系,这意味着在启动后,NCCL 内核将等待(并可能阻塞 CUDA 设备),直到通信器中的所有 rank 都启动它们的 NCCL 内核。CUDA 感知 MPI 也可能在设备之间创建此类依赖关系,具体取决于 MPI 实现。
因此,不能保证同时使用 MPI 和 NCCL 在同一组 CUDA 设备之间执行传输是安全的。