[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;
}