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