3. 编程模型

3.1. 通讯器管理

3.1.1. mcclGetVersion

mcclResult_t mcclGetVersion(int* version);

获取当前MCCL版本号。

3.1.2. mcclGetUniqueId

mcclResult_t mcclGetUniqueId(mcclUniqueId* uniqueId);

获取ID号,用于在多进程场景中初始化的ID,由任意一个进程获取,并通过MPI或socket发送给其它进程用于初始化。多节点环境同样适用。

3.1.3. mcclCommInitRank

mcclResult_t mcclCommInitRank(mcclComm_t* comm, int nranks, mcclUniqueId commId, int rank);

创建通讯器, nranks 为通讯器总数, commId 为使用 mcclGetUniqueId 获取到的ID, rank 为此通讯器的编号,其最小值为 0,最大值为 nranks-1。 此通讯器所使用的设备为当前线程正在使用的设备。每个通讯器不能使用相同的设备。

3.1.4. mcclCommInitAll

mcclResult_t mcclCommInitAll(mcclComm_t* comm, int ndev, const int* devlist);

用于在单进程场景中一次性创建所有通讯器。 devlist 包含了每个rank使用的设备。

3.1.5. mcclCommDestroy

mcclResult_t mcclCommDestroy(mcclComm_t comm);

销毁通讯器,结束集合通讯,用于正常通讯流程的退出。

3.1.6. mcclCommAbort

mcclResult_t mcclCommAbort(mcclComm_t comm);

中止通讯器,结束集合通讯,常用于从致命异常(如网络故障、节点故障或进程故障)中恢复通讯。当异常出现时,调用此API,然后创建新的通信器重新通讯。

3.1.7. mcclGetErrorString

const char* mcclGetErrorString(mcclResult_t result);

获取返回值的字符串。

3.1.8. mcclCommGetAsyncError

mcclResult_t mcclCommGetAsyncError(mcclComm_t comm, mcclResult_t* asyncError);

获取异步操作的返回值。

3.1.9. mcclCommCount

mcclResult_t mcclCommCount(const mcclComm_t comm, int* count);

获取通讯器数量。

3.1.10. mcclCommMcDevice

mcclResult_t mcclCommMcDevice(const mcclComm_t comm, int* device);

获取当前通讯器所使用的 device

3.1.11. mcclCommUserRank

mcclResult_t mcclCommUserRank(const mcclComm_t comm, int* rank);

获取当前通讯器的 rank 编号。

3.1.12. 示例

3.1.12.1. 单进程

int nranks = 4;
mcclComm_t comms[nranks];
int devs[nranks] = {0, 1, 2, 3};
mcclCommInitAll(comms, nranks, devs);
//...
//OPs...
//...
// finalizing MCCL
for (int i = 0; i < nranks; ++i)
   mcclCommDestroy(comms[i]);

3.1.12.2. 多进程

mcclUniqueId id;
mcclComm_t comm;

//initializing MPI
MPICHECK(MPI_Init(&argc, &argv));
MPICHECK(MPI_Comm_rank(MPI_COMM_WORLD, &myRank));
MPICHECK(MPI_Comm_size(MPI_COMM_WORLD, &nRanks));

//get MCCL unique ID at rank 0 and broadcast it to all others
if (myRank == 0) mcclGetUniqueId(&id);
MPICHECK(MPI_Bcast((void *)&id, sizeof(id), MPI_BYTE, 0, MPI_COMM_WORLD));

mcSetDevice(myRank);
mcclCommInitRank(&comm, nRanks, id, myRank);

//Other Ops.

//finalizing MCCL
mcclCommDestroy(comm);

3.2. 集合通讯

所有rank都必须参与集合通讯,即每一种操作必须被所有rank调用,否则其它rank将等待。

3.2.1. mcclAllReduce

mcclResult_t mcclAllReduce(const void* sendbuff, void* recvbuff, size_t count, mcclDataType_t datatype, mcclRedOp_t op, mcclComm_t comm, mcStream_t stream);

在所有rank上都执行归约计算,最终所有rank将得到相同的结果,并保存在 recvbuff 中。 sendbuff 为输入源, count 为数据的个数, datatype 为数据类型, op 为操作类型, comm 为通信器对象, stream 为MXMACA的stream。

3.2.2. mcclBroadcast

mcclResult_t mcclBroadcast(const void* sendbuff, void* recvbuff, size_t count, mcclDataType_t datatype, int root, mcclComm_t comm, mcStream_t stream);
mcclResult_t mcclBcast(void* buff, size_t count, mcclDataType_t datatype, int root, mcclComm_t comm, mcStream_t stream);

mcclBroadcast 可将 sendbuff 中的数据复制到所有rank的 recvbuff 中(包括发送者的 recvbuff )。 count 为数据的个数, datatype 为数据类型, root 为发送者的rank编号, comm 为通信器对象, stream 为MXMACA的stream,对于接收者, sendbuff 可为空。

mcclBcastmcclBroadcast 相同,若发送者的 sendbuffrecvbuff 相同,则可以用 mcclBcast 代替 mcclBroadcast

3.2.3. mcclReduce

