Pull Request已成功合入, 合并人@ascend-robot
(感谢 黄桂军 的贡献)变更摘要
本 PR 将 v2.7.1_import 分支上的懒加载性能优化同步到 v2.9.0,核心目标是延迟 torch._dynamo、torch._inductor 和 sympy 的加载时机。此前 import torch_npu 会立即触发这些重依赖的导入,现在改为在首次调用 torch.compile 时通过 _lazy_dynamo_setup 一次性完成所有 Dynamo/Inductor 注册,从而显著缩短 torch_npu 的导入耗时。同时将 RNG prims 补丁从 torch_npu.utils._inductor 中抽离到新文件 _rng_prims_patch.py,并消除多处模块顶层的 eager import,改用 TYPE_CHECKING 或就地导入来保持兼容性。
主要改动
-
延迟 Dynamo/Inductor 注册到首次
torch.compile:在torch_npu/utils/_dynamo.py中新增_lazy_dynamo_setup()和install_torch_compile_trigger(),将原本在import torch_npu时执行的 dynamo backends 注册、trace rules 注入、inductor 配置初始化和 graph tree 补丁全部推迟到首次编译调用;同时移除registry_manager.py中的_register_dynamo()整函数。 -
RNG prims 补丁独立为
_rng_prims_patch.py:将torch_npu/utils/_inductor.py中所有torch._prims.rng_prims相关补丁(_max_unpoolnd_patch、philox_rand_offset、register_philox_rand、run_and_save_rng_state、run_with_rng_state、rng_prims_device)迁移到新文件,封装为apply_rng_prims_patches(),由_register_inductor()在导入时调用,避免加载torch._inductor。 -
消除多处模块级 eager import 对 dynamo/inductor 的依赖:
torch_npu/dynamo/__init__.py将from torch._dynamo import register_backend移入_register_npu_backend()函数体;torch_npu/npu/deterministic.py用本地_forbid_in_graph替代from torch._dynamo.decorators import forbid_in_graph;torch_npu/npu/npugraph_ex/__init__.py使用TYPE_CHECKING包裹SearchFn等类型,并将fwd_only的导入后置到函数内按需 fallback。 -
补丁子模块的显式导入适配懒加载:
torch_npu/_inductor/utils.py在 patchhas_triton前显式from torch._inductor import compile_fx, scheduler;torch_npu/contrib/transfer_to_npu.py在_init()中显式导入torch._dynamo.trace_rules、torch._inductor.compile_fx等子模块,确保延迟加载后补丁仍能正确应用。 -
torch_npu.utils._inductor精简为占位模块:原文件 247 行缩减为 12 行的注释说明,RNG 补丁迁移至_rng_prims_patch.py,NPU device op 覆盖注册交由torch_npu._inductor包自身的 codegen 路径在首次编译时完成。


代码审查
审查总结
本次审查覆盖了全部 12 个变更文件。以下是按优先级统计的发现问题:
| 优先级 | 数量 | 说明 |
|---|---|---|
| P0 | 0 | — |
| P1 | 0 | — |
| P2 | 1 | NPUDeviceOpOverrides 被移除导致测试 import 失败 |
| P3 | 1 | _lazy_dynamo_setup 可能在预期外路径被触发(低置信度,可选优化) |
逐文件审查结论
| 文件 | 结论 |
|---|---|
test/_inductor/test_current_device.py |
无问题 |
test/torch_npu_schema.json |
无问题 |
torch_npu/_inductor/utils.py |
无问题 |
torch_npu/_init/patches/dynamo_patches.py |
无问题 |
torch_npu/_init/registry/registry_manager.py |
无问题 |
torch_npu/contrib/transfer_to_npu.py |
无问题 |
torch_npu/dynamo/__init__.py |
无问题 |
torch_npu/npu/deterministic.py |
无问题 |
torch_npu/npu/npugraph_ex/__init__.py |
无问题 |
torch_npu/utils/_dynamo.py |
1 个 P3(可选) |
torch_npu/utils/_inductor.py |
1 个 P2(测试 breakage) |
torch_npu/utils/_rng_prims_patch.py |
无问题 |
整体风险评估:低风险。 懒加载重构逻辑正确,除测试兼容性问题外,生产代码行为一致。建议在合入前修复 test/utils/test_inductor.py 的 import 路径。
| 类型 | 数量 |
|---|---|
| 🔴 阻塞 | 1 |
| 🟡 建议 | 2 |
⛔ 需要修改


