已关闭
[RFC]: 【重构】对多分支归一compat层做代码重构,完善校验机制 #4978
li_jing_hw创建于  9月21日关闭于  18 天前
li_jing_hw成员
9月21日 创建

状态(Status): Draft
作者(Authors): <待填>
创建日期(Created): 2026-09-21
更新日期(Updated): 2026-09-21
相关 Issue/PR: <待填>

1. 概述

1.1 简介

本提案重构 torch_npu/_compat/(多版本兼容适配层)的组织方式:把当前散落在各文
件里的版本判断,改成"装饰器登记 + 文件末尾统一兑现"的统一结构。

一个兼容点由三部分组成:一个 key、若干"侧"(每侧声明自己的命中条件)、以及模块
末尾的一行兑现语句。装饰器只登记、不执行;模块末尾按登记顺序选中当前版本命中的那
一侧,resolve() 只挑不调(把生效时机留给消费方),apply() 选中并当场调用。

核心价值是:版本阈值、上游依据、何时可删这些"事实"只有一个来源,并且由代码而不
是注释承载
——写错在 import torch_npu 时就会失败,而不是等到某个版本上补丁静默
失效。

1.2 动机

torch_npu 用一份代码同时支持多个上游 PyTorch 版本(当前下限见 version.py 的
MIN_SUPPORTED_VERSION),版本差异全部隔离在 _compat/ 里。重构前这里有 13 个
兼容点、三种写法并存(函数体内的早期返回、模块级 if/else 绑名字、别名透传),
痛点是:

  1. 同一个阈值出现在三处:COMPAT(>= 2.14) 注释、
    if CURRENT_VERSION >= (2, 14) 代码、以及
    CAN REMOVE when MIN_SUPPORTED >= (2, 14) 的删除提示。三者靠人保持一致,改一
    处漏一处不会报错。
  2. 边界极易写错:< 2.14 与 >= 2.14 差一,仓库自己的检查工具也明确拒绝
    > / <= 这两种操作符,说明这是公认的坑。
  3. 写错不报错:把 lt=(2, 15) 写成 lt=(2, 16),会出现两个版本区间同时命中
    或留空档,运行期不会有任何提示,表现为"补丁静默不生效"或"在新版本上错误生效
    "。
  4. 对外部贡献者不友好:torch_npu 是跨团队协作、开源贡献的生态,贡献者通常
    照着最近的代码抄。三种写法并存时,"最近的代码"不一定是正确样本;而注释里的约
    定(阈值、CAN REMOVE)在多人协作下退化很快。

不做本提案的后果:随着上游 2.14 / 2.15 持续变动,兼容点会继续增加,上述问题按点
数放大;而且 review 一个兼容点 PR 时,maintainer 需要人工核对"阈值是否互补、方向
是否写反、注释是否与代码一致",这类表单式检查会持续消耗 review 精力。

1.3 目标

目标

  1. 把机制性信息从注释搬进代码:版本阈值、这一侧要不要执行,都成为装饰器参
    数;注释只写"上游改了什么、为什么需要这一侧"。
  2. 统一形态:_compat 下所有版本差异都由同一种结构表达——一个 key + 若干执
    行函数 + 文件末尾一行兑现。
  3. 行为不变:每个兼容点的生效时机、生效范围、对外名字都与重构前一致;业务代
    码(from torch_npu._compat.xxx import yyy 的消费方)零改动。
  4. 边际成本不上升:以后新增兼容点的写法与现在相当,不额外增加模板量。

非目标

  • 不改 torch_npu/csrc/_compat/,C++ 侧继续用 TORCH_NPU_VERSION_GE 编译期分
    支。
  • 不改业务代码的 import 与调用方式。
  • 本次不改造 tools/compat_check/version_compat_check.py(它目前对本目录已静默
    失效,见§7)。

2. 用例分析

兼容层只有一类使用者——业务模块通过 from torch_npu._compat.xxx import yyy 取统
一接口。按"这个点在当前版本下要不要留一个可调用的东西给外部",现有 13 个兼容点分
成三类,这三类对应三种使用场景,也决定了各自用 resolve() 还是 apply():

