blob: ed07672fcfc09dfa81ee25a9d7c01feaba7f2caa [file] [edit]
// 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());
}