Files
TheRock/rocm-libraries/projects/rocprim/benchmark/benchmark_utils.hpp
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

587 lines
21 KiB
C++

// 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 <rocprim/block/block_load.hpp>
#include <rocprim/block/block_scan.hpp>
#include <rocprim/config.hpp>
#include <rocprim/device/config_types.hpp>
#include <rocprim/device/detail/device_config_helper.hpp>
#include <rocprim/intrinsics/arch.hpp>
#include <rocprim/intrinsics/thread.hpp>
#include <rocprim/type_traits.hpp>
#include <rocprim/types.hpp>
#include <rocprim/types/tuple.hpp>
#include <algorithm>
#include <array>
#include <cstddef>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <limits>
#include <memory>
#include <numeric>
#include <random>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <stdint.h>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
/** \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<typename OutputIterator, typename U, typename V, typename Generator>
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<rocprim::is_arithmetic<common::it_value_t<OutputIterator>>::value,
OutputIterator>
{
using value_type = common::it_value_t<OutputIterator>;
// Ensure value_type is valid for distribution
using dis_type = std::conditional_t<
rocprim::is_integral<value_type>::value,
// Integral
std::conditional_t<
common::is_valid_for_int_distribution<value_type>::value,
value_type,
std::conditional_t<std::is_signed<value_type>::value, int, unsigned int>>,
// Floating point
std::conditional_t<std::is_same_v<rocprim::half, value_type>
|| std::is_same_v<rocprim::bfloat16, value_type>,
float,
value_type>>;
using distribution_type =
typename std::conditional<rocprim::is_integral<dis_type>::value,
common::uniform_int_distribution<dis_type>,
std::uniform_real_distribution<dis_type>>::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<typename OutputIterator, typename Generator>
inline auto generate_random_data_n(OutputIterator it,
size_t size,
common::it_value_t<OutputIterator> min,
common::it_value_t<OutputIterator> max,
Generator& gen,
size_t max_random_size = 1024 * 1024)
-> std::enable_if_t<!rocprim::is_arithmetic<common::it_value_t<OutputIterator>>::value,
OutputIterator>
{
using T = common::it_value_t<OutputIterator>;
if constexpr(common::is_custom_type<T>::value)
{
using first_type = typename T::first_type;
using second_type = typename T::second_type;
std::vector<first_type> fdata(size);
std::vector<second_type> 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<decltype(max.x), void>(), "Custom types must have an x field");
using field_type = decltype(max.x);
std::vector<field_type> 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<typename T>
inline std::vector<T>
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<T> 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<typename T, typename U>
struct is_comparable
{
private:
// A dummy template function that attempts to compare two objects of types T and U
template<typename V, typename W>
static auto test(V&& v, W&& w)
-> decltype(std::declval<V>() < std::declval<W>(), std::true_type{});
// Fallback if the above template function is not valid
template<typename, typename>
static std::false_type test(...);
public:
// Final result
static constexpr bool value = decltype(test<T, U>(std::declval<T>(), std::declval<U>()))::value;
};
template<typename T, typename U, typename V>
struct is_comparable<common::custom_type<U, V>, T>
: std::conditional_t<rocprim::is_arithmetic<T>::value
|| !std::is_same<T, common::custom_type<U, V>>::value,
std::false_type,
std::true_type>
{};
template<typename CustomType>
struct custom_type_decomposer
{
static_assert(
common::is_custom_type<CustomType>::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<T&, U&>
operator()(CustomType& key) const
{
return ::rocprim::tuple<T&, U&>{key.x, key.y};
}
};
namespace common
{
template<typename T>
struct generate_limits<T, std::enable_if_t<common::is_custom_type<T>::value>>
{
using F = typename T::first_type;
using S = typename T::second_type;
static inline T min()
{
return T(generate_limits<F>::min(), generate_limits<S>::min());
}
static inline T max()
{
return T(generate_limits<F>::max(), generate_limits<S>::max());
}
};
} // namespace common
template<typename T, typename U, typename V>
inline std::vector<T> get_random_data(
size_t size, U min, V max, unsigned int seed, size_t max_random_size = 1024 * 1024)
{
std::vector<T> data(size);
engine_type gen(seed);
generate_random_data_n(data.begin(), size, min, max, gen, max_random_size);
return data;
}
template<typename T, typename U>
auto limit_cast(U value) -> T
{
static_assert(rocprim::is_arithmetic<T>::value && rocprim::is_arithmetic<U>::value
&& is_comparable<T, U>::value,
"Cannot use limit_cast with chosen types of T and U");
using common_type = typename std::common_type<T, U>::type;
if(rocprim::is_unsigned<T>::value)
{
if(value < 0)
{
return rocprim::numeric_limits<T>::min();
}
if(static_cast<common_type>(value)
> static_cast<common_type>(rocprim::numeric_limits<T>::max()))
{
return rocprim::numeric_limits<T>::max();
}
}
else if(rocprim::is_signed<T>::value && rocprim::is_unsigned<U>::value)
{
if(value > rocprim::numeric_limits<T>::max())
{
return rocprim::numeric_limits<T>::max();
}
}
else if(rocprim::is_floating_point<T>::value)
{
return static_cast<T>(value);
}
else // Both T and U are signed
{
if(value < static_cast<common_type>(rocprim::numeric_limits<T>::min()))
{
return rocprim::numeric_limits<T>::min();
}
else if(value > static_cast<common_type>(rocprim::numeric_limits<T>::max()))
{
return rocprim::numeric_limits<T>::max();
}
}
return static_cast<T>(value);
}
template<typename T, typename U, typename V>
inline auto limit_random_range(U range_start, V range_end)
{
if constexpr(common::is_custom_type<T>::value)
{
static_assert(is_comparable<typename T::first_type, U>::value,
"T::first_type must be comparable with U");
static_assert(is_comparable<typename T::second_type, U>::value,
"T::second_type must be comparable with U");
static_assert(is_comparable<typename T::first_type, V>::value,
"T::first_type must be comparable with V");
static_assert(is_comparable<typename T::second_type, V>::value,
"T::second_type must be comparable with V");
static_assert(rocprim::is_arithmetic<typename T::first_type>::value,
"T::first_type must be arithmetic");
static_assert(rocprim::is_arithmetic<typename T::second_type>::value,
"T::second_type must be arithmetic");
static_assert(rocprim::is_arithmetic<U>::value, "U must be arithmetic");
static_assert(rocprim::is_arithmetic<V>::value, "V must be arithmetic");
return std::pair<T, T>{
T{limit_cast<typename T::first_type>(range_start),
limit_cast<typename T::second_type>(range_start)},
T{ limit_cast<typename T::first_type>(range_end),
limit_cast<typename T::second_type>(range_end) }
};
}
else if constexpr(is_comparable<T, U>::value && is_comparable<T, V>::value)
{
if constexpr(is_comparable<V, U>::value)
{
using common_type = typename std::common_type<T, U>::type;
if(static_cast<common_type>(range_start) > static_cast<common_type>(range_end))
{
throw std::range_error("limit_random_range: Incorrect range used!");
}
}
T start = limit_cast<T>(range_start);
T end = limit_cast<T>(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<T, T>{static_cast<T>(range_start), static_cast<T>(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<typename T>
std::vector<T>
get_random_segments(const size_t size, const size_t max_segment_length, unsigned int seed)
{
static_assert(rocprim::is_arithmetic<T>::value, "Key type must be arithmetic");
engine_type prng(seed);
common::uniform_int_distribution<size_t> segment_length_distribution(
std::numeric_limits<size_t>::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<rocprim::half, T>::value,
float,
typename std::conditional<
rocprim::is_integral<T>::value && !common::is_valid_for_int_distribution<T>::value,
typename std::conditional<std::is_signed<T>::value, int, unsigned int>::type,
T>::type>::type;
using key_distribution_type = std::conditional_t<rocprim::is_integral<T>::value,
common::uniform_int_distribution<dis_type>,
std::uniform_real_distribution<dis_type>>;
key_distribution_type key_distribution(rocprim::numeric_limits<T>::max());
std::vector<T> 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<typename T>
std::vector<T>
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<size_t> segment_length_distribution(1, max_segment_length);
std::vector<T> 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<typename T, typename U = rocprim::empty_type, typename V = rocprim::empty_type>
inline auto get_random_value(U min, V max, size_t seed_value)
{
if constexpr(rocprim::is_arithmetic<T>::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<T>::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<typename T, T, typename>
struct make_index_range_impl;
template<typename T, T Start, T... I>
struct make_index_range_impl<T, Start, std::integer_sequence<T, I...>>
{
using type = std::integer_sequence<T, (Start + I)...>;
};
// make a std::integer_sequence with values from Start to End inclusive
template<typename T, T Start, T End>
using make_index_range =
typename make_index_range_impl<T, Start, std::make_integer_sequence<T, End - Start + 1>>::type;
template<typename T, template<T> class Function, T... I, typename... Args>
void static_for_each_impl(std::integer_sequence<T, I...>, Args&&... args)
{
int a[] = {(Function<I>{}(std::forward<Args>(args)...), 0)...};
static_cast<void>(a);
}
// call the supplied template with all values of the std::integer_sequence Indices
template<typename Indices, template<typename Indices::value_type> class Function, typename... Args>
void static_for_each(Args&&... args)
{
static_for_each_impl<typename Indices::value_type, Function>(Indices{},
std::forward<Args>(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<std::size_t Size, std::size_t Alignment>
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<int32_t, int32_t>;
PRIMBENCH_REGISTER_TYPE(custom_i32_i32, "custom<i32,i32>")
using custom_f32_f32 = common::custom_type<float, float>;
PRIMBENCH_REGISTER_TYPE(custom_f32_f32, "custom<f32,f32>")
using custom_f64_f64 = common::custom_type<double, double>;
PRIMBENCH_REGISTER_TYPE(custom_f64_f64, "custom<f64,f64>")
using custom_i32_f64 = common::custom_type<int32_t, double>;
PRIMBENCH_REGISTER_TYPE(custom_i32_f64, "custom<i32,f64>")
using custom_i8_f64 = common::custom_type<int8_t, double>;
PRIMBENCH_REGISTER_TYPE(custom_i8_f64, "custom<i8,f64>")
using custom_i8_i16 = common::custom_type<int8_t, int16_t>;
PRIMBENCH_REGISTER_TYPE(custom_i8_i16, "custom<i8,i16>")
using custom_i64_f64 = common::custom_type<int64_t, double>;
PRIMBENCH_REGISTER_TYPE(custom_i64_f64, "custom<i64,f64>")
using custom_f32_i16 = common::custom_type<float, int16_t>;
PRIMBENCH_REGISTER_TYPE(custom_f32_i16, "custom<f32,i16>")
using custom_i64_i64 = common::custom_type<int64_t, int64_t>;
PRIMBENCH_REGISTER_TYPE(custom_i64_i64, "custom<i64,i64>")
using copyable_i8_f64 = common::custom_type_copyable<int8_t, double>;
PRIMBENCH_REGISTER_TYPE(copyable_i8_f64, "copyable<i8,f64>")
using copyable_f64_f64 = common::custom_type_copyable<double, double>;
PRIMBENCH_REGISTER_TYPE(copyable_f64_f64, "copyable<f64,f64>")
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>")