xref: /aosp_15_r20/external/executorch/kernels/test/op_min_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 #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