Skip to content

Commit d2458eb

Browse files
committed
Initial separation of dparray into 2 parts.
1 parent dff89d1 commit d2458eb

File tree

4 files changed

+225
-1
lines changed

4 files changed

+225
-1
lines changed

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
include versioneer.py
2+
include dparray.py
23
recursive-include dpctl/include *.h *.hpp
34
include dpctl/*.pxd
45
include dpctl/*DPPL*Interface.*

dpctl/__init__.pxd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@
2929

3030
from dpctl._sycl_core cimport *
3131
from dpctl._memory import *
32-
32+
from dparray import *

dpctl/dparray.py

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import numpy as np
2+
from inspect import getmembers, isfunction, isclass, isbuiltin
3+
from numbers import Number
4+
from types import FunctionType as ftype, BuiltinFunctionType as bftype
5+
import sys
6+
#import importlib
7+
#import functools
8+
import inspect
9+
10+
debug = False
11+
12+
def dprint(*args):
13+
if debug:
14+
print(*args)
15+
sys.stdout.flush()
16+
17+
import dpctl
18+
from dpctl._memory import MemoryUSMShared
19+
20+
functions_list = [o[0] for o in getmembers(np) if isfunction(o[1]) or isbuiltin(o[1])]
21+
class_list = [o for o in getmembers(np) if isclass(o[1])]
22+
23+
array_interface_property = "__array_interface__"
24+
def has_array_interface(x):
25+
return hasattr(x, array_interface_property)
26+
27+
class ndarray(np.ndarray):
28+
"""
29+
numpy.ndarray subclass whose underlying memory buffer is allocated
30+
with a foreign allocator.
31+
"""
32+
def __new__(subtype, shape,
33+
dtype=float, buffer=None, offset=0,
34+
strides=None, order=None):
35+
# Create a new array.
36+
if buffer is None:
37+
dprint("dparray::ndarray __new__ buffer None")
38+
nelems = np.prod(shape)
39+
dt = np.dtype(dtype)
40+
isz = dt.itemsize
41+
buf = MemoryUSMShared(nbytes=isz*max(1,nelems))
42+
new_obj = np.ndarray.__new__(
43+
subtype, shape, dtype=dt,
44+
buffer=buf, offset=0,
45+
strides=strides, order=order)
46+
if hasattr(new_obj, array_interface_property):
47+
dprint("buffer None new_obj already has sycl_usm")
48+
else:
49+
dprint("buffer None new_obj will add sycl_usm")
50+
setattr(new_obj, array_interface_property, {})
51+
return new_obj
52+
# zero copy if buffer is a usm backed array-like thing
53+
elif hasattr(buffer, array_interface_property):
54+
dprint("dparray::ndarray __new__ buffer", array_interface_property)
55+
# also check for array interface
56+
new_obj = np.ndarray.__new__(
57+
subtype, shape, dtype=dtype,
58+
buffer=buffer, offset=offset,
59+
strides=strides, order=order)
60+
if hasattr(new_obj, array_interface_property):
61+
dprint("buffer None new_obj already has sycl_usm")
62+
else:
63+
dprint("buffer None new_obj will add sycl_usm")
64+
setattr(new_obj, array_interface_property, {})
65+
return new_obj
66+
else:
67+
dprint("dparray::ndarray __new__ buffer not None and not sycl_usm")
68+
nelems = np.prod(shape)
69+
# must copy
70+
ar = np.ndarray(shape,
71+
dtype=dtype, buffer=buffer,
72+
offset=offset, strides=strides,
73+
order=order)
74+
buf = MemoryUSMShared(nbytes=ar.nbytes)
75+
new_obj = np.ndarray.__new__(
76+
subtype, shape, dtype=dtype,
77+
buffer=buf, offset=0,
78+
strides=strides, order=order)
79+
np.copyto(new_obj, ar, casting='no')
80+
if hasattr(new_obj, array_interface_property):
81+
dprint("buffer None new_obj already has sycl_usm")
82+
else:
83+
dprint("buffer None new_obj will add sycl_usm")
84+
setattr(new_obj, array_interface_property, {})
85+
return new_obj
86+
87+
def __array_finalize__(self, obj):
88+
dprint("__array_finalize__:", obj, hex(id(obj)), type(obj))
89+
# When called from the explicit constructor, obj is None
90+
if obj is None: return
91+
# When called in new-from-template, `obj` is another instance of our own
92+
# subclass, that we might use to update the new `self` instance.
93+
# However, when called from view casting, `obj` can be an instance of any
94+
# subclass of ndarray, including our own.
95+
if hasattr(obj, array_interface_property):
96+
return
97+
if isinstance(obj, numba.core.runtime._nrt_python._MemInfo):
98+
mobj = obj
99+
while isinstance(mobj, numba.core.runtime._nrt_python._MemInfo):
100+
dprint("array_finalize got Numba MemInfo")
101+
ea = mobj.external_allocator
102+
d = mobj.data
103+
dprint("external_allocator:", hex(ea), type(ea))
104+
dprint("data:", hex(d), type(d))
105+
dppl_rt_allocator = numba.dppl._dppl_rt.get_external_allocator()
106+
dprint("dppl external_allocator:", hex(dppl_rt_allocator), type(dppl_rt_allocator))
107+
dprint(dir(mobj))
108+
if ea == dppl_rt_allocator:
109+
return
110+
mobj = mobj.parent
111+
if isinstance(mobj, ndarray):
112+
mobj = mobj.base
113+
if isinstance(obj, np.ndarray):
114+
ob = self
115+
while isinstance(ob, np.ndarray):
116+
if hasattr(obj, array_interface_property):
117+
return
118+
ob = ob.base
119+
120+
# Just raise an exception since __array_ufunc__ makes all reasonable cases not
121+
# need the code below.
122+
raise ValueError("Non-USM allocated ndarray can not viewed as a USM-allocated one without a copy")
123+
124+
# Tell Numba to not treat this type just like a NumPy ndarray but to propagate its type.
125+
# This way it will use the custom dparray allocator.
126+
__numba_no_subtype_ndarray__ = True
127+
128+
# Convert to a NumPy ndarray.
129+
def as_ndarray(self):
130+
return np.copy(self)
131+
132+
def __array__(self):
133+
return self
134+
135+
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
136+
if method == '__call__':
137+
N = None
138+
scalars = []
139+
typing = []
140+
for inp in inputs:
141+
if isinstance(inp, Number):
142+
scalars.append(inp)
143+
typing.append(inp)
144+
elif isinstance(inp, (self.__class__, np.ndarray)):
145+
if isinstance(inp, self.__class__):
146+
scalars.append(np.ndarray(inp.shape, inp.dtype, inp))
147+
typing.append(np.ndarray(inp.shape, inp.dtype))
148+
else:
149+
scalars.append(inp)
150+
typing.append(inp)
151+
if N is not None:
152+
if N != inp.shape:
153+
raise TypeError("inconsistent sizes")
154+
else:
155+
N = inp.shape
156+
else:
157+
return NotImplemented
158+
# Have to avoid recursive calls to array_ufunc here.
159+
# If no out kwarg then we create a dparray out so that we get
160+
# USM memory. However, if kwarg has dparray-typed out then
161+
# array_ufunc is called recursively so we cast out as regular
162+
# NumPy ndarray (having a USM data pointer).
163+
if kwargs.get('out', None) is None:
164+
# maybe copy?
165+
# deal with multiple returned arrays, so kwargs['out'] can be tuple
166+
res_type = np.result_type(*typing)
167+
out = empty(inputs[0].shape, dtype=res_type)
168+
out_as_np = np.ndarray(out.shape, out.dtype, out)
169+
kwargs['out'] = out_as_np
170+
else:
171+
# If they manually gave dparray as out kwarg then we have to also
172+
# cast as regular NumPy ndarray to avoid recursion.
173+
if isinstance(kwargs['out'], ndarray):
174+
out = kwargs['out']
175+
kwargs['out'] = np.ndarray(out.shape, out.dtype, out)
176+
else:
177+
out = kwargs['out']
178+
ret = ufunc(*scalars, **kwargs)
179+
return out
180+
else:
181+
return NotImplemented
182+
183+
def isdef(x):
184+
try:
185+
eval(x)
186+
return True
187+
except NameError:
188+
return False
189+
190+
for c in class_list:
191+
cname = c[0]
192+
if isdef(cname):
193+
continue
194+
# For now we do the simple thing and copy the types from NumPy module into dparray module.
195+
new_func = "%s = np.%s" % (cname, cname)
196+
try:
197+
the_code = compile(new_func, '__init__', 'exec')
198+
exec(the_code)
199+
except:
200+
print("Failed to exec type propagation", cname)
201+
pass
202+
203+
# Redefine all Numpy functions in this module and if they
204+
# return a Numpy array, transform that to a USM-backed array
205+
# instead. This is a stop-gap. We should eventually find a
206+
# way to do the allocation correct to start with.
207+
for fname in functions_list:
208+
if isdef(fname):
209+
continue
210+
new_func = "def %s(*args, **kwargs):\n" % fname
211+
new_func += " ret = np.%s(*args, **kwargs)\n" % fname
212+
new_func += " if type(ret) == np.ndarray:\n"
213+
new_func += " ret = ndarray(ret.shape, ret.dtype, ret)\n"
214+
new_func += " return ret\n"
215+
the_code = compile(new_func, '__init__', 'exec')
216+
exec(the_code)
217+
218+
def from_ndarray(x):
219+
return copy(x)
220+
221+
def as_ndarray(x):
222+
return np.copy(x)

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ def _get_cmdclass():
201201
cmdclass["develop"] = develop
202202
return cmdclass
203203

204+
print("packages:", find_packages(include=["*"]))
204205

205206
setup(
206207
name="dpctl",

0 commit comments

Comments
 (0)