Imported Upstream version 1.7.0
[platform/core/ml/nnfw.git] / tests / tools / tflite_run / src / tflite_run.cc
1 /*
2  * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *    http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "tflite/ext/kernels/register.h"
18 #include "tensorflow/lite/model.h"
19
20 #include "args.h"
21 #include "tensor_dumper.h"
22 #include "tensor_loader.h"
23 #include "misc/benchmark.h"
24 #include "misc/EnvVar.h"
25 #include "misc/fp32.h"
26 #include "tflite/Diff.h"
27 #include "tflite/Assert.h"
28 #include "tflite/Session.h"
29 #include "tflite/InterpreterSession.h"
30 #include "tflite/NNAPISession.h"
31 #include "misc/tensor/IndexIterator.h"
32 #include "misc/tensor/Object.h"
33 #include "benchmark.h"
34
35 #include <iostream>
36 #include <chrono>
37 #include <algorithm>
38 #include <vector>
39
40 #include <libgen.h>
41
42 using namespace tflite;
43 using namespace nnfw::tflite;
44 using namespace std::placeholders; // for _1, _2 ...
45
46 namespace
47 {
48
49 void print_max_idx(float *f, int size)
50 {
51   float *p = std::max_element(f, f + size);
52   std::cout << "max:" << p - f;
53 }
54
55 static const char *default_backend_cand = "tflite_cpu";
56
57 // Verifies whether the model is a flatbuffer file.
58 class BMFlatBufferVerifier : public tflite::TfLiteVerifier
59 {
60 public:
61   bool Verify(const char *data, int length, tflite::ErrorReporter *reporter) override
62   {
63
64     flatbuffers::Verifier verifier(reinterpret_cast<const uint8_t *>(data), length);
65     if (!tflite::VerifyModelBuffer(verifier))
66     {
67       reporter->Report("The model is not a valid Flatbuffer file");
68       return false;
69     }
70     return true;
71   }
72 };
73
74 } // namespace anonymous
75
76 int main(const int argc, char **argv)
77 {
78   const bool use_nnapi = nnfw::misc::EnvVar("USE_NNAPI").asBool(false);
79
80   StderrReporter error_reporter;
81
82   TFLiteRun::Args args(argc, argv);
83
84   std::chrono::milliseconds t_model_load(0), t_prepare(0);
85
86   // TODO Apply verbose level to phases
87   const int verbose = args.getVerboseLevel();
88   benchmark::Phases phases(
89       benchmark::PhaseOption{args.getMemoryPoll(), args.getGpuMemoryPoll(), args.getRunDelay()});
90
91   std::unique_ptr<FlatBufferModel> model;
92   std::unique_ptr<Interpreter> interpreter;
93   std::unique_ptr<tflite::TfLiteVerifier> verifier{new BMFlatBufferVerifier};
94
95   try
96   {
97     phases.run("MODEL_LOAD", [&](const benchmark::Phase &, uint32_t) {
98       if (args.getModelValidate())
99       {
100         model = FlatBufferModel::VerifyAndBuildFromFile(args.getTFLiteFilename().c_str(),
101                                                         verifier.get(), &error_reporter);
102       }
103       else
104       {
105         model = FlatBufferModel::BuildFromFile(args.getTFLiteFilename().c_str(), &error_reporter);
106       }
107       if (model == nullptr)
108       {
109         throw std::runtime_error{"Cannot create model"};
110       }
111
112       BuiltinOpResolver resolver;
113       InterpreterBuilder builder(*model, resolver);
114       TFLITE_ENSURE(builder(&interpreter))
115       interpreter->SetNumThreads(nnfw::misc::EnvVar("THREAD").asInt(-1));
116     });
117   }
118   catch (const std::exception &e)
119   {
120     std::cerr << e.what() << '\n';
121     return 1;
122   }
123
124   std::shared_ptr<nnfw::tflite::Session> sess;
125
126   if (use_nnapi)
127   {
128     sess = std::make_shared<nnfw::tflite::NNAPISession>(interpreter.get());
129   }
130   else
131   {
132     sess = std::make_shared<nnfw::tflite::InterpreterSession>(interpreter.get());
133   }
134
135   try
136   {
137     phases.run("PREPARE", [&](const benchmark::Phase &, uint32_t) { sess->prepare(); });
138   }
139   catch (const std::exception &e)
140   {
141     std::cerr << e.what() << '\n';
142     return 1;
143   }
144
145   if (args.getInputShapes().size() != 0)
146   {
147     const int dim_values = args.getInputShapes().size();
148     int offset = 0;
149
150     for (const auto &id : interpreter->inputs())
151     {
152       TfLiteTensor *tensor = interpreter->tensor(id);
153       std::vector<int32_t> new_dim;
154       new_dim.resize(tensor->dims->size);
155
156       for (uint32_t axis = 0; axis < tensor->dims->size; axis++, offset++)
157       {
158         new_dim[axis] =
159             ((offset < dim_values) ? args.getInputShapes()[offset] : tensor->dims->data[axis]);
160       }
161
162       interpreter->ResizeInputTensor(id, new_dim);
163
164       if (offset >= dim_values)
165         break;
166     }
167     interpreter->AllocateTensors();
168   }
169
170   TFLiteRun::TensorLoader tensor_loader(*interpreter);
171
172   // Load input from raw or dumped tensor file.
173   // Two options are exclusive and will be checked from Args.
174   if (!args.getInputFilename().empty() || !args.getCompareFilename().empty())
175   {
176     if (!args.getInputFilename().empty())
177     {
178       tensor_loader.loadRawTensors(args.getInputFilename(), interpreter->inputs());
179     }
180     else
181     {
182       tensor_loader.loadDumpedTensors(args.getCompareFilename());
183     }
184
185     for (const auto &o : interpreter->inputs())
186     {
187       const auto &tensor_view = tensor_loader.get(o);
188       TfLiteTensor *tensor = interpreter->tensor(o);
189
190       memcpy(reinterpret_cast<void *>(tensor->data.f),
191              reinterpret_cast<const void *>(tensor_view._base), tensor->bytes);
192     }
193   }
194   else
195   {
196     const int seed = 1; /* TODO Add an option for seed value */
197     nnfw::misc::RandomGenerator randgen{seed, 0.0f, 2.0f};
198
199     // No input specified. So we fill the input tensors with random values.
200     for (const auto &o : interpreter->inputs())
201     {
202       TfLiteTensor *tensor = interpreter->tensor(o);
203       if (tensor->type == kTfLiteInt32)
204       {
205         // Generate singed 32-bit integer (s32) input
206         auto tensor_view = nnfw::tflite::TensorView<int32_t>::make(*interpreter, o);
207
208         int32_t value = 0;
209
210         nnfw::misc::tensor::iterate(tensor_view.shape())
211             << [&](const nnfw::misc::tensor::Index &ind) {
212                  // TODO Generate random values
213                  // Gather operation: index should be within input coverage.
214                  tensor_view.at(ind) = value;
215                  value++;
216                };
217       }
218       else if (tensor->type == kTfLiteUInt8)
219       {
220         // Generate unsigned 8-bit integer input
221         auto tensor_view = nnfw::tflite::TensorView<uint8_t>::make(*interpreter, o);
222
223         uint8_t value = 0;
224
225         nnfw::misc::tensor::iterate(tensor_view.shape())
226             << [&](const nnfw::misc::tensor::Index &ind) {
227                  // TODO Generate random values
228                  tensor_view.at(ind) = value;
229                  value = (value + 1) & 0xFF;
230                };
231       }
232       else if (tensor->type == kTfLiteBool)
233       {
234         // Generate bool input
235         auto tensor_view = nnfw::tflite::TensorView<bool>::make(*interpreter, o);
236
237         auto fp = static_cast<bool (nnfw::misc::RandomGenerator::*)(
238             const ::nnfw::misc::tensor::Shape &, const ::nnfw::misc::tensor::Index &)>(
239             &nnfw::misc::RandomGenerator::generate<bool>);
240         const nnfw::misc::tensor::Object<bool> data(tensor_view.shape(),
241                                                     std::bind(fp, randgen, _1, _2));
242
243         nnfw::misc::tensor::iterate(tensor_view.shape())
244             << [&](const nnfw::misc::tensor::Index &ind) {
245                  const auto value = data.at(ind);
246                  tensor_view.at(ind) = value;
247                };
248       }
249       else
250       {
251         assert(tensor->type == kTfLiteFloat32);
252
253         const float *end = reinterpret_cast<const float *>(tensor->data.raw_const + tensor->bytes);
254         for (float *ptr = tensor->data.f; ptr < end; ptr++)
255         {
256           *ptr = randgen.generate<float>();
257         }
258       }
259     }
260   }
261
262   TFLiteRun::TensorDumper tensor_dumper;
263   // Must be called before `interpreter->Invoke()`
264   tensor_dumper.addTensors(*interpreter, interpreter->inputs());
265
266   std::cout << "input tensor indices = [";
267   for (const auto &o : interpreter->inputs())
268   {
269     std::cout << o << ",";
270   }
271   std::cout << "]" << std::endl;
272
273   // NOTE: Measuring memory can't avoid taking overhead. Therefore, memory will be measured on the
274   // only warmup.
275   if (verbose == 0)
276   {
277     phases.run("WARMUP", [&](const benchmark::Phase &, uint32_t) { sess->run(); },
278                args.getWarmupRuns());
279     phases.run("EXECUTE", [&](const benchmark::Phase &, uint32_t) { sess->run(); },
280                args.getNumRuns(), true);
281   }
282   else
283   {
284     phases.run("WARMUP", [&](const benchmark::Phase &, uint32_t) { sess->run(); },
285                [&](const benchmark::Phase &phase, uint32_t nth) {
286                  std::cout << "... "
287                            << "warmup " << nth + 1 << " takes " << phase.time[nth] / 1e3 << " ms"
288                            << std::endl;
289                },
290                args.getWarmupRuns());
291     phases.run("EXECUTE", [&](const benchmark::Phase &, uint32_t) { sess->run(); },
292                [&](const benchmark::Phase &phase, uint32_t nth) {
293                  std::cout << "... "
294                            << "run " << nth + 1 << " takes " << phase.time[nth] / 1e3 << " ms"
295                            << std::endl;
296                },
297                args.getNumRuns(), true);
298   }
299
300   sess->teardown();
301
302   // Must be called after `interpreter->Invoke()`
303   tensor_dumper.addTensors(*interpreter, interpreter->outputs());
304
305   std::cout << "output tensor indices = [";
306   for (const auto &o : interpreter->outputs())
307   {
308     std::cout << o << "(";
309
310     print_max_idx(interpreter->tensor(o)->data.f, interpreter->tensor(o)->bytes / sizeof(float));
311
312     std::cout << "),";
313   }
314   std::cout << "]" << std::endl;
315
316   // TODO Apply verbose level to result
317
318   // prepare result
319   benchmark::Result result(phases);
320
321   // to stdout
322   benchmark::printResult(result);
323
324   if (args.getWriteReport())
325   {
326     // prepare csv task
327     std::string exec_basename;
328     std::string model_basename;
329     std::string backend_name = default_backend_cand;
330     {
331       std::vector<char> vpath(args.getTFLiteFilename().begin(), args.getTFLiteFilename().end() + 1);
332       model_basename = basename(vpath.data());
333       size_t lastindex = model_basename.find_last_of(".");
334       model_basename = model_basename.substr(0, lastindex);
335       exec_basename = basename(argv[0]);
336     }
337     benchmark::writeResult(result, exec_basename, model_basename, backend_name);
338   }
339
340   if (!args.getDumpFilename().empty())
341   {
342     const std::string &dump_filename = args.getDumpFilename();
343     tensor_dumper.dump(dump_filename);
344     std::cout << "Input/output tensors have been dumped to file \"" << dump_filename << "\"."
345               << std::endl;
346   }
347
348   if (!args.getCompareFilename().empty())
349   {
350     const std::string &compare_filename = args.getCompareFilename();
351     std::cout << "========================================" << std::endl;
352     std::cout << "Comparing the results with \"" << compare_filename << "\"." << std::endl;
353     std::cout << "========================================" << std::endl;
354
355     // TODO Code duplication (copied from RandomTestRunner)
356
357     int tolerance = nnfw::misc::EnvVar("TOLERANCE").asInt(1);
358
359     auto equals = [tolerance](float lhs, float rhs) {
360       // NOTE Hybrid approach
361       // TODO Allow users to set tolerance for absolute_epsilon_equal
362       if (nnfw::misc::fp32::absolute_epsilon_equal(lhs, rhs))
363       {
364         return true;
365       }
366
367       return nnfw::misc::fp32::epsilon_equal(lhs, rhs, tolerance);
368     };
369
370     nnfw::misc::tensor::Comparator comparator(equals);
371     TfLiteInterpMatchApp app(comparator);
372     bool res = true;
373
374     for (const auto &o : interpreter->outputs())
375     {
376       auto expected = tensor_loader.get(o);
377       auto obtained = nnfw::tflite::TensorView<float>::make(*interpreter, o);
378
379       res = res && app.compareSingleTensorView(expected, obtained, o);
380     }
381
382     if (!res)
383     {
384       return 255;
385     }
386   }
387
388   return 0;
389 }