类别 场景 兑现方式 现有例子
二类 名字要被外部 import 后调用 resolve():只挑不调,由外部调用 4 个 patch_*、2 个 dynamo 入口、device_to_aten、get_default_generator、_mm_like_strategy(共 9 个)
一类 没人调用这个名字,导入时做完就算 apply():选中并当场调用 ShardedTensor 的 npu() 补丁、ProcessGroup 的 sequence number 补丁(共 2 个)
混合 当场执行,而且还要留名字 apply(),执行函数 return 出要绑定的值 _new_process_group_helper、_add_ephemeral_timeout_for_all_pgs(共 2 个)

对 DFX 的要求直接来自这三类场景:

  • 兼容性:三类点在支持的每个版本上都必须恰好命中一侧;不支持的组合要在导
    入期就被拦下,不能留到运行期。
  • 可维护性:新增/删除兼容点只改一个文件、一处阈值;"什么时候能删"要能从代码
    推出来。
  • 可测试性:注册表本身要能脱离 NPU 设备做单元测试。
  • 可靠性/性能:不改变任何补丁的生效时机;二类补丁不得在 import 期提前生效。

3. 方案设计

3.1 总体方案

一个兼容点的构成

一个兼容点 = 一个 key + 若干"侧",每侧声明自己的命中条件;模块末尾统一兑现。

# 1) 一个 key 下写两"侧",各自声明命中条件
@compat_impl(key="patch_xxx", ge=(2, 16))       # CURRENT_VERSION >= (2, 16)
def patch_xxx_upstream():
    return                                      # 上游已具备,什么都不用做


@compat_impl(key="patch_xxx", lt=(2, 16))       # CURRENT_VERSION <  (2, 16)
def patch_xxx_local():
    ...                                         # 老版本要打的补丁(body 原样搬进来)


# 2) 文件末尾兑现
patch_xxx = compat_impl_container["patch_xxx"].resolve()

# 3) 消费方照旧 import 这个 key 并调用,不需要改

三条基本约定:装饰器只登记(import 期没有副作用,且返回原函数);文件末尾
才兑现
(全模块只做一次选择);执行函数的函数体就是"这一侧要做的事"(原样
搬过来,不需要再包一层内层函数)。

兼容点的标识:key

key 有三个作用:一是分组,所有校验按 key 分组,同一个 key 的若干侧才构成
一个兼容点;二是查表(compat_impl_container[key]);三是命名约定,
key 应当等于该点对外绑定的名字,也就是文件末尾那行左边。

现有 13 个点里 11 个的 key 就是导出名;2 个一类点没有对外名字,取一个能说明它
在干什么的私有名。key 打错或撞名会让校验失效——两个单侧点撞名会静默合并,这
是唯一靠机制发现不了的错。

注册表结构(类图)

classDiagram
    direction TB

    class compat_impl {
        <<decorator>>
        +compat_impl(key, ge, lt, judgement)
    }

    class _Container {
        -_points : dict~str, CompatPoint~
        +__getitem__(key) CompatPoint
        +point(key, module) CompatPoint
        +keys() list
        -_reset_for_test() None
    }

    class CompatPoint {
        +key : str
        +module : str
        -_sides : list~_Side~
        -_add(side) None
        -_check_versioned(side) None
        -_pick() _Side
        +resolve() Any
        +apply() Any
        +describe_all() list
    }

    class _Side {
        +key : str
        +module : str
        +fn : Callable
        +op : str
        +version : tuple
        +matched : bool
        +matches() bool
        +describe() str
    }

    class CompatError {
        <<exception>>
    }

    compat_impl ..> _Side : 构造
    compat_impl ..> _Container : point(key, module)
    _Container "1" *-- "0..*" CompatPoint : _points[key]
    CompatPoint "1" *-- "1..2" _Side : _sides(登记顺序)
    CompatPoint ..> CompatError : 校验失败时抛出
    _Container ..> CompatError : 未注册的 key

    note for _Side "op=ge/lt 用 version;<br/>op=judgement 用 matched(登记期已求值)"
    note for CompatPoint "resolve() 只选不调(二类)<br/>apply() 选中并零参调用(一类/混合)"
  • _Container:全局单例,按 key 索引,只负责"取或建 CompatPoint"。
  • CompatPoint:一个 key 的全部侧,负责校验(§3.3)与选择。
  • _Side:一侧的全部数据(fn + 命中条件),自己知道怎么判断命中。

