提交 8e292493 authored 作者: Frédéric Bastien's avatar Frédéric Bastien 提交者: GitHub

Merge pull request #5612 from notoraptor/make-structs

Implement a struct generator for wrapping op params in Python and C codes.
...@@ -130,6 +130,16 @@ the most important ones: ...@@ -130,6 +130,16 @@ the most important ones:
change in the code. If you don't want to cache the compiled code change in the code. If you don't want to cache the compiled code
return an empty tuple or don't implement it. return an empty tuple or don't implement it.
.. method:: c_element_type()
Optional: should return the name of the primitive C type of
items into variables handled by this Theano type. For example,
for a matrix of 32-bit signed NumPy integers, it should return
``"npy_int32"``. If C type may change from an instance to another
(e.g. ``Scalar('int32')`` vs ``Scalar('int64')``), consider
implementing this method. If C type is fixed accross instances,
this method may be useless (as you already know the C type
when you work with the C code).
Each of these functions take two arguments, ``name`` and ``sub`` which Each of these functions take two arguments, ``name`` and ``sub`` which
must be used to parameterize the C code they return. ``name`` is a must be used to parameterize the C code they return. ``name`` is a
......
...@@ -73,8 +73,10 @@ attribute :attr:`params_type` to an instance of your params Type. ...@@ -73,8 +73,10 @@ attribute :attr:`params_type` to an instance of your params Type.
.. note:: .. note::
If you want to have multiple parameters you have to bundle those If you want to have multiple parameters, Theano provides the convenient class
inside a single object and use that as the params type. :class:`theano.gof.params_type.ParamsType` that allows to bundle many parameters into
one object that will be available in both Python (as a Python object) and C code (as a struct).
See :ref:`ParamsType tutorial and API documentation <libdoc_gof_params_type>` for more infos.
For example if we decide to use an int as the params the following For example if we decide to use an int as the params the following
would be appropriate: would be appropriate:
......
...@@ -17,4 +17,5 @@ ...@@ -17,4 +17,5 @@
fgraph fgraph
toolbox toolbox
type type
params_type
utils utils
.. _libdoc_gof_params_type:
============================================================
:mod:`theano.gof.params_type` -- Wrapper class for op params
============================================================
---------
Reference
---------
.. automodule:: theano.gof.params_type
:platform: Unix, Windows
:synopsis: Wrapper class for op params
:members:
.. moduleauthor:: LISA
\ No newline at end of file
...@@ -80,6 +80,8 @@ from theano.gof.type import \ ...@@ -80,6 +80,8 @@ from theano.gof.type import \
from theano.gof.utils import \ from theano.gof.utils import \
hashtype, object2, MethodNotDefined hashtype, object2, MethodNotDefined
from theano.gof.params_type import ParamsType, Params
import theano import theano
if theano.config.cmodule.preload_cache: if theano.config.cmodule.preload_cache:
......
...@@ -125,8 +125,9 @@ class Apply(Node): ...@@ -125,8 +125,9 @@ class Apply(Node):
Returns the params for the node, or NoParams if no params is set. Returns the params for the node, or NoParams if no params is set.
""" """
if hasattr(self.op, 'get_params'): try:
return self.op.get_params(self) return self.op.get_params(self)
except theano.gof.utils.MethodNotDefined:
return NoParams return NoParams
def __getstate__(self): def __getstate__(self):
......
...@@ -795,6 +795,22 @@ class Op(utils.object2, PureOp, CLinkerOp): ...@@ -795,6 +795,22 @@ class Op(utils.object2, PureOp, CLinkerOp):
Convenience class to bundle `PureOp` and `CLinkerOp`. Convenience class to bundle `PureOp` and `CLinkerOp`.
""" """
# We add a default get_params() implementation which will try to detect params from the op
# if params_type is set to a ParamsType. If not, we raise a MethodNotDefined exception.
def get_params(self, node):
if hasattr(self, 'params_type') and isinstance(self.params_type, theano.gof.ParamsType):
wrapper = self.params_type
if not all(hasattr(self, field) for field in wrapper.fields):
raise AttributeError('%s: missing attributes for ParamsType parameter.' % type(self).__name__)
wrap_dict = dict()
for i in range(wrapper.length):
field = wrapper.fields[i]
_type = wrapper.types[i]
wrap_dict[field] = _type.filter(getattr(self, field), strict=False, allow_downcast=True)
return theano.gof.Params(wrapper, **wrap_dict)
raise theano.gof.utils.MethodNotDefined('get_params')
def prepare_node(self, node, storage_map, compute_map, impl): def prepare_node(self, node, storage_map, compute_map, impl):
""" """
Make any special modifications that the Op needs before doing Make any special modifications that the Op needs before doing
...@@ -1377,7 +1393,25 @@ class COp(Op): ...@@ -1377,7 +1393,25 @@ class COp(Op):
The names must be strings that are not a C keyword and the The names must be strings that are not a C keyword and the
values must be strings of literal C representations. values must be strings of literal C representations.
If op uses a :class:`theano.gof.params_type.ParamsType` as ``params_type``,
it returns:
- a default macro ``PARAMS_TYPE`` which defines the class name of the
corresponding C struct.
- a macro ``DTYPE_PARAM_key`` for every ``key`` in the ParamsType for which associated
type implements the method :func:`theano.gof.type.CLinkerType.c_element_type`.
``DTYPE_PARAM_key`` defines the primitive C type name of an item in a variable
associated to ``key``.
""" """
if hasattr(self, 'params_type') and isinstance(self.params_type, theano.gof.ParamsType):
wrapper = self.params_type
params = [('PARAMS_TYPE', wrapper.name)]
for i in range(wrapper.length):
try:
params.append(('DTYPE_PARAM_' + wrapper.fields[i], wrapper.types[i].c_element_type()))
except utils.MethodNotDefined:
pass
return params
return [] return []
def c_code_cache_version(self): def c_code_cache_version(self):
......
差异被折叠。
差异被折叠。
#section support_code_apply
int APPLY_SPECIFIC(quadratic_function)(PyArrayObject* tensor, DTYPE_INPUT_0 a, DTYPE_INPUT_0 b, DTYPE_INPUT_0 c) {
NpyIter* iterator = NpyIter_New(tensor,
NPY_ITER_READWRITE | NPY_ITER_EXTERNAL_LOOP | NPY_ITER_REFS_OK,
NPY_KEEPORDER, NPY_NO_CASTING, NULL);
if(iterator == NULL) {
PyErr_SetString(PyExc_RuntimeError, "Unable to iterate over a tensor for an elemwise operation.");
return -1;
}
NpyIter_IterNextFunc* get_next = NpyIter_GetIterNext(iterator, NULL);
char** data_ptr = NpyIter_GetDataPtrArray(iterator);
npy_intp* stride_ptr = NpyIter_GetInnerStrideArray(iterator);
npy_intp* innersize_ptr = NpyIter_GetInnerLoopSizePtr(iterator);
do {
char* data = *data_ptr;
npy_intp stride = *stride_ptr;
npy_intp count = *innersize_ptr;
while(count) {
DTYPE_INPUT_0 x = *((DTYPE_INPUT_0*)data);
*((DTYPE_INPUT_0*)data) = a*x*x + b*x + c;
data += stride;
--count;
}
} while(get_next(iterator));
NpyIter_Deallocate(iterator);
return 0;
}
int APPLY_SPECIFIC(compute_quadratic)(PyArrayObject* X, PyArrayObject** Y, PARAMS_TYPE* coeff) {
DTYPE_INPUT_0 a = (DTYPE_INPUT_0) (*(DTYPE_PARAM_a*) PyArray_GETPTR1(coeff->a, 0)); // 0-D TensorType.
DTYPE_INPUT_0 b = coeff->b; // Scalar.
DTYPE_INPUT_0 c = (DTYPE_INPUT_0) PyFloat_AsDouble(coeff->c); // Generic.
Py_XDECREF(*Y);
*Y = (PyArrayObject*)PyArray_EMPTY(PyArray_NDIM(X), PyArray_DIMS(X), TYPENUM_INPUT_0, PyArray_IS_F_CONTIGUOUS(X));
if (PyArray_CopyInto(*Y, X) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Unable to copy input into output.");
return 1;
};
if (APPLY_SPECIFIC(quadratic_function)(*Y, a, b, c) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Unable to compute quadratic function.");
return 1;
}
return 0;
}
...@@ -35,6 +35,19 @@ class CLinkerType(CLinkerObject): ...@@ -35,6 +35,19 @@ class CLinkerType(CLinkerObject):
""" """
def c_element_type(self):
"""
Optional: Return the name of the primitive C type of items into variables
handled by this type.
e.g:
- For ``TensorType(dtype='int64', ...)``: should return ``"npy_int64"``.
- For ``GpuArrayType(dtype='int32', ...)``: should return ``"ga_int"``.
"""
raise MethodNotDefined("c_element_type", type(self), self.__class__.__name__)
def c_is_simple(self): def c_is_simple(self):
""" """
Optional: Return True for small or builtin C types. Optional: Return True for small or builtin C types.
......
...@@ -573,3 +573,20 @@ def hash_from_file(file_path): ...@@ -573,3 +573,20 @@ def hash_from_file(file_path):
with open(file_path, 'rb') as f: with open(file_path, 'rb') as f:
file_content = f.read() file_content = f.read()
return hash_from_code(file_content) return hash_from_code(file_content)
# Set of C and C++ keywords as defined (at March 2nd, 2017) in the pages below:
# - http://fr.cppreference.com/w/c/keyword
# - http://fr.cppreference.com/w/cpp/keyword
# Added `NULL` and `_Pragma` keywords.
c_cpp_keywords = {'_Alignas', '_Alignof', '_Atomic', '_Bool', '_Complex', '_Generic', '_Imaginary', '_Noreturn',
'_Pragma', '_Static_assert', '_Thread_local', 'alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto',
'bitand', 'bitor', 'bool', 'break', 'case', 'catch', 'char', 'char16_t', 'char32_t', 'class', 'compl',
'const', 'const_cast', 'constexpr', 'continue', 'decltype', 'default', 'delete', 'do', 'double',
'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend',
'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', 'noexcept', 'not', 'not_eq',
'NULL', 'nullptr', 'operator', 'or', 'or_eq', 'private', 'protected', 'public', 'register',
'reinterpret_cast', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', 'static_assert',
'static_cast', 'struct', 'switch', 'template', 'this', 'thread_local', 'throw', 'true', 'try',
'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using', 'virtual', 'void', 'volatile',
'wchar_t', 'while', 'xor', 'xor_eq'}
...@@ -459,6 +459,9 @@ class GpuArrayType(Type): ...@@ -459,6 +459,9 @@ class GpuArrayType(Type):
else: else:
return np.dtype(self.dtype).itemsize return np.dtype(self.dtype).itemsize
def c_element_type(self):
return pygpu.gpuarray.dtype_to_ctype(self.dtype)
def c_declare(self, name, sub, check_input=True): def c_declare(self, name, sub, check_input=True):
return """ return """
PyGpuArrayObject *%(name)s; PyGpuArrayObject *%(name)s;
......
...@@ -349,6 +349,9 @@ class Scalar(Type): ...@@ -349,6 +349,9 @@ class Scalar(Type):
return True return True
return abs(diff) <= (abs(a) * tolerance) + (abs(b) * tolerance) return abs(diff) <= (abs(a) * tolerance) + (abs(b) * tolerance)
def c_element_type(self):
return self.dtype_specs()[1]
def c_headers(self, c_compiler): def c_headers(self, c_compiler):
l = ['<math.h>'] l = ['<math.h>']
# These includes are needed by Scalar and TensorType, # These includes are needed by Scalar and TensorType,
......
...@@ -374,6 +374,9 @@ class TensorType(Type): ...@@ -374,6 +374,9 @@ class TensorType(Type):
def __repr__(self): def __repr__(self):
return str(self) return str(self)
def c_element_type(self):
return self.dtype_specs()[1]
def c_declare(self, name, sub, check_input=True): def c_declare(self, name, sub, check_input=True):
""" """
Override `CLinkerType.c_declare`. Override `CLinkerType.c_declare`.
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论