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 #include <executorch/runtime/core/exec_aten/util/scalar_type_util.h>
16 #include <executorch/test/utils/DeathTest.h>
17 #include <gtest/gtest.h>
18 #include <cmath>
19
20 using namespace ::testing;
21 using exec_aten::ArrayRef;
22 using exec_aten::ScalarType;
23 using exec_aten::Tensor;
24 using torch::executor::testing::TensorFactory;
25
26 class OpMinOutTest : public OperatorTest {
27 protected:
op_min_dim_min(const Tensor & in,int64_t dim,bool keepdim,Tensor & min,Tensor & min_indices)28 std::tuple<Tensor&, Tensor&> op_min_dim_min(
29 const Tensor& in,
30 int64_t dim,
31 bool keepdim,
32 Tensor& min,
33 Tensor& min_indices) {
34 return torch::executor::aten::min_outf(
35 context_, in, dim, keepdim, min, min_indices);
36 }
37
38 template <ScalarType IN_DTYPE>
test_min_out_invalid_dimensions()39 void test_min_out_invalid_dimensions() {
40 TensorFactory<IN_DTYPE> tf_in;
41 TensorFactory<ScalarType::Long> tf_long;
42
43 Tensor in = tf_in.ones(/*sizes=*/{2, 3, 4});
44 Tensor min = tf_in.zeros({2, 3, 2});
45 Tensor min_indices = tf_in.zeros({2, 3});
46
47 // output tensor dim mismatch
48 ET_EXPECT_KERNEL_FAILURE(
49 context_,
50 op_min_dim_min(in, /*dim=*/-1, /*keepdim=*/true, min, min_indices));
51
52 // output tensor shape incorrect: size of dimension: dim should be 1
53 min = tf_in.zeros({2, 3, 2});
54 min_indices = tf_in.zeros({2, 3, 2});
55 ET_EXPECT_KERNEL_FAILURE(
56 context_,
57 op_min_dim_min(in, /*dim=*/-1, /*keepdim=*/true, min, min_indices));
58
59 // output tensor shape should be squeezed when keepdim is false
60 min = tf_in.zeros({2, 3, 1});
61 min_indices = tf_in.zeros({2, 3, 1});
62 ET_EXPECT_KERNEL_FAILURE(
63 context_,
64 op_min_dim_min(in, /*dim=*/-1, /*keepdim=*/false, min, min_indices));
65
66 // invalid dim
67 min = tf_in.zeros({2, 3, 1});
68 min_indices = tf_in.zeros({2, 3, 1});
69 ET_EXPECT_KERNEL_FAILURE(
70 context_,
71 op_min_dim_min(in, /*dim=*/3, /*keepdim=*/true, min, min_indices));
72 }
73
test_dynamic_shape(const std::vector<int32_t> & out_shape,enum torch::executor::TensorShapeDynamism dynamism)74 void test_dynamic_shape(
75 const std::vector<int32_t>& out_shape,
76 enum torch::executor::TensorShapeDynamism dynamism) {
77 /* %python
78 %rewrite(min_template) */
79
80 TensorFactory<ScalarType::Float> tf;
81 TensorFactory<ScalarType::Long> tfl;
82
83 // clang-format off
84 Tensor input = tf.make(
85 {2, 3, 4},
86 {0.49, 0.76, 0.08, 0.13,
87 0.30, 0.63, 0.49, 0.89,
88 0.45, 0.63, 0.34, 0.40,
89
90 0.02, 0.16, 0.29, 0.51,
91 0.69, 0.80, 0.16, 0.28,
92 0.68, 0.91, 0.39, 0.87});
93 Tensor expected_min = tf.make(
94 {2, 4},
95 {0.30, 0.63, 0.08, 0.13,
96 0.02, 0.16, 0.16, 0.28});
97 // clang-format on
98
99 Tensor expected_min_indices = tfl.make({2, 4}, {1, 1, 0, 0, 0, 0, 1, 1});
100 Tensor min = tf.zeros(out_shape, dynamism);
101 Tensor min_indices = tfl.zeros(out_shape, dynamism);
102
103 op_min_dim_min(input, 1, false, min, min_indices);
104 EXPECT_TENSOR_EQ(min, expected_min);
105 EXPECT_TENSOR_EQ(min_indices, expected_min_indices);
106 }
107
108 template <ScalarType IN_DTYPE>
test_min_out_dtype()109 void test_min_out_dtype() {
110 TensorFactory<IN_DTYPE> tf_in;
111 TensorFactory<ScalarType::Long> tf_long;
112 // clang-format off
113 Tensor in = tf_in.make(
114 {2, 3, 4},
115 {
116 0, 1, 2, 4,
117 4, 2, 1, 0,
118 1, 0, 4, 2,
119
120 4, 2, 1, 0,
121 0, 1, 2, 4,
122 1, 0, 4, 2,
123 });
124 // clang-format on
125
126 Tensor min = tf_in.zeros({2, 4});
127 Tensor min_indices = tf_long.zeros({2, 4});
128 op_min_dim_min(in, /*dim=*/1, /*keepdim=*/false, min, min_indices);
129 // clang-format off
130 EXPECT_TENSOR_CLOSE(min, tf_in.make(
131 {2, 4},
132 {
133 0, 0, 1, 0,
134 0, 0, 1, 0
135 }));
136
137 EXPECT_TENSOR_EQ(min_indices, tf_long.make(
138 {2, 4},
139 {
140 0, 2, 1, 1,
141 1, 2, 0, 0
142 }));
143 // clang-format on
144
145 // negative dim should work
146 op_min_dim_min(in, /*dim=*/-2, /*keepdim=*/false, min, min_indices);
147 // clang-format off
148 EXPECT_TENSOR_CLOSE(min, tf_in.make(
149 {2, 4},
150 {
151 0, 0, 1, 0,
152 0, 0, 1, 0
153 }));
154 EXPECT_TENSOR_EQ(min_indices, tf_long.make(
155 {2, 4},
156 {
157 0, 2, 1, 1,
158 1, 2, 0, 0
159 }));
160 // clang-format on
161
162 // keepdim should work
163 min = tf_in.zeros({2, 3, 1});
164 min_indices = tf_long.zeros({2, 3, 1});
165 op_min_dim_min(in, /*dim=*/-1, /*keepdim=*/true, min, min_indices);
166 EXPECT_TENSOR_CLOSE(min, tf_in.make({2, 3, 1}, {0, 0, 0, 0, 0, 0}));
167 EXPECT_TENSOR_EQ(min_indices, tf_long.make({2, 3, 1}, {0, 3, 1, 3, 0, 1}));
168 }
169 };
170
171 template <>
test_min_out_dtype()172 void OpMinOutTest::test_min_out_dtype<ScalarType::Bool>() {
173 TensorFactory<ScalarType::Bool> tf_bool;
174 TensorFactory<ScalarType::Long> tf_long;
175 // clang-format off
176 Tensor in = tf_bool.make(
177 {2, 3, 4},
178 {
179 true, false, true, false,
180 false, false, false, false,
181 false, true, true, false,
182
183 false, false, true, false,
184 false, false, false, true,
185 true, true, true, true,
186 });
187 // clang-format on
188
189 Tensor min = tf_bool.zeros({2, 3, 1});
190 Tensor min_indices = tf_long.zeros({2, 3, 1});
191
192 // +/-inf and nan should work
193 op_min_dim_min(in, /*dim=*/-1, /*keepdim=*/true, min, min_indices);
194 // clang-format off
195 EXPECT_TENSOR_CLOSE(
196 min, tf_bool.make(
197 {2, 3, 1},
198 {
199 false,
200 false,
201 false,
202
203 false,
204 false,
205 true
206 }));
207 EXPECT_TENSOR_EQ(min_indices, tf_long.make(
208 {2, 3, 1},
209 {
210 1,
211 0,
212 0,
213
214 0,
215 0,
216 0
217 }));
218 // clang-format on
219 }
220
221 class OpMinUnaryOutTest : public OperatorTest {
222 protected:
op_min_unary_out(const Tensor & self,Tensor & out)223 Tensor& op_min_unary_out(const Tensor& self, Tensor& out) {
224 return torch::executor::aten::min_outf(context_, self, out);
225 }
226
227 template <ScalarType IN_DTYPE>
test_min_unary_out_dtype()228 void test_min_unary_out_dtype() {
229 TensorFactory<IN_DTYPE> tf_in;
230 TensorFactory<ScalarType::Float> tf_out;
231 Tensor input = tf_in.make({2, 3}, {7, 1, 3, 4, 4, 2});
232 Tensor out = tf_out.zeros({});
233 Tensor expected = tf_out.make({}, {1});
234 op_min_unary_out(input, out);
235 EXPECT_TENSOR_CLOSE(out, expected);
236 }
237
238 template <typename CTYPE, ScalarType IN_DTYPE>
test_min_unary_out_empty_integer()239 void test_min_unary_out_empty_integer() {
240 TensorFactory<IN_DTYPE> tf_in;
241 Tensor input = tf_in.make({2, 0}, {});
242 Tensor out = tf_in.zeros({});
243 Tensor expected = tf_in.make({}, {std::numeric_limits<CTYPE>::max()});
244 op_min_unary_out(input, out);
245 EXPECT_TENSOR_CLOSE(out, expected);
246 }
247
248 template <typename CTYPE, ScalarType IN_DTYPE>
test_min_unary_out_empty_floating()249 void test_min_unary_out_empty_floating() {
250 TensorFactory<IN_DTYPE> tf_in;
251 Tensor input = tf_in.make({2, 0}, {});
252 Tensor out = tf_in.zeros({});
253 Tensor expected = tf_in.make({}, {INFINITY});
254 op_min_unary_out(input, out);
255 EXPECT_TENSOR_CLOSE(out, expected);
256 }
257 };
258
TEST_F(OpMinUnaryOutTest,AllRealHBF16InputFloatOutputPasses)259 TEST_F(OpMinUnaryOutTest, AllRealHBF16InputFloatOutputPasses) {
260 #define TEST_ENTRY(ctype, dtype) test_min_unary_out_dtype<ScalarType::dtype>();
261 ET_FORALL_REALHBF16_TYPES(TEST_ENTRY);
262 #undef TEST_ENTRY
263 }
264
TEST_F(OpMinUnaryOutTest,EmptyIntegerInput)265 TEST_F(OpMinUnaryOutTest, EmptyIntegerInput) {
266 #define TEST_ENTRY(ctype, dtype) \
267 test_min_unary_out_empty_integer<ctype, ScalarType::dtype>();
268 ET_FORALL_INT_TYPES(TEST_ENTRY);
269 #undef TEST_ENTRY
270 }
271
TEST_F(OpMinUnaryOutTest,EmptyFloatingInput)272 TEST_F(OpMinUnaryOutTest, EmptyFloatingInput) {
273 #define TEST_ENTRY(ctype, dtype) \
274 test_min_unary_out_empty_floating<ctype, ScalarType::dtype>();
275 ET_FORALL_FLOATHBF16_TYPES(TEST_ENTRY);
276 #undef TEST_ENTRY
277 }
278
TEST_F(OpMinOutTest,MismatchedDimensionsDies)279 TEST_F(OpMinOutTest, MismatchedDimensionsDies) {
280 if (torch::executor::testing::SupportedFeatures::get()->is_aten) {
281 GTEST_SKIP() << "ATen kernel test fails";
282 }
283 #define TEST_ENTRY(ctype, dtype) \
284 test_min_out_invalid_dimensions<ScalarType::dtype>();
285 ET_FORALL_REAL_TYPES_AND(Bool, TEST_ENTRY);
286 #undef TEST_ENTRY
287 }
288
TEST_F(OpMinOutTest,MismatchedDTypesDies)289 TEST_F(OpMinOutTest, MismatchedDTypesDies) {
290 if (torch::executor::testing::SupportedFeatures::get()->is_aten) {
291 GTEST_SKIP() << "ATen kernel test fails";
292 }
293 TensorFactory<ScalarType::Float> tf_float;
294 TensorFactory<ScalarType::Long> tf_long;
295
296 Tensor in = tf_float.ones(/*sizes=*/{2, 3, 4});
297 Tensor min = tf_long.zeros({2, 3, 1});
298 Tensor min_indices = tf_long.zeros({2, 3, 1});
299
300 // dtype of in and min should match
301 ET_EXPECT_KERNEL_FAILURE(
302 context_,
303 op_min_dim_min(in, /*dim=*/-1, /*keepdim=*/true, min, min_indices));
304
305 // min_value tensor should have long as dtype
306 min = tf_float.zeros({2, 3, 1});
307 min_indices = tf_float.zeros({2, 3, 1});
308 ET_EXPECT_KERNEL_FAILURE(
309 context_,
310 op_min_dim_min(in, /*dim=*/-1, /*keepdim=*/true, min, min_indices));
311 }
312
TEST_F(OpMinOutTest,AllRealInputLongOutputPasses)313 TEST_F(OpMinOutTest, AllRealInputLongOutputPasses) {
314 #define TEST_ENTRY(ctype, dtype) test_min_out_dtype<ScalarType::dtype>();
315 ET_FORALL_REAL_TYPES_AND(Bool, TEST_ENTRY);
316 #undef TEST_ENTRY
317 }
318
TEST_F(OpMinOutTest,InfinityAndNANTest)319 TEST_F(OpMinOutTest, InfinityAndNANTest) {
320 TensorFactory<ScalarType::Float> tf_float;
321 TensorFactory<ScalarType::Long> tf_long;
322 // clang-format off
323 Tensor in = tf_float.make(
324 {2, 3, 4},
325 {
326 0, 1, 2, INFINITY,
327 INFINITY, -INFINITY, 1, 0,
328 NAN, INFINITY, -INFINITY, 2,
329
330 NAN, NAN, 1, 0,
331 0, INFINITY, NAN, 4,
332 1, NAN, 3.14, 2,
333 });
334 // clang-format on
335
336 Tensor min = tf_float.zeros({2, 3, 1});
337 Tensor min_indices = tf_long.zeros({2, 3, 1});
338
339 // +/-inf and nan should work
340 op_min_dim_min(in, /*dim=*/-1, /*keepdim=*/true, min, min_indices);
341 EXPECT_TENSOR_CLOSE(
342 min, tf_float.make({2, 3, 1}, {0, -INFINITY, NAN, NAN, NAN, NAN}));
343 // clang-format off
344 EXPECT_TENSOR_EQ(min_indices, tf_long.make(
345 {2, 3, 1},
346 {
347 0,
348 1,
349 0,
350
351 0,
352 2,
353 1
354 }));
355 // clang-format on
356 }
357
TEST_F(OpMinOutTest,DynamicShapeUpperBoundSameAsExpected)358 TEST_F(OpMinOutTest, DynamicShapeUpperBoundSameAsExpected) {
359 test_dynamic_shape(
360 {2, 4}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND);
361 }
362
TEST_F(OpMinOutTest,DynamicShapeUpperBoundLargerThanExpected)363 TEST_F(OpMinOutTest, DynamicShapeUpperBoundLargerThanExpected) {
364 test_dynamic_shape(
365 {10, 10}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND);
366 }
367
TEST_F(OpMinOutTest,DynamicShapeUnbound)368 TEST_F(OpMinOutTest, DynamicShapeUnbound) {
369 if (!torch::executor::testing::SupportedFeatures::get()->output_resize) {
370 GTEST_SKIP() << "Dynamic shape unbound not supported";
371 }
372 test_dynamic_shape(
373 {1, 1}, torch::executor::TensorShapeDynamism::DYNAMIC_UNBOUND);
374 }
375