[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
This commit is contained in:
EngineX CI
2026-07-30 09:35:51 +00:00
parent b4d01f481e
commit 56fd68e7dd
8871 changed files with 1454674 additions and 0 deletions

View File

@@ -0,0 +1,170 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <cub/detail/choose_offset.cuh>
#include <cub/device/device_scan.cuh>
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>
#include <cuda/std/cmath>
#include <look_back_helper.cuh>
#include <nvbench_helper.cuh>
// %RANGE% TUNE_ITEMS ipt 7:24:1
// %RANGE% TUNE_THREADS tpb 128:1024:32
// %RANGE% TUNE_MAGIC_NS ns 0:2048:4
// %RANGE% TUNE_DELAY_CONSTRUCTOR_ID dcid 0:7:1
// %RANGE% TUNE_L2_WRITE_LATENCY_NS l2w 0:1200:5
// %RANGE% TUNE_TRANSPOSE trp 0:1:1
// %RANGE% TUNE_LOAD ld 0:1:1
#if !TUNE_BASE
# if TUNE_TRANSPOSE == 0
# define TUNE_LOAD_ALGORITHM cub::BLOCK_LOAD_DIRECT
# define TUNE_STORE_ALGORITHM cub::BLOCK_STORE_DIRECT
# else // TUNE_TRANSPOSE == 1
# define TUNE_LOAD_ALGORITHM cub::BLOCK_LOAD_WARP_TRANSPOSE
# define TUNE_STORE_ALGORITHM cub::BLOCK_STORE_WARP_TRANSPOSE
# endif // TUNE_TRANSPOSE
# if TUNE_LOAD == 0
# define TUNE_LOAD_MODIFIER cub::LOAD_DEFAULT
# elif TUNE_LOAD == 1
# define TUNE_LOAD_MODIFIER cub::LOAD_CA
# endif // TUNE_LOAD
#endif // !TUNE_BASE
#include "../../policy_selector.h"
namespace impl
{
/*
* Given a sequence of logarithms of probability mass function values,
* compute sequence of logarithms of cumulative distribution function values.
*
* log(CDF(n)) = log(\sum( PDF(k), 0 <=k <=n ))
*
* This is inclusive scan using logaddexp binary operator:
* logaddexp( logpdf1, logpdf2 ) := log( exp(logpdf1) + exp(logpdf2) )
* == max(logpdf1, logpdf2) + log( 1 + exp(-abs(logpdf1 - logpdf2)))
*
* The last reformulation allows avoid numerical accuracy issues
* caused by underflows.
*
*/
struct log_add_plus
{
/* Operator is commutative and associative */
template <typename T>
T __host__ __device__ operator()(T v1, T v2)
{
T max12 = cuda::maximum{}(v1, v2);
T min12 = cuda::minimum{}(v1, v2);
T exp = cuda::std::exp(min12 - max12);
return max12 + cuda::std::log1p(exp);
}
};
template <typename T>
struct log_pdf_builder
{
T mu;
T norm;
cuda::std::size_t n;
T __host__ __device__ operator()(cuda::std::size_t i) const
{
return -mu * static_cast<T>(n - i) + norm;
}
};
template <typename T>
[[nodiscard]] bool validate(const thrust::device_vector<T>& output, cudaStream_t stream)
{
cudaStreamSynchronize(stream);
thrust::host_vector<T> h_output(output);
auto elements = h_output.size();
// test is designed so that last element of prefix scan sequence should be close to log(1.0) == 0.0
bool check = cuda::std::abs(h_output[elements - 1])
< cuda::std::sqrt(static_cast<T>(1 + elements)) * cuda::std::numeric_limits<T>::epsilon();
return check;
}
}; // namespace impl
template <typename FloatingPointT, typename OffsetT>
static void inclusive_scan(nvbench::state& state, nvbench::type_list<FloatingPointT, OffsetT>)
{
static_assert(cuda::std::is_floating_point_v<FloatingPointT>);
using value_t = FloatingPointT;
using input_t = const value_t*;
using output_t = value_t*;
using op_t = impl::log_add_plus;
using accum_t [[maybe_unused]] = value_t;
const auto elements = static_cast<std::size_t>(state.get_int64("Elements{io}"));
auto mu = static_cast<value_t>(state.get_float64("Mu{io}"));
auto norm = cuda::std::log1p(-cuda::std::exp(-mu)) - cuda::std::log1p(-cuda::std::exp(-mu * elements));
thrust::device_vector<value_t> input(elements, thrust::no_init);
cudaStream_t bench_stream = state.get_cuda_stream();
auto naturals_it = cuda::counting_iterator(cuda::std::size_t{0});
cub::DeviceTransform::Transform(
cuda::std::make_tuple(naturals_it),
input.begin(),
elements,
impl::log_pdf_builder<value_t>{mu, norm, elements},
bench_stream);
thrust::device_vector<value_t> output(elements, thrust::no_init);
input_t d_input = thrust::raw_pointer_cast(input.data());
output_t d_output = thrust::raw_pointer_cast(output.data());
state.add_element_count(elements);
state.add_global_memory_reads<value_t>(elements, "Size");
state.add_global_memory_writes<value_t>(elements);
caching_allocator_t alloc;
state.exec(nvbench::exec_tag::gpu | nvbench::exec_tag::no_batch, [&](nvbench::launch& launch) {
auto env = cub_bench_env(
alloc,
launch
#if !TUNE_BASE
,
cuda::execution::tune(policy_selector<accum_t>{})
#endif // !TUNE_BASE
);
_CCCL_TRY_CUDA_API(
cub::DeviceScan::InclusiveScan,
"InclusiveScan failed",
d_input,
d_output,
op_t{},
static_cast<OffsetT>(input.size()),
env);
});
// for validation, use
// assert(impl::validate(output, bench_stream));
}
#ifdef TUNE_T
using fp_types = nvbench::type_list<TUNE_T>;
#else
using fp_types = nvbench::type_list<float, double>;
#endif
NVBENCH_BENCH_TYPES(inclusive_scan, NVBENCH_TYPE_AXES(fp_types, offset_types))
.set_name("app-logcdf-from-logpdf")
.set_type_axes_names({"T{ct}", "OffsetT{ct}"})
.add_int64_power_of_two_axis("Elements{io}", nvbench::range(16, 28, 4))
.add_float64_axis("Mu{io}", {1e-4f});

View File

@@ -0,0 +1,153 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <cub/detail/choose_offset.cuh>
#include <cub/device/device_scan.cuh>
#include <cub/device/device_transform.cuh>
#include <look_back_helper.cuh>
#include <nvbench_helper.cuh>
// %RANGE% TUNE_ITEMS ipt 7:24:1
// %RANGE% TUNE_THREADS tpb 128:1024:32
// %RANGE% TUNE_MAGIC_NS ns 0:2048:4
// %RANGE% TUNE_DELAY_CONSTRUCTOR_ID dcid 0:7:1
// %RANGE% TUNE_L2_WRITE_LATENCY_NS l2w 0:1200:5
// %RANGE% TUNE_TRANSPOSE trp 0:1:1
// %RANGE% TUNE_LOAD ld 0:1:1
#if !TUNE_BASE
# if TUNE_TRANSPOSE == 0
# define TUNE_LOAD_ALGORITHM cub::BLOCK_LOAD_DIRECT
# define TUNE_STORE_ALGORITHM cub::BLOCK_STORE_DIRECT
# else // TUNE_TRANSPOSE == 1
# define TUNE_LOAD_ALGORITHM cub::BLOCK_LOAD_WARP_TRANSPOSE
# define TUNE_STORE_ALGORITHM cub::BLOCK_STORE_WARP_TRANSPOSE
# endif // TUNE_TRANSPOSE
# if TUNE_LOAD == 0
# define TUNE_LOAD_MODIFIER cub::LOAD_DEFAULT
# elif TUNE_LOAD == 1
# define TUNE_LOAD_MODIFIER cub::LOAD_CA
# endif // TUNE_LOAD
#endif // !TUNE_BASE
#include "../../policy_selector.h"
namespace impl
{
/* Consider free monoid with two generators, ``q`` and ``p``, modulo defining relationship (``p * q == 1``).
* Elements of this algebra are ``q^m * p^n``, identified by a pair of integral exponents. The identity
* element is ``1 == q^0 * p^0``, which maps to pair of zeros ``e = (0, 0)``.
*
* The product is defined by concatenation:
* q^m * p^n * q^r * p^s == q^m * p^{n-1} * p * q * q^{r-1} * p^s
* == q^m * p^{n-1} * q^{r-1} * p^s
*
* This reduction can be performed ``min(n, r)`` times resulting in
*
* q^m * p^n * q^r * p^s == q^{m + r - min(n, r)} * p^{s + n - min(n, r)}
*
* Hence this is a monoid, known as bicyclic monoid.
* This operation of pairs of integers is associative (since concatenation is), but non-commutative.
*
* Ref: https://en.wikipedia.org/wiki/Bicyclic_semigroup
* Ref: https://en.wikipedia.org/wiki/Monoid
*/
template <typename UnsignedIntegralT>
struct bicyclic_monoid_op
{
static_assert(cuda::std::is_integral_v<UnsignedIntegralT>);
static_assert(cuda::std::is_unsigned_v<UnsignedIntegralT>);
using pair_t = cuda::std::pair<UnsignedIntegralT, UnsignedIntegralT>;
using min_t = cuda::minimum<>;
// Operator is associative but non-commutative
pair_t __host__ __device__ operator()(pair_t v1, pair_t v2) const
{
auto [m, n] = v1;
auto [r, s] = v2;
auto min_nr = min_t{}(n, r);
return {m + r - min_nr, s + n - min_nr};
}
};
template <typename T>
struct repack_pair
{
cuda::std::pair<T, T> __host__ __device__ operator()(const T& v1, const T& v2) const
{
return {v1, v2};
};
};
}; // namespace impl
template <typename T, typename OffsetT>
static void inclusive_scan(nvbench::state& state, nvbench::type_list<T, OffsetT>)
{
static_assert(cuda::std::is_integral_v<T> && cuda::std::is_unsigned_v<T>, "Unsigned integral type should be used");
using pair_t = cuda::std::pair<T, T>;
using op_t = impl::bicyclic_monoid_op<T>;
using accum_t [[maybe_unused]] = pair_t;
const auto elements = static_cast<std::size_t>(state.get_int64("Elements{io}"));
thrust::device_vector<pair_t> output(elements);
thrust::device_vector<pair_t> input(elements);
{
thrust::device_vector<T> q_exponents = generate(elements);
thrust::device_vector<T> p_exponents = generate(elements);
impl::repack_pair<T> repack_op{};
cub::DeviceTransform::Transform(
cuda::std::tuple{q_exponents.begin(), p_exponents.begin()}, input.begin(), elements, repack_op);
// deallocate temporary arrays at the scope boundary
}
pair_t* d_input = thrust::raw_pointer_cast(input.data());
pair_t* d_output = thrust::raw_pointer_cast(output.data());
state.add_element_count(elements);
state.add_global_memory_reads<pair_t>(elements, "Size");
state.add_global_memory_writes<pair_t>(elements);
caching_allocator_t alloc;
state.exec(nvbench::exec_tag::gpu | nvbench::exec_tag::no_batch, [&](nvbench::launch& launch) {
auto env = cub_bench_env(
alloc,
launch
#if !TUNE_BASE
,
cuda::execution::tune(policy_selector<accum_t>{})
#endif // !TUNE_BASE
);
_CCCL_TRY_CUDA_API(
cub::DeviceScan::InclusiveScan,
"InclusiveScan failed",
d_input,
d_output,
op_t{},
static_cast<OffsetT>(input.size()),
env);
});
}
#ifdef TUNE_T
using uint_types = nvbench::type_list<TUNE_T>;
#else
# if _CCCL_HAS_INT128()
using uint_types = nvbench::type_list<cuda::std::uint32_t, cuda::std::uint64_t, uint128_t>;
# else
using uint_types = nvbench::type_list<cuda::std::uint32_t, cuda::std::uint64_t>;
# endif
#endif
NVBENCH_BENCH_TYPES(inclusive_scan, NVBENCH_TYPE_AXES(uint_types, offset_types))
.set_name("app-bicyclic-monoid")
.set_type_axes_names({"T{ct}", "OffsetT{ct}"})
.add_int64_power_of_two_axis("Elements{io}", nvbench::range(16, 28, 4));

View File

@@ -0,0 +1,347 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <cub/detail/choose_offset.cuh>
#include <cub/device/device_scan.cuh>
#include <thrust/host_vector.h>
#include <cuda/cmath>
#include <cuda/std/limits>
#include <iostream>
#include <look_back_helper.cuh>
#include <nvbench_helper.cuh>
// %RANGE% TUNE_ITEMS ipt 7:24:1
// %RANGE% TUNE_THREADS tpb 128:1024:32
// %RANGE% TUNE_MAGIC_NS ns 0:2048:4
// %RANGE% TUNE_DELAY_CONSTRUCTOR_ID dcid 0:7:1
// %RANGE% TUNE_L2_WRITE_LATENCY_NS l2w 0:1200:5
// %RANGE% TUNE_TRANSPOSE trp 0:1:1
// %RANGE% TUNE_LOAD ld 0:1:1
#if !TUNE_BASE
# if TUNE_TRANSPOSE == 0
# define TUNE_LOAD_ALGORITHM cub::BLOCK_LOAD_DIRECT
# define TUNE_STORE_ALGORITHM cub::BLOCK_STORE_DIRECT
# else // TUNE_TRANSPOSE == 1
# define TUNE_LOAD_ALGORITHM cub::BLOCK_LOAD_WARP_TRANSPOSE
# define TUNE_STORE_ALGORITHM cub::BLOCK_STORE_WARP_TRANSPOSE
# endif // TUNE_TRANSPOSE
# if TUNE_LOAD == 0
# define TUNE_LOAD_MODIFIER cub::LOAD_DEFAULT
# elif TUNE_LOAD == 1
# define TUNE_LOAD_MODIFIER cub::LOAD_CA
# endif // TUNE_LOAD
#endif // !TUNE_BASE
#include "../../policy_selector.h"
namespace impl
{
/* Denote epsilon, the identity element, be an empty sequence, and consider
* set of sequences of {0, 1} bits, with binary operation of concatenation.
*
* Define homomorphism K from the set of sequence to 2-by-2 integral matrices
* over cyclic ring Z_p for some prime p.
*
* K( '' ) = [[ 1, 0], [0, 1]]
* K( '0' ) = [[1, 0], [1, 1]]
* K( '1' ) = [[1, 1], [0, 1]]
*
* K( concat(seq1, seq2) ) := matmul( K(seq1), K(seq2) ) in Z_p
*
* Given a sequence of unsigned integers, encoding bit sequences,
* we build transform iterator mapping integer to the matrix. Then
* call inclusive_scan with matrix multiply operator in Z_p
*
* Ref: https://doi.org/10.1147/rd.312.0249
*/
// Types associated with the cyclic ring
using ZpT = cuda::std::uint32_t;
using WideT = cuda::std::uint64_t;
using MatT = cuda::std::array<ZpT, 4>;
inline ZpT __host__ __device__ Zp_mul(ZpT v1, ZpT v2, cuda::fast_mod_div<WideT> m_p)
{
const auto w1 = static_cast<WideT>(v1);
const auto w2 = static_cast<WideT>(v2);
return static_cast<ZpT>((w1 * w2) % m_p);
}
inline ZpT __host__ __device__ Zp_add(ZpT v1, ZpT v2, cuda::fast_mod_div<WideT> m_p)
{
const auto w1 = static_cast<WideT>(v1);
const auto w2 = static_cast<WideT>(v2);
return static_cast<ZpT>((w1 + w2) % m_p);
}
inline MatT __host__ __device__ Zp_matmul(MatT v1, MatT v2, cuda::fast_mod_div<WideT> m_p)
{
ZpT _1_00_2_00 = Zp_mul(v1[0], v2[0], m_p);
ZpT _1_01_2_10 = Zp_mul(v1[1], v2[2], m_p);
ZpT _r_00 = Zp_add(_1_00_2_00, _1_01_2_10, m_p);
ZpT _1_00_2_01 = Zp_mul(v1[0], v2[1], m_p);
ZpT _1_01_2_11 = Zp_mul(v1[1], v2[3], m_p);
ZpT _r_01 = Zp_add(_1_00_2_01, _1_01_2_11, m_p);
ZpT _1_10_2_00 = Zp_mul(v1[2], v2[0], m_p);
ZpT _1_11_2_10 = Zp_mul(v1[3], v2[2], m_p);
ZpT _r_10 = Zp_add(_1_10_2_00, _1_11_2_10, m_p);
ZpT _1_10_2_01 = Zp_mul(v1[2], v2[1], m_p);
ZpT _1_11_2_11 = Zp_mul(v1[3], v2[3], m_p);
ZpT _r_11 = Zp_add(_1_10_2_01, _1_11_2_11, m_p);
return {_r_00, _r_01, _r_10, _r_11};
}
struct RabinKarpOp
{
cuda::fast_mod_div<WideT> m_p;
__host__ __device__ RabinKarpOp(ZpT p)
: m_p(static_cast<WideT>(p))
{}
// scan operator: non-commutative and associative
MatT __host__ __device__ operator()(MatT v1, MatT v2) const
{
return Zp_matmul(v1, v2, m_p);
}
};
template <typename T>
struct ChunkToMat
{
static_assert(cuda::std::is_integral_v<T> && cuda::std::is_unsigned_v<T>, "Bit sequence should be represented");
cuda::fast_mod_div<WideT> m_p;
__host__ __device__ ChunkToMat(ZpT p)
: m_p(static_cast<WideT>(p))
{}
MatT __host__ __device__ operator()(const T& bits) const
{
static constexpr int n_bits = cuda::std::numeric_limits<T>::digits;
static_assert(n_bits >= 1, "Type must have non-zero bitwidth");
static constexpr MatT _0 = {ZpT{1}, ZpT{0}, ZpT{1}, ZpT{1}}; // [[1, 0], [1, 1]]
static constexpr MatT _1 = {ZpT{1}, ZpT{1}, ZpT{0}, ZpT{1}}; // [[1, 1], [0, 1]]
// initialize with identity matrix
MatT m = (bits & 1) ? _1 : _0;
T _bits = bits >> 1;
// use of cuda::static_for here results in performance regression due to increased register pressure
for (int i = 1; i < n_bits; ++i)
{
(void) i;
m = Zp_matmul((_bits & 1) ? _1 : _0, m, m_p);
_bits >>= 1;
}
return m;
}
};
// Iterator that performs assignment at specific index only, discards otherwise
//
// This iterator allows tp use inclusive_scan to perform reduction with
// non-commutative associative binary operator
//
template <typename OffsetT, typename Iter>
struct write_at_specific_index_or_discard
{
private:
OffsetT m_index{};
OffsetT m_target_index;
Iter m_iter;
void __host__ __device__ set_index(OffsetT index)
{
m_index = index;
}
public:
struct assign_proxy
{
private:
bool m_writable;
Iter m_iter;
public:
__host__ __device__ assign_proxy(bool writable, Iter iter)
: m_writable(writable)
, m_iter(iter)
{}
template <typename Tp>
constexpr assign_proxy& __host__ __device__ operator=(Tp&& v)
{
if (m_writable)
{
*m_iter = v;
}
return *this;
}
};
using iterator_concept = cuda::std::random_access_iterator_tag;
using iterator_category = cuda::std::random_access_iterator_tag;
using value_type = cuda::std::iter_value_t<Iter>;
using difference_type = cuda::std::iter_difference_t<Iter>;
using pointer = void;
using reference = void;
write_at_specific_index_or_discard() = delete;
explicit __host__ __device__ write_at_specific_index_or_discard(OffsetT offset, Iter iter)
: m_target_index(offset)
, m_iter(iter)
{}
write_at_specific_index_or_discard(const write_at_specific_index_or_discard&) = default;
write_at_specific_index_or_discard(write_at_specific_index_or_discard&&) = default;
write_at_specific_index_or_discard& operator=(const write_at_specific_index_or_discard&) = default;
write_at_specific_index_or_discard& operator=(write_at_specific_index_or_discard&&) = default;
assign_proxy __host__ __device__ operator[](difference_type n)
{
return {(m_index + static_cast<OffsetT>(n)) == m_target_index, m_iter};
}
write_at_specific_index_or_discard __host__ __device__ operator+(difference_type n) const
{
auto r = write_at_specific_index_or_discard(m_target_index, m_iter);
r.set_index((m_index + static_cast<OffsetT>(n)));
return r;
}
};
template <typename InputT, typename OutputT>
[[nodiscard]] bool validate(
const thrust::device_vector<InputT>& input, const thrust::device_vector<OutputT>& output, ZpT p, cudaStream_t stream)
{
using accum_t = OutputT;
using input_t = InputT;
cudaStreamSynchronize(stream);
thrust::host_vector<accum_t> h_out(output);
thrust::host_vector<input_t> h_inp(input);
accum_t ref_mat = {1, 0, 0, 1};
static constexpr accum_t mat_0 = {1, 0, 1, 1}; // lower diagonal
static constexpr accum_t mat_1 = {1, 1, 0, 1}; // upper diagonal
cuda::fast_mod_div<impl::WideT> mod(p);
for (auto&& el : h_inp)
{
input_t v = el;
accum_t word_mat = {1, 0, 0, 1};
for (int i = 0; i < sizeof(input_t) * 8; ++i)
{
if (v & 1)
{
word_mat = impl::Zp_matmul(mat_1, word_mat, mod);
}
else
{
word_mat = impl::Zp_matmul(mat_0, word_mat, mod);
}
v >>= 1;
}
ref_mat = impl::Zp_matmul(ref_mat, word_mat, mod);
}
const accum_t& res = h_out[0];
if (ref_mat != res)
{
std::cout << "FAILED: ";
std::cout << "cub_computed([[" << res[0] << ", " << res[1] << "], [" << res[2] << ", " << res[3] << "]]) != ";
std::cout
<< "reference([[" << ref_mat[0] << ", " << ref_mat[1] << "], [" << ref_mat[2] << ", " << ref_mat[3] << "]])\n";
return false;
}
return true;
}
}; // namespace impl
template <typename BitsetT, typename OffsetT>
static void inclusive_scan(nvbench::state& state, nvbench::type_list<BitsetT, OffsetT>)
{
using op_t = impl::RabinKarpOp;
using input_t = BitsetT;
using raw_it_t = const input_t*;
using input_it_t = cuda::transform_iterator<impl::ChunkToMat<input_t>, raw_it_t>;
using accum_t = impl::MatT;
using output_ptr_t = impl::MatT*;
using output_it_t = impl::write_at_specific_index_or_discard<OffsetT, output_ptr_t>;
using ZpT = impl::ZpT;
const auto elements = static_cast<std::size_t>(state.get_int64("Elements{io}"));
thrust::device_vector<input_t> input = generate(elements);
thrust::device_vector<accum_t> output(1, thrust::no_init);
// a large prime
ZpT p = static_cast<ZpT>(state.get_int64("Modulus"));
raw_it_t d_input = thrust::raw_pointer_cast(input.data());
output_ptr_t d_output = thrust::raw_pointer_cast(output.data());
input_it_t inp_it(d_input, impl::ChunkToMat<input_t>(p));
output_it_t out_it(static_cast<OffsetT>(elements - 1), d_output);
state.add_element_count(elements);
state.add_global_memory_reads<input_t>(elements, "Sequence Size");
state.add_global_memory_writes<accum_t>(1, "Hash Size");
caching_allocator_t alloc;
state.exec(nvbench::exec_tag::gpu | nvbench::exec_tag::no_batch, [&](nvbench::launch& launch) {
auto env = cub_bench_env(
alloc,
launch
#if !TUNE_BASE
,
cuda::execution::tune(policy_selector<accum_t>{})
#endif // !TUNE_BASE
);
_CCCL_TRY_CUDA_API(
cub::DeviceScan::InclusiveScan,
"InclusiveScan failed",
inp_it,
out_it, // iterator that only writes the last element of inclusive prefix scan sequence
op_t{p},
static_cast<OffsetT>(input.size()),
env);
});
// for validation uncomment these two lines
// assert(impl::validate(input, output, p, bench_stream));
}
#ifdef TUNE_T
using type_list = nvbench::type_list<TUNE_T>;
#else
// we can split stream of bits into 8-bit, 16-bit, etc. chunks, effectively
// serving as the number of bits processed by a thread
using type_list = nvbench::type_list<cuda::std::uint8_t, cuda::std::uint16_t, cuda::std::uint32_t, cuda::std::uint64_t>;
#endif
NVBENCH_BENCH_TYPES(inclusive_scan, NVBENCH_TYPE_AXES(type_list, offset_types))
.set_name("rabin-karp-fingerprinting-monoid")
.set_type_axes_names({"BitsetT{ct}", "OffsetT{ct}"})
.add_int64_power_of_two_axis("Elements{io}", nvbench::range(16, 28, 4))
.add_int64_axis("Modulus", {2725841});

View File

@@ -0,0 +1,247 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <cub/detail/choose_offset.cuh>
#include <cub/device/device_scan.cuh>
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>
#include <cuda/iterator>
#include <cuda/std/cmath>
#include <cuda/std/limits>
#include <cuda/std/utility>
#include <look_back_helper.cuh>
#include <nvbench_helper.cuh>
// %RANGE% TUNE_ITEMS ipt 7:24:1
// %RANGE% TUNE_THREADS tpb 128:1024:32
// %RANGE% TUNE_MAGIC_NS ns 0:2048:4
// %RANGE% TUNE_DELAY_CONSTRUCTOR_ID dcid 0:7:1
// %RANGE% TUNE_L2_WRITE_LATENCY_NS l2w 0:1200:5
// %RANGE% TUNE_TRANSPOSE trp 0:1:1
// %RANGE% TUNE_LOAD ld 0:1:1
#if !TUNE_BASE
# if TUNE_TRANSPOSE == 0
# define TUNE_LOAD_ALGORITHM cub::BLOCK_LOAD_DIRECT
# define TUNE_STORE_ALGORITHM cub::BLOCK_STORE_DIRECT
# else // TUNE_TRANSPOSE == 1
# define TUNE_LOAD_ALGORITHM cub::BLOCK_LOAD_WARP_TRANSPOSE
# define TUNE_STORE_ALGORITHM cub::BLOCK_STORE_WARP_TRANSPOSE
# endif // TUNE_TRANSPOSE
# if TUNE_LOAD == 0
# define TUNE_LOAD_MODIFIER cub::LOAD_DEFAULT
# elif TUNE_LOAD == 1
# define TUNE_LOAD_MODIFIER cub::LOAD_CA
# endif // TUNE_LOAD
#endif // !TUNE_BASE
#include "../../policy_selector.h"
namespace impl
{
/* Given input sequence of values, compute sequence of
* pairs corresponding to running minimum and running maximum values.
*/
/*! @brief Structure to hold minimum and maximum */
template <typename T>
struct min_max_t
{
private:
T m_min{cuda::std::numeric_limits<T>::max()};
T m_max{cuda::std::numeric_limits<T>::min()};
public:
min_max_t() = default;
__host__ __device__ min_max_t(T minimum, T maximum)
: m_min(minimum)
, m_max(maximum)
{}
T __host__ __device__ minimum() const
{
return m_min;
}
T __host__ __device__ maximum() const
{
return m_max;
}
};
/* Scan operator combining min-max pairs. It is commutative and associative */
struct scan_op
{
template <typename T>
min_max_t<T> __host__ __device__ operator()(min_max_t<T> v1, min_max_t<T> v2) const
{
auto min_r = cuda::minimum{}(v1.minimum(), v2.minimum());
auto max_r = cuda::maximum{}(v1.maximum(), v2.maximum());
return {min_r, max_r};
}
};
template <typename T>
struct embed_op
{
min_max_t<T> __host__ __device__ operator()(T v) const
{
return {v, v};
}
};
template <typename T>
struct extract_min
{
T __host__ __device__ operator()(min_max_t<T> pair) const
{
return pair.minimum();
}
};
template <typename T>
struct extract_max
{
T __host__ __device__ operator()(min_max_t<T> pair) const
{
return pair.maximum();
}
};
template <typename ValueT, typename PairT>
void validate(const thrust::device_vector<ValueT>& input,
const thrust::device_vector<PairT>& output,
cudaStream_t stream)
{
using value_t = ValueT;
auto elements = input.size();
thrust::device_vector<value_t> ref_mins(elements, thrust::no_init);
thrust::device_vector<value_t> ref_maxs(elements, thrust::no_init);
size_t tmp_size{};
auto d_input = thrust::raw_pointer_cast(input.data());
auto d_output = thrust::raw_pointer_cast(output.data());
cub::DeviceScan::InclusiveScanInit(
nullptr,
tmp_size,
d_input,
ref_mins.begin(),
cuda::minimum<>{},
cuda::std::numeric_limits<value_t>::max(),
input.size(),
stream);
thrust::device_vector<nvbench::uint8_t> tmp1(tmp_size, thrust::no_init);
nvbench::uint8_t* d_tmp1 = thrust::raw_pointer_cast(tmp1.data());
cub::DeviceScan::InclusiveScanInit(
d_tmp1,
tmp_size,
d_input,
ref_mins.begin(),
cuda::minimum<>{},
cuda::std::numeric_limits<value_t>::max(),
input.size(),
stream);
cub::DeviceScan::InclusiveScanInit(
nullptr,
tmp_size,
d_input,
ref_maxs.begin(),
cuda::minimum<>{},
cuda::std::numeric_limits<value_t>::max(),
input.size(),
stream);
thrust::device_vector<nvbench::uint8_t> tmp2(tmp_size, thrust::no_init);
nvbench::uint8_t* d_tmp2 = thrust::raw_pointer_cast(tmp2.data());
cub::DeviceScan::InclusiveScanInit(
d_tmp2,
tmp_size,
d_input,
ref_maxs.begin(),
cuda::maximum<>{},
cuda::std::numeric_limits<value_t>::min(),
input.size(),
stream);
thrust::device_vector<value_t> computed_mins(elements, thrust::no_init);
thrust::device_vector<value_t> computed_maxs(elements, thrust::no_init);
impl::extract_min<value_t> extract_min_op{};
cub::DeviceTransform::Transform(d_output, computed_mins.begin(), input.size(), extract_min_op, stream);
impl::extract_max<value_t> extract_max_op{};
cub::DeviceTransform::Transform(d_output, computed_maxs.begin(), input.size(), extract_max_op, stream);
assert(computed_mins == ref_mins);
assert(computed_maxs == ref_maxs);
}
}; // namespace impl
template <typename T, typename OffsetT>
void benchmark_impl(nvbench::state& state, nvbench::type_list<T, OffsetT>)
{
using value_t = T;
using pair_t = impl::min_max_t<value_t>;
using op_t = impl::scan_op;
using accum_t [[maybe_unused]] = pair_t;
using input_raw_t = const value_t*;
using input_it_t = cuda::transform_iterator<impl::embed_op<value_t>, input_raw_t>;
using output_it_t = pair_t*;
const auto elements = static_cast<std::size_t>(state.get_int64("Elements{io}"));
thrust::device_vector<pair_t> output(elements);
thrust::device_vector<value_t> input = generate(elements);
input_raw_t d_input = thrust::raw_pointer_cast(input.data());
output_it_t d_output = thrust::raw_pointer_cast(output.data());
input_it_t inp_it(d_input, impl::embed_op<value_t>{});
state.add_element_count(elements);
state.add_global_memory_reads<value_t>(elements, "Size");
state.add_global_memory_writes<pair_t>(elements);
caching_allocator_t alloc;
state.exec(nvbench::exec_tag::gpu | nvbench::exec_tag::no_batch, [&](nvbench::launch& launch) {
auto env = cub_bench_env(
alloc,
launch
#if !TUNE_BASE
,
cuda::execution::tune(policy_selector<accum_t>{})
#endif // !TUNE_BASE
);
_CCCL_TRY_CUDA_API(
cub::DeviceScan::InclusiveScan,
"InclusiveScan failed",
inp_it,
d_output,
op_t{},
static_cast<OffsetT>(input.size()),
env);
});
// for verification use
// impl::validate(input, output, state.get_cuda_stream().get_stream());
}
#ifdef TUNE_T
using bench_types = nvbench::type_list<TUNE_T>;
#else
using bench_types = nvbench::type_list<nvbench::uint32_t, nvbench::int64_t, nvbench::float32_t, nvbench::float64_t>;
#endif
NVBENCH_BENCH_TYPES(benchmark_impl, NVBENCH_TYPE_AXES(bench_types, offset_types))
.set_name("running-min-max")
.set_type_axes_names({"T{ct}", "OffsetT{ct}"})
.add_int64_power_of_two_axis("Elements{io}", nvbench::range(16, 28, 4));

View File

@@ -0,0 +1,165 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <cub/detail/choose_offset.cuh>
#include <cub/device/device_scan.cuh>
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>
#include <cuda/iterator>
#include <cuda/std/tuple>
#include <look_back_helper.cuh>
#include <nvbench_helper.cuh>
// %RANGE% TUNE_ITEMS ipt 7:24:1
// %RANGE% TUNE_THREADS tpb 128:1024:32
// %RANGE% TUNE_MAGIC_NS ns 0:2048:4
// %RANGE% TUNE_DELAY_CONSTRUCTOR_ID dcid 0:7:1
// %RANGE% TUNE_L2_WRITE_LATENCY_NS l2w 0:1200:5
// %RANGE% TUNE_TRANSPOSE trp 0:1:1
// %RANGE% TUNE_LOAD ld 0:1:1
#if !TUNE_BASE
# if TUNE_TRANSPOSE == 0
# define TUNE_LOAD_ALGORITHM cub::BLOCK_LOAD_DIRECT
# define TUNE_STORE_ALGORITHM cub::BLOCK_STORE_DIRECT
# else // TUNE_TRANSPOSE == 1
# define TUNE_LOAD_ALGORITHM cub::BLOCK_LOAD_WARP_TRANSPOSE
# define TUNE_STORE_ALGORITHM cub::BLOCK_STORE_WARP_TRANSPOSE
# endif // TUNE_TRANSPOSE
# if TUNE_LOAD == 0
# define TUNE_LOAD_MODIFIER cub::LOAD_DEFAULT
# elif TUNE_LOAD == 1
# define TUNE_LOAD_MODIFIER cub::LOAD_CA
# endif // TUNE_LOAD
#endif // !TUNE_BASE
#include "../../policy_selector.h"
namespace impl
{
template <typename T>
using triplet_t = cuda::std::tuple<T, T, T>;
/* The triplet corresponds to strictly upper triangular elements of a unitriangular matrix
A = [[1, a1, a12], [0, 1, a2], [0, 0, 1]], mapped to triplet [a1, a2, a12].
The set of unitriangular matrix forms a group, with product induced by matrix multiplication,
and the identity element corresponding to zero triplet.
*/
struct unitriangular_dim3_op
{
// Scan operation: associative and non-commutative
template <typename T>
triplet_t<T> __host__ __device__ operator()(triplet_t<T> a, triplet_t<T> b) const
{
auto [a1, a2, a12] = a;
auto [b1, b2, b12] = b;
return {a1 + b1, a2 + b1, a12 + b12 + a1 * b2};
}
};
// Utility operation to pack arguments into a triplet_t instance
struct pack_op
{
template <typename T>
triplet_t<T> __host__ __device__ operator()(T a1, T a2, T a12) const
{
return {a1, a2, a12};
} // namespace impl
};
template <typename TupleT, typename ScanOpT>
bool validation(const thrust::device_vector<TupleT>& input,
const thrust::device_vector<TupleT>& output,
ScanOpT op,
cudaStream_t stream)
{
cudaStreamSynchronize(stream);
using tuple_t = TupleT;
thrust::host_vector<tuple_t> h_input(input);
thrust::host_vector<tuple_t> h_output(output);
auto elements = input.size();
thrust::host_vector<tuple_t> h_reference(elements);
h_reference[0] = h_input[0];
for (std::size_t i = 1; i < elements; ++i)
{
h_reference[i] = op(h_reference[i - 1], h_input[i]);
}
return h_reference == h_output;
}
}; // namespace impl
template <typename T, typename OffsetT>
void benchmark_impl(nvbench::state& state, nvbench::type_list<T, OffsetT>)
{
using value_t = T;
using tuple_t = impl::triplet_t<value_t>;
using op_t = impl::unitriangular_dim3_op;
using accum_t [[maybe_unused]] = tuple_t;
const auto elements = static_cast<std::size_t>(state.get_int64("Elements{io}"));
cudaStream_t bench_stream = state.get_cuda_stream().get_stream();
thrust::device_vector<tuple_t> output(elements);
thrust::device_vector<value_t> _input = generate(cuda::std::tuple_size_v<tuple_t> * elements);
thrust::device_vector<tuple_t> input(elements);
cub::DeviceTransform::Transform(
cuda::std::make_tuple(cuda::strided_iterator(_input.begin(), std::size_t{3}),
cuda::strided_iterator(_input.begin() + 1, std::size_t{3}),
cuda::strided_iterator(_input.begin() + 2, std::size_t{3})),
input.begin(),
input.size(),
impl::pack_op{},
bench_stream);
state.add_element_count(elements);
state.add_global_memory_reads<tuple_t>(elements, "Size");
state.add_global_memory_writes<tuple_t>(elements);
auto d_input = thrust::raw_pointer_cast(input.data());
auto d_output = thrust::raw_pointer_cast(output.data());
caching_allocator_t alloc;
state.exec(nvbench::exec_tag::gpu | nvbench::exec_tag::no_batch, [&](nvbench::launch& launch) {
auto env = cub_bench_env(
alloc,
launch
#if !TUNE_BASE
,
cuda::execution::tune(policy_selector<accum_t>{})
#endif // !TUNE_BASE
);
_CCCL_TRY_CUDA_API(
cub::DeviceScan::InclusiveScan,
"InclusiveScan failed",
d_input,
d_output,
op_t{},
static_cast<OffsetT>(input.size()),
env);
});
// for validation use (recommended for integral types and smallish input sizes)
// assert(impl::validation(input, output, op_t{}, bench_stream));
}
#ifdef TUNE_T
using bench_types = nvbench::type_list<TUNE_T>;
#else
using bench_types = nvbench::type_list<nvbench::int32_t, nvbench::uint64_t, nvbench::float32_t, nvbench::float64_t>;
#endif
NVBENCH_BENCH_TYPES(benchmark_impl, NVBENCH_TYPE_AXES(bench_types, offset_types))
.set_name("unitriangular-monoid")
.set_type_axes_names({"T{ct}", "OffsetT{ct}"})
.add_int64_power_of_two_axis("Elements{io}", nvbench::range(16, 28, 4));

View File

@@ -0,0 +1,88 @@
// SPDX-FileCopyrightText: Copyright (c) 2011-2026, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
#pragma once
#include <cub/device/device_scan.cuh>
#include <cuda/std/__functional/invoke.h>
#include <nvbench_helper.cuh>
#include "../policy_selector.h"
template <typename T, typename OffsetT>
static void basic(nvbench::state& state, nvbench::type_list<T, OffsetT>)
try
{
using init_value_t = T;
using accum_t [[maybe_unused]] = ::cuda::std::__accumulator_t<op_t, init_value_t, T>;
using offset_t = cub::detail::choose_offset_t<OffsetT>;
#if USES_LOOKAHEAD()
static_assert(sizeof(offset_t) == sizeof(size_t)); // lookahead scan uses size_t internally
#endif // USES_LOOKAHEAD()
const auto elements = static_cast<std::size_t>(state.get_int64("Elements{io}"));
if (sizeof(offset_t) == 4 && elements > std::numeric_limits<offset_t>::max())
{
state.skip("Skipping: input size exceeds 32-bit offset type capacity.");
return;
}
thrust::device_vector<T> input = generate(elements);
thrust::device_vector<T> output(elements);
const T* d_input = thrust::raw_pointer_cast(input.data());
T* d_output = thrust::raw_pointer_cast(output.data());
state.add_element_count(elements);
state.add_global_memory_reads<T>(elements, "Size");
state.add_global_memory_writes<T>(elements);
caching_allocator_t alloc;
state.exec(nvbench::exec_tag::gpu | nvbench::exec_tag::no_batch, [&](nvbench::launch& launch) {
auto env = cub_bench_env(
alloc,
launch
#if !TUNE_BASE
,
cuda::execution::tune(policy_selector<accum_t>{})
#endif // !TUNE_BASE
);
_CCCL_TRY_CUDA_API(
cub::DeviceScan::ExclusiveScan,
"ExclusiveScan failed",
d_input,
d_output,
op_t{},
init_value_t{},
static_cast<offset_t>(input.size()),
env);
});
}
catch (const std::bad_alloc&)
{
state.skip("Skipping: out of memory.");
}
// __half and __nv_bfloat16 are added for full (non-tuning) runs; CUB has fast paths for them (see #9587).
#ifdef TUNE_T
using value_types = nvbench::type_list<TUNE_T>;
#else
using value_types =
push_back_t<all_types
# if _CCCL_HAS_NVFP16() && _CCCL_CTK_AT_LEAST(12, 2)
,
__half
# endif
# if _CCCL_HAS_NVBF16() && _CCCL_CTK_AT_LEAST(12, 2)
,
__nv_bfloat16
# endif
>;
#endif
NVBENCH_BENCH_TYPES(basic, NVBENCH_TYPE_AXES(value_types, scan_offset_types))
.set_name("base")
.set_type_axes_names({"T{ct}", "OffsetT{ct}"})
.add_int64_power_of_two_axis("Elements{io}", nvbench::range(16, 32, 4));

View File

@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: Copyright (c) 2011-2026, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
#include <cub/device/device_scan.cuh>
#include <look_back_helper.cuh>
#include <nvbench_helper.cuh>
// %RANGE% TUNE_ITEMS ipt 7:24:1
// %RANGE% TUNE_THREADS tpb 128:1024:32
// %RANGE% TUNE_MAGIC_NS ns 0:2048:4
// %RANGE% TUNE_DELAY_CONSTRUCTOR_ID dcid 0:7:1
// %RANGE% TUNE_L2_WRITE_LATENCY_NS l2w 0:1200:5
// %RANGE% TUNE_TRANSPOSE trp 0:1:1
// %RANGE% TUNE_LOAD ld 0:1:1
#if !TUNE_BASE
struct bench_scan_by_key_policy_selector
{
[[nodiscard]] _CCCL_HOST_DEVICE constexpr auto operator()(cuda::compute_capability) const -> cub::ScanByKeyPolicy
{
return {cub::ScanByKeyAlgorithm::lookback,
{TUNE_THREADS,
TUNE_ITEMS,
TUNE_TRANSPOSE == 0 ? cub::BLOCK_LOAD_DIRECT : cub::BLOCK_LOAD_WARP_TRANSPOSE,
TUNE_LOAD == 0 ? cub::LOAD_DEFAULT : cub::LOAD_CA,
TUNE_TRANSPOSE == 0 ? cub::BLOCK_STORE_DIRECT : cub::BLOCK_STORE_WARP_TRANSPOSE,
cub::BLOCK_SCAN_WARP_SCANS,
lookback_delay_policy}};
}
};
#endif // !TUNE_BASE
template <typename KeyT, typename ValueT, typename OffsetT>
static void scan(nvbench::state& state, nvbench::type_list<KeyT, ValueT, OffsetT>)
{
using init_value_t = ValueT;
using op_t = ::cuda::std::plus<>;
using equality_op_t = ::cuda::std::equal_to<>;
const auto elements = static_cast<std::size_t>(state.get_int64("Elements{io}"));
thrust::device_vector<ValueT> in_vals(elements);
thrust::device_vector<ValueT> out_vals(elements);
thrust::device_vector<KeyT> keys = generate.uniform.key_segments(elements, 0, 5200);
const KeyT* d_keys = thrust::raw_pointer_cast(keys.data());
const ValueT* d_in_vals = thrust::raw_pointer_cast(in_vals.data());
ValueT* d_out_vals = thrust::raw_pointer_cast(out_vals.data());
state.add_element_count(elements);
state.add_global_memory_reads<KeyT>(elements);
state.add_global_memory_reads<ValueT>(elements);
state.add_global_memory_writes<ValueT>(elements);
caching_allocator_t alloc;
state.exec(nvbench::exec_tag::gpu | nvbench::exec_tag::no_batch, [&](nvbench::launch& launch) {
auto env = cub_bench_env(
alloc,
launch
#if !TUNE_BASE
,
cuda::execution::tune(bench_scan_by_key_policy_selector{})
#endif // !TUNE_BASE
);
_CCCL_TRY_CUDA_API(
cub::DeviceScan::ExclusiveScanByKey,
"ExclusiveScanByKey failed",
d_keys,
d_in_vals,
d_out_vals,
op_t{},
init_value_t{},
static_cast<OffsetT>(elements),
equality_op_t{},
env);
});
}
using some_offset_types = nvbench::type_list<nvbench::int32_t>;
#ifdef TUNE_KeyT
using key_types = nvbench::type_list<TUNE_KeyT>;
#else // !defined(TUNE_KeyT)
using key_types = all_types;
#endif // TUNE_KeyT
#ifdef TUNE_ValueT
using value_types = nvbench::type_list<TUNE_ValueT>;
#else // !defined(TUNE_ValueT)
using value_types =
nvbench::type_list<int8_t,
int16_t,
int32_t,
int64_t
# if _CCCL_HAS_INT128()
,
int128_t
# endif
>;
#endif // TUNE_ValueT
NVBENCH_BENCH_TYPES(scan, NVBENCH_TYPE_AXES(key_types, value_types, some_offset_types))
.set_name("base")
.set_type_axes_names({"KeyT{ct}", "ValueT{ct}", "OffsetT{ct}"})
.add_int64_power_of_two_axis("Elements{io}", nvbench::range(16, 28, 4));

View File

@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: Copyright (c) 2011-2023, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
// This benchmark uses a custom operation, max_t, which is not known to CUB, so no operator specific optimizations and
// tunings are performed.
// Because CUB cannot detect this operator, we cannot add any tunings based on the results of this benchmark. Its main
// use is to detect regressions.
#include <nvbench_helper.cuh>
#define USES_LOOKAHEAD() 0
using op_t = max_t;
using scan_offset_types = offset_types;
#include "base.cuh"

View File

@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <cub/device/device_scan.cuh>
#include <cuda/__execution/determinism.h>
#include <cuda/__execution/require.h>
#include <cuda/std/__functional/invoke.h>
#include <nvbench_helper.cuh>
template <typename T, typename OffsetT>
static void exclusive_scan(nvbench::state& state, nvbench::type_list<T, OffsetT>)
try
{
using init_value_t = T;
using offset_t = OffsetT;
using scan_op_t = ::cuda::std::plus<T>;
const auto elements = static_cast<std::size_t>(state.get_int64("Elements{io}"));
thrust::device_vector<T> input = generate(elements);
thrust::device_vector<T> output(elements, thrust::no_init);
const T* d_input = thrust::raw_pointer_cast(input.data());
T* d_output = thrust::raw_pointer_cast(output.data());
state.add_element_count(elements);
state.add_global_memory_reads<T>(elements, "Size");
state.add_global_memory_writes<T>(elements);
caching_allocator_t alloc;
state.exec(nvbench::exec_tag::gpu | nvbench::exec_tag::no_batch, [&](nvbench::launch& launch) {
auto env = cub_bench_env(alloc, launch, cuda::execution::require(cuda::execution::determinism::run_to_run));
_CCCL_TRY_CUDA_API(
cub::DeviceScan::ExclusiveScan,
"ExclusiveScan failed",
d_input,
d_output,
scan_op_t{},
init_value_t{},
static_cast<offset_t>(elements),
env);
});
}
catch (const std::bad_alloc&)
{
state.skip("Skipping: out of memory.");
}
using types = nvbench::type_list<float, double>;
using offsets = nvbench::type_list<int64_t>;
NVBENCH_BENCH_TYPES(exclusive_scan, NVBENCH_TYPE_AXES(types, offsets))
.set_name("base")
.set_type_axes_names({"T{ct}", "OffsetT{ct}"})
.add_int64_power_of_two_axis("Elements{io}", nvbench::range(16, 28, 4));

View File

@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: Copyright (c) 2011-2023, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
// Tuning parameters found for signed integer types apply equally for unsigned integer types
#include <nvbench_helper.cuh>
// This benchmark tunes the old, non-lookahead scan implementation. Using it for benchmarking, will pick the lookahead
// implementation on SM100+, but it's better to use the sum.lookahead.cu benchmark instead, which uses a single OffsetT.
// %RANGE% TUNE_ITEMS ipt 7:24:1
// %RANGE% TUNE_THREADS tpb 128:1024:32
// %RANGE% TUNE_MAGIC_NS ns 0:2048:4
// %RANGE% TUNE_DELAY_CONSTRUCTOR_ID dcid 0:7:1
// %RANGE% TUNE_L2_WRITE_LATENCY_NS l2w 0:1200:5
// %RANGE% TUNE_TRANSPOSE trp 0:1:1
// %RANGE% TUNE_LOAD ld 0:1:1
#define USES_LOOKAHEAD() 0
using op_t = ::cuda::std::plus<>;
using scan_offset_types = offset_types;
#include "base.cuh"

View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// This tunes the lookahead implementation of scan, which is only available on SM100+. It has entirely different tuning
// parameters and is agnostic of the offset type. It is thus in a separate file, so we can continue to tune the old scan
// implementation on older hardware architectures.
#include <cuda/__cccl_config>
#if _CCCL_PP_COUNT(__CUDA_ARCH_LIST__) != 1
# warning "This benchmark does not support being compiled for multiple architectures. Disabling it."
#else // _CCCL_PP_COUNT(__CUDA_ARCH_LIST__) != 1
# if __CUDA_ARCH_LIST__ < 1000
// We don't care if clang-tidy can't parse this
# ifndef _CCCL_CLANG_TIDY_INVOKED
# warning "Lookahead scan requires at least sm_100. Disabling it."
# endif // !defined _CCCL_CLANG_TIDY_INVOKED
# else // __CUDA_ARCH_LIST__ < 1000
# if __cccl_ptx_isa < 860
# warning "Lookahead scan requires at least PTX ISA 8.6. Disabling it."
# else // if __cccl_ptx_isa < 860
# include <nvbench_helper.cuh>
// %RANGE% TUNE_NUM_REDUCE_SCAN_WARPS wrps 1:8:1
// %RANGE% TUNE_NUM_LOOKBACK_ITEMS lbi 1:8:1
// TODO(bgruber): find a good range and step width, items per thread should be coprime with 32 to avoid SMEM conflicts.
// Should we specify nominal items per thread instead?
// %RANGE% TUNE_ITEMS_PLUS_ONE ipt 8:256:8
// %RANGE% TUNE_LOOKBACK_STAGES lbs -2:2:1
// %RANGE% TUNE_BLOCK_IDX_STAGES bis -2:2:1
# define USES_LOOKAHEAD() 1
using op_t = ::cuda::std::plus<>;
using scan_offset_types = nvbench::type_list<int64_t>;
# include "base.cuh"
# endif // __cccl_ptx_isa < 860
# endif // __CUDA_ARCH_LIST__ < 1000
#endif // _CCCL_PP_COUNT(__CUDA_ARCH_LIST__) != 1

View File

@@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <cub/device/device_scan.cuh>
#ifndef USES_LOOKAHEAD
# define USES_LOOKAHEAD() 0
#endif
#if !TUNE_BASE
# if !USES_LOOKAHEAD()
# include <look_back_helper.cuh>
# endif // !USES_LOOKAHEAD()
template <typename AccumT>
struct policy_selector
{
[[nodiscard]] _CCCL_HOST_DEVICE constexpr auto operator()(cuda::compute_capability) const -> cub::ScanPolicy
{
# if USES_LOOKAHEAD()
return {cub::ScanAlgorithm::lookahead,
cub::ScanLookbackPolicy{},
cub::ScanLookaheadPolicy{
TUNE_NUM_REDUCE_SCAN_WARPS,
TUNE_ITEMS_PLUS_ONE - 1,
TUNE_NUM_LOOKBACK_ITEMS,
TUNE_LOOKBACK_STAGES,
TUNE_BLOCK_IDX_STAGES}};
# else
return cub::detail::scan::make_mem_scaled_lookback_scan_policy(
TUNE_THREADS,
TUNE_ITEMS,
int{sizeof(AccumT)},
TUNE_LOAD_ALGORITHM,
TUNE_LOAD_MODIFIER,
TUNE_STORE_ALGORITHM,
cub::BLOCK_SCAN_WARP_SCANS,
lookback_delay_policy);
# endif
}
};
#endif // !TUNE_BASE