Caffe2 - C++ API
A deep learning, cross platform ML framework
python_variable.cpp
1 #include <torch/csrc/autograd/python_variable.h>
2 
3 #include <torch/csrc/THP.h>
4 #include <torch/csrc/DynamicTypes.h>
5 #include <torch/csrc/Exceptions.h>
6 #include <torch/csrc/Device.h>
7 #include <torch/csrc/Size.h>
8 #include <torch/csrc/Types.h>
9 #include <torch/csrc/autograd/edge.h>
10 #include <torch/csrc/autograd/python_cpp_function.h>
11 #include <torch/csrc/autograd/python_hook.h>
12 #include <torch/csrc/autograd/python_variable_indexing.h>
13 #include <torch/csrc/autograd/variable.h>
14 #include <torch/csrc/autograd/functions/accumulate_grad.h>
15 #include <torch/csrc/autograd/function.h>
16 #include <torch/csrc/autograd/generated/VariableType.h>
17 #include <torch/csrc/autograd/utils/python_error_messages.h>
18 #include <torch/csrc/autograd/utils/wrap_outputs.h>
19 #include <torch/csrc/tensor/python_tensor.h>
20 #include <torch/csrc/utils/auto_gil.h>
21 #include <torch/csrc/utils/cuda_lazy_init.h>
22 #include <torch/csrc/utils/pybind.h>
23 #include <torch/csrc/utils/python_strings.h>
24 #include <torch/csrc/utils/python_arg_parser.h>
25 #include <torch/csrc/utils/tensor_new.h>
26 #include <torch/csrc/jit/tracer.h>
27 
28 #include <ATen/ATen.h>
29 #include <pybind11/pybind11.h>
30 
31 #include <structmember.h>
32 #include <memory>
33 #include <utility>
34 #include <vector>
35 
36 using namespace at;
37 using namespace torch;
38 using namespace torch::autograd;
39 
40 namespace py = pybind11;
41 
42 PyObject *THPVariableClass = nullptr;
43 
44 static const char* VOLATILE_WARNING =
45  "volatile was removed and now has no effect. Use "
46  "`with torch.no_grad():` instead.";
47 
48 // Creates a new Python object for a Variable. The Variable must not already
49 // have a PyObject* associated with it.
50 static PyObject* THPVariable_NewWithVar(PyTypeObject* type, Variable var)
51 {
52  PyObject* obj = type->tp_alloc(type, 0);
53  if (obj) {
54  auto v = (THPVariable*) obj;
55  new (&v->cdata) Variable(std::move(var));
56  v->cdata.set_pyobj(obj);
57  if (auto fn = dynamic_cast<PyFunction*>(v->cdata.grad_fn_unsafe())) {
58  // Create a new reference to the THPFunction. This ensures that ref count
59  // of the THPFunction is at least the number of referring THPVariables.
60  const auto output_nr = v->cdata.output_nr();
61  auto grad_fn = THPFunction_asFunction((THPFunction*)fn->obj);
62  v->cdata.set_gradient_edge({std::move(grad_fn), output_nr});
63  }
64  }
65  return obj;
66 }
67 
68 PyObject * THPVariable_Wrap(Variable var)
69 {
70  if (!var.defined()) {
71  Py_RETURN_NONE;
72  }
73 
74  if (auto obj = var.pyobj()) {
75  Py_INCREF(obj);
76  return obj;
77  }
78 
79  return THPVariable_NewWithVar((PyTypeObject *)THPVariableClass, std::move(var));
80 }
81 
82 static int THPVariable_traverse(THPVariable *self, visitproc visit, void *arg)
83 {
84  Py_VISIT(self->backward_hooks);
85  // We don't want to traverse the grad_fn, even if the Variable owns it and the
86  // shared pointer's use count is 1. This is because we would need to treat
87  // the grad_fn as part of the Python state and hold the GIL sometimes when
88  // grad_fn's shared_ptr is copied, otherwise a race condition with the Python
89  // GC could occur. Holding the GIL when the shared_ptr is copied adds
90  // undesirable complexity/overhead.
91  //
92  // When hooks, a Variable, and its grad_fn are involved in a Python reference
93  // cycle, because we're not traversing the grad_fn, the reference cycle will
94  // in fact leak.
95  //
96  // See https://gist.github.com/zou3519/7ac92b84dd7d206dcc6eae55fee8372c
97  // for more details about the race condition involving traversing the grad_fn
98  // and the python GC.
99  if (self->cdata.defined()) {
100  for (const auto& hook : self->cdata.hooks()) {
101  if (auto pyhook = dynamic_cast<PyFunctionPreHook*>(hook.get())) {
102  Py_VISIT(pyhook->dict);
103  }
104  }
105  }
106  return 0;
107 }
108 
109 static int THPVariable_clear(THPVariable *self)
110 {
111  Py_CLEAR(self->backward_hooks);
112  if (self->cdata.defined()) {
113  if (auto grad_acc = self->cdata.try_get_grad_accumulator()) {
114  grad_acc->pre_hooks().clear();
115  }
116  self->cdata.set_pyobj(nullptr);
117  }
118  self->cdata.reset();
119  return 0;
120 }
121 
122 static void THPVariable_dealloc(THPVariable* self)
123 {
124  PyObject_GC_UnTrack(self);
125  THPVariable_clear(self);
126  self->cdata.~Variable();
127  Py_TYPE(self)->tp_free((PyObject*)self);
128 }
129 
130 static PyObject *THPVariable_pynew(PyTypeObject *type, PyObject *args, PyObject *kwargs)
131 {
132  HANDLE_TH_ERRORS
133  jit::tracer::warn("torch.Tensor", jit::tracer::WARN_CONSTRUCTOR);
134  auto& default_type = torch::tensors::get_default_tensor_type();
135  auto tensor = torch::utils::legacy_tensor_ctor(default_type, args, kwargs);
136  return THPVariable_NewWithVar(type, std::move(tensor));
137  END_HANDLE_TH_ERRORS
138 }
139 
140 // Instantiates a subclass of torch.Tensor. Used by nn.Parameter()
141 static PyObject* THPVariable_make_subclass(PyObject* _ignored, PyObject* args, PyObject* kwargs) {
142  HANDLE_TH_ERRORS
143  static PythonArgParser parser({
144  "_make_subclass(PyObject* cls, Tensor data, bool require_grad=False)",
145  });
146  ParsedArgs<3> parsed_args{};
147  auto r = parser.parse(args, kwargs, parsed_args);
148  PyObject* cls = r.pyobject(0);
149  if (!PyType_Check(cls)) {
150  throw TypeError("cls must be a type (got %s)", Py_TYPE(cls)->tp_name);
151  }
152  auto& data = as_variable_ref(r.tensor(1)).data();
153  auto var = make_variable(data, r.toBool(2));
154  return THPVariable_NewWithVar((PyTypeObject*)cls, std::move(var));
155  END_HANDLE_TH_ERRORS
156 }
157 
158 typedef PyObject *(*getter)(PyObject *, void *);
159 typedef int (*setter)(PyObject *, PyObject *, void *);
160 
161 PyObject *THPVariable_get_cdata(THPVariable *self)
162 {
163  HANDLE_TH_ERRORS
164  auto& var = self->cdata;
165  return PyLong_FromVoidPtr(var.data().unsafeGetTensorImpl());
166  END_HANDLE_TH_ERRORS
167 }
168 
169 PyObject *THPVariable_get_version(THPVariable *self)
170 {
171  HANDLE_TH_ERRORS
172  auto& var = self->cdata;
173  return PyInt_FromLong(var.current_version());
174  END_HANDLE_TH_ERRORS
175 }
176 
177 PyObject *THPVariable_get_grad_fn(THPVariable *self)
178 {
179  HANDLE_TH_ERRORS
180  auto& var = self->cdata;
181  if (!var.grad_fn()) {
182  Py_RETURN_NONE;
183  }
184  return functionToPyObject(var.grad_fn());
185  END_HANDLE_TH_ERRORS
186 }
187 
188 static int THPVariable_set_grad_fn(THPVariable *self, PyObject *obj)
189 {
190  HANDLE_TH_ERRORS
191  THPUtils_assertRet(-1, obj, "Deletion of _grad_fn not allowed. Detach tensor instead!");
192  THPUtils_assertRet(-1, obj == Py_None, "_grad_fn can be only set to None");
193  self->cdata.detach_();
194  return 0;
195  END_HANDLE_TH_ERRORS_RET(-1)
196 }
197 
198 static PyObject *THPVariable_is_leaf(THPVariable *self)
199 {
200  HANDLE_TH_ERRORS
201  return PyBool_FromLong(!self->cdata.grad_fn());
202  END_HANDLE_TH_ERRORS
203 }
204 
205 static PyObject * THPVariable_get_data(THPVariable *self)
206 {
207  HANDLE_TH_ERRORS
216  auto var = make_variable(self->cdata.data(), /*requires_grad=*/false, /*allow_tensor_metadata_change=*/false);
217  return THPVariable_Wrap(var);
218  END_HANDLE_TH_ERRORS
219 }
220 
221 int THPVariable_set_data(THPVariable *self, PyObject *data)
222 {
223  HANDLE_TH_ERRORS
224  THPUtils_assertRet(-1, data, "Deleting tensor data is not allowed. Delete tensor instead!");
225  if (!THPVariable_Check(data)) {
226  throw torch::TypeError("Variable data has to be a tensor, but got %s", Py_TYPE(data)->tp_name);
227  }
228 
229  self->cdata.set_data(THPVariable_UnpackData(data));
230  return 0;
231  END_HANDLE_TH_ERRORS_RET(-1)
232 }
233 
234 PyObject *THPVariable_get_grad(THPVariable *self)
235 {
236  HANDLE_TH_ERRORS
237  return THPVariable_Wrap(self->cdata.grad());
238  END_HANDLE_TH_ERRORS
239 }
240 
241 int THPVariable_set_grad(THPVariable *self, PyObject *py_grad)
242 {
243  HANDLE_TH_ERRORS
244  auto& var = self->cdata;
245  if (!py_grad || py_grad == Py_None) {
246  var.grad().reset();
247  return 0;
248  }
249 
250  THPUtils_assertRet(-1, THPVariable_Check(py_grad),
251  "expected Variable or None (got %s)", THPUtils_typename(py_grad));
252  THPUtils_assertRet(-1, self != (THPVariable*)py_grad,
253  "can't assign Variable as its own grad");
254 
255  auto& grad = ((THPVariable*)py_grad)->cdata;
256  bool gradIsSparse = false;
257  auto backend = var.is_cuda() ? Backend::SparseCUDA : Backend::SparseCPU;
258  auto typeOpt = at::globalContext().getNonVariableTypeOpt(backend, var.scalar_type());
259  if (typeOpt) {
260  auto& sparseType = at::globalContext().getNonVariableType(backend, var.scalar_type());
261  auto& gradType = at::globalContext().getNonVariableType(grad.type().backend(), grad.scalar_type());
262  gradIsSparse = gradType == sparseType;
263  }
264 
265  THPUtils_assertRet(-1, grad.type() == var.type() || gradIsSparse,
266  "assigned grad has data of a different type");
267  if (var.is_cuda()) {
268  THPUtils_assertRet(-1, grad.get_device() == var.get_device(),
269  "assigned grad has data located on a different device");
270  }
271  THPUtils_assertRet(-1, grad.sizes().equals(var.sizes()),
272  "assigned grad has data of a different size");
273 
274  var.grad() = grad;
275  return 0;
276  END_HANDLE_TH_ERRORS_RET(-1)
277 }
278 
279 PyObject *THPVariable_get_volatile(THPVariable *self)
280 {
281  const char* msg = "volatile was removed (Variable.volatile is always False)";
282  PyErr_WarnEx(PyExc_UserWarning, msg, 1);
283  Py_RETURN_FALSE;
284 }
285 
286 int THPVariable_set_volatile(THPVariable *self, PyObject *obj)
287 {
288  return PyErr_WarnEx(PyExc_UserWarning, VOLATILE_WARNING, 1);
289 }
290 
291 PyObject *THPVariable_get_output_nr(THPVariable *self)
292 {
293  HANDLE_TH_ERRORS
294  const auto output_nr = static_cast<long>(self->cdata.output_nr());
295  return PyInt_FromLong(output_nr);
296  END_HANDLE_TH_ERRORS
297 }
298 
299 PyObject *THPVariable_get_requires_grad(THPVariable *self)
300 {
301  HANDLE_TH_ERRORS
302  return PyBool_FromLong(self->cdata.requires_grad());
303  END_HANDLE_TH_ERRORS
304 }
305 
306 int THPVariable_set_requires_grad(THPVariable *self, PyObject *obj)
307 {
308  HANDLE_TH_ERRORS
309  THPUtils_assertRet(-1, obj && PyBool_Check(obj), "requires_grad must be a bool");
310  auto& var = self->cdata;
311  auto requires_grad = (obj == Py_True);
312  if (!var.is_leaf()) {
313  THPUtils_setError(autograd::utils::requires_grad_leaf_error(obj == Py_True).c_str());
314  return -1;
315  }
316  if (requires_grad && !var.is_floating_point()) {
317  THPUtils_setError("only Tensors of floating point dtype can require gradients");
318  return -1;
319  }
320  var.set_requires_grad(requires_grad);
321  return 0;
322  END_HANDLE_TH_ERRORS_RET(-1)
323 }
324 
325 PyObject *THPVariable_get_name(THPVariable* self)
326 {
327  if (self->cdata.name() == "")
328  Py_RETURN_NONE;
329  return THPUtils_packString(self->cdata.name().c_str());
330 }
331 
332 PyObject *THPVariable_get_backwards_hooks(THPVariable *self)
333 {
334  HANDLE_TH_ERRORS
335  if (self->backward_hooks) {
336  Py_INCREF(self->backward_hooks);
337  return self->backward_hooks;
338  }
339  Py_RETURN_NONE;
340  END_HANDLE_TH_ERRORS
341 }
342 
343 int THPVariable_set_backwards_hooks(THPVariable *self, PyObject *obj)
344 {
345  HANDLE_TH_ERRORS
346  THPUtils_assertRet(-1, obj, "Deletion of _backwards_hooks not allowed!");
347  if (obj == Py_None) {
348  obj = nullptr;
349  }
350  Py_XINCREF(obj);
351  Py_XDECREF(self->backward_hooks);
352  self->backward_hooks = obj;
353  self->cdata.clear_hooks();
354  if (obj) {
355  self->cdata.add_hook(std::make_shared<PyFunctionPreHook>(obj, 0));
356  }
357  return 0;
358  END_HANDLE_TH_ERRORS_RET(-1)
359 }
360 
361 PyObject *THPVariable_get_base(THPVariable *self)
362 {
363  HANDLE_TH_ERRORS
364  if (self->cdata.is_view()) {
365  return THPVariable_Wrap(self->cdata.base());
366  }
367  Py_RETURN_NONE;
368  END_HANDLE_TH_ERRORS
369 }
370 
371 PyObject *THPVariable_get_shape(THPVariable *self)
372 {
373  HANDLE_TH_ERRORS
374  return THPSize_New(self->cdata);
375  END_HANDLE_TH_ERRORS
376 }
377 
378 PyObject *THPVariable_is_cuda(THPVariable *self)
379 {
380  HANDLE_TH_ERRORS
381  auto& self_ = self->cdata;
382  return torch::autograd::utils::wrap(self_.is_cuda());
383  END_HANDLE_TH_ERRORS
384 }
385 
386 PyObject *THPVariable_is_sparse(THPVariable *self)
387 {
388  HANDLE_TH_ERRORS
389  auto& self_ = self->cdata;
390  return torch::autograd::utils::wrap(self_.is_sparse());
391  END_HANDLE_TH_ERRORS
392 }
393 
394 static PyObject *THPVariable_dtype(THPVariable *self)
395 {
396  HANDLE_TH_ERRORS
397  auto& self_ = self->cdata;
398  return torch::autograd::utils::wrap(torch::getDtype(self_.scalar_type()));
399  END_HANDLE_TH_ERRORS
400 }
401 
402 static PyObject * THPVariable_layout(THPVariable* self) {
403  HANDLE_TH_ERRORS
404  auto& self_ = self->cdata;
405  return torch::autograd::utils::wrap(torch::getLayout(self_.type().backend()));
406  END_HANDLE_TH_ERRORS
407 }
408 
409 static PyObject * THPVariable_device(THPVariable* self) {
410  HANDLE_TH_ERRORS
411  return THPDevice_New(self->cdata.device());
412  END_HANDLE_TH_ERRORS
413 }
414 
415 static struct PyGetSetDef THPVariable_properties[] = {
416  {"_cdata", (getter)THPVariable_get_cdata, nullptr, nullptr, nullptr},
417  {"_version", (getter)THPVariable_get_version, nullptr, nullptr, nullptr},
418  {"grad_fn", (getter)THPVariable_get_grad_fn, nullptr, nullptr, nullptr},
419  {"_grad_fn", (getter)THPVariable_get_grad_fn, (setter)THPVariable_set_grad_fn, nullptr, nullptr},
420  {"is_leaf", (getter)THPVariable_is_leaf, nullptr, nullptr, nullptr},
421  {"data", (getter)THPVariable_get_data, (setter)THPVariable_set_data, nullptr, nullptr},
422  {"_grad", (getter)THPVariable_get_grad, (setter)THPVariable_set_grad, nullptr, nullptr}, // only for legacy reasons
423  {"grad", (getter)THPVariable_get_grad, (setter)THPVariable_set_grad, nullptr, nullptr},
424  {"_base", (getter)THPVariable_get_base, nullptr, nullptr, nullptr},
425  {"volatile", (getter)THPVariable_get_volatile, (setter)THPVariable_set_volatile, nullptr, nullptr},
426  {"output_nr", (getter)THPVariable_get_output_nr, nullptr, nullptr, nullptr},
427  {"requires_grad", (getter)THPVariable_get_requires_grad, (setter)THPVariable_set_requires_grad, nullptr, nullptr},
428  {"_backward_hooks", (getter)THPVariable_get_backwards_hooks, (setter)THPVariable_set_backwards_hooks, nullptr, nullptr},
429  {"name", (getter)THPVariable_get_name, nullptr, nullptr, nullptr},
430  {"shape", (getter)THPVariable_get_shape, nullptr, nullptr, nullptr},
431  {"is_cuda", (getter)THPVariable_is_cuda, nullptr, nullptr, nullptr},
432  {"is_sparse", (getter)THPVariable_is_sparse, nullptr, nullptr, nullptr},
433  {"dtype", (getter)THPVariable_dtype, nullptr, nullptr, nullptr},
434  {"layout", (getter)THPVariable_layout, nullptr, nullptr, nullptr},
435  {"device", (getter)THPVariable_device, nullptr, nullptr, nullptr},
436  {nullptr}
437 };
438 
439 static PyMappingMethods THPVariable_as_mapping = {
440  THPVariable_length,
441  THPVariable_getitem,
442  THPVariable_setitem,
443 };
444 
445 static PyMethodDef extra_methods[] = {
446  {"_make_subclass", (PyCFunction)THPVariable_make_subclass, METH_STATIC | METH_VARARGS | METH_KEYWORDS, nullptr},
447  {nullptr}
448 };
449 
450 PyTypeObject THPVariableType = {
451  PyVarObject_HEAD_INIT(nullptr, 0)
452  "torch._C._TensorBase", /* tp_name */
453  sizeof(THPVariable), /* tp_basicsize */
454  0, /* tp_itemsize */
455  (destructor)THPVariable_dealloc, /* tp_dealloc */
456  nullptr, /* tp_print */
457  nullptr, /* tp_getattr */
458  nullptr, /* tp_setattr */
459  nullptr, /* tp_reserved */
460  nullptr, /* tp_repr */
461  nullptr, /* tp_as_number */
462  nullptr, /* tp_as_sequence */
463  &THPVariable_as_mapping, /* tp_as_mapping */
464  nullptr, /* tp_hash */
465  nullptr, /* tp_call */
466  nullptr, /* tp_str */
467  nullptr, /* tp_getattro */
468  nullptr, /* tp_setattro */
469  nullptr, /* tp_as_buffer */
470  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */
471  nullptr, /* tp_doc */
472  (traverseproc)THPVariable_traverse, /* tp_traverse */
473  (inquiry)THPVariable_clear, /* tp_clear */
474  nullptr, /* tp_richcompare */
475  0, /* tp_weaklistoffset */
476  nullptr, /* tp_iter */
477  nullptr, /* tp_iternext */
478  nullptr, /* tp_methods */
479  nullptr, /* tp_members */
480  THPVariable_properties, /* tp_getset */
481  nullptr, /* tp_base */
482  nullptr, /* tp_dict */
483  nullptr, /* tp_descr_get */
484  nullptr, /* tp_descr_set */
485  0, /* tp_dictoffset */
486  nullptr, /* tp_init */
487  nullptr, /* tp_alloc */
488  THPVariable_pynew /* tp_new */
489 };
490 
491 namespace torch { namespace autograd {
492 
493 extern PyMethodDef variable_methods[];
494 extern void initTorchFunctions(PyObject *module);
495 
496 void initTensorImplConversion(PyObject* module) {
497  auto m = py::handle(module).cast<py::module>();
498  m.def("_wrap_tensor_impl", [](void* ptr) {
500  unsafe_reclaim_from_nonowning(static_cast<c10::TensorImpl*>(ptr));
501  AT_CHECK(p.defined(), "Can't wrap undefined tensor");
502  AT_CHECK(!p->is_variable(), "Can wrap only non-variable tensor");
503  auto tensor = at::Tensor::wrap_tensor_impl(std::move(p));
504  return py::cast(torch::autograd::Variable(
505  torch::autograd::make_variable(std::move(tensor), false)));
506  });
507  // set on the module level to avoid mixing pybind and plain CPython extensions
508  m.def("_tensor_impl_raw_handle", [](torch::autograd::Variable* t) -> void* {
509  auto p = t->data().getIntrusivePtr();
510  // We return a raw non-owning pointer here, we rely on surrounding
511  // code to keep the original tensor alive
512  return p.get();
513  });
514 }
515 }}
516 
517 bool THPVariable_initModule(PyObject *module)
518 {
519  static std::vector<PyMethodDef> methods;
520  THPUtils_addPyMethodDefs(methods, torch::autograd::variable_methods);
521  THPUtils_addPyMethodDefs(methods, extra_methods);
522  THPVariableType.tp_methods = methods.data();
523  if (PyType_Ready(&THPVariableType) < 0)
524  return false;
525  Py_INCREF(&THPVariableType);
526  PyModule_AddObject(module, "_TensorBase", (PyObject *)&THPVariableType);
527  torch::autograd::initTorchFunctions(module);
528  torch::autograd::initTensorImplConversion(module);
529  return true;
530 }
Variable A Variable augments a Tensor with the ability to interact in our autograd machinery...
Definition: variable.h:85
Definition: jit_type.h:17
TensorOptions requires_grad(bool requires_grad=true)
Convenience function that returns a TensorOptions object with the requires_grad set to the given one...
Flush-To-Zero and Denormals-Are-Zero mode.
static intrusive_ptr unsafe_reclaim_from_nonowning(TTarget *raw_ptr)
Turn a non-owning raw pointer to an intrusive_ptr.