[CCCL] 瘦身 + 补全: 移除 cudax/python/libcudacxx-tests 冗余文件, 新增 c2h 测试助手 + cmake 构建系统 + 8 个 CUDA thrust examples

变更摘要:
- 删除: cudax/ (783 files, 7.2M) — 实验性组件,竞赛不需要
- 删除: python/ (226 files, 2.0M) — Python 绑定,竞赛不需要
- 删除: libcudacxx/{test,benchmarks,codegen,cmake,share} (4432 files, 31M)
  保留: libcudacxx/include/ (1463 headers, cuda::std 编译依赖)
- 新增: c2h/ (27 files) — CUB Catch2 测试辅助头文件,编译 243 个测试必需
- 新增: cmake/ (29 files) — CCCL 原生 CMake 构建系统
- 新增: thrust/examples/cuda/ (7 files) + cpp_integration/ (1 file)
  async_reduce, custom_temporary_allocation, explicit_cuda_stream,
  global_device_vector, range_view, unwrap_pointer, wrap_pointer, device

结果: cccl_upstream 从 74M→35M (瘦身 53%), 核心内容 100% 保留:
  27/27 tuning headers, 78 benchmarks, 243 tests,
  60 thrust examples, 18 CUB examples, 全部编译头文件
This commit is contained in:
muh-bot
2026-08-03 12:39:26 +00:00
parent a2a5dd8f00
commit 24ef6a91b5
5439 changed files with 0 additions and 719516 deletions

View File

@@ -1,92 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
// Temporary nvcc workaround __host__ __device__ dtor conflict in cuda::buffer
#if defined(__CUDACC__)
# pragma nv_diag_suppress 20011
#endif
#include <cuda/memory_pool>
#include <cuda/std/cstddef>
#include <cuda/stream>
#include <cuda/experimental/__cuco/capacity.cuh>
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
#include <testing.cuh>
namespace cudax = cuda::experimental;
constexpr int empty_key = -1;
constexpr int empty_value = -1;
C2H_TEST("fixed_capacity_map dynamic capacity — capacity() reflects the valid capacity", "[capacity][dynamic]")
{
constexpr ::cuda::std::size_t requested = 1000;
using dyn_map_t = cudax::cuco::fixed_capacity_map<int, int>;
static_assert(dyn_map_t::capacity_v == ::cuda::std::dynamic_extent,
"capacity_v must be dynamic_extent for dynamic-capacity maps");
static_assert(dyn_map_t::ref_type::capacity_v == ::cuda::std::dynamic_extent,
"ref capacity_v must be dynamic_extent for dynamic maps");
const auto valid =
cudax::cuco::make_valid_capacity<dyn_map_t::probing_scheme_type, dyn_map_t::bucket_size>(requested);
::cuda::stream stream{::cuda::device_ref{0}};
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
dyn_map_t map{stream, mr, requested, cudax::cuco::empty_key{empty_key}, cudax::cuco::empty_value{empty_value}};
REQUIRE(map.capacity() == valid);
REQUIRE(map.capacity() >= requested);
}
C2H_TEST("fixed_capacity_map static capacity — valid capacity and capacity_v", "[capacity][static]")
{
// Double hashing rounds a requested slot count up to a prime-cycle capacity, so the valid capacity
// must be computed from the probing scheme and bucket size before it can name a static map type.
using probing = cudax::cuco::double_hashing<1, cudax::cuco::hash<int>>;
[[maybe_unused]] constexpr int bucket = 1;
constexpr ::cuda::std::size_t requested = 1000;
constexpr auto valid = cudax::cuco::make_valid_capacity<probing, bucket>(requested);
static_assert(valid > requested, "1000 is not a valid double-hashing capacity; it rounds up");
using smap_t =
cudax::cuco::fixed_capacity_map<int, int, valid, ::cuda::thread_scope_device, ::cuda::std::equal_to<int>, probing, 1>;
static_assert(smap_t::capacity_v == valid, "the map type carries the valid capacity, not the request");
static_assert(smap_t::ref_type::capacity_v == valid, "the ref carries the same valid capacity");
::cuda::stream stream{::cuda::device_ref{0}};
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
smap_t map{stream, mr, cudax::cuco::empty_key{empty_key}, cudax::cuco::empty_value{empty_value}};
REQUIRE(map.capacity() == valid);
}
C2H_TEST("fixed_capacity_map dynamic extent — load factor constructor", "[capacity][dynamic][load_factor]")
{
constexpr int num_elements = 500;
constexpr double load_factor = 0.5;
::cuda::stream stream{::cuda::device_ref{0}};
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
cudax::cuco::fixed_capacity_map<int, int> map{
stream,
mr,
static_cast<::cuda::std::size_t>(num_elements),
load_factor,
cudax::cuco::empty_key{empty_key},
cudax::cuco::empty_value{empty_value}};
// With load_factor = 0.5 and 500 elements, capacity should be >= 1000
REQUIRE(map.capacity() >= static_cast<::cuda::std::size_t>(num_elements / load_factor));
}

View File

