AI4C/0001-Add-batch-inference-feature-and-optimizer-model.patch

1041 lines
48 KiB
Diff

From 3c20ee014b27db29c3457b8b2d48821f30b11752 Mon Sep 17 00:00:00 2001
From: liufeiyang <liufeiyang6@huawei.com>
Date: Fri, 21 Jun 2024 11:42:59 +0800
Subject: [PATCH] Add batch inference feature and optimizer model.
---
aiframe/CMakeLists.txt | 8 +-
aiframe/ONNXRunner.cpp | 353 ++++++++++++++++++++++-------------
aiframe/include/ONNXRunner.h | 244 +++++++++++++++++++-----
models/optimizer.onnx | Bin 0 -> 41037 bytes
4 files changed, 426 insertions(+), 179 deletions(-)
create mode 100644 models/optimizer.onnx
diff --git a/aiframe/CMakeLists.txt b/aiframe/CMakeLists.txt
index 8ece1ce6..9f8022f5 100644
--- a/aiframe/CMakeLists.txt
+++ b/aiframe/CMakeLists.txt
@@ -4,8 +4,6 @@ project(ONNXRunner)
set(CMAKE_CXX_STANDARD 17)
-#set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall")
-
set(INC_DIR /usr/include)
set(INC_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/include)
set(LIB_DIR /usr/lib64)
@@ -18,6 +16,6 @@ link_directories(${LIB_DIR})
add_library(ONNXRunner SHARED ONNXRunner.cpp)
-#target_link_libraries(ONNXRunner
-#PRIVATE
-#libonnxruntime.so)
+target_link_libraries(ONNXRunner
+PRIVATE
+libcrypto.so) # libonnxruntime.so
diff --git a/aiframe/ONNXRunner.cpp b/aiframe/ONNXRunner.cpp
index 3ec159c7..3aefae33 100644
--- a/aiframe/ONNXRunner.cpp
+++ b/aiframe/ONNXRunner.cpp
@@ -1,151 +1,244 @@
-#include <iostream>
-#include <vector>
+#include "include/ONNXRunner.h"
#include <algorithm>
+#include <iostream>
#include <numeric>
-#include "include/ONNXRunner.h"
+#include <vector>
-namespace boltONNXRunner{
+namespace compilerONNXRunner {
-Ort::Value ONNXRunner::getInputValueFloat(Ort::Session *session,
+Ort::Value ONNXRunner::getInputValueFloat(Ort::Session *session,
std::vector<float> &input,
- int inputIdx) {
- auto typeInfo = session->GetInputTypeInfo(inputIdx);
- auto tensorInfo = typeInfo.GetTensorTypeAndShapeInfo();
- auto inputDims = tensorInfo.GetShape();
- std::replace_if(
- inputDims.begin(), inputDims.end(), [](int64_t &i) { return i < 0; }, 1);
-
- size_t inputTensorSize = std::accumulate(inputDims.begin(), inputDims.end(),
- 1, std::multiplies<int>());
- auto memory_info =
- Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
- auto inputTmp = Ort::Value::CreateTensor<float>(
- memory_info, input.data(), inputTensorSize, inputDims.data(),
- inputDims.size());
- auto inputTensor = &inputTmp;
- return inputTmp;
+ int inputIdx, int batchSize) {
+ auto typeInfo = session->GetInputTypeInfo(inputIdx);
+ auto tensorInfo = typeInfo.GetTensorTypeAndShapeInfo();
+ auto inputDims = tensorInfo.GetShape();
+ std::replace_if(
+ inputDims.begin(), inputDims.end(), [](int64_t &i) { return i < 0; }, 1);
+
+ size_t inputTensorSize = std::accumulate(inputDims.begin(), inputDims.end(),
+ 1, std::multiplies<int>());
+ // try to add batch size
+ inputDims[0] = batchSize;
+ inputTensorSize = inputTensorSize * batchSize;
+ auto memoryInfo =
+ Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
+ auto inputTmp =
+ Ort::Value::CreateTensor<float>(memoryInfo, input.data(), inputTensorSize,
+ inputDims.data(), inputDims.size());
+ auto inputTensor = &inputTmp;
+ return inputTmp;
}
-Ort::Value ONNXRunner::getInputValueString(Ort::AllocatorWithDefaultOptions allocator,
- Ort::Session *session,
- std::vector<std::string> &input,
- int inputIdx) {
- auto typeInfo = session->GetInputTypeInfo(inputIdx);
- auto tensorInfo = typeInfo.GetTensorTypeAndShapeInfo();
- auto inputDims = tensorInfo.GetShape();
-
- std::replace_if(
- inputDims.begin(), inputDims.end(), [](int64_t &i) { return i < 0; }, 1);
-
- size_t inputTensorSize = std::accumulate(inputDims.begin(), inputDims.end(),
- 1, std::multiplies<int>());
- const char* input_strings[inputTensorSize];
- for(int i = 0; i < inputTensorSize; i++) {
- input_strings[i] = input[i].c_str();
- }
-
- auto memory_info =
- Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
- auto inputTmp = Ort::Value::CreateTensor(allocator, inputDims.data(),
- inputDims.size(),
- ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING);
- inputTmp.FillStringTensor(input_strings, inputTensorSize);
- auto inputTensor = &inputTmp;
- return inputTmp;
+Ort::Value ONNXRunner::getInputValueString(
+ Ort::AllocatorWithDefaultOptions allocator, Ort::Session *session,
+ std::vector<std::string> &input, int inputIdx, int batchSize) {
+ auto typeInfo = session->GetInputTypeInfo(inputIdx);
+ auto tensorInfo = typeInfo.GetTensorTypeAndShapeInfo();
+ auto inputDims = tensorInfo.GetShape();
+
+ std::replace_if(
+ inputDims.begin(), inputDims.end(), [](int64_t &i) { return i < 0; }, 1);
+
+ size_t inputTensorSize = std::accumulate(inputDims.begin(), inputDims.end(),
+ 1, std::multiplies<int>());
+ inputDims[0] = batchSize;
+ inputTensorSize = inputTensorSize * batchSize;
+ const char *inputStrings[inputTensorSize];
+ for (int i = 0; i < inputTensorSize; i++) {
+ inputStrings[i] = input[i].c_str();
+ }
+
+ auto memoryInfo =
+ Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
+ auto inputTmp =
+ Ort::Value::CreateTensor(allocator, inputDims.data(), inputDims.size(),
+ ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING);
+ inputTmp.FillStringTensor(inputStrings, inputTensorSize);
+ auto inputTensor = &inputTmp;
+ return inputTmp;
}
-Ort::Value ONNXRunner::getInputValueInt64(Ort::Session *session,
+Ort::Value ONNXRunner::getInputValueInt64(Ort::Session *session,
std::vector<int64_t> &input,
- int inputIdx) {
- auto typeInfo = session->GetInputTypeInfo(inputIdx);
- auto tensorInfo = typeInfo.GetTensorTypeAndShapeInfo();
- auto inputDims = tensorInfo.GetShape();
- std::replace_if(
- inputDims.begin(), inputDims.end(), [](int64_t &i) { return i < 0; }, 1);
-
- size_t inputTensorSize = std::accumulate(inputDims.begin(), inputDims.end(),
- 1, std::multiplies<int>());
- auto memory_info =
- Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
- auto inputTmp = Ort::Value::CreateTensor<int64_t>(
- memory_info, input.data(), inputTensorSize, inputDims.data(),
- inputDims.size());
- auto inputTensor = &inputTmp;
- return inputTmp;
+ int inputIdx, int batchSize) {
+ auto typeInfo = session->GetInputTypeInfo(inputIdx);
+ auto tensorInfo = typeInfo.GetTensorTypeAndShapeInfo();
+ auto inputDims = tensorInfo.GetShape();
+ std::replace_if(
+ inputDims.begin(), inputDims.end(), [](int64_t &i) { return i < 0; }, 1);
+
+ size_t inputTensorSize = std::accumulate(inputDims.begin(), inputDims.end(),
+ 1, std::multiplies<int>());
+ inputDims[0] = batchSize;
+ inputTensorSize = inputTensorSize * batchSize;
+ auto memoryInfo =
+ Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
+ auto inputTmp = Ort::Value::CreateTensor<int64_t>(
+ memoryInfo, input.data(), inputTensorSize, inputDims.data(),
+ inputDims.size());
+ auto inputTensor = &inputTmp;
+ return inputTmp;
}
-float ONNXRunner::runONNXModel(std::vector<std::string> input_string, std::vector<int64_t> input_int64, std::vector<float> input_float){
- Ort::AllocatorWithDefaultOptions allocator;
-
- //Try to get input;
- int input_count = session->GetInputCount();
-
- //Get input name
- std::vector<std::string> inputNameList;
- for (int i = 0; i < input_count; i++) {
- auto inputName = session->GetInputNameAllocated(i, allocator);
- auto inputNameStr = inputName.get();
- inputNameList.push_back(inputNameStr);
- }
-
- //Form input tensor(s)
- std::vector<Ort::Value> input_final;
- std::vector<const char *> inputNameStr_final;
-
- int currentIdx = 0;
- if(!input_string.empty()) {
- input_final.push_back(getInputValueString(allocator, session, input_string, currentIdx));
- currentIdx ++;
- }
-
- if(!input_int64.empty()) {
- input_final.push_back(getInputValueInt64(session, input_int64, currentIdx));
- currentIdx ++;
- }
-
- if(!input_float.empty()) {
- input_final.push_back(getInputValueFloat(session, input_float, currentIdx));
- currentIdx ++;
+std::vector<float>
+ONNXRunner::runONNXModel(std::vector<std::string> inputString,
+ std::vector<int64_t> inputInt64,
+ std::vector<float> inputFloat, int batchSize) {
+ Ort::AllocatorWithDefaultOptions allocator;
+
+ // Get input count
+ int inputCount = session->GetInputCount();
+
+ // Get input name
+ std::vector<std::string> inputNameList;
+ for (int i = 0; i < inputCount; i++) {
+ auto inputName = session->GetInputNameAllocated(i, allocator);
+ auto inputNameStr = inputName.get();
+ inputNameList.push_back(inputNameStr);
+ }
+
+ // Form input tensor(s)
+ std::vector<Ort::Value> inputFinal;
+ std::vector<const char *> inputNameStrFinal;
+ int currentIdx = 0;
+ if (!inputString.empty()) {
+ inputFinal.push_back(getInputValueString(allocator, session, inputString,
+ currentIdx, batchSize));
+ currentIdx++;
+ }
+
+ if (!inputInt64.empty()) {
+ inputFinal.push_back(
+ getInputValueInt64(session, inputInt64, currentIdx, batchSize));
+ currentIdx++;
+ }
+
+ if (!inputFloat.empty()) {
+ inputFinal.push_back(
+ getInputValueFloat(session, inputFloat, currentIdx, batchSize));
+ currentIdx++;
+ }
+
+ for (int i = 0; i < inputCount; i++) {
+ inputNameStrFinal.push_back(inputNameList[i].c_str());
+ }
+
+ // Run model
+ int outputCount = session->GetOutputCount();
+ std::vector<std::string> outputNameList;
+ for (int i = 0; i < outputCount; i++) {
+ auto outputName = session->GetOutputNameAllocated(i, allocator);
+ std::string outputNameStr = outputName.get();
+ if (!outputNameStr.empty()) {
+ outputNameList.push_back(outputNameStr);
+ } else {
+ std::string outputNameDefault = "Output_" + std::to_string(i);
+ outputNameList.push_back(outputNameDefault);
}
+ }
+
+ std::vector<const char *> outputNameStrFinal;
+ for (int i = 0; i < outputCount; i++) {
+ outputNameStrFinal.push_back(outputNameList[i].c_str());
+ }
+
+ auto outputTensors = session->Run(
+ Ort::RunOptions{nullptr}, inputNameStrFinal.data(), inputFinal.data(),
+ inputCount, outputNameStrFinal.data(), outputCount);
+
+ // Get result and return
+ std::vector<float> probs;
+ float *outputProbability = outputTensors[0].GetTensorMutableData<float>();
+ for (int i = 0; i < batchSize; i++) {
+ Ort::Value mapOut =
+ outputTensors[1].GetValue(static_cast<int>(i), allocator);
+ Ort::Value keysOrt = mapOut.GetValue(0, allocator);
+ int64_t *keysRet = keysOrt.GetTensorMutableData<int64_t>();
+ Ort::Value valuesOrt = mapOut.GetValue(1, allocator);
+ float *valuesRet = valuesOrt.GetTensorMutableData<float>();
+ probs.push_back((*(valuesRet + 1)));
+ }
+
+ return probs;
+}
- for (int i = 0; i < input_count; i++) {
- inputNameStr_final.push_back(inputNameList[i].c_str());
+int64_t ONNXRunner::runONNXModelOptimizer(std::vector<std::string> inputString,
+ std::vector<int64_t> inputInt64,
+ std::vector<float> inputFloat,
+ int batchSize) {
+ Ort::AllocatorWithDefaultOptions allocator;
+
+ // Get input count
+ int inputCount = session->GetInputCount();
+ std::vector<int64_t> inputInt64Tensor(FEATURE_SIZE_INT64_OPT);
+ std::vector<std::string> inputStringTensor;
+ std::vector<Ort::Value> inputFinal;
+
+ inputInt64.clear();
+ inputInt64.resize(FEATURE_SIZE_INT64_OPT);
+ for (int i = 0; i < FEATURE_SIZE_INT64_OPT; i++) {
+ auto inputName = session->GetInputNameAllocated(i, allocator);
+ auto inputNameStr = inputName.get();
+
+ inputInt64Tensor.clear();
+ inputInt64Tensor.push_back(inputInt64[i]);
+ inputFinal.push_back(
+ getInputValueInt64(session, inputInt64Tensor, i, batchSize));
+ }
+
+ for (int i = FEATURE_SIZE_INT64_OPT;
+ i < FEATURE_SIZE_INT64_OPT + FEATURE_SIZE_STRING_OPT; i++) {
+ inputStringTensor.clear();
+ inputStringTensor.push_back(inputString[i - FEATURE_SIZE_INT64_OPT]);
+ inputFinal.push_back(getInputValueString(allocator, session,
+ inputStringTensor, i, batchSize));
+ }
+
+ // Get input name from model
+ std::vector<std::string> inputNameList;
+ for (int i = 0; i < inputCount; i++) {
+ auto inputName = session->GetInputNameAllocated(i, allocator);
+ auto inputNameStr = inputName.get();
+ inputNameList.push_back(inputNameStr);
+ }
+
+ // Form input tensor(s)
+ std::vector<const char *> inputNameStrFinal;
+ for (int i = 0; i < inputCount; i++) {
+ inputNameStrFinal.push_back(inputNameList[i].c_str());
+ }
+
+ // Run model
+ int outputCount = session->GetOutputCount();
+ std::vector<std::string> outputNameList;
+ for (int i = 0; i < outputCount; i++) {
+ auto outputName = session->GetOutputNameAllocated(i, allocator);
+ std::string outputNameStr = outputName.get();
+ if (!outputNameStr.empty()) {
+ outputNameList.push_back(outputNameStr);
+ } else {
+ std::string outputNameDefault = "Output_" + std::to_string(i);
+ outputNameList.push_back(outputNameDefault);
}
+ }
- //Run the model
- int output_count = session->GetOutputCount();
- std::vector<std::string> outputNameList;
- for (int i = 0; i < output_count; i++) {
- auto outputName = session->GetOutputNameAllocated(i, allocator);
- std::string outputNameStr = outputName.get();
- if(!outputNameStr.empty()) {
- outputNameList.push_back(outputNameStr);
- } else {
- std::string outputNameDefault = "Output_" + std::to_string(i);
- outputNameList.push_back(outputNameDefault);
- }
- }
+ std::vector<const char *> outputNameStrFinal;
+ for (int i = 0; i < outputCount; i++) {
+ outputNameStrFinal.push_back(outputNameList[i].c_str());
+ }
- std::vector<const char *> outputNameStr_final;
- for(int i = 0; i < output_count; i++) {
- outputNameStr_final.push_back(outputNameList[i].c_str());
- }
-
- auto outputTensors =
- session->Run(Ort::RunOptions{nullptr}, inputNameStr_final.data(),
- input_final.data(), input_count, outputNameStr_final.data(), output_count);
+ auto outputTensors = session->Run(
+ Ort::RunOptions{nullptr}, inputNameStrFinal.data(), inputFinal.data(),
+ inputCount, outputNameStrFinal.data(), outputCount);
- //Try to get the result & return
- float* output_probability = outputTensors[0].GetTensorMutableData<float>();
- Ort::Value map_out = outputTensors[1].GetValue(static_cast<int>(0), allocator);
+ // Get result and return
+ int64_t label = 0;
+ for (int i = 0; i < batchSize; i++) {
+ int64_t *outputLabel = outputTensors[0].GetTensorMutableData<int64_t>();
+ label = *outputLabel;
+ }
- Ort::Value keys_ort = map_out.GetValue(0, allocator);
- int64_t* keys_ret = keys_ort.GetTensorMutableData<int64_t>();
- Ort::Value values_ort = map_out.GetValue(1, allocator);
- float* values_ret = values_ort.GetTensorMutableData<float>();
-
- return *(values_ret + 1);
+ return label;
}
-} // namespace boltONNXRunner
-
+} // namespace compilerONNXRunner
diff --git a/aiframe/include/ONNXRunner.h b/aiframe/include/ONNXRunner.h
index 54bf341e..bc8535f1 100644
--- a/aiframe/include/ONNXRunner.h
+++ b/aiframe/include/ONNXRunner.h
@@ -1,64 +1,220 @@
-#ifndef BOLT_PROFILE_ONNXRUNNER_H
-#define BOLT_PROFILE_ONNXRUNNER_H
+#ifndef COMPILER_PROFILE_ONNXRUNNER_H
+#define COMPILER_PROFILE_ONNXRUNNER_H
-#include <iostream>
-#include <vector>
-#include <algorithm>
-#include <numeric>
#include "onnxruntime_c_api.h"
#include "onnxruntime_cxx_api.h"
+#include <algorithm>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <numeric>
+#include <openssl/sha.h>
+#include <sstream>
+#include <string>
+#include <vector>
extern "C" {
-namespace boltONNXRunner {
+namespace compilerONNXRunner {
+
+const char* MODEL_PATH_OPT = "/usr/lib64/AI4C/optimizer.onnx";
+const int FEATURE_SIZE_INT64_OPT = 6;
+const int FEATURE_SIZE_STRING_OPT = 11;
+
class ONNXRunner {
public:
- explicit ONNXRunner() {}
- explicit ONNXRunner(const char* model_path) {
- // prepare model and env
- session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_BASIC);
- session = new Ort::Session(env, model_path, session_options);
- }
- ~ONNXRunner() {
- delete session;
- }
- float runONNXModel(std::vector<std::string> input_string, std::vector<int64_t> input_int64, std::vector<float> input_float);
+ explicit ONNXRunner() {}
+ explicit ONNXRunner(const char *modelPath) {
+ // Prepare model and env
+ sessionOptions.SetGraphOptimizationLevel(
+ GraphOptimizationLevel::ORT_ENABLE_BASIC);
+ session = new Ort::Session(env, modelPath, sessionOptions);
+ }
+ ~ONNXRunner() { delete session; }
+ std::vector<float> runONNXModel(std::vector<std::string> inputString,
+ std::vector<int64_t> inputInt64,
+ std::vector<float> inputFloat, int batchSize);
+ int64_t runONNXModelOptimizer(std::vector<std::string> inputString,
+ std::vector<int64_t> inputInt64,
+ std::vector<float> inputFloat, int batchSize);
private:
- static Ort::Value getInputValueFloat(Ort::Session *session,
- std::vector<float> &input,
- int inputIdx);
-
- static Ort::Value getInputValueString(Ort::AllocatorWithDefaultOptions allocator,
- Ort::Session *session,
- std::vector<std::string> &input,
- int inputIdx);
-
- static Ort::Value getInputValueInt64(Ort::Session *session,
- std::vector<int64_t> &input,
- int inputIdx);
-
- Ort::SessionOptions session_options;
- Ort::Env env{ORT_LOGGING_LEVEL_WARNING, "test"};
- Ort::Session *session;
+ static Ort::Value getInputValueFloat(Ort::Session *session,
+ std::vector<float> &input, int inputIdx,
+ int batchSize);
+
+ static Ort::Value
+ getInputValueString(Ort::AllocatorWithDefaultOptions allocator,
+ Ort::Session *session, std::vector<std::string> &input,
+ int inputIdx, int batchSize);
+
+ static Ort::Value getInputValueInt64(Ort::Session *session,
+ std::vector<int64_t> &input,
+ int inputIdx, int batchSize);
+
+ Ort::SessionOptions sessionOptions;
+ Ort::Env env{ORT_LOGGING_LEVEL_WARNING, "test"};
+ Ort::Session *session;
};
-extern ONNXRunner* createONNXRunner(const char* model_path) {
- return new ONNXRunner(model_path);
+extern ONNXRunner *createONNXRunner(const char *modelPath) {
+ std::ifstream file(modelPath);
+ if (file.good()) {
+ return new ONNXRunner(modelPath);
+ } else {
+ return nullptr;
+ }
+}
+
+extern void deleteONNXRunner(ONNXRunner *instance) {
+ if (instance != nullptr) {
+ delete instance;
+ }
+}
+
+extern std::vector<float> runONNXModel(ONNXRunner *instance,
+ std::vector<std::string> inputString,
+ std::vector<int64_t> inputInt64,
+ std::vector<float> inputFloat,
+ int batchSize) {
+ std::vector<float> nullResult;
+ if (instance != nullptr) {
+ return instance->runONNXModel(inputString, inputInt64, inputFloat,
+ batchSize);
+ } else {
+ return nullResult;
+ }
+}
+
+static bool startsWithPatt(const std::string &str, const std::string &pattern) {
+ return str.rfind(pattern, 0) == 0;
}
-extern void deleteONNXRunner(ONNXRunner* instance) {
- if (instance != nullptr) {
- delete instance;
+static bool optionComparator(const std::string &str1, const std::string &str2) {
+ for (size_t i = 0; i < str1.size() && i < str2.size(); ++i) {
+ char c1 = str1[i];
+ char c2 = str2[i];
+ if (std::isupper(c1) && !std::isupper(c2)) {
+ return 1;
+ } else if (!std::isupper(c1) && std::isupper(c2)) {
+ return 0;
+ } else if (c1 != c2) {
+ return c1 > c2;
}
+ }
+
+ return str1.size() < str2.size();
+}
+
+static void truncatePrefix(const std::string &str, std::string &strPrefix) {
+ const char prefixIndicator = '_';
+ size_t idx = str.find(prefixIndicator);
+ if (idx == std::string::npos)
+ strPrefix = str;
+ else
+ strPrefix = str.substr(0, idx + 1);
+}
+
+static std::string encodeStringFeature(const std::string &str) {
+ unsigned char hash[SHA256_DIGEST_LENGTH];
+ SHA256_CTX sha256;
+ SHA256_Init(&sha256);
+ SHA256_Update(&sha256, str.c_str(), str.size());
+ SHA256_Final(hash, &sha256);
+
+ std::stringstream ss;
+ for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
+ ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
+ }
+ return ss.str();
}
-extern float runONNXModel(ONNXRunner* instance, std::vector<std::string> input_string, std::vector<int64_t> input_int64, std::vector<float> input_float) {
- if (instance != nullptr) {
- return instance->runONNXModel(input_string, input_int64, input_float);
- } else {
- return -1;
+static void preprocessData(std::vector<std::string> &inputString,
+ std::vector<int64_t> &inputInt64, int argcSW,
+ const char **argvSW, const char *mcpuOption,
+ int argcHW, int64_t *argvHW) {
+ const char *outputOption = "-o";
+ const char *macroPrefix = "-D";
+ const char *needle = "--param";
+ const char *flagPrefix = "-";
+ const char *defaultOption = "-fdefault-option";
+ const int defaultIntVal = 0;
+
+ // Preprocessing string features.
+ std::string outputFile = "";
+ std::vector<std::string> inputStringRaw = {std::string(mcpuOption)};
+ std::vector<std::string> macroOptions;
+ for (int i = 0; i < argcSW; ++i) {
+ std::string opt = std::string(argvSW[i]);
+ if (startsWithPatt(opt, macroPrefix)) {
+ macroOptions.push_back(opt);
+ }
+ if (i + 1 < argcSW && opt.compare(outputOption) == 0) {
+ truncatePrefix(std::string(argvSW[i + 1]), outputFile);
+ }
+ }
+ inputStringRaw.push_back(outputFile);
+
+ std::sort(macroOptions.begin(), macroOptions.end(), optionComparator);
+ for (size_t i = 0; i < macroOptions.size() &&
+ (int)inputStringRaw.size() < FEATURE_SIZE_STRING_OPT;
+ ++i) {
+ inputStringRaw.push_back(macroOptions[i]);
+ }
+
+ for (int i = 0;
+ i < argcSW && (int)inputStringRaw.size() < FEATURE_SIZE_STRING_OPT;
+ ++i) {
+ std::string opt = std::string(argvSW[i]);
+ if (!startsWithPatt(opt, macroPrefix) && !startsWithPatt(opt, needle) &&
+ startsWithPatt(opt, flagPrefix)) {
+ inputStringRaw.push_back(opt);
}
+ }
+
+ for (int i = (int)inputStringRaw.size(); i < FEATURE_SIZE_STRING_OPT; ++i) {
+ inputStringRaw.push_back(defaultOption);
+ }
+ for (size_t i = 0; i < inputStringRaw.size(); ++i) {
+ inputString.push_back(encodeStringFeature(inputStringRaw[i]));
+ }
+
+ // Preprocessing int64 features.
+ for (int i = 0; i < argcHW && i < FEATURE_SIZE_INT64_OPT; ++i) {
+ inputInt64.push_back(argvHW[i]);
+ }
+
+ for (int i = (int)inputInt64.size(); i < FEATURE_SIZE_INT64_OPT; ++i) {
+ inputInt64.push_back(defaultIntVal);
+ }
+}
+
+extern int64_t runONNXModelOptimizer(int argcSW, const char **argvSW,
+ const char *mcpuOption, int argcHW,
+ int64_t *argvHW) {
+ // Create model runner.
+ ONNXRunner *instance = createONNXRunner(MODEL_PATH_OPT);
+ if (instance == nullptr) {
+ return -1;
+ }
+
+ // Preprocess data.
+ std::vector<std::string> inputString;
+ std::vector<int64_t> inputInt64;
+ std::vector<float> inputFloat;
+ preprocessData(inputString, inputInt64, argcSW, argvSW, mcpuOption, argcHW,
+ argvHW);
+
+ // Run model.
+ int64_t output;
+ if (instance != nullptr) {
+ output =
+ instance->runONNXModelOptimizer(inputString, inputInt64, inputFloat, 1);
+ }
+
+ // Delete model runner.
+ deleteONNXRunner(instance);
+ return output;
}
-} // namespace boltONNXRunner
+} // namespace compilerONNXRunner
} // extern "C"
#endif
diff --git a/models/optimizer.onnx b/models/optimizer.onnx
new file mode 100644
index 0000000000000000000000000000000000000000..8218b0c2f589a4fd6d9010e67f4e23dab6358f0e
GIT binary patch
literal 41037
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
literal 0
HcmV?d00001
--
2.33.0