xref: /aosp_15_r20/external/XNNPACK/test/multiply2.cc (revision 4bdc94577ba0e567308109d787f7fec7b531ce36)
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