Pull Request已成功合入, 合并人@ascend-robot
(感谢 lvjiangdong 的贡献)变更摘要
此 PR 为 NPU Inductor 的图优化 Pass 新增了 4 个单元测试文件,覆盖 ascend_graph_pass 模块中 fold_four_op_pass(add/div 折叠)、fold_cat 和 fold_clone 三个优化 Pass。所有测试用例通过 fx.symbolic_trace 构建计算图并执行对应的折叠 Pass,验证了常量折叠的正确性、不同条件下不应折叠的边界情况、多下游用户替换的安全性以及无目标节点时图不被修改的容错行为。
主要改动
- 新增
test_fold_add_pass.py: 为fold_four_op_pass中的 add 零值折叠添加 9 个测试用例,覆盖0 + x、x + 0.0、x + 0 (int)、链式 +0 折叠、非零常量不折叠、多下游用户替换以及无 add 节点时图不变等场景 - 新增
test_fold_div_pass.py: 为fold_four_op_pass中的 div 除一折叠添加 8 个测试用例,覆盖x / 1.0、x / 1 (int)、1.0 / x、链式 /1 折叠、非 1 除数不折叠、多用户替换以及无 div 节点时图不变等场景 - 新增
test_fold_cat_pass.py: 为fold_catPass 添加 8 个测试用例,覆盖多层嵌套同轴 cat 折叠为单层、不同轴不折叠、多用户输入不折叠、负轴与正轴一致性、混合 cat/非 cat 输入以及单输入 cat 不折叠等场景 - 新增
test_fold_clone_pass.py: 为fold_clonePass 添加 9 个测试用例,覆盖相同 memory_format 折叠、不同 memory_format 不折叠、输出路径 clone 不折叠、部分折叠、多下游用户替换、无 clone 节点图不变以及缺少tensor_meta时的安全检查等场景


代码审查
我已经完成了所有四个文件的审查。现在做最终汇总。
审查总结
逐文件审查结果:
| 文件 | 结论 |
|---|---|
test/_inductor/test_fold_add_pass.py |
发现 2 个问题(P1 × 1, P3 × 1) |
test/_inductor/test_fold_cat_pass.py |
发现 1 个问题(P3 × 1) |
test/_inductor/test_fold_clone_pass.py |
无问题 |
test/_inductor/test_fold_div_pass.py |
发现 1 个问题(P2 × 1) |
按优先级统计:P0: 0 / P1: 1 / P2: 1 / P3: 2
整体风险判断:中等。最严重的问题是 test_add_zero_left_fold 的断言逻辑错误(P1)——当 fold_four_op_pass 正确工作时该测试会失败,建议在上线前修复。另外 test_rdiv_one_left_fold 声称验证折叠但实际未做断言(P2),会带来虚假的覆盖信心。两个 P3 问题为调试代码残留和文档描述不准确,影响较小。
| 类型 | 数量 |
|---|---|
| 🔴 阻塞 | 1 |
| 🟡 建议 | 3 |
⛔ 需要修改


Thanks for your pull-request.
The full list of commands accepted by me can be found at here。
You can get sig-info at here
PR Approval Progress
✅ Congratulations! All modules have met the lgtm and approve requirements.
Module Approval Details
| module | lgtm status | approve status |
|---|---|---|
| test | ✅ crazyDannyBoy, TonyYA (2/2) | ✅ crazyDannyBoy (1/1) |
💡 Tip:
- Committer can comment
/approveor/lgtm- Commenting
/approveimplies both code review (lgtm) and intent to merge (approve)


compile


当前仓库存在以下 保护分支 :
| Protected Branch | Version | Release |
|---|---|---|
| master | ||
| v2.12.0 | ||
| v2.11.0 | ||
| v2.7.1 | ||
| v2.7.1-26.1.0 | ||
| v2.12.0-26.1.0 | ||
| v2.9.0 | ||
| v2.11.0-26.1.0 | ||
| v2.9.0-26.1.0 | ||
| v2.10.0-26.1.0 | ||
| v2.10.0 | ||
| ci-test |
评论 /sync <branch1> <branch2> ... 可将当前 PR 修改同步到其它分支(创建同步 PR):
a) 如果当前 PR 是 Open 状态,同步操作将延迟到 PR 被合并时执行
b) 如果当前 PR 已经 Merged,将立即执行同步操作
注意:
- /sync 命令可以指定同步到多个分支,仅最后一个 /sync 命令生效
- 如果创建的同步 PR 不正确,可通过向同步 PR 的源分支提交轻量级 PR 完善,或使用 /close 命令关闭


