Files
project_6/cccl_upstream/cub/test/catch2_test_device_reduce.cuh
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

539 lines
18 KiB
Plaintext

// SPDX-FileCopyrightText: Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
#pragma once
#include <cub/thread/thread_operators.cuh>
#include <cub/util_namespace.cuh>
#include <cub/util_type.cuh>
#include <cuda/__functional/maximum.h>
#include <cuda/__functional/minimum.h>
#include <cuda/iterator>
#include <nv/target>
#include <iostream>
#include <numeric>
#include <type_traits>
#include <c2h/catch2_test_helper.h>
#include <c2h/custom_type.h>
#include <c2h/extended_types.h>
#include <c2h/test_util_vec.h>
#if TEST_HALF_T()
// Half support is provided by SM53+. We currently test against a few older architectures.
// The specializations below can be removed once we drop these architectures.
template <>
_CCCL_HOST_DEVICE_API inline __half
cuda::minimum<void>::operator()<__half, __half>(const __half& a, const __half& b) const
{
# if defined(__CUDA_NO_HALF_OPERATORS__)
return ::cuda::std::min(__half2float(a), __half2float(b));
# else // ^^^ __CUDA_NO_HALF_OPERATORS__ ^^^ / vvv !__CUDA_NO_HALF_OPERATORS__ vvv
NV_IF_ELSE_TARGET(
NV_PROVIDES_SM_53, (return ::cuda::std::min(a, b);), (return ::cuda::std::min(__half2float(a), __half2float(b));));
# endif // !__CUDA_NO_HALF_OPERATORS__
}
template <>
_CCCL_HOST_DEVICE_API inline __half
cuda::maximum<void>::operator()<__half, __half>(const __half& a, const __half& b) const
{
# if defined(__CUDA_NO_HALF_OPERATORS__)
return ::cuda::std::max(__half2float(a), __half2float(b));
# else // ^^^ __CUDA_NO_HALF_OPERATORS__ ^^^ / vvv !__CUDA_NO_HALF_OPERATORS__ vvv
NV_IF_ELSE_TARGET(
NV_PROVIDES_SM_53, (return ::cuda::std::max(a, b);), (return ::cuda::std::max(__half2float(a), __half2float(b));));
# endif // !__CUDA_NO_HALF_OPERATORS__
}
CUB_NAMESPACE_BEGIN
template <>
__host__ __device__ __forceinline__ //
KeyValuePair<int, __half>
ArgMin::operator()(const KeyValuePair<int, __half>& a, const KeyValuePair<int, __half>& b) const
{
const float av = __half2float(a.value);
const float bv = __half2float(b.value);
if ((bv < av) || ((av == bv) && (b.key < a.key)))
{
return b;
}
return a;
}
template <>
__host__ __device__ __forceinline__ //
KeyValuePair<int, __half>
ArgMax::operator()(const KeyValuePair<int, __half>& a, const KeyValuePair<int, __half>& b) const
{
const float av = __half2float(a.value);
const float bv = __half2float(b.value);
if ((bv > av) || ((av == bv) && (b.key < a.key)))
{
return b;
}
return a;
}
CUB_NAMESPACE_END
#endif // TEST_HALF_T()
// Comparing results computed on CPU and GPU for extended floating point types is impossible.
// For instance, when used with a constant iterator of two, the accumulator in sequential reference
// computation (CPU) bumps into the 4096 limits, which will never change (`4096 + 2 = 4096`).
// Meanwhile, per-thread aggregates (`2 * 16 = 32`) are accumulated within and among thread blocks,
// yielding `inf` as a result. No reasonable epsilon can be selected to compare `inf` with `4096`.
// To make `__half` and `__nv_bfloat16` arithmetic associative, the function object below raises
// extended floating points to the area of unsigned short integers. This allows us to test large
// inputs with few code-path differences in device algorithms.
struct ExtendedFloatSum
{
template <class T>
__host__ __device__ T operator()(T a, T b) const
{
T result{};
result.__x = a.raw() + b.raw();
return result;
}
#if TEST_HALF_T()
__host__ __device__ __half operator()(__half a, __half b) const
{
uint16_t result = this->operator()(half_t{a}, half_t(b)).raw();
return reinterpret_cast<__half&>(result);
}
#endif // TEST_HALF_T()
#if TEST_BF_T()
__device__ __nv_bfloat16 operator()(__nv_bfloat16 a, __nv_bfloat16 b) const
{
uint16_t result = this->operator()(bfloat16_t{a}, bfloat16_t(b)).raw();
return reinterpret_cast<__nv_bfloat16&>(result);
}
#endif // TEST_BF_T()
};
template <class It>
inline It unwrap_it(It it)
{
return it;
}
#if TEST_HALF_T()
inline __half* unwrap_it(half_t* it)
{
return reinterpret_cast<__half*>(it);
}
template <class OffsetT>
inline cuda::constant_iterator<__half, OffsetT> unwrap_it(cuda::constant_iterator<half_t, OffsetT> it)
{
half_t wrapped_val = *it;
__half val = wrapped_val.operator __half();
return cuda::constant_iterator<__half, OffsetT>(val);
}
#endif // TEST_HALF_T()
#if TEST_BF_T()
inline __nv_bfloat16* unwrap_it(bfloat16_t* it)
{
return reinterpret_cast<__nv_bfloat16*>(it);
}
template <class OffsetT>
cuda::constant_iterator<__nv_bfloat16, OffsetT> inline unwrap_it(cuda::constant_iterator<bfloat16_t, OffsetT> it)
{
bfloat16_t wrapped_val = *it;
__nv_bfloat16 val = wrapped_val.operator __nv_bfloat16();
return cuda::constant_iterator<__nv_bfloat16, OffsetT>(val);
}
#endif // TEST_BF_T()
template <typename T>
using unwrap_value_t = std::remove_reference_t<decltype(*unwrap_it(std::declval<T*>()))>;
template <class WrappedItT, //
class ItT = decltype(unwrap_it(std::declval<WrappedItT>()))>
std::integral_constant<bool, !std::is_same_v<WrappedItT, ItT>> //
inline reference_extended_fp(WrappedItT)
{
return {};
}
inline constexpr ExtendedFloatSum unwrap_op(std::true_type /* extended float */, ::cuda::std::plus<>) //
{
return {};
}
template <bool V, class OpT>
inline constexpr OpT unwrap_op(std::integral_constant<bool, V> /* base case */, OpT op)
{
return op;
}
/**
* @brief Initializes the given item type with a constant non-zero value.
*/
template <typename T>
inline void init_default_constant(T& val, int element_val = 2)
{
val = T{static_cast<T>(element_val)};
}
template <template <typename> class... Policies>
inline void init_default_constant(c2h::custom_type_t<Policies...>& val, int element_val = 2)
{
val.key = static_cast<size_t>(element_val);
val.val = static_cast<size_t>(element_val);
}
inline void init_default_constant(uchar3& val, int element_val = 2)
{
const auto element_init = static_cast<unsigned char>(element_val);
val = uchar3{element_init, element_init, element_init};
}
_CCCL_SUPPRESS_DEPRECATED_PUSH
_CCCL_SUPPRESS_DEPRECATED_NVRTC_DIAG
inline void init_default_constant(ulonglong4& val, int element_val = 2)
{
const auto element_init = static_cast<unsigned long long>(element_val);
val = ulonglong4{element_init, element_init, element_init, element_init};
}
_CCCL_SUPPRESS_DEPRECATED_POP
#if _CCCL_CTK_AT_LEAST(13, 0)
inline void init_default_constant(ulonglong4_16a& val, int element_val = 2)
{
const auto element_init = static_cast<unsigned long long>(element_val);
val = ulonglong4_16a{element_init, element_init, element_init, element_init};
}
#endif // _CCCL_CTK_AT_LEAST(13, 0)
template <typename InputItT,
typename OffsetItT,
typename SizeItT,
typename ReductionOpT,
typename InitValueT,
typename ResultOutItT>
inline void compute_host_reference(
InputItT h_in,
OffsetItT h_offsets,
SizeItT h_sizes_begin,
std::size_t num_segments,
ReductionOpT reduction_op,
InitValueT init,
ResultOutItT h_data_out)
{
for (std::size_t segment = 0; segment < num_segments; segment++)
{
auto seg_begin = h_in + h_offsets[segment];
auto seg_end = seg_begin + h_sizes_begin[segment];
// TODO Should this be using cub accumulator t?
h_data_out[segment] =
static_cast<cub::detail::it_value_t<ResultOutItT>>(std::accumulate(seg_begin, seg_end, init, reduction_op));
}
}
/**
* @brief Helper function to compute the reference solution for result verification taking an
* arbitrary host-accessible input iterator.
*/
template <typename InputItT, typename ReductionOpT, typename AccumulatorT>
inline AccumulatorT
compute_single_problem_reference(InputItT h_in_begin, InputItT h_in_end, ReductionOpT reduction_op, AccumulatorT init)
{
constexpr std::size_t num_segments = 1;
c2h::host_vector<AccumulatorT> h_results(num_segments);
compute_host_reference(
h_in_begin,
cuda::constant_iterator(0),
cuda::constant_iterator(cuda::std::distance(h_in_begin, h_in_end)),
num_segments,
reduction_op,
init,
h_results.begin());
return *h_results.begin();
}
/**
* @brief Helper function to compute the reference solution for result verification, taking a
* c2h::device_vector.
*/
template <typename ItemT, typename ReductionOpT, typename AccumulatorT>
inline AccumulatorT
compute_single_problem_reference(const c2h::device_vector<ItemT>& d_in, ReductionOpT reduction_op, AccumulatorT init)
{
constexpr std::size_t num_segments = 1;
c2h::host_vector<ItemT> h_items(d_in);
c2h::host_vector<AccumulatorT> h_results(num_segments);
return compute_single_problem_reference(h_items.cbegin(), h_items.cend(), reduction_op, init);
}
/**
* @brief Helper function to compute the reference solution for result verification, taking a
* c2h::device_vector of input items and a c2h::device_vector of offsets into the segments.
*/
template <typename ItemT, typename OffsetT, typename ReductionOpT, typename AccumulatorT, typename ResultItT>
void compute_segmented_problem_reference(
const c2h::device_vector<ItemT>& d_in,
const c2h::device_vector<OffsetT>& d_offsets,
ReductionOpT reduction_op,
AccumulatorT init,
ResultItT h_results)
{
c2h::host_vector<ItemT> h_items(d_in);
c2h::host_vector<OffsetT> h_offsets(d_offsets);
auto offsets_it = h_offsets.cbegin();
auto seg_sizes_it = cuda::transform_iterator(cuda::counting_iterator(std::size_t{0}), [offsets_it](std::size_t i) {
return offsets_it[i + 1] - offsets_it[i];
});
std::size_t num_segments = h_offsets.size() - 1;
compute_host_reference(
h_items.cbegin(), h_offsets.cbegin(), seg_sizes_it, num_segments, reduction_op, init, h_results);
}
/**
* @brief Helper function to compute the reference solution for result verification, taking a
* host-accessible input iterator and a c2h::device_vector of offsets into the segments.
*/
template <typename InputItT, typename OffsetT, typename ReductionOpT, typename AccumulatorT, typename ResultItT>
void compute_segmented_problem_reference(
InputItT in_it,
const c2h::device_vector<OffsetT>& d_offsets,
ReductionOpT reduction_op,
AccumulatorT init,
ResultItT h_results)
{
c2h::host_vector<OffsetT> h_offsets(d_offsets);
auto offsets_it = h_offsets.cbegin();
auto seg_sizes_it = cuda::transform_iterator(cuda::counting_iterator(std::size_t{0}), [offsets_it](std::size_t i) {
return offsets_it[i + 1] - offsets_it[i];
});
std::size_t num_segments = h_offsets.size() - 1;
compute_host_reference(in_it, h_offsets.cbegin(), seg_sizes_it, num_segments, reduction_op, init, h_results);
}
/**
* @brief Helper function to compute the reference solution for result verification, taking a
* c2h::device_vector of input items and a c2h::device_vector of offsets into the segments.
*/
template <typename ItemT, typename OffsetT, typename ResultItT>
void compute_segmented_argmin_reference(
const c2h::device_vector<ItemT>& d_in, const c2h::device_vector<OffsetT>& d_offsets, ResultItT h_results)
{
c2h::host_vector<ItemT> h_items(d_in);
c2h::host_vector<OffsetT> h_offsets(d_offsets);
const auto num_segments = h_offsets.size() - 1;
for (std::size_t seg = 0; seg < num_segments; seg++)
{
if (h_offsets[seg] >= h_offsets[seg + 1])
{
h_results[seg] = {1, ::cuda::std::numeric_limits<ItemT>::max()};
}
else
{
auto expected_result_it =
std::min_element(h_items.cbegin() + h_offsets[seg], h_items.cbegin() + h_offsets[seg + 1]);
int result_offset =
static_cast<int>(cuda::std::distance((h_items.cbegin() + h_offsets[seg]), expected_result_it));
h_results[seg] = {result_offset, *expected_result_it};
}
}
}
/**
* @brief Helper function to compute the reference solution for result verification, taking a
* c2h::device_vector of input items and a c2h::device_vector of offsets into the segments.
*/
template <typename ItemT, typename OffsetT, typename ResultItT>
void compute_segmented_argmax_reference(
const c2h::device_vector<ItemT>& d_in, const c2h::device_vector<OffsetT>& d_offsets, ResultItT h_results)
{
c2h::host_vector<ItemT> h_items(d_in);
c2h::host_vector<OffsetT> h_offsets(d_offsets);
const auto num_segments = h_offsets.size() - 1;
for (std::size_t seg = 0; seg < num_segments; seg++)
{
if (h_offsets[seg] >= h_offsets[seg + 1])
{
h_results[seg] = {1, ::cuda::std::numeric_limits<ItemT>::lowest()};
}
else
{
auto expected_result_it =
std::max_element(h_items.cbegin() + h_offsets[seg], h_items.cbegin() + h_offsets[seg + 1]);
int result_offset =
static_cast<int>(cuda::std::distance((h_items.cbegin() + h_offsets[seg]), expected_result_it));
h_results[seg] = {result_offset, *expected_result_it};
}
}
}
/**
* @brief Helper function to compute the reference solution for result verification, taking a
* c2h::device_vector of input items, num_segments and segment_size.
*/
template <typename ItemT, typename ReductionOpT, typename AccumulatorT, typename ResultItT>
void compute_fixed_size_segmented_problem_reference(
const c2h::device_vector<ItemT>& d_in,
const int num_segments,
const int segment_size,
ReductionOpT reduction_op,
AccumulatorT init,
ResultItT h_results)
{
c2h::host_vector<ItemT> h_items(d_in);
auto h_begin = h_items.cbegin();
for (int segment = 0; segment < num_segments; segment++)
{
auto seg_begin = h_begin + static_cast<long>(segment) * segment_size;
auto seg_end = seg_begin + segment_size;
h_results[segment] =
static_cast<cub::detail::it_value_t<ResultItT>>(std::accumulate(seg_begin, seg_end, init, reduction_op));
}
}
/**
* @brief Helper function to compute the reference solution for result verification, taking a
* c2h::device_vector of input items, num_segments and segment_size.
*/
template <typename ItemT, typename ResultItT>
void compute_fixed_size_segmented_argmax_reference(
const c2h::device_vector<ItemT>& d_in, const int num_segments, const int segment_size, ResultItT h_results)
{
c2h::host_vector<ItemT> h_items(d_in);
auto h_begin = h_items.begin();
for (int seg = 0; seg < num_segments; seg++)
{
if (segment_size == 0)
{
h_results[seg] = {1, ::cuda::std::numeric_limits<ItemT>::lowest()};
}
else
{
auto seg_begin = h_begin + static_cast<long>(seg) * segment_size;
auto seg_end = seg_begin + segment_size;
auto expected_result_it = std::max_element(seg_begin, seg_end);
int result_offset = static_cast<int>(::cuda::std::distance((seg_begin), expected_result_it));
h_results[seg] = {result_offset, *expected_result_it};
}
}
}
/**
* @brief Helper function to compute the reference solution for result verification, taking a
* c2h::device_vector of input items, num_segments and segment_size.
*/
template <typename ItemT, typename ResultItT>
void compute_fixed_size_segmented_argmin_reference(
const c2h::device_vector<ItemT>& d_in, const int num_segments, const int segment_size, ResultItT h_results)
{
c2h::host_vector<ItemT> h_items(d_in);
auto h_begin = h_items.begin();
for (int seg = 0; seg < num_segments; seg++)
{
if (segment_size == 0)
{
h_results[seg] = {1, ::cuda::std::numeric_limits<ItemT>::lowest()};
}
else
{
auto seg_begin = h_begin + static_cast<long>(seg) * segment_size;
auto seg_end = seg_begin + segment_size;
auto expected_result_it = std::min_element(seg_begin, seg_end);
int result_offset = static_cast<int>(::cuda::std::distance((seg_begin), expected_result_it));
h_results[seg] = {result_offset, *expected_result_it};
}
}
}
/**
* @brief Helper function to compute the reference solution for unique keys (i.e., collapsing each
* run of equal keys into a single key).
*/
template <typename InputItT, typename OutputItT>
inline OutputItT compute_unique_keys_reference(InputItT h_in_begin, std::size_t num_keys, OutputItT h_out_it)
{
if (num_keys == 0)
{
return h_out_it;
}
*h_out_it++ = h_in_begin[0];
for (std::size_t i = 1; i < num_keys; i++)
{
if (!(h_in_begin[i - 1] == h_in_begin[i]))
{
*h_out_it = h_in_begin[i];
h_out_it++;
}
}
return h_out_it;
}
/**
* @brief Helper function to compute the reference solution for unique keys (i.e., collapsing each
* run of equal keys into a single key).
*/
template <typename ItemT>
inline c2h::host_vector<ItemT> compute_unique_keys_reference(const c2h::device_vector<ItemT>& d_keys)
{
c2h::host_vector<ItemT> h_keys(d_keys);
c2h::host_vector<ItemT> h_unique_keys_out(d_keys.size());
auto end_it = compute_unique_keys_reference(h_keys.cbegin(), h_keys.size(), h_unique_keys_out.begin());
h_unique_keys_out.resize(cuda::std::distance(h_unique_keys_out.begin(), end_it));
return h_unique_keys_out;
}
/**
* @brief Helper class template to facilitate specifying input/output type pairs along with the key
* type for reduce-by-key algorithms.
*/
template <typename InputT, typename OutputT = InputT, typename KeyT = std::int32_t>
struct type_triple
{
using input_t = InputT;
using output_t = OutputT;
using key_t = KeyT;
};
/**
* @brief Helper class template to facilitate specifying input/output type pairs.
*/
template <typename InputT, typename OutputT = InputT>
struct type_pair
{
using input_t = InputT;
using output_t = OutputT;
};
/**
* @brief Helper class template to facilitate accessing types specified by type-parameterized tests.
*/
template <typename TestType>
struct params_t
{
using type_pair_t = typename c2h::get<0, TestType>;
using item_t = typename type_pair_t::input_t;
using output_t = typename type_pair_t::output_t;
};