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