Skip to main content

MCCL 概览

摩尔线程集合通信库(MCCL)可实现针对摩尔线程 GPU 和网络进行性能优化的多 GPU 和多节点通信基元。MCCL 提供了 AllReduce、Broadcast、Reduce、AllGather、ReduceScatter 以及点对点 send/receive 等原语,这些原语均经过优化,可通过节点内的 PCIe 和 MTLink 高速互联以及节点间的 InfiniBand 网络实现高带宽和低延迟。

MCCL 支持节点内和跨节点通信,可以实现拓扑的自动检测,计算最优路径,最终实现 GPUs 之间的高效传输。

MCCL 特性

  • 拓扑感知:自动检测 GPU 拓扑,计算最优通信路径
  • 高带宽低延迟:针对 PCIe、MTLink、InfiniBand 优化
  • 多种原语:AllReduce、Broadcast、Reduce、AllGather、ReduceScatter、P2P Send/Receive
  • 易于集成:简单的 C API,遵循 MPI 集体通信规范
  • stream 集成:接受 MUSA stream 参数,与 MUSA 编程模型直接集成
  • 多编程模型:支持单线程多卡、多线程(每 GPU 一线程)、多进程(MPI)

MCCL 框架

MCCL 框架

图 1 MCCL 框架图


节点内通信

节点内 GPU 之间进行数据传输,主要的物理介质是 PCIe 和 MTLink,涉及到的主要技术有 GPUDirect P2PGPUDirect P2P 避免数据通过 CPU 进行中转,降低延时,同时可以利用 GPU 高速互联的带宽。

PCIe P2P 示意图

图 2 PCIe 的 P2P 示意图


跨节点通信

节点间 GPU 进行数据传输,主要的物理介质有普通以太网或者 InfiniBand,涉及的技术有 GPUDirect RDMA

跨节点传输

图 3 跨节点传输


MCCL 支持的集合通信

Broadcast

Broadcast 将数据从一个 GPU(根 GPU)分发到所有 GPU。所有 GPU 的内容和根 GPU 的内容一致。

Broadcast 运算示意图

图 4 Broadcast 运算示意图

Reduce

Reduce 把所有 GPU 的数据聚合到一个 GPU,并对数据进行指定的规约操作。所有 GPU 必须发送同等大小的数据。此外,数据必须以能够执行 reduce 操作的方式进行对齐。MCCL 支持四种操作:SumProdMaxMin。所有 GPU 规约计算的结果会压缩到根 GPU。

Reduce 运算示意图

图 5 Reduce 运算示意图

AllGather

AllGather 收集所有 GPU 的数据,并发送到所有 GPU。所有发送的数据块大小必须相同。目的 GPU 的数据由每个 GPU 组合而成。

AllGather 运算示意图

图 6 AllGather 运算示意图

AllReduce

AllReduce 的操作和 Reduce 一样,但是需要向所有 GPU 发送相同的数据。

AllReduce 运算示意图

图 7 AllReduce 运算示意图

ReduceScatter

ReduceScatter 操作和 AllReduce 类似,不同的是最终发送到所有 GPU 上的是数据的一列。

ReduceScatter 运算示意图

图 8 ReduceScatter 运算示意图


MCCL 使用实例

单线程多卡

下面的例子展示单线程使用 MCCL 进行多卡计算,将多卡的算力充分利用起来。

#include <stdio.h>
#include "musa_runtime.h"
#include "mccl.h"

#define MUSACHECK(cmd) do { \
musaError_t err = cmd; \
if (err != musaSuccess) { \
printf("Failed: MUSA error %s:%d '%s'\n", \
__FILE__,__LINE__,musaGetErrorString(err)); \
exit(EXIT_FAILURE); \
} \
} while(0)


#define MCCLCHECK(cmd) do { \
mcclResult_t res = cmd; \
if (res != mcclSuccess) { \
printf("Failed, MCCL error %s:%d '%s'\n", \
__FILE__,__LINE__,mcclGetErrorString(res)); \
exit(EXIT_FAILURE); \
} \
} while(0)


