Line data Source code
1 : // SPDX-License-Identifier: Apache-2.0
2 : /**
3 : * Copyright (C) 2020 Parichay Kapoor <pk.kapoor@samsung.com>
4 : *
5 : * @file model_loader.cpp
6 : * @date 5 August 2020
7 : * @brief This is model loader class for the Neural Network
8 : * @see https://github.com/nnstreamer/nntrainer
9 : * @author Jijoong Moon <jijoong.moon@samsung.com>
10 : * @author Parichay Kapoor <pk.kapoor@samsung.com>
11 : * @bug No known bugs except for NYI items
12 : *
13 : */
14 : #include <sstream>
15 :
16 : #include <adam.h>
17 : #include <databuffer_factory.h>
18 : #include <filesystem>
19 : #include <ini_interpreter.h>
20 : #include <model_loader.h>
21 : #include <neuralnet.h>
22 : #include <nntrainer_error.h>
23 : #include <nntrainer_log.h>
24 : #include <optimizer_wrapped.h>
25 : #include <time_dist.h>
26 : #include <util_func.h>
27 :
28 : #if defined(ENABLE_NNSTREAMER_BACKBONE)
29 : #include <nnstreamer_layer.h>
30 : #endif
31 :
32 : #if defined(ENABLE_TFLITE_BACKBONE)
33 : #include <tflite_layer.h>
34 : #endif
35 :
36 : #ifdef ENABLE_ONNX_INTERPRETER
37 : #include <onnx_interpreter.h>
38 : #endif
39 :
40 : #define NN_INI_RETURN_STATUS() \
41 : do { \
42 : if (status != ML_ERROR_NONE) { \
43 : iniparser_freedict(ini); \
44 : return status; \
45 : } \
46 : } while (0)
47 :
48 : namespace nntrainer {
49 :
50 554 : int ModelLoader::loadLearningRateSchedulerConfigIni(
51 : dictionary *ini, std::shared_ptr<ml::train::Optimizer> &optimizer) {
52 : int status = ML_ERROR_NONE;
53 :
54 554 : if (iniparser_find_entry(ini, "LearningRateScheduler") == 0) {
55 : return ML_ERROR_NONE;
56 : }
57 :
58 : /** Default to adam optimizer */
59 : const char *lrs_type =
60 5 : iniparser_getstring(ini, "LearningRateScheduler:Type", "unknown");
61 : std::vector<std::string> properties =
62 10 : parseProperties(ini, "LearningRateScheduler", {"type"});
63 :
64 : try {
65 : auto lrs =
66 10 : ct_engine->createLearningRateSchedulerObject(lrs_type, properties);
67 : auto opt_wrapped = std::static_pointer_cast<OptimizerWrapped>(optimizer);
68 10 : opt_wrapped->setLearningRateScheduler(std::move(lrs));
69 0 : } catch (std::exception &e) {
70 0 : ml_loge("%s %s", typeid(e).name(), e.what());
71 : return ML_ERROR_INVALID_PARAMETER;
72 0 : } catch (...) {
73 0 : ml_loge("Creating the optimizer failed");
74 : return ML_ERROR_INVALID_PARAMETER;
75 0 : }
76 :
77 5 : return status;
78 5 : }
79 :
80 593 : int ModelLoader::loadOptimizerConfigIni(dictionary *ini, NeuralNetwork &model) {
81 : int status = ML_ERROR_NONE;
82 :
83 593 : if (iniparser_find_entry(ini, "Optimizer") == 0) {
84 27 : if (!model.opt) {
85 6 : ml_logw("there is no [Optimizer] section in given ini file."
86 : "This model can only be used for inference.");
87 : }
88 27 : return ML_ERROR_NONE;
89 : }
90 :
91 : /** Optimizer already set with deprecated method */
92 566 : if (model.opt) {
93 0 : ml_loge("Error: optimizers specified twice.");
94 0 : return ML_ERROR_INVALID_PARAMETER;
95 : }
96 :
97 : /** Default to adam optimizer */
98 566 : const char *opt_type = iniparser_getstring(ini, "Optimizer:Type", "adam");
99 : std::vector<std::string> properties =
100 1132 : parseProperties(ini, "Optimizer", {"type"});
101 :
102 : try {
103 : std::shared_ptr<ml::train::Optimizer> optimizer =
104 566 : createOptimizerWrapped(opt_type, properties);
105 554 : model.setOptimizer(optimizer);
106 554 : loadLearningRateSchedulerConfigIni(ini, optimizer);
107 12 : } catch (std::exception &e) {
108 12 : ml_loge("%s %s", typeid(e).name(), e.what());
109 : return ML_ERROR_INVALID_PARAMETER;
110 12 : } catch (...) {
111 0 : ml_loge("Creating the optimizer failed");
112 : return ML_ERROR_INVALID_PARAMETER;
113 0 : }
114 :
115 554 : return status;
116 566 : }
117 :
118 : /**
119 : * @brief load model config from ini
120 : */
121 665 : int ModelLoader::loadModelConfigIni(dictionary *ini, NeuralNetwork &model) {
122 : int status = ML_ERROR_NONE;
123 :
124 665 : if (iniparser_find_entry(ini, "Model") == 0) {
125 18 : ml_loge("there is no [Model] section in given ini file");
126 18 : return ML_ERROR_INVALID_PARAMETER;
127 : }
128 :
129 : std::vector<std::string> properties = parseProperties(
130 : ini, "Model",
131 : {"optimizer", "learning_rate", "decay_steps", "decay_rate", "beta1",
132 1294 : "beta2", "epsilon", "type", "save_path", "tensor_type", "tensor_format"});
133 : try {
134 647 : model.setProperty(properties);
135 0 : } catch (std::exception &e) {
136 0 : ml_loge("%s %s", typeid(e).name(), e.what());
137 : return ML_ERROR_INVALID_PARAMETER;
138 0 : } catch (...) {
139 0 : ml_loge("Creating the optimizer failed");
140 : return ML_ERROR_INVALID_PARAMETER;
141 0 : }
142 :
143 : /** handle save_path as a special case for model_file_context */
144 : const std::string &save_path =
145 647 : iniparser_getstring(ini, "Model:Save_path", unknown);
146 647 : if (save_path != unknown) {
147 120 : model.setProperty({"save_path=" + resolvePath(save_path)});
148 : }
149 :
150 : /**
151 : ********
152 : * Note: Below is only to maintain backward compatibility
153 : ********
154 : */
155 :
156 : /** If no optimizer specified, exit without error */
157 647 : const char *opt_type = iniparser_getstring(ini, "Model:Optimizer", unknown);
158 647 : if (opt_type == unknown)
159 : return status;
160 :
161 24 : if (model.opt) {
162 : /** Optimizer already set with a new section */
163 0 : ml_loge("Error: optimizers specified twice.");
164 0 : return ML_ERROR_INVALID_PARAMETER;
165 : }
166 :
167 24 : ml_logw("Warning: using deprecated ini style for optimizers.");
168 48 : ml_logw(
169 : "Warning: create [ Optimizer ] section in ini to specify optimizers.");
170 :
171 : try {
172 : std::shared_ptr<ml::train::Optimizer> optimizer =
173 48 : createOptimizerWrapped(opt_type, {});
174 48 : model.setOptimizer(optimizer);
175 0 : } catch (std::exception &e) {
176 0 : ml_loge("%s %s", typeid(e).name(), e.what());
177 : return ML_ERROR_INVALID_PARAMETER;
178 0 : } catch (...) {
179 0 : ml_loge("Creating the optimizer failed");
180 : return ML_ERROR_INVALID_PARAMETER;
181 0 : }
182 :
183 : std::vector<std::string> optimizer_prop = {};
184 :
185 : /** push only if ini_key exist as prop_key=ini_value */
186 144 : auto maybe_push = [ini](std::vector<std::string> &prop_vector,
187 : const std::string &ini_key,
188 : const std::string &prop_key) {
189 : constexpr const char *LOCAL_UNKNOWN = "unknown";
190 : std::string ini_value =
191 144 : iniparser_getstring(ini, ini_key.c_str(), LOCAL_UNKNOWN);
192 288 : if (!istrequal(ini_value, LOCAL_UNKNOWN)) {
193 48 : prop_vector.push_back(prop_key + "=" + ini_value);
194 : }
195 144 : };
196 :
197 : const std::vector<std::string> deprecated_optimizer_keys = {
198 24 : "learning_rate", "decay_rate", "decay_steps", "beta1", "beta2", "epsilon"};
199 168 : for (const auto &key : deprecated_optimizer_keys) {
200 288 : maybe_push(optimizer_prop, "Model:" + key, key);
201 : }
202 :
203 : try {
204 24 : model.opt->setProperty(optimizer_prop);
205 0 : } catch (std::exception &e) {
206 0 : ml_loge("%s %s", typeid(e).name(), e.what());
207 : return ML_ERROR_INVALID_PARAMETER;
208 0 : } catch (...) {
209 0 : ml_loge("Settings properties to optimizer failed.");
210 : return ML_ERROR_INVALID_PARAMETER;
211 0 : }
212 :
213 : return status;
214 701 : }
215 :
216 : /**
217 : * @brief load dataset config from ini
218 : */
219 647 : int ModelLoader::loadDatasetConfigIni(dictionary *ini, NeuralNetwork &model) {
220 : /************ helper functors **************/
221 647 : auto try_parse_datasetsection_for_backward_compatibility = [&]() -> int {
222 : int status = ML_ERROR_NONE;
223 647 : if (iniparser_find_entry(ini, "Dataset") == 0) {
224 : return ML_ERROR_NONE;
225 : }
226 :
227 91 : ml_logw("Using dataset section is deprecated, please consider using "
228 : "train_set, valid_set, test_set sections");
229 :
230 : /// @note DataSet:BufferSize is parsed for backward compatibility
231 91 : std::string bufsizepros("buffer_size=");
232 : bufsizepros +=
233 : iniparser_getstring(ini, "DataSet:BufferSize",
234 91 : iniparser_getstring(ini, "DataSet:buffer_size", "1"));
235 :
236 237 : auto parse_and_set = [&](const char *key, DatasetModeType dt,
237 : bool required) -> int {
238 237 : const char *path = iniparser_getstring(ini, key, NULL);
239 :
240 237 : if (path == NULL) {
241 92 : return required ? ML_ERROR_INVALID_PARAMETER : ML_ERROR_NONE;
242 : }
243 :
244 : try {
245 182 : model.data_buffers[static_cast<int>(dt)] =
246 182 : createDataBuffer(DatasetType::FILE, resolvePath(path).c_str());
247 546 : model.data_buffers[static_cast<int>(dt)]->setProperty({bufsizepros});
248 0 : } catch (...) {
249 0 : ml_loge("path is not valid, path: %s", resolvePath(path).c_str());
250 : return ML_ERROR_INVALID_PARAMETER;
251 0 : }
252 :
253 182 : return ML_ERROR_NONE;
254 182 : };
255 :
256 : status =
257 91 : parse_and_set("DataSet:TrainData", DatasetModeType::MODE_TRAIN, true);
258 91 : NN_RETURN_STATUS();
259 : status =
260 73 : parse_and_set("DataSet:ValidData", DatasetModeType::MODE_VALID, false);
261 73 : NN_RETURN_STATUS();
262 : status =
263 73 : parse_and_set("DataSet:TestData", DatasetModeType::MODE_TEST, false);
264 73 : NN_RETURN_STATUS();
265 73 : const char *path = iniparser_getstring(ini, "Dataset:LabelData", NULL);
266 73 : if (path != NULL) {
267 54 : ml_logi("setting labelData is deprecated!, it is essentially noop now!");
268 : }
269 :
270 73 : ml_logd("parsing dataset done");
271 73 : return status;
272 647 : };
273 :
274 1851 : auto parse_buffer_section = [ini, this,
275 : &model](const std::string §ion_name,
276 : DatasetModeType type) -> int {
277 1851 : if (iniparser_find_entry(ini, section_name.c_str()) == 0) {
278 : return ML_ERROR_NONE;
279 : }
280 : const char *db_type =
281 36 : iniparser_getstring(ini, (section_name + ":type").c_str(), unknown);
282 36 : auto &db = model.data_buffers[static_cast<int>(type)];
283 :
284 : /// @todo delegate this to app context (currently there is only file
285 : /// databuffer so file is directly used)
286 72 : if (!istrequal(db_type, "file")) {
287 36 : ml_loge("databuffer type is unknonw, type: %s", db_type);
288 36 : return ML_ERROR_INVALID_PARAMETER;
289 : }
290 :
291 : try {
292 0 : db = createDataBuffer(DatasetType::FILE);
293 : const std::vector<std::string> properties =
294 0 : parseProperties(ini, section_name, {"type"});
295 :
296 0 : db->setProperty(properties);
297 0 : } catch (std::exception &e) {
298 0 : ml_loge("error while creating and setting dataset, %s", e.what());
299 : return ML_ERROR_INVALID_PARAMETER;
300 0 : }
301 :
302 0 : return ML_ERROR_NONE;
303 647 : };
304 :
305 : /************ start of the procedure **************/
306 : int status = ML_ERROR_NONE;
307 647 : status = try_parse_datasetsection_for_backward_compatibility();
308 647 : NN_RETURN_STATUS();
309 :
310 629 : status = parse_buffer_section("train_set", DatasetModeType::MODE_TRAIN);
311 629 : NN_RETURN_STATUS();
312 617 : status = parse_buffer_section("valid_set", DatasetModeType::MODE_VALID);
313 617 : NN_RETURN_STATUS();
314 1210 : status = parse_buffer_section("test_set", DatasetModeType::MODE_TEST);
315 : NN_RETURN_STATUS();
316 :
317 : return status;
318 : }
319 :
320 : std::vector<std::string>
321 1218 : ModelLoader::parseProperties(dictionary *ini, const std::string §ion_name,
322 : const std::vector<std::string> &filter_props) {
323 1218 : int num_entries = iniparser_getsecnkeys(ini, section_name.c_str());
324 :
325 1218 : ml_logd("number of entries for %s: %d", section_name.c_str(), num_entries);
326 :
327 1218 : if (num_entries < 1) {
328 0 : std::stringstream ss;
329 : ss << "there are no entries in the section: " << section_name;
330 0 : throw std::invalid_argument(ss.str());
331 0 : }
332 :
333 1218 : std::unique_ptr<const char *[]> key_refs(new const char *[num_entries]);
334 :
335 1218 : if (iniparser_getseckeys(ini, section_name.c_str(), key_refs.get()) ==
336 : nullptr) {
337 0 : std::stringstream ss;
338 : ss << "failed to fetch key for section: " << section_name;
339 0 : throw std::invalid_argument(ss.str());
340 0 : }
341 :
342 : std::vector<std::string> properties;
343 1218 : properties.reserve(num_entries - 1);
344 :
345 7120 : for (int i = 0; i < num_entries; ++i) {
346 : /// key is ini section key, which is section_name + ":" + prop_key
347 5902 : std::string key(key_refs[i]);
348 5902 : std::string prop_key = key.substr(key.find(":") + 1);
349 :
350 : bool filter_key_found = false;
351 53044 : for (auto const &filter_key : filter_props)
352 47142 : if (istrequal(prop_key, filter_key))
353 : filter_key_found = true;
354 5902 : if (filter_key_found)
355 : continue;
356 :
357 4358 : std::string value = iniparser_getstring(ini, key_refs[i], unknown);
358 :
359 4358 : if (value == unknown) {
360 0 : std::stringstream ss;
361 : ss << "parsing property failed key: " << key;
362 0 : throw std::invalid_argument(ss.str());
363 0 : }
364 :
365 4358 : if (value == "") {
366 0 : std::stringstream ss;
367 0 : ss << "property key " << key << " has empty value. It is not allowed";
368 0 : throw std::invalid_argument(ss.str());
369 0 : }
370 4358 : ml_logd("parsed properties: %s=%s", prop_key.c_str(), value.c_str());
371 :
372 8716 : properties.push_back(prop_key + "=" + value);
373 : }
374 :
375 1218 : return properties;
376 0 : }
377 :
378 : /**
379 : * @brief load all of model and dataset from ini
380 : */
381 665 : int ModelLoader::loadFromIni(std::string ini_file, NeuralNetwork &model,
382 : bool bare_layers) {
383 : int status = ML_ERROR_NONE;
384 : int num_ini_sec = 0;
385 : dictionary *ini;
386 :
387 665 : if (ini_file.empty()) {
388 0 : ml_loge("Error: Configuration File is not defined");
389 0 : return ML_ERROR_INVALID_PARAMETER;
390 : }
391 :
392 1330 : if (!isFileExist(ini_file)) {
393 0 : ml_loge("Cannot open model configuration file, filename : %s",
394 : ini_file.c_str());
395 0 : return ML_ERROR_INVALID_PARAMETER;
396 : }
397 :
398 : /** Parse ini file */
399 665 : ini = iniparser_load(ini_file.c_str());
400 665 : if (ini == NULL) {
401 0 : ml_loge("Error: cannot parse file: %s\n", ini_file.c_str());
402 0 : return ML_ERROR_INVALID_PARAMETER;
403 : }
404 :
405 : /** Get number of sections in the file */
406 665 : num_ini_sec = iniparser_getnsec(ini);
407 665 : if (num_ini_sec < 0) {
408 0 : ml_loge("Error: invalid number of sections.");
409 : status = ML_ERROR_INVALID_PARAMETER;
410 0 : NN_INI_RETURN_STATUS();
411 : }
412 :
413 665 : if (!bare_layers) {
414 665 : status = loadModelConfigIni(ini, model);
415 665 : NN_INI_RETURN_STATUS();
416 :
417 647 : status = loadDatasetConfigIni(ini, model);
418 647 : NN_INI_RETURN_STATUS();
419 :
420 593 : status = loadOptimizerConfigIni(ini, model);
421 593 : NN_INI_RETURN_STATUS();
422 : }
423 :
424 : auto path_resolver = [this](const std::string &path) {
425 2903 : return resolvePath(path);
426 581 : };
427 :
428 581 : ml_logd("parsing graph started");
429 : try {
430 : std::unique_ptr<GraphInterpreter> ini_interpreter =
431 581 : std::make_unique<nntrainer::IniGraphInterpreter>(ct_engine,
432 : path_resolver);
433 581 : auto graph_representation = ini_interpreter->deserialize(ini_file);
434 :
435 3391 : for (auto &node : graph_representation) {
436 5676 : model.addLayer(node);
437 : }
438 1106 : ml_logd("parsing graph finished");
439 :
440 553 : if (model.empty()) {
441 26 : ml_loge("there is no layer section in the ini file");
442 : status = ML_ERROR_INVALID_PARAMETER;
443 : }
444 581 : } catch (std::exception &e) {
445 28 : ml_loge("failed to load graph, reason: %s ", e.what());
446 : status = ML_ERROR_INVALID_PARAMETER;
447 28 : }
448 :
449 581 : iniparser_freedict(ini);
450 : return status;
451 : }
452 :
453 : /**
454 : * @brief load model from ONNX file
455 : */
456 0 : int ModelLoader::loadFromONNX(std::string onnx_file, NeuralNetwork &model) {
457 : #ifdef ENABLE_ONNX_INTERPRETER
458 : int status = ML_ERROR_NONE;
459 :
460 : if (onnx_file.empty()) {
461 : ml_loge("Error: configuration file is not defined");
462 : return ML_ERROR_INVALID_PARAMETER;
463 : }
464 :
465 : if (!isFileExist(onnx_file)) {
466 : ml_loge("Cannot open onnx model configuration file, filename : %s",
467 : onnx_file.c_str());
468 : return ML_ERROR_INVALID_PARAMETER;
469 : }
470 :
471 : try {
472 : std::unique_ptr<GraphInterpreter> onnx_interpreter =
473 : std::make_unique<nntrainer::ONNXInterpreter>();
474 :
475 : auto graph_representation = onnx_interpreter->deserialize(onnx_file);
476 :
477 : for (auto &node : graph_representation) {
478 : model.addLayer(node);
479 : };
480 :
481 : if (model.empty()) {
482 : ml_loge("there is no layer section in the ini file");
483 : status = ML_ERROR_INVALID_PARAMETER;
484 : }
485 : } catch (std::exception &e) {
486 : ml_loge("failed to load graph, reason: %s ", e.what());
487 : status = ML_ERROR_INVALID_PARAMETER;
488 : }
489 :
490 : return status;
491 : #else
492 0 : throw std::runtime_error{"enable-onnx-interpreter option is not enabled"};
493 : #endif
494 : }
495 :
496 : /**
497 : * @brief load all properties from context
498 : */
499 666 : int ModelLoader::loadFromContext(NeuralNetwork &model) {
500 : /// @todo: Property for Context needs to updated
501 : // auto props = app_context.getProperties();
502 : // model.setTrainConfig(props);
503 :
504 666 : return ML_ERROR_NONE;
505 : }
506 :
507 : /**
508 : * @brief load all of model and dataset from given config file
509 : */
510 666 : int ModelLoader::loadFromConfig(std::string config, NeuralNetwork &model) {
511 :
512 666 : if (model_file_engine != nullptr) {
513 0 : ml_loge(
514 : "model_file_engine is already initialized, there is a possiblity that "
515 : "last load from config wasn't finished correctly, and model loader is "
516 : "reused");
517 0 : return ML_ERROR_UNKNOWN;
518 : }
519 :
520 1332 : model_file_engine = std::make_unique<Engine>();
521 :
522 666 : auto config_realpath_char = getRealpath(config.c_str(), nullptr);
523 666 : if (config_realpath_char == nullptr) {
524 : const size_t error_buflen = 100;
525 : char error_buf[error_buflen];
526 2 : ml_loge("failed to resolve config path to absolute path, reason: %s",
527 : SAFE_STRERROR(errno, error_buf, error_buflen));
528 : return ML_ERROR_INVALID_PARAMETER;
529 : }
530 665 : std::string config_realpath(config_realpath_char);
531 665 : free(config_realpath_char);
532 :
533 : auto base_path =
534 1330 : std::filesystem::path(config_realpath).parent_path().string();
535 :
536 665 : model_file_engine->setWorkingDirectory(base_path);
537 :
538 1330 : ml_logd("for the current model working directory is set to %s",
539 : base_path.c_str());
540 :
541 1330 : int status = loadFromConfig(config_realpath, model, false);
542 : model_file_engine.reset();
543 : return status;
544 : }
545 :
546 : /**
547 : * @brief load all of model and dataset from given config file
548 : */
549 665 : int ModelLoader::loadFromConfig(std::string config, NeuralNetwork &model,
550 : bool bare_layers) {
551 665 : if (isIniFile(config)) {
552 1330 : return loadFromIni(config, model, bare_layers);
553 : }
554 :
555 0 : if (isONNXFile(config)) {
556 0 : return loadFromONNX(config, model);
557 : }
558 :
559 : return ML_ERROR_INVALID_PARAMETER;
560 : }
561 :
562 665 : bool ModelLoader::fileExt(const std::string &filename, const std::string &ext) {
563 : size_t position = filename.find_last_of(".");
564 665 : if (position == std::string::npos)
565 : return false;
566 :
567 665 : if (filename.substr(position + 1) == ext) {
568 : return true;
569 : }
570 :
571 : return false;
572 : }
573 :
574 665 : bool ModelLoader::isIniFile(const std::string &filename) {
575 1330 : return fileExt(filename, "ini");
576 : }
577 :
578 0 : bool ModelLoader::isTfLiteFile(const std::string &filename) {
579 0 : return fileExt(filename, "tflite");
580 : }
581 :
582 0 : bool ModelLoader::isONNXFile(const std::string &filename) {
583 0 : return fileExt(filename, "onnx");
584 : }
585 :
586 : } // namespace nntrainer
|