diff --git a/dpctl/tensor/__init__.py b/dpctl/tensor/__init__.py index ab2bf72ebb..944c80fa47 100644 --- a/dpctl/tensor/__init__.py +++ b/dpctl/tensor/__init__.py @@ -29,16 +29,15 @@ """ -from dpctl.tensor._copy_utils import astype, copy -from dpctl.tensor._copy_utils import copy_from_numpy as from_numpy -from dpctl.tensor._copy_utils import copy_to_numpy as asnumpy -from dpctl.tensor._copy_utils import copy_to_numpy as to_numpy +from dpctl.tensor._copy_utils import asnumpy, astype, copy, from_numpy, to_numpy from dpctl.tensor._ctors import asarray, empty +from dpctl.tensor._device import Device from dpctl.tensor._dlpack import from_dlpack from dpctl.tensor._reshape import reshape from dpctl.tensor._usmarray import usm_ndarray __all__ = [ + "Device", "usm_ndarray", "asarray", "astype", diff --git a/dpctl/tensor/_copy_utils.py b/dpctl/tensor/_copy_utils.py index 9422210a6d..a158912d59 100644 --- a/dpctl/tensor/_copy_utils.py +++ b/dpctl/tensor/_copy_utils.py @@ -19,6 +19,7 @@ import dpctl.memory as dpm import dpctl.tensor as dpt +from dpctl.tensor._device import normalize_queue_device def contract_iter2(shape, strides1, strides2): @@ -64,7 +65,7 @@ def contract_iter2(shape, strides1, strides2): return (sh, st1, disp1, st2, disp2) -def has_memory_overlap(x1, x2): +def _has_memory_overlap(x1, x2): m1 = dpm.as_usm_memory(x1) m2 = dpm.as_usm_memory(x2) if m1.sycl_device == m2.sycl_device: @@ -77,7 +78,7 @@ def has_memory_overlap(x1, x2): return False -def copy_to_numpy(ary): +def _copy_to_numpy(ary): if type(ary) is not dpt.usm_ndarray: raise TypeError h = ary.usm_data.copy_to_host().view(ary.dtype) @@ -93,7 +94,7 @@ def copy_to_numpy(ary): ) -def copy_from_numpy(np_ary, usm_type="device", sycl_queue=None): +def _copy_from_numpy(np_ary, usm_type="device", sycl_queue=None): "Copies numpy array `np_ary` into a new usm_ndarray" # This may peform a copy to meet stated requirements Xnp = np.require(np_ary, requirements=["A", "O", "C", "E"]) @@ -111,7 +112,8 @@ def copy_from_numpy(np_ary, usm_type="device", sycl_queue=None): return Xusm -def copy_from_numpy_into(dst, np_ary): +def _copy_from_numpy_into(dst, np_ary): + "Copies `np_ary` into `dst` of type :class:`dpctl.tensor.usm_ndarray" if not isinstance(np_ary, np.ndarray): raise TypeError("Expected numpy.ndarray, got {}".format(type(np_ary))) src_ary = np.broadcast_to(np.asarray(np_ary, dtype=dst.dtype), dst.shape) @@ -122,6 +124,54 @@ def copy_from_numpy_into(dst, np_ary): usm_mem.copy_from_host(host_buf) +def from_numpy(np_ary, device=None, usm_type="device", sycl_queue=None): + """ + from_numpy(arg, device=None, usm_type="device", sycl_queue=None) + + Creates :class:`dpctl.tensor.usm_ndarray` from instance of + `numpy.ndarray`. + + Args: + arg: An instance of `numpy.ndarray` + device: array API specification of device where the output array + is created. + sycl_queue: a :class:`dpctl.SyclQueue` used to create the output + array is created + """ + q = normalize_queue_device(sycl_queue=sycl_queue, device=device) + return _copy_from_numpy(np_ary, usm_type=usm_type, sycl_queue=q) + + +def to_numpy(usm_ary): + """ + to_numpy(usm_ary) + + Copies content of :class:`dpctl.tensor.usm_ndarray` instance `usm_ary` + into `numpy.ndarray` instance of the same shape and same data type. + + Args: + usm_ary: An instance of :class:`dpctl.tensor.usm_ndarray` + Returns: + An instance of `numpy.ndarray` populated with content of `usm_ary`. + """ + return _copy_to_numpy(usm_ary) + + +def asnumpy(usm_ary): + """ + asnumpy(usm_ary) + + Copies content of :class:`dpctl.tensor.usm_ndarray` instance `usm_ary` + into `numpy.ndarray` instance of the same shape and same data type. + + Args: + usm_ary: An instance of :class:`dpctl.tensor.usm_ndarray` + Returns: + An instance of `numpy.ndarray` populated with content of `usm_ary`. + """ + return _copy_to_numpy(usm_ary) + + class Dummy: def __init__(self, iface): self.__sycl_usm_array_interface__ = iface @@ -138,9 +188,9 @@ def copy_same_dtype(dst, src): raise ValueError # check that memory regions do not overlap - if has_memory_overlap(dst, src): - tmp = copy_to_numpy(src) - copy_from_numpy_into(dst, tmp) + if _has_memory_overlap(dst, src): + tmp = _copy_to_numpy(src) + _copy_from_numpy_into(dst, tmp) return if (dst.flags & 1) and (src.flags & 1): @@ -184,10 +234,10 @@ def copy_same_shape(dst, src): return # check that memory regions do not overlap - if has_memory_overlap(dst, src): - tmp = copy_to_numpy(src) + if _has_memory_overlap(dst, src): + tmp = _copy_to_numpy(src) tmp = tmp.astype(dst.dtype) - copy_from_numpy_into(dst, tmp) + _copy_from_numpy_into(dst, tmp) return # simplify strides @@ -218,7 +268,7 @@ def copy_same_shape(dst, src): mdst.copy_from_host(tmp.view("u1")) -def copy_from_usm_ndarray_to_usm_ndarray(dst, src): +def _copy_from_usm_ndarray_to_usm_ndarray(dst, src): if type(dst) is not dpt.usm_ndarray or type(src) is not dpt.usm_ndarray: raise TypeError @@ -389,7 +439,7 @@ def astype(usm_ary, newdtype, order="K", casting="unsafe", copy=True): buffer=R.usm_data, strides=new_strides, ) - copy_from_usm_ndarray_to_usm_ndarray(R, usm_ary) + _copy_from_usm_ndarray_to_usm_ndarray(R, usm_ary) return R else: return usm_ary diff --git a/dpctl/tensor/_ctors.py b/dpctl/tensor/_ctors.py index d0cce717b0..1499146e13 100644 --- a/dpctl/tensor/_ctors.py +++ b/dpctl/tensor/_ctors.py @@ -20,6 +20,7 @@ import dpctl.memory as dpm import dpctl.tensor as dpt import dpctl.utils +from dpctl.tensor._device import normalize_queue_device _empty_tuple = tuple() _host_set = frozenset([None]) @@ -72,29 +73,6 @@ def _array_info_sequence(li): return (n,) + dim, dt, device -def _normalize_queue_device(q=None, d=None): - if q is None: - d = dpt._device.Device.create_device(d) - return d.sycl_queue - else: - if not isinstance(q, dpctl.SyclQueue): - raise TypeError(f"Expected dpctl.SyclQueue, got {type(q)}") - if d is None: - return q - d = dpt._device.Device.create_device(d) - qq = dpctl.utils.get_execution_queue( - ( - q, - d.sycl_queue, - ) - ) - if qq is None: - raise TypeError( - "sycl_queue and device keywords can not be both specified" - ) - return qq - - def _asarray_from_usm_ndarray( usm_ndary, dtype=None, @@ -115,7 +93,7 @@ def _asarray_from_usm_ndarray( exec_q = dpctl.utils.get_execution_queue( [usm_ndary.sycl_queue, sycl_queue] ) - copy_q = _normalize_queue_device(q=sycl_queue, d=exec_q) + copy_q = normalize_queue_device(sycl_queue=sycl_queue, device=exec_q) else: copy_q = usm_ndary.sycl_queue # Conditions for zero copy: @@ -194,7 +172,7 @@ def _asarray_from_numpy_ndarray( usm_type = "device" if dtype is None: dtype = ary.dtype - copy_q = _normalize_queue_device(q=None, d=sycl_queue) + copy_q = normalize_queue_device(sycl_queue=None, device=sycl_queue) f_contig = ary.flags["F"] c_contig = ary.flags["C"] fc_contig = f_contig or c_contig @@ -327,7 +305,9 @@ def asarray( ) # 5. Normalize device/sycl_queue [keep it None if was None] if device is not None or sycl_queue is not None: - sycl_queue = _normalize_queue_device(q=sycl_queue, d=device) + sycl_queue = normalize_queue_device( + sycl_queue=sycl_queue, device=device + ) # handle instance(obj, usm_ndarray) if isinstance(obj, dpt.usm_ndarray): @@ -459,7 +439,7 @@ def empty( raise TypeError( f"Expected usm_type to be of type str, got {type(usm_type)}" ) - sycl_queue = _normalize_queue_device(q=sycl_queue, d=device) + sycl_queue = normalize_queue_device(sycl_queue=sycl_queue, device=device) res = dpt.usm_ndarray( sh, dtype=dtype, diff --git a/dpctl/tensor/_device.py b/dpctl/tensor/_device.py index 7959735b4e..94564238e8 100644 --- a/dpctl/tensor/_device.py +++ b/dpctl/tensor/_device.py @@ -105,3 +105,54 @@ def __repr__(self): except TypeError: # This is a sub-device return repr(self.sycl_queue) + + +def normalize_queue_device(sycl_queue=None, device=None): + """ + normalize_queue_device(sycl_queue=None, device=None) + + Utility to process exclusive keyword arguments 'device' + and 'sycl_queue' in functions of `dpctl.tensor`. + + Args: + sycl_queue(:class:`dpctl.SyclQueue`, optional): + explicitly indicates where USM allocation is done + and the population code (if any) is executed. + Value `None` is interpreted as get the SYCL queue + from `device` keyword, or use default queue. + Default: None + device (string, :class:`dpctl.SyclDevice`, :class:`dpctl.SyclQueue, + :class:`dpctl.tensor.Device`, optional): + array-API keyword indicating non-partitioned SYCL device + where array is allocated. + Returns + :class:`dpctl.SyclQueue` object implied by either of provided + keywords. If both are None, `dpctl.SyclQueue()` is returned. + If both are specified and imply the same queue, `sycl_queue` + is returned. + Raises: + TypeError: if argument is not of the expected type, or keywords + imply incompatible queues. + """ + q = sycl_queue + d = device + if q is None: + d = Device.create_device(d) + return d.sycl_queue + else: + if not isinstance(q, dpctl.SyclQueue): + raise TypeError(f"Expected dpctl.SyclQueue, got {type(q)}") + if d is None: + return q + d = Device.create_device(d) + qq = dpctl.utils.get_execution_queue( + ( + q, + d.sycl_queue, + ) + ) + if qq is None: + raise TypeError( + "sycl_queue and device keywords can not be both specified" + ) + return qq diff --git a/dpctl/tensor/_usmarray.pyx b/dpctl/tensor/_usmarray.pyx index 75131fd916..0b9a02a29b 100644 --- a/dpctl/tensor/_usmarray.pyx +++ b/dpctl/tensor/_usmarray.pyx @@ -884,13 +884,13 @@ cdef class usm_ndarray: except (ValueError, IndexError) as e: raise e from ._copy_utils import ( - copy_from_numpy_into, - copy_from_usm_ndarray_to_usm_ndarray, + _copy_from_numpy_into, + _copy_from_usm_ndarray_to_usm_ndarray, ) if isinstance(val, usm_ndarray): - copy_from_usm_ndarray_to_usm_ndarray(Xv, val) + _copy_from_usm_ndarray_to_usm_ndarray(Xv, val) else: - copy_from_numpy_into(Xv, np.asarray(val)) + _copy_from_numpy_into(Xv, np.asarray(val)) def __sub__(first, other): "See comment in __add__" diff --git a/dpctl/tests/test_usm_ndarray_ctor.py b/dpctl/tests/test_usm_ndarray_ctor.py index b4d2ed7872..e6cee4a3a9 100644 --- a/dpctl/tests/test_usm_ndarray_ctor.py +++ b/dpctl/tests/test_usm_ndarray_ctor.py @@ -18,13 +18,12 @@ import numbers import numpy as np -import numpy.lib.stride_tricks as np_st import pytest import dpctl import dpctl.memory as dpm import dpctl.tensor as dpt -from dpctl.tensor._usmarray import Device +from dpctl.tensor import Device @pytest.mark.parametrize( @@ -125,6 +124,7 @@ def test_properties(dt): assert isinstance(X.nbytes, numbers.Integral) assert isinstance(X.ndim, numbers.Integral) assert isinstance(X._pointer, numbers.Integral) + assert isinstance(X.device, Device) @pytest.mark.parametrize("func", [bool, float, int, complex]) @@ -198,49 +198,10 @@ def test_basic_slice(ind): assert S.dtype == X.dtype -def _from_numpy(np_ary, device=None, usm_type="shared"): - if type(np_ary) is np.ndarray: - if np_ary.flags["FORC"]: - x = np_ary - else: - x = np.ascontiguous(np_ary) - R = dpt.usm_ndarray( - np_ary.shape, - dtype=np_ary.dtype, - buffer=usm_type, - buffer_ctor_kwargs={ - "queue": Device.create_device(device).sycl_queue - }, - ) - R.usm_data.copy_from_host(x.reshape((-1)).view("|u1")) - return R - else: - raise ValueError("Expected numpy.ndarray, got {}".format(type(np_ary))) - - -def _to_numpy(usm_ary): - if type(usm_ary) is dpt.usm_ndarray: - usm_buf = usm_ary.usm_data - s = usm_buf.nbytes - host_buf = usm_buf.copy_to_host().view(usm_ary.dtype) - usm_ary_itemsize = usm_ary.itemsize - R_offset = ( - usm_ary.__sycl_usm_array_interface__["offset"] * usm_ary_itemsize - ) - R = np.ndarray((s,), dtype="u1", buffer=host_buf) - R = R[R_offset:].view(usm_ary.dtype) - R_strides = (usm_ary_itemsize * si for si in usm_ary.strides) - return np_st.as_strided(R, shape=usm_ary.shape, strides=R_strides) - else: - raise ValueError( - "Expected dpctl.tensor.usm_ndarray, got {}".format(type(usm_ary)) - ) - - def test_slice_constructor_1d(): Xh = np.arange(37, dtype="i4") default_device = dpctl.select_default_device() - Xusm = _from_numpy(Xh, device=default_device, usm_type="device") + Xusm = dpt.from_numpy(Xh, device=default_device, usm_type="device") for ind in [ slice(1, None, 2), slice(0, None, 3), @@ -252,14 +213,14 @@ def test_slice_constructor_1d(): slice(None, None, -13), ]: assert np.array_equal( - _to_numpy(Xusm[ind]), Xh[ind] + dpt.asnumpy(Xusm[ind]), Xh[ind] ), "Failed for {}".format(ind) def test_slice_constructor_3d(): Xh = np.empty((37, 24, 35), dtype="i4") default_device = dpctl.select_default_device() - Xusm = _from_numpy(Xh, device=default_device, usm_type="device") + Xusm = dpt.from_numpy(Xh, device=default_device, usm_type="device") for ind in [ slice(1, None, 2), slice(0, None, 3), @@ -272,7 +233,7 @@ def test_slice_constructor_3d(): (slice(None, None, -2), Ellipsis, None, 15), ]: assert np.array_equal( - _to_numpy(Xusm[ind]), Xh[ind] + dpt.to_numpy(Xusm[ind]), Xh[ind] ), "Failed for {}".format(ind) @@ -280,7 +241,7 @@ def test_slice_constructor_3d(): def test_slice_suai(usm_type): Xh = np.arange(0, 10, dtype="u1") default_device = dpctl.select_default_device() - Xusm = _from_numpy(Xh, device=default_device, usm_type=usm_type) + Xusm = dpt.from_numpy(Xh, device=default_device, usm_type=usm_type) for ind in [slice(2, 3, None), slice(5, 7, None), slice(3, 9, None)]: assert np.array_equal( dpm.as_usm_memory(Xusm[ind]).copy_to_host(), Xh[ind] @@ -786,13 +747,13 @@ def test_to_device(): def test_astype(): - X = dpt.usm_ndarray((5, 5), "i4") + X = dpt.empty((5, 5), dtype="i4") X[:] = np.full((5, 5), 7, dtype="i4") Y = dpt.astype(X, "c16", order="C") assert np.allclose(dpt.to_numpy(Y), np.full((5, 5), 7, dtype="c16")) - Y = dpt.astype(X, "f2", order="K") - assert np.allclose(dpt.to_numpy(Y), np.full((5, 5), 7, dtype="f2")) - Y = dpt.astype(X, "i4", order="K", copy=False) + Y = dpt.astype(X[::2, ::-1], "f2", order="K") + assert np.allclose(dpt.to_numpy(Y), np.full(Y.shape, 7, dtype="f2")) + Y = dpt.astype(X[::2, ::-1], "i4", order="K", copy=False) assert Y.usm_data is X.usm_data