mirror of
https://github.com/PaddlePaddle/FastDeploy.git
synced 2025-10-06 00:57:33 +08:00

* Add notes for tensors * Optimize some apis * move some warnings * Support build with Paddle2ONNX * Add protobuf support * Fix compile on mac * add clearn package script * Add paddle2onnx code * remove submodule * Add onnx ocde * remove softlink * add onnx code * fix error * Add cmake file * fix patchelf * update paddle2onnx * Delete .gitmodules --------- Co-authored-by: PaddleCI <paddle_ci@example.com> Co-authored-by: pangyoki <pangyoki@126.com> Co-authored-by: jiangjiajun <jiangjiajun@baidu.lcom>
549 lines
20 KiB
C++
549 lines
20 KiB
C++
// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "paddle2onnx/mapper/exporter.h"
|
|
|
|
#include <google/protobuf/message.h>
|
|
#include <onnx/checker.h>
|
|
|
|
#include <array>
|
|
|
|
#include "onnxoptimizer/optimize.h"
|
|
#include "paddle2onnx/optimizer/convert_fp32_to_fp16.h"
|
|
#include "paddle2onnx/optimizer/eliminate_non_transpose.h"
|
|
#include "paddle2onnx/optimizer/fuse_constant_cast.h"
|
|
#include "paddle2onnx/optimizer/fuse_constant_reshape.h"
|
|
#include "paddle2onnx/optimizer/fuse_constant_unsqueeze.h"
|
|
#include "paddle2onnx/optimizer/fuse_paddle_conv_bias.h"
|
|
#include "paddle2onnx/optimizer/fuse_unsqueeze_conv2d_squeeze.h"
|
|
|
|
namespace paddle2onnx {
|
|
MapperHelper* MapperHelper::helper = nullptr;
|
|
|
|
void ModelExporter::ExportParameters(
|
|
const std::map<std::string, Weight>& params, bool use_initializer) {
|
|
for (auto& item : params) {
|
|
// TODO(jiangjiajun) I'm not handling use_initializer now, but some day I
|
|
// will
|
|
auto node = MakeConstant(item.first, item.second);
|
|
parameters.push_back(std::move(node));
|
|
}
|
|
}
|
|
|
|
void ModelExporter::UpdateParameters(
|
|
const std::map<std::string, Weight>& params) {
|
|
for (auto& item : params) {
|
|
auto node = MakeConstant(item.first, item.second);
|
|
bool updated = false;
|
|
for (int i = 0; i < parameters.size(); ++i) {
|
|
auto old_node = parameters[i];
|
|
if (old_node->output(0) == item.first) {
|
|
parameters.erase(parameters.begin() + i);
|
|
parameters.push_back(std::move(node));
|
|
updated = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!updated) {
|
|
parameters.push_back(std::move(node));
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelExporter::ExportInputOutputs(
|
|
const std::vector<TensorInfo>& input_infos,
|
|
const std::vector<TensorInfo>& output_infos) {
|
|
for (auto& item : input_infos) {
|
|
auto value_info = MakeValueInfo(item);
|
|
inputs.push_back(std::move(value_info));
|
|
}
|
|
for (auto& item : output_infos) {
|
|
auto value_info = MakeValueInfo(item);
|
|
outputs.push_back(std::move(value_info));
|
|
}
|
|
}
|
|
|
|
void ModelExporter::ExportOp(const PaddleParser& parser, OnnxHelper* helper,
|
|
int32_t opset_version, int64_t block_id,
|
|
int64_t op_id, bool verbose) {
|
|
_current_exported_num += 1;
|
|
auto op = parser.GetOpDesc(block_id, op_id);
|
|
#ifdef PADDLE2ONNX_DEBUG
|
|
P2OLogger(true) << "---Converting operator: " << op.type() << " ---"
|
|
<< std::endl;
|
|
#endif
|
|
if (op.type() == "while") {
|
|
return ExportLoop(parser, helper, opset_version, block_id, op_id, verbose);
|
|
}
|
|
|
|
auto mapper = MapperHelper::Get()->CreateMapper(op.type(), parser, helper,
|
|
block_id, op_id);
|
|
mapper->deploy_backend = _deploy_backend;
|
|
#ifdef PADDLE2ONNX_DEBUG
|
|
P2OLogger(true) << "Mapper Name: " << mapper->Name() << std::endl;
|
|
#endif
|
|
// Some operators will export as custom operator
|
|
auto iter = custom_ops.find(op.type());
|
|
if (iter != custom_ops.end()) {
|
|
mapper->export_as_custom_op = true;
|
|
mapper->custom_op_name = iter->second;
|
|
}
|
|
mapper->Run();
|
|
delete mapper;
|
|
|
|
#ifdef PADDLE2ONNX_DEBUG
|
|
P2OLogger(true) << "---Converting operator: " << op.type() << " done---"
|
|
<< std::endl;
|
|
#endif
|
|
}
|
|
|
|
void ModelExporter::ProcessGraphDumplicateNames(
|
|
std::vector<std::shared_ptr<ONNX_NAMESPACE::NodeProto>>* parameters,
|
|
std::vector<std::shared_ptr<ONNX_NAMESPACE::ValueInfoProto>>* inputs,
|
|
std::vector<std::shared_ptr<ONNX_NAMESPACE::ValueInfoProto>>* outputs,
|
|
std::vector<std::shared_ptr<ONNX_NAMESPACE::NodeProto>>* nodes,
|
|
std::map<std::string, QuantizeInfo>* quantize_info) {
|
|
// process dumplicate tensor names
|
|
std::map<std::string, std::string> renamer;
|
|
std::set<std::string> tensor_names;
|
|
for (auto& item : *parameters) {
|
|
for (size_t i = 0; i < item->output_size(); ++i) {
|
|
if (tensor_names.find(item->output(i)) != tensor_names.end()) {
|
|
Assert(false, "There's dumplicate names in exported parameters.");
|
|
}
|
|
tensor_names.insert(item->output(i));
|
|
}
|
|
}
|
|
for (auto& item : *inputs) {
|
|
if (tensor_names.find(item->name()) != tensor_names.end()) {
|
|
Assert(false, "There's dumplicate names:" + item->name() +
|
|
" in exported parameters and inputs.");
|
|
}
|
|
tensor_names.insert(item->name());
|
|
}
|
|
for (auto& item : *nodes) {
|
|
// update node inputs
|
|
for (size_t i = 0; i < item->input_size(); ++i) {
|
|
if (renamer.find(item->input(i)) != renamer.end()) {
|
|
auto updated_name = renamer[item->input(i)];
|
|
while (renamer.find(updated_name) != renamer.end()) {
|
|
updated_name = renamer[updated_name];
|
|
}
|
|
*(item->mutable_input(i)) = updated_name;
|
|
}
|
|
}
|
|
// if there's dumplicate name
|
|
// will generate new name and replace it
|
|
for (size_t i = 0; i < item->output_size(); ++i) {
|
|
if (tensor_names.find(item->output(i)) != tensor_names.end()) {
|
|
std::string renamed_tensor_name = item->output(i);
|
|
while (renamer.find(renamed_tensor_name) != renamer.end()) {
|
|
renamed_tensor_name = renamer[renamed_tensor_name];
|
|
}
|
|
auto new_tensor_name =
|
|
MapperHelper::Get()->GenName(renamed_tensor_name);
|
|
P2OLogger() << "Find dumplicate output name '" << renamed_tensor_name
|
|
<< "', it will rename to '" << new_tensor_name << "'."
|
|
<< std::endl;
|
|
if (quantize_info &&
|
|
quantize_info->find(renamed_tensor_name) != quantize_info->end()) {
|
|
(*quantize_info)[new_tensor_name] =
|
|
(*quantize_info)[renamed_tensor_name];
|
|
}
|
|
*(item->mutable_output(i)) = new_tensor_name;
|
|
renamer[renamed_tensor_name] = new_tensor_name;
|
|
}
|
|
tensor_names.insert(item->output(i));
|
|
}
|
|
}
|
|
|
|
for (auto& item : *outputs) {
|
|
if (renamer.find(item->name()) != renamer.end()) {
|
|
auto updated_name = renamer[item->name()];
|
|
while (renamer.find(updated_name) != renamer.end()) {
|
|
updated_name = renamer[updated_name];
|
|
}
|
|
item->set_name(updated_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelExporter::SaveExternalData(::paddle2onnx::GraphProto* graph,
|
|
const std::string& external_file_path,
|
|
bool* save_external) {
|
|
P2OLogger() << "The exported ONNX model is bigger than 2G, external data "
|
|
"will save to file: "
|
|
<< external_file_path << std::endl;
|
|
std::string file_name = GetFilenameFromPath(external_file_path);
|
|
if (save_external) {
|
|
*save_external = true;
|
|
}
|
|
std::fstream f(external_file_path, std::ios::out);
|
|
Assert(f.is_open(), "Failed to open: " + external_file_path +
|
|
" file to save external data");
|
|
for (auto index = 0; index < graph->node_size(); index++) {
|
|
auto node = graph->mutable_node(index);
|
|
if (node->op_type() != "Constant") {
|
|
continue;
|
|
}
|
|
for (auto i = 0; i < node->attribute_size(); i++) {
|
|
auto attr = node->mutable_attribute(i);
|
|
if (attr->name() != "value") {
|
|
continue;
|
|
}
|
|
auto tensor = attr->mutable_t();
|
|
|
|
if (tensor->raw_data().size() <= 128) {
|
|
continue;
|
|
}
|
|
|
|
tensor->set_data_location(TensorProto::EXTERNAL);
|
|
auto external_data = tensor->add_external_data();
|
|
external_data->set_key("location");
|
|
external_data->set_value(file_name);
|
|
|
|
external_data = tensor->add_external_data();
|
|
external_data->set_key("offset");
|
|
f.seekg(0, std::ios::end);
|
|
int64_t offset = f.tellg();
|
|
external_data->set_value(std::to_string(offset));
|
|
auto raw_data = tensor->raw_data();
|
|
f << raw_data;
|
|
external_data = tensor->add_external_data();
|
|
external_data->set_key("length");
|
|
int64_t raw_datas_size = raw_data.size();
|
|
external_data->set_value(std::to_string(raw_datas_size));
|
|
tensor->clear_raw_data();
|
|
}
|
|
}
|
|
f.close();
|
|
}
|
|
void ModelExporter::ONNXChecker(const ONNX_NAMESPACE::ModelProto& model,
|
|
const bool& verbose) {
|
|
// TODO(jiangjiajun)
|
|
// If we need to integrate with framework
|
|
// this check will return a information
|
|
// to let framework know the conversion is
|
|
// pass or fail
|
|
try {
|
|
// ONNX_NAMESPACE::checker::check_model(*(model.get()));
|
|
ONNX_NAMESPACE::checker::check_model(model);
|
|
} catch (const std::exception& e) {
|
|
P2OLogger(verbose) << "The exported ONNX model is invalid." << std::endl;
|
|
P2OLogger(verbose) << "Model checker error log: " << e.what() << std::endl;
|
|
}
|
|
P2OLogger(verbose) << "PaddlePaddle model is exported as ONNX format now."
|
|
<< std::endl;
|
|
}
|
|
|
|
std::string ModelExporter::Run(
|
|
const PaddleParser& parser, int opset_version, bool auto_upgrade_opset,
|
|
bool verbose, bool enable_onnx_checker, bool enable_experimental_op,
|
|
bool enable_optimize, const std::string& deploy_backend,
|
|
std::string* calibration_cache, const std::string& external_file,
|
|
bool* save_external, bool export_fp16_model) {
|
|
_deploy_backend = deploy_backend;
|
|
_helper.SetOpsetVersion(opset_version);
|
|
_total_ops_num = 0;
|
|
_current_exported_num = 0;
|
|
for (auto i = 0; i < parser.NumOfBlocks(); ++i) {
|
|
_total_ops_num += parser.NumOfOps(i);
|
|
}
|
|
_helper.nodes.reserve(_total_ops_num * 3);
|
|
Assert(opset_version <= MAX_ONNX_OPSET_VERSION && opset_version >= 7,
|
|
"Paddle2ONNX now only support opset version in range of [7, " +
|
|
std::to_string(MAX_ONNX_OPSET_VERSION) + "].");
|
|
_helper.Clear();
|
|
inputs.clear();
|
|
outputs.clear();
|
|
parameters.clear();
|
|
|
|
// clear name_counter
|
|
// this use to generate unique name
|
|
// for intermdiate
|
|
// while converting all the op
|
|
MapperHelper::Get()->ClearNameCounter();
|
|
|
|
std::set<std::string> unsupported_ops;
|
|
if (!CheckIfOpSupported(parser, &unsupported_ops, enable_experimental_op)) {
|
|
auto logger = P2OLogger();
|
|
logger << "Oops, there are some operators not supported yet, including ";
|
|
for (auto& item : unsupported_ops) {
|
|
logger << item << ",";
|
|
}
|
|
logger << std::endl;
|
|
Assert(1 == 0,
|
|
"Due to the unsupported operators, the conversion is aborted.");
|
|
}
|
|
|
|
int32_t min_opset = GetMinOpset(parser, verbose);
|
|
if (min_opset < 0) {
|
|
Assert(false,
|
|
"Model exporting failed, you can report this problem to "
|
|
"https://github.com/PaddlePaddle/Paddle2ONNX.git.");
|
|
}
|
|
if (!auto_upgrade_opset) {
|
|
if (min_opset > opset_version) {
|
|
P2OLogger() << "This PaddlePaddle model is not able to export to ONNX "
|
|
"with opset_version="
|
|
<< opset_version << ", please set the opset_version to "
|
|
<< min_opset << " or higher for successfully conversion."
|
|
<< std::endl;
|
|
Assert(false,
|
|
"Due to opset version, the model exporting is aborted, please set "
|
|
"a higher opset_version or set auto_upgrade_opset=true.");
|
|
}
|
|
} else {
|
|
if (min_opset > opset_version) {
|
|
P2OLogger() << "Opset version will change to " << min_opset << " from "
|
|
<< opset_version << std::endl;
|
|
opset_version = min_opset;
|
|
}
|
|
}
|
|
_helper.SetOpsetVersion(opset_version);
|
|
P2OLogger(verbose) << "Use opset_version = " << _helper.GetOpsetVersion()
|
|
<< " for ONNX export." << std::endl;
|
|
ExportParameters(parser.params);
|
|
ExportInputOutputs(parser.inputs, parser.outputs);
|
|
|
|
// Only convert blocks 0 now
|
|
// because control flow is not supported yet
|
|
for (auto i = 0; i < parser.NumOfOps(0); ++i) {
|
|
auto op = parser.GetOpDesc(0, i);
|
|
if (op.type() == "feed") {
|
|
continue;
|
|
} else if (op.type() == "fetch") {
|
|
continue;
|
|
}
|
|
ExportOp(parser, &_helper, opset_version, 0, i, verbose);
|
|
}
|
|
|
|
// construct a onnx model proto
|
|
auto model = std::make_shared<ONNX_NAMESPACE::ModelProto>();
|
|
// TODO(jiangjiajun) ir version is related to onnx version
|
|
model->set_ir_version(ONNX_NAMESPACE::IR_VERSION);
|
|
auto graph = model->mutable_graph();
|
|
graph->set_name("Model from PaddlePaddle.");
|
|
auto opset_id = model->add_opset_import();
|
|
opset_id->set_domain("");
|
|
opset_id->set_version(opset_version);
|
|
if (custom_ops.size()) {
|
|
auto opset_paddle_id = model->add_opset_import();
|
|
opset_paddle_id->set_domain("Paddle");
|
|
opset_paddle_id->set_version(1);
|
|
}
|
|
|
|
ProcessGraphDumplicateNames(¶meters, &inputs, &outputs, &_helper.nodes,
|
|
&_helper.quantize_info);
|
|
if (parser.is_quantized_model) {
|
|
quantize_model_processer.ProcessQuantizeModel(
|
|
¶meters, &inputs, &outputs, &_helper.nodes, &_helper,
|
|
deploy_backend, parser, calibration_cache);
|
|
// Update int8 weights in quantized OP to float32
|
|
UpdateParameters(_helper.updated_params);
|
|
}
|
|
|
|
for (auto& item : parameters) {
|
|
*(graph->add_node()) = *(item.get());
|
|
}
|
|
for (auto& item : inputs) {
|
|
*(graph->add_input()) = *(item.get());
|
|
}
|
|
for (auto& item : _helper.nodes) {
|
|
*(graph->add_node()) = (*item.get());
|
|
}
|
|
for (auto& item : outputs) {
|
|
*(graph->add_output()) = (*item.get());
|
|
}
|
|
for (auto& item : _helper.value_infos) {
|
|
*(graph->add_value_info()) = (*item.get());
|
|
}
|
|
|
|
ONNX_NAMESPACE::ModelProto onnx_model;
|
|
std::string out;
|
|
if (enable_optimize) {
|
|
onnx_model = Optimize(*(model.get()));
|
|
} else {
|
|
onnx_model = *model.get();
|
|
}
|
|
|
|
// convert fp32 model to fp16
|
|
if (export_fp16_model) {
|
|
P2OLogger(verbose) << "Convert FP32 ONNX model to FP16." << std::endl;
|
|
ConvertFp32ToFp16 convert;
|
|
convert.SetCustomOps(custom_ops);
|
|
convert.Convert(&onnx_model);
|
|
}
|
|
|
|
// save external data file for big model
|
|
std::string external_data_file;
|
|
if (onnx_model.ByteSizeLong() > INT_MAX) {
|
|
if (external_file.empty()) {
|
|
external_data_file = "external_data";
|
|
} else {
|
|
external_data_file = external_file;
|
|
}
|
|
}
|
|
if (external_data_file.size()) {
|
|
SaveExternalData(onnx_model.mutable_graph(), external_data_file,
|
|
save_external);
|
|
}
|
|
// check model
|
|
if (enable_onnx_checker) {
|
|
ONNXChecker(onnx_model, verbose);
|
|
}
|
|
|
|
if (!onnx_model.SerializeToString(&out)) {
|
|
P2OLogger(verbose)
|
|
<< "Error happenedd while optimizing the exported ONNX model."
|
|
<< std::endl;
|
|
return "";
|
|
}
|
|
return out;
|
|
}
|
|
|
|
bool ModelExporter::CheckIfOpSupported(const PaddleParser& parser,
|
|
std::set<std::string>* unsupported_ops,
|
|
bool enable_experimental_op) {
|
|
unsupported_ops->clear();
|
|
for (auto i = 0; i < parser.NumOfBlocks(); ++i) {
|
|
for (auto j = 0; j < parser.NumOfOps(i); ++j) {
|
|
auto op = parser.GetOpDesc(i, j);
|
|
if (op.type() == "feed" || op.type() == "fetch") {
|
|
continue;
|
|
}
|
|
if (op.type() == "while" && enable_experimental_op) {
|
|
if (!IsLoopSupported(parser, i, j)) {
|
|
unsupported_ops->insert("while");
|
|
}
|
|
continue;
|
|
}
|
|
if (!MapperHelper::Get()->IsRegistered(op.type())) {
|
|
unsupported_ops->insert(op.type());
|
|
} else if (!enable_experimental_op) {
|
|
auto mapper = MapperHelper::Get()->CreateMapper(op.type(), parser,
|
|
&_helper, i, j);
|
|
if (mapper->IsExperimentalOp()) {
|
|
unsupported_ops->insert(op.type());
|
|
}
|
|
delete mapper;
|
|
}
|
|
}
|
|
}
|
|
return (unsupported_ops->size() == 0);
|
|
}
|
|
|
|
int32_t ModelExporter::GetMinOpset(const PaddleParser& parser, bool verbose) {
|
|
int32_t opset_version = _helper.GetOpsetVersion();
|
|
int32_t max_opset = 7;
|
|
bool exportable = true;
|
|
// Record the number of ops that need to be converted
|
|
int converted_op_num = 0;
|
|
std::set<std::string> verbose_log;
|
|
for (auto i = 0; i < parser.NumOfBlocks(); ++i) {
|
|
for (auto j = 0; j < parser.NumOfOps(i); ++j) {
|
|
auto op = parser.GetOpDesc(i, j);
|
|
if (custom_ops.find(op.type()) != custom_ops.end()) {
|
|
continue;
|
|
}
|
|
if (op.type() == "feed" || op.type() == "fetch") {
|
|
continue;
|
|
}
|
|
converted_op_num += 1;
|
|
int current_min_opset = 7;
|
|
if (op.type() == "while") {
|
|
P2OLogger() << "Detected there's control flow 'while' op in your "
|
|
"model, this requires the minimal opset version of 13."
|
|
<< std::endl;
|
|
current_min_opset = 13;
|
|
} else {
|
|
auto mapper = MapperHelper::Get()->CreateMapper(op.type(), parser,
|
|
&_helper, i, j);
|
|
auto iter = custom_ops.find(op.type());
|
|
if (iter != custom_ops.end()) {
|
|
mapper->export_as_custom_op = true;
|
|
}
|
|
current_min_opset = mapper->GetMinOpset(verbose);
|
|
delete mapper;
|
|
}
|
|
if (current_min_opset < 0) {
|
|
exportable = false;
|
|
P2OLogger(verbose) << "Due to the operator: " << op.type()
|
|
<< ", this model cannot be exported to ONNX."
|
|
<< std::endl;
|
|
} else if (current_min_opset > max_opset) {
|
|
max_opset = current_min_opset;
|
|
if (verbose && current_min_opset > opset_version) {
|
|
verbose_log.insert("Due to the operator: " + op.type() +
|
|
", requires opset_version >= " +
|
|
std::to_string(current_min_opset) + ".");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (verbose) {
|
|
for (auto iter = verbose_log.begin(); iter != verbose_log.end(); ++iter) {
|
|
P2OLogger() << *iter << std::endl;
|
|
}
|
|
}
|
|
|
|
// Here we put some checks to make sure
|
|
// paddle2onnx could compatible with
|
|
// other version of onnx
|
|
int32_t max_support_opset = MAX_ONNX_OPSET_VERSION;
|
|
if (exportable && (max_opset > MAX_ONNX_OPSET_VERSION)) {
|
|
exportable = false;
|
|
P2OLogger() << "[ERROR] The compiled ONNX version only supports opset 7~"
|
|
<< MAX_ONNX_OPSET_VERSION
|
|
<< ", but now this model need as least opset " << max_opset
|
|
<< ", please compile with higher version of ONNX." << std::endl;
|
|
}
|
|
if (exportable) {
|
|
return max_opset;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
ONNX_NAMESPACE::ModelProto ModelExporter::Optimize(
|
|
const ONNX_NAMESPACE::ModelProto& model) {
|
|
ONNX_NAMESPACE::optimization::Optimizer::passes
|
|
.registerPass<ONNX_NAMESPACE::optimization::FuseConstantReshape>();
|
|
ONNX_NAMESPACE::optimization::Optimizer::passes
|
|
.registerPass<ONNX_NAMESPACE::optimization::FuseConstantUnsqueeze>();
|
|
ONNX_NAMESPACE::optimization::Optimizer::passes
|
|
.registerPass<ONNX_NAMESPACE::optimization::FusePaddleConvBias>();
|
|
ONNX_NAMESPACE::optimization::Optimizer::passes
|
|
.registerPass<ONNX_NAMESPACE::optimization::FuseUnsqueezeConv2dSqueeze>();
|
|
ONNX_NAMESPACE::optimization::Optimizer::passes
|
|
.registerPass<ONNX_NAMESPACE::optimization::EliminateNonTranspose>();
|
|
ONNX_NAMESPACE::optimization::Optimizer::passes
|
|
.registerPass<ONNX_NAMESPACE::optimization::FuseConstantCast>();
|
|
std::vector<std::string> passes = {"eliminate_identity",
|
|
"eliminate_deadend",
|
|
"eliminate_deadend",
|
|
"fuse_constant_reshape",
|
|
"fuse_constant_unsqueeze",
|
|
"fuse_paddle_conv_bias",
|
|
"fuse_consecutive_transposes",
|
|
"eliminate_non_transpose",
|
|
"fuse_matmul_add_bias_into_gemm",
|
|
"eliminate_identity",
|
|
"eliminate_deadend",
|
|
"eliminate_unused_initializer"};
|
|
return ONNX_NAMESPACE::optimization::Optimize(model, passes);
|
|
}
|
|
|
|
} // namespace paddle2onnx
|