| 249 | } |
| 250 | |
| 251 | Status NcclManager::GetCommunicator(NcclManager::Collective* collective, |
| 252 | NcclManager::Communicator** communicator) { |
| 253 | // Sort by global rank to make ordering of participants deterministic. |
| 254 | std::sort(collective->participants.begin(), collective->participants.end(), |
| 255 | [](const std::unique_ptr<Participant>& a, |
| 256 | const std::unique_ptr<Participant>& b) { |
| 257 | if (a->global_rank == b->global_rank) { |
| 258 | return a->executor < b->executor; |
| 259 | } |
| 260 | return a->global_rank < b->global_rank; |
| 261 | }); |
| 262 | |
| 263 | mutex_lock l(mu_); |
| 264 | |
| 265 | if (collective->communicator_key.empty()) { |
| 266 | // For single-node collectives, when the caller does not specify a |
| 267 | // `communicator_key`, we identify a communicator uniquely by the set of |
| 268 | // devices participating in the collective. For example, if a collective is |
| 269 | // for GPUs 0, 1, and 2 then this will scan to find the communicator for |
| 270 | // GPUs 0, 1, and 2. |
| 271 | // |
| 272 | // Note that each executor identifies a context on one device, so this is |
| 273 | // the same as getting the communicator connecting the devices in the |
| 274 | // collective. A device can be in different communicators as well - for |
| 275 | // example, a communicator for GPUs 0 and 1 is separate from one for GPUs 0, |
| 276 | // 1, and 2. |
| 277 | // |
| 278 | // Since it's expected that a small number of distinct communicators will |
| 279 | // be needed, communicators_ is not garbage collected currently. |
| 280 | // |
| 281 | // Launching of kernels must be serialized so that, given collectives A and |
| 282 | // B, and an order of them (e.g., A before B), then for each comm_stream |
| 283 | // involved, the kernel for A is launched before the kernel for B. This is |
| 284 | // guaranteed currently be a global mutex controlling additions of the |
| 285 | // kernels to per-stream launch queues. The launch queues are processed by |
| 286 | // LoopKernelLaunches. |
| 287 | for (auto& comm : communicators_) { |
| 288 | if (comm->num_devices == collective->num_global_devices) { |
| 289 | int i; |
| 290 | for (i = 0; i < collective->num_local_devices; ++i) { |
| 291 | if (comm->members[i].nccl_stream->executor != |
| 292 | collective->participants[i]->executor) { |
| 293 | break; |
| 294 | } |
| 295 | } |
| 296 | if (i == collective->num_local_devices) { |
| 297 | *communicator = comm.get(); |
| 298 | return Status::OK(); |
| 299 | } |
| 300 | } |
| 301 | } |
| 302 | } else { |
| 303 | #if NCCL_MAJOR < 2 |
| 304 | return errors::Internal( |
| 305 | "Cannot use multi-node NCCL collectives with NCCL 1.x"); |
| 306 | #endif |
| 307 | if (collective->communicator_key.size() != NCCL_UNIQUE_ID_BYTES) { |
| 308 | return errors::Internal("Expected communicator_key of size ", |
nothing calls this directly
no test coverage detected