跳到主要内容

组调用

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

单线程管理多个 GPU

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

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

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

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

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

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

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

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

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

相关链接:

聚合操作

group 语义也可以用于将多个 collective 操作合并到一次 MCCL launch 中执行。这有助于减少 MCCL launch 开销,因为它只对多个操作发生一次。初始化函数不能与其他初始化函数聚合,也不能与通信函数聚合。

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

在以下示例中,我们一次性启动一个 Broadcast 操作和两个 AllReduce 操作作为一次 MCCL launch 发起。

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 启动和聚合时,mcclGroupStartmcclGroupEnd 可以一次使用,也可以在每个级别使用。以下示例将不同层的 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 循环内的 mcclGroupStartmcclGroupEnd 调用是不必要的,没有任何作用。

相关链接:

非阻塞组操作

如果通信器通过 mcclCommInitRankConfig 被标记为非阻塞模式,group 函数也会相应地变为异步。在这种情况下,如果用户在一个组中发出多个 MCCL 操作,从 mcclGroupEnd() 返回可能并不意味着 MCCL 通信内核已经被提交到 MUSA Stream。如果 mcclGroupEnd() 返回 mcclSuccess,这意味着 MCCL 内核已经被提交到 MUSA Stream;如果它返回 mcclInProgress,这意味着 MCCL 内核正在后台被提交到 MUSA Stream。用户有责任确保在调用相关 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]);
}

相关链接: