2 * Copyright (c) 2022 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.
18 #include "PythonHooks.h"
19 #include "RandomUtils.h"
21 #include <luci/Importer.h>
22 #include <foder/FileLoader.h>
23 #include <dio_hdf5/HDF5Importer.h>
25 #include <pybind11/embed.h>
30 using Shape = std::vector<loco::Dimension>;
31 using DataType = loco::DataType;
33 namespace py = pybind11;
38 uint32_t numElements(const luci::CircleNode *node)
40 assert(node != nullptr); // FIX_CALLER_UNLESS
42 uint32_t num_elements = 1;
43 for (uint32_t i = 0; i < node->rank(); i++)
44 num_elements *= node->dim(i).value();
49 // Return tensor's size in bytes
50 template <typename NodeT> size_t getByteSize(const NodeT *node)
52 assert(node != nullptr); // FIX_CALLER_UNLESS
54 uint32_t dtype_size = loco::size(node->dtype());
55 return static_cast<size_t>(dtype_size) * static_cast<size_t>(numElements(node));
58 // Throw exception if input has one of the following conditions.
59 // 1. Have unknown dimension
60 // 2. Number of elements is 0
61 void checkInputDimension(const luci::CircleInput *input)
63 assert(input != nullptr); // FIX_CALLER_UNLESS
65 for (uint32_t i = 0; i < input->rank(); i++)
66 if (!input->dim(i).known())
67 throw std::runtime_error(input->name() + " has unknown dimension");
69 if (numElements(input) == 0)
70 throw std::runtime_error(input->name() + " is a zero-sized input");
73 // Check the type and the shape of CircleInput
74 // Throw an exception if type or shape does not match
75 void verifyTypeShape(const luci::CircleInput *input_node, const DataType &dtype, const Shape &shape)
77 assert(input_node != nullptr); // FIX_CALLER_UNLESS
80 if (dtype != input_node->dtype())
81 throw std::runtime_error("Wrong input type.");
83 if (shape.size() != input_node->rank())
84 throw std::runtime_error("Input rank mismatch.");
86 for (uint32_t i = 0; i < shape.size(); i++)
88 if (not(shape.at(i) == input_node->dim(i)))
89 throw std::runtime_error("Input shape mismatch.");
98 void Dalgona::initialize(const std::string &input_model_path)
100 // Load model from the file
101 foder::FileLoader loader{input_model_path};
102 std::vector<char> model_data = loader.load();
104 // Verify flatbuffers
105 flatbuffers::Verifier verifier{reinterpret_cast<const uint8_t *>(model_data.data()),
107 if (not circle::VerifyModelBuffer(verifier))
108 throw std::runtime_error("Failed to verify circle '" + input_model_path + "'");
110 auto circle_model = circle::GetModel(model_data.data());
112 if (not circle_model)
113 throw std::runtime_error("Failed to load '" + input_model_path + "'");
115 _module = luci::Importer().importModule(circle_model);
118 throw std::runtime_error("ERROR: Failed to load '" + input_model_path + "'");
120 // Initialize interpreter
121 _interpreter = std::make_unique<luci_interpreter::Interpreter>(_module.get());
123 _hooks = std::make_unique<PythonHooks>(_interpreter.get());
125 _interpreter->attachObserver(_hooks.get());
128 void Dalgona::runAnalysisWithH5Input(const std::string &input_data_path,
129 const std::string &analysis_path,
130 const std::string &analysis_args)
132 py::object scope = py::module::import("__main__").attr("__dict__");
133 _hooks->importAnalysis(analysis_path, scope, analysis_args);
137 dio::hdf5::HDF5Importer importer(input_data_path);
138 importer.importGroup("value");
140 bool is_raw_data = importer.isRawData();
142 const auto num_records = importer.numData();
143 if (num_records == 0)
144 throw std::runtime_error("The input data file does not contain any record.");
146 const auto input_nodes = loco::input_nodes(_module->graph());
147 const auto num_inputs = input_nodes.size();
149 for (int32_t record_idx = 0; record_idx < num_records; record_idx++)
151 if (num_inputs != static_cast<uint32_t>(importer.numInputs(record_idx)))
152 throw std::runtime_error("Wrong number of inputs.");
154 std::cout << "Running " << record_idx << "'th data" << std::endl;
156 for (uint32_t input_idx = 0; input_idx < num_inputs; input_idx++)
158 const auto *input_node = loco::must_cast<const luci::CircleInput *>(input_nodes[input_idx]);
159 assert(input_node->index() == input_idx);
160 checkInputDimension(input_node);
161 std::vector<char> input_data(getByteSize(input_node));
165 // Skip type/shape check for raw data
166 importer.readTensor(record_idx, input_idx, input_data.data(), input_data.size());
172 importer.readTensor(record_idx, input_idx, &dtype, &shape, input_data.data(),
175 // Check the type and the shape of the input data is valid
176 verifyTypeShape(input_node, dtype, shape);
179 _interpreter->writeInputTensor(input_node, input_data.data(), input_data.size());
182 _hooks->startNetworkExecution(_module->graph());
183 _interpreter->interpret();
184 _hooks->endNetworkExecution(_module->graph());
187 std::cout << "Finished executing " << num_records << "'th data" << std::endl;
188 _hooks->endAnalysis();
190 catch (const H5::Exception &e)
192 H5::Exception::printErrorStack();
193 throw std::runtime_error("HDF5 error occurred.");
197 void Dalgona::runAnalysisWithRandomInput(const std::string &analysis_path,
198 const std::string &analysis_args)
200 py::object scope = py::module::import("__main__").attr("__dict__");
201 _hooks->importAnalysis(analysis_path, scope, analysis_args);
203 const auto input_nodes = loco::input_nodes(_module->graph());
204 const auto num_inputs = input_nodes.size();
206 for (uint32_t input_idx = 0; input_idx < num_inputs; input_idx++)
208 const auto *input_node = loco::must_cast<const luci::CircleInput *>(input_nodes[input_idx]);
209 assert(input_node->index() == input_idx);
210 checkInputDimension(input_node);
212 uint32_t num_elems = numElements(input_node);
213 switch (input_node->dtype())
215 case DataType::FLOAT32:
217 // Synced with record-minmax (-5,5)
218 auto input_data = genRandomFloatData(num_elems, -5, 5);
219 _interpreter->writeInputTensor(input_node, input_data.data(),
220 input_data.size() * sizeof(float));
225 auto input_data = genRandomIntData<uint8_t>(num_elems, std::numeric_limits<uint8_t>::min(),
226 std::numeric_limits<uint8_t>::max());
227 _interpreter->writeInputTensor(input_node, input_data.data(),
228 input_data.size() * sizeof(uint8_t));
233 auto input_data = genRandomIntData<int16_t>(num_elems, std::numeric_limits<int16_t>::min(),
234 std::numeric_limits<int16_t>::max());
235 _interpreter->writeInputTensor(input_node, input_data.data(),
236 input_data.size() * sizeof(int16_t));
241 // Synced with record-minmax (0, 100)
242 auto input_data = genRandomIntData<int32_t>(num_elems, 0, 100);
243 _interpreter->writeInputTensor(input_node, input_data.data(),
244 input_data.size() * sizeof(int32_t));
249 // Synced with record-minmax (0, 100)
250 auto input_data = genRandomIntData<int64_t>(num_elems, 0, 100);
251 _interpreter->writeInputTensor(input_node, input_data.data(),
252 input_data.size() * sizeof(int64_t));
257 // Bool is represented as uint8 (0 or 1)
258 auto input_data = genRandomIntData<uint8_t>(num_elems, 0, 1);
259 _interpreter->writeInputTensor(input_node, input_data.data(),
260 input_data.size() * sizeof(uint8_t));
264 throw std::runtime_error("Unsupported input data type in " + input_node->name());
268 _hooks->startNetworkExecution(_module->graph());
269 _interpreter->interpret();
270 _hooks->endNetworkExecution(_module->graph());
272 std::cout << "Finished executing a random input" << std::endl;
273 _hooks->endAnalysis();
276 } // namespace dalgona