From cde6795417734b940ec2a365881d852e0a4daaf8 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 15 Jan 2025 13:48:12 +0100 Subject: [PATCH 1/6] Add stream keyword --- dpnp/dpnp_array.py | 49 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index af0cf753c1dc..5227d0e8433e 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -153,13 +153,6 @@ def mT(self): return dpnp_array._create_from_usm_ndarray(self._array_obj.mT) - def to_device(self, target_device): - """Transfer array to target device.""" - - return dpnp_array( - shape=self.shape, buffer=self.get_array().to_device(target_device) - ) - @property def sycl_queue(self): return self._array_obj.sycl_queue @@ -1693,6 +1686,48 @@ def take(self, indices, axis=None, out=None, mode="wrap"): return dpnp.take(self, indices, axis=axis, out=out, mode=mode) + def to_device(self, device, /, *, stream=None): + """ + Transfers this array to specified target device. + + Parameters + ---------- + device : {string, SyclDevice, SyclQueue} + Array API concept of target device. It can be an OneAPI filter + selector string, an instance of :class:`dpctl.SyclDevice` + corresponding to a non-partitioned SYCL device, an instance of + :class:`dpctl.SyclQueue`, or a :class:`dpctl.tensor.Device` object + returned by :obj:`dpnp.dpnp_array.dpnp_array.device` property. + stream : SyclQueue, optional + Execution queue to synchronize with. If ``None``, synchronization + is not performed. + Default: ``None``. + + Returns + ------- + out : dpnp.ndarray + A view if data copy is not required, and a copy otherwise. + If copying is required, it is done by copying from the original + allocation device to the host, followed by copying from host + to the target device. + + Examples + -------- + >>> import dpnp as np, dpctl + >>> x = np.full(100, 2, dtype=np.int64) + >>> q_prof = dpctl.SyclQueue(x.sycl_device, property="enable_profiling") + >>> # return a view with profile-enabled queue + >>> y = x.to_device(q_prof) + >>> timer = dpctl.SyclTimer() + >>> with timer(q_prof): + ... z = y * y + >>> print(timer.dt) + + """ + + usm_res = self._array_obj.to_device(device, stream=stream) + return dpnp_array._create_from_usm_ndarray(usm_res) + # 'tobytes', # 'tofile', # 'tolist', From b950e842c72dd1ee0a47e3b83c66fbbbc935d149 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 15 Jan 2025 13:50:54 +0100 Subject: [PATCH 2/6] Add test coverage --- dpnp/tests/test_sycl_queue.py | 58 ++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/dpnp/tests/test_sycl_queue.py b/dpnp/tests/test_sycl_queue.py index 9da87c3db865..711149aae191 100644 --- a/dpnp/tests/test_sycl_queue.py +++ b/dpnp/tests/test_sycl_queue.py @@ -1932,23 +1932,51 @@ def test_svd(shape, full_matrices, compute_uv, device): assert_sycl_queue_equal(dpnp_s_queue, expected_queue) -@pytest.mark.parametrize( - "device_from", - valid_devices, - ids=[device.filter_string for device in valid_devices], -) -@pytest.mark.parametrize( - "device_to", - valid_devices, - ids=[device.filter_string for device in valid_devices], -) -def test_to_device(device_from, device_to): - data = [1.0, 1.0, 1.0, 1.0, 1.0] +class TestToDevice: + @pytest.mark.parametrize( + "device_from", + valid_devices, + ids=[device.filter_string for device in valid_devices], + ) + @pytest.mark.parametrize( + "device_to", + valid_devices, + ids=[device.filter_string for device in valid_devices], + ) + def test_basic(self, device_from, device_to): + data = [1.0, 1.0, 1.0, 1.0, 1.0] + x = dpnp.array(data, dtype=dpnp.float32, device=device_from) + + y = x.to_device(device_to) + assert y.sycl_device == device_to + + def test_to_queue(self): + x = dpnp.full(100, 2, dtype=dpnp.int64) + q_prof = dpctl.SyclQueue(x.sycl_device, property="enable_profiling") + + y = x.to_device(q_prof) + assert_sycl_queue_equal(y.sycl_queue, q_prof) + + def test_stream(self): + x = dpnp.full(100, 2, dtype=dpnp.int64) + q_prof = dpctl.SyclQueue(x.sycl_device, property="enable_profiling") + q_exec = dpctl.SyclQueue(x.sycl_device) + + y = x.to_device(q_prof, stream=q_exec) + assert_sycl_queue_equal(y.sycl_queue, q_prof) + + q_exec = dpctl.SyclQueue(x.sycl_device) + _ = dpnp.linspace(0, 20, num=10**5, sycl_queue=q_exec) + y = x.to_device(q_prof, stream=q_exec) + assert_sycl_queue_equal(y.sycl_queue, q_prof) - x = dpnp.array(data, dtype=dpnp.float32, device=device_from) - y = x.to_device(device_to) + def test_stream_no_sync(self): + x = dpnp.full(100, 2, dtype=dpnp.int64) + q_prof = dpctl.SyclQueue(x.sycl_device, property="enable_profiling") - assert y.sycl_device == device_to + for stream in [None, 1, dpctl.SyclDevice(), x.sycl_queue]: + y = x.to_device(q_prof, stream=stream) + assert_sycl_queue_equal(y.sycl_queue, q_prof) @pytest.mark.parametrize( From de9d72af3541af556fed1badefecf7b649a9f647 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 15 Jan 2025 13:51:39 +0100 Subject: [PATCH 3/6] Enable muted array-api test --- .github/workflows/array-api-skips.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/array-api-skips.txt b/.github/workflows/array-api-skips.txt index c4f5bc5d71ed..102320283c9f 100644 --- a/.github/workflows/array-api-skips.txt +++ b/.github/workflows/array-api-skips.txt @@ -57,9 +57,6 @@ array_api_tests/test_sorting_functions.py::test_sort array_api_tests/test_signatures.py::test_func_signature[std] array_api_tests/test_signatures.py::test_func_signature[var] -# missing 'stream' keyword argument -array_api_tests/test_signatures.py::test_array_method_signature[to_device] - # wrong shape is returned array_api_tests/test_linalg.py::test_vecdot array_api_tests/test_linalg.py::test_linalg_vecdot From 0dfcfedc88f8dc7147bfc815ea55b70beb6b4ddd Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Fri, 17 Jan 2025 12:51:43 +0100 Subject: [PATCH 4/6] State None as a possible value of stream argument in docstrings --- dpnp/dpnp_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index 5227d0e8433e..ff28b0c42568 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -1698,7 +1698,7 @@ def to_device(self, device, /, *, stream=None): corresponding to a non-partitioned SYCL device, an instance of :class:`dpctl.SyclQueue`, or a :class:`dpctl.tensor.Device` object returned by :obj:`dpnp.dpnp_array.dpnp_array.device` property. - stream : SyclQueue, optional + stream : {SyclQueue, None}, optional Execution queue to synchronize with. If ``None``, synchronization is not performed. Default: ``None``. From 6a33f0d2d13c810d833d98dbfeaee9c8d96af4c3 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Fri, 17 Jan 2025 12:56:32 +0100 Subject: [PATCH 5/6] Update tests for dpnp.to_device --- dpnp/tests/test_sycl_queue.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/dpnp/tests/test_sycl_queue.py b/dpnp/tests/test_sycl_queue.py index 711149aae191..59f8b2418248 100644 --- a/dpnp/tests/test_sycl_queue.py +++ b/dpnp/tests/test_sycl_queue.py @@ -1949,12 +1949,14 @@ def test_basic(self, device_from, device_to): y = x.to_device(device_to) assert y.sycl_device == device_to + assert (x.asnumpy() == y.asnumpy()).all() def test_to_queue(self): x = dpnp.full(100, 2, dtype=dpnp.int64) q_prof = dpctl.SyclQueue(x.sycl_device, property="enable_profiling") y = x.to_device(q_prof) + assert (x.asnumpy() == y.asnumpy()).all() assert_sycl_queue_equal(y.sycl_queue, q_prof) def test_stream(self): @@ -1963,21 +1965,34 @@ def test_stream(self): q_exec = dpctl.SyclQueue(x.sycl_device) y = x.to_device(q_prof, stream=q_exec) + assert (x.asnumpy() == y.asnumpy()).all() assert_sycl_queue_equal(y.sycl_queue, q_prof) q_exec = dpctl.SyclQueue(x.sycl_device) _ = dpnp.linspace(0, 20, num=10**5, sycl_queue=q_exec) y = x.to_device(q_prof, stream=q_exec) + assert (x.asnumpy() == y.asnumpy()).all() assert_sycl_queue_equal(y.sycl_queue, q_prof) def test_stream_no_sync(self): x = dpnp.full(100, 2, dtype=dpnp.int64) q_prof = dpctl.SyclQueue(x.sycl_device, property="enable_profiling") - for stream in [None, 1, dpctl.SyclDevice(), x.sycl_queue]: + for stream in [None, x.sycl_queue]: y = x.to_device(q_prof, stream=stream) + assert (x.asnumpy() == y.asnumpy()).all() assert_sycl_queue_equal(y.sycl_queue, q_prof) + @pytest.mark.parametrize( + "stream", + [1, dict(), dpctl.SyclDevice()], + ids=["scalar", "dictionary", "device"], + ) + def test_invalid_stream(self, stream): + x = dpnp.ones(2, dtype=dpnp.int64) + q_prof = dpctl.SyclQueue(x.sycl_device, property="enable_profiling") + assert_raises(TypeError, x.to_device, q_prof, stream=stream) + @pytest.mark.parametrize( "device", From 5d8eaaaf66692250d72c59c99cabea10bfa09883 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Fri, 17 Jan 2025 12:58:14 +0100 Subject: [PATCH 6/6] Update impacted tests with dpnp.__dlpack__ --- dpnp/tests/test_dlpack.py | 14 ++++++++++++-- .../third_party/cupy/core_tests/test_dlpack.py | 8 +++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/dpnp/tests/test_dlpack.py b/dpnp/tests/test_dlpack.py index 57d3191e5081..5cebaec8e835 100644 --- a/dpnp/tests/test_dlpack.py +++ b/dpnp/tests/test_dlpack.py @@ -1,6 +1,7 @@ +import dpctl import numpy import pytest -from numpy.testing import assert_array_equal +from numpy.testing import assert_array_equal, assert_raises import dpnp @@ -10,11 +11,20 @@ class TestDLPack: - @pytest.mark.parametrize("stream", [None, 1]) + @pytest.mark.parametrize("stream", [None, dpctl.SyclQueue()]) def test_stream(self, stream): x = dpnp.arange(5) x.__dlpack__(stream=stream) + @pytest.mark.parametrize( + "stream", + [1, dict(), dpctl.SyclDevice()], + ids=["scalar", "dictionary", "device"], + ) + def test_invaid_stream(self, stream): + x = dpnp.arange(5) + assert_raises(TypeError, x.__dlpack__, stream=stream) + @pytest.mark.parametrize("copy", [True, None, False]) def test_copy(self, copy): x = dpnp.arange(5) diff --git a/dpnp/tests/third_party/cupy/core_tests/test_dlpack.py b/dpnp/tests/third_party/cupy/core_tests/test_dlpack.py index 82bd3336d89d..14b6a1d9937c 100644 --- a/dpnp/tests/third_party/cupy/core_tests/test_dlpack.py +++ b/dpnp/tests/third_party/cupy/core_tests/test_dlpack.py @@ -206,7 +206,13 @@ def test_stream(self): for src_s in [self._get_stream(s) for s in allowed_streams]: for dst_s in [self._get_stream(s) for s in allowed_streams]: orig_array = _gen_array(cupy.float32, alloc_q=src_s) - dltensor = orig_array.__dlpack__(stream=orig_array) + + q = dpctl.SyclQueue( + orig_array.sycl_context, + orig_array.sycl_device, + property="enable_profiling", + ) + dltensor = orig_array.__dlpack__(stream=q) out_array = dlp.from_dlpack_capsule(dltensor) out_array = cupy.from_dlpack(out_array, device=dst_s)