Imported Upstream version 1.25.0
[platform/core/ml/nnfw.git] / compiler / dalgona / src / Dalgona.cpp
1 /*
2  * Copyright (c) 2022 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 "Dalgona.h"
18 #include "PythonHooks.h"
19 #include "RandomUtils.h"
20
21 #include <luci/Importer.h>
22 #include <foder/FileLoader.h>
23 #include <dio_hdf5/HDF5Importer.h>
24
25 #include <pybind11/embed.h>
26
27 #include <iostream>
28 #include <limits>
29
30 using Shape = std::vector<loco::Dimension>;
31 using DataType = loco::DataType;
32
33 namespace py = pybind11;
34
35 namespace
36 {
37
38 uint32_t numElements(const luci::CircleNode *node)
39 {
40   assert(node != nullptr); // FIX_CALLER_UNLESS
41
42   uint32_t num_elements = 1;
43   for (uint32_t i = 0; i < node->rank(); i++)
44     num_elements *= node->dim(i).value();
45
46   return num_elements;
47 }
48
49 // Return tensor's size in bytes
50 template <typename NodeT> size_t getByteSize(const NodeT *node)
51 {
52   assert(node != nullptr); // FIX_CALLER_UNLESS
53
54   uint32_t dtype_size = loco::size(node->dtype());
55   return static_cast<size_t>(dtype_size) * static_cast<size_t>(numElements(node));
56 }
57
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)
62 {
63   assert(input != nullptr); // FIX_CALLER_UNLESS
64
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");
68
69   if (numElements(input) == 0)
70     throw std::runtime_error(input->name() + " is a zero-sized input");
71 }
72
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)
76 {
77   assert(input_node != nullptr); // FIX_CALLER_UNLESS
78
79   // Type check
80   if (dtype != input_node->dtype())
81     throw std::runtime_error("Wrong input type.");
82
83   if (shape.size() != input_node->rank())
84     throw std::runtime_error("Input rank mismatch.");
85
86   for (uint32_t i = 0; i < shape.size(); i++)
87   {
88     if (not(shape.at(i) == input_node->dim(i)))
89       throw std::runtime_error("Input shape mismatch.");
90   }
91 }
92
93 } // namespace
94
95 namespace dalgona
96 {
97
98 void Dalgona::initialize(const std::string &input_model_path)
99 {
100   // Load model from the file
101   foder::FileLoader loader{input_model_path};
102   std::vector<char> model_data = loader.load();
103
104   // Verify flatbuffers
105   flatbuffers::Verifier verifier{reinterpret_cast<const uint8_t *>(model_data.data()),
106                                  model_data.size()};
107   if (not circle::VerifyModelBuffer(verifier))
108     throw std::runtime_error("Failed to verify circle '" + input_model_path + "'");
109
110   auto circle_model = circle::GetModel(model_data.data());
111
112   if (not circle_model)
113     throw std::runtime_error("Failed to load '" + input_model_path + "'");
114
115   _module = luci::Importer().importModule(circle_model);
116
117   if (not _module)
118     throw std::runtime_error("ERROR: Failed to load '" + input_model_path + "'");
119
120   // Initialize interpreter
121   _interpreter = std::make_unique<luci_interpreter::Interpreter>(_module.get());
122
123   _hooks = std::make_unique<PythonHooks>(_interpreter.get());
124
125   _interpreter->attachObserver(_hooks.get());
126 }
127
128 void Dalgona::runAnalysisWithH5Input(const std::string &input_data_path,
129                                      const std::string &analysis_path,
130                                      const std::string &analysis_args)
131 {
132   py::object scope = py::module::import("__main__").attr("__dict__");
133   _hooks->importAnalysis(analysis_path, scope, analysis_args);
134
135   try
136   {
137     dio::hdf5::HDF5Importer importer(input_data_path);
138     importer.importGroup("value");
139
140     bool is_raw_data = importer.isRawData();
141
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.");
145
146     const auto input_nodes = loco::input_nodes(_module->graph());
147     const auto num_inputs = input_nodes.size();
148
149     for (int32_t record_idx = 0; record_idx < num_records; record_idx++)
150     {
151       if (num_inputs != static_cast<uint32_t>(importer.numInputs(record_idx)))
152         throw std::runtime_error("Wrong number of inputs.");
153
154       std::cout << "Running " << record_idx << "'th data" << std::endl;
155
156       for (uint32_t input_idx = 0; input_idx < num_inputs; input_idx++)
157       {
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));
162
163         if (is_raw_data)
164         {
165           // Skip type/shape check for raw data
166           importer.readTensor(record_idx, input_idx, input_data.data(), input_data.size());
167         }
168         else
169         {
170           DataType dtype;
171           Shape shape;
172           importer.readTensor(record_idx, input_idx, &dtype, &shape, input_data.data(),
173                               input_data.size());
174
175           // Check the type and the shape of the input data is valid
176           verifyTypeShape(input_node, dtype, shape);
177         }
178
179         _interpreter->writeInputTensor(input_node, input_data.data(), input_data.size());
180       }
181
182       _hooks->startNetworkExecution(_module->graph());
183       _interpreter->interpret();
184       _hooks->endNetworkExecution(_module->graph());
185     }
186
187     std::cout << "Finished executing " << num_records << "'th data" << std::endl;
188     _hooks->endAnalysis();
189   }
190   catch (const H5::Exception &e)
191   {
192     H5::Exception::printErrorStack();
193     throw std::runtime_error("HDF5 error occurred.");
194   }
195 }
196
197 void Dalgona::runAnalysisWithRandomInput(const std::string &analysis_path,
198                                          const std::string &analysis_args)
199 {
200   py::object scope = py::module::import("__main__").attr("__dict__");
201   _hooks->importAnalysis(analysis_path, scope, analysis_args);
202
203   const auto input_nodes = loco::input_nodes(_module->graph());
204   const auto num_inputs = input_nodes.size();
205
206   for (uint32_t input_idx = 0; input_idx < num_inputs; input_idx++)
207   {
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);
211
212     uint32_t num_elems = numElements(input_node);
213     switch (input_node->dtype())
214     {
215       case DataType::FLOAT32:
216       {
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));
221         break;
222       }
223       case DataType::U8:
224       {
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));
229         break;
230       }
231       case DataType::S16:
232       {
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));
237         break;
238       }
239       case DataType::S32:
240       {
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));
245         break;
246       }
247       case DataType::S64:
248       {
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));
253         break;
254       }
255       case DataType::BOOL:
256       {
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));
261         break;
262       }
263       default:
264         throw std::runtime_error("Unsupported input data type in " + input_node->name());
265     }
266   }
267
268   _hooks->startNetworkExecution(_module->graph());
269   _interpreter->interpret();
270   _hooks->endNetworkExecution(_module->graph());
271
272   std::cout << "Finished executing a random input" << std::endl;
273   _hooks->endAnalysis();
274 }
275
276 } // namespace dalgona