1 // Copyright 2020 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 <cmath> 13 #include <cstddef> 14 #include <cstdlib> 15 #include <functional> 16 #include <random> 17 #include <vector> 18 19 #include <xnnpack.h> 20 21 22 class ELUOperatorTester { 23 public: channels(size_t channels)24 inline ELUOperatorTester& channels(size_t channels) { 25 assert(channels != 0); 26 this->channels_ = channels; 27 return *this; 28 } 29 channels()30 inline size_t channels() const { 31 return this->channels_; 32 } 33 input_stride(size_t input_stride)34 inline ELUOperatorTester& input_stride(size_t input_stride) { 35 assert(input_stride != 0); 36 this->input_stride_ = input_stride; 37 return *this; 38 } 39 input_stride()40 inline size_t input_stride() const { 41 if (this->input_stride_ == 0) { 42 return this->channels_; 43 } else { 44 assert(this->input_stride_ >= this->channels_); 45 return this->input_stride_; 46 } 47 } 48 output_stride(size_t output_stride)49 inline ELUOperatorTester& output_stride(size_t output_stride) { 50 assert(output_stride != 0); 51 this->output_stride_ = output_stride; 52 return *this; 53 } 54 output_stride()55 inline size_t output_stride() const { 56 if (this->output_stride_ == 0) { 57 return this->channels_; 58 } else { 59 assert(this->output_stride_ >= this->channels_); 60 return this->output_stride_; 61 } 62 } 63 batch_size(size_t batch_size)64 inline ELUOperatorTester& batch_size(size_t batch_size) { 65 assert(batch_size != 0); 66 this->batch_size_ = batch_size; 67 return *this; 68 } 69 batch_size()70 inline size_t batch_size() const { 71 return this->batch_size_; 72 } 73 alpha(float alpha)74 inline ELUOperatorTester& alpha(float alpha) { 75 assert(alpha > 0.0f); 76 assert(alpha < 1.0f); 77 this->alpha_ = alpha; 78 return *this; 79 } 80 alpha()81 inline float alpha() const { 82 return this->alpha_; 83 } 84 input_scale(float input_scale)85 inline ELUOperatorTester& input_scale(float input_scale) { 86 assert(input_scale > 0.0f); 87 assert(std::isnormal(input_scale)); 88 this->input_scale_ = input_scale; 89 return *this; 90 } 91 input_scale()92 inline float input_scale() const { 93 return this->input_scale_; 94 } 95 input_zero_point(uint8_t input_zero_point)96 inline ELUOperatorTester& input_zero_point(uint8_t input_zero_point) { 97 this->input_zero_point_ = input_zero_point; 98 return *this; 99 } 100 input_zero_point()101 inline uint8_t input_zero_point() const { 102 return this->input_zero_point_; 103 } 104 output_scale(float output_scale)105 inline ELUOperatorTester& output_scale(float output_scale) { 106 assert(output_scale > 0.0f); 107 assert(std::isnormal(output_scale)); 108 this->output_scale_ = output_scale; 109 return *this; 110 } 111 output_scale()112 inline float output_scale() const { 113 return this->output_scale_; 114 } 115 output_zero_point(uint8_t output_zero_point)116 inline ELUOperatorTester& output_zero_point(uint8_t output_zero_point) { 117 this->output_zero_point_ = output_zero_point; 118 return *this; 119 } 120 output_zero_point()121 inline uint8_t output_zero_point() const { 122 return this->output_zero_point_; 123 } 124 qmin(uint8_t qmin)125 inline ELUOperatorTester& qmin(uint8_t qmin) { 126 this->qmin_ = qmin; 127 return *this; 128 } 129 qmin()130 inline uint8_t qmin() const { 131 return this->qmin_; 132 } 133 qmax(uint8_t qmax)134 inline ELUOperatorTester& qmax(uint8_t qmax) { 135 this->qmax_ = qmax; 136 return *this; 137 } 138 qmax()139 inline uint8_t qmax() const { 140 return this->qmax_; 141 } 142 iterations(size_t iterations)143 inline ELUOperatorTester& iterations(size_t iterations) { 144 this->iterations_ = iterations; 145 return *this; 146 } 147 iterations()148 inline size_t iterations() const { 149 return this->iterations_; 150 } 151 TestF32()152 void TestF32() const { 153 std::random_device random_device; 154 auto rng = std::mt19937(random_device()); 155 auto f32rng = std::bind(std::uniform_real_distribution<float>(-20.0f, 20.0f), std::ref(rng)); 156 157 std::vector<float> input(XNN_EXTRA_BYTES / sizeof(float) + (batch_size() - 1) * input_stride() + channels()); 158 std::vector<float> output((batch_size() - 1) * output_stride() + channels()); 159 std::vector<double> output_ref(batch_size() * channels()); 160 for (size_t iteration = 0; iteration < iterations(); iteration++) { 161 std::generate(input.begin(), input.end(), std::ref(f32rng)); 162 std::fill(output.begin(), output.end(), std::nanf("")); 163 164 // Compute reference results. 165 for (size_t i = 0; i < batch_size(); i++) { 166 for (size_t c = 0; c < channels(); c++) { 167 const double x = double(input[i * input_stride() + c]); 168 output_ref[i * channels() + c] = std::signbit(x) ? std::expm1(x) * alpha() : x; 169 } 170 } 171 172 // Create, setup, run, and destroy ELU operator. 173 ASSERT_EQ(xnn_status_success, xnn_initialize(nullptr /* allocator */)); 174 xnn_operator_t elu_op = nullptr; 175 176 ASSERT_EQ(xnn_status_success, 177 xnn_create_elu_nc_f32( 178 channels(), input_stride(), output_stride(), 179 alpha(), 180 0, &elu_op)); 181 ASSERT_NE(nullptr, elu_op); 182 183 // Smart pointer to automatically delete elu_op. 184 std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_elu_op(elu_op, xnn_delete_operator); 185 186 ASSERT_EQ(xnn_status_success, 187 xnn_setup_elu_nc_f32( 188 elu_op, 189 batch_size(), 190 input.data(), output.data(), 191 nullptr /* thread pool */)); 192 193 ASSERT_EQ(xnn_status_success, 194 xnn_run_operator(elu_op, nullptr /* thread pool */)); 195 196 // Verify results. 197 for (size_t i = 0; i < batch_size(); i++) { 198 for (size_t c = 0; c < channels(); c++) { 199 ASSERT_NEAR(output[i * output_stride() + c], 200 output_ref[i * channels() + c], 201 std::abs(output_ref[i * channels() + c]) * 1.0e-5) 202 << "at batch " << i << " / " << batch_size() << ", channel " << c << " / " << channels() 203 << ", input " << input[i * input_stride() + c] << ", alpha " << alpha(); 204 } 205 } 206 } 207 } 208 TestQS8()209 void TestQS8() const { 210 std::random_device random_device; 211 auto rng = std::mt19937(random_device()); 212 auto i8rng = std::bind( 213 std::uniform_int_distribution<int32_t>(std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max()), 214 std::ref(rng)); 215 216 std::vector<int8_t> input((batch_size() - 1) * input_stride() + channels() + XNN_EXTRA_BYTES / sizeof(int8_t)); 217 std::vector<int8_t> output((batch_size() - 1) * output_stride() + channels()); 218 std::vector<float> output_ref(batch_size() * channels()); 219 for (size_t iteration = 0; iteration < iterations(); iteration++) { 220 std::generate(input.begin(), input.end(), std::ref(i8rng)); 221 std::fill(output.begin(), output.end(), 0xA5); 222 223 // Compute reference results. 224 for (size_t i = 0; i < batch_size(); i++) { 225 for (size_t c = 0; c < channels(); c++) { 226 const float x = input_scale() * 227 (int32_t(input[i * input_stride() + c]) - int32_t(input_zero_point() - 0x80)); 228 const float elu_x = std::signbit(x) ? alpha() * std::expm1(x) : x; 229 const float scaled_elu_x = elu_x / output_scale(); 230 float y = scaled_elu_x; 231 y = std::min<float>(y, int32_t(qmax() - 0x80) - int32_t(output_zero_point() - 0x80)); 232 y = std::max<float>(y, int32_t(qmin() - 0x80) - int32_t(output_zero_point() - 0x80)); 233 output_ref[i * channels() + c] = y + int32_t(output_zero_point() - 0x80); 234 } 235 } 236 237 // Create, setup, run, and destroy Sigmoid operator. 238 ASSERT_EQ(xnn_status_success, xnn_initialize(nullptr /* allocator */)); 239 xnn_operator_t elu_op = nullptr; 240 241 ASSERT_EQ(xnn_status_success, 242 xnn_create_elu_nc_qs8( 243 channels(), input_stride(), output_stride(), 244 alpha(), 245 int8_t(input_zero_point() - 0x80), input_scale(), 246 int8_t(output_zero_point() - 0x80), output_scale(), 247 int8_t(qmin() - 0x80), int8_t(qmax() - 0x80), 248 0, &elu_op)); 249 ASSERT_NE(nullptr, elu_op); 250 251 // Smart pointer to automatically delete elu_op. 252 std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_sigmoid_op(elu_op, xnn_delete_operator); 253 254 ASSERT_EQ(xnn_status_success, 255 xnn_setup_elu_nc_qs8( 256 elu_op, 257 batch_size(), 258 input.data(), output.data(), 259 nullptr /* thread pool */)); 260 261 ASSERT_EQ(xnn_status_success, 262 xnn_run_operator(elu_op, nullptr /* thread pool */)); 263 264 // Verify results. 265 for (size_t i = 0; i < batch_size(); i++) { 266 for (size_t c = 0; c < channels(); c++) { 267 ASSERT_NEAR(float(int32_t(output[i * output_stride() + c])), output_ref[i * channels() + c], 0.6f); 268 } 269 } 270 } 271 } 272 273 private: 274 size_t batch_size_{1}; 275 size_t channels_{1}; 276 size_t input_stride_{0}; 277 size_t output_stride_{0}; 278 float alpha_{0.5f}; 279 float input_scale_{0.75f}; 280 uint8_t input_zero_point_{121}; 281 float output_scale_{0.75f}; 282 uint8_t output_zero_point_{121}; 283 uint8_t qmin_{0}; 284 uint8_t qmax_{255}; 285 size_t iterations_{15}; 286 }; 287