• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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