Files
TheRock/rocm-systems/projects/hip-tests/catch/unit/deviceLib/AtomicsWithRandomActiveLanesInWavefront.cc
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

794 lines
29 KiB
C++

/*
* Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
*
* SPDX-License-Identifier: MIT
*/
// Testcase Description:
// Verifies working of following Global Atomic Operations:
// 1. atomicAdd,
// 2. atomicSub,
// 3. atomicMax,
// 4. atomicMin,
// 5. atomicAnd,
// 6. atomicOr, and
// 7. atomicXor
// for INT, UNSIGNED and FLOAT input types.
// Tests consist of following scenarios:
// 1) Uniform input value to atomic operation (i.e. every thread will be
// performing atomic operation with the same value)
// 2) Divergent input value (i.e. every thread with be performing
// atomic operation on different values)
#include <hip_test_common.hh>
using namespace std;
////////////////////////////////////////////////////////////////////////////////
// Auto-Verification Code
////////////////////////////////////////////////////////////////////////////////
// Atomic Operations
enum AtomicOp : unsigned {
Add,
Sub,
Max,
Min,
And,
Or,
Xor,
};
// Initial values for atomic ops and various types
template <typename T, AtomicOp Op> struct Initial {
constexpr static T value;
};
template <> const int Initial<int, AtomicOp::Add>::value = 0;
template <> const unsigned Initial<unsigned, AtomicOp::Add>::value = 0;
template <> const float Initial<float, AtomicOp::Add>::value = 0.0f;
template <> const int Initial<int, AtomicOp::Sub>::value = 0;
template <> const unsigned Initial<unsigned, AtomicOp::Sub>::value = 0;
template <> const float Initial<float, AtomicOp::Sub>::value = 0.0f;
template <> const int Initial<int, AtomicOp::Min>::value = INT_MAX;
template <> const unsigned Initial<unsigned, AtomicOp::Min>::value = UINT_MAX;
template <> const float Initial<float, AtomicOp::Min>::value = std::numeric_limits<float>::min();
template <> const int Initial<int, AtomicOp::Max>::value = INT_MIN;
template <> const unsigned Initial<unsigned, AtomicOp::Max>::value = 0;
template <> const float Initial<float, AtomicOp::Max>::value = std::numeric_limits<float>::max();
template <> const int Initial<int, AtomicOp::And>::value = 0xffffffff;
template <> const unsigned Initial<unsigned, AtomicOp::And>::value = 0xffffffff;
template <> const int Initial<int, AtomicOp::Or>::value = 0;
template <> const unsigned Initial<unsigned, AtomicOp::Or>::value = 0;
template <> const int Initial<int, AtomicOp::Xor>::value = 0x5a5a5a5a;
template <> const unsigned Initial<unsigned, AtomicOp::Xor>::value = 0x5a5a5a5a;
// Uniform values for atomic ops and various types
template <typename T, AtomicOp Op> struct Uniform {
constexpr static T value;
};
template <> const int Uniform<int, AtomicOp::Add>::value = 10;
template <> const unsigned Uniform<unsigned, AtomicOp::Add>::value = 10;
template <> const float Uniform<float, AtomicOp::Add>::value = 10.0f;
template <> const int Uniform<int, AtomicOp::Sub>::value = 10;
template <> const unsigned Uniform<unsigned, AtomicOp::Sub>::value = 10;
template <> const float Uniform<float, AtomicOp::Sub>::value = 10.0f;
template <> const int Uniform<int, AtomicOp::Min>::value = 10;
template <> const unsigned Uniform<unsigned, AtomicOp::Min>::value = 10;
template <> const float Uniform<float, AtomicOp::Min>::value = 10.0f;
template <> const int Uniform<int, AtomicOp::Max>::value = 10;
template <> const unsigned Uniform<unsigned, AtomicOp::Max>::value = 10;
template <> const float Uniform<float, AtomicOp::Max>::value = 10.0f;
template <> const int Uniform<int, AtomicOp::And>::value = 10;
template <> const unsigned Uniform<unsigned, AtomicOp::And>::value = 10;
template <> const int Uniform<int, AtomicOp::Or>::value = 10;
template <> const unsigned Uniform<unsigned, AtomicOp::Or>::value = 10;
template <> const int Uniform<int, AtomicOp::Xor>::value = 10;
template <> const unsigned Uniform<unsigned, AtomicOp::Xor>::value = 10;
// Auto-verification APIs for uniform values for various atomic ops
template <typename T> bool verifyAdd(T* gpuData, int len, bool* activeLanes) {
T val = Initial<T, AtomicOp::Add>::value;
T uniformValue = Uniform<T, AtomicOp::Add>::value;
for (int i = 0; i < len; ++i) {
if (activeLanes[i]) val += uniformValue;
}
return val == gpuData[0];
}
template <typename T> bool verifySub(T* gpuData, int len, bool* activeLanes) {
T val = Initial<T, AtomicOp::Sub>::value;
T uniformValue = Uniform<T, AtomicOp::Sub>::value;
for (int i = 0; i < len; ++i) {
if (activeLanes[i]) val -= uniformValue;
}
return val == gpuData[1];
}
template <typename T> bool verifyMax(T* gpuData, int len, bool* activeLanes) {
T val = Initial<T, AtomicOp::Max>::value;
T uniformValue = Uniform<T, AtomicOp::Max>::value;
for (int i = 0; i < len; ++i) {
if (activeLanes[i]) val = std::max<T>(val, uniformValue);
}
return val == gpuData[2];
}
template <typename T> bool verifyMin(T* gpuData, int len, bool* activeLanes) {
T val = Initial<T, AtomicOp::Min>::value;
T uniformValue = Uniform<T, AtomicOp::Min>::value;
for (int i = 0; i < len; ++i) {
if (activeLanes[i]) val = std::min<T>(val, uniformValue);
}
return val == gpuData[3];
}
template <typename T> bool verifyAnd(T* gpuData, int len, bool* activeLanes) {
T val = Initial<T, AtomicOp::And>::value;
T uniformValue = Uniform<T, AtomicOp::And>::value;
for (int i = 0; i < len; ++i) {
if (activeLanes[i]) val &= uniformValue;
}
return val == gpuData[4];
}
template <typename T> bool verifyOr(T* gpuData, int len, bool* activeLanes) {
T val = Initial<T, AtomicOp::Or>::value;
T uniformValue = Uniform<T, AtomicOp::Or>::value;
for (int i = 0; i < len; ++i) {
if (activeLanes[i]) val |= uniformValue;
}
return val == gpuData[5];
}
template <typename T> bool verifyXor(T* gpuData, int len, bool* activeLanes) {
T val = Initial<T, AtomicOp::Xor>::value;
T uniformValue = Uniform<T, AtomicOp::Xor>::value;
for (int i = 0; i < len; ++i) {
if (activeLanes[i]) val ^= uniformValue;
}
return val == gpuData[6];
}
// Auto-verification APIs for divergent values for various atomic ops
template <typename T>
bool verifyAdd_divValue(T* gpuData, int len, bool* activeLanes, T* divergentValue) {
T val = Initial<T, AtomicOp::Add>::value;
for (int i = 0; i < len; ++i) {
if (activeLanes[i]) val += divergentValue[i];
}
if (std::is_same<T, float>::value) {
REQUIRE(val == Catch::Approx(gpuData[0]));
return true;
}
return val == gpuData[0];
}
template <typename T>
bool verifySub_divValue(T* gpuData, int len, bool* activeLanes, T* divergentValue) {
T val = Initial<T, AtomicOp::Sub>::value;
for (int i = 0; i < len; ++i) {
if (activeLanes[i]) val -= divergentValue[i];
}
if (std::is_same<T, float>::value) {
REQUIRE(val == Catch::Approx(gpuData[1]));
return true;
}
return val == gpuData[1];
}
template <typename T>
bool verifyMax_divValue(T* gpuData, int len, bool* activeLanes, T* divergentValue) {
T val = Initial<T, AtomicOp::Max>::value;
for (int i = 0; i < len; ++i) {
if (activeLanes[i]) val = std::max<T>(val, divergentValue[i]);
}
if (std::is_same<T, float>::value) {
REQUIRE(val == Catch::Approx(gpuData[2]));
return true;
}
return val == gpuData[2];
}
template <typename T>
bool verifyMin_divValue(T* gpuData, int len, bool* activeLanes, T* divergentValue) {
T val = Initial<T, AtomicOp::Min>::value;
for (int i = 0; i < len; ++i) {
if (activeLanes[i]) val = std::min<T>(val, divergentValue[i]);
}
if (std::is_same<T, float>::value) {
REQUIRE(val == Catch::Approx(gpuData[3]));
return true;
}
return val == gpuData[3];
}
template <typename T>
bool verifyAnd_divValue(T* gpuData, int len, bool* activeLanes, T* divergentValue) {
T val = Initial<T, AtomicOp::And>::value;
for (int i = 0; i < len; ++i) {
if (activeLanes[i]) val &= ~(1 << divergentValue[i]);
}
return val == gpuData[4];
}
template <typename T>
bool verifyOr_divValue(T* gpuData, int len, bool* activeLanes, T* divergentValue) {
T val = Initial<T, AtomicOp::Or>::value;
for (int i = 0; i < len; ++i) {
if (activeLanes[i]) val |= (1 << divergentValue[i]);
}
return val == gpuData[5];
}
template <typename T>
bool verifyXor_divValue(T* gpuData, int len, bool* activeLanes, T* divergentValue) {
T val = Initial<T, AtomicOp::Xor>::value;
for (int i = 0; i < len; ++i) {
if (activeLanes[i]) val ^= divergentValue[i];
}
return val == gpuData[6];
}
// Kernels exercising atomic ops on uniform value for random set of active lanes in wavefront
template <typename T> __global__ void testAtomicAdd_uniValue(T* g_odata, bool* g_activelanes) {
T uniformValue = Uniform<T, AtomicOp::Add>::value;
const unsigned tid = blockDim.x * blockIdx.x + threadIdx.x;
if (g_activelanes[tid]) atomicAdd(&g_odata[0], uniformValue);
}
template <typename T> __global__ void testAtomicSub_uniValue(T* g_odata, bool* g_activelanes) {
T uniformValue = Uniform<T, AtomicOp::Sub>::value;
const unsigned tid = blockDim.x * blockIdx.x + threadIdx.x;
if (g_activelanes[tid]) atomicSub(&g_odata[1], uniformValue);
}
template <typename T> __global__ void testAtomicMax_uniValue(T* g_odata, bool* g_activelanes) {
T uniformValue = Uniform<T, AtomicOp::Max>::value;
const unsigned tid = blockDim.x * blockIdx.x + threadIdx.x;
if (g_activelanes[tid]) atomicMax(&g_odata[2], uniformValue);
}
template <typename T> __global__ void testAtomicMin_uniValue(T* g_odata, bool* g_activelanes) {
T uniformValue = Uniform<T, AtomicOp::Min>::value;
const unsigned tid = blockDim.x * blockIdx.x + threadIdx.x;
if (g_activelanes[tid]) atomicMin(&g_odata[3], uniformValue);
}
template <typename T> __global__ void testAtomicAnd_uniValue(T* g_odata, bool* g_activelanes) {
T uniformValue = Uniform<T, AtomicOp::And>::value;
const unsigned tid = blockDim.x * blockIdx.x + threadIdx.x;
if (g_activelanes[tid]) atomicAnd(&g_odata[4], uniformValue);
}
template <typename T> __global__ void testAtomicOr_uniValue(T* g_odata, bool* g_activelanes) {
T uniformValue = Uniform<T, AtomicOp::Or>::value;
const unsigned tid = blockDim.x * blockIdx.x + threadIdx.x;
if (g_activelanes[tid]) atomicOr(&g_odata[5], uniformValue);
}
template <typename T> __global__ void testAtomicXor_uniValue(T* g_odata, bool* g_activelanes) {
T uniformValue = Uniform<T, AtomicOp::Xor>::value;
const unsigned tid = blockDim.x * blockIdx.x + threadIdx.x;
if (g_activelanes[tid]) atomicXor(&g_odata[6], uniformValue);
}
// Kernels exercising atomic ops on divergent values for random set of active lanes in wavefront
template <typename T>
__global__ void testAtomicAdd_divValue(T* g_odata, bool* g_activelanes, T* g_divergentvalue) {
const unsigned tid = blockDim.x * blockIdx.x + threadIdx.x;
if (g_activelanes[tid]) atomicAdd(&g_odata[0], g_divergentvalue[tid]);
;
}
template <typename T>
__global__ void testAtomicSub_divValue(T* g_odata, bool* g_activelanes, T* g_divergentvalue) {
const unsigned tid = blockDim.x * blockIdx.x + threadIdx.x;
if (g_activelanes[tid]) atomicSub(&g_odata[1], g_divergentvalue[tid]);
}
template <typename T>
__global__ void testAtomicMax_divValue(T* g_odata, bool* g_activelanes, T* g_divergentvalue) {
const unsigned tid = blockDim.x * blockIdx.x + threadIdx.x;
if (g_activelanes[tid]) atomicMax(&g_odata[2], g_divergentvalue[tid]);
}
template <typename T>
__global__ void testAtomicMin_divValue(T* g_odata, bool* g_activelanes, T* g_divergentvalue) {
const unsigned tid = blockDim.x * blockIdx.x + threadIdx.x;
if (g_activelanes[tid]) atomicMin(&g_odata[3], g_divergentvalue[tid]);
}
template <typename T>
__global__ void testAtomicAnd_divValue(T* g_odata, bool* g_activelanes, T* g_divergentvalue) {
const unsigned tid = blockDim.x * blockIdx.x + threadIdx.x;
if (g_activelanes[tid]) atomicAnd(&g_odata[4], ~(1 << g_divergentvalue[tid]));
}
template <typename T>
__global__ void testAtomicOr_divValue(T* g_odata, bool* g_activelanes, T* g_divergentvalue) {
const unsigned tid = blockDim.x * blockIdx.x + threadIdx.x;
if (g_activelanes[tid]) atomicOr(&g_odata[5], (1 << g_divergentvalue[tid]));
}
template <typename T>
__global__ void testAtomicXor_divValue(T* g_odata, bool* g_activelanes, T* g_divergentvalue) {
const unsigned tid = blockDim.x * blockIdx.x + threadIdx.x;
if (g_activelanes[tid]) atomicXor(&g_odata[6], g_divergentvalue[tid]);
}
template <typename T> static void runIntTest() {
bool testResult = true;
unsigned int numThreads = 256;
unsigned int numBlocks = 64;
// This test exercises 7 atomic operations
// 1. atomicAdd
// 2. atomicSub
// 3. atomicMax
// 4. atomicMin
// 5. atomicAnd
// 6. atomicOr
// 7. atomicXor
unsigned int numData = 7;
unsigned int memSize = sizeof(T) * numData;
unsigned int N = numThreads * numBlocks;
// allocate mem for the result on host side
T* hOData = reinterpret_cast<T*>(malloc(memSize));
bool* hIActiveLanes = (bool*)(malloc(N * sizeof(bool)));
// initialize the memory
hOData[0] = Initial<T, AtomicOp::Add>::value;
hOData[1] = Initial<T, AtomicOp::Sub>::value;
hOData[2] = Initial<T, AtomicOp::Max>::value;
hOData[3] = Initial<T, AtomicOp::Min>::value;
hOData[4] = Initial<T, AtomicOp::And>::value;
hOData[5] = Initial<T, AtomicOp::Or>::value;
hOData[6] = Initial<T, AtomicOp::Xor>::value;
for (unsigned int i = 0; i < N; i++) {
// Genearte random values betwenn 0 to 9 to get random active and inactive lanes for atomic
// operations
hIActiveLanes[i] = std::rand() / ((RAND_MAX + 1u) / 9) % 2 == 0;
}
// allocate device memory for result
T* dOData;
bool* dIActiveLanes;
HIP_CHECK(hipMalloc(reinterpret_cast<void**>(&dOData), memSize));
HIP_CHECK(hipMalloc(reinterpret_cast<bool**>(&dIActiveLanes), N * sizeof(bool)));
// copy host memory to device to initialize to zero
HIP_CHECK(hipMemcpy(dOData, hOData, memSize, hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(dIActiveLanes, hIActiveLanes, N, hipMemcpyHostToDevice));
// execute the atomicAdd kernel
hipLaunchKernelGGL(testAtomicAdd_uniValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult == verifyAdd(hOData, numThreads * numBlocks, hIActiveLanes));
// execute the atomicSub kernel
hipLaunchKernelGGL(testAtomicSub_uniValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult == verifySub(hOData, numThreads * numBlocks, hIActiveLanes));
// execute the atomicMax kernel
hipLaunchKernelGGL(testAtomicMax_uniValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult == verifyMax(hOData, numThreads * numBlocks, hIActiveLanes));
// execute the atomicMin kernel
hipLaunchKernelGGL(testAtomicMin_uniValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult == verifyMin(hOData, numThreads * numBlocks, hIActiveLanes));
// execute the atomicAnd kernel
hipLaunchKernelGGL(testAtomicAnd_uniValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult == verifyAnd(hOData, numThreads * numBlocks, hIActiveLanes));
// execute the atomicOr kernel
hipLaunchKernelGGL(testAtomicOr_uniValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult == verifyOr(hOData, numThreads * numBlocks, hIActiveLanes));
// execute the atomicXor kernel
hipLaunchKernelGGL(testAtomicXor_uniValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult == verifyXor(hOData, numThreads * numBlocks, hIActiveLanes));
// Cleanup memory
free(hOData);
free(hIActiveLanes);
HIP_CHECK(hipFree(dOData));
HIP_CHECK(hipFree(dIActiveLanes));
}
static void runFloatTest() {
bool testResult = true;
unsigned int numThreads = 256;
unsigned int numBlocks = 64;
unsigned int numData = 4;
unsigned int memSize = sizeof(float) * numData;
unsigned int N = numThreads * numBlocks;
// allocate mem for the result on host side
float* hOData = reinterpret_cast<float*>(malloc(memSize));
bool* hIActiveLanes = (bool*)(malloc(N * sizeof(bool)));
// initialize the memory
hOData[0] = Initial<float, AtomicOp::Add>::value;
hOData[1] = Initial<float, AtomicOp::Sub>::value;
hOData[2] = Initial<float, AtomicOp::Max>::value;
hOData[3] = Initial<float, AtomicOp::Min>::value;
for (unsigned int i = 0; i < N; i++) {
// Genearte random values betwenn 0 to 9 to get random active and inactive lanes for atomic
// operations
hIActiveLanes[i] = std::rand() / ((RAND_MAX + 1u) / 9) % 2 == 0;
}
// allocate device memory for result
float* dOData;
bool* dIActiveLanes;
HIP_CHECK(hipMalloc(reinterpret_cast<void**>(&dOData), memSize));
HIP_CHECK(hipMalloc(reinterpret_cast<bool**>(&dIActiveLanes), N * sizeof(bool)));
// copy host memory to device to initialize to zero
HIP_CHECK(hipMemcpy(dOData, hOData, memSize, hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(dIActiveLanes, hIActiveLanes, N * sizeof(bool), hipMemcpyHostToDevice));
// execute the atomicAdd kernel
hipLaunchKernelGGL(testAtomicAdd_uniValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult == verifyAdd(hOData, numThreads * numBlocks, hIActiveLanes));
// execute the atomicSub kernel
hipLaunchKernelGGL(testAtomicSub_uniValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult == verifySub(hOData, numThreads * numBlocks, hIActiveLanes));
// execute the atomicMax kernel
hipLaunchKernelGGL(testAtomicMax_uniValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult == verifyMax(hOData, numThreads * numBlocks, hIActiveLanes));
// execute the atomicMin kernel
hipLaunchKernelGGL(testAtomicMin_uniValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult == verifyMin(hOData, numThreads * numBlocks, hIActiveLanes));
// Cleanup memory
free(hOData);
free(hIActiveLanes);
HIP_CHECK(hipFree(dOData));
HIP_CHECK(hipFree(dIActiveLanes));
}
template <typename T> static void runDivIntTest() {
bool testResult = true;
unsigned int numThreads = 256;
unsigned int numBlocks = 64;
unsigned int numData = 7;
unsigned int memSize = sizeof(T) * numData;
unsigned int N = numThreads * numBlocks;
// allocate mem for the result on host side
T* hOData = reinterpret_cast<T*>(malloc(memSize));
T* hIDivValues = reinterpret_cast<T*>(malloc(N * sizeof(T)));
bool* hIActiveLanes = (bool*)(malloc(N * sizeof(bool)));
// initialize the memory
hOData[0] = Initial<T, AtomicOp::Add>::value;
hOData[1] = Initial<T, AtomicOp::Sub>::value;
hOData[2] = Initial<T, AtomicOp::Max>::value;
hOData[3] = Initial<T, AtomicOp::Min>::value;
hOData[4] = Initial<T, AtomicOp::And>::value;
hOData[5] = Initial<T, AtomicOp::Or>::value;
hOData[6] = Initial<T, AtomicOp::Xor>::value;
for (unsigned int i = 0; i < N; i++) {
// Genearte random values betwenn 0 to 9 to get radom active and inactive lanes for atomic
// operations
unsigned randomValue = std::rand() / ((RAND_MAX + 1u) / 9);
hIDivValues[i] = randomValue;
hIActiveLanes[i] = randomValue % 2 == 0;
}
// allocate device memory for result
T* dOData;
bool* dIActiveLanes;
T* dIDivValues;
HIP_CHECK(hipMalloc(reinterpret_cast<void**>(&dOData), memSize));
HIP_CHECK(hipMalloc(reinterpret_cast<bool**>(&dIActiveLanes), N * sizeof(bool)));
HIP_CHECK(hipMalloc(reinterpret_cast<void**>(&dIDivValues), N * sizeof(T)));
// copy host memory to device to initialize to zero
HIP_CHECK(hipMemcpy(dOData, hOData, memSize, hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(dIActiveLanes, hIActiveLanes, N * sizeof(bool), hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(dIDivValues, hIDivValues, N * sizeof(T), hipMemcpyHostToDevice));
// execute the atomicAdd kernel
hipLaunchKernelGGL(testAtomicAdd_divValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes, dIDivValues);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult ==
verifyAdd_divValue(hOData, numThreads * numBlocks, hIActiveLanes, hIDivValues));
// execute the atomicSub kernel
hipLaunchKernelGGL(testAtomicSub_divValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes, dIDivValues);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult ==
verifySub_divValue(hOData, numThreads * numBlocks, hIActiveLanes, hIDivValues));
// execute the atomicMax kernel
hipLaunchKernelGGL(testAtomicMax_divValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes, dIDivValues);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult ==
verifyMax_divValue(hOData, numThreads * numBlocks, hIActiveLanes, hIDivValues));
// execute the atomicMin kernel
hipLaunchKernelGGL(testAtomicMin_divValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes, dIDivValues);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult ==
verifyMin_divValue(hOData, numThreads * numBlocks, hIActiveLanes, hIDivValues));
// execute the atomicAnd kernel
hipLaunchKernelGGL(testAtomicAnd_divValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes, dIDivValues);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult ==
verifyAnd_divValue(hOData, numThreads * numBlocks, hIActiveLanes, hIDivValues));
// execute the atomicOr kernel
hipLaunchKernelGGL(testAtomicOr_divValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes, dIDivValues);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult ==
verifyOr_divValue(hOData, numThreads * numBlocks, hIActiveLanes, hIDivValues));
// execute the atomicXor kernel
hipLaunchKernelGGL(testAtomicXor_divValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes, dIDivValues);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult ==
verifyXor_divValue(hOData, numThreads * numBlocks, hIActiveLanes, hIDivValues));
// Cleanup memory
free(hOData);
free(hIDivValues);
free(hIActiveLanes);
HIP_CHECK(hipFree(dOData));
HIP_CHECK(hipFree(dIActiveLanes));
HIP_CHECK(hipFree(dIDivValues));
}
static void runDivFloatTest() {
bool testResult = true;
unsigned int numThreads = 256;
unsigned int numBlocks = 64;
unsigned int numData = 4;
unsigned int memSize = sizeof(float) * numData;
unsigned int N = numThreads * numBlocks;
// allocate mem for the result on host side
float* hOData = reinterpret_cast<float*>(malloc(memSize));
float* hIDivValues = reinterpret_cast<float*>(malloc(N * sizeof(float)));
bool* hIActiveLanes = (bool*)(malloc(N * sizeof(bool)));
// initialize the memory
hOData[0] = Initial<float, AtomicOp::Add>::value;
hOData[1] = Initial<float, AtomicOp::Sub>::value;
hOData[2] = Initial<float, AtomicOp::Max>::value;
hOData[3] = Initial<float, AtomicOp::Min>::value;
for (unsigned int i = 0; i < N; i++) {
// Genearte random values betwenn 0 to 9 to get random active and inactive lanes for atomic
// operations
unsigned randomValue = std::rand() / ((RAND_MAX + 1u) / 9);
hIDivValues[i] = (float)std::rand() / (float)randomValue;
hIActiveLanes[i] = randomValue % 2 == 0;
}
// allocate device memory for result
float* dOData;
bool* dIActiveLanes;
float* dIDivValues;
HIP_CHECK(hipMalloc(reinterpret_cast<void**>(&dOData), memSize));
HIP_CHECK(hipMalloc(reinterpret_cast<bool**>(&dIActiveLanes), N * sizeof(bool)));
HIP_CHECK(hipMalloc(reinterpret_cast<void**>(&dIDivValues), N * sizeof(float)));
// copy host memory to device to initialize to zero
HIP_CHECK(hipMemcpy(dOData, hOData, memSize, hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(dIActiveLanes, hIActiveLanes, N * sizeof(bool), hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(dIDivValues, hIDivValues, N * sizeof(float), hipMemcpyHostToDevice));
// execute the atomicAdd kernel
hipLaunchKernelGGL(testAtomicAdd_divValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes, dIDivValues);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult ==
verifyAdd_divValue(hOData, numThreads * numBlocks, hIActiveLanes, hIDivValues));
// execute the atomicSub kernel
hipLaunchKernelGGL(testAtomicSub_divValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes, dIDivValues);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult ==
verifySub_divValue(hOData, numThreads * numBlocks, hIActiveLanes, hIDivValues));
// execute the atomicMax kernel
hipLaunchKernelGGL(testAtomicMax_divValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes, dIDivValues);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult ==
verifyMax_divValue(hOData, numThreads * numBlocks, hIActiveLanes, hIDivValues));
// execute the atomicMin kernel
hipLaunchKernelGGL(testAtomicMin_divValue, dim3(numBlocks), dim3(numThreads), 0, 0, dOData,
dIActiveLanes, dIDivValues);
// Copy result from device to host
HIP_CHECK(hipMemcpy(hOData, dOData, memSize, hipMemcpyDeviceToHost));
// Compute reference solution
REQUIRE(testResult ==
verifyMin_divValue(hOData, numThreads * numBlocks, hIActiveLanes, hIDivValues));
// Cleanup memory
free(hOData);
free(hIDivValues);
free(hIActiveLanes);
HIP_CHECK(hipFree(dOData));
HIP_CHECK(hipFree(dIActiveLanes));
HIP_CHECK(hipFree(dIDivValues));
}
/*
This testcases perform the following scenario of atomic opearations on Uniform and Divergent values
for INT, UNSIGNED INT, and FLOAT types:
Uniform and Divergent Integer (int, unsigned int):
// 1. atomicAdd
// 2. atomicSub
// 3. atomicMax
// 4. atomicMin
// 5. atomicAnd
// 6. atomicOr
// 7. atomicXor
Uniform and Divergent Float:
// 1. atomicAdd
// 2. atomicSub
// 3. atomicMax
// 4. atomicMin
*/
HIP_TEST_CASE(Unit_AtomicsWithRandomActiveLanesInWavefront) {
SECTION("Uniform Integer - int") { runIntTest<int>(); }
SECTION("Uniform Integer - unsigned int") { runIntTest<unsigned int>(); }
SECTION("Uniform Float") { runFloatTest(); }
SECTION("Divergent Integer - int") { runDivIntTest<int>(); }
SECTION("Divergent Integer - unsigned int") { runDivIntTest<unsigned int>(); }
SECTION("Divergent Float") { runDivFloatTest(); }
}