@@ -1,172 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
// Temporary nvcc workaround __host__ __device__ dtor conflict in cuda::buffer
#if defined(__CUDACC__)
# pragma nv_diag_suppress 20011
#endif // defined(__CUDACC__)
#include <thrust/execution_policy.h>
#include <thrust/logical.h>
#include <cuda/buffer>
#include <cuda/iterator>
#include <cuda/memory_pool>
#include <cuda/std/cstddef>
#include <cuda/std/cstdint>
#include <cuda/std/type_traits>
#include <cuda/stream>
#include <cuda/experimental/__cuco/capacity.cuh>
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
#include <testing.cuh>
namespace cudax = cuda::experimental;
template <int N>
using int_c = ::cuda::std::integral_constant<int, N>;
using key_types = c2h::type_list<::cuda::std::int32_t, ::cuda::std::int64_t>;
using cg_sizes = c2h::type_list<int_c<1>, int_c<2>>;
using bucket_sizes = c2h::type_list<int_c<1>, int_c<2>>;
using probing_kinds = c2h::type_list<int_c<0>, int_c<1>>; // 0 = linear probing, 1 = double hashing
// Payloads are offset from their key so a bug that returns the key instead of the mapped value is caught.
constexpr int payload_offset = 7;
template <class Pair>
struct iota_pair
{
__host__ __device__ Pair operator()(typename Pair::first_type i) const noexcept
{
return Pair{i, static_cast<typename Pair::second_type>(i + payload_offset)};
}
};
// Present keys [0, num_keys) find their payload (key + payload_offset) and absent keys
// [num_keys, ...) find the empty value sentinel.
template <class Key>
struct match_found
{
const Key* found;
int num_keys;
Key sentinel;
__device__ bool operator()(int i) const noexcept
{
return (i < num_keys) ? (found[i] == static_cast<Key>(i) + payload_offset) : (found[i] == sentinel);
}
};
template <class Key>
struct is_not_sentinel
{
Key sentinel;
__device__ bool operator()(Key value) const noexcept
{
return value != sentinel;
}
};
struct is_even
{
__device__ bool operator()(int i) const noexcept
{
return (i % 2) == 0;
}
};
// find_if queries only even keys; odd positions resolve to the empty value sentinel
template <class Key>
struct match_find_if
{
const Key* found;
Key sentinel;
__device__ bool operator()(int i) const noexcept
{
return ((i % 2) == 0) ? (found[i] == static_cast<Key>(i) + payload_offset) : (found[i] == sentinel);
}
};
C2H_TEST("fixed_capacity_map find", "[container]", key_types, cg_sizes, bucket_sizes, probing_kinds)
{
using key_type = c2h::get<0, TestType>;
[[maybe_unused]] constexpr int cg_size = c2h::get<1, TestType>::value;
[[maybe_unused]] constexpr int bucket_size = c2h::get<2, TestType>::value;
[[maybe_unused]] constexpr int probing = c2h::get<3, TestType>::value;
using hasher = cudax::cuco::hash<key_type>;
using probing_type =
::cuda::std::conditional_t<probing == 0,
cudax::cuco::linear_probing<cg_size, hasher>,
cudax::cuco::double_hashing<cg_size, hasher>>;
using map_type = cudax::cuco::fixed_capacity_map<
key_type,
key_type,
::cuda::std::dynamic_extent,
::cuda::thread_scope_device,
::cuda::std::equal_to<key_type>,
probing_type,
bucket_size>;
using value_type = typename map_type::value_type;
constexpr int num_keys = 400;
constexpr key_type sentinel = key_type{-1};
::cuda::stream stream{::cuda::device_ref{0}};
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
map_type map{stream,
mr,
static_cast<::cuda::std::size_t>(num_keys * 2),
cudax::cuco::empty_key{sentinel},
cudax::cuco::empty_value{sentinel}};
auto pairs = cuda::transform_iterator(cuda::counting_iterator<key_type>{0}, iota_pair<value_type>{});
map.insert(stream, pairs, pairs + num_keys);
// Find present keys [0, num_keys) and absent keys [num_keys, 2 * num_keys)
auto found = ::cuda::make_buffer<key_type>(stream, mr, 2 * num_keys, key_type{0});
map.find(stream, cuda::counting_iterator<key_type>{0}, cuda::counting_iterator<key_type>{2 * num_keys}, found.begin());
REQUIRE(::thrust::all_of(
::thrust::cuda::par.on(stream.get()),
cuda::counting_iterator<int>{0},
cuda::counting_iterator<int>{2 * num_keys},
match_found<key_type>{found.data(), num_keys, sentinel}));
// find_if only queries even keys; odd positions resolve to the empty value sentinel
auto found_if = ::cuda::make_buffer<key_type>(stream, mr, num_keys, key_type{0});
map.find_if(stream,
cuda::counting_iterator<key_type>{0},
cuda::counting_iterator<key_type>{num_keys},
cuda::counting_iterator<int>{0},
is_even{},
found_if.begin());
REQUIRE(::thrust::all_of(
::thrust::cuda::par.on(stream.get()),
cuda::counting_iterator<int>{0},
cuda::counting_iterator<int>{num_keys},
match_find_if<key_type>{found_if.data(), sentinel}));
// After clear the map is empty, so every key resolves to the empty value sentinel
map.clear(stream);
auto cleared = ::cuda::make_buffer<key_type>(stream, mr, num_keys, key_type{0});
map.find(stream, cuda::counting_iterator<key_type>{0}, cuda::counting_iterator<key_type>{num_keys}, cleared.begin());
REQUIRE(::thrust::none_of(
::thrust::cuda::par.on(stream.get()),
cleared.data(),
cleared.data() + num_keys,
is_not_sentinel<key_type>{sentinel}));
}

View File

@@ -1,150 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
// Temporary nvcc workaround __host__ __device__ dtor conflict in cuda::buffer
#if defined(__CUDACC__)
# pragma nv_diag_suppress 20011
#endif
#include <thrust/execution_policy.h>
#include <thrust/logical.h>
#include <cuda/buffer>
#include <cuda/iterator>
#include <cuda/memory_pool>
#include <cuda/std/__bit/has_single_bit.h>
#include <cuda/std/cstddef>
#include <cuda/std/cstdint>
#include <cuda/std/limits>
#include <cuda/std/type_traits>
#include <cuda/stream>
#include <cuda/experimental/__cuco/capacity.cuh>
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
#include <testing.cuh>
namespace cudax = cuda::experimental;
template <int _N>
using _int_c = ::cuda::std::integral_constant<int, _N>;
using key_types =
c2h::type_list<::cuda::std::uint8_t, ::cuda::std::uint16_t, ::cuda::std::int32_t, ::cuda::std::int64_t>;
using cg_sizes = c2h::type_list<_int_c<1>, _int_c<2>>;
using bucket_sizes = c2h::type_list<_int_c<1>, _int_c<2>>;
using probing_kinds = c2h::type_list<_int_c<0>, _int_c<1>>; // 0 = linear probing, 1 = double hashing
template <class _Pair>
struct iota_pair
{
__host__ __device__ _Pair operator()(typename _Pair::first_type __i) const noexcept
{
return _Pair{__i, __i};
}
};
// Present keys [0, num_keys) are found, absent keys [num_keys, ...) are not
struct match_expected
{
const int* found;
int num_keys;
__device__ bool operator()(int i) const noexcept
{
return static_cast<bool>(found[i]) == (i < num_keys);
}
};
struct is_nonzero
{
__device__ bool operator()(int v) const noexcept
{
return v != 0;
}
};
C2H_TEST("fixed_capacity_map insert and contains", "[container]", key_types, cg_sizes, bucket_sizes, probing_kinds)
{
using key_type = c2h::get<0, TestType>;
[[maybe_unused]] constexpr int cg_size = c2h::get<1, TestType>::value;
[[maybe_unused]] constexpr int bucket_size = c2h::get<2, TestType>::value;
[[maybe_unused]] constexpr int probing = c2h::get<3, TestType>::value;
using hasher = cudax::cuco::hash<key_type>;
using probing_type =
::cuda::std::conditional_t<probing == 0,
cudax::cuco::linear_probing<cg_size, hasher>,
cudax::cuco::double_hashing<cg_size, hasher>>;
using map_type = cudax::cuco::fixed_capacity_map<
key_type,
key_type,
::cuda::std::dynamic_extent,
::cuda::thread_scope_device,
::cuda::std::equal_to<key_type>,
probing_type,
bucket_size>;
using value_type = typename map_type::value_type;
constexpr int num_keys = (::cuda::std::numeric_limits<key_type>::max() > 800) ? 400 : 100;
::cuda::stream stream{::cuda::device_ref{0}};
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
map_type map{stream,
mr,
static_cast<::cuda::std::size_t>(num_keys * 2),
cudax::cuco::empty_key{static_cast<key_type>(-1)},
cudax::cuco::empty_value{static_cast<key_type>(-1)}};
auto __pairs = cuda::transform_iterator(cuda::counting_iterator<key_type>{0}, iota_pair<value_type>{});
map.insert(stream, __pairs, __pairs + num_keys);
// Query present keys [0, num_keys) and absent keys [num_keys, 2 * num_keys)
auto found = ::cuda::make_buffer<int>(stream, mr, 2 * num_keys, 0);
map.contains(
stream, cuda::counting_iterator<key_type>{0}, cuda::counting_iterator<key_type>{2 * num_keys}, found.begin());
REQUIRE(::thrust::all_of(
::thrust::cuda::par.on(stream.get()),
cuda::counting_iterator<int>{0},
cuda::counting_iterator<int>{2 * num_keys},
match_expected{found.data(), num_keys}));
// After clear the map is empty, so none of the previously inserted keys are found
map.clear(stream);
auto cleared = ::cuda::make_buffer<int>(stream, mr, num_keys, 1);
map.contains(
stream, cuda::counting_iterator<key_type>{0}, cuda::counting_iterator<key_type>{num_keys}, cleared.begin());
REQUIRE(
::thrust::none_of(::thrust::cuda::par.on(stream.get()), cleared.data(), cleared.data() + num_keys, is_nonzero{}));
}
template <class _Key, class _Tp>
using __map_of = cudax::cuco::fixed_capacity_map<
_Key,
_Tp,
::cuda::std::dynamic_extent,
::cuda::thread_scope_device,
::cuda::std::equal_to<_Key>,
cudax::cuco::linear_probing<1, cudax::cuco::hash<_Key>>,
1>;
C2H_TEST("fixed_capacity_map key and slot size constraint", "[container]")
{
static_assert(sizeof(typename __map_of<::cuda::std::uint8_t, ::cuda::std::uint8_t>::value_type) == 2,
"<uint8_t, uint8_t> is a valid 2-byte slot");
static_assert(sizeof(typename __map_of<::cuda::std::uint16_t, ::cuda::std::uint16_t>::value_type) == 4,
"<uint16_t, uint16_t> is a valid 4-byte slot");
static_assert(sizeof(typename __map_of<::cuda::std::uint32_t, ::cuda::std::uint32_t>::value_type) == 8,
"<uint32_t, uint32_t> is a valid 8-byte slot");
static_assert(sizeof(typename __map_of<::cuda::std::uint8_t, ::cuda::std::uint32_t>::value_type) == 8,
"a mismatched <uint8_t, uint32_t> slot is a valid 8-byte slot");
}

