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 23 24 class ClampOperatorTester { 25 public: channels(size_t channels)26 inline ClampOperatorTester& channels(size_t channels) { 27 assert(channels != 0); 28 this->channels_ = channels; 29 return *this; 30 } 31 channels()32 inline size_t channels() const { 33 return this->channels_; 34 } 35 input_stride(size_t input_stride)36 inline ClampOperatorTester& input_stride(size_t input_stride) { 37 assert(input_stride != 0); 38 this->input_stride_ = input_stride; 39 return *this; 40 } 41 input_stride()42 inline size_t input_stride() const { 43 if (this->input_stride_ == 0) { 44 return this->channels_; 45 } else { 46 assert(this->input_stride_ >= this->channels_); 47 return this->input_stride_; 48 } 49 } 50 output_stride(size_t output_stride)51 inline ClampOperatorTester& output_stride(size_t output_stride) { 52 assert(output_stride != 0); 53 this->output_stride_ = output_stride; 54 return *this; 55 } 56 output_stride()57 inline size_t output_stride() const { 58 if (this->output_stride_ == 0) { 59 return this->channels_; 60 } else { 61 assert(this->output_stride_ >= this->channels_); 62 return this->output_stride_; 63 } 64 } 65 batch_size(size_t batch_size)66 inline ClampOperatorTester& batch_size(size_t batch_size) { 67 assert(batch_size != 0); 68 this->batch_size_ = batch_size; 69 return *this; 70 } 71 batch_size()72 inline size_t batch_size() const { 73 return this->batch_size_; 74 } 75 qmin(uint8_t qmin)76 inline ClampOperatorTester& qmin(uint8_t qmin) { 77 this->qmin_ = qmin; 78 return *this; 79 } 80 qmin()81 inline uint8_t qmin() const { 82 return this->qmin_; 83 } 84 qmax(uint8_t qmax)85 inline ClampOperatorTester& qmax(uint8_t qmax) { 86 this->qmax_ = qmax; 87 return *this; 88 } 89 qmax()90 inline uint8_t qmax() const { 91 return this->qmax_; 92 } 93 iterations(size_t iterations)94 inline ClampOperatorTester& iterations(size_t iterations) { 95 this->iterations_ = iterations; 96 return *this; 97 } 98 iterations()99 inline size_t iterations() const { 100 return this->iterations_; 101 } 102 TestU8()103 void TestU8() const { 104 std::random_device random_device; 105 auto rng = std::mt19937(random_device()); 106 auto u8rng = std::bind(std::uniform_int_distribution<uint8_t>(), rng); 107 108 std::vector<uint8_t> input(XNN_EXTRA_BYTES / sizeof(uint8_t) + 109 (batch_size() - 1) * input_stride() + channels()); 110 std::vector<uint8_t> output((batch_size() - 1) * output_stride() + channels()); 111 std::vector<uint8_t> output_ref(batch_size() * channels()); 112 for (size_t iteration = 0; iteration < iterations(); iteration++) { 113 std::generate(input.begin(), input.end(), std::ref(u8rng)); 114 std::fill(output.begin(), output.end(), 0xA5); 115 116 // Compute reference results. 117 for (size_t i = 0; i < batch_size(); i++) { 118 for (size_t c = 0; c < channels(); c++) { 119 const uint8_t x = input[i * input_stride() + c]; 120 const uint8_t y = std::min(std::max(x, qmin()), qmax()); 121 output_ref[i * channels() + c] = y; 122 } 123 } 124 125 // Create, setup, run, and destroy Clamp operator. 126 ASSERT_EQ(xnn_status_success, xnn_initialize(nullptr /* allocator */)); 127 xnn_operator_t clamp_op = nullptr; 128 129 ASSERT_EQ(xnn_status_success, 130 xnn_create_clamp_nc_u8( 131 channels(), input_stride(), output_stride(), 132 qmin(), qmax(), 133 0, &clamp_op)); 134 ASSERT_NE(nullptr, clamp_op); 135 136 // Smart pointer to automatically delete clamp_op. 137 std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_clamp_op(clamp_op, xnn_delete_operator); 138 139 ASSERT_EQ(xnn_status_success, 140 xnn_setup_clamp_nc_u8( 141 clamp_op, 142 batch_size(), 143 input.data(), output.data(), 144 nullptr /* thread pool */)); 145 146 ASSERT_EQ(xnn_status_success, 147 xnn_run_operator(clamp_op, nullptr /* thread pool */)); 148 149 // Verify results . 150 for (size_t i = 0; i < batch_size(); i++) { 151 for (size_t c = 0; c < channels(); c++) { 152 ASSERT_LE(uint32_t(output[i * output_stride() + c]), uint32_t(qmax())) 153 << "at position " << i << ", batch size = " << batch_size() << ", channels = " << channels(); 154 ASSERT_GE(uint32_t(output[i * output_stride() + c]), uint32_t(qmin())) 155 << "at position " << i << ", batch size = " << batch_size() << ", channels = " << channels(); 156 ASSERT_EQ(uint32_t(output_ref[i * channels() + c]), uint32_t(output[i * output_stride() + c])) 157 << "at position " << i << ", batch size = " << batch_size() << ", channels = " << channels() 158 << ", qmin = " << uint32_t(qmin()) << ", qmax = " << uint32_t(qmax()); 159 } 160 } 161 } 162 } 163 TestF32()164 void TestF32() const { 165 std::random_device random_device; 166 auto rng = std::mt19937(random_device()); 167 auto f32rng = std::bind(std::uniform_real_distribution<float>(0.0f, 255.0f), rng); 168 169 std::vector<float> input(XNN_EXTRA_BYTES / sizeof(float) + 170 (batch_size() - 1) * input_stride() + channels()); 171 std::vector<float> output((batch_size() - 1) * output_stride() + channels()); 172 std::vector<float> output_ref(batch_size() * channels()); 173 for (size_t iteration = 0; iteration < iterations(); iteration++) { 174 std::generate(input.begin(), input.end(), std::ref(f32rng)); 175 std::fill(output.begin(), output.end(), std::nanf("")); 176 177 // Compute reference results. 178 for (size_t i = 0; i < batch_size(); i++) { 179 for (size_t c = 0; c < channels(); c++) { 180 const float x = input[i * input_stride() + c]; 181 const float y = std::min(std::max(x, float(qmin())), float(qmax())); 182 output_ref[i * channels() + c] = y; 183 } 184 } 185 186 // Create, setup, run, and destroy Clamp operator. 187 ASSERT_EQ(xnn_status_success, xnn_initialize(nullptr /* allocator */)); 188 xnn_operator_t clamp_op = nullptr; 189 190 ASSERT_EQ(xnn_status_success, 191 xnn_create_clamp_nc_f32( 192 channels(), input_stride(), output_stride(), 193 float(qmin()), float(qmax()), 194 0, &clamp_op)); 195 ASSERT_NE(nullptr, clamp_op); 196 197 // Smart pointer to automatically delete clamp_op. 198 std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_clamp_op(clamp_op, xnn_delete_operator); 199 200 ASSERT_EQ(xnn_status_success, 201 xnn_setup_clamp_nc_f32( 202 clamp_op, 203 batch_size(), 204 input.data(), output.data(), 205 nullptr /* thread pool */)); 206 207 ASSERT_EQ(xnn_status_success, 208 xnn_run_operator(clamp_op, nullptr /* thread pool */)); 209 210 // Verify results. 211 for (size_t i = 0; i < batch_size(); i++) { 212 for (size_t c = 0; c < channels(); c++) { 213 ASSERT_LE(output[i * output_stride() + c], float(qmax())) 214 << "at position " << i << ", batch size = " << batch_size() << ", channels = " << channels(); 215 ASSERT_GE(output[i * output_stride() + c], float(qmin())) 216 << "at position " << i << ", batch size = " << batch_size() << ", channels = " << channels(); 217 ASSERT_EQ(output_ref[i * channels() + c], output[i * output_stride() + c]) 218 << "at position " << i << ", batch size = " << batch_size() << ", channels = " << channels() 219 << ", qmin = " << uint32_t(qmin()) << ", qmax = " << uint32_t(qmax()); 220 } 221 } 222 } 223 } 224 225 private: 226 size_t batch_size_{1}; 227 size_t channels_{1}; 228 size_t input_stride_{0}; 229 size_t output_stride_{0}; 230 uint8_t qmin_{5}; 231 uint8_t qmax_{250}; 232 size_t iterations_{15}; 233 }; 234