blob: 777099b9e7eae43871f5ff53cd686701259968d8 [file] [log] [blame]
XNNPACK Teamb455b122019-09-27 18:10:33 -07001// 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
XNNPACK Teamb455b122019-09-27 18:10:33 -070013#include <algorithm>
14#include <cfloat>
15#include <cmath>
Marat Dukhan5ce30d92020-04-14 03:31:26 -070016#include <cstddef>
17#include <cstdlib>
XNNPACK Teamb455b122019-09-27 18:10:33 -070018#include <functional>
Marat Dukhan5ce30d92020-04-14 03:31:26 -070019#include <limits>
XNNPACK Teamb455b122019-09-27 18:10:33 -070020#include <random>
21#include <vector>
22
23#include <xnnpack/params.h>
Marat Dukhanfe7acb62020-03-09 19:30:05 -070024#include <xnnpack/requantization-stubs.h>
Marat Dukhan2e23d2b2020-07-29 16:01:37 -070025#include <xnnpack/requantization.h>
XNNPACK Teamb455b122019-09-27 18:10:33 -070026
27
28class RequantizationTester {
29 public:
30 inline RequantizationTester& s(uint32_t s) {
31 this->s_ = s;
32 return *this;
33 }
34
35 inline uint32_t s() const {
36 return this->s_;
37 }
38
39 inline float scale() const {
40 return ldexpf(1.0f, -s());
41 }
42
Marat Dukhanfe7acb62020-03-09 19:30:05 -070043 inline RequantizationTester& zero_point(int32_t zero_point) {
44 this->zero_point_ = zero_point;
XNNPACK Teamb455b122019-09-27 18:10:33 -070045 return *this;
46 }
47
Marat Dukhanfe7acb62020-03-09 19:30:05 -070048 inline int32_t zero_point() const {
49 return this->zero_point_;
XNNPACK Teamb455b122019-09-27 18:10:33 -070050 }
51
Marat Dukhan2e23d2b2020-07-29 16:01:37 -070052 inline RequantizationTester& qmin(int16_t qmin) {
XNNPACK Teamb455b122019-09-27 18:10:33 -070053 this->qmin_ = qmin;
54 return *this;
55 }
56
Marat Dukhan2e23d2b2020-07-29 16:01:37 -070057 inline int16_t qmin() const {
XNNPACK Teamb455b122019-09-27 18:10:33 -070058 return this->qmin_;
59 }
60
Marat Dukhan2e23d2b2020-07-29 16:01:37 -070061 inline RequantizationTester& qmax(int16_t qmax) {
XNNPACK Teamb455b122019-09-27 18:10:33 -070062 this->qmax_ = qmax;
63 return *this;
64 }
65
Marat Dukhan2e23d2b2020-07-29 16:01:37 -070066 inline int16_t qmax() const {
XNNPACK Teamb455b122019-09-27 18:10:33 -070067 return this->qmax_;
68 }
69
70 inline RequantizationTester& iterations(size_t iterations) {
71 this->iterations_ = iterations;
72 return *this;
73 }
74
75 inline size_t iterations() const {
76 return this->iterations_;
77 }
78
79 /*
80 * Test that requantization of numbers ((i - zero point) * 2**s) with
81 * - scale = exp2(-s)
82 * - zero point in [0, 255]
83 * - no output clamping
84 * produces exactly i, provided that ((i - zero point) * 2**s) does not overflow.
85 */
Marat Dukhan5b69f8b2020-07-24 15:26:48 -070086 void TestExactDivideByPO2(xnn_qu8_requantization_function requantize) const {
Marat Dukhan2e23d2b2020-07-29 16:01:37 -070087 ASSERT_GE(zero_point(), std::numeric_limits<uint8_t>::min());
88 ASSERT_LE(zero_point(), std::numeric_limits<uint8_t>::max());
89 ASSERT_GE(qmin(), std::numeric_limits<uint8_t>::min());
90 ASSERT_LE(qmin(), std::numeric_limits<uint8_t>::max());
91 ASSERT_GE(qmax(), std::numeric_limits<uint8_t>::min());
92 ASSERT_LE(qmax(), std::numeric_limits<uint8_t>::max());
93 ASSERT_LT(qmin(), qmax());
XNNPACK Teamb455b122019-09-27 18:10:33 -070094
95 /* Note: need s >= 1 to ensure scale = exp2(-s) < 1.0 */
96 ASSERT_GE(s(), 1);
97 ASSERT_LT(s(), 32);
98
99 std::vector<int32_t> inputs(256);
100 std::vector<uint8_t> outputs(inputs.size());
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700101 const int32_t max_i = (uint32_t(std::numeric_limits<int32_t>::max()) >> s()) + zero_point();
102 const int32_t min_i = -(-uint32_t(std::numeric_limits<int32_t>::min()) >> s()) + zero_point();
103 for (int32_t i = 0; i <= std::numeric_limits<uint8_t>::max(); i++) {
104 const int32_t clamped_i = std::max(min_i, std::min(max_i, i));
105 inputs[i] = int32_t(uint32_t(clamped_i - zero_point()) << s());
XNNPACK Teamb455b122019-09-27 18:10:33 -0700106 }
107 requantize(inputs.size(), inputs.data(),
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700108 scale(), zero_point(), qmin(), qmax(),
XNNPACK Teamb455b122019-09-27 18:10:33 -0700109 outputs.data());
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700110 for (int32_t i = 0; i <= std::numeric_limits<uint8_t>::max(); i++) {
111 const int32_t clamped_i = std::max(min_i, std::min(max_i, i));
112 ASSERT_EQ(uint32_t(clamped_i), uint32_t(outputs[i]))
Marat Dukhan138560c2020-08-03 18:57:34 -0700113 << "i = " << i << ", clamped i = " << clamped_i << ", input = " << inputs[i]
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700114 << ", min i = " << min_i << ", max i = " << max_i
115 << ", s = " << s() << ", zero point = " << zero_point();
116 }
117 }
118
119 /*
120 * Test that requantization of numbers ((i - zero point) * 2**s) with
121 * - scale = exp2(-s)
122 * - zero point in [-128, 127]
123 * - no output clamping
124 * produces exactly i, provided that ((i - zero point) * 2**s) does not overflow.
125 */
126 void TestExactDivideByPO2(xnn_qs8_requantization_function requantize) const {
127 ASSERT_GE(zero_point(), std::numeric_limits<int8_t>::min());
128 ASSERT_LE(zero_point(), std::numeric_limits<int8_t>::max());
129 ASSERT_GE(qmin(), std::numeric_limits<int8_t>::min());
130 ASSERT_LE(qmin(), std::numeric_limits<int8_t>::max());
131 ASSERT_GE(qmax(), std::numeric_limits<int8_t>::min());
132 ASSERT_LE(qmax(), std::numeric_limits<int8_t>::max());
133 ASSERT_LT(qmin(), qmax());
134
135 /* Note: need s >= 1 to ensure scale = exp2(-s) < 1.0 */
136 ASSERT_GE(s(), 1);
137 ASSERT_LT(s(), 32);
138
139 std::vector<int32_t> inputs(256);
140 std::vector<int8_t> outputs(inputs.size());
141 const int32_t max_i = (uint32_t(std::numeric_limits<int32_t>::max()) >> s()) + zero_point();
142 const int32_t min_i = -(-uint32_t(std::numeric_limits<int32_t>::min()) >> s()) + zero_point();
143 for (int32_t i = std::numeric_limits<int8_t>::min(); i <= std::numeric_limits<int8_t>::max(); i++) {
144 const int32_t clamped_i = std::max(min_i, std::min(max_i, i));
145 inputs[i - std::numeric_limits<int8_t>::min()] = int32_t(uint32_t(clamped_i - zero_point()) << s());
146 }
147 requantize(inputs.size(), inputs.data(),
148 scale(), zero_point(), qmin(), qmax(),
149 outputs.data());
150 for (int32_t i = std::numeric_limits<int8_t>::min(); i <= std::numeric_limits<int8_t>::max(); i++) {
151 const int32_t clamped_i = std::max(min_i, std::min(max_i, i));
152 ASSERT_EQ(clamped_i, int32_t(outputs[i - std::numeric_limits<int8_t>::min()]))
153 << "i = " << i << ", clamped i = " << clamped_i
Marat Dukhan138560c2020-08-03 18:57:34 -0700154 << ", input = " << inputs[i - std::numeric_limits<int8_t>::min()]
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700155 << ", min i = " << min_i << ", max i = " << max_i
156 << ", s = " << s() << ", zero point = " << zero_point();
XNNPACK Teamb455b122019-09-27 18:10:33 -0700157 }
158 }
159
160 /*
161 * Test that requantization of numbers (i * 2**s + sign(i - zero point) * 2**(s-1)) with
162 * - scale = exp2(-s)
163 * - zero point in [1, 255]
164 * - no output clamping
165 * produces exactly i, provided that ((i - zero point) * 2**s) does not overflow.
166 */
Marat Dukhan5b69f8b2020-07-24 15:26:48 -0700167 void TestDivideByPO2WithRoundingUp(xnn_qu8_requantization_function requantize) {
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700168 ASSERT_GE(zero_point(), std::numeric_limits<uint8_t>::min());
169 ASSERT_LE(zero_point(), std::numeric_limits<uint8_t>::max());
170 ASSERT_GE(qmin(), std::numeric_limits<uint8_t>::min());
171 ASSERT_LE(qmin(), std::numeric_limits<uint8_t>::max());
172 ASSERT_GE(qmax(), std::numeric_limits<uint8_t>::min());
173 ASSERT_LE(qmax(), std::numeric_limits<uint8_t>::max());
174 ASSERT_LT(qmin(), qmax());
XNNPACK Teamb455b122019-09-27 18:10:33 -0700175
176 /* Note: need s >= 1 to ensure scale = exp2(-s) < 1.0 */
177 ASSERT_GE(s(), 1);
178 ASSERT_LT(s(), 32);
179
180 std::vector<int32_t> inputs(256);
181 std::vector<uint8_t> outputs(inputs.size());
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700182 for (int32_t i = 0; i <= std::numeric_limits<uint8_t>::max(); i++) {
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700183 const int64_t input = RequantizationTester::ShiftLeft(i - zero_point(), s()) -
184 (INT64_C(1) << (s() - 1)) + (int64_t) (i <= zero_point());
XNNPACK Teamb455b122019-09-27 18:10:33 -0700185 inputs[i] = int32_t(input);
186 }
187 requantize(inputs.size(), inputs.data(),
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700188 scale(), zero_point(), qmin(), qmax(),
XNNPACK Teamb455b122019-09-27 18:10:33 -0700189 outputs.data());
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700190 for (int32_t i = 0; i <= std::numeric_limits<uint8_t>::max(); i++) {
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700191 const int64_t input = RequantizationTester::ShiftLeft(i - zero_point(), s()) -
192 (INT64_C(1) << (s() - 1)) + (int64_t) (i <= zero_point());
XNNPACK Teamb455b122019-09-27 18:10:33 -0700193 if (int32_t(input) == input) {
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700194 ASSERT_EQ(i, int32_t(outputs[i]))
195 << "i = " << i << ", input = " << input
196 << ", s = " << s() << ", zero point = " << zero_point();
197 }
198 }
199 }
200
201 /*
202 * Test that requantization of numbers (i * 2**s + sign(i - zero point) * 2**(s-1)) with
203 * - scale = exp2(-s)
204 * - zero point in [-128, 127]
205 * - no output clamping
206 * produces exactly i, provided that ((i - zero point) * 2**s) does not overflow.
207 */
208 void TestDivideByPO2WithRoundingUp(xnn_qs8_requantization_function requantize) {
209 ASSERT_GE(zero_point(), std::numeric_limits<int8_t>::min());
210 ASSERT_LE(zero_point(), std::numeric_limits<int8_t>::max());
211 ASSERT_GE(qmin(), std::numeric_limits<int8_t>::min());
212 ASSERT_LE(qmin(), std::numeric_limits<int8_t>::max());
213 ASSERT_GE(qmax(), std::numeric_limits<int8_t>::min());
214 ASSERT_LE(qmax(), std::numeric_limits<int8_t>::max());
215 ASSERT_LT(qmin(), qmax());
216
217 /* Note: need s >= 1 to ensure scale = exp2(-s) < 1.0 */
218 ASSERT_GE(s(), 1);
219 ASSERT_LT(s(), 32);
220
221 std::vector<int32_t> inputs(256);
222 std::vector<int8_t> outputs(inputs.size());
223 for (int32_t i = std::numeric_limits<int8_t>::min(); i <= std::numeric_limits<int8_t>::max(); i++) {
224 const int64_t input = RequantizationTester::ShiftLeft(i - zero_point(), s()) -
225 (INT64_C(1) << (s() - 1)) + (int64_t) (i <= zero_point());
226 inputs[i - std::numeric_limits<int8_t>::min()] = int32_t(input);
227 }
228 requantize(inputs.size(), inputs.data(),
229 scale(), zero_point(), qmin(), qmax(),
230 outputs.data());
231 for (int32_t i = std::numeric_limits<int8_t>::min(); i <= std::numeric_limits<int8_t>::max(); i++) {
232 const int64_t input = RequantizationTester::ShiftLeft(i - zero_point(), s()) -
233 (INT64_C(1) << (s() - 1)) + (int64_t) (i <= zero_point());
234 if (int32_t(input) == input) {
235 ASSERT_EQ(i, int32_t(outputs[i - std::numeric_limits<int8_t>::min()]))
236 << "i = " << i << ", input = " << input
237 << ", s = " << s() << ", zero point = " << zero_point();
XNNPACK Teamb455b122019-09-27 18:10:33 -0700238 }
239 }
240 }
241
242 /*
243 * Test that requantization of numbers (i * 2**s + sign(i - zero point) * 2**(s-1)) with
244 * - scale = exp2(-s)
245 * - zero point in [1, 255]
246 * - no output clamping
247 * produces exactly i, provided that ((i - zero point) * 2**s) does not overflow.
248 */
Marat Dukhan5b69f8b2020-07-24 15:26:48 -0700249 void TestDivideByPO2WithRoundingDown(xnn_qu8_requantization_function requantize) {
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700250 ASSERT_GE(zero_point(), std::numeric_limits<uint8_t>::min());
251 ASSERT_LE(zero_point(), std::numeric_limits<uint8_t>::max());
252 ASSERT_GE(qmin(), std::numeric_limits<uint8_t>::min());
253 ASSERT_LE(qmin(), std::numeric_limits<uint8_t>::max());
254 ASSERT_GE(qmax(), std::numeric_limits<uint8_t>::min());
255 ASSERT_LE(qmax(), std::numeric_limits<uint8_t>::max());
256 ASSERT_LT(qmin(), qmax());
XNNPACK Teamb455b122019-09-27 18:10:33 -0700257
258 /* Note: need s >= 1 to ensure scale = exp2(-s) < 1.0 */
259 ASSERT_GE(s(), 1);
260 ASSERT_LT(s(), 32);
261
262 std::vector<int32_t> inputs(256);
263 std::vector<uint8_t> outputs(inputs.size());
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700264 for (int32_t i = 0; i <= std::numeric_limits<uint8_t>::max(); i++) {
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700265 const int64_t input = RequantizationTester::ShiftLeft(i - zero_point(), s()) +
266 (INT64_C(1) << (s() - 1)) - (int64_t) (i >= zero_point());
XNNPACK Teamb455b122019-09-27 18:10:33 -0700267 inputs[i] = int32_t(input);
268 }
269 requantize(inputs.size(), inputs.data(),
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700270 scale(), zero_point(), qmin(), qmax(),
XNNPACK Teamb455b122019-09-27 18:10:33 -0700271 outputs.data());
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700272 for (int32_t i = 0; i <= std::numeric_limits<uint8_t>::max(); i++) {
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700273 const int64_t input = RequantizationTester::ShiftLeft(i - zero_point(), s()) +
274 (INT64_C(1) << (s() - 1)) - (int64_t) (i >= zero_point());
XNNPACK Teamb455b122019-09-27 18:10:33 -0700275 if (int32_t(input) == input) {
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700276 ASSERT_EQ(i, int32_t(outputs[i]))
277 << "i = " << i << ", input = " << input
278 << ", s = " << s() << ", zero point = " << zero_point();
279 }
280 }
281 }
282
283 /*
284 * Test that requantization of numbers (i * 2**s + sign(i - zero point) * 2**(s-1)) with
285 * - scale = exp2(-s)
286 * - zero point in [-128, 127]
287 * - no output clamping
288 * produces exactly i, provided that ((i - zero point) * 2**s) does not overflow.
289 */
290 void TestDivideByPO2WithRoundingDown(xnn_qs8_requantization_function requantize) {
291 ASSERT_GE(zero_point(), std::numeric_limits<int8_t>::min());
292 ASSERT_LE(zero_point(), std::numeric_limits<int8_t>::max());
293 ASSERT_GE(qmin(), std::numeric_limits<int8_t>::min());
294 ASSERT_LE(qmin(), std::numeric_limits<int8_t>::max());
295 ASSERT_GE(qmax(), std::numeric_limits<int8_t>::min());
296 ASSERT_LE(qmax(), std::numeric_limits<int8_t>::max());
297 ASSERT_LT(qmin(), qmax());
298
299 /* Note: need s >= 1 to ensure scale = exp2(-s) < 1.0 */
300 ASSERT_GE(s(), 1);
301 ASSERT_LT(s(), 32);
302
303 std::vector<int32_t> inputs(256);
304 std::vector<int8_t> outputs(inputs.size());
305 for (int32_t i = std::numeric_limits<int8_t>::min(); i <= std::numeric_limits<int8_t>::max(); i++) {
306 const int64_t input = RequantizationTester::ShiftLeft(i - zero_point(), s()) +
307 (INT64_C(1) << (s() - 1)) - (int64_t) (i >= zero_point());
308 inputs[i - std::numeric_limits<int8_t>::min()] = int32_t(input);
309 }
310 requantize(inputs.size(), inputs.data(),
311 scale(), zero_point(), qmin(), qmax(),
312 outputs.data());
313 for (int32_t i = std::numeric_limits<int8_t>::min(); i <= std::numeric_limits<int8_t>::max(); i++) {
314 const int64_t input = RequantizationTester::ShiftLeft(i - zero_point(), s()) +
315 (INT64_C(1) << (s() - 1)) - (int64_t) (i >= zero_point());
316 if (int32_t(input) == input) {
317 ASSERT_EQ(i, int32_t(outputs[i - std::numeric_limits<int8_t>::min()]))
318 << "i = " << i << ", input = " << input
319 << ", s = " << s() << ", zero point = " << zero_point();
XNNPACK Teamb455b122019-09-27 18:10:33 -0700320 }
321 }
322 }
323
Marat Dukhan5b69f8b2020-07-24 15:26:48 -0700324 void TestDivideByPO2WithRoundingAway(xnn_qu8_requantization_function requantize) {
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700325 ASSERT_GE(zero_point(), std::numeric_limits<uint8_t>::min());
326 ASSERT_LE(zero_point(), std::numeric_limits<uint8_t>::max());
327 ASSERT_GE(qmin(), std::numeric_limits<uint8_t>::min());
328 ASSERT_LE(qmin(), std::numeric_limits<uint8_t>::max());
329 ASSERT_GE(qmax(), std::numeric_limits<uint8_t>::min());
330 ASSERT_LE(qmax(), std::numeric_limits<uint8_t>::max());
331 ASSERT_LT(qmin(), qmax());
XNNPACK Teamb455b122019-09-27 18:10:33 -0700332
333 /* Note: need s >= 1 to ensure scale = exp2(-s) < 1.0 */
334 ASSERT_GE(s(), 1);
335 ASSERT_LT(s(), 32);
336
337 std::vector<int32_t> inputs(256);
338 std::vector<uint8_t> outputs(inputs.size());
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700339 for (int32_t i = 0; i <= std::numeric_limits<uint8_t>::max(); i++) {
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700340 int64_t input = RequantizationTester::ShiftLeft(i - zero_point(), s());
XNNPACK Teamb455b122019-09-27 18:10:33 -0700341 if (input > 0) {
342 input -= INT64_C(1) << (s() - 1);
343 } else if (input < 0) {
344 input += INT64_C(1) << (s() - 1);
345 }
346 inputs[i] = int32_t(input);
347 }
348 requantize(inputs.size(), inputs.data(),
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700349 scale(), zero_point(), qmin(), qmax(),
XNNPACK Teamb455b122019-09-27 18:10:33 -0700350 outputs.data());
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700351 for (int32_t i = 0; i <= std::numeric_limits<uint8_t>::max(); i++) {
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700352 int64_t input = RequantizationTester::ShiftLeft(i - zero_point(), s());
XNNPACK Teamb455b122019-09-27 18:10:33 -0700353 if (input > 0) {
354 input -= INT64_C(1) << (s() - 1);
355 } else if (input < 0) {
356 input += INT64_C(1) << (s() - 1);
357 }
358 if (int32_t(input) == input) {
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700359 ASSERT_EQ(i, int32_t(outputs[i]))
360 << "i = " << i << ", input = " << input
361 << ", s = " << s() << ", zero point = " << zero_point();
362 }
363 }
364 }
365
366 void TestDivideByPO2WithRoundingAway(xnn_qs8_requantization_function requantize) {
367 ASSERT_GE(zero_point(), std::numeric_limits<int8_t>::min());
368 ASSERT_LE(zero_point(), std::numeric_limits<int8_t>::max());
369 ASSERT_GE(qmin(), std::numeric_limits<int8_t>::min());
370 ASSERT_LE(qmin(), std::numeric_limits<int8_t>::max());
371 ASSERT_GE(qmax(), std::numeric_limits<int8_t>::min());
372 ASSERT_LE(qmax(), std::numeric_limits<int8_t>::max());
373 ASSERT_LT(qmin(), qmax());
374
375 /* Note: need s >= 1 to ensure scale = exp2(-s) < 1.0 */
376 ASSERT_GE(s(), 1);
377 ASSERT_LT(s(), 32);
378
379 std::vector<int32_t> inputs(256);
380 std::vector<int8_t> outputs(inputs.size());
381 for (int32_t i = std::numeric_limits<int8_t>::min(); i <= std::numeric_limits<int8_t>::max(); i++) {
382 int64_t input = RequantizationTester::ShiftLeft(i - zero_point(), s());
383 if (input > 0) {
384 input -= INT64_C(1) << (s() - 1);
385 } else if (input < 0) {
386 input += INT64_C(1) << (s() - 1);
387 }
388 inputs[i - std::numeric_limits<int8_t>::min()] = int32_t(input);
389 }
390 requantize(inputs.size(), inputs.data(),
391 scale(), zero_point(), qmin(), qmax(),
392 outputs.data());
393 for (int32_t i = std::numeric_limits<int8_t>::min(); i <= std::numeric_limits<int8_t>::max(); i++) {
394 int64_t input = RequantizationTester::ShiftLeft(i - zero_point(), s());
395 if (input > 0) {
396 input -= INT64_C(1) << (s() - 1);
397 } else if (input < 0) {
398 input += INT64_C(1) << (s() - 1);
399 }
400 if (int32_t(input) == input) {
401 ASSERT_EQ(i, int32_t(outputs[i - std::numeric_limits<int8_t>::min()]))
402 << "i = " << i << ", input = " << input
403 << ", s = " << s() << ", zero point = " << zero_point();
XNNPACK Teamb455b122019-09-27 18:10:33 -0700404 }
405 }
406 }
407
Marat Dukhan5b69f8b2020-07-24 15:26:48 -0700408 void TestSpecialCases(xnn_qu8_requantization_function requantize) {
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700409 ASSERT_GE(qmin(), std::numeric_limits<uint8_t>::min());
410 ASSERT_LE(qmin(), std::numeric_limits<uint8_t>::max());
411 ASSERT_GE(qmax(), std::numeric_limits<uint8_t>::min());
412 ASSERT_LE(qmax(), std::numeric_limits<uint8_t>::max());
413 ASSERT_LT(qmin(), qmax());
414
XNNPACK Teamb455b122019-09-27 18:10:33 -0700415 std::vector<int32_t> inputs(256);
416 std::vector<uint8_t> outputs(inputs.size());
417
418 std::fill(inputs.begin(), inputs.end(), std::numeric_limits<int32_t>::min());
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700419 for (int32_t zero_point = 0; zero_point <= std::numeric_limits<uint8_t>::max(); zero_point++) {
XNNPACK Teamb455b122019-09-27 18:10:33 -0700420 requantize(
421 inputs.size(),
422 inputs.data(),
423 ldexpf(1.0f, -32) /* scale */,
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700424 zero_point /* zero point */,
XNNPACK Teamb455b122019-09-27 18:10:33 -0700425 std::numeric_limits<uint8_t>::min(),
426 std::numeric_limits<uint8_t>::max(),
427 outputs.data());
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700428 for (size_t i = 0; i < outputs.size(); i++) {
429 ASSERT_EQ(std::max(int32_t(int32_t(std::numeric_limits<uint8_t>::min())), zero_point - 1), int32_t(outputs[i]));
430 }
XNNPACK Teamb455b122019-09-27 18:10:33 -0700431 }
432
433 std::fill(inputs.begin(), inputs.end(), std::numeric_limits<int32_t>::max());
434 requantize(
435 inputs.size(),
436 inputs.data(),
437 0x1.FFFFFEp-1f /* scale */,
438 std::numeric_limits<uint8_t>::max() /* zero point */,
439 std::numeric_limits<uint8_t>::min(),
440 std::numeric_limits<uint8_t>::max(),
441 outputs.data());
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700442 for (size_t i = 0; i < outputs.size(); i++) {
443 ASSERT_EQ(std::numeric_limits<uint8_t>::max(), int32_t(outputs[i]));
444 }
445 }
446
447 void TestSpecialCases(xnn_qs8_requantization_function requantize) {
448 ASSERT_GE(qmin(), std::numeric_limits<int8_t>::min());
449 ASSERT_LE(qmin(), std::numeric_limits<int8_t>::max());
450 ASSERT_GE(qmax(), std::numeric_limits<int8_t>::min());
451 ASSERT_LE(qmax(), std::numeric_limits<int8_t>::max());
452 ASSERT_LT(qmin(), qmax());
453
454 std::vector<int32_t> inputs(256);
455 std::vector<int8_t> outputs(inputs.size());
456
457 std::fill(inputs.begin(), inputs.end(), std::numeric_limits<int32_t>::min());
458 for (int32_t zero_point = std::numeric_limits<int8_t>::min();
459 zero_point <= std::numeric_limits<int8_t>::max();
460 zero_point++)
461 {
462 requantize(
463 inputs.size(),
464 inputs.data(),
465 ldexpf(1.0f, -32) /* scale */,
466 zero_point,
467 std::numeric_limits<int8_t>::min(),
468 std::numeric_limits<int8_t>::max(),
469 outputs.data());
470 for (size_t i = 0; i < outputs.size(); i++) {
471 ASSERT_EQ(std::max(int32_t(std::numeric_limits<int8_t>::min()), zero_point - 1), int32_t(outputs[i]));
472 }
473 }
474
475 std::fill(inputs.begin(), inputs.end(), std::numeric_limits<int32_t>::max());
476 requantize(
477 inputs.size(),
478 inputs.data(),
479 0x1.FFFFFEp-1f /* scale */,
480 std::numeric_limits<int8_t>::max() /* zero point */,
481 std::numeric_limits<int8_t>::min(),
482 std::numeric_limits<int8_t>::max(),
483 outputs.data());
484 for (size_t i = 0; i < outputs.size(); i++) {
485 ASSERT_EQ(std::numeric_limits<int8_t>::max(), int32_t(outputs[i]));
XNNPACK Teamb455b122019-09-27 18:10:33 -0700486 }
487 }
488
Marat Dukhan5b69f8b2020-07-24 15:26:48 -0700489 void TestRandomCasesPrecise(xnn_qu8_requantization_function requantize) {
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700490 ASSERT_GE(zero_point(), std::numeric_limits<uint8_t>::min());
491 ASSERT_LE(zero_point(), std::numeric_limits<uint8_t>::max());
492 ASSERT_GE(qmin(), std::numeric_limits<uint8_t>::min());
493 ASSERT_LE(qmin(), std::numeric_limits<uint8_t>::max());
494 ASSERT_GE(qmax(), std::numeric_limits<uint8_t>::min());
495 ASSERT_LE(qmax(), std::numeric_limits<uint8_t>::max());
496 ASSERT_LT(qmin(), qmax());
497
XNNPACK Teamb455b122019-09-27 18:10:33 -0700498 std::random_device random_device;
Marat Dukhan5ce30d92020-04-14 03:31:26 -0700499 std::mt19937 rng(random_device());
XNNPACK Teamb455b122019-09-27 18:10:33 -0700500 for (size_t iteration = 0; iteration < iterations(); iteration++) {
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700501 auto u8rng =
502 std::bind(std::uniform_int_distribution<uint32_t>(0, std::numeric_limits<uint8_t>::max()), std::ref(rng));
XNNPACK Teamb455b122019-09-27 18:10:33 -0700503
504 std::vector<int32_t> inputs(4096);
505 std::vector<uint8_t> outputs(inputs.size());
506
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700507 std::uniform_real_distribution<float> scale_distribution(0x1.000000p-23f, 0x1.FFFFFEp-1f);
Marat Dukhan5ce30d92020-04-14 03:31:26 -0700508 const float scale = scale_distribution(rng);
XNNPACK Teamb455b122019-09-27 18:10:33 -0700509 for (size_t i = 0; i < inputs.size(); i++) {
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700510 const uint8_t approximate_output = std::min(std::max(uint8_t(u8rng()), uint8_t(qmin())), uint8_t(qmax()));
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700511 const int32_t input = int32_t(double(approximate_output) / double(scale));
XNNPACK Teamb455b122019-09-27 18:10:33 -0700512 inputs[i] = input;
513 }
514
515 requantize(
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700516 inputs.size(), inputs.data(), scale, zero_point(), qmin(), qmax(),
XNNPACK Teamb455b122019-09-27 18:10:33 -0700517 outputs.data());
518
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700519 /* Ensure that outputs are not all identical, as in this case the test doesn't validate much */
XNNPACK Teamb455b122019-09-27 18:10:33 -0700520 ASSERT_NE(
521 *std::max_element(outputs.cbegin(), outputs.cend()),
522 *std::min_element(outputs.cbegin(), outputs.cend()));
523
524 for (size_t i = 0; i < inputs.size(); i++) {
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700525 const uint8_t reference_output =
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700526 xnn_qu8_requantize_precise(inputs[i], scale, zero_point(), qmin(), qmax());
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700527 ASSERT_EQ(uint32_t(reference_output), uint32_t(outputs[i]));
XNNPACK Teamb455b122019-09-27 18:10:33 -0700528 }
529 }
530 }
531
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700532 void TestRandomCasesPrecise(xnn_qs8_requantization_function requantize) {
533 ASSERT_GE(zero_point(), std::numeric_limits<int8_t>::min());
534 ASSERT_LE(zero_point(), std::numeric_limits<int8_t>::max());
535 ASSERT_GE(qmin(), std::numeric_limits<int8_t>::min());
536 ASSERT_LE(qmin(), std::numeric_limits<int8_t>::max());
537 ASSERT_GE(qmax(), std::numeric_limits<int8_t>::min());
538 ASSERT_LE(qmax(), std::numeric_limits<int8_t>::max());
539 ASSERT_LT(qmin(), qmax());
540
XNNPACK Teamb455b122019-09-27 18:10:33 -0700541 std::random_device random_device;
Marat Dukhan5ce30d92020-04-14 03:31:26 -0700542 std::mt19937 rng(random_device());
XNNPACK Teamb455b122019-09-27 18:10:33 -0700543 for (size_t iteration = 0; iteration < iterations(); iteration++) {
Marat Dukhanecd83112020-08-03 21:50:28 -0700544 auto i8rng = std::bind(
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700545 std::uniform_int_distribution<int32_t>(std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max()), std::ref(rng));
XNNPACK Teamb455b122019-09-27 18:10:33 -0700546
547 std::vector<int32_t> inputs(4096);
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700548 std::vector<int8_t> outputs(inputs.size());
XNNPACK Teamb455b122019-09-27 18:10:33 -0700549
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700550 std::uniform_real_distribution<float> scale_distribution(0x1.000000p-23f, 0x1.FFFFFEp-1f);
Marat Dukhan5ce30d92020-04-14 03:31:26 -0700551 const float scale = scale_distribution(rng);
XNNPACK Teamb455b122019-09-27 18:10:33 -0700552 for (size_t i = 0; i < inputs.size(); i++) {
Marat Dukhanecd83112020-08-03 21:50:28 -0700553 const int8_t approximate_output = std::min(std::max(int8_t(i8rng()), int8_t(qmin())), int8_t(qmax()));
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700554 const int32_t input = int32_t(double(approximate_output) / double(scale));
XNNPACK Teamb455b122019-09-27 18:10:33 -0700555 inputs[i] = input;
556 }
557
558 requantize(
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700559 inputs.size(), inputs.data(), scale, zero_point(), qmin(), qmax(),
560 outputs.data());
561
562 /* Ensure that outputs are not all identical, as in this case the test doesn't validate much */
563 ASSERT_NE(
564 *std::max_element(outputs.cbegin(), outputs.cend()),
565 *std::min_element(outputs.cbegin(), outputs.cend()));
566
567 for (size_t i = 0; i < inputs.size(); i++) {
568 const int8_t reference_output =
569 xnn_qs8_requantize_precise(inputs[i], scale, zero_point(), qmin(), qmax());
570 ASSERT_EQ(int32_t(reference_output), int32_t(outputs[i]));
571 }
572 }
573 }
574
575 void TestRandomCasesApproximate(xnn_qu8_requantization_function requantize) {
576 ASSERT_GE(zero_point(), std::numeric_limits<uint8_t>::min());
577 ASSERT_LE(zero_point(), std::numeric_limits<uint8_t>::max());
578 ASSERT_GE(qmin(), std::numeric_limits<uint8_t>::min());
579 ASSERT_LE(qmin(), std::numeric_limits<uint8_t>::max());
580 ASSERT_GE(qmax(), std::numeric_limits<uint8_t>::min());
581 ASSERT_LE(qmax(), std::numeric_limits<uint8_t>::max());
582 ASSERT_LT(qmin(), qmax());
583
584 std::random_device random_device;
585 std::mt19937 rng(random_device());
586 for (size_t iteration = 0; iteration < iterations(); iteration++) {
587 auto u8rng =
588 std::bind(std::uniform_int_distribution<uint32_t>(0, std::numeric_limits<uint8_t>::max()), std::ref(rng));
589
590 std::vector<int32_t> inputs(4096);
591 std::vector<uint8_t> outputs(inputs.size());
592
593 std::uniform_real_distribution<float> scale_distribution(0x1.000000p-23f, 0x1.FFFFFEp-1f);
594 const float scale = scale_distribution(rng);
595 for (size_t i = 0; i < inputs.size(); i++) {
596 const uint8_t approximate_output = std::min(std::max(uint8_t(u8rng()), uint8_t(qmin())), uint8_t(qmax()));
597 const int32_t input = int32_t(double(approximate_output) / double(scale));
598 inputs[i] = input;
599 }
600
601 requantize(
602 inputs.size(), inputs.data(), scale, zero_point(), qmin(), qmax(),
XNNPACK Teamb455b122019-09-27 18:10:33 -0700603 outputs.data());
604
605 /* Ensure that outputs are not all identical, as in this case Test doesn't validate much */
606 ASSERT_NE(
607 *std::max_element(outputs.cbegin(), outputs.cend()),
608 *std::min_element(outputs.cbegin(), outputs.cend()));
609
610 for (size_t i = 0; i < inputs.size(); i++) {
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700611 const double reference_output = RequantizationTester::RequantizeApproximate(
612 inputs[i], scale, uint8_t(zero_point()), uint8_t(qmin()), uint8_t(qmax()));
613 ASSERT_LE(std::abs(reference_output - double(outputs[i])), 0.55)
614 << "input = " << inputs[i] << ", output = " << int32_t(outputs[i])
615 << ", reference output = " << reference_output;
616 }
617 }
618 }
619
620 void TestRandomCasesApproximate(xnn_qs8_requantization_function requantize) {
621 ASSERT_GE(zero_point(), std::numeric_limits<int8_t>::min());
622 ASSERT_LE(zero_point(), std::numeric_limits<int8_t>::max());
623 ASSERT_GE(qmin(), std::numeric_limits<int8_t>::min());
624 ASSERT_LE(qmin(), std::numeric_limits<int8_t>::max());
625 ASSERT_GE(qmax(), std::numeric_limits<int8_t>::min());
626 ASSERT_LE(qmax(), std::numeric_limits<int8_t>::max());
627 ASSERT_LT(qmin(), qmax());
628
629 std::random_device random_device;
630 std::mt19937 rng(random_device());
631 for (size_t iteration = 0; iteration < iterations(); iteration++) {
Marat Dukhanecd83112020-08-03 21:50:28 -0700632 auto i8rng = std::bind(
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700633 std::uniform_int_distribution<int32_t>(std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max()), std::ref(rng));
634
635 std::vector<int32_t> inputs(4096);
636 std::vector<int8_t> outputs(inputs.size());
637
638 std::uniform_real_distribution<float> scale_distribution(0x1.000000p-23f, 0x1.FFFFFEp-1f);
639 const float scale = scale_distribution(rng);
640 for (size_t i = 0; i < inputs.size(); i++) {
Marat Dukhanecd83112020-08-03 21:50:28 -0700641 const int8_t approximate_output = std::min(std::max(int8_t(i8rng()), int8_t(qmin())), int8_t(qmax()));
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700642 const int32_t input = int32_t(double(approximate_output) / double(scale));
643 inputs[i] = input;
644 }
645
646 requantize(
647 inputs.size(), inputs.data(), scale, zero_point(), qmin(), qmax(),
648 outputs.data());
649
650 /* Ensure that outputs are not all identical, as in this case Test doesn't validate much */
651 ASSERT_NE(
652 *std::max_element(outputs.cbegin(), outputs.cend()),
653 *std::min_element(outputs.cbegin(), outputs.cend()));
654
655 for (size_t i = 0; i < inputs.size(); i++) {
656 const double reference_output = RequantizationTester::RequantizeApproximate(
657 inputs[i], scale, int8_t(zero_point()), int8_t(qmin()), int8_t(qmax()));
658 ASSERT_LE(std::abs(reference_output - double(outputs[i])), 0.55)
659 << "input = " << inputs[i] << ", output = " << int32_t(outputs[i])
660 << ", reference output = " << reference_output;
XNNPACK Teamb455b122019-09-27 18:10:33 -0700661 }
662 }
663 }
664
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700665 static inline int64_t ShiftLeft(int64_t w, uint32_t n) {
XNNPACK Teamb455b122019-09-27 18:10:33 -0700666 return (int64_t) ((uint64_t) w << n);
667 }
668
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700669 static inline double RequantizeApproximate(
XNNPACK Teamb455b122019-09-27 18:10:33 -0700670 int32_t value,
671 float scale,
Marat Dukhanfe7acb62020-03-09 19:30:05 -0700672 uint8_t zero_point,
XNNPACK Teamb455b122019-09-27 18:10:33 -0700673 uint8_t qmin,
674 uint8_t qmax)
675 {
676 assert(scale < 1.0f);
677 assert(scale >= 0x1.0p-32f);
678
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700679 return std::min(std::max(double(value) * double(scale) + double(zero_point), double(qmin)), double(qmax));
680 }
XNNPACK Teamb455b122019-09-27 18:10:33 -0700681
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700682 static inline double RequantizeApproximate(
683 int32_t value,
684 float scale,
685 int8_t zero_point,
686 int8_t qmin,
687 int8_t qmax)
688 {
689 assert(scale < 1.0f);
690 assert(scale >= 0x1.0p-32f);
XNNPACK Teamb455b122019-09-27 18:10:33 -0700691
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700692 return std::min(std::max(double(value) * double(scale) + double(zero_point), double(qmin)), double(qmax));
XNNPACK Teamb455b122019-09-27 18:10:33 -0700693 }
694
695 private:
Marat Dukhan2e23d2b2020-07-29 16:01:37 -0700696 uint32_t s_{1};
697 int32_t zero_point_{0};
698 int16_t qmin_{std::numeric_limits<int16_t>::min()};
699 int16_t qmax_{std::numeric_limits<int16_t>::max()};
XNNPACK Teamb455b122019-09-27 18:10:33 -0700700 size_t iterations_{1};
701};