跳到主要内容

创建通信器

在创建通信器时,必须为参与该通信器的 n 个 MUSA 设备都分配一个 0 到 n-1 之间的唯一 rank。不支持将同一个 MUSA 设备在同一个 MCCL 通信器中重复用作不同的 rank,这可能会导致挂起。

给定 rank 到 MUSA 设备的静态映射,mcclCommInitRank()mcclCommInitAll() 函数将创建通信器对象,每个通信器对象都与一个固定的 rank 和 MUSA 设备相关联。然后,这些对象将被用来启动通信操作。

在调用 mcclCommInitRank() 之前,您需要首先创建一个独特的对象,该对象将被所有进程和线程用来同步并理解它们是同一个通信器的一部分。这是通过调用 mcclGetUniqueId() 函数完成的。

mcclGetUniqueId()函数返回一个 ID,该 ID 必须使用任何 CPU 通信系统广播到所有参与的线程和进程,例如,将 ID 指针传递给多个线程,或者使用 MPI 或其他并行环境(例如,套接字)广播到其他进程。

您还可以调用 mcclCommInitAll 操作,在单个进程中一次性创建 n 个通信器对象。由于它仅限于单个进程,因此此函数不允许节点间通信。mcclCommInitAll 等同于调用 mcclGetUniqueIdmcclCommInitRank 的组合。

以下示例代码是 mcclCommInitAll 的简化实现。

mcclResult_t mcclCommInitAll(mcclComm_t* comm, int ndev, const int* devlist) {
mcclUniqueId Id;
mcclGetUniqueId(&Id);
mcclGroupStart();
for (int i=0; i<ndev; i++) {
musaSetDevice(devlist[i]);
mcclCommInitRank(comm+i, ndev, Id, i);
}
mcclGroupEnd();
}

相关链接:

同时使用多个 MCCL 通信器

每个设备使用多个 MCCL 通信器需要通过 MUSA stream 依赖性或同步将所有通信操作的顺序序列化到一致的全局顺序,否则可能会导致死锁。为了保持无死锁,用户必须确保所有设备主机端启动的顺序匹配。这最容易通过使用每个设备单个主机线程的确定性顺序来实现。例如:

mcclAllReduce(..., comm1, stream1); // 所有 rank 首先执行这个
mcclAllReduce(..., comm2, stream2); // 然后执行这个

当 MCCL 在 MUSA Graph 中被捕获时,相同的规则适用于捕获时间和启动时间。在捕获时间,这意味着同一 MUSA Graph 中的 MCCL 调用必须以相同的顺序被捕获:

// stream1 和 stream2 都在同一图中捕获
mcclAllReduce(..., comm1, stream1); // 所有 rank 首先执行这个
mcclAllReduce(..., comm2, stream2); // 然后执行这个

在图启动时间,不同的图必须以全局一致的顺序启动:

musaGraphLaunch(graph1, stream1); // 所有 rank 首先执行这个
musaGraphLaunch(graph2, stream2); // 然后执行这个

操作的隐式排序是使用 MUSA 启动完成事件创建的,这允许两个通信器的内核并行执行。

销毁通信器

一旦通信器被最终确定,下一步是释放所有资源,包括通信器本身。与通信器相关的本地资源可以使用 mcclCommDestroy 销毁。如果通信器的状态在调用 mcclCommDestroy 时为 mcclSuccess,则调用保证是非阻塞的;否则 mcclCommDestroy 可能会阻塞。在所有情况下,mcclCommDestroy 调用将释放通信器的资源并返回,通信器在 mcclCommDestroy 返回后不应再被访问。

相关链接:mcclCommDestroy()