0497b7934f
- Create a local copy for ROCm/rccl-tests for our examples.
- Update argument parsing to no longer use getopt_long.
- Workaround for Dyninst instrumentation.
---------
Co-authored-by: David Galiffi <David.Galiffi@amd.com>
[ROCm/rocprofiler-systems commit: 4e5029221b]
1988 lines
68 KiB
C++
1988 lines
68 KiB
C++
|
|
/*************************************************************************
|
|
* Copyright (c) 2016-2022, NVIDIA CORPORATION. All rights reserved.
|
|
* Modifications Copyright (c) 2019-2022 Advanced Micro Devices, Inc. All rights reserved.
|
|
* Modifications Copyright (c) Microsoft Corporation. Licensed under the MIT License.
|
|
*
|
|
* See LICENSE.txt for license information
|
|
************************************************************************/
|
|
|
|
#include "common.h"
|
|
#include "cuda.h"
|
|
#include "cuda_runtime.h"
|
|
#include "rccl_float8.h"
|
|
#include <cstdio>
|
|
#include <ctype.h>
|
|
#include <getopt.h>
|
|
#include <hip/hip_bfloat16.h>
|
|
#include <libgen.h>
|
|
#include <pthread.h>
|
|
#include <string.h>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
// #define DEBUG_PRINT
|
|
|
|
#include "git_version.h"
|
|
#include "verifiable.h"
|
|
|
|
int test_ncclVersion = 0; // init'd with ncclGetVersion()
|
|
int32_t gpu_block3;
|
|
size_t cache_bytes = 192 * 1024 * 1024; // Use 192MB
|
|
|
|
#if NCCL_MAJOR >= 2
|
|
ncclDataType_t test_types[ncclNumTypes] = { ncclInt8,
|
|
ncclUint8,
|
|
ncclInt32,
|
|
ncclUint32,
|
|
ncclInt64,
|
|
ncclUint64,
|
|
ncclHalf,
|
|
ncclFloat,
|
|
ncclDouble
|
|
# if RCCL_BFLOAT16 == 1
|
|
,
|
|
ncclBfloat16
|
|
# endif
|
|
# if RCCL_FLOAT8 == 1
|
|
,
|
|
ncclFp8E4M3,
|
|
ncclFp8E5M2
|
|
# endif
|
|
};
|
|
const char* test_typenames[ncclNumTypes] = { "int8",
|
|
"uint8",
|
|
"int32",
|
|
"uint32",
|
|
"int64",
|
|
"uint64",
|
|
"half",
|
|
"float",
|
|
"double"
|
|
# if RCCL_BFLOAT16 == 1
|
|
,
|
|
"bfloat16"
|
|
# endif
|
|
# if RCCL_FLOAT8 == 1
|
|
,
|
|
"fp8_e4m3",
|
|
"fp8_e5m2"
|
|
# endif
|
|
};
|
|
int test_typenum = -1;
|
|
|
|
const char* test_opnames[] = { "sum", "prod", "max", "min", "avg", "mulsum" };
|
|
ncclRedOp_t test_ops[] = {
|
|
ncclSum,
|
|
ncclProd,
|
|
ncclMax,
|
|
ncclMin
|
|
# if NCCL_VERSION_CODE >= NCCL_VERSION(2, 10, 0)
|
|
,
|
|
ncclAvg
|
|
# endif
|
|
# if NCCL_VERSION_CODE >= NCCL_VERSION(2, 11, 0)
|
|
,
|
|
ncclNumOps // stand in for ncclRedOpCreatePreMulSum() created on-demand
|
|
# endif
|
|
};
|
|
int test_opnum = -1;
|
|
#else
|
|
ncclDataType_t test_types[ncclNumTypes] = { ncclChar, ncclInt, ncclHalf, ncclFloat,
|
|
ncclDouble, ncclInt64, ncclUint64 };
|
|
const char* test_typenames[ncclNumTypes] = { "char", "int", "half", "float",
|
|
"double", "int64", "uint64" };
|
|
int test_typenum = 7;
|
|
const char* test_opnames[] = { "sum", "prod", "max", "min" };
|
|
ncclRedOp_t test_ops[] = { ncclSum, ncclProd, ncclMax, ncclMin };
|
|
int test_opnum = 4;
|
|
#endif
|
|
|
|
const char* test_memorytypes[nccl_NUM_MTYPES] = { "coarse", "fine", "host", "managed" };
|
|
|
|
// For libnccl's < 2.13
|
|
extern "C" __attribute__((weak)) char const*
|
|
ncclGetLastError(ncclComm_t comm)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
int is_main_proc = 0;
|
|
thread_local int is_main_thread = 0;
|
|
|
|
// Command line parameter defaults
|
|
static int nThreads = 1;
|
|
static int nGpus = 1;
|
|
static size_t minBytes = 32 * 1024 * 1024;
|
|
static size_t maxBytes = 32 * 1024 * 1024;
|
|
static size_t stepBytes = 1 * 1024 * 1024;
|
|
static size_t stepFactor = 1;
|
|
static int datacheck = 1;
|
|
static int warmup_iters = 5;
|
|
static int iters = 20;
|
|
static int agg_iters = 1;
|
|
static int run_cycles = 1;
|
|
static int ncclop = ncclSum;
|
|
static int nccltype = ncclFloat;
|
|
static int ncclroot = 0;
|
|
static int parallel_init = 0;
|
|
static int blocking_coll = 0;
|
|
static int memorytype = 0;
|
|
static uint32_t cumask[4];
|
|
static int streamnull = 0;
|
|
static int timeout = 0;
|
|
static int cudaGraphLaunches = 0;
|
|
std::string output_file;
|
|
std::string output_format;
|
|
static int report_cputime = 0;
|
|
// Report average iteration time: (0=RANK0,1=AVG,2=MIN,3=MAX)
|
|
static int average = 1;
|
|
static int numDevices = 1;
|
|
static int delay_inout_place = 0;
|
|
static int enable_out_of_place = 1;
|
|
static int enable_in_place = 1;
|
|
static int enable_cache_flush = 0;
|
|
static int enable_rotating_tensor = 0;
|
|
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 19, 0)
|
|
static int local_register = 0;
|
|
#endif
|
|
|
|
Reporter::Reporter(std::string fileName, std::string outputFormat)
|
|
: _outputFormat(outputFormat)
|
|
{
|
|
if(!fileName.empty())
|
|
{
|
|
if(isMainThread())
|
|
{
|
|
_out = std::ofstream(fileName, std::ios_base::out);
|
|
_outputValid = true;
|
|
if(_outputFormat == "csv")
|
|
{
|
|
_out << "numCycle, ";
|
|
_out << "collective, ";
|
|
#ifdef MPI_SUPPORT
|
|
_out << "ranks, rankspernode, gpusperrank, ";
|
|
#else
|
|
_out << "gpus, ";
|
|
#endif
|
|
_out << "size, type, redop, inplace, time, algbw, busbw, #wrong\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Reporter::setParameters(const size_t numCycle, const char* name, const char* typeName,
|
|
const char* opName)
|
|
{
|
|
if(!isMainThread() || !_outputValid) return;
|
|
|
|
_numCycle = numCycle;
|
|
_collectiveName = name;
|
|
_typeName = typeName;
|
|
_opName = opName;
|
|
}
|
|
|
|
void
|
|
Reporter::addResult(int gpusPerRank, int ranksPerNode, int totalRanks, size_t numBytes,
|
|
int inPlace, double timeUsec, double algBw, double busBw,
|
|
int64_t wrongElts)
|
|
{
|
|
if(!isMainThread() || !_outputValid) return;
|
|
|
|
std::vector<std::pair<std::string, std::string>> outputValuesKeys;
|
|
std::string wrongEltsStr = (wrongElts == -1) ? "N/A" : std::to_string(wrongElts);
|
|
int nodes = totalRanks / ranksPerNode;
|
|
|
|
outputValuesKeys.push_back(makeValueKeyPair(_numCycle, "numCycle"));
|
|
outputValuesKeys.push_back(makeValueKeyPair(_collectiveName, "name"));
|
|
#ifdef MPI_SUPPORT
|
|
outputValuesKeys.push_back(makeValueKeyPair(nodes, "nodes"));
|
|
outputValuesKeys.push_back(makeValueKeyPair(totalRanks, "ranks"));
|
|
outputValuesKeys.push_back(makeValueKeyPair(ranksPerNode, "ranksPerNode"));
|
|
outputValuesKeys.push_back(makeValueKeyPair(gpusPerRank, "gpusPerRank"));
|
|
#else
|
|
outputValuesKeys.push_back(makeValueKeyPair(gpusPerRank, "gpus"));
|
|
#endif
|
|
outputValuesKeys.push_back(makeValueKeyPair(numBytes, "size"));
|
|
outputValuesKeys.push_back(makeValueKeyPair(_typeName, "type"));
|
|
outputValuesKeys.push_back(makeValueKeyPair(_opName, "redop"));
|
|
outputValuesKeys.push_back(makeValueKeyPair(inPlace, "inPlace"));
|
|
outputValuesKeys.push_back(makeValueKeyPair(timeUsec, "time"));
|
|
outputValuesKeys.push_back(makeValueKeyPair(algBw, "algBw"));
|
|
outputValuesKeys.push_back(makeValueKeyPair(busBw, "busBw"));
|
|
outputValuesKeys.push_back(makeValueKeyPair(wrongEltsStr, "wrong"));
|
|
|
|
for(auto iter = outputValuesKeys.begin(); iter != outputValuesKeys.end(); ++iter)
|
|
{
|
|
if(_outputFormat == "csv")
|
|
{
|
|
_out << iter->first;
|
|
if(std::next(iter) != outputValuesKeys.end())
|
|
{
|
|
_out << ", ";
|
|
}
|
|
}
|
|
else
|
|
{ // json
|
|
if(iter == outputValuesKeys.begin())
|
|
{
|
|
_out << "{";
|
|
}
|
|
_out << "\"" << iter->second << "\":" << iter->first;
|
|
if(std::next(iter) != outputValuesKeys.end())
|
|
{
|
|
_out << ", ";
|
|
}
|
|
else
|
|
{
|
|
_out << "}";
|
|
}
|
|
}
|
|
}
|
|
_out << std::endl;
|
|
}
|
|
|
|
bool
|
|
Reporter::isMainThread()
|
|
{
|
|
return is_main_thread == 1;
|
|
}
|
|
|
|
#define NUM_BLOCKS 32
|
|
|
|
#ifndef CHECK_HIP_ERROR
|
|
# define CHECK_HIP_ERROR(error) \
|
|
if(error != hipSuccess) \
|
|
{ \
|
|
fprintf(stderr, "Hip error: '%s'(%d) at %s:%d\n", hipGetErrorString(error), \
|
|
error, __FILE__, __LINE__); \
|
|
exit(EXIT_FAILURE); \
|
|
}
|
|
#endif
|
|
|
|
extern "C" __global__ void
|
|
flush_icache()
|
|
{
|
|
asm __volatile__("s_icache_inv \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t"
|
|
"s_nop 0 \n\t" ::
|
|
:);
|
|
}
|
|
|
|
static double
|
|
parsesize(const char* value)
|
|
{
|
|
long long int units;
|
|
double size;
|
|
char size_lit[2];
|
|
|
|
int count = sscanf(value, "%lf %1s", &size, size_lit);
|
|
|
|
switch(count)
|
|
{
|
|
case 2:
|
|
switch(size_lit[0])
|
|
{
|
|
case 'G':
|
|
case 'g': units = 1024 * 1024 * 1024; break;
|
|
case 'M':
|
|
case 'm': units = 1024 * 1024; break;
|
|
case 'K':
|
|
case 'k': units = 1024; break;
|
|
default: return -1.0;
|
|
};
|
|
break;
|
|
case 1: units = 1; break;
|
|
default: return -1.0;
|
|
}
|
|
|
|
return size * units;
|
|
}
|
|
|
|
static bool
|
|
minReqVersion(int rmajor, int rminor, int rpatch)
|
|
{
|
|
int version;
|
|
int major, minor, patch, rem;
|
|
ncclGetVersion(&version);
|
|
|
|
if(version < 10000)
|
|
{
|
|
major = version / 1000;
|
|
rem = version % 1000;
|
|
minor = rem / 100;
|
|
patch = rem % 100;
|
|
}
|
|
else
|
|
{
|
|
major = version / 10000;
|
|
rem = version % 10000;
|
|
minor = rem / 100;
|
|
patch = rem % 100;
|
|
}
|
|
|
|
if(major < rmajor)
|
|
return false;
|
|
else if(major > rmajor)
|
|
return true;
|
|
|
|
// major == rmajor
|
|
if(minor < rminor)
|
|
return false;
|
|
else if(minor > rminor)
|
|
return true;
|
|
|
|
// major == rmajor && minor == rminor
|
|
if(patch < rpatch) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
testResult_t
|
|
CheckDelta(void* results, void* expected, size_t count, size_t offset,
|
|
ncclDataType_t type, ncclRedOp_t op, uint64_t seed, int nranks,
|
|
int64_t* wrongEltN)
|
|
{
|
|
ncclVerifiableVerify(results, expected, count, (int) type, (int) op, nranks, seed,
|
|
offset, wrongEltN, cudaStreamDefault);
|
|
CUDACHECK(cudaDeviceSynchronize());
|
|
return testSuccess;
|
|
}
|
|
|
|
testResult_t
|
|
InitDataReduce(void* data, const size_t count, const size_t offset, ncclDataType_t type,
|
|
ncclRedOp_t op, uint64_t seed, int nranks)
|
|
{
|
|
ncclVerifiablePrepareExpected(data, count, (int) type, (int) op, nranks, seed, offset,
|
|
cudaStreamDefault);
|
|
return testSuccess;
|
|
}
|
|
|
|
testResult_t
|
|
InitData(void* data, const size_t count, size_t offset, ncclDataType_t type,
|
|
ncclRedOp_t op, uint64_t seed, int nranks, int rank)
|
|
{
|
|
ncclVerifiablePrepareInput(data, count, (int) type, (int) op, nranks, rank, seed,
|
|
offset, cudaStreamDefault);
|
|
return testSuccess;
|
|
}
|
|
|
|
void
|
|
Barrier(struct threadArgs* args)
|
|
{
|
|
thread_local int epoch = 0;
|
|
static pthread_mutex_t lock[2] = { PTHREAD_MUTEX_INITIALIZER,
|
|
PTHREAD_MUTEX_INITIALIZER };
|
|
static pthread_cond_t cond[2] = { PTHREAD_COND_INITIALIZER,
|
|
PTHREAD_COND_INITIALIZER };
|
|
static int counter[2] = { 0, 0 };
|
|
|
|
pthread_mutex_lock(&lock[epoch]);
|
|
if(++counter[epoch] == args->nThreads) pthread_cond_broadcast(&cond[epoch]);
|
|
|
|
if(args->thread + 1 == args->nThreads)
|
|
{
|
|
while(counter[epoch] != args->nThreads)
|
|
pthread_cond_wait(&cond[epoch], &lock[epoch]);
|
|
#ifdef MPI_SUPPORT
|
|
MPI_Barrier(MPI_COMM_WORLD);
|
|
#endif
|
|
counter[epoch] = 0;
|
|
pthread_cond_broadcast(&cond[epoch]);
|
|
}
|
|
else
|
|
{
|
|
while(counter[epoch] != 0)
|
|
pthread_cond_wait(&cond[epoch], &lock[epoch]);
|
|
}
|
|
pthread_mutex_unlock(&lock[epoch]);
|
|
epoch ^= 1;
|
|
}
|
|
|
|
// Inter-thread/process barrier+allreduce. The quality of the return value
|
|
// for average=0 (which means broadcast from rank=0) is dubious. The returned
|
|
// value will actually be the result of process-local broadcast from the local thread=0.
|
|
template <typename T>
|
|
void
|
|
Allreduce(struct threadArgs* args, T* value, int average)
|
|
{
|
|
thread_local int epoch = 0;
|
|
static pthread_mutex_t lock[2] = { PTHREAD_MUTEX_INITIALIZER,
|
|
PTHREAD_MUTEX_INITIALIZER };
|
|
static pthread_cond_t cond[2] = { PTHREAD_COND_INITIALIZER,
|
|
PTHREAD_COND_INITIALIZER };
|
|
static T accumulator[2];
|
|
static int counter[2] = { 0, 0 };
|
|
|
|
pthread_mutex_lock(&lock[epoch]);
|
|
if(counter[epoch] == 0)
|
|
{
|
|
if(average != 0 || args->thread == 0) accumulator[epoch] = *value;
|
|
}
|
|
else
|
|
{
|
|
switch(average)
|
|
{
|
|
case /*r0*/ 0:
|
|
if(args->thread == 0) accumulator[epoch] = *value;
|
|
break;
|
|
case /*avg*/ 1: accumulator[epoch] += *value; break;
|
|
case /*min*/ 2:
|
|
accumulator[epoch] = std::min<T>(accumulator[epoch], *value);
|
|
break;
|
|
case /*max*/ 3:
|
|
accumulator[epoch] = std::max<T>(accumulator[epoch], *value);
|
|
break;
|
|
case /*sum*/ 4: accumulator[epoch] += *value; break;
|
|
}
|
|
}
|
|
|
|
if(++counter[epoch] == args->nThreads) pthread_cond_broadcast(&cond[epoch]);
|
|
|
|
if(args->thread + 1 == args->nThreads)
|
|
{
|
|
while(counter[epoch] != args->nThreads)
|
|
pthread_cond_wait(&cond[epoch], &lock[epoch]);
|
|
|
|
#ifdef MPI_SUPPORT
|
|
if(average != 0)
|
|
{
|
|
static_assert(std::is_same<T, long long>::value ||
|
|
std::is_same<T, double>::value,
|
|
"Allreduce<T> only for T in {long long, double}");
|
|
MPI_Datatype ty = std::is_same<T, long long>::value ? MPI_LONG_LONG
|
|
: std::is_same<T, double>::value ? MPI_DOUBLE
|
|
: MPI_Datatype();
|
|
MPI_Op op = average == 1 ? MPI_SUM
|
|
: average == 2 ? MPI_MIN
|
|
: average == 3 ? MPI_MAX
|
|
: average == 4 ? MPI_SUM
|
|
: MPI_Op();
|
|
MPI_Allreduce(MPI_IN_PLACE, (void*) &accumulator[epoch], 1, ty, op,
|
|
MPI_COMM_WORLD);
|
|
}
|
|
#endif
|
|
|
|
if(average == 1) accumulator[epoch] /= args->totalProcs * args->nThreads;
|
|
counter[epoch] = 0;
|
|
pthread_cond_broadcast(&cond[epoch]);
|
|
}
|
|
else
|
|
{
|
|
while(counter[epoch] != 0)
|
|
pthread_cond_wait(&cond[epoch], &lock[epoch]);
|
|
}
|
|
pthread_mutex_unlock(&lock[epoch]);
|
|
|
|
*value = accumulator[epoch];
|
|
epoch ^= 1;
|
|
}
|
|
|
|
testResult_t
|
|
CheckData(struct threadArgs* args, ncclDataType_t type, ncclRedOp_t op, int root,
|
|
int in_place, int64_t* wrongElts)
|
|
{
|
|
int nranks = args->nProcs * args->nGpus * args->nThreads;
|
|
size_t count = args->expectedBytes / wordSize(type);
|
|
|
|
int64_t* wrongPerGpu = nullptr;
|
|
CUDACHECK(hipHostMalloc((void**) &wrongPerGpu, args->nGpus * sizeof(int64_t),
|
|
cudaHostAllocMapped));
|
|
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
int rank = ((args->proc * args->nThreads + args->thread) * args->nGpus + i);
|
|
CUDACHECK(cudaSetDevice(args->gpus[i]));
|
|
void* data = in_place ? ((void*) ((uintptr_t) args->recvbuffs[i] +
|
|
args->recvInplaceOffset * rank))
|
|
: args->recvbuffs[i];
|
|
|
|
TESTCHECK(CheckDelta(data, args->expected[i], count, 0, type, op, 0, nranks,
|
|
wrongPerGpu + i));
|
|
|
|
#if 1 && DEBUG_PRINT
|
|
if(args->reportErrors && wrongPerGpu[i] != 0)
|
|
{
|
|
printf("rank=%d #wrong=%d\n", rank, (int) wrongPerGpu[i]);
|
|
char* expectedHost = (char*) malloc(args->expectedBytes);
|
|
char* dataHost = (char*) malloc(args->expectedBytes);
|
|
int eltsz = wordSize(type);
|
|
cudaMemcpy(expectedHost, args->expected[i], args->expectedBytes,
|
|
cudaMemcpyDeviceToHost);
|
|
cudaMemcpy(dataHost, data, args->expectedBytes, cudaMemcpyDeviceToHost);
|
|
|
|
for(int j = 0; j < args->expectedBytes / eltsz; j++)
|
|
{
|
|
unsigned long long want, got;
|
|
want = 0;
|
|
memcpy(&want, expectedHost + j * eltsz, eltsz);
|
|
got = 0;
|
|
memcpy(&got, dataHost + j * eltsz, eltsz);
|
|
if(want != got)
|
|
{
|
|
printf(" rank=%d elt[%d]: want=0x%llx got=0x%llx\n", rank, j, want,
|
|
got);
|
|
}
|
|
}
|
|
free(expectedHost);
|
|
free(dataHost);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
*wrongElts = 0;
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
*wrongElts += wrongPerGpu[i];
|
|
cudaFreeHost(wrongPerGpu);
|
|
|
|
if(args->reportErrors && *wrongElts) args->errors[0]++;
|
|
return testSuccess;
|
|
}
|
|
|
|
testResult_t
|
|
testStreamSynchronize(int ngpus, cudaStream_t* streams, ncclComm_t* comms)
|
|
{
|
|
cudaError_t cudaErr;
|
|
int remaining = ngpus;
|
|
int* done = (int*) malloc(sizeof(int) * ngpus);
|
|
memset(done, 0, sizeof(int) * ngpus);
|
|
timer tim;
|
|
|
|
while(remaining)
|
|
{
|
|
int idle = 1;
|
|
for(int i = 0; i < ngpus; i++)
|
|
{
|
|
if(done[i]) continue;
|
|
|
|
cudaErr = cudaStreamQuery(streams[i]);
|
|
if(cudaErr == cudaSuccess)
|
|
{
|
|
done[i] = 1;
|
|
remaining--;
|
|
idle = 0;
|
|
continue;
|
|
}
|
|
|
|
if(cudaErr != cudaErrorNotReady) CUDACHECK(cudaErr);
|
|
|
|
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 4, 0)
|
|
if(test_ncclVersion >= NCCL_VERSION(2, 4, 0) && comms)
|
|
{
|
|
ncclResult_t ncclAsyncErr;
|
|
NCCLCHECK(ncclCommGetAsyncError(comms[i], &ncclAsyncErr));
|
|
if(ncclAsyncErr != ncclSuccess)
|
|
{
|
|
// An asynchronous error happened. Stop the operation and destroy
|
|
// the communicator
|
|
for(int i = 0; i < ngpus; i++)
|
|
NCCLCHECK(ncclCommAbort(comms[i]));
|
|
// Abort the perf test
|
|
NCCLCHECK(ncclAsyncErr);
|
|
}
|
|
}
|
|
double delta = tim.elapsed();
|
|
if(delta > timeout && timeout > 0)
|
|
{
|
|
for(int i = 0; i < ngpus; i++)
|
|
NCCLCHECK(ncclCommAbort(comms[i]));
|
|
char hostname[1024];
|
|
getHostName(hostname, 1024);
|
|
printf("%s: Test timeout (%ds) %s:%d\n", hostname, timeout, __FILE__,
|
|
__LINE__);
|
|
free(done);
|
|
return testTimeout;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// We might want to let other threads (including NCCL threads) use the CPU.
|
|
if(idle) sched_yield();
|
|
}
|
|
free(done);
|
|
return testSuccess;
|
|
}
|
|
|
|
testResult_t
|
|
startColl(struct threadArgs* args, ncclDataType_t type, ncclRedOp_t opIndex, int root,
|
|
int in_place, int iter)
|
|
{
|
|
size_t count = args->nbytes / wordSize(type);
|
|
|
|
// Try to change offset for each iteration so that we avoid cache effects and catch
|
|
// race conditions in ptrExchange
|
|
size_t shift = 0;
|
|
if(enable_rotating_tensor)
|
|
{
|
|
shift = cache_bytes * (iter % 2);
|
|
}
|
|
else
|
|
{
|
|
size_t totalnbytes = std::max(args->sendBytes, args->expectedBytes);
|
|
size_t steps = totalnbytes ? args->maxbytes / totalnbytes : 1;
|
|
shift = totalnbytes * (iter % steps);
|
|
}
|
|
|
|
if(args->nGpus > 1) NCCLCHECK(ncclGroupStart());
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
#ifndef NCCL_MAJOR
|
|
CUDACHECK(cudaSetDevice(args->gpus[i]));
|
|
#endif
|
|
int rank = ((args->proc * args->nThreads + args->thread) * args->nGpus + i);
|
|
char* recvBuff = ((char*) args->recvbuffs[i]) + shift;
|
|
char* sendBuff = ((char*) args->sendbuffs[i]) + shift;
|
|
ncclRedOp_t op;
|
|
|
|
if(opIndex < ncclNumOps)
|
|
{
|
|
op = opIndex;
|
|
}
|
|
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 11, 0)
|
|
else
|
|
{
|
|
union
|
|
{
|
|
int8_t i8;
|
|
uint8_t u8;
|
|
int32_t i32;
|
|
uint32_t u32;
|
|
int64_t i64;
|
|
uint64_t u64;
|
|
half f16;
|
|
float f32;
|
|
double f64;
|
|
# if defined(RCCL_BFLOAT16)
|
|
hip_bfloat16 bf16;
|
|
# endif
|
|
# if defined(RCCL_FLOAT8)
|
|
rccl_float8 fp8_e4m3;
|
|
rccl_bfloat8 fp8_e5m2;
|
|
# endif
|
|
};
|
|
switch(type)
|
|
{
|
|
case ncclInt8: i8 = ncclVerifiablePremulScalar<int8_t>(rank); break;
|
|
case ncclUint8: u8 = ncclVerifiablePremulScalar<uint8_t>(rank); break;
|
|
case ncclInt32: i32 = ncclVerifiablePremulScalar<int32_t>(rank); break;
|
|
case ncclUint32: u32 = ncclVerifiablePremulScalar<uint32_t>(rank); break;
|
|
case ncclInt64: i64 = ncclVerifiablePremulScalar<int64_t>(rank); break;
|
|
case ncclUint64: u64 = ncclVerifiablePremulScalar<uint64_t>(rank); break;
|
|
case ncclFloat16: f16 = ncclVerifiablePremulScalar<half>(rank); break;
|
|
case ncclFloat32: f32 = ncclVerifiablePremulScalar<float>(rank); break;
|
|
case ncclFloat64: f64 = ncclVerifiablePremulScalar<double>(rank); break;
|
|
# if defined(RCCL_BFLOAT16)
|
|
case ncclBfloat16:
|
|
bf16 = ncclVerifiablePremulScalar<hip_bfloat16>(rank);
|
|
break;
|
|
# endif
|
|
# if defined(RCCL_FLOAT8)
|
|
case ncclFp8E4M3:
|
|
fp8_e4m3 = ncclVerifiablePremulScalar<rccl_float8>(rank);
|
|
break;
|
|
case ncclFp8E5M2:
|
|
fp8_e5m2 = ncclVerifiablePremulScalar<rccl_bfloat8>(rank);
|
|
break;
|
|
# endif
|
|
case ncclNumTypes: break;
|
|
}
|
|
NCCLCHECK(ncclRedOpCreatePreMulSum(&op, &u64, type, ncclScalarHostImmediate,
|
|
args->comms[i]));
|
|
}
|
|
#endif
|
|
|
|
if(enable_cache_flush > 0 && ((iter % enable_cache_flush) == 0))
|
|
{
|
|
hipLaunchKernelGGL(flush_icache, dim3(gpu_block3), dim3(64), 0,
|
|
args->streams[i]);
|
|
}
|
|
|
|
TESTCHECK(args->collTest->runColl(
|
|
(void*) (in_place ? recvBuff + args->sendInplaceOffset * rank : sendBuff),
|
|
(void*) (in_place ? recvBuff + args->recvInplaceOffset * rank : recvBuff),
|
|
count, type, op, root, args->comms[i], args->streams[i]));
|
|
|
|
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 11, 0)
|
|
if(opIndex >= ncclNumOps)
|
|
{
|
|
NCCLCHECK(ncclRedOpDestroy(op, args->comms[i]));
|
|
}
|
|
#endif
|
|
}
|
|
if(args->nGpus > 1) NCCLCHECK(ncclGroupEnd());
|
|
|
|
if(blocking_coll)
|
|
{
|
|
// Complete op before returning
|
|
TESTCHECK(testStreamSynchronize(args->nGpus, args->streams, args->comms));
|
|
}
|
|
if(blocking_coll) Barrier(args);
|
|
return testSuccess;
|
|
}
|
|
|
|
testResult_t
|
|
completeColl(struct threadArgs* args)
|
|
{
|
|
if(blocking_coll) return testSuccess;
|
|
|
|
TESTCHECK(testStreamSynchronize(args->nGpus, args->streams, args->comms));
|
|
return testSuccess;
|
|
}
|
|
|
|
testResult_t
|
|
BenchTime(struct threadArgs* args, ncclDataType_t type, ncclRedOp_t op, int root,
|
|
int in_place)
|
|
{
|
|
size_t count = args->nbytes / wordSize(type);
|
|
if(datacheck)
|
|
{
|
|
// Initialize sendbuffs, recvbuffs and expected
|
|
TESTCHECK(args->collTest->initData(args, type, op, root, 99, in_place));
|
|
}
|
|
|
|
if(warmup_iters)
|
|
{
|
|
// Sync
|
|
TESTCHECK(startColl(args, type, op, root, in_place, 0));
|
|
TESTCHECK(completeColl(args));
|
|
}
|
|
|
|
Barrier(args);
|
|
|
|
#if HIP_VERSION >= 50221310
|
|
std::vector<cudaGraph_t> graphs(args->nGpus);
|
|
std::vector<cudaGraphExec_t> graphExec(args->nGpus);
|
|
if(cudaGraphLaunches >= 1)
|
|
{
|
|
// Begin cuda graph capture
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
// Thread local mdoe is needed for:
|
|
// - Multi-thread mode: where graph capture and instantiation can happen
|
|
// concurrently across threads
|
|
// - P2P pre-connect: when there is no warm-up, P2P pre-connect is done during
|
|
// graph capture.
|
|
// Since pre-connect calls cudaMalloc, we cannot use global capture mode
|
|
CUDACHECK(cudaStreamBeginCapture(args->streams[i],
|
|
cudaStreamCaptureModeThreadLocal));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Performance Benchmark
|
|
timer tim;
|
|
for(int iter = 0; iter < iters; iter++)
|
|
{
|
|
if(agg_iters > 1) NCCLCHECK(ncclGroupStart());
|
|
for(int aiter = 0; aiter < agg_iters; aiter++)
|
|
{
|
|
TESTCHECK(
|
|
startColl(args, type, op, root, in_place, iter * agg_iters + aiter));
|
|
}
|
|
if(agg_iters > 1) NCCLCHECK(ncclGroupEnd());
|
|
}
|
|
|
|
#if HIP_VERSION >= 50221310
|
|
if(cudaGraphLaunches >= 1)
|
|
{
|
|
// End cuda graph capture
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(cudaStreamEndCapture(args->streams[i], graphs.data() + i));
|
|
}
|
|
// Instantiate cuda graph
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(
|
|
cudaGraphInstantiate(graphExec.data() + i, graphs[i], NULL, NULL, 0));
|
|
}
|
|
// Resync CPU, restart timing, launch cuda graph
|
|
Barrier(args);
|
|
tim.reset();
|
|
for(int l = 0; l < cudaGraphLaunches; l++)
|
|
{
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(cudaGraphLaunch(graphExec[i], args->streams[i]));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
double cputimeSec = tim.elapsed() / (iters * agg_iters);
|
|
TESTCHECK(completeColl(args));
|
|
|
|
double deltaSec = tim.elapsed();
|
|
deltaSec = deltaSec / (iters * agg_iters);
|
|
if(cudaGraphLaunches >= 1) deltaSec = deltaSec / cudaGraphLaunches;
|
|
Allreduce(args, &deltaSec, average);
|
|
|
|
#if HIP_VERSION >= 50221310
|
|
if(cudaGraphLaunches >= 1)
|
|
{
|
|
// destroy cuda graph
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(cudaGraphExecDestroy(graphExec[i]));
|
|
CUDACHECK(cudaGraphDestroy(graphs[i]));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
double algBw, busBw;
|
|
args->collTest->getBw(count, wordSize(type), deltaSec, &algBw, &busBw,
|
|
args->nProcs * args->nThreads * args->nGpus);
|
|
|
|
Barrier(args);
|
|
|
|
int64_t wrongElts = 0;
|
|
static __thread int rep = 0;
|
|
rep++;
|
|
for(int c = 0; c < datacheck; c++)
|
|
{
|
|
// Initialize sendbuffs, recvbuffs and expected
|
|
TESTCHECK(args->collTest->initData(args, type, op, root, rep, in_place));
|
|
|
|
#if HIP_VERSION >= 50221310
|
|
if(cudaGraphLaunches >= 1)
|
|
{
|
|
// Begin cuda graph capture for data check
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(cudaStreamBeginCapture(args->streams[i],
|
|
args->nThreads > 1
|
|
? cudaStreamCaptureModeThreadLocal
|
|
: cudaStreamCaptureModeGlobal));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// test validation in single itertion, should ideally be included into the
|
|
// multi-iteration run
|
|
TESTCHECK(startColl(args, type, op, root, in_place, 0));
|
|
|
|
#if HIP_VERSION >= 50221310
|
|
if(cudaGraphLaunches >= 1)
|
|
{
|
|
// End cuda graph capture
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(cudaStreamEndCapture(args->streams[i], graphs.data() + i));
|
|
}
|
|
// Instantiate cuda graph
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(
|
|
cudaGraphInstantiate(graphExec.data() + i, graphs[i], NULL, NULL, 0));
|
|
}
|
|
// Launch cuda graph
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(cudaGraphLaunch(graphExec[i], args->streams[i]));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TESTCHECK(completeColl(args));
|
|
|
|
#if HIP_VERSION >= 50221310
|
|
if(cudaGraphLaunches >= 1)
|
|
{
|
|
// destroy cuda graph
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(cudaGraphExecDestroy(graphExec[i]));
|
|
CUDACHECK(cudaGraphDestroy(graphs[i]));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TESTCHECK(CheckData(args, type, op, root, in_place, &wrongElts));
|
|
|
|
// aggregate delta from all threads and procs
|
|
long long wrongElts1 = wrongElts;
|
|
// if (wrongElts) fprintf(stderr, "\nERROR: Data corruption : rank %d size %ld
|
|
// wrongElts %ld\n", args->proc, args->expectedBytes, wrongElts);
|
|
Allreduce(args, &wrongElts1, /*sum*/ 4);
|
|
wrongElts = wrongElts1;
|
|
if(wrongElts) break;
|
|
}
|
|
|
|
double timeUsec = (report_cputime ? cputimeSec : deltaSec) * 1.0E6;
|
|
char timeStr[100];
|
|
if(timeUsec >= 10000.0)
|
|
{
|
|
sprintf(timeStr, "%7.0f", timeUsec);
|
|
}
|
|
else if(timeUsec >= 100.0)
|
|
{
|
|
sprintf(timeStr, "%7.1f", timeUsec);
|
|
}
|
|
else
|
|
{
|
|
sprintf(timeStr, "%7.2f", timeUsec);
|
|
}
|
|
if(args->reportErrors)
|
|
{
|
|
PRINT(" %7s %6.2f %6.2f %5g", timeStr, algBw, busBw, (double) wrongElts);
|
|
}
|
|
else
|
|
{
|
|
PRINT(" %7s %6.2f %6.2f %5s", timeStr, algBw, busBw, "N/A");
|
|
}
|
|
|
|
auto largestMessageSize = std::max(args->sendBytes, args->expectedBytes);
|
|
if(args->reporter)
|
|
{
|
|
if(args->reportErrors)
|
|
{
|
|
args->reporter->addResult((args->nThreads * args->nGpus), args->nProcs,
|
|
args->totalProcs, largestMessageSize, in_place,
|
|
timeUsec, algBw, busBw, wrongElts);
|
|
}
|
|
else
|
|
{
|
|
args->reporter->addResult((args->nThreads * args->nGpus), args->nProcs,
|
|
args->totalProcs, largestMessageSize, in_place,
|
|
timeUsec, algBw, busBw);
|
|
}
|
|
}
|
|
|
|
args->bw[0] += busBw;
|
|
args->bw_count[0]++;
|
|
return testSuccess;
|
|
}
|
|
|
|
void
|
|
setupArgs(size_t size, ncclDataType_t type, struct threadArgs* args)
|
|
{
|
|
int nranks = args->nProcs * args->nGpus * args->nThreads;
|
|
size_t count, sendCount, recvCount, paramCount, sendInplaceOffset, recvInplaceOffset;
|
|
|
|
count = size / wordSize(type);
|
|
args->collTest->getCollByteCount(&sendCount, &recvCount, ¶mCount,
|
|
&sendInplaceOffset, &recvInplaceOffset,
|
|
(size_t) count, wordSize(type), (size_t) nranks);
|
|
|
|
args->nbytes = paramCount * wordSize(type);
|
|
args->sendBytes = sendCount * wordSize(type);
|
|
args->expectedBytes = recvCount * wordSize(type);
|
|
args->sendInplaceOffset = sendInplaceOffset * wordSize(type);
|
|
args->recvInplaceOffset = recvInplaceOffset * wordSize(type);
|
|
}
|
|
|
|
testResult_t
|
|
TimeTest(struct threadArgs* args, ncclDataType_t type, const char* typeName,
|
|
ncclRedOp_t op, const char* opName, int root)
|
|
{
|
|
// Sync to avoid first-call timeout
|
|
Barrier(args);
|
|
|
|
// Warm-up for large size
|
|
setupArgs(args->maxbytes, type, args);
|
|
#if HIP_VERSION >= 50221310
|
|
std::vector<cudaGraph_t> graphs(args->nGpus);
|
|
std::vector<cudaGraphExec_t> graphExec(args->nGpus);
|
|
if(cudaGraphLaunches >= 1)
|
|
{
|
|
// Begin cuda graph capture
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
// Thread local mode is needed for:
|
|
// - Multi-thread mode: where graph capture and instantiation can happen
|
|
// concurrently across threads
|
|
// - P2P pre-connect: when there is no warm-up, P2P pre-connect is done during
|
|
// graph capture.
|
|
// Since pre-connect calls cudaMalloc, we cannot use global capture mode
|
|
CUDACHECK(cudaStreamBeginCapture(args->streams[i],
|
|
cudaStreamCaptureModeThreadLocal));
|
|
}
|
|
}
|
|
#endif
|
|
for(int iter = 0; iter < warmup_iters; iter++)
|
|
{
|
|
TESTCHECK(startColl(args, type, op, root, 0, iter));
|
|
}
|
|
|
|
#if HIP_VERSION >= 50221310
|
|
if(cudaGraphLaunches >= 1)
|
|
{
|
|
// End cuda graph capture
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(cudaStreamEndCapture(args->streams[i], graphs.data() + i));
|
|
}
|
|
// Instantiate cuda graph
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(
|
|
cudaGraphInstantiate(graphExec.data() + i, graphs[i], NULL, NULL, 0));
|
|
}
|
|
// Resync CPU, restart timing, launch cuda graph
|
|
Barrier(args);
|
|
for(int l = 0; l < cudaGraphLaunches; l++)
|
|
{
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(cudaGraphLaunch(graphExec[i], args->streams[i]));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TESTCHECK(completeColl(args));
|
|
|
|
#if HIP_VERSION >= 50221310
|
|
if(cudaGraphLaunches >= 1)
|
|
{
|
|
// destroy cuda graph
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(cudaGraphExecDestroy(graphExec[i]));
|
|
CUDACHECK(cudaGraphDestroy(graphs[i]));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Warm-up for small size
|
|
setupArgs(args->minbytes, type, args);
|
|
#if HIP_VERSION >= 50221310
|
|
if(cudaGraphLaunches >= 1)
|
|
{
|
|
// Begin cuda graph capture
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
// Thread local mode is needed for:
|
|
// - Multi-thread mode: where graph capture and instantiation can happen
|
|
// concurrently across threads
|
|
// - P2P pre-connect: when there is no warm-up, P2P pre-connect is done during
|
|
// graph capture.
|
|
// Since pre-connect calls cudaMalloc, we cannot use global capture mode
|
|
CUDACHECK(cudaStreamBeginCapture(args->streams[i],
|
|
cudaStreamCaptureModeThreadLocal));
|
|
}
|
|
}
|
|
#endif
|
|
for(int iter = 0; iter < warmup_iters; iter++)
|
|
{
|
|
TESTCHECK(startColl(args, type, op, root, iter < warmup_iters / 2 ? 0 : 1, iter));
|
|
}
|
|
|
|
#if HIP_VERSION >= 50221310
|
|
if(cudaGraphLaunches >= 1)
|
|
{
|
|
// End cuda graph capture
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(cudaStreamEndCapture(args->streams[i], graphs.data() + i));
|
|
}
|
|
// Instantiate cuda graph
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(
|
|
cudaGraphInstantiate(graphExec.data() + i, graphs[i], NULL, NULL, 0));
|
|
}
|
|
// Resync CPU, restart timing, launch cuda graph
|
|
Barrier(args);
|
|
for(int l = 0; l < cudaGraphLaunches; l++)
|
|
{
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(cudaGraphLaunch(graphExec[i], args->streams[i]));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TESTCHECK(completeColl(args));
|
|
|
|
#if HIP_VERSION >= 50221310
|
|
if(cudaGraphLaunches >= 1)
|
|
{
|
|
// destroy cuda graph
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
CUDACHECK(cudaGraphExecDestroy(graphExec[i]));
|
|
CUDACHECK(cudaGraphDestroy(graphs[i]));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Benchmark
|
|
long repeat = run_cycles;
|
|
size_t iter = 0;
|
|
|
|
do
|
|
{
|
|
if(run_cycles > 1) PRINT("# Testing %lu cycle.\n", iter + 1);
|
|
if(args->reporter)
|
|
{
|
|
args->reporter->setParameters(iter, args->collTest->name, typeName, opName);
|
|
}
|
|
for(size_t size = args->minbytes; size <= args->maxbytes;
|
|
size = ((args->stepfactor > 1) ? size * args->stepfactor
|
|
: size + args->stepbytes))
|
|
{
|
|
setupArgs(size, type, args);
|
|
char rootName[100];
|
|
sprintf(rootName, "%6i", root);
|
|
PRINT("%12li %12li %8s %6s %6s",
|
|
std::max(args->sendBytes, args->expectedBytes),
|
|
args->nbytes / wordSize(type), typeName, opName, rootName);
|
|
if(enable_out_of_place)
|
|
{
|
|
TESTCHECK(BenchTime(args, type, op, root, 0));
|
|
usleep(delay_inout_place);
|
|
}
|
|
if(enable_in_place) TESTCHECK(BenchTime(args, type, op, root, 1));
|
|
PRINT("\n");
|
|
}
|
|
--repeat;
|
|
++iter;
|
|
} while(repeat != 0);
|
|
|
|
return testSuccess;
|
|
}
|
|
|
|
testResult_t
|
|
threadRunTests(struct threadArgs* args)
|
|
{
|
|
// Set device to the first of our GPUs. If we don't do that, some operations
|
|
// will be done on the current GPU (by default : 0) and if the GPUs are in
|
|
// exclusive mode those operations will fail.
|
|
CUDACHECK(cudaSetDevice(args->gpus[0]));
|
|
TESTCHECK(ncclTestEngine.runTest(args, ncclroot, (ncclDataType_t) nccltype,
|
|
test_typenames[nccltype], (ncclRedOp_t) ncclop,
|
|
test_opnames[ncclop]));
|
|
return testSuccess;
|
|
}
|
|
|
|
testResult_t
|
|
threadInit(struct threadArgs* args)
|
|
{
|
|
char hostname[1024];
|
|
getHostName(hostname, 1024);
|
|
int nranks = args->nProcs * args->nThreads * args->nGpus;
|
|
|
|
// set main thread again
|
|
is_main_thread = (is_main_proc && args->thread == 0) ? 1 : 0;
|
|
|
|
NCCLCHECK(ncclGroupStart());
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
int rank =
|
|
args->proc * args->nThreads * args->nGpus + args->thread * args->nGpus + i;
|
|
CUDACHECK(cudaSetDevice(args->gpus[i]));
|
|
NCCLCHECK(ncclCommInitRank(args->comms + i, nranks, args->ncclId, rank));
|
|
}
|
|
NCCLCHECK(ncclGroupEnd());
|
|
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 19, 0)
|
|
void** sendRegHandles =
|
|
(local_register) ? (void**) malloc(sizeof(*sendRegHandles) * args->nGpus) : NULL;
|
|
void** recvRegHandles =
|
|
(local_register) ? (void**) malloc(sizeof(*recvRegHandles) * args->nGpus) : NULL;
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
if(local_register)
|
|
NCCLCHECK(ncclCommRegister(args->comms[i], args->sendbuffs[i], args->maxbytes,
|
|
&sendRegHandles[i]));
|
|
if(local_register)
|
|
NCCLCHECK(ncclCommRegister(args->comms[i], args->recvbuffs[i], args->maxbytes,
|
|
&recvRegHandles[i]));
|
|
}
|
|
#endif
|
|
|
|
TESTCHECK(threadRunTests(args));
|
|
|
|
for(int i = 0; i < args->nGpus; i++)
|
|
{
|
|
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 19, 0)
|
|
if(local_register)
|
|
NCCLCHECK(ncclCommDeregister(args->comms[i], sendRegHandles[i]));
|
|
if(local_register)
|
|
NCCLCHECK(ncclCommDeregister(args->comms[i], recvRegHandles[i]));
|
|
#endif
|
|
NCCLCHECK(ncclCommDestroy(args->comms[i]));
|
|
}
|
|
return testSuccess;
|
|
}
|
|
|
|
void*
|
|
threadLauncher(void* thread_)
|
|
{
|
|
struct testThread* thread = (struct testThread*) thread_;
|
|
thread->ret = thread->func(&thread->args);
|
|
return NULL;
|
|
}
|
|
testResult_t
|
|
threadLaunch(struct testThread* thread)
|
|
{
|
|
pthread_create(&thread->thread, NULL, threadLauncher, thread);
|
|
return testSuccess;
|
|
}
|
|
|
|
testResult_t
|
|
AllocateBuffs(void** sendbuff, size_t sendBytes, void** recvbuff, size_t recvBytes,
|
|
void** expected, size_t nbytes)
|
|
{
|
|
if(enable_rotating_tensor)
|
|
{
|
|
recvBytes = recvBytes + cache_bytes;
|
|
nbytes = nbytes + cache_bytes;
|
|
}
|
|
if(memorytype == ncclFine)
|
|
{
|
|
if(HIP_VERSION >= 50700000)
|
|
{
|
|
CUDACHECK(hipExtMallocWithFlags(sendbuff, nbytes, hipDeviceMallocUncached));
|
|
CUDACHECK(hipExtMallocWithFlags(recvbuff, nbytes, hipDeviceMallocUncached));
|
|
if(datacheck)
|
|
CUDACHECK(
|
|
hipExtMallocWithFlags(expected, recvBytes, hipDeviceMallocUncached));
|
|
}
|
|
else
|
|
{
|
|
CUDACHECK(
|
|
hipExtMallocWithFlags(sendbuff, nbytes, hipDeviceMallocFinegrained));
|
|
CUDACHECK(
|
|
hipExtMallocWithFlags(recvbuff, nbytes, hipDeviceMallocFinegrained));
|
|
if(datacheck)
|
|
CUDACHECK(hipExtMallocWithFlags(expected, recvBytes,
|
|
hipDeviceMallocFinegrained));
|
|
}
|
|
}
|
|
else if(memorytype == ncclHost)
|
|
{
|
|
CUDACHECK(hipHostMalloc(sendbuff, nbytes));
|
|
CUDACHECK(hipHostMalloc(recvbuff, nbytes));
|
|
if(datacheck) CUDACHECK(hipHostMalloc(expected, recvBytes));
|
|
}
|
|
else if(memorytype == ncclManaged)
|
|
{
|
|
CUDACHECK(cudaMallocManaged(sendbuff, nbytes));
|
|
CUDACHECK(cudaMallocManaged(recvbuff, nbytes));
|
|
if(datacheck) CUDACHECK(cudaMallocManaged(expected, recvBytes));
|
|
#if 0
|
|
CUDACHECK(cudaMemset(*sendbuff, 0, nbytes));
|
|
CUDACHECK(cudaMemset(*recvbuff, 0, nbytes));
|
|
if (datacheck) CUDACHECK(cudaMemset(*expected, 0, recvBytes));
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
CUDACHECK(cudaMalloc(sendbuff, nbytes));
|
|
CUDACHECK(cudaMalloc(recvbuff, nbytes));
|
|
if(datacheck) CUDACHECK(cudaMalloc(expected, recvBytes));
|
|
}
|
|
CUDACHECK(hipMemset(*sendbuff, 1, nbytes));
|
|
if(datacheck) CUDACHECK(hipMemset(*expected, 1, recvBytes));
|
|
return testSuccess;
|
|
}
|
|
|
|
testResult_t
|
|
run(); // Main function
|
|
|
|
int
|
|
main(int argc, char* argv[])
|
|
{
|
|
// Make sure everyline is flushed so that we see the progress of the test
|
|
setlinebuf(stdout);
|
|
|
|
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 4, 0)
|
|
ncclGetVersion(&test_ncclVersion);
|
|
#else
|
|
test_ncclVersion = NCCL_VERSION_CODE;
|
|
#endif
|
|
|
|
// printf("# NCCL_VERSION_CODE=%d ncclGetVersion=%d\n", NCCL_VERSION_CODE,
|
|
// test_ncclVersion);
|
|
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 0, 0)
|
|
test_opnum = 4;
|
|
test_typenum = 9;
|
|
if(NCCL_VERSION_CODE >= NCCL_VERSION(2, 10, 0) &&
|
|
test_ncclVersion >= NCCL_VERSION(2, 10, 0))
|
|
{
|
|
test_opnum++; // ncclAvg
|
|
# if defined(RCCL_BFLOAT16)
|
|
test_typenum++; // bfloat16
|
|
# endif
|
|
# if defined(RCCL_FLOAT8)
|
|
test_typenum++; // fp8_e4m3
|
|
test_typenum++; // fp8_e5m2
|
|
# endif
|
|
}
|
|
if(NCCL_VERSION_CODE >= NCCL_VERSION(2, 11, 0) &&
|
|
test_ncclVersion >= NCCL_VERSION(2, 11, 0))
|
|
{
|
|
test_opnum++; // PreMulSum
|
|
}
|
|
#endif
|
|
|
|
// Parse args
|
|
// Replace getopt_long with manual argument parsing
|
|
double parsed;
|
|
for(int argi = 1; argi < argc; ++argi)
|
|
{
|
|
const char* arg = argv[argi];
|
|
if(strcmp(arg, "-t") == 0 || strcmp(arg, "--nthreads") == 0)
|
|
{
|
|
nThreads = strtol(argv[++argi], NULL, 0);
|
|
}
|
|
else if(strcmp(arg, "-g") == 0 || strcmp(arg, "--ngpus") == 0)
|
|
{
|
|
nGpus = strtol(argv[++argi], NULL, 0);
|
|
}
|
|
else if(strcmp(arg, "-b") == 0 || strcmp(arg, "--minbytes") == 0)
|
|
{
|
|
parsed = parsesize(argv[++argi]);
|
|
if(parsed < 0)
|
|
{
|
|
fprintf(stderr, "invalid size specified for 'minbytes'\n");
|
|
return -1;
|
|
}
|
|
minBytes = (size_t) parsed;
|
|
}
|
|
else if(strcmp(arg, "-e") == 0 || strcmp(arg, "--maxbytes") == 0)
|
|
{
|
|
parsed = parsesize(argv[++argi]);
|
|
if(parsed < 0)
|
|
{
|
|
fprintf(stderr, "invalid size specified for 'maxbytes'\n");
|
|
return -1;
|
|
}
|
|
maxBytes = (size_t) parsed;
|
|
}
|
|
else if(strcmp(arg, "-i") == 0 || strcmp(arg, "--stepbytes") == 0)
|
|
{
|
|
parsed = parsesize(argv[++argi]);
|
|
if(parsed < 0)
|
|
{
|
|
fprintf(stderr, "invalid size specified for 'stepBytes'\n");
|
|
return -1;
|
|
}
|
|
stepBytes = (size_t) parsed;
|
|
}
|
|
else if(strcmp(arg, "-f") == 0 || strcmp(arg, "--stepfactor") == 0)
|
|
{
|
|
stepFactor = strtol(argv[++argi], NULL, 0);
|
|
}
|
|
else if(strcmp(arg, "-n") == 0 || strcmp(arg, "--iters") == 0)
|
|
{
|
|
iters = (int) strtol(argv[++argi], NULL, 0);
|
|
}
|
|
else if(strcmp(arg, "-m") == 0 || strcmp(arg, "--agg_iters") == 0)
|
|
{
|
|
#if NCCL_MAJOR > 2 || (NCCL_MAJOR >= 2 && NCCL_MINOR >= 2)
|
|
agg_iters = (int) strtol(argv[++argi], NULL, 0);
|
|
#else
|
|
fprintf(stderr, "Option -m not supported before NCCL 2.2. Ignoring\n");
|
|
++argi;
|
|
#endif
|
|
}
|
|
else if(strcmp(arg, "-w") == 0 || strcmp(arg, "--warmup_iters") == 0)
|
|
{
|
|
warmup_iters = (int) strtol(argv[++argi], NULL, 0);
|
|
}
|
|
else if(strcmp(arg, "-N") == 0 || strcmp(arg, "--run_cycles") == 0)
|
|
{
|
|
run_cycles = (int) strtol(argv[++argi], NULL, 0);
|
|
}
|
|
else if(strcmp(arg, "-p") == 0 || strcmp(arg, "--parallel_init") == 0)
|
|
{
|
|
parallel_init = (int) strtol(argv[++argi], NULL, 0);
|
|
}
|
|
else if(strcmp(arg, "-c") == 0 || strcmp(arg, "--check") == 0)
|
|
{
|
|
datacheck = (int) strtol(argv[++argi], NULL, 0);
|
|
}
|
|
else if(strcmp(arg, "-o") == 0 || strcmp(arg, "--op") == 0)
|
|
{
|
|
ncclop = ncclstringtoop(argv[++argi]);
|
|
}
|
|
else if(strcmp(arg, "-d") == 0 || strcmp(arg, "--datatype") == 0)
|
|
{
|
|
nccltype = ncclstringtotype(argv[++argi]);
|
|
}
|
|
else if(strcmp(arg, "-r") == 0 || strcmp(arg, "--root") == 0)
|
|
{
|
|
ncclroot = ncclstringtoroot(argv[++argi]);
|
|
}
|
|
else if(strcmp(arg, "-z") == 0 || strcmp(arg, "--blocking") == 0)
|
|
{
|
|
blocking_coll = strtol(argv[++argi], NULL, 0);
|
|
}
|
|
else if(strcmp(arg, "-y") == 0 || strcmp(arg, "--stream_null") == 0)
|
|
{
|
|
streamnull = strtol(argv[++argi], NULL, 0);
|
|
}
|
|
else if(strcmp(arg, "-T") == 0 || strcmp(arg, "--timeout") == 0)
|
|
{
|
|
timeout = strtol(argv[++argi], NULL, 0);
|
|
}
|
|
else if(strcmp(arg, "-G") == 0 || strcmp(arg, "--cudagraph") == 0)
|
|
{
|
|
#if(NCCL_MAJOR > 2 || (NCCL_MAJOR >= 2 && NCCL_MINOR >= 9)) && HIP_VERSION >= 50221310
|
|
cudaGraphLaunches = strtol(argv[++argi], NULL, 0);
|
|
#else
|
|
printf("Option -G (HIP graph) not supported before NCCL 2.9 + ROCm 5.2 "
|
|
"Ignoring\n");
|
|
++argi;
|
|
#endif
|
|
}
|
|
else if(strcmp(arg, "-C") == 0 || strcmp(arg, "--report_cputime") == 0)
|
|
{
|
|
report_cputime = strtol(argv[++argi], NULL, 0);
|
|
}
|
|
else if(strcmp(arg, "-a") == 0 || strcmp(arg, "--average") == 0)
|
|
{
|
|
average = (int) strtol(argv[++argi], NULL, 0);
|
|
}
|
|
else if(strcmp(arg, "-R") == 0 || strcmp(arg, "--local_register") == 0)
|
|
{
|
|
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 19, 0)
|
|
if((int) strtol(argv[++argi], NULL, 0))
|
|
{
|
|
local_register = 1;
|
|
}
|
|
#else
|
|
printf("Option -R (register) is not supported before NCCL 2.19. Ignoring\n");
|
|
++argi;
|
|
#endif
|
|
}
|
|
else if(strcmp(arg, "-Y") == 0 || strcmp(arg, "--memory_type") == 0)
|
|
{
|
|
memorytype = ncclstringtomtype(argv[++argi]);
|
|
}
|
|
else if(strcmp(arg, "-u") == 0 || strcmp(arg, "--cumask") == 0)
|
|
{
|
|
int nmasks = 0;
|
|
char* maskstr = argv[++argi];
|
|
char* mask = strtok(maskstr, ",");
|
|
while(mask != NULL && nmasks < 4)
|
|
{
|
|
cumask[nmasks++] = strtol(mask, NULL, 16);
|
|
mask = strtok(NULL, ",");
|
|
}
|
|
}
|
|
else if(strcmp(arg, "-O") == 0 || strcmp(arg, "--out_of_place") == 0)
|
|
{
|
|
enable_out_of_place = strtol(argv[++argi], NULL, 0);
|
|
enable_in_place = enable_out_of_place ? 0 : 1;
|
|
}
|
|
else if(strcmp(arg, "-q") == 0 || strcmp(arg, "--delay_inout_place") == 0)
|
|
{
|
|
delay_inout_place = (int) strtol(argv[++argi], NULL, 10);
|
|
}
|
|
else if(strcmp(arg, "-F") == 0 || strcmp(arg, "--cache_flush") == 0)
|
|
{
|
|
enable_cache_flush = strtol(argv[++argi], NULL, 0);
|
|
if(enable_cache_flush > 0)
|
|
{
|
|
hipDeviceProp_t deviceProps;
|
|
CHECK_HIP_ERROR(hipGetDeviceProperties(&deviceProps, 0));
|
|
gpu_block3 = deviceProps.multiProcessorCount * 60;
|
|
}
|
|
}
|
|
else if(strcmp(arg, "-E") == 0 || strcmp(arg, "--rotating_tensor") == 0)
|
|
{
|
|
enable_rotating_tensor = strtol(argv[++argi], NULL, 0);
|
|
}
|
|
else if(strcmp(arg, "-x") == 0 || strcmp(arg, "--output_file") == 0)
|
|
{
|
|
output_file = argv[++argi];
|
|
}
|
|
else if(strcmp(arg, "-Z") == 0 || strcmp(arg, "--output_format") == 0)
|
|
{
|
|
output_format = argv[++argi];
|
|
}
|
|
else if(strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0)
|
|
{
|
|
printf("USAGE: %s \n\t"
|
|
"[-t,--nthreads <num threads>] \n\t"
|
|
"[-g,--ngpus <gpus per thread>] \n\t"
|
|
"[-b,--minbytes <min size in bytes>] \n\t"
|
|
"[-e,--maxbytes <max size in bytes>] \n\t"
|
|
"[-i,--stepbytes <increment size>] \n\t"
|
|
"[-f,--stepfactor <increment factor>] \n\t"
|
|
"[-n,--iters <iteration count>] \n\t"
|
|
"[-m,--agg_iters <aggregated iteration count>] \n\t"
|
|
"[-w,--warmup_iters <warmup iteration count>] \n\t"
|
|
"[-N,--run_cycles <cycle count> run & print each cycle (default: 1; "
|
|
"0=infinite)] \n\t"
|
|
"[-p,--parallel_init <0/1>] \n\t"
|
|
"[-c,--check <check iteration count>] \n\t"
|
|
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 11, 0)
|
|
"[-o,--op <sum/prod/min/max/avg/mulsum/all>] \n\t"
|
|
#elif NCCL_VERSION_CODE >= NCCL_VERSION(2, 10, 0)
|
|
"[-o,--op <sum/prod/min/max/avg/all>] \n\t"
|
|
#else
|
|
"[-o,--op <sum/prod/min/max/all>] \n\t"
|
|
#endif
|
|
"[-d,--datatype <nccltype/all>] \n\t"
|
|
"[-r,--root <root/all>] \n\t"
|
|
"[-z,--blocking <0/1>] \n\t"
|
|
"[-y,--stream_null <0/1>] \n\t"
|
|
"[-T,--timeout <time in seconds>] \n\t"
|
|
"[-G,--cudagraph <num graph launches>] \n\t"
|
|
"[-C,--report_cputime <0/1>] \n\t"
|
|
"[-a,--average <0/1/2/3> report average iteration time "
|
|
"<0=RANK0/1=AVG/2=MIN/3=MAX>] \n\t"
|
|
"[-R,--local_register <1/0> enable local buffer registration on "
|
|
"send/recv buffers (default: disable)] \n\t"
|
|
"[-Y,--memory_type <coarse/fine/host/managed>] \n\t"
|
|
"[-u,--cumask <d0,d1,d2,d3>] \n\t"
|
|
"[-O,--out_of_place <0/1>] \n\t"
|
|
"[-q,--delay <delay between out-of-place and in-place in "
|
|
"microseconds>] \n\t"
|
|
"[-F,--cache_flush <number of iterations between instruction cache "
|
|
"flush>] \n\t"
|
|
"[-E,--rotating_tensor <0/1>] \n\t"
|
|
"[-x,--output_file <output file name>] \n\t"
|
|
"[-Z,--output_format <output format <csv|json>] \n\t"
|
|
"[-h,--help]\n",
|
|
basename(argv[0]));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
CUDACHECK(cudaGetDeviceCount(&numDevices));
|
|
#ifndef MPI_SUPPORT
|
|
if(nGpus > numDevices)
|
|
{
|
|
fprintf(stderr,
|
|
"[ERROR] The number of requested GPUs (%d) is greater than the number of "
|
|
"GPUs available (%d)\n",
|
|
nGpus, numDevices);
|
|
return testNcclError;
|
|
}
|
|
#endif
|
|
if(minBytes > maxBytes)
|
|
{
|
|
fprintf(stderr, "invalid sizes for 'minbytes' and 'maxbytes': %llu > %llu\n",
|
|
(unsigned long long) minBytes, (unsigned long long) maxBytes);
|
|
return -1;
|
|
}
|
|
if(!output_format.empty())
|
|
{
|
|
if(!(output_format == "csv" || output_format == "json"))
|
|
{
|
|
std::cerr << "Invalid --output_format: " << output_format << "\n";
|
|
return -1;
|
|
}
|
|
}
|
|
#ifdef MPI_SUPPORT
|
|
MPI_Init(&argc, &argv);
|
|
#endif
|
|
|
|
TESTCHECK(run());
|
|
return 0;
|
|
}
|
|
|
|
#ifdef MPI_SUPPORT
|
|
// parse int for base 2/10/16, will ignore first whitespaces
|
|
static bool
|
|
parseInt(char* s, int* num)
|
|
{
|
|
char* p = NULL;
|
|
if(!s || !num) return false;
|
|
while(*s && isspace(*s))
|
|
++s;
|
|
if(!*s) return false;
|
|
|
|
if(strncasecmp(s, "0b", 2) == 0)
|
|
*num = (int) strtoul(s + 2, &p, 2);
|
|
else
|
|
*num = (int) strtoul(s, &p, 0);
|
|
|
|
if(p == s) return false;
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
testResult_t
|
|
run()
|
|
{
|
|
int totalProcs = 1, proc = 0, ncclProcs = 1, ncclProc = 0, color = 0;
|
|
int localRank = 0;
|
|
int localSize = 0;
|
|
char hostname[1024];
|
|
getHostName(hostname, 1024);
|
|
|
|
#ifdef MPI_SUPPORT
|
|
MPI_Comm_size(MPI_COMM_WORLD, &totalProcs);
|
|
MPI_Comm_rank(MPI_COMM_WORLD, &proc);
|
|
std::vector<uint64_t> hostHashs(totalProcs);
|
|
hostHashs[proc] = getHostHash(hostname);
|
|
MPI_Allgather(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL, hostHashs.data(), sizeof(uint64_t),
|
|
MPI_BYTE, MPI_COMM_WORLD);
|
|
for(int p = 0; p < totalProcs; p++)
|
|
{
|
|
if(p == proc) break;
|
|
if(hostHashs[p] == hostHashs[proc]) localRank++;
|
|
}
|
|
|
|
char* splitMaskEnv = NULL;
|
|
if((splitMaskEnv = getenv("NCCL_TESTS_SPLIT_MASK")))
|
|
{
|
|
color = proc & strtoul(splitMaskEnv, NULL, 16);
|
|
}
|
|
else if((splitMaskEnv = getenv("NCCL_TESTS_SPLIT")))
|
|
{
|
|
if((strncasecmp(splitMaskEnv, "AND", strlen("AND")) == 0 &&
|
|
parseInt(splitMaskEnv + strlen("AND"), &color)) ||
|
|
(strncasecmp(splitMaskEnv, "&", strlen("&")) == 0 &&
|
|
parseInt(splitMaskEnv + strlen("&"), &color)))
|
|
color = proc & color;
|
|
if((strncasecmp(splitMaskEnv, "OR", strlen("OR")) == 0 &&
|
|
parseInt(splitMaskEnv + strlen("OR"), &color)) ||
|
|
(strncasecmp(splitMaskEnv, "|", strlen("|")) == 0 &&
|
|
parseInt(splitMaskEnv + strlen("|"), &color)))
|
|
color = proc | color;
|
|
if((strncasecmp(splitMaskEnv, "MOD", strlen("MOD")) == 0 &&
|
|
parseInt(splitMaskEnv + strlen("MOD"), &color)) ||
|
|
(strncasecmp(splitMaskEnv, "%", strlen("%")) == 0 &&
|
|
parseInt(splitMaskEnv + strlen("%"), &color)))
|
|
color = proc % color;
|
|
if((strncasecmp(splitMaskEnv, "DIV", strlen("DIV")) == 0 &&
|
|
parseInt(splitMaskEnv + strlen("DIV"), &color)) ||
|
|
(strncasecmp(splitMaskEnv, "/", strlen("/")) == 0 &&
|
|
parseInt(splitMaskEnv + strlen("/"), &color)))
|
|
color = proc / color;
|
|
}
|
|
|
|
MPI_Comm mpi_comm;
|
|
MPI_Comm_split(MPI_COMM_WORLD, color, proc, &mpi_comm);
|
|
MPI_Comm_size(mpi_comm, &ncclProcs);
|
|
MPI_Comm_rank(mpi_comm, &ncclProc);
|
|
|
|
for(int p = 0; p < totalProcs; p++)
|
|
{
|
|
if(hostHashs[p] == hostHashs[proc]) localSize++;
|
|
}
|
|
if(nGpus * localSize > numDevices && numDevices != 1)
|
|
{
|
|
fprintf(stderr,
|
|
"[ERROR] The number of requested GPUs (%d) is greater than the number of "
|
|
"GPUs available (%d) on node (%s)\n",
|
|
nGpus * localSize, numDevices, hostname);
|
|
return testNcclError;
|
|
}
|
|
#endif
|
|
is_main_thread = is_main_proc = (proc == 0) ? 1 : 0;
|
|
|
|
PRINT("# nThread %d nGpus %d minBytes %ld maxBytes %ld step: %ld(%s) warmup iters: "
|
|
"%d iters: %d agg iters: %d validation: %d graph: %d\n",
|
|
nThreads, nGpus, minBytes, maxBytes, (stepFactor > 1) ? stepFactor : stepBytes,
|
|
(stepFactor > 1) ? "factor" : "bytes", warmup_iters, iters, agg_iters,
|
|
datacheck, cudaGraphLaunches);
|
|
if(blocking_coll)
|
|
PRINT("# Blocking Enabled: wait for completion and barrier after each collective "
|
|
"\n");
|
|
if(parallel_init)
|
|
PRINT("# Parallel Init Enabled: threads call into NcclInitRank concurrently \n");
|
|
PRINT("#\n");
|
|
PRINT("rccl-tests: Version %s\n", rcclTestsGitHash);
|
|
PRINT("# Using devices\n");
|
|
#define MAX_LINE 2048
|
|
char line[MAX_LINE];
|
|
int len = 0;
|
|
size_t maxMem = ~0;
|
|
char* envstr = getenv("NCCL_TESTS_DEVICE");
|
|
int gpu0 = envstr ? atoi(envstr) : -1;
|
|
for(int i = 0; i < nThreads * nGpus; i++)
|
|
{
|
|
int cudaDev =
|
|
((gpu0 != -1 ? gpu0 : localRank * nThreads * nGpus) + i) % numDevices;
|
|
int rank = proc * nThreads * nGpus + i;
|
|
cudaDeviceProp prop;
|
|
CUDACHECK(cudaGetDeviceProperties(&prop, cudaDev));
|
|
// char busIdStr[] = "00000000:00:00.0";
|
|
// CUDACHECK(cudaDeviceGetPCIBusId(busIdStr, sizeof(busIdStr), cudaDev));
|
|
// len += snprintf(line+len, MAX_LINE>len ? MAX_LINE-len : 0, "# Rank %2d Group
|
|
// %2d Pid %6d on %10s device %2d [%04x:%s:%02x] %s\n",
|
|
// rank, color, getpid(), hostname, cudaDev, prop.pciDomainID,
|
|
// busIdStr, prop.pciDeviceID, prop.name);
|
|
len += snprintf(
|
|
line + len, MAX_LINE > len ? MAX_LINE - len : 0,
|
|
"# Rank %2d Group %2d Pid %6d on %10s device %2d [%04x:%02x:%02x] %s\n",
|
|
rank, color, getpid(), hostname, cudaDev, prop.pciDomainID, prop.pciBusID,
|
|
prop.pciDeviceID, prop.name);
|
|
maxMem = std::min(maxMem, prop.totalGlobalMem);
|
|
}
|
|
#if MPI_SUPPORT
|
|
char* lines = (proc == 0) ? (char*) malloc(totalProcs * MAX_LINE) : NULL;
|
|
// Gather all output in rank order to root (0)
|
|
MPI_Gather(line, MAX_LINE, MPI_BYTE, lines, MAX_LINE, MPI_BYTE, 0, MPI_COMM_WORLD);
|
|
if(proc == 0)
|
|
{
|
|
for(int p = 0; p < totalProcs; p++)
|
|
PRINT("%s", lines + MAX_LINE * p);
|
|
free(lines);
|
|
}
|
|
MPI_Allreduce(MPI_IN_PLACE, &maxMem, 1, MPI_LONG, MPI_MIN, MPI_COMM_WORLD);
|
|
#else
|
|
PRINT("%s", line);
|
|
#endif
|
|
|
|
// We need sendbuff, recvbuff, expected (when datacheck enabled), plus 1G for the
|
|
// rest.
|
|
size_t memMaxBytes = (maxMem - (1 << 30)) / (datacheck ? 3 : 2);
|
|
if(maxBytes > memMaxBytes)
|
|
{
|
|
maxBytes = memMaxBytes;
|
|
if(proc == 0)
|
|
printf("#\n# Reducing maxBytes to %ld due to memory limitation\n", maxBytes);
|
|
}
|
|
|
|
ncclUniqueId ncclId;
|
|
if(ncclProc == 0)
|
|
{
|
|
NCCLCHECK(ncclGetUniqueId(&ncclId));
|
|
}
|
|
#ifdef MPI_SUPPORT
|
|
MPI_Bcast(&ncclId, sizeof(ncclId), MPI_BYTE, 0, mpi_comm);
|
|
MPI_Barrier(MPI_COMM_WORLD); // Ensure Bcast is complete for HCOLL
|
|
#endif
|
|
|
|
std::vector<int> gpus(nGpus * nThreads);
|
|
std::vector<cudaStream_t> streams(nGpus * nThreads);
|
|
std::vector<void*> sendbuffs(nGpus * nThreads);
|
|
std::vector<void*> recvbuffs(nGpus * nThreads);
|
|
std::vector<void*> expected(nGpus * nThreads);
|
|
size_t sendBytes, recvBytes;
|
|
|
|
ncclTestEngine.getBuffSize(&sendBytes, &recvBytes, (size_t) maxBytes,
|
|
(size_t) ncclProcs * nGpus * nThreads);
|
|
|
|
envstr = getenv("NCCL_TESTS_DEVICE");
|
|
gpu0 = envstr ? atoi(envstr) : -1;
|
|
for(int i = 0; i < nGpus * nThreads; i++)
|
|
{
|
|
gpus[i] = ((gpu0 != -1 ? gpu0 : localRank * nThreads * nGpus) + i) % numDevices;
|
|
CUDACHECK(cudaSetDevice(gpus[i]));
|
|
TESTCHECK(AllocateBuffs(sendbuffs.data() + i, sendBytes, recvbuffs.data() + i,
|
|
recvBytes, expected.data() + i, (size_t) maxBytes));
|
|
if(streamnull)
|
|
streams[i] = NULL;
|
|
else
|
|
CUDACHECK(
|
|
cudaStreamCreateWithFlags(streams.data() + i, cudaStreamNonBlocking));
|
|
}
|
|
|
|
// if parallel init is not selected, use main thread to initialize NCCL
|
|
ncclComm_t* comms = (ncclComm_t*) malloc(sizeof(ncclComm_t) * nThreads * nGpus);
|
|
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 19, 0)
|
|
void** sendRegHandles = NULL;
|
|
void** recvRegHandles = NULL;
|
|
#endif
|
|
if(!parallel_init)
|
|
{
|
|
if(ncclProcs == 1)
|
|
{
|
|
NCCLCHECK(ncclCommInitAll(comms, nGpus * nThreads, gpus.data()));
|
|
}
|
|
else
|
|
{
|
|
NCCLCHECK(ncclGroupStart());
|
|
for(int i = 0; i < nGpus * nThreads; i++)
|
|
{
|
|
CUDACHECK(cudaSetDevice(gpus[i]));
|
|
NCCLCHECK(ncclCommInitRank(comms + i, ncclProcs * nThreads * nGpus,
|
|
ncclId, ncclProc * nThreads * nGpus + i));
|
|
}
|
|
NCCLCHECK(ncclGroupEnd());
|
|
}
|
|
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 19, 0)
|
|
sendRegHandles = (local_register)
|
|
? (void**) malloc(sizeof(*sendRegHandles) * nThreads * nGpus)
|
|
: NULL;
|
|
recvRegHandles = (local_register)
|
|
? (void**) malloc(sizeof(*recvRegHandles) * nThreads * nGpus)
|
|
: NULL;
|
|
for(int i = 0; i < nGpus * nThreads; i++)
|
|
{
|
|
if(local_register)
|
|
NCCLCHECK(ncclCommRegister(comms[i], &sendbuffs[i], maxBytes,
|
|
&sendRegHandles[i]));
|
|
if(local_register)
|
|
NCCLCHECK(ncclCommRegister(comms[i], &recvbuffs[i], maxBytes,
|
|
&recvRegHandles[i]));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
std::vector<int> errors(nThreads);
|
|
std::vector<double> bw(nThreads);
|
|
double* delta;
|
|
CUDACHECK(hipHostMalloc(&delta, sizeof(double) * nThreads * NUM_BLOCKS,
|
|
cudaHostAllocPortable | cudaHostAllocMapped));
|
|
std::vector<int> bw_count(nThreads);
|
|
for(int t = 0; t < nThreads; t++)
|
|
{
|
|
bw[t] = 0.0;
|
|
errors[t] = bw_count[t] = 0;
|
|
}
|
|
|
|
fflush(stdout);
|
|
|
|
const char* timeStr = report_cputime ? "cputime" : "time";
|
|
PRINT("#\n");
|
|
if(enable_out_of_place && enable_in_place)
|
|
{
|
|
PRINT("# %10s %12s %8s %6s %6s out-of-place "
|
|
"in-place \n",
|
|
"", "", "", "", "");
|
|
PRINT("# %10s %12s %8s %6s %6s %7s %6s %6s %6s %7s %6s %6s %6s\n",
|
|
"size", "count", "type", "redop", "root", timeStr, "algbw", "busbw",
|
|
"#wrong", timeStr, "algbw", "busbw", "#wrong");
|
|
PRINT("# %10s %12s %8s %6s %6s %7s %6s %6s %5s %7s %6s %6s %5s\n",
|
|
"(B)", "(elements)", "", "", "", "(us)", "(GB/s)", "(GB/s)", "", "(us)",
|
|
"(GB/s)", "(GB/s)", "");
|
|
}
|
|
else if(enable_out_of_place)
|
|
{
|
|
PRINT("# %10s %12s %8s %6s %6s out-of-place \n", "", "", "",
|
|
"", "");
|
|
PRINT("# %10s %12s %8s %6s %6s %7s %6s %6s %6s\n", "size", "count", "type",
|
|
"redop", "root", timeStr, "algbw", "busbw", "#wrong");
|
|
PRINT("# %10s %12s %8s %6s %6s %7s %6s %6s %5s\n", "(B)", "(elements)",
|
|
"", "", "", "(us)", "(GB/s)", "(GB/s)", "");
|
|
}
|
|
else
|
|
{
|
|
PRINT("# %10s %12s %8s %6s %6s in-place \n", "", "", "",
|
|
"", "");
|
|
PRINT("# %10s %12s %8s %6s %6s %7s %6s %6s %6s\n", "size", "count", "type",
|
|
"redop", "root", timeStr, "algbw", "busbw", "#wrong");
|
|
PRINT("# %10s %12s %8s %6s %6s %7s %6s %6s %5s\n", "(B)", "(elements)",
|
|
"", "", "", "(us)", "(GB/s)", "(GB/s)", "");
|
|
}
|
|
Reporter reporter(output_file, output_format);
|
|
|
|
std::vector<testThread> threads(nThreads);
|
|
memset(threads.data(), 0, sizeof(struct testThread) * nThreads);
|
|
|
|
for(int t = nThreads - 1; t >= 0; t--)
|
|
{
|
|
threads[t].args.minbytes = minBytes;
|
|
threads[t].args.maxbytes = maxBytes;
|
|
threads[t].args.stepbytes = stepBytes;
|
|
threads[t].args.stepfactor = stepFactor;
|
|
threads[t].args.localRank = localRank;
|
|
|
|
threads[t].args.totalProcs = totalProcs;
|
|
threads[t].args.nProcs = ncclProcs;
|
|
threads[t].args.proc = ncclProc;
|
|
threads[t].args.nThreads = nThreads;
|
|
threads[t].args.thread = t;
|
|
threads[t].args.nGpus = nGpus;
|
|
threads[t].args.gpus = gpus.data() + t * nGpus;
|
|
threads[t].args.sendbuffs = sendbuffs.data() + t * nGpus;
|
|
threads[t].args.recvbuffs = recvbuffs.data() + t * nGpus;
|
|
threads[t].args.expected = expected.data() + t * nGpus;
|
|
threads[t].args.ncclId = ncclId;
|
|
threads[t].args.comms = comms + t * nGpus;
|
|
threads[t].args.streams = streams.data() + t * nGpus;
|
|
threads[t].args.enable_out_of_place = enable_out_of_place;
|
|
threads[t].args.enable_in_place = enable_in_place;
|
|
threads[t].args.enable_cache_flush = enable_cache_flush;
|
|
threads[t].args.enable_rotating_tensor = enable_rotating_tensor;
|
|
threads[t].args.errors = errors.data() + t;
|
|
threads[t].args.bw = bw.data() + t;
|
|
threads[t].args.bw_count = bw_count.data() + t;
|
|
|
|
threads[t].args.reportErrors = datacheck;
|
|
threads[t].args.reporter = &reporter;
|
|
|
|
threads[t].func = parallel_init ? threadInit : threadRunTests;
|
|
if(t)
|
|
TESTCHECK(threadLaunch(threads.data() + t));
|
|
else
|
|
TESTCHECK(threads[t].func(&threads[t].args));
|
|
}
|
|
|
|
// Wait for other threads and accumulate stats and errors
|
|
for(int t = nThreads - 1; t >= 0; t--)
|
|
{
|
|
if(t) pthread_join(threads[t].thread, NULL);
|
|
TESTCHECK(threads[t].ret);
|
|
if(t)
|
|
{
|
|
errors[0] += errors[t];
|
|
bw[0] += bw[t];
|
|
bw_count[0] += bw_count[t];
|
|
}
|
|
}
|
|
|
|
#ifdef MPI_SUPPORT
|
|
MPI_Allreduce(MPI_IN_PLACE, &errors[0], 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
|
|
#endif
|
|
|
|
if(!parallel_init)
|
|
{
|
|
for(int i = 0; i < nGpus * nThreads; ++i)
|
|
{
|
|
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 19, 0)
|
|
if(local_register) NCCLCHECK(ncclCommDeregister(comms[i], sendRegHandles[i]));
|
|
if(local_register) NCCLCHECK(ncclCommDeregister(comms[i], recvRegHandles[i]));
|
|
#endif
|
|
NCCLCHECK(ncclCommDestroy(comms[i]));
|
|
}
|
|
free(comms);
|
|
}
|
|
|
|
// Free off CUDA allocated memory
|
|
for(int i = 0; i < nGpus * nThreads; i++)
|
|
{
|
|
if(sendbuffs[i]) CUDACHECK(cudaFree((char*) sendbuffs[i]));
|
|
if(recvbuffs[i]) CUDACHECK(cudaFree((char*) recvbuffs[i]));
|
|
if(datacheck) CUDACHECK(cudaFree(expected[i]));
|
|
}
|
|
CUDACHECK(cudaFreeHost(delta));
|
|
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 19, 0)
|
|
free(sendRegHandles);
|
|
free(recvRegHandles);
|
|
#endif
|
|
|
|
envstr = getenv("NCCL_TESTS_MIN_BW");
|
|
double check_avg_bw = envstr ? atof(envstr) : -1;
|
|
bw[0] /= bw_count[0];
|
|
|
|
if(datacheck)
|
|
PRINT("# Errors with asterisks indicate errors that have exceeded the maximum "
|
|
"threshold.\n");
|
|
PRINT("# Out of bounds values : %d %s\n", errors[0], errors[0] ? "FAILED" : "OK");
|
|
PRINT("# Avg bus bandwidth : %g %s\n", bw[0],
|
|
check_avg_bw == -1 ? "" : (bw[0] < check_avg_bw * (0.9) ? "FAILED" : "OK"));
|
|
PRINT("#\n");
|
|
#ifdef MPI_SUPPORT
|
|
MPI_Comm_free(&mpi_comm);
|
|
MPI_Finalize();
|
|
#endif
|
|
|
|
// 'cuda-memcheck --leak-check full' requires this
|
|
PRINT("%s\n", ncclGetLastError(NULL));
|
|
cudaDeviceReset();
|
|
|
|
if(errors[0] || bw[0] < check_avg_bw * (0.9))
|
|
exit(EXIT_FAILURE);
|
|
else
|
|
exit(EXIT_SUCCESS);
|
|
}
|