| // 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 |
| |
| // Dispatch functions mapping DeviceMatrix expressions to NVIDIA library calls, |
| // plus the DeviceMatrix members that need a complete gpu::Context. The |
| // expression argument selects the dispatch() overload. |
| |
| #ifndef EIGEN_GPU_DEVICE_DISPATCH_H |
| #define EIGEN_GPU_DEVICE_DISPATCH_H |
| |
| // IWYU pragma: private |
| #include "./InternalHeaderCheck.h" |
| |
| #include <cstdint> |
| |
| #include "./DeviceExpr.h" |
| #include "./DeviceBlasExpr.h" |
| #include "./DeviceSolverExpr.h" |
| #include "./GpuContext.h" |
| #include "./CuSolverSupport.h" |
| |
| namespace Eigen { |
| namespace gpu { |
| namespace internal { |
| template <typename Scalar> |
| bool aliases_device_memory(const DeviceMatrix<Scalar>& a, const DeviceMatrix<Scalar>& b) { |
| return a.data() != nullptr && a.data() == b.data(); |
| } |
| |
| template <typename Lhs, typename Rhs> |
| void dispatch(Context& ctx, DeviceMatrix<scalar_type_t<Lhs>>& dst, const GemmExpr<Lhs, Rhs>& expr, |
| scalar_type_t<Lhs> beta_val, scalar_type_t<Lhs> alpha_scale = scalar_type_t<Lhs>(1)) { |
| using Scalar = scalar_type_t<Lhs>; |
| using traits_lhs = device_expr_traits<Lhs>; |
| using traits_rhs = device_expr_traits<Rhs>; |
| |
| const DeviceMatrix<Scalar>& A = traits_lhs::matrix(expr.lhs()); |
| const DeviceMatrix<Scalar>& B = traits_rhs::matrix(expr.rhs()); |
| |
| // cuBLAS leaves C aliasing A or B undefined. |
| eigen_assert(!aliases_device_memory(dst, A) && "GEMM: output aliases left operand (use a temporary)"); |
| eigen_assert(!aliases_device_memory(dst, B) && "GEMM: output aliases right operand (use a temporary)"); |
| |
| constexpr cublasOperation_t transA = to_cublas_op(traits_lhs::op); |
| constexpr cublasOperation_t transB = to_cublas_op(traits_rhs::op); |
| |
| const int64_t m = (traits_lhs::op == GpuOp::NoTrans) ? A.rows() : A.cols(); |
| const int64_t k = (traits_lhs::op == GpuOp::NoTrans) ? A.cols() : A.rows(); |
| const int64_t n = (traits_rhs::op == GpuOp::NoTrans) ? B.cols() : B.rows(); |
| const int64_t rhs_k = (traits_rhs::op == GpuOp::NoTrans) ? B.rows() : B.cols(); |
| |
| eigen_assert(k == rhs_k && "DeviceMatrix GEMM dimension mismatch"); |
| |
| const int64_t lda = A.rows(); |
| const int64_t ldb = B.rows(); |
| |
| if (!dst.empty()) { |
| dst.waitReady(ctx.stream()); |
| } |
| |
| const bool resized = dst.empty() || dst.rows() != m || dst.cols() != n; |
| if (resized) { |
| dst.resize(m, n); |
| } |
| const int64_t ldc = dst.rows(); |
| |
| Scalar alpha_local = alpha_scale * traits_lhs::alpha(expr.lhs()) * traits_rhs::alpha(expr.rhs()); |
| |
| A.waitReady(ctx.stream()); |
| B.waitReady(ctx.stream()); |
| |
| if (resized && beta_val != Scalar(0) && dst.sizeInBytes() > 0) { |
| EIGEN_CUDA_RUNTIME_CHECK(cudaMemsetAsync(dst.data(), 0, dst.sizeInBytes(), ctx.stream())); |
| } |
| |
| // cuBLAS reads alpha and beta through host pointers. Holding them in an array |
| // keeps the compiler from eliding their stack slots — at -O1+ clang and MSVC |
| // otherwise drop the stores for complex types, leaving cuBLAS with a dangling |
| // pointer. |
| Scalar scalars[2] = {alpha_local, beta_val}; |
| cublaslt_gemm(ctx.cublasLtHandle(), ctx.cublasHandle(), transA, transB, m, n, k, &scalars[0], A.data(), lda, B.data(), |
| ldb, &scalars[1], dst.data(), ldc, ctx.gemmWorkspace(), ctx.gemmPlanCache(), |
| ctx.cublasLtMaxWorkspaceBytes(), ctx.stream()); |
| |
| dst.recordReady(ctx.stream()); |
| } |
| |
| // Debug-build status check shared by the one-shot solver dispatches: syncs |
| // the stream and asserts on the two info words (factorize, solve). Release |
| // builds skip both the check and the sync — one-shot expressions are then |
| // fully async with no failure detection; use gpu::LLT / gpu::LU + info() |
| // when failures must be detected. |
| inline void oneshot_check_info(Context& ctx, OneShotSolverScratch& scratch, const char* what) { |
| #ifdef EIGEN_NO_DEBUG |
| EIGEN_UNUSED_VARIABLE(ctx); |
| EIGEN_UNUSED_VARIABLE(scratch); |
| EIGEN_UNUSED_VARIABLE(what); |
| #else |
| int* info_words = static_cast<int*>(scratch.h_info.get()); |
| EIGEN_CUDA_RUNTIME_CHECK( |
| cudaMemcpyAsync(info_words, scratch.d_info.get(), kOneShotInfoBytes, cudaMemcpyDeviceToHost, ctx.stream())); |
| EIGEN_CUDA_RUNTIME_CHECK(cudaStreamSynchronize(ctx.stream())); |
| eigen_assert(info_words[0] == 0 && "cuSOLVER one-shot factorization failed" && what); |
| eigen_assert(info_words[1] == 0 && "cuSOLVER one-shot solve failed" && what); |
| EIGEN_UNUSED_VARIABLE(what); |
| #endif |
| } |
| |
| template <typename Scalar, int UpLo> |
| void dispatch(Context& ctx, DeviceMatrix<Scalar>& dst, const LltSolveExpr<Scalar, UpLo>& expr) { |
| const DeviceMatrix<Scalar>& A = expr.matrix(); |
| const DeviceMatrix<Scalar>& B = expr.rhs(); |
| |
| eigen_assert(A.rows() == A.cols() && "LLT requires a square matrix"); |
| eigen_assert(B.rows() == A.rows() && "LLT solve: RHS rows must match matrix size"); |
| |
| if (A.rows() == 0 || B.cols() == 0) { |
| if (!dst.empty()) dst.waitReady(ctx.stream()); |
| dst.resize(A.rows(), B.cols()); |
| return; |
| } |
| |
| A.waitReady(ctx.stream()); |
| B.waitReady(ctx.stream()); |
| if (!dst.empty()) dst.waitReady(ctx.stream()); |
| |
| // thread_local: must outlive the async kernels (no end-of-call sync), and |
| // only TUs that instantiate the one-shot path pull in cuSOLVER symbols. |
| static thread_local CusolverParams params; |
| constexpr cublasFillMode_t uplo = cusolver_fill_mode<UpLo>::value; |
| const int64_t n = static_cast<int64_t>(A.rows()); |
| constexpr cudaDataType_t dtype = cuda_data_type<Scalar>::value; |
| OneShotSolverScratch& scratch = ctx.oneshotSolverScratch(); |
| { |
| const size_t mat_bytes = A.sizeInBytes(); |
| // Context-owned grow-only scratch: no per-call allocation, no end-of-call sync. |
| ensure_sized(scratch.d_factor, mat_bytes); |
| EIGEN_CUDA_RUNTIME_CHECK( |
| cudaMemcpyAsync(scratch.d_factor.get(), A.data(), mat_bytes, cudaMemcpyDeviceToDevice, ctx.stream())); |
| } |
| const int64_t lda = static_cast<int64_t>(A.rows()); |
| size_t dev_ws = 0; |
| size_t host_ws = 0; |
| EIGEN_CUSOLVER_CHECK(cusolverDnXpotrf_bufferSize(ctx.cusolverHandle(), params.p, uplo, n, dtype, |
| scratch.d_factor.get(), lda, dtype, &dev_ws, &host_ws)); |
| ensure_sized(scratch.d_workspace, dev_ws); |
| if (scratch.h_workspace.size() < host_ws) scratch.h_workspace.resize(host_ws); |
| // Two info slots (potrf, potrs) so both kernels queue back-to-back. If potrf |
| // fails, potrs runs on garbage but the debug check catches both at once. |
| int* d_info_potrf = static_cast<int*>(scratch.d_info.get()); |
| int* d_info_potrs = d_info_potrf + 1; |
| EIGEN_CUSOLVER_CHECK(cusolverDnXpotrf(ctx.cusolverHandle(), params.p, uplo, n, dtype, scratch.d_factor.get(), lda, |
| dtype, scratch.d_workspace.get(), dev_ws, |
| host_ws > 0 ? scratch.h_workspace.data() : nullptr, host_ws, d_info_potrf)); |
| |
| dst.resize(n, B.cols()); |
| const size_t rhs_bytes = B.sizeInBytes(); |
| EIGEN_CUDA_RUNTIME_CHECK(cudaMemcpyAsync(dst.data(), B.data(), rhs_bytes, cudaMemcpyDeviceToDevice, ctx.stream())); |
| |
| const int64_t nrhs = static_cast<int64_t>(B.cols()); |
| EIGEN_CUSOLVER_CHECK(cusolverDnXpotrs(ctx.cusolverHandle(), params.p, uplo, n, nrhs, dtype, scratch.d_factor.get(), |
| lda, dtype, dst.data(), static_cast<int64_t>(dst.rows()), d_info_potrs)); |
| oneshot_check_info(ctx, scratch, "llt"); |
| dst.recordReady(ctx.stream()); |
| } |
| |
| template <typename Scalar> |
| void dispatch(Context& ctx, DeviceMatrix<Scalar>& dst, const LuSolveExpr<Scalar>& expr) { |
| const DeviceMatrix<Scalar>& A = expr.matrix(); |
| const DeviceMatrix<Scalar>& B = expr.rhs(); |
| |
| eigen_assert(A.rows() == A.cols() && "LU requires a square matrix"); |
| eigen_assert(B.rows() == A.rows() && "LU solve: RHS rows must match matrix size"); |
| |
| if (A.rows() == 0 || B.cols() == 0) { |
| if (!dst.empty()) dst.waitReady(ctx.stream()); |
| dst.resize(A.rows(), B.cols()); |
| return; |
| } |
| |
| A.waitReady(ctx.stream()); |
| B.waitReady(ctx.stream()); |
| if (!dst.empty()) dst.waitReady(ctx.stream()); |
| |
| // thread_local: must outlive the async kernels (no end-of-call sync), and |
| // only TUs that instantiate the one-shot path pull in cuSOLVER symbols. |
| static thread_local CusolverParams params; |
| const int64_t n = static_cast<int64_t>(A.rows()); |
| constexpr cudaDataType_t dtype = cuda_data_type<Scalar>::value; |
| OneShotSolverScratch& scratch = ctx.oneshotSolverScratch(); |
| { |
| const size_t mat_bytes = A.sizeInBytes(); |
| // Context-owned grow-only scratch: no per-call allocation, no end-of-call sync. |
| ensure_sized(scratch.d_factor, mat_bytes); |
| EIGEN_CUDA_RUNTIME_CHECK( |
| cudaMemcpyAsync(scratch.d_factor.get(), A.data(), mat_bytes, cudaMemcpyDeviceToDevice, ctx.stream())); |
| } |
| ensure_sized(scratch.d_ipiv, static_cast<size_t>(n) * sizeof(int64_t)); |
| const int64_t lda = static_cast<int64_t>(A.rows()); |
| size_t dev_ws = 0; |
| size_t host_ws = 0; |
| EIGEN_CUSOLVER_CHECK(cusolverDnXgetrf_bufferSize(ctx.cusolverHandle(), params.p, n, n, dtype, scratch.d_factor.get(), |
| lda, dtype, &dev_ws, &host_ws)); |
| ensure_sized(scratch.d_workspace, dev_ws); |
| if (scratch.h_workspace.size() < host_ws) scratch.h_workspace.resize(host_ws); |
| int* d_info_getrf = static_cast<int*>(scratch.d_info.get()); |
| int* d_info_getrs = d_info_getrf + 1; |
| EIGEN_CUSOLVER_CHECK(cusolverDnXgetrf(ctx.cusolverHandle(), params.p, n, n, dtype, scratch.d_factor.get(), lda, |
| static_cast<int64_t*>(scratch.d_ipiv.get()), dtype, scratch.d_workspace.get(), |
| dev_ws, host_ws > 0 ? scratch.h_workspace.data() : nullptr, host_ws, |
| d_info_getrf)); |
| |
| dst.resize(n, B.cols()); |
| const size_t rhs_bytes = B.sizeInBytes(); |
| EIGEN_CUDA_RUNTIME_CHECK(cudaMemcpyAsync(dst.data(), B.data(), rhs_bytes, cudaMemcpyDeviceToDevice, ctx.stream())); |
| |
| const int64_t nrhs = static_cast<int64_t>(B.cols()); |
| EIGEN_CUSOLVER_CHECK(cusolverDnXgetrs(ctx.cusolverHandle(), params.p, CUBLAS_OP_N, n, nrhs, dtype, |
| scratch.d_factor.get(), lda, static_cast<const int64_t*>(scratch.d_ipiv.get()), |
| dtype, dst.data(), static_cast<int64_t>(dst.rows()), d_info_getrs)); |
| oneshot_check_info(ctx, scratch, "lu"); |
| dst.recordReady(ctx.stream()); |
| } |
| |
| template <typename Scalar, int UpLo> |
| void dispatch(Context& ctx, DeviceMatrix<Scalar>& dst, const TrsmExpr<Scalar, UpLo>& expr) { |
| const DeviceMatrix<Scalar>& A = expr.matrix(); |
| const DeviceMatrix<Scalar>& B = expr.rhs(); |
| |
| eigen_assert(A.rows() == A.cols() && "TRSM requires a square triangular matrix"); |
| eigen_assert(B.rows() == A.rows() && "TRSM: RHS rows must match matrix size"); |
| |
| const int64_t n = A.rows(); |
| const int64_t nrhs = B.cols(); |
| |
| if (n == 0 || nrhs == 0) { |
| if (!dst.empty()) dst.waitReady(ctx.stream()); |
| dst.resize(n, B.cols()); |
| return; |
| } |
| |
| A.waitReady(ctx.stream()); |
| B.waitReady(ctx.stream()); |
| eigen_assert(!aliases_device_memory(dst, A) && "DeviceMatrix TRSM destination aliases triangular operand"); |
| eigen_assert(!aliases_device_memory(dst, B) && "DeviceMatrix TRSM destination aliases RHS operand"); |
| if (!dst.empty()) dst.waitReady(ctx.stream()); |
| |
| dst.resize(n, B.cols()); |
| const size_t rhs_bytes = static_cast<size_t>(dst.rows()) * static_cast<size_t>(nrhs) * sizeof(Scalar); |
| EIGEN_CUDA_RUNTIME_CHECK(cudaMemcpyAsync(dst.data(), B.data(), rhs_bytes, cudaMemcpyDeviceToDevice, ctx.stream())); |
| |
| constexpr cublasFillMode_t uplo = (UpLo == Lower) ? CUBLAS_FILL_MODE_LOWER : CUBLAS_FILL_MODE_UPPER; |
| Scalar alpha(1); |
| |
| EIGEN_CUBLAS_CHECK(cublasXtrsm(ctx.cublasHandle(), CUBLAS_SIDE_LEFT, uplo, CUBLAS_OP_N, CUBLAS_DIAG_NON_UNIT, n, nrhs, |
| &alpha, A.data(), A.rows(), dst.data(), dst.rows())); |
| |
| dst.recordReady(ctx.stream()); |
| } |
| |
| template <typename Scalar, int UpLo> |
| void dispatch(Context& ctx, DeviceMatrix<Scalar>& dst, const SymmExpr<Scalar, UpLo>& expr) { |
| const DeviceMatrix<Scalar>& A = expr.matrix(); |
| const DeviceMatrix<Scalar>& B = expr.rhs(); |
| |
| eigen_assert(A.rows() == A.cols() && "SYMM requires a square matrix"); |
| eigen_assert(B.rows() == A.rows() && "SYMM: RHS rows must match matrix size"); |
| |
| const int64_t m = A.rows(); |
| const int64_t n = B.cols(); |
| |
| if (m == 0 || n == 0) { |
| if (!dst.empty()) dst.waitReady(ctx.stream()); |
| dst.resize(m, B.cols()); |
| return; |
| } |
| |
| A.waitReady(ctx.stream()); |
| B.waitReady(ctx.stream()); |
| eigen_assert(!aliases_device_memory(dst, A) && "DeviceMatrix SYMM destination aliases self-adjoint operand"); |
| eigen_assert(!aliases_device_memory(dst, B) && "DeviceMatrix SYMM destination aliases RHS operand"); |
| if (!dst.empty()) dst.waitReady(ctx.stream()); |
| |
| dst.resize(m, n); |
| |
| constexpr cublasFillMode_t uplo = (UpLo == Lower) ? CUBLAS_FILL_MODE_LOWER : CUBLAS_FILL_MODE_UPPER; |
| // The array keeps the host-pointer stack slots alive; see the GEMM dispatch. |
| Scalar scalars[2] = {Scalar(1), Scalar(0)}; |
| |
| EIGEN_CUBLAS_CHECK(cublasXsymm(ctx.cublasHandle(), CUBLAS_SIDE_LEFT, uplo, m, n, &scalars[0], A.data(), A.rows(), |
| B.data(), B.rows(), &scalars[1], dst.data(), dst.rows())); |
| |
| dst.recordReady(ctx.stream()); |
| } |
| |
| template <typename Scalar, int UpLo> |
| void dispatch(Context& ctx, DeviceMatrix<Scalar>& dst, const SyrkExpr<Scalar, UpLo>& expr, |
| typename NumTraits<Scalar>::Real alpha_val, typename NumTraits<Scalar>::Real beta_val) { |
| using RealScalar = typename NumTraits<Scalar>::Real; |
| const DeviceMatrix<Scalar>& A = expr.matrix(); |
| |
| const int64_t n = A.rows(); |
| const int64_t k = A.cols(); |
| |
| if (n == 0) { |
| if (!dst.empty()) dst.waitReady(ctx.stream()); |
| dst.resize(0, 0); |
| return; |
| } |
| |
| A.waitReady(ctx.stream()); |
| eigen_assert(!aliases_device_memory(dst, A) && "DeviceMatrix SYRK destination aliases input operand"); |
| if (!dst.empty()) dst.waitReady(ctx.stream()); |
| |
| if (dst.empty() || dst.rows() != n || dst.cols() != n) { |
| dst.resize(n, n); |
| if (beta_val != RealScalar(0)) { |
| EIGEN_CUDA_RUNTIME_CHECK(cudaMemsetAsync(dst.data(), 0, dst.sizeInBytes(), ctx.stream())); |
| } |
| } |
| |
| constexpr cublasFillMode_t uplo = (UpLo == Lower) ? CUBLAS_FILL_MODE_LOWER : CUBLAS_FILL_MODE_UPPER; |
| |
| EIGEN_CUBLAS_CHECK(cublasXsyrk(ctx.cublasHandle(), uplo, CUBLAS_OP_N, n, k, &alpha_val, A.data(), A.rows(), &beta_val, |
| dst.data(), dst.rows())); |
| |
| dst.recordReady(ctx.stream()); |
| } |
| |
| // DeviceAddExpr → cublasXgeam: dst = alpha * A + beta * B. Safe when dst |
| // aliases A and/or B (geam supports in-place operation with equal leading |
| // dimensions, which always holds here since DeviceMatrix is fully dense). |
| |
| template <typename Scalar> |
| void dispatch(Context& ctx, DeviceMatrix<Scalar>& dst, const DeviceAddExpr<Scalar>& expr) { |
| const DeviceMatrix<Scalar>& A = expr.A(); |
| const DeviceMatrix<Scalar>& B = expr.B(); |
| eigen_assert(A.rows() == B.rows() && A.cols() == B.cols()); |
| const int64_t m = A.rows(); |
| const int64_t n = A.cols(); |
| // Wait on dst before resize — resize may free the old buffer while another |
| // stream is still reading it. |
| if (!dst.empty()) dst.waitReady(ctx.stream()); |
| dst.resize(A.rows(), A.cols()); |
| if (m > 0 && n > 0) { |
| A.waitReady(ctx.stream()); |
| B.waitReady(ctx.stream()); |
| // See the GEMM dispatch: array prevents compiler from eliding host-pointer stack slots. |
| Scalar scalars[2] = {expr.alpha(), expr.beta()}; |
| EIGEN_CUBLAS_CHECK(cublasXgeam(ctx.cublasHandle(), CUBLAS_OP_N, CUBLAS_OP_N, m, n, &scalars[0], A.data(), m, |
| &scalars[1], B.data(), m, dst.data(), m)); |
| dst.recordReady(ctx.stream()); |
| } |
| } |
| } // namespace internal |
| |
| template <typename Scalar_> |
| class Assignment { |
| public: |
| using Scalar = Scalar_; |
| |
| Assignment(DeviceMatrix<Scalar>& dst, Context& ctx) : dst_(dst), ctx_(ctx) {} |
| |
| template <typename Lhs, typename Rhs> |
| DeviceMatrix<Scalar>& operator=(const GemmExpr<Lhs, Rhs>& expr) { |
| internal::dispatch(ctx_, dst_, expr, Scalar(0)); |
| return dst_; |
| } |
| |
| template <typename Lhs, typename Rhs> |
| DeviceMatrix<Scalar>& operator+=(const GemmExpr<Lhs, Rhs>& expr) { |
| internal::dispatch(ctx_, dst_, expr, Scalar(1)); |
| return dst_; |
| } |
| |
| template <typename Lhs, typename Rhs> |
| DeviceMatrix<Scalar>& operator-=(const GemmExpr<Lhs, Rhs>& expr) { |
| internal::dispatch(ctx_, dst_, expr, Scalar(1), Scalar(-1)); |
| return dst_; |
| } |
| |
| template <int UpLo> |
| DeviceMatrix<Scalar>& operator=(const LltSolveExpr<Scalar, UpLo>& expr) { |
| internal::dispatch(ctx_, dst_, expr); |
| return dst_; |
| } |
| |
| DeviceMatrix<Scalar>& operator=(const LuSolveExpr<Scalar>& expr) { |
| internal::dispatch(ctx_, dst_, expr); |
| return dst_; |
| } |
| |
| template <int UpLo> |
| DeviceMatrix<Scalar>& operator=(const TrsmExpr<Scalar, UpLo>& expr) { |
| internal::dispatch(ctx_, dst_, expr); |
| return dst_; |
| } |
| |
| template <int UpLo> |
| DeviceMatrix<Scalar>& operator=(const SymmExpr<Scalar, UpLo>& expr) { |
| internal::dispatch(ctx_, dst_, expr); |
| return dst_; |
| } |
| |
| DeviceMatrix<Scalar>& operator=(const DeviceAddExpr<Scalar>& expr) { |
| internal::dispatch(ctx_, dst_, expr); |
| return dst_; |
| } |
| |
| DeviceMatrix<Scalar>& operator=(const Scaled<DeviceMatrix<Scalar>>& expr) { |
| // geam with beta == 0: cuBLAS documents B as unread, so pass A twice. |
| internal::dispatch(ctx_, dst_, DeviceAddExpr<Scalar>(expr.scalar(), expr.inner(), Scalar(0), expr.inner())); |
| return dst_; |
| } |
| |
| template <typename Expr> |
| DeviceMatrix<Scalar>& operator=(const Expr&) { |
| static_assert(sizeof(Expr) == 0, |
| "DeviceMatrix expression not supported: no cuBLAS/cuSOLVER mapping. " |
| "Supported: GEMM (A*B), geam (A + alpha*B, alpha*A), " |
| "TRSM (.triangularView().solve()), SYMM (.selfadjointView()*B), " |
| "LLT (.llt().solve()), LU (.lu().solve())."); |
| return dst_; |
| } |
| |
| private: |
| DeviceMatrix<Scalar>& dst_; |
| Context& ctx_; |
| }; |
| |
| // The definitions below call Context::threadLocal(), so they cannot live in |
| // DeviceMatrix.h, where Context is still incomplete. |
| |
| template <typename Scalar_> |
| template <typename Lhs, typename Rhs> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator=(const GemmExpr<Lhs, Rhs>& expr) { |
| device(Context::threadLocal()) = expr; |
| return *this; |
| } |
| |
| template <typename Scalar_> |
| template <typename Lhs, typename Rhs> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator+=(const GemmExpr<Lhs, Rhs>& expr) { |
| device(Context::threadLocal()) += expr; |
| return *this; |
| } |
| |
| template <typename Scalar_> |
| template <typename Lhs, typename Rhs> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator-=(const GemmExpr<Lhs, Rhs>& expr) { |
| device(Context::threadLocal()) -= expr; |
| return *this; |
| } |
| |
| template <typename Scalar_> |
| template <int UpLo> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator=(const LltSolveExpr<Scalar_, UpLo>& expr) { |
| device(Context::threadLocal()) = expr; |
| return *this; |
| } |
| |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator=(const LuSolveExpr<Scalar_>& expr) { |
| device(Context::threadLocal()) = expr; |
| return *this; |
| } |
| |
| template <typename Scalar_> |
| template <int UpLo> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator=(const TrsmExpr<Scalar_, UpLo>& expr) { |
| device(Context::threadLocal()) = expr; |
| return *this; |
| } |
| |
| template <typename Scalar_> |
| template <int UpLo> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator=(const SymmExpr<Scalar_, UpLo>& expr) { |
| device(Context::threadLocal()) = expr; |
| return *this; |
| } |
| |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator=(const Scaled<DeviceMatrix>& expr) { |
| device(Context::threadLocal()) = expr; |
| return *this; |
| } |
| |
| // Enable copy-initialization straight from an expression, e.g. |
| // DeviceMatrix<double> d_C = d_A * d_B; |
| // Each default-constructs and delegates to the matching operator=. |
| |
| template <typename Scalar_> |
| template <typename Lhs, typename Rhs> |
| DeviceMatrix<Scalar_>::DeviceMatrix(const GemmExpr<Lhs, Rhs>& expr) : DeviceMatrix() { |
| *this = expr; |
| } |
| |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_>::DeviceMatrix(const Scaled<DeviceMatrix>& expr) : DeviceMatrix() { |
| *this = expr; |
| } |
| |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_>::DeviceMatrix(const DeviceAddExpr<Scalar_>& expr) : DeviceMatrix() { |
| *this = expr; |
| } |
| |
| template <typename Scalar_> |
| template <int UpLo> |
| DeviceMatrix<Scalar_>::DeviceMatrix(const LltSolveExpr<Scalar_, UpLo>& expr) : DeviceMatrix() { |
| *this = expr; |
| } |
| |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_>::DeviceMatrix(const LuSolveExpr<Scalar_>& expr) : DeviceMatrix() { |
| *this = expr; |
| } |
| |
| template <typename Scalar_> |
| template <int UpLo> |
| DeviceMatrix<Scalar_>::DeviceMatrix(const TrsmExpr<Scalar_, UpLo>& expr) : DeviceMatrix() { |
| *this = expr; |
| } |
| |
| template <typename Scalar_> |
| template <int UpLo> |
| DeviceMatrix<Scalar_>::DeviceMatrix(const SymmExpr<Scalar_, UpLo>& expr) : DeviceMatrix() { |
| *this = expr; |
| } |
| |
| template <typename Scalar_, int UpLo_> |
| void SelfAdjointView<Scalar_, UpLo_>::rankUpdate(const DeviceMatrix<Scalar_>& A, RealScalar alpha) { |
| SyrkExpr<Scalar_, UpLo_> expr(A); |
| RealScalar beta = matrix().empty() ? RealScalar(0) : RealScalar(1); |
| internal::dispatch(Context::threadLocal(), matrix(), expr, alpha, beta); |
| } |
| |
| namespace internal { |
| // Runs `f` with the handle temporarily in CUBLAS_POINTER_MODE_DEVICE, restoring |
| // the caller's mode afterwards. |
| template <typename F> |
| void with_device_pointer_mode(cublasHandle_t h, F&& f) { |
| cublasPointerMode_t prev; |
| EIGEN_CUBLAS_CHECK(cublasGetPointerMode(h, &prev)); |
| EIGEN_CUBLAS_CHECK(cublasSetPointerMode(h, CUBLAS_POINTER_MODE_DEVICE)); |
| f(); |
| EIGEN_CUBLAS_CHECK(cublasSetPointerMode(h, prev)); |
| } |
| } // namespace internal |
| |
| // The reductions below (dot, norm, squaredNorm) run under |
| // CUBLAS_POINTER_MODE_DEVICE: the scalar result is written to device memory and |
| // stays there until DeviceScalar's conversion to Scalar syncs and reads it. |
| |
| namespace internal { |
| inline int64_t blas1_size(Index rows, Index cols) { return static_cast<int64_t>(rows) * static_cast<int64_t>(cols); } |
| } // namespace internal |
| |
| template <typename Scalar_> |
| DeviceScalar<typename DeviceMatrix<Scalar_>::Scalar> DeviceMatrix<Scalar_>::dot(Context& ctx, |
| const DeviceMatrix& other) const { |
| const int64_t n = internal::blas1_size(rows_, cols_); |
| eigen_assert(n == internal::blas1_size(other.rows_, other.cols_)); |
| if (n > 0) { |
| // Allocated uninitialized: cublasXdot overwrites the slot, so uploading a |
| // zero first would be a wasted H2D transfer per reduction. |
| DeviceScalar<Scalar> result(ctx.stream()); |
| waitReady(ctx.stream()); |
| other.waitReady(ctx.stream()); |
| internal::with_device_pointer_mode(ctx.cublasHandle(), [&] { |
| EIGEN_CUBLAS_CHECK( |
| internal::cublasXdot(ctx.cublasHandle(), n, data_.get(), 1, other.data_.get(), 1, result.devicePtr())); |
| }); |
| return result; |
| } |
| return DeviceScalar<Scalar>(Scalar(0), ctx.stream()); |
| } |
| |
| namespace internal { |
| // For real Scalar, dot(x,x) already has type DeviceScalar<RealScalar>, so a move |
| // suffices and nothing syncs. |
| template <typename Scalar, typename RealScalar> |
| std::enable_if_t<std::is_same<Scalar, RealScalar>::value, DeviceScalar<RealScalar>> squaredNorm_from_dot( |
| DeviceScalar<Scalar>&& d, cudaStream_t) { |
| return std::move(d); |
| } |
| // Complex must sync to extract the real part: DeviceScalar arithmetic is real-only. |
| template <typename Scalar, typename RealScalar> |
| std::enable_if_t<!std::is_same<Scalar, RealScalar>::value, DeviceScalar<RealScalar>> squaredNorm_from_dot( |
| DeviceScalar<Scalar>&& d, cudaStream_t stream) { |
| return DeviceScalar<RealScalar>(numext::real(Scalar(d)), stream); |
| } |
| } // namespace internal |
| |
| template <typename Scalar_> |
| DeviceScalar<typename NumTraits<Scalar_>::Real> DeviceMatrix<Scalar_>::squaredNorm(Context& ctx) const { |
| // dot(x,x) rather than nrm2()^2: the dot kernel is ~4.5x faster, since nrm2 |
| // runs a scaled sum of squares whose overflow protection convergence checks do |
| // not need. |
| using RealScalar = typename NumTraits<Scalar_>::Real; |
| return internal::squaredNorm_from_dot<Scalar_, RealScalar>(dot(ctx, *this), ctx.stream()); |
| } |
| |
| template <typename Scalar_> |
| DeviceScalar<typename NumTraits<Scalar_>::Real> DeviceMatrix<Scalar_>::norm(Context& ctx) const { |
| using RealScalar = typename NumTraits<Scalar>::Real; |
| const int64_t n = internal::blas1_size(rows_, cols_); |
| if (n > 0) { |
| // See dot(): uninitialized on purpose, cublasXnrm2 overwrites the slot. |
| DeviceScalar<RealScalar> result(ctx.stream()); |
| waitReady(ctx.stream()); |
| internal::with_device_pointer_mode(ctx.cublasHandle(), [&] { |
| EIGEN_CUBLAS_CHECK(internal::cublasXnrm2(ctx.cublasHandle(), n, data_.get(), 1, result.devicePtr())); |
| }); |
| return result; |
| } |
| return DeviceScalar<RealScalar>(RealScalar(0), ctx.stream()); |
| } |
| |
| template <typename Scalar_> |
| void DeviceMatrix<Scalar_>::setZero(cudaStream_t stream) { |
| if (sizeInBytes() > 0) { |
| waitReady(stream); |
| EIGEN_CUDA_RUNTIME_CHECK(cudaMemsetAsync(data_.get(), 0, sizeInBytes(), stream)); |
| recordReady(stream); |
| } |
| } |
| |
| template <typename Scalar_> |
| void DeviceMatrix<Scalar_>::setZero(Context& ctx) { |
| setZero(ctx.stream()); |
| } |
| |
| template <typename Scalar_> |
| void DeviceMatrix<Scalar_>::addScaled(Context& ctx, Scalar alpha, const DeviceMatrix& x) { |
| const int64_t n = internal::blas1_size(rows_, cols_); |
| eigen_assert(n == internal::blas1_size(x.rows_, x.cols_)); |
| if (n > 0) { |
| waitReady(ctx.stream()); |
| x.waitReady(ctx.stream()); |
| EIGEN_CUBLAS_CHECK(internal::cublasXaxpy(ctx.cublasHandle(), n, &alpha, x.data_.get(), 1, data_.get(), 1)); |
| recordReady(ctx.stream()); |
| } |
| } |
| |
| template <typename Scalar_> |
| void DeviceMatrix<Scalar_>::scale(Context& ctx, Scalar alpha) { |
| const int64_t n = internal::blas1_size(rows_, cols_); |
| if (n > 0) { |
| waitReady(ctx.stream()); |
| EIGEN_CUBLAS_CHECK(internal::cublasXscal(ctx.cublasHandle(), n, &alpha, data_.get(), 1)); |
| recordReady(ctx.stream()); |
| } |
| } |
| |
| template <typename Scalar_> |
| void DeviceMatrix<Scalar_>::copyFrom(Context& ctx, const DeviceMatrix& other) { |
| // Wait on *this before resize — resize may free the old buffer while another |
| // stream is still reading it. |
| if (!empty()) waitReady(ctx.stream()); |
| resize(other.rows_, other.cols_); |
| const int64_t n = internal::blas1_size(rows_, cols_); |
| if (n > 0) { |
| other.waitReady(ctx.stream()); |
| EIGEN_CUBLAS_CHECK(internal::cublasXcopy(ctx.cublasHandle(), n, other.data_.get(), 1, data_.get(), 1)); |
| recordReady(ctx.stream()); |
| } |
| } |
| |
| // this += alpha * x (axpy) |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator+=(const Scaled<DeviceMatrix>& expr) { |
| addScaled(Context::threadLocal(), expr.scalar(), internal::device_expr_traits<DeviceMatrix>::matrix(expr.inner())); |
| return *this; |
| } |
| |
| // this -= alpha * x (axpy with negated alpha) |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator-=(const Scaled<DeviceMatrix>& expr) { |
| addScaled(Context::threadLocal(), -expr.scalar(), internal::device_expr_traits<DeviceMatrix>::matrix(expr.inner())); |
| return *this; |
| } |
| |
| // this += x (axpy with alpha=1) |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator+=(const DeviceMatrix& other) { |
| Scalar one(1); |
| addScaled(Context::threadLocal(), one, other); |
| return *this; |
| } |
| |
| // this -= x (axpy with alpha=-1) |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator-=(const DeviceMatrix& other) { |
| Scalar neg_one(-1); |
| addScaled(Context::threadLocal(), neg_one, other); |
| return *this; |
| } |
| |
| // this *= alpha (scal, host pointer) |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator*=(Scalar alpha) { |
| scale(Context::threadLocal(), alpha); |
| return *this; |
| } |
| |
| // this *= alpha (scal, device pointer — avoids host sync) |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator*=(const DeviceScalar<Scalar>& alpha) { |
| const int64_t n = internal::blas1_size(rows_, cols_); |
| if (n > 0) { |
| auto& ctx = Context::threadLocal(); |
| waitReady(ctx.stream()); |
| internal::with_device_pointer_mode(ctx.cublasHandle(), [&] { |
| EIGEN_CUBLAS_CHECK(internal::cublasXscal(ctx.cublasHandle(), n, alpha.devicePtr(), data_.get(), 1)); |
| }); |
| recordReady(ctx.stream()); |
| } |
| return *this; |
| } |
| |
| // this += DeviceScalar * x (axpy with CUBLAS_POINTER_MODE_DEVICE) |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator+=(const DeviceScaledDevice<Scalar_>& expr) { |
| const int64_t n = internal::blas1_size(rows_, cols_); |
| const auto& x = expr.matrix(); |
| eigen_assert(n == internal::blas1_size(x.rows_, x.cols_)); |
| if (n > 0) { |
| auto& ctx = Context::threadLocal(); |
| waitReady(ctx.stream()); |
| x.waitReady(ctx.stream()); |
| internal::with_device_pointer_mode(ctx.cublasHandle(), [&] { |
| EIGEN_CUBLAS_CHECK( |
| internal::cublasXaxpy(ctx.cublasHandle(), n, expr.alpha().devicePtr(), x.data_.get(), 1, data_.get(), 1)); |
| }); |
| recordReady(ctx.stream()); |
| } |
| return *this; |
| } |
| |
| // this -= DeviceScalar * x (axpy with negated device scalar) |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator-=(const DeviceScaledDevice<Scalar_>& expr) { |
| auto neg_alpha = -expr.alpha(); |
| DeviceScaledDevice<Scalar_> neg_expr(neg_alpha, expr.matrix()); |
| return operator+=(neg_expr); |
| } |
| |
| // this = alpha * A + beta * B (cuBLAS geam) |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_>& DeviceMatrix<Scalar_>::operator=(const DeviceAddExpr<Scalar_>& expr) { |
| internal::dispatch(Context::threadLocal(), *this, expr); |
| return *this; |
| } |
| |
| // cwiseProduct (allocating). |
| template <typename Scalar_> |
| DeviceMatrix<Scalar_> DeviceMatrix<Scalar_>::cwiseProduct(Context& ctx, const DeviceMatrix& other) const { |
| const int64_t n = internal::blas1_size(rows_, cols_); |
| eigen_assert(n == internal::blas1_size(other.rows_, other.cols_)); |
| DeviceMatrix result(rows_, cols_); |
| if (n > 0) { |
| waitReady(ctx.stream()); |
| other.waitReady(ctx.stream()); |
| internal::device_cwiseProduct(data_.get(), other.data_.get(), result.data_.get(), n, ctx.stream()); |
| result.recordReady(ctx.stream()); |
| } |
| return result; |
| } |
| |
| // In-place cwiseProduct: this = a .* b (reuses this buffer, no allocation). |
| template <typename Scalar_> |
| void DeviceMatrix<Scalar_>::cwiseProduct(Context& ctx, const DeviceMatrix& a, const DeviceMatrix& b) { |
| const int64_t n = internal::blas1_size(a.rows_, a.cols_); |
| eigen_assert(n == internal::blas1_size(b.rows_, b.cols_)); |
| if (!empty()) waitReady(ctx.stream()); |
| resize(a.rows_, a.cols_); |
| if (n > 0) { |
| a.waitReady(ctx.stream()); |
| b.waitReady(ctx.stream()); |
| internal::device_cwiseProduct(a.data_.get(), b.data_.get(), data_.get(), n, ctx.stream()); |
| recordReady(ctx.stream()); |
| } |
| } |
| |
| // Convenience overloads using thread-local default Context. |
| template <typename Scalar_> |
| DeviceScalar<typename DeviceMatrix<Scalar_>::Scalar> DeviceMatrix<Scalar_>::dot(const DeviceMatrix& other) const { |
| return dot(Context::threadLocal(), other); |
| } |
| |
| template <typename Scalar_> |
| DeviceScalar<typename NumTraits<Scalar_>::Real> DeviceMatrix<Scalar_>::squaredNorm() const { |
| return squaredNorm(Context::threadLocal()); |
| } |
| |
| template <typename Scalar_> |
| DeviceScalar<typename NumTraits<Scalar_>::Real> DeviceMatrix<Scalar_>::norm() const { |
| return norm(Context::threadLocal()); |
| } |
| |
| template <typename Scalar_> |
| void DeviceMatrix<Scalar_>::setZero() { |
| setZero(Context::threadLocal()); |
| } |
| } // namespace gpu |
| } // namespace Eigen |
| |
| #endif // EIGEN_GPU_DEVICE_DISPATCH_H |