1 /* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 #include <algorithm>
16 #include <cmath>
17 #include <cstdlib>
18 #include <functional>
19 #include <iterator>
20 #include <limits>
21 #include <random>
22 #include <string>
23 #include <vector>
24
25 #include <gtest/gtest.h>
26 #include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h"
27 #include "tensorflow/lite/kernels/internal/quantization_util.h"
28 #include "tensorflow/lite/kernels/internal/reference/integer_ops/dequantize.h"
29 #include "tensorflow/lite/kernels/internal/reference/integer_ops/log_softmax.h"
30 #include "tensorflow/lite/kernels/internal/reference/reference_ops.h"
31 #include "tensorflow/lite/kernels/internal/test_util.h"
32 #include "tensorflow/lite/string_type.h"
33
34 namespace tflite {
35 namespace {
36
RunLogSoftmaxFloatReference(const uint8 * input_data,const RuntimeShape & shape_common,int32 input_offset,const double input_scale,int stride,float beta,uint8 * reference_output_data)37 void RunLogSoftmaxFloatReference(const uint8* input_data,
38 const RuntimeShape& shape_common,
39 int32 input_offset, const double input_scale,
40 int stride, float beta,
41 uint8* reference_output_data) {
42 const int ref_buffer_size = shape_common.FlatSize();
43 std::vector<float> reference_dequant_data(ref_buffer_size);
44 std::vector<float> reference_output_float_data(ref_buffer_size);
45
46 // Reference data generated via Dequant of input into float, and then applying
47 // float LogSoftmax.
48 DequantizationParams dq_params;
49 dq_params.zero_point = input_offset;
50 dq_params.scale = input_scale;
51 reference_ops::Dequantize(dq_params, shape_common, input_data, shape_common,
52 reference_dequant_data.data());
53 SoftmaxParams sm_params;
54 optimized_ops::LogSoftmax(sm_params, shape_common,
55 reference_dequant_data.data(), shape_common,
56 reference_output_float_data.data());
57 // Work with quantized scaling for LogSoftmax, under which 255 represents 0,
58 // and -16 gets nudged up to 0.
59 for (int i = 0; i < ref_buffer_size; i++) {
60 reference_output_data[i] = std::max(
61 0, static_cast<int>(
62 255 + std::round(16.0f * reference_output_float_data[i])));
63 }
64 }
65
66 // Same as above except for the following change:
67 // - input and output data type
68 // - Dequnatize function
69 // - clamping values
RunLogSoftmaxFloatReference(const int8 * input_data,const RuntimeShape & shape_common,int32 input_offset,const double input_scale,int stride,float beta,int8 * reference_output_data)70 void RunLogSoftmaxFloatReference(const int8* input_data,
71 const RuntimeShape& shape_common,
72 int32 input_offset, const double input_scale,
73 int stride, float beta,
74 int8* reference_output_data) {
75 const int ref_buffer_size = shape_common.FlatSize();
76 std::vector<float> reference_dequant_data(ref_buffer_size);
77 std::vector<float> reference_output_float_data(ref_buffer_size);
78
79 // Reference data generated via Dequant of input into float, and then applying
80 // float LogSoftmax.
81 DequantizationParams dq_params;
82 dq_params.zero_point = input_offset;
83 dq_params.scale = input_scale;
84 reference_integer_ops::Dequantize(dq_params, shape_common, input_data,
85 shape_common,
86 reference_dequant_data.data());
87 SoftmaxParams sm_params;
88 optimized_ops::LogSoftmax(sm_params, shape_common,
89 reference_dequant_data.data(), shape_common,
90 reference_output_float_data.data());
91 // Work with quantized scaling for LogSoftmax, under which 255 represents 0,
92 // and -16 gets nudged up to 0.
93 for (int i = 0; i < ref_buffer_size; i++) {
94 reference_output_data[i] = std::max(
95 -128, static_cast<int>(
96 127 + std::round(16.0f * reference_output_float_data[i])));
97 }
98 }
99
100 template <typename T>
CheckOutputData(const T * test_output,const T * reference_output,const RuntimeShape & shape_common,const string & check_label,bool be_exacting)101 void CheckOutputData(const T* test_output, const T* reference_output,
102 const RuntimeShape& shape_common,
103 const string& check_label, bool be_exacting) {
104 const int buffer_size = shape_common.FlatSize();
105 // While calculating some metrics in floating point, we work with quantized
106 // scaling.
107 std::vector<int> diff(buffer_size);
108 int64_t sum_diff = 0;
109 int64_t sum_abs_diff = 0;
110 for (int i = 0; i < buffer_size; i++) {
111 diff[i] = static_cast<int>(test_output[i]) - reference_output[i];
112 sum_diff += diff[i];
113 sum_abs_diff += std::abs(diff[i]);
114 }
115 // These stats help understand test failures.
116 std::sort(std::begin(diff), std::end(diff));
117 const int min_diff = diff.front();
118 const int max_diff = diff.back();
119 const int median_diff = diff[diff.size() / 2];
120 const float mean_diff = static_cast<float>(sum_diff) / buffer_size;
121 const float mean_abs_diff = static_cast<float>(sum_abs_diff) / buffer_size;
122 // We either check for bit exactness (against the reference quantized version)
123 // or for general accuracy, allowing off-by-one (against the float reference).
124 if (be_exacting) {
125 ASSERT_TRUE(std::abs(min_diff) == 0 && std::abs(max_diff) == 0)
126 << check_label << ": "
127 << "std::abs(min_diff)=" << std::abs(min_diff)
128 << ", std::abs(max_diff)=" << std::abs(max_diff);
129 } else {
130 // For small numbers of samples, the estimates of the means vary more.
131 // Rather than widen the tolerances, we skip the smaller tests.
132 ASSERT_TRUE(((std::abs(mean_diff) < 2e-2f && mean_abs_diff < 3e-2f) ||
133 buffer_size < 10000) &&
134 std::abs(median_diff) == 0 && std::abs(min_diff) <= 1 &&
135 std::abs(max_diff) <= 1)
136 << check_label << ": "
137 << "buffer_size=" << buffer_size << ", mean_diff=" << mean_diff
138 << ", mean_abs_diff=" << mean_abs_diff
139 << ", median_diff=" << median_diff << ", min_diff=" << min_diff
140 << ", max_diff=" << max_diff;
141 }
142 }
143
144 // Runs the LogSoftmax and compares against the float reference implementation
145 // and the quantized reference implementation.
RunOneLogSoftmaxTest(const uint8 * input_data,const RuntimeShape & shape_common,int32 input_offset,const double input_scale,int stride,float beta)146 void RunOneLogSoftmaxTest(const uint8* input_data,
147 const RuntimeShape& shape_common, int32 input_offset,
148 const double input_scale, int stride, float beta) {
149 const int buffer_size = shape_common.FlatSize();
150 std::vector<uint8> optimized_logsoftmax_output(buffer_size);
151 std::vector<uint8> reference_float_logsoftmax_output(buffer_size);
152 std::vector<uint8> reference_quant_logsoftmax_output(buffer_size);
153
154 RunLogSoftmaxFloatReference(input_data, shape_common, input_offset,
155 input_scale, stride, beta,
156 reference_float_logsoftmax_output.data());
157
158 int32 input_beta_multiplier;
159 int input_beta_left_shift;
160 int32 reverse_scaling_divisor;
161 int reverse_scaling_right_shift;
162 static const int kScaledDiffIntegerBits = 5;
163 tflite::PreprocessLogSoftmaxScalingExp(
164 beta, input_scale, kScaledDiffIntegerBits, &input_beta_multiplier,
165 &input_beta_left_shift, &reverse_scaling_divisor,
166 &reverse_scaling_right_shift);
167 reverse_scaling_right_shift *= -1;
168 // diff_min has a negative value, and is used to limit the maximum magnitude
169 // of the diffs, which are <= 0.
170 const int diff_min = -tflite::CalculateInputRadius(kScaledDiffIntegerBits,
171 input_beta_left_shift);
172
173 SoftmaxParams params;
174 float table[256];
175 params.input_multiplier = input_beta_multiplier;
176 params.input_left_shift = input_beta_left_shift;
177 params.reverse_scaling_divisor = reverse_scaling_divisor;
178 params.reverse_scaling_right_shift = reverse_scaling_right_shift;
179 params.diff_min = diff_min;
180
181 params.scale = 1.0f / 16.0f;
182 params.zero_point = 255;
183 params.table = table;
184 optimized_ops::PopulateSoftmaxLookupTable(¶ms, input_scale, beta);
185 optimized_ops::LogSoftmax(params, input_scale, shape_common, input_data,
186 shape_common, optimized_logsoftmax_output.data());
187 reference_ops::LogSoftmax(params, shape_common, input_data, shape_common,
188 reference_quant_logsoftmax_output.data());
189
190 CheckOutputData<uint8_t>(optimized_logsoftmax_output.data(),
191 reference_float_logsoftmax_output.data(),
192 shape_common, "Optimized vs float reference", false);
193 CheckOutputData<uint8_t>(optimized_logsoftmax_output.data(),
194 reference_quant_logsoftmax_output.data(),
195 shape_common, "Optimized vs quant reference", false);
196 CheckOutputData<uint8_t>(reference_quant_logsoftmax_output.data(),
197 reference_float_logsoftmax_output.data(),
198 shape_common, "Quant reference vs float reference",
199 false);
200 }
201
202 // Runs the LogSoftmax and compares against the float reference implementation
203 // and the int8 quantized reference implementation.
RunOneLogSoftmaxTest(const int8 * input_data,const RuntimeShape & shape_common,int32 input_offset,const double input_scale,int stride,float beta)204 void RunOneLogSoftmaxTest(const int8* input_data,
205 const RuntimeShape& shape_common, int32 input_offset,
206 const double input_scale, int stride, float beta) {
207 const int buffer_size = shape_common.FlatSize();
208 std::vector<int8> quantized_logsoftmax_reference_implementation(buffer_size);
209 std::vector<int8> float_logsoftmax_optimized_implementation(buffer_size);
210
211 RunLogSoftmaxFloatReference(input_data, shape_common, input_offset,
212 input_scale, stride, beta,
213 float_logsoftmax_optimized_implementation.data());
214
215 int32 input_beta_multiplier;
216 int input_beta_left_shift;
217 int32 reverse_scaling_divisor;
218 int reverse_scaling_right_shift;
219 static const int kScaledDiffIntegerBits = 5;
220 tflite::PreprocessLogSoftmaxScalingExp(
221 beta, input_scale, kScaledDiffIntegerBits, &input_beta_multiplier,
222 &input_beta_left_shift, &reverse_scaling_divisor,
223 &reverse_scaling_right_shift);
224 reverse_scaling_right_shift *= -1;
225 // diff_min has a negative value, and is used to limit the maximum magnitude
226 // of the diffs, which are <= 0.
227 const int diff_min = -tflite::CalculateInputRadius(kScaledDiffIntegerBits,
228 input_beta_left_shift);
229
230 const int outer_size =
231 shape_common.Dims(0) * shape_common.Dims(1) * shape_common.Dims(2);
232 const int inner_size = shape_common.Dims(3);
233 reference_integer_ops::LogSoftmax(
234 input_beta_multiplier, input_beta_left_shift, reverse_scaling_divisor,
235 reverse_scaling_right_shift, diff_min, outer_size, inner_size, input_data,
236 quantized_logsoftmax_reference_implementation.data());
237
238 CheckOutputData<int8_t>(quantized_logsoftmax_reference_implementation.data(),
239 float_logsoftmax_optimized_implementation.data(),
240 shape_common, "Quant reference vs float reference",
241 false);
242 }
243
244 // This function picks some random LogSoftmax params, which are checked for
245 // desirability. If not acceptable, it returns false. If they're OK,
246 // it runs the LogSoftmax test and returns true. This allows the caller
247 // to loop until a test has been run.
248 //
249 // Currently we do not reject for any reason.
250 template <typename T>
TryOneUniformLogSoftmax()251 bool TryOneUniformLogSoftmax() {
252 // We pick mostly positive values, on the whole emphasizing smaller values and
253 // therefore faster tests. We test a wider range of depths. In the case of
254 // LogSoftmax, the width and height really just create test repetitions.
255 const int batch = ExponentialRandomPositiveInt(0.9f, 3, 20);
256 const int input_depth = ExponentialRandomPositiveInt(0.75f, 175, 500);
257 const int input_width = ExponentialRandomPositiveInt(0.8f, 20, 200);
258 const int input_height = ExponentialRandomPositiveInt(0.8f, 20, 200);
259 const int stride = ExponentialRandomPositiveInt(0.9f, 3, 8);
260 const double input_scale = std::pow(10.0, UniformRandomFloat(-2.0, 1.0));
261 const int32 input_offset = UniformRandomInt(-256, 0);
262 static constexpr float beta = 1.0f;
263
264 auto shape_common =
265 RuntimeShape({batch, input_height, input_width, input_depth});
266 const int buffer_size = shape_common.FlatSize();
267
268 std::vector<T> input_data(buffer_size);
269 FillRandom(&input_data);
270 RunOneLogSoftmaxTest(input_data.data(), shape_common, input_offset,
271 input_scale, stride, beta);
272 return true;
273 }
274
275 // See TryOneUniformLogSoftmax() for a general description.
276 //
277 // Tests with "skyscraper" input patterns are included for two reasons. (a)
278 // Bimodal distributions are potentially challenging and perhaps more
279 // realistic than simple uniform random inputs. (b) Some implementations of
280 // LogSoftmax may adapt as they traverse the depth, and so we test handling of
281 // cases where relatively small values are encountered at the beginning and end.
TryOneSkyscraperLogSoftmax(bool small_depth)282 bool TryOneSkyscraperLogSoftmax(bool small_depth) {
283 // We pick mostly positive values, on the whole emphasizing smaller values and
284 // therefore faster tests. We test a wider range of depths. In the case of
285 // LogSoftmax, the width and height really just create test repetitions.
286 const int batch = ExponentialRandomPositiveInt(0.9f, 3, 20);
287 const int input_depth = small_depth
288 ? ExponentialRandomPositiveInt(0.75f, 40, 500)
289 : ExponentialRandomPositiveInt(0.75f, 175, 500);
290 const int input_width = ExponentialRandomPositiveInt(0.7f, 20, 200);
291 const int input_height = ExponentialRandomPositiveInt(0.7f, 20, 200);
292 const int stride = ExponentialRandomPositiveInt(0.9f, 3, 8);
293 const double input_scale = std::pow(10.0, UniformRandomFloat(-2.0, 1.0));
294 const int32 input_offset = UniformRandomInt(-256, 0);
295 static constexpr float beta = 1.0f;
296 // Extra parameters for skyscraper input patterns.
297 const double middle_proportion =
298 ExponentialRandomPositiveFloat(0.65f, 0.1, 1.0);
299 const int middle_min = UniformRandomInt(0, 255);
300 const int sides_max = UniformRandomInt(0, middle_min);
301
302 auto shape_common =
303 RuntimeShape({batch, input_height, input_width, input_depth});
304 const int buffer_size = shape_common.FlatSize();
305
306 std::vector<uint8> input_data(buffer_size);
307 FillRandomSkyscraper(&input_data, input_depth, middle_proportion, middle_min,
308 sides_max);
309 RunOneLogSoftmaxTest(input_data.data(), shape_common, input_offset,
310 input_scale, stride, beta);
311 return true;
312 }
313
TEST(TestQuantizedLogSoftmax,UniformLogSoftmaxUint8Tests)314 TEST(TestQuantizedLogSoftmax, UniformLogSoftmaxUint8Tests) {
315 const int kTestsToRun = 100;
316 for (int i = 0; i < kTestsToRun; i++) {
317 while (!TryOneUniformLogSoftmax<uint8_t>()) {
318 }
319 }
320 }
321
TEST(TestQuantizedLogSoftmax,UniformLogSoftmaxUint8Int8Tests)322 TEST(TestQuantizedLogSoftmax, UniformLogSoftmaxUint8Int8Tests) {
323 const int kTestsToRun = 100;
324 for (int i = 0; i < kTestsToRun; i++) {
325 while (!TryOneUniformLogSoftmax<int8_t>()) {
326 }
327 }
328 }
329
TEST(TestQuantizedLogSoftmax,SkyscraperLogSoftmaxUint8Tests)330 TEST(TestQuantizedLogSoftmax, SkyscraperLogSoftmaxUint8Tests) {
331 const int kTestsToRun = 100;
332 for (int i = 0; i < kTestsToRun; i++) {
333 while (!TryOneSkyscraperLogSoftmax(false)) {
334 }
335 }
336 }
337
TEST(TestQuantizedLogSoftmax,SmallSkyscraperLogSoftmaxUint8Tests)338 TEST(TestQuantizedLogSoftmax, SmallSkyscraperLogSoftmaxUint8Tests) {
339 const int kTestsToRun = 100;
340 for (int i = 0; i < kTestsToRun; i++) {
341 while (!TryOneSkyscraperLogSoftmax(true)) {
342 }
343 }
344 }
345 } // namespace
346 } // namespace tflite
347