已合并
perf: defer dynamo/inductor loading on v2.9.0 (#2788) #43444
黄桂军创建于 18 天前
perf: defer dynamo/inductor loading on v2.9.0 (#2788) #43444
已合并
黄桂军创建于 18 天前
黄桂军
黄桂军成员
18 天前

变更说明

将 v2.7.1_import 上的懒加载性能优化同步到 v2.9.0:

  • import torch_npu 时不再提前加载 torch._dynamotorch._inductor 和 sympy。
  • 首次导入或使用 Dynamo 入口时完成 Dynamo lazy setup;只有选择 Inductor backend 时才继续完成 Inductor lazy setup,非 Inductor backend 不初始化 Inductor。
  • 设计上保留 torchair、npugraph_ex、transfer_to_npu、RNG prims 与公开接口行为;本文列出的测试和 CI 范围内未发现兼容性回归。
  • 采用 v2.9.0 原生的 NPU Inductor device-op 注册路径,并保留目标分支的版本特有语义。
  • 除 v2.7.1_import 的懒加载同步外,target 还包含一项来自 v2.10 修复意图、基于 v2.9 当前代码实现的 aten.erfc decomposition 重注册修复。

v2.7.1_import 与 v2.9.0_import 改动差异总报告

1. 报告目的

方便 committer 进行代码检视。

2. “相同”与“差异”的判定规则

对每个文件分别提取两边 patch 的新增行和删除行,保留原始顺序和全部字符,然后逐字节比较:

  • 相同:新增/删除代码行序列完全一致,包括空格、空行和文件末尾换行。
  • 差异:只要一个字符不同,或新增/删除行数不同,就标记为差异。
  • 仅 v2.7.1:v2.7.1 import 修改了该文件,v2.9.0 import 没有修改。
  • 仅 v2.9.0:v2.9.0 import 修改了该文件,v2.7.1 import 没有修改。

“改动代码相同”不等于“最终文件相同”。两个版本的基线代码本身可能不同;报告会把“同步 hunk 相同、最终文件因版本基线不同”单独说明。

本报告固定比较以下 SHA,避免维护分支继续前进后改变统计口径:

  • v2.7.1 source:0c9b783ca5635da8000e571fa60370f266a4e978..371545dc2e4a09ca2024ec220c3eed1b9a50ba48
  • v2.9.0 target:dd936ee52a7202c144bd1322c04041955f0d2205..0bf5bdadcfa38f20608f6b5a6434dbe97ccb2c1b

3. 总体统计

两边 patch 的文件并集共 34 个文件;source 修改 33 个文件,target 修改 29 个文件:

  • 18 个文件的改动代码逐字符相同。
  • 10 个文件双方都修改,但改动代码存在字符差异。
  • 5 个文件仅 v2.7.1 修改,v2.9.0 采用不同的 DTensor 方案。
  • 1 个文件仅 v2.9.0 修改,用于修复 aten.erfc decomposition 重复注册。

3.1 改动代码完全相同的 18 个文件

文件 v2.7.1 增/删 v2.9.0 增/删 结论
setup.py +5/-0 +5/-0 entry point 声明完全相同
test/_inductor/test_current_device.py +2/-1 +2/-1 完全相同
test/_inductor/test_mlir_enable.py +4/-1 +4/-1 完全相同
test/npu/test_stream.py +5/-0 +5/-0 完全相同
test/utils/test_inductor.py +0/-33 +0/-33 删除代码完全相同
torch_npu/_init/patches/api_patches.py +0/-2 +0/-2 完全相同
torch_npu/_init/patches/distributed_patches.py +61/-10 +61/-10 完全相同
torch_npu/_init/patches/dynamo_patches.py +0/-7 +0/-7 完全相同
torch_npu/contrib/transfer_to_npu.py +13/-1 +13/-1 完全相同
torch_npu/distributed/fsdp/__init__.py +2/-0 +2/-0 完全相同
torch_npu/distributed/tensor/__init__.py +14/-1 +14/-1 完全相同
torch_npu/dynamo/__init__.py +67/-5 +67/-5 改动行完全相同;最终文件有一个基线空行差异
torch_npu/npu/__init__.py +13/-2 +13/-2 完全相同
torch_npu/npu/_graph_tree.py +1/-12 +1/-12 改动行完全相同;保留各版本图树基线
torch_npu/npu/_graph_tree_state.py +11/-0 +11/-0 新文件完全相同
torch_npu/npu/deterministic.py +11/-3 +11/-3 完全相同
torch_npu/utils/_graph_tree.py +20/-9 +20/-9 改动行完全相同;保留各版本 API 基线
torch_npu/utils/_rng_prims_patch.py +255/-0 +255/-0 新文件完全相同

3.2 双方都修改但代码不同的 10 个文件

文件 v2.7.1 增/删 v2.9.0 增/删 主要差异原因
test/dynamo/test_compile_trigger.py +1372/-0 +1438/-0 PyTorch 2.9 行为适配及 target 独有 has_triton、shape、fork 锁测试
test/test_torch_npu_init.py +89/-14 +79/-15 DTensor OpSpec、TMA 名称及 bootstrap 快照版本差异
torch_npu/_inductor/kernel/bmm.py +5/-4 +3/-2 v2.9 已无 bmm_configs/bmm_template 本地别名
torch_npu/_inductor/utils.py +2/-1 +2/-2 source 在此定义并安装 has_triton;target 复用 _dynamo.py 的 v2.9 实现,只同步 Inductor 已绑定入口
torch_npu/_init/registry/registry_manager.py +10/-24 +8/-31 两版 registry 项目和初始化列表不同
torch_npu/distributed/__init__.py +19/-1 +18/-0 两版 distributed 基线 import 结构不同
torch_npu/npu/npugraph_ex/__init__.py +2/-2 +1/-1 基线空格/EOF 不同;最终文件收敛相同
torch_npu/utils/__init__.py +1/-1 +1/-0 source 替换已有 import,target 在不同基线上新增;最终相同
torch_npu/utils/_dynamo.py +447/-133 +428/-129 v2.9 wrapper、options、has_triton、AscendC、lookup 与 fork 锁适配
torch_npu/utils/_inductor.py +8/-248 +8/-247 删除代码相差一个基线空行;最终兼容转发文件相同

3.3 仅 v2.7.1 修改的 5 个文件

文件 v2.7.1 增/删 v2.9.0 原因
torch_npu/distributed/tensor/_attention.py +2/-2 未修改 v2.9 直接使用上游 register_sharding
torch_npu/distributed/tensor/_dtensor_patch.py +63/-0 未修改 2.7.1 需要本地轻量 helper;2.9 不需要复制上游实现
torch_npu/distributed/tensor/_math_ops.py +2/-1 未修改 同上
torch_npu/distributed/tensor/_matrix_ops.py +1/-1 未修改 同上
torch_npu/distributed/tensor/_moe_ops.py +3/-2 未修改 同上

3.4 仅 v2.9.0 修改的 1 个文件

文件 v2.7.1 v2.9.0 增/删 原因
torch_npu/_inductor/decomposition.py 未修改 +1/-0 aten.erfc 加入重注册前的 overload 清理集合,避免 backend 重载时重复注册

4. 最重要的版本差异

4.1 DTensor:v2.9 不同步 source 的 5 个文件改动是有意设计

v2.7.1 为避免导入 DTensor experimental API 间接加载 compiler 模块,在已有 _dtensor_patch.py 中增加轻量 register_sharding helper,并让 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.1 register_sharding helper 的副本。

因此,target 未引入 _sharding_registration.py 对应的上游源码副本,也没有这类新增副本的 Meta/BSD License 声明要求;不能表述为 target 不存在 _dtensor_patch.py

4.2 has_triton:保留 v2.9 实现,并按初始化层级复用

v2.7.1 的 has_tritontorch_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,而是保留以下顺序:

NPU option 名规范化及 npu_backend 类型预检
→ PyTorch 2.9 src_init/src_apply_options 完成 mode、option 名和值校验
→ 根据规范化 options/config/env 选择 NPU backend
→ 初始化所选 backend
→ 执行延迟记录的 shape handling patch
→ 执行 MLIR/DVM 构造期附加 scope 操作

target 还支持 PyTorch 2.9 原生的连字符 option alias:

attr_name = key.replace("-", "_")

因此 npu-backend/npu_backendenable-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_OPadd_overload() 会展开 packet,并在现有 decorator 再次注册前删除 defaultout 等旧 overload。在标准干净 registry 状态下,首次注册结果和数值公式不变,只修复重复初始化。

该语义参考 v2.10 提交 ab0acd320e,但实现是基于 v2.9 当前 decomposition 结构完成;它不是 v2.7.1_import patch 的一部分,也没有同步 ab0acd320e 的其他文件。

5. 测试差异与审查提示

test/dynamo/test_compile_trigger.py 中:

  • source 有 27 个测试方法,target 有 28 个测试方法。
  • 双方有 24 个同名方法,其中 19 个逐字符相同,5 个因版本调用链和断言差异而不同。
  • 按方法名机械比较,source 独有 3 个方法,target 独有 4 个方法。

target 独有覆盖包括:

  • has_triton 的 v2.9 CUDA/XPU/CPU/NPU 设备判定语义。
  • 合法 MLIR + shape handling 的 v2.9 构造顺序。
  • 父线程持有 compiler lookup lock 时,fork child 重建锁并从 torch._dynamo.lookup_backend() 恢复。
  • 连字符 npu-backend 与下划线 npu_backend 的 backend 选择一致性。

source 按方法名独有的三项是:

  • wheel entry-point metadata/cold load 独立测试。
  • 公共 Dynamo/Inductor lazy setup 首次失败后的完整 retry 测试。
  • 合并验证连字符 backend/shape options 的测试;target 将相关语义拆分到 backend alias 与 shape 初始化顺序测试中,因此这不是实际覆盖缺失。

前两项仍是 target 没有逐字直接对应测试的覆盖差异;部分行为已被 target 的 backend 可见性、partial retry、invalid options 和 inventory 测试间接覆盖。两边最新测试都已覆盖 eval_frame.lookup_backend 已绑定入口、并发首次加载和注册后无锁热路径,不能再将它列为 target 独有覆盖。

测试

  • git diff --check dd936ee52a7202c144bd1322c04041955f0d2205..0bf5bdadcfa38f20608f6b5a6434dbe97ccb2c1b
  • 受影响 Python 文件 py_compile
  • torch 2.9.0 / torch_npu 2.9.0 环境导入检查
  • aten.erfc.default/out 预置冲突后可被清理并重新注册;连续两次 Triton decomposition 注册通过
  • GitCode PR #43444 CI:pipeline #1012707,SHA 0bf5bdadcfa3ci-pipeline-passed

影响评估

主体是 import/lazy 性能优化,并包含 aten.erfc decomposition 重注册正确性修复。改动未设计新增或删除公开接口;在上述专项测试和 CI 覆盖范围内未发现兼容性回归。wheel metadata/cold load 独立测试和完整 public lazy retry 测试仍是 target 相对 source 的直接覆盖缺口,应避免把“未发现回归”表述为对所有导入顺序和第三方组合的绝对保证。

likedislike
Pull Request已成功合入, 合并人@ascend-robot
(感谢 黄桂军 的贡献)
黄桂军黄桂军成员
18 天前 创建了 pull request,commit 49eb8b27
atomgit-bot
atomgit-bot
18 天前 评论:

变更摘要

本 PR 将 v2.7.1_import 分支上的懒加载性能优化同步到 v2.9.0,核心目标是延迟 torch._dynamotorch._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_patchphilox_rand_offsetregister_philox_randrun_and_save_rng_staterun_with_rng_staterng_prims_device)迁移到新文件,封装为 apply_rng_prims_patches(),由 _register_inductor() 在导入时调用,避免加载 torch._inductor

  • 消除多处模块级 eager import 对 dynamo/inductor 的依赖torch_npu/dynamo/__init__.pyfrom torch._dynamo import register_backend 移入 _register_npu_backend() 函数体;torch_npu/npu/deterministic.py 用本地 _forbid_in_graph 替代 from torch._dynamo.decorators import forbid_in_graphtorch_npu/npu/npugraph_ex/__init__.py 使用 TYPE_CHECKING 包裹 SearchFn 等类型,并将 fwd_only 的导入后置到函数内按需 fallback。

  • 补丁子模块的显式导入适配懒加载torch_npu/_inductor/utils.py 在 patch has_triton 前显式 from torch._inductor import compile_fx, schedulertorch_npu/contrib/transfer_to_npu.py_init() 中显式导入 torch._dynamo.trace_rulestorch._inductor.compile_fx 等子模块,确保延迟加载后补丁仍能正确应用。

  • torch_npu.utils._inductor 精简为占位模块:原文件 247 行缩减为 12 行的注释说明,RNG 补丁迁移至 _rng_prims_patch.py,NPU device op 覆盖注册交由 torch_npu._inductor 包自身的 codegen 路径在首次编译时完成。

likedislike
atomgit-bot
atomgit-bot
18 天前 评论:

代码审查

审查总结

本次审查覆盖了全部 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

⛔ 需要修改

likedislike
ascend-robotascend-robot成员
18 天前 添加了label:stat/needs-squash
此处折叠了401条消息 查看更多
ascend-robotascend-robot成员
5 天前 删除了label:needs-issue
黄桂军
黄桂军成员
5 天前 评论:

/check-issue

likedislike
ascend-robotascend-robot成员
5 天前 合入了pull request
ascend-robot
ascend-robot成员
5 天前 评论:

Pull Request 已合并或已关闭。

If you want to solve this problem, you can click here to do it in the FAQs.

likedislike
ascend-robot
ascend-robot成员
5 天前 评论:
流水线 pytorch_gitcode_PR_multiVersion#13918 [ commitID:2facc0ae ] 已完成
likedislike