xref: /aosp_15_r20/external/executorch/kernels/test/op_minimum_test.cpp (revision 523fa7a60841cd1ecfb9cc4201f1ca8b03ed023a)
1 /*
2  * Copyright (c) Meta Platforms, Inc. and affiliates.
3  * All rights reserved.
4  *
5  * This source code is licensed under the BSD-style license found in the
6  * LICENSE file in the root directory of this source tree.
7  */
8 
9 #include <executorch/kernels/test/FunctionHeaderWrapper.h> // Declares the operator
10 #include <executorch/kernels/test/TestUtil.h>
11 #include <executorch/kernels/test/supported_features.h>
12 #include <executorch/runtime/core/exec_aten/exec_aten.h>
13 #include <executorch/runtime/core/exec_aten/testing_util/tensor_factory.h>
14 #include <executorch/runtime/core/exec_aten/testing_util/tensor_util.h>
15 
16 #include <gtest/gtest.h>
17 
18 using namespace ::testing;
19 using exec_aten::ScalarType;
20 using exec_aten::Tensor;
21 using torch::executor::testing::TensorFactory;
22 
23 class OpMinimumOutTest : public OperatorTest {
24  protected:
op_minimum_out(const Tensor & self,const Tensor & other,Tensor & out)25   Tensor& op_minimum_out(const Tensor& self, const Tensor& other, Tensor& out) {
26     return torch::executor::aten::minimum_outf(context_, self, other, out);
27   }
28 
29   // Common testing for minimum operator
30   template <ScalarType DTYPE>
test_minimum_out_same_size()31   void test_minimum_out_same_size() {
32     TensorFactory<DTYPE> tf;
33     const std::vector<int32_t> sizes = {2, 2};
34 
35     // Destination for the minimum operator.
36     Tensor out = tf.zeros(sizes);
37 
38     op_minimum_out(
39         tf.make(sizes, /*data=*/{1, 2, 4, 8}),
40         tf.make(sizes, /*data=*/{3, 0, 4, 9}),
41         out);
42 
43     // Check that it matches to the expected output.
44     EXPECT_TENSOR_EQ(out, tf.make(sizes, /*data=*/{1, 0, 4, 8}));
45   }
46 };
47 
TEST_F(OpMinimumOutTest,ByteTensors)48 TEST_F(OpMinimumOutTest, ByteTensors) {
49   test_minimum_out_same_size<ScalarType::Byte>();
50 }
51 
TEST_F(OpMinimumOutTest,CharTensors)52 TEST_F(OpMinimumOutTest, CharTensors) {
53   test_minimum_out_same_size<ScalarType::Char>();
54 }
55 
TEST_F(OpMinimumOutTest,ShortTensors)56 TEST_F(OpMinimumOutTest, ShortTensors) {
57   test_minimum_out_same_size<ScalarType::Short>();
58 }
59 
TEST_F(OpMinimumOutTest,IntTensors)60 TEST_F(OpMinimumOutTest, IntTensors) {
61   test_minimum_out_same_size<ScalarType::Int>();
62 }
63 
TEST_F(OpMinimumOutTest,LongTensors)64 TEST_F(OpMinimumOutTest, LongTensors) {
65   test_minimum_out_same_size<ScalarType::Long>();
66 }
67 
TEST_F(OpMinimumOutTest,HalfTensors)68 TEST_F(OpMinimumOutTest, HalfTensors) {
69   test_minimum_out_same_size<ScalarType::Half>();
70 }
71 
TEST_F(OpMinimumOutTest,FloatTensors)72 TEST_F(OpMinimumOutTest, FloatTensors) {
73   test_minimum_out_same_size<ScalarType::Float>();
74 }
75 
TEST_F(OpMinimumOutTest,DoubleTensors)76 TEST_F(OpMinimumOutTest, DoubleTensors) {
77   test_minimum_out_same_size<ScalarType::Double>();
78 }
79 
TEST_F(OpMinimumOutTest,BothScalarTensors)80 TEST_F(OpMinimumOutTest, BothScalarTensors) {
81   // Checks the case when both cases are scalar.
82   TensorFactory<ScalarType::Float> tf;
83   const std::vector<int32_t> sizes = {1, 1};
84   Tensor out = tf.zeros(sizes);
85   op_minimum_out(tf.make(sizes, {1.2}), tf.make(sizes, {3.5}), out);
86   EXPECT_TENSOR_EQ(out, tf.make(sizes, {1.2}));
87 }
88 
TEST_F(OpMinimumOutTest,LeftScalarTensor)89 TEST_F(OpMinimumOutTest, LeftScalarTensor) {
90   // Checks the case where one of the tensor is a singleton tensor.
91 
92   TensorFactory<ScalarType::Float> tf;
93   const std::vector<int32_t> sizes_1 = {1, 1};
94   const std::vector<int32_t> sizes_2 = {2, 2};
95   Tensor out1 = tf.zeros(sizes_2);
96   Tensor out2 = tf.zeros(sizes_2);
97 
98   auto a = tf.make(sizes_1, /*data=*/{1.0});
99   auto b = tf.make(sizes_2, /*data=*/{3.5, -1.0, 0.0, 5.5});
100 
101   // Case 1 : First argument is singleton.
102   op_minimum_out(a, b, out1);
103   EXPECT_TENSOR_EQ(out1, tf.make(sizes_2, {1.0, -1.0, 0.0, 1.0}));
104 
105   // Case 2: Second argument is singleton
106   op_minimum_out(b, a, out2);
107   EXPECT_TENSOR_EQ(out2, tf.make(sizes_2, {1.0, -1.0, 0.0, 1.0}));
108 }
109 
TEST_F(OpMinimumOutTest,MismatchedInputShapesDies)110 TEST_F(OpMinimumOutTest, MismatchedInputShapesDies) {
111   // First and second argument have different shape
112   TensorFactory<ScalarType::Float> tf;
113   Tensor out = tf.zeros({2, 2});
114 
115   ET_EXPECT_KERNEL_FAILURE(
116       context_, op_minimum_out(tf.ones({2, 2}), tf.ones({3, 3}), out));
117 }
118 
TEST_F(OpMinimumOutTest,MismatchedOutputShapesDies)119 TEST_F(OpMinimumOutTest, MismatchedOutputShapesDies) {
120   // First and second argument have same shape, but output has different shape.
121   TensorFactory<ScalarType::Float> tf;
122   Tensor out = tf.zeros({3, 3});
123 
124   ET_EXPECT_KERNEL_FAILURE(
125       context_, op_minimum_out(tf.ones({2, 2}), tf.ones({3, 3}), out));
126 }
127 
TEST_F(OpMinimumOutTest,MismatchedOutputShapeWithSingletonDies)128 TEST_F(OpMinimumOutTest, MismatchedOutputShapeWithSingletonDies) {
129   if (torch::executor::testing::SupportedFeatures::get()->is_aten) {
130     GTEST_SKIP() << "ATen kernel can handle mismatched output shape";
131   }
132   // First argument is singleton but second and output has different shape.
133   TensorFactory<ScalarType::Float> tf;
134   Tensor out = tf.zeros({4, 4});
135 
136   ET_EXPECT_KERNEL_FAILURE(
137       context_, op_minimum_out(tf.ones({1, 1}), tf.ones({3, 3}), out));
138 }
139 
140 /* %python
141 import torch
142 torch.manual_seed(0)
143 x = torch.rand(3, 2)
144 y = torch.rand(3, 2)
145 res = torch.minimum(x, y)
146 op = "op_minimum_out"
147 dtype = "ScalarType::Float"
148 check = "EXPECT_TENSOR_EQ" */
149 
TEST_F(OpMinimumOutTest,DynamicShapeUpperBoundSameAsExpected)150 TEST_F(OpMinimumOutTest, DynamicShapeUpperBoundSameAsExpected) {
151   /* %python
152   out_args = "{3, 2}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND"
153   %rewrite(binary_op) */
154 
155   TensorFactory<ScalarType::Float> tf;
156 
157   Tensor x = tf.make(
158       {3, 2},
159       {0.49625658988952637,
160        0.7682217955589294,
161        0.08847743272781372,
162        0.13203048706054688,
163        0.30742281675338745,
164        0.6340786814689636});
165   Tensor y = tf.make(
166       {3, 2},
167       {0.4900934100151062,
168        0.8964447379112244,
169        0.455627977848053,
170        0.6323062777519226,
171        0.3488934636116028,
172        0.40171730518341064});
173   Tensor expected = tf.make(
174       {3, 2},
175       {0.4900934100151062,
176        0.7682217955589294,
177        0.08847743272781372,
178        0.13203048706054688,
179        0.30742281675338745,
180        0.40171730518341064});
181 
182   Tensor out =
183       tf.zeros({3, 2}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND);
184   op_minimum_out(x, y, out);
185   EXPECT_TENSOR_EQ(out, expected);
186 }
187 
TEST_F(OpMinimumOutTest,DynamicShapeUpperBoundLargerThanExpected)188 TEST_F(OpMinimumOutTest, DynamicShapeUpperBoundLargerThanExpected) {
189   if (!torch::executor::testing::SupportedFeatures::get()->output_resize) {
190     GTEST_SKIP() << "Dynamic shape not supported";
191   }
192   /* %python
193   out_args = "{10, 10}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND"
194   %rewrite(binary_op) */
195 
196   TensorFactory<ScalarType::Float> tf;
197 
198   Tensor x = tf.make(
199       {3, 2},
200       {0.49625658988952637,
201        0.7682217955589294,
202        0.08847743272781372,
203        0.13203048706054688,
204        0.30742281675338745,
205        0.6340786814689636});
206   Tensor y = tf.make(
207       {3, 2},
208       {0.4900934100151062,
209        0.8964447379112244,
210        0.455627977848053,
211        0.6323062777519226,
212        0.3488934636116028,
213        0.40171730518341064});
214   Tensor expected = tf.make(
215       {3, 2},
216       {0.4900934100151062,
217        0.7682217955589294,
218        0.08847743272781372,
219        0.13203048706054688,
220        0.30742281675338745,
221        0.40171730518341064});
222 
223   Tensor out =
224       tf.zeros({10, 10}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND);
225   op_minimum_out(x, y, out);
226   EXPECT_TENSOR_EQ(out, expected);
227 }
228 
TEST_F(OpMinimumOutTest,DynamicShapeUnbound)229 TEST_F(OpMinimumOutTest, DynamicShapeUnbound) {
230   if (!torch::executor::testing::SupportedFeatures::get()->output_resize) {
231     GTEST_SKIP() << "Dynamic shape not supported";
232   }
233   /* %python
234   out_args = "{1, 1}, torch::executor::TensorShapeDynamism::DYNAMIC_UNBOUND"
235   %rewrite(binary_op) */
236 
237   TensorFactory<ScalarType::Float> tf;
238 
239   Tensor x = tf.make(
240       {3, 2},
241       {0.49625658988952637,
242        0.7682217955589294,
243        0.08847743272781372,
244        0.13203048706054688,
245        0.30742281675338745,
246        0.6340786814689636});
247   Tensor y = tf.make(
248       {3, 2},
249       {0.4900934100151062,
250        0.8964447379112244,
251        0.455627977848053,
252        0.6323062777519226,
253        0.3488934636116028,
254        0.40171730518341064});
255   Tensor expected = tf.make(
256       {3, 2},
257       {0.4900934100151062,
258        0.7682217955589294,
259        0.08847743272781372,
260        0.13203048706054688,
261        0.30742281675338745,
262        0.40171730518341064});
263 
264   Tensor out =
265       tf.zeros({1, 1}, torch::executor::TensorShapeDynamism::DYNAMIC_UNBOUND);
266   op_minimum_out(x, y, out);
267   EXPECT_TENSOR_EQ(out, expected);
268 }
269