组调用
组函数(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调用是不必要的,没有任何作用。
相关链接: