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