• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IntelPython / dpnp / 28712245257

04 Jul 2026 04:20PM UTC coverage: 77.302% (-0.8%) from 78.107%
28712245257

Pull #2841

github

web-flow
Merge d8e29409e into 95d316374
Pull Request #2841: Feature sparse linalg solvers

1539 of 2916 branches covered (52.78%)

Branch coverage included in aggregate %.

664 of 1170 new or added lines in 11 files covered. (56.75%)

32 existing lines in 1 file now uncovered.

26646 of 33545 relevant lines covered (79.43%)

8327.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/dpnp/backend/extensions/sparse/gemv.cpp
1
//*****************************************************************************
2
// Copyright (c) 2026, Intel Corporation
3
// All rights reserved.
4
//
5
// Redistribution and use in source and binary forms, with or without
6
// modification, are permitted provided that the following conditions are met:
7
// - Redistributions of source code must retain the above copyright notice,
8
//   this list of conditions and the following disclaimer.
9
// - Redistributions in binary form must reproduce the above copyright notice,
10
//   this list of conditions and the following disclaimer in the documentation
11
//   and/or other materials provided with the distribution.
12
// - Neither the name of the copyright holder nor the names of its contributors
13
//   may be used to endorse or promote products derived from this software
14
//   without specific prior written permission.
15
//
16
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
// POSSIBILITY OF SUCH DAMAGE.
27
//*****************************************************************************
28

29
#include <cstdint>
30
#include <stdexcept>
31
#include <string>
32
#include <tuple>
33
#include <utility>
34
#include <vector>
35

36
#include <pybind11/pybind11.h>
37

38
// utils extension header
39
#include "ext/common.hpp"
40

41
// dpnp tensor headers
42
#include "utils/memory_overlap.hpp"
43
#include "utils/output_validation.hpp"
44
#include "utils/type_utils.hpp"
45

46
#include "gemv.hpp"
47
#include "types_matrix.hpp"
48

