Skip to content

Commit

Permalink
Rework COp to allow all methods to be defined.
Browse files Browse the repository at this point in the history
  • Loading branch information
abergeron committed Dec 19, 2014
1 parent 0056cb7 commit a9acfba
Show file tree
Hide file tree
Showing 2 changed files with 353 additions and 259 deletions.
222 changes: 123 additions & 99 deletions doc/tutorial/extending_theano_c.txt
Original file line number Diff line number Diff line change
Expand Up @@ -688,14 +688,13 @@ To help with this, Theano defines a class, ``COp``, from which new C ops
can inherit. The class ``COp`` aims to simplify the process of implementing
C ops by doing the following :

* It allows you to define the C implementation of your op in a distinct
C code file. This makes it easier to keep your Python and C code
readable and well indented.
* It allows you to define the C implementation of your op in a distinct
C code file. This makes it easier to keep your Python and C code
readable and well indented.

* It automatically handles the methods :meth:`Op.c_code()`,
:meth:`Op.c_support_code()`, :meth:`Op.c_support_code_apply()` and
:meth:`Op.c_code_cache_version()` based on the provided external C
implementation.
* It can automatically handle all the methods that return C code,
in addition to :meth:`Op.c_code_cache_version()` based on the
provided external C implementation.

To illustrate how much simpler the class ``COp`` makes the process of defining
a new op with a C implementation, let's revisit the second example of this
Expand Down Expand Up @@ -740,7 +739,7 @@ C file named vectorTimesVector.c :

.. code-block:: c

THEANO_SUPPORT_CODE_SECTION
#section support_code

// Support code function
bool vector_same_shape(PyArrayObject* arr1, PyArrayObject* arr2)
Expand All @@ -749,7 +748,7 @@ C file named vectorTimesVector.c :
}


THEANO_APPLY_CODE_SECTION
#section support_code_apply

