xref: /aosp_15_r20/external/XNNPACK/test/static-transpose.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 StaticTransposeTestQS8 = UnaryTest<int8_t, int8_t, /*min_dim=*/1>;
23 using StaticTransposeTestQU8 = UnaryTest<uint8_t, uint8_t, /*min_dim=*/1>;
24 using StaticTransposeTestF32 = UnaryTest<float, float, /*min_dim=*/1>;
25 
26 namespace {
RandomPermutation(const std::vector<size_t> & input,Rng rng)27 template <typename Rng> std::vector<size_t> RandomPermutation(const std::vector<size_t>& input, Rng rng)
28 {
29   std::vector<size_t> perm = std::vector<size_t>(input);
30   std::iota(perm.begin(), perm.end(), 0);
31   std::shuffle(perm.begin(), perm.end(), rng);
32   return perm;
33 }
34 
PermuteInputDimensions(const std::vector<size_t> & input,std::vector<size_t> perm)35 std::vector<size_t> PermuteInputDimensions(const std::vector<size_t>& input, std::vector<size_t> perm)
36 {
37   std::vector<size_t> output = input;
38   for (size_t i = 0; i < input.size(); i++) {
39     output[i] = input[perm[i]];
40   }
41   return output;
42 }
43 }  // namespace
44 
TEST_F(StaticTransposeTestQS8,define)45 TEST_F(StaticTransposeTestQS8, define)
46 {
47   const int32_t input_zero_point = i8dist(rng);
48   const float input_scale = scale_dist(rng);
49   const int32_t output_zero_point = input_zero_point;
50   const float output_scale = input_scale;
51   std::vector<size_t> perm = RandomPermutation(dims, rng);
52   std::vector<size_t> output_dims = PermuteInputDimensions(dims, perm);
53 
54   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
55 
56   xnn_subgraph_t subgraph = nullptr;
57   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
58   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
59 
60   input_id = XNN_INVALID_NODE_ID;
61   ASSERT_EQ(
62     xnn_status_success, xnn_define_quantized_tensor_value(
63                           subgraph, xnn_datatype_qint8, input_zero_point, input_scale, dims.size(), dims.data(),
64                           nullptr, 0, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
65   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
66 
67   output_id = XNN_INVALID_NODE_ID;
68   ASSERT_EQ(
69     xnn_status_success, xnn_define_quantized_tensor_value(
70                           subgraph, xnn_datatype_qint8, output_zero_point, output_scale, output_dims.size(),
71                           output_dims.data(), nullptr, 1, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
72   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
73 
74   ASSERT_EQ(
75     xnn_status_success,
76     xnn_define_static_transpose(subgraph, perm.size(), perm.data(), input_id, output_id, /*flags=*/0));
77 
78   ASSERT_EQ(subgraph->num_nodes, 1);
79   const struct xnn_node* node = &subgraph->nodes[0];
80   ASSERT_EQ(node->type, xnn_node_type_static_transpose);
81   ASSERT_EQ(node->compute_type, xnn_compute_type_qs8);
82   ASSERT_EQ(node->params.transpose.num_dims, dims.size());
83   for (size_t i = 0; i < dims.size(); i++) {
84     ASSERT_EQ(node->params.transpose.perm[i], perm[i]);
85   }
86   ASSERT_EQ(node->num_inputs, 1);
87   ASSERT_EQ(node->inputs[0], input_id);
88   ASSERT_EQ(node->num_outputs, 1);
89   ASSERT_EQ(node->outputs[0], output_id);
90   ASSERT_EQ(node->flags, 0);
91 }
92 
TEST_F(StaticTransposeTestQU8,define)93 TEST_F(StaticTransposeTestQU8, define)
94 {
95   const int32_t input_zero_point = u8dist(rng);
96   const float input_scale = scale_dist(rng);
97   const int32_t output_zero_point = input_zero_point;
98   const float output_scale = input_scale;
99   std::vector<size_t> perm = RandomPermutation(dims, rng);
100   std::vector<size_t> output_dims = PermuteInputDimensions(dims, perm);
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_quint8, input_zero_point, input_scale, dims.size(), dims.data(),
112                           nullptr, 0, /*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_quantized_tensor_value(
118                           subgraph, xnn_datatype_quint8, output_zero_point, output_scale, output_dims.size(),
119                           output_dims.data(), nullptr, 1, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
120   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
121 
122   ASSERT_EQ(
123     xnn_status_success,
124     xnn_define_static_transpose(subgraph, perm.size(), perm.data(), input_id, output_id, /*flags=*/0));
125 
126   ASSERT_EQ(subgraph->num_nodes, 1);
127   const struct xnn_node* node = &subgraph->nodes[0];
128   ASSERT_EQ(node->type, xnn_node_type_static_transpose);
129   ASSERT_EQ(node->compute_type, xnn_compute_type_qu8);
130   for (size_t i = 0; i < dims.size(); i++) {
131     ASSERT_EQ(node->params.transpose.perm[i], perm[i]);
132   }
133   ASSERT_EQ(node->num_inputs, 1);
134   ASSERT_EQ(node->inputs[0], input_id);
135   ASSERT_EQ(node->num_outputs, 1);
136   ASSERT_EQ(node->outputs[0], output_id);
137   ASSERT_EQ(node->flags, 0);
138 }
139 
TEST_F(StaticTransposeTestF32,define)140 TEST_F(StaticTransposeTestF32, define)
141 {
142   std::vector<size_t> perm = RandomPermutation(dims, rng);
143   std::vector<size_t> output_dims = PermuteInputDimensions(dims, perm);
144 
145   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
146 
147   xnn_subgraph_t subgraph = nullptr;
148   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
149   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
150 
151   input_id = XNN_INVALID_NODE_ID;
152   ASSERT_EQ(
153     xnn_status_success, xnn_define_tensor_value(
154                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, 0,
155                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
156   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
157 
158   output_id = XNN_INVALID_NODE_ID;
159   ASSERT_EQ(
160     xnn_status_success, xnn_define_tensor_value(
161                           subgraph, xnn_datatype_fp32, output_dims.size(), output_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(
166     xnn_status_success,
167     xnn_define_static_transpose(subgraph, perm.size(), perm.data(), input_id, output_id, /*flags=*/0));
168 
169   ASSERT_EQ(subgraph->num_nodes, 1);
170   const struct xnn_node* node = &subgraph->nodes[0];
171   ASSERT_EQ(node->type, xnn_node_type_static_transpose);
172   ASSERT_EQ(node->compute_type, xnn_compute_type_fp32);
173   for (size_t i = 0; i < dims.size(); i++) {
174     ASSERT_EQ(node->params.transpose.perm[i], perm[i]);
175   }
176   ASSERT_EQ(node->num_inputs, 1);
177   ASSERT_EQ(node->inputs[0], input_id);
178   ASSERT_EQ(node->num_outputs, 1);
179   ASSERT_EQ(node->outputs[0], output_id);
180   ASSERT_EQ(node->flags, 0);
181 }
182 
TEST_F(StaticTransposeTestQS8,matches_operator_api)183 TEST_F(StaticTransposeTestQS8, matches_operator_api)
184 {
185   const int32_t input_zero_point = i8dist(rng);
186   const float input_scale = scale_dist(rng);
187   const int32_t output_zero_point = input_zero_point;
188   const float output_scale = input_scale;
189   std::generate(input.begin(), input.end(), [&]() { return i8dist(rng); });
190   std::fill(operator_output.begin(), operator_output.end(), INT8_C(0xA5));
191   std::fill(subgraph_output.begin(), subgraph_output.end(), INT8_C(0xA5));
192   std::vector<size_t> perm = RandomPermutation(dims, rng);
193   std::vector<size_t> output_dims = PermuteInputDimensions(dims, perm);
194 
195   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
196 
197   // Call operator API.
198   xnn_operator_t op = nullptr;
199   const xnn_status status = xnn_create_transpose_nd_x8(/*flags=*/0, &op);
200   if (status == xnn_status_unsupported_hardware) {
201     GTEST_SKIP();
202   }
203   ASSERT_EQ(xnn_status_success, status);
204   ASSERT_NE(nullptr, 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,
209     xnn_setup_transpose_nd_x8(
210       op, input.data(), operator_output.data(), dims.size(), dims.data(), perm.data(), /*threadpool=*/nullptr));
211   ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
212 
213   // Call subgraph API.
214   xnn_subgraph_t subgraph = nullptr;
215   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
216   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
217   input_id = XNN_INVALID_NODE_ID;
218   ASSERT_EQ(
219     xnn_status_success, xnn_define_quantized_tensor_value(
220                           subgraph, xnn_datatype_qint8, input_zero_point, input_scale, dims.size(), dims.data(),
221                           nullptr, /*external_id=*/0, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
222   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
223 
224   output_id = XNN_INVALID_NODE_ID;
225   ASSERT_EQ(
226     xnn_status_success, xnn_define_quantized_tensor_value(
227                           subgraph, xnn_datatype_qint8, output_zero_point, output_scale, dims.size(), dims.data(),
228                           nullptr, /*external_id=*/1, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
229   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
230 
231   ASSERT_EQ(
232     xnn_status_success,
233     xnn_define_static_transpose(subgraph, perm.size(), perm.data(), input_id, output_id, /*flags=*/0));
234 
235   xnn_runtime_t runtime = nullptr;
236   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
237   ASSERT_NE(nullptr, runtime);
238   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
239 
240   std::array<xnn_external_value, 2> external = {
241     xnn_external_value{input_id, input.data()}, xnn_external_value{output_id, subgraph_output.data()}};
242   ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
243   ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
244 
245   ASSERT_EQ(subgraph_output, operator_output);
246 }
247 
TEST_F(StaticTransposeTestQU8,matches_operator_api)248 TEST_F(StaticTransposeTestQU8, matches_operator_api)
249 {
250   const int32_t input_zero_point = u8dist(rng);
251   const float input_scale = scale_dist(rng);
252   const int32_t output_zero_point = input_zero_point;
253   const float output_scale = input_scale;
254   std::generate(input.begin(), input.end(), [&]() { return u8dist(rng); });
255   std::fill(operator_output.begin(), operator_output.end(), UINT8_C(0xA5));
256   std::fill(subgraph_output.begin(), subgraph_output.end(), UINT8_C(0xA5));
257   std::vector<size_t> perm = RandomPermutation(dims, rng);
258   std::vector<size_t> output_dims = PermuteInputDimensions(dims, perm);
259 
260   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
261 
262   // Call operator API.
263   xnn_operator_t op = nullptr;
264   const xnn_status status = xnn_create_transpose_nd_x8(/*flags=*/0, &op);
265   if (status == xnn_status_unsupported_hardware) {
266     GTEST_SKIP();
267   }
268   ASSERT_EQ(xnn_status_success, status);
269   ASSERT_NE(nullptr, op);
270   std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_op(op, xnn_delete_operator);
271 
272   ASSERT_EQ(
273     xnn_status_success,
274     xnn_setup_transpose_nd_x8(
275       op, input.data(), operator_output.data(), dims.size(), dims.data(), perm.data(), /*threadpool=*/nullptr));
276   ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
277 
278   // Call subgraph API.
279   xnn_subgraph_t subgraph = nullptr;
280   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
281   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
282   input_id = XNN_INVALID_NODE_ID;
283   ASSERT_EQ(
284     xnn_status_success, xnn_define_quantized_tensor_value(
285                           subgraph, xnn_datatype_quint8, input_zero_point, input_scale, dims.size(), dims.data(),
286                           nullptr, /*external_id=*/0, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
287   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
288 
289   output_id = XNN_INVALID_NODE_ID;
290   ASSERT_EQ(
291     xnn_status_success, xnn_define_quantized_tensor_value(
292                           subgraph, xnn_datatype_quint8, output_zero_point, output_scale, dims.size(), dims.data(),
293                           nullptr, /*external_id=*/1, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
294   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
295 
296   ASSERT_EQ(
297     xnn_status_success,
298     xnn_define_static_transpose(subgraph, perm.size(), perm.data(), input_id, output_id, /*flags=*/0));
299 
300   xnn_runtime_t runtime = nullptr;
301   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
302   ASSERT_NE(nullptr, runtime);
303   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
304 
305   std::array<xnn_external_value, 2> external = {
306     xnn_external_value{input_id, input.data()}, xnn_external_value{output_id, subgraph_output.data()}};
307   ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
308   ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
309 
310   ASSERT_EQ(subgraph_output, operator_output);
311 }
312 
TEST_F(StaticTransposeTestF32,matches_operator_api)313 TEST_F(StaticTransposeTestF32, matches_operator_api)
314 {
315   std::generate(input.begin(), input.end(), [&]() { return f32dist(rng); });
316   std::fill(operator_output.begin(), operator_output.end(), nanf(""));
317   std::fill(subgraph_output.begin(), subgraph_output.end(), nanf(""));
318   std::vector<size_t> perm = RandomPermutation(dims, rng);
319   std::vector<size_t> output_dims = PermuteInputDimensions(dims, perm);
320 
321   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
322 
323   // Call operator API.
324   xnn_operator_t op = nullptr;
325   const xnn_status status = xnn_create_transpose_nd_x32(/*flags=*/0, &op);
326   if (status == xnn_status_unsupported_hardware) {
327     GTEST_SKIP();
328   }
329 
330   ASSERT_EQ(xnn_status_success, status);
331   ASSERT_NE(nullptr, op);
332   std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_op(op, xnn_delete_operator);
333 
334   ASSERT_EQ(
335     xnn_status_success,
336     xnn_setup_transpose_nd_x32(
337       op, input.data(), operator_output.data(), dims.size(), dims.data(), perm.data(), /*threadpool=*/nullptr));
338 
339   ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
340 
341   // Call subgraph API.
342   xnn_subgraph_t subgraph = nullptr;
343   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
344   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
345   input_id = XNN_INVALID_NODE_ID;
346   ASSERT_EQ(
347     xnn_status_success, xnn_define_tensor_value(
348                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, /*external_id=*/0,
349                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
350   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
351 
352   output_id = XNN_INVALID_NODE_ID;
353   ASSERT_EQ(
354     xnn_status_success, xnn_define_tensor_value(
355                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, /*external_id=*/1,
356                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
357   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
358 
359   xnn_runtime_t runtime = nullptr;
360   ASSERT_EQ(
361     xnn_status_success,
362     xnn_define_static_transpose(subgraph, perm.size(), perm.data(), input_id, output_id, /*flags=*/0));
363   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
364   ASSERT_NE(nullptr, runtime);
365   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
366   std::array<xnn_external_value, 2> external = {
367     xnn_external_value{input_id, input.data()}, xnn_external_value{output_id, subgraph_output.data()}};
368   ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
369   ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
370 
371   ASSERT_EQ(subgraph_output, operator_output);
372 }
373