From 03450eb880af8c26610c774373cf635c1d4e521c Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Tue, 14 Feb 2023 15:37:12 +0100 Subject: [PATCH 1/3] Add dlpack support with tests and docstrings --- dpnp/dpnp_array.py | 10 ++++++++-- dpnp/dpnp_iface.py | 25 +++++++++++++++++++++++++ tests/test_dparray.py | 19 +++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index 473e08c83fc8..7bdb5a7560f6 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -140,7 +140,7 @@ def __bool__(self): return self._array_obj.__bool__() # '__class__', - + def __complex__(self): return self._array_obj.__complex__() @@ -153,6 +153,12 @@ def __complex__(self): # '__divmod__', # '__doc__', + def __dlpack__(self, stream=None): + return self._array_obj.__dlpack__(stream=stream) + + def __dlpack_device__(self): + return self._array_obj.__dlpack_device__() + def __eq__(self, other): return dpnp.equal(self, other) @@ -190,7 +196,7 @@ def __gt__(self, other): # '__imatmul__', # '__imod__', # '__imul__', - + def __index__(self): return self._array_obj.__index__() diff --git a/dpnp/dpnp_iface.py b/dpnp/dpnp_iface.py index 1c60d1c999e1..019f2324f3e0 100644 --- a/dpnp/dpnp_iface.py +++ b/dpnp/dpnp_iface.py @@ -64,6 +64,7 @@ "default_float_type", "dpnp_queue_initialize", "dpnp_queue_is_cpu", + "from_dlpack", "get_dpnp_descriptor", "get_include", "get_normalized_queue_device" @@ -222,6 +223,30 @@ def default_float_type(device=None, sycl_queue=None): return map_dtype_to_device(float64, _sycl_queue.sycl_device) +def from_dlpack(obj): + """ + Create a dpnp array from a Python object implementing the ``__dlpack__`` + protocol. + + See https://dmlc.github.io/dlpack/latest/ for more details. + + Parameters + ---------- + obj : A Python object representing an array that implements the ``__dlpack__`` + and ``__dlpack_device__`` methods. + + Returns + ------- + array : dpnp_array + + """ + + usm_ary = dpt.from_dlpack(obj) + dpnp_ary = dpnp_array.__new__(dpnp_array) + dpnp_ary._array_obj = usm_ary + return dpnp_ary + + def get_dpnp_descriptor(ext_obj, copy_when_strides=True, copy_when_nondefault_queue=True, diff --git a/tests/test_dparray.py b/tests/test_dparray.py index 62a0120f8a33..c49db4da1aed 100644 --- a/tests/test_dparray.py +++ b/tests/test_dparray.py @@ -23,6 +23,25 @@ def test_astype(arr, arr_dtype, res_dtype): assert_array_equal(expected, result) +@pytest.mark.parametrize("arr_dtype", get_all_dtypes()) +@pytest.mark.parametrize("shape", [tuple(), (2,), (3, 0, 1), (2, 2, 2)]) +def test_from_dlpack(arr_dtype,shape): + X = dpnp.empty(shape=shape,dtype=arr_dtype) + Y = dpnp.from_dlpack(X) + assert_array_equal(X, Y) + assert X.__dlpack_device__() == Y.__dlpack_device__() + assert X.shape == Y.shape + assert X.dtype == Y.dtype or ( + str(X.dtype) == "bool" and str(Y.dtype) == "uint8" + ) + assert X.sycl_device == Y.sycl_device + assert X.usm_type == Y.usm_type + if Y.ndim: + V = Y[::-1] + W = dpnp.from_dlpack(V) + assert V.strides == W.strides + + @pytest.mark.parametrize("arr_dtype", get_all_dtypes()) @pytest.mark.parametrize("arr", [[-2, -1, 0, 1, 2], [[-2, -1], [1, 2]], []], From 75695ce81b3f09a5c0e4dad0a8902f9872bd908f Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Tue, 14 Feb 2023 16:27:28 +0100 Subject: [PATCH 2/3] Add a test for dlpack with dpt --- tests/test_dparray.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_dparray.py b/tests/test_dparray.py index c49db4da1aed..8c40631c930c 100644 --- a/tests/test_dparray.py +++ b/tests/test_dparray.py @@ -41,6 +41,20 @@ def test_from_dlpack(arr_dtype,shape): W = dpnp.from_dlpack(V) assert V.strides == W.strides +@pytest.mark.parametrize("arr_dtype", get_all_dtypes()) +def test_from_dlpack_with_dpt(arr_dtype): + X = dpt.empty((64,),dtype=arr_dtype) + Y = dpnp.from_dlpack(X) + assert_array_equal(X, Y) + assert isinstance(Y, dpnp.dpnp_array.dpnp_array) + assert X.__dlpack_device__() == Y.__dlpack_device__() + assert X.shape == Y.shape + assert X.dtype == Y.dtype or ( + str(X.dtype) == "bool" and str(Y.dtype) == "uint8" + ) + assert X.sycl_device == Y.sycl_device + assert X.usm_type == Y.usm_type + @pytest.mark.parametrize("arr_dtype", get_all_dtypes()) @pytest.mark.parametrize("arr", From 4c158daa0738043c7addf4a57855c0685124c9a7 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Wed, 15 Feb 2023 19:44:47 +0100 Subject: [PATCH 3/3] Fix remarks, add _create_from_usm_ndarray func and move tests to test_sycl_queue --- dpnp/dpnp_array.py | 10 +++++++++ dpnp/dpnp_iface.py | 13 ++++++------ tests/helper.py | 2 +- tests/test_dparray.py | 33 ----------------------------- tests/test_sycl_queue.py | 45 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 61 insertions(+), 42 deletions(-) diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index 7bdb5a7560f6..8e284ca590b2 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -319,6 +319,16 @@ def __truediv__(self, other): # '__xor__', + @staticmethod + def _create_from_usm_ndarray(usm_ary : dpt.usm_ndarray): + if not isinstance(usm_ary, dpt.usm_ndarray): + raise TypeError( + f"Expected dpctl.tensor.usm_ndarray, got {type(usm_ary)}" + ) + res = dpnp_array.__new__(dpnp_array) + res._array_obj = usm_ary + return res + def all(self, axis=None, out=None, keepdims=False): """ Returns True if all elements evaluate to True. diff --git a/dpnp/dpnp_iface.py b/dpnp/dpnp_iface.py index 019f2324f3e0..b7cdef8cc615 100644 --- a/dpnp/dpnp_iface.py +++ b/dpnp/dpnp_iface.py @@ -223,7 +223,7 @@ def default_float_type(device=None, sycl_queue=None): return map_dtype_to_device(float64, _sycl_queue.sycl_device) -def from_dlpack(obj): +def from_dlpack(obj, /): """ Create a dpnp array from a Python object implementing the ``__dlpack__`` protocol. @@ -232,19 +232,20 @@ def from_dlpack(obj): Parameters ---------- - obj : A Python object representing an array that implements the ``__dlpack__`` + obj : object + A Python object representing an array that implements the ``__dlpack__`` and ``__dlpack_device__`` methods. Returns ------- - array : dpnp_array + out : dpnp_array + Returns a new dpnp array containing the data from another array + (obj) with the ``__dlpack__`` method on the same device as object. """ usm_ary = dpt.from_dlpack(obj) - dpnp_ary = dpnp_array.__new__(dpnp_array) - dpnp_ary._array_obj = usm_ary - return dpnp_ary + return dpnp_array._create_from_usm_ndarray(usm_ary) def get_dpnp_descriptor(ext_obj, diff --git a/tests/helper.py b/tests/helper.py index be550a995dce..17c62cecd289 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -32,7 +32,7 @@ def get_all_dtypes(no_bool=False, dtypes.append(dpnp.complex64) if dev.has_aspect_fp64: dtypes.append(dpnp.complex128) - + # add None value to validate a default dtype if not no_none: dtypes.append(None) diff --git a/tests/test_dparray.py b/tests/test_dparray.py index 8c40631c930c..62a0120f8a33 100644 --- a/tests/test_dparray.py +++ b/tests/test_dparray.py @@ -23,39 +23,6 @@ def test_astype(arr, arr_dtype, res_dtype): assert_array_equal(expected, result) -@pytest.mark.parametrize("arr_dtype", get_all_dtypes()) -@pytest.mark.parametrize("shape", [tuple(), (2,), (3, 0, 1), (2, 2, 2)]) -def test_from_dlpack(arr_dtype,shape): - X = dpnp.empty(shape=shape,dtype=arr_dtype) - Y = dpnp.from_dlpack(X) - assert_array_equal(X, Y) - assert X.__dlpack_device__() == Y.__dlpack_device__() - assert X.shape == Y.shape - assert X.dtype == Y.dtype or ( - str(X.dtype) == "bool" and str(Y.dtype) == "uint8" - ) - assert X.sycl_device == Y.sycl_device - assert X.usm_type == Y.usm_type - if Y.ndim: - V = Y[::-1] - W = dpnp.from_dlpack(V) - assert V.strides == W.strides - -@pytest.mark.parametrize("arr_dtype", get_all_dtypes()) -def test_from_dlpack_with_dpt(arr_dtype): - X = dpt.empty((64,),dtype=arr_dtype) - Y = dpnp.from_dlpack(X) - assert_array_equal(X, Y) - assert isinstance(Y, dpnp.dpnp_array.dpnp_array) - assert X.__dlpack_device__() == Y.__dlpack_device__() - assert X.shape == Y.shape - assert X.dtype == Y.dtype or ( - str(X.dtype) == "bool" and str(Y.dtype) == "uint8" - ) - assert X.sycl_device == Y.sycl_device - assert X.usm_type == Y.usm_type - - @pytest.mark.parametrize("arr_dtype", get_all_dtypes()) @pytest.mark.parametrize("arr", [[-2, -1, 0, 1, 2], [[-2, -1], [1, 2]], []], diff --git a/tests/test_sycl_queue.py b/tests/test_sycl_queue.py index bc42f70b3700..1a1c2a85f2e8 100644 --- a/tests/test_sycl_queue.py +++ b/tests/test_sycl_queue.py @@ -1,9 +1,14 @@ import pytest +from .helper import get_all_dtypes import dpnp import dpctl import numpy +from numpy.testing import ( + assert_array_equal +) + list_of_backend_str = [ "host", @@ -155,7 +160,7 @@ def test_array_creation_like(func, kwargs, device_x, device_y): dpnp_kwargs = dict(kwargs) dpnp_kwargs['device'] = device_y - + y = getattr(dpnp, func)(x, **dpnp_kwargs) numpy.testing.assert_array_equal(y_orig, y) assert_sycl_queue_equal(y.sycl_queue, x.to_device(device_y).sycl_queue) @@ -637,7 +642,7 @@ def test_eig(device): dpnp_val_queue = dpnp_val.get_array().sycl_queue dpnp_vec_queue = dpnp_vec.get_array().sycl_queue - # compare queue and device + # compare queue and device assert_sycl_queue_equal(dpnp_val_queue, expected_queue) assert_sycl_queue_equal(dpnp_vec_queue, expected_queue) @@ -806,3 +811,39 @@ def test_array_copy(device, func, device_param, queue_param): result = dpnp.array(dpnp_data, **kwargs) assert_sycl_queue_equal(result.sycl_queue, dpnp_data.sycl_queue) + + +@pytest.mark.parametrize("device", + valid_devices, + ids=[device.filter_string for device in valid_devices]) +#TODO need to delete no_bool=True when use dlpack > 0.7 version +@pytest.mark.parametrize("arr_dtype", get_all_dtypes(no_float16=True, no_bool=True)) +@pytest.mark.parametrize("shape", [tuple(), (2,), (3, 0, 1), (2, 2, 2)]) +def test_from_dlpack(arr_dtype, shape, device): + X = dpnp.empty(shape=shape, dtype=arr_dtype, device=device) + Y = dpnp.from_dlpack(X) + assert_array_equal(X, Y) + assert X.__dlpack_device__() == Y.__dlpack_device__() + assert X.sycl_device == Y.sycl_device + assert X.sycl_context == Y.sycl_context + assert X.usm_type == Y.usm_type + if Y.ndim: + V = Y[::-1] + W = dpnp.from_dlpack(V) + assert V.strides == W.strides + + +@pytest.mark.parametrize("device", + valid_devices, + ids=[device.filter_string for device in valid_devices]) +#TODO need to delete no_bool=True when use dlpack > 0.7 version +@pytest.mark.parametrize("arr_dtype", get_all_dtypes(no_float16=True, no_bool=True)) +def test_from_dlpack_with_dpt(arr_dtype, device): + X = dpctl.tensor.empty((64,), dtype=arr_dtype, device=device) + Y = dpnp.from_dlpack(X) + assert_array_equal(X, Y) + assert isinstance(Y, dpnp.dpnp_array.dpnp_array) + assert X.__dlpack_device__() == Y.__dlpack_device__() + assert X.sycl_device == Y.sycl_device + assert X.sycl_context == Y.sycl_context + assert X.usm_type == Y.usm_type