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 OpRoundTest : public OperatorTest {
24 protected:
op_round_out(const Tensor & self,Tensor & out)25 Tensor& op_round_out(const Tensor& self, Tensor& out) {
26 return torch::executor::aten::round_outf(context_, self, out);
27 }
28
29 // Common testing for round on two floating point Tensors.
30 template <ScalarType DTYPE>
test_round_execution_floats()31 void test_round_execution_floats() {
32 TensorFactory<DTYPE> tf;
33
34 const std::vector<int32_t> sizes = {11};
35
36 Tensor in = tf.make(
37 sizes,
38 /*data=*/{1.5, -1.5, 0, 1.5, 2.5, 3.5, 4.5, 1.4, -1.4, 1.7, -1.7});
39
40 // Destination for the round.
41 Tensor out = tf.zeros(sizes);
42
43 // Run round.
44 op_round_out(in, out);
45
46 // Check that it matches the expected output.
47 EXPECT_TENSOR_EQ(
48 out,
49 tf.make(
50 sizes,
51 /*data=*/
52 {2.0, -2.0, 0.0, 2.0, 2.0, 4.0, 4.0, 1.0, -1.0, 2.0, -2.0}));
53 }
54
55 template <ScalarType DTYPE>
test_round_execution_ints()56 void test_round_execution_ints() {
57 TensorFactory<DTYPE> tf;
58
59 const std::vector<int32_t> sizes = {6};
60
61 Tensor in = tf.make(sizes, /*data=*/{-1, 2, 0, 3, 0, -5});
62
63 // Destination for the round.
64 Tensor out = tf.zeros(sizes);
65
66 // Run round.
67 op_round_out(in, out);
68
69 // Check that it matches the expected output.
70 EXPECT_TENSOR_EQ(
71 out,
72 tf.make(
73 sizes,
74 /*data=*/
75 {-1, 2, 0, 3, 0, -5}));
76 }
77 };
78
TEST_F(OpRoundTest,FloatTensors)79 TEST_F(OpRoundTest, FloatTensors) {
80 test_round_execution_floats<ScalarType::Float>();
81 }
82
TEST_F(OpRoundTest,DoubleTensors)83 TEST_F(OpRoundTest, DoubleTensors) {
84 test_round_execution_floats<ScalarType::Double>();
85 }
86
TEST_F(OpRoundTest,ByteTensors)87 TEST_F(OpRoundTest, ByteTensors) {
88 TensorFactory<ScalarType::Byte> tf;
89
90 const std::vector<int32_t> sizes = {6};
91
92 Tensor in = tf.make(sizes, /*data=*/{1, 2, 0, 3, 0, 5});
93
94 // Destination for the round.
95 Tensor out = tf.zeros(sizes);
96
97 // Run round.
98 op_round_out(in, out);
99
100 // Check that it matches the expected output.
101 EXPECT_TENSOR_EQ(
102 out,
103 tf.make(
104 sizes,
105 /*data=*/
106 {1, 2, 0, 3, 0, 5}));
107 }
108
TEST_F(OpRoundTest,CharTensors)109 TEST_F(OpRoundTest, CharTensors) {
110 test_round_execution_ints<ScalarType::Char>();
111 }
112
TEST_F(OpRoundTest,ShortTensors)113 TEST_F(OpRoundTest, ShortTensors) {
114 test_round_execution_ints<ScalarType::Short>();
115 }
116
TEST_F(OpRoundTest,IntTensors)117 TEST_F(OpRoundTest, IntTensors) {
118 test_round_execution_ints<ScalarType::Int>();
119 }
120
TEST_F(OpRoundTest,LongTensors)121 TEST_F(OpRoundTest, LongTensors) {
122 test_round_execution_ints<ScalarType::Long>();
123 }
124
TEST_F(OpRoundTest,InfAndNanPreserved)125 TEST_F(OpRoundTest, InfAndNanPreserved) {
126 TensorFactory<ScalarType::Float> tf;
127
128 const std::vector<int32_t> sizes = {7};
129
130 Tensor in = tf.make(
131 sizes,
132 /*data=*/
133 {1.7, 1.4, NAN, std::numeric_limits<float>::infinity(), 1.5, -1.5, 0});
134
135 // Destination for the round.
136 Tensor out = tf.zeros(sizes);
137
138 // Run full round.
139 op_round_out(in, out);
140
141 // Check that it matches the expected output.
142 EXPECT_TENSOR_EQ(
143 out,
144 tf.make(
145 sizes,
146 /*data=*/
147 {2.0,
148 1.0,
149 NAN,
150 std::numeric_limits<float>::infinity(),
151 2.0,
152 -2.0,
153 0.0}));
154 }
155
TEST_F(OpRoundTest,UnhandledDtypeDies)156 TEST_F(OpRoundTest, UnhandledDtypeDies) {
157 // round() doesn't handle Bool.
158 TensorFactory<ScalarType::Bool> tf;
159
160 const std::vector<int32_t> sizes = {2, 2};
161
162 Tensor a = tf.make(sizes, /*data=*/{false, true, false, true});
163
164 // Destination for the round.
165 Tensor out = tf.zeros(sizes);
166
167 ET_EXPECT_KERNEL_FAILURE(context_, op_round_out(a, out));
168 }
169
170 /* %python
171 import torch
172 torch.manual_seed(0)
173 x = torch.rand(3, 2) * 10.0 - 5.0
174 res = torch.round(x)
175 op = "op_round_out"
176 dtype = "ScalarType::Float"
177 check = "EXPECT_TENSOR_EQ" */
178
TEST_F(OpRoundTest,DynamicShapeUpperBoundSameAsExpected)179 TEST_F(OpRoundTest, DynamicShapeUpperBoundSameAsExpected) {
180 /* %python
181 out_args = "{3, 2}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND"
182 %rewrite(unary_op) */
183
184 TensorFactory<ScalarType::Float> tf;
185
186 Tensor x = tf.make(
187 {3, 2},
188 {-0.03743410110473633,
189 2.682218074798584,
190 -4.115225791931152,
191 -3.6796951293945312,
192 -1.925771713256836,
193 1.3407869338989258});
194 Tensor expected = tf.make({3, 2}, {-0.0, 3.0, -4.0, -4.0, -2.0, 1.0});
195
196 Tensor out =
197 tf.zeros({3, 2}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND);
198 op_round_out(x, out);
199 EXPECT_TENSOR_EQ(out, expected);
200 }
201
TEST_F(OpRoundTest,DynamicShapeUpperBoundLargerThanExpected)202 TEST_F(OpRoundTest, DynamicShapeUpperBoundLargerThanExpected) {
203 /* %python
204 out_args = "{10, 10}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND"
205 %rewrite(unary_op) */
206
207 TensorFactory<ScalarType::Float> tf;
208
209 Tensor x = tf.make(
210 {3, 2},
211 {-0.03743410110473633,
212 2.682218074798584,
213 -4.115225791931152,
214 -3.6796951293945312,
215 -1.925771713256836,
216 1.3407869338989258});
217 Tensor expected = tf.make({3, 2}, {-0.0, 3.0, -4.0, -4.0, -2.0, 1.0});
218
219 Tensor out =
220 tf.zeros({10, 10}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND);
221 op_round_out(x, out);
222 EXPECT_TENSOR_EQ(out, expected);
223 }
224
TEST_F(OpRoundTest,DynamicShapeUnbound)225 TEST_F(OpRoundTest, DynamicShapeUnbound) {
226 GTEST_SKIP() << "Dynamic shape unbound not supported";
227 /* %python
228 out_args = "{1, 1}, torch::executor::TensorShapeDynamism::DYNAMIC_UNBOUND"
229 %rewrite(unary_op) */
230
231 TensorFactory<ScalarType::Float> tf;
232
233 Tensor x = tf.make(
234 {3, 2},
235 {-0.03743410110473633,
236 2.682218074798584,
237 -4.115225791931152,
238 -3.6796951293945312,
239 -1.925771713256836,
240 1.3407869338989258});
241 Tensor expected = tf.make({3, 2}, {-0.0, 3.0, -4.0, -4.0, -2.0, 1.0});
242
243 Tensor out =
244 tf.zeros({1, 1}, torch::executor::TensorShapeDynamism::DYNAMIC_UNBOUND);
245 op_round_out(x, out);
246 EXPECT_TENSOR_EQ(out, expected);
247 }
248