Skip to content

Commit 97cc81f

Browse files
authored
Merge pull request #211 from IntelPython/master
Update gold/2021 with master
2 parents f67d2c6 + 8db2b2c commit 97cc81f

File tree

5 files changed

+394
-1
lines changed

5 files changed

+394
-1
lines changed

dpctl/__init__.pxd

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,3 @@
2828
# cython: language_level=3
2929

3030
from dpctl._sycl_core cimport *
31-
from dpctl._memory import *

dpctl/dptensor/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import dpctl.dptensor.numpy_usm_shared

dpctl/dptensor/numpy_usm_shared.py

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
##===---------- dparray.py - dpctl -------*- Python -*----===##
2+
##
3+
## Data Parallel Control (dpCtl)
4+
##
5+
## Copyright 2020 Intel Corporation
6+
##
7+
## Licensed under the Apache License, Version 2.0 (the "License");
8+
## you may not use this file except in compliance with the License.
9+
## You may obtain a copy of the License at
10+
##
11+
## http://www.apache.org/licenses/LICENSE-2.0
12+
##
13+
## Unless required by applicable law or agreed to in writing, software
14+
## distributed under the License is distributed on an "AS IS" BASIS,
15+
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
## See the License for the specific language governing permissions and
17+
## limitations under the License.
18+
##
19+
##===----------------------------------------------------------------------===##
20+
###
21+
### \file
22+
### This file implements a dparray - USM aware implementation of ndarray.
23+
##===----------------------------------------------------------------------===##
24+
25+
import numpy as np
26+
from inspect import getmembers, isfunction, isclass, isbuiltin
27+
from numbers import Number
28+
import sys
29+
import inspect
30+
import dpctl
31+
from dpctl.memory import MemoryUSMShared
32+
33+
debug = False
34+
35+
36+
def dprint(*args):
37+
if debug:
38+
print(*args)
39+
sys.stdout.flush()
40+
41+
42+
functions_list = [o[0] for o in getmembers(np) if isfunction(o[1]) or isbuiltin(o[1])]
43+
class_list = [o for o in getmembers(np) if isclass(o[1])]
44+
45+
array_interface_property = "__sycl_usm_array_interface__"
46+
47+
48+
def has_array_interface(x):
49+
return hasattr(x, array_interface_property)
50+
51+
52+
def _get_usm_base(ary):
53+
ob = ary
54+
while True:
55+
if ob is None:
56+
return None
57+
elif hasattr(ob, "__sycl_usm_array_interface__"):
58+
return ob
59+
elif isinstance(ob, np.ndarray):
60+
ob = ob.base
61+
elif isinstance(ob, memoryview):
62+
ob = ob.obj
63+
else:
64+
return None
65+
66+
67+
class ndarray(np.ndarray):
68+
"""
69+
numpy.ndarray subclass whose underlying memory buffer is allocated
70+
with a foreign allocator.
71+
"""
72+
73+
def __new__(
74+
subtype, shape, dtype=float, buffer=None, offset=0, strides=None, order=None
75+
):
76+
# Create a new array.
77+
if buffer is None:
78+
dprint("dparray::ndarray __new__ buffer None")
79+
nelems = np.prod(shape)
80+
dt = np.dtype(dtype)
81+
isz = dt.itemsize
82+
nbytes = int(isz * max(1, nelems))
83+
buf = MemoryUSMShared(nbytes)
84+
new_obj = np.ndarray.__new__(
85+
subtype,
86+
shape,
87+
dtype=dt,
88+
buffer=buf,
89+
offset=0,
90+
strides=strides,
91+
order=order,
92+
)
93+
if hasattr(new_obj, array_interface_property):
94+
dprint("buffer None new_obj already has sycl_usm")
95+
else:
96+
dprint("buffer None new_obj will add sycl_usm")
97+
setattr(
98+
new_obj,
99+
array_interface_property,
100+
new_obj._getter_sycl_usm_array_interface_(),
101+
)
102+
return new_obj
103+
# zero copy if buffer is a usm backed array-like thing
104+
elif hasattr(buffer, array_interface_property):
105+
dprint("dparray::ndarray __new__ buffer", array_interface_property)
106+
# also check for array interface
107+
new_obj = np.ndarray.__new__(
108+
subtype,
109+
shape,
110+
dtype=dtype,
111+
buffer=buffer,
112+
offset=offset,
113+
strides=strides,
114+
order=order,
115+
)
116+
if hasattr(new_obj, array_interface_property):
117+
dprint("buffer None new_obj already has sycl_usm")
118+
else:
119+
dprint("buffer None new_obj will add sycl_usm")
120+
setattr(
121+
new_obj,
122+
array_interface_property,
123+
new_obj._getter_sycl_usm_array_interface_(),
124+
)
125+
return new_obj
126+
else:
127+
dprint("dparray::ndarray __new__ buffer not None and not sycl_usm")
128+
nelems = np.prod(shape)
129+
# must copy
130+
ar = np.ndarray(
131+
shape,
132+
dtype=dtype,
133+
buffer=buffer,
134+
offset=offset,
135+
strides=strides,
136+
order=order,
137+
)
138+
nbytes = int(ar.nbytes)
139+
buf = MemoryUSMShared(nbytes)
140+
new_obj = np.ndarray.__new__(
141+
subtype,
142+
shape,
143+
dtype=dtype,
144+
buffer=buf,
145+
offset=0,
146+
strides=strides,
147+
order=order,
148+
)
149+
np.copyto(new_obj, ar, casting="no")
150+
if hasattr(new_obj, array_interface_property):
151+
dprint("buffer None new_obj already has sycl_usm")
152+
else:
153+
dprint("buffer None new_obj will add sycl_usm")
154+
setattr(
155+
new_obj,
156+
array_interface_property,
157+
new_obj._getter_sycl_usm_array_interface_(),
158+
)
159+
return new_obj
160+
161+
def _getter_sycl_usm_array_interface_(self):
162+
ary_iface = self.__array_interface__
163+
_base = _get_usm_base(self)
164+
if _base is None:
165+
raise TypeError
166+
167+
usm_iface = getattr(_base, "__sycl_usm_array_interface__", None)
168+
if usm_iface is None:
169+
raise TypeError
170+
171+
if ary_iface["data"][0] == usm_iface["data"][0]:
172+
ary_iface["version"] = usm_iface["version"]
173+
ary_iface["syclobj"] = usm_iface["syclobj"]
174+
else:
175+
raise TypeError
176+
return ary_iface
177+
178+
def __array_finalize__(self, obj):
179+
dprint("__array_finalize__:", obj, hex(id(obj)), type(obj))
180+
# When called from the explicit constructor, obj is None
181+
if obj is None:
182+
return
183+
# When called in new-from-template, `obj` is another instance of our own
184+
# subclass, that we might use to update the new `self` instance.
185+
# However, when called from view casting, `obj` can be an instance of any
186+
# subclass of ndarray, including our own.
187+
if hasattr(obj, array_interface_property):
188+
return
189+
if isinstance(obj, np.ndarray):
190+
ob = self
191+
while isinstance(ob, np.ndarray):
192+
if hasattr(ob, array_interface_property):
193+
return
194+
ob = ob.base
195+
196+
# Just raise an exception since __array_ufunc__ makes all reasonable cases not
197+
# need the code below.
198+
raise ValueError(
199+
"Non-USM allocated ndarray can not viewed as a USM-allocated one without a copy"
200+
)
201+
202+
# Tell Numba to not treat this type just like a NumPy ndarray but to propagate its type.
203+
# This way it will use the custom dparray allocator.
204+
__numba_no_subtype_ndarray__ = True
205+
206+
# Convert to a NumPy ndarray.
207+
def as_ndarray(self):
208+
return np.copy(self)
209+
210+
def __array__(self):
211+
return self
212+
213+
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
214+
if method == "__call__":
215+
N = None
216+
scalars = []
217+
typing = []
218+
for inp in inputs:
219+
if isinstance(inp, Number):
220+
scalars.append(inp)
221+
typing.append(inp)
222+
elif isinstance(inp, (self.__class__, np.ndarray)):
223+
if isinstance(inp, self.__class__):
224+
scalars.append(np.ndarray(inp.shape, inp.dtype, inp))
225+
typing.append(np.ndarray(inp.shape, inp.dtype))
226+
else:
227+
scalars.append(inp)
228+
typing.append(inp)
229+
if N is not None:
230+
if N != inp.shape:
231+
raise TypeError("inconsistent sizes")
232+
else:
233+
N = inp.shape
234+
else:
235+
return NotImplemented
236+
# Have to avoid recursive calls to array_ufunc here.
237+
# If no out kwarg then we create a dparray out so that we get
238+
# USM memory. However, if kwarg has dparray-typed out then
239+
# array_ufunc is called recursively so we cast out as regular
240+
# NumPy ndarray (having a USM data pointer).
241+
if kwargs.get("out", None) is None:
242+
# maybe copy?
243+
# deal with multiple returned arrays, so kwargs['out'] can be tuple
244+
res_type = np.result_type(*typing)
245+
out = empty(inputs[0].shape, dtype=res_type)
246+
out_as_np = np.ndarray(out.shape, out.dtype, out)
247+
kwargs["out"] = out_as_np
248+
else:
249+
# If they manually gave dparray as out kwarg then we have to also
250+
# cast as regular NumPy ndarray to avoid recursion.
251+
if isinstance(kwargs["out"], ndarray):
252+
out = kwargs["out"]
253+
kwargs["out"] = np.ndarray(out.shape, out.dtype, out)
254+
else:
255+
out = kwargs["out"]
256+
ret = ufunc(*scalars, **kwargs)
257+
return out
258+
else:
259+
return NotImplemented
260+
261+
262+
def isdef(x):
263+
try:
264+
eval(x)
265+
return True
266+
except NameError:
267+
return False
268+
269+
270+
for c in class_list:
271+
cname = c[0]
272+
if isdef(cname):
273+
continue
274+
# For now we do the simple thing and copy the types from NumPy module into dparray module.
275+
new_func = "%s = np.%s" % (cname, cname)
276+
try:
277+
the_code = compile(new_func, "__init__", "exec")
278+
exec(the_code)
279+
except:
280+
print("Failed to exec type propagation", cname)
281+
pass
282+
283+
# Redefine all Numpy functions in this module and if they
284+
# return a Numpy array, transform that to a USM-backed array
285+
# instead. This is a stop-gap. We should eventually find a
286+
# way to do the allocation correct to start with.
287+
for fname in functions_list:
288+
if isdef(fname):
289+
continue
290+
new_func = "def %s(*args, **kwargs):\n" % fname
291+
new_func += " ret = np.%s(*args, **kwargs)\n" % fname
292+
new_func += " if type(ret) == np.ndarray:\n"
293+
new_func += " ret = ndarray(ret.shape, ret.dtype, ret)\n"
294+
new_func += " return ret\n"
295+
the_code = compile(new_func, "__init__", "exec")
296+
exec(the_code)
297+
298+
299+
def from_ndarray(x):
300+
return copy(x)
301+
302+
303+
def as_ndarray(x):
304+
return np.copy(x)

dpctl/tests/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# Top-level module of all dpctl Python unit test cases.
2323
# ===-----------------------------------------------------------------------===#
2424

25+
from .test_dparray import *
2526
from .test_dump_functions import *
2627
from .test_sycl_device import *
2728
from .test_sycl_kernel_submit import *
@@ -30,3 +31,4 @@
3031
from .test_sycl_queue_manager import *
3132
from .test_sycl_queue_memcpy import *
3233
from .test_sycl_usm import *
34+
from .test_dparray import *

0 commit comments

Comments
 (0)