• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) Facebook, Inc. and its affiliates.
2 // All rights reserved.
3 //
4 // Copyright 2019 Google LLC
5 //
6 // This source code is licensed under the BSD-style license found in the
7 // LICENSE file in the root directory of this source tree.
8 
9 #pragma once
10 
11 #include <gtest/gtest.h>
12 
13 #include <algorithm>
14 #include <cassert>
15 #include <cstddef>
16 #include <cstdlib>
17 #include <functional>
18 #include <random>
19 #include <vector>
20 
21 #include <xnnpack.h>
22 #include <xnnpack/params-init.h>
23 #include <xnnpack/params.h>
24 #include <xnnpack/requantization.h>
25 
26 
27 class VAddMicrokernelTester {
28  public:
29   enum class Variant {
30     Native,
31     Scalar,
32   };
33 
n(size_t n)34   inline VAddMicrokernelTester& n(size_t n) {
35     assert(n != 0);
36     this->n_ = n;
37     return *this;
38   }
39 
n()40   inline size_t n() const {
41     return this->n_;
42   }
43 
inplace_a(bool inplace_a)44   inline VAddMicrokernelTester& inplace_a(bool inplace_a) {
45     this->inplace_a_ = inplace_a;
46     return *this;
47   }
48 
inplace_a()49   inline bool inplace_a() const {
50     return this->inplace_a_;
51   }
52 
inplace_b(bool inplace_b)53   inline VAddMicrokernelTester& inplace_b(bool inplace_b) {
54     this->inplace_b_ = inplace_b;
55     return *this;
56   }
57 
inplace_b()58   inline bool inplace_b() const {
59     return this->inplace_b_;
60   }
61 
a_scale(float a_scale)62   inline VAddMicrokernelTester& a_scale(float a_scale) {
63     assert(a_scale > 0.0f);
64     assert(std::isnormal(a_scale));
65     this->a_scale_ = a_scale;
66     return *this;
67   }
68 
a_scale()69   inline float a_scale() const {
70     return this->a_scale_;
71   }
72 
a_zero_point(uint8_t a_zero_point)73   inline VAddMicrokernelTester& a_zero_point(uint8_t a_zero_point) {
74     this->a_zero_point_ = a_zero_point;
75     return *this;
76   }
77 
a_zero_point()78   inline uint8_t a_zero_point() const {
79     return this->a_zero_point_;
80   }
81 
b_scale(float b_scale)82   inline VAddMicrokernelTester& b_scale(float b_scale) {
83     assert(b_scale > 0.0f);
84     assert(std::isnormal(b_scale));
85     this->b_scale_ = b_scale;
86     return *this;
87   }
88 
b_scale()89   inline float b_scale() const {
90     return this->b_scale_;
91   }
92 
b_zero_point(uint8_t b_zero_point)93   inline VAddMicrokernelTester& b_zero_point(uint8_t b_zero_point) {
94     this->b_zero_point_ = b_zero_point;
95     return *this;
96   }
97 
b_zero_point()98   inline uint8_t b_zero_point() const {
99     return this->b_zero_point_;
100   }
101 
y_scale(float y_scale)102   inline VAddMicrokernelTester& y_scale(float y_scale) {
103     assert(y_scale > 0.0f);
104     assert(std::isnormal(y_scale));
105     this->y_scale_ = y_scale;
106     return *this;
107   }
108 
y_scale()109   inline float y_scale() const {
110     return this->y_scale_;
111   }
112 
y_zero_point(uint8_t y_zero_point)113   inline VAddMicrokernelTester& y_zero_point(uint8_t y_zero_point) {
114     this->y_zero_point_ = y_zero_point;
115     return *this;
116   }
117 
y_zero_point()118   inline uint8_t y_zero_point() const {
119     return this->y_zero_point_;
120   }
121 
qmin(uint8_t qmin)122   inline VAddMicrokernelTester& qmin(uint8_t qmin) {
123     this->qmin_ = qmin;
124     return *this;
125   }
126 
qmin()127   inline uint8_t qmin() const {
128     return this->qmin_;
129   }
130 
qmax(uint8_t qmax)131   inline VAddMicrokernelTester& qmax(uint8_t qmax) {
132     this->qmax_ = qmax;
133     return *this;
134   }
135 
qmax()136   inline uint8_t qmax() const {
137     return this->qmax_;
138   }
139 
iterations(size_t iterations)140   inline VAddMicrokernelTester& iterations(size_t iterations) {
141     this->iterations_ = iterations;
142     return *this;
143   }
144 
iterations()145   inline size_t iterations() const {
146     return this->iterations_;
147   }
148 
149   void Test(xnn_q8_vadd_ukernel_function vadd, Variant variant = Variant::Native) const {
150     std::random_device random_device;
151     auto rng = std::mt19937(random_device());
152     auto u8rng = std::bind(std::uniform_int_distribution<uint8_t>(), rng);
153 
154     std::vector<uint8_t> a(n() + XNN_EXTRA_BYTES / sizeof(uint8_t));
155     std::vector<uint8_t> b(n() + XNN_EXTRA_BYTES / sizeof(uint8_t));
156     std::vector<uint8_t> y(n() + (inplace_a() || inplace_b() ? XNN_EXTRA_BYTES / sizeof(uint8_t) : 0));
157     std::vector<float> y_fp(n());
158     std::vector<uint8_t> y_ref(n());
159     for (size_t iteration = 0; iteration < iterations(); iteration++) {
160       std::generate(a.begin(), a.end(), std::ref(u8rng));
161       std::generate(b.begin(), b.end(), std::ref(u8rng));
162       if (inplace_a() || inplace_b()) {
163         std::generate(y.begin(), y.end(), std::ref(u8rng));
164       } else {
165         std::fill(y.begin(), y.end(), 0xA5);
166       }
167       const uint8_t* a_data = inplace_a() ? y.data() : a.data();
168       const uint8_t* b_data = inplace_b() ? y.data() : b.data();
169 
170       // Prepare quantization parameters.
171       xnn_q8_add_params quantization_params = { };
172       switch (variant) {
173         case Variant::Native:
174           quantization_params = xnn_init_q8_add_params(
175             a_zero_point(), b_zero_point(), y_zero_point(),
176             a_scale() / y_scale(), b_scale() / y_scale(),
177             qmin(), qmax());
178           break;
179         case Variant::Scalar:
180           quantization_params = xnn_init_scalar_q8_add_params(
181             a_zero_point(), b_zero_point(), y_zero_point(),
182             a_scale() / y_scale(), b_scale() / y_scale(),
183             qmin(), qmax());
184           break;
185       }
186       const xnn_q8_add_params scalar_quantization_params =
187           xnn_init_scalar_q8_add_params(
188             a_zero_point(), b_zero_point(), y_zero_point(),
189             a_scale() / y_scale(), b_scale() / y_scale(),
190             qmin(), qmax());
191 
192       // Compute reference results.
193       for (size_t i = 0; i < n(); i++) {
194         y_fp[i] = float(y_zero_point()) +
195           float(int32_t(a_data[i]) - int32_t(a_zero_point())) * (a_scale() / y_scale()) +
196           float(int32_t(b_data[i]) - int32_t(b_zero_point())) * (b_scale() / y_scale());
197         y_fp[i] = std::min<float>(y_fp[i], float(qmax()));
198         y_fp[i] = std::max<float>(y_fp[i], float(qmin()));
199         y_ref[i] = xnn_add_quantize(a_data[i], b_data[i], scalar_quantization_params);
200       }
201 
202       // Call optimized micro-kernel.
203       vadd(n(), a_data, b_data, y.data(), &quantization_params);
204 
205       // Verify results.
206       for (size_t i = 0; i < n(); i++) {
207         ASSERT_LE(uint32_t(y[i]), uint32_t(qmax()))
208           << "at " << i << ", n = " << n();
209         ASSERT_GE(uint32_t(y[i]), uint32_t(qmin()))
210           << "at " << i << ", n = " << n();
211         ASSERT_NEAR(float(int32_t(y[i])), y_fp[i], 0.6f)
212           << "at " << i << ", n = " << n();
213         ASSERT_EQ(uint32_t(y_ref[i]), uint32_t(y[i]))
214           << "at " << i << ", n = " << n();
215       }
216     }
217   }
218 
219  private:
220   size_t n_{1};
221   bool inplace_a_{false};
222   bool inplace_b_{false};
223   float a_scale_{0.75f};
224   float b_scale_{1.25f};
225   float y_scale_{0.96875f};
226   uint8_t a_zero_point_{121};
227   uint8_t b_zero_point_{127};
228   uint8_t y_zero_point_{133};
229   uint8_t qmin_{0};
230   uint8_t qmax_{255};
231   size_t iterations_{15};
232 };
233