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