View File

@@ -1,80 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
// Temporary nvcc workaround __host__ __device__ dtor conflict in cuda::buffer
#if defined(__CUDACC__)
# pragma nv_diag_suppress 20011
#endif
#include <thrust/execution_policy.h>
#include <thrust/logical.h>
#include <cuda/buffer>
#include <cuda/iterator>
#include <cuda/memory_pool>
#include <cuda/std/cstddef>
#include <cuda/stream>
#include <cuda/experimental/__cuco/capacity.cuh>
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
#include <testing.cuh>
namespace cudax = cuda::experimental;
constexpr int empty_key = -1;
constexpr int empty_value = -1;
// Constructing a map with an erased-key sentinel must keep insert and contains correct (no key ever
// collides with the empty or erased sentinels).
template <class _Pair>
struct iota_pair
{
__host__ __device__ _Pair operator()(typename _Pair::first_type __i) const noexcept
{
return _Pair{__i, __i};
}
};
struct is_nonzero
{
__device__ bool operator()(int v) const noexcept
{
return v != 0;
}
};
C2H_TEST("fixed_capacity_map — empty and erased key sentinels", "[sentinel]")
{
constexpr int erased_sentinel = -2;
constexpr int num_keys = 256;
using probing = cudax::cuco::linear_probing<1, cudax::cuco::hash<int>>;
[[maybe_unused]] constexpr int bucket = 1;
[[maybe_unused]] constexpr ::cuda::std::size_t capacity =
cudax::cuco::make_valid_capacity<probing, bucket>(::cuda::std::size_t{num_keys} * 2);
using map_type = cudax::cuco::fixed_capacity_map<int, int, capacity>;
::cuda::stream stream{::cuda::device_ref{0}};
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
map_type map{stream,
mr,
cudax::cuco::empty_key{empty_key},
cudax::cuco::empty_value{empty_value},
cudax::cuco::erased_key{erased_sentinel}};
auto __pairs = cuda::transform_iterator(cuda::counting_iterator<int>{0}, iota_pair<::cuda::std::pair<int, int>>{});
map.insert(stream, __pairs, __pairs + num_keys);
auto found = ::cuda::make_buffer<int>(stream, mr, num_keys, 0);
map.contains(stream, cuda::counting_iterator<int>{0}, cuda::counting_iterator<int>{num_keys}, found.begin());
REQUIRE(::thrust::all_of(::thrust::cuda::par.on(stream.get()), found.data(), found.data() + num_keys, is_nonzero{}));
}

View File

@@ -1,26 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
// Static error check: a key type whose size is not a power of two is rejected. `char3` is 3 bytes,
// which falls between the supported key widths (1, 2, 4, 8 bytes).
#include <cuda/std/cstdint>
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
namespace cudax = cuda::experimental;
int main()
{
using map_t = cudax::cuco::fixed_capacity_map<char3, ::cuda::std::uint8_t>;
// expected-error {{"key_type size must be a power of two"}}
static_assert(sizeof(typename map_t::ref_type) > 0);
return 0;
}

View File