注册表不做调度(没有执行顺序表)、不做自动发现(不扫描目录、不 import 业务模
块),_compat/__init__.py 保持空文件。

时序:登记 → 兑现 → 调用

sequenceDiagram
    autonumber
    participant T as torch_npu 启动链路
    participant M as _compat.某模块
    participant D as compat_impl
    participant C as 注册表
    participant S as _Side
    participant U as 消费方

    rect rgb(245, 245, 245)
    Note over T,D: ① 登记 —— 装饰器只登记,不执行
    T->>M: import(谁先用到谁先触发)
    M->>D: @compat_impl(key="x", lt=(2, 15))
    D->>D: _validate → 归一为 (op, version, matched)
    D->>C: point(key, module)._add(_Side(...))
    C->>S: 校验:跨模块?混用?方向重复?阈值互补?过期?
    D-->>M: return fn(名字仍可用)
    end

    rect rgb(245, 245, 245)
    Note over M,C: ② 兑现 —— 模块末尾按登记顺序选中一侧
    M->>C: container["key"]
    C->>S: 逐个 matches()
    S-->>C: 第一个命中的侧
    alt 二类:resolve() 只选不调
        C-->>M: side.fn(不调用)
    else 一类 / 混合:apply() 选中并零参调用
        C->>S: side.fn()
        S-->>C: 返回值 = 该 key 要绑定的值
        C-->>M: 返回值
    end
    end

    rect rgb(245, 245, 245)
    Note over M,U: ③ 调用 —— 补丁类的生效时机由消费方决定
    M->>U: 模块属性就绪(补丁类此刻还没执行)
    U->>M: 调用 —— 补丁类真正生效
    end

两条要记住的:装饰器只登记,import 期不会有副作用;二类补丁的生效时机由消
费方决定
(resolve() 不调用它),这是保住"延迟执行"效果的关键——例如 inductor
的补丁不能在 import 期生效,否则只为 triton 路径准备的补丁会波及 ascendc 后端。

3.2 技术选型

放弃:把实现包进内层函数再返回(所有点统一成"零参工厂")

这样能让所有执行函数形状一致,但需要把大块实现整体缩进一层。现有最大的两份实现是
325 行和 116 行,纯缩进的 diff 无法有效 review,且回归风险不低。最终只对确实需要
"造函数"的点(一类/混合)使用零参工厂,其余保持平铺。

放弃:把注册表做成调度器

仓库里已有 PatchManager(按 group 顺序统一 apply)的先例,但 _compat 的生效
时机由import 关系决定(谁先用到谁先触发),且存在跨块顺序依赖,再造一套调度会变
成"两个调度器互相影响"。注册表只做"登记 + 选择"。

3.3 功能与性能设计

命中条件三选一

@compat_impl(key="patch_xxx", ge=(2, 16))       # CURRENT_VERSION >= (2, 16)
@compat_impl(key="patch_xxx", lt=(2, 16))       # CURRENT_VERSION <  (2, 16)
@compat_impl(key="some_point", judgement=<bool 表达式>)   # 逃生门
  • 只提供 ge / lt 两种操作符:> 与 <= 和 >= / < 差一,是最容易翻
    车的地方,所以从 API 层面就不提供。
  • 同一个 key 里不能混用 ge/lt 和 judgement,混了无法判断互补性。
  • judgement 是逃生门,只给"同一个 minor 内部还有分叉"的点用(例如上游改动
    落在 2.14 中段的 nightly,而 CURRENT_VERSION 只保留 major.minor)。写法是把
    条件提成模块级变量、两侧用 not 互补;版本判断必须写在前面,靠短路保证旧
    版本不去跑 inspect.getsource()。使用 judgement 的点豁免全部阈值校验,因此
    有配额(当前 4 处)。

判定逻辑就是这三种条件的直接实现:

flowchart TD
    A["_Side.matches()"] --> B{"op == judgement ?"}
    B -- 是 --> C["返回 matched(登记期已求值)"]
    B -- 否 --> D{"op == ge ?"}
    D -- 是 --> E["CURRENT_VERSION >= version"]
    D -- 否 --> F["CURRENT_VERSION < version"]

兑现语义

# 二类:只选择,不执行
patch_xxx = compat_impl_container["patch_xxx"].resolve()

