RCCL 2.4 update

This commit is contained in:
Wenkai Du
2019-07-05 15:43:00 -07:00
rodzic 4d579e51cc
commit f11c8f60cd
95 zmienionych plików z 7829 dodań i 614 usunięć
+72
Wyświetl plik
@@ -0,0 +1,72 @@
cmake_minimum_required(VERSION 2.8.12)
if(BUILD_TESTS)
message("Going to build unit tests (Installed in /test/UnitTests)")
# chrpath is required to properly set rpath for the UnitTests executable
find_program(CHRPATH chrpath)
if(NOT CHRPATH)
message(FATAL_ERROR "chrpath is required for UnitTests. Please install (e.g. sudo apt-get install chrpath)")
endif()
# OpenMP is used to drive GPUs (one per thread)
find_package(OpenMP REQUIRED)
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
# Download and unpack googletest at configure time
configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt)
execute_process(
COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
RESULT_VARIABLE result
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download)
if(result)
message(FATAL_ERROR "CMake step for googletest failed: ${result}")
endif()
execute_process(
COMMAND ${CMAKE_COMMAND} --build .
RESULT_VARIABLE result
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download)
if(result)
message(FATAL_ERROR "Build step for googletest failed: ${result}")
endif()
# Add googletest directly to our build. This adds the following targets:
# gtest, gtest_main, gmock and gmock_main
add_subdirectory("${CMAKE_BINARY_DIR}/googletest-src"
"${CMAKE_BINARY_DIR}/googletest-build")
# Add googletest directly to our build. This defines the gtest and gtest_main
# targets. add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src
# ${CMAKE_CURRENT_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL)
# Collect source files for tests
set(TEST_SOURCES
test_AllGather.cpp
test_AllReduce.cpp
test_Broadcast.cpp
test_Reduce.cpp
test_ReduceScatter.cpp
test_GroupCalls.cpp
test_CombinedCalls.cpp
test_AllReduceAbort.cpp
test_BroadcastAbort.cpp
)
add_executable(UnitTests ${TEST_SOURCES})
target_include_directories(UnitTests PRIVATE /opt/rocm)
target_link_libraries(UnitTests PRIVATE gtest_main PRIVATE rccl)
install(TARGETS UnitTests RUNTIME DESTINATION test)
# HCC adds /opt/rocm/lib as RPATH, even though the install process is supposed to
# remove RPATH. As a work-around, set the correct RPATH for the unit test executable
# as a post-install step
install(
CODE
"execute_process(COMMAND chrpath -r ${CMAKE_INSTALL_PREFIX}/lib:/opt/rocm/lib ${CMAKE_INSTALL_PREFIX}/test/UnitTests)"
)
else()
message("Not building unit tests")
endif()
+15
Wyświetl plik
@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 2.8.2)
project(googletest-download NONE)
include(ExternalProject)
ExternalProject_Add(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.8.1
SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src"
BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)
+360
Wyświetl plik
@@ -0,0 +1,360 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#ifndef CORRECTNESSTEST_HPP
#define CORRECTNESSTEST_HPP
#include <cstdio>
#include <tuple>
#include <vector>
#include <gtest/gtest.h>
#include "rccl.h"
#define HIP_CALL(x) ASSERT_EQ(x, hipSuccess)
#define NCCL_CALL(x) ASSERT_EQ(x, ncclSuccess)
namespace CorrectnessTests
{
// Performs the various basic reduction operations
template <typename T>
T ReduceOp(ncclRedOp_t const op, T const A, T const B)
{
switch (op)
{
case ncclSum: return A + B;
case ncclProd: return A * B;
case ncclMax: return std::max(A, B);
case ncclMin: return std::min(A, B);
default:
fprintf(stderr, "[ERROR] Unsupported reduction operator (%d)\n", op);
exit(0);
}
}
// Returns the number of bytes per element for each supported datatype
static int DataTypeToBytes(ncclDataType_t const dataType)
{
switch (dataType)
{
case ncclInt8: return 1;
case ncclUint8: return 1;
case ncclInt32: return 4;
case ncclUint32: return 4;
case ncclInt64: return 8;
case ncclUint64: return 8;
case ncclFloat16: return 2;
case ncclFloat32: return 4;
case ncclFloat64: return 8;
default:
fprintf(stderr, "[ERROR] Unsupported datatype (%d)\n", dataType);
exit(0);
}
}
// Encapsulates all the memory used per devices for collectives, as well as reference results
struct Dataset
{
int numDevices; // Number of devices participating
size_t numElements; // Number of elements per array
ncclDataType_t dataType; // Data type of each input/output pointer
bool inPlace; // Whether or not output pointers are same as input pointers
std::vector<void *> inputs; // Input pointers (1 per device)
std::vector<void *> outputs; // Output pointers (1 per device)
// May be identical to input pointers for in-place tests
std::vector<void *> expected; // Expected output (1 per device)
size_t NumBytes() const
{
return numElements * DataTypeToBytes(dataType);
}
void Initialize(int const numDevices_,
size_t const numElements_,
ncclDataType_t const dataType_,
bool const inPlace_)
{
numDevices = numDevices_;
numElements = numElements_;
dataType = dataType_;
inPlace = inPlace_;
inputs.resize(numDevices);
outputs.resize(numDevices);
expected.resize(numDevices);
// Allocate per-device memory
size_t const numBytes = NumBytes();
for (int i = 0; i < numDevices; i++)
{
HIP_CALL(hipSetDevice(i));
HIP_CALL(hipMalloc((void **)&inputs[i], numBytes));
if (inPlace)
outputs[i] = inputs[i];
else
HIP_CALL(hipMalloc((void **)&outputs[i], numBytes));
expected[i] = malloc(numBytes);
}
}
// Explicit memory release to avoid double-free from subDatasets
void Release()
{
for (int i = 0; i < outputs.size(); i++)
{
if (!inPlace) hipFree(outputs[i]);
hipFree(inputs[i]);
free(expected[i]);
}
outputs.clear();
}
// Creates a dataset by pointing to an existing dataset
// Primarily to allow for testing with different starting byte-alignments
void ExtractSubDataset(size_t const startElement,
size_t const lastElement,
Dataset& subDataset)
{
ASSERT_LE(startElement, lastElement);
ASSERT_LT(lastElement, numElements);
subDataset.numDevices = numDevices;
subDataset.numElements = lastElement - startElement + 1;
subDataset.dataType = dataType;
subDataset.inPlace = inPlace;
subDataset.inputs.resize(numDevices);
subDataset.outputs.resize(numDevices);
subDataset.expected.resize(numDevices);
size_t const byteOffset = (startElement * DataTypeToBytes(dataType));
for (int i = 0; i < numDevices; i++)
{
subDataset.inputs[i] = (int8_t *)inputs[i] + byteOffset;
subDataset.outputs[i] = (int8_t *)outputs[i] + byteOffset;
subDataset.expected[i] = (int8_t *)expected[i] + byteOffset;
}
}
};
typedef std::tuple<ncclRedOp_t /* op */,
ncclDataType_t /* dataType */,
size_t /* numElements */,
int /* numDevices */,
bool /* inPlace */> TestTuple;
// Base class for each collective test
// - Each test is instantiated with a different TestTuple
class CorrectnessTest : public testing::TestWithParam<TestTuple>
{
protected:
// This code is called per test-tuple
void SetUp() override
{
// Check for fine-grained env variable (otherwise will hang)
if (!getenv("HSA_FORCE_FINE_GRAIN_PCIE"))
{
printf("Must set HSA_FORCE_FINE_GRAIN_PCIE=1 prior to execution\n");
exit(0);
}
// Make the test tuple parameters accessible
std::tie(op, dataType, numElements, numDevices, inPlace) = GetParam();
// Collect the number of available GPUs
HIP_CALL(hipGetDeviceCount(&numDevicesAvailable));
// Only proceed with testing if there are enough GPUs
if (numDevices > numDevicesAvailable)
{
fprintf(stdout, "[ SKIPPED ] Test requires %d devices (only %d available)\n",
numDevices, numDevicesAvailable);
// Modify the number of devices so that tear-down doesn't occur
// This is temporary until GTEST_SKIP() becomes available
numDevices = 0;
numDevicesAvailable = -1;
return;
}
// Initialize communicators
comms.resize(numDevices);
NCCL_CALL(ncclCommInitAll(comms.data(), numDevices, NULL));
// Create streams
streams.resize(numDevices);
for (int i = 0; i < numDevices; i++)
{
HIP_CALL(hipSetDevice(i));
HIP_CALL(hipStreamCreate(&streams[i]));
}
}
// Clean up per TestTuple
void TearDown() override
{
// Release communicators and streams
for (int i = 0; i < numDevices; i++)
{
NCCL_CALL(ncclCommDestroy(comms[i]));
HIP_CALL(hipStreamDestroy(streams[i]));
}
}
void FillDatasetWithPattern(Dataset& dataset)
{
int8_t* arrayI1 = (int8_t *)malloc(dataset.NumBytes());
uint8_t* arrayU1 = (uint8_t *)arrayI1;
int32_t* arrayI4 = (int32_t *)arrayI1;
uint32_t* arrayU4 = (uint32_t *)arrayI1;
int64_t* arrayI8 = (int64_t *)arrayI1;
uint64_t* arrayU8 = (uint64_t *)arrayI1;
float* arrayF4 = (float *)arrayI1;
double* arrayF8 = (double *)arrayI1;
// NOTE: Currently half-precision float tests are unsupported due to half being supported
// on GPU only and not host
// Fills input data[i][j] with (i + j) % 6
// - Keeping range small to reduce likelihood of overflow
// - Sticking with floating points values that are perfectly representable
for (int i = 0; i < dataset.numDevices; i++)
{
for (int j = 0; j < dataset.numElements; j++)
{
int valueI = (i + j) % 6;
float valueF = (float)valueI;
switch (dataset.dataType)
{
case ncclInt8: arrayI1[j] = valueI; break;
case ncclUint8: arrayU1[j] = valueI; break;
case ncclInt32: arrayI4[j] = valueI; break;
case ncclUint32: arrayU4[j] = valueI; break;
case ncclInt64: arrayI8[j] = valueI; break;
case ncclUint64: arrayU8[j] = valueI; break;
case ncclFloat32: arrayF4[j] = valueF; break;
case ncclFloat64: arrayF8[j] = valueF; break;
default:
fprintf(stderr, "[ERROR] Unsupported datatype\n");
exit(0);
}
}
HIP_CALL(hipSetDevice(i));
HIP_CALL(hipMemcpy(dataset.inputs[i], arrayI1, dataset.NumBytes(), hipMemcpyHostToDevice));
// Fills output data[i][j] with 0 (if not inplace)
if (!dataset.inPlace)
HIP_CALL(hipMemset(dataset.outputs[i], 0, dataset.NumBytes()));
}
free(arrayI1);
}
void Synchronize() const
{
// Wait for reduction to complete
for (int i = 0; i < numDevices; i++)
{
HIP_CALL(hipSetDevice(i));
HIP_CALL(hipStreamSynchronize(streams[i]));
}
}
void ValidateResults(Dataset const& dataset) const
{
int8_t* outputI1 = (int8_t *)malloc(dataset.NumBytes());
uint8_t* outputU1 = (uint8_t *)outputI1;
int32_t* outputI4 = (int32_t *)outputI1;
uint32_t* outputU4 = (uint32_t *)outputI1;
int64_t* outputI8 = (int64_t *)outputI1;
uint64_t* outputU8 = (uint64_t *)outputI1;
float* outputF4 = (float *)outputI1;
double* outputF8 = (double *)outputI1;
bool isMatch = true;
// Loop over each device's output and compare it to the expected output
// (Each collective operation computes its own expected results)
for (int i = 0; i < dataset.numDevices && isMatch; i++)
{
HIP_CALL(hipMemcpy(outputI1, dataset.outputs[i], dataset.NumBytes(), hipMemcpyDeviceToHost));
int8_t* expectedI1 = (int8_t *)dataset.expected[i];
uint8_t* expectedU1 = (uint8_t *)expectedI1;
int32_t* expectedI4 = (int32_t *)expectedI1;
uint32_t* expectedU4 = (uint32_t *)expectedI1;
int64_t* expectedI8 = (int64_t *)expectedI1;
uint64_t* expectedU8 = (uint64_t *)expectedI1;
float* expectedF4 = (float *)expectedI1;
double* expectedF8 = (double *)expectedI1;
for (int j = 0; j < dataset.numElements && isMatch; j++)
{
switch (dataset.dataType)
{
case ncclInt8: isMatch &= (outputI1[j] == expectedI1[j]); break;
case ncclUint8: isMatch &= (outputU1[j] == expectedU1[j]); break;
case ncclInt32: isMatch &= (outputI4[j] == expectedI4[j]); break;
case ncclUint32: isMatch &= (outputU4[j] == expectedU4[j]); break;
case ncclInt64: isMatch &= (outputI8[j] == expectedI8[j]); break;
case ncclUint64: isMatch &= (outputU8[j] == expectedU8[j]); break;
case ncclFloat32: isMatch &= (outputF4[j] == expectedF4[j]); break;
case ncclFloat64: isMatch &= (outputF8[j] == expectedF8[j]); break;
default:
fprintf(stderr, "[ERROR] Unsupported datatype\n");
exit(0);
}
if (!isMatch)
{
switch (dataset.dataType)
{
case ncclInt8:
printf("Expected %d. Output %d on device %d[%d]\n", outputI1[j], expectedI1[j], i, j); break;
case ncclUint8:
printf("Expected %u. Output %u on device %d[%d]\n", outputU1[j], expectedU1[j], i, j); break;
case ncclInt32:
printf("Expected %d. Output %d on device %d[%d]\n", outputI4[j], expectedI4[j], i, j); break;
case ncclUint32:
printf("Expected %u. Output %u on device %d[%d]\n", outputU4[j], expectedU4[j], i, j); break;
case ncclInt64:
printf("Expected %ld. Output %ld on device %d[%d]\n", outputI8[j], expectedI8[j], i, j); break;
case ncclUint64:
printf("Expected %lu. Output %lu on device %d[%d]\n", outputU8[j], expectedU8[j], i, j); break;
case ncclFloat32:
printf("Expected %f. Output %f on device %d[%d]\n", outputF4[j], expectedF4[j], i, j); break;
case ncclFloat64:
printf("Expected %lf. Output %lf on device %d[%d]\n", outputF8[j], expectedF8[j], i, j); break;
default:
fprintf(stderr, "[ERROR] Unsupported datatype\n");
exit(0);
}
}
}
ASSERT_EQ(isMatch, true);
}
}
// Passed in parameters from TestTuple
ncclRedOp_t op;
ncclDataType_t dataType;
size_t numElements;
int numDevices;
bool inPlace;
int numDevicesAvailable;
std::vector<ncclComm_t> comms;
std::vector<hipStream_t> streams;
};
}
#endif
+111
Wyświetl plik
@@ -0,0 +1,111 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#include "test_AllGather.hpp"
#include <omp.h>
namespace CorrectnessTests
{
TEST_P(AllGatherCorrectnessTest, Correctness)
{
if (numDevices > numDevicesAvailable) return;
if (numElements % numDevices != 0) return;
// Prepare input / output / expected results
Dataset dataset;
dataset.Initialize(numDevices, numElements, dataType, inPlace);
FillDatasetWithPattern(dataset);
ComputeExpectedResults(dataset);
size_t const byteCount = dataset.NumBytes() / dataset.numDevices;
size_t const sendCount = dataset.numElements / dataset.numDevices;
// Launch the reduction (1 thread per GPU)
#pragma omp parallel for num_threads(numDevices)
for (int i = 0; i < numDevices; i++)
{
ncclAllGather((int8_t *)dataset.inputs[i] + (i * byteCount),
dataset.outputs[i], sendCount,
dataType, comms[i], streams[i]);
}
// Wait for reduction to complete
Synchronize();
// Check results
ValidateResults(dataset);
dataset.Release();
}
TEST_P(AllGatherCorrectnessTest, Alignment)
{
if (numDevices > numDevicesAvailable) return;
if (numElements % numDevices != 0) return;
// Allocate dataset
Dataset dataset;
dataset.Initialize(numDevices, numElements, dataType, inPlace);
// Loop over several offsets (so that device pointers are not aligned)
for (int firstElement = 1; firstElement <= 11; firstElement += 2)
{
if (firstElement < numElements)
{
// Select last element so that total number of elements is multiple of numDevices
int const lastElement = firstElement + ((numElements - firstElement) / numDevices) * numDevices - 1;
if (lastElement >= numElements) break;
Dataset subDataset;
dataset.ExtractSubDataset(firstElement, lastElement, subDataset);
// Compute reference results for sub-dataset
FillDatasetWithPattern(subDataset);
ComputeExpectedResults(subDataset);
size_t const byteCount = subDataset.NumBytes() / subDataset.numDevices;
size_t const sendCount = subDataset.numElements / subDataset.numDevices;
// Launch the reduction (1 thread per GPU)
#pragma omp parallel for num_threads(numDevices)
for (int i = 0; i < numDevices; i++)
{
ncclAllGather((int8_t *)subDataset.inputs[i] + (i * byteCount),
subDataset.outputs[i], sendCount,
dataType, comms[i], streams[i]);
}
// Wait for reduction to complete
Synchronize();
// Check results
ValidateResults(subDataset);
}
}
dataset.Release();
}
INSTANTIATE_TEST_CASE_P(AllGatherCorrectnessSweep,
AllGatherCorrectnessTest,
testing::Combine(
// Reduction operator (not used)
testing::Values(ncclSum),
// Data types
testing::Values(ncclInt8,
ncclUint8,
ncclInt32,
ncclUint32,
ncclInt64,
ncclUint64,
//ncclFloat16,
ncclFloat32,
ncclFloat64),
// Number of elements
testing::Values(3072, 3145728),
// Number of devices
testing::Values(2,3,4),
// In-place or not
testing::Values(false, true)));
} // namespace
+32
Wyświetl plik
@@ -0,0 +1,32 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#ifndef TEST_ALLGATHER_HPP
#define TEST_ALLGATHER_HPP
#include "CorrectnessTest.hpp"
namespace CorrectnessTests
{
class AllGatherCorrectnessTest : public CorrectnessTest
{
public:
static void ComputeExpectedResults(Dataset& dataset)
{
size_t const byteCount = dataset.NumBytes() / dataset.numDevices;
int8_t* result = (int8_t *)malloc(dataset.NumBytes());
for (int i = 0; i < dataset.numDevices; i++)
HIP_CALL(hipMemcpy(result + i * byteCount, (int8_t *)dataset.inputs[i] + (i * byteCount),
byteCount, hipMemcpyDeviceToHost));
for (int i = 0; i < dataset.numDevices; i++)
memcpy(dataset.expected[i], result, dataset.NumBytes());
}
};
}
#endif
+60
Wyświetl plik
@@ -0,0 +1,60 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#include "test_AllReduce.hpp"
#include <omp.h>
namespace CorrectnessTests
{
TEST_P(AllReduceCorrectnessTest, Correctness)
{
if (numDevices > numDevicesAvailable) return;
// Prepare input / output / expected results
Dataset dataset;
dataset.Initialize(numDevices, numElements, dataType, inPlace);
FillDatasetWithPattern(dataset);
ComputeExpectedResults(dataset, op);
// Launch the reduction (1 thread per GPU)
#pragma omp parallel for num_threads(numDevices)
for (int i = 0; i < numDevices; i++)
{
ncclAllReduce(dataset.inputs[i], dataset.outputs[i],
numElements, dataType, op, comms[i], streams[i]);
}
// Wait for reduction to complete
Synchronize();
// Check results
ValidateResults(dataset);
dataset.Release();
}
INSTANTIATE_TEST_CASE_P(AllReduceCorrectnessSweep,
AllReduceCorrectnessTest,
testing::Combine(
// Reduction operator
testing::Values(ncclSum, ncclProd, ncclMax, ncclMin),
// Data types
testing::Values(ncclInt8,
ncclUint8,
ncclInt32,
ncclUint32,
ncclInt64,
ncclUint64,
//ncclFloat16,
ncclFloat32,
ncclFloat64),
// Number of elements
testing::Values(1024, 1048576),
// Number of devices
testing::Values(2,3,4),
// In-place or not
testing::Values(false, true)));
} // namespace
+76
Wyświetl plik
@@ -0,0 +1,76 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#ifndef TEST_ALLREDUCE_HPP
#define TEST_ALLREDUCE_HPP
#include "CorrectnessTest.hpp"
namespace CorrectnessTests
{
class AllReduceCorrectnessTest : public CorrectnessTest
{
public:
static void ComputeExpectedResults(Dataset& dataset, ncclRedOp_t const op)
{
// Copy all inputs to expected arrays temporarily to perform reduction on host
for (int i = 0; i < dataset.numDevices; i++)
HIP_CALL(hipMemcpy(dataset.expected[i], dataset.inputs[i],
dataset.NumBytes(), hipMemcpyDeviceToHost));
// Allocate temporary host array to accumulate results
int8_t* resultI1 = (int8_t *)malloc(dataset.NumBytes());
uint8_t* resultU1 = (uint8_t *)resultI1;
int32_t* resultI4 = (int32_t *)resultI1;
uint32_t* resultU4 = (uint32_t *)resultI1;
int64_t* resultI8 = (int64_t *)resultI1;
uint64_t* resultU8 = (uint64_t *)resultI1;
float* resultF4 = (float *)resultI1;
double* resultF8 = (double *)resultI1;
// Initialize the result with the first device's array
memcpy(resultI1, dataset.expected[0], dataset.NumBytes());
// Perform reduction on the other device arrays
for (int i = 1; i < dataset.numDevices; i++)
{
int8_t* arrayI1 = (int8_t *)dataset.expected[i];
uint8_t* arrayU1 = (uint8_t *)arrayI1;
int32_t* arrayI4 = (int32_t *)arrayI1;
uint32_t* arrayU4 = (uint32_t *)arrayI1;
int64_t* arrayI8 = (int64_t *)arrayI1;
uint64_t* arrayU8 = (uint64_t *)arrayI1;
float* arrayF4 = (float *)arrayI1;
double* arrayF8 = (double *)arrayI1;
for (int j = 0; j < dataset.numElements; j++)
{
switch (dataset.dataType)
{
case ncclInt8: resultI1[j] = ReduceOp(op, resultI1[j], arrayI1[j]); break;
case ncclUint8: resultU1[j] = ReduceOp(op, resultU1[j], arrayU1[j]); break;
case ncclInt32: resultI4[j] = ReduceOp(op, resultI4[j], arrayI4[j]); break;
case ncclUint32: resultU4[j] = ReduceOp(op, resultU4[j], arrayU4[j]); break;
case ncclInt64: resultI8[j] = ReduceOp(op, resultI8[j], arrayI8[j]); break;
case ncclUint64: resultU8[j] = ReduceOp(op, resultU8[j], arrayU8[j]); break;
case ncclFloat32: resultF4[j] = ReduceOp(op, resultF4[j], arrayF4[j]); break;
case ncclFloat64: resultF8[j] = ReduceOp(op, resultF8[j], arrayF8[j]); break;
default:
fprintf(stderr, "[ERROR] Unsupported datatype\n");
exit(0);
}
}
}
// Copy results into expected arrays
for (int i = 0; i < dataset.numDevices; i++)
memcpy(dataset.expected[i], resultI1, dataset.NumBytes());
free(resultI1);
}
};
}
#endif
+150
Wyświetl plik
@@ -0,0 +1,150 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#include "test_AllReduceAbort.hpp"
#include "../include/core.h"
#include <omp.h>
#define NUM_ITER 8
#define FAKE_OP_COUNT NUM_ITER+1
namespace CorrectnessTests
{
#define HIPCHECK(cmd) \
do { \
hipError_t error = (cmd); \
if (error != hipSuccess) { \
std::cerr << "Encountered HIP error (" << error << ") at line " \
<< __LINE__ << " in file " << __FILE__ << "\n"; \
exit(-1); \
} \
} while (0)
#define LOAD(VAR) __atomic_load_n((VAR), __ATOMIC_SEQ_CST)
#define STORE(DST, SRC) __atomic_store_n((DST), (SRC), __ATOMIC_SEQ_CST)
TEST_P(AllReduceAbortTest, Correctness) {
if (numDevices > numDevicesAvailable) return;
// Prepare input / output / expected results
Dataset dataset;
dataset.Initialize(numDevices, numElements, dataType, inPlace);
FillDatasetWithPattern(dataset);
int gpu = 0; // GPU number to trigger abort
ncclComm_t comm = comms[gpu];
HIPCHECK(hipSetDevice(gpu));
hipStream_t stream;
HIPCHECK(hipStreamCreateWithFlags(&stream, hipStreamNonBlocking));
struct ncclChannel* channel = comm->channels;
struct ncclRing *ring = &channel->ring;
struct ncclConnector* send = &channel->peers[ring->next].send;
size_t op_offset = &(send->conn.opCountRem) - (uint64_t **)channel->peers;
size_t head_offset = &(send->conn.head) - (uint64_t **)channel->peers;
uint64_t **p_dev_opCount = (uint64_t **)(channel->devPeers) + op_offset;
uint64_t **p_dev_head = (uint64_t **)(channel->devPeers) + head_offset;
uint64_t *real_opCount, *fake_opCount, *fake_o;
uint64_t *real_head, *fake_head, *fake_h;
// get original opCount and head
HIPCHECK(hipMemcpyAsync(&real_opCount, p_dev_opCount, sizeof(uint64_t*), hipMemcpyDeviceToHost, stream));
HIPCHECK(hipMemcpyAsync(&real_head, p_dev_head, sizeof(uint64_t*), hipMemcpyDeviceToHost, stream));
HIPCHECK(hipStreamSynchronize(stream));
// allocate and install fakes
HIPCHECK(hipHostMalloc(&fake_opCount, sizeof(uint64_t*), hipHostMallocMapped));
HIPCHECK(hipMemcpyAsync(p_dev_opCount, &fake_opCount, sizeof(uint64_t*), hipMemcpyHostToDevice, stream));
*fake_opCount = FAKE_OP_COUNT;
HIPCHECK(hipHostMalloc(&fake_head, sizeof(uint64_t*), hipHostMallocMapped));
HIPCHECK(hipMemcpyAsync(p_dev_head, &fake_head, sizeof(uint64_t*), hipMemcpyHostToDevice, stream));
*fake_head = 0;
HIPCHECK(hipStreamSynchronize(stream));
// read back fakes to confirm
HIPCHECK(hipMemcpyAsync(&fake_o, p_dev_opCount, sizeof(uint64_t*), hipMemcpyDeviceToHost, stream));
HIPCHECK(hipMemcpyAsync(&fake_h, p_dev_head, sizeof(uint64_t*), hipMemcpyDeviceToHost, stream));
HIPCHECK(hipStreamSynchronize(stream));
//std::cerr << "[ ] replaced gpu " << gpu << " real_opCount = " << real_opCount << " to fake_opCount = " << fake_o << std::endl;
//std::cerr << "[ ] replaced gpu " << gpu << " real_head = " << real_head << " to fake_head = " << fake_h << std::endl;
// Perform a number of iterations and introduce abort
for (int j = 0; j < NUM_ITER; j++) {
//std::cerr << "[ ] iter = " << j << std::endl;
// Start a group call
ncclGroupStart();
for (int i = 0; i < numDevices; i++) {
ncclAllReduce(dataset.inputs[i], dataset.outputs[i],
numElements, dataType, op, comms[i], streams[i]);
}
// Signal end of group call
ncclGroupEnd();
}
// Wait for reduction to complete
auto start = std::chrono::high_resolution_clock::now();
hipError_t hipErr;
int remaining = numDevices;
int* done = (int*)malloc(sizeof(int)*numDevices);
memset(done, 0, sizeof(int)*numDevices);
bool timeout = false, abort_called = false;
while (remaining) {
int idle = 1;
for (int i=0; i<numDevices; i++) {
if (done[i]) continue;
hipErr = hipStreamQuery(streams[i]);
if (hipErr == hipSuccess) {
done[i] = 1;
remaining--;
idle = 0;
continue;
}
#if NCCL_MAJOR >= 2
#if NCCL_VERSION_CODE >= NCCL_VERSION(2,4,0)
auto delta = std::chrono::high_resolution_clock::now() - start;
double deltaSec = std::chrono::duration_cast<std::chrono::duration<double>>(delta).count();
if (deltaSec > 10.0 && !timeout) {
std::cerr << "[ ] timeout condition, calling ncclCommAbort ... " << std::endl;
timeout = true;
}
ncclResult_t ncclAsyncErr;
ncclCommGetAsyncError(comms[i], &ncclAsyncErr);
if ((ncclAsyncErr != ncclSuccess || timeout) && !abort_called) {
// An asynchronous error happened. Stop the operation and destroy
// the communicator
std::cerr << "[ ] ncclAsyncErr = " << ncclAsyncErr << std::endl;
for (int i=0; i<numDevices; i++)
ncclCommAbort(comms[i]);
// Abort the perf test
abort_called = true;
break;
}
#endif
#endif
}
// We might want to let other threads (including NCCL threads) use the CPU.
if (idle) pthread_yield();
}
HIPCHECK(hipHostFree(fake_opCount));
HIPCHECK(hipStreamDestroy(stream));
dataset.Release();
}
INSTANTIATE_TEST_CASE_P(AllReduceAbortSweep,
AllReduceAbortTest,
testing::Combine(
// Reduction operator
testing::Values(ncclSum),
// Data types
testing::Values(ncclFloat32),
// Number of elements
testing::Values(1024, 1048576),
// Number of devices
testing::Values(2, 4),
// In-place or not
testing::Values(false)));
} // namespace
+20
Wyświetl plik
@@ -0,0 +1,20 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#ifndef TEST_ALLREDUCE_HPP
#define TEST_ALLREDUCE_HPP
#include "CorrectnessTest.hpp"
namespace CorrectnessTests
{
class AllReduceAbortTest : public CorrectnessTest
{
protected:
public:
};
}
#endif
+69
Wyświetl plik
@@ -0,0 +1,69 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#include "test_Broadcast.hpp"
#include <omp.h>
namespace CorrectnessTests
{
TEST_P(BroadcastCorrectnessTest, Correctness)
{
if (numDevices > numDevicesAvailable) return;
// Allocate data
Dataset dataset;
dataset.Initialize(numDevices, numElements, dataType, inPlace);
// Test each possible root
for (int root = 0; root < numDevices; root++)
{
// Prepare input / output / expected results
FillDatasetWithPattern(dataset);
ComputeExpectedResults(dataset, root);
// Launch the reduction (1 thread per GPU)
#pragma omp parallel for num_threads(numDevices)
for (int i = 0; i < numDevices; i++)
{
ncclBroadcast(dataset.inputs[i],
dataset.outputs[i],
numElements, dataType,
root, comms[i], streams[i]);
}
// Wait for reduction to complete
Synchronize();
// Check results
ValidateResults(dataset);
}
dataset.Release();
}
INSTANTIATE_TEST_CASE_P(BroadcastCorrectnessSweep,
BroadcastCorrectnessTest,
testing::Combine(
// Reduction operator is not used
testing::Values(ncclSum),
// Data types
testing::Values(ncclInt8,
ncclUint8,
ncclInt32,
ncclUint32,
ncclInt64,
ncclUint64,
//ncclFloat16,
ncclFloat32,
ncclFloat64),
// Number of elements
testing::Values(1024, 1048576),
// Number of devices
testing::Values(2,3,4),
// In-place or not
testing::Values(false, true)));
} // namespace
+26
Wyświetl plik
@@ -0,0 +1,26 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#ifndef TEST_BROADCAST_HPP
#define TEST_BROADCAST_HPP
#include "CorrectnessTest.hpp"
#include <omp.h>
namespace CorrectnessTests
{
class BroadcastCorrectnessTest : public CorrectnessTest
{
public:
static void ComputeExpectedResults(Dataset& dataset, int const root)
{
for (int i = 0; i < dataset.numDevices; i++)
HIP_CALL(hipMemcpy(dataset.expected[i], dataset.inputs[root],
dataset.NumBytes(), hipMemcpyDeviceToHost));
}
};
}
#endif
+153
Wyświetl plik
@@ -0,0 +1,153 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#include "test_BroadcastAbort.hpp"
#include "../include/core.h"
#include <omp.h>
#define NUM_ITER 8
#define FAKE_OP_COUNT NUM_ITER+1
namespace CorrectnessTests
{
#define HIPCHECK(cmd) \
do { \
hipError_t error = (cmd); \
if (error != hipSuccess) { \
std::cerr << "Encountered HIP error (" << error << ") at line " \
<< __LINE__ << " in file " << __FILE__ << "\n"; \
exit(-1); \
} \
} while (0)
#define LOAD(VAR) __atomic_load_n((VAR), __ATOMIC_SEQ_CST)
#define STORE(DST, SRC) __atomic_store_n((DST), (SRC), __ATOMIC_SEQ_CST)
TEST_P(BroadcastAbortTest, Correctness) {
if (numDevices > numDevicesAvailable) return;
// Prepare input / output / expected results
Dataset dataset;
dataset.Initialize(numDevices, numElements, dataType, inPlace);
FillDatasetWithPattern(dataset);
int root = 0;
int gpu = 0; // GPU number to trigger abort
ncclComm_t comm = comms[gpu];
HIPCHECK(hipSetDevice(gpu));
hipStream_t stream;
HIPCHECK(hipStreamCreateWithFlags(&stream, hipStreamNonBlocking));
struct ncclChannel* channel = comm->channels;
struct ncclRing *ring = &channel->ring;
struct ncclConnector* send = &channel->peers[ring->next].send;
size_t op_offset = &(send->conn.opCountRem) - (uint64_t **)channel->peers;
size_t head_offset = &(send->conn.head) - (uint64_t **)channel->peers;
uint64_t **p_dev_opCount = (uint64_t **)(channel->devPeers) + op_offset;
uint64_t **p_dev_head = (uint64_t **)(channel->devPeers) + head_offset;
uint64_t *real_opCount, *fake_opCount, *fake_o;
uint64_t *real_head, *fake_head, *fake_h;
// get original opCount and head
HIPCHECK(hipMemcpyAsync(&real_opCount, p_dev_opCount, sizeof(uint64_t*), hipMemcpyDeviceToHost, stream));
HIPCHECK(hipMemcpyAsync(&real_head, p_dev_head, sizeof(uint64_t*), hipMemcpyDeviceToHost, stream));
HIPCHECK(hipStreamSynchronize(stream));
// allocate and install fakes
HIPCHECK(hipHostMalloc(&fake_opCount, sizeof(uint64_t*), hipHostMallocMapped));
HIPCHECK(hipMemcpyAsync(p_dev_opCount, &fake_opCount, sizeof(uint64_t*), hipMemcpyHostToDevice, stream));
*fake_opCount = FAKE_OP_COUNT;
HIPCHECK(hipHostMalloc(&fake_head, sizeof(uint64_t*), hipHostMallocMapped));
HIPCHECK(hipMemcpyAsync(p_dev_head, &fake_head, sizeof(uint64_t*), hipMemcpyHostToDevice, stream));
*fake_head = 0;
HIPCHECK(hipStreamSynchronize(stream));
// read back fakes to confirm
HIPCHECK(hipMemcpyAsync(&fake_o, p_dev_opCount, sizeof(uint64_t*), hipMemcpyDeviceToHost, stream));
HIPCHECK(hipMemcpyAsync(&fake_h, p_dev_head, sizeof(uint64_t*), hipMemcpyDeviceToHost, stream));
HIPCHECK(hipStreamSynchronize(stream));
//std::cerr << "[ ] replaced gpu " << gpu << " real_opCount = " << real_opCount << " to fake_opCount = " << fake_o << std::endl;
//std::cerr << "[ ] replaced gpu " << gpu << " real_head = " << real_head << " to fake_head = " << fake_h << std::endl;
// Perform a number of iterations and introduce abort
for (int j = 0; j < NUM_ITER; j++) {
//std::cerr << "[ ] iter = " << j << std::endl;
// Start a group call
ncclGroupStart();
for (int i = 0; i < numDevices; i++) {
ncclBroadcast(dataset.inputs[i],
dataset.outputs[i],
numElements, dataType,
root, comms[i], streams[i]);
}
// Signal end of group call
ncclGroupEnd();
}
// Wait for reduction to complete
auto start = std::chrono::high_resolution_clock::now();
hipError_t hipErr;
int remaining = numDevices;
int* done = (int*)malloc(sizeof(int)*numDevices);
memset(done, 0, sizeof(int)*numDevices);
bool timeout = false, abort_called = false;
while (remaining) {
int idle = 1;
for (int i=0; i<numDevices; i++) {
if (done[i]) continue;
hipErr = hipStreamQuery(streams[i]);
if (hipErr == hipSuccess) {
done[i] = 1;
remaining--;
idle = 0;
continue;
}
#if NCCL_MAJOR >= 2
#if NCCL_VERSION_CODE >= NCCL_VERSION(2,4,0)
auto delta = std::chrono::high_resolution_clock::now() - start;
double deltaSec = std::chrono::duration_cast<std::chrono::duration<double>>(delta).count();
if (deltaSec > 10.0 && !timeout) {
std::cerr << "[ ] timeout condition, calling ncclCommAbort ... " << std::endl;
timeout = true;
}
ncclResult_t ncclAsyncErr;
ncclCommGetAsyncError(comms[i], &ncclAsyncErr);
if ((ncclAsyncErr != ncclSuccess || timeout) && !abort_called) {
// An asynchronous error happened. Stop the operation and destroy
// the communicator
std::cerr << "[ ] ncclAsyncErr = " << ncclAsyncErr << std::endl;
for (int i=0; i<numDevices; i++)
ncclCommAbort(comms[i]);
// Abort the perf test
abort_called = true;
break;
}
#endif
#endif
}
// We might want to let other threads (including NCCL threads) use the CPU.
if (idle) pthread_yield();
}
HIPCHECK(hipHostFree(fake_opCount));
HIPCHECK(hipStreamDestroy(stream));
dataset.Release();
}
INSTANTIATE_TEST_CASE_P(BroadcastAbortSweep,
BroadcastAbortTest,
testing::Combine(
// Reduction operator
testing::Values(ncclSum),
// Data types
testing::Values(ncclFloat32),
// Number of elements
testing::Values(1048576),
// Number of devices
testing::Values(2, 4),
// In-place or not
testing::Values(false)));
} // namespace
+20
Wyświetl plik
@@ -0,0 +1,20 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#ifndef TEST_ALLREDUCE_HPP
#define TEST_ALLREDUCE_HPP
#include "CorrectnessTest.hpp"
namespace CorrectnessTests
{
class BroadcastAbortTest : public CorrectnessTest
{
protected:
public:
};
}
#endif
+99
Wyświetl plik
@@ -0,0 +1,99 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#include "test_CombinedCalls.hpp"
#include "test_AllGather.hpp"
#include "test_AllReduce.hpp"
#include "test_Broadcast.hpp"
#include "test_Reduce.hpp"
#include "test_ReduceScatter.hpp"
#include <omp.h>
namespace CorrectnessTests
{
TEST_P(CombinedCallsCorrectnessTest, Correctness)
{
if (numDevices > numDevicesAvailable) return;
// Create multiple datasets for combined operation
std::vector<Dataset> datasets(5);
for (int i = 0; i < datasets.size(); i++)
{
datasets[i].Initialize(numDevices, numElements, dataType, inPlace);
FillDatasetWithPattern(datasets[i]);
}
// Compute expected results for each dataset in combined
int const root = 0;
AllGatherCorrectnessTest::ComputeExpectedResults(datasets[0]);
AllReduceCorrectnessTest::ComputeExpectedResults(datasets[1], op);
BroadcastCorrectnessTest::ComputeExpectedResults(datasets[2], root);
ReduceCorrectnessTest::ComputeExpectedResults(datasets[3], op, root);
ReduceScatterCorrectnessTest::ComputeExpectedResults(datasets[4], op);
size_t const byteCount = datasets[0].NumBytes() / numDevices;
size_t const elemCount = numElements / numDevices;
#pragma omp parallel for num_threads(numDevices)
for (int i = 0; i < numDevices; i++)
{
ncclAllGather((int8_t *)datasets[0].inputs[i] + (i * byteCount),
datasets[0].outputs[i], elemCount,
dataType, comms[i], streams[i]);
ncclAllReduce(datasets[1].inputs[i], datasets[1].outputs[i],
numElements, dataType, op, comms[i], streams[i]);
ncclBroadcast(datasets[2].inputs[i],
datasets[2].outputs[i],
numElements, dataType,
root, comms[i], streams[i]);
ncclReduce(datasets[3].inputs[i],
datasets[3].outputs[i],
numElements, dataType, op,
root, comms[i], streams[i]);
ncclReduceScatter(datasets[4].inputs[i],
(int8_t *)datasets[4].outputs[i] + (i * byteCount),
elemCount, dataType, op,
comms[i], streams[i]);
}
// Wait for reduction to complete
Synchronize();
// Check results for each collective in the combined
for (int i = 0; i < 5; i++)
{
ValidateResults(datasets[i]);
datasets[i].Release();
}
}
INSTANTIATE_TEST_CASE_P(CombinedCallsCorrectnessSweep,
CombinedCallsCorrectnessTest,
testing::Combine(
// Reduction operator (not used)
testing::Values(ncclSum),
// Data types
testing::Values(ncclInt8,
ncclUint8,
ncclInt32,
ncclUint32,
ncclInt64,
ncclUint64,
//ncclFloat16,
ncclFloat32,
ncclFloat64),
// Number of elements
testing::Values(3072, 3145728),
// Number of devices
testing::Values(2,3,4),
// In-place or not
testing::Values(false, true)));
} // namespace
+17
Wyświetl plik
@@ -0,0 +1,17 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#ifndef TEST_COMBINEDCALLS_HPP
#define TEST_COMBINEDCALLS_HPP
#include "CorrectnessTest.hpp"
namespace CorrectnessTests
{
class CombinedCallsCorrectnessTest : public CorrectnessTest {};
}
#endif
+120
Wyświetl plik
@@ -0,0 +1,120 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#include "test_GroupCalls.hpp"
#include "test_AllGather.hpp"
#include "test_AllReduce.hpp"
#include "test_Broadcast.hpp"
#include "test_Reduce.hpp"
#include "test_ReduceScatter.hpp"
#include <omp.h>
namespace CorrectnessTests
{
TEST_P(GroupCallsCorrectnessTest, Correctness)
{
if (numDevices > numDevicesAvailable) return;
// Create multiple datasets for group operation
std::vector<Dataset> datasets(5);
for (int i = 0; i < datasets.size(); i++)
{
datasets[i].Initialize(numDevices, numElements, dataType, inPlace);
FillDatasetWithPattern(datasets[i]);
}
// Compute expected results for each dataset in group
int const root = 0;
AllGatherCorrectnessTest::ComputeExpectedResults(datasets[0]);
AllReduceCorrectnessTest::ComputeExpectedResults(datasets[1], op);
BroadcastCorrectnessTest::ComputeExpectedResults(datasets[2], root);
ReduceCorrectnessTest::ComputeExpectedResults(datasets[3], op, root);
ReduceScatterCorrectnessTest::ComputeExpectedResults(datasets[4], op);
// Start a group call
ncclGroupStart();
// AllGather
size_t const byteCount = datasets[0].NumBytes() / numDevices;
size_t const elemCount = numElements / numDevices;
for (int i = 0; i < numDevices; i++)
{
ncclAllGather((int8_t *)datasets[0].inputs[i] + (i * byteCount),
datasets[0].outputs[i], elemCount,
dataType, comms[i], streams[i]);
}
// AllReduce
for (int i = 0; i < numDevices; i++)
{
ncclAllReduce(datasets[1].inputs[i], datasets[1].outputs[i],
numElements, dataType, op, comms[i], streams[i]);
}
// Broadcast
for (int i = 0; i < numDevices; i++)
{
ncclBroadcast(datasets[2].inputs[i],
datasets[2].outputs[i],
numElements, dataType,
root, comms[i], streams[i]);
}
// Reduce
for (int i = 0; i < numDevices; i++)
{
ncclReduce(datasets[3].inputs[i],
datasets[3].outputs[i],
numElements, dataType, op,
root, comms[i], streams[i]);
}
// ReduceScatter
for (int i = 0; i < numDevices; i++)
{
ncclReduceScatter(datasets[4].inputs[i],
(int8_t *)datasets[4].outputs[i] + (i * byteCount),
elemCount, dataType, op,
comms[i], streams[i]);
}
// Signal end of group call
ncclGroupEnd();
// Wait for reduction to complete
Synchronize();
// Check results for each collective in the group
for (int i = 0; i < 5; i++)
{
ValidateResults(datasets[i]);
datasets[i].Release();
}
}
INSTANTIATE_TEST_CASE_P(GroupCallsCorrectnessSweep,
GroupCallsCorrectnessTest,
testing::Combine(
// Reduction operator (not used)
testing::Values(ncclSum),
// Data types
testing::Values(ncclInt8,
ncclUint8,
ncclInt32,
ncclUint32,
ncclInt64,
ncclUint64,
//ncclFloat16,
ncclFloat32,
ncclFloat64),
// Number of elements
testing::Values(3072, 3145728),
// Number of devices
testing::Values(2,3,4),
// In-place or not
testing::Values(false, true)));
} // namespace
+17
Wyświetl plik
@@ -0,0 +1,17 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#ifndef TEST_GROUPCALLS_HPP
#define TEST_GROUPCALLS_HPP
#include "CorrectnessTest.hpp"
namespace CorrectnessTests
{
class GroupCallsCorrectnessTest : public CorrectnessTest {};
}
#endif
+68
Wyświetl plik
@@ -0,0 +1,68 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#include "test_Reduce.hpp"
#include <omp.h>
namespace CorrectnessTests
{
TEST_P(ReduceCorrectnessTest, Correctness)
{
if (numDevices > numDevicesAvailable) return;
// Allocate data
Dataset dataset;
dataset.Initialize(numDevices, numElements, dataType, inPlace);
// Test each possible root
for (int root = 0; root < numDevices; root++)
{
// Prepare input / output / expected results
FillDatasetWithPattern(dataset);
ComputeExpectedResults(dataset, op, root);
// Launch the reduction (1 thread per GPU)
#pragma omp parallel for num_threads(numDevices)
for (int i = 0; i < numDevices; i++)
{
ncclReduce(dataset.inputs[i],
dataset.outputs[i],
numElements, dataType, op,
root, comms[i], streams[i]);
}
// Wait for reduction to complete
Synchronize();
// Check results
ValidateResults(dataset);
}
dataset.Release();
}
INSTANTIATE_TEST_CASE_P(ReduceCorrectnessSweep,
ReduceCorrectnessTest,
testing::Combine(
// Reduction operator
testing::Values(ncclSum, ncclProd, ncclMax, ncclMin),
// Data types
testing::Values(ncclInt8,
ncclUint8,
ncclInt32,
ncclUint32,
ncclInt64,
ncclUint64,
//ncclFloat16,
ncclFloat32,
ncclFloat64),
// Number of elements
testing::Values(1024, 1048576),
// Number of devices
testing::Values(2,3,4),
// In-place or not
testing::Values(false, true)));
} // namespace
+80
Wyświetl plik
@@ -0,0 +1,80 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#ifndef TEST_REDUCE_HPP
#define TEST_REDUCE_HPP
#include "CorrectnessTest.hpp"
namespace CorrectnessTests
{
class ReduceCorrectnessTest : public CorrectnessTest
{
public:
static void ComputeExpectedResults(Dataset& dataset, ncclRedOp_t const op, int const root)
{
// Copy all inputs to expected arrays temporarily to perform reduction on host
for (int i = 0; i < dataset.numDevices; i++)
HIP_CALL(hipMemcpy(dataset.expected[i], dataset.inputs[i],
dataset.NumBytes(), hipMemcpyDeviceToHost));
// Allocate temporary host array to accumulate results
int8_t* resultI1 = (int8_t *)malloc(dataset.NumBytes());
uint8_t* resultU1 = (uint8_t *)resultI1;
int32_t* resultI4 = (int32_t *)resultI1;
uint32_t* resultU4 = (uint32_t *)resultI1;
int64_t* resultI8 = (int64_t *)resultI1;
uint64_t* resultU8 = (uint64_t *)resultI1;
float* resultF4 = (float *)resultI1;
double* resultF8 = (double *)resultI1;
// Initialize the result with the first device's array
memcpy(resultI1, dataset.expected[0], dataset.NumBytes());
// Perform reduction on the other device arrays
for (int i = 1; i < dataset.numDevices; i++)
{
int8_t* arrayI1 = (int8_t *)dataset.expected[i];
uint8_t* arrayU1 = (uint8_t *)arrayI1;
int32_t* arrayI4 = (int32_t *)arrayI1;
uint32_t* arrayU4 = (uint32_t *)arrayI1;
int64_t* arrayI8 = (int64_t *)arrayI1;
uint64_t* arrayU8 = (uint64_t *)arrayI1;
float* arrayF4 = (float *)arrayI1;
double* arrayF8 = (double *)arrayI1;
for (int j = 0; j < dataset.numElements; j++)
{
switch (dataset.dataType)
{
case ncclInt8: resultI1[j] = ReduceOp(op, resultI1[j], arrayI1[j]); break;
case ncclUint8: resultU1[j] = ReduceOp(op, resultU1[j], arrayU1[j]); break;
case ncclInt32: resultI4[j] = ReduceOp(op, resultI4[j], arrayI4[j]); break;
case ncclUint32: resultU4[j] = ReduceOp(op, resultU4[j], arrayU4[j]); break;
case ncclInt64: resultI8[j] = ReduceOp(op, resultI8[j], arrayI8[j]); break;
case ncclUint64: resultU8[j] = ReduceOp(op, resultU8[j], arrayU8[j]); break;
case ncclFloat32: resultF4[j] = ReduceOp(op, resultF4[j], arrayF4[j]); break;
case ncclFloat64: resultF8[j] = ReduceOp(op, resultF8[j], arrayF8[j]); break;
default:
fprintf(stderr, "[ERROR] Unsupported datatype\n");
exit(0);
}
}
}
// Copy results into expected arrays
for (int i = 0; i < dataset.numDevices; i++)
{
if (i == root)
memcpy(dataset.expected[root], resultI1, dataset.NumBytes());
else
HIP_CALL(hipMemcpy(dataset.expected[i], dataset.outputs[i], dataset.NumBytes(), hipMemcpyDeviceToHost));
}
free(resultI1);
}
};
}
#endif
+67
Wyświetl plik
@@ -0,0 +1,67 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#include "test_ReduceScatter.hpp"
#include <omp.h>
namespace CorrectnessTests
{
TEST_P(ReduceScatterCorrectnessTest, Correctness)
{
if (numDevices > numDevicesAvailable) return;
if (numElements % numDevices != 0) return;
// Prepare input / output / expected results
Dataset dataset;
dataset.Initialize(numDevices, numElements, dataType, inPlace);
FillDatasetWithPattern(dataset);
ComputeExpectedResults(dataset, op);
size_t const byteCount = dataset.NumBytes() / dataset.numDevices;
size_t const recvCount = dataset.numElements / dataset.numDevices;
// Launch the reduction (1 thread per GPU)
#pragma omp parallel for num_threads(numDevices)
for (int i = 0; i < numDevices; i++)
{
ncclReduceScatter(dataset.inputs[i],
(int8_t *)dataset.outputs[i] + (i * byteCount),
recvCount, dataType, op,
comms[i], streams[i]);
}
// Wait for reduction to complete
Synchronize();
// Check results
ValidateResults(dataset);
dataset.Release();
}
INSTANTIATE_TEST_CASE_P(ReduceScatterCorrectnessSweep,
ReduceScatterCorrectnessTest,
testing::Combine(
// Reduction operator
testing::Values(ncclSum, ncclProd, ncclMax, ncclMin),
// Data types
testing::Values(ncclInt8,
ncclUint8,
ncclInt32,
ncclUint32,
ncclInt64,
ncclUint64,
//ncclFloat16,
ncclFloat32,
ncclFloat64),
// Number of elements
testing::Values(3072, 3145728),
// Number of devices
testing::Values(2,3,4),
// In-place or not
testing::Values(false, true)));
} // namespace
+83
Wyświetl plik
@@ -0,0 +1,83 @@
/*************************************************************************
* Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#ifndef TEST_REDUCE_SCATTER_HPP
#define TEST_REDUCE_SCATTER_HPP
#include "CorrectnessTest.hpp"
namespace CorrectnessTests
{
class ReduceScatterCorrectnessTest : public CorrectnessTest
{
public:
static void ComputeExpectedResults(Dataset& dataset, ncclRedOp_t const op)
{
// Copy all inputs to expected arrays temporarily to perform reduction on host
for (int i = 0; i < dataset.numDevices; i++)
HIP_CALL(hipMemcpy(dataset.expected[i], dataset.inputs[i],
dataset.NumBytes(), hipMemcpyDeviceToHost));
// Allocate temporary host array to accumulate results
int8_t* resultI1 = (int8_t *)malloc(dataset.NumBytes());
uint8_t* resultU1 = (uint8_t *)resultI1;
int32_t* resultI4 = (int32_t *)resultI1;
uint32_t* resultU4 = (uint32_t *)resultI1;
int64_t* resultI8 = (int64_t *)resultI1;
uint64_t* resultU8 = (uint64_t *)resultI1;
float* resultF4 = (float *)resultI1;
double* resultF8 = (double *)resultI1;
// Initialize the result with the first device's array
memcpy(resultI1, dataset.expected[0], dataset.NumBytes());
// Perform reduction on the other device arrays
for (int i = 1; i < dataset.numDevices; i++)
{
int8_t* arrayI1 = (int8_t *)dataset.expected[i];
uint8_t* arrayU1 = (uint8_t *)arrayI1;
int32_t* arrayI4 = (int32_t *)arrayI1;
uint32_t* arrayU4 = (uint32_t *)arrayI1;
int64_t* arrayI8 = (int64_t *)arrayI1;
uint64_t* arrayU8 = (uint64_t *)arrayI1;
float* arrayF4 = (float *)arrayI1;
double* arrayF8 = (double *)arrayI1;
for (int j = 0; j < dataset.numElements; j++)
{
switch (dataset.dataType)
{
case ncclInt8: resultI1[j] = ReduceOp(op, resultI1[j], arrayI1[j]); break;
case ncclUint8: resultU1[j] = ReduceOp(op, resultU1[j], arrayU1[j]); break;
case ncclInt32: resultI4[j] = ReduceOp(op, resultI4[j], arrayI4[j]); break;
case ncclUint32: resultU4[j] = ReduceOp(op, resultU4[j], arrayU4[j]); break;
case ncclInt64: resultI8[j] = ReduceOp(op, resultI8[j], arrayI8[j]); break;
case ncclUint64: resultU8[j] = ReduceOp(op, resultU8[j], arrayU8[j]); break;
case ncclFloat32: resultF4[j] = ReduceOp(op, resultF4[j], arrayF4[j]); break;
case ncclFloat64: resultF8[j] = ReduceOp(op, resultF8[j], arrayF8[j]); break;
default:
fprintf(stderr, "[ERROR] Unsupported datatype\n");
exit(0);
}
}
}
// Copy results into expected arrays
size_t const byteCount = dataset.NumBytes() / dataset.numDevices;
for (int i = 0; i < dataset.numDevices; i++)
HIP_CALL(hipMemcpy(dataset.expected[i], dataset.outputs[i],
dataset.NumBytes(), hipMemcpyDeviceToHost));
for (int i = 0; i < dataset.numDevices; i++)
memcpy((int8_t *)dataset.expected[i] + (i * byteCount),
resultI1 + (i * byteCount), byteCount);
free(resultI1);
}
};
}
#endif