// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-806117. // // This file is part of the MFEM library. For more information and source code // availability visit https://mfem.org. // // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. #include "gpu_blas.hpp" #include "../../general/forall.hpp" #if defined(MFEM_USE_CUDA) #define MFEM_cu_or_hip(stub) cu##stub #define MFEM_CU_or_HIP(stub) CU##stub #elif defined(MFEM_USE_HIP) #define MFEM_cu_or_hip(stub) hip##stub #define MFEM_CU_or_HIP(stub) HIP##stub #endif #define MFEM_CONCAT(x, y, z) MFEM_CONCAT_(x, y, z) #define MFEM_CONCAT_(x, y, z) x ## y ## z #ifdef MFEM_USE_SINGLE #define MFEM_GPUBLAS_PREFIX(stub) MFEM_CONCAT(MFEM_cu_or_hip(blas), S, stub) #elif defined(MFEM_USE_DOUBLE) #define MFEM_GPUBLAS_PREFIX(stub) MFEM_CONCAT(MFEM_cu_or_hip(blas), D, stub) #endif #define MFEM_BLAS_SUCCESS MFEM_CU_or_HIP(BLAS_STATUS_SUCCESS) namespace mfem { GPUBlas &GPUBlas::Instance() { static GPUBlas instance; return instance; } GPUBlas::HandleType GPUBlas::Handle() { return Instance().handle; } #ifndef MFEM_USE_CUDA_OR_HIP GPUBlas::GPUBlas() { } GPUBlas::~GPUBlas() { } void GPUBlas::EnableAtomics() { } void GPUBlas::DisableAtomics() { } #else using blasStatus_t = MFEM_cu_or_hip(blasStatus_t); GPUBlas::GPUBlas() { blasStatus_t status = MFEM_cu_or_hip(blasCreate)(&handle); MFEM_VERIFY(status == MFEM_BLAS_SUCCESS, "Cannot initialize GPU BLAS."); } GPUBlas::~GPUBlas() { MFEM_cu_or_hip(blasDestroy)(handle); } void GPUBlas::EnableAtomics() { const blasStatus_t status = MFEM_cu_or_hip(blasSetAtomicsMode)( Handle(), MFEM_CU_or_HIP(BLAS_ATOMICS_ALLOWED)); MFEM_VERIFY(status == MFEM_BLAS_SUCCESS, "GPU BLAS error."); } void GPUBlas::DisableAtomics() { const blasStatus_t status = MFEM_cu_or_hip(blasSetAtomicsMode)( Handle(), MFEM_CU_or_HIP(BLAS_ATOMICS_NOT_ALLOWED)); MFEM_VERIFY(status == MFEM_BLAS_SUCCESS, "GPU BLAS error."); } void GPUBlasBatchedLinAlg::AddMult(const DenseTensor &A, const Vector &x, Vector &y, real_t alpha, real_t beta, Op op) const { const bool tr = (op == Op::T); const int m = tr ? A.SizeJ() : A.SizeI(); const int n = tr ? A.SizeI() : A.SizeJ(); const int n_mat = A.SizeK(); const int k = x.Size() / n / n_mat; auto d_A = A.Read(); auto d_x = x.Read(); // Shape: (n, k, n_mat) auto d_y = beta == 0.0 ? y.Write() : y.ReadWrite(); // Shape (m, k, n_mat) const auto op_A = tr ? MFEM_CU_or_HIP(BLAS_OP_T) : MFEM_CU_or_HIP(BLAS_OP_N); const auto op_B = MFEM_CU_or_HIP(BLAS_OP_N); const blasStatus_t status = MFEM_GPUBLAS_PREFIX(gemmStridedBatched)( GPUBlas::Handle(), op_A, op_B, m, k, n, &alpha, d_A, m, m*n, d_x, n, n*k, &beta, d_y, m, m*k, n_mat); MFEM_VERIFY(status == MFEM_BLAS_SUCCESS, "GPU BLAS error."); } void GPUBlasBatchedLinAlg::LUFactor(DenseTensor &A, Array &P) const { const int n = A.SizeI(); const int n_mat = A.SizeK(); P.SetSize(n*n_mat); Array info_array(n_mat); real_t *A_base = A.ReadWrite(); Array A_ptrs(n_mat); real_t **d_A_ptrs = A_ptrs.Write(); mfem::forall(n_mat, [=] MFEM_HOST_DEVICE (int i) { d_A_ptrs[i] = A_base + i*n*n; }); const blasStatus_t status = MFEM_GPUBLAS_PREFIX(getrfBatched)( GPUBlas::Handle(), n, d_A_ptrs, n, P.Write(), info_array.Write(), n_mat); MFEM_VERIFY(status == MFEM_BLAS_SUCCESS, ""); } void GPUBlasBatchedLinAlg::LUSolve( const DenseTensor &LU, const Array &P, Vector &x) const { const int n = LU.SizeI(); const int n_mat = LU.SizeK(); const int n_rhs = x.Size() / n / n_mat; Array A_ptrs(n_mat); real_t **d_A_ptrs = A_ptrs.Write(); Array B_ptrs(n_mat); real_t **d_B_ptrs = B_ptrs.Write(); { real_t *A_base = const_cast(LU.Read()); real_t *B_base = x.ReadWrite(); mfem::forall(n_mat, [=] MFEM_HOST_DEVICE (int i) { d_A_ptrs[i] = A_base + i*n*n; d_B_ptrs[i] = B_base + i*n*n_rhs; }); } int info = 0; const blasStatus_t status = MFEM_GPUBLAS_PREFIX(getrsBatched)( GPUBlas::Handle(), MFEM_CU_or_HIP(BLAS_OP_N), n, n_rhs, d_A_ptrs, n, P.Read(), d_B_ptrs, n, &info, n_mat); MFEM_VERIFY(status == MFEM_BLAS_SUCCESS, ""); } void GPUBlasBatchedLinAlg::Invert(DenseTensor &A) const { const int n = A.SizeI(); const int n_mat = A.SizeK(); DenseTensor LU(A.SizeI(), A.SizeJ(), A.SizeK()); LU.Write(); LU.GetMemory().CopyFrom(A.GetMemory(), A.TotalSize()); Array LU_ptrs(n_mat); Array A_ptrs(n_mat); real_t **d_A_ptrs = A_ptrs.Write(); real_t **d_LU_ptrs = LU_ptrs.Write(); { real_t *A_base = A.ReadWrite(); real_t *LU_base = LU.Write(); mfem::forall(n_mat, [=] MFEM_HOST_DEVICE (int i) { d_A_ptrs[i] = A_base + i*n*n; d_LU_ptrs[i] = LU_base + i*n*n; }); } Array P(n*n_mat); Array info_array(n_mat); blasStatus_t status; status = MFEM_GPUBLAS_PREFIX(getrfBatched)( GPUBlas::Handle(), n, d_LU_ptrs, n, P.Write(), info_array.Write(), n_mat); MFEM_VERIFY(status == MFEM_BLAS_SUCCESS, ""); status = MFEM_GPUBLAS_PREFIX(getriBatched)( GPUBlas::Handle(), n, d_LU_ptrs, n, P.ReadWrite(), d_A_ptrs, n, info_array.Write(), n_mat); MFEM_VERIFY(status == MFEM_BLAS_SUCCESS, ""); } #endif } // namespace mfem