@@ -1,137 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
// Inserts and lookups must stay correct when the slot storage is under-aligned for the packed
// atomic CAS, which forces the insert path onto the non-packed fallback.
// Temporary nvcc workaround __host__ __device__ dtor conflict in cuda::buffer
#if defined(__CUDACC__)
# pragma nv_diag_suppress 20011
#endif
#include <cuda/__memory/align_up.h>
#include <cuda/std/cstddef>
#include <cuda/std/cstdint>
#include <cuda/std/functional>
#include <cuda/experimental/__cuco/capacity.cuh>
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
#include <testing.cuh>
namespace cudax = cuda::experimental;
template <class ValueType>
__global__ void fill_sentinel_kernel(ValueType* slots, int cap, ValueType sentinel)
{
const int i = static_cast<int>(blockIdx.x * blockDim.x + threadIdx.x);
if (i < cap)
{
slots[i] = sentinel;
}
}
template <class RefType, class Key>
__global__ void insert_kernel(RefType ref, int num_keys)
{
const int i = static_cast<int>(blockIdx.x * blockDim.x + threadIdx.x);
if (i < num_keys)
{
[[maybe_unused]] const bool inserted =
ref.insert(typename RefType::value_type{static_cast<Key>(i), static_cast<Key>(i)});
}
}
template <class RefType, class Key>
__global__ void contains_kernel(RefType ref, int num_probes, int* out)
{
const int i = static_cast<int>(blockIdx.x * blockDim.x + threadIdx.x);
if (i < num_probes)
{
out[i] = ref.contains(static_cast<Key>(i)) ? 1 : 0;
}
}
template <class Key, class Mapped>
void run_misaligned_external_storage()
{
using probing_type = cudax::cuco::linear_probing<1, cudax::cuco::hash<Key>>;
constexpr int bucket_size = 1;
using map_type = cudax::cuco::fixed_capacity_map<
Key,
Mapped,
::cuda::std::dynamic_extent,
::cuda::thread_scope_device,
::cuda::std::equal_to<Key>,
probing_type,
bucket_size>;
using ref_type = typename map_type::ref_type;
using value_type = typename map_type::value_type;
using span_type = typename ref_type::storage_span_type;
constexpr int num_keys = 200;
const auto capacity =
cudax::cuco::make_valid_capacity<probing_type, bucket_size>(static_cast<::cuda::std::size_t>(num_keys) * 2);
const Key empty_k = static_cast<Key>(-1);
const Mapped empty_v = static_cast<Mapped>(-1);
const ::cuda::std::size_t nbytes = (capacity + 2) * sizeof(value_type);
void* raw = nullptr;
REQUIRE(cudaMalloc(&raw, nbytes) == cudaSuccess);
auto* const aligned_raw = ::cuda::align_up(static_cast<::cuda::std::byte*>(raw), sizeof(value_type));
auto* const slots = reinterpret_cast<value_type*>(aligned_raw + alignof(value_type));
const auto slots_addr = reinterpret_cast<::cuda::std::uintptr_t>(slots);
REQUIRE(slots_addr % alignof(value_type) == 0);
REQUIRE(slots_addr % sizeof(value_type) != 0);
constexpr int block = 128;
const int fill_grid = static_cast<int>((capacity + block - 1) / block);
fill_sentinel_kernel<value_type>
<<<fill_grid, block>>>(slots, static_cast<int>(capacity), value_type{empty_k, empty_v});
REQUIRE(cudaGetLastError() == cudaSuccess);
REQUIRE(cudaDeviceSynchronize() == cudaSuccess);
ref_type ref{cudax::cuco::empty_key<Key>{empty_k},
cudax::cuco::empty_value<Mapped>{empty_v},
::cuda::std::equal_to<Key>{},
probing_type{},
span_type{slots, capacity}};
insert_kernel<ref_type, Key><<<(num_keys + block - 1) / block, block>>>(ref, num_keys);
REQUIRE(cudaGetLastError() == cudaSuccess);
REQUIRE(cudaDeviceSynchronize() == cudaSuccess);
constexpr int num_probes = 2 * num_keys;
int* d_out = nullptr;
REQUIRE(cudaMalloc(&d_out, sizeof(int) * num_probes) == cudaSuccess);
contains_kernel<ref_type, Key><<<(num_probes + block - 1) / block, block>>>(ref, num_probes, d_out);
REQUIRE(cudaGetLastError() == cudaSuccess);
int h_out[num_probes];
REQUIRE(cudaMemcpy(h_out, d_out, sizeof(int) * num_probes, cudaMemcpyDeviceToHost) == cudaSuccess);
REQUIRE(cudaDeviceSynchronize() == cudaSuccess);
for (int i = 0; i < num_probes; ++i)
{
REQUIRE(static_cast<bool>(h_out[i]) == (i < num_keys));
}
REQUIRE(cudaFree(d_out) == cudaSuccess);
REQUIRE(cudaFree(raw) == cudaSuccess);
}
C2H_TEST("fixed_capacity_map insert and contains over misaligned external storage", "[container]")
{
run_misaligned_external_storage<::cuda::std::int32_t, ::cuda::std::int32_t>();
run_misaligned_external_storage<::cuda::std::uint16_t, ::cuda::std::uint16_t>();
}

View File

@@ -1,102 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
// Temporary nvcc workaround __host__ __device__ dtor conflict in cuda::buffer
#if defined(__CUDACC__)
# pragma nv_diag_suppress 20011
#endif
#include <thrust/execution_policy.h>
#include <thrust/logical.h>
#include <cuda/buffer>
#include <cuda/iterator>
#include <cuda/memory>
#include <cuda/memory_pool>
#include <cuda/std/cstddef>
#include <cuda/stream>
#include <cuda/experimental/__cuco/capacity.cuh>
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
#include <testing.cuh>
namespace cudax = cuda::experimental;
constexpr int empty_key = -1;
constexpr int empty_value = -1;
// A static-capacity map with cg_size 1 so the test can use scalar device inserts.
using probing = cudax::cuco::linear_probing<1, cudax::cuco::hash<int>>;
inline constexpr int bucket = 1;
inline constexpr ::cuda::std::size_t static_capacity =
cudax::cuco::make_valid_capacity<probing, bucket>(::cuda::std::size_t{512});
using fixed_capacity_map_512_type = cudax::cuco::
fixed_capacity_map<int, int, static_capacity, ::cuda::thread_scope_device, ::cuda::std::equal_to<int>, probing>;
template <class Pair>
struct iota_pair
{
__host__ __device__ Pair operator()(typename Pair::first_type key) const noexcept
{
return Pair{key, key};
}
};
struct is_nonzero
{
__device__ bool operator()(int v) const noexcept
{
return v != 0;
}
};
// Demonstrates compile-time __shared__ sizing via ref_type::capacity_v.
template <class PairIt>
__global__ void insert_shmem_kernel(fixed_capacity_map_512_type::ref_type global_ref, PairIt pairs, int num_keys)
{
using ref_t = fixed_capacity_map_512_type::ref_type;
static_assert(ref_t::capacity_v != ::cuda::std::dynamic_extent,
"capacity_v must be a compile-time constant for static extents");
__shared__ ::cuda::__uninitialized_array<ref_t::value_type, ref_t::capacity_v> smem;
const auto idx = static_cast<int>(blockIdx.x) * blockDim.x + threadIdx.x;
smem[threadIdx.x] = (idx < num_keys) ? pairs[idx] : ref_t::value_type{};
__syncthreads();
if (idx < num_keys)
{
global_ref.insert(smem[threadIdx.x]);
}
}
C2H_TEST("fixed_capacity_map static extent — shared memory sizing via capacity_v", "[shmem][static]")
{
constexpr int num_keys = 64;
::cuda::stream stream{::cuda::device_ref{0}};
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
fixed_capacity_map_512_type map{stream, mr, cudax::cuco::empty_key{empty_key}, cudax::cuco::empty_value{empty_value}};
const int block_size = 128;
const int grid_size = (num_keys + block_size - 1) / block_size;
insert_shmem_kernel<<<grid_size, block_size, 0, stream.get()>>>(
map.ref(),
cuda::transform_iterator(cuda::counting_iterator<int>{0}, iota_pair<fixed_capacity_map_512_type::value_type>{}),
num_keys);
REQUIRE(cudaGetLastError() == cudaSuccess);
// Verify the insertions actually landed in the global map
auto found = ::cuda::make_buffer<int>(stream, mr, num_keys, 0);
map.contains(stream, cuda::counting_iterator<int>{0}, cuda::counting_iterator<int>{num_keys}, found.begin());
REQUIRE(::thrust::all_of(::thrust::cuda::par.on(stream.get()), found.data(), found.data() + num_keys, is_nonzero{}));
}

View File