# 一类 / 混合:选择并当场落实
compat_impl_container["sharded_tensor_npu_method"].apply()
_new_process_group_helper = compat_impl_container["_new_process_group_helper"].apply()
  • resolve():按登记顺序返回第一个命中的函数对象(不调用)。没有一侧命中时
    抛错。
  • apply():零参调用命中的执行函数,返回值即该 key 要绑定的值。没有一侧命中时什
    么都不做。

校验规则(注册期,发现问题直接抛 CompatError,import torch_npu 即失败)

检查 目的
key 非空;ge/lt/judgement 恰好一个 参数完备性
阈值是 (int, int) 防止写成 (2, "15")
阈值 <= MIN_SUPPORTED_VERSION 该侧已过期,提示删除
同一 key 最多两侧,且必须一个 ge 一个 lt、阈值相同 保证两侧互补(不留空档、不重叠)
同一 key 的所有侧来自同一模块 防止跨模块撞名
同一 key 不混用 ge/lt 与 judgement 互补性可判
judgement 侧最多一侧命中 防条件重叠

解析期:resolve() 没有命中侧时报错(二类的名字必须有值);apply() 没有命中侧
时静默(一类允许"这个版本不需要我")。

性能影响

  • 选择只在模块导入时做一次,不在业务热路径上。
  • 净效果是热路径少一次判断:重构前二类补丁每次调用都要过一次
    if CURRENT_VERSION >= (2, 15): return,现在由 resolve() 在导入期选定,调用
    时不再比较版本。
  • 一类/混合点的导入期开销与重构前相同。

3.4 安全隐私与DFX设计

安全隐私:本提案只调整版本差异的组织形式,不新增数据通路、不处理用户数据、不
改变任何对外接口的可见性,因此不涉及新的安全与隐私面。

兼容性

  • 对业务代码完全兼容:所有 from torch_npu._compat.xxx import yyy 的消费方
    零改动,导出名字与签名不变。

  • 已知的行为变化(已逐条核对):

    变化 说明
    import 时机变惰性 _mm_like_strategy、_new_process_group_helper 两侧原本在模块导入时执行的 import,改为首次调用时执行
    补丁执行位置变化 distributed.py 的 5 个点从"文件中间各自的位置"移到"文件末尾的兑现行";顺序仍等于书写顺序,语义不变
    缩进变化一处 只有 _mm_like_strategy 的本地实现从 if 块里抬到模块级(8 格 → 4 格,33 行)
    新增私有名字 模块上多出 <key>_local / <key>_upstream,对 __all__ 无影响
    CURRENT_VERSION 引用收敛 判断不再埋在函数体里,只剩 2 处 judgement 逃生门
    测试同步 test/test_torch_npu_init.py::test_07 断言了 shim 的模块属性,需同步调整(该断言在 torch >= 2.14 分支上仍然成立)
  • 破坏性变更:无。对外名字、签名、生效时机与重构前一致。

可维护性

  • 阈值单点维护:同一次上游改动的多个使用点(如 #190615、#193904、#192345 各 2
    处)共享同一个 key,改阈值只改一处。
  • "什么时候能删"由代码推导:阈值 <= MIN_SUPPORTED_VERSION 的侧在导入期直接报
    错,等于一份自动生成的待办清单,不依赖工具或记忆。
  • 抬版本时的清理动作有固定套路(见 §3.5.1)。

可测试性

  • 注册表本身脱离 NPU 设备可测:test/test_compat_registry.py(20 条)只需能
    import torch,版本分支通过 monkeypatch CURRENT_VERSION 覆盖。
  • 兼容点行为可对照:有一个 23 条的冒烟脚本,逐点观测"命中哪一侧 + 补丁是否生效
    ",产出可diff 的指纹,用于重构前后对比。

3.5 编程与调用设计

3.5.1 编程模型基本设计

开发环境:torch_npu 源码树 +
bash ci/build.sh --python=<ver> --torch=<ver> 编译出wheel,
pip install dist/torch_npu-*.whl 后即可验证。改 _compat 属于纯 Python 改动,
不涉及 C++ 编译。

