已合并
fix: 符号表达式反序列化健壮性修复、tile对齐占位符合法化与Broadcast int64放开 #2164
xiebangrui2025创建于 25 天前
fix: 符号表达式反序列化健壮性修复、tile对齐占位符合法化与Broadcast int64放开 #2164
已合并
共 20 个文件变更+277-37
| @@ -143,7 +143,6 @@ REG_ASC_IR(Store) | |||
| 143 | {{"T", TensorType{DT_INT8, DT_UINT8, DT_INT16, DT_UINT16, DT_INT32, DT_UINT32, DT_FLOAT16, | 143 | {{"T", TensorType{DT_INT8, DT_UINT8, DT_INT16, DT_UINT16, DT_INT32, DT_UINT32, DT_FLOAT16, |
| 144 | DT_FLOAT, DT_INT64, DT_BF16}}}}); | 144 | DT_FLOAT, DT_INT64, DT_BF16}}}}); |
| 145 | 145 | ||
| 146 | -// todo: Broadcast DT_INT64 后面根据需要放开 | ||
| 147 | REG_ASC_IR(Broadcast) | 146 | REG_ASC_IR(Broadcast) |
| 148 | .Input("x", "T") | 147 | .Input("x", "T") |
| 149 | .Output("y", "T") | 148 | .Output("y", "T") |
| @@ -151,7 +150,7 @@ REG_ASC_IR(Broadcast) | |||
| 151 | .Impl(v1_soc_versions, {af::ascir::AscIrImplCreator<af::ascir::BroadcastAscIrAttImpl>(), | 150 | .Impl(v1_soc_versions, {af::ascir::AscIrImplCreator<af::ascir::BroadcastAscIrAttImpl>(), |
| 152 | af::ascir::AscIrImplCreator<af::ascir::BroadcastAscIrCodegenImpl>(), | 151 | af::ascir::AscIrImplCreator<af::ascir::BroadcastAscIrCodegenImpl>(), |
| 153 | {{"T", TensorType{DT_UINT8, DT_INT16, DT_INT32, DT_FLOAT16, DT_FLOAT, DT_INT8, DT_UINT16, | 152 | {{"T", TensorType{DT_UINT8, DT_INT16, DT_INT32, DT_FLOAT16, DT_FLOAT, DT_INT8, DT_UINT16, |
| 154 | - DT_UINT32, DT_UINT64}}}}); | 153 | + DT_UINT32, DT_UINT64, DT_INT64}}}}); |
| 155 | 154 | ||
| 156 | REG_ASC_IR(RemovePad) | 155 | REG_ASC_IR(RemovePad) |
| 157 | .Input("x", "T") | 156 | .Input("x", "T") |
| @@ -896,7 +896,7 @@ std::string AxesReorderSolverGen::InitiateArgs() { | |||
| 896 | if (IsValid(arg) && (!input_align_expr.IsConstExpr() || | 896 | if (IsValid(arg) && (!input_align_expr.IsConstExpr() || |
| 897 | (input_align_expr.IsConstExpr() && | 897 | (input_align_expr.IsConstExpr() && |
| 898 | af::SymbolicUtils::StaticCheckNe(input_align_expr, af::Symbol(1)) == af::TriBool::kTrue))) { | 898 | af::SymbolicUtils::StaticCheckNe(input_align_expr, af::Symbol(1)) == af::TriBool::kTrue))) { |
| 899 | - std::string input_align = "std::max(1, " + Str(input_align_expr) + ")"; | 899 | + std::string input_align = "std::max(1, " + RuntimeAlignExprToCode(Str(input_align_expr)) + ")"; |
| 900 | strs += " " + arg_string + ".value = (tiling_data.get_" + arg_string + "() + " + input_align + " - 1) / " + | 900 | strs += " " + arg_string + ".value = (tiling_data.get_" + arg_string + "() + " + input_align + " - 1) / " + |
| 901 | input_align + " * " + input_align + ";\n"; | 901 | input_align + " * " + input_align + ";\n"; |
| 902 | } else { | 902 | } else { |
| @@ -1362,7 +1362,7 @@ std::string AxesReorderSolverGen::GenInputInfo(std::vector<Expr> &all_cons, std: | |||
| 1362 | const auto align = arg_align_map_[local_var]; | 1362 | const auto align = arg_align_map_[local_var]; |
| 1363 | const auto prompt_align = arg_prompt_align_map_[local_var]; | 1363 | const auto prompt_align = arg_prompt_align_map_[local_var]; |
| 1364 | const auto data_type_size = data_type_size_map_[local_var]; | 1364 | const auto data_type_size = data_type_size_map_[local_var]; |
| 1365 | - strs += " " + Str(local_var) + ".align = std::max(1, " + Str(align) + ");\n"; | 1365 | + strs += " " + Str(local_var) + ".align = std::max(1, " + RuntimeAlignExprToCode(Str(align)) + ");\n"; |
| 1366 | if (data_type_size > 0U) { | 1366 | if (data_type_size > 0U) { |
| 1367 | strs += " " + Str(local_var) + ".data_type_size = " + std::to_string(data_type_size) + ";\n"; | 1367 | strs += " " + Str(local_var) + ".data_type_size = " + std::to_string(data_type_size) + ";\n"; |
| 1368 | } | 1368 | } |
| @@ -9,6 +9,7 @@ | |||
| 9 | */ | 9 | */ |
| 10 | 10 | ||
| 11 | #include "general_solver_gen.h" | 11 | #include "general_solver_gen.h" |
| 12 | +#include "common_utils.h" | ||
| 12 | #include "graph/symbolizer/symbolic.h" | 13 | #include "graph/symbolizer/symbolic.h" |
| 13 | #include "graph/symbolizer/symbolic_utils.h" | 14 | #include "graph/symbolizer/symbolic_utils.h" |
| 14 | 15 | ||
| @@ -340,7 +341,7 @@ std::string GeneralSolverGen::GenAlignInput(const Expr arg, const std::string in | |||
| 340 | if (IsValid(arg) && (!input_align_expr.IsConstExpr() || | 341 | if (IsValid(arg) && (!input_align_expr.IsConstExpr() || |
| 341 | (input_align_expr.IsConstExpr() && | 342 | (input_align_expr.IsConstExpr() && |
| 342 | af::SymbolicUtils::StaticCheckNe(input_align_expr, af::Symbol(1)) == af::TriBool::kTrue))) { | 343 | af::SymbolicUtils::StaticCheckNe(input_align_expr, af::Symbol(1)) == af::TriBool::kTrue))) { |
| 343 | - input_align = Str(input_align_expr); | 344 | + input_align = ascgen_utils::RuntimeAlignExprToCode(Str(input_align_expr)); |
| 344 | codes += indent + Str(arg) + " = ((" + Str(arg) + " + " + input_align + " - 1) / " + input_align + ") * " + | 345 | codes += indent + Str(arg) + " = ((" + Str(arg) + " + " + input_align + " - 1) / " + input_align + ") * " + |
| 345 | input_align + ";\n"; | 346 | input_align + ";\n"; |
| 346 | } | 347 | } |
| @@ -1082,7 +1082,10 @@ af::Status CreateCVFusionResult(ascir::FusedScheduledResult &elemwise_schedule_r | |||
| 1082 | } | 1082 | } |
| 1083 | if (ax->type == ascir::Axis::kAxisTypeTileInner) { | 1083 | if (ax->type == ascir::Axis::kAxisTypeTileInner) { |
| 1084 | GELOGI("Add tile inner axis(%s) symbol align for graph(%s)", ax->name.c_str(), graph.GetName().c_str()); | 1084 | GELOGI("Add tile inner axis(%s) symbol align for graph(%s)", ax->name.c_str(), graph.GetName().c_str()); |
| 1085 | - ax->align = af::Symbol("get_g_basen_basem_align()"); | 1085 | + // 运行时占位符(由生成 tiling 代码中 set/get_g_basen_basem_align 维护), |
| 1086 | + // 名字必须为合法标识符:轴属性会经 AscGraph 序列化克隆(FusedGraphUnfolder::CloneAscGraphs), | ||
| 1087 | + // 带 "()" 的名字无法被表达式 scanner/parser 解析,导致克隆图 align 丢失 | ||
| 1088 | + ax->align = af::Symbol(kRuntimeAlignFuncName); | ||
| 1086 | } | 1089 | } |
| 1087 | } | 1090 | } |
| 1088 | }; | 1091 | }; |
| @@ -168,6 +168,18 @@ static std::string VectorToStr(const std::vector<T> &vec, char start = '[', char | |||
| 168 | return result; | 168 | return result; |
| 169 | } | 169 | } |
| 170 | af::Expression GetTensorSize(const af::AscTensor &tensor); | 170 | af::Expression GetTensorSize(const af::AscTensor &tensor); |
| 171 | + | ||
| 172 | +// cube 融合 tile 内轴的运行时对齐占位符:符号名必须为合法标识符(轴属性经 AscGraph | ||
| 173 | +// 序列化克隆时需可被表达式 scanner/parser 解析),打印进生成的求解器/tiling 代码时 | ||
| 174 | +// 需还原为函数调用形态(getter 由生成的 tiling 代码提供,运行时求值) | ||
| 175 | +inline constexpr char kRuntimeAlignFuncName[] = "get_g_basen_basem_align"; | ||
| 176 | + | ||
| 177 | +inline std::string RuntimeAlignExprToCode(const std::string &expr_str) { | ||
| 178 | + if (expr_str == kRuntimeAlignFuncName) { | ||
| 179 | + return std::string(kRuntimeAlignFuncName) + "()"; | ||
| 180 | + } | ||
| 181 | + return expr_str; | ||
| 182 | +} | ||
| 171 | af::Expression CalculateOneWorkspaceSize(const af::AscNodePtr &workspace_nodes); | 183 | af::Expression CalculateOneWorkspaceSize(const af::AscNodePtr &workspace_nodes); |
| 172 | af::Expression CalculateWorkspaceSize(const std::vector<af::AscNodePtr> &workspace_nodes); | 184 | af::Expression CalculateWorkspaceSize(const std::vector<af::AscNodePtr> &workspace_nodes); |
| 173 | af::Expression CalcExtraTmpBufForAscGraph(const ascir::ImplGraph &graph); | 185 | af::Expression CalcExtraTmpBufForAscGraph(const ascir::ImplGraph &graph); |
| @@ -424,7 +424,10 @@ graphStatus ShapeEnvAttr::AppendReplacement(const Expression &target, const Expr | |||
| 424 | return GRAPH_SUCCESS; | 424 | return GRAPH_SUCCESS; |
| 425 | } | 425 | } |
| 426 | } | 426 | } |
| 427 | - // 判断replacement是否成环 | 427 | + // 判断replacement是否成环:成环时跳过该条替换(保留既有替换集合不变), |
| 428 | + // 不返回失败——等式两侧共享符号的合法自引用约束(如 s1*s1 == s3)同样会命中 | ||
| 429 | + // 该检测,若拒绝会让推导整体失败并污染 guard 判定结果,静默跳过语义等价于 | ||
| 430 | + // 不建立该替换(guard 仍正常记录,仅替换关系不进并查集) | ||
| 428 | if (CheckReplacementCycle(expr1, expr2)) { | 431 | if (CheckReplacementCycle(expr1, expr2)) { |
| 429 | GELOGW("Unsupported append replacement %s to %s, replacement contains the other.", | 432 | GELOGW("Unsupported append replacement %s to %s, replacement contains the other.", |
| 430 | SymbolicUtils::ToString(expr1).c_str(), SymbolicUtils::ToString(expr2).c_str()); | 433 | SymbolicUtils::ToString(expr1).c_str(), SymbolicUtils::ToString(expr2).c_str()); |
| @@ -484,18 +487,39 @@ bool ShapeEnvAttr::CheckReplacementCycle(const Expression &expr1, const Expressi | |||
| 484 | GE_ASSERT_SUCCESS(FindRootExpr(expr2, root_expr2)); | 487 | GE_ASSERT_SUCCESS(FindRootExpr(expr2, root_expr2)); |
| 485 | } | 488 | } |
| 486 | 489 | ||
| 487 | - /* | 490 | + if (root_expr1 == root_expr2) { |
| 488 | - * *判断exp1和expr2是否相互包含, | 491 | + return false; |
| 489 | - * 1) 先判断原表达式是否包含, 例如expr1: s0 + s1, expr2: s1, 则expr1包含expr2 | ||
| 490 | - * 2) 然后判断化简后是否包含, 例如已有replacement: s1 == s2, expr1: s0 + s1, expr2: s2 | ||
| 491 | - * 则expr1化简后为s0 + s2, 包含expr2 | ||
| 492 | - */ | ||
| 493 | - if (root_expr2.IsVariableExpr()) { | ||
| 494 | - return root_expr1.ContainVar(root_expr2) || root_expr1.Simplify().ContainVar(root_expr2); | ||
| 495 | - } else if (root_expr1.IsVariableExpr()) { | ||
| 496 | - return root_expr2.ContainVar(root_expr1) || root_expr2.Simplify().ContainVar(root_expr1); | ||
| 497 | } | 492 | } |
| 498 | - return false; | 493 | + |
| 494 | + /* | ||
| 495 | + * 判断expr1与expr2是否互相包含: | ||
| 496 | + * 旧实现仅在某一侧根为单符号时用 ContainVar+Simplify 判断,两根均为复合表达式时 | ||
| 497 | + * 直接漏判(例如已有 replacement: s0 == s2,再 Append(s0 + s1, s2):s0 的根是 s2, | ||
| 498 | + * expr1 经符号根解析后包含 s2,构成环,旧实现检测不到)。 | ||
| 499 | + * 新实现逐表达式枚举自由符号并解析其替换根,与对方根比较即可覆盖复合表达式对, | ||
| 500 | + * 且无需对根表达式做化简展开(化简在替换链较长时代价高)。 | ||
| 501 | + */ | ||
| 502 | + const auto contains_root = [this](const Expression &expr, const Expression &target_root) { | ||
| 503 | + for (const auto &symbol : expr.FreeSymbols()) { | ||
| 504 | + Expression symbol_root = symbol; | ||
| 505 | + if (replacements_.find(symbol) != replacements_.end() && FindRootExpr(symbol, symbol_root) != GRAPH_SUCCESS) { | ||
| 506 | + return true; | ||
| 507 | + } | ||
| 508 | + if (symbol_root == target_root) { | ||
| 509 | + return true; | ||
| 510 | + } | ||
| 511 | + // 根表达式包含对方根的自由符号时构成间接环:如已有 s3 == Min(2, s2) 时再建立 | ||
| 512 | + // s2 == (s0+s3)*Ceil(s1),替换链闭合出 s2 -> 复合 -> s3 -> Min(2, s2) 的环 | ||
| 513 | + // (ContainVar 仅接受无参表达式,复合根需逐自由符号检查) | ||
| 514 | + for (const auto &target_sym : target_root.FreeSymbols()) { | ||
| 515 | + if (symbol_root.ContainVar(target_sym)) { | ||
| 516 | + return true; | ||
| 517 | + } | ||
| 518 | + } | ||
| 519 | + } | ||
| 520 | + return false; | ||
| 521 | + }; | ||
| 522 | + return contains_root(root_expr1, root_expr2) || contains_root(root_expr2, root_expr1); | ||
| 499 | } | 523 | } |
| 500 | 524 | ||
| 501 | } // namespace af | 525 | } // namespace af |
| @@ -8,6 +8,7 @@ | |||
| 8 | * See LICENSE in the root of the software repository for the full text of the License. | 8 | * See LICENSE in the root of the software repository for the full text of the License. |
| 9 | */ | 9 | */ |
| 10 | #include "expr_parser.h" | 10 | #include "expr_parser.h" |
| 11 | +#include <unordered_map> | ||
| 11 | #include "common/checker.h" | 12 | #include "common/checker.h" |
| 12 | #include "symengine/real_double.h" | 13 | #include "symengine/real_double.h" |
| 13 | namespace af { | 14 | namespace af { |
| @@ -28,6 +29,12 @@ graphStatus ExprParser::Eat(TokenType type) { | |||
| 28 | } | 29 | } |
| 29 | 30 | ||
| 30 | ExpressionImplPtr ExprParser::ParserFactor() { | 31 | ExpressionImplPtr ExprParser::ParserFactor() { |
| 32 | + // 单参函数(Log/Ceil/Floor/Abs/Exp/Sqrt)均为 Func(expr) 结构,走分发表统一派发, | ||
| 33 | + // 新增单参函数只需在表中注册,避免本函数分支随函数 token 增长 | ||
| 34 | + static const std::unordered_map<TokenType, ExpressionImplPtr (ExprParser::*)()> kUnaryFuncParsers = { | ||
| 35 | + {TokenType::kLog, &ExprParser::ParserLogFunction}, {TokenType::kCeil, &ExprParser::ParserCeilFunction}, | ||
| 36 | + {TokenType::kFloor, &ExprParser::ParserFloorFunction}, {TokenType::kAbs, &ExprParser::ParserAbsFunction}, | ||
| 37 | + {TokenType::kExp, &ExprParser::ParserExpFunction}, {TokenType::kSqrt, &ExprParser::ParserSqrtFunction}}; | ||
| 31 | switch (currentToken_.type) { | 38 | switch (currentToken_.type) { |
| 32 | case TokenType::kIdentifier: | 39 | case TokenType::kIdentifier: |
| 33 | return ParserIdentifier(); | 40 | return ParserIdentifier(); |
| @@ -41,14 +48,6 @@ ExpressionImplPtr ExprParser::ParserFactor() { | |||
| 41 | return ParserPowFunction(); | 48 | return ParserPowFunction(); |
| 42 | case TokenType::kMod: | 49 | case TokenType::kMod: |
| 43 | return ParserModFunction(); | 50 | return ParserModFunction(); |
| 44 | - case TokenType::kLog: | ||
| 45 | - return ParserLogFunction(); | ||
| 46 | - case TokenType::kCeil: | ||
| 47 | - return ParserCeilFunction(); | ||
| 48 | - case TokenType::kFloor: | ||
| 49 | - return ParserFloorFunction(); | ||
| 50 | - case TokenType::kAbs: | ||
| 51 | - return ParserAbsFunction(); | ||
| 52 | case TokenType::kRational: | 51 | case TokenType::kRational: |
| 53 | return ParserRationalFunction(); | 52 | return ParserRationalFunction(); |
| 54 | case TokenType::kNumber: | 53 | case TokenType::kNumber: |
| @@ -71,9 +70,14 @@ ExpressionImplPtr ExprParser::ParserFactor() { | |||
| 71 | case TokenType::kLogicalOr: | 70 | case TokenType::kLogicalOr: |
| 72 | return ParserLogicalOr(); | 71 | return ParserLogicalOr(); |
| 73 | default: | 72 | default: |
| 74 | - GELOGE(ge::PARAM_INVALID, "Unsupported operator %d when Parser factor.", currentToken_.type); | 73 | + break; |
| 75 | - return nullptr; | ||
| 76 | } | 74 | } |
| 75 | + const auto it = kUnaryFuncParsers.find(currentToken_.type); | ||
| 76 | + if (it != kUnaryFuncParsers.end()) { | ||
| 77 | + return (this->*(it->second))(); | ||
| 78 | + } | ||
| 79 | + GELOGE(ge::PARAM_INVALID, "Unsupported operator %d when Parser factor.", currentToken_.type); | ||
| 80 | + return nullptr; | ||
| 77 | } | 81 | } |
| 78 | 82 | ||
| 79 | ExpressionImplPtr ExprParser::ParserAddSubtract() { | 83 | ExpressionImplPtr ExprParser::ParserAddSubtract() { |
| @@ -273,6 +277,25 @@ ExpressionImplPtr ExprParser::ParserRationalFunction() { | |||
| 273 | return Rational(arg1, arg2); | 277 | return Rational(arg1, arg2); |
| 274 | } | 278 | } |
| 275 | 279 | ||
| 280 | +// Exp(x)/Sqrt(x) 为打印侧 DefaultPowPrinter 对 Pow(E, x)/Pow(x, 1/2) 的输出形态, | ||
| 281 | +// 解析时还原为等价的 Pow 表达式,保证序列化-反序列化往返闭环 | ||
| 282 | +ExpressionImplPtr ExprParser::ParserExpFunction() { | ||
| 283 | + GE_ASSERT_SUCCESS(Eat(TokenType::kExp)); | ||
| 284 | + GE_ASSERT_SUCCESS(Eat(TokenType::kLparen)); | ||
| 285 | + auto arg = ParserExpression(); | ||
| 286 | + GE_ASSERT_SUCCESS(Eat(TokenType::kRparen)); | ||
| 287 | + return Exp(arg); | ||
| 288 | +} | ||
| 289 | + | ||
| 290 | +ExpressionImplPtr ExprParser::ParserSqrtFunction() { | ||
| 291 | + GE_ASSERT_SUCCESS(Eat(TokenType::kSqrt)); | ||
| 292 | + GE_ASSERT_SUCCESS(Eat(TokenType::kLparen)); | ||
| 293 | + auto arg = ParserExpression(); | ||
| 294 | + GE_ASSERT_SUCCESS(Eat(TokenType::kRparen)); | ||
| 295 | + return Pow(arg, Rational(ExpressionImpl::CreateExpressionImpl(static_cast<int64_t>(1)), | ||
| 296 | + ExpressionImpl::CreateExpressionImpl(static_cast<int64_t>(2)))); | ||
| 297 | +} | ||
| 298 | + | ||
| 276 | ExpressionImplPtr ExprParser::ParserNumber() { | 299 | ExpressionImplPtr ExprParser::ParserNumber() { |
| 277 | const std::string &numberStr = currentToken_.value; | 300 | const std::string &numberStr = currentToken_.value; |
| 278 | try { | 301 | try { |
| @@ -25,6 +25,10 @@ class ExprParser { | |||
| 25 | Init(); | 25 | Init(); |
| 26 | } | 26 | } |
| 27 | ExpressionImplPtr ParserExpression(); | 27 | ExpressionImplPtr ParserExpression(); |
| 28 | + // ParserExpression 返回后,输入串是否已被完整消费(未残留多余 token) | ||
| 29 | + bool IsAtEnd() const { | ||
| 30 | + return currentToken_.type == TokenType::kEnd; | ||
| 31 | + } | ||
| 28 | 32 | ||
| 29 | private: | 33 | private: |
| 30 | graphStatus Init(); | 34 | graphStatus Init(); |
| @@ -41,6 +45,8 @@ class ExprParser { | |||
| 41 | ExpressionImplPtr ParserFloorFunction(); | 45 | ExpressionImplPtr ParserFloorFunction(); |
| 42 | ExpressionImplPtr ParserAbsFunction(); | 46 | ExpressionImplPtr ParserAbsFunction(); |
| 43 | ExpressionImplPtr ParserRationalFunction(); | 47 | ExpressionImplPtr ParserRationalFunction(); |
| 48 | + ExpressionImplPtr ParserExpFunction(); | ||
| 49 | + ExpressionImplPtr ParserSqrtFunction(); | ||
| 44 | ExpressionImplPtr ParserNumber(); | 50 | ExpressionImplPtr ParserNumber(); |
| 45 | ExpressionImplPtr ParserIdentifier(); | 51 | ExpressionImplPtr ParserIdentifier(); |
| 46 | ExpressionImplPtr ParserLParen(); | 52 | ExpressionImplPtr ParserLParen(); |
| @@ -208,16 +208,44 @@ ExpressionImplPtr ExpressionImpl::Parse(const std::string &expr_str) { | |||
| 208 | return ret; | 208 | return ret; |
| 209 | } | 209 | } |
| 210 | 210 | ||
| 211 | -ExpressionImplPtr ExpressionImpl::Deserialize(const std::string &expr_str) { | 211 | +namespace { |
| 212 | - auto ret = Parse(expr_str); | 212 | +// 完整解析输入串:解析失败或输入串存在未消费的残留 token(如 "a(s0)" 被截断为 "a")均视为失败 |
| 213 | - GE_WARN_ASSERT(ret != nullptr); | 213 | +ExpressionImplPtr ParseWholeExpression(const std::string &expr_str) { |
| 214 | - if (ret->Str() == expr_str) { | 214 | + Scanner scanner(expr_str); |
| 215 | - return ret; | 215 | + ExprParser expr_parser(scanner); |
| 216 | - } else { | 216 | + auto ret = expr_parser.ParserExpression(); |
| 217 | - GELOGW("Parse expression str %s abnormal, result is %s, please check the string is valid.", expr_str.c_str(), | 217 | + if (ret == nullptr || !expr_parser.IsAtEnd()) { |
| 218 | - ret->Str().c_str()); | ||
| 219 | return nullptr; | 218 | return nullptr; |
| 220 | } | 219 | } |
| 220 | + return ret; | ||
| 221 | +} | ||
| 222 | +} // namespace | ||
| 223 | + | ||
| 224 | +ExpressionImplPtr ExpressionImpl::Deserialize(const std::string &expr_str) { | ||
| 225 | + auto ret = ParseWholeExpression(expr_str); | ||
| 226 | + if (ret == nullptr) { | ||
| 227 | + GELOGE(FAILED, "Parse expression str %s failed or incomplete.", expr_str.c_str()); | ||
| 228 | + return nullptr; | ||
| 229 | + } | ||
| 230 | + const std::string reserialized = ret->Str(); | ||
| 231 | + if (reserialized == expr_str) { | ||
| 232 | + return ret; | ||
| 233 | + } | ||
| 234 | + // SymEngine 表达式树是规范形:语义相同的表达式解析后得到相同的树 | ||
| 235 | + // (Add 合并同类项、交换律参数按规范化顺序排列)。因此将打印结果再次解析 | ||
| 236 | + // 并比较两棵树,即可区分"仅打印形式不同"与"语义不等价"。 | ||
| 237 | + // 背景:SymEngine 规范打印会重排负系数 Add 项、交换交换律算子参数,合法的 | ||
| 238 | + // 序列化串(如 guard)曾因字节不等被拒,返回 nullptr 后上层 guard 集合 | ||
| 239 | + // 比较器解引用空指针断言崩溃。 | ||
| 240 | + const auto reparsed = ParseWholeExpression(reserialized); | ||
| 241 | + if (reparsed != nullptr && ret->Compare(*reparsed) == 0) { | ||
| 242 | + GELOGW("Parse expression str %s abnormal, reserialized to %s, they are semantically equal, accepted.", | ||
| 243 | + expr_str.c_str(), reserialized.c_str()); | ||
| 244 | + return ret; | ||
| 245 | + } | ||
| 246 | + GELOGE(FAILED, "Parse expression str %s abnormal, reserialized to %s, they are not semantically equal.", | ||
| 247 | + expr_str.c_str(), reserialized.c_str()); | ||
| 248 | + return nullptr; | ||
| 221 | } | 249 | } |
| 222 | 250 | ||
| 223 | ExpressionImplPtr ExpressionImpl::Replace(const std::map<ExpressionImpl *, ExpressionImpl *> &replace_vars) const { | 251 | ExpressionImplPtr ExpressionImpl::Replace(const std::map<ExpressionImpl *, ExpressionImpl *> &replace_vars) const { |
| @@ -681,6 +709,13 @@ ExpressionImplPtr Pow(const ExpressionImplPtr &a, const ExpressionImplPtr &b) { | |||
| 681 | return ExpressionImpl::CreateExpressionImpl<const SymEngineExprPtr &>(sym_expr); | 709 | return ExpressionImpl::CreateExpressionImpl<const SymEngineExprPtr &>(sym_expr); |
| 682 | } | 710 | } |
| 683 | 711 | ||
| 712 | +ExpressionImplPtr Exp(const ExpressionImplPtr &a) { | ||
| 713 | + GE_ASSERT_NOTNULL(a); | ||
| 714 | + GE_ASSERT_TRUE(!a->sym_expr_.is_null()); | ||
| 715 | + SymEngineExprPtr sym_expr = SymEngine::pow(SymEngine::E, a->sym_expr_); | ||
| 716 | + return ExpressionImpl::CreateExpressionImpl<const SymEngineExprPtr &>(sym_expr); | ||
| 717 | +} | ||
| 718 | + | ||
| 684 | ExpressionImplPtr Mod(const ExpressionImplPtr &a, const ExpressionImplPtr &b) { | 719 | ExpressionImplPtr Mod(const ExpressionImplPtr &a, const ExpressionImplPtr &b) { |
| 685 | GE_ASSERT_NOTNULL(a); | 720 | GE_ASSERT_NOTNULL(a); |
| 686 | GE_ASSERT_NOTNULL(b); | 721 | GE_ASSERT_NOTNULL(b); |
| @@ -133,6 +133,7 @@ class ExpressionImpl { | |||
| 133 | friend ExpressionImplPtr Min(const ExpressionImplPtr &a, const ExpressionImplPtr &b); | 133 | friend ExpressionImplPtr Min(const ExpressionImplPtr &a, const ExpressionImplPtr &b); |
| 134 | friend ExpressionImplPtr Abs(const ExpressionImplPtr &a); | 134 | friend ExpressionImplPtr Abs(const ExpressionImplPtr &a); |
| 135 | friend ExpressionImplPtr Pow(const ExpressionImplPtr &a, const ExpressionImplPtr &b); | 135 | friend ExpressionImplPtr Pow(const ExpressionImplPtr &a, const ExpressionImplPtr &b); |
| 136 | + friend ExpressionImplPtr Exp(const ExpressionImplPtr &a); | ||
| 136 | friend ExpressionImplPtr Mod(const ExpressionImplPtr &a, const ExpressionImplPtr &b); | 137 | friend ExpressionImplPtr Mod(const ExpressionImplPtr &a, const ExpressionImplPtr &b); |
| 137 | friend ExpressionImplPtr Log(const ExpressionImplPtr &a); // 默认以E为底 | 138 | friend ExpressionImplPtr Log(const ExpressionImplPtr &a); // 默认以E为底 |
| 138 | friend ExpressionImplPtr Log(const ExpressionImplPtr &arg, const ExpressionImplPtr &base); | 139 | friend ExpressionImplPtr Log(const ExpressionImplPtr &arg, const ExpressionImplPtr &base); |
| @@ -164,6 +165,7 @@ ExpressionImplPtr Max(const ExpressionImplPtr &a, const ExpressionImplPtr &b); | |||
| 164 | ExpressionImplPtr Min(const ExpressionImplPtr &a, const ExpressionImplPtr &b); | 165 | ExpressionImplPtr Min(const ExpressionImplPtr &a, const ExpressionImplPtr &b); |
| 165 | ExpressionImplPtr Abs(const ExpressionImplPtr &a); | 166 | ExpressionImplPtr Abs(const ExpressionImplPtr &a); |
| 166 | ExpressionImplPtr Pow(const ExpressionImplPtr &a, const ExpressionImplPtr &b); | 167 | ExpressionImplPtr Pow(const ExpressionImplPtr &a, const ExpressionImplPtr &b); |
| 168 | +ExpressionImplPtr Exp(const ExpressionImplPtr &a); | ||
| 167 | ExpressionImplPtr Mod(const ExpressionImplPtr &a, const ExpressionImplPtr &b); | 169 | ExpressionImplPtr Mod(const ExpressionImplPtr &a, const ExpressionImplPtr &b); |
| 168 | ExpressionImplPtr Log(const ExpressionImplPtr &a); // 默认以E为底 | 170 | ExpressionImplPtr Log(const ExpressionImplPtr &a); // 默认以E为底 |
| 169 | ExpressionImplPtr Log(const ExpressionImplPtr &arg, const ExpressionImplPtr &base); | 171 | ExpressionImplPtr Log(const ExpressionImplPtr &arg, const ExpressionImplPtr &base); |
| @@ -36,6 +36,8 @@ std::unordered_map<std::string, TokenType> kTokenMap = {{"Max", TokenType::kMax} | |||
| 36 | {"False", TokenType::kFalse}, | 36 | {"False", TokenType::kFalse}, |
| 37 | {"Floor", TokenType::kFloor}, | 37 | {"Floor", TokenType::kFloor}, |
| 38 | {"Mod", TokenType::kMod}, | 38 | {"Mod", TokenType::kMod}, |
| 39 | + {"Exp", TokenType::kExp}, | ||
| 40 | + {"Sqrt", TokenType::kSqrt}, | ||
| 39 | {"LogicAnd", TokenType::kLogicalAnd}, | 41 | {"LogicAnd", TokenType::kLogicalAnd}, |
| 40 | {"LogicOr", TokenType::kLogicalOr}}; | 42 | {"LogicOr", TokenType::kLogicalOr}}; |
| 41 | } | 43 | } |
| @@ -38,6 +38,8 @@ enum class TokenType { | |||
| 38 | kFloor, // floor | 38 | kFloor, // floor |
| 39 | kAbs, // abs | 39 | kAbs, // abs |
| 40 | kRational, // rational | 40 | kRational, // rational |
| 41 | + kExp, // exp: Pow(E, x) 的打印形态 | ||
| 42 | + kSqrt, // sqrt: Pow(x, 1/2) 的打印形态 | ||
| 41 | kEq, // EXPECT_EQ | 43 | kEq, // EXPECT_EQ |
| 42 | kNe, // EXPECT_NE | 44 | kNe, // EXPECT_NE |
| 43 | kLt, // EXPECT_LT | 45 | kLt, // EXPECT_LT |
| @@ -53,6 +53,10 @@ Expression Pow(const Expression &base, const Expression &exp) { | |||
| 53 | return Expression(Pow(base.impl_, exp.impl_)); | 53 | return Expression(Pow(base.impl_, exp.impl_)); |
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | +Expression Exp(const Expression &a) { | ||
| 57 | + return Expression(Exp(a.impl_)); | ||
| 58 | +} | ||
| 59 | + | ||
| 56 | Expression Mod(const Expression &base, const Expression &exp) { | 60 | Expression Mod(const Expression &base, const Expression &exp) { |
| 57 | return Expression(Mod(base.impl_, exp.impl_)); | 61 | return Expression(Mod(base.impl_, exp.impl_)); |
| 58 | } | 62 | } |
| @@ -22,6 +22,7 @@ Expression Div(const Expression &a, const Expression &b); | |||
| 22 | Expression Max(const Expression &a, const Expression &b); | 22 | Expression Max(const Expression &a, const Expression &b); |
| 23 | Expression Min(const Expression &a, const Expression &b); | 23 | Expression Min(const Expression &a, const Expression &b); |
| 24 | Expression Pow(const Expression &base, const Expression &exp); | 24 | Expression Pow(const Expression &base, const Expression &exp); |
| 25 | +Expression Exp(const Expression &a); | ||
| 25 | Expression Mod(const Expression &base, const Expression &exp); | 26 | Expression Mod(const Expression &base, const Expression &exp); |
| 26 | Expression Abs(const Expression &a); | 27 | Expression Abs(const Expression &a); |
| 27 | Expression Log(const Expression &a); // 默认以E为底 | 28 | Expression Log(const Expression &a); // 默认以E为底 |
| @@ -208,6 +208,7 @@ class Expression { | |||
| 208 | friend Expression sym::Max(const Expression &a, const Expression &b); | 208 | friend Expression sym::Max(const Expression &a, const Expression &b); |
| 209 | friend Expression sym::Min(const Expression &a, const Expression &b); | 209 | friend Expression sym::Min(const Expression &a, const Expression &b); |
| 210 | friend Expression sym::Pow(const Expression &base, const Expression &exp); | 210 | friend Expression sym::Pow(const Expression &base, const Expression &exp); |
| 211 | + friend Expression sym::Exp(const Expression &a); | ||
| 211 | friend Expression sym::Mod(const Expression &base, const Expression &exp); | 212 | friend Expression sym::Mod(const Expression &base, const Expression &exp); |
| 212 | friend Expression sym::Abs(const Expression &a); | 213 | friend Expression sym::Abs(const Expression &a); |
| 213 | friend Expression sym::Log(const Expression &a); // 默认以E为底 | 214 | friend Expression sym::Log(const Expression &a); // 默认以E为底 |
| @@ -0,0 +1,21 @@ | |||
| 1 | +file(GLOB EXPRESSION_UT_SRCS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/testcase/*.cc") | ||
| 2 | +add_executable(ut_expression ${EXPRESSION_UT_SRCS}) | ||
| 3 | + | ||
| 4 | +target_compile_options(ut_expression PRIVATE | ||
| 5 | + -g -O0 | ||
| 6 | + -Wno-deprecated-declarations | ||
| 7 | + -Wall -Wfloat-equal | ||
| 8 | + -fno-access-control | ||
| 9 | +) | ||
| 10 | + | ||
| 11 | +target_link_libraries(ut_expression PRIVATE | ||
| 12 | + aihac_symbolizer_af | ||
| 13 | + GTest::gtest | ||
| 14 | + GTest::gtest_main | ||
| 15 | + c_sec | ||
| 16 | + unified_dlog | ||
| 17 | + -lgcov | ||
| 18 | + -Wl,--no-as-needed | ||
| 19 | +) | ||
| 20 | + | ||
| 21 | +add_test(NAME ut_expression COMMAND ut_expression) | ||
| @@ -0,0 +1,84 @@ | |||
| 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 should 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 <gtest/gtest.h> | ||
| 12 | +#include "graph/symbolizer/symbolic.h" | ||
| 13 | +#include "graph/symbolizer/symbol_checker.h" | ||
| 14 | +#include "attribute_group/attr_group_shape_env.h" | ||
| 15 | +#include "util/mem_utils.h" | ||
| 16 | +#include "expression/testcase/source_stub.h" | ||
| 17 | + | ||
| 18 | +namespace af { | ||
| 19 | +namespace { | ||
| 20 | +using namespace sym; | ||
| 21 | +using ge::GraphInputShapeSourceStub; | ||
| 22 | + | ||
| 23 | +class UtestExpression : public testing::Test { | ||
| 24 | + protected: | ||
| 25 | + void SetUp() {} | ||
| 26 | + void TearDown() {} | ||
| 27 | +}; | ||
| 28 | + | ||
| 29 | +// SymEngine 规范打印会重排负系数 Add 项:语义相等但字节不等的合法串(如来自 | ||
| 30 | +// 不同打印路径的 guard 序列化串)经重解析+Compare 接受(曾因字节不等被拒返回 | ||
| 31 | +// 空表达式,上层 guard 集合比较器解引用空指针断言崩溃) | ||
| 32 | +TEST_F(UtestExpression, Deserialize_SemanticEqualReorderedStr) { | ||
| 33 | + auto s0 = Symbol("s0"); | ||
| 34 | + auto s1 = Symbol("s1"); | ||
| 35 | + const auto parsed = Expression::Deserialize("-(2 * s1) + s0"); | ||
| 36 | + EXPECT_EQ(parsed.IsValid(), true); | ||
| 37 | + EXPECT_EQ(parsed, s0 - Mul(Symbol(2), s1)); | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +// 打印侧将 Pow(E, x) 输出为 Exp(x)、Pow(x, 1/2) 输出为 Sqrt(x):含这两种形态的 | ||
| 41 | +// 序列化串经 Deserialize 还原为等价的 Pow 表达式,序列化-反序列化往返闭环 | ||
| 42 | +TEST_F(UtestExpression, ExpSqrtSerializeAndDeserialize_RoundTrip) { | ||
| 43 | + auto s0 = Symbol("s0"); | ||
| 44 | + auto s1 = Symbol("s1"); | ||
| 45 | + | ||
| 46 | + const auto exp_expr = sym::Exp(s0); | ||
| 47 | + const std::string exp_str(exp_expr.Serialize().get()); | ||
| 48 | + EXPECT_EQ(exp_str, "Exp(s0)"); | ||
| 49 | + EXPECT_EQ(Expression::Deserialize(exp_str.c_str()), exp_expr); | ||
| 50 | + | ||
| 51 | + const auto sqrt_expr = sym::Pow(s1, sym::Div(Symbol(1), Symbol(2))); | ||
| 52 | + const std::string sqrt_str(sqrt_expr.Serialize().get()); | ||
| 53 | + EXPECT_EQ(sqrt_str, "Sqrt(s1)"); | ||
| 54 | + EXPECT_EQ(Expression::Deserialize(sqrt_str.c_str()), sqrt_expr); | ||
| 55 | + | ||
| 56 | + const auto guard = Eq(exp_expr, sqrt_expr); | ||
| 57 | + const std::string guard_str(guard.Serialize().get()); | ||
| 58 | + EXPECT_EQ(guard_str, "ExpectEq(Exp(s0), Sqrt(s1))"); | ||
| 59 | + EXPECT_EQ(Expression::Deserialize(guard_str.c_str()), guard); | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +class AttributeGroupShapeEnvUt : public testing::Test {}; | ||
| 63 | + | ||
| 64 | +// 间接环:已有 s3 == Min(2, s2) 的替换(变量 s3 的替换根为复合表达式 Min(2, s2))时, | ||
| 65 | +// 再建立 s2 == (s0+s3)*Ceil(s1) 会闭合出 s2 -> 复合 -> s3 -> Min(2, s2) 的替换链环; | ||
| 66 | +// 检测命中后跳过该条替换(s2 不进入替换集合),既有替换保持不变 | ||
| 67 | +TEST_F(AttributeGroupShapeEnvUt, CheckReplacementCycleIndirectTest) { | ||
| 68 | + ShapeEnvAttr shape_env; | ||
| 69 | + SetCurShapeEnvContext(&shape_env); | ||
| 70 | + Symbol s0 = shape_env.CreateSymbol(1, MakeShared<GraphInputShapeSourceStub>(0, 0)); | ||
| 71 | + Symbol s1 = shape_env.CreateSymbol(1, MakeShared<GraphInputShapeSourceStub>(0, 1)); | ||
| 72 | + Symbol s2 = shape_env.CreateSymbol(3, MakeShared<GraphInputShapeSourceStub>(0, 2)); | ||
| 73 | + Symbol s3 = shape_env.CreateSymbol(2, MakeShared<GraphInputShapeSourceStub>(0, 3)); | ||
| 74 | + | ||
| 75 | + EXPECT_EQ(EXPECT_SYMBOL_EQ(s3, sym::Min(Symbol(2), s2)), true); | ||
| 76 | + EXPECT_NE(shape_env.replacements_.find(s3), shape_env.replacements_.end()); | ||
| 77 | + | ||
| 78 | + EXPECT_EQ(EXPECT_SYMBOL_EQ((s0 + s3) * sym::Ceiling(s1), s2), true); | ||
| 79 | + EXPECT_EQ(shape_env.replacements_.find(s2), shape_env.replacements_.end()); | ||
| 80 | + | ||
| 81 | + SetCurShapeEnvContext(nullptr); | ||
| 82 | +} | ||
| 83 | +} // namespace | ||
| 84 | +} // namespace af | ||
| @@ -777,4 +777,24 @@ TEST_F(CommonUtilsTest, Conv2DLegacyOptionalInputAttrParse) { | |||
| 777 | EXPECT_TRUE(attr.has_offset_w); | 777 | EXPECT_TRUE(attr.has_offset_w); |
| 778 | } | 778 | } |
| 779 | } | 779 | } |
| 780 | + | ||
| 781 | +// cube 融合 tile 内轴的运行时占位符 align(CreateFusionCommonResult 中注入)经图属性拷贝 | ||
| 782 | +// (AscGraph::CopyAttrFrom,内部走 SerializeAttr -> DeserializeAttr 序列化中转)后应保留。 | ||
| 783 | +// 占位符名必须为合法标识符:带 "()" 的名字无法被表达式 scanner/parser 解析, | ||
| 784 | +// 克隆图 align 丢失并产生 "Parse expression str ... failed or incomplete" ERROR。 | ||
| 785 | +TEST_F(CommonUtilsTest, TileInnerAxisRuntimeAlignPlaceholderSurvivesClone) { | ||
| 786 | + af::AscGraph graph("tile_inner_runtime_align_graph"); | ||
| 787 | + af::Axis &axis = graph.CreateAxis("z1t", af::Axis::kAxisTypeTileInner, af::Symbol("s1"), {}, af::kIdNone); | ||
| 788 | + axis.align = af::Symbol(kRuntimeAlignFuncName); | ||
| 789 | + | ||
| 790 | + af::AscGraph cloned("tile_inner_runtime_align_graph"); | ||
| 791 | + ASSERT_TRUE(cloned.CopyAttrFrom(graph)); | ||
| 792 | + | ||
| 793 | + const auto cloned_axes = cloned.GetAllAxis(); | ||
| 794 | + ASSERT_EQ(cloned_axes.size(), 1UL); | ||
| 795 | + EXPECT_TRUE(cloned_axes[0]->align.IsValid()) << "tile inner axis align lost after serialize-clone"; | ||
| 796 | + if (cloned_axes[0]->align.IsValid()) { | ||
| 797 | + EXPECT_STREQ(cloned_axes[0]->align.Str().get(), kRuntimeAlignFuncName); | ||
| 798 | + } | ||
| 799 | +} | ||
| 780 | } // namespace ascgen_utils | 800 | } // namespace ascgen_utils |
| @@ -7,6 +7,7 @@ add_subdirectory(py_module) | |||
| 7 | add_subdirectory(e2e) | 7 | add_subdirectory(e2e) |
| 8 | add_subdirectory(common) | 8 | add_subdirectory(common) |
| 9 | add_subdirectory(ascir) | 9 | add_subdirectory(ascir) |
| 10 | +add_subdirectory(${CODE_ROOT_DIR}/tests/graph_metadef/ut/expression ${CMAKE_CURRENT_BINARY_DIR}/graph_metadef_expression) | ||
| 10 | find_program(PYTEST_EXECUTABLE pytest) | 11 | find_program(PYTEST_EXECUTABLE pytest) |
| 11 | 12 | ||
| 12 | if(PYTEST_EXECUTABLE) | 13 | if(PYTEST_EXECUTABLE) |
| @@ -548,7 +548,6 @@ REG_ASC_IR(Store).Impl(v2_soc_versions, | |||
| 548 | {{"T", TensorType{DT_INT8, DT_UINT8, DT_INT16, DT_UINT16, DT_INT32, DT_UINT32, DT_FLOAT16, | 548 | {{"T", TensorType{DT_INT8, DT_UINT8, DT_INT16, DT_UINT16, DT_INT32, DT_UINT32, DT_FLOAT16, |
| 549 | DT_FLOAT, DT_INT64, DT_BF16, DT_UINT64, DT_BOOL}}}}); | 549 | DT_FLOAT, DT_INT64, DT_BF16, DT_UINT64, DT_BOOL}}}}); |
| 550 | 550 | ||
| 551 | -// todo: Broadcast DT_INT64 后面根据需要放开 | ||
| 552 | REG_ASC_IR(Broadcast).Impl(v2_soc_versions, | 551 | REG_ASC_IR(Broadcast).Impl(v2_soc_versions, |
| 553 | {af::ascir::AscIrImplCreator<af::ascir::BroadcastAscIrAttImplV2>(), | 552 | {af::ascir::AscIrImplCreator<af::ascir::BroadcastAscIrAttImplV2>(), |
| 554 | af::ascir::AscIrImplCreator<af::ascir::BroadcastAscIrCodegenImplV2>(), | 553 | af::ascir::AscIrImplCreator<af::ascir::BroadcastAscIrCodegenImplV2>(), |