Skip to content

Commit 0b543f8

Browse files
committed
warning fix (pytorch#142)
Represent empty tensors as size {0} tensors and fix scalar checks. This gets rid of kUndefinedDimensions and has nice properties like: - the dimensionality always matches the length of the sizes and strides. - the number of elements is always the product of the sizes (starting at the identity) - the shape you pass to factory functions (e.g. randn) matches the shape that is returned etc. In addition to the empty tensor change, this makes some related changes: 1) expand is now a native function, because it needs to operate on the ATen view of the size/strides. 2) adds tests for a number of functions operating on empty, scalar, non-scalar tensors. This uncovered a number of scalar_check bugs; some of these are fixed in the generated code, some that need to be manually specified can be specified by a 'scalar_check' argument in the cwrap. 3) fixes the formatting of empty tensors 4) changes the THLongStorageView API; the public API was getting overly complicated, so now you call 'makeFromSize', 'makeFromStride', 'makeFromLength' and it just handles the correct mapping for that type. Address review comments. Add comment explaining return of dim() when tensor is a scalar. tighten hasCUDA check Update ExpandUtils.h Include what you use, otherwise compilation may break. @prigoyal reported compilation errors with gcc-4.9 I believe. Correct dimensions for reduction functions, squeeze, unsqueeze. Reduction functions that take a dimension now properly reduce down to scalars if passed a 1-dimensional tensor. Squeeze now properly reduces down to scalars as well (and is implemented as a native function). Unsqueeze now handles scalar inputs correctly (so unsqueezing a scalar returns a dim 1 tensor, rather than a dim 2 tensor). update dlpack header and convertors Add an at() method for indexing. (pytorch#152) Signed-off-by: Edward Z. Yang <[email protected]> Fix handling of inf and nan (pytorch#153) Implement stack as a native function. update nn v2 Fix for THD squeeze_out and NativeFunction stack.
1 parent afdf50c commit 0b543f8

22 files changed

+832
-212
lines changed

ExpandUtils.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#include "ATen/ExpandUtils.h"
2+
3+
namespace at {
4+
5+
std::vector<int64_t> infer_size(IntList a, IntList b) {
6+
auto dimsA = a.size();
7+
auto dimsB = b.size();
8+
ptrdiff_t ndim = dimsA > dimsB ? dimsA : dimsB;
9+
std::vector<int64_t> expandedSizes(ndim);
10+
11+
for (long i = ndim - 1; i >= 0; --i) {
12+
long offset = ndim - 1 - i;
13+
long dimA = dimsA - 1 - offset;
14+
long dimB = dimsB - 1 - offset;
15+
long sizeA = (dimA >= 0) ? a[dimA] : 1;
16+
long sizeB = (dimB >= 0) ? b[dimB] : 1;
17+
if (sizeA == sizeB || sizeA == 1 || sizeB == 1) {
18+
expandedSizes[i] = std::max(sizeA, sizeB);
19+
} else {
20+
std::ostringstream oss;
21+
oss << "The size of tensor a (" << sizeA << ") must match the size of tensor b ("
22+
<< sizeB << ") at non-singleton dimension " << i;
23+
throw std::runtime_error(oss.str());
24+
}
25+
}
26+
27+
return expandedSizes;
28+
}
29+
30+
std::tuple<std::vector<int64_t>, std::vector<int64_t> >
31+
inferExpandGeometry(const Tensor &tensor, IntList sizes) {
32+
int64_t ndim = sizes.size();
33+
34+
if (tensor.dim() == 0) {
35+
std::vector<int64_t> expandedStrides(ndim, 0);
36+
return std::tuple<std::vector<int64_t>, std::vector<int64_t>>(sizes.vec(), expandedStrides);
37+
}
38+
std::vector<int64_t> expandedSizes(ndim);
39+
std::vector<int64_t> expandedStrides(ndim);
40+
41+
// create a new geometry for the tensors
42+
for (int64_t i = ndim - 1; i >= 0; --i) {
43+
int64_t offset = ndim - 1 - i;
44+
int64_t dim = tensor.dim() - 1 - offset;
45+
int64_t size = (dim >= 0) ? tensor.sizes()[dim] : 1;
46+
int64_t stride = (dim >= 0) ?
47+
tensor.strides()[dim] : expandedSizes[i + 1] * expandedStrides[i + 1];
48+
int64_t targetSize = sizes[i];
49+
if (targetSize == -1) {
50+
if (dim < 0) {
51+
std::ostringstream oss;
52+
oss << "The expanded size of the tensor (" << targetSize << ") isn't allowed in a leading, "
53+
<< "non-existing dimension " << i;
54+
throw std::runtime_error(oss.str());
55+
} else {
56+
targetSize = size;
57+
}
58+
}
59+
if (size != targetSize) {
60+
if (size == 1) {
61+
size = targetSize;
62+
stride = 0;
63+
} else {
64+
std::ostringstream oss;
65+
oss << "The expanded size of the tensor (" << targetSize << ") must match the existing size (" << size
66+
<< ") at non-singleton dimension " << i;
67+
throw std::runtime_error(oss.str());
68+
}
69+
}
70+
expandedSizes[i] = size;
71+
expandedStrides[i] = stride;
72+
}
73+
return std::tuple<std::vector<int64_t>, std::vector<int64_t>>(expandedSizes, expandedStrides);
74+
}
75+
76+
}

test/scalar_tensor_test.cpp

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
#include "ATen/ATen.h"
2+
#include <iostream>
3+
#include <numeric>
4+
5+
using namespace at;
6+
7+
void assert_equal_size_dim(const Tensor &lhs, const Tensor &rhs) {
8+
assert(lhs.dim() == rhs.dim());
9+
assert(lhs.sizes().equals(rhs.sizes()));
10+
}
11+
12+
bool should_expand(const IntList &from_size, const IntList &to_size) {
13+
if(from_size.size() > to_size.size()) {
14+
return false;
15+
}
16+
for (auto from_dim_it = from_size.rbegin(); from_dim_it != from_size.rend(); ++from_dim_it) {
17+
for (auto to_dim_it = to_size.rbegin(); to_dim_it != to_size.rend(); ++to_dim_it) {
18+
if (*from_dim_it != 1 && *from_dim_it != *to_dim_it) {
19+
return false;
20+
}
21+
}
22+
}
23+
return true;
24+
}
25+
26+
int main() {
27+
Type & T = CPU(kFloat);
28+
29+
std::vector<std::vector<int64_t> > sizes = { {}, {0}, {1}, {1, 1}, {2}};
30+
31+
// single-tensor/size tests
32+
for (auto s = sizes.begin(); s != sizes.end(); ++s) {
33+
// verify that the dim, sizes, strides, etc match what was requested.
34+
auto t = T.ones(*s);
35+
assert(t.dim() == s->size());
36+
assert(t.ndimension() == s->size());
37+
assert(t.sizes().equals(*s));
38+
assert(t.strides().size() == s->size());
39+
auto numel = std::accumulate(s->begin(), s->end(), 1, std::multiplies<int64_t>());
40+
assert(t.numel() == numel);
41+
// verify we can output
42+
std::cout << t << std::endl;
43+
44+
// set_
45+
auto t2 = T.ones(*s);
46+
t2.set_();
47+
assert_equal_size_dim(t2, T.ones({0}));
48+
49+
// unsqueeze
50+
if (t.numel() != 0) {
51+
assert(t.unsqueeze(0).dim() == t.dim() + 1);
52+
} else {
53+
try {
54+
// can't unsqueeze empty tensor
55+
t.unsqueeze(0);
56+
assert (false);
57+
} catch (std::runtime_error &e) {}
58+
}
59+
60+
// unsqueeze_
61+
{
62+
auto t2 = T.ones(*s);
63+
if (t2.numel() != 0) {
64+
auto r = t2.unsqueeze_(0);
65+
assert(r.dim() == t.dim() + 1);
66+
} else {
67+
try {
68+
// can't unsqueeze empty tensor
69+
t2.unsqueeze_(0);
70+
assert (false);
71+
} catch (std::runtime_error &e) {}
72+
}
73+
}
74+
75+
// squeeze (with dimension argument)
76+
if (t.dim() > 0 && t.sizes()[0] == 1) {
77+
assert(t.squeeze(0).dim() == t.dim() - 1);
78+
} else if (t.dim() == 0) {
79+
try {
80+
t.squeeze(0);
81+
assert(false);
82+
} catch (std::runtime_error &e) {}
83+
} else {
84+
// In PyTorch, it is a no-op to try to squeeze a dimension that has size != 1;
85+
// in NumPy this is an error.
86+
assert(t.squeeze(0).dim() == t.dim());
87+
}
88+
89+
// squeeze (with no dimension argument)
90+
{
91+
std::vector<int64_t> size_without_ones;
92+
for (auto size : *s) {
93+
if (size != 1) {
94+
size_without_ones.push_back(size);
95+
}
96+
}
97+
auto result = t.squeeze();
98+
assert_equal_size_dim(result, T.ones(size_without_ones));
99+
}
100+
101+
{
102+
// squeeze_ (with dimension argument)
103+
auto t2 = T.ones(*s);
104+
if (t2.dim() > 0 && t2.sizes()[0] == 1) {
105+
assert(t2.squeeze_(0).dim() == t.dim() - 1);
106+
} else if (t2.dim() == 0) {
107+
try {
108+
t2.squeeze_(0);
109+
assert(false);
110+
} catch (std::runtime_error &e) {}
111+
} else {
112+
// In PyTorch, it is a no-op to try to squeeze a dimension that has size != 1;
113+
// in NumPy this is an error.
114+
assert(t2.squeeze_(0).dim() == t.dim());
115+
}
116+
}
117+
118+
// squeeze_ (with no dimension argument)
119+
{
120+
auto t2 = T.ones(*s);
121+
std::vector<int64_t> size_without_ones;
122+
for (auto size : *s) {
123+
if (size != 1) {
124+
size_without_ones.push_back(size);
125+
}
126+
}
127+
auto r = t2.squeeze_();
128+
assert_equal_size_dim(t2, T.ones(size_without_ones));
129+
}
130+
131+
// reduce (with dimension argument and with 1 return argument)
132+
if (t.dim() > 0 && t.numel() != 0) {
133+
assert(t.sum(0).dim() == t.dim() - 1);
134+
} else if (t.dim() == 0) {
135+
try {
136+
t.sum(0);
137+
assert(false);
138+
} catch (std::runtime_error &e) {}
139+
} else {
140+
// FIXME: you should be able to reduce over size {0}
141+
try {
142+
t.sum(0);
143+
assert(false);
144+
} catch (std::runtime_error &e) {}
145+
}
146+
147+
// reduce (with dimension argument and with 2 return arguments)
148+
if (t.dim() > 0 && t.numel() != 0) {
149+
auto ret = t.min(0);
150+
assert(std::get<0>(ret).dim() == t.dim() - 1);
151+
assert(std::get<1>(ret).dim() == t.dim() - 1);
152+
} else if (t.dim() == 0) {
153+
try {
154+
t.sum(0);
155+
assert(false);
156+
} catch (std::runtime_error &e) {}
157+
} else {
158+
// FIXME: you should be able to reduce over size {0}
159+
try {
160+
t.sum(0);
161+
assert(false);
162+
} catch (std::runtime_error &e) {}
163+
}
164+
165+
// simple indexing
166+
if (t.dim() > 0 && t.numel() != 0) {
167+
assert(t[0].dim() == std::max<int64_t>(t.dim() - 1, 0));
168+
} else if (t.dim() == 0) {
169+
try {
170+
t[0];
171+
assert(false);
172+
} catch (std::runtime_error &e) {}
173+
}
174+
}
175+
176+
for (auto lhs_it = sizes.begin(); lhs_it != sizes.end(); ++lhs_it) {
177+
for (auto rhs_it = sizes.begin(); rhs_it != sizes.end(); ++rhs_it) {
178+
// is_same_size should only match if they are the same shape
179+
{
180+
auto lhs = T.ones(*lhs_it);
181+
auto rhs = T.ones(*rhs_it);
182+
if(*lhs_it != *rhs_it) {
183+
assert(!lhs.is_same_size(rhs));
184+
assert(!rhs.is_same_size(lhs));
185+
}
186+
}
187+
// forced size functions (resize_, resize_as, set_)
188+
{
189+
// resize_
190+
{
191+
auto lhs = T.ones(*lhs_it);
192+
auto rhs = T.ones(*rhs_it);
193+
lhs.resize_(*rhs_it);
194+
assert_equal_size_dim(lhs, rhs);
195+
}
196+
// resize_as_
197+
{
198+
auto lhs = T.ones(*lhs_it);
199+
auto rhs = T.ones(*rhs_it);
200+
lhs.resize_as_(rhs);
201+
assert_equal_size_dim(lhs, rhs);
202+
}
203+
// set_
204+
{
205+
{
206+
// with tensor
207+
auto lhs = T.ones(*lhs_it);
208+
auto rhs = T.ones(*rhs_it);
209+
lhs.set_(rhs);
210+
assert_equal_size_dim(lhs, rhs);
211+
}
212+
{
213+
// with storage
214+
auto lhs = T.ones(*lhs_it);
215+
auto rhs = T.ones(*rhs_it);
216+
auto storage = T.storage(rhs.numel());
217+
lhs.set_(*storage);
218+
// should not be dim 0 because an empty storage is dim 1; all other storages aren't scalars
219+
assert(lhs.dim() != 0);
220+
}
221+
{
222+
// with storage, offset, sizes, strides
223+
auto lhs = T.ones(*lhs_it);
224+
auto rhs = T.ones(*rhs_it);
225+
auto storage = T.storage(rhs.numel());
226+
lhs.set_(*storage, rhs.storage_offset(), rhs.sizes(), rhs.strides());
227+
assert_equal_size_dim(lhs, rhs);
228+
}
229+
}
230+
231+
// assign_
232+
{
233+
auto lhs = T.ones(*lhs_it);
234+
auto lhs_save = T.ones(*lhs_it);
235+
auto rhs = T.ones(*rhs_it);
236+
try {
237+
lhs.assign_(rhs);
238+
assert(lhs_save.numel() == rhs.numel());
239+
// ensure didn't change shape
240+
assert_equal_size_dim(lhs, lhs_save);
241+
} catch (std::runtime_error &e) {
242+
assert(lhs_save.numel() != rhs.numel());
243+
}
244+
}
245+
}
246+
247+
// view
248+
{
249+
auto lhs = T.ones(*lhs_it);
250+
auto rhs = T.ones(*rhs_it);
251+
auto rhs_size = *rhs_it;
252+
try {
253+
auto result = lhs.view(rhs_size);
254+
assert(lhs.numel() == rhs.numel());
255+
assert_equal_size_dim(result, rhs);
256+
} catch (std::runtime_error &e) {
257+
assert(lhs.numel() != rhs.numel());
258+
}
259+
}
260+
261+
// expand
262+
{
263+
auto lhs = T.ones(*lhs_it);
264+
auto lhs_size = *lhs_it;
265+
auto rhs = T.ones(*rhs_it);
266+
auto rhs_size = *rhs_it;
267+
bool should_pass = should_expand(lhs_size, rhs_size);
268+
try {
269+
auto result = lhs.expand(rhs_size);
270+
assert(should_pass);
271+
assert_equal_size_dim(result, rhs);
272+
} catch (std::runtime_error &e) {
273+
assert(!should_pass);
274+
}
275+
276+
// in-place functions (would be good if we can also do a non-broadcasting one, b/c
277+
// broadcasting functions will always end up operating on tensors of same size;
278+
// is there an example of this outside of assign_ ?)
279+
{
280+
bool should_pass_inplace = should_expand(rhs_size, lhs_size);
281+
try {
282+
lhs.add_(rhs);
283+
assert(should_pass_inplace);
284+
assert_equal_size_dim(lhs, T.ones(*lhs_it));
285+
} catch (std::runtime_error &e) {
286+
assert(!should_pass_inplace);
287+
}
288+
}
289+
}
290+
}
291+
}
292+
293+
return 0;
294+
}

torch/lib/ATen/ArrayRef.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ namespace at {
139139
return Data[Index];
140140
}
141141

142+
/// Vector compatibility
143+
const T &at(size_t Index) const {
144+
assert(Index < Length && "Invalid index!");
145+
return Data[Index];
146+
}
147+
142148
/// Disallow accidental assignment from a temporary.
143149
///
144150
/// The declaration here is extra complicated so that "arrayRef = {}"

torch/lib/ATen/Context.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ Context & globalContext() {
5555

5656
bool Context::hasCUDA() const {
5757
#ifdef AT_CUDA_ENABLED
58+
int count;
59+
cudaError_t err = cudaGetDeviceCount(&count);
60+
if (err == cudaErrorInsufficientDriver) {
61+
return false;
62+
}
5863
return true;
5964
#else
6065
return false;

0 commit comments

Comments
 (0)