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