@@ -1,32 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
// Static error check: a slot (key/payload pair) whose size is not a power of two is rejected. A
// 1-byte key with a 5-byte payload forms a 6-byte slot, which the packed atomic update cannot
// address even though the key size alone is a valid power of two.
#include <cuda/std/cstdint>
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
namespace cudax = cuda::experimental;
struct byte5
{
unsigned char a, b, c, d, e;
};
int main()
{
using map_t = cudax::cuco::fixed_capacity_map<::cuda::std::uint8_t, byte5>;
// expected-error {{"value_type size must be a power of two"}}
static_assert(sizeof(typename map_t::ref_type) > 0);
return 0;
}

View File

@@ -1,341 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <thrust/execution_policy.h>
#include <thrust/sequence.h>
#include <cuda/buffer>
#include <cuda/iterator>
#include <cuda/memory_pool>
#include <cuda/std/cmath>
#include <cuda/std/cstddef>
#include <cuda/std/span>
#include <cuda/std/type_traits>
#include <cuda/stream>
#include <cuda/experimental/__cuco/hash_functions.cuh>
#include <cuda/experimental/__cuco/hyperloglog.cuh>
#include <cuda/experimental/__cuco/hyperloglog_ref.cuh>
#include <cooperative_groups.h>
#include <testing.cuh>
#include <c2h/catch2_test_helper.h>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
namespace cudax = cuda::experimental;
template <typename Ref, typename InputIt, typename OutputIt>
__global__ void estimate_kernel(typename Ref::sketch_size_kb sketch_size_kb, InputIt in, size_t n, OutputIt out)
{
extern __shared__ cuda::std::byte local_sketch[];
const auto block = cooperative_groups::this_thread_block();
// only a single block computes the estimate
if (block.group_index().x == 0)
{
Ref estimator(cuda::std::span(local_sketch, Ref::sketch_bytes(sketch_size_kb)));
estimator.clear(block);
block.sync();
for (int i = static_cast<int>(block.thread_rank()); i < n; i += static_cast<int>(block.num_threads()))
{
estimator.add(*(in + i));
}
block.sync();
static_assert(cuda::std::is_same_v<decltype(estimator.estimate(block)), double>);
const auto estimate = estimator.estimate(block);
if (block.thread_rank() == 0)
{
*out = estimate;
}
}
}
template <typename Ref>
__global__ void merge_kernel(Ref destination, const Ref source)
{
const auto block = cooperative_groups::this_thread_block();
destination.merge(block, source);
}
using test_types = c2h::type_list<int32_t, int64_t>;
// Maps index i to i / repeats, yielding `repeats` duplicates of each value
struct scaled_index
{
std::size_t repeats;
__device__ int operator()(std::size_t i) const noexcept
{
return static_cast<int>(i / repeats);
}
};
C2H_TEST("HyperLogLog device ref", "[hyperloglog]", test_types)
{
using T = c2h::get<0, TestType>;
using estimator_type = cudax::cuco::hyperloglog<T>;
// Test parameters
const std::size_t num_items_pow2 = GENERATE(25, 26, 28);
const int hll_precision = GENERATE(8, 10, 12, 13);
const typename estimator_type::sketch_size_kb sketch_size_kb(4.0 * (1ull << hll_precision) / 1024.0);
const std::size_t num_items = 1ull << num_items_pow2;
CAPTURE(num_items, hll_precision, sketch_size_kb);
::cuda::stream stream{::cuda::device_ref{0}};
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
// Generate `num_items` distinct items
auto items = ::cuda::make_buffer<T>(stream, mr, num_items, ::cuda::no_init);
thrust::sequence(thrust::cuda::par_nosync.on(stream.get()), items.begin(), items.end(), T{0});
// Initialize the estimator
estimator_type estimator{stream, mr, sketch_size_kb};
STATIC_REQUIRE(cuda::std::is_same_v<decltype(estimator.estimate(stream)), double>);
STATIC_REQUIRE(cuda::std::is_same_v<decltype(estimator.ref().estimate(stream)), double>);
// Add all items to the estimator
estimator.add(stream, items.begin(), items.end());
const auto host_estimate = estimator.estimate(stream);
auto device_estimate = cuda::make_buffer<double>(stream, mr, 1, cuda::no_init);
estimate_kernel<typename estimator_type::template ref_type<cuda::thread_scope_block>>
<<<1, 512, estimator.sketch_bytes(), stream.get()>>>(
sketch_size_kb, items.begin(), num_items, device_estimate.begin());
REQUIRE(cudaGetLastError() == cudaSuccess);
double device_estimate_value{};
REQUIRE_CUDART(cudaMemcpyAsync(
&device_estimate_value, device_estimate.data(), sizeof(double), cudaMemcpyDeviceToHost, stream.get()));
stream.sync();
REQUIRE_THAT(device_estimate_value, Catch::Matchers::WithinRel(host_estimate, 1e-10));
}
C2H_TEST("HyperLogLog device ref merge", "[hyperloglog]")
{
using T = int32_t;
using estimator_type = cudax::cuco::hyperloglog<T>;
constexpr std::size_t num_items = 1 << 20;
const estimator_type::precision precision{8};
::cuda::stream stream{::cuda::device_ref{0}};
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
estimator_type source{stream, mr, precision};
const auto first = ::cuda::counting_iterator<T>{0};
source.add(stream, first, first + num_items);
const auto source_estimate = source.estimate(stream);
estimator_type destination{stream, mr, precision};
merge_kernel<<<1, 128, 0, stream.get()>>>(destination.ref(), source.ref());
REQUIRE(cudaGetLastError() == cudaSuccess);
REQUIRE(destination.estimate(stream) == source_estimate);
REQUIRE(source.estimate(stream) == source_estimate);
}
C2H_TEST("HyperLogLog unique sequence", "[hyperloglog]", test_types)
{
using T = c2h::get<0, TestType>;
using estimator_type = cudax::cuco::hyperloglog<T>;
const std::size_t num_items_pow2 = GENERATE(25, 26, 28);
const int hll_precision = GENERATE(8, 10, 12, 13, 18);
const typename estimator_type::sketch_size_kb sketch_size_kb(4.0 * (1ull << hll_precision) / 1024.0);
const std::size_t num_items = 1ull << num_items_pow2;
CAPTURE(num_items, hll_precision, sketch_size_kb);
// This factor determines the error threshold for passing the test
constexpr double tolerance_factor = 2.5;
// RSD for a given precision is given by the following formula
const double relative_standard_deviation = 1.04 / std::sqrt(static_cast<double>(1ull << hll_precision));
::cuda::stream stream{::cuda::device_ref{0}};
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
// Generate `num_items` distinct items
auto items = ::cuda::make_buffer<T>(stream, mr, num_items, ::cuda::no_init);
thrust::sequence(thrust::cuda::par_nosync.on(stream.get()), items.begin(), items.end(), T{0});
// Initialize the estimator
estimator_type estimator{stream, mr, sketch_size_kb};
REQUIRE(estimator.estimate(stream) == 0);
// Add all items to the estimator
estimator.add(stream, items.begin(), items.end());
const auto estimate = estimator.estimate(stream);
// Adding the same items again should not affect the result
estimator.add(stream, items.begin(), items.begin() + num_items / 2);
REQUIRE(estimator.estimate(stream) == estimate);
// Adding the same items again (might use shared memory code path) should not affect the result
auto* ptr = items.data();
estimator.add(stream, ptr, ptr + num_items / 2);
REQUIRE(estimator.estimate(stream) == estimate);
// Clearing the estimator should reset the estimate
estimator.clear(stream);
REQUIRE(estimator.estimate(stream) == 0);
const double relative_error = std::abs((static_cast<double>(estimate) / static_cast<double>(num_items)) - 1.0);
// Check if the error is acceptable
REQUIRE(relative_error < tolerance_factor * relative_standard_deviation);
}
//! @brief The following unit tests mimic Spark's unit tests which can be found here:
//! https://github.com/apache/spark/blob/d10dbaa31a44878df5c7e144f111e18261346531/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/aggregate/HyperLogLogPlusPlusSuite.scala
//!
C2H_TEST("HyperLogLog Spark parity deterministic", "[hyperloglog]")
{
using T = int;
using estimator_type = cudax::cuco::hyperloglog<T>;
constexpr std::size_t repeats = 10;
// This factor determines the error threshold for passing the test
constexpr double tolerance_factor = 3.0;
const auto num_items = GENERATE(100, 500, 1000, 5000, 10000, 50000, 100000, 500000, 1000000);
const auto standard_deviation = GENERATE(0.1, 0.05, 0.025, 0.01, 0.005, 0.0025);
const auto expected_hll_precision =
std::max(static_cast<int32_t>(4),
static_cast<int32_t>(std::ceil(2.0 * std::log(1.106 / standard_deviation) / std::log(2.0))));
const auto expected_sketch_bytes = 4 * (1ull << expected_hll_precision);
CAPTURE(num_items, standard_deviation, expected_hll_precision, expected_sketch_bytes);
const estimator_type::standard_deviation sd(standard_deviation);
const estimator_type::sketch_size_kb sb(expected_sketch_bytes / 1024.0);
// Validate sketch size calculation
REQUIRE(estimator_type::sketch_bytes(sd) >= 64);
REQUIRE(estimator_type::sketch_bytes(sd) == expected_sketch_bytes);
REQUIRE(estimator_type::sketch_bytes(sd) == estimator_type::sketch_bytes(sb));
auto items_begin = cuda::transform_iterator(cuda::counting_iterator<std::size_t>{0}, scaled_index{repeats});
::cuda::stream stream{::cuda::device_ref{0}};
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
estimator_type estimator{stream, mr, sd};
REQUIRE(estimator.estimate(stream) == 0);
// Add all items to the estimator
estimator.add(stream, items_begin, items_begin + num_items);
// Spark rounds the floating-point estimate to the nearest integer with Math.round.
const auto estimate = cuda::std::round(estimator.estimate(stream));
const double expected_count = static_cast<double>(num_items) / static_cast<double>(repeats);
const double relative_error = std::abs((static_cast<double>(estimate) / expected_count) - 1.0);
// RSD for a given precision is given by the following formula
const double expected_standard_deviation = 1.04 / std::sqrt(static_cast<double>(1ull << expected_hll_precision));
// Check if the error is acceptable
REQUIRE(relative_error < expected_standard_deviation * tolerance_factor);
}
C2H_TEST("HyperLogLog precision constructor", "[hyperloglog]")
{
using T = int;
using estimator_type = cudax::cuco::hyperloglog<T>;
const auto precision_value = GENERATE(4, 6, 8, 12, 16, 18);
const estimator_type::precision precision(precision_value);
const auto expected_sketch_bytes = 4 * (1ull << precision_value);
CAPTURE(precision_value, expected_sketch_bytes);
REQUIRE(estimator_type::sketch_bytes(precision) == expected_sketch_bytes);
::cuda::stream stream{::cuda::device_ref{0}};
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
estimator_type estimator{stream, mr, precision};
REQUIRE(estimator.sketch_bytes() == expected_sketch_bytes);
REQUIRE(estimator.estimate(stream) == 0);
}
C2H_TEST("HyperLogLog estimate preserves fractional cardinality", "[hyperloglog]")
{
using estimator_type = cudax::cuco::hyperloglog<int32_t>;
cuda::stream stream{cuda::device_ref{0}};
auto mr = cuda::device_default_memory_pool(cuda::device_ref{0});
estimator_type estimator{stream, mr, estimator_type::precision{8}};
const auto item = cuda::counting_iterator<int32_t>{0};
estimator.add(stream, item, item + 1);
const auto estimate = estimator.estimate(stream);
REQUIRE(estimate > 1.0);
REQUIRE(estimate < 2.0);
}
C2H_TEST("HyperLogLog ref validates sketch storage size", "[hyperloglog]")
{
using ref_type = cudax::cuco::hyperloglog_ref<int32_t>;
alignas(ref_type::sketch_alignment()) cuda::std::byte undersized_storage[32]{};
REQUIRE_THROWS_WITH(ref_type{cuda::std::span<cuda::std::byte>{undersized_storage}},
"Minimum required sketch size is 0.0625KB or 64B");
alignas(ref_type::sketch_alignment()) cuda::std::byte rounded_storage[96]{};
const ref_type ref{cuda::std::span<cuda::std::byte>{rounded_storage}};
REQUIRE(ref.sketch_bytes() == 64);
}
#if _CCCL_CTK_AT_LEAST(12, 9) // Pinned memory resource is only supported with CTK 12.9 and later
C2H_TEST("Hyperloglog estimate works with pinned memory pool", "[hyperloglog]")
{
using T = int32_t;
using estimator_type = cudax::cuco::hyperloglog<T>;
const std::size_t num_items = 1 << 20;
const int hll_precision = 12;
const typename estimator_type::sketch_size_kb sketch_size_kb(4.0 * (1ull << hll_precision) / 1024.0);
CAPTURE(num_items, hll_precision, sketch_size_kb);
constexpr double tolerance_factor = 2.5;
const double relative_standard_deviation = 1.04 / std::sqrt(static_cast<double>(1ull << hll_precision));
::cuda::stream stream{::cuda::device_ref{0}};
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
auto items = ::cuda::make_buffer<T>(stream, mr, num_items, ::cuda::no_init);
thrust::sequence(thrust::cuda::par_nosync.on(stream.get()), items.begin(), items.end(), T{0});
estimator_type estimator{stream, mr, sketch_size_kb};
estimator.add(stream, items.begin(), items.end());
auto host_mr = ::cuda::pinned_default_memory_pool();
const auto estimate = estimator.estimate(stream, host_mr);
const double relative_error = std::abs((static_cast<double>(estimate) / static_cast<double>(num_items)) - 1.0);
REQUIRE(relative_error < tolerance_factor * relative_standard_deviation);
}
#endif // _CCCL_CTK_AT_LEAST(12, 9)

