Eigen-Contrib  5.0.1
 
Loading...
Searching...
No Matches
GpuEigenSolver.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// GPU self-adjoint eigenvalue decomposition using cuSOLVER's divide-and-conquer
12// cusolverDnXsyevd. Eigenvalues and eigenvectors stay on device.
13
14#ifndef EIGEN_GPU_EIGENSOLVER_H
15#define EIGEN_GPU_EIGENSOLVER_H
16
17// IWYU pragma: private
18#include "./InternalHeaderCheck.h"
19
20#include "./GpuSolverContext.h"
21
22namespace Eigen {
23namespace gpu {
24template <typename Scalar_>
25class SelfAdjointEigenSolver {
26 public:
27 using Scalar = Scalar_;
28 using RealScalar = typename NumTraits<Scalar>::Real;
29 using PlainMatrix = Eigen::Matrix<Scalar, Dynamic, Dynamic, ColMajor>;
30 using RealVector = Eigen::Matrix<RealScalar, Dynamic, 1>;
31
32 SelfAdjointEigenSolver() = default;
33
37 explicit SelfAdjointEigenSolver(Context& ctx) : solver_ctx_(ctx) {}
38
40 template <typename InputType>
41 explicit SelfAdjointEigenSolver(const DenseBase<InputType>& A, int options = ComputeEigenvectors) {
42 compute(A, options);
43 }
44
45 explicit SelfAdjointEigenSolver(const DeviceMatrix<Scalar>& d_A, int options = ComputeEigenvectors) {
46 compute(d_A, options);
47 }
48
50 explicit SelfAdjointEigenSolver(DeviceMatrix<Scalar>&& d_A, int options = ComputeEigenvectors) {
51 compute(std::move(d_A), options);
52 }
53
55 template <typename InputType>
56 SelfAdjointEigenSolver(Context& ctx, const DenseBase<InputType>& A, int options = ComputeEigenvectors)
57 : solver_ctx_(ctx) {
58 compute(A, options);
59 }
60
62 SelfAdjointEigenSolver(Context& ctx, const DeviceMatrix<Scalar>& d_A, int options = ComputeEigenvectors)
63 : solver_ctx_(ctx) {
64 compute(d_A, options);
65 }
66
68 SelfAdjointEigenSolver(Context& ctx, DeviceMatrix<Scalar>&& d_A, int options = ComputeEigenvectors)
69 : solver_ctx_(ctx) {
70 compute(std::move(d_A), options);
71 }
72
73 ~SelfAdjointEigenSolver() = default;
74
75 SelfAdjointEigenSolver(const SelfAdjointEigenSolver&) = delete;
76 SelfAdjointEigenSolver& operator=(const SelfAdjointEigenSolver&) = delete;
77
78 SelfAdjointEigenSolver(SelfAdjointEigenSolver&& o) noexcept
79 : solver_ctx_(std::move(o.solver_ctx_)),
80 d_A_(std::move(o.d_A_)),
81 d_W_(std::move(o.d_W_)),
82 compute_eigenvectors_(o.compute_eigenvectors_),
83 n_(o.n_),
84 lda_(o.lda_) {
85 o.compute_eigenvectors_ = true;
86 o.n_ = 0;
87 o.lda_ = 0;
88 }
89
90 SelfAdjointEigenSolver& operator=(SelfAdjointEigenSolver&& o) noexcept {
91 if (this != &o) {
92 solver_ctx_ = std::move(o.solver_ctx_);
93 d_A_ = std::move(o.d_A_);
94 d_W_ = std::move(o.d_W_);
95 compute_eigenvectors_ = o.compute_eigenvectors_;
96 n_ = o.n_;
97 lda_ = o.lda_;
98 o.compute_eigenvectors_ = true;
99 o.n_ = 0;
100 o.lda_ = 0;
101 }
102 return *this;
103 }
104
105 template <typename InputType>
106 SelfAdjointEigenSolver& compute(const DenseBase<InputType>& A, int options = ComputeEigenvectors) {
107 // Route through the adopting overload: the freshly uploaded matrix is
108 // decomposed in place (syevd overwrites its input) — no second device copy.
109 return compute(DeviceMatrix<Scalar>::fromHost(A.derived(), solver_ctx_.stream()), options);
110 }
111
112 SelfAdjointEigenSolver& compute(const DeviceMatrix<Scalar>& d_A, int options = ComputeEigenvectors) {
113 if (!begin_compute(d_A, options)) return *this;
114
115 const size_t mat_bytes = static_cast<size_t>(lda_) * static_cast<size_t>(n_) * sizeof(Scalar);
116 internal::ensure_sized(d_A_, mat_bytes);
117 EIGEN_CUDA_RUNTIME_CHECK(
118 cudaMemcpyAsync(d_A_.get(), d_A.data(), mat_bytes, cudaMemcpyDeviceToDevice, solver_ctx_.stream()));
119
120 factorize();
121 return *this;
122 }
123
127 SelfAdjointEigenSolver& compute(DeviceMatrix<Scalar>&& d_A, int options = ComputeEigenvectors) {
128 if (d_A.isView()) return compute(static_cast<const DeviceMatrix<Scalar>&>(d_A), options);
129 if (!begin_compute(d_A, options)) return *this;
130
131 d_A_ = internal::DeviceBuffer::adopt(static_cast<void*>(d_A.release()),
132 static_cast<size_t>(lda_) * static_cast<size_t>(n_) * sizeof(Scalar));
133
134 factorize();
135 return *this;
136 }
137
138 ComputationInfo info() const { return solver_ctx_.info(); }
139
140 Index cols() const { return n_; }
141 Index rows() const { return n_; }
142
144 RealVector eigenvalues() const {
145 eigen_assert(solver_ctx_.info() == Success);
146 RealVector W(n_);
147 if (n_ > 0) {
148 solver_ctx_.download(W.data(), d_W_.get(), static_cast<size_t>(n_) * sizeof(RealScalar));
149 }
150 return W;
151 }
152
155 PlainMatrix eigenvectors() const {
156 eigen_assert(solver_ctx_.info() == Success);
157 eigen_assert(compute_eigenvectors_ && "eigenvectors() requires ComputeEigenvectors option");
158 PlainMatrix V(n_, n_);
159 if (n_ > 0) {
160 solver_ctx_.download(V.data(), d_A_.get(), static_cast<size_t>(lda_) * static_cast<size_t>(n_) * sizeof(Scalar));
161 }
162 return V;
163 }
164
165 //
166 // These return non-owning DeviceMatrix views over this solver's internal storage. The
167 // view borrows the pointer: destruction does not free; this solver must outlive any
168 // view derived from it. Both accessors are pure metadata — zero kernel launches.
169
173 DeviceMatrix<RealScalar> d_eigenvalues() const {
174 eigen_assert(solver_ctx_.info() == Success);
175 auto v = DeviceMatrix<RealScalar>::view(static_cast<RealScalar*>(d_W_.get()), n_, 1);
176 v.recordReady(solver_ctx_.stream());
177 return v;
178 }
179
182 DeviceMatrix<Scalar> d_eigenvectors() const {
183 eigen_assert(solver_ctx_.info() == Success);
184 eigen_assert(compute_eigenvectors_ && "d_eigenvectors() requires ComputeEigenvectors option");
185 auto v = DeviceMatrix<Scalar>::view(static_cast<Scalar*>(d_A_.get()), n_, n_);
186 v.recordReady(solver_ctx_.stream());
187 return v;
188 }
189
190 cudaStream_t stream() const { return solver_ctx_.stream(); }
191
192 private:
193 mutable internal::GpuSolverContext solver_ctx_;
194 // Overwritten with eigenvectors by syevd. Host and rvalue input are adopted;
195 // the const& path is grow-only.
196 internal::DeviceBuffer d_A_;
197 internal::DeviceBuffer d_W_; // grow-only; eigenvalues (RealScalar, length n)
198 bool compute_eigenvectors_ = true;
199 int64_t n_ = 0;
200 int64_t lda_ = 0;
201
202 // Common compute() prologue: validate, record shape, reset info, wait on
203 // input. Returns false (and clears stale buffers) for empty input.
204 bool begin_compute(const DeviceMatrix<Scalar>& d_A, int options) {
205 eigen_assert(d_A.rows() == d_A.cols() && "SelfAdjointEigenSolver requires a square matrix");
206 eigen_assert((options == ComputeEigenvectors || options == EigenvaluesOnly) &&
207 "options must be ComputeEigenvectors or EigenvaluesOnly");
208 compute_eigenvectors_ = (options == ComputeEigenvectors);
209 n_ = d_A.rows();
210 if (!solver_ctx_.begin_compute(n_ != 0)) {
211 d_A_ = internal::DeviceBuffer();
212 d_W_ = internal::DeviceBuffer();
213 return false;
214 }
215 lda_ = n_;
216 d_A.waitReady(solver_ctx_.stream());
217 return true;
218 }
219
220 void factorize() {
221 constexpr cudaDataType_t dtype = internal::cusolver_data_type<Scalar>::value;
222 constexpr cudaDataType_t rtype = internal::cuda_data_type<RealScalar>::value;
223
224 solver_ctx_.mark_pending();
225
226 internal::ensure_sized(d_W_, static_cast<size_t>(n_) * sizeof(RealScalar));
227
228 const cusolverEigMode_t jobz = compute_eigenvectors_ ? CUSOLVER_EIG_MODE_VECTOR : CUSOLVER_EIG_MODE_NOVECTOR;
229
230 constexpr cublasFillMode_t uplo = CUBLAS_FILL_MODE_LOWER;
231
232 size_t dev_ws = 0, host_ws = 0;
233 EIGEN_CUSOLVER_CHECK(cusolverDnXsyevd_bufferSize(solver_ctx_.cusolverHandle(), solver_ctx_.params_.p, jobz, uplo,
234 n_, dtype, d_A_.get(), lda_, rtype, d_W_.get(), dtype, &dev_ws,
235 &host_ws));
236
237 solver_ctx_.ensure_scratch(dev_ws);
238 solver_ctx_.h_workspace_.resize(host_ws);
239
240 EIGEN_CUSOLVER_CHECK(cusolverDnXsyevd(solver_ctx_.cusolverHandle(), solver_ctx_.params_.p, jobz, uplo, n_, dtype,
241 d_A_.get(), lda_, rtype, d_W_.get(), dtype, solver_ctx_.scratch_workspace(),
242 dev_ws, host_ws > 0 ? solver_ctx_.h_workspace_.data() : nullptr, host_ws,
243 solver_ctx_.scratch_info()));
244
245 solver_ctx_.enqueue_info_copy();
246 }
247};
248} // namespace gpu
249} // namespace Eigen
250
251#endif // EIGEN_GPU_EIGENSOLVER_H
static DeviceMatrix fromHost(const DenseBase< Derived > &host, cudaStream_t stream=nullptr)
Definition DeviceMatrix.h:226
static DeviceMatrix view(Scalar *device_ptr, Index rows, Index cols)
Definition DeviceMatrix.h:576
ComputationInfo
ComputeEigenvectors
EigenvaluesOnly
Namespace containing all symbols from the Eigen library.