Files

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

422 righe
16 KiB
C++

2019-11-19 14:57:39 -08:00
/*************************************************************************
2022-01-07 06:39:55 -08:00
* Copyright (c) 2016-2022, NVIDIA CORPORATION. All rights reserved.
2019-11-19 14:57:39 -08:00
*
* See LICENSE.txt for license information
************************************************************************/
#include "core.h"
#include "nccl_net.h"
2025-03-12 13:46:21 -07:00
#include <ctime>
2019-11-19 14:57:39 -08:00
#include <stdlib.h>
#include <stdarg.h>
2024-12-18 08:26:06 -08:00
#include <stdio.h>
2024-06-11 01:28:01 -07:00
#include <string.h>
#include <strings.h>
2022-05-24 02:02:31 -07:00
#include <sys/syscall.h>
2024-06-11 01:28:01 -07:00
#include <chrono>
2023-09-26 05:47:28 -07:00
#include "param.h"
2019-11-19 14:57:39 -08:00
2025-05-29 20:56:40 -07:00
#define NCCL_DEBUG_RESET_TRIGGERED (-2)
2019-11-19 14:57:39 -08:00
int ncclDebugLevel = -1;
2025-03-12 13:46:21 -07:00
static uint32_t ncclDebugTimestampLevels = 0; // bitmaps of levels that have timestamps turned on
static char ncclDebugTimestampFormat[256]; // with space for subseconds
static int ncclDebugTimestampSubsecondsStart; // index where the subseconds starts
static uint64_t ncclDebugTimestampMaxSubseconds; // Max number of subseconds plus 1, used in duration ratio
static int ncclDebugTimestampSubsecondDigits; // Number of digits to display
2022-05-24 02:02:31 -07:00
static int pid = -1;
static char hostname[1024];
2019-11-19 14:57:39 -08:00
thread_local int ncclDebugNoWarn = 0;
2022-05-24 02:02:31 -07:00
char ncclLastError[1024] = ""; // Global string for the last error in human readable form
2025-09-02 13:21:14 -07:00
uint64_t ncclDebugMask = 0;
2019-11-19 14:57:39 -08:00
FILE *ncclDebugFile = stdout;
2024-06-11 01:28:01 -07:00
static pthread_mutex_t ncclDebugLock = PTHREAD_MUTEX_INITIALIZER;
static std::chrono::steady_clock::time_point ncclEpoch;
static bool ncclWarnSetDebugInfo = false;
2022-05-24 02:02:31 -07:00
static __thread int tid = -1;
2019-11-19 14:57:39 -08:00
2025-05-29 20:56:40 -07:00
// This function must be called with ncclDebugLock locked!
2024-06-11 01:28:01 -07:00
static void ncclDebugInit() {
2023-09-26 05:47:28 -07:00
const char* nccl_debug = ncclGetEnv("NCCL_DEBUG");
2022-08-03 20:47:40 -07:00
int tempNcclDebugLevel = -1;
2025-05-29 20:56:40 -07:00
uint64_t tempNcclDebugMask = NCCL_INIT | NCCL_BOOTSTRAP | NCCL_ENV; // Default debug sub-system mask
if (ncclDebugLevel == NCCL_DEBUG_RESET_TRIGGERED && ncclDebugFile != stdout) {
// Finish the reset initiated via ncclResetDebugInit().
fclose(ncclDebugFile);
ncclDebugFile = stdout;
}
2022-08-03 20:47:40 -07:00
if (nccl_debug == NULL) {
tempNcclDebugLevel = NCCL_LOG_ERROR;
} else if (strcasecmp(nccl_debug, "NONE") == 0) {
2022-08-03 20:47:40 -07:00
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;
}
2019-11-19 14:57:39 -08:00
/* Parse the NCCL_DEBUG_SUBSYS env var
* This can be a comma separated list such as INIT,COLL
* or ^INIT,COLL etc
*/
2023-09-26 05:47:28 -07:00
const char* ncclDebugSubsysEnv = ncclGetEnv("NCCL_DEBUG_SUBSYS");
2019-11-19 14:57:39 -08:00
if (ncclDebugSubsysEnv != NULL) {
int invert = 0;
if (ncclDebugSubsysEnv[0] == '^') { invert = 1; ncclDebugSubsysEnv++; }
2025-05-29 20:56:40 -07:00
tempNcclDebugMask = invert ? ~0ULL : 0ULL;
2019-11-19 14:57:39 -08:00
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;
2020-05-12 14:40:18 -07:00
} else if (strcasecmp(subsys, "ENV") == 0) {
mask = NCCL_ENV;
2021-05-11 18:16:30 -07:00
} else if (strcasecmp(subsys, "ALLOC") == 0) {
mask = NCCL_ALLOC;
2022-05-24 02:02:31 -07:00
} else if (strcasecmp(subsys, "CALL") == 0) {
mask = NCCL_CALL;
2023-04-03 05:32:07 -07:00
} else if (strcasecmp(subsys, "PROXY") == 0) {
mask = NCCL_PROXY;
2023-02-27 02:48:21 -08:00
} else if (strcasecmp(subsys, "NVLS") == 0) {
mask = NCCL_NVLS;
2024-03-26 06:08:55 -07:00
} else if (strcasecmp(subsys, "BOOTSTRAP") == 0) {
mask = NCCL_BOOTSTRAP;
} else if (strcasecmp(subsys, "REG") == 0) {
mask = NCCL_REG;
2024-06-11 01:28:01 -07:00
} else if (strcasecmp(subsys, "PROFILE") == 0) {
mask = NCCL_PROFILE;
2024-12-18 08:26:06 -08:00
} else if (strcasecmp(subsys, "RAS") == 0) {
mask = NCCL_RAS;
} else if (strcasecmp(subsys, "VERBS") == 0) {
mask = NCCL_VERBS;
2019-11-19 14:57:39 -08:00
} else if (strcasecmp(subsys, "ALL") == 0) {
mask = NCCL_ALL;
}
if (mask) {
2025-05-29 20:56:40 -07:00
if (invert) tempNcclDebugMask &= ~mask; else tempNcclDebugMask |= mask;
2019-11-19 14:57:39 -08:00
}
subsys = strtok(NULL, ",");
}
free(ncclDebugSubsys);
}
2024-06-11 01:28:01 -07:00
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;
}
2025-03-12 13:46:21 -07:00
// Determine which debug levels will have timestamps.
const char* timestamps = ncclGetEnv("NCCL_DEBUG_TIMESTAMP_LEVELS");
if (timestamps == nullptr) {
ncclDebugTimestampLevels = (1<<NCCL_LOG_WARN);
} else {
int invert = 0;
if (timestamps[0] == '^') { invert = 1; ++timestamps; }
ncclDebugTimestampLevels = invert ? ~0U : 0U;
char *timestampsDup = strdup(timestamps);
char *level = strtok(timestampsDup, ",");
while (level != NULL) {
uint32_t mask = 0;
if (strcasecmp(level, "ALL") == 0) {
mask = ~0U;
} else if (strcasecmp(level, "VERSION") == 0) {
mask = (1<<NCCL_LOG_VERSION);
} else if (strcasecmp(level, "WARN") == 0) {
mask = (1<<NCCL_LOG_WARN);
} else if (strcasecmp(level, "INFO") == 0) {
mask = (1<<NCCL_LOG_INFO);
} else if (strcasecmp(level, "ABORT") == 0) {
mask = (1<<NCCL_LOG_ABORT);
} else if (strcasecmp(level, "TRACE") == 0) {
mask = (1<<NCCL_LOG_TRACE);
} else {
// Silently fail.
}
if (mask) {
if (invert) ncclDebugTimestampLevels &= ~mask;
else ncclDebugTimestampLevels |= mask;
}
level = strtok(NULL, ",");
}
free(timestampsDup);
}
// Store a copy of the timestamp format with space for the subseconds, if used.
const char* tsFormat = ncclGetEnv("NCCL_DEBUG_TIMESTAMP_FORMAT");
if (tsFormat == nullptr) tsFormat = "[%F %T] ";
ncclDebugTimestampSubsecondsStart = -1;
// Find where the subseconds are in the format.
for (int i=0; tsFormat[i] != '\0'; ++i) {
if (tsFormat[i]=='%' && tsFormat[i+1]=='%') { // Next two chars are "%"
// Skip the next character, too, and restart checking after that.
++i;
continue;
}
if (tsFormat[i]=='%' && // Found a percentage
('1' <= tsFormat[i+1] && tsFormat[i+1] <= '9') && // Next char is a digit between 1 and 9 inclusive
tsFormat[i+2]=='f' // Two characters later is an "f"
) {
constexpr int replaceLen = sizeof("%Xf") - 1;
ncclDebugTimestampSubsecondDigits = tsFormat[i+1] - '0';
if (ncclDebugTimestampSubsecondDigits + strlen(tsFormat) - replaceLen > sizeof(ncclDebugTimestampFormat) - 1) {
// Won't fit; fall back on the default.
break;
}
ncclDebugTimestampSubsecondsStart = i;
ncclDebugTimestampMaxSubseconds = 1;
memcpy(ncclDebugTimestampFormat, tsFormat, i);
for (int j=0; j<ncclDebugTimestampSubsecondDigits; ++j) {
ncclDebugTimestampFormat[i+j] = ' ';
ncclDebugTimestampMaxSubseconds *= 10;
}
strcpy(ncclDebugTimestampFormat+i+ncclDebugTimestampSubsecondDigits, tsFormat+i+replaceLen);
break;
}
}
if (ncclDebugTimestampSubsecondsStart == -1) {
if (strlen(tsFormat) < sizeof(ncclDebugTimestampFormat)) {
strcpy(ncclDebugTimestampFormat, tsFormat);
} else {
strcpy(ncclDebugTimestampFormat, "[%F %T] ");
}
}
2025-04-22 13:50:40 -07:00
// Replace underscore with spaces... it is hard to put spaces in command line parameters.
for (int i=0; ncclDebugTimestampFormat[i] != '\0'; ++i) {
if (ncclDebugTimestampFormat[i]=='_') ncclDebugTimestampFormat[i] = ' ';
}
2025-03-12 13:46:21 -07:00
2022-05-24 02:02:31 -07:00
// Cache pid and hostname
getHostName(hostname, 1024, '.');
pid = getpid();
2019-11-19 14:57:39 -08:00
/* 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
*/
2023-09-26 05:47:28 -07:00
const char* ncclDebugFileEnv = ncclGetEnv("NCCL_DEBUG_FILE");
2022-08-03 20:47:40 -07:00
if (tempNcclDebugLevel > NCCL_LOG_VERSION && ncclDebugFileEnv != NULL) {
2019-11-19 14:57:39 -08:00
int c = 0;
char debugFn[PATH_MAX+1] = "";
char *dfn = debugFn;
2024-09-10 05:57:10 -07:00
while (ncclDebugFileEnv[c] != '\0' && (dfn - debugFn) < PATH_MAX) {
2019-11-19 14:57:39 -08:00
if (ncclDebugFileEnv[c++] != '%') {
*dfn++ = ncclDebugFileEnv[c-1];
continue;
}
switch (ncclDebugFileEnv[c++]) {
case '%': // Double %
*dfn++ = '%';
break;
case 'h': // %h = hostname
2024-09-10 05:57:10 -07:00
dfn += snprintf(dfn, PATH_MAX + 1 - (dfn - debugFn), "%s", hostname);
2019-11-19 14:57:39 -08:00
break;
case 'p': // %p = pid
2024-09-10 05:57:10 -07:00
dfn += snprintf(dfn, PATH_MAX + 1 - (dfn - debugFn), "%d", pid);
2019-11-19 14:57:39 -08:00
break;
default: // Echo everything we don't understand
*dfn++ = '%';
2024-09-10 05:57:10 -07:00
if ((dfn - debugFn) < PATH_MAX) {
*dfn++ = ncclDebugFileEnv[c-1];
}
2019-11-19 14:57:39 -08:00
break;
}
2024-09-10 05:57:10 -07:00
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;
}
2019-11-19 14:57:39 -08:00
}
*dfn = '\0';
if (debugFn[0] != '\0') {
FILE *file = fopen(debugFn, "w");
2022-05-24 02:02:31 -07:00
if (file != nullptr) {
2025-05-29 20:56:40 -07:00
setlinebuf(file); // disable block buffering
2019-11-19 14:57:39 -08:00
ncclDebugFile = file;
}
}
}
2022-05-24 02:02:31 -07:00
ncclEpoch = std::chrono::steady_clock::now();
2025-05-29 20:56:40 -07:00
ncclDebugMask = tempNcclDebugMask;
2022-05-24 02:02:31 -07:00
__atomic_store_n(&ncclDebugLevel, tempNcclDebugLevel, __ATOMIC_RELEASE);
2019-11-19 14:57:39 -08:00
}
/* 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, ...) {
2025-05-29 20:56:40 -07:00
bool locked = false; // Keeps track of the ncclDebugLock state.
int gotLevel = __atomic_load_n(&ncclDebugLevel, __ATOMIC_ACQUIRE);
2020-01-16 16:02:42 -08:00
if (ncclDebugNoWarn != 0 && level == NCCL_LOG_WARN) { level = NCCL_LOG_INFO; flags = ncclDebugNoWarn; }
2022-05-24 02:02:31 -07:00
// Save the last error (WARN) as a human readable string
if (level == NCCL_LOG_WARN) {
pthread_mutex_lock(&ncclDebugLock);
2025-05-29 20:56:40 -07:00
locked = true;
2022-05-24 02:02:31 -07:00
va_list vargs;
va_start(vargs, fmt);
(void) vsnprintf(ncclLastError, sizeof(ncclLastError), fmt, vargs);
va_end(vargs);
2025-05-29 20:56:40 -07:00
}
if (gotLevel >= 0 && (gotLevel < level || (flags & ncclDebugMask) == 0)) {
if (locked)
pthread_mutex_unlock(&ncclDebugLock);
return;
}
if (!locked) {
pthread_mutex_lock(&ncclDebugLock);
locked = true;
}
// From this point on ncclDebugLock is always locked so we don't need to check "locked" anymore.
if (ncclDebugLevel < 0)
ncclDebugInit();
if (ncclDebugLevel < level || ((flags & ncclDebugMask) == 0)) {
2022-05-24 02:02:31 -07:00
pthread_mutex_unlock(&ncclDebugLock);
2025-05-29 20:56:40 -07:00
return;
2022-05-24 02:02:31 -07:00
}
2019-11-19 14:57:39 -08:00
2022-05-24 02:02:31 -07:00
if (tid == -1) {
tid = syscall(SYS_gettid);
}
2025-03-12 13:46:21 -07:00
char buffer[1024];
size_t len = 0;
// WARNs come with an extra newline at the beginning.
if (level == NCCL_LOG_WARN) {
buffer[len++] = '\n';
};
// Add the timestamp to the buffer if they are turned on for this level.
if (ncclDebugTimestampLevels & (1<<level)) {
if (ncclDebugTimestampFormat[0] != '\0') {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts); // clock_gettime failure should never happen
std::tm nowTm;
localtime_r(&ts.tv_sec, &nowTm);
// Add the subseconds portion if it is part of the format.
char localTimestampFormat[sizeof(ncclDebugTimestampFormat)];
const char* pformat = ncclDebugTimestampFormat;
if (ncclDebugTimestampSubsecondsStart != -1) {
pformat = localTimestampFormat; // Need to use the local version which has subseconds
memcpy(localTimestampFormat, ncclDebugTimestampFormat, ncclDebugTimestampSubsecondsStart);
snprintf(localTimestampFormat + ncclDebugTimestampSubsecondsStart,
ncclDebugTimestampSubsecondDigits+1,
"%0*ld", ncclDebugTimestampSubsecondDigits,
2025-04-22 13:50:40 -07:00
ts.tv_nsec / (1000000000UL/ncclDebugTimestampMaxSubseconds));
2025-03-12 13:46:21 -07:00
strcpy( localTimestampFormat+ncclDebugTimestampSubsecondsStart+ncclDebugTimestampSubsecondDigits,
ncclDebugTimestampFormat+ncclDebugTimestampSubsecondsStart+ncclDebugTimestampSubsecondDigits);
}
// Format the time. If it runs out of space, fall back on a simpler format.
int adv = std::strftime(buffer+len, sizeof(buffer)-len, pformat, &nowTm);
if (adv==0 && ncclDebugTimestampFormat[0] != '\0') {
// Ran out of space. Fall back on the default. This should never fail.
adv = std::strftime(buffer+len, sizeof(buffer)-len, "[%F %T] ", &nowTm);
}
len += adv;
}
}
len = std::min(len, sizeof(buffer)-1); // prevent overflows
// Add hostname, pid and tid portion of the log line.
if (level != NCCL_LOG_VERSION) {
len += snprintf(buffer+len, sizeof(buffer)-len, "%s:%d:%d ", hostname, pid, tid);
len = std::min(len, sizeof(buffer)-1); // prevent overflows
}
2024-09-10 05:57:10 -07:00
int cudaDev = 0;
2022-05-24 02:02:31 -07:00
if (!(level == NCCL_LOG_TRACE && flags == NCCL_CALL)) {
2024-09-10 05:57:10 -07:00
(void)cudaGetDevice(&cudaDev);
2022-05-24 02:02:31 -07:00
}
2019-11-19 14:57:39 -08:00
2025-03-12 13:46:21 -07:00
// Add level specific formatting.
2022-05-24 02:02:31 -07:00
if (level == NCCL_LOG_WARN) {
2025-03-12 13:46:21 -07:00
len += snprintf(buffer+len, sizeof(buffer)-len, "[%d] %s:%d NCCL WARN ", cudaDev, filefunc, line);
2025-05-29 20:56:40 -07:00
if (ncclWarnSetDebugInfo) __atomic_store_n(&ncclDebugLevel, NCCL_LOG_INFO, __ATOMIC_RELEASE);
2022-05-24 02:02:31 -07:00
} else if (level == NCCL_LOG_INFO) {
2025-03-12 13:46:21 -07:00
len += snprintf(buffer+len, sizeof(buffer)-len, "[%d] NCCL INFO ", cudaDev);
2022-05-24 02:02:31 -07:00
} else if (level == NCCL_LOG_TRACE && flags == NCCL_CALL) {
2025-03-12 13:46:21 -07:00
len += snprintf(buffer+len, sizeof(buffer)-len, "NCCL CALL ");
2022-05-24 02:02:31 -07:00
} else if (level == NCCL_LOG_TRACE) {
auto delta = std::chrono::steady_clock::now() - ncclEpoch;
2019-11-19 14:57:39 -08:00
double timestamp = std::chrono::duration_cast<std::chrono::duration<double>>(delta).count()*1000;
2025-03-12 13:46:21 -07:00
len += snprintf(buffer+len, sizeof(buffer)-len, "[%d] %f %s:%d NCCL TRACE ", cudaDev, timestamp, filefunc, line);
} else if (level == NCCL_LOG_ERROR) {
len += snprintf(buffer+len, sizeof(buffer)-len, "[%d] [FATAL ERROR]: ", cudaDev);
2019-11-19 14:57:39 -08:00
}
2025-03-12 13:46:21 -07:00
len = std::min(len, sizeof(buffer)-1); // prevent overflows
2022-05-24 02:02:31 -07:00
2025-03-12 13:46:21 -07:00
// Add the message as given by the call site.
2024-06-11 01:28:01 -07:00
va_list vargs;
va_start(vargs, fmt);
len += vsnprintf(buffer+len, sizeof(buffer)-len, fmt, vargs);
va_end(vargs);
2024-09-10 05:57:10 -07:00
// vsnprintf may return len >= sizeof(buffer) in the case of a truncated output.
2025-03-12 13:46:21 -07:00
// Rewind len so that we can replace the final \0 by "\n"
len = std::min(len, sizeof(buffer)-1); // prevent overflows
// Add a newline and write it to the debug file. No terminating null is
// necessary since we write bytes instead of the string.
buffer[len++] = '\n';
fwrite(buffer, 1, len, ncclDebugFile);
2025-05-29 20:56:40 -07:00
pthread_mutex_unlock(&ncclDebugLock);
2019-11-19 14:57:39 -08:00
}
2022-01-07 06:39:55 -08:00
2024-12-18 08:26:06 -08:00
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.
2025-05-29 20:56:40 -07:00
pthread_mutex_lock(&ncclDebugLock);
// Let ncclDebugInit() know to complete the reset.
__atomic_store_n(&ncclDebugLevel, NCCL_DEBUG_RESET_TRIGGERED, __ATOMIC_RELEASE);
pthread_mutex_unlock(&ncclDebugLock);
2024-12-18 08:26:06 -08:00
}
2022-01-07 06:39:55 -08:00
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
}