• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2018 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/reference_ops.h"
29 #include "tensorflow/lite/kernels/internal/test_util.h"
30 #include "tensorflow/lite/string_type.h"
31 
32 namespace tflite {
33 namespace {
34 
RunSoftmaxFloatReference(const uint8 * input_data,const RuntimeShape & shape_common,int32 input_offset,const double input_scale,int stride,float beta,uint8 * reference_output_data)35 void RunSoftmaxFloatReference(const uint8* input_data,
36                               const RuntimeShape& shape_common,
37                               int32 input_offset, const double input_scale,
38                               int stride, float beta,
39                               uint8* reference_output_data) {
40   const int ref_buffer_size = shape_common.FlatSize();
41   std::vector<float> reference_dequant_data(ref_buffer_size);
42   std::vector<float> reference_output_float_data(ref_buffer_size);
43 
44   // Reference data generated via Dequant of input into float, and then applying
45   // float Softmax.
46   DequantizationParams dq_params;
47   dq_params.zero_point = input_offset;
48   dq_params.scale = input_scale;
49   reference_ops::Dequantize(dq_params, shape_common, input_data, shape_common,
50                             reference_dequant_data.data());
51   SoftmaxParams sm_params;
52   sm_params.beta = beta;
53   optimized_ops::Softmax(sm_params, shape_common, reference_dequant_data.data(),
54                          shape_common, reference_output_float_data.data());
55   // Work with quantized scaling for Softmax, under which 256 represents 1, but
56   // we limit this to 255.
57   for (int i = 0; i < ref_buffer_size; i++) {
58     reference_output_data[i] = std::min(
59         255,
60         static_cast<int>(std::round(256.0f * reference_output_float_data[i])));
61   }
62 }
63 
64 template <typename T>
CheckOutputData(const T * test_output,const T * reference_output,const RuntimeShape & shape_common,const string & check_label,bool be_exacting)65 void CheckOutputData(const T* test_output, const T* reference_output,
66                      const RuntimeShape& shape_common,
67                      const string& check_label, bool be_exacting) {
68   const int buffer_size = shape_common.FlatSize();
69   // While calculating some metrics in floating point, we work with quantized
70   // scaling.
71   std::vector<int> diff(buffer_size);
72   int64_t sum_diff = 0;
73   int64_t sum_abs_diff = 0;
74   for (int i = 0; i < buffer_size; i++) {
75     diff[i] = static_cast<int>(test_output[i]) - reference_output[i];
76     sum_diff += diff[i];
77     sum_abs_diff += std::abs(diff[i]);
78   }
79   // These stats help understand test failures.
80   std::sort(std::begin(diff), std::end(diff));
81   const int min_diff = diff.front();
82   const int max_diff = diff.back();
83   const int median_diff = diff[diff.size() / 2];
84   const float mean_diff = static_cast<float>(sum_diff) / buffer_size;
85   const float mean_abs_diff = static_cast<float>(sum_abs_diff) / buffer_size;
86   // We either check for bit exactness (against the reference quantized version)
87   // or for general accuracy, allowing off-by-one (against the float reference).
88   if (be_exacting) {
89     ASSERT_EQ(std::abs(min_diff), 0);
90     ASSERT_EQ(std::abs(max_diff), 0);
91   } else {
92     // For small numbers of samples, the estimates of the means vary more.
93     // Rather than widen the tolerances, we skip the smaller tests.
94     if (buffer_size >= 10000) {
95       ASSERT_LT(std::abs(mean_diff), 2e-2f);
96       ASSERT_LT(mean_abs_diff, 3e-2f);
97     }
98     ASSERT_EQ(std::abs(median_diff), 0);
99     ASSERT_LE(std::abs(min_diff), 1);
100     ASSERT_LE(std::abs(max_diff), 1);
101   }
102 }
103 
104 // Runs the Softmax and compares against the float reference implementation and
105 // the quantized reference implementation.
RunOneSoftmaxTest(const uint8 * input_data,const RuntimeShape & shape_common,int32 input_offset,const double input_scale,int stride,float beta)106 void RunOneSoftmaxTest(const uint8* input_data,
107                        const RuntimeShape& shape_common, int32 input_offset,
108                        const double input_scale, int stride, float beta) {
109   const int buffer_size = shape_common.FlatSize();
110   std::vector<uint8> optimized_softmax_output(buffer_size);
111   std::vector<uint8> reference_float_softmax_output(buffer_size);
112   std::vector<uint8> reference_quant_softmax_output(buffer_size);
113 
114   RunSoftmaxFloatReference(input_data, shape_common, input_offset, input_scale,
115                            stride, beta, reference_float_softmax_output.data());
116 
117   int32 input_beta_multiplier;
118   int input_beta_left_shift;
119   static const int kScaledDiffIntegerBits = 5;
120   tflite::PreprocessSoftmaxScaling(beta, input_scale, kScaledDiffIntegerBits,
121                                    &input_beta_multiplier,
122                                    &input_beta_left_shift);
123   // diff_min has a negative value, and is used to limit the maximum magnitude
124   // of the diffs, which are <= 0.
125   const int diff_min = -tflite::CalculateInputRadius(kScaledDiffIntegerBits,
126                                                      input_beta_left_shift);
127 
128   SoftmaxParams params;
129   float table[256];
130   params.input_multiplier = input_beta_multiplier;
131   params.input_left_shift = input_beta_left_shift;
132   params.diff_min = diff_min;
133   params.scale = 1.0f / 256;
134   params.zero_point = 0;
135   params.table = table;
136 
137   optimized_ops::PopulateSoftmaxLookupTable(&params, input_scale, beta);
138   optimized_ops::Softmax(params, shape_common, input_data, shape_common,
139                          optimized_softmax_output.data());
140   reference_ops::Softmax(params, shape_common, input_data, shape_common,
141                          reference_quant_softmax_output.data());
142 
143   CheckOutputData<uint8_t>(optimized_softmax_output.data(),
144                            reference_float_softmax_output.data(), shape_common,
145                            "Optimized vs float reference", false);
146   CheckOutputData<uint8_t>(optimized_softmax_output.data(),
147                            reference_quant_softmax_output.data(), shape_common,
148                            "Optimized vs quant reference", false);
149   CheckOutputData<uint8_t>(reference_quant_softmax_output.data(),
150                            reference_float_softmax_output.data(), shape_common,
151                            "Quant reference vs float reference", false);
152 
153 #if __aarch64__ && __clang__
154   uint8_t uint8_table1[256];
155   uint8_t uint8_table2[256];
156   params.uint8_table1 = uint8_table1;
157   params.uint8_table2 = uint8_table2;
158   optimized_ops::PopulateSoftmaxUInt8LookupTable(&params, input_scale, beta);
159   optimized_ops::SoftmaxInt8LUT(params, shape_common, input_data, shape_common,
160                                 optimized_softmax_output.data());
161   CheckOutputData<uint8_t>(
162       optimized_softmax_output.data(), reference_quant_softmax_output.data(),
163       shape_common, "Optimized (Uint8 Lookup table) vs quant reference", false);
164 #endif
165 }
166 
167 // This function picks some random Softmax params, which are checked for
168 // desirability.  If not acceptable, it returns false. If they're OK,
169 // it runs the Softmax test and returns true. This allows the caller
170 // to loop until a test has been run.
171 //
172 // Currently we do not reject for any reason.
TryOneUniformSoftmax()173 bool TryOneUniformSoftmax() {
174   // We pick mostly positive values, on the whole emphasizing smaller values and
175   // therefore faster tests.  We test a wider range of depths.  In the case of
176   // Softmax, the width and height really just create test repetitions.
177   const int batch = ExponentialRandomPositiveInt(0.9f, 3, 20);
178   const int input_depth = ExponentialRandomPositiveInt(0.75f, 175, 500);
179   const int input_width = ExponentialRandomPositiveInt(0.8f, 20, 200);
180   const int input_height = ExponentialRandomPositiveInt(0.8f, 20, 200);
181   const int stride = ExponentialRandomPositiveInt(0.9f, 3, 8);
182   const double input_scale = std::pow(10.0, UniformRandomFloat(-2.0, 1.0));
183   const int32 input_offset = UniformRandomInt(-256, 0);
184   const float beta = 1.0f + ExponentialRandomPositiveFloat(0.9f, 2, 10);
185 
186   auto shape_common =
187       RuntimeShape({batch, input_height, input_width, input_depth});
188   const int buffer_size = shape_common.FlatSize();
189 
190   std::vector<uint8> input_data(buffer_size);
191   FillRandom(&input_data);
192   RunOneSoftmaxTest(input_data.data(), shape_common, input_offset, input_scale,
193                     stride, beta);
194   return true;
195 }
196 
197 // See TryOneUniformSoftmax() for a general description.
198 //
199 // Tests with "skyscraper" input patterns are included for two reasons. (a)
200 // Bimodal distributions are potentially challenging and perhaps more
201 // realistic than simple uniform random inputs.  (b) Some implementations of
202 // Softmax may adapt as they traverse the depth, and so we test handling of
203 // cases where relatively small values are encountered at the beginning and end.
TryOneSkyscraperSoftmax(bool small_depth)204 bool TryOneSkyscraperSoftmax(bool small_depth) {
205   // We pick mostly positive values, on the whole emphasizing smaller values and
206   // therefore faster tests.  We test a wider range of depths.  In the case of
207   // Softmax, the width and height really just create test repetitions.
208   const int batch = ExponentialRandomPositiveInt(0.9f, 3, 20);
209   const int input_depth = small_depth
210                               ? ExponentialRandomPositiveInt(0.75f, 40, 500)
211                               : ExponentialRandomPositiveInt(0.75f, 175, 500);
212   const int input_width = ExponentialRandomPositiveInt(0.7f, 20, 200);
213   const int input_height = ExponentialRandomPositiveInt(0.7f, 20, 200);
214   const int stride = ExponentialRandomPositiveInt(0.9f, 3, 8);
215   const double input_scale = std::pow(10.0, UniformRandomFloat(-2.0, 1.0));
216   const int32 input_offset = UniformRandomInt(-256, 0);
217   const float beta = 1.0f + ExponentialRandomPositiveFloat(0.9f, 2, 10);
218   // Extra parameters for skyscraper input patterns.
219   const double middle_proportion =
220       ExponentialRandomPositiveFloat(0.65f, 0.1, 1.0);
221   const int middle_min = UniformRandomInt(0, 255);
222   const int sides_max = UniformRandomInt(0, middle_min);
223 
224   auto shape_common =
225       RuntimeShape({batch, input_height, input_width, input_depth});
226   const int buffer_size = shape_common.FlatSize();
227 
228   std::vector<uint8> input_data(buffer_size);
229   FillRandomSkyscraper(&input_data, input_depth, middle_proportion, middle_min,
230                        sides_max);
231   RunOneSoftmaxTest(input_data.data(), shape_common, input_offset, input_scale,
232                     stride, beta);
233   return true;
234 }
235 
TEST(TestQuantizedSoftmax,UniformSoftmaxTests)236 TEST(TestQuantizedSoftmax, UniformSoftmaxTests) {
237   const int kTestsToRun = 100;
238   for (int i = 0; i < kTestsToRun; i++) {
239     while (!TryOneUniformSoftmax()) {
240     }
241   }
242 }
243 
TEST(TestQuantizedSoftmax,SkyscraperSoftmaxTests)244 TEST(TestQuantizedSoftmax, SkyscraperSoftmaxTests) {
245   const int kTestsToRun = 100;
246   for (int i = 0; i < kTestsToRun; i++) {
247     while (!TryOneSkyscraperSoftmax(false)) {
248     }
249   }
250 }
251 
TEST(TestQuantizedSoftmax,SmallSkyscraperSoftmaxTests)252 TEST(TestQuantizedSoftmax, SmallSkyscraperSoftmaxTests) {
253   const int kTestsToRun = 100;
254   for (int i = 0; i < kTestsToRun; i++) {
255     while (!TryOneSkyscraperSoftmax(true)) {
256     }
257   }
258 }
259 }  // namespace
260 }  // namespace tflite
261