Summary: As part of the Variable/Tensor merge work: https://github.com/pytorch/pytorch/issues/13638, we make the following changes in this PR: 1. Remove the `Variable::Impl` class and the `DifferentiableViewImpl` class 2. Change all `Variable.data()` call sites to either use `Variable` directly, or use `Variable.tensor_data()` 3. Remove `Variable.data()` API 3. Add `Variable.variable_data()` that matches `tensor.data` in Python API, which creates a new `Variable` that shares the same storage and tensor metadata with the original `Variable`, but with a completely new autograd history. After this PR, Variable doesn't wrap a Tensor internally anymore, and both Variable and Tensor use the same TensorImpl class as its `impl_`. The only difference is that Variable always has AutogradMeta in its TensorImpl, but Tensor doesn't. **Note that this PR is BC-breaking in the following use cases:** **Use Case 1:** Previously, `x.data = y` works even if `x` and `y` are of different TensorImpl type (e.g. `x` is a CPU dense tensor whose impl is of type TensorImpl, while `y` is a CPU sparse tensor whose impl is of type SparseTensorImpl). However, after this PR, `x.data = y` doesn't work anymore if `x` and `y` are of different TensorImpl type, because the underlying implementation `variable.set_data(tensor)` no longer works if `variable` and `tensor` have different TensorImpl type. **Use Case 2:** If a tensor `x`'s `grad` is sparse, accumulating dense gradients to `x` will change the tensor that `x.grad` is pointing to. This is better illustrated with the following example: ```python params = torch.tensor([1.5, 1.5]).requires_grad_() with torch.no_grad(): # Change gradient to a sparse tensor params.grad = torch.sparse_coo_tensor(torch.tensor([[1, 1]]).long(), torch.tensor([1., 1.])) grad_saved = params.grad params.backward(torch.tensor([1.5, 1.5])) assert id(grad_saved) == id(params.grad) # This will fail after this PR ``` The assertion in the last line will fail after this PR, because adding dense gradients to sparse gradients will change the `params.grad` tensor reference. Pull Request resolved: https://github.com/pytorch/pytorch/pull/17072 Differential Revision: D14075257 Pulled By: yf225 fbshipit-source-id: 0e681df641270dea586042dd26db59f2e76b5957
csrc
The csrc directory contains all of the code concerned with integration with Python. This is in contrast to lib, which contains the Torch libraries that are Python agnostic. csrc depends on lib, but not vice versa.
There are a number of utilities for easing integration with Python which are worth knowing about, which we briefly describe here. But the most important gotchas:
-
DO NOT forget to take out the GIL with
AutoGilbefore calling Python API or bringing aTHPObjectPtrinto scope. -
Make sure you include
Python.hfirst in your header files, before any system headers; otherwise, you will geterror: "_XOPEN_SOURCE" redefinederror. If you pay attention to warnings, you will see where you need to do this.
Notes
Note [Storage is not nullptr]
Historically, Torch supported nullptr storage, as a minor optimization to avoid having to allocate a storage object when it would be empty. However, this is actually a confusing special case to deal with, so by-in-large, PyTorch assumes that, in fact, storage is never nullptr.
One important case where this assumption is important is when tracking the CUDA device a tensor is stored in: this information is stored solely in the storage, so if a storage is nullptr, we lose this information.
Although storage is never nullptr, the data field of THStorage may be nullptr. This mostly occurs when we want to pre-allocate an output tensor struct, but then have it be resized and filled with data by some operator: there's no point in allocating data for it in this case!
Files
Exceptions.h
Frequently when working with the Python API, you may call a function which returns an error. In this case, we want to return directly to the Python interpreter, so that this exception can be propagated accordingly; however, because the Python API is C-based, what actually will happen is it will return control to whatever C++ code called it. Similarly, if we raise a C++ exception, prior to returning to the Python interpreter, we must set the Python error flags, so it turns into a C++ exception.
Exceptions defines some useful helpers: HANDLE_TH_ERRORS, END_HANDLE_TH_ERRORS
and an exception class python_error. You call them like this:
// Entry point from Python interpreter
PyObject* run() {
HANDLE_TH_ERRORS
...
if (!x) throw python_error();
...
END_HANDLE_TH_ERRORS
}
The HANDLE_TH_ERRORS macro will catch all exceptions and convert them
into an appropriate Python signal. python_error is a special
exception which doesn't contain any info, instead it says, "An error
occurred in the Python API; if you return to the interpreter, Python
will raise that exception, nothing else needs to be done."
utils/auto_gil.h
Whenever you make any calls to the Python API, you must have taken out
the Python GIL, as none of these calls are thread safe. AutoGIL is
a RAII struct which handles taking and releasing the GIL. Use it like
this:
void iWantToUsePython() {
AutoGil gil;
...
}
In general, the compiler will NOT warn you if you use Python functionality without taking out the GIL, so DO NOT FORGET this call.
utils/object_ptr.h
THPPointer is a smart pointer class analogous to std::shared_ptr,
but which is overloaded to handle reference counting scheme of various
objects which are not based on shared_ptr. The most important overloads are:
-
PyObject(so important we've aliased it asTHPObjectPtr), which hooks into Python reference counting. (By the way, that means you MUST take out the GIL before bringing one of these into scope!) -
The various TH tensor and storage types (e.g.,
THTensor), which hook into TH's reference counting. (TH's reference counting IS thread safe, no locks necessary.)