Files
2026-07-13 13:33:03 +08:00

694 lines
28 KiB
C++

//
// onnxOpConverter.cpp
// MNNConverter
//
// Created by MNN on 2019/01/31.
// Copyright © 2018, Alibaba Group Holding Limited
//
#include <queue>
#include "onnxOpConverter.hpp"
#include "OpCount.hpp"
#include "OnnxTmpGraph.hpp"
#include "core/FileLoader.hpp"
#include "core/MNNFileUtils.h"
using namespace MNN;
static int32_t _limit(int64_t i64) {
if (i64 > (int64_t)(1 << 30)) {
return 1 << 30;
}
if (i64 < (int64_t)(-(1 << 30))) {
return (-(1 << 30));
}
return i64;
}
std::vector<int> OnnxScope::topoSort(const onnx::GraphProto& onnxGraph) {
std::vector<int> idxMap;
const int nodeCount = onnxGraph.node_size();
std::map<std::string, int> outputMap;
std::map<int, std::vector<int>> graph; // key --[in]--> values
std::vector<int> inDegree(nodeCount);
// build Graph and inDegree
for (int i = 0; i < nodeCount; ++i) {
const auto& onnxNode = onnxGraph.node(i);
for (int k = 0; k < onnxNode.output_size(); k++) {
outputMap.insert(std::make_pair(onnxNode.output(k), i));
}
}
for (int i = 0; i < nodeCount; ++i) {
const auto& onnxNode = onnxGraph.node(i);
for (int k = 0; k < onnxNode.input_size(); k++) {
auto inputName = onnxNode.input(k);
auto iter = outputMap.find(inputName);
if (iter != outputMap.end()) {
graph[iter->second].push_back(i);
}
}
if (onnxNode.op_type() == "Loop") {
auto& body = onnxNode.attribute(0).g();
for (int j=0; j<body.node_size(); ++j) {
for (int k=0; k<body.node(j).input_size(); ++k) {
auto inputName = body.node(j).input(k);
auto iter = outputMap.find(inputName);
if (iter != outputMap.end()) {
graph[iter->second].push_back(i);
}
}
}
}
}
for (auto node : graph) {
for (auto output : node.second) {
inDegree[output]++;
}
}
// topo sort
std::queue<int> validNode;
for (int i = 0; i < nodeCount; i++) {
if (!inDegree[i]) {
validNode.push(i);
}
}
while (!validNode.empty()) {
int node = validNode.front();
validNode.pop();
idxMap.push_back(node);
for (auto succ : graph[node]) {
if (--inDegree[succ] == 0) {
validNode.push(succ);
}
}
}
MNN_ASSERT(idxMap.size() == nodeCount);
return idxMap;
}
class DefaultonnxOpConverter : public onnxOpConverter {
public:
virtual void run(MNN::OpT* dstOp, const onnx::NodeProto* onnxNode,
OnnxScope* scope) override {
auto extra = new ExtraT;
dstOp->main.type = OpParameter_Extra;
dstOp->main.value = extra;
extra->engine = "ONNX";
extra->type = onnxNode->op_type();
for (auto srcAttr : onnxNode->attribute()) {
std::unique_ptr<AttributeT> attr(new AttributeT);
attr->key = srcAttr.name();
switch (srcAttr.type()) {
case onnx::AttributeProto_AttributeType_INTS:
attr->list.reset(new ListValueT);
attr->list->i.resize(srcAttr.ints_size());
for (int i = 0; i < srcAttr.ints_size(); ++i) {
attr->list->i[i] = _limit(srcAttr.ints(i));
}
break;
case onnx::AttributeProto_AttributeType_FLOATS:
attr->list.reset(new ListValueT);
attr->list->f.resize(srcAttr.floats_size());
for (int i = 0; i < srcAttr.floats_size(); ++i) {
attr->list->f[i] = srcAttr.floats(i);
}
break;
case onnx::AttributeProto_AttributeType_TENSOR:
attr->tensor.reset(convertTensorToBlob(&srcAttr.t(), scope->mModelDir, dstOp));
break;
case onnx::AttributeProto_AttributeType_STRINGS:
attr->list.reset(new ListValueT);
attr->list->s.resize(srcAttr.strings_size());
for (int i = 0; i < srcAttr.strings_size(); ++i) {
attr->list->s[i] = srcAttr.strings(i);
}
break;
default:
break;
}
attr->i = _limit(srcAttr.i());
attr->s = srcAttr.s();
attr->f = srcAttr.f();
extra->attr.emplace_back(std::move(attr));
}
// add onnx ir version for some differet impl
std::unique_ptr<AttributeT> attr(new AttributeT);
attr->key = "onnx_opset_version";
attr->i = scope->mOpsetVersion;
extra->attr.emplace_back(std::move(attr));
}
virtual MNN::OpParameter type() override {
return OpParameter_Extra;
}
virtual MNN::OpType opType() override {
return OpType_Extra;
}
};
onnxOpConverterSuit::onnxOpConverterSuit() {
}
onnxOpConverterSuit::~onnxOpConverterSuit() {
for (auto& iter : mConverterContainer) {
delete iter.second;
}
mConverterContainer.clear();
}
onnxOpConverterSuit* onnxOpConverterSuit::global = nullptr;
onnxOpConverterSuit* onnxOpConverterSuit::get() {
if (global == nullptr) {
global = new onnxOpConverterSuit;
}
return global;
}
void onnxOpConverterSuit::insert(onnxOpConverter* t, const char* name) {
MNN::OpCount::get()->insertOp("ONNX", std::string(name));
mConverterContainer.insert(std::make_pair(name, t));
}
onnxOpConverter* onnxOpConverterSuit::search(const std::string& name) {
auto iter = mConverterContainer.find(name);
if (iter == mConverterContainer.end()) {
static DefaultonnxOpConverter defaultConverter;
return &defaultConverter;
}
return iter->second;
}
static int _getDataSizeForRead(int32_t itype) {
static std::map<::onnx::TensorProto_DataType, int> dataTypeMap{
{onnx::TensorProto_DataType_FLOAT, 4},
{onnx::TensorProto_DataType_FLOAT16, 2},
{onnx::TensorProto_DataType_BFLOAT16, 2},
{onnx::TensorProto_DataType_INT8, 1},
{onnx::TensorProto_DataType_INT32, 4},
{onnx::TensorProto_DataType_INT64, 8},
{onnx::TensorProto_DataType_DOUBLE, 8},
{onnx::TensorProto_DataType_UINT8, 1},
{onnx::TensorProto_DataType_BOOL, 4},
{onnx::TensorProto_DataType_INT16, 2},
{onnx::TensorProto_DataType_UINT16, 2},
{onnx::TensorProto_DataType_UINT32, 4},
{onnx::TensorProto_DataType_UINT64, 8},
};
auto type = static_cast<::onnx::TensorProto_DataType>(itype);
if (dataTypeMap.find(type) != dataTypeMap.end()) {
return dataTypeMap[type];
}
// Use Max
return 8;
}
MNN::DataType onnxOpConverter::convertDataType(int32_t itype) {
static std::map<::onnx::TensorProto_DataType, MNN::DataType> dataTypeMap{
{onnx::TensorProto_DataType_FLOAT, MNN::DataType_DT_FLOAT},
{onnx::TensorProto_DataType_FLOAT16, MNN::DataType_DT_HALF},
{onnx::TensorProto_DataType_BFLOAT16, MNN::DataType_DT_BFLOAT16},
{onnx::TensorProto_DataType_INT8, MNN::DataType_DT_INT8},
{onnx::TensorProto_DataType_INT32, MNN::DataType_DT_INT32},
{onnx::TensorProto_DataType_INT64, MNN::DataType_DT_INT32}, // For compability, use int32 instead of int64
{onnx::TensorProto_DataType_DOUBLE, MNN::DataType_DT_FLOAT}, // For compability, use float instead of double
{onnx::TensorProto_DataType_UINT8, MNN::DataType_DT_UINT8},
{onnx::TensorProto_DataType_INT8, MNN::DataType_DT_INT8},
{onnx::TensorProto_DataType_BOOL, MNN::DataType_DT_INT32}, // For compability, use int32 instead of bool
{onnx::TensorProto_DataType_INT16, MNN::DataType_DT_INT32}, // For compability, use int32 instead of int16
{onnx::TensorProto_DataType_UINT16, MNN::DataType_DT_INT32}, // For compability, use int32 instead of uint16
{onnx::TensorProto_DataType_UINT32, MNN::DataType_DT_INT32}, // For compability, use int32 instead of uint32
{onnx::TensorProto_DataType_UINT64, MNN::DataType_DT_INT32}, // For compability, use int32 instead of uint64
};
auto type = static_cast<::onnx::TensorProto_DataType>(itype);
if (dataTypeMap.find(type) != dataTypeMap.end()) {
return dataTypeMap[type];
}
return MNN::DataType_DT_INVALID;
}
static bool _needConvert(int onnxDataType) {
switch (onnxDataType) {
case onnx::TensorProto_DataType_FLOAT:
case onnx::TensorProto_DataType_FLOAT16:
case onnx::TensorProto_DataType_BFLOAT16:
case onnx::TensorProto_DataType_INT32:
case onnx::TensorProto_DataType_UINT8:
case onnx::TensorProto_DataType_INT8:
return false;
default:
break;
}
return true;
}
MNN::BlobT* onnxOpConverter::convertTensorToBlob(const onnx::TensorProto* constantTp, const std::string& modelDir, MNN::OpT* op) {
auto constantParam = new MNN::BlobT;
auto dataType = convertDataType(constantTp->data_type());
// printf("origindataType = %d, dataType = %s\n", constantTp->data_type(), MNN::EnumNameDataType(dataType));
constantParam->dataType = dataType;
constantParam->dataFormat = MNN::MNN_DATA_FORMAT_NCHW;
size_t dimSize = constantTp->dims().size();
constantParam->dims.resize(dimSize);
int64_t dataSize = 1;
for (int i = 0; i < dimSize; ++i) {
constantParam->dims[i] = constantTp->dims(i);
dataSize = dataSize * constantTp->dims(i);
}
std::vector<int64_t> alignContent;
if (constantTp->data_location() == onnx::TensorProto_DataLocation_EXTERNAL) {
std::string location;
int64_t offset = 0;
int64_t length = -1;
for (const auto& k : constantTp->external_data()) {
if (k.key() == "location") {
location = k.value();
} else if (k.key() == "offset") {
offset = std::atoll(k.value().c_str());
} else if (k.key() == "length") {
length = std::atoll(k.value().c_str());
}
}
if (!modelDir.empty()) {
location = modelDir + location;
}
if (length < 0) {
length = _getDataSizeForRead(constantTp->data_type()) * dataSize;
}
if (_needConvert(constantTp->data_type())) {
MNN::FileLoader fp(location.c_str(), true);
if (!fp.valid()) {
DLOG(FATAL) << "Fail to open external data: " << location;
return nullptr;
}
fp.offset(offset);
alignContent.resize((length + sizeof(int64_t) - 1) / sizeof(int64_t));
fp.read((char*)alignContent.data(), length);
} else {
op->externalPath = location;
constantParam->external = {
offset, length
};
dataSize = 0;
}
} else {
alignContent.resize((constantTp->raw_data().size() + sizeof(int64_t) - 1) / sizeof(int64_t));
::memcpy(alignContent.data(), constantTp->raw_data().data(), constantTp->raw_data().size());
}
const void* tensor_content = (const void*)alignContent.data();
switch (constantTp->data_type()) {
#define CASE_DATA_TYPE(src, dst) \
case src: \
if (constantTp->dst##_data_size() != 0) { \
tensor_content = constantTp->dst##_data().data(); \
} \
break;
CASE_DATA_TYPE(onnx::TensorProto_DataType_DOUBLE, double);
CASE_DATA_TYPE(onnx::TensorProto_DataType_INT64, int64);
CASE_DATA_TYPE(onnx::TensorProto_DataType_INT32, int32);
CASE_DATA_TYPE(onnx::TensorProto_DataType_UINT8, int32);
CASE_DATA_TYPE(onnx::TensorProto_DataType_INT8, int32);
CASE_DATA_TYPE(onnx::TensorProto_DataType_FLOAT, float);
CASE_DATA_TYPE(onnx::TensorProto_DataType_UINT64, uint64);
CASE_DATA_TYPE(onnx::TensorProto_DataType_BOOL, int32);
default:
break;
}
if (0 == dataSize) {
// Empty blob
return constantParam;
}
if (!tensor_content) {
DLOG(FATAL) << "Convert no data, "
"Please make sure ";
return nullptr;
}
switch (constantTp->data_type()) {
case onnx::TensorProto_DataType_DOUBLE: {
constantParam->float32s.resize(dataSize);
auto source = (double*)tensor_content;
for (int i = 0; i < dataSize; ++i) {
constantParam->float32s[i] = source[i];
}
break;
}
case onnx::TensorProto_DataType_INT64: {
constantParam->int32s.resize(dataSize);
auto source = (int64_t*)tensor_content;
for (int i = 0; i < dataSize; ++i) {
constantParam->int32s[i] = _limit(source[i]);
}
break;
}
case onnx::TensorProto_DataType_INT32: {
auto source = (int32_t*)tensor_content;
constantParam->int32s.resize(dataSize);
for (int i = 0; i < dataSize; ++i) {
constantParam->int32s[i] = source[i];
}
break;
}
case onnx::TensorProto_DataType_UINT16: {
auto source = (uint16_t*)tensor_content;
constantParam->int32s.resize(dataSize);
for (int i = 0; i < dataSize; ++i) {
constantParam->int32s[i] = source[i];
}
break;
}
case onnx::TensorProto_DataType_INT16: {
auto source = (int16_t*)tensor_content;
constantParam->int32s.resize(dataSize);
for (int i = 0; i < dataSize; ++i) {
constantParam->int32s[i] = source[i];
}
break;
}
case onnx::TensorProto_DataType_BOOL: {
auto source = (bool*)tensor_content;
constantParam->int32s.resize(dataSize);
for (int i = 0; i < dataSize; ++i) {
constantParam->int32s[i] = source[i];
}
break;
}
case onnx::TensorProto_DataType_INT8: {
auto source = (int8_t*)tensor_content;
constantParam->int8s.resize(dataSize);
for (int i = 0; i < dataSize; ++i) {
constantParam->int8s[i] = source[i];
}
break;
}
case onnx::TensorProto_DataType_UINT8: {
constantParam->uint8s.resize(dataSize);
if (constantTp->int32_data_size() > 0) {
auto source = (int32_t*)tensor_content;
for (int i = 0; i < dataSize; ++i) {
constantParam->uint8s[i] = source[i];
}
} else {
::memcpy(constantParam->uint8s.data(), tensor_content, dataSize * sizeof(uint8_t));
}
break;
}
case onnx::TensorProto_DataType_FLOAT16: {
constantParam->uint8s.resize(dataSize * sizeof(int16_t));
::memcpy(constantParam->uint8s.data(), tensor_content, dataSize * sizeof(int16_t));
break;
}
case onnx::TensorProto_DataType_BFLOAT16: {
constantParam->uint8s.resize(dataSize * sizeof(int16_t));
::memcpy(constantParam->uint8s.data(), tensor_content, dataSize * sizeof(int16_t));
break;
}
case onnx::TensorProto_DataType_FLOAT: {
float* tempFloatData = (float*)tensor_content;
constantParam->float32s.resize(dataSize);
for (int i = 0; i < dataSize; ++i) {
constantParam->float32s[i] = tempFloatData[i];
}
break;
}
case onnx::TensorProto_DataType_UINT32: {
auto source = (uint32_t*)tensor_content;
constantParam->int32s.resize(dataSize);
for (int i = 0; i < dataSize; ++i) {
constantParam->int32s[i] = source[i];
}
break;
}
case onnx::TensorProto_DataType_UINT64: {
auto source = (uint64_t*)tensor_content;
constantParam->int32s.resize(dataSize);
for (int i = 0; i < dataSize; ++i) {
constantParam->int32s[i] = _limit(source[i]);
}
break;
}
default: {
DLOG(FATAL) << "Don't support " << constantTp->data_type();
break;
}
}
return constantParam;
}
void OnnxScope::onnxInit() {
const int initializerCount = mGraph->initializer_size();
for (int i = 0; i < initializerCount; ++i) {
const auto& initializer = mGraph->initializer(i);
mInitializers.insert(std::make_pair(initializer.name(), &initializer));
}
const int inputCount = mGraph->input_size();
for (int i = 0; i < inputCount; ++i) {
const auto& input = mGraph->input(i);
mInputs.insert(std::make_pair(input.name(), &input));
}
const int outputCount = mGraph->output_size();
for (int i = 0; i < outputCount; ++i) {
const auto& output = mGraph->output(i);
mOutputs.insert(std::make_pair(output.name(), &output));
}
}
int OnnxScope::lookupTensor(std::string name) {
// onnx have optional input, which may be a placeholder when pytorch export onnx model,
// so drop this input, but we should check it out sometimes.
if(name == ""){
return -1;
}
const auto iter = mTensorIdx.find(name);
if (iter != mTensorIdx.end()) {
return iter->second;
}
return -1;
}
std::pair<int, int> OnnxScope::buildTensorArrayOp(std::vector<int> element_shape, bool identical, const std::string& name, int init_size, DataType dataType) {
std::unique_ptr<MNN::OpT> tensorArrayOp(new MNN::OpT);
tensorArrayOp->name = name;
tensorArrayOp->type = MNN::OpType_TensorArray;
tensorArrayOp->defaultDimentionFormat = MNN_DATA_FORMAT_NCHW;
tensorArrayOp->main.type = MNN::OpParameter_TensorArray;
auto tensorArray = new MNN::TensorArrayT;
tensorArray->T = dataType;
tensorArray->dynamic_size = true;
tensorArray->identical_element_shapes = identical;
tensorArray->element_shape = element_shape;
tensorArrayOp->main.value = tensorArray;
tensorArrayOp->inputIndexes.push_back(buildIntConstOp({init_size}, name + "/init_size"));
int idx_handle = declareTensor(name + "/handle");
int idx = declareTensor(name);
tensorArrayOp->outputIndexes.push_back(idx_handle);
tensorArrayOp->outputIndexes.push_back(idx);
oplists().emplace_back(std::move(tensorArrayOp));
return std::make_pair(idx_handle, idx);
}
void OnnxScope::buildAccumulate(const std::string& name, const std::string& uName, const std::string& iName, const std::string& oName) {
// for while_body: %user_defined_val = Add(%user_defined_val, %output)
int idxAcc = declareTensor(name + "/accumulate_u");
MNN::OpT* accumulateOp = new MNN::OpT;
accumulateOp->name = name + "/accumulate";
accumulateOp->type = MNN::OpType_TensorArrayWrite;
accumulateOp->defaultDimentionFormat = MNN_DATA_FORMAT_NCHW;
accumulateOp->main.type = MNN::OpParameter_TensorArray;
auto param = new MNN::TensorArrayT;
param->T = MNN::DataType_DT_FLOAT;
accumulateOp->main.value = param;
// handle, index, value, flow_in
addInputForOp(accumulateOp, uName + "/handle");
addInputForOp(accumulateOp, iName);
addInputForOp(accumulateOp, oName);
addInputForOp(accumulateOp, uName);
accumulateOp->outputIndexes.push_back(idxAcc);
oplists().emplace_back(accumulateOp);
mSubNet->outputs.push_back(idxAcc);
}
std::vector<std::string> OnnxScope::buildSubGraph(const onnx::GraphProto* graph, std::string& name, bool forLoop) {
for (auto& iter : mNet->subgraphs) {
if (iter.get() != nullptr && iter->name == name) {
// TODO: Avoid rebuild new subgraph
MNN_PRINT("Rebuild subgraph for %s (rename to %s_), may increase model size\n", name.c_str(), name.c_str());
name = name + "_";
break;
}
}
std::unique_ptr<MNN::SubGraphProtoT> subgraph(new MNN::SubGraphProtoT);
subgraph->name = name;
std::unique_ptr<OnnxScope> scope(new OnnxScope(graph, subgraph.get(), mNet, this));
const auto& initializers = scope->mInitializers;
const auto& inputs = scope->mInputs;
const auto& outputs = scope->mOutputs;
// set input node to MNN net
for (int index=0; index < graph->input_size(); ++index) {
auto inputName = graph->input(index).name();
bool notHaveInitializer = initializers.find(inputName) == initializers.end();
if (notHaveInitializer) {
MNN::OpT* MNNOp = new MNN::OpT;
MNNOp->name = inputName;
MNNOp->type = MNN::OpType_Input;
MNNOp->main.type = MNN::OpParameter_Input;
auto inputParam = new MNN::InputT;
const auto it = inputs.find(inputName);
const auto& tensorInfo = (it->second)->type().tensor_type();
const int inputDimSize = tensorInfo.shape().dim_size();
inputParam->dims.resize(inputDimSize);
for (int i = 0; i < inputDimSize; ++i) {
inputParam->dims[i] = tensorInfo.shape().dim(i).dim_value();
}
inputParam->dtype = onnxOpConverter::convertDataType(tensorInfo.elem_type());
inputParam->dformat = MNN::MNN_DATA_FORMAT_NCHW;
MNNOp->outputIndexes.push_back(scope->declareTensor(inputName));
MNNOp->main.value = inputParam;
subgraph->inputs.emplace_back(MNNOp->outputIndexes[0]);
subgraph->nodes.emplace_back(MNNOp);
}
}
// Find Extra Input from outside graph
std::map<std::string, int> outsideInputs;
auto findConst = [&](const std::string& name) {
if (scope->lookupTensor(name) >= 0) {
return;
}
// onnx subgraph may use tensor from initializers in outter level graph, recurrsive find it
for (auto curScope = scope.get(); curScope != nullptr; ) {
const auto& curInits = curScope->mInitializers;
const auto it = curInits.find(name);
if (it != curInits.end()) {
// Create const Op
MNN::OpT* constOp = new MNN::OpT;
constOp->type = MNN::OpType_Const;
constOp->main.type = MNN::OpParameter_Blob;
constOp->main.value = onnxOpConverter::convertTensorToBlob(it->second, mModelDir, constOp);
constOp->name = it->first;
constOp->outputIndexes.push_back(scope->declareTensor(it->first));
subgraph->nodes.emplace_back(constOp);
break;
}
if (scope.get() != curScope) {
auto constIt = curScope->mConstIdx.find(name);
if (constIt != curScope->mConstIdx.end()) {
// Copy Const Op
flatbuffers::FlatBufferBuilder builder;
builder.Finish(MNN::Op::Pack(builder, constIt->second));
MNN::OpT* constOp = flatbuffers::GetRoot<MNN::Op>(builder.GetBufferPointer())->UnPack();
constOp->outputIndexes = {scope->declareTensor(constIt->first)};
subgraph->nodes.emplace_back(constOp);
break;
}
}
curScope = reinterpret_cast<decltype(curScope)>(curScope->mParent);
}
};
for (int i=0; i<graph->output_size(); ++i) {
findConst(graph->output(i).name());
}
auto indexes = OnnxScope::topoSort(*graph);
// Firstly declare output names
for (auto i : indexes) {
const auto& onnxNode = graph->node(i);
for (int k = 0; k < onnxNode.output_size(); k++) {
scope->declareTensor(onnxNode.output(k));
}
}
for (auto i : indexes) {
const auto& onnxNode = graph->node(i);
const auto& opType = onnxNode.op_type();
// name maybe null, use the first output name as node-name
const auto& name = onnxNode.output(0);
auto opConverter = onnxOpConverterSuit::get()->search(opType);
MNN::OpT* MNNOp = new MNN::OpT;
MNNOp->name = name;
MNNOp->type = opConverter->opType();
MNNOp->main.type = opConverter->type();
for (int k = 0; k < onnxNode.input_size(); ++k) {
const auto& inputName = onnxNode.input(k);
findConst(inputName);
}
// build input and output
for (int k = 0; k < onnxNode.input_size(); k++) {
auto inputName = onnxNode.input(k);
int idx = scope->lookupTensor(inputName);
if (idx < 0 && inputName != "") {
auto iter = outsideInputs.find(inputName);
if (iter == outsideInputs.end()) {
idx = scope->declareTensor(inputName);
std::unique_ptr<MNN::OpT> inputOp(new MNN::OpT);
inputOp->name = inputName;
inputOp->type = MNN::OpType_Input;
inputOp->main.type = MNN::OpParameter_Input;
auto param = new MNN::InputT;
param->dtype = MNN::DataType_DT_INT32;
param->dformat = MNN::MNN_DATA_FORMAT_NCHW;
param->dims = {-1};
inputOp->main.value = param;
inputOp->outputIndexes.push_back(idx);
subgraph->nodes.emplace_back(std::move(inputOp));
outsideInputs.insert(std::make_pair(inputName, idx));
} else {
idx = iter->second;
}
}
MNNOp->inputIndexes.push_back(idx);
}
for (int k = 0; k < onnxNode.output_size(); k++) {
MNNOp->outputIndexes.push_back(scope->declareTensor(onnxNode.output(k)));
}
auto originIdx = subgraph->inputs.size();
opConverter->run(MNNOp, &onnxNode, scope.get());
// subgraph own by op may introduce extra input which is not exist on current graph, create it in op converter and detect it by subgraph->inputs
for (int inputIdx = originIdx; inputIdx < subgraph->inputs.size(); ++inputIdx) {
auto idx = subgraph->inputs[inputIdx];
outsideInputs.insert(std::make_pair(scope->lookupTensorByIdx(idx), idx));
}
subgraph->inputs.erase(subgraph->inputs.begin() + originIdx, subgraph->inputs.end());
subgraph->nodes.emplace_back(MNNOp);
}
if (!forLoop) {
std::vector<std::string> resOutside;
for (auto& iter : outsideInputs) {
subgraph->inputs.emplace_back(iter.second);
resOutside.emplace_back(iter.first);
}
for (int i = 0; i < graph->output_size(); ++i) {
int idx = scope->lookupTensor(graph->output(i).name());
MNN_ASSERT(idx >= 0);
if (idx >= 0) {
subgraph->outputs.push_back(idx);
}
}
mNet->subgraphs.emplace_back(std::move(subgraph));
return resOutside;
}
int N = graph->input_size() - 2, K = graph->output_size() - N - 1;
for (int i = 0; i < N + 1; i++) {
int idx = scope->lookupTensor(graph->output(i).name());
if (idx >= 0) {
subgraph->outputs.push_back(idx);
} else {
FUNC_PRINT_ALL(graph->output(i).name().c_str(), s);
}
}
std::vector<std::string> resOutside;
for (auto& iter : outsideInputs) {
subgraph->inputs.emplace_back(iter.second);
subgraph->outputs.emplace_back(iter.second);
resOutside.emplace_back(iter.first);
}
for (int i = 0; i < K; ++i) {
int idx = scope->lookupTensor(graph->output(i + N + 1).name());
MNN_ASSERT(idx >= 0);
if (idx >= 0) {
subgraph->outputs.push_back(idx);
}
}
mNet->subgraphs.emplace_back(std::move(subgraph));
return resOutside;
}