Eigen-Contrib  5.0.1
 
Loading...
Searching...
No Matches
GpuSparseSolverBase.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// Common base for the GPU sparse direct solvers (LLT, LDLT, LU) via cuDSS.
12//
13// All three share one analyzePattern → factorize → solve workflow and differ only
14// in the cudssMatrixType_t and cudssMatrixViewType_t they pass to cuDSS, so this
15// CRTP base implements the workflow and derived classes supply the two types.
16//
17// Not thread-safe: concurrent calls race on the cuDSS handle, the bound stream,
18// and the cached scratch buffers. This includes concurrent solve() calls on one
19// instance, const though solve() is. Use one solver per thread.
20
21#ifndef EIGEN_GPU_SPARSE_SOLVER_BASE_H
22#define EIGEN_GPU_SPARSE_SOLVER_BASE_H
23
24// IWYU pragma: private
25#include "./InternalHeaderCheck.h"
26
27#include "./CuSparseSupport.h"
28#include "./CuDssSupport.h"
29
30namespace Eigen {
31namespace gpu {
32
33// The named algorithms carry the cuDSS values, so apply_config() can
34// static_cast them. cuDSS < 0.8 names none of them, so there they are not
35// declared at all: selecting an algorithm the linked cuDSS cannot honor is a
36// compile error rather than a runtime rejection, and EIGEN_HAS_CUDSS_SOLVER_CONFIG
37// lets a caller branch on which case a build is in.
38// Default is declared either way and is a sentinel rather than a cuDSS value:
39// apply_config() skips its cudssConfigSet(), leaving the cuDSS default in place
40// without depending on its numeric value.
41
46enum class SparseReordering : int {
47 Default = -1,
48#if EIGEN_HAS_CUDSS_SOLVER_CONFIG
49 BtfColamd = CUDSS_REORDERING_ALG_BTF_COLAMD,
50 Colamd = CUDSS_REORDERING_ALG_COLAMD,
51 Amd = CUDSS_REORDERING_ALG_AMD,
52 NestedDissection = CUDSS_REORDERING_ALG_NESTED_DISSECTION,
53 Natural = CUDSS_REORDERING_ALG_NONE,
54#endif
55};
56
61enum class SparseMatching : int {
62 Default = -1,
63#if EIGEN_HAS_CUDSS_SOLVER_CONFIG
64 None = CUDSS_MATCHING_ALG_NONE,
65 MaxDiagCount = CUDSS_MATCHING_ALG_MAX_DIAG_COUNT,
66 MaxMinDiag = CUDSS_MATCHING_ALG_MAX_MIN_DIAG,
67 MaxMinDiagAlt = CUDSS_MATCHING_ALG_MAX_MIN_DIAG_ALT,
68 MaxDiagSum = CUDSS_MATCHING_ALG_MAX_DIAG_SUM,
69 MaxDiagProduct = CUDSS_MATCHING_ALG_MAX_DIAG_PRODUCT,
70 Auto = CUDSS_MATCHING_ALG_AUTO,
71#endif
72};
73
78enum class SparsePivoting : int {
79 Default = -1,
80#if EIGEN_HAS_CUDSS_SOLVER_CONFIG
81 None = CUDSS_PIVOT_NONE,
82 GlobalCol = CUDSS_PIVOT_GLOBAL_COL,
83 GlobalRow = CUDSS_PIVOT_GLOBAL_ROW,
84 Diagonal = CUDSS_PIVOT_DIAGONAL,
85 LocalBlock = CUDSS_PIVOT_LOCAL_BLOCK,
86#endif
87};
88
97 SparseReordering reordering = SparseReordering::Default;
98 SparseMatching matching = SparseMatching::Default;
99 SparsePivoting pivoting = SparsePivoting::Default;
101 double pivotThreshold = -1.0;
103 double pivotEpsilon = -1.0;
107 double refinementTolerance = -1.0;
110 bool hybridMemory = false;
117 bool hybridExecute = false;
118
120 bool isDefault() const {
121 return reordering == SparseReordering::Default && matching == SparseMatching::Default &&
122 pivoting == SparsePivoting::Default && pivotThreshold < 0 && pivotEpsilon < 0 && refinementSteps < 0 &&
124 }
125};
126
127namespace internal {
128
138template <typename Scalar_, typename Derived>
139class SparseSolverBase {
140 public:
141 using Scalar = Scalar_;
142 using RealScalar = typename NumTraits<Scalar>::Real;
143 using StorageIndex = int;
146 using DenseVector = Matrix<Scalar, Dynamic, 1>;
148
149 SparseSolverBase() { init_context(/*stream=*/nullptr, /*borrow_stream=*/false); }
150
155 explicit SparseSolverBase(Context& ctx) { init_context(ctx.stream(), /*borrow_stream=*/true); }
156
158 destroy_cudss_objects();
159 if (handle_) (void)cudssDestroy(handle_);
160 if (owns_stream_ && stream_) (void)cudaStreamDestroy(stream_);
161 }
162
163 SparseSolverBase(const SparseSolverBase&) = delete;
164 SparseSolverBase& operator=(const SparseSolverBase&) = delete;
165
186 Derived& setConfig(const SparseSolverConfig& cfg) {
187#if !EIGEN_HAS_CUDSS_SOLVER_CONFIG
188 eigen_assert(cfg.isDefault() && "SparseSolverConfig knobs require cuDSS >= 0.8");
189 if (!cfg.isDefault()) {
190 // The assert above is compiled out in release builds. Record what was
191 // asked for and fail through info() rather than factorizing with the
192 // cuDSS defaults this call meant to replace. analyzePattern() keeps
193 // failing while the stored config is one this cuDSS cannot honor.
194 config_opts_ = cfg;
195 analysis_done_ = false;
196 info_ = InvalidInput;
197 return derived();
198 }
199#endif
200
201 const bool hybrid_mode_changed =
202 cfg.hybridMemory != config_opts_.hybridMemory || cfg.hybridExecute != config_opts_.hybridExecute;
203 eigen_assert((!hybrid_mode_changed || !analysis_done_) &&
204 "hybridMemory/hybridExecute change what analyzePattern() builds, so they must be set before it");
205
206 config_opts_ = cfg;
207 apply_config();
208
209 if (hybrid_mode_changed && analysis_done_) {
210 // The assert above is compiled out in release builds. Fail through
211 // info() rather than factorizing against the wrong analysis.
212 analysis_done_ = false;
213 info_ = InvalidInput;
214 }
215 return derived();
216 }
217
219 const SparseSolverConfig& config() const { return config_opts_; }
220
222 template <typename InputType>
225 if (info_ == Success) {
226 factorize(A);
227 }
228 return derived();
229 }
230
233 template <typename InputType>
235#if !EIGEN_HAS_CUDSS_SOLVER_CONFIG
236 if (!config_opts_.isDefault()) {
237 // setConfig() refused this config; analyzing as if it had been applied
238 // would hand factorize() state built for settings the caller replaced.
239 info_ = InvalidInput;
240 analysis_done_ = false;
241 return derived();
242 }
243#endif
244
245 const InputType& input = A.derived();
246 check_storage_index_bounds<StorageIndex>(input.rows(), input.cols(), input.nonZeros());
247 eigen_assert(input.rows() == input.cols() && "GpuSparseSolver requires a square matrix");
248
249 n_ = input.rows();
250 info_ = InvalidInput;
251 analysis_done_ = false;
252
253 if (n_ == 0) {
254 nnz_ = 0;
255 info_ = Success;
256 analysis_done_ = true;
257 return derived();
258 }
259
260 // For symmetric solvers, ColMajor CSC can be reinterpreted as CSR with
261 // swapped triangle view — zero copy, except that Hermitian matrix types
262 // need the value array conjugated (see csc_upload_values). For general
263 // solvers, we must convert to actual RowMajor CSR so cuDSS sees the
264 // correct matrix, not A^T.
265 if (Derived::needs_csr_conversion()) {
266 const CsrMat csr(input);
267 nnz_ = csr.nonZeros();
268 upload_compressed(csr.outerIndexPtr(), csr.innerIndexPtr(), csr.valuePtr());
269 } else {
270 // Bind by reference when the input is already a compressed CSC matrix —
271 // no host copy.
272 SpMat storage;
273 const SpMat& csc = bind_sparse<SpMat>(input, storage);
274 nnz_ = csc.nonZeros();
275 upload_compressed(csc.outerIndexPtr(), csc.innerIndexPtr(), csc_upload_values(csc.valuePtr(), nnz_));
276 }
277 create_cudss_matrix();
278
279 if (data_) EIGEN_CUDSS_CHECK(cudssDataDestroy(handle_, data_));
280 EIGEN_CUDSS_CHECK(cudssDataCreate(handle_, &data_));
281
282 create_placeholder_dense();
283
284 EIGEN_CUDSS_CHECK(cudssExecute(handle_, CUDSS_PHASE_ANALYSIS, config_, data_, d_A_cudss_, d_x_cudss_, d_b_cudss_));
285
286 analysis_done_ = true;
287 info_ = Success;
288 return derived();
289 }
290
299 template <typename InputType>
301 eigen_assert(analysis_done_ && "factorize() requires analyzePattern() first");
302 if (!analysis_done_) {
303 // Reachable in release builds whenever setConfig() invalidated the
304 // analysis rather than applying the request; see setConfig().
305 info_ = InvalidInput;
306 return derived();
307 }
308
309 if (n_ == 0) {
310 info_ = Success;
311 return derived();
312 }
313
314 // Convert to the same format used in analyzePattern. When the input is
315 // already a compressed CSC matrix (symmetric solvers), it is bound by
316 // reference — no host copy or conversion on refactorize (Hermitian types
317 // re-conjugate the value array; the pattern stays zero-copy).
318 // The temporaries must outlive the async memcpy (pageable H2D is actually
319 // synchronous w.r.t. the host, but keep them alive for clarity).
320 const InputType& input = A.derived();
321 check_storage_index_bounds<StorageIndex>(input.rows(), input.cols(), input.nonZeros());
322 eigen_assert(input.rows() == n_ && input.cols() == n_);
323
324 const Scalar* value_ptr;
325 Index value_nnz;
326 CsrMat csr_tmp;
327 SpMat csc_storage;
328 if (Derived::needs_csr_conversion()) {
329 csr_tmp = CsrMat(input);
330 value_ptr = csr_tmp.valuePtr();
331 value_nnz = csr_tmp.nonZeros();
332 } else {
333 const SpMat& csc = bind_sparse<SpMat>(input, csc_storage);
334 value_ptr = csc_upload_values(csc.valuePtr(), csc.nonZeros());
335 value_nnz = csc.nonZeros();
336 }
337 eigen_assert(value_nnz == nnz_);
338 EIGEN_UNUSED_VARIABLE(value_nnz);
339
340 EIGEN_CUDA_RUNTIME_CHECK(cudaMemcpyAsync(d_values_.get(), value_ptr, static_cast<size_t>(nnz_) * sizeof(Scalar),
341 cudaMemcpyHostToDevice, stream_));
342
343 EIGEN_CUDSS_CHECK(cudssMatrixSetValues(d_A_cudss_, d_values_.get()));
344
345 info_ = InvalidInput;
346 info_synced_ = false;
347 EIGEN_CUDSS_CHECK(
348 cudssExecute(handle_, CUDSS_PHASE_FACTORIZATION, config_, data_, d_A_cudss_, d_x_cudss_, d_b_cudss_));
349
350 return derived();
351 }
352
355 template <typename Rhs>
356 DenseMatrix solve(const MatrixBase<Rhs>& B) const {
357 // Debug builds verify the factorization (info() synchronizes on the first
358 // call after factorize()); release builds skip both the check and the
359 // sync — use info() explicitly when failure must be detected.
360 eigen_assert(info() == Success && "GpuSparseSolver::solve requires a successful factorization");
361 eigen_assert(B.rows() == n_);
362
363 if (n_ == 0) return DenseMatrix(0, B.cols());
364
365 const Ref<const DenseMatrix> rhs(B.derived());
366 const int64_t nrhs = static_cast<int64_t>(rhs.cols());
367
368 // Reuse cached d_b/d_x scratch to avoid cudaMalloc/cudaFree per solve.
369 const size_t rhs_bytes = static_cast<size_t>(n_) * static_cast<size_t>(nrhs) * sizeof(Scalar);
370 ensure_solve_buffer(d_b_solve_, rhs_bytes);
371 ensure_solve_buffer(d_x_solve_, rhs_bytes);
372 internal::upload_host_matrix(static_cast<Scalar*>(d_b_solve_.get()), n_, rhs.data(), rhs.outerStride(), n_, nrhs,
373 stream_);
374
375 update_solve_descriptors(nrhs, d_b_solve_.get(), d_x_solve_.get());
376 EIGEN_CUDSS_CHECK(
377 cudssExecute(handle_, CUDSS_PHASE_SOLVE, config_, data_, d_A_cudss_, x_solve_cudss_, b_solve_cudss_));
378
379 DenseMatrix X(n_, rhs.cols());
380 EIGEN_CUDA_RUNTIME_CHECK(cudaMemcpyAsync(X.data(), d_x_solve_.get(), rhs_bytes, cudaMemcpyDeviceToHost, stream_));
381 EIGEN_CUDA_RUNTIME_CHECK(cudaStreamSynchronize(stream_));
382
383 return X;
384 }
385
392 eigen_assert(info() == Success && "GpuSparseSolver::solve requires a successful factorization");
393 eigen_assert(d_B.rows() == n_);
394
395 const int64_t nrhs = static_cast<int64_t>(d_B.cols());
396 DeviceMatrix<Scalar> X(n_, d_B.cols());
397 if (n_ == 0 || nrhs == 0) return X;
398
399 d_B.waitReady(stream_);
400 update_solve_descriptors(nrhs, const_cast<Scalar*>(d_B.data()), X.data());
401 EIGEN_CUDSS_CHECK(
402 cudssExecute(handle_, CUDSS_PHASE_SOLVE, config_, data_, d_A_cudss_, x_solve_cudss_, b_solve_cudss_));
403 X.recordReady(stream_);
404 return X;
405 }
406
407 ComputationInfo info() const {
408 sync_info();
409 return info_;
410 }
411 Index rows() const { return n_; }
412 Index cols() const { return n_; }
413
414 cudaStream_t stream() const { return stream_; }
415
416 protected:
417 cudaStream_t stream_ = nullptr;
418 bool owns_stream_ = true;
419 cudssHandle_t handle_ = nullptr;
420 cudssConfig_t config_ = nullptr;
421 cudssData_t data_ = nullptr;
422 cudssMatrix_t d_A_cudss_ = nullptr;
423 cudssMatrix_t d_x_cudss_ = nullptr;
424 cudssMatrix_t d_b_cudss_ = nullptr;
425
426 DeviceBuffer d_rowPtr_;
427 DeviceBuffer d_colIdx_;
428 DeviceBuffer d_values_;
429
430 // Host staging for the conjugated value array (Hermitian zero-copy path
431 // only; see csc_upload_values). Kept as a member so it outlives the async
432 // H2D copy.
433 DenseVector conj_values_;
434
435 mutable DeviceBuffer d_b_solve_;
436 mutable DeviceBuffer d_x_solve_;
437
438 // Cached cuDSS dense descriptors for solve, re-pointed per call and
439 // recreated only when nrhs changes.
440 mutable cudssMatrix_t b_solve_cudss_ = nullptr;
441 mutable cudssMatrix_t x_solve_cudss_ = nullptr;
442 mutable int64_t solve_desc_nrhs_ = -1;
443
444 int64_t n_ = 0;
445 int64_t nnz_ = 0;
446 mutable ComputationInfo info_ = InvalidInput;
447 mutable bool info_synced_ = true;
448 bool analysis_done_ = false;
449 SparseSolverConfig config_opts_;
450
451 private:
452 Derived& derived() { return static_cast<Derived&>(*this); }
453 const Derived& derived() const { return static_cast<const Derived&>(*this); }
454
455 void init_context(cudaStream_t stream, bool borrow_stream) {
456 if (borrow_stream) {
457 // nullptr is CUDA's valid legacy default stream, so ownership cannot be
458 // inferred from the stream value.
459 stream_ = stream;
460 owns_stream_ = false;
461 } else {
462 EIGEN_CUDA_RUNTIME_CHECK(cudaStreamCreate(&stream_));
463 owns_stream_ = true;
464 }
465 EIGEN_CUDSS_CHECK(cudssCreate(&handle_));
466 EIGEN_CUDSS_CHECK(cudssSetStream(handle_, stream_));
467 EIGEN_CUDSS_CHECK(cudssConfigCreate(&config_));
468 }
469
470 // Rebuild config_ from config_opts_. Recreating rather than mutating lets a
471 // field reset to Default restore the cuDSS default, whose numeric value is
472 // not part of the cuDSS API contract.
473 void apply_config() {
474#if EIGEN_HAS_CUDSS_SOLVER_CONFIG
475 if (config_) {
476 // cuDSS reads the config during execution, and both factorize() and the
477 // device-resident solve() return with their phase still queued. Retire
478 // that work before the config it is reading goes away.
479 EIGEN_CUDA_RUNTIME_CHECK(cudaStreamSynchronize(stream_));
480 (void)cudssConfigDestroy(config_);
481 }
482 config_ = nullptr;
483 EIGEN_CUDSS_CHECK(cudssConfigCreate(&config_));
484 const SparseSolverConfig& c = config_opts_;
485 if (c.reordering != SparseReordering::Default) {
486 const cudssReorderingAlg_t v = static_cast<cudssReorderingAlg_t>(c.reordering);
487 EIGEN_CUDSS_CHECK(cudssConfigSet(config_, CUDSS_CONFIG_REORDERING_ALG, &v, sizeof(v)));
488 }
489 if (c.matching != SparseMatching::Default) {
490 const cudssMatchingAlg_t v = static_cast<cudssMatchingAlg_t>(c.matching);
491 EIGEN_CUDSS_CHECK(cudssConfigSet(config_, CUDSS_CONFIG_MATCHING_ALG, &v, sizeof(v)));
492 }
493 if (c.pivoting != SparsePivoting::Default) {
494 const cudssPivotType_t v = static_cast<cudssPivotType_t>(c.pivoting);
495 EIGEN_CUDSS_CHECK(cudssConfigSet(config_, CUDSS_CONFIG_PIVOT_TYPE, &v, sizeof(v)));
496 }
497 if (c.pivotThreshold >= 0) {
498 EIGEN_CUDSS_CHECK(
499 cudssConfigSet(config_, CUDSS_CONFIG_PIVOT_THRESHOLD, &c.pivotThreshold, sizeof(c.pivotThreshold)));
500 }
501 if (c.pivotEpsilon >= 0) {
502 EIGEN_CUDSS_CHECK(cudssConfigSet(config_, CUDSS_CONFIG_PIVOT_EPSILON, &c.pivotEpsilon, sizeof(c.pivotEpsilon)));
503 }
504 if (c.refinementSteps >= 0) {
505 EIGEN_CUDSS_CHECK(
506 cudssConfigSet(config_, CUDSS_CONFIG_IR_N_STEPS, &c.refinementSteps, sizeof(c.refinementSteps)));
507 }
508 if (c.refinementTolerance >= 0) {
509 EIGEN_CUDSS_CHECK(
510 cudssConfigSet(config_, CUDSS_CONFIG_IR_TOL, &c.refinementTolerance, sizeof(c.refinementTolerance)));
511 }
512 if (c.hybridMemory) {
513 const int v = 1;
514 EIGEN_CUDSS_CHECK(cudssConfigSet(config_, CUDSS_CONFIG_HYBRID_MEMORY_MODE, &v, sizeof(v)));
515 if (c.hybridMemoryDeviceLimit >= 0) {
516 const int64_t limit = c.hybridMemoryDeviceLimit;
517 EIGEN_CUDSS_CHECK(cudssConfigSet(config_, CUDSS_CONFIG_HYBRID_DEVICE_MEMORY_LIMIT, &limit, sizeof(limit)));
518 }
519 }
520 if (c.hybridExecute) {
521 const int v = 1;
522 EIGEN_CUDSS_CHECK(cudssConfigSet(config_, CUDSS_CONFIG_HYBRID_EXECUTE_MODE, &v, sizeof(v)));
523 }
524#else
525 // setConfig() returns early for anything else, so there is nothing to apply.
526 eigen_internal_assert(config_opts_.isDefault());
527#endif
528 }
529
530 void ensure_solve_buffer(DeviceBuffer& buf, size_t needed) const {
531 if (needed > buf.size()) {
532 if (buf) EIGEN_CUDA_RUNTIME_CHECK(cudaStreamSynchronize(stream_));
533 buf = DeviceBuffer(needed);
534 }
535 }
536
537 // Recreate the solve descriptors when nrhs changes; otherwise just re-point
538 // them (cudssMatrixSetValues is a host-side pointer update).
539 void update_solve_descriptors(int64_t nrhs, void* b_ptr, void* x_ptr) const {
540 constexpr cudss_value_type_t dtype = to_cudss_data_type(cuda_data_type<Scalar>::value);
541 if (!b_solve_cudss_ || solve_desc_nrhs_ != nrhs) {
542 destroy_solve_descriptors();
543 EIGEN_CUDSS_CHECK(cudssMatrixCreateDn(&b_solve_cudss_, n_, nrhs, n_, b_ptr, dtype, CUDSS_LAYOUT_COL_MAJOR));
544 EIGEN_CUDSS_CHECK(cudssMatrixCreateDn(&x_solve_cudss_, n_, nrhs, n_, x_ptr, dtype, CUDSS_LAYOUT_COL_MAJOR));
545 solve_desc_nrhs_ = nrhs;
546 } else {
547 EIGEN_CUDSS_CHECK(cudssMatrixSetValues(b_solve_cudss_, b_ptr));
548 EIGEN_CUDSS_CHECK(cudssMatrixSetValues(x_solve_cudss_, x_ptr));
549 }
550 }
551
552 void destroy_solve_descriptors() const {
553 if (b_solve_cudss_) {
554 (void)cudssMatrixDestroy(b_solve_cudss_);
555 b_solve_cudss_ = nullptr;
556 }
557 if (x_solve_cudss_) {
558 (void)cudssMatrixDestroy(x_solve_cudss_);
559 x_solve_cudss_ = nullptr;
560 }
561 solve_desc_nrhs_ = -1;
562 }
563
564 void sync_info() const {
565 if (!info_synced_) {
566 // cudssDataGet for CUDSS_DATA_INFO synchronizes the stream internally,
567 // so an explicit cudaStreamSynchronize would be redundant.
568 int cudss_info = 0;
569 EIGEN_CUDSS_CHECK(cudssDataGet(handle_, data_, CUDSS_DATA_INFO, &cudss_info, sizeof(cudss_info), nullptr));
570 info_ = (cudss_info == 0) ? Success : NumericalIssue;
571 info_synced_ = true;
572 }
573 }
574
575 // Destructor-only cleanup: there is no useful recovery path for failures.
576 void destroy_cudss_objects() {
577 // Same lifetime rule as apply_config(): the queued phase reads the data
578 // handle and the matrix descriptors as well as the config. Unchecked
579 // because this runs from the destructor.
580 (void)cudaStreamSynchronize(stream_);
581 destroy_solve_descriptors();
582 if (d_A_cudss_) {
583 (void)cudssMatrixDestroy(d_A_cudss_);
584 d_A_cudss_ = nullptr;
585 }
586 if (d_x_cudss_) {
587 (void)cudssMatrixDestroy(d_x_cudss_);
588 d_x_cudss_ = nullptr;
589 }
590 if (d_b_cudss_) {
591 (void)cudssMatrixDestroy(d_b_cudss_);
592 d_b_cudss_ = nullptr;
593 }
594 if (data_) {
595 (void)cudssDataDestroy(handle_, data_);
596 data_ = nullptr;
597 }
598 if (config_) {
599 (void)cudssConfigDestroy(config_);
600 config_ = nullptr;
601 }
602 }
603
604 // The zero-copy CSC-as-CSR reinterpretation hands cuDSS the stored triangle
605 // of A^T. Real symmetric types are unaffected (A^T = A), but the Hermitian
606 // types need the triangle of A, whose entries are the conjugates of A^T's.
607 static constexpr bool needs_value_conjugation() {
608 return Derived::cudss_matrix_type() == CUDSS_MTYPE_HPD || Derived::cudss_matrix_type() == CUDSS_MTYPE_HERMITIAN;
609 }
610
611 // Value array for the zero-copy CSC-as-CSR path: conjugated into
612 // conj_values_ for Hermitian matrix types, passed through otherwise. The
613 // pattern arrays remain zero-copy either way.
614 const Scalar* csc_upload_values(const Scalar* values, Index nnz) {
615 if (!needs_value_conjugation()) return values;
616 conj_values_ = Map<const DenseVector>(values, nnz).conjugate();
617 return conj_values_.data();
618 }
619
620 void upload_compressed(const StorageIndex* outer, const StorageIndex* inner, const Scalar* values) {
621 const size_t rowptr_bytes = static_cast<size_t>(n_ + 1) * sizeof(StorageIndex);
622 const size_t colidx_bytes = static_cast<size_t>(nnz_) * sizeof(StorageIndex);
623 const size_t values_bytes = static_cast<size_t>(nnz_) * sizeof(Scalar);
624
625 d_rowPtr_ = DeviceBuffer(rowptr_bytes);
626 d_colIdx_ = DeviceBuffer(colidx_bytes);
627 d_values_ = DeviceBuffer(values_bytes);
628
629 EIGEN_CUDA_RUNTIME_CHECK(cudaMemcpyAsync(d_rowPtr_.get(), outer, rowptr_bytes, cudaMemcpyHostToDevice, stream_));
630 EIGEN_CUDA_RUNTIME_CHECK(cudaMemcpyAsync(d_colIdx_.get(), inner, colidx_bytes, cudaMemcpyHostToDevice, stream_));
631 EIGEN_CUDA_RUNTIME_CHECK(cudaMemcpyAsync(d_values_.get(), values, values_bytes, cudaMemcpyHostToDevice, stream_));
632 }
633
634 void create_cudss_matrix() {
635 if (d_A_cudss_) EIGEN_CUDSS_CHECK(cudssMatrixDestroy(d_A_cudss_));
636
637 constexpr cudss_value_type_t idx_type = to_cudss_data_type(cudss_index_type<StorageIndex>::value);
638 constexpr cudss_value_type_t val_type = to_cudss_data_type(cuda_data_type<Scalar>::value);
639 constexpr cudssMatrixType_t mtype = Derived::cudss_matrix_type();
640 constexpr cudssMatrixViewType_t mview = Derived::cudss_matrix_view();
641
642#if defined(CUDSS_VERSION) && CUDSS_VERSION >= 800
643 // cuDSS 0.8 split the index type into separate offset/index type params.
644 EIGEN_CUDSS_CHECK(cudssMatrixCreateCsr(&d_A_cudss_, n_, n_, nnz_, d_rowPtr_.get(),
645 /*rowEnd=*/nullptr, d_colIdx_.get(), d_values_.get(), idx_type, idx_type,
646 val_type, mtype, mview, CUDSS_BASE_ZERO));
647#else
648 EIGEN_CUDSS_CHECK(cudssMatrixCreateCsr(&d_A_cudss_, n_, n_, nnz_, d_rowPtr_.get(),
649 /*rowEnd=*/nullptr, d_colIdx_.get(), d_values_.get(), idx_type, val_type,
650 mtype, mview, CUDSS_BASE_ZERO));
651#endif
652 }
653
654 void create_placeholder_dense() {
655 // A new analysis may change n_, so the cached solve descriptors are stale.
656 destroy_solve_descriptors();
657 if (d_x_cudss_) EIGEN_CUDSS_CHECK(cudssMatrixDestroy(d_x_cudss_));
658 if (d_b_cudss_) EIGEN_CUDSS_CHECK(cudssMatrixDestroy(d_b_cudss_));
659 constexpr cudss_value_type_t dtype = to_cudss_data_type(cuda_data_type<Scalar>::value);
660 EIGEN_CUDSS_CHECK(cudssMatrixCreateDn(&d_x_cudss_, n_, 1, n_, nullptr, dtype, CUDSS_LAYOUT_COL_MAJOR));
661 EIGEN_CUDSS_CHECK(cudssMatrixCreateDn(&d_b_cudss_, n_, 1, n_, nullptr, dtype, CUDSS_LAYOUT_COL_MAJOR));
662 }
663};
664} // namespace internal
665} // namespace gpu
666} // namespace Eigen
667
668#endif // EIGEN_GPU_SPARSE_SOLVER_BASE_H
constexpr Scalar * data()
StorageIndex * innerIndexPtr()
StorageIndex * outerIndexPtr()
Index nonZeros() const
Unified GPU execution context owning a CUDA stream and library handles.
Definition GpuContext.h:81
RAII wrapper for a dense column-major matrix in GPU device memory.
Definition DeviceMatrix.h:122
void recordReady(cudaStream_t stream)
Definition DeviceMatrix.h:363
void waitReady(cudaStream_t stream) const
Definition DeviceMatrix.h:372
Definition GpuSparseSolverBase.h:139
Derived & analyzePattern(const SparseMatrixBase< InputType > &A)
Definition GpuSparseSolverBase.h:234
DenseMatrix solve(const MatrixBase< Rhs > &B) const
Definition GpuSparseSolverBase.h:356
SparseSolverBase(Context &ctx)
Definition GpuSparseSolverBase.h:155
Derived & setConfig(const SparseSolverConfig &cfg)
Definition GpuSparseSolverBase.h:186
Derived & factorize(const SparseMatrixBase< InputType > &A)
Definition GpuSparseSolverBase.h:300
Derived & compute(const SparseMatrixBase< InputType > &A)
Definition GpuSparseSolverBase.h:223
DeviceMatrix< Scalar > solve(const DeviceMatrix< Scalar > &d_B) const
Definition GpuSparseSolverBase.h:391
const SparseSolverConfig & config() const
Definition GpuSparseSolverBase.h:219
ComputationInfo
NumericalIssue
Namespace containing all symbols from the Eigen library.
constexpr Derived & derived()
Definition GpuSparseSolverBase.h:96
bool hybridMemory
Definition GpuSparseSolverBase.h:110
bool hybridExecute
Definition GpuSparseSolverBase.h:117
double refinementTolerance
Definition GpuSparseSolverBase.h:107
int refinementSteps
Definition GpuSparseSolverBase.h:105
double pivotEpsilon
Definition GpuSparseSolverBase.h:103
bool isDefault() const
Definition GpuSparseSolverBase.h:120
double pivotThreshold
Definition GpuSparseSolverBase.h:101
int64_t hybridMemoryDeviceLimit
Definition GpuSparseSolverBase.h:114