1 /* Copyright 2017 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 "tensorflow/lite/kernels/test_util.h"
16
17 #include <numeric>
18
19 #include <gmock/gmock.h>
20 #include <gtest/gtest.h>
21 #include "tensorflow/core/platform/logging.h"
22 #include "tensorflow/lite/c/common.h"
23 #include "tensorflow/lite/delegates/nnapi/acceleration_test_util.h"
24 #include "tensorflow/lite/delegates/nnapi/nnapi_delegate.h"
25 #include "tensorflow/lite/interpreter.h"
26 #include "tensorflow/lite/kernels/acceleration_test_util.h"
27 #include "tensorflow/lite/minimal_logging.h"
28 #include "tensorflow/lite/nnapi/nnapi_implementation.h"
29 #include "tensorflow/lite/version.h"
30
31 namespace tflite {
32
33 using ::testing::FloatNear;
34 using ::testing::Matcher;
35
36 namespace {
37
38 // Whether to enable (global) use of NNAPI. Note that this will typically
39 // be set via a command-line flag.
40 static bool force_use_nnapi = false;
41
TestNnApiDelegate()42 TfLiteDelegate* TestNnApiDelegate() {
43 static TfLiteDelegate* delegate = [] {
44 StatefulNnApiDelegate::Options options;
45 // In Android Q, the NNAPI delegate avoids delegation if the only device
46 // is the reference CPU. However, for testing purposes, we still want
47 // delegation coverage, so force use of this reference path.
48 options.accelerator_name = "nnapi-reference";
49 return new StatefulNnApiDelegate(options);
50 }();
51 return delegate;
52 }
53
54 } // namespace
55
ArrayFloatNear(const std::vector<float> & values,float max_abs_error)56 std::vector<Matcher<float>> ArrayFloatNear(const std::vector<float>& values,
57 float max_abs_error) {
58 std::vector<Matcher<float>> matchers;
59 matchers.reserve(values.size());
60 for (const float& v : values) {
61 matchers.emplace_back(FloatNear(v, max_abs_error));
62 }
63 return matchers;
64 }
65
ArrayComplex64Near(const std::vector<std::complex<float>> & values,float max_abs_error)66 std::vector<Matcher<std::complex<float>>> ArrayComplex64Near(
67 const std::vector<std::complex<float>>& values, float max_abs_error) {
68 std::vector<Matcher<std::complex<float>>> matchers;
69 matchers.reserve(values.size());
70 for (const std::complex<float>& v : values) {
71 matchers.emplace_back(
72 AllOf(::testing::Property(&std::complex<float>::real,
73 FloatNear(v.real(), max_abs_error)),
74 ::testing::Property(&std::complex<float>::imag,
75 FloatNear(v.imag(), max_abs_error))));
76 }
77 return matchers;
78 }
79
AddInput(const TensorData & t,bool is_variable)80 int SingleOpModel::AddInput(const TensorData& t, bool is_variable) {
81 int id = 0;
82 if (t.per_channel_quantization) {
83 id = AddTensorPerChannelQuant(t);
84 } else {
85 id = AddTensor<float>(t, {}, is_variable);
86 }
87 inputs_.push_back(id);
88 return id;
89 }
90
AddIntermediate(TensorType type,const std::vector<float> & scale,const std::vector<int64_t> & zero_point)91 int SingleOpModel::AddIntermediate(TensorType type,
92 const std::vector<float>& scale,
93 const std::vector<int64_t>& zero_point) {
94 // Currently supports only int16 intermediate types.
95 // TODO(jianlijianli): make use of the type.
96 int id = tensors_.size();
97 flatbuffers::Offset<QuantizationParameters> q_params =
98 CreateQuantizationParameters(builder_, /*min=*/0, /*max=*/0,
99 builder_.CreateVector<float>(scale),
100 builder_.CreateVector<int64_t>(zero_point));
101 tensors_.push_back(CreateTensor(builder_, builder_.CreateVector<int>({}),
102 type,
103 /*buffer=*/0,
104 /*name=*/0, q_params, false));
105 intermediates_.push_back(id);
106 return id;
107 }
108
AddNullInput()109 int SingleOpModel::AddNullInput() {
110 int id = kTfLiteOptionalTensor;
111 inputs_.push_back(id);
112 return id;
113 }
114
AddOutput(const TensorData & t)115 int SingleOpModel::AddOutput(const TensorData& t) {
116 int id = AddTensor<float>(t, {});
117 outputs_.push_back(id);
118 return id;
119 }
120
SetBuiltinOp(BuiltinOperator type,BuiltinOptions builtin_options_type,flatbuffers::Offset<void> builtin_options)121 void SingleOpModel::SetBuiltinOp(BuiltinOperator type,
122 BuiltinOptions builtin_options_type,
123 flatbuffers::Offset<void> builtin_options) {
124 opcodes_.push_back(CreateOperatorCode(builder_, type, 0));
125 operators_.push_back(CreateOperator(
126 builder_, /*opcode_index=*/0, builder_.CreateVector<int32_t>(inputs_),
127 builder_.CreateVector<int32_t>(outputs_), builtin_options_type,
128 builtin_options,
129 /*custom_options=*/0, CustomOptionsFormat_FLEXBUFFERS, 0,
130 builder_.CreateVector<int32_t>(intermediates_)));
131 }
132
SetCustomOp(const string & name,const std::vector<uint8_t> & custom_option,const std::function<TfLiteRegistration * ()> & registration)133 void SingleOpModel::SetCustomOp(
134 const string& name, const std::vector<uint8_t>& custom_option,
135 const std::function<TfLiteRegistration*()>& registration) {
136 custom_registrations_[name] = registration;
137 opcodes_.push_back(
138 CreateOperatorCodeDirect(builder_, BuiltinOperator_CUSTOM, name.data()));
139 operators_.push_back(CreateOperator(
140 builder_, /*opcode_index=*/0, builder_.CreateVector<int32_t>(inputs_),
141 builder_.CreateVector<int32_t>(outputs_), BuiltinOptions_NONE, 0,
142 builder_.CreateVector<uint8_t>(custom_option),
143 CustomOptionsFormat_FLEXBUFFERS));
144 }
145
BuildInterpreter(std::vector<std::vector<int>> input_shapes,int num_threads,bool allow_fp32_relax_to_fp16,bool apply_delegate)146 void SingleOpModel::BuildInterpreter(std::vector<std::vector<int>> input_shapes,
147 int num_threads,
148 bool allow_fp32_relax_to_fp16,
149 bool apply_delegate) {
150 auto opcodes = builder_.CreateVector(opcodes_);
151 auto operators = builder_.CreateVector(operators_);
152 auto tensors = builder_.CreateVector(tensors_);
153 auto inputs = builder_.CreateVector<int32_t>(inputs_);
154 auto outputs = builder_.CreateVector<int32_t>(outputs_);
155 // Create a single subgraph
156 std::vector<flatbuffers::Offset<SubGraph>> subgraphs;
157 auto subgraph = CreateSubGraph(builder_, tensors, inputs, outputs, operators);
158 subgraphs.push_back(subgraph);
159 auto subgraphs_flatbuffer = builder_.CreateVector(subgraphs);
160
161 auto buffers = builder_.CreateVector(buffers_);
162 auto description = builder_.CreateString("programmatic model");
163 builder_.Finish(CreateModel(builder_, TFLITE_SCHEMA_VERSION, opcodes,
164 subgraphs_flatbuffer, description, buffers));
165
166 auto* model = GetModel(builder_.GetBufferPointer());
167
168 if (!resolver_) {
169 auto resolver = new ops::builtin::BuiltinOpResolver();
170 for (const auto& reg : custom_registrations_) {
171 resolver->AddCustom(reg.first.data(), reg.second());
172 }
173 resolver_ = std::unique_ptr<OpResolver>(resolver);
174 }
175 CHECK(InterpreterBuilder(model, *resolver_)(&interpreter_, num_threads) ==
176 kTfLiteOk);
177
178 CHECK(interpreter_ != nullptr);
179
180 for (size_t i = 0; i < input_shapes.size(); ++i) {
181 const int input_idx = interpreter_->inputs()[i];
182 if (input_idx == kTfLiteOptionalTensor) continue;
183 const auto& shape = input_shapes[i];
184 if (shape.empty()) continue;
185 CHECK(interpreter_->ResizeInputTensor(input_idx, shape) == kTfLiteOk);
186 }
187
188 interpreter_->SetAllowFp16PrecisionForFp32(allow_fp32_relax_to_fp16);
189
190 CHECK(interpreter_->AllocateTensors() == kTfLiteOk)
191 << "Cannot allocate tensors";
192 interpreter_->ResetVariableTensors();
193
194 // In some rare cases a test may need to postpone modifying the graph with
195 // a delegate, e.g. if tensors are not fully specified. In such cases the
196 // test has to explicitly call ApplyDelegate() when necessary.
197 if (apply_delegate) ApplyDelegate();
198 }
199
ApplyDelegate()200 void SingleOpModel::ApplyDelegate() {
201 if (force_use_nnapi) {
202 // TODO(b/124505407): Check the result and fail accordingly.
203 interpreter_->ModifyGraphWithDelegate(TestNnApiDelegate());
204 }
205
206 // Modify delegate with function.
207 if (apply_delegate_fn_) {
208 apply_delegate_fn_(interpreter_.get());
209 }
210 }
211
Invoke()212 void SingleOpModel::Invoke() { ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); }
213
InvokeUnchecked()214 TfLiteStatus SingleOpModel::InvokeUnchecked() { return interpreter_->Invoke(); }
215
BuildInterpreter(std::vector<std::vector<int>> input_shapes)216 void SingleOpModel::BuildInterpreter(
217 std::vector<std::vector<int>> input_shapes) {
218 BuildInterpreter(input_shapes, /*num_threads=*/-1,
219 /*allow_fp32_relax_to_fp16=*/false,
220 /*apply_delegate=*/true);
221 }
222
BuildInterpreter(std::vector<std::vector<int>> input_shapes,bool allow_fp32_relax_to_fp16,bool apply_delegate)223 void SingleOpModel::BuildInterpreter(std::vector<std::vector<int>> input_shapes,
224 bool allow_fp32_relax_to_fp16,
225 bool apply_delegate) {
226 BuildInterpreter(input_shapes, /*num_threads=*/-1, allow_fp32_relax_to_fp16,
227 apply_delegate);
228 }
229
BuildInterpreter(std::vector<std::vector<int>> input_shapes,int num_threads)230 void SingleOpModel::BuildInterpreter(std::vector<std::vector<int>> input_shapes,
231 int num_threads) {
232 BuildInterpreter(input_shapes, num_threads,
233 /*allow_fp32_relax_to_fp16=*/false,
234 /*apply_delegate=*/true);
235 }
236
237 // static
SetForceUseNnapi(bool use_nnapi)238 void SingleOpModel::SetForceUseNnapi(bool use_nnapi) {
239 force_use_nnapi = use_nnapi;
240 }
241
242 // static
GetForceUseNnapi()243 bool SingleOpModel::GetForceUseNnapi() { return force_use_nnapi; }
244
GetTensorSize(int index) const245 int32_t SingleOpModel::GetTensorSize(int index) const {
246 TfLiteTensor* t = interpreter_->tensor(index);
247 CHECK(t);
248 int total_size = 1;
249 for (int i = 0; i < t->dims->size; ++i) {
250 total_size *= t->dims->data[i];
251 }
252 return total_size;
253 }
254
255 template <>
ExtractVector(int index) const256 std::vector<string> SingleOpModel::ExtractVector(int index) const {
257 TfLiteTensor* tensor_ptr = interpreter_->tensor(index);
258 CHECK(tensor_ptr != nullptr);
259 const int num_strings = GetStringCount(tensor_ptr);
260 std::vector<string> result;
261 result.reserve(num_strings);
262 for (int i = 0; i < num_strings; ++i) {
263 const auto str = GetString(tensor_ptr, i);
264 result.emplace_back(str.str, str.len);
265 }
266 return result;
267 }
268
269 namespace {
270
271 // Returns the number of partitions associated, as result of a call to
272 // ModifyGraphWithDelegate, to the given delegate.
CountPartitionsDelegatedTo(Subgraph * subgraph,const TfLiteDelegate * delegate)273 int CountPartitionsDelegatedTo(Subgraph* subgraph,
274 const TfLiteDelegate* delegate) {
275 return std::count_if(
276 subgraph->nodes_and_registration().begin(),
277 subgraph->nodes_and_registration().end(),
278 [delegate](
279 std::pair<TfLiteNode, TfLiteRegistration> node_and_registration) {
280 return node_and_registration.first.delegate == delegate;
281 });
282 }
283
284 // Returns the number of partitions associated, as result of a call to
285 // ModifyGraphWithDelegate, to the given delegate.
CountPartitionsDelegatedTo(Interpreter * interpreter,const TfLiteDelegate * delegate)286 int CountPartitionsDelegatedTo(Interpreter* interpreter,
287 const TfLiteDelegate* delegate) {
288 int result = 0;
289 for (int i = 0; i < interpreter->subgraphs_size(); i++) {
290 Subgraph* subgraph = interpreter->subgraph(i);
291
292 result += CountPartitionsDelegatedTo(subgraph, delegate);
293 }
294
295 return result;
296 }
297
298 // Returns the number of nodes that will be executed on the CPU
CountPartitionsExecutedByCpuKernel(const Interpreter * interpreter)299 int CountPartitionsExecutedByCpuKernel(const Interpreter* interpreter) {
300 int result = 0;
301 for (int node_idx : interpreter->execution_plan()) {
302 TfLiteNode node;
303 TfLiteRegistration reg;
304 std::tie(node, reg) = *(interpreter->node_and_registration(node_idx));
305
306 if (node.delegate == nullptr) {
307 ++result;
308 }
309 }
310
311 return result;
312 }
313
314 } // namespace
315
ExpectOpAcceleratedWithNnapi(const std::string & test_id)316 void SingleOpModel::ExpectOpAcceleratedWithNnapi(const std::string& test_id) {
317 std::optional<NnapiAccelerationTestParams> validation_params =
318 GetNnapiAccelerationTestParam(test_id);
319 if (!validation_params.has_value()) {
320 return;
321 }
322
323 TFLITE_LOG_PROD(TFLITE_LOG_INFO, "Validating acceleration");
324 const NnApi* nnapi = NnApiImplementation();
325 if (nnapi && nnapi->nnapi_exists &&
326 nnapi->android_sdk_version >=
327 validation_params.value().MinAndroidSdkVersion()) {
328 EXPECT_EQ(
329 CountPartitionsDelegatedTo(interpreter_.get(), TestNnApiDelegate()), 1)
330 << "Expecting operation to be accelerated but cannot find a partition "
331 "associated to the NNAPI delegate";
332 }
333 }
334
ValidateAcceleration()335 void SingleOpModel::ValidateAcceleration() {
336 if (force_use_nnapi) {
337 ExpectOpAcceleratedWithNnapi(GetCurrentTestId());
338 }
339 }
340
CountOpsExecutedByCpuKernel()341 int SingleOpModel::CountOpsExecutedByCpuKernel() {
342 return CountPartitionsExecutedByCpuKernel(interpreter_.get());
343 }
344
~SingleOpModel()345 SingleOpModel::~SingleOpModel() { ValidateAcceleration(); }
346
AddBuiltinOp(BuiltinOperator type,BuiltinOptions builtin_options_type,const flatbuffers::Offset<void> & builtin_options,const std::vector<int32_t> & inputs,const std::vector<int32_t> & outputs)347 void MultiOpModel::AddBuiltinOp(
348 BuiltinOperator type, BuiltinOptions builtin_options_type,
349 const flatbuffers::Offset<void>& builtin_options,
350 const std::vector<int32_t>& inputs, const std::vector<int32_t>& outputs) {
351 opcodes_.push_back(CreateOperatorCode(builder_, type, 0));
352 const int opcode_index = opcodes_.size() - 1;
353 operators_.push_back(CreateOperator(
354 builder_, opcode_index, builder_.CreateVector<int32_t>(inputs),
355 builder_.CreateVector<int32_t>(outputs), builtin_options_type,
356 builtin_options,
357 /*custom_options=*/0, CustomOptionsFormat_FLEXBUFFERS));
358 }
359
AddCustomOp(const string & name,const std::vector<uint8_t> & custom_option,const std::function<TfLiteRegistration * ()> & registration,const std::vector<int32_t> & inputs,const std::vector<int32_t> & outputs)360 void MultiOpModel::AddCustomOp(
361 const string& name, const std::vector<uint8_t>& custom_option,
362 const std::function<TfLiteRegistration*()>& registration,
363 const std::vector<int32_t>& inputs, const std::vector<int32_t>& outputs) {
364 custom_registrations_[name] = registration;
365 opcodes_.push_back(
366 CreateOperatorCodeDirect(builder_, BuiltinOperator_CUSTOM, name.data()));
367 const int opcode_index = opcodes_.size() - 1;
368 operators_.push_back(CreateOperator(
369 builder_, opcode_index, builder_.CreateVector<int32_t>(inputs),
370 builder_.CreateVector<int32_t>(outputs), BuiltinOptions_NONE, 0,
371 builder_.CreateVector<uint8_t>(custom_option),
372 CustomOptionsFormat_FLEXBUFFERS));
373 }
374
375 } // namespace tflite
376