Caffe2 - C++ API
A deep learning, cross platform ML framework
python_variable_indexing.cpp
1 #include <torch/csrc/autograd/python_variable_indexing.h>
2 
3 #include <torch/csrc/DynamicTypes.h>
4 #include <torch/csrc/Exceptions.h>
5 #include <torch/csrc/THP_export.h>
6 #include <torch/csrc/autograd/function.h>
7 #include <torch/csrc/autograd/python_variable.h>
8 #include <torch/csrc/autograd/utils/wrap_outputs.h>
9 #include <torch/csrc/autograd/variable.h>
10 #include <torch/csrc/utils/python_compat.h>
11 #include <torch/csrc/utils/python_numbers.h>
12 #include <torch/csrc/utils/tensor_new.h>
13 #include <torch/csrc/jit/tracer.h>
14 
15 #include <ATen/DeviceGuard.h>
16 #include <ATen/ExpandUtils.h>
17 #include <c10/core/TensorOptions.h>
18 
19 #include <vector>
20 #include <tuple>
21 
22 using namespace at;
23 using namespace torch::autograd::utils;
24 
25 namespace torch { namespace autograd {
26 
27 Py_ssize_t THPVariable_length(PyObject* self) {
28  HANDLE_TH_ERRORS
29  auto& self_ = reinterpret_cast<THPVariable*>(self)->cdata;
30  if (self_.dim() == 0) {
31  return 0;
32  }
33  return (Py_ssize_t)self_.size(0);
34  END_HANDLE_TH_ERRORS_RET(-1)
35 }
36 
37 
38 // We allow indexing by integers, slices, ellipsis, None, Variables,
39 // and tuples of those types. We also handle bools as if they were a
40 // Variable[ByteTensor].
41 
42 static int64_t count_specified_dimensions(PyObject* index) {
43  // Count the number of indexed dimensions (everything but ellipsis and None)
44  int64_t count = 0;
45  auto size = PyTuple_GET_SIZE(index); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
46  for (Py_ssize_t i = 0; i < size; i++) {
47  PyObject* obj = PyTuple_GET_ITEM(index, i); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
48  if (THPVariable_Check(obj)) {
49  auto& var = reinterpret_cast<THPVariable*>(obj)->cdata;
50  if (var.scalar_type() == kByte) {
51  count += var.dim();
52  } else {
53  count++;
54  }
55  } else if (obj != Py_None && obj != Py_Ellipsis && obj != Py_True && obj != Py_False) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
56  count++;
57  }
58  }
59  return count;
60 }
61 
62 [[noreturn]]
63 static void invalid_index(PyObject* obj) {
64  throw IndexError(
65  "only integers, slices (`:`), ellipsis (`...`), None and long or byte "
66  "Variables are valid indices (got %s)", Py_TYPE(obj)->tp_name);
67 }
68 
69 static Variable applySlice(const Variable& self, int64_t dim, PyObject* slice, bool ensure_view=false) {
70  Py_ssize_t start, stop, step;
71  auto length = self.size(dim);
72  if (!THPUtils_unpackSlice(slice, &start, &stop, &step)) {
73  throw python_error();
74  }
75  if (step == 0) {
76  throw ValueError("step cannot be zero");
77  }
78  if (step < 0) {
79  // TODO: implement negative step
80  throw ValueError("negative step not yet supported");
81  }
82  // Skip this optimization if we are tracing, as the trace may be polymorphic
83  // over the shape of the `self` tensor, and we still want to record
84  // the slice.
85  if (!ensure_view && start == 0 && stop == length && step == 1 && !jit::tracer::isTracing()) {
86  return self;
87  }
88  return self.slice(dim, start, stop, step);
89 }
90 
91 static Variable applySelect(const Variable& self, int64_t dim, int64_t index, int64_t real_dim=0) {
92  if (index == 0 && dim == 0 && self.dim() == 0) {
93  throw IndexError(
94  "invalid index of a 0-dim tensor. "
95  "Use tensor.item() to convert a 0-dim tensor to a Python number");
96  }
97  int64_t size = self.size(dim);
98  if (index < -size || index >= size) {
99  throw IndexError("index %lld is out of bounds for dimension %lld with size %lld",
100  index, real_dim, size);
101  }
102  // if the index is negative, do not normalize it because that would fix the index
103  // on the current tensor size in the tracer.
104  // aten::select also works on negative indices
105  return self.select(dim, index);
106 }
107 
108 static Variable sequenceToVariable(const at::Type& type, PyObject* seq) {
109  auto& idx_type = type.toScalarType(kLong);
110  return torch::utils::indexing_tensor_from_data(idx_type, c10::nullopt, seq);
111 }
112 
113 static Variable valueToTensor(const at::Type & type, PyObject* value) {
114  if (THPVariable_Check(value)) {
115  return reinterpret_cast<THPVariable*>(value)->cdata;
116  }
117  if (THPUtils_checkLong(value)) {
118  return at::scalar_tensor(Scalar(THPUtils_unpackLong(value)), type.options());
119  }
120  if (PyFloat_Check(value)) {
121  return at::scalar_tensor(Scalar(THPUtils_unpackDouble(value)), type.options());
122  }
123  throw TypeError("can't assign a %s to a %s", Py_TYPE(value)->tp_name, type.toString());
124 }
125 
126 static Variable boolToIndexingTensor(const Variable& self, bool value) {
127  // booleans add a dimension of size 1. true indexes this dimension as if 0:, false as empty.
128  if (value) {
129  return at::zeros({1}, self.options().dtype(kLong));
130  } else {
131  return at::empty({0}, self.options().dtype(kLong));
132  }
133 }
134 
135 static Variable applySlicing(const Variable& self, PyObject* index, variable_list& outIndices) {
136  int64_t size = PyTuple_GET_SIZE(index); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
137  int64_t dim = 0;
138  int64_t specified_dims = count_specified_dimensions(index);
139 
140  auto handle_var = [&](const Variable& var) {
141  // TODO: check scalarType
142  outIndices.resize(dim + 1);
143  outIndices[dim] = var;
144  dim++;
145  };
146 
147  if (specified_dims > self.dim()) {
148  throw IndexError("too many indices for tensor of dimension %d", (int)self.dim());
149  }
150 
151  Variable result = self;
152  for (int64_t i = 0; i < size; i++) {
153  PyObject* obj = PyTuple_GET_ITEM(index, i); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
154  if (THPUtils_checkLong(obj)) {
155  result = applySelect(result, dim, THPUtils_unpackLong(obj), i);
156  } else if (PySlice_Check(obj)) {
157  result = applySlice(result, dim, obj);
158  dim++;
159  } else if (obj == Py_Ellipsis) {
160  dim += self.dim() - specified_dims;
161  } else if (obj == Py_None) {
162  result = result.unsqueeze(dim);
163  dim++;
164  } else if (PyBool_Check(obj)) {
165  result = result.unsqueeze(dim);
166  handle_var(boolToIndexingTensor(result, obj == Py_True)); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
167  } else if (THPVariable_Check(obj)) {
168  auto& var = THPVariable_Unpack(obj);
169  auto scalar_type = var.scalar_type();
170  if (var.dim() == 0 && at::isIntegralType(scalar_type)) {
171  if (scalar_type != at::kByte) {
172  result = applySelect(result, dim, THPUtils_unpackLong(obj), i);
173  } else {
174  result = result.unsqueeze(dim);
175  handle_var(boolToIndexingTensor(result, var.item<uint8_t>() != 0));
176  }
177  } else {
178  handle_var(var);
179  }
180  } else if (PySequence_Check(obj)) {
181  handle_var(sequenceToVariable(self.type(), obj));
182  } else {
183  auto index = THPObjectPtr(PyNumber_Index(obj));
184  if (!index) {
185  PyErr_Clear();
186  invalid_index(obj);
187  }
188  result = applySelect(result, dim, THPUtils_unpackLong(index), i);
189  }
190  }
191  return result;
192 }
193 
194 static std::vector<Tensor> typeConvertIndices(const Variable& self, const variable_list& indices) {
195  std::vector<Tensor> converted_inds(indices.size());
196  for (size_t i = 0; i < indices.size(); ++i) {
197  const auto &ind = indices[i];
198  if (ind.defined()) {
199  converted_inds[i] = ind.to(ind.options().device(self.device()));
200  } else {
201  converted_inds[i] = indices[i];
202  }
203  }
204  return converted_inds;
205 }
206 
207 static Variable dispatch_index(const Variable& self, const variable_list& indices) {
208  AutoNoGIL no_gil;
209  std::vector<Tensor> converted_indices = typeConvertIndices(self, indices);
210  OptionalDeviceGuard device_guard(device_of(self));
211  return self.index(converted_indices);
212 }
213 
214 static Variable dispatch_index_put_(Variable& self, const variable_list& indices, const Variable& value) {
215  AutoNoGIL no_gil;
216  std::vector<Tensor> converted_indices = typeConvertIndices(self, indices);
217  OptionalDeviceGuard device_guard(device_of(self));
218  return self.index_put_(converted_indices, value);
219 }
220 
221 static bool treatSequenceAsTuple(PyObject* index) {
222  if (PyTuple_Check(index)) {
223  return true;
224  }
225  if (!PySequence_Check(index)) {
226  return false;
227  }
228  // This uses a heuristics from NumPy for determining whether to treat
229  // non-tuple sequences as if they were a tuple. From the NumPy code comments:
230  //
231  // "At this point, we're left with a non-tuple, non-array, sequence:
232  // typically, a list. We use some somewhat-arbitrary heuristics from here
233  // onwards to decided whether to treat that list as a single index, or a
234  // list of indices. Backwards compatibility only takes effect for short
235  // sequences - otherwise we treat it like any other scalar."
236  auto n = PySequence_Size(index);
237  if (n < 0) {
238  // Negative size indicates a Python error in the PySequence_Size call.
239  PyErr_Clear();
240  return false;
241  }
242  if (n >= 32) {
243  return false;
244  }
245  for (Py_ssize_t i = 0; i < n; i++) {
246  auto obj = THPObjectPtr{PySequence_GetItem(index, i)};
247  if (!obj.get()) {
248  PyErr_Clear();
249  return false;
250  }
251  if (THPVariable_Check(obj.get()) || PySequence_Check(obj.get()) || PySlice_Check(obj.get())) {
252  return true;
253  }
254  if (obj.get() == Py_Ellipsis || obj.get() == Py_None) {
255  return true;
256  }
257  }
258  return false;
259 }
260 
261 static THPObjectPtr wrapTuple(PyObject* index) {
262  THPObjectPtr res;
263  if (treatSequenceAsTuple(index)) {
264  res = PySequence_Tuple(index);
265  } else {
266  res = PyTuple_Pack(1, index); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
267  }
268  if (!res) throw python_error();
269  return res;
270 }
271 
272 PyObject* THPVariable_getitem(PyObject* self, PyObject* index) {
273  HANDLE_TH_ERRORS
274  auto& self_ = reinterpret_cast<THPVariable*>(self)->cdata;
275  OptionalDeviceGuard device_guard(device_of(self_));
276 
277  // handle simple types: integers, slices, ellipsis
278  if (index == Py_None) {
279  return wrap(self_.unsqueeze(0));
280  } else if (index == Py_Ellipsis) {
281  return wrap(at::alias(self_));
282  } else if (THPUtils_checkLong(index)) {
283  return wrap(applySelect(self_, 0, THPUtils_unpackLong(index)));
284  } else if (PySlice_Check(index)) {
285  return wrap(applySlice(self_, 0, index, true));
286  }
287 
288  // wrap index in a tuple if it's not already one
289  THPObjectPtr holder = wrapTuple(index);
290 
291  variable_list variableIndices;
292  Variable sliced = applySlicing(self_, holder.get(), variableIndices);
293  if (variableIndices.empty()) {
294  if (sliced.is_same(self_)) {
295  // ensure we return a shallow copy for things like x[...]
296  sliced = at::alias(sliced);
297  }
298  return wrap(sliced);
299  }
300 
301  // indexing by tensors ("advanced" indexing)
302  return wrap(dispatch_index(sliced, variableIndices));
303  Py_RETURN_NONE;
304  END_HANDLE_TH_ERRORS
305 }
306 
307 // To match numpy semantics:
308 // As a special case for backwards compatibility,
309 // strip away unit dimensions from the left of 'src'
310 static IntArrayRef slicePrefix1sSize(IntArrayRef sizes) {
311  size_t first_non1_src = sizes.size();
312  for (size_t i = 0; i < sizes.size(); ++i) {
313  if (sizes[i] != 1) {
314  first_non1_src = i;
315  break;
316  }
317  }
318 
319  return sizes.slice(first_non1_src);
320 }
321 
322 static void copy_to(Variable dst, const Variable& src) {
323  Tensor b_src;
324  IntArrayRef sliced_src_sizes = slicePrefix1sSize(src.sizes());
325  std::tie(b_src) = expand_inplace(dst, src.view(sliced_src_sizes), "setitem");
326  dst.copy_(b_src);
327 }
328 
329 int THPVariable_setitem(PyObject* self, PyObject* index, PyObject* py_value) {
330  HANDLE_TH_ERRORS
331  if (py_value == nullptr) {
332  throw TypeError("Tensor does not support deleting items");
333  }
334 
335  auto& self_ = reinterpret_cast<THPVariable*>(self)->cdata;
336  OptionalDeviceGuard device_guard(device_of(self_));
337  auto value = valueToTensor(self_.type(), py_value);
338 
339  // handle simple types: integers, slices, ellipsis, bool
340  if (index == Py_False) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
341  // do nothing for false (technically we should check the size, but we don't have
342  // real 0-sized shapes.
343  return 0;
344  } else if (index == Py_Ellipsis) {
345  copy_to(self_, value);
346  return 0;
347  } else if (index == Py_None || index == Py_True) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
348  copy_to(self_.unsqueeze(0), value);
349  return 0;
350  } else if (THPUtils_checkLong(index)) {
351  copy_to(applySelect(self_, 0, THPUtils_unpackLong(index)), value);
352  return 0;
353  } else if (PySlice_Check(index)) {
354  copy_to(applySlice(self_, 0, index), value);
355  return 0;
356  }
357 
358  // wrap index in a tuple if it's not already one
359  THPObjectPtr holder = wrapTuple(index);
360 
361  variable_list variableIndices;
362  Variable sliced = applySlicing(self_, holder.get(), variableIndices);
363  if (variableIndices.empty()) {
364  copy_to(sliced, value);
365  return 0;
366  }
367 
368  IntArrayRef slicedValueSizes = slicePrefix1sSize(value.sizes());
369  torch::autograd::Variable valuesSliced;
370  if (!value.sizes().equals(slicedValueSizes)) {
371  valuesSliced = value.view(slicedValueSizes);
372  } else {
373  valuesSliced = value;
374  }
375  dispatch_index_put_(sliced, variableIndices, valuesSliced);
376  return 0;
377  END_HANDLE_TH_ERRORS_RET(-1)
378 }
379 
380 }} // namespace torch::autograd
optional< Device > device_of(Tensor t)
Return the Device of a Tensor, if the Tensor is defined.
Definition: DeviceGuard.h:17
Scalar represents a 0-dimensional tensor which contains a single element.
Definition: Scalar.h:22
AT_CPP14_CONSTEXPR ArrayRef< T > slice(size_t N, size_t M) const
slice(n, m) - Chop off the first N elements of the array, and keep M elements in the array...
Definition: ArrayRef.h:161
constexpr size_t size() const
size - Get the array size.
Definition: ArrayRef.h:138
A OptionalDeviceGuard is an RAII class that sets a device to some value on initialization, and resets the device to its original value on destruction.
Definition: DeviceGuard.h:119
Variable A Variable augments a Tensor with the ability to interact in our autograd machinery...
Definition: variable.h:85
Definition: jit_type.h:17
Flush-To-Zero and Denormals-Are-Zero mode.