/************************************************************************* * Copyright (c) 2015-2019, NVIDIA CORPORATION. All rights reserved. * Modifications Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. * * See LICENSE.txt for license information ************************************************************************/ #include "group.h" #include "debug.h" #include "enqueue.h" #define MAX_ASYNC_OPS 128 thread_local pthread_t ncclGroupThreads[MAX_ASYNC_OPS]; thread_local int ncclGroupIndex = 0; thread_local int ncclGroupMode = 0; thread_local ncclResult_t ncclGroupError = ncclSuccess; bool ncclAsyncMode() { return ncclGroupMode > 0; } ncclResult_t ncclAsyncErrCheck(ncclResult_t ret) { if (ncclGroupError == ncclSuccess || ret != ncclSuccess) ncclGroupError = ret; return ret; } struct ncclInitArgs { ncclInitFunc_t func; int cudaDev; ncclComm_t* newcomm; int ndev; ncclUniqueId commId; int myrank; }; struct ncclCollArgs { ncclComm_t comm; }; enum ncclAsyncFuncType { ASYNC_FUNC_INVALID = 0, ASYNC_FUNC_INIT = 1, ASYNC_FUNC_COLL = 2, }; struct ncclAsyncArgs { ncclResult_t ret; enum ncclAsyncFuncType funcType; union { ncclCollArgs coll; ncclInitArgs init; }; }; thread_local struct ncclAsyncArgs ncclGroupArgs[MAX_ASYNC_OPS]; #define CHECK(a) do { \ if ((args->ret = (a)) != ncclSuccess) { \ INFO(NCCL_INIT,"%s:%d -> %d [Async thread]", __FILE__, __LINE__, args->ret); \ return args; \ } \ } while(0) void* ncclAsyncThreadMain(void* args_) { struct ncclAsyncArgs* args = (struct ncclAsyncArgs*)args_; CHECK(args->init.func(args->init.newcomm, args->init.ndev, args->init.commId, args->init.myrank, args->init.cudaDev)); return args; } ncclResult_t ncclAsyncInit(ncclInitFunc_t func, ncclComm_t* newcomm, int ndev, ncclUniqueId commId, int myrank, int cudaDev) { if (ncclGroupIndex >= MAX_ASYNC_OPS) { WARN("Too many async operations in progress, max is %d", MAX_ASYNC_OPS); return ncclAsyncErrCheck(ncclInvalidUsage); } int index = ncclGroupIndex++; struct ncclAsyncArgs* args = ncclGroupArgs+index; args->funcType = ASYNC_FUNC_INIT; args->init.func = func; args->init.cudaDev = cudaDev; args->init.newcomm = newcomm; args->init.ndev = ndev; memcpy(&args->init.commId, &commId, sizeof(commId)); args->init.myrank = myrank; return ncclSuccess; } ncclResult_t ncclAsyncColl(ncclComm_t comm) { struct ncclAsyncArgs* args = ncclGroupArgs; for (int i=0; icoll.comm == comm) return ncclSuccess; args++; } if (ncclGroupIndex >= MAX_ASYNC_OPS) { WARN("Too many async operations in progress, max is %d", MAX_ASYNC_OPS); return ncclAsyncErrCheck(ncclInvalidUsage); } ncclGroupIndex++; args->funcType = ASYNC_FUNC_COLL; args->coll.comm = comm; return ncclSuccess; } NCCL_API(ncclResult_t, ncclGroupStart); ncclResult_t ncclGroupStart() { ncclGroupMode++; return ncclSuccess; } NCCL_API(ncclResult_t, ncclGroupEnd); ncclResult_t ncclGroupEnd() { ncclGroupMode--; if (ncclGroupMode > 0) return ncclSuccess; int savedDev; CUDACHECK(hipGetDevice(&savedDev)); int done = ncclGroupIndex; int doneArray[MAX_ASYNC_OPS]; for (int i=0; ifuncType == ASYNC_FUNC_INIT) { pthread_create(ncclGroupThreads+i, NULL, ncclAsyncThreadMain, args); } } /* Collectives are done in three steps : * 1. Barrier Check In. Only the last call may call cudaLaunchKernel[cooperative] * 2. Barrier Wait. No CUDA call is permitted * 3. Enqueue Events. CUDA event wait/enqueue. * This is needed because step 2 cannot call any CUDA primitive, otherwise if * hipFree happens between 1 and 3, it could block that CUDA call and * prevent some ranks from launching their network threads, which would * prevent the NCCL call from completing, blocking the hipFree call. */ for (int i=0; ifuncType == ASYNC_FUNC_COLL) { if (args->coll.comm->userStream == NULL) CUDACHECKGOTO(hipSetDevice(args->coll.comm->cudaDev), ret, end); NCCLCHECKGOTO(ncclBarrierEnqueue(args->coll.comm), ret, end); } } for (int i=0; ifuncType == ASYNC_FUNC_COLL) { CUDACHECKGOTO(hipSetDevice(args->coll.comm->cudaDev), ret, end); NCCLCHECKGOTO(ncclBarrierEnqueueWait(args->coll.comm), ret, end); } } for (int i=0; ifuncType == ASYNC_FUNC_COLL) { if (args->coll.comm->userStream == NULL) CUDACHECKGOTO(hipSetDevice(args->coll.comm->cudaDev), ret, end); NCCLCHECKGOTO(ncclEnqueueEvents(args->coll.comm), ret, end); doneArray[i] = 1; done--; } } /* For init, since we use threads, we just wait for threads to complete */ while (done) { for (int i=0; ifuncType == ASYNC_FUNC_INIT && doneArray[i] == 0) { int err = pthread_tryjoin_np(ncclGroupThreads[i], NULL); if (err == EBUSY) continue; if (err != 0) ret = ncclSystemError; if (args->ret != ncclSuccess) ret = args->ret; doneArray[i] = 1; done--; } } } goto end; group_cleanup: if (ret != ncclSuccess) { // At least one call in the group failed. Since we want to make that group // an atomic operation, we need to cancel all operations. for (int i=0; ifuncType == ASYNC_FUNC_INIT && doneArray[i] == 0) { if (args->init.newcomm) NCCLCHECK(ncclCommDestroy(*args->init.newcomm)); *args->init.newcomm = NULL; } else { struct ncclComm* comm = args->coll.comm; for (int c=0; cnChannels; c++) { struct ncclChannel* channel = comm->channels+c; for (int i=0; icollCount; i++) { channel->collectives[(channel->collStart + i)%NCCL_MAX_OPS].active = 0; } channel->collFifoTail = channel->collStart; channel->collCount = 0; } /* Cancel all proxy ops : mark them as ncclProxyOpNone and they should be freed later on */ struct ncclProxyState* state = &comm->proxyState; struct ncclProxyArgs *op, *start; pthread_mutex_lock(&state->mutex); op = start = state->ops; while (op) { if (op->opCount >= comm->lastOpCount) op->state = ncclProxyOpNone; struct ncclProxyArgs* peerOp = op->nextPeer; while (peerOp) { if (peerOp->opCount >= comm->lastOpCount) peerOp->state = ncclProxyOpNone; peerOp = peerOp->nextPeer; } op = op->next; if (op == start) break; } comm->opCount = comm->lastOpCount; pthread_cond_signal(&state->cond); pthread_mutex_unlock(&state->mutex); comm->myParams->gridDim.x = comm->myParams->blockDim.x = 0; comm->userStreamSet = false; } } } end: ncclGroupError = ncclSuccess; ncclGroupIndex = 0; CUDACHECK(hipSetDevice(savedDev)); // do other clean-ups first before calling hipSetDevice, because this call can fail too return ret; }