You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: _posts/2017-5-11-Internals.md
+29-24Lines changed: 29 additions & 24 deletions
Original file line number
Diff line number
Diff line change
@@ -56,7 +56,7 @@ typedef struct {
56
56
The `PyObject_HEAD` is a macro that brings in the code that implements an object's reference counting, and a pointer to the corresponding type object. So in this case, to implement a float, the only other "state" needed is the floating point value itself.
57
57
58
58
Now, let's see the struct for our `THPTensor` type:
59
-
```
59
+
```cpp
60
60
structTHPTensor {
61
61
PyObject_HEAD
62
62
THTensor *cdata;
@@ -65,7 +65,7 @@ struct THPTensor {
65
65
Pretty simple, right? We are just wrapping the underlying `TH` tensor by storing a pointer to it.
66
66
67
67
The key part is defining the "type object" for a new type. An example definition of a type object for our Python float takes the form:
68
-
```
68
+
```cpp
69
69
static PyTypeObject py_FloatType = {
70
70
PyVarObject_HEAD_INIT(NULL, 0)
71
71
"py.FloatObject", /* tp_name */
@@ -97,16 +97,16 @@ The type object for our `THPTensor` is `THPTensorType`, defined in `csrc/generic
97
97
98
98
As an example, let's take a look at the `tp_new` function we set in the `PyTypeObject`:
99
99
100
-
```
100
+
```cpp
101
101
PyTypeObject THPTensorType = {
102
102
PyVarObject_HEAD_INIT(NULL, 0)
103
103
...
104
104
THPTensor_(pynew), /* tp_new */
105
105
};
106
106
```
107
-
The `tp_new` function enables object creation. It is responsible for creating (as opposed to initializing) objects of that type and is equivalent to the `__new()__` method at the Python level. The C implementation is a static method that is passed the type being instantiated and any arguments, and returns a newly created object.
107
+
The `tp_new` function enables object creation. It is responsible for creating (as opposed to initializing) objects of that type and is equivalent to the `__new__()` method at the Python level. The C implementation is a static method that is passed the type being instantiated and any arguments, and returns a newly created object.
The first thing our new function does is allocate the `THPTensor`. It then runs through a series of initializations based off of the args passed to the function. For example, when creating a `THPTensor` *x* from another `THPTensor` *y*, we set the newly created `THPTensor`'s `cdata` field to be the result of calling `THTensor_(newWithTensor)` with the *y*'s underlying `TH` Tensor as an argument. Similar constructors exist for sizes, storages, NumPy arrays, and sequences.
119
119
120
-
** Note that we solely use `tp_new`, and not a combination of `tp_new` and `tp_init` (which corresponds to the `__init()__` function).
120
+
** Note that we solely use `tp_new`, and not a combination of `tp_new` and `tp_init` (which corresponds to the `__init__()` function).
121
121
122
122
The other important thing defined in Tensor.cpp is how indexing works. PyTorch Tensors support Python's **Mapping Protocol**. This allows us to do things like:
123
123
```python
@@ -136,7 +136,7 @@ The most important methods are `THPTensor_(getValue)` and `THPTensor_(setValue)`
136
136
---
137
137
138
138
We could spend a ton of time exploring various aspects of the `THPTensor` and how it relates to defining a new Python object. But we still need to see how the `THPTensor_(init)()` function is translated to the `THPIntTensor_init()` we used in our module initialization. How do we take our `Tensor.cpp` file that defines a "generic" Tensor and use it to generate Python objects for all the permutations of types? To put it another way, `Tensor.cpp` is littered with lines of code like:
This illustrates both cases we need to make type-specific:
@@ -149,15 +149,15 @@ In other words, for all supported Tensor types, we need to "generate" source cod
149
149
One component building an Extension module using Setuptools is to list the source files involved in the compilation. However, our `csrc/generic/Tensor.cpp` file is not listed! So how does the code in this file end up being a part of the end product?
150
150
151
151
Recall that we are calling the `THPTensor*` functions (such as `init`) from the directory above `generic`. If we take a look in this directory, there is another file `Tensor.cpp` defined. The last line of this file is important:
Note that this `Tensor.cpp` file is included in `setup.py`, but it is wrapped in a call to a Python helper function called `split_types`. This function takes as input a file, and looks for the "//generic_include" string in the file contents. If it is found, it generates a new output file for each Tensor type, with the following changes:
156
156
157
157
- The output file is renamed to `Tensor<Type>.cpp`
158
158
- The output file is slightly modified as follows:
@@ -166,7 +166,8 @@ Note that this `Tensor.cpp` file is included in `setup.py`, but it is wrapped in
166
166
#include "TH/THGenerate<Type>Type.h"
167
167
```
168
168
Including the header file on the second line has the side effect of including the source code in `Tensor.cpp` with some additional context defined. Let's take a look at one of the headers:
169
-
```
169
+
170
+
```cpp
170
171
#ifndef TH_GENERIC_FILE
171
172
#error "You must define TH_GENERIC_FILE before including THGenerateFloatType.h"
172
173
#endif
@@ -192,21 +193,22 @@ Including the header file on the second line has the side effect of including th
192
193
#undef TH_GENERIC_FILE
193
194
#endif
194
195
```
196
+
195
197
What this is doing is bringing in the code from the generic `Tensor.cpp` file and surrounding it with the following macro definitions. For example, we define real as a float, so any code in the generic Tensor implementation that refers to something as a real will have that real replaced with a float. In the corresponding file `THGenerateIntType.h`, the same macro would replace `real` with `int`.
196
198
197
199
These output files are returned from `split_types` and added to the list of source files, so we can see how the `.cpp` code for different types is created.
198
200
199
-
There are a few things to note here: First, the `split_types` function is not strictly necessary. We could wrap the code in `Tensor.cpp` in a single file, repeating it for each type. The reason we split the code into separate files is to speed up compilation. Second, what we mean when we talk about the type replacement (e.g. replace real with a float) is that the C preprocessor will perform these subsitutions during compilaiton. Merely surrounding the source code with these macros has no side effects until preprocessing.
201
+
There are a few things to note here: First, the `split_types` function is not strictly necessary. We could wrap the code in `Tensor.cpp` in a single file, repeating it for each type. The reason we split the code into separate files is to speed up compilation. Second, what we mean when we talk about the type replacement (e.g. replace real with a float) is that the C preprocessor will perform these substitutions during compilation. Merely surrounding the source code with these macros has no side effects until preprocessing.
200
202
201
203
### Generic Builds (Part Two)
202
204
---
203
205
204
206
Now that we have source files for all the Tensor types, we need to consider how the corresponding header declarations are created, and also how the conversions from `THTensor_(method)` and `THPTensor_(method)` to `TH<Type>Tensor_method` and `THP<Type>Tensor_method` work. For example, `csrc/generic/Tensor.h` has declarations like:
We use the same strategy for generating code in the source files for the headers. In `csrc/Tensor.h`, we do the following:
209
-
```
211
+
```cpp
210
212
#include "generic/Tensor.h"
211
213
#include <TH/THGenerateAllTypes.h>
212
214
@@ -216,26 +218,28 @@ We use the same strategy for generating code in the source files for the headers
216
218
This has the same effect, where we draw in the code from the generic header, wrapped with the same macro definitions, for each type. The only difference is that the resulting code is contained all within the same header file, as opposed to being split into multiple source files.
217
219
218
220
Lastly, we need to consider how we "convert" or "substitute" the function types. If we look in the same header file, we see a bunch of `#define` statements, including:
This macro says that any string in the source code matching the format `THPTensor_(NAME)` should be replaced with `THPRealTensor_NAME`, where Real is derived from whatever the symbol Real is `#define`'d to be at the time. Because our header code and source code is surrounded by macro definitions for all the types as seen above, after the preprocessor has run, the resulting code is what we would expect. The code in the `TH` library defines the same macro for `THTensor_(NAME)`, supporting the translation of those functions as well. In this way, we end up with header and source files with specialized code.
223
-
####Module Objects and Type Methods
225
+
226
+
#### Module Objects and Type Methods
227
+
224
228
Now we have seen how we have wrapped `TH`'s Tensor definition in `THP`, and generated THP methods such as `THPFloatTensor_init(...)`. Now we can explore what the above code actually does in terms of the module we are creating. The key line in `THPTensor_(init)` is:
225
-
```
229
+
```cpp
226
230
# THPTensorBaseStr, THPTensorType are also macros that are specific
This function registers our Tensor objects to the extension module, so we can use THPFloatTensor, THPIntTensor, etc. in our Python code.
231
235
232
236
Just being able to create Tensors isn't very useful - we need to be able to call all the methods that `TH` defines. A simple example shows calling the in-place `zero_` method on a Tensor.
233
-
```
237
+
```python
234
238
x = torch.FloatTensor(10)
235
239
x.zero_()
236
240
```
237
241
Let's start by seeing how we add methods to newly defined types. One of the fields in the "type object" is `tp_methods`. This field holds an array of method definitions (`PyMethodDef`s) and is used to associate methods (and their underlying C/C++ implementations) with a type. Suppose we wanted to define a new method on our `PyFloatObject` that replaces the value. We could implement this as follows:
It is instructive to read more about how defining methods works in CPython. In general, methods take as the first parameter the instance of the object, and optionally parameters for the positional arguments and keyword arguments. This static function is registered as a method on our float:
This registers a method called replace, which is implemented by the C function of the same name. The `METH_VARARGS` flag indicates that the method takes a tuple of arguments representing all the arguments to the function. This array is set to the `tp_methods` field of the type object, and then we can use the `replace` method on objects of that type.
262
266
263
267
We would like to be able to call all of the methods for `TH` tensors on our `THP` tensor equivalents. However, writing wrappers for all of the `TH` methods would be time-consuming and error prone. We need a better way to do this.
268
+
264
269
### PyTorch cwrap
265
270
---
266
-
PyTorch implements its own cwrap tool to wrap the `TH` Tensor methods for use in the Python backend. We define a `.cwrap` file containing a series of C method declarations in our custom YAML format(http://yaml.org). The cwrap tool takes this file and outputs `.cpp` source files containing the wrapped methods in a format that is compatible with our `THPTensor` Python object and the Python C extension method calling format. This tool is used to generate code to wrap not only `TH`, but also `CuDNN`. It is defined to be extensible.
271
+
PyTorch implements its own cwrap tool to wrap the `TH` Tensor methods for use in the Python backend. We define a `.cwrap` file containing a series of C method declarations in our custom [YAML format](http://yaml.org). The cwrap tool takes this file and outputs `.cpp` source files containing the wrapped methods in a format that is compatible with our `THPTensor` Python object and the Python C extension method calling format. This tool is used to generate code to wrap not only `TH`, but also `CuDNN`. It is defined to be extensible.
267
272
268
273
An example YAML "declaration" for the in-place `addmv_` function is as follows:
269
274
```
@@ -284,7 +289,7 @@ An example YAML "declaration" for the in-place `addmv_` function is as follows:
284
289
```
285
290
The architecture of the cwrap tool is very simple. It reads in a file, and then processes it with a series of **plugins.** See `tools/cwrap/plugins/__init__.py` for documentation on all the ways a plugin can alter the code.
286
291
287
-
The source code generation occurs in a series of passes. First, the YAML "declaration" is parsed and processed. Then the source code is generated piece-by-piece - adding things like argument checks and extractions, defining the method header, and the actual call to the underlying library such as `TH`. Finally, the cwrap tool allows for processing the entire file at a time. The resulting output for `addmv_` can be explored here: https://gist.github.com/killeent/c00de46c2a896335a52552604cc4d74b.
292
+
The source code generation occurs in a series of passes. First, the YAML "declaration" is parsed and processed. Then the source code is generated piece-by-piece - adding things like argument checks and extractions, defining the method header, and the actual call to the underlying library such as `TH`. Finally, the cwrap tool allows for processing the entire file at a time. The resulting output for `addmv_` can be [explored here](https://gist.github.com/killeent/c00de46c2a896335a52552604cc4d74b).
288
293
289
294
In order to interface with the CPython backend, the tool generates an array of `PyMethodDef`s that can be stored or appended to the `THPTensor`'s `tp_methods` field.
290
295
@@ -322,4 +327,4 @@ This is just a snapshot of parts of the build system for PyTorch. There is more
322
327
### Resources:
323
328
---
324
329
325
-
-https://docs.python.org/3.7/extending/index.html is invaluable for understanding how to write C/C++ Extension to Python
330
+
-<https://docs.python.org/3.7/extending/index.html> is invaluable for understanding how to write C/C++ Extension to Python
0 commit comments