| // 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 |