1 // Copyright 2022 Google LLC
2 //
3 // This source code is licensed under the BSD-style license found in the
4 // LICENSE file in the root directory of this source tree.
5
6 #include <algorithm> // For std::generate, std::min.
7 #include <array> // For std::array.
8 #include <cmath> // For std::lrintf.
9 #include <cstddef> // For size_t.
10 #include <cstdint> // For uint32_t.
11 #include <limits> // For std::numeric_limits.
12 #include <memory> // For std::unique_ptr.
13 #include <random> // For std::random_device, std::mt19937, std::uniform_real_distribution.
14 #include <vector> // For std::vector.
15
16 #include <xnnpack.h>
17 #include <xnnpack/operator.h>
18 #include <xnnpack/subgraph.h>
19
20 #include <gtest/gtest.h>
21
22 template <class T> class StaticResizeBilinear2DTestBase : public ::testing::Test {
23 protected:
StaticResizeBilinear2DTestBase()24 StaticResizeBilinear2DTestBase()
25 {
26 random_device = std::unique_ptr<std::random_device>(new std::random_device());
27 rng = std::mt19937((*random_device)());
28 input_size_dist = std::uniform_int_distribution<uint32_t>(10, 15);
29 kernel_size_dist = std::uniform_int_distribution<uint32_t>(2, 5);
30 stride_dist = std::uniform_int_distribution<uint32_t>(1, 2);
31 f32dist = std::uniform_real_distribution<float>();
32 scale_dist = std::uniform_real_distribution<float>(1.0f, 5.0f);
33 i32dist = std::uniform_int_distribution<int32_t>(-10000, 10000);
34 dilation_dist = std::uniform_int_distribution<uint32_t>(1, 2);
35 i8dist =
36 std::uniform_int_distribution<int32_t>(std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max());
37 u8dist =
38 std::uniform_int_distribution<int32_t>(std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max());
39
40 batch_size = input_size_dist(rng);
41 input_height = input_size_dist(rng);
42 input_width = input_size_dist(rng);
43 channels = input_size_dist(rng);
44 output_height = input_size_dist(rng);
45 output_width = input_size_dist(rng);
46
47 input_dims = {{batch_size, input_height, input_width, channels}};
48 output_dims = {{batch_size, output_height, output_width, channels}};
49
50 input = std::vector<T>(XNN_EXTRA_BYTES / sizeof(T) + batch_size * input_height * input_width * channels);
51 operator_output = std::vector<T>(batch_size * output_height * output_width * channels);
52 subgraph_output = std::vector<T>(batch_size * output_height * output_width * channels);
53 }
54
55 std::unique_ptr<std::random_device> random_device;
56 std::mt19937 rng;
57 std::uniform_int_distribution<uint32_t> input_size_dist;
58 std::uniform_int_distribution<uint32_t> kernel_size_dist;
59 std::uniform_int_distribution<uint32_t> stride_dist;
60 std::uniform_int_distribution<int32_t> i32dist;
61 std::uniform_real_distribution<float> f32dist;
62 std::uniform_real_distribution<float> scale_dist;
63 std::uniform_int_distribution<uint32_t> dilation_dist;
64 std::uniform_int_distribution<int32_t> i8dist;
65 std::uniform_int_distribution<int32_t> u8dist;
66
67 uint32_t batch_size;
68 uint32_t input_height;
69 uint32_t input_width;
70 uint32_t channels;
71 uint32_t output_height;
72 uint32_t output_width;
73
74 std::array<size_t, 4> input_dims;
75 std::array<size_t, 4> output_dims;
76
77 std::vector<T> input;
78 std::vector<T> operator_output;
79 std::vector<T> subgraph_output;
80 };
81
82 using StaticResizeBilinear2DTestQS8 = StaticResizeBilinear2DTestBase<int8_t>;
83 using StaticResizeBilinear2DTestQU8 = StaticResizeBilinear2DTestBase<uint8_t>;
84 using StaticResizeBilinear2DTestF32 = StaticResizeBilinear2DTestBase<float>;
85
TEST_F(StaticResizeBilinear2DTestQS8,define)86 TEST_F(StaticResizeBilinear2DTestQS8, define)
87 {
88 ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
89
90 xnn_subgraph_t subgraph = nullptr;
91 ASSERT_EQ(xnn_status_success, xnn_create_subgraph(2, /*flags=*/0, &subgraph));
92 std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
93
94 uint32_t input_id = XNN_INVALID_NODE_ID;
95 ASSERT_EQ(
96 xnn_status_success, xnn_define_quantized_tensor_value(
97 subgraph, xnn_datatype_qint8, 0, 1.0f, input_dims.size(), input_dims.data(), nullptr,
98 /*external_id=*/0, /*flags=*/0, &input_id));
99 ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
100
101 uint32_t output_id = XNN_INVALID_NODE_ID;
102 ASSERT_EQ(
103 xnn_status_success, xnn_define_quantized_tensor_value(
104 subgraph, xnn_datatype_qint8, 0, 1.0f, output_dims.size(), output_dims.data(), nullptr,
105 /*external_id=*/1, /*flags=*/0, &output_id));
106 ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
107
108 ASSERT_EQ(
109 xnn_status_success,
110 xnn_define_static_resize_bilinear_2d(subgraph, output_height, output_width, input_id, output_id, /*flags=*/0));
111
112 ASSERT_EQ(subgraph->num_nodes, 1);
113 const struct xnn_node* node = &subgraph->nodes[0];
114 ASSERT_EQ(node->type, xnn_node_type_static_resize_bilinear_2d);
115 ASSERT_EQ(node->compute_type, xnn_compute_type_qs8);
116 ASSERT_EQ(node->params.static_resize.new_height, output_height);
117 ASSERT_EQ(node->params.static_resize.new_width, output_width);
118 ASSERT_EQ(node->num_inputs, 1);
119 ASSERT_EQ(node->inputs[0], input_id);
120 ASSERT_EQ(node->num_outputs, 1);
121 ASSERT_EQ(node->outputs[0], output_id);
122 ASSERT_EQ(node->flags, 0);
123 }
124
TEST_F(StaticResizeBilinear2DTestQU8,define)125 TEST_F(StaticResizeBilinear2DTestQU8, define)
126 {
127 ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
128
129 xnn_subgraph_t subgraph = nullptr;
130 ASSERT_EQ(xnn_status_success, xnn_create_subgraph(2, /*flags=*/0, &subgraph));
131 std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
132
133 uint32_t input_id = XNN_INVALID_NODE_ID;
134 ASSERT_EQ(
135 xnn_status_success, xnn_define_quantized_tensor_value(
136 subgraph, xnn_datatype_quint8, 0, 1.0f, input_dims.size(), input_dims.data(), nullptr,
137 /*external_id=*/0, /*flags=*/0, &input_id));
138 ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
139
140 uint32_t output_id = XNN_INVALID_NODE_ID;
141 ASSERT_EQ(
142 xnn_status_success, xnn_define_quantized_tensor_value(
143 subgraph, xnn_datatype_quint8, 0, 1.0f, output_dims.size(), output_dims.data(), nullptr,
144 /*external_id=*/1, /*flags=*/0, &output_id));
145 ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
146
147 ASSERT_EQ(
148 xnn_status_success,
149 xnn_define_static_resize_bilinear_2d(subgraph, output_height, output_width, input_id, output_id, /*flags=*/0));
150
151 ASSERT_EQ(subgraph->num_nodes, 1);
152 const struct xnn_node* node = &subgraph->nodes[0];
153 ASSERT_EQ(node->type, xnn_node_type_static_resize_bilinear_2d);
154 ASSERT_EQ(node->compute_type, xnn_compute_type_qu8);
155 ASSERT_EQ(node->params.static_resize.new_height, output_height);
156 ASSERT_EQ(node->params.static_resize.new_width, output_width);
157 ASSERT_EQ(node->num_inputs, 1);
158 ASSERT_EQ(node->inputs[0], input_id);
159 ASSERT_EQ(node->num_outputs, 1);
160 ASSERT_EQ(node->outputs[0], output_id);
161 ASSERT_EQ(node->flags, 0);
162 }
163
TEST_F(StaticResizeBilinear2DTestF32,define)164 TEST_F(StaticResizeBilinear2DTestF32, define)
165 {
166 ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
167
168 xnn_subgraph_t subgraph = nullptr;
169 ASSERT_EQ(xnn_status_success, xnn_create_subgraph(2, /*flags=*/0, &subgraph));
170 std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
171
172 uint32_t input_id = XNN_INVALID_NODE_ID;
173 ASSERT_EQ(
174 xnn_status_success, xnn_define_tensor_value(
175 subgraph, xnn_datatype_fp32, input_dims.size(), input_dims.data(), nullptr,
176 /*external_id=*/0, /*flags=*/0, &input_id));
177 ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
178
179 uint32_t output_id = XNN_INVALID_NODE_ID;
180 ASSERT_EQ(
181 xnn_status_success, xnn_define_tensor_value(
182 subgraph, xnn_datatype_fp32, output_dims.size(), output_dims.data(), nullptr,
183 /*external_id=*/1, /*flags=*/0, &output_id));
184 ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
185
186 ASSERT_EQ(
187 xnn_status_success,
188 xnn_define_static_resize_bilinear_2d(subgraph, output_height, output_width, input_id, output_id, /*flags=*/0));
189
190 ASSERT_EQ(subgraph->num_nodes, 1);
191 const struct xnn_node* node = &subgraph->nodes[0];
192 ASSERT_EQ(node->type, xnn_node_type_static_resize_bilinear_2d);
193 ASSERT_EQ(node->compute_type, xnn_compute_type_fp32);
194 ASSERT_EQ(node->params.static_resize.new_height, output_height);
195 ASSERT_EQ(node->params.static_resize.new_width, output_width);
196 ASSERT_EQ(node->num_inputs, 1);
197 ASSERT_EQ(node->inputs[0], input_id);
198 ASSERT_EQ(node->num_outputs, 1);
199 ASSERT_EQ(node->outputs[0], output_id);
200 ASSERT_EQ(node->flags, 0);
201 }
202
TEST_F(StaticResizeBilinear2DTestQS8,matches_operator_api)203 TEST_F(StaticResizeBilinear2DTestQS8, matches_operator_api)
204 {
205 ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
206 std::generate(input.begin(), input.end(), [&]() { return i8dist(rng); });
207 std::fill(operator_output.begin(), operator_output.end(), INT8_C(0xA5));
208 std::fill(subgraph_output.begin(), subgraph_output.end(), INT8_C(0xA5));
209 const int8_t input_zero_point = i8dist(rng);
210 const float input_scale = scale_dist(rng);
211 const float output_zero_point = input_zero_point;
212 const float output_scale = input_scale;
213
214 // Call operator API.
215 xnn_operator_t op = nullptr;
216 const xnn_status status = xnn_create_resize_bilinear2d_nhwc_s8(channels, channels, channels, /*flags=*/0, &op);
217 std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_op(op, xnn_delete_operator);
218
219 if (status == xnn_status_unsupported_hardware) {
220 GTEST_SKIP();
221 }
222
223 ASSERT_EQ(xnn_status_success, status);
224 ASSERT_NE(nullptr, op);
225 ASSERT_EQ(
226 xnn_status_success,
227 xnn_setup_resize_bilinear2d_nhwc_s8(
228 op, batch_size, input_height, input_width, output_height, output_width, input.data(), operator_output.data(),
229 /*threadpool=*/nullptr));
230
231 ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
232
233 // Call subgraph API.
234 xnn_subgraph_t subgraph = nullptr;
235 ASSERT_EQ(xnn_status_success, xnn_create_subgraph(2, /*flags=*/0, &subgraph));
236 std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
237
238 uint32_t input_id = XNN_INVALID_NODE_ID;
239 ASSERT_EQ(
240 xnn_status_success, xnn_define_quantized_tensor_value(
241 subgraph, xnn_datatype_qint8, input_zero_point, input_scale, input_dims.size(),
242 input_dims.data(), nullptr, /*external_id=*/0, XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
243 ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
244
245 uint32_t output_id = XNN_INVALID_NODE_ID;
246 ASSERT_EQ(
247 xnn_status_success, xnn_define_quantized_tensor_value(
248 subgraph, xnn_datatype_qint8, output_zero_point, output_scale, output_dims.size(),
249 output_dims.data(), nullptr, /*external_id=*/1, XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
250 ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
251 ASSERT_EQ(
252 xnn_status_success,
253 xnn_define_static_resize_bilinear_2d(subgraph, output_height, output_width, input_id, output_id, /*flags=*/0));
254
255 xnn_runtime_t runtime = nullptr;
256 ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
257 ASSERT_NE(nullptr, runtime);
258 std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
259 std::array<xnn_external_value, 2> external = {
260 xnn_external_value{input_id, input.data()}, xnn_external_value{output_id, subgraph_output.data()}};
261 ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
262 ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
263
264 ASSERT_EQ(subgraph_output, operator_output);
265 }
266
TEST_F(StaticResizeBilinear2DTestQU8,matches_operator_api)267 TEST_F(StaticResizeBilinear2DTestQU8, matches_operator_api)
268 {
269 ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
270 std::generate(input.begin(), input.end(), [&]() { return i8dist(rng); });
271 std::fill(operator_output.begin(), operator_output.end(), UINT8_C(0xA5));
272 std::fill(subgraph_output.begin(), subgraph_output.end(), UINT8_C(0xA5));
273 const uint8_t input_zero_point = u8dist(rng);
274 const float input_scale = scale_dist(rng);
275 const uint8_t output_zero_point = input_zero_point;
276 const float output_scale = input_scale;
277
278 // Call operator API.
279 xnn_operator_t op = nullptr;
280 const xnn_status status = xnn_create_resize_bilinear2d_nhwc_u8(channels, channels, channels, /*flags=*/0, &op);
281 std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_op(op, xnn_delete_operator);
282
283 if (status == xnn_status_unsupported_hardware) {
284 GTEST_SKIP();
285 }
286
287 ASSERT_EQ(xnn_status_success, status);
288 ASSERT_NE(nullptr, op);
289 ASSERT_EQ(
290 xnn_status_success,
291 xnn_setup_resize_bilinear2d_nhwc_u8(
292 op, batch_size, input_height, input_width, output_height, output_width, input.data(), operator_output.data(),
293 /*threadpool=*/nullptr));
294
295 ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
296
297 // Call subgraph API.
298 xnn_subgraph_t subgraph = nullptr;
299 ASSERT_EQ(xnn_status_success, xnn_create_subgraph(2, /*flags=*/0, &subgraph));
300 std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
301
302 uint32_t input_id = XNN_INVALID_NODE_ID;
303 ASSERT_EQ(
304 xnn_status_success, xnn_define_quantized_tensor_value(
305 subgraph, xnn_datatype_quint8, input_zero_point, input_scale, input_dims.size(),
306 input_dims.data(), nullptr, /*external_id=*/0, XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
307 ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
308
309 uint32_t output_id = XNN_INVALID_NODE_ID;
310 ASSERT_EQ(
311 xnn_status_success, xnn_define_quantized_tensor_value(
312 subgraph, xnn_datatype_quint8, output_zero_point, output_scale, output_dims.size(),
313 output_dims.data(), nullptr, /*external_id=*/1, XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
314 ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
315 ASSERT_EQ(
316 xnn_status_success,
317 xnn_define_static_resize_bilinear_2d(subgraph, output_height, output_width, input_id, output_id, /*flags=*/0));
318
319 xnn_runtime_t runtime = nullptr;
320 ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
321 ASSERT_NE(nullptr, runtime);
322 std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
323 std::array<xnn_external_value, 2> external = {
324 xnn_external_value{input_id, input.data()}, xnn_external_value{output_id, subgraph_output.data()}};
325 ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
326 ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
327
328 ASSERT_EQ(subgraph_output, operator_output);
329 }
330
TEST_F(StaticResizeBilinear2DTestF32,matches_operator_api)331 TEST_F(StaticResizeBilinear2DTestF32, matches_operator_api)
332 {
333 ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
334 std::generate(input.begin(), input.end(), [&]() { return f32dist(rng); });
335 std::fill(operator_output.begin(), operator_output.end(), std::nanf(""));
336 std::fill(subgraph_output.begin(), subgraph_output.end(), std::nanf(""));
337
338 // Call operator API.
339 xnn_operator_t op = nullptr;
340 const xnn_status status = xnn_create_resize_bilinear2d_nhwc_f32(channels, channels, channels, /*flags=*/0, &op);
341 std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_op(op, xnn_delete_operator);
342
343 if (status == xnn_status_unsupported_hardware) {
344 GTEST_SKIP();
345 }
346
347 ASSERT_EQ(xnn_status_success, status);
348 ASSERT_NE(nullptr, op);
349 ASSERT_EQ(
350 xnn_status_success,
351 xnn_setup_resize_bilinear2d_nhwc_f32(
352 op, batch_size, input_height, input_width, output_height, output_width, input.data(), operator_output.data(),
353 /*threadpool=*/nullptr));
354
355 ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
356
357 // Call subgraph API.
358 xnn_subgraph_t subgraph = nullptr;
359 ASSERT_EQ(xnn_status_success, xnn_create_subgraph(2, /*flags=*/0, &subgraph));
360 std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
361
362 uint32_t input_id = XNN_INVALID_NODE_ID;
363 ASSERT_EQ(
364 xnn_status_success, xnn_define_tensor_value(
365 subgraph, xnn_datatype_fp32, input_dims.size(), input_dims.data(), nullptr, /*external_id=*/0,
366 XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
367 ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
368
369 uint32_t output_id = XNN_INVALID_NODE_ID;
370 ASSERT_EQ(
371 xnn_status_success, xnn_define_tensor_value(
372 subgraph, xnn_datatype_fp32, output_dims.size(), output_dims.data(), nullptr,
373 /*external_id=*/1, XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
374 ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
375 ASSERT_EQ(
376 xnn_status_success,
377 xnn_define_static_resize_bilinear_2d(subgraph, output_height, output_width, input_id, output_id, /*flags=*/0));
378
379 xnn_runtime_t runtime = nullptr;
380 ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
381 ASSERT_NE(nullptr, runtime);
382 std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
383 std::array<xnn_external_value, 2> external = {
384 xnn_external_value{input_id, input.data()}, xnn_external_value{output_id, subgraph_output.data()}};
385 ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
386 ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
387
388 ASSERT_EQ(subgraph_output, operator_output);
389 }
390