• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2020 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 
16 #include "tensorflow/lite/delegates/xnnpack/quantized_binary_elementwise_tester.h"
17 
18 #include <algorithm>
19 #include <array>
20 #include <cstdint>
21 #include <functional>
22 #include <limits>
23 #include <numeric>
24 #include <random>
25 #include <vector>
26 
27 #include <gtest/gtest.h>
28 #include "flatbuffers/flatbuffers.h"  // from @flatbuffers
29 #include "tensorflow/lite/kernels/register.h"
30 #include "tensorflow/lite/model.h"
31 #include "tensorflow/lite/schema/schema_conversion_utils.h"
32 #include "tensorflow/lite/schema/schema_generated.h"
33 #include "tensorflow/lite/version.h"
34 
35 namespace tflite {
36 namespace xnnpack {
37 
OutputShape() const38 std::vector<int32_t> QuantizedBinaryElementwiseTester::OutputShape() const {
39   std::vector<int32_t> output_shape;
40   if (!input1_shape_.empty()) {
41     output_shape.insert(
42         output_shape.end(), input1_shape_.cbegin(),
43         input1_shape_.cbegin() +
44             std::max(input1_shape_.size(), input2_shape_.size()) -
45             input2_shape_.size());
46   }
47   if (!input2_shape_.empty()) {
48     output_shape.insert(
49         output_shape.end(), input2_shape_.cbegin(),
50         input2_shape_.cbegin() +
51             std::max(input2_shape_.size(), input1_shape_.size()) -
52             input1_shape_.size());
53   }
54   for (size_t i = std::min(input1_shape_.size(), input2_shape_.size()); i >= 1;
55        i--) {
56     output_shape.push_back(
57         std::max(*(input1_shape_.cend() - i), *(input2_shape_.cend() - i)));
58   }
59   return output_shape;
60 }
61 
62 template <class T>
Test(Interpreter * delegate_interpreter,Interpreter * default_interpreter) const63 void QuantizedBinaryElementwiseTester::Test(
64     Interpreter* delegate_interpreter, Interpreter* default_interpreter) const {
65   std::random_device random_device;
66   auto rng = std::mt19937(random_device());
67   std::uniform_int_distribution<int32_t> input1_distribution(
68       std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
69   std::uniform_int_distribution<int32_t> input2_distribution(
70       std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
71   auto input1_rng = std::bind(input1_distribution, std::ref(rng));
72   auto input2_rng = std::bind(input2_distribution, std::ref(rng));
73   if (!Input1Static()) {
74     T* default_input1_data = default_interpreter->typed_input_tensor<T>(0);
75     std::generate(default_input1_data,
76                   default_input1_data + ComputeSize(Input1Shape()),
77                   std::ref(input1_rng));
78 
79     T* xnnpack_input1_data = delegate_interpreter->typed_input_tensor<T>(0);
80     std::copy(default_input1_data,
81               default_input1_data + ComputeSize(Input1Shape()),
82               xnnpack_input1_data);
83   }
84 
85   if (!Input2Static()) {
86     T* default_input2_data =
87         default_interpreter->typed_input_tensor<T>(Input1Static() ? 0 : 1);
88     std::generate(default_input2_data,
89                   default_input2_data + ComputeSize(Input2Shape()),
90                   std::ref(input2_rng));
91 
92     T* xnnpack_input2_data =
93         delegate_interpreter->typed_input_tensor<T>(Input1Static() ? 0 : 1);
94     std::copy(default_input2_data,
95               default_input2_data + ComputeSize(Input2Shape()),
96               xnnpack_input2_data);
97   }
98 
99   ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
100   ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
101 
102   T* default_output_data = default_interpreter->typed_output_tensor<T>(0);
103   T* delegate_output_data = delegate_interpreter->typed_output_tensor<T>(0);
104 
105   for (size_t i = 0; i < ComputeSize(OutputShape()); i++) {
106     ASSERT_LE(std::abs(static_cast<int32_t>(default_output_data[i]) -
107                        static_cast<int32_t>(delegate_output_data[i])),
108               1)
109         << "default " << static_cast<int32_t>(default_output_data[i])
110         << ", delegate " << static_cast<int32_t>(delegate_output_data[i])
111         << " at index " << i << " / " << ComputeSize(OutputShape());
112   }
113 }
114 
Test(tflite::BuiltinOperator binary_op,TfLiteDelegate * delegate) const115 void QuantizedBinaryElementwiseTester::Test(tflite::BuiltinOperator binary_op,
116                                             TfLiteDelegate* delegate) const {
117   if (Input1Static()) {
118     ASSERT_FALSE(Input2Static());
119   }
120 
121   std::vector<char> buffer = CreateTfLiteModel(binary_op);
122   const Model* model = GetModel(buffer.data());
123 
124   std::unique_ptr<Interpreter> delegate_interpreter;
125   ASSERT_EQ(
126       InterpreterBuilder(
127           model,
128           ::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
129           &delegate_interpreter),
130       kTfLiteOk);
131   std::unique_ptr<Interpreter> default_interpreter;
132   ASSERT_EQ(
133       InterpreterBuilder(
134           model,
135           ::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
136           &default_interpreter),
137       kTfLiteOk);
138 
139   ASSERT_TRUE(delegate_interpreter);
140   ASSERT_TRUE(default_interpreter);
141 
142   if (Input1Static() || Input2Static()) {
143     ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
144     ASSERT_EQ(default_interpreter->inputs().size(), 1);
145   } else {
146     ASSERT_EQ(delegate_interpreter->inputs().size(), 2);
147     ASSERT_EQ(default_interpreter->inputs().size(), 2);
148   }
149 
150   ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
151   ASSERT_EQ(default_interpreter->outputs().size(), 1);
152 
153   ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
154   ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
155 
156   ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
157 
158   if (Unsigned()) {
159     Test<uint8_t>(delegate_interpreter.get(), default_interpreter.get());
160   } else {
161     Test<int8_t>(delegate_interpreter.get(), default_interpreter.get());
162   }
163 }
164 
CreateTfLiteModel(tflite::BuiltinOperator binary_op) const165 std::vector<char> QuantizedBinaryElementwiseTester::CreateTfLiteModel(
166     tflite::BuiltinOperator binary_op) const {
167   std::random_device random_device;
168   auto rng = std::mt19937(random_device());
169   std::uniform_int_distribution<int32_t> input1_distribution(
170       std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max());
171   std::uniform_int_distribution<int32_t> input2_distribution(
172       std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max());
173   auto input1_rng = std::bind(input1_distribution, std::ref(rng));
174   auto input2_rng = std::bind(input2_distribution, std::ref(rng));
175 
176   flatbuffers::FlatBufferBuilder builder;
177   const std::array<flatbuffers::Offset<OperatorCode>, 1> operator_codes{
178       {CreateOperatorCode(builder, binary_op)}};
179 
180   std::vector<flatbuffers::Offset<Buffer>> buffers{{
181       CreateBuffer(builder, builder.CreateVector({})),
182   }};
183 
184   int32_t input1_buffer = 0;
185   if (Input1Static()) {
186     std::vector<int8_t> input1_data(ComputeSize(Input1Shape()));
187     std::generate(input1_data.begin(), input1_data.end(), input1_rng);
188 
189     input1_buffer = buffers.size();
190     buffers.push_back(CreateBuffer(
191         builder, builder.CreateVector(
192                      reinterpret_cast<const uint8_t*>(input1_data.data()),
193                      sizeof(int8_t) * input1_data.size())));
194   }
195 
196   int32_t input2_buffer = 0;
197   if (Input2Static()) {
198     std::vector<int8_t> input2_data(ComputeSize(Input2Shape()));
199     std::generate(input2_data.begin(), input2_data.end(), input2_rng);
200 
201     input2_buffer = buffers.size();
202     buffers.push_back(CreateBuffer(
203         builder, builder.CreateVector(
204                      reinterpret_cast<const uint8_t*>(input2_data.data()),
205                      sizeof(int8_t) * input2_data.size())));
206   }
207 
208   const std::vector<int32_t> output_shape = OutputShape();
209   const std::array<flatbuffers::Offset<Tensor>, 3> tensors{{
210       CreateTensor(builder,
211                    builder.CreateVector<int32_t>(Input1Shape().data(),
212                                                  Input1Shape().size()),
213                    Unsigned() ? TensorType_UINT8 : TensorType_INT8,
214                    input1_buffer, /*name=*/0,
215                    CreateQuantizationParameters(
216                        builder, /*min=*/0, /*max=*/0,
217                        builder.CreateVector<float>({Input1Scale()}),
218                        builder.CreateVector<int64_t>({Input1ZeroPoint()}))),
219       CreateTensor(builder,
220                    builder.CreateVector<int32_t>(Input2Shape().data(),
221                                                  Input2Shape().size()),
222                    Unsigned() ? TensorType_UINT8 : TensorType_INT8,
223                    input2_buffer, /*name=*/0,
224                    CreateQuantizationParameters(
225                        builder, /*min=*/0, /*max=*/0,
226                        builder.CreateVector<float>({Input2Scale()}),
227                        builder.CreateVector<int64_t>({Input2ZeroPoint()}))),
228       CreateTensor(builder,
229                    builder.CreateVector<int32_t>(OutputShape().data(),
230                                                  OutputShape().size()),
231                    Unsigned() ? TensorType_UINT8 : TensorType_INT8,
232                    /*buffer=*/0, /*name=*/0,
233                    CreateQuantizationParameters(
234                        builder, /*min=*/0, /*max=*/0,
235                        builder.CreateVector<float>({OutputScale()}),
236                        builder.CreateVector<int64_t>({OutputZeroPoint()}))),
237   }};
238 
239   const std::array<int32_t, 2> op_inputs{{0, 1}};
240   const std::array<int32_t, 1> op_outputs{{2}};
241   const std::array<flatbuffers::Offset<Operator>, 1> operators{{CreateOperator(
242       builder, /*opcode_index=*/0,
243       builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
244       builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
245       BuiltinOptions_AddOptions,
246       CreateAddOptions(builder, Activation()).Union())}};
247 
248   std::vector<int32_t> subgraph_inputs;
249   if (!Input1Static()) {
250     subgraph_inputs.push_back(0);
251   }
252   if (!Input2Static()) {
253     subgraph_inputs.push_back(1);
254   }
255   const std::array<int32_t, 1> subgraph_outputs{{2}};
256   flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
257       builder, builder.CreateVector(tensors.data(), tensors.size()),
258       builder.CreateVector<int32_t>(subgraph_inputs.data(),
259                                     subgraph_inputs.size()),
260       builder.CreateVector<int32_t>(subgraph_outputs.data(),
261                                     subgraph_outputs.size()),
262       builder.CreateVector(operators.data(), operators.size()));
263 
264   flatbuffers::Offset<flatbuffers::String> description =
265       builder.CreateString("Quantized binary operator model");
266 
267   flatbuffers::Offset<Model> model_buffer = CreateModel(
268       builder, TFLITE_SCHEMA_VERSION,
269       builder.CreateVector(operator_codes.data(), operator_codes.size()),
270       builder.CreateVector(&subgraph, 1), description,
271       builder.CreateVector(buffers.data(), buffers.size()));
272 
273   builder.Finish(model_buffer);
274 
275   return std::vector<char>(builder.GetBufferPointer(),
276                            builder.GetBufferPointer() + builder.GetSize());
277 }
278 
ComputeSize(const std::vector<int32_t> & shape)279 int32_t QuantizedBinaryElementwiseTester::ComputeSize(
280     const std::vector<int32_t>& shape) {
281   return std::accumulate(shape.cbegin(), shape.cend(), 1,
282                          std::multiplies<int32_t>());
283 }
284 
285 }  // namespace xnnpack
286 }  // namespace tflite
287