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