Eigen-Contrib  5.0.1
 
Loading...
Searching...
No Matches
DeviceMatrix.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2026 Rasmus Munk Larsen <rmlarsen@gmail.com>
5//
6// This Source Code Form is subject to the terms of the Mozilla
7// Public License v. 2.0. If a copy of the MPL was not distributed
8// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
9// SPDX-License-Identifier: MPL-2.0
10
11// Typed RAII wrapper for a dense column-major matrix in GPU device memory.
12//
13// Cross-stream safety is automatic: an internal CUDA event records when the last
14// write completed, and consumers on a different stream wait on it before reading.
15
16#ifndef EIGEN_GPU_DEVICE_MATRIX_H
17#define EIGEN_GPU_DEVICE_MATRIX_H
18
19// IWYU pragma: private
20#include "./InternalHeaderCheck.h"
21
22#include <cstring>
23
24#include "./FwdDecl.h"
25#include "./GpuSupport.h"
26
27namespace Eigen {
28namespace gpu {
29
38template <typename Scalar_>
39class HostTransfer {
40 public:
41 using Scalar = Scalar_;
43
46 PlainMatrix& get() {
47 if (!synced_) {
48 EIGEN_CUDA_RUNTIME_CHECK(cudaEventSynchronize(event_));
49 if (pinned_buf_ && host_buf_.size() > 0) {
50 std::memcpy(host_buf_.data(), pinned_buf_.get(), static_cast<size_t>(host_buf_.size()) * sizeof(Scalar));
51 }
52 pinned_buf_ = internal::PinnedHostBuffer(); // free pinned memory early
53 synced_ = true;
54 }
55 return host_buf_;
56 }
57
59 bool ready() const {
60 if (synced_) return true;
61 const cudaError_t err = cudaEventQuery(event_);
62 if (err == cudaSuccess) return true;
63 if (err != cudaErrorNotReady)
64 EIGEN_GPU_CHECK_FAILED(cudaGetErrorName(err), "cudaEventQuery(event_)", __FILE__, __LINE__);
65 return false;
66 }
67
69 if (event_) (void)cudaEventDestroy(event_);
70 }
71
72 HostTransfer(HostTransfer&& o) noexcept
73 : host_buf_(std::move(o.host_buf_)), pinned_buf_(std::move(o.pinned_buf_)), event_(o.event_), synced_(o.synced_) {
74 o.event_ = nullptr;
75 o.synced_ = true;
76 }
77
78 HostTransfer& operator=(HostTransfer&& o) noexcept {
79 if (this != &o) {
80 // Unchecked like the destructor: eigen_assert may throw, and this operator is noexcept.
81 if (event_) (void)cudaEventDestroy(event_);
82 host_buf_ = std::move(o.host_buf_);
83 pinned_buf_ = std::move(o.pinned_buf_);
84 event_ = o.event_;
85 synced_ = o.synced_;
86 o.event_ = nullptr;
87 o.synced_ = true;
88 }
89 return *this;
90 }
91
92 HostTransfer(const HostTransfer&) = delete;
93 HostTransfer& operator=(const HostTransfer&) = delete;
94
95 private:
96 template <typename>
97 friend class DeviceMatrix;
98
99 HostTransfer(PlainMatrix&& buf, internal::PinnedHostBuffer&& pinned, cudaEvent_t event)
100 : host_buf_(std::move(buf)), pinned_buf_(std::move(pinned)), event_(event), synced_(false) {}
101
102 PlainMatrix host_buf_; // final destination (pageable)
103 internal::PinnedHostBuffer pinned_buf_; // staging buffer for async DMA
104 cudaEvent_t event_ = nullptr;
105 bool synced_ = false;
106};
107
121template <typename Scalar_>
123 public:
124 using Scalar = Scalar_;
125 using RealScalar = typename NumTraits<Scalar>::Real;
126 using PlainObject = DeviceMatrix; // owning type, as generic solver code expects
128
130 DeviceMatrix() = default;
131
134 explicit DeviceMatrix(Index n) : rows_(n), cols_(1) {
135 eigen_assert(n >= 0);
136 allocate(sizeInBytes());
137 }
138
140 DeviceMatrix(Index rows, Index cols) : rows_(rows), cols_(cols) {
141 eigen_assert(rows >= 0 && cols >= 0);
142 allocate(sizeInBytes());
143 }
144
145 // Copy-initialization from a device expression, mirroring the Eigen CPU idiom
146 // `DeviceMatrix<double> d_C = d_A * d_B;`. Each delegates to the corresponding
147 // operator= on the thread-local Context, and is defined out-of-line in
148 // DeviceDispatch.h — GpuSparseContext.h for SpMV — where Context is complete.
149
150 template <typename Lhs, typename Rhs>
151 DeviceMatrix(const GemmExpr<Lhs, Rhs>& expr);
154 template <int UpLo>
157 template <int UpLo>
159 template <int UpLo>
161 DeviceMatrix(const SpMVExpr<Scalar>& expr);
163
164 ~DeviceMatrix() {
165 // cudaEventDestroy on a pending event is non-blocking: the runtime defers
166 // teardown until the event completes. The trailing cudaFree() (via
167 // data_.reset()) is itself synchronous, so the buffer outlives any
168 // in-flight kernel that may still be touching it.
169 if (ready_event_) (void)cudaEventDestroy(ready_event_);
170 }
171
172 DeviceMatrix(DeviceMatrix&& o) noexcept
173 : data_(std::move(o.data_)),
174 rows_(o.rows_),
175 cols_(o.cols_),
176 capacity_bytes_(o.capacity_bytes_),
177 ready_event_(o.ready_event_),
178 ready_stream_(o.ready_stream_),
179 retained_buffer_(std::move(o.retained_buffer_)) {
180 o.rows_ = 0;
181 o.cols_ = 0;
182 o.capacity_bytes_ = 0;
183 o.ready_event_ = nullptr;
184 o.ready_stream_ = nullptr;
185 }
186
187 DeviceMatrix& operator=(DeviceMatrix&& o) noexcept {
188 if (this != &o) {
189 // Unchecked like the destructor: eigen_assert may throw, and this operator is noexcept.
190 if (ready_event_) (void)cudaEventDestroy(ready_event_);
191 data_ = std::move(o.data_);
192 rows_ = o.rows_;
193 cols_ = o.cols_;
194 capacity_bytes_ = o.capacity_bytes_;
195 ready_event_ = o.ready_event_;
196 ready_stream_ = o.ready_stream_;
197 retained_buffer_ = std::move(o.retained_buffer_);
198 o.rows_ = 0;
199 o.cols_ = 0;
200 o.capacity_bytes_ = 0;
201 o.ready_event_ = nullptr;
202 o.ready_stream_ = nullptr;
203 }
204 return *this;
205 }
206
213 DeviceMatrix(const DeviceMatrix& other);
214 DeviceMatrix& operator=(const DeviceMatrix& other);
215
225 template <typename Derived>
226 static DeviceMatrix fromHost(const DenseBase<Derived>& host, cudaStream_t stream = nullptr) {
227 // Ref binds any column-major direct-access input in place (no host copy);
228 // row-major layouts and expressions evaluate into its temporary. A bound
229 // block keeps its parent's outer stride, so the upload must honour
230 // outerStride() rather than assume rows() -- see upload_host_matrix.
231 const Ref<const PlainMatrix> mat(host.derived());
232 DeviceMatrix dm(mat.rows(), mat.cols());
233 if (dm.sizeInBytes() > 0) {
234 internal::upload_host_matrix(dm.data_.get(), mat.rows(), mat.data(), mat.outerStride(), mat.rows(), mat.cols(),
235 stream);
236 EIGEN_CUDA_RUNTIME_CHECK(cudaStreamSynchronize(stream));
237 }
238 return dm;
239 }
240
251 static DeviceMatrix fromHostAsync(const Scalar* host_data, Index rows, Index cols, cudaStream_t stream) {
252 eigen_assert(rows >= 0 && cols >= 0);
253 eigen_assert(host_data != nullptr || (rows == 0 || cols == 0));
254 DeviceMatrix dm(rows, cols);
255 if (dm.sizeInBytes() > 0) {
256 EIGEN_CUDA_RUNTIME_CHECK(
257 cudaMemcpyAsync(dm.data_.get(), host_data, dm.sizeInBytes(), cudaMemcpyHostToDevice, stream));
258 dm.recordReady(stream);
259 }
260 return dm;
261 }
262
270 PlainMatrix toHost(cudaStream_t stream = nullptr) const {
271 PlainMatrix host_buf(rows_, cols_);
272 if (sizeInBytes() > 0) {
273 waitReady(stream);
274 EIGEN_CUDA_RUNTIME_CHECK(
275 cudaMemcpyAsync(host_buf.data(), data_.get(), sizeInBytes(), cudaMemcpyDeviceToHost, stream));
276 EIGEN_CUDA_RUNTIME_CHECK(cudaStreamSynchronize(stream));
277 }
278 return host_buf;
279 }
280
289 HostTransfer<Scalar> toHostAsync(cudaStream_t stream = nullptr) const {
290 PlainMatrix host_buf(rows_, cols_);
291 internal::PinnedHostBuffer pinned_buf(sizeInBytes());
292 if (sizeInBytes() > 0) {
293 waitReady(stream);
294 EIGEN_CUDA_RUNTIME_CHECK(
295 cudaMemcpyAsync(pinned_buf.get(), data_.get(), sizeInBytes(), cudaMemcpyDeviceToHost, stream));
296 }
297 cudaEvent_t transfer_event;
298 EIGEN_CUDA_RUNTIME_CHECK(cudaEventCreateWithFlags(&transfer_event, cudaEventDisableTiming));
299 EIGEN_CUDA_RUNTIME_CHECK(cudaEventRecord(transfer_event, stream));
300 return HostTransfer<Scalar>(std::move(host_buf), std::move(pinned_buf), transfer_event);
301 }
302
307 DeviceMatrix clone(cudaStream_t stream = nullptr) const {
308 DeviceMatrix result(rows_, cols_);
309 if (sizeInBytes() > 0) {
310 waitReady(stream);
311 EIGEN_CUDA_RUNTIME_CHECK(
312 cudaMemcpyAsync(result.data_.get(), data_.get(), sizeInBytes(), cudaMemcpyDeviceToDevice, stream));
313 result.recordReady(stream);
314 }
315 return result;
316 }
317
323 void resize(Index rows, Index cols) {
324 eigen_assert(rows >= 0 && cols >= 0);
325 if (rows == rows_ && cols == cols_) return;
326 const size_t bytes = static_cast<size_t>(rows) * static_cast<size_t>(cols) * sizeof(Scalar);
327 if (bytes > 0 && bytes <= capacity_bytes_ && data_) {
328 // Reuse the allocation; the ready event still orders any in-flight
329 // writes to this buffer ahead of its next producer.
330 rows_ = rows;
331 cols_ = cols;
332 return;
333 }
334 data_.reset();
335 capacity_bytes_ = 0;
336 if (ready_event_) {
337 EIGEN_CUDA_RUNTIME_CHECK(cudaEventDestroy(ready_event_));
338 ready_event_ = nullptr;
339 }
340 ready_stream_ = nullptr;
341 retained_buffer_ = internal::DeviceBuffer();
342 rows_ = rows;
343 cols_ = cols;
344 allocate(bytes);
345 }
346
347 Scalar* data() { return data_.get(); }
348 const Scalar* data() const { return data_.get(); }
349 Index rows() const { return rows_; }
350 Index cols() const { return cols_; }
351 bool empty() const { return rows_ == 0 || cols_ == 0; }
352
357 bool isView() const { return data_ != nullptr && data_.get_deleter().borrow; }
358
360 size_t sizeInBytes() const { return static_cast<size_t>(rows_) * static_cast<size_t>(cols_) * sizeof(Scalar); }
361
363 void recordReady(cudaStream_t stream) {
364 ensureEvent();
365 EIGEN_CUDA_RUNTIME_CHECK(cudaEventRecord(ready_event_, stream));
366 ready_stream_ = stream;
367 }
368
372 void waitReady(cudaStream_t stream) const {
373 if (ready_event_ && stream != ready_stream_) {
374 EIGEN_CUDA_RUNTIME_CHECK(cudaStreamWaitEvent(stream, ready_event_, 0));
375 }
376 }
377
380
383
386 Assignment<Scalar> device(Context& ctx) { return Assignment<Scalar>(*this, ctx); }
387
388 template <typename Lhs, typename Rhs>
389 DeviceMatrix& operator=(const GemmExpr<Lhs, Rhs>& expr);
390
391 template <typename Lhs, typename Rhs>
392 DeviceMatrix& operator+=(const GemmExpr<Lhs, Rhs>& expr);
393
395 template <typename Lhs, typename Rhs>
397
400
402 template <int UpLo>
404 return LLTView<Scalar, UpLo>(*this);
405 }
406
408 LUView<Scalar> lu() const { return LUView<Scalar>(*this); }
409
410 template <int UpLo>
411 DeviceMatrix& operator=(const LltSolveExpr<Scalar, UpLo>& expr);
412
413 DeviceMatrix& operator=(const LuSolveExpr<Scalar>& expr);
414
416 template <int UpLo>
420
422 template <int UpLo>
426
428 template <int UpLo>
432
433 template <int UpLo>
434 DeviceMatrix& operator=(const TrsmExpr<Scalar, UpLo>& expr);
435
436 template <int UpLo>
437 DeviceMatrix& operator=(const SymmExpr<Scalar, UpLo>& expr);
438
439 // A DeviceMatrix is always dense (lda == rows) and a vector is one with
440 // cols == 1, so the BLAS-1 methods below simply run over the flat rows*cols
441 // array and serve both. Passing an explicit Context& lets callers keep every
442 // operation on one stream, which elides the cross-stream event waits.
443
446 DeviceScalar<Scalar> dot(Context& ctx, const DeviceMatrix& other) const;
447
450
455
459
464 void dot(Context& ctx, const DeviceMatrix& other, DeviceScalar<Scalar>& result) const;
465 void squaredNorm(Context& ctx, DeviceScalar<RealScalar>& result) const;
466 void norm(Context& ctx, DeviceScalar<RealScalar>& result) const;
467 void stableNorm(Context& ctx, DeviceScalar<RealScalar>& result) const;
468
470 void setZero(Context& ctx);
471 void setZero(cudaStream_t stream);
472
474 void addScaled(Context& ctx, Scalar alpha, const DeviceMatrix& x);
475
477 void scale(Context& ctx, Scalar alpha);
478
482 void divide(Context& ctx, Scalar alpha);
483
485 void copyFrom(Context& ctx, const DeviceMatrix& other);
486
487 DeviceScalar<Scalar> dot(const DeviceMatrix& other) const;
491 void setZero();
492
493 // The operators below let iterative-solver code written against Matrix — say
494 // `x += alpha * p` — compile unchanged against DeviceMatrix, dispatching to
495 // cuBLAS axpy/scal. `alpha * DeviceMatrix` yields Scaled<DeviceMatrix<Scalar>>
496 // from DeviceExpr.h.
497
499 DeviceMatrix& operator+=(const Scaled<DeviceMatrix>& expr);
500
503
505 DeviceMatrix& operator+=(const DeviceMatrix& other);
506
508 DeviceMatrix& operator-=(const DeviceMatrix& other);
509
511 DeviceMatrix& operator*=(Scalar alpha);
512
514 DeviceMatrix& operator/=(Scalar alpha);
515
518
520 DeviceMatrix cwiseProduct(Context& ctx, const DeviceMatrix& other) const;
521
524 void cwiseProduct(Context& ctx, const DeviceMatrix& a, const DeviceMatrix& b);
525
527 DeviceMatrix& operator+=(const DeviceScaledDevice<Scalar>& expr);
528
531
533 DeviceMatrix& operator=(const SpMVExpr<Scalar>& expr);
534
539 DeviceMatrix& operator=(const SpMVAffineExpr<Scalar>& expr);
540
542 DeviceMatrix& operator=(const DeviceAddExpr<Scalar>& expr);
543
546 DeviceMatrix& operator=(const Scaled<DeviceMatrix>& expr);
547
557 DeviceMatrix& noalias() { return *this; }
558
560 static DeviceMatrix adopt(Scalar* device_ptr, Index rows, Index cols) {
561 DeviceMatrix dm;
562 dm.data_.reset(device_ptr);
563 dm.rows_ = rows;
564 dm.cols_ = cols;
565 dm.capacity_bytes_ = dm.sizeInBytes();
566 return dm;
567 }
568
576 static DeviceMatrix view(Scalar* device_ptr, Index rows, Index cols) {
577 DeviceMatrix dm;
578 dm.data_ =
579 std::unique_ptr<Scalar, internal::CudaFreeDeleter>(device_ptr, internal::CudaFreeDeleter{/*borrow=*/true});
580 dm.rows_ = rows;
581 dm.cols_ = cols;
582 return dm;
583 }
584
588 Scalar* release() {
589 Scalar* p = data_.release();
590 rows_ = 0;
591 cols_ = 0;
592 capacity_bytes_ = 0;
593 if (ready_event_) {
594 EIGEN_CUDA_RUNTIME_CHECK(cudaEventDestroy(ready_event_));
595 ready_event_ = nullptr;
596 }
597 ready_stream_ = nullptr;
598 return p;
599 }
600
601 private:
602 // Fresh owning allocation of `bytes` (no-op for empty). Also resets the
603 // deleter so a previously borrowed (view) deleter cannot leak the new
604 // owned pointer.
605 void allocate(size_t bytes) {
606 if (bytes > 0) {
607 data_ = std::unique_ptr<Scalar, internal::CudaFreeDeleter>(static_cast<Scalar*>(internal::device_malloc(bytes)),
608 internal::CudaFreeDeleter{});
609 capacity_bytes_ = bytes;
610 }
611 }
612
613 void ensureEvent() {
614 if (!ready_event_) {
615 EIGEN_CUDA_RUNTIME_CHECK(cudaEventCreateWithFlags(&ready_event_, cudaEventDisableTiming));
616 }
617 }
618
619 void retainBuffer(internal::DeviceBuffer&& buffer) { retained_buffer_ = std::move(buffer); }
620
621 std::unique_ptr<Scalar, internal::CudaFreeDeleter> data_;
622 Index rows_ = 0;
623 Index cols_ = 0;
624 size_t capacity_bytes_ = 0; // owned allocation size (0 for borrowed views)
625 cudaEvent_t ready_event_ = nullptr; // internal: tracks last write completion
626 cudaStream_t ready_stream_ = nullptr; // stream that recorded ready_event_ (for same-stream skip)
627 internal::DeviceBuffer retained_buffer_; // internal: keeps async aux buffers alive
628};
629} // namespace gpu
630} // namespace Eigen
631
632#endif // EIGEN_GPU_DEVICE_MATRIX_H
constexpr Scalar * data()
View returned by DeviceMatrix::adjoint(); maps to the cuBLAS conjugate-transpose operand flag.
Definition DeviceExpr.h:46
Definition DeviceBlasExpr.h:81
Unified GPU execution context owning a CUDA stream and library handles.
Definition GpuContext.h:81
Linear combination of two device matrices.
Definition DeviceExpr.h:260
RAII wrapper for a dense column-major matrix in GPU device memory.
Definition DeviceMatrix.h:122
static DeviceMatrix fromHost(const DenseBase< Derived > &host, cudaStream_t stream=nullptr)
Definition DeviceMatrix.h:226
DeviceMatrix & operator*=(Scalar alpha)
Definition DeviceDispatch.h:744
LLTView< Scalar, Lower > llt() const
Definition DeviceMatrix.h:399
HostTransfer< Scalar > toHostAsync(cudaStream_t stream=nullptr) const
Definition DeviceMatrix.h:289
static DeviceMatrix fromHostAsync(const Scalar *host_data, Index rows, Index cols, cudaStream_t stream)
Definition DeviceMatrix.h:251
Scalar * release()
Definition DeviceMatrix.h:588
DeviceMatrix(Index n)
Definition DeviceMatrix.h:134
void addScaled(Context &ctx, Scalar alpha, const DeviceMatrix &x)
Definition DeviceDispatch.h:677
DeviceMatrix(Index rows, Index cols)
Definition DeviceMatrix.h:140
Assignment< Scalar > device(Context &ctx)
Definition DeviceMatrix.h:386
DeviceMatrix cwiseProduct(Context &ctx, const DeviceMatrix &other) const
Definition DeviceDispatch.h:858
TransposeView< Scalar > transpose() const
Definition DeviceMatrix.h:382
void scale(Context &ctx, Scalar alpha)
Definition DeviceDispatch.h:689
void copyFrom(Context &ctx, const DeviceMatrix &other)
Definition DeviceDispatch.h:699
size_t sizeInBytes() const
Definition DeviceMatrix.h:360
DeviceMatrix & operator/=(Scalar alpha)
Definition DeviceDispatch.h:778
LLTView< Scalar, UpLo > llt() const
Definition DeviceMatrix.h:403
bool isView() const
Definition DeviceMatrix.h:357
DeviceMatrix & operator-=(const GemmExpr< Lhs, Rhs > &expr)
void divide(Context &ctx, Scalar alpha)
Definition DeviceDispatch.h:767
ConstSelfAdjointView< Scalar, UpLo > selfadjointView() const
Definition DeviceMatrix.h:429
static DeviceMatrix view(Scalar *device_ptr, Index rows, Index cols)
Definition DeviceMatrix.h:576
static DeviceMatrix adopt(Scalar *device_ptr, Index rows, Index cols)
Definition DeviceMatrix.h:560
void recordReady(cudaStream_t stream)
Definition DeviceMatrix.h:363
DeviceMatrix clone(cudaStream_t stream=nullptr) const
Definition DeviceMatrix.h:307
AdjointView< Scalar > adjoint() const
Definition DeviceMatrix.h:379
PlainMatrix toHost(cudaStream_t stream=nullptr) const
Definition DeviceMatrix.h:270
DeviceScalar< Scalar > dot(Context &ctx, const DeviceMatrix &other) const
Definition DeviceDispatch.h:599
SelfAdjointView< Scalar, UpLo > selfadjointView()
Definition DeviceMatrix.h:423
LUView< Scalar > lu() const
Definition DeviceMatrix.h:408
void resize(Index rows, Index cols)
Definition DeviceMatrix.h:323
DeviceMatrix & noalias()
Definition DeviceMatrix.h:557
TriangularView< Scalar, UpLo > triangularView() const
Definition DeviceMatrix.h:417
void waitReady(cudaStream_t stream) const
Definition DeviceMatrix.h:372
RAII wrapper for a scalar in GPU device memory.
Definition DeviceScalar.h:33
Expression that scales a device matrix by a DeviceScalar.
Definition DeviceExpr.h:231
Expression returned by operator*(lhs_expr, rhs_expr), dispatched to cuBLAS GEMM.
Definition DeviceExpr.h:92
Future for an asynchronous device-to-host matrix transfer.
Definition DeviceMatrix.h:39
PlainMatrix & get()
Definition DeviceMatrix.h:46
bool ready() const
Definition DeviceMatrix.h:59
Definition DeviceSolverExpr.h:61
Definition DeviceSolverExpr.h:76
Definition DeviceSolverExpr.h:30
Definition DeviceSolverExpr.h:46
Expression returned by operator*(Scalar, DeviceMatrix/View), carrying the scalar factor.
Definition DeviceExpr.h:77
Definition DeviceBlasExpr.h:61
Definition GpuSparseContext.h:166
Definition GpuSparseContext.h:146
Definition DeviceBlasExpr.h:96
View returned by DeviceMatrix::transpose(); maps to the cuBLAS transpose operand flag.
Definition DeviceExpr.h:58
Definition DeviceBlasExpr.h:29
Definition DeviceBlasExpr.h:45
Internal RAII owner for an untyped GPU device allocation.
Definition GpuSupport.h:293
Namespace containing all symbols from the Eigen library.