开发约束(写新兼容点时必须遵守,也是 review 的检查点):

  1. 登记顺序 = 装饰器在文件里出现的顺序,resolve() 按这个顺序取第一个命中项;不
    要按阈值或字母重排。
  2. 带探测的那侧写在另一侧之前(judgement 在装饰器那一行求值,探测要读到未打
    补丁
    的上游)。
  3. 末尾兑现行的书写顺序就是执行顺序(distributed.py 里 ProcessGroup 补丁依赖
    _new_process_group_helper 先执行)。
  4. 兑现必须写在本模块体内、文件末尾,不能推迟到 torch_npu 启动流程里统一调用
    (from X import Y 取的是那一刻的值,不是活引用)。
  5. 二类的执行函数体里不能有"必须发生在导入期"的副作用;需要导入期副作用的点属于
    一类/混合。
  6. apply() 零参调用执行函数,所以一类/混合的执行函数不能带业务参数。
  7. key 必须与文件末尾绑定行的左边一致。

可验收设计:见 §4 测试设计。

3.5.2 接口定义与设计
3.5.2.1 compat_impl(装饰器)
  • 接口描述:登记兼容点的一侧。只登记,不执行、不改名字,返回被装饰的函数。
  • 接口原型:compat_impl(*, key, ge=None, lt=None, judgement=None) → decorator
  • 输入/输出参数:
参数名称 输入/输出 类型 描述 取值范围
key 输入 str 兼容点唯一标识,约定等于该点对外绑定的名字 非空字符串
ge 输入 tuple[int, int] 该侧在 CURRENT_VERSION >= ge 时命中 (major, minor),须 > MIN_SUPPORTED_VERSION
lt 输入 tuple[int, int] 该侧在 CURRENT_VERSION < lt 时命中 同上
judgement 输入 bool 逃生门:版本号判不出来时的任意布尔条件 True / False,且与 ge/lt 互斥

ge / lt / judgement 三者必须恰好给一个。

  • 返回参数:
参数名称 类型 描述 取值范围
decorator Callable 装饰器,返回被装饰函数本身 —
  • 异常处理:参数校验或注册期校验失败时抛 CompatError,import torch_npu 直接
    失败。
  • 约束说明:同一 key 最多两侧(一个 ge、一个 lt,阈值相同);所有侧必须来
    自同一模块、且不混用 ge/lt 与 judgement。
  • 变更说明:新增接口,替代原有的 if CURRENT_VERSION >= (X, Y) 写法。
  • 调用参考代码:
@compat_impl(key="patch_xxx", ge=(2, 16))       # 新版本侧
def patch_xxx_upstream():
    return


@compat_impl(key="patch_xxx", lt=(2, 16))       # 老版本侧
def patch_xxx_local():
    ...
3.5.2.2 compat_impl_container[key].resolve()
  • 接口描述:二类兑现方式——返回当前版本命中的那一侧的函数对象,不调用它,由消
    费方在自己的时机调用。
  • 接口原型:compat_impl_container[key].resolve() → Callable
  • 输入/输出参数:
参数名称 输入/输出 类型 描述 取值范围
key 输入 str 已登记的兼容点标识 注册表中存在的 key
  • 返回参数:
参数名称 类型 描述 取值范围
命中侧的函数对象 Callable 二类补丁此时只被"挑出",尚未执行 —
  • 异常处理:key 未注册或当前版本没有任何一侧命中时抛 CompatError。
  • 约束说明:必须写在 _compat 模块体内、文件末尾。
  • 变更说明:新增接口,替代原有的"模块级 if/else 绑定名字"。
  • 调用参考代码:
patch_cache_base_get_system = compat_impl_container["patch_cache_base_get_system"].resolve()
3.5.2.3 compat_impl_container[key].apply()
  • 接口描述:一类/混合的兑现方式——选中命中的那一侧并零参调用,把返回值作为该
    key 要绑定的值。
  • 接口原型:compat_impl_container[key].apply() → Any
  • 输入/输出参数:
参数名称 输入/输出 类型 描述 取值范围
key 输入 str 已登记的兼容点标识 注册表中存在的 key
  • 返回参数:
