blob: 5ea4aeccd8f9db72babc3ba74bd6c9c33ac72d84 [file]
// 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