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