| 阶段 | 任务名 | 状态 | 详情 |
|---|---|---|---|
| 编译构建 | Build_X86 | 🕚 | >>> |
| Build_ARM | 🕚 | >>> | |
| Build_LibTorch_x86 | 🕚 | >>> | |
| Build_LibTorch_ARM | 🕚 | >>> | |
| Build_X86_torchair | 🕚 | >>> | |
| Build_ARM_torchair | 🕚 | >>> | |
| patch_test | 🕚 | >>> | |
| 恶意代码检查 | Antipoison | 🟨 | >>> |
| 编码安全与规范检查 | CodeCheck | 🟨 | >>> |
| check_error | 🟨 | >>> | |
| CodeCheck_lintrunner | 🟨 | >>> | |
| 开源片段检查 | SCA | 🟨 | >>> |
| 开发者测试 | UT_ARM_A3_Part_01 | 🕚 | >>> |
| UT_ARM_A3_Part_02 | 🕚 | >>> | |
| UT_ARM_A2_Part_01 | 🕚 | >>> | |
| UT_ARM_A2_Part_02 | 🕚 | >>> | |
| UT_ARM_A2_Part_03 | 🕚 | >>> | |
| UT_inductor_Part_01 | 🕚 | >>> | |
| UT_inductor_Part_02 | 🕚 | >>> | |
| UT_inductor_Part_03 | 🕚 | >>> | |
| UT_inductor_Part_04 | 🕚 | >>> | |
| UT_DIST_ARM_Part_01 | 🕚 | >>> | |
| UT_DIST_ARM_Part_02 | 🕚 | >>> | |
| UT_DIST_ARM_Part_03 | 🕚 | >>> | |
| UT_DIST_ARM_Part_04 | 🕚 | >>> | |
| UT_ARM_A2_Select_Part_01 | 🕚 | >>> | |
| UT_ARM_A2_Select_Part_02 | 🕚 | >>> | |
| 流水线 | PR-pipeline_pytorch | 🟨 | >>> |
- compile、compile_inductor、compile_torchair : 运行流水线
- retry : 重试流水线所有失败子任务
- retry <任务名> : 仅重试指定失败子任务
- stop : 停止流水线


ascend docs pipeline is running...


ascend docs pipeline is running...


compile


| 阶段 | 任务名 | 状态 | 详情 |
|---|---|---|---|
| 编译构建 | Build_X86 | 🕚 | >>> |
| Build_ARM | 🕚 | >>> | |
| Build_LibTorch_x86 | 🕚 | >>> | |
| Build_LibTorch_ARM | 🕚 | >>> | |
| Build_X86_torchair | 🕚 | >>> | |
| Build_ARM_torchair | 🕚 | >>> | |
| patch_test | 🕚 | >>> | |
| 恶意代码检查 | Antipoison | 🟨 | >>> |
| 编码安全与规范检查 | CodeCheck | 🟨 | >>> |
| check_error | ✅ | >>> | |
| CodeCheck_lintrunner | ✅ | >>> | |
| 开源片段检查 | SCA | ✅ | >>> |
| 开发者测试 | UT_ARM_A3_Part_01 | 🕚 | >>> |
| UT_ARM_A3_Part_02 | 🕚 | >>> | |
| UT_ARM_A2_Part_01 | 🕚 | >>> | |
| UT_ARM_A2_Part_02 | 🕚 | >>> | |
| UT_ARM_A2_Part_03 | 🕚 | >>> | |
| UT_inductor_Part_01 | 🕚 | >>> | |
| UT_inductor_Part_02 | 🕚 | >>> | |
| UT_inductor_Part_03 | 🕚 | >>> | |
| UT_inductor_Part_04 | 🕚 | >>> | |
| UT_DIST_ARM_Part_01 | 🕚 | >>> | |
| UT_DIST_ARM_Part_02 | 🕚 | >>> | |
| UT_DIST_ARM_Part_03 | 🕚 | >>> | |
| UT_DIST_ARM_Part_04 | 🕚 | >>> | |
| UT_ARM_A2_Select_Part_01 | 🕚 | >>> | |
| UT_ARM_A2_Select_Part_02 | 🕚 | >>> | |
| 流水线 | PR-pipeline_pytorch | 🟨 | >>> |
- compile、compile_inductor、compile_torchair : 运行流水线
- retry : 重试流水线所有失败子任务
- retry <任务名> : 仅重试指定失败子任务
- stop : 停止流水线


ascend docs pipeline is running...


