Line data Source code
1 : // SPDX-License-Identifier: Apache-2.0
2 : /**
3 : * Copyright (C) 2021 Jihoon Lee <jhoon.it.lee@samsung.com>
4 : *
5 : * @file ini_interpreter.cpp
6 : * @date 02 April 2021
7 : * @brief NNTrainer Ini Interpreter (partly moved from model_loader.c)
8 : * @see https://github.com/nnstreamer/nntrainer
9 : * @author Jijoong Moon <jijoong.moon@samsung.com>
10 : * @author Parichay Kapoor <pk.kapoor@samsung.com>
11 : * @author Jihoon Lee <jhoon.it.lee@samsung.com>
12 : * @bug No known bugs except for NYI items
13 : */
14 : #include <ini_interpreter.h>
15 :
16 : #include <sstream>
17 : #include <vector>
18 :
19 : #include <ini_wrapper.h>
20 : #include <layer.h>
21 : #include <layer_node.h>
22 : #include <nntrainer_error.h>
23 : #include <nntrainer_log.h>
24 : #include <time_dist.h>
25 : #include <util_func.h>
26 :
27 : #if defined(ENABLE_NNSTREAMER_BACKBONE)
28 : #include <nnstreamer_layer.h>
29 : #endif
30 :
31 : #if defined(ENABLE_TFLITE_BACKBONE)
32 : #include <tflite_layer.h>
33 : #endif
34 :
35 : static constexpr const char *FUNC_TAG = "[IniInterpreter] ";
36 :
37 : static constexpr const char *UNKNOWN_STR = "UNKNOWN";
38 : static constexpr const char *NONE_STR = "NONE";
39 : static constexpr const char *MODEL_STR = "model";
40 : static constexpr const char *DATASET_STR = "dataset";
41 : static constexpr const char *TRAINSET_STR = "train_set";
42 : static constexpr const char *VALIDSET_STR = "valid_set";
43 : static constexpr const char *TESTSET_STR = "test_set";
44 : static constexpr const char *OPTIMIZER_STR = "optimizer";
45 : static constexpr const char *LRSCHED_STR = "LearningRateScheduler";
46 :
47 : namespace nntrainer {
48 :
49 822 : IniGraphInterpreter::IniGraphInterpreter(
50 : const Engine *ct_eg_,
51 822 : std::function<const std::string(const std::string &)> pathResolver_) :
52 822 : ct_engine(ct_eg_), pathResolver(pathResolver_) {}
53 :
54 1403 : IniGraphInterpreter::~IniGraphInterpreter() {}
55 :
56 : namespace {
57 :
58 : /** @todo:
59 : * 1. deprecate tag dispatching along with #1072
60 : * 2. deprecate getMergeableGraph (extendGraph should accept graph itself)
61 : *
62 : * @brief Plain Layer tag
63 : */
64 : class PlainLayer {};
65 :
66 : /**
67 : * @brief Backbone Layer tag
68 : */
69 : class BackboneLayer {};
70 :
71 : /**
72 : * @brief convert section to list of string based properties
73 : *
74 : * @param ini ini handler
75 : * @param sec_name section name
76 : * @param pathResolver path resolver of ini. If relative path is given to ini,
77 : * it is prioritized to be interpreted relative to ini file. So this resolver is
78 : * required @see ModelLoader::resolvePath for detail.
79 : * @return std::vector<std::string> list of properties
80 : */
81 2889 : std::vector<std::string> section2properties(
82 : dictionary *ini, const std::string &sec_name,
83 : std::function<const std::string(std::string)> &pathResolver) {
84 2889 : int num_entries = iniparser_getsecnkeys(ini, sec_name.c_str());
85 2889 : NNTR_THROW_IF(num_entries < 1, std::invalid_argument)
86 : << "there are no entries in the layer section: " << sec_name;
87 2889 : ml_logd("number of entries for %s: %d", sec_name.c_str(), num_entries);
88 :
89 2889 : std::unique_ptr<const char *[]> key_refs(new const char *[num_entries]);
90 2889 : NNTR_THROW_IF(iniparser_getseckeys(ini, sec_name.c_str(), key_refs.get()) ==
91 : nullptr,
92 : std::invalid_argument)
93 : << "failed to fetch keys for the section: " << sec_name;
94 :
95 : std::vector<std::string> properties;
96 2889 : properties.reserve(num_entries - 1);
97 :
98 26327 : for (int i = 0; i < num_entries; ++i) {
99 23438 : std::string key(key_refs[i]);
100 23438 : std::string prop_key = key.substr(key.find(":") + 1);
101 :
102 43989 : if (istrequal(prop_key, "type") || istrequal(prop_key, "backbone")) {
103 : continue;
104 : }
105 :
106 20549 : std::string value = iniparser_getstring(ini, key_refs[i], UNKNOWN_STR);
107 20549 : NNTR_THROW_IF(value == UNKNOWN_STR || value.empty(), std::invalid_argument)
108 : << "parsing property failed key: " << key << " value: " << value;
109 :
110 : /// @todo systematically manage those props
111 41098 : if (istrequal(prop_key, "model_path")) {
112 0 : value = pathResolver(value);
113 : }
114 :
115 20549 : ml_logd("parsed properties: %s=%s", prop_key.c_str(), value.c_str());
116 41098 : properties.push_back(prop_key + "=" + value);
117 : }
118 :
119 5778 : properties.push_back("name=" + sec_name);
120 :
121 2889 : return properties;
122 0 : }
123 :
124 : /**
125 : * @brief convert section to a layer object (later it might be layer node)
126 : *
127 : * @tparam T tag
128 : * @param ini dictionary * ini
129 : * @param sec_name section name
130 : * @param ac app context to search for the layer
131 : * @param backbone_file full path of backbone file (defaulted to be omitted)
132 : * @param pathResolver if path is given and need resolving, called here
133 : * @return std::shared_ptr<Layer> return layer object
134 : */
135 : template <typename T>
136 : std::shared_ptr<LayerNode>
137 : section2layer(dictionary *ini, const std::string &sec_name, const Engine *eg,
138 : const std::string &backbone_file,
139 : std::function<const std::string(std::string)> &pathResolver) {
140 : throw std::invalid_argument("supported only with a tag for now");
141 : }
142 :
143 : template <>
144 2887 : std::shared_ptr<LayerNode> section2layer<PlainLayer>(
145 : dictionary *ini, const std::string &sec_name, const Engine *eg,
146 : const std::string &backbone_file,
147 : std::function<const std::string(std::string)> &pathResolver) {
148 :
149 : const std::string &layer_type =
150 2887 : iniparser_getstring(ini, (sec_name + ":Type").c_str(), UNKNOWN_STR);
151 2906 : NNTR_THROW_IF(layer_type == UNKNOWN_STR, std::invalid_argument)
152 : << FUNC_TAG << "section type is invalid for section name: " << sec_name;
153 :
154 2887 : auto properties = section2properties(ini, sec_name, pathResolver);
155 :
156 : auto layer =
157 5755 : createLayerNode(eg->createLayerObject(layer_type, properties), properties);
158 2868 : return layer;
159 2887 : }
160 :
161 : template <>
162 2 : std::shared_ptr<LayerNode> section2layer<BackboneLayer>(
163 : dictionary *ini, const std::string &sec_name, const Engine *eg,
164 : const std::string &backbone_file,
165 : std::function<const std::string(std::string)> &pathResolver) {
166 : std::string type;
167 :
168 : #if defined(ENABLE_NNSTREAMER_BACKBONE)
169 : type = NNStreamerLayer::type;
170 : #endif
171 :
172 : /** TfLite has higher priority */
173 : #if defined(ENABLE_TFLITE_BACKBONE)
174 4 : if (endswith(backbone_file, ".tflite")) {
175 : type = TfLiteLayer::type;
176 : }
177 : #endif
178 :
179 3 : NNTR_THROW_IF(type.empty(), std::invalid_argument)
180 : << FUNC_TAG
181 : << "This nntrainer does not support external section: " << sec_name
182 : << " backbone: " << backbone_file;
183 :
184 2 : auto properties = section2properties(ini, sec_name, pathResolver);
185 2 : properties.push_back("model_path=" + backbone_file);
186 :
187 2 : auto layer = createLayerNode(type, properties);
188 :
189 1 : return layer;
190 2 : }
191 :
192 : /**
193 : * @brief check if graph is supported
194 : *
195 : * @param backbone_name name of the backbone
196 : * @retval true if the file extension is supported to make a graph
197 : * @retval false if the file extension is not supported
198 : */
199 2917 : static bool graphSupported(const std::string &backbone_name) {
200 5834 : return endswith(backbone_name, ".ini");
201 : }
202 :
203 : /**
204 : * @brief Get the Mergeable Graph object
205 : * @note This function is commented to prohibit using this but left intact to be
206 : referenced
207 : * @param graph currently, extendGraph accepts
208 : * std::vector<std::shared_ptr<Layer>>, so return in this format
209 : * @param ini ini to parse property
210 : * @param sec_name section name
211 : * @return std::vector<std::shared_ptr<Layer>> mergeable graph
212 : */
213 : // std::vector<std::shared_ptr<LayerNode>>
214 : // getMergeableGraph(const GraphRepresentation& graph,
215 : // dictionary *ini, const std::string &sec_name) {
216 : // std::string input_layer =
217 : // iniparser_getstring(ini, (sec_name + ":InputLayer").c_str(), "");
218 : // std::string output_layer =
219 : // iniparser_getstring(ini, (sec_name + ":OutputLayer").c_str(), "");
220 :
221 : // auto g = graph->getUnsortedLayers(input_layer, output_layer);
222 :
223 : // NNTR_THROW_IF(g.empty(), std::invalid_argument)
224 : // << FUNC_TAG << "backbone graph is empty";
225 :
226 : // /** Wait for #361 Load the backbone from its saved file */
227 : // // bool preload =
228 : // // iniparser_getboolean(ini, (sec_name + ":Preload").c_str(), true);
229 :
230 : // const std::string &trainable =
231 : // iniparser_getstring(ini, (sec_name + ":Trainable").c_str(), "true");
232 :
233 : // for (auto &lnode : g) {
234 : // lnode->setProperty({"trainable=" + trainable});
235 : // /** TODO #361: this needs update in model file to be of dictionary format
236 : // */
237 : // // if (preload) {
238 : // // layer->weight_initializer = Initializer::FILE_INITIALIZER;
239 : // // layer->bias_initializer = Initializer::FILE_INITIALIZER;
240 : // // layer->initializer_file = backbone.save_path;
241 : // // }
242 : // }
243 :
244 : // // set input dimension for the first layer in the graph
245 :
246 : // /** FIXME :the layers is not the actual model_graph. It is just the vector of
247 : // * layers generated by Model Loader. so graph[0] is still valid. Also we need
248 : // * to consider that the first layer of ini might be the first of layers. Need
249 : // * to change by compiling the backbone before using here. */
250 : // std::string input_shape =
251 : // iniparser_getstring(ini, (sec_name + ":Input_Shape").c_str(), "");
252 : // if (!input_shape.empty()) {
253 : // g[0]->setProperty({"input_shape=" + input_shape});
254 : // }
255 :
256 : // std::string input_layers =
257 : // iniparser_getstring(ini, (sec_name + ":Input_Layers").c_str(), "");
258 : // if (!input_layers.empty()) {
259 : // g[0]->setProperty({"input_layers=" + input_layers});
260 : // }
261 :
262 : // return g;
263 : // };
264 :
265 : } // namespace
266 :
267 242 : void IniGraphInterpreter::serialize(const GraphRepresentation &representation,
268 : const std::string &out) {
269 :
270 : std::vector<IniSection> sections;
271 2095 : for (auto iter = representation.cbegin(); iter != representation.cend();
272 : iter++) {
273 : const auto &ln = *iter;
274 :
275 3706 : IniSection s = IniSection::FromExportable(ln->getName(), *ln);
276 3706 : s.setEntry("type", ln->getType());
277 :
278 1853 : sections.push_back(s);
279 : }
280 :
281 242 : auto ini = IniWrapper(out, sections);
282 242 : ini.save_ini(out);
283 242 : }
284 :
285 619 : GraphRepresentation IniGraphInterpreter::deserialize(const std::string &in) {
286 621 : NNTR_THROW_IF(in.empty(), std::invalid_argument)
287 : << FUNC_TAG << "given in file is empty";
288 :
289 1244 : NNTR_THROW_IF(!isFileExist(in), std::invalid_argument)
290 : << FUNC_TAG << "given ini file does not exist, file_path: " << in;
291 :
292 607 : dictionary *ini = iniparser_load(in.c_str());
293 607 : NNTR_THROW_IF(!ini, std::runtime_error) << "loading ini failed";
294 :
295 607 : auto freedict = [ini] { iniparser_freedict(ini); };
296 :
297 : /** Get number of sections in the file */
298 607 : int num_ini_sec = iniparser_getnsec(ini);
299 607 : NNTR_THROW_IF_CLEANUP(num_ini_sec < 0, std::invalid_argument, freedict)
300 : << FUNC_TAG << "invalid number of sections.";
301 :
302 : GraphRepresentation graph;
303 :
304 : try {
305 607 : ml_logi("==========================parsing ini...");
306 607 : ml_logi("not-allowed property for the layer throws error");
307 607 : ml_logi("valid property with invalid value throws error as well");
308 4716 : for (int idx = 0; idx < num_ini_sec; ++idx) {
309 4137 : auto sec_name_ = iniparser_getsecname(ini, idx);
310 4137 : NNTR_THROW_IF_CLEANUP(!sec_name_, std::runtime_error, freedict)
311 : << FUNC_TAG << "parsing a section name returned error, filename: " << in
312 : << "idx: " << idx;
313 :
314 4137 : std::string sec_name(sec_name_);
315 :
316 15299 : if (istrequal(sec_name, MODEL_STR) || istrequal(sec_name, DATASET_STR) ||
317 11089 : istrequal(sec_name, TRAINSET_STR) ||
318 11089 : istrequal(sec_name, VALIDSET_STR) ||
319 11089 : istrequal(sec_name, TESTSET_STR) ||
320 11750 : istrequal(sec_name, OPTIMIZER_STR) ||
321 7059 : istrequal(sec_name, LRSCHED_STR)) {
322 : /// dedicated sections so skip
323 1220 : continue;
324 : }
325 : /** Parse all the layers defined as sections in order */
326 2945 : ml_logd("probing section_name: %s", sec_name_);
327 2917 : std::shared_ptr<LayerNode> layer;
328 :
329 : /**
330 : * If this section is a backbone, load backbone section from this
331 : * @note The order of backbones in the ini file defines the order on the
332 : * backbones in the model graph
333 : */
334 : const char *backbone_path =
335 2917 : iniparser_getstring(ini, (sec_name + ":Backbone").c_str(), UNKNOWN_STR);
336 :
337 2917 : NNTR_THROW_IF(backbone_path == nullptr, std::invalid_argument)
338 : << FUNC_TAG << "backbone path is null";
339 :
340 2945 : const std::string &backbone = pathResolver(backbone_path);
341 2917 : if (graphSupported(backbone)) {
342 : /// @todo: this will be changed to a general way to add a graph
343 28 : auto bg = this->deserialize(backbone);
344 : const std::string &trainable =
345 40 : iniparser_getstring(ini, (sec_name + ":Trainable").c_str(), "true");
346 :
347 50 : for (auto &node : bg) {
348 60 : node->setProperty({"trainable=" + trainable});
349 : }
350 20 : graph.insert(graph.end(), bg.begin(), bg.end());
351 : continue;
352 20 : }
353 :
354 2889 : if (std::strcmp(backbone_path, UNKNOWN_STR) == 0) {
355 : layer =
356 5755 : section2layer<PlainLayer>(ini, sec_name, ct_engine, "", pathResolver);
357 : } else {
358 29 : layer = section2layer<BackboneLayer>(ini, sec_name, ct_engine, backbone,
359 2 : pathResolver);
360 : }
361 :
362 2869 : graph.push_back(layer);
363 : }
364 : /// @todo if graph Model Type is of recurrent_wrapper, parse model and
365 : /// realize before return
366 28 : } catch (...) {
367 : /** clean up and rethrow */
368 : freedict();
369 28 : throw;
370 28 : }
371 :
372 : freedict();
373 579 : return graph;
374 58 : }
375 :
376 : } // namespace nntrainer
|