Skip to content

Commit 29239b6

Browse files
Implement asynchronous fill method using dpctl kernels (#2055)
* Enhance `dpnp_array.fill` method Leverages dpctl's strided fill and memset for setting contiguous memory to 0 * Fix missing disclaimer in dpnp_arraycreation.py * Import `dpnp_array` directly * Skip `test_fill_with_numpy_scalar_ndarray` New fill implementation does not permit NumPy array values, consistent with fill_diagonal * Add dependencies to zeros and full kernels in `dpnp_fill` * Remove redundant validation of first `dpnp_fill` argument * Improve `dpnp_fill` array/scalar path logic * Disallow inputs to `dpnp_fill` on separate queues * Adjust skip message for `test_fill_with_numpy_scalar_ndarray` * Tweak error messages in `dpnp_fill` * Add tests for new `fill` method * Update docstring for `fill` method * Fix pre-commit in cupy fill tests * Change `asarray` to `astype` in `dpnp_fill` NumPy arrays are no longer permitted and queue coercion does not occur in the `fill` method, so `astype` is sufficient * Expand TEST_SCOPE to include `test_fill.py` * Remove redundant check from `dpnp_fill` * Use `_cast_fill_val` private function from `dpctl.tensor._ctors` * Add tests per PR review by @antonwolfy * Improve validation of `val` for `fill` method * Add to permit NumPy bools as `dpnp_fill` scalar fill values * Use `dpnp.bool` in `dpnp_fill` and make `isinstance` check more efficient * Replace branching for `fill` scalar type with `_cast_fill_value` * Add additional tests for `fill` `test_fill_non_scalar` now checks for strings and `test_fill_bool` added to verify bools are properly cast to 1 --------- Co-authored-by: Anton <100830759+antonwolfy@users.noreply.github.com>
1 parent e236ad9 commit 29239b6

File tree

6 files changed

+201
-6
lines changed

6 files changed

+201
-6
lines changed

.github/workflows/conda-package.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ env:
2929
test_copy.py
3030
test_counting.py
3131
test_fft.py
32+
test_fill.py
3233
test_flat.py
3334
test_histogram.py
3435
test_indexing.py

dpnp/dpnp_algo/dpnp_arraycreation.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
# -*- coding: utf-8 -*-
2+
# *****************************************************************************
3+
# Copyright (c) 2016-2024, Intel Corporation
4+
# All rights reserved.
5+
#
6+
# Redistribution and use in source and binary forms, with or without
7+
# modification, are permitted provided that the following conditions are met:
8+
# - Redistributions of source code must retain the above copyright notice,
9+
# this list of conditions and the following disclaimer.
10+
# - Redistributions in binary form must reproduce the above copyright notice,
11+
# this list of conditions and the following disclaimer in the documentation
12+
# and/or other materials provided with the distribution.
13+
#
14+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24+
# THE POSSIBILITY OF SUCH DAMAGE.
25+
# *****************************************************************************
26+
127
import math
228
import operator
329

dpnp/dpnp_algo/dpnp_fill.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# -*- coding: utf-8 -*-
2+
# *****************************************************************************
3+
# Copyright (c) 2016-2024, Intel Corporation
4+
# All rights reserved.
5+
#
6+
# Redistribution and use in source and binary forms, with or without
7+
# modification, are permitted provided that the following conditions are met:
8+
# - Redistributions of source code must retain the above copyright notice,
9+
# this list of conditions and the following disclaimer.
10+
# - Redistributions in binary form must reproduce the above copyright notice,
11+
# this list of conditions and the following disclaimer in the documentation
12+
# and/or other materials provided with the distribution.
13+
#
14+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24+
# THE POSSIBILITY OF SUCH DAMAGE.
25+
# *****************************************************************************
26+
27+
from numbers import Number
28+
29+
import dpctl.tensor as dpt
30+
import dpctl.utils as dpu
31+
from dpctl.tensor._ctors import _cast_fill_val
32+
from dpctl.tensor._tensor_impl import (
33+
_copy_usm_ndarray_into_usm_ndarray,
34+
_full_usm_ndarray,
35+
_zeros_usm_ndarray,
36+
)
37+
38+
import dpnp
39+
40+
41+
def dpnp_fill(arr, val):
42+
arr = dpnp.get_usm_ndarray(arr)
43+
exec_q = arr.sycl_queue
44+
45+
# if val is an array, process it
46+
if dpnp.is_supported_array_type(val):
47+
val = dpnp.get_usm_ndarray(val)
48+
if val.shape != ():
49+
raise ValueError("`val` must be a scalar or 0D-array")
50+
if dpu.get_execution_queue((exec_q, val.sycl_queue)) is None:
51+
raise dpu.ExecutionPlacementError(
52+
"Input arrays have incompatible queues."
53+
)
54+
a_val = dpt.astype(val, arr.dtype)
55+
a_val = dpt.broadcast_to(a_val, arr.shape)
56+
_manager = dpu.SequentialOrderManager[exec_q]
57+
dep_evs = _manager.submitted_events
58+
h_ev, c_ev = _copy_usm_ndarray_into_usm_ndarray(
59+
src=a_val, dst=arr, sycl_queue=exec_q, depends=dep_evs
60+
)
61+
_manager.add_event_pair(h_ev, c_ev)
62+
return
63+
elif not isinstance(val, (Number, dpnp.bool)):
64+
raise TypeError(
65+
f"array cannot be filled with `val` of type {type(val)}"
66+
)
67+
val = _cast_fill_val(val, arr.dtype)
68+
69+
_manager = dpu.SequentialOrderManager[exec_q]
70+
dep_evs = _manager.submitted_events
71+
72+
# can leverage efficient memset when val is 0
73+
if arr.flags["FORC"] and val == 0:
74+
h_ev, zeros_ev = _zeros_usm_ndarray(arr, exec_q, depends=dep_evs)
75+
_manager.add_event_pair(h_ev, zeros_ev)
76+
else:
77+
h_ev, fill_ev = _full_usm_ndarray(val, arr, exec_q, depends=dep_evs)
78+
_manager.add_event_pair(h_ev, fill_ev)

dpnp/dpnp_array.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -928,13 +928,16 @@ def fill(self, value):
928928
"""
929929
Fill the array with a scalar value.
930930
931+
For full documentation refer to :obj:`numpy.ndarray.fill`.
932+
931933
Parameters
932934
----------
933-
value : scalar
935+
value : {dpnp.ndarray, usm_ndarray, scalar}
934936
All elements of `a` will be assigned this value.
935937
936938
Examples
937939
--------
940+
>>> import dpnp as np
938941
>>> a = np.array([1, 2])
939942
>>> a.fill(0)
940943
>>> a
@@ -946,8 +949,10 @@ def fill(self, value):
946949
947950
"""
948951

949-
for i in range(self.size):
950-
self.flat[i] = value
952+
# lazy import avoids circular imports
953+
from .dpnp_algo.dpnp_fill import dpnp_fill
954+
955+
dpnp_fill(self, value)
951956

952957
@property
953958
def flags(self):

tests/test_fill.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import dpctl
2+
import numpy as np
3+
import pytest
4+
from dpctl.utils import ExecutionPlacementError
5+
from numpy.testing import assert_array_equal
6+
7+
import dpnp as dnp
8+
9+
10+
@pytest.mark.parametrize(
11+
"val, error",
12+
[
13+
pytest.param(dnp.ones(2, dtype="i4"), ValueError, id="array"),
14+
pytest.param(dict(), TypeError, id="dictionary"),
15+
pytest.param("0", TypeError, id="string"),
16+
],
17+
)
18+
def test_fill_non_scalar(val, error):
19+
a = dnp.ones(5, dtype="i4")
20+
with pytest.raises(error):
21+
a.fill(val)
22+
23+
24+
def test_fill_compute_follows_data():
25+
q1 = dpctl.SyclQueue()
26+
q2 = dpctl.SyclQueue()
27+
28+
a = dnp.ones(5, dtype="i4", sycl_queue=q1)
29+
val = dnp.ones((), dtype=a.dtype, sycl_queue=q2)
30+
31+
with pytest.raises(ExecutionPlacementError):
32+
a.fill(val)
33+
34+
35+
def test_fill_strided_array():
36+
a = dnp.zeros(100, dtype="i4")
37+
b = a[::-2]
38+
39+
expected = dnp.tile(dnp.asarray([0, 1], dtype=a.dtype), 50)
40+
41+
b.fill(1)
42+
assert_array_equal(b, 1)
43+
assert_array_equal(a, expected)
44+
45+
46+
@pytest.mark.parametrize("order", ["C", "F"])
47+
def test_fill_strided_2d_array(order):
48+
a = dnp.zeros((10, 10), dtype="i4", order=order)
49+
b = a[::-2, ::2]
50+
51+
expected = dnp.copy(a)
52+
expected[::-2, ::2] = 1
53+
54+
b.fill(1)
55+
assert_array_equal(b, 1)
56+
assert_array_equal(a, expected)
57+
58+
59+
@pytest.mark.parametrize("order", ["C", "F"])
60+
def test_fill_memset(order):
61+
a = dnp.ones((10, 10), dtype="i4", order=order)
62+
a.fill(0)
63+
64+
assert_array_equal(a, 0)
65+
66+
67+
def test_fill_float_complex_to_int():
68+
a = dnp.ones((10, 10), dtype="i4")
69+
70+
a.fill(complex(2, 0))
71+
assert_array_equal(a, 2)
72+
73+
a.fill(float(3))
74+
assert_array_equal(a, 3)
75+
76+
77+
def test_fill_complex_to_float():
78+
a = dnp.ones((10, 10), dtype="f4")
79+
80+
a.fill(complex(2, 0))
81+
assert_array_equal(a, 2)
82+
83+
84+
def test_fill_bool():
85+
a = dnp.full(5, fill_value=7, dtype="i4")
86+
a.fill(True)
87+
assert_array_equal(a, 1)

tests/third_party/cupy/core_tests/test_ndarray_copy_and_view.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,7 @@ def test_fill(self, xp, dtype):
274274
a.fill(1)
275275
return a
276276

277-
@testing.with_requires("numpy>=1.24.0")
278-
@testing.for_all_dtypes_combination(("dtype1", "dtype2"))
279-
@testing.numpy_cupy_array_equal(accept_error=ComplexWarning)
277+
@pytest.mark.skip("Numpy allows Numpy scalar arrays as fill value")
280278
def test_fill_with_numpy_scalar_ndarray(self, xp, dtype1, dtype2):
281279
a = testing.shaped_arange((2, 3, 4), xp, dtype1)
282280
a.fill(numpy.ones((), dtype=dtype2))

0 commit comments

Comments
 (0)