[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:
@@ -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));
|
||||
}
|
||||
@@ -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}));
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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{}));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
@@ -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{}));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user