Files
TheRock/rocm-libraries/projects/rocthrust/benchmark/bench_utils/generation_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

652 lines
19 KiB
C++

// MIT License
//
// Copyright (c) 2024-2025 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.
#ifndef ROCTHRUST_BENCHMARKS_BENCH_UTILS_GENERATION_UTILS_HPP_
#define ROCTHRUST_BENCHMARKS_BENCH_UTILS_GENERATION_UTILS_HPP_
// Utils
#include "common/types.hpp"
// Thrust
#include <thrust/detail/config.h>
#include <thrust/copy.h>
#include <thrust/detail/raw_pointer_cast.h>
#include <thrust/device_vector.h>
#include <thrust/distance.h>
#include <thrust/execution_policy.h>
#include <thrust/fill.h>
#include <thrust/find.h>
#include <thrust/generate.h>
#include <thrust/host_vector.h>
#include <thrust/iterator/constant_iterator.h>
#include <thrust/iterator/counting_iterator.h>
#include <thrust/iterator/transform_output_iterator.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/scan.h>
#include <thrust/sequence.h>
#include <thrust/tuple.h>
// rocPRIM
#include <rocprim/rocprim.hpp>
// rocRAND
#include <rocrand/rocrand.h>
// Google Benchmark
#include <benchmark/benchmark.h>
// STL
#include <algorithm>
#include <cstdint>
#include <limits>
#include <random>
#include <string>
#include <type_traits>
#define ROCRAND_CHECK(condition) \
{ \
rocrand_status _status = condition; \
if (_status != ROCRAND_STATUS_SUCCESS) \
{ \
std::cout << "rocRAND error: " << _status << " line: " << __LINE__ << std::endl; \
exit(_status); \
} \
}
namespace bench_utils
{
/// \brief Provides a sequence of seeds.
class managed_seed
{
public:
/// \param[in] seed_string Either "random" to get random seeds,
/// or an unsigned integer to get (a sequence) of deterministic seeds.
managed_seed(const std::string& seed_string)
{
is_random = seed_string == "random";
if (!is_random)
{
const unsigned int seed = std::stoul(seed_string);
std::seed_seq seq{seed};
seq.generate(seeds.begin(), seeds.end());
}
}
managed_seed()
: managed_seed("random"){};
unsigned int get_0() const
{
return is_random ? std::random_device{}() : seeds[0];
}
unsigned int get_1() const
{
return is_random ? std::random_device{}() : seeds[1];
}
unsigned int get_2() const
{
return is_random ? std::random_device{}() : seeds[2];
}
private:
std::array<unsigned int, 3> seeds;
bool is_random;
};
float get_entropy_percentage(int entropy_reduction)
{
switch (entropy_reduction)
{
case 0:
return 100.0;
case 1:
return 81.1;
case 2:
return 54.4;
case 3:
return 33.7;
case 4:
return 20.1;
default:
return 0;
}
}
template <typename T>
T value_from_entropy(float64_t percentage)
{
if (percentage == 100.0)
{
return std::numeric_limits<T>::max();
}
percentage /= 100; // convert percentage to per one
// Select value from the line between the lowest and the highest representable
// values of type T based on the entropy value.
const auto max_val = static_cast<double>(std::numeric_limits<T>::max());
const auto min_val = static_cast<double>(std::numeric_limits<T>::lowest());
const auto result = min_val + percentage * (max_val - min_val);
return static_cast<T>(result);
}
const int entropy_reductions[] = {0, 2, 4, 6};
namespace detail
{
// std::uniform_int_distribution is undefined for anything other than:
// short, int, long, long long, unsigned short, unsigned int, unsigned long, or unsigned long long
template <typename T>
struct is_valid_for_int_distribution
: std::integral_constant<bool,
std::is_same<short, T>::value || std::is_same<unsigned short, T>::value
|| std::is_same<int, T>::value || std::is_same<unsigned int, T>::value
|| std::is_same<long, T>::value || std::is_same<unsigned long, T>::value
|| std::is_same<long long, T>::value || std::is_same<unsigned long long, T>::value>
{};
template <class T, class Enable = void>
struct random_to_item_t
{};
// Floating point types
template <typename T>
struct random_to_item_t<T, typename std::enable_if<std::is_floating_point<T>::value>::type>
{
double m_min;
double m_max;
__host__ __device__ random_to_item_t(T min, T max)
: m_min(static_cast<double>(min))
, m_max(static_cast<double>(max))
{}
__host__ __device__ auto operator()(double random_value) const
{
return static_cast<T>((m_max - m_min) * random_value + m_min);
}
};
// Integral types
template <typename T>
struct random_to_item_t<T, typename std::enable_if<!std::is_floating_point<T>::value>::type>
{
#if THRUST_BENCHMARKS_HAVE_INT128_SUPPORT
using CastT =
typename std::conditional<std::is_same<T, int128_t>::value || std::is_same<T, uint128_t>::value,
typename std::conditional<std::is_signed<T>::value, long, unsigned long>::type,
T>::type;
#else
using CastT = typename std::conditional<std::is_signed<T>::value, long, unsigned long>::type;
#endif
double m_min;
double m_max;
__host__ __device__ random_to_item_t(T min, T max)
: m_min(static_cast<double>(min))
, m_max(static_cast<double>(max))
{}
__host__ __device__ auto operator()(double random_value) const
{
return static_cast<CastT>(floor((m_max - m_min + 1) * random_value + m_min));
}
};
struct and_t
{
template <class T>
__host__ __device__ T operator()(T a, T b) const
{
return a & b;
}
__host__ __device__ float operator()(float a, float b) const
{
const std::uint32_t result = reinterpret_cast<std::uint32_t&>(a) & reinterpret_cast<std::uint32_t&>(b);
return reinterpret_cast<const float&>(result);
}
__host__ __device__ double operator()(double a, double b) const
{
const std::uint64_t result = reinterpret_cast<std::uint64_t&>(a) & reinterpret_cast<std::uint64_t&>(b);
return reinterpret_cast<const double&>(result);
}
};
template <class T>
struct geq_t
{
T val;
__host__ __device__ bool operator()(T x)
{
return x >= val;
}
};
template <class T>
class value_wrapper_t
{
T m_val{};
public:
explicit value_wrapper_t(T val)
: m_val(val)
{}
T get() const
{
return m_val;
}
value_wrapper_t& operator++()
{
m_val++;
return *this;
}
};
class seed_t : public value_wrapper_t<unsigned long long int>
{
public:
using value_wrapper_t::value_wrapper_t;
using value_wrapper_t::operator++;
seed_t()
: value_wrapper_t(42)
{}
};
struct device_generator_base_t
{
const std::size_t elements{0};
const std::string seed_type{"random"};
seed_t seed{};
const int entropy_reduction{0 /*bit_entropy::_1_000*/};
device_generator_base_t(std::size_t m_elements, const std::string& m_seed_type, int m_entropy_reduction)
: elements(m_elements)
, seed_type(m_seed_type)
, entropy_reduction(m_entropy_reduction)
{
ROCRAND_CHECK(rocrand_create_generator(&gen, ROCRAND_RNG_PSEUDO_DEFAULT));
const managed_seed managed_seed{seed_type};
seed = seed_t{managed_seed.get_0()};
}
~device_generator_base_t()
{
ROCRAND_CHECK(rocrand_destroy_generator(gen));
}
template <typename T>
thrust::device_vector<T> generate(T min, T max)
{
thrust::device_vector<T> data(elements);
const thrust::detail::device_t policy{};
if (entropy_reduction == 0) /*bit_entropy::_1_000*/
{
const double* uniform_distribution = this->new_uniform_distribution(seed, data.size());
thrust::transform(
policy, uniform_distribution, uniform_distribution + data.size(), data.data(), random_to_item_t<T>(min, max));
return data;
}
else if (entropy_reduction >= 5) /*bit_entropy::_0_000*/
{
std::mt19937 rng;
rng.seed(static_cast<std::mt19937::result_type>(seed.get()));
std::uniform_real_distribution<float> dist(0.0f, 1.0f);
T random_value = detail::random_to_item_t<T>(min, max)(dist(rng));
thrust::fill(policy, data.data(), data.data() + data.size(), random_value);
return data;
}
else
{
const double* uniform_distribution = this->new_uniform_distribution(seed, data.size());
++seed;
thrust::transform(
policy, uniform_distribution, uniform_distribution + data.size(), data.data(), random_to_item_t<T>(min, max));
const int number_of_steps = entropy_reduction;
thrust::device_vector<T> tmp(data.size());
for (int i = 0; i < number_of_steps; i++, ++seed)
{
this->no_entropy_generate(tmp, min, max);
thrust::transform(policy, data.data(), data.data() + data.size(), tmp.data(), data.data(), detail::and_t{});
}
return data;
}
}
const double* new_uniform_distribution(seed_t seed, std::size_t num_items)
{
distribution.resize(num_items);
double* d_distribution = thrust::raw_pointer_cast(distribution.data());
ROCRAND_CHECK(rocrand_set_seed(gen, seed.get()));
ROCRAND_CHECK(rocrand_generate_uniform_double(gen, d_distribution, num_items));
hipError_t error = hipDeviceSynchronize();
if (error != hipSuccess)
{
std::cout
<< "HIP error: " << hipGetErrorString(error) << " file: " << __FILE__ << " line: " << __LINE__ << std::endl;
exit(error);
}
return d_distribution;
}
private:
rocrand_generator gen;
thrust::device_vector<double> distribution;
template <typename T, typename Policy = thrust::detail::device_t>
void no_entropy_generate(thrust::device_vector<T>& data, T m_min, T m_max)
{
const double* uniform_distribution = this->new_uniform_distribution(seed, data.size());
thrust::transform(
Policy{},
uniform_distribution,
uniform_distribution + data.size(),
data.data(),
detail::random_to_item_t<T>(m_min, m_max));
return;
}
};
template <class T>
struct device_vector_generator_t : device_generator_base_t
{
const T min{std::numeric_limits<T>::min()};
const T max{std::numeric_limits<T>::max()};
device_vector_generator_t(
std::size_t m_elements, const std::string& m_seed_type, int m_entropy_reduction, T m_min, T m_max)
: device_generator_base_t(m_elements, m_seed_type, m_entropy_reduction)
, min(m_min)
, max(m_max)
{}
operator thrust::device_vector<T>()
{
return device_generator_base_t::generate(min, max);
}
};
template <>
struct device_vector_generator_t<void> : device_generator_base_t
{
device_vector_generator_t(std::size_t m_elements, const std::string& m_seed_type, int m_entropy_reduction)
: device_generator_base_t(m_elements, m_seed_type, m_entropy_reduction)
{}
template <typename T>
operator thrust::device_vector<T>()
{
return device_generator_base_t::generate(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
}
};
template <typename T>
std::size_t gen_uniform_offsets(
const std::string seed_type,
thrust::device_vector<T>& segment_offsets,
const std::size_t min_segment_size,
const std::size_t max_segment_size)
{
const T elements = segment_offsets.size() - 2;
segment_offsets = device_generator_base_t(segment_offsets.size(), seed_type, 0 /*bit_entropy::_1_000*/)
.generate(static_cast<T>(min_segment_size), static_cast<T>(max_segment_size));
// Find the range of contiguous offsets starting from index 0 which sum is greater or
// equal than 'elements'.
const thrust::detail::device_t policy{};
// Add the offset 'elements + 1' to the array of segment offsets to make sure that
// there is at least one offset greater than 'elements'.
thrust::fill_n(policy, segment_offsets.data() + elements, 1, elements + 1);
// Perform an exclusive prefix sum scan with first value 0, so what we compute is
// scan[i + 1] = \sum_{i=0}^{i} segment_offsets[i] for i \in [0, elements+1]
// and scan[0] = 0.
thrust::exclusive_scan(
policy,
segment_offsets.data(),
segment_offsets.data() + segment_offsets.size(),
segment_offsets.data() /*, thrust::plus<>{}*/);
// Find first sum of offsets greater than 'elements', we are sure that there is
// going to be one because we added elements + 1 at the end of the segment_offsets.
auto iter = thrust::find_if(
policy, segment_offsets.data(), segment_offsets.data() + segment_offsets.size(), geq_t<T>{elements});
// Compute the element's index.
auto dist = thrust::distance(segment_offsets.data(), iter);
// Fill next item with 'elements'.
thrust::fill_n(policy, segment_offsets.data() + dist, 1, elements);
// Return next item's index.
return dist + 1;
}
template <class T>
struct constant_op
{
std::size_t val;
__device__ __forceinline__ T operator()(const T& /*key*/) const
{
return static_cast<T>(val);
}
};
template <class T>
struct idx_to_op
{
T* keys = nullptr;
std::size_t* segment_offsets = nullptr;
__device__ __forceinline__ std::size_t operator()(std::size_t i)
{
const std::size_t init_offset = segment_offsets[i];
const std::size_t end_offset = segment_offsets[i + 1];
thrust::transform(thrust::device, keys + init_offset, keys + end_offset, keys + init_offset, constant_op<T>{i});
return i;
}
};
// Temporal approach for generation of key segments.
template <typename T>
void gen_key_segments(thrust::device_vector<T>& keys, thrust::device_vector<size_t>& segment_offsets)
{
const std::size_t total_segments = segment_offsets.size() - 1;
thrust::counting_iterator<int> iota(0);
thrust::device_vector<std::size_t> segment_indices(iota, iota + total_segments);
idx_to_op<T> op{thrust::raw_pointer_cast(keys.data()), thrust::raw_pointer_cast(segment_offsets.data())};
thrust::transform(segment_indices.begin(), segment_indices.end(), segment_indices.begin(), op);
}
// TODO: use this approach for gen_key_segments when rocPRIM allows it.
// template <class T>
// struct repeat_index_t
// {
// __host__ __device__ __forceinline__ thrust::constant_iterator<T> operator()(std::size_t i)
// {
// return thrust::constant_iterator<T>(static_cast<T>(i));
// }
// };
// template <typename T>
// struct offset_to_iterator_t
// {
// T* base_it;
// __host__ __device__ __forceinline__ T* operator()(std::size_t offset) const
// {
// return base_it + offset;
// }
// };
// struct offset_to_size_t
// {
// std::size_t* offsets = nullptr;
// __host__ __device__ __forceinline__ std::size_t operator()(std::size_t i)
// {
// return offsets[i + 1] - offsets[i];
// }
// };
//
// template <typename T>
// void gen_key_segments(thrust::device_vector<T>& keys,
// thrust::device_vector<std::size_t>& segment_offsets)
// {
// const std::size_t total_segments = segment_offsets.size() - 1;
// thrust::counting_iterator<int> iota(0);
// repeat_index_t<T> src_transform_op {};
// offset_to_iterator_t<T> dst_transform_op {thrust::raw_pointer_cast(keys.data())};
// offset_to_size_t size_transform_op {thrust::raw_pointer_cast(segment_offsets.data())};
// auto d_range_srcs = thrust::make_transform_iterator(iota, src_transform_op);
// auto d_range_dsts
// = thrust::make_transform_iterator(segment_offsets.begin(), dst_transform_op);
// auto d_range_sizes = thrust::make_transform_iterator(iota, size_transform_op);
// std::uint8_t* d_temp_storage = nullptr;
// std::size_t temp_storage_bytes = 0;
// rocprim::batch_copy(d_temp_storage,
// temp_storage_bytes,
// d_range_srcs,
// d_range_dsts,
// d_range_sizes,
// total_segments);
// thrust::device_vector<std::uint8_t> temp_storage(temp_storage_bytes);
// d_temp_storage = thrust::raw_pointer_cast(temp_storage.data());
// rocprim::batch_copy(d_temp_storage,
// temp_storage_bytes,
// d_range_srcs,
// d_range_dsts,
// d_range_sizes,
// total_segments);
// hipDeviceSynchronize();
// }
struct device_uniform_key_segments_generator_t
{
const std::size_t elements{0};
const std::string seed_type{"random"};
const std::size_t min_segment_size{0};
const std::size_t max_segment_size{0};
device_uniform_key_segments_generator_t(
std::size_t m_elements,
const std::string m_seed_type,
const std::size_t m_min_segment_size,
const std::size_t m_max_segment_size)
: elements(m_elements)
, seed_type(m_seed_type)
, min_segment_size(m_min_segment_size)
, max_segment_size(m_max_segment_size)
{}
template <class KeyT>
operator thrust::device_vector<KeyT>()
{
thrust::device_vector<KeyT> keys(elements);
thrust::device_vector<std::size_t> segment_offsets(keys.size() + 2);
const std::size_t offsets_size =
gen_uniform_offsets(seed_type, segment_offsets, min_segment_size, max_segment_size);
segment_offsets.resize(offsets_size);
gen_key_segments(keys, segment_offsets);
return keys;
}
};
struct gen_uniform_key_segments_t
{
device_uniform_key_segments_generator_t operator()(
const std::size_t elements,
const std::string seed_type,
const std::size_t min_segment_size,
const std::size_t max_segment_size) const
{
return {elements, seed_type, min_segment_size, max_segment_size};
}
};
struct gen_uniform_t
{
gen_uniform_key_segments_t key_segments{};
};
struct gen_t
{
template <class T>
device_vector_generator_t<T> operator()(
std::size_t elements,
const std::string seed_type,
const int entropy = 0 /*100*/,
T min = std::numeric_limits<T>::min,
T max = std::numeric_limits<T>::max()) const
{
return {elements, seed_type, entropy, min, max};
}
device_vector_generator_t<void>
operator()(std::size_t elements, const std::string seed_type, const int entropy = 0 /*100*/) const
{
return {elements, seed_type, entropy};
}
gen_uniform_t uniform{};
};
} // namespace detail
detail::gen_t generate;
} // namespace bench_utils
#endif // ROCTHRUST_BENCHMARKS_BENCH_UTILS_GENERATION_UTILS_HPP_