View File

@@ -1,58 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <cuda/std/cstddef>
#include <cuda/experimental/__cuco/capacity.cuh>
#include <cuda/experimental/__cuco/hash_functions.cuh>
#include <cuda/experimental/__cuco/probing_scheme.cuh>
#include <testing.cuh>
namespace cudax = cuda::experimental;
C2H_TEST("cuco make_valid_capacity rounding and validation", "[capacity]")
{
using probing = cudax::cuco::double_hashing<1, cudax::cuco::hash<int>>;
[[maybe_unused]] constexpr int bucket = 1;
static_assert(cudax::cuco::is_double_hashing_v<probing>, "scheme is double hashing");
// make_valid_capacity rounds up and is idempotent; is_valid_capacity is derived from it
constexpr auto valid = cudax::cuco::make_valid_capacity<probing, bucket>(::cuda::std::size_t{1000});
static_assert(valid >= 1000, "rounds up");
static_assert(cudax::cuco::is_valid_capacity<probing, bucket>(valid), "result is valid");
static_assert(cudax::cuco::make_valid_capacity<probing, bucket>(valid) == valid, "idempotent");
// 1000 is not a valid double-hashing capacity; it rounds up to a prime-cycle capacity
static_assert(!cudax::cuco::is_valid_capacity<probing, bucket>(::cuda::std::size_t{1000}), "1000 is not valid");
// equal-rounding requests produce the same valid capacity
static_assert(cudax::cuco::make_valid_capacity<probing, bucket>(::cuda::std::size_t{1000})
== cudax::cuco::make_valid_capacity<probing, bucket>(::cuda::std::size_t{1008}),
"requests that round to the same capacity agree");
// cuCollections extent_test parity: double hashing, cg_size 2, bucket_size 4.
// 1234 rounds up to next_prime(ceil(1234 / 8) = 155) = 157, times the stride 8 -> 1256.
using dh4 = cudax::cuco::double_hashing<2, cudax::cuco::hash<int>>;
[[maybe_unused]] constexpr int bucket4 = 4;
static_assert(cudax::cuco::make_valid_capacity<dh4, bucket4>(::cuda::std::size_t{1234}) == ::cuda::std::size_t{1256},
"compile-time valid capacity matches the cuCollections extent test");
REQUIRE(cudax::cuco::make_valid_capacity<dh4, bucket4>(::cuda::std::size_t{1234}) == ::cuda::std::size_t{1256});
// a desired load factor outside (0, 1] is rejected
using lp4 = cudax::cuco::linear_probing<2, cudax::cuco::hash<int>>;
auto bad_lf = [](double __lf) {
[[maybe_unused]] auto __r = cudax::cuco::make_valid_capacity<lp4, bucket4>(::cuda::std::size_t{1000}, __lf);
};
REQUIRE_THROWS(bad_lf(0.0));
REQUIRE_THROWS(bad_lf(-0.5));
REQUIRE_THROWS(bad_lf(1.5));
}

