Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

752 lines
22 KiB
C++

//===----------------------------------------------------------------------===//
//
// Part of the libcu++ Project, 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
//
//===----------------------------------------------------------------------===//
// Modifications Copyright (c) 2024-2025 Advanced Micro Devices, Inc.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef HETEROGENEOUS_HELPERS_H
#define HETEROGENEOUS_HELPERS_H
#include "../cuda_runtime.h"
#include <cuda/std/type_traits>
#include <cstdio>
#include <new>
#include <thread>
#include <vector>
#include "meta.h"
#define DEFINE_ASYNC_TRAIT(...) \
template <typename T, typename = cuda::std::true_type> \
struct async##__VA_ARGS__##_trait_impl \
{ \
using type = cuda::std::false_type; \
}; \
\
template <typename T> \
struct async##__VA_ARGS__##_trait_impl<T, typename T::async##__VA_ARGS__> \
{ \
using type = cuda::std::true_type; \
}; \
\
template <typename T> \
using async##__VA_ARGS__##_trait = typename async##__VA_ARGS__##_trait_impl<T>::type;
DEFINE_ASYNC_TRAIT()
DEFINE_ASYNC_TRAIT(_initialize)
DEFINE_ASYNC_TRAIT(_validate)
#undef DEFINE_ASYNC_TRAIT
template <typename T, typename = int>
struct has_threadcount : std::false_type
{};
template <typename T>
struct has_threadcount<T, decltype((void) T::threadcount, (int) 0)> : std::true_type
{};
template <typename T, bool = has_threadcount<T>::value>
struct threadcount_trait_impl
{
static constexpr size_t value = 1;
};
template <typename T>
struct threadcount_trait_impl<T, true>
{
static constexpr size_t value = T::threadcount;
};
template <typename T>
using threadcount_trait = threadcount_trait_impl<T>;
#define HETEROGENEOUS_SAFE_CALL(...) \
do \
{ \
cudaError_t err = __VA_ARGS__; \
if (err != cudaSuccess) \
{ \
printf("CUDA ERROR: %s: %s\n", cudaGetErrorName(err), cudaGetErrorString(err)); \
abort(); \
} \
} while (false)
__host__ inline std::vector<std::thread>& host_threads()
{
static std::vector<std::thread> threads;
return threads;
}
__host__ inline void sync_host_threads()
{
for (auto&& thread : host_threads())
{
thread.join();
}
host_threads().clear();
}
__host__ inline std::vector<std::thread>& device_threads()
{
static std::vector<std::thread> threads;
return threads;
}
__host__ inline void sync_device_threads()
{
for (auto&& thread : device_threads())
{
thread.join();
}
device_threads().clear();
}
__host__ void sync_all()
{
sync_host_threads();
sync_device_threads();
}
struct async_tester_fence
{
template <typename T>
__host__ __device__ static void initialize(T&)
{}
template <typename T>
__host__ __device__ static void validate(T&)
{}
template <typename T>
__host__ __device__ static void perform(T&)
{}
};
template <typename... Testers>
using tester_list = type_list<Testers...>;
template <typename Tester, typename T>
__host__ __device__ void initialize(T& object)
{
Tester::initialize(object);
}
template <typename Tester, typename T>
__host__ __device__ auto validate_impl(T& object) -> decltype(Tester::validate(object), void())
{
Tester::validate(object);
}
template <typename, typename... Ts>
__host__ __device__ void validate_impl(Ts&&...)
{}
template <typename Tester, typename T>
__host__ __device__ void validate(T& object)
{
validate_impl<Tester>(object);
}
template <typename T, typename... Args>
__global__ void construct_kernel(void* address, Args... args)
{
new (address) T(args...);
}
template <typename T>
__global__ void destroy_kernel(T* object)
{
object->~T();
}
template <typename Tester, typename T>
__global__ void initialization_kernel(T& object)
{
initialize<Tester>(object);
}
template <typename Tester, typename T>
__global__ void validation_kernel(T& object)
{
validate<Tester>(object);
}
template <typename T, typename... Args>
T* device_construct(void* address, Args... args)
{
construct_kernel<T><<<1, 1>>>(address, args...);
HETEROGENEOUS_SAFE_CALL(cudaGetLastError());
HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize());
return reinterpret_cast<T*>(address);
}
template <typename T>
void device_destroy(T* object)
{
destroy_kernel<<<1, 1>>>(object);
HETEROGENEOUS_SAFE_CALL(cudaGetLastError());
HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize());
}
template <typename Fn>
void device_launch_async(Fn& launcher)
{
auto streamManager = [launcher]() {
cudaStream_t stream;
HETEROGENEOUS_SAFE_CALL(cudaStreamCreate(&stream));
launcher(stream);
HETEROGENEOUS_SAFE_CALL(cudaGetLastError());
HETEROGENEOUS_SAFE_CALL(cudaStreamSynchronize(stream));
HETEROGENEOUS_SAFE_CALL(cudaStreamDestroy(stream));
};
device_threads().push_back(std::thread(streamManager));
}
template <typename Tester, typename T>
void device_initialize(T& object)
{
#ifdef DEBUG_TESTERS
printf(" %s\n", __PRETTY_FUNCTION__);
fflush(stdout);
#endif
auto kernel_launcher = [&object](cudaStream_t stream) {
constexpr auto tc = threadcount_trait<Tester>::value;
#ifdef DEBUG_TESTERS
printf(" %i device init threads launched\r\n", (int) tc);
fflush(stdout);
#endif
initialization_kernel<Tester><<<1, tc, 0, stream>>>(object);
};
device_launch_async(kernel_launcher);
if (!async_initialize_trait<Tester>::value)
{
HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize());
sync_all();
}
}
template <typename Tester, typename T>
void device_validate(T& object)
{
#ifdef DEBUG_TESTERS
printf(" %s\n", __PRETTY_FUNCTION__);
fflush(stdout);
#endif
auto kernel_launcher = [&object](cudaStream_t stream) {
constexpr auto tc = threadcount_trait<Tester>::value;
#ifdef DEBUG_TESTERS
printf(" %i device validate threads launched\r\n", (int) tc);
fflush(stdout);
#endif
validation_kernel<Tester><<<1, tc, 0, stream>>>(object);
};
device_launch_async(kernel_launcher);
if (!async_validate_trait<Tester>::value)
{
HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize());
sync_all();
}
}
template <typename Tester, typename T>
void host_initialize(T& object)
{
#ifdef DEBUG_TESTERS
printf(" %s\n", __PRETTY_FUNCTION__);
fflush(stdout);
#endif
constexpr auto tc = threadcount_trait<Tester>::value;
#ifdef DEBUG_TESTERS
printf(" %i host init threads launched\r\n", (int) tc);
fflush(stdout);
#endif
for (size_t i = 0; i < tc; i++)
{
host_threads().emplace_back([&] {
initialize<Tester>(object);
});
}
if (!async_initialize_trait<Tester>::value)
{
HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize());
sync_all();
}
}
template <typename Tester, typename T>
void host_validate(T& object)
{
#ifdef DEBUG_TESTERS
printf(" %s\n", __PRETTY_FUNCTION__);
fflush(stdout);
#endif
constexpr auto tc = threadcount_trait<Tester>::value;
#ifdef DEBUG_TESTERS
printf(" %i host validate threads launched\r\n", (int) tc);
fflush(stdout);
#endif
for (size_t i = 0; i < tc; i++)
{
host_threads().emplace_back([&] {
validate<Tester>(object);
});
}
if (!async_initialize_trait<Tester>::value)
{
HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize());
sync_all();
}
}
template <typename T>
using performer = void (*)(T&);
template <typename T>
struct initializer_validator
{
performer<T> initializer;
performer<T> validator;
};
template <typename T>
struct host_launcher
{
template <typename Tester>
static initializer_validator<T> get_exec()
{
return initializer_validator<T>{host_initialize<Tester>, host_validate<Tester>};
}
};
template <typename T>
struct device_launcher
{
template <typename Tester>
static initializer_validator<T> get_exec()
{
return initializer_validator<T>{device_initialize<Tester>, device_validate<Tester>};
}
};
template <typename T, typename... Testers, typename... Launchers>
void do_heterogeneous_test(T* test_input, type_list<Testers...>, type_list<Launchers...>)
{
initializer_validator<T> performers[] = {{Launchers::template get_exec<Testers>()}...};
for (auto&& performer : performers)
{
performer.initializer(*test_input);
performer.validator(*test_input);
}
HETEROGENEOUS_SAFE_CALL(cudaGetLastError());
HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize());
sync_all();
}
template <size_t Idx>
using enable_if_permutations_remain = typename std::enable_if<Idx != 0, int>::type;
template <size_t Idx>
using enable_if_no_permutations_remain = typename std::enable_if<Idx == 0, int>::type;
template <size_t Idx, typename Fn, typename Launchers, enable_if_permutations_remain<Idx> = 0>
void permute_tests(const Fn& fn, Launchers launchers)
{
#ifdef DEBUG_TESTERS
printf(" Testing permutation %zd (%s)\r\n", Idx, __PRETTY_FUNCTION__);
fflush(stdout);
#endif
fn(launchers);
permute_tests<Idx - 1>(fn, rotl<Launchers>{});
}
template <size_t Idx, typename Fn, typename Launchers, enable_if_no_permutations_remain<Idx> = 0>
void permute_tests(const Fn&, Launchers)
{}
template <typename Fn, typename... Launchers>
void permute_tests(const Fn& fn, type_list<Launchers...> launchers)
{
permute_tests<sizeof...(Launchers)>(fn, launchers);
}
template <typename Testers, typename InputCreator, typename InputDestructor>
struct test_wrapper
{
InputCreator creator;
InputDestructor destructor;
template <typename Launchers>
void operator()(Launchers) const
{
auto input = creator();
do_heterogeneous_test(input, Testers{}, Launchers{});
destructor(input);
}
};
template <typename T, typename... Testers, typename... Args>
void validate_device_dynamic(tester_list<Testers...> testers, Args... args)
{
auto test_input_creator = [args...]() -> T* {
void* pointer = nullptr;
HETEROGENEOUS_SAFE_CALL(cudaMallocHost(&pointer, sizeof(T)));
return device_construct<T>(pointer, args...);
};
auto test_input_destructor = [](T* test_input) {
device_destroy(test_input);
HETEROGENEOUS_SAFE_CALL(cudaFreeHost(test_input));
};
test_wrapper<tester_list<Testers...>, decltype(test_input_creator), decltype(test_input_destructor)> test_harness{
test_input_creator, test_input_destructor};
// ex: type_list<device_launcher, host_launcher, host_launcher>
using initial_launcher_list = append_n<sizeof...(Testers) - 1, type_list<device_launcher<T>>, host_launcher<T>>;
#ifdef DEBUG_TESTERS
printf("Launching %zd permutations\r\n", sizeof...(Testers));
fflush(stdout);
#endif
permute_tests(test_harness, initial_launcher_list{});
}
#if _CCCL_STD_VER >= 2014
template <typename T>
struct manual_object
{
template <typename... Args>
void construct(Args... args)
{
new (static_cast<void*>(&data.object)) T(args...);
}
template <typename... Args>
void device_construct(Args... args)
{
::device_construct<T>(&data.object, args...);
}
void destroy()
{
data.object.~T();
}
void device_destroy()
{
::device_destroy(&data.object);
}
T& get()
{
return data.object;
}
union data
{
__host__ __device__ constexpr data() noexcept
: dummy(){};
char dummy = {};
T object;
} data;
constexpr manual_object() noexcept {}
};
template <typename T>
__managed__ manual_object<T> managed_variable{};
#endif
template <typename Creator, typename Destroyer, typename Validator, std::size_t N>
void validate_in_managed_memory_helper(const Creator& creator, const Destroyer& destroyer, Validator (&performers)[N])
{
auto object = creator();
for (auto&& performer : performers)
{
performer.initializer(*object);
performer.validator(*object);
}
HETEROGENEOUS_SAFE_CALL(cudaGetLastError());
HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize());
sync_all();
destroyer(object);
}
template <typename T, typename... Testers, typename... Args>
void validate_managed(tester_list<Testers...>, Args... args)
{
initializer_validator<T> host_init_device_check[] = {{host_initialize<Testers>, device_validate<Testers>}...};
initializer_validator<T> device_init_host_check[] = {{device_initialize<Testers>, host_validate<Testers>}...};
auto host_constructor = [args...]() -> T* {
void* pointer;
HETEROGENEOUS_SAFE_CALL(cudaMallocManaged(&pointer, sizeof(T)));
return new (pointer) T(args...);
};
auto device_constructor = [args...]() -> T* {
void* pointer;
HETEROGENEOUS_SAFE_CALL(cudaMallocManaged(&pointer, sizeof(T)));
return device_construct<T>(pointer, args...);
};
auto host_destructor = [](T* object) {
object->~T();
HETEROGENEOUS_SAFE_CALL(cudaFree(object));
};
auto device_destructor = [](T* object) {
device_destroy(object);
HETEROGENEOUS_SAFE_CALL(cudaFree(object));
};
validate_in_managed_memory_helper(host_constructor, host_destructor, host_init_device_check);
validate_in_managed_memory_helper(host_constructor, host_destructor, device_init_host_check);
validate_in_managed_memory_helper(host_constructor, device_destructor, host_init_device_check);
validate_in_managed_memory_helper(host_constructor, device_destructor, device_init_host_check);
validate_in_managed_memory_helper(device_constructor, host_destructor, host_init_device_check);
validate_in_managed_memory_helper(device_constructor, host_destructor, device_init_host_check);
validate_in_managed_memory_helper(device_constructor, device_destructor, host_init_device_check);
validate_in_managed_memory_helper(device_constructor, device_destructor, device_init_host_check);
#if _CCCL_STD_VER >= 2014 && !defined(__clang__)
// The managed variable template part of this test is disabled under clang, pending nvbug 2790305 being fixed.
auto host_variable_constructor = [args...]() -> T* {
managed_variable<T>.construct(args...);
return &managed_variable<T>.get();
};
auto device_variable_constructor = [args...]() -> T* {
managed_variable<T>.device_construct(args...);
return &managed_variable<T>.get();
};
auto host_variable_destructor = [](T*) {
managed_variable<T>.destroy();
};
auto device_variable_destructor = [](T*) {
managed_variable<T>.device_destroy();
};
validate_in_managed_memory_helper(host_variable_constructor, host_variable_destructor, host_init_device_check);
validate_in_managed_memory_helper(host_variable_constructor, host_variable_destructor, device_init_host_check);
validate_in_managed_memory_helper(host_variable_constructor, device_variable_destructor, host_init_device_check);
validate_in_managed_memory_helper(host_variable_constructor, device_variable_destructor, device_init_host_check);
validate_in_managed_memory_helper(device_variable_constructor, host_variable_destructor, host_init_device_check);
validate_in_managed_memory_helper(device_variable_constructor, host_variable_destructor, device_init_host_check);
validate_in_managed_memory_helper(device_variable_constructor, device_variable_destructor, host_init_device_check);
validate_in_managed_memory_helper(device_variable_constructor, device_variable_destructor, device_init_host_check);
#endif
}
bool check_managed_memory_support(bool is_async)
{
int current_device, property_value;
HETEROGENEOUS_SAFE_CALL(cudaGetDevice(&current_device));
HETEROGENEOUS_SAFE_CALL(cudaDeviceGetAttribute(
&property_value, is_async ? cudaDevAttrConcurrentManagedAccess : cudaDevAttrManagedMemory, current_device));
return property_value == 1;
}
struct dummy_tester
{
template <typename... Ts>
__host__ __device__ static void initialize(Ts&&...)
{}
template <typename... Ts>
__host__ __device__ static void validate(Ts&&...)
{}
};
template <bool PerformerCombinations, typename List>
struct validate_list
{
using type = List;
};
template <bool PerformerCombinations>
struct validate_list<PerformerCombinations, tester_list<>>
{
using type = tester_list<dummy_tester>;
};
template <bool... Values>
struct any_of;
template <bool... Tail>
struct any_of<true, Tail...> : cuda::std::true_type
{};
template <bool... Tail>
struct any_of<false, Tail...> : any_of<Tail...>
{};
template <>
struct any_of<> : cuda::std::false_type
{};
template <typename TesterList>
struct is_tester_list_async;
template <typename... Testers>
struct is_tester_list_async<tester_list<Testers...>>
: any_of<async_initialize_trait<Testers>::value..., async_validate_trait<Testers>::value...>
{};
template <typename T, typename TesterList, typename... Args>
void validate_pinned(Args... args)
{
using list_t = typename validate_list<false, TesterList>::type;
list_t list0;
#ifdef DEBUG_TESTERS
printf("%s\n", "Launching permuted H/D tests");
fflush(stdout);
#endif
validate_device_dynamic<T>(list0, args...);
if (check_managed_memory_support(is_tester_list_async<list_t>::value))
{
#ifdef DEBUG_TESTERS
printf("%s\n", "Launching mixed H/D tests");
fflush(stdout);
#endif
typename validate_list<true, TesterList>::type list1;
validate_managed<T>(list1, args...);
}
}
enum class performer_side
{
initialize,
validate
};
template <typename Performer, performer_side>
struct performer_adapter;
template <typename Performer>
struct performer_adapter<Performer, performer_side::initialize>
{
using async_initialize = async_trait<Performer>;
using async_validate = async_trait<Performer>;
static constexpr auto threadcount = threadcount_trait<Performer>::value;
template <typename T>
__host__ __device__ static void initialize(T& t)
{
Performer::perform(t);
}
};
template <typename Performer>
struct performer_adapter<Performer, performer_side::validate>
{
using async_initialize = async_trait<Performer>;
using async_validate = async_trait<Performer>;
static constexpr auto threadcount = threadcount_trait<Performer>::value;
template <typename T>
__host__ __device__ static void initialize(T&)
{}
template <typename T>
__host__ __device__ static void validate(T& t)
{
Performer::perform(t);
}
};
template <typename... Ts>
struct performer_list
{};
template <typename Front, typename... Performers>
struct generate_variants_t;
template <typename... Fronts>
struct generate_variants_t<tester_list<Fronts...>>
{
using type = tester_list<Fronts..., async_tester_fence>;
};
template <typename... Fronts, typename First, typename... Performers>
struct generate_variants_t<tester_list<Fronts...>, First, Performers...>
{
using type =
append<typename generate_variants_t<tester_list<Fronts..., performer_adapter<First, performer_side::initialize>>,
Performers...>::type,
typename generate_variants_t<tester_list<Fronts..., performer_adapter<First, performer_side::validate>>,
Performers...>::type>;
};
template <typename Front, typename... Performers>
using generate_variants = typename generate_variants_t<Front, Performers...>::type;
template <typename First, typename... Rest>
struct validate_list<true, performer_list<First, Rest...>>
{
using type = generate_variants<
// Lock the first performer to initialize only.
// Otherwise, here's what would get generated (for 2 performers):
//
// ii, iv, vi, vv (i - initialize, v - validate)
//
// but because the testing process itself is symmetrical, that means that
// testing for `ii` also covers `vv`, and testing for `iv` also covers `vi`.
// Therefore, only the first half of the generated sequence is actually
// meaningfully useful.
tester_list<performer_adapter<First, performer_side::initialize>>,
Rest...>;
};
template <typename... All>
struct validate_list<false, performer_list<All...>>
{
using type = tester_list<performer_adapter<All, performer_side::initialize>...>;
};
#endif