// SPDX-FileCopyrightText: Copyright (c) 2011-2025, NVIDIA CORPORATION. All rights reserved. // SPDX-License-Identifier: BSD-3-Clause #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if C2H_HAS_CURAND # include #else # include #endif namespace c2h::detail { #if !C2H_HAS_CURAND struct i_to_rnd_t { __host__ __device__ i_to_rnd_t(thrust::default_random_engine engine) : m_engine(engine) {} thrust::default_random_engine m_engine{}; template __host__ __device__ float operator()(IndexType n) { m_engine.discard(n); return thrust::uniform_real_distribution{0.0f, 1.0f}(m_engine); } }; #endif // !C2H_HAS_CURAND class generator_t { public: generator_t() { #if C2H_HAS_CURAND curandCreateGenerator(&m_gen, CURAND_RNG_PSEUDO_DEFAULT); #endif } ~generator_t() { #if C2H_HAS_CURAND curandDestroyGenerator(m_gen); #endif } float* prepare_random_generator(seed_t seed, std::size_t num_items) { m_distribution.resize(num_items); #if C2H_HAS_CURAND curandSetPseudoRandomGeneratorSeed(m_gen, seed.get()); #else m_gen.seed(seed.get()); #endif generate(); return thrust::raw_pointer_cast(m_distribution.data()); } // re-fills the currently held distribution vector with new random values void generate() { #if C2H_HAS_CURAND curandGenerateUniform(m_gen, thrust::raw_pointer_cast(m_distribution.data()), m_distribution.size()); #else thrust::tabulate(device_policy, m_distribution.begin(), m_distribution.end(), i_to_rnd_t{m_gen}); m_gen.discard(m_distribution.size()); #endif } private: #if C2H_HAS_CURAND curandGenerator_t #else thrust::default_random_engine #endif m_gen; c2h::device_vector m_distribution; }; // global generator state cuda::std::optional generator; void init_generator() { _CCCL_VERIFY(!generator.has_value(), ""); generator.emplace(); } float* prepare_random_data(seed_t seed, std::size_t num_items) { return generator.value().prepare_random_generator(seed, num_items); } void cleanup_generator() { _CCCL_VERIFY(generator.has_value(), ""); generator.reset(); } struct random_to_custom_t { static constexpr std::size_t m_max_key = std::numeric_limits::max(); __device__ void operator()(std::size_t idx) const { auto out = reinterpret_cast(m_out + idx * m_element_size); out->key = static_cast(static_cast(m_max_key) * m_in[idx * 2 + 0]); out->val = static_cast(static_cast(m_max_key) * m_in[idx * 2 + 1]); } float* m_in{}; char* m_out{}; std::size_t m_element_size{}; }; void gen_custom_type_state( seed_t seed, char* d_out, custom_type_state_t /* min */, custom_type_state_t /* max */, std::size_t elements, std::size_t element_size) { // FIXME(bgruber): implement min/max handling for custom_type_state_t float* d_in = prepare_random_data(seed, elements * 2); thrust::for_each(device_policy, thrust::counting_iterator{0}, thrust::counting_iterator{elements}, random_to_custom_t{d_in, d_out, element_size}); } template struct spaced_out_it_op { char* base_it; std::size_t element_size; __host__ __device__ __forceinline__ T& operator()(std::size_t offset) const { return *reinterpret_cast(base_it + (element_size * offset)); } }; template struct offset_to_iterator_t { char* base_it; std::size_t element_size; __host__ __device__ __forceinline__ thrust::transform_iterator, thrust::counting_iterator> operator()(std::size_t offset) const { // The pointer to the beginning of this "buffer" (aka a series of same "keys") auto base_ptr = base_it + (element_size * offset); // We need to make sure that the i-th element within this "buffer" is spaced out by // `element_size` auto counting_it = thrust::make_counting_iterator(std::size_t{0}); spaced_out_it_op space_out_op{base_ptr, element_size}; return thrust::make_transform_iterator(counting_it, space_out_op); } }; template struct repeat_index_t { __host__ __device__ __forceinline__ cuda::constant_iterator operator()(std::size_t i) { return cuda::constant_iterator(static_cast(i)); } }; template <> struct repeat_index_t { __host__ __device__ __forceinline__ cuda::constant_iterator operator()(std::size_t i) { custom_type_state_t item{}; item.key = i; item.val = i; return cuda::constant_iterator(item); } }; template struct offset_to_size_t { const OffsetT* offsets; __host__ __device__ __forceinline__ std::size_t operator()(std::size_t i) { return offsets[i + 1] - offsets[i]; } }; /** * @brief Initializes key-segment ranges from an offsets-array like the one given by * `gen_uniform_offset`. */ template void init_key_segments(::cuda::std::span segment_offsets, KeyT* d_out, std::size_t element_size) { OffsetT total_segments = static_cast(segment_offsets.size() - 1); const OffsetT* d_offsets = segment_offsets.data(); thrust::counting_iterator iota(0); offset_to_iterator_t dst_transform_op{reinterpret_cast(d_out), element_size}; auto d_range_srcs = thrust::make_transform_iterator(iota, repeat_index_t{}); auto d_range_dsts = thrust::make_transform_iterator(d_offsets, dst_transform_op); auto d_range_sizes = thrust::make_transform_iterator(iota, offset_to_size_t{d_offsets}); #if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA std::uint8_t* d_temp_storage = nullptr; std::size_t temp_storage_bytes = 0; // TODO(bgruber): replace by a non-CUB implementation cub::DeviceCopy::Batched( d_temp_storage, temp_storage_bytes, d_range_srcs, d_range_dsts, d_range_sizes, total_segments); # if THRUST_VERSION >= 300100 device_vector temp_storage(temp_storage_bytes, thrust::no_init); # else device_vector temp_storage(temp_storage_bytes); # endif // THRUST_VERSION >= 300100 d_temp_storage = thrust::raw_pointer_cast(temp_storage.data()); // TODO(bgruber): replace by a non-CUB implementation cub::DeviceCopy::Batched( d_temp_storage, temp_storage_bytes, d_range_srcs, d_range_dsts, d_range_sizes, total_segments); cudaDeviceSynchronize(); #else // THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA static_assert(sizeof(OffsetT) == 0, "Need to implement a non-CUB version of cub::DeviceCopy::Batched"); // TODO(bgruber): implement and *test* a non-CUB version, here is a sketch: // thrust::for_each( // thrust::device, // thrust::counting_iterator{0}, // thrust::counting_iterator{total_segments}, // [&](OffsetT i) { // const auto value = d_range_srcs[i]; // const auto start = d_range_sizes[i]; // const auto end = d_range_sizes[i + 1]; // for (auto j = start; j < end; ++j) // { // d_range_dsts[j] = value; // } // }); #endif // THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA } template void init_key_segments(::cuda::std::span segment_offsets, std::int32_t* out, std::size_t element_size); template void init_key_segments(::cuda::std::span segment_offsets, std::uint8_t* out, std::size_t element_size); template void init_key_segments(::cuda::std::span segment_offsets, float* out, std::size_t element_size); template void init_key_segments( ::cuda::std::span segment_offsets, custom_type_state_t* out, std::size_t element_size); #if TEST_HALF_T() template void init_key_segments(::cuda::std::span segment_offsets, half_t* out, std::size_t element_size); #endif // TEST_HALF_T() #if TEST_BF_T() template void init_key_segments(::cuda::std::span segment_offsets, bfloat16_t* out, std::size_t element_size); #endif // TEST_BF_T() } // namespace c2h::detail