xref: /aosp_15_r20/external/XNNPACK/test/convert.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 <limits>
11 #include <memory>
12 #include <random>
13 
14 #include <xnnpack.h>
15 #include <xnnpack/node-type.h>
16 #include <xnnpack/operator.h>
17 #include <xnnpack/subgraph.h>
18 
19 #include "subgraph-unary-tester.h"
20 #include <gtest/gtest.h>
21 
22 using ConvertTestF32ToQS8 = UnaryTest<float, int8_t>;
23 using ConvertTestF32ToQU8 = UnaryTest<float, uint8_t>;
24 using ConvertTestQS8ToQS8 = UnaryTest<int8_t, int8_t>;
25 using ConvertTestQS8ToF32 = UnaryTest<int8_t, float>;
26 using ConvertTestQU8ToQU8 = UnaryTest<uint8_t, uint8_t>;
27 using ConvertTestQU8ToF32 = UnaryTest<uint8_t, float>;
28 
TEST_F(ConvertTestF32ToQS8,define)29 TEST_F(ConvertTestF32ToQS8, define)
30 {
31   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
32 
33   xnn_subgraph_t subgraph = nullptr;
34   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
35   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
36 
37   input_id = XNN_INVALID_NODE_ID;
38   ASSERT_EQ(
39     xnn_status_success, xnn_define_tensor_value(
40                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, 0,
41                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
42   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
43 
44   output_id = XNN_INVALID_NODE_ID;
45   ASSERT_EQ(
46     xnn_status_success, xnn_define_quantized_tensor_value(
47                           subgraph, xnn_datatype_qint8, signed_zero_point, scale, dims.size(), dims.data(), nullptr, 1,
48                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
49   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
50 
51   ASSERT_EQ(xnn_status_success, xnn_define_convert(subgraph, input_id, output_id, /*flags=*/0));
52 
53   ASSERT_EQ(subgraph->num_nodes, 1);
54   const struct xnn_node* node = &subgraph->nodes[0];
55   ASSERT_EQ(node->type, xnn_node_type_convert);
56   ASSERT_EQ(node->compute_type, xnn_compute_type_fp32_to_qs8);
57   ASSERT_EQ(node->num_inputs, 1);
58   ASSERT_EQ(node->inputs[0], input_id);
59   ASSERT_EQ(node->num_outputs, 1);
60   ASSERT_EQ(node->outputs[0], output_id);
61   ASSERT_EQ(node->flags, 0);
62 }
63 
TEST_F(ConvertTestF32ToQU8,define)64 TEST_F(ConvertTestF32ToQU8, define)
65 {
66   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
67 
68   xnn_subgraph_t subgraph = nullptr;
69   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
70   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
71 
72   input_id = XNN_INVALID_NODE_ID;
73   ASSERT_EQ(
74     xnn_status_success, xnn_define_tensor_value(
75                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, 0,
76                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
77   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
78 
79   output_id = XNN_INVALID_NODE_ID;
80   ASSERT_EQ(
81     xnn_status_success,
82     xnn_define_quantized_tensor_value(
83       subgraph, xnn_datatype_quint8, unsigned_zero_point, scale, dims.size(), dims.data(), nullptr, 1,
84       /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
85   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
86 
87   ASSERT_EQ(xnn_status_success, xnn_define_convert(subgraph, input_id, output_id, /*flags=*/0));
88 
89   ASSERT_EQ(subgraph->num_nodes, 1);
90   const struct xnn_node* node = &subgraph->nodes[0];
91   ASSERT_EQ(node->type, xnn_node_type_convert);
92   ASSERT_EQ(node->compute_type, xnn_compute_type_fp32_to_qu8);
93   ASSERT_EQ(node->num_inputs, 1);
94   ASSERT_EQ(node->inputs[0], input_id);
95   ASSERT_EQ(node->num_outputs, 1);
96   ASSERT_EQ(node->outputs[0], output_id);
97   ASSERT_EQ(node->flags, 0);
98 }
99 
TEST_F(ConvertTestQS8ToF32,define)100 TEST_F(ConvertTestQS8ToF32, define)
101 {
102   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
103 
104   xnn_subgraph_t subgraph = nullptr;
105   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
106   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
107 
108   input_id = XNN_INVALID_NODE_ID;
109   ASSERT_EQ(
110     xnn_status_success, xnn_define_quantized_tensor_value(
111                           subgraph, xnn_datatype_qint8, signed_zero_point, scale, dims.size(), dims.data(), nullptr, 0,
112                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
113   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
114 
115   output_id = XNN_INVALID_NODE_ID;
116   ASSERT_EQ(
117     xnn_status_success, xnn_define_tensor_value(
118                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, 1,
119                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
120   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
121 
122   ASSERT_EQ(xnn_status_success, xnn_define_convert(subgraph, input_id, output_id, /*flags=*/0));
123 
124   ASSERT_EQ(subgraph->num_nodes, 1);
125   const struct xnn_node* node = &subgraph->nodes[0];
126   ASSERT_EQ(node->type, xnn_node_type_convert);
127   ASSERT_EQ(node->compute_type, xnn_compute_type_qs8_to_fp32);
128   ASSERT_EQ(node->num_inputs, 1);
129   ASSERT_EQ(node->inputs[0], input_id);
130   ASSERT_EQ(node->num_outputs, 1);
131   ASSERT_EQ(node->outputs[0], output_id);
132   ASSERT_EQ(node->flags, 0);
133 }
134 
TEST_F(ConvertTestQS8ToQS8,define)135 TEST_F(ConvertTestQS8ToQS8, define)
136 {
137   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
138 
139   const int32_t input_zero_point = i8dist(rng);
140   const int32_t output_zero_point = i8dist(rng);
141   // Scale distributions chosen to guarantee 2**-8 <= input_scale / output_scale <= 2**7
142   const float input_scale = std::uniform_real_distribution<float>(0.0883883f, 11.3137f)(rng);
143   const float output_scale = std::uniform_real_distribution<float>(0.0883883f, 11.3137f)(rng);
144 
145   xnn_subgraph_t subgraph = nullptr;
146   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
147   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
148 
149   input_id = XNN_INVALID_NODE_ID;
150   ASSERT_EQ(
151     xnn_status_success,
152     xnn_define_quantized_tensor_value(
153       subgraph, xnn_datatype_qint8, input_zero_point, input_scale, dims.size(), dims.data(), nullptr, 0,
154       /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
155   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
156 
157   output_id = XNN_INVALID_NODE_ID;
158   ASSERT_EQ(
159     xnn_status_success,
160     xnn_define_quantized_tensor_value(
161       subgraph, xnn_datatype_qint8, output_zero_point, output_scale, dims.size(), dims.data(), nullptr, 1,
162       /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
163   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
164 
165   ASSERT_EQ(xnn_status_success, xnn_define_convert(subgraph, input_id, output_id, /*flags=*/0));
166 
167   ASSERT_EQ(subgraph->num_nodes, 1);
168   const struct xnn_node* node = &subgraph->nodes[0];
169   ASSERT_EQ(node->type, xnn_node_type_convert);
170   ASSERT_EQ(node->compute_type, xnn_compute_type_qs8);
171   ASSERT_EQ(node->num_inputs, 1);
172   ASSERT_EQ(node->inputs[0], input_id);
173   ASSERT_EQ(node->num_outputs, 1);
174   ASSERT_EQ(node->outputs[0], output_id);
175   ASSERT_EQ(node->flags, 0);
176 }
177 
TEST_F(ConvertTestQU8ToF32,define)178 TEST_F(ConvertTestQU8ToF32, define)
179 {
180   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
181 
182   xnn_subgraph_t subgraph = nullptr;
183   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
184   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
185 
186   input_id = XNN_INVALID_NODE_ID;
187   ASSERT_EQ(
188     xnn_status_success,
189     xnn_define_quantized_tensor_value(
190       subgraph, xnn_datatype_quint8, unsigned_zero_point, scale, dims.size(), dims.data(), nullptr, 0,
191       /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
192   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
193 
194   output_id = XNN_INVALID_NODE_ID;
195   ASSERT_EQ(
196     xnn_status_success, xnn_define_tensor_value(
197                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, 1,
198                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
199   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
200 
201   ASSERT_EQ(xnn_status_success, xnn_define_convert(subgraph, input_id, output_id, /*flags=*/0));
202 
203   ASSERT_EQ(subgraph->num_nodes, 1);
204   const struct xnn_node* node = &subgraph->nodes[0];
205   ASSERT_EQ(node->type, xnn_node_type_convert);
206   ASSERT_EQ(node->compute_type, xnn_compute_type_qu8_to_fp32);
207   ASSERT_EQ(node->num_inputs, 1);
208   ASSERT_EQ(node->inputs[0], input_id);
209   ASSERT_EQ(node->num_outputs, 1);
210   ASSERT_EQ(node->outputs[0], output_id);
211   ASSERT_EQ(node->flags, 0);
212 }
213 
TEST_F(ConvertTestQU8ToQU8,define)214 TEST_F(ConvertTestQU8ToQU8, define)
215 {
216   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
217 
218   const int32_t input_zero_point = u8dist(rng);
219   const int32_t output_zero_point = u8dist(rng);
220   // Scale distributions chosen to guarantee 2**-8 <= input_scale / output_scale <= 2**7
221   const float input_scale = std::uniform_real_distribution<float>(0.0883883f, 11.3137f)(rng);
222   const float output_scale = std::uniform_real_distribution<float>(0.0883883f, 11.3137f)(rng);
223 
224   xnn_subgraph_t subgraph = nullptr;
225   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
226   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
227 
228   input_id = XNN_INVALID_NODE_ID;
229   ASSERT_EQ(
230     xnn_status_success,
231     xnn_define_quantized_tensor_value(
232       subgraph, xnn_datatype_quint8, input_zero_point, input_scale, dims.size(), dims.data(), nullptr, 0,
233       /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
234   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
235 
236   output_id = XNN_INVALID_NODE_ID;
237   ASSERT_EQ(
238     xnn_status_success,
239     xnn_define_quantized_tensor_value(
240       subgraph, xnn_datatype_quint8, output_zero_point, output_scale, dims.size(), dims.data(), nullptr, 1,
241       /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
242   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
243 
244   ASSERT_EQ(xnn_status_success, xnn_define_convert(subgraph, input_id, output_id, /*flags=*/0));
245 
246   ASSERT_EQ(subgraph->num_nodes, 1);
247   const struct xnn_node* node = &subgraph->nodes[0];
248   ASSERT_EQ(node->type, xnn_node_type_convert);
249   ASSERT_EQ(node->compute_type, xnn_compute_type_qu8);
250   ASSERT_EQ(node->num_inputs, 1);
251   ASSERT_EQ(node->inputs[0], input_id);
252   ASSERT_EQ(node->num_outputs, 1);
253   ASSERT_EQ(node->outputs[0], output_id);
254   ASSERT_EQ(node->flags, 0);
255 }
256 
TEST_F(ConvertTestF32ToQS8,matches_operator_api)257 TEST_F(ConvertTestF32ToQS8, matches_operator_api)
258 {
259   std::uniform_real_distribution<float> f32dist(-1.0f, 1.0f);
260   std::generate(input.begin(), input.end(), [&]() { return f32dist(rng); });
261   std::fill(operator_output.begin(), operator_output.end(), INT8_C(0xA5));
262   std::fill(subgraph_output.begin(), subgraph_output.end(), INT8_C(0xA5));
263 
264   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
265 
266   // Call operator API.
267   xnn_operator_t op = nullptr;
268   const xnn_status status = xnn_create_convert_nc_f32_qs8(
269     channels, channels, channels, scale, signed_zero_point, INT8_MIN, INT8_MAX, /*flags=*/0, &op);
270   if (status == xnn_status_unsupported_hardware) {
271     GTEST_SKIP();
272   }
273 
274   ASSERT_EQ(xnn_status_success, status);
275   ASSERT_NE(nullptr, op);
276   std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_op(op, xnn_delete_operator);
277 
278   ASSERT_EQ(
279     xnn_status_success,
280     xnn_setup_convert_nc_f32_qs8(op, batch_size, input.data(), operator_output.data(), /*threadpool=*/nullptr));
281 
282   ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
283 
284   // Call subgraph API.
285   xnn_subgraph_t subgraph = nullptr;
286   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
287   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
288   input_id = XNN_INVALID_NODE_ID;
289   ASSERT_EQ(
290     xnn_status_success, xnn_define_tensor_value(
291                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, /*external_id=*/0,
292                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
293   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
294 
295   output_id = XNN_INVALID_NODE_ID;
296   ASSERT_EQ(
297     xnn_status_success,
298     xnn_define_quantized_tensor_value(
299       subgraph, xnn_datatype_qint8, signed_zero_point, scale, dims.size(), dims.data(), nullptr, /*external_id=*/1,
300       /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
301   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
302 
303   xnn_runtime_t runtime = nullptr;
304   ASSERT_EQ(xnn_status_success, xnn_define_convert(subgraph, input_id, output_id, /*flags=*/0));
305   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
306   ASSERT_NE(nullptr, runtime);
307   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
308   std::array<xnn_external_value, 2> external = {
309     xnn_external_value{input_id, input.data()}, xnn_external_value{output_id, subgraph_output.data()}};
310   ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
311   ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
312 
313   ASSERT_EQ(subgraph_output, operator_output);
314 }
315 
TEST_F(ConvertTestF32ToQU8,matches_operator_api)316 TEST_F(ConvertTestF32ToQU8, matches_operator_api)
317 {
318   std::uniform_real_distribution<float> f32dist(-1.0f, 1.0f);
319   std::generate(input.begin(), input.end(), [&]() { return f32dist(rng); });
320   std::fill(operator_output.begin(), operator_output.end(), UINT8_C(0xA5));
321   std::fill(subgraph_output.begin(), subgraph_output.end(), UINT8_C(0xA5));
322 
323   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
324 
325   // Call operator API.
326   xnn_operator_t op = nullptr;
327   const xnn_status status = xnn_create_convert_nc_f32_qu8(
328     channels, channels, channels, scale, unsigned_zero_point, 0, UINT8_MAX, /*flags=*/0, &op);
329   if (status == xnn_status_unsupported_hardware) {
330     GTEST_SKIP();
331   }
332 
333   ASSERT_EQ(xnn_status_success, status);
334   ASSERT_NE(nullptr, op);
335   std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_op(op, xnn_delete_operator);
336 
337   ASSERT_EQ(
338     xnn_status_success,
339     xnn_setup_convert_nc_f32_qu8(op, batch_size, input.data(), operator_output.data(), /*threadpool=*/nullptr));
340 
341   ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
342 
343   // Call subgraph API.
344   xnn_subgraph_t subgraph = nullptr;
345   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
346   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
347   input_id = XNN_INVALID_NODE_ID;
348   ASSERT_EQ(
349     xnn_status_success, xnn_define_tensor_value(
350                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, /*external_id=*/0,
351                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
352   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
353 
354   output_id = XNN_INVALID_NODE_ID;
355   ASSERT_EQ(
356     xnn_status_success,
357     xnn_define_quantized_tensor_value(
358       subgraph, xnn_datatype_quint8, unsigned_zero_point, scale, dims.size(), dims.data(), nullptr, /*external_id=*/1,
359       /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
360   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
361 
362   xnn_runtime_t runtime = nullptr;
363   ASSERT_EQ(xnn_status_success, xnn_define_convert(subgraph, input_id, output_id, /*flags=*/0));
364   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
365   ASSERT_NE(nullptr, runtime);
366   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
367   std::array<xnn_external_value, 2> external = {
368     xnn_external_value{input_id, input.data()}, xnn_external_value{output_id, subgraph_output.data()}};
369   ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
370   ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
371 
372   ASSERT_EQ(subgraph_output, operator_output);
373 }
374 
TEST_F(ConvertTestQS8ToF32,matches_operator_api)375 TEST_F(ConvertTestQS8ToF32, matches_operator_api)
376 {
377   std::generate(input.begin(), input.end(), [&]() { return i8dist(rng); });
378   std::fill(operator_output.begin(), operator_output.end(), std::nanf(""));
379   std::fill(subgraph_output.begin(), subgraph_output.end(), std::nanf(""));
380 
381   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
382 
383   // Call operator API.
384   xnn_operator_t op = nullptr;
385   const xnn_status status =
386     xnn_create_convert_nc_qs8_f32(channels, channels, channels, scale, signed_zero_point, /*flags=*/0, &op);
387   if (status == xnn_status_unsupported_hardware) {
388     GTEST_SKIP();
389   }
390 
391   ASSERT_EQ(xnn_status_success, status);
392   ASSERT_NE(nullptr, op);
393   std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_op(op, xnn_delete_operator);
394 
395   ASSERT_EQ(
396     xnn_status_success,
397     xnn_setup_convert_nc_qs8_f32(op, batch_size, input.data(), operator_output.data(), /*threadpool=*/nullptr));
398 
399   ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
400 
401   // Call subgraph API.
402   xnn_subgraph_t subgraph = nullptr;
403   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
404   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
405 
406   input_id = XNN_INVALID_NODE_ID;
407   ASSERT_EQ(
408     xnn_status_success, xnn_define_quantized_tensor_value(
409                           subgraph, xnn_datatype_qint8, signed_zero_point, scale, dims.size(), dims.data(), nullptr, 0,
410                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
411   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
412 
413   output_id = XNN_INVALID_NODE_ID;
414   ASSERT_EQ(
415     xnn_status_success, xnn_define_tensor_value(
416                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, 1,
417                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
418   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
419 
420   xnn_runtime_t runtime = nullptr;
421   ASSERT_EQ(xnn_status_success, xnn_define_convert(subgraph, input_id, output_id, /*flags=*/0));
422   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
423   ASSERT_NE(nullptr, runtime);
424   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
425   std::array<xnn_external_value, 2> external = {
426     xnn_external_value{input_id, input.data()}, xnn_external_value{output_id, subgraph_output.data()}};
427   ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
428   ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
429 
430   ASSERT_EQ(subgraph_output, operator_output);
431 }
432 
TEST_F(ConvertTestQS8ToQS8,matches_operator_api)433 TEST_F(ConvertTestQS8ToQS8, matches_operator_api)
434 {
435   const int8_t input_zero_point = i8dist(rng);
436   const int8_t output_zero_point = i8dist(rng);
437   const float input_scale = std::uniform_real_distribution<float>(0.25f, 4.0f)(rng);
438   const float output_scale = std::uniform_real_distribution<float>(0.25f, 4.0f)(rng);
439 
440   std::generate(input.begin(), input.end(), [&]() { return i8dist(rng); });
441   std::fill(operator_output.begin(), operator_output.end(), INT8_C(0xA5));
442   std::fill(subgraph_output.begin(), subgraph_output.end(), INT8_C(0x5A));
443 
444   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
445 
446   // Call operator API.
447   xnn_operator_t op = nullptr;
448   const xnn_status status =
449     xnn_create_convert_nc_qs8(channels, channels, channels, input_scale, input_zero_point, output_scale, output_zero_point, /*flags=*/0, &op);
450   if (status == xnn_status_unsupported_hardware) {
451     GTEST_SKIP();
452   }
453 
454   ASSERT_EQ(xnn_status_success, status);
455   ASSERT_NE(nullptr, op);
456   std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_op(op, xnn_delete_operator);
457 
458   ASSERT_EQ(
459     xnn_status_success,
460     xnn_setup_convert_nc_qs8(op, batch_size, input.data(), operator_output.data(), /*threadpool=*/nullptr));
461 
462   ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
463 
464   // Call subgraph API.
465   xnn_subgraph_t subgraph = nullptr;
466   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
467   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
468 
469   input_id = XNN_INVALID_NODE_ID;
470   ASSERT_EQ(
471     xnn_status_success, xnn_define_quantized_tensor_value(
472                           subgraph, xnn_datatype_qint8, input_zero_point, input_scale,
473                           dims.size(), dims.data(), nullptr, 0,
474                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
475   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
476 
477   output_id = XNN_INVALID_NODE_ID;
478   ASSERT_EQ(
479     xnn_status_success, xnn_define_quantized_tensor_value(
480                           subgraph, xnn_datatype_qint8, output_zero_point, output_scale,
481                           dims.size(), dims.data(), nullptr, 1,
482                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
483   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
484 
485   xnn_runtime_t runtime = nullptr;
486   ASSERT_EQ(xnn_status_success, xnn_define_convert(subgraph, input_id, output_id, /*flags=*/0));
487   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
488   ASSERT_NE(nullptr, runtime);
489   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
490   const std::array<xnn_external_value, 2> external = {
491     xnn_external_value{input_id, input.data()},
492     xnn_external_value{output_id, subgraph_output.data()}
493   };
494   ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
495   ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
496 
497   ASSERT_EQ(subgraph_output, operator_output);
498 }
499 
TEST_F(ConvertTestQU8ToF32,matches_operator_api)500 TEST_F(ConvertTestQU8ToF32, matches_operator_api)
501 {
502   std::generate(input.begin(), input.end(), [&]() { return u8dist(rng); });
503   std::fill(operator_output.begin(), operator_output.end(), std::nanf(""));
504   std::fill(subgraph_output.begin(), subgraph_output.end(), std::nanf(""));
505 
506   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
507 
508   // Call operator API.
509   xnn_operator_t op = nullptr;
510   const xnn_status status =
511     xnn_create_convert_nc_qu8_f32(channels, channels, channels, scale, unsigned_zero_point, /*flags=*/0, &op);
512   if (status == xnn_status_unsupported_hardware) {
513     GTEST_SKIP();
514   }
515 
516   ASSERT_EQ(xnn_status_success, status);
517   ASSERT_NE(nullptr, op);
518   std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_op(op, xnn_delete_operator);
519 
520   ASSERT_EQ(
521     xnn_status_success,
522     xnn_setup_convert_nc_qu8_f32(op, batch_size, input.data(), operator_output.data(), /*threadpool=*/nullptr));
523 
524   ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
525 
526   // Call subgraph API.
527   xnn_subgraph_t subgraph = nullptr;
528   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
529   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
530 
531   input_id = XNN_INVALID_NODE_ID;
532   ASSERT_EQ(
533     xnn_status_success,
534     xnn_define_quantized_tensor_value(
535       subgraph, xnn_datatype_quint8, unsigned_zero_point, scale, dims.size(), dims.data(), nullptr, 0,
536       /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
537   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
538 
539   output_id = XNN_INVALID_NODE_ID;
540   ASSERT_EQ(
541     xnn_status_success, xnn_define_tensor_value(
542                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, 1,
543                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
544   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
545 
546   xnn_runtime_t runtime = nullptr;
547   ASSERT_EQ(xnn_status_success, xnn_define_convert(subgraph, input_id, output_id, /*flags=*/0));
548   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
549   ASSERT_NE(nullptr, runtime);
550   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
551   std::array<xnn_external_value, 2> external = {
552     xnn_external_value{input_id, input.data()}, xnn_external_value{output_id, subgraph_output.data()}};
553   ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
554   ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
555 
556   ASSERT_EQ(subgraph_output, operator_output);
557 }
558 
TEST_F(ConvertTestQU8ToQU8,matches_operator_api)559 TEST_F(ConvertTestQU8ToQU8, matches_operator_api)
560 {
561   const uint8_t input_zero_point = u8dist(rng);
562   const uint8_t output_zero_point = u8dist(rng);
563   const float input_scale = std::uniform_real_distribution<float>(0.25f, 4.0f)(rng);
564   const float output_scale = std::uniform_real_distribution<float>(0.25f, 4.0f)(rng);
565 
566   std::generate(input.begin(), input.end(), [&]() { return u8dist(rng); });
567   std::fill(operator_output.begin(), operator_output.end(), UINT8_C(0xA5));
568   std::fill(subgraph_output.begin(), subgraph_output.end(), UINT8_C(0x5A));
569 
570   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
571 
572   // Call operator API.
573   xnn_operator_t op = nullptr;
574   const xnn_status status =
575     xnn_create_convert_nc_qu8(channels, channels, channels, input_scale, input_zero_point, output_scale, output_zero_point, /*flags=*/0, &op);
576   if (status == xnn_status_unsupported_hardware) {
577     GTEST_SKIP();
578   }
579 
580   ASSERT_EQ(xnn_status_success, status);
581   ASSERT_NE(nullptr, op);
582   std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_op(op, xnn_delete_operator);
583 
584   ASSERT_EQ(
585     xnn_status_success,
586     xnn_setup_convert_nc_qu8(op, batch_size, input.data(), operator_output.data(), /*threadpool=*/nullptr));
587 
588   ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
589 
590   // Call subgraph API.
591   xnn_subgraph_t subgraph = nullptr;
592   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
593   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
594 
595   input_id = XNN_INVALID_NODE_ID;
596   ASSERT_EQ(
597     xnn_status_success, xnn_define_quantized_tensor_value(
598                           subgraph, xnn_datatype_quint8, input_zero_point, input_scale,
599                           dims.size(), dims.data(), nullptr, 0,
600                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
601   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
602 
603   output_id = XNN_INVALID_NODE_ID;
604   ASSERT_EQ(
605     xnn_status_success, xnn_define_quantized_tensor_value(
606                           subgraph, xnn_datatype_quint8, output_zero_point, output_scale,
607                           dims.size(), dims.data(), nullptr, 1,
608                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
609   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
610 
611   xnn_runtime_t runtime = nullptr;
612   ASSERT_EQ(xnn_status_success, xnn_define_convert(subgraph, input_id, output_id, /*flags=*/0));
613   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
614   ASSERT_NE(nullptr, runtime);
615   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
616   const std::array<xnn_external_value, 2> external = {
617     xnn_external_value{input_id, input.data()},
618     xnn_external_value{output_id, subgraph_output.data()}
619   };
620   ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
621   ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
622 
623   ASSERT_EQ(subgraph_output, operator_output);
624 }
625