2 * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #include "RecordMinMax.h"
18 #include "RecordFunction.h"
19 #include "CircleExpContract.h"
20 #include "MinMaxObserver.h"
21 #include "HDF5Importer.h"
23 #include <luci/Importer.h>
24 #include <luci/CircleExporter.h>
25 #include <luci/IR/CircleQuantParam.h>
34 using Shape = luci_interpreter::Shape;
35 using DataType = luci_interpreter::DataType;
41 * @brief getTensorSize will return size in bytes
43 template <typename NodeT> size_t getTensorSize(const NodeT *node)
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();
52 * @brief verifyTypeShape checks the type and the shape of CircleInput
53 * This throws an exception if type or shape does not match
55 void verifyTypeShape(const luci::CircleInput *input_node, const DataType &dtype, const Shape &shape)
58 if (dtype != input_node->dtype())
59 throw std::runtime_error("Wrong input type.");
61 if (shape.num_dims() != input_node->rank())
62 throw std::runtime_error("Input rank mismatch.");
64 for (uint32_t i = 0; i < shape.num_dims(); i++)
66 if (shape.dim(i) != input_node->dim(i).value())
67 throw std::runtime_error("Input shape mismatch.");
73 namespace record_minmax
76 void RecordMinMax::initialize(const std::string &input_model_path)
78 // Load model from the file
79 std::ifstream fs(input_model_path, std::ifstream::binary);
82 throw std::runtime_error("Cannot open model file \"" + input_model_path + "\".\n");
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()));
88 if (_module == nullptr)
90 throw std::runtime_error("ERROR: Failed to load '" + input_model_path + "'");
93 // Initialize interpreter
94 _interpreter = std::make_unique<luci_interpreter::Interpreter>(_module.get());
96 _observer = std::make_unique<MinMaxObserver>();
98 _interpreter->attachObserver(_observer.get());
101 void RecordMinMax::profileData(const std::string &mode, const std::string &input_data_path,
102 float min_percentile, float max_percentile)
104 HDF5Importer importer(input_data_path);
105 importer.importGroup();
107 bool is_raw_data = importer.isRawData();
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.");
113 const auto input_nodes = loco::input_nodes(_module->graph());
114 const auto num_inputs = input_nodes.size();
116 for (int32_t record_idx = 0; record_idx < num_records; record_idx++)
118 if (num_inputs != importer.numInputs(record_idx))
119 throw std::runtime_error("Wrong number of inputs.");
121 if (record_idx % 100 == 0)
122 std::cout << "Recording " << record_idx << "'th data" << std::endl;
124 for (int32_t input_idx = 0; input_idx < num_inputs; input_idx++)
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));
133 Shape shape(input_node->rank());
134 importer.readTensor(record_idx, input_idx, &dtype, &shape, input_data.data());
136 // Check the type and the shape of the input data is valid
137 verifyTypeShape(input_node, dtype, shape);
141 // Skip type/shape check for raw data
142 importer.readTensor(record_idx, input_idx, input_data.data());
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());
150 _interpreter->interpret();
153 std::cout << "Recording finished. Number of recorded data: " << num_records << std::endl;
155 auto minmax_map = _observer->minMaxData()->getMap();
156 for (auto iter = minmax_map->begin(); iter != minmax_map->end(); ++iter)
158 auto node = iter->first;
159 auto minmax = iter->second;
161 float min{0.0f}, max{0.0f};
162 if (mode == "percentile")
164 min = getNthPercentile(minmax.min_vector, min_percentile);
165 max = getNthPercentile(minmax.max_vector, max_percentile);
167 else if (mode == "moving_average")
169 min = getMovingAverage(minmax.min_vector, 0.9, 16, true);
170 max = getMovingAverage(minmax.max_vector, 0.9, 16, false);
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);
177 assert(node->quantparam() == nullptr);
179 auto mutable_node = const_cast<luci::CircleNode *>(node);
180 mutable_node->quantparam(std::move(quantparam));
184 void RecordMinMax::saveModel(const std::string &output_model_path)
186 // Export to output Circle file
187 luci::CircleExporter exporter;
188 CircleExpContract contract(_module.get(), output_model_path);
190 if (!exporter.invoke(&contract))
192 throw std::runtime_error("ERROR: Failed to export '" + output_model_path + "'");
196 } // namespace record_minmax