Skip to content
This repository was archived by the owner on Feb 2, 2024. It is now read-only.

Commit eef020d

Browse files
committed
generating typing/lowering code and exec, adding test
1 parent ed4b9f6 commit eef020d

3 files changed

Lines changed: 191 additions & 117 deletions

File tree

‎examples/d4p_linreg.py‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,21 @@
44

55
daal4py.daalinit(spmd=True)
66

7-
#@hpat.jit
8-
#def tunbox(r):
9-
# print(r.model.NumberOfBetas)
7+
@hpat.jit
8+
def lr_predict(N, D, model):
9+
data = np.random.ranf((N/2,D))
10+
return daal4py.linear_regression_prediction().compute(data, model)
1011

1112
@hpat.jit
12-
def linreg(N, D):
13+
def lr_train(N, D):
1314
data = np.random.ranf((N,D))
1415
gt = np.random.ranf((N,2))
15-
tres = daal4py.linear_regression_training(interceptFlag=True, method='qrDense').compute(data, gt)
16-
data = np.random.ranf((N/2,D))
17-
pres = daal4py.linear_regression_prediction().compute(data, tres.model)
18-
#tunbox(pres)
19-
return tres, pres #(pres.prediction[0], tres.model.NumberOfBetas, tres.model.NumberOfResponses, tres.model.Beta, tres.model.NumberOfFeatures)
16+
return daal4py.linear_regression_training(interceptFlag=True, method='qrDense').compute(data, gt)
17+
18+
t_res = lr_train(1000, 10)
19+
p_res = lr_predict(1000, 10, t_res.model)
2020

21-
t,p = linreg(1000, 10)
22-
print(p.prediction[0], t.model.NumberOfBetas)
21+
print(p_res.prediction[0], t_res.model.NumberOfBetas)
2322

2423
hpat.distribution_report()
2524

‎hpat/ml/d4p.py‎

