• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <cstdint>
9 #include <cstddef>
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 ClampTestQS8 = UnaryTest<int8_t>;
23 using ClampTestQU8 = UnaryTest<uint8_t>;
24 using ClampTestF32 = UnaryTest<float>;
25 
TEST_F(ClampTestQS8,define)26 TEST_F(ClampTestQS8, define)
27 {
28   const int32_t input_zero_point = i8dist(rng);
29   const float input_scale = scale_dist(rng);
30   const int32_t output_zero_point = input_zero_point;
31   const float output_scale = input_scale;
32   const int8_t quantized_output_min = std::uniform_int_distribution<int32_t>(-128, 0)(rng);
33   const int8_t quantized_output_max = std::uniform_int_distribution<int32_t>(1, 127)(rng);
34   const float output_min = (quantized_output_min - input_zero_point) * input_scale;
35   const float output_max = (quantized_output_max - input_zero_point) * input_scale;
36 
37   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
38 
39   xnn_subgraph_t subgraph = nullptr;
40   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
41   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
42 
43   input_id = XNN_INVALID_NODE_ID;
44   ASSERT_EQ(
45     xnn_status_success, xnn_define_quantized_tensor_value(
46                           subgraph, xnn_datatype_qint8, input_zero_point, input_scale, dims.size(), dims.data(),
47                           nullptr, 0, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
48   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
49 
50   output_id = XNN_INVALID_NODE_ID;
51   ASSERT_EQ(
52     xnn_status_success, xnn_define_quantized_tensor_value(
53                           subgraph, xnn_datatype_qint8, output_zero_point, output_scale, dims.size(), dims.data(),
54                           nullptr, 1, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
55   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
56 
57   ASSERT_EQ(xnn_status_success, xnn_define_clamp(subgraph, output_min, output_max, input_id, output_id, /*flags=*/0));
58 
59   ASSERT_EQ(subgraph->num_nodes, 1);
60   const struct xnn_node* node = &subgraph->nodes[0];
61   ASSERT_EQ(node->type, xnn_node_type_clamp);
62   ASSERT_EQ(node->compute_type, xnn_compute_type_qs8);
63   ASSERT_EQ(node->num_inputs, 1);
64   ASSERT_EQ(node->inputs[0], input_id);
65   ASSERT_EQ(node->num_outputs, 1);
66   ASSERT_EQ(node->outputs[0], output_id);
67   ASSERT_EQ(node->flags, 0);
68 }
69 
TEST_F(ClampTestQU8,define)70 TEST_F(ClampTestQU8, define)
71 {
72   const int32_t input_zero_point = u8dist(rng);
73   const float input_scale = scale_dist(rng);
74   const int32_t output_zero_point = input_zero_point;
75   const float output_scale = input_scale;
76   const uint8_t quantized_output_min = std::uniform_int_distribution<uint32_t>(0, 127)(rng);
77   const uint8_t quantized_output_max = std::uniform_int_distribution<uint32_t>(128, 255)(rng);
78   const float output_min = (quantized_output_min - input_zero_point) * input_scale;
79   const float output_max = (quantized_output_max - input_zero_point) * input_scale;
80 
81   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
82 
83   xnn_subgraph_t subgraph = nullptr;
84   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
85   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
86 
87   input_id = XNN_INVALID_NODE_ID;
88   ASSERT_EQ(
89     xnn_status_success, xnn_define_quantized_tensor_value(
90                           subgraph, xnn_datatype_quint8, input_zero_point, input_scale, dims.size(), dims.data(),
91                           nullptr, 0, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
92   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
93 
94   output_id = XNN_INVALID_NODE_ID;
95   ASSERT_EQ(
96     xnn_status_success, xnn_define_quantized_tensor_value(
97                           subgraph, xnn_datatype_quint8, output_zero_point, output_scale, dims.size(), dims.data(),
98                           nullptr, 1, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
99   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
100 
101   ASSERT_EQ(xnn_status_success, xnn_define_clamp(subgraph, output_min, output_max, input_id, output_id, /*flags=*/0));
102 
103   ASSERT_EQ(subgraph->num_nodes, 1);
104   const struct xnn_node* node = &subgraph->nodes[0];
105   ASSERT_EQ(node->type, xnn_node_type_clamp);
106   ASSERT_EQ(node->compute_type, xnn_compute_type_qu8);
107   ASSERT_EQ(node->num_inputs, 1);
108   ASSERT_EQ(node->inputs[0], input_id);
109   ASSERT_EQ(node->num_outputs, 1);
110   ASSERT_EQ(node->outputs[0], output_id);
111   ASSERT_EQ(node->flags, 0);
112 }
113 
TEST_F(ClampTestF32,define)114 TEST_F(ClampTestF32, define)
115 {
116   const float output_min = std::uniform_real_distribution<float>(-128.0f, 0.0f)(rng);
117   const float output_max = std::uniform_real_distribution<float>(1.0f, 127.0f)(rng);
118 
119   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
120 
121   xnn_subgraph_t subgraph = nullptr;
122   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
123   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
124 
125   input_id = XNN_INVALID_NODE_ID;
126   ASSERT_EQ(
127     xnn_status_success, xnn_define_tensor_value(
128                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, 0,
129                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
130   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
131 
132   output_id = XNN_INVALID_NODE_ID;
133   ASSERT_EQ(
134     xnn_status_success, xnn_define_tensor_value(
135                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, 1,
136                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
137   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
138 
139   ASSERT_EQ(xnn_status_success, xnn_define_clamp(subgraph, output_min, output_max, input_id, output_id, /*flags=*/0));
140 
141   ASSERT_EQ(subgraph->num_nodes, 1);
142   const struct xnn_node* node = &subgraph->nodes[0];
143   ASSERT_EQ(node->type, xnn_node_type_clamp);
144   ASSERT_EQ(node->compute_type, xnn_compute_type_fp32);
145   ASSERT_EQ(node->num_inputs, 1);
146   ASSERT_EQ(node->inputs[0], input_id);
147   ASSERT_EQ(node->num_outputs, 1);
148   ASSERT_EQ(node->outputs[0], output_id);
149   ASSERT_EQ(node->flags, 0);
150 }
151 
TEST_F(ClampTestQS8,matches_operator_api)152 TEST_F(ClampTestQS8, matches_operator_api)
153 {
154   const int32_t input_zero_point = i8dist(rng);
155   const float input_scale = scale_dist(rng);
156   const int32_t output_zero_point = input_zero_point;
157   const float output_scale = input_scale;
158   const int8_t quantized_output_min = std::uniform_int_distribution<int32_t>(-128, 0)(rng);
159   const int8_t quantized_output_max = std::uniform_int_distribution<int32_t>(1, 127)(rng);
160   const float output_min = (quantized_output_min - input_zero_point) * input_scale;
161   const float output_max = (quantized_output_max - input_zero_point) * input_scale;
162   std::generate(input.begin(), input.end(), [&]() { return i8dist(rng); });
163   std::fill(operator_output.begin(), operator_output.end(), INT8_C(0xA5));
164   std::fill(subgraph_output.begin(), subgraph_output.end(), INT8_C(0xA5));
165 
166   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
167 
168   // Call operator API.
169   xnn_operator_t op = nullptr;
170   const xnn_status status =
171     xnn_create_clamp_nc_s8(channels, channels, channels, quantized_output_min, quantized_output_max, /*flags=*/0, &op);
172   if (status == xnn_status_unsupported_hardware) {
173     GTEST_SKIP();
174   }
175   ASSERT_EQ(xnn_status_success, status);
176   ASSERT_NE(nullptr, op);
177   std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_op(op, xnn_delete_operator);
178 
179   ASSERT_EQ(
180     xnn_status_success,
181     xnn_setup_clamp_nc_s8(op, batch_size, input.data(), operator_output.data(), /*threadpool=*/nullptr));
182   ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
183 
184   // Call subgraph API.
185   xnn_subgraph_t subgraph = nullptr;
186   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
187   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
188   input_id = XNN_INVALID_NODE_ID;
189   ASSERT_EQ(
190     xnn_status_success, xnn_define_quantized_tensor_value(
191                           subgraph, xnn_datatype_qint8, input_zero_point, input_scale, dims.size(), dims.data(),
192                           nullptr, /*external_id=*/0, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
193   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
194 
195   output_id = XNN_INVALID_NODE_ID;
196   ASSERT_EQ(
197     xnn_status_success, xnn_define_quantized_tensor_value(
198                           subgraph, xnn_datatype_qint8, output_zero_point, output_scale, dims.size(), dims.data(),
199                           nullptr, /*external_id=*/1, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
200   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
201 
202   ASSERT_EQ(xnn_status_success, xnn_define_clamp(subgraph, output_min, output_max, input_id, output_id, /*flags=*/0));
203 
204   xnn_runtime_t runtime = nullptr;
205   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
206   ASSERT_NE(nullptr, runtime);
207   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
208 
209   std::array<xnn_external_value, 2> external = {
210     xnn_external_value{input_id, input.data()}, xnn_external_value{output_id, subgraph_output.data()}};
211   ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
212   ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
213 
214   ASSERT_EQ(subgraph_output, operator_output);
215 }
216 
TEST_F(ClampTestQU8,matches_operator_api)217 TEST_F(ClampTestQU8, matches_operator_api)
218 {
219   const int32_t input_zero_point = u8dist(rng);
220   const float input_scale = scale_dist(rng);
221   const int32_t output_zero_point = input_zero_point;
222   const float output_scale = input_scale;
223   const uint8_t quantized_output_min = std::uniform_int_distribution<uint32_t>(0, 127)(rng);
224   const uint8_t quantized_output_max = std::uniform_int_distribution<uint32_t>(128, 255)(rng);
225   const float output_min = (quantized_output_min - input_zero_point) * input_scale;
226   const float output_max = (quantized_output_max - input_zero_point) * input_scale;
227   std::generate(input.begin(), input.end(), [&]() { return u8dist(rng); });
228   std::fill(operator_output.begin(), operator_output.end(), UINT8_C(0xA5));
229   std::fill(subgraph_output.begin(), subgraph_output.end(), UINT8_C(0xA5));
230 
231   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
232 
233   // Call operator API.
234   xnn_operator_t op = nullptr;
235   const xnn_status status =
236     xnn_create_clamp_nc_u8(channels, channels, channels, quantized_output_min, quantized_output_max, /*flags=*/0, &op);
237   if (status == xnn_status_unsupported_hardware) {
238     GTEST_SKIP();
239   }
240   ASSERT_EQ(xnn_status_success, status);
241   ASSERT_NE(nullptr, op);
242   std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_op(op, xnn_delete_operator);
243 
244   ASSERT_EQ(
245     xnn_status_success,
246     xnn_setup_clamp_nc_u8(op, batch_size, input.data(), operator_output.data(), /*threadpool=*/nullptr));
247   ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
248 
249   // Call subgraph API.
250   xnn_subgraph_t subgraph = nullptr;
251   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
252   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
253   input_id = XNN_INVALID_NODE_ID;
254   ASSERT_EQ(
255     xnn_status_success, xnn_define_quantized_tensor_value(
256                           subgraph, xnn_datatype_quint8, input_zero_point, input_scale, dims.size(), dims.data(),
257                           nullptr, /*external_id=*/0, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
258   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
259 
260   output_id = XNN_INVALID_NODE_ID;
261   ASSERT_EQ(
262     xnn_status_success, xnn_define_quantized_tensor_value(
263                           subgraph, xnn_datatype_quint8, output_zero_point, output_scale, dims.size(), dims.data(),
264                           nullptr, /*external_id=*/1, /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
265   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
266 
267   ASSERT_EQ(xnn_status_success, xnn_define_clamp(subgraph, output_min, output_max, input_id, output_id, /*flags=*/0));
268 
269   xnn_runtime_t runtime = nullptr;
270   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
271   ASSERT_NE(nullptr, runtime);
272   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
273 
274   std::array<xnn_external_value, 2> external = {
275     xnn_external_value{input_id, input.data()}, xnn_external_value{output_id, subgraph_output.data()}};
276   ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
277   ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
278 
279   ASSERT_EQ(subgraph_output, operator_output);
280 }
281 
TEST_F(ClampTestF32,matches_operator_api)282 TEST_F(ClampTestF32, matches_operator_api)
283 {
284   const float output_min = std::uniform_real_distribution<float>(-128.0f, 0.0f)(rng);
285   const float output_max = std::uniform_real_distribution<float>(1.0f, 127.0f)(rng);
286   std::uniform_real_distribution<float> f32dist(-255.0f, 255.0f);
287   std::generate(input.begin(), input.end(), [&]() { return f32dist(rng); });
288   std::fill(operator_output.begin(), operator_output.end(), nanf(""));
289   std::fill(subgraph_output.begin(), subgraph_output.end(), nanf(""));
290 
291   ASSERT_EQ(xnn_status_success, xnn_initialize(/*allocator=*/nullptr));
292 
293   // Call operator API.
294   xnn_operator_t op = nullptr;
295   const xnn_status status =
296     xnn_create_clamp_nc_f32(channels, channels, channels, output_min, output_max, /*flags=*/0, &op);
297   if (status == xnn_status_unsupported_hardware) {
298     GTEST_SKIP();
299   }
300 
301   ASSERT_EQ(xnn_status_success, status);
302   ASSERT_NE(nullptr, op);
303   std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_op(op, xnn_delete_operator);
304 
305   ASSERT_EQ(
306     xnn_status_success,
307     xnn_setup_clamp_nc_f32(op, batch_size, input.data(), operator_output.data(), /*threadpool=*/nullptr));
308 
309   ASSERT_EQ(xnn_status_success, xnn_run_operator(op, /*threadpool=*/nullptr));
310 
311   // Call subgraph API.
312   xnn_subgraph_t subgraph = nullptr;
313   ASSERT_EQ(xnn_status_success, xnn_create_subgraph(/*external_value_ids=*/2, /*flags=*/0, &subgraph));
314   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
315   input_id = XNN_INVALID_NODE_ID;
316   ASSERT_EQ(
317     xnn_status_success, xnn_define_tensor_value(
318                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, /*external_id=*/0,
319                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id));
320   ASSERT_NE(input_id, XNN_INVALID_NODE_ID);
321 
322   output_id = XNN_INVALID_NODE_ID;
323   ASSERT_EQ(
324     xnn_status_success, xnn_define_tensor_value(
325                           subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, /*external_id=*/1,
326                           /*flags=*/XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id));
327   ASSERT_NE(output_id, XNN_INVALID_NODE_ID);
328 
329   xnn_runtime_t runtime = nullptr;
330   ASSERT_EQ(xnn_status_success, xnn_define_clamp(subgraph, output_min, output_max, input_id, output_id, /*flags=*/0));
331   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v3(subgraph, nullptr, nullptr, /*flags=*/0, &runtime));
332   ASSERT_NE(nullptr, runtime);
333   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
334   std::array<xnn_external_value, 2> external = {
335     xnn_external_value{input_id, input.data()}, xnn_external_value{output_id, subgraph_output.data()}};
336   ASSERT_EQ(xnn_status_success, xnn_setup_runtime(runtime, external.size(), external.data()));
337   ASSERT_EQ(xnn_status_success, xnn_invoke_runtime(runtime));
338 
339   ASSERT_EQ(subgraph_output, operator_output);
340 }
341