已合并
fix: 符号表达式反序列化健壮性修复、tile对齐占位符合法化与Broadcast int64放开 #2164
fix: 符号表达式反序列化健壮性修复、tile对齐占位符合法化与Broadcast int64放开 #2164
已合并
xiebangrui2025创建于 25 天前
共 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 后面根据需要放开
147REG_ASC_IR(Broadcast)146REG_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 
156REG_ASC_IR(RemovePad)155REG_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}
170af::Expression GetTensorSize(const af::AscTensor &tensor);170af::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+}
171af::Expression CalculateOneWorkspaceSize(const af::AscNodePtr &workspace_nodes);183af::Expression CalculateOneWorkspaceSize(const af::AscNodePtr &workspace_nodes);
172af::Expression CalculateWorkspaceSize(const std::vector<af::AscNodePtr> &workspace_nodes);184af::Expression CalculateWorkspaceSize(const std::vector<af::AscNodePtr> &workspace_nodes);
173af::Expression CalcExtraTmpBufForAscGraph(const ascir::ImplGraph &graph);185af::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 af525} // 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"
13namespace af {14namespace af {
@@ -28,6 +29,12 @@ graphStatus ExprParser::Eat(TokenType type) {
28}29}
29 30 
30ExpressionImplPtr ExprParser::ParserFactor() {31ExpressionImplPtr 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 
79ExpressionImplPtr ExprParser::ParserAddSubtract() {83ExpressionImplPtr 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+ 
276ExpressionImplPtr ExprParser::ParserNumber() {299ExpressionImplPtr 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 
223ExpressionImplPtr ExpressionImpl::Replace(const std::map<ExpressionImpl *, ExpressionImpl *> &replace_vars) const {251ExpressionImplPtr 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+ 
684ExpressionImplPtr Mod(const ExpressionImplPtr &a, const ExpressionImplPtr &b) {719ExpressionImplPtr 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);
164ExpressionImplPtr Min(const ExpressionImplPtr &a, const ExpressionImplPtr &b);165ExpressionImplPtr Min(const ExpressionImplPtr &a, const ExpressionImplPtr &b);
165ExpressionImplPtr Abs(const ExpressionImplPtr &a);166ExpressionImplPtr Abs(const ExpressionImplPtr &a);
166ExpressionImplPtr Pow(const ExpressionImplPtr &a, const ExpressionImplPtr &b);167ExpressionImplPtr Pow(const ExpressionImplPtr &a, const ExpressionImplPtr &b);
168+ExpressionImplPtr Exp(const ExpressionImplPtr &a);
167ExpressionImplPtr Mod(const ExpressionImplPtr &a, const ExpressionImplPtr &b);169ExpressionImplPtr Mod(const ExpressionImplPtr &a, const ExpressionImplPtr &b);
168ExpressionImplPtr Log(const ExpressionImplPtr &a); // 默认以E为底170ExpressionImplPtr Log(const ExpressionImplPtr &a); // 默认以E为底
169ExpressionImplPtr Log(const ExpressionImplPtr &arg, const ExpressionImplPtr &base);171ExpressionImplPtr 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, // floor38 kFloor, // floor
39 kAbs, // abs39 kAbs, // abs
40 kRational, // rational40 kRational, // rational
41+ kExp, // exp: Pow(E, x) 的打印形态
42+ kSqrt, // sqrt: Pow(x, 1/2) 的打印形态
41 kEq, // EXPECT_EQ43 kEq, // EXPECT_EQ
42 kNe, // EXPECT_NE44 kNe, // EXPECT_NE
43 kLt, // EXPECT_LT45 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+ 
56Expression Mod(const Expression &base, const Expression &exp) {60Expression 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);
22Expression Max(const Expression &a, const Expression &b);22Expression Max(const Expression &a, const Expression &b);
23Expression Min(const Expression &a, const Expression &b);23Expression Min(const Expression &a, const Expression &b);
24Expression Pow(const Expression &base, const Expression &exp);24Expression Pow(const Expression &base, const Expression &exp);
25+Expression Exp(const Expression &a);
25Expression Mod(const Expression &base, const Expression &exp);26Expression Mod(const Expression &base, const Expression &exp);
26Expression Abs(const Expression &a);27Expression Abs(const Expression &a);
27Expression Log(const Expression &a); // 默认以E为底28Expression 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_utils800} // namespace ascgen_utils
@@ -7,6 +7,7 @@ add_subdirectory(py_module)
7add_subdirectory(e2e)7add_subdirectory(e2e)
8add_subdirectory(common)8add_subdirectory(common)
9add_subdirectory(ascir)9add_subdirectory(ascir)
10+add_subdirectory(${CODE_ROOT_DIR}/tests/graph_metadef/ut/expression ${CMAKE_CURRENT_BINARY_DIR}/graph_metadef_expression)
10find_program(PYTEST_EXECUTABLE pytest)11find_program(PYTEST_EXECUTABLE pytest)
11 12 
12if(PYTEST_EXECUTABLE)13if(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 后面根据需要放开
552REG_ASC_IR(Broadcast).Impl(v2_soc_versions,551REG_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>(),