// MIT License // // Copyright (c) 2024-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 "benchmark_utils.hpp" #include "../common/utils_data_generation.hpp" #include "../common/utils_device_ptr.hpp" #include #include #include #include #include #ifdef BENCHMARK_CONFIG_TUNING #include #endif #include #include #include #include #ifdef BENCHMARK_CONFIG_TUNING #include #include #endif enum class select_probability { p005, p025, p050, p075, tuning }; constexpr inline float get_probability(select_probability probability) { switch(probability) { case select_probability::p005: return 0.05f; case select_probability::p025: return 0.25f; case select_probability::p050: return 0.50f; case select_probability::p075: return 0.75f; case select_probability::tuning: return 0.0f; // not used } return 0.0f; } constexpr inline const char* get_probability_name(select_probability probability) { switch(probability) { case select_probability::p005: return "0.05"; case select_probability::p025: return "0.25"; case select_probability::p050: return "0.50"; case select_probability::p075: return "0.75"; case select_probability::tuning: return "tuning"; } return "invalid"; } template constexpr auto config_name() { if constexpr(std::is_same_v) { return std::string("default"); } else { auto config = Config(); return primbench::json{} .add("bs", config.kernel_config.block_size) .add("ipt", config.kernel_config.items_per_thread); } } template struct device_select_flag_benchmark : public primbench::benchmark_interface { primbench::json meta() const override { return primbench::json{} .add("lvl", "device") .add("algo", "device_select") .add("subalgo", "flag") .add("data_type", primbench::name()) .add("flag_type", primbench::name()) .add("probability", get_probability_name(Probability)) .add("cfg", config_name()); } void run(primbench::state& state) override { const auto& stream = state.stream; const auto& bytes = state.size; const auto& seed = state.seed; auto seeds = primbench::seeds<2>(seed); size_t items = bytes / sizeof(DataType); std::vector input = get_random_data(items, common::generate_limits::min(), common::generate_limits::max(), seeds[0]); std::vector flags_0; std::vector flags_1; std::vector flags_2; if(is_tuning) { flags_0 = get_random_data01(items, 0.0f, seeds[1]); flags_1 = get_random_data01(items, 0.5f, seeds[1]); flags_2 = get_random_data01(items, 1.0f, seeds[1]); } else { flags_0 = get_random_data01(items, get_probability(Probability), seeds[1]); } common::device_ptr d_input(input); common::device_ptr d_flags_0(flags_0); common::device_ptr d_flags_1; common::device_ptr d_flags_2; if(is_tuning) { d_flags_1.store(flags_1); d_flags_2.store(flags_2); } common::device_ptr d_output(items); common::device_ptr d_selected_count_output(1); const auto dispatch = [&](void* d_temp_storage, size_t& temp_storage_size_bytes) { const auto dispatch_flags = [&](FlagType* d_flags) { HIP_CHECK(rocprim::select(d_temp_storage, temp_storage_size_bytes, d_input.get(), d_flags, d_output.get(), d_selected_count_output.get(), items, stream)); }; dispatch_flags(d_flags_0.get()); if(is_tuning) { dispatch_flags(d_flags_1.get()); dispatch_flags(d_flags_2.get()); } }; // Allocate temporary storage memory size_t temp_storage_size_bytes{}; dispatch(nullptr, temp_storage_size_bytes); common::device_ptr d_temp_storage(temp_storage_size_bytes); state.set_items(items); state.add_reads(items); state.run([&] { dispatch(d_temp_storage.get(), temp_storage_size_bytes); }); } static constexpr bool is_tuning = Probability == select_probability::tuning; }; template struct device_select_predicate_benchmark : public primbench::benchmark_interface { primbench::json meta() const override { return primbench::json{} .add("lvl", "device") .add("algo", "device_select") .add("subalgo", "predicate") .add("data_type", primbench::name()) .add("probability", get_probability_name(Probability)) .add("cfg", config_name()); } void run(primbench::state& state) override { const auto& stream = state.stream; const auto& bytes = state.size; const auto& seed = state.seed; size_t items = bytes / sizeof(DataType); // all data types can represent [0, 127], -1 so a predicate can select all std::vector input = get_random_data(items, static_cast(0), static_cast(126), seed); common::device_ptr d_input(input); common::device_ptr d_output(items); common::device_ptr d_selected_count_output(1); const auto dispatch = [&](void* d_temp_storage, size_t& temp_storage_size_bytes) { const auto dispatch_predicate = [&](float probability) { auto predicate = [probability](const DataType& value) -> bool { return value < static_cast(127 * probability); }; HIP_CHECK(rocprim::select(d_temp_storage, temp_storage_size_bytes, d_input.get(), d_output.get(), d_selected_count_output.get(), items, predicate, stream)); }; if(is_tuning) { dispatch_predicate(0.0f); dispatch_predicate(0.5f); dispatch_predicate(1.0f); } else { dispatch_predicate(get_probability(Probability)); } }; size_t temp_storage_size_bytes{}; dispatch(nullptr, temp_storage_size_bytes); common::device_ptr d_temp_storage(temp_storage_size_bytes); state.set_items(items); state.add_reads(items); state.run([&] { dispatch(d_temp_storage.get(), temp_storage_size_bytes); }); } static constexpr bool is_tuning = Probability == select_probability::tuning; }; template struct device_select_predicated_flag_benchmark : public primbench::benchmark_interface { primbench::json meta() const override { return primbench::json{} .add("lvl", "device") .add("algo", "device_select") .add("subalgo", "predicated_flag") .add("data_type", primbench::name()) .add("flag_type", primbench::name()) .add("probability", get_probability_name(Probability)) .add("cfg", config_name()); } void run(primbench::state& state) override { const auto& stream = state.stream; const auto& bytes = state.size; const auto& seed = state.seed; auto seeds = primbench::seeds<2>(seed); size_t items = bytes / sizeof(DataType); std::vector input = get_random_data(items, common::generate_limits::min(), common::generate_limits::max(), seeds[0]); std::vector flags_0; std::vector flags_1; std::vector flags_2; if(is_tuning) { flags_0 = get_random_data01(items, 0.0f, seeds[1]); flags_1 = get_random_data01(items, 0.5f, seeds[1]); flags_2 = get_random_data01(items, 1.0f, seeds[1]); } else { flags_0 = get_random_data01(items, get_probability(Probability), seeds[1]); } common::device_ptr d_input(input); common::device_ptr d_flags_0(flags_0); common::device_ptr d_flags_1; common::device_ptr d_flags_2; if(is_tuning) { d_flags_1.store(flags_1); d_flags_2.store(flags_2); } common::device_ptr d_output(items); common::device_ptr d_selected_count_output(1); const auto dispatch = [&](void* d_temp_storage, size_t& temp_storage_size_bytes) { const auto dispatch_predicated_flags = [&](FlagType* d_flags) { auto predicate = [](const FlagType& value) -> bool { return value; }; HIP_CHECK(rocprim::select(d_temp_storage, temp_storage_size_bytes, d_input.get(), d_flags, d_output.get(), d_selected_count_output.get(), items, predicate, stream)); }; dispatch_predicated_flags(d_flags_0.get()); if(is_tuning) { dispatch_predicated_flags(d_flags_1.get()); dispatch_predicated_flags(d_flags_2.get()); } }; // Allocate temporary storage memory size_t temp_storage_size_bytes{}; dispatch(nullptr, temp_storage_size_bytes); common::device_ptr d_temp_storage(temp_storage_size_bytes); state.set_items(items); state.add_reads(items); state.run([&] { dispatch(d_temp_storage.get(), temp_storage_size_bytes); }); } static constexpr bool is_tuning = Probability == select_probability::tuning; }; template inline std::vector get_unique_input(size_t items, float probability, unsigned int seed) { using op_type = typename std::conditional::value, half_plus, rocprim::plus>::type; op_type op; std::vector input(items); auto input01 = get_random_data01(items, probability, seed); auto acc = input01[0]; input[0] = acc; for(size_t i = 1; i < input01.size(); ++i) { input[i] = op(acc, input01[i]); } return input; } template struct device_select_unique_benchmark : public primbench::benchmark_interface { primbench::json meta() const override { return primbench::json{} .add("lvl", "device") .add("algo", "device_select") .add("subalgo", "unique") .add("data_type", primbench::name()) .add("probability", get_probability_name(Probability)) .add("cfg", config_name()); } void run(primbench::state& state) override { const auto& stream = state.stream; const auto& bytes = state.size; const auto& seed = state.seed; size_t items = bytes / sizeof(DataType); std::vector input_0; std::vector input_1; std::vector input_2; if(is_tuning) { input_0 = get_unique_input(items, 0.0f, seed); input_1 = get_unique_input(items, 0.5f, seed); input_2 = get_unique_input(items, 1.0f, seed); } else { input_0 = get_unique_input(items, get_probability(Probability), seed); } common::device_ptr d_input_0(input_0); common::device_ptr d_input_1; common::device_ptr d_input_2; if(is_tuning) { d_input_1.store(input_1); d_input_2.store(input_2); } common::device_ptr d_output(items); common::device_ptr d_selected_count_output(1); const auto dispatch = [&](void* d_temp_storage, size_t& temp_storage_size_bytes) { const auto dispatch_flags = [&](DataType* d_input) { HIP_CHECK(rocprim::unique(d_temp_storage, temp_storage_size_bytes, d_input, d_output.get(), d_selected_count_output.get(), items, rocprim::equal_to(), stream)); }; dispatch_flags(d_input_0.get()); if(is_tuning) { dispatch_flags(d_input_1.get()); dispatch_flags(d_input_2.get()); } }; // Allocate temporary storage memory size_t temp_storage_size_bytes{}; dispatch(nullptr, temp_storage_size_bytes); common::device_ptr d_temp_storage(temp_storage_size_bytes); state.set_items(items); state.add_reads(items); state.run([&] { dispatch(d_temp_storage.get(), temp_storage_size_bytes); }); } static constexpr bool is_tuning = Probability == select_probability::tuning; }; template struct device_select_unique_by_key_benchmark : public primbench::benchmark_interface { primbench::json meta() const override { return primbench::json{} .add("lvl", "device") .add("algo", "device_select") .add("subalgo", "unique_by_key") .add("key_type", primbench::name()) .add("value_type", primbench::name()) .add("probability", get_probability_name(Probability)) .add("cfg", config_name()); } void run(primbench::state& state) override { const auto& stream = state.stream; const auto& bytes = state.size; const auto& seed = state.seed; auto seeds = primbench::seeds<2>(seed); size_t items = bytes / sizeof(KeyType); std::vector input_keys_0; std::vector input_keys_1; std::vector input_keys_2; if(is_tuning) { input_keys_0 = get_unique_input(items, 0.0f, seeds[0]); input_keys_1 = get_unique_input(items, 0.5f, seeds[0]); input_keys_2 = get_unique_input(items, 1.0f, seeds[0]); } else { input_keys_0 = get_unique_input(items, get_probability(Probability), seeds[0]); } const auto random_range = limit_random_range(-1000, 1000); const auto input_values = get_random_data(items, random_range.first, random_range.second, seeds[1]); common::device_ptr d_keys_input_0(input_keys_0); common::device_ptr d_keys_input_1; common::device_ptr d_keys_input_2; if(is_tuning) { d_keys_input_1.store(input_keys_1); d_keys_input_2.store(input_keys_2); } common::device_ptr d_values_input(input_values); common::device_ptr d_keys_output(items); common::device_ptr d_values_output(items); common::device_ptr d_selected_count_output(1); const auto dispatch = [&](void* d_temp_storage, size_t& temp_storage_size_bytes) { const auto dispatch_flags = [&](KeyType* d_keys_input) { HIP_CHECK(rocprim::unique_by_key(d_temp_storage, temp_storage_size_bytes, d_keys_input, d_values_input.get(), d_keys_output.get(), d_values_output.get(), d_selected_count_output.get(), items, rocprim::equal_to(), stream)); }; dispatch_flags(d_keys_input_0.get()); if(is_tuning) { dispatch_flags(d_keys_input_1.get()); dispatch_flags(d_keys_input_2.get()); } }; // Allocate temporary storage memory size_t temp_storage_size_bytes{}; dispatch(nullptr, temp_storage_size_bytes); common::device_ptr d_temp_storage(temp_storage_size_bytes); state.set_items(items); state.add_reads(items); state.add_reads(items); state.run([&] { dispatch(d_temp_storage.get(), temp_storage_size_bytes); }); } static constexpr bool is_tuning = Probability == select_probability::tuning; }; #ifdef BENCHMARK_CONFIG_TUNING template struct create_benchmark { static constexpr unsigned int block_size = Config().kernel_config.block_size; static constexpr unsigned int items_per_thread = Config().kernel_config.items_per_thread; static constexpr unsigned int max_shared_memory = TUNING_SHARED_MEMORY_MAX; static constexpr unsigned int max_size_per_element = sizeof(KeyType) + sizeof(ValueType); static constexpr unsigned int max_items_per_thread = max_shared_memory / (block_size * max_size_per_element); void operator()(std::vector>& storage) { storage.emplace_back( std::make_unique>()); if(items_per_thread <= max_items_per_thread) { storage.emplace_back( std::make_unique< device_select_predicated_flag_benchmark>()); } } }; template struct create_benchmark { void operator()(std::vector>& storage) { storage.emplace_back(std::make_unique>()); storage.emplace_back( std::make_unique>()); storage.emplace_back(std::make_unique>()); } }; template struct device_select_benchmark_generator { template struct create_ipt { void operator()(std::vector>& storage) { using config = rocprim::select_config; create_benchmark{}(storage); } }; static void create(std::vector>& storage) { static constexpr int max_items_per_thread = std::min(64 / std::max(sizeof(KeyType), sizeof(ValueType)), size_t{32}); static_for_each, create_ipt>(storage); } }; #endif // BENCHMARK_CONFIG_TUNING