问题说明
从您提供的日志和定界结果看,QW 缺少底层依赖后,导入失败但已注册的 schema 仍保留;后续重新初始化时,_ensure_initialized() 对 ds41.indexer_prologue_qw 的检查未识别已有注册,再次执行 Library.define(),最终重复注册异常掩盖了缺包错误。该结论限于本次 K/QW 导入场景,不涉及 NPU 计算,也不能推广到 ds41 全组。
处理建议
可以考虑修正公共 OpBuilder 对算子名及 overload 的检查,使重复初始化不重复注册;同时调整 _load_op,接口导入失败后不再继续加载其 graph-convert,并保留原始依赖异常。QW 的依赖检查也可前移至注册之前,减少失败后残留注册的情况。重点验证缺包时错误清晰、依赖可达时 K/QW 均可导入,避免仅靠捕获 RuntimeError 隐藏问题。


已修复 torch_extension 在缺底层依赖时,因带点号算子(如 ds41.indexer_prologue_qw)重复注册导致 import cann_ops_transformer 失败的问题。
原因:
OpBuilder 用 hasattr(torch.ops.cann_ops_transformer, "ds41.xxx") 判断是否已注册;点号名在 PyTorch 中是分层命名空间,该检查恒为 False,导入失败后 schema 残留再初始化会二次 Library.define。
entry-point 路径的 load_op 在接口 import 失败后仍继续加载 graph_convert*,再次触发失败模块初始化,放大为 Duplicate registration,掩盖原始缺依赖错误。
改动:
op_builder/builder.py:新增 is_op_registered(),按 . 逐层检查算子是否已注册。
ops/init.py:entry-point 加载失败时提前 return,不再拉 graph_convert*。
缺依赖时应只报告依赖缺失;依赖齐全时 K/QW 等接口可正常导入,不再出现重复注册。


Describe the current behavior / 问题描述
同时安装常规 PyTorch 接口包与实验接口包后,在实验接口依赖的底层 Python 模块不可导入时,仅执行
import cann_ops_transformer,导入过程先出现缺少模块的错误,随后连续出现同一接口 schema 重复注册的错误,最终导致主包导入失败。本次最小复现选择 ds41 的以下两个接口:
indexer_prologue_k_dsl:注册入口torch.ops.cann_ops_transformer.ds41.indexer_prologue_k。indexer_prologue_qw_dsl:注册入口torch.ops.cann_ops_transformer.ds41.indexer_prologue_qw。当前 DSL 工程目录表中的 ds41 组包括
mqsmla、qli、qsli、indexer_prologue_qw、indexer_prologue_k、engram_gate、attn_epilogue。本机最小复现仅选择 K、QW,不据此认定组内其他算子均有相同表现。异常发生在 Python 导入阶段,未调用算子,未执行 NPU 计算。
Environment Information / 环境信息
Steps to reproduce the issue / 重现步骤
在完整环境中,对应接口打包和导入方式如下:
# 常规接口包 bash build.sh --torch_extension --incremental # 实验接口包,选择 K、QW bash build.sh --torch_extension --incremental \ --experimental \ --ops=indexer_prologue_k_dsl,indexer_prologue_qw_dsl \ --vendor_name=experimentalops.indexer_prologue_qw,例如尚未安装包含该模块的底层算子包。import cann_ops_transformerNo module named 'ops.indexer_prologue_qw',随后出现cann_ops_transformer::ds41.indexer_prologue_qw的 schema 重复注册错误,最终 import 失败。本机隔离探针使用保留的原始接口包,在独立 Python 进程中重复两次,均复现该错误。仅提供可导入的模拟 QW 底层模块作对照时,两个接口均可访问,导入成功;对照没有调用实现函数。
Describe the expected behavior / 预期结果
接口包能够正常导入。如果缺少必需的底层依赖,应明确报告该依赖缺失。一次导入过程不应在缺少依赖后进一步出现相同 schema 重复注册错误,掩盖最初的异常。
触发条件与本机定界结果
以下链路已在保留的未修改版本上逐行跟踪,不涉及算子计算正确性,也不据此认定其他环境中的所有重复注册错误均由同一条件引起。
主包
cann_ops_transformer.ops扫描cann_ops_transformer.opsentry-point 分组,从中发现实验包提供的 K、QW 接口,因此仅 import 主包也会进入实验包。实验包的 ops 初始化使用同一套发现逻辑。导入深层接口模块时,Python 先初始化父包。实验 wheel 生成的
ops/attention/__init__.py依次执行:from .indexer_prologue_k_dsl import * from .indexer_prologue_qw_dsl import *原始 QW 接口先执行
indexer_prologue_qw_op_builder._ensure_initialized(),注册 schema、Meta;随后在模块顶层执行:from ops.indexer_prologue_qw import indexer_prologue_qw as dsl_indexer_prologue_qw如果该底层模块不存在,QW 模块和 attention 父包初始化失败。已完成的 PyTorch 注册仍然保留,持有注册对象的 OpBuilder 模块已成功加载;Python 失败模块的缓存则不会作为成功导入保留。
torch_extension/cann_ops_transformer/ops/__init__.py::_load_op捕获首次 ImportError 并记录 warning,随后继续加载。实际跟踪中,第一次重复注册发生在循环继续加载 QW entry point、再次初始化 attention/QW 时。之后无条件尝试导入 graph-convert 子模块,又会触发失败父包的初始化。原始公共注册器在
OpBuilder._ensure_initialized中使用:hasattr(torch.ops.cann_ops_transformer, self.name) # self.name == "ds41.indexer_prologue_qw"该检查不会把点号拆成逐层属性访问。对已注册接口,实测:
hasattr(torch.ops.cann_ops_transformer, "ds41.indexer_prologue_qw") # False hasattr(torch.ops.cann_ops_transformer.ds41, "indexer_prologue_qw") # True因此重导时仍进入
Library.define(schema),与第一次导入留下的 schema 冲突。entry-point 分支的 graph-convert 导入只捕获 ImportError;此时重复注册产生的 RuntimeError 会继续向外传播,最终导致主包 import 失败。
定界结论:本机复现的起点是 QW 底层模块不可导入;失败后再次初始化与已有 overload 注册判断失效共同导致后续重复注册。共用
torch.ops.cann_ops_transformer本身不足以触发该错误。Related log / screenshot / 日志 / 截图
以下为本机原始版本隔离复现的完整输出(保留具体接口名称、schema 和堆栈;路径为本机隔离验证目录):
仅补充可导入的模拟 QW 底层模块后的对照输出:
Special notes for this issue / 备注
ds41.<overload>注册名,但其底层 ops 模块导入位于调用函数内部,正常 import 时不会触发本例的底层缺包条件;这不等于重复注册检查对它们一定正确。