int main(int argc, char* argv[])
{
mcclComm_t comms[4];

//managing 4 devices
int nDev = 4;
int size = 32*1024*1024;
int devs[4] = { 0, 1, 2, 3 };


//allocating and initializing device buffers
float** sendbuff = (float**)malloc(nDev * sizeof(float*));
float** recvbuff = (float**)malloc(nDev * sizeof(float*));
musaStream_t* s = (musaStream_t*)malloc(sizeof(musaStream_t)*nDev);


for (int i = 0; i < nDev; ++i) {
MUSACHECK(musaSetDevice(i));
MUSACHECK(musaMalloc(sendbuff + i, size * sizeof(float)));
MUSACHECK(musaMalloc(recvbuff + i, size * sizeof(float)));
MUSACHECK(musaMemset(sendbuff[i], 1, size * sizeof(float)));
MUSACHECK(musaMemset(recvbuff[i], 0, size * sizeof(float)));
MUSACHECK(musaStreamCreate(s+i));
}


//initializing MCCL
MCCLCHECK(mcclCommInitAll(comms, nDev, devs));


//calling MCCL communication API. Group API is required when using
//multiple devices per thread
MCCLCHECK(mcclGroupStart());
for (int i = 0; i < nDev; ++i)
MCCLCHECK(mcclAllReduce((const void*)sendbuff[i], (void*)recvbuff[i], size, mcclFloat, mcclSum,
comms[i], s[i]));
MCCLCHECK(mcclGroupEnd());

//synchronizing on MUSA streams to wait for completion of MCCL operation
for (int i = 0; i < nDev; ++i) {
MUSACHECK(musaSetDevice(i));
MUSACHECK(musaStreamSynchronize(s[i]));
}

//free device buffers
for (int i = 0; i < nDev; ++i) {
MUSACHECK(musaSetDevice(i));
MUSACHECK(musaFree(sendbuff[i]));
MUSACHECK(musaFree(recvbuff[i]));
}

//finalizing MCCL
for(int i = 0; i < nDev; ++i)
mcclCommDestroy(comms[i]);

printf("Success \n");
return 0;
}

MCCL 还有其他的使用方式,如:

  • 多线程:每个 GPU 使用一个线程
  • 多进程:MPI 与 GPU 上的多线程操作相结合

具体可以参考 MCCL 的文档。


FAQ

多任务并行因 GPU 资源竞争导致死锁

错误描述:多个任务并行,每个任务同时使用多卡进行分布式计算或通信,且必须进行多卡间同步时,有可能发生因 GPU 计算资源竞争而导致的死锁。比如任务 1 和任务 2(可以是 process、thread、stream)都同时使用 GPU0-7 进行计算或通信,每个 Step 都需要进行一次 8 卡同步,则有可能出现以下死锁情景:

  • 任务 1 优先抢占到 GPU 0、1、2、3 资源,成功启动 Kernel1,但在 GPU 4、5、6、7 上,Kernel1 等待调度,同步被阻塞。
  • 任务 2 优先抢占到 GPU 4、5、6、7 资源,成功启动 Kernel2,但在 GPU 0、1、2、3 上,Kernel2 等待调度,同步被阻塞。

解决方法:应用侧保证各 GPU 上的调度时序,以确保任务 1 或任务 2 同时在所有 GPU 上被优先调度。具体如下:

  • 任务 1 及任务 2,在同一块 GPU 上,使用同一个 musa MUSA stream 以保证 kernel 调度串行化,同时所有 GPU 上,使用相同的调度时序,即 Kernel1-Kernel2 或 Kernel2-Kernel1。
  • 在不同任务间添加 barrier,确保任务间串行占用 GPU 资源,即任务 1-设备同步 - 任务 2。

多任务并行调度