/* * Copyright (c) Advanced Micro Devices, Inc., or its affiliates. * * SPDX-License-Identifier: MIT */ #define HIP_ENABLE_WARP_SYNC_BUILTINS #define HIP_ENABLE_EXTRA_WARP_SYNC_TYPES #include #include "warp_common.hh" #include #include #include #include #include #include #include #include #include #include #define NELEMS(array) (sizeof(array) / sizeof(array[0])) template // @input an array containing one value per lane to be used as input for the reduction // @masks a list of masks, none of them sharing bits __global__ void multipleMasksKernel(T* output, const T* input, const unsigned long long* masks, int numMasks) { bool isInAnyOfTheMasks = false; int numMask = 0; unsigned long long mask; while (numMask < numMasks && !isInAnyOfTheMasks) { mask = masks[numMask]; if ((1ul << threadIdx.x) & mask) isInAnyOfTheMasks = true; numMask++; } if (!isInAnyOfTheMasks) return; output[threadIdx.x] = __reduce_add_sync(mask, input[threadIdx.x]); } template __global__ void reduceOp(T* output, const T* input, const MaskType* masks, int numReduces, Op) { int tid = threadIdx.x; int laneId = tid % warpSize; for (int i = 0; i < numReduces; i++) { if (masks[i] & (1ul << laneId)) { int idx = warpSize * i + laneId; // call the operator only if the lane is mentioned in the mask T& result = output[idx]; if constexpr (std::is_same>::value) result = __reduce_add_sync(masks[i], input[idx]); else if constexpr (std::is_same>::value) result = __reduce_min_sync(masks[i], input[idx]); else if constexpr (std::is_same>::value) result = __reduce_max_sync(masks[i], input[idx]); else if constexpr (std::is_same>::value) result = __reduce_and_sync(masks[i], input[idx]); else if (std::is_same>::value) result = __reduce_or_sync(masks[i], input[idx]); else if (std::is_same>::value) result = __reduce_xor_sync(masks[i], input[idx]); else assert(false && "Unsupported operator"); } } } template void runTestMultipleMasks(unsigned long long masks[], int numMasks) { using namespace Catch::Matchers; using distribution = typename DistributionType::type; unsigned int wavefrontSize = getWarpSize(); LinearAllocGuard d_masks(LinearAllocs::hipMalloc, numMasks * sizeof(decltype(masks[0]))); LinearAllocGuard d_input, input; LinearAllocGuard output(LinearAllocs::malloc, wavefrontSize * sizeof(T)); LinearAllocGuard d_output(LinearAllocs::hipMalloc, wavefrontSize * sizeof(T)); std::plus op; std::mt19937_64 gen(123); typename distribution::result_type a = std::is_same::value? std::numeric_limits::lowest() : (std::is_signed::value? -1023 : 0); typename distribution::result_type b = std::is_same::value? std::numeric_limits::max() : 1023; distribution distInput(a, b); dim3 blkDim{wavefrontSize}; dim3 grdDim{1u}; T expectedByLane[64]; HIP_CHECK(hipMemcpy(d_masks.ptr(), &masks[0], d_masks.size_bytes(), hipMemcpyHostToDevice)); genRandomBuffers(d_input, input, distInput, gen, wavefrontSize); multipleMasksKernel <<>>(d_output.ptr(), d_input.ptr(), d_masks.ptr(), numMasks); HIP_CHECK(hipMemcpy(output.ptr(), d_output.ptr(), d_output.size_bytes(), hipMemcpyDeviceToHost)); for (int numMask = 0; numMask < numMasks; numMask++) { unsigned long long mask = masks[numMask]; T expected = calculateExpected(expectedByLane, input.ptr(), op, mask, AggregationType::Reduce); int lane = 0; while (lane < wavefrontSize) { if ((1ul << lane) & mask) { T result = output.ptr()[lane]; if constexpr (std::is_integral::value) { // for integral types the result should match exactly if (result != expected) { printMismatch(result, expected, input.ptr(), mask, lane); REQUIRE(result == expected); } } else { compareFloatingPoint>(result, expected, mask, input.ptr(), lane); } } lane++; } } } HIP_TEMPLATE_TEST_CASE(Unit_hipReduceSingleMasks, int, unsigned int, long long, unsigned long long, float, half, double) { unsigned long long fullMask = getWarpSize() == 64 ? ~0ul : 0xFFFFFFFF; unsigned long long oneBitMasks[] = {0b1 & fullMask}; unsigned long long everyFifthMasks[] = {Every5thBit & fullMask}; unsigned long long everyNinethMasks[] = {Every9thBit & fullMask}; unsigned long long everyFifthButNinethMasks[] = {Every5thBut9th & fullMask}; runTestMultipleMasks(oneBitMasks, NELEMS(oneBitMasks)); runTestMultipleMasks(everyFifthMasks, NELEMS(everyFifthMasks)); runTestMultipleMasks(everyNinethMasks, NELEMS(everyNinethMasks)); runTestMultipleMasks(everyFifthButNinethMasks, NELEMS(everyFifthButNinethMasks)); } HIP_TEMPLATE_TEST_CASE(Unit_hipReduceMultipleMasks, int, unsigned int, long long, unsigned long long, float, half, double) { if (getWarpSize() == 64) { unsigned long long masks[] = {0b0110011, 0x0F0F0F0F00000000, 0xF0F0F0F000000000, 0x000000000F0F0F00, 0b0000100}; // these divergent masks, when combined, occupy the whole set of lanes unsigned long long fullMasks[] = {0xFFFF000000000000, 0x0000FFFFFFFF0000, 0x000000000000FFFF}; unsigned long long fullMasksEvenOdd[] = {0x5555555555555555, // even lanes 0xAAAAAAAAAAAAAAAA}; // odd lanes runTestMultipleMasks(masks, NELEMS(masks)); runTestMultipleMasks(fullMasks, NELEMS(fullMasks)); runTestMultipleMasks(fullMasksEvenOdd, NELEMS(fullMasksEvenOdd)); } else { unsigned long long masks1[] = {0x0F0F0F0F, 0xF0F0F0F0}; unsigned long long masks2[] = {0b0110011, 0x0F0F0F00, 0b0000100}; runTestMultipleMasks(masks1, NELEMS(masks1)); runTestMultipleMasks(masks2, NELEMS(masks2)); } } template