• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&params, 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