/************************************************************************* * Copyright (c) 2017-2020, NVIDIA CORPORATION. All rights reserved. * Modifications Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. * * See LICENSE.txt for license information ************************************************************************/ #include "enqueue.h" #include "argcheck.h" #include "coll_net.h" #include "../graph/topo.h" // Only generate inline kernels for LL #define NCCL_FUNC5(coll, op, dtype) \ NCCL_KERN_NAME(coll##LL, op, dtype), \ NCCL_KERN_NAME(coll##LL, op, dtype), \ NCCL_KERN_NAME(coll##LL, op, dtype) #define NCCL_FUNC4(coll, op, dtype) \ NCCL_FUNC5(coll##Tree, op, dtype), \ NCCL_FUNC5(coll##Ring, op, dtype), \ NCCL_FUNC5(coll##CollNet, op, dtype) // Must be consistent with ncclDataType_t #define NCCL_FUNCS3A(coll, op) \ NCCL_FUNC4(coll, op, i8), \ NCCL_FUNC4(coll, op, u8), \ NCCL_FUNC4(coll, op, i32), \ NCCL_FUNC4(coll, op, u32), \ NCCL_FUNC4(coll, op, i64), \ NCCL_FUNC4(coll, op, u64), \ NCCL_FUNC4(coll, op, f16), \ NCCL_FUNC4(coll, op, f32), \ NCCL_FUNC4(coll, op, f64), \ NCCL_FUNC4(coll, op, b16) #define NCCL_FUNCS3B(coll, op) \ NCCL_FUNC4(coll, op, i8), \ NCCL_FUNC4(coll, op, i8), \ NCCL_FUNC4(coll, op, i8), \ NCCL_FUNC4(coll, op, i8), \ NCCL_FUNC4(coll, op, i8), \ NCCL_FUNC4(coll, op, i8), \ NCCL_FUNC4(coll, op, i8), \ NCCL_FUNC4(coll, op, i8), \ NCCL_FUNC4(coll, op, i8), \ NCCL_FUNC4(coll, op, i8) // Must be consistent with ncclRedOp_t -- but we only generate kernel for sums. #define NCCL_FUNCS2A(coll) \ NCCL_FUNCS3A(coll, sum), \ NCCL_FUNCS3A(coll, sum), \ NCCL_FUNCS3A(coll, sum), \ NCCL_FUNCS3A(coll, sum) #define NCCL_FUNCS2B(coll) \ NCCL_FUNCS3B(coll, copy), \ NCCL_FUNCS3B(coll, copy), \ NCCL_FUNCS3B(coll, copy), \ NCCL_FUNCS3B(coll, copy) typedef void(*ncclKern_t)(struct ncclDevComm*); // Must be consistent with the ncclFuncSet enum static ncclKern_t const ncclKerns[1] = { NCCL_KERN_NAME(ncclSendRecv, copy, i8) }; /*****************************************************************************/ /* Launch system : synchronization and CUDA kernel launch */ /*****************************************************************************/ ncclResult_t ncclLaunchCooperativeKernelMultiDevice(hipLaunchParams *paramsList, int* cudaDevs, int numDevices, int cgMode) { if (cgMode & 0x01) { CUDACHECK(hipExtLaunchMultiKernelMultiDevice(paramsList, numDevices, // These flags are to reduce the latency of using this API #if __HIP__ hipCooperativeLaunchMultiDeviceNoPreSync|hipCooperativeLaunchMultiDeviceNoPostSync)); #else 0)); #endif return ncclSuccess; } int savedDev; CUDACHECK(hipGetDevice(&savedDev)); for (int i = 0; i < numDevices; i++) { hipLaunchParams* params = paramsList+i; CUDACHECK(hipSetDevice(cudaDevs[i])); hipLaunchKernelGGL(((void (*)(struct ncclDevComm*))params->func), params->gridDim, params->blockDim, params->sharedMem, params->stream, **((struct ncclDevComm ***)(params->args))); } CUDACHECK(hipSetDevice(savedDev)); return ncclSuccess; } ncclResult_t setupLaunch(struct ncclComm* comm, hipLaunchParams* params) { // Only launch blocks where we have work to do. for (int c=0; cnChannels, comm->p2pnChannels); c++) { if (comm->channels[c].collCount) params->gridDim.x = c+1; } // Set active = 2 for the last operation and add a no-op on empty channels (p2p case). for (int c=0; cgridDim.x; c++) { struct ncclChannel* channel = comm->channels+c; if (channel->collCount == 0) { int opIndex = channel->collFifoTail; struct ncclColl* c = channel->collectives+opIndex; volatile uint8_t* activePtr = (volatile uint8_t*)&c->active; while (activePtr[0] != 0) sched_yield(); c->args.p2p.delta = -1; // no-op c->funcIndex = FUNC_INDEX_P2P; c->args.comm = comm->devComm; c->active = 1; opIndex = (opIndex+1)%NCCL_MAX_OPS; c->nextIndex = opIndex; channel->collFifoTail = opIndex; channel->collCount++; } STORE(&channel->collectives[(channel->collStart+channel->collCount-1)%NCCL_MAX_OPS].active, 2); } // Find the first operation, choose the kernel accordingly and pass it // as the first argument. struct ncclColl* coll = comm->channels[0].collectives+comm->channels[0].collStart; comm->args = comm->devComm; params->func = (void *)ncclKerns[0]; return ncclSuccess; } ncclResult_t ncclCpuBarrierIn(struct ncclComm* comm, int* isLast) { volatile int* ptr = (volatile int*)(comm->intraBarrier+comm->intraPhase); int val = LOAD(ptr); bool done = false; while (done == false) { if (val >= comm->intraRanks) { WARN("Trying to launch too many collectives"); return ncclInvalidUsage; } if (val+1 == comm->intraRanks) { // Reset the barrier. comm->intraBarrier[comm->intraPhase^1] = 0; *isLast = 1; return ncclSuccess; } done = __sync_bool_compare_and_swap(ptr, val, val+1); val++; } *isLast = 0; return ncclSuccess; } ncclResult_t ncclCpuBarrierLast(struct ncclComm* comm) { volatile int* ptr = (volatile int*)(comm->intraBarrier+comm->intraPhase); int val = LOAD(ptr); if (__sync_bool_compare_and_swap(ptr, val, val+1) != true) { WARN("Trying to launch too many collectives"); return ncclInternalError; } return ncclSuccess; } ncclResult_t ncclCpuBarrierOut(struct ncclComm* comm) { volatile int* ptr = (volatile int*)(comm->intraBarrier+comm->intraPhase); while (LOAD(ptr) < comm->intraRanks) pthread_yield(); comm->intraPhase ^= 1; return ncclSuccess; } ncclResult_t ncclBarrierEnqueue(struct ncclComm* comm) { hipLaunchParams* params = comm->myParams; if (params->gridDim.x == 0) return ncclSuccess; NCCLCHECK(setupLaunch(comm, params)); // Use internal NCCL stream for CGMD/GROUP launch if required or if the user stream is NULL if (comm->launchMode == ncclComm::GROUP && (comm->groupCudaStream || comm->userStream == NULL)) { // Enqueue event in user stream CUDACHECK(hipEventRecord(comm->doneEvent, comm->userStream)); // Create dependency between user stream and internal NCCL stream CUDACHECK(hipStreamWaitEvent(comm->groupStream, comm->doneEvent, 0)); params->stream = comm->groupStream; } else { if (comm->userStream != params->stream) { // Stream changed from last call, create dependency against last NCCL kernel launch CUDACHECK(hipStreamWaitEvent(comm->userStream, comm->doneEvent, 0)); } params->stream = comm->userStream; } if (comm->launchMode == ncclComm::GROUP) { int isLast = 0; NCCLCHECK(ncclCpuBarrierIn(comm, &isLast)); if (isLast) { // I'm the last. Launch all operations. NCCLCHECK(ncclLaunchCooperativeKernelMultiDevice(comm->intraParams, comm->intraCudaDevs, comm->intraRanks, *comm->intraCGMode)); NCCLCHECK(ncclCpuBarrierLast(comm)); } } return ncclSuccess; } ncclResult_t ncclBarrierEnqueueWait(ncclComm_t comm) { hipLaunchParams *params = comm->myParams; if (params->gridDim.x == 0) return ncclSuccess; // We can't print the CG mode before the first barrier happened. if (comm->rank == 0 && *comm->intraCGMode & 0x10) { *comm->intraCGMode ^= 0x10; INFO(NCCL_INIT,"Launch mode %s%s%s", comm->launchMode == ncclComm::GROUP ? "Group" : "Parallel", *comm->intraCGMode ? "/CGMD" : "", (comm->launchMode == ncclComm::GROUP && comm->groupCudaStream) ? "/Stream" : ""); } if (comm->launchMode == ncclComm::PARALLEL) { hipLaunchKernelGGL(((void (*)(struct ncclDevComm*))params->func), params->gridDim, params->blockDim, params->sharedMem, params->stream, **((struct ncclDevComm ***)(params->args))); } else { NCCLCHECK(ncclCpuBarrierOut(comm)); } // Start the network proxies as soon as the kernel has been launched. We can't // perform any CUDA call between the two or having a cudaFree between the CUDA // launch and the ncclProxyStart call could cause a deadlock. // Also, starting the proxies after the CUDA launch seems to be better for // performance (latency). for (int r=0; rgridDim.x; r++) { struct ncclChannel* channel = comm->channels+r; channel->collStart = channel->collFifoTail; channel->collCount = 0; } params->gridDim.x = params->blockDim.x = 0; comm->lastOpCount = comm->opCount; NCCLCHECK(ncclProxyStart(comm)); return ncclSuccess; } ncclResult_t ncclEnqueueEvents(ncclComm_t comm) { hipLaunchParams *params = comm->myParams; // Enqueue event after NCCL kernel CUDACHECK(hipEventRecord(comm->doneEvent, params->stream)); // Use internal NCCL stream for CGMD/GROUP launch if required or if the user stream is NULL if (comm->launchMode == ncclComm::GROUP && (comm->groupCudaStream || comm->userStream == NULL)) { // Create dependency between NCCL internal stream and user stream CUDACHECK(hipStreamWaitEvent(comm->userStream, comm->doneEvent, 0)); } comm->userStreamSet = false; return ncclSuccess; } /*****************************************************************************/ /* Enqueueing system : computation of kernel and proxy operations parameters */ /*****************************************************************************/ static ncclResult_t getAlgoInfo(struct ncclInfo* info) { struct ncclComm* comm = info->comm; float minTime = 3600000000.0; // Hopefully no operation will take an hour to complete. // Find algorithm / protocol. info->algorithm = -1; info->protocol = -1; int nAlgos = NCCL_NUM_ALGORITHMS; // Check collNet support int collNetTypeSupport = 0; if (info->comm->collNetSupport) NCCLCHECK(collNetReduceSupport(info->datatype, info->op, &collNetTypeSupport)); if (collNetTypeSupport != 1) nAlgos--; for (int a=0; a= 0 && time < minTime) { info->algorithm = a; info->protocol = p; minTime = time; } } } if (info->coll == ncclCollAllToAll || info->coll == ncclCollGather || info->coll == ncclCollScatter || info->coll == ncclCollAllToAllv) { info->algorithm = NCCL_ALGO_RING; info->protocol = NCCL_PROTO_SIMPLE; } if (info->algorithm == -1 || info->protocol == -1) { WARN("Error : no algorithm/protocol available"); return ncclInternalError; } //if (comm->rank == 0) INFO(NCCL_TUNING, "%ld Bytes -> Algo %d proto %d time %f", info->nBytes, info->algorithm, info->protocol, minTime); TRACE(NCCL_COLL, "%ld Bytes -> Algo %d proto %d time %f", info->nBytes, info->algorithm, info->protocol, minTime); int nc = (info->algorithm == NCCL_ALGO_COLLNET) ? comm->nChannels/2 : comm->nChannels; // CollNet uses one channel for up and one channel for down if (info->comm->topo->type == RCCL_TOPO_4P2H_ROME && (info->coll == ncclCollAllToAll || info->coll == ncclCollGather || info->coll == ncclCollScatter || info->coll == ncclCollAllToAllv)) nc = 2; int nt = comm->maxThreads[info->algorithm][info->protocol]; int threadThreshold = comm->threadThresholds[info->algorithm][info->protocol]; while (info->nBytes < nc*nt*threadThreshold) { // do not reduce channels in case of alltoall if (info->algorithm != NCCL_ALGO_COLLNET && info->coll != ncclCollAllToAll && info->coll != ncclCollGather && info->coll != ncclCollScatter && info->coll != ncclCollAllToAllv && nc >= 2) nc--; #if defined(__HIP_PLATFORM_HCC__) || defined(__HCC__) || defined(__HIPCC__) // do not reduce threads count on VEGA #else else if ((nt % 128) == 0) nt/=2; #endif else break; } #if defined(__HIP_PLATFORM_HCC__) || defined(__HCC__) || defined(__HIPCC__) #else if (info->protocol == NCCL_PROTO_SIMPLE) nt += WARP_SIZE; // Extra warp for sync #endif info->nChannels = nc; info->nThreads = nt; return ncclSuccess; } static ncclResult_t getPatternInfo(struct ncclInfo* info) { switch (info->coll) { case ncclCollBroadcast: info->pattern = info->algorithm == NCCL_ALGO_TREE ? ncclPatternTreeDown : ncclPatternPipelineFrom; break; case ncclCollReduce: info->pattern = info->algorithm == NCCL_ALGO_TREE ? ncclPatternTreeUp : ncclPatternPipelineTo; break; case ncclCollReduceScatter: case ncclCollAllGather: info->pattern = ncclPatternRing; break; case ncclCollAllReduce: info->pattern = info->algorithm == NCCL_ALGO_COLLNET ? ncclPatternCollTreeUp : info->algorithm == NCCL_ALGO_TREE ? ncclPatternTreeUpDown : ncclPatternRingTwice; break; case ncclCollGather: case ncclCollScatter: case ncclCollAllToAll: case ncclCollAllToAllv: info->pattern = ncclPatternAll; break; default: WARN("Unknown pattern for collective %d algorithm %d", info->coll, info->algorithm); return ncclInternalError; } return ncclSuccess; } static ncclResult_t getLoopInfo(struct ncclInfo* info) { switch (info->pattern) { case ncclPatternTreeUp: case ncclPatternTreeDown: case ncclPatternTreeUpDown: case ncclPatternPipelineFrom: case ncclPatternPipelineTo: case ncclPatternCollTreeUp: case ncclPatternCollTreeDown: case ncclPatternAll: info->nstepsPerLoop = info->nchunksPerLoop = 1; break; case ncclPatternRing: info->nstepsPerLoop = info->comm->nRanks-1; info->nchunksPerLoop = info->comm->nRanks; break; case ncclPatternRingTwice: info->nstepsPerLoop = 2*(info->comm->nRanks-1); info->nchunksPerLoop = info->comm->nRanks; break; default: WARN("Unknown pattern %d\n", info->pattern); return ncclInternalError; } return ncclSuccess; } static ncclResult_t computeColl(struct ncclInfo* info /* input */, struct ncclColl* coll, struct ncclProxyArgs* proxyArgs /* output */) { coll->args.sendbuff = info->sendbuff; coll->args.recvbuff = info->recvbuff; coll->args.comm = info->comm->devComm; coll->args.opCount = info->comm->opCount; if (info->coll == ncclCollSendRecv) { coll->args.p2p.sendCount = info->sendbytes; coll->args.p2p.recvCount = info->recvbytes; coll->args.p2p.delta = info->delta; coll->funcIndex = FUNC_INDEX_P2P; #if defined(__HIP_PLATFORM_HCC__) || defined(__HCC__) || defined(__HIPCC__) coll->args.p2p.nThreads = info->nThreads = info->comm->maxThreads[NCCL_ALGO_RING][NCCL_PROTO_SIMPLE]; #else coll->args.p2p.nThreads = info->nThreads = info->comm->maxThreads[NCCL_ALGO_RING][NCCL_PROTO_SIMPLE]+2*WARP_SIZE; #endif return ncclSuccess; } // Set nstepsPerLoop and nchunksPerLoop NCCLCHECK(getAlgoInfo(info)); NCCLCHECK(getPatternInfo(info)); NCCLCHECK(getLoopInfo(info)); if (info->coll == ncclCollAllToAllv) { coll->args.a2av.count = info->count; coll->args.a2av.nChannels = info->nChannels; coll->args.a2av.nThreads = info->nThreads; } else { coll->args.coll.root = info->root; coll->args.coll.count = info->count; coll->args.coll.nChannels = info->nChannels; coll->args.coll.nThreads = info->nThreads; } coll->funcIndex = FUNC_INDEX(info->coll, info->op, info->datatype, info->algorithm, info->protocol); int stepSize = info->comm->buffSizes[info->protocol]/NCCL_STEPS; int chunkSteps = (info->protocol == NCCL_PROTO_SIMPLE && info->algorithm == NCCL_ALGO_RING) ? info->chunkSteps : 1; int sliceSteps = (info->protocol == NCCL_PROTO_SIMPLE && info->algorithm == NCCL_ALGO_RING) ? info->sliceSteps : 1; int chunkSize = stepSize*chunkSteps; // Compute lastChunkSize if (info->algorithm == NCCL_ALGO_TREE && info->protocol == NCCL_PROTO_SIMPLE) { if (info->pattern == ncclPatternTreeUpDown) { // Optimize chunkSize / nSteps while (info->nBytes / (info->nChannels*chunkSize) < info->comm->channels[0].treeUp.depth*8 && chunkSize > 131072) chunkSize /= 2; while (info->nBytes / (info->nChannels*chunkSize) < info->comm->channels[0].treeUp.depth*4 && chunkSize > 65536) chunkSize /= 2; while (info->nBytes / (info->nChannels*chunkSize) < info->comm->channels[0].treeUp.depth && chunkSize > 32768) chunkSize /= 2; } // Use lastChunkSize as chunkSize coll->args.coll.lastChunkSize = chunkSize / ncclTypeSize(info->datatype); } else if (info->algorithm == NCCL_ALGO_COLLNET && info->protocol == NCCL_PROTO_SIMPLE) { // Optimize chunkSize / nSteps while (info->nBytes / (info->nChannels*chunkSize) < info->comm->channels[0].collTreeUp.depth*16 && chunkSize > 131072) chunkSize /= 2; while (info->nBytes / (info->nChannels*chunkSize) < info->comm->channels[0].collTreeUp.depth*4 && chunkSize > 65536) chunkSize /= 2; while (info->nBytes / (info->nChannels*chunkSize) < info->comm->channels[0].collTreeUp.depth && chunkSize > 32768) chunkSize /= 2; // Use lastChunkSize as chunkSize coll->args.coll.lastChunkSize = chunkSize / ncclTypeSize(info->datatype); } else if (info->protocol == NCCL_PROTO_LL) { const ssize_t sliceSize = stepSize*sizeof(uint64_t)/sizeof(union ncclLLFifoLine); const ssize_t loopSize = info->nChannels*info->nchunksPerLoop*(ssize_t)sliceSize; coll->args.coll.lastChunkSize = DIVUP((info->nBytes-(info->nBytes/loopSize)*loopSize), info->nChannels*info->nchunksPerLoop); ALIGN_SIZE(coll->args.coll.lastChunkSize, info->nThreads*sizeof(uint64_t)); coll->args.coll.lastChunkSize /= ncclTypeSize(info->datatype); } else if (info->algorithm == NCCL_ALGO_TREE && info->protocol == NCCL_PROTO_LL128) { int nNodes = info->comm->nNodes; float ppn = info->comm->nRanks / (float)nNodes; float nstepsLL128 = 1+log2i(nNodes) + 0.1*ppn; while (info->nBytes / (info->nChannels*chunkSize) < nstepsLL128*64/ppn && chunkSize > 131072) chunkSize /= 2; while (info->nBytes / (info->nChannels*chunkSize) < nstepsLL128*16/ppn && chunkSize > 32768) chunkSize /= 2; // Use lastChunkSize as chunkSize coll->args.coll.lastChunkSize = chunkSize*NCCL_LL128_DATAELEMS/(NCCL_LL128_LINEELEMS*ncclTypeSize(info->datatype)); } // Compute nSteps for proxies int chunkEffectiveSize = chunkSize; if (info->protocol == NCCL_PROTO_LL) chunkEffectiveSize /= 2; if (info->protocol == NCCL_PROTO_LL128) chunkEffectiveSize = (chunkSize / NCCL_LL128_LINEELEMS) * NCCL_LL128_DATAELEMS; //if (info->comm->rank == 0) printf("Coll %d, size %ld -> %dx%d, chunkSize %d (algo %d proto%d)\n", info->coll, info->nBytes, info->nChannels, info->nThreads, chunkSize, info->algorithm, info->protocol); int nLoops; if (info->pattern != ncclPatternAll) nLoops = (int)(DIVUP(info->nBytes, (((size_t)(info->nChannels))*info->nchunksPerLoop*chunkEffectiveSize))); else nLoops = (int)(DIVUP(info->nBytes, (((size_t)((info->nChannels >= info->comm->nRanks ? (info->nChannels/info->comm->nRanks) : 1))))*info->comm->nRanks*info->nchunksPerLoop*chunkEffectiveSize)); proxyArgs->nsteps = info->nstepsPerLoop * nLoops * chunkSteps; proxyArgs->sliceSteps = sliceSteps; proxyArgs->chunkSteps = chunkSteps; proxyArgs->protocol = info->protocol; proxyArgs->opCount = info->comm->opCount; proxyArgs->dtype = info->datatype; proxyArgs->redOp = info->op; if (info->coll != ncclCollAllToAllv) TRACE(NCCL_NET,"opCount %lx slicesteps %d spl %d cpl %d ces %d nbytes %zi -> protocol %d nchannels %d nthreads %d, nloops %d nsteps %d comm %p", coll->args.opCount, proxyArgs->sliceSteps, info->nstepsPerLoop, info->nchunksPerLoop, chunkEffectiveSize, info->nBytes, info->protocol, info->nChannels, info->nThreads, nLoops, proxyArgs->nsteps, info->comm); return ncclSuccess; } static ncclResult_t checkSetStream(struct ncclInfo* info) { if (info->comm->userStreamSet == false) { info->comm->userStream = info->stream; info->comm->userStreamSet = true; } else if (info->stream != info->comm->userStream) { WARN("Error : mixing different streams within a group call is not supported."); return ncclInvalidUsage; } return ncclSuccess; } ncclResult_t ncclSaveKernel(struct ncclInfo* info) { if (info->comm->nRanks == 1 && info->coll != ncclCollSendRecv) { if (info->sendbuff != info->recvbuff) CUDACHECK(hipMemcpyAsync(info->recvbuff, info->sendbuff, info->nBytes, hipMemcpyDeviceToDevice, info->stream)); return ncclSuccess; } struct ncclColl coll; struct ncclProxyArgs proxyArgs; memset(&proxyArgs, 0, sizeof(struct ncclProxyArgs)); NCCLCHECK(computeColl(info, &coll, &proxyArgs)); info->comm->myParams->blockDim.x = std::max(info->comm->myParams->blockDim.x, info->nThreads); int nChannels = info->coll == ncclCollSendRecv ? 1 : coll.args.coll.nChannels; int nSubChannels = (info->pattern == ncclPatternCollTreeUp || info->pattern == ncclPatternCollTreeDown) ? 2 : 1; for (int bid=0; bidcoll == ncclCollSendRecv) ? info->channelId : info->comm->myParams->gridDim.x % info->comm->nChannels; struct ncclChannel* channel = info->comm->channels+channelId; if (channel->collCount == NCCL_MAX_OPS) { WARN("Too many aggregated operations on channel %d (%d max)", channel->id, NCCL_MAX_OPS); return ncclInvalidUsage; } // Proxy proxyArgs.channel = channel; // Adjust pattern for CollNet based on channel index if (nSubChannels == 2) { info->pattern = (channelId < info->comm->nChannels/nSubChannels) ? ncclPatternCollTreeUp : ncclPatternCollTreeDown; } if (info->coll == ncclCollSendRecv) { info->comm->myParams->gridDim.x = std::max(info->comm->myParams->gridDim.x, channelId+1); NCCLCHECK(ncclProxySaveP2p(info, channel)); } else if (info->coll == ncclCollAllToAll || info->coll == ncclCollScatter || info->coll == ncclCollGather || info->coll == ncclCollAllToAllv) { NCCLCHECK(ncclProxySaveA2a(&proxyArgs, info)); } else { NCCLCHECK(ncclProxySaveColl(&proxyArgs, info->pattern, info->root, info->comm->nRanks)); } info->comm->myParams->gridDim.x++; int opIndex = channel->collFifoTail; struct ncclColl* c = channel->collectives+opIndex; volatile uint8_t* activePtr = (volatile uint8_t*)&c->active; while (LOAD(activePtr) != 0) sched_yield(); memcpy(c, &coll, sizeof(struct ncclColl)); if (info->coll == ncclCollAllToAllv) { c->args.a2av.extra = channel->collectivesExtra + info->comm->nRanks*4*opIndex; memcpy(c->args.a2av.extra, info->sendcounts, sizeof(size_t*)*(info->comm->nRanks)); memcpy(c->args.a2av.extra+info->comm->nRanks, info->sdispls, sizeof(size_t*)*(info->comm->nRanks)); memcpy(c->args.a2av.extra+info->comm->nRanks*2, info->recvcounts, sizeof(size_t*)*(info->comm->nRanks)); memcpy(c->args.a2av.extra+info->comm->nRanks*3, info->rdispls, sizeof(size_t*)*(info->comm->nRanks)); c->args.a2av.bid = bid % coll.args.coll.nChannels; } else if (info->coll != ncclCollSendRecv) c->args.coll.bid = bid % coll.args.coll.nChannels; STORE(&c->active, 1); opIndex = (opIndex+1)%NCCL_MAX_OPS; c->nextIndex = opIndex; channel->collFifoTail = opIndex; channel->collCount++; } info->comm->opCount++; return ncclSuccess; } // Save p2p operations in comm->p2plist. Operations will be posted to channels // during ncclGroupEnd() ncclResult_t ncclSaveP2p(struct ncclInfo* info) { struct ncclComm* comm = info->comm; struct ncclP2Plist* p2plist = &comm->p2plist; int peer = info->root; p2plist->count++; ssize_t nBytes = info->count*ncclTypeSize(info->datatype); if (info->recvbuff == NULL) { if (peer != comm->rank) { int delta = (comm->nRanks - (comm->rank-peer)) % comm->nRanks; for (int c=0; cp2pnChannelsPerPeer; c++) { int channelId = (delta+comm->p2pChannels[c]) % comm->p2pnChannels; if (comm->channels[channelId].peers[peer].send.connected == 0) { p2plist->connect.send[channelId*comm->nRanks+p2plist->connect.nsend[channelId]++] = peer; } } } p2plist->peerlist[info->root].sendbytes = nBytes; p2plist->peerlist[info->root].sendbuff = info->sendbuff; } else { if (peer != comm->rank) { int delta = (comm->nRanks + (comm->rank-peer)) % comm->nRanks; for (int c=0; cp2pnChannelsPerPeer; c++) { int channelId = (delta+comm->p2pChannels[c]) % comm->p2pnChannels; if (comm->channels[channelId].peers[peer].recv.connected == 0) { p2plist->connect.recv[channelId*comm->nRanks+p2plist->connect.nrecv[channelId]++] = peer; } } } p2plist->peerlist[info->root].recvbytes = nBytes; p2plist->peerlist[info->root].recvbuff = info->recvbuff; } return ncclSuccess; } ncclResult_t ncclEnqueueCheck(struct ncclInfo* info) { // Launch asynchronously if needed if (ncclAsyncMode()) { ncclResult_t ret = ncclSuccess; int savedDev = -1; // Check arguments NCCLCHECK(PtrCheck(info->comm, info->opName, "comm")); if (info->comm->checkPointers) { CUDACHECKGOTO(hipGetDevice(&savedDev), ret, end); CUDACHECKGOTO(hipSetDevice(info->comm->cudaDev), ret, end); } NCCLCHECKGOTO(ArgsCheck(info), ret, end); // Always register comm even in case of error to make sure ncclGroupEnd // cleans it up. NCCLCHECKGOTO(ncclAsyncColl(info->comm), ret, end); NCCLCHECKGOTO(checkSetStream(info), ret, end); if (info->coll == ncclCollAllToAllv) INFO(NCCL_COLL,"%s: opCount %lx sendbuff %p sendcounts %p sdispls %p recvbuff %p recvcounts %p rdispls %p datatype %d typesize %zi op %d root %d comm %p [nranks=%d] stream %p", info->opName, info->comm->opCount, info->sendbuff, info->sendcounts, info->sdispls, info->recvbuff, info->recvcounts, info->rdispls, info->datatype, info->count, info->op, info->root, info->comm, info->comm->nRanks, info->stream); else INFO(NCCL_COLL,"%s: opCount %lx sendbuff %p recvbuff %p count %zi datatype %d op %d root %d comm %p [nranks=%d] stream %p", info->opName, info->comm->opCount, info->sendbuff, info->recvbuff, info->count, info->datatype, info->op, info->root, info->comm, info->comm->nRanks, info->stream); if (info->coll == ncclCollSendRecv) { //p2p stored separately NCCLCHECKGOTO(ncclSaveP2p(info), ret, end); } else { NCCLCHECKGOTO(ncclSaveKernel(info), ret, end); } end: if (savedDev != -1) CUDACHECK(hipSetDevice(savedDev)); ncclAsyncErrCheck(ret); return ret; } else { NCCLCHECK(PtrCheck(info->comm, info->opName, "comm")); NCCLCHECK(ArgsCheck(info)); NCCLCHECK(checkSetStream(info)); if (info->coll == ncclCollAllToAllv) INFO(NCCL_COLL,"%s: opCount %lx sendbuff %p sendcounts %p sdispls %p recvbuff %p recvcounts %p rdispls %p datatype %d typesize %zi op %d root %d comm %p [nranks=%d] stream %p", info->opName, info->comm->opCount, info->sendbuff, info->sendcounts, info->sdispls, info->recvbuff, info->recvcounts, info->rdispls, info->datatype, info->count, info->op, info->root, info->comm, info->comm->nRanks, info->stream); else INFO(NCCL_COLL,"%s: opCount %lx sendbuff %p recvbuff %p count %zi datatype %d op %d root %d comm %p [nranks=%d] stream %p", info->opName, info->comm->opCount, info->sendbuff, info->recvbuff, info->count, info->datatype, info->op, info->root, info->comm, info->comm->nRanks, info->stream); NCCLCHECK(ncclSaveKernel(info)); NCCLCHECK(ncclBarrierEnqueue(info->comm)); NCCLCHECK(ncclBarrierEnqueueWait(info->comm)); NCCLCHECK(ncclEnqueueEvents(info->comm)); return ncclSuccess; } }