View File

@@ -1,243 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <thrust/detail/raw_pointer_cast.h>
#include <cuda/std/array>
#include <cuda/std/cstddef>
#include <cuda/std/functional>
#include <cuda/std/limits>
#include <cuda/std/span>
#include <cuda/experimental/__cuco/hash_functions.cuh>
#include <testing.cuh>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_test_macros.hpp>
template <int32_t Words>
struct large_key
{
constexpr _CCCL_HOST_DEVICE large_key(int32_t value) noexcept
{
for (int32_t i = 0; i < Words; ++i)
{
data_[i] = value;
}
}
private:
int32_t data_[Words];
};
template <cudax::cuco::hash_algorithm Algorithm>
struct hash_test
{
template <typename Key, typename ResultT, typename... HashConstructorArgs>
_CCCL_HOST_DEVICE void
operator()(Key const& key, ResultT expected, HashConstructorArgs&&... hash_constructor_args) noexcept
{
cudax::cuco::hash<Key, Algorithm> hasher(::cuda::std::forward<HashConstructorArgs>(hash_constructor_args)...);
cuda::std::array<Key, 1> arr_keys = {key};
REQUIRE(hasher(key) == expected);
REQUIRE(hasher(cuda::std::span<Key>(thrust::raw_pointer_cast(arr_keys.data()), arr_keys.size())) == expected);
}
};
struct test_xxhash32
{
hash_test<cudax::cuco::hash_algorithm::xxhash_32> xxhash32_test;
_CCCL_HOST_DEVICE void operator()()
{
xxhash32_test(static_cast<char>(0), 3479547966u, 0);
xxhash32_test(static_cast<char>(42), 3774771295u, 0);
xxhash32_test(static_cast<char>(0), 2099223482u, 42);
xxhash32_test(static_cast<int32_t>(0), 148298089u, 0);
xxhash32_test(static_cast<int32_t>(0), 2132181312u, 42);
xxhash32_test(static_cast<int32_t>(42), 1161967057u, 0);
xxhash32_test(static_cast<int32_t>(123456789), 2987034094u, 0);
xxhash32_test(static_cast<int64_t>(0), 3736311059u, 0);
xxhash32_test(static_cast<int64_t>(0), 1076387279u, 42);
xxhash32_test(static_cast<int64_t>(42), 2332451213u, 0);
xxhash32_test(static_cast<int64_t>(123456789), 1561711919u, 0);
#if _CCCL_HAS_INT128()
xxhash32_test(static_cast<__int128_t>(123456789), 1846633701u, 0);
#endif
xxhash32_test(large_key<32>(123456789), 3715432378u, 0);
}
};
struct test_xxhash64
{
hash_test<cudax::cuco::hash_algorithm::xxhash_64> xxhash64_test;
_CCCL_HOST_DEVICE void operator()()
{
xxhash64_test(static_cast<char>(0), 16804241149081757544ull, 0);
xxhash64_test(static_cast<char>(42), 765293966243412708ull, 0);
xxhash64_test(static_cast<char>(0), 9486749600008296231ull, 42);
xxhash64_test(static_cast<int32_t>(0), 4246796580750024372ull, 0);
xxhash64_test(static_cast<int32_t>(0), 3614696996920510707ull, 42);
xxhash64_test(static_cast<int32_t>(42), 15516826743637085169ull, 0);
xxhash64_test(static_cast<int32_t>(123456789), 9462334144942111946ull, 0);
xxhash64_test(static_cast<int64_t>(0), 3803688792395291579ull, 0);
xxhash64_test(static_cast<int64_t>(0), 13194218611613725804ull, 42);
xxhash64_test(static_cast<int64_t>(42), 13066772586158965587ull, 0);
xxhash64_test(static_cast<int64_t>(123456789), 14662639848940634189ull, 0);
#if _CCCL_HAS_INT128()
xxhash64_test(static_cast<__int128_t>(123456789), 7986913354431084250ull, 0);
#endif
xxhash64_test(large_key<32>(123456789), 2031761887105658523ull, 0);
}
};
struct test_murmurhash3_32
{
hash_test<cudax::cuco::hash_algorithm::murmurhash3_32> murmurhash3_32_test;
_CCCL_HOST_DEVICE void operator()()
{
murmurhash3_32_test(static_cast<char>(0), 1364076727u, 0);
murmurhash3_32_test(static_cast<char>(42), 338914844u, 0);
murmurhash3_32_test(static_cast<char>(0), 3712240066u, 42);
murmurhash3_32_test(static_cast<int32_t>(0), 593689054u, 0);
murmurhash3_32_test(static_cast<int32_t>(0), 933211791u, 42);
murmurhash3_32_test(static_cast<int32_t>(42), 3160117731u, 0);
murmurhash3_32_test(static_cast<int32_t>(123456789), 3206620847u, 0);
murmurhash3_32_test(static_cast<int64_t>(0), 1669671676u, 0);
murmurhash3_32_test(static_cast<int64_t>(0), 2624043101u, 42);
murmurhash3_32_test(static_cast<int64_t>(42), 1871679806u, 0);
murmurhash3_32_test(static_cast<int64_t>(123456789), 690028081u, 0);
#if _CCCL_HAS_INT128()
murmurhash3_32_test(static_cast<__int128_t>(123456789), 2191144977u, 0);
#endif
murmurhash3_32_test(large_key<32>(123456789), 2555553099u, 0);
}
};
#if _CCCL_HAS_INT128()
struct test_murmurhash3_x86_128
{
hash_test<cudax::cuco::hash_algorithm::murmurhash3_x86_128> murmurhash3_x86_128_test;
_CCCL_HOST_DEVICE __uint128_t conv(cuda::std::array<uint32_t, 4> const& arr) const
{
return cuda::std::bit_cast<__uint128_t>(arr);
}
_CCCL_HOST_DEVICE void operator()()
{
murmurhash3_x86_128_test(int32_t(0), conv({3422973727u, 2656139328u, 2656139328u, 2656139328u}), 0);
murmurhash3_x86_128_test(int32_t(9), conv({2808089785u, 314604614u, 314604614u, 314604614u}), 0);
murmurhash3_x86_128_test(int32_t(42), conv({3611919118u, 1962256489u, 1962256489u, 1962256489u}), 0);
murmurhash3_x86_128_test(int32_t(42), conv({3399017053u, 732469929u, 732469929u, 732469929u}), 42);
murmurhash3_x86_128_test(
cuda::std::array<int32_t, 2>{2, 2}, conv({1234494082u, 1431451587u, 431049201u, 431049201u}), 0);
murmurhash3_x86_128_test(
cuda::std::array<int32_t, 3>{1, 4, 9}, conv({2516796247u, 2757675829u, 778406919u, 2453259553u}), 42);
murmurhash3_x86_128_test(
cuda::std::array<int32_t, 4>{42, 64, 108, 1024}, conv({2686265656u, 591236665u, 3797082165u, 2731908938u}), 63);
murmurhash3_x86_128_test(cuda::std::array<int32_t, 16>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
conv({3918256832u, 4205523739u, 1707810111u, 1625952473u}),
1024);
murmurhash3_x86_128_test(
cuda::std::array<int64_t, 2>{2, 2}, conv({3811075945u, 727160712u, 3510740342u, 235225510u}), 0);
murmurhash3_x86_128_test(
cuda::std::array<int64_t, 3>{1, 4, 9}, conv({2817194959u, 206796677u, 3391242768u, 248681098u}), 42);
murmurhash3_x86_128_test(
cuda::std::array<int64_t, 4>{42, 64, 108, 1024}, conv({2335912146u, 1566515912u, 760710030u, 452077451u}), 63);
murmurhash3_x86_128_test(cuda::std::array<int64_t, 16>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
conv({1101169764u, 1758958147u, 2406511780u, 2903571412u}),
1024);
}
};
struct test_murmurhash3_x64_128
{
hash_test<cudax::cuco::hash_algorithm::murmurhash3_x64_128> murmurhash3_x64_128_test;
_CCCL_HOST_DEVICE __uint128_t conv(cuda::std::array<uint64_t, 2> const& arr) const
{
return cuda::std::bit_cast<__uint128_t>(arr);
}
_CCCL_HOST_DEVICE void operator()()
{
murmurhash3_x64_128_test(int32_t(0), conv({14961230494313510588ull, 6383328099726337777ull}), 0);
murmurhash3_x64_128_test(int32_t(9), conv({1779292183511753683ull, 16298496441448380334ull}), 0);
murmurhash3_x64_128_test(int32_t(42), conv({2913627637088662735ull, 16344193523890567190ull}), 0);
murmurhash3_x64_128_test(int32_t(42), conv({2248879576374326886ull, 18006515275339376488ull}), 42);
murmurhash3_x64_128_test(
cuda::std::array<int32_t, 2>{2, 2}, conv({12221386834995143465ull, 6690950894782946573ull}), 0);
murmurhash3_x64_128_test(
cuda::std::array<int32_t, 3>{1, 4, 9}, conv({299140022350411792ull, 9891903873182035274ull}), 42);
murmurhash3_x64_128_test(
cuda::std::array<int32_t, 4>{42, 64, 108, 1024}, conv({4333511168876981289ull, 4659486988434316416ull}), 63);
murmurhash3_x64_128_test(cuda::std::array<int32_t, 16>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
conv({3302412811061286680ull, 7070355726356610672ull}),
1024);
murmurhash3_x64_128_test(
cuda::std::array<int64_t, 2>{2, 2}, conv({8554944597931919519ull, 14938998000509429729ull}), 0);
murmurhash3_x64_128_test(
cuda::std::array<int64_t, 3>{1, 4, 9}, conv({13442629947720186435ull, 7061727494178573325ull}), 42);
murmurhash3_x64_128_test(
cuda::std::array<int64_t, 4>{42, 64, 108, 1024}, conv({8786399719555989948ull, 14954183901757012458ull}), 63);
murmurhash3_x64_128_test(cuda::std::array<int64_t, 16>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
conv({15409921801541329777ull, 10546487400963404004ull}),
1024);
}
};
#endif // _CCCL_HAS_INT128()
template <typename TestFn>
__global__ void test_hasher_kernel(TestFn test_fn)
{
test_fn();
}
template <typename TestFn>
void test_hasher_on_device(TestFn test_fn)
{
test_hasher_kernel<<<1, 1>>>(test_fn);
REQUIRE_CUDART(cudaDeviceSynchronize());
}
TEST_CASE("Test Hasher's on host and device", "")
{
SECTION("host-generated hash values match the reference implementation.")
{
test_xxhash32{}();
test_xxhash64{}();
test_murmurhash3_32{}();
#if _CCCL_HAS_INT128()
test_murmurhash3_x86_128{}();
test_murmurhash3_x64_128{}();
#endif // _CCCL_HAS_INT128()
}
SECTION("device-generated hash values match the reference implementation.")
{
test_hasher_on_device(test_xxhash32{});
test_hasher_on_device(test_xxhash64{});
test_hasher_on_device(test_murmurhash3_32{});
#if _CCCL_HAS_INT128()
test_hasher_on_device(test_murmurhash3_x86_128{});
test_hasher_on_device(test_murmurhash3_x64_128{});
#endif // _CCCL_HAS_INT128()
}
}