diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index cae09804a..3fd85ebdb 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -143,6 +143,11 @@ endif() if(CUDA_FOUND) list (APPEND MAIN_SOURCE_FILES opm/simulators/linalg/bda/cuda/cusparseSolverBackend.cu) list (APPEND MAIN_SOURCE_FILES opm/simulators/linalg/bda/cuda/cuWellContributions.cu) + + # CUISTL + list (APPEND PUBLIC_HEADER_FILES opm/simulators/linalg/cuistl/detail/cuda_safe_call.hpp) + list (APPEND PUBLIC_HEADER_FILES opm/simulators/linalg/cuistl/detail/cusparse_safe_call.hpp) + list (APPEND PUBLIC_HEADER_FILES opm/simulators/linalg/cuistl/detail/cublas_safe_call.hpp) endif() if(OPENCL_FOUND) list (APPEND MAIN_SOURCE_FILES opm/simulators/linalg/bda/BlockedMatrix.cpp) @@ -220,6 +225,9 @@ if(MPI_FOUND) endif() if(CUDA_FOUND) list(APPEND TEST_SOURCE_FILES tests/test_cusparseSolver.cpp) + list(APPEND TEST_SOURCE_FILES tests/cuistl/test_cusparse_safe_call.cpp) + list(APPEND TEST_SOURCE_FILES tests/cuistl/test_cublas_safe_call.cpp) + list(APPEND TEST_SOURCE_FILES tests/cuistl/test_cuda_safe_call.cpp) endif() if(OPENCL_FOUND) list(APPEND TEST_SOURCE_FILES tests/test_openclSolver.cpp) diff --git a/opm/simulators/linalg/cuistl/detail/cublas_safe_call.hpp b/opm/simulators/linalg/cuistl/detail/cublas_safe_call.hpp new file mode 100644 index 000000000..ab1d8d078 --- /dev/null +++ b/opm/simulators/linalg/cuistl/detail/cublas_safe_call.hpp @@ -0,0 +1,88 @@ +/* + Copyright 2022-2023 SINTEF AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#ifndef CUBLAS_SAFE_CALL_HPP +#define CUBLAS_SAFE_CALL_HPP +#include +#include +#include +#include +#define CHECK_CUBLAS_ERROR_TYPE(code, x) \ + if (code == x) { \ + return #x; \ + } +namespace +{ +/** + * @brief getCublasErrorMessage Converts an error code returned from a cublas function a human readable string. + * @param code an error code from a cublas routine + * @return a human readable string. + */ +inline std::string +getCublasErrorMessage(int code) +{ + CHECK_CUBLAS_ERROR_TYPE(code, CUBLAS_STATUS_SUCCESS); + CHECK_CUBLAS_ERROR_TYPE(code, CUBLAS_STATUS_NOT_INITIALIZED); + CHECK_CUBLAS_ERROR_TYPE(code, CUBLAS_STATUS_ALLOC_FAILED); + CHECK_CUBLAS_ERROR_TYPE(code, CUBLAS_STATUS_INVALID_VALUE); + CHECK_CUBLAS_ERROR_TYPE(code, CUBLAS_STATUS_ARCH_MISMATCH); + CHECK_CUBLAS_ERROR_TYPE(code, CUBLAS_STATUS_MAPPING_ERROR); + CHECK_CUBLAS_ERROR_TYPE(code, CUBLAS_STATUS_EXECUTION_FAILED); + CHECK_CUBLAS_ERROR_TYPE(code, CUBLAS_STATUS_INTERNAL_ERROR); + CHECK_CUBLAS_ERROR_TYPE(code, CUBLAS_STATUS_NOT_SUPPORTED); + CHECK_CUBLAS_ERROR_TYPE(code, CUBLAS_STATUS_LICENSE_ERROR); + + return fmt::format("UNKNOWN CUBLAS ERROR {}.", code); +} +} // namespace +#undef CHECK_CUSPRASE_ERROR_TYPE + +/** + * @brief OPM_CUBLAS_SAFE_CALL checks the return type of the cublas expression (function call) and throws an exception + * if it does not equal CUBLAS_STATUS_SUCCESS. + * + * Example usage: + * @code{.cpp} + * #include + * #include + * + * void some_function() { + * cublasHandle_t cublasHandle; + * OPM_CUBLAS_SAFE_CALL(cublasCreate(&cublasHandle)); + * } + * @endcode + * + * @note This should be used for any call to cuBlas unless you have a good reason not to. + */ +#define OPM_CUBLAS_SAFE_CALL(expression) \ + do { \ + cublasStatus_t error = expression; \ + if (error != CUBLAS_STATUS_SUCCESS) { \ + OPM_THROW(std::runtime_error, \ + fmt::format("cuBLAS expression did not execute correctly. Expression was: \n\n" \ + " {}\n\n" \ + "in function {}, in {}, at line {}.\n" \ + "CuBLAS error code was: {}\n", \ + #expression, \ + __func__, \ + __FILE__, \ + __LINE__, \ + getCublasErrorMessage(error))); \ + } \ + } while (false) +#endif // CUBLAS_SAFE_CALL_HPP diff --git a/opm/simulators/linalg/cuistl/detail/cuda_safe_call.hpp b/opm/simulators/linalg/cuistl/detail/cuda_safe_call.hpp new file mode 100644 index 000000000..71e42bebf --- /dev/null +++ b/opm/simulators/linalg/cuistl/detail/cuda_safe_call.hpp @@ -0,0 +1,58 @@ +/* + Copyright 2022-2023 SINTEF AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#ifndef CUDA_SAFE_CALL_HPP +#define CUDA_SAFE_CALL_HPP +#include +#include +#include + +/** + * @brief OPM_CUDA_SAFE_CALL checks the return type of the CUDA expression (function call) and throws an exception if it + * does not equal cudaSuccess. + * + * Example usage: + * @code{.cpp} + * #include + * #include + * + * void some_function() { + * void* somePointer; + * OPM_CUDA_SAFE_CALL(cudaMalloc(&somePointer, 1)); + * } + * @endcode + * + * @note This should be used for any call to cuSparse unless you have a good reason not to. + */ +#define OPM_CUDA_SAFE_CALL(expression) \ + do { \ + cudaError_t error = expression; \ + if (error != cudaSuccess) { \ + OPM_THROW(std::runtime_error, \ + fmt::format("CUDA expression did not execute correctly. Expression was: \n" \ + " {}\n" \ + "CUDA error was {}\n" \ + "in function {}, in {}, at line {}\n", \ + #expression, \ + cudaGetErrorString(error), \ + __func__, \ + __FILE__, \ + __LINE__)); \ + } \ + } while (false) +#endif diff --git a/opm/simulators/linalg/cuistl/detail/cusparse_safe_call.hpp b/opm/simulators/linalg/cuistl/detail/cusparse_safe_call.hpp new file mode 100644 index 000000000..ccdb0a93e --- /dev/null +++ b/opm/simulators/linalg/cuistl/detail/cusparse_safe_call.hpp @@ -0,0 +1,91 @@ +/* + Copyright 2022-2023 SINTEF AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#ifndef CUSPARSE_SAFE_CALL_HPP +#define CUSPARSE_SAFE_CALL_HPP +#include +#include +#include +#include +#define CHECK_CUSPARSE_ERROR_TYPE(code, x) \ + if (code == x) { \ + return #x; \ + } +namespace +{ + +/** + * @brief getCusparseErrorMessage Converts an error code returned from a cusparse function a human readable string. + * @param code an error code from a cusparse routine + * @return a human readable string. + */ +inline std::string +getCusparseErrorMessage(int code) +{ + CHECK_CUSPARSE_ERROR_TYPE(code, CUSPARSE_STATUS_SUCCESS); + CHECK_CUSPARSE_ERROR_TYPE(code, CUSPARSE_STATUS_NOT_INITIALIZED); + CHECK_CUSPARSE_ERROR_TYPE(code, CUSPARSE_STATUS_ALLOC_FAILED); + CHECK_CUSPARSE_ERROR_TYPE(code, CUSPARSE_STATUS_INVALID_VALUE); + CHECK_CUSPARSE_ERROR_TYPE(code, CUSPARSE_STATUS_ARCH_MISMATCH); + CHECK_CUSPARSE_ERROR_TYPE(code, CUSPARSE_STATUS_MAPPING_ERROR); + CHECK_CUSPARSE_ERROR_TYPE(code, CUSPARSE_STATUS_EXECUTION_FAILED); + CHECK_CUSPARSE_ERROR_TYPE(code, CUSPARSE_STATUS_INTERNAL_ERROR); + CHECK_CUSPARSE_ERROR_TYPE(code, CUSPARSE_STATUS_MATRIX_TYPE_NOT_SUPPORTED); + CHECK_CUSPARSE_ERROR_TYPE(code, CUSPARSE_STATUS_ZERO_PIVOT); + CHECK_CUSPARSE_ERROR_TYPE(code, CUSPARSE_STATUS_NOT_SUPPORTED); + CHECK_CUSPARSE_ERROR_TYPE(code, CUSPARSE_STATUS_INSUFFICIENT_RESOURCES); + return fmt::format("UNKNOWN CUSPARSE ERROR {}.", code); +} +} // namespace + + +#undef CHECK_CUSPARSE_ERROR_TYPE + +/** + * @brief OPM_CUSPARSE_SAFE_CALL checks the return type of the cusparse expression (function call) and throws an + * exception if it does not equal CUSPARSE_STATUS_SUCCESS. + * + * Example usage: + * @code{.cpp} + * #include + * #include + * + * void some_function() { + * cusparseHandle_t cusparseHandle; + * OPM_CUSPARSE_SAFE_CALL(cusparseCreate(&cublasHandle)); + * } + * @endcode + * + * @note This should be used for any call to cuSparse unless you have a good reason not to. + */ +#define OPM_CUSPARSE_SAFE_CALL(expression) \ + do { \ + cusparseStatus_t error = expression; \ + if (error != CUSPARSE_STATUS_SUCCESS) { \ + OPM_THROW(std::runtime_error, \ + fmt::format("cuSparse expression did not execute correctly. Expression was: \n\n" \ + " {}\n\nin function {}, in {}, at line {}\n" \ + "CuSparse error code was: {}\n", \ + #expression, \ + __func__, \ + __FILE__, \ + __LINE__, \ + getCusparseErrorMessage(error))); \ + } \ + } while (false) +#endif // CUSPARSE_SAFE_CALL_HPP diff --git a/tests/cuistl/test_cublas_safe_call.cpp b/tests/cuistl/test_cublas_safe_call.cpp new file mode 100644 index 000000000..61906a840 --- /dev/null +++ b/tests/cuistl/test_cublas_safe_call.cpp @@ -0,0 +1,47 @@ +/* + Copyright 2022-2023 SINTEF AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#include + +#define BOOST_TEST_MODULE TestCublasSafeCall + +#include +#include +#include + +BOOST_AUTO_TEST_CASE(TestCreateHandle) +{ + cublasHandle_t cublasHandle; + BOOST_CHECK_NO_THROW(OPM_CUBLAS_SAFE_CALL(cublasCreate(&cublasHandle));); +} + +BOOST_AUTO_TEST_CASE(TestThrows) +{ + std::vector errorCodes {{CUBLAS_STATUS_NOT_INITIALIZED, + CUBLAS_STATUS_ALLOC_FAILED, + CUBLAS_STATUS_INVALID_VALUE, + CUBLAS_STATUS_ARCH_MISMATCH, + CUBLAS_STATUS_MAPPING_ERROR, + CUBLAS_STATUS_EXECUTION_FAILED, + CUBLAS_STATUS_INTERNAL_ERROR, + CUBLAS_STATUS_NOT_SUPPORTED, + CUBLAS_STATUS_LICENSE_ERROR}}; + for (auto code : errorCodes) { + BOOST_CHECK_THROW(OPM_CUBLAS_SAFE_CALL(code), std::exception); + } +} diff --git a/tests/cuistl/test_cuda_safe_call.cpp b/tests/cuistl/test_cuda_safe_call.cpp new file mode 100644 index 000000000..35c3e4976 --- /dev/null +++ b/tests/cuistl/test_cuda_safe_call.cpp @@ -0,0 +1,40 @@ +/* + Copyright 2022-2023 SINTEF AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#include + +#define BOOST_TEST_MODULE TestCudaSafeCall +#include +#include +#include + +BOOST_AUTO_TEST_CASE(TestCudaMalloc) +{ + void* pointer; + BOOST_CHECK_NO_THROW(OPM_CUDA_SAFE_CALL(cudaMalloc(&pointer, 1));); +} + + +BOOST_AUTO_TEST_CASE(TestThrows) +{ + // Just testing a subset here. + std::vector errorCodes {{cudaErrorAddressOfConstant, cudaErrorAlreadyAcquired}}; + for (auto code : errorCodes) { + BOOST_CHECK_THROW(OPM_CUDA_SAFE_CALL(code), std::exception); + } +} diff --git a/tests/cuistl/test_cusparse_safe_call.cpp b/tests/cuistl/test_cusparse_safe_call.cpp new file mode 100644 index 000000000..cdeebaab5 --- /dev/null +++ b/tests/cuistl/test_cusparse_safe_call.cpp @@ -0,0 +1,49 @@ +/* + Copyright 2022-2023 SINTEF AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#include + +#define BOOST_TEST_MODULE TestCusparseSafeCall + +#include +#include +#include + +BOOST_AUTO_TEST_CASE(TestCreateHandle) +{ + cusparseHandle_t cusparseHandle; + BOOST_CHECK_NO_THROW(OPM_CUSPARSE_SAFE_CALL(cusparseCreate(&cusparseHandle));); +} + +BOOST_AUTO_TEST_CASE(TestThrows) +{ + std::vector errorCodes {{CUSPARSE_STATUS_NOT_INITIALIZED, + CUSPARSE_STATUS_ALLOC_FAILED, + CUSPARSE_STATUS_INVALID_VALUE, + CUSPARSE_STATUS_ARCH_MISMATCH, + CUSPARSE_STATUS_MAPPING_ERROR, + CUSPARSE_STATUS_EXECUTION_FAILED, + CUSPARSE_STATUS_INTERNAL_ERROR, + CUSPARSE_STATUS_MATRIX_TYPE_NOT_SUPPORTED, + CUSPARSE_STATUS_ZERO_PIVOT, + CUSPARSE_STATUS_NOT_SUPPORTED, + CUSPARSE_STATUS_INSUFFICIENT_RESOURCES}}; + for (auto code : errorCodes) { + BOOST_CHECK_THROW(OPM_CUSPARSE_SAFE_CALL(code), std::exception); + } +}