LCOV - code coverage report
Current view: top level - nntrainer/compiler - ini_interpreter.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 97.8 % 93 91
Test Date: 2025-12-14 20:38:17 Functions: 100.0 % 9 9

            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
        

Generated by: LCOV version 2.0-1