6aae379278
Network user buffer support for collectives * Leverage user buffer registration to achieve zero-copy inter-node communications for Ring, NVLS and Collnet Add RAS subsystem * Create a RAS thread keeping track of all NCCL communicators. * Add a ncclras tool contacting the RAS thread and getting a report. Add fp8 support * Add support for e5m2 and e4m3 8-bit floating point operations. * Use Tree/PAT algorithms when possible for better numerical stability. Add NIC fusion * Add a NET API to ask the network plugin to fuse a set of interfaces together. * Fuse multiple NICs under the same PCI switch as a single, larger NIC. Socket connection failure retry * Retry in case of socket connection failure (unreachable host) * Avoid "Software caused connection abort" errors on retries QP connection failure retry * Retry in case of IB QP connection failure during ibv_modify_qp. NET API improvements * Allow plugins to force a flush in case data and completion ordering is not guaranteed. * Indicate when completion is not needed (e.g. for the LL128 protocol), allowing plugins to skip generating a completion. * Allow for full offload of allgather operations when using one GPU per node. NCCL_ALGO/NCCL_PROTO strict enforcement * Extend NCCL_ALGO/NCCL_PROTO syntax to be able to specify ALGO/PROTO filters for each collective operation. * Strictly enforce the ALGO/PROTO filters, no longer fall back on the ring algorithm when the filtering leaves no option and error out instead. Enable CUMEM host allocations * Use cumem functions for host memory allocation by default. Improved profiler plugin API * Avoid dependencies with NCCL includes. * Add information on whether the buffer is registered or not Adjust PAT tuning * Improve transition between PAT and ring at scale. Fix hangs when running with different CPU architectures * Detect when we use a mix of GPU architectures * Ensure Algo/Proto decisions are made based on that unified state. Fix FD leak in UDS * Fix a leak when mapping buffers intra-node with cumem IPCs. Fix crash when mixing buffer registration and graph buffer registration. * Separate local and graph registration to avoid crashes when we free buffers. Fix user buffer registration with dmabuf * Make ncclSend/ncclRecv communication with buffer registration functional on network plugins relying on dmabuf for buffer registration. Fix crash in IB code caused by uninitialized fields. Fix non-blocking ncclSend/ncclRecv * Fix case where ncclSend/ncclRecv would return ncclSuccess in non-blocking mode even though the operation was not enqueued onto the stream. * Issue #1495 Various compiler tweaks and fixes * PR #758 Fix typo in ncclTopoPrintGraph * Issue #1468
258 lines
9.0 KiB
C++
258 lines
9.0 KiB
C++
/*************************************************************************
|
|
* Copyright (c) 2016-2022, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* See LICENSE.txt for license information
|
|
************************************************************************/
|
|
|
|
#include "core.h"
|
|
#include "nccl_net.h"
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <sys/syscall.h>
|
|
#include <chrono>
|
|
#include "param.h"
|
|
|
|
int ncclDebugLevel = -1;
|
|
static int pid = -1;
|
|
static char hostname[1024];
|
|
thread_local int ncclDebugNoWarn = 0;
|
|
char ncclLastError[1024] = ""; // Global string for the last error in human readable form
|
|
static uint64_t ncclDebugMask = NCCL_INIT | NCCL_BOOTSTRAP | NCCL_ENV; // Default debug sub-system mask is INIT and ENV
|
|
FILE *ncclDebugFile = stdout;
|
|
static pthread_mutex_t ncclDebugLock = PTHREAD_MUTEX_INITIALIZER;
|
|
static std::chrono::steady_clock::time_point ncclEpoch;
|
|
static bool ncclWarnSetDebugInfo = false;
|
|
|
|
static __thread int tid = -1;
|
|
|
|
static void ncclDebugInit() {
|
|
pthread_mutex_lock(&ncclDebugLock);
|
|
if (ncclDebugLevel != -1) { pthread_mutex_unlock(&ncclDebugLock); return; }
|
|
const char* nccl_debug = ncclGetEnv("NCCL_DEBUG");
|
|
int tempNcclDebugLevel = -1;
|
|
if (nccl_debug == NULL) {
|
|
tempNcclDebugLevel = NCCL_LOG_NONE;
|
|
} else if (strcasecmp(nccl_debug, "VERSION") == 0) {
|
|
tempNcclDebugLevel = NCCL_LOG_VERSION;
|
|
} else if (strcasecmp(nccl_debug, "WARN") == 0) {
|
|
tempNcclDebugLevel = NCCL_LOG_WARN;
|
|
} else if (strcasecmp(nccl_debug, "INFO") == 0) {
|
|
tempNcclDebugLevel = NCCL_LOG_INFO;
|
|
} else if (strcasecmp(nccl_debug, "ABORT") == 0) {
|
|
tempNcclDebugLevel = NCCL_LOG_ABORT;
|
|
} else if (strcasecmp(nccl_debug, "TRACE") == 0) {
|
|
tempNcclDebugLevel = NCCL_LOG_TRACE;
|
|
}
|
|
|
|
/* Parse the NCCL_DEBUG_SUBSYS env var
|
|
* This can be a comma separated list such as INIT,COLL
|
|
* or ^INIT,COLL etc
|
|
*/
|
|
const char* ncclDebugSubsysEnv = ncclGetEnv("NCCL_DEBUG_SUBSYS");
|
|
if (ncclDebugSubsysEnv != NULL) {
|
|
int invert = 0;
|
|
if (ncclDebugSubsysEnv[0] == '^') { invert = 1; ncclDebugSubsysEnv++; }
|
|
ncclDebugMask = invert ? ~0ULL : 0ULL;
|
|
char *ncclDebugSubsys = strdup(ncclDebugSubsysEnv);
|
|
char *subsys = strtok(ncclDebugSubsys, ",");
|
|
while (subsys != NULL) {
|
|
uint64_t mask = 0;
|
|
if (strcasecmp(subsys, "INIT") == 0) {
|
|
mask = NCCL_INIT;
|
|
} else if (strcasecmp(subsys, "COLL") == 0) {
|
|
mask = NCCL_COLL;
|
|
} else if (strcasecmp(subsys, "P2P") == 0) {
|
|
mask = NCCL_P2P;
|
|
} else if (strcasecmp(subsys, "SHM") == 0) {
|
|
mask = NCCL_SHM;
|
|
} else if (strcasecmp(subsys, "NET") == 0) {
|
|
mask = NCCL_NET;
|
|
} else if (strcasecmp(subsys, "GRAPH") == 0) {
|
|
mask = NCCL_GRAPH;
|
|
} else if (strcasecmp(subsys, "TUNING") == 0) {
|
|
mask = NCCL_TUNING;
|
|
} else if (strcasecmp(subsys, "ENV") == 0) {
|
|
mask = NCCL_ENV;
|
|
} else if (strcasecmp(subsys, "ALLOC") == 0) {
|
|
mask = NCCL_ALLOC;
|
|
} else if (strcasecmp(subsys, "CALL") == 0) {
|
|
mask = NCCL_CALL;
|
|
} else if (strcasecmp(subsys, "PROXY") == 0) {
|
|
mask = NCCL_PROXY;
|
|
} else if (strcasecmp(subsys, "NVLS") == 0) {
|
|
mask = NCCL_NVLS;
|
|
} else if (strcasecmp(subsys, "BOOTSTRAP") == 0) {
|
|
mask = NCCL_BOOTSTRAP;
|
|
} else if (strcasecmp(subsys, "REG") == 0) {
|
|
mask = NCCL_REG;
|
|
} else if (strcasecmp(subsys, "PROFILE") == 0) {
|
|
mask = NCCL_PROFILE;
|
|
} else if (strcasecmp(subsys, "RAS") == 0) {
|
|
mask = NCCL_RAS;
|
|
} else if (strcasecmp(subsys, "ALL") == 0) {
|
|
mask = NCCL_ALL;
|
|
}
|
|
if (mask) {
|
|
if (invert) ncclDebugMask &= ~mask; else ncclDebugMask |= mask;
|
|
}
|
|
subsys = strtok(NULL, ",");
|
|
}
|
|
free(ncclDebugSubsys);
|
|
}
|
|
|
|
const char* ncclWarnSetDebugInfoEnv = ncclGetEnv("NCCL_WARN_ENABLE_DEBUG_INFO");
|
|
if (ncclWarnSetDebugInfoEnv != NULL && strlen(ncclWarnSetDebugInfoEnv) > 0) {
|
|
int64_t value;
|
|
errno = 0;
|
|
value = strtoll(ncclWarnSetDebugInfoEnv, NULL, 0);
|
|
if (!errno)
|
|
ncclWarnSetDebugInfo = value;
|
|
}
|
|
|
|
// Cache pid and hostname
|
|
getHostName(hostname, 1024, '.');
|
|
pid = getpid();
|
|
|
|
/* Parse and expand the NCCL_DEBUG_FILE path and
|
|
* then create the debug file. But don't bother unless the
|
|
* NCCL_DEBUG level is > VERSION
|
|
*/
|
|
const char* ncclDebugFileEnv = ncclGetEnv("NCCL_DEBUG_FILE");
|
|
if (tempNcclDebugLevel > NCCL_LOG_VERSION && ncclDebugFileEnv != NULL) {
|
|
int c = 0;
|
|
char debugFn[PATH_MAX+1] = "";
|
|
char *dfn = debugFn;
|
|
while (ncclDebugFileEnv[c] != '\0' && (dfn - debugFn) < PATH_MAX) {
|
|
if (ncclDebugFileEnv[c++] != '%') {
|
|
*dfn++ = ncclDebugFileEnv[c-1];
|
|
continue;
|
|
}
|
|
switch (ncclDebugFileEnv[c++]) {
|
|
case '%': // Double %
|
|
*dfn++ = '%';
|
|
break;
|
|
case 'h': // %h = hostname
|
|
dfn += snprintf(dfn, PATH_MAX + 1 - (dfn - debugFn), "%s", hostname);
|
|
break;
|
|
case 'p': // %p = pid
|
|
dfn += snprintf(dfn, PATH_MAX + 1 - (dfn - debugFn), "%d", pid);
|
|
break;
|
|
default: // Echo everything we don't understand
|
|
*dfn++ = '%';
|
|
if ((dfn - debugFn) < PATH_MAX) {
|
|
*dfn++ = ncclDebugFileEnv[c-1];
|
|
}
|
|
break;
|
|
}
|
|
if ((dfn - debugFn) > PATH_MAX) {
|
|
// snprintf wanted to overfill the buffer: set dfn to the end
|
|
// of the buffer (for null char) and it will naturally exit
|
|
// the loop.
|
|
dfn = debugFn + PATH_MAX;
|
|
}
|
|
}
|
|
*dfn = '\0';
|
|
if (debugFn[0] != '\0') {
|
|
FILE *file = fopen(debugFn, "w");
|
|
if (file != nullptr) {
|
|
setbuf(file, nullptr); // disable buffering
|
|
ncclDebugFile = file;
|
|
}
|
|
}
|
|
}
|
|
|
|
ncclEpoch = std::chrono::steady_clock::now();
|
|
__atomic_store_n(&ncclDebugLevel, tempNcclDebugLevel, __ATOMIC_RELEASE);
|
|
pthread_mutex_unlock(&ncclDebugLock);
|
|
}
|
|
|
|
/* Common logging function used by the INFO, WARN and TRACE macros
|
|
* Also exported to the dynamically loadable Net transport modules so
|
|
* they can share the debugging mechanisms and output files
|
|
*/
|
|
void ncclDebugLog(ncclDebugLogLevel level, unsigned long flags, const char *filefunc, int line, const char *fmt, ...) {
|
|
if (__atomic_load_n(&ncclDebugLevel, __ATOMIC_ACQUIRE) == -1) ncclDebugInit();
|
|
if (ncclDebugNoWarn != 0 && level == NCCL_LOG_WARN) { level = NCCL_LOG_INFO; flags = ncclDebugNoWarn; }
|
|
|
|
// Save the last error (WARN) as a human readable string
|
|
if (level == NCCL_LOG_WARN) {
|
|
pthread_mutex_lock(&ncclDebugLock);
|
|
va_list vargs;
|
|
va_start(vargs, fmt);
|
|
(void) vsnprintf(ncclLastError, sizeof(ncclLastError), fmt, vargs);
|
|
va_end(vargs);
|
|
pthread_mutex_unlock(&ncclDebugLock);
|
|
}
|
|
if (ncclDebugLevel < level || ((flags & ncclDebugMask) == 0)) return;
|
|
|
|
if (tid == -1) {
|
|
tid = syscall(SYS_gettid);
|
|
}
|
|
|
|
int cudaDev = 0;
|
|
if (!(level == NCCL_LOG_TRACE && flags == NCCL_CALL)) {
|
|
(void)cudaGetDevice(&cudaDev);
|
|
}
|
|
|
|
char buffer[1024];
|
|
size_t len = 0;
|
|
if (level == NCCL_LOG_WARN) {
|
|
len = snprintf(buffer, sizeof(buffer), "\n%s:%d:%d [%d] %s:%d NCCL WARN ",
|
|
hostname, pid, tid, cudaDev, filefunc, line);
|
|
if (ncclWarnSetDebugInfo) ncclDebugLevel = NCCL_LOG_INFO;
|
|
} else if (level == NCCL_LOG_INFO) {
|
|
len = snprintf(buffer, sizeof(buffer), "%s:%d:%d [%d] NCCL INFO ", hostname, pid, tid, cudaDev);
|
|
} else if (level == NCCL_LOG_TRACE && flags == NCCL_CALL) {
|
|
len = snprintf(buffer, sizeof(buffer), "%s:%d:%d NCCL CALL ", hostname, pid, tid);
|
|
} else if (level == NCCL_LOG_TRACE) {
|
|
auto delta = std::chrono::steady_clock::now() - ncclEpoch;
|
|
double timestamp = std::chrono::duration_cast<std::chrono::duration<double>>(delta).count()*1000;
|
|
len = snprintf(buffer, sizeof(buffer), "%s:%d:%d [%d] %f %s:%d NCCL TRACE ",
|
|
hostname, pid, tid, cudaDev, timestamp, filefunc, line);
|
|
}
|
|
|
|
va_list vargs;
|
|
va_start(vargs, fmt);
|
|
len += vsnprintf(buffer+len, sizeof(buffer)-len, fmt, vargs);
|
|
va_end(vargs);
|
|
// vsnprintf may return len >= sizeof(buffer) in the case of a truncated output.
|
|
// Rewind len so that we can replace the final \0 by \n
|
|
if (len >= sizeof(buffer)) len = sizeof(buffer)-1;
|
|
if (len) {
|
|
buffer[len++] = '\n';
|
|
fwrite(buffer, 1, len, ncclDebugFile);
|
|
}
|
|
}
|
|
|
|
NCCL_API(void, ncclResetDebugInit);
|
|
void ncclResetDebugInit() {
|
|
// Cleans up from a previous ncclDebugInit() and reruns.
|
|
// Use this after changing NCCL_DEBUG and related parameters in the environment.
|
|
__atomic_load_n(&ncclDebugLevel, __ATOMIC_ACQUIRE);
|
|
if (ncclDebugFile != stdout) {
|
|
fclose(ncclDebugFile);
|
|
ncclDebugFile = stdout;
|
|
}
|
|
ncclDebugLevel = -1;
|
|
ncclDebugInit();
|
|
}
|
|
|
|
NCCL_PARAM(SetThreadName, "SET_THREAD_NAME", 0);
|
|
|
|
void ncclSetThreadName(pthread_t thread, const char *fmt, ...) {
|
|
// pthread_setname_np is nonstandard GNU extension
|
|
// needs the following feature test macro
|
|
#ifdef _GNU_SOURCE
|
|
if (ncclParamSetThreadName() != 1) return;
|
|
char threadName[NCCL_THREAD_NAMELEN];
|
|
va_list vargs;
|
|
va_start(vargs, fmt);
|
|
vsnprintf(threadName, NCCL_THREAD_NAMELEN, fmt, vargs);
|
|
va_end(vargs);
|
|
pthread_setname_np(thread, threadName);
|
|
#endif
|
|
}
|