blob: e9e477a7bbfce49914d761c5b1b1c4653bb75a0a [file] [log] [blame]
Marat Dukhanc07cb7f2019-11-14 15:32:05 -08001// Copyright 2019 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#pragma once
7
8#include <gtest/gtest.h>
9
10#include <algorithm>
11#include <cassert>
12#include <cstddef>
13#include <cstdlib>
14#include <functional>
15#include <random>
16#include <vector>
17
Frank Barchard9c1a7352020-06-04 20:15:01 -070018#include <fp16.h>
19
Marat Dukhanc07cb7f2019-11-14 15:32:05 -080020#include <xnnpack.h>
21#include <xnnpack/params-init.h>
22#include <xnnpack/params.h>
23
24
Marat Dukhan10f1fe02021-05-13 12:51:12 -070025class VBinaryCMicrokernelTester {
Marat Dukhanc07cb7f2019-11-14 15:32:05 -080026 public:
27 enum class OpType {
28 AddC,
Marat Dukhan77ca6302019-12-06 12:48:15 -080029 DivC,
30 RDivC,
Marat Dukhan403b7d42019-12-05 12:49:11 -080031 MaxC,
32 MinC,
Marat Dukhanc07cb7f2019-11-14 15:32:05 -080033 MulC,
Marat Dukhan13bafb02020-06-05 00:43:11 -070034 SqrDiffC,
Marat Dukhanc07cb7f2019-11-14 15:32:05 -080035 SubC,
36 RSubC,
37 };
38
39 enum class Variant {
40 Native,
41 Scalar,
42 };
43
Marat Dukhan10f1fe02021-05-13 12:51:12 -070044 inline VBinaryCMicrokernelTester& batch_size(size_t batch_size) {
Marat Dukhanc07cb7f2019-11-14 15:32:05 -080045 assert(batch_size != 0);
46 this->batch_size_ = batch_size;
47 return *this;
48 }
49
50 inline size_t batch_size() const {
51 return this->batch_size_;
52 }
53
Marat Dukhan10f1fe02021-05-13 12:51:12 -070054 inline VBinaryCMicrokernelTester& inplace(bool inplace) {
Marat Dukhanc07cb7f2019-11-14 15:32:05 -080055 this->inplace_ = inplace;
56 return *this;
57 }
58
59 inline bool inplace() const {
60 return this->inplace_;
61 }
62
Marat Dukhan10f1fe02021-05-13 12:51:12 -070063 inline VBinaryCMicrokernelTester& qmin(uint8_t qmin) {
Marat Dukhanc07cb7f2019-11-14 15:32:05 -080064 this->qmin_ = qmin;
65 return *this;
66 }
67
68 inline uint8_t qmin() const {
69 return this->qmin_;
70 }
71
Marat Dukhan10f1fe02021-05-13 12:51:12 -070072 inline VBinaryCMicrokernelTester& qmax(uint8_t qmax) {
Marat Dukhanc07cb7f2019-11-14 15:32:05 -080073 this->qmax_ = qmax;
74 return *this;
75 }
76
77 inline uint8_t qmax() const {
78 return this->qmax_;
79 }
80
Marat Dukhan10f1fe02021-05-13 12:51:12 -070081 inline VBinaryCMicrokernelTester& iterations(size_t iterations) {
Marat Dukhanc07cb7f2019-11-14 15:32:05 -080082 this->iterations_ = iterations;
83 return *this;
84 }
85
86 inline size_t iterations() const {
87 return this->iterations_;
88 }
89
Frank Barchardbf31e3f2020-05-12 14:00:07 -070090 void Test(xnn_f16_vbinary_ukernel_function vbinaryc, OpType op_type) const {
Frank Barchardd793f6c2020-05-08 13:37:43 -070091 std::random_device random_device;
92 auto rng = std::mt19937(random_device());
Frank Barchard967712d2021-03-22 12:01:44 -070093 auto f32rng = std::bind(std::uniform_real_distribution<float>(1.0e-3f, 1.0f), rng);
Frank Barchardd793f6c2020-05-08 13:37:43 -070094 auto f16rng = std::bind(fp16_ieee_from_fp32_value, f32rng);
95
96 std::vector<uint16_t> a(batch_size() + XNN_EXTRA_BYTES / sizeof(uint16_t));
97 const uint16_t b = f16rng();
98 std::vector<uint16_t> y(batch_size() + (inplace() ? XNN_EXTRA_BYTES / sizeof(uint16_t) : 0));
99 std::vector<float> y_ref(batch_size());
100 for (size_t iteration = 0; iteration < iterations(); iteration++) {
101 std::generate(a.begin(), a.end(), std::ref(f16rng));
102 if (inplace()) {
103 std::generate(y.begin(), y.end(), std::ref(f16rng));
104 } else {
105 std::fill(y.begin(), y.end(), nanf(""));
106 }
107 const uint16_t* a_data = inplace() ? y.data() : a.data();
108
109 // Compute reference results.
110 for (size_t i = 0; i < batch_size(); i++) {
111 switch (op_type) {
112 case OpType::AddC:
113 y_ref[i] = fp16_ieee_to_fp32_value(a_data[i]) + fp16_ieee_to_fp32_value(b);
114 break;
115 case OpType::DivC:
116 y_ref[i] = fp16_ieee_to_fp32_value(a_data[i]) / fp16_ieee_to_fp32_value(b);
117 break;
118 case OpType::RDivC:
119 y_ref[i] = fp16_ieee_to_fp32_value(b) / fp16_ieee_to_fp32_value(a_data[i]);
120 break;
121 case OpType::MaxC:
122 y_ref[i] = std::max<float>(fp16_ieee_to_fp32_value(a_data[i]), fp16_ieee_to_fp32_value(b));
123 break;
124 case OpType::MinC:
125 y_ref[i] = std::min<float>(fp16_ieee_to_fp32_value(a_data[i]), fp16_ieee_to_fp32_value(b));
126 break;
127 case OpType::MulC:
128 y_ref[i] = fp16_ieee_to_fp32_value(a_data[i]) * fp16_ieee_to_fp32_value(b);
129 break;
Marat Dukhan13bafb02020-06-05 00:43:11 -0700130 case OpType::SqrDiffC:
131 {
132 const float diff = fp16_ieee_to_fp32_value(a_data[i]) - fp16_ieee_to_fp32_value(b);
133 y_ref[i] = diff * diff;
134 break;
135 }
Frank Barchardd793f6c2020-05-08 13:37:43 -0700136 case OpType::SubC:
137 y_ref[i] = fp16_ieee_to_fp32_value(a_data[i]) - fp16_ieee_to_fp32_value(b);
138 break;
139 case OpType::RSubC:
140 y_ref[i] = fp16_ieee_to_fp32_value(b) - fp16_ieee_to_fp32_value(a_data[i]);
141 break;
142 }
143 }
144 // Call optimized micro-kernel.
145 vbinaryc(batch_size() * sizeof(uint16_t), a_data, &b, y.data(), nullptr);
146
147 // Verify results.
148 for (size_t i = 0; i < batch_size(); i++) {
Frank Barchard2b9d29b2020-09-17 12:03:39 -0700149 ASSERT_NEAR(fp16_ieee_to_fp32_value(y[i]), y_ref[i], std::max(1.0e-4f, std::abs(y_ref[i]) * 1.0e-2f))
Frank Barchardd793f6c2020-05-08 13:37:43 -0700150 << "at " << i << " / " << batch_size();
151 }
152 }
153 }
154
Frank Barchardbf31e3f2020-05-12 14:00:07 -0700155 void Test(xnn_f16_vbinary_minmax_ukernel_function vbinaryc_minmax, OpType op_type) const {
Frank Barchardd793f6c2020-05-08 13:37:43 -0700156 std::random_device random_device;
157 auto rng = std::mt19937(random_device());
Frank Barchard967712d2021-03-22 12:01:44 -0700158 auto f32rng = std::bind(std::uniform_real_distribution<float>(1.0e-3f, 1.0f), rng);
Frank Barchardd793f6c2020-05-08 13:37:43 -0700159 auto f16rng = std::bind(fp16_ieee_from_fp32_value, f32rng);
160
161 std::vector<uint16_t> a(batch_size() + XNN_EXTRA_BYTES / sizeof(uint16_t));
162 const uint16_t b = f16rng();
163 std::vector<uint16_t> y(batch_size() + (inplace() ? XNN_EXTRA_BYTES / sizeof(uint16_t) : 0));
164 std::vector<float> y_ref(batch_size());
165 for (size_t iteration = 0; iteration < iterations(); iteration++) {
166 std::generate(a.begin(), a.end(), std::ref(f16rng));
167 if (inplace()) {
168 std::generate(y.begin(), y.end(), std::ref(f16rng));
169 } else {
170 std::fill(y.begin(), y.end(), nanf(""));
171 }
172 const uint16_t* a_data = inplace() ? y.data() : a.data();
173
174 // Compute reference results.
175 for (size_t i = 0; i < batch_size(); i++) {
176 switch (op_type) {
177 case OpType::AddC:
178 y_ref[i] = fp16_ieee_to_fp32_value(a_data[i]) + fp16_ieee_to_fp32_value(b);
179 break;
180 case OpType::DivC:
181 y_ref[i] = fp16_ieee_to_fp32_value(a_data[i]) / fp16_ieee_to_fp32_value(b);
182 break;
183 case OpType::RDivC:
184 y_ref[i] = fp16_ieee_to_fp32_value(b) / fp16_ieee_to_fp32_value(a_data[i]);
185 break;
186 case OpType::MaxC:
187 y_ref[i] = std::max<float>(fp16_ieee_to_fp32_value(a_data[i]), fp16_ieee_to_fp32_value(b));
188 break;
189 case OpType::MinC:
190 y_ref[i] = std::min<float>(fp16_ieee_to_fp32_value(a_data[i]), fp16_ieee_to_fp32_value(b));
191 break;
192 case OpType::MulC:
193 y_ref[i] = fp16_ieee_to_fp32_value(a_data[i]) * fp16_ieee_to_fp32_value(b);
194 break;
Marat Dukhan13bafb02020-06-05 00:43:11 -0700195 case OpType::SqrDiffC:
196 {
197 const float diff = fp16_ieee_to_fp32_value(a_data[i]) - fp16_ieee_to_fp32_value(b);
198 y_ref[i] = diff * diff;
199 break;
200 }
Frank Barchardd793f6c2020-05-08 13:37:43 -0700201 case OpType::SubC:
202 y_ref[i] = fp16_ieee_to_fp32_value(a_data[i]) - fp16_ieee_to_fp32_value(b);
203 break;
204 case OpType::RSubC:
205 y_ref[i] = fp16_ieee_to_fp32_value(b) - fp16_ieee_to_fp32_value(a_data[i]);
206 break;
207 }
208 }
209 const float accumulated_min = *std::min_element(y_ref.cbegin(), y_ref.cend());
210 const float accumulated_max = *std::max_element(y_ref.cbegin(), y_ref.cend());
211 const float accumulated_range = accumulated_max - accumulated_min;
212 const float y_max = fp16_ieee_to_fp32_value(fp16_ieee_from_fp32_value(accumulated_range > 0.0f ?
213 (accumulated_max - accumulated_range / 255.0f * float(255 - qmax())) :
214 +std::numeric_limits<float>::infinity()));
215 const float y_min = fp16_ieee_to_fp32_value(fp16_ieee_from_fp32_value(accumulated_range > 0.0f ?
216 (accumulated_min + accumulated_range / 255.0f * float(qmin())) :
217 -std::numeric_limits<float>::infinity()));
218 for (size_t i = 0; i < batch_size(); i++) {
219 y_ref[i] = std::max<float>(std::min<float>(y_ref[i], y_max), y_min);
220 }
221
Frank Barchard9f3a8432020-06-02 13:59:35 -0700222 // Prepare parameters.
Frank Barchardbf31e3f2020-05-12 14:00:07 -0700223 xnn_f16_minmax_params params = xnn_init_f16_minmax_params(
Frank Barchardd793f6c2020-05-08 13:37:43 -0700224 fp16_ieee_from_fp32_value(y_min),
225 fp16_ieee_from_fp32_value(y_max));
226
227 // Call optimized micro-kernel.
228 vbinaryc_minmax(batch_size() * sizeof(uint16_t), a_data, &b, y.data(), &params);
229
230 // Verify results.
231 for (size_t i = 0; i < batch_size(); i++) {
Frank Barchard2b9d29b2020-09-17 12:03:39 -0700232 ASSERT_NEAR(fp16_ieee_to_fp32_value(y[i]), y_ref[i], std::max(1.0e-4f, std::abs(y_ref[i]) * 1.0e-2f))
Frank Barchardd793f6c2020-05-08 13:37:43 -0700233 << "at " << i << " / " << batch_size();
234 }
235 }
236 }
237
Marat Dukhan1e782c42019-11-21 17:02:40 -0800238 void Test(xnn_f32_vbinary_ukernel_function vbinaryc, OpType op_type, Variant variant = Variant::Native) const {
Marat Dukhanc07cb7f2019-11-14 15:32:05 -0800239 std::random_device random_device;
240 auto rng = std::mt19937(random_device());
241 auto f32rng = std::bind(std::uniform_real_distribution<float>(0.0f, 1.0f), rng);
242
243 std::vector<float> a(batch_size() + XNN_EXTRA_BYTES / sizeof(float));
244 const float b = f32rng();
245 std::vector<float> y(batch_size() + (inplace() ? XNN_EXTRA_BYTES / sizeof(float) : 0));
246 std::vector<float> y_ref(batch_size());
247 for (size_t iteration = 0; iteration < iterations(); iteration++) {
248 std::generate(a.begin(), a.end(), std::ref(f32rng));
249 if (inplace()) {
250 std::generate(y.begin(), y.end(), std::ref(f32rng));
251 } else {
252 std::fill(y.begin(), y.end(), nanf(""));
253 }
254 const float* a_data = inplace() ? y.data() : a.data();
255
256 // Compute reference results.
257 for (size_t i = 0; i < batch_size(); i++) {
258 switch (op_type) {
259 case OpType::AddC:
260 y_ref[i] = a_data[i] + b;
261 break;
Marat Dukhan77ca6302019-12-06 12:48:15 -0800262 case OpType::DivC:
263 y_ref[i] = a_data[i] / b;
264 break;
265 case OpType::RDivC:
266 y_ref[i] = b / a_data[i];
267 break;
Marat Dukhan403b7d42019-12-05 12:49:11 -0800268 case OpType::MaxC:
269 y_ref[i] = std::max<float>(a_data[i], b);
270 break;
271 case OpType::MinC:
272 y_ref[i] = std::min<float>(a_data[i], b);
273 break;
Marat Dukhanc07cb7f2019-11-14 15:32:05 -0800274 case OpType::MulC:
275 y_ref[i] = a_data[i] * b;
276 break;
Marat Dukhan13bafb02020-06-05 00:43:11 -0700277 case OpType::SqrDiffC:
278 {
279 const float diff = a_data[i] - b;
280 y_ref[i] = diff * diff;
281 break;
282 }
Marat Dukhanc07cb7f2019-11-14 15:32:05 -0800283 case OpType::SubC:
284 y_ref[i] = a_data[i] - b;
285 break;
286 case OpType::RSubC:
287 y_ref[i] = b - a_data[i];
288 break;
289 }
290 }
Marat Dukhan91cd2b72020-04-09 23:57:31 -0700291 // Call optimized micro-kernel.
292 vbinaryc(batch_size() * sizeof(float), a_data, &b, y.data(), nullptr);
293
294 // Verify results.
295 for (size_t i = 0; i < batch_size(); i++) {
296 ASSERT_NEAR(y[i], y_ref[i], std::abs(y_ref[i]) * 1.0e-6f)
297 << "at " << i << " / " << batch_size();
298 }
299 }
300 }
301
302 void Test(xnn_f32_vbinary_minmax_ukernel_function vbinaryc_minmax, OpType op_type, Variant variant = Variant::Native) const {
303 std::random_device random_device;
304 auto rng = std::mt19937(random_device());
305 auto f32rng = std::bind(std::uniform_real_distribution<float>(0.0f, 1.0f), rng);
306
307 std::vector<float> a(batch_size() + XNN_EXTRA_BYTES / sizeof(float));
308 const float b = f32rng();
309 std::vector<float> y(batch_size() + (inplace() ? XNN_EXTRA_BYTES / sizeof(float) : 0));
310 std::vector<float> y_ref(batch_size());
311 for (size_t iteration = 0; iteration < iterations(); iteration++) {
312 std::generate(a.begin(), a.end(), std::ref(f32rng));
313 if (inplace()) {
314 std::generate(y.begin(), y.end(), std::ref(f32rng));
315 } else {
316 std::fill(y.begin(), y.end(), nanf(""));
317 }
318 const float* a_data = inplace() ? y.data() : a.data();
319
320 // Compute reference results.
321 for (size_t i = 0; i < batch_size(); i++) {
322 switch (op_type) {
323 case OpType::AddC:
324 y_ref[i] = a_data[i] + b;
325 break;
326 case OpType::DivC:
327 y_ref[i] = a_data[i] / b;
328 break;
329 case OpType::RDivC:
330 y_ref[i] = b / a_data[i];
331 break;
332 case OpType::MaxC:
333 y_ref[i] = std::max<float>(a_data[i], b);
334 break;
335 case OpType::MinC:
336 y_ref[i] = std::min<float>(a_data[i], b);
337 break;
338 case OpType::MulC:
339 y_ref[i] = a_data[i] * b;
340 break;
Marat Dukhan13bafb02020-06-05 00:43:11 -0700341 case OpType::SqrDiffC:
342 {
343 const float diff = a_data[i] - b;
344 y_ref[i] = diff * diff;
345 break;
346 }
Marat Dukhan91cd2b72020-04-09 23:57:31 -0700347 case OpType::SubC:
348 y_ref[i] = a_data[i] - b;
349 break;
350 case OpType::RSubC:
351 y_ref[i] = b - a_data[i];
352 break;
353 }
354 }
Marat Dukhanc07cb7f2019-11-14 15:32:05 -0800355 const float accumulated_min = *std::min_element(y_ref.cbegin(), y_ref.cend());
356 const float accumulated_max = *std::max_element(y_ref.cbegin(), y_ref.cend());
357 const float accumulated_range = accumulated_max - accumulated_min;
358 const float y_max = accumulated_range > 0.0f ?
359 (accumulated_max - accumulated_range / 255.0f * float(255 - qmax())) :
360 +std::numeric_limits<float>::infinity();
361 const float y_min = accumulated_range > 0.0f ?
362 (accumulated_min + accumulated_range / 255.0f * float(qmin())) :
363 -std::numeric_limits<float>::infinity();
364 for (size_t i = 0; i < batch_size(); i++) {
365 y_ref[i] = std::max<float>(std::min<float>(y_ref[i], y_max), y_min);
366 }
367
Frank Barchard9f3a8432020-06-02 13:59:35 -0700368 // Prepare parameters.
Frank Barcharde70dbeb2020-05-01 15:46:41 -0700369 xnn_f32_minmax_params params = { };
Marat Dukhanc07cb7f2019-11-14 15:32:05 -0800370 switch (variant) {
371 case Variant::Native:
Frank Barcharde70dbeb2020-05-01 15:46:41 -0700372 params = xnn_init_f32_minmax_params(y_min, y_max);
Marat Dukhanc07cb7f2019-11-14 15:32:05 -0800373 break;
374 case Variant::Scalar:
Frank Barcharde70dbeb2020-05-01 15:46:41 -0700375 params = xnn_init_scalar_f32_minmax_params(y_min, y_max);
Marat Dukhanc07cb7f2019-11-14 15:32:05 -0800376 break;
377 }
378
379 // Call optimized micro-kernel.
Frank Barcharde70dbeb2020-05-01 15:46:41 -0700380 vbinaryc_minmax(batch_size() * sizeof(float), a_data, &b, y.data(), &params);
Marat Dukhanc07cb7f2019-11-14 15:32:05 -0800381
382 // Verify results.
383 for (size_t i = 0; i < batch_size(); i++) {
384 ASSERT_NEAR(y[i], y_ref[i], std::abs(y_ref[i]) * 1.0e-6f)
385 << "at " << i << " / " << batch_size();
386 }
387 }
388 }
389
Frank Barchard674778d2020-08-08 10:17:25 -0700390 void Test(xnn_f32_vbinary_relu_ukernel_function vbinaryc_relu, OpType op_type, Variant variant = Variant::Native) const {
391 std::random_device random_device;
392 auto rng = std::mt19937(random_device());
393 auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.0f, 1.0f), rng);
394
395 std::vector<float> a(batch_size() + XNN_EXTRA_BYTES / sizeof(float));
396 const float b = f32rng();
397 std::vector<float> y(batch_size() + (inplace() ? XNN_EXTRA_BYTES / sizeof(float) : 0));
398 std::vector<float> y_ref(batch_size());
399 for (size_t iteration = 0; iteration < iterations(); iteration++) {
400 std::generate(a.begin(), a.end(), std::ref(f32rng));
401 if (inplace()) {
402 std::generate(y.begin(), y.end(), std::ref(f32rng));
403 } else {
404 std::fill(y.begin(), y.end(), nanf(""));
405 }
406 const float* a_data = inplace() ? y.data() : a.data();
407
408 // Compute reference results.
409 for (size_t i = 0; i < batch_size(); i++) {
410 switch (op_type) {
411 case OpType::AddC:
412 y_ref[i] = a_data[i] + b;
413 break;
414 case OpType::DivC:
415 y_ref[i] = a_data[i] / b;
416 break;
417 case OpType::RDivC:
418 y_ref[i] = b / a_data[i];
419 break;
420 case OpType::MaxC:
421 y_ref[i] = std::max<float>(a_data[i], b);
422 break;
423 case OpType::MinC:
424 y_ref[i] = std::min<float>(a_data[i], b);
425 break;
426 case OpType::MulC:
427 y_ref[i] = a_data[i] * b;
428 break;
429 case OpType::SqrDiffC:
430 {
431 const float diff = a_data[i] - b;
432 y_ref[i] = diff * diff;
433 break;
434 }
435 case OpType::SubC:
436 y_ref[i] = a_data[i] - b;
437 break;
438 case OpType::RSubC:
439 y_ref[i] = b - a_data[i];
440 break;
441 }
442 }
443 for (size_t i = 0; i < batch_size(); i++) {
444 y_ref[i] = std::max(y_ref[i], 0.0f);
445 }
446
447 // Prepare parameters.
448 xnn_f32_relu_params params = { };
449
450 // Call optimized micro-kernel.
451 vbinaryc_relu(batch_size() * sizeof(float), a_data, &b, y.data(), &params);
452
453 // Verify results.
454 for (size_t i = 0; i < batch_size(); i++) {
455 ASSERT_GE(y[i], 0.0f)
456 << "at " << i << " / " << batch_size();
457 ASSERT_NEAR(y[i], y_ref[i], std::abs(y_ref[i]) * 1.0e-6f)
458 << "at " << i << " / " << batch_size();
459 }
460 }
461 }
462
Marat Dukhanc07cb7f2019-11-14 15:32:05 -0800463 private:
464 size_t batch_size_{1};
465 bool inplace_{false};
466 uint8_t qmin_{0};
467 uint8_t qmax_{255};
468 size_t iterations_{15};
469};