已合并
【PR】: feat:autofuse支持extendconv2d #4290
hemahema创建于 8月6日
【PR】: feat:autofuse支持extendconv2d #4290
已合并
hemahema创建于 8月6日
共 29 个文件变更+3270-150
@@ -75,6 +75,7 @@ const std::string kConvSubgraphName = "conv_subgraph";
75const std::string kMatMulNodeType = "MatMulV3";75const std::string kMatMulNodeType = "MatMulV3";
76const std::string kMatMulBatchMatmulNodeType = "BatchMatMulV3";76const std::string kMatMulBatchMatmulNodeType = "BatchMatMulV3";
77const std::string kConv2DNodeType = "Conv2DV2";77const std::string kConv2DNodeType = "Conv2DV2";
78+const std::string kExtendConv2DNodeType = "ExtendConv2D";
78constexpr int32_t kSocVersionLen = 50;79constexpr int32_t kSocVersionLen = 50;
79 80 
80gert::KernelContextHolder BuildTilingParseContextHolder(81gert::KernelContextHolder BuildTilingParseContextHolder(
@@ -863,6 +864,7 @@ ge::graphStatus AutofuseNodeWithMatmulTiling(const ge::Operator &op, const fe::P
863 864 
864ge::graphStatus AutofuseNodeWithConvTiling(const ge::Operator &op, const fe::PlatFormInfos &ge_platform_infos,865ge::graphStatus AutofuseNodeWithConvTiling(const ge::Operator &op, const fe::PlatFormInfos &ge_platform_infos,
865 OpRunInfoV2 &run_info, ge::ConstNodePtr node) {866 OpRunInfoV2 &run_info, ge::ConstNodePtr node) {
867+ // CV 融合二次 tiling:先跑子图中的 Conv2DV2/ExtendConv2D tiling,再跑 Autofuse tiling。
866 ge::Operator conv_op = ge::OpDescUtils::CreateOperatorFromNode(node);868 ge::Operator conv_op = ge::OpDescUtils::CreateOperatorFromNode(node);
867 auto op_desc = ge::OpDescUtils::GetOpDescFromOperator(conv_op);869 auto op_desc = ge::OpDescUtils::GetOpDescFromOperator(conv_op);
868 const auto &space_registry = gert::DefaultOpImplSpaceRegistryV2::GetInstance().GetSpaceRegistry(870 const auto &space_registry = gert::DefaultOpImplSpaceRegistryV2::GetInstance().GetSpaceRegistry(
@@ -916,7 +918,8 @@ ge::graphStatus AicoreRtParseAndTiling(const ge::Operator &op, const fe::PlatFor
916 if (conv_subgraph != nullptr) {918 if (conv_subgraph != nullptr) {
917 GELOGI("Get autofuse conv node: %s", op_desc->GetName().c_str());919 GELOGI("Get autofuse conv node: %s", op_desc->GetName().c_str());
918 for (auto &node : conv_subgraph->GetAllNodes()) {920 for (auto &node : conv_subgraph->GetAllNodes()) {
919- if (node->GetType() == kConv2DNodeType.c_str()) {921+ // ExtendConv2D 与 Conv2DV2 共用 CV 融合二次 tiling 路径。
922+ if (node->GetType() == kConv2DNodeType.c_str() || node->GetType() == kExtendConv2DNodeType.c_str()) {
920 GE_ASSERT_GRAPH_SUCCESS(AutofuseNodeWithConvTiling(op, ge_platform_infos, run_info, node));923 GE_ASSERT_GRAPH_SUCCESS(AutofuseNodeWithConvTiling(op, ge_platform_infos, run_info, node));
921 return ge::GRAPH_SUCCESS;924 return ge::GRAPH_SUCCESS;
922 }925 }
@@ -1832,8 +1832,6 @@ Status BackendUtils::CreateNewNodeInputDescAttr(const NodePtr &new_node, const N
1832 for (input_idx = 0U; input_idx < node1_input_map.size(); input_idx++) {1832 for (input_idx = 0U; input_idx < node1_input_map.size(); input_idx++) {
1833 auto input_desc = op_desc->MutableInputDesc(input_idx);1833 auto input_desc = op_desc->MutableInputDesc(input_idx);
1834 GE_ASSERT_NOTNULL(input_desc);1834 GE_ASSERT_NOTNULL(input_desc);
1835- auto input_attr = input_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();
1836- GE_ASSERT_NOTNULL(input_attr);
1837 1835 
1838 auto node1_op_desc = node1->GetOpDescBarePtr();1836 auto node1_op_desc = node1->GetOpDescBarePtr();
1839 const auto node1_input_desc = node1_op_desc->MutableInputDesc(node1_input_map[input_idx]);1837 const auto node1_input_desc = node1_op_desc->MutableInputDesc(node1_input_map[input_idx]);
@@ -1841,19 +1839,14 @@ Status BackendUtils::CreateNewNodeInputDescAttr(const NodePtr &new_node, const N
1841 auto node1_attr = node1_input_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();1839 auto node1_attr = node1_input_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();
1842 GE_ASSERT_NOTNULL(node1_attr);1840 GE_ASSERT_NOTNULL(node1_attr);
1843 1841 
1844- input_desc->SetOriginShape(node1_input_desc->GetOriginShape());1842+ *input_desc = *node1_input_desc; // 整描述拷贝,保留 format/shape 之外的扩展属性(含 SymbolicDesc)。
1845- input_desc->SetShape(node1_input_desc->GetShape());1843+ auto input_attr = input_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();
1846- input_desc->SetDataType(node1_input_desc->GetDataType());1844+ GE_ASSERT_NOTNULL(input_attr);
1847- input_desc->SetOriginDataType(node1_input_desc->GetOriginDataType());
1848- input_desc->SetFormat(node1_input_desc->GetFormat());
1849- input_desc->SetOriginFormat(node1_input_desc->GetOriginFormat());
1850 *input_attr = *node1_attr;1845 *input_attr = *node1_attr;
1851 }1846 }
1852 for (; input_idx < node2_input_map.size(); input_idx++) {1847 for (; input_idx < node2_input_map.size(); input_idx++) {
1853 auto input_desc = op_desc->MutableInputDesc(input_idx);1848 auto input_desc = op_desc->MutableInputDesc(input_idx);
1854 GE_ASSERT_NOTNULL(input_desc);1849 GE_ASSERT_NOTNULL(input_desc);
1855- auto input_attr = input_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();
1856- GE_ASSERT_NOTNULL(input_attr);
1857 1850 
1858 GE_ASSERT_NOTNULL(node2);1851 GE_ASSERT_NOTNULL(node2);
1859 auto node2_op_desc = node2->GetOpDescBarePtr();1852 auto node2_op_desc = node2->GetOpDescBarePtr();
@@ -1863,12 +1856,9 @@ Status BackendUtils::CreateNewNodeInputDescAttr(const NodePtr &new_node, const N
1863 auto node2_attr = node2_input_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();1856 auto node2_attr = node2_input_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();
1864 GE_ASSERT_NOTNULL(node2_attr);1857 GE_ASSERT_NOTNULL(node2_attr);
1865 1858 
1866- input_desc->SetOriginShape(node2_input_desc->GetOriginShape());1859+ *input_desc = *node2_input_desc; // 整描述拷贝,保留 format/shape 之外的扩展属性(含 SymbolicDesc)。
1867- input_desc->SetShape(node2_input_desc->GetShape());1860+ auto input_attr = input_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();
1868- input_desc->SetDataType(node2_input_desc->GetDataType());1861+ GE_ASSERT_NOTNULL(input_attr);
1869- input_desc->SetOriginDataType(node2_input_desc->GetOriginDataType());
1870- input_desc->SetFormat(node2_input_desc->GetFormat());
1871- input_desc->SetOriginFormat(node2_input_desc->GetOriginFormat());
1872 *input_attr = *node2_attr;1862 *input_attr = *node2_attr;
1873 }1863 }
1874 return SUCCESS;1864 return SUCCESS;
@@ -1883,8 +1873,6 @@ Status BackendUtils::CreateNewNodeOutputDescAttr(const NodePtr &new_node, const
1883 for (output_idx = 0U; output_idx < node1_output_map.size(); output_idx++) {1873 for (output_idx = 0U; output_idx < node1_output_map.size(); output_idx++) {
1884 auto output_desc = op_desc->MutableOutputDesc(output_idx);1874 auto output_desc = op_desc->MutableOutputDesc(output_idx);
1885 GE_ASSERT_NOTNULL(output_desc);1875 GE_ASSERT_NOTNULL(output_desc);
1886- auto output_attr = output_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();
1887- GE_ASSERT_NOTNULL(output_attr);
1888 1876 
1889 auto node1_op_desc = node1->GetOpDescBarePtr();1877 auto node1_op_desc = node1->GetOpDescBarePtr();
1890 auto node1_output_desc = node1_op_desc->MutableOutputDesc(node1_output_map[output_idx]);1878 auto node1_output_desc = node1_op_desc->MutableOutputDesc(node1_output_map[output_idx]);
@@ -1892,19 +1880,14 @@ Status BackendUtils::CreateNewNodeOutputDescAttr(const NodePtr &new_node, const
1892 auto node1_attr = node1_output_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();1880 auto node1_attr = node1_output_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();
1893 GE_ASSERT_NOTNULL(node1_attr);1881 GE_ASSERT_NOTNULL(node1_attr);
1894 1882 
1895- output_desc->SetOriginShape(node1_output_desc->GetOriginShape());1883+ *output_desc = *node1_output_desc;
1896- output_desc->SetShape(node1_output_desc->GetShape());1884+ auto output_attr = output_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();
1897- output_desc->SetDataType(node1_output_desc->GetDataType());1885+ GE_ASSERT_NOTNULL(output_attr);
1898- output_desc->SetOriginDataType(node1_output_desc->GetOriginDataType());
1899- output_desc->SetFormat(node1_output_desc->GetFormat());
1900- output_desc->SetOriginFormat(node1_output_desc->GetOriginFormat());
1901 *output_attr = *node1_attr;1886 *output_attr = *node1_attr;
1902 }1887 }
1903 for (; output_idx < node2_output_map.size(); output_idx++) {1888 for (; output_idx < node2_output_map.size(); output_idx++) {
1904 auto output_desc = op_desc->MutableOutputDesc(output_idx);1889 auto output_desc = op_desc->MutableOutputDesc(output_idx);
1905 GE_ASSERT_NOTNULL(output_desc);1890 GE_ASSERT_NOTNULL(output_desc);
1906- auto output_attr = output_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();
1907- GE_ASSERT_NOTNULL(output_attr);
1908 1891 
1909 GE_ASSERT_NOTNULL(node2);1892 GE_ASSERT_NOTNULL(node2);
1910 auto node2_op_desc = node2->GetOpDescBarePtr();1893 auto node2_op_desc = node2->GetOpDescBarePtr();
@@ -1914,12 +1897,9 @@ Status BackendUtils::CreateNewNodeOutputDescAttr(const NodePtr &new_node, const
1914 auto node2_attr = node2_output_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();1897 auto node2_attr = node2_output_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();
1915 GE_ASSERT_NOTNULL(node2_attr);1898 GE_ASSERT_NOTNULL(node2_attr);
1916 1899 
1917- output_desc->SetOriginShape(node2_output_desc->GetOriginShape());1900+ *output_desc = *node2_output_desc;
1918- output_desc->SetShape(node2_output_desc->GetShape());1901+ auto output_attr = output_desc->GetOrCreateAttrsGroup<ge::SymbolicDescAttr>();
1919- output_desc->SetDataType(node2_output_desc->GetDataType());1902+ GE_ASSERT_NOTNULL(output_attr);
1920- output_desc->SetOriginDataType(node2_output_desc->GetOriginDataType());
1921- output_desc->SetFormat(node2_output_desc->GetFormat());
1922- output_desc->SetOriginFormat(node2_output_desc->GetOriginFormat());
1923 *output_attr = *node2_attr;1903 *output_attr = *node2_attr;
1924 }1904 }
1925 return SUCCESS;1905 return SUCCESS;
@@ -27,10 +27,44 @@
27#include "graph/graph.h"27#include "graph/graph.h"
28#include "graph/utils/graph_utils_ex.h"28#include "graph/utils/graph_utils_ex.h"
29#include "graph/utils/file_utils.h"29#include "graph/utils/file_utils.h"
30+#include "graph/attribute_group/attr_group_symbolic_desc.h"
31+#include "graph/symbolizer/symbolic.h"
30 32 
31namespace ge {33namespace ge {
32using namespace autofuse;34using namespace autofuse;
33namespace {35namespace {
36+graphStatus CompleteStaticSymbolicShapeForConv(const ComputeGraphPtr &graph) {
37+ // ExtendConv2D 等场景下部分静态 shape 可能未挂 SymbolicDescAttr;
38+ // lowering 前补齐,避免后续 shape 推导/融合判断把静态图误判为动态。
39+ for (const auto &node : graph->GetAllNodes()) {
40+ if (node->GetType() != "ExtendConv2D") {
41+ continue;
42+ }
43+ const auto op_desc = node->GetOpDesc();
44+ GE_ASSERT_NOTNULL(op_desc);
45+ for (size_t i = 0U; i < op_desc->GetOutputsSize(); ++i) {
46+ const auto output_desc = op_desc->MutableOutputDesc(i);
47+ GE_ASSERT_NOTNULL(output_desc);
48+ if (output_desc->GetAttrsGroup<SymbolicDescAttr>() != nullptr) {
49+ continue;
50+ }
51+ const auto &shape =
52+ output_desc->IsOriginShapeInitialized() ? output_desc->GetOriginShape() : output_desc->GetShape();
53+ if (shape.IsUnknownShape()) {
54+ continue;
55+ }
56+ const auto sym_attr = output_desc->GetOrCreateAttrsGroup<SymbolicDescAttr>();
57+ GE_ASSERT_NOTNULL(sym_attr);
58+ auto &dims = sym_attr->symbolic_tensor.MutableOriginSymbolShape().MutableDims();
59+ dims.reserve(shape.GetDimNum());
60+ for (const auto dim : shape.GetDims()) {
61+ dims.emplace_back(Symbol(dim));
62+ }
63+ }
64+ }
65+ return GRAPH_SUCCESS;
66+}
67+ 
34bool IsNodeShouldPrune(const NodePtr &node) {68bool IsNodeShouldPrune(const NodePtr &node) {
35 if (!node->GetOutNodes().empty()) {69 if (!node->GetOutNodes().empty()) {
36 return false;70 return false;
@@ -166,6 +200,7 @@ graphStatus AscIrLowerer::Lowering(const ComputeGraphPtr &graph) {
166 do_lowered_ = false;200 do_lowered_ = false;
167 return GRAPH_SUCCESS;201 return GRAPH_SUCCESS;
168 }202 }
203+ GE_ASSERT_GRAPH_SUCCESS(CompleteStaticSymbolicShapeForConv(graph));
169 // 若使能子图落盘,提前深拷贝原始图204 // 若使能子图落盘,提前深拷贝原始图
170 if (ge::AutoFuseConfig::LoweringConfig().enable_subgraph_recover) {205 if (ge::AutoFuseConfig::LoweringConfig().enable_subgraph_recover) {
171 pre_lowering_graph_ = ComGraphMakeShared<ComputeGraph>(graph->GetName() + "_pre_lowering");206 pre_lowering_graph_ = ComGraphMakeShared<ComputeGraph>(graph->GetName() + "_pre_lowering");
@@ -67,7 +67,13 @@ namespace loop {
67 GET_IR_ATTR(OpType, Obj.Src())->SetData_format(Attr.data_format); \67 GET_IR_ATTR(OpType, Obj.Src())->SetData_format(Attr.data_format); \
68 GET_IR_ATTR(OpType, Obj.Src())->SetOffset_x(Attr.offset_x); \68 GET_IR_ATTR(OpType, Obj.Src())->SetOffset_x(Attr.offset_x); \
69 GET_IR_ATTR(OpType, Obj.Src())->SetEnable_hf32(Attr.enable_hf32); \69 GET_IR_ATTR(OpType, Obj.Src())->SetEnable_hf32(Attr.enable_hf32); \
70- GET_IR_ATTR(OpType, Obj.Src())->SetHas_relu(0)70+ GET_IR_ATTR(OpType, Obj.Src())->SetFixed_shift_value(Attr.fixed_shift_value)
71+ 
72+// ExtendConv2D 在 Conv2D 公共属性基础上补充 round_mode / relu0。
73+#define SET_EXTEND_CONV2D_ATTRS(OpType, Obj, Attr) \
74+ SET_CONV2D_ATTRS(OpType, Obj, Attr); \
75+ GET_IR_ATTR(OpType, Obj.Src())->SetRound_mode(Attr.round_mode); \
76+ GET_IR_ATTR(OpType, Obj.Src())->SetEnable_relu0(Attr.enable_relu0)
71 77 
72template <typename T>78template <typename T>
73bool InferAscirDataType(const std::vector<DataType> &input_dtypes, std::vector<DataType> &output_dtypes) {79bool InferAscirDataType(const std::vector<DataType> &input_dtypes, std::vector<DataType> &output_dtypes) {
@@ -408,7 +414,31 @@ class AscOverrides final : public OpOverrides {
408 GE_WARN_ASSERT(inputs.size() >= 2U && inputs.size() <= 4U);414 GE_WARN_ASSERT(inputs.size() >= 2U && inputs.size() <= 4U);
409 415 
410 AscVar conv;416 AscVar conv;
411- if (inputs.size() == 2U) {417+ // ExtendConv2D 按 (bias, scale0) 组合映射到 4 种 ASCIR 变体;Conv2DV2 仍走原有 bias/offset_w 组合。
418+ if (conv2d_attr.is_extend_conv2d) {
X
Xxuyafei8月18日

提取单独子函数处理

likedislike
419+ if (inputs.size() == 2U) {
420+ conv = MakeAscVar<ascir_op::ExtendConv2D>(explicit_output_dtypes, inputs[0], inputs[1]);
421+ SET_EXTEND_CONV2D_ATTRS(ExtendConv2D, conv, conv2d_attr);
422+ } else if (inputs.size() == 3U) {
423+ if (conv2d_attr.has_bias) {
424+ conv = MakeAscVar<ascir_op::ExtendConv2DBias>(explicit_output_dtypes, inputs[0], inputs[1], inputs[2]);
425+ SET_EXTEND_CONV2D_ATTRS(ExtendConv2DBias, conv, conv2d_attr);
426+ } else if (conv2d_attr.has_scale0) {
427+ conv = MakeAscVar<ascir_op::ExtendConv2DScale>(explicit_output_dtypes, inputs[0], inputs[1], inputs[2]);
428+ SET_EXTEND_CONV2D_ATTRS(ExtendConv2DScale, conv, conv2d_attr);
429+ } else {
430+ GE_WARN_ASSERT(false, "ExtendConv2D attr info not match, input=3, bias=false, scale0=false.");
431+ }
432+ } else if (inputs.size() == 4U) {
433+ if (conv2d_attr.has_bias && conv2d_attr.has_scale0) {
434+ conv = MakeAscVar<ascir_op::ExtendConv2DBiasScale>(explicit_output_dtypes, inputs[0], inputs[1], inputs[2],
435+ inputs[3]);
436+ SET_EXTEND_CONV2D_ATTRS(ExtendConv2DBiasScale, conv, conv2d_attr);
437+ } else {
438+ GE_WARN_ASSERT(false, "ExtendConv2D attr info not match, input=4, bias or scale0 is false.");
439+ }
440+ }
441+ } else if (inputs.size() == 2U) {
412 conv = MakeAscVar<ascir_op::Conv2D>(explicit_output_dtypes, inputs[0], inputs[1]);442 conv = MakeAscVar<ascir_op::Conv2D>(explicit_output_dtypes, inputs[0], inputs[1]);
413 SET_CONV2D_ATTRS(Conv2D, conv, conv2d_attr);443 SET_CONV2D_ATTRS(Conv2D, conv, conv2d_attr);
414 } else if (inputs.size() == 3U) {444 } else if (inputs.size() == 3U) {
@@ -73,6 +73,20 @@ KernelBox StoreExtern(const ge::OutDataAnchorPtr &dst) {
73 return lowered->Set(dst, std::make_shared<KernelBoxMeta>());73 return lowered->Set(dst, std::make_shared<KernelBoxMeta>());
74}74}
75 75 
76+KernelBox StoreIgnoredOutput(const ge::OutDataAnchorPtr &dst) {
77+ LOWERING_ASSERT_NOTNULL(dst);
78+ LOWERING_ASSERT_NOTNULL(dst->GetOwnerNode());
79+ LOWERING_ASSERT_NOTNULL(dst->GetOwnerNode()->GetOpDesc());
80+ const auto lowered = dst->GetOwnerNode()->GetOpDesc()->GetOrCreateAttrsGroup<LoweringResultAttrs>();
81+ LOWERING_ASSERT_NOTNULL(lowered);
82+ auto meta = std::make_shared<KernelBoxMeta>();
83+ // 默认 KernelBoxMeta 是 Extern;改成占位后,GetNodeKernelBoxes 不会因这一路空槽 Fallback 整节点。
84+ meta->type = FuseType::kCube;
85+ meta->is_support = true;
86+ meta->realize = false;
87+ return lowered->Set(dst, meta);
88+}
89+ 
76namespace {90namespace {
77void DropLowerResultIfNeeded(const std::shared_ptr<KernelBoxMeta> &meta, const ge::OutDataAnchorPtr &dst,91void DropLowerResultIfNeeded(const std::shared_ptr<KernelBoxMeta> &meta, const ge::OutDataAnchorPtr &dst,
78 const LoopVar &result) {92 const LoopVar &result) {
@@ -437,28 +451,6 @@ LoopVar Permute(const LoopVar &op, std::vector<size_t> order) {
437 return LoopVar(std::make_shared<PermuteOp>(op.Op(), std::move(order)));451 return LoopVar(std::make_shared<PermuteOp>(op.Op(), std::move(order)));
438}452}
439 453 
440-// 检查描述符是否都是静态的
441-bool AreDescriptorsStatic(const ge::OpDesc *op_desc, bool check_inputs) {
442- if (check_inputs) {
443- for (size_t i = 0; i < op_desc->GetInputsSize(); i++) {
444- const auto input_desc = op_desc->MutableInputDesc(i);
445- GE_ASSERT_NOTNULL(input_desc);
446- if (input_desc->GetOriginShape().IsUnknownShape()) {
447- return false;
448- }
449- }
450- } else {
451- for (size_t i = 0; i < op_desc->GetOutputsSize(); i++) {
452- const auto output_desc = op_desc->MutableOutputDesc(i);
453- GE_ASSERT_NOTNULL(output_desc);
454- if (output_desc->GetOriginShape().IsUnknownShape()) {
455- return false;
456- }
457- }
458- }
459- return true;
460-}
461- 
462bool IsStaticShape(std::vector<Expression> output_dims, const ge::OutDataAnchorPtr &dst,454bool IsStaticShape(std::vector<Expression> output_dims, const ge::OutDataAnchorPtr &dst,
463 const std::vector<ge::InDataAnchorPtr> &inputs) {455 const std::vector<ge::InDataAnchorPtr> &inputs) {
464 // 检查输出维度是否都是常量表达式456 // 检查输出维度是否都是常量表达式
@@ -468,24 +460,29 @@ bool IsStaticShape(std::vector<Expression> output_dims, const ge::OutDataAnchorP
468 }460 }
469 }461 }
470 462 
471- // 检查输出节点的形状是否都是静态的463+ // dst / inputs 已是本次 lowering 的非空边,只检查这些边生产者的 output desc,不扫节点上全部锚点。
464+ // optional 输入的节点 InputDesc 可能是 FORMAT_RESERVED 占位,MutableInputDesc 会得到空指针。
472 GE_ASSERT_NOTNULL(dst);465 GE_ASSERT_NOTNULL(dst);
473 GE_ASSERT_NOTNULL(dst->GetOwnerNode());466 GE_ASSERT_NOTNULL(dst->GetOwnerNode());
474 const auto &outputs_op_desc = dst->GetOwnerNode()->GetOpDescBarePtr();467 const auto &outputs_op_desc = dst->GetOwnerNode()->GetOpDescBarePtr();
475 GE_ASSERT_NOTNULL(outputs_op_desc);468 GE_ASSERT_NOTNULL(outputs_op_desc);
476- if (!AreDescriptorsStatic(outputs_op_desc, false)) {469+ const auto output_desc = outputs_op_desc->MutableOutputDesc(dst->GetIdx());
470+ GE_ASSERT_NOTNULL(output_desc);
471+ if (output_desc->GetOriginShape().IsUnknownShape()) {
477 return false;472 return false;
478 }473 }
479 474 
480- // 检查输入节点的形状是否都是静态的475+ for (const auto &input : inputs) {
481- if (!inputs.empty()) {476+ GE_ASSERT_NOTNULL(input);
482- for (const auto &input : inputs) {477+ const auto peer = input->GetPeerOutAnchor();
483- GE_ASSERT_NOTNULL(input->GetOwnerNode());478+ GE_ASSERT_NOTNULL(peer);
484- const auto &inputs_op_desc = input->GetOwnerNode()->GetOpDescBarePtr();479+ GE_ASSERT_NOTNULL(peer->GetOwnerNode());
485- GE_ASSERT_NOTNULL(inputs_op_desc);480+ const auto peer_op_desc = peer->GetOwnerNode()->GetOpDescBarePtr();
486- if (!AreDescriptorsStatic(inputs_op_desc, true)) {481+ GE_ASSERT_NOTNULL(peer_op_desc);
487- return false;482+ const auto peer_desc = peer_op_desc->MutableOutputDesc(peer->GetIdx());
488- }483+ GE_ASSERT_NOTNULL(peer_desc);
484+ if (peer_desc->GetOriginShape().IsUnknownShape()) {
485+ return false;
489 }486 }
490 }487 }
491 488 
@@ -153,6 +153,8 @@ KernelBox StorePack(const ge::OutDataAnchorPtr &dst, const std::vector<ge::InDat
153 int64_t packed_dim);153 int64_t packed_dim);
154KernelBox StoreReshape(const ge::OutDataAnchorPtr &dst, const LoopVar &src);154KernelBox StoreReshape(const ge::OutDataAnchorPtr &dst, const LoopVar &src);
155KernelBox StoreExtern(const ge::OutDataAnchorPtr &dst);155KernelBox StoreExtern(const ge::OutDataAnchorPtr &dst);
156+// 给未使用的 optional 输出挂非 Extern 占位,避免空 KernelBox 把同节点其它输出整段 Fallback。
157+KernelBox StoreIgnoredOutput(const ge::OutDataAnchorPtr &dst);
156KernelBox GetKernelBox(const ge::OutDataAnchorPtr &dst);158KernelBox GetKernelBox(const ge::OutDataAnchorPtr &dst);
157 159 
158LoopVar Scalar(std::string face, ge::DataType dtype);160LoopVar Scalar(std::string face, ge::DataType dtype);
@@ -1132,7 +1132,8 @@ class StoreConv2DOp : public LoopOp {
1132 << "pads=[" << loop::StrJoin(attrs_.pads) << "], dilations=[" << loop::StrJoin(attrs_.dilations) << "], "1132 << "pads=[" << loop::StrJoin(attrs_.pads) << "], dilations=[" << loop::StrJoin(attrs_.dilations) << "], "
1133 << "groups=" << attrs_.groups << ", data_format=" << attrs_.data_format << ", offset_x=" << attrs_.offset_x1133 << "groups=" << attrs_.groups << ", data_format=" << attrs_.data_format << ", offset_x=" << attrs_.offset_x
1134 << ", pad_mode=" << attrs_.pad_mode << ", enable_hf32=" << attrs_.enable_hf321134 << ", pad_mode=" << attrs_.pad_mode << ", enable_hf32=" << attrs_.enable_hf32
1135- << ", has_bias=" << attrs_.has_bias << ", has_offset_w=" << attrs_.has_offset_w << ")";1135+ << ", fixed_shift_value=" << attrs_.fixed_shift_value << ", has_bias=" << attrs_.has_bias
1136+ << ", has_offset_w=" << attrs_.has_offset_w << ")";
1136 return ss_conv.str();1137 return ss_conv.str();
1137 }1138 }
1138 1139 
@@ -9,6 +9,7 @@
9 */9 */
10 10 
11#include "liftings.h"11#include "liftings.h"
12+#include <algorithm>
12#include "common/checker.h"13#include "common/checker.h"
13#include "graph_metadef/graph/debug/ge_util.h"14#include "graph_metadef/graph/debug/ge_util.h"
14#include "graph/debug/ge_attr_define.h"15#include "graph/debug/ge_attr_define.h"
@@ -31,12 +32,16 @@ constexpr size_t kMinComputeNodes = 2U;
31constexpr size_t kMinOneNodeInData = 64U;32constexpr size_t kMinOneNodeInData = 64U;
32constexpr size_t kMatmulMinInputNum = 2U;33constexpr size_t kMatmulMinInputNum = 2U;
33constexpr size_t kNumOne = 1U;34constexpr size_t kNumOne = 1U;
35+constexpr int32_t kConvFilterInputIndex = 1;
36+// 二次 tiling 缺省 workspace,单位字节,对应 2MB。
37+constexpr int64_t kDefaultAscendcOpParaSize = 2 * 1024 * 1024;
34const char *const kMatmulSubgraph = "matmul_subgraph";38const char *const kMatmulSubgraph = "matmul_subgraph";
35const char *const kConvSubgraph = "conv_subgraph";39const char *const kConvSubgraph = "conv_subgraph";
36const char *const kMMV3Type = "MatMulV3";40const char *const kMMV3Type = "MatMulV3";
37const char *const kBMMV3Type = "BatchMatMulV3";41const char *const kBMMV3Type = "BatchMatMulV3";
38const char *const kConv2DType = "Conv2D";42const char *const kConv2DType = "Conv2D";
39const char *const kConv2DV2Type = "Conv2DV2";43const char *const kConv2DV2Type = "Conv2DV2";
44+const char *const kExtendConv2DType = "ExtendConv2D";
40 45 
41GeShape TransferShapeBetweenHwcnNchw(const GeShape &old_shape, const Format &old_format, const Format &new_format) {46GeShape TransferShapeBetweenHwcnNchw(const GeShape &old_shape, const Format &old_format, const Format &new_format) {
42 if (old_shape.GetDimNum() != 4U) {47 if (old_shape.GetDimNum() != 4U) {
@@ -55,42 +60,91 @@ GeShape TransferShapeBetweenHwcnNchw(const GeShape &old_shape, const Format &old
55 return old_shape;60 return old_shape;
56}61}
57 62 
63+NodePtr AddConvSubgraphInputNode(const ComputeGraphPtr &sub_graph, const Node *org_node,
64+ const GeTensorDesc &conv_input_desc, const int32_t input_index,
65+ const OutDataAnchorPtr &peer_anchor) {
66+ OpDescPtr input_op_desc;
67+ if (peer_anchor != nullptr) {
68+ const auto peer_node = peer_anchor->GetOwnerNode();
69+ GE_ASSERT_NOTNULL(peer_node);
70+ input_op_desc = GraphUtils::CopyOpDesc(peer_node->GetOpDesc(), nullptr);
71+ GE_ASSERT_NOTNULL(input_op_desc);
72+ input_op_desc->SetName(peer_node->GetName());
73+ } else {
74+ const auto input_name = org_node->GetName() + "_autofuse_input_" + std::to_string(input_index);
75+ input_op_desc = ComGraphMakeShared<OpDesc>(input_name, DATA);
76+ GE_ASSERT_NOTNULL(input_op_desc);
77+ GE_ASSERT_GRAPH_SUCCESS(input_op_desc->AddOutputDesc(conv_input_desc));
78+ GELOGI("[AutoFuseConvSubgraph] Restore disconnected input %s:%d with Data node %s.", org_node->GetNamePtr(),
79+ input_index, input_name.c_str());
80+ }
81+ return sub_graph->AddNode(input_op_desc);
82+}
83+ 
58graphStatus CreateConvSubgraphAttr(const NodePtr &node, vector<const Node *> &compute_ops, size_t &cube_real_inputs) {84graphStatus CreateConvSubgraphAttr(const NodePtr &node, vector<const Node *> &compute_ops, size_t &cube_real_inputs) {
85+ // ExtendConv2D 存在 optional 输入,按真实锚点连边,不再用 cube_real_inputs 做连续紧凑索引。
86+ (void)cube_real_inputs;
59 const auto &sub_graph = ComGraphMakeShared<ComputeGraph>(kConvSubgraph + node->GetName());87 const auto &sub_graph = ComGraphMakeShared<ComputeGraph>(kConvSubgraph + node->GetName());
60 GE_ASSERT_NOTNULL(sub_graph);88 GE_ASSERT_NOTNULL(sub_graph);
61 for (auto *org_node : compute_ops) {89 for (auto *org_node : compute_ops) {
62- if (org_node->GetType() == kConv2DV2Type) {90+ // Conv2DV2 / ExtendConv2D 都需要落盘到 conv_subgraph,供运行时二次 tiling 使用。
91+ if (org_node->GetType() == kConv2DV2Type || org_node->GetType() == kExtendConv2DType) {
63 const auto &op_desc = GraphUtils::CopyOpDesc(org_node->GetOpDesc(), nullptr);92 const auto &op_desc = GraphUtils::CopyOpDesc(org_node->GetOpDesc(), nullptr);
64 GE_ASSERT_NOTNULL(op_desc);93 GE_ASSERT_NOTNULL(op_desc);
65 op_desc->SetName(org_node->GetName());94 op_desc->SetName(org_node->GetName());
66- auto conv_output_desc = op_desc->MutableOutputDesc(0);95+ const auto &ir_attr_names = op_desc->GetIrAttrNames();
67- const auto data_format =96+ // ops-nn 增加了 private attr fixed_shift_value(紧跟 ascendc_op_para_size)。
68- conv_output_desc->GetFormat(); // 1.单算子在自动融合后formatJudge更新format,自动融合需在subgraph更新format97+ // 子图拷贝后若缺 ascendc_op_para_size,二次 tiling 按 IR attr 序构造上下文会越界,这里显式补齐。
98+ if (std::find(ir_attr_names.cbegin(), ir_attr_names.cend(), "ascendc_op_para_size") == ir_attr_names.cend()) {
99+ op_desc->AppendIrAttrName("ascendc_op_para_size");
100+ GELOGI("[AutoFuseConvSubgraph] Node:%s(%s) missing IR attr ascendc_op_para_size, append it.",
101+ op_desc->GetNamePtr(), op_desc->GetTypePtr());
102+ }
103+ if (!AttrUtils::HasAttr(op_desc, "ascendc_op_para_size")) {
104+ GELOGI("[AutoFuseConvSubgraph] Node:%s(%s) missing attr ascendc_op_para_size, set default %" PRId64 " bytes.",
105+ op_desc->GetNamePtr(), op_desc->GetTypePtr(), kDefaultAscendcOpParaSize);
106+ GE_ASSERT_TRUE(AttrUtils::SetInt(op_desc, "ascendc_op_para_size", kDefaultAscendcOpParaSize));
107+ }
69 auto conv_node = sub_graph->AddNode(op_desc);108 auto conv_node = sub_graph->AddNode(op_desc);
70 GE_ASSERT_NOTNULL(conv_node);109 GE_ASSERT_NOTNULL(conv_node);
71- for (auto i = 0U; i < cube_real_inputs; i++) {110+ const auto &org_in_anchors = org_node->GetAllInDataAnchors();
72- auto conv_input_desc = op_desc->MutableInputDesc(i);111+ for (const auto &org_in_anchor : org_in_anchors) {
73- GE_ASSERT_NOTNULL(conv_input_desc);112+ GE_ASSERT_NOTNULL(org_in_anchor);
74- const auto old_format = conv_input_desc->GetFormat();113+ const auto org_input_index = org_in_anchor->GetIdx();
75- conv_input_desc->SetFormat(114+ auto peer_anchor = org_in_anchor->GetPeerOutAnchor();
76- data_format); // 2.conv节点的输入更新为与输出一致(filter输入会不一样,单算子执行时tiling会校验这个format一致)115+ auto conv_input_desc = op_desc->MutableInputDesc(org_input_index);
77- const auto new_shape = TransferShapeBetweenHwcnNchw(conv_input_desc->GetShape(), old_format, data_format);116+ if (conv_input_desc == nullptr) {
78- conv_input_desc->SetShape(new_shape);117+ GELOGI("[AutoFuseConvSubgraph] Node:%s(%s), input:%d has no input desc.", op_desc->GetNamePtr(),
79- conv_input_desc->SetOriginShape(new_shape);118+ op_desc->GetTypePtr(), org_input_index);
80- const auto &src_anchor = node->GetInDataAnchor(i);119+ continue;
81- GE_ASSERT_NOTNULL(src_anchor);120+ }
82- auto peer_anchor = src_anchor->GetPeerOutAnchor();121+ const bool is_input_desc_valid = conv_input_desc->IsValid() == GRAPH_SUCCESS;
83- GE_ASSERT_NOTNULL(peer_anchor);122+ if (!is_input_desc_valid) {
84- auto peer_node = peer_anchor->GetOwnerNode();123+ GE_ASSERT_TRUE(org_input_index > kConvFilterInputIndex,
85- GE_ASSERT_NOTNULL(peer_node);124+ "Node:%s(%s) required input:%d has invalid tensor desc.", op_desc->GetNamePtr(),
86- const auto &peer_op_desc = GraphUtils::CopyOpDesc(peer_node->GetOpDesc(), nullptr);125+ op_desc->GetTypePtr(), org_input_index);
87- GE_ASSERT_NOTNULL(peer_op_desc);126+ GELOGI("[AutoFuseConvSubgraph] Node:%s(%s), optional input:%d has invalid tensor desc.",
88- peer_op_desc->SetName(peer_node->GetName());127+ op_desc->GetNamePtr(), op_desc->GetTypePtr(), org_input_index);
89- auto conv_peer_node = sub_graph->AddNode(peer_op_desc);128+ continue;
129+ }
130+ // 针对5102,二次 tiling 侧 filter 期望 FRACTAL_Z;同步更新融合节点与子图节点,避免 format 校验失败。
131+ if (org_input_index == kConvFilterInputIndex) {
132+ auto fused_filter_desc = node->GetOpDesc()->MutableInputDesc(kConvFilterInputIndex);
133+ GE_ASSERT_NOTNULL(fused_filter_desc);
134+ fused_filter_desc->SetFormat(FORMAT_FRACTAL_Z);
135+ // conv_input_desc 是拷出来挂到 conv_subgraph 上的那份。如果convfixpipe前移并完成format转换,
136+ // 这里应该不需要手动设置。
137+ conv_input_desc->SetFormat(FORMAT_FRACTAL_Z);
138+ }
139+ // 原始边仍在时复制真实 peer;边已被无用边清理或融合流程删除时,根据有效 input desc 创建 Data 占位。
140+ auto conv_peer_node =
141+ AddConvSubgraphInputNode(sub_graph, org_node, *conv_input_desc, org_input_index, peer_anchor);
90 GE_ASSERT_NOTNULL(conv_peer_node);142 GE_ASSERT_NOTNULL(conv_peer_node);
91- const auto &peer_node_out_anchor = conv_peer_node->GetOutDataAnchor(peer_anchor->GetIdx());143+ // Data 占位只有 output 0;复制真实 peer 时则保持其原始输出索引。
144+ const auto peer_output_index = peer_anchor == nullptr ? 0 : peer_anchor->GetIdx();
145+ const auto &peer_node_out_anchor = conv_peer_node->GetOutDataAnchor(peer_output_index);
92 GE_ASSERT_NOTNULL(peer_node_out_anchor);146 GE_ASSERT_NOTNULL(peer_node_out_anchor);
93- GE_ASSERT_GRAPH_SUCCESS(GraphUtils::AddEdge(peer_node_out_anchor, conv_node->GetInDataAnchor(i)));147+ GE_ASSERT_GRAPH_SUCCESS(GraphUtils::AddEdge(peer_node_out_anchor, conv_node->GetInDataAnchor(org_input_index)));
94 }148 }
95 break;149 break;
96 }150 }
@@ -176,7 +230,9 @@ bool IsCubeSkipLifting(const NodePtr &node, const size_t min_compute_nodes, cons
176 continue;230 continue;
177 }231 }
178 if (asc_node->GetType() == kConv2DType || asc_node->GetType() == kConv2DBias ||232 if (asc_node->GetType() == kConv2DType || asc_node->GetType() == kConv2DBias ||
179- asc_node->GetType() == kConv2DOffset || asc_node->GetType() == kConv2DOffsetBias) {233+ asc_node->GetType() == kConv2DOffset || asc_node->GetType() == kConv2DOffsetBias ||
234+ asc_node->GetType() == kExtendConv2DType || asc_node->GetType() == kExtendConv2DBias ||
235+ asc_node->GetType() == kExtendConv2DScale || asc_node->GetType() == kExtendConv2DBiasScale) {
180 has_conv = true;236 has_conv = true;
181 } else {237 } else {
182 // 当前cube只有matmul和conv,非conv就是matmul238 // 当前cube只有matmul和conv,非conv就是matmul
@@ -38,10 +38,16 @@ struct Conv2DAttr {
38 int64_t groups = 1;38 int64_t groups = 1;
39 std::string data_format = "NCHW";39 std::string data_format = "NCHW";
40 int64_t offset_x = 0;40 int64_t offset_x = 0;
41+ std::string round_mode = "rint"; // ExtendConv2D only
41 std::string pad_mode = "SPECIFIC";42 std::string pad_mode = "SPECIFIC";
42 bool enable_hf32 = false;43 bool enable_hf32 = false;
44+ bool enable_relu0 = false; // ExtendConv2D only
45+ int64_t fixed_shift_value = 0;
46+ // 以下字段非 op proto 属性,供 ASCIR/codegen 选择算子变体;后端据此还原 ExtendConv2D 逻辑输入槽位。
43 bool has_bias = false;47 bool has_bias = false;
44 bool has_offset_w = false;48 bool has_offset_w = false;
49+ bool has_scale0 = false; // ExtendConv2D only
50+ bool is_extend_conv2d = false;
45};51};
46} // namespace ge52} // namespace ge
47 53 
@@ -1300,8 +1300,12 @@ void BuildConv2DAttr(const OpDescPtr &op_desc, Conv2DAttr &conv2d_attr) {
1300 (void)AttrUtils::GetInt(op_desc, "groups", conv2d_attr.groups);1300 (void)AttrUtils::GetInt(op_desc, "groups", conv2d_attr.groups);
1301 (void)AttrUtils::GetStr(op_desc, "data_format", conv2d_attr.data_format);1301 (void)AttrUtils::GetStr(op_desc, "data_format", conv2d_attr.data_format);
1302 (void)AttrUtils::GetInt(op_desc, "offset_x", conv2d_attr.offset_x);1302 (void)AttrUtils::GetInt(op_desc, "offset_x", conv2d_attr.offset_x);
1303+ (void)AttrUtils::GetStr(op_desc, "pad_mode", conv2d_attr.pad_mode);
1303 (void)AttrUtils::GetStr(op_desc, "padding", conv2d_attr.pad_mode);1304 (void)AttrUtils::GetStr(op_desc, "padding", conv2d_attr.pad_mode);
1304 (void)AttrUtils::GetBool(op_desc, "enable_hf32", conv2d_attr.enable_hf32);1305 (void)AttrUtils::GetBool(op_desc, "enable_hf32", conv2d_attr.enable_hf32);
1306+ (void)AttrUtils::GetInt(op_desc, "fixed_shift_value", conv2d_attr.fixed_shift_value);
1307+ // ExtendConv2D 独有属性;Conv2DV2 上 GetStr 失败时保持默认 "rint"。
1308+ (void)AttrUtils::GetStr(op_desc, "round_mode", conv2d_attr.round_mode);
1305}1309}
1306 1310 
1307graphStatus GetConv2DDims(const NodePtr &node, const ge::InDataAnchorPtr &x_anchor,1311graphStatus GetConv2DDims(const NodePtr &node, const ge::InDataAnchorPtr &x_anchor,
@@ -1337,7 +1341,19 @@ void SetConv2DBiasAndOffsetW(const NodePtr &node, const OpDescPtr &op_desc, Conv
1337 }1341 }
1338}1342}
1339 1343 
1344+void SetExtendConv2DFixpipeParams(const NodePtr &node, const OpDescPtr &op_desc, Conv2DAttr &conv2d_attr) {
1345+ // ExtendConv2D 通过可选 scale0 开启 fixpipe quant/dequant 路径。
1346+ auto scale0_index = op_desc->GetInputIndexByName("scale0");
1347+ if (scale0_index >= 0) {
1348+ const auto scale0_anchor = node->GetInDataAnchor(scale0_index);
1349+ if (scale0_anchor != nullptr && scale0_anchor->GetPeerOutAnchor() != nullptr) {
1350+ conv2d_attr.has_scale0 = true;
1351+ }
1352+ }
1353+}
1354+ 
1340std::vector<ge::InDataAnchorPtr> CollectConv2DInputs(const NodePtr &node) {1355std::vector<ge::InDataAnchorPtr> CollectConv2DInputs(const NodePtr &node) {
1356+ // 仅收集已连接输入。ExtendConv2D 空槽由后端按 has_bias(2)/has_scale0(4) 还原,不再传 nullptr_inputs_index。
1341 std::vector<ge::InDataAnchorPtr> inputs;1357 std::vector<ge::InDataAnchorPtr> inputs;
1342 for (const auto &in_anchor : node->GetAllInDataAnchors()) {1358 for (const auto &in_anchor : node->GetAllInDataAnchors()) {
1343 GE_ASSERT_NOTNULL(in_anchor);1359 GE_ASSERT_NOTNULL(in_anchor);
@@ -1350,7 +1366,8 @@ std::vector<ge::InDataAnchorPtr> CollectConv2DInputs(const NodePtr &node) {
1350}1366}
1351} // anonymous namespace1367} // anonymous namespace
1352 1368 
1353-graphStatus LowerConv2D(const NodePtr &node) {1369+// Conv2DV2 / ExtendConv2D 共用 lowering;is_extend_conv2d 决定 ASCIR 算子变体与扩展属性。
1370+graphStatus InnerLowerConv2D(const NodePtr &node, bool is_extend_conv2d) {
1354 const auto x_anchor = node->GetInDataAnchor(0);1371 const auto x_anchor = node->GetInDataAnchor(0);
1355 const auto w_anchor = node->GetInDataAnchor(1);1372 const auto w_anchor = node->GetInDataAnchor(1);
1356 GE_ASSERT_NOTNULL(x_anchor);1373 GE_ASSERT_NOTNULL(x_anchor);
@@ -1363,28 +1380,53 @@ graphStatus LowerConv2D(const NodePtr &node) {
1363 auto op_desc = node->GetOpDesc();1380 auto op_desc = node->GetOpDesc();
1364 GE_ASSERT_NOTNULL(op_desc);1381 GE_ASSERT_NOTNULL(op_desc);
1365 1382 
1383+ conv2d_attr.is_extend_conv2d = is_extend_conv2d;
1366 BuildConv2DAttr(op_desc, conv2d_attr);1384 BuildConv2DAttr(op_desc, conv2d_attr);
1367 SetConv2DBiasAndOffsetW(node, op_desc, conv2d_attr);1385 SetConv2DBiasAndOffsetW(node, op_desc, conv2d_attr);
1368- 1386+ if (is_extend_conv2d) {
1369- auto output_desc = op_desc->GetOutputDescPtr(0);1387+ (void)AttrUtils::GetBool(op_desc, "enable_relu0", conv2d_attr.enable_relu0);
1370- if (output_desc != nullptr) {1388+ SetExtendConv2DFixpipeParams(node, op_desc, conv2d_attr);
1371- conv2d_attr.output_dtype = output_desc->GetDataType();
1372 }1389 }
1390+ auto output_desc = op_desc->GetOutputDescPtr(0);
1391+ GE_ASSERT_NOTNULL(output_desc);
1392+ conv2d_attr.output_dtype = output_desc->GetDataType();
1373 1393 
1374 auto inputs = CollectConv2DInputs(node);1394 auto inputs = CollectConv2DInputs(node);
1375 1395 
1376 GELOGI(1396 GELOGI(
1377 "Conv2D lowering: output_dtype=%s, strides=[%s], pads=[%s], dilations=[%s], "1397 "Conv2D lowering: output_dtype=%s, strides=[%s], pads=[%s], dilations=[%s], "
1378- "groups=%ld, data_format=%s, offset_x=%ld, pad_mode=%s, enable_hf32=%d, has_bias=%d, has_offset_w=%d",1398+ "groups=%ld, data_format=%s, offset_x=%ld, pad_mode=%s, enable_hf32=%d, fixed_shift_value=%ld, has_bias=%d, "
1399+ "has_scale0=%d, "
1400+ "has_offset_w=%d, is_extend_conv2d=%d",
1379 TypeUtils::DataTypeToSerialString(conv2d_attr.output_dtype).c_str(), loop::StrJoin(conv2d_attr.strides).c_str(),1401 TypeUtils::DataTypeToSerialString(conv2d_attr.output_dtype).c_str(), loop::StrJoin(conv2d_attr.strides).c_str(),
1380 loop::StrJoin(conv2d_attr.pads).c_str(), loop::StrJoin(conv2d_attr.dilations).c_str(), conv2d_attr.groups,1402 loop::StrJoin(conv2d_attr.pads).c_str(), loop::StrJoin(conv2d_attr.dilations).c_str(), conv2d_attr.groups,
1381 conv2d_attr.data_format.c_str(), conv2d_attr.offset_x, conv2d_attr.pad_mode.c_str(), conv2d_attr.enable_hf32,1403 conv2d_attr.data_format.c_str(), conv2d_attr.offset_x, conv2d_attr.pad_mode.c_str(), conv2d_attr.enable_hf32,
1382- conv2d_attr.has_bias, conv2d_attr.has_offset_w);1404+ conv2d_attr.fixed_shift_value, conv2d_attr.has_bias, conv2d_attr.has_scale0, conv2d_attr.has_offset_w,
1405+ conv2d_attr.is_extend_conv2d);
1383 1406 
1384 auto kernel_box = loop::StoreConv2D(node->GetOutDataAnchor(0), inputs, conv2d_attr);1407 auto kernel_box = loop::StoreConv2D(node->GetOutDataAnchor(0), inputs, conv2d_attr);
1408+ // ExtendConv2D 的 y1 常未连接;lowering 只写 y0。未使用输出若不占位,GetKernelBox 会生成 Extern,
1409+ // PostProcess 会把整节点 Fallback。这里只给未使用输出挂 非Extern 占位,不改变公共收集函数。
1410+ for (const auto &out_anchor : node->GetAllOutDataAnchors()) {
1411+ if (out_anchor == nullptr || out_anchor->GetIdx() == 0 || out_anchor->GetPeerInDataNodesSize() != 0U) {
1412+ GELOGI("Check ignored-output skip on node %s: idx=%d, peer_in_nodes=%u", node->GetNamePtr(), out_anchor->GetIdx(),
1413+ out_anchor->GetPeerInDataNodesSize());
1414+ continue;
1415+ }
1416+ (void)loop::StoreIgnoredOutput(out_anchor);
1417+ GELOGI("StoreIgnoredOutput on node %s: idx=%d", node->GetNamePtr(), out_anchor->GetIdx());
1418+ }
1385 return GRAPH_SUCCESS;1419 return GRAPH_SUCCESS;
1386}1420}
1387 1421 
1422+graphStatus LowerConv2D(const NodePtr &node) {
1423+ return InnerLowerConv2D(node, false);
1424+}
1425+ 
1426+graphStatus LowerExtendConv2D(const NodePtr &node) {
1427+ return InnerLowerConv2D(node, true);
1428+}
1429+ 
1388REGISTER_POINTWISE_LOWER(Abs, loop::Abs);1430REGISTER_POINTWISE_LOWER(Abs, loop::Abs);
1389REGISTER_POINTWISE_LOWER(Add, loop::Add);1431REGISTER_POINTWISE_LOWER(Add, loop::Add);
1390REGISTER_POINTWISE_LOWER(AddV2, loop::Add);1432REGISTER_POINTWISE_LOWER(AddV2, loop::Add);
@@ -1467,6 +1509,8 @@ REGISTER_LOWERING_WITH_EXISTED(BatchMatMulV3, BatchLowerMatMul);
1467 1509 
1468REGISTER_LOWERING_WITH_EXISTED(Conv2D, LowerConv2D);1510REGISTER_LOWERING_WITH_EXISTED(Conv2D, LowerConv2D);
1469REGISTER_LOWERING_WITH_EXISTED(Conv2DV2, LowerConv2D);1511REGISTER_LOWERING_WITH_EXISTED(Conv2DV2, LowerConv2D);
1512+// ExtendConv2D 走同一套 InnerLowerConv2D,通过 is_extend_conv2d 切换 ASCIR 变体。
1513+REGISTER_LOWERING_WITH_EXISTED(ExtendConv2D, LowerExtendConv2D);
1470 1514 
1471REGISTER_LOWERING(ClipByValue) {1515REGISTER_LOWERING(ClipByValue) {
1472 loop::Index broadcasted;1516 loop::Index broadcasted;
@@ -884,7 +884,11 @@ bool AutofuseUtils::IsCubeNodeType(const NodePtr &node) {
884 {kConv2D, kConv2D},884 {kConv2D, kConv2D},
885 {kConv2DBias, kConv2DBias},885 {kConv2DBias, kConv2DBias},
886 {kConv2DOffset, kConv2DOffset},886 {kConv2DOffset, kConv2DOffset},
887- {kConv2DOffsetBias, kConv2DOffsetBias}};887+ {kConv2DOffsetBias, kConv2DOffsetBias},
888+ {kExtendConv2D, kExtendConv2D},
889+ {kExtendConv2DBias, kExtendConv2DBias},
890+ {kExtendConv2DScale, kExtendConv2DScale},
891+ {kExtendConv2DBiasScale, kExtendConv2DBiasScale}};
888 892 
889 auto node_type = node->GetType();893 auto node_type = node->GetType();
890 auto it = kCubeTypeList.find(node_type);894 auto it = kCubeTypeList.find(node_type);
@@ -48,6 +48,10 @@ const std::string kConv2D = "Conv2D";
48const std::string kConv2DBias = "Conv2DBias";48const std::string kConv2DBias = "Conv2DBias";
49const std::string kConv2DOffset = "Conv2DOffset";49const std::string kConv2DOffset = "Conv2DOffset";
50const std::string kConv2DOffsetBias = "Conv2DOffsetBias";50const std::string kConv2DOffsetBias = "Conv2DOffsetBias";
51+const std::string kExtendConv2D = "ExtendConv2D";
52+const std::string kExtendConv2DBias = "ExtendConv2DBias";
53+const std::string kExtendConv2DScale = "ExtendConv2DScale";
54+const std::string kExtendConv2DBiasScale = "ExtendConv2DBiasScale";
51 55 
52class DefaultCounter : public Counter {56class DefaultCounter : public Counter {
53 public:57 public:
@@ -6717,6 +6717,16 @@ struct Conv2D : public Operator {
6717 GE_ASSERT_NOTNULL(attr_value);6717 GE_ASSERT_NOTNULL(attr_value);
6718 return attr_value->SetValue(enable_hf32);6718 return attr_value->SetValue(enable_hf32);
6719 }6719 }
6720+ graphStatus GetFixed_shift_value(int64_t &fixed_shift_value) const {
6721+ auto attr_value = attr_store_.GetAnyValue("fixed_shift_value");
6722+ GE_WARN_ASSERT(attr_value != nullptr);
6723+ return attr_value->GetValue(fixed_shift_value);
6724+ }
6725+ graphStatus SetFixed_shift_value(int64_t fixed_shift_value) {
6726+ auto attr_value = attr_store_.GetOrCreateAnyValue("fixed_shift_value");
6727+ GE_ASSERT_NOTNULL(attr_value);
6728+ return attr_value->SetValue(fixed_shift_value);
6729+ }
6720 };6730 };
6721 AscConv2DIrAttrDef &ir_attr;6731 AscConv2DIrAttrDef &ir_attr;
6722 af::AscOpInput<0> x;6732 af::AscOpInput<0> x;
@@ -26,9 +26,12 @@ constexpr size_t kStridesIdxConv2d = 0U;
26constexpr size_t kPadsIdxConv2d = 1U;26constexpr size_t kPadsIdxConv2d = 1U;
27constexpr size_t kDilationsIdxConv2d = 2U;27constexpr size_t kDilationsIdxConv2d = 2U;
28constexpr size_t kGroupsIdxConv2d = 3U;28constexpr size_t kGroupsIdxConv2d = 3U;
29-// attr 索引 6:Conv2D 对应 "padding",Conv2DV2 对应 "pad_mode"。29+// attr 索引:
30-// 二者本质都是 pad 模式字符串,统一走同一套分支处理。30+// - Conv2D: "padding" 位于 6
31+// - Conv2DV2: "pad_mode" 位于 6
32+// - ExtendConv2D: proto 在 pad_mode 前多了 "round_mode",因此 "pad_mode" 位于 7
31constexpr size_t kPaddingIdxConv2d = 6U;33constexpr size_t kPaddingIdxConv2d = 6U;
34+constexpr size_t kPaddingIdxExtendConv2d = 7U;
32// NCHW shape35// NCHW shape
33constexpr int32_t kNCHWdimNIdxConv2d = 0;36constexpr int32_t kNCHWdimNIdxConv2d = 0;
34constexpr int32_t kNCHWdimCIdxConv2d = 1;37constexpr int32_t kNCHWdimCIdxConv2d = 1;
@@ -176,7 +179,8 @@ ge::graphStatus CheckConv2DGroups(const gert::InferSymbolShapeContext *context,
176 179 
177 Expression ex_groups = Symbol(groups);180 Expression ex_groups = Symbol(groups);
178 const auto *node_type = context->GetNodeType();181 const auto *node_type = context->GetNodeType();
179- const bool is_conv2dv2 = (node_type != nullptr) && (std::string(node_type) == "Conv2DV2");182+ const bool is_conv2dv2 =
183+ (node_type != nullptr) && (std::string(node_type) == "Conv2DV2" || std::string(node_type) == "ExtendConv2D");
180 if (!is_conv2dv2) {184 if (!is_conv2dv2) {
181 // Conv2D:保持历史逻辑,避免 UT/现网行为回归。185 // Conv2D:保持历史逻辑,避免 UT/现网行为回归。
182 // 当 groups == 1 且 kc != 0,尝试按 ic/kc 推导有效 groups。186 // 当 groups == 1 且 kc != 0,尝试按 ic/kc 推导有效 groups。
@@ -428,11 +432,16 @@ ge::graphStatus GetConv2DPads(const gert::InferSymbolShapeContext *context, cons
428 conv2d_attrs.padr = Symbol(static_cast<int64_t>(pads_array[kPadRightIdxConv2d]));432 conv2d_attrs.padr = Symbol(static_cast<int64_t>(pads_array[kPadRightIdxConv2d]));
429 }433 }
430 // Infer pads if "padding/pad_mode" is defined.434 // Infer pads if "padding/pad_mode" is defined.
431- if (attrs->GetAttrNum() > kPaddingIdxConv2d) {435+ const auto *node_type = context->GetNodeType();
432- const char *padding_ptr = attrs->GetAttrPointer<char>(kPaddingIdxConv2d);436+ const bool is_extend_conv2d = (node_type != nullptr) && (std::string(node_type) == "ExtendConv2D");
437+ const size_t padding_attr_idx = is_extend_conv2d ? kPaddingIdxExtendConv2d : kPaddingIdxConv2d;
438+ if (attrs->GetAttrNum() > padding_attr_idx) {
439+ const char *padding_ptr = attrs->GetAttrPointer<char>(padding_attr_idx);
433 GE_ASSERT_NOTNULL(padding_ptr);440 GE_ASSERT_NOTNULL(padding_ptr);
434- const auto *node_type = context->GetNodeType();441+ // Conv2DV2 与 ExtendConv2D 的 pad_mode 语义一致,共用 SAME/VALID 推导分支。
435- const bool is_conv2dv2 = (node_type != nullptr) && (std::string(node_type) == "Conv2DV2");442+ const bool is_conv2dv2 =
443+ (node_type != nullptr) && (std::string(node_type) == "Conv2DV2" || std::string(node_type) == "ExtendConv2D");
444+ 
436 GE_ASSERT_GRAPH_SUCCESS(445 GE_ASSERT_GRAPH_SUCCESS(
437 InferConv2DPadsWithPadding(pads_size, x_shapes, w_shapes, padding_ptr, is_conv2dv2, conv2d_attrs));446 InferConv2DPadsWithPadding(pads_size, x_shapes, w_shapes, padding_ptr, is_conv2dv2, conv2d_attrs));
438 }447 }
@@ -591,5 +600,7 @@ IMPL_OP_INFER_SYMBOL_SHAPE_INNER(Conv2D).InferSymbolShape(InferShape4Conv2D);
591// Conv2DV2 与 Conv2D 复用同一套几何公式,600// Conv2DV2 与 Conv2D 复用同一套几何公式,
592// 语义差异在上面的 attr/groups 分支中处理。601// 语义差异在上面的 attr/groups 分支中处理。
593IMPL_OP_INFER_SYMBOL_SHAPE_INNER(Conv2DV2).InferSymbolShape(InferShape4Conv2D);602IMPL_OP_INFER_SYMBOL_SHAPE_INNER(Conv2DV2).InferSymbolShape(InferShape4Conv2D);
603+// ExtendConv2D 几何推导与 Conv2DV2 相同,差异仅在 attr 索引与 groups 分支。
604+IMPL_OP_INFER_SYMBOL_SHAPE_INNER(ExtendConv2D).InferSymbolShape(InferShape4Conv2D);
594} // namespace605} // namespace
595} // namespace ge606} // namespace ge
@@ -0,0 +1,561 @@
1+# Autofuse Adaptation for ExtendConv2D: Key GE Repository Flows
2+ 
3+This document describes the compilation/runtime pipeline changes made in the GE repository to support Autofuse automatic fusion for ExtendConv2D (Conv2DV2 was already supported).
4+The entry point is `Autofuser::Fuse` (`compiler/graph/optimize/autofuse/autofuse/autofuser.cpp`).
5+ 
6+Scope: changes in the `ge` repository. Kernel codegen, `scaleGM`, and the tiling-key template dimension are in the `graph-autofusion` repository, which consumes the AscGraph / `Conv2DAttr` / `conv_subgraph` produced here.
7+ 
8+For how to run LLTs, inspect coverage, and determine whether GE UTs depend on the autofuse repository, see [autofuse_extend_conv2d_llt.md](./autofuse_extend_conv2d_llt.md).
9+ 
10+---
11+ 
12+## 0. Objects Beginners Should Understand First
13+ 
14+Autofuse does not directly concatenate GE nodes into a piece of C++. It passes through representations at several different levels:
15+ 
16+| Object | How to understand it | Storage location |
17+| ------------------------------ | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------- |
18+| `ComputeGraph / Node / OpDesc` | Original GE compute graph; nodes are operators such as Conv, Add, and Relu, while `OpDesc` stores I/O descriptions and attributes | GE graph |
19+| `SymbolicDescAttr` | A "computable expression" version of tensor shape; a static dimension 64 is represented as `Symbol(64)`, and dynamic dimensions can also be represented by expressions | attrs group of `GeTensorDesc` |
20+| `LoopVar / LoopOp` | Loop semantics translated from one GE operator, such as load, broadcast, reduce, and store | Temporary lowering objects |
21+| `KernelBox` | A group of LoopOps bounded by an output; a candidate kernel that can continue to merge with neighbors | `LoweringResultAttrs` corresponding to an output `OutDataAnchor` |
22+| `AscGraph / ASCIR` | A graph closer to AscendC codegen; nodes have become ASCIR types such as `Add`, `Reduce`, and `ExtendConv2DScale` | `AutoFuseAttrs::asc_graph` |
23+| `AscBackend` | A node in the GE graph representing that a group of original operators will generate one fused kernel | Remains in the GE graph after replacing original nodes |
24+| `AutoFuseAttrs` | The accompanying record of an AscBackend: internal AscGraph, original GE nodes, original inputs/outputs, fusion type, and so on | `OpDesc` of AscBackend |
25+| `conv_subgraph` | A minimal copy of the original convolution used only for secondary ops-nn tiling; it is not executed again | ExtAttr of the AscBackend `OpDesc` |
26+| `OpRunInfoV2` | Kernel launch parameters: tiling key/data, workspace, `block_dim`, and so on | Output of the tiling stage |
27+ 
28+The main process can be understood as:
29+ 
30+```text
31+GE operator graph
32+ → Translate each operator into a LoopOp and attach it to the output to form a KernelBox
33+ → Merge adjacent KernelBoxes and realize them into an AscGraph
34+ → Replace this group of GE operators with one AscBackend node
35+ → can_fuse attempts to continue merging the AscBackend with more neighbors
36+ → Lifting checks whether the fusion is worth retaining; restore it if not, retain it if so
37+ → codegen generates a fused kernel from the AscGraph
38+ → tiling generates kernel launch parameters from the actual shape
39+```
40+ 
41+This framework is not limited to convolution:
42+ 
43+- **Pointwise**: Add, Mul, Relu, and similar operators are generally the easiest to fuse with one another.
44+- **Reduction**: ReduceSum/Max and similar operators require additional reduction-axis and scheduling information.
45+- **View**: Reshape/Transpose and similar operators may only change indexing and do not necessarily produce real computation.
46+- **Cube**: Conv/MatMul run on AIC and are often CV-fused with an AIV vector tail.
47+- **Fallback/Extern**: When no lowering is registered, the shape does not meet the conditions, or a feature switch is disabled, the operator remains external instead of being forcibly fused.
48+ 
49+---
50+ 
51+## 1. End-to-End Sequence
52+ 
53+```text
54+[Compile time, before Autofuse]
55+ InferSymbolShape Attach SymbolicDescAttr to tensors
56+ 
57+[Autofuser::Fuse]
58+ PatternFusion Graph-level patterns (weakly related to this change)
59+ Lowering GE operator → Loop IR / ASCIR → synthesize AscBackend
60+ can_fuse Continue vector fusion on lowered nodes
61+ Lifting Most fusions can be split back; cube operators such as convolution/MatMul are not,
62+ and instead the original convolution is copied into an ExtAttr of the fused node (conv_subgraph)
63+ PostProcess AscBackend post-processing
64+ 
65+[Late compilation / runtime]
66+ AicoreRtParseAndTiling Read conv_subgraph, run convolution tiling first, then fused-node tiling
67+```
68+ 
69+Corresponding code sequence:
70+ 
71+```text
72+autofuser.cpp:150 patter_fusion.RunAllPatternFusion(graph);
73+autofuser.cpp:157 lowerer.Lowering(graph);
74+ ├─ asc_ir_lowerer.cpp:200 CompleteStaticSymbolicShapeForConv
75+ ├─ asc_ir_lowerer.cpp:215 LoweringManager::LoweringGraph
76+ └─ asc_ir_lowerer.cpp:216 FusedLoopToAscBackendOp
77+autofuser.cpp:170 FuseSubgraphsAndRootGraph(...) // can_fuse
78+autofuser.cpp:177 lowerer.Lifting(graph);
79+ └─ liftings.cpp:423 IsSkipLifting
80+ └─ liftings.cpp:174 IsCubeSkipLifting
81+ └─ liftings.cpp:61 CreateConvSubgraphAttr
82+autofuser.cpp:183 post_processor.Do(graph);
83+```
84+ 
85+Runtime processing is not inside `Autofuser`; it is triggered by `AicoreRtParseAndTiling` (`base/common/op_tiling/op_tiling_rt2.cc`) when tiling is required.
86+ 
87+> Line numbers are provided for quickly locating code in the current version. As the code evolves, search by function name first.
88+ 
89+---
90+ 
91+## 2. InferSymbolShape (Before Lowering)
92+ 
93+**File**: `compiler/graph/optimize/symbolic/infer_symbolic_shape/infer/conv2d.cc`
94+ 
95+**Why this change is needed**: Autofuse lowering does not obtain shapes through `GeTensorDesc::GetShape()`; it only uses `GetBufferShape()` → `SymbolicDescAttr`. If ExtendConv2D does not register inference, its output (and subsequent nodes that depend on it) may have no symbolic shape, causing lowering to fail immediately.
96+ 
97+**Inputs**:
98+ 
99+- An `ExtendConv2D` node in the GE graph
100+- Symbolic shapes of inputs x / filter
@@ -0,0 +1,219 @@
1+# Autofuse Adaptation for ExtendConv2D: LLT and Cross-Repository Dependencies
2+ 
3+This document describes how to run the LLT (UT + ST) after ExtendConv2D integration, how to inspect coverage, and **whether tests in the GE repository depend on building and installing the graph-autofusion repository**.
4+ 
5+For the feature flow, see [autofuse_extend_conv2d_cv_fusion.md](./autofuse_extend_conv2d_cv_fusion.md) in the same directory.
6+ 
7+---
8+ 
9+## 1. Conclusion First: Do GE UTs Require Building and Installing the Autofuse Repository?
10+ 
11+**For routine development and execution of UTs in the GE repository, you do not need to build the graph-autofusion repository or install its artifacts into CANN.**
12+ 
13+This was verified on August 25, 2026, using a clean reinstallation of cann-9.2.0: the official toolkit **includes** the `graph_autofusion` component (`share/info/graph_autofusion/version.info`, `lib64/libaihac_codegen.so`, and others). It is not a repository manually overlaid in the past, but part of the current CANN package. The official package **does not yet contain** AscendC IR registrations for `ExtendConv2D*`.
14+ 
15+| What you are doing | Build the autofuse repository? | Install the autofuse repository into CANN? | What is actually used |
16+|--------------------|--------------------------------|--------------------------------------------|-----------------------|
17+| GE infer-shape / tiling UT | No | No | GE source code + test stubs |
18+| GE autofuse frontend UT (`autofusion_ut`) | No | No | `libautofuse.so` built from GE source code; codegen links against `libaihac_codegen.so` **included in the official toolkit** |
19+| GE lowering Realize produces a new AscendC IR type (`ExtendConv2D*`) | No | **Only required when the official package does not yet contain these types** | AscendC IR registered in the toolkit |
20+| UT/ST of the Autofuse repository itself | **Build the autofuse repository** | No (tests use artifacts from this repository's `build/`) | graph-autofusion source code |
21+| GE ST / real kernel / secondary tiling | Yes | **Yes** | Overlay the newly built autofuse repository into CANN |
22+ 
23+Rerunning `ExtendConv2DLoweringUT.*` + `StoreIgnoredOutputIsNotExtern` on the clean package: **5/5 passed**. The log still contains `unknown op type 'ExtendConv2D'`, indicating that Realize uses the old AscendC IR in the official package; this does not affect the UT assertions here.
24+ 
25+---
26+ 
27+## 2. What Each Repository Tests
28+ 
29+```text
30+GE repository (frontend)
31+ InferSymbolShape pad_mode is at ExtendConv2D attribute index 7 (round_mode occupies 6)
32+ Lowering selects among 4 AscendC IR variants based on bias/scale0; unused y1 uses StoreIgnoredOutput
33+ Lifting / tiling conv_subgraph + ExtendConv2D tiling attributes
34+ 
35+graph-autofusion repository (backend)
36+ ParseConv2DAttr is_extend_conv2d / has_bias / has_scale0 / round_mode
37+ build_conv_args 10 logical input slots, dual-output placeholders
38+ codegen / e2e ST ExtendConv2D and ExtendConv2DBiasScale produce a tiling key
39+```
40+ 
41+The GE repository's frontend source code is under `compiler/graph/optimize/autofuse/`, and its AscendC IR header is the vendored `temporary_dependencies/ascir/ascir_ops.h`.
42+Kernel templates, tiling keys, and Python codegen are in the graph-autofusion repository.
43+ 
44+---
45+ 
46+## 3. Environment
47+ 
48+Local reference environment:
49+ 
50+- toolkit: `/usr/local/Ascend/ascend-toolkit/latest` → cann-9.2.0
51+- On machines with few cores, build with `-j 3` (the default parallelism is too high and can easily cause OOM)
52+- Before running GE tests:
53+ 
54+```bash
55+source /usr/local/Ascend/ascend-toolkit/latest/bin/setenv.bash
56+unset LD_LIBRARY_PATH
57+unset ASCEND_OPP_PATH
58+```
59+ 
60+The variables are unset to prevent stale `LD_LIBRARY_PATH` / `ASCEND_OPP_PATH` values from linking tests against the wrong package.
61+ 
62+---
63+ 
64+## 4. How to Run LLT and Inspect Coverage in the Autofuse Repository
65+ 
66+Unified entry point: `graph-autofusion/scripts/test/run_autofuse_test.sh`.
67+ 
68+```bash
69+cd /root/workspace/cann-open/graph-autofusion
70+ 
71+# C++ common UT + coverage
72+bash scripts/test/run_autofuse_test.sh -u -m common -c -j 3 \
73+ --ascend_install_path=/usr/local/Ascend/ascend-toolkit/latest
74+ 
75+# Python codegen UT (module name: codegen)
76+bash scripts/test/run_autofuse_test.sh -u -m codegen -j 3 \
77+ --ascend_install_path=/usr/local/Ascend/ascend-toolkit/latest
78+ 
79+# Backend ST (including Conv2D / ExtendConv2D codegen)
80+bash scripts/test/run_autofuse_test.sh -s -m backend -j 3 \
81+ --ascend_install_path=/usr/local/Ascend/ascend-toolkit/latest
82+```
83+ 
84+UT modules: `common` / `att` / `optimize` / `codegen` / `framework` / `all`.
85+ST modules: `backend` / `e2e` / `codegen` / `framework` / `all`.
86+ 
87+You can also incrementally build a specific binary:
88+ 
89+```bash
90+cmake --build build --target test_common -j 3
91+./build/autofuse/tests/ut/common/test_common --gtest_filter='CommonUtilsTest.ExtendConv2D*'
92+```
93+ 
94+Coverage is enabled by adding `-c`:
95+ 
96+```text
97+graph-autofusion/cov/coverage_report/index.html
98+```
99+ 
100+Note: The official script writes to `build/` by default. If `build/` already contains a Release/package build, running `-u -c` changes its CMake configuration. A separate `build_ut` directory was used locally to build UTs and avoid overwriting the package build directory.
101+ 
102+---
103+ 
104+## 5. How to Run LLT and Inspect Coverage in the GE Repository
105+ 
106+GE has two build configurations; do not mix them.
107+ 
108+### 5.1 Autofuse Frontend UT (Does Not Depend on Building the Autofuse Repository)
109+ 
110+The build directory is `ge/build` (`RUN_TEST=1`), and the binary is `autofusion_ut`.
111+ 
112+```bash
113+cd /root/workspace/cann-open/ge
114+source /usr/local/Ascend/ascend-toolkit/latest/bin/setenv.bash
115+unset LD_LIBRARY_PATH
116+unset ASCEND_OPP_PATH
117+ 
118+# Run the complete script (including coverage)
119+bash scripts/test/run_autofuse_test.sh -u -m autofuse -c -j 3 \
120+ --ascend_install_path=/usr/local/Ascend/ascend-toolkit/latest
121+ 
122+# Alternatively, build incrementally and run selected tests
123+cmake --build build --target autofusion_ut -j 3
124+./build/tests/autofuse/ut/autofuse/autofusion_ut \
125+ --gtest_filter='ExtendConv2DLoweringUT.*:LoopApiUT.StoreIgnoredOutputIsNotExtern'
126+```
127+ 
128+The lowering tests under `v35/` are **not** included in `autofusion_ut` (the entire directory is missing headers and cannot be added with `add_subdirectory(v35)`). The ExtendConv2D lowering tests are in `tests/autofuse/ut/autofuse/extend_conv2d_lowering_unittest.cpp`.
129+ 
130+### 5.2 GE Infer-Shape / Tiling UT
131+ 
132+The build directory must be `cmake-build-gcov` (`CMAKE_BUILD_TYPE=GCOV -DENABLE_GE_UT=ON`).
133+ 
134+```bash
135+make -C cmake-build-gcov ut_libge_symbol_infer_utest -j 3
136+./cmake-build-gcov/tests/ge/ut/ge/ut_libge_symbol_infer_utest \
137+ --gtest_filter='SymbolicShapeInferFuncUT.InferSymbolicShapeForExtendConv2D'
138+ 
139+make -C cmake-build-gcov ut_libge_common_utest -j 3
140+./cmake-build-gcov/tests/ge/ut/ge/ut_libge_common_utest \
141+ --gtest_filter='RegisterOpTilingRT2UT.AutofuseNodeWithExtendConvTilingAttrsSuccess'
142+```
143+ 
144+Both targets link against `ge_compiler`. If the toolkit headers are older than the code (for example, if `aclskScopeVerifyGraphInfo` is missing), compilation fails; this is unrelated to the autofuse repository.
145+ 
146+Coverage:
147+ 
148+```bash
149+bash scripts/coverage/ge_cov.sh
150+# Or use -c with the autofuse script
151+# Report: ge/cov/coverage_report/index.html
152+```
153+ 
154+When only some tests are run, whole-file coverage is low (for example, when only ExtendConv2D lowering tests are run, line coverage for `lowering_impl.cpp` is approximately a dozen percent). When inspecting coverage, open the HTML report and navigate to the specific functions instead of looking only at the whole-file percentage.
155+ 
156+---
157+ 
158+## 6. Tests Added for ExtendConv2D
159+ 
160+### Autofuse Repository
161+ 
162+| File | Test | Assertion |
163+|------|------|-----------|
164+| `autofuse/tests/ut/python/test_asc_codegen_compile_conv2d.py` | `test_build_conv_args_extend_conv2d_*` | 10 input slots and dual outputs; optional slots may be empty |
165+| `autofuse/tests/ut/common/test_common_utils.cpp` | `CommonUtilsTest.ExtendConv2D*` | bias, scale0, and round_mode handling in `IsConv2DGraphType` / `ParseConv2DAttr` |
166+| `autofuse/tests/st/backend_e2e/conv2d_elemwise_test/conv2d_backend_generate.cpp` | `ExtendConv2DE2eCodegen`, `ExtendConv2DBiasScaleE2eCodegen` | codegen can produce `GenCVFusionTilingKey` |
167+ 
168+The previously commented-out `add_subdirectory(conv2d_elemwise_test)` in `tests/st/backend_e2e/CMakeLists.txt` has been enabled; otherwise, the ST is not included in the build.
169+ 
170+### GE Repository
171+ 
172+| File | Test | Assertion |
173+|------|------|-----------|
174+| `tests/autofuse/ut/autofuse/extend_conv2d_lowering_unittest.cpp` | Four bias/scale0 combinations | Lowering succeeds; y0 is Cube; unused y1 is not Extern |
175+| `tests/autofuse/ut/autofuse/loop_api_unittest.cpp` | `StoreIgnoredOutputIsNotExtern` | The placeholder is not Extern and is Cube |
176+| `tests/ge/ut/ge/graph/optimize/symbolic/symbolic_shape_infer_func_unittest.cc` | `InferSymbolicShapeForExtendConv2D` | SPECIFIC / SAME_LOWER succeed; invalid pad_mode fails |
177+| `tests/ge/ut/ge/common/op_tiling_rt2_unittest.cc` | `AutofuseNodeWithExtendConvTilingAttrsSuccess` | ExtendConv2D tiling attributes |
178+ 
179+Graph construction helpers `all_ops.cpp` / `all_ops.h` / `all_ops_cpp.h` and `op_creator_register.h` were extended with `ExtendConv2D`. y1 is required in the IR but may remain disconnected in the graph.
180+ 
181+---
182+ 
183+## 7. When the Autofuse Repository Must Be Built and Installed
184+ 
185+It is required only in these scenarios:
186+ 
187+1. You modified graph-autofusion source code and need to run the **autofuse repository's own** UT/ST.
188+2. GE lowering must **Realize a new AscendC IR node** (the toolkit does not yet contain `ExtendConv2D*`).
189+3. GE ST / compilation of a fused kernel on real hardware / secondary tiling.
190+ 
191+Installation steps (when the local toolkit is 9.2.0, do not reuse an old `build/` that points to 9.1.0):
192+ 
193+```bash
194+cd /root/workspace/cann-open/graph-autofusion
195+source /usr/local/Ascend/ascend-toolkit/latest/bin/setenv.bash
196+# If the CMake cache in the old build/ still uses cann-9.1.0, remove it first
197+rm -rf build
198+sh build.sh --pkg -j 3
199+# Install the generated cann-graph-autofusion_*.run into the toolkit
200+```
201+ 
202+To avoid installing the package and only make local GE processes load the newly built shared object first:
203+ 
204+```bash
205+export LD_LIBRARY_PATH=<autofuse-build-directory>/autofuse:$LD_LIBRARY_PATH
206+```
207+ 
208+The ABI must match the toolkit (compiler and Build Type).
209+ 
210+---
211+ 
212+## 8. Issues Encountered Locally
213+ 
214+1. **Do not** add the entire `tests/autofuse/v35` directory to `autofusion_ut`; headers such as `base/att_const_values.h` are missing, so the directory does not compile.
215+2. `es::Tensor` has no default constructor; write an optional input as `es::Tensor bias(nullptr)`.
216+3. After an optional input is connected, the corresponding `InputDesc` on the node may still be empty. Before lowering, synchronize the peer output desc to the input desc; otherwise, the `IsStaticShape` assertion fails.
217+4. When the GE `cmake-build-gcov` configuration and toolkit headers are from different versions, `ge_compiler` does not compile and the infer-shape / tiling UTs both fail. This is unrelated to the autofuse repository.
218+5. A gcov checksum mismatch is usually caused by stale `.gcda` files that do not match the current `.o` files. Delete the corresponding `.gcda` files or perform a full rebuild.
219+6. The coverage script depends on the locally installed `lcov`/`gcov` versions; lcov 2.x often requires `--ignore-errors mismatch,empty`.
@@ -360,6 +360,8 @@ Taking Regular InferShape as an example, the runtime execution flow:
3603. Operator InferShape function reads input Shape and writes output OriginShape through `InferShapeContext` interface3603. Operator InferShape function reads input Shape and writes output OriginShape through `InferShapeContext` interface
3614. `TransformAllOutputsShape()` automatically converts output OriginShape to StorageShape (dimension expansion + format conversion)3614. `TransformAllOutputsShape()` automatically converts output OriginShape to StorageShape (dimension expansion + format conversion)
362 362 
363+`FindInferShapeFunc` only serves the `OpImplSpaceRegistryV2` path. The lowering stage constructs this node only after `IsInferShapeRegistered()` has confirmed that the current op type has a v2 infer_shape, so a lookup failure at runtime means the registry/type/version is inconsistent and should fail immediately. Custom operators' ShapeInferOp does not fall back to the process-level `CustomOpFactory` through this node; it goes through `LoweringCustomNode -> InferCustomOpShape -> FindCustomOp -> InferCustomOpShapeFromInput`, and uses the model-level `CustomOpRegistry` that `GeRootModel` injects into `LoweringGlobalData`.
364+ 
363### 6.2 Execution Graph Optimization365### 6.2 Execution Graph Optimization
364 366 
365#### FindInferShapeFunc Deduplication367#### FindInferShapeFunc Deduplication
@@ -430,6 +432,7 @@ PostProcess(compute_graph) // Post-processing wrapper
430```432```
431 433 
432> **Note**: The `SymbolicShapeInference` class (symbolic_shape_inference.h) has only one public method `Infer()`. `Simplify()` is a free function defined in an anonymous namespace (symbolic_shape_inference.cc), called internally by `Infer()` after inference completes (symbolic_shape_inference.cc), not an independent class method. `PreProcess()` and `PostProcess()` are wrapper functions of the `AutofuseOptimize` class, encapsulating preprocessing graph optimization and post-processing logic respectively.434> **Note**: The `SymbolicShapeInference` class (symbolic_shape_inference.h) has only one public method `Infer()`. `Simplify()` is a free function defined in an anonymous namespace (symbolic_shape_inference.cc), called internally by `Infer()` after inference completes (symbolic_shape_inference.cc), not an independent class method. `PreProcess()` and `PostProcess()` are wrapper functions of the `AutofuseOptimize` class, encapsulating preprocessing graph optimization and post-processing logic respectively.
435+Before Autofuse lowering, constant symbolic Shapes are filled in for outputs that lack symbolic attributes and have fully static Shapes, covering cases where preceding stages insert or rewrite nodes without synchronizing symbolic attributes.
433 436 
434### 7.4 Operator Implementation Examples437### 7.4 Operator Implementation Examples
435 438 
@@ -0,0 +1,595 @@
1+# Autofuse 适配 ExtendConv2D:GE 仓关键流程
2+ 
3+本文记录 GE 仓为 Autofuse 自动融合 ExtendConv2D(此前已支持 Conv2DV2)所改动的编译/运行链路。
4+入口在 `Autofuser::Fuse`(`compiler/graph/optimize/autofuse/autofuse/autofuser.cpp`)。
5+ 
6+适用范围:`ge` 仓改动。kernel codegen、`scaleGM`、tiling key 模板维在 `graph-autofusion` 仓,消费的是本文产出的 AscGraph / `Conv2DAttr` / `conv_subgraph`。
7+ 
8+LLT 怎么跑、覆盖率怎么看、GE UT 是否依赖 autofuse 仓:见 [autofuse_extend_conv2d_llt.md](./autofuse_extend_conv2d_llt.md)。
9+ 
10+---
11+ 
12+## 0. 初学者先理解的几个对象
13+ 
14+Autofuse 并不是直接把 GE 节点拼成一段 C++。它会经过几种不同层次的表示:
15+ 
16+ 
17+| 对象 | 可以怎样理解 | 存放位置 |
18+| ------------------------------ | -------------------------------------------------------------------------- | -------------------------------------------- |
19+| `ComputeGraph / Node / OpDesc` | 原始 GE 计算图;节点是 Conv、Add、Relu 等算子,`OpDesc` 保存输入输出描述和属性 | GE 图 |
20+| `SymbolicDescAttr` | tensor shape 的“可计算表达式版本”;静态维 64 表示为 `Symbol(64)`,动态维也可用表达式描述 | `GeTensorDesc` 的 attrs group |
21+| `LoopVar / LoopOp` | 单个 GE 算子被翻译后的循环语义,例如 load、broadcast、reduce、store | lowering 临时对象 |
22+| `KernelBox` | 以某个输出为边界的一组 LoopOp,是“可以继续与邻居合并的候选 kernel” | 输出 `OutDataAnchor` 对应的 `LoweringResultAttrs` |
23+| `AscGraph / ASCIR` | 更接近 AscendC codegen 的图;节点已变成 `Add`、`Reduce`、`ExtendConv2DScale` 等 ASCIR 类型 | `AutoFuseAttrs::asc_graph` |
24+| `AscBackend` | GE 图上代表“这一组原始算子将生成一个融合 kernel”的节点 | 替换原始节点后留在 GE 图上 |
25+| `AutoFuseAttrs` | AscBackend 的随身档案:内部 AscGraph、原始 GE 节点、原始输入输出、融合类型等 | AscBackend 的 `OpDesc` |
26+| `conv_subgraph` | 原始卷积的最小副本,只给 ops-nn 二次 tiling 使用,不参与再次执行 | AscBackend 的 `OpDesc` ExtAttr |
27+| `OpRunInfoV2` | kernel 启动参数:tiling key/data、workspace、block_dim 等 | tiling 阶段输出 |
28+ 
29+ 
30+可以把主过程理解成:
31+ 
32+```text
33+GE 算子图
34+ → 每个算子翻译成 LoopOp,并挂到输出上形成 KernelBox
35+ → 相邻 KernelBox 合并并 realize 成 AscGraph
36+ → 用一个 AscBackend 节点替换这组 GE 算子
37+ → can_fuse 尝试把 AscBackend 与更多邻居继续合并
38+ → Lifting 检查融合是否值得保留;不值得则恢复,值得则保留
39+ → codegen 根据 AscGraph 生成融合 kernel
40+ → tiling 根据实际 shape 生成 kernel 启动参数
41+```
42+ 
43+这个框架并不只服务卷积:
44+ 
45+- **Pointwise**:Add、Mul、Relu 等通常最容易互相融合。
46+- **Reduction**:ReduceSum/Max 等需要额外的归约轴和调度信息。
47+- **View**:Reshape/Transpose 等可能只改变索引,不一定产生真实计算。
48+- **Cube**:Conv/MatMul 在 AIC 上计算,并常与 AIV vector 尾部做 CV 融合。
49+- **Fallback/Extern**:没有 lowering 注册、shape 不满足条件或功能开关关闭时,保持为外部算子,不强行融合。
50+ 
51+---
52+ 
53+ 
54+ 
55+## 1. 端到端顺序
56+ 
57+```text
58+[编译期,Autofuse 之前]
59+ InferSymbolShape 给 tensor 挂 SymbolicDescAttr
60+ 
61+[Autofuser::Fuse]
62+ PatternFusion 图级 pattern(与本次改动弱相关)
63+ Lowering GE 算子 → Loop IR / ASCIR → 合成 AscBackend
64+ can_fuse 在已 lowering 的节点上继续并 vector
65+ Lifting 多数融合可拆回;卷积/MatMul 这类 cube 不拆,
66+ 而是把原始卷积拷进融合节点的 ExtAttr(conv_subgraph)
67+ PostProcess AscBackend 后处理
68+ 
69+[编译后期 / 运行时]
70+ AicoreRtParseAndTiling 读 conv_subgraph,先跑卷积 tiling,再跑融合节点 tiling
71+```
72+ 
73+对应代码顺序:
74+ 
75+```text
76+autofuser.cpp:150 patter_fusion.RunAllPatternFusion(graph);
77+autofuser.cpp:157 lowerer.Lowering(graph);
78+ ├─ asc_ir_lowerer.cpp:200 CompleteStaticSymbolicShapeForConv
79+ ├─ asc_ir_lowerer.cpp:215 LoweringManager::LoweringGraph
80+ └─ asc_ir_lowerer.cpp:216 FusedLoopToAscBackendOp
81+autofuser.cpp:170 FuseSubgraphsAndRootGraph(...) // can_fuse
82+autofuser.cpp:177 lowerer.Lifting(graph);
83+ └─ liftings.cpp:423 IsSkipLifting
84+ └─ liftings.cpp:174 IsCubeSkipLifting
85+ └─ liftings.cpp:61 CreateConvSubgraphAttr
86+autofuser.cpp:183 post_processor.Do(graph);
87+```
88+ 
89+运行时不在 `Autofuser` 里,由 `AicoreRtParseAndTiling`(`base/common/op_tiling/op_tiling_rt2.cc`)在需要 tiling 时触发。
90+ 
91+> 行号用于快速定位当前版本;代码演进后优先按函数名搜索。
92+ 
93+---
94+ 
95+ 
96+ 
97+## 2. InferSymbolShape(Lowering 之前)
98+ 
99+**文件**:`compiler/graph/optimize/symbolic/infer_symbolic_shape/infer/conv2d.cc`
100+ 
@@ -0,0 +1,219 @@
1+# Autofuse 适配 ExtendConv2D:LLT 与仓间依赖
2+ 
3+本文记录 ExtendConv2D 联调后的 LLT(UT + ST)怎么跑、覆盖率怎么看,以及 **GE 仓测试是否依赖 graph-autofusion 仓的编译和安装**。
4+ 
5+特性流程见同目录 [autofuse_extend_conv2d_cv_fusion.md](./autofuse_extend_conv2d_cv_fusion.md)。
6+ 
7+---
8+ 
9+## 1. 结论先说:GE 的 UT 要不要编、装 autofuse 仓?
10+ 
11+**日常写/跑 GE 仓 UT,不需要编译 graph-autofusion 仓,也不需要把该仓的产物再装进 CANN。**
12+ 
13+2026-08-25 在干净重装的 cann-9.2.0 上复核过:官方 toolkit **自带** `graph_autofusion` 组件(`share/info/graph_autofusion/version.info`,`lib64/libaihac_codegen.so` 等)。这不是过去手工叠加的仓,而是当前 CANN 包的一部分。官方包里**还没有** `ExtendConv2D*` ASCIR 注册。
14+ 
15+| 你在做什么 | 要不要编 autofuse 仓 | 要不要把 autofuse 仓装进 CANN | 实际用到什么 |
16+|------------|----------------------|-------------------------------|--------------|
17+| GE infer-shape / tiling UT | 否 | 否 | GE 源码 + 测试 stub |
18+| GE autofuse frontend UT(`autofusion_ut`) | 否 | 否 | GE 源码编 `libautofuse.so`;codegen 链接 **官方 toolkit 自带的** `libaihac_codegen.so` |
19+| GE lowering Realize 出新 ASCIR 类型(`ExtendConv2D*`) | 否 | **官方包还没有这些类型时才需要** | toolkit 里已注册的 ASCIR |
20+| Autofuse 仓自己的 UT/ST | **要编 autofuse 仓** | 否(测的是本仓 `build/` 产物) | graph-autofusion 源码 |
21+| GE ST / 真 kernel / 二次 tiling | 要 | **要** | 把本仓新编的 autofuse 覆盖进 CANN |
22+ 
23+干净包上复跑 `ExtendConv2DLoweringUT.*` + `StoreIgnoredOutputIsNotExtern`:**5/5 通过**。日志仍有 `unknown op type 'ExtendConv2D'`,说明 Realize 用的是官方包里的旧 ASCIR,不影响本次 UT 断言。
24+ 
25+---
26+ 
27+## 2. 两仓各自测什么
28+ 
29+```text
30+GE 仓(frontend)
31+ InferSymbolShape pad_mode 在 ExtendConv2D 属性下标 7(round_mode 占 6)
32+ Lowering 按 bias/scale0 选 4 种 ASCIR 变体;未使用 y1 走 StoreIgnoredOutput
33+ Lifting / tiling conv_subgraph + ExtendConv2D tiling 属性
34+ 
35+graph-autofusion 仓(backend)
36+ ParseConv2DAttr is_extend_conv2d / has_bias / has_scale0 / round_mode
37+ build_conv_args 10 个逻辑输入槽、双输出占位
38+ codegen / e2e ST ExtendConv2D、ExtendConv2DBiasScale 出 tiling key
39+```
40+ 
41+GE 仓 frontend 源码在 `compiler/graph/optimize/autofuse/`,ASCIR 头文件是 vendored 的 `temporary_dependencies/ascir/ascir_ops.h`。
42+kernel 模板、tiling key、Python codegen 在 graph-autofusion 仓。
43+ 
44+---
45+ 
46+## 3. 环境
47+ 
48+本机参考:
49+ 
50+- toolkit:`/usr/local/Ascend/ascend-toolkit/latest` → cann-9.2.0
51+- 核数少时编译用 `-j 3`(默认过大容易 OOM)
52+- 跑 GE 测试前:
53+ 
54+```bash
55+source /usr/local/Ascend/ascend-toolkit/latest/bin/setenv.bash
56+unset LD_LIBRARY_PATH
57+unset ASCEND_OPP_PATH
58+```
59+ 
60+`unset` 是为了避免旧 `LD_LIBRARY_PATH` / `ASCEND_OPP_PATH` 把测试链到错误的包。
61+ 
62+---
63+ 
64+## 4. Autofuse 仓怎么跑 LLT、怎么看覆盖率
65+ 
66+统一入口:`graph-autofusion/scripts/test/run_autofuse_test.sh`。
67+ 
68+```bash
69+cd /root/workspace/cann-open/graph-autofusion
70+ 
71+# C++ common UT + 覆盖率
72+bash scripts/test/run_autofuse_test.sh -u -m common -c -j 3 \
73+ --ascend_install_path=/usr/local/Ascend/ascend-toolkit/latest
74+ 
75+# Python codegen UT(模块名 codegen)
76+bash scripts/test/run_autofuse_test.sh -u -m codegen -j 3 \
77+ --ascend_install_path=/usr/local/Ascend/ascend-toolkit/latest
78+ 
79+# backend ST(含 Conv2D / ExtendConv2D codegen)
80+bash scripts/test/run_autofuse_test.sh -s -m backend -j 3 \
81+ --ascend_install_path=/usr/local/Ascend/ascend-toolkit/latest
82+```
83+ 
84+UT 模块:`common` / `att` / `optimize` / `codegen` / `framework` / `all`。
85+ST 模块:`backend` / `e2e` / `codegen` / `framework` / `all`。
86+ 
87+也可以增量编指定二进制:
88+ 
89+```bash
90+cmake --build build --target test_common -j 3
91+./build/autofuse/tests/ut/common/test_common --gtest_filter='CommonUtilsTest.ExtendConv2D*'
92+```
93+ 
94+覆盖率:加 `-c` 后打开
95+ 
96+```text
97+graph-autofusion/cov/coverage_report/index.html
98+```
99+ 
100+注意:官方脚本默认写 `build/`。若已有 Release/pkg 的 `build/`,再跑 `-u -c` 会改 cmake 配置。本机曾用独立目录 `build_ut` 编 UT,避免冲掉打包目录。
101+ 
102+---
103+ 
104+## 5. GE 仓怎么跑 LLT、怎么看覆盖率
105+ 
106+GE 有两套构建,不要混用。
107+ 
108+### 5.1 Autofuse frontend UT(不依赖编 autofuse 仓)
109+ 
110+构建目录是 `ge/build`(`RUN_TEST=1`),二进制 `autofusion_ut`。
111+ 
112+```bash
113+cd /root/workspace/cann-open/ge
114+source /usr/local/Ascend/ascend-toolkit/latest/bin/setenv.bash
115+unset LD_LIBRARY_PATH
116+unset ASCEND_OPP_PATH
117+ 
118+# 脚本一条龙(含覆盖率)
119+bash scripts/test/run_autofuse_test.sh -u -m autofuse -c -j 3 \
120+ --ascend_install_path=/usr/local/Ascend/ascend-toolkit/latest
121+ 
122+# 或增量编、单跑
123+cmake --build build --target autofusion_ut -j 3
124+./build/tests/autofuse/ut/autofuse/autofusion_ut \
125+ --gtest_filter='ExtendConv2DLoweringUT.*:LoopApiUT.StoreIgnoredOutputIsNotExtern'
126+```
127+ 
128+`v35/` 下那批 lowering 用例**没有**挂进 `autofusion_ut`(整目录缺头文件,不能 `add_subdirectory(v35)`)。ExtendConv2D lowering 用例放在 `tests/autofuse/ut/autofuse/extend_conv2d_lowering_unittest.cpp`。
129+ 
130+### 5.2 GE infer-shape / tiling UT
131+ 
132+构建目录必须是 `cmake-build-gcov`(`CMAKE_BUILD_TYPE=GCOV -DENABLE_GE_UT=ON`)。
133+ 
134+```bash
135+make -C cmake-build-gcov ut_libge_symbol_infer_utest -j 3
136+./cmake-build-gcov/tests/ge/ut/ge/ut_libge_symbol_infer_utest \
137+ --gtest_filter='SymbolicShapeInferFuncUT.InferSymbolicShapeForExtendConv2D'
138+ 
139+make -C cmake-build-gcov ut_libge_common_utest -j 3
140+./cmake-build-gcov/tests/ge/ut/ge/ut_libge_common_utest \
141+ --gtest_filter='RegisterOpTilingRT2UT.AutofuseNodeWithExtendConvTilingAttrsSuccess'
142+```
143+ 
144+这两个 target 都链 `ge_compiler`。toolkit 头文件若比代码旧(缺 `aclskScopeVerifyGraphInfo` 等),会编不过,与 autofuse 仓无关。
145+ 
146+覆盖率:
147+ 
148+```bash
149+bash scripts/coverage/ge_cov.sh
150+# 或 autofuse 脚本 -c
151+# 报告:ge/cov/coverage_report/index.html
152+```
153+ 
154+只跑部分用例时,整文件覆盖率会偏低(例如只跑 ExtendConv2D lowering,`lowering_impl.cpp` 行覆盖大约十几个百分点)。看覆盖率时要进 HTML,点到具体函数,不要只看整文件百分比。
155+ 
156+---
157+ 
158+## 6. 本次为 ExtendConv2D 补的用例
159+ 
160+### Autofuse 仓
161+ 
162+| 文件 | 用例 | 断言 |
163+|------|------|------|
164+| `autofuse/tests/ut/python/test_asc_codegen_compile_conv2d.py` | `test_build_conv_args_extend_conv2d_*` | 10 个输入槽、双输出;可选槽可空 |
165+| `autofuse/tests/ut/common/test_common_utils.cpp` | `CommonUtilsTest.ExtendConv2D*` | `IsConv2DGraphType` / `ParseConv2DAttr` 的 bias、scale0、round_mode |
166+| `autofuse/tests/st/backend_e2e/conv2d_elemwise_test/conv2d_backend_generate.cpp` | `ExtendConv2DE2eCodegen`、`ExtendConv2DBiasScaleE2eCodegen` | codegen 能出 `GenCVFusionTilingKey` |
167+ 
168+`tests/st/backend_e2e/CMakeLists.txt` 里原先注释掉的 `add_subdirectory(conv2d_elemwise_test)` 已打开,否则 ST 编不进来。
169+ 
170+### GE 仓
171+ 
172+| 文件 | 用例 | 断言 |
173+|------|------|------|
174+| `tests/autofuse/ut/autofuse/extend_conv2d_lowering_unittest.cpp` | 四种 bias/scale0 组合 | Lowering 成功;y0 Cube;未用 y1 非 Extern |
175+| `tests/autofuse/ut/autofuse/loop_api_unittest.cpp` | `StoreIgnoredOutputIsNotExtern` | 占位不是 Extern、是 Cube |
176+| `tests/ge/ut/ge/graph/optimize/symbolic/symbolic_shape_infer_func_unittest.cc` | `InferSymbolicShapeForExtendConv2D` | SPECIFIC / SAME_LOWER 成功,非法 pad_mode 失败 |
177+| `tests/ge/ut/ge/common/op_tiling_rt2_unittest.cc` | `AutofuseNodeWithExtendConvTilingAttrsSuccess` | ExtendConv2D tiling 属性 |
178+ 
179+构图辅助:`all_ops.cpp` / `all_ops.h` / `all_ops_cpp.h`、`op_creator_register.h` 增加了 `ExtendConv2D`。y1 在 IR 上是 required,图里可以不连边。
180+ 
181+---
182+ 
183+## 7. 什么时候才需要编、装 autofuse 仓
184+ 
185+只有这些场景需要:
186+ 
187+1. 改了 graph-autofusion 源码,要跑 **autofuse 仓自己的** UT/ST。
188+2. GE lowering 之后要 **Realize 出新 ASCIR 节点**(toolkit 里还没有 `ExtendConv2D*`)。
189+3. GE ST / 真机编译融合 kernel / 二次 tiling。
190+ 
191+安装步骤(本机 toolkit 是 9.2.0 时,不要复用指向 9.1.0 的旧 `build/`):
192+ 
193+```bash
194+cd /root/workspace/cann-open/graph-autofusion
195+source /usr/local/Ascend/ascend-toolkit/latest/bin/setenv.bash
196+# 旧 build/ 若 cmake 缓存仍是 cann-9.1.0,先清掉
197+rm -rf build
198+sh build.sh --pkg -j 3
199+# 把生成的 cann-graph-autofusion_*.run 装到 toolkit
200+```
201+ 
202+不装包、只想让本机 GE 进程优先加载刚编的 so:
203+ 
204+```bash
205+export LD_LIBRARY_PATH=<autofuse构建目录>/autofuse:$LD_LIBRARY_PATH
206+```
207+ 
208+ABI 要和 toolkit 一致(编译器、Build Type)。
209+ 
210+---
211+ 
212+## 8. 本机踩过的坑
213+ 
214+1. **不要**把整个 `tests/autofuse/v35` 加进 `autofusion_ut`,缺 `base/att_const_values.h` 等,整目录编不过。
215+2. `es::Tensor` 没有默认构造,可选输入写成 `es::Tensor bias(nullptr)`。
216+3. optional 输入连上以后,节点上对应 `InputDesc` 可能仍为空;lowering 前要把对端 output desc 同步到 input desc,否则 `IsStaticShape` 断言失败。
217+4. GE `cmake-build-gcov` 与 toolkit 头文件版本不一致时,`ge_compiler` 编不过,infer-shape / tiling UT 会一起挂,这和 autofuse 仓无关。
218+5. gcov 报 checksum mismatch,多半是旧 `.gcda` 和当前 `.o` 不一致,删对应 `.gcda` 或全量重编即可。
219+6. 覆盖率脚本依赖本机 `lcov`/`gcov` 版本;lcov 2.x 常要加 `--ignore-errors mismatch,empty`。
@@ -432,6 +432,7 @@ PostProcess(compute_graph) // 后处理 wrapper
432```432```
433 433 
434> **说明**:`SymbolicShapeInference` 类(symbolic_shape_inference.h)仅有一个公有方法 `Infer()`。`Simplify()` 是定义在匿名命名空间中的自由函数(symbolic_shape_inference.cc),由 `Infer()` 在推导结束后内部调用(symbolic_shape_inference.cc),并非独立的类方法。`PreProcess()` 和 `PostProcess()` 是 `AutofuseOptimize` 类的 wrapper 函数,分别封装了预处理图优化和后处理逻辑。434> **说明**:`SymbolicShapeInference` 类(symbolic_shape_inference.h)仅有一个公有方法 `Infer()`。`Simplify()` 是定义在匿名命名空间中的自由函数(symbolic_shape_inference.cc),由 `Infer()` 在推导结束后内部调用(symbolic_shape_inference.cc),并非独立的类方法。`PreProcess()` 和 `PostProcess()` 是 `AutofuseOptimize` 类的 wrapper 函数,分别封装了预处理图优化和后处理逻辑。
435+Autofuse lowering 前会为缺少符号属性且 Shape 完全静态的输出补齐常量符号 Shape,兼容前序流程新插入或改写节点后符号属性未同步的场景。
435 436 
436### 7.4 算子实现示例437### 7.4 算子实现示例
437 438 
@@ -0,0 +1,98 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "extend_conv2d_ops.h"
12+#include "esb_graph.h"
13+#include "compliant_op_desc_builder.h"
14+#include "common/checker.h"
15+#include "graph/node.h"
16+#include "graph/utils/graph_utils.h"
17+ 
18+#include <string>
19+#include <utility>
20+#include <vector>
21+ 
22+namespace {
23+ge::OpDescPtr BuildExtendConv2DOpDesc(EsbGraph &graph, const int64_t *strides, int64_t strides_num, const int64_t *pads,
24+ int64_t pads_num, const int64_t *dilations, int64_t dilations_num, int64_t groups,
25+ const char *data_format, int64_t offset_x, const char *round_mode,
26+ const char *pad_mode, bool enable_relu0) {
27+ return ge::CompliantOpDescBuilder()
28+ .OpType("ExtendConv2D")
29+ .Name(("ExtendConv2D_" + std::to_string(graph.NextNodeIndex())).c_str())
30+ .IrDefInputs({
31+ {"x", ge::kIrInputRequired, ""},
32+ {"filter", ge::kIrInputRequired, ""},
33+ {"bias", ge::kIrInputOptional, ""},
34+ {"offset_w", ge::kIrInputOptional, ""},
35+ {"scale0", ge::kIrInputOptional, ""},
36+ })
37+ .IrDefOutputs({
38+ {"y", ge::kIrOutputRequired, ""},
39+ {"y1", ge::kIrOutputRequired, ""},
40+ })
41+ .IrDefAttrs({
42+ {"strides", ge::kAttrRequired, "VT_LIST_INT",
43+ ge::AnyValue::CreateFrom(std::vector<int64_t>(strides, strides + strides_num))},
44+ {"pads", ge::kAttrRequired, "VT_LIST_INT",
45+ ge::AnyValue::CreateFrom(std::vector<int64_t>(pads, pads + pads_num))},
46+ {"dilations", ge::kAttrOptional, "VT_LIST_INT",
47+ ge::AnyValue::CreateFrom(std::vector<int64_t>(dilations, dilations + dilations_num))},
48+ {"groups", ge::kAttrOptional, "VT_INT", ge::AnyValue::CreateFrom(static_cast<int64_t>(groups))},
49+ {"data_format", ge::kAttrOptional, "VT_STRING", ge::AnyValue::CreateFrom(std::string(data_format))},
50+ {"offset_x", ge::kAttrOptional, "VT_INT", ge::AnyValue::CreateFrom(static_cast<int64_t>(offset_x))},
51+ {"round_mode", ge::kAttrOptional, "VT_STRING", ge::AnyValue::CreateFrom(std::string(round_mode))},
52+ {"pad_mode", ge::kAttrOptional, "VT_STRING", ge::AnyValue::CreateFrom(std::string(pad_mode))},
53+ {"enable_hf32", ge::kAttrOptional, "VT_BOOL", ge::AnyValue::CreateFrom(false)},
54+ {"enable_relu0", ge::kAttrOptional, "VT_BOOL", ge::AnyValue::CreateFrom(enable_relu0)},
55+ })
56+ .Build();
57+}
58+ 
59+ge::graphStatus ConnectExtendConv2DInputs(ge::NodePtr &node, EsbTensor *x, EsbTensor *filter, EsbTensor *bias,
60+ EsbTensor *offset_w, EsbTensor *scale0) {
61+ GE_ASSERT_GRAPH_SUCCESS(ge::GraphUtils::AddEdge(x->GetAnchor(), node->GetInDataAnchor(0)));
62+ GE_ASSERT_GRAPH_SUCCESS(ge::GraphUtils::AddEdge(filter->GetAnchor(), node->GetInDataAnchor(1)));
63+ if (bias != nullptr) {
64+ GE_ASSERT_GRAPH_SUCCESS(ge::GraphUtils::AddEdge(bias->GetAnchor(), node->GetInDataAnchor(2)));
65+ }
66+ if (offset_w != nullptr) {
67+ GE_ASSERT_GRAPH_SUCCESS(ge::GraphUtils::AddEdge(offset_w->GetAnchor(), node->GetInDataAnchor(3)));
68+ }
69+ if (scale0 != nullptr) {
70+ GE_ASSERT_GRAPH_SUCCESS(ge::GraphUtils::AddEdge(scale0->GetAnchor(), node->GetInDataAnchor(4)));
71+ }
72+ return ge::GRAPH_SUCCESS;
73+}
74+} // namespace
75+ 
76+#ifdef __cplusplus
77+extern "C" {
78+#endif
79+ 
80+EsbTensor *EsExtendConv2D(EsbTensor *x, EsbTensor *filter, EsbTensor *bias, EsbTensor *offset_w, EsbTensor *scale0,
81+ const int64_t *strides, int64_t strides_num, const int64_t *pads, int64_t pads_num,
82+ const int64_t *dilations, int64_t dilations_num, int64_t groups, const char *data_format,
83+ int64_t offset_x, const char *round_mode, const char *pad_mode, bool enable_relu0) {
84+ GE_ASSERT_NOTNULL(x);
85+ GE_ASSERT_NOTNULL(filter);
86+ auto &graph = x->GetOwner();
87+ auto desc = BuildExtendConv2DOpDesc(graph, strides, strides_num, pads, pads_num, dilations, dilations_num, groups,
88+ data_format, offset_x, round_mode, pad_mode, enable_relu0);
89+ GE_ASSERT_NOTNULL(desc);
90+ auto node = graph.GetComputeGraph()->AddNode(desc);
91+ GE_ASSERT_NOTNULL(node);
92+ GE_ASSERT_GRAPH_SUCCESS(ConnectExtendConv2DInputs(node, x, filter, bias, offset_w, scale0));
93+ return graph.GetEsbTensorFromNode(std::move(node), 0);
94+}
95+ 
96+#ifdef __cplusplus
97+}
98+#endif
@@ -0,0 +1,29 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#ifndef AIR_CXX_COMPILER_GRAPH_EAGER_STYLE_GRAPH_BUILDER_EXTEND_CONV2D_OPS_H_
12+#define AIR_CXX_COMPILER_GRAPH_EAGER_STYLE_GRAPH_BUILDER_EXTEND_CONV2D_OPS_H_
13+ 
14+#include "esb_funcs.h"
15+ 
16+#ifdef __cplusplus
17+extern "C" {
18+#endif
19+ 
20+EsbTensor *EsExtendConv2D(EsbTensor *x, EsbTensor *filter, EsbTensor *bias, EsbTensor *offset_w, EsbTensor *scale0,
21+ const int64_t *strides, int64_t strides_num, const int64_t *pads, int64_t pads_num,
22+ const int64_t *dilations, int64_t dilations_num, int64_t groups, const char *data_format,
23+ int64_t offset_x, const char *round_mode, const char *pad_mode, bool enable_relu0);
24+ 
25+#ifdef __cplusplus
26+}
27+#endif
28+ 
29+#endif // AIR_CXX_COMPILER_GRAPH_EAGER_STYLE_GRAPH_BUILDER_EXTEND_CONV2D_OPS_H_
@@ -0,0 +1,31 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#ifndef AIR_CXX_COMPILER_GRAPH_EAGER_STYLE_GRAPH_BUILDER_EXTEND_CONV2D_OPS_CPP_H_
12+#define AIR_CXX_COMPILER_GRAPH_EAGER_STYLE_GRAPH_BUILDER_EXTEND_CONV2D_OPS_CPP_H_
13+ 
14+#include "esb_funcs_cpp.h"
15+#include "extend_conv2d_ops.h"
16+ 
17+namespace es {
18+inline Tensor ExtendConv2D(const Tensor &x, const Tensor &filter, const Tensor &bias, const Tensor &offset_w,
19+ const Tensor &scale0, const std::vector<int64_t> &strides, const std::vector<int64_t> &pads,
20+ const std::vector<int64_t> &dilations = {1, 1, 1, 1}, int64_t groups = 1,
21+ const char *data_format = "NHWC", int64_t offset_x = 0, const char *round_mode = "rint",
22+ const char *pad_mode = "SPECIFIC", bool enable_relu0 = false) {
23+ auto out = EsExtendConv2D(x.GetEsbTensor(), filter.GetEsbTensor(), bias.GetEsbTensor(), offset_w.GetEsbTensor(),
24+ scale0.GetEsbTensor(), strides.data(), static_cast<int64_t>(strides.size()), pads.data(),
25+ static_cast<int64_t>(pads.size()), dilations.data(), static_cast<int64_t>(dilations.size()),
26+ groups, data_format, offset_x, round_mode, pad_mode, enable_relu0);
27+ return out;
28+}
29+} // namespace es
30+ 
31+#endif // AIR_CXX_COMPILER_GRAPH_EAGER_STYLE_GRAPH_BUILDER_EXTEND_CONV2D_OPS_CPP_H_