Imported Upstream version 1.9.0
[platform/core/ml/nnfw.git] / compiler / record-minmax / src / RecordMinMax.cpp
1 /*
2  * Copyright (c) 2020 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 "RecordMinMax.h"
18 #include "RecordFunction.h"
19 #include "MinMaxObserver.h"
20 #include "HDF5Importer.h"
21
22 #include <luci/Importer.h>
23 #include <luci/CircleExporter.h>
24 #include <luci/CircleFileExpContract.h>
25 #include <luci/IR/CircleQuantParam.h>
26
27 #include <algorithm>
28 #include <cmath>
29 #include <fstream>
30 #include <numeric>
31 #include <stdexcept>
32 #include <iostream>
33
34 using Shape = luci_interpreter::Shape;
35 using DataType = luci_interpreter::DataType;
36
37 namespace
38 {
39
40 /**
41  * @brief  getTensorSize will return size in bytes
42  */
43 template <typename NodeT> size_t getTensorSize(const NodeT *node)
44 {
45   uint32_t tensor_size = loco::size(node->dtype());
46   for (uint32_t i = 0; i < node->rank(); ++i)
47     tensor_size *= node->dim(i).value();
48   return tensor_size;
49 }
50
51 /**
52  * @brief  verifyTypeShape checks the type and the shape of CircleInput
53  *         This throws an exception if type or shape does not match
54  */
55 void verifyTypeShape(const luci::CircleInput *input_node, const DataType &dtype, const Shape &shape)
56 {
57   // Type check
58   if (dtype != input_node->dtype())
59     throw std::runtime_error("Wrong input type.");
60
61   if (shape.num_dims() != input_node->rank())
62     throw std::runtime_error("Input rank mismatch.");
63
64   for (uint32_t i = 0; i < shape.num_dims(); i++)
65   {
66     if (shape.dim(i) != input_node->dim(i).value())
67       throw std::runtime_error("Input shape mismatch.");
68   }
69 }
70
71 } // namespace
72
73 namespace record_minmax
74 {
75
76 void RecordMinMax::initialize(const std::string &input_model_path)
77 {
78   // Load model from the file
79   std::ifstream fs(input_model_path, std::ifstream::binary);
80   if (fs.fail())
81   {
82     throw std::runtime_error("Cannot open model file \"" + input_model_path + "\".\n");
83   }
84   std::vector<char> model_data((std::istreambuf_iterator<char>(fs)),
85                                std::istreambuf_iterator<char>());
86
87   // Verify flatbuffers
88   flatbuffers::Verifier verifier{reinterpret_cast<const uint8_t *>(model_data.data()),
89                                  model_data.size()};
90   if (!circle::VerifyModelBuffer(verifier))
91   {
92     throw std::runtime_error("ERROR: Failed to verify circle '" + input_model_path + "'");
93   }
94
95   _module = luci::Importer().importModule(circle::GetModel(model_data.data()));
96
97   if (_module == nullptr)
98   {
99     throw std::runtime_error("ERROR: Failed to load '" + input_model_path + "'");
100   }
101
102   // Initialize interpreter
103   _interpreter = std::make_unique<luci_interpreter::Interpreter>(_module.get());
104
105   _observer = std::make_unique<MinMaxObserver>();
106
107   _interpreter->attachObserver(_observer.get());
108 }
109
110 void RecordMinMax::profileData(const std::string &mode, const std::string &input_data_path,
111                                float min_percentile, float max_percentile)
112 {
113   HDF5Importer importer(input_data_path);
114   importer.importGroup();
115
116   bool is_raw_data = importer.isRawData();
117
118   const auto num_records = importer.numRecords();
119   if (num_records == 0)
120     throw std::runtime_error("The input data file does not contain any record.");
121
122   const auto input_nodes = loco::input_nodes(_module->graph());
123   const auto num_inputs = input_nodes.size();
124
125   for (int32_t record_idx = 0; record_idx < num_records; record_idx++)
126   {
127     if (num_inputs != importer.numInputs(record_idx))
128       throw std::runtime_error("Wrong number of inputs.");
129
130     if (record_idx % 100 == 0)
131       std::cout << "Recording " << record_idx << "'th data" << std::endl;
132
133     for (int32_t input_idx = 0; input_idx < num_inputs; input_idx++)
134     {
135       const auto *input_node = loco::must_cast<const luci::CircleInput *>(input_nodes[input_idx]);
136       assert(input_node->index() == input_idx);
137       std::vector<char> input_data(getTensorSize(input_node));
138
139       if (!is_raw_data)
140       {
141         DataType dtype;
142         Shape shape(input_node->rank());
143         importer.readTensor(record_idx, input_idx, &dtype, &shape, input_data.data());
144
145         // Check the type and the shape of the input data is valid
146         verifyTypeShape(input_node, dtype, shape);
147       }
148       else
149       {
150         // Skip type/shape check for raw data
151         importer.readTensor(record_idx, input_idx, input_data.data());
152       }
153
154       // TODO: Input data is copied twice (file -> buffer (input_data) -> interpreter inputs)
155       //       We can redcue the copy by directly writing data from file to interpreter inputs
156       _interpreter->writeInputTensor(input_node, input_data.data(), input_data.size());
157     }
158
159     _interpreter->interpret();
160   }
161
162   std::cout << "Recording finished. Number of recorded data: " << num_records << std::endl;
163
164   auto minmax_map = _observer->minMaxData()->getMap();
165   for (auto iter = minmax_map->begin(); iter != minmax_map->end(); ++iter)
166   {
167     auto node = iter->first;
168     auto minmax = iter->second;
169
170     float min{0.0f}, max{0.0f};
171     if (mode == "percentile")
172     {
173       min = getNthPercentile(minmax.min_vector, min_percentile);
174       max = getNthPercentile(minmax.max_vector, max_percentile);
175     }
176     else if (mode == "moving_average")
177     {
178       min = getMovingAverage(minmax.min_vector, 0.9, 16, true);
179       max = getMovingAverage(minmax.max_vector, 0.9, 16, false);
180     }
181     assert(mode == "percentile" || mode == "moving_average");
182     auto quantparam = std::make_unique<luci::CircleQuantParam>();
183     quantparam->min.push_back(min);
184     quantparam->max.push_back(max);
185
186     assert(node->quantparam() == nullptr);
187
188     auto mutable_node = const_cast<luci::CircleNode *>(node);
189     mutable_node->quantparam(std::move(quantparam));
190   }
191 }
192
193 void RecordMinMax::saveModel(const std::string &output_model_path)
194 {
195   // Export to output Circle file
196   luci::CircleExporter exporter;
197
198   luci::CircleFileExpContract contract(_module.get(), output_model_path);
199
200   if (!exporter.invoke(&contract))
201   {
202     throw std::runtime_error("ERROR: Failed to export '" + output_model_path + "'");
203   }
204 }
205
206 } // namespace record_minmax