参数名称 类型 描述 取值范围
执行函数的返回值 Any 该 key 要绑定的值;一类点通常为 None(不绑定名字) —
  • 异常处理:当前版本没有任何一侧命中时不报错,返回 None(一类允许"这个版本
    不需要我")。
  • 约束说明:执行函数必须零参;需要留名字的点要 return 出要绑定的值。
  • 变更说明:新增接口。
  • 调用参考代码:
compat_impl_container["sharded_tensor_npu_method"].apply()
_new_process_group_helper = compat_impl_container["_new_process_group_helper"].apply()
3.5.3 编程手册设计

贡献者需要的不是完整设计文档,而是"照着抄 + 别踩坑"。因此在 torch_npu/_compat/
下随代码放一份 README.md,与代码同目录、改动时顺手可见。内容包含:

  1. 一个兼容点的骨架(可照抄)
  2. 三类判据表(决定用 resolve() 还是 apply())
  3. key 的约定
  4. 命中条件 / 逃生门 / 上游侧写法 / 命名 / 兑现
  5. 顺序与时机硬约束(§3.5.1 的 7 条)
  6. 会被检查什么
  7. 反例清单("看着能跑但会坏"的写法)
  8. 抬 MIN_SUPPORTED_VERSION 时怎么删改(含三处版本号与四类点的删法)
  9. 测试入口

该文档为独立输出,不并入现有用户手册;后续如有对外发布需求,可作为内嵌章节的底
稿。

4. 测试设计

单元测试(不需要 NPU 设备)

test/test_compat_registry.py,20 条,覆盖注册表的全部行为:

# 用例 断言
1–3 两侧命中 / 登记顺序 / 互补性 ge 侧在 >= 时选中、lt 侧在 < 时选中;每个版本恰好命中一侧
4–6 阈值不一致 / 方向重复 / 超过两侧 抛 CompatError
7 阈值过期 抛 CompatError
8–10 judgement 命中 / 重叠 / 与 ge/lt 混用 重叠与混用抛 CompatError
11–15 resolve 未命中报错 / apply 未命中静默 / apply 零参调用 / 两种 thunk 形状 行为与设计一致
16–20 跨模块同名 / 阈值类型 / key 为空 / 装饰器返回原函数 / 未注册 key 抛 CompatError 或返回原函数

版本分支通过 monkeypatch.setattr(_impl, "CURRENT_VERSION", (2, 14)) 覆盖。

集成 / 回归测试(需要 NPU 设备)

重构前后各跑一遍同一组既有用例,逐条对比通过情况:

用例 覆盖的兼容点
test/test_torch_npu_init.py(14) import 顺序、公开导出、distributed 补丁行为
test/distributed/test_c10d_default_backend_type.py(3) _new_process_group_helper
test/distributed/test_c10d_get_sequence_number.py(2) ProcessGroup sequence number 补丁
test/distributed/test_c10d_add_ephemeral_timeout.py(2) _add_ephemeral_timeout_for_all_pgs
test/npu/test_npu.py 的 accelerator generator 两项 get_default_generator
test/dynamo/test_compile_trigger.py(27) dynamo + inductor 初始化链
test/_inductor/test_add.py / test_cat.py / test_sum.py inductor 的 4 个 patch_* 与 device_to_aten

冒烟对照

一个 23 条的观测脚本,逐点记录"命中哪一侧 + 补丁是否真的生效",输出可 diff 的指
纹;重构前后指纹应逐字一致(目前实测一致)。

环境说明

本机验证时遇到一个与本提案无关的环境问题:torch 2.14 的 ATen/ATen.h 要求
C++20,而 triton 的 ascend backend 硬编码 -std=c++17,导致 inductor 用例在编译
阶段全部失败。用预编译npu_utils.so 塞进 triton 缓存可规避,不影响本提案的验证
结论。

5. 缺点和风险

项 说明 应对
一次性改动量 4 个文件迁移、新增注册表(213 行)与单测(222 行) 已按文件分批迁移并验证;注册表可独立测试
新的维护面 注册表本身成为需要维护的代码 单测 20 条 + README;逻辑集中在 _impl.py 一个文件
引入新的中间层 调用链多了一层"注册 → 选择",读代码时需要先理解模型 README 用一张骨架 + 一张时序图说明;13 个点形态统一,照抄即可
diff 噪声 大块实现有两处需要移动位置(缩进不变,但文件内位置变化) 迁移按文件分批提交,便于 review
工具侧退步 tools/compat_check/version_compat_check.py 扫的是 CURRENT_VERSION >= (X, Y) 字面量,迁移后对本目录静默失效 列为未解决项(§7),需改造为读装饰器参数
不引入的性能回退 无;选择只在导入期做一次,热路径反而少一次版本比较 已核对
Breaking Change 无(导出名、签名、生效时机均不变) —

6. 现有技术

(本提案未做横向调研,暂缺。)

7. 未解决问题

  1. 兼容性检查工具改造:tools/compat_check/version_compat_check.py 需要从"
    扫CURRENT_VERSION >= (X, Y) 字面量"改为"扫装饰器参数",并把注册期已有的规则
    (阈值互补、方向唯一、过期、judgement 配额、每个 key 必须有兑现行)搬进去。
    这对外部贡献者尤其重要:PR 门禁通常跑不了 import torch_npu(需要 torch +
    CANN),文本层的静态检查不可替代。
  2. judgement 逃生门配额:目前 4 处(2 个点 × 两侧),需要工具侧盯住,防止
    被滥用。
  3. 全量回归:本次回归覆盖了与兼容层直接相关的用例集;test/ 下其余用例尚未
    做全量前后对比。
  4. 删除流程的进一步自动化:抬 MIN_SUPPORTED_VERSION 时目前是逐条报错(一次
    报一个过期侧),可考虑一次性列出全部待清理项。

附录

参考资料链接

  • 涉及的上游改动:pytorch#186667(_mm_like_strategy)、#179901(NPU 默认
    backend)、#187939(ShardedTensor 硬件无关)、#190138(sequence number 白
    名单)、#191980 (backend 通用 ephemeral timeout)、#190615
    (DeviceOpOverrides / DeviceInterface)、#193904(ATen device 映射)、
    #193908(cache system metadata)、#192345(dynamo _dynamo_backend_init
    钩子)
  • torch_npu/_compat/README.md(随代码发布的使用说明)

