跳到主要内容

组调用

组函数(mcclGroupStart/mcclGroupEnd)可以用来将多个调用合并为一个。这有三个用途:管理单个线程上的多个GPU(以避免死锁)、聚合通信操作以提高性能,或合并多个发送/接收点对点操作(见点对点通信部分)。所有三种用途可以结合使用,有一个例外:对mcclCommInitRank()的调用不能与其他调用合并。

单线程管理多个GPU

当单个线程管理多个设备时,必须使用组语义。这是因为每个MCCL调用可能需要阻塞,等待其他线程/等级到达,然后才能在给定的流上有效地发布MCCL操作。因此,像下面这样在多个设备上的简单循环可能会在第一个调用上阻塞,等待其他调用:

for (int i=0; i<nLocalDevs; i++) {
mcclAllReduce(..., comm[i], stream[i]);
}

要定义这些调用是同一集体操作的一部分,应使用mcclGroupStart和mcclGroupEnd:

mcclGroupStart();
for (int i=0; i<nLocalDevs; i++) {
mcclAllReduce(..., comm[i], stream[i]);
}
mcclGroupEnd();

这将告诉MCCL将mcclGroupStart和mcclGroupEnd之间的所有调用视为对多个设备的单个调用。

注意:当在组内调用时,流操作(如mcclAllReduce)可能在未在流上排队操作的情况下返回。因此,只能在mcclGroupEnd返回后调用流操作,如musaStreamSynchronize。

组调用还必须用于创建一个通信器,当一个线程管理多个设备时:

mcclGroupStart();
for (int i=0; i<nLocalDevs; i++) {
musaSetDevice(device[i]);
mcclCommInitRank(comms+i, nranks, commId, rank[i]);
}
mcclGroupEnd();

注意:与MCCL 1.x不同,在组内的每个MCCL通信调用之前不需要设置MUSA设备,但在组内调用mcclCommInitRank时仍然需要。

相关链接:

聚合操作(2.2及以后版本)

组语义还可以用来在单个MCCL启动中执行多个集体操作。这有助于减少启动开销,换句话说,就是延迟,因为它只对多个操作发生一次。初始化函数不能与其他初始化函数聚合,也不能与通信函数聚合。

通过在mcclGroupStart / mcclGroupEnd部分内有多个对MCCL的调用,可以简单地聚合集体操作。

在以下示例中,我们一次性启动一个广播和两个allReduce操作作为一个单一的MCCL启动。

mcclGroupStart();
mcclBroadcast(sendbuff1, recvbuff1, count1, datatype, root, comm, stream);
mcclAllReduce(sendbuff2, recvbuff2, count2, datatype, comm, stream);
mcclAllReduce(sendbuff3, recvbuff3, count3, datatype, comm, stream);
mcclGroupEnd();

允许将聚合与多GPU启动结合使用,并在组启动中使用不同的通信器,如在单线程管理多个GPU主题中所示。当结合多GPU启动和聚合时,mcclGroupStart和mcclGroupEnd可以一次使用,也可以在每个级别使用。以下示例将不同层的allReduce操作和多个MUSA设备上的allReduce操作分组:

mcclGroupStart();
for (int i=0; i<nlayers; i++) {
mcclGroupStart();
for (int g=0; g<ngpus; g++) {
mcclAllReduce(sendbuffs[g]+offsets[i], recvbuffs[g]+offsets[i], counts[i], datatype[i], comms[g], streams[g]);
}
mcclGroupEnd();
}
mcclGroupEnd();

注意:MCCL操作只会在最后一次调用mcclGroupEnd时作为一个整体启动。for循环内的mcclGroupStart和mcclGroupEnd调用是不必要的,没有任何作用。

相关链接:

非阻塞组操作

如果通信器通过mcclCommInitRankConfig被标记为非阻塞,组函数相应地变为异步。在这种情况下,如果用户在一个组中发出多个MCCL操作,从mcclGroupEnd()返回可能并不意味着MCCL通信内核已经被发布到MUSA流。如果mcclGroupEnd()返回mcclSuccess,这意味着MCCL内核已经被发布到流;如果它返回mcclInProgress,这意味着MCCL内核正在后台被发布到流。用户有责任确保在调用相关MUSA调用(例如musaStreamSynchronize)之前,通信器的状态变为mcclSuccess:

mcclGroupStart();
for (int g=0; g<ngpus; g++) {
mcclAllReduce(sendbuffs[g]+offsets[i], recvbuffs[g]+offsets[i], counts[i], datatype[i], comms[g], streams[g]);
}
ret = mcclGroupEnd();
if (ret == mcclInProgress) {
for (int g=0; g<ngpus; g++) {
do {
mcclCommGetAsyncError(comms[g], &state);
} while (state == mcclInProgress);
}
} else if (ret == mcclSuccess) {
/* 成功发布 */
printf("MCCL内核发布成功\n");
} else {
/* 发生错误 */
reportErrorAndRestart();
}

for (int g=0; g<ngpus; g++) {
musaStreamSynchronize(streams[g]);
}

相关链接: