DPNP C++ backend kernel library 0.20.0dev6
Data Parallel Extension for NumPy*
Loading...
Searching...
No Matches
dpnp4pybind11.hpp
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
26// THE POSSIBILITY OF SUCH DAMAGE.
27//*****************************************************************************
28
29#pragma once
30
31// Include for dpctl_capi struct and casters
32#include "dpctl4pybind11.hpp"
33
34// Include generated Cython headers for usm_ndarray
35// (struct definition and constants only)
36#include "dpnp/tensor/_usmarray.h"
37#include "dpnp/tensor/_usmarray_api.h"
38
39#include <array>
40#include <cassert>
41#include <cstddef> // for std::size_t for C++ linkage
42#include <cstdint>
43#include <memory>
44#include <stdexcept>
45#include <utility>
46#include <vector>
47
48#include <pybind11/pybind11.h>
49
50#include <sycl/sycl.hpp>
51
52namespace py = pybind11;
53
54namespace dpnp
55{
56namespace detail
57{
58// Lookup a type according to its size, and return a value corresponding to the
59// NumPy typenum.
60
61template <typename Concrete>
62constexpr int platform_typeid_lookup()
63{
64 return -1;
65}
66
67template <typename Concrete, typename T, typename... Ts, typename... Ints>
68constexpr int platform_typeid_lookup(int I, Ints... Is)
69{
70 return sizeof(Concrete) == sizeof(T)
71 ? I
72 : platform_typeid_lookup<Concrete, Ts...>(Is...);
73}
74
76{
77public:
78 PyTypeObject *PyUSMArrayType_;
79
80 int USM_ARRAY_C_CONTIGUOUS_;
81 int USM_ARRAY_F_CONTIGUOUS_;
82 int USM_ARRAY_WRITABLE_;
83 int UAR_BOOL_, UAR_BYTE_, UAR_UBYTE_, UAR_SHORT_, UAR_USHORT_, UAR_INT_,
84 UAR_UINT_, UAR_LONG_, UAR_ULONG_, UAR_LONGLONG_, UAR_ULONGLONG_,
85 UAR_FLOAT_, UAR_DOUBLE_, UAR_CFLOAT_, UAR_CDOUBLE_, UAR_TYPE_SENTINEL_,
86 UAR_HALF_;
87 int UAR_INT8_, UAR_UINT8_, UAR_INT16_, UAR_UINT16_, UAR_INT32_, UAR_UINT32_,
88 UAR_INT64_, UAR_UINT64_;
89
90 ~dpnp_capi() { default_usm_ndarray_.reset(); };
91
92 static auto &get()
93 {
94 static dpnp_capi api{};
95 return api;
96 }
97
98 py::object default_usm_ndarray_pyobj() { return *default_usm_ndarray_; }
99
100private:
101 struct Deleter
102 {
103 void operator()(py::object *p) const
104 {
105 const bool initialized = Py_IsInitialized();
106#if PY_VERSION_HEX < 0x30d0000
107 const bool finalizing = _Py_IsFinalizing();
108#else
109 const bool finalizing = Py_IsFinalizing();
110#endif
111 const bool guard = initialized && !finalizing;
112
113 if (guard) {
114 delete p;
115 }
116 }
117 };
118
119 std::shared_ptr<py::object> default_usm_ndarray_;
120
121 dpnp_capi()
122 : PyUSMArrayType_(nullptr), USM_ARRAY_C_CONTIGUOUS_(0),
123 USM_ARRAY_F_CONTIGUOUS_(0), USM_ARRAY_WRITABLE_(0), UAR_BOOL_(-1),
124 UAR_BYTE_(-1), UAR_UBYTE_(-1), UAR_SHORT_(-1), UAR_USHORT_(-1),
125 UAR_INT_(-1), UAR_UINT_(-1), UAR_LONG_(-1), UAR_ULONG_(-1),
126 UAR_LONGLONG_(-1), UAR_ULONGLONG_(-1), UAR_FLOAT_(-1),
127 UAR_DOUBLE_(-1), UAR_CFLOAT_(-1), UAR_CDOUBLE_(-1),
128 UAR_TYPE_SENTINEL_(-1), UAR_HALF_(-1), UAR_INT8_(-1), UAR_UINT8_(-1),
129 UAR_INT16_(-1), UAR_UINT16_(-1), UAR_INT32_(-1), UAR_UINT32_(-1),
130 UAR_INT64_(-1), UAR_UINT64_(-1), default_usm_ndarray_{}
131
132 {
133 // Import dpnp tensor module for PyUSMArrayType
134 import_dpnp__tensor___usmarray();
135
136 this->PyUSMArrayType_ = &PyUSMArrayType;
137
138 // constants
139 this->USM_ARRAY_C_CONTIGUOUS_ = USM_ARRAY_C_CONTIGUOUS;
140 this->USM_ARRAY_F_CONTIGUOUS_ = USM_ARRAY_F_CONTIGUOUS;
141 this->USM_ARRAY_WRITABLE_ = USM_ARRAY_WRITABLE;
142 this->UAR_BOOL_ = UAR_BOOL;
143 this->UAR_BYTE_ = UAR_BYTE;
144 this->UAR_UBYTE_ = UAR_UBYTE;
145 this->UAR_SHORT_ = UAR_SHORT;
146 this->UAR_USHORT_ = UAR_USHORT;
147 this->UAR_INT_ = UAR_INT;
148 this->UAR_UINT_ = UAR_UINT;
149 this->UAR_LONG_ = UAR_LONG;
150 this->UAR_ULONG_ = UAR_ULONG;
151 this->UAR_LONGLONG_ = UAR_LONGLONG;
152 this->UAR_ULONGLONG_ = UAR_ULONGLONG;
153 this->UAR_FLOAT_ = UAR_FLOAT;
154 this->UAR_DOUBLE_ = UAR_DOUBLE;
155 this->UAR_CFLOAT_ = UAR_CFLOAT;
156 this->UAR_CDOUBLE_ = UAR_CDOUBLE;
157 this->UAR_TYPE_SENTINEL_ = UAR_TYPE_SENTINEL;
158 this->UAR_HALF_ = UAR_HALF;
159
160 // deduced disjoint types
161 this->UAR_INT8_ = UAR_BYTE;
162 this->UAR_UINT8_ = UAR_UBYTE;
163 this->UAR_INT16_ = UAR_SHORT;
164 this->UAR_UINT16_ = UAR_USHORT;
165 this->UAR_INT32_ =
166 platform_typeid_lookup<std::int32_t, long, int, short>(
167 UAR_LONG, UAR_INT, UAR_SHORT);
168 this->UAR_UINT32_ =
169 platform_typeid_lookup<std::uint32_t, unsigned long, unsigned int,
170 unsigned short>(UAR_ULONG, UAR_UINT,
171 UAR_USHORT);
172 this->UAR_INT64_ =
173 platform_typeid_lookup<std::int64_t, long, long long, int>(
174 UAR_LONG, UAR_LONGLONG, UAR_INT);
175 this->UAR_UINT64_ =
176 platform_typeid_lookup<std::uint64_t, unsigned long,
177 unsigned long long, unsigned int>(
178 UAR_ULONG, UAR_ULONGLONG, UAR_UINT);
179
180 py::object py_default_usm_memory =
181 ::dpctl::detail::dpctl_capi::get().default_usm_memory_pyobj();
182
183 py::module_ mod_usmarray = py::module_::import("dpnp.tensor._usmarray");
184 auto tensor_kl = mod_usmarray.attr("usm_ndarray");
185
186 const py::object &py_default_usm_ndarray =
187 tensor_kl(py::tuple(), py::arg("dtype") = py::str("u1"),
188 py::arg("buffer") = py_default_usm_memory);
189
190 default_usm_ndarray_ = std::shared_ptr<py::object>(
191 new py::object{py_default_usm_ndarray}, Deleter{});
192 }
193
194 dpnp_capi(dpnp_capi const &) = default;
195 dpnp_capi &operator=(dpnp_capi const &) = default;
196 dpnp_capi &operator=(dpnp_capi &&) = default;
197
198}; // struct dpnp_capi
199} // namespace detail
200
201namespace tensor
202{
203inline std::vector<py::ssize_t>
204 c_contiguous_strides(int nd,
205 const py::ssize_t *shape,
206 py::ssize_t element_size = 1)
207{
208 if (nd > 0) {
209 std::vector<py::ssize_t> c_strides(nd, element_size);
210 for (int ic = nd - 1; ic > 0;) {
211 py::ssize_t next_v = c_strides[ic] * shape[ic];
212 c_strides[--ic] = next_v;
213 }
214 return c_strides;
215 }
216 else {
217 return std::vector<py::ssize_t>();
218 }
219}
220
221inline std::vector<py::ssize_t>
222 f_contiguous_strides(int nd,
223 const py::ssize_t *shape,
224 py::ssize_t element_size = 1)
225{
226 if (nd > 0) {
227 std::vector<py::ssize_t> f_strides(nd, element_size);
228 for (int i = 0; i < nd - 1;) {
229 py::ssize_t next_v = f_strides[i] * shape[i];
230 f_strides[++i] = next_v;
231 }
232 return f_strides;
233 }
234 else {
235 return std::vector<py::ssize_t>();
236 }
237}
238
239inline std::vector<py::ssize_t>
240 c_contiguous_strides(const std::vector<py::ssize_t> &shape,
241 py::ssize_t element_size = 1)
242{
243 return c_contiguous_strides(shape.size(), shape.data(), element_size);
244}
245
246inline std::vector<py::ssize_t>
247 f_contiguous_strides(const std::vector<py::ssize_t> &shape,
248 py::ssize_t element_size = 1)
249{
250 return f_contiguous_strides(shape.size(), shape.data(), element_size);
251}
252
253class usm_ndarray : public py::object
254{
255public:
256 PYBIND11_OBJECT(usm_ndarray, py::object, [](PyObject *o) -> bool {
257 return PyObject_TypeCheck(
258 o, detail::dpnp_capi::get().PyUSMArrayType_) != 0;
259 })
260
262 : py::object(detail::dpnp_capi::get().default_usm_ndarray_pyobj(),
263 borrowed_t{})
264 {
265 if (!m_ptr)
266 throw py::error_already_set();
267 }
268
269 char *get_data() const
270 {
271 PyUSMArrayObject *raw_ar = usm_array_ptr();
272 return raw_ar->data_;
273 }
274
275 template <typename T>
276 T *get_data() const
277 {
278 return reinterpret_cast<T *>(get_data());
279 }
280
281 int get_ndim() const
282 {
283 PyUSMArrayObject *raw_ar = usm_array_ptr();
284 return raw_ar->nd_;
285 }
286
287 const py::ssize_t *get_shape_raw() const
288 {
289 PyUSMArrayObject *raw_ar = usm_array_ptr();
290 return raw_ar->shape_;
291 }
292
293 std::vector<py::ssize_t> get_shape_vector() const
294 {
295 auto raw_sh = get_shape_raw();
296 auto nd = get_ndim();
297
298 std::vector<py::ssize_t> shape_vector(raw_sh, raw_sh + nd);
299 return shape_vector;
300 }
301
302 py::ssize_t get_shape(int i) const
303 {
304 auto shape_ptr = get_shape_raw();
305 return shape_ptr[i];
306 }
307
308 const py::ssize_t *get_strides_raw() const
309 {
310 PyUSMArrayObject *raw_ar = usm_array_ptr();
311 return raw_ar->strides_;
312 }
313
314 std::vector<py::ssize_t> get_strides_vector() const
315 {
316 auto raw_st = get_strides_raw();
317 auto nd = get_ndim();
318
319 if (raw_st == nullptr) {
320 auto is_c_contig = is_c_contiguous();
321 auto is_f_contig = is_f_contiguous();
322 auto raw_sh = get_shape_raw();
323 if (is_c_contig) {
324 const auto &contig_strides = c_contiguous_strides(nd, raw_sh);
325 return contig_strides;
326 }
327 else if (is_f_contig) {
328 const auto &contig_strides = f_contiguous_strides(nd, raw_sh);
329 return contig_strides;
330 }
331 else {
332 throw std::runtime_error("Invalid array encountered when "
333 "building strides");
334 }
335 }
336 else {
337 std::vector<py::ssize_t> st_vec(raw_st, raw_st + nd);
338 return st_vec;
339 }
340 }
341
342 py::ssize_t get_size() const
343 {
344 PyUSMArrayObject *raw_ar = usm_array_ptr();
345
346 int ndim = raw_ar->nd_;
347 const py::ssize_t *shape = raw_ar->shape_;
348
349 py::ssize_t nelems = 1;
350 for (int i = 0; i < ndim; ++i) {
351 nelems *= shape[i];
352 }
353
354 assert(nelems >= 0);
355 return nelems;
356 }
357
358 std::pair<py::ssize_t, py::ssize_t> get_minmax_offsets() const
359 {
360 PyUSMArrayObject *raw_ar = usm_array_ptr();
361
362 int nd = raw_ar->nd_;
363 const py::ssize_t *shape = raw_ar->shape_;
364 const py::ssize_t *strides = raw_ar->strides_;
365
366 py::ssize_t offset_min = 0;
367 py::ssize_t offset_max = 0;
368 if (strides == nullptr) {
369 py::ssize_t stride(1);
370 for (int i = 0; i < nd; ++i) {
371 offset_max += stride * (shape[i] - 1);
372 stride *= shape[i];
373 }
374 }
375 else {
376 for (int i = 0; i < nd; ++i) {
377 py::ssize_t delta = strides[i] * (shape[i] - 1);
378 if (strides[i] > 0) {
379 offset_max += delta;
380 }
381 else {
382 offset_min += delta;
383 }
384 }
385 }
386 return std::make_pair(offset_min, offset_max);
387 }
388
389 sycl::queue get_queue() const
390 {
391 PyUSMArrayObject *raw_ar = usm_array_ptr();
392 Py_MemoryObject *mem_obj =
393 reinterpret_cast<Py_MemoryObject *>(raw_ar->base_);
394
395 auto const &dpctl_api = ::dpctl::detail::dpctl_capi::get();
396 DPCTLSyclQueueRef QRef = dpctl_api.Memory_GetQueueRef_(mem_obj);
397 return *(reinterpret_cast<sycl::queue *>(QRef));
398 }
399
400 sycl::device get_device() const
401 {
402 PyUSMArrayObject *raw_ar = usm_array_ptr();
403 Py_MemoryObject *mem_obj =
404 reinterpret_cast<Py_MemoryObject *>(raw_ar->base_);
405
406 auto const &dpctl_api = ::dpctl::detail::dpctl_capi::get();
407 DPCTLSyclQueueRef QRef = dpctl_api.Memory_GetQueueRef_(mem_obj);
408 return reinterpret_cast<sycl::queue *>(QRef)->get_device();
409 }
410
411 int get_typenum() const
412 {
413 PyUSMArrayObject *raw_ar = usm_array_ptr();
414 return raw_ar->typenum_;
415 }
416
417 int get_flags() const
418 {
419 PyUSMArrayObject *raw_ar = usm_array_ptr();
420 return raw_ar->flags_;
421 }
422
423 int get_elemsize() const
424 {
425 int typenum = get_typenum();
426 auto const &api = detail::dpnp_capi::get();
427
428 // Lookup table for element sizes based on typenum
429 if (typenum == api.UAR_BOOL_)
430 return 1;
431 if (typenum == api.UAR_BYTE_)
432 return 1;
433 if (typenum == api.UAR_UBYTE_)
434 return 1;
435 if (typenum == api.UAR_SHORT_)
436 return 2;
437 if (typenum == api.UAR_USHORT_)
438 return 2;
439 if (typenum == api.UAR_INT_)
440 return 4;
441 if (typenum == api.UAR_UINT_)
442 return 4;
443 if (typenum == api.UAR_LONG_)
444 return sizeof(long);
445 if (typenum == api.UAR_ULONG_)
446 return sizeof(unsigned long);
447 if (typenum == api.UAR_LONGLONG_)
448 return 8;
449 if (typenum == api.UAR_ULONGLONG_)
450 return 8;
451 if (typenum == api.UAR_FLOAT_)
452 return 4;
453 if (typenum == api.UAR_DOUBLE_)
454 return 8;
455 if (typenum == api.UAR_CFLOAT_)
456 return 8;
457 if (typenum == api.UAR_CDOUBLE_)
458 return 16;
459 if (typenum == api.UAR_HALF_)
460 return 2;
461
462 return 0; // Unknown type
463 }
464
465 bool is_c_contiguous() const
466 {
467 int flags = get_flags();
468 auto const &api = detail::dpnp_capi::get();
469 return static_cast<bool>(flags & api.USM_ARRAY_C_CONTIGUOUS_);
470 }
471
472 bool is_f_contiguous() const
473 {
474 int flags = get_flags();
475 auto const &api = detail::dpnp_capi::get();
476 return static_cast<bool>(flags & api.USM_ARRAY_F_CONTIGUOUS_);
477 }
478
479 bool is_writable() const
480 {
481 int flags = get_flags();
482 auto const &api = detail::dpnp_capi::get();
483 return static_cast<bool>(flags & api.USM_ARRAY_WRITABLE_);
484 }
485
487 py::object get_usm_data() const
488 {
489 PyUSMArrayObject *raw_ar = usm_array_ptr();
490 // base_ is the Memory object - return new reference
491 PyObject *usm_data = raw_ar->base_;
492 Py_XINCREF(usm_data);
493
494 // pass reference ownership to py::object
495 return py::reinterpret_steal<py::object>(usm_data);
496 }
497
498 bool is_managed_by_smart_ptr() const
499 {
500 PyUSMArrayObject *raw_ar = usm_array_ptr();
501 PyObject *usm_data = raw_ar->base_;
502
503 auto const &dpctl_api = ::dpctl::detail::dpctl_capi::get();
504 if (!PyObject_TypeCheck(usm_data, dpctl_api.Py_MemoryType_)) {
505 return false;
506 }
507
508 Py_MemoryObject *mem_obj =
509 reinterpret_cast<Py_MemoryObject *>(usm_data);
510 const void *opaque_ptr = dpctl_api.Memory_GetOpaquePointer_(mem_obj);
511
512 return bool(opaque_ptr);
513 }
514
515 const std::shared_ptr<void> &get_smart_ptr_owner() const
516 {
517 PyUSMArrayObject *raw_ar = usm_array_ptr();
518 PyObject *usm_data = raw_ar->base_;
519
520 auto const &dpctl_api = ::dpctl::detail::dpctl_capi::get();
521
522 if (!PyObject_TypeCheck(usm_data, dpctl_api.Py_MemoryType_)) {
523 throw std::runtime_error(
524 "usm_ndarray object does not have Memory object "
525 "managing lifetime of USM allocation");
526 }
527
528 Py_MemoryObject *mem_obj =
529 reinterpret_cast<Py_MemoryObject *>(usm_data);
530 void *opaque_ptr = dpctl_api.Memory_GetOpaquePointer_(mem_obj);
531
532 if (opaque_ptr) {
533 auto shptr_ptr =
534 reinterpret_cast<std::shared_ptr<void> *>(opaque_ptr);
535 return *shptr_ptr;
536 }
537 else {
538 throw std::runtime_error(
539 "Memory object underlying usm_ndarray does not have "
540 "smart pointer managing lifetime of USM allocation");
541 }
542 }
543
544private:
545 PyUSMArrayObject *usm_array_ptr() const
546 {
547 return reinterpret_cast<PyUSMArrayObject *>(m_ptr);
548 }
549};
550} // end namespace tensor
551
552namespace utils
553{
554namespace detail
555{
556// TODO: future version of dpctl will include a more general way of passing
557// shared_ptrs to keep_args_alive, so that future overload can be used here
558// instead of reimplementing keep_args_alive
559
561{
562 // TODO: do we need to check for memory here? Or can we assume only
563 // dpnp::tensor::usm_ndarray will be passed?
564 static bool is_usm_managed_by_shared_ptr(const py::object &h)
565 {
566
567 if (py::isinstance<::dpctl::memory::usm_memory>(h)) {
568 const auto &usm_memory_inst =
569 py::cast<::dpctl::memory::usm_memory>(h);
570 return usm_memory_inst.is_managed_by_smart_ptr();
571 }
572 else if (py::isinstance<tensor::usm_ndarray>(h)) {
573 const auto &usm_array_inst = py::cast<tensor::usm_ndarray>(h);
574 return usm_array_inst.is_managed_by_smart_ptr();
575 }
576
577 return false;
578 }
579
580 static const std::shared_ptr<void> &extract_shared_ptr(const py::object &h)
581 {
582 if (py::isinstance<dpctl::memory::usm_memory>(h)) {
583 const auto &usm_memory_inst =
584 py::cast<dpctl::memory::usm_memory>(h);
585 return usm_memory_inst.get_smart_ptr_owner();
586 }
587 else if (py::isinstance<tensor::usm_ndarray>(h)) {
588 const auto &usm_array_inst = py::cast<tensor::usm_ndarray>(h);
589 return usm_array_inst.get_smart_ptr_owner();
590 }
591
592 throw std::runtime_error(
593 "Attempted extraction of shared_ptr on an unrecognized type");
594 }
595};
596} // end of namespace detail
597
598template <std::size_t num>
599sycl::event keep_args_alive(sycl::queue &q,
600 const py::object (&py_objs)[num],
601 const std::vector<sycl::event> &depends = {})
602{
603 std::size_t n_objects_held = 0;
604 std::array<std::shared_ptr<py::handle>, num> shp_arr{};
605
606 std::size_t n_usm_owners_held = 0;
607 std::array<std::shared_ptr<void>, num> shp_usm{};
608
609 for (std::size_t i = 0; i < num; ++i) {
610 const auto &py_obj_i = py_objs[i];
611 if (detail::ManagedMemory::is_usm_managed_by_shared_ptr(py_obj_i)) {
612 const auto &shp =
613 detail::ManagedMemory::extract_shared_ptr(py_obj_i);
614 shp_usm[n_usm_owners_held] = shp;
615 ++n_usm_owners_held;
616 }
617 else {
618 shp_arr[n_objects_held] = std::make_shared<py::handle>(py_obj_i);
619 shp_arr[n_objects_held]->inc_ref();
620 ++n_objects_held;
621 }
622 }
623
624 bool use_depends = true;
625 sycl::event host_task_ev;
626
627 if (n_usm_owners_held > 0) {
628 host_task_ev = q.submit([&](sycl::handler &cgh) {
629 if (use_depends) {
630 cgh.depends_on(depends);
631 use_depends = false;
632 }
633 else {
634 cgh.depends_on(host_task_ev);
635 }
636 cgh.host_task([shp_usm = std::move(shp_usm)]() {
637 // no body, but shared pointers are captured in
638 // the lambda, ensuring that USM allocation is
639 // kept alive
640 });
641 });
642 }
643
644 if (n_objects_held > 0) {
645 host_task_ev = q.submit([&](sycl::handler &cgh) {
646 if (use_depends) {
647 cgh.depends_on(depends);
648 use_depends = false;
649 }
650 else {
651 cgh.depends_on(host_task_ev);
652 }
653 cgh.host_task([n_objects_held, shp_arr = std::move(shp_arr)]() {
654 py::gil_scoped_acquire acquire;
655
656 for (std::size_t i = 0; i < n_objects_held; ++i) {
657 shp_arr[i]->dec_ref();
658 }
659 });
660 });
661 }
662
663 return host_task_ev;
664}
665
666// add to namespace for convenience
667using ::dpctl::utils::queues_are_compatible;
668
671template <std::size_t num>
672bool queues_are_compatible(const sycl::queue &exec_q,
673 const tensor::usm_ndarray (&arrs)[num])
674{
675 for (std::size_t i = 0; i < num; ++i) {
676
677 if (exec_q != arrs[i].get_queue()) {
678 return false;
679 }
680 }
681 return true;
682}
683} // end namespace utils
684} // end namespace dpnp
py::object get_usm_data() const
Get usm_data property of array.