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