Lines changed: 131 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# FIXME:
1212
# - sub-classing: parameters of '__interface__' types must accept derived types
13-
# - boxing/unboxing
13+
# - boxing/unboxing of algorithms
1414
# - GC: result/model objects returned by daal4py wrappers are newly allocated shared pointers, need to get gc'ed
1515
# - float32 tables, input type selection etc.
1616
# - key-word/optional input arguments
@@ -21,8 +21,8 @@
2121
from numba import types, cgutils
2222
from numba.extending import (intrinsic, typeof_impl, overload, overload_method,
2323
overload_attribute, box, unbox, make_attribute_wrapper,
24-
type_callable, models, register_model, lower_builtin,
25-
NativeValue, lower_cast, get_cython_function_address)
24+
type_callable, models, register_model, lower_builtin, lower_getattr,
25+
NativeValue, lower_cast, get_cython_function_address, typeof_impl)
2626
from numba.targets.imputils import impl_ret_new_ref
2727
from numba.typing.templates import (signature, AbstractTemplate, infer, infer_getattr,
2828
ConcreteTemplate, AttributeTemplate, bound_function, infer_global)
@@ -155,6 +155,7 @@ class algo_factory(object):
155155
'itable_type' : itable_type,
156156
'size_t' : types.uint64,
157157
'double' : types.float64,
158+
'float' : types.float32,
158159
'bool' : types.boolean,
159160
'std::string&': string_type,
160161
}
@@ -169,15 +170,15 @@ def from_d4p(self, spec):
169170
return D4PSpec(spec['pyclass'],
170171
spec['c_name'],
171172
params = spec['params'],
172-
input_types = [(x[0], self.all_nbtypes[x[1].rstrip('*')], d4p_dtypes[x[2]]) for x in spec['input_types']],
173+
input_types = [(x[0], x[1].rstrip('*'), d4p_dtypes[x[2]]) for x in spec['input_types']],
173174
result_dist = d4p_dtypes[spec['result_dist']])
174175
elif 'attrs' in spec:
175176
# filter out (do not support) properties for which we do not know the numba type
176177
attrs = []
177178
for x in spec['attrs']:
178179
typ = x[1].rstrip('*')
179180
if typ in self.all_nbtypes:
180-
attrs.append((x[0], self.all_nbtypes[typ]))
181+
attrs.append((x[0], typ))
181182
else:
182183
warnings.warn("Warning: couldn't find numba type for '" + x[1] +"'. Ignored.")
183184
return D4PSpec(spec['pyclass'],
@@ -203,7 +204,6 @@ def activate(self):
203204
return
204205
self.spec = self.from_d4p(self.spec)
205206
self.mk_ctor()
206-
self.mk_compute()
207207
self.mk_attrs()
208208
self.mk_boxing()
209209

@@ -223,6 +223,7 @@ def __init__(self):
223223
# also register their opaque data model
224224
self.NbType = mk_simple(self.name + '_nbtype')
225225
self.all_nbtypes[self.name] = self.NbType()
226+
226227
register_model(self.NbType)(models.OpaqueModel)
227228

228229
# some of the classes can be parameters to others and have a default NULL/None
@@ -241,7 +242,13 @@ def mk_ctor(self):
241242
'''
242243

243244
if not self.spec or not self.spec.params:
244-
# this must be a result or model, not an algo class
245+
if self.spec:
246+
# this must be a result or model, not an algo class
247+
# we don't want a constructor but Numba must know ts model for boxing/unboxing
248+
@typeof_impl.register(self.spec.pyclass)
249+
def typeof_index(val, c):
250+
return self.all_nbtypes[self.name]
251+
245252
return
246253

247254
# FIXME: check args
@@ -282,108 +289,121 @@ def _ovld(*args, **kwargs):
282289
impl = loc_vars['_ovld_impl']
283290
return impl
284291

285-
286-
def mk_compute(self):
287-
'''provide the typing and lowering for calling compute on an algo object'''
288-
289-
if not self.spec or not self.spec.input_types:
290-
# this must be a result or model, not an algo class
291-
return
292-
293-
algo_type = self.NbType
294-
result_type = self.all_nbtypes[self.name+'_result']
295-
compute_name = '.'.join([self.spec.pyclass.__module__.strip('_'), self.spec.pyclass.__name__, 'compute'])
296-
297-
@infer_getattr
298-
class AlgoAttributes(AttributeTemplate):
299-
'''declares numba signatures of attributes/methods of algo objects'''
300-
key = algo_type
301-
302-
@bound_function(compute_name)
303-
def resolve_compute(self, dict, args, kws):
304-
# FIXME: keyword args
305-
# FIXME: check args
306-
return signature(result_type, *args)
307-
308-
309-
@lower_builtin(compute_name, self.all_nbtypes[self.name], *[self.all_nbtypes[x[1]] if isinstance(x[1], str) else x[1] for x in self.spec.input_types])
310-
def lower_compute(context, builder, sig, args):
311-
'''lowers compute method algo objects'''
312-
# First prepare list of argument types
313-
lir_types = [lir.IntType(8).as_pointer()] # the first arg is always our algo object (shrd_ptr)
314-
c_args = [args[0]] # the first arg is always our algo object (shrd_ptr)
315-
for i in range(1, len(args)):
316-
lirt = get_lir_type(context, sig.args[i])
317-
if isinstance(lirt, list): # Array!
318-
# generate lir code to extract actual arguments
319-
# collect args/types in list
320-
lir_types += lirt
321-
in_arrtype = sig.args[i]
322-
in_array = context.make_array(in_arrtype)(context, builder, args[i])
323-
in_shape = cgutils.unpack_tuple(builder, in_array.shape)
324-
c_args += [in_array.data, in_shape[0], in_shape[1]]
325-
else:
326-
lir_types.append(lirt)
327-
c_args.append(args[i])
328-
# Now we can define the signature and call the C-function
329-
fnty = lir.FunctionType(lir.IntType(8).as_pointer(), lir_types)
330-
fn = builder.module.get_or_insert_function(fnty, name='compute_' + self.spec.c_name)
331-
# finally we call the function
332-
return builder.call(fn, c_args)
333-
334-
335-
def add_attr(self, NbType, attr, attr_type, c_func):
292+
def gen_call(context, builder, sig, args, c_func):
336293
'''
337-
Generate getter for attribute 'attr' on objects of numba result/model type.
338-
Calls c_func to retrieve the attribute from the given result/model object.
339-
Converts to ndarray if attr_type is Array
294+
This is generating the llvm code for calling a d4p C function.
295+
May also convert to ndarray.
296+
Used by our dynamically generated/exec'ed @lower_builtin/@lower_getattr functions below.
340297
'''
341-
342-
if isinstance(attr_type, str):
343-
attr_type = self.all_nbtypes[attr_type]
344-
is_array = isinstance(attr_type, types.Array)
345-
346-
@intrinsic
347-
def get_attr_impl(typingctx, obj):
348-
'''
349-
This is creating the llvm wrapper calling the C function.
350-
May also convert to ndarray.
351-
'''
352-
def codegen(context, builder, sig, args):
353-
assert(len(args) == 1)
354-
c_func_ret_type = lir.IntType(8).as_pointer() if is_array else context.get_data_type(attr_type)
355-
# First call the getter
356-
fnty = lir.FunctionType(c_func_ret_type, [lir.IntType(8).as_pointer()])
357-
fn = builder.module.get_or_insert_function(fnty, name=c_func)
358-
ptr = builder.call(fn, args)
359-
return nt2nd(context, builder, ptr, sig.return_type) if is_array else ptr
360-
361-
return attr_type(obj), codegen
362-
363-
@overload_attribute(NbType, attr)
364-
def get_attr(obj):
365-
'''declaring getter for attribute 'attr' of objects of type "NbType"'''
366-
def getter(obj):
367-
return get_attr_impl(obj)
368-
return getter
298+
lir_types = [lir.IntType(8).as_pointer()] # the first arg is always our algo object (shrd_ptr)
299+
c_args = [args[0]] # the first arg is always our algo object (shrd_ptr)
300+
# prepare our args (usually none for most get_* attribuutes/properties)
301+
for i in range(1, len(args)):
302+
lirt = get_lir_type(context, sig.args[i])
303+
if isinstance(lirt, list): # Array!
304+
# generate lir code to extract actual arguments
305+
# collect args/types in list
306+
lir_types += lirt
307+
in_arrtype = sig.args[i]
308+
in_array = context.make_array(in_arrtype)(context, builder, args[i])
309+
in_shape = cgutils.unpack_tuple(builder, in_array.shape)
310+
c_args += [in_array.data, in_shape[0], in_shape[1]]
311+
else:
312+
lir_types.append(lirt)
313+
c_args.append(args[i])
314+
#ret_typ = sig if c_func.startswith('get_') else sig.return_type
315+
# Our getter might return an array, which needs special handling
316+
ret_is_array = isinstance(sig.return_type, types.Array)
317+
# define our llvm return type
318+
c_func_ret_type = lir.IntType(8).as_pointer() if ret_is_array else context.get_data_type(sig.return_type)
319+
# Now we can define the signature
320+
fnty = lir.FunctionType(c_func_ret_type, lir_types)
321+
# Get function
322+
fn = builder.module.get_or_insert_function(fnty, name=c_func)
323+
# and finally generate the call
324+
ptr = builder.call(fn, c_args)
325+
return nt2nd(context, builder, ptr, sig.return_type) if ret_is_array else ptr
369326

370327

371328
def mk_attrs(self):
372-
'''Make attributes of result and model known to numba and how to get their values.'''
329+
'''
330+
Provide the typing and lowering for getters and calling compute
331+
Again, we can't use static code for our infer_getarr class, so we need
332+
to generate code that we exec.
333+
Apparently the @infer_getattr/@bound_function and @lower_builtin need to
334+
be in the same context, so we need to include the @lower_builtin stuff
335+
there, too.
336+
'''
373337

374-
if not self.spec or not self.spec.attrs:
375-
# this must be algo class, which does not have attributes
376-
# or it is an alias only
338+
if not self.spec:
377339
return
378340

379-
for a in self.spec.attrs:
380-
self.add_attr(self.NbType, a[0], a[1], '_'.join(['get', self.spec.c_name, a[0]]))
341+
# Initing the code we want to exec
342+
lower_code = ''
343+
infer_code = '''# This is our class providing the resolve_* methods
344+
@infer_getattr
345+
class AlgoAttributes_{0}(AttributeTemplate):
346+
"declares numba signatures of attributes/methods of {0} objects"
347+
key = algo_factory.all_nbtypes['{0}'].__class__
348+
'''.format(self.spec.c_name)
349+
350+
# The python name stub (module + class)
351+
name_stub = '_'+'.'.join([self.spec.pyclass.__module__.strip('_'), self.spec.pyclass.__name__])
352+
result_type = None
353+
354+
# First handle properties (result/model classes)
355+
if self.spec.attrs:
356+
for a in self.spec.attrs:
357+
c_func = '_'.join(['get', self.spec.c_name, a[0]])
358+
full_name = '.'.join([name_stub, a[0]])
359+
# for typing we simply provide resolve_* method for each attribute
360+
infer_code += '''
361+
def resolve_{0}(self, obj):
362+
return algo_factory.all_nbtypes['{1}']
363+
'''.format(a[0], a[1])
364+
365+
# lowering code for properties through @lower_getattr
366+
lower_code += '''
367+
@lower_getattr(algo_factory.all_nbtypes['{0}'], '{1}') # getters have no args (other than self)
368+
def lower_{2}(context, builder, typ, val):
369+
return algo_factory.gen_call(context, builder, signature(algo_factory.all_nbtypes['{3}']), [val], '{2}')
370+
'''.format(self.name, a[0], c_func, a[1])
371+
372+
# Now we handle compute method algorithms classes
373+
if self.spec.input_types:
374+
compute_name = '.'.join([name_stub, 'compute'])
375+
376+
# using bound_function for typing
377+
infer_code += '''
378+
@bound_function("{0}")
379+
def resolve_compute(self, dict, args, kws):
380+
# FIXME: keyword args
381+
# FIXME: check args
382+
return signature(algo_factory.all_nbtypes['{1}_result'], *args)
383+
'''.format(compute_name, self.name)
384+
385+
# lowering methods provided with lower_builtin
386+
lower_code += '''
387+
@lower_builtin('{0}', algo_factory.all_nbtypes['{1}'], *[{2}])
388+
def lower_compute(context, builder, sig, args):
389+
return algo_factory.gen_call(context, builder, sig, args, 'compute_{3}')
390+
'''.format(compute_name,
391+
self.name,
392+
', '.join(["algo_factory.all_nbtypes['{}']".format(x[1]) for x in self.spec.input_types]), #if isinstance(x[1], str) else "'{}'")
393+
self.spec.c_name)
394+
395+
loc_vars = {}
396+
exec(infer_code+lower_code, globals(), loc_vars)
381397

382398

383-
def cy_unbox(self):
399+
def cy_unboxer(self):
400+
'''
401+
Return the address of the cython generated C(++) function which
402+
provides the actual DAAL C++ pointer for our result/model Python object.
403+
'''
384404
addr = get_cython_function_address("_daal4py", 'unbox_'+self.spec.c_name)
385-
functype = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.py_object)
386-
return functype(addr)
405+
return addr
406+
387407

388408
def mk_boxing(self):
389409
'''provide boxing and unboxing'''
@@ -392,24 +412,29 @@ def mk_boxing(self):
392412
# we only support models/results at this point
393413
return
394414

395-
self.unbox = self.cy_unbox()
415+
ll.add_symbol('unbox_'+self.spec.c_name, self.cy_unboxer())
396416

397417
@box(self.NbType)
398418
def box_me(typ, val, c):
419+
'Call Cythons contructor with the C++ pointer and return Python object.'
399420
ll_intp = c.context.get_value_type(types.uintp)
400421
addr = c.builder.ptrtoint(val, ll_intp)
401422
v = c.box(types.uintp, addr)
402423
py_obj = c.pyapi.unserialize(c.pyapi.serialize_object(self.spec.pyclass))
403424
res = c.pyapi.call_function_objargs(py_obj, (v,))
404425
return res
405426

406-
#@unbox(self.NbType)
407-
#def unbox_me(typ, obj, c):
408-
# print('unbox_'+self.spec.c_name, self.NbType())
409-
# val = cgutils.alloca_once(c.builder, lir.IntType(8).as_pointer())
410-
# longobj = self.unbox(self.spec.c_name, val)
411-
# c.builder.store(longobj, val)
412-
# return NativeValue(c.builder.load(val), is_error=c.pyapi.c_api_error())
427+
@unbox(self.NbType)
428+
def unbox_me(typ, obj, c):
429+
'Call C++ unboxing function and return as NativeValue'
430+
# Define the signature
431+
fnty = lir.FunctionType(lir.IntType(8).as_pointer(), [lir.IntType(8).as_pointer(),])
432+
# Get function
433+
fn = c.builder.module.get_or_insert_function(fnty, name='unbox_'+self.spec.c_name)
434+
# finally generate the call
435+
ptr = c.builder.call(fn, [obj])
436+
return NativeValue(ptr, is_error=c.pyapi.c_api_error())
437+
413438

414439

415440
##############################################################################

0 commit comments

Comments
 (0)