✅ 跳过 docs ci 检查,没有需要检查的文档文件


✅ 跳过 docs ci 检查,没有需要检查的文档文件


✅ 跳过 docs ci 检查,没有需要检查的文档文件


🟡 Medium Priority
测试名和 docstring 都声称 "1.0 / x should be folded",但测试体中仅通过 torch.testing.assert_close(m(t), gm(t)) 验证正确性,未对图中是否仍有 div 节点做任何断言。
查看 fold_four_op_pass 实现:div 分支仅处理右操作数为 1 的情况(try_match(inp0, inp1, is_one_like, "right")),不处理左操作数为 1 的 1.0 / x 场景(无 rdiv_ops 分支)。因此该 pass 实际上不会折叠 torch.div(1.0, x)。
后果:测试会通过(pass 未破坏正确性),但并未验证其声称的折叠行为,造成虚假的测试覆盖信心。
修复方向:要么在 pass 中补充 rdiv 折叠逻辑并加入对应的节点断言;要么将测试改名/改 docstring 为验证"不对 1.0/x 做错误折叠"。
建议:要么为 fold_four_op_pass 补充 rdiv 折叠能力并在测试中加入节点计数断言;要么将测试名和 docstring 改为表示验证"1.0/x 不被错误折叠"。


🟠 High Priority
测试 test_add_zero_left_fold 在第 73-77 行断言图中不应存在任何 add 节点:self.assertFalse(any(n.target in (torch.add, torch.ops.aten.add.Tensor, torch.ops.aten.add.Scalar) for n in gm.graph.nodes))。
但模型定义(第 59-62 行)中:y = torch.ops.aten.add.Tensor(0, x) 折叠后 y→x,而 torch.relu(y + 1.0) 中的 y + 1.0 仍会生成一个新的 add 节点(add.Tensor(x, 1.0) 或 add.Scalar(x, 1.0),因为 1.0 ≠ 0,不会被 fold_four_op_pass 折叠)。因此图中必然残留一个 add 节点,assertFalse 将失败。
触发条件:fold_four_op_pass 正确折叠 0 + x 后,x + 1.0 的 add 节点仍然存在。
修复方向:将断言改为验证折叠后的图中仅剩余 x + 1.0 这一个 add 节点(即 len(add_nodes) == 1),或修改模型使 y + 1.0 不产生 add 节点(例如改为 torch.mul(y, 2.0))。
建议:将断言从 assertFalse(零 add 节点)改为 assertEqual(len(add_nodes), 1),只验证 0 + x 被折叠,而 x + 1.0 正常保留。


🟡 Medium Priority
变更行:第 79-96 行(test_clone_different_memory_format_no_fold)。
证据链:
- 测试模型的
forward直接把cloned作为返回值(第 86 行return cloned),即 clone 节点是 output 节点的直接输入。 fold_clone在构建候选列表时(ascend_graph_pass.py第 370 行),要求get_node_unique_id(node) not in output_storages。该 clone 在output_storages中,因此永远不会成为候选节点。- 实际的 memory_format 比较(
ascend_graph_pass.py第 377-378 行)对此 clone 节点从未被执行。 - 测试断言
len(clone_nodes) == 1通过,但原因不是"memory_format 不同",而是"clone 在输出路径上被保护"。
失败模式:如果后续有人误修改了 fold_clone 的 memory_format 比较逻辑(例如错误地把 preserve_format 视为与 contiguous_format 等同),该测试不会捕获到回归——因为 clone 从未进入 memory_format 检查分支。这造成了对 memory_format 守卫逻辑的虚假覆盖率。
建议:将模型改为 clone 不直接作为输出,让 clone 进入 fold_clone 的候选列表,真正触发 memory_format 比较路径。例如:return cloned + 1.0 代替 return cloned。


