diff --git a/test/test_shape_ops.py b/test/test_shape_ops.py index ed9a88d7f4aa..1d65eb163e78 100644 --- a/test/test_shape_ops.py +++ b/test/test_shape_ops.py @@ -8,9 +8,10 @@ from torch._six import nan from torch.testing._internal.common_utils import ( TestCase, run_tests, make_tensor, torch_to_numpy_dtype_dict) +from torch.testing._internal.common_methods_invocations import shape_funcs from torch.testing._internal.common_device_type import ( instantiate_device_type_tests, onlyCPU, dtypes, onlyOnCPUAndCUDA, - dtypesIfCPU, dtypesIfCUDA) + dtypesIfCPU, dtypesIfCUDA, ops) # TODO: replace with make_tensor def _generate_input(shape, dtype, device, with_extremal): @@ -599,7 +600,21 @@ def test_nonzero_non_diff(self, device): nz = x.nonzero() self.assertFalse(nz.requires_grad) +class TestShapeFuncs(TestCase): + """Test suite for Shape manipulating operators using the ShapeFuncInfo.""" + + @dtypes(*(torch.uint8, torch.int64, torch.double, torch.complex128)) + @ops([op for op in shape_funcs if op.name in ['tile', 'repeat']]) + def test_repeat_tile_vs_numpy(self, device, dtype, op): + samples = op.sample_inputs(device, dtype, requires_grad=False) + for sample in samples: + (t, dims) = sample.input + expected = op.ref(t.cpu().numpy(), dims, **sample.kwargs) + result = op(t, dims, **sample.kwargs).cpu().numpy() + self.assertEqual(expected, result) + instantiate_device_type_tests(TestShapeOps, globals()) +instantiate_device_type_tests(TestShapeFuncs, globals()) if __name__ == '__main__': run_tests() diff --git a/test/test_tensor_creation_ops.py b/test/test_tensor_creation_ops.py index 200971ce3147..8c616e096369 100644 --- a/test/test_tensor_creation_ops.py +++ b/test/test_tensor_creation_ops.py @@ -702,27 +702,6 @@ def test_cat_different_devices(self, devices): "input tensors must be on the same device"): torch.cat((cpu, cuda0)) - @onlyOnCPUAndCUDA - def test_tile(self, device): - shapes = ((6, 4, 3), - (1,), - ()) - reps = ((1, 10, 10, 99), - (25, 1, 1), - (3, 3, 3), - (1, 2, 0), - (2, 2), - (2,), - (1,), - ()) - for shape in shapes: - tensor = torch.randn(shape, device=device) - for t in (tensor, tensor.T): - for dims in reps: - expected = np.tile(t.cpu().numpy(), dims) - result = torch.tile(t, dims).cpu().numpy() - self.assertEqual(expected, result) - # TODO: reconcile with other cat tests # TODO: Compare with a NumPy reference instead of CPU @onlyCUDA @@ -1127,25 +1106,6 @@ def test_stack_out(self, device): self.assertEqual(res_out.select(dim, 1), y, atol=0, rtol=0) self.assertEqual(res_out.select(dim, 2), z, atol=0, rtol=0) - def test_repeat(self, device): - initial_shape = (8, 4) - tensor = torch.rand(*initial_shape, device=device) - - size = (3, 1, 1) - torchSize = torch.Size(size) - target = [3, 8, 4] - self.assertEqual(tensor.repeat(*size).size(), target, msg='Error in repeat') - self.assertEqual(tensor.repeat(torchSize).size(), target, - msg='Error in repeat using LongStorage') - result = tensor.repeat(*size) - self.assertEqual(result.size(), target, msg='Error in repeat using result') - result = tensor.repeat(torchSize) - self.assertEqual(result.size(), target, msg='Error in repeat using result and LongStorage') - self.assertEqual(result.mean(0).view(8, 4), tensor, msg='Error in repeat (not equal)') - - zeroDimTarget = torch.Size([24, 0]) - self.assertEqual(tensor.repeat((3, 0)).size(), zeroDimTarget, msg="Error when calling with 0 repeats") - def test_repeat_interleave(self, device): x = torch.tensor([0, 1, 2, 3], device=device) expected = torch.tensor([1, 2, 2, 3, 3, 3], device=device) @@ -1196,26 +1156,6 @@ def test_repeat_interleave(self, device): y = torch.repeat_interleave(x, x) self.assertEqual(y, x) - def test_repeat_tile(self, device): - initial_shape = (8, 4) - - repeats = ((3, 1, 1), - (3, 3, 3), - (1, 2, 1), - (2, 2, 2, 2)) - - def _generate_noncontiguous_input(): - out = np.broadcast_to(np.random.random((1, 4)), initial_shape) - # Note: non-writeable NumPy arrays will warn if converted to tensors - out.setflags(write=True) - assert not (out.flags.c_contiguous or out.flags.f_contiguous) - return out - - for repeat in repeats: - for tensor in (torch.from_numpy(np.random.random(initial_shape)).to(device), - torch.from_numpy(_generate_noncontiguous_input()).to(device)): - self.assertEqual(tensor.repeat(*repeat).cpu().numpy(), np.tile(tensor.cpu().numpy(), repeat)) - # TODO: udpate to work on CUDA, too @onlyCPU def test_new_methods_requires_grad(self, device): diff --git a/test/test_torch.py b/test/test_torch.py index 33199e910baf..84a9ea0da30e 100644 --- a/test/test_torch.py +++ b/test/test_torch.py @@ -6834,7 +6834,6 @@ def inner(self, device, dtype): ('renorm', '2_norm', _small_3d, lambda t, d: [2, 1, 1], 1e-3, 1e-5, 1e-5, _float_types), ('renorm', '2_norm_neg_dim', _small_3d, lambda t, d: [2, -1, 1], 1e-3, 1e-5, 1e-5, _float_types), ('renorm', '1_5_norm', _small_3d, lambda t, d: [1.5, 1, 1], 1e-3, 1e-5, 1e-5, _float_types), - ('repeat', '', _small_2d, lambda t, d: [2, 2, 2], 1e-5, 1e-5, 1e-5, _types, _cpu_types, False), ('size', '', _new_t((1, 2, 3, 4)), lambda t, d: [], 1e-5, 1e-5, 1e-5, _types, _cpu_types, False), ('size', 'dim', _new_t((1, 2, 3, 4)), lambda t, d: [1], 1e-5, 1e-5, 1e-5, _types, _cpu_types, False), ('size', 'neg_dim', _new_t((1, 2, 3, 4)), lambda t, d: [-2], 1e-5, 1e-5, 1e-5, _types, _cpu_types, False), diff --git a/torch/testing/_internal/common_methods_invocations.py b/torch/testing/_internal/common_methods_invocations.py index 6c983abffba0..f534e1139f4d 100644 --- a/torch/testing/_internal/common_methods_invocations.py +++ b/torch/testing/_internal/common_methods_invocations.py @@ -443,6 +443,34 @@ def sample_movedim_moveaxis(op_info, device, dtype, requires_grad): requires_grad=requires_grad), (0, -1, -2, -3), (-3, -2, -1, -0)))) + +def sample_repeat_tile(op_info, device, dtype, requires_grad): + rep_dims = ((), (0, ), (1, ), (0, 2), (1, 1), (2, 3), (2, 3, 2), (0, 2, 3), (2, 1, 1, 1),) + shapes = ((), (0,), (2,), (3, 0), (3, 2), (3, 0, 1)) + + if requires_grad: + # Tests for variant_consistency_jit, grad, gradgrad + # are slower. Use smaller bags of `rep_dims` and `shapes` + # in this case. + rep_dims = ((), (0, ), (0, 2), (1, 1), (2, 3), (1, 3, 2), (3, 1, 1)) # type: ignore + shapes = ((), (0,), (2,), (3, 2)) # type: ignore + + tensors = [make_tensor(shape, device, dtype, + low=None, high=None, + requires_grad=requires_grad) for shape in shapes] + + samples = [] + for rep_dim, tensor in product(rep_dims, tensors): + for t in (tensor, tensor.T): + if op_info.name == 'repeat' and len(rep_dim) >= t.dim(): + # `torch.repeat` errors for `len(rep_dims) < t.dim()`, + # so we filter such combinations. + samples.append(SampleInput((t, rep_dim),)) + elif op_info.name == 'tile': + samples.append(SampleInput((t, rep_dim),)) + + return samples + def np_unary_ufunc_integer_promotion_wrapper(fn): # Wrapper that passes PyTorch's default scalar # type as an argument to the wrapped NumPy @@ -529,6 +557,28 @@ def sample_inputs(self, device, dtype, requires_grad=False): ] +class ShapeFuncInfo(OpInfo): + """Early version of a specialized OpInfo for Shape manipulating operations like tile and roll""" + def __init__(self, + name, # the string name of the function + *, + ref, # a reference function + dtypes=floating_types(), + dtypesIfCPU=None, + dtypesIfCUDA=None, + dtypesIfROCM=None, + sample_inputs_func=None, + **kwargs): + super(ShapeFuncInfo, self).__init__(name, + dtypes=dtypes, + dtypesIfCPU=dtypesIfCPU, + dtypesIfCUDA=dtypesIfCUDA, + dtypesIfROCM=dtypesIfROCM, + sample_inputs_func=sample_inputs_func, + **kwargs) + self.ref = ref + + class HermitianOpInfo(OpInfo): """Operator information for Hermitian functions These are functions that take Hermitian matrices as input. @@ -578,7 +628,6 @@ def sample_inputs_linalg_pinv_hermitian(op_info, device, dtype, requires_grad=Fa o.kwargs = {"hermitian": True} return out - def sample_inputs_linalg_solve(op_info, device, dtype, requires_grad=False): """ This function generates always solvable input for torch.linalg.solve @@ -1405,6 +1454,24 @@ def sample_inputs_fliplr_flipud(op_info, device, dtype, requires_grad): test_inplace_grad=False, supports_tensor_out=False, sample_inputs_func=sample_movedim_moveaxis), + ShapeFuncInfo('repeat', + op=lambda x, dims: x.repeat(dims), + ref=np.tile, + dtypes=all_types_and_complex_and(torch.bool, torch.float16, torch.bfloat16), + supports_tensor_out=False, + test_inplace_grad=False, + skips=( + # torch.repeat does not exist so we get a RuntimeError. + SkipInfo('TestCommon', 'test_variant_consistency_jit', + dtypes=all_types_and_complex_and(torch.bool, torch.float16, torch.bfloat16)), + ), + sample_inputs_func=sample_repeat_tile), + ShapeFuncInfo('tile', + ref=np.tile, + dtypes=all_types_and_complex_and(torch.bool, torch.float16, torch.bfloat16), + supports_tensor_out=False, + test_inplace_grad=False, + sample_inputs_func=sample_repeat_tile), ] if TEST_SCIPY: @@ -1506,6 +1573,7 @@ def reference_sigmoid(x): unary_ufuncs = [op for op in op_db if isinstance(op, UnaryUfuncInfo)] spectral_funcs = [op for op in op_db if isinstance(op, SpectralFuncInfo)] sparse_unary_ufuncs = [op for op in op_db if isinstance(op, UnaryUfuncInfo) and op.supports_sparse is True] +shape_funcs = [op for op in op_db if isinstance(op, ShapeFuncInfo)] def index_variable(shape, max_indices, device=torch.device('cpu')): if not isinstance(shape, tuple): @@ -1982,14 +2050,6 @@ def method_tests(): ('renorm', (S, S, S), (2, 1, 0.5), 'dim', (), [1]), ('renorm', (S, S, S), (1, 2, 3), 'norm_1'), ('renorm', (S, S, S), (inf, 2, 0.5), 'norm_inf'), - ('repeat', (S,), (2,), 'single_number'), - ('repeat', (), (2, 3), 'scalar'), - ('repeat', (2, 2), (3, 2)), - ('repeat', (2, 2), (1, 3, 1, 2), 'unsqueeze'), - ('repeat', (S, S), (1, 1), 'keepdim0'), - ('repeat', (S, S), (3, 1, 1), 'keepdim1'), - ('repeat', (S,), (0, ), 'zero_dim'), - ('repeat', (S,), (0, 2), 'zero_dim_multi'), ('logcumsumexp', (S, S, S), (0,), 'dim0', (), [0]), ('logcumsumexp', (S, S, S), (1,), 'dim1', (), [0]), ('logcumsumexp', (), (0,), 'dim0_scalar', (), [0]), @@ -2206,11 +2266,6 @@ def method_tests(): ('diagonal', (M, M, M), (1, 1, 2), '3d_1'), ('diagonal', (M, M, M), (2, 0, 1), '3d_2'), ('diagonal', (M, M, M), (-2, 0, 1), '3d_3'), - ('tile', (2, 2), ([2, 2, 2],), 'more_reps_dims', (False,)), - ('tile', (2, 2), ([2, 2],), 'same_reps_dims', (False,)), - ('tile', (2, 2), ([2, 3],), 'less_reps_dims', (False,)), - ('tile', (2, 2, 2), ([2, 2, 0],), 'zero_rep_dim', (False,)), - ('tile', (), ([S, S, S],), 'empty_tensor', (False,)), ('tril', (M, M), NO_ARGS), ('tril', (M, M), (2,), 'idx'), ('tril', (S, M, M), NO_ARGS, 'batched'),