def test_save_different_dtype_unallocated(self): devices = ['cpu'] if torch.cuda.is_available(): devices.append('cuda') def save_load_check(a, b): with io.BytesIO() as f: torch.save([a, b], f) f.seek(0) a_loaded, b_loaded = torch.load(f) self.assertEqual(a, a_loaded) self.assertEqual(b, b_loaded) for device, dtype in product( devices, all_types_and_complex_and(torch.half, torch.bfloat16, torch.bool)): a = torch.tensor([], dtype=dtype, device=device) for other_dtype in all_types_and_complex_and( torch.half, torch.bfloat16, torch.bool): s = torch._TypedStorage(wrap_storage=a.storage()._untyped(), dtype=other_dtype) save_load_check(a, s) save_load_check(a.storage(), s) b = torch.tensor([], dtype=other_dtype, device=device) save_load_check(a, b)
def get_supported_dtypes(op, sample_inputs_fn, device_type): # Returns the supported dtypes for the given operator and device_type pair. assert device_type in ['cpu', 'cuda'] if not TEST_CUDA and device_type == 'cuda': warnings.warn( "WARNING: CUDA is not available, empty_dtypes dispatch will be returned!" ) return _dynamic_dispatch_dtypes(()) supported_dtypes = set() for dtype in all_types_and_complex_and(torch.bool, torch.bfloat16, torch.half): try: samples = sample_inputs_fn(op, device_type, dtype, False) except RuntimeError: # If `sample_inputs_fn` doesn't support sampling for a given # `dtype`, we assume that the `dtype` is not supported. # We raise a warning, so that user knows that this was the case # and can investigate if there was an issue with the `sample_inputs_fn`. warnings.warn( f"WARNING: Unable to generate sample for device:{device_type} and dtype:{dtype}" ) continue # We assume the dtype is supported # only if all samples pass for the given dtype. supported = True for sample in samples: try: op(sample.input, *sample.args, **sample.kwargs) except RuntimeError as re: # dtype is not supported supported = False break if supported: supported_dtypes.add(dtype) return _dynamic_dispatch_dtypes(supported_dtypes)
class TestForeach(TestCase): @property def is_cuda(self): return self.device_type == 'cuda' # note(mkozuki): It might be the case that the expected number of `cudaLaunchKernel`s # is greater than 1 once foreach functions internally separate their input `TensorList`s by # devices & dtypes into vectors of tensors. def _get_funcs(self, op, n_expected_cudaLaunchKernels: int): return ( ForeachFuncWrapper(op.method_variant, n_expected_cudaLaunchKernels), RegularFuncWrapper(op.ref), ForeachFuncWrapper(op.inplace_variant, n_expected_cudaLaunchKernels), RegularFuncWrapper(op.ref_inplace), ) def _binary_test(self, dtype, op, ref, inputs, is_fastpath, is_inplace, *, alpha=None): ref_inputs = [[t.clone().detach() for t in inputs[0]], inputs[1] ] if is_inplace else inputs try: actual = op(inputs, self.is_cuda, is_fastpath) except RuntimeError as e: with self.assertRaisesRegex(type(e), re.escape(str(e))): ref(ref_inputs) else: expected = ref(ref_inputs) self.assertEqual(actual, expected) if alpha is not None: kwargs = {'alpha': alpha} ref_inputs = inputs try: actual = op(inputs, self.is_cuda, is_fastpath, **kwargs) except RuntimeError as e: with self.assertRaisesRegex(type(e), re.escape(str(e))): ref(ref_inputs, **kwargs) else: expected = ref(ref_inputs, **kwargs) if dtype in (torch.float16, torch.bfloat16) and TEST_WITH_ROCM: self.assertEqual(expected, actual, atol=1.e-3, rtol=default_tolerances(dtype)[0]) else: self.assertEqual(expected, actual) def _test_binary_op_tensorlists(self, device, dtype, opinfo, N, is_fastpath, disable_fastpath): n_expected_cudaLaunchKernels = N if disable_fastpath else 1 op, ref, inplace_op, inplace_ref = self._get_funcs( opinfo, n_expected_cudaLaunchKernels) inputs = [ opinfo.sample_inputs(device, dtype, N, noncontiguous=not is_fastpath), opinfo.sample_inputs(device, dtype, N, noncontiguous=not is_fastpath), ] self._binary_test(dtype, op, ref, inputs, is_fastpath, is_inplace=False) self._binary_test(dtype, inplace_op, inplace_ref, inputs, is_fastpath, is_inplace=True) if opinfo.supports_alpha_param: alpha = None if dtype in integral_types(): alpha = 3 elif dtype.is_complex: alpha = complex(3, 3) else: alpha = 3.14 self._binary_test(dtype, op, ref, inputs, is_fastpath, is_inplace=False, alpha=alpha) self._binary_test(dtype, inplace_op, inplace_ref, inputs, is_fastpath, is_inplace=True, alpha=alpha) # Tests of implicit broadcasting # When sizes of tensors don't match, foreach functions are supposed to choose slow path # even if this methods's argument `is_fastpath` is True. # `cudaLaunchKernel` will be equal to `N`. For assert in `ForeachFuncWrapper` to pass, # we pass `is_fastpath and disable_fastpath` to `_binary_test`'s argument of is_fastpath. # as n_expected_cudaLaunchKernels is N if disable_fastpath. inputs = [ opinfo.sample_inputs(device, dtype, N, noncontiguous=not is_fastpath), [ make_tensor((N - i, 1), device=device, dtype=dtype, noncontiguous=not is_fastpath) for i in range(N) ], ] self._binary_test(dtype, op, ref, inputs, is_fastpath and disable_fastpath, is_inplace=False) self._binary_test(dtype, inplace_op, inplace_ref, inputs, is_fastpath and disable_fastpath, is_inplace=True) @skipMeta @ops(foreach_binary_op_db) def test_binary_op_tensorlists_fastpath(self, device, dtype, op): for N in N_values: disable_fastpath = op.ref == torch.div and dtype in integral_types_and( torch.bool) if op.ref == torch.add and dtype == torch.bool: disable_fastpath = True self._test_binary_op_tensorlists(device, dtype, op, N, True, disable_fastpath) @ops(foreach_binary_op_db) def test_binary_op_tensorlists_slowpath(self, device, dtype, op): for N in N_values: self._test_binary_op_tensorlists(device, dtype, op, N, False, False) def _test_binary_op_scalar(self, device, dtype, opinfo, N, scalar, is_fastpath, disable_fastpath): n_expected_cudaLaunchKernels = N if disable_fastpath else 1 op, ref, inplace_op, inplace_ref = self._get_funcs( opinfo, n_expected_cudaLaunchKernels) inputs = [ opinfo.sample_inputs(device, dtype, N, noncontiguous=not is_fastpath), scalar ] self._binary_test(dtype, op, ref, inputs, is_fastpath, is_inplace=False) self._binary_test(dtype, inplace_op, inplace_ref, inputs, is_fastpath, is_inplace=True) @skipMeta @ops(foreach_binary_op_db) def test_binary_op_scalar_fastpath(self, device, dtype, op): for N, scalar in itertools.product(N_values, Scalars): disable_fastpath = op.ref == torch.div and dtype in integral_types_and( torch.bool) if isinstance(scalar, int): disable_fastpath |= dtype == torch.bool if isinstance(scalar, float): disable_fastpath |= dtype in integral_types_and(torch.bool) if isinstance(scalar, bool): disable_fastpath |= dtype == torch.bool if op.ref in (torch.add, torch.mul): disable_fastpath = False if isinstance(scalar, complex): disable_fastpath |= dtype not in complex_types() self._test_binary_op_scalar(device, dtype, op, N, scalar, True, disable_fastpath) @ops(foreach_binary_op_db) def test_binary_op_scalar_slowpath(self, device, dtype, op): for N, scalar in itertools.product(N_values, Scalars): self._test_binary_op_scalar(device, dtype, op, N, scalar, False, False) def _test_binary_op_scalarlist(self, device, dtype, opinfo, N, scalarlist, is_fastpath, disable_fastpath): n_expected_cudaLaunchKernels = N if disable_fastpath else 1 op, ref, inplace_op, inplace_ref = self._get_funcs( opinfo, n_expected_cudaLaunchKernels) inputs = [ opinfo.sample_inputs(device, dtype, N, noncontiguous=not is_fastpath), scalarlist ] self._binary_test(dtype, op, ref, inputs, is_fastpath, is_inplace=False) self._binary_test(dtype, inplace_op, inplace_ref, inputs, is_fastpath, is_inplace=True) # note(mkozuki): Why two functions depending on with/without bool? # `foreach_sub` & `foreach_sub_` do `sub_check(tensors[i], scalars[i])` from i=1...N. # So, if scalarlist has one or more bool values, `foreach_sub` and `foreach_sub_` # raise bool subtraction error before doing any math. # While regular `sub` and `sub_` do some math until they encounter bool. # So, foreach sub's throw bool sub error first. However, regular sub's throw different # errors depending on the order of scalarlist. To keep actual unit test impl simple, # separating mixed scalarlist tests. By setting the first element of scalarlist to bool, # they are expected to throw bool sub error even in inplace test. @skipMeta @ops(foreach_binary_op_db) def test_binary_op_scalarlist_fastpath(self, device, dtype, op): for N in N_values: for type_str, scalarlist in getScalarLists(N): bool_int_div = op.ref == torch.div and dtype in integral_types_and( torch.bool) disable_fastpath = bool_int_div if type_str == "int": disable_fastpath |= dtype == torch.bool if type_str == "float": disable_fastpath |= dtype in integral_types_and(torch.bool) if type_str == "complex": disable_fastpath |= dtype not in complex_types() if type_str == "mixed": disable_fastpath |= True and dtype not in complex_types() self._test_binary_op_scalarlist(device, dtype, op, N, scalarlist, True, disable_fastpath) @ops(foreach_binary_op_db) def test_binary_op_scalarlist_slowpath(self, device, dtype, op): for N in N_values: for _, scalarlist in getScalarLists(N): self._test_binary_op_scalarlist(device, dtype, op, N, scalarlist, False, False) def _pointwise_test(self, dtype, op, ref, inputs, is_fastpath, is_inplace, *, values=None): ref_inputs = [[t.clone().detach() for t in inputs[0]], inputs[1], inputs[2]] if is_inplace else inputs try: actual = op(inputs, self.is_cuda, is_fastpath) except RuntimeError as e: with self.assertRaisesRegex(type(e), re.escape(str(e))): ref(ref_inputs) else: expected = ref(ref_inputs) self.assertEqual(expected, actual) if values is not None: try: actual = op(inputs + [values], self.is_cuda, is_fastpath) except RuntimeError as e: with self.assertRaisesRegex(type(e), re.escape(str(e))): ref(ref_inputs, values=values) else: expected = ref(ref_inputs, values=values) self.assertEqual(expected, actual) def _test_pointwise_op(self, device, dtype, opinfo, N, is_fastpath, disable_fastpath, *, values=None): n_expected_cudaLaunchKernels = N if disable_fastpath else 1 op, ref, inplace_op, inplace_ref = self._get_funcs( opinfo, n_expected_cudaLaunchKernels) inputs = [ opinfo.sample_inputs(device, dtype, N, noncontiguous=not is_fastpath), opinfo.sample_inputs(device, dtype, N, noncontiguous=not is_fastpath), opinfo.sample_inputs(device, dtype, N, noncontiguous=not is_fastpath), ] self._pointwise_test(dtype, op, ref, inputs, is_fastpath, is_inplace=False, values=values) self._pointwise_test(dtype, inplace_op, inplace_ref, inputs, is_fastpath, is_inplace=True, values=values) # Tests of implicit broadcasting inputs = [ opinfo.sample_inputs(device, dtype, N, noncontiguous=not is_fastpath, same_size=True), [ make_tensor((N - i, 1), device=device, dtype=dtype, noncontiguous=not is_fastpath) for i in range(N) ], [ make_tensor((1, N - i), device=device, dtype=dtype, noncontiguous=not is_fastpath) for i in range(N) ], ] self._pointwise_test(dtype, op, ref, inputs, is_fastpath and disable_fastpath, is_inplace=False, values=values) self._pointwise_test(dtype, inplace_op, inplace_ref, inputs, is_fastpath and disable_fastpath, is_inplace=True, values=values) @skipMeta @ops(foreach_pointwise_op_db) def test_pointwise_op_fastpath(self, device, dtype, op): disable_fastpath = dtype in integral_types_and(torch.bool) # for N, scalar in itertools.product(N_values, Scalars): for N in N_values: self._test_pointwise_op(device, dtype, op, N, True, disable_fastpath) for scalar in Scalars: self._test_pointwise_op(device, dtype, op, N, True, disable_fastpath, values=scalar) for _, scalarlist in getScalarLists(N): self._test_pointwise_op(device, dtype, op, N, True, disable_fastpath, values=scalarlist) @ops(foreach_pointwise_op_db) def test_pointwise_op_slowpath(self, device, dtype, op): # for N, scalar in itertools.product(N_values, Scalars): for N in N_values: self._test_pointwise_op(device, dtype, op, N, False, False) for scalar in Scalars: self._test_pointwise_op(device, dtype, op, N, False, False, values=scalar) for _, scalarlist in getScalarLists(N): self._test_pointwise_op(device, dtype, op, N, False, False, values=scalarlist) # note(mkozuki): fastpath test uses dtypes which fastpath implementation supports. # To confirm the dtypes of `OpInfo` cover the dtypes that the function support, # this test does not use `try-except` for fastpath. def _regular_unary_test(self, dtype, op, ref, inputs, is_fastpath): if is_fastpath: self.assertEqual(ref(inputs), op(inputs, self.is_cuda, is_fastpath)) return try: actual = op(inputs, self.is_cuda, is_fastpath) except RuntimeError as e: with self.assertRaisesRegex(type(e), re.escape(str(e))): ref(inputs) else: expected = ref(inputs) self.assertEqual(actual, expected) # note(mkozuki): why `try-except` for both fastpath? # - inputs for fastpath can be integer tensors. # - this is becase opinfo dtypes are configured for outpulace implementation # - for integer inputs, trigonometric functions and exponential function returns float outputs, # which causes "result type Float can't be case to the desired type" error. # Thus, `try-except` is used even if `is_fastpath` is `True`. def _inplace_unary_test(self, dtype, inplace, inplace_ref, inputs, is_fastpath): copied_inputs = [[t.clone().detach() for t in tensors] for tensors in inputs] try: inplace(inputs, self.is_cuda, is_fastpath) except RuntimeError as e: with self.assertRaisesRegex(type(e), re.escape(str(e))): inplace_ref(copied_inputs) else: inplace_ref(copied_inputs), self.assertEqual(copied_inputs, inputs) def _test_unary(self, device, dtype, opinfo, N, is_fastpath): op, ref, inplace_op, inplace_ref = self._get_funcs(opinfo, 1) inputs = opinfo.sample_inputs(device, dtype, N, noncontiguous=not is_fastpath), # note(mkozuki): Complex inputs for `_foreach_abs` go through slowpath. if opinfo.name == "_foreach_abs" and dtype in complex_types(): is_fastpath = False self._regular_unary_test(dtype, op, ref, inputs, is_fastpath) self._inplace_unary_test(dtype, inplace_op, inplace_ref, inputs, is_fastpath) @skipMeta @ops(foreach_unary_op_db) def test_unary_fastpath(self, device, dtype, op): for N in N_values: self._test_unary(device, dtype, op, N, is_fastpath=True) @ops(foreach_unary_op_db, dtypes=all_types_and_complex_and(torch.half, torch.bfloat16, torch.bool)) def test_unary_slowpath(self, device, dtype, op): for N in N_values: self._test_unary(device, dtype, op, N, is_fastpath=False) # note(crcrpar): `torch.maximum` and `torch.minimum` support `out` arg but there seem to be no inplace versions. # So, compare `inplace_op` results with `ref`'s outputs. def _minmax_test(self, opinfo, inputs, is_fastpath, n_expected_cudaLaunchKernels): op, ref, inplace_op, _ = self._get_funcs(opinfo, n_expected_cudaLaunchKernels) expected = ref(inputs) self.assertEqual(expected, op(inputs, self.is_cuda, is_fastpath)) inplace_inputs = [[t.clone() for t in inputs[0]], inputs[1]] inplace_op(inplace_inputs, self.is_cuda, is_fastpath) self.assertEqual(expected, inplace_inputs[0]) @ops(foreach_minmax_op_db) def test_minmax_fastpath(self, device, dtype, op): for N in N_values: inputs = tuple( op.sample_inputs(device, dtype, N) for _ in range(2)) self._minmax_test(op, inputs, True, N if dtype == torch.bool else 1) @ops(foreach_minmax_op_db, dtypes=all_types_and(torch.half, torch.bfloat16, torch.bool)) def test_minmax_slowpath(self, device, dtype, op): for N in N_values: inputs = tuple( op.sample_inputs(device, dtype, N, noncontiguous=True) for _ in range(2)) self._minmax_test(op, inputs, False, 1) # note(mkozuki): ForeachFuncInfo's of both `_foreach_maximum` and `_foreach_minimum` include integer types. # so, manually limit dtypes to fp types for inf&nan tests. @ops(foreach_minmax_op_db, dtypes=floating_types_and(torch.half, torch.bfloat16)) def test_minmax_float_inf_nan(self, device, dtype, op): inputs = ( [ torch.tensor([float('inf')], device=device, dtype=dtype), torch.tensor([-float('inf')], device=device, dtype=dtype), torch.tensor([float('nan')], device=device, dtype=dtype), torch.tensor([float('nan')], device=device, dtype=dtype) ], [ torch.tensor([-float('inf')], device=device, dtype=dtype), torch.tensor([float('inf')], device=device, dtype=dtype), torch.tensor([float('inf')], device=device, dtype=dtype), torch.tensor([float('nan')], device=device, dtype=dtype) ], ) self._minmax_test(op, inputs, True, 1) def _reduce_test(self, opinfo, inputs, ord, is_fastpath, n_expected_cudaLaunchKernels): op, ref, _, _ = self._get_funcs(opinfo, n_expected_cudaLaunchKernels) self.assertEqual(ref(inputs, ord=ord), op(inputs, self.is_cuda, is_fastpath, ord=ord)) @ops(foreach_reduce_op_db) def test_reduce_fastpath(self, device, dtype, op): for N, ord in itertools.product(N_values, (0, 1, 2, -1, -2)): if ord in (1, 2) and dtype in floating_types_and( torch.half, torch.bfloat16): n_expected_cudaLaunchKernels = 3 else: n_expected_cudaLaunchKernels = N inputs = op.sample_inputs(device, dtype, N, noncontiguous=False), self._reduce_test(op, inputs, ord, True, n_expected_cudaLaunchKernels) @ops(foreach_reduce_op_db) def test_reduce_slowpath(self, device, dtype, op): for N, ord in itertools.product(N_values, (0, 1, 2, -1, -2)): inputs = op.sample_inputs(device, dtype, N, noncontiguous=True), self._reduce_test(op, inputs, ord, False, 1) @dtypes(*all_types_and_complex_and(torch.half, torch.bfloat16, torch.bool)) def test_add_scalar_with_empty_list_and_empty_tensor(self, device, dtype): # TODO: enable empty list case for tensors in [[torch.randn([0])]]: res = torch._foreach_add(tensors, 1) self.assertEqual(res, tensors) torch._foreach_add_(tensors, 1) self.assertEqual(res, tensors) @ops(foreach_binary_op_db, dtypes=all_types_and_complex_and(torch.half, torch.bfloat16, torch.bool)) def test_binary_op_scalar_with_overlapping_tensors(self, device, dtype, op): foreach_op, ref = op.method_variant, op.ref tensors = [ torch.ones(1, 1, device=device, dtype=dtype).expand(2, 1, 3) ] if ref == torch.sub and dtype == torch.bool: with self.assertRaisesRegex(RuntimeError, re.escape(_BOOL_SUB_ERR_MSG)): [ref(t, 1) for t in tensors] with self.assertRaisesRegex(RuntimeError, re.escape(_BOOL_SUB_ERR_MSG)): foreach_op(tensors, 1) return expected = [ref(t, 1) for t in tensors] res = foreach_op(tensors, 1) self.assertEqual(res, expected) # note(mkozuki): this test case fails with Meta at least in my local environment. # The message was # `AssertionError: NotImplementedError("Could not run 'aten::_foreach_add.Scalar' with arguments from the 'Meta' backend.` @skipMeta @ops(foreach_binary_op_db, allowed_dtypes=[torch.float]) def test_binary_op_scalar_with_different_tensor_dtypes( self, device, dtype, op): foreach_op = op.method_variant tensors = [ torch.tensor([1.1], dtype=torch.float, device=device), torch.tensor([1], dtype=torch.long, device=device) ] runtime_error = None try: foreach_op(tensors, 1) except RuntimeError as e: runtime_error = e self.assertIsNone(runtime_error) @ops(foreach_binary_op_db, dtypes=all_types_and_complex_and(torch.half, torch.bfloat16, torch.bool)) def test_binary_op_list_error_cases(self, device, dtype, op): foreach_op, foreach_op_, ref, ref_ = op.method_variant, op.inplace_variant, op.ref, op.ref_inplace tensors1 = [] tensors2 = [] # Empty lists with self.assertRaisesRegex( RuntimeError, "There were no tensor arguments to this function"): foreach_op(tensors1, tensors2) with self.assertRaisesRegex( RuntimeError, "There were no tensor arguments to this function"): foreach_op_(tensors1, tensors2) # One empty list tensors1.append(torch.tensor([1], device=device, dtype=dtype)) with self.assertRaisesRegex( RuntimeError, "Tensor list must have same number of elements as scalar list." ): foreach_op(tensors1, tensors2) with self.assertRaisesRegex( RuntimeError, "Tensor list must have same number of elements as scalar list." ): foreach_op_(tensors1, tensors2) # Lists have different amount of tensors tensors2.append(torch.tensor([1], device=device)) tensors2.append(torch.tensor([1], device=device)) with self.assertRaisesRegex( RuntimeError, "Tensor lists must have the same number of tensors, got 1 and 2" ): foreach_op(tensors1, tensors2) with self.assertRaisesRegex( RuntimeError, "Tensor lists must have the same number of tensors, got 1 and 2" ): foreach_op_(tensors1, tensors2) # Corresponding tensors with different sizes that aren't compatible with broadcast # If sizes are different then foreach chooses slow path, thus error messages are expected # to be the same as torch regular function. tensors1 = [ torch.zeros(10, 10, device=device, dtype=dtype) for _ in range(10) ] tensors2 = [ torch.ones(11, 11, device=device, dtype=dtype) for _ in range(10) ] try: foreach_op(tensors1, tensors2) except RuntimeError as e: with self.assertRaisesRegex(type(e), re.escape(str(e))): [ref(t1, t2) for t1, t2 in zip(tensors1, tensors2)] try: foreach_op_(tensors1, tensors2) except RuntimeError as e: with self.assertRaisesRegex(type(e), re.escape(str(e))): [ref_(t1, t2) for t1, t2 in zip(tensors1, tensors2)] # different devices if self.device_type == "cuda" and torch.cuda.device_count() > 1: tensor1 = torch.zeros(10, 10, device="cuda:0", dtype=dtype) tensor2 = torch.ones(10, 10, device="cuda:1", dtype=dtype) if dtype == torch.bool and foreach_op == torch._foreach_sub: with self.assertRaisesRegex(RuntimeError, re.escape(_BOOL_SUB_ERR_MSG)): foreach_op([tensor1], [tensor2]) with self.assertRaisesRegex(RuntimeError, re.escape(_BOOL_SUB_ERR_MSG)): foreach_op_([tensor1], [tensor2]) return with self.assertRaisesRegex( RuntimeError, "Expected all tensors to be on the same device"): foreach_op([tensor1], [tensor2]) if dtype in integral_types_and( torch.bool) and foreach_op == torch._foreach_div: with self.assertRaisesRegex(RuntimeError, "result type"): foreach_op_([tensor1], [tensor2]) else: with self.assertRaisesRegex( RuntimeError, "Expected all tensors to be on the same device"): foreach_op_([tensor1], [tensor2]) @skipMeta @unittest.skipIf(not torch.cuda.is_available(), "CUDA not found") @ops(foreach_binary_op_db, dtypes=all_types_and_complex_and(torch.half, torch.bfloat16, torch.bool)) def test_binary_op_list_slow_path(self, device, dtype, op): # note(mkozuki): why `n_expected_cudaLaunchKernels=0`? # In this test, foreach functions don't go through fast path, # but as there is only one tensor in each list of tensors, # `cudaLaunchKernel` is 1 so ForeachFuncWrapper internal assert fails. foreach_op, native_op, foreach_op_, native_op_ = self._get_funcs( op, n_expected_cudaLaunchKernels=0) # 0-strides tensor1 = make_tensor((10, 10), dtype=dtype, device=device) tensor2 = make_tensor((1, ), device=device, dtype=dtype).expand_as(tensor1) inputs = ([tensor1], [tensor2]) self._binary_test(dtype, foreach_op, native_op, inputs, is_fastpath=False, is_inplace=False) self._binary_test(dtype, foreach_op_, native_op_, inputs, is_fastpath=False, is_inplace=True) # different strides tensor1 = torch.zeros(10, 10, device=device, dtype=dtype) tensor2 = torch.ones(10, 10, device=device, dtype=dtype) inputs = ([tensor1], [tensor2.t()]) self._binary_test(dtype, foreach_op, native_op, inputs, is_fastpath=False, is_inplace=False) self._binary_test(dtype, foreach_op_, native_op_, inputs, is_fastpath=False, is_inplace=True) # non contiguous tensor1 = make_tensor((5, 2, 1, 3), device=device, dtype=dtype, noncontiguous=True) tensor2 = make_tensor((5, 2, 1, 3), device=device, dtype=dtype, noncontiguous=True) self.assertFalse(tensor1.is_contiguous()) self.assertFalse(tensor2.is_contiguous()) inputs = ([tensor1], [tensor2]) self._binary_test(dtype, foreach_op, native_op, inputs, is_fastpath=False, is_inplace=False) self._binary_test(dtype, foreach_op_, native_op_, inputs, is_fastpath=False, is_inplace=True) # sliced tensor tensor1 = make_tensor((5, 2, 1, 3), device=device, dtype=dtype) tensor2 = make_tensor((5, 2, 1, 3 * 7), device=device, dtype=dtype)[:, :, :, ::7] inputs = ([tensor1], [tensor2]) self._binary_test(dtype, foreach_op, native_op, inputs, is_fastpath=False, is_inplace=False) self._binary_test(dtype, foreach_op_, native_op_, inputs, is_fastpath=False, is_inplace=True) # note: Below three tests (postfixed with `_tensors_on_different_devices`) # checks whether foreach works with lists of tensors on different devices # but tensors of the same index are on the same device, e.g., ['cuda', 'cpu]. @onlyCUDA @ops(foreach_unary_op_db) def test_unary_op_tensors_on_different_devices(self, device, dtype, op): method, ref, inplace_method, ref_inplace = self._get_funcs(op, 1) # tensors: ['cuda', 'cpu] tensors = op.sample_inputs(device, dtype, 2) tensors[1] = tensors[1].to('cpu') try: actual = method((tensors, ), False, False) except RuntimeError as e: with self.assertRaisesRegex(type(e), str(e)): ref((tensors, )) else: expected = ref((tensors, )) self.assertEqual(expected, actual) try: inplace_method((tensors, ), False, False) except RuntimeError as e: with self.assertRaisesRegex(type(e), str(e)): ref_inplace((tensors, )) else: self.assertEqual(expected, tensors) @onlyCUDA @ops(foreach_binary_op_db) def test_binary_op_tensors_on_different_devices(self, device, dtype, op): # `tensors1`: ['cuda', 'cpu'] # `tensors2`: ['cuda', 'cpu'] _cuda_tensors = op.sample_inputs(device, dtype, 2, same_size=True) _cpu_tensors = op.sample_inputs('cpu', dtype, 2, same_size=True) tensors1, tensors2 = list( tensors for tensors in zip(_cuda_tensors, _cpu_tensors)) foreach_op, foreach_op_ = op.method_variant, op.inplace_variant native_op, native_op_ = op.ref, op.ref_inplace try: actual = foreach_op(tensors1, tensors2) except RuntimeError as e: with self.assertRaisesRegex(type(e), re.escape(str(e))): [native_op(t1, t2) for t1, t2 in zip(tensors1, tensors2)] else: expected = [ native_op(t1, t2) for t1, t2 in zip(tensors1, tensors2) ] self.assertEqual(expected, actual) try: foreach_op_(tensors1, tensors2) except RuntimeError as e: with self.assertRaisesRegex(type(e), re.escape(str(e))): [native_op_(t1, t2) for t1, t2 in zip(tensors1, tensors2)] else: self.assertEqual(actual, tensors1) @onlyCUDA @ops(foreach_pointwise_op_db, allowed_dtypes=floating_types()) def test_pointwise_op_tensors_on_different_devices(self, device, dtype, op): # tensors1: ['cuda', 'cpu] # tensors2: ['cuda', 'cpu] # tensors3: ['cuda', 'cpu] _cuda_tensors = op.sample_inputs(device, dtype, 3, same_size=True) _cpu_tensors = op.sample_inputs('cpu', dtype, 3, same_size=True) tensors1, tensors2, tensors3 = list( tensors for tensors in zip(_cuda_tensors, _cpu_tensors)) foreach_op, foreach_op_, native_op = op.method_variant, op.inplace_variant, op.ref actual = foreach_op(tensors1, tensors2, tensors3) expected = [native_op(*_cuda_tensors), native_op(*_cpu_tensors)] self.assertEqual(expected, actual) # note(mkozuki): Limiting dtypes to FP32&FP64, we can safely run inplace ops. foreach_op_(tensors1, tensors2, tensors3) self.assertEqual(expected, tensors1) # note: BFloat16 has the same number of exponent bits as FP32 # so if squared L2 norm overflows in BF16, then it also overflows in FP32. @onlyCUDA @ops(foreach_reduce_op_db, allowed_dtypes=(torch.half, torch.bfloat16)) def test_foreach_l2_large_value_input(self, device, dtype, op): ord, N = 2, 10 max_value = torch.finfo(dtype).max scaler = torch.tensor([max_value]).sqrt().to(device=device, dtype=dtype) inputs = [ t * scaler for t in op.sample_inputs( device, dtype, N, noncontiguous=False, low=1) ], # make sure that the min. of squared L2 norm value per tensor is greater than the max value of `dtype`. self.assertTrue(scaler * scaler * N > max_value) fn, ref_fn, *_ = self._get_funcs(op, 3) actual = fn(inputs, is_cuda=True, is_fastpath=True, ord=ord) expect = ref_fn(inputs, ord=ord) if dtype == torch.float16: # making sure the reference L2 norm values are in the range of FP16. self.assertFalse(any(torch.isinf(e) for e in expect)) else: self.assertTrue(all(torch.isinf(e) for e in expect)) self.assertEqual(expect, actual, equal_nan=False)
class TestShapeOps(TestCase): # TODO: update to work on CUDA, too @onlyCPU def test_unbind(self, device): x = torch.rand(2, 3, 4, 5) for dim in range(4): res = torch.unbind(x, dim) res2 = x.unbind(dim) self.assertEqual(x.size(dim), len(res)) self.assertEqual(x.size(dim), len(res2)) for i in range(dim): self.assertEqual(x.select(dim, i), res[i]) self.assertEqual(x.select(dim, i), res2[i]) # TODO: update to work on CUDA, too? @onlyCPU def test_tolist(self, device): list0D = [] tensor0D = torch.tensor(list0D) self.assertEqual(tensor0D.tolist(), list0D) table1D = [1., 2., 3.] tensor1D = torch.tensor(table1D) storage = torch.Storage(table1D) self.assertEqual(tensor1D.tolist(), table1D) self.assertEqual(storage.tolist(), table1D) self.assertEqual(tensor1D.tolist(), table1D) self.assertEqual(storage.tolist(), table1D) table2D = [[1, 2], [3, 4]] tensor2D = torch.tensor(table2D) self.assertEqual(tensor2D.tolist(), table2D) tensor3D = torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) tensorNonContig = tensor3D.select(1, 1) self.assertFalse(tensorNonContig.is_contiguous()) self.assertEqual(tensorNonContig.tolist(), [[3, 4], [7, 8]]) @dtypes(torch.int64, torch.float, torch.complex128) def test_movedim_invalid(self, device, dtype): shape = self._rand_shape(4, min_size=5, max_size=10) x = _generate_input(shape, dtype, device, False) for fn in [torch.movedim, torch.moveaxis]: # Invalid `source` and `destination` dimension with self.assertRaisesRegex(IndexError, "Dimension out of range"): fn(x, 5, 0) with self.assertRaisesRegex(IndexError, "Dimension out of range"): fn(x, 0, 5) # Mismatch in size of `source` and `destination` with self.assertRaisesRegex( RuntimeError, "movedim: Invalid source or destination dims:"): fn(x, (1, 0), (0, )) with self.assertRaisesRegex(RuntimeError, "movedim: repeated dim in `source`"): fn(x, (0, 0), (0, 1)) with self.assertRaisesRegex(RuntimeError, "movedim: repeated dim in `source`"): fn(x, (0, 1, 0), (0, 1, 2)) with self.assertRaisesRegex( RuntimeError, "movedim: repeated dim in `destination`"): fn(x, (0, 1), (1, 1)) with self.assertRaisesRegex( RuntimeError, "movedim: repeated dim in `destination`"): fn(x, (0, 1, 2), (1, 0, 1)) @dtypes(torch.int64, torch.float, torch.complex128) def test_movedim(self, device, dtype): for fn in [torch.moveaxis, torch.movedim]: for nd in range(5): shape = self._rand_shape(nd, min_size=5, max_size=10) x = _generate_input(shape, dtype, device, with_extremal=False) for random_negative in [True, False]: for src_dim, dst_dim in permutations(range(nd), r=2): random_prob = random.random() if random_negative and random_prob > 0.66: src_dim = src_dim - nd elif random_negative and random_prob > 0.33: dst_dim = dst_dim - nd elif random_negative: src_dim = src_dim - nd dst_dim = dst_dim - nd # Integer `source` and `destination` torch_fn = partial(fn, source=src_dim, destination=dst_dim) np_fn = partial(np.moveaxis, source=src_dim, destination=dst_dim) self.compare_with_numpy(torch_fn, np_fn, x, device=None, dtype=None) if nd == 0: continue def make_index_negative(sequence, idx): sequence = list(sequence) sequence[random_idx] = sequence[random_idx] - nd return tuple(src_sequence) for src_sequence in permutations(range(nd), r=random.randint(1, nd)): # Sequence `source` and `destination` dst_sequence = tuple( random.sample(range(nd), len(src_sequence))) # Randomly change a dim to a negative dim representation of itself. random_prob = random.random() if random_negative and random_prob > 0.66: random_idx = random.randint( 0, len(src_sequence) - 1) src_sequence = make_index_negative( src_sequence, random_idx) elif random_negative and random_prob > 0.33: random_idx = random.randint( 0, len(src_sequence) - 1) dst_sequence = make_index_negative( dst_sequence, random_idx) elif random_negative: random_idx = random.randint( 0, len(src_sequence) - 1) dst_sequence = make_index_negative( dst_sequence, random_idx) random_idx = random.randint( 0, len(src_sequence) - 1) src_sequence = make_index_negative( src_sequence, random_idx) torch_fn = partial(fn, source=src_sequence, destination=dst_sequence) np_fn = partial(np.moveaxis, source=src_sequence, destination=dst_sequence) self.compare_with_numpy(torch_fn, np_fn, x, device=None, dtype=None) # Move dim to same position x = torch.randn(2, 3, 5, 7, 11) torch_fn = partial(fn, source=(0, 1), destination=(0, 1)) np_fn = partial(np.moveaxis, source=(0, 1), destination=(0, 1)) self.compare_with_numpy(torch_fn, np_fn, x, device=None, dtype=None) torch_fn = partial(fn, source=1, destination=1) np_fn = partial(np.moveaxis, source=1, destination=1) self.compare_with_numpy(torch_fn, np_fn, x, device=None, dtype=None) # Empty Sequence torch_fn = partial(fn, source=(), destination=()) np_fn = partial(np.moveaxis, source=(), destination=()) self.compare_with_numpy(torch_fn, np_fn, x, device=None, dtype=None) @dtypes(torch.float, torch.bool) def test_diag(self, device, dtype): if dtype is torch.bool: x = torch.rand(100, 100, device=device) >= 0.5 else: x = torch.rand(100, 100, dtype=dtype, device=device) res1 = torch.diag(x) res2 = torch.tensor((), dtype=dtype, device=device) torch.diag(x, out=res2) self.assertEqual(res1, res2) def test_diagonal(self, device): x = torch.randn((100, 100), device=device) result = torch.diagonal(x) expected = torch.diag(x) self.assertEqual(result, expected) x = torch.randn((100, 100), device=device) result = torch.diagonal(x, 17) expected = torch.diag(x, 17) self.assertEqual(result, expected) @onlyCPU @dtypes(torch.float) def test_diagonal_multidim(self, device, dtype): x = torch.randn(10, 11, 12, 13, dtype=dtype, device=device) xn = x.numpy() for args in [(2, 2, 3), (2, ), (-2, 1, 2), (0, -2, -1)]: result = torch.diagonal(x, *args) expected = xn.diagonal(*args) self.assertEqual(expected.shape, result.shape) self.assertEqual(expected, result) # test non-continguous xp = x.permute(1, 2, 3, 0) result = torch.diagonal(xp, 0, -2, -1) expected = xp.numpy().diagonal(0, -2, -1) self.assertEqual(expected.shape, result.shape) self.assertEqual(expected, result) @onlyNativeDeviceTypes @dtypes(*all_types()) @dtypesIfCUDA(*all_types_and(torch.half)) def test_trace(self, device, dtype): def test(shape): tensor = make_tensor(shape, dtype=dtype, device=device, low=-9, high=9) expected_dtype = tensor.sum().dtype expected_dtype = torch_to_numpy_dtype_dict[expected_dtype] result = np.trace(tensor.cpu().numpy(), dtype=expected_dtype) expected = torch.tensor(result, device=device) self.assertEqual(tensor.trace(), expected) shapes = ( [10, 1], [1, 10], [100, 100], [20, 100], [100, 20], ) for shape in shapes: test(shape) def generate_clamp_baseline(self, device, dtype, *, min_vals, max_vals, with_nans): """ Creates a random tensor for a given device and dtype, and computes the expected clamped values given the min_vals and/or max_vals. If with_nans is provided, then some values are randomly set to nan. """ X = torch.rand(100, device=device).mul(50).add(-25) # uniform in [-25, 25] X = X.to(dtype) if with_nans: mask = torch.randint(0, 2, X.shape, dtype=torch.bool, device=device) X[mask] = nan if isinstance(min_vals, torch.Tensor): min_vals = min_vals.cpu().numpy() if isinstance(max_vals, torch.Tensor): max_vals = max_vals.cpu().numpy() # Use NumPy implementation as reference X_clamped = torch.tensor(np.clip(X.cpu().numpy(), a_min=min_vals, a_max=max_vals), device=device) return X, X_clamped # Tests clamp and its alias, clip @dtypes(torch.int64, torch.float32) def test_clamp(self, device, dtype): op_list = (torch.clamp, torch.Tensor.clamp, torch.Tensor.clamp_, torch.clip, torch.Tensor.clip, torch.Tensor.clip_) # min/max argument product args = product((-10, None), (10, None)) for op in op_list: for min_val, max_val in args: if min_val is None and max_val is None: continue X, Y_expected = self.generate_clamp_baseline(device, dtype, min_vals=min_val, max_vals=max_val, with_nans=False) # Test op X1 = X.clone() # So that the in-place ops do not change X Y_actual = op(X1, min_val, max_val) self.assertEqual(Y_expected, Y_actual) # Test op-out behavior (out does not exist for method versions) if op in (torch.clamp, torch.clip): Y_out = torch.empty_like(X) op(X, min=min_val, max=max_val, out=Y_out) self.assertEqual(Y_expected, Y_out) def test_clamp_propagates_nans(self, device): op_list = (torch.clamp, torch.Tensor.clamp, torch.Tensor.clamp_, torch.clip, torch.Tensor.clip, torch.Tensor.clip_) # min/max argument product args = product((-10, None), (10, None)) for op in op_list: for min_val, max_val in args: if min_val is None and max_val is None: continue X, Y_expected = self.generate_clamp_baseline(device, torch.float, min_vals=min_val, max_vals=max_val, with_nans=True) Y_expected = torch.isnan(Y_expected) # Test op X1 = X.clone() # So that the in-place ops do not change X Y_actual = op(X1, min_val, max_val) self.assertEqual(Y_expected, torch.isnan(Y_actual)) # Test op-out behavior (out does not exist for method versions) if op in (torch.clamp, torch.clip): Y_out = torch.empty_like(X) op(X, min_val, max_val, out=Y_out) self.assertEqual(Y_expected, torch.isnan(Y_out)) def test_clamp_raises_arg_errors(self, device): X = torch.randn(100, dtype=torch.float, device=device) error_msg = 'At least one of \'min\' or \'max\' must not be None' with self.assertRaisesRegex(RuntimeError, error_msg): X.clamp() with self.assertRaisesRegex(RuntimeError, error_msg): X.clamp_() with self.assertRaisesRegex(RuntimeError, error_msg): torch.clamp(X) @dtypes(*all_types_and_complex_and(torch.half, torch.bool, torch.bfloat16)) def test_flip(self, device, dtype): make_from_data = partial(torch.tensor, device=device, dtype=dtype) make_from_size = partial(make_tensor, device=device, dtype=dtype) def test_flip_impl(input_t, dims, output_t): def all_t(): yield input_t, output_t if dtype is torch.float: # We generate quantized versions as well for qdtype in (torch.quint8, torch.qint8, torch.qint32): qinput_t = torch.quantize_per_tensor( input_t, 0.1, 5, qdtype) qoutput_t = torch.quantize_per_tensor( output_t, 0.1, 5, qdtype) yield qinput_t, qoutput_t for in_t, out_t in all_t(): self.assertEqual(in_t.flip(dims), out_t) n = in_t.ndim if not isinstance(dims, tuple): # Wrap dim self.assertEqual(in_t.flip(-n + dims), out_t) else: # Permute dimensions for p_dims in permutations(dims): self.assertEqual(in_t.flip(p_dims), out_t) if len(p_dims) > 0: # Wrap 1st dim self.assertEqual( in_t.flip((-n + p_dims[0], ) + p_dims[1:]), out_t) def gen_data(): # Basic tests data = make_from_data([1, 2, 3, 4, 5, 6, 7, 8]).view(2, 2, 2) nonctg = make_from_size((2, 2, 2), noncontiguous=True).copy_(data) dims_result = ((0, make_from_data([5, 6, 7, 8, 1, 2, 3, 4]).view(2, 2, 2)), (1, make_from_data([3, 4, 1, 2, 7, 8, 5, 6]).view(2, 2, 2)), (2, make_from_data([2, 1, 4, 3, 6, 5, 8, 7]).view(2, 2, 2)), ((0, 1), make_from_data([7, 8, 5, 6, 3, 4, 1, 2]).view(2, 2, 2)), ((0, 1, 2), make_from_data([8, 7, 6, 5, 4, 3, 2, 1]).view(2, 2, 2))) for in_tensor, (dims, out_tensor) in product((data, nonctg), dims_result): yield in_tensor, dims, out_tensor # Expanded in_t = make_from_data([1, 2, 3]).view(3, 1).expand(3, 2) dims = 0 out_t = make_from_data([3, 3, 2, 2, 1, 1]).view(3, 2) yield in_t, dims, out_t # Noop on expanded dimension yield in_t, 1, in_t # Transposed in_t = make_from_data([1, 2, 3, 4, 5, 6, 7, 8]).view(2, 2, 2).transpose(0, 1) dims = (0, 1, 2) out_t = make_from_data([8, 7, 4, 3, 6, 5, 2, 1]).view(2, 2, 2) yield in_t, dims, out_t # Rectangular case in_t = make_from_data([1, 2, 3, 4, 5, 6]).view(2, 3) dims = 0 out_t = make_from_data([[4, 5, 6], [1, 2, 3]]) yield in_t, dims, out_t dims = 1 out_t = make_from_data([[3, 2, 1], [6, 5, 4]]) yield in_t, dims, out_t # Noops (edge cases) # Size 0 in_t = make_from_data(()) yield in_t, 0, in_t yield in_t, (), in_t # dims = () in_t = make_from_size((3, 2, 1)) yield in_t, (), in_t # Zero elements, non-zero size in_t = make_from_size((3, 0, 2)) for i in range(in_t.ndim): yield in_t, i, in_t # Size 1 in_t = make_from_size(()) yield in_t, 0, in_t in_t = make_from_size((1, )) yield in_t, 0, in_t for in_tensor, dims, out_tensor in gen_data(): test_flip_impl(in_tensor, dims, out_tensor) # test for shape size = [2, 3, 4] data = make_from_size(size) possible_dims = range(len(size)) test_dims = chain(combinations(possible_dims, 1), combinations(possible_dims, 2)) for dims in test_dims: self.assertEqual(size, list(data.flip(dims).size())) @dtypes(*all_types_and_complex_and(torch.half, torch.bool, torch.bfloat16)) def test_flip_errors(self, device, dtype): make_arg = partial(make_tensor, dtype=dtype, device=device) data = make_arg((2, 2, 2)) # not allow flip on the same dim more than once self.assertRaises(RuntimeError, lambda: data.flip(0, 1, 1)) # not allow empty list as input self.assertRaises(TypeError, lambda: data.flip()) # not allow dim > max dim self.assertRaises(IndexError, lambda: data.flip(0, 1, 2, 3)) self.assertRaises(IndexError, lambda: data.flip(3)) def _rand_shape(self, dim, min_size, max_size): return tuple(torch.randint(min_size, max_size + 1, (dim, ))) @dtypes(*all_types_and_complex_and(torch.half, torch.bool, torch.bfloat16)) def test_flip_numpy(self, device, dtype): make_arg = partial(make_tensor, dtype=dtype, device=device) for ndim in [3, 4]: shape = self._rand_shape(ndim, 5, 10) data = make_arg(shape) # Axis to sample for given shape. for i in range(1, ndim + 1): # Check all combinations of `i` axis. for flip_dim in combinations(range(ndim), i): torch_fn = partial(torch.flip, dims=flip_dim) np_fn = partial(np.flip, axis=flip_dim) self.compare_with_numpy(torch_fn, np_fn, data) @onlyCUDA # CPU is too slow @largeTensorTest('17GB' ) # 4 tensors of 4GB (in, out) x (torch, numpy) + 1GB @largeTensorTest( "81GB", "cpu") # even for CUDA test, sufficient system memory is required def test_flip_large_tensor(self, device): t_in = torch.empty(2**32 + 1, dtype=torch.uint8).random_() torch_fn = partial(torch.flip, dims=(0, )) np_fn = partial(np.flip, axis=0) self.compare_with_numpy(torch_fn, np_fn, t_in) del t_in def _test_fliplr_flipud(self, torch_fn, np_fn, min_dim, max_dim, device, dtype): for dim in range(min_dim, max_dim + 1): shape = self._rand_shape(dim, 5, 10) # Randomly scale the input if dtype.is_floating_point or dtype.is_complex: data = torch.randn(*shape, device=device, dtype=dtype) else: data = torch.randint(0, 10, shape, device=device, dtype=dtype) self.compare_with_numpy(torch_fn, np_fn, data) @dtypes(torch.int64, torch.double, torch.cdouble) def test_fliplr(self, device, dtype): self._test_fliplr_flipud(torch.fliplr, np.fliplr, 2, 4, device, dtype) @dtypes(torch.int64, torch.double, torch.cdouble) def test_fliplr_invalid(self, device, dtype): x = torch.randn(42).to(dtype) with self.assertRaisesRegex(RuntimeError, "Input must be >= 2-d."): torch.fliplr(x) with self.assertRaisesRegex(RuntimeError, "Input must be >= 2-d."): torch.fliplr(torch.tensor(42, device=device, dtype=dtype)) @dtypes(torch.int64, torch.double, torch.cdouble) def test_flipud(self, device, dtype): self._test_fliplr_flipud(torch.flipud, np.flipud, 1, 4, device, dtype) @dtypes(torch.int64, torch.double, torch.cdouble) def test_flipud_invalid(self, device, dtype): with self.assertRaisesRegex(RuntimeError, "Input must be >= 1-d."): torch.flipud(torch.tensor(42, device=device, dtype=dtype)) def test_rot90(self, device): data = torch.arange(1, 5, device=device).view(2, 2) self.assertEqual( torch.tensor([1, 2, 3, 4]).view(2, 2), data.rot90(0, [0, 1])) self.assertEqual( torch.tensor([2, 4, 1, 3]).view(2, 2), data.rot90(1, [0, 1])) self.assertEqual( torch.tensor([4, 3, 2, 1]).view(2, 2), data.rot90(2, [0, 1])) self.assertEqual( torch.tensor([3, 1, 4, 2]).view(2, 2), data.rot90(3, [0, 1])) # test for default args k=1, dims=[0, 1] self.assertEqual(data.rot90(), data.rot90(1, [0, 1])) # test for reversed order of dims self.assertEqual(data.rot90(3, [0, 1]), data.rot90(1, [1, 0])) # test for modulo of k self.assertEqual(data.rot90(5, [0, 1]), data.rot90(1, [0, 1])) self.assertEqual(data.rot90(3, [0, 1]), data.rot90(-1, [0, 1])) self.assertEqual(data.rot90(-5, [0, 1]), data.rot90(-1, [0, 1])) # test for dims out-of-range error self.assertRaises(RuntimeError, lambda: data.rot90(1, [0, -3])) self.assertRaises(RuntimeError, lambda: data.rot90(1, [0, 2])) # test tensor with more than 2D data = torch.arange(1, 9, device=device).view(2, 2, 2) self.assertEqual( torch.tensor([2, 4, 1, 3, 6, 8, 5, 7]).view(2, 2, 2), data.rot90(1, [1, 2])) self.assertEqual(data.rot90(1, [1, -1]), data.rot90(1, [1, 2])) # test for errors self.assertRaises(RuntimeError, lambda: data.rot90(1, [0, 3])) self.assertRaises(RuntimeError, lambda: data.rot90(1, [1, 1])) self.assertRaises(RuntimeError, lambda: data.rot90(1, [0, 1, 2])) self.assertRaises(RuntimeError, lambda: data.rot90(1, [0])) @dtypes(torch.cfloat, torch.cdouble) def test_complex_rot90(self, device, dtype): shape = self._rand_shape(random.randint(2, 4), 5, 10) for rot_times in range(4): data = torch.randn(*shape, device=device, dtype=dtype) torch_fn = partial(torch.rot90, k=rot_times, dims=[0, 1]) np_fn = partial(np.rot90, k=rot_times, axes=[0, 1]) self.compare_with_numpy(torch_fn, np_fn, data) # TODO: update once warning flag is available to always trigger ONCE warnings # Ensures nonzero does not throw a warning, even when the as_tuple argument # is not provided def test_nonzero_no_warning(self, device): t = torch.randn((2, 2), device=device) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") torch.nonzero(t) t.nonzero() self.assertEqual(len(w), 0) @dtypes(*all_types_and(torch.half, torch.bool, torch.bfloat16)) def test_nonzero(self, device, dtype): shapes = [ torch.Size((12, )), torch.Size((12, 1)), torch.Size((1, 12)), torch.Size((6, 2)), torch.Size((3, 2, 2)), torch.Size((5, 5, 5)), ] def gen_nontrivial_input(shape, dtype, device): if dtype != torch.bfloat16: return torch.randint(2, shape, device=device, dtype=dtype) else: # windows does not work for bfloat16 randing return torch.randint(2, shape, device=device, dtype=torch.float).to(dtype) for shape in shapes: tensor = gen_nontrivial_input(shape, dtype, device) dst1 = torch.nonzero(tensor, as_tuple=False) dst2 = tensor.nonzero(as_tuple=False) dst3 = torch.empty([], dtype=torch.long, device=device) torch.nonzero(tensor, out=dst3) if self.device_type != 'xla': # xla does not raise runtime error self.assertRaisesRegex( RuntimeError, "scalar type Long", lambda: torch.nonzero( tensor, out=torch.empty([], dtype=torch.float, device=device))) if self.device_type == 'cuda': self.assertRaisesRegex( RuntimeError, "on the same device", lambda: torch.nonzero( tensor, out=torch.empty([], dtype=torch.long))) np_array = tensor.cpu().numpy( ) if dtype != torch.bfloat16 else tensor.float().cpu().numpy() np_result = torch.from_numpy(np.stack(np_array.nonzero())).t() self.assertEqual(dst1.cpu(), np_result, atol=0, rtol=0) self.assertEqual(dst2.cpu(), np_result, atol=0, rtol=0) self.assertEqual(dst3.cpu(), np_result, atol=0, rtol=0) tup1 = torch.nonzero(tensor, as_tuple=True) tup2 = tensor.nonzero(as_tuple=True) tup1 = torch.stack(tup1).t().cpu() tup2 = torch.stack(tup2).t().cpu() self.assertEqual(tup1, np_result, atol=0, rtol=0) self.assertEqual(tup2, np_result, atol=0, rtol=0) def test_nonzero_astuple_out(self, device): t = torch.randn((3, 3, 3), device=device) out = torch.empty_like(t, dtype=torch.long) with self.assertRaises(RuntimeError): torch.nonzero(t, as_tuple=True, out=out) self.assertEqual(torch.nonzero(t, as_tuple=False, out=out), torch.nonzero(t, out=out)) # Verifies that JIT script cannot handle the as_tuple kwarg # See Issue https://github.com/pytorch/pytorch/issues/45499. def _foo(t): tuple_result = torch.nonzero(t, as_tuple=True) nontuple_result = torch.nonzero(t, as_tuple=False) out = torch.empty_like(nontuple_result) torch.nonzero(t, as_tuple=False, out=out) return tuple_result, nontuple_result, out with self.assertRaises(RuntimeError): scripted_foo = torch.jit.script(_foo) # Verifies that JIT tracing works fine traced_foo = torch.jit.trace(_foo, t) traced_tuple, traced_nontuple, traced_out = traced_foo(t) expected_tuple = torch.nonzero(t, as_tuple=True) expected_nontuple = torch.nonzero(t) self.assertEqual(traced_tuple, expected_tuple) self.assertEqual(traced_nontuple, expected_nontuple) self.assertEqual(traced_out, expected_nontuple) @onlyNativeDeviceTypes def test_nonzero_discontiguous(self, device): shape = (4, 4) tensor = torch.randint(2, shape, device=device) tensor_nc = torch.empty(shape[0], shape[1] * 2, device=device)[:, ::2].copy_(tensor) dst1 = tensor.nonzero(as_tuple=False) dst2 = tensor_nc.nonzero(as_tuple=False) self.assertEqual(dst1, dst2, atol=0, rtol=0) dst3 = torch.empty_like(dst1) data_ptr = dst3.data_ptr() # expect dst3 storage to be reused torch.nonzero(tensor, out=dst3) self.assertEqual(data_ptr, dst3.data_ptr()) self.assertEqual(dst1, dst3, atol=0, rtol=0) # discontiguous out dst4 = torch.empty(dst1.size(0), dst1.size(1) * 2, dtype=torch.long, device=device)[:, ::2] data_ptr = dst4.data_ptr() strides = dst4.stride() torch.nonzero(tensor, out=dst4) self.assertEqual(data_ptr, dst4.data_ptr()) self.assertEqual(dst1, dst4, atol=0, rtol=0) self.assertEqual(strides, dst4.stride()) def test_nonzero_non_diff(self, device): x = torch.randn(10, requires_grad=True) nz = x.nonzero() self.assertFalse(nz.requires_grad)
class TestPythonJiterator(TestCase): @skipCUDAIfRocm @parametrize("shape_strides", [ (([3, 3], [3, 1]), ([3, 3], [3, 1])), # contiguous ]) @dtypes(*product(all_types_and_complex_and(torch.half, torch.bfloat16), all_types_and_complex_and(torch.half, torch.bfloat16))) def test_all_dtype_contiguous(self, device, dtypes, shape_strides): a_buffer = torch.rand(9, device=device).mul(10).type(dtypes[0]) b_buffer = torch.rand(9, device=device).mul(10).type(dtypes[1]) a = a_buffer.as_strided(*shape_strides[0]) b = b_buffer.as_strided(*shape_strides[1]) expected = ref_fn(a, b) result = jitted_fn(a, b) self.assertEqual(expected, result) @skipCUDAIfRocm # See https://github.com/pytorch/pytorch/pull/76394#issuecomment-1118018287 for details @skipCUDAIf(_get_torch_cuda_version() < (11, 6), "On cuda 11.3, nvrtcCompileProgram is taking too long to " "compile jiterator generated kernels for non-contiguous input that requires dynamic-casting.") @parametrize("shape_strides", [ (([3, 3], [1, 3]), ([3, 1], [1, 3])), # non-contiguous ]) @dtypes(*product(all_types_and_complex_and(torch.half, torch.bfloat16), all_types_and_complex_and(torch.half, torch.bfloat16))) def test_all_dtype_noncontiguous(self, device, dtypes, shape_strides): a_buffer = torch.rand(9, device=device).mul(10).type(dtypes[0]) b_buffer = torch.rand(9, device=device).mul(10).type(dtypes[1]) a = a_buffer.as_strided(*shape_strides[0]) b = b_buffer.as_strided(*shape_strides[1]) expected = ref_fn(a, b) result = jitted_fn(a, b) self.assertEqual(expected, result) @skipCUDAIfRocm @dtypes(torch.float, torch.double, torch.float16, torch.bfloat16) @parametrize("alpha", [-1, 2.0, None]) @parametrize("beta", [3, -4.2, None]) @toleranceOverride({torch.float16 : tol(atol=1e-2, rtol=1e-3)}) def test_extra_args(self, device, dtype, alpha, beta): a = torch.rand(3, device=device).mul(10).type(dtype) b = torch.rand(3, device=device).mul(10).type(dtype) extra_args = {} if alpha is not None: extra_args["alpha"] = alpha if beta is not None: extra_args["beta"] = beta expected = ref_fn(a, b, **extra_args) result = jitted_fn(a, b, **extra_args) self.assertEqual(expected, result) @skipCUDAIfRocm @parametrize("is_train", [True, False]) def test_bool_extra_args(self, device, is_train): code_string = "template <typename T> T conditional(T x, T mask, bool is_train) { return is_train ? x * mask : x; }" jitted_fn = create_jit_fn(code_string, is_train=False) def ref_fn(x, mask, is_train): return x * mask if is_train else x a = torch.rand(3, device=device) b = torch.rand(3, device=device) expected = ref_fn(a, b, is_train=is_train) result = jitted_fn(a, b, is_train=is_train) self.assertEqual(expected, result) @skipCUDAIfRocm def test_multiple_functors(self, device): code_string = ''' template <typename T> T fn(T x, T mask) { return x * mask; } template <typename T> T main_fn(T x, T mask, T y) { return fn(x, mask) + y; } ''' jitted_fn = create_jit_fn(code_string) def ref_fn(x, mask, y): return x * mask + y a = torch.rand(3, device=device) b = torch.rand(3, device=device) c = torch.rand(3, device=device) expected = ref_fn(a, b, c) result = jitted_fn(a, b, c) self.assertEqual(expected, result) @skipCUDAIfRocm @parametrize("num_inputs", [1, 5, 8]) def test_various_num_inputs(self, num_inputs): inputs = [] for i in range(num_inputs): inputs.append(torch.rand(3, device='cuda').mul(10)) input_string = ",".join([f"T i{i}" for i in range(num_inputs)]) function_body = "+".join([f"i{i}" for i in range(num_inputs)]) code_string = f"template <typename T> T my_kernel({input_string}) {{ return {function_body}; }}" jitted_fn = create_jit_fn(code_string) def ref_fn(*inputs): return torch.sum(torch.stack(inputs), dim=0) expected = ref_fn(*inputs) result = jitted_fn(*inputs) self.assertEqual(expected, result) @skipCUDAIfRocm @parametrize("num_outputs", [1, 4, 8]) def test_various_num_outputs(self, num_outputs): input = torch.rand(3, device='cuda') output_string = ", ".join([f"T& out{i}" for i in range(num_outputs)]) function_body = "" for i in range(num_outputs): function_body += f"out{i} = input + {i};\n" code_string = f"template <typename T> T my_kernel(T input, {output_string}) {{ {function_body} }}" jitted_fn = create_multi_output_jit_fn(code_string, num_outputs) def ref_fn(input): outputs = [] for i in range(num_outputs): outputs.append(input + i) if num_outputs == 1: return outputs[0] return tuple(outputs) expected = ref_fn(input) result = jitted_fn(input) for i in range(num_outputs): self.assertEqual(expected[i], result[i]) @skipCUDAIfRocm @parametrize("code_string", [ "template <typename T> T my _kernel(T x) { return x; }", "template <typename T> Tmy_kernel(T x) { return x; }", ]) def test_invalid_function_name(self, code_string): with self.assertRaises(Exception): jitted_fn = create_jit_fn(code_string)
def test_dtypes(self, device, op): # dtypes to try to backward in allowed_backward_dtypes = floating_and_complex_types_and( torch.bfloat16, torch.float16) # lists for (un)supported dtypes supported_dtypes = [] unsupported_dtypes = [] supported_backward_dtypes = [] unsupported_backward_dtypes = [] def unsupported(dtype): unsupported_dtypes.append(dtype) if dtype in allowed_backward_dtypes: unsupported_backward_dtypes.append(dtype) for dtype in all_types_and_complex_and(torch.half, torch.bfloat16, torch.bool): # tries to acquire samples - failure indicates lack of support requires_grad = (dtype in allowed_backward_dtypes and op.supports_autograd) try: samples = list( op.sample_inputs(device, dtype, requires_grad=requires_grad)) except Exception as e: unsupported(dtype) continue # Counts number of successful backward attempts # NOTE: This exists as a kludge because this only understands how to # request a gradient if the output is a tensor or a sequence with # a tensor as its first element. num_backward_successes = 0 for sample in samples: # tries to call operator with the sample - failure indicates # lack of support try: result = op(sample.input, *sample.args, **sample.kwargs) except Exception as e: # NOTE: some ops will fail in forward if their inputs # require grad but they don't support computing the gradient # in that type! This is a bug in the op! unsupported(dtype) # Short-circuits testing this dtype -- it doesn't work if dtype in unsupported_dtypes: break # Short-circuits if the dtype isn't a backward dtype or # it's already identified as not supported if dtype not in allowed_backward_dtypes or dtype in unsupported_backward_dtypes: continue # Checks for backward support in the same dtype try: result = sample.output_process_fn_grad(result) if isinstance(result, torch.Tensor): backward_tensor = result elif isinstance(result, Sequence) and isinstance( result[0], torch.Tensor): backward_tensor = result[0] else: continue # Note: this grad may not have the same dtype as dtype # For functions like complex (float -> complex) or abs # (complex -> float) the grad tensor will have a # different dtype than the input. # For simplicity, this is still modeled as these ops # supporting grad in the input dtype. grad = torch.randn_like(backward_tensor) backward_tensor.backward(grad) num_backward_successes += 1 except Exception as e: unsupported_backward_dtypes.append(dtype) if dtype not in unsupported_dtypes: supported_dtypes.append(dtype) if num_backward_successes > 0 and dtype not in unsupported_backward_dtypes: supported_backward_dtypes.append(dtype) # Checks that dtypes are listed correctly and generates an informative # error message device_type = torch.device(device).type claimed_supported = set(op.supported_dtypes(device_type)) supported_dtypes = set(supported_dtypes) supported_but_unclaimed = supported_dtypes - claimed_supported claimed_but_unsupported = claimed_supported - supported_dtypes msg = """The supported dtypes for {0} on {1} according to its OpInfo are {2}, but the detected supported dtypes are {3}. """.format(op.name, device_type, claimed_supported, supported_dtypes) if len(supported_but_unclaimed) > 0: msg += "The following dtypes should be added to the OpInfo: {0}. ".format( supported_but_unclaimed) if len(claimed_but_unsupported) > 0: msg += "The following dtypes should be removed from the OpInfo: {0}.".format( claimed_but_unsupported) self.assertEqual(supported_dtypes, claimed_supported, msg=msg) # Checks that backward dtypes are listed correctly and generates an # informative error message # NOTE: this code is nearly identical to the check + msg generation claimed_backward_supported = set( op.supported_backward_dtypes(device_type)) supported_backward_dtypes = set(supported_backward_dtypes) supported_but_unclaimed = supported_backward_dtypes - claimed_backward_supported claimed_but_unsupported = claimed_backward_supported - supported_backward_dtypes msg = """The supported backward dtypes for {0} on {1} according to its OpInfo are {2}, but the detected supported backward dtypes are {3}. """.format(op.name, device_type, claimed_backward_supported, supported_backward_dtypes) if len(supported_but_unclaimed) > 0: msg += "The following backward dtypes should be added to the OpInfo: {0}. ".format( supported_but_unclaimed) if len(claimed_but_unsupported) > 0: msg += "The following backward dtypes should be removed from the OpInfo: {0}.".format( claimed_but_unsupported) self.assertEqual(supported_backward_dtypes, claimed_backward_supported, msg=msg)
class TestNumPyInterop(TestCase): # Note: the warning this tests for only appears once per program, so # other instances of this warning should be addressed to avoid # the tests depending on the order in which they're run. @onlyCPU def test_numpy_non_writeable(self, device): arr = np.zeros(5) arr.flags['WRITEABLE'] = False self.assertWarns(UserWarning, lambda: torch.from_numpy(arr)) @onlyCPU def test_numpy_unresizable(self, device) -> None: x = np.zeros((2, 2)) y = torch.from_numpy(x) with self.assertRaises(ValueError): x.resize((5, 5)) z = torch.randn(5, 5) w = z.numpy() with self.assertRaises(RuntimeError): z.resize_(10, 10) with self.assertRaises(ValueError): w.resize((10, 10)) @onlyCPU def test_to_numpy(self, device) -> None: def get_castable_tensor(shape, dtype): if dtype.is_floating_point: dtype_info = torch.finfo(dtype) # can't directly use min and max, because for double, max - min # is greater than double range and sampling always gives inf. low = max(dtype_info.min, -1e10) high = min(dtype_info.max, 1e10) t = torch.empty(shape, dtype=torch.float64).uniform_(low, high) else: # can't directly use min and max, because for int64_t, max - min # is greater than int64_t range and triggers UB. low = max(torch.iinfo(dtype).min, int(-1e10)) high = min(torch.iinfo(dtype).max, int(1e10)) t = torch.empty(shape, dtype=torch.int64).random_(low, high) return t.to(dtype) dtypes = [ torch.uint8, torch.int8, torch.short, torch.int, torch.half, torch.float, torch.double, torch.long, ] for dtp in dtypes: # 1D sz = 10 x = get_castable_tensor(sz, dtp) y = x.numpy() for i in range(sz): self.assertEqual(x[i], y[i]) # 1D > 0 storage offset xm = get_castable_tensor(sz * 2, dtp) x = xm.narrow(0, sz - 1, sz) self.assertTrue(x.storage_offset() > 0) y = x.numpy() for i in range(sz): self.assertEqual(x[i], y[i]) def check2d(x, y): for i in range(sz1): for j in range(sz2): self.assertEqual(x[i][j], y[i][j]) # empty x = torch.tensor([]).to(dtp) y = x.numpy() self.assertEqual(y.size, 0) # contiguous 2D sz1 = 3 sz2 = 5 x = get_castable_tensor((sz1, sz2), dtp) y = x.numpy() check2d(x, y) self.assertTrue(y.flags['C_CONTIGUOUS']) # with storage offset xm = get_castable_tensor((sz1 * 2, sz2), dtp) x = xm.narrow(0, sz1 - 1, sz1) y = x.numpy() self.assertTrue(x.storage_offset() > 0) check2d(x, y) self.assertTrue(y.flags['C_CONTIGUOUS']) # non-contiguous 2D x = get_castable_tensor((sz2, sz1), dtp).t() y = x.numpy() check2d(x, y) self.assertFalse(y.flags['C_CONTIGUOUS']) # with storage offset xm = get_castable_tensor((sz2 * 2, sz1), dtp) x = xm.narrow(0, sz2 - 1, sz2).t() y = x.numpy() self.assertTrue(x.storage_offset() > 0) check2d(x, y) # non-contiguous 2D with holes xm = get_castable_tensor((sz2 * 2, sz1 * 2), dtp) x = xm.narrow(0, sz2 - 1, sz2).narrow(1, sz1 - 1, sz1).t() y = x.numpy() self.assertTrue(x.storage_offset() > 0) check2d(x, y) if dtp != torch.half: # check writeable x = get_castable_tensor((3, 4), dtp) y = x.numpy() self.assertTrue(y.flags.writeable) y[0][1] = 3 self.assertTrue(x[0][1] == 3) y = x.t().numpy() self.assertTrue(y.flags.writeable) y[0][1] = 3 self.assertTrue(x[0][1] == 3) def test_to_numpy_bool(self, device) -> None: x = torch.tensor([True, False], dtype=torch.bool) self.assertEqual(x.dtype, torch.bool) y = x.numpy() self.assertEqual(y.dtype, np.bool_) for i in range(len(x)): self.assertEqual(x[i], y[i]) x = torch.tensor([True], dtype=torch.bool) self.assertEqual(x.dtype, torch.bool) y = x.numpy() self.assertEqual(y.dtype, np.bool_) self.assertEqual(x[0], y[0]) def test_from_numpy(self, device) -> None: dtypes = [ np.double, np.float64, np.float16, np.complex64, np.complex128, np.int64, np.int32, np.int16, np.int8, np.uint8, np.longlong, np.bool_, ] complex_dtypes = [ np.complex64, np.complex128, ] for dtype in dtypes: array = np.array([1, 2, 3, 4], dtype=dtype) tensor_from_array = torch.from_numpy(array) # TODO: change to tensor equality check once HalfTensor # implements `==` for i in range(len(array)): self.assertEqual(tensor_from_array[i], array[i]) # ufunc 'remainder' not supported for complex dtypes if dtype not in complex_dtypes: # This is a special test case for Windows # https://github.com/pytorch/pytorch/issues/22615 array2 = array % 2 tensor_from_array2 = torch.from_numpy(array2) for i in range(len(array2)): self.assertEqual(tensor_from_array2[i], array2[i]) # Test unsupported type array = np.array([1, 2, 3, 4], dtype=np.uint16) with self.assertRaises(TypeError): tensor_from_array = torch.from_numpy(array) # check storage offset x = np.linspace(1, 125, 125) x.shape = (5, 5, 5) x = x[1] expected = torch.arange(1, 126, dtype=torch.float64).view(5, 5, 5)[1] self.assertEqual(torch.from_numpy(x), expected) # check noncontiguous x = np.linspace(1, 25, 25) x.shape = (5, 5) expected = torch.arange(1, 26, dtype=torch.float64).view(5, 5).t() self.assertEqual(torch.from_numpy(x.T), expected) # check noncontiguous with holes x = np.linspace(1, 125, 125) x.shape = (5, 5, 5) x = x[:, 1] expected = torch.arange(1, 126, dtype=torch.float64).view(5, 5, 5)[:, 1] self.assertEqual(torch.from_numpy(x), expected) # check zero dimensional x = np.zeros((0, 2)) self.assertEqual(torch.from_numpy(x).shape, (0, 2)) x = np.zeros((2, 0)) self.assertEqual(torch.from_numpy(x).shape, (2, 0)) # check ill-sized strides raise exception x = np.array([3., 5., 8.]) x.strides = (3,) self.assertRaises(ValueError, lambda: torch.from_numpy(x)) @skipMeta def test_from_list_of_ndarray_warning(self, device): warning_msg = r"Creating a tensor from a list of numpy.ndarrays is extremely slow" with self.assertWarnsOnceRegex(UserWarning, warning_msg): torch.tensor([np.array([0]), np.array([1])], device=device) def test_ctor_with_invalid_numpy_array_sequence(self, device): # Invalid list of numpy array with self.assertRaisesRegex(ValueError, "expected sequence of length"): torch.tensor([np.random.random(size=(3, 3)), np.random.random(size=(3, 0))], device=device) # Invalid list of list of numpy array with self.assertRaisesRegex(ValueError, "expected sequence of length"): torch.tensor([[np.random.random(size=(3, 3)), np.random.random(size=(3, 2))]], device=device) with self.assertRaisesRegex(ValueError, "expected sequence of length"): torch.tensor([[np.random.random(size=(3, 3)), np.random.random(size=(3, 3))], [np.random.random(size=(3, 3)), np.random.random(size=(3, 2))]], device=device) # expected shape is `[1, 2, 3]`, hence we try to iterate over 0-D array # leading to type error : not a sequence. with self.assertRaisesRegex(TypeError, "not a sequence"): torch.tensor([[np.random.random(size=(3)), np.random.random()]], device=device) # list of list or numpy array. with self.assertRaisesRegex(ValueError, "expected sequence of length"): torch.tensor([[1, 2, 3], np.random.random(size=(2,)), ], device=device) @onlyCPU def test_ctor_with_numpy_scalar_ctor(self, device) -> None: dtypes = [ np.double, np.float64, np.float16, np.int64, np.int32, np.int16, np.uint8, np.bool_, ] for dtype in dtypes: self.assertEqual(dtype(42), torch.tensor(dtype(42)).item()) @onlyCPU def test_numpy_index(self, device): i = np.array([0, 1, 2], dtype=np.int32) x = torch.randn(5, 5) for idx in i: self.assertFalse(isinstance(idx, int)) self.assertEqual(x[idx], x[int(idx)]) @onlyCPU def test_numpy_array_interface(self, device): types = [ torch.DoubleTensor, torch.FloatTensor, torch.HalfTensor, torch.LongTensor, torch.IntTensor, torch.ShortTensor, torch.ByteTensor, ] dtypes = [ np.float64, np.float32, np.float16, np.int64, np.int32, np.int16, np.uint8, ] for tp, dtype in zip(types, dtypes): # Only concrete class can be given where "Type[number[_64Bit]]" is expected if np.dtype(dtype).kind == 'u': # type: ignore[misc] # .type expects a XxxTensor, which have no type hints on # purpose, so ignore during mypy type checking x = torch.tensor([1, 2, 3, 4]).type(tp) # type: ignore[call-overload] array = np.array([1, 2, 3, 4], dtype=dtype) else: x = torch.tensor([1, -2, 3, -4]).type(tp) # type: ignore[call-overload] array = np.array([1, -2, 3, -4], dtype=dtype) # Test __array__ w/o dtype argument asarray = np.asarray(x) self.assertIsInstance(asarray, np.ndarray) self.assertEqual(asarray.dtype, dtype) for i in range(len(x)): self.assertEqual(asarray[i], x[i]) # Test __array_wrap__, same dtype abs_x = np.abs(x) abs_array = np.abs(array) self.assertIsInstance(abs_x, tp) for i in range(len(x)): self.assertEqual(abs_x[i], abs_array[i]) # Test __array__ with dtype argument for dtype in dtypes: x = torch.IntTensor([1, -2, 3, -4]) asarray = np.asarray(x, dtype=dtype) self.assertEqual(asarray.dtype, dtype) # Only concrete class can be given where "Type[number[_64Bit]]" is expected if np.dtype(dtype).kind == 'u': # type: ignore[misc] wrapped_x = np.array([1, -2, 3, -4], dtype=dtype) for i in range(len(x)): self.assertEqual(asarray[i], wrapped_x[i]) else: for i in range(len(x)): self.assertEqual(asarray[i], x[i]) # Test some math functions with float types float_types = [torch.DoubleTensor, torch.FloatTensor] float_dtypes = [np.float64, np.float32] for tp, dtype in zip(float_types, float_dtypes): x = torch.tensor([1, 2, 3, 4]).type(tp) # type: ignore[call-overload] array = np.array([1, 2, 3, 4], dtype=dtype) for func in ['sin', 'sqrt', 'ceil']: ufunc = getattr(np, func) res_x = ufunc(x) res_array = ufunc(array) self.assertIsInstance(res_x, tp) for i in range(len(x)): self.assertEqual(res_x[i], res_array[i]) # Test functions with boolean return value for tp, dtype in zip(types, dtypes): x = torch.tensor([1, 2, 3, 4]).type(tp) # type: ignore[call-overload] array = np.array([1, 2, 3, 4], dtype=dtype) geq2_x = np.greater_equal(x, 2) geq2_array = np.greater_equal(array, 2).astype('uint8') self.assertIsInstance(geq2_x, torch.ByteTensor) for i in range(len(x)): self.assertEqual(geq2_x[i], geq2_array[i]) @onlyCPU def test_multiplication_numpy_scalar(self, device) -> None: for np_dtype in [np.float32, np.float64, np.int32, np.int64, np.int16, np.uint8]: for t_dtype in [torch.float, torch.double]: # mypy raises an error when np.floatXY(2.0) is called # even though this is valid code np_sc = np_dtype(2.0) # type: ignore[abstract, arg-type] t = torch.ones(2, requires_grad=True, dtype=t_dtype) r1 = t * np_sc self.assertIsInstance(r1, torch.Tensor) self.assertTrue(r1.dtype == t_dtype) self.assertTrue(r1.requires_grad) r2 = np_sc * t self.assertIsInstance(r2, torch.Tensor) self.assertTrue(r2.dtype == t_dtype) self.assertTrue(r2.requires_grad) @onlyCPU def test_parse_numpy_int(self, device): # Only concrete class can be given where "Type[number[_64Bit]]" is expected self.assertRaisesRegex(RuntimeError, "Overflow", lambda: torch.mean(torch.randn(1, 1), np.uint64(-1))) # type: ignore[call-overload] # https://github.com/pytorch/pytorch/issues/29252 for nptype in [np.int16, np.int8, np.uint8, np.int32, np.int64]: scalar = 3 np_arr = np.array([scalar], dtype=nptype) np_val = np_arr[0] # np integral type can be treated as a python int in native functions with # int parameters: self.assertEqual(torch.ones(5).diag(scalar), torch.ones(5).diag(np_val)) self.assertEqual(torch.ones([2, 2, 2, 2]).mean(scalar), torch.ones([2, 2, 2, 2]).mean(np_val)) # numpy integral type parses like a python int in custom python bindings: self.assertEqual(torch.Storage(np_val).size(), scalar) # type: ignore[attr-defined] tensor = torch.tensor([2], dtype=torch.int) tensor[0] = np_val self.assertEqual(tensor[0], np_val) # Original reported issue, np integral type parses to the correct # PyTorch integral type when passed for a `Scalar` parameter in # arithmetic operations: t = torch.from_numpy(np_arr) self.assertEqual((t + np_val).dtype, t.dtype) self.assertEqual((np_val + t).dtype, t.dtype) def test_has_storage_numpy(self, device): for dtype in [np.float32, np.float64, np.int64, np.int32, np.int16, np.uint8]: arr = np.array([1], dtype=dtype) self.assertIsNotNone(torch.tensor(arr, device=device, dtype=torch.float32).storage()) self.assertIsNotNone(torch.tensor(arr, device=device, dtype=torch.double).storage()) self.assertIsNotNone(torch.tensor(arr, device=device, dtype=torch.int).storage()) self.assertIsNotNone(torch.tensor(arr, device=device, dtype=torch.long).storage()) self.assertIsNotNone(torch.tensor(arr, device=device, dtype=torch.uint8).storage()) @dtypes(*all_types_and_complex_and(torch.half, torch.bfloat16, torch.bool)) def test_numpy_scalar_cmp(self, device, dtype): if dtype.is_complex: tensors = (torch.tensor(complex(1, 3), dtype=dtype, device=device), torch.tensor([complex(1, 3), 0, 2j], dtype=dtype, device=device), torch.tensor([[complex(3, 1), 0], [-1j, 5]], dtype=dtype, device=device)) else: tensors = (torch.tensor(3, dtype=dtype, device=device), torch.tensor([1, 0, -3], dtype=dtype, device=device), torch.tensor([[3, 0, -1], [3, 5, 4]], dtype=dtype, device=device)) for tensor in tensors: if dtype == torch.bfloat16: with self.assertRaises(TypeError): np_array = tensor.cpu().numpy() continue np_array = tensor.cpu().numpy() for t, a in product((tensor.flatten()[0], tensor.flatten()[0].item()), (np_array.flatten()[0], np_array.flatten()[0].item())): self.assertEqual(t, a) if dtype == torch.complex64 and torch.is_tensor(t) and type(a) == np.complex64: # TODO: Imaginary part is dropped in this case. Need fix. # https://github.com/pytorch/pytorch/issues/43579 self.assertFalse(t == a) else: self.assertTrue(t == a)
op_db: List[OpInfo] = [ ReductionOpInfo( "_masked.sum", ref=reference_reduction_numpy(np.sum), method_variant=None, identity=0, nan_policy="propagate", supports_out=False, supports_forward_ad=True, supports_fwgrad_bwgrad=True, supports_sparse=True, supports_sparse_csr=True, promotes_int_to_int64=True, dtypes=all_types_and_complex_and(torch.bool, torch.float16, torch.bfloat16), skips=( DecorateInfo( unittest.skip("Failing on some jobs"), "TestReductions", "test_reference_masked", dtypes=(torch.bool, torch.int8, torch.int16, torch.int32), ), DecorateInfo( unittest.expectedFailure, "TestNormalizeOperators", "test_normalize_operator_exhaustive", ), # FIXME: sum reduces all dimensions when dim=[] DecorateInfo(unittest.expectedFailure, "TestReductions", "test_dim_empty"), DecorateInfo(
class TestTorchDlPack(TestCase): exact_dtype = True @skipMeta @onlyNativeDeviceTypes @dtypes(*all_types_and_complex_and(torch.half, torch.bfloat16)) def test_dlpack_capsule_conversion(self, device, dtype): # DLpack does not explicitly support bool (xref dmlc/dlpack#75) x = make_tensor((5,), dtype=dtype, device=device) z = from_dlpack(to_dlpack(x)) self.assertEqual(z, x) @skipMeta @onlyNativeDeviceTypes @dtypes(*all_types_and_complex_and(torch.half, torch.bfloat16)) def test_dlpack_protocol_conversion(self, device, dtype): x = make_tensor((5,), dtype=dtype, device=device) z = from_dlpack(x) self.assertEqual(z, x) @skipMeta @onlyNativeDeviceTypes def test_dlpack_shared_storage(self, device): x = make_tensor((5,), dtype=torch.float64, device=device) z = from_dlpack(to_dlpack(x)) z[0] = z[0] + 20.0 self.assertEqual(z, x) @skipMeta @onlyCUDA @dtypes(*all_types_and_complex_and(torch.half, torch.bfloat16)) def test_dlpack_conversion_with_streams(self, device, dtype): # Create a stream where the tensor will reside stream = torch.cuda.Stream() with torch.cuda.stream(stream): # Do an operation in the actual stream x = make_tensor((5,), dtype=dtype, device=device) + 1 # DLPack protocol helps establish a correct stream order # (hence data dependency) at the exchange boundary. # DLPack manages this synchronization for us, so we don't need to # explicitly wait until x is populated stream = torch.cuda.Stream() with torch.cuda.stream(stream): z = from_dlpack(x) stream.synchronize() self.assertEqual(z, x) @skipMeta @onlyNativeDeviceTypes @dtypes(*all_types_and_complex_and(torch.half, torch.bfloat16)) def test_from_dlpack(self, device, dtype): x = make_tensor((5,), dtype=dtype, device=device) y = torch.from_dlpack(x) self.assertEqual(x, y) @skipMeta @onlyNativeDeviceTypes @dtypes(*all_types_and_complex_and(torch.half, torch.bfloat16)) def test_from_dlpack_noncontinguous(self, device, dtype): x = make_tensor((25,), dtype=dtype, device=device).reshape(5, 5) y1 = x[0] y1_dl = torch.from_dlpack(y1) self.assertEqual(y1, y1_dl) y2 = x[:, 0] y2_dl = torch.from_dlpack(y2) self.assertEqual(y2, y2_dl) y3 = x[1, :] y3_dl = torch.from_dlpack(y3) self.assertEqual(y3, y3_dl) y4 = x[1] y4_dl = torch.from_dlpack(y4) self.assertEqual(y4, y4_dl) y5 = x.t() y5_dl = torch.from_dlpack(y5) self.assertEqual(y5, y5_dl) @skipMeta @onlyCUDA @dtypes(*all_types_and_complex_and(torch.half, torch.bfloat16)) def test_dlpack_conversion_with_diff_streams(self, device, dtype): stream_a = torch.cuda.Stream() stream_b = torch.cuda.Stream() # DLPack protocol helps establish a correct stream order # (hence data dependency) at the exchange boundary. # the `tensor.__dlpack__` method will insert a synchronization event # in the current stream to make sure that it was correctly populated. with torch.cuda.stream(stream_a): x = make_tensor((5,), dtype=dtype, device=device) + 1 z = torch.from_dlpack(x.__dlpack__(stream_b.cuda_stream)) stream_a.synchronize() stream_b.synchronize() self.assertEqual(z, x) @skipMeta @onlyNativeDeviceTypes @dtypes(*all_types_and_complex_and(torch.half, torch.bfloat16)) def test_from_dlpack_dtype(self, device, dtype): x = make_tensor((5,), dtype=dtype, device=device) y = torch.from_dlpack(x) assert x.dtype == y.dtype @skipMeta @onlyCUDA def test_dlpack_default_stream(self, device): class DLPackTensor: def __init__(self, tensor): self.tensor = tensor def __dlpack_device__(self): return self.tensor.__dlpack_device__() def __dlpack__(self, stream=None): if torch.version.hip is None: assert stream == 1 else: assert stream == 0 capsule = self.tensor.__dlpack__(stream) return capsule # CUDA-based tests runs on non-default streams with torch.cuda.stream(torch.cuda.default_stream()): x = DLPackTensor(make_tensor((5,), dtype=torch.float32, device=device)) from_dlpack(x) @skipMeta @onlyNativeDeviceTypes @dtypes(*all_types_and_complex_and(torch.half, torch.bfloat16)) def test_dlpack_tensor_invalid_stream(self, device, dtype): with self.assertRaises(TypeError): x = make_tensor((5,), dtype=dtype, device=device) x.__dlpack__(stream=object()) @skipMeta def test_dlpack_error_on_bool_tensor(self): x = torch.tensor([True], dtype=torch.bool) with self.assertRaises(RuntimeError): to_dlpack(x) # TODO: add interchange tests once NumPy 1.22 (dlpack support) is required @skipMeta def test_dlpack_export_requires_grad(self): x = torch.zeros(10, dtype=torch.float32, requires_grad=True) with self.assertRaisesRegex(RuntimeError, r"require gradient"): x.__dlpack__() @skipMeta def test_dlpack_export_is_conj(self): x = torch.tensor([-1 + 1j, -2 + 2j, 3 - 3j]) y = torch.conj(x) with self.assertRaisesRegex(RuntimeError, r"conjugate bit"): y.__dlpack__() @skipMeta def test_dlpack_export_non_strided(self): x = torch.sparse_coo_tensor([[0]], [1], size=(1,)) y = torch.conj(x) with self.assertRaisesRegex(RuntimeError, r"strided"): y.__dlpack__() @skipMeta def test_dlpack_normalize_strides(self): x = torch.rand(16) y = x[::3][:1] self.assertEqual(y.shape, (1,)) self.assertEqual(y.stride(), (3,)) z = from_dlpack(y) self.assertEqual(z.shape, (1,)) # gh-83069, make sure __dlpack__ normalizes strides self.assertEqual(z.stride(), (1,))
yield SampleInput(mt((9, 10))) yield SampleInput(mt((50, )), kwargs=dict(dim=0)) yield SampleInput(mt((5, 11)), kwargs=dict(dim=(1, ))) yield SampleInput(mt((5, 6)), kwargs=dict(dim=(0, 1))) yield SampleInput(mt((5, 6, 2)), kwargs=dict(dim=(0, 2))) # Operator database op_db: List[OpInfo] = [ SpectralFuncInfo( "fft.fft", aten_name="fft_fft", decomp_aten_name="_fft_c2c", ref=np.fft.fft, ndimensional=SpectralFuncType.OneD, dtypes=all_types_and_complex_and(torch.bool), # rocFFT doesn't support Half/Complex Half Precision FFT # CUDA supports Half/ComplexHalf Precision FFT only on SM53 or later archs dtypesIfCUDA=all_types_and_complex_and( torch.bool, *(() if (TEST_WITH_ROCM or not SM53OrLater) else (torch.half, torch.complex32)), ), # https://github.com/pytorch/pytorch/issues/80411 gradcheck_fast_mode=True, supports_forward_ad=True, supports_fwgrad_bwgrad=True, # See https://github.com/pytorch/pytorch/pull/78358 check_batched_forward_grad=False, ), SpectralFuncInfo(
def test_dtypes(self, device, op): # Check complex32 support only if the op claims. # TODO: Once the complex32 support is better, we should add check for complex32 unconditionally. device_type = torch.device(device).type include_complex32 = ((torch.complex32, ) if op.supports_dtype( torch.complex32, device_type) else ()) # dtypes to try to backward in allowed_backward_dtypes = floating_and_complex_types_and( *((torch.half, torch.bfloat16) + include_complex32)) # lists for (un)supported dtypes supported_dtypes = set() unsupported_dtypes = set() supported_backward_dtypes = set() unsupported_backward_dtypes = set() def unsupported(dtype): unsupported_dtypes.add(dtype) if dtype in allowed_backward_dtypes: unsupported_backward_dtypes.add(dtype) for dtype in all_types_and_complex_and( *((torch.half, torch.bfloat16, torch.bool) + include_complex32)): # tries to acquire samples - failure indicates lack of support requires_grad = (dtype in allowed_backward_dtypes) try: samples = tuple( op.sample_inputs(device, dtype, requires_grad=requires_grad)) except Exception as e: unsupported(dtype) continue for sample in samples: # tries to call operator with the sample - failure indicates # lack of support try: result = op(sample.input, *sample.args, **sample.kwargs) supported_dtypes.add(dtype) except Exception as e: # NOTE: some ops will fail in forward if their inputs # require grad but they don't support computing the gradient # in that type! This is a bug in the op! unsupported(dtype) continue # Checks for backward support in the same dtype try: result = sample.output_process_fn_grad(result) if isinstance(result, torch.Tensor): backward_tensor = result elif isinstance(result, Sequence) and isinstance( result[0], torch.Tensor): backward_tensor = result[0] else: continue # Note: this grad may not have the same dtype as dtype # For functions like complex (float -> complex) or abs # (complex -> float) the grad tensor will have a # different dtype than the input. # For simplicity, this is still modeled as these ops # supporting grad in the input dtype. grad = torch.randn_like(backward_tensor) backward_tensor.backward(grad) supported_backward_dtypes.add(dtype) except Exception as e: unsupported_backward_dtypes.add(dtype) # Checks that dtypes are listed correctly and generates an informative # error message supported_forward = supported_dtypes - unsupported_dtypes partially_supported_forward = supported_dtypes & unsupported_dtypes unsupported_forward = unsupported_dtypes - supported_dtypes supported_backward = supported_backward_dtypes - unsupported_backward_dtypes partially_supported_backward = supported_backward_dtypes & unsupported_backward_dtypes unsupported_backward = unsupported_backward_dtypes - supported_backward_dtypes device_type = torch.device(device).type claimed_forward = set(op.supported_dtypes(device_type)) supported_but_unclaimed_forward = supported_forward - claimed_forward claimed_but_unsupported_forward = claimed_forward & unsupported_forward claimed_backward = set(op.supported_backward_dtypes(device_type)) supported_but_unclaimed_backward = supported_backward - claimed_backward claimed_but_unsupported_backward = claimed_backward & unsupported_backward # Partially supporting a dtype is not an error, but we print a warning if (len(partially_supported_forward) + len(partially_supported_backward)) > 0: msg = "Some dtypes for {0} on device type {1} are only partially supported!\n".format( op.name, device_type) if len(partially_supported_forward) > 0: msg = msg + "The following dtypes only worked on some samples during forward: {0}.\n".format( partially_supported_forward) if len(partially_supported_backward) > 0: msg = msg + "The following dtypes only worked on some samples during backward: {0}.\n".format( partially_supported_backward) print(msg) if (len(supported_but_unclaimed_forward) + len(claimed_but_unsupported_forward) + len(supported_but_unclaimed_backward) + len(claimed_but_unsupported_backward)) == 0: return # Generates error msg msg = "The supported dtypes for {0} on device type {1} are incorrect!\n".format( op.name, device_type) if len(supported_but_unclaimed_forward) > 0: msg = msg + "The following dtypes worked in forward but are not listed by the OpInfo: {0}.\n".format( supported_but_unclaimed_forward) if len(supported_but_unclaimed_backward) > 0: msg = msg + "The following dtypes worked in backward but are not listed by the OpInfo: {0}.\n".format( supported_but_unclaimed_backward) if len(claimed_but_unsupported_forward) > 0: msg = msg + "The following dtypes did not work in forward but are listed by the OpInfo: {0}.\n".format( claimed_but_unsupported_forward) if len(claimed_but_unsupported_backward) > 0: msg = msg + "The following dtypes did not work in backward but are listed by the OpInfo: {0}.\n".format( claimed_but_unsupported_backward) self.fail(msg)