已合并
【PR】: feat:autofuse支持extendconv2d #4290
hemahema创建于 8月6日
【PR】: feat:autofuse支持extendconv2d #4290
已合并
共 29 个文件变更+3270-150
| @@ -75,6 +75,7 @@ const std::string kConvSubgraphName = "conv_subgraph"; | |||
| 75 | const std::string kMatMulNodeType = "MatMulV3"; | 75 | const std::string kMatMulNodeType = "MatMulV3"; |
| 76 | const std::string kMatMulBatchMatmulNodeType = "BatchMatMulV3"; | 76 | const std::string kMatMulBatchMatmulNodeType = "BatchMatMulV3"; |
| 77 | const std::string kConv2DNodeType = "Conv2DV2"; | 77 | const std::string kConv2DNodeType = "Conv2DV2"; |
| 78 | +const std::string kExtendConv2DNodeType = "ExtendConv2D"; | ||
| 78 | constexpr int32_t kSocVersionLen = 50; | 79 | constexpr int32_t kSocVersionLen = 50; |
| 79 | 80 | ||
| 80 | gert::KernelContextHolder BuildTilingParseContextHolder( | 81 | gert::KernelContextHolder BuildTilingParseContextHolder( |
| @@ -863,6 +864,7 @@ ge::graphStatus AutofuseNodeWithMatmulTiling(const ge::Operator &op, const fe::P | |||
| 863 | 864 | ||
| 864 | ge::graphStatus AutofuseNodeWithConvTiling(const ge::Operator &op, const fe::PlatFormInfos &ge_platform_infos, | 865 | ge::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 | ||
| 31 | namespace ge { | 33 | namespace ge { |
| 32 | using namespace autofuse; | 34 | using namespace autofuse; |
| 33 | namespace { | 35 | namespace { |
| 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 | + | ||
| 34 | bool IsNodeShouldPrune(const NodePtr &node) { | 68 | bool 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 | ||
| 72 | template <typename T> | 78 | template <typename T> |
| 73 | bool InferAscirDataType(const std::vector<DataType> &input_dtypes, std::vector<DataType> &output_dtypes) { | 79 | bool 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 | |||
| 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 | + | ||
| 76 | namespace { | 90 | namespace { |
| 77 | void DropLowerResultIfNeeded(const std::shared_ptr<KernelBoxMeta> &meta, const ge::OutDataAnchorPtr &dst, | 91 | void 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 | - | ||
| 462 | bool IsStaticShape(std::vector<Expression> output_dims, const ge::OutDataAnchorPtr &dst, | 454 | bool 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); |
| 154 | KernelBox StoreReshape(const ge::OutDataAnchorPtr &dst, const LoopVar &src); | 154 | KernelBox StoreReshape(const ge::OutDataAnchorPtr &dst, const LoopVar &src); |
| 155 | KernelBox StoreExtern(const ge::OutDataAnchorPtr &dst); | 155 | KernelBox StoreExtern(const ge::OutDataAnchorPtr &dst); |
| 156 | +// 给未使用的 optional 输出挂非 Extern 占位,避免空 KernelBox 把同节点其它输出整段 Fallback。 | ||
| 157 | +KernelBox StoreIgnoredOutput(const ge::OutDataAnchorPtr &dst); | ||
| 156 | KernelBox GetKernelBox(const ge::OutDataAnchorPtr &dst); | 158 | KernelBox GetKernelBox(const ge::OutDataAnchorPtr &dst); |
| 157 | 159 | ||
| 158 | LoopVar Scalar(std::string face, ge::DataType dtype); | 160 | LoopVar 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_x | 1133 | << "groups=" << attrs_.groups << ", data_format=" << attrs_.data_format << ", offset_x=" << attrs_.offset_x |
| 1134 | << ", pad_mode=" << attrs_.pad_mode << ", enable_hf32=" << attrs_.enable_hf32 | 1134 | << ", 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; | |||
| 31 | constexpr size_t kMinOneNodeInData = 64U; | 32 | constexpr size_t kMinOneNodeInData = 64U; |
| 32 | constexpr size_t kMatmulMinInputNum = 2U; | 33 | constexpr size_t kMatmulMinInputNum = 2U; |
| 33 | constexpr size_t kNumOne = 1U; | 34 | constexpr size_t kNumOne = 1U; |
| 35 | +constexpr int32_t kConvFilterInputIndex = 1; | ||
| 36 | +// 二次 tiling 缺省 workspace,单位字节,对应 2MB。 | ||
| 37 | +constexpr int64_t kDefaultAscendcOpParaSize = 2 * 1024 * 1024; | ||
| 34 | const char *const kMatmulSubgraph = "matmul_subgraph"; | 38 | const char *const kMatmulSubgraph = "matmul_subgraph"; |
| 35 | const char *const kConvSubgraph = "conv_subgraph"; | 39 | const char *const kConvSubgraph = "conv_subgraph"; |
| 36 | const char *const kMMV3Type = "MatMulV3"; | 40 | const char *const kMMV3Type = "MatMulV3"; |
| 37 | const char *const kBMMV3Type = "BatchMatMulV3"; | 41 | const char *const kBMMV3Type = "BatchMatMulV3"; |
| 38 | const char *const kConv2DType = "Conv2D"; | 42 | const char *const kConv2DType = "Conv2D"; |
| 39 | const char *const kConv2DV2Type = "Conv2DV2"; | 43 | const char *const kConv2DV2Type = "Conv2DV2"; |
| 44 | +const char *const kExtendConv2DType = "ExtendConv2D"; | ||
| 40 | 45 | ||
| 41 | GeShape TransferShapeBetweenHwcnNchw(const GeShape &old_shape, const Format &old_format, const Format &new_format) { | 46 | GeShape 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 | + | ||
| 58 | graphStatus CreateConvSubgraphAttr(const NodePtr &node, vector<const Node *> &compute_ops, size_t &cube_real_inputs) { | 84 | graphStatus 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更新format | 97 | + // 子图拷贝后若缺 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就是matmul | 238 | // 当前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 ge | 52 | } // 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 | ||
| 1307 | graphStatus GetConv2DDims(const NodePtr &node, const ge::InDataAnchorPtr &x_anchor, | 1311 | graphStatus 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 | + | ||
| 1340 | std::vector<ge::InDataAnchorPtr> CollectConv2DInputs(const NodePtr &node) { | 1355 | std::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 namespace | 1367 | } // 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 | + | ||
| 1388 | REGISTER_POINTWISE_LOWER(Abs, loop::Abs); | 1430 | REGISTER_POINTWISE_LOWER(Abs, loop::Abs); |
| 1389 | REGISTER_POINTWISE_LOWER(Add, loop::Add); | 1431 | REGISTER_POINTWISE_LOWER(Add, loop::Add); |
| 1390 | REGISTER_POINTWISE_LOWER(AddV2, loop::Add); | 1432 | REGISTER_POINTWISE_LOWER(AddV2, loop::Add); |
| @@ -1467,6 +1509,8 @@ REGISTER_LOWERING_WITH_EXISTED(BatchMatMulV3, BatchLowerMatMul); | |||
| 1467 | 1509 | ||
| 1468 | REGISTER_LOWERING_WITH_EXISTED(Conv2D, LowerConv2D); | 1510 | REGISTER_LOWERING_WITH_EXISTED(Conv2D, LowerConv2D); |
| 1469 | REGISTER_LOWERING_WITH_EXISTED(Conv2DV2, LowerConv2D); | 1511 | REGISTER_LOWERING_WITH_EXISTED(Conv2DV2, LowerConv2D); |
| 1512 | +// ExtendConv2D 走同一套 InnerLowerConv2D,通过 is_extend_conv2d 切换 ASCIR 变体。 | ||
| 1513 | +REGISTER_LOWERING_WITH_EXISTED(ExtendConv2D, LowerExtendConv2D); | ||
| 1470 | 1514 | ||
| 1471 | REGISTER_LOWERING(ClipByValue) { | 1515 | REGISTER_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"; | |||
| 48 | const std::string kConv2DBias = "Conv2DBias"; | 48 | const std::string kConv2DBias = "Conv2DBias"; |
| 49 | const std::string kConv2DOffset = "Conv2DOffset"; | 49 | const std::string kConv2DOffset = "Conv2DOffset"; |
| 50 | const std::string kConv2DOffsetBias = "Conv2DOffsetBias"; | 50 | const 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 | ||
| 52 | class DefaultCounter : public Counter { | 56 | class 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; | |||
| 26 | constexpr size_t kPadsIdxConv2d = 1U; | 26 | constexpr size_t kPadsIdxConv2d = 1U; |
| 27 | constexpr size_t kDilationsIdxConv2d = 2U; | 27 | constexpr size_t kDilationsIdxConv2d = 2U; |
| 28 | constexpr size_t kGroupsIdxConv2d = 3U; | 28 | constexpr 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 | ||
| 31 | constexpr size_t kPaddingIdxConv2d = 6U; | 33 | constexpr size_t kPaddingIdxConv2d = 6U; |
| 34 | +constexpr size_t kPaddingIdxExtendConv2d = 7U; | ||
| 32 | // NCHW shape | 35 | // NCHW shape |
| 33 | constexpr int32_t kNCHWdimNIdxConv2d = 0; | 36 | constexpr int32_t kNCHWdimNIdxConv2d = 0; |
| 34 | constexpr int32_t kNCHWdimCIdxConv2d = 1; | 37 | constexpr 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 分支中处理。 |
| 593 | IMPL_OP_INFER_SYMBOL_SHAPE_INNER(Conv2DV2).InferSymbolShape(InferShape4Conv2D); | 602 | IMPL_OP_INFER_SYMBOL_SHAPE_INNER(Conv2DV2).InferSymbolShape(InferShape4Conv2D); |
| 603 | +// ExtendConv2D 几何推导与 Conv2DV2 相同,差异仅在 attr 索引与 groups 分支。 | ||
| 604 | +IMPL_OP_INFER_SYMBOL_SHAPE_INNER(ExtendConv2D).InferSymbolShape(InferShape4Conv2D); | ||
| 594 | } // namespace | 605 | } // namespace |
| 595 | } // namespace ge | 606 | } // 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: | |||
| 360 | 3. Operator InferShape function reads input Shape and writes output OriginShape through `InferShapeContext` interface | 360 | 3. Operator InferShape function reads input Shape and writes output OriginShape through `InferShapeContext` interface |
| 361 | 4. `TransformAllOutputsShape()` automatically converts output OriginShape to StorageShape (dimension expansion + format conversion) | 361 | 4. `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 Optimization | 365 | ### 6.2 Execution Graph Optimization |
| 364 | 366 | ||
| 365 | #### FindInferShapeFunc Deduplication | 367 | #### 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 Examples | 437 | ### 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_ | ||
提取单独子函数处理