Files
alibaba--mnn/tools/converter/source/torch/torchOptimize.cpp
T
2026-07-13 13:33:03 +08:00

556 lines
19 KiB
C++

//
// torchOptimize.cpp
// MNNConverter
//
// Created by MNN on 2021/05/12.
// Copyright © 2018, Alibaba Group Holding Limited
//
#include "torchOptimize.hpp"
#include <torch/csrc/jit/passes/freeze_module.h>
#include <torch/csrc/jit/passes/inliner.h>
#include <torch/csrc/jit/passes/peephole.h>
#include <torch/csrc/jit/passes/dead_code_elimination.h>
#include <torch/csrc/jit/passes/constant_pooling.h>
#include <torch/csrc/jit/passes/constant_propagation.h>
#include <torch/csrc/jit/passes/fold_conv_bn.h>
#include <torch/csrc/jit/passes/remove_dropout.h>
#include <torch/csrc/jit/passes/remove_inplace_ops.h>
#include <torch/csrc/jit/passes/remove_mutation.h>
#include <torch/csrc/jit/passes/normalize_ops.h>
#include <torch/csrc/jit/passes/graph_fuser.h>
#include <torch/csrc/jit/passes/erase_number_types.h>
#include "torchOpConverter.hpp"
namespace torch {
namespace jit {
void removeUselessOps(Block* block) {
for (auto it = block->nodes().begin(), end = block->nodes().end(); it != end; ++it) {
for (auto b : it->blocks()) {
removeUselessOps(b);
}
std::set<NodeKind> uselessKind = {
// prime
prim::Print,
prim::RaiseException,
prim::TimePoint,
prim::annotate,
// aten
aten::warn,
};
// useless op
if (uselessKind.count(it->kind())) {
for (size_t i = 0; i < it->inputs().size();) {
auto input = it->inputs().at(i);
// only handling constants bc of potential side effects
if (input->uses().size() == 1 &&
input->node()->kind() == prim::Constant) {
it->removeInput(i);
input->node()->destroy();
} else {
++i;
}
}
it.destroyCurrent();
}
if (it->kind() == prim::Loop) {
if (it->outputs().empty()) {
it.destroyCurrent();
}
}
if (it->kind().toUnqualString() == std::string("data") ||
it->kind() == prim::NumToTensor ||
it->kind() == aten::ScalarImplicit ||
it->kind() == aten::contiguous ||
it->kind() == aten::dropout ||
it->kind() == aten::dropout_ ||
it->kind() == aten::feature_dropout ||
it->kind() == aten::clone) {
it->output()->replaceAllUsesWith(it->input(0));
for (int i = it->inputs().size()-1; i >= 0; i--) {
it->removeInput(i);
}
it.destroyCurrent();
}
if (it->kind() == aten::detach ||
it->kind() == aten::list ||
it->kind().toDisplayString() == std::string("aten::cpu")) {
it->output()->replaceAllUsesWith(it->input(0));
for (int i = it->inputs().size()-1; i >= 0; i--) {
it->removeInput(i);
}
it.destroyCurrent();
}
if (it->kind() == aten::to) {
auto dst = it->input(1);
auto ivalue = toIValue(dst);
if(!ivalue->isInt()) {
it->output()->replaceAllUsesWith(it->input(0));
for (int i = it->inputs().size()-1; i >= 0; i--) {
it->removeInput(i);
}
it.destroyCurrent();
}
}
if (it->kind() == aten::slice) {
auto start = it->input(2);
auto end = it->input(3);
// [0 : 1 : INT_MAX] can remove
if (toIValue(start) && getValue<int64_t>(start) == 0 &&
toIValue(end) && getValue<int64_t>(end) == 9223372036854775807) {
if (it->inputs().size() > 4) {
auto stride = it->input(4);
if (toIValue(stride) && getValue<int64_t>(stride) != 1) {
continue;
}
}
it->output()->replaceAllUsesWith(it->input(0));
for (int i = it->inputs().size()-1; i >= 0; i--) {
it->removeInput(i);
}
it.destroyCurrent();
}
}
}
}
/*
We rewrite something like:
x = {v0}
x.append(v1)
foo(x)
x.append(v2)
bar(x)
to:
x1 = {v0, v1}
foo(x1)
x2 = {v0, v1, v2}
bar(x2)
this is a strengthen version of RemoveListMutation
*/
void removeListAppend(Graph* graph, Block* block) {
for (auto it = block->nodes().begin(); it != block->nodes().end();) {
auto* node = *it;
it++;
for (Block* sub_block : node->blocks()) {
removeListAppend(graph, sub_block);
}
if (!(node->kind() == aten::append && node->inputs().at(0)->node()->kind() == prim::ListConstruct)) {
continue;
}
Value* mutated_value = node->inputs().at(0);
Node* list_node = mutated_value->node();
Node* new_list_node = graph->create(prim::ListConstruct, 1);
for (Value* input : list_node->inputs()) {
new_list_node->addInput(input);
}
new_list_node->addInput(node->inputs().at(1));
new_list_node->copyMetadata(list_node);
new_list_node->insertAfter(node);
new_list_node->output()->setType(list_node->output()->type());
mutated_value->replaceAllUsesAfterNodeWith(node, new_list_node->output());
node->destroy();
}
}
/*
We remove all ListConstruct op with only one input and not used by aten::cat, like below:
%116 : Tensor?[] = prim::ListConstruct(%115)
%alpha0.1 : Tensor = aten::index_put_(%alpha.1, %116, %x.1, %16)
ListConstruct used by aten::cat will be reserved like below:
%features.2 : Tensor[] = prim::ListConstruct(%input3.4)
%concated_features.380 : Tensor = aten::cat(%features.2, %5)
Attention: Runing this pass after removeListAppend
*/
void removeListConstructOps(Block* block) {
for (auto it = block->nodes().begin(), end = block->nodes().end(); it != end; ++it) {
for (auto b : it->blocks()) {
removeUselessOps(b);
}
if (it->kind() == prim::ListConstruct && it->inputs().size() == 1) {
bool remove = true;
for (auto use : it->output()->uses()) {
if (use.user->kind() == aten::cat) {
remove = false;
break;
}
}
if (remove) {
it->output()->replaceAllUsesWith(it->input(0));
it->removeInput(0);
it.destroyCurrent();
}
}
}
}
/*
We rewrite something like:
y = chunk(x)
v1, v2, v3 = ListUnpack(y)
to:
v1, v2, v3 = chunk(x)
*/
void FuseListUnpack(Block* block) {
for (auto it = block->nodes().begin(); it != block->nodes().end();) {
auto* node = *it;
it++;
for (Block* sub_block : node->blocks()) {
FuseListUnpack(sub_block);
}
std::set<NodeKind> fusekind = {
aten::split,
aten::split_with_sizes,
aten::split_with_sizes,
aten::unsafe_split_with_sizes,
aten::unbind,
aten::chunk,
aten::unsafe_chunk,
aten::where,
};
if (fusekind.count(it->kind()) &&
it->outputs().size() == 1 &&
it->output()->uses().size() == 1) {
const auto listunpack = it->output()->uses()[0].user;
if (listunpack->kind() == prim::ListUnpack) {
// it->i_(Symbol::fromQualString("attr::_outputs"),
// static_cast<int64_t>(listunpack->outputs().size()));
for (auto i = 0; i < listunpack->outputs().size(); ++i) {
auto new_output = it->addOutput();
new_output->copyMetadata(listunpack->output(i));
}
listunpack->removeAllInputs();
it->eraseOutput(0);
listunpack->replaceAllUsesWith(*it);
listunpack->destroy();
}
}
}
}
/*
We rewrite something like:
x = ListConstruct(v1, v2, v3)
y = stack(y, axis)
to:
y = stack(v1, v2, v3, axis)
*/
void FuseListStack(Block* block) {
for (auto it = block->nodes().begin(); it != block->nodes().end();) {
auto* node = *it;
it++;
for (Block* sub_block : node->blocks()) {
FuseListUnpack(sub_block);
}
std::set<NodeKind> fusekind = {
aten::stack
};
if (it->kind() == aten::stack) {
auto input = it->input(0)->node();
if (input->kind() == prim::ListConstruct) {
auto axis = it->input(1);
it->removeAllInputs();
for (int i = 0; i < input->inputs().size(); i++) {
it->addInput(input->input(i));
}
it->addInput(axis);
input->destroy();
}
}
}
}
/*
We rewrite something like:
%y : int, %z : int = prim::Loop(%6, %2, %y.1, %z.1) # <ipython-input-14-d0a2ead71c2a>:6:4
block0(%i.1 : int, %y.11 : int, %z.11 : int):
%y.5 : int = aten::add(%y.11, %i.1) # <ipython-input-14-d0a2ead71c2a>:7:8
%z.5 : int = aten::mul(%z.11, %5) # <ipython-input-14-d0a2ead71c2a>:8:8
-> (%2, %y.5, %z.5)
to:
%y : int, %z : int = prim::Loop(%6, %2, %y.1, %z.1) # <ipython-input-14-d0a2ead71c2a>:6:4
block0(%i.1 : int, %y.11 : int, %z.11 : int):
%y.5 : int = aten::add(%y.1, %i.1) # <ipython-input-14-d0a2ead71c2a>:7:8
%z.5 : int = aten::mul(%z.1, %5) # <ipython-input-14-d0a2ead71c2a>:8:8
-> (%2, %y.5, %z.5)
*/
void LoopBodyLegal(Graph* graph, Block* block) {
for (auto it = block->nodes().begin(); it != block->nodes().end();) {
auto* node = *it;
it++;
for (Block* sub_block : node->blocks()) {
LoopBodyLegal(graph, sub_block);
}
if (node->kind() == prim::Loop) {
auto body = node->blocks()[0];
for (int i = body->inputs().size() - 1; i > 0; i--) {
body->inputs().at(i)->replaceAllUsesWith(node->inputs().at(i + 1));
}
}
}
}
/*
inference input type, such as below:
x: Tensor;
y = aten::embedding(_, x);
then x's scalar type is int
*/
void InputTypeInfer(Graph* graph) {
// TODO: add more typeOps and propagateOps
static std::map<NodeKind, std::vector<ScalarType>> opInputTypes {
// aten::embedding(Tensor weight, Tensor indices, int padding_idx, bool scale_grad_by_freq, bool sparse) -> Tensor
{ aten::embedding, { ScalarType::Float, ScalarType::Int } },
// aten::matmul(Tensor self, Tensor other) -> Tensor
{ aten::matmul, { ScalarType::Float, ScalarType::Float } },
// aten::linear(Tensor input, Tensor weight, Tensor bias) -> Tensor
{ aten::linear, { ScalarType::Float, ScalarType::Float, ScalarType::Float } },
// aten::conv2d(Tensor input, Tensor weight, Tensor bias, int[] stride, int[] padding, int[] dilation, int groups) -> Tensor
{ aten::conv2d, { ScalarType::Float, ScalarType::Float, ScalarType::Float } },
};
static std::set<NodeKind> typePropagateOps {
// shape change
aten::slice, aten::view, aten::transpose, aten::permute,
// compute
aten::add, aten::sub, aten::mul, aten::div,
};
auto mergeType = [](ScalarType type, ScalarType newType) {
if (type == newType || newType == c10::ScalarType::Undefined) {
return type;
}
if (type == c10::ScalarType::Undefined) {
return newType;
}
MNN_ASSERT(false);
return c10::ScalarType::Undefined;
};
std::function<ScalarType(Value*)> getScalarType = [&](Value* input) -> ScalarType {
auto inputType = ScalarType::Undefined;
for (auto use : input->uses()) {
int idx = -1;
for (int i = 0; i < use.user->inputs().size(); i++) {
if (use.user->input(i) == input) {
idx = i;
}
}
auto newType = ScalarType::Undefined;
if (typePropagateOps.find(use.user->kind()) != typePropagateOps.end()) {
newType = getScalarType(use.user->output());
} else {
const auto iter = opInputTypes.find(use.user->kind());
if (iter != opInputTypes.end() && idx >= 0 && idx < iter->second.size()) {
newType = iter->second[idx];
}
}
inputType = mergeType(inputType, newType);
}
return inputType;
};
for (auto input : graph->inputs()) {
auto type = input->type()->cast<TensorType>();
if (!type) {
continue;
}
auto scalarType = getScalarType(input);
input->setType(type->withScalarType(scalarType));
}
}
/*
Unpack outputs, such as below:
return List(x, y); -> return x, y;
return Dict('x', x); -> return x;
return Tuple(Tuple(x, y), z); return x, y, z;
*/
void OutputsUnpack(Graph* graph) {
std::function<void(Node* tuple, std::vector<Node*>&, std::vector<Value*>&)> flattenTuple =
[&flattenTuple](Node* tuple, std::vector<Node*>& tuples, std::vector<Value*>& values) -> void
{
tuples.push_back(tuple);
for (auto input : tuple->inputs()) {
auto node = input->node();
if (node->kind() == prim::TupleConstruct) {
flattenTuple(node, tuples, values);
} else {
values.push_back(input);
}
}
};
for (int i = 0; i < graph->outputs().size(); i++) {
auto node = graph->outputs()[i]->node();
// unpack output
switch (node->kind()) {
case prim::TupleConstruct: {
std::vector<Node*> tuples;
std::vector<Value*> values;
flattenTuple(node, tuples, values);
for (auto realOutput : values) {
graph->registerOutput(realOutput);
}
graph->eraseOutput(i);
for (auto tuple : tuples) {
if (!tuple->hasUses()) {
tuple->destroy();
}
}
break;
}
case prim::DictConstruct: {
graph->registerOutput(node->input(1));
graph->eraseOutput(i);
node->destroy();
break;
}
case prim::ListConstruct: {
for (int i = 0; i < node->inputs().size(); i++) {
graph->registerOutput(node->input(i));
}
graph->eraseOutput(i);
node->destroy();
break;
}
}
}
}
/*
distinguish overloaded function, such as below:
torch.max(Tensor, Tensor) is compare
torch.max(Tensor, int) is reduce
*/
void overloadDistinguish(Block* block) {
auto symb = c10::Symbol::fromQualString("attr::mnn_tag");
for (auto it = block->nodes().begin(); it != block->nodes().end();) {
auto* node = *it;
it++;
for (Block* sub_block : node->blocks()) {
overloadDistinguish(sub_block);
}
switch (node->kind()) {
// min/max(Tensor, Tensor) is compare
// min/max(Tensor, int) is reduce
case aten::min:
case aten::max:
case aten::sum:
if (node->inputs().size() > 1 &&
(node->input(1)->type()->kind() == c10::TypeKind::IntType ||
node->input(1)->type()->kind() == c10::TypeKind::ListType)) {
node->s_(symb, "reduce");
} else {
node->s_(symb, "binary");
}
break;
case aten::index:
if (node->input(1)->node()->kind() == prim::ListConstruct) {
node->s_(symb, "stridedslice");
}
break;
default:
// do nothing
break;
}
}
}
/*
fuse as_tensor, such as below:
d = prim::dtype(b);
c = aten::as_tensor(a, d);
-> c = aten::type_as(a, b)
*/
void FuseAsTensor(Graph* graph, Block* block) {
for (auto it = block->nodes().begin(); it != block->nodes().end();) {
auto* node = *it;
it++;
for (Block* sub_block : node->blocks()) {
FuseAsTensor(graph, sub_block);
}
if (node->kind() == prim::dtype) {
for (auto use : node->output(0)->uses()) {
auto as_tensor = use.user;
Node* typeAs = graph->create(aten::type_as, 1);
typeAs->addInput(as_tensor->input(0));
typeAs->addInput(node->input(0));
typeAs->output(0)->copyMetadata(as_tensor->output(0));
as_tensor->replaceAllUsesWith(typeAs);
as_tensor->removeAllInputs();
as_tensor->destroy();
}
}
}
}
/*
fuse uniform, such as below:
d = aten::empty(shape);
c = aten::uniform_(a, low, hight);
-> c = aten::uniform_(shape, low, hight)
*/
void FuseUniform(Graph* graph, Block* block) {
for (auto it = block->nodes().begin(); it != block->nodes().end();) {
auto* node = *it;
it++;
for (Block* sub_block : node->blocks()) {
FuseUniform(graph, sub_block);
}
if (it->kind().toUnqualString() == std::string("uniform_")) {
auto input = it->input(0)->node();
if (input->kind() == aten::empty) {
it->replaceInput(0, input->input(0));
input->destroy();
}
}
}
}
std::shared_ptr<Graph> torchOptPass(Module& module) {
module.eval();
module = torch::jit::freeze_module(module);
auto graph = module.get_methods()[0].graph();
Inline(*(graph.get()));
// normalize, Example: aten::absolute -> aten::abs
NormalizeOps(graph);
// remove some ops, Example: prim::RaiseException
removeUselessOps(graph->block());
removeDropout(module);
// Example: x = x + 1; -> x_1 = x + 1;
RemoveInplaceOps(graph);
// Example: x = {v0}; x.append(v1); -> x = {v0, v1};
// RemoveListMutation(graph);
removeListAppend(graph.get(), graph->block());
removeListConstructOps(graph->block());
//RemoveTensorMutation(graph);
// elimate dead code
EliminateDeadCode(graph, DCESideEffectPolicy::ALLOW_DELETING_NODES_WITH_SIDE_EFFECTS);
// constant propagation
ConstantPooling(graph);
ConstantPropagation(graph);
// fuse
FuseGraph(graph, true);
PeepholeOptimize(graph);
FuseAddMM(graph);
// FoldConvBatchNorm(module);
FuseListUnpack(graph->block());
FuseListStack(graph->block());
// distinguish overload function
overloadDistinguish(graph->block());
// legal loop body's var name
LoopBodyLegal(graph.get(), graph->block());
// infer input tensor's scalar type by op
InputTypeInfer(graph.get());
// split output tensor if wrap with list/tuple
OutputsUnpack(graph.get());
// dtype + as_tensor -> type_as
FuseAsTensor(graph.get(), graph->block());
// empty + uniform -> uniform
FuseUniform(graph.get(), graph->block());
#ifdef MNN_DUMP_TORCHSCRIPT
graph->dump();
#endif
return graph;
}
}
}