* Copyright (c) 2025 Huawei Technologies Co., Ltd.
* This program is free software, you can redistribute it and/or modify it under the terms and conditions of
* CANN Open Software License Agreement Version 2.0 (the "License").
* Please refer to the License for details. You may not use this file except in compliance with the License.
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
* See LICENSE in the root of the software repository for the full text of the License.
*/
#include "autofuse_optimize.h"
#include "graph/debug/ge_attr_define.h"
#include "graph/utils/graph_utils.h"
#include "graph/passes/feature/constant_clip_pass.h"
#include "graph/passes/standard_optimize/constant_folding/constant_folding_pass.h"
#include "graph/passes/standard_optimize/common_subexpression_elimination_pass.h"
#include "graph/passes/pass_manager.h"
#include "common/checker.h"
#include "graph/operator_factory.h"
#include "common/plugin/ge_make_unique_util.h"
#include "common/checker.h"
#include "api/aclgrph/option_utils.h"
#include "graph/optimize/symbolic/infer_symbolic_shape/symbolic_shape_inference.h"
#include "graph/optimize/symbolic/infer_symbolic_shape/symbolic_info_pre_processor.h"
#include "graph/optimize/symbolic/infer_symbolic_shape/symbolic_info_post_processor.h"
#include "graph/optimize/symbolic/infer_symbolic_shape/symbolic_shape_symbolizer.h"
#include "graph/passes/feature/auto_fuse_pass.h"
#include "mmpa/mmpa_api.h"
#include "common/compile_profiling/ge_trace_wrapper.h"
namespace ge {
namespace {
constexpr char const *kCastInsertBeforeAutoFuse = "_is_insert_before_autofuse";
constexpr char const *kAutoFuseEnableOption = "--enable_autofuse";
Status InsertCastForDataTypeUnconsistantNode(const ge::ComputeGraphPtr &compute_graph) {
size_t insert_cast_op_count = 0UL;
for (const auto &node : compute_graph->GetAllNodes()) {
GE_ASSERT_NOTNULL(node);
const auto &op_desc = node->GetOpDesc();
GE_ASSERT_NOTNULL(op_desc);
for (const auto &out_anchor : node->GetAllOutDataAnchors()) {
GE_ASSERT_NOTNULL(out_anchor);
auto out_data_type = op_desc->GetOutputDesc(out_anchor->GetIdx()).GetDataType();
for (const auto &peer_in_anchor : out_anchor->GetPeerInDataAnchors()) {
GE_ASSERT_NOTNULL(peer_in_anchor);
const auto peer_node = peer_in_anchor->GetOwnerNode();
GE_ASSERT_NOTNULL(peer_node);
const auto &peer_op_desc = peer_node->GetOpDesc();
GE_ASSERT_NOTNULL(peer_op_desc);
auto peer_in_data_type = peer_op_desc->GetInputDesc(peer_in_anchor->GetIdx()).GetDataType();
if (out_data_type != peer_in_data_type) {
auto cast_op_name = "insert_before_autofuse_cast_op_" + std::to_string(insert_cast_op_count);
insert_cast_op_count++;
auto cast_operator = OperatorFactory::CreateOperator(cast_op_name.c_str(), CAST);
auto cast_op = OpDescUtils::GetOpDescFromOperator(cast_operator);
GE_ASSERT_NOTNULL(cast_op);
GE_ASSERT_TRUE(AttrUtils::SetBool(cast_op, kCastInsertBeforeAutoFuse, true));
GE_ASSERT_TRUE(AttrUtils::SetInt(cast_op, CAST_ATTR_DST_TYPE, static_cast<int64_t>(peer_in_data_type)));
GE_ASSERT_SUCCESS(cast_op->UpdateInputDesc(0, op_desc->GetOutputDesc(out_anchor->GetIdx())));
GE_ASSERT_SUCCESS(cast_op->UpdateOutputDesc(0, peer_op_desc->GetInputDesc(peer_in_anchor->GetIdx())));
GE_ASSERT_TRUE(GraphUtils::InsertNodeAfter(out_anchor, {peer_in_anchor}, cast_op, 0, 0) != nullptr);
GELOGI("Insert node:%s(%s) between node:%s(%s) output:%d and node:%s(%s) input:%d before autofuse.",
cast_op->GetName().c_str(), cast_op->GetType().c_str(), op_desc->GetName().c_str(),
op_desc->GetType().c_str(), out_anchor->GetIdx(), peer_op_desc->GetName().c_str(),
peer_op_desc->GetType().c_str(), peer_in_anchor->GetIdx());
}
}
}
}
return GRAPH_SUCCESS;
}
OpDescPtr CreateTensorShape(const GeTensorDesc &data_tensor) {
GeTensorPtr tensor = MakeShared<GeTensor>();
GE_ASSERT_NOTNULL(tensor, "New GeTensor failed");
tensor->MutableTensorDesc().SetDataType(DT_INT64);
tensor->MutableTensorDesc().SetFormat(FORMAT_ND);
const auto &dst_ge_shape = data_tensor.GetOriginShape();
auto dim_cnt = static_cast<int64_t>(dst_ge_shape.GetDimNum());
if (dim_cnt == 0) {
tensor->MutableTensorDesc().SetShape(GeShape({0}));
} else {
tensor->MutableTensorDesc().SetShape(GeShape(std::vector<int64_t>({dim_cnt})));
auto dst_shape = MakeUnique<int64_t[]>(dim_cnt);
GE_ASSERT_NOTNULL(dst_shape, "Malloc buffer failed, size:%zu", dim_cnt);
for (int64_t i = 0; i < dim_cnt; ++i) {
dst_shape[i] = dst_ge_shape.GetDim(static_cast<size_t>(i));
}
GE_ASSERT_GRAPH_SUCCESS(
tensor->SetData(reinterpret_cast<const uint8_t *>(dst_shape.get()), dim_cnt * sizeof(int64_t)),
"Set data to tensor failed");
}
tensor->MutableTensorDesc().SetOriginShape(tensor->MutableTensorDesc().GetShape());
GELOGD("Create shape input dim [%s]", dst_ge_shape.ToString().c_str());
return OpDescUtils::CreateConstOp(tensor);
}
Status AbnormalReshapeRecovery(const ge::ComputeGraphPtr &compute_graph) {
for (const auto &node : compute_graph->GetAllNodes()) {
if (node->GetType() != RESHAPE || node->GetAllInDataAnchorsSize() == 2U) {
continue;
}
GELOGI("Start recover abnormal reshape node %s[%s]", node->GetNamePtr(), node->GetTypePtr());
auto output_desc = node->GetOpDesc()->GetOutputDescPtr(0);
GE_ASSERT_NOTNULL(output_desc);
GE_ASSERT_TRUE(!output_desc->GetShape().IsUnknownShape(),
"reshape output shape is unknown shape while current reshape node %s[%s] is abnormal",
node->GetNamePtr(), node->GetTypePtr());
auto shape_op_desc = CreateTensorShape(*output_desc);
GE_ASSERT_NOTNULL(shape_op_desc, "[Create][TensorShape] Failed to add shape for reshape");
auto owner_compute_graph = node->GetOwnerComputeGraph();
GE_ASSERT_NOTNULL(owner_compute_graph);
auto shape_node = owner_compute_graph->InsertNodeBefore(node, shape_op_desc);
GE_ASSERT_NOTNULL(shape_node, "Add node:%s(%s) to graph:%s failed", shape_op_desc->GetNamePtr(),
shape_op_desc->GetTypePtr(), owner_compute_graph->GetName().c_str());
GE_ASSERT_SUCCESS(node->AddLinkFrom(shape_node));
GELOGD("[Reshape][Const] In %s Add edge between op:%s(%s)(out_index:0) and op:%s(%s)(in_index:1) success.",
owner_compute_graph->GetName().c_str(), shape_node->GetNamePtr(), shape_node->GetTypePtr(),
node->GetNamePtr(), RESHAPE);
}
return ge::GRAPH_SUCCESS;
}
Status GraphOptimizer1BeforeAutofuse(const ge::ComputeGraphPtr &compute_graph) {
GE_ASSERT_NOTNULL(compute_graph);
PassManager graph_pass;
GE_ASSERT_SUCCESS(graph_pass.AddPass("BeforeAutofusePass::CommonSubexpressionEliminationPass",
new (std::nothrow) CommonSubexpressionEliminationPass));
GE_ASSERT_SUCCESS(graph_pass.Run(compute_graph));
return GRAPH_SUCCESS;
}
Status GraphOptimizer2BeforeAutofuse(const ge::ComputeGraphPtr &compute_graph) {
GE_ASSERT_NOTNULL(compute_graph);
GE_ASSERT_SUCCESS(AbnormalReshapeRecovery(compute_graph));
GEPass ge_passes(compute_graph);
NamesToPass names_to_passes;
ConstantClipPass constant_clip_pass;
ConstantFoldingPass constant_folding_pass;
names_to_passes.emplace_back("BeforeAutofuse::ConstantClipPass", &constant_clip_pass);
names_to_passes.emplace_back("BeforeAutofuse::ConstantFoldingPass", &constant_folding_pass);
GE_ASSERT_SUCCESS(ge_passes.Run(names_to_passes));
PassManager graph_pass;
GE_ASSERT_SUCCESS(graph_pass.AddPass("BeforeAutofusePass::CommonSubexpressionEliminationPass",
new (std::nothrow) CommonSubexpressionEliminationPass));
GE_ASSERT_SUCCESS(graph_pass.Run(compute_graph));
return GRAPH_SUCCESS;
}
Status DeleteCastForDataTypeUnconsistantNode(const ge::ComputeGraphPtr &compute_graph) {
for (const auto &node : compute_graph->GetAllNodes()) {
GE_ASSERT_NOTNULL(node);
if (AttrUtils::HasAttr(node->GetOpDesc(), kCastInsertBeforeAutoFuse)) {
GE_ASSERT_SUCCESS(GraphUtils::IsolateNode(node, {0}), "Isolate node[%s][%s] failed.", node->GetTypePtr(),
node->GetNamePtr());
const auto owner_graph = node->GetOwnerComputeGraph();
GE_ASSERT_NOTNULL(owner_graph);
GE_ASSERT_SUCCESS(GraphUtils::RemoveNodeWithoutRelink(owner_graph, node), "Remove node[%s][%s] failed.",
node->GetTypePtr(), node->GetNamePtr());
GELOGI("Delete node:[%][%s] after autofuse", node->GetName().c_str(), node->GetType().c_str());
}
}
return GRAPH_SUCCESS;
}
bool IsEnableAutofuse() {
if (GetAutofuseFlagValue(kAutoFuseEnableOption) == "true") {
return true;
}
GELOGI("Option --enable_autofuse=true is not set in env AUTOFUSE_FLAGS, skip autofuse.");
return false;
}
}
Status AutofuseOptimize::PreProcess(const ge::ComputeGraphPtr &compute_graph) const {
GE_ASSERT_SUCCESS(GraphOptimizer1BeforeAutofuse(compute_graph));
GE_ASSERT_SUCCESS(InsertCastForDataTypeUnconsistantNode(compute_graph));
GE_ASSERT_SUCCESS(GraphOptimizer2BeforeAutofuse(compute_graph));
return GRAPH_SUCCESS;
}
Status AutofuseOptimize::PostProcess(const ge::ComputeGraphPtr &compute_graph) const {
GE_ASSERT_SUCCESS(SymbolicInfoPostProcessor::Run(compute_graph));
GE_ASSERT_SUCCESS(DeleteCastForDataTypeUnconsistantNode(compute_graph));
return GRAPH_SUCCESS;
}
Status AutofuseOptimize::Run(const ge::ComputeGraphPtr &compute_graph, const std::vector<GeTensor> &inputs) const {
if (!IsEnableAutofuse()) {
GELOGI("Auto fuse env is disable, skip it.");
return GRAPH_SUCCESS;
}
bool is_single_op_scene = false;
ge::AttrUtils::GetBool(compute_graph, ge::ATTR_SINGLE_OP_SCENE, is_single_op_scene);
if (is_single_op_scene) {
GELOGI("Skip auto fuse for single op scene.");
return GRAPH_SUCCESS;
}
GE_DUMP(compute_graph, "AutofuseOptimize_Entry");
GE_ASSERT_SUCCESS(PreProcess(compute_graph));
GE_DUMP(compute_graph, "AutofuseOptimize_AfterPreprocess");
PassManager graph_pass_for_autofuse;
GE_TRACE_START(Symbolize);
GE_ASSERT_GRAPH_SUCCESS(SymbolicShapeSymbolizer::Symbolize(compute_graph, inputs),
"Symbolize graph input failed, graph %s", compute_graph->GetName().c_str());
GE_COMPILE_TRACE_TIMESTAMP_END(Symbolize, ("SymbolicShapeInference::Symbolize::" + compute_graph->GetName()).c_str());
GE_ASSERT_SUCCESS(SymbolicInfoPreProcessor::Run(compute_graph, inputs));
GE_CHK_STATUS_RET(graph_pass_for_autofuse.AddPass("PreRun::AutoFusePass", new (std::nothrow) AutoFusePass()));
GE_CHK_STATUS_RET(graph_pass_for_autofuse.Run(compute_graph));
GE_ASSERT_SUCCESS(PostProcess(compute_graph));
GE_DUMP(compute_graph, "AutofuseOptimize_Exit");
return GRAPH_SUCCESS;
}
}