🟡 Medium Priority
变更行:第 176-194 行(test_clone_without_tensor_meta_no_fold)。
证据链:
- 测试模型的
forward直接把cloned作为返回值(第 182 行return cloned),即 clone 节点是 output 节点的直接输入。 fold_clone在构建候选列表时(ascend_graph_pass.py第 370 行),要求get_node_unique_id(node) not in output_storages。该 clone 在output_storages中,因此永远不会成为候选节点。- 对候选节点的
tensor_meta检查(ascend_graph_pass.py第 374 行)对此 clone 节点从未被执行。 - 测试在第 187-189 行删除
tensor_meta,然后在第 191 行调用fold_clone,断言len(clone_nodes) == 1。断言通过,但原因不是"缺少 tensor_meta 导致跳过折叠",而是"clone 在输出路径上被保护"——与第 187-189 行的 tensor_meta 删除操作完全无关。
失败模式:如果后续有人误修改了 fold_clone 的 tensor_meta 守卫(例如错误地删除了第 374 行的 continue),该测试不会捕获到回归——因为 clone 从未进入 tensor_meta 检查分支。这造成了对 tensor_meta 守卫逻辑的虚假覆盖率。此外,第 187-189 行的 "故意移除 tensor_meta" 注释和实际代码暗示作者意图测试 tensor_meta 守卫,但测试结构无法达到此目的。
建议:将模型改为 clone 不直接作为输出(如 return cloned + 1.0),并先用 ShapeProp 添加 tensor_meta 再删除,确保 clone 进入候选列表后真正触发 tensor_meta 缺失的守卫逻辑。
|
194 | + @parametrize('shape', [(4, 4)]) |
|
195 | + def test_clone_without_tensor_meta_no_fold(self, shape): |
|
196 | + """输入没有 tensor_meta → 不折叠(安全检查)""" |
|
197 | + class M(torch.nn.Module): |
|
198 | + def forward(self, x): |
|
199 | + cloned = torch.ops.aten.clone.default(x) |
|
200 | + return cloned + 1.0 # clone 不作为直接输出,使其进入候选列表 |
|
201 | + |
|
202 | + x = self._generate_tensor(shape, 'float32') |
|
203 | + gm = fx.symbolic_trace(M()) |
|
204 | + ShapeProp(gm).propagate(x) # 先添加 tensor_meta |
|
205 | + # 故意移除 tensor_meta |
|
206 | + for node in gm.graph.nodes: |
|
207 | + if "tensor_meta" in node.meta: |
|
208 | + del node.meta["tensor_meta"] |
|
209 | + |
|
210 | + fold_clone(gm.graph) |
|
211 | + |
|
212 | + clone_nodes = [n for n in gm.graph.nodes if n.target == torch.ops.aten.clone.default] |
| 194
213 | self.assertEqual(len(clone_nodes), 1, "缺少 tensor_meta 不应折叠") |


| 阶段 | 任务名 | 状态 | 详情 |
|---|---|---|---|
| 编译构建 | Build_X86 | ✅ | >>> |
| Build_ARM | ✅ | >>> | |
| Build_LibTorch_x86 | ✅ | >>> | |
| Build_LibTorch_ARM | ✅ | >>> | |
| Build_X86_torchair | 🛑 | >>> | |
| Build_ARM_torchair | 🛑 | >>> | |
| patch_test | 🛑 | >>> | |
| 恶意代码检查 | Antipoison | ✅ | >>> |
| 编码安全与规范检查 | CodeCheck | ✅ | >>> |
| check_error | ✅ | >>> | |
| CodeCheck_lintrunner | ✅ | >>> | |
| 开源片段检查 | SCA | ✅ | >>> |
| 开发者测试 | UT_ARM_A3_Part_01 | 🛑 | >>> |
| UT_ARM_A3_Part_02 | 🛑 | >>> | |
| UT_ARM_A2_Part_01 | ✅ | >>> | |
| UT_ARM_A2_Part_02 | ✅ | >>> | |
| UT_ARM_A2_Part_03 | ✅ | >>> | |
| UT_inductor_Part_01 | ✅ | >>> | |
| UT_inductor_Part_02 | ✅ | >>> | |
| UT_inductor_Part_03 | ✅ | >>> | |
| UT_inductor_Part_04 | ✅ | >>> | |
| UT_DIST_ARM_Part_01 | 🛑 | >>> | |
| UT_DIST_ARM_Part_02 | 🛑 | >>> | |
| UT_DIST_ARM_Part_03 | 🛑 | >>> | |
| UT_DIST_ARM_Part_04 | 🛑 | >>> | |
| UT_ARM_A2_Select_Part_01 | ✅ | >>> | |
| UT_ARM_A2_Select_Part_02 | ✅ | >>> | |
| 流水线 | PR-pipeline_pytorch | ✅ | >>> |
- compile、compile_inductor、compile_torchair : 运行流水线
- retry : 重试流水线所有失败子任务
- retry <任务名> : 仅重试指定失败子任务
- stop : 停止流水线


/lgtm


/lgtm
/approve


Pull Request 已合并或已关闭。
If you want to solve this problem, you can click here to do it in the FAQs.




#3446
【合入来源】
【修改方案】
【资料变更】
【接口变更】
【功能验证】
【CheckList】