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