Skip to content
Open
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This release is compatible with NumPy 2.5.
* Added `dpnp.tensor.broadcast_shapes` to align with the 2025.12 version of the Python array API [#3009](https://github.com/IntelPython/dpnp/pull/3009)
* Added support for free-threaded Python builds [gh-3026](https://github.com/IntelPython/dpnp/pull/3026)
* Added `dpnp.broadcast` class implementation [#2901](https://github.com/IntelPython/dpnp/pull/2901)
* Added `UsmNDArray_RemoveQueueRef` C API function to release a queue reference obtained from `UsmNDArray_GetQueueRef` [#3042](https://github.com/IntelPython/dpnp/pull/3042)

### Changed

Expand Down Expand Up @@ -89,6 +90,7 @@ This release is compatible with NumPy 2.5.
* Fixed `dpnp.ndarray.view` ignoring the USM element offset of a sliced array, which also caused `dpnp.einsum` to silently return wrong results for a single sliced operand with no summed index [#3037](https://github.com/IntelPython/dpnp/pull/3037)
* Fixed `dpnp.all` and `dpnp.any` aborting when reducing over an empty axis (e.g. an array with a zero-length dimension) [#3021](https://github.com/IntelPython/dpnp/pull/3021)
* Released the GIL before the blocking OneMKL DFT calls in the FFT extension [#3040](https://github.com/IntelPython/dpnp/pull/3040)
* Fixed a per-call `sycl::queue` leak in `usm_ndarray::get_queue()`/`get_device()` [#3042](https://github.com/IntelPython/dpnp/pull/3042)

### Security

Expand Down
27 changes: 23 additions & 4 deletions dpnp/include/dpnp4pybind11.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class dpnp_capi
int (*UsmNDArray_GetElementSize_)(PyUSMArrayObject *);
int (*UsmNDArray_GetFlags_)(PyUSMArrayObject *);
DPCTLSyclQueueRef (*UsmNDArray_GetQueueRef_)(PyUSMArrayObject *);
void (*UsmNDArray_RemoveQueueRef_)(DPCTLSyclQueueRef);
py::ssize_t (*UsmNDArray_GetOffset_)(PyUSMArrayObject *);
PyObject *(*UsmNDArray_GetUSMData_)(PyUSMArrayObject *);
void (*UsmNDArray_SetWritableFlag_)(PyUSMArrayObject *, int);
Expand Down Expand Up @@ -186,8 +187,9 @@ class dpnp_capi
UsmNDArray_GetNDim_(nullptr), UsmNDArray_GetShape_(nullptr),
UsmNDArray_GetStrides_(nullptr), UsmNDArray_GetTypenum_(nullptr),
UsmNDArray_GetElementSize_(nullptr), UsmNDArray_GetFlags_(nullptr),
UsmNDArray_GetQueueRef_(nullptr), UsmNDArray_GetOffset_(nullptr),
UsmNDArray_GetUSMData_(nullptr), UsmNDArray_SetWritableFlag_(nullptr),
UsmNDArray_GetQueueRef_(nullptr), UsmNDArray_RemoveQueueRef_(nullptr),
UsmNDArray_GetOffset_(nullptr), UsmNDArray_GetUSMData_(nullptr),
UsmNDArray_SetWritableFlag_(nullptr),
UsmNDArray_MakeSimpleFromMemory_(nullptr),
UsmNDArray_MakeSimpleFromPtr_(nullptr),
UsmNDArray_MakeFromPtr_(nullptr), USM_ARRAY_C_CONTIGUOUS_(0),
Expand Down Expand Up @@ -215,6 +217,7 @@ class dpnp_capi
this->UsmNDArray_GetElementSize_ = UsmNDArray_GetElementSize;
this->UsmNDArray_GetFlags_ = UsmNDArray_GetFlags;
this->UsmNDArray_GetQueueRef_ = UsmNDArray_GetQueueRef;
this->UsmNDArray_RemoveQueueRef_ = UsmNDArray_RemoveQueueRef;
this->UsmNDArray_GetOffset_ = UsmNDArray_GetOffset;
this->UsmNDArray_GetUSMData_ = UsmNDArray_GetUSMData;
this->UsmNDArray_SetWritableFlag_ = UsmNDArray_SetWritableFlag;
Expand Down Expand Up @@ -489,17 +492,33 @@ class usm_ndarray : public py::object
PyUSMArrayObject *raw_ar = usm_array_ptr();

auto const &api = detail::dpnp_capi::get();
// UsmNDArray_GetQueueRef_ returns an owning copy; free it through
// RemoveQueueRef (dpctl's DPCTLQueue_Delete)
DPCTLSyclQueueRef QRef = api.UsmNDArray_GetQueueRef_(raw_ar);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QRef might be NULL on OOM inside DPCTLQueue_Copy, while below dereferencing of NULL is UB.
It seems legacy and can be done in the follow-up PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good catch
I will be implemented in the follow-up PR

return *(reinterpret_cast<sycl::queue *>(QRef));
auto qref_deleter = [](sycl::queue *q) {
detail::dpnp_capi::get().UsmNDArray_RemoveQueueRef_(
reinterpret_cast<DPCTLSyclQueueRef>(q));
};
std::unique_ptr<sycl::queue, decltype(qref_deleter)> q_ptr{
reinterpret_cast<sycl::queue *>(QRef), qref_deleter};
return *q_ptr;
}

sycl::device get_device() const
{
PyUSMArrayObject *raw_ar = usm_array_ptr();

auto const &api = detail::dpnp_capi::get();
// UsmNDArray_GetQueueRef_ returns an owning copy; free it through
// RemoveQueueRef (dpctl's DPCTLQueue_Delete)
DPCTLSyclQueueRef QRef = api.UsmNDArray_GetQueueRef_(raw_ar);
return reinterpret_cast<sycl::queue *>(QRef)->get_device();
auto qref_deleter = [](sycl::queue *q) {
detail::dpnp_capi::get().UsmNDArray_RemoveQueueRef_(
reinterpret_cast<DPCTLSyclQueueRef>(q));
};
std::unique_ptr<sycl::queue, decltype(qref_deleter)> q_ptr{
reinterpret_cast<sycl::queue *>(QRef), qref_deleter};
return q_ptr->get_device();
}

int get_typenum() const
Expand Down
6 changes: 6 additions & 0 deletions dpnp/tensor/_usmarray.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1850,6 +1850,12 @@ cdef api c_dpctl.DPCTLSyclQueueRef UsmNDArray_GetQueueRef(usm_ndarray arr):
return arr.get_queue_ref()


cdef api void UsmNDArray_RemoveQueueRef(c_dpctl.DPCTLSyclQueueRef QRef):
"""Delete a DPCTLSyclQueueRef previously returned by
UsmNDArray_GetQueueRef"""
c_dpctl.DPCTLQueue_Delete(QRef) # safe on NULL


cdef api Py_ssize_t UsmNDArray_GetOffset(usm_ndarray arr):
"""Get offset of zero-index array element from the beginning of the USM
allocation"""
Expand Down
11 changes: 11 additions & 0 deletions dpnp/tests/tensor/test_usm_ndarray_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,19 @@ def test_pyx_capi_get_queue_ref():
fn_restype=ctypes.c_void_p,
fn_argtypes=(ctypes.py_object,),
)
remove_queue_ref_fn = _pyx_capi_fnptr_to_callable(
X,
"UsmNDArray_RemoveQueueRef",
b"void (DPCTLSyclQueueRef)",
fn_restype=None,
fn_argtypes=(ctypes.c_void_p,),
)
queue_ref = get_queue_ref_fn(X) # address of a copy, should be unequal
assert queue_ref != X.sycl_queue.addressof_ref()
# UsmNDArray_GetQueueRef returns an owning copy;
# UsmNDArray_RemoveQueueRef must free it and be a no-op on NULL
remove_queue_ref_fn(queue_ref)
remove_queue_ref_fn(None)


def test_pyx_capi_make_from_memory():
Expand Down
Loading