mcclResult_t mcclReduce(const void* sendbuff, void* recvbuff, size_t count, mcclDataType_t datatype, mcclRedOp_t op, int root, mcclComm_t comm, mcStream_t stream);

在rank为 root 的通讯器上执行归约计算,结果保存在 rootrecvbuff 中。对于其它rank, recvbuff 可为空。 对于发送方, root 为发送的目标rank。 root 为执行归约的通讯器rank号, comm 为通信器对象, stream 为MXMACA的stream。

3.2.4. mcclAllGather

mcclResult_t mcclAllGather(const void* sendbuff, void* recvbuff, size_t sendcount, mcclDataType_t datatype, mcclComm_t comm, mcStream_t stream);

将所有rank的 sendbuff 中的数据按rank顺序排列保存到每一个rank的 recvbuff 中,最终每个rank得到相同的结果。 sendcountsendbuff 的数据个数, recvbuff 的数据个数将会是 sendcount*NN 为所有rank的个数)。 datatype 为数据类型, comm 为通信器对象, stream 为MXMACA的stream。 调用者必须确保 recvbuff 有足够的空间。

3.2.5. mcclReduceScatter

mcclResult_t mcclReduceScatter(const void* sendbuff, void* recvbuff, size_t recvcount, mcclDataType_t datatype, mcclRedOp_t op, mcclComm_t comm, mcStream_t stream);

对所有rank的 sendbuff 进行归约,然后将结果分成 N 份,并按rank顺序分发给所有通讯器的 recvbuff 中。 recvcount 为接收数据的个数,发送数据的个数是 recvcount*Nop 为操作类型, comm 为通信器对象, stream 为MXMACA的stream。

3.2.6. mcclAllToAll

mcclResult_t mcclAllToAll(const void* sendbuff, void* recvbuff, size_t count, mcclDataType_t datatype, mcclComm_t comm, mcStream_t stream);

在所有rank上执行全交换操作,每个rank将 sendbuff 中的数据发送给所有其他rank,并从其他rank接收数据到 recvbuff 中。 每个rank发送和接收的数据量相同,均为 countdatatype 类型的数据。 最终,每个rank的 recvbuff 中将包含来自所有rank的数据,且数据顺序与rank编号一致。 sendbuff 为发送数据的缓冲区, recvbuff 为接收数据的缓冲区, count 为每个rank发送和接收的数据个数。 datatype 为数据类型, comm 为通信器对象, stream 为MXMACA的stream。 调用者必须确保 recvbuff 有足够的空间来存储所有rank发送的数据。

3.2.7. mcclAllToAllv

mcclResult_t mcclAllToAllv(const void* sendbuff, const size_t sendcounts[], const size_t sdispls[], void* recvbuff, const size_t recvcounts[], const size_t rdispls[], mcclDataType_t datatype, mcclComm_t comm, mcStream_t stream);

在所有rank上执行非均匀全交换操作,每个rank可以发送不同数量的数据给其他rank,并从其他rank接收不同数量的数据。 sendbuff 为发送数据的缓冲区, sendcounts 为数组, sendcounts[i] 表示发送给rank i 的数据个数。 sdispls 为数组, sdispls[i] 表示发送给rank i 的数据在sendbuff中的起始偏移量。 recvbuff 为接收数据的缓冲区, recvcounts 为数组, recvcounts[i] 表示从rank i 接收的数据个数。 rdispls 为数组, rdispls[i] 表示从rank i 接收的数据在 recvbuff 中的起始偏移量。 datatype 为数据类型, comm 为通信器对象, stream 为MXMACA的stream。 调用者必须确保 sendbuffrecvbuff 有足够的空间,且 sendcountssdisplsrecvcountsrdispls 的数组长度必须与通信器中的rank数量一致。

3.2.8. mcclAllToAlld

mcclResult_t mcclAllToAlld(const void* sendbuff[], const size_t sendcounts[], void* recvbuff[], const size_t recvcounts[], mcclDataType_t datatype, mcclComm_t comm, mcStream_t stream);

在所有rank上执行分布式全交换操作,每个rank发送多个数据块给其他rank,并从其他rank接收多个数据块。 sendbuffrecvbuff 是数组的数组,分别表示每个rank发送和接收的数据块。 sendcountsrecvcounts 分别指定每个rank发送和接收的数据块的大小。 sendbuff 为数组, sendbuff[i] 指向发送给rank i 的数据缓冲区, sendcounts 为数组, sendcounts[i] 表示发送给rank i 的数据个数。 recvbuff 为数组, recvbuff[i] 指向接收来自rank i 的数据缓冲区, recvcounts 为数组, recvcounts[i] 表示从rank i 接收的数据个数。 datatype 为数据类型, comm 为通信器对象, stream 为MXMACA的stream。 调用者必须确保 sendbuffrecvbuff 中的每个缓冲区有足够的空间,且 sendbuffrecvbuffsendcountsrecvcounts 的数组长度必须与通信器中的rank数量一致。

3.2.9. 示例

mcclGroupStart();
for (int i = 0; i < nranks; ++i) {
   mcclAllReduce((const void*)sendbuff[i], (void*)recvbuff[i], size, mcclFloat, mcclSum, comms[i], s[i]);
}
mcclGroupEnd();

