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