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(¶ms, 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(¶ms, 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