// Copyright (c) 2024 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 "paddle/fluid/framework/ir/sparse_conv_optim_pass.h" #include #include #include #include #include namespace paddle { namespace framework { namespace ir { #define GET_IR_NODE(node__) GET_IR_NODE_FROM_SUBGRAPH(node__, node__, pattern); #define GET_NODES \ GET_IR_NODE(sp_conv3d_x); \ GET_IR_NODE(sp_conv3d_kernel); \ GET_IR_NODE(sp_conv3d_op); \ GET_IR_NODE(sp_conv3d_out); SparseConvOptimPass::SparseConvOptimPass() { AddOpCompat(OpCompat("sparse_conv3d")) .AddInput("x") .IsTensor() .End() .AddInput("kernel") .IsTensor() .End() .AddOutput("out") .IsTensor() .End() .AddAttr("dilations") .IsType>() .End() .AddAttr("paddings") .IsType>() .End() .AddAttr("strides") .IsType>() .End() .AddAttr("groups") .IsNumGE(1) .End() .AddAttr("subm") .IsType() .End(); } void SparseConvOptimPass::ApplyImpl(ir::Graph* graph) const { const std::string pattern_name = "sparse_conv_optim_pattern"; FusePassBase::Init(pattern_name, graph); GraphPatternDetector gpd; auto* scope = param_scope(); PADDLE_ENFORCE_NOT_NULL( scope, common::errors::InvalidArgument( "Scope in SparseConvOptimPass should not be null.")); // Create pattern patterns::SparseConvOptimPattern pattern(gpd.mutable_pattern(), pattern_name); pattern(); int found_count = 0; auto handler = [&](const GraphPatternDetector::subgraph_t& subgraph, Graph* g) { GET_NODES; /* if (!IsCompat(subgraph, g)) { LOG(WARNING) << "sparse_conv_optim_pass compat check failed."; return; } */ std::vector dilations = PADDLE_GET_CONST( std::vector, sp_conv3d_op->Op()->GetAttr("dilations")); std::vector paddings = PADDLE_GET_CONST( std::vector, sp_conv3d_op->Op()->GetAttr("paddings")); std::vector strides = PADDLE_GET_CONST( std::vector, sp_conv3d_op->Op()->GetAttr("strides")); auto output_name = sp_conv3d_out->Name(); auto base_op_desc = *sp_conv3d_op->Op()->Proto(); PADDLE_ENFORCE_EQ((dilations.size() == paddings.size() && paddings.size() == strides.size()), true, common::errors::InvalidArgument( "The dilations, paddings, strides must have the same " "rank, but received %d, %d, %d.", dilations.size(), paddings.size(), strides.size())); bool is2D = dilations.size() == 2 ? true : false; auto sp_conv3d_to_2d = [&]() { if (is2D || paddings[0] != 0) return false; Node* sp_reshape_unsqueeze = nullptr; for (auto* node : sp_conv3d_x->inputs) { if (!node->IsOp() || node->Op()->Type() != "sparse_reshape") return false; auto shape = PADDLE_GET_CONST(std::vector, node->Op()->GetAttr("shape")); if (shape.size() != 5 || shape[0] != 1) return false; sp_reshape_unsqueeze = node; } if (!sp_reshape_unsqueeze || sp_reshape_unsqueeze->inputs.size() != 1) return false; auto sp_reshape_unsqueeze_x = sp_reshape_unsqueeze->inputs[0]; Node* reshape = nullptr; for (auto* node : sp_conv3d_kernel->inputs) { if (!node->IsOp() || node->Op()->Type() != "reshape2") return false; auto shape = PADDLE_GET_CONST(std::vector, node->Op()->GetAttr("shape")); if (shape.size() != 5 || shape[0] != 1) return false; reshape = node; } if (!reshape || reshape->inputs.size() != 1) return false; auto reshape_x = reshape->inputs[0]; Node* sp_reshape_squeeze = nullptr; for (auto* node : sp_conv3d_out->outputs) { if (!node->IsOp() || node->Op()->Type() != "sparse_reshape") return false; auto shape = PADDLE_GET_CONST(std::vector, node->Op()->GetAttr("shape")); if (shape.size() != 4) return false; sp_reshape_squeeze = node; } if (!sp_reshape_squeeze || sp_reshape_squeeze->outputs.size() != 1) return false; auto sp_reshape_squeeze_out = sp_reshape_squeeze->outputs[0]; dilations = {dilations[1], dilations[2]}; paddings = {paddings[1], paddings[2]}; strides = {strides[1], strides[2]}; sp_conv3d_op->Op()->SetAttr("dilations", dilations); sp_conv3d_op->Op()->SetAttr("paddings", paddings); sp_conv3d_op->Op()->SetAttr("strides", strides); sp_conv3d_op->Op()->SetInput("x", {sp_reshape_unsqueeze_x->Name()}); sp_conv3d_op->Op()->SetInput("kernel", {reshape_x->Name()}); sp_conv3d_op->Op()->SetOutput("out", {sp_reshape_squeeze_out->Name()}); IR_NODE_LINK_TO(sp_reshape_unsqueeze_x, sp_conv3d_op); // Input IR_NODE_LINK_TO(reshape_x, sp_conv3d_op) // Filter IR_NODE_LINK_TO(sp_conv3d_op, sp_reshape_squeeze_out); // Output std::unordered_set nodes2rm = {}; nodes2rm.insert(sp_reshape_unsqueeze); nodes2rm.insert(sp_conv3d_x); nodes2rm.insert(reshape); nodes2rm.insert(sp_conv3d_kernel); nodes2rm.insert(sp_conv3d_out); nodes2rm.insert(sp_reshape_squeeze); GraphSafeRemoveNodes(graph, nodes2rm); is2D = true; return true; }; if (sp_conv3d_to_2d()) { VLOG(4) << "SparseConv3D(output:" << output_name << ") has been converted to 2D implementation!"; } bool is_subm = PADDLE_GET_CONST(bool, sp_conv3d_op->Op()->GetAttr("subm")); if (is2D) { if (is_subm && strides[0] == 1 && strides[1] == 1 && dilations[0] == 1 && dilations[1] == 1) { sp_conv3d_op->Op()->SetType("sparse_conv3d_implicit_gemm"); } } else { if (is_subm && strides[0] == 1 && strides[1] == 1 && strides[2] == 1 && dilations[0] == 1 && dilations[1] == 1 && dilations[2] == 1) { sp_conv3d_op->Op()->SetType("sparse_conv3d_implicit_gemm"); } } found_count++; }; gpd(graph, handler); AddStatis(found_count); } } // namespace ir } // namespace framework } // namespace paddle REGISTER_PASS(sparse_conv_optim_pass, paddle::framework::ir::SparseConvOptimPass);