Caffe2 - C++ API
A deep learning, cross platform ML framework
tensor_numpy.cpp
1 #include <torch/csrc/utils/tensor_numpy.h>
2 
3 #include <torch/csrc/utils/numpy_stub.h>
4 
5 #ifndef USE_NUMPY
6 namespace torch { namespace utils {
7 PyObject* tensor_to_numpy(const at::Tensor& tensor) {
8  throw std::runtime_error("PyTorch was compiled without NumPy support");
9 }
10 at::Tensor tensor_from_numpy(PyObject* obj) {
11  throw std::runtime_error("PyTorch was compiled without NumPy support");
12 }
13 bool is_numpy_scalar(PyObject* obj) {
14  throw std::runtime_error("PyTorch was compiled without NumPy support");
15 }
16 }}
17 #else
18 
19 #include <torch/csrc/DynamicTypes.h>
20 #include <torch/csrc/Exceptions.h>
21 #include <torch/csrc/autograd/python_variable.h>
22 
23 #include <ATen/ATen.h>
24 #include <memory>
25 #include <sstream>
26 #include <stdexcept>
27 
28 using namespace at;
29 using namespace torch::autograd;
30 
31 namespace torch { namespace utils {
32 
33 static std::vector<npy_intp> to_numpy_shape(IntArrayRef x) {
34  // shape and stride conversion from int64_t to npy_intp
35  auto nelem = x.size();
36  auto result = std::vector<npy_intp>(nelem);
37  for (size_t i = 0; i < nelem; i++) {
38  result[i] = static_cast<npy_intp>(x[i]);
39  }
40  return result;
41 }
42 
43 static std::vector<int64_t> to_aten_shape(int ndim, npy_intp* values) {
44  // shape and stride conversion from npy_intp to int64_t
45  auto result = std::vector<int64_t>(ndim);
46  for (int i = 0; i < ndim; i++) {
47  result[i] = static_cast<int64_t>(values[i]);
48  }
49  return result;
50 }
51 
52 static int aten_to_dtype(const ScalarType scalar_type);
53 
54 PyObject* tensor_to_numpy(const at::Tensor& tensor) {
55  if (tensor.is_cuda()) {
56  throw TypeError(
57  "can't convert CUDA tensor to numpy. Use Tensor.cpu() to "
58  "copy the tensor to host memory first.");
59  }
60  if (tensor.is_sparse()) {
61  throw TypeError(
62  "can't convert sparse tensor to numpy. Use Tensor.to_dense() to "
63  "convert to a dense tensor first.");
64  }
65  if (tensor.type().backend() != Backend::CPU) {
66  throw TypeError("NumPy conversion for %s is not supported", tensor.type().toString());
67  }
68  auto dtype = aten_to_dtype(tensor.scalar_type());
69  auto sizes = to_numpy_shape(tensor.sizes());
70  auto strides = to_numpy_shape(tensor.strides());
71  // NumPy strides use bytes. Torch strides use element counts.
72  auto element_size_in_bytes = tensor.element_size();
73  for (auto& stride : strides) {
74  stride *= element_size_in_bytes;
75  }
76 
77  auto array = THPObjectPtr(PyArray_New(
78  &PyArray_Type,
79  tensor.dim(),
80  sizes.data(),
81  dtype,
82  strides.data(),
83  tensor.data_ptr(),
84  0,
85  NPY_ARRAY_ALIGNED | NPY_ARRAY_WRITEABLE,
86  nullptr));
87  if (!array) return nullptr;
88 
89  // TODO: This attempts to keep the underlying memory alive by setting the base
90  // object of the ndarray to the tensor and disabling resizes on the storage.
91  // This is not sufficient. For example, the tensor's storage may be changed
92  // via Tensor.set_, which can free the underlying memory.
93  PyObject* py_tensor = THPVariable_Wrap(make_variable(tensor, false));
94  if (!py_tensor) throw python_error();
95  if (PyArray_SetBaseObject((PyArrayObject*)array.get(), py_tensor) == -1) {
96  return nullptr;
97  }
98  // Use the private storage API
99  tensor.storage().unsafeGetStorageImpl()->set_resizable(false);
100 
101  return array.release();
102 }
103 
104 at::Tensor tensor_from_numpy(PyObject* obj) {
105  if (!PyArray_Check(obj)) {
106  throw TypeError("expected np.ndarray (got %s)", Py_TYPE(obj)->tp_name);
107  }
108 
109  auto array = (PyArrayObject*)obj;
110  int ndim = PyArray_NDIM(array);
111  auto sizes = to_aten_shape(ndim, PyArray_DIMS(array));
112  auto strides = to_aten_shape(ndim, PyArray_STRIDES(array));
113  // NumPy strides use bytes. Torch strides use element counts.
114  auto element_size_in_bytes = PyArray_ITEMSIZE(array);
115  for (auto& stride : strides) {
116  if (stride%element_size_in_bytes != 0) {
117  throw ValueError(
118  "given numpy array strides not a multiple of the element byte size. "
119  "Copy the numpy array to reallocate the memory.");
120  }
121  stride /= element_size_in_bytes;
122  }
123 
124  size_t storage_size = 1;
125  for (int i = 0; i < ndim; i++) {
126  if (strides[i] < 0) {
127  throw ValueError(
128  "some of the strides of a given numpy array are negative. This is "
129  "currently not supported, but will be added in future releases.");
130  }
131  // XXX: this won't work for negative strides
132  storage_size += (sizes[i] - 1) * strides[i];
133  }
134 
135  void* data_ptr = PyArray_DATA(array);
136  auto& type = CPU(numpy_dtype_to_aten(PyArray_TYPE(array)));
137  if (!PyArray_EquivByteorders(PyArray_DESCR(array)->byteorder, NPY_NATIVE)) {
138  throw ValueError(
139  "given numpy array has byte order different from the native byte order. "
140  "Conversion between byte orders is currently not supported.");
141  }
142  Py_INCREF(obj);
143  return type.tensorFromBlob(data_ptr, sizes, strides, [obj](void* data) {
144  AutoGIL gil;
145  Py_DECREF(obj);
146  });
147 }
148 
149 static int aten_to_dtype(const ScalarType scalar_type) {
150  switch (scalar_type) {
151  case kDouble: return NPY_DOUBLE;
152  case kFloat: return NPY_FLOAT;
153  case kHalf: return NPY_HALF;
154  case kLong: return NPY_INT64;
155  case kInt: return NPY_INT32;
156  case kShort: return NPY_INT16;
157  case kChar: return NPY_INT8;
158  case kByte: return NPY_UINT8;
159  default:
160  throw ValueError("Got unsupported ScalarType ", toString(scalar_type));
161  }
162 }
163 
164 ScalarType numpy_dtype_to_aten(int dtype) {
165  switch (dtype) {
166  case NPY_DOUBLE: return kDouble;
167  case NPY_FLOAT: return kFloat;
168  case NPY_HALF: return kHalf;
169  case NPY_INT32: return kInt;
170  case NPY_INT16: return kShort;
171  case NPY_INT8: return kChar;
172  case NPY_UINT8: return kByte;
173  default:
174  // Workaround: MSVC does not support two switch cases that have the same value
175  if (dtype == NPY_LONGLONG || dtype == NPY_INT64) {
176  return kLong;
177  } else {
178  break;
179  }
180  }
181  auto pytype = THPObjectPtr(PyArray_TypeObjectFromType(dtype));
182  if (!pytype) throw python_error();
183  throw TypeError(
184  "can't convert np.ndarray of type %s. The only supported types are: "
185  "float64, float32, float16, int64, int32, int16, int8, and uint8.",
186  ((PyTypeObject*)pytype.get())->tp_name);
187 }
188 
189 bool is_numpy_scalar(PyObject* obj) {
190  return (PyArray_IsIntegerScalar(obj) ||
191  PyArray_IsScalar(obj, Floating));
192 }
193 
194 }} // namespace torch::utils
195 
196 #endif // USE_NUMPY
bool is_cuda() const
Returns if a Tensor has CUDA backend.
bool is_sparse() const
Returns if a Tensor has sparse backend.
Definition: jit_type.h:17
Flush-To-Zero and Denormals-Are-Zero mode.