Update Conv-Add-Relu Fusion Transformation (#15834)

…n name to make it more intuitive.

### Description
Update Conv-Add-Relu Fusion Transformation to handle additional case
where NhwcFusedConv is present.



### Motivation and Context
Handle additional case where NhwcFusedConv is present.
This commit is contained in:
Jian Chen 2023-05-09 11:12:49 -07:00 committed by GitHub
parent 02d94bcc8e
commit c2e631d356
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 110 additions and 18 deletions

View file

@ -71,9 +71,9 @@ bool ConvFusionDataTypeCheck(const Node& conv_node) {
return true;
}
class ConvActivation : public NodeSelector {
class ConvActivationSelector : public NodeSelector {
public:
ConvActivation() = default;
ConvActivationSelector() = default;
std::optional<NodesToOptimizeIndices> Select(const GraphViewer& graph_viewer, const Node& node) const override {
const std::string_view node_ep = node.GetExecutionProviderType();
@ -172,7 +172,7 @@ class ConvAddRelu : public NodeSelector {
namespace actions {
using NTO = NodesToOptimize;
class FuseConvActivation : public ReplaceWithNew {
class FuseConvActivationAction : public ReplaceWithNew {
private:
std::string OpType(const RuntimeState&) const override { return "FusedConv"; }
@ -258,9 +258,9 @@ class FuseConvAddRelu : public ReplaceWithNew {
void RegisterConvActivationFusionRules(SelectorActionRegistry& registry) {
const auto name = "ConvAct";
auto action = std::make_unique<actions::FuseConvActivation>();
auto action = std::make_unique<actions::FuseConvActivationAction>();
#if !defined(ORT_MINIMAL_BUILD)
auto selector = std::make_unique<selectors::ConvActivation>();
auto selector = std::make_unique<selectors::ConvActivationSelector>();
registry.RegisterSelectorAndAction(name, {{"Conv", {1, 11}}},
std::move(selector), std::move(action));
#else

View file

@ -40,10 +40,9 @@ const Node* GetLoneConsumerNode(const GraphViewer& graph_viewer, const Node& nod
return &*node.OutputNodesBegin();
}
class ConvAddActivation : public NodeSelector {
class ConvAddActivationSelector : public NodeSelector {
public:
ConvAddActivation() = default;
ConvAddActivationSelector() = default;
std::optional<NodesToOptimizeIndices> Select(const GraphViewer& graph_viewer, const Node& node) const override {
const std::string_view node_ep = node.GetExecutionProviderType();
#ifdef MLAS_F16VEC_INTRINSICS_SUPPORTED
@ -62,7 +61,7 @@ class ConvAddActivation : public NodeSelector {
// 1. Its type is 'conv', 2. it has to satisfy the other requirements,like shape, please refer to SelectConvProducer for more info
const Node* conv_node = nullptr;
const auto* add_node = GetLoneConsumerNode(graph_viewer, node);
if (!add_node) {
if (add_node == nullptr) {
return std::nullopt;
}
// Let's support addition first, leave any-element-wise-op fusion in the future.
@ -73,13 +72,13 @@ class ConvAddActivation : public NodeSelector {
if (graph_utils::IsSupportedOptypeVersionAndDomain(*add_node, "Add", {7, 13, 14})) {
conv_node = SelectProducerConv(*add_node);
}
if (!conv_node) {
if (conv_node == nullptr) {
return std::nullopt;
}
// GetLoneConsumerNode will ensure outputedge_count is 1
const auto* act_node = GetLoneConsumerNode(graph_viewer, *add_node);
// even the next node is not a activation node, it's also fine.
if (!act_node) {
if (act_node == nullptr) {
// we can't fuse add-activation when add_node has multiple consumer nodes
act_node = nullptr;
} else if (SelectActivation(graph_viewer, *act_node)) {
@ -91,7 +90,7 @@ class ConvAddActivation : public NodeSelector {
NodesToOptimizeIndicesBuilder builder{};
builder.target_node = conv_node->Index();
builder.output_nodes = {add_node->Index()};
if (act_node) {
if (act_node != nullptr) {
builder.output_nodes.push_back(act_node->Index());
}
return builder.Build();
@ -176,8 +175,8 @@ class ConvAddActivation : public NodeSelector {
// Check if this is a single use convolution that hasn't already
// been fused with another Add/Sum node. The Add/Sum can also only be
// fused if the convolution isn't itself fused with an activation.
if ((inputs_node[n]->OpType() == "Conv") && (pre_input_defs_count < 4) && (producer_input_args_count.size() < 4) &&
(graph_utils::GetNodeAttribute(*inputs_node[n], "activation") == nullptr) && (inputs_node[n]->GetOutputEdgesCount() == 1)) {
if (
(inputs_node[n]->OpType() == "Conv" || inputs_node[n]->OpType() == "NhwcFusedConv") && (pre_input_defs_count < 4) && (producer_input_args_count.size() < 4) && (graph_utils::GetNodeAttribute(*inputs_node[n], "activation") == nullptr) && (inputs_node[n]->GetOutputEdgesCount() == 1)) {
if (pre_input_defs_count < 3) {
// The optional bias parameter is empty so set to an empty string.
// TODO, add a new null arguments for bias
@ -196,9 +195,14 @@ class ConvAddActivation : public NodeSelector {
namespace actions {
using NTO = NodesToOptimize;
class FuseConvAddActivation : public ReplaceWithNew {
class FuseConvAddActivationAction : public ReplaceWithNew {
public:
FuseConvAddActivationAction() = default;
private:
std::string OpType(const RuntimeState&) const override { return "FusedConv"; }
std::string OpType(const RuntimeState& runtimeState) const override {
return (runtimeState.selected_nodes.Target().OpType() == "Conv") ? "FusedConv" : "NhwcFusedConv";
}
std::string Domain(const RuntimeState&) const override { return kMSDomain; }
@ -272,8 +276,8 @@ class FuseConvAddActivation : public ReplaceWithNew {
void RegisterConvAddActivationFusionRules(SelectorActionRegistry& registry) {
const auto name = "ConvAddAct";
auto action = std::make_unique<actions::FuseConvAddActivation>();
auto selector = std::make_unique<selectors::ConvAddActivation>();
auto action = std::make_unique<actions::FuseConvAddActivationAction>();
auto selector = std::make_unique<selectors::ConvAddActivationSelector>();
registry.RegisterSelectorAndAction(name, {{"Conv", {1, 11}}},
std::move(selector), std::move(action));
}

View file

@ -112,6 +112,23 @@ TEST_F(ResNet50FusionTests, FuseConvAddUnitTest) {
ASSERT_STATUS_OK(Model::Save(*p_model, "conv_add_fp16_fused.onnx"));
ASSERT_TRUE(op_to_count["Add"] == 0); // Add removed from graph
}
TEST_F(ResNet50FusionTests, FuseConvReluUnitTest) {
constexpr const ORTCHAR_T* model_uri = MODEL_FOLDER "fusion/conv_relu_fp16.onnx";
std::shared_ptr<Model> p_model;
ASSERT_STATUS_OK(Model::Load(model_uri, p_model, nullptr, *logger));
Graph& graph = p_model->MainGraph();
for (auto& node : p_model->MainGraph().Nodes()) {
node.SetExecutionProviderType(kCpuExecutionProvider);
}
std::map<std::string, int> op_to_count = CountOpsInGraph(graph);
ASSERT_TRUE(op_to_count["Relu"] == 1);
onnxruntime::GraphTransformerManager graph_transformation_mgr{5};
ASSERT_STATUS_OK(graph_transformation_mgr.Register(std::make_unique<ConvActivationFusion>(), TransformerLevel::Level3));
ASSERT_STATUS_OK(graph_transformation_mgr.ApplyTransformers(graph, TransformerLevel::Level3, *logger));
op_to_count = CountOpsInGraph(graph);
ASSERT_STATUS_OK(Model::Save(*p_model, "conv_relu_fp16_fused.onnx"));
ASSERT_TRUE(op_to_count["Relu"] == 0); // Add removed from graph
}
#endif // defined(MLAS_F16VEC_INTRINSICS_SUPPORTED) && !defined(DISABLE_CONTRIB_OPS)
} // namespace test
} // namespace onnxruntime

View file

@ -0,0 +1,71 @@
onnx_conv_relu:Ë
;
Wgraph_input_cast_1graph_input_cast1"Cast*
to
 
;
Xgraph_input_cast_0graph_input_cast0"Cast*
to
 
H
graph_input_cast_0
graph_input_cast_1Y"Conv*
kernel_shape@@ 

Ygraph_output_cast_0"Relu
=
graph_output_cast_0Zgraph_output_cast0"Cast*
to 
test-modelZ
X




Z
W




b
Z




j
Y





j
Z




j,
graph_input_cast_0





j,
graph_input_cast_1





j-
graph_output_cast_0





B