Files
project_6/cccl_upstream/cub/test/catch2_test_device_find.cu
EngineX CI 56fd68e7dd [INFRA] Import NVIDIA/CCCL upstream as optimization reference library
CCCL (CUDA C++ Core Libraries) provides:
- CUB: device/block/warp-level GPU primitives (reduce, scan, sort, topk)
- Thrust: high-level parallel algorithms (transform_reduce, sort, scan)
- libcudacxx: CUDA C++ standard library (atomics, barriers, memory)
- cudax: experimental features (memory resources, allocators)
- Tuning policies: per-SM hardware-specific algorithm parameters

Competition optimization vectors mapped to CCCL:
- Output TPS (83% weight): warp_reduce, block_reduce, device_topk
- Input TPS (14% weight): device_scan, block_load, prefetch
- Cache TPS (3% weight): prefix caching strategy patterns
- Memory (0.9 util): pooled/cached/buddy allocators

Source: https://github.com/NVIDIA/cccl (shallow clone, HEAD only)
License: Apache-2.0
2026-07-30 09:35:51 +00:00

382 lines
13 KiB
Plaintext

// SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "insert_nested_NVTX_range_guard.h"
#include <cub/device/device_find.cuh>
#include <thrust/detail/raw_pointer_cast.h>
#include <thrust/sort.h>
#include <thrust/tabulate.h>
#include <cuda/iterator>
#include "catch2_test_device_reduce.cuh"
#include "catch2_test_launch_helper.h"
#include <c2h/catch2_test_helper.h>
// %PARAM% TEST_LAUNCH lid 0:1:2
DECLARE_LAUNCH_WRAPPER(cub::DeviceFind::FindIf, find_if);
DECLARE_LAUNCH_WRAPPER(cub::DeviceFind::LowerBound, lower_bound);
DECLARE_LAUNCH_WRAPPER(cub::DeviceFind::UpperBound, upper_bound);
// List of types to test
using custom_t = c2h::custom_type_t<c2h::equal_comparable_t>;
using value_types =
c2h::type_list<std::int8_t,
std::int16_t,
std::int32_t,
std::int64_t,
#if TEST_INT128()
__int128_t,
#endif // TEST_INT128()
custom_t>;
using offset_types = c2h::type_list<int32_t, int64_t>;
enum class gen_data_t
{
GEN_TYPE_RANDOM, /// Uniform random data generation
GEN_TYPE_CONST /// Constant value as input data
};
template <typename OffsetT, typename InputIt, typename Predicate>
auto compute_find_if_reference(InputIt first, InputIt last, Predicate predicate) -> OffsetT
{
const auto it = std::find_if(first, last, predicate); // not thrust::find_if because it will rely on cub::FindIf
return static_cast<OffsetT>(std::distance(first, it));
}
C2H_TEST("Device find_if works", "[device][find_if]", value_types, offset_types)
{
using input_t = c2h::get<0, TestType>;
using offset_t = c2h::get<1, TestType>;
constexpr offset_t min_items = 1;
constexpr offset_t max_items = 10'000'000; // 10M items for reasonable test time
// TODO(bgruber): test a value larger than UINT32
// Generate the input sizes to test for
const offset_t num_items =
GENERATE_COPY(offset_t{1}, offset_t{100}, offset_t{5324}, max_items, take(5, random(min_items, max_items)));
const gen_data_t data_gen_mode = GENERATE(gen_data_t::GEN_TYPE_RANDOM, gen_data_t::GEN_TYPE_CONST);
const bool value_exists = GENERATE(false, true);
CAPTURE(c2h::type_name<input_t>(), c2h::type_name<offset_t>(), num_items, data_gen_mode, value_exists);
constexpr bool is_custom_t = cuda::std::is_same_v<input_t, custom_t>;
if constexpr (is_custom_t)
{
if (data_gen_mode == gen_data_t::GEN_TYPE_RANDOM && !value_exists)
{
// min/max handling is not implemented for c2h::gen and custom_t, so we cannot pick a value that does not exist
// in the input sequence
return;
}
}
// Generate input data
c2h::device_vector<input_t> in_items(num_items, thrust::default_init);
if (data_gen_mode == gen_data_t::GEN_TYPE_RANDOM)
{
if constexpr (is_custom_t)
{
c2h::gen(C2H_SEED(1), in_items);
}
else
{
// omit the largest value from the random values so we have a value to that does not occur
c2h::gen(C2H_SEED(1), in_items, input_t{0}, static_cast<input_t>(::cuda::std::numeric_limits<input_t>::max() - 1));
}
}
else
{
// fill with 1s
input_t default_constant;
init_default_constant(default_constant, 1);
thrust::fill(c2h::device_policy, in_items.begin(), in_items.end(), default_constant);
}
auto d_in_it = thrust::raw_pointer_cast(in_items.data());
using predice_t = cuda::equal_to_value<input_t>;
input_t val_to_find{};
// Generate test cases for both "found" and "not found" scenarios
if (value_exists)
{
// take a random value from the input sequence
val_to_find = in_items[GENERATE_COPY(take(1, random(offset_t{0}, num_items - 1)))];
}
else
{
// max value is neither in the random input and nor in the constant
val_to_find = ::cuda::std::numeric_limits<input_t>::max();
}
auto predicate = predice_t{val_to_find};
SECTION("Generic find if case")
{
// Prepare verification data
c2h::host_vector<input_t> host_items(in_items);
const auto expected_result = compute_find_if_reference<offset_t>(host_items.begin(), host_items.end(), predicate);
// Run test
c2h::device_vector<offset_t> out_result(1, thrust::no_init);
find_if(d_in_it, thrust::raw_pointer_cast(out_result.data()), predicate, num_items);
REQUIRE(expected_result == out_result[0]);
}
SECTION("find_if works with thrust contiguous iterator")
{
// Prepare verification data
c2h::host_vector<input_t> host_items(in_items);
const auto expected = compute_find_if_reference<offset_t>(host_items.begin(), host_items.end(), predicate);
// Run test
c2h::device_vector<offset_t> out_result(1);
find_if(in_items.begin(), out_result.begin(), predicate, num_items);
REQUIRE(expected == out_result[0]);
}
SECTION("find_if works for unaligned input")
{
for (int offset = 1; offset < 4; ++offset)
{
if (num_items > offset)
{
// Prepare verification data
c2h::host_vector<input_t> host_items(in_items);
const auto expected =
compute_find_if_reference<offset_t>(host_items.begin() + offset, host_items.end(), predicate);
// Run test
c2h::device_vector<offset_t> out_result(1, thrust::no_init);
find_if(d_in_it + offset, thrust::raw_pointer_cast(out_result.data()), predicate, num_items - offset);
REQUIRE(expected == out_result[0]);
}
}
}
}
C2H_TEST("Device find_if works with non primitive iterator", "[device][find_if]")
{
using input_t = int32_t;
using offset_t = int32_t;
constexpr offset_t min_items = 1;
constexpr offset_t max_items = 10000000; // 10M items for reasonable test time
input_t val_to_find = static_cast<input_t>(GENERATE_COPY(take(1, random(min_items, max_items))));
// Generate the input sizes to test for
const offset_t num_items = GENERATE_COPY(
take(1, random(min_items, max_items)),
values({
min_items,
max_items,
}));
CAPTURE(num_items, val_to_find);
const auto expected_if_found = cuda::std::min(static_cast<offset_t>(val_to_find), num_items);
// counting_iterator input
auto c_it = cuda::make_counting_iterator(input_t{0});
{
c2h::device_vector<offset_t> out_result(1, thrust::no_init);
auto predicate = cuda::equal_to_value<input_t>{val_to_find};
find_if(c_it, thrust::raw_pointer_cast(out_result.data()), predicate, num_items);
REQUIRE(expected_if_found == out_result[0]);
}
{ // transform_iterator of counting_iterator input and thrust device_ptr output
auto t_it = cuda::make_transform_iterator(c_it, ::cuda::std::negate{});
c2h::device_vector<offset_t> out_result(1, thrust::no_init);
auto predicate = cuda::equal_to_value<input_t>{-val_to_find};
find_if(t_it, out_result.data(), predicate, num_items);
REQUIRE(expected_if_found == out_result[0]);
}
{ // counting_iterator input and transform_output_iterator output
c2h::device_vector<offset_t> out_result(1, thrust::no_init);
auto predicate = cuda::equal_to_value<input_t>{val_to_find};
auto out_it = cuda::make_transform_output_iterator(out_result.begin(), ::cuda::std::negate{});
find_if(c_it, out_it, predicate, num_items);
REQUIRE(-expected_if_found == out_result[0]);
}
}
struct NotDefaultConstructible
{
int value_;
__host__ __device__ constexpr explicit NotDefaultConstructible(int value)
: value_(value)
{}
__host__ __device__ friend constexpr bool
operator==(const NotDefaultConstructible& lhs, const NotDefaultConstructible& rhs)
{
return lhs.value_ == rhs.value_;
}
__host__ __device__ friend constexpr bool
operator!=(const NotDefaultConstructible& lhs, const NotDefaultConstructible& rhs)
{
return lhs.value_ != rhs.value_;
}
__host__ __device__ constexpr operator int() const noexcept
{
return value_;
}
};
struct index_to_value
{
__host__ __device__ NotDefaultConstructible operator()(int i)
{
return NotDefaultConstructible{static_cast<int>(i)};
}
};
static_assert(!cuda::std::is_default_constructible_v<NotDefaultConstructible>,
"NotDefaultConstructible should not be default constructible");
C2H_TEST("Device find_if works with non default constructible types", "[device][find_if]")
{
using input_t = NotDefaultConstructible;
using offset_t = int;
constexpr offset_t min_items = 1;
constexpr offset_t max_items = 10'000; // 10k items for reasonable test time
// Generate the input sizes to test for
const offset_t num_items = GENERATE_COPY(
take(1, random(min_items, max_items)),
values({
min_items,
max_items,
}));
const auto val_to_find = static_cast<int>(num_items - 1);
CAPTURE(num_items, val_to_find);
// raw device iterator to some device vector so that vectorized path is taken
c2h::device_vector<input_t> d_vec(num_items, NotDefaultConstructible(0));
// fill with arbitrary values dont use c2h gen because NotDefaultConstructible is not default constructible
thrust::tabulate(c2h::device_policy, d_vec.begin(), d_vec.end(), index_to_value{});
auto it = thrust::raw_pointer_cast(d_vec.data());
c2h::device_vector<offset_t> out_result(1, thrust::no_init);
auto predicate = cuda::equal_to_value<NotDefaultConstructible>{NotDefaultConstructible{val_to_find}};
find_if(it, thrust::raw_pointer_cast(out_result.data()), predicate, num_items);
REQUIRE(val_to_find == out_result[0]);
}
// LowerBound / UpperBound tests (formerly in catch2_test_device_binary_search.cu)
using binary_search_types = c2h::type_list<std::uint8_t, std::int16_t, std::uint32_t, double>;
struct std_lower_bound_t
{
template <typename RangeIteratorT, typename T, typename CompareOpT>
RangeIteratorT operator()(RangeIteratorT first, RangeIteratorT last, const T& value, CompareOpT comp) const
{
return std::lower_bound(first, last, value, comp);
}
} std_lower_bound;
struct std_upper_bound_t
{
template <typename RangeIteratorT, typename T, typename CompareOpT>
RangeIteratorT operator()(RangeIteratorT first, RangeIteratorT last, const T& value, CompareOpT comp) const
{
return std::upper_bound(first, last, value, comp);
}
} std_upper_bound;
template <typename Value, typename Variant, typename HostVariant, typename CompareOp = cuda::std::less<Value>>
void test_vectorized(Variant variant, HostVariant host_variant, std::size_t num_items = 7492, CompareOp compare_op = {})
{
c2h::device_vector<Value> target_values_d(num_items / 100, thrust::default_init);
c2h::gen(C2H_SEED(1), target_values_d);
c2h::device_vector<Value> values_d(num_items + target_values_d.size(), thrust::default_init);
c2h::gen(C2H_SEED(1), values_d);
thrust::copy(c2h::device_policy, target_values_d.begin(), target_values_d.end(), values_d.begin());
thrust::sort(c2h::device_policy, values_d.begin(), values_d.end(), compare_op);
using Result = std::ptrdiff_t;
c2h::device_vector<Result> offsets_d(target_values_d.size(), thrust::default_init);
variant(thrust::raw_pointer_cast(values_d.data()),
num_items,
thrust::raw_pointer_cast(target_values_d.data()),
target_values_d.size(),
thrust::raw_pointer_cast(offsets_d.data()),
compare_op);
c2h::host_vector<Value> target_values_h = target_values_d;
c2h::host_vector<Value> values_h = values_d;
c2h::host_vector<Result> offsets_h = offsets_d;
c2h::host_vector<std::ptrdiff_t> offsets_ref(offsets_h.size(), thrust::default_init);
for (auto i = 0u; i < target_values_h.size(); ++i)
{
offsets_ref[i] =
host_variant(values_h.data(), values_h.data() + num_items, target_values_h[i], compare_op) - values_h.data();
}
CHECK(offsets_ref == offsets_h);
}
C2H_TEST("DeviceFind::LowerBound works", "[find][device][binary-search]", binary_search_types)
{
using value_type = c2h::get<0, TestType>;
test_vectorized<value_type>(lower_bound, std_lower_bound);
}
C2H_TEST("DeviceFind::UpperBound works", "[find][device][binary-search]", binary_search_types)
{
using value_type = c2h::get<0, TestType>;
test_vectorized<value_type>(upper_bound, std_upper_bound);
}
// this test exceeds 4GiB of memory and the range of 32-bit integers
C2H_TEST("DeviceFind::LowerBound really large input",
"[find][device][binary-search][skip-cs-rangecheck][skip-cs-initcheck][skip-cs-synccheck]")
{
try
{
using value_type = char;
const auto size = std::int64_t{1} << GENERATE(30, 31, 32, 33);
test_vectorized<value_type>(lower_bound, std_lower_bound, size);
}
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");
}
}
// this test exceeds 4GiB of memory and the range of 32-bit integers
C2H_TEST("DeviceFind::UpperBound really large input",
"[find][device][binary-search][skip-cs-rangecheck][skip-cs-initcheck][skip-cs-synccheck]")
{
try
{
using value_type = char;
const auto size = std::int64_t{1} << GENERATE(30, 31, 32, 33);
test_vectorized<value_type>(upper_bound, std_upper_bound, size);
}
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");
}
}