术语表

术语 含义
兼容点 一处版本差异,由一个 key 和若干"侧"构成
侧 兼容点在某个版本区间上的实现(ge / lt / judgement 三选一)
兑现 模块末尾用 resolve() 或 apply() 选定当前版本该用的那一侧
一类 / 二类 / 混合 按"要不要留给外部一个可调用的东西"划分的三类兼容点
逃生门 judgement,用于版本号判不出来的场景(如同 minor 内部的 nightly 分叉)

文档更新计划

欢迎加入社区,感谢您对社区的贡献 🎉!

likedislike
Lli_jing_hw成员
9月21日 添加了label:rfc
TorchNPU-BotTorchNPU-Bot成员
9月21日 添加了label:triage-review
TorchNPU-Bot
TorchNPU-Bot成员
9月21日 评论:

issue待分派,添加triage-review标签

likedislike
Lli_jing_hw成员
9月21日 修改了issue 的描述
Lli_jing_hw成员
9月21日 修改了issue 的描述
Lli_jing_hw成员
9月21日 关联了pull request:Refactor compat impl for strong validation instead of comment
TorchNPU-BotTorchNPU-Bot成员
9月21日 添加了label:bot-triaged;删除了label:triage-review
TorchNPU-Bot
TorchNPU-Bot成员
9月21日 评论:

检测到当前 issue 已关联 PR,自动添加标签:bot-triaged

likedislike
li_jing_hw成员
9月23日 评论:

1.key和符号一致,自动导出符号,需要权衡代码可读性。
2.__all__约束导出的符号。

likedislike
chenrayraychenrayray成员
9月24日 关联了里程碑:v26.2.0
chenrayraychenrayray成员
19 天前 移除了里程碑
chenrayray
chenrayray成员
18 天前 评论:

因为本 RFC 的实现 !46750 已于 2026-10-09 合入 master(e75e3d4285),兼容层登记、统一兑现与校验重构已进入官方代码,所以按实现已合入先关闭。

如仍有问题或仍需推进,请重新打开,并补充当前 torch/torch_npu/CANN 版本、受影响分支、最小复现或具体未完成项及最新日志,便于继续处理。

likedislike
chenrayraychenrayray成员
18 天前 关闭了 issue
chenrayraychenrayray成员
18 天前 issue状态由 TODO 改变为 DONE
ascend-robotascend-robot成员
18 天前 添加了label:resolved