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

316 lines
12 KiB
C++

//
// TFGraphResolver.cpp
// MNNConverter
//
// Created by MNN on 2020/06/13.
// Copyright © 2018, Alibaba Group Holding Limited
//
#include "TFGraphResolver.hpp"
#include "TFGraphResolverHelpers.hpp"
#include <vector>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include "graph.pb.h"
#include "tfOpConverter.hpp"
#include "MNN_generated.h"
#include "../compression/quantization.hpp"
#include <flatbuffers/util.h>
void TFGraph::AddNode(const NodeDef* node) {
std::unique_ptr<TFNode> tf_node(new TFNode);
tf_node->node_def = node;
tf_node->name = node->name();
tf_node->op = node->op();
nodes_.push_back(std::move(tf_node));
}
void TFGraph::Finalize() {
std::unordered_map<std::string, TFNode*> nodes;
for (auto& node : nodes_) {
nodes.emplace(node->name, node.get());
}
for (auto& node : nodes_) {
const NodeDef* node_def = node->node_def;
for (int i = 0; i < node_def->input_size(); ++i) {
const std::string& input = node_def->input(i);
if (IsControlInput(input)) {
continue;
}
std::string input_op = input;
auto splits = RSplitString(input, ":");
if (splits.size() == 2) {
input_op = splits.at(0);
}
TFNode* start = nodes.at(input_op);
std::unique_ptr<TFEdge> edge(new TFEdge);
*edge = TFEdge{input, start, node.get()};
node->inputs.push_back(edge.get());
start->outputs.push_back(edge.get());
edges_.push_back(std::move(edge));
}
}
for (auto& node : nodes_) {
if (node->outputs.empty()) {
final_nodes_.push_back(node.get());
}
}
}
std::unique_ptr<MNN::SubGraphProtoT> TFGraph::ToProto() const {
std::unique_ptr<MNN::SubGraphProtoT> graph_proto(new MNN::SubGraphProtoT);
graph_proto->name = name_;
std::vector<const TFNode*> entry_nodes;
std::unordered_map<std::string, int> tensor_indices;
// Add normal nodes.
for (int i = 0; i < nodes_.size(); ++i) {
TFNode* node = nodes_[i].get();
std::shared_ptr<TmpNode> tempNode(new TmpNode());
tempNode->opName = node->name;
tempNode->opType = node->op;
tempNode->tfNode = node->node_def;
MNN::OpT *op = new MNN::OpT;
auto creator = tfOpConverterSuit::get()->search(tempNode->opType);
DCHECK(creator) << "MNN Converter NOT_SUPPORTED_OP: [ "
<< tempNode->opType << " ]";
op->name = tempNode->opName;
op->type = creator->opType();
op->main.type = creator->type();
// resize the inputIndexes and outputIndexes
int input_size = node->inputs.size();
op->inputIndexes.resize(input_size);
// -1 is placeholder value, and the number of -1 is the number of
// output tensors.
// defalut: every op output one tensor, if the number of the output
// tensors is bigger than 1, set the outputIndexes in the op
// converter(void run(MNN::OpT *dstOp, TmpNode *srcNode))
op->outputIndexes = {-1};
creator->run(op, tempNode.get());
for (int j = 0; j < input_size; j++) {
std::string input = node->inputs[j]->name;
auto it = tensor_indices.find(input);
if (it == tensor_indices.end()) {
int index = tensor_indices.size();
it = tensor_indices.emplace(input, index).first;
graph_proto->tensors.push_back(input);
}
op->inputIndexes[j] = it->second;
}
int output_size = node->outputs.size();
for (int j = 0; j < node->outputs.size(); ++j) {
std::string output = node->outputs[j]->name;
auto it = tensor_indices.find(output);
if (it == tensor_indices.end()) {
int index = tensor_indices.size();
it = tensor_indices.emplace(output, index).first;
graph_proto->tensors.push_back(output);
}
int index = 0;
auto splits = RSplitString(output, ":");
if (splits.size() == 2) {
index = atoi(splits[1].c_str());
}
if (op->outputIndexes.size() <= index) {
int origin_size = op->outputIndexes.size();
op->outputIndexes.resize(index + 1);
for (int p = origin_size; p <= index; ++p) {
op->outputIndexes[p] = -1;
}
}
op->outputIndexes[index] = it->second;
}
graph_proto->nodes.emplace_back(op);
}
for (auto &op : graph_proto->nodes) {
for (int i = 0; i < op->outputIndexes.size(); ++i) {
if (op->outputIndexes[i] == -1) {
int index = graph_proto->tensors.size();
op->outputIndexes[i] = index;
std::string output = op->name;
if (i != 0) {
output += ":" + flatbuffers::NumToString(i);
}
graph_proto->tensors.emplace_back(output);
}
}
}
return std::move(graph_proto);
}
std::unique_ptr<TFEdge> TFGraphResolver::BuildEdge(
const std::string& name, TFNode* start, TFNode* end) {
std::unique_ptr<TFEdge> edge(new TFEdge);
*edge = TFEdge{name, start, end};
return std::move(edge);
}
std::unique_ptr<TFNode> TFGraphResolver::BuildQuantOrDequantNode(
const std::string& name,
const std::string& op,
const int& nbit,
const std::vector<float>& scales,
const float& zero_point, const float& clamp_min, const float& clamp_max,
const MNN::Compression::LayerQuantizeParams_QuantMethod& method) {
std::unique_ptr<NodeDef> node_def(new NodeDef);
*(node_def->mutable_name()) = name;
*(node_def->mutable_op()) = op;
(*node_def->mutable_attr())["nbit"].set_i(nbit);
auto* list = (*node_def->mutable_attr())["scale"].mutable_list();
for (int i = 0; i < scales.size(); ++i) {
if (op == "CustomQuantize") {
list->mutable_f()->Add(1.f / scales[i]);
} else {
list->mutable_f()->Add(scales[i]);
}
}
(*node_def->mutable_attr())["zero_point"].set_f(zero_point);
(*node_def->mutable_attr())["clamp_min"].set_f(clamp_min);
(*node_def->mutable_attr())["clamp_max"].set_f(clamp_max);
(*node_def->mutable_attr())["method"].set_i(int(method));
std::unique_ptr<TFNode> quant_node(new TFNode);
quant_node->name = name;
quant_node->op = op;
quant_node->node_def = node_def.get();
main_graph()->allocated_nodes_.push_back(std::move(node_def));
return std::move(quant_node);
}
void TFGraphResolver::ResolveQuantization(
TFGraph* graph,
const compression::Quantization& int8_calibration) {
std::vector<std::unique_ptr<TFNode>> append_nodes;
std::vector<std::unique_ptr<TFEdge>> append_edges;
static int64_t uuid = 0;
auto AddQuantizeAndDequantizeNodes =
[&, this](const std::vector<TFEdge*> edges,
const compression::Quantization::TensorParams& params) {
TFNode* start_node = edges.at(0)->start;
for (TFEdge* edge : edges) {
EraseOutput(start_node, edge);
}
auto splits = RSplitString(edges.at(0)->name, ":");
const std::string& op_name = splits.at(0);
// Add quantize node.
std::string quant_name = op_name + "_quant_" + flatbuffers::NumToString(uuid);
std::unique_ptr<TFNode> quant_node = BuildQuantOrDequantNode(
quant_name, "CustomQuantize", params.nbit, params.scale,
params.zero_point, params.clamp_min, params.clamp_max, params.method);
// Add dequantize node.
std::string dequant_name = quant_name + "_dequant_" + flatbuffers::NumToString(uuid);
std::unique_ptr<TFNode> dequant_node = BuildQuantOrDequantNode(
dequant_name, "CustomDequantize", params.nbit, params.scale,
params.zero_point, params.clamp_min, params.clamp_max, params.method);
// Update UUID.
++uuid;
// Connect quantize and dequantize node.
std::unique_ptr<TFEdge> quant_edge =
BuildEdge(edges.at(0)->name, start_node, quant_node.get());
// Connect dequantize and the next node.
std::unique_ptr<TFEdge> dequant_edge =
BuildEdge(quant_node->name, quant_node.get(), dequant_node.get());
AddOutput(start_node, quant_edge.get());
quant_node->inputs = {quant_edge.get()};
quant_node->outputs = {dequant_edge.get()};
dequant_node->inputs = {dequant_edge.get()};
dequant_node->outputs = edges;
for (TFEdge* edge : edges) {
edge->name = dequant_node->name;
edge->start = dequant_node.get();
}
append_nodes.push_back(std::move(quant_node));
append_nodes.push_back(std::move(dequant_node));
append_edges.push_back(std::move(quant_edge));
append_edges.push_back(std::move(dequant_edge));
// Return dequant edge.
return append_edges.back().get();
};
const auto& tensor_params = int8_calibration.tensors;
for (auto& node : graph->nodes_) {
std::unordered_map<std::string, std::vector<TFEdge*>> quant_edges;
for (TFEdge* output : node->outputs) {
std::string tensor_name = output->name;
if (node->op == "Enter" || node->op == "Switch") {
// The input names of the node maybe replaced by the quantize
// and dequantize op, so here we use the input name from the
// `node_def` since it should not be modified at any time.
// tensor_name = node->inputs.at(0)->name;
tensor_name = node->node_def->input(0);
}
quant_edges[tensor_name].push_back(output);
}
for (const auto& it : quant_edges) {
auto p = tensor_params.find(it.first);
if (p == tensor_params.end()) {
continue;
}
const auto& params = p->second.at(0);
AddQuantizeAndDequantizeNodes(it.second, params);
}
}
for (auto& node : graph->nodes_) {
std::unordered_map<std::string, std::vector<TFEdge*>> quant_edges;
for (int i = 0; i < node->inputs.size(); ++i) {
TFEdge* edge = node->inputs[i];
quant_edges[edge->name].push_back(edge);
}
for (const auto& it : quant_edges) {
auto p = tensor_params.find(it.first);
if (p == tensor_params.end()) {
continue;
}
const auto& params = p->second.at(0);
AddQuantizeAndDequantizeNodes(it.second, params);
}
}
// Append nodes and edges to root graph.
for (auto& node : append_nodes) {
main_graph()->nodes_.push_back(std::move(node));
}
for (auto& edge : append_edges) {
main_graph()->edges_.push_back(std::move(edge));
}
}
TFGraphResolver::TFGraphResolver(const tensorflow::GraphDef& graph_def) {
std::unique_ptr<TFGraph> tf_graph(new TFGraph);
const int count = graph_def.node_size();
for (int i = 0; i < count; ++i) {
const NodeDef& node_def = graph_def.node(i);
tf_graph->AddNode(&node_def);
}
tf_graph->Finalize();
graphs_.push_back(std::move(tf_graph));
TFGraph* main_graph = graphs_.back().get();
}
const TFGraph* TFGraphResolver::graph(const int graph_index) const {
return graphs_.at(graph_index).get();
}
TFGraph* TFGraphResolver::graph(const int graph_index) {
return graphs_.at(graph_index).get();
}
TFGraph* TFGraphResolver::main_graph() {
return this->graph(0);
}