// MIT License // // 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 "benchmark_utils.hpp" #include "../common/utils_device_ptr.hpp" #ifndef BENCHMARK_CONFIG_TUNING #include "../common/utils_custom_type.hpp" #endif #include #include #include #include #include #include #ifdef BENCHMARK_CONFIG_TUNING #include #include #include #else #include #include #endif #include #include #include #include #ifdef BENCHMARK_CONFIG_TUNING #include #else #include #endif inline const char* get_block_scan_algorithm_name(rocprim::block_scan_algorithm alg) { switch(alg) { case rocprim::block_scan_algorithm::using_warp_scan: return "block_scan_algorithm::using_warp_scan"; case rocprim::block_scan_algorithm::reduce_then_scan: return "block_scan_algorithm::reduce_then_scan"; // Not using `default: ...` because it kills effectiveness of -Wswitch } return "default_algorithm"; } 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) .add("method", get_block_scan_algorithm_name(config.block_scan_method)); } } template struct device_scan_by_key_benchmark : public primbench::benchmark_interface { primbench::json meta() const override { auto algo = [] { if constexpr(Deterministic) { return "device_scan_by_key_deterministic"; } else { return "device_scan_by_key"; } }; return primbench::json{} .add("lvl", "device") .add("algo", algo()) .add("exclusive", Exclusive) .add("key_type", primbench::name()) .add("value_type", primbench::name()) .add("max_segment_length", MaxSegmentLength) .add("cfg", config_name()); } template auto run_device_scan_by_key(void* temporary_storage, size_t& storage_size, const Key* keys, const Value* input, Value* output, const Value initial_value, const size_t input_size, const ScanOp scan_op, const CompareOp compare_op, const hipStream_t stream, const bool debug = false) const { if constexpr(excl) { if constexpr(!Deterministic) { return rocprim::exclusive_scan_by_key(temporary_storage, storage_size, keys, input, output, initial_value, input_size, scan_op, compare_op, stream, debug); } else { return rocprim::deterministic_exclusive_scan_by_key(temporary_storage, storage_size, keys, input, output, initial_value, input_size, scan_op, compare_op, stream, debug); } } else { if constexpr(!Deterministic) { return rocprim::inclusive_scan_by_key(temporary_storage, storage_size, keys, input, output, input_size, scan_op, compare_op, stream, debug); } else { return rocprim::deterministic_inclusive_scan_by_key(temporary_storage, storage_size, keys, input, output, input_size, scan_op, compare_op, stream, debug); } } } 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(Value); constexpr bool debug = false; const std::vector keys = get_random_segments(items, MaxSegmentLength, seeds[0]); const auto random_range = limit_random_range(0, 1000); const std::vector input = get_random_data(items, random_range.first, random_range.second, seeds[1]); ScanOp scan_op{}; CompareOp compare_op{}; Value initial_value = Value(123); common::device_ptr d_input(input); common::device_ptr d_keys(keys); common::device_ptr d_output(input.size()); // Allocate temporary storage memory size_t temp_storage_size_bytes; // Get size of d_temp_storage HIP_CHECK((run_device_scan_by_key(nullptr, temp_storage_size_bytes, d_keys.get(), d_input.get(), d_output.get(), initial_value, items, scan_op, compare_op, stream, debug))); common::device_ptr d_temp_storage(temp_storage_size_bytes); state.set_items(items); state.add_reads(items); state.add_reads(items); state.run( [&] { HIP_CHECK((run_device_scan_by_key(d_temp_storage.get(), temp_storage_size_bytes, d_keys.get(), d_input.get(), d_output.get(), initial_value, items, scan_op, compare_op, stream, debug))); }); } }; #ifdef BENCHMARK_CONFIG_TUNING template struct device_scan_by_key_benchmark_generator { template struct create_block_scan_algorithm { template struct create_block_size { template struct create_ipt { void operator()( std::vector>& storage) { storage.emplace_back(std::make_unique, rocprim::equal_to, 1024, false, rocprim::scan_by_key_config< block_size, ItemsPerThread, rocprim::block_load_method::block_load_transpose, rocprim::block_store_method::block_store_transpose, BlockScanAlgorithm>>>()); } }; void operator()(std::vector>& storage) { // Limit items per thread to not over-use shared memory static constexpr unsigned int max_items_per_thread = ::rocprim::min( 65536 / (block_size * (sizeof(KeyType) + sizeof(ValueType) + (sizeof(KeyType) == 16 && sizeof(ValueType) == 1))), 24); static_for_each, create_ipt>(storage); } static constexpr unsigned int block_size = 1u << BlockSizeExponent; }; static void create(std::vector>& storage) { static_for_each(storage); } }; static void create(std::vector>& storage) { // Block sizes 64, 128, 256 create_block_scan_algorithm>::create(storage); } }; #else // BENCHMARK_CONFIG_TUNING #define CREATE_BY_KEY_BENCHMARK(EXCL, T, SCAN_OP, MAX_SEGMENT_LENGTH) \ executor.queue, \ MAX_SEGMENT_LENGTH, \ Deterministic>>(); #define CREATE_EXCL_INCL_BENCHMARK(EXCL, T, SCAN_OP) \ CREATE_BY_KEY_BENCHMARK(EXCL, T, SCAN_OP, 1) \ CREATE_BY_KEY_BENCHMARK(EXCL, T, SCAN_OP, 16) \ CREATE_BY_KEY_BENCHMARK(EXCL, T, SCAN_OP, 256) \ CREATE_BY_KEY_BENCHMARK(EXCL, T, SCAN_OP, 4096) \ CREATE_BY_KEY_BENCHMARK(EXCL, T, SCAN_OP, 65536) #define CREATE_BENCHMARK(T) \ CREATE_EXCL_INCL_BENCHMARK(false, T, rocprim::plus) \ CREATE_EXCL_INCL_BENCHMARK(true, T, rocprim::plus) #define BENCHMARK_TYPE_TUNING(KEY_TYPE, VALUE_TYPE) \ executor.queue, \ rocprim::equal_to, \ 1024, \ false>>(); // All of the limited tuned types #define BENCHMARK_TYPES_TUNING(KEY_TYPE) \ BENCHMARK_TYPE_TUNING(KEY_TYPE, rocprim::int128_t) \ BENCHMARK_TYPE_TUNING(KEY_TYPE, int64_t) \ BENCHMARK_TYPE_TUNING(KEY_TYPE, int) \ BENCHMARK_TYPE_TUNING(KEY_TYPE, short) \ BENCHMARK_TYPE_TUNING(KEY_TYPE, int8_t) template void add_benchmarks(primbench::executor& executor) { if constexpr(!Deterministic) { // Tuned types BENCHMARK_TYPES_TUNING(rocprim::int128_t) BENCHMARK_TYPES_TUNING(int64_t) BENCHMARK_TYPES_TUNING(int) BENCHMARK_TYPES_TUNING(short) BENCHMARK_TYPES_TUNING(int8_t) BENCHMARK_TYPES_TUNING(double) BENCHMARK_TYPES_TUNING(float) BENCHMARK_TYPES_TUNING(rocprim::half) } #ifndef BENCHMARK_AUTOTUNED_TYPES_ONLY // Not tuned types CREATE_BENCHMARK(float) CREATE_BENCHMARK(double) CREATE_BENCHMARK(float2) CREATE_BENCHMARK(double2) CREATE_BENCHMARK(uint8_t) CREATE_BENCHMARK(rocprim::half) CREATE_BENCHMARK(rocprim::uint128_t) // Not tuned custom types using custom_f32_f32 = common::custom_type; using custom_f64_f64 = common::custom_type; CREATE_BENCHMARK(custom_f32_f32) CREATE_BENCHMARK(custom_f64_f64) #endif } #endif // BENCHMARK_CONFIG_TUNING