/check-issue


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




变更说明
将 v2.7.1_import 上的懒加载性能优化同步到 v2.9.0:
import torch_npu时不再提前加载torch._dynamo、torch._inductor和 sympy。aten.erfcdecomposition 重注册修复。v2.7.1_import 与 v2.9.0_import 改动差异总报告
1. 报告目的
方便 committer 进行代码检视。
2. “相同”与“差异”的判定规则
对每个文件分别提取两边 patch 的新增行和删除行,保留原始顺序和全部字符,然后逐字节比较:
“改动代码相同”不等于“最终文件相同”。两个版本的基线代码本身可能不同;报告会把“同步 hunk 相同、最终文件因版本基线不同”单独说明。
本报告固定比较以下 SHA,避免维护分支继续前进后改变统计口径:
0c9b783ca5635da8000e571fa60370f266a4e978..371545dc2e4a09ca2024ec220c3eed1b9a50ba48dd936ee52a7202c144bd1322c04041955f0d2205..0bf5bdadcfa38f20608f6b5a6434dbe97ccb2c1b3. 总体统计
两边 patch 的文件并集共 34 个文件;source 修改 33 个文件,target 修改 29 个文件:
aten.erfcdecomposition 重复注册。3.1 改动代码完全相同的 18 个文件
setup.py+5/-0+5/-0test/_inductor/test_current_device.py+2/-1+2/-1test/_inductor/test_mlir_enable.py+4/-1+4/-1test/npu/test_stream.py+5/-0+5/-0test/utils/test_inductor.py+0/-33+0/-33torch_npu/_init/patches/api_patches.py+0/-2+0/-2torch_npu/_init/patches/distributed_patches.py+61/-10+61/-10torch_npu/_init/patches/dynamo_patches.py+0/-7+0/-7torch_npu/contrib/transfer_to_npu.py+13/-1+13/-1torch_npu/distributed/fsdp/__init__.py+2/-0+2/-0torch_npu/distributed/tensor/__init__.py+14/-1+14/-1torch_npu/dynamo/__init__.py+67/-5+67/-5torch_npu/npu/__init__.py+13/-2+13/-2torch_npu/npu/_graph_tree.py+1/-12+1/-12torch_npu/npu/_graph_tree_state.py+11/-0+11/-0torch_npu/npu/deterministic.py+11/-3+11/-3torch_npu/utils/_graph_tree.py+20/-9+20/-9torch_npu/utils/_rng_prims_patch.py+255/-0+255/-03.2 双方都修改但代码不同的 10 个文件
test/dynamo/test_compile_trigger.py+1372/-0+1438/-0test/test_torch_npu_init.py+89/-14+79/-15torch_npu/_inductor/kernel/bmm.py+5/-4+3/-2bmm_configs/bmm_template本地别名torch_npu/_inductor/utils.py+2/-1+2/-2has_triton;target 复用_dynamo.py的 v2.9 实现,只同步 Inductor 已绑定入口torch_npu/_init/registry/registry_manager.py+10/-24+8/-31torch_npu/distributed/__init__.py+19/-1+18/-0torch_npu/npu/npugraph_ex/__init__.py+2/-2+1/-1torch_npu/utils/__init__.py+1/-1+1/-0torch_npu/utils/_dynamo.py+447/-133+428/-129torch_npu/utils/_inductor.py+8/-248+8/-2473.3 仅 v2.7.1 修改的 5 个文件
torch_npu/distributed/tensor/_attention.py+2/-2register_shardingtorch_npu/distributed/tensor/_dtensor_patch.py+63/-0torch_npu/distributed/tensor/_math_ops.py+2/-1torch_npu/distributed/tensor/_matrix_ops.py+1/-1torch_npu/distributed/tensor/_moe_ops.py+3/-23.4 仅 v2.9.0 修改的 1 个文件
torch_npu/_inductor/decomposition.py+1/-0aten.erfc加入重注册前的 overload 清理集合,避免 backend 重载时重复注册4. 最重要的版本差异
4.1 DTensor:v2.9 不同步 source 的 5 个文件改动是有意设计
v2.7.1 为避免导入 DTensor experimental API 间接加载 compiler 模块,在已有
_dtensor_patch.py中增加轻量register_shardinghelper,并让 4 个算子文件改用该 helper。它依赖 2.7.1 内部结构,例如TupleStrategy.childs和_is_inplace_op。v2.9 保留算子文件对 PyTorch 自带
register_sharding的直接使用。目标基线同时存在以下两种 PyTorch 2.9 兼容路径:# _attention.py / _matrix_ops.py / _moe_ops.py from torch.distributed._tensor.experimental import register_sharding # _math_ops.py from torch.distributed.tensor.experimental import register_sharding在 PyTorch 2.9 当前实现中,这些导入路径不会加载 Dynamo/Inductor,因此无需复制 2.7.1 的本地 decorator。target 最终没有新增
_sharding_registration.py,但仍保留并在 strategy 注册前加载目标基线已有的_dtensor_patch.py;该文件负责 v2.9 的 kwargs strategy 和expand_to_full_mesh_op_strategy兼容,不是 2.7.1register_shardinghelper 的副本。因此,target 未引入
_sharding_registration.py对应的上游源码副本,也没有这类新增副本的 Meta/BSD License 声明要求;不能表述为 target 不存在_dtensor_patch.py。4.2
has_triton:保留 v2.9 实现,并按初始化层级复用v2.7.1 的
has_triton是torch_npu/_inductor/utils.py::patch_has_triton()内部的缓存函数。v2.9 最终没有复制该函数体,也没有把自身实现移动到 Inductor 模块,而是把 v2.9 原有语义保留在torch_npu/utils/_dynamo.py:@functools.lru_cache(None) def has_triton() -> bool: from torch.utils._triton import has_triton_package if not has_triton_package(): return False from torch._dynamo.device_interface import get_interface_for_device ... _dynamo_register_interface_for_device() for device, extra_check in triton_supported_devices.items(): device_interface = get_interface_for_device(device) if device_interface.is_available() and extra_check(device_interface): return True return False该实现逐项检查 CUDA、XPU、CPU、NPU;CPU 还要求 Triton CPU backend 存在,NPU 不查询 CUDA device properties。
import torch_npu时仅将这个轻量 callable 提前安装到torch.utils._triton.has_triton,函数体内部的 Dynamo/Triton backend 导入仍延迟到首次调用,因此不会因为安装 patch 而提前加载 Dynamo/Inductor。首次初始化 Inductor 后,
torch_npu/_inductor/utils.py复用同一个函数对象,更新 Inductor 已按值绑定的入口:def patch_has_triton(): from torch._inductor import compile_fx from torch_npu.utils._dynamo import has_triton torch._inductor.scheduler.has_triton = has_triton compile_fx.has_triton = has_triton这种归属确保全局只有一份 v2.9 设备判定实现,同时兼顾 import 阶段的早期 consumer 和 Inductor 加载后的已绑定 consumer。
4.3 Inductor wrapper:v2.9 保留参数校验和 shape 延迟顺序
PyTorch 2.9 的
_TorchCompileInductorWrapper.__init__会在构造期间调用self.apply_options()。target 因此不能复制 2.7.1 wrapper,而是保留以下顺序:target 还支持 PyTorch 2.9 原生的连字符 option alias:
attr_name = key.replace("-", "_")因此
npu-backend/npu_backend、enable-shape-handling/enable_shape_handling行为一致。任何校验失败都发生在_setup_inductor_for_compile()前,不会先初始化 NPU Inductor。4.4 backend lookup:两边最新实现均覆盖已绑定入口
v2.9 的
torch._dynamo.eval_frame会按值绑定lookup_backend。target 除更新 registry 外,还按 identity 更新已绑定入口:registry.lookup_backend = lookup_backend if torch_dynamo.lookup_backend is original_lookup_backend: torch_dynamo.lookup_backend = lookup_backend if eval_frame.lookup_backend is original_lookup_backend: eval_frame.lookup_backend = lookup_backend注册完成后通过
_COMPILER_FNS无锁返回;只有指定 NPU backend 尚未出现在_COMPILER_FNS时才进入 RLock,避免正常 compile 热路径锁竞争。v2.7.1_import 最新提交也已同步该已绑定入口和无锁热路径语义,因此这不再是 target 独有能力。4.5 fork:两边均在 child 中重建 compiler 状态
两边最新实现都会在 child 中重建每个
run_once的 Condition,并清除可能指向父进程线程的 running owner;已完成的has_run状态保留,fork 时尚未完成的 lazy setup 可由 child 重试。两边也都维护 backend lookup 与 backend scope 两把 RLock。当前实现不在 fork 前获取并等待这些锁,而是在 child hook 中直接丢弃继承的锁对象:
def _reset_compiler_locks_after_fork(): global _INDUCTOR_NPU_BACKEND_LOCK, _DYNAMO_NPU_BACKEND_LOOKUP_LOCK _INDUCTOR_NPU_BACKEND_LOCK = threading.RLock() _DYNAMO_NPU_BACKEND_LOOKUP_LOCK = threading.RLock() os.register_at_fork(after_in_child=_reset_compiler_locks_after_fork)父进程继续使用原锁,child 使用新锁,避免 child 继承一个由不存在的父线程永久持有的 RLock。该方案不保证 fork 前临时
TORCHINDUCTOR_NPU_BACKEND已恢复;环境变量按 fork 瞬间复制,child backend scope 保存并恢复的是 fork 瞬间继承的值,不会自动推断父线程进入 scope 前的值。target 额外直接验证了父线程持有 lookup lock 时,child 可通过torch._dynamo.lookup_backend("npu")恢复;scope 测试直接覆盖_NpuBackendScope,不能描述成 fork child 内执行完整 Inductor compile。4.6 AscendC deterministic:仅 target 需要保留的 v2.9 语义
AscendC deterministic 是 v2.9 target 独有的基线语义。实际 compile 调用进入
_NpuBackendScope("ascendc")后,先按临时 backend 环境加载或切换 NPU Inductor,再安装 deterministic level cache-key patch;随后new_call()在调用 PyTorch 原 wrapper 前安装 Dynamo deterministic guard。两者都位于 backend scope 内,异常时 scope 仍恢复TORCHINDUCTOR_NPU_BACKEND并释放锁。2.7.1 source 没有对应分支,跨版本同步时必须保留 target 实现。4.7
aten.erfc:target 独有的重复注册修复target 原本已定义 Triton
erfc = 1 - erf(x)decomposition,但 backend 重载会再次执行注册。若aten.erfc.default等 overload 仍留在全局 decomposition 表,第二次注册会先报duplicate registrations for aten.erfc.default。0bf5bdadcf仅将aten.erfc加入DECOMPOSITION_OVERLOAD_OP:add_overload()会展开 packet,并在现有 decorator 再次注册前删除default、out等旧 overload。在标准干净 registry 状态下,首次注册结果和数值公式不变,只修复重复初始化。该语义参考 v2.10 提交
ab0acd320e,但实现是基于 v2.9 当前 decomposition 结构完成;它不是 v2.7.1_import patch 的一部分,也没有同步ab0acd320e的其他文件。5. 测试差异与审查提示
test/dynamo/test_compile_trigger.py中:target 独有覆盖包括:
has_triton的 v2.9 CUDA/XPU/CPU/NPU 设备判定语义。torch._dynamo.lookup_backend()恢复。npu-backend与下划线npu_backend的 backend 选择一致性。source 按方法名独有的三项是:
前两项仍是 target 没有逐字直接对应测试的覆盖差异;部分行为已被 target 的 backend 可见性、partial retry、invalid options 和 inventory 测试间接覆盖。两边最新测试都已覆盖
eval_frame.lookup_backend已绑定入口、并发首次加载和注册后无锁热路径,不能再将它列为 target 独有覆盖。测试
git diff --check dd936ee52a7202c144bd1322c04041955f0d2205..0bf5bdadcfa38f20608f6b5a6434dbe97ccb2c1bpy_compileaten.erfc.default/out预置冲突后可被清理并重新注册;连续两次 Triton decomposition 注册通过0bf5bdadcfa3,ci-pipeline-passed影响评估
主体是 import/lazy 性能优化,并包含
aten.erfcdecomposition 重注册正确性修复。改动未设计新增或删除公开接口;在上述专项测试和 CI 覆盖范围内未发现兼容性回归。wheel metadata/cold load 独立测试和完整 public lazy retry 测试仍是 target 相对 source 的直接覆盖缺口,应避免把“未发现回归”表述为对所有导入顺序和第三方组合的绝对保证。