diff --git a/projects/rccl/makefiles/common.mk b/projects/rccl/makefiles/common.mk index 1a1c2b66f8..0c0d04a86f 100644 --- a/projects/rccl/makefiles/common.mk +++ b/projects/rccl/makefiles/common.mk @@ -31,13 +31,17 @@ CUDA8_GENCODE = -gencode=arch=compute_35,code=sm_35 \ -gencode=arch=compute_61,code=sm_61 CUDA9_GENCODE = -gencode=arch=compute_70,code=sm_70 CUDA11_GENCODE = -gencode=arch=compute_80,code=sm_80 +CUDA11_8_GENCODE = -gencode=arch=compute_90,code=sm_90 CUDA8_PTX = -gencode=arch=compute_61,code=compute_61 CUDA9_PTX = -gencode=arch=compute_70,code=compute_70 CUDA11_PTX = -gencode=arch=compute_80,code=compute_80 +CUDA11_8_PTX = -gencode=arch=compute_90,code=compute_90 -# Include Ampere support if we're using CUDA11 or above -ifeq ($(shell test "0$(CUDA_MAJOR)" -ge 11; echo $$?),0) +ifeq ($(shell test "0$(CUDA_MAJOR)" -eq 11 -a "0$(CUDA_MINOR)" -ge 8 -o "0$(CUDA_MAJOR)" -gt 11; echo $$?),0) +# Include Hopper support if we're using CUDA11.8 or above + NVCC_GENCODE ?= $(CUDA8_GENCODE) $(CUDA9_GENCODE) $(CUDA11_GENCODE) $(CUDA11_8_GENCODE) $(CUDA11_8_PTX) +else ifeq ($(shell test "0$(CUDA_MAJOR)" -ge 11; echo $$?),0) NVCC_GENCODE ?= $(CUDA8_GENCODE) $(CUDA9_GENCODE) $(CUDA11_GENCODE) $(CUDA11_PTX) # Include Volta support if we're using CUDA9 or above else ifeq ($(shell test "0$(CUDA_MAJOR)" -ge 9; echo $$?),0) @@ -45,7 +49,7 @@ else ifeq ($(shell test "0$(CUDA_MAJOR)" -ge 9; echo $$?),0) else NVCC_GENCODE ?= $(CUDA8_GENCODE) $(CUDA8_PTX) endif -#$(info NVCC_GENCODE is ${NVCC_GENCODE}) +$(info NVCC_GENCODE is ${NVCC_GENCODE}) CXXFLAGS := -DCUDA_MAJOR=$(CUDA_MAJOR) -DCUDA_MINOR=$(CUDA_MINOR) -fPIC -fvisibility=hidden \ -Wall -Wno-unused-function -Wno-sign-compare -std=c++11 -Wvla \ diff --git a/projects/rccl/makefiles/version.mk b/projects/rccl/makefiles/version.mk index 55fa6cc273..be64e9a90c 100644 --- a/projects/rccl/makefiles/version.mk +++ b/projects/rccl/makefiles/version.mk @@ -1,6 +1,6 @@ ##### version NCCL_MAJOR := 2 -NCCL_MINOR := 14 -NCCL_PATCH := 3 +NCCL_MINOR := 15 +NCCL_PATCH := 5 NCCL_SUFFIX := PKG_REVISION := 1 diff --git a/projects/rccl/src/channel.cc b/projects/rccl/src/channel.cc index 95870086ec..05140766b1 100644 --- a/projects/rccl/src/channel.cc +++ b/projects/rccl/src/channel.cc @@ -20,14 +20,14 @@ ncclResult_t initChannel(struct ncclComm* comm, int channelId) { // The extra on nRanks+1 is for collnet root (i.e. network) channel->peers = ncclMemoryStackAlloc(&comm->memPermanent, nRanks+1); - NCCLCHECK(ncclCudaCallocAsync(&channel->devPeers, nRanks+1, comm->deviceStream.stream)); + NCCLCHECK(ncclCudaCallocAsync(&channel->devPeers, nRanks+1, comm->deviceStream.cudaStream)); ncclCommPushCudaFree(comm, channel->devPeers); channel->ring.userRanks = ncclMemoryStackAlloc(&comm->memPermanent, nRanks); - NCCLCHECK(ncclCudaCallocAsync(&channel->devRingUserRanks, nRanks, comm->deviceStream.stream)); + NCCLCHECK(ncclCudaCallocAsync(&channel->devRingUserRanks, nRanks, comm->deviceStream.cudaStream)); ncclCommPushCudaFree(comm, channel->devRingUserRanks); - NCCLCHECK(ncclStrongStreamRelease(ncclCudaGraphNull(), &comm->deviceStream)); + NCCLCHECK(ncclStrongStreamRelease(ncclCudaGraphNone(), &comm->deviceStream)); for (int r=0; r < nRanks+1; ++r) { for (int b=0; b < NCCL_MAX_CONNS; b++) { diff --git a/projects/rccl/src/enqueue.cc b/projects/rccl/src/enqueue.cc index 4fc77a3175..bf42e446f9 100644 --- a/projects/rccl/src/enqueue.cc +++ b/projects/rccl/src/enqueue.cc @@ -366,7 +366,7 @@ static void finishPlan(struct ncclKernelPlan* plan) { plan->channelCount = channelCount; plan->channelMask = channelMask; plan->hasProxyOps = hasProxyOps; - plan->threadPerBlock = std::max(plan->threadPerBlock, 4*WARP_SIZE); + plan->threadPerBlock = std::max(plan->threadPerBlock, 4*plan->comm->WarpSize); } static ncclResult_t registerIntraNodeBuffers( @@ -918,19 +918,38 @@ ncclResult_t ncclLaunchPrepare(struct ncclComm* comm) { struct ncclKernelPlan* planHead = ncclIntruQueueHead(&comm->planQueue); comm->unlaunchedPlansHead = planHead; + // Semantically we want these dependencies for the kernels launched: + // 1. Launch host task on hostStream. + // 2. Launch kernel, depends on all of {deviceStream, hostStream, userStream[i]...} + // 3. {deviceStream, userStream[i]...} depend on kernel. + // We achieve this by: + // 1. userStream[0] waits on deviceStream + // 2. deviceStream waits on each of userStream[1...] + // 3. host task launch on hostStream + // 4. userStream[0] waits on hostStream + // 5. kernel launch on userStream[0] + // 6. deviceStream waits on userStream[0] + // 7. userStream[1...] each waits on deviceStream + // The two-level fan-in fan-out is because ncclStrongStreamWaitStream() requires + // at least one of the two streams to be strong-stream. + hipStream_t launchStream = tasks->streams->stream; NCCLCHECKGOTO(ncclStrongStreamAcquire(tasks->capturingGraph, &comm->deviceStream), result, failure); - // Create dependency for nccl device work on user streams. - for (struct ncclCudaStreamList* l=tasks->streams; l != nullptr && tasks->numStreams != 1; l = l->next) { - NCCLCHECKGOTO(ncclStrongStreamWaitStream(tasks->capturingGraph, &comm->deviceStream, l->stream), result, failure); - } - - if (tasks->numStreams == 1 && tasks->streams->stream != comm->lastStream) { + if (tasks->numStreams != 1) { + // Create dependency for device stream on user streams. First from extra user + // streams to deviceStream. Then deviceStream to first user stream. + for (struct ncclCudaStreamList* l=tasks->streams->next; l != nullptr; l = l->next) { + NCCLCHECKGOTO(ncclStrongStreamWaitStream(tasks->capturingGraph, &comm->deviceStream, l->stream), result, failure); + } + NCCLCHECKGOTO(ncclStrongStreamWaitStream(tasks->capturingGraph, launchStream, &comm->deviceStream), result, failure); + } else if (tasks->streams->stream != comm->lastStream) { // Stream changed from last call, create dependency against last NCCL kernel launch CUDACHECK(hipStreamWaitEvent(tasks->streams->stream, comm->doneEvent, 0)); } if (persistent || comm->persistentRefs != 0) { + // We have to launch host tasks to push proxy args. We are careful to only + // do this if necessary since host tasks impose a high performance cost in CUDA. bool acquired = false; for (struct ncclKernelPlan* plan=planHead; plan != nullptr; plan = plan->next) { if (plan->hasProxyOps) { @@ -942,6 +961,8 @@ ncclResult_t ncclLaunchPrepare(struct ncclComm* comm) { } } if (acquired) { + // Make to-be-launched kernels dependent on just-launched host stream tasks. + if (tasks->numStreams != 1) NCCLCHECKGOTO(ncclStrongStreamWaitStream(tasks->capturingGraph, launchStream, &comm->hostStream), result, failure); NCCLCHECKGOTO(ncclStrongStreamRelease(tasks->capturingGraph, &comm->hostStream), result, failure); } } @@ -967,8 +988,15 @@ ncclResult_t ncclLaunchKernelBefore_NoUncapturedCuda(struct ncclComm* comm, stru return ncclSuccess; } +#if CUDART_VERSION >= 11080 +#define NCCL_MAX_CGA_CLUSTER_SIZE 8 +NCCL_PARAM(CGAClusterSize, "CGA_CLUSTER_SIZE", 0); +#endif + ncclResult_t ncclLaunchKernel(struct ncclComm* comm, struct ncclKernelPlan* plan) { struct ncclTasks* tasks = &comm->tasks; + void *fn = plan->kernelFn; + hipStream_t launchStream = tasks->streams->stream; dim3 grid = {(unsigned)plan->channelCount, 1, 1}; dim3 block = {(unsigned)plan->threadPerBlock, 1, 1}; void *args[3] = {&comm->devComm, &plan->channelMask, &plan->workHead}; @@ -976,9 +1004,54 @@ ncclResult_t ncclLaunchKernel(struct ncclComm* comm, struct ncclKernelPlan* plan CUDACHECK(hipExtLaunchKernel(plan->kernelFn, grid, block, args, 0, tasks->streams->stream, NULL, comm->doneEvent, 0)); comm->lastStream = tasks->streams->stream; } else { - NCCLCHECK(ncclStrongStreamLaunchKernel( - tasks->capturingGraph, &comm->deviceStream, plan->kernelFn, grid, block, args, 0 - )); + #if CUDART_VERSION >= 11080 + int driverVersion; + NCCLCHECK(ncclCudaDriverVersion(&driverVersion)); + + unsigned int clusterSize = 0; + clusterSize = ncclParamCGAClusterSize(); + if (clusterSize > NCCL_MAX_CGA_CLUSTER_SIZE) { + static bool warned = false; + if (warned == false) { + WARN("NCCL_CGA_CLUSTER_SIZE value %d is too big. Limiting value to %d.", + clusterSize, NCCL_MAX_CGA_CLUSTER_SIZE); + warned = true; + } + clusterSize = NCCL_MAX_CGA_CLUSTER_SIZE; + } + + if (clusterSize && driverVersion >= 11080) { + cudaLaunchConfig_t launchConfig = {0}; + cudaLaunchAttribute launchAttrs[2]; + /* Cooperative Group Array (CGA) + * On sm90 and later we have an extra level of hierarchy where we + * can group together several blocks within the Grid, called + * Thread Block Clusters. + * Clusters enable multiple thread blocks running concurrently + * across multiple SMs to synchronize and collaboratively fetch + * and exchange data. A cluster of blocks are guaranteed to be + * concurrently scheduled onto a group of SMs. + * The maximum value is 8 and it must be divisible into the grid dimensions + */ + // Grid dimension must be divisible by clusterSize + if (grid.x % clusterSize) clusterSize = 1; + launchAttrs[0].id = cudaLaunchAttributeClusterDimension; + launchAttrs[0].val.clusterDim = {clusterSize, 1, 1}; + launchAttrs[1].id = cudaLaunchAttributeClusterSchedulingPolicyPreference; + launchAttrs[1].val.clusterSchedulingPolicyPreference = cudaClusterSchedulingPolicySpread; + + launchConfig.gridDim = grid; + launchConfig.blockDim = block; + launchConfig.attrs = launchAttrs; + launchConfig.numAttrs = sizeof(launchAttrs)/sizeof(launchAttrs[0]); + launchConfig.stream = launchStream; + + CUDACHECK(cudaLaunchKernelExC(&launchConfig, fn, args)); + return ncclSuccess; + } + #endif + // Standard kernel launch + CUDACHECK(hipLaunchKernel(fn, grid, block, args, 0, launchStream)); } return ncclSuccess; } @@ -1005,18 +1078,22 @@ ncclResult_t ncclLaunchFinish(struct ncclComm* comm) { // Reset queue to empty without destroying plans since those will be sent // back to us for reclaiming via callbackQueue. ncclIntruQueueConstruct(&comm->planQueue); - // Close strong stream "transaction" encompassing cuda launches - NCCLCHECKGOTO(ncclStrongStreamRelease(tasks->capturingGraph, &comm->deviceStream), result, resume1); + hipStream_t launchStream = tasks->streams->stream; // First user stream gets launch + // Create dependency for deviceStream on launchStream. + if (tasks->numStreams != 1) NCCLCHECKGOTO(ncclStrongStreamWaitStream(tasks->capturingGraph, &comm->deviceStream, launchStream), result, resume1); resume1: - // Create dependency for user streams on nccl device work. - struct ncclCudaStreamList* sl = tasks->streams; - tasks->streams = nullptr; // reset streams to empty + // Create dependency for other user streams (skip launch stream). + struct ncclCudaStreamList* sl = tasks->streams->next; + tasks->streams = nullptr; // Reset comm->tasks.streams to empty. while (sl != nullptr && tasks->numStreams != 1) { NCCLCHECKGOTO(ncclStrongStreamWaitStream(tasks->capturingGraph, sl->stream, &comm->deviceStream), result, resume2); resume2: sl = sl->next; } tasks->numStreams = 0; + // Release device stream as acquired in ncclLaunchPrepare() + NCCLCHECKGOTO(ncclStrongStreamRelease(tasks->capturingGraph, &comm->deviceStream), result, resume3); + resume3:; } return result; } @@ -1412,20 +1489,20 @@ static ncclResult_t taskAppend(struct ncclComm* comm, struct ncclInfo const* inf NCCLCHECK(ncclChannelComputeFromBase(comm, channelBaseId, c, &channelId)); if (isSendNotRecv) { if (comm->channels[channelId].peers[peer].send[1].connected == 0) { // P2P uses only 1 connector - comm->connectSend[peer] |= (1<connectSend[peer] |= (1UL<p2pNet && comm->channels[channelId].peers[peer].send[NCCL_CONN_IDX_P2P_NET].connected == 0) { - comm->connectSend[peer+comm->nRanks*NCCL_CONN_IDX_P2P_NET] |= (1<connectSend[peer+comm->nRanks*NCCL_CONN_IDX_P2P_NET] |= (1UL<channels[channelId].peers[peer].recv[1].connected == 0) { // P2P uses only 1 connector - comm->connectRecv[peer] |= (1<connectRecv[peer] |= (1UL<p2pNet && comm->channels[channelId].peers[peer].recv[NCCL_CONN_IDX_P2P_NET].connected == 0) { - comm->connectRecv[peer+comm->nRanks*NCCL_CONN_IDX_P2P_NET] |= (1<connectRecv[peer+comm->nRanks*NCCL_CONN_IDX_P2P_NET] |= (1UL<stream == info->stream) break; // Already seen stream. + l = l->next; } } return ncclSuccess; diff --git a/projects/rccl/src/graph/connect.cc b/projects/rccl/src/graph/connect.cc index db1cd4495f..3ba3f8806d 100644 --- a/projects/rccl/src/graph/connect.cc +++ b/projects/rccl/src/graph/connect.cc @@ -35,7 +35,7 @@ /******************************************************************/ ncclResult_t ncclTopoPreset(struct ncclComm* comm, - struct ncclTopoGraph* treeGraph, struct ncclTopoGraph* ringGraph, + struct ncclTopoGraph* treeGraph, struct ncclTopoGraph* ringGraph, struct ncclTopoGraph* collNetGraph, struct ncclTopoRanks* topoRanks) { int rank = comm->rank; int nChannels = comm->nChannels; @@ -60,6 +60,7 @@ ncclResult_t ncclTopoPreset(struct ncclComm* comm, int* ringIntra = ringGraph->intra+c*localRanks; int* treeIntra = treeGraph->intra+c*localRanks; + int* collNetIntra = collNetGraph->intra+c*localRanks; for (int i=0; itreeToChild1[c] = treeIntra[child1Index]; channel->tree.up = i == 0 ? -1 : treeIntra[i-1]; channel->tree.down[0] = i == localRanks-1 ? -1 : treeIntra[i+1]; - channel->collnetChain.up = i == 0 ? comm->nRanks : treeIntra[i-1]; - channel->collnetChain.down[0] = i == localRanks-1 ? -1 : treeIntra[i+1]; + } + if (collNetIntra[i] == rank) { + channel->collnetChain.up = i == 0 ? comm->nRanks : collNetIntra[i-1]; + channel->collnetChain.down[0] = i == localRanks-1 ? -1 : collNetIntra[i+1]; } } topoRanks->ringPrev[c] = channel->ring.prev; diff --git a/projects/rccl/src/graph/paths.cc b/projects/rccl/src/graph/paths.cc index 9519a21d9a..bf95319b22 100644 --- a/projects/rccl/src/graph/paths.cc +++ b/projects/rccl/src/graph/paths.cc @@ -424,6 +424,19 @@ ncclResult_t ncclTopoCheckGdr(struct ncclTopoSystem* system, int64_t busId, int return ncclSuccess; } +// Set to 0 to disable the flush on Hopper when using GDR +NCCL_PARAM(NetForceFlush, "NET_FORCE_FLUSH", 1); + +// Determine whether we need to flush the GDR recv buffers +ncclResult_t ncclTopoNeedFlush(struct ncclTopoSystem* system, int64_t busId, int* flush) { + int g; + NCCLCHECK(ncclTopoIdToIndex(system, GPU, busId, &g)); + struct ncclTopoNode* gpu = system->nodes[GPU].nodes+g; + // Flush is required on Ampere and earlier + *flush = gpu->gpu.cudaCompCap < 90 ? 1 : ncclParamNetForceFlush(); + return ncclSuccess; +} + NCCL_PARAM(NetDisableIntra, "NET_DISABLE_INTRA", 1); // Check whether going through the network would be faster than going through P2P/SHM. diff --git a/projects/rccl/src/graph/search.cc b/projects/rccl/src/graph/search.cc index 8a8c551153..f680619ab5 100644 --- a/projects/rccl/src/graph/search.cc +++ b/projects/rccl/src/graph/search.cc @@ -797,7 +797,7 @@ float speedArrayIntra[] = { 24.0, 20.0, 18.0, 15.0, 12.0, 10.0, 9.0, 7.0, 6.0, 5 float speedArrayInter[] = { 24.0, 20.0, 18.0, 15.0, 12.0, 10.0, 9.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.4, 1.2, 0.24, 0.12 }; #else float speedArrayIntra[] = { 44.0, 30.0, 22.0, 18.0, 15.0, 12.0, 10.0, 9.0, 7.0, 6.0, 5.0, 4.0, 3.0 }; -float speedArrayInter[] = { 48.0, 30.0, 24.0, 22.0, 18.0, 15.0, 12.0, 10.0, 9.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.4, 1.2, 0.24, 0.12 }; +float speedArrayInter[] = { 48.0, 30.0, 28.0, 24.0, 22.0, 18.0, 15.0, 12.0, 10.0, 9.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.4, 1.2, 0.24, 0.12 }; #endif #define NSPEEDSINTRA (sizeof(speedArrayIntra)/sizeof(float)) #define NSPEEDSINTER (sizeof(speedArrayInter)/sizeof(float)) @@ -918,6 +918,7 @@ ncclResult_t ncclTopoCompute(ncclTopoSystem* system, struct ncclTopoGraph* graph while (speedArray[speedIndex] > system->maxBw && speedIndex < nspeeds-1) speedIndex++; tmpGraph.bwIntra = tmpGraph.bwInter = speedArray[speedIndex]; int64_t globalTimeout = NCCL_SEARCH_GLOBAL_TIMEOUT; + search: int time = tmpGraph.sameChannels ? NCCL_SEARCH_TIMEOUT_SAMECHANNELS : tmpGraph.pattern == NCCL_TOPO_PATTERN_TREE ? NCCL_SEARCH_TIMEOUT_TREE : NCCL_SEARCH_TIMEOUT; @@ -953,6 +954,7 @@ search: if (time != -1) globalTimeout += time; else globalTimeout = NCCL_SEARCH_GLOBAL_TIMEOUT; if (globalTimeout < 0 && graph->nChannels) goto done; + int maxTypeIntra = system->nodes[NET].count > 0 ? tmpGraph.typeInter : PATH_SYS; if (tmpGraph.typeIntra < maxTypeIntra && (graph->nChannels == 0 || tmpGraph.typeIntra < graph->typeIntra)) { tmpGraph.typeIntra += 1; diff --git a/projects/rccl/src/graph/tuning.cc b/projects/rccl/src/graph/tuning.cc index 1527cfa848..f45b881cc5 100644 --- a/projects/rccl/src/graph/tuning.cc +++ b/projects/rccl/src/graph/tuning.cc @@ -13,11 +13,11 @@ NCCL_PARAM(Nthreads, "NTHREADS", -2); NCCL_PARAM(Ll128Nthreads, "LL128_NTHREADS", -2); -static int getNthreads(const char* name, int env, int min, int max, int def) { +static int getNthreads(const char* name, int env, int min, int max, int def, int WarpSize) { int nt = env; if (nt > 0) { - if (nt % WARP_SIZE != 0) { - WARN("Invalid %s %d (must be a multiple of %d)", name, nt, WARP_SIZE); + if (nt % WarpSize != 0) { + WARN("Invalid %s %d (must be a multiple of %d)", name, nt, WarpSize); nt = max; } else if (nt > max) { WARN("Invalid %s %d (maximum %d).", name, nt, max); @@ -226,22 +226,36 @@ static struct tuningModel rcclTuningModel[] = { tuning_model_4, }; +/* Array indexes used below */ +#define VOLTA_COMPCAP_IDX 0 +#define AMPERE_COMPCAP_IDX 1 +#define HOPPER_COMPCAP_IDX 2 + // LL128 max BW per channel static const double ll128MaxBwPerCh = 20.0; -static const double llMaxBws[2][3] = { /* Volta-N1/Intel-N2/Intel-N4) */ {39.0, 39.0, 20.4}, /* Ampere-N1/AMD-N2/AMD-N4) */ {87.7, 22.5 /*avg of ring & tree*/, 19.0} }; -static const double perChMaxTreeBws[2][3] = { /* Volta (N1/N2/N4) */ {26.5, 18.5, 10.0}, /* Ampere (N1/N2/N4) */ {24.0, 23.6, 17.8} }; +static const double llMaxBws[3][3] = { + /* Volta-N1/Intel-N2/Intel-N4) */ {39.0, 39.0, 20.4}, + /* Ampere-N1/AMD-N2/AMD-N4) */ {87.7, 22.5 /*avg of ring & tree*/, 19.0}, + /* Hopper-N1/AMD-N2/AMD-N4) */ {87.7, 22.5 /*avg of ring & tree*/, 19.0} +}; + +static const double perChMaxTreeBws[3][3] = { + /* Volta (N1/N2/N4) */ {26.5, 18.5, 10.0}, + /* Ampere (N1/N2/N4) */ {24.0, 23.6, 17.8}, + /* Hopper (N1/N2/N4) */ {24.0, 23.6, 17.8}, +}; ncclResult_t ncclTopoTuneModel(struct ncclComm* comm, int minCompCap, int maxCompCap, struct ncclTopoGraph* treeGraph, struct ncclTopoGraph* ringGraph, struct ncclTopoGraph* collNetGraph) { int simpleDefaultThreads = (ringGraph->bwIntra*ringGraph->nChannels <= PCI_BW) ? 256 : NCCL_SIMPLE_MAX_NTHREADS; comm->maxThreads[NCCL_ALGO_RING][NCCL_PROTO_SIMPLE] = #if defined(__HIP_PLATFORM_HCC__) || defined(__HCC__) || defined(__HIPCC__) - getNthreads("NCCL_NTHREADS", ncclParamNthreads(), 4*comm->WarpSize, NCCL_MAX_NTHREADS, simpleDefaultThreads); + getNthreads("NCCL_NTHREADS", ncclParamNthreads(), 4*comm->WarpSize, NCCL_MAX_NTHREADS, simpleDefaultThreads, comm->WarpSize); comm->maxThreads[NCCL_ALGO_TREE][NCCL_PROTO_SIMPLE] = comm->maxThreads[NCCL_ALGO_COLLNET_DIRECT][NCCL_PROTO_SIMPLE] = - getNthreads("NCCL_NTHREADS", ncclParamNthreads(), 4*comm->WarpSize, NCCL_MAX_NTHREADS, NCCL_MAX_NTHREADS); + getNthreads("NCCL_NTHREADS", ncclParamNthreads(), 4*comm->WarpSize, NCCL_MAX_NTHREADS, NCCL_MAX_NTHREADS, comm->WarpSize); comm->maxThreads[NCCL_ALGO_RING][NCCL_PROTO_LL] = comm->maxThreads[NCCL_ALGO_TREE][NCCL_PROTO_LL] = comm->maxThreads[NCCL_ALGO_COLLNET_DIRECT][NCCL_PROTO_LL] = - getNthreads("NCCL_NTHREADS", ncclParamNthreads(), 4*comm->WarpSize, NCCL_MAX_NTHREADS, NCCL_MAX_NTHREADS); + getNthreads("NCCL_NTHREADS", ncclParamNthreads(), 4*comm->WarpSize, NCCL_MAX_NTHREADS, NCCL_MAX_NTHREADS, comm->WarpSize); comm->maxThreads[NCCL_ALGO_RING][NCCL_PROTO_LL128] = comm->maxThreads[NCCL_ALGO_TREE][NCCL_PROTO_LL128] = - getNthreads("NCCL_LL128_NTHREADS", ncclParamLl128Nthreads(), 4*comm->WarpSize, NCCL_LL128_MAX_NTHREADS, NCCL_LL128_MAX_NTHREADS); + getNthreads("NCCL_LL128_NTHREADS", ncclParamLl128Nthreads(), 4*comm->WarpSize, NCCL_LL128_MAX_NTHREADS, NCCL_LL128_MAX_NTHREADS, comm->WarpSize); #else getNthreads("NCCL_NTHREADS", ncclParamNthreads(), 2*WARP_SIZE, NCCL_SIMPLE_MAX_NTHREADS, simpleDefaultThreads); comm->maxThreads[NCCL_ALGO_TREE][NCCL_PROTO_SIMPLE] = @@ -258,14 +272,14 @@ ncclResult_t ncclTopoTuneModel(struct ncclComm* comm, int minCompCap, int maxCom int nRanks = comm->nRanks; if (nRanks <= 1) return ncclSuccess; - int compCap80 = minCompCap == 80 && maxCompCap == 80 ? 1 : 0; + int compCapIndex = (minCompCap == 80 && maxCompCap == 80) ? AMPERE_COMPCAP_IDX : ((minCompCap == 90 && maxCompCap == 90) ? HOPPER_COMPCAP_IDX : VOLTA_COMPCAP_IDX); int cpuArch, cpuVendor, cpuModel; NCCLCHECK(ncclTopoCpuType(comm->topo, &cpuArch, &cpuVendor, &cpuModel)); int index2 = nNodes <= 2 ? nNodes-1 : 2; // LL: for single node, we look at GPU type; for multi-node, we look at CPU type - int index1 = nNodes == 1 ? compCap80 : cpuVendor == NCCL_TOPO_CPU_VENDOR_AMD ? 1 : 0; + int index1 = nNodes == 1 ? compCapIndex : cpuVendor == NCCL_TOPO_CPU_VENDOR_AMD ? 1 : 0; double llMaxBw = llMaxBws[index1][index2]; - double perChMaxTreeBw = perChMaxTreeBws[compCap80][index2]; + double perChMaxTreeBw = perChMaxTreeBws[compCapIndex][index2]; // De-penalize Tree/Simple latency on Power systems to favor Tree than Ring //if (cpuArch == NCCL_TOPO_CPU_ARCH_POWER) hwLat[NCCL_HW_PCI][NCCL_ALGO_TREE][NCCL_PROTO_SIMPLE] = hwLat[NCCL_HW_PCI][NCCL_ALGO_RING][NCCL_PROTO_SIMPLE]; float ppn = (float)nRanks / nNodes; // if ppn < 2, then we are sending/receiving at the same GPU through the NIC, apply some bw discount @@ -298,7 +312,7 @@ ncclResult_t ncclTopoTuneModel(struct ncclComm* comm, int minCompCap, int maxCom else busBw *= rcclTuningModel[comm->topo->tuning].bwRatio[1][a][p]; #else - if (compCap80) busBw = std::min(busBw, 235.0f); + if (compCapIndex == AMPERE_COMPCAP_IDX) busBw = std::min(busBw, 235.0f); if (a == NCCL_ALGO_RING && p == NCCL_PROTO_LL) { busBw = std::min(llMaxBw, busBw * ((nNodes > 1 || coll == ncclFuncAllReduce || coll == ncclFuncReduce) ? 1.0/4.0 : 1.0/3.0)); } if (a == NCCL_ALGO_RING && p == NCCL_PROTO_LL128) busBw = std::min(busBw * (ppn < 2 ? 0.7 : 0.92 /*120.0/128.0*/), ll128MaxBwPerCh*graphs[a]->nChannels); if (a == NCCL_ALGO_TREE) busBw = std::min(busBw*.92, graphs[a]->nChannels*perChMaxTreeBw); @@ -306,14 +320,14 @@ ncclResult_t ncclTopoTuneModel(struct ncclComm* comm, int minCompCap, int maxCom if (a == NCCL_ALGO_TREE && p == NCCL_PROTO_LL128) busBw = std::min(busBw * (nNodes == 1 ? 7.0/9.0 : 120.0/128.0), ll128MaxBwPerCh*graphs[a]->nChannels); if (a == NCCL_ALGO_COLLNET_DIRECT && p != NCCL_PROTO_SIMPLE) busBw = 0; // Not used if (a == NCCL_ALGO_COLLNET_CHAIN && p != NCCL_PROTO_SIMPLE) busBw = 0; // Not used - if (a == NCCL_ALGO_COLLNET_DIRECT && p == NCCL_PROTO_SIMPLE) { - // Collnet+Direct requires all GPUs to have a local NIC to work at full speed - float factor = ppn / (1.0*graphs[a]->nChannels); // GPU/NIC ratio - factor -= (factor-1)/2; - busBw /= factor; - } - if (a == NCCL_ALGO_COLLNET_CHAIN && p == NCCL_PROTO_SIMPLE) busBw *= .75; + if (a == NCCL_ALGO_COLLNET_DIRECT && p == NCCL_PROTO_SIMPLE) { + // Collnet+Direct requires all GPUs to have a local NIC to work at full speed + float factor = ppn / (1.0*graphs[a]->nChannels); // GPU/NIC ratio + factor -= (factor-1)/2; + busBw /= factor; + } #endif + if (a == NCCL_ALGO_COLLNET_CHAIN && p == NCCL_PROTO_SIMPLE) busBw *= .75; // Convert bus BW to algorithm BW float ratio = (a != NCCL_ALGO_RING) ? .5 : (1.0 * nRanks) / nsteps; @@ -387,11 +401,18 @@ ncclResult_t ncclTopoTuneModel(struct ncclComm* comm, int minCompCap, int maxCom pEnable = (graphs[a]->typeInter <= PATH_PXB) && graphs[a]->typeIntra <= PATH_NVL && (comm->topo->nodes[GPU].nodes[0].gpu.gcn == 910 && comm->topo->ll128Enabled) ? 1 : 0; #else - // Enable LL128 by default only on Volta/Ampere+NVLink. Other cases are not tested and may cause silent data corruption. - pEnable = (graphs[a]->typeInter <= PATH_PXB) && graphs[a]->typeIntra <= PATH_NVL && - ((minCompCap == 70 && maxCompCap == 70) || (minCompCap == 80 && maxCompCap == 80)) ? 1 : 0; + // Enable LL128 by default only on Volta/Ampere/Hopper+NVLink. Other cases are not tested and may cause silent data corruption. + pEnable = 1; + pEnable &= (graphs[a]->typeInter <= PATH_PXB); + pEnable &= (graphs[a]->typeIntra <= PATH_NVL); + pEnable &= (minCompCap == maxCompCap); + switch (minCompCap) { + case 70: pEnable &= 1; break; + case 80: pEnable &= 1; break; + case 90: pEnable &= !(CUDART_VERSION == 11080 && c == ncclFuncAllReduce && a == NCCL_ALGO_RING && comm->nRanks == 2); break; + default: pEnable &= 0; break; + } #endif - if (comm->rank == 0 && c == 0 && a == 0) INFO(NCCL_INIT, "Using tuning table %d with LL128 %s", comm->topo->tuning, pEnable ? "enabled" : "disabled"); } if (pEnable == 0) comm->bandwidths[c][a][p] = 0; // Only disable algo for Allreduce since others only have one diff --git a/projects/rccl/src/graph/xml.cc b/projects/rccl/src/graph/xml.cc index 99dd5ab9f7..3b8691c6d8 100644 --- a/projects/rccl/src/graph/xml.cc +++ b/projects/rccl/src/graph/xml.cc @@ -688,7 +688,7 @@ ncclResult_t ncclTopoGetXmlFromGpu(struct ncclXmlNode* pciNode, uint32_t rocmDev } #else // NVML NVLink detection - int maxNvLinks = (sm < 60) ? 0 : (sm < 70) ? 4 : (sm < 80) ? 6 : 12; + int maxNvLinks = (sm < 60) ? 0 : (sm < 70) ? 4 : (sm < 80) ? 6 : (sm < 90) ? 12 : 18; if (maxNvLinks > 0 && nvmlDev == NULL) { WARN("No NVML device handle. Skipping nvlink detection."); @@ -701,8 +701,21 @@ ncclResult_t ncclTopoGetXmlFromGpu(struct ncclXmlNode* pciNode, uint32_t rocmDev if ((ncclNvmlDeviceGetNvLinkCapability(nvmlDev, l, NVML_NVLINK_CAP_P2P_SUPPORTED, &canP2P) != ncclSuccess) || !canP2P) continue; // Make sure the Nvlink is up. The previous call should have trained the link. - nvmlEnableState_t isActive; - if ((ncclNvmlDeviceGetNvLinkState(nvmlDev, l, &isActive) != ncclSuccess) || (isActive != NVML_FEATURE_ENABLED)) continue; + nvmlEnableState_t isActive = NVML_FEATURE_DISABLED; +#if CUDART_VERSION >= 11080 + if (sm >= 90) { + nvmlFieldValue_t fv; + fv.fieldId = NVML_FI_DEV_NVLINK_GET_STATE; + fv.scopeId = l; + // fv.value will contain NV_FEATURE_ENABLED or NV_FEATURE_DISABLED + if ((ncclNvmlDeviceGetFieldValues(nvmlDev, 1, &fv) == ncclSuccess) && (fv.nvmlReturn == NVML_SUCCESS)) + isActive = (nvmlEnableState_t) fv.value.uiVal; + } else /* FALLTHRU to GetNvLinkState if before SM90 */ +#endif + { + (void) ncclNvmlDeviceGetNvLinkState(nvmlDev, l, &isActive); + } + if (isActive != NVML_FEATURE_ENABLED) continue; // Try to figure out what's on the other side of the NVLink nvmlPciInfo_t remoteProc; diff --git a/projects/rccl/src/group.cc b/projects/rccl/src/group.cc index af03c1fd20..bc0e2a54a1 100644 --- a/projects/rccl/src/group.cc +++ b/projects/rccl/src/group.cc @@ -213,8 +213,8 @@ static void groupCleanup(struct ncclComm** groupCommHeadPtr, struct ncclComm** g for (int i = 0; i < comm->nRanks; i++) { comm->tasks.peers[i].sendSeen = false; comm->tasks.peers[i].recvSeen = false; - comm->connectSend[i] = 0; - comm->connectRecv[i] = 0; + comm->connectSend[i] = 0UL; + comm->connectRecv[i] = 0UL; } comm->unlaunchedPlansHead = nullptr; // Reclaim abandoned kernel plan memory. Note ncclWork structs were already @@ -333,6 +333,8 @@ static ncclResult_t groupLaunch(struct ncclAsyncJob *job_) { job = job->next; } while (job != nullptr); + // Let preconnect threads progress. + if (jobsDone == false) usleep(1); } while (jobsDone == false); if (ret != ncclSuccess) goto fail; diff --git a/projects/rccl/src/include/comm.h b/projects/rccl/src/include/comm.h index 41997b35d6..ef7bc5c94f 100644 --- a/projects/rccl/src/include/comm.h +++ b/projects/rccl/src/include/comm.h @@ -174,8 +174,8 @@ struct ncclComm { ncclCollNet_t* ncclCollNet; void* bootstrap; // Bitmasks for ncclTransportP2pSetup - uint32_t* connectSend; - uint32_t* connectRecv; + uint64_t* connectSend; + uint64_t* connectRecv; int rank; // my rank in the communicator int nRanks; // number of GPUs in communicator diff --git a/projects/rccl/src/include/cudawrap.h b/projects/rccl/src/include/cudawrap.h index 2bd3b4dab0..0fd594582a 100644 --- a/projects/rccl/src/include/cudawrap.h +++ b/projects/rccl/src/include/cudawrap.h @@ -8,6 +8,8 @@ #define NCCL_CUDAWRAP_H_ #include +#include +#include "checks.h" #if CUDART_VERSION >= 11030 #include @@ -83,6 +85,18 @@ DECLARE_CUDA_PFN_EXTERN(cuDriverGetVersion, 2020); DECLARE_CUDA_PFN_EXTERN(cuGetProcAddress, 11030); -ncclResult_t cudaLibraryInit(void); +ncclResult_t ncclCudaLibraryInit(void); + +extern int ncclCudaDriverVersionCache; + +inline ncclResult_t ncclCudaDriverVersion(int* driver) { + int version = __atomic_load_n(&ncclCudaDriverVersionCache, __ATOMIC_RELAXED); + if (version == -1) { + CUDACHECK(cudaDriverGetVersion(&version)); + __atomic_store_n(&ncclCudaDriverVersionCache, version, __ATOMIC_RELAXED); + } + *driver = version; + return ncclSuccess; +} #endif diff --git a/projects/rccl/src/include/graph.h b/projects/rccl/src/include/graph.h index 2e4e6769b4..063c1b1289 100644 --- a/projects/rccl/src/include/graph.h +++ b/projects/rccl/src/include/graph.h @@ -37,6 +37,7 @@ ncclResult_t ncclTopoCheckGdr(struct ncclTopoSystem* topo, int64_t busId, int ne #define MAX_XGMI_INTER_GPUS 4 ncclResult_t ncclTopoGetIntraNetDev(struct ncclTopoSystem* system, int rank, struct ncclTopoGraph* graph, int channelId, int type, int* dev); ncclResult_t ncclTopoGetLinkType(struct ncclTopoSystem* system, int cudaDev1, int cudaDev2, bool* isXGMI, int maxInter=MAX_XGMI_INTER_GPUS, int nInter=0, int *inter=nullptr); +ncclResult_t ncclTopoNeedFlush(struct ncclTopoSystem* system, int64_t busId, int* flush); ncclResult_t ncclTopoCheckNet(struct ncclTopoSystem* system, int64_t id1, int64_t id2, int* net); int ncclPxnDisable(struct ncclComm* comm); ncclResult_t ncclTopoGetPxnRanks(struct ncclComm* comm, int** intermediateRanks, int* nranks); @@ -109,7 +110,7 @@ struct ncclTopoRanks { }; ncclResult_t ncclTopoPreset(struct ncclComm* comm, - struct ncclTopoGraph* treeGraph, struct ncclTopoGraph* ringGraph, + struct ncclTopoGraph* treeGraph, struct ncclTopoGraph* ringGraph, struct ncclTopoGraph* collNetGraph, struct ncclTopoRanks* topoRanks); ncclResult_t ncclTopoPostset(struct ncclComm* comm, int* firstRanks, int* treePatterns, diff --git a/projects/rccl/src/include/nvmlwrap.h b/projects/rccl/src/include/nvmlwrap.h index 29731dd835..fa1f5cf835 100644 --- a/projects/rccl/src/include/nvmlwrap.h +++ b/projects/rccl/src/include/nvmlwrap.h @@ -107,6 +107,75 @@ typedef enum nvmlGpuP2PCapsIndex_enum NVML_P2P_CAPS_INDEX_UNKNOWN } nvmlGpuP2PCapsIndex_t; +/** + * Represents the type for sample value returned + */ +typedef enum nvmlValueType_enum +{ + NVML_VALUE_TYPE_DOUBLE = 0, + NVML_VALUE_TYPE_UNSIGNED_INT = 1, + NVML_VALUE_TYPE_UNSIGNED_LONG = 2, + NVML_VALUE_TYPE_UNSIGNED_LONG_LONG = 3, + NVML_VALUE_TYPE_SIGNED_LONG_LONG = 4, + + // Keep this last + NVML_VALUE_TYPE_COUNT +}nvmlValueType_t; + + +/** + * Union to represent different types of Value + */ +typedef union nvmlValue_st +{ + double dVal; //!< If the value is double + unsigned int uiVal; //!< If the value is unsigned int + unsigned long ulVal; //!< If the value is unsigned long + unsigned long long ullVal; //!< If the value is unsigned long long + signed long long sllVal; //!< If the value is signed long long +}nvmlValue_t; + +/** + * Field Identifiers. + * + * All Identifiers pertain to a device. Each ID is only used once and is guaranteed never to change. + */ + +/* NVLink Speed */ +#define NVML_FI_DEV_NVLINK_SPEED_MBPS_COMMON 90 //!< Common NVLink Speed in MBps for active links +#define NVML_FI_DEV_NVLINK_LINK_COUNT 91 //!< Number of NVLinks present on the device + +/** + * Remote device NVLink ID + * + * Link ID needs to be specified in the scopeId field in nvmlFieldValue_t. + */ +#define NVML_FI_DEV_NVLINK_REMOTE_NVLINK_ID 146 //!< Remote device NVLink ID + +/** + * NVSwitch: connected NVLink count + */ +#define NVML_FI_DEV_NVSWITCH_CONNECTED_LINK_COUNT 147 //!< Number of NVLinks connected to NVSwitch + +#define NVML_FI_DEV_NVLINK_GET_SPEED 164 +#define NVML_FI_DEV_NVLINK_GET_STATE 165 +#define NVML_FI_DEV_NVLINK_GET_VERSION 166 +#define NVML_FI_MAX 167 //!< One greater than the largest field ID defined above + +/** + * Information for a Field Value Sample + */ +typedef struct nvmlFieldValue_st +{ + unsigned int fieldId; //!< ID of the NVML field to retrieve. This must be set before any call that uses this struct. See the constants starting with NVML_FI_ above. + unsigned int scopeId; //!< Scope ID can represent data used by NVML depending on fieldId's context. For example, for NVLink throughput counter data, scopeId can represent linkId. + long long timestamp; //!< CPU Timestamp of this value in microseconds since 1970 + long long latencyUsec; //!< How long this field value took to update (in usec) within NVML. This may be averaged across several fields that are serviced by the same driver call. + nvmlValueType_t valueType; //!< Type of the value stored in value + nvmlReturn_t nvmlReturn; //!< Return code for retrieving this value. This must be checked before looking at value, as value is undefined if nvmlReturn != NVML_SUCCESS + nvmlValue_t value; //!< Value for this field. This is only valid if nvmlReturn == NVML_SUCCESS +} nvmlFieldValue_t; + /* End of nvml.h */ #endif // NCCL_NVML_DIRECT @@ -135,4 +204,6 @@ ncclResult_t ncclNvmlDeviceGetNvLinkRemotePciInfo(nvmlDevice_t device, unsigned ncclResult_t ncclNvmlDeviceGetNvLinkCapability(nvmlDevice_t device, unsigned int link, nvmlNvLinkCapability_t capability, unsigned int *capResult); ncclResult_t ncclNvmlDeviceGetCudaComputeCapability(nvmlDevice_t device, int* major, int* minor); ncclResult_t ncclNvmlDeviceGetP2PStatus(nvmlDevice_t device1, nvmlDevice_t device2, nvmlGpuP2PCapsIndex_t p2pIndex, nvmlGpuP2PStatus_t* p2pStatus); +ncclResult_t ncclNvmlDeviceGetFieldValues(nvmlDevice_t device, int valuesCount, nvmlFieldValue_t *values); + #endif // End include guard diff --git a/projects/rccl/src/include/strongstream.h b/projects/rccl/src/include/strongstream.h index 2882e55aa5..b57649e491 100644 --- a/projects/rccl/src/include/strongstream.h +++ b/projects/rccl/src/include/strongstream.h @@ -18,11 +18,11 @@ struct ncclCudaGraph { #if CUDART_VERSION >= 11030 cudaGraph_t graph; - uint64_t graphId; + unsigned long long graphId; #endif }; -inline struct ncclCudaGraph ncclCudaGraphNull() { +inline struct ncclCudaGraph ncclCudaGraphNone() { struct ncclCudaGraph tmp; #if CUDART_VERSION >= 11030 tmp.graph = nullptr; @@ -50,7 +50,6 @@ inline bool ncclCudaGraphSame(struct ncclCudaGraph a, struct ncclCudaGraph b) { ncclResult_t ncclCudaGetCapturingGraph(struct ncclCudaGraph* graph, hipStream_t stream); ncclResult_t ncclCudaGraphAddDestructor(struct ncclCudaGraph graph, hipHostFn_t fn, void* arg); - /* ncclStrongStream: An abstraction over CUDA streams that do not lose their * identity while being captured. Regular streams have the deficiency that the * captured form of a stream in one graph launch has no relation to the @@ -58,37 +57,30 @@ ncclResult_t ncclCudaGraphAddDestructor(struct ncclCudaGraph graph, hipHostFn_t * streams unfit for the use of serializing access to a persistent resource. * Strong streams have been introduced to address this need. * - * Constraints of using strong streams: + * - All updates to a strong stream must be enclosed by a Acquire/Release pair. * - * - Operations that enqueue work to the strong stream need to be enclosed by - * ncclStrongStream[Acquire/Release] pairs. Acquire/release act like fences, - * the strong stream is not stateful so there is no harm in redundant acquire - * or releases. + * - The Acquire, Release, and all updates take a ncclCudaGraph parameter + * indicating the currently capturing graph (or none). This parameter must be + * the same for the entire sequence of {Acquire; ...; Release}. * * - An {Acquire; ...; Release} sequence must not be concurrent with any * other operations against the strong stream including graph launches which * reference this stream. - * - * - All strong stream functions take a "graph" parameter which must reference - * the currently capturing graph, or null if none. */ struct ncclStrongStream; ncclResult_t ncclStrongStreamConstruct(struct ncclStrongStream* ss); ncclResult_t ncclStrongStreamDestruct(struct ncclStrongStream* ss); -// Has this strong stream ever been captured in a graph. -bool ncclStrongStreamEverCaptured(struct ncclStrongStream* ss); - // Acquire-fence the strong stream. ncclResult_t ncclStrongStreamAcquire( struct ncclCudaGraph graph, struct ncclStrongStream* ss ); // Acquire-fence the strong stream assuming no graph is capturing. This permits -// the caller to enqueue directly to the `ss->stream` member using native CUDA -// calls. Strong stream must be released via: -// ncclStrongStreamRelease(ncclCudaGraphNull(), graphRefs, ss); +// the caller to enqueue directly to the `ss->cudaStream` member using native CUDA +// calls. Strong stream still must be released via: +// ncclStrongStreamRelease(ncclCudaGraphNone(), ss); ncclResult_t ncclStrongStreamAcquireUncaptured(struct ncclStrongStream* ss); // Release-fence of the strong stream. @@ -104,6 +96,7 @@ ncclResult_t ncclStrongStreamLaunchKernel( struct ncclCudaGraph graph, struct ncclStrongStream* ss, void* fn, dim3 grid, dim3 block, void** args, size_t sharedMemBytes ); + // Cause `a` to wait for the current state `b`. Both `a` and `b` must be acquired. ncclResult_t ncclStrongStreamWaitStream( struct ncclCudaGraph graph, struct ncclStrongStream* a, struct ncclStrongStream* b @@ -122,21 +115,23 @@ ncclResult_t ncclStrongStreamSynchronize(struct ncclStrongStream* ss); //////////////////////////////////////////////////////////////////////////////// +struct ncclStrongStreamGraph; // internal to ncclStrongStream + struct ncclStrongStream { - hipStream_t stream; - hipEvent_t event; - #if CUDART_VERSION >= 11030 - cudaGraphNode_t node; // null if never captured, otherwise never null again - uint64_t graphId:63, eventIsLagging:1; - #endif + // Used when not graph capturing. + hipStream_t cudaStream; +#if CUDART_VERSION >= 11030 + // The event used to establish order between graphs and streams. During acquire + // this event is waited on, during release it is recorded to. + cudaEvent_t serialEvent; + // This stream ever appeared in a graph capture. + bool everCaptured; + // Tracks whether serialEvent needs to be recorded to upon Release(). + bool serialEventNeedsRecord; + struct ncclStrongStreamGraph* graphHead; +#else + hipEvent_t scratchEvent; +#endif }; -inline bool ncclStrongStreamEverCaptured(struct ncclStrongStream* ss) { - #if CUDART_VERSION >= 11030 - return ss->node != nullptr; - #else - return false; - #endif -} - #endif diff --git a/projects/rccl/src/init.cc b/projects/rccl/src/init.cc index 7e3ccef8f6..8c6be42fa7 100644 --- a/projects/rccl/src/init.cc +++ b/projects/rccl/src/init.cc @@ -540,7 +540,7 @@ static ncclResult_t devCommSetup(ncclComm_t comm) { int nRanks = comm->nRanks; struct ncclDevCommAndChannels *devCommAndChans, tmpCommAndChans; - NCCLCHECK(ncclCudaCallocAsync(&devCommAndChans, 1, comm->deviceStream.stream)); + NCCLCHECK(ncclCudaCallocAsync(&devCommAndChans, 1, comm->deviceStream.cudaStream)); ncclCommPushCudaFree(comm, devCommAndChans); comm->devComm = &devCommAndChans->comm; tmpCommAndChans.comm.rank = comm->rank; @@ -587,7 +587,7 @@ static ncclResult_t devCommSetup(ncclComm_t comm) { tmpCommAndChans.channels[c].workFifoDone = &comm->workFifoDone[c]; if (comm->channels[c].ring.userRanks != nullptr) { - NCCLCHECK(ncclCudaMemcpyAsync(tmpCommAndChans.channels[c].ring.userRanks, comm->channels[c].ring.userRanks, nRanks, comm->deviceStream.stream)); + NCCLCHECK(ncclCudaMemcpyAsync(tmpCommAndChans.channels[c].ring.userRanks, comm->channels[c].ring.userRanks, nRanks, comm->deviceStream.cudaStream)); } } @@ -608,10 +608,9 @@ static ncclResult_t devCommSetup(ncclComm_t comm) { NCCLCHECK(ncclCudaCalloc(&tmpCommAndChans.comm.devProf, MAXCHANNELS*PROFILE_NUM_LAUNCHES), comm->sideStream); #endif - NCCLCHECK(ncclCudaMemcpyAsync(devCommAndChans, &tmpCommAndChans, 1, comm->deviceStream.stream)); - CUDACHECK(hipStreamSynchronize(comm->deviceStream.stream)); - NCCLCHECK(ncclStrongStreamRelease(ncclCudaGraphNull(), &comm->deviceStream)); - + NCCLCHECK(ncclCudaMemcpyAsync(devCommAndChans, &tmpCommAndChans, 1, comm->deviceStream.cudaStream)); + CUDACHECK(hipStreamSynchronize(comm->deviceStream.cudaStream)); + NCCLCHECK(ncclStrongStreamRelease(ncclCudaGraphNone(), &comm->deviceStream)); return ncclSuccess; } @@ -942,7 +941,7 @@ static ncclResult_t initTransportsRank(struct ncclComm* comm, ncclUniqueId* comm comm->nChannels = (comm->topo->nodes[GPU].count != comm->topo->nRanks && comm->topo->nodes[NET].count) ? std::min(treeGraph.nChannels, ringGraph.nChannels) : ringGraph.nChannels; - NCCLCHECK(ncclTopoPreset(comm, &treeGraph, &ringGraph, &allGather3Data[rank].topoRanks)); + NCCLCHECK(ncclTopoPreset(comm, &treeGraph, &ringGraph, &collNetGraph, &allGather3Data[rank].topoRanks)); NCCLCHECK(bootstrapAllGather(comm->bootstrap, allGather3Data, sizeof(*allGather3Data))); @@ -1288,13 +1287,13 @@ collnet_cleanup: for (int c=0; cp2pnChannelsPerPeer; c++) { NCCLCHECK(ncclChannelCompute(comm, peer, c, ncclFuncSend, &channelId)); if (comm->channels[channelId].peers[peer].send[1].connected == 0) { - comm->connectSend[peer] |= (1<connectSend[peer] |= (1UL<p2pnChannelsPerPeer; c++) { NCCLCHECK(ncclChannelCompute(comm, peer, c, ncclFuncRecv, &channelId)); if (comm->channels[channelId].peers[peer].recv[1].connected == 0) { - comm->connectRecv[peer] |= (1<connectRecv[peer] |= (1UL<= 11030 /* @@ -107,16 +107,17 @@ static void initOnceFunc() { goto error; } - res = pfn_cuDriverGetVersion(&cudaDriverVersion); + int driverVersion; + res = pfn_cuDriverGetVersion(&driverVersion); if (res != 0) { WARN("cuDriverGetVersion failed with %d", res); goto error; } - INFO(NCCL_INIT, "cudaDriverVersion %d", cudaDriverVersion); + INFO(NCCL_INIT, "cudaDriverVersion %d", driverVersion); - if (cudaDriverVersion < CUDA_DRIVER_MIN_VERSION) { - // WARN("CUDA Driver version found is %d. Minimum requirement is %d", cudaDriverVersion, CUDA_DRIVER_MIN_VERSION); + if (driverVersion < CUDA_DRIVER_MIN_VERSION) { + // WARN("CUDA Driver version found is %d. Minimum requirement is %d", driverVersion, CUDA_DRIVER_MIN_VERSION); // Silently ignore version check mismatch for backwards compatibility goto error; } @@ -148,7 +149,7 @@ error: return; } -ncclResult_t cudaLibraryInit() { +ncclResult_t ncclCudaLibraryInit() { pthread_once(&initOnceControl, initOnceFunc); return initResult; } diff --git a/projects/rccl/src/misc/nvmlwrap.cc b/projects/rccl/src/misc/nvmlwrap.cc index 5db7c6be5c..2de993a6e5 100644 --- a/projects/rccl/src/misc/nvmlwrap.cc +++ b/projects/rccl/src/misc/nvmlwrap.cc @@ -38,6 +38,7 @@ namespace { NCCL_NVML_FN(nvmlDeviceGetNvLinkCapability, nvmlReturn_t, (nvmlDevice_t device, unsigned int link, nvmlNvLinkCapability_t capability, unsigned int *capResult)) NCCL_NVML_FN(nvmlDeviceGetCudaComputeCapability, nvmlReturn_t, (nvmlDevice_t device, int* major, int* minor)) NCCL_NVML_FN(nvmlDeviceGetP2PStatus, nvmlReturn_t, (nvmlDevice_t device1, nvmlDevice_t device2, nvmlGpuP2PCapsIndex_t p2pIndex, nvmlGpuP2PStatus_t* p2pStatus)) + NCCL_NVML_FN(nvmlDeviceGetFieldValues, nvmlReturn_t, (nvmlDevice_t device, int valuesCount, nvmlFieldValue_t *values)) std::mutex lock; // NVML has had some thread safety bugs bool initialized = false; @@ -80,7 +81,8 @@ ncclResult_t ncclNvmlEnsureInitialized() { {(void**)&pfn_nvmlDeviceGetNvLinkRemotePciInfo, "nvmlDeviceGetNvLinkRemotePciInfo"}, {(void**)&pfn_nvmlDeviceGetNvLinkCapability, "nvmlDeviceGetNvLinkCapability"}, {(void**)&pfn_nvmlDeviceGetCudaComputeCapability, "nvmlDeviceGetCudaComputeCapability"}, - {(void**)&pfn_nvmlDeviceGetP2PStatus, "nvmlDeviceGetP2PStatus"} + {(void**)&pfn_nvmlDeviceGetP2PStatus, "nvmlDeviceGetP2PStatus"}, + {(void**)&pfn_nvmlDeviceGetFieldValues, "nvmlDeviceGetFieldValues"} }; for(Symbol sym: symbols) { *sym.ppfn = dlsym(libhandle, sym.name); @@ -260,3 +262,10 @@ ncclResult_t ncclNvmlDeviceGetP2PStatus( } return ncclSuccess; } + +ncclResult_t ncclNvmlDeviceGetFieldValues(nvmlDevice_t device, int valuesCount, nvmlFieldValue_t *values) { + NCCLCHECK(ncclNvmlEnsureInitialized()); + std::lock_guard locked(lock); + NVMLTRY(nvmlDeviceGetFieldValues, device, valuesCount, values); + return ncclSuccess; +} diff --git a/projects/rccl/src/misc/strongstream.cc b/projects/rccl/src/misc/strongstream.cc index 936d720a85..9e0dcc4666 100644 --- a/projects/rccl/src/misc/strongstream.cc +++ b/projects/rccl/src/misc/strongstream.cc @@ -5,37 +5,65 @@ ************************************************************************/ #include "strongstream.h" +#include "rocmwrap.h" #include "checks.h" #include "param.h" +// Tracks the chain of graph nodes for a given graph captured identified by +// its graph id. This state has to live for as long as captured work is being +// submitted. CUDA doesn't have mechanism to inform us when the user ends capture +// so the best we can do is get notified when the graph is destroyed. +struct ncclStrongStreamGraph { + struct ncclStrongStreamGraph* next; + // Atomically exchanged to false by both the main thread or the graph destructor + // callback. The last to arrive deletes the node. + bool alive; + unsigned long long graphId; + // For each graph we track the "tip" of the chain of graph nodes. A linear + // chain would always have just one node at its tip, but since we have to merge + // in chains from other streams (via ncclStrongStreamWaitStream) some spots + // in the chain can be wider than a single node and thus need a list, so we + // maintain a dynamically sized array of tip nodes. + int tipCount, tipCapacity; + hipGraphNode_t* tipNodes; +}; + +static void ncclStrongStreamGraphDelete(struct ncclStrongStreamGraph* g) { + free(g->tipNodes); + free(g); +} + //////////////////////////////////////////////////////////////////////////////// ncclResult_t ncclCudaGetCapturingGraph( struct ncclCudaGraph* graph, hipStream_t stream ) { - #if CUDART_VERSION >= 11030 - thread_local int driver = -1; - if (driver == -1) { - CUDACHECK(cudaDriverGetVersion(&driver)); - } - if (driver < 11030) { + #if CUDART_VERSION >= 10000 // cudaStreamGetCaptureInfo + int driver; + NCCLCHECK(ncclCudaDriverVersion(&driver)); + if (CUDART_VERSION < 11030 || driver < 11030) { cudaStreamCaptureStatus status; unsigned long long gid; - graph->graph = nullptr; CUDACHECK(cudaStreamGetCaptureInfo(stream, &status, &gid)); + #if CUDART_VERSION >= 11030 + graph->graph = nullptr; + graph->graphId = ULLONG_MAX; + #endif if (status != cudaStreamCaptureStatusNone) { - WARN("The installed CUDA driver is older than the minimum version (R465) required for NCCL's CUDA Graphs support"); + WARN("NCCL cannot be captured in a graph if either it wasn't built with CUDA runtime >= 11.3 or if the installed CUDA driver < R465."); return ncclInvalidUsage; } } else { - cudaStreamCaptureStatus status; - unsigned long long gid; - CUDACHECK(cudaStreamGetCaptureInfo_v2(stream, &status, &gid, &graph->graph, nullptr, nullptr)); - if (status != cudaStreamCaptureStatusActive) { - graph->graph = nullptr; - gid = ULLONG_MAX; - } - graph->graphId = gid; + #if CUDART_VERSION >= 11030 + cudaStreamCaptureStatus status; + unsigned long long gid; + CUDACHECK(cudaStreamGetCaptureInfo_v2(stream, &status, &gid, &graph->graph, nullptr, nullptr)); + if (status != cudaStreamCaptureStatusActive) { + graph->graph = nullptr; + gid = ULLONG_MAX; + } + graph->graphId = gid; + #endif } #endif return ncclSuccess; @@ -58,52 +86,114 @@ ncclResult_t ncclCudaGraphAddDestructor(struct ncclCudaGraph graph, hipHostFn_t //////////////////////////////////////////////////////////////////////////////// ncclResult_t ncclStrongStreamConstruct(struct ncclStrongStream* ss) { - CUDACHECK(hipStreamCreateWithFlags(&ss->stream, hipStreamNonBlocking)); - CUDACHECK(hipEventCreateWithFlags(&ss->event, hipEventDisableTiming)); + CUDACHECK(hipStreamCreateWithFlags(&ss->cudaStream, hipStreamNonBlocking)); #if CUDART_VERSION >= 11030 - ss->node = nullptr; - ss->graphId = (1ull<<(8*sizeof(long long)-1))-1; - ss->eventIsLagging = 0; + CUDACHECK(cudaEventCreateWithFlags(&ss->serialEvent, cudaEventDisableTiming)); + ss->everCaptured = false; + ss->serialEventNeedsRecord = false; + ss->graphHead = nullptr; + #else + CUDACHECK(hipEventCreateWithFlags(&ss->scratchEvent, hipEventDisableTiming)); #endif return ncclSuccess; } +static void graphDestructor(void* arg) { + struct ncclStrongStreamGraph* g = (struct ncclStrongStreamGraph*)arg; + if (false == __atomic_exchange_n(&g->alive, false, __ATOMIC_ACQ_REL)) { + // Last to arrive deletes list node. + ncclStrongStreamGraphDelete(g); + } +} + ncclResult_t ncclStrongStreamDestruct(struct ncclStrongStream* ss) { + CUDACHECK(hipStreamDestroy(ss->cudaStream)); #if CUDART_VERSION >= 11030 - CUDACHECK(cudaEventDestroy(ss->event)); + CUDACHECK(hipEventDestroy(ss->serialEvent)); + // Delete list of per-graph chains. + struct ncclStrongStreamGraph* g = ss->graphHead; + while (g != nullptr) { + struct ncclStrongStreamGraph* next = g->next; + if (false == __atomic_exchange_n(&g->alive, false, __ATOMIC_ACQ_REL)) { + // Last to arrive deletes list node. + ncclStrongStreamGraphDelete(g); + } + g = next; + } + #else + CUDACHECK(hipEventDestroy(ss->scratchEvent)); #endif - CUDACHECK(hipStreamDestroy(ss->stream)); return ncclSuccess; } NCCL_PARAM(GraphMixingSupport, "GRAPH_MIXING_SUPPORT", 1) +static void ensureTips(struct ncclStrongStreamGraph* g, int n) { + if (g->tipCapacity < n) { + g->tipNodes = (hipGraphNode_t*)realloc(g->tipNodes, n*sizeof(hipGraphNode_t)); + g->tipCapacity = n; + } +} + ncclResult_t ncclStrongStreamAcquire( struct ncclCudaGraph graph, struct ncclStrongStream* ss ) { #if CUDART_VERSION >= 11030 bool mixing = ncclParamGraphMixingSupport(); if (graph.graph == nullptr) { - if (mixing && ncclStrongStreamEverCaptured(ss)) { - CUDACHECK(cudaStreamWaitEvent(ss->stream, ss->event, 0)); - ss->eventIsLagging = 0; + if (mixing && ss->everCaptured) { + CUDACHECK(cudaStreamWaitEvent(ss->cudaStream, ss->serialEvent, 0)); + ss->serialEventNeedsRecord = false; } } else { - if (ss->graphId != graph.graphId) { - if (mixing && ss->eventIsLagging) { - // Can only be here if previous release was for uncaptured work that - // elided updating the event because no capture had yet occurred. - CUDACHECK(cudaStreamWaitEvent(ss->stream, ss->event, 0)); - CUDACHECK(cudaEventRecord(ss->event, ss->stream)); - } - ss->graphId = graph.graphId; - ss->eventIsLagging = 0; - if (mixing) { - CUDACHECK(cudaGraphAddEventWaitNode(&ss->node, graph.graph, nullptr, 0, ss->event)); + ss->everCaptured = true; + // Find the current graph in our list of graphs if it exists. + struct ncclStrongStreamGraph** pg = &ss->graphHead; + struct ncclStrongStreamGraph* g; + while (*pg != nullptr) { + g = *pg; + if (g->graphId == graph.graphId) { + // Move to front of list so that operations after acquire don't have to search the list. + *pg = g->next; + g->next = ss->graphHead; + ss->graphHead = g; + return ncclSuccess; + } else if (false == __atomic_load_n(&g->alive, __ATOMIC_ACQUIRE)) { + // Unrelated graph that has been destroyed. Remove and delete. + *pg = g->next; + ncclStrongStreamGraphDelete(g); } else { - CUDACHECK(cudaGraphAddEmptyNode(&ss->node, graph.graph, nullptr, 0)); + pg = &g->next; } } + + // This is a new graph so add to the list. + g = (struct ncclStrongStreamGraph*)malloc(sizeof(struct ncclStrongStreamGraph)); + g->graphId = graph.graphId; + g->tipNodes = nullptr; + g->tipCapacity = 0; + g->tipCount = 0; + g->next = ss->graphHead; + ss->graphHead = g; + g->alive = true; + NCCLCHECK(ncclCudaGraphAddDestructor(graph, graphDestructor, (void*)g)); + + if (mixing && ss->serialEventNeedsRecord) { + // Can only be here if previous release was for uncaptured work that + // elided updating the event because no capture had yet occurred. + CUDACHECK(cudaStreamWaitEvent(ss->cudaStream, ss->serialEvent, 0)); + CUDACHECK(cudaEventRecord(ss->serialEvent, ss->cudaStream)); + } + ss->serialEventNeedsRecord = false; + + // First node in the chain must be a wait on the serialEvent. + if (mixing) { + ensureTips(g, 1); + CUDACHECK(cudaGraphAddEventWaitNode(&g->tipNodes[0], graph.graph, nullptr, 0, ss->serialEvent)); + g->tipCount = 1; + } else { + g->tipCount = 0; + } } #endif return ncclSuccess; @@ -112,26 +202,38 @@ ncclResult_t ncclStrongStreamAcquire( ncclResult_t ncclStrongStreamAcquireUncaptured(struct ncclStrongStream* ss) { #if CUDART_VERSION >= 11030 bool mixing = ncclParamGraphMixingSupport(); - if (mixing && ncclStrongStreamEverCaptured(ss)) { - CUDACHECK(cudaStreamWaitEvent(ss->stream, ss->event, 0)); + if (mixing && ss->everCaptured) { + CUDACHECK(cudaStreamWaitEvent(ss->cudaStream, ss->serialEvent, 0)); } - ss->eventIsLagging = 1; // Assume the caller is going to add work to stream. + ss->serialEventNeedsRecord = true; // Assume the caller is going to add work to stream. #endif return ncclSuccess; } +static ncclResult_t checkGraphId(struct ncclStrongStreamGraph* g, unsigned long long id) { + if (g == nullptr || g->graphId != id) { + WARN("Expected graph id=%llu was not at head of strong stream's internal list.", id); + return ncclInternalError; + } + return ncclSuccess; +} + ncclResult_t ncclStrongStreamRelease(struct ncclCudaGraph graph, struct ncclStrongStream* ss) { #if CUDART_VERSION >= 11030 bool mixing = ncclParamGraphMixingSupport(); - if (mixing && ss->eventIsLagging) { + if (mixing && ss->serialEventNeedsRecord) { if (graph.graph == nullptr) { - if (ncclStrongStreamEverCaptured(ss)) { - CUDACHECK(cudaEventRecord(ss->event, ss->stream)); - ss->eventIsLagging = 0; + if (ss->everCaptured) { + CUDACHECK(cudaEventRecord(ss->serialEvent, ss->cudaStream)); + ss->serialEventNeedsRecord = false; } } else { - CUDACHECK(cudaGraphAddEventRecordNode(&ss->node, graph.graph, &ss->node, 1, ss->event)); - ss->eventIsLagging = 0; + struct ncclStrongStreamGraph* g = ss->graphHead; + NCCLCHECK(checkGraphId(g, graph.graphId)); + ensureTips(g, 1); + CUDACHECK(cudaGraphAddEventRecordNode(&g->tipNodes[0], graph.graph, g->tipNodes, g->tipCount, ss->serialEvent)); + g->tipCount = 1; + ss->serialEventNeedsRecord = false; } } #endif @@ -143,17 +245,20 @@ ncclResult_t ncclStrongStreamLaunchHost( ) { #if CUDART_VERSION >= 11030 if (graph.graph == nullptr) { - CUDACHECK(cudaLaunchHostFunc(ss->stream, fn, arg)); + CUDACHECK(cudaLaunchHostFunc(ss->cudaStream, fn, arg)); } else { cudaHostNodeParams p; p.fn = fn; p.userData = arg; - CUDACHECK(cudaGraphAddHostNode(&ss->node, graph.graph, &ss->node, 1, &p)); + struct ncclStrongStreamGraph* g = ss->graphHead; + NCCLCHECK(checkGraphId(g, graph.graphId)); + ensureTips(g, 1); + CUDACHECK(cudaGraphAddHostNode(&g->tipNodes[0], graph.graph, g->tipNodes, g->tipCount, &p)); + g->tipCount = 1; } - ss->eventIsLagging = 1; + ss->serialEventNeedsRecord = true; #else - //CUDACHECK(hipLaunchHostFunc(ss->stream, fn, arg)); - CUDACHECK(hipStreamAddCallback(ss->stream, (hipStreamCallback_t)fn, arg, 0)); + CUDACHECK(hipStreamAddCallback(ss->cudaStream, (hipStreamCallback_t)fn, arg, 0)); #endif return ncclSuccess; } @@ -164,9 +269,8 @@ ncclResult_t ncclStrongStreamLaunchKernel( ) { #if CUDART_VERSION >= 11030 if (graph.graph == nullptr) { - CUDACHECK(cudaLaunchKernel(fn, grid, block, args, sharedMemBytes, ss->stream)); + CUDACHECK(cudaLaunchKernel(fn, grid, block, args, sharedMemBytes, ss->cudaStream)); } else { - cudaGraphNode_t tip = ss->node; cudaKernelNodeParams p; p.func = fn; p.gridDim = grid; @@ -174,33 +278,53 @@ ncclResult_t ncclStrongStreamLaunchKernel( p.kernelParams = args; p.sharedMemBytes = sharedMemBytes; p.extra = nullptr; - CUDACHECK(cudaGraphAddKernelNode(&ss->node, graph.graph, &tip, 1, &p)); + struct ncclStrongStreamGraph* g = ss->graphHead; + NCCLCHECK(checkGraphId(g, graph.graphId)); + ensureTips(g, 1); + CUDACHECK(cudaGraphAddKernelNode(&g->tipNodes[0], graph.graph, g->tipNodes, g->tipCount, &p)); + g->tipCount = 1; } - ss->eventIsLagging = 1; + ss->serialEventNeedsRecord = true; #else - CUDACHECK(hipLaunchKernel(fn, grid, block, args, sharedMemBytes, ss->stream)); + CUDACHECK(hipLaunchKernel(fn, grid, block, args, sharedMemBytes, ss->cudaStream)); #endif return ncclSuccess; } +// Merge node list `b` into list `a` but don't add duplicates. +static void mergeTips(struct ncclStrongStreamGraph* a, hipGraphNode_t const* bNodes, int bn) { + int an = a->tipCount; + ensureTips(a, an + bn); + for (int bi=0; bi < bn; bi++) { + for (int ai=0; ai < an; ai++) { + if (a->tipNodes[ai] == bNodes[bi]) goto next_b; + } + a->tipNodes[a->tipCount++] = bNodes[bi]; + next_b:; + } +} + ncclResult_t ncclStrongStreamWaitStream( struct ncclCudaGraph graph, struct ncclStrongStream* a, struct ncclStrongStream* b ) { #if CUDART_VERSION >= 11030 if (graph.graph == nullptr) { - if (b->eventIsLagging) { - b->eventIsLagging = 0; - CUDACHECK(cudaEventRecord(b->event, b->stream)); + if (b->serialEventNeedsRecord) { + b->serialEventNeedsRecord = false; + CUDACHECK(cudaEventRecord(b->serialEvent, b->cudaStream)); } - CUDACHECK(cudaStreamWaitEvent(a->stream, b->event, 0)); - a->eventIsLagging = 1; + CUDACHECK(cudaStreamWaitEvent(a->cudaStream, b->serialEvent, 0)); } else { - cudaGraphNode_t pair[2] = {a->node, b->node}; - CUDACHECK(cudaGraphAddEmptyNode(&a->node, graph.graph, pair, 2)); + struct ncclStrongStreamGraph* ag = a->graphHead; + NCCLCHECK(checkGraphId(ag, graph.graphId)); + struct ncclStrongStreamGraph* bg = b->graphHead; + NCCLCHECK(checkGraphId(bg, graph.graphId)); + mergeTips(ag, bg->tipNodes, bg->tipCount); } + a->serialEventNeedsRecord = true; #else - CUDACHECK(hipEventRecord(b->event, b->stream)); - CUDACHECK(hipStreamWaitEvent(a->stream, b->event, 0)); + CUDACHECK(hipEventRecord(b->scratchEvent, b->cudaStream)); + CUDACHECK(hipStreamWaitEvent(a->cudaStream, b->scratchEvent, 0)); #endif return ncclSuccess; } @@ -210,36 +334,29 @@ ncclResult_t ncclStrongStreamWaitStream( ) { #if CUDART_VERSION >= 11030 if (graph.graph == nullptr) { - CUDACHECK(cudaEventRecord(a->event, b)); - CUDACHECK(cudaStreamWaitEvent(a->stream, a->event, 0)); - // We used a->event to record b so it no longer reflects anything about a. - a->eventIsLagging = 1; + // It is ok to use a->serialEvent to record b since we'll be setting + // a->serialEventNeedsRecord so the event won't be considered accurate + // until re-recorded. + CUDACHECK(cudaEventRecord(a->serialEvent, b)); + CUDACHECK(cudaStreamWaitEvent(a->cudaStream, a->serialEvent, 0)); } else { cudaStreamCaptureStatus status; - unsigned long long gid1; - cudaGraphNode_t const* deps; - size_t depN = 0; - CUDACHECK(cudaStreamGetCaptureInfo_v2(b, &status, &gid1, nullptr, &deps, &depN)); - if (status != cudaStreamCaptureStatusActive || graph.graphId != gid1) { + unsigned long long bGraphId; + cudaGraphNode_t const* bNodes; + size_t bCount = 0; + CUDACHECK(cudaStreamGetCaptureInfo_v2(b, &status, &bGraphId, nullptr, &bNodes, &bCount)); + if (status != cudaStreamCaptureStatusActive || graph.graphId != bGraphId) { WARN("Stream is not being captured by the expected graph."); return ncclInvalidUsage; } - if (depN > 0 && (depN > 1 || deps[0] != a->node)) { - cudaGraphNode_t tie; - if (depN == 1) { - tie = deps[0]; - } else { - CUDACHECK(cudaGraphAddEmptyNode(&tie, graph.graph, deps, depN)); - } - cudaGraphNode_t pair[2] = {a->node, tie}; - CUDACHECK(cudaGraphAddEmptyNode(&a->node, graph.graph, pair, 2)); - } - // a->eventIsLagging doesn't change since we are just updating the - // dependencies of a->node. + struct ncclStrongStreamGraph* ag = a->graphHead; + NCCLCHECK(checkGraphId(ag, graph.graphId)); + mergeTips(ag, bNodes, bCount); } + a->serialEventNeedsRecord = true; #else - CUDACHECK(hipEventRecord(a->event, b)); - CUDACHECK(hipStreamWaitEvent(a->stream, a->event, 0)); + CUDACHECK(hipEventRecord(a->scratchEvent, b)); + CUDACHECK(hipStreamWaitEvent(a->cudaStream, a->scratchEvent, 0)); #endif return ncclSuccess; } @@ -249,25 +366,28 @@ ncclResult_t ncclStrongStreamWaitStream( ) { #if CUDART_VERSION >= 11030 if (graph.graph == nullptr) { - if (b->eventIsLagging) { - b->eventIsLagging = 0; - CUDACHECK(cudaEventRecord(b->event, b->stream)); + if (b->serialEventNeedsRecord) { + b->serialEventNeedsRecord = false; + CUDACHECK(cudaEventRecord(b->serialEvent, b->cudaStream)); } - CUDACHECK(cudaStreamWaitEvent(a, b->event, 0)); + CUDACHECK(cudaStreamWaitEvent(a, b->serialEvent, 0)); } else { - CUDACHECK(cudaStreamUpdateCaptureDependencies(a, &b->node, 1, cudaStreamAddCaptureDependencies)); + struct ncclStrongStreamGraph* bg = b->graphHead; + NCCLCHECK(checkGraphId(bg, graph.graphId)); + CUDACHECK(cudaStreamUpdateCaptureDependencies(a, bg->tipNodes, bg->tipCount, cudaStreamAddCaptureDependencies)); } #else - CUDACHECK(hipEventRecord(b->event, b->stream)); - CUDACHECK(hipStreamWaitEvent(a, b->event, 0)); + CUDACHECK(hipEventRecord(b->scratchEvent, b->cudaStream)); + CUDACHECK(hipStreamWaitEvent(a, b->scratchEvent, 0)); #endif return ncclSuccess; } ncclResult_t ncclStrongStreamSynchronize(struct ncclStrongStream* ss) { #if CUDART_VERSION >= 11030 - CUDACHECK(cudaStreamWaitEvent(ss->stream, ss->event, 0)); + CUDACHECK(cudaStreamWaitEvent(ss->cudaStream, ss->serialEvent, 0)); + ss->serialEventNeedsRecord = false; #endif - CUDACHECK(hipStreamSynchronize(ss->stream)); + CUDACHECK(hipStreamSynchronize(ss->cudaStream)); return ncclSuccess; } diff --git a/projects/rccl/src/proxy.cc b/projects/rccl/src/proxy.cc index 6638bb8607..26ca0453e7 100644 --- a/projects/rccl/src/proxy.cc +++ b/projects/rccl/src/proxy.cc @@ -1056,8 +1056,8 @@ void* ncclProxyService(void* _args) { int asyncOpCount = 0; while ((stop == 0 || (stop == 1 && npeers > 0)) && *comm->abortFlag == 0) { /* never let proxy service thread blocks in poll, or it cannot receive abortFlag. */ - if (int error = poll(pollfds, NCCL_MAX_LOCAL_RANKS+1, asyncOpCount ? 0 : 500) < 0) { - WARN("[Proxy Service] Poll failed with error %d", error); + if (poll(pollfds, NCCL_MAX_LOCAL_RANKS+1, asyncOpCount ? 0 : 500) < 0) { + WARN("[Proxy Service] Poll failed: %s\n", strerror(errno)); return NULL; } if (pollfds[NCCL_MAX_LOCAL_RANKS].revents) { diff --git a/projects/rccl/src/transport.cc b/projects/rccl/src/transport.cc index 82c0c86a26..943c9da75f 100644 --- a/projects/rccl/src/transport.cc +++ b/projects/rccl/src/transport.cc @@ -54,7 +54,7 @@ static ncclResult_t selectTransport(struct ncclComm* comm, struct ncclTopoGraph* ncclResult_t ncclTransportP2pConnect(struct ncclComm* comm, int channelId, int nrecv, int* peerRecv, int nsend, int* peerSend, int connIndex) { TRACE(NCCL_INIT, "nsend %d nrecv %d", nsend, nrecv); struct ncclChannel* channel = &comm->channels[channelId]; - uint32_t mask = 1 << channelId; + uint64_t mask = 1UL << channel->id; for (int i=0; i= comm->nRanks || peer == comm->rank || channel->peers[peer].recv[connIndex].connected) continue; @@ -85,15 +85,15 @@ ncclResult_t ncclTransportP2pSetup(struct ncclComm* comm, struct ncclTopoGraph* int bootstrapTag = (i<<8) + (graph ? graph->id+1 : 0); int recvPeer = (comm->rank - i + comm->nRanks) % comm->nRanks; int sendPeer = (comm->rank + i) % comm->nRanks; - uint32_t recvMask = comm->connectRecv[recvPeer+comm->nRanks*(connIndex == NCCL_CONN_IDX_P2P_NET ? NCCL_CONN_IDX_P2P_NET : 0)]; - uint32_t sendMask = comm->connectSend[sendPeer+comm->nRanks*(connIndex == NCCL_CONN_IDX_P2P_NET ? NCCL_CONN_IDX_P2P_NET : 0)]; + uint64_t recvMask = comm->connectRecv[recvPeer+comm->nRanks*(connIndex == NCCL_CONN_IDX_P2P_NET ? NCCL_CONN_IDX_P2P_NET : 0)]; + uint64_t sendMask = comm->connectSend[sendPeer+comm->nRanks*(connIndex == NCCL_CONN_IDX_P2P_NET ? NCCL_CONN_IDX_P2P_NET : 0)]; struct ncclConnect* recvData = data; int sendChannels = 0, recvChannels = 0; int type; TIME_START(0); for (int c=0; c(comm, graph, recvData+recvChannels++, c, recvPeer, connIndex, &type)); if (type > highestType) highestType = type; } @@ -102,7 +102,7 @@ ncclResult_t ncclTransportP2pSetup(struct ncclComm* comm, struct ncclTopoGraph* TIME_START(1); struct ncclConnect* sendData = recvData+recvChannels; for (int c=0; c(comm, graph, sendData+sendChannels++, c, sendPeer, connIndex, &type)); if (type > highestType) highestType = type; } @@ -127,7 +127,7 @@ ncclResult_t ncclTransportP2pSetup(struct ncclComm* comm, struct ncclTopoGraph* TIME_START(3); for (int c=0; cchannels[c].peers[sendPeer].send + connIndex; NCCLCHECK(conn->transportComm->connect(comm, sendData++, 1, comm->rank, conn)); conn->connected = 1; @@ -139,7 +139,7 @@ ncclResult_t ncclTransportP2pSetup(struct ncclComm* comm, struct ncclTopoGraph* TIME_STOP(3); TIME_START(4); for (int c=0; cchannels[c].peers[recvPeer].recv + connIndex; NCCLCHECK(conn->transportComm->connect(comm, recvData++, 1, comm->rank, conn)); conn->connected = 1; @@ -148,7 +148,7 @@ ncclResult_t ncclTransportP2pSetup(struct ncclComm* comm, struct ncclTopoGraph* } } TIME_STOP(4); - comm->connectRecv[recvPeer+comm->nRanks*(connIndex == NCCL_CONN_IDX_P2P_NET ? NCCL_CONN_IDX_P2P_NET : 0)] = comm->connectSend[sendPeer+comm->nRanks*(connIndex == NCCL_CONN_IDX_P2P_NET ? NCCL_CONN_IDX_P2P_NET : 0)] = 0; + comm->connectRecv[recvPeer+comm->nRanks*(connIndex == NCCL_CONN_IDX_P2P_NET ? NCCL_CONN_IDX_P2P_NET : 0)] = comm->connectSend[sendPeer+comm->nRanks*(connIndex == NCCL_CONN_IDX_P2P_NET ? NCCL_CONN_IDX_P2P_NET : 0)] = 0UL; } CUDACHECK(hipStreamSynchronize(comm->sideStream)); if (highestTransportType != NULL) *highestTransportType = highestType; diff --git a/projects/rccl/src/transport/coll_net.cc b/projects/rccl/src/transport/coll_net.cc index da158d9d21..28aff7a53f 100644 --- a/projects/rccl/src/transport/coll_net.cc +++ b/projects/rccl/src/transport/coll_net.cc @@ -123,6 +123,7 @@ struct recvResources { int netDev; int useGdr; int useDmaBuf; + int needFlush; uint64_t* gdcSync; uint64_t* gdcFlush; void* gdrDesc; @@ -142,6 +143,7 @@ static ncclResult_t canConnect(int* ret, struct ncclTopoSystem* topo, struct ncc struct setupReq { int netDev; int useGdr; + int needFlush; }; @@ -154,6 +156,8 @@ static ncclResult_t sendSetup(struct ncclComm* comm, struct ncclTopoGraph* graph NCCLCHECK(ncclTopoGetNetDev(comm, myInfo->rank, graph, channelId, -1, &req.netDev, &proxyRank)); NCCLCHECK(ncclTopoCheckGdr(comm->topo, myInfo->busId, req.netDev, 1, &req.useGdr)); send->conn.direct |= req.useGdr ? NCCL_DIRECT_NIC : 0; + // Determine whether we need to flush the GDR buffer on recv or not + if (req.useGdr) NCCLCHECK(ncclTopoNeedFlush(comm->topo, myInfo->busId, &req.needFlush)); NCCLCHECK(ncclTopoGetLocalRank(comm->topo, myInfo->rank, &send->proxyConn.localRank)); NCCLCHECK(ncclProxyConnect(comm, TRANSPORT_COLLNET, 1, myInfo->rank, &send->proxyConn)); @@ -395,6 +399,7 @@ static ncclResult_t recvProxySetup(struct ncclProxyConnection* connection, struc resources->netDev = req->netDev; resources->useGdr = req->useGdr; + resources->needFlush = req->needFlush; ncclNetProperties_t props; NCCLCHECK(collNetGetProperties(comm, req->netDev, &props)); /* DMA-BUF support */ @@ -763,7 +768,7 @@ static ncclResult_t recvProxyProgress(struct ncclComm* comm, struct ncclProxyArg TRACE(NCCL_NET, "recvProxy [%lu/%d/%d] received, size %d", sub->received, group, buffSlot, totalSize); sub->received += args->sliceSteps; sub->requests[buffSlot] = NULL; - if (1 && reqFifo[group][buffSlot].size > 0 && resources->useGdr) { + if (reqFifo[group][buffSlot].size > 0 && resources->useGdr && resources->needFlush) { // GDRCOPY support if (resources->gdcFlush) { #if defined (__x86_64__) diff --git a/projects/rccl/src/transport/net.cc b/projects/rccl/src/transport/net.cc index 8253449541..b8f11b3edc 100644 --- a/projects/rccl/src/transport/net.cc +++ b/projects/rccl/src/transport/net.cc @@ -125,6 +125,7 @@ struct recvResources { int netDev; int useGdr; int useDmaBuf; + int needFlush; int maxRecvs; uint64_t* gdcSync; uint64_t* gdcFlush; @@ -163,6 +164,7 @@ struct setupReq { int shared; int netDev; int useGdr; + int needFlush; int channelId; int connIndex; uint32_t* curr_hdp_reg; @@ -226,6 +228,9 @@ static ncclResult_t recvSetup(struct ncclComm* comm, struct ncclTopoGraph* graph if (req.netDev < 0) NCCLCHECK(ncclTopoGetNetDev(comm, myInfo->rank, graph, channelId, myInfo->rank, &req.netDev, &proxyRank)); NCCLCHECK(ncclTopoCheckGdr(comm->topo, myInfo->busId, req.netDev, 0, &req.useGdr)); + // Determine whether we need to flush the GDR buffer on recv or not + if (req.useGdr) NCCLCHECK(ncclTopoNeedFlush(comm->topo, myInfo->busId, &req.needFlush)); + // We don't support PXN on receive yet NCCLCHECK(ncclProxyConnect(comm, TRANSPORT_NET, 0, myInfo->rank, &recv->proxyConn)); @@ -492,6 +497,7 @@ static ncclResult_t recvProxySetup(struct ncclProxyConnection* connection, struc resources->netDev = req->netDev; resources->shared = connection->shared = req->shared; resources->useGdr = req->useGdr; + resources->needFlush = req->needFlush; resources->channelId = req->channelId; resources->connIndex = req->connIndex; ncclNetProperties_t props; @@ -1150,7 +1156,7 @@ static ncclResult_t recvProxyProgress(struct ncclComm* comm, struct ncclProxyArg for (int i=0; irequests[step%NCCL_STEPS], &done, sizes)); if (done) { - int useGdr = 0; + int needFlush = 0; int totalSize = 0; for (int i=0; igroupSize; i++) { @@ -1175,11 +1181,11 @@ static ncclResult_t recvProxyProgress(struct ncclComm* comm, struct ncclProxyArg for (uint64_t step=sub->received-args->sliceSteps; stepreceived; step++) ncclProfilingRecord(args, s+i, step, ncclProxyProfileRecvFlushWait); if (step < sub->nsteps) { struct recvResources* resources = (struct recvResources*) (sub->connection->transportResources); - if (resources->useGdr) useGdr = 1; + if (resources->useGdr) needFlush |= resources->needFlush; } } subGroup->requests[step%NCCL_STEPS] = NULL; - if (totalSize > 0 && p == NCCL_PROTO_SIMPLE && useGdr) { + if (totalSize > 0 && p == NCCL_PROTO_SIMPLE && needFlush) { // GDRCOPY support struct recvResources* resources = (struct recvResources*) (subGroup->connection->transportResources); if (resources->gdcFlush) {