Skip to content
This repository was archived by the owner on Nov 17, 2023. It is now read-only.

[MXNET-283] Error handling for non-positive reps of tile op #10417

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/operator/tensor/matrix_op-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1442,7 +1442,8 @@ struct TileParam : public dmlc::Parameter<TileParam> {
TShape reps;
DMLC_DECLARE_PARAMETER(TileParam) {
DMLC_DECLARE_FIELD(reps)
.describe("The number of times for repeating the tensor a."
.describe("The number of times for repeating the tensor a. Each dim size of reps"
" must be a positive integer."
" If reps has length d, the result will have dimension of max(d, a.ndim);"
" If a.ndim < d, a is promoted to be d-dimensional by prepending new axes."
" If a.ndim > d, reps is promoted to a.ndim by pre-pending 1's to it.");
Expand All @@ -1462,6 +1463,9 @@ inline bool TileOpShape(const nnvm::NodeAttrs& attrs,
SHAPE_ASSIGN_CHECK(*out_attrs, 0, ishape);
return true;
}
for (size_t i = 0; i < reps.ndim(); ++i) {
CHECK_GT(reps[i], 0) << "invalid reps=" << i << ", dim size must be greater than zero";
}
TShape oshape(std::max(ishape.ndim(), reps.ndim()));
int i1 = static_cast<int>(ishape.ndim()) - 1;
int i2 = static_cast<int>(reps.ndim()) - 1;
Expand Down
44 changes: 20 additions & 24 deletions tests/python/unittest/test_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import itertools
from numpy.testing import assert_allclose, assert_array_equal
from mxnet.test_utils import *
from mxnet.base import py_str
from mxnet.base import py_str, MXNetError
from common import setup_module, with_seed
import unittest

Expand Down Expand Up @@ -3480,24 +3480,23 @@ def test_reverse():
@with_seed()
def test_tile():
def test_normal_case():
ndim_max = 3 # max number of dims of the ndarray
size_max = 10 # max number of elements in each dim
length_max = 3 # max length of reps
rep_max = 10 # max number of tiling in each dim
for ndim in range(ndim_max, ndim_max+1):
shape = ()
for i in range(0, ndim):
shape += (np.random.randint(1, size_max+1), )
ndim_min = 1
ndim_max = 5 # max number of dims of the ndarray
size_max = 10 # max number of elements in each dim
length_max = 3 # max length of reps
rep_max = 10 # max number of tiling in each dim
for ndim in range(ndim_min, ndim_max+1):
shape = []
for i in range(1, ndim+1):
shape.append(np.random.randint(1, size_max+1))
shape = tuple(shape)
a = np.random.randint(0, 100, shape)
a = np.asarray(a, dtype=np.int32)
if ndim == 0:
a = np.array([])
b = mx.nd.array(a, ctx=default_context(), dtype=a.dtype)
b = mx.nd.array(a, dtype=a.dtype)

reps_len = np.random.randint(0, length_max+1)
reps_len = np.random.randint(1, length_max+1)
reps_tuple = ()
for i in range(1, reps_len):
reps_tuple += (np.random.randint(0, rep_max), )
reps_tuple += (np.random.randint(1, rep_max), )
reps_array = np.asarray(reps_tuple)

a_tiled = np.tile(a, reps_array)
Expand All @@ -3521,14 +3520,6 @@ def test_empty_reps():
b_tiled = mx.nd.tile(b, ()).asnumpy()
assert same(a_tiled, b_tiled)

def test_zero_reps():
a = np.array([[2, 3, 4], [5, 6, 7]], dtype=np.int32)
b = mx.nd.array(a, ctx=default_context(), dtype=a.dtype)
reps = (2, 0, 4, 5)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this supported previously? What was the behavior?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would return an empty NDArray with some dim size equal to zero. I was following the NumPy's convention to implement the behavior. Since MXNet does not support empty NDArrays in any way, I think it's okay to move this to assert_exception test?

a_tiled = np.tile(a, reps)
b_tiled = mx.nd.tile(b, reps).asnumpy()
assert same(a_tiled, b_tiled)

def test_tile_backward():
data = mx.sym.Variable('data')
n1 = 2
Expand Down Expand Up @@ -3565,12 +3556,17 @@ def test_tile_numeric_gradient():
test = mx.sym.tile(data, reps=reps)
check_numeric_gradient(test, [data_tmp], numeric_eps=1e-2, rtol=1e-2)

def test_invalid_reps():
data = mx.nd.arange(16).reshape((4, 4))
assert_exception(mx.nd.tile, MXNetError, data, (1, 2, -3))
assert_exception(mx.nd.tile, MXNetError, data, (1, 0, 3))

test_normal_case()
test_empty_tensor()
test_empty_reps()
test_zero_reps()
test_tile_backward()
test_tile_numeric_gradient()
test_invalid_reps()


@with_seed()
Expand Down