Caffe2 - C++ API
A deep learning, cross platform ML framework
python_tensor.cpp
1 #include <torch/csrc/tensor/python_tensor.h>
2 
3 #include <structmember.h>
4 #include <pybind11/pybind11.h>
5 
6 #include <torch/csrc/Dtype.h>
7 #include <torch/csrc/DynamicTypes.h>
8 #include <torch/csrc/Exceptions.h>
9 #include <torch/csrc/Layout.h>
10 #include <torch/csrc/autograd/variable.h>
11 #include <torch/csrc/autograd/python_variable.h>
12 #include <torch/csrc/autograd/generated/VariableType.h>
13 #include <torch/csrc/autograd/utils/wrap_outputs.h>
14 #include <torch/csrc/utils/cuda_enabled.h>
15 #include <torch/csrc/utils/cuda_lazy_init.h>
16 #include <torch/csrc/utils/python_strings.h>
17 #include <torch/csrc/utils/tensor_new.h>
18 #include <torch/csrc/utils/tensor_types.h>
19 
20 #include <ATen/ATen.h>
21 
22 #include <sstream>
23 #include <string>
24 #include <type_traits>
25 #include <vector>
26 
27 namespace torch { namespace tensors {
28 
29 using namespace at;
30 using namespace torch::autograd;
31 
32 struct PyTensorType {
33  PyTypeObject py_type;
34  at::Type* aten_type_;
35  THPDtype* dtype;
36  THPLayout* layout;
37  bool is_cuda;
38  char name[64];
39  int backend;
40  int scalar_type;
41 
42  // Precondition: Access to this struct is protected by the GIL
43  at::Type* aten_type() {
44  if (!aten_type_) {
45  if (is_cuda) {
46  torch::utils::cuda_lazy_init();
47  }
48  auto* baseType = globalContext().getNonVariableTypeOpt(static_cast<at::Backend>(backend), static_cast<at::ScalarType>(scalar_type));
49  aten_type_ = baseType ? torch::autograd::VariableType::getVariableTypeFromBaseType(*baseType) : nullptr;
50  }
51  return aten_type_;
52  }
53 };
54 
55 static_assert(std::is_standard_layout<PyTensorType>::value, "PyTensorType must be standard layout");
56 
57 // This is always an instance of VariableType
58 static at::Type* default_tensor_type;
59 
60 static void py_bind_tensor_types(const std::vector<PyTensorType>& tensor_types);
61 
62 static TypeError unavailable_type(const PyTensorType& type) {
63  const char* cuda_msg = torch::utils::cuda_enabled() ? ". Torch not compiled with CUDA enabled." : "";
64  return TypeError("type %s not available%s", type.name, cuda_msg);
65 }
66 
67 static PyObject* Tensor_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) {
68  HANDLE_TH_ERRORS
69  auto& tensor_type = *((PyTensorType*)type);
70  auto aten_type = tensor_type.aten_type();
71  if (!aten_type) {
72  throw unavailable_type(tensor_type);
73  }
74  return THPVariable_Wrap(torch::utils::legacy_tensor_ctor(*aten_type, args, kwargs));
75  END_HANDLE_TH_ERRORS
76 }
77 
78 static PyObject* Tensor_instancecheck(PyTensorType* self, PyObject* arg) {
79  HANDLE_TH_ERRORS
80  if (THPVariable_Check(arg)) {
81  auto& var = ((THPVariable*)arg)->cdata;
82  // NB: This is a little unfortunate, in that if I do an isinstance check
83  // against torch.cuda.FloatTensor, this will immediately initialize CUDA.
84  // I originally thought that it would not be possible for aten_type_ to
85  // be nullptr if you had a tensor of some type, in which case you can
86  // skip initializign aten_type(), but TestAutograd.test_type_conversions
87  // seems to violate this property (for whatever reason.)
88  if (&var.type() == self->aten_type()) {
89  Py_RETURN_TRUE;
90  }
91  }
92  Py_RETURN_FALSE;
93  END_HANDLE_TH_ERRORS
94 }
95 
96 PyObject *Tensor_dtype(PyTensorType* self) {
97  return torch::autograd::utils::wrap(self->dtype);
98 }
99 
100 PyObject *Tensor_layout(PyTensorType* self) {
101  return torch::autograd::utils::wrap(self->layout);
102 }
103 
104 PyObject *Tensor_is_cuda(PyTensorType* self) {
105  if (self->is_cuda) {
106  Py_RETURN_TRUE;
107  } else {
108  Py_RETURN_FALSE;
109  }
110 }
111 
112 PyObject *Tensor_is_sparse(PyTensorType *self) {
113  if (self->layout->layout == at::Layout::Strided) {
114  Py_RETURN_FALSE;
115  } else {
116  Py_RETURN_TRUE;
117  }
118 }
119 
120 static struct PyMethodDef metaclass_methods[] = {
121  {"__instancecheck__", (PyCFunction)Tensor_instancecheck, METH_O, nullptr},
122  {nullptr}
123 };
124 
125 typedef PyObject *(*getter)(PyObject *, void *);
126 
127 static struct PyGetSetDef metaclass_properties[] = {
128  {"dtype", (getter)Tensor_dtype, nullptr, nullptr, nullptr},
129  {"layout", (getter)Tensor_layout, nullptr, nullptr, nullptr},
130  {"is_cuda", (getter)Tensor_is_cuda, nullptr, nullptr, nullptr},
131  {"is_sparse", (getter)Tensor_is_sparse, nullptr, nullptr, nullptr},
132  {nullptr}
133 };
134 
135 static PyTypeObject metaclass;
136 
137 static void py_initialize_metaclass(PyTypeObject& metaclass) {
138  ((PyObject*)&metaclass)->ob_refcnt = 1;
139  metaclass.tp_basicsize = sizeof(PyTypeObject);
140  metaclass.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
141  metaclass.tp_methods = metaclass_methods;
142  metaclass.tp_getset = metaclass_properties;
143  metaclass.tp_name = "torch.tensortype";
144  metaclass.tp_base = &PyType_Type;
145  if (PyType_Ready(&metaclass) < 0) {
146  throw python_error();
147  }
148 }
149 
150 static void py_initialize_tensor_type(PyTypeObject& type, const char* name, PyObject* tp_dict) {
151  // NOTE: we don't use the typical static declaration of PyTypeObject because
152  // we need to initialize as many types as there are VariableType instances.
153  // The typical PyVarObject_HEAD_INIT(nullptr, 0) is described in the Python
154  // documentation: it initializes the refcnt to 1 and the other object header
155  // fields to zero.
156  memset(&type, 0, sizeof(PyTypeObject));
157  ((PyObject*)&type)->ob_refcnt = 1;
158  ((PyObject*)&type)->ob_type = &metaclass;
159  type.tp_basicsize = sizeof(PyTensorType);
160  type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
161  type.tp_name = name;
162  type.tp_new = Tensor_new;
163  if (PyType_Ready(&type) < 0) {
164  throw python_error();
165  }
166  if (PyDict_Merge(type.tp_dict, tp_dict, 0) < 0) {
167  throw python_error();
168  }
169 }
170 
171 static const char* get_module(Backend backend) {
172  switch (backend) {
173  case Backend::CPU: return "torch";
174  case Backend::CUDA: return "torch.cuda";
175  case Backend::SparseCPU: return "torch.sparse";
176  case Backend::SparseCUDA: return "torch.cuda.sparse";
177  default: AT_ERROR("invalid backend: ", toString(backend));
178  }
179 }
180 
181 static std::string get_name(Backend backend, ScalarType scalarType) {
182  std::ostringstream ss;
183  ss << get_module(backend) << "." << toString(scalarType) << "Tensor";
184  return ss.str();
185 }
186 
187 static THPObjectPtr get_storage_obj(const Type& type) {
188  auto module_name = get_module(type.backend());
189  auto module_obj = THPObjectPtr(PyImport_ImportModule(module_name));
190  if (!module_obj) throw python_error();
191 
192  auto storage_name = std::string(toString(type.scalarType())) + "Storage";
193  THPObjectPtr storage(PyObject_GetAttrString(module_obj.get(), storage_name.c_str()));
194  if (!storage.get()) {
195  throw TypeError("couldn't find storage object %s", storage_name.c_str());
196  }
197  return storage;
198 }
199 
200 static void set_type(PyTensorType& type_obj, Backend backend, ScalarType scalarType) {
201  // This field is lazily initialized from backend and scalar_type
202  type_obj.aten_type_ = nullptr;
203  type_obj.backend = static_cast<int>(backend);
204  type_obj.scalar_type = static_cast<int>(scalarType);
205  type_obj.layout = torch::getLayout(backend);
206  type_obj.dtype = torch::getDtype(scalarType);
207  type_obj.is_cuda = (backend == at::Backend::CUDA || backend == at::Backend::SparseCUDA);
208 }
209 
210 static void set_name(PyTensorType& type_obj, const std::string& name) {
211  size_t n = sizeof(type_obj.name);
212  strncpy(type_obj.name, name.c_str(), n);
213  type_obj.name[n - 1] = '\0';
214 }
215 
216 static THPObjectPtr get_tensor_dict() {
217  auto torch = THPObjectPtr(PyImport_ImportModule("torch"));
218  if (!torch) throw python_error();
219 
220  auto tensor_class = THPObjectPtr(PyObject_GetAttrString(torch, "Tensor"));
221  if (!tensor_class) throw python_error();
222 
223  auto tensor_type = (PyTypeObject*)tensor_class.get();
224  AT_CHECK(tensor_type->tp_base, "missing base type for Tensor");
225 
226  auto res = THPObjectPtr(PyDict_New());
227  if (!res) throw python_error();
228 
229  if (PyDict_Merge(res.get(), tensor_type->tp_dict, 0) < 0) {
230  throw python_error();
231  }
232  if (PyDict_Merge(res.get(), tensor_type->tp_base->tp_dict, 0) < 0) {
233  throw python_error();
234  }
235 
236  return res;
237 }
238 
239 static std::vector<PyTensorType> tensor_types;
240 
241 static void initialize_aten_types(std::vector<PyTensorType>& tensor_types) {
242  // includes CUDA types even when PyTorch is not built with CUDA
243  auto declared_types = torch::utils::all_declared_types();
244  tensor_types.resize(declared_types.size());
245 
246  for (size_t i = 0, end = declared_types.size(); i != end; i++) {
247  auto& tensor_type = tensor_types[i];
248  Backend backend = declared_types[i].first;
249  ScalarType scalar_type = declared_types[i].second;
250  set_type(tensor_type, backend, scalar_type);
251  set_name(tensor_type, get_name(backend, scalar_type));
252  }
253 }
254 
255 void initialize_python_bindings() {
256  // Initialize the at::Type* pointers, name, and properties of the PyTensorType
257  // vector. After this call, the vector must not be resized.
258  initialize_aten_types(tensor_types);
259 
260  // Initialize the Python metaclass for the torch.FloatTensor, etc. types.
261  // The metaclass handles __instancecheck__ checks and binds the dtype property
262  // on the type objects.
263  py_initialize_metaclass(metaclass);
264 
265  // Get the tp_dict of the Variable class. We copy function definitions
266  // onto each Tensor type object so that they can be accessed via e.g.
267  // `torch.FloatTensor.add`.
268  auto tensor_dict = get_tensor_dict();
269 
270  // Initialize each Python type object torch.FloatTensor, torch.DoubleTensor, etc.
271  for (auto& tensor_type : tensor_types) {
272  py_initialize_tensor_type(tensor_type.py_type, tensor_type.name, tensor_dict.get());
273  }
274 
275  // Add the type objects to their corresponding modules. e.g. torch.FloatTensor
276  // is added to the `torch` module as `FloatTensor`. Also add all the type
277  // objects to the set torch._tensor_classes.
278  py_bind_tensor_types(tensor_types);
279 
280  // Use torch.float32 as the default tensor type
281  set_default_tensor_type(at::globalContext().getVariableType(at::Backend::CPU, at::kFloat));
282 }
283 
284 static void py_bind_tensor_types(const std::vector<PyTensorType>& tensor_types) {
285  auto torch_module = THPObjectPtr(PyImport_ImportModule("torch"));
286  if (!torch_module) throw python_error();
287 
288  auto tensor_classes = THPObjectPtr(PyObject_GetAttrString(torch_module.get(), "_tensor_classes"));
289  if (!tensor_classes) throw python_error();
290 
291  for (auto& tensor_type : tensor_types) {
292  auto name = std::string(tensor_type.name);
293  auto idx = name.rfind('.');
294  auto type_name = name.substr(idx + 1);
295  auto module_name = name.substr(0, idx);
296 
297  auto module_obj = THPObjectPtr(PyImport_ImportModule(module_name.c_str()));
298  if (!module_obj) throw python_error();
299 
300  PyObject* type_obj = (PyObject*)&tensor_type;
301  Py_INCREF(type_obj);
302  if (PyModule_AddObject(module_obj.get(), type_name.c_str(), type_obj) < 0) {
303  throw python_error();
304  }
305  if (PySet_Add(tensor_classes.get(), type_obj) < 0) {
306  throw python_error();
307  }
308  }
309 }
310 
311 static bool PyTensorType_Check(PyObject* obj) {
312  auto it = std::find_if(tensor_types.begin(), tensor_types.end(),
313  [obj](const PyTensorType& x) {
314  return (PyObject*)&x == obj;
315  });
316  return it != tensor_types.end();
317 }
318 
319 static PyTensorType& get_tensor_type(THPDtype *dtype, THPLayout *layout, bool is_cuda) {
320  auto it = std::find_if(tensor_types.begin(), tensor_types.end(),
321  [dtype, layout, is_cuda](const PyTensorType& x) {
322  return x.dtype == dtype && x.layout == layout && x.is_cuda == is_cuda;
323  });
324  if (it == tensor_types.end()) {
325  throw TypeError("invalid dtype object");
326  }
327  return *it;
328 }
329 
330 void py_set_default_tensor_type(PyObject* obj) {
331  PyTensorType *type;
332  if (PyTensorType_Check(obj)) {
333  type = (PyTensorType*)obj;
334  } else {
335  throw TypeError("invalid type object");
336  }
337  auto aten_type = type->aten_type();
338  if (!aten_type) {
339  throw unavailable_type(*type);
340  }
341  set_default_tensor_type(*aten_type);
342 }
343 
344 void py_set_default_dtype(PyObject* obj) {
345  PyTensorType *type;
346  if (THPDtype_Check(obj)) {
347  auto &current_default = get_default_tensor_type();
348  type = &get_tensor_type((THPDtype*)obj, torch::getLayout(current_default.backend()),
349  torch::getDeviceType(current_default) == at::Device::Type::CUDA);
350  } else {
351  throw TypeError("invalid type object");
352  }
353  auto aten_type = type->aten_type();
354  if (!aten_type) {
355  throw unavailable_type(*type);
356  }
357  set_default_tensor_type(*aten_type);
358 }
359 
360 void set_default_tensor_type(const at::Type& type) {
361  if (!at::isFloatingType(type.scalarType())) {
362  throw TypeError("only floating-point types are supported as the default type");
363  }
364  if (!type.is_variable() && !type.is_undefined()) {
365  throw TypeError("only variable types are supported");
366  }
367  if (type.is_sparse()) {
368  throw TypeError("only dense types are supported as the default type");
369  }
370 
371  // get the storage first, so if it doesn't exist we don't change the default tensor type
372  THPObjectPtr storage = get_storage_obj(type);
373  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
374  default_tensor_type = const_cast<Type*>(&type);
375  at::set_default_dtype(default_tensor_type->typeMeta());
376 
377  auto torch_module = THPObjectPtr(PyImport_ImportModule("torch"));
378  if (!torch_module) throw python_error();
379 
380  if (PyObject_SetAttrString(torch_module.get(), "Storage", storage) != 0) {
381  // technically, we should undo the change of default tensor type.
382  throw python_error();
383  }
384 }
385 
386 at::Type& get_default_tensor_type() {
387  AT_ASSERT(default_tensor_type);
388  return *default_tensor_type;
389 }
390 }} // namespace torch::tensors
Backend
This legacy enum class defines the set of backends supported by old school, code generated Type-based...
Definition: Backend.h:23
Definition: Dtype.h:9
Definition: jit_type.h:17
Flush-To-Zero and Denormals-Are-Zero mode.