// Copyright (c) 2017-2026 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. #pragma once #include "primbench.hpp" #include "../common/utils.hpp" #include "../common/utils_custom_type.hpp" #include "../common/utils_data_generation.hpp" #include "../common/utils_half.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** \brief Maximum shared memory (in bytes) used for kernel tuning. * * 65536 bytes equals 64 KiB, which is the typical per-block shared memory limit on CUDA * and the per-work-group LDS limit on HIP for most GPUs up to CDNA3. * * \note Future GPU architectures do support more than 64 KiB of shared * memory per block or work-group. This value should eventually be obtained * dynamically from device properties such as hipDeviceProp_t::sharedMemPerBlock * instead of being hardcoded. */ #ifndef TUNING_SHARED_MEMORY_MAX #define TUNING_SHARED_MEMORY_MAX 65536u #endif struct half_less { ROCPRIM_HOST_DEVICE inline bool operator()(const rocprim::half& a, const rocprim::half& b) const { #if __HIP_DEVICE_COMPILE__ return a < b; #else return common::half_to_native(a) < common::half_to_native(b); #endif } }; struct half_plus { ROCPRIM_HOST_DEVICE inline rocprim::half operator()(const rocprim::half& a, const rocprim::half& b) const { #if __HIP_DEVICE_COMPILE__ return a + b; #else return common::native_to_half(common::half_to_native(a) + common::half_to_native(b)); #endif } }; struct half_equal_to { ROCPRIM_HOST_DEVICE inline bool operator()(const rocprim::half& a, const rocprim::half& b) const { #if __HIP_DEVICE_COMPILE__ return a == b; #else return common::half_to_native(a) == common::half_to_native(b); #endif } }; using engine_type = std::minstd_rand; // generate_random_data_n() generates only part of sequence and replicates it, // because benchmarks usually do not need "true" random sequence. template inline auto generate_random_data_n(OutputIterator it, size_t size, U min, V max, Generator& gen, size_t max_random_size = 1024 * 1024) -> std::enable_if_t>::value, OutputIterator> { using value_type = common::it_value_t; // Ensure value_type is valid for distribution using dis_type = std::conditional_t< rocprim::is_integral::value, // Integral std::conditional_t< common::is_valid_for_int_distribution::value, value_type, std::conditional_t::value, int, unsigned int>>, // Floating point std::conditional_t || std::is_same_v, float, value_type>>; using distribution_type = typename std::conditional::value, common::uniform_int_distribution, std::uniform_real_distribution>::type; distribution_type distribution(min, max); std::generate_n(it, std::min(size, max_random_size), [&]() { return distribution(gen); }); for(size_t i = max_random_size; i < size; i += max_random_size) { std::copy_n(it, std::min(size - i, max_random_size), it + i); } return it + size; } // Non arithmetic types template inline auto generate_random_data_n(OutputIterator it, size_t size, common::it_value_t min, common::it_value_t max, Generator& gen, size_t max_random_size = 1024 * 1024) -> std::enable_if_t>::value, OutputIterator> { using T = common::it_value_t; if constexpr(common::is_custom_type::value) { using first_type = typename T::first_type; using second_type = typename T::second_type; std::vector fdata(size); std::vector sdata(size); generate_random_data_n(fdata.begin(), size, min.x, max.x, gen, max_random_size); generate_random_data_n(sdata.begin(), size, min.y, max.y, gen, max_random_size); for(size_t i = 0; i < size; ++i) { it[i] = T(fdata[i], sdata[i]); } } else { static_assert(!std::is_same(), "Custom types must have an x field"); using field_type = decltype(max.x); std::vector field_data(size); generate_random_data_n(field_data.begin(), size, min.x, max.x, gen, max_random_size); for(size_t i = 0; i < size; ++i) { it[i] = T(field_data[i]); } } return it + size; } template inline std::vector get_random_data01(size_t size, float p, unsigned int seed, size_t max_random_size = 1024 * 1024) { engine_type gen(seed); std::bernoulli_distribution distribution(p); std::vector data(size); std::generate(data.begin(), data.begin() + std::min(size, max_random_size), [&]() { return distribution(gen); }); for(size_t i = max_random_size; i < size; i += max_random_size) { std::copy_n(data.begin(), std::min(size - i, max_random_size), data.begin() + i); } return data; } template struct is_comparable { private: // A dummy template function that attempts to compare two objects of types T and U template static auto test(V&& v, W&& w) -> decltype(std::declval() < std::declval(), std::true_type{}); // Fallback if the above template function is not valid template static std::false_type test(...); public: // Final result static constexpr bool value = decltype(test(std::declval(), std::declval()))::value; }; template struct is_comparable, T> : std::conditional_t::value || !std::is_same>::value, std::false_type, std::true_type> {}; template struct custom_type_decomposer { static_assert( common::is_custom_type::value, "custom_type_decomposer can only be used with instantiations of common::custom_type"); using T = typename CustomType::first_type; using U = typename CustomType::second_type; __host__ __device__ ::rocprim::tuple operator()(CustomType& key) const { return ::rocprim::tuple{key.x, key.y}; } }; namespace common { template struct generate_limits::value>> { using F = typename T::first_type; using S = typename T::second_type; static inline T min() { return T(generate_limits::min(), generate_limits::min()); } static inline T max() { return T(generate_limits::max(), generate_limits::max()); } }; } // namespace common template inline std::vector get_random_data( size_t size, U min, V max, unsigned int seed, size_t max_random_size = 1024 * 1024) { std::vector data(size); engine_type gen(seed); generate_random_data_n(data.begin(), size, min, max, gen, max_random_size); return data; } template auto limit_cast(U value) -> T { static_assert(rocprim::is_arithmetic::value && rocprim::is_arithmetic::value && is_comparable::value, "Cannot use limit_cast with chosen types of T and U"); using common_type = typename std::common_type::type; if(rocprim::is_unsigned::value) { if(value < 0) { return rocprim::numeric_limits::min(); } if(static_cast(value) > static_cast(rocprim::numeric_limits::max())) { return rocprim::numeric_limits::max(); } } else if(rocprim::is_signed::value && rocprim::is_unsigned::value) { if(value > rocprim::numeric_limits::max()) { return rocprim::numeric_limits::max(); } } else if(rocprim::is_floating_point::value) { return static_cast(value); } else // Both T and U are signed { if(value < static_cast(rocprim::numeric_limits::min())) { return rocprim::numeric_limits::min(); } else if(value > static_cast(rocprim::numeric_limits::max())) { return rocprim::numeric_limits::max(); } } return static_cast(value); } template inline auto limit_random_range(U range_start, V range_end) { if constexpr(common::is_custom_type::value) { static_assert(is_comparable::value, "T::first_type must be comparable with U"); static_assert(is_comparable::value, "T::second_type must be comparable with U"); static_assert(is_comparable::value, "T::first_type must be comparable with V"); static_assert(is_comparable::value, "T::second_type must be comparable with V"); static_assert(rocprim::is_arithmetic::value, "T::first_type must be arithmetic"); static_assert(rocprim::is_arithmetic::value, "T::second_type must be arithmetic"); static_assert(rocprim::is_arithmetic::value, "U must be arithmetic"); static_assert(rocprim::is_arithmetic::value, "V must be arithmetic"); return std::pair{ T{limit_cast(range_start), limit_cast(range_start)}, T{ limit_cast(range_end), limit_cast(range_end) } }; } else if constexpr(is_comparable::value && is_comparable::value) { if constexpr(is_comparable::value) { using common_type = typename std::common_type::type; if(static_cast(range_start) > static_cast(range_end)) { throw std::range_error("limit_random_range: Incorrect range used!"); } } T start = limit_cast(range_start); T end = limit_cast(range_end); return std::make_pair(start, end); } // Selected for non-standard float types, e.g. half, which cannot be compared with the limit types. return std::pair{static_cast(range_start), static_cast(range_end)}; } inline bool is_warp_size_supported(const unsigned int required_warp_size, const int device_id) { unsigned int warp_size; HIP_CHECK(::rocprim::host_warp_size(device_id, warp_size)); return warp_size >= required_warp_size; } /// \brief Get segments of uniform random size in [1, max_segment_length] with random key. template std::vector get_random_segments(const size_t size, const size_t max_segment_length, unsigned int seed) { static_assert(rocprim::is_arithmetic::value, "Key type must be arithmetic"); engine_type prng(seed); common::uniform_int_distribution segment_length_distribution( std::numeric_limits::min(), max_segment_length); // std::uniform_real_distribution cannot handle: // - rocprim::half, use float instead. // - single byte integer-based types (char, unsigned char, int8_t, uint8_t), use int instead using dis_type = typename std::conditional< std::is_same::value, float, typename std::conditional< rocprim::is_integral::value && !common::is_valid_for_int_distribution::value, typename std::conditional::value, int, unsigned int>::type, T>::type>::type; using key_distribution_type = std::conditional_t::value, common::uniform_int_distribution, std::uniform_real_distribution>; key_distribution_type key_distribution(rocprim::numeric_limits::max()); std::vector keys(size); size_t keys_start_index = 0; while(keys_start_index < size) { const size_t new_segment_length = segment_length_distribution(prng); const size_t new_segment_end = std::min(size, keys_start_index + new_segment_length); const T key = key_distribution(prng); std::fill(keys.begin() + keys_start_index, keys.begin() + new_segment_end, key); keys_start_index += new_segment_length; } return keys; } /// \brief Get segments of uniform random size in [1, max_segment_length] with unique incrementing key. template std::vector get_random_segments_iota(const size_t size, const size_t max_segment_length, unsigned int seed) { engine_type prng(seed); common::uniform_int_distribution segment_length_distribution(1, max_segment_length); std::vector keys(size); size_t segment_index = 0; size_t keys_start_index = 0; while(keys_start_index < size) { const size_t new_segment_length = segment_length_distribution(prng); const size_t new_segment_end = std::min(size, keys_start_index + new_segment_length); const T key = segment_index++; std::fill(keys.begin() + keys_start_index, keys.begin() + new_segment_end, key); keys_start_index += new_segment_length; } return keys; } template inline auto get_random_value(U min, V max, size_t seed_value) { if constexpr(rocprim::is_arithmetic::value) { T result; engine_type gen(seed_value); generate_random_data_n(&result, 1, min, max, gen); return result; } if constexpr(common::is_custom_type::value) { typename T::first_type result_first; typename T::second_type result_second; engine_type gen(seed_value); generate_random_data_n(&result_first, 1, min.x, max.x, gen); generate_random_data_n(&result_second, 1, min.y, max.y, gen); return T{result_first, result_second}; } } template struct make_index_range_impl; template struct make_index_range_impl> { using type = std::integer_sequence; }; // make a std::integer_sequence with values from Start to End inclusive template using make_index_range = typename make_index_range_impl>::type; template class Function, T... I, typename... Args> void static_for_each_impl(std::integer_sequence, Args&&... args) { int a[] = {(Function{}(std::forward(args)...), 0)...}; static_cast(a); } // call the supplied template with all values of the std::integer_sequence Indices template class Function, typename... Args> void static_for_each(Args&&... args) { static_for_each_impl(Indices{}, std::forward(args)...); } // Inserts spaces at beginning of string if string shorter than specified length. inline std::string pad_string(std::string str, const size_t len) { if(len > str.size()) { str.insert(str.begin(), len - str.size(), ' '); } return str; } template struct alignas(Alignment) custom_aligned_type { unsigned char data[Size]; }; PRIMBENCH_REGISTER_TYPE(int8_t, "i8") PRIMBENCH_REGISTER_TYPE(int16_t, "i16") PRIMBENCH_REGISTER_TYPE(int32_t, "i32") PRIMBENCH_REGISTER_TYPE(int64_t, "i64") PRIMBENCH_REGISTER_TYPE(uint8_t, "u8") PRIMBENCH_REGISTER_TYPE(uint32_t, "u32") PRIMBENCH_REGISTER_TYPE(uint64_t, "u64") PRIMBENCH_REGISTER_TYPE(float, "f32") PRIMBENCH_REGISTER_TYPE(double, "f64") PRIMBENCH_REGISTER_TYPE(float2, "float2") PRIMBENCH_REGISTER_TYPE(float4, "float4") PRIMBENCH_REGISTER_TYPE(double2, "double2") PRIMBENCH_REGISTER_TYPE(rocprim::half, "half") PRIMBENCH_REGISTER_TYPE(rocprim::empty_type, "empty") PRIMBENCH_REGISTER_TYPE(rocprim::int128_t, "i128") PRIMBENCH_REGISTER_TYPE(rocprim::uint128_t, "u128") using custom_i32_i32 = common::custom_type; PRIMBENCH_REGISTER_TYPE(custom_i32_i32, "custom") using custom_f32_f32 = common::custom_type; PRIMBENCH_REGISTER_TYPE(custom_f32_f32, "custom") using custom_f64_f64 = common::custom_type; PRIMBENCH_REGISTER_TYPE(custom_f64_f64, "custom") using custom_i32_f64 = common::custom_type; PRIMBENCH_REGISTER_TYPE(custom_i32_f64, "custom") using custom_i8_f64 = common::custom_type; PRIMBENCH_REGISTER_TYPE(custom_i8_f64, "custom") using custom_i8_i16 = common::custom_type; PRIMBENCH_REGISTER_TYPE(custom_i8_i16, "custom") using custom_i64_f64 = common::custom_type; PRIMBENCH_REGISTER_TYPE(custom_i64_f64, "custom") using custom_f32_i16 = common::custom_type; PRIMBENCH_REGISTER_TYPE(custom_f32_i16, "custom") using custom_i64_i64 = common::custom_type; PRIMBENCH_REGISTER_TYPE(custom_i64_i64, "custom") using copyable_i8_f64 = common::custom_type_copyable; PRIMBENCH_REGISTER_TYPE(copyable_i8_f64, "copyable") using copyable_f64_f64 = common::custom_type_copyable; PRIMBENCH_REGISTER_TYPE(copyable_f64_f64, "copyable") using huge_1024_f32_f32 = common::custom_huge_type<1024, float, float>; PRIMBENCH_REGISTER_TYPE(huge_1024_f32_f32, "huge<1024,f32,f32>") using huge_2048_f32_f32 = common::custom_huge_type<2048, float, float>; PRIMBENCH_REGISTER_TYPE(huge_2048_f32_f32, "huge<2048,f32,f32>")