// SPDX-FileCopyrightText: Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "insert_nested_NVTX_range_guard.h" #include #include #include #include #include #include #include "catch2_test_launch_helper.h" #include // %PARAM% TEST_LAUNCH lid 0:1:2 DECLARE_LAUNCH_WRAPPER(cub::DeviceMerge::MergePairs, merge_pairs); DECLARE_LAUNCH_WRAPPER(cub::DeviceMerge::MergeKeys, merge_keys); using types = c2h::type_list; template , typename MergeKeys = decltype(::merge_keys)> void test_keys(Offset size1 = 3623, Offset size2 = 6346, CompareOp compare_op = {}, MergeKeys merge_keys = ::merge_keys) { CAPTURE(c2h::type_name(), c2h::type_name(), size1, size2); c2h::device_vector keys1_d(size1, thrust::default_init); c2h::device_vector keys2_d(size2, thrust::default_init); c2h::gen(C2H_SEED(1), keys1_d); c2h::gen(C2H_SEED(1), keys2_d); thrust::sort(c2h::device_policy, keys1_d.begin(), keys1_d.end(), compare_op); thrust::sort(c2h::device_policy, keys2_d.begin(), keys2_d.end(), compare_op); // CAPTURE(keys1_d, keys2_d); c2h::device_vector result_d(size1 + size2, thrust::default_init); merge_keys(thrust::raw_pointer_cast(keys1_d.data()), static_cast(keys1_d.size()), thrust::raw_pointer_cast(keys2_d.data()), static_cast(keys2_d.size()), thrust::raw_pointer_cast(result_d.data()), compare_op); c2h::host_vector keys1_h = keys1_d; c2h::host_vector keys2_h = keys2_d; c2h::host_vector reference_h(size1 + size2, thrust::default_init); std::merge(keys1_h.begin(), keys1_h.end(), keys2_h.begin(), keys2_h.end(), reference_h.begin(), compare_op); // comparing std::vectors instead compiles in 1m19s, thrust::host_vector 1m23s, thrust::device_vector 1m38 // let's pick the host_vector, so we don't stress device memory with another (potentially big) allocation c2h::host_vector result_h(result_d); // perform copy outside CHECK() to propagate a potential bad_alloc CHECK(reference_h == result_h); } C2H_TEST("DeviceMerge::MergeKeys key types", "[merge][device]", types) { using key_t = c2h::get<0, TestType>; using offset_t = int; test_keys(); } C2H_TEST("DeviceMerge::MergeKeys works for large number of items", "[merge][device][skip-cs-racecheck][skip-cs-initcheck][skip-cs-synccheck]") try { using key_t = char; using offset_t = int64_t; // Clamp 64-bit offset type problem sizes to just slightly larger than 2^32 items const auto num_items_int_max = static_cast(cuda::std::numeric_limits::max()); // Generate the input sizes to test for const offset_t num_items_lhs = GENERATE_COPY(values({num_items_int_max + offset_t{1000000}, num_items_int_max - 1, offset_t{3}})); const offset_t num_items_rhs = GENERATE_COPY(values({num_items_int_max + offset_t{1000000}, num_items_int_max, offset_t{3}})); test_keys(num_items_lhs, num_items_rhs, cuda::std::less<>{}); } catch (const std::bad_alloc&) { // allocation failure is not a test failure, so we can run tests on smaller GPUs SUCCEED("allocation failure is not a test failure"); } C2H_TEST("DeviceMerge::MergeKeys input sizes", "[merge][device]") { using key_t = int; using offset_t = int; // TODO(bgruber): maybe less combinations const auto size1 = offset_t{GENERATE(0, 1, 23, 123, 3234)}; const auto size2 = offset_t{GENERATE(0, 1, 52, 556, 56767)}; test_keys(size1, size2); } C2H_TEST("DeviceMerge::MergeKeys almost tile-sized input sizes", "[merge][device]") { using key_t = int; using offset_t = int; cuda::compute_capability cc{}; REQUIRE(cub::detail::ptx_compute_cap(cc) == cudaSuccess); const offset_t items_per_tile = cub::detail::merge::policy_selector_from_types{}(cc) .items_per_thread; test_keys(items_per_tile - 1, 1); test_keys(items_per_tile, 1); test_keys(1, items_per_tile - 1); test_keys(1, items_per_tile); } // cannot put those in an anon namespace, or nvcc complains that the kernels have internal linkage using unordered_t = c2h::custom_type_t; struct order { __host__ __device__ auto operator()(const unordered_t& a, const unordered_t& b) const -> bool { return a.key < b.key; } }; C2H_TEST("DeviceMerge::MergeKeys no operator<", "[merge][device]") { using key_t = unordered_t; using offset_t = int; test_keys(); } namespace { // must use thrust::make_zip_iterator for now // see https://github.com/NVIDIA/cccl/issues/6400 template auto zip(Its... its) -> decltype(thrust::make_zip_iterator(its...)) { return thrust::make_zip_iterator(its...); } template struct key_to_value { template __host__ __device__ auto operator()(const Key& k) const -> Value { Value v{}; convert(k, v, 0); return v; } template __host__ __device__ static void convert(const Key& k, Value& v, ...) { v = static_cast(k); } template