./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``)
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:
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
// 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 ¶ms): BaseCodeGenerator(params)
{
// EMPTY
}
public:
- static CCodeGenerator create(const std::string &headerFile,
- const std::string &codeFile,
- const std::string &modelFile);
+ static CCodeGenerator create(const Parameters ¶ms);
protected:
void formatTensorNames(const ModelAnalyzer &ma) override;
// 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 ¶ms): BaseCodeGenerator(params)
{
// EMPTY
}
public:
- static CPPCodeGenerator create(const std::string &headerFile,
- const std::string &codeFile,
- const std::string &modelFile);
+ static CPPCodeGenerator create(const Parameters ¶ms);
protected:
void formatTensorNames(const ModelAnalyzer &ma) override;
#include "PluginInstance.h"
#include "nnc/core/IR/model/graph/graph.h"
+#include "base_generator.h"
#include <string>
#include <map>
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;
#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>
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 ¶ms): _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];
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();
}
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()
namespace soft
{
-CCodeGenerator CCodeGenerator::create(const string &headerFile,
- const string &codeFile,
- const string &modelFile)
+CCodeGenerator CCodeGenerator::create(const Parameters ¶ms)
{
- CCodeGenerator gen(headerFile, codeFile, modelFile);
- gen.checkCorrectness();
+ CCodeGenerator gen(params);
return gen;
}
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()
#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"
namespace soft
{
-CPPCodeGenerator CPPCodeGenerator::create(const std::string &headerFile,
- const std::string &codeFile,
- const std::string &modelFile)
+CPPCodeGenerator CPPCodeGenerator::create(const Parameters ¶ms)
{
- CPPCodeGenerator gen(headerFile, codeFile, modelFile);
- gen.checkCorrectness();
+ CPPCodeGenerator gen(params);
return gen;
}
for (const ModelAnalyzer::OpDescr &op: ma.getInferenceSequence())
{
using Type = OpDescr::Type;
- using TensorDescription = ModelAnalyzer::TensorDescription;
if (op._type == Type::IN)
continue;
// create temporary tensors
{
string className = ma.getModelName() + "Model";
- out << "#include \"" << _headerFile << "\"\n";
+ out << "#include \"" << _params._outName << ".h\"\n";
// put operations from tflite
out.write(eigen, sizeof(eigen));
// 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);
#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;
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
{
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);
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)
switch (optionIt->second)
{
case OPT_ID::TARGET:
- {
break;
- }
default:
throw ConfigException("[" + _pluginName + "] Unsupported flag parameter <" + name + ">");
}
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
#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)
{
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);
}
-