[dtest] Cooperative Groups related tests
Converted tests from below git to hipdirected_tests http://gitlab1.amd.com/jgreatho/cuda_cooperative_groups_test Modified to cover multi-GPUs Disabled tests for cuda because of some header file issues SWDEV-238517 for enhancing hip unit tests Change-Id: If35fd710e8ab61debcf66bca5b6503539c567ec1
This commit is contained in:
committed by
Mohan Kumar Mithur
parent
449848cb5a
commit
40ca4a5ea8
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
Copyright (c) 2020-present Advanced Micro Devices, Inc. All rights reserved.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
// Test Description:
|
||||
/*The general idea of the application is to test how Cooperative Groups kernel
|
||||
launches work when launching too many warps to the target device. This test
|
||||
first queries the nominal warp size of the target device. It then walks through
|
||||
block sizes from 1 thread, 1 warp, 2 warps, ... `maximum_warps_in_a_block`. For
|
||||
each of these, it queries the maximum number of blocks that can fit in each SM.
|
||||
It then queries the number of SMs on the target device. This will yield a
|
||||
calculation for the maximum number of blocks that can be co-scheduled on this
|
||||
device.
|
||||
|
||||
The Cooperative Groups API says that users should not launch more than this
|
||||
many warps (or blocks, etc.) to the target device. This test first tires to
|
||||
launch 2x as many blcoks, to confirm that the runtime prevents such a launch
|
||||
by returning a proper error value (`hipErrorCooperativeLaunchTooLarge`).
|
||||
|
||||
It then ensures that trying to launch too large of a kernel invocation does
|
||||
not break the GPU by launching a kernel with exactly the maximum number of
|
||||
blocks.
|
||||
|
||||
Finally, we run the same test for a block size that is larger than the maximum
|
||||
allowed by the device, to ensure that this case is properly detected by the
|
||||
runtime and that nothing breaks.*/
|
||||
|
||||
|
||||
|
||||
/* HIT_START
|
||||
* BUILD: %t %s ../../test_common.cpp EXCLUDE_HIP_PLATFORM nvcc
|
||||
* TEST: %t
|
||||
* HIT_END
|
||||
*/
|
||||
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <hip/hip_cooperative_groups.h>
|
||||
#include "test_common.h"
|
||||
|
||||
|
||||
static inline void hipCheckAndFail(hipError_t errval,
|
||||
const char *file, int line) {
|
||||
hipError_t last_err = hipGetLastError();
|
||||
if (errval != hipSuccess) {
|
||||
std::cerr << "hip error: " << hipGetErrorString(errval);
|
||||
std::cerr << std::endl;
|
||||
std::cerr << " Location: " << file << ":" << line << std::endl;
|
||||
failed("");
|
||||
}
|
||||
if (last_err != errval) {
|
||||
std::cerr << "Error: the return value of a function was not the same ";
|
||||
std::cerr << "as the value returned by hipGetLastError()" << std::endl;
|
||||
std::cerr << " Location: " << file << ":" << line << std::endl;
|
||||
std::cerr << " Function returned: " << hipGetErrorString(errval);
|
||||
std::cerr << " (" << errval << ")" << std::endl;
|
||||
std::cerr << "hipGetLastError() returned: " << hipGetErrorString(last_err);
|
||||
std::cerr << " (" << last_err << ")" << std::endl;
|
||||
failed("");
|
||||
}
|
||||
}
|
||||
#define hipCheckErr(errval) \
|
||||
do { hipCheckAndFail((errval), __FILE__, __LINE__); } while (0)
|
||||
|
||||
static inline bool hipCheckExpected(hipError_t errval,
|
||||
hipError_t expected_err, const char *file, int line) {
|
||||
hipError_t last_err = hipGetLastError();
|
||||
if (errval != expected_err) {
|
||||
std::cerr << "hip error: " << hipGetErrorString(errval);
|
||||
std::cerr << std::endl;
|
||||
std::cerr << " Location: " << file << ":" << line << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (last_err != errval) {
|
||||
std::cerr << "Error: the return value of a function was not the same ";
|
||||
std::cerr << "as the value returned by hipGetLastError()" << std::endl;
|
||||
std::cerr << " Location: " << file << ":" << line << std::endl;
|
||||
std::cerr << " Function returned: " << hipGetErrorString(errval);
|
||||
std::cerr << " (" << errval << ")" << std::endl;
|
||||
std::cerr << "hipGetLastError() returned: " << hipGetErrorString(last_err);
|
||||
std::cerr << " (" << last_err << ")" << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cooperative_groups_support(int device_id) {
|
||||
hipError_t err;
|
||||
int cooperative_attribute;
|
||||
HIPCHECK(hipDeviceGetAttribute(&cooperative_attribute,
|
||||
hipDeviceAttributeCooperativeLaunch, device_id));
|
||||
if (!cooperative_attribute) {
|
||||
std::cerr << "Cooperative launch support not available in ";
|
||||
std::cerr << "the device attribute for device " << device_id;
|
||||
std::cerr << std::endl;
|
||||
return false;
|
||||
}
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, device_id));
|
||||
if (device_properties.cooperativeLaunch == 0) {
|
||||
std::cerr << "Cooperative group support not available in ";
|
||||
std::cerr << "device properties." << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
__global__ void test_kernel(long long *array) {
|
||||
unsigned int rank = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
array[rank] += clock64();
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
hipError_t err;
|
||||
int device_num, FailFlag = 0;
|
||||
// Alocate the host input buffer, and two device-focused buffers that we
|
||||
// will use for our test.
|
||||
unsigned int *dev_array[2];
|
||||
HIPCHECK(hipGetDeviceCount(&device_num));
|
||||
for (int dev = 0; dev < device_num; ++dev) {
|
||||
/*************************************************************************/
|
||||
/* Test whether target device supports cooperative groups ****************/
|
||||
HIPCHECK(hipSetDevice(dev));
|
||||
if (!cooperative_groups_support(dev)) {
|
||||
std::cout << "Skipping the test with Pass result.\n";
|
||||
passed();
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* Create the streams we will use in this test. **************************/
|
||||
hipStream_t streams[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipStreamCreate(&streams[i]));
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* We will try to launch more waves than the GPU can fit. ***************/
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, dev));
|
||||
int warp_size = device_properties.warpSize;
|
||||
int num_sms = device_properties.multiProcessorCount;
|
||||
int max_num_threads = device_properties.maxThreadsPerBlock;
|
||||
|
||||
// Check single-thread block, all numbers of warps, then too-large block
|
||||
for (int block_size = 0; block_size <= (max_num_threads + warp_size);
|
||||
block_size += warp_size) {
|
||||
if (block_size == 0) {
|
||||
block_size = 1;
|
||||
}
|
||||
int max_blocks_per_sm;
|
||||
// Calculate the device occupancy to know how many blocks can be run.
|
||||
HIPCHECK(hipOccupancyMaxActiveBlocksPerMultiprocessorWithFlags(
|
||||
&max_blocks_per_sm, test_kernel, block_size, 0,
|
||||
hipOccupancyDefault));
|
||||
|
||||
if ((block_size > max_num_threads) && (max_blocks_per_sm != 0)) {
|
||||
std::cerr << "ERROR! Occupancy API indicated that we can have >0 ";
|
||||
std::cerr << "blocks in a kernel when the block size is too large ";
|
||||
std::cerr << "to work on the device." << std::endl;
|
||||
std::cerr << "This is incorrect, and could possibly lead users ";
|
||||
std::cerr << "to try to launch kernels that will fail." << std::endl;
|
||||
//failed("");
|
||||
FailFlag = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
int desired_blocks = max_blocks_per_sm * num_sms;
|
||||
bool expect_fail = false;
|
||||
if (desired_blocks == 0) {
|
||||
desired_blocks = 1;
|
||||
expect_fail = true;
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
/* Set up data to pass into the kernel ********************************/
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
int test_size;
|
||||
// Case where we expect to fail at launch.
|
||||
if (i == 0) {
|
||||
test_size = 2 * desired_blocks;
|
||||
} else {
|
||||
test_size = desired_blocks;
|
||||
}
|
||||
HIPCHECK(hipMalloc(reinterpret_cast<void**>(&dev_array[i]),
|
||||
test_size * block_size * sizeof(long long)));
|
||||
HIPCHECK(hipMemsetAsync(dev_array[i], 0,
|
||||
test_size * block_size * sizeof(long long),
|
||||
streams[i]));
|
||||
}
|
||||
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
|
||||
/***********************************************************************/
|
||||
/* Launch the kernels **************************************************/
|
||||
void *coop_params[2][1];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
coop_params[i][0] = reinterpret_cast<void*>(&dev_array[i]);
|
||||
}
|
||||
|
||||
err = hipLaunchCooperativeKernel(reinterpret_cast<void*>(test_kernel),
|
||||
2 * desired_blocks, block_size,
|
||||
coop_params[0], 0, streams[0]);
|
||||
|
||||
hipError_t expect_to_see;
|
||||
if (expect_fail) {
|
||||
expect_to_see = hipErrorInvalidConfiguration;
|
||||
} else {
|
||||
expect_to_see = hipErrorCooperativeLaunchTooLarge;
|
||||
}
|
||||
if (!hipCheckExpected(err, expect_to_see, __FILE__, __LINE__)) {
|
||||
std::cerr << "ERROR! Tried to launch a cooperative kernel with ";
|
||||
std::cerr << "too many warps." << std::endl;
|
||||
std::cerr << "This SHOULD have failed with the error ";
|
||||
std::cerr << hipGetErrorString(expect_to_see);
|
||||
std::cerr << " (" << expect_to_see << ")." << std::endl;
|
||||
std::cerr << "Instead, the launch returned " << hipGetErrorName(err);
|
||||
std::cerr << " (" << err << ")" << std::endl;
|
||||
FailFlag = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
err = hipLaunchCooperativeKernel(reinterpret_cast<void*>(test_kernel),
|
||||
desired_blocks, block_size,
|
||||
coop_params[1], 0, streams[1]);
|
||||
|
||||
if (expect_fail) {
|
||||
expect_to_see = hipErrorInvalidConfiguration;
|
||||
} else {
|
||||
expect_to_see = hipSuccess;
|
||||
}
|
||||
if (!hipCheckExpected(err, expect_to_see, __FILE__, __LINE__)) {
|
||||
std::cerr << "ERROR! Tried to launch a cooperative kernel ";
|
||||
std::cerr << "with a normal size, but a block size of ";
|
||||
std::cerr << desired_blocks << std::endl;
|
||||
std::cerr << "This SHOULD have returned ";
|
||||
std::cerr << hipGetErrorString(expect_to_see);
|
||||
std::cerr << " (" << expect_to_see << ")." << std::endl;
|
||||
std::cerr << "Instead, the launch returned " << hipGetErrorName(err);
|
||||
std::cerr << " (" << err << ")" << std::endl;
|
||||
FailFlag = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
|
||||
if (block_size == 1) {
|
||||
block_size = 0;
|
||||
}
|
||||
for (int m = 0; m < 2; ++m) {
|
||||
HIPCHECK(hipFree(dev_array[m]));
|
||||
}
|
||||
}
|
||||
for (int m = 0; m < 2; ++m) {
|
||||
HIPCHECK(hipStreamDestroy(streams[m]));
|
||||
}
|
||||
if (FailFlag == 1) {
|
||||
for (int m = 0; m < 2; ++m) {
|
||||
HIPCHECK(hipFree(dev_array[m]));
|
||||
}
|
||||
failed("");
|
||||
}
|
||||
}
|
||||
passed();
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
Copyright (c) 2020-present Advanced Micro Devices, Inc. All rights reserved.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANNTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INNCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANNY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER INN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR INN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
// Test Description:
|
||||
/*
|
||||
The general idea of the application is to test how Cooperative Groups kernel
|
||||
launches to a stream interact with other kernels being launched to different
|
||||
streams.
|
||||
|
||||
For example: the HIP runtime will force cooperative kernel launches to run
|
||||
serially, even if they are launched to different streams. However,
|
||||
cooperative kernel launches can run in parallel with regular kernels that
|
||||
are launched to other streams. This limitation is so that the cooperative
|
||||
kernels do not conflict with one another for resources and potentially
|
||||
deadlock the system.
|
||||
|
||||
As such, this benchmark tests three situations:
|
||||
|
||||
1. Launching a cooperative kernel by itself to stream[0]
|
||||
2. Launching two cooperative kernels in parallel to stream[0] and stream[1]
|
||||
3. Launching two cooperative kernels in parallel to stream[0] and stream[1]
|
||||
and launching a third non-cooperative kernel to stream[2]
|
||||
|
||||
We time how long it takes to run each of these benchmarks and print it as
|
||||
the output of the benchmark. The kernels themselves are just useless time-
|
||||
wasting code so that the kernel takes a meaningful amount of time on the
|
||||
GPU before it exits. We only launch a single wavefront for each kernel, so
|
||||
any serialization should not be because of GPU occupancy concerns.
|
||||
|
||||
If test #2 takes roughly twice as long as #1, that implies that cooperative
|
||||
kernels are properly serialized with each other by the runtime.
|
||||
|
||||
If test #3 takes the same amount of time as test #2, that implies that
|
||||
regular kernels can properly run in parallel with cooperative kernels.
|
||||
*/
|
||||
|
||||
/* HIT_START
|
||||
* BUILD: %t %s ../../test_common.cpp EXCLUDE_HIP_PLATFORM nvcc
|
||||
* TEST: %t
|
||||
* HIT_END
|
||||
*/
|
||||
|
||||
#include <chrono>
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <hip/hip_cooperative_groups.h>
|
||||
#include "test_common.h"
|
||||
|
||||
static inline void hipCheckAndFail(hipError_t errval,
|
||||
const char *file, int line) {
|
||||
hipError_t last_err = hipGetLastError();
|
||||
if (errval != hipSuccess) {
|
||||
std::cerr << "hip error: " << hipGetErrorString(errval);
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "Location: " << file << ":" << line << std::endl;
|
||||
failed("");
|
||||
}
|
||||
if (last_err != errval) {
|
||||
std::cerr << "Error: the return value of a function was not the same ";
|
||||
std::cerr << "as the value returned by hipGetLastError()" << std::endl;
|
||||
std::cerr << "Location: " << file << ":" << line << std::endl;
|
||||
std::cerr << "Function returned: " << hipGetErrorString(errval);
|
||||
std::cerr << " (" << errval << ")" << std::endl;
|
||||
std::cerr << "hipGetLastError() returned: " << hipGetErrorString(last_err);
|
||||
std::cerr << " (" << last_err << ")" << std::endl;
|
||||
failed("");
|
||||
}
|
||||
}
|
||||
#define hipCheckErr(errval) \
|
||||
do { hipCheckAndFail((errval), __FILE__, __LINE__); } while (0)
|
||||
|
||||
static int cooperative_groups_support(int device_id) {
|
||||
hipError_t err;
|
||||
int cooperative_attribute;
|
||||
HIPCHECK(hipDeviceGetAttribute(&cooperative_attribute,
|
||||
hipDeviceAttributeCooperativeLaunch, device_id));
|
||||
if (!cooperative_attribute) {
|
||||
std::cerr << "Cooperative launch support not available in ";
|
||||
std::cerr << "the device attribute for device " << device_id;
|
||||
std::cerr << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, device_id));
|
||||
if (device_properties.cooperativeLaunch == 0) {
|
||||
std::cerr << "Cooperative group support not available in ";
|
||||
std::cerr << "device properties." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
__global__ void test_kernel(uint32_t loops, unsigned long long *array) {
|
||||
unsigned int rank = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
|
||||
for (int i = 0; i < loops; i++) {
|
||||
long long start_clock = clock64();
|
||||
while (clock64() < (start_clock+1000000)) {}
|
||||
array[rank] += clock64();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
hipError_t err;
|
||||
/*************************************************************************/
|
||||
int device_num = 0, loops = 1000, FailFlag = 0;
|
||||
/* Create the streams we will use in this test. **************************/
|
||||
hipStream_t streams[3];
|
||||
// Alocate the host input buffer, and two device-focused buffers that we
|
||||
// will use for our test.
|
||||
unsigned long long *dev_array[3];
|
||||
HIPCHECK(hipGetDeviceCount(&device_num));
|
||||
for (int dev = 0; dev < device_num; ++dev) {
|
||||
/*************************************************************************/
|
||||
/* Test whether target device supports cooperative groups ****************/
|
||||
HIPCHECK(hipSetDevice(dev));
|
||||
if (!cooperative_groups_support(dev)) {
|
||||
std::cout << "Skipping the test with Pass result.\n";
|
||||
passed();
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* We will launch enough waves to fill up all of the GPU *****************/
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, dev));
|
||||
int warp_size = device_properties.warpSize;
|
||||
int num_sms = device_properties.multiProcessorCount;
|
||||
int desired_blocks = 1;
|
||||
std::cout << "Device: " << dev << std::endl;
|
||||
std::cout << "Device name: " << device_properties.name << std::endl;
|
||||
|
||||
int max_blocks_per_sm;
|
||||
// Calculate the device occupancy to know how many blocks can be run.
|
||||
HIPCHECK(hipOccupancyMaxActiveBlocksPerMultiprocessor(&max_blocks_per_sm,
|
||||
test_kernel,
|
||||
warp_size, 0));
|
||||
|
||||
if (desired_blocks > max_blocks_per_sm * num_sms) {
|
||||
std::cerr << "The requested number of blocks will not fit on the GPU";
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "You requested " << desired_blocks << " but we can only ";
|
||||
std::cerr << "fit " << (max_blocks_per_sm * num_sms) << std::endl;
|
||||
failed("");
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
for (int i = 0; i < 3; i++) {
|
||||
HIPCHECK(hipStreamCreate(&streams[i]));
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* Set up data to pass into the kernel ***********************************/
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
HIPCHECK(hipMalloc(reinterpret_cast<void**>(&dev_array[i]),
|
||||
warp_size * sizeof(long long)));
|
||||
HIPCHECK(hipMemsetAsync(dev_array[i], 0, warp_size * sizeof(long long),
|
||||
streams[i]));
|
||||
}
|
||||
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
|
||||
/*************************************************************************/
|
||||
/* Launch the kernels ****************************************************/
|
||||
void *coop_params[3][2];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
coop_params[i][0] = reinterpret_cast<void*>(&loops);
|
||||
coop_params[i][1] = reinterpret_cast<void*>(&dev_array[i]);
|
||||
}
|
||||
|
||||
std::cout << "Launching a single cooperative kernel..." << std::endl;
|
||||
auto single_start = std::chrono::system_clock::now();
|
||||
HIPCHECK(hipLaunchCooperativeKernel(reinterpret_cast<void*>(test_kernel),
|
||||
desired_blocks, warp_size,
|
||||
coop_params[0], 0, streams[0]));
|
||||
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
auto single_end = std::chrono::system_clock::now();
|
||||
std::cout << "Launching 2 cooperative kernels to different streams...";
|
||||
std::cout << std::endl;
|
||||
|
||||
auto double_start = std::chrono::system_clock::now();
|
||||
HIPCHECK(hipLaunchCooperativeKernel(reinterpret_cast<void*>(test_kernel),
|
||||
desired_blocks, warp_size,
|
||||
coop_params[0], 0, streams[0]));
|
||||
HIPCHECK(hipLaunchCooperativeKernel(reinterpret_cast<void*>(test_kernel),
|
||||
desired_blocks, warp_size,
|
||||
coop_params[1], 0, streams[1]));
|
||||
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
auto double_end = std::chrono::system_clock::now();
|
||||
std::cout << "Launching 2 cooperative kernels and 1 normal kernel...";
|
||||
std::cout << std::endl;
|
||||
|
||||
auto triple_start = std::chrono::system_clock::now();
|
||||
HIPCHECK(hipLaunchCooperativeKernel(reinterpret_cast<void*>(test_kernel),
|
||||
desired_blocks, warp_size,
|
||||
coop_params[0], 0, streams[0]));
|
||||
HIPCHECK(hipLaunchCooperativeKernel(reinterpret_cast<void*>(test_kernel),
|
||||
desired_blocks, warp_size,
|
||||
coop_params[1], 0, streams[1]));
|
||||
hipLaunchKernelGGL(test_kernel, dim3(desired_blocks), dim3(warp_size),
|
||||
0, streams[2], loops, dev_array[2]);
|
||||
err = hipGetLastError();
|
||||
hipCheckErr(err);
|
||||
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
auto triple_end = std::chrono::system_clock::now();
|
||||
std::chrono::duration<double> single_kernel_time =
|
||||
(single_end - single_start);
|
||||
std::chrono::duration<double> double_kernel_time =
|
||||
(double_end - double_start);
|
||||
std::chrono::duration<double> triple_kernel_time =
|
||||
(triple_end - triple_start);
|
||||
|
||||
std::cout << "A single kernel took:" << std::endl;
|
||||
std::cout << " " << single_kernel_time.count();
|
||||
std::cout << " seconds" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Two cooperative kernels that could run together took:";
|
||||
std::cout << std::endl;
|
||||
std::cout << " " << double_kernel_time.count();
|
||||
std::cout << " seconds" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Two coop kernels and a third regular kernel took:";
|
||||
std::cout << std::endl << " ";
|
||||
std::cout << triple_kernel_time.count();
|
||||
std::cout << " seconds" << std::endl;
|
||||
|
||||
std::cout << "Testing whether these times make sense.." << std::endl;
|
||||
// Test that two cooperative kernels is roughly twice as long as one
|
||||
if (double_kernel_time < 1.8 * single_kernel_time) {
|
||||
std::cerr << "ERROR!" << std::endl;
|
||||
std::cerr << "Two cooperative kernels launched at the same ";
|
||||
std::cerr << "time did not take roughly twice as long as a single ";
|
||||
std::cerr << "cooperative kernel." << std::endl;
|
||||
std::cerr << "Were they truly serialized?" << std::endl;
|
||||
FailFlag = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// Test that the three kernels together took roughly as long as two
|
||||
// cooperative kernels.
|
||||
if (triple_kernel_time > 1.1 * double_kernel_time) {
|
||||
std::cerr << "ERROR!" << std::endl;
|
||||
std::cerr << "Launching a normal kernel in parallel with two ";
|
||||
std::cerr << "back-to-back cooperative kernels still ended up taking ";
|
||||
std::cerr << "more than 10% longer than the two cooperative kernels ";
|
||||
std::cerr << "alone." << std::endl;
|
||||
std::cerr << "Is the normal kernel being serialized with the ";
|
||||
std::cerr << "cooperative kernels on different streams?" << std::endl;
|
||||
FailFlag = 1;
|
||||
break;
|
||||
}
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
HIPCHECK(hipFree(dev_array[k]));
|
||||
HIPCHECK(hipStreamDestroy(streams[k]));
|
||||
}
|
||||
}
|
||||
if (FailFlag == 1) {
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
HIPCHECK(hipFree(dev_array[k]));
|
||||
HIPCHECK(hipStreamDestroy(streams[k]));
|
||||
}
|
||||
failed("");
|
||||
}
|
||||
passed();
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
Copyright (c) 2020-present Advanced Micro Devices, Inc. All rights reserved.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANNTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INNCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANNY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER INN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR INN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
// Test Description:
|
||||
/*The general idea of the application is to create a buffer of width N. N is a
|
||||
command line parameter, and the user will need to make sure that we can fit
|
||||
two buffers of N unsigned integers onto the target GPU at the same time.
|
||||
|
||||
We then launch a fixed number of warps to the GPU. This number is calculated
|
||||
to fill the GPU with as many warps as can simultaneously run on the GPU.
|
||||
The threads in these warps then walk over two arrays. First, values from
|
||||
A[offset] are added into B[offset]. After all of A is added into all of B
|
||||
in this element-wise manner, all of the waves barrier with one another.
|
||||
|
||||
After the barrier, the waves start adding values from B[mirror_offset] into
|
||||
A[offset]. Mirror offset means that the wave that is writing into A[7] is
|
||||
reading from B[7 before the last value]. This was probably written by a
|
||||
different thread before the barrier.
|
||||
|
||||
After going through this loop a certain number of times, the kernel ends and
|
||||
we read the arrays back out and recalculate this algorithm serially on the
|
||||
CPU. We compare the serial version to the version that has inter-thread data
|
||||
sharing and barriers and ensure they result in the same answer.
|
||||
|
||||
If they do have the same answer, then we can pretty confidently say that
|
||||
writing from thread X and then hitting a barrier allows thread Y to see the
|
||||
values.*/
|
||||
|
||||
/* HIT_START
|
||||
* BUILD: %t %s ../../test_common.cpp EXCLUDE_HIP_PLATFORM nvcc
|
||||
* TEST: %t
|
||||
* HIT_END
|
||||
*/
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <hip/hip_cooperative_groups.h>
|
||||
#include "test_common.h"
|
||||
|
||||
static inline void hipCheckAndFail(hipError_t errval,
|
||||
const char *file, int line) {
|
||||
hipError_t last_err = hipGetLastError();
|
||||
if (errval != hipSuccess) {
|
||||
std::cerr << "hip error: " << hipGetErrorString(errval);
|
||||
std::cerr << std::endl;
|
||||
std::cerr << " Location: " << file << ":" << line << std::endl;
|
||||
exit(errval);
|
||||
}
|
||||
if (last_err != errval) {
|
||||
std::cerr << "Error: the return value of a function was not the same ";
|
||||
std::cerr << "as the value returned by hipGetLastError()" << std::endl;
|
||||
std::cerr << " Location: " << file << ":" << line << std::endl;
|
||||
std::cerr << " Function returned: " << hipGetErrorString(errval);
|
||||
std::cerr << " (" << errval << ")" << std::endl;
|
||||
std::cerr << "hipGetLastError() returned: " << hipGetErrorString(last_err);
|
||||
std::cerr << " (" << last_err << ")" << std::endl;
|
||||
failed("");
|
||||
}
|
||||
}
|
||||
#define hipCheckErr(errval)\
|
||||
do { hipCheckAndFail((errval), __FILE__, __LINE__); } while (0)
|
||||
|
||||
static int cooperative_groups_support(int device_id) {
|
||||
hipError_t err;
|
||||
|
||||
int cooperative_attribute;
|
||||
HIPCHECK(hipDeviceGetAttribute(&cooperative_attribute,
|
||||
hipDeviceAttributeCooperativeLaunch, device_id));
|
||||
if (!cooperative_attribute) {
|
||||
std::cerr << "Cooperative launch support not available in ";
|
||||
std::cerr << "the device attribute for device " << device_id;
|
||||
std::cerr << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, device_id));
|
||||
if (device_properties.cooperativeLaunch == 0) {
|
||||
std::cerr << "Cooperative group support not available in ";
|
||||
std::cerr << "device properties." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int verify_coop_arrays(unsigned int loops, unsigned int *host_input,
|
||||
unsigned int *first_array,
|
||||
unsigned int *second_array,
|
||||
unsigned int array_len) {
|
||||
unsigned int *host_first_array = host_input;
|
||||
unsigned int *host_second_array = (unsigned int*)calloc(array_len,
|
||||
sizeof(int));
|
||||
|
||||
for (int i = 0; i < loops; i++) {
|
||||
for (int offset = 0; offset < array_len; offset++) {
|
||||
host_second_array[offset] += host_first_array[offset];
|
||||
}
|
||||
|
||||
for (int offset = 0; offset < array_len; offset++) {
|
||||
unsigned int swizzle_offset = array_len - offset - 1;
|
||||
host_first_array[offset] += host_second_array[swizzle_offset];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < array_len; i++) {
|
||||
if (host_first_array[i] != first_array[i]) {
|
||||
std::cerr << "Test failure!" << std::endl;
|
||||
std::cerr << " host_first_array[" << i << "] contains the ";
|
||||
std::cerr << "value " << host_first_array[i] << std::endl;
|
||||
std::cerr << " GPU first_array[" << i << "] contains the ";
|
||||
std::cerr << "value " << first_array[i] << std::endl;
|
||||
return -1;
|
||||
}
|
||||
if (host_second_array[i] != second_array[i]) {
|
||||
std::cerr << "Test failure!" << std::endl;
|
||||
std::cerr << " host_second_array[" << i << "] contains the ";
|
||||
std::cerr << "value " << host_second_array[i] << std::endl;
|
||||
std::cerr << " GPU second_array[" << i << "] contains the ";
|
||||
std::cerr << "value " << second_array[i] << std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Coop test appears to work properly!" << std::endl;
|
||||
free(host_second_array);
|
||||
return 0;
|
||||
}
|
||||
|
||||
__global__ void
|
||||
coop_kernel(unsigned int *first_array, unsigned int *second_array,
|
||||
unsigned int loops, unsigned int array_len) {
|
||||
cooperative_groups::grid_group grid = cooperative_groups::this_grid();
|
||||
unsigned int rank = grid.thread_rank();
|
||||
unsigned int grid_size = grid.size();
|
||||
|
||||
for (int i = 0; i < loops; i++) {
|
||||
// The goal of this loop is to directly add in values from
|
||||
// array one into array two, on a per-wave basis.
|
||||
for (int offset = rank; offset < array_len; offset += grid_size) {
|
||||
second_array[offset] += first_array[offset];
|
||||
}
|
||||
|
||||
grid.sync();
|
||||
|
||||
// The goal of this loop is to pull data the "mirror" lane in
|
||||
// array two and add it back into array one. This causes inter-
|
||||
// thread swizzling.
|
||||
for (int offset = rank; offset < array_len; offset += grid_size) {
|
||||
unsigned int swizzle_offset = array_len - offset - 1;
|
||||
first_array[offset] += second_array[swizzle_offset];
|
||||
}
|
||||
|
||||
grid.sync();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
hipError_t err;
|
||||
/*************************************************************************/
|
||||
/* Parse the command line parameters *************************************/
|
||||
// Arguments to pull out of the command line.
|
||||
int device_num = 0, loops = 2, width = 4096, flag = 0;
|
||||
HIPCHECK(hipGetDeviceCount(&device_num));
|
||||
for (int dev = 0; dev < device_num; ++dev) {
|
||||
std::cout << "Device number: " << dev << std::endl;
|
||||
std::cout << "Loops: " << loops << std::endl;
|
||||
std::cout << "Width: " << width << std::endl;
|
||||
|
||||
/*************************************************************************/
|
||||
/* Test whether target device supports cooperative groups ****************/
|
||||
HIPCHECK(hipSetDevice(dev));
|
||||
|
||||
if (!cooperative_groups_support(dev)) {
|
||||
std::cout << "Skipping the test with Pass result.\n";
|
||||
passed();
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* We will launch enough waves to fill up all of the GPU *****************/
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, dev));
|
||||
|
||||
int warp_size = device_properties.warpSize;
|
||||
int num_sms = device_properties.multiProcessorCount;
|
||||
|
||||
std::cout << "Device name: " << device_properties.name << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Calculate the device occupancy to know how many blocks can be run.
|
||||
int max_blocks_per_sm;
|
||||
HIPCHECK(hipOccupancyMaxActiveBlocksPerMultiprocessor(&max_blocks_per_sm,
|
||||
coop_kernel,
|
||||
warp_size, 0));
|
||||
|
||||
int total_blocks = max_blocks_per_sm * num_sms;
|
||||
|
||||
/*************************************************************************/
|
||||
/* Create the streams we will use in this test. **************************/
|
||||
hipStream_t streams[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipStreamCreate(&streams[i]));
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* Set up data to pass into the kernel ***********************************/
|
||||
|
||||
// Alocate the host input buffer, and two device-focused buffers that we
|
||||
// will use for our test.
|
||||
unsigned int *input_buffer = (unsigned int*)calloc(width,
|
||||
sizeof(unsigned int));
|
||||
for (int i = 0; i < width; i++) {
|
||||
input_buffer[i] = i;
|
||||
}
|
||||
|
||||
unsigned int *first_dev_array;
|
||||
HIPCHECK(hipMalloc(reinterpret_cast<void**>(&first_dev_array),
|
||||
width * sizeof(unsigned int)));
|
||||
|
||||
HIPCHECK(hipMemcpyAsync(first_dev_array, input_buffer,
|
||||
width * sizeof(unsigned int),
|
||||
hipMemcpyHostToDevice, streams[0]));
|
||||
|
||||
unsigned int *second_dev_array;
|
||||
HIPCHECK(hipMalloc(reinterpret_cast<void**>(&second_dev_array),
|
||||
width * sizeof(unsigned int)));
|
||||
HIPCHECK(hipMemsetAsync(second_dev_array, 0, width * sizeof(unsigned int),
|
||||
streams[0]));
|
||||
|
||||
/*************************************************************************/
|
||||
/* Launch the kernels ****************************************************/
|
||||
std::cout << "Launching a cooperative kernel with " << total_blocks;
|
||||
std::cout << " thread blocks, each with " << warp_size << " threads";
|
||||
std::cout << std::endl;
|
||||
|
||||
void *coop_params[4];
|
||||
coop_params[0] = reinterpret_cast<void*>(&first_dev_array);
|
||||
coop_params[1] = reinterpret_cast<void*>(&second_dev_array);
|
||||
coop_params[2] = reinterpret_cast<void*>(&loops);
|
||||
coop_params[3] = reinterpret_cast<void*>(&width);
|
||||
HIPCHECK(hipLaunchCooperativeKernel(reinterpret_cast<void*>(coop_kernel),
|
||||
total_blocks, warp_size, coop_params,
|
||||
0, streams[0]));
|
||||
|
||||
/*************************************************************************/
|
||||
/* Read back the buffers and print out their data ************************/
|
||||
unsigned int *first_array = (unsigned int*)calloc(width,
|
||||
sizeof(unsigned int));
|
||||
unsigned int *second_array = (unsigned int*)calloc(width,
|
||||
sizeof(unsigned int));
|
||||
HIPCHECK(hipMemcpyAsync(first_array, first_dev_array,
|
||||
width * sizeof(unsigned int),
|
||||
hipMemcpyDeviceToHost, streams[0]));
|
||||
|
||||
HIPCHECK(hipMemcpyAsync(second_array, second_dev_array,
|
||||
width * sizeof(unsigned int),
|
||||
hipMemcpyDeviceToHost, streams[0]));
|
||||
|
||||
std::cout << "Waiting for cooperative work to finish..." << std::endl;
|
||||
std::cout << std::flush;
|
||||
|
||||
HIPCHECK(hipStreamSynchronize(streams[0]));
|
||||
|
||||
|
||||
int ret_val = 0;
|
||||
|
||||
std::cout << "Attemping to verify buffers." << std::endl;
|
||||
std::cout << std::flush;
|
||||
ret_val = verify_coop_arrays(loops, input_buffer, first_array,
|
||||
second_array, width);
|
||||
if (!ret_val) {
|
||||
std::cout << "It appears that inter-thread data sharing at ";
|
||||
std::cout << "grid_group sync points works properly!" << std::endl;
|
||||
} else {
|
||||
flag = 1;
|
||||
}
|
||||
for (int k = 0; k < 2; ++k) {
|
||||
HIPCHECK(hipStreamDestroy(streams[k]));
|
||||
}
|
||||
HIPCHECK(hipFree(first_dev_array));
|
||||
HIPCHECK(hipFree(second_dev_array));
|
||||
free(input_buffer);
|
||||
free(first_array);
|
||||
free(second_array);
|
||||
}
|
||||
if (!flag) {
|
||||
passed();
|
||||
} else {
|
||||
failed("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,568 @@
|
||||
/*
|
||||
Copyright (c) 2020-present Advanced Micro Devices, Inc. All rights reserved.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANNTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INNCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANNY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER INN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR INN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
// Test Description:
|
||||
/*The general idea of the application is to test how Cooperative Groups kernel
|
||||
launches work when launching too many warps to multiple target devices. This
|
||||
tests the following failure modes for hipLaunchCooperativeKernelMultiDevice:
|
||||
1) Do not launch more warps to any device than can fit on that device
|
||||
2) All device targets for the multi-device launch function must be different
|
||||
3) All streams must be explicit (non-NULL)
|
||||
4) The kernels sent in must be identical between devices
|
||||
5) The grid and block sizes must be identical between devices
|
||||
6) The block dimensions must be non-zero
|
||||
7) The dynamic shared memory size must be identical between devices.
|
||||
|
||||
This test ensures that the proper error conditions are returned, even if the
|
||||
target kernel does not actually use any fo the cooperative groups features.
|
||||
|
||||
Note that tests 4, 5, and 7 only hold on Nvidia GPUs. AMD GPUs running ROCm
|
||||
do not have these constraints. As such, the test checks to see whether they
|
||||
should fail or succeed and compares this to what actually happens.
|
||||
*/
|
||||
|
||||
/* HIT_START
|
||||
* BUILD: %t %s ../../test_common.cpp EXCLUDE_HIP_PLATFORM nvcc
|
||||
* TEST: %t
|
||||
* HIT_END
|
||||
*/
|
||||
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <hip/hip_cooperative_groups.h>
|
||||
#include "test_common.h"
|
||||
|
||||
static inline void hipCheckAndFail(hipError_t errval,
|
||||
const char *file, int line) {
|
||||
hipError_t last_err = hipGetLastError();
|
||||
if (errval != hipSuccess) {
|
||||
std::cerr << "hip error: " << hipGetErrorString(errval);
|
||||
std::cerr << std::endl;
|
||||
std::cerr << " Location: " << file << ":" << line << std::endl;
|
||||
failed("");
|
||||
}
|
||||
if (last_err != errval) {
|
||||
std::cerr << "Error: the return value of a function was not the same ";
|
||||
std::cerr << "as the value returned by hipGetLastError()" << std::endl;
|
||||
std::cerr << " Location: " << file << ":" << line << std::endl;
|
||||
std::cerr << " Function returned: " << hipGetErrorString(errval);
|
||||
std::cerr << " (" << errval << ")" << std::endl;
|
||||
std::cerr << "hipGetLastError() returned: " << hipGetErrorString(last_err);
|
||||
std::cerr << " (" << last_err << ")" << std::endl;
|
||||
failed("");
|
||||
}
|
||||
}
|
||||
#define hipCheckErr(errval) \
|
||||
do { hipCheckAndFail((errval), __FILE__, __LINE__); } while (0)
|
||||
|
||||
static int cooperative_groups_support(int device_id) {
|
||||
hipError_t err;
|
||||
|
||||
int cooperative_attribute;
|
||||
HIPCHECK(hipDeviceGetAttribute(&cooperative_attribute,
|
||||
hipDeviceAttributeCooperativeLaunch, device_id));
|
||||
if (!cooperative_attribute) {
|
||||
std::cerr << "Cooperative launch support not available in ";
|
||||
std::cerr << "the device attribute for device " << device_id;
|
||||
std::cerr << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int multi_gpu_cooperative_attribute;
|
||||
HIPCHECK(hipDeviceGetAttribute(&multi_gpu_cooperative_attribute,
|
||||
hipDeviceAttributeCooperativeMultiDeviceLaunch, device_id));
|
||||
|
||||
if (!multi_gpu_cooperative_attribute) {
|
||||
std::cerr << "Multi-GPU cooperative launch support not available in ";
|
||||
std::cerr << "the device attribute for device " << device_id;
|
||||
std::cerr << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, device_id));
|
||||
if (device_properties.cooperativeLaunch == 0) {
|
||||
std::cerr << "Cooperative group support not available in ";
|
||||
std::cerr << "device properties." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
if (device_properties.cooperativeMultiDeviceLaunch == 0) {
|
||||
std::cerr << "Multi-GPU cooperative group support not available in ";
|
||||
std::cerr << "device properties." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int support_for_separate_kernels(int device_id) {
|
||||
hipError_t err;
|
||||
|
||||
int separate_kernel_supported;
|
||||
HIPCHECK(hipDeviceGetAttribute(&separate_kernel_supported,
|
||||
hipDeviceAttributeCooperativeMultiDeviceUnmatchedFunc,
|
||||
device_id));
|
||||
if (!separate_kernel_supported) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, device_id));
|
||||
if (device_properties.cooperativeMultiDeviceUnmatchedFunc == 0) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int support_for_separate_grid_sizes(int device_id) {
|
||||
hipError_t err;
|
||||
int separate_sizes_supported;
|
||||
HIPCHECK(hipDeviceGetAttribute(&separate_sizes_supported,
|
||||
hipDeviceAttributeCooperativeMultiDeviceUnmatchedGridDim,
|
||||
device_id));
|
||||
if (!separate_sizes_supported) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, device_id));
|
||||
if (device_properties.cooperativeMultiDeviceUnmatchedGridDim == 0) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int support_for_separate_block_dims(int device_id) {
|
||||
hipError_t err;
|
||||
int separate_sizes_supported;
|
||||
HIPCHECK(hipDeviceGetAttribute(&separate_sizes_supported,
|
||||
hipDeviceAttributeCooperativeMultiDeviceUnmatchedBlockDim,
|
||||
device_id));
|
||||
if (!separate_sizes_supported) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, device_id));
|
||||
if (device_properties.cooperativeMultiDeviceUnmatchedBlockDim == 0) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int support_for_separate_shared_sizes(int device_id) {
|
||||
hipError_t err;
|
||||
int separate_sizes_supported;
|
||||
HIPCHECK(hipDeviceGetAttribute(&separate_sizes_supported,
|
||||
hipDeviceAttributeCooperativeMultiDeviceUnmatchedSharedMem,
|
||||
device_id));
|
||||
if (!separate_sizes_supported) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, device_id));
|
||||
if (device_properties.cooperativeMultiDeviceUnmatchedSharedMem == 0) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
__global__ void test_kernel(long long *array) {
|
||||
unsigned int rank = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
array[rank] += clock64();
|
||||
}
|
||||
|
||||
__global__ void second_test_kernel(long long *array) {
|
||||
unsigned int rank = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
array[rank] += clock64();
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
hipError_t err;
|
||||
/*************************************************************************/
|
||||
/* Parse the command line parameters *************************************/
|
||||
// Arguments to pull out of the command line.
|
||||
int device_num, FailFlag = 0;
|
||||
HIPCHECK(hipGetDeviceCount(&device_num));
|
||||
if (device_num < 2) {
|
||||
std::cout << "This test requires atleast two gpus but the system has ";
|
||||
std::cout << " only "<< device_num <<std::endl;
|
||||
std::cout << "The test is skipping with Pass result" << std::endl;
|
||||
passed();
|
||||
}
|
||||
for (int dev = 0; dev < (device_num-1); ++dev) {
|
||||
std::cout << "First device number: " << dev << std::endl;
|
||||
std::cout << "Second device number: " << (dev + 1) << std::endl;
|
||||
|
||||
/*************************************************************************/
|
||||
/* Test whether target devices support cooperative groups ****************/
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (!cooperative_groups_support((dev + i))) {
|
||||
std::cout << "Skipping the test with Pass result.\n";
|
||||
passed();
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* We will try to launch more waves than the GPUs can fit. ***************/
|
||||
int warp_sizes[2];
|
||||
int num_sms[2];
|
||||
hipDeviceProp_t device_properties[2];
|
||||
int warp_size = INT_MAX;
|
||||
int num_sm = INT_MAX;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties[i], (dev + i)));
|
||||
warp_sizes[i] = device_properties[i].warpSize;
|
||||
if (warp_sizes[i] < warp_size) {
|
||||
warp_size = warp_sizes[i];
|
||||
}
|
||||
num_sms[i] = device_properties[i].multiProcessorCount;
|
||||
if (num_sms[i] < num_sm) {
|
||||
num_sm = num_sms[i];
|
||||
}
|
||||
std::cout << "Device " << (dev + i);
|
||||
std::cout << " name: " << device_properties[i].name << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// Calculate the device occupancy to know how many blocks can be run.
|
||||
int max_blocks_per_sm_arr[2];
|
||||
int max_blocks_per_sm = INT_MAX;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipSetDevice((dev + i)));
|
||||
HIPCHECK(hipOccupancyMaxActiveBlocksPerMultiprocessor(
|
||||
&max_blocks_per_sm_arr[i], test_kernel, warp_size, 0));
|
||||
if (max_blocks_per_sm_arr[i] < max_blocks_per_sm) {
|
||||
max_blocks_per_sm = max_blocks_per_sm_arr[i];
|
||||
}
|
||||
}
|
||||
|
||||
int desired_blocks = max_blocks_per_sm * num_sm;
|
||||
|
||||
/*************************************************************************/
|
||||
/* Create the streams we will use in this test. **************************/
|
||||
hipStream_t streams[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipSetDevice((dev + i)));
|
||||
HIPCHECK(hipStreamCreate(&streams[i]));
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* Set up data to pass into the kernel ***********************************/
|
||||
|
||||
// Alocate the host input buffer, and two device-focused buffers per GPU
|
||||
// that we will use for our test.
|
||||
unsigned int *good_dev_array[2];
|
||||
unsigned int *bad_dev_array[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
int good_size = desired_blocks * warp_size * sizeof(long long);
|
||||
int bad_size = 2 * desired_blocks * warp_size * sizeof(long long);
|
||||
|
||||
HIPCHECK(hipSetDevice((dev + i)));
|
||||
HIPCHECK(hipMalloc(reinterpret_cast<void**>(&good_dev_array[i]),
|
||||
good_size));
|
||||
HIPCHECK(hipMemsetAsync(good_dev_array[i], 0, good_size, streams[i]));
|
||||
HIPCHECK(hipMalloc(reinterpret_cast<void**>(&bad_dev_array[i]),
|
||||
bad_size));
|
||||
HIPCHECK(hipMemsetAsync(bad_dev_array[i], 0, bad_size, streams[i]));
|
||||
}
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
|
||||
/*************************************************************************/
|
||||
/* Launch the kernels ****************************************************/
|
||||
std::cout << "Launching a multi-GPU cooperative kernel with too many ";
|
||||
std::cout << "warps..." << std::endl;
|
||||
|
||||
void *dev_params[2][1];
|
||||
hipLaunchParams md_params[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
dev_params[i][0] = reinterpret_cast<void*>(&bad_dev_array[i]);
|
||||
|
||||
md_params[i].func = reinterpret_cast<void*>(test_kernel);
|
||||
md_params[i].gridDim = 2 * desired_blocks;
|
||||
md_params[i].blockDim = warp_size;
|
||||
md_params[i].sharedMem = 0;
|
||||
md_params[i].stream = streams[i];
|
||||
md_params[i].args = dev_params[i];
|
||||
}
|
||||
|
||||
err = hipLaunchCooperativeKernelMultiDevice(md_params, 2, 0);
|
||||
if (err != hipErrorCooperativeLaunchTooLarge) {
|
||||
std::cerr << "ERROR! Tried to launch a multi-GPU cooperative kernel ";
|
||||
std::cerr << "with too many warps." << std::endl;
|
||||
std::cerr << "This SHOULD have failed with the error ";
|
||||
std::cerr << "hipErrorCooperativeLaunchTooLarge (";
|
||||
std::cerr << hipErrorCooperativeLaunchTooLarge << ")." << std::endl;
|
||||
std::cerr << "Instead, the launch returned " << hipGetErrorName(err);
|
||||
std::cerr << " (" << err << ")" << std::endl;
|
||||
FailFlag = 1;
|
||||
} else {
|
||||
std::cout << "\tProperly saw this return ";
|
||||
std::cout << "hipErrorCooperativeLaunchTooLarge" << std::endl;
|
||||
}
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
|
||||
std::cout << "Launching a multi-GPU cooperative kernel to the same ";
|
||||
std::cout << "device twice..." << std::endl;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
dev_params[i][0] = reinterpret_cast<void*>(&good_dev_array[i]);
|
||||
md_params[i].gridDim = desired_blocks;
|
||||
md_params[i].stream = streams[0];
|
||||
}
|
||||
err = hipLaunchCooperativeKernelMultiDevice(md_params, 2, 0);
|
||||
if (err != hipErrorInvalidDevice) {
|
||||
std::cerr << "ERROR! Tried to launch a multi-GPU cooperative kernel ";
|
||||
std::cerr << "to the same device twice." << std::endl;
|
||||
std::cerr << "This SHOULD have failed with the error ";
|
||||
std::cerr << "hipErrorInvalidDevice (";
|
||||
std::cerr << hipErrorInvalidDevice << ")." << std::endl;
|
||||
std::cerr << "Instead, the launch returned " << hipGetErrorName(err);
|
||||
std::cerr << " (" << err << ")" << std::endl;
|
||||
FailFlag = 1;
|
||||
} else {
|
||||
std::cout << "\tProperly saw this return ";
|
||||
std::cout << "hipErrorInvalidDevice" << std::endl;
|
||||
}
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
|
||||
std::cout << "Launching a multi-GPU cooperative kernel to the NULL ";
|
||||
std::cout << "stream" << std::endl;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
md_params[i].stream = NULL;
|
||||
}
|
||||
err = hipLaunchCooperativeKernelMultiDevice(md_params, 2, 0);
|
||||
if (err != hipErrorInvalidResourceHandle) {
|
||||
std::cerr << "ERROR! Tried to launch a multi-GPU cooperative kernel ";
|
||||
std::cerr << "to the NULL stream." << std::endl;
|
||||
std::cerr << "This SHOULD have failed with the error ";
|
||||
std::cerr << "hipErrorInvalidResourceHandle (";
|
||||
std::cerr << hipErrorInvalidResourceHandle << ")." << std::endl;
|
||||
std::cerr << "Instead, the launch returned " << hipGetErrorName(err);
|
||||
std::cerr << " (" << err << ")" << std::endl;
|
||||
FailFlag = 1;
|
||||
} else {
|
||||
std::cout << "\tProperly saw this return ";
|
||||
std::cout << "hipErrorInvalidResourceHandle" << std::endl;
|
||||
}
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
|
||||
std::cout << "Launching a multi-GPU cooperative kernel with two ";
|
||||
std::cout << "different kernels." << std::endl;
|
||||
bool supports_sep_kernels = true;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
md_params[i].stream = streams[i];
|
||||
if (!support_for_separate_kernels((dev + i))) {
|
||||
supports_sep_kernels = false;
|
||||
}
|
||||
}
|
||||
md_params[1].func = reinterpret_cast<void*>(second_test_kernel);
|
||||
err = hipLaunchCooperativeKernelMultiDevice(md_params, 2, 0);
|
||||
if ((supports_sep_kernels && err != hipSuccess) ||
|
||||
(!supports_sep_kernels && err != hipErrorInvalidValue)) {
|
||||
if (supports_sep_kernels) {
|
||||
std::cerr << "ERROR! Tried to launch a multi-GPU cooperative kernel ";
|
||||
std::cerr << "with two different kernels." << std::endl;
|
||||
std::cerr << "This SHOULD have succeeded with hipSuccess (";
|
||||
std::cerr << hipSuccess << ")." << std::endl;
|
||||
std::cerr << "Instead, the launch returned " << hipGetErrorName(err);
|
||||
std::cerr << " (" << err << ")" << std::endl;
|
||||
} else {
|
||||
std::cerr << "ERROR! Tried to launch a multi-GPU cooperative kernel ";
|
||||
std::cerr << "with two different kernels." << std::endl;
|
||||
std::cerr << "This SHOULD have failed with the error ";
|
||||
std::cerr << "hipErrorInvalidValue (";
|
||||
std::cerr << hipErrorInvalidValue << ")." << std::endl;
|
||||
std::cerr << "Instead, the launch returned " << hipGetErrorName(err);
|
||||
std::cerr << " (" << err << ")" << std::endl;
|
||||
}
|
||||
FailFlag = 1;
|
||||
} else {
|
||||
std::cout << "\tProperly saw this return ";
|
||||
if (supports_sep_kernels) {
|
||||
std::cout << "hipSuccess" << std::endl;
|
||||
} else {
|
||||
std::cout << "hipErrorInvalidValue" << std::endl;
|
||||
}
|
||||
}
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
|
||||
std::cout << "Launching a multi-GPU cooperative kernel with two ";
|
||||
std::cout << "different grid sizes." << std::endl;
|
||||
bool supports_sep_sizes = true;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
md_params[i].func = reinterpret_cast<void*>(test_kernel);
|
||||
md_params[i].gridDim = i+1;
|
||||
if (!support_for_separate_grid_sizes((dev + i))) {
|
||||
supports_sep_sizes = false;
|
||||
}
|
||||
}
|
||||
err = hipLaunchCooperativeKernelMultiDevice(md_params, 2, 0);
|
||||
if ((supports_sep_sizes && err != hipSuccess) ||
|
||||
(!supports_sep_sizes && err == hipErrorInvalidValue)) {
|
||||
if (supports_sep_sizes) {
|
||||
std::cerr << "ERROR! Tried to launch a multi-GPU cooperative kernel ";
|
||||
std::cerr << "with two different grid sizes." << std::endl;
|
||||
std::cerr << "This SHOULD have succeeded with hipSuccess (";
|
||||
std::cerr << hipSuccess << ")." << std::endl;
|
||||
std::cerr << "Instead, the launch returned " << hipGetErrorName(err);
|
||||
std::cerr << " (" << err << ")" << std::endl;
|
||||
} else {
|
||||
std::cerr << "ERROR! Tried to launch a multi-GPU cooperative kernel ";
|
||||
std::cerr << "with two different grid sizes." << std::endl;
|
||||
std::cerr << "This SHOULD have failed with the error ";
|
||||
std::cerr << "hipErrorInvalidValue (";
|
||||
std::cerr << hipErrorInvalidValue << ")." << std::endl;
|
||||
std::cerr << "Instead, the launch returned " << hipGetErrorName(err);
|
||||
std::cerr << " (" << err << ")" << std::endl;
|
||||
FailFlag = 1;
|
||||
}
|
||||
} else {
|
||||
std::cout << "\tProperly saw this return ";
|
||||
if (supports_sep_kernels) {
|
||||
std::cout << "hipSuccess" << std::endl;
|
||||
} else {
|
||||
std::cout << "hipErrorInvalidValue" << std::endl;
|
||||
}
|
||||
}
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
|
||||
std::cout << "Launching a multi-GPU cooperative kernel with two ";
|
||||
std::cout << "different block dimensions." << std::endl;
|
||||
supports_sep_sizes = true;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
md_params[i].gridDim = desired_blocks;
|
||||
md_params[i].blockDim = i+1;
|
||||
if (!support_for_separate_block_dims((dev + i))) {
|
||||
supports_sep_sizes = false;
|
||||
}
|
||||
}
|
||||
err = hipLaunchCooperativeKernelMultiDevice(md_params, 2, 0);
|
||||
if ((supports_sep_sizes && err != hipSuccess) ||
|
||||
(!supports_sep_sizes && err == hipErrorInvalidValue)) {
|
||||
if (supports_sep_sizes) {
|
||||
std::cerr << "ERROR! Tried to launch a multi-GPU cooperative kernel ";
|
||||
std::cerr << "with two different block dimensions." << std::endl;
|
||||
std::cerr << "This SHOULD have succeeded with hipSuccess (";
|
||||
std::cerr << hipSuccess << ")." << std::endl;
|
||||
std::cerr << "Instead, the launch returned " << hipGetErrorName(err);
|
||||
std::cerr << " (" << err << ")" << std::endl;
|
||||
} else {
|
||||
std::cerr << "ERROR! Tried to launch a multi-GPU cooperative kernel ";
|
||||
std::cerr << "with two different block dimensions." << std::endl;
|
||||
std::cerr << "This SHOULD have failed with the error ";
|
||||
std::cerr << "hipErrorInvalidValue (";
|
||||
std::cerr << hipErrorInvalidValue << ")." << std::endl;
|
||||
std::cerr << "Instead, the launch returned " << hipGetErrorName(err);
|
||||
std::cerr << " (" << err << ")" << std::endl;
|
||||
FailFlag = 1;
|
||||
}
|
||||
} else {
|
||||
std::cout << "\tProperly saw this return ";
|
||||
if (supports_sep_kernels) {
|
||||
std::cout << "hipSuccess" << std::endl;
|
||||
} else {
|
||||
std::cout << "hipErrorInvalidValue" << std::endl;
|
||||
}
|
||||
}
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
|
||||
std::cout << "Launching a multi-GPU cooperative kernel with block ";
|
||||
std::cout << "dimensions of zero." << std::endl;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
md_params[i].blockDim = 0;
|
||||
}
|
||||
err = hipLaunchCooperativeKernelMultiDevice(md_params, 2, 0);
|
||||
if (err != hipErrorInvalidConfiguration) {
|
||||
std::cerr << "ERROR! Tried to launch a multi-GPU cooperative kernel ";
|
||||
std::cerr << "with block dimensions of zero." << std::endl;
|
||||
std::cerr << "This SHOULD have failed with the error ";
|
||||
std::cerr << "hipErrorInvalidConfiguration (";
|
||||
std::cerr << hipErrorInvalidConfiguration << ")." << std::endl;
|
||||
std::cerr << "Instead, the launch returned " << hipGetErrorName(err);
|
||||
std::cerr << " (" << err << ")" << std::endl;
|
||||
FailFlag = 1;
|
||||
} else {
|
||||
std::cout << "\tProperly saw this return ";
|
||||
std::cout << "hipErrorInvalidConfiguration" << std::endl;
|
||||
}
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
|
||||
std::cout << "Launching a multi-GPU cooperative kernel with two ";
|
||||
std::cout << "different shared memory sizes." << std::endl;
|
||||
supports_sep_sizes = true;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
md_params[i].blockDim = warp_size;
|
||||
md_params[i].sharedMem = i;
|
||||
if (!support_for_separate_shared_sizes((dev + i))) {
|
||||
supports_sep_sizes = false;
|
||||
}
|
||||
}
|
||||
err = hipLaunchCooperativeKernelMultiDevice(md_params, 2, 0);
|
||||
if ((supports_sep_sizes && err != hipSuccess) ||
|
||||
(!supports_sep_sizes && err == hipErrorInvalidValue)) {
|
||||
if (supports_sep_sizes) {
|
||||
std::cerr << "ERROR! Tried to launch a multi-GPU cooperative kernel ";
|
||||
std::cerr << "with two different shared memory sizes." << std::endl;
|
||||
std::cerr << "This SHOULD have succeeded with hipSuccess (";
|
||||
std::cerr << hipSuccess << ")." << std::endl;
|
||||
std::cerr << "Instead, the launch returned " << hipGetErrorName(err);
|
||||
std::cerr << " (" << err << ")" << std::endl;
|
||||
} else {
|
||||
std::cerr << "ERROR! Tried to launch a multi-GPU cooperative kernel ";
|
||||
std::cerr << "with two different shared memory sizes." << std::endl;
|
||||
std::cerr << "This SHOULD have failed with the error ";
|
||||
std::cerr << "hipErrorInvalidValue (";
|
||||
std::cerr << hipErrorInvalidValue << ")." << std::endl;
|
||||
std::cerr << "Instead, the launch returned " << hipGetErrorName(err);
|
||||
std::cerr << " (" << err << ")" << std::endl;
|
||||
FailFlag = 1;
|
||||
}
|
||||
} else {
|
||||
std::cout << "\tProperly saw this return ";
|
||||
if (supports_sep_kernels) {
|
||||
std::cout << "hipSuccess" << std::endl;
|
||||
} else {
|
||||
std::cout << "hipErrorInvalidValue" << std::endl;
|
||||
}
|
||||
}
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
|
||||
std::cout << "Launching a multi-GPU cooperative kernel with maximum ";
|
||||
std::cout << "number of warps..." << std::endl;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
md_params[i].sharedMem = 0;
|
||||
}
|
||||
HIPCHECK(hipLaunchCooperativeKernelMultiDevice(md_params, 2, 0));
|
||||
std::cout << "\tProperly launched." << std::endl;
|
||||
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
for (int m = 0; m < 2; ++m) {
|
||||
HIPCHECK(hipFree(good_dev_array[m]));
|
||||
HIPCHECK(hipFree(bad_dev_array[m]));
|
||||
HIPCHECK(hipStreamDestroy(streams[m]));
|
||||
}
|
||||
if (FailFlag == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (FailFlag == 1) {
|
||||
failed("");
|
||||
} else {
|
||||
passed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,581 @@
|
||||
/*
|
||||
Copyright (c) 2020-present Advanced Micro Devices, Inc. All rights reserved.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANNTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INNCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANNY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER INN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR INN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
// Test Description:
|
||||
/*The general idea of the application is to test how multi-GPU Cooperative
|
||||
Groups kernel launches to a stream interact with other things that may be
|
||||
simultaneously running in the same streams.
|
||||
|
||||
The HIP specification says that a multi-GPU cooperative launch will wait
|
||||
until all of the streams it's using finish their work. Only then will the
|
||||
cooperative kernel be launched to all of the devices. Then no other work
|
||||
can take part in the any of the streams until all of the multi-GPU
|
||||
cooperative work is done.
|
||||
|
||||
However, there are flags that allow you to disable each of these
|
||||
serialization points: hipCooperativeLaunchMultiDeviceNoPreSync and
|
||||
hipCooperativeLaunchMultiDeviceNoPostSync.
|
||||
|
||||
As such, this benchmark tests the following five situations launching
|
||||
to two GPUs (and thus two streams):
|
||||
|
||||
1. Normal multi-GPU cooperative kernel:
|
||||
This should result in the following pattern:
|
||||
Stream 0: Cooperative
|
||||
Stream 1: Cooperative
|
||||
2. Regular kernel launches and multi-GPU cooperative kernel launches
|
||||
with the default flags, resulting in the following pattern:
|
||||
Stream 0: Regular --> Cooperative
|
||||
Stream 1: --> Cooperative --> Regular
|
||||
|
||||
3. Regular kernel launches and multi-GPU cooperative kernel launches
|
||||
that turn off "pre-sync". This should allow a cooperative kernel
|
||||
to launch even if work is already in a stream pointing to
|
||||
another GPU.
|
||||
This should result in the following pattern:
|
||||
Stream 0: Regular --> Cooperative
|
||||
Stream 1: Cooperative --> Regular
|
||||
|
||||
4. Regular kernel launches and multi-GPU cooperative kernel launches
|
||||
that turn off "post-sync". This should allow a new kernel to enter
|
||||
a GPU even if another GPU still has a cooperative kernel on it.
|
||||
This should result in the following pattern:
|
||||
Stream 0: Regular --> Cooperative
|
||||
Stream 1: --> Cooperative--> Regular
|
||||
|
||||
5. Regular kernel launches and multi-GPU cooperative kernel launches
|
||||
that turn off both pre- and post-sync. This should allow any of
|
||||
the kernels to launch to their GPU regardless of the status of
|
||||
other kernels in other multi-GPU stream groups.
|
||||
This should result in the following pattern:
|
||||
Stream 0: Regular --> Cooperative
|
||||
Stream 1: Cooperative --> Regular
|
||||
|
||||
We time how long it takes to run each of these benchmarks and print it as
|
||||
the output of the benchmark. The kernels themselves are just useless time-
|
||||
wasting code so that the kernel takes a meaningful amount of time on the
|
||||
GPU before it exits. We only launch a single wavefront for each kernel, so
|
||||
any serialization should not be because of GPU occupancy concerns.
|
||||
|
||||
If tests 2, 3, and 4 take roughly 3x as long as #1, that implies that
|
||||
cooperative kernels are serialized as expected.
|
||||
|
||||
If test #5 takes roughly twice as long as #1, that implies that the
|
||||
overlap-allowing flags work as expected.
|
||||
*/
|
||||
|
||||
/* HIT_START
|
||||
* BUILD: %t %s ../../test_common.cpp EXCLUDE_HIP_PLATFORM nvcc
|
||||
* TEST: %t
|
||||
* HIT_END
|
||||
*/
|
||||
|
||||
#include <chrono>
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <hip/hip_cooperative_groups.h>
|
||||
#include "test_common.h"
|
||||
|
||||
static inline void hipCheckAndFail(hipError_t errval,
|
||||
const char *file, int line) {
|
||||
hipError_t last_err = hipGetLastError();
|
||||
if (errval != hipSuccess) {
|
||||
std::cerr << "hip error: " << hipGetErrorString(errval);
|
||||
std::cerr << std::endl;
|
||||
std::cerr << " Location: " << file << ":" << line << std::endl;
|
||||
failed("");
|
||||
}
|
||||
if (last_err != errval) {
|
||||
std::cerr << "Error: the return value of a function was not the same ";
|
||||
std::cerr << "as the value returned by hipGetLastError()" << std::endl;
|
||||
std::cerr << " Location: " << file << ":" << line << std::endl;
|
||||
std::cerr << " Function returned: " << hipGetErrorString(errval);
|
||||
std::cerr << " (" << errval << ")" << std::endl;
|
||||
std::cerr << "hipGetLastError() returned: " << hipGetErrorString(last_err);
|
||||
std::cerr << " (" << last_err << ")" << std::endl;
|
||||
failed("");
|
||||
}
|
||||
}
|
||||
#define hipCheckErr(errval) \
|
||||
do { hipCheckAndFail((errval), __FILE__, __LINE__); } while (0)
|
||||
|
||||
static int cooperative_groups_support(int device_id) {
|
||||
hipError_t err;
|
||||
int cooperative_attribute;
|
||||
HIPCHECK(hipDeviceGetAttribute(&cooperative_attribute,
|
||||
hipDeviceAttributeCooperativeLaunch, device_id));
|
||||
if (!cooperative_attribute) {
|
||||
std::cerr << "Cooperative launch support not available in ";
|
||||
std::cerr << "the device attribute for device " << device_id;
|
||||
std::cerr << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int multi_gpu_cooperative_attribute;
|
||||
HIPCHECK(hipDeviceGetAttribute(&multi_gpu_cooperative_attribute,
|
||||
hipDeviceAttributeCooperativeMultiDeviceLaunch, device_id));
|
||||
if (!multi_gpu_cooperative_attribute) {
|
||||
std::cerr << "Multi-GPU cooperative launch support not available in ";
|
||||
std::cerr << "the device attribute for device " << device_id;
|
||||
std::cerr << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, device_id));
|
||||
if (device_properties.cooperativeLaunch == 0) {
|
||||
std::cerr << "Cooperative group support not available in ";
|
||||
std::cerr << "device properties." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
if (device_properties.cooperativeMultiDeviceLaunch == 0) {
|
||||
std::cerr << "Multi-GPU cooperative group support not available in ";
|
||||
std::cerr << "device properties." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
__global__ void test_coop_kernel(unsigned int loops, long long *array,
|
||||
int fast_gpu) {
|
||||
cooperative_groups::multi_grid_group mgrid =
|
||||
cooperative_groups::this_multi_grid();
|
||||
unsigned int rank = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
|
||||
if (mgrid.grid_rank() == fast_gpu) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < loops; i++) {
|
||||
long long start_clock = clock64();
|
||||
while (clock64() < (start_clock+1000000)) {}
|
||||
array[rank] += clock64();
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void test_kernel(uint32_t loops, unsigned long long *array) {
|
||||
unsigned int rank = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
|
||||
for (int i = 0; i < loops; i++) {
|
||||
long long start_clock = clock64();
|
||||
while (clock64() < (start_clock+1000000)) {}
|
||||
array[rank] += clock64();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
hipError_t err;
|
||||
int device_num, FailFlag = 0;
|
||||
uint32_t loops = 2000;
|
||||
uint32_t fast_loops = 1;
|
||||
int32_t fast_gpu = -1;
|
||||
HIPCHECK(hipGetDeviceCount(&device_num));
|
||||
if (device_num < 2) {
|
||||
std::cout << "This test requires atleast two gpus but the system has ";
|
||||
std::cout << " only "<< device_num <<std::endl;
|
||||
std::cout << "The test is skipping with Pass result" << std::endl;
|
||||
passed();
|
||||
}
|
||||
for (int dev = 0; dev < (device_num-1); ++dev) {
|
||||
std::cout << "First device number: " << dev << std::endl;
|
||||
std::cout << "Second device number: " << (dev + 1) << std::endl;
|
||||
std::cout << "Loops: " << loops << std::endl;
|
||||
|
||||
/*************************************************************************/
|
||||
/* Test whether target devices support cooperative groups ****************/
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (!cooperative_groups_support(dev + i)) {
|
||||
std::cout << "Skipping the test with Pass result.\n";
|
||||
passed();
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* We will launch enough waves to fill up all of the GPU *****************/
|
||||
int warp_sizes[2];
|
||||
int num_sms[2];
|
||||
hipDeviceProp_t device_properties[2];
|
||||
int warp_size = INT_MAX;
|
||||
int num_sm = INT_MAX;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties[i], (dev + i)));
|
||||
warp_sizes[i] = device_properties[i].warpSize;
|
||||
if (warp_sizes[i] < warp_size) {
|
||||
warp_size = warp_sizes[i];
|
||||
}
|
||||
num_sms[i] = device_properties[i].multiProcessorCount;
|
||||
if (num_sms[i] < num_sm) {
|
||||
num_sm = num_sms[i];
|
||||
}
|
||||
std::cout << "Device " << (i + 1);
|
||||
std::cout << " name: " << device_properties[i].name << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// Calculate the device occupancy to know how many blocks can be run.
|
||||
int max_blocks_per_sm_arr[2];
|
||||
int max_blocks_per_sm = INT_MAX;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipSetDevice(dev + i));
|
||||
HIPCHECK(hipOccupancyMaxActiveBlocksPerMultiprocessor(
|
||||
&max_blocks_per_sm_arr[i], test_kernel, warp_size, 0));
|
||||
if (max_blocks_per_sm_arr[i] < max_blocks_per_sm) {
|
||||
max_blocks_per_sm = max_blocks_per_sm_arr[i];
|
||||
}
|
||||
}
|
||||
int desired_blocks = 1;
|
||||
|
||||
if (desired_blocks > max_blocks_per_sm * num_sm) {
|
||||
std::cerr << "The requested number of blocks will not fit on the GPU";
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "You requested " << desired_blocks << " but we can only ";
|
||||
std::cerr << "fit " << (max_blocks_per_sm * num_sm) << std::endl;
|
||||
failed("");
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* Create the streams we will use in this test. **************************/
|
||||
hipStream_t streams[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipSetDevice(dev + i));
|
||||
HIPCHECK(hipStreamCreate(&streams[i]));
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* Set up data to pass into the kernelx **********************************/
|
||||
|
||||
// Alocate the host input buffer, and two device-focused buffers that we
|
||||
// will use for our test.
|
||||
unsigned long long *dev_array[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
int good_size = desired_blocks * warp_size * sizeof(long long);
|
||||
HIPCHECK(hipSetDevice(dev + i));
|
||||
HIPCHECK(hipMalloc(reinterpret_cast<void**>(&dev_array[i]), good_size));
|
||||
HIPCHECK(hipMemsetAsync(dev_array[i], 0, good_size, streams[i]));
|
||||
}
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipSetDevice(dev + i));
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* Launch the kernels ****************************************************/
|
||||
void *dev_params[2][3];
|
||||
hipLaunchParams md_params[2];
|
||||
std::chrono::time_point<std::chrono::system_clock> start_time[6];
|
||||
std::chrono::time_point<std::chrono::system_clock> end_time[6];
|
||||
|
||||
std::cout << "Test 0: Launching a multi-GPU cooperative kernel...\n";
|
||||
std::cout << "This should result in the following pattern:" << std::endl;
|
||||
std::cout << "GPU " << dev << ": Long Coop Kernel" << std::endl;
|
||||
std::cout << "GPU " << (dev + 1) << ": Long Coop Kernel" << std::endl;
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
dev_params[i][0] = reinterpret_cast<void*>(&loops);
|
||||
dev_params[i][1] = reinterpret_cast<void*>(&dev_array[i]);
|
||||
dev_params[i][2] = reinterpret_cast<void*>(&fast_gpu);
|
||||
md_params[i].func = reinterpret_cast<void*>(test_coop_kernel);
|
||||
md_params[i].gridDim = desired_blocks;
|
||||
md_params[i].blockDim = warp_size;
|
||||
md_params[i].sharedMem = 0;
|
||||
md_params[i].stream = streams[i];
|
||||
md_params[i].args = dev_params[i];
|
||||
}
|
||||
|
||||
start_time[0] = std::chrono::system_clock::now();
|
||||
HIPCHECK(hipLaunchCooperativeKernelMultiDevice(md_params, 2, 0));
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipSetDevice(dev + i));
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
}
|
||||
end_time[0] = std::chrono::system_clock::now();
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Test 1: Launching a multi-GPU cooperative kernel with the ";
|
||||
std::cout << "following pattern:" << std::endl;
|
||||
std::cout << "GPU " << dev << ": Standard Kernel --> Long Coop Kernel\n";
|
||||
std::cout << "GPU " << (dev + 1) << ": --> Coop ";
|
||||
std::cout << "--> Standard Kernel\n";
|
||||
fast_gpu = 1;
|
||||
start_time[1] = std::chrono::system_clock::now();
|
||||
HIPCHECK(hipSetDevice(dev));
|
||||
hipLaunchKernelGGL(test_kernel, dim3(desired_blocks), dim3(warp_size), 0,
|
||||
streams[0], loops, dev_array[0]);
|
||||
HIPCHECK(hipGetLastError());
|
||||
HIPCHECK(hipLaunchCooperativeKernelMultiDevice(md_params, 2, 0));
|
||||
HIPCHECK(hipSetDevice(dev + 1));
|
||||
hipLaunchKernelGGL(test_kernel, dim3(desired_blocks), dim3(warp_size), 0,
|
||||
streams[1], loops, dev_array[1]);
|
||||
HIPCHECK(hipGetLastError());
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipSetDevice(dev + i));
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
}
|
||||
end_time[1] = std::chrono::system_clock::now();
|
||||
fast_gpu = -1;
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Test 2: Launching a multi-GPU cooperative kernel with the ";
|
||||
std::cout << "following pattern:" << std::endl;
|
||||
std::cout << "GPU " << dev << ": Standard Kernel --> Coop" << std::endl;
|
||||
std::cout << "GPU " << (dev + 1) << ": --> Long Coop";
|
||||
std::cout << " Kernel --> ";
|
||||
std::cout << "Standard Kernel\n";
|
||||
fast_gpu = 0;
|
||||
start_time[2] = std::chrono::system_clock::now();
|
||||
HIPCHECK(hipSetDevice(dev));
|
||||
hipLaunchKernelGGL(test_kernel, dim3(desired_blocks), dim3(warp_size), 0,
|
||||
streams[0], loops, dev_array[0]);
|
||||
HIPCHECK(hipGetLastError());
|
||||
HIPCHECK(hipLaunchCooperativeKernelMultiDevice(md_params, 2, 0));
|
||||
HIPCHECK(hipSetDevice(dev + 1));
|
||||
hipLaunchKernelGGL(test_kernel, dim3(desired_blocks), dim3(warp_size), 0,
|
||||
streams[1], loops, dev_array[1]);
|
||||
HIPCHECK(hipGetLastError());
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipSetDevice(dev + i));
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
}
|
||||
end_time[2] = std::chrono::system_clock::now();
|
||||
fast_gpu = -1;
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Test 3: Launching a multi-GPU cooperative kernel with the ";
|
||||
std::cout << "ability to overlap regular and cooperative kernels ";
|
||||
std::cout << "only at the beginning." << std::endl;
|
||||
std::cout << "This should result in the following pattern:" << std::endl;
|
||||
std::cout << "GPU " << dev << ": Standard Kernel --> Coop" << std::endl;
|
||||
std::cout << "GPU " << (dev + 1) << ": Long Coop Kernel --> Standard";
|
||||
std::cout<< " Kernel\n";
|
||||
fast_gpu = 0;
|
||||
start_time[3] = std::chrono::system_clock::now();
|
||||
HIPCHECK(hipSetDevice(dev));
|
||||
hipLaunchKernelGGL(test_kernel, dim3(desired_blocks), dim3(warp_size), 0,
|
||||
streams[0], loops, dev_array[0]);
|
||||
HIPCHECK(hipGetLastError());
|
||||
HIPCHECK(hipLaunchCooperativeKernelMultiDevice(md_params, 2,
|
||||
hipCooperativeLaunchMultiDeviceNoPreSync));
|
||||
HIPCHECK(hipSetDevice(dev + 1));
|
||||
hipLaunchKernelGGL(test_kernel, dim3(desired_blocks), dim3(warp_size), 0,
|
||||
streams[1], loops, dev_array[1]);
|
||||
HIPCHECK(hipGetLastError());
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipSetDevice(dev + i));
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
}
|
||||
end_time[3] = std::chrono::system_clock::now();
|
||||
fast_gpu = -1;
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Test 4: Launching a multi-GPU cooperative kernel with the ";
|
||||
std::cout << "ability to overlap regular and cooperative kernels ";
|
||||
std::cout << "only at the end." << std::endl;
|
||||
std::cout << "This should result in the following pattern:" << std::endl;
|
||||
std::cout << "GPU " << dev << ": Standard Kernel --> Long Coop Kernel\n";
|
||||
std::cout << "GPU " << (dev + 1) << ": --> Coop --> ";
|
||||
std::cout << "Standard Kernel\n";
|
||||
fast_gpu = 1;
|
||||
start_time[4] = std::chrono::system_clock::now();
|
||||
HIPCHECK(hipSetDevice(dev));
|
||||
hipLaunchKernelGGL(test_kernel, dim3(desired_blocks), dim3(warp_size), 0,
|
||||
streams[0], loops, dev_array[0]);
|
||||
HIPCHECK(hipGetLastError());
|
||||
HIPCHECK(hipLaunchCooperativeKernelMultiDevice(md_params, 2,
|
||||
hipCooperativeLaunchMultiDeviceNoPostSync));
|
||||
HIPCHECK(hipSetDevice(dev + 1));
|
||||
hipLaunchKernelGGL(test_kernel, dim3(desired_blocks), dim3(warp_size), 0,
|
||||
streams[1], loops, dev_array[1]);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipSetDevice(dev + i));
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
}
|
||||
end_time[4] = std::chrono::system_clock::now();
|
||||
fast_gpu = -1;
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Test 5: Launching a multi-GPU cooperative kernel with the ";
|
||||
std::cout << "ability to overlap regular and cooperative kernels";
|
||||
std::cout << std::endl;
|
||||
std::cout << "This should result in the following pattern:" << std::endl;
|
||||
std::cout << "GPU " << dev << ": Standard Kernel --> Long Coop Kernel\n";
|
||||
std::cout << "GPU " << (dev + 1) << ": Long Coop Kernel --> Standard";
|
||||
std::cout << " Kernel\n";
|
||||
start_time[5] = std::chrono::system_clock::now();
|
||||
HIPCHECK(hipSetDevice(dev));
|
||||
hipLaunchKernelGGL(test_kernel, dim3(desired_blocks), dim3(warp_size), 0,
|
||||
streams[0], loops, dev_array[0]);
|
||||
HIPCHECK(hipGetLastError());
|
||||
HIPCHECK(hipLaunchCooperativeKernelMultiDevice(md_params, 2,
|
||||
hipCooperativeLaunchMultiDeviceNoPreSync |
|
||||
hipCooperativeLaunchMultiDeviceNoPostSync));
|
||||
HIPCHECK(hipSetDevice(dev + 1));
|
||||
hipLaunchKernelGGL(test_kernel, dim3(desired_blocks), dim3(warp_size), 0,
|
||||
streams[1], loops, dev_array[1]);
|
||||
HIPCHECK(hipGetLastError());
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipSetDevice(dev + i));
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
}
|
||||
end_time[5] = std::chrono::system_clock::now();
|
||||
|
||||
std::chrono::duration<double> single_kernel_time =
|
||||
(end_time[0] - start_time[0]);
|
||||
std::chrono::duration<double> serialized_gpu0_time =
|
||||
(end_time[1] - start_time[1]);
|
||||
std::chrono::duration<double> serialized_gpu1_time =
|
||||
(end_time[2] - start_time[2]);
|
||||
std::chrono::duration<double> pre_overlapped_time =
|
||||
(end_time[3] - start_time[3]);
|
||||
std::chrono::duration<double> post_overlapped_time =
|
||||
(end_time[4] - start_time[4]);
|
||||
std::chrono::duration<double> overlapped_time =
|
||||
(end_time[5] - start_time[5]);
|
||||
|
||||
std::cout << "Test 0: A single kernel on both GPUs took:" << std::endl;
|
||||
std::cout << " " << single_kernel_time.count();
|
||||
std::cout << " seconds" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Test 1: Serialized set of three kernels with GPU0";
|
||||
std::cout << " being long took:";
|
||||
std::cout << " " << serialized_gpu0_time.count();
|
||||
std::cout << " seconds" << std::endl;
|
||||
std::cerr << "Expect between " << (2.7 * single_kernel_time.count());
|
||||
std::cerr << " and ";
|
||||
std::cerr << (3.3 * single_kernel_time.count()) << " seconds.\n";
|
||||
std::cout << std::endl;
|
||||
std::cout << "Test 2: Serialized set of three kernels with GPU1";
|
||||
std::cout << " being long took:" << std::endl;
|
||||
std::cout << " " << serialized_gpu1_time.count();
|
||||
std::cout << " seconds" << std::endl;
|
||||
std::cerr << "Expect between " << (2.7 * single_kernel_time.count());
|
||||
std::cerr << " and ";
|
||||
std::cerr << (3.3 * single_kernel_time.count()) << " seconds.\n";
|
||||
std::cout << std::endl;
|
||||
std::cout << "Test 3: Multiple kernels with pre-overlap allowed took:\n";
|
||||
std::cout << " " << pre_overlapped_time.count();
|
||||
std::cout << " seconds" << std::endl;
|
||||
std::cerr << "Expect between " << (1.7 * single_kernel_time.count());
|
||||
std::cerr << " and ";
|
||||
std::cerr << (2.3 * single_kernel_time.count()) << " seconds.\n";
|
||||
std::cout << std::endl;
|
||||
std::cout << "Test 4: Multiple kernels with post-overlap allowed took:\n";
|
||||
std::cout << " " << post_overlapped_time.count();
|
||||
std::cout << " seconds" << std::endl;
|
||||
std::cerr << "Expect between " << (1.7 * single_kernel_time.count());
|
||||
std::cerr << " and ";
|
||||
std::cerr << (2.3 * single_kernel_time.count()) << " seconds.";
|
||||
std::cout << std::endl;
|
||||
std::cout << "Test 5: Multiple kernels with overlap allowed took:\n";
|
||||
std::cout << " " << overlapped_time.count();
|
||||
std::cout << " seconds" << std::endl;
|
||||
std::cerr << "Expect between " << (1.8 * single_kernel_time.count());
|
||||
std::cerr << " and ";
|
||||
std::cerr << (2.2 * single_kernel_time.count()) << " seconds.\n";
|
||||
|
||||
// Test that fully not-overlapped kernels take roughly 3x as long as one
|
||||
// cooperative kernel.
|
||||
if (serialized_gpu0_time > 3.3 * single_kernel_time ||
|
||||
serialized_gpu0_time < 2.7 * single_kernel_time) {
|
||||
std::cerr << "ERROR!" << std::endl;
|
||||
std::cerr << "Test 1, the first case where all kernels should be ";
|
||||
std::cerr << "serialized, had a runtime that was very different ";
|
||||
std::cerr << "than what was expected." << std::endl;
|
||||
std::cerr << "Was " << serialized_gpu0_time.count() << " seconds.\n";
|
||||
std::cerr << "Expected between ";
|
||||
std::cerr << (2.7 * single_kernel_time.count()) << " and ";
|
||||
std::cerr << (3.3 * single_kernel_time.count()) << " seconds.\n";
|
||||
std::cerr << "Were they truly serialized?" << std::endl;
|
||||
FailFlag = 1;
|
||||
}
|
||||
|
||||
// Test that fully not-overlapped kernels take roughly 3x as long as one
|
||||
// cooperative kernel.
|
||||
if (serialized_gpu1_time > 3.3 * single_kernel_time ||
|
||||
serialized_gpu1_time < 2.7 * single_kernel_time) {
|
||||
std::cerr << "ERROR!" << std::endl;
|
||||
std::cerr << "Test 2, the second case where all kernels should be ";
|
||||
std::cerr << "serialized, had a runtime that was very different ";
|
||||
std::cerr << "than what was expected." << std::endl;
|
||||
std::cerr << "Was " << serialized_gpu1_time.count();
|
||||
std::cerr << " seconds." << std::endl;
|
||||
std::cerr << "Expected between ";
|
||||
std::cerr << (2.7 * single_kernel_time.count()) << " and ";
|
||||
std::cerr << (3.3 * single_kernel_time.count()) << " seconds.\n";
|
||||
std::cerr << "Were they truly serialized?" << std::endl;
|
||||
FailFlag = 1;
|
||||
}
|
||||
|
||||
// Test that kernels that can overlap only before the cooperative kernel
|
||||
// launches kernels take roughly the same time (in this case)
|
||||
if (pre_overlapped_time > 2.3 * single_kernel_time ||
|
||||
pre_overlapped_time < 1.7 * single_kernel_time) {
|
||||
std::cerr << "ERROR!" << std::endl;
|
||||
std::cerr << "Test 3, the case where the last kernel is serialized, had ";
|
||||
std::cerr << "a runtime that was very different than what was ";
|
||||
std::cerr << "expected." << std::endl;
|
||||
std::cerr << "Was " << pre_overlapped_time.count() << " seconds.\n";
|
||||
std::cerr << "Expected between ";
|
||||
std::cerr << (1.7 * single_kernel_time.count()) << " and ";
|
||||
std::cerr << (2.3 * single_kernel_time.count()) << " seconds.\n";
|
||||
FailFlag = 1;
|
||||
}
|
||||
|
||||
// Test that kernels that can overlap only after the cooperative kernel
|
||||
// launches kernels take roughly the same time (in this case)
|
||||
if (post_overlapped_time > 2.3 * single_kernel_time ||
|
||||
post_overlapped_time < 1.7 * single_kernel_time) {
|
||||
std::cerr << "ERROR!" << std::endl;
|
||||
std::cerr << "Teste 4, the case where the first kernel is ";
|
||||
std::cerr << "serialized, had a runtime that was very different ";
|
||||
std::cerr << "than what was expected." << std::endl;
|
||||
std::cerr << "Was " << post_overlapped_time.count() << " seconds.\n";
|
||||
std::cerr << "Expected between ";
|
||||
std::cerr << (1.7 * single_kernel_time.count()) << " and ";
|
||||
std::cerr << (2.3 * single_kernel_time.count()) << " seconds.\n";
|
||||
FailFlag = 1;
|
||||
}
|
||||
|
||||
// Test that, with the right flags on the kernel launch, that we prevent
|
||||
// incomplete launches from serializing the cooperative launch streams.
|
||||
if (overlapped_time > 2.2 * single_kernel_time ||
|
||||
overlapped_time < 1.8 * single_kernel_time) {
|
||||
std::cerr << "ERROR!" << std::endl;
|
||||
std::cerr << "Test 5, the case where normal and cooperative kernel ";
|
||||
std::cerr << "launches should overlap, does not appear to have done so.";
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "Was " << overlapped_time.count() << " seconds.\n";
|
||||
std::cerr << "Expected between ";
|
||||
std::cerr << (1.8 * single_kernel_time.count()) << " and ";
|
||||
std::cerr << (2.2 * single_kernel_time.count()) << " seconds.\n";
|
||||
std::cerr << "Is the normal kernel being serialized with the ";
|
||||
std::cerr << "cooperative kernels on different streams?" << std::endl;
|
||||
FailFlag = 1;
|
||||
}
|
||||
for (int k = 0; k < 2; ++k) {
|
||||
HIPCHECK(hipFree(dev_array[k]));
|
||||
HIPCHECK(hipStreamDestroy(streams[k]));
|
||||
}
|
||||
if (FailFlag == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (FailFlag == 1) {
|
||||
failed("");
|
||||
} else {
|
||||
passed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
Copyright (c) 2020-present Advanced Micro Devices, Inc. All rights reserved.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANNTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INNCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANNY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER INN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR INN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// Test Description:
|
||||
/*The general idea of the application is to launch N warps to all GPUs detected
|
||||
in the HIP system. N is a command-line parameter, but the user should set N
|
||||
small enough that all warps can be on each of the GPUs at the same time.
|
||||
|
||||
All of the warps do a "work loop". Within the work loop, every warp
|
||||
atomically increments a global variable that is shared between both fo the
|
||||
target GPUs. The value returned from this atomic increment entriely depends
|
||||
on the order the warps from the GPUs arrive at the atomic instruction. Each
|
||||
warp then stores the result into a global array based on its warp ID.
|
||||
|
||||
We also add a sleep/wait loop into the code so that the last warp runs much
|
||||
slower than everyone else. As such, it should store much larger values than
|
||||
all the other warps.
|
||||
|
||||
If there are no barrier within the loop, then warp 0 will likely ge to the
|
||||
global variable the first time while all the other warps have each
|
||||
incremented it many times. If the barrier properly works, then each warp
|
||||
will increment the variable once per time through the loop, and all threads
|
||||
will sleep on the barrier waiting for the last warp to finally catch up.
|
||||
*/
|
||||
|
||||
/* HIT_START
|
||||
* BUILD: %t %s ../../test_common.cpp EXCLUDE_HIP_PLATFORM nvcc
|
||||
* TEST: %t
|
||||
* HIT_END
|
||||
*/
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <hip/hip_cooperative_groups.h>
|
||||
#include "test_common.h"
|
||||
|
||||
static int cooperative_groups_support(int device_id) {
|
||||
hipError_t err;
|
||||
int cooperative_attribute;
|
||||
HIPCHECK(hipDeviceGetAttribute(&cooperative_attribute,
|
||||
hipDeviceAttributeCooperativeLaunch, device_id));
|
||||
if (!cooperative_attribute) {
|
||||
std::cerr << "Cooperative launch support not available in ";
|
||||
std::cerr << "the device attribute for device " << device_id;
|
||||
std::cerr << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int multi_gpu_cooperative_attribute;
|
||||
HIPCHECK(hipDeviceGetAttribute(&multi_gpu_cooperative_attribute,
|
||||
hipDeviceAttributeCooperativeMultiDeviceLaunch, device_id));
|
||||
if (!multi_gpu_cooperative_attribute) {
|
||||
std::cerr << "Multi-GPU cooperative launch support not available in ";
|
||||
std::cerr << "the device attribute for device " << device_id;
|
||||
std::cerr << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, device_id));
|
||||
if (device_properties.cooperativeLaunch == 0) {
|
||||
std::cerr << "Cooperative group support not available in ";
|
||||
std::cerr << "device properties." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
if (device_properties.cooperativeMultiDeviceLaunch == 0) {
|
||||
std::cerr << "Multi-GPU cooperative group support not available in ";
|
||||
std::cerr << "device properties." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int verify_barrier_buffer(unsigned int loops, unsigned int warps,
|
||||
unsigned int *host_buffer,
|
||||
unsigned int num_devs) {
|
||||
unsigned int max_in_this_loop = 0;
|
||||
for (unsigned int i = 0; i < loops; i++) {
|
||||
max_in_this_loop += (warps * num_devs);
|
||||
for (unsigned int j = 0; j < warps; j++) {
|
||||
if (host_buffer[i*warps+j] > max_in_this_loop) {
|
||||
std::cerr << "Barrier failure!" << std::endl;
|
||||
std::cerr << " Buffer entry " << i*warps+j;
|
||||
std::cerr << " contains the value " << host_buffer[i*warps+j];
|
||||
std::cerr << " but it should not be more than ";
|
||||
std::cerr << max_in_this_loop << std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cout << "\tBarriers work properly!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verify_multi_gpu_buffer(unsigned int loops, unsigned int array_val) {
|
||||
unsigned int desired_val = 0;
|
||||
for (int i = 0; i < loops; i++) {
|
||||
if (i % 2 == 0) {
|
||||
desired_val += 2;
|
||||
} else {
|
||||
desired_val *= 2;
|
||||
}
|
||||
}
|
||||
std::cout << "Desired value is " << desired_val << std::endl;
|
||||
if (array_val != desired_val) {
|
||||
std::cerr << "ERROR! Multi-grid barrier does not appear to work.";
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "Expected the multi-GPUs to work together to produce ";
|
||||
std::cerr << "the value " << desired_val << std::endl;
|
||||
std::cerr << "However, the entry returned from the multi-GPU ";
|
||||
std::cerr << "kernel was " << array_val << std::endl;
|
||||
return -1;
|
||||
}
|
||||
std::cout << "\tMulti-GPU barriers appear to work here." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
__global__ void
|
||||
test_kernel(unsigned int *atomic_val, unsigned int *global_array,
|
||||
unsigned int *array, uint32_t loops) {
|
||||
cooperative_groups::grid_group grid = cooperative_groups::this_grid();
|
||||
cooperative_groups::multi_grid_group mgrid =
|
||||
cooperative_groups::this_multi_grid();
|
||||
unsigned rank = grid.thread_rank();
|
||||
unsigned global_rank = mgrid.thread_rank();
|
||||
|
||||
int offset = blockIdx.x;
|
||||
for (int i = 0; i < loops; i++) {
|
||||
// Make the last thread run way behind everyone else.
|
||||
// If the grid barrier below fails, then the other threads may hit the
|
||||
// atomicInc instruction many times before the last thread ever gets
|
||||
// to it.
|
||||
// As such, without the barrier, the last array entry will eventually
|
||||
// contain a very large value, defined by however many times the other
|
||||
// wavefronts make it through this loop.
|
||||
// If the barrier works, then it will likely contain some number
|
||||
// near "total number of blocks". It will be the last wavefront to
|
||||
// reach the atomicInc, but everyone will have only hit the atomic once.
|
||||
if (rank == (grid.size() - 1)) {
|
||||
long long start_clock = clock64();
|
||||
while (clock64() < (start_clock+1000000)) {}
|
||||
}
|
||||
if (threadIdx.x == 0) {
|
||||
array[offset] = atomicInc(atomic_val, UINT_MAX);
|
||||
}
|
||||
grid.sync();
|
||||
|
||||
// Make the last thread in the entire multi-grid run way behind
|
||||
// everyone else.
|
||||
// If the mgrid barrier below fails, then the two global_array entries
|
||||
// will end up being out of sync, because the intermingling of adds
|
||||
// and multiplies will not be aligned between to the two GPUs.
|
||||
if (global_rank == (mgrid.size() - 1)) {
|
||||
long long start_clock = clock64();
|
||||
while (clock64() < (start_clock+100000000)) {}
|
||||
}
|
||||
// During even iterations, add into your own array entry
|
||||
// During odd iterations, add into your partner's array entry
|
||||
unsigned grid_rank = mgrid.grid_rank();
|
||||
unsigned inter_gpu_offset = (grid_rank + i) % mgrid.num_grids();
|
||||
if (rank == (grid.size() - 1)) {
|
||||
if (i % mgrid.num_grids() == 0) {
|
||||
global_array[grid_rank] += 2;
|
||||
} else {
|
||||
global_array[inter_gpu_offset] *= 2;
|
||||
}
|
||||
}
|
||||
mgrid.sync();
|
||||
offset += gridDim.x;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
hipError_t err;
|
||||
int num_devices = 0;
|
||||
uint32_t loops = 2;
|
||||
uint32_t warps = 10;
|
||||
uint32_t block_size = 1;
|
||||
|
||||
std::cout << "Loops: " << loops << std::endl;
|
||||
std::cout << "Warps: " << warps << std::endl;
|
||||
std::cout << "Block size: " << block_size << std::endl;
|
||||
|
||||
HIPCHECK(hipGetDeviceCount(&num_devices));
|
||||
if (num_devices < 2) {
|
||||
std::cout << "Not enough GPUs to run test." << std::endl;
|
||||
std::cout << "We require at least 2 GPUs, but only found ";
|
||||
std::cout << num_devices << std::endl;
|
||||
std::cout << "Skipping the test with PASSED result\n";
|
||||
passed();
|
||||
}
|
||||
|
||||
uint32_t device_num[num_devices];
|
||||
|
||||
/*************************************************************************/
|
||||
/* Test whether target device supports cooperative groups ****************/
|
||||
for (int i = 0; i < num_devices; i++) {
|
||||
device_num[i] = i;
|
||||
if (!cooperative_groups_support(device_num[i])) {
|
||||
std::cout << "Skipping the test with Pass result.\n";
|
||||
passed();
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* Test whether the requested size will fit on the GPU *******************/
|
||||
int warp_sizes[num_devices];
|
||||
int num_sms[num_devices];
|
||||
hipDeviceProp_t device_properties[num_devices];
|
||||
int warp_size = INT_MAX;
|
||||
int num_sm = INT_MAX;
|
||||
for (int i = 0; i < num_devices; i++) {
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties[i], device_num[i]));
|
||||
warp_sizes[i] = device_properties[i].warpSize;
|
||||
if (warp_sizes[i] < warp_size) {
|
||||
warp_size = warp_sizes[i];
|
||||
}
|
||||
num_sms[i] = device_properties[i].multiProcessorCount;
|
||||
if (num_sms[i] < num_sm) {
|
||||
num_sm = num_sms[i];
|
||||
}
|
||||
std::cout << "Device " << (i + 1);
|
||||
std::cout << " name: " << device_properties[i].name << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
int num_threads_in_block = block_size * warp_size;
|
||||
|
||||
// Calculate the device occupancy to know how many blocks can be run.
|
||||
int max_blocks_per_sm_arr[num_devices];
|
||||
int max_blocks_per_sm = INT_MAX;
|
||||
for (int i = 0; i < num_devices; i++) {
|
||||
HIPCHECK(hipSetDevice(device_num[i]));
|
||||
HIPCHECK(hipOccupancyMaxActiveBlocksPerMultiprocessor(
|
||||
&max_blocks_per_sm_arr[i], test_kernel, num_threads_in_block, 0));
|
||||
if (max_blocks_per_sm_arr[i] < max_blocks_per_sm) {
|
||||
max_blocks_per_sm = max_blocks_per_sm_arr[i];
|
||||
}
|
||||
}
|
||||
|
||||
int requested_blocks = warps / block_size;
|
||||
if (requested_blocks > max_blocks_per_sm * num_sm) {
|
||||
std::cerr << "Requesting to run " << requested_blocks << " blocks, ";
|
||||
std::cerr << "but we can only guarantee to simultaneously run ";
|
||||
std::cerr << (max_blocks_per_sm * num_sm) << std::endl;
|
||||
failed("");
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* Set up data to pass into the kernel ***********************************/
|
||||
// Each block will output a single value per loop.
|
||||
uint32_t total_buffer_len = requested_blocks*loops;
|
||||
|
||||
// Alocate the buffer that will hold the kernel's output, and which will
|
||||
// also be used to globally synchronize during GWS initialization
|
||||
unsigned int *host_buffer[num_devices];
|
||||
unsigned int *kernel_buffer[num_devices];
|
||||
unsigned int *kernel_atomic[num_devices];
|
||||
hipStream_t streams[num_devices];
|
||||
for (int i = 0; i < num_devices; i++) {
|
||||
host_buffer[i] = (unsigned int*)calloc(total_buffer_len,
|
||||
sizeof(unsigned int));
|
||||
HIPCHECK(hipSetDevice(device_num[i]));
|
||||
HIPCHECK(hipMalloc(reinterpret_cast<void**>(&kernel_buffer[i]),
|
||||
total_buffer_len * sizeof(unsigned int)));
|
||||
HIPCHECK(hipMemcpy(kernel_buffer[i], host_buffer[i],
|
||||
total_buffer_len * sizeof(unsigned int),
|
||||
hipMemcpyHostToDevice));
|
||||
HIPCHECK(hipMalloc(reinterpret_cast<void**>(&kernel_atomic[i]),
|
||||
sizeof(unsigned int)));
|
||||
HIPCHECK(hipMemset(kernel_atomic[i], 0, sizeof(unsigned int)));
|
||||
HIPCHECK(hipStreamCreate(&streams[i]));
|
||||
}
|
||||
|
||||
// Single kernel atomic shared between both devices; put it on the host
|
||||
unsigned int* global_array;
|
||||
HIPCHECK(hipHostMalloc(reinterpret_cast<void**>(&global_array),
|
||||
num_devices * sizeof(unsigned int), 0));
|
||||
HIPCHECK(hipMemset(global_array, 0, num_devices * sizeof(unsigned int)));
|
||||
|
||||
/*************************************************************************/
|
||||
/* Launch the kernels ****************************************************/
|
||||
std::cout << "Launching a kernel with " << warps << " warps ";
|
||||
std::cout << "in " << requested_blocks << " thread blocks.";
|
||||
std::cout << std::endl;
|
||||
|
||||
void *dev_params[num_devices][4];
|
||||
hipLaunchParams md_params[num_devices];
|
||||
for (int i = 0; i < num_devices; i++) {
|
||||
dev_params[i][0] = reinterpret_cast<void*>(&kernel_atomic[i]);
|
||||
dev_params[i][1] = reinterpret_cast<void*>(&global_array);
|
||||
dev_params[i][2] = reinterpret_cast<void*>(&kernel_buffer[i]);
|
||||
dev_params[i][3] = reinterpret_cast<void*>(&loops);
|
||||
md_params[i].func = reinterpret_cast<void*>(test_kernel);
|
||||
md_params[i].gridDim = requested_blocks;
|
||||
md_params[i].blockDim = num_threads_in_block;
|
||||
md_params[i].sharedMem = 0;
|
||||
md_params[i].stream = streams[i];
|
||||
md_params[i].args = dev_params[i];
|
||||
}
|
||||
|
||||
HIPCHECK(hipLaunchCooperativeKernelMultiDevice(md_params, num_devices, 0));
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
|
||||
/*************************************************************************/
|
||||
/* Read back the buffers and print out its data **************************/
|
||||
for (int dev = 0; dev < num_devices; dev++) {
|
||||
HIPCHECK(hipMemcpy(host_buffer[dev], kernel_buffer[dev],
|
||||
total_buffer_len * sizeof(unsigned int),
|
||||
hipMemcpyDeviceToHost));
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < loops; i++) {
|
||||
for (int dev = 0; dev < num_devices; dev++) {
|
||||
std::cout << "+++++++++++++++++ Device " << dev;
|
||||
std::cout << "+++++++++++++++++" << std::endl;
|
||||
for (unsigned int j = 0; j < requested_blocks; j++) {
|
||||
std::cout << "Buffer entry " << (i*warps+j);
|
||||
std::cout << " (written by warp " << j << ")";
|
||||
std::cout << " is " << host_buffer[dev][i*requested_blocks+j];
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
std::cout << "==========================\n";
|
||||
}
|
||||
for (unsigned int dev = 0; dev < num_devices; dev++) {
|
||||
std::cout << "Testing output from device " << dev << std::endl;
|
||||
int local_ret_val = verify_barrier_buffer(loops, requested_blocks,
|
||||
host_buffer[dev], num_devices);
|
||||
if (local_ret_val) {
|
||||
failed("");
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << std::endl << "The multi-GPU shared updates contain:\n";
|
||||
for (int i = 0; i < num_devices; i++) {
|
||||
std::cout << "Entry " << i << ": ";
|
||||
std::cout << global_array[i] << std::endl;
|
||||
}
|
||||
int flag = 0;
|
||||
for (int dev = 0; dev < num_devices; dev++) {
|
||||
std::cout << "Testing multi-GPU output for entry " << dev << std::endl;
|
||||
int local_ret_val = verify_multi_gpu_buffer(loops, global_array[dev]);
|
||||
if (local_ret_val) {
|
||||
flag = 1;
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < num_devices; ++k) {
|
||||
HIPCHECK(hipFree(kernel_buffer[k]));
|
||||
HIPCHECK(hipFree(kernel_atomic[k]));
|
||||
HIPCHECK(hipStreamDestroy(streams[k]));
|
||||
free(host_buffer[k]);
|
||||
}
|
||||
if (flag == 1) {
|
||||
failed("");
|
||||
} else {
|
||||
passed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
Copyright (c) 2020-present Advanced Micro Devices, Inc. All rights reserved.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANNTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INNCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANNY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER INN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR INN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// Test Description:
|
||||
/*The general idea of the application is to launch N warps. N is a command-line
|
||||
parameter, but the user should set N small enough that all warps can be on
|
||||
the GPU at the same time.
|
||||
|
||||
All of the warps do a "work loop". Within the work loop, every warp
|
||||
atomically increments a global variable. The value returned from this atomic
|
||||
increment entriely depends on the order the threads arrive at the atomic
|
||||
instruction. Each warp then stores the result into a global array based on its
|
||||
warp ID.
|
||||
|
||||
We also add a sleep/wait loop into the code so that the last warp runs much
|
||||
slower than everyone else. As such, it should store much larger values than
|
||||
all the other warps.
|
||||
|
||||
If there are no barrier within the loop, then the last warp will likely get to
|
||||
the global variable the first time after all the other warps have each
|
||||
incremented it many times. If the barrier properly works, then each warp
|
||||
will increment the variable once per time through the loop, and all threads
|
||||
will sleep on the barrier waiting for the last warp to finally catch up.
|
||||
*/
|
||||
|
||||
/* HIT_START
|
||||
* BUILD: %t %s ../../test_common.cpp EXCLUDE_HIP_PLATFORM nvcc
|
||||
* TEST: %t
|
||||
* HIT_END
|
||||
*/
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <hip/hip_cooperative_groups.h>
|
||||
#include "test_common.h"
|
||||
|
||||
static int cooperative_groups_support(int device_id) {
|
||||
hipError_t err;
|
||||
int cooperative_attribute;
|
||||
HIPCHECK(hipDeviceGetAttribute(&cooperative_attribute,
|
||||
hipDeviceAttributeCooperativeLaunch, device_id));
|
||||
if (!cooperative_attribute) {
|
||||
std::cerr << "Cooperative launch support not available in ";
|
||||
std::cerr << "the device attribute for device " << device_id;
|
||||
std::cerr << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, device_id));
|
||||
if (device_properties.cooperativeLaunch == 0) {
|
||||
std::cerr << "Cooperative group support not available in ";
|
||||
std::cerr << "device properties." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int verify_barrier_buffer(unsigned int loops, unsigned int warps,
|
||||
unsigned int *host_buffer) {
|
||||
unsigned int max_in_this_loop = 0;
|
||||
for (unsigned int i = 0; i < loops; i++) {
|
||||
max_in_this_loop += warps;
|
||||
for (unsigned int j = 0; j < warps; j++) {
|
||||
if (host_buffer[i*warps+j] > max_in_this_loop) {
|
||||
std::cerr << "Barrier failure!" << std::endl;
|
||||
std::cerr << " Buffer entry " << i*warps+j;
|
||||
std::cerr << " contains the value " << host_buffer[i*warps+j];
|
||||
std::cerr << " but it should not be more than ";
|
||||
std::cerr << max_in_this_loop << std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cout << "Barriers work properly!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
__global__ void
|
||||
test_kernel(unsigned int *atomic_val, unsigned int *array,
|
||||
unsigned int loops) {
|
||||
cooperative_groups::grid_group grid = cooperative_groups::this_grid();
|
||||
unsigned rank = grid.thread_rank();
|
||||
|
||||
int offset = blockIdx.x;
|
||||
for (int i = 0; i < loops; i++) {
|
||||
// Make the last thread run way behind everyone else.
|
||||
// If the barrier below fails, then the other threads may hit the
|
||||
// atomicInc instruction many times before the last thread ever gets
|
||||
// to it.
|
||||
// As such, without the barrier, the last array entry will eventually
|
||||
// contain a very large value, defined by however many times the other
|
||||
// wavefronts make it through this loop.
|
||||
// If the barrier works, then it will likely contain some number
|
||||
// near "total number of blocks". It will be the last wavefront to
|
||||
// reach the atomicInc, but everyone will have only hit the atomic once.
|
||||
if (rank == (grid.size() - 1)) {
|
||||
long long start_clock = clock64();
|
||||
while (clock64() < (start_clock+1000000)) {}
|
||||
}
|
||||
|
||||
if (threadIdx.x == 0) {
|
||||
array[offset] = atomicInc(&atomic_val[0], UINT_MAX);
|
||||
}
|
||||
grid.sync();
|
||||
offset += gridDim.x;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
hipError_t err;
|
||||
int device_num;
|
||||
uint32_t loops = 2;
|
||||
uint32_t warps = 10;
|
||||
uint32_t block_size = 1;
|
||||
HIPCHECK(hipGetDeviceCount(&device_num));
|
||||
for (int dev = 0; dev < device_num; ++dev) {
|
||||
std::cout << "Device number: " << dev << std::endl;
|
||||
std::cout << "Loops: " << loops << std::endl;
|
||||
std::cout << "Warps: " << warps << std::endl;
|
||||
std::cout << "Block size: " << block_size << std::endl;
|
||||
|
||||
/*************************************************************************/
|
||||
/* Test whether target device supports cooperative groups ****************/
|
||||
HIPCHECK(hipSetDevice(dev));
|
||||
if (!cooperative_groups_support(dev)) {
|
||||
std::cout << "Skipping the test with Pass result.\n";
|
||||
passed();
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* Test whether the requested size will fit on the GPU *******************/
|
||||
int warp_size;
|
||||
int num_sms;
|
||||
int max_blocks_per_sm;
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, dev));
|
||||
warp_size = device_properties.warpSize;
|
||||
num_sms = device_properties.multiProcessorCount;
|
||||
|
||||
std::cout << "Device name: " << device_properties.name << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
int num_threads_in_block = block_size * warp_size;
|
||||
|
||||
// Calculate the device occupancy to know how many blocks can be run.
|
||||
HIPCHECK(hipOccupancyMaxActiveBlocksPerMultiprocessor(&max_blocks_per_sm,
|
||||
test_kernel, num_threads_in_block, 0));
|
||||
|
||||
int requested_blocks = warps / block_size;
|
||||
if (requested_blocks > max_blocks_per_sm * num_sms) {
|
||||
std::cerr << "Requesting to run " << requested_blocks << " blocks, ";
|
||||
std::cerr << "but we can only guarantee to simultaneously run ";
|
||||
std::cerr << (max_blocks_per_sm * num_sms) << std::endl;
|
||||
failed("");
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* Set up data to pass into the kernel ***********************************/
|
||||
// Each block will output a single value per loop.
|
||||
uint32_t total_buffer_len = requested_blocks*loops;
|
||||
|
||||
// Alocate the buffer that will hold the kernel's output, and which will
|
||||
// also be used to globally synchronize during GWS initialization
|
||||
unsigned int *host_buffer = (unsigned int*)calloc(total_buffer_len,
|
||||
sizeof(unsigned int));
|
||||
|
||||
unsigned int *kernel_buffer;
|
||||
HIPCHECK(hipMalloc(reinterpret_cast<void**>(&kernel_buffer),
|
||||
total_buffer_len * sizeof(unsigned int)));
|
||||
HIPCHECK(hipMemcpy(kernel_buffer, host_buffer,
|
||||
total_buffer_len * sizeof(unsigned int),
|
||||
hipMemcpyHostToDevice));
|
||||
|
||||
unsigned int *kernel_atomic;
|
||||
HIPCHECK(hipMalloc(reinterpret_cast<void**>(&kernel_atomic),
|
||||
sizeof(unsigned int)));
|
||||
HIPCHECK(hipMemset(kernel_atomic, 0, sizeof(unsigned int)));
|
||||
|
||||
/*************************************************************************/
|
||||
/* Launch the kernel *****************************************************/
|
||||
std::cout << "Launching a kernel with " << warps << " warps ";
|
||||
std::cout << "in " << requested_blocks << " thread blocks.";
|
||||
std::cout << std::endl;
|
||||
|
||||
void *params[3];
|
||||
params[0] = reinterpret_cast<void*>(&kernel_atomic);
|
||||
params[1] = reinterpret_cast<void*>(&kernel_buffer);
|
||||
params[2] = reinterpret_cast<void*>(&loops);
|
||||
HIPCHECK(hipLaunchCooperativeKernel(reinterpret_cast<void*>(test_kernel),
|
||||
requested_blocks,
|
||||
num_threads_in_block, params, 0, NULL));
|
||||
|
||||
/*************************************************************************/
|
||||
/* Read back the buffer and print out its data****************************/
|
||||
HIPCHECK(hipMemcpy(host_buffer, kernel_buffer,
|
||||
total_buffer_len * sizeof(unsigned int),
|
||||
hipMemcpyDeviceToHost));
|
||||
|
||||
for (unsigned int i = 0; i < loops; i++) {
|
||||
for (unsigned int j = 0; j < requested_blocks; j++) {
|
||||
std::cout << "Buffer entry " << (i*warps+j);
|
||||
std::cout << " (written by warp " << j << ")";
|
||||
std::cout << " is " << host_buffer[i * requested_blocks + j];
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << "==========================\n";
|
||||
}
|
||||
int ret_val = verify_barrier_buffer(loops, requested_blocks, host_buffer);
|
||||
HIPCHECK(hipFree(kernel_buffer));
|
||||
HIPCHECK(hipFree(kernel_atomic));
|
||||
if (ret_val == -1) {
|
||||
failed("");
|
||||
} else {
|
||||
passed();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
Copyright (c) 2020-present Advanced Micro Devices, Inc. All rights reserved.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANNTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INNCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANNY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER INN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR INN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// Test Description:
|
||||
/*The general idea of the application is to launch N warps to each of two GPUs.
|
||||
N is a command-line parameter, but the user should set N small enough that all
|
||||
warps can be on each of the GPUs at the same time.
|
||||
|
||||
All of the warps do a "work loop". Within the work loop, every warp
|
||||
atomically increments a global variable that is shared between both fo the
|
||||
target GPUs. The value returned from this atomic increment entriely depends
|
||||
on the order the warps from the GPUs arrive at the atomic instruction. Each
|
||||
warp then stores the result into a global array based on its warp ID.
|
||||
|
||||
We also add a sleep/wait loop into the code so that the last warp runs much
|
||||
slower than everyone else. As such, it should store much larger values than
|
||||
all the other warps.
|
||||
|
||||
If there are no barrier within the loop, then warp 0 will likely ge to the
|
||||
global variable the first time while all the other warps have each
|
||||
incremented it many times. If the barrier properly works, then each warp
|
||||
will increment the variable once per time through the loop, and all threads
|
||||
will sleep on the barrier waiting for the last warp to finally catch up.
|
||||
*/
|
||||
|
||||
/* HIT_START
|
||||
* BUILD: %t %s ../../test_common.cpp EXCLUDE_HIP_PLATFORM nvcc
|
||||
* TEST: %t
|
||||
* HIT_END
|
||||
*/
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <hip/hip_cooperative_groups.h>
|
||||
#include "test_common.h"
|
||||
|
||||
static int cooperative_groups_support(int device_id) {
|
||||
hipError_t err;
|
||||
int cooperative_attribute;
|
||||
HIPCHECK(hipDeviceGetAttribute(&cooperative_attribute,
|
||||
hipDeviceAttributeCooperativeLaunch, device_id));
|
||||
if (!cooperative_attribute) {
|
||||
std::cerr << "Cooperative launch support not available in ";
|
||||
std::cerr << "the device attribute for device " << device_id;
|
||||
std::cerr << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int multi_gpu_cooperative_attribute;
|
||||
HIPCHECK(hipDeviceGetAttribute(&multi_gpu_cooperative_attribute,
|
||||
hipDeviceAttributeCooperativeMultiDeviceLaunch, device_id));
|
||||
if (!multi_gpu_cooperative_attribute) {
|
||||
std::cerr << "Multi-GPU cooperative launch support not available in ";
|
||||
std::cerr << "the device attribute for device " << device_id;
|
||||
std::cerr << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
hipDeviceProp_t device_properties;
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties, device_id));
|
||||
if (device_properties.cooperativeLaunch == 0) {
|
||||
std::cerr << "Cooperative group support not available in ";
|
||||
std::cerr << "device properties." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
if (device_properties.cooperativeMultiDeviceLaunch == 0) {
|
||||
std::cerr << "Multi-GPU cooperative group support not available in ";
|
||||
std::cerr << "device properties." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int verify_barrier_buffer(unsigned int loops, unsigned int warps,
|
||||
unsigned int *host_buffer,
|
||||
unsigned int num_devs) {
|
||||
unsigned int max_in_this_loop = 0;
|
||||
for (unsigned int i = 0; i < loops; i++) {
|
||||
max_in_this_loop += (warps * num_devs);
|
||||
for (unsigned int j = 0; j < warps; j++) {
|
||||
if (host_buffer[i*warps+j] > max_in_this_loop) {
|
||||
std::cerr << "Barrier failure!" << std::endl;
|
||||
std::cerr << " Buffer entry " << i*warps+j;
|
||||
std::cerr << " contains the value " << host_buffer[i*warps+j];
|
||||
std::cerr << " but it should not be more than ";
|
||||
std::cerr << max_in_this_loop << std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cout << "\tBarriers work properly!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verify_multi_gpu_buffer(unsigned int loops, unsigned int array_val) {
|
||||
unsigned int desired_val = 0;
|
||||
for (int i = 0; i < loops; i++) {
|
||||
if (i % 2 == 0) {
|
||||
desired_val += 2;
|
||||
} else {
|
||||
desired_val *= 2;
|
||||
}
|
||||
}
|
||||
std::cout << "Desired value is " << desired_val << std::endl;
|
||||
if (array_val != desired_val) {
|
||||
std::cerr << "ERROR! Multi-grid barrier does not appear to work.";
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "Expected the multi-GPUs to work together to produce ";
|
||||
std::cerr << "the value " << desired_val << std::endl;
|
||||
std::cerr << "However, the entry returned from the multi-GPU ";
|
||||
std::cerr << "kernel was " << array_val << std::endl;
|
||||
return -1;
|
||||
}
|
||||
std::cout << "\tMulti-GPU barriers appear to work here." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
__global__ void
|
||||
test_kernel(unsigned int *atomic_val, unsigned int *global_array,
|
||||
unsigned int *array, uint32_t loops) {
|
||||
cooperative_groups::grid_group grid = cooperative_groups::this_grid();
|
||||
cooperative_groups::multi_grid_group mgrid =
|
||||
cooperative_groups::this_multi_grid();
|
||||
unsigned rank = grid.thread_rank();
|
||||
unsigned global_rank = mgrid.thread_rank();
|
||||
|
||||
int offset = blockIdx.x;
|
||||
for (int i = 0; i < loops; i++) {
|
||||
// Make the last thread run way behind everyone else.
|
||||
// If the grid barrier below fails, then the other threads may hit the
|
||||
// atomicInc instruction many times before the last thread ever gets
|
||||
// to it.
|
||||
// As such, without the barrier, the last array entry will eventually
|
||||
// contain a very large value, defined by however many times the other
|
||||
// wavefronts make it through this loop.
|
||||
// If the barrier works, then it will likely contain some number
|
||||
// near "total number of blocks". It will be the last wavefront to
|
||||
// reach the atomicInc, but everyone will have only hit the atomic once.
|
||||
if (rank == (grid.size() - 1)) {
|
||||
long long start_clock = clock64();
|
||||
while (clock64() < (start_clock + 1000000)) {}
|
||||
}
|
||||
if (threadIdx.x == 0) {
|
||||
array[offset] = atomicInc(atomic_val, UINT_MAX);
|
||||
}
|
||||
grid.sync();
|
||||
|
||||
// Make the last thread in the entire multi-grid run way behind
|
||||
// everyone else.
|
||||
// If the mgrid barrier below fails, then the two global_array entries
|
||||
// will end up being out of sync, because the intermingling of adds
|
||||
// and multiplies will not be aligned between to the two GPUs.
|
||||
if (global_rank == (mgrid.size() - 1)) {
|
||||
long long start_clock = clock64();
|
||||
while (clock64() < (start_clock + 100000000)) {}
|
||||
}
|
||||
// During even iterations, add into your own array entry
|
||||
// During odd iterations, add into your partner's array entry
|
||||
unsigned grid_rank = mgrid.grid_rank();
|
||||
unsigned inter_gpu_offset = (grid_rank + i) % mgrid.num_grids();
|
||||
if (rank == (grid.size() - 1)) {
|
||||
if (i % mgrid.num_grids() == 0) {
|
||||
global_array[grid_rank] += 2;
|
||||
} else {
|
||||
global_array[inter_gpu_offset] *= 2;
|
||||
}
|
||||
}
|
||||
mgrid.sync();
|
||||
offset += gridDim.x;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
hipError_t err;
|
||||
int device_num = 0, flag = 0;
|
||||
uint32_t loops = 2;
|
||||
uint32_t warps = 10;
|
||||
uint32_t block_size = 1;
|
||||
HIPCHECK(hipGetDeviceCount(&device_num));
|
||||
if (device_num < 2) {
|
||||
std::cout << "This test needs atleast two gpus but found only";
|
||||
std::cout << device_num << std::endl;
|
||||
std::cout << "Hence skipping the test with pass result\n";
|
||||
passed();
|
||||
}
|
||||
|
||||
for (int d = 0; d < (device_num - 1); ++d) {
|
||||
std::cout << "First device number: " << d << std::endl;
|
||||
std::cout << "Second device number: " << (d + 1) << std::endl;
|
||||
std::cout << "Loops: " << loops << std::endl;
|
||||
std::cout << "Warps: " << warps << std::endl;
|
||||
std::cout << "Block size: " << block_size << std::endl;
|
||||
|
||||
/*************************************************************************/
|
||||
/* Test whether target device supports cooperative groups ****************/
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (!cooperative_groups_support((d + i))) {
|
||||
std::cout << "Skipping the test with Pass result.\n";
|
||||
passed();
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* Test whether the requested size will fit on the GPU *******************/
|
||||
int warp_sizes[2];
|
||||
int num_sms[2];
|
||||
hipDeviceProp_t device_properties[2];
|
||||
int warp_size = INT_MAX;
|
||||
int num_sm = INT_MAX;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipGetDeviceProperties(&device_properties[i], (d + i)));
|
||||
warp_sizes[i] = device_properties[i].warpSize;
|
||||
if (warp_sizes[i] < warp_size) {
|
||||
warp_size = warp_sizes[i];
|
||||
}
|
||||
num_sms[i] = device_properties[i].multiProcessorCount;
|
||||
if (num_sms[i] < num_sm) {
|
||||
num_sm = num_sms[i];
|
||||
}
|
||||
std::cout << "Device " << (d + i);
|
||||
std::cout << " name: " << device_properties[i].name << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
int num_threads_in_block = block_size * warp_size;
|
||||
|
||||
// Calculate the device occupancy to know how many blocks can be run.
|
||||
int max_blocks_per_sm_arr[2];
|
||||
int max_blocks_per_sm = INT_MAX;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
HIPCHECK(hipSetDevice((d + i)));
|
||||
HIPCHECK(hipOccupancyMaxActiveBlocksPerMultiprocessor(
|
||||
&max_blocks_per_sm_arr[i], test_kernel, num_threads_in_block,
|
||||
0));
|
||||
if (max_blocks_per_sm_arr[i] < max_blocks_per_sm) {
|
||||
max_blocks_per_sm = max_blocks_per_sm_arr[i];
|
||||
}
|
||||
}
|
||||
|
||||
int requested_blocks = warps / block_size;
|
||||
if (requested_blocks > max_blocks_per_sm * num_sm) {
|
||||
std::cerr << "Requesting to run " << requested_blocks << " blocks, ";
|
||||
std::cerr << "but we can only guarantee to simultaneously run ";
|
||||
std::cerr << (max_blocks_per_sm * num_sm) << std::endl;
|
||||
failed("");
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* Set up data to pass into the kernel ***********************************/
|
||||
// Each block will output a single value per loop.
|
||||
uint32_t total_buffer_len = requested_blocks*loops;
|
||||
|
||||
// Alocate the buffer that will hold the kernel's output, and which will
|
||||
// also be used to globally synchronize during GWS initialization
|
||||
unsigned int *host_buffer[2];
|
||||
unsigned int *kernel_buffer[2];
|
||||
unsigned int *kernel_atomic[2];
|
||||
hipStream_t streams[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
host_buffer[i] = (unsigned int*)calloc(total_buffer_len,
|
||||
sizeof(unsigned int));
|
||||
HIPCHECK(hipSetDevice((d + i)));
|
||||
HIPCHECK(hipMalloc(reinterpret_cast<void**>(&kernel_buffer[i]),
|
||||
total_buffer_len * sizeof(unsigned int)));
|
||||
HIPCHECK(hipMemcpy(kernel_buffer[i], host_buffer[i],
|
||||
total_buffer_len * sizeof(unsigned int), hipMemcpyHostToDevice));
|
||||
HIPCHECK(hipMalloc(reinterpret_cast<void**>(&kernel_atomic[i]),
|
||||
sizeof(unsigned int)));
|
||||
HIPCHECK(hipMemset(kernel_atomic[i], 0, sizeof(unsigned int)));
|
||||
HIPCHECK(hipStreamCreate(&streams[i]));
|
||||
}
|
||||
|
||||
// Single kernel atomic shared between both devices; put it on the host
|
||||
unsigned int* global_array;
|
||||
HIPCHECK(hipHostMalloc(reinterpret_cast<void**>(&global_array),
|
||||
2 * sizeof(unsigned int), 0));
|
||||
HIPCHECK(hipMemset(global_array, 0, 2 * sizeof(unsigned int)));
|
||||
|
||||
/*************************************************************************/
|
||||
/* Launch the kernels ****************************************************/
|
||||
std::cout << "Launching a kernel with " << warps << " warps ";
|
||||
std::cout << "in " << requested_blocks << " thread blocks.";
|
||||
std::cout << std::endl;
|
||||
|
||||
void *dev_params[2][4];
|
||||
hipLaunchParams md_params[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
dev_params[i][0] = reinterpret_cast<void*>(&kernel_atomic[i]);
|
||||
dev_params[i][1] = reinterpret_cast<void*>(&global_array);
|
||||
dev_params[i][2] = reinterpret_cast<void*>(&kernel_buffer[i]);
|
||||
dev_params[i][3] = reinterpret_cast<void*>(&loops);
|
||||
md_params[i].func = reinterpret_cast<void*>(test_kernel);
|
||||
md_params[i].gridDim = requested_blocks;
|
||||
md_params[i].blockDim = num_threads_in_block;
|
||||
md_params[i].sharedMem = 0;
|
||||
md_params[i].stream = streams[i];
|
||||
md_params[i].args = dev_params[i];
|
||||
}
|
||||
|
||||
HIPCHECK(hipLaunchCooperativeKernelMultiDevice(md_params, 2, 0));
|
||||
HIPCHECK(hipDeviceSynchronize());
|
||||
|
||||
/*************************************************************************/
|
||||
/* Read back the buffers and print out its data **************************/
|
||||
for (int dev = 0; dev < 2; dev++) {
|
||||
HIPCHECK(hipMemcpy(host_buffer[d + dev], kernel_buffer[d + dev],
|
||||
total_buffer_len * sizeof(unsigned int),
|
||||
hipMemcpyDeviceToHost));
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < loops; i++) {
|
||||
for (int dev = 0; dev < 2; dev++) {
|
||||
std::cout << "+++++++++++++++++ Device " << (d + dev);
|
||||
std::cout << "+++++++++++++++++" << std::endl;
|
||||
for (unsigned int j = 0; j < requested_blocks; j++) {
|
||||
std::cout << "Buffer entry " << (i * warps + j);
|
||||
std::cout << " (written by warp " << j << ")";
|
||||
std::cout << " is " << host_buffer[dev][i * requested_blocks + j];
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
std::cout << "==========================\n";
|
||||
}
|
||||
for (unsigned int dev = 0; dev < 2; dev++) {
|
||||
std::cout << "Testing output from device " << (d + dev) << std::endl;
|
||||
int local_ret_val = verify_barrier_buffer(loops, requested_blocks,
|
||||
host_buffer[dev], 2);
|
||||
if (local_ret_val == -1) {
|
||||
flag = 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << std::endl << "The multi-GPU shared updates contain:";
|
||||
std::cout << std::endl;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
std::cout << "Entry " << i << ": ";
|
||||
std::cout << global_array[i] << std::endl;
|
||||
}
|
||||
for (int dev = 0; dev < 2; dev++) {
|
||||
std::cout << "Testing multi-GPU output for entry " << (d + dev);
|
||||
std::cout << std::endl;
|
||||
int local_ret_val = verify_multi_gpu_buffer(loops, global_array[dev]);
|
||||
if (local_ret_val) {
|
||||
flag = 1;
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < 2; ++k) {
|
||||
HIPCHECK(hipFree(kernel_buffer[k]));
|
||||
HIPCHECK(hipFree(kernel_atomic[k]));
|
||||
HIPCHECK(hipStreamDestroy(streams[k]));
|
||||
free(host_buffer[k]);
|
||||
}
|
||||
}
|
||||
if (flag == 1) {
|
||||
failed("");
|
||||
} else {
|
||||
passed();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user