Generalize options for soft backend (#923)
authorEfimov Alexander/AI Tools Lab/./Samsung Electronics <a.efimov@samsung.com>
Mon, 13 Aug 2018 15:33:43 +0000 (18:33 +0300)
committerSergey Vostokov/AI Tools Lab /SRR/Staff Engineer/삼성전자 <s.vostokov@samsung.com>
Mon, 13 Aug 2018 15:33:43 +0000 (18:33 +0300)
Replace specific file paths with output directory and common name

Signed-off-by: Efimov Alexander <a.efimov@samsung.com>
13 files changed:
contrib/nnc/README.md
contrib/nnc/libs/backend/soft/include/base_generator.h
contrib/nnc/libs/backend/soft/include/c_generator.h
contrib/nnc/libs/backend/soft/include/cpp_generator.h
contrib/nnc/libs/backend/soft/include/soft_backend.h
contrib/nnc/libs/backend/soft/src/base_generator.cpp
contrib/nnc/libs/backend/soft/src/c_backend.cpp
contrib/nnc/libs/backend/soft/src/c_generator.cpp
contrib/nnc/libs/backend/soft/src/cpp_backend.cpp
contrib/nnc/libs/backend/soft/src/cpp_generator.cpp
contrib/nnc/libs/backend/soft/src/serializer.cpp
contrib/nnc/libs/backend/soft/src/soft_backend.cpp
contrib/nnc/unittests/soft_backend/generator.cpp

index 42e9825..ee23a80 100644 (file)
@@ -59,16 +59,13 @@ Assuming that current directory is a build root and we have tflite model(for exa
 ./nnc --plugins-path . \
 --input-filename inceptionv3.tflite \
 --emit-c++ \
---out-code nn.cpp \
---out-header nn.h \
---out-model param.file</p>
+--out-dir output \
+--out-name inception</p>
 ```
 
 ``--emit-c++`` is a _soft backend_ option that selects target language to emit(for now supported ``--emit-c++`` and ``--emit-c``)
 
-``--out-code nn.cpp`` is a _soft backend_ option, it sets path to source code file
+``--out-dir output`` is a _soft backend_ option, it sets path for output files, parameter is optional with default value ``out``
 
-``--out-header nn.h`` is a _soft backend_ option, it sets path to header file
-
-``--out-model param.file`` is a _soft backend_ option, it sets path to file with model parameters
+``--out-name inception`` is a _soft backend_ option, it sets name for output files(in case of c++ it is ``inception.cpp``, ``inception.h``, ``inception.params``)
 
index 17a3ca1..9825c4e 100644 (file)
@@ -22,6 +22,15 @@ class Serializer;
 class BaseCodeGenerator
 {
 public:
+
+  struct Parameters
+  {
+    // directory for output files
+    std::string _outDir;
+    // common name for output files
+    std::string _outName;
+  };
+
   void generate(nncc::contrib::core::IR::model::Graph *g);
 
 protected:
@@ -32,20 +41,16 @@ protected:
 
   BaseCodeGenerator(BaseCodeGenerator &g) = default;
 
-  // check validity of selected output files, throws appropriate exception on error
-  void checkCorrectness();
-
-  BaseCodeGenerator(const std::string &headerFile,
-      const std::string &codeFile, const std::string &modelFile);
+  BaseCodeGenerator(const Parameters &opt);
 
   std::vector<std::string> _formattedTensors;
 
-  // Code output file
-  std::string _headerFile;
-  // Code output file
-  std::string _codeFile;
-  // Model output file
-  std::string _modelFile;
+  // set of options
+  Parameters _params;
+
+  std::string _headerPath;
+  std::string _codePath;
+  std::string _paramsPath;
 };
 
 } // namespace soft
index a19afc8..5bff59a 100644 (file)
@@ -15,16 +15,13 @@ namespace soft
 // C generator
 class CCodeGenerator: public BaseCodeGenerator
 {
-  CCodeGenerator(const std::string &headerFile, const std::string &codeFile, const std::string &modelFile):
-      BaseCodeGenerator(headerFile, codeFile, modelFile)
+  CCodeGenerator(const Parameters &params): BaseCodeGenerator(params)
   {
     // EMPTY
   }
 
 public:
-  static CCodeGenerator create(const std::string &headerFile,
-                               const std::string &codeFile,
-                               const std::string &modelFile);
+  static CCodeGenerator create(const Parameters &params);
 
 protected:
   void formatTensorNames(const ModelAnalyzer &ma) override;
index 46ff6f7..eb21e76 100644 (file)
@@ -15,16 +15,13 @@ namespace soft
 // C++ generator
 class CPPCodeGenerator: public BaseCodeGenerator
 {
-  CPPCodeGenerator(const std::string &headerFile, const std::string &codeFile, const std::string &modelFile):
-      BaseCodeGenerator(headerFile, codeFile, modelFile)
+  CPPCodeGenerator(const Parameters &params): BaseCodeGenerator(params)
   {
     // EMPTY
   }
 
 public:
-  static CPPCodeGenerator create(const std::string &headerFile,
-                                 const std::string &codeFile,
-                                 const std::string &modelFile);
+  static CPPCodeGenerator create(const Parameters &params);
 
 protected:
   void formatTensorNames(const ModelAnalyzer &ma) override;
index 679f8d2..4dfdaa9 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "PluginInstance.h"
 #include "nnc/core/IR/model/graph/graph.h"
+#include "base_generator.h"
 
 #include <string>
 #include <map>
@@ -36,12 +37,10 @@ public:
   void setParam(const std::string &name, const std::string &value) override;
 
 protected:
-  std::string _outHeaderFile;
-  std::string _outCodeFile;
-  std::string _outModelFile;
-
   const std::string _pluginName;
 
+  BaseCodeGenerator::Parameters _params;
+
   // general options with some expected value
   std::map<std::string, OPT_ID> _opts;
 
index b6671e5..79785e9 100644 (file)
@@ -4,8 +4,10 @@
 #include "PluginException.h"
 #include "nnc/core/IR/model/actions/ShapeInference.h"
 
-#include <sys/types.h>
+#include "param_constants.def"
+
 #include <sys/stat.h>
+#include <sys/types.h>
 #include <unistd.h>
 
 #include <cerrno>
@@ -27,95 +29,41 @@ namespace backend
 namespace soft
 {
 
-namespace parameters_format
-{
-const int MAGIC_LEN = 4;
-const int VERSION_LEN = 4;
-const int HASH_LEN = 4;
-const int HEADER_LEN = MAGIC_LEN + VERSION_LEN + HASH_LEN;
-
-const char MAGIC[MAGIC_LEN + 1] = "NNMP"; // Neural Network Model Parameters
-}
-
 namespace
 {
 
-bool areFilesEqual(const struct stat &f1, const struct stat &f2)
+unique_ptr<ofstream> getStream(const string &path)
 {
-  return f1.st_dev == f2.st_dev && f1.st_ino == f2.st_ino;
-}
-
-bool fillFileStats(const string &path, struct stat &s)
-{
-  if (path.empty())
-    return false;
-  if (stat(path.c_str(), &s))
+  unique_ptr<ofstream> ofs(new ofstream(path));
+  if (ofs->fail())
   {
-    if (errno == ENOENT)
-    {
-      // file not found, try to create it
-      ofstream f(path);
-      if (!f.good())
-        throw PluginException("Can not create output file: " + path);
-      // try again
-      int res = stat(path.c_str(), &s);
-      (void) res;
-      // on this step everythink should be fine, since we just created file
-      assert(!res);
-      return true;
-    }
-    else
-      throw PluginException("Can not get info for file: " + path);
+    throw PluginException("Can not open code output file: " + path);
   }
-  // check that selected file is regular or char device(to accept /dev/null, and simular)
-  if (!(s.st_mode & (S_IFREG | S_IFCHR)))
-    throw PluginException("Not a regular file: " + path);
-  return true;
+  return ofs;
 }
 
-using ostream_ptr = unique_ptr<ostream, void (*)(ostream *)>;
-
-ostream_ptr getStream(const string &path)
+void createDir(const string &path)
 {
-  if (path.empty())
-    return ostream_ptr(&cout, [](ostream *){});
-  ofstream *ofs = new ofstream(path);
-  if (ofs->fail())
+  int res = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+  if (res != 0 && errno != EEXIST)
   {
-    delete ofs;
-    throw PluginException("Can not open code output file: " + path);
+    throw PluginException("Failed to create output directory");
   }
-  return ostream_ptr(ofs, [](ostream *ofs){delete ofs;});
 }
 
 } // unnamed namespace
 
-BaseCodeGenerator::BaseCodeGenerator(const string &headerFile, const string &codeFile, const string &modelFile):
-    _headerFile(headerFile), _codeFile(codeFile), _modelFile(modelFile)
+BaseCodeGenerator::BaseCodeGenerator(const Parameters &params): _params(params)
 {
-  // EMPTY
-}
-
-void BaseCodeGenerator::checkCorrectness()
-{
-  // check that header, code and model file paths refers to different files.
-  // Exception: stdout could be common output stream
-  struct stat headerStat, codeStat, modelStat;
-  bool checkHeader = fillFileStats(_headerFile, headerStat);
-  bool checkCode = fillFileStats(_codeFile, codeStat);
-  bool checkModel = fillFileStats(_modelFile, modelStat);
-
-  if ((checkHeader && checkCode && areFilesEqual(headerStat, codeStat)) ||
-      (checkHeader && checkModel && areFilesEqual(headerStat, modelStat)) ||
-      (checkCode && checkModel && areFilesEqual(codeStat, modelStat)))
-  {
-    throw PluginException("output files should not be equal");
-  }
+  string basePath = _params._outDir + "/" + _params._outName;
+  _headerPath = basePath + ".h";
+  _codePath = basePath + ".cpp";
+  _paramsPath = basePath + ".params";
 }
 
 void BaseCodeGenerator::materializeModelParams(ostream &out, const Serializer &s)
 {
-  using namespace parameters_format;
+  using namespace params;
 
   // First form a dump header
   char header[HEADER_LEN];
@@ -153,18 +101,21 @@ void BaseCodeGenerator::generate(Graph *g)
   serializer.serialize(ma.getInferenceSequence());
   // rename tensors for specific backend language
   formatTensorNames(ma);
+
+  createDir(_params._outDir);
+
   // Print header
-  auto headerStream = getStream(_headerFile);
+  auto headerStream = getStream(_headerPath);
   materializeHeader(*headerStream, ma);
   headerStream.reset();
 
   // Print code
-  auto codeStream = getStream(_codeFile);
+  auto codeStream = getStream(_codePath);
   materializeCode(*codeStream, ma, serializer);
   codeStream.reset();
 
   // Print model parameters
-  auto modelStream = getStream(_modelFile);
+  auto modelStream = getStream(_paramsPath);
   materializeModelParams(*modelStream, serializer);
   modelStream.reset();
 }
index 173799e..6bc40fa 100644 (file)
@@ -37,7 +37,7 @@ public:
 
   void generate(nncc::contrib::core::IR::model::Graph *g) override
   {
-    CCodeGenerator::create(_outHeaderFile, _outCodeFile, _outModelFile).generate(g);
+    CCodeGenerator::create(_params).generate(g);
   }
 
   static CSoftBackend &getInstance()
index 074351e..122de00 100644 (file)
@@ -14,12 +14,9 @@ namespace backend
 namespace soft
 {
 
-CCodeGenerator CCodeGenerator::create(const string &headerFile,
-                                      const string &codeFile,
-                                      const string &modelFile)
+CCodeGenerator CCodeGenerator::create(const Parameters &params)
 {
-  CCodeGenerator gen(headerFile, codeFile, modelFile);
-  gen.checkCorrectness();
+  CCodeGenerator gen(params);
   return gen;
 }
 
index a0a3ae4..8d3e5d9 100644 (file)
@@ -37,7 +37,7 @@ public:
 
   void generate(nncc::contrib::core::IR::model::Graph *g) override
   {
-    CPPCodeGenerator::create(_outHeaderFile, _outCodeFile, _outModelFile).generate(g);
+    CPPCodeGenerator::create(_params).generate(g);
   }
 
   static CPPSoftBackend &getInstance()
index e552d31..3435329 100644 (file)
@@ -3,16 +3,16 @@
 #include "serializer.h"
 #include "PluginException.h"
 
-#include "param_constants.def"
-
 using namespace std;
 using namespace nncc::contrib;
 using namespace nncc::contrib::core::IR::model;
 
 #include "cpp_header_types.h"
 #include "cpp_operations.h"
-#include "param_constants.h"
 
+#include "param_constants.def"
+
+#include "param_constants.h"
 #include "eigen.h"
 #include "cpp_common_funcs.h"
 #include "cpp_add_bias.h"
@@ -34,12 +34,9 @@ namespace backend
 namespace soft
 {
 
-CPPCodeGenerator CPPCodeGenerator::create(const std::string &headerFile,
-                                          const std::string &codeFile,
-                                          const std::string &modelFile)
+CPPCodeGenerator CPPCodeGenerator::create(const Parameters &params)
 {
-  CPPCodeGenerator gen(headerFile, codeFile, modelFile);
-  gen.checkCorrectness();
+  CPPCodeGenerator gen(params);
   return gen;
 }
 
@@ -171,7 +168,6 @@ void CPPCodeGenerator::materializeInferenceSequence(ostream &out, const ModelAna
   for (const ModelAnalyzer::OpDescr &op: ma.getInferenceSequence())
   {
     using Type = OpDescr::Type;
-    using TensorDescription = ModelAnalyzer::TensorDescription;
     if (op._type == Type::IN)
       continue;
     // create temporary tensors
@@ -199,7 +195,7 @@ void CPPCodeGenerator::materializeCode(ostream &out, const ModelAnalyzer &ma, co
 {
   string className = ma.getModelName() + "Model";
 
-  out << "#include \"" << _headerFile << "\"\n";
+  out << "#include \"" << _params._outName << ".h\"\n";
 
   // put operations from tflite
   out.write(eigen, sizeof(eigen));
index 6828238..dd682f2 100644 (file)
@@ -105,7 +105,7 @@ void Serializer::serializePads(const Op &op, uint32_t padsRank)
   // serialize pads
   assert(padsRank < MAX_DIMS);
   serializeT<char>(padsRank);
-  for (int i = 0; i < padsRank; ++i)
+  for (int i = 0; i < static_cast<int>(padsRank); ++i)
   {
     auto pad = op.getPadding(i);
     assert(pad <= MAX_DIM_SIZE);
index ca8188e..c3fa362 100644 (file)
@@ -9,6 +9,9 @@
 #include "PluginType.h"
 #include "nnc/core/IR/model/graph/graph.h"
 
+#include <sys/types.h>
+#include <dirent.h>
+
 using namespace std;
 using namespace nncc::contrib;
 using namespace nncc::contrib::config;
@@ -31,9 +34,8 @@ const string pluginVersion = "0.01";
 const string pluginDesc = "Generates source code for selected programming language from IR";
 const PluginType pluginType = typeBackEnd;
 
-const char *outCodeOpt = "out-code";
-const char *outHeaderOpt = "out-header";
-const char *outModelOpt = "out-model";
+const char *outDir = "out-dir";
+const char *outName = "out-name";
 } // unnamed namespace
 
 // Helpers
@@ -41,17 +43,15 @@ enum class OPT_ID
 {
   INVALID,
   TARGET,
-  OUT_HEADER,
-  OUT_CODE,
-  OUT_MODEL
+  OUT_DIR,
+  OUT_NAME
 };
 
 void BaseSoftBackend::fillSession()
 {
   const static map<string, string> info = {{"module description", pluginDesc}};
-  const static vector<PluginParam> moduleParams = {{outCodeOpt, "output file for code", true},
-                                                   {outHeaderOpt, "output file for header", true},
-                                                   {outModelOpt, "output file for model parameters", true}};
+  const static vector<PluginParam> moduleParams = {{outDir, "path to output directory", true},
+                                                   {outName, "common name for generated files", true}};
 
   AbstractPluginInstance::fillSessionBase(pluginType, pluginVersion, _pluginName);
 
@@ -64,7 +64,33 @@ void BaseSoftBackend::fillSession()
 
 void BaseSoftBackend::checkConfig()
 {
-  // Nothing to check for now
+  if (_params._outName.empty())
+  {
+    throw PluginException("Output file name should not be empty");
+  }
+  if (_params._outDir.empty())
+  {
+    throw PluginException("Output directory should not be empty");
+  }
+  auto dir = opendir(_params._outDir.c_str());
+  if (dir)
+  {
+    closedir(dir);
+    return;
+  }
+  auto err = errno;
+  switch (err)
+  {
+    case ENOENT:
+      return;
+    case ENOTDIR:
+      throw PluginException("Output path is not directory");
+    case EACCES:
+      throw PluginException("Has no permission to open output directory");
+    default:
+      throw PluginException("Can not open output directory");
+  }
+
 }
 
 void *BaseSoftBackend::execute(void *data)
@@ -82,9 +108,7 @@ void BaseSoftBackend::setParam(const string &name)
   switch (optionIt->second)
   {
     case OPT_ID::TARGET:
-    {
       break;
-    }
     default:
       throw ConfigException("[" + _pluginName + "] Unsupported flag parameter <" + name + ">");
   }
@@ -95,33 +119,23 @@ void BaseSoftBackend::setParam(const string &name, const string &value)
   auto optionIt = _opts.find(name);
   switch (optionIt->second)
   {
-    case OPT_ID::OUT_HEADER:
-    {
-      _outHeaderFile = value;
-      break;
-    }
-    case OPT_ID::OUT_CODE:
-    {
-      _outCodeFile = value;
+    case OPT_ID::OUT_DIR:
+      _params._outDir = value;
       break;
-    }
-    case OPT_ID::OUT_MODEL:
-    {
-      _outModelFile = value;
+    case OPT_ID::OUT_NAME:
+      _params._outName = value;
       break;
-    }
     default:
       throw ConfigException("[" + _pluginName + "] Unsupported parameter with value <" + name + ">");
   }
 }
 
 BaseSoftBackend::BaseSoftBackend(const std::string &name, const std::string &target_opt):
-        _pluginName(name)
+        _pluginName(name), _params({"out", "nnmodel"})
 {
   _flags[target_opt] = OPT_ID::TARGET;
-  _opts[outCodeOpt] = OPT_ID::OUT_CODE;
-  _opts[outHeaderOpt] = OPT_ID::OUT_HEADER;
-  _opts[outModelOpt] = OPT_ID::OUT_MODEL;
+  _opts[outDir] = OPT_ID::OUT_DIR;
+  _opts[outName] = OPT_ID::OUT_NAME;
 }
 
 } // namespace soft
index 0aeb150..62600b2 100644 (file)
 #include <fcntl.h>
 #include <unistd.h>
 #include <cstdio>
+#include <ftw.h>
 
 using namespace std;
 using namespace nncc::contrib;
 using namespace nncc::contrib::backend::soft;
+using namespace nncc::contrib::core::IR::model;
 
 static bool isFileExists(const string &path)
 {
@@ -21,76 +23,76 @@ static bool isFileExists(const string &path)
   return f.good();
 }
 
-static void createFile(const string &path)
+static void deleteFile(const string &path)
 {
-  assert(!isFileExists(path));
-  ofstream f(path);
-  assert(f.good());
-  f << "hello world\n";
+  int res = remove(path.c_str());
+  assert(!res && "failed to remove file");
 }
 
-static void createDir(const string &path)
+int removeRec(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
 {
-  int res = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
-  assert(!res);
+  deleteFile(fpath);
+  return 0;
 }
 
-static void deleteFile(const string &path)
+static void deleteDir(const string &path)
 {
-  int res = remove(path.c_str());
-  assert(!res && "failed to remove file");
+  int res = nftw(path.c_str(), removeRec, 1, FTW_DEPTH | FTW_PHYS);
+  assert(!res && "failed to remove dir");
 }
 
-static void deleteDir(const string &path)
+static void checkOutputExists(const string &commonPath)
 {
-  int res = rmdir(path.c_str());
-  assert(!res && "failed to remove dir");
+  ASSERT_TRUE(isFileExists(commonPath + ".h"));
+  ASSERT_TRUE(isFileExists(commonPath + ".cpp"));
+  ASSERT_TRUE(isFileExists(commonPath + ".params"));
+}
+
+static void emptyFile(const string &path)
+{
+  ofstream of(path);
 }
 
-TEST(Generator, check_path_consistency)
+TEST(Generator, check_generator_call)
 {
   // assume here that c++ and c code generators behave identically in terms of parameters check
   // test only c++ generator
-  const char *TEST_FILE1 = "test_file1";
-  const char *TEST_FILE2 = "test_file2";
-  const char *TEST_DIR = "test_dir";
-
-  // iterate this arrays to check that all arguments are working equally
-  const char *fileNames1[] = {"", "" , TEST_FILE1, "", ""};
-  const char *fileNames2[] = {TEST_FILE1, TEST_FILE1, "", TEST_FILE1, TEST_FILE1};
-  const char *fileNames3[] = {TEST_FILE1, TEST_FILE2, "", TEST_FILE1, TEST_FILE2};
-  const char *dirNames[] = {"", "" , TEST_DIR, "", ""};
-
-  assert(!isFileExists(TEST_DIR) && "remove test_dir");
-  assert(!isFileExists(TEST_FILE2) && "remove test_file2");
-  createDir(TEST_DIR);
-  for (int i = 0; i < 3; ++i)
+  #define TEST_DIR "out_dir"
+  #define TEST_NAME "someName"
+  #define BASE_NAME TEST_DIR "/" TEST_NAME
+
+  nncc::contrib::core::IR::model::Graph g;
+  g.create<ops::VariableOp>("input");
+
+  // test that generator creates output dir and files
+  if (isFileExists(TEST_DIR))
   {
-    // check that test file is created
-    assert(!isFileExists(TEST_FILE1) && "remove test_file1");
-    CPPCodeGenerator::create(fileNames1[i], fileNames1[i + 1], fileNames1[i + 2]);
-    ASSERT_TRUE(isFileExists(TEST_FILE1));
-    // file already exists
-    struct stat s1;
-    int res = stat(TEST_FILE1, &s1);
-    assert(!res);
-    CPPCodeGenerator::create(fileNames1[i], fileNames1[i + 1], fileNames1[i + 2]);
-    struct stat s2;
-    res = stat(TEST_FILE1, &s2);
-    assert(!res);
-    deleteFile(TEST_FILE1);
-    // check that file is not changed at this point
-    ASSERT_EQ(s1.st_ino, s2.st_ino);
-    ASSERT_EQ(s1.st_size, s2.st_size);
-    // check that dir is not valid output
-    EXPECT_THROW(CPPCodeGenerator::create(dirNames[i], dirNames[i + 1], dirNames[i + 2]), PluginException);
-    // check that equal files are not permitted
-    EXPECT_THROW(CPPCodeGenerator::create(fileNames2[i], fileNames2[i + 1], fileNames2[i + 2]), PluginException);
-    // check that different files are ok
-    CPPCodeGenerator::create(fileNames3[i], fileNames3[i + 1], fileNames3[i + 2]);
-    deleteFile(TEST_FILE1);
+    deleteDir(TEST_DIR);
   }
+  assert(!isFileExists(TEST_DIR) && "remove output dir");
+  CPPCodeGenerator::create(BaseCodeGenerator::Parameters({TEST_DIR, TEST_NAME})).generate(&g);
+  checkOutputExists(BASE_NAME);
+
+  // test that generator creates output files in existing empty dir
+  deleteFile(BASE_NAME ".h");
+  deleteFile(BASE_NAME ".cpp");
+  deleteFile(BASE_NAME ".params");
+  CPPCodeGenerator::create(BaseCodeGenerator::Parameters({TEST_DIR, TEST_NAME})).generate(&g);
+  checkOutputExists(BASE_NAME);
+
+  // test that generator rewrites existing files
+  emptyFile(BASE_NAME ".h");
+  struct stat sBefore, sAfter;
+  int res = stat(BASE_NAME ".h", &sBefore);
+  assert(res == 0);
+  assert(sBefore.st_size == 0);
+
+  CPPCodeGenerator::create(BaseCodeGenerator::Parameters({TEST_DIR, TEST_NAME})).generate(&g);
+
+  res = stat(BASE_NAME ".h", &sAfter);
+  assert(res == 0);
+
+  ASSERT_NE(sBefore.st_size, sAfter.st_size);
+
   deleteDir(TEST_DIR);
-  deleteFile(TEST_FILE2);
 }
-