diff --git a/dpnp/backend/include/dpnp4pybind11.hpp b/dpnp/backend/include/dpnp4pybind11.hpp index 3150d63146f..6394987a5f8 100644 --- a/dpnp/backend/include/dpnp4pybind11.hpp +++ b/dpnp/backend/include/dpnp4pybind11.hpp @@ -77,6 +77,37 @@ class dpnp_capi public: PyTypeObject *PyUSMArrayType_; + char *(*UsmNDArray_GetData_)(PyUSMArrayObject *); + int (*UsmNDArray_GetNDim_)(PyUSMArrayObject *); + py::ssize_t *(*UsmNDArray_GetShape_)(PyUSMArrayObject *); + py::ssize_t *(*UsmNDArray_GetStrides_)(PyUSMArrayObject *); + int (*UsmNDArray_GetTypenum_)(PyUSMArrayObject *); + int (*UsmNDArray_GetElementSize_)(PyUSMArrayObject *); + int (*UsmNDArray_GetFlags_)(PyUSMArrayObject *); + DPCTLSyclQueueRef (*UsmNDArray_GetQueueRef_)(PyUSMArrayObject *); + py::ssize_t (*UsmNDArray_GetOffset_)(PyUSMArrayObject *); + PyObject *(*UsmNDArray_GetUSMData_)(PyUSMArrayObject *); + void (*UsmNDArray_SetWritableFlag_)(PyUSMArrayObject *, int); + PyObject *(*UsmNDArray_MakeSimpleFromMemory_)(int, + const py::ssize_t *, + int, + Py_MemoryObject *, + py::ssize_t, + char); + PyObject *(*UsmNDArray_MakeSimpleFromPtr_)(size_t, + int, + DPCTLSyclUSMRef, + DPCTLSyclQueueRef, + PyObject *); + PyObject *(*UsmNDArray_MakeFromPtr_)(int, + const py::ssize_t *, + int, + const py::ssize_t *, + DPCTLSyclUSMRef, + DPCTLSyclQueueRef, + py::ssize_t, + PyObject *); + int USM_ARRAY_C_CONTIGUOUS_; int USM_ARRAY_F_CONTIGUOUS_; int USM_ARRAY_WRITABLE_; @@ -119,7 +150,15 @@ class dpnp_capi std::shared_ptr default_usm_ndarray_; dpnp_capi() - : PyUSMArrayType_(nullptr), USM_ARRAY_C_CONTIGUOUS_(0), + : PyUSMArrayType_(nullptr), UsmNDArray_GetData_(nullptr), + 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_MakeSimpleFromMemory_(nullptr), + UsmNDArray_MakeSimpleFromPtr_(nullptr), + UsmNDArray_MakeFromPtr_(nullptr), USM_ARRAY_C_CONTIGUOUS_(0), USM_ARRAY_F_CONTIGUOUS_(0), USM_ARRAY_WRITABLE_(0), UAR_BOOL_(-1), UAR_BYTE_(-1), UAR_UBYTE_(-1), UAR_SHORT_(-1), UAR_USHORT_(-1), UAR_INT_(-1), UAR_UINT_(-1), UAR_LONG_(-1), UAR_ULONG_(-1), @@ -135,6 +174,23 @@ class dpnp_capi this->PyUSMArrayType_ = &PyUSMArrayType; + // dpnp.tensor.usm_ndarray API + this->UsmNDArray_GetData_ = UsmNDArray_GetData; + this->UsmNDArray_GetNDim_ = UsmNDArray_GetNDim; + this->UsmNDArray_GetShape_ = UsmNDArray_GetShape; + this->UsmNDArray_GetStrides_ = UsmNDArray_GetStrides; + this->UsmNDArray_GetTypenum_ = UsmNDArray_GetTypenum; + this->UsmNDArray_GetElementSize_ = UsmNDArray_GetElementSize; + this->UsmNDArray_GetFlags_ = UsmNDArray_GetFlags; + this->UsmNDArray_GetQueueRef_ = UsmNDArray_GetQueueRef; + this->UsmNDArray_GetOffset_ = UsmNDArray_GetOffset; + this->UsmNDArray_GetUSMData_ = UsmNDArray_GetUSMData; + this->UsmNDArray_SetWritableFlag_ = UsmNDArray_SetWritableFlag; + this->UsmNDArray_MakeSimpleFromMemory_ = + UsmNDArray_MakeSimpleFromMemory; + this->UsmNDArray_MakeSimpleFromPtr_ = UsmNDArray_MakeSimpleFromPtr; + this->UsmNDArray_MakeFromPtr_ = UsmNDArray_MakeFromPtr; + // constants this->USM_ARRAY_C_CONTIGUOUS_ = USM_ARRAY_C_CONTIGUOUS; this->USM_ARRAY_F_CONTIGUOUS_ = USM_ARRAY_F_CONTIGUOUS; @@ -269,7 +325,9 @@ class usm_ndarray : public py::object char *get_data() const { PyUSMArrayObject *raw_ar = usm_array_ptr(); - return raw_ar->data_; + + auto const &api = detail::dpnp_capi::get(); + return api.UsmNDArray_GetData_(raw_ar); } template @@ -281,13 +339,17 @@ class usm_ndarray : public py::object int get_ndim() const { PyUSMArrayObject *raw_ar = usm_array_ptr(); - return raw_ar->nd_; + + auto const &api = detail::dpnp_capi::get(); + return api.UsmNDArray_GetNDim_(raw_ar); } const py::ssize_t *get_shape_raw() const { PyUSMArrayObject *raw_ar = usm_array_ptr(); - return raw_ar->shape_; + + auto const &api = detail::dpnp_capi::get(); + return api.UsmNDArray_GetShape_(raw_ar); } std::vector get_shape_vector() const @@ -308,7 +370,9 @@ class usm_ndarray : public py::object const py::ssize_t *get_strides_raw() const { PyUSMArrayObject *raw_ar = usm_array_ptr(); - return raw_ar->strides_; + + auto const &api = detail::dpnp_capi::get(); + return api.UsmNDArray_GetStrides_(raw_ar); } std::vector get_strides_vector() const @@ -343,8 +407,9 @@ class usm_ndarray : public py::object { PyUSMArrayObject *raw_ar = usm_array_ptr(); - int ndim = raw_ar->nd_; - const py::ssize_t *shape = raw_ar->shape_; + auto const &api = detail::dpnp_capi::get(); + int ndim = api.UsmNDArray_GetNDim_(raw_ar); + const py::ssize_t *shape = api.UsmNDArray_GetShape_(raw_ar); py::ssize_t nelems = 1; for (int i = 0; i < ndim; ++i) { @@ -359,9 +424,10 @@ class usm_ndarray : public py::object { PyUSMArrayObject *raw_ar = usm_array_ptr(); - int nd = raw_ar->nd_; - const py::ssize_t *shape = raw_ar->shape_; - const py::ssize_t *strides = raw_ar->strides_; + auto const &api = detail::dpnp_capi::get(); + int nd = api.UsmNDArray_GetNDim_(raw_ar); + const py::ssize_t *shape = api.UsmNDArray_GetShape_(raw_ar); + const py::ssize_t *strides = api.UsmNDArray_GetStrides_(raw_ar); py::ssize_t offset_min = 0; py::ssize_t offset_max = 0; @@ -389,77 +455,43 @@ class usm_ndarray : public py::object sycl::queue get_queue() const { PyUSMArrayObject *raw_ar = usm_array_ptr(); - Py_MemoryObject *mem_obj = - reinterpret_cast(raw_ar->base_); - auto const &dpctl_api = ::dpctl::detail::dpctl_capi::get(); - DPCTLSyclQueueRef QRef = dpctl_api.Memory_GetQueueRef_(mem_obj); + auto const &api = detail::dpnp_capi::get(); + DPCTLSyclQueueRef QRef = api.UsmNDArray_GetQueueRef_(raw_ar); return *(reinterpret_cast(QRef)); } sycl::device get_device() const { PyUSMArrayObject *raw_ar = usm_array_ptr(); - Py_MemoryObject *mem_obj = - reinterpret_cast(raw_ar->base_); - auto const &dpctl_api = ::dpctl::detail::dpctl_capi::get(); - DPCTLSyclQueueRef QRef = dpctl_api.Memory_GetQueueRef_(mem_obj); + auto const &api = detail::dpnp_capi::get(); + DPCTLSyclQueueRef QRef = api.UsmNDArray_GetQueueRef_(raw_ar); return reinterpret_cast(QRef)->get_device(); } int get_typenum() const { PyUSMArrayObject *raw_ar = usm_array_ptr(); - return raw_ar->typenum_; + + auto const &api = detail::dpnp_capi::get(); + return api.UsmNDArray_GetTypenum_(raw_ar); } int get_flags() const { PyUSMArrayObject *raw_ar = usm_array_ptr(); - return raw_ar->flags_; + + auto const &api = detail::dpnp_capi::get(); + return api.UsmNDArray_GetFlags_(raw_ar); } int get_elemsize() const { - int typenum = get_typenum(); - auto const &api = detail::dpnp_capi::get(); + PyUSMArrayObject *raw_ar = usm_array_ptr(); - // Lookup table for element sizes based on typenum - if (typenum == api.UAR_BOOL_) - return 1; - if (typenum == api.UAR_BYTE_) - return 1; - if (typenum == api.UAR_UBYTE_) - return 1; - if (typenum == api.UAR_SHORT_) - return 2; - if (typenum == api.UAR_USHORT_) - return 2; - if (typenum == api.UAR_INT_) - return 4; - if (typenum == api.UAR_UINT_) - return 4; - if (typenum == api.UAR_LONG_) - return sizeof(long); - if (typenum == api.UAR_ULONG_) - return sizeof(unsigned long); - if (typenum == api.UAR_LONGLONG_) - return 8; - if (typenum == api.UAR_ULONGLONG_) - return 8; - if (typenum == api.UAR_FLOAT_) - return 4; - if (typenum == api.UAR_DOUBLE_) - return 8; - if (typenum == api.UAR_CFLOAT_) - return 8; - if (typenum == api.UAR_CDOUBLE_) - return 16; - if (typenum == api.UAR_HALF_) - return 2; - - return 0; // Unknown type + auto const &api = detail::dpnp_capi::get(); + return api.UsmNDArray_GetElementSize_(raw_ar); } bool is_c_contiguous() const @@ -487,9 +519,10 @@ class usm_ndarray : public py::object py::object get_usm_data() const { PyUSMArrayObject *raw_ar = usm_array_ptr(); + + auto const &api = detail::dpnp_capi::get(); // base_ is the Memory object - return new reference - PyObject *usm_data = raw_ar->base_; - Py_XINCREF(usm_data); + PyObject *usm_data = api.UsmNDArray_GetUSMData_(raw_ar); // pass reference ownership to py::object return py::reinterpret_steal(usm_data); @@ -498,10 +531,13 @@ class usm_ndarray : public py::object bool is_managed_by_smart_ptr() const { PyUSMArrayObject *raw_ar = usm_array_ptr(); - PyObject *usm_data = raw_ar->base_; + + auto const &api = detail::dpnp_capi::get(); + PyObject *usm_data = api.UsmNDArray_GetUSMData_(raw_ar); auto const &dpctl_api = ::dpctl::detail::dpctl_capi::get(); if (!PyObject_TypeCheck(usm_data, dpctl_api.Py_MemoryType_)) { + Py_DECREF(usm_data); return false; } @@ -509,17 +545,20 @@ class usm_ndarray : public py::object reinterpret_cast(usm_data); const void *opaque_ptr = dpctl_api.Memory_GetOpaquePointer_(mem_obj); + Py_DECREF(usm_data); return bool(opaque_ptr); } const std::shared_ptr &get_smart_ptr_owner() const { PyUSMArrayObject *raw_ar = usm_array_ptr(); - PyObject *usm_data = raw_ar->base_; - auto const &dpctl_api = ::dpctl::detail::dpctl_capi::get(); + auto const &api = detail::dpnp_capi::get(); + PyObject *usm_data = api.UsmNDArray_GetUSMData_(raw_ar); + auto const &dpctl_api = ::dpctl::detail::dpctl_capi::get(); if (!PyObject_TypeCheck(usm_data, dpctl_api.Py_MemoryType_)) { + Py_DECREF(usm_data); throw std::runtime_error( "usm_ndarray object does not have Memory object " "managing lifetime of USM allocation"); @@ -528,6 +567,7 @@ class usm_ndarray : public py::object Py_MemoryObject *mem_obj = reinterpret_cast(usm_data); void *opaque_ptr = dpctl_api.Memory_GetOpaquePointer_(mem_obj); + Py_DECREF(usm_data); if (opaque_ptr) { auto shptr_ptr = diff --git a/dpnp/tensor/_usmarray.pyx b/dpnp/tensor/_usmarray.pyx index c696056d53c..7d90ffeb05a 100644 --- a/dpnp/tensor/_usmarray.pyx +++ b/dpnp/tensor/_usmarray.pyx @@ -1740,6 +1740,238 @@ cdef usm_ndarray _zero_like(usm_ndarray ary): return r +cdef api char* UsmNDArray_GetData(usm_ndarray arr): + """Get allocation pointer of zero index element of array """ + return arr.get_data() + + +cdef api int UsmNDArray_GetNDim(usm_ndarray arr): + """Get array rank: length of its shape""" + return arr.get_ndim() + + +cdef api Py_ssize_t* UsmNDArray_GetShape(usm_ndarray arr): + """Get host pointer to shape vector""" + return arr.get_shape() + + +cdef api Py_ssize_t* UsmNDArray_GetStrides(usm_ndarray arr): + """Get host pointer to strides vector""" + return arr.get_strides() + + +cdef api int UsmNDArray_GetTypenum(usm_ndarray arr): + """Get type number for data type of array elements""" + return arr.get_typenum() + + +cdef api int UsmNDArray_GetElementSize(usm_ndarray arr): + """Get array element size in bytes""" + return arr.get_itemsize() + + +cdef api int UsmNDArray_GetFlags(usm_ndarray arr): + """Get flags of array""" + return arr.get_flags() + + +cdef api c_dpctl.DPCTLSyclQueueRef UsmNDArray_GetQueueRef(usm_ndarray arr): + """Get DPCTLSyclQueueRef for queue associated with the array""" + return arr.get_queue_ref() + + +cdef api Py_ssize_t UsmNDArray_GetOffset(usm_ndarray arr): + """Get offset of zero-index array element from the beginning of the USM + allocation""" + return arr.get_offset() + + +cdef api object UsmNDArray_GetUSMData(usm_ndarray arr): + """Get USM data object underlying the array""" + return arr.get_base() + + +cdef api void UsmNDArray_SetWritableFlag(usm_ndarray arr, int flag): + """Set/unset USM_ARRAY_WRITABLE in the given array `arr`.""" + arr._set_writable_flag(flag) + + +cdef api object UsmNDArray_MakeSimpleFromMemory( + int nd, const Py_ssize_t *shape, int typenum, + c_dpmem._Memory mobj, Py_ssize_t offset, char order +): + """Create contiguous usm_ndarray. + + Args: + nd: number of dimensions (non-negative) + shape: array of nd non-negative array's sizes along each dimension + typenum: array elemental type number + ptr: pointer to the start of allocation + QRef: DPCTLSyclQueueRef associated with the allocation + offset: distance between element with zero multi-index and the + start of allocation + order: Memory layout of the array. Use 'C' for C-contiguous or + row-major layout; 'F' for F-contiguous or column-major layout + Returns: + Created usm_ndarray instance + """ + cdef object shape_tuple = _make_int_tuple(nd, shape) + cdef usm_ndarray arr = usm_ndarray( + shape_tuple, + dtype=_make_typestr(typenum), + buffer=mobj, + offset=offset, + order=(order) + ) + return arr + + +cdef api object UsmNDArray_MakeSimpleFromPtr( + size_t nelems, + int typenum, + c_dpctl.DPCTLSyclUSMRef ptr, + c_dpctl.DPCTLSyclQueueRef QRef, + object owner +): + """Create 1D contiguous usm_ndarray from pointer. + + Args: + nelems: number of elements in array + typenum: array elemental type number + ptr: pointer to the start of allocation + QRef: DPCTLSyclQueueRef associated with the allocation + owner: Python object managing lifetime of USM allocation. + Value None implies transfer of USM allocation ownership + to the created array object. + Returns: + Created usm_ndarray instance + """ + cdef int itemsize = type_bytesize(typenum) + if (itemsize < 1): + raise ValueError( + "dtype with typenum=" + str(typenum) + " is not supported." + ) + cdef size_t nbytes = ( itemsize) * nelems + cdef c_dpmem._Memory mobj + mobj = c_dpmem._Memory.create_from_usm_pointer_size_qref( + ptr, nbytes, QRef, memory_owner=owner + ) + cdef usm_ndarray arr = usm_ndarray( + (nelems,), + dtype=_make_typestr(typenum), + buffer=mobj + ) + return arr + +cdef api object UsmNDArray_MakeFromPtr( + int nd, + const Py_ssize_t *shape, + int typenum, + const Py_ssize_t *strides, + c_dpctl.DPCTLSyclUSMRef ptr, + c_dpctl.DPCTLSyclQueueRef QRef, + Py_ssize_t offset, + object owner +): + """ + General usm_ndarray constructor from externally made USM-allocation. + + Args: + nd: number of dimensions (non-negative) + shape: array of nd non-negative array's sizes along each dimension + typenum: array elemental type number + strides: array of nd strides along each dimension in elements + ptr: pointer to the start of allocation + QRef: DPCTLSyclQueueRef associated with the allocation + offset: distance between element with zero multi-index and the + start of allocation + owner: Python object managing lifetime of USM allocation. + Value None implies transfer of USM allocation ownership + to the created array object. + Returns: + Created usm_ndarray instance + """ + cdef int itemsize = type_bytesize(typenum) + cdef size_t nelems = 1 + cdef Py_ssize_t min_disp = 0 + cdef Py_ssize_t max_disp = 0 + cdef Py_ssize_t step_ = 0 + cdef Py_ssize_t dim_ = 0 + cdef it = 0 + cdef c_dpmem._Memory mobj + cdef usm_ndarray arr + cdef object obj_shape + cdef object obj_strides + + if (itemsize < 1): + raise ValueError( + "dtype with typenum=" + str(typenum) + " is not supported." + ) + if (nd < 0): + raise ValueError("Dimensionality must be non-negative") + if (ptr is NULL or QRef is NULL): + raise ValueError( + "Non-null USM allocation pointer and QRef are expected" + ) + if (nd == 0): + # case of 0d scalars + mobj = c_dpmem._Memory.create_from_usm_pointer_size_qref( + ptr, itemsize, QRef, memory_owner=owner + ) + arr = usm_ndarray( + tuple(), + dtype=_make_typestr(typenum), + buffer=mobj + ) + return arr + if (shape is NULL or strides is NULL): + raise ValueError("Both shape and stride vectors are required") + for it in range(nd): + dim_ = shape[it] + if dim_ < 0: + raise ValueError( + f"Dimension along axis {it} must be non-negative" + ) + nelems *= dim_ + if dim_ > 0: + step_ = strides[it] + if step_ > 0: + max_disp += step_ * (dim_ - 1) + else: + min_disp += step_ * (dim_ - 1) + + obj_shape = _make_int_tuple(nd, shape) + obj_strides = _make_int_tuple(nd, strides) + if nelems == 0: + mobj = c_dpmem._Memory.create_from_usm_pointer_size_qref( + ptr, itemsize, QRef, memory_owner=owner + ) + arr = usm_ndarray( + obj_shape, + dtype=_make_typestr(typenum), + strides=obj_strides, + buffer=mobj, + offset=0 + ) + return arr + if offset + min_disp < 0: + raise ValueError( + "Given shape, strides and offset reference out-of-bound memory" + ) + nbytes = ( itemsize) * (offset + max_disp + 1) + mobj = c_dpmem._Memory.create_from_usm_pointer_size_qref( + ptr, nbytes, QRef, memory_owner=owner + ) + arr = usm_ndarray( + obj_shape, + dtype=_make_typestr(typenum), + strides=obj_strides, + buffer=mobj, + offset=offset + ) + return arr + + def _is_object_with_buffer_protocol(o): "Returns True if object supports Python buffer protocol" return _is_buffer(o) diff --git a/dpnp/tests/tensor/test_usm_ndarray_ctor.py b/dpnp/tests/tensor/test_usm_ndarray_ctor.py index 70066860b19..39ee17ed46d 100644 --- a/dpnp/tests/tensor/test_usm_ndarray_ctor.py +++ b/dpnp/tests/tensor/test_usm_ndarray_ctor.py @@ -183,6 +183,554 @@ def test_usm_ndarray_writable_flag_views(): assert not a.imag.flags.writable +def _pyx_capi_fnptr_to_callable( + X, + pyx_capi_name, + caps_name, + fn_restype=ctypes.c_void_p, + fn_argtypes=(ctypes.py_object,), +): + import sys + + mod = sys.modules[X.__class__.__module__] + cap = mod.__pyx_capi__.get(pyx_capi_name, None) + if cap is None: + raise ValueError( + "__pyx_capi__ does not export {} capsule".format(pyx_capi_name) + ) + # construct Python callable to invoke these functions + cap_ptr_fn = ctypes.pythonapi.PyCapsule_GetPointer + cap_ptr_fn.restype = ctypes.c_void_p + cap_ptr_fn.argtypes = [ctypes.py_object, ctypes.c_char_p] + fn_ptr = cap_ptr_fn(cap, caps_name) + callable_maker_ptr = ctypes.PYFUNCTYPE(fn_restype, *fn_argtypes) + return callable_maker_ptr(fn_ptr) + + +def test_pyx_capi_get_data(): + try: + X = dpt.usm_ndarray(17, dtype="i8")[1::2] + except dpctl.SyclDeviceCreationError: + pytest.skip("No SYCL devices available") + get_data_fn = _pyx_capi_fnptr_to_callable( + X, + "UsmNDArray_GetData", + b"char *(struct PyUSMArrayObject *)", + fn_restype=ctypes.c_void_p, + fn_argtypes=(ctypes.py_object,), + ) + r1 = get_data_fn(X) + sua_iface = X.__sycl_usm_array_interface__ + assert r1 == sua_iface["data"][0] + sua_iface.get("offset") * X.itemsize + + +def test_pyx_capi_get_shape(): + try: + X = dpt.usm_ndarray(17, dtype="u4")[1::2] + except dpctl.SyclDeviceCreationError: + pytest.skip("No SYCL devices available") + get_shape_fn = _pyx_capi_fnptr_to_callable( + X, + "UsmNDArray_GetShape", + b"Py_ssize_t *(struct PyUSMArrayObject *)", + fn_restype=ctypes.c_void_p, + fn_argtypes=(ctypes.py_object,), + ) + c_longlong_p = ctypes.POINTER(ctypes.c_longlong) + shape0 = ctypes.cast(get_shape_fn(X), c_longlong_p).contents.value + assert shape0 == X.shape[0] + + +def test_pyx_capi_get_strides(): + try: + X = dpt.usm_ndarray(17, dtype="f4")[1::2] + except dpctl.SyclDeviceCreationError: + pytest.skip("No SYCL devices available") + get_strides_fn = _pyx_capi_fnptr_to_callable( + X, + "UsmNDArray_GetStrides", + b"Py_ssize_t *(struct PyUSMArrayObject *)", + fn_restype=ctypes.c_void_p, + fn_argtypes=(ctypes.py_object,), + ) + c_longlong_p = ctypes.POINTER(ctypes.c_longlong) + strides0_p = get_strides_fn(X) + if strides0_p: + strides0_p = ctypes.cast(strides0_p, c_longlong_p).contents + strides0_p = strides0_p.value + assert strides0_p == 0 or strides0_p == X.strides[0] + + +def test_pyx_capi_get_ndim(): + try: + X = dpt.usm_ndarray(17, dtype="?")[1::2] + except dpctl.SyclDeviceCreationError: + pytest.skip("No SYCL devices available") + get_ndim_fn = _pyx_capi_fnptr_to_callable( + X, + "UsmNDArray_GetNDim", + b"int (struct PyUSMArrayObject *)", + fn_restype=ctypes.c_int, + fn_argtypes=(ctypes.py_object,), + ) + assert get_ndim_fn(X) == X.ndim + + +def test_pyx_capi_get_typenum(): + try: + X = dpt.usm_ndarray(17, dtype="c8")[1::2] + except dpctl.SyclDeviceCreationError: + pytest.skip("No SYCL devices available") + get_typenum_fn = _pyx_capi_fnptr_to_callable( + X, + "UsmNDArray_GetTypenum", + b"int (struct PyUSMArrayObject *)", + fn_restype=ctypes.c_int, + fn_argtypes=(ctypes.py_object,), + ) + typenum = get_typenum_fn(X) + assert type(typenum) is int + assert typenum == X.dtype.num + + +def test_pyx_capi_get_elemsize(): + try: + X = dpt.usm_ndarray(17, dtype="u8")[1::2] + except dpctl.SyclDeviceCreationError: + pytest.skip("No SYCL devices available") + get_elemsize_fn = _pyx_capi_fnptr_to_callable( + X, + "UsmNDArray_GetElementSize", + b"int (struct PyUSMArrayObject *)", + fn_restype=ctypes.c_int, + fn_argtypes=(ctypes.py_object,), + ) + itemsize = get_elemsize_fn(X) + assert type(itemsize) is int + assert itemsize == X.itemsize + + +def test_pyx_capi_get_flags(): + try: + X = dpt.usm_ndarray(17, dtype="i8")[1::2] + except dpctl.SyclDeviceCreationError: + pytest.skip("No SYCL devices available") + get_flags_fn = _pyx_capi_fnptr_to_callable( + X, + "UsmNDArray_GetFlags", + b"int (struct PyUSMArrayObject *)", + fn_restype=ctypes.c_int, + fn_argtypes=(ctypes.py_object,), + ) + flags = get_flags_fn(X) + assert type(flags) is int and X.flags == flags + + +def test_pyx_capi_get_offset(): + try: + X = dpt.usm_ndarray(17, dtype="u2")[1::2] + except dpctl.SyclDeviceCreationError: + pytest.skip("No SYCL devices available") + get_offset_fn = _pyx_capi_fnptr_to_callable( + X, + "UsmNDArray_GetOffset", + b"Py_ssize_t (struct PyUSMArrayObject *)", + fn_restype=ctypes.c_longlong, + fn_argtypes=(ctypes.py_object,), + ) + offset = get_offset_fn(X) + assert type(offset) is int + assert offset == X.__sycl_usm_array_interface__["offset"] + + +def test_pyx_capi_get_usmdata(): + try: + X = dpt.usm_ndarray(17, dtype="u2")[1::2] + except dpctl.SyclDeviceCreationError: + pytest.skip("No SYCL devices available") + get_usmdata_fn = _pyx_capi_fnptr_to_callable( + X, + "UsmNDArray_GetUSMData", + b"PyObject *(struct PyUSMArrayObject *)", + fn_restype=ctypes.py_object, + fn_argtypes=(ctypes.py_object,), + ) + capi_usm_data = get_usmdata_fn(X) + assert isinstance(capi_usm_data, dpm._memory._Memory) + assert capi_usm_data.nbytes == X.usm_data.nbytes + assert capi_usm_data._pointer == X.usm_data._pointer + assert capi_usm_data.sycl_queue == X.usm_data.sycl_queue + + +def test_pyx_capi_get_queue_ref(): + try: + X = dpt.usm_ndarray(17, dtype="i2")[1::2] + except dpctl.SyclDeviceCreationError: + pytest.skip("No SYCL devices available") + get_queue_ref_fn = _pyx_capi_fnptr_to_callable( + X, + "UsmNDArray_GetQueueRef", + b"DPCTLSyclQueueRef (struct PyUSMArrayObject *)", + fn_restype=ctypes.c_void_p, + fn_argtypes=(ctypes.py_object,), + ) + queue_ref = get_queue_ref_fn(X) # address of a copy, should be unequal + assert queue_ref != X.sycl_queue.addressof_ref() + + +def test_pyx_capi_make_from_memory(): + q = get_queue_or_skip() + n0, n1 = 4, 6 + c_tuple = (ctypes.c_ssize_t * 2)(n0, n1) + mem = dpm.MemoryUSMShared(n0 * n1 * 4, queue=q) + typenum = dpt.dtype("single").num + any_usm_ndarray = dpt.empty((), dtype="i4", sycl_queue=q) + make_from_memory_fn = _pyx_capi_fnptr_to_callable( + any_usm_ndarray, + "UsmNDArray_MakeSimpleFromMemory", + b"PyObject *(int, Py_ssize_t const *, int, " + b"struct Py_MemoryObject *, Py_ssize_t, char)", + fn_restype=ctypes.py_object, + fn_argtypes=( + ctypes.c_int, + ctypes.POINTER(ctypes.c_ssize_t), + ctypes.c_int, + ctypes.py_object, + ctypes.c_ssize_t, + ctypes.c_char, + ), + ) + r = make_from_memory_fn( + ctypes.c_int(2), + c_tuple, + ctypes.c_int(typenum), + mem, + ctypes.c_ssize_t(0), + ctypes.c_char(b"C"), + ) + assert isinstance(r, dpt.usm_ndarray) + assert r.ndim == 2 + assert r.shape == (n0, n1) + assert r._pointer == mem._pointer + assert r.usm_type == "shared" + assert r.sycl_queue == q + assert r.flags["C"] + r2 = make_from_memory_fn( + ctypes.c_int(2), + c_tuple, + ctypes.c_int(typenum), + mem, + ctypes.c_ssize_t(0), + ctypes.c_char(b"F"), + ) + ptr = mem._pointer + del mem + del r + assert isinstance(r2, dpt.usm_ndarray) + assert r2._pointer == ptr + assert r2.usm_type == "shared" + assert r2.sycl_queue == q + assert r2.flags["F"] + + +def test_pyx_capi_set_writable_flag(): + q = get_queue_or_skip() + usm_ndarray = dpt.empty((4, 5), dtype="i4", sycl_queue=q) + assert isinstance(usm_ndarray, dpt.usm_ndarray) + assert usm_ndarray.flags["WRITABLE"] is True + set_writable = _pyx_capi_fnptr_to_callable( + usm_ndarray, + "UsmNDArray_SetWritableFlag", + b"void (struct PyUSMArrayObject *, int)", + fn_restype=None, + fn_argtypes=(ctypes.py_object, ctypes.c_int), + ) + set_writable(usm_ndarray, ctypes.c_int(0)) + assert isinstance(usm_ndarray, dpt.usm_ndarray) + assert usm_ndarray.flags["WRITABLE"] is False + set_writable(usm_ndarray, ctypes.c_int(1)) + assert isinstance(usm_ndarray, dpt.usm_ndarray) + assert usm_ndarray.flags["WRITABLE"] is True + + +def test_pyx_capi_make_from_ptr(): + q = get_queue_or_skip() + usm_ndarray = dpt.empty((), dtype="i4", sycl_queue=q) + make_from_ptr = _pyx_capi_fnptr_to_callable( + usm_ndarray, + "UsmNDArray_MakeSimpleFromPtr", + b"PyObject *(size_t, int, DPCTLSyclUSMRef, " + b"DPCTLSyclQueueRef, PyObject *)", + fn_restype=ctypes.py_object, + fn_argtypes=( + ctypes.c_size_t, + ctypes.c_int, + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.py_object, + ), + ) + nelems = 10 + dt = dpt.int64 + mem = dpm.MemoryUSMDevice(nelems * dt.itemsize, queue=q) + arr = make_from_ptr( + ctypes.c_size_t(nelems), + dt.num, + mem._pointer, + mem.sycl_queue.addressof_ref(), + mem, + ) + assert isinstance(arr, dpt.usm_ndarray) + assert arr.shape == (nelems,) + assert arr.dtype == dt + assert arr.sycl_queue == q + assert arr._pointer == mem._pointer + del mem + assert isinstance(arr.__repr__(), str) + + +def test_pyx_capi_make_general(): + q = get_queue_or_skip() + usm_ndarray = dpt.empty((), dtype="i4", sycl_queue=q) + make_from_ptr = _pyx_capi_fnptr_to_callable( + usm_ndarray, + "UsmNDArray_MakeFromPtr", + b"PyObject *(int, Py_ssize_t const *, int, Py_ssize_t const *, " + b"DPCTLSyclUSMRef, DPCTLSyclQueueRef, Py_ssize_t, PyObject *)", + fn_restype=ctypes.py_object, + fn_argtypes=( + ctypes.c_int, + ctypes.POINTER(ctypes.c_ssize_t), + ctypes.c_int, + ctypes.POINTER(ctypes.c_ssize_t), + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_ssize_t, + ctypes.py_object, + ), + ) + # Create array to view into diagonal of a matrix + n = 5 + mat = dpt.reshape( + dpt.arange(n * n, dtype="i4", sycl_queue=q), + ( + n, + n, + ), + ) + c_shape = (ctypes.c_ssize_t * 1)( + n, + ) + c_strides = (ctypes.c_ssize_t * 1)( + n + 1, + ) + diag = make_from_ptr( + ctypes.c_int(1), + c_shape, + ctypes.c_int(mat.dtype.num), + c_strides, + mat._pointer, + mat.sycl_queue.addressof_ref(), + ctypes.c_ssize_t(0), + mat, + ) + assert isinstance(diag, dpt.usm_ndarray) + assert diag.shape == (n,) + assert diag.strides == (n + 1,) + assert diag.dtype == mat.dtype + assert diag.sycl_queue == q + assert diag._pointer == mat._pointer + del mat + assert isinstance(diag.__repr__(), str) + # create 0d scalar + mat = dpt.reshape( + dpt.arange(n * n, dtype="i4", sycl_queue=q), + ( + n, + n, + ), + ) + sc = make_from_ptr( + ctypes.c_int(0), + None, # NULL pointer + ctypes.c_int(mat.dtype.num), + None, # NULL pointer + mat._pointer, + mat.sycl_queue.addressof_ref(), + ctypes.c_ssize_t(0), + mat, + ) + assert isinstance(sc, dpt.usm_ndarray) + assert sc.shape == () + assert sc.dtype == mat.dtype + assert sc.sycl_queue == q + assert sc._pointer == mat._pointer + c_shape = (ctypes.c_ssize_t * 2)(0, n) + c_strides = (ctypes.c_ssize_t * 2)(0, 1) + zd_arr = make_from_ptr( + ctypes.c_int(2), + c_shape, + ctypes.c_int(mat.dtype.num), + c_strides, + mat._pointer, + mat.sycl_queue.addressof_ref(), + ctypes.c_ssize_t(0), + mat, + ) + assert isinstance(zd_arr, dpt.usm_ndarray) + assert zd_arr.shape == ( + 0, + n, + ) + assert zd_arr.strides == ( + 0, + 1, + ) + assert zd_arr.dtype == mat.dtype + assert zd_arr.sycl_queue == q + assert zd_arr._pointer == mat._pointer + + +def test_pyx_capi_make_fns_invalid_typenum(): + q = get_queue_or_skip() + usm_ndarray = dpt.empty((), dtype="i4", sycl_queue=q) + + make_simple_from_ptr = _pyx_capi_fnptr_to_callable( + usm_ndarray, + "UsmNDArray_MakeSimpleFromPtr", + b"PyObject *(size_t, int, DPCTLSyclUSMRef, " + b"DPCTLSyclQueueRef, PyObject *)", + fn_restype=ctypes.py_object, + fn_argtypes=( + ctypes.c_size_t, + ctypes.c_int, + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.py_object, + ), + ) + + nelems = 10 + dtype = dpt.int64 + arr = dpt.arange(nelems, dtype=dtype, sycl_queue=q) + + with pytest.raises(ValueError): + make_simple_from_ptr( + ctypes.c_size_t(nelems), + -1, + arr._pointer, + arr.sycl_queue.addressof_ref(), + arr, + ) + + make_from_ptr = _pyx_capi_fnptr_to_callable( + usm_ndarray, + "UsmNDArray_MakeFromPtr", + b"PyObject *(int, Py_ssize_t const *, int, Py_ssize_t const *, " + b"DPCTLSyclUSMRef, DPCTLSyclQueueRef, Py_ssize_t, PyObject *)", + fn_restype=ctypes.py_object, + fn_argtypes=( + ctypes.c_int, + ctypes.POINTER(ctypes.c_ssize_t), + ctypes.c_int, + ctypes.POINTER(ctypes.c_ssize_t), + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_ssize_t, + ctypes.py_object, + ), + ) + c_shape = (ctypes.c_ssize_t * 1)( + nelems, + ) + c_strides = (ctypes.c_ssize_t * 1)( + 1, + ) + with pytest.raises(ValueError): + make_from_ptr( + ctypes.c_int(1), + c_shape, + -1, + c_strides, + arr._pointer, + arr.sycl_queue.addressof_ref(), + ctypes.c_ssize_t(0), + arr, + ) + del arr + + +def _pyx_capi_int(X, pyx_capi_name, caps_name=b"int", val_restype=ctypes.c_int): + import sys + + mod = sys.modules[X.__class__.__module__] + cap = mod.__pyx_capi__.get(pyx_capi_name, None) + if cap is None: + raise ValueError( + "__pyx_capi__ does not export {} capsule".format(pyx_capi_name) + ) + # construct Python callable to invoke these functions + cap_ptr_fn = ctypes.pythonapi.PyCapsule_GetPointer + cap_ptr_fn.restype = ctypes.c_void_p + cap_ptr_fn.argtypes = [ctypes.py_object, ctypes.c_char_p] + cap_ptr = cap_ptr_fn(cap, caps_name) + val_ptr = ctypes.cast(cap_ptr, ctypes.POINTER(val_restype)) + return val_ptr.contents.value + + +def test_pyx_capi_check_constants(): + try: + X = dpt.usm_ndarray(17, dtype="i1")[1::2] + except dpctl.SyclDeviceCreationError: + pytest.skip("No SYCL devices available") + cc_flag = _pyx_capi_int(X, "USM_ARRAY_C_CONTIGUOUS") + assert cc_flag > 0 and 0 == (cc_flag & (cc_flag - 1)) + fc_flag = _pyx_capi_int(X, "USM_ARRAY_F_CONTIGUOUS") + assert fc_flag > 0 and 0 == (fc_flag & (fc_flag - 1)) + w_flag = _pyx_capi_int(X, "USM_ARRAY_WRITABLE") + assert w_flag > 0 and 0 == (w_flag & (w_flag - 1)) + + bool_typenum = _pyx_capi_int(X, "UAR_BOOL") + assert bool_typenum == dpt.dtype("bool_").num + + byte_typenum = _pyx_capi_int(X, "UAR_BYTE") + assert byte_typenum == dpt.dtype(np.byte).num + ubyte_typenum = _pyx_capi_int(X, "UAR_UBYTE") + assert ubyte_typenum == dpt.dtype(np.ubyte).num + + short_typenum = _pyx_capi_int(X, "UAR_SHORT") + assert short_typenum == dpt.dtype(np.short).num + ushort_typenum = _pyx_capi_int(X, "UAR_USHORT") + assert ushort_typenum == dpt.dtype(np.ushort).num + + int_typenum = _pyx_capi_int(X, "UAR_INT") + assert int_typenum == dpt.dtype(np.intc).num + uint_typenum = _pyx_capi_int(X, "UAR_UINT") + assert uint_typenum == dpt.dtype(np.uintc).num + + long_typenum = _pyx_capi_int(X, "UAR_LONG") + assert long_typenum == dpt.dtype("l").num + ulong_typenum = _pyx_capi_int(X, "UAR_ULONG") + assert ulong_typenum == dpt.dtype("L").num + + longlong_typenum = _pyx_capi_int(X, "UAR_LONGLONG") + assert longlong_typenum == dpt.dtype(np.longlong).num + ulonglong_typenum = _pyx_capi_int(X, "UAR_ULONGLONG") + assert ulonglong_typenum == dpt.dtype(np.ulonglong).num + + half_typenum = _pyx_capi_int(X, "UAR_HALF") + assert half_typenum == dpt.dtype(np.half).num + float_typenum = _pyx_capi_int(X, "UAR_FLOAT") + assert float_typenum == dpt.dtype(np.single).num + double_typenum = _pyx_capi_int(X, "UAR_DOUBLE") + assert double_typenum == dpt.dtype(np.double).num + + cfloat_typenum = _pyx_capi_int(X, "UAR_CFLOAT") + assert cfloat_typenum == dpt.dtype(np.csingle).num + cdouble_typenum = _pyx_capi_int(X, "UAR_CDOUBLE") + assert cdouble_typenum == dpt.dtype(np.cdouble).num + + @pytest.mark.parametrize("dt1", _all_dtypes) @pytest.mark.parametrize("dt2", _all_dtypes) def test_usm_ndarray_from_zero_sized_usm_ndarray(dt1, dt2): @@ -298,7 +846,7 @@ def test_properties(dt): V.mT -@pytest.mark.parametrize("shape", [tuple(), (1,), (1, 1), (1, 1, 1)]) +@pytest.mark.parametrize("shape", [(), (1,), (1, 1), (1, 1, 1)]) @pytest.mark.parametrize("dtype", ["|b1", "|u2", "|f4", "|i8"]) class TestCopyScalar: @pytest.mark.parametrize("func", [bool, float, int, complex]) @@ -361,7 +909,7 @@ def test_index_noninteger(): @pytest.mark.parametrize( "ind", [ - tuple(), + (), (None,), ( None, @@ -671,7 +1219,7 @@ def test_pyx_capi_check_constants(): @pytest.mark.parametrize( - "shape", [tuple(), (1,), (5,), (2, 3), (2, 3, 4), (2, 2, 2, 2, 2)] + "shape", [(), (1,), (5,), (2, 3), (2, 3, 4), (2, 2, 2, 2, 2)] ) @pytest.mark.parametrize( "dtype", @@ -791,7 +1339,7 @@ def test_setitem_broadcasting_empty_dst_edge_case(): broadasting rule, hence no exception""" get_queue_or_skip() dst = dpt.ones(1, dtype="i8")[0:0] - src = dpt.ones(tuple(), dtype="i8") + src = dpt.ones((), dtype="i8") dst[...] = src @@ -963,7 +1511,7 @@ def test_len(): assert len(X) == 1 X = dpt.usm_ndarray((2, 1), "i4") assert len(X) == 2 - X = dpt.usm_ndarray(tuple(), "i4") + X = dpt.usm_ndarray((), "i4") with pytest.raises(TypeError): len(X) @@ -1353,19 +1901,19 @@ def test_full_cmplx128(): dtype = "c16" skip_if_dtype_not_supported(dtype, q) fill_v = 1 + 1j - X = dpt.full(tuple(), fill_value=fill_v, dtype=dtype, sycl_queue=q) + X = dpt.full((), fill_value=fill_v, dtype=dtype, sycl_queue=q) assert np.array_equal( - dpt.asnumpy(X), np.full(tuple(), fill_value=fill_v, dtype=dtype) + dpt.asnumpy(X), np.full((), fill_value=fill_v, dtype=dtype) ) fill_v = 0 + 1j - X = dpt.full(tuple(), fill_value=fill_v, dtype=dtype, sycl_queue=q) + X = dpt.full((), fill_value=fill_v, dtype=dtype, sycl_queue=q) assert np.array_equal( - dpt.asnumpy(X), np.full(tuple(), fill_value=fill_v, dtype=dtype) + dpt.asnumpy(X), np.full((), fill_value=fill_v, dtype=dtype) ) fill_v = 0 + 0j - X = dpt.full(tuple(), fill_value=fill_v, dtype=dtype, sycl_queue=q) + X = dpt.full((), fill_value=fill_v, dtype=dtype, sycl_queue=q) assert np.array_equal( - dpt.asnumpy(X), np.full(tuple(), fill_value=fill_v, dtype=dtype) + dpt.asnumpy(X), np.full((), fill_value=fill_v, dtype=dtype) ) @@ -1640,7 +2188,7 @@ def test_empty_like(dt, usm_kind): assert X.usm_type == Y.usm_type assert X.sycl_queue == Y.sycl_queue - X = dpt.empty(tuple(), dtype=dt, usm_type=usm_kind, sycl_queue=q) + X = dpt.empty((), dtype=dt, usm_type=usm_kind, sycl_queue=q) Y = dpt.empty_like(X) assert X.shape == Y.shape assert X.dtype == Y.dtype @@ -1680,7 +2228,7 @@ def test_zeros_like(dt, usm_kind): assert X.sycl_queue == Y.sycl_queue assert np.allclose(dpt.asnumpy(Y), np.zeros(X.shape, dtype=X.dtype)) - X = dpt.empty(tuple(), dtype=dt, usm_type=usm_kind, sycl_queue=q) + X = dpt.empty((), dtype=dt, usm_type=usm_kind, sycl_queue=q) Y = dpt.zeros_like(X) assert X.shape == Y.shape assert X.dtype == Y.dtype @@ -1713,7 +2261,7 @@ def test_ones_like(dt, usm_kind): assert X.sycl_queue == Y.sycl_queue assert np.allclose(dpt.asnumpy(Y), np.ones(X.shape, dtype=X.dtype)) - X = dpt.empty(tuple(), dtype=dt, usm_type=usm_kind, sycl_queue=q) + X = dpt.empty((), dtype=dt, usm_type=usm_kind, sycl_queue=q) Y = dpt.ones_like(X) assert X.shape == Y.shape assert X.dtype == Y.dtype @@ -1747,7 +2295,7 @@ def test_full_like(dt, usm_kind): assert X.sycl_queue == Y.sycl_queue assert np.allclose(dpt.asnumpy(Y), np.ones(X.shape, dtype=X.dtype)) - X = dpt.empty(tuple(), dtype=dt, usm_type=usm_kind, sycl_queue=q) + X = dpt.empty((), dtype=dt, usm_type=usm_kind, sycl_queue=q) Y = dpt.full_like(X, fill_v) assert X.shape == Y.shape assert X.dtype == Y.dtype @@ -2003,7 +2551,7 @@ def test_common_arg_validation(): def test_flags(): try: - x = dpt.empty(tuple(), dtype="i4") + x = dpt.empty((), dtype="i4") except dpctl.SyclDeviceCreationError: pytest.skip("No SYCL devices available") f = x.flags