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