| // This file is part of Eigen, a lightweight C++ template library | |
| // for linear algebra. | |
| // | |
| // 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-FileCopyrightText: The Eigen Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| // A failed CUDA runtime or library call has to reach EIGEN_GPU_CHECK_FAILED in a | |
| // release build as much as in a debug one (runtime_checks_ndebug.cpp compiles | |
| // this file with EIGEN_NO_DEBUG). The hook is overridden here to record the | |
| // failure instead of stopping the process. | |
| #include <string> | |
| struct RecordedGpuFailure { | |
| std::string error; | |
| std::string expression; | |
| std::string file; | |
| int line = 0; | |
| }; | |
| static RecordedGpuFailure g_last_failure; | |
| static int g_num_failures = 0; | |
| static bool g_throw_failures = false; // throw the record, as a user's hook may | |
| static void record_gpu_failure(const char* error, const char* expression, const char* file, int line) { | |
| ++g_num_failures; | |
| g_last_failure.error = error; | |
| g_last_failure.expression = expression; | |
| g_last_failure.file = file; | |
| g_last_failure.line = line; | |
| if (g_throw_failures) throw g_last_failure; | |
| } | |
| #define EIGEN_GPU_CHECK_FAILED(error, expression, file, line) ::record_gpu_failure(error, expression, file, line) | |
| #define EIGEN_USE_GPU | |
| #include "main.h" | |
| #include <contrib/Eigen/GPU> | |
| #include "./gpu_test_helpers.h" | |
| using namespace Eigen; | |
| static bool starts_with(const std::string& s, const std::string& prefix) { | |
| return s.compare(0, prefix.size(), prefix) == 0; | |
| } | |
| static bool ends_with(const std::string& s, const std::string& suffix) { | |
| return s.size() >= suffix.size() && s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0; | |
| } | |
| // A runtime call that fails inside the module is reported with the call's own | |
| // text and location: downloading a view of a null pointer copies from null. | |
| void test_runtime_call() { | |
| gpu::Context ctx; | |
| g_num_failures = 0; | |
| gpu::DeviceMatrix<double> bogus = gpu::DeviceMatrix<double>::view(nullptr, 4, 1); | |
| (void)bogus.toHost(ctx.stream()); | |
| VERIFY_IS_EQUAL(g_num_failures, 1); | |
| VERIFY_IS_EQUAL(g_last_failure.error, std::string("cudaErrorInvalidValue")); | |
| VERIFY(starts_with(g_last_failure.expression, "cudaMemcpyAsync")); | |
| VERIFY(ends_with(g_last_failure.file, "DeviceMatrix.h")); | |
| VERIFY(g_last_failure.line > 0); | |
| // The context is still usable, and a successful call reports nothing. | |
| const VectorXd x = VectorXd::Random(8); | |
| VERIFY_IS_APPROX(VectorXd(gpu::DeviceMatrix<double>::fromHost(x, ctx.stream()).toHost(ctx.stream())), x); | |
| VERIFY_IS_EQUAL(g_num_failures, 1); | |
| } | |
| // cuBLAS before 11.6.1 cannot name a status, see cublas_check_failed(). | |
| static std::string cublas_status_text(cublasStatus_t status, const char* name) { | |
| #if defined(CUBLAS_VERSION) && CUBLAS_VERSION >= 110601 | |
| EIGEN_UNUSED_VARIABLE(status); | |
| return name; | |
| #else | |
| EIGEN_UNUSED_VARIABLE(name); | |
| return "cuBLAS status " + std::to_string(int(status)); | |
| #endif | |
| } | |
| // Each library's check macro reports a failed status, named where the library | |
| // can name it. The macros are given the statuses directly: what a library | |
| // returns for invalid arguments differs between versions, and some calls | |
| // dereference a null handle instead of returning a status. | |
| void test_library_checks() { | |
| g_num_failures = 0; | |
| EIGEN_CUBLAS_CHECK(CUBLAS_STATUS_SUCCESS); | |
| EIGEN_NPP_CHECK(NPP_NO_OPERATION_WARNING); // positive NPP statuses are warnings | |
| VERIFY_IS_EQUAL(g_num_failures, 0); | |
| EIGEN_CUBLAS_CHECK(CUBLAS_STATUS_INVALID_VALUE); | |
| VERIFY_IS_EQUAL(g_num_failures, 1); | |
| VERIFY_IS_EQUAL(g_last_failure.error, cublas_status_text(CUBLAS_STATUS_INVALID_VALUE, "CUBLAS_STATUS_INVALID_VALUE")); | |
| VERIFY_IS_EQUAL(g_last_failure.expression, std::string("CUBLAS_STATUS_INVALID_VALUE")); | |
| VERIFY(ends_with(g_last_failure.file, "runtime_checks.cpp")); | |
| VERIFY(g_last_failure.line > 0); | |
| EIGEN_CUBLASLT_CHECK(CUBLAS_STATUS_NOT_SUPPORTED); | |
| VERIFY_IS_EQUAL(g_num_failures, 2); | |
| VERIFY_IS_EQUAL(g_last_failure.error, cublas_status_text(CUBLAS_STATUS_NOT_SUPPORTED, "CUBLAS_STATUS_NOT_SUPPORTED")); | |
| EIGEN_CUSOLVER_CHECK(CUSOLVER_STATUS_INVALID_VALUE); | |
| VERIFY_IS_EQUAL(g_num_failures, 3); | |
| VERIFY_IS_EQUAL(g_last_failure.error, std::string("CUSOLVER_STATUS_INVALID_VALUE")); | |
| EIGEN_CUSPARSE_CHECK(CUSPARSE_STATUS_INVALID_VALUE); | |
| VERIFY_IS_EQUAL(g_num_failures, 4); | |
| VERIFY_IS_EQUAL(g_last_failure.error, std::string("CUSPARSE_STATUS_INVALID_VALUE")); | |
| EIGEN_CUFFT_CHECK(CUFFT_INVALID_PLAN); | |
| VERIFY_IS_EQUAL(g_num_failures, 5); | |
| VERIFY_IS_EQUAL(g_last_failure.error, "cuFFT status " + std::to_string(int(CUFFT_INVALID_PLAN))); | |
| EIGEN_NPP_CHECK(NPP_NULL_POINTER_ERROR); | |
| VERIFY_IS_EQUAL(g_num_failures, 6); | |
| VERIFY_IS_EQUAL(g_last_failure.error, "NPP status " + std::to_string(int(NPP_NULL_POINTER_ERROR))); | |
| } | |
| // A hook that throws unwinds through the module with the cuBLAS handle back in | |
| // its pointer mode, so that later host-scalar calls on the context still work. | |
| void test_throwing_hook() { | |
| gpu::Context ctx; | |
| cublasPointerMode_t mode = CUBLAS_POINTER_MODE_DEVICE; | |
| EIGEN_CUBLAS_CHECK(cublasGetPointerMode(ctx.cublasHandle(), &mode)); | |
| VERIFY(mode == CUBLAS_POINTER_MODE_HOST); | |
| bool thrown = false; | |
| g_throw_failures = true; | |
| try { | |
| gpu::internal::with_device_pointer_mode(ctx.cublasHandle(), [] { EIGEN_CUBLAS_CHECK(CUBLAS_STATUS_ALLOC_FAILED); }); | |
| } catch (const RecordedGpuFailure&) { | |
| thrown = true; | |
| } | |
| g_throw_failures = false; | |
| VERIFY(thrown); | |
| EIGEN_CUBLAS_CHECK(cublasGetPointerMode(ctx.cublasHandle(), &mode)); | |
| VERIFY(mode == CUBLAS_POINTER_MODE_HOST); | |
| const VectorXd x = VectorXd::Random(16); | |
| gpu::DeviceMatrix<double> d_x = gpu::DeviceMatrix<double>::fromHost(x, ctx.stream()); | |
| d_x.scale(ctx, 2.0); | |
| VERIFY_IS_APPROX(VectorXd(d_x.toHost(ctx.stream())), VectorXd(2.0 * x)); | |
| } | |
| EIGEN_DECLARE_TEST(gpu_runtime_checks) { | |
| gpu_test::require_cuda_device(); | |
| CALL_SUBTEST(test_runtime_call()); | |
| CALL_SUBTEST(test_library_checks()); | |
| CALL_SUBTEST(test_throwing_hook()); | |
| } |