// Apply-specific support function
void APPLY_SPECIFIC(vector_elemwise_mult)(
Expand Down Expand Up @@ -822,45 +821,44 @@ this new version of the VectorTimesVector op :
* Parent class : instead of inheriting from the class :class:`Op`,
VectorTimesVector inherits from the class ``COp``.

* Constructor : in our new op, the ``__init__()`` method has an important
use; to inform the constructor of the ``COp`` class of the location,
on the filesystem of the C implementation of this op. To do this, it
gives the path of file containing the C code as well as the name of
the function, in that file, that should be called to perform the
computation. The path should be given as a relative path from the
folder where the descendant of the ``COp`` class is defined.

* ``make_node()`` : the ``make_node()`` method is absolutely identical to
the one in our old example. Using the ``COp`` class doesn't change
anything here.

* External C code : the external C code performs the computation
associated with the op. It contains, at the very least, a 'main' function
having the same name as provided to the constructor of the Python class
``COp``. Writing this C code involves a few subtleties which deserve their
own respective sections.

* Constructor : in our new op, the ``__init__()`` method has an
important use; to inform the constructor of the ``COp`` class
of the location, on the filesystem of the C implementation of
this op. To do this, it gives a list of file paths containing
the C code for this op. To auto-generate the c_code method
with a function call you can specify the function name as the
second parameter. The paths should be given as a relative
path from the folder where the descendant of the ``COp`` class
is defined.

* ``make_node()`` : the ``make_node()`` method is absolutely
identical to the one in our old example. Using the ``COp``
class doesn't change anything here.

* External C code : the external C code implements the various
functions associated with the op. associated with the op.
Writing this C code involves a few subtleties which deserve
their own respective sections.

Main function
-------------

The external C implementation must implement a main function whose name
is passed by the op to the ``__init__()`` method of the ``COp`` class. This
main C function must respect the following constraints :
If you pass a function name to the ``__init__()`` method of the ``COp`` class, it must respect the following constraints:

* It must return an int. The value of that int indicates whether the
op could perform its task or not. A value of 0 indicates success while
any non-zero value will interrupt the execution of the Theano function.
Before returning a non-zero integer, the main function should call the
function ``PyErr_Format()`` to setup a Python exception.
* It must return an int. The value of that int indicates whether
the op could perform its task or not. A value of 0 indicates
success while any non-zero value will interrupt the execution
of the Theano function. When returning non-zero the function
must set a python exception indicating the details of the
problem.

* It must receive one pointer for each input to the op followed by one
pointer to a pointer for each output of the op.
* It must receive one argument for each input to the op followed by one
pointer to an argument for each output of the op.

For example, the main C function of an op that takes two scalars as inputs and
returns both their sum and the difference between them would have four
parameters (two for the op's inputs and two for its outputs) and it's
signature would look something like this :
For example, the main C function of an op that takes two arrays as
inputs and returns both their sum and the difference between them
would have four parameters (two for the op's inputs and two for its
outputs) and it's signature would look something like this :

.. code-block:: c

Expand All @@ -870,11 +868,21 @@ signature would look something like this :
Macros
------

The ``COp`` class defines a number of macros that can you can use in your C
implementation to make it simpler and more generic.
For certain section tags, your C code can benefit from a number of
pre-defined macros. These section tags have no macros: ``init_code``,
``c_support_code``. All other tags will have the support macros
discussed below.

For every input array 'i' (indexed from 0) of the op, the following macros are
defined:
* ``APPLY_SPECIFIC(str)`` which will automatically append a name
unique to the :ref:`Apply node that applies the Op at the end
of the provided ``str``. The use of this macro is discussed
futher below.

For every input which has a :attr:`dtype` attribute (this means
Tensors, and equivalent types on GPU), the following macros will be
defined unless your Op class has an :attr:`Op.check_input` attribute
defined to False. In these descrptions 'i' refers to the position
(indexed from 0) in the input array.

* ``DTYPE_INPUT_{i}`` : NumPy dtype of the data in the array.
This is the variable type corresponding to the NumPy dtype, not the
Expand All @@ -889,71 +897,87 @@ defined:

* ``TYPENUM_INPUT_{i}`` : Typenum of the data in the array

* ``ITEMSIZE_INPUT_{i}`` : Size, in bytes, of the elements in the array.
* ``ITEMSIZE_INPUT_{i}`` : Size, in bytes, of the elements in
the array.

In the same way, the macros ``DTYPE_OUTPUT_{i}``,
``ITEMSIZE_OUTPUT_{i}`` and ``TYPENUM_OUTPUT_{i}`` are defined for
every output 'i' of the op.

In the same way, the macros ``DTYPE_OUTPUT_{i}``, ``ITEMSIZE_OUTPUT_{i}`` and
``TYPENUM_OUTPUT_{i}`` are defined for every output 'i' of the op.
In addition to these macros, the ``init_code_struct``, ``code``, and
``code_cleanup`` also have the following macros:

* ``FAIL`` : Code to insert at error points. A python exception
should be set prior to this code. An invocation look like this:

.. code-block:: c

if (error) {
// Set python exception
FAIL
}

The ``COp`` class also defines the macro ``APPLY_SPECIFIC(str)`` which will
automatically append the name of the :ref:`Apply node that applies the Op at
the end of the provided ``str``. The use of this macro is discussed below.
You can add a semicolon after the macro if it makes your editor
happy.

You should be aware, however, that these macros are apply-specific. As such,
any function that uses them is considered to contain apply-specific code.
* ``CONTEXT`` : Name of the context variable for this node. (only
for Ops which have a context, which is discussed elsewhere)

Finally the tag ``code`` and ``code_cleanup`` have macros to
pass the inputs and output names. These are name ``INPUT_{i}`` and
``OUTPUT_{i}`` where `i` is the 0-based index position in the input
and output arrays respectively.

Support code
------------

The file whose name is provided to the ``COp`` class is not constrained to
contain only one function. It can in fact contain many functions, with every
function but the main one acting as support code.

When we defined the VectorTimesVector op without using the ``COp`` class, we
had to make a distinction between two types of support_code : the support
code that was apply-specific and the support code that wasn't.
The apply-specific code was defined in the ` c_support_code_apply()`` method
and the elements defined in that code (global variables and functions) had to
include the name of the Apply node in their own names to avoid conflicts
between the different versions of the apply-specific code. The code that
wasn't apply-specific was simply defined in the ``c_support_code()`` method.

When using the ``COp`` class, we still have to make the distinction between
apply-specific and apply-agnostic support code but we express it differently
in the code since it is all defined in the same external C file.
These two types of support code should each be defined in their own section of
the file, like in the example above. These sections should be delimited by the
markers ``THEANO_SUPPORT_CODE_SECTION`` (to be put on its own line, at the
beginning of the apply-agnostic support code section) and
``THEANO_APPLY_CODE_SECTION`` (to be put on its own line at the beginning of
the apply-specific code section). Moreover, just like in the previous examples
of this tutorial, apply-specific functions and global variables need to
include the name of the :ref:`Apply` node in their names. To achieve this,
the macro ``APPLY_SPECIFIC(str)`` should be used when defining those elements
as well as when referring to them. In the above example, this macro is used
when defining the functions ``vector_elemwise_mult()`` and
Certain section are limited in what you can place in them due to
semantic and syntactic restrictions of the C++ language. Most of
these restrictions apply to the tags that end in ``_struct``.

When we defined the VectorTimesVector op without using the ``COp``
class, we had to make a distinction between two types of support_code
: the support code that was apply-specific and the support code that
wasn't. The apply-specific code was defined in the
``c_support_code_apply()`` method and the elements defined in that
code (global variables and functions) had to include the name of the
Apply node in their own names to avoid conflicts between the different
versions of the apply-specific code. The code that wasn't
apply-specific was simply defined in the ``c_support_code()`` method.

To make indentifiers that include the :ref:`Apply` node name use the
``APPLY_SPECIFIC(str)`` macro. In the above example, this macro is
used when defining the functions ``vector_elemwise_mult()`` and
``vector_times_vector()`` as well as when calling function
``vector_elemwise_mult()`` from inside ``vector_times_vector()``.

:note:

The macro ``APPLY_SPECIFIC(str)`` should only ever be used for
apply-specific code. It should not be used for apply-agnostic code.

The rules for knowing if a piece of code should be put in the apply-agnostic
or the apply-specific support code section of the file are simple. If it uses
any of the macros defined by the class ``COp`` then it is apply-specific and
goes in the corresponding section. If it calls any apply-specific code then
it is apply-specific. Otherwise, it is apply-agnostic and goes in the
apply-agnostic support code section.

In the above example, the ``function vector_same_shape()`` is apply-agnostic
because it uses none of the macros defined by the class ``COp`` and it doesn't
rely on any apply-specific code. The function ``vector_elemwise_mult()`` is
apply-specific because it uses the macros defined by ``COp``. Finally, the
function ``vector_times_vector()`` is apply-specific because it uses those
same macros and also because it calls ``vector_elemwise_mult()`` which is an
apply-specific function.
When using the ``COp`` class, we still have to make the distinction
between C code for each of the methods of a C class. These sections of
code are separated by ``#section <tag>`` markers. The tag determines
the name of the method this C code applies to with the rule that
``<tag>`` applies to `c_<tag>`. Unknown tags are an error and will be
reported. Duplicate tags will be merged together in the order the
appear in the C files.

The rules for knowing if where a piece of code should be put can be
sometimes tricky. The key thing to remember is that things that can
be shared between instances of the op should be apply-agnostic and go
into a section which does not end in ``_apply`` or ``_struct``. The
distinction of ``_apply`` and ``_struct`` mostly hinghes on how you
want to manange the lifetime of the object. Note that to use an
apply-specific object, you have to be in a apply-specific section, so
some portions of the code that might seem apply-agnostic may still be
apply-specific because of the data they use (this does not include
arguments).

In the above example, the ``function vector_same_shape()`` is
apply-agnostic because it uses none of the macros defined by the class
``COp`` and it doesn't rely on any apply-specific code. The function
``vector_elemwise_mult()`` is apply-specific because it uses the
macros defined by ``COp``. Finally, the function
``vector_times_vector()`` is apply-specific because it uses those same
macros and also because it calls ``vector_elemwise_mult()`` which is
an apply-specific function.

Final Note
==========
Expand Down
Loading

0 comments on commit a9acfba

Please sign in to comment.