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