1 // SPDX-License-Identifier: Apache-2.0
3 * Copyright (C) 2020 Parichay Kapoor <pk.kapoor@samsung.com>
5 * @file model_loader.cpp
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
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>
27 #if defined(ENABLE_NNSTREAMER_BACKBONE)
28 #include <nnstreamer_layer.h>
31 #if defined(ENABLE_TFLITE_BACKBONE)
32 #include <tflite_layer.h>
35 #define NN_INI_RETURN_STATUS() \
37 if (status != ML_ERROR_NONE) { \
38 iniparser_freedict(ini); \
45 int ModelLoader::loadLearningRateSchedulerConfigIni(
46 dictionary *ini, std::shared_ptr<ml::train::Optimizer> &optimizer) {
47 int status = ML_ERROR_NONE;
49 if (iniparser_find_entry(ini, "LearningRateScheduler") == 0) {
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"});
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;
68 ml_loge("Creating the optimizer failed");
69 return ML_ERROR_INVALID_PARAMETER;
75 int ModelLoader::loadOptimizerConfigIni(dictionary *ini, NeuralNetwork &model) {
76 int status = ML_ERROR_NONE;
78 if (iniparser_find_entry(ini, "Optimizer") == 0) {
80 ml_logw("there is no [Optimizer] section in given ini file."
81 "This model can only be used for inference.");
86 /** Optimizer already set with deprecated method */
88 ml_loge("Error: optimizers specified twice.");
89 return ML_ERROR_INVALID_PARAMETER;
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"});
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;
106 ml_loge("Creating the optimizer failed");
107 return ML_ERROR_INVALID_PARAMETER;
114 * @brief load model config from ini
116 int ModelLoader::loadModelConfigIni(dictionary *ini, NeuralNetwork &model) {
117 int status = ML_ERROR_NONE;
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;
124 std::vector<std::string> properties =
125 parseProperties(ini, "Model",
126 {"optimizer", "learning_rate", "decay_steps", "decay_rate",
127 "beta1", "beta2", "epsilon", "type", "save_path"});
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;
134 ml_loge("Creating the optimizer failed");
135 return ML_ERROR_INVALID_PARAMETER;
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)});
147 * Note: Below is only to maintain backward compatibility
151 /** If no optimizer specified, exit without error */
152 const char *opt_type = iniparser_getstring(ini, "Model:Optimizer", unknown);
153 if (opt_type == unknown)
157 /** Optimizer already set with a new section */
158 ml_loge("Error: optimizers specified twice.");
159 return ML_ERROR_INVALID_PARAMETER;
162 ml_logw("Warning: using deprecated ini style for optimizers.");
164 "Warning: create [ Optimizer ] section in ini to specify optimizers.");
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;
174 ml_loge("Creating the optimizer failed");
175 return ML_ERROR_INVALID_PARAMETER;
178 std::vector<std::string> optimizer_prop = {};
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);
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);
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;
204 ml_loge("Settings properties to optimizer failed.");
205 return ML_ERROR_INVALID_PARAMETER;
212 * @brief load dataset config from ini
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;
222 ml_logw("Using dataset section is deprecated, please consider using "
223 "train_set, valid_set, test_set sections");
225 /// @note DataSet:BufferSize is parsed for backward compatibility
226 std::string bufsizepros("buffer_size=");
228 iniparser_getstring(ini, "DataSet:BufferSize",
229 iniparser_getstring(ini, "DataSet:buffer_size", "1"));
231 auto parse_and_set = [&](const char *key, DatasetModeType dt,
232 bool required) -> int {
233 const char *path = iniparser_getstring(ini, key, NULL);
236 return required ? ML_ERROR_INVALID_PARAMETER : ML_ERROR_NONE;
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});
244 ml_loge("path is not valid, path: %s", resolvePath(path).c_str());
245 return ML_ERROR_INVALID_PARAMETER;
248 return ML_ERROR_NONE;
252 parse_and_set("DataSet:TrainData", DatasetModeType::MODE_TRAIN, true);
255 parse_and_set("DataSet:ValidData", DatasetModeType::MODE_VALID, false);
258 parse_and_set("DataSet:TestData", DatasetModeType::MODE_TEST, false);
260 const char *path = iniparser_getstring(ini, "Dataset:LabelData", NULL);
262 ml_logi("setting labelData is deprecated!, it is essentially noop now!");
265 ml_logd("parsing dataset done");
269 auto parse_buffer_section = [ini, this,
270 &model](const std::string §ion_name,
271 DatasetModeType type) -> int {
272 if (iniparser_find_entry(ini, section_name.c_str()) == 0) {
273 return ML_ERROR_NONE;
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)];
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;
287 db = createDataBuffer(DatasetType::FILE);
288 const std::vector<std::string> properties =
289 parseProperties(ini, section_name, {"type"});
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;
297 return ML_ERROR_NONE;
300 /************ start of the procedure **************/
301 int status = ML_ERROR_NONE;
302 status = try_parse_datasetsection_for_backward_compatibility();
305 status = parse_buffer_section("train_set", DatasetModeType::MODE_TRAIN);
307 status = parse_buffer_section("valid_set", DatasetModeType::MODE_VALID);
309 status = parse_buffer_section("test_set", DatasetModeType::MODE_TEST);
315 std::vector<std::string>
316 ModelLoader::parseProperties(dictionary *ini, const std::string §ion_name,
317 const std::vector<std::string> &filter_props) {
318 int num_entries = iniparser_getsecnkeys(ini, section_name.c_str());
320 ml_logd("number of entries for %s: %d", section_name.c_str(), num_entries);
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());
328 std::unique_ptr<const char *[]> key_refs(new const char *[num_entries]);
330 if (iniparser_getseckeys(ini, section_name.c_str(), key_refs.get()) ==
332 std::stringstream ss;
333 ss << "failed to fetch key for section: " << section_name;
334 throw std::invalid_argument(ss.str());
337 std::vector<std::string> properties;
338 properties.reserve(num_entries - 1);
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);
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)
352 std::string value = iniparser_getstring(ini, key_refs[i], unknown);
354 if (value == unknown) {
355 std::stringstream ss;
356 ss << "parsing property failed key: " << key;
357 throw std::invalid_argument(ss.str());
361 std::stringstream ss;
362 ss << "property key " << key << " has empty value. It is not allowed";
363 throw std::invalid_argument(ss.str());
365 ml_logd("parsed properties: %s=%s", prop_key.c_str(), value.c_str());
367 properties.push_back(prop_key + "=" + value);
374 * @brief load all of model and dataset from ini
376 int ModelLoader::loadFromIni(std::string ini_file, NeuralNetwork &model,
378 int status = ML_ERROR_NONE;
382 if (ini_file.empty()) {
383 ml_loge("Error: Configuration File is not defined");
384 return ML_ERROR_INVALID_PARAMETER;
387 if (!isFileExist(ini_file)) {
388 ml_loge("Cannot open model configuration file, filename : %s",
390 return ML_ERROR_INVALID_PARAMETER;
393 /** Parse ini file */
394 ini = iniparser_load(ini_file.c_str());
396 ml_loge("Error: cannot parse file: %s\n", ini_file.c_str());
397 return ML_ERROR_INVALID_PARAMETER;
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();
409 status = loadModelConfigIni(ini, model);
410 NN_INI_RETURN_STATUS();
412 status = loadDatasetConfigIni(ini, model);
413 NN_INI_RETURN_STATUS();
415 status = loadOptimizerConfigIni(ini, model);
416 NN_INI_RETURN_STATUS();
419 auto path_resolver = [this](const std::string &path) {
420 return resolvePath(path);
423 ml_logd("parsing graph started");
425 std::unique_ptr<GraphInterpreter> ini_interpreter =
426 std::make_unique<nntrainer::IniGraphInterpreter>(app_context,
428 auto graph_representation = ini_interpreter->deserialize(ini_file);
430 for (auto &node : graph_representation) {
431 model.addLayer(node);
433 ml_logd("parsing graph finished");
436 ml_loge("there is no layer section in the ini file");
437 status = ML_ERROR_INVALID_PARAMETER;
439 } catch (std::exception &e) {
440 ml_loge("failed to load graph, reason: %s ", e.what());
441 status = ML_ERROR_INVALID_PARAMETER;
444 iniparser_freedict(ini);
449 * @brief load all properties from context
451 int ModelLoader::loadFromContext(NeuralNetwork &model) {
452 auto props = app_context.getProperties();
453 model.setTrainConfig(props);
455 return ML_ERROR_NONE;
459 * @brief load all of model and dataset from given config file
461 int ModelLoader::loadFromConfig(std::string config, NeuralNetwork &model) {
463 if (model_file_context != nullptr) {
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 "
468 return ML_ERROR_UNKNOWN;
471 model_file_context = std::make_unique<AppContext>();
473 auto config_realpath_char = getRealpath(config.c_str(), nullptr);
474 if (config_realpath_char == nullptr) {
475 ml_loge("failed to resolve config path to absolute path, reason: %s",
477 return ML_ERROR_INVALID_PARAMETER;
479 std::string config_realpath(config_realpath_char);
480 free(config_realpath_char);
482 auto pos = config_realpath.find_last_of("/");
483 if (pos == std::string::npos) {
484 ml_loge("resolved model path does not contain any path seperater. %s",
485 config_realpath.c_str());
486 return ML_ERROR_UNKNOWN;
489 auto base_path = config_realpath.substr(0, pos);
490 model_file_context->setWorkingDirectory(base_path);
491 ml_logd("for the current model working directory is set to %s",
494 int status = loadFromConfig(config_realpath, model, false);
495 model_file_context.reset();
500 * @brief load all of model and dataset from given config file
502 int ModelLoader::loadFromConfig(std::string config, NeuralNetwork &model,
504 if (fileIni(config)) {
505 return loadFromIni(config, model, bare_layers);
508 return ML_ERROR_INVALID_PARAMETER;
511 bool ModelLoader::fileExt(const std::string &filename, const std::string &ext) {
512 size_t position = filename.find_last_of(".");
513 if (position == std::string::npos)
516 if (filename.substr(position + 1) == ext) {
523 bool ModelLoader::fileIni(const std::string &filename) {
524 return fileExt(filename, "ini");
527 bool ModelLoader::fileTfLite(const std::string &filename) {
528 return fileExt(filename, "tflite");
531 } // namespace nntrainer