Files
EngineX CI 56fd68e7dd [INFRA] Import NVIDIA/CCCL upstream as optimization reference library
CCCL (CUDA C++ Core Libraries) provides:
- CUB: device/block/warp-level GPU primitives (reduce, scan, sort, topk)
- Thrust: high-level parallel algorithms (transform_reduce, sort, scan)
- libcudacxx: CUDA C++ standard library (atomics, barriers, memory)
- cudax: experimental features (memory resources, allocators)
- Tuning policies: per-SM hardware-specific algorithm parameters

Competition optimization vectors mapped to CCCL:
- Output TPS (83% weight): warp_reduce, block_reduce, device_topk
- Input TPS (14% weight): device_scan, block_load, prefetch
- Cache TPS (3% weight): prefix caching strategy patterns
- Memory (0.9 util): pooled/cached/buddy allocators

Source: https://github.com/NVIDIA/cccl (shallow clone, HEAD only)
License: Apache-2.0
2026-07-30 09:35:51 +00:00

486 lines
14 KiB
C++

//===----------------------------------------------------------------------===//
//
// 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) 2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <algorithm>
#include <cstdint>
#include <iostream> // std::cerr
#include <optional> // std::optional
#include <string>
#include <cuda_runtime.h>
#include "algorithm_execution.h"
#include "build_result_caching.h"
#include "test_util.h"
#include <cccl/c/for.h>
using BuildResultT = cccl_device_for_build_result_t;
struct for_each_cleanup
{
CUresult operator()(BuildResultT* build_data) const noexcept
{
return cccl_device_for_cleanup(build_data);
}
};
using for_each_deleter = BuildResultDeleter<BuildResultT, for_each_cleanup>;
using for_each_build_cache_t = build_cache_t<std::string, result_wrapper_t<BuildResultT, for_each_deleter>>;
struct for_each_build
{
template <typename... Ts>
CUresult operator()(BuildResultT* build_ptr, cccl_iterator_t input, uint64_t, cccl_op_t op, Ts... args) const noexcept
{
return cccl_device_for_build(build_ptr, input, op, args...);
}
};
struct for_each_run
{
template <typename... Ts>
CUresult operator()(BuildResultT build, void* scratch, size_t* nbytes, Ts... args) const noexcept
{
*nbytes = 1;
// only run if scratch is not null
return (scratch) ? cccl_device_for(build, args...) : CUDA_SUCCESS;
}
};
template <typename BuildCache = for_each_build_cache_t, typename KeyT = std::string>
void for_each(cccl_iterator_t input,
uint64_t num_items,
cccl_op_t op,
std::optional<BuildCache>& cache,
const std::optional<KeyT>& lookup_key)
{
AlgorithmExecute<BuildResultT, for_each_build, for_each_cleanup, for_each_run, BuildCache, KeyT>(
cache, lookup_key, input, num_items, op);
}
// Specialization for a pointer input
struct DeviceFor_Pointer_Fixture_Tag;
template <typename T>
void for_each_pointer_input(pointer_t<T>& input_ptr, uint64_t num_items, cccl_op_t op)
{
auto& build_cache = fixture<for_each_build_cache_t, DeviceFor_Pointer_Fixture_Tag>::get_or_create().get_value();
const auto& test_key = make_key<T>();
for_each(static_cast<cccl_iterator_t>(input_ptr), num_items, op, build_cache, test_key);
}
// specialization without caching
void for_each_uncached(cccl_iterator_t input, uint64_t num_items, cccl_op_t op)
{
std::optional<for_each_build_cache_t> no_cache = std::nullopt;
std::optional<std::string> no_key = std::nullopt;
for_each(input, num_items, op, no_cache, no_key);
}
using integral_types = c2h::type_list<int32_t, uint32_t, int64_t, uint64_t>;
C2H_TEST("for works with integral types", "[for]", integral_types)
{
using T = c2h::get<0, TestType>;
const uint64_t num_items = GENERATE(0, 42, take(4, random(1 << 12, 1 << 24)));
operation_t op = make_operation("op", get_for_op(get_type_info<T>().type));
std::vector<T> input(num_items, T(1));
pointer_t<T> input_ptr(input);
for_each_pointer_input(input_ptr, num_items, op);
// Copy input array back to host
input = input_ptr;
REQUIRE(std::all_of(input.begin(), input.end(), [](auto&& v) {
return v == T{2};
}));
}
struct pair
{
short a;
size_t b;
};
C2H_TEST("for works with custom types", "[for]")
{
const int num_items = GENERATE(0, 42, take(4, random(1 << 12, 1 << 24)));
operation_t op = make_operation("op",
R"XXX(
struct pair { short a; size_t b; };
extern "C" __device__ void op(void* a_ptr) {
pair* a = static_cast<pair*>(a_ptr);
a->a++;
a->b++;
}
)XXX");
std::vector<pair> input(num_items, pair{short(1), size_t(1)});
pointer_t<pair> input_ptr(input);
for_each_pointer_input(input_ptr, num_items, op);
// Copy back input array
input = input_ptr;
REQUIRE(std::all_of(input.begin(), input.end(), [](auto v) {
return (v.a == short(2)) && (v.b == size_t(2));
}));
}
struct invocation_counter_state_t
{
int* d_counter;
};
C2H_TEST("for_each works with stateful operators", "[for_each]")
{
const int num_items = 1 << 12;
pointer_t<int> counter(1);
invocation_counter_state_t op_state = {counter.ptr};
stateful_operation_t<invocation_counter_state_t> op = make_operation(
"op",
R"XXX(
struct invocation_counter_state_t { int* d_counter; };
extern "C" __device__ void op(void* state_ptr, void* a_ptr) {
invocation_counter_state_t* state = static_cast<invocation_counter_state_t*>(state_ptr);
atomicAdd(state->d_counter, *static_cast<int*>(a_ptr));
}
)XXX",
op_state);
std::vector<int> input(num_items, 1);
pointer_t<int> input_ptr(input);
for_each_uncached(input_ptr, num_items, op);
const int invocation_count = counter[0];
REQUIRE(invocation_count == num_items);
}
struct large_state_t
{
int x;
int* d_counter;
int y, z, a;
};
C2H_TEST("for_each works with large stateful operators", "[for_each]")
{
const int num_items = 1 << 12;
pointer_t<int> counter(1);
large_state_t op_state = {1, counter.ptr, 2, 3, 4};
stateful_operation_t<large_state_t> op = make_operation(
"op",
R"XXX(
struct large_state_t
{
int x;
int* d_counter;
int y, z, a;
};
extern "C" __device__ void op(void* state_ptr, void* a_ptr) {
large_state_t* state = static_cast<large_state_t*>(state_ptr);
atomicAdd(state->d_counter, *static_cast<int*>(a_ptr));
}
)XXX",
op_state);
std::vector<int> input(num_items, 1);
pointer_t<int> input_ptr(input);
for_each_uncached(input_ptr, num_items, op);
const int invocation_count = counter[0];
REQUIRE(invocation_count == num_items);
}
C2H_TEST("for works with C++ source operations", "[for]")
{
using T = int32_t;
const uint64_t num_items = GENERATE(42, 1337, 42000);
// Create operation from C++ source instead of LTO-IR
std::string cpp_source = R"(
extern "C" __device__ void op(void* a) {
int* ia = (int*)a;
*ia = *ia + 1;
}
)";
operation_t op = make_cpp_operation("op", cpp_source);
std::vector<T> input(num_items, T(1));
pointer_t<T> input_ptr(input);
// Test key including flag that this uses C++ source
std::optional<std::string> test_key = std::format("cpp_source_test_{}_{}", num_items, typeid(T).name());
auto& cache = fixture<for_each_build_cache_t, DeviceFor_Pointer_Fixture_Tag>::get_or_create().get_value();
std::optional<for_each_build_cache_t> cache_opt = cache;
for_each(input_ptr, num_items, op, cache_opt, test_key);
// Copy input array back to host
input = input_ptr;
REQUIRE(std::all_of(input.begin(), input.end(), [](auto&& v) {
return v == T{2};
}));
}
C2H_TEST("For works with C++ source operations using custom headers", "[for]")
{
using T = int32_t;
const uint64_t num_items = GENERATE(42, 1337, 42000);
// Create operation from C++ source that uses the identity function from header
std::string cpp_source = R"(
#include "test_identity.h"
extern "C" __device__ void op(void* a) {
int* ia = (int*)a;
int val = test_identity(*ia);
*ia = val + 1;
}
)";
operation_t op = make_cpp_operation("op", cpp_source);
std::vector<T> input(num_items, T(1));
pointer_t<T> input_ptr(input);
// Test _ex version with custom build configuration
const char* extra_flags[] = {"-DTEST_IDENTITY_ENABLED"};
const char* extra_dirs[] = {TEST_INCLUDE_PATH};
cccl_build_config config = make_build_config(extra_flags, 1, extra_dirs, 1);
// Build with _ex version
cccl_device_for_build_result_t build{};
const auto& build_info = BuildInformation<>::init();
REQUIRE(
CUDA_SUCCESS
== cccl_device_for_build_ex(
&build,
input_ptr,
op,
build_info.get_cc_major(),
build_info.get_cc_minor(),
build_info.get_cub_path(),
build_info.get_thrust_path(),
build_info.get_libcudacxx_path(),
build_info.get_ctk_path(),
&config));
// Execute the for_each
REQUIRE(CUDA_SUCCESS == cccl_device_for(build, input_ptr, num_items, op, CU_STREAM_LEGACY));
// Verify results
std::vector<T> output(num_items);
cudaMemcpy(output.data(), static_cast<void*>(input_ptr.ptr), sizeof(T) * num_items, cudaMemcpyDeviceToHost);
std::vector<T> expected = input;
std::transform(expected.begin(), expected.end(), expected.begin(), [](T x) {
return x * 2;
});
REQUIRE(output == expected);
// Cleanup
REQUIRE(CUDA_SUCCESS == cccl_device_for_cleanup(&build));
}
#ifndef CCCL_C_PARALLEL_V2
C2H_TEST("For build result has serialization metadata populated", "[for][serialization]")
{
using T = int32_t;
constexpr int device_id = 0;
const auto& build_info = BuildInformation<device_id>::init();
operation_t op = make_operation("op", get_for_op(get_type_info<T>().type));
pointer_t<T> input_ptr(1);
BuildResultT build{};
REQUIRE(
CUDA_SUCCESS
== cccl_device_for_build(
&build,
input_ptr,
op,
build_info.get_cc_major(),
build_info.get_cc_minor(),
build_info.get_cub_path(),
build_info.get_thrust_path(),
build_info.get_libcudacxx_path(),
build_info.get_ctk_path()));
CHECK(build.cc == build_info.get_cc_major() * 10 + build_info.get_cc_minor());
CHECK((build.payload != nullptr && build.payload_kind == CCCL_PAYLOAD_CUBIN));
CHECK(build.payload_size > 0);
REQUIRE(build.static_kernel_lowered_name != nullptr);
CHECK(build.static_kernel_lowered_name[0] != '\0');
REQUIRE(CUDA_SUCCESS == cccl_device_for_cleanup(&build));
}
C2H_TEST("For compile/load round-trip", "[for][serialization]")
{
using T = int32_t;
constexpr int device_id = 0;
const auto& build_info = BuildInformation<device_id>::init();
constexpr std::size_t n = 16;
const std::vector<T> input_h(n, T{1});
operation_t op = make_operation("op", get_for_op(get_type_info<T>().type));
pointer_t<T> input_ptr(input_h);
BuildResultT build{};
REQUIRE(
CUDA_SUCCESS
== cccl_device_for_compile(
&build,
input_ptr,
op,
build_info.get_cc_major(),
build_info.get_cc_minor(),
build_info.get_cub_path(),
build_info.get_thrust_path(),
build_info.get_libcudacxx_path(),
build_info.get_ctk_path(),
nullptr));
REQUIRE((build.payload != nullptr && build.payload_kind == CCCL_PAYLOAD_CUBIN));
REQUIRE(build.payload_size > 0);
REQUIRE(build.static_kernel_lowered_name != nullptr);
CHECK(build.library == nullptr);
CHECK(build.static_kernel == nullptr);
REQUIRE(CUDA_SUCCESS == cccl_device_for_load(&build));
REQUIRE(build.library != nullptr);
CHECK(build.static_kernel != nullptr);
REQUIRE(CUDA_SUCCESS == cccl_device_for(build, input_ptr, n, op, CU_STREAM_LEGACY));
std::vector<T> output(n);
cudaMemcpy(output.data(), input_ptr.ptr, sizeof(T) * n, cudaMemcpyDeviceToHost);
REQUIRE(std::all_of(output.begin(), output.end(), [](T v) {
return v == T{2};
}));
REQUIRE(CUDA_SUCCESS == cccl_device_for_cleanup(&build));
}
C2H_TEST("For link_ltoir round-trip", "[for][serialization]")
{
using T = int32_t;
constexpr int device_id = 0;
const auto& build_info = BuildInformation<device_id>::init();
// Kernel-only compile: op has a name but no LTOIR (code_size == 0).
// compile() will produce kernel LTOIR with an unresolved external reference to "op".
cccl_op_t op_ko{};
op_ko.type = CCCL_STATELESS;
op_ko.name = "op";
op_ko.code = nullptr;
op_ko.code_size = 0;
op_ko.code_type = CCCL_OP_LTOIR;
op_ko.size = 1;
op_ko.alignment = 1;
constexpr std::size_t n = 16;
const std::vector<T> input_h(n, T{1});
pointer_t<T> input_ptr(input_h);
BuildResultT build{};
REQUIRE(
CUDA_SUCCESS
== cccl_device_for_compile(
&build,
input_ptr,
op_ko,
build_info.get_cc_major(),
build_info.get_cc_minor(),
build_info.get_cub_path(),
build_info.get_thrust_path(),
build_info.get_libcudacxx_path(),
build_info.get_ctk_path(),
nullptr));
// After kernel-only compile: payload is kernel LTOIR, not a cubin.
REQUIRE((build.payload != nullptr && build.payload_kind == CCCL_PAYLOAD_LTOIR));
REQUIRE(build.payload_size > 0);
CHECK(build.library == nullptr);
// Compile the operator LTOIR separately (user-supplied blob).
operation_t op_full = make_operation("op", get_for_op(get_type_info<T>().type));
const void* op_blob = op_full.code.data();
size_t op_size = op_full.code.size();
REQUIRE(CUDA_SUCCESS == cccl_device_for_link_ltoir(&build, &op_blob, &op_size, 1));
REQUIRE((build.payload != nullptr && build.payload_kind == CCCL_PAYLOAD_CUBIN));
REQUIRE(build.library == nullptr);
REQUIRE(CUDA_SUCCESS == cccl_device_for_load(&build));
REQUIRE(build.library != nullptr);
CHECK(build.static_kernel != nullptr);
cccl_op_t op_run = op_full;
REQUIRE(CUDA_SUCCESS == cccl_device_for(build, input_ptr, n, op_run, CU_STREAM_LEGACY));
std::vector<T> output(n);
cudaMemcpy(output.data(), input_ptr.ptr, sizeof(T) * n, cudaMemcpyDeviceToHost);
REQUIRE(std::all_of(output.begin(), output.end(), [](T v) {
return v == T{2};
}));
REQUIRE(CUDA_SUCCESS == cccl_device_for_cleanup(&build));
}
#endif // CCCL_C_PARALLEL_V2
// TODO:
/*
C2H_TEST("for works with iterators", "[for]")
{
const int num_items = GENERATE(1, 42, take(4, random(1 << 12, 1 << 16)));
iterator_t<int, constant_iterator_state_t<int>> input_it = make_iterator<int, constant_iterator_state_t<int>>(
{"constant_iterator_state_t", "struct constant_iterator_state_t { int value; };\n"},
{"in_advance", "extern \"C\" __device__ void in_advance(constant_iterator_state_t*, unsigned long long) {}"},
{"in_dereference",
"extern \"C\" __device__ void in_dereference(constant_iterator_state_t* state, int* result) { \n"
" *result = state->value;\n"
"}"});
input_it.state.value = 1;
pointer_t<int> counter(1);
invocation_counter_state_t op_state = {counter.ptr};
stateful_operation_t<invocation_counter_state_t> op = make_operation(
"op",
R"XXX(
struct invocation_counter_state_t { int* d_counter; };
extern "C" __device__ void op(invocation_counter_state_t* state, int a) {
atomicAdd(state->d_counter, a);
}
)XXX",
op_state);
for_each_uncached(input_it, num_items, op);
const int invocation_count = counter[0];
REQUIRE(invocation_count == num_items);
}
*/