// MIT License // // Copyright (c) 2017-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. // required test headers #include "../common_test_header.hpp" // required rocprim headers #include "../../common/utils_device_ptr.hpp" #include "../rocprim/test_seed.hpp" #include "../rocprim/test_utils.hpp" #include #include // required STL headers #include template void generate_needles(const std::vector& input, std::vector& output, const size_t search_needle_size, const std::pair& bounds, const seed_type& seed_value) { // Pick 50% of the needles from the input vector std::vector indices = test_utils::get_random_data(search_needle_size / 2, 0, input.size() - 1, seed_value); // Do selection on the in-bounds indices and write the results to the output vector std::transform(indices.begin(), indices.end(), output.begin(), [&input](const KeyType& index) { return input[index]; }); // Generate the other 50% from outside the input vector const KeyType max_val = std::get<1>(bounds); std::vector out_of_bounds_vals = test_utils::get_random_data(search_needle_size - search_needle_size / 2, max_val, max_val * 2, seed_value); // Append the out-of-bounds values for (size_t i = 0; i < out_of_bounds_vals.size(); i++) output[indices.size() + i] = out_of_bounds_vals[i]; // Mix up the in-bounds and out-of-bounds values to make the test a bit more robust std::random_device rd; std::default_random_engine gen(rd()); std::shuffle(output.begin(), output.end(), gen); } template void computeExpectedSortAndSearchResult(std::vector& sort_input, const std::vector& search_needles, std::vector& expected_search_output, BinaryFunction compare_op) { // Sort std::stable_sort(sort_input.begin(), sort_input.end(), compare_op); // Search for (size_t i = 0; i < search_needles.size(); i++) expected_search_output[i] = std::binary_search(sort_input.begin(), sort_input.end(), search_needles[i], compare_op); } // This test creates a graph that performs a device-wide merge_sort followed by a device-wide binary_search. // After the graph is created, it is launched multiple times, each time using different data. TEST(TestHipGraphAlgs, SortAndSearch) { // Test case params using key_type = int; using compare_fcn_type = typename ::rocprim::less; compare_fcn_type compare_op; const size_t sort_data_size = 4096; const size_t search_needle_size = 100; const size_t num_trials = 5; std::pair bounds = std::make_pair(-10000, 10000); // generated data will fall in this range const bool debug_synchronous = false; // Set the device int device_id = test_common_utils::obtain_device_from_ctest(); SCOPED_TRACE(testing::Message() << "with device_id = " << device_id); HIP_CHECK(hipSetDevice(device_id)); // Generate data on the host const seed_type seed_value = seeds[random_seeds_count - 1]; SCOPED_TRACE(testing::Message() << "with seed = " << seed_value); SCOPED_TRACE(testing::Message() << "with sort_data_size = " << sort_data_size); SCOPED_TRACE(testing::Message() << "with search_needle_size = " << search_needle_size); // Allocate device buffers and copy data into them common::device_ptr d_sort_input(sort_data_size); common::device_ptr d_sort_output(sort_data_size); // also used as search_input common::device_ptr d_search_output(search_needle_size); common::device_ptr d_search_needles(search_needle_size); // Default stream does not support hipGraph stream capture, so create a non-blocking one hipStream_t stream = 0; HIP_CHECK(hipStreamCreateWithFlags(&stream, hipStreamNonBlocking)); // Get temporary storage size required for merge_sort. // Note: doing this inside a graph doesn't gain us any benefit, // since these calls run entirely on the host - however, it is // important to validate that they work inside a graph capture block. size_t sort_temp_storage_bytes = 0; HIP_CHECK(rocprim::merge_sort(nullptr, sort_temp_storage_bytes, d_sort_input.get(), d_sort_output.get(), sort_data_size, compare_op, stream, debug_synchronous)); // Get size of temporary storage required for binary_search size_t search_temp_storage_bytes = 0; HIP_CHECK(rocprim::binary_search(nullptr, search_temp_storage_bytes, d_sort_output.get(), d_search_needles.get(), d_search_output.get(), sort_data_size, search_needle_size, compare_op, stream, debug_synchronous)); // Allocate the temp storage // Note: a single store will be used for both the sort and search algorithms size_t temp_storage_bytes = std::max(sort_temp_storage_bytes, search_temp_storage_bytes); // temp_storage_size_bytes must be > 0 ASSERT_GT(temp_storage_bytes, 0); common::device_ptr d_temp_storage(temp_storage_bytes); // Begin graph capture test_utils::GraphHelper gHelper; gHelper.startStreamCapture(stream); // Launch merge_sort HIP_CHECK(rocprim::merge_sort(d_temp_storage.get(), sort_temp_storage_bytes, d_sort_input.get(), d_sort_output.get(), sort_data_size, compare_op, stream, false)); // Launch binary_search HIP_CHECK(rocprim::binary_search(d_temp_storage.get(), search_temp_storage_bytes, d_sort_output.get(), d_search_needles.get(), d_search_output.get(), sort_data_size, search_needle_size, compare_op, stream, false);); // End graph capture, but do not execute the graph yet. gHelper.endStreamCapture(stream); gHelper.createGraph(); std::vector sort_input; std::vector search_needles(search_needle_size); std::vector expected_search_output(search_needle_size); std::vector device_output(search_needle_size); // We'll launch the graph multiple times with different data. for (size_t i = 0; i < num_trials; i++) { // Generate the test data sort_input = test_utils::get_random_data(sort_data_size, std::get<0>(bounds), std::get<1>(bounds), seed_value); generate_needles(sort_input, search_needles, search_needle_size, bounds, seed_value); // Compute the expected result on the host computeExpectedSortAndSearchResult(sort_input, search_needles, expected_search_output, compare_op); // Copy input data to the device d_sort_input.store(sort_input); d_search_needles.store(search_needles); // Launch the graph gHelper.launchGraph(stream, true); // Copy output back to host device_output = d_search_output.load(); // Validate the results ASSERT_NO_FATAL_FAILURE(test_utils::assert_eq(device_output, expected_search_output)); } // Clean up gHelper.cleanupGraphHelper(); HIP_CHECK(hipStreamDestroy(stream)); }