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