mcclGroupStart();
for (int i = 0; i < nranks; ++i) {
   mcclBroadcast((const void*)sendbuff[i], (void*)recvbuff[i], size, mcclFloat, 0, comms[i], s[i]);
}
mcclGroupEnd();

mcclGroupStart();
for (int i = 0; i < nranks; ++i) {
   mcclReduce((const void*)sendbuff[i], (void*)recvbuff[i], size, mcclFloat, mcclSum, 0, comms[i], s[i]);
}
mcclGroupEnd();

mcclGroupStart();
for (int i = 0; i < nranks; ++i) {
   mcclAllGather((const void*)sendbuff[i], (void*)recvbuff[i], size, mcclFloat, comms[i], s[i]);
}
mcclGroupEnd();

mcclGroupStart();
for (int i = 0; i < nranks; ++i) {
   mcclAllReduceScatter((const void*)sendbuff[i], (void*)recvbuff[i], size, mcclFloat, mcclSum, comms[i], s[i]);
}
mcclGroupEnd();

mcclGroupStart();
for (int i = 0; i < nranks; ++i) {
   mcclAllToAll((const void*)sendbuff[i], (void*)recvbuff[i], size, mcclFloat, comms[i], s[i]);
}
mcclGroupEnd();

mcclGroupStart();
for (int i = 0; i < nranks; ++i) {
   mcclToAllAllv((const void*)sendbuff[i], sendcounts, sdispls, (void*)recvbuff[i], recvcounts, rdispls, mcclFloat, comms[i], s[i]);
}
mcclGroupEnd();

mcclGroupStart();
for (int i = 0; i < nranks; ++i) {
   mcclAllToAlld((const void**)sendbuff[i], sendcounts, (void**)recvbuff[i], recvcounts, mcclFloat, comms[i], s[i]);
}
mcclGroupEnd();

3.3. 组调用

组调用可以把一系列MCCL操作合并在一起放入后台执行,不同stream的操作可互不影响地执行。这样可以实现MCCL操作在不同stream并发执行。

3.3.1. mcclGroupStart

mcclResult_t mcclGroupStart();

开始一个组编程。在此之后的所有MCCL函数都不会立即执行,而是放入一个执行队列中等待执行。

3.3.2. mcclGroupEnd

mcclResult_t mcclGroupEnd();

结束组编程。立即启动队列中的所有操作,并根据不同stream放入不同队列执行。在所有操作完成之前,阻塞当前线程。

3.3.3. 示例

mcclGroupStart();
mcclSend(sendbuff[0], size, mcclFloat, 1, comms[0], s[0]);
mcclRecv(recvbuff[1], size, mcclFloat, 0, comms[1], s[1]);
mcclGroupEnd();

3.4. 点对点通讯

点对点的通讯,即把数据从一个rank复制给另一个rank,也就是将显存中的内容,由一个显卡复制到另一个显卡中。

3.4.1. mcclSend

mcclResult_t mcclSend(const void* sendbuff, size_t count, mcclDataType_t datatype, int peer, mcclComm_t comm, mcStream_t stream);

sendbuff 中的内存发送给接收端 peercount 为数据的个数, datatype 为数据类型。

comm 为通信器对象, stream 为MXMACA的stream。

3.4.2. mcclRecv

mcclResult_t mcclRecv(void* recvbuff, size_t count, mcclDataType_t datatype, int peer, mcclComm_t comm, mcStream _t stream);

peer 端接收数据到 recvbuff 中。 count 为数据的个数, datatype 为数据类型。

comm 为通信器对象, stream 为MXMACA的stream。

3.4.3. 示例

mcclSend(sendbuff[0], size, mcclFloat, 1, comms[0], s[0]);
mcclRecv(recvbuff[1], size, mcclFloat, 0, comms[1], s[1]);

3.5. 数据类型

MCCL的数据类型用于指定各操作原语所使用的数据类型,对应MXMACA的数据类型。对应关系如下:

  • mcclInt8:char

  • mcclChar:char

  • mcclUint8:unsigned char

  • mcclInt32:int

  • mcclInt:int

  • mcclUint32:unsigned int

  • mcclInt64:long

  • mcclUint64:unsigned long

  • mcclFloat16:half

  • mcclHalf:half

  • mcclFloat32:float

  • mcclFloat:float

  • mcclFloat64:double

  • mcclDouble:double

  • mcclBfloat16:bfloat16

3.6. 返回值类型

  • mcclSuccess:成功

  • mcclUnhandledMacaError:调用MXMACA API时返回错误

  • mcclSystemError:调用系统函数错误

  • mcclInternalError:MCCL内部错误,可能是内存错误

  • mcclInvalidArgument:参数错误

  • mcclInvalidUsage:使用错误,可能是用户编程导致

  • mcclRemoteError:多节点场景时,网络连接错误

3.7. 归约操作类型

  • mcclSum:求和

  • mcclProd:求乘积

  • mcclMax:求最大值

  • mcclMin:求最小值

  • mcclAvg:求平均值