diff --git a/backends/test/harness/tester.py b/backends/test/harness/tester.py index 06db1aae13d..7019b734290 100644 --- a/backends/test/harness/tester.py +++ b/backends/test/harness/tester.py @@ -311,7 +311,10 @@ def run_method_and_compare_outputs( print(f"Comparing Stage {stage} with Stage {reference_stage}") for run_iteration in range(number_of_runs): inputs_to_run = inputs if inputs else next(self.generate_random_inputs()) - input_shapes = [generated_input.shape for generated_input in inputs_to_run] + input_shapes = [ + generated_input.shape if hasattr(generated_input, "shape") else None + for generated_input in inputs_to_run + ] print(f"Run {run_iteration} with input shapes: {input_shapes}") # Reference output (and quantization scale) diff --git a/backends/test/suite/operators/test_amax.py b/backends/test/suite/operators/test_amax.py new file mode 100644 index 00000000000..aff33476e69 --- /dev/null +++ b/backends/test/suite/operators/test_amax.py @@ -0,0 +1,255 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + +from typing import List, Optional, Tuple, Union + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class AmaxModel(torch.nn.Module): + def __init__( + self, + dim: Optional[Union[int, Tuple[int, ...], List[int]]] = None, + keepdim: bool = False, + ): + super().__init__() + self.dim = dim + self.keepdim = keepdim + + def forward(self, x): + return torch.amax(x, dim=self.dim, keepdim=self.keepdim) + + +@operator_test +class Amax(OperatorTest): + @dtype_test + def test_amax_dtype(self, flow: TestFlow, dtype) -> None: + self._test_op( + AmaxModel().to(dtype), + (torch.rand(10, 10).to(dtype),), + flow, + ) + + def test_amax_dim(self, flow: TestFlow) -> None: + self._test_op( + AmaxModel(dim=0), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + AmaxModel(dim=1), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + AmaxModel(dim=0), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(dim=1), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(dim=2), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(dim=1), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(dim=-1), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(dim=-2), + (torch.randn(3, 4, 5),), + flow, + ) + + def test_amax_multi_dim(self, flow: TestFlow) -> None: + self._test_op( + AmaxModel(dim=(0, 1)), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(dim=(0, 2)), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(dim=(1, 2)), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(dim=(1, 3)), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(dim=(0, 2)), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(dim=(-1, -3)), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(dim=(0, 1, 2, 3)), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + def test_amax_keepdim(self, flow: TestFlow) -> None: + self._test_op( + AmaxModel(dim=0, keepdim=True), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + AmaxModel(dim=1, keepdim=True), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + AmaxModel(dim=1, keepdim=True), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(dim=2, keepdim=True), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(dim=(1, 2), keepdim=True), + (torch.randn(3, 4, 5),), + flow, + ) + + def test_amax_shapes(self, flow: TestFlow) -> None: + self._test_op( + AmaxModel(), + (torch.randn(20),), + flow, + ) + self._test_op( + AmaxModel(dim=0), + (torch.randn(20),), + flow, + ) + + self._test_op( + AmaxModel(), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + AmaxModel(), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + AmaxModel(), + (torch.randn(2, 2, 3, 4, 5),), + flow, + ) + + def test_amax_edge_cases(self, flow: TestFlow) -> None: + x = torch.tensor([[1.0, float("inf"), 3.0], [4.0, 5.0, float("inf")]]) + self._test_op( + AmaxModel(), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + AmaxModel(dim=0), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + AmaxModel(dim=1), + (x,), + flow, + use_random_test_inputs=False, + ) + + x = torch.tensor([[1.0, float("nan"), 3.0], [4.0, 5.0, float("nan")]]) + self._test_op( + AmaxModel(), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + AmaxModel(dim=0), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + AmaxModel(dim=1), + (x,), + flow, + use_random_test_inputs=False, + ) + + def test_amax_scalar(self, flow: TestFlow) -> None: + self._test_op( + AmaxModel(), + (torch.tensor([5.0]),), + flow, + ) + self._test_op( + AmaxModel(dim=0), + (torch.tensor([5.0]),), + flow, + ) diff --git a/backends/test/suite/operators/test_amin.py b/backends/test/suite/operators/test_amin.py new file mode 100644 index 00000000000..ab59d77d0be --- /dev/null +++ b/backends/test/suite/operators/test_amin.py @@ -0,0 +1,257 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + +from typing import List, Optional, Tuple, Union + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class AminModel(torch.nn.Module): + def __init__( + self, + dim: Optional[Union[int, Tuple[int, ...], List[int]]] = None, + keepdim: bool = False, + ): + super().__init__() + self.dim = dim + self.keepdim = keepdim + + def forward(self, x): + if self.dim is None: + return torch.amin(x, keepdim=self.keepdim) + return torch.amin(x, dim=self.dim, keepdim=self.keepdim) + + +@operator_test +class Amin(OperatorTest): + @dtype_test + def test_amin_dtype(self, flow: TestFlow, dtype) -> None: + self._test_op( + AminModel().to(dtype), + (torch.rand(10, 10).to(dtype),), + flow, + ) + + def test_amin_dim(self, flow: TestFlow) -> None: + self._test_op( + AminModel(dim=0), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + AminModel(dim=1), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + AminModel(dim=0), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(dim=1), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(dim=2), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(dim=1), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(dim=-1), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(dim=-2), + (torch.randn(3, 4, 5),), + flow, + ) + + def test_amin_multi_dim(self, flow: TestFlow) -> None: + self._test_op( + AminModel(dim=(0, 1)), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(dim=(0, 2)), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(dim=(1, 2)), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(dim=(1, 3)), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(dim=(0, 2)), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(dim=(-1, -3)), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(dim=(0, 1, 2, 3)), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + def test_amin_keepdim(self, flow: TestFlow) -> None: + self._test_op( + AminModel(dim=0, keepdim=True), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + AminModel(dim=1, keepdim=True), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + AminModel(dim=1, keepdim=True), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(dim=2, keepdim=True), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(dim=(1, 2), keepdim=True), + (torch.randn(3, 4, 5),), + flow, + ) + + def test_amin_shapes(self, flow: TestFlow) -> None: + self._test_op( + AminModel(), + (torch.randn(20),), + flow, + ) + self._test_op( + AminModel(dim=0), + (torch.randn(20),), + flow, + ) + + self._test_op( + AminModel(), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + AminModel(), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + AminModel(), + (torch.randn(2, 2, 3, 4, 5),), + flow, + ) + + def test_amin_edge_cases(self, flow: TestFlow) -> None: + x = torch.tensor([[1.0, float("-inf"), 3.0], [4.0, 5.0, float("-inf")]]) + self._test_op( + AminModel(), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + AminModel(dim=0), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + AminModel(dim=1), + (x,), + flow, + use_random_test_inputs=False, + ) + + x = torch.tensor([[1.0, float("nan"), 3.0], [4.0, 5.0, float("nan")]]) + self._test_op( + AminModel(), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + AminModel(dim=0), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + AminModel(dim=1), + (x,), + flow, + use_random_test_inputs=False, + ) + + def test_amin_scalar(self, flow: TestFlow) -> None: + self._test_op( + AminModel(), + (torch.tensor([5.0]),), + flow, + ) + self._test_op( + AminModel(dim=0), + (torch.tensor([5.0]),), + flow, + ) diff --git a/backends/test/suite/operators/test_argmax.py b/backends/test/suite/operators/test_argmax.py new file mode 100644 index 00000000000..adf1e43a340 --- /dev/null +++ b/backends/test/suite/operators/test_argmax.py @@ -0,0 +1,199 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + +from typing import Optional + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class ArgmaxModel(torch.nn.Module): + def __init__(self, dim: Optional[int] = None, keepdim: bool = False): + super().__init__() + self.dim = dim + self.keepdim = keepdim + + def forward(self, x): + return torch.argmax(x, dim=self.dim, keepdim=self.keepdim) + + +@operator_test +class Argmax(OperatorTest): + @dtype_test + def test_argmax_dtype(self, flow: TestFlow, dtype) -> None: + self._test_op( + ArgmaxModel().to(dtype), + (torch.rand(10, 10).to(dtype),), + flow, + ) + + def test_argmax_dim(self, flow: TestFlow) -> None: + self._test_op( + ArgmaxModel(dim=0), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + ArgmaxModel(dim=1), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + ArgmaxModel(dim=0), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + ArgmaxModel(dim=1), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + ArgmaxModel(dim=2), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + ArgmaxModel(dim=1), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + ArgmaxModel(dim=-1), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + ArgmaxModel(dim=-2), + (torch.randn(3, 4, 5),), + flow, + ) + + def test_argmax_keepdim(self, flow: TestFlow) -> None: + self._test_op( + ArgmaxModel(dim=0, keepdim=True), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + ArgmaxModel(dim=1, keepdim=True), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + ArgmaxModel(dim=1, keepdim=True), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + ArgmaxModel(dim=2, keepdim=True), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + def test_argmax_shapes(self, flow: TestFlow) -> None: + self._test_op( + ArgmaxModel(), + (torch.randn(20),), + flow, + ) + + self._test_op( + ArgmaxModel(), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + ArgmaxModel(), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + ArgmaxModel(), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + ArgmaxModel(), + (torch.randn(2, 2, 3, 4, 5),), + flow, + ) + + def test_argmax_edge_cases(self, flow: TestFlow) -> None: + x = torch.tensor([[1.0, float("inf"), 3.0], [4.0, 5.0, float("inf")]]) + self._test_op( + ArgmaxModel(), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + ArgmaxModel(dim=0), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + ArgmaxModel(dim=1), + (x,), + flow, + use_random_test_inputs=False, + ) + + x = torch.tensor([[1.0, float("nan"), 3.0], [4.0, 5.0, float("nan")]]) + self._test_op( + ArgmaxModel(), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + ArgmaxModel(dim=0), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + ArgmaxModel(dim=1), + (x,), + flow, + use_random_test_inputs=False, + ) + + x = torch.tensor([5.0]) + self._test_op( + ArgmaxModel(), + (x,), + flow, + ) + + def test_argmax_scalar(self, flow: TestFlow) -> None: + self._test_op( + ArgmaxModel(), + (torch.tensor([5.0]),), + flow, + ) diff --git a/backends/test/suite/operators/test_argmin.py b/backends/test/suite/operators/test_argmin.py new file mode 100644 index 00000000000..0613c74a3ee --- /dev/null +++ b/backends/test/suite/operators/test_argmin.py @@ -0,0 +1,199 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + +from typing import Optional + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class ArgminModel(torch.nn.Module): + def __init__(self, dim: Optional[int] = None, keepdim: bool = False): + super().__init__() + self.dim = dim + self.keepdim = keepdim + + def forward(self, x): + return torch.argmin(x, dim=self.dim, keepdim=self.keepdim) + + +@operator_test +class Argmin(OperatorTest): + @dtype_test + def test_argmin_dtype(self, flow: TestFlow, dtype) -> None: + self._test_op( + ArgminModel().to(dtype), + (torch.rand(10, 10).to(dtype),), + flow, + ) + + def test_argmin_dim(self, flow: TestFlow) -> None: + self._test_op( + ArgminModel(dim=0), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + ArgminModel(dim=1), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + ArgminModel(dim=0), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + ArgminModel(dim=1), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + ArgminModel(dim=2), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + ArgminModel(dim=1), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + ArgminModel(dim=-1), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + ArgminModel(dim=-2), + (torch.randn(3, 4, 5),), + flow, + ) + + def test_argmin_keepdim(self, flow: TestFlow) -> None: + self._test_op( + ArgminModel(dim=0, keepdim=True), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + ArgminModel(dim=1, keepdim=True), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + ArgminModel(dim=1, keepdim=True), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + ArgminModel(dim=2, keepdim=True), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + def test_argmin_shapes(self, flow: TestFlow) -> None: + self._test_op( + ArgminModel(), + (torch.randn(20),), + flow, + ) + + self._test_op( + ArgminModel(), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + ArgminModel(), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + ArgminModel(), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + ArgminModel(), + (torch.randn(2, 2, 3, 4, 5),), + flow, + ) + + def test_argmin_edge_cases(self, flow: TestFlow) -> None: + x = torch.tensor([[1.0, float("-inf"), 3.0], [4.0, 5.0, float("-inf")]]) + self._test_op( + ArgminModel(), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + ArgminModel(dim=0), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + ArgminModel(dim=1), + (x,), + flow, + use_random_test_inputs=False, + ) + + x = torch.tensor([[1.0, float("nan"), 3.0], [4.0, 5.0, float("nan")]]) + self._test_op( + ArgminModel(), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + ArgminModel(dim=0), + (x,), + flow, + use_random_test_inputs=False, + ) + self._test_op( + ArgminModel(dim=1), + (x,), + flow, + use_random_test_inputs=False, + ) + + x = torch.tensor([5.0]) + self._test_op( + ArgminModel(), + (x,), + flow, + ) + + def test_argmin_scalar(self, flow: TestFlow) -> None: + self._test_op( + ArgminModel(), + (torch.tensor([5.0]),), + flow, + ) diff --git a/backends/test/suite/operators/test_floor_divide.py b/backends/test/suite/operators/test_floor_divide.py new file mode 100644 index 00000000000..87104af11dc --- /dev/null +++ b/backends/test/suite/operators/test_floor_divide.py @@ -0,0 +1,213 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class FloorDivideModel(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(self, x, y): + return torch.floor_divide(x, y) + + +@operator_test +class TestFloorDivide(OperatorTest): + @dtype_test + def test_floor_divide_dtype(self, flow: TestFlow, dtype) -> None: + # Test with different dtypes + model = FloorDivideModel().to(dtype) + # Use values that won't cause division by zero + x = torch.randint(-100, 100, (10, 10)).to(dtype) + y = torch.full_like(x, 2) # Divisor of 2 + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + def test_floor_divide_scalar_divisors(self, flow: TestFlow) -> None: + # Test with different scalar divisors as tensors + + # Positive divisor + x = torch.randint(-100, 100, (10, 10)) + y = torch.full_like(x, 3) # Divisor of 3 + self._test_op( + FloorDivideModel(), (x, y), flow, generate_random_test_inputs=False + ) + + # Negative divisor + x = torch.randint(-100, 100, (10, 10)) + y = torch.full_like(x, -2) # Divisor of -2 + self._test_op( + FloorDivideModel(), (x, y), flow, generate_random_test_inputs=False + ) + + # Fractional divisor + x = torch.randint(-100, 100, (10, 10)).float() + y = torch.full_like(x, 2.5) # Divisor of 2.5 + self._test_op( + FloorDivideModel(), (x, y), flow, generate_random_test_inputs=False + ) + + # Large divisor + x = torch.randint(-1000, 1000, (10, 10)) + y = torch.full_like(x, 100) # Divisor of 100 + self._test_op( + FloorDivideModel(), (x, y), flow, generate_random_test_inputs=False + ) + + # Small divisor + x = torch.randint(-100, 100, (10, 10)).float() + y = torch.full_like(x, 0.5) # Divisor of 0.5 + self._test_op( + FloorDivideModel(), (x, y), flow, generate_random_test_inputs=False + ) + + def test_floor_divide_tensor_divisors(self, flow: TestFlow) -> None: + # Test with tensor divisors + + # Constant divisor tensor + x = torch.randint(-100, 100, (10, 10)) + y = torch.full_like(x, 2) # All elements are 2 + self._test_op( + FloorDivideModel(), (x, y), flow, generate_random_test_inputs=False + ) + + # Random divisor tensor (non-zero) + x = torch.randint(-100, 100, (10, 10)) + y = torch.randint(1, 10, (10, 10)) # Positive divisors + self._test_op( + FloorDivideModel(), (x, y), flow, generate_random_test_inputs=False + ) + + # Mixed positive and negative divisors + x = torch.randint(-100, 100, (10, 10)) + y = torch.randint(-10, 10, (10, 10)) + # Replace zeros to avoid division by zero + y[y == 0] = 1 + self._test_op( + FloorDivideModel(), (x, y), flow, generate_random_test_inputs=False + ) + + # Broadcasting: scalar dividend, tensor divisor + x = torch.tensor([10]) + y = torch.arange(1, 5) # [1, 2, 3, 4] + self._test_op( + FloorDivideModel(), (x, y), flow, generate_random_test_inputs=False + ) + + # Broadcasting: tensor dividend, scalar divisor + x = torch.arange(-10, 10) + y = torch.tensor([2]) + self._test_op( + FloorDivideModel(), (x, y), flow, generate_random_test_inputs=False + ) + + def test_floor_divide_shapes(self, flow: TestFlow) -> None: + # Test with different tensor shapes + model = FloorDivideModel() + + # 1D tensor + x = torch.randint(-100, 100, (20,)) + y = torch.full_like(x, 2) # Divisor of 2 + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + # 2D tensor + x = torch.randint(-100, 100, (5, 10)) + y = torch.full_like(x, 2) # Divisor of 2 + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + # 3D tensor + x = torch.randint(-100, 100, (3, 4, 5)) + y = torch.full_like(x, 2) # Divisor of 2 + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + # 4D tensor + x = torch.randint(-100, 100, (2, 3, 4, 5)) + y = torch.full_like(x, 2) # Divisor of 2 + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + # 5D tensor + x = torch.randint(-100, 100, (2, 2, 3, 4, 5)) + y = torch.full_like(x, 2) # Divisor of 2 + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + def test_floor_divide_values(self, flow: TestFlow) -> None: + # Test with different value ranges + model = FloorDivideModel() + + # Test with specific dividend values + x = torch.tensor([-7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7]) + + # Divide by 2 + y = torch.tensor([2]).expand_as(x).clone() + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + # Divide by -2 + y = torch.tensor([-2]).expand_as(x).clone() + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + # Divide by 3 + y = torch.tensor([3]).expand_as(x).clone() + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + # Divide by -3 + y = torch.tensor([-3]).expand_as(x).clone() + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + # Test with floating point values + x = torch.tensor( + [-3.8, -3.5, -3.2, -0.8, -0.5, -0.2, 0.0, 0.2, 0.5, 0.8, 3.2, 3.5, 3.8] + ) + + # Divide by 2.0 + y = torch.tensor([2.0]).expand_as(x).clone() + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + # Divide by -2.0 + y = torch.tensor([-2.0]).expand_as(x).clone() + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + def test_floor_divide_edge_cases(self, flow: TestFlow) -> None: + # Test edge cases + model = FloorDivideModel() + + # Zero dividend + x = torch.zeros(10) + y = torch.full_like(x, 2) + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + # Division with remainder + x = torch.tensor([1, 3, 5, 7, 9]) + y = torch.full_like(x, 2) + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + # Tensor with infinity + x = torch.tensor([float("inf"), float("-inf"), 10.0, -10.0]) + y = torch.full_like(x, 2) + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + # Tensor with NaN + x = torch.tensor([float("nan"), 10.0, -10.0]) + y = torch.full_like(x, 2) + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + # Very large values + x = torch.tensor([1e10, -1e10]) + y = torch.full_like(x, 3) + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) + + # Very small values + x = torch.tensor([1e-10, -1e-10]) + y = torch.full_like(x, 2) + self._test_op(model, (x, y), flow, generate_random_test_inputs=False) diff --git a/backends/test/suite/operators/test_index_put.py b/backends/test/suite/operators/test_index_put.py new file mode 100644 index 00000000000..b5333b40984 --- /dev/null +++ b/backends/test/suite/operators/test_index_put.py @@ -0,0 +1,455 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class IndexPutInPlaceModel(torch.nn.Module): + def __init__(self, accumulate=False): + super().__init__() + self.accumulate = accumulate + + def forward(self, x, indices, values): + # Clone the input to avoid modifying it in-place + result = x.clone() + # Apply index_put_ and return the modified tensor + result.index_put_(indices, values, self.accumulate) + return result + + +class IndexPutModel(torch.nn.Module): + def __init__(self, accumulate=False): + super().__init__() + self.accumulate = accumulate + + def forward(self, x, indices, values): + # Use the non-in-place variant which returns a new tensor + return torch.index_put(x, indices, values, self.accumulate) + + +@operator_test +class IndexPut(OperatorTest): + @dtype_test + def test_index_put_in_place_dtype(self, flow: TestFlow, dtype) -> None: + indices = (torch.tensor([0, 2]),) + values = torch.tensor([10.0, 20.0]).to(dtype) + self._test_op( + IndexPutInPlaceModel(), + ((torch.rand(5, 2) * 100).to(dtype), indices, values), + flow, + generate_random_test_inputs=False, + ) + + @dtype_test + def test_index_put_dtype(self, flow: TestFlow, dtype) -> None: + indices = (torch.tensor([0, 2]),) + values = torch.tensor([10.0, 20.0]).to(dtype) + self._test_op( + IndexPutModel(), + ((torch.rand(5, 2) * 100).to(dtype), indices, values), + flow, + generate_random_test_inputs=False, + ) + + def test_index_put_in_place_accumulate(self, flow: TestFlow) -> None: + indices = (torch.tensor([0, 2]),) + values = torch.tensor([10.0, 20.0]) + self._test_op( + IndexPutInPlaceModel(accumulate=False), + (torch.ones(5, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + indices = (torch.tensor([0, 2]),) + values = torch.tensor([10.0, 20.0]) + self._test_op( + IndexPutInPlaceModel(accumulate=True), + (torch.ones(5, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + def test_index_put_accumulate(self, flow: TestFlow) -> None: + indices = (torch.tensor([0, 2]),) + values = torch.tensor([10.0, 20.0]) + self._test_op( + IndexPutModel(accumulate=False), + (torch.ones(5, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + indices = (torch.tensor([0, 2]),) + values = torch.tensor([10.0, 20.0]) + self._test_op( + IndexPutModel(accumulate=True), + (torch.ones(5, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + def test_index_put_in_place_shapes(self, flow: TestFlow) -> None: + indices = (torch.tensor([0, 2]),) + values = torch.tensor([10.0, 20.0]) + self._test_op( + IndexPutInPlaceModel(), + (torch.randn(5), indices, values), + flow, + generate_random_test_inputs=False, + ) + + indices = (torch.tensor([0, 2]), torch.tensor([1, 1])) + values = torch.tensor([10.0, 20.0]) + self._test_op( + IndexPutInPlaceModel(), + (torch.randn(5, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + indices = (torch.tensor([0, 2]), torch.tensor([1, 1]), torch.tensor([0, 1])) + values = torch.tensor([10.0, 20.0]) + self._test_op( + IndexPutInPlaceModel(), + (torch.randn(5, 3, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + indices = ( + torch.tensor([0, 2]), + torch.tensor([1, 1]), + torch.tensor([0, 1]), + torch.tensor([2, 3]), + ) + values = torch.tensor( + [ + 10.0, + ] + ) + self._test_op( + IndexPutInPlaceModel(), + (torch.randn(5, 3, 2, 4), indices, values), + flow, + generate_random_test_inputs=False, + ) + + def test_index_put_shapes(self, flow: TestFlow) -> None: + indices = (torch.tensor([0, 2]),) + values = torch.tensor([10.0, 20.0]) + self._test_op( + IndexPutModel(), + (torch.randn(5), indices, values), + flow, + generate_random_test_inputs=False, + ) + + indices = (torch.tensor([0, 2]), torch.tensor([1, 1])) + values = torch.tensor([10.0, 20.0]) + self._test_op( + IndexPutModel(), + (torch.randn(5, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + indices = (torch.tensor([0, 2]), torch.tensor([1, 1]), torch.tensor([0, 1])) + values = torch.tensor([10.0, 20.0]) + self._test_op( + IndexPutModel(), + (torch.randn(5, 3, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + indices = ( + torch.tensor([0, 2]), + torch.tensor([1, 1]), + torch.tensor([0, 1]), + torch.tensor([2, 3]), + ) + values = torch.tensor( + [ + 10.0, + ] + ) + self._test_op( + IndexPutModel(), + (torch.randn(5, 3, 2, 4), indices, values), + flow, + generate_random_test_inputs=False, + ) + + def test_index_put_in_place_indices(self, flow: TestFlow) -> None: + indices = (torch.tensor([2]),) + values = torch.tensor([10.0]) + self._test_op( + IndexPutInPlaceModel(), + (torch.randn(5, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + indices = (torch.tensor([0, 2, 4]),) + values = torch.tensor([10.0, 20.0, 30.0]) + self._test_op( + IndexPutInPlaceModel(), + (torch.randn(5, 3), indices, values), + flow, + generate_random_test_inputs=False, + ) + + indices = (torch.tensor([1, 1, 3, 3]),) + values = torch.tensor([10.0, 20.0, 30.0, 40.0]) + self._test_op( + IndexPutInPlaceModel(accumulate=True), + (torch.randn(5), indices, values), + flow, + generate_random_test_inputs=False, + ) + + def test_index_put_indices(self, flow: TestFlow) -> None: + indices = (torch.tensor([2]),) + values = torch.tensor([10.0]) + self._test_op( + IndexPutModel(), + (torch.randn(5, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + indices = (torch.tensor([0, 2, 4]),) + values = torch.tensor([10.0, 20.0, 30.0]) + self._test_op( + IndexPutModel(), + (torch.randn(5, 3), indices, values), + flow, + generate_random_test_inputs=False, + ) + + indices = (torch.tensor([1, 1, 3, 3]),) + values = torch.tensor([10.0, 20.0, 30.0, 40.0]) + self._test_op( + IndexPutModel(accumulate=True), + (torch.randn(5), indices, values), + flow, + generate_random_test_inputs=False, + ) + + def test_index_put_in_place_broadcasting(self, flow: TestFlow) -> None: + # Test scalar broadcasting - single value to multiple positions + indices = (torch.tensor([0, 2, 4]),) + values = torch.tensor([42.0]) + self._test_op( + IndexPutInPlaceModel(), + (torch.randn(5, 3), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test 1D broadcasting to 2D indexed positions + indices = (torch.tensor([0, 1]), torch.tensor([1, 2])) + values = torch.tensor([10.0, 20.0]) # 1D tensor + self._test_op( + IndexPutInPlaceModel(), + (torch.randn(3, 4), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test broadcasting with compatible shapes - 1D to multiple 2D slices + indices = (torch.tensor([0, 2]),) + values = torch.tensor([5.0, 15.0]) # Will broadcast to (2, 3) shape + self._test_op( + IndexPutInPlaceModel(), + (torch.randn(4, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test 2D values broadcasting to 3D indexed positions + indices = (torch.tensor([0, 1]),) + values = torch.tensor([[1.0, 2.0], [3.0, 4.0]]) # 2D tensor + self._test_op( + IndexPutInPlaceModel(), + (torch.randn(3, 2, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test broadcasting with accumulate=True + indices = (torch.tensor([1, 1, 1]),) + values = torch.tensor([5.0]) # Scalar will be added 3 times to same position + self._test_op( + IndexPutInPlaceModel(accumulate=True), + (torch.ones(4, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + def test_index_put_broadcasting(self, flow: TestFlow) -> None: + # Test scalar broadcasting - single value to multiple positions + indices = (torch.tensor([0, 2, 4]),) + values = torch.tensor([42.0]) + self._test_op( + IndexPutModel(), + (torch.randn(5, 3), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test 1D broadcasting to 2D indexed positions + indices = (torch.tensor([0, 1]), torch.tensor([1, 2])) + values = torch.tensor([10.0, 20.0]) # 1D tensor + self._test_op( + IndexPutModel(), + (torch.randn(3, 4), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test broadcasting with compatible shapes - 1D to multiple 2D slices + indices = (torch.tensor([0, 2]),) + values = torch.tensor([5.0, 15.0]) # Will broadcast to (2, 3) shape + self._test_op( + IndexPutModel(), + (torch.randn(4, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test 2D values broadcasting to 3D indexed positions + indices = (torch.tensor([0, 1]),) + values = torch.tensor([[1.0, 2.0], [3.0, 4.0]]) # 2D tensor + self._test_op( + IndexPutModel(), + (torch.randn(3, 2, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test broadcasting with accumulate=True + indices = (torch.tensor([1, 1, 1]),) + values = torch.tensor([5.0]) # Scalar will be added 3 times to same position + self._test_op( + IndexPutModel(accumulate=True), + (torch.ones(4, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + def test_index_put_in_place_two_indices(self, flow: TestFlow) -> None: + # Test basic two-index tensor indexing + indices = (torch.tensor([0, 1, 2]), torch.tensor([1, 0, 2])) + values = torch.tensor([10.0, 20.0, 30.0]) + self._test_op( + IndexPutInPlaceModel(), + (torch.randn(4, 3), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test two-index with different lengths (broadcasting) + indices = (torch.tensor([0, 2]), torch.tensor([1, 1])) + values = torch.tensor([15.0, 25.0]) + self._test_op( + IndexPutInPlaceModel(), + (torch.randn(3, 3), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test two-index with repeated positions and accumulate=True + indices = (torch.tensor([1, 1, 2]), torch.tensor([0, 0, 1])) + values = torch.tensor([5.0, 10.0, 15.0]) + self._test_op( + IndexPutInPlaceModel(accumulate=True), + (torch.zeros(3, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test two-index with repeated positions and accumulate=False + indices = (torch.tensor([1, 1, 2]), torch.tensor([0, 0, 1])) + values = torch.tensor([5.0, 10.0, 15.0]) + self._test_op( + IndexPutInPlaceModel(accumulate=False), + (torch.zeros(3, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test two-index with index broadcast. + indices = (torch.tensor([1]), torch.tensor([0, 0, 1])) + values = torch.tensor([5.0, 10.0, 15.0]) + self._test_op( + IndexPutInPlaceModel(accumulate=False), + (torch.zeros(3, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + def test_index_put_two_indices(self, flow: TestFlow) -> None: + # Test basic two-index tensor indexing + indices = (torch.tensor([0, 1, 2]), torch.tensor([1, 0, 2])) + values = torch.tensor([10.0, 20.0, 30.0]) + self._test_op( + IndexPutModel(), + (torch.randn(4, 3), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test two-index with different lengths (broadcasting) + indices = (torch.tensor([0, 2]), torch.tensor([1, 1])) + values = torch.tensor([15.0, 25.0]) + self._test_op( + IndexPutModel(), + (torch.randn(3, 3), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test two-index with repeated positions and accumulate=True + indices = (torch.tensor([1, 1, 2]), torch.tensor([0, 0, 1])) + values = torch.tensor([5.0, 10.0, 15.0]) + self._test_op( + IndexPutModel(accumulate=True), + (torch.zeros(3, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test two-index with repeated positions and accumulate=False + indices = (torch.tensor([1, 1, 2]), torch.tensor([0, 0, 1])) + values = torch.tensor([5.0, 10.0, 15.0]) + self._test_op( + IndexPutModel(accumulate=False), + (torch.zeros(3, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) + + # Test two-index with index broadcast. + indices = (torch.tensor([1]), torch.tensor([0, 0, 1])) + values = torch.tensor([5.0, 10.0, 15.0]) + self._test_op( + IndexPutModel(accumulate=False), + (torch.zeros(3, 2), indices, values), + flow, + generate_random_test_inputs=False, + ) diff --git a/backends/test/suite/operators/test_index_select.py b/backends/test/suite/operators/test_index_select.py new file mode 100644 index 00000000000..46a8018ef93 --- /dev/null +++ b/backends/test/suite/operators/test_index_select.py @@ -0,0 +1,128 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class IndexSelectModel(torch.nn.Module): + def __init__(self, dim=0): + super().__init__() + self.dim = dim + + def forward(self, x, indices): + return torch.index_select(x, self.dim, indices) + + +@operator_test +class IndexSelect(OperatorTest): + @dtype_test + def test_index_select_dtype(self, flow: TestFlow, dtype) -> None: + indices = torch.tensor([0, 2], dtype=torch.int64) + self._test_op( + IndexSelectModel(dim=0), + ((torch.rand(5, 3) * 100).to(dtype), indices), + flow, + generate_random_test_inputs=False, + ) + + def test_index_select_dimensions(self, flow: TestFlow) -> None: + indices = torch.tensor([0, 2], dtype=torch.int64) + self._test_op( + IndexSelectModel(dim=0), + (torch.randn(5, 3), indices), + flow, + generate_random_test_inputs=False, + ) + + indices = torch.tensor([0, 1], dtype=torch.int64) + self._test_op( + IndexSelectModel(dim=1), + (torch.randn(5, 3), indices), + flow, + generate_random_test_inputs=False, + ) + + indices = torch.tensor([0, 2], dtype=torch.int64) + self._test_op( + IndexSelectModel(dim=2), + (torch.randn(3, 4, 5), indices), + flow, + generate_random_test_inputs=False, + ) + + def test_index_select_shapes(self, flow: TestFlow) -> None: + indices = torch.tensor([0, 1], dtype=torch.int64) + + self._test_op( + IndexSelectModel(dim=0), + (torch.randn(5), indices), + flow, + generate_random_test_inputs=False, + ) + + self._test_op( + IndexSelectModel(dim=0), + (torch.randn(5, 3), indices), + flow, + generate_random_test_inputs=False, + ) + + self._test_op( + IndexSelectModel(dim=0), + (torch.randn(5, 3, 2), indices), + flow, + generate_random_test_inputs=False, + ) + + self._test_op( + IndexSelectModel(dim=0), + (torch.randn(5, 3, 2, 4), indices), + flow, + generate_random_test_inputs=False, + ) + + def test_index_select_indices(self, flow: TestFlow) -> None: + indices = torch.tensor([2], dtype=torch.int64) + self._test_op( + IndexSelectModel(dim=0), + (torch.randn(5, 3), indices), + flow, + generate_random_test_inputs=False, + ) + + indices = torch.tensor([0, 2, 4], dtype=torch.int64) + self._test_op( + IndexSelectModel(dim=0), + (torch.randn(5, 3), indices), + flow, + generate_random_test_inputs=False, + ) + + indices = torch.tensor([1, 1, 3, 3], dtype=torch.int64) + self._test_op( + IndexSelectModel(dim=0), + (torch.randn(5, 3), indices), + flow, + generate_random_test_inputs=False, + ) + + indices = torch.tensor([4, 3, 2, 1, 0], dtype=torch.int64) + self._test_op( + IndexSelectModel(dim=0), + (torch.randn(5, 3), indices), + flow, + generate_random_test_inputs=False, + ) diff --git a/backends/test/suite/operators/test_mean.py b/backends/test/suite/operators/test_mean.py new file mode 100644 index 00000000000..746a4b16d9f --- /dev/null +++ b/backends/test/suite/operators/test_mean.py @@ -0,0 +1,303 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + +from typing import List, Optional, Tuple, Union + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class MeanModel(torch.nn.Module): + def __init__( + self, + dim: Optional[Union[int, Tuple[int, ...], List[int]]] = None, + keepdim: bool = False, + dtype: Optional[torch.dtype] = None, + ): + super().__init__() + self.dim = dim + self.keepdim = keepdim + self.dtype = dtype + + def forward(self, x): + return torch.mean(x, dim=self.dim, keepdim=self.keepdim, dtype=self.dtype) + + +@operator_test +class Mean(OperatorTest): + @dtype_test + def test_mean_dtype(self, flow: TestFlow, dtype) -> None: + self._test_op( + MeanModel().to(dtype), + (torch.rand(10, 10).to(dtype),), + flow, + ) + + def test_mean_basic(self, flow: TestFlow) -> None: + self._test_op( + MeanModel(), + (torch.randn(10, 10),), + flow, + ) + + def test_mean_dim(self, flow: TestFlow) -> None: + self._test_op( + MeanModel(dim=0), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + MeanModel(dim=1), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + MeanModel(dim=0), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(dim=1), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(dim=2), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(dim=1), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(dim=-1), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(dim=-2), + (torch.randn(3, 4, 5),), + flow, + ) + + def test_mean_multi_dim(self, flow: TestFlow) -> None: + self._test_op( + MeanModel(dim=(0, 1)), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(dim=(0, 2)), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(dim=(1, 2)), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(dim=(1, 3)), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(dim=(0, 2)), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(dim=(-1, -3)), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(dim=(0, 1, 2, 3)), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + def test_mean_keepdim(self, flow: TestFlow) -> None: + self._test_op( + MeanModel(dim=0, keepdim=True), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + MeanModel(dim=1, keepdim=True), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + MeanModel(dim=1, keepdim=True), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(dim=2, keepdim=True), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(dim=(1, 2), keepdim=True), + (torch.randn(3, 4, 5),), + flow, + ) + + def test_mean_output_dtype(self, flow: TestFlow) -> None: + self._test_op( + MeanModel(dtype=torch.float32), + (torch.randint(0, 10, (5, 10)),), + flow, + ) + + self._test_op( + MeanModel(dtype=torch.float64), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + MeanModel(dim=1, dtype=torch.float64), + (torch.randn(5, 10),), + flow, + ) + + def test_mean_shapes(self, flow: TestFlow) -> None: + self._test_op( + MeanModel(), + (torch.randn(20),), + flow, + ) + self._test_op( + MeanModel(dim=0), + (torch.randn(20),), + flow, + ) + + self._test_op( + MeanModel(), + (torch.randn(5, 10),), + flow, + ) + + self._test_op( + MeanModel(), + (torch.randn(3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(), + (torch.randn(2, 3, 4, 5),), + flow, + ) + + self._test_op( + MeanModel(), + (torch.randn(2, 2, 3, 4, 5),), + flow, + ) + + def test_mean_edge_cases(self, flow: TestFlow) -> None: + x = torch.tensor([[1.0, float("inf"), 3.0], [4.0, 5.0, float("inf")]]) + self._test_op( + MeanModel(), + (x,), + flow, + generate_random_test_inputs=False, + ) + self._test_op( + MeanModel(dim=0), + (x,), + flow, + generate_random_test_inputs=False, + ) + self._test_op( + MeanModel(dim=1), + (x,), + flow, + generate_random_test_inputs=False, + ) + + x = torch.tensor([[1.0, float("-inf"), 3.0], [4.0, 5.0, float("-inf")]]) + self._test_op( + MeanModel(), + (x,), + flow, + generate_random_test_inputs=False, + ) + self._test_op( + MeanModel(dim=0), + (x,), + flow, + generate_random_test_inputs=False, + ) + self._test_op( + MeanModel(dim=1), + (x,), + flow, + generate_random_test_inputs=False, + ) + + x = torch.tensor([[1.0, float("nan"), 3.0], [4.0, 5.0, float("nan")]]) + self._test_op( + MeanModel(), + (x,), + flow, + generate_random_test_inputs=False, + ) + self._test_op( + MeanModel(dim=0), + (x,), + flow, + generate_random_test_inputs=False, + ) + self._test_op( + MeanModel(dim=1), + (x,), + flow, + generate_random_test_inputs=False, + ) + + def test_mean_scalar(self, flow: TestFlow) -> None: + self._test_op( + MeanModel(), + (torch.tensor([5.0]),), + flow, + ) + self._test_op( + MeanModel(dim=0), + (torch.tensor([5.0]),), + flow, + ) diff --git a/backends/test/suite/operators/test_median.py b/backends/test/suite/operators/test_median.py new file mode 100644 index 00000000000..93823b812ca --- /dev/null +++ b/backends/test/suite/operators/test_median.py @@ -0,0 +1,186 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + +from typing import Optional + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class MedianModel(torch.nn.Module): + def __init__(self, dim: Optional[int] = None, keepdim: bool = False): + super().__init__() + self.dim = dim + self.keepdim = keepdim + + def forward(self, x): + return torch.median(x, dim=self.dim, keepdim=self.keepdim) + + +class MedianValueOnlyModel(torch.nn.Module): + """Model that returns only the median values (not indices) when dim is specified.""" + + def __init__(self, dim: Optional[int] = None, keepdim: bool = False): + super().__init__() + self.dim = dim + self.keepdim = keepdim + + def forward(self, x): + if self.dim is not None: + return torch.median(x, dim=self.dim, keepdim=self.keepdim)[0] + else: + return torch.median(x) + + +@operator_test +class Median(OperatorTest): + @dtype_test + def test_median_dtype(self, flow: TestFlow, dtype) -> None: + # Test with different dtypes (global reduction) + model = MedianValueOnlyModel().to(dtype) + self._test_op(model, (torch.rand(10, 10).to(dtype),), flow) + + def test_median_basic(self, flow: TestFlow) -> None: + # Basic test with default parameters (global reduction) + self._test_op(MedianValueOnlyModel(), (torch.randn(10, 10),), flow) + + def test_median_dim(self, flow: TestFlow) -> None: + # Test with different dimensions (values only) + + # 2D tensor, dim=0 + self._test_op(MedianValueOnlyModel(dim=0), (torch.randn(5, 10),), flow) + + # 2D tensor, dim=1 + self._test_op(MedianValueOnlyModel(dim=1), (torch.randn(5, 10),), flow) + + # 3D tensor, dim=0 + self._test_op(MedianValueOnlyModel(dim=0), (torch.randn(3, 4, 5),), flow) + + # 3D tensor, dim=1 + self._test_op(MedianValueOnlyModel(dim=1), (torch.randn(3, 4, 5),), flow) + + # 3D tensor, dim=2 + self._test_op(MedianValueOnlyModel(dim=2), (torch.randn(3, 4, 5),), flow) + + # 4D tensor, dim=1 + self._test_op(MedianValueOnlyModel(dim=1), (torch.randn(2, 3, 4, 5),), flow) + + # Negative dim (last dimension) + self._test_op(MedianValueOnlyModel(dim=-1), (torch.randn(3, 4, 5),), flow) + + # Negative dim (second-to-last dimension) + self._test_op(MedianValueOnlyModel(dim=-2), (torch.randn(3, 4, 5),), flow) + + def test_median_with_indices(self, flow: TestFlow) -> None: + # Test with different dimensions (values and indices) + + # 2D tensor, dim=0 + self._test_op(MedianModel(dim=0), (torch.randn(5, 10),), flow) + + # 2D tensor, dim=1 + self._test_op(MedianModel(dim=1), (torch.randn(5, 10),), flow) + + # 3D tensor, dim=0 + self._test_op(MedianModel(dim=0), (torch.randn(3, 4, 5),), flow) + + # 3D tensor, dim=1 + self._test_op(MedianModel(dim=1), (torch.randn(3, 4, 5),), flow) + + # 3D tensor, dim=2 + self._test_op(MedianModel(dim=2), (torch.randn(3, 4, 5),), flow) + + # 4D tensor, dim=1 + self._test_op(MedianModel(dim=1), (torch.randn(2, 3, 4, 5),), flow) + + # Negative dim (last dimension) + self._test_op(MedianModel(dim=-1), (torch.randn(3, 4, 5),), flow) + + # Negative dim (second-to-last dimension) + self._test_op(MedianModel(dim=-2), (torch.randn(3, 4, 5),), flow) + + def test_median_keepdim(self, flow: TestFlow) -> None: + # Test with keepdim=True (values only) + + # 2D tensor, dim=0, keepdim=True + self._test_op( + MedianValueOnlyModel(dim=0, keepdim=True), (torch.randn(5, 10),), flow + ) + + # 2D tensor, dim=1, keepdim=True + self._test_op( + MedianValueOnlyModel(dim=1, keepdim=True), (torch.randn(5, 10),), flow + ) + + # 3D tensor, dim=1, keepdim=True + self._test_op( + MedianValueOnlyModel(dim=1, keepdim=True), (torch.randn(3, 4, 5),), flow + ) + + # 4D tensor, dim=2, keepdim=True + self._test_op( + MedianValueOnlyModel(dim=2, keepdim=True), (torch.randn(2, 3, 4, 5),), flow + ) + + def test_median_keepdim_with_indices(self, flow: TestFlow) -> None: + # Test with keepdim=True (values and indices) + + # 2D tensor, dim=0, keepdim=True + self._test_op(MedianModel(dim=0, keepdim=True), (torch.randn(5, 10),), flow) + + # 2D tensor, dim=1, keepdim=True + self._test_op(MedianModel(dim=1, keepdim=True), (torch.randn(5, 10),), flow) + + # 3D tensor, dim=1, keepdim=True + self._test_op(MedianModel(dim=1, keepdim=True), (torch.randn(3, 4, 5),), flow) + + # 4D tensor, dim=2, keepdim=True + self._test_op( + MedianModel(dim=2, keepdim=True), (torch.randn(2, 3, 4, 5),), flow + ) + + def test_median_shapes(self, flow: TestFlow) -> None: + # Test with different tensor shapes (global reduction) + + # 1D tensor + self._test_op(MedianValueOnlyModel(), (torch.randn(20),), flow) + + # 2D tensor + self._test_op(MedianValueOnlyModel(), (torch.randn(5, 10),), flow) + + # 3D tensor + self._test_op(MedianValueOnlyModel(), (torch.randn(3, 4, 5),), flow) + + # 4D tensor + self._test_op(MedianValueOnlyModel(), (torch.randn(2, 3, 4, 5),), flow) + + # 5D tensor + self._test_op(MedianValueOnlyModel(), (torch.randn(2, 2, 3, 4, 5),), flow) + + def test_median_edge_cases(self, flow: TestFlow) -> None: + # Tensor with NaN (NaN should be propagated) + x = torch.tensor([[1.0, float("nan"), 3.0], [4.0, 5.0, float("nan")]]) + self._test_op( + MedianValueOnlyModel(), (x,), flow, generate_random_test_inputs=False + ) + self._test_op( + MedianValueOnlyModel(dim=0), (x,), flow, generate_random_test_inputs=False + ) + self._test_op( + MedianValueOnlyModel(dim=1), (x,), flow, generate_random_test_inputs=False + ) + + def test_median_scalar(self, flow: TestFlow) -> None: + # Test with scalar input (1-element tensor) + self._test_op(MedianValueOnlyModel(), (torch.tensor([5.0]),), flow) + self._test_op(MedianValueOnlyModel(dim=0), (torch.tensor([5.0]),), flow) diff --git a/backends/test/suite/operators/test_neg.py b/backends/test/suite/operators/test_neg.py new file mode 100644 index 00000000000..35c9d851817 --- /dev/null +++ b/backends/test/suite/operators/test_neg.py @@ -0,0 +1,67 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class NegModel(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(self, x): + return torch.neg(x) + + +@operator_test +class TestNeg(OperatorTest): + @dtype_test + def test_neg_dtype(self, flow: TestFlow, dtype) -> None: + # Test with different dtypes + model = NegModel().to(dtype) + self._test_op( + model, + (torch.rand(10, 10).to(dtype) * 2 - 1,), + flow, + generate_random_test_inputs=False, + ) + + def test_neg_shapes(self, flow: TestFlow) -> None: + # Test with different tensor shapes + + # 1D tensor + self._test_op( + NegModel(), (torch.randn(20),), flow, generate_random_test_inputs=False + ) + + # 2D tensor + self._test_op( + NegModel(), (torch.randn(5, 10),), flow, generate_random_test_inputs=False + ) + + # 3D tensor + self._test_op( + NegModel(), (torch.randn(3, 4, 5),), flow, generate_random_test_inputs=False + ) + + def test_neg_edge_cases(self, flow: TestFlow) -> None: + # Test edge cases + + # Tensor with infinity + x = torch.tensor([float("inf"), float("-inf"), 1.0, -1.0]) + self._test_op(NegModel(), (x,), flow, generate_random_test_inputs=False) + + # Tensor with NaN + x = torch.tensor([float("nan"), 1.0, -1.0]) + self._test_op(NegModel(), (x,), flow, generate_random_test_inputs=False) diff --git a/backends/test/suite/operators/test_pow.py b/backends/test/suite/operators/test_pow.py new file mode 100644 index 00000000000..334038d73d3 --- /dev/null +++ b/backends/test/suite/operators/test_pow.py @@ -0,0 +1,146 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class PowModel(torch.nn.Module): + def __init__(self, exponent=None): + super().__init__() + self.exponent = exponent + + def forward(self, x): + if self.exponent is not None: + return torch.pow(x, self.exponent) + return torch.pow(x, 2) # Default to squaring if no exponent provided + + +class PowTensorModel(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(self, x, y): + return torch.pow(x, y) + + +@operator_test +class TestPow(OperatorTest): + @dtype_test + def test_pow_dtype(self, flow: TestFlow, dtype) -> None: + # Test with different dtypes + model = PowModel(2).to(dtype) + # Use positive values to avoid complex results with fractional powers + self._test_op( + model, + (torch.rand(10, 10).to(dtype) + 0.1,), + flow, + generate_random_test_inputs=False, + ) + + def test_pow_scalar_exponents(self, flow: TestFlow) -> None: + # Test with different scalar exponents + + # Power of 0 (should return 1 for all inputs) + self._test_op( + PowModel(0), + (torch.rand(10, 10) + 0.1,), + flow, + generate_random_test_inputs=False, + ) + + # Power of 1 (should return the input unchanged) + self._test_op( + PowModel(1), + (torch.rand(10, 10) + 0.1,), + flow, + generate_random_test_inputs=False, + ) + + # Power of 2 (squaring) + self._test_op( + PowModel(2), + (torch.rand(10, 10) + 0.1,), + flow, + generate_random_test_inputs=False, + ) + + # Power of 3 (cubing) + self._test_op( + PowModel(3), + (torch.rand(10, 10) + 0.1,), + flow, + generate_random_test_inputs=False, + ) + + # Negative power (-1, reciprocal) + self._test_op( + PowModel(-1), + (torch.rand(10, 10) + 0.1,), + flow, + generate_random_test_inputs=False, + ) + + # Fractional power (square root) + self._test_op( + PowModel(0.5), + (torch.rand(10, 10) + 0.1,), + flow, + generate_random_test_inputs=False, + ) + + # Large power + self._test_op( + PowModel(10), + (torch.rand(10, 10) * 0.5 + 0.5,), + flow, + generate_random_test_inputs=False, + ) + + def test_pow_shapes(self, flow: TestFlow) -> None: + # Test with different tensor shapes + model = PowModel(2) # Square the input + + # 1D tensor + self._test_op( + model, (torch.rand(20) + 0.1,), flow, generate_random_test_inputs=False + ) + + # 2D tensor + self._test_op( + model, (torch.rand(5, 10) + 0.1,), flow, generate_random_test_inputs=False + ) + + # 3D tensor + self._test_op( + model, (torch.rand(3, 4, 5) + 0.1,), flow, generate_random_test_inputs=False + ) + + def test_pow_edge_cases(self, flow: TestFlow) -> None: + # Test edge cases + + # 0^0 = 1 (by convention) + x = torch.zeros(1) + y = torch.zeros(1) + self._test_op(PowTensorModel(), (x, y), flow, generate_random_test_inputs=False) + + # Tensor with infinity + x = torch.tensor([float("inf"), 2.0, 3.0]) + y = torch.tensor([2.0, 2.0, 2.0]) + self._test_op(PowTensorModel(), (x, y), flow, generate_random_test_inputs=False) + + # Tensor with NaN + x = torch.tensor([float("nan"), 2.0, 3.0]) + y = torch.tensor([2.0, 2.0, 2.0]) + self._test_op(PowTensorModel(), (x, y), flow, generate_random_test_inputs=False) diff --git a/backends/test/suite/operators/test_round.py b/backends/test/suite/operators/test_round.py new file mode 100644 index 00000000000..ca8e6368d48 --- /dev/null +++ b/backends/test/suite/operators/test_round.py @@ -0,0 +1,125 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class RoundModel(torch.nn.Module): + def __init__(self, decimals=None): + super().__init__() + self.decimals = decimals + + def forward(self, x): + if self.decimals is not None: + return torch.round(x, decimals=self.decimals) + return torch.round(x) + + +@operator_test +class TestRound(OperatorTest): + @dtype_test + def test_round_dtype(self, flow: TestFlow, dtype) -> None: + # Test with different dtypes + model = RoundModel().to(dtype) + self._test_op(model, (torch.rand(10, 10).to(dtype) * 10 - 5,), flow) + + def test_round_shapes(self, flow: TestFlow) -> None: + # Test with different tensor shapes + + # 1D tensor + self._test_op(RoundModel(), (torch.randn(20) * 5,), flow) + + # 2D tensor + self._test_op(RoundModel(), (torch.randn(5, 10) * 5,), flow) + + # 3D tensor + self._test_op(RoundModel(), (torch.randn(3, 4, 5) * 5,), flow) + + def test_round_values(self, flow: TestFlow) -> None: + # Values with specific fractional parts + x = torch.arange(-5, 5, 0.5) # [-5.0, -4.5, -4.0, ..., 4.0, 4.5] + self._test_op(RoundModel(), (x,), flow, generate_random_test_inputs=False) + + def test_round_edge_cases(self, flow: TestFlow) -> None: + # Test edge cases + + # Values exactly halfway between integers (should round to even) + x = torch.tensor([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5]) + self._test_op(RoundModel(), (x,), flow, generate_random_test_inputs=False) + + # Tensor with infinity + x = torch.tensor([float("inf"), float("-inf"), 1.4, -1.4]) + self._test_op(RoundModel(), (x,), flow, generate_random_test_inputs=False) + + # Tensor with NaN + x = torch.tensor([float("nan"), 1.4, -1.4]) + self._test_op(RoundModel(), (x,), flow, generate_random_test_inputs=False) + + # Very large values (where fractional part becomes insignificant) + x = torch.tensor([1e10, 1e10 + 0.4, 1e10 + 0.6]) + self._test_op(RoundModel(), (x,), flow, generate_random_test_inputs=False) + + def test_round_decimals(self, flow: TestFlow) -> None: + # Test with different decimal places + + # Round to 1 decimal place + x = torch.tensor([1.44, 1.45, 1.46, -1.44, -1.45, -1.46]) + self._test_op( + RoundModel(decimals=1), (x,), flow, generate_random_test_inputs=False + ) + + # Round to 2 decimal places + x = torch.tensor([1.444, 1.445, 1.446, -1.444, -1.445, -1.446]) + self._test_op( + RoundModel(decimals=2), (x,), flow, generate_random_test_inputs=False + ) + + # Round to negative decimal places (tens) + x = torch.tensor([14.4, 15.5, 16.6, -14.4, -15.5, -16.6]) + self._test_op( + RoundModel(decimals=-1), (x,), flow, generate_random_test_inputs=False + ) + + # Round to negative decimal places (hundreds) + x = torch.tensor([144.4, 155.5, 166.6, -144.4, -155.5, -166.6]) + self._test_op( + RoundModel(decimals=-2), (x,), flow, generate_random_test_inputs=False + ) + + def test_round_decimals_edge_cases(self, flow: TestFlow) -> None: + # Test edge cases with decimal places + + # Infinity and NaN with various decimal places + x = torch.tensor([float("inf"), float("-inf"), float("nan")]) + self._test_op( + RoundModel(decimals=2), (x,), flow, generate_random_test_inputs=False + ) + self._test_op( + RoundModel(decimals=-2), (x,), flow, generate_random_test_inputs=False + ) + + # Values exactly at the rounding threshold for different decimal places + x = torch.tensor([0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95]) + self._test_op( + RoundModel(decimals=1), (x,), flow, generate_random_test_inputs=False + ) + + # Negative values exactly at the rounding threshold + x = torch.tensor( + [-0.05, -0.15, -0.25, -0.35, -0.45, -0.55, -0.65, -0.75, -0.85, -0.95] + ) + self._test_op( + RoundModel(decimals=1), (x,), flow, generate_random_test_inputs=False + ) diff --git a/backends/test/suite/operators/test_rsqrt.py b/backends/test/suite/operators/test_rsqrt.py new file mode 100644 index 00000000000..175bbcdb2cc --- /dev/null +++ b/backends/test/suite/operators/test_rsqrt.py @@ -0,0 +1,55 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class RsqrtModel(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(self, x): + return torch.rsqrt(x) + + +@operator_test +class TestRsqrt(OperatorTest): + @dtype_test + def test_rsqrt_dtype(self, flow: TestFlow, dtype) -> None: + # Test with different dtypes + model = RsqrtModel().to(dtype) + # Use positive values only for rsqrt to avoid division by zero + self._test_op(model, (torch.rand(10, 10).to(dtype) + 0.01,), flow) + + def test_rsqrt_shapes(self, flow: TestFlow) -> None: + # Test with different tensor shapes + + # 1D tensor + self._test_op(RsqrtModel(), (torch.rand(20) + 0.01,), flow) + + # 2D tensor + self._test_op(RsqrtModel(), (torch.rand(5, 10) + 0.01,), flow) + + # 3D tensor + self._test_op(RsqrtModel(), (torch.rand(3, 4, 5) + 0.01,), flow) + + def test_rsqrt_edge_cases(self, flow: TestFlow) -> None: + # Tensor with infinity + x = torch.tensor([float("inf"), 1.0, 4.0]) + self._test_op(RsqrtModel(), (x,), flow, generate_random_test_inputs=False) + + # Tensor with NaN + x = torch.tensor([float("nan"), 1.0, 4.0]) + self._test_op(RsqrtModel(), (x,), flow, generate_random_test_inputs=False) diff --git a/backends/test/suite/operators/test_sqrt.py b/backends/test/suite/operators/test_sqrt.py new file mode 100644 index 00000000000..c3874dcb209 --- /dev/null +++ b/backends/test/suite/operators/test_sqrt.py @@ -0,0 +1,57 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class SqrtModel(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(self, x): + return torch.sqrt(x) + + +@operator_test +class TestSqrt(OperatorTest): + @dtype_test + def test_sqrt_dtype(self, flow: TestFlow, dtype) -> None: + # Test with different dtypes + model = SqrtModel().to(dtype) + # Use non-negative values only for sqrt + self._test_op(model, (torch.rand(10, 10).to(dtype),), flow) + + def test_sqrt_shapes(self, flow: TestFlow) -> None: + # Test with different tensor shapes + + # 1D tensor + self._test_op(SqrtModel(), (torch.rand(20),), flow) + + # 2D tensor + self._test_op(SqrtModel(), (torch.rand(5, 10),), flow) + + # 3D tensor + self._test_op(SqrtModel(), (torch.rand(3, 4, 5),), flow) + + def test_sqrt_edge_cases(self, flow: TestFlow) -> None: + # Test edge cases + + # Tensor with infinity + x = torch.tensor([float("inf"), 1.0, 4.0]) + self._test_op(SqrtModel(), (x,), flow, generate_random_test_inputs=False) + + # Tensor with NaN + x = torch.tensor([float("nan"), 1.0, 4.0]) + self._test_op(SqrtModel(), (x,), flow, generate_random_test_inputs=False) diff --git a/backends/test/suite/operators/test_square.py b/backends/test/suite/operators/test_square.py new file mode 100644 index 00000000000..52cd739bf9f --- /dev/null +++ b/backends/test/suite/operators/test_square.py @@ -0,0 +1,64 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class SquareModel(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(self, x): + return torch.square(x) + + +@operator_test +class TestSquare(OperatorTest): + @dtype_test + def test_square_dtype(self, flow: TestFlow, dtype) -> None: + # Test with different dtypes + model = SquareModel().to(dtype) + self._test_op(model, (torch.rand(10, 10).to(dtype) * 2 - 1,), flow) + + def test_square_shapes(self, flow: TestFlow) -> None: + # Test with different tensor shapes + + # 1D tensor + self._test_op(SquareModel(), (torch.randn(20),), flow) + + # 2D tensor + self._test_op(SquareModel(), (torch.randn(5, 10),), flow) + + # 3D tensor + self._test_op(SquareModel(), (torch.randn(3, 4, 5),), flow) + + def test_square_edge_cases(self, flow: TestFlow) -> None: + # Test edge cases + + # Tensor with infinity + x = torch.tensor([float("inf"), float("-inf"), 1.0, -1.0]) + self._test_op(SquareModel(), (x,), flow, generate_random_test_inputs=False) + + # Tensor with NaN + x = torch.tensor([float("nan"), 1.0, -1.0]) + self._test_op(SquareModel(), (x,), flow, generate_random_test_inputs=False) + + # Very large values (close to overflow for some dtypes) + x = torch.tensor([1e10, -1e10]) + self._test_op(SquareModel(), (x,), flow, generate_random_test_inputs=False) + + # Very small values (close to underflow) + x = torch.tensor([1e-10, -1e-10]) + self._test_op(SquareModel(), (x,), flow, generate_random_test_inputs=False) diff --git a/backends/test/suite/operators/test_trunc.py b/backends/test/suite/operators/test_trunc.py new file mode 100644 index 00000000000..1d6d18817bd --- /dev/null +++ b/backends/test/suite/operators/test_trunc.py @@ -0,0 +1,70 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-unsafe + +import torch +from executorch.backends.test.suite.flow import TestFlow + +from executorch.backends.test.suite.operators import ( + dtype_test, + operator_test, + OperatorTest, +) + + +class TruncModel(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(self, x): + return torch.trunc(x) + + +@operator_test +class TestTrunc(OperatorTest): + @dtype_test + def test_trunc_dtype(self, flow: TestFlow, dtype) -> None: + # Test with different dtypes + model = TruncModel().to(dtype) + self._test_op(model, (torch.rand(10, 10).to(dtype) * 10 - 5,), flow) + + def test_trunc_shapes(self, flow: TestFlow) -> None: + # Test with different tensor shapes + + # 1D tensor + self._test_op(TruncModel(), (torch.randn(20) * 5,), flow) + + # 2D tensor + self._test_op(TruncModel(), (torch.randn(5, 10) * 5,), flow) + + # 3D tensor + self._test_op(TruncModel(), (torch.randn(3, 4, 5) * 5,), flow) + + def test_trunc_edge_cases(self, flow: TestFlow) -> None: + # Test edge cases + + # Integer values (should remain unchanged) + self._test_op( + TruncModel(), + (torch.arange(-5, 6).float(),), + flow, + generate_random_test_inputs=False, + ) + + # Values with different fractional parts + x = torch.tensor( + [-2.9, -2.5, -2.1, -0.9, -0.5, -0.1, 0.0, 0.1, 0.5, 0.9, 2.1, 2.5, 2.9] + ) + self._test_op(TruncModel(), (x,), flow, generate_random_test_inputs=False) + + # Tensor with infinity + x = torch.tensor([float("inf"), float("-inf"), 1.4, -1.4]) + self._test_op(TruncModel(), (x,), flow, generate_random_test_inputs=False) + + # Tensor with NaN + x = torch.tensor([float("nan"), 1.4, -1.4]) + self._test_op(TruncModel(), (x,), flow, generate_random_test_inputs=False)