17c6aa6ff8a021a07a587443245eff2ae86d115a
[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 "CircleExpContract.h"
20 #include "MinMaxObserver.h"
21 #include "HDF5Importer.h"
22
23 #include <luci/Importer.h>
24 #include <luci/CircleExporter.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   _module = luci::Importer().importModule(circle::GetModel(model_data.data()));
87
88   if (_module == nullptr)
89   {
90     throw std::runtime_error("ERROR: Failed to load '" + input_model_path + "'");
91   }
92
93   // Initialize interpreter
94   _interpreter = std::make_unique<luci_interpreter::Interpreter>(_module.get());
95
96   _observer = std::make_unique<MinMaxObserver>();
97
98   _interpreter->attachObserver(_observer.get());
99 }
100
101 void RecordMinMax::profileData(const std::string &mode, const std::string &input_data_path,
102                                float min_percentile, float max_percentile)
103 {
104   HDF5Importer importer(input_data_path);
105   importer.importGroup();
106
107   bool is_raw_data = importer.isRawData();
108
109   const auto num_records = importer.numRecords();
110   if (num_records == 0)
111     throw std::runtime_error("The input data file does not contain any record.");
112
113   const auto input_nodes = loco::input_nodes(_module->graph());
114   const auto num_inputs = input_nodes.size();
115
116   for (int32_t record_idx = 0; record_idx < num_records; record_idx++)
117   {
118     if (num_inputs != importer.numInputs(record_idx))
119       throw std::runtime_error("Wrong number of inputs.");
120
121     if (record_idx % 100 == 0)
122       std::cout << "Recording " << record_idx << "'th data" << std::endl;
123
124     for (int32_t input_idx = 0; input_idx < num_inputs; input_idx++)
125     {
126       const auto *input_node = loco::must_cast<const luci::CircleInput *>(input_nodes[input_idx]);
127       assert(input_node->index() == input_idx);
128       std::vector<char> input_data(getTensorSize(input_node));
129
130       if (!is_raw_data)
131       {
132         DataType dtype;
133         Shape shape(input_node->rank());
134         importer.readTensor(record_idx, input_idx, &dtype, &shape, input_data.data());
135
136         // Check the type and the shape of the input data is valid
137         verifyTypeShape(input_node, dtype, shape);
138       }
139       else
140       {
141         // Skip type/shape check for raw data
142         importer.readTensor(record_idx, input_idx, input_data.data());
143       }
144
145       // TODO: Input data is copied twice (file -> buffer (input_data) -> interpreter inputs)
146       //       We can redcue the copy by directly writing data from file to interpreter inputs
147       _interpreter->writeInputTensor(input_node, input_data.data(), input_data.size());
148     }
149
150     _interpreter->interpret();
151   }
152
153   std::cout << "Recording finished. Number of recorded data: " << num_records << std::endl;
154
155   auto minmax_map = _observer->minMaxData()->getMap();
156   for (auto iter = minmax_map->begin(); iter != minmax_map->end(); ++iter)
157   {
158     auto node = iter->first;
159     auto minmax = iter->second;
160
161     float min{0.0f}, max{0.0f};
162     if (mode == "percentile")
163     {
164       min = getNthPercentile(minmax.min_vector, min_percentile);
165       max = getNthPercentile(minmax.max_vector, max_percentile);
166     }
167     else if (mode == "moving_average")
168     {
169       min = getMovingAverage(minmax.min_vector, 0.9, 16, true);
170       max = getMovingAverage(minmax.max_vector, 0.9, 16, false);
171     }
172     assert(mode == "percentile" || mode == "moving_average");
173     auto quantparam = std::make_unique<luci::CircleQuantParam>();
174     quantparam->min.push_back(min);
175     quantparam->max.push_back(max);
176
177     assert(node->quantparam() == nullptr);
178
179     auto mutable_node = const_cast<luci::CircleNode *>(node);
180     mutable_node->quantparam(std::move(quantparam));
181   }
182 }
183
184 void RecordMinMax::saveModel(const std::string &output_model_path)
185 {
186   // Export to output Circle file
187   luci::CircleExporter exporter;
188   CircleExpContract contract(_module.get(), output_model_path);
189
190   if (!exporter.invoke(&contract))
191   {
192     throw std::runtime_error("ERROR: Failed to export '" + output_model_path + "'");
193   }
194 }
195
196 } // namespace record_minmax