49
namespace dpnp::extensions::sparse
50
{
51

52
#if defined(USE_ONEMATH)
53
namespace mkl = oneapi::math;
54
namespace mkl_sparse = oneapi::math::sparse;
55
#else
56
namespace mkl = oneapi::mkl;
57
namespace mkl_sparse = oneapi::mkl::sparse;
58
#endif
59

60
namespace py = pybind11;
61
namespace type_utils = dpnp::tensor::type_utils;
62

63
using ext::common::init_dispatch_table;
64

65
// ---------------------------------------------------------------------------
66
// Dispatch table types
67
// ---------------------------------------------------------------------------
68

69
/**
70
 * init_impl: builds the sparse matrix handle from the CSR arrays.
71
 * Returns (handle_ptr, event). All CSR arrays are *not* copied -- they
72
 * must stay alive until release.
73
 */
74
typedef std::pair<std::uintptr_t, sycl::event> (*gemv_init_fn_ptr_t)(
75
    sycl::queue &,
76
    mkl::transpose,
77
    const char *,       // row_ptr (typeless)
78
    const char *,       // col_ind (typeless)
79
    const char *,       // values  (typeless)
80
    const std::int64_t, // num_rows
81
    const std::int64_t, // num_cols
82
    const std::int64_t, // nnz
83
    const std::vector<sycl::event> &);
84

85
/**
86
 * compute_impl: fires a single sparse matrix-vector product using a
87
 * pre-built handle. Returns the kernel event directly -- no host_task
88
 * wrapping.
89
 */
90
typedef sycl::event (*gemv_compute_fn_ptr_t)(
91
    sycl::queue &,
92
    std::uintptr_t, // pre-built handle (matrix_handle_t or cache ptr)
93
    mkl::transpose,
94
    const double,       // alpha (cast to Tv inside)
95
    const char *,       // x (typeless)
96
    const double,       // beta  (cast to Tv inside)
97
    char *,             // y (typeless, writable)
98
    const std::int64_t, // op_rows (length of y)
99
    const std::int64_t, // op_cols (length of x)
100
    const std::vector<sycl::event> &);
101

102
// Init dispatch: 2-D on (Tv, Ti).
103
static gemv_init_fn_ptr_t gemv_init_dispatch_table[dpnp_td_ns::num_types]
104
                                                  [dpnp_td_ns::num_types];
105

106
// Compute dispatch: 1-D on Tv. The index type is baked into the handle,
107
// so compute doesn't need it.
108
static gemv_compute_fn_ptr_t gemv_compute_dispatch_table[dpnp_td_ns::num_types];
109

110
#if defined(USE_ONEMATH)
111

112
// ---------------------------------------------------------------------------
113
// oneMath sparse API (v2) cache
114
// ---------------------------------------------------------------------------
115

116
/**
117
 * Owns the five oneMath objects an spmv needs (matrix handle, x / y
118
 * dense-vector handles, descriptor, workspace), so a single cached
119
 * handle can drive repeated matvecs. init returns its address as the
120
 * opaque uintptr_t handle; release frees the workspace and deletes it.
121
 * spmv_buffer_size + spmv_optimize run once on the first compute;
122
 * later calls only rebind the x / y data (see gemv_compute_impl).
123
 */
124
struct SpmvCache
125
{
126
    mkl_sparse::matrix_handle_t A = nullptr;
127
    mkl_sparse::dense_vector_handle_t x = nullptr;
128
    mkl_sparse::dense_vector_handle_t y = nullptr;
129
    mkl_sparse::spmv_descr_t descr = nullptr;
130
    void *workspace = nullptr;
131
    mkl_sparse::matrix_view view{};
132
    bool optimized = false;
133
};
134

135
// ---------------------------------------------------------------------------
136
// Per-type init implementation (oneMath)
137
// ---------------------------------------------------------------------------
138

139
template <typename Tv, typename Ti>
140
static std::pair<std::uintptr_t, sycl::event>
141
    gemv_init_impl(sycl::queue &exec_q,
142
                   mkl::transpose mkl_trans,
143
                   const char *row_ptr_data,
144
                   const char *col_ind_data,
145
                   const char *values_data,
146
                   std::int64_t num_rows,
147
                   std::int64_t num_cols,
148
                   std::int64_t nnz,
149
                   const std::vector<sycl::event> &depends)
150
{
151
    type_utils::validate_type_for_device<Tv>(exec_q);
152

153
    // init_csr_matrix has no dependency-list overload in the USM API;
154
    // the caller-supplied depends are honoured at the first compute
155
    // (spmv_optimize / spmv accept them).
156
    static_cast<void>(depends);
157

158
    Ti *row_ptr = const_cast<Ti *>(reinterpret_cast<const Ti *>(row_ptr_data));
159
    Ti *col_ind = const_cast<Ti *>(reinterpret_cast<const Ti *>(col_ind_data));
160
    Tv *values = const_cast<Tv *>(reinterpret_cast<const Tv *>(values_data));
161

162
    // op(A) is (num_rows x num_cols) for trans=N, transposed otherwise;
163
    // x has op_cols elements, y has op_rows.
164
    const bool is_non_trans = (mkl_trans == mkl::transpose::nontrans);
165
    const std::int64_t op_rows = is_non_trans ? num_rows : num_cols;
166
    const std::int64_t op_cols = is_non_trans ? num_cols : num_rows;
167

168
    auto *cache = new SpmvCache;
169

170
    // Release whatever was created before the failure, then drop the
171
    // cache, so a throwing init does not leak oneMath handles.
172
    auto cleanup_partial = [&]() {
173
        if (cache->descr != nullptr)
174
            mkl_sparse::release_spmv_descr(exec_q, cache->descr, {});
175
        if (cache->x != nullptr)
176
            mkl_sparse::release_dense_vector(exec_q, cache->x, {});
177
        if (cache->y != nullptr)
178
            mkl_sparse::release_dense_vector(exec_q, cache->y, {});
179
        if (cache->A != nullptr)
180
            mkl_sparse::release_sparse_matrix(exec_q, cache->A, {});
181
        delete cache;
182
    };
183

184
    try {
185
        mkl_sparse::init_csr_matrix(exec_q, &cache->A, num_rows, num_cols, nnz,
186
                                    mkl::index_base::zero, row_ptr, col_ind,
187
                                    values);
188

189
        // values is a placeholder pointer; the real x / y pointers are
190
        // bound on every compute call via set_dense_vector_data.
191
        mkl_sparse::init_dense_vector(exec_q, &cache->x, op_cols, values);
192
        mkl_sparse::init_dense_vector(exec_q, &cache->y, op_rows, values);
193

194
        mkl_sparse::init_spmv_descr(exec_q, &cache->descr);
195
    } catch (mkl::exception const &e) {
196
        cleanup_partial();
197
        throw std::runtime_error(
198
            std::string("sparse_gemv_init: oneMath exception in init: ") +
199
            e.what());
200
    } catch (sycl::exception const &e) {
201
        cleanup_partial();
202
        throw std::runtime_error(
203
            std::string("sparse_gemv_init: SYCL exception in init: ") +
204
            e.what());
205
    }
206

207
    auto handle_ptr = reinterpret_cast<std::uintptr_t>(cache);
208
    // No optimize event yet -- optimization is deferred to first compute.
209
    // Return a completed event so the caller's wait() is a no-op.
210
    return {handle_ptr, sycl::event{}};
211
}
212

213
#else // legacy oneMKL sparse API (v1)
214

215
// ---------------------------------------------------------------------------
216
// Per-type init implementation (oneMKL)
217
// ---------------------------------------------------------------------------
218

219
template <typename Tv, typename Ti>
220
static std::pair<std::uintptr_t, sycl::event>
221
    gemv_init_impl(sycl::queue &exec_q,
222
                   mkl::transpose mkl_trans,
223
                   const char *row_ptr_data,
224
                   const char *col_ind_data,
225
                   const char *values_data,
226
                   std::int64_t num_rows,
227
                   std::int64_t num_cols,
228
                   std::int64_t nnz,
229
                   const std::vector<sycl::event> &depends)
NEW
230
{
×
NEW
231
    type_utils::validate_type_for_device<Tv>(exec_q);
×
232

NEW
233
    const Ti *row_ptr = reinterpret_cast<const Ti *>(row_ptr_data);
×
NEW
234
    const Ti *col_ind = reinterpret_cast<const Ti *>(col_ind_data);
×
NEW
235
    const Tv *values = reinterpret_cast<const Tv *>(values_data);
×
236

NEW
237
    mkl_sparse::matrix_handle_t spmat = nullptr;
×
NEW
238
    mkl_sparse::init_matrix_handle(&spmat);
×
239

NEW
240
    auto ev_set = mkl_sparse::set_csr_data(
×
NEW
241
        exec_q, spmat, num_rows, num_cols, nnz, mkl::index_base::zero,
×
NEW
242
        const_cast<Ti *>(row_ptr), const_cast<Ti *>(col_ind),
×
NEW
243
        const_cast<Tv *>(values), depends);
×
244

NEW
245
    sycl::event ev_opt;
×
NEW
246
    try {
×
NEW
247
        ev_opt = mkl_sparse::optimize_gemv(exec_q, mkl_trans, spmat, {ev_set});
×
NEW
248
    } catch (mkl::exception const &e) {
×
NEW
249
        mkl_sparse::release_matrix_handle(exec_q, &spmat, {});
×
NEW
250
        throw std::runtime_error(
×
NEW
251
            std::string("sparse_gemv_init: MKL exception in optimize_gemv: ") +
×
NEW
252
            e.what());
×
NEW
253
    } catch (sycl::exception const &e) {
×
NEW
254
        mkl_sparse::release_matrix_handle(exec_q, &spmat, {});
×
NEW
255
        throw std::runtime_error(
×
NEW
256
            std::string("sparse_gemv_init: SYCL exception in optimize_gemv: ") +
×
NEW
257
            e.what());
×
NEW
258
    }
×
259

NEW
260
    auto handle_ptr = reinterpret_cast<std::uintptr_t>(spmat);
×
NEW
261
    return {handle_ptr, ev_opt};
×
NEW
262
}
×
263

264
#endif // USE_ONEMATH
265

266
// ---------------------------------------------------------------------------
267
// Per-type compute implementation
268
// ---------------------------------------------------------------------------
269

270
#if defined(USE_ONEMATH)
271

272
template <typename Tv>
273
static sycl::event gemv_compute_impl(sycl::queue &exec_q,
274
                                     std::uintptr_t handle_ptr,
275
                                     mkl::transpose mkl_trans,
276
                                     double alpha_d,
277
                                     const char *x_data,
278
                                     double beta_d,
279
                                     char *y_data,
280
                                     std::int64_t op_rows,
281
                                     std::int64_t op_cols,
282
                                     const std::vector<sycl::event> &depends)
283
{
284
    auto *cache = reinterpret_cast<SpmvCache *>(handle_ptr);
285

286
    // Complex Tv loses the imaginary part here; solvers use alpha=1,
287
    // beta=0 so this is exact for them.
288
    const Tv alpha = static_cast<Tv>(alpha_d);
289
    const Tv beta = static_cast<Tv>(beta_d);
290

291
    Tv *x = const_cast<Tv *>(reinterpret_cast<const Tv *>(x_data));
292
    Tv *y = reinterpret_cast<Tv *>(y_data);
293

294
    try {
295
        // The spec permits resetting x / y data (and alpha / beta)
296
        // before each spmv without re-optimizing, as long as the
297
        // handles passed to spmv match those passed to spmv_optimize.
298
        mkl_sparse::set_dense_vector_data(exec_q, cache->x, op_cols, x);
299
        mkl_sparse::set_dense_vector_data(exec_q, cache->y, op_rows, y);
300

301
        constexpr auto alg = mkl_sparse::spmv_alg::default_alg;
302

303
        if (!cache->optimized) {
304
            // spmv_buffer_size + spmv_optimize must each run at least
305
            // once before spmv; do so on the first matvec only.
306
            std::size_t workspace_bytes = 0;
307
            mkl_sparse::spmv_buffer_size(exec_q, mkl_trans, &alpha, cache->view,
308
                                         cache->A, cache->x, &beta, cache->y,
309
                                         alg, cache->descr, workspace_bytes);
310
            if (workspace_bytes > 0) {
311
                cache->workspace = sycl::malloc_device(workspace_bytes, exec_q);
312
                if (cache->workspace == nullptr)
313
                    throw std::runtime_error(
314
                        "sparse_gemv_compute: failed to allocate spmv "
315
                        "workspace.");
316
            }
317

318
            sycl::event ev_opt = mkl_sparse::spmv_optimize(
319
                exec_q, mkl_trans, &alpha, cache->view, cache->A, cache->x,
320
                &beta, cache->y, alg, cache->descr, cache->workspace, depends);
321
            cache->optimized = true;
322

323
            return mkl_sparse::spmv(exec_q, mkl_trans, &alpha, cache->view,
324
                                    cache->A, cache->x, &beta, cache->y, alg,
325
                                    cache->descr, {ev_opt});
326
        }
327

328
        return mkl_sparse::spmv(exec_q, mkl_trans, &alpha, cache->view,
329
                                cache->A, cache->x, &beta, cache->y, alg,
330
                                cache->descr, depends);
331
    } catch (mkl::exception const &e) {
332
        throw std::runtime_error(
333
            std::string("sparse_gemv_compute: oneMath exception: ") + e.what());
334
    } catch (sycl::exception const &e) {
335
        throw std::runtime_error(
336
            std::string("sparse_gemv_compute: SYCL exception: ") + e.what());
337
    }
338
}
339

340
#else // legacy oneMKL sparse API (v1)
341

342
template <typename Tv>
343
static sycl::event gemv_compute_impl(sycl::queue &exec_q,
344
                                     std::uintptr_t handle_ptr,
345
                                     mkl::transpose mkl_trans,
346
                                     double alpha_d,
347
                                     const char *x_data,
348
                                     double beta_d,
349
                                     char *y_data,
350
                                     std::int64_t op_rows,
351
                                     std::int64_t op_cols,
352
                                     const std::vector<sycl::event> &depends)
NEW
353
{
×
354
    // op_rows / op_cols are unused here (the handle encodes the
355
    // dimensions); kept for ABI parity with the oneMath path.
NEW
356
    static_cast<void>(op_rows);
×
NEW
357
    static_cast<void>(op_cols);
×
358

NEW
359
    auto spmat = reinterpret_cast<mkl_sparse::matrix_handle_t>(handle_ptr);
×
360

361
    // Complex Tv loses the imaginary part here; solvers use alpha=1,
362
    // beta=0 so this is exact for them.
NEW
363
    const Tv alpha = static_cast<Tv>(alpha_d);
×
NEW
364
    const Tv beta = static_cast<Tv>(beta_d);
×
365

NEW
366
    const Tv *x = reinterpret_cast<const Tv *>(x_data);
×
NEW
367
    Tv *y = reinterpret_cast<Tv *>(y_data);
×
368

NEW
369
    try {
×
NEW
370
        return mkl_sparse::gemv(exec_q, mkl_trans, alpha, spmat, x, beta, y,
×
NEW
371
                                depends);
×
NEW
372
    } catch (mkl::exception const &e) {
×
NEW
373
        throw std::runtime_error(
×
NEW
374
            std::string("sparse_gemv_compute: MKL exception: ") + e.what());
×
NEW
375
    } catch (sycl::exception const &e) {
×
NEW
376
        throw std::runtime_error(
×
NEW
377
            std::string("sparse_gemv_compute: SYCL exception: ") + e.what());
×
NEW
378
    }
×
NEW
379
}
×
380

381
#endif // USE_ONEMATH
382

383
// ---------------------------------------------------------------------------
384
// Public entry points
385
// ---------------------------------------------------------------------------
386

387
static mkl::transpose decode_trans(const int trans)
NEW
388
{
×
NEW
389
    switch (trans) {
×
NEW
390
    case 0:
×
NEW
391
        return mkl::transpose::nontrans;
×
NEW
392
    case 1:
×
NEW
393
        return mkl::transpose::trans;
×
NEW
394
    case 2:
×
NEW
395
        return mkl::transpose::conjtrans;
×
NEW
396
    default:
×
NEW
397
        throw std::invalid_argument(
×
NEW
398
            "sparse_gemv: trans must be 0 (N), 1 (T), or 2 (C)");
×
NEW
399
    }
×
NEW
400
}
×
401

402
std::tuple<std::uintptr_t, int, sycl::event>
403
    sparse_gemv_init(sycl::queue &exec_q,
404
                     const int trans,
405
                     const dpnp::tensor::usm_ndarray &row_ptr,
406
                     const dpnp::tensor::usm_ndarray &col_ind,
407
                     const dpnp::tensor::usm_ndarray &values,
408
                     const std::int64_t num_rows,
409
                     const std::int64_t num_cols,
410
                     const std::int64_t nnz,
411
                     const std::vector<sycl::event> &depends)
NEW
412
{
×
NEW
413
    if (!dpctl::utils::queues_are_compatible(
×
NEW
414
            exec_q,
×
NEW
415
            {row_ptr.get_queue(), col_ind.get_queue(), values.get_queue()}))
×
NEW
416
        throw py::value_error(
×
NEW
417
            "sparse_gemv_init: USM allocations are not compatible with the "
×
NEW
418
            "execution queue.");
×
419

NEW
420
    if (row_ptr.get_ndim() != 1 || col_ind.get_ndim() != 1 ||
×
NEW
421
        values.get_ndim() != 1)
×
NEW
422
        throw py::value_error(
×
NEW
423
            "sparse_gemv_init: row_ptr, col_ind, values must all be 1-D.");
×
424

NEW
425
    if (row_ptr.get_shape(0) != num_rows + 1)
×
NEW
426
        throw py::value_error(
×
NEW
427
            "sparse_gemv_init: row_ptr length must equal num_rows + 1.");
×
428

NEW
429
    if (col_ind.get_shape(0) != nnz || values.get_shape(0) != nnz)
×
NEW
430
        throw py::value_error(
×
NEW
431
            "sparse_gemv_init: col_ind and values length must equal nnz.");
×
432

433
    // Index types of row_ptr and col_ind must match.
NEW
434
    if (row_ptr.get_typenum() != col_ind.get_typenum())
×
NEW
435
        throw py::value_error(
×
NEW
436
            "sparse_gemv_init: row_ptr and col_ind must have the same dtype.");
×
437

NEW
438
    auto mkl_trans = decode_trans(trans);
×
439

NEW
440
    auto array_types = dpnp_td_ns::usm_ndarray_types();
×
NEW
441
    const int val_id = array_types.typenum_to_lookup_id(values.get_typenum());
×
NEW
442
    const int idx_id = array_types.typenum_to_lookup_id(row_ptr.get_typenum());
×
443

NEW
444
    gemv_init_fn_ptr_t init_fn = gemv_init_dispatch_table[val_id][idx_id];
×
NEW
445
    if (init_fn == nullptr)
×
NEW
446
        throw py::value_error(
×
NEW
447
            "sparse_gemv_init: no implementation for the given value/index "
×
NEW
448
            "dtype combination. Supported: {float32,float64,complex64,"
×
NEW
449
            "complex128} x {int32,int64}.");
×
450

NEW
451
    auto [handle_ptr, ev_opt] =
×
NEW
452
        init_fn(exec_q, mkl_trans, row_ptr.get_data(), col_ind.get_data(),
×
NEW
453
                values.get_data(), num_rows, num_cols, nnz, depends);
×
454

NEW
455
    return {handle_ptr, val_id, ev_opt};
×
NEW
456
}
×
457

458
sycl::event sparse_gemv_compute(sycl::queue &exec_q,
459
                                const std::uintptr_t handle_ptr,
460
                                const int val_type_id,
461
                                const int trans,
462
                                const double alpha,
463
                                const dpnp::tensor::usm_ndarray &x,
464
                                const double beta,
465
                                const dpnp::tensor::usm_ndarray &y,
466
                                const std::int64_t num_rows,
467
                                const std::int64_t num_cols,
468
                                const std::vector<sycl::event> &depends)
NEW
469
{
×
NEW
470
    if (x.get_ndim() != 1)
×
NEW
471
        throw py::value_error("sparse_gemv_compute: x must be a 1-D array.");
×
NEW
472
    if (y.get_ndim() != 1)
×
NEW
473
        throw py::value_error("sparse_gemv_compute: y must be a 1-D array.");
×
474

NEW
475
    if (!dpctl::utils::queues_are_compatible(exec_q,
×
NEW
476
                                             {x.get_queue(), y.get_queue()}))
×
NEW
477
        throw py::value_error(
×
NEW
478
            "sparse_gemv_compute: USM allocations are not compatible with the "
×
NEW
479
            "execution queue.");
×
480

NEW
481
    auto const &overlap = dpnp::tensor::overlap::MemoryOverlap();
×
NEW
482
    if (overlap(x, y))
×
NEW
483
        throw py::value_error(
×
NEW
484
            "sparse_gemv_compute: x and y are overlapping memory segments.");
×
485

NEW
486
    dpnp::tensor::validation::CheckWritable::throw_if_not_writable(y);
×
487

488
    // Shape validation: op(A) is (num_rows, num_cols) for trans=N,
489
    // (num_cols, num_rows) for trans={T,C}.
NEW
490
    auto mkl_trans = decode_trans(trans);
×
NEW
491
    const bool is_non_trans = (mkl_trans == mkl::transpose::nontrans);
×
NEW
492
    const std::int64_t op_rows = is_non_trans ? num_rows : num_cols;
×
NEW
493
    const std::int64_t op_cols = is_non_trans ? num_cols : num_rows;
×
494

NEW
495
    if (x.get_shape(0) != op_cols)
×
NEW
496
        throw py::value_error(
×
NEW
497
            "sparse_gemv_compute: x length does not match operator columns.");
×
NEW
498
    if (y.get_shape(0) != op_rows)
×
NEW
499
        throw py::value_error(
×
NEW
500
            "sparse_gemv_compute: y length does not match operator rows.");
×
501

NEW
502
    dpnp::tensor::validation::AmpleMemory::throw_if_not_ample(
×
NEW
503
        y, static_cast<std::size_t>(op_rows));
×
504

NEW
505
    auto array_types = dpnp_td_ns::usm_ndarray_types();
×
NEW
506
    const int x_val_id = array_types.typenum_to_lookup_id(x.get_typenum());
×
NEW
507
    const int y_val_id = array_types.typenum_to_lookup_id(y.get_typenum());
×
508

NEW
509
    if (x_val_id != val_type_id || y_val_id != val_type_id)
×
NEW
510
        throw py::value_error(
×
NEW
511
            "sparse_gemv_compute: x and y dtype must match the value dtype "
×
NEW
512
            "of the sparse matrix used to build the handle.");
×
513

NEW
514
    if (val_type_id < 0 || val_type_id >= dpnp_td_ns::num_types)
×
NEW
515
        throw py::value_error("sparse_gemv_compute: val_type_id out of range.");
×
516

NEW
517
    gemv_compute_fn_ptr_t compute_fn = gemv_compute_dispatch_table[val_type_id];
×
518

NEW
519
    if (compute_fn == nullptr)
×
NEW
520
        throw py::value_error("sparse_gemv_compute: unsupported value dtype.");
×
521

NEW
522
    return compute_fn(exec_q, handle_ptr, mkl_trans, alpha, x.get_data(), beta,
×
NEW
523
                      const_cast<char *>(y.get_data()), op_rows, op_cols,
×
NEW
524
                      depends);
×
NEW
525
}
×
526

527
#if defined(USE_ONEMATH)
528

529
sycl::event sparse_gemv_release(sycl::queue &exec_q,
530
                                const std::uintptr_t handle_ptr,
531
                                const std::vector<sycl::event> &depends)
532
{
533
    auto *cache = reinterpret_cast<SpmvCache *>(handle_ptr);
534
    if (cache == nullptr)
535
        return sycl::event{};
536

537
    // Release every owned oneMath object; each takes `depends` so it
538
    // waits for pending compute before freeing.
539
    std::vector<sycl::event> rel_evs;
540
    rel_evs.push_back(
541
        mkl_sparse::release_spmv_descr(exec_q, cache->descr, depends));
542
    rel_evs.push_back(
543
        mkl_sparse::release_dense_vector(exec_q, cache->x, depends));
544
    rel_evs.push_back(
545
        mkl_sparse::release_dense_vector(exec_q, cache->y, depends));
546
    rel_evs.push_back(
547
        mkl_sparse::release_sparse_matrix(exec_q, cache->A, depends));
548

549
    // Free the USM workspace and delete the cache only after all
550
    // releases complete (the spec forbids freeing the workspace before
551
    // the spmv using it has finished). A host_task orders this without
552
    // blocking the caller.
553
    sycl::event cleanup_ev = exec_q.submit([&](sycl::handler &cgh) {
554
        cgh.depends_on(rel_evs);
555
        cgh.host_task([cache, exec_q]() {
556
            if (cache->workspace != nullptr)
557
                sycl::free(cache->workspace, exec_q);
558
            delete cache;
559
        });
560
    });
561

562
    return cleanup_ev;
563
}
564

565
#else // legacy oneMKL sparse API (v1)
566

567
sycl::event sparse_gemv_release(sycl::queue &exec_q,
568
                                const std::uintptr_t handle_ptr,
569
                                const std::vector<sycl::event> &depends)
NEW
570
{
×
NEW
571
    auto spmat = reinterpret_cast<mkl_sparse::matrix_handle_t>(handle_ptr);
×
572

573
    // release_matrix_handle takes `depends` so the handle is not freed
574
    // until pending compute on it completes.
NEW
575
    sycl::event release_ev =
×
NEW
576
        mkl_sparse::release_matrix_handle(exec_q, &spmat, depends);
×
577

NEW
578
    return release_ev;
×
NEW
579
}
×
580

581
#endif // USE_ONEMATH
582

583
// ---------------------------------------------------------------------------
584
// Dispatch table factories and registration
585
// ---------------------------------------------------------------------------
586

587
template <typename fnT, typename Tv, typename Ti>
588
struct GemvInitContigFactory
589
{
590
    fnT get()
NEW
591
    {
×
592
        if constexpr (types::SparseGemvInitTypePairSupportFactory<
593
                          Tv, Ti>::is_defined)
NEW
594
            return gemv_init_impl<Tv, Ti>;
×
595
        else
NEW
596
            return nullptr;
×
NEW
597
    }
×
598
};
599

600
template <typename fnT, typename Tv>
601
struct GemvComputeContigFactory
602
{
603
    fnT get()
NEW
604
    {
×
605
        if constexpr (types::SparseGemvComputeTypeSupportFactory<
606
                          Tv>::is_defined)
NEW
607
            return gemv_compute_impl<Tv>;
×
608
        else
NEW
609
            return nullptr;
×
NEW
610
    }
×
611
};
612

613
void init_sparse_gemv_dispatch_tables(void)
NEW
614
{
×
615
    // 2-D table on (Tv, Ti) for init.
NEW
616
    init_dispatch_table<gemv_init_fn_ptr_t, GemvInitContigFactory>(
×
NEW
617
        gemv_init_dispatch_table);
×
618

619
    // 1-D table on Tv for compute. dpctl's type dispatch headers expose
620
    // DispatchVectorBuilder as the 1-D analogue of DispatchTableBuilder.
NEW
621
    dpnp_td_ns::DispatchVectorBuilder<
×
NEW
622
        gemv_compute_fn_ptr_t, GemvComputeContigFactory, dpnp_td_ns::num_types>
×
NEW
623
        builder;
×
NEW
624
    builder.populate_dispatch_vector(gemv_compute_dispatch_table);
×
NEW
625
}
×
626

627
} // namespace dpnp::extensions::sparse
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc