// This file is part of Eigen, a lightweight C++ template library // for linear algebra. // // Copyright (C) 2026 Rasmus Munk Larsen // // 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/. // Typed RAII wrapper for a dense matrix in GPU device memory. // // DeviceMatrix holds a column-major matrix on the GPU with tracked // dimensions. Always dense (leading dimension = rows). It can be passed to GPU solvers // (GpuLLT, GpuLU, future cuBLAS/cuDSS) without host round-trips. // // Cross-stream safety is automatic: an internal CUDA event tracks when the // last write completed. Consumers on a different stream wait on that event // before reading. // // Usage: // auto d_A = DeviceMatrix::fromHost(A); // upload (sync) // GpuLLT llt; // llt.compute(d_A); // factor on device // auto d_X = llt.solve(d_B); // async, no sync // MatrixXd X = d_X.toHost(); // download + block // // Async variants: // auto d_A = DeviceMatrix::fromHostAsync(A.data(), n, n, stream); // auto transfer = d_X.toHostAsync(stream); // enqueue D2H // // ... overlap with other work ... // MatrixXd X = transfer.get(); // block + retrieve #ifndef EIGEN_GPU_DEVICE_MATRIX_H #define EIGEN_GPU_DEVICE_MATRIX_H // IWYU pragma: private #include "./InternalHeaderCheck.h" #include "./GpuSupport.h" namespace Eigen { // Forward declarations. template class GpuLLT; template class GpuLU; template class DeviceAdjointView; template class DeviceTransposeView; template class DeviceAssignment; template class GemmExpr; template class LltSolveExpr; template class LuSolveExpr; template class DeviceLLTView; template class DeviceLUView; template class DeviceTriangularView; template class DeviceSelfAdjointView; template class ConstDeviceSelfAdjointView; template class TrsmExpr; template class SymmExpr; template class SyrkExpr; class GpuContext; // -------------------------------------------------------------------------- // HostTransfer — future-like wrapper for an async device-to-host transfer. // -------------------------------------------------------------------------- /** \ingroup GPU_Module * \class HostTransfer * \brief Future for an asynchronous device-to-host matrix transfer. * * Returned by DeviceMatrix::toHostAsync(). The transfer runs asynchronously * on the given CUDA stream. Call get() to block until complete and retrieve * the host matrix, or ready() to poll without blocking. */ template class HostTransfer { public: using Scalar = Scalar_; using PlainMatrix = Matrix; /** Block until the transfer completes and return the host matrix. * Idempotent: subsequent calls return the same matrix without re-syncing. */ PlainMatrix& get() { if (!synced_) { EIGEN_CUDA_RUNTIME_CHECK(cudaEventSynchronize(event_)); synced_ = true; } return host_buf_; } /** Non-blocking check: has the transfer completed? */ bool ready() const { if (synced_) return true; cudaError_t err = cudaEventQuery(event_); if (err == cudaSuccess) return true; eigen_assert(err == cudaErrorNotReady && "cudaEventQuery failed"); return false; } ~HostTransfer() { if (event_) (void)cudaEventDestroy(event_); } HostTransfer(HostTransfer&& o) noexcept : host_buf_(std::move(o.host_buf_)), event_(o.event_), synced_(o.synced_) { o.event_ = nullptr; o.synced_ = true; } HostTransfer& operator=(HostTransfer&& o) noexcept { if (this != &o) { if (event_) (void)cudaEventDestroy(event_); host_buf_ = std::move(o.host_buf_); event_ = o.event_; synced_ = o.synced_; o.event_ = nullptr; o.synced_ = true; } return *this; } HostTransfer(const HostTransfer&) = delete; HostTransfer& operator=(const HostTransfer&) = delete; private: template friend class DeviceMatrix; HostTransfer(PlainMatrix&& buf, cudaEvent_t event) : host_buf_(std::move(buf)), event_(event), synced_(false) {} PlainMatrix host_buf_; cudaEvent_t event_ = nullptr; bool synced_ = false; }; // -------------------------------------------------------------------------- // DeviceMatrix — typed RAII wrapper for a dense matrix in device memory. // -------------------------------------------------------------------------- /** \ingroup GPU_Module * \class DeviceMatrix * \brief RAII wrapper for a dense column-major matrix in GPU device memory. * * \tparam Scalar_ Element type: float, double, complex, complex * * Owns a device allocation with tracked dimensions. Always dense * (leading dimension = rows; no stride padding). * An internal CUDA event records when the data was last written, enabling * safe cross-stream consumption without user-visible synchronization. * * Each method has a synchronous and an asynchronous variant: * - fromHost() / fromHostAsync(): upload from host * - toHost() / toHostAsync(): download to host */ template class DeviceMatrix { public: using Scalar = Scalar_; using PlainMatrix = Matrix; // ---- Construction / destruction ------------------------------------------ /** Default: empty (0x0, no allocation). */ DeviceMatrix() = default; /** Allocate uninitialized device memory for a rows x cols matrix. */ DeviceMatrix(Index rows, Index cols) : rows_(rows), cols_(cols) { eigen_assert(rows >= 0 && cols >= 0); size_t bytes = sizeInBytes(); if (bytes > 0) { EIGEN_CUDA_RUNTIME_CHECK(cudaMalloc(reinterpret_cast(&data_), bytes)); } } ~DeviceMatrix() { if (data_) (void)cudaFree(data_); if (ready_event_) (void)cudaEventDestroy(ready_event_); } // ---- Move-only ----------------------------------------------------------- DeviceMatrix(DeviceMatrix&& o) noexcept : data_(o.data_), rows_(o.rows_), cols_(o.cols_), ready_event_(o.ready_event_), ready_stream_(o.ready_stream_), retained_buffer_(std::move(o.retained_buffer_)) { o.data_ = nullptr; o.rows_ = 0; o.cols_ = 0; o.ready_event_ = nullptr; o.ready_stream_ = nullptr; } DeviceMatrix& operator=(DeviceMatrix&& o) noexcept { if (this != &o) { if (data_) (void)cudaFree(data_); if (ready_event_) (void)cudaEventDestroy(ready_event_); data_ = o.data_; rows_ = o.rows_; cols_ = o.cols_; ready_event_ = o.ready_event_; ready_stream_ = o.ready_stream_; retained_buffer_ = std::move(o.retained_buffer_); o.data_ = nullptr; o.rows_ = 0; o.cols_ = 0; o.ready_event_ = nullptr; o.ready_stream_ = nullptr; } return *this; } DeviceMatrix(const DeviceMatrix&) = delete; DeviceMatrix& operator=(const DeviceMatrix&) = delete; // ---- Upload from host ---------------------------------------------------- /** Upload a host Eigen matrix to device memory (synchronous). * * Evaluates the expression into a contiguous ColMajor temporary, copies to * device via cudaMemcpyAsync on \p stream, and synchronizes before returning. * * \param host Any Eigen matrix expression. * \param stream CUDA stream for the transfer (default: stream 0). */ template static DeviceMatrix fromHost(const MatrixBase& host, cudaStream_t stream = nullptr) { const PlainMatrix mat(host.derived()); DeviceMatrix dm(mat.rows(), mat.cols()); if (dm.sizeInBytes() > 0) { EIGEN_CUDA_RUNTIME_CHECK(cudaMemcpyAsync(dm.data_, mat.data(), dm.sizeInBytes(), cudaMemcpyHostToDevice, stream)); EIGEN_CUDA_RUNTIME_CHECK(cudaStreamSynchronize(stream)); } return dm; } /** Upload from a raw host pointer to device memory (asynchronous). * * Enqueues an async H2D copy on \p stream and records an internal event. * The caller must keep \p host_data alive until the transfer completes * (check via the internal event or synchronize the stream). * * \param host_data Pointer to contiguous column-major host data. * \param rows Number of rows. * \param cols Number of columns. * \param stream CUDA stream for the transfer. */ static DeviceMatrix fromHostAsync(const Scalar* host_data, Index rows, Index cols, cudaStream_t stream) { eigen_assert(rows >= 0 && cols >= 0); eigen_assert(host_data != nullptr || (rows == 0 || cols == 0)); DeviceMatrix dm(rows, cols); if (dm.sizeInBytes() > 0) { EIGEN_CUDA_RUNTIME_CHECK(cudaMemcpyAsync(dm.data_, host_data, dm.sizeInBytes(), cudaMemcpyHostToDevice, stream)); dm.recordReady(stream); } return dm; } // ---- Download to host ---------------------------------------------------- /** Download device matrix to host memory (synchronous). * * Waits on the internal ready event, enqueues a D2H copy on \p stream, * synchronizes, and returns the host matrix directly. * * \param stream CUDA stream for the transfer (default: stream 0). */ PlainMatrix toHost(cudaStream_t stream = nullptr) const { PlainMatrix host_buf(rows_, cols_); if (sizeInBytes() > 0) { waitReady(stream); EIGEN_CUDA_RUNTIME_CHECK(cudaMemcpyAsync(host_buf.data(), data_, sizeInBytes(), cudaMemcpyDeviceToHost, stream)); EIGEN_CUDA_RUNTIME_CHECK(cudaStreamSynchronize(stream)); } return host_buf; } /** Enqueue an async device-to-host transfer and return a future. * * Waits on the internal ready event (if any) to ensure the device data is * valid, then enqueues the D2H copy on \p stream. Returns a HostTransfer * future; call .get() to block and retrieve the host matrix. * * \param stream CUDA stream for the transfer (default: stream 0). */ HostTransfer toHostAsync(cudaStream_t stream = nullptr) const { PlainMatrix host_buf(rows_, cols_); if (sizeInBytes() > 0) { waitReady(stream); EIGEN_CUDA_RUNTIME_CHECK(cudaMemcpyAsync(host_buf.data(), data_, sizeInBytes(), cudaMemcpyDeviceToHost, stream)); } // Record a transfer-complete event. cudaEvent_t transfer_event; EIGEN_CUDA_RUNTIME_CHECK(cudaEventCreateWithFlags(&transfer_event, cudaEventDisableTiming)); EIGEN_CUDA_RUNTIME_CHECK(cudaEventRecord(transfer_event, stream)); return HostTransfer(std::move(host_buf), transfer_event); } // ---- Device-to-device copy ----------------------------------------------- /** Deep copy on device. Fully async — records event on the result, no sync. * * \param stream CUDA stream for the D2D copy (default: stream 0). */ DeviceMatrix clone(cudaStream_t stream = nullptr) const { DeviceMatrix result(rows_, cols_); if (sizeInBytes() > 0) { waitReady(stream); EIGEN_CUDA_RUNTIME_CHECK(cudaMemcpyAsync(result.data_, data_, sizeInBytes(), cudaMemcpyDeviceToDevice, stream)); result.recordReady(stream); } return result; } // ---- Resize (destructive) ------------------------------------------------ /** Discard contents and reallocate to (rows x cols). Clears the ready event. */ void resize(Index rows, Index cols) { if (rows == rows_ && cols == cols_) return; if (data_) { (void)cudaFree(data_); data_ = nullptr; } if (ready_event_) { (void)cudaEventDestroy(ready_event_); ready_event_ = nullptr; } ready_stream_ = nullptr; retained_buffer_ = internal::DeviceBuffer(); rows_ = rows; cols_ = cols; size_t bytes = sizeInBytes(); if (bytes > 0) { EIGEN_CUDA_RUNTIME_CHECK(cudaMalloc(reinterpret_cast(&data_), bytes)); } } // ---- Accessors ----------------------------------------------------------- Scalar* data() { return data_; } const Scalar* data() const { return data_; } Index rows() const { return rows_; } Index cols() const { return cols_; } bool empty() const { return rows_ == 0 || cols_ == 0; } /** Size of the device allocation in bytes. */ size_t sizeInBytes() const { return static_cast(rows_) * static_cast(cols_) * sizeof(Scalar); } // ---- Event synchronization (public for library dispatch interop) --------- /** Record that device data is ready after work on \p stream. */ void recordReady(cudaStream_t stream) { ensureEvent(); EIGEN_CUDA_RUNTIME_CHECK(cudaEventRecord(ready_event_, stream)); ready_stream_ = stream; } /** Make \p stream wait until the device data is ready. * No-op if no event recorded, or if the consumer stream is the same as the * producer stream (CUDA guarantees in-order execution within a stream). */ void waitReady(cudaStream_t stream) const { if (ready_event_ && stream != ready_stream_) { EIGEN_CUDA_RUNTIME_CHECK(cudaStreamWaitEvent(stream, ready_event_, 0)); } } // ---- Expression methods (dispatch to cuBLAS/cuSOLVER) -------------------- /** Adjoint view for GEMM dispatch. Maps to cublasXgemm with ConjTrans. */ DeviceAdjointView adjoint() const { return DeviceAdjointView(*this); } /** Transpose view for GEMM dispatch. Maps to cublasXgemm with Trans. */ DeviceTransposeView transpose() const { return DeviceTransposeView(*this); } /** Bind this matrix to a GpuContext for expression assignment. * Returns a DeviceAssignment proxy: `d_C.device(ctx) = d_A * d_B;` */ DeviceAssignment device(GpuContext& ctx) { return DeviceAssignment(*this, ctx); } /** Assign from a GEMM expression using the thread-local default GpuContext. * Defined out-of-line after GpuContext is fully declared (see DeviceDispatch.h). */ template DeviceMatrix& operator=(const GemmExpr& expr); /** Accumulate from a GEMM expression using the thread-local default GpuContext. */ template DeviceMatrix& operator+=(const GemmExpr& expr); /** Cholesky view: d_A.llt().solve(d_B) → LltSolveExpr. */ DeviceLLTView llt() const { return DeviceLLTView(*this); } /** Cholesky view with explicit triangle: d_A.llt().solve(d_B). */ template DeviceLLTView llt() const { return DeviceLLTView(*this); } /** LU view: d_A.lu().solve(d_B) → LuSolveExpr. */ DeviceLUView lu() const { return DeviceLUView(*this); } /** Assign from an LLT solve expression (thread-local default context). */ template DeviceMatrix& operator=(const LltSolveExpr& expr); /** Assign from an LU solve expression (thread-local default context). */ DeviceMatrix& operator=(const LuSolveExpr& expr); /** Triangular view: d_A.triangularView().solve(d_B) → TrsmExpr. */ template DeviceTriangularView triangularView() const { return DeviceTriangularView(*this); } /** Self-adjoint view (mutable): d_C.selfadjointView().rankUpdate(d_A). */ template DeviceSelfAdjointView selfadjointView() { return DeviceSelfAdjointView(*this); } /** Self-adjoint view (const): d_A.selfadjointView() * d_B → SymmExpr. */ template ConstDeviceSelfAdjointView selfadjointView() const { return ConstDeviceSelfAdjointView(*this); } /** Assign from a TRSM expression (thread-local default context). */ template DeviceMatrix& operator=(const TrsmExpr& expr); /** Assign from a SYMM expression (thread-local default context). */ template DeviceMatrix& operator=(const SymmExpr& expr); private: // ---- Private: adopt a raw device pointer (used by friend solvers) -------- DeviceMatrix(Scalar* device_ptr, Index rows, Index cols) : data_(device_ptr), rows_(rows), cols_(cols) {} /** Transfer ownership of the device pointer out. Zeros internal state. */ Scalar* release() { Scalar* p = data_; data_ = nullptr; rows_ = 0; cols_ = 0; if (ready_event_) { (void)cudaEventDestroy(ready_event_); ready_event_ = nullptr; } ready_stream_ = nullptr; return p; } // ---- Private helpers ------------------------------------------------------- void ensureEvent() { if (!ready_event_) { EIGEN_CUDA_RUNTIME_CHECK(cudaEventCreateWithFlags(&ready_event_, cudaEventDisableTiming)); } } void retainBuffer(internal::DeviceBuffer&& buffer) { retained_buffer_ = std::move(buffer); } // ---- Friend declarations ------------------------------------------------ template friend class GpuLLT; template friend class GpuLU; template friend class GpuQR; template friend class GpuSVD; template friend class GpuSelfAdjointEigenSolver; // ---- Data members -------------------------------------------------------- Scalar* data_ = nullptr; Index rows_ = 0; Index cols_ = 0; cudaEvent_t ready_event_ = nullptr; // internal: tracks last write completion cudaStream_t ready_stream_ = nullptr; // stream that recorded ready_event_ (for same-stream skip) internal::DeviceBuffer retained_buffer_; // internal: keeps async aux buffers alive }; } // namespace Eigen #endif // EIGEN_GPU_DEVICE_MATRIX_H