| // 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 | |
| // Tests for gpu::SparseContext: GPU SpMV/SpMM via cuSPARSE. | |
| #define EIGEN_USE_GPU | |
| #include "main.h" | |
| #include <Eigen/Sparse> | |
| #include <contrib/Eigen/GPU> | |
| #include "gpu_test_helpers.h" | |
| using namespace Eigen; | |
| // ---- Helper: build a random sparse matrix ----------------------------------- | |
| template <typename Scalar> | |
| SparseMatrix<Scalar, ColMajor, int> make_sparse(Index rows, Index cols, double density = 0.1) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat R(rows, cols); | |
| R.reserve(VectorXi::Constant(cols, static_cast<int>(rows * density) + 1)); | |
| for (Index j = 0; j < cols; ++j) { | |
| for (Index i = 0; i < rows; ++i) { | |
| if ((std::rand() / double(RAND_MAX)) < density) { | |
| const RealScalar re = RealScalar(std::rand() / double(RAND_MAX) - 0.5); | |
| const RealScalar im = RealScalar(std::rand() / double(RAND_MAX) - 0.5); | |
| R.insert(i, j) = gpu_test::make_test_value<Scalar>(re, im); | |
| } | |
| } | |
| } | |
| R.makeCompressed(); | |
| return R; | |
| } | |
| // ---- SpMV: y = A * x ------------------------------------------------------- | |
| template <typename Scalar> | |
| void test_spmv(Index rows, Index cols) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat A = make_sparse<Scalar>(rows, cols); | |
| Vec x = Vec::Random(cols); | |
| gpu::SparseContext<Scalar> ctx; | |
| Vec y_gpu = ctx.multiply(A, x); | |
| Vec y_cpu = A * x; | |
| RealScalar tol = RealScalar(10) * RealScalar((std::max)(rows, cols)) * NumTraits<Scalar>::epsilon(); | |
| VERIFY_IS_EQUAL(y_gpu.size(), rows); | |
| VERIFY((y_gpu - y_cpu).norm() / (y_cpu.norm() + RealScalar(1)) < tol); | |
| } | |
| // ---- SpMV with alpha/beta: y = alpha*A*x + beta*y --------------------------- | |
| template <typename Scalar> | |
| void test_spmv_alpha_beta(Index n) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat A = make_sparse<Scalar>(n, n); | |
| Vec x = Vec::Random(n); | |
| Vec y_init = Vec::Random(n); | |
| Scalar alpha(2); | |
| Scalar beta(3); | |
| Vec y_cpu = alpha * (A * x) + beta * y_init; | |
| gpu::SparseContext<Scalar> ctx; | |
| Vec y_gpu = y_init; | |
| ctx.multiply(A, x, y_gpu, alpha, beta); | |
| RealScalar tol = RealScalar(10) * RealScalar(n) * NumTraits<Scalar>::epsilon(); | |
| VERIFY((y_gpu - y_cpu).norm() / (y_cpu.norm() + RealScalar(1)) < tol); | |
| } | |
| // ---- SpMV into a strided destination: a row of a column-major matrix --------- | |
| template <typename Scalar> | |
| void test_spmv_strided_dest(Index n) { | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| const SparseMatrix<Scalar, ColMajor, int> A = make_sparse<Scalar>(n, n); | |
| const Matrix<Scalar, Dynamic, 1> x = Matrix<Scalar, Dynamic, 1>::Random(n); | |
| Mat M = Mat::Zero(3, n), M_cpu = Mat::Zero(3, n); | |
| auto row = M.row(1); | |
| gpu::SparseContext<Scalar> ctx; | |
| ctx.multiply(A, x, row); | |
| M_cpu.row(1) = A * x; | |
| VERIFY_IS_APPROX(M, M_cpu); | |
| // A 1 x n block shaped at run time: innerStride() is 1, its elements are outerStride() apart. | |
| M.setZero(); | |
| auto block_row = M.middleRows(1, 1); | |
| ctx.multiply(A, x, block_row); | |
| VERIFY_IS_APPROX(M, M_cpu); | |
| } | |
| // ---- Transpose: y = A^T * x ------------------------------------------------ | |
| template <typename Scalar> | |
| void test_spmv_transpose(Index rows, Index cols) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat A = make_sparse<Scalar>(rows, cols); | |
| Vec x = Vec::Random(rows); | |
| gpu::SparseContext<Scalar> ctx; | |
| Vec y_gpu = ctx.multiplyT(A, x); | |
| Vec y_cpu = A.transpose() * x; | |
| RealScalar tol = RealScalar(10) * RealScalar((std::max)(rows, cols)) * NumTraits<Scalar>::epsilon(); | |
| VERIFY_IS_EQUAL(y_gpu.size(), cols); | |
| VERIFY((y_gpu - y_cpu).norm() / (y_cpu.norm() + RealScalar(1)) < tol); | |
| } | |
| // ---- SpMV adjoint: y = A^H * x ---------------------------------------------- | |
| template <typename Scalar> | |
| void test_spmv_adjoint(Index rows, Index cols) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat A = make_sparse<Scalar>(rows, cols); | |
| Vec x = Vec::Random(rows); | |
| gpu::SparseContext<Scalar> ctx; | |
| Vec y_gpu = ctx.multiplyAdjoint(A, x); | |
| Vec y_cpu = A.adjoint() * x; | |
| RealScalar tol = RealScalar(10) * RealScalar((std::max)(rows, cols)) * NumTraits<Scalar>::epsilon(); | |
| VERIFY_IS_EQUAL(y_gpu.size(), cols); | |
| VERIFY((y_gpu - y_cpu).norm() / (y_cpu.norm() + RealScalar(1)) < tol); | |
| } | |
| // ---- SpMM: Y = A * X (multiple RHS) ---------------------------------------- | |
| template <typename Scalar> | |
| void test_spmm(Index rows, Index cols, Index nrhs) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat A = make_sparse<Scalar>(rows, cols); | |
| Mat X = Mat::Random(cols, nrhs); | |
| gpu::SparseContext<Scalar> ctx; | |
| Mat Y_gpu = ctx.multiplyMat(A, X); | |
| Mat Y_cpu = A * X; | |
| RealScalar tol = RealScalar(10) * RealScalar((std::max)(rows, cols)) * NumTraits<Scalar>::epsilon(); | |
| VERIFY_IS_EQUAL(Y_gpu.rows(), rows); | |
| VERIFY_IS_EQUAL(Y_gpu.cols(), nrhs); | |
| VERIFY((Y_gpu - Y_cpu).norm() / (Y_cpu.norm() + RealScalar(1)) < tol); | |
| } | |
| // ---- SpMM transpose: Y = A^T * X -------------------------------------------- | |
| template <typename Scalar> | |
| void test_spmm_transpose(Index rows, Index cols, Index nrhs) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat A = make_sparse<Scalar>(rows, cols); | |
| Mat X = Mat::Random(rows, nrhs); | |
| gpu::SparseContext<Scalar> ctx; | |
| Mat Y_gpu = ctx.multiplyMat(A, X, gpu::GpuOp::Trans); | |
| Mat Y_cpu = A.transpose() * X; | |
| RealScalar tol = RealScalar(10) * RealScalar((std::max)(rows, cols)) * NumTraits<Scalar>::epsilon(); | |
| VERIFY_IS_EQUAL(Y_gpu.rows(), cols); | |
| VERIFY_IS_EQUAL(Y_gpu.cols(), nrhs); | |
| VERIFY((Y_gpu - Y_cpu).norm() / (Y_cpu.norm() + RealScalar(1)) < tol); | |
| } | |
| // ---- Pattern replacement at unchanged host pointers ------------------------- | |
| // Regression test: assigning a different same-shape/same-nnz sparsity pattern | |
| // to the same SparseMatrix reuses its index allocations, so every host | |
| // pointer the context saw before is unchanged. A structure cache keyed on | |
| // pointer identity silently keeps the stale device indices and computes with | |
| // the old pattern (here: returns x itself instead of the permuted x). | |
| template <typename Scalar> | |
| void test_pattern_replacement(Index n) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat A(n, n); | |
| A.setIdentity(); | |
| A.makeCompressed(); | |
| Vec x = Vec::Random(n); | |
| gpu::SparseContext<Scalar> ctx; | |
| Vec y1 = ctx.multiply(A, x); | |
| RealScalar tol = NumTraits<Scalar>::epsilon(); | |
| VERIFY((y1 - x).norm() < tol); | |
| // Warm-up repeat: arms any structure cache keyed on the host arrays before | |
| // the pattern is replaced. | |
| Vec y1b = ctx.multiply(A, x); | |
| VERIFY((y1b - x).norm() < tol); | |
| // Value-only update at unchanged structure is picked up (values are | |
| // re-uploaded on every host-input call). | |
| A.coeffRef(0, 0) = Scalar(2); | |
| Vec y1c = ctx.multiply(A, x); | |
| Vec y1c_ref = A * x; | |
| VERIFY((y1c - y1c_ref).norm() < tol); | |
| // Replace the pattern with a cyclic permutation: one entry per column at | |
| // row (j+1) % n, all values 1, so shape and nnz match the identity and the | |
| // result differs from x only through the *index* arrays. | |
| const int* inner_before = A.innerIndexPtr(); | |
| SpMat P(n, n); | |
| P.reserve(VectorXi::Constant(n, 1)); | |
| for (Index j = 0; j < n; ++j) P.insert((j + 1) % n, j) = Scalar(1); | |
| P.makeCompressed(); | |
| A = P; | |
| // Precondition this regression relies on: the assignment reused A's index | |
| // storage. If SparseMatrix assignment ever stops doing so, rewrite the | |
| // pattern in place (e.g. through innerIndexPtr()) to keep the coverage. | |
| VERIFY(A.innerIndexPtr() == inner_before); | |
| Vec y2 = ctx.multiply(A, x); | |
| Vec y2_ref = A * x; | |
| VERIFY((y2 - y2_ref).norm() < tol); | |
| } | |
| // ---- Context reuse ---------------------------------------------------------- | |
| template <typename Scalar> | |
| void test_reuse(Index n) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| gpu::SparseContext<Scalar> ctx; | |
| RealScalar tol = RealScalar(10) * RealScalar(n) * NumTraits<Scalar>::epsilon(); | |
| for (int trial = 0; trial < 3; ++trial) { | |
| SpMat A = make_sparse<Scalar>(n, n); | |
| Vec x = Vec::Random(n); | |
| Vec y_gpu = ctx.multiply(A, x); | |
| Vec y_cpu = A * x; | |
| VERIFY((y_gpu - y_cpu).norm() / (y_cpu.norm() + RealScalar(1)) < tol); | |
| } | |
| } | |
| // ---- Empty ------------------------------------------------------------------ | |
| template <typename Scalar> | |
| void test_empty() { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| SpMat A(0, 0); | |
| A.makeCompressed(); | |
| Vec x(0); | |
| gpu::SparseContext<Scalar> ctx; | |
| Vec y = ctx.multiply(A, x); | |
| VERIFY_IS_EQUAL(y.size(), 0); | |
| } | |
| // ---- gpu::DeviceMatrix SpMV (no host roundtrip) ---------------------------------- | |
| template <typename Scalar> | |
| void test_spmv_device(Index n) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat A = make_sparse<Scalar>(n, n); | |
| Vec x = Vec::Random(n); | |
| // Use shared gpu::Context for same-stream execution. | |
| gpu::Context gpu_ctx; | |
| gpu::SparseContext<Scalar> ctx(gpu_ctx); | |
| auto d_x = gpu::DeviceMatrix<Scalar>::fromHost(x, gpu_ctx.stream()); | |
| gpu::DeviceMatrix<Scalar> d_y; | |
| ctx.multiply(A, d_x, d_y); | |
| Vec y_gpu = d_y.toHost(gpu_ctx.stream()); | |
| Vec y_cpu = A * x; | |
| RealScalar tol = RealScalar(10) * RealScalar(n) * NumTraits<Scalar>::epsilon(); | |
| VERIFY((y_gpu - y_cpu).norm() / (y_cpu.norm() + RealScalar(1)) < tol); | |
| } | |
| // ---- Expression syntax: d_y = d_A * d_x ------------------------------------ | |
| template <typename Scalar> | |
| void test_spmv_expr(Index n) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat A = make_sparse<Scalar>(n, n); | |
| Vec x = Vec::Random(n); | |
| gpu::Context gpu_ctx; | |
| gpu::SparseContext<Scalar> ctx(gpu_ctx); | |
| // Upload sparse matrix and create device view. | |
| auto d_A = ctx.deviceView(A); | |
| // Upload x. | |
| auto d_x = gpu::DeviceMatrix<Scalar>::fromHost(x, gpu_ctx.stream()); | |
| // Expression syntax: d_y = d_A * d_x | |
| gpu::DeviceMatrix<Scalar> d_y; | |
| d_y = d_A * d_x; | |
| // Also test with noalias(): | |
| gpu::DeviceMatrix<Scalar> d_tmp; | |
| d_tmp.noalias() = d_A * d_x; | |
| Vec y_gpu = d_y.toHost(gpu_ctx.stream()); | |
| Vec tmp_gpu = d_tmp.toHost(gpu_ctx.stream()); | |
| Vec y_cpu = A * x; | |
| RealScalar tol = RealScalar(10) * RealScalar(n) * NumTraits<Scalar>::epsilon(); | |
| VERIFY((y_gpu - y_cpu).norm() / (y_cpu.norm() + RealScalar(1)) < tol); | |
| VERIFY((tmp_gpu - y_cpu).norm() / (y_cpu.norm() + RealScalar(1)) < tol); | |
| } | |
| // ---- Expression syntax with a dense addend: d_y = d_b ± d_A * d_x ----------- | |
| template <typename Scalar> | |
| void test_spmv_affine_expr(Index n) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat A = make_sparse<Scalar>(n, n); | |
| Vec x = Vec::Random(n), b = Vec::Random(n); | |
| const Vec Ax = A * x; | |
| // The addend copy runs on the thread-local Context and the SpMV on the SparseContext's | |
| // stream. A second Context makes those two different streams; the readiness events | |
| // must order the copy before the product. | |
| gpu::Context gpu_ctx, copy_ctx; | |
| gpu::Context::setThreadLocal(©_ctx); | |
| gpu::SparseContext<Scalar> ctx(gpu_ctx); | |
| auto d_A = ctx.deviceView(A); | |
| auto d_x = gpu::DeviceMatrix<Scalar>::fromHost(x, gpu_ctx.stream()); | |
| auto d_b = gpu::DeviceMatrix<Scalar>::fromHost(b, gpu_ctx.stream()); | |
| const RealScalar tol = RealScalar(10) * RealScalar(n) * NumTraits<Scalar>::epsilon(); | |
| auto check = [&](const gpu::DeviceMatrix<Scalar>& d_y, const Vec& y_ref) { | |
| Vec y = d_y.toHost(gpu_ctx.stream()); | |
| VERIFY((y - y_ref).norm() / (y_ref.norm() + RealScalar(1)) < tol); | |
| }; | |
| gpu::DeviceMatrix<Scalar> d_y = d_b - d_A * d_x; // copy-initialization | |
| check(d_y, b - Ax); | |
| d_y = d_b + d_A * d_x; | |
| check(d_y, b + Ax); | |
| d_y = d_A * d_x + d_b; | |
| check(d_y, Ax + b); | |
| d_y.noalias() = d_A * d_x - d_b; | |
| check(d_y, Ax - b); | |
| // The addend is the destination: beta accumulates in place, without a copy. | |
| gpu::DeviceMatrix<Scalar> d_r = d_b; | |
| d_r = d_r - d_A * d_x; | |
| check(d_r, b - Ax); | |
| gpu::Context::setThreadLocal(nullptr); | |
| } | |
| template <typename Scalar> | |
| void test_device_spmm_affine(Index n, Index nrhs) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat A = make_sparse<Scalar>(n, n); | |
| Mat X = Mat::Random(n, nrhs), B = Mat::Random(n, nrhs); | |
| gpu::Context gctx; | |
| gpu::Context::setThreadLocal(&gctx); | |
| gpu::SparseContext<Scalar> ctx(gctx); | |
| auto view = ctx.deviceView(A); | |
| auto d_X = gpu::DeviceMatrix<Scalar>::fromHost(X, gctx.stream()); | |
| auto d_B = gpu::DeviceMatrix<Scalar>::fromHost(B, gctx.stream()); | |
| gpu::DeviceMatrix<Scalar> d_Y = d_B - view * d_X; // nrhs > 1 -> SpMM with beta = 1 | |
| gpu::Context::setThreadLocal(nullptr); | |
| Mat Y_ref = B - A * X; | |
| RealScalar tol = RealScalar(10) * RealScalar(n) * NumTraits<Scalar>::epsilon(); | |
| VERIFY((d_Y.toHost() - Y_ref).norm() / (Y_ref.norm() + RealScalar(1)) < tol); | |
| } | |
| // A matrix without stored entries reduces d_b ± d_A * d_x to ±d_b. | |
| template <typename Scalar> | |
| void test_affine_empty(Index n) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| SpMat A(n, n); | |
| A.makeCompressed(); | |
| Vec x = Vec::Random(n), b = Vec::Random(n); | |
| gpu::Context gctx; | |
| gpu::Context::setThreadLocal(&gctx); | |
| gpu::SparseContext<Scalar> ctx(gctx); | |
| auto d_A = ctx.deviceView(A); | |
| VERIFY_IS_EQUAL(d_A.nonZeros(), 0); | |
| auto d_x = gpu::DeviceMatrix<Scalar>::fromHost(x, gctx.stream()); | |
| auto d_b = gpu::DeviceMatrix<Scalar>::fromHost(b, gctx.stream()); | |
| gpu::DeviceMatrix<Scalar> d_y = d_b - d_A * d_x; | |
| VERIFY(d_y.toHost(gctx.stream()) == b); | |
| d_y = d_A * d_x - d_b; | |
| VERIFY(d_y.toHost(gctx.stream()) == -b); | |
| gpu::Context::setThreadLocal(nullptr); | |
| } | |
| // ---- deviceView overwrite: second view replaces first ----------------------- | |
| template <typename Scalar> | |
| void test_deviceview_overwrite(Index n) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat A1 = make_sparse<Scalar>(n, n); | |
| SpMat A2 = make_sparse<Scalar>(n, n); // different random matrix | |
| Vec x = Vec::Random(n); | |
| gpu::Context gpu_ctx; | |
| gpu::SparseContext<Scalar> ctx(gpu_ctx); | |
| // First view: A1. | |
| auto d_A1 = ctx.deviceView(A1); | |
| auto d_x = gpu::DeviceMatrix<Scalar>::fromHost(x, gpu_ctx.stream()); | |
| gpu::DeviceMatrix<Scalar> d_y1; | |
| d_y1 = d_A1 * d_x; | |
| Vec y1_gpu = d_y1.toHost(gpu_ctx.stream()); | |
| Vec y1_cpu = A1 * x; | |
| RealScalar tol = RealScalar(10) * RealScalar(n) * NumTraits<Scalar>::epsilon(); | |
| VERIFY((y1_gpu - y1_cpu).norm() / (y1_cpu.norm() + RealScalar(1)) < tol); | |
| // Second view overwrites first: now uses A2. | |
| auto d_A2 = ctx.deviceView(A2); | |
| gpu::DeviceMatrix<Scalar> d_y2; | |
| d_y2 = d_A2 * d_x; | |
| Vec y2_gpu = d_y2.toHost(gpu_ctx.stream()); | |
| Vec y2_cpu = A2 * x; | |
| VERIFY((y2_gpu - y2_cpu).norm() / (y2_cpu.norm() + RealScalar(1)) < tol); | |
| } | |
| // ---- Device-resident SpMM + GpuOp device multiply ----------------------------- | |
| template <typename Scalar> | |
| void test_device_spmm(Index n, Index nrhs) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat A = make_sparse<Scalar>(n, n); | |
| Mat X = Mat::Random(n, nrhs); | |
| gpu::Context gctx; | |
| gpu::SparseContext<Scalar> ctx(gctx); | |
| auto view = ctx.deviceView(A); | |
| VERIFY(view.generation() == ctx.uploadGeneration()); | |
| auto d_X = gpu::DeviceMatrix<Scalar>::fromHost(X, gctx.stream()); | |
| gpu::DeviceMatrix<Scalar> d_Y = view * d_X; // nrhs > 1 -> SpMM | |
| Mat Y_ref = A * X; | |
| RealScalar tol = RealScalar(10) * RealScalar(n) * NumTraits<Scalar>::epsilon(); | |
| VERIFY((d_Y.toHost() - Y_ref).norm() / (Y_ref.norm() + RealScalar(1)) < tol); | |
| } | |
| template <typename Scalar> | |
| void test_device_multiply_gpuop(Index n) { | |
| using SpMat = SparseMatrix<Scalar, ColMajor, int>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| SpMat A = make_sparse<Scalar>(n, n); | |
| Vec x = Vec::Random(n); | |
| gpu::Context gctx; | |
| gpu::SparseContext<Scalar> ctx(gctx); | |
| auto d_x = gpu::DeviceMatrix<Scalar>::fromHost(x, gctx.stream()); | |
| gpu::DeviceMatrix<Scalar> d_y; | |
| ctx.multiply(A, d_x, d_y, Scalar(1), Scalar(0), gpu::GpuOp::Trans); | |
| Vec y_ref = A.transpose() * x; | |
| RealScalar tol = RealScalar(10) * RealScalar(n) * NumTraits<Scalar>::epsilon(); | |
| VERIFY((d_y.toHost() - y_ref).norm() / (y_ref.norm() + RealScalar(1)) < tol); | |
| } | |
| // ---- BlockSparseMatrix input ------------------------------------------------- | |
| // Regression coverage for the CSC fallback: a ColMajor 1 x 1 or rectangular | |
| // BlockSparseMatrix reached deviceView() through the implicit SparseMatrix | |
| // conversion before the BSR overloads existed and must keep working on every | |
| // toolkit. Square blocks of size >= 2 take BSR where EIGEN_HAS_CUSPARSE_BSR is 1. | |
| template <typename Scalar, int Options, int BlockRows, int BlockCols> | |
| void test_block_sparse_input(Index block_rows, Index block_cols) { | |
| using Bsm = BlockSparseMatrix<Scalar, Options, BlockRows, BlockCols, int>; | |
| using Block = typename Bsm::BlockType; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| std::vector<typename Bsm::TripletType> triplets; | |
| for (Index bi = 0; bi < block_rows; ++bi) { | |
| for (Index bj = 0; bj < block_cols; ++bj) { | |
| if (internal::random<double>(0.0, 1.0) < 0.4) triplets.emplace_back(int(bi), int(bj), Block::Random()); | |
| } | |
| } | |
| Bsm A(block_rows, block_cols); | |
| A.setFromTriplets(triplets.begin(), triplets.end()); | |
| const Vec x = Vec::Random(A.cols()); | |
| const Vec xt = Vec::Random(A.rows()); | |
| const Mat X = Mat::Random(A.cols(), 3); | |
| const RealScalar tol = RealScalar(10) * RealScalar((std::max)(A.rows(), A.cols())) * NumTraits<Scalar>::epsilon(); | |
| auto verify_close = [tol](const auto& result, const auto& ref) { | |
| VERIFY((result - ref).norm() / (ref.norm() + RealScalar(1)) < tol); | |
| }; | |
| gpu::Context gctx; | |
| gpu::SparseContext<Scalar> ctx(gctx); | |
| const Vec y_ref = A * x; | |
| verify_close(ctx.multiply(A, x), y_ref); | |
| const Vec yt_ref = A.transpose() * xt; | |
| verify_close(ctx.multiplyT(A, xt), yt_ref); | |
| Vec y_acc = Vec::Random(A.cols()); | |
| const Vec y_acc_ref = Scalar(2) * yt_ref + Scalar(3) * y_acc; | |
| ctx.multiply(A, xt, y_acc, Scalar(2), Scalar(3), gpu::GpuOp::Trans); | |
| verify_close(y_acc, y_acc_ref); | |
| // As for test_spmv_adjoint below: shapes on the CSC path cannot form A^H * x | |
| // for complex scalars on cuSPARSE < 12, where SparseContext asserts. | |
| #if !defined(CUSPARSE_VERSION) || CUSPARSE_VERSION >= 12000 | |
| constexpr bool kTestAdjoint = true; | |
| #else | |
| constexpr bool kTestAdjoint = !NumTraits<Scalar>::IsComplex; | |
| #endif | |
| if (kTestAdjoint) verify_close(ctx.multiplyAdjoint(A, xt), Vec(A.adjoint() * xt)); | |
| verify_close(ctx.multiplyMat(A, X), Mat(A * X)); | |
| auto d_x = gpu::DeviceMatrix<Scalar>::fromHost(x, gctx.stream()); | |
| gpu::DeviceMatrix<Scalar> d_y; | |
| ctx.multiply(A, d_x, d_y); | |
| verify_close(d_y.toHost(gctx.stream()), y_ref); | |
| auto view = ctx.deviceView(A); | |
| VERIFY_IS_EQUAL(view.rows(), A.rows()); | |
| VERIFY_IS_EQUAL(view.cols(), A.cols()); | |
| gpu::DeviceMatrix<Scalar> d_y2 = view * d_x; | |
| verify_close(d_y2.toHost(gctx.stream()), y_ref); | |
| } | |
| template <typename Scalar> | |
| void test_block_sparse_inputs() { | |
| CALL_SUBTEST((test_block_sparse_input<Scalar, ColMajor, 1, 1>(5, 4))); | |
| CALL_SUBTEST((test_block_sparse_input<Scalar, RowMajor, 1, 1>(4, 5))); | |
| CALL_SUBTEST((test_block_sparse_input<Scalar, ColMajor, 2, 3>(4, 3))); | |
| CALL_SUBTEST((test_block_sparse_input<Scalar, RowMajor, 2, 3>(3, 4))); | |
| CALL_SUBTEST((test_block_sparse_input<Scalar, ColMajor, 3, 3>(4, 4))); | |
| CALL_SUBTEST((test_block_sparse_input<Scalar, RowMajor, 3, 3>(4, 4))); | |
| } | |
| // ---- Per-scalar driver ------------------------------------------------------ | |
| template <typename Scalar> | |
| void test_scalar() { | |
| CALL_SUBTEST(test_block_sparse_inputs<Scalar>()); | |
| CALL_SUBTEST(test_device_spmm<Scalar>(64, 5)); | |
| CALL_SUBTEST(test_device_multiply_gpuop<Scalar>(64)); | |
| CALL_SUBTEST(test_spmv<Scalar>(64, 64)); | |
| CALL_SUBTEST(test_spmv<Scalar>(128, 64)); // non-square | |
| CALL_SUBTEST(test_spmv<Scalar>(64, 128)); // wide | |
| CALL_SUBTEST(test_spmv_alpha_beta<Scalar>(64)); | |
| CALL_SUBTEST(test_spmv_strided_dest<Scalar>(64)); | |
| CALL_SUBTEST(test_spmv_transpose<Scalar>(128, 64)); | |
| // cuSPARSE < 12 cannot represent A^H * x for complex scalars with the | |
| // CSR-of-A^T trick used by SparseContext; SparseContext asserts in that | |
| // case. Real-scalar ConjTrans is demoted to Trans and works fine. | |
| #if !defined(CUSPARSE_VERSION) || CUSPARSE_VERSION >= 12000 | |
| CALL_SUBTEST(test_spmv_adjoint<Scalar>(128, 64)); | |
| #else | |
| if (!NumTraits<Scalar>::IsComplex) CALL_SUBTEST(test_spmv_adjoint<Scalar>(128, 64)); | |
| #endif | |
| CALL_SUBTEST(test_spmm<Scalar>(64, 64, 4)); | |
| CALL_SUBTEST(test_spmm_transpose<Scalar>(128, 64, 4)); | |
| CALL_SUBTEST(test_pattern_replacement<Scalar>(64)); | |
| CALL_SUBTEST(test_reuse<Scalar>(64)); | |
| CALL_SUBTEST(test_empty<Scalar>()); | |
| CALL_SUBTEST(test_spmv_device<Scalar>(64)); | |
| CALL_SUBTEST(test_spmv_expr<Scalar>(64)); | |
| CALL_SUBTEST(test_spmv_affine_expr<Scalar>(64)); | |
| CALL_SUBTEST(test_device_spmm_affine<Scalar>(64, 5)); | |
| CALL_SUBTEST(test_affine_empty<Scalar>(16)); | |
| CALL_SUBTEST(test_deviceview_overwrite<Scalar>(64)); | |
| } | |
| EIGEN_DECLARE_TEST(gpu_cusparse_spmv) { | |
| gpu_test::require_cusparse_context(); | |
| // Split by scalar so each part compiles in parallel. | |
| CALL_SUBTEST_1(test_scalar<float>()); | |
| CALL_SUBTEST_2(test_scalar<double>()); | |
| CALL_SUBTEST_3(test_scalar<std::complex<float>>()); | |
| CALL_SUBTEST_4(test_scalar<std::complex<double>>()); | |
| } |