blob: 7801c4140f09b78afd0a51d5fe6b1ae20a9d7e9c [file] [edit]
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2026 Rasmus Munk Larsen <rmlarsen@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// SPDX-License-Identifier: MPL-2.0
// Helpers shared across GPU library tests:
// * runtime probes that exit(77) (CI skip) when a library is unavailable
// * a small make_test_value() that constructs a Scalar with an imaginary
// component for complex types so the complex code paths are genuinely
// exercised — without this, Scalar(real_value) silently zeros the imag.
#ifndef EIGEN_UNSUPPORTED_TEST_GPU_TEST_HELPERS_H
#define EIGEN_UNSUPPORTED_TEST_GPU_TEST_HELPERS_H
#include <Eigen/Core>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdlib>
#include <iostream>
#include <mutex>
#include <type_traits>
namespace gpu_test {
template <typename Scalar, typename RealScalar>
inline std::enable_if_t<Eigen::NumTraits<Scalar>::IsComplex, Scalar> make_test_value(RealScalar re, RealScalar im) {
return Scalar(re, im);
}
template <typename Scalar, typename RealScalar>
inline std::enable_if_t<!Eigen::NumTraits<Scalar>::IsComplex, Scalar> make_test_value(RealScalar re,
RealScalar /*im*/) {
return Scalar(re);
}
#ifdef CUDART_VERSION
// The CUDA runtime loads without a driver, so a GPU test binary starts happily on
// a machine with no device and only fails at its first allocation -- as an abort
// out of EIGEN_CUDA_RUNTIME_CHECK, not a skip. Probe the device count first so
// those runs report as skipped, the way the per-library probes below do.
inline void require_cuda_device() {
int count = 0;
const cudaError_t status = cudaGetDeviceCount(&count);
if (status != cudaSuccess || count == 0) {
std::cout << "SKIP: GPU tests require a CUDA device. cudaGetDeviceCount reported " << cudaGetErrorString(status)
<< " with " << count << " device(s)." << std::endl;
std::exit(77);
}
}
// Parks `stream` behind a host function that returns when the ParkedStream is
// destroyed or after `timeout`, whichever comes first. An operation that does
// not wait for `stream` returns while held() is still true; one that does
// returns only after the timeout, when held() is false.
class ParkedStream {
public:
explicit ParkedStream(cudaStream_t stream, std::chrono::milliseconds timeout = std::chrono::seconds(1))
: stream_(stream), timeout_(timeout) {
EIGEN_CUDA_RUNTIME_CHECK(cudaLaunchHostFunc(stream_, &ParkedStream::hold, this));
}
~ParkedStream() {
{
std::lock_guard<std::mutex> lock(mutex_);
unpark_ = true;
}
cv_.notify_one();
(void)cudaStreamSynchronize(stream_);
}
ParkedStream(const ParkedStream&) = delete;
ParkedStream& operator=(const ParkedStream&) = delete;
bool held() const { return !released_.load(std::memory_order_acquire); }
private:
static void CUDART_CB hold(void* data) {
ParkedStream* self = static_cast<ParkedStream*>(data);
std::unique_lock<std::mutex> lock(self->mutex_);
self->cv_.wait_for(lock, self->timeout_, [self] { return self->unpark_; });
self->released_.store(true, std::memory_order_release);
}
cudaStream_t stream_;
std::chrono::milliseconds timeout_;
std::mutex mutex_;
std::condition_variable cv_;
bool unpark_ = false;
std::atomic<bool> released_{false};
};
#endif
#ifdef CUSOLVER_VERSION
// cusolverDnXpotrf in cuSOLVER 11.4.1 (CUDA 11.8) and 11.4.2 (CUDA 12.0) reports
// success on a matrix that is not positive definite and returns a NaN factor;
// 11.4.4 (CUDA 12.1) and later set info > 0 again. Tests that expect
// NumericalIssue from such an LLT pass the reported info here: on an affected
// library this prints a warning and returns true, and the caller skips the check.
inline bool cusolver_potrf_missed_non_spd(Eigen::ComputationInfo info) {
constexpr int kFirstVersionReportingNonSpd = 11404;
int version = 0;
if (cusolverGetVersion(&version) != CUSOLVER_STATUS_SUCCESS || version >= kFirstVersionReportingNonSpd ||
info != Eigen::Success) {
return false;
}
std::cout << "WARNING: cuSOLVER " << version / 1000 << '.' << version / 100 % 10 << '.' << version % 100
<< " reported success for the potrf of a matrix that is not positive definite (defect in 11.4.1 and "
"11.4.2, fixed in 11.4.4); skipping the NumericalIssue check."
<< std::endl;
return true;
}
#endif
#ifdef CUDSS_VERSION
inline void require_cudss_context() {
cudssHandle_t handle = nullptr;
const cudssStatus_t status = cudssCreate(&handle);
if (status != CUDSS_STATUS_SUCCESS) {
std::cout << "SKIP: cuDSS tests require an initialized cuDSS context. cudssCreate failed with status "
<< static_cast<int>(status) << std::endl;
std::exit(77);
}
EIGEN_CUDSS_CHECK(cudssDestroy(handle));
}
#endif
#ifdef CUSPARSE_VERSION
inline void require_cusparse_context() {
cusparseHandle_t handle = nullptr;
const cusparseStatus_t status = cusparseCreate(&handle);
if (status != CUSPARSE_STATUS_SUCCESS) {
std::cout << "SKIP: cuSPARSE tests require an initialized cuSPARSE context. cusparseCreate failed with status "
<< static_cast<int>(status) << std::endl;
std::exit(77);
}
EIGEN_CUSPARSE_CHECK(cusparseDestroy(handle));
}
#endif
#ifdef CUFFT_VERSION
inline void require_cufft_context() {
cufftHandle plan = 0;
// cufftCreate allocates a plan handle without configuring it; succeeds only
// when the cuFFT runtime is loadable.
const cufftResult status = cufftCreate(&plan);
if (status != CUFFT_SUCCESS) {
std::cout << "SKIP: cuFFT tests require a working cuFFT runtime. cufftCreate failed with status "
<< static_cast<int>(status) << std::endl;
std::exit(77);
}
EIGEN_CUFFT_CHECK(cufftDestroy(plan));
}
#endif
} // namespace gpu_test
#endif // EIGEN_UNSUPPORTED_TEST_GPU_TEST_HELPERS_H