草稿
[WIP]【PR】: 迁移 ONNX Plugin 文档至 develop #2
gentle-knight创建于 7月30日
[WIP]【PR】: 迁移 ONNX Plugin 文档至 develop #2
草稿
gentle-knight创建于 7月30日
18 个文件变更+9017-0
@@ -0,0 +1,161 @@
1+# hfqx - 个人开发笔记
2+ 
3+本目录用于记录 GE (Graph Engine) 开发过程中的环境配置、开发日志、需求分析等内容。
4+ 
5+## 目录结构
6+ 
7+```
8+hfqx/
9+├── README.md # 本文件(索引 + 环境速查)
10+├── env/ # 环境配置
11+│ ├── ge-env-setup.md # GE 开发环境完整配置指南(唯一环境文档,含踩坑记录+问题追踪)
12+│ └── issues.md # 已知问题索引(纯索引,详情见 ge-env-setup.md)
13+├── dev-log/ # 开发日志(按时间记录的事件)
14+│ ├── custom_op-samples-results.md # custom_op 5 个样例运行结果
15+│ └── atc-onnx-experiments.md # atc 编译 ONNX 实验记录
16+├── notes/ # 方法论与工作规范(非时间性)
17+│ └── dev-process-notes.md # 开发流程要点(信息同步、跨包核对等)
18+├── requirements/ # 权威需求与评审决策
19+│ └── onnx_plugin_python_requirement_analysis.md # ONNX Plugin Python 化权威 what/why
20+├── analysis/ # 通用框架参考与技术方案
21+│ ├── ge_project_framework_understanding.md # GE 项目整体框架理解(非权威参考)
22+│ ├── onnx_plugin_cpp_pipeline_analysis.md # ONNX Plugin C++ 真实链路调研
23+│ ├── onnx_plugin_python_modification_plan.md # ONNX Plugin Python 化完整评审底稿与详细设计
24+│ ├── onnx_plugin_python_developer_design.md # ONNX Plugin Python 化开发者友好版设计文档
25+│ ├── onnx_plugin_python_competitor_research.md # ONNX Plugin Python 化竞品调研与证据索引
26+│ ├── onnx_plugin_python_walkthrough_script.md # ONNX Plugin Python 化串讲稿与角色问答
27+│ ├── onnx_plugin_python_pr_2_revision_requirements.md # PR 2 评审决策与开发分期
28+│ └── archive/ # 历史方案和评审记录归档
29+└── issue/ # Issue 草稿与分析
30+ ├── issue_draft.md # Issue 草稿(提交后删除)
31+ └── doc_path_issues.md # 文档路径失效分析
32+```
33+ 
34+## 记录规则
35+ 
36+> 本规则用于避免内容散落、职责交叉、重复新建文件。新内容记录前必读。
37+ 
38+### 各目录职责
39+ 
40+| 目录 | 职责 | 内容特征 | 示例 |
41+|------|------|----------|------|
42+| `env/` | 环境状态 | 事实性、非时间性("环境是什么样") | 安装步骤、版本信息、踩坑记录、已知问题 |
43+| `dev-log/` | 开发日志 | 时间性事件("我做了什么") | 实验记录、样例运行结果、操作过程 |
44+| `notes/` | 方法论与规范 | 非时间性、抽象经验("应该怎么做") | 信息同步原则、跨包核对检查清单 |
45+| `requirements/` | 权威需求与评审决策 | 定义需求的 what/why 与验收口径 | 需求分析、评审决策 |
46+| `analysis/` | 框架参考与技术方案 | 通用非权威参考或需求的 how/design | 框架理解、技术方案 |
47+| `issue/` | Issue 相关 | 草稿与分析报告 | 待提交的 issue 草稿、问题分析 |
48+ 
49+### 记录决策流程
50+ 
51+新内容记录前,依次问自己:
52+ 
53+1. **这是环境状态吗?**(安装/配置/版本/踩坑)→ `env/`
54+2. **这是一次性事件/实验吗?**(有明确日期、记录过程)→ `dev-log/`
55+3. **这是通用经验/规范吗?**(可复用于未来活动)→ `notes/`
56+4. **这是需求或技术分析吗?**`requirements/``analysis/`
57+5. **现有文档能追加吗?****优先追加到同类文档,不新建文件**
58+ 
59+### 反模式(必须避免)
60+ 
61+-**每次遇到新内容就新建文件** → 优先追加到现有同类文档
62+-**环境信息写在 dev-log 里** → 归 `env/`
63+-**方法论写在 dev-log 里** → 归 `notes/`(日志记事件,不记原则)
64+-**同一信息多处重复** → 单一来源 + 其他文档引用
65+-**issues.md 写详细根因** → issues.md 只做纯索引,详情在 ge-env-setup.md
66+ 
67+### 信息同步原则(跨包核对)
68+ 
69+CANN 生态文档/代码分散在多个位置,单一信息源不可靠。下结论前必须核对:
70+ 
71+1. GE 仓源码能 grep 到吗?
72+2. 不能 → 是 `gen_esb` 生成产物吗?(查 `docs/zh/user_guides/es_graph/tools/gen_esb.md`
73+3. 还不能 → 在 metadef 外部头里吗?(查 `inc/graph_metadef/register/graph_register.h` 的 include 链)
74+4. 还不能 → 官方文档有提到吗?(hiascend.com)
75+5. 结论必须标注信息源("GE 仓确认" / "官方文档" / "未验证")
76+ 
77+详见 [notes/dev-process-notes.md](notes/dev-process-notes.md)。
78+ 
79+## 文档索引
80+ 
81+### 环境配置
82+ 
83+| 文档 | 说明 | 更新日期 |
84+|------|------|----------|
85+| [env/ge-env-setup.md](env/ge-env-setup.md) | GE 开发环境完整配置指南(唯一环境文档:从零安装+踩坑记录+版本真相+框架依赖+问题追踪) | 2026-07-26 |
86+| [env/issues.md](env/issues.md) | 已知问题索引(纯索引,详情见 ge-env-setup.md) | 2026-07-26 |
87+ 
88+### 开发日志
89+ 
90+| 文档 | 说明 | 更新日期 |
91+|------|------|----------|
92+| [dev-log/custom_op-samples-results.md](dev-log/custom_op-samples-results.md) | custom_op 5 个样例运行结果记录 | 2026-07-22 |
93+| [dev-log/atc-onnx-experiments.md](dev-log/atc-onnx-experiments.md) | atc 编译 ONNX 实验记录(3 个实验:Clip内置成功 / ThresholdedRelu解析成功编译失败 / MyCustomOp无插件报错) | 2026-07-26 |
94+ 
95+### 方法论
96+ 
97+| 文档 | 说明 | 更新日期 |
98+|------|------|----------|
99+| [notes/dev-process-notes.md](notes/dev-process-notes.md) | 开发流程要点(信息同步、跨包核对等) | 2026-07-26 |
100+ 
101+### 需求分析
102+ 
103+| 文档 | 说明 | 更新日期 |
104+|------|------|----------|
105+| [requirements/onnx_plugin_python_requirement_analysis.md](requirements/onnx_plugin_python_requirement_analysis.md) | ONNX Plugin Python 化权威需求、PR 派生决策与验收来源 | 2026-07-28 |
106+ 
107+### 技术分析
108+ 
109+| 文档 | 说明 | 更新日期 |
110+|------|------|----------|
111+| [analysis/ge_project_framework_understanding.md](analysis/ge_project_framework_understanding.md) | GE 项目整体框架理解,保留 parser、compiler、runtime、AscendIR 等通用事实,不作为 ONNX Plugin 需求或设计来源 | 2026-07-28 |
112+| [analysis/onnx_plugin_cpp_pipeline_analysis.md](analysis/onnx_plugin_cpp_pipeline_analysis.md) | ONNX Plugin C++ 注册、解析、回调、Operator、ES 和初始化入口的真实代码链路调研 | 2026-07-31 |
113+| [analysis/onnx_plugin_python_modification_plan.md](analysis/onnx_plugin_python_modification_plan.md) | ONNX Plugin Python 化完整评审底稿,保留详细架构、源码证据、回调分期、bridge、ES 构图和测试设计 | 2026-07-28 |
114+| [analysis/onnx_plugin_python_developer_design.md](analysis/onnx_plugin_python_developer_design.md) | ONNX Plugin Python 化开发者友好版设计文档,前置用户接口、能力映射、使用方式和实现框架 | 2026-07-31 |
115+| [analysis/onnx_plugin_python_competitor_research.md](analysis/onnx_plugin_python_competitor_research.md) | PyTorch、Torch-TensorRT 和 NVIDIA TensorRT 的用户接口调研、GE 采纳结论与源码证据 | 2026-07-31 |
116+| [analysis/onnx_plugin_python_walkthrough_script.md](analysis/onnx_plugin_python_walkthrough_script.md) | ONNX Plugin Python 化串讲会照读稿,包含展示提示、分角色打断问题、参考回答和会后行动项 | 2026-07-30 |
117+| [analysis/onnx_plugin_python_pr_2_revision_requirements.md](analysis/onnx_plugin_python_pr_2_revision_requirements.md) | PR 2 评审决策、首轮支持矩阵、注册主链要求、开发分期和待确认项 | 2026-07-31 |
118+| [analysis/archive/onnx_plugin_python_previous_20260730/](analysis/archive/onnx_plugin_python_previous_20260730/) | PR 2 修订前的方案、能力盘点和 PR 4151 评论记录归档 | 2026-07-30 |
119+ 
120+### Issue
121+ 
122+| 文档 | 说明 | 更新日期 |
123+|------|------|----------|
124+| [issue/doc_path_issues.md](issue/doc_path_issues.md) | 文档路径失效分析(57处失效引用记录) | 2026-07-24 |
125+| [issue/issue_draft.md](issue/issue_draft.md) | Issue 草稿(提交后删除) | 2026-07-24 |
126+ 
127+## 环境速查
128+ 
129+- **芯片**: Ascend910 (SoC: Ascend910_9362, NPU Arch: dav-2201)
130+- **CANN 实际版本**: 9.1.0 release (2026-07-15 构建,目录名为 cann-9.2.0)
131+- **Ops 包**: cann-910b-ops (与 toolkit 同版本)
132+- **GE 分支**: master
133+- **安装路径**: `/home/developer/Ascend/cann-9.2.0/cann-9.2.0`(唯一 CANN 安装)
134+- **环境激活**: `source /home/developer/Ascend/cann/set_env.sh`(cann 符号链接已指向 9.2.0)
135+- **Python**: 3.11.4
136+- **torch**: 2.7.1+cpu / **torch_npu**: 2.7.1.post4
137+- **TorchAir**: 7.3.0(源码编译)
138+ 
139+**重要**: CANN 目录名 cann-9.2.0 不代表实际版本是 9.2.0。内部 version.info 标注为 9.1.0。以 version.info 为准。
140+ 
141+## 常用命令
142+ 
143+```bash
144+# 激活环境(新 shell 自动执行,写在 .bashrc 中)
145+source /home/developer/Ascend/cann/set_env.sh
146+ 
147+# 查看实际 CANN 版本
148+cat $ASCEND_OPP_PATH/version.info
149+$ASCEND_HOME_PATH/query_pkg_version.sh
150+ 
151+# 编译 GE
152+bash build.sh --ge_compiler --ge_executor -j$(nproc)
153+ 
154+# 运行 custom_op 示例
155+cd examples/custom_op/args_refresh_add_custom/python && bash run.sh
156+cd examples/custom_op/compilable_add_custom && bash run.sh
157+cd examples/custom_op/data_dependent_shape_custom && bash run.sh
158+ 
159+# 查看芯片信息
160+npu-smi info
161+```
@@ -0,0 +1,1181 @@
1+# C++ ONNX Plugin Capability Inventory
2+ 
3+> **Status**: Authoritative factual source of truth, SHA-pinned and independently verified.
4+> **Purpose**: Enumerate every C++ class, registration field, callback, registry entry, and callback-visible operation that determines the ONNX plugin Pythonization boundary. This is a factual inventory, not a design document. Design decisions live in `onnx_plugin_python_modification_plan.md`.
5+> **Justification**: Separated from the design document so the design stays concise and future header/plugin changes can revalidate against this inventory without rewriting design prose.
6+ 
7+## 1. Baselines and Dirty-State Guard
8+ 
9+### 1.1 Pinned SHAs
10+ 
11+| Repository | SHA | Verified |
12+|------------|-----|----------|
13+| GE (source repo) | `f9994c80958fdd1bc7733552b2e45dc841ff37a4` | Task 1, verify-task-1 (confidence 1.0) |
14+| metadef (sibling repo) | `195fce37a0f532882fe94f20f62368f0ced10b7d` | Task 1, verify-task-1 |
15+| ops-nn (sibling repo) | `c1aaaeecb180fa3593175936063defb66b63af01` | Task 1, verify-task-1 |
16+| GE review baseline | `d82ce1f263b05d5c33e1f0b7a13a384454697395` | Code-equivalence exit 0 (code dirs unchanged) |
17+ 
18+### 1.2 Dirty-State Guard
19+ 
20+| Repo | Dirty paths | Ownership |
21+|------|-------------|-----------|
22+| GE | `.gitignore`(M), `.opencode/opencode.json`(M), `hfqx/README.md`(M), `.omo/`(??), `third_party/`(??) | User-owned, preserved byte-for-byte |
23+| metadef | `.gitignore`(M) | User-owned, preserved |
24+| ops-nn | (clean) | N/A |
25+ 
26+No repository was modified, staged, committed, reverted, or cleaned during evidence collection (Tasks 1-6) or this synthesis (Task 7).
27+ 
28+### 1.3 Evidence Sources
29+ 
30+| Task | Evidence file | Verifier verdict |
31+|------|---------------|-----------------|
32+| Task 1 | `task-1-baselines.txt` | confirmed (1.0) |
33+| Task 2 | `task-2-registration-surface.json` + `.txt` | confirmed (1.0) |
34+| Task 3 | `task-3-callback-class-surface.json` + `.txt` | confirmed (0.92) |
35+| Task 4 | `task-4-ops-nn-usage.json` + `.csv` + `.txt` | confirmed (0.97, rev 3) |
36+| Task 5 | `task-5-reachability.csv` + `.txt` | confirmed (0.95, rev 2) |
37+| Task 6 | `task-6-python-se-map.md` + `.log` | confirmed (0.92) |
38+ 
39+## 2. Methodology
40+ 
41+### 2.1 Collection Waves
42+ 
43+- **Wave 1 (Tasks 1-6)**: Each task independently derived its evidence from pinned source files. No task used another task's output as input. Verifiers independently re-extracted symbols and performed bidirectional diffs.
44+- **Wave 2 (Task 7, this document)**: Synthesizes Wave 1 evidence into a single inventory. The expected-ID manifest was independently derived from the task-5 CSV (173 rows), cross-checked against task-2 JSON (88 IDs) and task-3 JSON.
45+ 
46+### 2.2 Reachability States
47+ 
48+Every capability has exactly one reachability state:
49+ 
50+| State | Count | Meaning |
51+|-------|-------|---------|
52+| `direct ONNX` | 91 | Consumed by ONNX parser or ONNX plugin callback |
53+| `framework utility used by ONNX` | 8 | Framework utility reachable through ONNX plugin callback chain |
54+| `other-framework-only` | 29 | Consumed only by TensorFlow/Caffe parsers |
55+| `not currently consumed` | 40 | Public API with zero ONNX plugin usage |
56+| `prototype-only` | 5 | Protected API used in IR prototype definition only |
57+| `adapter-mediated ONNX` | 0 | OnnxCustomParserAdapter is mandatory dispatch, classified as direct ONNX |
58+| **Total** | **173** | |
59+ 
60+### 2.3 Negative Classification Method
61+ 
62+Every negative state (`other-framework-only`, `not currently consumed`) includes bounded search scope: `grep -rn <pattern> <scope> = 0 hits` plus `grep -rl <pattern> --include='*onnx_plugin.cpp' ops-nn = 0 files`. ops-nn non-use is never the sole N/A proof.
63+ 
64+## 3. Inclusion and Exclusion Rules
65+ 
66+### 3.1 Inclusion Rule
67+ 
68+Include classes and functions that cross or determine the Pythonization boundary: registration, loading, callback dispatch, callback-visible values/ports/descriptors/graphs, subgraph mapping, and Python bridge reuse.
69+ 
70+Classify adjacent prototype and framework capabilities explicitly rather than recursively inventorying all transitive dependencies.
71+ 
72+### 3.2 Bounded Scope
73+ 
74+| Include | Exclude |
75+|---------|---------|
76+| Registration callbacks (7 OpRegistrationData + 1 framework utility) | Protobuf wire-format internals |
77+| OpRegistrationData constructors, builders, getters | Pure internal implementation details that never cross the plugin author boundary |
78+| OpRegistrationDataImpl stored fields | Unrelated framework parsers (non-ONNX) unless they share a registered capability |
79+| OpRegistry maps, register op, getters | Auxiliary proto classes (ModelProto, StringStringEntryProto, TensorAnnotation, SparseTensorProto, TensorProto.DataLocation, TypeProto.Map, TensorProto.Segment) |
80+| OpRegistrationTbe (Instance, Finalize, RegisterParser) | Private/internal Operator methods (GetOutput, GetOperatorImplPtr, GetInputConstDataOut, GetNode, GetSubgraphImpl) |
81+| FrameworkRegistry (6 methods) | Version enum, OperatorSetIdProto |
82+| Plugin loaders (ATC, session, ACL) | |
83+| Type adaptation (Message2Operator, kOnnxOpMap, OnnxCustomParserAdapter, OpParserFactory, ConstructOriType) | |
84+| ONNX proto classes (7: NodeProto, AttributeProto, TensorProto, ValueInfoProto, GraphProto, TypeProto, TensorShapeProto) | |
85+| Callback-visible Operator method families (27) | |
86+| Callback-visible Graph/Tensor/TensorDesc/Shape/AttrValue/NamedAttrs families | |
87+| Framework utility functions (AutoMapping family) | |
88+| Tensor-typed attr value (cross-cutting) | |
89+ 
90+## 4. Class/Module Topology and Lifecycle
91+ 
92+### 4.1 High-Level Class Interaction Diagram
93+ 
94+```
95+ +-------------------+
96+ | ops-nn .so |
97+ | (93 files, 98 |
98+ | registrations) |
99+ +---------+---------+
100+ |
101+ REGISTER_CUSTOM_OP macro
102+ |
103+ v
104+ +-------------------+
105+ | OpReceiver | register.cpp:713
106+ | (static init) |
107+ +---------+---------+
108+ |
109+ pushes to registrationDatas
110+ |
111+ v
112+ +--------------------+--------------------+
113+ | Plugin Loaders |
114+ | PLUGIN-LOAD-001 (ATC) |
115+ | PLUGIN-LOAD-002 (Session) |
116+ | PLUGIN-LOAD-003 (ACL) |
117+ +--------------------+--------------------+
118+ |
119+ Finalize + Register
120+ |
121+ +---------------+---------------+
122+ | |
123+ v v
124+ +------------------+ +------------------+
125+ | OpRegistrationTbe| | OpRegistry |
126+ | ORT-R-001..003 | | OR-R-001 |
127+ | (Finalize, | | (Register) |
128+ | RegisterParser) | | |
129+ +--------+---------+ +--------+---------+
130+ | |
131+ v v
132+ +------------------+ +----------------------------------+
133+ | OpParserFactory | | OpRegistry Maps (10) |
134+ | (ONNX) | | OR-M-001..010 |
135+ | TYPE-ADAPT-004 | | (callback maps, key maps, etc.) |
136+ +--------+---------+ +----------------------------------+
137+ |
138+ v
139+ +------------------+
140+ | OnnxCustomParser |
141+ | Adapter |
142+ | TYPE-ADAPT-003 |
143+ +--------+---------+
144+ |
145+ ParseParams / ParseOpToGraph / ParseSubgraphPost
146+ |
147+ v
148+ +------------------+ +------------------+ +------------------+
149+ | ge::Operator | | ge::Graph | | ONNX Proto |
150+ | (callback target)| | (expand/subgraph)| | PROTO-001..007 |
151+ | CB-OP-FAM-001.. | | CB-GRAPH-FAM-.. | | (source side) |
152+ +------------------+ +------------------+ +------------------+
153+ | |
154+ v v
155+ +------------------+ +------------------+
156+ | ge::Tensor | | FrameworkRegistry|
157+ | ge::TensorDesc | | FR-001..006 |
158+ | ge::Shape | | FUCB-001 |
159+ | ge::AttrValue | | (subgraph IO map)|
160+ +------------------+ +------------------+
161+```
162+ 
163+### 4.2 Lifecycle Stages
164+ 
165+| Stage | Owner | When |
166+|-------|-------|------|
167+| IR definition | REG_OP macro (compile-time) | Prototype-only APIs (InputRegister, AttrRegister, etc.) |
168+| Plugin .so load | OpReceiver static init | dlopen of plugin .so |
169+| Registration | OpRegistrationTbe::Finalize + OpRegistry::Register | ATC/ACL init |
170+| Parser dispatch | OnnxCustomParserAdapter | ONNX model parsing |
171+| Callback execution | Plugin callback function | Per-node parsing |
172+| Callback target | OperatorImpl/GraphImpl shared_ptr | During callback execution |
173+ 
174+## 5. Registration Callback Matrix
175+ 
176+Seven OpRegistrationData callbacks plus one framework utility callback. Each callback has a stable ID preserved from Task 2.
177+ 
178+| ID | Callback | Signature | Impl Field | Builder | Getter | Registry Map | Reg Getter | Reachability | ops-nn Usage | Python Action | Status | Evidence |
179+|----|----------|-----------|------------|---------|--------|--------------|------------|--------------|--------------|---------------|--------|----------|
180+| CB-001 | ParseParamFunc | `(Message*, Operator&) -> Status` | parseParamFn_ | ORD-B-006 | ORD-G-007 | OR-M-002 | OR-G-003 | direct ONNX | 88 files, 92 regs | 新增 | [ ] | task-5 CSV row 2 |
181+| CB-002 | ParseParamByOpFunc | `(Operator&, Operator&) -> Status` | parse_param_by_op_fn_ | ORD-B-007 | ORD-G-008 | OR-M-003 | OR-G-004 | direct ONNX | 6 files, 6 regs | 新增 | [ ] | task-5 CSV row 3 |
182+| CB-003 | FusionParseParamFunc | `(vector<Message*>, Operator&)` | fusionParseParamFn_ | ORD-B-008 | ORD-G-009 | OR-M-004 | OR-G-005 | other-framework-only | 0 files | N/A | N/A | task-5 CSV row 4 |
183+| CB-004 | FusionParseParamByOpFunc | `(vector<Operator>, Operator&)` | fusion_parse_param_by_op_fn_ | ORD-B-009 | ORD-G-010 | OR-M-005 | OR-G-006 | other-framework-only | 0 files | N/A | N/A | task-5 CSV row 5 |
184+| CB-005 | ParseSubgraphFunc (legacy) | `(string, Graph) -> Status` | parse_subgraph_post_fn_ | ORD-B-010 [DEPRECATED] | ORD-G-011 | OR-M-006 | OR-G-009 | not currently consumed | 0 legacy regs | N/A | N/A | task-5 CSV row 6 |
185+| CB-006 | ParseOpToGraphFunc | `(Operator&, Graph&) -> Status` | parse_op_to_graph_fn_ | ORD-B-018 | ORD-G-012 | OR-M-009 | OR-G-014 | direct ONNX | 28 files, 29 regs | 新增 | [ ] | task-5 CSV row 7 |
186+| CB-007 | ParseSubgraphFuncV2 | `(AscendString, Graph) -> Status` | parse_subgraph_post_fn_v2_ | ORD-B-011 | ORD-G-013 | OR-M-010 | OR-G-010 | direct ONNX | 1 file, 1 reg | 新增 | [ ] | task-5 CSV row 8 |
187+| FUCB-001 | AutoMappingSubgraphIOIndexFunc | `(Graph, input_fn, output_fn)` | fmk_type_to_auto_mapping_subgraph_index_fun_ | FR-005 | FR-006 | (FrameworkRegistryImpl map) | FR-006 | framework utility used by ONNX | 1 file (if plugin) | 新增 | [ ] | task-5 CSV row 9 |
188+ 
189+**Deprecated alias**: CB-005 (legacy ParseSubgraphFunc) is `ATTRIBUTED_DEPRECATED`, canonicalized to CB-007 (ParseSubgraphFuncV2). Both remain separate C++ rows. Python normalization deferred to Todo 9.
190+ 
191+## 6. OpRegistrationData Constructors, Builders, and Getters
192+ 
193+### 6.1 Constructors
194+ 
195+| ID | Constructor | Deprecated | Canonical | Reachability | ops-nn | Python Action | Status | Evidence |
196+|----|-------------|-----------|-----------|--------------|--------|---------------|--------|----------|
197+| ORD-C-001 | `OpRegistrationData(const string&)` | YES | ORD-C-002 | direct ONNX | 0 (all use char_t*) | 新增 | [ ] | task-5 row 10 |
198+| ORD-C-002 | `OpRegistrationData(const char_t*)` | no | (canonical) | direct ONNX | 98 regs | 新增 | [ ] | task-5 row 11 |
199+| ORD-C-003 | `~OpRegistrationData()` | no | N/A | direct ONNX | (destructor) | N/A | N/A | task-5 row 12 |
200+ 
201+### 6.2 Builders
202+ 
203+| ID | Builder | Deprecated | Canonical | Stored Field | Reachability | ops-nn | Python Action | Status | Evidence |
204+|----|---------|-----------|-----------|--------------|--------------|--------|---------------|--------|----------|
205+| ORD-B-001 | `FrameworkType(FrameworkType)` | no | | fmk_type_ | direct ONNX | 98 regs | 新增 | [ ] | task-5 row 13 |
206+| ORD-B-002 | `OriginOpType(initializer_list<string>)` | YES | ORD-B-003 | ori_optype_set_ | direct ONNX | 0 | 新增 | [ ] | task-5 row 14 |
207+| ORD-B-003 | `OriginOpType(vector<AscendString>)` | no | | ori_optype_set_ | direct ONNX | 98 regs | 新增 | [ ] | task-5 row 15 |
208+| ORD-B-004 | `OriginOpType(string)` | YES | ORD-B-005 | ori_optype_set_ | direct ONNX | 0 | 新增 | [ ] | task-5 row 16 |
209+| ORD-B-005 | `OriginOpType(const char_t*)` | no | | ori_optype_set_ | direct ONNX | (canonical single-type) | 新增 | [ ] | task-5 row 17 |
210+| ORD-B-006 | `ParseParamsFn(ParseParamFunc)` | no | | parseParamFn_ [CB-001] | direct ONNX | 88 files | 新增 | [ ] | task-5 row 18 |
211+| ORD-B-007 | `ParseParamsByOperatorFn(ParseParamByOpFunc)` | no | | parse_param_by_op_fn_ [CB-002] | direct ONNX | 6 files | 新增 | [ ] | task-5 row 19 |
212+| ORD-B-008 | `FusionParseParamsFn(FusionParseParamFunc)` | no | | fusionParseParamFn_ [CB-003] | other-framework-only | 0 | N/A | N/A | task-5 row 20 |
213+| ORD-B-009 | `FusionParseParamsFn(FusionParseParamByOpFunc)` | no | (overload of ORD-B-008) | fusion_parse_param_by_op_fn_ [CB-004] | other-framework-only | 0 | N/A | N/A | task-5 row 21 |
214+| ORD-B-010 | `ParseSubgraphPostFn(ParseSubgraphFunc)` | YES | ORD-B-011 | parse_subgraph_post_fn_ [CB-005] | not currently consumed | 0 legacy | N/A | N/A | task-5 row 22 |
215+| ORD-B-011 | `ParseSubgraphPostFn(ParseSubgraphFuncV2)` | no | | parse_subgraph_post_fn_v2_ [CB-007] | direct ONNX | 1 file | 新增 | [ ] | task-5 row 23 |
216+| ORD-B-012 | `ImplyType(ImplyType)` | no | | imply_type_ | direct ONNX | 98 regs | 新增 | [ ] | task-5 row 24 |
217+| ORD-B-013 | `DelInputWithCond(int, string, bool)` | YES | ORD-B-014 | remove_input_configure_vec_ | other-framework-only | 0 | N/A | N/A | task-5 row 25 |
218+| ORD-B-014 | `DelInputWithCond(int, char_t*, bool)` | no | | remove_input_configure_vec_ | other-framework-only | 0 | N/A | N/A | task-5 row 26 |
219+| ORD-B-015 | `DelInputWithOriginalType(int, string)` | YES | ORD-B-016 | remove_input_configure_vec_ | other-framework-only | 0 | N/A | N/A | task-5 row 27 |
220+| ORD-B-016 | `DelInputWithOriginalType(int, char_t*)` | no | | remove_input_configure_vec_ | other-framework-only | 0 | N/A | N/A | task-5 row 28 |
221+| ORD-B-017 | `InputReorderVector(vector<int32_t>)` | no | | remove_input_configure_vec_ | other-framework-only | 0 | N/A | N/A | task-5 row 29 |
222+| ORD-B-018 | `ParseOpToGraphFn(ParseOpToGraphFunc)` | no | | parse_op_to_graph_fn_ [CB-006] | direct ONNX | 28 files | 新增 | [ ] | task-5 row 30 |
223+ 
224+### 6.3 Getters
225+ 
226+| ID | Getter | Deprecated | Canonical | Default | Reachability | ops-nn | Python Action | Status | Evidence |
227+|----|--------|-----------|-----------|---------|--------------|--------|---------------|--------|----------|
228+| ORD-G-001 | `GetImplyType()` | no | | BUILDIN | direct ONNX | (Finalize logging) | 新增 | [ ] | task-5 row 31 |
229+| ORD-G-002 | `GetOmOptype()` string | YES | ORD-G-003 | "" | direct ONNX | 0 | 新增 | [ ] | task-5 row 32 |
230+| ORD-G-003 | `GetOmOptype(AscendString&)` | no | | SUCCESS | direct ONNX | (Finalize/RegisterParser) | 新增 | [ ] | task-5 row 33 |
231+| ORD-G-004 | `GetOriginOpTypeSet()` set<string> | YES | ORD-G-005 | empty | direct ONNX | 0 | 新增 | [ ] | task-5 row 34 |
232+| ORD-G-005 | `GetOriginOpTypeSet(set<AscendString>&)` | no | | SUCCESS | direct ONNX | (Finalize) | 新增 | [ ] | task-5 row 35 |
233+| ORD-G-006 | `GetFrameworkType()` | no | | RESERVED | direct ONNX | (loaders) | 新增 | [ ] | task-5 row 36 |
234+| ORD-G-007 | `GetParseParamFn()` | no | | nullptr | direct ONNX | (RegisterParser) | 新增 | [ ] | task-5 row 37 |
235+| ORD-G-008 | `GetParseParamByOperatorFn()` | no | | nullptr | direct ONNX | (RegisterParser) | 新增 | [ ] | task-5 row 38 |
236+| ORD-G-009 | `GetFusionParseParamFn()` | no | | nullptr | other-framework-only | 0 | N/A | N/A | task-5 row 39 |
237+| ORD-G-010 | `GetFusionParseParamByOpFn()` | no | | nullptr | other-framework-only | 0 | N/A | N/A | task-5 row 40 |
238+| ORD-G-011 | `GetParseSubgraphPostFn()` legacy | no | | nullptr | direct ONNX | (via OR-G-009) | 新增 | [ ] | task-5 row 41 |
239+| ORD-G-012 | `GetParseOpToGraphFn()` | no | | nullptr | direct ONNX | (via OR-G-014) | 新增 | [ ] | task-5 row 42 |
240+| ORD-G-013 | `GetParseSubgraphPostFn(V2&)` | no | | FAILED | direct ONNX | (via OR-G-010) | 新增 | [ ] | task-5 row 43 |
241+ 
242+## 7. Registry Storage, Key, and Lookup Matrix
243+ 
244+### 7.1 OpRegistrationDataImpl Fields
245+ 
246+| ID | Field | Type | Default | Callback | Reachability | ops-nn | Python Action | Status | Evidence |
247+|----|-------|------|---------|----------|--------------|--------|---------------|--------|----------|
248+| ORDI-F-001 | fmk_type_ | FrameworkType | RESERVED | | direct ONNX | 98 regs | 新增 | [ ] | task-5 row 44 |
249+| ORDI-F-002 | ori_optype_set_ | set<string> | empty | | direct ONNX | 98 regs | 新增 | [ ] | task-5 row 45 |
250+| ORDI-F-003 | om_optype_ | string | param | | direct ONNX | 98 regs | 新增 | [ ] | task-5 row 46 |
251+| ORDI-F-004 | imply_type_ | ImplyType | BUILDIN | | direct ONNX | 98 regs | 新增 | [ ] | task-5 row 47 |
252+| ORDI-F-005 | parseParamFn_ | ParseParamFunc | nullptr | CB-001 | direct ONNX | 88 files | 新增 | [ ] | task-5 row 48 |
253+| ORDI-F-006 | parse_param_by_op_fn_ | ParseParamByOpFunc | nullptr | CB-002 | direct ONNX | 6 files | 新增 | [ ] | task-5 row 49 |
254+| ORDI-F-007 | fusionParseParamFn_ | FusionParseParamFunc | nullptr | CB-003 | other-framework-only | 0 | N/A | N/A | task-5 row 50 |
255+| ORDI-F-008 | fusion_parse_param_by_op_fn_ | FusionParseParamByOpFunc | nullptr | CB-004 | other-framework-only | 0 | N/A | N/A | task-5 row 51 |
256+| ORDI-F-009 | parse_subgraph_post_fn_ | ParseSubgraphFunc | nullptr | CB-005 | not currently consumed | 0 legacy | N/A | N/A | task-5 row 52 |
257+| ORDI-F-010 | parse_subgraph_post_fn_v2_ | ParseSubgraphFuncV2 | nullptr | CB-007 | direct ONNX | 1 file | 新增 | [ ] | task-5 row 53 |
258+| ORDI-F-011 | remove_input_configure_vec_ | vector<RemoveInputConfigure> | empty | | other-framework-only | 0 | N/A | N/A | task-5 row 54 |
259+| ORDI-F-012 | parse_op_to_graph_fn_ | ParseOpToGraphFunc | nullptr | CB-006 | direct ONNX | 28 files | 新增 | [ ] | task-5 row 55 |
260+ 
261+### 7.2 OpRegistry Maps
262+ 
263+| ID | Map | Key Pattern | Value | Reg Getter | Reachability | ops-nn | Python Action | Status | Evidence |
264+|----|-----|-------------|-------|------------|--------------|--------|---------------|--------|----------|
265+| OR-M-001 | op_run_mode_map_ | om_optype (bare) | ImplyType | OR-G-001 | other-framework-only | 0 | N/A | N/A | task-5 row 56 |
266+| OR-M-002 | op_parse_params_fn_map_ | om_optype_ori_type | ParseParamFunc | OR-G-003 | direct ONNX | 88 files | 新增 | [ ] | task-5 row 57 |
267+| OR-M-003 | parse_params_by_op_func_map_ | om_optype_ori_type | ParseParamByOpFunc | OR-G-004 | direct ONNX | 6 files | 新增 | [ ] | task-5 row 58 |
268+| OR-M-004 | fusion_op_parse_params_fn_map_ | om_optype_ori_type | FusionParseParamFunc | OR-G-005 | other-framework-only | 0 | N/A | N/A | task-5 row 59 |
269+| OR-M-005 | fusion_parse_params_by_op_fn_map_ | om_optype_ori_type | FusionParseParamByOpFunc | OR-G-006 | other-framework-only | 0 | N/A | N/A | task-5 row 60 |
270+| OR-M-006 | op_types_to_parse_subgraph_post_func_ | om_optype_FmkSerial | ParseSubgraphFunc | OR-G-009 | direct ONNX | 0 legacy (map checked) | N/A | N/A | task-5 row 61 |
271+| OR-M-007 | remove_input_configure_map_ | om_optype_ori_type | vector<RemoveInputConfigure> | OR-G-012 | other-framework-only | 0 | N/A | N/A | task-5 row 62 |
272+| OR-M-008 | origin_type_to_om_type_ | ori_type (bare) | om_optype | OR-G-013 | direct ONNX | (AdapterOpType) | 新增 | [ ] | task-5 row 63 |
273+| OR-M-009 | parse_op_to_graph_fn_map_ | om_optype_ori_type | ParseOpToGraphFunc | OR-G-014 | direct ONNX | 28 files | 新增 | [ ] | task-5 row 64 |
274+| OR-M-010 | op_types_to_parse_subgraph_post_func_v2_ | om_optype_FmkSerial | ParseSubgraphFuncV2 | OR-G-010 | direct ONNX | 1 file | 新增 | [ ] | task-5 row 65 |
275+ 
276+### 7.3 Registry Key Formats
277+ 
278+| ID | Key Format | Used By |
279+|----|-----------|---------|
280+| OR-K-001 | `om_optype + "_" + ori_type` | OR-M-002, 003, 004, 005, 007, 009 |
281+| OR-K-002 | `om_optype + "_" + FmkTypeSerialString` | OR-M-006, 010 |
282+| OR-K-003 | `om_optype` (bare) | OR-M-001 |
283+| OR-K-004 | `ori_type` (bare) | OR-M-008 |
284+ 
285+### 7.4 OpRegistry Register and Getters
286+ 
287+| ID | Operation | Reachability | ops-nn | Python Action | Status | Evidence |
288+|----|-----------|--------------|--------|---------------|--------|----------|
289+| OR-R-001 | `OpRegistry::Register` | direct ONNX | 98 regs | 新增 | [ ] | task-5 row 66 |
290+| OR-G-001 | `GetImplyType(op_type)` | other-framework-only | 0 | N/A | N/A | task-5 row 67 |
291+| OR-G-002 | `GetOpTypeByImplyType` | other-framework-only | 0 | N/A | N/A | task-5 row 68 |
292+| OR-G-003 | `GetParseParamFunc(op_type, ori_type)` | direct ONNX | 88 files | 新增 | [ ] | task-5 row 69 |
293+| OR-G-004 | `GetParseParamByOperatorFunc(ori_type)` | direct ONNX | 6 files | 新增 | [ ] | task-5 row 70 |
294+| OR-G-005 | `GetFusionParseParamFunc(op_type, ori_type)` | other-framework-only | 0 | N/A | N/A | task-5 row 71 |
295+| OR-G-006 | `GetFusionParseParamByOpFunc(op_type, ori_type)` | other-framework-only | 0 | N/A | N/A | task-5 row 72 |
296+| OR-G-007 | `GetParseSubgraphPostFunc(op_type)` | not currently consumed | 0 | N/A | N/A | task-5 row 73 |
297+| OR-G-008 | `GetParseSubgraphPostFunc(op_type, V2&)` | not currently consumed | 0 | N/A | N/A | task-5 row 74 |
298+| OR-G-009 | `GetParseSubgraphPostFunc(op_type, fmk_type)` | direct ONNX | (legacy lookup) | N/A | N/A | task-5 row 75 |
299+| OR-G-010 | `GetParseSubgraphPostFunc(op_type, fmk_type, V2&)` | direct ONNX | 1 file | 新增 | [ ] | task-5 row 76 |
300+| OR-G-011 | `GetImplyTypeByOriOpType(ori_optype)` | other-framework-only | 0 | N/A | N/A | task-5 row 77 |
301+| OR-G-012 | `GetRemoveInputConfigure(ori_optype)` | other-framework-only | 0 | N/A | N/A | task-5 row 78 |
302+| OR-G-013 | `GetOmTypeByOriOpType(ori_optype, om_type)` | direct ONNX | (AdapterOpType) | 新增 | [ ] | task-5 row 79 |
303+| OR-G-014 | `GetParseOpToGraphFunc(op_type, ori_type)` | direct ONNX | 28 files | 新增 | [ ] | task-5 row 80 |
304+ 
305+### 7.5 OpRegistrationTbe
306+ 
307+| ID | Method | Reachability | ops-nn | Python Action | Status | Evidence |
308+|----|--------|--------------|--------|---------------|--------|----------|
309+| ORT-R-001 | `Instance()` | direct ONNX | (singleton) | N/A | N/A | task-5 row 81 |
310+| ORT-R-002 | `Finalize` | direct ONNX | 98 regs | N/A | N/A | task-5 row 82 |
311+| ORT-R-003 | `RegisterParser` | direct ONNX | (adapter reg) | N/A | N/A | task-5 row 83 |
312+ 
313+### 7.6 FrameworkRegistry
314+ 
315+| ID | Method | Reachability | ops-nn | Python Action | Status | Evidence |
316+|----|--------|--------------|--------|---------------|--------|----------|
317+| FR-001 | `FrameworkRegistry(copy) = delete` | framework utility used by ONNX | (singleton integrity) | N/A | N/A | task-5 row 84 |
318+| FR-002 | `operator=(copy) = delete` | framework utility used by ONNX | (singleton integrity) | N/A | N/A | task-5 row 85 |
319+| FR-003 | `~FrameworkRegistry()` | framework utility used by ONNX | (destructor) | N/A | N/A | task-5 row 86 |
320+| FR-004 | `Instance()` | framework utility used by ONNX | 1 file (if plugin) | N/A | N/A | task-5 row 87 |
321+| FR-005 | `AddAutoMappingSubgraphIOIndexFunc` | framework utility used by ONNX | (static init) | 新增 | [ ] | task-5 row 88 |
322+| FR-006 | `GetAutoMappingSubgraphIOIndexFunc` | framework utility used by ONNX | 1 file (if plugin) | 新增 | [ ] | task-5 row 89 |
323+ 
324+## 8. Plugin Loader and Type Adaptation Matrix
325+ 
326+| ID | Capability | Reachability | ops-nn | Python Action | Status | Evidence |
327+|----|-----------|--------------|--------|---------------|--------|----------|
328+| PLUGIN-LOAD-001 | LoadCustomOpLib (ATC) | direct ONNX | 98 regs | N/A | N/A | task-5 row 96 |
329+| PLUGIN-LOAD-002 | TBEPluginManager::LoadCustomOpLib (session) | direct ONNX | 98 regs | N/A | N/A | task-5 row 97 |
330+| PLUGIN-LOAD-003 | AclGraphParserUtil::AclParserInitialize | direct ONNX | 98 regs | N/A | N/A | task-5 row 98 |
331+| TYPE-ADAPT-001 | Message2Operator::ParseOperatorAttrs | direct ONNX | 6 files (ByOp path) | N/A | N/A | task-5 row 99 |
332+| TYPE-ADAPT-002 | kOnnxOpMap | direct ONNX | (built-in types) | N/A | N/A | task-5 row 100 |
333+| TYPE-ADAPT-003 | OnnxCustomParserAdapter | direct ONNX | (mandatory dispatch) | N/A | N/A | task-5 row 101 |
334+| TYPE-ADAPT-004 | OpParserFactory (ONNX) | direct ONNX | (factory) | N/A | N/A | task-5 row 102 |
335+| TYPE-ADAPT-005 | ConstructOriType | direct ONNX | (domain prefix) | N/A | N/A | task-5 row 103 |
336+| AUTO-MAP-001 | AutoMappingSubgraphIndexByDataNodeAndOutputNodesInfo | framework utility used by ONNX | 1 file (if plugin) | 新增 | [ ] | task-5 row 90 |
337+| AUTO-MAP-002 | AutoMappingSubgraphIndex (register.cpp) | other-framework-only | 0 | N/A | N/A | task-5 row 91 |
338+| AUTO-MAP-003 | AutoMappingByOpFn | other-framework-only | 0 | N/A | N/A | task-5 row 92 |
339+| AUTO-MAP-004 | AutoMappingFn [DEPRECATED] | other-framework-only | 0 | N/A | N/A | task-5 row 93 |
340+| AUTO-MAP-005 | AutoMappingByOpFnDynamic | other-framework-only | 0 | N/A | N/A | task-5 row 94 |
341+| AUTO-MAP-006 | AutoMappingFnDynamic [DEPRECATED] | other-framework-only | 0 | N/A | N/A | task-5 row 95 |
342+ 
343+## 9. ONNX Proto Class Matrix
344+ 
345+| ID | Proto Class | Reachability | Semantics | ops-nn | Python Action | Status | Evidence |
346+|----|-------------|--------------|-----------|--------|---------------|--------|----------|
347+| PROTO-001 | NodeProto | direct ONNX | Source-side operator (input/output/attribute/op_type) | 88 ParseParamsFn callbacks | 新增 | [ ] | task-5 row 104 |
348+| PROTO-002 | AttributeProto | direct ONNX | Node attributes (f/i/s/t/floats/ints/strings/tensors/graphs) | all attr-extracting callbacks | 新增 | [ ] | task-5 row 105 |
349+| PROTO-003 | TensorProto | direct ONNX | Initializer parsing, Message2Operator attr adaptation | (initializer parsing) | 新增 | [ ] | task-5 row 106 |
350+| PROTO-004 | ValueInfoProto | direct ONNX | IO value info extraction | (ParseInput/ParseOutput) | 新增 | [ ] | task-5 row 107 |
351+| PROTO-005 | GraphProto | direct ONNX | Graph-level iteration | (ParseAllNodeProto) | 新增 | [ ] | task-5 row 108 |
352+| PROTO-006 | TypeProto | direct ONNX | Type extraction from ValueInfoProto | (IO type inference) | 新增 | [ ] | task-5 row 109 |
353+| PROTO-007 | TensorShapeProto | direct ONNX | Shape extraction from TypeProto | (IO shape inference) | 新增 | [ ] | task-5 row 110 |
354+ 
355+## 10. Callback-Visible Operator Method Family Matrix
356+ 
357+27 method families covering 80+ method symbols. Each family names all members without collapsing.
358+ 
359+| ID | Family | Members | Reachability | ops-nn | Python Counterpart | Action | Status | Evidence |
360+|----|--------|---------|--------------|--------|-------------------|--------|--------|----------|
361+| CB-OP-FAM-001 | identity | IsEmpty, GetName, GetOpType | direct ONNX | GetName: 10 calls/5 files | ge.graph.Node (partial) | 扩展 | [ ] | task-5 row 111 |
362+| CB-OP-FAM-002 | graph-wiring | SetInput (multiple overloads by name/index/handler), AddControlInput | direct ONNX | SetInput: 29 calls; AddControlInput: 2 calls | ge.es.GraphBuilder | 复用 | [x] | task-5 row 112 |
363+| CB-OP-FAM-003 | const-data-read | GetInputConstData | not currently consumed | 0 hits | None | 新增 | [ ] | task-5 row 113 |
364+| CB-OP-FAM-004 | fixed-input-desc | GetInputDesc (by name + by index), GetInputDescByName, TryGetInputDesc | direct ONNX | GetInputDesc: 10 calls | ge.graph.Node.get_input_desc | 复用 | [x] | task-5 row 114 |
365+| CB-OP-FAM-005 | fixed-input-desc-update | UpdateInputDesc (by name + by index) | direct ONNX | 7 calls | ge.graph.Node.update_input_desc | 复用 | [x] | task-5 row 115 |
366+| CB-OP-FAM-006 | fixed-output-desc | GetOutputDesc (by name + by index), GetOutputDescByName | direct ONNX | 15 calls | ge.graph.Node.get_output_desc | 复用 | [x] | task-5 row 116 |
367+| CB-OP-FAM-007 | fixed-output-desc-update | UpdateOutputDesc (by name + by index) | direct ONNX | 11 calls | ge.graph.Node.update_output_desc | 复用 | [x] | task-5 row 117 |
368+| CB-OP-FAM-008 | dynamic-input-desc | GetDynamicInputDesc (by name+index) | not currently consumed | 0 hits | None | 新增 | [ ] | task-5 row 118 |
369+| CB-OP-FAM-009 | dynamic-input-desc-update | UpdateDynamicInputDesc (by name+index) | not currently consumed | 0 hits | None | 新增 | [ ] | task-5 row 119 |
370+| CB-OP-FAM-010 | dynamic-output-desc | GetDynamicOutputDesc (by name+index) | not currently consumed | 0 hits | None | 新增 | [ ] | task-5 row 120 |
371+| CB-OP-FAM-011 | dynamic-output-desc-update | UpdateDynamicOutputDesc (by name+index) | not currently consumed | 0 hits | None | 新增 | [ ] | task-5 row 121 |
372+| CB-OP-FAM-012 | dynamic-io-count | GetDynamicInputNum, GetDynamicOutputNum | not currently consumed | 0 hits | None | 新增 | [ ] | task-5 row 122 |
373+| CB-OP-FAM-013 | infer-shape-type | InferShapeAndType, SetInferenceContext, GetInferenceContext | not currently consumed | 0 hits | None | N/A | N/A | task-5 row 123 |
374+| CB-OP-FAM-014 | attr-verify | VerifyAllAttr, VerifyAll | not currently consumed | 0 hits | None | N/A | N/A | task-5 row 124 |
375+| CB-OP-FAM-015 | io-size | GetInputsSize, GetOutputsSize | direct ONNX | 4+3 calls | ge.graph.Node (partial) | 扩展 | [ ] | task-5 row 125 |
376+| CB-OP-FAM-016 | attr-names | GetAllAttrNamesAndTypes, GetAllIrAttrNamesAndTypes | not currently consumed | 0 hits | None | N/A | N/A | task-5 row 126 |
377+| CB-OP-FAM-017 | attr-set-get | SetAttr (all type overloads), GetAttr (all type overloads) | direct ONNX | 342+154 calls | ge.graph.Node.set_attr/get_attr (via _AttrValue) | 扩展 | [ ] | task-5 row 127 |
378+| CB-OP-FAM-018 | port-attr | SetInputAttr, GetInputAttr, SetOutputAttr, GetOutputAttr | not currently consumed | 0 hits | None | N/A | N/A | task-5 row 128 |
379+| CB-OP-FAM-019 | break-connect | BreakConnect | direct ONNX | (parser dispatch) | None | N/A | N/A | task-5 row 129 |
380+| CB-OP-FAM-020 | subgraph-access | GetSubgraphNamesCount, GetSubgraphNames, GetSubgraphBuilder, GetSubgraph, GetDynamicSubgraphBuilder, GetDynamicSubgraph | not currently consumed | 0 hits | None | 新增 | [ ] | task-5 row 130 |
381+| CB-OP-FAM-021 | prototype-io-register | InputRegister, OptionalInputRegister, OutputRegister | prototype-only | 0 hits | None | N/A | N/A | task-5 row 131 |
382+| CB-OP-FAM-022 | prototype-dynamic-register | DynamicInputRegister (2 public overloads), DynamicInputRegisterByIndex, DynamicOutputRegister (2 public overloads) | direct ONNX | 30+30 calls (2-arg form) | None | 新增 | [ ] | task-5 row 132 |
383+| CB-OP-FAM-023 | prototype-attr-register | AttrRegister, RequiredAttrRegister, RequiredAttrWithTypeRegister | prototype-only | 0 hits | None | N/A | N/A | task-5 row 133 |
384+| CB-OP-FAM-024 | prototype-datatype-register | DataTypeRegister (3 overloads: TensorType, ListTensorType, Promote) | prototype-only | 0 hits | None | N/A | N/A | task-5 row 134 |
385+| CB-OP-FAM-025 | prototype-func-register | InferFuncRegister, VerifierFuncRegister, InferFormatFuncRegister | prototype-only | 0 hits | None | N/A | N/A | task-5 row 135 |
386+| CB-OP-FAM-026 | prototype-subgraph-register | SubgraphRegister, SubgraphCountRegister, SetSubgraphBuilder, SetSubgraphInstanceName | prototype-only | 0 hits | None | N/A | N/A | task-5 row 136 |
387+| CB-OP-FAM-027 | internal-private | GetOutput, GetOperatorImplPtr, GetInputConstDataOut, GetNode, GetSubgraphImpl | not currently consumed | 0 hits | None | N/A | N/A | task-5 row 137 |
388+ 
389+**Type aliases** (18 canonical + 1 typo alias): OpInt, OpFloat, OpString, OpAscendString, OpBool, OpTensor, OpType, OpNamedAttrs, OpListInt, OpListFloat, OpListString, OpListAscendString, OpListBool, OpListTensor, OpBytes, OpListListInt, OpListType, OpListNamedAttrs. The alias `OpListAcendString` (operator.h:84, typo for OpListAscendString) is semantically identical and recorded here canonically.
390+ 
391+**SetInput overload note**: The public SetInput overloads vary by parameter combination (Operator&, name, index, OutHandler). The exact count differs by access level (public vs protected). The family is fully covered; individual overload counts are not material to the Pythonization boundary.
392+ 
393+## 11. Callback-Visible Graph, Tensor, TensorDesc, Shape, AttrValue, NamedAttrs Matrix
394+ 
395+### 11.1 Graph Method Families (12)
396+ 
397+| ID | Family | Members | Reachability | ops-nn | Python Counterpart | Action | Status | Evidence |
398+|----|--------|---------|--------------|--------|-------------------|--------|--------|----------|
399+| CB-GRAPH-FAM-001 | construction | IsValid, SetValid | not currently consumed | 0 | ge.graph.Graph | 复用 | [x] | task-5 row 138 |
400+| CB-GRAPH-FAM-002 | graph-wiring | SetInputs, SetOutputs (4 overloads), SetTargets | direct ONNX | 29 SetInputs calls | ge.graph.Graph / ge.es.GraphBuilder | 复用 | [x] | task-5 row 139 |
401+| CB-GRAPH-FAM-003 | op-management | AddOp, FindOpByName, FindOpByType, GetAllOpName | not currently consumed | 0 | ge.graph.Graph | 复用 | [x] | task-5 row 140 |
402+| CB-GRAPH-FAM-004 | node-access | GetAllNodes, GetDirectNode, AddNodeByOp, RemoveNode (2 overloads), RemoveEdge, AddDataEdge, AddControlEdge, FindNodeByName | not currently consumed | 0 | ge.graph.Graph | 复用 | [x] | task-5 row 141 |
403+| CB-GRAPH-FAM-005 | subgraph-access | GetAllSubgraphs, GetSubGraph, AddSubGraph, RemoveSubgraph, GetParentGraph, GetParentNode | not currently consumed | 0 | ge.graph.Graph | 复用 | [x] | task-5 row 142 |
404+| CB-GRAPH-FAM-006 | identity | GetName | not currently consumed | 0 | ge.graph.Graph | 复用 | [x] | task-5 row 143 |
405+| CB-GRAPH-FAM-007 | iteration | SetNeedIteration | not currently consumed | 0 | None | N/A | N/A | task-5 row 144 |
406+| CB-GRAPH-FAM-008 | serialization | SaveToFile, LoadFromFile, LoadFromSerializedModelArray, SaveToMem, LoadFromMem | not currently consumed | 0 | None | N/A | N/A | task-5 row 145 |
407+| CB-GRAPH-FAM-009 | copy | CopyFrom | not currently consumed | 0 | None | N/A | N/A | task-5 row 146 |
408+| CB-GRAPH-FAM-010 | construct | ConstructFromInputs | not currently consumed | 0 | None | N/A | N/A | task-5 row 147 |
409+| CB-GRAPH-FAM-011 | attr | SetAttr, GetAttr | not currently consumed | 0 | ge.graph.Graph | 复用 | [x] | task-5 row 148 |
410+| CB-GRAPH-FAM-012 | dump | Dump, DumpToFile | not currently consumed | 0 | ge.graph.Graph | 复用 | [x] | task-5 row 149 |
411+ 
412+### 11.2 Tensor Method Families (10 + 1 cross-cutting)
413+ 
414+| ID | Family | Members | Reachability | ops-nn | Python Counterpart | Action | Status | Evidence |
415+|----|--------|---------|--------------|--------|-------------------|--------|--------|----------|
416+| CB-TENSOR-FAM-001 | desc-access | GetTensorDesc, SetTensorDesc | direct ONNX | 29 constructions | ge.graph.Tensor | 复用 | [x] | task-5 row 150 |
417+| CB-TENSOR-FAM-002 | data-access | GetData (const + non-const), GetSize, ResetData (2 overloads) | direct ONNX | 29 constructions | ge.graph.Tensor | 复用 | [x] | task-5 row 151 |
418+| CB-TENSOR-FAM-003 | data-set | SetData (multiple overloads varying by data type and ownership) | direct ONNX | 29 constructions | ge.graph.Tensor | 复用 | [x] | task-5 row 152 |
419+| CB-TENSOR-FAM-004 | validity | IsValid | not currently consumed | 0 | ge.graph.Tensor | 复用 | [x] | task-5 row 153 |
420+| CB-TENSOR-FAM-005 | shape-access | SetOriginShapeDimNum, GetOriginShapeDimNum, SetOriginShapeDim, GetOriginShapeDim, SetShapeDimNum, GetShapeDimNum, SetShapeDim, GetShapeDim | direct ONNX | 29 constructions | ge.graph.Tensor | 复用 | [x] | task-5 row 154 |
421+| CB-TENSOR-FAM-006 | format-access | SetOriginFormat, GetOriginFormat, SetFormat, GetFormat | direct ONNX | 29 constructions | ge.graph.Tensor | 复用 | [x] | task-5 row 155 |
422+| CB-TENSOR-FAM-007 | dtype-access | SetDataType, GetDataType | direct ONNX | 29 constructions | ge.graph.Tensor | 复用 | [x] | task-5 row 156 |
423+| CB-TENSOR-FAM-008 | placement-access | SetPlacement, GetPlacement | not currently consumed | 0 | ge.graph.Tensor | 复用 | [x] | task-5 row 157 |
424+| CB-TENSOR-FAM-009 | expand-dims | SetExpandDimsRule, GetExpandDimsRule | not currently consumed | 0 | None | N/A | N/A | task-5 row 158 |
425+| CB-TENSOR-FAM-010 | clone | Clone | not currently consumed | 0 | None (copy blocked) | N/A | N/A | task-5 row 159 |
426+| CB-TENSOR-ATTR-001 | SetAttr with Tensor value | (cross-cutting: Tensor passed as attr value) | direct ONNX | 6 files | ge.graph.Tensor (via _AttrValue.set_tensor) | 复用 | [x] | task-5 row 174 |
427+ 
428+**SetData overload note**: The public SetData overloads vary by data type (uint8_t, int8_t, etc.), buffer ownership, and size parameter. The exact count differs by access level. The family is fully covered.
429+ 
430+### 11.3 TensorDesc Method Families (11)
431+ 
432+| ID | Family | Members | Reachability | ops-nn | Python Counterpart | Action | Status | Evidence |
433+|----|--------|---------|--------------|--------|-------------------|--------|--------|----------|
434+| CB-TDESC-FAM-001 | update | Update | direct ONNX | 15 constructions | ge.graph.TensorDesc | 复用 | [x] | task-5 row 160 |
435+| CB-TDESC-FAM-002 | shape | GetShape, SetShape, SetUnknownDimNumShape, SetShapeRange, GetShapeRange, GetOriginShape, SetOriginShape | direct ONNX | 15 constructions | ge.graph.TensorDesc | 复用 | [x] | task-5 row 161 |
436+| CB-TDESC-FAM-003 | format | GetFormat, SetFormat, GetOriginFormat, SetOriginFormat | direct ONNX | 15 constructions | ge.graph.TensorDesc | 复用 | [x] | task-5 row 162 |
437+| CB-TDESC-FAM-004 | dtype | GetDataType, SetDataType | direct ONNX | 15 constructions | ge.graph.TensorDesc | 复用 | [x] | task-5 row 163 |
438+| CB-TDESC-FAM-005 | name | GetName, SetName | not currently consumed | 0 | ge.graph.TensorDesc | 复用 | [x] | task-5 row 164 |
439+| CB-TDESC-FAM-006 | size | SetSize, GetSize | not currently consumed | 0 | None | N/A | N/A | task-5 row 165 |
440+| CB-TDESC-FAM-007 | real-dim-cnt | GetRealDimCnt, SetRealDimCnt | not currently consumed | 0 | None | N/A | N/A | task-5 row 166 |
441+| CB-TDESC-FAM-008 | placement | SetPlacement, GetPlacement | not currently consumed | 0 | None | N/A | N/A | task-5 row 167 |
442+| CB-TDESC-FAM-009 | const-data | SetConstData, GetConstData | not currently consumed | 0 | None | N/A | N/A | task-5 row 168 |
443+| CB-TDESC-FAM-010 | expand-dims | SetExpandDimsRule, GetExpandDimsRule | not currently consumed | 0 | None | N/A | N/A | task-5 row 169 |
444+| CB-TDESC-FAM-011 | reuse-input | SetReuseInputIndex | not currently consumed | 0 | None | N/A | N/A | task-5 row 170 |
445+ 
446+### 11.4 Shape, AttrValue, NamedAttrs
447+ 
448+| ID | Family | Members | Reachability | ops-nn | Python Counterpart | Action | Status | Evidence |
449+|----|--------|---------|--------------|--------|-------------------|--------|--------|----------|
450+| CB-SHAPE-FAM-001 | shape-access | GetDimNum, GetDim, SetDim, GetDims, GetShapeSize | direct ONNX | 29+15 constructions | ge.graph.Shape (list subclass) | 复用 | [x] | task-5 row 171 |
451+| CB-ATTR-FAM-001 | set-get-value | SetAttrValue (13 overloads), GetAttrValue (13 overloads), GetValue (3+template) | direct ONNX | 342+154 calls | ge.graph._AttrValue (private) | 扩展 | [ ] | task-5 row 172 |
452+| CB-NAMEDATTR-FAM-001 | named-attrs-access | SetName, GetName, GetItem | not currently consumed | 0 | None | 新增 | [ ] | task-5 row 173 |
453+ 
454+## 12. Attr/Type Matrix
455+ 
456+### 12.1 C++ vs ONNX AttributeProto Category Comparison
457+ 
458+| C++ Category | ONNX Category | Mapping | Notes |
459+|--------------|---------------|---------|-------|
460+| scalar int (int64_t) | INT | direct | deprecated int32/uint32 overloads |
461+| scalar float (float32_t) | FLOAT | direct | |
462+| scalar bool | INT | lossy | ONNX has no BOOL attr type |
463+| scalar string (AscendString) | STRING | direct | ONNX STRING is bytes |
464+| scalar Tensor | TENSOR | direct | requires proto-to-Tensor conversion |
465+| bytes (OpBytes) | STRING | direct | ONNX STRING is bytes |
466+| scalar DataType | INT | lossy | ONNX has no DataType attr type |
467+| scalar NamedAttrs | no equivalent | none | C++ only |
468+| list int (vector<int64_t>) | INTS | direct | deprecated int32/uint32 overloads |
469+| list float (vector<float32_t>) | FLOATS | direct | |
470+| list bool (vector<bool>) | INTS | lossy | ONNX has no BOOLS attr type |
471+| list string (vector<AscendString>) | STRINGS | direct | |
472+| list Tensor (vector<Tensor>) | TENSORS | direct | requires proto-to-Tensor conversion |
473+| nested list int (vector<vector<int64_t>>) | no equivalent | none | C++ only |
474+| list DataType (vector<DataType>) | no equivalent | none | C++ only |
475+| list NamedAttrs (vector<NamedAttrs>) | no equivalent | none | C++ only |
476+| no equivalent | GRAPH | none | maps to subgraph registration, not SetAttr |
477+| no equivalent | GRAPHS | none | maps to dynamic subgraph registration |
478+| no equivalent | SPARSE_TENSOR | none | C++ has no sparse-tensor attr type |
479+| no equivalent | SPARSE_TENSORS | none | C++ has no list-of-sparse-tensor type |
480+| AttrValue (generic) | any | indirect | type-erased wrapper |
481+ 
482+**TensorDesc attr non-support**: `Operator::SetAttr(TensorDesc)` and `Operator::GetAttr(TensorDesc)` overloads do NOT exist in `operator.h`. While `VT_TENSOR_DESC` and `VT_LIST_TENSOR_DESC` exist in the Python `AttrValueType` enum, they are not exposed as C++ `Operator::SetAttr/GetAttr` overloads. TensorDesc is not a C++ attr value category for `Operator::SetAttr`.
483+ 
484+### 12.2 Python AttrValueType Coverage (22 meaningful categories)
485+ 
486+The Python `AttrValueType` enum (types.py:131-158) has 24 total members including `VT_NONE=0` and `VT_LIST_BASE=1000`. Excluding these two base constants, there are **22 meaningful type categories**.
487+ 
488+| AttrValueType | _AttrValue Method | C Wrapper Function | Status | Action |
489+|---------------|-------------------|-------------------|--------|--------|
490+| VT_STRING (1) | set/get_string | SetString/GetString | reuse | [x] |
491+| VT_FLOAT (2) | set/get_float | SetFloat/GetFloat | reuse | [x] |
492+| VT_BOOL (3) | set/get_bool | SetBool/GetBool | reuse | [x] |
493+| VT_INT (4) | set/get_int | SetInt/GetInt | reuse | [x] |
494+| VT_TENSOR_DESC (5) | MISSING | MISSING | extend (gap) | [ ] |
495+| VT_TENSOR (6) | set/get_tensor | SetTensor/GetTensor | reuse | [x] |
496+| VT_BYTES (7) | MISSING | MISSING | extend (gap) | [ ] |
497+| VT_GRAPH (8) | MISSING | MISSING | extend (gap) | [ ] |
498+| VT_NAMED_ATTRS (9) | MISSING | MISSING | extend (gap) | [ ] |
499+| VT_LIST_LIST_INT (10) | MISSING | MISSING | extend (gap) | [ ] |
500+| VT_DATA_TYPE (11) | set/get_data_type | SetDataType/GetDataType | reuse | [x] |
501+| VT_LIST_LIST_FLOAT (12) | MISSING | MISSING | extend (gap) | [ ] |
502+| VT_LIST_STRING (1001) | set/get_list_string | SetListString/GetListString | reuse | [x] |
503+| VT_LIST_FLOAT (1002) | set/get_list_float | SetListFloat/GetListFloat | reuse | [x] |
504+| VT_LIST_BOOL (1003) | set/get_list_bool | SetListBool/GetListBool | reuse | [x] |
505+| VT_LIST_INT (1004) | set/get_list_int | SetListInt/GetListInt | reuse | [x] |
506+| VT_LIST_TENSOR_DESC (1005) | MISSING | MISSING | extend (gap) | [ ] |
507+| VT_LIST_TENSOR (1006) | MISSING | MISSING | extend (gap) | [ ] |
508+| VT_LIST_GRAPH (1008) | MISSING | MISSING | extend (gap) | [ ] |
509+| VT_LIST_BYTES (1007) | MISSING | MISSING | extend (gap) | [ ] |
510+| VT_LIST_DATA_TYPE (1011) | set/get_list_data_type | SetListDataType/GetListDataType | reuse | [x] |
511+| VT_LIST_NAMED_ATTRS (1009) | MISSING | MISSING | extend (gap) | [ ] |
512+ 
513+**Summary**: 11 types reuse, 11 types gap (extend). The gap is in BOTH the Python `_AttrValue` class AND the C wrapper `pygraph_wrapper.py` / `c_anyvalue.cc`. The C++ source `c_anyvalue.cc` uses `ge::AttrValue::SetAttrValue`/`GetAttrValue` template dispatch and may support more types internally, but the C wrapper does not expose them.
514+ 
515+## 13. Five-Concept IO Ownership Matrix
516+ 
517+| # | Concept | Owner | Lifecycle | Relevance | C++ Methods | Python Counterpart | Action | Status |
518+|---|---------|-------|-----------|-----------|-------------|-------------------|--------|--------|
519+| 1 | SOURCE-ONNX-IO | Protobuf Message | ONNX model source (read-only) | direct ONNX | NodeProto.input/output (repeated string) | None | 新增 | [ ] |
520+| 2 | TARGET-REQUIRED-INPUT | IR definition (REG_OP) | Compile-time | prototype-only | InputRegister (prototype), GetInputDesc/UpdateInputDesc (callback) | ge.graph.Node.get/update_input_desc | 复用 | [x] |
521+| 3 | TARGET-OPTIONAL-INPUT | IR definition (REG_OP) | Compile-time | prototype-only | OptionalInputRegister (prototype), TryGetInputDesc (callback) | ge.graph.Node.get_input_desc | 复用 | [x] |
522+| 4 | TARGET-OUTPUT | IR definition (REG_OP) | Compile-time | prototype-only | OutputRegister (prototype), GetOutputDesc/UpdateOutputDesc (callback) | ge.graph.Node.get/update_output_desc | 复用 | [x] |
523+| 5 | DYNAMIC-IO-GROUPS | IR definition + runtime expansion | Prototype + callback | direct ONNX (callback-visible) | DynamicInputRegister/DynamicOutputRegister (prototype+public), GetDynamicInputDesc/UpdateDynamicInputDesc/GetDynamicOutputDesc/UpdateDynamicOutputDesc, GetDynamicInputNum/GetDynamicOutputNum | None | 新增 | [ ] |
524+ 
525+## 14. Graph/Subgraph Matrix
526+ 
527+| Capability | C++ Surface | Reachability | ops-nn | Python Counterpart | Action | Status |
528+|-----------|-------------|--------------|--------|-------------------|--------|--------|
529+| Expanded-graph boundary | ParseOpToGraphFn callback receives `(const Operator&, Graph&)` | direct ONNX | 28 files | ge.es.GraphBuilder | 复用 | [x] |
530+| Graph wiring (Operator level) | SetInput (multiple overloads), AddControlInput | direct ONNX | 29+2 calls | ge.es.GraphBuilder | 复用 | [x] |
531+| Graph wiring (Graph level) | SetInputs, SetOutputs (4 overloads), SetTargets, AddDataEdge, AddControlEdge, RemoveEdge | direct ONNX | 29 SetInputs calls | ge.graph.Graph | 复用 | [x] |
532+| Subgraph registration | SubgraphRegister/SubgraphCountRegister/SetSubgraphBuilder (REG_OP) | prototype-only | 0 | None | N/A | N/A |
533+| Subgraph post-mapping (legacy) | ParseSubgraphPostFn(ParseSubgraphFunc) `(string, Graph)` | not currently consumed | 0 legacy | None | N/A | N/A |
534+| Subgraph post-mapping (V2) | ParseSubgraphPostFn(ParseSubgraphFuncV2) `(AscendString, Graph)` | direct ONNX | 1 file (If) | None | 新增 | [ ] |
535+| Framework auto-mapping | AutoMappingSubgraphIOIndexFunc registered for ONNX | framework utility used by ONNX | 1 file (If) | None | 新增 | [ ] |
536+| Legacy/V2 ABI difference | `std::string` vs `ge::AscendString` for subgraph_name | (semantic equivalence, ABI-only) | | Python normalization deferred to Todo 9 | | |
537+ 
538+## 15. ops-nn Usage Summary (93 Files, 98 Registrations)
539+ 
540+### 15.1 Totals
541+ 
542+| Metric | Value | Source |
543+|--------|-------|--------|
544+| Tracked `*onnx_plugin.cpp` files | 93 | task-4 JSON, verify-task-4 (confirmed) |
545+| `REGISTER_CUSTOM_OP` registrations | 98 | task-4 JSON, verify-task-4 (confirmed) |
546+| Files with 2 registrations | 5 (softmax, bounding_box_decode, resize, topk, dynamic_quant) | task-4 TXT section 16 |
547+| Files with 1 registration | 88 | task-4 TXT section 16 |
548+ 
549+### 15.2 Callback Builder Counts (reproduce 88/6/28/1)
550+ 
551+| Builder | File Count | Registration Count | Verified |
552+|---------|-----------|-------------------|----------|
553+| ParseParamsFn | 88 | 92 | verify-task-4 (confirmed) |
554+| ParseParamsByOperatorFn | 6 | 6 | verify-task-4 (confirmed) |
555+| ParseOpToGraphFn | 28 | 29 | verify-task-4 (confirmed) |
556+| ParseSubgraphPostFn | 1 | 1 | verify-task-4 (confirmed) |
557+| FusionParseParamFn | 0 | 0 | verify-task-4 (confirmed) |
558+| **Total param-parsing** | | **98** | |
559+ 
560+### 15.3 ImplyType Distribution
561+ 
562+| ImplyType | Count |
563+|-----------|-------|
564+| TVM | 95 |
565+| CUSTOM | 1 |
566+| GELOCAL | 1 |
567+| AI_CPU | 1 |
568+ 
569+### 15.4 Operator Method Aggregate (file-level)
570+ 
571+| Method | Call Count |
572+|--------|-----------|
573+| SetAttr | 83 (registrations) |
574+| GetAttr | 36 |
575+| DynamicInputRegister | 31 |
576+| DynamicOutputRegister | 31 |
577+| GetOutputDesc | 8 |
578+| UpdateOutputDesc | 8 |
579+| GetInputDesc | 7 |
580+| UpdateInputDesc | 7 |
581+| GetName | 5 (registrations, 10 calls in 5 files) |
582+| GetInputsSize | 4 |
583+| GetOutputsSize | 3 |
584+ 
585+### 15.5 Tensor Attr Files (6)
586+ 
587+1. `common/src/framework/Int8Transpose_onnx_plugin.cpp`
588+2. `common/src/framework/constantofshape_onnx_plugin.cpp`
589+3. `common/src/framework/int8_giveint_tensorfill_onnx_plugin.cpp`
590+4. `common/src/framework/topk_onnx_plugin.cpp`
591+5. `index/gather_v2/framework/compress_onnx_plugin.cpp`
592+6. `index/gather_v2/framework/gather_onnx_plugin.cpp`
593+ 
594+### 15.6 TensorDesc Files (7, explicit declaration only)
595+ 
596+1. `common/src/framework/average_pool_onnx_plugin.cpp`
597+2. `common/src/framework/constantofshape_onnx_plugin.cpp`
598+3. `common/src/framework/int8_conv_relu_onnx_plugin.cpp`
599+4. `common/src/framework/int8_fc_onnx_plugin.cpp`
600+5. `common/src/framework/lppool_onnx_plugin.cpp`
601+6. `common/src/framework/max_pool_onnx_plugin.cpp`
602+7. `quant/ascend_anti_quant_v2/framework/npu_anti_quant_onnx_plugin.cpp`
603+ 
604+Note: 3 additional files (ascend_dequant, ascend_quant, int8_quantize) use `auto tensor = op.GetInputDesc(i)` without explicit TensorDesc declaration and are excluded.
605+ 
606+### 15.7 Advanced Dynamic Overload Use
607+ 
608+Zero. All `DynamicInputRegister`/`DynamicOutputRegister` calls use the 2-arg form `(name, count)`. No 3+ arg advanced overloads observed.
609+ 
610+### 15.8 Observed-Use vs Zero-Use Base Capabilities
611+ 
612+**Observed-use** (previously misclassified as zero-use, corrected in verify-task-4 rev 3):
613+- `Operator.GetName()` (no-arg method): 10 calls in 5 files (layer_normalization, sparse_to_dense, lppool, max_pool, maxunpool). All are `OP_LOGE`/`OP_LOGD` logging statements inside callback bodies. The `GetName(op_name)` output-param overload and `GetOpName` free function are NOT counted here.
614+ 
615+**Zero-use** (confirmed by grep):
616+- DynamicInputRegister 3+ args: 0
617+- DynamicOutputRegister 3+ args: 0
618+- FusionParseParamFn: 0
619+- ParseParamByOpFunc (as builder): 0
620+- graph.AddEdge / RemoveEdge / AddNode: 0
621+- AttributeProto tensors() (tensor_list): 0
622+- TensorDesc.SetRealDimCnt / GetRealDimCnt: 0
623+ 
624+Full per-registration data: see `task-4-ops-nn-usage.json`, `task-4-ops-nn-usage.csv`, `task-4-ops-nn-usage.txt`.
625+ 
626+## 16. Strict Reachability Summary (173 Rows)
627+ 
628+Full 173-row reachability data: see `task-5-reachability.csv` and `task-5-reachability.txt`.
629+ 
630+### 16.1 State Distribution
631+ 
632+| State | Count |
633+|-------|-------|
634+| direct ONNX | 91 |
635+| framework utility used by ONNX | 8 |
636+| other-framework-only | 29 |
637+| not currently consumed | 40 |
638+| prototype-only | 5 |
639+| adapter-mediated ONNX | 0 |
640+| **Total** | **173** |
641+ 
642+### 16.2 Family Distribution
643+ 
644+| Family | Count |
645+|--------|-------|
646+| REG-CALLBACK | 7 |
647+| REG-CONSTRUCTOR | 3 |
648+| REG-BUILDER | 18 |
649+| REG-GETTER | 13 |
650+| REG-IMPL-FIELD | 12 |
651+| REGISTRY-MAP | 10 |
652+| REGISTRY-OP | 1 |
653+| REGISTRY-GETTER | 14 |
654+| REG-TBE | 3 |
655+| FRAMEWORK-REG | 7 |
656+| FRAMEWORK-UTIL | 6 |
657+| PLUGIN-LOAD | 3 |
658+| TYPE-ADAPT | 5 |
659+| ONNX-PROTO | 7 |
660+| CB-OPERATOR | 27 |
661+| CB-GRAPH | 12 |
662+| CB-TENSOR | 11 |
663+| CB-TENSORDESC | 11 |
664+| CB-SHAPE | 1 |
665+| CB-ATTRVALUE | 1 |
666+| CB-NAMEDATTRS | 1 |
667+| **Total** | **173** |
668+ 
669+### 16.3 Key Reachability Chains
670+ 
671+**Chain 1 (ParseParamFunc, 88 plugins)**: `REGISTER_CUSTOM_OP` -> `OpReceiver` -> `registrationDatas` -> `LoadCustomOpLib`/`AclParserInitialize` -> `Finalize` -> `RegisterParser` -> `OpParserFactory(ONNX)` registers `OnnxCustomParserAdapter` -> `ParseOpParam` -> `GetParseParamFunc(om_type, ori_type)` -> `parseParamFn(op_src, op_dest)`
672+ 
673+**Chain 2 (ParseParamByOpFunc, 6 plugins)**: Same prefix -> `GetParseParamByOperatorFunc(ori_type)` -> `Message2Operator::ParseOperatorAttrs` -> `parse_param_by_op_fn(op_src, op_dest)`
674+ 
675+**Chain 3 (ParseOpToGraphFunc, 28 plugins)**: Same prefix -> `GetParseOpToGraphFunc(type, ori_type)` -> `ExpandOneToManyGraph` -> `parse_op_to_graph_func(op, subgraph)`
676+ 
677+**Chain 4 (ParseSubgraphFuncV2, 1 plugin)**: Same prefix -> `GetParseSubgraphPostFunc(op_type, ONNX, parse_func_v2)` -> `parse_func_v2(graph_name.c_str(), graph)` -> inside callback: `FrameworkRegistry::GetAutoMappingSubgraphIOIndexFunc(ONNX)` -> `AutoMappingSubgraphIndexByDataNodeAndOutputNodesInfo`
678+ 
679+### 16.4 ATC vs Session vs ACL Timing
680+ 
681+| Aspect | ATC | Session | ACL |
682+|--------|-----|---------|-----|
683+| Clears registrationDatas | YES | NO | Backup+Clear (OperatorFactory only) |
684+| dlopen flags | RTLD_NOW\|RTLD_LOCAL | RTLD_NOW\|RTLD_GLOBAL\|RTLD_NODELETE | via LoadPluginSo |
685+| Calls Finalize | YES | NO | YES |
686+| Calls Register | YES | YES | YES |
687+ 
688+Critical difference: Session path does NOT call Finalize, so `OnnxCustomParserAdapter` is not registered via session path alone. Session relies on prior ATC/ACL initialization.
689+ 
690+### 16.5 Stale-State Risk
691+ 
692+`OpRegistry::Register` (register.cpp:1050-1092) has two skip guards:
693+1. Lines 1056-1060: if `op_parse_params_fn_map_` already contains the key and `!is_custom_op`, logs "already registered, skip" and continues, skipping ALL per-ori_type map population.
694+2. Lines 1086-1088: if `op_run_mode_map_` already contains `om_optype` and `!is_custom_op`, returns `true` (misleading success).
695+ 
696+Reachability states are based on first-load behavior (clean OpRegistry maps). Stale-state risk on duplicate registration does not affect first-load reachability classification.
697+ 
698+## 17. Python Public Interface and Module Action Table
699+ 
700+> **Derived from**: Approved inventory (Task 8 APPROVED, hash `9686837d69a94c766ec4cc65e832844c39c737791123e53715aeda562e86c030`); revised per Oracle final advisory (descriptor read-then-write parity, source/target value separation).
701+> **Method**: Every public symbol maps to one or more capability IDs; every required capability has a Python action/status or N/A reason. No speculative symbols.
702+> **Scope**: Conceptual interface members only — no implementation pseudocode, bridge code, or line-by-line C++ translation. Design prose belongs in `onnx_plugin_python_modification_plan.md`.
703+ 
704+### 17.1 Derivation Principles
705+ 
706+1. **Write/read facade split**: `OperatorPatch` (write) and `OperatorView` (read) are distinct because CB-001/CB-002 write to the target Operator while CB-006 reads from it and produces a Graph. The inventory proves these are separate boundaries (Section 5, Section 10).
707+2. **Typed source dispatch**: CB-001 source is `OnnxNode` (from NodeProto via `Message*`); CB-002/CB-006 source is `OperatorView` (from `Operator&`). Distinguished by typed callback source, NOT by a `parse_by_op: bool` flag.
708+3. **Read-then-write parity**: Observed ops-nn plugins (ascend_dequant, ascend_quant, int8_quantize, npu_anti_quant, lppool, max_pool, average_pool, int8_conv_relu) read target descriptors before writing updates. Both name and index paths are used (e.g., `GetInputDesc("x")` + `UpdateInputDesc("x", desc)`; `GetInputDesc(i)` + `UpdateInputDesc(i, desc)`). CB-001 and CB-002 parse callbacks therefore receive a read-only `target: OperatorView` in addition to the source, enabling descriptor reads before patch application.
709+4. **Source/target value separation**: `OnnxNode.attrs` uses `OnnxAttrValue` (source domain, from ONNX `AttributeProto`); `OperatorPatch.attrs` and `OperatorView.attrs` use public GE `AttrValue` (target domain). These are distinct value domains — ONNX source attrs are read-only projections of protobuf, while target attrs are GE operator attributes.
710+5. **Opset is registration-time origin expansion**: `opset` is NOT a NodeProto field (opset lives in `OperatorSetIdProto` at `ModelProto` level, not per-node). It expands `source` into full origin types via ORD-B-003 `OriginOpType` + TYPE-ADAPT-005 `ConstructOriType` at registration time.
711+6. **NodeProto domain folded into origin_type**: NodeProto HAS a `domain` field (PROTO-001, `ge_onnx.proto:186`), but the parser folds it into `origin_type` via `ConstructOriType`. It is intentionally not exposed separately on `OnnxNode` to avoid redundancy with `origin_type`.
712+7. **Per-op subgraph vs framework auto-mapping**: CB-007 `ParseSubgraphFuncV2` is per-op (decorator parameter); FUCB-001 `AutoMappingSubgraphIOIndexFunc` is framework-level (separate `register_onnx_auto_mapping` API).
713+8. **Legacy/V2 subgraph normalization**: CB-005 (legacy, `std::string`) and CB-007 (V2, `AscendString`) remain separate C++ rows (Section 5); Python normalizes both to `str`.
714+9. **No `OperatorPatch.name`**: Oracle correction — C++ `Operator` has no public `SetName`; the node name comes from NodeProto, not from the patch.
715+10. **No `TensorValue`/`TensorDescValue`**: Task 6 proves `ge.graph.Tensor` reuse; no wrapper needed.
716+11. **No live mutable context**: No `PluginContext`, raw handle, or private pointer is exposed. All writes go through the returned `OperatorPatch`; the `OperatorView` is read-only and callback-scoped.
717+ 
718+### 17.2 Public Interface Classes (Conceptual Members with Capability IDs)
719+ 
720+#### 17.2.1 `OnnxNode` (新增)
721+ 
722+Source: CB-001 `ParseParamFunc` `(Message*, Operator&)``Message*` is NodeProto (PROTO-001).
723+ 
724+| Member | Type | Capability ID | Origin |
725+|--------|------|---------------|--------|
726+| `name` | `str` | PROTO-001 | `NodeProto.name()` |
727+| `origin_type` | `str` | TYPE-ADAPT-005 | Full `domain::version::OpType` from `ConstructOriType` (folds NodeProto `domain` + model opset + `op_type`) |
728+| `inputs` | `tuple[str, ...]` | PROTO-001 | `NodeProto.input()` repeated string |
729+| `outputs` | `tuple[str, ...]` | PROTO-001 | `NodeProto.output()` repeated string |
730+| `attrs` | `Mapping[str, OnnxAttrValue]` | PROTO-002 | `AttributeProto` adapted to read-only source values (Section 17.2.7) |
731+ 
732+**NOT included**: `opset` (not a NodeProto field; lives in `OperatorSetIdProto` at ModelProto level; expanded at registration time via ORD-B-003/TYPE-ADAPT-005). `domain` (NodeProto HAS `domain` field 7, but parser folds it into `origin_type` via `ConstructOriType`; intentionally not exposed separately to avoid redundancy).
733+ 
734+#### 17.2.2 `OperatorPatch` (新增)
735+ 
736+Role: declarative write facade for target Operator mutation. Returned by CB-001 and CB-002 parse callbacks. No live mutable context — all writes are declared here and applied by the bridge after the callback returns.
737+ 
738+| Member | Type | Capability ID | Origin |
739+|--------|------|---------------|--------|
740+| `attrs` | `Mapping[str, AttrValue]` | CB-OP-FAM-017 | `Operator::SetAttr` / `GetAttr` family (target GE attr domain) |
741+| `dynamic_inputs` | `tuple[DynamicPortSpec, ...]` | CB-OP-FAM-022 | `DynamicInputRegister(name, count)` |
742+| `dynamic_outputs` | `tuple[DynamicPortSpec, ...]` | CB-OP-FAM-022 | `DynamicOutputRegister(name, count)` |
743+| `input_desc_updates` | `Sequence[DescriptorUpdate]` | CB-OP-FAM-005 | `UpdateInputDesc(name_or_index, desc)` — covers both name and index paths |
744+| `output_desc_updates` | `Sequence[DescriptorUpdate]` | CB-OP-FAM-007 | `UpdateOutputDesc(name_or_index, desc)` — covers both name and index paths |
745+ 
746+**NOT included**: `name` (no C++ `Operator::SetName`; name comes from NodeProto, not patch). `dynamic_inputs`/`dynamic_outputs` use structured `DynamicPortSpec`, NOT generic `Mapping[str, int]`. Descriptor updates use `Sequence[DescriptorUpdate]` with `PortRef`, NOT `Mapping[int, TensorDesc]` (which only covers index-based updates).
747+ 
748+#### 17.2.3 `OperatorView` (新增)
749+ 
750+Role: read-only, callback-scoped projection of a GE Operator. Used as: (a) the **source** for CB-002/CB-006 (first `Operator&` param), and (b) the **target** for CB-001/CB-002 parse callbacks (second `Operator&` param), enabling descriptor reads before patch application. No raw handle or private pointer is exposed; all writes go through the returned `OperatorPatch`.
751+ 
752+| Member / Method | Type | Capability ID | Origin |
753+|-----------------|------|---------------|--------|
754+| `name` | `str` | CB-OP-FAM-001 | `Operator::GetName()` |
755+| `target` | `str` | CB-OP-FAM-001 | `Operator::GetOpType()` |
756+| `origin_type` | `str` | TYPE-ADAPT-005 | `ATTR_NAME_FRAMEWORK_ORIGINAL_TYPE` attr |
757+| `attrs` | `Mapping[str, AttrValue]` | CB-OP-FAM-017 | `Operator::GetAttr` family (target GE attr domain) |
758+| `input_desc(port: PortRef)` | `TensorDesc` | CB-OP-FAM-004 | `GetInputDesc(name)` / `GetInputDesc(index)` — both name and index via `PortRef` |
759+| `output_desc(port: PortRef)` | `TensorDesc` | CB-OP-FAM-006 | `GetOutputDesc(name)` / `GetOutputDesc(index)` — both name and index via `PortRef` |
760+| `input_size` | `int` | CB-OP-FAM-015 | `GetInputsSize()` |
761+| `output_size` | `int` | CB-OP-FAM-015 | `GetOutputsSize()` |
762+ 
763+All members and methods are read-only. `TensorDesc` is `ge.graph.TensorDesc` (复用, Section 17.10). `PortRef` supports both name and index access (Section 17.2.4). Descriptor reads on expand-created subgraph operators remain ES/GraphBuilder responsibility and are NOT confused with this source/target view.
764+ 
765+**Deferred**: `TryGetInputDesc` (CB-OP-FAM-004 optional-input variant) — zero ops-nn use; deferred with explicit zero-use reason. Dynamic descriptor APIs (CB-OP-FAM-008..012) — zero ops-nn use; deferred (Section 17.11). Deprecated `GetInputDescByName`/`GetOutputDescByName` aliases — N/A while current by-name overload is covered via `PortRef`.
766+ 
767+#### 17.2.4 `PortRef` (新增)
768+ 
769+Unified port reference supporting both name and index access paths, matching observed ops-nn usage.
770+ 
771+| Member | Type | Capability ID | Origin |
772+|--------|------|---------------|--------|
773+| `name` | `str \| None` | CB-OP-FAM-004, CB-OP-FAM-006 | `GetInputDesc(name)` / `GetOutputDesc(name)` by-name path |
774+| `index` | `int \| None` | CB-OP-FAM-004, CB-OP-FAM-006 | `GetInputDesc(index)` / `GetOutputDesc(index)` by-index path |
775+ 
776+Exactly one of `name`/`index` is set. Used by `OperatorView.input_desc(port)` / `output_desc(port)` and `DescriptorUpdate.port`.
777+ 
778+#### 17.2.5 `DescriptorUpdate` (新增)
779+ 
780+Structured descriptor update covering both name and index paths, matching observed ops-nn read-then-write patterns (e.g., `GetInputDesc("x")``UpdateInputDesc("x", desc)`; `GetInputDesc(i)``UpdateInputDesc(i, desc)`).
781+ 
782+| Member | Type | Capability ID | Origin |
783+|--------|------|---------------|--------|
784+| `port` | `PortRef` | CB-OP-FAM-005, CB-OP-FAM-007 | `UpdateInputDesc`/`UpdateOutputDesc` name or index parameter |
785+| `desc` | `TensorDesc` | CB-OP-FAM-005, CB-OP-FAM-007, CB-TDESC-FAM-001..005 | `ge.graph.TensorDesc` (复用) — the updated descriptor |
786+ 
787+Replaces `Mapping[int, TensorDesc]` which only covered index-based updates. `TensorDesc` is `ge.graph.TensorDesc` (复用, Section 17.10).
788+ 
789+#### 17.2.6 `DynamicPortSpec` (新增)
790+ 
791+| Member | Type | Capability ID | Origin |
792+|--------|------|---------------|--------|
793+| `name` | `str` | CB-OP-FAM-022 | `DynamicInputRegister`/`DynamicOutputRegister` name parameter |
794+| `count` | `int` | CB-OP-FAM-022 | `DynamicInputRegister`/`DynamicOutputRegister` count parameter |
795+ 
796+Replaces generic `Mapping[str, int]` for dynamic IO. Only the 2-arg form `(name, count)` is used by ops-nn (Section 15.7: zero advanced overloads).
797+ 
798+#### 17.2.7 `OnnxAttrValue` and `OnnxAttrKind` (新增, source value domain)
799+ 
800+Read-only source value domain for ONNX `AttributeProto` (PROTO-002). Distinct from public target `AttrValue` (Section 17.2.8). Hides protobuf and preserves all ONNX `AttributeProto.AttributeType` categories (PROTO-002, `ge_onnx.proto:101-116`).
801+ 
802+**`OnnxAttrKind` enum (新增)** — explicit discriminator grounded in `AttributeProto.AttributeType` (PROTO-002):
803+ 
804+| Member | AttributeType | Value Type | Capability ID | Status |
805+|--------|--------------|------------|---------------|--------|
806+| `FLOAT` | FLOAT (1) | `float` | PROTO-002 | supported |
807+| `INT` | INT (2) | `int` | PROTO-002 | supported |
808+| `STRING` | STRING (3) | `bytes` | PROTO-002 | supported |
809+| `TENSOR` | TENSOR (4) | `ge.graph.Tensor` | PROTO-002, PROTO-003 | supported (reuse) |
810+| `GRAPH` | GRAPH (5) | `ge.graph.Graph` | PROTO-002, PROTO-005 | supported (reuse) |
811+| `FLOATS` | FLOATS (6) | `tuple[float, ...]` | PROTO-002 | supported |
812+| `INTS` | INTS (7) | `tuple[int, ...]` | PROTO-002 | supported |
813+| `STRINGS` | STRINGS (8) | `tuple[bytes, ...]` | PROTO-002 | supported |
814+| `TENSORS` | TENSORS (9) | `tuple[ge.graph.Tensor, ...]` | PROTO-002, PROTO-003 | supported (reuse) |
815+| `GRAPHS` | GRAPHS (10) | `tuple[ge.graph.Graph, ...]` | PROTO-002, PROTO-005 | supported (reuse) |
816+| `UNDEFINED` | UNDEFINED (0) | `None` | PROTO-002 | N/A — no data; bridge raises if encountered |
817+| `SPARSE_TENSOR` | SPARSE_TENSOR (11) | (none) | PROTO-002 | Deferred — no public reusable sparse representation; zero ops-nn use |
818+| `SPARSE_TENSORS` | SPARSE_TENSORS (12) | (none) | PROTO-002 | Deferred — no public reusable sparse representation; zero ops-nn use |
819+ 
820+**`OnnxAttrValue` (新增)** — minimal tagged source value, NOT an optional-field bag:
821+ 
822+| Member | Type | Capability ID | Origin |
823+|--------|------|---------------|--------|
824+| `kind` | `OnnxAttrKind` | PROTO-002 | `AttributeProto.type` discriminator (`ge_onnx.proto:136`) |
825+| `value` | `float \| int \| bytes \| ge.graph.Tensor \| ge.graph.Graph \| tuple[...] \| None` | PROTO-002, PROTO-003, PROTO-005 | Exactly one value type per `kind`; `None` only for `UNDEFINED`/deferred kinds |
826+ 
827+The `kind` discriminator preserves the element type of empty lists (e.g., `FLOATS` with zero elements is distinguishable from `INTS` with zero elements), which a bare Python list cannot. For `SPARSE_TENSOR`/`SPARSE_TENSORS`, `value` is `None` and the kind signals the deferred category. No raw `SparseTensorProto` is exposed.
828+ 
829+**`ref_attr_name` handling**: `AttributeProto.ref_attr_name` (`ge_onnx.proto:125`) is a reference to an attribute in a parent function scope, used only in function subgraphs. It is N/A for the current ONNX plugin scope because ops-nn plugins operate on main-graph nodes, not function subgraphs. If a future plugin encounters a non-empty `ref_attr_name`, the bridge raises an error rather than silently exposing an unresolved reference.
830+ 
831+| Aspect | Decision | Capability IDs | Rationale |
832+|--------|----------|----------------|-----------|
833+| Source domain | 新增 | PROTO-002, PROTO-003, PROTO-005 | ONNX `AttributeProto` all 13 categories; `TensorProto` (PROTO-003) for tensor attrs; `GraphProto` (PROTO-005) for graph attrs |
834+| Read-only | Yes | PROTO-002 | Source values are read-only projections of protobuf; no mutation |
835+| Protobuf hidden | Yes | PROTO-002 | No raw protobuf object exposed to Python |
836+| Discriminator | `OnnxAttrKind` enum | PROTO-002 | Grounded in `AttributeProto.AttributeType`; preserves empty-list element type |
837+| Sparse handling | Deferred | PROTO-002 | No public reusable sparse representation; zero ops-nn use |
838+| `ref_attr_name` | N/A | PROTO-002 | Function-subgraph reference; not applicable to main-graph plugin scope |
839+| Distinct from `AttrValue` | Yes | PROTO-002 vs CB-ATTR-FAM-001 | Source domain (ONNX AttributeProto) ≠ target domain (GE Operator attr) |
840+ 
841+`OnnxNode.attrs` uses `OnnxAttrValue`; `OperatorPatch.attrs` and `OperatorView.attrs` use public GE `AttrValue`. The bridge projects protobuf `AttributeProto` to read-only `OnnxAttrValue` for the source. The plugin author explicitly constructs target `AttrValue` values in `OperatorPatch`; patch application performs **no automatic source-to-target conversion**. The plugin author is responsible for reading `OnnxAttrValue` and constructing the corresponding `AttrValue` for the target operator.
842+ 
843+#### 17.2.8 `AttrValue` (新增, public target wrapper)
844+ 
845+| Aspect | Decision | Rationale |
846+|--------|----------|-----------|
847+| Public wrapper | 新增 | `_AttrValue` is PRIVATE (not in `ge.graph.__all__`); `OperatorPatch.attrs` and `OperatorView.attrs` require a public value type at the user boundary |
848+| Capability IDs | CB-ATTR-FAM-001, CB-OP-FAM-017 | `SetAttrValue`/`GetAttrValue` 13 overloads; `Operator::SetAttr`/`GetAttr` family |
849+| Supported types | 11 reuse + 5 extend = 16 | See Section 17.7 for the 11/5/6 split |
850+| `TensorValue` | Rejected | Task 6 proves `ge.graph.Tensor` reuse (29 probes, all passed) |
851+| `TensorDescValue` | Rejected | Task 6 proves `ge.graph.TensorDesc` reuse for descriptor ops |
852+| Distinct from `OnnxAttrValue` | Yes | Target GE attr domain ≠ source ONNX attr domain (Section 17.2.7) |
853+ 
854+`_AttrValue` stays private; the public `AttrValue` wrapper is the user boundary for target GE operator attributes. It is NEW because no public attr value type exists in `ge.graph`.
855+ 
856+#### 17.2.9 `Backend` enum (新增)
857+ 
858+| Member | Value | Capability ID | Origin |
859+|--------|-------|---------------|--------|
860+| `TVM` | default | ORD-B-012, ORDI-F-004 | `ImplyType::TVM` (95 regs) |
861+| `AI_CPU` | | ORD-B-012 | `ImplyType::AI_CPU` (1 reg) |
862+| `CUSTOM` | | ORD-B-012 | `ImplyType::CUSTOM` (1 reg) |
863+| `GELOCAL` | | ORD-B-012 | `ImplyType::GELOCAL` (1 reg) |
864+ 
865+Hides C++ `ImplyType` from the plugin author; only the 4 values used by ops-nn (Section 15.3) are exposed.
866+ 
867+### 17.3 Callback Source/Target Dispatch (Typed, not `parse_by_op`)
868+ 
869+```
870+ ONNX Model Source
871+ |
872+ NodeProto (PROTO-001)
873+ |
874+ C++ Bridge (internal)
875+ |
876+ +---------------+---------------+
877+ | | |
878+ CB-001 CB-002 CB-006
879+ source: OnnxNode source: source: OperatorView
880+ target: OperatorView OperatorView (no target; expand-only)
881+ | | |
882+ v v v
883+ parse(node, parse(source, expand(source)
884+ target) target) -> Graph
885+ | | |
886+ v v v
887+ OperatorPatch OperatorPatch ge.es.GraphBuilder
888+ | | |
889+ v v v
890+ C++ Bridge C++ Bridge ge.graph.Graph
891+ applies patch applies patch (CopyFrom to parser)
892+```
893+ 
894+| Callback ID | Source | Target | Python Signature | Rationale |
895+|-------------|--------|--------|-----------------|-----------|
896+| CB-001 | `OnnxNode` (from NodeProto) | `OperatorView` (target Operator) | `parse(node: OnnxNode, target: OperatorView) -> OperatorPatch` | Read-then-write parity: plugin reads target descriptors, returns patch |
897+| CB-002 | `OperatorView` (source Operator) | `OperatorView` (target Operator) | `parse(source: OperatorView, target: OperatorView) -> OperatorPatch` | Read-then-write parity: plugin reads source + target, returns patch |
898+| CB-006 | `OperatorView` (source Operator) | (none; expand-only) | `expand(source: OperatorView) -> Graph` | Source-only; graph expansion via ES/GraphBuilder |
899+| CB-007 | `str` (subgraph name) | `Graph` (mutated in-place) | `subgraph(name: str, graph: Graph) -> None` | Per-op subgraph post-mapping |
900+ 
901+CB-001 and CB-002 are distinguished by **typed source dispatch** (`OnnxNode` vs `OperatorView`), NOT by a `parse_by_op: bool` flag. The bridge selects the correct source adapter based on which callback was registered. The `target: OperatorView` parameter enables descriptor reads before patch application (observed in ascend_dequant, ascend_quant, int8_quantize, npu_anti_quant, lppool, max_pool, average_pool, int8_conv_relu). All writes go through the returned `OperatorPatch`; no live mutable context is exposed.
902+ 
903+### 17.4 Decorator Registration Parameters
904+ 
905+`onnx_plugin(...)` decorator (新增):
906+ 
907+| Parameter | Type | Capability ID | Semantics |
908+|-----------|------|---------------|-----------|
909+| `source` | `str` (`domain::OpType`) | ORD-B-003, ORD-B-005 | `OriginOpType` builder; two segments both non-empty |
910+| `opset` | `Collection[int]` | ORD-B-003, TYPE-ADAPT-005 | Registration-time origin expansion: `source + opset` → full `domain::version::OpType` via `ConstructOriType`. NOT a NodeProto field (opset lives in `OperatorSetIdProto` at ModelProto level). |
911+| `target` | `str` | ORD-C-002, ORDI-F-003 | `OpRegistrationData` constructor (target op name) + `om_optype_` stored field; must exist as GE op prototype |
912+| `expand` | `Callable[[OperatorView], Graph] \| None` | CB-006 | `ParseOpToGraphFn` registration; `None` means no expand callback registered |
913+| `subgraph` | `Callable[[str, Graph], None] \| None` | CB-007 | Per-op `ParseSubgraphFuncV2` registration; `None` means no subgraph post-mapping |
914+| `backend` | `Backend` (default `TVM`) | ORD-B-012, ORDI-F-004 | `ImplyType` builder + `imply_type_` stored field; hides C++ enum from plugin author |
915+ 
916+**NOT included**: `auto_map_io` (framework-level auto-mapping is a separate API, Section 17.5). `parse_by_op` (source dispatch is typed, Section 17.3).
917+ 
918+### 17.5 Framework-Level Auto Mapping API
919+ 
920+`register_onnx_auto_mapping(callback)` (新增):
921+ 
922+| Aspect | Decision | Capability IDs |
923+|--------|----------|----------------|
924+| API | Separate from decorator | FUCB-001, FR-005, FR-006, AUTO-MAP-001 |
925+| Rationale | `AutoMappingSubgraphIOIndexFunc` is framework-level (`FrameworkRegistry`), not per-op (`OpRegistrationData`); registered once for ONNX framework type, not per plugin |
926+| Callback signature | `(graph: Graph, input_fn: Callable, output_fn: Callable) -> None` | FUCB-001 `(Graph, input_fn, output_fn)` |
927+| ops-nn usage | 1 file (`if_onnx_plugin.cpp`) | Section 15, CB-007 chain |
928+ 
929+### 17.6 IO Ownership Model (Explicit Separation)
930+ 
931+The IO model is NOT a generic `dynamic_inputs: Mapping[str, int]`. It explicitly separates nine IO concepts by ownership and lifecycle, including parse target descriptor reads:
932+ 
933+| # | IO Concept | Python Surface | C++ Capability IDs | Action | Status |
934+|---|-----------|---------------|-------------------|--------|--------|
935+| 1 | ONNX source IO | `OnnxNode.inputs` / `OnnxNode.outputs` | PROTO-001, CB-OP-FAM-001 | 新增 | [ ] |
936+| 2 | Prototype required input | (IR definition, not Python-exposed) | CB-OP-FAM-021 `InputRegister` | N/A | N/A |
937+| 3 | Prototype optional input | (IR definition, not Python-exposed) | CB-OP-FAM-021 `OptionalInputRegister` | N/A | N/A |
938+| 4 | Prototype output | (IR definition, not Python-exposed) | CB-OP-FAM-021 `OutputRegister` | N/A | N/A |
939+| 5 | Runtime dynamic IO groups | `DynamicPortSpec` in `OperatorPatch` | CB-OP-FAM-022 | 新增 | [ ] |
940+| 6 | Fixed descriptor updates (name+index) | `OperatorPatch.input_desc_updates` / `output_desc_updates` as `Sequence[DescriptorUpdate]` | CB-OP-FAM-005, CB-OP-FAM-007, CB-TDESC-FAM-001..005 | 新增 | [ ] |
941+| 7 | Parse target descriptor reads | `OperatorView.input_desc(port)` / `output_desc(port)` via `PortRef` | CB-OP-FAM-004, CB-OP-FAM-006, CB-OP-FAM-015 | 新增 | [ ] |
942+| 8 | Expanded Graph boundary | `ge.es.GraphBuilder``ge.graph.Graph` | CB-006, CB-GRAPH-FAM-002 | 复用 | [x] |
943+| 9 | Subgraph mapping | decorator `subgraph` param + `register_onnx_auto_mapping` | CB-007, FUCB-001 | 新增 | [ ] |
944+ 
945+**N/A reasons**: Concepts 2-4 are prototype-only (IR definition time, not callback-visible). Dynamic descriptor capabilities (CB-OP-FAM-008..012) have zero ops-nn use (Section 15.7); dynamic IO is handled via `DynamicPortSpec` registration (concept 5), not imperative descriptor mutation. Descriptor reads on expand-created subgraph operators remain ES/GraphBuilder responsibility (concept 8), not confused with parse target reads (concept 7).
946+ 
947+Concept 6 is 新增 (structured `DescriptorUpdate` surface with reused `TensorDesc`) because the update surface itself is a new Python value object, even though `TensorDesc` is reused. Concept 7 is 新增 because `OperatorView.input_desc(port)` / `output_desc(port)` is a new read surface enabling parse target descriptor reads.
948+ 
949+### 17.7 Attribute Type Public-Parity Decision (11 Reuse / 5 Extend / 6 N/A)
950+ 
951+Preserves Task 6's factual gap table (22 meaningful `AttrValueType` categories). Adds the ONNX plugin public-parity decision based on whether C++ `Operator::SetAttr` overloads exist. This applies to the **target** `AttrValue` domain (Section 17.2.8), not the source `OnnxAttrValue` domain (Section 17.2.7).
952+ 
953+| Category | AttrValueType | C++ `Operator::SetAttr` | Python `_AttrValue` | Public Parity | Reason |
954+|----------|--------------|------------------------|---------------------|---------------|--------|
955+| String | VT_STRING (1) | ✓ | ✓ | 复用 | supported |
956+| Float | VT_FLOAT (2) | ✓ | ✓ | 复用 | supported |
957+| Bool | VT_BOOL (3) | ✓ | ✓ | 复用 | supported |
958+| Int | VT_INT (4) | ✓ | ✓ | 复用 | supported |
959+| Tensor | VT_TENSOR (6) | ✓ | ✓ | 复用 | supported; reuse `ge.graph.Tensor` |
960+| DataType | VT_DATA_TYPE (11) | ✓ | ✓ | 复用 | supported |
961+| ListString | VT_LIST_STRING (1001) | ✓ | ✓ | 复用 | supported |
962+| ListFloat | VT_LIST_FLOAT (1002) | ✓ | ✓ | 复用 | supported |
963+| ListBool | VT_LIST_BOOL (1003) | ✓ | ✓ | 复用 | supported |
964+| ListInt | VT_LIST_INT (1004) | ✓ | ✓ | 复用 | supported |
965+| ListDataType | VT_LIST_DATA_TYPE (1011) | ✓ | ✓ | 复用 | supported |
966+| Bytes | VT_BYTES (7) | ✓ | ✗ | 扩展 | C++ `SetAttr(OpBytes)` overload exists |
967+| NamedAttrs | VT_NAMED_ATTRS (9) | ✓ | ✗ | 扩展 | C++ `SetAttr(NamedAttrs)` overload exists |
968+| ListTensor | VT_LIST_TENSOR (1006) | ✓ | ✗ | 扩展 | C++ `SetAttr(vector<Tensor>)` overload exists |
969+| ListListInt | VT_LIST_LIST_INT (10) | ✓ | ✗ | 扩展 | C++ `SetAttr(vector<vector<int64_t>>)` overload exists |
970+| ListNamedAttrs | VT_LIST_NAMED_ATTRS (1009) | ✓ | ✗ | 扩展 | C++ `SetAttr(vector<NamedAttrs>)` overload exists |
971+| TensorDesc | VT_TENSOR_DESC (5) | ✗ | ✗ | N/A | No C++ `Operator::SetAttr(TensorDesc)` overload (Section 12.1) |
972+| Graph | VT_GRAPH (8) | ✗ | ✗ | N/A | Maps to subgraph registration (CB-007), not `SetAttr` |
973+| ListListFloat | VT_LIST_LIST_FLOAT (12) | ✗ | ✗ | N/A | No C++ `Operator::SetAttr` overload |
974+| ListTensorDesc | VT_LIST_TENSOR_DESC (1005) | ✗ | ✗ | N/A | No C++ `Operator::SetAttr` overload |
975+| ListBytes | VT_LIST_BYTES (1007) | ✗ | ✗ | N/A | No C++ `Operator::SetAttr` overload |
976+| ListGraph | VT_LIST_GRAPH (1008) | ✗ | ✗ | N/A | Maps to dynamic subgraph registration, not `SetAttr` |
977+ 
978+**Summary**: 11 reuse, 5 extend, 6 N/A. The 6 N/A categories have no C++ `Operator::SetAttr` overload and are therefore not part of the ONNX plugin public attr boundary. `ge.graph.Tensor` is reused (no `TensorValue`); `ge.graph.TensorDesc` is reused for descriptor ops (no `TensorDescValue`).
979+ 
980+### 17.8 Module Action Table
981+ 
982+Every module/interface is labeled 新增/复用/扩展/N/A with capability IDs. Infrastructure NEW/N/A modules cite the capabilities/pattern responsibilities they serve — no blank pattern-only rows.
983+ 
984+| Module / Interface | Action | Status | C++ Capability IDs |
985+|--------------------|--------|--------|-------------------|
986+| `ge.onnx_plugin` package | 新增 | [ ] | Does NOT exist in source repo; serves CB-001/002/006/007, FUCB-001, PROTO-001..007 |
987+| `OnnxNode` | 新增 | [ ] | CB-001, PROTO-001, PROTO-002, CB-OP-FAM-001 |
988+| `OperatorPatch` | 新增 | [ ] | CB-001, CB-002, CB-OP-FAM-005, CB-OP-FAM-007, CB-OP-FAM-017, CB-OP-FAM-022 |
989+| `OperatorView` | 新增 | [ ] | CB-001, CB-002, CB-006, CB-OP-FAM-001, CB-OP-FAM-004, CB-OP-FAM-006, CB-OP-FAM-015, CB-OP-FAM-017 |
990+| `PortRef` | 新增 | [ ] | CB-OP-FAM-004, CB-OP-FAM-005, CB-OP-FAM-006, CB-OP-FAM-007 |
991+| `DescriptorUpdate` | 新增 | [ ] | CB-OP-FAM-005, CB-OP-FAM-007, CB-TDESC-FAM-001..005 |
992+| `DynamicPortSpec` | 新增 | [ ] | CB-OP-FAM-022 |
993+| `OnnxAttrKind` enum (source discriminator) | 新增 | [ ] | PROTO-002 |
994+| `OnnxAttrValue` (source value domain) | 新增 | [ ] | PROTO-002, PROTO-003, PROTO-005 |
995+| `AttrValue` (target public wrapper) | 新增 | [ ] | CB-ATTR-FAM-001, CB-OP-FAM-017 |
996+| `Backend` enum | 新增 | [ ] | ORD-B-012, ORDI-F-004 |
997+| `onnx_plugin()` decorator | 新增 | [ ] | ORD-C-002, ORD-B-001..018, ORDI-F-003, CB-001, CB-002, CB-006, CB-007 |
998+| `register_onnx_auto_mapping()` | 新增 | [ ] | FUCB-001, FR-005, FR-006, AUTO-MAP-001 |
999+| `ge.graph.Graph` | 复用 | [x] | CB-GRAPH-FAM-001..012 |
1000+| `ge.graph.Tensor` | 复用 | [x] | CB-TENSOR-FAM-001..008, CB-TENSOR-ATTR-001 |
1001+| `ge.graph.TensorDesc` (descriptor ops) | 复用 | [x] | CB-TDESC-FAM-001..005 |
1002+| `ge.graph.Shape` | 复用 | [x] | CB-SHAPE-FAM-001 |
1003+| `ge.graph.DataType` / `Format` | 复用 | [x] | CB-TENSOR-FAM-006 (format-access), CB-TENSOR-FAM-007 (dtype-access), CB-TDESC-FAM-003 (format), CB-TDESC-FAM-004 (dtype) |
1004+| `ge.es.GraphBuilder` | 复用 | [x] | CB-OP-FAM-002, CB-GRAPH-FAM-002 |
1005+| `ge.es.TensorHolder` | 复用 | [x] | CB-TENSOR-FAM-005 (shape-access), CB-TENSOR-FAM-006 (format-access), CB-TENSOR-FAM-007 (dtype-access) |
1006+| `ge._internal.plugin_loader` | 复用 | [x] | PLUGIN-LOAD-001, PLUGIN-LOAD-002, PLUGIN-LOAD-003 (env-var discovery pattern) |
1007+| `ge.es._plugin_loader` | 扩展 | [ ] | PLUGIN-LOAD-001..003 (entry_points pattern; group name differs for ONNX plugins) |
1008+| `ge.custom_op` registry pattern | 扩展 | [ ] | ORD-B-001..018 (descriptor pattern reusable, fields differ) |
1009+| `ge.passes` bridge/session pattern | 扩展 | [ ] | TYPE-ADAPT-003, TYPE-ADAPT-004 (adapter dispatch + factory pattern); CB-001/002/006/007 callback dispatch responsibilities |
1010+| `ge.custom_op/passes _artifact_utils` | 复用 | [x] | PLUGIN-LOAD-001..003 (artifact resolution pattern for bridge SO selection) |
1011+| `ge.graph._AttrValue` | 扩展 | [ ] | CB-ATTR-FAM-001; 5 extend types (Section 17.7) |
1012+| ONNX plugin bridge SO | 新增 | [ ] | TYPE-ADAPT-003, TYPE-ADAPT-004 (adapter dispatch); CB-001/002/006/007 callback dispatch; PROTO-001..007 adaptation |
1013+| ONNX plugin native binding | 新增 | [ ] | PROTO-001..007 (NodeProto/AttributeProto/TensorProto adaptation); CB-OP-FAM-001..027 (Operator method dispatch) |
1014+| `GePythonRuntimeManager` | N/A | N/A | C++ only; Python does not manage interpreter lifecycle |
1015+| `RuntimePluginLoader` | N/A | N/A | C++ only; loads .so plugins, not Pythonized |
1016+| `python_bridge_loader` template | N/A | N/A | C++ template; Python uses `_artifact_utils` pattern instead |
1017+ 
1018+### 17.9 Legacy/V2 Subgraph Normalization
1019+ 
1020+| Aspect | CB-005 (legacy) | CB-007 (V2) | Python |
1021+|--------|-----------------|-------------|--------|
1022+| C++ subgraph_name type | `std::string` | `ge::AscendString` | `str` |
1023+| C++ row | Preserved (Section 5) | Preserved (Section 5) | Normalized |
1024+| Reachability | not currently consumed | direct ONNX (1 file) | CB-007 is the active path |
1025+ 
1026+Python normalizes both to `str` because the semantic difference is ABI-only (`std::string` vs `AscendString`). The C++ rows remain separate in Section 5 to preserve factual accuracy. CB-005 is N/A (not currently consumed); CB-007 is 新增 (active, 1 file).
1027+ 
1028+### 17.10 Tensor/TensorDesc Decision (Preserved from Task 6)
1029+ 
1030+**Tensor reuse (DECISION: reuse)**: `ge.graph.Tensor` supports construction from data, attr roundtrip (`set_tensor`/`get_tensor`, VT_TENSOR=6), `Graph.set_attr(Tensor)`/`get_attr` roundtrip, `get_tensor_desc()`, and ownership transfer. Copy/deepcopy blocked is acceptable for ONNX plugin callbacks (in-scope construction, no cross-scope copy needed). No `TensorValue` wrapper needed. Existing Tensor reuse is proven by 29 runtime probes (task-6, verify-task-6).
1031+ 
1032+**TensorDesc reuse (DECISION: reuse for descriptor ops)**: `ge.graph.TensorDesc` supports construction, update (all 5 setters), and descriptor operations. Copy/deepcopy blocked is acceptable. TensorDesc as an attr value (`VT_TENSOR_DESC`) is N/A (Section 17.7: no C++ `Operator::SetAttr(TensorDesc)` overload). No `TensorDescValue` wrapper needed.
1033+ 
1034+**TensorValue rejection**: `TensorValue` is rejected because existing `ge.graph.Tensor` reuse is proven. No concrete failure proves existing Tensor cannot be safely copied or extended.
1035+ 
1036+### 17.11 N/A and Deferred Public C++ Families (Explicit Reasons)
1037+ 
1038+| C++ Family | N/A or Deferred | Reason |
1039+|-----------|-----------------|--------|
1040+| CB-OP-FAM-003 (const-data-read) | N/A | Not currently consumed; 0 ops-nn hits |
1041+| CB-OP-FAM-004 `TryGetInputDesc` (optional-input variant) | Deferred | Zero ops-nn use; optional-input descriptor read not needed by observed plugins |
1042+| CB-OP-FAM-004 `GetInputDescByName` / CB-OP-FAM-006 `GetOutputDescByName` (deprecated aliases) | N/A | Deprecated; current by-name overload covered via `PortRef(name=...)` |
1043+| CB-OP-FAM-008 (dynamic-input-desc) | Deferred | Zero ops-nn use; dynamic IO via `DynamicPortSpec` registration |
1044+| CB-OP-FAM-009 (dynamic-input-desc-update) | Deferred | Zero ops-nn use; dynamic IO via `DynamicPortSpec` registration |
1045+| CB-OP-FAM-010 (dynamic-output-desc) | Deferred | Zero ops-nn use; dynamic IO via `DynamicPortSpec` registration |
1046+| CB-OP-FAM-011 (dynamic-output-desc-update) | Deferred | Zero ops-nn use; dynamic IO via `DynamicPortSpec` registration |
1047+| CB-OP-FAM-012 (dynamic-io-count) | Deferred | Zero ops-nn use; dynamic IO via `DynamicPortSpec` registration |
1048+| CB-OP-FAM-013 (infer-shape-type) | N/A | Not currently consumed; 0 ops-nn hits |
1049+| CB-OP-FAM-014 (attr-verify) | N/A | Not currently consumed; 0 ops-nn hits |
1050+| CB-OP-FAM-016 (attr-names) | N/A | Not currently consumed; 0 ops-nn hits |
1051+| CB-OP-FAM-018 (port-attr) | N/A | Not currently consumed; 0 ops-nn hits |
1052+| CB-OP-FAM-019 (break-connect) | N/A | Parser dispatch internal; not plugin author boundary |
1053+| CB-OP-FAM-020 (subgraph-access) | N/A | Not currently consumed; deferred to V2 |
1054+| CB-OP-FAM-021 (prototype-io-register) | N/A | Prototype-only; IR definition time, not callback-visible |
1055+| CB-OP-FAM-023 (prototype-attr-register) | N/A | Prototype-only; IR definition time |
1056+| CB-OP-FAM-024 (prototype-datatype-register) | N/A | Prototype-only; IR definition time |
1057+| CB-OP-FAM-025 (prototype-func-register) | N/A | Prototype-only; IR definition time |
1058+| CB-OP-FAM-026 (prototype-subgraph-register) | N/A | Prototype-only; IR definition time |
1059+| CB-OP-FAM-027 (internal-private) | N/A | Private/internal; never crosses plugin author boundary |
1060+| CB-GRAPH-FAM-007 (iteration) | N/A | Not currently consumed; 0 ops-nn hits |
1061+| CB-GRAPH-FAM-008 (serialization) | N/A | Not currently consumed; 0 ops-nn hits |
1062+| CB-GRAPH-FAM-009 (copy) | N/A | Not currently consumed; 0 ops-nn hits |
1063+| CB-GRAPH-FAM-010 (construct) | N/A | Not currently consumed; 0 ops-nn hits |
1064+| CB-TENSOR-FAM-009 (expand-dims) | N/A | Not currently consumed; 0 ops-nn hits |
1065+| CB-TENSOR-FAM-010 (clone) | N/A | Not currently consumed; 0 ops-nn hits |
1066+| CB-TDESC-FAM-006 (size) | N/A | Not currently consumed; 0 ops-nn hits |
1067+| CB-TDESC-FAM-007 (real-dim-cnt) | N/A | Not currently consumed; 0 ops-nn hits |
1068+| CB-TDESC-FAM-008 (placement) | N/A | Not currently consumed; 0 ops-nn hits |
1069+| CB-TDESC-FAM-009 (const-data) | N/A | Not currently consumed; 0 ops-nn hits |
1070+| CB-TDESC-FAM-010 (expand-dims) | N/A | Not currently consumed; 0 ops-nn hits |
1071+| CB-TDESC-FAM-011 (reuse-input) | N/A | Not currently consumed; 0 ops-nn hits |
1072+| CB-NAMEDATTR-FAM-001 (named-attrs-access) | N/A | Not currently consumed as standalone; NamedAttrs as attr value is 扩展 (Section 17.7) |
1073+ 
1074+### 17.12 ge.onnx_plugin Package Status
1075+ 
1076+**NEW**: The `ge.onnx_plugin` package does NOT exist in the GE source repo at `api/python/ge/ge/onnx_plugin/`. Verified: `ls api/python/ge/ge/onnx_plugin` returns "No such file or directory".
1077+ 
1078+An experimental version exists in the installed CANN package at `/home/developer/Ascend/cann-9.2.0/cann-9.2.0/python/site-packages/ge/onnx_plugin/` with symbols: `OnnxPlugin`, `OnnxPluginDescriptor`, `PluginContext`, `register`, `get_registered_plugins`, etc. This is NOT part of the GE source repo and must be treated as NEW for the source-repo-based inventory.
1079+ 
1080+## 18. Unresolved Blockers
1081+ 
1082+### 18.1 C++ Completeness
1083+ 
1084+**None.** All 173 capabilities from Tasks 2-3 are traced with reachability states, positive citations, or bounded negative search scope. The 88 task-2 IDs and 83 task-3 capabilities are fully covered (88/88 + 83/83 = zero gaps, verified by verify-task-5 rev 2).
1085+ 
1086+### 18.2 Python Implementation
1087+ 
1088+**Todo 9 decisions resolved.** The Python interface derivation (Todo 9) is complete; the concise design document (Todo 10) has not been started. The following decisions were resolved in Section 17:
1089+ 
1090+1. **Legacy/V2 subgraph normalization** (RESOLVED, Section 17.9): Both CB-005 and CB-007 normalize to Python `str`; C++ rows remain separate. CB-005 is N/A (not currently consumed); CB-007 is 新增.
1091+2. **Public AttrValue API** (RESOLVED, Section 17.2.8): `_AttrValue` stays private; a NEW public `AttrValue` wrapper is the user boundary for target GE attrs, citing CB-ATTR-FAM-001 and 16 supported types (11 reuse + 5 extend). Source domain uses separate `OnnxAttrValue` (Section 17.2.7).
1092+3. **OperatorPatch/OperatorView** (RESOLVED, Section 17.2.2, 17.2.3): Distinct write/read facades proven by CB-001/002 (write) vs CB-006 (read/Graph) boundaries. `OperatorPatch` has no `name` (no C++ `SetName`); `OperatorView` is read-only/callback-scoped. Parse callbacks receive `target: OperatorView` for read-then-write parity (Section 17.3).
1093+4. **Dynamic IO registration Python surface** (RESOLVED, Section 17.2.6, 17.6): `DynamicPortSpec(name, count)` replaces generic `Mapping[str, int]`; only 2-arg form used by ops-nn (zero advanced overloads).
1094+5. **ONNX protobuf Python wrapper** (RESOLVED, Section 17.2.1): `OnnxNode` exposes `name`, `origin_type`, `inputs`, `outputs`, `attrs` (as `OnnxAttrValue`) — no raw protobuf. `opset` not exposed (not a NodeProto field; registration-time expansion via ORD-B-003/TYPE-ADAPT-005). `domain` not exposed separately (NodeProto HAS `domain` field 7, but parser folds it into `origin_type`).
1095+6. **Descriptor read-then-write parity** (RESOLVED, Section 17.2.3, 17.2.4, 17.2.5, 17.3): `PortRef` + `DescriptorUpdate` cover both name and index descriptor paths (CB-OP-FAM-004/005/006/007). Parse signatures include `target: OperatorView` for CB-001/002. `OperatorPatch` descriptor updates use `Sequence[DescriptorUpdate]`, NOT `Mapping[int, TensorDesc]`.
1096+7. **Source/target value separation** (RESOLVED, Section 17.2.7, 17.2.8): `OnnxAttrValue` with `OnnxAttrKind` discriminator (source domain, PROTO-002/003/005) has a concrete minimal tagged value shape (`kind` + `value`), covering all 13 `AttributeProto.AttributeType` categories plus `ref_attr_name` handling. Distinct from `AttrValue` (target domain, CB-ATTR-FAM-001). `OnnxNode.attrs` uses `OnnxAttrValue`; `OperatorPatch/View.attrs` use `AttrValue`. Bridge projects protobuf to read-only `OnnxAttrValue`; plugin author explicitly constructs target `AttrValue`; no automatic source-to-target conversion.
1097+ 
1098+**Remaining for Todo 10**: Concise design document rewrite from the approved inventory and Section 17 decisions.
1099+ 
1100+## 19. Repeatable Refresh Procedure
1101+ 
1102+To revalidate this inventory against future header/plugin changes:
1103+ 
1104+### 19.1 Prerequisites
1105+ 
1106+```bash
1107+# Verify repo SHAs match pinned values (Section 1.1)
1108+GIT_MASTER=1 git -C /mnt/workspace/gitCode/gentle-knight/ge rev-parse HEAD
1109+GIT_MASTER=1 git -C /mnt/workspace/gitCode/gentle-knight/metadef rev-parse HEAD
1110+GIT_MASTER=1 git -C /mnt/workspace/gitCode/gentle-knight/ops-nn rev-parse HEAD
1111+```
1112+ 
1113+### 19.2 Re-derive Wave 1 Evidence
1114+ 
1115+1. **Task 1**: Re-run SHA verification and dirty-state guard. Expected: 93 files, 98 registrations.
1116+2. **Task 2**: Re-extract registration surface from `metadef/inc/external/register/register.h`, `register.cpp`, `op_registry.h`, `op_registration_tbe.cc`. Expected: 88 IDs.
1117+3. **Task 3**: Re-extract callback-visible class surface from `operator.h`, `graph.h`, `tensor.h`, `ge_onnx.proto`, `attr_value.h`, `ge_attr_value.h`. Expected: 7 proto + 63 families + 5 utils + 8 callbacks.
1118+4. **Task 4**: Re-scan all 93 `*onnx_plugin.cpp` files. Expected: 93/98, 88/6/28/1.
1119+5. **Task 5**: Re-trace reachability for all 173 capabilities. Expected: 91/8/29/40/5/0 state distribution.
1120+6. **Task 6**: Re-run Python/SE reuse experiments. Expected: 11 reuse + 11 gap.
1121+ 
1122+### 19.3 Re-derive Expected-ID Manifest
1123+ 
1124+```bash
1125+python3 /tmp/opencode/task7/build_expected_manifest.py
1126+# Expected: 173 IDs, all task-2 (88) and task-3 IDs present in task-5 CSV
1127+```
1128+ 
1129+### 19.4 Run Bidirectional Diff
1130+ 
1131+```bash
1132+python3 /tmp/opencode/task7/bidirectional_diff.py \
1133+ /tmp/opencode/task7/expected_id_manifest.json \
1134+ hfqx/analysis/onnx_plugin_cpp_capability_inventory.md
1135+# Expected: 0 missing, 0 extra
1136+```
1137+ 
1138+### 19.5 Run Validation Checks
1139+ 
1140+```bash
1141+python3 /tmp/opencode/task7/validate_inventory.py \
1142+ hfqx/analysis/onnx_plugin_cpp_capability_inventory.md
1143+# Expected: no blank cells, 93/98 counts, 88/6/28/1 counts, all sections present
1144+```
1145+ 
1146+### 19.6 Update SHAs
1147+ 
1148+If any repo SHA changed, update Section 1.1 with new SHAs and re-run the full procedure. If header files changed, re-derive the affected task evidence before updating this inventory.
1149+ 
1150+---
1151+ 
1152+## Appendix A: Capability ID Index
1153+ 
1154+All 173 capability IDs organized by family:
1155+ 
1156+- **CB-001..007**: Registration callbacks (7)
1157+- **FUCB-001**: Framework utility callback (1)
1158+- **ORD-C-001..003**: OpRegistrationData constructors (3)
1159+- **ORD-B-001..018**: OpRegistrationData builders (18)
1160+- **ORD-G-001..013**: OpRegistrationData getters (13)
1161+- **ORDI-F-001..012**: OpRegistrationDataImpl fields (12)
1162+- **OR-M-001..010**: OpRegistry maps (10)
1163+- **OR-R-001**: OpRegistry::Register (1)
1164+- **OR-G-001..014**: OpRegistry getters (14)
1165+- **ORT-R-001..003**: OpRegistrationTbe (3)
1166+- **FR-001..006**: FrameworkRegistry (6)
1167+- **AUTO-MAP-001..006**: Framework utility functions (6)
1168+- **PLUGIN-LOAD-001..003**: Plugin loaders (3)
1169+- **TYPE-ADAPT-001..005**: Type adaptation (5)
1170+- **PROTO-001..007**: ONNX proto classes (7)
1171+- **CB-OP-FAM-001..027**: Operator method families (27)
1172+- **CB-GRAPH-FAM-001..012**: Graph method families (12)
1173+- **CB-TENSOR-FAM-001..010**: Tensor method families (10)
1174+- **CB-TDESC-FAM-001..011**: TensorDesc method families (11)
1175+- **CB-SHAPE-FAM-001**: Shape method family (1)
1176+- **CB-ATTR-FAM-001**: AttrValue method family (1)
1177+- **CB-NAMEDATTR-FAM-001**: NamedAttrs method family (1)
1178+- **CB-TENSOR-ATTR-001**: Tensor-typed attr value (1)
1179+- **OR-K-001..004**: Registry key formats (4, supplementary)
1180+ 
1181+**Total**: 173 capability IDs (excluding supplementary OR-K keys).
@@ -0,0 +1,440 @@
1+# ONNX Plugin Python 化需求分析与设计
2+ 
3+> 文档状态:精简设计稿(Todo 10)
4+>
5+> 需求来源:[ONNX Plugin Python 化需求分析](../requirements/onnx_plugin_python_requirement_analysis.md)
6+>
7+> 事实基线:[C++ 能力清单](onnx_plugin_cpp_capability_inventory.md)(Task 8 独立审计 APPROVED,173 项有界能力,93 文件/98 注册,回调计数 88/6/28/1)
8+>
9+> 评审来源:[PR 4151 评审意见](onnx_plugin_python_pr_4151_review_comments.md)
10+>
11+> 调研基线:GE `d82ce1f2...`,ops-nn `c1aaaeec...`
12+ 
13+# 简介
14+ 
15+## 目的
16+ 
17+本文是 ONNX Plugin Python 化的精简设计文档,面向 GE parser、GE Python、ES 和 ONNX 算子插件的开发与评审人员。它回答三个问题:插件作者如何只用 Python 描述 ONNX 到 GE IR 的转换;Python 描述如何经独立 bridge 接入现有注册与解析链路;Python 插件如何与现有 C++ 插件共存。
18+ 
19+本文不重复 C++ 能力清单的逐项证据,能力事实以 [C++ 能力清单](onnx_plugin_cpp_capability_inventory.md) 为唯一来源。公开接口、回调签名和模块动作均由该清单 Section 17 推导,不包含未经验证的实现细节。
20+ 
21+## 范围
22+ 
23+本文包括:Python 插件装饰器与公开值对象;ONNX NodeProto 到 Python 值的转换边界;使用现有 ES Python API 构造一对多展开子图;Python 插件发现、注册、冲突处理和生命周期;CB-001/CB-002/CB-006/CB-007 四类回调的 Python 表达;对静态 Shape、动态 Shape、图拆分、离线和在线场景的影响分析。
24+ 
25+本文不包括:改写或淘汰现有 C++ ONNX 插件;修改 AscendIR、OM 格式、编译优化或运行时执行逻辑;bridge 内部 C++ 实现伪代码或逐行源码翻译;未由能力清单证明的加载器顺序、C ABI、事务或错误码细节。
26+ 
27+# 总体概述
28+ 
29+## 软件概述
30+ 
31+### 项目介绍
32+ 
33+当前 ONNX 自定义算子通过 C++ `REGISTER_CUSTOM_OP` 注册。以 ops-nn 的 `ThresholdedRelu` 为例,插件把 `ai.onnx::ThresholdedRelu` 映射为 `PartitionedCall`,从 NodeProto 读取属性写入 Operator,并通过 `ParseOpToGraphFn` 构造 `Data -> Identity -> Threshold -> Mul` 子图。
34+ 
35+C++ 能力清单已独立审计通过(Task 8 APPROVED),覆盖 173 项有界能力、93 个插件文件、98 条注册,观测回调计数为 `ParseParamsFn` 88、`ParseParamsByOperatorFn` 6、`ParseOpToGraphFn` 28、`ParseSubgraphPostFn` 1。本特性在现有 C++ 机制之上增加 Python 作者界面,GE 仍调用 C++ 回调,bridge 负责值转换与回调包装,现有 parser 注册表和图展开流程不改变。
36+ 
37+### 产品环境介绍
38+ 
39+本特性是 GE parser 前端的扩展。GE parser 负责把 ONNX/PB/Caffe/MindSpore 模型转为 AscendIR,本特性只影响 ONNX parser 路径。算子定义由独立算子仓维护,GE 不定义算子语义,Python 插件只做 parser 映射和可选子图展开。编译优化、内存规划、模型下沉和运行时执行不在本特性范围内。
40+ 
41+主要组件交互见整体介绍章节的组件图。Python runtime 和 bridge 不进入 `graph_metadef`,遵守 [graph_metadef 设计约束](../../docs/zh/design/constraints/graph_metadef.md)。
42+ 
43+## 软件功能
44+ 
45+1. 使用 `@onnx_plugin(source, opset, target, ...)` 声明 ONNX 源算子、opset 范围和 GE 目标算子。
46+2. CB-001 parse 函数接收不可变 `OnnxNode` 和只读 `OperatorView`,返回声明式 `OperatorPatch`
47+3. CB-002 parse 函数接收只读源 `OperatorView` 和目标 `OperatorView`,返回 `OperatorPatch`
48+4. CB-006 expand 函数接收只读 `OperatorView`,使用 ES 构图返回 `ge.graph.Graph`
49+5. CB-007 subgraph 函数按名称和 Graph 做子图后处理;框架级自动映射由独立的 `register_onnx_auto_mapping` 注册。
50+6. C++ 插件先注册,Python bridge 按完整版本化 `origin_type` 检查冲突,已有注册则跳过。
51+ 
52+## 设计约束
53+ 
54+- 公开 API 屏蔽 protobuf、`ge::Operator`、裸指针、borrow handle 和 GE 状态码。
55+- 不公开聚合上下文的 `PluginContext`,所有写操作通过返回的 `OperatorPatch` 声明。
56+- 一对多构图使用 `ge.es` 生成式算子包,不新增字符串型 `graph.op` 工厂。
57+- 不在 `graph_metadef` 引入 Python runtime 或 pybind 依赖。
58+- 不区分芯片型号,不修改执行态代码,不新增线程,不修改 OM 序列化格式。
59+- `ge.custom_op` 面向 Eager 运行时,不复用为 parser 构图接口。
60+ 
61+## 假设和依赖关系
62+ 
63+- Python 插件路径复用 `ASCEND_CUSTOM_OPP_PATH`,发现规则复用 `ge._internal.plugin_loader`
64+- 目标 GE op 的原型和执行实现仍由现有 OPP/GE 机制提供。
65+- 生成式 ES 包由 `ge.es.plugins` entry point 提供,已验证 `ge.es.math.Identity``ge.es.nn.Threshold``ge.es.math.Mul` 可用。
66+- bridge 制品与构建时 Python 环境绑定,跨 Python 版本兼容性在实现阶段确认。
67+- Python 插件属于受信任本地代码,与 C++ 自定义插件一样不提供沙箱。
68+ 
69+# ONNX Plugin Python 化需求分析与设计
70+ 
71+## 整体介绍
72+ 
73+方案采用"Python 描述、C++ 适配、现有 registry 执行"的分层模型。插件作者只接触不可变值对象和声明式返回,bridge 在 C++ ABI 边界完成 NodeProto 类型识别、值转换、回调包装和异常翻译。
74+ 
75+```mermaid
76+flowchart LR
77+ Author["插件作者<br/>@onnx_plugin + parse/expand/subgraph"]
78+ Pkg["ge.onnx_plugin<br/>新增公开 API 与值对象"]
79+ Bridge["独立 bridge SO<br/>NodeProto 适配与回调包装"]
80+ Reg["OpRegistry / OpRegistrationTbe<br/>现有 C++ 注册表"]
81+ Parser["OnnxModelParser / ParserUtils<br/>现有解析与图展开"]
82+ ES["ge.es GraphBuilder<br/>现有生成式构图"]
83+ Author --> Pkg
84+ Pkg --> Bridge
85+ Bridge --> Reg
86+ Parser --> Reg
87+ Parser --> Bridge
88+ Bridge --> ES
89+```
90+ 
91+图中绿色为本需求新增模块,其余为现有 GE 能力。bridge 是独立 SO,不加入 `graph_metadef`,不定义公开业务 API。Python 解释器初始化和动态库加载由 bridge 负责,复用现有 Python 运行时和制品解析模式,不在本文命名具体 C++ 实现类。
92+ 
93+## 功能需求
94+ 
95+### 插件声明与参数解析
96+ 
97+插件作者用一个装饰器和纯函数完成声明。装饰器隐藏 `FrameworkType(ONNX)`、版本化 `OriginOpType` 展开、C++ 回调注册和状态返回。`source` 和每个 `opset` 在注册期展开为完整 origin type `domain::version::OpType`,与现有 `ConstructOriType` 一致。`opset` 不是 NodeProto 字段,它存在于 ModelProto 级别的 `OperatorSetIdProto`
98+ 
99+CB-001 parse 函数签名:`parse(node: OnnxNode, target: OperatorView) -> OperatorPatch``OnnxNode` 是从 NodeProto 复制的不可变源值,包含 `name``origin_type``inputs``outputs``attrs`(类型为 `OnnxAttrValue`)。NodeProto 的 `domain` 字段被 parser 折叠进 `origin_type`,不单独暴露。`target: OperatorView` 是目标 Operator 的只读投影,使插件作者能在返回 patch 前读取目标描述符,覆盖 ops-nn 中观测到的 read-then-write 模式。
100+ 
101+CB-002 parse 函数签名:`parse(source: OperatorView, target: OperatorView) -> OperatorPatch`。源和目标都是只读 `OperatorView`,bridge 按 C++ 回调类型选择正确的源适配器,不使用 `parse_by_op` 标志区分。
102+ 
103+`OperatorPatch` 是声明式写回结果,不暴露 `ge::Operator`,包含 `attrs``dynamic_inputs``dynamic_outputs``input_desc_updates``output_desc_updates`。动态端口使用结构化 `DynamicPortSpec(name, count)`,不用泛型 `Mapping[str, int]`。描述符更新使用 `Sequence[DescriptorUpdate]`,通过 `PortRef` 同时覆盖按名和按下标两条路径。`OperatorPatch` 没有 `name` 字段,因为 C++ Operator 没有公开 `SetName`,节点名来自 NodeProto。
104+ 
105+### 一对多 ES 子图展开
106+ 
107+当装饰器传入 `expand` 时,bridge 为该 descriptor 注册 `ParseOpToGraphFn`。CB-006 expand 函数签名:`expand(source: OperatorView) -> Graph``source` 是只读 `OperatorView`,插件作者通过 `ge.es.GraphBuilder` 创建输入,调用 `ge.es.math``ge.es.nn` 等生成式接口构图,调用 `build_and_reset` 返回 Python `ge.graph.Graph`
108+ 
109+bridge 取得 Python Graph 对应的 `ge::Graph`,调用现有 `Graph::CopyFrom` 深拷贝到 parser 提供的输出引用。回调结束后 Python Graph 和 builder 正常释放,parser 输出图独立持有复制后的图结构。不把 Python Graph 内部指针直接赋给 parser 输出图,避免悬空引用或重复释放。无 `expand` 的 descriptor 不注册空回调,不改变现有 `ExpandOneToManyGraph` 的判定。
110+ 
111+### 子图后处理与框架自动映射
112+ 
113+CB-007 subgraph 函数签名:`subgraph(name: str, graph: Graph) -> None`,按子图名称对 Graph 做后处理。这是 per-op 级别的注册,通过装饰器 `subgraph` 参数声明。
114+ 
115+框架级自动映射是独立关注点。`AutoMappingSubgraphIOIndexFunc` 属于 `FrameworkRegistry`,按 ONNX 框架类型注册一次,不是 per-op。因此提供独立的 `register_onnx_auto_mapping(callback)` API,不放进装饰器。ops-nn 中仅 `if_onnx_plugin.cpp` 使用该能力。
116+ 
117+### 插件发现与注册
118+ 
119+插件发现复用 `ASCEND_CUSTOM_OPP_PATH` 和共享 loader,不新增产品级环境变量。ONNX registry 仅收集使用 `@onnx_plugin` 的函数,同一模块可同时包含其他 Python 插件声明,模块文件只执行一次。
120+ 
121+注册时序保证 C++ 优先:parser 先加载 C++ plugin SO 并完成 `Finalize/Register`;bridge 初始化 Python runtime 后导入模块并收集冻结的 descriptors;对每个完整 origin type 调用 `GetOmTypeByOriOpType`,已存在映射说明 C++ 或更早注册拥有该 origin,Python 记录后跳过。`Register(..., false)` 的内部 skip 仅作为第二道保护,不作为唯一保证。Python-Python 重复 origin 在写 registry 前直接报错,不采用后注册覆盖。descriptor 在写入 registry 前全量校验;任一底层注册失败时持久化初始化失败状态,禁止当前进程继续 ONNX 解析。
122+ 
123+### 源属性与目标属性分离
124+ 
125+ONNX 源属性和 GE 目标属性是两个不同的值域。`OnnxNode.attrs` 使用 `OnnxAttrValue`(源域,来自 ONNX `AttributeProto`),带 `OnnxAttrKind` 判别符,覆盖全部 13 种 `AttributeType` 分类。`OperatorPatch.attrs``OperatorView.attrs` 使用公开 GE `AttrValue`(目标域)。bridge 把 protobuf `AttributeProto` 投影为只读 `OnnxAttrValue`,插件作者显式构造目标 `AttrValue`,patch 应用时不做自动源到目标转换。稀疏张量属性因无可复用的公开表示且 ops-nn 零使用,暂不暴露,不暴露原始 protobuf。
126+ 
127+目标 `AttrValue` 的公开类型对等决策为 11 复用、5 扩展、6 N/A。11 复用覆盖 String、Float、Bool、Int、Tensor、DataType 及对应列表类型,其中 Tensor 复用 `ge.graph.Tensor`。5 扩展覆盖 Bytes、NamedAttrs、ListTensor、ListListInt、ListNamedAttrs,这些类型 C++ `Operator::SetAttr` 有重载但现有私有 `_AttrValue` 未支持。6 N/A 类型(TensorDesc、Graph、ListListFloat、ListTensorDesc、ListBytes、ListGraph)没有 C++ `Operator::SetAttr` 重载,不属于公开属性边界。不为 Tensor 或 TensorDesc 新增包装类型,因为 Task 6 已证明 `ge.graph.Tensor``ge.graph.TensorDesc` 可复用。
128+ 
129+### 接口覆盖样例
130+ 
131+以下四个样例证明公开接口覆盖不同能力组合,样例本身不是交付阶段。交付批次见软件设计章节。
132+ 
133+| 样例 | 覆盖能力 | 关键接口 |
134+|------|----------|----------|
135+| ThresholdedRelu | CB-001 加 CB-006,属性、动态 IO、ES 一对多展开 | `OnnxNode``OperatorView``OperatorPatch``DynamicPortSpec``ge.es.GraphBuilder` |
136+| NPUAntiQuant | 目标描述符 read-then-write,按名和按下标,TensorDesc 复用 | `OperatorView.input_desc/output_desc``PortRef``DescriptorUpdate``ge.graph.TensorDesc` |
137+| If | CB-007 per-op 子图后处理 加 FUCB-001 框架级自动映射 | `subgraph` 参数、`register_onnx_auto_mapping` |
138+| Int8Transpose | CB-002 typed OperatorView 源,Tensor 属性 | `OperatorView`(源)、`OperatorPatch``AttrValue`(Tensor 复用) |
139+ 
140+#### ThresholdedRelu 接口草图
141+ 
142+以下为接口草图,展示公开值对象的命名与组合方式,不是精确实现语法。构造函数签名在实现阶段按代码规范确定。
143+ 
144+```python
145+from ge.onnx_plugin import (
146+ OnnxNode, OperatorView, OperatorPatch, DynamicPortSpec,
147+ AttrValue, onnx_plugin,
148+)
149+from ge.es import GraphBuilder, math, nn
150+ 
151+ 
152+def expand_thresholded_relu(source: OperatorView):
153+ builder = GraphBuilder(f"{source.name}_expand")
154+ x = builder.create_input(0, name="data1")
155+ identity = math.Identity(x)
156+ # 从 source.attrs["alpha"] 提取具体值,提取接口在实现阶段确定
157+ alpha_value: float = ...
158+ threshold = nn.Threshold(identity, threshold=alpha_value)
159+ y = math.Mul(identity, threshold)
160+ return builder.build_and_reset(outputs=[y])
161+ 
162+ 
163+@onnx_plugin(
164+ source="ai.onnx::ThresholdedRelu",
165+ opset=range(10, 19),
166+ target="PartitionedCall",
167+ expand=expand_thresholded_relu,
168+)
169+def thresholded_relu(node: OnnxNode, target: OperatorView) -> OperatorPatch:
170+ # 从 node.name 构造目标域 AttrValue(字符串类型)
171+ name_attr: AttrValue = ... # 构造方式在实现阶段确定
172+ # 从 node.attrs["alpha"] 读取源值后构造目标域 AttrValue(浮点类型)
173+ alpha_attr: AttrValue = ... # 构造方式在实现阶段确定
174+ return OperatorPatch(
175+ attrs={"name": name_attr, "alpha": alpha_attr},
176+ dynamic_inputs=(DynamicPortSpec("x", 1),),
177+ dynamic_outputs=(DynamicPortSpec("y", 1),),
178+ )
179+```
180+ 
181+展开后子图语义为 `Data -> Identity -> Threshold(alpha) -> Mul`,与现有 C++ 插件等价。`Identity` 的输出同时送入 `Threshold``Mul``Mul` 输出作为子图输出。bridge 自动设置完整 `original_type`,插件作者不手写版本字符串。
182+ 
183+## 非功能需求
184+ 
185+### 可维护性
186+ 
187+Python 公开层、bridge、parser loader 和现有 registry 各自单一职责。descriptor 是不可变数据,注册完成后 registry 冻结,解析期只读。Python API 不复刻七种 C++ callback 形状,按 CB-001/002/006/007 四类 typed 回调组织。代码实现后同步更新 `docs/zh/design/modules/ge_python/` 下模块文档。
188+ 
189+### 可测试性
190+ 
191+decorator、descriptor 和值对象可在纯 Python UT 中测试。bridge 转换与异常翻译通过 C++ UT 注入 NodeProto 和 Python 测试模块。parser 注册顺序、冲突和图展开通过现有 ONNX parser UT/ST 验证。每个交付批次至少迁移一个 ops-nn C++ 插件作为等价基线。
192+ 
193+### 可移植性
194+ 
195+不包含芯片分支,只依赖 Host 侧 parser、Python runtime 和 ES。bridge 按 OS 和架构选择制品。无 Python 插件时不加载 bridge,不要求环境安装额外第三方 Python 包。
196+ 
197+### 可靠性
198+ 
199+初始化只执行一次并持久保存结果。descriptor 在 registry 写入前全量校验,底层注册失败后不重试并禁止解析。注册在 parser 解析前完成,解析期 registry 只读。bridge 所有入口捕获 Python 异常,任何失败都不留下部分写回的 Operator 或 Graph。Graph 通过 `CopyFrom` 交接,不依赖 Python 对象回调后的生命周期。
200+ 
201+### 平台化要求
202+ 
203+不涉及芯片能力和 RTS 接口,不按 SoC 分支。交付要求仅与 Host OS、CPU 架构和 Python ABI 制品匹配有关。
204+ 
205+### 特性交叉分析
206+ 
207+| 场景 | 适用性 | 分析说明 |
208+|------|--------|----------|
209+| 静态 Shape | 适用 | Python 插件在 ONNX parser 阶段生成与 C++ 插件等价的 GE 图,后续静态 Shape 优化、内存规划和 runtime/v1 不变 |
210+| 动态 Shape | 适用 | `OnnxNode` 保留动态维信息,不修改 Shape 推导和 runtime/v2,生成图按已有算子规则参与动态 Shape 编译 |
211+| 动态 Shape 静态子图(图拆分) | 适用 | 特性只发生在图拆分前的 parser 阶段,不新增跨 v2 到 v1 数据,不修改 `DavinciModelCreate/V2` 接口或拆分边界 |
212+| 离线场景(atc 编译) | 适用 | ATC 是首要入口,初始化时加载 Python 插件,OM 格式和序列化字段不变,产物只保存展开后的标准 GE 图 |
213+| 在线场景(框架适配) | 适用 | 调用 ONNX parser 的在线入口复用同一初始化,不修改 TorchAir/TF Adapter 接口,不经过 ONNX parser 的在线编译不受影响 |
214+ 
215+本特性位于 Host 侧 parser 前端,不修改 runtime/v1、runtime/v2、图拆分、内存、流或 OM 协议。测试仍需覆盖静态和动态输入模型,证明生成图在现有后续链路中行为一致。
216+ 
217+## 性能
218+ 
219+### 模型编译时长
220+ 
221+无 Python 入口时只做 `ASCEND_CUSTOM_OPP_PATH` 一层候选扫描并跳过 bridge,需设基线验证增量。有 Python 入口时增加解释器就绪、模块导入、descriptor 注册和每个自定义节点的 Python 回调时间,这是显式使用能力的编译期成本。一对多展开多一次 `Graph::CopyFrom`。本特性不进入 `optimizeStage1/optimizeStage2/build/loadmodelonline` 的执行算法,但会增加这些阶段之前的 parser 时长。验收以相同 Python 插件重复编译无持续增长、无插件场景无可观测回归为准。
222+ 
223+### OM 大小和加载占用内存
224+ 
225+OM 中只保存标准 GE 图,不保存 Python 源码、descriptor 或 bridge 状态,相同展开图的 OM 大小应与 C++ 插件等价。Python runtime 和 registry 只增加编译进程 Host 内存,Graph 复制的临时对象在回调后释放。
226+ 
227+### 执行性能
228+ 
229+Python 仅在模型解析和编译期运行,生成 OM 后不参与模型加载、算子调度或 Device 执行,执行性能预期无影响。
230+ 
231+## 接口设计
232+ 
233+### 新增/修改接口描述
234+ 
235+| 接口 | 稳定性 | 所属模块 | 功能 |
236+|------|--------|----------|------|
237+| `onnx_plugin(...)` | 对外 | `ge.onnx_plugin` | 声明 source/opset/target/expand/subgraph/backend |
238+| `register_onnx_auto_mapping(...)` | 对外 | `ge.onnx_plugin` | 注册框架级子图自动映射 |
239+| `OnnxNode` | 对外 | `ge.onnx_plugin` | 不可变 ONNX 节点源值对象 |
240+| `OperatorPatch` | 对外 | `ge.onnx_plugin` | 声明式目标 Operator 写回 |
241+| `OperatorView` | 对外 | `ge.onnx_plugin` | 只读回调级目标 Operator 投影 |
242+| `PortRef` / `DescriptorUpdate` / `DynamicPortSpec` | 对外 | `ge.onnx_plugin` | 端口引用、描述符更新、动态端口声明 |
243+| `OnnxAttrKind` / `OnnxAttrValue` | 对外 | `ge.onnx_plugin` | 源域属性判别符与值 |
244+| `AttrValue` | 对外 | `ge.onnx_plugin` | 目标域公开属性值包装 |
245+| `Backend` | 对外 | `ge.onnx_plugin` | TVM/AI_CPU/CUSTOM/GELOCAL 后端枚举 |
246+ 
247+公开函数以 Python 异常表示错误,不暴露 GE `Status`。bridge 在 C ABI 边界包含所有失败,具体异常类与状态码映射在实现阶段和接口评审时确定,本文不预设映射表。`ge.graph.Graph``Tensor``TensorDesc``Shape``DataType``Format``ge.es.GraphBuilder``TensorHolder` 均为复用,不在本表重复。
248+ 
249+### 接口检查项
250+ 
251+| 检查项 | 子检查项 | 是否涉及 | 结论 |
252+|--------|----------|----------|------|
253+| 接口说明 | 是否需要评审 | 是 | `onnx_plugin``OnnxNode``OperatorPatch``OperatorView` 是新增对外接口,开发前需接口评审 |
254+| 接口说明 | 是否补充资料 | 是 | 需新增插件开发指南和 ThresholdedRelu 样例 |
255+| 接口说明 | 原型、功能、返回值 | 是 | 本节给出原型,落地后生成正式 API 文档 |
256+| 接口兼容 | 修改前后行为 | 否 | 只新增接口,不修改现有 C++ 插件接口 |
257+| 接口兼容 | 新接口在老版本 | 是 | 老版本无该 Python 能力,Python 插件包需声明最低 CANN 版本 |
258+| 接口兼容 | 调用时序约束 | 是 | 插件模块必须在 parser 初始化时可发现,注册后不可动态增删 |
259+| 接口约束 | 非法调用报错 | 是 | 导入、descriptor 校验和 callback 三层给出明确异常或日志 |
260+| 接口约束 | 单独测试 | 是 | Python API、bridge ABI 和 parser E2E 分层测试 |
261+ 
262+## 软件设计
263+ 
264+### 关键数据结构
265+ 
266+| 数据结构 | 所有权与生命周期 | 说明 |
267+|----------|------------------|------|
268+| `PluginDescriptor` | Python registry 进程级持有,初始化后冻结 | source/opset/target/backend 和函数标识,不保存 C++ 对象 |
269+| `OnnxNode` | 单次回调 Python 值,回调结束可释放 | 从 NodeProto 复制的不可变源输入 |
270+| `OperatorPatch` | 用户函数新建,bridge 校验后消费 | 声明式写回,不持有 Operator |
271+| `OperatorView` | 单次回调 Python 值,只读 | 从 Operator 复制的必要字段,回调结束可释放 |
272+| callback dispatch map | bridge 进程级只读 | 完整 origin type 到 Python callable 的映射 |
273+| Python `Graph` | expand 回调局部持有 | `CopyFrom` 后可释放,不与 parser 输出图共享所有权 |
274+ 
275+### 关键技术/算法
276+ 
277+**NodeProto 边界**:现有 `OnnxCustomParserAdapter``Message*` 视为 ONNX NodeProto。新增 wrapper 在 bridge 内完成类型确认和转换,不把 opaque handle 交给 Python。错误在 ABI 边界被发现,公开层只处理稳定值。
278+ 
279+**注册时序与查找键**:ONNX parser 先构造完整 origin type 再进入 parse callback。bridge 直接使用该完整字符串调度 Python 函数。图展开阶段从 `ATTR_NAME_FRAMEWORK_ORIGINAL_TYPE` 读取同一字符串,按 `(target, origin_type)` 查找 `ParseOpToGraphFn`
280+ 
281+**ES Graph 所有权**`GraphBuilder.build_and_reset()` 返回 Python `Graph`,builder 随后不可复用。bridge 用 `Graph::CopyFrom` 把结果复制到 parser 提供的输出引用,明确区分 Python 临时图和 parser 所有图。
282+ 
283+**typed 源分发**:CB-001 源是 `OnnxNode`,CB-002 和 CB-006 源是 `OperatorView`。bridge 按注册的回调类型选择源适配器,不使用 `parse_by_op` 标志。
284+ 
285+**并发约束**:注册只在 parser 初始化阶段执行,完成后 descriptor 和 dispatch map 只读。不新建线程,回调遵守现有 parser 和 graph 单线程变更约束。解析期 registry 只读,回调期间不修改全局注册状态。
286+ 
287+### 流程设计
288+ 
289+```mermaid
290+sequenceDiagram
291+ participant Init as AclParserInitialize
292+ participant Cpp as C++ plugin registry
293+ participant Loader as Python bridge loader
294+ participant Py as ge.onnx_plugin
295+ participant Reg as OpRegistry
296+ participant Parser as OnnxModelParser
297+ 
298+ Init->>Cpp: Finalize/Register C++ registrationDatas
299+ Init->>Loader: 加载 Python 插件
300+ Loader->>Py: 导入路径并读取 descriptors
301+ loop 每个完整 origin_type
302+ Loader->>Reg: GetOmTypeByOriOpType(origin_type)
303+ alt 已有 C++ 或更早注册
304+ Loader-->>Loader: 记录 skip,不覆盖
305+ else 未注册
306+ Loader->>Reg: Finalize + Register Python wrapper
307+ end
308+ end
309+ Init->>Init: 完成 parser 初始化
310+ Parser->>Reg: 按 target + origin_type 取回调
311+ Reg-->>Parser: C++ wrapper
312+ Parser->>Py: wrapper 转值后按 typed 源调用 Python
313+```
314+ 
315+C++ 插件先完成注册,Python 注册与冲突检查在 parser 初始化完成前执行,使 Python 可做显式 origin 冲突检查,且 Python 新 target creator 在解析开始前建立。
316+ 
317+参数解析流程:ONNX parser 构造完整 origin type 和 target,`OpRegistry` 返回 custom parser adapter,adapter 调用 Python C++ wrapper,wrapper 按 typed 源构造 `OnnxNode``OperatorView`,调用 Python parse,校验 `OperatorPatch` 后一次性写入 Operator,失败不部分提交。
318+ 
319+一对多流程:parser 完成全图解析后 `ExpandOneToManyGraph` 读取 target 和 original_type,有 expand wrapper 才调用 Python,Python 用 ES 构图返回 Graph,bridge 校验并 `CopyFrom`,现有 `ExpandNodeToSubgraph` 替换原节点、重连边并拓扑排序。
320+ 
321+### 对子模块的修改
322+ 
323+| 模块 | 修改内容 |
324+|-----------|----------|
325+| `ge.onnx_plugin` Python package | 新增 decorator、registry、公开值对象、bootstrap 和内部 callback 调度 |
326+| ONNX parser bridge/loader 集成 | 新增独立 bridge SO 和 callback wrappers,接入现有 parser 初始化与解析流程,不进入 `graph_metadef` |
327+| `ge.graph` 属性支持 | 扩展私有属性值类型以覆盖 5 扩展类别,提供公开 `AttrValue` |
328+| `ge.es` 与插件发现模式 | 扩展 entry_points 发现以支持 ONNX 插件组,复用共享路径去重 |
329+| 文档与测试 | 同步模块设计文档、插件开发指南和分层 UT/ST |
330+ 
331+明确不修改:`graph_metadef` 公共依赖边界、现有 C++ `REGISTER_CUSTOM_OP` API、runtime/v1、runtime/v2、AscendIR 和 OM 格式。
332+ 
333+### 模块与接口动作表
334+ 
335+| 模块/接口 | 动作 | 能力 ID 或职责 |
336+|-----------|------|----------------|
337+| `ge.onnx_plugin` package | 新增 | 源仓不存在,服务 CB-001/002/006/007、FUCB-001、PROTO-001..007 |
338+| `OnnxNode` | 新增 | CB-001, PROTO-001, PROTO-002 |
339+| `OperatorPatch` | 新增 | CB-001/002, CB-OP-FAM-005/007/017/022 |
340+| `OperatorView` | 新增 | CB-001/002/006, CB-OP-FAM-001/004/006/015/017 |
341+| `PortRef` / `DescriptorUpdate` / `DynamicPortSpec` | 新增 | CB-OP-FAM-004..007/022, CB-TDESC-FAM-001..005 |
342+| `OnnxAttrKind` / `OnnxAttrValue` | 新增 | PROTO-002/003/005(源域) |
343+| `AttrValue`(目标公开包装) | 新增 | CB-ATTR-FAM-001, CB-OP-FAM-017(11 复用 5 扩展 6 N/A) |
344+| `Backend` enum | 新增 | ORD-B-012, ORDI-F-004 |
345+| `onnx_plugin()` decorator | 新增 | ORD-C-002, ORD-B-001..018, CB-001/002/006/007 |
346+| `register_onnx_auto_mapping()` | 新增 | FUCB-001, FR-005/006, AUTO-MAP-001 |
347+| `ge.graph.Graph` / `Tensor` / `TensorDesc` / `Shape` / `DataType` / `Format` | 复用 | CB-GRAPH/TENSOR/TDESC/SHAPE-FAM 系列 |
348+| `ge.es.GraphBuilder` / `TensorHolder` | 复用 | CB-OP-FAM-002, CB-GRAPH-FAM-002, CB-TENSOR-FAM-005..007 |
349+| `ge._internal.plugin_loader` / `_artifact_utils` | 复用 | PLUGIN-LOAD-001..003 |
350+| `ge.es._plugin_loader` | 扩展 | PLUGIN-LOAD-001..003(entry_points 模式,组名不同) |
351+| `ge.custom_op` registry pattern | 扩展 | ORD-B-001..018(descriptor 模式可复用,字段不同) |
352+| `ge.passes` bridge/session pattern | 扩展 | TYPE-ADAPT-003/004, CB-001/002/006/007 分发职责 |
353+| `ge.graph._AttrValue` | 扩展 | CB-ATTR-FAM-001,5 扩展类型 |
354+| ONNX plugin bridge SO | 新增 | TYPE-ADAPT-003/004, CB-001/002/006/007, PROTO-001..007 |
355+| ONNX plugin native binding | 新增 | PROTO-001..007, CB-OP-FAM-001..027 |
356+| C++ 运行时管理器与 .so 加载器 | N/A | C++ only,Python 不管理解释器生命周期或 .so 加载 |
357+ 
358+### 错误处理
359+ 
360+#### 系统错误
361+ 
362+bridge 制品缺失或不匹配时,有 Python ONNX 插件入口则初始化失败,无入口不加载。Python runtime 初始化失败时保存失败状态,当前和后续 ONNX 解析均返回失败,不重复半初始化。内存分配或 Graph 复制失败时记录 origin 和 node,返回失败并释放临时 Python 对象。模块导入或运行时异常时捕获 traceback,返回初始化或 callback 失败。
363+ 
364+#### 接口错误
365+ 
366+source/opset/target 非法时 Python 层抛出明确异常,不产生 descriptor。Python-Python 重复 origin 抛出明确异常,初始化失败。与 C++ origin 冲突时不抛异常,输出 INFO 日志并跳过 Python origin。Message 不是 NodeProto 时不进入 Python,bridge 返回参数非法错误。属性类型不支持或 `OperatorPatch` 字段非法时 Python 层抛出明确异常,bridge 返回参数非法错误。用户函数抛异常时保留原异常 traceback,bridge 返回失败。expand 返回非 Graph 或非法图时 Python 层抛出明确异常,bridge 返回参数非法错误。具体异常类与状态码映射在实现阶段和接口评审时确定,本文不预设映射表。
367+ 
368+## 安全检查
369+ 
370+### 编码军规
371+ 
372+所有路径先规范化,再按现有 loader 规则限制为文件、单层目录和 package,不递归扫描任意目录。C++ 不信任 Python 返回类型和长度,写回前完整校验,整数转换检查范围。日志不输出 Tensor 内容、用户模型数据或环境变量完整值。Python 异常不穿越 C ABI,C++ 资源使用 RAII,所有 callback 路径释放临时对象。
373+ 
374+### 编码检查项
375+ 
376+| 检查项 | 是否涉及 | 设计结论 |
377+|--------|----------|----------|
378+| 是否涉及资源生命周期管理 | 是 | Python runtime 进程级,descriptor 进程级只读,OnnxNode/OperatorPatch/OperatorView/Graph 单回调级,需设计评审 |
379+| 是否创建新线程 | 否 | 复用调用线程,不新建线程 |
380+ 
381+插件文件是用户主动配置的受信任代码,具备任意 Python 执行能力,本文不把它当作不可信模型输入,也不承诺进程内沙箱隔离。
382+ 
383+## 兼容性检查
384+ 
385+现有 C++ 插件不修改,加载顺序和显式冲突检查保证其优先。没有 Python 插件时,现有 ONNX 解析结果、OM 结构和运行时行为不变。Python 插件生成的图只含已有标准 GE IR,老 OM 在新版本执行不受影响。新 Python 插件不能在不含该 API 和 bridge 的老版本运行,插件包需声明最低版本并在导入时给出清晰错误。同一模型若 C++ 和 Python 都声明完整 origin type,使用 C++,移除 C++ 插件后才启用 Python,实现可预测切换。
386+ 
387+## DT 设计
388+ 
389+### 测试边界
390+ 
391+Python UT 入口为 `ge.onnx_plugin`、descriptor registry 和值对象,出口为值、异常和 descriptor 内容。C++ bridge UT 入口为构造 `NodeProto``Operator` 调 wrapper,出口为状态、Operator 属性、动态端口和 Graph 结构。parser UT/ST 入口为 ONNX 文件或内存解析及 ATC,出口为 GE 图 target、`original_type`、属性、展开拓扑和编译结果。打桩边界为 Python 模块导入、runtime manager、`GetOmTypeByOriOpType` 和 Graph `CopyFrom`
392+ 
393+### 测试设计
394+ 
395+| 测试类别 | 关键测试项 | 测试方法 | 用例类型 |
396+|----------|------------|----------|----------|
397+| 功能 | decorator 展开 opset、typed 源分发、动态 IO、描述符更新 | 纯 Python 构造 descriptor 和 OperatorPatch | UT |
398+| 功能 | NodeProto 到 OnnxNode 到 OperatorPatch 到 Operator | 覆盖 scalar、list、Tensor 属性和缺省值 | UT |
399+| 功能 | ES 一对多展开和 CopyFrom 所有权 | 校验 Data、Identity、Threshold、Mul 和边关系 | UT/ST |
400+| 性能 | 无插件和 1/100/1000 节点 | 对比 parser 时长与 Host 峰值内存 | ST |
401+| 精度 | Python 与 C++ 等价插件生成图对比 | 比较属性、动态端口和展开拓扑 | ST |
402+| 兼容性 | 仅 C++、仅 Python、C++ 加 Python | 比较生成图和最终 ATC 结果 | ST |
403+| 特性交叉 | 静态 Shape、动态 Shape、动态图静态子图 | 编译同语义模型并校验后续图流程 | ST |
404+ 
405+### 测试框架设计
406+ 
407+现有 Python pytest、GE parser gtest 和 ST 框架可覆盖全部交付批次,不新增测试框架。bridge UT 增加最小 Python fixture 模块,ST 复用 ONNX 模型生成和 ATC 调用能力。
408+ 
409+## 验收标准
410+ 
411+1. 插件作者可用装饰器、一个 parse 函数和可选 expand 或 subgraph 函数完成 ThresholdedRelu,不接触 protobuf、Operator、PluginContext、状态码或字符串型 `graph.op`
412+2. C++ bridge 在进入 Python 前完成 NodeProto 类型识别,Python 只收到不可变值。
413+3. C++ 与 Python 同时声明完整 origin type 时,C++ 回调和 target 均不被覆盖,日志明确记录跳过原因。
414+4. 无 expand 的 descriptor 不注册 `ParseOpToGraphFn`,有 expand 时 ES 图经 `Graph::CopyFrom` 后可被正常展开。
415+5. 无 Python 插件场景行为兼容,已有 C++ ONNX 插件 UT/ST 全部通过。
416+6. Python 异常、非法返回和首次初始化失败均可稳定复现并给出明确错误,进程无崩溃、悬空引用和资源泄漏。
417+7. 静态 Shape、动态 Shape、离线 ATC 和相关在线 ONNX 解析场景验证通过,OM 格式和执行性能不变。
418+ 
419+## 交付批次
420+ 
421+交付批次与接口覆盖样例分离。样例证明接口覆盖,批次序列化实现优先级,不把样例称为 V1/V1.1/V2 代表,也不因前期分期推迟已审批的公开 API。
422+ 
423+| 批次 | 范围 | 内容 |
424+|------|------|------|
425+| 1 公开值对象与 API | 全部审批公开类与装饰器 | OnnxNode、OperatorPatch、OperatorView、PortRef、DescriptorUpdate、DynamicPortSpec、OnnxAttrKind/Value、AttrValue、Backend、onnx_plugin、register_onnx_auto_mapping |
426+| 2 bridge 与 loader 集成 | bridge SO 与 parser 接入 | NodeProto 适配、typed 源分发、callback wrappers、C++ 注册时序、冲突检查、异常翻译 |
427+| 3 子图与框架工具覆盖 | CB-006/007 与自动映射 | ES 一对多展开、CopyFrom 所有权、subgraph 后处理、register_onnx_auto_mapping |
428+ 
429+每批次至少迁移一个 ops-nn C++ 插件作为等价基线,全部公开 API 在批次 1 完成,不推迟到后续批次。
430+ 
431+## 评审意见追溯
432+ 
433+PR 4151 的 11 条评审意见已在 [评审意见原文](onnx_plugin_python_pr_4151_review_comments.md) 中记录,核心结论映射如下:使用 ES 生成算子包而非字符串工厂,bridge 使用独立 SO 且不进 graph_metadef,NodeProto 类型识别在 C++ 完成,不公开 PluginContext,C++ 注册优先通过加载顺序加显式 origin 检查保证,API 以插件作者易用性为中心隐藏 protobuf 和状态码。详细评论与方案章节的映射见评审意见文档。
434+ 
435+## 设计文档检查结果
436+ 
437+- [x] 跨特性交叉影响:已按 `cross_feature_check.md` 逐场景分析静态 Shape、动态 Shape、动态 Shape 静态子图、离线 ATC 和在线框架适配,本特性位于 parser 前端,不新增 v2 到 v1 数据,不修改 DavinciModel、图拆分或 runtime 接口。
438+- [x] 关键设计原则:已加载 graph_metadef 约束,Python runtime 和 bridge 不进入 `graph_metadef`,接口只新增不修改现有 C++ 接口,图基础结构不包含业务状态,图变更单线程回调级,不区分芯片,不新增线程,不修改 OM 序列化格式因为不新增 Graph 字段。
439+- [x] 接口完整性:已给出装饰器、typed 回调签名、输入输出值对象、调用时序和异常语义。
440+- [x] 可验证性:已定义分层 UT/ST、冲突、生命周期、性能和 E2E 验收标准。
@@ -0,0 +1,292 @@
1+# PR 4151 ONNX Plugin Python 化评审评论记录
2+ 
3+## 1. 文档范围
4+ 
5+本文记录 [cann/ge PR 4151](https://gitcode.com/cann/ge/pull/4151) 中与 ONNX Plugin Python 化需求和方案直接相关的人工评审意见,并将每条行内评论映射到评审时的原始文档内容。
6+ 
7+| 项目 | 内容 |
8+|------|------|
9+| PR | `cann/ge#4151` |
10+| PR 标题 | `【PR】: 新增 ONNX Plugin Python 化分析与框架理解文档` |
11+| 评审对应 head SHA | `d82ce1f263b05d5c33e1f0b7a13a384454697395` |
12+| 评审对象 | [原始需求分析](../requirements/onnx_plugin_python_requirement_analysis.md)、[原始修改方案](./onnx_plugin_python_modification_plan.md) |
13+| 收录范围 | 10 条人工行内评论、1 条人工普通评论 |
14+| 数据来源 | GitCode v5 PR comments API 和单条评论详情 API |
15+| 拉取日期 | 2026-07-29 |
16+ 
17+当前工作区中的需求和方案已经逐字回退到上述 head SHA,因此本文引用的行号、段落和代码与评论产生时一致。评论原文保持不变;“评审关注点”是根据评论原文做的索引性归纳,不代替评审者原话,也不表示意见已经落实。
18+ 
19+## 2. 评论索引
20+ 
21+| 评论 ID | 作者 | 时间 | 评审关注点 | PR 位置 |
22+|---------|------|------|------------|---------|
23+| `181877622` | `kobemini` | 2026-07-27 17:35:54 +08:00 | 构图优先使用 ES 生成算子包 | 方案第 127 行 |
24+| `181877832` | `kobemini` | 2026-07-27 17:36:35 +08:00 | 明确评估或移植 `ge.custom_op` 的目的 | 方案第 127 行 |
25+| `181878679` | `kobemini` | 2026-07-27 17:39:29 +08:00 | `NodeProto` 类型识别应在 C++ bridge 完成 | 方案第 143 行 |
26+| `181879102` | `kobemini` | 2026-07-27 17:40:55 +08:00 | 不建议公开 `PluginContext` | 方案第 177 行 |
27+| `181879397` | `kobemini` | 2026-07-27 17:42:05 +08:00 | 补充 ES 构图示例 | 方案第 194-206 行 |
28+| `181879709` | `kobemini` | 2026-07-27 17:43:19 +08:00 | 说明 C++ 注册优先的保证机制 | 方案第 7 行 |
29+| `181880610` | `kobemini` | 2026-07-27 17:46:33 +08:00 | 调查全部 plugin.cc 和 Parse 接口并分期 | 方案第 144 行 |
30+| `181880795` | `kobemini` | 2026-07-27 17:47:17 +08:00 | bridge 使用独立 SO | 方案第 145 行 |
31+| `181880993` | `kobemini` | 2026-07-27 17:48:14 +08:00 | 公开接口应更 Pythonic | 方案第 179-190 行 |
32+| `181903794` | `kobemini` | 2026-07-27 19:52:20 +08:00 | 使用直接表达 source/opset/target 的装饰器 | 方案第 173 行 |
33+| `181948337` | `gentle-knight` | 2026-07-28 08:46:56 +08:00 | 以插件作者易用性为中心,不逐行翻译 C++ API | PR 普通评论 |
34+ 
35+## 3. 行内评审评论
36+ 
37+### 3.1 ES 算子包优先于通用字符串工厂
38+ 
39+**评论信息**:ID `181877622`,作者 `kobemini`,时间 `2026-07-27T17:35:54+08:00`
40+ 
41+**评审位置**:[修改方案第 4 节“修改点清单”](./onnx_plugin_python_modification_plan.md#4-修改点清单),PR head 第 127 行。
42+ 
43+**被评审内容**
44+ 
45+> `es/graph_builder.py` 补通用 `op(type, name, **inputs) -> TensorHolder` 工厂;先确认 `es_math`/`es_nn` 覆盖范围,不覆盖再扩展 ES C API,同时评估 TorchAir `ge.custom_op` 能否移植。
46+ 
47+**评论原文**
48+ 
49+> op(type, name, **inputs) -- 这个接口定位是什么?parse_op_to_graph里面的构图接口如果是算子级别的 优先使用es_xx的算子包接口
50+ 
51+**评审关注点**:算子级构图应优先复用 `es_xx` 生成算子接口,而不是先设计按字符串创建任意算子的通用工厂。
52+ 
53+### 3.2 明确 `ge.custom_op` 评估目的
54+ 
55+**评论信息**:ID `181877832`,作者 `kobemini`,时间 `2026-07-27T17:36:35+08:00`
56+ 
57+**评审位置**:[修改方案第 4 节“修改点清单”](./onnx_plugin_python_modification_plan.md#4-修改点清单),PR head 第 127 行,与上一条评论指向同一表格项。
58+ 
59+**被评审内容**
60+ 
61+> `es/graph_builder.py` 的扩展项提出“同时评估 TorchAir `ge.custom_op` 能否移植”。
62+ 
63+**评论原文**
64+ 
65+> 移植ge.custom_op的目的是?
66+ 
67+**评审关注点**:在决定复用或移植前,需要先说明 `ge.custom_op` 与 ONNX parser 插件之间的目标关系和预期收益。
68+ 
69+### 3.3 在 C++ bridge 完成 `NodeProto` 类型识别
70+ 
71+**评论信息**:ID `181878679`,作者 `kobemini`,时间 `2026-07-27T17:39:29+08:00`
72+ 
73+**评审位置**:[修改方案第 6 节“待对齐”](./onnx_plugin_python_modification_plan.md#6-待对齐),PR head 第 143 行。
74+ 
75+**被评审内容**
76+ 
77+> protobuf 跨语言传递:C++ `Message*` 如何传到 Python。GE 仓编译用 `-Dgoogle=ascend_private` 重命名 protobuf 命名空间,pybind11 直接包装不可行,**opaque handle 是唯一可行方案**(参考 `passes/` 的 `borrow_node(uintptr_t)` 模式)。
78+ 
79+**评论原文**
80+ 
81+> python的接口是否可以封装为ge::onnx::NodeProto的包装?即dynamic_cast 不应该让 Python 处理,而应当在 C++ bridge 进入 Python之前完成
82+ 
83+**评审关注点**:通用 protobuf message 到 `ge::onnx::NodeProto` 的类型确认属于 C++ bridge 边界职责,Python 不应承担 `dynamic_cast` 或通用 message 类型判断。
84+ 
85+### 3.4 不建议公开 `PluginContext`
86+ 
87+**评论信息**:ID `181879102`,作者 `kobemini`,时间 `2026-07-27T17:40:55+08:00`
88+ 
89+**评审位置**:[修改方案第 7 节“Python 版 ThresholdedRelu 插件预览”](./onnx_plugin_python_modification_plan.md#7-python-版-thresholdedrelu-插件预览),PR head 第 177 行。
90+ 
91+**被评审内容**
92+ 
93+```python
94+def parse_params(self, ctx: PluginContext) -> int:
95+```
96+ 
97+方案中的 [模块清单](./onnx_plugin_python_modification_plan.md#4-修改点清单) 还计划在 `ge/onnx_plugin/` 中新增包含 `.onnx``.dest``.graph``PluginContext`
98+ 
99+**评论原文**
100+ 
101+> 封装PluginContext的好处是啥?不建议封装
102+ 
103+**评审关注点**:公开 API 不应通过 context 对象逐项复刻 C++ 回调上下文,需要重新评估更直接的 Python 输入和返回模型。
104+ 
105+### 3.5 补充 ES 构图示例
106+ 
107+**评论信息**:ID `181879397`,作者 `kobemini`,时间 `2026-07-27T17:42:05+08:00`
108+ 
109+**评审位置**:[修改方案第 7 节“Python 版 ThresholdedRelu 插件预览”](./onnx_plugin_python_modification_plan.md#7-python-版-thresholdedrelu-插件预览),PR head 第 194-206 行。
110+ 
111+**被评审内容**
112+ 
113+```python
114+ori_name = ctx.dest.get_attr("name")
115+alpha = ctx.dest.get_attr("alpha")
116+ 
117+data = ctx.graph.op("Data", f"{ori_name}_data1").set_attr("index", 0)
118+identity = ctx.graph.op("Identity", f"{ori_name}_identity").set_input("x", data)
119+threshold = ctx.graph.op("Threshold", f"{ori_name}_threshold") \
120+ .set_input("x", identity).set_attr("threshold", alpha)
121+mul = ctx.graph.op("Mul", f"{ori_name}_mul") \
122+ .set_input("x1", identity).set_input("x2", threshold)
123+ 
124+ctx.graph.set_inputs([data]).set_outputs([(mul, 0)])
125+```
126+ 
127+**评论原文**
128+ 
129+> 需要给一个es构图的示例
130+ 
131+**评审关注点**:当前预览使用的是拟新增的 `ctx.graph.op` 接口;方案还需给出使用现有 ES 能力构造同一子图的完整示例。
132+ 
133+### 3.6 说明 C++ 注册优先的保证机制
134+ 
135+**评论信息**:ID `181879709`,作者 `kobemini`,时间 `2026-07-27T17:43:19+08:00`
136+ 
137+**评审位置**:[修改方案第 1 节“核心思路”](./onnx_plugin_python_modification_plan.md#1-核心思路),PR head 第 7 行。
138+ 
139+**被评审内容**
140+ 
141+> Python 化就是让注册、ParseParams、ParseOpToGraph 能用 Python 写,**与现有 C++ 插件互补共存**(同 `(ge_op, origin_type)` key 互斥,C++ 已注册的 Python 不覆盖)。
142+ 
143+方案随后提出调用 `OpRegistry::Register` 时传入 `is_custom_op=false`,依赖同 key 已存在时的 skip 行为。
144+ 
145+**评论原文**
146+ 
147+> C++ 已注册的 Python 不覆盖 -- 这个是哪里保证的
148+ 
149+**评审关注点**:需要用明确的注册顺序、冲突检测和 `OpRegistry` 行为证明“不覆盖”,不能只声明共存结论。
150+ 
151+### 3.7 调查全部 plugin.cc 并制定分期
152+ 
153+**评论信息**:ID `181880610`,作者 `kobemini`,时间 `2026-07-27T17:46:33+08:00`
154+ 
155+**评审位置**:[修改方案第 6 节“待对齐”](./onnx_plugin_python_modification_plan.md#6-待对齐),PR head 第 144 行。
156+ 
157+**被评审内容**
158+ 
159+> 回调覆盖范围:metadef 定义了 7 种回调 typedef。MVP 通过完整 `Register()` 一次性覆盖 `ParseParamFunc`、`ParseParamByOpFunc` 和 `ParseOpToGraphFunc`;`ParseSubgraphFunc`/`ParseSubgraphFuncV2` 因 ThresholdedRelu 示例未使用而暂不纳入,`FusionParseParam*` 可后续支持。
160+ 
161+原始需求也明确说明当前分析基于单个 [ThresholdedRelu 示例](../requirements/onnx_plugin_python_requirement_analysis.md#1-目标能力)。
162+ 
163+**评论原文**
164+ 
165+> threshold_relu_onnx_plugin.cpp只是其中一个例子,有很多plugin.cc,里面覆盖了其他的Parse接口,所以不应该限制在这个文件,需要全部看一下,具体开发可以分步来,先支持哪些,后续计划支持哪些(比如ParseSubgraph相关的)
166+ 
167+**评审关注点**:回调范围不能只由一个示例推导;应调查其他插件文件的实际接口使用,再明确首批支持范围和后续阶段。
168+ 
169+### 3.8 bridge 使用独立 SO
170+ 
171+**评论信息**:ID `181880795`,作者 `kobemini`,时间 `2026-07-27T17:47:17+08:00`
172+ 
173+**评审位置**:[修改方案第 6 节“待对齐”](./onnx_plugin_python_modification_plan.md#6-待对齐),PR head 第 145 行。
174+ 
175+**被评审内容**
176+ 
177+> bridge 编入位置:编入 `fmk_onnx_parser`、独立 `.so` 或编入 `graph_metadef`?需根据依赖关系评估。方案认为 Pass 场景的 bridge `.so` 原则不能直接照搬。
178+ 
179+**评论原文**
180+ 
181+> bridge的so推荐是独立的so,不建议编入graph_metadef,可以参考pass的bridge的so
182+ 
183+**评审关注点**:优先采用独立 bridge SO,并参考现有 Python Pass bridge 的依赖隔离方式,不将 Python bridge 编入 `graph_metadef`
184+ 
185+### 3.9 公开接口应更 Pythonic
186+ 
187+**评论信息**:ID `181880993`,作者 `kobemini`,时间 `2026-07-27T17:48:14+08:00`
188+ 
189+**评审位置**:[修改方案第 7 节“Python 版 ThresholdedRelu 插件预览”](./onnx_plugin_python_modification_plan.md#7-python-版-thresholdedrelu-插件预览),PR head 第 179-190 行。
190+ 
191+**被评审内容**
192+ 
193+```python
194+onnx_node = ctx.onnx
195+alpha = 1.0
196+for attr in onnx_node.attributes:
197+ if attr.name == "alpha":
198+ alpha = attr.f
199+ 
200+ctx.dest.set_attr("name", onnx_node.name)
201+ctx.dest.set_attr("alpha", alpha)
202+ctx.dest.dynamic_input_register("x", 1)
203+ctx.dest.dynamic_output_register("y", 1)
204+```
205+ 
206+**评论原文**
207+ 
208+> 可以考虑是否有更pythonic的写法
209+ 
210+**评审关注点**:属性读取、结果描述和动态端口声明不应逐行翻译 C++ `NodeProto``Operator` API,应利用 Python 值、默认参数和声明式返回结构降低作者负担。
211+ 
212+### 3.10 使用直接的注册装饰器
213+ 
214+**评论信息**:ID `181903794`,作者 `kobemini`,时间 `2026-07-27T19:52:20+08:00`
215+ 
216+**评审位置**:[修改方案第 7 节“Python 版 ThresholdedRelu 插件预览”](./onnx_plugin_python_modification_plan.md#7-python-版-thresholdedrelu-插件预览),PR head 第 173 行;评论附着在原 `@register(...)` 注册块结尾。
217+ 
218+**被评审内容**
219+ 
220+```python
221+@register(
222+ om_optype="PartitionedCall",
223+ origin_op_types=[
224+ "ai.onnx::10::ThresholdedRelu",
225+ # 中间版本省略
226+ "ai.onnx::18::ThresholdedRelu",
227+ ],
228+ fmk_type="ONNX",
229+ imply_type="TVM",
230+)
231+```
232+ 
233+**评论原文**
234+ 
235+```python
236+ @onnx_plugin(
237+ source="ai.onnx::ThresholdedRelu",
238+ opset=range(10, 18),
239+ target="PartitionedCall",
240+ )
241+```
242+ 
243+> 可否就直接写成类似这样的装饰器
244+ 
245+**评审关注点**:注册接口可直接表达源算子、opset 范围和目标算子,隐藏 framework、版本化 origin type 列表及默认 imply type 等机械信息。
246+ 
247+## 4. PR 普通评审评论
248+ 
249+### 4.1 以插件作者易用性为中心设计 API
250+ 
251+**评论信息**:ID `181948337`,作者 `gentle-knight`,时间 `2026-07-28T08:46:56+08:00`
252+ 
253+**关联内容**:该评论没有行内位置,针对整体 Python API 设计。主要关联以下原始内容:
254+ 
255+1. [需求分析第 1 节示例](../requirements/onnx_plugin_python_requirement_analysis.md#1-目标能力)使用 `ctx.onnx``ctx.dest``ctx.graph` 暴露逐项操作接口。
256+2. [修改方案第 4 节](./onnx_plugin_python_modification_plan.md#4-修改点清单)计划新增 `PluginContext``OperatorBinding`
257+3. [修改方案第 7 节](./onnx_plugin_python_modification_plan.md#7-python-版-thresholdedrelu-插件预览)把 C++ 注册、属性遍历、状态返回和构图步骤逐项翻译为 Python。
258+ 
259+**被评审内容示例**
260+ 
261+```python
262+def thresholded_relu(ctx):
263+ alpha = ctx.onnx.attr("alpha", default=1.0, dtype="float")
264+ ctx.dest.set_attr("name", ctx.onnx.name)
265+ ctx.dest.dynamic_input("x", 1)
266+ ctx.dest.dynamic_output("y", 1)
267+```
268+ 
269+**评论原文**
270+ 
271+> 有一个最重要的点是python化的插件注册要体现python语言的简介不必拘泥于c++的写法一行一行的翻译,所以可以进行适当的封装(比如屏蔽protobuf,operator类等,但是不应该是ctx,ctx依次提供c++对应的pythonapi这种),这方面可以多做考虑,优化一下设计界面,假设你是要写插件的人 怎么写简单怎么来
272+ 
273+**评审关注点**:Python 接口应屏蔽 protobuf、Operator 和 C++ 回调结构,不采用 context 对象逐项提供 C++ API 的方式;设计应从插件作者的最简写法出发。
274+ 
275+## 5. 未纳入正文的远端评论
276+ 
277+PR API 共返回 16 条评论。除正文收录的 11 条人工设计评审外,以下 5 条不属于 ONNX Plugin 需求/方案人工评审,故只记录索引,不混入设计要求:
278+ 
279+| 评论 ID | 作者 | 类型 | 未纳入原因 |
280+|---------|------|------|------------|
281+| `181828411` | `gitcode-bot` | 普通评论 | 自动生成的 PR 变更摘要 |
282+| `181828413` | `gitcode-bot` | 普通评论 | 自动安全和文档扫描总结 |
283+| `181828449` | `cann-robot` | 普通评论 | CLA 与审批进度机器人消息 |
284+| `181835725` | `gitcode-bot` | 行内评论 | 指向 `custom_op-samples-results.md` 的样例统计问题 |
285+| `181835728` | `gitcode-bot` | 行内评论 | 指向 `issue_draft.md` 的失效路径问题 |
286+ 
287+## 6. 使用约束
288+ 
289+1. 本文是评论原文与评审对象之间的追溯记录,不是修订后的需求或技术方案。
290+2. 后续修改需求或方案时,应引用对应评论 ID,并在独立决策记录中说明采纳、部分采纳或不采纳及原因。
291+3. 不得把“评审关注点”当作评论原文引用;需要引用评审者意见时,应使用各条目的“评论原文”。
292+4. 如果 PR 评论发生新增或修改,应重新通过 GitCode API 拉取,并同步评论数量、时间、位置和原文。
@@ -0,0 +1,1108 @@
1+# GE 项目整体框架理解文档
2+ 
3+> **文档定位**:实习生入门 GE 项目的通用框架参考,不作为 ONNX Plugin Python 化的需求或设计来源。架构与机制结论以 [官方文档] 和 [GE 仓源码] 为主;[metadef 仓] 外部契约、[实验观察] 和 [重构示例] 分别标注。所有 `[文件名:行号](路径)` 均可点击跳转。
4+>
5+> **阅读顺序建议**:先读第 1-2 章建立全局认知 → 第 3 章理解完整链路 → 第 4 章聚焦 ONNX 解析 → 第 5 章理解插件机制 → 第 6 章看 Python 现状 → 第 7 章理解 ONNX Plugin Python 化相关的现状事实。
6+>
7+> **信息源标注**:[官方文档] = GE 仓 docs/zh/design/ 下文档;[GE 仓源码] = 仓内 .cc/.h/.py;[metadef 仓] = 需查 metadef 仓(GE 仓通过 include 引入);[实验观察] = hfqx/dev-log/ 下实验记录;[重构示例] = 依据外部接口契约、GE 调用链和实验现象重构的教学代码,非 GE 仓或上游 ops 仓逐行源码。
8+ 
9+---
10+ 
11+## 目录
12+ 
13+- [第 1 章 GE 是什么](#第-1-章-ge-是什么)
14+- [第 2 章 项目目录结构总览](#第-2-章-项目目录结构总览)
15+- [第 3 章 完整链路:从模型文件到执行](#第-3-章-完整链路从模型文件到执行)
16+- [第 4 章 ONNX 到 GE IR 的解析链路](#第-4-章-onnx-到-ge-ir-的解析链路)
17+- [第 5 章 C++ 插件机制详解](#第-5-章-c-插件机制详解)
18+- [第 6 章 Python 模块全景](#第-6-章-python-模块全景)
19+- [第 7 章 ONNX Plugin Python 化相关的现状事实](#第-7-章-onnx-plugin-python-化相关的现状事实)
20+- [第 8 章 关键文档与代码索引](#第-8-章-关键文档与代码索引)
21+ 
22+---
23+ 
24+## 第 1 章 GE 是什么
25+ 
26+### 1.1 一句话定位
27+ 
28+> GE(Graph Engine)是华为为昇腾 AI 芯片做的**图编译器和执行器**。它把用户用 PyTorch/TensorFlow/ONNX 写的模型,翻译成昇腾芯片能执行的指令,并在芯片上安排算子执行顺序、内存分配、多流并行。
29+>
30+> [官方文档:architecture.md](../../docs/zh/design/architecture.md)
31+ 
32+### 1.2 GE 在 CANN 生态中的位置
33+ 
34+```mermaid
35+flowchart LR
36+ subgraph 前端["前端框架"]
37+ PT["PyTorch"]
38+ TF["TensorFlow"]
39+ end
40+ 
41+ subgraph 适配层["适配层(在线)"]
42+ TA["TorchAir"]
43+ TFA["TF Adapter"]
44+ end
45+ 
46+ subgraph 模型文件["模型文件(离线)"]
47+ ONNX[".onnx"]
48+ PB[".pb"]
49+ end
50+ 
51+ subgraph GE["GE(图编译器+执行器)"]
52+ Parser["Parser<br/>解析"]
53+ AIR["AscendIR / ge::Graph"]
54+ Compiler["Compiler<br/>编译"]
55+ Executor["Executor<br/>执行"]
56+ Parser --> AIR
57+ AIR --> Compiler --> Executor
58+ end
59+ 
60+ OM["OM 文件<br/>(编译产物)"]
61+ NPU["昇腾 NPU"]
62+ 
63+ PT --> TA
64+ TF --> TFA
65+ TA -->|产出 AscendIR| AIR
66+ TFA -->|产出 AscendIR| AIR
67+ 
68+ ONNX -->|atc| Parser
69+ PB -->|atc| Parser
70+ 
71+ Compiler --> OM
72+ OM --> Executor
73+ Executor --> NPU
74+ 
75+ style 前端 fill:#e6f3ff,stroke:#4a90d9
76+ style 适配层 fill:#f0e6ff,stroke:#9b59b6
77+ style 模型文件 fill:#fff3cd,stroke:#ffc107
78+ style GE fill:#d4edda,stroke:#28a745
79+ style AIR fill:#fff3cd,stroke:#ffc107
80+ style OM fill:#ffe4b5,stroke:#ff8c00
81+ style NPU fill:#f8d7da,stroke:#dc3545
82+```
83+ 
84+[官方文档:architecture.md §系统架构总览](../../docs/zh/design/architecture.md)
85+ 
86+GE 有两种工作模式:
87+- **在线场景**:前端框架(PyTorch/TensorFlow)通过适配层(TorchAir/TFA)直接驱动 GE,GE 作为框架的后端(上图中蓝色/紫色路径)
88+- **离线场景**:用户用 `atc` 命令行工具把 `.onnx`/`.pb` 模型文件编译成 `.om` 文件,OM 文件可独立部署到设备执行(上图中黄色路径)
89+ 
90+### 1.3 核心概念:AscendIR(GE IR)
91+ 
92+GE 内部用一种叫 **AscendIR**(也叫 AIR)的格式描述计算图。AscendIR 是一张有向无环图(DAG),核心元素:
93+ 
94+| 元素 | 说明 |
95+|------|------|
96+| **Graph(图)** | 承载节点、边、输入输出描述,是编译的基本单元 |
97+| **Node(节点)** | 表示算子级计算单元,包含算子类型、输入输出引用及属性 |
98+| **Tensor(张量)** | 算子的输入输出数据实体,含 shape、dtype、format |
99+| **Attribute(属性)** | 构图时确定的算子附加信息(如 mode、配置参数) |
100+| **Data Edge(数据边)** | 表示 Tensor 的生产者-消费者关系 |
101+| **Control Edge(控制边)** | 表示纯依赖关系,约束执行顺序 |
102+ 
103+[官方文档:ascend-ir.md](../../docs/zh/design/modules/graph_metadef/ascend-ir.md)
104+ 
105+**关键设计特征**:图中**不存在独立的 Edge 对象**,连接关系由节点上的"锚点(Anchor)"维护。`InDataAnchor` 只能有一个 peer(单输入),`OutDataAnchor` 可以有多个 peer(扇出)。
106+ 
107+### 1.4 算子定义不在 GE 仓
108+ 
109+> **重要认知**:GE 是图编译器与执行器,**不定义每个算子的语义与实现**。算子定义在独立的算子仓(如 ops-math、ops-nn、ops-transformer),通过 `.so` 动态加载注册。
110+ 
111+[官方文档:architecture.md §算子定义体系](../../docs/zh/design/architecture.md)
112+ 
113+这意味着:
114+- GE 仓源码里 grep 不到 `MatMul`/`Conv`/`Relu` 等算子的实现
115+- 解析插件 `.so` 随 ops 包发布,位于 `$ASCEND_OPP_PATH/built-in/framework/onnx/` 下(详见 [第 5.8 节](#58-插件-so-的加载))
116+- 遇到仓内找不到的 API,先查 ops 包 / metadef 外部头(见 [dev-process-notes.md §跨包核对](../notes/dev-process-notes.md))
117+ 
118+---
119+ 
120+## 第 2 章 项目目录结构总览
121+ 
122+```
123+ge/
124+├── api/ # API 接口实现(ACL、ATC、session、Python 绑定)
125+│ ├── atc/ # atc 命令行工具(离线编译入口)
126+│ ├── session/ # Session(在线编译执行入口)
127+│ └── python/ge/ge/ # ★ Python 模块(本需求重点关注)
128+├── base/ # 基础工具方法、主机 CPU 引擎
129+├── compiler/ # ★ GE Compiler 图编译模块
130+│ └── graph/ # 图优化 pass、融合、引擎分区、构建
131+│ └── build/ # 构建(流分配、内存规划、任务生成)
132+├── runtime/ # ★ GE Executor 图执行模块
133+│ ├── v1/ # 静态 shape 执行器(Task Sink)
134+│ └── v2/ # 动态 shape 执行器(RT2.0,Lowering)
135+├── parser/ # ★ 模型格式解析器(ONNX/PB/Caffe/MindSpore)
136+│ └── parser/
137+│ ├── onnx/ # ONNX 解析器(本需求核心)
138+│ ├── common/ # 公共解析基础设施(OpRegistry 调用、插件加载)
139+│ ├── caffe/ # Caffe 解析器
140+│ └── tensorflow/ # TensorFlow 解析器
141+├── graph_metadef/ # ★ 图元数据定义和算子注册
142+│ ├── register/ # OpRegistry 实现(插件注册表单例)
143+│ └── proto/onnx/ # ONNX protobuf 定义(ge_onnx.proto)
144+├── inc/ # 头文件
145+│ ├── graph_metadef/ # graph_metadef 对外头(含 op_registry.h)
146+│ └── parser/external/ # parser 对外头(含 onnx_parser.h)
147+├── dflow/ # 分布式流框架(LLM 数据分发、UDF)
148+├── docs/ # ★ 关联文档目录
149+│ └── zh/design/ # 设计文档(架构/模块/特性/约束)
150+├── examples/ # 使用样例
151+└── tests/ # 综合测试套件(UT/ST/基准)
152+```
153+ 
154+[官方文档:architecture.md §项目结构](../../docs/zh/design/architecture.md)
155+ 
156+**本需求最相关的目录**
157+- `parser/parser/onnx/` — ONNX 解析链路
158+- `parser/parser/common/` — 公共基础设施(OpRegistry 调用、插件加载)
159+- `graph_metadef/register/` — OpRegistry 实现(插件注册表)
160+- `api/python/ge/ge/` — Python 模块
161+- `inc/graph_metadef/register/op_registry.h` — OpRegistry 声明
162+- `inc/parser/external/parser/onnx_parser.h` — ONNX 解析对外 API
163+ 
164+---
165+ 
166+## 第 3 章 完整链路:从模型文件到执行
167+ 
168+### 3.1 三大阶段总览
169+ 
170+GE 把模型从输入到执行分为三大阶段,每个阶段由独立模块负责:
171+ 
172+```mermaid
173+flowchart LR
174+ A["模型文件<br/>.onnx / .pb"] -->|Parser| B["AscendIR<br/>(GE IR 图)"]
175+ B -->|Compiler| C["OM 模型<br/>(编译产物)"]
176+ C -->|Executor| D["昇腾设备<br/>(执行)"]
177+ 
178+ style A fill:#e6f3ff,stroke:#4a90d9
179+ style B fill:#fff3cd,stroke:#ffc107
180+ style C fill:#ffe4b5,stroke:#ff8c00
181+ style D fill:#d4edda,stroke:#28a745
182+```
183+ 
184+| 阶段 | 模块 | 职责 | 关键目录 |
185+|------|------|------|----------|
186+| **Parser** | parser/ | 把外部模型格式(ONNX/PB)转成 AscendIR | `parser/parser/onnx/` |
187+| **Compiler** | compiler/ | 把 AscendIR 编译成 OM(优化、引擎分区、内存规划、任务生成) | `compiler/graph/`, `compiler/graph/build/` |
188+| **Executor** | runtime/ | 把 OM 加载到设备并执行 | `runtime/v1/`, `runtime/v2/` |
189+ 
190+[官方文档:compiler.md §1.1 CompilerStages](../../docs/zh/design/modules/compiler/compiler.md)
191+ 
192+### 3.2 Parser 阶段(本需求重点)
193+ 
194+**职责**:读取模型文件,转换成 AscendIR 的 `ge::Graph` 对象。
195+ 
196+**ONNX Parser 入口**`aclgrphParseONNX`([onnx_parser.cc:110](../../parser/parser/onnx/onnx_parser.cc#L110))
197+-`.onnx` 文件 → 反序列化成 `ge::onnx::ModelProto`(protobuf)
198+- 遍历每个 `NodeProto` → 转成 GE 的 `Operator` → 加入 `ge::Graph`
199+- 返回填充好的 `ge::Graph` 给调用方
200+ 
201+**关键特征**:Parser **不调用 Compiler**。Parser 通过 `aclgrphParseONNX` 的 out-param 把 `ge::Graph` 返回给调用方(atc/session),由调用方再交给 Compiler。Parser 与 Compiler 的边界就在 `aclgrphParseONNX` 的返回处。
202+ 
203+[GE 仓源码:onnx_parser.cc:110-140](../../parser/parser/onnx/onnx_parser.cc#L110)
204+ 
205+### 3.3 Compiler 阶段
206+ 
207+**职责**:把 AscendIR 编译成可在昇腾设备执行的 OM 模型。
208+ 
209+**四大子阶段**([官方文档:compiler.md §1.1](../../docs/zh/design/modules/compiler/compiler.md)):
210+ 
211+```mermaid
212+flowchart TD
213+ AIR["AscendIR<br/>(GE IR 图)"] --> S1
214+ 
215+ subgraph S1["阶段1: 预处理"]
216+ S1a["图规范化"] --> S1b["Shape 推理"] --> S1c["量化准备"]
217+ end
218+ 
219+ subgraph S2["阶段2: 图优化"]
220+ S2a["通用优化<br/>CSE / 常量折叠 / DCE"] --> S2b["融合优化<br/>Pattern 融合 + 自动融合"] --> S2c["精度/格式调整"]
221+ end
222+ 
223+ subgraph S3["阶段3: 引擎分区"]
224+ S3a["引擎分配"] --> S3b["组合引擎分区"] --> S3c["原子引擎分区"] --> S3d["子图优化"]
225+ end
226+ 
227+ subgraph S4["阶段4: 构建"]
228+ S4a["流分配"] --> S4b["内存规划"] --> S4c["任务生成"] --> S4d["模型序列化"]
229+ end
230+ 
231+ S1 --> S2 --> S3 --> S4
232+ S4 --> OM["OM 模型<br/>(编译产物)"]
233+ 
234+ style AIR fill:#fff3cd,stroke:#ffc107
235+ style S1 fill:#e6f3ff,stroke:#4a90d9
236+ style S2 fill:#e6f3ff,stroke:#4a90d9
237+ style S3 fill:#e6f3ff,stroke:#4a90d9
238+ style S4 fill:#e6f3ff,stroke:#4a90d9
239+ style OM fill:#ffe4b5,stroke:#ff8c00
240+```
241+ 
242+四大子阶段:
243+1. **预处理**:图规范化、Shape 推理、量化准备
244+2. **图优化**:CSE/常量折叠/DCE(通用优化)+ Pattern 融合/自动融合(融合优化)
245+3. **引擎分区**:按引擎(AIcore/Vector/AI_CPU/HCCL...)划分子图
246+4. **构建**:流分配、内存规划、任务生成、序列化成 OM
247+ 
248+**编译产物**`GeRootModel`,包含根图 + 子图模型映射 + 任务序列 + 权重 + TBE Kernel。序列化为 OM 文件后可独立部署。
249+ 
250+[官方文档:compiler.md](../../docs/zh/design/modules/compiler/compiler.md)
251+ 
252+### 3.4 Executor 阶段
253+ 
254+**职责**:把 OM 模型加载到设备并执行。
255+ 
256+**双版本架构**([官方文档:runtime.md](../../docs/zh/design/modules/runtime/runtime.md)):
257+ 
258+```mermaid
259+flowchart LR
260+ OM["OM 模型"] --> Split{模型类型?}
261+ 
262+ subgraph V1["v1 静态 shape 执行器"]
263+ V1a["DavinciModel<br/>核心对象"]
264+ V1b["Task Sink<br/>任务预下发到设备"]
265+ V1c["rtModelExecute<br/>Host 一次触发"]
266+ V1a --> V1b --> V1c
267+ end
268+ 
269+ subgraph V2["v2 动态 shape 执行器"]
270+ V2a["ModelV2Executor<br/>核心对象"]
271+ V2b["Lowering<br/>ComputeGraph→ExecuteGraph"]
272+ V2c["Host 顺序/拓扑执行"]
273+ V2a --> V2b --> V2c
274+ end
275+ 
276+ Split -->|静态 shape| V1
277+ Split -->|动态 shape| V2
278+ V1 --> NPU["昇腾 NPU"]
279+ V2 --> NPU
280+ 
281+ style OM fill:#ffe4b5,stroke:#ff8c00
282+ style V1 fill:#d4edda,stroke:#28a745
283+ style V2 fill:#e6f3ff,stroke:#4a90d9
284+ style NPU fill:#f8d7da,stroke:#dc3545
285+```
286+ 
287+- **v1(静态 shape 执行器)**`runtime/v1/`,核心是 `DavinciModel`,走 Task Sink(任务预下发到设备,Host 一次 `rtModelExecute` 触发)
288+- **v2(动态 shape 执行器)**`runtime/v2/`,核心是 `ModelV2Executor`,走 Lowering(ComputeGraph→ExecuteGraph),Host 顺序/拓扑执行
289+ 
290+**ONNX 插件只影响 Parser 阶段**。插件不参与 Compiler 和 Executor 阶段。这一点已在 [实验观察:atc-onnx-experiments.md](../dev-log/atc-onnx-experiments.md) 中验证:ThresholdedRelu 解析插件工作正常(Parser 成功),但子图里的 `Threshold` 算子缺 kernel(Compiler 失败),报错在 `engine_place.cc` 而非 parser。
291+ 
292+---
293+ 
294+## 第 4 章 ONNX 到 GE IR 的解析链路
295+ 
296+### 4.1 入口与主流程
297+ 
298+**对外 API**([inc/parser/external/parser/onnx_parser.h](../../inc/parser/external/parser/onnx_parser.h)):
299+```cpp
300+graphStatus aclgrphParseONNX(const char *model_file,
301+ const std::map<AscendString, AscendString> &parser_params,
302+ ge::Graph &graph); // out-param
303+```
304+ 
305+**完整调用链**([GE 仓源码:onnx_parser.cc](../../parser/parser/onnx/onnx_parser.cc)):
306+ 
307+```mermaid
308+flowchart TD
309+ Entry["aclgrphParseONNX<br/>onnx_parser.cc:110"]
310+ Entry --> Prep["PrepareBeforeParse<br/>onnx_parser.cc:58"]
311+ Prep --> CreateParser["ModelParserFactory::CreateModelParser(ONNX)<br/>onnx_parser.cc:88"]
312+ Prep --> CreateGraph["CreateGraphFromComputeGraph<br/>onnx_parser.cc:87"]
313+ CreateParser --> Parse["model_parser->Parse(model_file, graph)<br/>onnx_parser.cc:125"]
314+ Parse --> OnnxParse["OnnxModelParser::Parse<br/>onnx_parser.cc:1124"]
315+ OnnxParse --> GetModel["GetModelFromFile<br/>onnx_parser.cc:1126"]
316+ GetModel --> ReadProto["ReadProtoFromBinaryFile<br/>onnx_parser.cc:758<br/>.onnx → ModelProto"]
317+ OnnxParse --> ModelToGraph["ModelParseToGraph<br/>onnx_parser.cc:1131"]
318+ ModelToGraph --> Impl["ModelParseToGraphImpl<br/>onnx_parser.cc:1000<br/>12 步流水线<br/>(// 1.~// 12.,// 12. 为条件步骤)"]
319+ 
320+ Impl --> Step0["// 1. Get all initializer<br/>onnx_parser.cc:1004"]
321+ Impl --> Step1["// 2. ParseInput<br/>onnx_parser.cc:1014<br/>input → Data 节点"]
322+ Impl --> Step2["// 3. ParseInitializer<br/>onnx_parser.cc:1024<br/>权重 → Constant"]
323+ Impl --> Step3["// 4. ParseOutput<br/>onnx_parser.cc:1031"]
324+ Impl --> Step4["// 5. UpdateNodeNameAndOpType<br/>onnx_parser.cc:1038"]
325+ Impl --> Step5["// 6. Prechecker<br/>onnx_parser.cc:1041"]
326+ Impl --> Step6["// 7. ParseAllNodeProto ★<br/>onnx_parser.cc:1054<br/>遍历所有 NodeProto"]
327+ Impl --> Step7["// 8. SetOperatorInputs<br/>onnx_parser.cc:1065<br/>连边"]
328+ Impl --> Step8["// 9. GetGraphInputs/SetInputs<br/>onnx_parser.cc:1072"]
329+ Impl --> Step9["// 10. GetGraphOutputs/SetOutputs<br/>onnx_parser.cc:1094"]
330+ Impl --> Step10["// 11. ExpandOneToManyGraph ★<br/>onnx_parser.cc:1107<br/>一对多图展开"]
331+ Impl --> Step11["// 12. Set outputs info (条件步骤)<br/>onnx_parser.cc:1111<br/>仅 root graph 执行"]
332+ 
333+ Entry --> Handle["HandleAfterParse<br/>onnx_parser.cc:134"]
334+ Handle --> Return["返回 graph(out-param)<br/>onnx_parser.cc:140"]
335+ 
336+ style Entry fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px
337+ style Step6 fill:#fff3cd,stroke:#ffc107,stroke-width:2px
338+ style Step10 fill:#fff3cd,stroke:#ffc107,stroke-width:2px
339+ style Return fill:#d4edda,stroke:#28a745,stroke-width:2px
340+```
341+ 
342+> ★ 标记的两个步骤是核心:`ParseAllNodeProto` 遍历每个 ONNX 节点并转成 GE Operator,`ExpandOneToManyGraph` 把需要展开的算子展开成子图。
343+ 
344+### 4.2 NodeProto 遍历:ParseAllNodeProto
345+ 
346+每个 ONNX 节点的处理在 [onnx_parser.cc:631](../../parser/parser/onnx/onnx_parser.cc#L631):
347+ 
348+```cpp
349+631: Status OnnxModelParser::ParseAllNodeProto(ge::onnx::GraphProto &onnx_graph, ge::Graph &graph) {
350+632: for (int i = 0; i < onnx_graph.node_size(); i++) { // ★ 遍历每个 NodeProto
351+633: ge::onnx::NodeProto *node_proto = onnx_graph.mutable_node(i);
352+```
353+ 
354+循环内每个 NodeProto 的处理顺序:
355+ 
356+```mermaid
357+flowchart LR
358+ A["1. AdapterOpType<br/>行 639<br/>算子类型适配<br/>(内置/自定义分流)"]
359+ B["2. TransNodeToOperator<br/>行 649<br/>创建 GE Operator"]
360+ C["3. OpParserFactory<br/>::CreateOpParser<br/>行 657-659<br/>取算子解析器"]
361+ D["4. ParseOpParam<br/>行 661<br/>解析参数<br/>(内置/自定义分流)"]
362+ E["5. graph.AddOp(op)<br/>行 671<br/>加入 Graph"]
363+ F["6. ConstructInputOutput<br/>Context<br/>行 680<br/>记录 IO 名→节点映射"]
364+ 
365+ A --> B --> C --> D --> E --> F
366+ 
367+ style A fill:#fff3cd,stroke:#ffc107,stroke-width:2px
368+ style D fill:#fff3cd,stroke:#ffc107,stroke-width:2px
369+```
370+ 
371+1. `AdapterOpType`(行 639)— 算子类型适配(内置/自定义分流,见 4.3)
372+2. `TransNodeToOperator`(行 649)— `OperatorFactory::CreateOperator` 创建 GE Operator
373+3. `OpParserFactory::CreateOpParser(op_type)`(行 657-659)— 取算子解析器
374+4. `ParseOpParam`(行 661)— 解析参数(内置/自定义分流的核心,见 4.3)
375+5. `graph.AddOp(op)`(行 671)— 加入 Graph
376+6. `ConstructInputOutputContext`(行 680)— 记录输入输出名→节点映射
377+ 
378+### 4.3 内置算子 vs 自定义算子的分流
379+ 
380+ONNX 节点在解析时有**两处分流**,先分流算子类型,再分流参数解析方式:
381+ 
382+```mermaid
383+flowchart TD
384+ Node["ONNX NodeProto<br/>op_type 如 'ThresholdedRelu'"]
385+ 
386+ Node --> Split1{"第一处分流<br/>AdapterOpType<br/>onnx_parser.cc:453"}
387+ 
388+ Split1 -->|"在 kOnnxOpMap 白名单中<br/>(Input/Constant/FileConstant)"| BuiltIn["内置算子<br/>直接用映射后的 om_type"]
389+ Split1 -->|"不在白名单"| ConstructOri["ConstructOriType<br/>onnx_parser.cc:412<br/>构造 'ai.onnx::11::ThresholdedRelu'"]
390+ ConstructOri --> GetOm["GetOmTypeByOriOpType<br/>onnx_parser.cc:471<br/>查 OpRegistry 拿 om_type"]
391+ 
392+ BuiltIn --> CreateOp["TransNodeToOperator<br/>创建 GE Operator"]
393+ GetOm --> CreateOp
394+ 
395+ CreateOp --> Split2{"第二处分流<br/>ParseOpParam<br/>onnx_parser.cc:596"}
396+ 
397+ Split2 -->|"GetParseParamByOperatorFunc<br/>返回 null"| PathA["路径A: 内置或旧式自定义<br/>op_parser->ParseParams(Message*, Operator&)<br/>onnx_parser.cc:605"]
398+ Split2 -->|"GetParseParamByOperatorFunc<br/>返回非空"| PathB["路径B: 新式自定义<br/>OnnxCustomParserAdapter.ParseParams(Operator&, Operator&)<br/>onnx_parser.cc:617"]
399+ 
400+ PathA --> PathA2{"op_parser 类型?"}
401+ PathA2 -->|"OnnxDataParser/<br/>OnnxConstantParser"| BuiltInParse["内置 parser 直接解析"]
402+ PathA2 -->|"OnnxCustomParserAdapter<br/>(旧式: 只有 ParseParamFunc)"| AdapterMsg["adapter Message 重载<br/>GetParseParamFunc<br/>adapter.cc:32"]
403+ 
404+ PathB --> AdapterOp["adapter Operator 重载<br/>GetParseParamByOperatorFunc<br/>adapter.cc:43"]
405+ 
406+ style Node fill:#e6f3ff,stroke:#4a90d9
407+ style Split1 fill:#fff3cd,stroke:#ffc107
408+ style Split2 fill:#fff3cd,stroke:#ffc107
409+ style BuiltIn fill:#d4edda,stroke:#28a745
410+ style BuiltInParse fill:#d4edda,stroke:#28a745
411+ style AdapterMsg fill:#ffe4b5,stroke:#ff8c00
412+ style AdapterOp fill:#ffe4b5,stroke:#ff8c00
413+```
414+ 
415+**第一处分流:算子类型适配 `AdapterOpType`**([onnx_parser.cc:453](../../parser/parser/onnx/onnx_parser.cc#L453))
416+ 
417+内置算子白名单 `kOnnxOpMap`([onnx_parser.cc:175-177](../../parser/parser/onnx/onnx_parser.cc#L175)):
418+```cpp
419+175: const std::map<std::string, std::string> kOnnxOpMap = {{ge::kOpTypeInput, ge::parser::DATA}, // ONNX "Input" → GE "Data"
420+176: {ge::kOpTypeConstant, ge::parser::CONSTANT}, // ONNX "Constant" → GE "Constant"
421+177: {ge::kFileConstant, ge::parser::FILECONSTANT}}; // ONNX "FileConstant" → GE "FileConstant"
422+```
423+ 
424+判断逻辑([onnx_parser.cc:458-471](../../parser/parser/onnx/onnx_parser.cc#L458)):
425+```cpp
426+458: auto map_it = kOnnxOpMap.find(ori_type);
427+459: if (map_it != kOnnxOpMap.end()) {
428+460: op_type = map_it->second; // 内置:直接用映射后的 om_type
429+461: ori_to_om_type_[ori_type] = op_type; // 缓存 ori→om 映射
430+462: return SUCCESS;
431+463: }
432+465: ConstructOriType(node_proto, ori_type); // 自定义:构造带版本号 ori_type,如 "ai.onnx::11::ThresholdedRelu"
433+471: GetOmTypeByOriOpType(ori_type, op_type); // 查 OpRegistry 拿 om_type(如 "PartitionedCall")
434+```
435+ 
436+**`ConstructOriType`**([onnx_parser.cc:412,449](../../parser/parser/onnx/onnx_parser.cc#L412)):构造 `domain::version::op_type` 格式:
437+```cpp
438+449: ori_type = domain + "::" + to_string(version) + "::" + ori_type;
439+// 例:ai.onnx::11::ThresholdedRelu
440+```
441+ 
442+**第二处分流:参数解析 `ParseOpParam`**([onnx_parser.cc:596](../../parser/parser/onnx/onnx_parser.cc#L596))—— 真正的解析分流:
443+ 
444+```cpp
445+603: ParseParamByOpFunc parse_param_func = OpRegistry::Instance()->GetParseParamByOperatorFunc(op_type);
446+604: if (parse_param_func == nullptr) {
447+605: status = op_parser->ParseParams(node_proto, op); // 路径A:内置或旧式自定义
448+606: } else {
449+607: ge::Operator op_src(node_proto->name().c_str(), op_type.c_str());
450+608: Message2Operator::ParseOperatorAttrs(node_proto, 1, op_src); // 自动映射属性
451+615: auto onnx_custom_op_parser = dynamic_pointer_cast<OnnxCustomParserAdapter>(op_parser);
452+617: status = onnx_custom_op_parser->ParseParams(op_src, op); // 路径B:新式自定义(ParseParamByOpFunc)
453+```
454+ 
455+| 路径 | 触发条件 | op_parser 实际类型 | 走向 |
456+|------|----------|-------------------|------|
457+| **A. 内置算子** | `GetParseParamByOperatorFunc` 返回 null(算子未注册新式回调) | `OnnxDataParser`/`OnnxConstantParser`/`OnnxFileConstantParser` | 内置 parser 直接解析 |
458+| **B. 自定义算子(新式)** | `GetParseParamByOperatorFunc` 返回非空(插件注册了 `ParseParamByOpFunc`) | `OnnxCustomParserAdapter` | adapter 调用插件注册的 `ParseParamByOpFunc` |
459+| **A'. 自定义算子(旧式)** | 插件只注册了 `ParseParamFunc`,未注册 `ParseParamByOpFunc` | `OnnxCustomParserAdapter` | adapter 的 Message 重载调用 `GetParseParamFunc` |
460+ 
461+### 4.4 一对多图展开:ExpandOneToManyGraph
462+ 
463+某些算子需要展开成子图(如 ThresholdedRelu → Data→Identity→Threshold→Mul)。这一步在 [onnx_parser.cc:1109](../../parser/parser/onnx/onnx_parser.cc#L1109) 调用 [parser_utils.cc:42-83](../../parser/parser/common/parser_utils.cc#L42) 的 `ExpandOneToManyGraph`
464+ 
465+```cpp
466+47: std::string ori_type;
467+48: AttrUtils::GetStr(node->GetOpDesc(), ATTR_NAME_FRAMEWORK_ORIGINAL_TYPE, ori_type);
468+49: ParseOpToGraphFunc parse_op_to_graph_func =
469+50: OpRegistry::Instance()->GetParseOpToGraphFunc(node->GetType(), ori_type); // ★ 不经 adapter
470+60: ret = parse_op_to_graph_func(op, subgraph); // 调用插件注册的子图展开回调
471+67: ret = ExpandNodeToSubgraph(subgraph, node, graph, output_mapping); // 展开节点为子图
472+```
473+ 
474+**关键认知**`GetParseOpToGraphFunc` 的调用点在 `parser_utils.cc:50`**不经 `OnnxCustomParserAdapter`**。这是 Python 化时必须覆盖的咽喉点(见第 7 章)。
475+ 
476+### 4.5 三个核心对象的流转
477+ 
478+整个链路里,有三个对象在流转,理解它们就理解了一切:
479+ 
480+```mermaid
481+flowchart LR
482+ NP["NodeProto<br/>(ONNX 节点)"]
483+ OP["Operator<br/>(GE 算子,挂属性)"]
484+ GR["Graph<br/>(GE 子图)"]
485+ 
486+ NP -->|"ParseParams<br/>读 ONNX 属性"| OP
487+ OP -->|"ParseOpToGraph<br/>展开成子图"| GR
488+ 
489+ style NP fill:#e6f3ff,stroke:#4a90d9
490+ style OP fill:#fff3cd,stroke:#ffc107
491+ style GR fill:#d4edda,stroke:#28a745
492+```
493+ 
494+> **颜色含义**:蓝色 = ONNX 侧对象(输入),黄色 = GE 中间对象(过渡),绿色 = GE 最终产物(输出)。整个插件机制就是在做这个转换。
495+ 
496+一对多插件做的事,本质就是一次"转换流水线":读 ONNX 属性 → 写 Operator 属性 → 展开 Operator 成子图。不提供 `ParseOpToGraph` 的插件只走前两步。
497+ 
498+---
499+ 
500+## 第 5 章 C++ 插件机制详解
501+ 
502+### 5.1 为什么叫"插件"?
503+ 
504+GE 不可能认识 ONNX 里的每一个算子(ONNX 有上百种算子,且持续演进)。所以 GE 提供了一个**插件机制**
505+ 
506+> 你(开发者)写一小段代码,告诉 GE:「当你在 ONNX 模型里遇到 `ThresholdedRelu` 这个算子时,按我这段逻辑来处理。」
507+ 
508+这段代码就是"插件"。它**插件化地扩展**了 GE 的算子认识能力,不需要修改 GE 主体代码。当前插件只能用 C++ 写,本需求就是让它也能用 Python 写。
509+ 
510+[官方文档:architecture.md §插件和扩展机制](../../docs/zh/design/architecture.md)
511+ 
512+### 5.2 插件的基线与可选回调
513+ 
514+1. **注册(必须)**:通过 `REGISTER_CUSTOM_OP` 宏登记算子的来源框架、原始类型、目标类型、ImplyType 等注册信息
515+2. **参数解析回调(至少一种)**:按插件形态注册 `ParseParamFunc``ParseParamByOpFunc`,把源算子信息转换到目标 GE Operator
516+3. **ParseOpToGraph(展开成子图,可选)**:仅需要一对多展开的算子才提供(如用 `Data→Identity→Threshold→Mul` 实现 ThresholdedRelu)
517+ 
518+### 5.3 C++ 插件的三件套(ThresholdedRelu 重构示例,非仓内源码)
519+ 
520+插件的基线是完成注册并提供至少一种参数解析回调。ParseOpToGraph 回调是**可选的**,只有需要一对多展开的算子才提供(ThresholdedRelu 恰好需要,因此该示例包含注册、ParseParams 和 ParseOpToGraph)。以 ThresholdedRelu 为例,其 ParseOpToGraph 会把一个算子展开成子图,展开后的子图结构如下:
521+ 
522+```mermaid
523+flowchart LR
524+ Data["Data<br/>(输入, index=0)"]
525+ Identity["Identity<br/>(原样输出)"]
526+ Threshold["Threshold<br/>(小于 alpha 置零)"]
527+ Mul["Mul<br/>(输出)"]
528+ 
529+ Data --> Identity
530+ Identity --> Mul
531+ Identity --> Threshold
532+ Threshold --> Mul
533+ 
534+ style Data fill:#e6f3ff,stroke:#4a90d9
535+ style Mul fill:#d4edda,stroke:#28a745,stroke-width:2px
536+ style Threshold fill:#fff3cd,stroke:#ffc107
537+```
538+ 
539+> 含义:把输入 x 先过 Identity(原样输出),同时过 Threshold(小于 alpha 置零),两者相乘得到 ThresholdedRelu 的效果。
540+ 
541+> [重构示例] 以下代码依据 metadef 外部接口契约、GE 仓调用链(见 5.4-5.9)和 [实验观察] 现象重构,用于说明三件套的结构,不是 GE 仓或上游 ops 仓的逐行源码。
542+ 
543+重构示意代码:
544+ 
545+```cpp
546+// 第一件:ParseParams 回调
547+static Status ParseParamsThresholdedRelu(const Message* op_src, ge::Operator& op_dest) {
548+ const NodeProto* node = dynamic_cast<const NodeProto*>(op_src);
549+ float alpha = 1.0;
550+ for (auto attr : node->attribute()) { if (attr.name() == "alpha") alpha = attr.f(); }
551+ op_dest.SetAttr("name", node->name());
552+ op_dest.SetAttr("alpha", alpha);
553+ op_dest.SetAttr("original_type", "ai.onnx::11::ThresholdedRelu");
554+ op_dest.DynamicInputRegister("x", 1);
555+ op_dest.DynamicOutputRegister("y", 1);
556+ return SUCCESS;
557+}
558+ 
559+// 第二件:ParseOpToGraph 回调
560+static Status ParseOpToThresholdedRelu(const ge::Operator& op, Graph& graph) {
561+ std::string ori_name; op.GetAttr("name", ori_name);
562+ float alpha = 1.0f; op.GetAttr("alpha", alpha);
563+ auto data = op::Data((ori_name + "_data1").c_str()).set_attr_index(0);
564+ auto identity_op = op::Identity((ori_name + "_identity").c_str()).set_input_x(data);
565+ auto threshold_op = op::Threshold((ori_name + "_threshold").c_str()).set_input_x(identity_op).set_attr_threshold(alpha);
566+ auto mul_op = op::Mul((ori_name + "_mul").c_str()).set_input_x1(identity_op).set_input_x2(threshold_op);
567+ graph.SetInputs({data}).SetOutputs({{mul_op, {0}}});
568+ return SUCCESS;
569+}
570+ 
571+// 第三件:注册
572+REGISTER_CUSTOM_OP("PartitionedCall") // GE 目标算子名
573+ .FrameworkType(ONNX) // 来源框架
574+ .OriginOpType({"ai.onnx::10::ThresholdedRelu", ...}) // 处理哪些 ONNX 算子版本
575+ .ParseParamsFn(ParseParamsThresholdedRelu) // 绑定第一个回调
576+ .ParseOpToGraphFn(ParseOpToThresholdedRelu) // 绑定第二个回调
577+ .ImplyType(ImplyType::TVM); // 暗示类型
578+```
579+ 
580+### 5.4 REGISTER_CUSTOM_OP 宏的工作原理
581+ 
582+> **重要**:`REGISTER_CUSTOM_OP` 宏、`OpReceiver` 类、`OpRegistrationData` 类的**声明**都在 **metadef 仓**的 `inc/external/register/register.h`,GE 仓通过 include 链引入。GE 仓 `graph_metadef/register/register.cpp` 提供了它们的**实现**
583+>
584+> [metadef 仓] 需单独查找
585+ 
586+宏展开后(三层宏防止变量名冲突):
587+```cpp
588+static OpReceiver register_op0 __attribute__((unused)) =
589+ OpRegistrationData("PartitionedCall")
590+ .FrameworkType(ONNX)
591+ .OriginOpType({...})
592+ .ParseParamsFn(ParseParamsThresholdedRelu)
593+ .ParseOpToGraphFn(ParseOpToThresholdedRelu)
594+ .ImplyType(ImplyType::TVM);
595+```
596+ 
597+**关键点**
598+- `static OpReceiver register_op0 = ...`**静态变量声明 + 初始化**
599+- 插件 .so 的静态变量初始化发生在 **`dlopen` 加载该 .so 时**(不是程序启动时),由动态链接器触发
600+- `OpReceiver` 构造函数**只把注册数据 push 到 `registrationDatas` 队列**(见 5.5),不调用 `OpRegistry::Register`
601+- 所以**只要这个 `.cpp` 被编译链接进 .so 且 .so 被 dlopen 加载,注册数据就自动入队**——真正的 `Register` 调用由调用方在 dlopen 之后显式触发(见 5.5-5.8)
602+ 
603+### 5.5 OpReceiver:只 push,不 Register
604+ 
605+> **重要修正**:初版分析可能认为 `OpReceiver` 构造函数直接调用 `OpRegistry::Register`。一手核验发现**不是**
606+ 
607+[GE 仓源码:register.cpp:713-715](../../graph_metadef/register/register.cpp#L713):
608+```cpp
609+OpReceiver::OpReceiver(OpRegistrationData &reg_data) {
610+ OpRegistry::Instance()->registrationDatas.push_back(reg_data); // 只 push 到 vector,不 Register!
611+}
612+```
613+ 
614+真正的 `Register` 调用由 **atc/session 主动触发**
615+- atc 路径:[main_impl.cc:1307-1326](../../api/atc/main_impl.cc#L1307) — 先 `registrationDatas.clear()`,再 `dlopen` 各 .so(触发 static 变量注册),最后遍历 `registrationDatas` 调用 `OpRegistrationTbe::Finalize`(L1323)+ `OpRegistry::Register`(L1324),**Finalize 和 Register 在同一遍历中成对调用**
616+- session 路径分两阶段:
617+ 1. **插件加载阶段**([tbe_plugin_manager.cc:160-178](../../api/session/common/plugin/tbe_plugin_manager.cc#L160)):dlopen .so 后遍历 `registrationDatas`**只调用 `OpRegistry::Register`**(L176),不调用 Finalize
618+ 2. **解析初始化阶段**([acl_graph_parser_util.cc:247-253](../../parser/parser/common/acl_graph_parser_util.cc#L247) 的 `AclParserInitialize`):遍历 `registrationDatas`**同时调用 `OpRegistrationTbe::Finalize`(L251)和 `OpRegistry::Register`(L252)**,时机晚于 tbe_plugin_manager 的加载阶段
619+ 
620+> **注意**:session 路径的 `Register` 被调用两次(tbe_plugin_manager 一次、AclParserInitialize 一次)。`OpRegistry::Register`([register.cpp:1056-1060](../../graph_metadef/register/register.cpp#L1056))对非 custom op 有 key 存在则 skip 的逻辑,因此第二次调用的大部分条目会被跳过。但 `Finalize`([op_registration_tbe.cc:124-128](../../parser/parser/common/op_registration_tbe.cc#L124))只在 AclParserInitialize 中调用:它从 framework 级 `CustomParserAdapterRegistry` 获取静态注册的 adapter creator,再为具体 `om_type` 注册到 `OpParserFactory`。
621+ 
622+**三阶段机制**
623+ 
624+```mermaid
625+flowchart TD
626+ subgraph 阶段1["阶段1: dlopen .so"]
627+ S1a["dlopen liboponnx_plugin_nn.so"] --> S1b["触发 C++ static 变量初始化"]
628+ S1b --> S1c["OpReceiver 构造函数"]
629+ S1c --> S1d["push 到 registrationDatas<br/>register.cpp:714"]
630+ end
631+ 
632+ subgraph 阶段2["阶段2: 调用方遍历 registrationDatas"]
633+ S2a["atc: main_impl.cc:1320<br/>session 加载: tbe_plugin_manager.cc:168<br/>session 解析: acl_graph_parser_util.cc:249"]
634+ S2a --> S2b["遍历每个 OpRegistrationData"]
635+ S2b --> S2c["OpRegistrationTbe::Finalize<br/>使用 framework creator 注册具体 om_type 到 OpParserFactory<br/>atc: main_impl.cc:1323<br/>session: acl_graph_parser_util.cc:251<br/>(tbe_plugin_manager 不调用)"]
636+ S2b --> S2d["OpRegistry::Register<br/>register.cpp:1050<br/>把回调存入各 map<br/>atc: main_impl.cc:1324<br/>session 加载: tbe_plugin_manager.cc:176<br/>session 解析: acl_graph_parser_util.cc:252"]
637+ end
638+ 
639+ subgraph 阶段3["阶段3: OpRegistry::Register"]
640+ S3a["遍历 ori_optype_set_"]
641+ S3a --> S3b["组 key = om_type + _ + ori_type"]
642+ S3b --> S3c["存入 op_parse_params_fn_map_"]
643+ S3b --> S3d["存入 parse_op_to_graph_fn_map_"]
644+ S3b --> S3e["存入其他 map..."]
645+ end
646+ 
647+ 阶段1 --> 阶段2 --> 阶段3
648+ 
649+ style 阶段1 fill:#e6f3ff,stroke:#4a90d9
650+ style 阶段2 fill:#fff3cd,stroke:#ffc107
651+ style 阶段3 fill:#d4edda,stroke:#28a745
652+ style S2c fill:#ffe4b5,stroke:#ff8c00
653+```
654+ 
655+1. **dlopen .so** → 触发 .so 内 static 变量初始化 → `OpReceiver` 构造 → **只 push 到 `registrationDatas`**(不 Register)
656+2. **调用方遍历 registrationDatas** → 对每个 `OpRegistrationData` 调用 `OpRegistrationTbe::Finalize`(使用 framework creator 为具体 `om_type` 注册到 OpParserFactory)+ `OpRegistry::Register`(把回调存入各 map)。atc 路径在一次遍历中成对调用;session 路径分两阶段(tbe_plugin_manager 先调 Register,AclParserInitialize 后调 Finalize + Register)
657+3. **`OpRegistry::Register`** 把回调存入各 map
658+ 
659+### 5.6 OpRegistry:注册表单例
660+ 
661+[声明:inc/graph_metadef/register/op_registry.h:47-95](../../inc/graph_metadef/register/op_registry.h#L47)
662+[实现:graph_metadef/register/register.cpp:1039-1230](../../graph_metadef/register/register.cpp#L1039)
663+ 
664+`OpRegistry` 是单例,内部维护 10 个 map([op_registry.h:85-94](../../inc/graph_metadef/register/op_registry.h#L85)):
665+ 
666+| map | key | value | 用途 |
667+|-----|-----|-------|------|
668+| `op_parse_params_fn_map_` | `om_type + "_" + ori_type` | `ParseParamFunc` | 旧式参数解析回调 |
669+| `parse_params_by_op_func_map_` | `om_type + "_" + ori_type` | `ParseParamByOpFunc` | 新式参数解析回调 |
670+| `parse_op_to_graph_fn_map_` | `om_type + "_" + ori_type` | `ParseOpToGraphFunc` | 子图展开回调 |
671+| `fusion_op_parse_params_fn_map_` | `om_type + "_" + ori_type` | `FusionParseParamFunc` | 融合参数解析 |
672+| `fusion_parse_params_by_op_fn_map_` | `om_type + "_" + ori_type` | `FusionParseParamByOpFunc` | 融合参数解析(by op) |
673+| `op_types_to_parse_subgraph_post_func_` | `om_type + "_" + fmk_type_serial` | `ParseSubgraphFunc` | 子图后处理(V1) |
674+| `op_types_to_parse_subgraph_post_func_v2_` | `om_type + "_" + fmk_type_serial` | `ParseSubgraphFuncV2` | 子图后处理(V2) |
675+| `origin_type_to_om_type_` | `ori_type` | `om_type` | ori→om 类型映射 |
676+| `op_run_mode_map_` | `om_type` | `ImplyType` | 暗示类型 |
677+| `remove_input_configure_map_` | `om_type + "_" + ori_type` | `vector<RemoveInputConfigure>` | 输入裁剪配置 |
678+ 
679+**Register 实现**([register.cpp:1050-1092](../../graph_metadef/register/register.cpp#L1050)):遍历 `ori_optype_set_`,对每个 ori_type 组 key(`om_type + "_" + ori_type`),把各回调存入对应 map。
680+ 
681+OpRegistry 共有 **11 个不同名 Get\* 查找函数****14 个声明**(声明在 [op_registry.h:55-82](../../inc/graph_metadef/register/op_registry.h#L55);其中 `GetParseSubgraphPostFunc` 有 4 个重载声明,分别在 [L67](../../inc/graph_metadef/register/op_registry.h#L67)/[L69](../../inc/graph_metadef/register/op_registry.h#L69)/[L71](../../inc/graph_metadef/register/op_registry.h#L71)/[L73](../../inc/graph_metadef/register/op_registry.h#L73))。11 个函数名如下:
682+ 
683+1. `GetImplyType` (L55)
684+2. `GetOpTypeByImplyType` (L57)
685+3. `GetParseParamFunc` (L59)
686+4. `GetParseParamByOperatorFunc` (L61)
687+5. `GetFusionParseParamFunc` (L63)
688+6. `GetFusionParseParamByOpFunc` (L65)
689+7. `GetParseSubgraphPostFunc` (L67/L69/L71/L73,4 个重载)
690+8. `GetImplyTypeByOriOpType` (L76)
691+9. `GetRemoveInputConfigure` (L78)
692+10. `GetOmTypeByOriOpType` (L80)
693+11. `GetParseOpToGraphFunc` (L82)
694+ 
695+其中 ONNX 链路核心的 6 个如下表:
696+ 
697+| Get* 函数 | 在 ONNX 链路的调用点 | 经 adapter? |
698+|-----------|---------------------|-------------|
699+| `GetOmTypeByOriOpType` (L80) | [onnx_parser.cc:471](../../parser/parser/onnx/onnx_parser.cc#L471) `AdapterOpType` | 否 |
700+| `GetParseParamByOperatorFunc` (L61) | [onnx_parser.cc:603](../../parser/parser/onnx/onnx_parser.cc#L603) `ParseOpParam` 判断分流 + [adapter.cc:43](../../parser/parser/onnx/onnx_custom_parser_adapter.cc#L43) | 是(adapter 重载3) |
701+| `GetParseParamFunc` (L59) | [adapter.cc:32](../../parser/parser/onnx/onnx_custom_parser_adapter.cc#L32) adapter 重载1 | 是(adapter 重载1) |
702+| `GetParseOpToGraphFunc` (L82) | [parser_utils.cc:50](../../parser/parser/common/parser_utils.cc#L50) `ExpandOneToManyGraph` | **否!不经 adapter!** |
703+| `GetFusionParseParamFunc` (L63) | ONNX 链路不调用(仅 TF 融合链路) | — |
704+| `GetFusionParseParamByOpFunc` (L65) | ONNX 链路不调用(仅 TF 融合链路) | — |
705+ 
706+### 5.7 OnnxCustomParserAdapter:链路咽喉之一
707+ 
708+[文件:onnx_custom_parser_adapter.cc](../../parser/parser/onnx/onnx_custom_parser_adapter.cc)(56 行)+ [.h](../../parser/parser/onnx/onnx_custom_parser_adapter.h)(36 行)
709+ 
710+继承 `OnnxOpParser`,有 **3 个 ParseParams 重载**,其中两个有效重载分别调用不同的 `OpRegistry::Get*` 函数,一个重载为空实现:
711+ 
712+```mermaid
713+flowchart TD
714+ subgraph Adapter["OnnxCustomParserAdapter (56 行)"]
715+ OL1["重载1: ParseParams(const Message*, Operator&)<br/>.cc:25<br/>旧式: 基于 ONNX NodeProto"]
716+ OL2["重载2: ParseParams(const Message*, OpDescPtr&)<br/>.h:26<br/>空实现, 直接 return SUCCESS"]
717+ OL3["重载3: ParseParams(const Operator&, Operator&) const<br/>.cc:41<br/>新式: 基于 GE Operator"]
718+ end
719+ 
720+ OL1 -->|"adapter.cc:32"| Get1["OpRegistry::GetParseParamFunc<br/>op_registry.h:59"]
721+ OL3 -->|"adapter.cc:43"| Get2["OpRegistry::GetParseParamByOperatorFunc<br/>op_registry.h:61"]
722+ 
723+ Get1 --> Map1["op_parse_params_fn_map_<br/>(旧式回调)"]
724+ Get2 --> Map2["parse_params_by_op_func_map_<br/>(新式回调)"]
725+ 
726+ Map1 --> Plugin1["插件注册的 ParseParamFunc<br/>Status(const Message*, Operator&)"]
727+ Map2 --> Plugin2["插件注册的 ParseParamByOpFunc<br/>Status(const Operator&, Operator&)"]
728+ 
729+ Reg["注册: REGISTER_CUSTOM_PARSER_ADAPTER_CREATOR(ONNX, OnnxCustomParserAdapter)<br/>.cc:55"]
730+ Reg --> Adapter
731+ 
732+ Wiring["接线: op_registration_tbe.cc:130-138<br/>每个自定义算子注册时<br/>把 OnnxCustomParserAdapter<br/>注册到 OpParserFactory"]
733+ Wiring --> Adapter
734+ 
735+ style Adapter fill:#fff3cd,stroke:#ffc107,stroke-width:2px
736+ style Get1 fill:#e6f3ff,stroke:#4a90d9
737+ style Get2 fill:#e6f3ff,stroke:#4a90d9
738+ style Plugin1 fill:#d4edda,stroke:#28a745
739+ style Plugin2 fill:#d4edda,stroke:#28a745
740+```
741+ 
742+| # | 签名 | 位置 | 干什么 |
743+|---|------|------|--------|
744+| 1 | `ParseParams(const Message*, Operator&)` | [.cc:25](../../parser/parser/onnx/onnx_custom_parser_adapter.cc#L25) | 旧式:调 `GetParseParamFunc`,再调插件注册的 `ParseParamFunc` |
745+| 2 | `ParseParams(const Message*, OpDescPtr&)` | [.h:26](../../parser/parser/onnx/onnx_custom_parser_adapter.h#L26) | 空实现,直接 return SUCCESS |
746+| 3 | `ParseParams(const Operator&, Operator&) const` | [.cc:41](../../parser/parser/onnx/onnx_custom_parser_adapter.cc#L41) | 新式:调 `GetParseParamByOperatorFunc`,再调插件注册的 `ParseParamByOpFunc` |
747+ 
748+**注册**:[.cc:55](../../parser/parser/onnx/onnx_custom_parser_adapter.cc#L55) `REGISTER_CUSTOM_PARSER_ADAPTER_CREATOR(ONNX, OnnxCustomParserAdapter)`
749+ 
750+**接线**:[op_registration_tbe.cc:130-138](../../parser/parser/common/op_registration_tbe.cc#L130) — 每个自定义算子通过 `REGISTER_CUSTOM_OP` 注册时,TBE 接线层把 `OnnxCustomParserAdapter` 的 creator 注册到 `OpParserFactory` 中该 op_type 名下。
751+ 
752+### 5.8 插件 .so 的加载
753+ 
754+> **重要认知**:插件 .so(如 `liboponnx_plugin_nn.so`)**不在 GE 仓源码里**,它是 ops 包的产物,随 ops 包发布。
755+ 
756+[实验观察:atc-onnx-experiments.md](../dev-log/atc-onnx-experiments.md) 确认插件 .so 位于(**环境特定实验观察,不同 CANN/ops 版本可能不同**):
757+```
758+$ASCEND_OPP_PATH/built-in/framework/onnx/liboponnx_plugin_nn.so
759+$ASCEND_OPP_PATH/built-in/framework/onnx/liboponnx_plugin_legacy.so
760+```
761+ 
762+**路径构造**([tbe_plugin_loader.cc:215-244](../../parser/parser/common/tbe_plugin_loader.cc#L215) `GetCustomOpPath`):
763+1. 从 options 读 `FRAMEWORK_TYPE`,转成 serial string(如 "onnx")
764+2. `GetOppPath()` 获取 OPP 根路径(优先读 `ASCEND_OPP_PATH` 环境变量)
765+3. 拼接:`$ASCEND_OPP_PATH/built-in/framework/onnx/`(新版 OPP 结构)
766+ 
767+**加载流程**(atc 路径,[main_impl.cc:1284-1326](../../api/atc/main_impl.cc#L1284)):
768+ 
769+```mermaid
770+flowchart TD
771+ A["GetCustomOpPath<br/>tbe_plugin_loader.cc:215"]
772+ A --> B["找到路径<br/>$ASCEND_OPP_PATH/built-in/framework/onnx/"]
773+ B --> C["GetPluginSoFileList<br/>列出所有 .so"]
774+ C --> D["registrationDatas.clear()<br/>main_impl.cc:1307<br/>清空旧注册"]
775+ D --> E["dlopen 每个 .so<br/>main_impl.cc:1312"]
776+ E --> F["触发 static OpReceiver 初始化<br/>→ push 到 registrationDatas"]
777+ F --> G["遍历 registrationDatas<br/>main_impl.cc:1320-1326"]
778+ G --> H["OpRegistrationTbe::Finalize<br/>main_impl.cc:1323<br/>把 OnnxCustomParserAdapter<br/>注册到 OpParserFactory"]
779+ G --> I["OpRegistry::Register<br/>main_impl.cc:1324<br/>把回调存入各 map"]
780+ 
781+ style A fill:#e6f3ff,stroke:#4a90d9
782+ style D fill:#fff3cd,stroke:#ffc107
783+ style E fill:#fff3cd,stroke:#ffc107
784+ style H fill:#d4edda,stroke:#28a745
785+ style I fill:#d4edda,stroke:#28a745
786+```
787+ 
788+### 5.9 完整调用链路图
789+ 
790+```mermaid
791+flowchart TD
792+ Start["atc 命令 / session 启动"]
793+ 
794+ Start --> Step1["[1] 加载插件 .so<br/>main_impl.cc:1284 / tbe_plugin_manager.cc:160<br/>GetCustomOpPath → dlopen liboponnx_plugin_nn.so<br/>→ 触发 static OpReceiver → push 到 registrationDatas"]
795+ 
796+ Step1 --> Step2["[2] 遍历 registrationDatas,调用 Finalize + Register<br/>atc: main_impl.cc:1323-1324 调用 Finalize + Register<br/>session 加载: tbe_plugin_manager.cc:176 仅调用 Register<br/>session 解析: acl_graph_parser_util.cc:251-252 调用 Finalize + Register"]
797+ 
798+ Step2 --> Step3["[3] 解析 ONNX 模型<br/>aclgrphParseONNX → OnnxModelParser::Parse<br/>遍历每个 NodeProto:"]
799+ 
800+ Step3 --> Step3a["AdapterOpType: GetOmTypeByOriOpType<br/>onnx_parser.cc:471 → 拿 om_type"]
801+ Step3 --> Step3b["OpParserFactory::CreateOpParser<br/>onnx_parser.cc:659 → 拿 OnnxCustomParserAdapter"]
802+ Step3 --> Step3c["ParseOpParam<br/>onnx_parser.cc:596"]
803+ Step3 --> Step3d["graph.AddOp(op)"]
804+ 
805+ Step3c --> Step3c1{"GetParseParamByOperatorFunc<br/>onnx_parser.cc:603<br/>判断分流"}
806+ Step3c1 -->|"返回 null"| PathA["路径A: 内置 parser.ParseParams<br/>Message*, Operator&"]
807+ Step3c1 -->|"返回非空"| PathB["路径B: OnnxCustomParserAdapter.ParseParams<br/>Operator&, Operator&<br/>onnx_parser.cc:617"]
808+ PathB --> PathB2["GetParseParamByOperatorFunc<br/>adapter.cc:43 → 调插件回调"]
809+ 
810+ Step3d --> Step4["[4] 一对多图展开<br/>ParserUtils::ExpandOneToManyGraph<br/>parser_utils.cc:42"]
811+ Step4 --> Step4a["GetParseOpToGraphFunc<br/>parser_utils.cc:50 ★ 不经 adapter"]
812+ Step4a -->|"非空"| Step4b["调用插件注册的 ParseOpToGraphFunc<br/>→ 填充子图"]
813+ Step4a -->|"为空,跳过展开"| Step5
814+ Step4b --> Step4c["ExpandNodeToSubgraph<br/>展开节点为子图"]
815+ 
816+ Step4c --> Step5["[5] 返回 ge::Graph 给调用方<br/>onnx_parser.cc:140"]
817+ Step5 --> Step6["[6] Compiler 阶段<br/>(不在本需求范围)"]
818+ 
819+ style Start fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px
820+ style Step1 fill:#e6f3ff,stroke:#4a90d9
821+ style Step2 fill:#fff3cd,stroke:#ffc107
822+ style Step3 fill:#fff3cd,stroke:#ffc107
823+ style Step4 fill:#fff3cd,stroke:#ffc107
824+ style Step4a fill:#ffe4b5,stroke:#ff8c00,stroke-width:2px
825+ style Step5 fill:#d4edda,stroke:#28a745,stroke-width:2px
826+ style Step6 fill:#d4edda,stroke:#28a745
827+```
828+ 
829+> ★ 标记的 `GetParseOpToGraphFunc` 调用点**不经 adapter**,这是 Python 化时必须覆盖的咽喉点(见第 7 章)。
830+ 
831+---
832+ 
833+## 第 6 章 Python 模块全景
834+ 
835+### 6.1 Python 模块目录结构
836+ 
837+[官方文档:ge_python.md](../../docs/zh/design/modules/ge_python/ge_python.md)
838+[GE 仓源码:api/python/ge/ge/](../../api/python/ge/ge/)
839+ 
840+```
841+api/python/ge/ge/
842+├── _internal/ # ★ 跨子模块共享内部 helper(插件加载、native 产物发现)
843+├── _capi/ # ★ ctypes 绑定层(所有 C 库包装器)
844+├── graph/ # ★ Graph/Node/Tensor/TensorDesc/DataType/Format
845+├── es/ # ★ ES eager-style 构图(GraphBuilder + TensorHolder)
846+├── custom_op/ # ★ Python 自定义算子(eager 执行)
847+├── passes/ # ★ 图优化 pass(融合/分解)Python 化
848+├── runtime/ # 运行时数据结构(Tensor/Shape/StorageFormat)
849+├── session/ # Session(图加载/执行)
850+├── ge_global/ # GE 初始化/反初始化
851+├── allocator/ # 外部内存分配器抽象
852+├── utils/ # GeUtils(InferShape/CheckNodeSupportOnAicore)
853+├── offline_compile/ # 离线编译 API
854+└── pyatc/ # ATC 命令行 Python 入口
855+```
856+ 
857+**模块依赖关系**(现有代码事实):
858+ 
859+```mermaid
860+flowchart TD
861+ subgraph 现有["现有 Python 模块"]
862+ GRAPH["graph/<br/>Graph + Node + TensorDesc + DataType/Format"]
863+ ES["es/<br/>GraphBuilder + TensorHolder"]
864+ CAPI["_capi/<br/>ctypes 绑定层"]
865+ CUSTOM["custom_op/<br/>registry + _bridge 模式"]
866+ PASSES["passes/<br/>装饰器注册 + bridge + bootstrap"]
867+ INTERNAL["_internal/<br/>plugin_loader + artifact_utils"]
868+ end
869+ 
870+ subgraph 底层["GE C++ 层"]
871+ REG["OpRegistry<br/>注册表单例"]
872+ ADAPTER["OnnxCustomParserAdapter<br/>ONNX 解析适配器"]
873+ end
874+ 
875+ ES -->|build_and_reset 返回| GRAPH
876+ ES -->|ctypes 调用| CAPI
877+ CUSTOM -->|调用 load_plugins_from_env| INTERNAL
878+ PASSES -->|调用 load_plugins_from_env| INTERNAL
879+ ADAPTER -->|运行时查回调| REG
880+ 
881+ style 现有 fill:#e6f3ff,stroke:#4a90d9
882+ style 底层 fill:#ffe4b5,stroke:#ff8c00
883+ style GRAPH fill:#e6f3ff,stroke:#4a90d9
884+ style ES fill:#e6f3ff,stroke:#4a90d9
885+ style CAPI fill:#e6f3ff,stroke:#4a90d9
886+ style CUSTOM fill:#e6f3ff,stroke:#4a90d9
887+ style PASSES fill:#e6f3ff,stroke:#4a90d9
888+ style INTERNAL fill:#e6f3ff,stroke:#4a90d9
889+ style REG fill:#ffe4b5,stroke:#ff8c00
890+ style ADAPTER fill:#ffe4b5,stroke:#ff8c00
891+```
892+ 
893+> **颜色含义**:蓝色 = 现有 Python 模块,橙色 = C++ 层。本图只反映现有代码事实,不包含未来设计或新建模块。
894+ 
895+### 6.2 各子模块职责与绑定方式
896+ 
897+| 子模块 | 职责 | 绑定方式 | 绑定的 C 库 |
898+|--------|------|----------|-------------|
899+| `graph/` | Graph/Node/Tensor/TensorDesc 操作 | ctypes | `libgraph_wrapper.so` |
900+| `es/` | eager-style 构图 | ctypes | `libeager_style_graph_builder_base.so` + `libes_all.so`/`libes_math.so` |
901+| `custom_op/` | 自定义算子 eager 执行 | **pybind11** | `_ge_custom_op_native.so` + `libge_python_custom_op_bridge.so` |
902+| `passes/` | 图优化 pass | **pybind11** | `_ge_pass_native.so` + `libge_python_pass_bridge.so` |
903+| `runtime/` | 运行时数据结构 | pybind11 | `_ge_runtime_native.so` |
904+| `session/` | Session | ctypes | `libge_runtime_wrapper.so` |
905+| `ge_global/` | GE 初始化 | ctypes | `libge_runtime_wrapper.so` |
906+| `utils/` | InferShape 等 | ctypes | `libge_utils_wrapper.so` |
907+| `offline_compile/` | 离线编译 | ctypes | `liboffline_compile_wrapper.so` |
908+| `pyatc/` | ATC CLI | ctypes | `libpyatc_wrapper.so` |
909+ 
910+### 6.3 ES 模块(子图构建主力)
911+ 
912+[官方文档:es_graph/design/architecture_design.md](../../docs/zh/design/modules/es_graph/design/architecture_design.md)
913+ 
914+**ES(Eager Style)** 是函数式构图 API。核心类:
915+ 
916+- **`GraphBuilder`**([graph_builder.py:50](../../api/python/ge/ge/es/graph_builder.py#L50)):构图入口
917+ - `create_input(index, name, ...)` (L177) — 创建图输入
918+ - `create_const_int64/float/uint64/int32/uint32(value, shape)` (L267+) — 创建常量
919+ - `create_scalar_int64/int32/float/uint64/uint32(value)` (L530+) — 创建标量
920+ - `create_variable(index, name)` (L664) — 创建变量
921+ - `set_graph_output(tensor, output_index)` (L693) — 设置图输出
922+ - `build_and_reset(outputs=None) -> Graph` (L938) — 构建并返回 `ge.graph.Graph`
923+ - `set_graph_attr_int64/string/bool` (L713+) — 设置图属性
924+ - `set_tensor_attr_int64/string/bool` (L773+) — 设置张量属性
925+ - `set_node_attr_int64/string/bool` (L845+) — 设置节点属性
926+ 
927+- **`TensorHolder`**([tensor_holder.py:36](../../api/python/ge/ge/es/tensor_holder.py#L36)):张量持有者
928+ - 支持 `+ - * /` 运算符重载(L219+),分别对应 `Add/Sub/Mul/Div` 算子
929+ - **仅 4 个数学算子有 Python 运算符重载**
930+ 
931+**能力边界**:ES core 没有通用 `op(type, name)` 工厂,ONNX Plugin 方案也明确不新增这类字符串工厂。当前算子创建途径是:
932+1. `GraphBuilder.create_input/const_*/scalar_*/variable` — 特定类型节点构造
933+2. `TensorHolder.add/sub/mul/div` — 仅 4 个数学算子
934+3. 其他算子(MatMul/Conv/Relu/Identity/Threshold 等)由生成的 ES 算子包通过 entry_points 机制提供,该包属于外部 ops 依赖
935+ 
936+### 6.4 custom_op 模块(注册/bridge 模式参考)
937+ 
938+[官方文档:ge_python_custom_op_design.md](../../docs/zh/design/modules/ge_python/ge_python_custom_op_design.md)
939+ 
940+**面向 eager 执行**(不是图构建)。核心:
941+ 
942+- **`register_op_impl(op_type)` 装饰器**([registry.py:123](../../api/python/ge/ge/custom_op/registry.py#L123)):注册 `EagerExecuteOp` 子类
943+- **`OpImplDescriptor` 描述符**([registry.py:26-27](../../api/python/ge/ge/custom_op/registry.py#L26)):字段 `descriptor_key/op_type/module_name/class_name/interfaces/cls`,不包含 ONNX parser 的注册身份字段
944+- **`_bridge.py` 的 instance_id 分发**([_bridge.py:78](../../api/python/ge/ge/custom_op/_bridge.py#L78)):C++ 侧为每个算子实例分配 instance_id,Python 侧用 dict 维护 instance_id→holder
945+- **`bootstrap.py` 的插件发现**([bootstrap.py:21-24](../../api/python/ge/ge/custom_op/bootstrap.py#L21)):环境变量 `ASCEND_CUSTOM_OPP_PATH`,复用 `_internal/plugin_loader.load_plugins_from_env`
946+ 
947+### 6.5 passes 模块(架构借鉴价值最高)
948+ 
949+[官方文档:ge_python_pass_design.md](../../docs/zh/design/modules/ge_python/ge_python_pass_design.md)
950+ 
951+**面向图优化 pass**(pattern match + replace)。核心:
952+ 
953+- **`register_fusion_pass/register_decompose_pass` 装饰器**([registry.py:129,143](../../api/python/ge/ge/passes/registry.py#L129))
954+- **`PassDescriptor` 描述符**([registry.py:28-39](../../api/python/ge/ge/passes/registry.py#L28)):共 **8 个字段**`descriptor_key/pass_name/module_name/class_name/stage/kind/cls/op_types`,其中 `op_types` 默认为空列表
955+- **`DecomposePass`**([base.py:113](../../api/python/ge/ge/passes/base.py#L113)):`meet_requirements(node) + replacement(node) -> Graph`,结构上与 ONNX 插件的 ParseOpToGraph 相似,但操作 `Node`(GNode) 而非 `Operator&`
956+- **`_bridge.py` 的 borrow_node 句柄借用**(调用在 [_bridge.py:159](../../api/python/ge/ge/passes/_bridge.py#L159),定义在 `_native.py:48` 即 C++ pybind11 绑定):从 C++ node 指针构造不拥有所有权的 Node 视图
957+- **`bootstrap.py` 的插件发现**:环境变量 `ASCEND_GE_PY_PASS_PATH`
958+- **`runtime.py` 的 fallback codegen**([runtime.py:540](../../api/python/ge/ge/passes/runtime.py#L540)):找不到 .so 时自动 codegen 编译兜底
959+ 
960+### 6.6 graph 模块
961+ 
962+- **`Graph` 类**([graph.py:34](../../api/python/ge/ge/graph/graph.py#L34)):图操作(set_attr/get_attr/add_data_edge/add_subgraph/save_to_air)。**没有 add_node/create_op 方法**——不能直接创建算子节点
963+- **`Node` 类**([node.py:25](../../api/python/ge/ge/graph/node.py#L25)):节点操作(set_attr/get_attr/get_input_desc/update_input_desc)。包装的是 GNode,不是 `ge::Operator`
964+- **没有 `Operator` 类的 Python 绑定**。已用 grep 确认
965+ 
966+### 6.7 _capi 与 _internal
967+ 
968+- **`_capi/`**:所有 ctypes C 库包装器(见 6.2 表格)
969+- **`_internal/plugin_loader.py`**([plugin_loader.py:80](../../api/python/ge/ge/_internal/plugin_loader.py#L80)):`load_plugins_from_env(env_name, module_prefix, plugin_kind)` 通用插件文件扫描,被 custom_op/passes 复用
970+- **`_internal/artifact_utils.py`**([artifact_utils.py:27](../../api/python/ge/ge/_internal/artifact_utils.py#L27)):`PythonArtifact`/`find_compatible_artifact`/`load_module_from_path`,native 产物发现
971+ 
972+---
973+ 
974+## 第 7 章 ONNX Plugin Python 化相关的现状事实
975+ 
976+> 本章仅汇总与 Python 化相关的现有代码事实,不定义公开接口、共存规则、分期计划或 bridge 设计。权威 what/why 见 [需求与评审决策](../requirements/onnx_plugin_python_requirement_analysis.md),完整 how/design 见 [技术方案底稿](./onnx_plugin_python_modification_plan.md),开发者阅读版见 [开发者友好版设计](./onnx_plugin_python_developer_design.md),已确认的方案错误、阻塞项与设计空白见 [方案评审记录](./onnx_plugin_python_plan_review_record.md)。
977+ 
978+### 7.1 Parser 侧接入事实
979+ 
980+- ONNX 自定义算子解析以 `OpRegistry` 为统一注册和查找入口,回调表由 `OpRegistry::Register`([register.cpp:1050](../../graph_metadef/register/register.cpp#L1050))填充。
981+- `ParseParamByOperatorFunc` 在 [onnx_parser.cc:603](../../parser/parser/onnx/onnx_parser.cc#L603) 查询并分流;`ParseParamFunc``OnnxCustomParserAdapter` 调用([adapter.cc:32](../../parser/parser/onnx/onnx_custom_parser_adapter.cc#L32))。
982+- `ParseOpToGraphFunc` 在 [parser_utils.cc:50](../../parser/parser/common/parser_utils.cc#L50) 查询,**不经 adapter**`ExpandOneToManyGraph` 在 [parser_utils.cc:48](../../parser/parser/common/parser_utils.cc#L48) 读取 `ATTR_NAME_FRAMEWORK_ORIGINAL_TYPE` 后查询该回调。
983+- 插件 .so 的 `OpReceiver` 在 dlopen 时只把注册数据 push 到 `registrationDatas`,真正的 `Finalize`(使用 framework creator 为具体 `om_type` 注册到 OpParserFactory)和 `Register`(填充回调 map)由调用方显式触发(见第 5 章)。
984+ 
985+### 7.2 Python 侧现有能力
986+ 
987+- `GraphBuilder.build_and_reset`([graph_builder.py:938](../../api/python/ge/ge/es/graph_builder.py#L938))返回拥有所有权的 `ge.graph.Graph``owns_handle=True`,[graph.py:76](../../api/python/ge/ge/graph/graph.py#L76))。
988+- `ge.graph.Graph`([graph.py:34](../../api/python/ge/ge/graph/graph.py#L34))无 `add_node`/`create_op` 方法,不支持 `__copy__`/`__deepcopy__`([graph.py:86-92](../../api/python/ge/ge/graph/graph.py#L86))。
989+- `GraphUtilsEx::CopyGraph`([graph_utils_ex.cc:92](../../graph_metadef/graph/utils/graph_utils_ex.cc#L92))提供 C++ 侧图拷贝。
990+- `ReleaseGraph`([graph_handle_binding.cc:25](../../api/python/ge/ge/passes/native_bindings/graph_handle_binding.cc#L25))提供 Python 侧图所有权释放的 pybind11 绑定。
991+- `_internal/plugin_loader.py``load_plugins_from_env`([plugin_loader.py:80](../../api/python/ge/ge/_internal/plugin_loader.py#L80))是通用插件发现入口,被 `custom_op/bootstrap.py`([bootstrap.py:21](../../api/python/ge/ge/custom_op/bootstrap.py#L21),环境变量 `ASCEND_CUSTOM_OPP_PATH`)和 passes 复用。
992+- `passes/registry.py``PassDescriptor`([registry.py:28](../../api/python/ge/ge/passes/registry.py#L28))含 8 个字段:`descriptor_key/pass_name/module_name/class_name/stage/kind/cls/op_types`
993+- C++ 侧 `python_bridge_loader_utils.h`([python_bridge_loader_utils.h:24](../../base/common/python_runtime/python_bridge_loader_utils.h#L24))和 `python_artifact_utils.h`([python_artifact_utils.h:30](../../base/common/python_runtime/python_artifact_utils.h#L30))提供 bridge 加载、ABI 校验和 artifact 发现的现有基础设施。
994+ 
995+### 7.3 测试目标现状
996+ 
997+- Parser UT 目标 `ut_parser`([tests/parser/ut/parser/CMakeLists.txt:48](../../tests/parser/ut/parser/CMakeLists.txt#L48)),含 `onnx_parser_unittest.cc`
998+- Parser ST 目标 `st_parser`([tests/parser/st/CMakeLists.txt:40](../../tests/parser/st/CMakeLists.txt#L40)),含 `test_onnx_parser.cc`
999+- Python GE 测试环境安装入口在 [tests/ge/ut/ge/graph/pyge_tests/CMakeLists.txt:55-76](../../tests/ge/ut/ge/graph/pyge_tests/CMakeLists.txt#L55),安装 ge-py 和 es_ut_test 包(这是测试环境安装入口,不是名为 pyge_tests 的 CMake target)。
1000+ 
1001+所有关于公开 API 形态、共存规则、回调分期、bridge 架构、Python 包布局、初始化钩子和测试新增的决策,均不属于本文档范围。
1002+ 
1003+---
1004+ 
1005+## 第 8 章 关键文档与代码索引
1006+ 
1007+### 8.1 官方文档(docs/zh/design/)
1008+ 
1009+| 文档 | 用途 |
1010+|------|------|
1011+| [architecture.md](../../docs/zh/design/architecture.md) | GE 架构总览、AscendIR 介绍、编译优化、插件扩展机制 |
1012+| [modules/graph_metadef/ascend-ir.md](../../docs/zh/design/modules/graph_metadef/ascend-ir.md) | AscendIR 四层对象模型(ComputeGraph/Node/OpDesc/GeTensorDesc)、锚点系统、算子注册 |
1013+| [modules/compiler/compiler.md](../../docs/zh/design/modules/compiler/compiler.md) | 编译器四阶段(预处理/图优化/引擎分区/构建)、Pass 体系、融合优化 |
1014+| [modules/runtime/runtime.md](../../docs/zh/design/modules/runtime/runtime.md) | 运行时双版本(v1 静态/v2 动态)、模型加载、Task Sink |
1015+| [modules/ge_python/ge_python.md](../../docs/zh/design/modules/ge_python/ge_python.md) | Python 模块类关系(graph/es/custom_op/passes/session 等) |
1016+| [modules/ge_python/ge_python_custom_op_design.md](../../docs/zh/design/modules/ge_python/ge_python_custom_op_design.md) | Python 自定义算子设计 |
1017+| [modules/ge_python/ge_python_pass_design.md](../../docs/zh/design/modules/ge_python/ge_python_pass_design.md) | Python Pass 设计 |
1018+| [modules/es_graph/design/architecture_design.md](../../docs/zh/design/modules/es_graph/design/architecture_design.md) | ES 构图设计(C/C++/Python 三语言、API/ABI 兼容性、IR 语义兼容) |
1019+| [cross_feature_check.md](../../docs/zh/design/cross_feature_check.md) | 特性交叉分析(静态/动态 Shape、离线/在线场景) |
1020+ 
1021+### 8.2 关键源码索引
1022+ 
1023+#### Parser 链路
1024+| 文件 | 关键内容 |
1025+|------|----------|
1026+| [parser/parser/onnx/onnx_parser.cc](../../parser/parser/onnx/onnx_parser.cc) | ONNX 解析主入口 `aclgrphParseONNX` (L110)、`OnnxModelParser::Parse` (L1124)、`ParseAllNodeProto` (L631)、`AdapterOpType` (L453)、`ConstructOriType` (L412)、`ParseOpParam` (L596) |
1027+| [parser/parser/onnx/onnx_custom_parser_adapter.cc](../../parser/parser/onnx/onnx_custom_parser_adapter.cc) | 链路咽喉,3 个 ParseParams 重载 (L25/L41),调 `GetParseParamFunc` (L32) / `GetParseParamByOperatorFunc` (L43) |
1028+| [parser/parser/common/parser_utils.cc](../../parser/parser/common/parser_utils.cc) | `ExpandOneToManyGraph` (L42),调 `GetParseOpToGraphFunc` (L50) —— **不经 adapter** |
1029+| [parser/parser/common/op_registration_tbe.cc](../../parser/parser/common/op_registration_tbe.cc) | 接线层,把 OnnxCustomParserAdapter 注册到 OpParserFactory (L130-138) |
1030+| [parser/parser/common/tbe_plugin_loader.cc](../../parser/parser/common/tbe_plugin_loader.cc) | 插件 .so 动态加载器,`GetCustomOpPath` (L215) |
1031+| [parser/parser/common/op_parser_factory.h](../../parser/parser/common/op_parser_factory.h) | OpParser 工厂 + `CustomParserAdapterRegistry` + `REGISTER_CUSTOM_PARSER_ADAPTER_CREATOR` 宏 (L183) |
1032+ 
1033+#### OpRegistry
1034+| 文件 | 关键内容 |
1035+|------|----------|
1036+| [inc/graph_metadef/register/op_registry.h](../../inc/graph_metadef/register/op_registry.h) | OpRegistry 类声明 (L47-95),11 个不同名 Get* 函数 / 14 个声明 (L55-82),10 个私有 map (L85-94) |
1037+| [graph_metadef/register/register.cpp](../../graph_metadef/register/register.cpp) | OpRegistry 实现:`Instance()` (L1039)、`Register()` (L1050)、各 Get* (L1094-1230)、`OpReceiver` (L713)、`OpRegistrationDataImpl` (L717) |
1038+| [api/atc/main_impl.cc](../../api/atc/main_impl.cc) | atc 插件加载 `LoadCustomOpLib` (L1284-1326) |
1039+| [api/session/common/plugin/tbe_plugin_manager.cc](../../api/session/common/plugin/tbe_plugin_manager.cc) | session 插件加载 `LoadCustomOpLib` (L160-178) |
1040+ 
1041+#### Python 模块
1042+| 文件 | 关键内容 |
1043+|------|----------|
1044+| [api/python/ge/ge/es/graph_builder.py](../../api/python/ge/ge/es/graph_builder.py) | GraphBuilder:create_input (L177)、create_const_* (L267+)、build_and_reset (L938) |
1045+| [api/python/ge/ge/es/tensor_holder.py](../../api/python/ge/ge/es/tensor_holder.py) | TensorHolder:`+ - * /` 重载 (L219+) → Add/Sub/Mul/Div |
1046+| [api/python/ge/ge/custom_op/registry.py](../../api/python/ge/ge/custom_op/registry.py) | `register_op_impl` 装饰器 (L123)、`OpImplDescriptor` (L26) |
1047+| [api/python/ge/ge/custom_op/_bridge.py](../../api/python/ge/ge/custom_op/_bridge.py) | instance_id 分发:`call_execute` (L78) |
1048+| [api/python/ge/ge/custom_op/bootstrap.py](../../api/python/ge/ge/custom_op/bootstrap.py) | 插件发现 `ASCEND_CUSTOM_OPP_PATH` (L21) |
1049+| [api/python/ge/ge/passes/registry.py](../../api/python/ge/ge/passes/registry.py) | `register_fusion_pass` (L129) / `register_decompose_pass` (L143)、`PassDescriptor` (L28) |
1050+| [api/python/ge/ge/passes/base.py](../../api/python/ge/ge/passes/base.py) | `FusionBasePass` (L42) / `PatternFusionPass` (L49) / `DecomposePass` (L113) |
1051+| [api/python/ge/ge/passes/_bridge.py](../../api/python/ge/ge/passes/_bridge.py) | borrow_node 句柄借用 (L159)、instance_id 分发 |
1052+| [api/python/ge/ge/passes/bootstrap.py](../../api/python/ge/ge/passes/bootstrap.py) | 插件发现 `ASCEND_GE_PY_PASS_PATH` |
1053+| [api/python/ge/ge/passes/runtime.py](../../api/python/ge/ge/passes/runtime.py) | fallback codegen 编译兜底 (L540) |
1054+| [api/python/ge/ge/_internal/plugin_loader.py](../../api/python/ge/ge/_internal/plugin_loader.py) | 通用插件加载 `load_plugins_from_env` (L80) |
1055+| [api/python/ge/ge/graph/graph.py](../../api/python/ge/ge/graph/graph.py) | Graph 类(无 add_node/create_op) |
1056+| [api/python/ge/ge/graph/node.py](../../api/python/ge/ge/graph/node.py) | Node 类(包装 GNode,非 Operator) |
1057+ 
1058+#### ONNX protobuf
1059+| 文件 | 关键内容 |
1060+|------|----------|
1061+| [graph_metadef/proto/onnx/ge_onnx.proto](../../graph_metadef/proto/onnx/ge_onnx.proto) | ONNX 数据结构定义:`NodeProto` (L175)、`AttributeProto` (L97) |
1062+ 
1063+### 8.3 hfqx 已有笔记
1064+ 
1065+| 文档 | 说明 |
1066+|------|------|
1067+| [requirements/onnx_plugin_python_requirement_analysis.md](../requirements/onnx_plugin_python_requirement_analysis.md) | ONNX Plugin Python 化权威需求与评审决策 |
1068+| [analysis/onnx_plugin_python_modification_plan.md](./onnx_plugin_python_modification_plan.md) | ONNX Plugin Python 化完整技术方案和评审底稿 |
1069+| [analysis/onnx_plugin_python_developer_design.md](./onnx_plugin_python_developer_design.md) | ONNX Plugin Python 化开发者友好版设计文档 |
1070+| [analysis/onnx_plugin_python_plan_review_record.md](./onnx_plugin_python_plan_review_record.md) | ONNX Plugin Python 化技术方案评审记录(错误、阻塞项、设计空白) |
1071+| [dev-log/atc-onnx-experiments.md](../dev-log/atc-onnx-experiments.md) | atc 编译 ONNX 实验记录(解析插件 vs 算子实现两层认知) |
1072+| [dev-log/custom_op-samples-results.md](../dev-log/custom_op-samples-results.md) | custom_op 5 个样例运行结果 |
1073+| [notes/dev-process-notes.md](../notes/dev-process-notes.md) | 开发流程要点(跨包核对原则) |
1074+| [env/ge-env-setup.md](../env/ge-env-setup.md) | GE 开发环境配置指南 |
1075+ 
1076+---
1077+ 
1078+## 附:名词解释
1079+ 
1080+| 术语 | 解释 |
1081+|------|------|
1082+| **GE** | Graph Engine,华为昇腾的图编译器和执行器 |
1083+| **AscendIR / AIR** | Ascend Intermediate Representation,GE 内部的中间表示格式 |
1084+| **ONNX** | Open Neural Network Exchange,通用模型文件格式 |
1085+| **Operator** | GE 里的"算子"概念,可以理解为图里的一个节点 |
1086+| **Graph** | GE 里的"图"概念,由多个 Operator 组成 |
1087+| **NodeProto** | ONNX 里描述一个算子节点的数据结构(protobuf) |
1088+| **protobuf** | Google 的数据序列化库,ONNX 用它存储数据 |
1089+| **插件** | 开发者写的"当遇到某个 ONNX 算子时怎么处理"的代码,插件化地扩展 GE 的算子认识能力 |
1090+| **ParseParams** | 参数解析回调:解析源算子信息并写入目标 GE Operator;现有链路包含 Message 和 Operator 两种形态 |
1091+| **ParseOpToGraph** | 可选的一对多展开回调:把一个 GE Operator 展开成子图 |
1092+| **OpRegistry** | GE 的注册表单例,存所有插件的登记信息 |
1093+| **OpRegistrationData** | 登记表数据结构,用链式 builder 填充 |
1094+| **OpReceiver** | 触发注册的 C++ 静态变量类(只 push 到 registrationDatas,不直接 Register) |
1095+| **ImplyType** | 暗示类型,当前映射见 [type_utils.cc:70-73](../../graph_metadef/graph/utils/type_utils.cc#L70):BUILDIN/TVM/CUSTOM/AI_CPU/CCE/GELOCAL/HCCL/INVALID(注意是 AI_CPU 而非 CPU) |
1096+| **DynamicInputRegister** | 动态注册输入(算子输入数量不固定时用) |
1097+| **TensorDesc** | 张量描述符,描述数据类型/格式/形状 |
1098+| **ES 构图** | Eager-Style 构图,GE 提供的函数式图构建方式 |
1099+| **GraphBuilder** | ES 构图的核心类,提供 create_input/const/build_and_reset |
1100+| **TensorHolder** | ES 构图里的张量对象,支持 +−*/ 运算符重载 |
1101+| **ctypes** | Python 标准库,用于调用 C 动态库 |
1102+| **pybind11** | C++ 库,用于把 C++ 类/函数暴露给 Python |
1103+| **bridge** | 桥接层,让 C++ 和 Python 互通 |
1104+| **entry_points** | Python 包的插件发现机制,GE 的 ES 用它加载外部插件 |
1105+| **ASCEND_CUSTOM_OPP_PATH** | 自定义算子包路径环境变量 |
1106+| **ASCEND_GE_PY_PASS_PATH** | Python Pass 插件路径环境变量 |
1107+| **atc** | ATC(Ascend Tensor Compiler),GE 的命令行编译工具 |
1108+| **OM** | Offline Model,GE 编译产物,可在设备上直接加载执行 |
@@ -0,0 +1,919 @@
1+# ONNX Plugin C++ 链路完整调研
2+ 
3+> 调研日期:2026-07-30
4+> 目的:为 ONNX Plugin Python 化提供 C++ 侧完整能力清单,确保 Python 模块能覆盖所有 C++ 链路功能。
5+ 
6+---
7+ 
8+## 一、C++ 完整生命周期链路图
9+ 
10+```mermaid
11+flowchart TD
12+ subgraph Phase0["阶段0: 编译期 - 静态注册数据准备"]
13+ A1["cpp: REGISTER_CUSTOM_OP(PartitionedCall)"] --> A2
14+ A2["展开为: static OpReceiver register_opN = OpRegistrationData('PartitionedCall')"] --> A3
15+ A3["OpRegistrationData 构造 → 链式调用设置 FrameworkType/OriginOpType/ImplyType/回调函数指针"]
16+ end
17+ 
18+ subgraph Phase1["阶段1: 加载期 - 插件 .so 动态加载"]
19+ B1["aclgrphParseONNX() 入口"] --> B2["PrepareBeforeParse()"]
20+ B2 --> B3["AclParserInitialize() → dlopen 自定义 plugin.so"]
21+ B3 --> B4["plugin.so 加载时触发所有 static OpReceiver 构造"]
22+ B4 --> B5["OpReceiver 构造: OpRegistry::Instance()->registrationDatas.push_back(reg_data)"]
23+ end
24+ 
25+ subgraph Phase2["阶段2: 注册提交期 - Finalize + OpRegistry::Register"]
26+ C1["调用方遍历 registrationDatas"] --> C1a["OpRegistrationTbe::Finalize(reg_data)"]
27+ C1a --> C2["OpRegistry::Register(reg_data, is_custom_op=false)"]
28+ C2 --> C3["按不同 key 格式写入内部 Map (详见模块13 GetParserKey):"]
29+ C3 --> C3a["parse_params_by_op_func_map_[om_type_ori_type] = parse_param_by_op_fn_"]
30+ C3 --> C3b["op_parse_params_fn_map_[om_type_ori_type] = parseParamFn_"]
31+ C3 --> C3c["fusion_op_parse_params_fn_map_[om_type_ori_type] = fusionParseParamFn_"]
32+ C3 --> C3d["fusion_parse_params_by_op_fn_map_[om_type_ori_type] = fusion_parse_param_by_op_fn_"]
33+ C3 --> C3e["parse_op_to_graph_fn_map_[om_type_ori_type] = parse_op_to_graph_fn_"]
34+ C3 --> C3f["remove_input_configure_map_[om_type_ori_type] = remove_input_configure_vec_"]
35+ C3 --> C3g["origin_type_to_om_type_[ori_type] = om_optype"]
36+ C3 --> C3h["op_run_mode_map_[om_optype] = imply_type_"]
37+ C3 --> C3i["op_types_to_parse_subgraph_post_func_[om_fmk_type] / ..._v2_ (按 omtype+fmk key)"]
38+ end
39+ 
40+ subgraph Phase3["阶段3: 解析期 - ONNX 模型解析"]
41+ D1["OnnxModelParser::Parse(model_file)"] --> D2["ModelParseToGraph() BFS 遍历"]
42+ D2 --> D3["ModelParseToGraphImpl() 12步解析"]
43+ D3 --> D4["Step7: ParseAllNodeProto()"]
44+ D4 --> D5["逐 NodeProto 处理"]
45+ D5 --> D6["AdapterOpType() → ConstructOriType() 构造 'domain::version::op_type'"]
46+ D6 --> D6b["node_proto->set_op_type(ori_type) 重写为完整原始类型"]
47+ D6b --> D7["TransNodeToOperator() → OperatorFactory::CreateOperator()"]
48+ D7 --> D8["ParseOpParam(node_proto, op)"]
49+ D8 --> D9["Step11: ExpandOneToManyGraph()"]
50+ D9 --> D10["PostOpProcessForSubgraph() → GetParseSubgraphPostFunc"]
51+ end
52+ 
53+ subgraph Phase4["阶段4: 回调期 - Python 桥接的目标"]
54+ E1["ParseOpParam() 内部:"] --> E2{"GetParseParamByOperatorFunc(op_type) 有注册?"}
55+ E2 -->|否| E3["默认解析: op_parser->ParseParams(node_proto, op)"]
56+ E2 -->|是| E4["创建 op_src, Message2Operator 自动映射"]
57+ E4 --> E4b["OnnxCustomParserAdapter::ParseParams(op_src, op_dest)"]
58+ E4b --> E5["再次 GetParseParamByOperatorFunc(op_type)"]
59+ E5 --> E6["parse_param_by_op_fn(op_src, op_dest) ★ 调用注册的回调"]
60+ E6 --> E7["返回 SUCCESS / FAILED / PARAM_INVALID"]
61+ 
62+ F1["Step11: ExpandOneToManyGraph()"] --> F2["遍历图中所有节点"]
63+ F2 --> F3["GetParseOpToGraphFunc(node_type, ori_type)"]
64+ F3 --> F4{"有注册?"}
65+ F4 -->|否| F5["skip"]
66+ F4 -->|是| F6["parse_op_to_graph_func(op, subgraph) ★ 调用注册的回调"]
67+ F6 --> F7["ExpandNodeToSubgraph() → 将子图替换原节点"]
68+ 
69+ G1["子图后处理: PostOpProcessForSubgraph()"] --> G2["GetParseSubgraphPostFunc(op_type, domi::ONNX)"]
70+ G2 --> G3{"有 legacy/V2 回调?"}
71+ G3 -->|否| G4["skip"]
72+ G3 -->|是| G5["post_func(subgraph_name, graph) ★ 子图后处理回调"]
73+ end
74+ 
75+ Phase0 --> Phase1
76+ Phase1 --> Phase2
77+ Phase2 --> Phase3
78+ Phase3 --> Phase4
79+ 
80+ style Phase0 fill:#e1f5fe
81+ style Phase1 fill:#fff3e0
82+ style Phase2 fill:#f3e5f5
83+ style Phase3 fill:#e8f5e9
84+ style Phase4 fill:#fce4ec
85+```
86+ 
87+### 入口差异
88+ 
89+上图展示公共注册提交链,实际调用方有差异:
90+ 
91+| 场景 | 入口 | 注册时序 |
92+|---|---|---|
93+| atc | `api/atc/main_impl.cc:1284-1327` `LoadCustomOpLib` | 先 `Finalize`,再 `Register`,不经过 `AclParserInitialize` |
94+| session 插件加载 | `api/session/common/plugin/tbe_plugin_manager.cc:160-178` | 只调用 `Register` |
95+| session ONNX 解析 | `parser/parser/common/acl_graph_parser_util.cc:247-253` | 再调用 `Finalize``Register` |
96+ 
97+`OpRegistry::Register(is_custom_op=false)` 对已有 key 可能静默跳过并返回成功,因此 Python coordinator 的冲突预检查必须发生在 `Register` 之前,不能依赖底层返回值。
98+ 
99+---
100+ 
101+## 二、按模块分类的完整接口清单
102+ 
103+### 模块 1: metadef 注册基础设施 (`graph_metadef/register/`)
104+ 
105+#### 1.1 头文件: `inc/external/register/register.h` (metadef 外部公开头文件)
106+> GE 内引用路径: `inc/graph_metadef/register/graph_register.h` → `#include "register/register.h"`
107+>
108+> `register.h` 在 metadef 仓库源码中可直接读取: `metadef/inc/external/register/register.h`
109+ 
110+| 接口/类 | 类型 | 说明 | Python 化必要性 |
111+|---------|------|------|----------------|
112+| `REGISTER_CUSTOM_OP(name)` | 宏 | 触发静态注册,展开为 `static OpReceiver` + `OpRegistrationData(name)` | ⭐ 核心:需要 Python 替代机制 |
113+| `REGISTER_AUTOMAPPING_SUBGRAPH_IO_INDEX_FUNC(framework, fun)` | 宏 | 注册子图 IO 自动映射函数 | ONNX 已注册(`auto_mapping_subgraph_io_index_func.cc:189`) |
114+| `OpRegistrationData(const char_t *om_optype)` | 构造函数 | 创建注册数据,参数为 OM 模型中的算子类型名 | ⭐ 需要 Python 表达 |
115+| `OpRegistrationData::FrameworkType(fmk_type)` | Builder 方法 | 设置框架类型 (ONNX=5) | ⭐ 需要 Python 表达 |
116+| `OpRegistrationData::OriginOpType(initializer_list<string>)` | Builder 方法 | 设置原始模型中算子类型列表,支持多个 | ⭐ 需要 Python 表达 |
117+| `OpRegistrationData::OriginOpType(vector<AscendString>)` | Builder 方法 | OriginOpType 的 AscendString 版本 | ⭐ |
118+| `OpRegistrationData::OriginOpType(const char_t*)` | Builder 方法 | 单个 origin type 的便捷方法 | ⭐ |
119+| `OpRegistrationData::ParseParamsFn(ParseParamFunc)` | Builder 方法 | 设置 ParseParam 回调 (protobuf::Message* → Operator&) | ⭐ 核心回调 |
120+| `OpRegistrationData::ParseParamsByOperatorFn(ParseParamByOpFunc)` | Builder 方法 | 设置 ParseParam 回调 (Operator& → Operator&),较新 API | ⭐ 核心回调 |
121+| `OpRegistrationData::FusionParseParamsFn(FusionParseParamFunc)` | Builder 方法 | 设置融合参数解析回调 | 可暂缓 |
122+| `OpRegistrationData::FusionParseParamsFn(FusionParseParamByOpFunc)` | Builder 方法 | 设置融合参数解析回调 V2 | 可暂缓 |
123+| `OpRegistrationData::ParseSubgraphPostFn(ParseSubgraphFunc)` | Builder 方法 | 子图后处理回调(legacy) | ⭐ 已使用(如 If 算子子图后处理) |
124+| `OpRegistrationData::ParseSubgraphPostFn(ParseSubgraphFuncV2)` | Builder 方法 | 子图后处理回调 V2(AscendString) | ⭐ 已使用 |
125+| `OpRegistrationData::ImplyType(domi::ImplyType)` | Builder 方法 | 设置执行类型 (TVM/CUSTOM/AI_CPU 等) | ⭐ 需要 Python 表达 |
126+| `OpRegistrationData::DelInputWithCond(int32_t, string, bool)` | Builder 方法 | 条件删除输入 | 可暂缓 |
127+| `OpRegistrationData::DelInputWithOriginalType(int32_t, string)` | Builder 方法 | 按原始类型删除输入 | 可暂缓 |
128+| `OpRegistrationData::InputReorderVector(vector<int32_t>)` | Builder 方法 | 输入重排序 | 可暂缓 |
129+| `OpRegistrationData::ParseOpToGraphFn(ParseOpToGraphFunc)` | Builder 方法 | 设置一对多图展开回调 | ⭐ 核心回调 |
130+| `OpRegistrationData::GetImplyType()` | Getter | 获取执行类型 | 内部使用 |
131+| `OpRegistrationData::GetOmOptype()` | Getter | 获取 OM 算子类型 | 内部使用 |
132+| `OpRegistrationData::GetOriginOpTypeSet()` | Getter | 获取原始算子类型集合 | 内部使用 |
133+| `OpRegistrationData::GetFrameworkType()` | Getter | 获取框架类型 | 内部使用 |
134+| `OpRegistrationData::GetParseParamFn()` | Getter | 获取 ParseParam 回调 | 内部使用 |
135+| `OpRegistrationData::GetParseOpToGraphFn()` | Getter | 获取 ParseOpToGraph 回调 | 内部使用 |
136+ 
137+#### 1.2 回调函数类型定义
138+ 
139+| 类型名 | 完整签名 | 用途 | Python 化 |
140+|--------|---------|------|------------|
141+| `ParseParamFunc` | `Status(const google::protobuf::Message*, ge::Operator&)` | 从 ONNX/TF protobuf 解析参数到 GE Operator | ⭐ 核心 |
142+| `ParseParamByOpFunc` | `Status(const ge::Operator&, ge::Operator&)` | 从已构造的源 Operator 解析到目标 Operator | ⭐ 核心(新版 API) |
143+| `FusionParseParamFunc` | `Status(const vector<const Message*>&, Operator&)` | 融合多节点参数到单 Operator | 暂缓 |
144+| `FusionParseParamByOpFunc` | `Status(const vector<Operator>&, Operator&)` | 融合多 Operator 到单 Operator | 暂缓 |
145+| `ParseSubgraphFunc` | `Status(const string&, const Graph&)` | 子图生成后处理(legacy) | ⭐ 已使用(PostOpProcessForSubgraph 查询) |
146+| `ParseSubgraphFuncV2` | `Status(const AscendString&, const Graph&)` | 子图后处理 V2 | ⭐ 已使用 |
147+| `ParseOpToGraphFunc` | `Status(const ge::Operator&, ge::Graph&)` | 将单个 OP 展开为完整子图 | ⭐ 核心 |
148+| `AutoMappingSubgraphIOIndexFunc` | 复杂 IO 映射签名 | 子图 IO 索引自动映射 | 已注册 (ONNX 映射函数注册于 `auto_mapping_subgraph_io_index_func.cc:189`) |
149+ 
150+#### 1.3 枚举定义
151+ 
152+**`domi::FrameworkType`** (CANN SDK, `register_fmk_types.h`):
153+| 枚举值 | 数值 | 字符串 | 说明 |
154+|--------|------|--------|------|
155+| `CAFFE` | 0 | `"caffe"` | Caffe 框架 |
156+| `MINDSPORE` | 1 | `"mindspore"` | 昇思 MindSpore |
157+| `TENSORFLOW` | 3 | `"tensorflow"` | TensorFlow |
158+| `ANDROID_NN` | 4 | `"android_nn"` | Android NN |
159+| `ONNX` | 5 | `"onnx"` | ONNX ⭐ |
160+| `FRAMEWORK_RESERVED` | 6 | `"framework_reserved"` | 保留值 |
161+ 
162+**`ge::FrameworkType`** (`inc/graph_metadef/common/ge_common/ge_types.h:31-37`):
163+```cpp
164+enum FrameworkType { CAFFE = 0, MINDSPORE = 1, TENSORFLOW = 3, ANDROID_NN = 4, ONNX = 5 };
165+```
166+ 
167+**`domi::ImplyType`** (`register_fmk_types.h`):
168+| 枚举值 | 序列化字符串 | 说明 |
169+|--------|------------|------|
170+| `BUILDIN` | `"buildin"` | 内置算子 |
171+| `TVM` | `"tvm"` | TBE 算子 |
172+| `CUSTOM` | `"custom"` | 自定义算子 |
173+| `AI_CPU` | `"ai_cpu"` | AI CPU 算子 |
174+| `CCE` | `"cce"` | CCE 算子 |
175+| `GELOCAL` | `"gelocal"` | GE Local 虚拟算子 |
176+| `HCCL` | `"hccl"` | HCCL 集合通信 |
177+| `INVALID` | `"invalid"` | 无效 |
178+ 
179+#### 1.4 实现文件: `graph_metadef/register/register.cpp`
180+ 
181+| 类/函数 | 行号 | 说明 | Python 化 |
182+|---------|------|------|----------|
183+| `OpReceiver::OpReceiver(OpRegistrationData &reg_data)` | ~713 | 构造函数:将 reg_data push 到 `registrationDatas` 向量 | ⭐ 触发点 |
184+| `OpRegistrationDataImpl` (内部类) | ~717-738 | PIMPL 实现,持有所有注册数据字段 | 不直接暴露 |
185+| `OpRegistry::Register(reg_data, is_custom_op)` | 1050-1092 | 核心:将注册数据写入 9 个内部 map;非 custom 重复 key 可能静默跳过 | ⭐ 核心流程 |
186+| `OpRegistry::Instance()` | - | 单例获取 | 内部 |
187+| `OpRegistry::GetParseParamFunc(op_type, ori_type)` | 声明 ~59 | 按 OM类型+原始类型 查 ParseParam 回调 | ⭐ 查询接口 |
188+| `OpRegistry::GetParseParamByOperatorFunc(ori_type)` | 声明 ~61 | 按原始类型查 ParseParamByOp 回调 | ⭐ 查询接口 |
189+| `OpRegistry::GetFusionParseParamFunc(op_type, ori_type)` | 声明 ~63 | 查融合回调 | 暂缓 |
190+| `OpRegistry::GetParseOpToGraphFunc(op_type, ori_type)` | 声明 ~82 | 查一对多展开回调 | ⭐ 查询接口 |
191+| `OpRegistry::GetImplyType(op_type)` | 声明 ~55 | 查算子的执行类型 | 内部 |
192+| `OpRegistry::GetOmTypeByOriOpType(ori_optype, om_type)` | 声明 ~78 | 原始类型→OM类型映射 | 内部 |
193+| `OpRegistry::GetParseSubgraphPostFunc(op_type)` | 声明 ~65 | 查子图后处理回调 | 暂缓 |
194+| `OpRegistry::GetRemoveInputConfigure(ori_optype)` | 声明 ~76 | 查输入移除配置 | 暂缓 |
195+ 
196+---
197+ 
198+### 模块 2: ONNX Parser 解析引擎 (`parser/parser/onnx/`)
199+ 
200+#### 2.1 入口和外部 API
201+ 
202+| 文件 | 接口 | 说明 | Python 化 |
203+|------|------|------|----------|
204+| `inc/parser/external/parser/onnx_parser.h:30` | `aclgrphParseONNX(model_file, parser_params, graph)` | 解析 ONNX 模型文件→GE Graph | 已有 Python 间接调用 |
205+| `inc/parser/external/parser/onnx_parser.h:33` | `aclgrphParseONNXFromMem(buffer, size, parser_params, graph)` | 从内存解析 ONNX 模型 | 已有 Python 间接调用 |
206+ 
207+#### 2.2 OnnxModelParser 关键方法 (`parser/parser/onnx/onnx_parser.cc`)
208+ 
209+| 方法 | 行号 | 功能 | 说明 |
210+|------|------|------|------|
211+| `PrepareBeforeParse()` | ~56-92 | 加载插件 .so,创建 Parser 实例 | 设置 `ParserContext.type = domi::ONNX` |
212+| `Parse(model_file)` | ~1124 | 解析入口 | → `GetModelFromFile``ModelParseToGraph` |
213+| `ModelParseToGraph(onnx_model, graph)` | ~911 | 支持子图递归的 BFS 遍历 | 处理嵌套子图 |
214+| `ModelParseToGraphImpl(is_subgraph, onnx_graph, graph)` | ~1000 | 核心 12 步解析流程 | 详见下方子表 |
215+| `ParseInput()` | - | 解析图输入 → Data 节点 | |
216+| `ParseInitializer()` | - | 解析常量/权重 → Constant/FileConstant | |
217+| `ParseOutput()` | - | 记录图输出 tensor 名 | |
218+| `UpdateNodeNameAndOpType()` | - | 更新节点名称和常量类型 | |
219+| `ParseAllNodeProto()` | ~631 | **逐节点解析核心** | ⭐ |
220+| `SetOperatorInputs()` | - | 连接算子间的 tensor 边 | |
221+| `ExpandOneToManyGraph()` | ~1109 | **一对多图展开** | ⭐ 回调触发点 |
222+| `PostOpProcessForSubgraph()` | ~230 | **子图后处理** | ⭐ 查 ParseSubgraphPostFunc/V2 |
223+| `ParseOpParam(node_proto, op, op_parser)` | ~596-628 | 单个算子参数解析 | ⭐ 回调触发点,见 10.4 完整流程 |
224+| `ConstructOriType(node_proto, ori_type)` | 412-451 | 构造 `domain::version::type` 格式 | 由调用方决定是否写回 NodeProto |
225+| `AdapterOpType(node_proto, ori_type, op_type)` | 453-480(调用约 639) | `"ai.onnx::11::Op"` → OM type | 调用 ConstructOriType |
226+| `TransNodeToOperator(node_proto, op, op_type)` | 482-499(调用约 649) | NodeProto → Operator 对象 | ⭐ |
227+ 
228+#### 2.3 OnnxCustomParserAdapter (`parser/parser/onnx/onnx_custom_parser_adapter.cc`)
229+ 
230+| 方法 | 行号 | 说明 | Python 化 |
231+|------|------|------|----------|
232+| `ParseParams(const Message* op_src, Operator& op_dest)` | 25-39 | 通过 `GetParseParamFunc` 查回调并调用 | ⭐ Bridge 插入点 |
233+| `ParseParams(const Operator& op_src, Operator& op_dest)` | 41-53 | 通过 `GetParseParamByOperatorFunc` 查回调并调用 | ⭐ Bridge 插入点 |
234+| `REGISTER_CUSTOM_PARSER_ADAPTER_CREATOR(ONNX, OnnxCustomParserAdapter)` | 55 | framework 级静态 creator 注册 | 内部 |
235+ 
236+#### 2.4 ONNX 数据读取 (protobuf 层)
237+ 
238+| 数据 | protobuf 字段 | 读取方式 | Python 化要求 | 备注 |
239+|------|-------------|---------|-------------|------|
240+| 节点名 | `NodeProto::name()` | `node_proto->name()` | `OnnxNode.name` | |
241+| 算子类型 | `NodeProto::op_type()` | `node_proto->op_type()` | `OnnxNode.op_type` | parser 对非内置映射算子通过 `ConstructOriType()``onnx_parser.cc:412-451`)构造完整 origin type,并在 `ParseAllNodeProto` 中执行 `set_op_type``639-649`);不单独暴露 domain/opset |
242+| Domain | `NodeProto::domain()` | `node_proto->domain()` | 视需求决定 | 见上 |
243+| 输入列表 | `NodeProto::input(i)` | `node_proto->input_size()`, 遍历 | `OnnxNode.inputs` (tuple/list) | |
244+| 输出列表 | `NodeProto::output(i)` | `node_proto->output_size()`, 遍历 | `OnnxNode.outputs` (tuple/list) | |
245+| 属性列表 | `NodeProto::attribute()` | parser 侧识别并转换 AttributeProto | `OnnxNode.attrs` read-only Mapping | 首轮不通过 Message2Operator 自动映射 |
246+ 
247+#### 2.5 ONNX Proto 定义 (`graph_metadef/proto/onnx/ge_onnx.proto`)
248+ 
249+```protobuf
250+message NodeProto {
251+ repeated string input = 1; // 输入 tensor 名列表
252+ repeated string output = 2; // 输出 tensor 名列表
253+ string name = 3; // 节点名
254+ string op_type = 4; // 原始算子类型 (如 "ThresholdedRelu")
255+ string domain = 7; // 算子域 (如 "ai.onnx")
256+ repeated AttributeProto attribute = 5; // 属性列表
257+ string doc_string = 6; // 文档字符串
258+}
259+ 
260+message AttributeProto {
261+ string name = 1; // 属性名
262+ AttributeType type = 20; // 属性值类型枚举
263+ // oneof value: f, i, s, t, g, floats, ints, strings, tensors, graphs, ...
264+}
265+```
266+ 
267+---
268+ 
269+### 模块 3: Graph 扩展 (`parser/parser/common/parser_utils.cc`)
270+ 
271+| 函数 | 行号 | 说明 | Python 化 |
272+|------|------|------|----------|
273+| `ParserUtils::ExpandOneToManyGraph(graph, output_mapping)` | ~42-78 | 遍历图节点,查 ParseOpToGraphFunc 并展开 | ⭐ Bridge 插入点 |
274+| `ParserUtils::ExpandNodeToSubgraph(subgraph, node, graph, output_mapping)` | ~80-110 | 将展开的子图替换原节点,重新做拓扑排序 | 内部 |
275+ 
276+---
277+ 
278+### 模块 4: GE IR 算子接口 (`inc/graph_metadef/external/graph/operator.h`)
279+ 
280+#### 4.1 ge::Operator 构造
281+ 
282+| 方法 | 签名 | 说明 | Python 化 |
283+|------|------|------|----------|
284+| `Operator(type)` | `explicit Operator(const char_t *type)` | 按类型名构造 | ⭐ |
285+| `Operator(name, type)` | `Operator(const char_t *name, const char_t *type)` | 按名称+类型构造 | ⭐ |
286+| `Operator(name, type)` | `Operator(const AscendString &name, const AscendString &type)` | AscendString 版本 | ⭐ |
287+ 
288+#### 4.2 ge::Operator 属性操作 (SetAttr/GetAttr)
289+ 
290+支持以下**全部**属性数据类型(每种都有 `const char_t*``const std::string&` 两个版本):
291+ 
292+| 类型 | C++ SetAttr | C++ GetAttr | 说明 | Python 化 |
293+|------|------------|------------|------|-----------|
294+| `int64_t` | `SetAttr(name, int64_t)` | `GetAttr(name, int64_t&)` | 64位整型 | ⭐ |
295+| `int32_t` | `SetAttr(name, int32_t)` | `GetAttr(name, int32_t&)` | 32位整型 | ⭐ |
296+| `uint32_t` | `SetAttr(name, uint32_t)` | `GetAttr(name, uint32_t&)` | 无符号32位整型 | ⭐ |
297+| `float32_t` | `SetAttr(name, float32_t)` | `GetAttr(name, float32_t&)` | 单精度浮点 | ⭐ |
298+| `bool` | `SetAttr(name, bool)` | `GetAttr(name, bool&)` | 布尔 | ⭐ |
299+| `std::string` | `SetAttr(name, string)` | `GetAttr(name, string&)` | 字符串 | ⭐ |
300+| `AscendString` | `SetAttr(name, AscendString&)` | `GetAttr(name, AscendString&)` | GE内部字符串 | ⭐ |
301+| `ge::Tensor` | `SetAttr(name, Tensor&)` | `GetAttr(name, Tensor&)` | 张量 | ○ |
302+| `ge::AttrValue` | `SetAttr(name, AttrValue&&)` | `GetAttr(name, AttrValue&)` | 通用属性值 | ○ |
303+| `ge::DataType` | `SetAttr(name, DataType&)` | `GetAttr(name, DataType&)` | 数据类型枚举 | ○ |
304+| `ge::NamedAttrs` | `SetAttr(name, NamedAttrs&)` | `GetAttr(name, NamedAttrs&)` | 命名属性集 | 暂缓 |
305+| `OpBytes` | `SetAttr(name, OpBytes&)` | `GetAttr(name, OpBytes&)` | 字节数组 | 暂缓 |
306+| `vector<int64_t>` | `SetAttr(name, vector<int64_t>)` | `GetAttr(name, vector<int64_t>&)` | 64位整型列表 | ⭐ |
307+| `vector<int32_t>` | `SetAttr(name, vector<int32_t>)` | `GetAttr(name, vector<int32_t>&)` | 32位整型列表 | ⭐ |
308+| `vector<uint32_t>` | `SetAttr(name, vector<uint32_t>)` | `GetAttr(name, vector<uint32_t>&)` | 无符号列表 | ○ |
309+| `vector<float32_t>` | `SetAttr(name, vector<float32_t>)` | `GetAttr(name, vector<float32_t>&)` | 浮点列表 | ⭐ |
310+| `vector<bool>` | `SetAttr(name, vector<bool>)` | `GetAttr(name, vector<bool>&)` | 布尔列表 | ○ |
311+| `vector<string>` | `SetAttr(name, vector<string>)` | `GetAttr(name, vector<string>&)` | 字符串列表 | ○ |
312+| `vector<vector<int64_t>>` | `SetAttr(name, ...)` | `GetAttr(name, ...)` | 二维整型列表 | ○ |
313+| `vector<DataType>` | `SetAttr(name, ...)` | `GetAttr(name, ...)` | 数据类型列表 | ○ |
314+| `vector<Tensor>` | `SetAttr(name, ...)` | `GetAttr(name, ...)` | 张量列表 | 暂缓 |
315+| `vector<NamedAttrs>` | `SetAttr(name, ...)` | `GetAttr(name, ...)` | 命名属性列表 | 暂缓 |
316+ 
317+#### 4.3 ge::Operator 动态输入输出
318+ 
319+| 方法 | 签名 | 说明 | Python 化 | 实际使用 |
320+|------|------|------|-----------|---------|
321+| `DynamicInputRegister` | `(const char_t *name, uint32_t num, bool is_push_back=true)` | 注册动态输入: 名称、数量、是否追加 | ⭐ | ops-nn 全部 30 次调用使用此简单形式 |
322+| `DynamicInputRegister` | `(const char_t *name, uint32_t num, const char_t *datatype_symbol, bool is_push_back)` | 带 datatype_symbol 的版本 | ○ | **当前零使用** |
323+| `DynamicInputRegisterByIndex` | `(const char_t *name, uint32_t num, size_t index)` | 按索引注册动态输入 | ○ | **当前零使用** |
324+| `DynamicOutputRegister` | `(const char_t *name, uint32_t num, bool is_push_back=true)` | 注册动态输出 | ⭐ | ops-nn 全部 30 次调用使用此简单形式 |
325+| `DynamicOutputRegister` | `(const char_t *name, uint32_t num, const char_t *datatype_symbol, bool is_push_back)` | 带 datatype_symbol 的版本 | ○ | **当前零使用** |
326+ 
327+#### 4.4 ge::Operator TensorDesc 操作
328+ 
329+| 方法 | 说明 | Python 化 |
330+|------|------|-----------|
331+| `GetInputDesc(name/index)` | 获取输入 TensorDesc | ⭐ 当前 ops-nn 使用 10~15 次 |
332+| `GetOutputDesc(name/index)` | 获取输出 TensorDesc | ⭐ 当前 ops-nn 使用 10~15 次 |
333+| `UpdateInputDesc(name/index, TensorDesc)` | 更新输入描述 | ⭐ 当前 ops-nn 使用 7~11 次(如 NPUAntiQuant) |
334+| `UpdateOutputDesc(name/index, TensorDesc)` | 更新输出描述 | ⭐ 同上 |
335+| `TryGetInputDesc(name, TensorDesc&)` | 尝试获取输入描述 | ○ |
336+ 
337+#### 4.5 ge::Operator 连接操作
338+ 
339+| 方法 | 说明 | Python 化 |
340+|------|------|-----------|
341+| `SetInput(dst_name, src_oprt)` | 按目标输入名连接算子 (src index=0) | 内部使用 |
342+| `SetInput(dst_name, src_oprt, src_name)` | 按目标输入名+源输出名连接 | 内部 |
343+| `SetInput(dst_name, src_oprt, uint32_t src_index)` | 按目标输入名+源输出index连接 | 内部 |
344+| `SetInput(uint32_t dst_index, src_oprt, uint32_t src_index)` | 按index连接 | 内部 |
345+| `AddControlInput(src_oprt)` | 添加控制依赖 | 暂缓 |
346+ 
347+---
348+ 
349+### 模块 5: GE IR 图接口 (`inc/graph_metadef/external/graph/graph.h`)
350+ 
351+| 方法 | 签名 | 说明 | Python 化 |
352+|------|------|------|----------|
353+| `Graph(name)` | `explicit Graph(const char_t *name)` | 创建命名图 | ⭐ |
354+| `Graph()` | `Graph() = default` | 默认构造 | |
355+| `SetInputs` | `Graph& SetInputs(const vector<Operator> &inputs)` | 设置图输入 | ⭐ |
356+| `SetOutputs` | `Graph& SetOutputs(const vector<Operator> &outputs)` | 设置图输出 (单输出单index) | ⭐ |
357+| `SetOutputs` | `SetOutputs(const vector<pair<Operator, vector<size_t>>>&)` | 设置图输出 (多index) | ⭐ |
358+| `SetOutputs` | `SetOutputs(const vector<pair<GNode, int32_t>>&)` | 按 GNode 设置输出 | ○ |
359+| `SetTargets` | `Graph& SetTargets(const vector<Operator> &targets)` | 设置目标节点 | ○ |
360+| `AddOp` | `graphStatus AddOp(const Operator &op)` | 向图中添加算子 | ⭐ |
361+| `FindOpByName` | `graphStatus FindOpByName(name, Operator&)` | 按名查找算子 | ○ |
362+| `FindOpByType` | `graphStatus FindOpByType(type, vector<Operator>&)` | 按类型查找算子 | ○ |
363+| `GetAllOpName` | `graphStatus GetAllOpName(vector<AscendString>&)` | 获取所有算子名 | ○ |
364+| `GetAllNodes` | `vector<GNode> GetAllNodes()` | 获取所有图节点 | ○ |
365+| `AddNodeByOp` | `GNode AddNodeByOp(const Operator &op)` | 通过 Operator 添加节点 | ○ |
366+| `FindNodeByName` | `GNodePtr FindNodeByName(name)` | 按名查找节点 | ○ |
367+| `AddDataEdge` | `graphStatus AddDataEdge(GNode&, int32_t, GNode&, int32_t)` | 添加数据边 | 暂缓 |
368+| `AddControlEdge` | `graphStatus AddControlEdge(GNode&, GNode&)` | 添加控制边 | 暂缓 |
369+| `RemoveNode` | `graphStatus RemoveNode(GNode&)` | 移除节点 | 暂缓 |
370+| `GetAllSubgraphs` | `vector<GraphPtr> GetAllSubgraphs()` | 获取所有子图 | ○ |
371+| `GetSubGraph` | `GraphPtr GetSubGraph(name)` | 按名获取子图 | ○ |
372+| `AddSubGraph` | `graphStatus AddSubGraph(const Graph&)` | 添加子图 | ○ |
373+| `ConstructFromInputs` | `static GraphPtr ConstructFromInputs(...)` | 从输入构造图 | ○ |
374+| `SaveToFile` | `graphStatus SaveToFile(file_name)` | 序列化到文件 | ○ |
375+| `LoadFromFile` | `graphStatus LoadFromFile(file_name)` | 从文件加载 | ○ |
376+| `SetAttr` | `graphStatus SetAttr(name, AttrValue&)` | 设置图属性 | ○ |
377+| `GetAttr` | `graphStatus GetAttr(name, AttrValue&)` | 获取图属性 | ○ |
378+ 
379+---
380+ 
381+### 模块 6: GeTensorDesc (`inc/graph_metadef/graph/ge_tensor.h`)
382+ 
383+> 外部 API 使用 `TensorDesc` 类型,内部通过 `TensorDesc2GeTensorDesc` / `GeTensorDesc2TensorDesc` 转换。
384+> ⚠️ ops-nn 中高频使用 GetInputDesc/GetOutputDesc/UpdateInputDesc/UpdateOutputDesc(见模块 4.4)。这是能力盘点结论,不代表当前 PR 2 首轮范围;按修订决策,TensorDesc 读写延后到迭代 2,并在编码前冻结 handle 所有权和同步语义。
385+ 
386+| 方法 | 说明 | Python 化 |
387+|------|------|-----------|
388+| `GetShape() / MutableShape() / SetShape(GeShape)` | Shape 操作 | ⭐ |
389+| `GetOriginShape() / MutableOriginShape() / SetOriginShape()` | OriginShape 操作 | ⭐ |
390+| `GetFormat() / SetFormat(Format)` | 内存排布格式 | ⭐ |
391+| `GetOriginFormat() / SetOriginFormat(Format)` | 原始格式 | ⭐ |
392+| `GetDataType() / SetDataType(DataType)` | 数据类型 | ⭐ |
393+| `GetOriginDataType() / SetOriginDataType(DataType)` | 原始数据类型 | ⭐ |
394+| `GetPlacement() / SetPlacement(Placement)` | 设备放置 | ○ |
395+| `SetShapeRange(vector<pair<int64_t,int64_t>>)` | 动态 shape 范围 | ○ |
396+ 
397+---
398+ 
399+### 模块 7: ES (Eager Style) 构图接口
400+ 
401+> **推荐使用**,已有完整的 Python 能力 (`api/python/ge/ge/es/`)。
402+ 
403+#### 7.1 C++ 公开头文件
404+ 
405+| 类 | 文件 | 说明 |
406+|------|------|------|
407+| `EsGraphBuilder` | `inc/external/ge/eager_style_graph_builder/cpp/es_graph_builder.h` | 图构建器 |
408+| `EsTensorHolder` | `inc/external/ge/eager_style_graph_builder/cpp/es_tensor_holder.h` | 张量持有者 |
409+| `CompliantNodeBuilder` | `inc/external/ge/eager_style_graph_builder/cpp/compliant_node_builder.h` | 合规节点构建器 |
410+ 
411+#### 7.2 Python 已有接口 (`api/python/ge/ge/es/`)
412+ 
413+| Python 类/函数 | 说明 | 现状 |
414+|---------------|------|------|
415+| `GraphBuilder(name)` | 创建图构建器 | ✅ 已有 |
416+| `GraphBuilder.create_input(index, *, name, type_str, data_type, format, shape)` | 创建输入 | ✅ 已有 |
417+| `GraphBuilder.create_const_float(value, shape)` | 创建 float 常量 | ✅ 已有 |
418+| `GraphBuilder.create_const_int64(value, shape)` | 创建 int64 常量 | ✅ 已有 |
419+| `GraphBuilder.create_const_int32(value, shape)` | 创建 int32 常量 | ✅ 已有 |
420+| `GraphBuilder.create_scalar_float(value)` | 创建 float 标量 | ✅ 已有 |
421+| `GraphBuilder.create_scalar_int64(value)` | 创建 int64 标量 | ✅ 已有 |
422+| `GraphBuilder.set_graph_output(tensor, output_index)` | 设置图输出 | ✅ 已有 |
423+| `GraphBuilder.set_graph_attr_int64(name, value)` | 设置图属性 | ✅ 已有 |
424+| `GraphBuilder.build_and_reset()``Graph` | 构建并返回 Graph 对象 | ✅ 已有 |
425+| `TensorHolder` | 张量持有者,支持 `+`, `-`, `*`, `/` 运算符 | ✅ 已有 |
426+| `TensorHolder.set_data_type(dt)` | 设置数据类型 | ✅ 已有 |
427+| `TensorHolder.set_format(fmt)` | 设置格式 | ✅ 已有 |
428+| `TensorHolder.set_shape(dims)` | 设置 shape | ✅ 已有 |
429+ 
430+> **但是**:当前 ES GraphBuilder 支持的是**内置算子**的创建(通过 `entry_points` 插件发现)。对于 ONNX Plugin 中需要的 **任意 GE IR 算子** (`Identity`, `Threshold`, `Mul`, `Data` 等),需要确认 ES 是否支持 `call_builtin_op(type, ...)` 通用工厂方式。
431+ 
432+#### 7.3 OP 定义宏 (`inc/graph_metadef/external/graph/operator_reg.h`)
433+ 
434+| 宏 | 行号 | 说明 |
435+|------|------|------|
436+| `REG_OP(X)` | ~280 | 声明 `op::X` 类 (继承 Operator),自动生成强类型构造 |
437+| `INPUT(x, t)` | ~385 | 定义输入,生成 `set_input_x()`, `get_input_desc_x()` |
438+| `OPTIONAL_INPUT(x, t)` | ~427 | 可选输入 |
439+| `OUTPUT(x, t)` | ~470 | 定义输出 |
440+| `DYNAMIC_INPUT(x, t)` | ~494 | 动态输入 |
441+| `DYNAMIC_OUTPUT(x, t)` | ~535 | 动态输出 |
442+| `ATTR(x, Type, default)` | ~303 | 属性定义 |
443+| `REQUIRED_ATTR(x, Type)` | ~342 | 必需属性 |
444+| `GRAPH(x)` | ~560 | 子图定义 |
445+| `OP_END_FACTORY_REG(x)` | ~637 | 结束标志 |
446+ 
447+---
448+ 
449+### 模块 8: 现有 Python 绑定层 (`api/python/`)
450+ 
451+#### 8.1 目录结构总览
452+ 
453+```
454+api/python/ge/ge/
455+├── es/ ← ES 构图 (GraphBuilder + TensorHolder) — ctypes
456+├── graph/ ← 图/节点/张量/元数据操作 — ctypes
457+├── session/ ← Session 运行图 — ctypes
458+├── offline_compile/ ← 离线编译 — ctypes
459+├── passes/ ← Pass 框架 (FusionPass, DecomposePass) — pybind11 + ctypes
460+├── custom_op/ ← 自定义算子注册 — pybind11 + ctypes
461+├── runtime/ ← 运行时类型 — pybind11
462+├── allocator/ ← 外部内存分配器 — ctypes
463+├── ge_global/ ← GE 初始化 — ctypes
464+├── pyatc/ ← ATC 命令行 — ctypes
465+├── _capi/ ← ctypes C API 声明层
466+└── _internal/ ← 内部工具
467+```
468+ 
469+#### 8.2 ctypes Bridge 层 (`api/python/ge/ge_api_c_wrapper/`)
470+ 
471+| C API 文件 | 包装的 C++ 能力 | 说明 |
472+|-----------|----------------|------|
473+| `c_graph.cc` | `Graph` (create/dump/subgraph) | graph 生命周期管理和 dump |
474+| `c_gnode.cc` | `GNode` (名称/类型/属性访问) | 图节点包装 |
475+| `c_tensor.cc` | `Tensor` (创建/数据访问) | 张量数据包装 |
476+| `c_tensor_desc.cc` | `TensorDesc` / `Shape` | 张量描述符包装 |
477+| `c_anyvalue.cc` | `AttrValue` (set/get) | 通用属性值包装 |
478+| `c_session.cc` | `Session` (run_graph) | Session 生命周期 |
479+| `c_offline_compile.cc` | `build_model` / `save_model` | 离线编译 |
480+ 
481+**Python ctypes 声明层** (`api/python/ge/ge/_capi/`):
482+| 文件 | 声明内容 |
483+|------|---------|
484+| `pygraph_wrapper.py` | Graph/Node/AttrValue/TensorDesc C API |
485+| `pyes_graph_builder_wrapper.py` | ES GraphBuilder C API |
486+| `pygeapi_wrapper.py` | GeApi 初始化 |
487+| `pysession_wrapper.py` | Session API |
488+| `pyoffline_compile_wrapper.py` | 离线编译 API |
489+| `pypyatc_wrapper.py` | ATC API |
490+| `pytensor_runtime_wrapper.py` | Tensor 运行时 API |
491+| `pyge_utils_wrapper.py` | GE 工具 API |
492+| `_lib_loader.py` | .so 动态加载 |
493+ 
494+#### 8.3 pybind11 绑定
495+ 
496+| 模块 | 文件 | 说明 |
497+|------|------|------|
498+| `_ge_pass_native` | `passes/native_bindings/module.cc` | Pass 框架 pybind11 模块 |
499+| `_ge_runtime_native` | `runtime/native_bindings/module.cc` | 运行时类型 pybind11 模块 |
500+| `_ge_custom_op_native` | `custom_op/native_bindings/module.cc` | 自定义算子 pybind11 模块 |
501+| `libge_python_pass_bridge.so` | `compiler/graph/fusion/pass/python_pass_pybind_bridge.cc` | Pass Bridge |
502+ 
503+---
504+ 
505+### 模块 9: 错误码
506+ 
507+**文件**: `inc/graph_metadef/register/graph_optimizer/graph_optimize_register_error_codes.h`
508+ 
509+| 状态码 | 编码公式 | 值 (hex) | 值 (dec) | 说明 |
510+|--------|---------|---------|----------|------|
511+| `SUCCESS` | `FE_DEF_ERRORNO(0, 0, SUCCESS, 0, ...)` | `0x00000000` | `0` | 成功 |
512+| `FAILED` | `FE_DEF_ERRORNO(0xFF, 0xFF, FAILED, 0xFFFF, ...)` | `0xFFFFFFFF` | `4294967295` | 失败 |
513+| `PARAM_INVALID` | `FE_DEF_ERRORNO(3, 50, PARAM_INVALID, 1, ...)` | `0x03320001` | `50331649` | 参数无效 |
514+ 
515+> **编码规则**: `((sysid & 0xFF) << 24) | ((modid & 0xFF) << 16) | (value & 0xFFFF)`,`SYSID_FE = 3`, `FE_MODID_COMMON = 50`
516+ 
517+---
518+ 
519+### 模块 10: Message2Operator — ONNX 属性自动映射 (`parser/parser/common/convert/`)
520+ 
521+> ⚠️ **后续能力说明**:`Message2Operator` 是 `ParseParamByOpFunc` 路径的现有 C++ 属性映射工具。当迭代 2 评审该 callback 时,需确认是否复用它;首轮 `ParseParamsFn` 由 parser 侧直接生成 `OnnxNode.attrs`,不向 Python 暴露 `ctx.onnx` 或预填充 `op_src`。
522+ 
523+#### 10.1 头文件: `parser/parser/common/convert/message2operator.h`
524+ 
525+| 方法 | 说明 | Python 化影响 |
526+|------|------|-------------|
527+| `Message2Operator::ParseOperatorAttrs(message, depth, op)` | 将 protobuf Message 的字段自动映射到 GE Operator 属性 | 迭代 2 `ParseParamByOpFunc` 候选复用点,首轮不属于 Python API |
528+| `ParseField(reflection, message, field, depth, op)` | 处理单个标量字段 → `ops.SetAttr(name, value)` | 内部 |
529+| `ParseRepeatedField(reflection, message, field, op)` | 处理 repeated 字段 → `ops.SetAttr(name, vector<T>)` | 内部 |
530+ 
531+#### 10.2 protobuf 类型 → GE AttrValue 映射表
532+ 
533+| protobuf CPPTYPE | 处理方式 | 结果 AttrValue 类型 | 示例 |
534+|-----------------|---------|-------------------|------|
535+| `CPPTYPE_INT32` | `reflection->GetInt32``SetAttr(name, int32_t)` | `VT_INT` | ONNX 整型属性 |
536+| `CPPTYPE_UINT32` | `reflection->GetUInt32``SetAttr(name, uint32_t)` | `VT_INT` | |
537+| `CPPTYPE_INT64` | `reflection->GetInt64``SetAttr(name, int64_t)` | `VT_INT` | |
538+| `CPPTYPE_FLOAT` | `reflection->GetFloat``SetAttr(name, float)` | `VT_FLOAT` | `alpha=1.0` |
539+| `CPPTYPE_DOUBLE` | `reflection->GetDouble``SetAttr(name, float)` | `VT_FLOAT` | |
540+| `CPPTYPE_BOOL` | `reflection->GetBool``SetAttr(name, bool)` | `VT_BOOL` | |
541+| `CPPTYPE_STRING` | `reflection->GetString``SetAttr(name, string)` | `VT_STRING` | `name="thresholded_relu"` |
542+| `CPPTYPE_ENUM` | `enum->number()``SetAttr(name, int_value)` | `VT_INT` | |
543+| `CPPTYPE_MESSAGE` (标量) | **递归**: `ParseOperatorAttrs(&sub_msg, depth+1, op)` | 各自类型(扁平化) | `AttributeProto` 的子字段 |
544+| `CPPTYPE_MESSAGE` (repeated) | **JSON 序列化**: `Pb2Json + json.dump()``SetAttr(name, json_str)` | `VT_STRING` (JSON) | repeated 嵌套 message |
545+ 
546+#### 10.3 深度限制
547+ 
548+`ParseOperatorAttrs``depth` 参数上限为 **5**,防止无限递归。超过深度则跳过。
549+ 
550+#### 10.4 ParseOpParam 完整流程(含自动映射前置步骤)
551+ 
552+```
553+ParseOpParam(node_proto, op_dest) [onnx_parser.cc:596]
554+ │ ※ 注意: 在此之前, ParseAllNodeProto 已通过 AdapterOpType() 构造 ori_type
555+ │ 并在调用 TransNodeToOperator 前执行 node_proto->set_op_type(ori_type) [约 644]
556+ │ 如 "ai.onnx::11::ThresholdedRelu"
557+
558+ ├── GetParseParamByOperatorFunc(op_type) ← 查注册表
559+ │ │
560+ │ ├── nullptr → 默认路径:
561+ │ │ op_parser->ParseParams(node_proto, op_dest) ← 内部自动映射
562+ │ │
563+ │ └── 非 nullptr → 插件路径:
564+ │ ├── 1. 创建 op_src: Operator(name, op_type)
565+ │ ├── 2. Message2Operator::ParseOperatorAttrs(node_proto, 1, op_src) ★ 自动映射
566+ │ │ → 将 ONNX NodeProto 的所有属性扁平化写入 op_src
567+ │ ├── 3. OnnxCustomParserAdapter::ParseParams(op_src, op_dest)
568+ │ │ └── GetParseParamByOperatorFunc(op_type) → custom_fn(op_src, op_dest)
569+ │ │ ★ 此时 op_src 已有所有属性,可 GetAttr 读取
570+ │ └── 4. op_src.BreakConnect() ← 清理源算子连接
571+```
572+ 
573+> **对 Python 化的关键影响**
574+> - Python 回调签名对应 `ParseParamByOpFunc = Status(const Operator& op_src, Operator& op_dest)`
575+> - `op_src` 是 C++ Bridge 已通过 `Message2Operator` 预先填充的源算子,包含 ONNX NodeProto 所有属性
576+> - Bridge 将 Python 函数包装为 C++ 函数对象,注册到 `parse_params_by_op_func_map_`,key = `GetParserKey(om_type, ori_type)`
577+ 
578+---
579+ 
580+### 模块 11: 对象层级架构与生命周期管理
581+ 
582+> ⚠️ **需求文档明确要求**: "Python 对象和 GE C++ 对象的生命周期。使用 ctypes 或者 pybind 提供 python 能力,需要注意生命周期的管理"
583+ 
584+#### 11.1 对象层级架构图
585+ 
586+```mermaid
587+graph TD
588+ subgraph "外部 API 层 (用户可见)"
589+ G[Graph] --> |持有 shared_ptr| GI[GraphImpl]
590+ O[Operator] --> |持有 shared_ptr| OI[OperatorImpl]
591+ TD[TensorDesc] --> |持有 shared_ptr| TDI[TensorDescImpl]
592+ end
593+ 
594+ subgraph "内部实现层"
595+ GI --> |持有 shared_ptr| CG[ComputeGraph]
596+ OI --> |持有 shared_ptr| OD[OpDesc]
597+ CG --> |持有 shared_ptr| CGI[ComputeGraphImpl]
598+ CGI --> |持有| NODES[Nodes 列表]
599+ NODES --> |每个 Node 持有| OD2[OpDesc shared_ptr]
600+ end
601+ 
602+ subgraph "生命周期跟踪"
603+ OK[OperatorKeeper<br/>全局 weak_ptr 集合]
604+ OK -.-> |weak_ptr 跟踪| OI
605+ end
606+ 
607+ style G fill:#e1f5fe
608+ style O fill:#e1f5fe
609+ style TD fill:#e1f5fe
610+ style CG fill:#f3e5f5
611+ style OD fill:#f3e5f5
612+ style OK fill:#fff3e0
613+```
614+ 
615+#### 11.2 各层 shared_ptr 持有关系
616+ 
617+| 层级 | 持有者 | 被持有者 | shared_ptr 类型名 | 拷贝行为 |
618+|------|--------|---------|------------------|---------|
619+| Operator → | `operator_impl_` | `OperatorImpl` | `OperatorImplPtr` | **浅拷贝**: 两个 Operator 共享同一个 OperatorImpl/OpDesc |
620+| OperatorImpl → | `op_desc_` | `OpDesc` | `OpDescPtr` | |
621+| Graph → | `impl_` | `GraphImpl` | `shared_ptr<GraphImpl>` | **浅拷贝**: 两个 Graph 共享同一个 ComputeGraph |
622+| GraphImpl → | `compute_graph_` | `ComputeGraph` | `ComputeGraphPtr` | |
623+| ComputeGraph → | `impl_` | `ComputeGraphImpl` | `shared_ptr<ComputeGraphImpl>` | |
624+| ComputeGraphImpl → | `nodes_` | `Node` 列表 | `vector<NodePtr>` | 每个 Node 持有自己的 OpDesc |
625+ 
626+#### 11.3 OperatorKeeper — 全局生命周期跟踪
627+ 
628+**文件**: `graph_metadef/graph/normal_graph/operator_impl.h:113-150`
629+ 
630+```cpp
631+class OperatorKeeper {
632+ public:
633+ static OperatorKeeper &Instance();
634+ void Register(const std::shared_ptr<OperatorImpl> &impl); // 注册 weak_ptr
635+ void Clean(); // 清理已释放的 weak_ptr
636+ private:
637+ std::set<std::weak_ptr<OperatorImpl>, std::owner_less<>> operators_;
638+};
639+```
640+ 
641+- 所有 `OperatorImpl` 创建时自动注册 `weak_ptr`
642+- 当外部所有 `Operator` 对象销毁时,`weak_ptr` 自动过期
643+- `Clean()` 定期清理过期的 `weak_ptr`
644+ 
645+#### 11.4 Graph 析构时的清理
646+ 
647+**文件**: `graph_metadef/graph/normal_graph/graph.cc:42-47`
648+ 
649+```cpp
650+~GraphImpl() {
651+ GraphUtilsEx::BreakConnect(compute_graph_->GetAllNodesInfo()); // 断开节点间连接
652+ for (const auto &it : op_list_) {
653+ Operator op = it.second;
654+ op.BreakConnect(); // 断开 Operator 间连接
655+ }
656+}
657+```
658+ 
659+#### 11.5 Python 化的生命周期要点
660+ 
661+| 风险点 | 说明 | 对策 |
662+|--------|------|------|
663+| **浅拷贝陷阱** | Operator 拷贝是浅拷贝,修改副本会影响原始对象 | Python 侧注意区分 `op_src`(只读) 和 `op_dest`(写入) |
664+| **悬空引用** | Python 持有的 C++ 指针可能因 Graph 析构而失效 | Bridge 中持有 shared_ptr 引用计数,确保 Python 函数返回前对象存活 |
665+| **回调期间生命周期** | ParseOpToGraph 中创建的 `subgraph` 在回调返回后由 C++ 接管 | Python 侧不持有 subgraph 的所有权 |
666+| **ctypes vs pybind** | ctypes 不管理 C++ 对象生命周期,须手动 ensure | 设计 C API 时遵循:Python 创建 → 返回 handle → C++ 持有 shared_ptr |
667+| **GIL 释放** | 长时间的回调可能阻塞 Python GIL | ParseParams 通常很快,无需特殊处理 |
668+ 
669+---
670+ 
671+### 模块 12: atc/session parser 插件注册入口 (`parser/parser/common/`)
672+ 
673+#### 12.0 三类入口对照
674+ 
675+`AclParserInitialize` 不是唯一插件注册入口。Python ONNX plugin 设计必须同时覆盖以下实际路径:
676+ 
677+| 场景 | 源码入口 | 注册动作 | 关键结论 |
678+|---|---|---|---|
679+| atc | `api/atc/main_impl.cc:1284-1327` `LoadCustomOpLib` | `Finalize``Register` | atc 不依赖 `AclParserInitialize` 完成注册 |
680+| session 插件加载 | `api/session/common/plugin/tbe_plugin_manager.cc:160-178` | 只调用 `Register` | 此阶段不完成 parser creator |
681+| session/aclgrphParseONNX 解析初始化 | `parser/parser/common/acl_graph_parser_util.cc:247-253` | `Finalize``Register` | 必须在首次 parser factory 查询前完成 creator |
682+ 
683+#### 12.1 AclParserInitialize — 解析初始化流程
684+ 
685+> ⚠️ 这是 Python 插件注册接入点需要理解和修改的关键模块。
686+ 
687+#### 12.1 入口和幂等性保护
688+ 
689+**文件**: `parser/parser/onnx/onnx_parser.cc:58-72`
690+ 
691+```cpp
692+static std::once_flag flag;
693+std::call_once(flag, [&init_ret, &acl_graph_parse_util]() {
694+ std::map<string, string> options;
695+ options.insert({string(FRAMEWORK_TYPE), to_string(domi::ONNX)});
696+ init_ret = acl_graph_parse_util.AclParserInitialize(options);
697+});
698+```
699+ 
700+**文件**: `parser/parser/common/acl_graph_parser_util.cc:216-264`
701+ 
702+```cpp
703+AclGraphParserUtil::AclParserInitialize(options, is_train)
704+ ├── 幂等性检查: parser_initialized = true (只执行一次)
705+ ├── OperatorFactoryImpl::BackupAndClearRegInfoOnce()
706+ ├── OpLibRegistry::PreProcessForCustomOp()
707+ ├── LoadOpsProtoLib() ← 加载自定义 op proto 定义
708+ ├── SaveCustomCaffeProtoPath()
709+ ├── TBEPluginLoader::LoadPluginSo(options) ← ★ dlopen 所有插件 .so
710+ ├── for each OpRegistrationData in OpRegistry::registrationDatas:
711+ │ └── if (GetFrameworkType() == fmk_type):
712+ │ ├── OpRegistrationTbe::Finalize(reg_data, is_train) ← 注册至 OpParserFactory
713+ │ └── OpRegistry::Register(reg_data) ← 填充 10 个 map
714+ └── OperatorFactoryImpl::MergeBackupCreatorsOnce()
715+```
716+ 
717+#### 12.2 插件 .so 搜索路径
718+ 
719+**文件**: `parser/parser/common/tbe_plugin_loader.cc:215-246`
720+ 
721+`GetCustomOpPath()` 解析搜索路径,基于 `$ASCEND_OPP_PATH`:
722+ 
723+| 路径模式 | 说明 |
724+|----------|------|
725+| `$ASCEND_OPP_PATH/framework/custom/<fmk_type>/` | 新版结构化路径 |
726+| `$ASCEND_OPP_PATH/vendors/<vendor>/framework/<fmk_type>/` | 厂商扩展路径 |
727+| `$ASCEND_OPP_PATH/framework/<fmk_type>/` | 旧版平铺路径 |
728+ 
729+**文件**: `parser/parser/common/tbe_plugin_loader.cc:81-117`
730+ 
731+```cpp
732+void TBEPluginLoader::LoadPluginSo(options) {
733+ GetCustomOpPath(plugin_path); // 确定搜索路径
734+ GetPluginSoFileList(plugin_path, file_list, ...); // 枚举 *.so 文件
735+ for (auto elem : file_list) {
736+ void *handle = mmDlopen(elem.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_NODELETE);
737+ // ↑ dlopen 时触发 .so 中所有 static 变量构造
738+ // → REGISTER_CUSTOM_OP 宏创建的 static OpReceiver 被构造
739+ // → OpRegistry::registrationDatas.push_back(reg_data)
740+ }
741+}
742+```
743+ 
744+#### 12.3 Python 插件发现的可能接入点
745+ 
746+| 方案 | 说明 | 优势 | 劣势 |
747+|------|------|------|------|
748+| **A) 复用 `$ASCEND_OPP_PATH`** | 在 `LoadPluginSo` 中增加 `.py` 文件扫描 | 统一路径管理,用户无感知 | 需修改 C++ 加载逻辑 |
749+| **B) 独立 `ASCEND_CUSTOM_OPP_PATH`** | 新建环境变量指向 Python 插件目录 | 隔离性好,不影响现有 .so | 用户需设置新变量 |
750+| **C) Python `entry_points`** | 用 pip 包的标准插件发现 | pip 生态标准,已有 `ge.es` 先例 | 需用户安装为 pip 包 |
751+ 
752+---
753+ 
754+### 模块 13: GetParserKey — 注册表 key 格式
755+ 
756+**文件**: `graph_metadef/register/register.cpp:1045-1047`
757+ 
758+```cpp
759+static std::string GetParserKey(const std::string &om_type, const std::string &ori_type) {
760+ return om_type + "_" + ori_type;
761+}
762+```
763+ 
764+**Key 示例**:
765+| 场景 | om_type | ori_type | 结果 key |
766+|------|---------|----------|----------|
767+| ParseParams | `PartitionedCall` | `ai.onnx::11::ThresholdedRelu` | `PartitionedCall_ai.onnx::11::ThresholdedRelu` |
768+| ParseParams | `Conv` | `Conv` | `Conv_Conv` |
769+| ParseOpToGraph | `PartitionedCall` | `ai.onnx::11::ThresholdedRelu` | `PartitionedCall_ai.onnx::11::ThresholdedRelu` |
770+| ParseSubgraphPost | `If` | `onnx`(FmkType 序列化) | `If_onnx` |
771+ 
772+> **Python Bridge 影响**: Python 注册的 `origin_types` 和 `ge_op` 会被用来构造相同的 key 格式写入 C++ 的 `parse_params_by_op_func_map_` 和 `parse_op_to_graph_fn_map_`。
773+ 
774+---
775+ 
776+### 补充: ONNX AttributeProto 完整类型枚举
777+ 
778+**文件**: `graph_metadef/proto/onnx/ge_onnx.proto:101-157`
779+ 
780+```protobuf
781+enum AttributeType {
782+ UNDEFINED = 0;
783+ FLOAT = 1; INT = 2; STRING = 3;
784+ TENSOR = 4; GRAPH = 5; SPARSE_TENSOR = 11;
785+ FLOATS = 6; INTS = 7; STRINGS = 8;
786+ TENSORS = 9; GRAPHS = 10; SPARSE_TENSORS = 12;
787+}
788+ 
789+message AttributeProto {
790+ string name = 1; // 属性名
791+ string ref_attr_name = 21; // 引用属性名
792+ string doc_string = 13; // 文档
793+ AttributeType type = 20; // 标识下面使用的字段
794+ 
795+ float f = 2; // FLOAT
796+ int64 i = 3; // INT
797+ bytes s = 4; // STRING
798+ TensorProto t = 5; // TENSOR
799+ GraphProto g = 6; // GRAPH
800+ SparseTensorProto sparse_tensor = 22; // SPARSE_TENSOR
801+ 
802+ repeated float floats = 7; // FLOATS
803+ repeated int64 ints = 8; // INTS
804+ repeated bytes strings = 9; // STRINGS
805+ repeated TensorProto tensors = 10; // TENSORS
806+ repeated GraphProto graphs = 11; // GRAPHS
807+ repeated SparseTensorProto sparse_tensors = 23; // SPARSE_TENSORS
808+}
809+```
810+ 
811+**后续 `ParseParamByOpFunc` 的 Message2Operator 映射表**:
812+| ONNX AttributeType | protobuf 字段 | 经过 Message2Operator 后的 GE AttrValue | Python 返回类型 |
813+|-------------------|-------------|--------------------------------------|---------------|
814+| `FLOAT` | `f` | `AttrValue::VT_FLOAT` | `float` |
815+| `INT` | `i` | `AttrValue::VT_INT` | `int` |
816+| `STRING` | `s` | `AttrValue::VT_STRING` / `VT_BYTES` | `str` / `bytes` |
817+| `TENSOR` | `t` | `AttrValue::VT_TENSOR` | `ge.Tensor` |
818+| `GRAPH` | `g` | 递归扁平化 | 子字段各类型 |
819+| `FLOATS` | `floats` | `AttrValue::VT_LIST_FLOAT` | `list[float]` |
820+| `INTS` | `ints` | `AttrValue::VT_LIST_INT` | `list[int]` |
821+| `STRINGS` | `strings` | `AttrValue::VT_LIST_STRING` / `VT_LIST_BYTES` | `list[str]` / `list[bytes]` |
822+| `TENSORS` | `tensors` | `AttrValue::VT_LIST_TENSOR` | `list[ge.Tensor]` |
823+ 
824+---
825+ 
826+## 三、Python 化关键 Bridge 插入点总结
827+ 
828+| 阶段 | C++ 插入点 | 桥接方式 | 说明 |
829+|------|-----------|---------|------|
830+| **注册** | parser/session 初始化前置入口 | Python 插件发现 → parser-side coordinator 预检查 creator/全部 callback map → `Finalize``Register` | 必须同时完成 `OpParserFactory` creator 和 `OpRegistry` map;两者返回值都要检查 |
831+| **ParseParams 回调** | `OnnxCustomParserAdapter::ParseParams``GetParseParamFunc` | Bridge 将 Python 函数包装为 `ParseParamFunc`,NodeProto 由 parser 侧转换为扁平值 | 首轮不走 `ParseParamByOperatorFunc`,Python 不直接处理 protobuf |
832+| **ParseOpToGraph 回调** | `ParserUtils::ExpandOneToManyGraph()``GetParseOpToGraphFunc` | Bridge 将 Python 函数包装为 `ParseOpToGraphFunc` 注册到 `parse_op_to_graph_fn_map_` | 该查询不经过 `OnnxCustomParserAdapter`,属于迭代 2 独立接入点 |
833+| **子图后处理** | `PostOpProcessForSubgraph()``GetParseSubgraphPostFunc` | Bridge 注册 `ParseSubgraphFunc` / `ParseSubgraphFuncV2` | 如 If 等控制流算子需要的子图后处理 |
834+| **Operator 属性** | `ge::Operator::SetAttr/GetAttr` | ctypes/pybind 封装 | 需新增 `c_operator.cc` |
835+| **TensorDesc** | `Operator::GetInputDesc/GetOutputDesc/UpdateInputDesc/UpdateOutputDesc` | ctypes/pybind 封装 | 首轮延后;进入迭代 2 前先冻结 handle 所有权和同步语义 |
836+| **Dynamic IO** | `Operator::DynamicInputRegister/DynamicOutputRegister` (name, num) | ctypes/pybind 封装 | 仅简单形式,高级重载当前零使用 |
837+| **ES 构图** | `EsGraphBuilder` / `TensorHolder` | ✅ 已有 ctypes Python API | 复用现有 `ge.es` 模块 |
838+ 
839+---
840+ 
841+## 四、下一步:需要新增/修改的模块和接口
842+ 
843+基于以上 13 个模块的完整调研,待确认的模块清单如下。**加粗标记**为本次补齐调研后发现的新增项。
844+ 
845+### 4.1 设计原则
846+ 
847+**不要逐行翻译 C++ 类为 Python 类**。正确路径是:
848+1. **Python 侧**:业务开发者用 Python 编写回调函数,使用 Python 友好的 API
849+2. **C++ Bridge**:将 Python 回调包装为 C++ `ParseParamByOpFunc` / `ParseOpToGraphFunc` 等函数对象
850+3. **复用现有 Registry**:coordinator 先完成 creator 预检查和 `Finalize`,再通过 `OpRegistry::Register()` 注册包装后的 C++ 函数对象
851+4. **回调链路不变**:parser 调用 `GetParseParamByOperatorFunc` / `GetParseOpToGraphFunc` 时自动找到 Bridge 注册的回调
852+5. **不增加查询 fallback**:首轮 ParseParams 复用现有 adapter 查询;迭代 2 的 ParseOpToGraph 仍需直接写入其既有 map,不在 `parser_utils` 增加 Python 分支
853+ 
854+### 4.2 需要新增的 Python 模块
855+ 
856+| 序号 | 模块/文件 | 说明 | 优先级 |
857+|------|----------|------|--------|
858+| 1 | **`ge.onnx_plugin`** | ONNX Plugin Python 开发框架 | ⭐ MVP |
859+| | `register.py` | 注册装饰器,形如 `@register_onnx_plugin(ge_op, origin_types, imply_type)` | ⭐ |
860+| | `discovery.py` | 插件发现机制(复用 `ASCEND_CUSTOM_OPP_PATH` 扫描 .py 文件) | ⭐ |
861+| 2 | **`ge.onnx_plugin.bridge`** | Python → C++ 桥接内部实现 | ⭐ |
862+| | `_native.py` / `bootstrap.py` | 桥接 .so 加载管理(参照 `ge.passes` 的 pybind11 模式) | ⭐ |
863+ 
864+> **说明**: 回调 API 已按 PR 2 收敛为 `parse_node(node, target)`;`ctx` 聚合上下文不对外暴露。`expand` 和 ES 构图仍属于迭代 2,准确 ES 签名需单独核验。
865+ 
866+### 4.3 需要新增的 C API / C++ Bridge
867+ 
868+| 序号 | 文件 | 说明 | 优先级 |
869+|------|------|------|--------|
870+| 3 | **`c_operator.cc`** | 首轮提供 Operator 的 SetAttr/GetAttr/DynamicInputRegister/DynamicOutputRegister;TensorDesc 方法按迭代 2 单独评审 | ⭐ |
871+| 4 | **`c_onnx_plugin_bridge.cc`** | Bridge 核心:接收 parser 侧扁平 NodeProto 值,构造 `OpRegistrationData`,将 Python 回调包装为 C++ 函数对象;由 coordinator 完成 `Finalize` 与 `Register` | ⭐ |
872+| 5 | **`python_plugin_loader.cc`** (parser/) | Python 插件加载器:扫描路径、导入模块、收集装饰器注册的函数、调用 Bridge 完成注册 | ⭐ |
873+ 
874+### 4.4 需要修改的现有模块
875+ 
876+| 序号 | 文件 | 修改内容 | 优先级 |
877+|------|------|---------|--------|
878+| 6 | **parser/session 初始化入口** | 在首次查询 creator/registry 前增加统一 Python plugin bootstrap;具体入口需同时覆盖 atc 与 online/session | ⭐ |
879+| 7 | **`CMakeLists.txt` (多处)** | 新增编译目标、Python 开发头文件依赖 | ⭐ |
880+ 
881+### 4.5 关键设计决策(需对齐确认)
882+ 
883+| 决策点 | 选项 | 建议 |
884+|--------|------|------|
885+| **插件发现机制** | `$ASCEND_CUSTOM_OPP_PATH` 复用现有扫描能力 | **采用 canonical-path 一次 import;custom_op 与 ONNX plugin 分别提交各自 C++ registry** |
886+| **回调绑定方式** | ctypes / pybind11 | **待 bridge 构建评审确认**;不改变 C++ callback 签名 |
887+| **NodeProto 转换** | parser 侧读取私有 protobuf并扁平化 / Python直接解析 protobuf | **采用 parser-side flattener**:Python 只接收 `OnnxNode` 自然值 |
888+| **构图方式** | `ge.Graph` + `ge.Operator` / ES `GraphBuilder` + `TensorHolder` | **迭代 2 采用 ES**;不新增字符串型万能工厂 |
889+| **C++/Python 冲突** | Register 内部 skip / coordinator 预检查 | **采用 coordinator 预检查后拒绝 Python**;不依赖 `Register(false)` 返回值 |
890+| **冲突参数** | `Register(reg_data)` 使用默认 `is_custom_op=false` | Python 首轮不通过 `is_custom_op=true` 覆盖;由 coordinator 预检查冲突并拒绝 Python |
891+ 
892+### 4.6 接口 Python 化优先级汇总
893+ 
894+```
895+MVP (本次实现):
896+ @register_onnx_plugin() 注册装饰器
897+ Operator::SetAttr (int/float/bool/str/list) 属性写入
898+ Operator::GetAttr 属性读取 (ONNX 属性通过 op_src 读取)
899+ DynamicInputRegister / DynamicOutputRegister (name, num) 动态 IO (仅简单形式)
900+ GetInputDesc / GetOutputDesc / UpdateInputDesc / UpdateOutputDesc TensorDesc 读写
901+ ES GraphBuilder 封装 子图构建 (Data, Const, 任意 GE IR 算子)
902+ SUCCESS / FAILED / PARAM_INVALID 错误码
903+ 
904+第二阶段 (可后续补齐):
905+ Tensor / DataType / vector<vector<int64_t>> 属性类型
906+ DynamicInputRegister 带 datatype_symbol 版本(当前零使用,先列出)
907+ Const / Cast / Shape / Slice 等更多节点类型
908+ ParseSubgraphPostFn 子图后处理回调
909+ ParseSubgraphPostFnV2 子图后处理 V2
910+ 
911+第三阶段 (暂不实现):
912+ FusionParseParamFunc / FusionParseParamByOpFunc 融合算子解析
913+ DelInputWithCond / DelInputWithOriginalType / InputReorderVector 输入变换
914+ NamedAttrs / OpBytes 特殊属性类型
915+```
916+ 
917+---
918+ 
919+> **说明**: 标记 ⭐ = MVP 必须、○ = 可后续补齐、暂缓 = MVP 阶段不做、✅ = 已有能力可复用
@@ -0,0 +1,620 @@
1+# ONNX Plugin Python 化竞品调研
2+ 
3+> 文档定位:面向插件开发者、API 设计者和评审人员的独立调研文档。
4+>
5+> 本文为 [ONNX Plugin Python 化开发设计](onnx_plugin_python_developer_design.md) 提供完整调研证据;影响接口和错误设计的结论已同步到开发设计文档。
6+>
7+> 权威需求:[ONNX Plugin Python 化需求分析](../requirements/onnx_plugin_python_requirement_analysis.md)
8+>
9+> 当前决策:[PR 2 评审决策与开发计划修订要求](onnx_plugin_python_pr_2_revision_requirements.md)
10+ 
11+## 阅读说明
12+ 
13+本文关注竞品的用户体验。重点分析以下问题:
14+ 
15+- 插件开发者需要编写哪些代码;
16+- 插件开发者需要理解哪些概念;
17+- 框架可以隐藏哪些内部机制;
18+- 常见错误能否被快速定位;
19+- 哪些接口适合 GE ONNX Plugin Python 化。
20+ 
21+本文不比较三个项目的整体能力。三个项目的转换方向不同:
22+ 
23+| 项目 | 主要转换链路 | 本文的参考范围 |
24+| --- | --- | --- |
25+| PyTorch | PyTorch/FX -> ONNX | 导出扩展、注册、错误和测试接口 |
26+| Torch-TensorRT | PyTorch custom op -> TensorRT plugin | schema、自动生成和声明式接口 |
27+| NVIDIA TensorRT | ONNX -> TensorRT Network/Plugin | ONNX Parser、Plugin Registry 和 Python Plugin |
28+ 
29+文中使用以下结论类型:
30+ 
31+| 类型 | 含义 |
32+| --- | --- |
33+| 竞品事实 | 由本地源码、文档或示例确认的内容。 |
34+| 用户体验分析 | 从插件开发者角度进行的评价。 |
35+| GE 设计建议 | 对 GE 公开接口的建议,不等同于最终冻结结果。 |
36+| 已有决策 | 已由需求或 PR 2 评审确定的内容。 |
37+ 
38+## 1. 调研结论
39+ 
40+### 1.1 推荐的用户模型
41+ 
42+GE 应采用“插件描述对象 + 窄 callback”的用户模型。
43+ 
44+```python
45+elu = onnx_plugin(
46+ source="Elu",
47+ domain="ai.onnx",
48+ opsets=range(8, 19),
49+ target="Elu",
50+)
51+ 
52+ 
53+@elu.parse_node
54+def parse_elu(node, target):
55+ target.set_attr("alpha", node.attrs.get("alpha", 1.0))
56+```
57+ 
58+用户代码只表达转换规则:
59+ 
60+```text
61+读取 ONNX 节点信息
62+ -> 修改 GE Operator
63+ -> 返回 None
64+```
65+ 
66+以下内容由框架完成:
67+ 
68+- 插件文件发现;
69+- descriptor 校验;
70+- parser creator 注册;
71+- callback 注册;
72+- Python 异常转换;
73+- callback 期间的对象生命周期管理。
74+ 
75+### 1.2 主要设计结论
76+ 
77+1. 插件身份只声明一次。source、domain、opset 和 target 不在多个 callback 中重复声明。
78+2. `OnnxNode` 提供只读自然值。不暴露 protobuf 指针。
79+3. callback 直接修改受控的 `ge.graph.Operator`。不增加 `OperatorView``OperatorUpdate`
80+4. 首轮只公开 `parse_node`。该接口覆盖属性解析、固定端口和动态 IO 注册。
81+5. `expand`、TensorDesc 和低频 callback 后续开放。开放前需要验证生命周期和真实用例。
82+6. 自动发现必须配套诊断信息。用户不应通过查询内部 registry 排查插件是否生效。
83+7. 错误应包含模块、origin type、节点名、属性名和 Python callback 信息。
84+8. 首轮保持一条推荐 Python API。避免同时引入多套同级注册接口。
85+ 
86+### 1.3 竞品的主要启示
87+ 
88+| 竞品 | 可借鉴内容 | 不建议直接复用的内容 |
89+| --- | --- | --- |
90+| PyTorch | 显式 translation callable、阶段化错误、导出结果验证、弃用治理 | `symbolic``GraphContext` 和 exporter 参数。它们服务于 PyTorch -> ONNX |
91+| Torch-TensorRT | 单一 schema、自动生成重复工件、注册时校验、声明式 shape | `torch.library`、FakeTensor、kernel、JIT/AOT 和 autotune 生命周期 |
92+| NVIDIA TensorRT | 较小的 Parser 入口、Parser 与 Registry 解耦、节点级错误上下文 | V2/V3 多代生命周期、ONNX 私有属性约定和低层 Plugin API |
93+ 
94+## 2. 用户任务
95+ 
96+### 2.1 目标用户
97+ 
98+目标用户已经了解以下内容:
99+ 
100+- ONNX source op、domain 和 opset;
101+- GE target Operator type;
102+- ONNX 属性到 GE 属性的映射关系;
103+- 一对一解析和一对多展开的区别。
104+ 
105+以下内容不属于普通插件开发者的必要知识:
106+ 
107+- `OpRegistrationData``OpParserFactory``OpRegistry`
108+- `google::protobuf::Message *``ge::Operator *`
109+- creator、callback map 和注册顺序;
110+- Python C API、GIL、bridge ABI 和 C++ `Status`
111+ 
112+### 2.2 用户任务与接口需求
113+ 
114+| 用户任务 | 需要完成的操作 | 首轮 GE 接口 |
115+| --- | --- | --- |
116+| 声明 ONNX 映射 | 指定 source、domain、opset 和 target | `onnx_plugin(...)` |
117+| 读取节点信息 | 读取 name、op type、inputs、outputs 和 attrs | `OnnxNode` |
118+| 转换属性 | 读取 source 属性并写入 target 属性 | `Operator.get_attr/set_attr` |
119+| 注册固定端口 | 按 target 需要注册 required/optional 输入和普通输出 | `Operator.register_input/register_optional_input/register_output` |
120+| 注册动态端口 | 按当前节点的输入输出数量注册端口 | `Operator.register_dynamic_input/output` |
121+| 处理 callback 错误 | 抛出带有业务信息的 Python 异常 | bridge 错误转换 |
122+| 加载插件 | 将插件文件放入既定 OPP 路径 | plugin loader |
123+| 验证转换结果 | 从真实插件和 ONNX 模型执行解析 | parser ST |
124+ 
125+### 2.3 用户体验评价标准
126+ 
127+后续竞品对比使用以下标准:
128+ 
129+| 标准 | 关注点 |
130+| --- | --- |
131+| 首次成功成本 | 第一个可运行插件所需的代码和概念数量 |
132+| 任务直观性 | 用户代码是否接近转换逻辑 |
133+| 声明一致性 | schema、版本和 callback 是否重复声明 |
134+| 框架隐藏程度 | 用户是否需要操作 registry 和生命周期 |
135+| 动态能力 | 动态端口、多输出和动态 shape 的表达方式 |
136+| 错误定位能力 | 错误是否包含节点、属性和代码位置 |
137+| 可测试性 | 是否可以分层验证 callback 和最终转换结果 |
138+| 版本演进能力 | 新 callback、opset 和弃用接口的扩展方式 |
139+ 
140+## 3. 竞品用户接口
141+ 
142+### 3.1 PyTorch
143+ 
144+#### 竞品事实
145+ 
146+PyTorch 当前推荐使用 `torch.onnx.export(..., dynamo=True)`
147+用户可以通过 `custom_translation_table` 为 Torch op 指定 ONNX translation callable。
148+用户也可以通过 `torch.onnx.ops.symbolic` 创建 ONNX 节点。
149+ 
150+```python
151+from onnxscript import opset18 as op
152+ 
153+ 
154+def custom_add(self, other):
155+ return op.Sub(self, other)
156+ 
157+ 
158+program = torch.onnx.export(
159+ model,
160+ args,
161+ custom_translation_table={torch.ops.aten.add.Tensor: custom_add},
162+)
163+```
164+ 
165+PyTorch 还提供以下能力:
166+ 
167+- `dynamic_shapes` 表达动态输入维度;
168+- `symbolic_multi_out` 表达多个输出;
169+- `report=True` 生成导出报告;
170+- `ONNXProgram` 支持保存、执行和验证;
171+- `assert_onnx_program``verify_onnx_program` 支持结果校验。
172+ 
173+legacy `register_custom_op_symbolic` 已进入弃用路径。
174+ 
175+#### 用户体验分析
176+ 
177+PyTorch 的扩展入口较清晰。用户可以在一次 export 中注入转换逻辑。
178+错误信息区分捕获、分解和翻译阶段。
179+导出结果可以继续用于执行和验证。
180+ 
181+缺点是新旧导出路径并存。legacy symbolic 与 dynamo translation 的函数签名不同。
182+内部 `ONNXRegistry` 也不是稳定的主要用户入口。
183+ 
184+#### GE 设计启示
185+ 
186+- 采用窄 callback,而不是暴露内部 registry。
187+- 为解析、注册和 callback 错误提供阶段信息。
188+- 为 Python callback 提供独立测试入口。
189+- 建立明确的版本和弃用规则。
190+ 
191+PyTorch 的 exporter 参数、`GraphContext``symbolic` 不直接适用于 GE。
192+ 
193+### 3.2 Torch-TensorRT
194+ 
195+#### 竞品事实
196+ 
197+Torch-TensorRT 的 QDP 路径依赖 `torch.library` 和 TensorRT `tensorrt.plugin`
198+基本流程如下:
199+ 
200+```text
201+torch.library.custom_op
202+ -> torch.library.register_fake
203+ -> tensorrt.plugin register/impl/aot_impl
204+ -> 自动生成 converter
205+ -> torch_tensorrt.dynamo.compile
206+```
207+ 
208+Torch-TensorRT 提供 `custom_op`,用于自动生成 TensorRT plugin 和 converter:
209+ 
210+```python
211+torch_tensorrt.dynamo.conversion.plugins.custom_op(
212+ "mylib::scaled_add",
213+ supports_dynamic_shapes=True,
214+)
215+```
216+ 
217+高层 `cuda_kernel_op` 还可以从 `KernelSpec` 自动生成 schema、fake/meta、AOT 实现和 converter。
218+ 
219+#### 用户体验分析
220+ 
221+该方案将重复工件集中到一份 schema。
222+用户不需要分别编写全部 converter 样板。
223+注册阶段会校验输入、输出和 shape 关系。
224+自定义算子可以在模型中按照普通 Torch op 使用。
225+ 
226+该方案的使用前提较多:
227+ 
228+- 用户需要了解 `torch.library`
229+- 用户需要提供 fake/meta 逻辑;
230+- 用户需要理解 Torch-TensorRT 和 TensorRT 两套注册关系;
231+- QDP、JIT、AOT 和 kernel 实现不属于同一抽象层。
232+ 
233+#### GE 设计启示
234+ 
235+- 插件描述应成为注册信息的唯一来源。
236+- 框架应自动生成标准注册链路中的重复部分。
237+- descriptor 校验应在写入 C++ registry 前完成。
238+- 动态 shape 和 kernel plugin 不应提前加入 GE parser 首轮接口。
239+ 
240+`torch.library`、FakeTensor 和 QDP kernel 生命周期不适用于 GE ONNX parser。
241+ 
242+### 3.3 NVIDIA TensorRT
243+ 
244+#### 竞品事实
245+ 
246+TensorRT 的普通 ONNX 解析入口较小:
247+ 
248+```python
249+parser = trt.OnnxParser(network, logger)
250+if not parser.parse(model_bytes):
251+ for index in range(parser.num_errors):
252+ print(parser.get_error(index))
253+```
254+ 
255+Parser 还提供以下查询和诊断接口:
256+ 
257+- `supports_operator`
258+- `supports_model_v2`
259+- `is_subgraph_supported`
260+- `get_subgraph_nodes`
261+- `num_errors``get_error`
262+- `get_used_vc_plugin_libraries`
263+ 
264+TensorRT QDP 使用装饰器描述 Plugin:
265+ 
266+```python
267+import tensorrt.plugin as trtp
268+ 
269+ 
270+@trtp.register("sample::elemwise_add_plugin")
271+def describe(inp: trtp.TensorDesc, block_size: int) -> trtp.TensorDesc:
272+ return inp.like()
273+ 
274+ 
275+@trtp.impl("sample::elemwise_add_plugin")
276+def implement(inp: trtp.Tensor, block_size: int, outputs, stream):
277+ ...
278+```
279+ 
280+`register`负责输出 shape、类型和属性声明。
281+`impl`负责计算。
282+`aot_impl``autotune`属于可选高级能力。
283+ 
284+ONNX custom node 与 TensorRT Plugin 的连接依赖以下约定:
285+ 
286+- ONNX `op` 等于 plugin name;
287+- 节点包含 `plugin_namespace` 属性;
288+- 使用 AOT 时可以增加 `aot=True`
289+ 
290+#### 用户体验分析
291+ 
292+TensorRT 将 Parser、Network 和 Plugin 实现分层。
293+普通用户不需要了解 Plugin 生命周期。
294+QDP 进一步隐藏 creator 和 registry。
295+`TensorDesc` 和 ShapeExpr 支持动态 shape 与多输出。
296+ 
297+Parser 错误包含节点索引、节点名、算子名、错误码和源码位置。
298+该错误模型对 parser plugin 具有直接参考价值。
299+ 
300+TensorRT 也存在明显复杂度:
301+ 
302+- V2、V3 和 QDP 多代接口并存;
303+- 低层 PluginV3 需要多个 capability 接口;
304+- ONNX custom node 依赖私有属性约定;
305+- Python Plugin 还涉及 GIL 和运行时生命周期。
306+ 
307+#### GE 设计启示
308+ 
309+- Parser 应与 Python/C++ plugin 的具体实现解耦。
310+- 错误信息应包含节点级上下文。
311+- 高层接口应隐藏 creator、registry 和生命周期。
312+- GE 应使用 ONNX domain/opset 表达插件身份,不增加 `plugin_namespace` 等模型私有属性。
313+- GE 首轮不引入 V2/V3、kernel、JIT、AOT 和 autotune 生命周期。
314+ 
315+## 4. 典型场景对比
316+ 
317+### 4.1 插件声明与注册
318+ 
319+| 项目 | 用户提供 | 框架完成 | 主要限制 |
320+| --- | --- | --- | --- |
321+| PyTorch | Torch op 与 translation callable | 创建本次 export 的 registry | 方向是 PyTorch -> ONNX |
322+| Torch-TensorRT | Torch custom op、fake/meta 和 plugin 声明 | 生成 converter 和参数适配 | 依赖 PyTorch schema |
323+| TensorRT QDP | `register``impl` 等装饰器 | creator、registry 和 Plugin 桥接 | 多个装饰器重复 plugin id |
324+| GE 建议 | `onnx_plugin(...)``parse_node` | loader、coordinator、creator 和 callback 注册 | 首轮只覆盖 ParseParamsFn |
325+ 
326+GE 建议不要求用户显式调用 `register_all()`
327+插件应沿用既定 OPP 路径完成发现。
328+ 
329+### 4.2 属性读取与目标修改
330+ 
331+GE 首轮使用 `OnnxNode.attrs``Operator.set_attr`
332+ 
333+```python
334+@elu.parse_node
335+def parse_elu(node: OnnxNode, target: Operator) -> None:
336+ alpha = node.attrs.get("alpha", 1.0)
337+ target.set_attr("alpha", alpha)
338+```
339+ 
340+不建议把 ONNX 属性全部展开为 callback 参数。
341+属性集合可能随 opset 变化。
342+`OnnxNode` 还需要统一承载节点名、输入和输出。
343+ 
344+### 4.3 固定端口、动态端口与多输出
345+ 
346+PyTorch `symbolic_multi_out` 要求用户提供每个输出的 dtype 和 shape。
347+TensorRT QDP 使用 tuple 返回多个 `TensorDesc`
348+Torch-TensorRT 使用 schema 和 fake/meta 推导输出。
349+ 
350+GE 首轮直接提供固定端口和默认动态端口注册,不增加端口描述对象:
351+ 
352+```python
353+@sum_plugin.parse_node
354+def parse_sum(node: OnnxNode, target: Operator) -> None:
355+ count = len(node.inputs)
356+ if count == 0:
357+ raise ValueError("Sum requires at least one input")
358+ target.register_dynamic_input("x", count)
359+ target.set_attr("N", count)
360+```
361+ 
362+动态端口注册不等同于动态 shape 推导。
363+首轮不新增 ShapeExpr DSL。
364+ 
365+### 4.4 一对多图转换
366+ 
367+一对多图转换属于 GE 迭代 2。
368+后续应复用 ES 构图能力:
369+ 
370+```python
371+@thresholded_relu.expand
372+def expand_thresholded_relu(source):
373+ builder = GraphBuilder(source.name)
374+ data = builder.create_input(0, name=f"{source.name}_data")
375+ identity = Identity(data)
376+ selected = Threshold(identity, threshold=source.get_attr("alpha", 1.0))
377+ output = Mul(identity, selected)
378+ return builder.build_and_reset(outputs=[output])
379+```
380+ 
381+该示例是后续接口草案。
382+它不构成首轮 API 承诺。
383+ 
384+## 5. GE 公开接口建议
385+ 
386+### 5.1 首轮公开接口
387+ 
388+| 接口 | 用户任务 | 公开内容 |
389+| --- | --- | --- |
390+| `ge.onnx_plugin.onnx_plugin` | 声明 ONNX 到 GE 的映射 | `source/domain/opsets/target/imply_type` |
391+| `ge.onnx_plugin.ImplyType` | 选择 imply type | `TVM``AI_CPU``CUSTOM``GELOCAL` |
392+| `ge.onnx_plugin.OnnxPlugin` | 绑定 parser callback | `parse_node` |
393+| `ge.onnx_plugin.OnnxNode` | 读取 ONNX 节点 | `name``op_type``inputs``outputs``attrs` |
394+| `ge.graph.Operator` | 修改目标 Operator | 属性读写、固定端口和默认动态端口注册 |
395+ 
396+接口约束如下:
397+ 
398+- `OnnxNode` 为只读对象;
399+- `inputs``outputs` 使用只读 tuple;
400+- `attrs` 使用 Mapping;
401+- `parse_node` 成功返回 `None`
402+- `Operator` 只在 callback 期间有效;
403+- callback 结束后继续访问 `Operator` 应抛出 `RuntimeError`
404+- registry、coordinator、bridge 和 C API 不属于公开接口。
405+ 
406+### 5.2 首轮不公开接口
407+ 
408+以下接口不进入首轮 `__all__`
409+ 
410+- `registry``coordinator``bootstrap`
411+- `PluginContext` 等万能上下文对象;
412+- `OperatorView``OperatorUpdate``DynamicPort``ExpandResult`
413+- TensorDesc 访问和修改;
414+- `expand`
415+- `ParseParamsByOperatorFn`
416+- `ParseOpToGraphFn`
417+- `ParseSubgraphPostFn`
418+- 自定义公开异常类体系。
419+ 
420+### 5.3 接口扩展原则
421+ 
422+后续 callback 继续绑定到同一个 descriptor:
423+ 
424+```python
425+@plugin.parse_node
426+def parse_node(node, target): ...
427+ 
428+ 
429+@plugin.expand
430+def expand(source): ...
431+```
432+ 
433+不建议增加万能 `convert(context)` 接口。
434+不同 callback 的读写对象和生命周期不同。
435+窄入口有利于类型提示和接口演进。
436+ 
437+## 6. 首轮范围与后续迭代
438+ 
439+### 6.1 首轮范围
440+ 
441+首轮覆盖:
442+ 
443+- Python 插件发现;
444+- descriptor 校验和注册;
445+- `parse_node`
446+- Node 基础字段和常用属性;
447+- Operator 属性读写;
448+- 固定输入输出注册;
449+- 默认动态输入输出注册;
450+- callback 异常转换;
451+- C++/Python 注册冲突诊断。
452+ 
453+Elu 用于验证属性解析。
454+Sum 用于验证动态输入。
455+ 
456+### 6.2 后续迭代
457+ 
458+| 能力 | 代表场景 | 进入条件 |
459+| --- | --- | --- |
460+| `expand` | ThresholdedRelu | ES 签名、Graph 输入输出和生命周期完成评审 |
461+| TensorDesc | format、dtype 和 shape 修改 | handle 和同步语义冻结 |
462+| `ParseParamsByOperatorFn` | source/target Operator 参数解析 | 具备真实迁移用例 |
463+| `ParseSubgraphPostFn` | 子图后处理 | 完成调用链和生命周期验证 |
464+| Tensor、Graph 和稀疏属性 | 复杂 ONNX 属性 | 完成类型映射和所有权设计 |
465+| 高级动态端口 | ByIndex、append 和 datatype symbol | 具备真实插件需求 |
466+ 
467+## 7. 用户体验要求
468+ 
469+### 7.1 加载与注册
470+ 
471+插件加载失败时,错误至少包含:
472+ 
473+- 扫描路径;
474+- 插件模块路径;
475+- import traceback;
476+- source、domain、opset 和 target。
477+ 
478+注册冲突时,错误至少包含:
479+ 
480+- origin key;
481+- target;
482+- 冲突的 callback map;
483+- C++ 或 Python 来源;
484+- 冲突模块路径。
485+ 
486+用户不需要查询内部 registry。
487+ 
488+### 7.2 节点解析与 callback
489+ 
490+节点解析失败时,错误至少包含:
491+ 
492+- 节点名称;
493+- origin type;
494+- 节点算子名称;
495+- 属性名称和类型;
496+- callback 名称;
497+- Python traceback 摘要。
498+ 
499+callback 抛出异常后,当前 Operator 不加入 Graph。
500+ 
501+### 7.3 测试要求
502+ 
503+首轮至少提供以下测试层次:
504+ 
505+| 测试层次 | 主要内容 |
506+| --- | --- |
507+| Python UT | descriptor、参数校验和异常类型 |
508+| Bridge UT | Node 转换、Operator setter、生命周期和异常转换 |
509+| Parser ST | 真实插件文件、ONNX 模型和最终 GE Graph |
510+| 接口 Sample | Elu 属性解析和 Sum 动态输入 |
511+ 
512+接口 Sample 不应依赖内部 registry 或 bridge helper。
513+ 
514+## 8. 与开发设计文档的同步关系
515+ 
516+### 8.1 同步原则
517+ 
518+开发设计文档只同步影响 GE 设计的内容:
519+ 
520+1. 提取用户体验原则,不复制完整竞品介绍。
521+2. 保留少量对比代码,不复制全部示例。
522+3. 将竞品事实改写为 GE 设计依据。
523+4. 将尚未评审的建议标记为待定,不写成冻结接口。
524+5. 详细证据继续保留在本文附录。
525+ 
526+### 8.2 同步位置
527+ 
528+| 本文内容 | 开发设计文档位置 |
529+| --- | --- |
530+| 第 1 节调研结论 | 阅读说明、目标公开接口 |
531+| 第 2 节用户任务 | 用户使用方式 |
532+| 第 4 节典型场景 | 插件注册、OnnxNode、Operator、输入输出注册 |
533+| 第 5 节接口建议 | 完整公开接口清单和接口边界 |
534+| 第 6 节分期 | 首轮支持矩阵和开发计划 |
535+| 第 7 节错误与测试 | 错误处理、测试策略和验收标准 |
536+| 附录证据 | 仅提取必要索引,其余保留本文 |
537+ 
538+## 附录 A:调研基线
539+ 
540+| 项目 | 本地路径 | 基线 |
541+| --- | --- | --- |
542+| PyTorch | `/mnt/workspace/gitCode/gentle-knight/pytorch` | commit `05dab25851a8b0b2925aed717dc010ddc643280c` |
543+| Torch-TensorRT | `/mnt/workspace/gitCode/gentle-knight/TensorRT` | commit `8ab7654642750fab50101e04f0407604dca8014e` |
544+| NVIDIA TensorRT | `/mnt/workspace/gitCode/gentle-knight/TensorRT-core` | commit `a892d22267d9cd2dedc1a0893e6892ac901f6d3d`,VERSION `11.1.0.106` |
545+ 
546+TensorRT ONNX Parser 的 C++ 实现在未检出的子模块中。
547+本文使用公开头文件、Python 绑定和官方示例作为证据。
548+ 
549+本文证据范围已扩展到 TensorRT 核心仓,相关结论已同步到开发设计和 PR 2 决策文档。
550+ 
551+## 附录 B:源码证据索引
552+ 
553+### PyTorch
554+ 
555+| 主题 | 证据位置 |
556+| --- | --- |
557+| `torch.onnx.export` | `pytorch/torch/onnx/__init__.py:65-102` |
558+| `custom_translation_table` 注入 | `pytorch/torch/onnx/_internal/exporter/_compat.py:149-159` |
559+| `ONNXRegistry` | `pytorch/torch/onnx/_internal/exporter/_registration.py:137-303` |
560+| `symbolic``symbolic_multi_out` | `pytorch/torch/onnx/ops/__init__.py:73-279` |
561+| symbolic 文档 | `pytorch/docs/source/onnx_ops.md:7-16` |
562+| legacy symbolic | `pytorch/torch/onnx/_internal/torchscript_exporter/utils.py:1842-1887` |
563+| legacy 弃用说明 | `pytorch/docs/source/onnx.md:108-117` |
564+| `ONNXProgram` | `pytorch/torch/onnx/_internal/exporter/_onnx_program.py:204-489` |
565+| 测试和验证 | `pytorch/torch/onnx/_internal/exporter/_testing.py:18-102`; `_verification.py:148-212` |
566+| 阶段化错误 | `pytorch/torch/onnx/_internal/exporter/_core.py:79-100` |
567+ 
568+### Torch-TensorRT
569+ 
570+| 主题 | 证据位置 |
571+| --- | --- |
572+| plugin 路径总览 | `TensorRT/docsrc/tutorials/extensibility/plugins/plugins.rst:6-47` |
573+| converter 生成 | `TensorRT/docsrc/tutorials/extensibility/plugins/plugins.rst:60-114` |
574+| converter 调试 | `TensorRT/docsrc/tutorials/extensibility/plugins/plugins.rst:127-148` |
575+| `cuda_kernel_op``ptx_op` | `TensorRT/py/torch_tensorrt/kernels/_ops.py:29-183` |
576+| `SameAs``ReduceDims` | `TensorRT/py/torch_tensorrt/kernels/_dsl.py:18-45` |
577+| 注册校验 | `TensorRT/py/torch_tensorrt/kernels/_validation.py:22-142` |
578+| 自动 descriptor | `TensorRT/py/torch_tensorrt/dynamo/conversion/plugins/_generate_plugin.py:196-270` |
579+| 自动 converter | `TensorRT/py/torch_tensorrt/dynamo/conversion/plugins/_generate_plugin_converter.py:74-180` |
580+| `custom_op` | `TensorRT/py/torch_tensorrt/dynamo/conversion/plugins/_custom_op.py:13-51` |
581+| eager/TRT 对拍 | `TensorRT/tests/py/dynamo/conversion/harness.py:361-410` |
582+ 
583+`register``impl``aot_impl``autotune` 来自核心 TensorRT 的 `tensorrt.plugin`
584+Torch-TensorRT 提供生成和 converter 接入层。
585+ 
586+### NVIDIA TensorRT
587+ 
588+| 主题 | 证据位置 |
589+| --- | --- |
590+| ONNX Parser 接口 | `TensorRT-core/include/NvOnnxParser.h:189-440` |
591+| Parser Python 绑定 | `TensorRT-core/python/src/parsers/pyOnnx.cpp:174-208` |
592+| ParserError 绑定 | `TensorRT-core/python/src/parsers/pyOnnx.cpp:221-253` |
593+| QDP 用户说明 | `TensorRT-core/samples/python/quickly_deployable_plugins/README.md:22-73` |
594+| 数据依赖 shape | `TensorRT-core/samples/python/quickly_deployable_plugins/README.md:121-146` |
595+| ONNX custom node 契约 | `TensorRT-core/samples/python/quickly_deployable_plugins/README.md:162-183` |
596+| AOT ONNX 属性 | `TensorRT-core/samples/python/quickly_deployable_plugins/README.md:239-265` |
597+| QDP 装饰器 | `TensorRT-core/python/packaging/bindings_wheel/tensorrt/plugin/_lib.py:371-695` |
598+| TensorDesc 和 ShapeExpr | `TensorRT-core/python/packaging/bindings_wheel/tensorrt/plugin/_tensor.py:226-363,639-937` |
599+| shape 工厂 | `TensorRT-core/python/packaging/bindings_wheel/tensorrt/plugin/_top_level.py:99-129` |
600+| QDP 类型校验 | `TensorRT-core/python/packaging/bindings_wheel/tensorrt/plugin/_validate.py:30-168` |
601+| Plugin Registry | `TensorRT-core/python/src/infer/pyPlugin.cpp:3836-3866` |
602+| network plugin | `TensorRT-core/python/src/infer/pyGraph.cpp:100-137,1113-1118` |
603+ 
604+## 附录 C:采纳矩阵
605+ 
606+| 竞品能力 | GE 处理 | 结论 |
607+| --- | --- | --- |
608+| PyTorch 显式 translation callable | 采纳 callback 直观性 | 不采用每次 atc 传入 table |
609+| PyTorch 局部 registry | 不作为首轮用户接口 | GE 需要沿用 OPP 自动发现 |
610+| PyTorch 返回可测试 program | 采纳可测试性原则 | 不新增首轮 program 对象 |
611+| PyTorch report/verify | 采纳阶段化错误思想 | 数值验证不属于首轮 parser 范围 |
612+| Torch-TensorRT 单一 schema | 采纳声明一次原则 | descriptor 统一生成注册信息 |
613+| Torch-TensorRT FakeTensor | 不采纳实现 | GE 复用自身 shape 体系 |
614+| Torch-TensorRT 自动 converter | 部分采纳 | 标准 bridge 自动完成,用户负责转换逻辑 |
615+| TensorRT Parser/Registry 解耦 | 采纳 | Python/C++ plugin 进入统一 parser 主链 |
616+| TensorRT QDP 窄装饰器 | 采纳接口风格 | 首轮只提供 `parse_node` |
617+| TensorRT TensorDesc/ShapeExpr | 后续参考 | 首轮不新增 Shape DSL |
618+| TensorRT ParserError 上下文 | 采纳 | 增加节点级诊断信息 |
619+| TensorRT V2/V3/QDP 多代接口 | 不采纳 | GE 保持单一推荐 API |
620+| TensorRT `plugin_namespace``aot` 属性 | 不采纳 | GE 使用 domain/opset 和 descriptor |
@@ -0,0 +1,1521 @@
1+# ONNX Plugin Python 化
2+ 
3+> 文档定位:面向插件开发者和实现开发者的主阅读版本。
4+>
5+> 完整评审底稿:[ONNX Plugin Python 化技术方案](onnx_plugin_python_modification_plan.md)
6+>
7+> 权威需求:[ONNX Plugin Python 化需求分析](../requirements/onnx_plugin_python_requirement_analysis.md)
8+>
9+> PR 2 决策:[PR 2 评审决策与开发计划修订要求](onnx_plugin_python_pr_2_revision_requirements.md)
10+>
11+> 竞品证据:[ONNX Plugin Python 化竞品调研](onnx_plugin_python_competitor_research.md)
12+ 
13+## 阅读说明
14+ 
15+### 文档目的
16+ 
17+本文说明如何让插件开发者使用 Python 编写 ONNX 到 GE Operator 的转换逻辑,并给出首轮公开接口、用户写法、C++ 接入链路、模块修改、测试和验收要求。
18+ 
19+本文优先回答以下问题:
20+ 
21+- Python ONNX Plugin 怎么写;
22+- 首轮可以读取哪些 ONNX 内容、修改哪些 GE Operator 内容;
23+- Python callback 如何进入现有 GE parser;
24+- atc、session 和 custom_op 的初始化关系是什么;
25+- 哪些能力延后到后续迭代;
26+- 编码前还需要冻结哪些事项。
27+ 
28+### 文档范围
29+ 
30+首轮包括:
31+ 
32+- `onnx_plugin(...)` 插件描述;
33+- `ParseParamsFn` 对应的 `parse_node`
34+- `OnnxNode` 基础字段和常用属性;
35+- `ge.graph.Operator` 常用属性操作;
36+- required/optional 输入、普通输出和默认动态输入输出注册;
37+- Python 插件发现、registration coordinator 和独立 bridge;
38+- Python UT、bridge UT 和 parser ST。
39+ 
40+首轮不包括:
41+ 
42+- `ParseParamsByOperatorFn``ParseOpToGraphFn``ParseSubgraphPostFn`
43+- TensorDesc 修改、Tensor/Graph/稀疏属性和属性引用;
44+- 输入删除、输入重排和动态端口高级重载;
45+- ThresholdedRelu 的完整 ES 子图展开;
46+- Operator 原型、Kernel、compiler、runtime、AscendIR 或 OM 格式修改。
47+ 
48+### 名词说明
49+ 
50+| 名称 | 含义 |
51+|---|---|
52+| ONNX plugin | 将 ONNX 原始节点转换为 GE 目标 Operator 的 parser 插件。 |
53+| source | ONNX 原始 op type,例如 `Elu`。 |
54+| target | GE 中已经注册的目标 Operator type,例如 `AccumulateNV2`。 |
55+| origin type | 由 domain、opset 和 source 构成的完整类型,例如 `ai.onnx::11::Elu`。 |
56+| `OnnxNode` | parser 从 `NodeProto` 提取出的 Python 只读值对象。 |
57+| `ge.graph.Operator` | parser callback 期间对 `ge::Operator` 的受控 Python 包装。 |
58+| `parse_node` | 首轮 Python callback,对应 C++ `ParseParamsFn`。 |
59+| registration coordinator | 负责 descriptor 校验、creator/callback map 冲突检查和注册提交的内部组件。 |
60+| parser creator | `OpParserFactory` 中按 target type 创建 parser adapter 的函数。 |
61+| callback map | `OpRegistry` 中保存 ParseParams、ParseOpToGraph 等 callback 的映射。 |
62+| ES | GE Eager Style 构图能力,后续用于一对多子图展开。 |
63+| custom_op | 面向 `CustomOpRegistry/OpLibRegistry` 的 Python 自定义算子能力,不等同于 ONNX parser plugin。 |
64+ 
65+## 1. 竞品用户接口分析
66+ 
67+### 1.1 PyTorch ONNX Exporter
68+ 
69+#### 1.1.1 适用链路
70+ 
71+PyTorch 的主要链路是 `PyTorch/FX -> ONNX`
72+它不是 ONNX Parser。
73+本文借鉴其 Python 扩展接口,不直接复用其转换对象。
74+ 
75+#### 1.1.2 覆盖已有算子
76+ 
77+PyTorch 通过 `custom_translation_table` 为已有 Torch op 指定 ONNX translation callable:
78+ 
79+```python
80+from onnxscript import opset18 as op
81+ 
82+ 
83+def custom_add(self, other):
84+ return op.Sub(self, other)
85+ 
86+ 
87+onnx_program = torch.onnx.export(
88+ model,
89+ args,
90+ custom_translation_table={
91+ torch.ops.aten.add.Tensor: custom_add,
92+ },
93+ dynamo=True,
94+)
95+```
96+ 
97+用户需要提供:
98+ 
99+- Torch op 的 callable target;
100+- ONNXScript translation callable;
101+- translation callable 的输入签名。
102+ 
103+框架负责:
104+ 
105+- 创建本次导出的 registry;
106+- 注入 custom translation;
107+- 按目标 opset 选择实现;
108+- 构造 ONNX 图。
109+ 
110+该接口适合“覆盖已有算子的转换规则”。
111+它不适合直接表达 GE Operator 的修改。
112+ 
113+#### 1.1.3 创建自定义 ONNX 节点
114+ 
115+PyTorch 提供 `torch.onnx.ops.symbolic`
116+ 
117+```python
118+return torch.onnx.ops.symbolic(
119+ "custom_domain::CustomOp",
120+ (x, None),
121+ attrs={"alpha": 1.0},
122+ dtype=x.dtype,
123+ shape=[1, 2, 3],
124+ version=1,
125+)
126+```
127+ 
128+多输出使用 `symbolic_multi_out`
129+ 
130+```python
131+return torch.onnx.ops.symbolic_multi_out(
132+ "custom_domain::CustomOp",
133+ (x,),
134+ attrs={"axis": 1},
135+ dtypes=(torch.float32, torch.int32),
136+ shapes=([1, 2], [1]),
137+ version=1,
138+)
139+```
140+ 
141+该接口要求用户显式提供输出 dtype 和 shape。
142+这是因为 symbolic operator 不执行实际计算。
143+ 
144+#### 1.1.4 动态 shape、错误和测试
145+ 
146+动态输入维度通过 `dynamic_shapes` 传入:
147+ 
148+```python
149+dynamic_shapes = {
150+ "input": {0: torch.export.Dim.DYNAMIC},
151+}
152+ 
153+onnx_program = torch.onnx.export(
154+ model,
155+ args,
156+ dynamic_shapes=dynamic_shapes,
157+ dynamo=True,
158+)
159+```
160+ 
161+导出错误按阶段区分:
162+ 
163+1. `torch.export` 捕获失败;
164+2. FX 分解失败;
165+3. ONNX 转换失败。
166+ 
167+`report=True` 可以生成导出报告。
168+`ONNXProgram` 可以执行、保存和验证导出结果。
169+ 
170+#### 1.1.5 对 GE 的启示
171+ 
172+- 使用窄 callback 表达转换逻辑;
173+- 按阶段区分错误;
174+- 为扩展结果提供独立测试入口;
175+- 建立明确的版本和弃用规则。
176+ 
177+不采用以下设计:
178+ 
179+- `GraphContext`
180+- `symbolic` 的 ONNX 图构造语义;
181+- 每次解析时传入 translation table;
182+- PyTorch 内部 registry 作为 GE 公开接口。
183+ 
184+### 1.2 Torch-TensorRT QDP
185+ 
186+#### 1.2.1 适用链路
187+ 
188+Torch-TensorRT QDP 的主要链路是:
189+ 
190+```text
191+torch.library.custom_op
192+ -> torch.library.register_fake
193+ -> tensorrt.plugin 注册
194+ -> 自动生成 TensorRT converter
195+ -> torch_tensorrt.dynamo.compile
196+```
197+ 
198+该链路服务于 PyTorch custom op 到 TensorRT Plugin 的转换。
199+它不是 ONNX 到 GE Operator 的参数解析链路。
200+ 
201+#### 1.2.2 声明 Torch custom op
202+ 
203+用户首先声明 eager 实现:
204+ 
205+```python
206+@torch.library.custom_op("mylib::scaled_add", mutates_args=())
207+def scaled_add(
208+ x: torch.Tensor,
209+ y: torch.Tensor,
210+ alpha: float,
211+) -> torch.Tensor:
212+ return x + y * alpha
213+```
214+ 
215+然后声明 fake 实现:
216+ 
217+```python
218+@torch.library.register_fake("mylib::scaled_add")
219+def scaled_add_fake(
220+ x: torch.Tensor,
221+ y: torch.Tensor,
222+ alpha: float,
223+) -> torch.Tensor:
224+ return torch.empty_like(x)
225+```
226+ 
227+`custom_op` 的 schema 同时用于:
228+ 
229+- Torch dispatcher;
230+- fake/meta 推导;
231+- plugin 属性解析;
232+- converter 参数拆分。
233+ 
234+#### 1.2.3 自动生成 Plugin 和 converter
235+ 
236+Torch-TensorRT 提供以下入口:
237+ 
238+```python
239+torch_tensorrt.dynamo.conversion.plugins.custom_op(
240+ "mylib::scaled_add",
241+ supports_dynamic_shapes=True,
242+ use_aot_if_available=True,
243+)
244+```
245+ 
246+框架自动完成:
247+ 
248+- 生成 TensorRT Plugin descriptor;
249+- 根据 fake 实现推导输出 shape;
250+- 生成 JIT 或 AOT Plugin 适配;
251+- 生成并注册 Dynamo converter;
252+- 将 tensor 输入和 scalar 属性传入 Plugin。
253+ 
254+用户还可以直接使用 TensorRT 的 QDP 装饰器:
255+ 
256+```python
257+import tensorrt.plugin as trtp
258+ 
259+ 
260+@trtp.register("mylib::scaled_add")
261+def describe(x: trtp.TensorDesc, alpha: float) -> trtp.TensorDesc:
262+ return x.like()
263+ 
264+ 
265+@trtp.impl("mylib::scaled_add")
266+def implement(x: trtp.Tensor, alpha: float, outputs, stream):
267+ ...
268+```
269+ 
270+如果需要脱离 Python 运行时,可以增加 AOT 实现:
271+ 
272+```python
273+@trtp.aot_impl("mylib::scaled_add")
274+def implement_aot(x, outputs, tactic):
275+ return kernel_name, ptx, launch_params, extra_args
276+```
277+ 
278+#### 1.2.4 动态 shape、多输入和多输出
279+ 
280+动态 shape 通过 `supports_dynamic_shapes=True` 声明。
281+AOT 实现使用 `trtp.SymInt32``trtp.SymIntExprs` 表达符号维度。
282+ 
283+多输入由 Torch schema 或 `InputDecl` 表达:
284+ 
285+```python
286+inputs = [InputDecl("x"), InputDecl("y")]
287+```
288+ 
289+多输出由多个 `OutputDecl` 或 tuple schema 表达:
290+ 
291+```python
292+outputs = [
293+ OutputDecl("sum", shape=SameAs("x")),
294+ OutputDecl("diff", shape=SameAs("x")),
295+]
296+```
297+ 
298+#### 1.2.5 错误和测试
299+ 
300+注册阶段校验以下内容:
301+ 
302+- 输入和输出名称;
303+- 输入输出数量;
304+- shape 关系;
305+- geometry 参数;
306+- QDP 和 converter 是否已注册。
307+ 
308+测试通常采用 eager 与 TensorRT 编译结果对拍。
309+`dryrun=True` 用于检查 converter 覆盖情况。
310+ 
311+#### 1.2.6 对 GE 的启示
312+ 
313+- descriptor 应作为注册信息的唯一来源;
314+- 框架应自动完成标准注册链路中的重复工作;
315+- descriptor 应在写入 C++ registry 前完成校验;
316+- 动态 shape 的声明应与后续 GE shape 体系衔接。
317+ 
318+不采用以下设计:
319+ 
320+- `torch.library` 作为 ONNX Plugin 的 schema 来源;
321+- FakeTensor 作为 GE shape 推导实现;
322+- kernel、JIT、AOT 和 autotune 作为首轮 parser API;
323+- 进程内 QDP registry 作为 GE 插件发现机制。
324+ 
325+### 1.3 NVIDIA TensorRT
326+ 
327+#### 1.3.1 普通 ONNX Parser
328+ 
329+TensorRT 的普通 ONNX 解析入口如下:
330+ 
331+```python
332+logger = trt.Logger(trt.Logger.WARNING)
333+parser = trt.OnnxParser(network, logger)
334+ 
335+if not parser.parse(model_bytes):
336+ for index in range(parser.num_errors):
337+ print(parser.get_error(index))
338+```
339+ 
340+Parser 还提供:
341+ 
342+- `parse_from_file`
343+- `supports_operator`
344+- `supports_model_v2`
345+- `is_subgraph_supported`
346+- `get_subgraph_nodes`
347+- `get_used_vc_plugin_libraries`
348+ 
349+#### 1.3.2 Parser 错误
350+ 
351+`ParserError` 提供以下信息:
352+ 
353+- 错误码;
354+- 错误描述;
355+- 源码文件、行号和函数;
356+- ONNX 节点索引;
357+- 节点名;
358+- 算子名;
359+- local function 调用栈。
360+ 
361+该错误对象可以支持节点级问题定位。
362+ 
363+#### 1.3.3 QDP Plugin
364+ 
365+TensorRT QDP 使用 `register` 描述输出特征,使用 `impl` 描述计算:
366+ 
367+```python
368+import tensorrt.plugin as trtp
369+ 
370+ 
371+@trtp.register("sample::elemwise_add_plugin")
372+def describe(x: trtp.TensorDesc, block_size: int) -> trtp.TensorDesc:
373+ return x.like()
374+ 
375+ 
376+@trtp.impl("sample::elemwise_add_plugin")
377+def implement(x: trtp.Tensor, block_size: int, outputs, stream):
378+ ...
379+```
380+ 
381+`aot_impl`返回预编译 kernel。
382+`autotune`声明数据类型、format 和 tactic 组合。
383+高层 QDP 隐藏 creator、registry 和 Plugin 生命周期。
384+ 
385+#### 1.3.4 ONNX Custom Node 到 Plugin
386+ 
387+TensorRT 的 ONNX custom node 使用约定连接 Plugin:
388+ 
389+```python
390+node = gs.Node(
391+ op="elemwise_add_plugin",
392+ attrs={
393+ "plugin_namespace": "sample",
394+ "alpha": 1.0,
395+ },
396+)
397+```
398+ 
399+约束如下:
400+ 
401+- ONNX `op` 必须等于 Plugin name;
402+- `plugin_namespace` 指定 Plugin namespace;
403+- 使用 AOT 时可以增加 `aot=True`
404+ 
405+该约定不是 ONNX 标准字段。
406+ 
407+#### 1.3.5 动态 shape、多输出和数据依赖 shape
408+ 
409+QDP 使用 `TensorDesc``ShapeExpr` 表达动态 shape:
410+ 
411+```python
412+@trtp.register("sample::non_zero_plugin")
413+def describe(x: trtp.TensorDesc):
414+ upper_bound = x.shape_expr[0] * x.shape_expr[1]
415+ size = trtp.size_tensor(upper_bound // 2, upper_bound)
416+ return trtp.from_shape_expr((size.expr(), 2), dtype=trt.int32), size
417+```
418+ 
419+多个输出通过 tuple 返回多个 `TensorDesc`
420+输入输出别名通过 `aliased()` 表达。
421+ 
422+#### 1.3.6 对 GE 的启示
423+ 
424+- Parser 应与 Python/C++ Plugin 的实现语言解耦;
425+- 高层 API 应隐藏 creator、registry 和生命周期;
426+- 错误应包含节点级上下文;
427+- 后续 TensorDesc 和 symbolic shape 可以参考 `TensorDesc``ShapeExpr``size_tensor`
428+- GE 使用 ONNX domain/opset 表达插件身份,不增加 `plugin_namespace` 等模型私有属性。
429+ 
430+不采用 V2/V3 多代 Plugin 生命周期。
431+首轮不引入 kernel、JIT、AOT 和 autotune 接口。
432+ 
433+### 1.4 三套竞品的统一结论
434+ 
435+| 设计方向 | GE 结论 |
436+|---|---|
437+| 插件声明 | 使用 descriptor 统一声明 source、domain、opsets 和 target。 |
438+| callback | 首轮提供 `parse_node`,后续 callback 使用独立窄入口。 |
439+| 节点对象 | 使用只读 `OnnxNode`,隐藏 protobuf。 |
440+| 目标对象 | 直接操作受控 `ge.graph.Operator`,不增加 Update 类。 |
441+| 注册机制 | 自动发现并由 coordinator 完成注册,用户不操作内部 registry。 |
442+| 错误诊断 | 补充模块、origin type、节点、属性和 callback 上下文。 |
443+| 动态能力 | 首轮支持固定端口和默认动态端口;TensorDesc 和 shape DSL 后续开放。 |
444+| 执行实现 | parser plugin 与 kernel、compiler、runtime 解耦。 |
445+ 
446+## 2. 当前能力和首轮缺口
447+ 
448+### 2.1 当前已有能力
449+ 
450+GE 和现有 Python 模块已经具备以下基础能力:
451+ 
452+- C++ ONNX plugin 可以通过 `REGISTER_CUSTOM_OP``OpRegistrationData` 描述映射;
453+- `OpRegistrationTbe::Finalize``OpParserFactory` 可以建立 target parser creator;
454+- `OpRegistry` 可以保存 ParseParams、ParseOpToGraph 等 callback;
455+- `OnnxCustomParserAdapter` 可以调用 `ParseParamsFn`
456+- `ge::Operator` 已提供属性、动态输入输出和 TensorDesc 接口;
457+- `ge.graph._AttrValue` 已支持常用 Python 值到 GE 属性的转换;
458+- `ge._internal.plugin_loader` 已能按环境变量扫描 Python 文件或 package;
459+- `ge.es.GraphBuilder` 已能创建输入并构建 Graph;
460+- `ge.graph.Node``TensorDesc``Graph` 等基础 Python 对象已经存在。
461+ 
462+现有 ops-nn 和 ops-math 注册调用扫描结果如下:
463+ 
464+| C++ callback | 注册调用数 | 当前分期 |
465+|---|---:|---|
466+| `ParseParamsFn` | 175 | 首轮 |
467+| `ParseParamsByOperatorFn` | 7 | 迭代 2 |
468+| `ParseOpToGraphFn` | 74 | 迭代 2 |
469+| `ParseSubgraphPostFn` | 1 | 迭代 3 |
470+ 
471+### 2.2 当前缺口
472+ 
473+当前主要缺少以下能力:
474+ 
475+- 插件开发者不能使用 Python 声明 ONNX source 到 GE target 的映射;
476+- Python callback 不能直接接收 ONNX 节点值和 callback 期 `ge::Operator`
477+- Python descriptor 不能进入 `OpParserFactory``OpRegistry`
478+- atc、session 和 aclgrphParseONNX 没有统一的 Python ONNX plugin bootstrap;
479+- custom_op 与 ONNX plugin 共用路径时,没有统一的一次 import 约束;
480+- callback 异常、Operator handle 失效和注册冲突还没有 Python 侧错误语义。
481+ 
482+### 2.3 首轮支持矩阵
483+ 
484+| 能力 | 首轮状态 | 不支持时的行为 |
485+|---|---|---|
486+| `onnx_plugin(source, domain, opsets, target, imply_type)` | 支持 | 非法字段在 C++ 注册前报错。 |
487+| `parse_node(node, target)` | 支持 | callback 异常使当前节点解析失败。 |
488+| Node name、origin type、inputs、outputs | 支持 | 转换失败则当前节点解析失败。 |
489+| FLOAT、INT、STRING | 支持 | 未知或不支持类型明确报错。 |
490+| FLOATS、INTS、STRINGS | 支持 | 混合或非法元素类型明确报错。 |
491+| Operator bool/int/float/str 和同类型 list | 支持 | 非白名单值明确报错。 |
492+| required/optional 输入、普通输出注册 | 支持 | 非法 name 明确报错。 |
493+| 默认动态输入输出注册 | 支持 | 非法 name/count 明确报错。 |
494+| Tensor、Graph、SPARSE 属性 | 延后 | callback 前报告不支持。 |
495+| `ref_attr_name` 非空 | 延后 | 拒绝转换,不静默丢弃。 |
496+| TensorDesc 修改 | 延后 | 首轮不提供入口。 |
497+| `ParseParamsByOperatorFn` | 延后 | 首轮不存在对应装饰器。 |
498+| `ParseOpToGraphFn` 和 ES | 延后 | 首轮不存在 `expand` 入口。 |
499+| `ParseSubgraphPostFn` | 延后 | 首轮不存在 subgraph 入口。 |
500+ 
501+## 3. 目标公开接口
502+ 
503+### 3.1 完整公开接口清单
504+ 
505+首轮新增对外接口限定为以下 5 个符号。未出现在本表中的 registry、bootstrap、coordinator、bridge、C API 和异常转换 helper 都是内部实现,不允许加入 `__all__`
506+ 
507+| 公开路径 | 类型 | 用户获得方式 | 首轮公开成员 | 构造限制 |
508+|---|---|---|---|---|
509+| `ge.onnx_plugin.onnx_plugin` | 函数 | 直接导入调用 | `source/domain/opsets/target/imply_type` 关键字参数 | 用户直接调用。 |
510+| `ge.onnx_plugin.ImplyType` | Enum | 直接导入使用 | `TVM``AI_CPU``CUSTOM``GELOCAL` | 与 `domi::ImplyType` 对齐;使用枚举成员,不接受任意字符串。 |
511+| `ge.onnx_plugin.OnnxNode` | 只读类 | 由 GE 作为 callback 参数传入 | `name``op_type``inputs``outputs``attrs` | 不承诺用户直接构造;不提供 setter。 |
512+| `ge.onnx_plugin.OnnxPlugin` | descriptor 类 | 由 `onnx_plugin()` 返回 | `parse_node(fn)` | 不公开直接构造函数和内部 registry 状态。 |
513+| `ge.graph.Operator` | callback-bound 类 | 由 GE 作为 callback 参数传入 | `name``type``get_attr``set_attr``register_input``register_optional_input``register_output``register_dynamic_input``register_dynamic_output` | 不允许用户直接构造,callback 结束后失效。 |
514+ 
515+首轮模块导出目标:
516+ 
517+```python
518+# ge.onnx_plugin.__all__
519+[
520+ "ImplyType",
521+ "OnnxNode",
522+ "OnnxPlugin",
523+ "onnx_plugin",
524+]
525+ 
526+# ge.graph.__all__ 增加
527+["Operator"]
528+```
529+ 
530+接口边界:
531+ 
532+- `OnnxPlugin.parse_node` 返回原始 callable,保持装饰器后的函数名称、类型注解和可测试性;
533+- `OnnxNode.inputs/outputs` 使用只读 tuple,`attrs` 使用 `collections.abc.Mapping`;这些是标准库类型,不新增 GE 容器接口;
534+- 首轮使用 Python 内置 `TypeError``ValueError``RuntimeError`,不新增公开异常类;
535+- `Operator` 方法名、返回值、异常和 handle 失效语义必须整体参加公开接口评审,不能只评审类名;
536+- 迭代 2 的 `expand`、TensorDesc 访问和 ByOperator callback 不属于首轮公开 API,不得提前加入 `__all__`
537+ 
538+### 3.2 插件注册和 `parse_node`
539+ 
540+公开写法为“描述对象 + callback 装饰器”:
541+ 
542+```python
543+from ge.graph import Operator
544+from ge.onnx_plugin import ImplyType, OnnxNode, onnx_plugin
545+ 
546+ 
547+elu = onnx_plugin(
548+ source="Elu",
549+ domain="ai.onnx",
550+ opsets=range(8, 19),
551+ target="Elu",
552+ imply_type=ImplyType.TVM,
553+)
554+ 
555+ 
556+@elu.parse_node
557+def parse_elu(node: OnnxNode, target: Operator) -> None:
558+ alpha = node.attrs.get("alpha", 1.0)
559+ if not isinstance(alpha, float):
560+ alpha = 1.0
561+ target.set_attr("alpha", alpha)
562+```
563+ 
564+参数含义:
565+ 
566+| 参数 | 含义 | 约束 |
567+|---|---|---|
568+| `source` | ONNX 原始 op type。 | 非空,不包含 domain 和 opset。 |
569+| `domain` | ONNX domain。 | descriptor 中非空;标准 ONNX 使用 `ai.onnx`。 |
570+| `opsets` | 支持的 ONNX opset 集合。 | 非空、正整数、不能重复。 |
571+| `target` | GE 目标 Operator type。 | 原型必须已经安装并注册。 |
572+| `imply_type` | GE `domi::ImplyType` 的 Python 表达。 | 首轮支持 TVM、AI_CPU、CUSTOM、GELOCAL。 |
573+ 
574+处理规则:
575+ 
576+- descriptor 将 source、domain 和每个 opset 展开为完整 origin type;
577+- `FrameworkType` 在内部固定为 ONNX,不由用户填写;
578+- 同一个完整 origin key 只能由一个 C++ 或 Python plugin 提供;
579+- `parse_node` 返回值必须是 `None`
580+- Python 异常由 bridge 捕获并转换为 parser failure;
581+- callback 失败后当前 target 不执行 `Graph::AddOp`
582+ 
583+输出和错误:
584+ 
585+- `onnx_plugin(...)` 返回 `OnnxPlugin` 描述对象;
586+- `parse_node` 成功返回 `None`
587+- 非法 descriptor 抛出 `ValueError``TypeError`
588+- 重复注册报告 origin key、target、callback map 和模块路径;
589+- 用户不能直接读取或修改内部 registry。
590+ 
591+### 3.3 `OnnxNode`
592+ 
593+首轮公开对象:
594+ 
595+```python
596+class OnnxNode:
597+ name: str
598+ op_type: str
599+ inputs: tuple[str, ...]
600+ outputs: tuple[str, ...]
601+ attrs: Mapping[str, object]
602+```
603+ 
604+字段含义:
605+ 
606+| 字段 | 来源 | 说明 |
607+|---|---|---|
608+| `name` | `NodeProto::name()` | ONNX 节点名称。 |
609+| `op_type` | parser 构造的 origin type | 对普通 ONNX/custom 节点为完整 `domain::version::type`。 |
610+| `inputs` | `NodeProto::input()` | 只读输入 tensor 名称序列。 |
611+| `outputs` | `NodeProto::output()` | 只读输出 tensor 名称序列。 |
612+| `attrs` | `AttributeProto` | 使用 Python 标量或同类型列表表达。 |
613+ 
614+转换规则:
615+ 
616+- parser 组件使用 GE 私有 `ge::onnx::NodeProto`
617+- parser 侧完成 Message 类型确认和字段扁平化;
618+- bridge 不直接链接 GE 私有 protobuf 生成类型;
619+- `OnnxNode` 不保存 protobuf 指针;
620+- `attrs` 是只读 Mapping;
621+- STRING 按 UTF-8 解码,失败时报告属性名称;
622+- GE 当前 ONNX proto 不含 `TYPE_PROTO/TYPE_PROTOS`
623+- 非空 `ref_attr_name` 作为未解析属性引用拒绝。
624+ 
625+### 3.4 `ge.graph.Operator`
626+ 
627+首轮目标接口:
628+ 
629+```python
630+class Operator:
631+ @property
632+ def name(self) -> str: ...
633+ 
634+ @property
635+ def type(self) -> str: ...
636+ 
637+ def get_attr(self, name: str) -> object: ...
638+ def set_attr(self, name: str, value: object) -> None: ...
639+ def register_input(self, name: str) -> None: ...
640+ def register_optional_input(self, name: str) -> None: ...
641+ def register_output(self, name: str) -> None: ...
642+ def register_dynamic_input(self, name: str, count: int) -> None: ...
643+ def register_dynamic_output(self, name: str, count: int) -> None: ...
644+```
645+ 
646+对象约束:
647+ 
648+- `Operator` 包装 callback 期的 `ge::Operator`,用户不能直接构造;
649+- handle 只在 callback 期间有效;
650+- callback 返回或抛出异常后,所有方法都必须报告失效;
651+- 对象不能 copy、deepcopy、pickle;
652+- 同一个 Operator 不允许并发修改;
653+- 首轮属性白名单为 bool、int、float、str 和对应同类型 list;
654+- 方法名和 handle 实现仍需公开接口评审冻结。
655+ 
656+`ge.graph.Node``ge.graph.Operator` 不相同:
657+ 
658+| 对象 | C++ 对象 | 使用阶段 |
659+|---|---|---|
660+| `ge.graph.Node` | `ge::GNode` | 节点已经加入 Graph 后。 |
661+| `ge.graph.Operator` | `ge::Operator` | parser callback 期间,尚未加入 Graph。 |
662+ 
663+两者不共享 handle,但可以复用属性转换、TensorDesc 和错误处理代码。
664+ 
665+### 3.5 输入输出注册
666+ 
667+首轮直接提供 `ge::Operator` 的基础端口注册能力,不增加 `DynamicPort` 或端口更新对象:
668+ 
669+```python
670+def register_ports(node: OnnxNode, target: Operator) -> None:
671+ target.register_input("x")
672+ target.register_optional_input("bias")
673+ target.register_dynamic_input("args", max(0, len(node.inputs) - 2))
674+ target.register_output("y")
675+```
676+ 
677+固定 required/optional 输入和普通输出通常已经由 target 原型定义,此时 callback 不需要重复注册。对于确实需要在解析期补充端口的 target,用户直接调用上述方法;bridge 只校验非空名称并转发到对应 C++ 注册能力,不引入额外去重、排序或事务语义。动态端口方法用于按当前节点实例数展开已定义的动态端口。
678+ 
679+当前 `InputRegister``OptionalInputRegister``OutputRegister` 位于 `ge::Operator``protected` 区域,不能由独立 bridge 直接调用。编码时需要在 `ge::Operator` 提供不改变对象布局的 public 转发入口或经 graph 基础接口评审认可的等价访问机制;不允许 bridge 绕过 `Operator` 直接修改内部 `OpDesc`
680+ 
681+Sum 到 AccumulateNV2 的写法如下:
682+ 
683+```python
684+sum_plugin = onnx_plugin(
685+ source="Sum",
686+ domain="ai.onnx",
687+ opsets=range(8, 19),
688+ target="AccumulateNV2",
689+ imply_type=ImplyType.TVM,
690+)
691+ 
692+ 
693+@sum_plugin.parse_node
694+def parse_sum(node: OnnxNode, target: Operator) -> None:
695+ input_count = len(node.inputs)
696+ if input_count < 1:
697+ raise ValueError("Sum requires at least one input")
698+ target.register_dynamic_input("x", input_count)
699+ target.set_attr("N", input_count)
700+```
701+ 
702+首轮同时支持 required、optional、普通 output 和 dynamic 端口注册。已有原型端口不应重复注册;Sum 只需要根据当前节点输入数创建动态实例。
703+ 
704+首轮只支持 C++ 默认重载:
705+ 
706+```text
707+DynamicInputRegister(name, count, is_push_back=true)
708+DynamicOutputRegister(name, count, is_push_back=true)
709+```
710+ 
711+ByIndex、datatype symbol 和 append 行为进入后续真实需求评审。
712+ 
713+### 3.6 迭代 2:图展开和 TensorDesc
714+ 
715+`ParseOpToGraphFn` 的目标形态为:
716+ 
717+```python
718+@thresholded_relu.expand
719+def expand_thresholded_relu(source):
720+ builder = GraphBuilder(source.name)
721+ data = builder.create_input(0, name=f"{source.name}_data")
722+ identity = Identity(data)
723+ selected = Threshold(identity, threshold=source.get_attr("alpha", 1.0))
724+ output = Mul(identity, selected)
725+ return builder.build_and_reset(outputs=[output])
726+```
727+ 
728+该代码是迭代 2 草案,不是首轮 API 承诺。进入迭代 2 前必须确认:
729+ 
730+- `GraphBuilder` 和生成式算子的准确导入路径;
731+- Identity、Threshold、Mul 是否都有可复用的 ES 生成式入口;
732+- Graph 输入输出的准确设置方式;
733+- `expand` 的返回值和 source Operator 生命周期;
734+- TensorDesc getter 返回副本还是关联 handle;
735+- TensorDesc 修改如何同步回所属 Operator。
736+ 
737+### 3.7 接口评审和资料交付
738+ 
739+首轮设计评审收敛为两个必须与 SE 对齐的门禁,不能只评审示例代码:
740+ 
741+1. 对外公开接口的完整性、命名、生命周期和类型声明;
742+2. 是否复用现有 `ASCEND_CUSTOM_OPP_PATH`,以及复用后的发现、加载、冲突和兼容语义。
743+ 
744+对外接口评审至少包括:
745+ 
746+| 评审对象 | 必须确认的内容 | 当前状态 |
747+|---|---|---|
748+| `onnx_plugin` | 参数类型、默认值、domain/opset 语义、重复注册和异常 | 待评审 |
749+| `ImplyType` | 枚举值、与 `domi::ImplyType` 的映射、不支持值的行为 | 待评审 |
750+| `OnnxNode` | 只读性、字段类型、origin type 语义、是否允许直接构造 | 待评审 |
751+| `OnnxPlugin` | 是否公开类名、`parse_node` 返回值、直接构造限制 | 待评审 |
752+| `Operator` | 类归属、方法名、类型白名单、返回值、失效和线程语义 | 待评审 |
753+| 模块导出 | `ge.onnx_plugin.__all__``ge.graph.__all__` | 待评审 |
754+ 
755+接口评审通过后,代码合入前需要同步以下资料:
756+ 
757+- API Reference:逐个公开符号说明原型、参数、返回值、异常、生命周期和线程约束;
758+- 用户指南:插件目录、`ASCEND_CUSTOM_OPP_PATH`、加载时机、C++/Python 冲突和常见错误;
759+- 入门 Sample:Elu 属性解析和 Sum 动态输入;
760+- 测试 Sample:测试专用 origin type、模型生成、atc/parser 执行和图结果检查;
761+- C++ 迁移指南:`REGISTER_CUSTOM_OP``ParseParamsFn``SetAttr`、动态端口到 Python 的对应关系;
762+- 版本和废弃说明:新增接口版本、后续 callback 增加方式和废弃流程;
763+- `.pyi`/类型提示:公开类型、方法签名和只读属性与接口评审结果一致。
764+ 
765+### 4.7 环境变量评审:`ASCEND_CUSTOM_OPP_PATH`
766+ 
767+当前仓库确认的环境变量名称是 `ASCEND_CUSTOM_OPP_PATH`,不是另一个 ONNX 专用名称。已有使用证据包括:
768+ 
769+| 现有能力 | 代码位置 | 作用 |
770+|---|---|---|
771+| Python custom_op bootstrap | `api/python/ge/ge/custom_op/bootstrap.py:21` | 将环境变量作为 Python 插件路径入口,并复用共享 loader。 |
772+| Python custom_op bridge | `runtime/custom_op/python_custom_op_bridge_loader.cc:126-134` | 读取路径并加载 Python custom_op。 |
773+| C++ OPP/plugin 管理 | `graph_metadef/base/common/plugin/plugin_manager.cc:461,573-605` | 读取 OPP 路径和已安装插件包。 |
774+ 
775+当前方案建议 ONNX plugin 复用该环境变量和 `ge._internal.plugin_loader`,原因如下:
776+ 
777+- 用户只需配置一套现有 OPP 扩展路径,不新增产品级环境变量和另一套部署文档;
778+- ONNX plugin、custom_op 可以共享 Python runtime、路径扫描规则和 canonical-path module cache;
779+- 同一 Python 文件可以声明不同类型的插件,模块只 import 一次,再分别分发到各自 registry;
780+- 未配置该环境变量时保持现有 C++ parser/custom_op 行为,不增加默认扫描开销和兼容分支。
781+ 
782+该复用不是默认冻结结论,必须在编码前完成 SE 专项评审。至少确认:
783+ 
784+| 评审项 | 需要确认的内容 |
785+|---|---|
786+| 路径和扫描规则 | `.py` 文件、package、路径列表、排序和是否只扫描当前层。 |
787+| 加载时序 | atc、session、`aclgrphParseONNX` 是否都能在首次 parser 查询前完成 bootstrap。 |
788+| 共存语义 | C++ `.so`、Python custom_op 和 ONNX plugin 的优先级、冲突诊断和同文件一次 import。 |
789+| 回归和性能 | 环境变量为空、仅有 C++ 插件、路径无 Python 文件时的行为和 parser 初始化开销。 |
790+| 安全与发布 | OPP 路径来源、错误信息、安装包交付方式和跨版本兼容性。 |
791+ 
792+SE 确认复用可行后,用户指南、ST Sample、loader 设计和初始化门禁才能按此方案冻结;未确认前不新增第二个 ONNX plugin 环境变量,也不把复用结论标记为已完成。
793+ 
794+## 4. 用户使用方式
795+ 
796+### 4.1 Elu:读取可选属性
797+ 
798+Elu 只需要 `parse_node`
799+ 
800+```python
801+@elu.parse_node
802+def parse_elu(node: OnnxNode, target: Operator) -> None:
803+ alpha = node.attrs.get("alpha", 1.0)
804+ if not isinstance(alpha, float):
805+ alpha = 1.0
806+ target.set_attr("alpha", alpha)
807+```
808+ 
809+执行过程为:
810+ 
811+```text
812+读取 alpha
813+ -> 属性不存在时使用 1.0
814+ -> 属性不是 FLOAT 时保持现有 C++ 插件的默认值语义
815+ -> 写入目标 Operator
816+```
817+ 
818+生产环境已存在 Elu C++ plugin。为避免注册冲突,parser ST 使用测试专用 origin type 执行等价逻辑。
819+ 
820+### 4.2 Sum:注册动态输入
821+ 
822+```python
823+@sum_plugin.parse_node
824+def parse_sum(node: OnnxNode, target: Operator) -> None:
825+ count = len(node.inputs)
826+ if count == 0:
827+ raise ValueError("Sum requires at least one input")
828+ target.register_dynamic_input("x", count)
829+ target.set_attr("N", count)
830+```
831+ 
832+这条用例验证:
833+ 
834+- ONNX inputs 转换;
835+- 动态输入数量;
836+- Operator 属性设置;
837+- 零输入异常;
838+- 不引入 `DynamicPort` 中间对象。
839+ 
840+### 4.3 ThresholdedRelu:总体交付边界
841+ 
842+原始需求中的 ThresholdedRelu 仍是总体目标:
843+ 
844+```text
845+ONNX ThresholdedRelu
846+ -> ParseParams: name、alpha、动态 IO
847+ -> ParseOpToGraph: Data -> Identity -> Threshold -> Mul
848+ -> 设置 Graph 输入输出
849+```
850+ 
851+当前分期:
852+ 
853+| 内容 | 阶段 |
854+|---|---|
855+| 属性解析等价链 | 首轮由 Elu 验证。 |
856+| 默认动态输入 | 首轮由 Sum 验证。 |
857+| `ParseOpToGraphFn` | 迭代 2。 |
858+| ES 子图和 Graph 输入输出 | 迭代 2。 |
859+ 
860+该延期需要需求方和 SE 确认。未确认前不启动迭代 1 编码,也不把原始需求标记为全部完成。
861+ 
862+### 4.4 接口使用 Sample:属性解析 ST
863+ 
864+本 Sample 面向 ST/系统测试,不是 Python UT。它从插件文件和 ONNX 模型开始,经过真实 loader、coordinator、bridge 和 parser,最后检查解析后的 GE Graph;环境具备完整算子包时可以继续使用 atc 做黑盒编译。
865+ 
866+测试前置条件:parser ST 夹具注册测试专用 target 原型 `GePythonPluginTestOp`,包含一个必选输入 `x`、一个输出 `y` 和 FLOAT 属性 `alpha`,但不注册 C++ parser creator 或 callback。使用专用 target 可以验证 Python `Finalize` 建立 creator 的完整链路,避免生产 Elu plugin 已有 creator 导致预检查拒绝。
867+ 
868+目录结构:
869+ 
870+```text
871+onnx_plugin_parse_sample/
872+ plugin/
873+ plugin.py
874+ model/
875+ generate_model.py
876+ output/
877+```
878+ 
879+`plugin/plugin.py`
880+ 
881+```python
882+from ge.graph import Operator
883+from ge.onnx_plugin import ImplyType, OnnxNode, onnx_plugin
884+ 
885+ 
886+test_elu = onnx_plugin(
887+ source="GePythonPluginTestElu",
888+ domain="ge.test",
889+ opsets=(1,),
890+ target="GePythonPluginTestOp",
891+ imply_type=ImplyType.TVM,
892+)
893+ 
894+ 
895+@test_elu.parse_node
896+def parse_test_elu(node: OnnxNode, target: Operator) -> None:
897+ target.set_attr("alpha", node.attrs.get("alpha", 1.0))
898+```
899+ 
900+`model/generate_model.py`
901+ 
902+```python
903+import pathlib
904+ 
905+import onnx
906+from onnx import TensorProto, helper
907+ 
908+ 
909+output_dir = pathlib.Path(__file__).parent.parent / "output"
910+output_dir.mkdir(exist_ok=True)
911+ 
912+x = helper.make_tensor_value_info("x", TensorProto.FLOAT, [1, 4])
913+y = helper.make_tensor_value_info("y", TensorProto.FLOAT, [1, 4])
914+node = helper.make_node(
915+ "GePythonPluginTestElu",
916+ ["x"],
917+ ["y"],
918+ domain="ge.test",
919+ name="test_elu",
920+ alpha=0.25,
921+)
922+graph = helper.make_graph([node], "ge_python_plugin_test", [x], [y])
923+model = helper.make_model(
924+ graph,
925+ opset_imports=[
926+ helper.make_opsetid("", 13),
927+ helper.make_opsetid("ge.test", 1),
928+ ],
929+)
930+onnx.checker.check_model(model)
931+onnx.save(model, output_dir / "test_elu.onnx")
932+```
933+ 
934+执行方式:
935+ 
936+```bash
937+python3 model/generate_model.py
938+export ASCEND_CUSTOM_OPP_PATH="$(pwd)/plugin"
939+ 
940+# parser ST 使用测试入口解析 output/test_elu.onnx 并检查 Graph。
941+# 安装了测试 target 原型和实现的环境可继续执行 atc 黑盒编译:
942+atc --framework=5 \
943+ --model="$(pwd)/output/test_elu.onnx" \
944+ --output="$(pwd)/output/test_elu" \
945+ --soc_version="${SOC_VERSION}"
946+```
947+ 
948+ST 必须检查:
949+ 
950+| 检查点 | 预期结果 |
951+|---|---|
952+| 模块导入 | `plugin.py` 只执行一次。 |
953+| origin key | 生成 `ge.test::1::GePythonPluginTestElu`。 |
954+| parser creator | target `GePythonPluginTestOp` 可以由 `OpParserFactory` 创建 parser。 |
955+| 目标节点 | Graph 中目标节点 type 为 `GePythonPluginTestOp`。 |
956+| 属性 | `alpha` 类型为 FLOAT,值为 `0.25`。 |
957+| 图连接 | 一个输入、一个输出,名称与 ONNX 模型一致。 |
958+| 生命周期 | callback 返回后保存的 Operator 引用不可继续调用。 |
959+| 黑盒结果 | 安装测试 target 原型和 Kernel 后,atc 返回成功并生成 OM。未安装 Kernel 时只要求 parser ST 通过。 |
960+ 
961+测试专用 source、domain 和 target 的目的,是同时避免生产 C++ plugin 的 origin key、target creator 和 callback map 冲突。Sample 验证的是接口和完整调用链,不要求替换生产 Elu plugin。
962+ 
963+### 4.5 接口使用 Sample:动态输入 ST
964+ 
965+动态输入 Sample 沿用相同目录和执行方式,只需要增加测试插件和模型节点:
966+ 
967+```python
968+test_sum = onnx_plugin(
969+ source="GePythonPluginTestSum",
970+ domain="ge.test",
971+ opsets=(1,),
972+ target="GePythonPluginTestDynamicOp",
973+ imply_type=ImplyType.TVM,
974+)
975+ 
976+ 
977+@test_sum.parse_node
978+def parse_test_sum(node: OnnxNode, target: Operator) -> None:
979+ count = len(node.inputs)
980+ if count == 0:
981+ raise ValueError("GePythonPluginTestSum requires at least one input")
982+ target.register_dynamic_input("x", count)
983+ target.set_attr("N", count)
984+```
985+ 
986+模型生成器分别构造 0、1、3 个输入,预期如下:
987+ 
988+parser ST 夹具需要注册测试专用 target 原型 `GePythonPluginTestDynamicOp`,包含动态输入 `x`、输出 `y` 和 INT 属性 `N`,同样不注册 C++ parser creator 或 callback。
989+ 
990+| 输入数 | 预期结果 |
991+|---:|---|
992+| 0 | parser 失败,错误包含 callback、origin type 和“requires at least one input”。 |
993+| 1 | 解析成功,动态输入 `x` 数量为 1,属性 `N=1`。 |
994+| 3 | 解析成功,动态输入 `x` 数量为 3,属性 `N=3`。 |
995+ 
996+### 4.6 测试泛化矩阵
997+ 
998+测试同事可以在上述两个 Sample 上扩展参数,而不重新设计测试框架:
999+ 
1000+| 维度 | 基础值 | 泛化值 | 主要检查 |
1001+|---|---|---|---|
1002+| 插件载体 | 单个 `plugin.py` | package、多个文件、同文件声明 custom_op | 导入次数、排序和 registry 分流。 |
1003+| domain | `ge.test` | 空模型 domain、其他 custom domain | origin type 构造和 opset 匹配。 |
1004+| opset | 单个 1 | 多个、不连续、重叠、非法值 | descriptor 展开和冲突诊断。 |
1005+| 属性 | FLOAT alpha | 缺省、INT、STRING、列表、不支持类型 | 转换、默认值和负向错误。 |
1006+| 输入数量 | 1 | 0、3、较大数量 | 动态端口和边界值。 |
1007+| callback 返回 | `None` | 非 None、抛出异常 | Status 转换和 Graph 不加节点。 |
1008+| 注册关系 | 无冲突 | C++/Python、Python/Python、跨 callback map | C++ 优先和预检查。 |
1009+| 初始化入口 | parser ST | atc、session、aclgrphParseONNX、多模型连续解析 | 时序和幂等。 |
1010+| 生命周期 | callback 内访问 | callback 后访问、保存到全局 | handle 失效。 |
1011+| 无插件回归 | 配置测试插件 | 环境变量为空、路径无 Python 文件 | 原 C++ parser 行为不变。 |
1012+ 
1013+Sample 后续应作为独立测试资源随接口资料交付。ST 可以替换 source、属性和输入数量,但不能依赖 Python registry 私有字段或直接调用内部 bridge helper,否则无法验证真实用户接口。
1014+ 
1015+## 5. C++ 能力如何体现在 Python 中
1016+ 
1017+| GE/metadef C++ 能力 | Python 表达 | bridge/框架动作 | 阶段 |
1018+|---|---|---|---|
1019+| `REGISTER_CUSTOM_OP``OpRegistrationData` | `onnx_plugin(...)` | descriptor 转换为 registration data。 | 首轮 |
1020+| `FrameworkType(ONNX)` | 不对外暴露 | bootstrap 内部固定。 | 首轮 |
1021+| `OriginOpType` | source、domain、opsets | 展开完整 origin type 集合。 | 首轮 |
1022+| `ImplyType` | `ImplyType` | 转换为 TVM、AI_CPU、CUSTOM、GELOCAL。 | 首轮子集 |
1023+| `ParseParamsFn` | `@plugin.parse_node` | C++ wrapper 调用 Python。 | 首轮 |
1024+| `NodeProto` | `OnnxNode` | parser 侧扁平化。 | 首轮常用字段 |
1025+| `AttributeProto` | Python 标量/list | 类型白名单转换。 | 首轮常用类型 |
1026+| `Operator::GetAttr/SetAttr` | `get_attr/set_attr` | callback-bound handle 调用现有接口。 | 首轮子集 |
1027+| `InputRegister` | `register_input` | 经评审后的 public 转发入口注册 required 输入。 | 首轮 |
1028+| `OptionalInputRegister` | `register_optional_input` | 经评审后的 public 转发入口注册 optional 输入。 | 首轮 |
1029+| `OutputRegister` | `register_output` | 经评审后的 public 转发入口注册普通输出。 | 首轮 |
1030+| `DynamicInputRegister` | `register_dynamic_input` | 固定默认 `is_push_back=true`。 | 首轮 |
1031+| `DynamicOutputRegister` | `register_dynamic_output` | 固定默认 `is_push_back=true`。 | 首轮 |
1032+| `ParseParamsByOperatorFn` | 独立窄入口,名称待评审 | 写入 ByOperator map。 | 迭代 2 |
1033+| `ParseOpToGraphFn` | `@plugin.expand` 目标形态 | 直接写入 graph callback map。 | 迭代 2 |
1034+| `Graph::SetInputs` | `GraphBuilder.create_input` | builder 绑定输入 index。 | 迭代 2 |
1035+| `Graph::SetOutputs` | `build_and_reset(outputs=[...])` | builder 设置输出并返回 Graph。 | 迭代 2 |
1036+| TensorDesc Get/Update | 复用 `ge.graph.TensorDesc` | 所有权和同步语义待冻结。 | 迭代 2 |
1037+| `ParseSubgraphFuncV2` | 独立 subgraph 入口 | 写入 subgraph callback map。 | 迭代 3 |
1038+ 
1039+不向 Python 用户暴露:
1040+ 
1041+- `google::protobuf::Message *`
1042+- `ge::Operator *`
1043+- C++ `Status`
1044+- `OpRegistrationData`
1045+- `OpParserFactory`
1046+- `OpRegistry`
1047+- GIL 和 Python C API。
1048+ 
1049+详细接口、重载和源码位置见[附录 A](#附录-a源码证据索引)。
1050+ 
1051+## 6. 总体实现方案
1052+ 
1053+### 6.1 各层职责
1054+ 
1055+| 层次 | 主要职责 | 不承担的职责 |
1056+|---|---|---|
1057+| Python 公开接口层 | 提供 `onnx_plugin``ImplyType``OnnxNode``OnnxPlugin``ge.graph.Operator`。 | 不感知 C++ registry、protobuf 和 Status。 |
1058+| Python descriptor registry | 收集、校验、冻结 Python descriptors。 | 不直接修改 C++ parser map。 |
1059+| Python plugin loader | 按路径只 import 一次模块。 | 不处理 creator/callback 冲突。 |
1060+| parser registration coordinator | 预检查 creator 和全部 callback map,协调 Finalize/Register。 | 不重新实现 parser 查询逻辑。 |
1061+| parser-side flattener | 读取私有 NodeProto 并转换为扁平值。 | 不调用用户 Python 业务代码。 |
1062+| 独立 C++ bridge SO | 管理 GIL、Operator handle、Python callback 和异常转换。 | 不编入 graph_metadef,不实现 ONNX 业务规则。 |
1063+| 现有 parser/registry | 创建 adapter、查找 callback、执行 Graph::AddOp。 | 不增加 Python 专用查询旁路。 |
1064+ 
1065+### 6.2 用户代码到 parser 的主链
1066+ 
1067+```text
1068+ASCEND_CUSTOM_OPP_PATH/plugin.py
1069+ -> plugin_loader 按 canonical path 导入一次
1070+ -> Python registry 冻结 descriptors
1071+ -> registration coordinator 预检查
1072+ -> OpRegistrationTbe::Finalize
1073+ -> OpParserFactory target creator
1074+ -> OpRegistry::Register callback maps
1075+ -> OnnxParser 创建 OnnxCustomParserAdapter
1076+ -> parser-side flattener 生成 OnnxNode
1077+ -> bridge 创建 callback-bound Operator
1078+ -> parse_node(node, target)
1079+ -> callback 成功后 Graph::AddOp
1080+```
1081+ 
1082+### 6.3 atc 和 session 初始化路径
1083+ 
1084+`AclParserInitialize` 不是唯一入口:
1085+ 
1086+| 场景 | 真实入口 | 现有行为 | Python 接入要求 |
1087+|---|---|---|---|
1088+| atc | `api/atc/main_impl.cc::LoadCustomOpLib` | `Finalize``Register`。 | `ParseGraph` 前完成 descriptor 发现和提交。 |
1089+| session 插件加载 | `TBEPluginManager::LoadCustomOpLib` | 只调用 `Register`。 | 不认为 creator 已完成,只复用一次 import。 |
1090+| session/aclgrphParseONNX | `AclGraphParserUtil::AclParserInitialize` | 调用 `Finalize``Register`。 | 首次 factory/registry 查询前完成 coordinator。 |
1091+| aclgrphParseONNX | `onnx_parser.cc``std::call_once` | parser 初始化只执行一次。 | Python bootstrap 保持进程级幂等。 |
1092+ 
1093+coordinator 必须具有进程级状态:
1094+ 
1095+```text
1096+UNINITIALIZED
1097+ -> LOADING
1098+ -> PREFLIGHTED
1099+ -> REGISTERED
1100+ 
1101+任一步失败
1102+ -> FAILED
1103+ -> 当前进程不继续 ONNX Python plugin 解析
1104+```
1105+ 
1106+### 6.4 custom_op 与 ONNX plugin 共存
1107+ 
1108+两类能力可以共用:
1109+ 
1110+- Python interpreter;
1111+- `ASCEND_CUSTOM_OPP_PATH`
1112+- 路径扫描规则;
1113+- canonical-path module cache。
1114+ 
1115+两类能力不能共用 C++ registry:
1116+ 
1117+```text
1118+ge.custom_op
1119+ -> PythonCustomOpBridge
1120+ -> CustomOpRegistry / OpLibRegistry
1121+ 
1122+ONNX plugin
1123+ -> parser registration coordinator
1124+ -> OpParserFactory / OpRegistry
1125+```
1126+ 
1127+同一个 Python 文件可以声明两类 descriptor,但模块只能 import 一次,两类 descriptor 分别提交各自 registry。
1128+ 
1129+### 6.5 注册冲突处理
1130+ 
1131+注册前检查:
1132+ 
1133+- target 原型是否存在;
1134+- target parser creator 是否已经存在;
1135+- ParseParamsFn map;
1136+- ParseParamsByOperatorFn map;
1137+- ParseOpToGraphFn map;
1138+- subgraph callback map;
1139+- Python descriptors 之间的 opset 重叠。
1140+ 
1141+首轮策略:
1142+ 
1143+- C++/Python 任一冲突时保留 C++,拒绝 Python;
1144+- Python/Python 冲突使初始化失败;
1145+- 已有 target creator 时拒绝 Python target;
1146+- 日志包含 target、origin type、callback map 和模块路径;
1147+- 不允许 Python 覆盖 C++ callback。
1148+ 
1149+`OpRegistry::Register(..., is_custom_op=false)` 对已有 key 可能静默跳过并返回成功,因此不能用它发现冲突。冲突检查和诊断必须在 coordinator 调用 `Register` 前完成。
1150+ 
1151+### 6.6 callback 调用和生命周期
1152+ 
1153+ONNX parser 会先查询 `ParseParamsByOperatorFn`,未命中后才进入 `ParseParamsFn`。首轮 coordinator 只注册 `ParseParamsFn`,并检查 ByOperator map 不存在冲突。
1154+ 
1155+Operator handle 状态:
1156+ 
1157+```text
1158+CREATED
1159+ -> ACTIVE
1160+ -> INVALID
1161+ 
1162+Python callback 异常
1163+ -> INVALID
1164+```
1165+ 
1166+规则:
1167+ 
1168+- callback 期间 bridge 持有目标 Operator;
1169+- Python 不能获得裸指针;
1170+- callback 结束立即失效;
1171+- 保存 Python 引用不会延长 C++ 对象生命周期;
1172+- 所有公开方法先检查 ACTIVE;
1173+- callback 失败后当前 Operator 不加入 Graph。
1174+ 
1175+## 7. 子模块修改关系
1176+ 
1177+| 子模块 | 主要修改 | 目的 |
1178+|---|---|---|
1179+| `api/python/ge/ge/onnx_plugin/` | 新增 descriptor、公开入口、内部 registry/bootstrap。 | 提供 Python ONNX plugin 用户界面。 |
1180+| `api/python/ge/ge/graph/operator.py` | 新增 callback-bound Operator。 | 包装 callback 期 `ge::Operator`。 |
1181+| `api/python/ge/ge/graph/__init__.py` | 导出 Operator。 | 形成稳定公开入口。 |
1182+| `api/python/ge/ge/_internal/plugin_loader.py` | 增加 canonical-path module cache 或统一复用入口。 | 同一文件只 import 一次。 |
1183+| parser-side NodeProto flattener | 读取私有 protobuf并输出扁平值。 | 隔离 protobuf ABI。 |
1184+| parser registration coordinator | creator/callback 预检查和 Finalize/Register 提交。 | 闭合真实注册链。 |
1185+| parser/atc/session 初始化入口 | 接入统一 bootstrap。 | 覆盖所有解析入口。 |
1186+| 独立 bridge SO | callback wrapper、GIL、Operator handle、异常转换。 | 连接 Python 与现有 parser callback。 |
1187+| build/package | 编译和安装 bridge 与 Python package。 | 保证组件版本匹配。 |
1188+| tests | Python UT、bridge UT、parser ST。 | 验证用户接口和真实链路。 |
1189+ 
1190+实现后需要同步:
1191+ 
1192+- `docs/zh/design/modules/ge_python/ge_python.md`
1193+- `docs/zh/design/modules/ge_python/ge_python_custom_op_design.md`
1194+- `docs/zh/design/modules/ge_python/ge_python_pass_design.md` 中受影响的 loader/runtime 说明;
1195+- 必要时新增 `docs/zh/design/features/` 下的 ONNX plugin 特性文档。
1196+ 
1197+## 8. 非功能、错误和兼容性设计
1198+ 
1199+### 8.1 可维护性和可测试性
1200+ 
1201+- 公开 API 只导出 `onnx_plugin``ImplyType``OnnxNode``OnnxPlugin``ge.graph.Operator`
1202+- registry、bootstrap、coordinator 和 bridge helper 保持内部可见;
1203+- 注册字段只声明一次;
1204+- 新 callback 使用独立窄入口,不修改既有 callback 签名;
1205+- descriptor、OnnxNode 和参数校验可以独立 Python UT;
1206+- protobuf 提取、GIL、Operator handle 和异常可以独立 C++ UT;
1207+- parser ST 从 ONNX 输入验证最终 Operator 属性和动态端口。
1208+ 
1209+### 8.2 可靠性和并发
1210+ 
1211+- descriptor 在写入任何 C++ registry 前完整校验;
1212+- 注册完成后 registry 只读;
1213+- bridge 不新建线程;
1214+- callback 在 parser 调用线程内获取 GIL;
1215+- 图基础对象采用现有单线程修改模型;
1216+- 同一 Operator 不允许并发 setter;
1217+- callback 异常不跨越 C++ ABI;
1218+- registration coordinator 失败后保持 FAILED,不继续提交剩余 descriptor。
1219+ 
1220+### 8.3 错误处理
1221+ 
1222+| 错误 | 行为 |
1223+|---|---|
1224+| 非法 descriptor 字段 | Python 注册阶段抛 `ValueError``TypeError`。 |
1225+| Python/Python origin 重叠 | 初始化失败,报告两个模块。 |
1226+| C++/Python creator 或 callback 冲突 | 保留 C++,拒绝 Python,报告 key/map。 |
1227+| target 原型不存在 | 注册失败。 |
1228+| 插件模块导入失败 | 初始化失败,不提交不完整集合。 |
1229+| NodeProto 属性类型不支持 | 当前节点解析失败,报告属性名和类型。 |
1230+| callback 返回非 `None` | 当前节点解析失败。 |
1231+| callback 抛出异常 | bridge 记录模块、origin type 和 traceback 摘要,返回 FAILED。 |
1232+| Operator handle 已失效 | Python 抛 `RuntimeError`。 |
1233+| bridge/Python package 版本不匹配 | 初始化失败并报告两侧版本。 |
1234+| Finalize/Register 失败 | coordinator 进入 FAILED,不继续解析。 |
1235+ 
1236+错误设计参考 PyTorch 的阶段化导出错误和 TensorRT 的节点级 `ParserError`。GE 不新增首轮公开异常类,但必须补充模块、origin type、节点和 callback 上下文。错误应能区分模块发现、descriptor 校验、注册冲突、Node 转换和 callback 执行阶段。
1237+ 
1238+### 8.4 安全检查
1239+ 
1240+- 不暴露裸指针、handle 数值或 Python 对象地址;
1241+- 不递归扫描插件目录;
1242+- 不执行模型属性中的代码;
1243+- 日志不打印 Tensor 内容或完整模型数据;
1244+- traceback 过滤内部地址和敏感路径;
1245+- callback 和 setter 热路径不打印逐次 INFO/ERROR;
1246+- 所有跨 ABI 异常在 bridge 内转换。
1247+ 
1248+编码检查:
1249+ 
1250+| 检查项 | 是否涉及 | 结论 |
1251+|---|---|---|
1252+| 资源生命周期 | 是 | descriptor 进程级,OnnxNode/Operator callback 级;需要 handle 失效专项测试。 |
1253+| 新线程 | 否 | bridge 不创建线程,使用调用方线程并获取 GIL。 |
1254+ 
1255+### 8.5 兼容性
1256+ 
1257+- 未配置 Python plugin 时不导入用户模块,现有 C++ parser 行为不变;
1258+- 不修改现有 C++ plugin API、registry key 和 callback 签名;
1259+- 固定端口注册只增加不改变对象布局的 public 访问入口,不修改 graph C++ 二进制 ABI、AscendIR 和 OM 格式;
1260+- Python 生成标准 GE Operator,老 OM 在新版本下执行不受影响;
1261+- Python plugin 不承诺在没有 bridge 的老版本 GE 中使用;
1262+- 新增 `ge.graph.Operator` 不改变现有 Node、Graph、TensorDesc 语义;
1263+- atc、session 和 online 入口分别验证,不能假设共用同一初始化函数。
1264+ 
1265+### 8.6 特性交叉分析
1266+ 
1267+| 场景 | 适用性 | 分析说明 |
1268+|---|---|---|
1269+| 静态 Shape | 适用 | parser 生成标准 Operator,后续静态编译、内存和执行流程不变。 |
1270+| 动态 Shape | 适用 | 首轮不修改 TensorDesc,动态维按现有流程进入推导,不涉及 runtime/v2。 |
1271+| 动态 Shape 静态子图 | 适用 | 特性发生在 parser 前端,不新增 v2 到 v1 数据,不修改 DavinciModelCreate。 |
1272+| 离线场景 | 适用 | atc 是主要入口,增加 Python bootstrap 和 callback 时间,OM 格式不变。 |
1273+| 在线场景 | 适用 | 仅在线入口调用 ONNX parser 时生效,需要验证真实 session 初始化锚点。 |
1274+ 
1275+### 8.7 性能
1276+ 
1277+模型编译新增耗时来自:
1278+ 
1279+- 路径扫描和模块导入;
1280+- descriptor 校验和注册;
1281+- NodeProto 扁平化;
1282+- Python callback;
1283+- GIL 获取和 Python/C++ 值转换。
1284+ 
1285+至少观测:
1286+ 
1287+- 未配置 Python plugin 时的 parser 初始化回归;
1288+- 单插件和多插件导入耗时;
1289+- 单节点和多节点 callback 平均耗时;
1290+- 多模型连续编译时 registry 复用效果;
1291+- descriptor 数量与 Host 常驻内存增长关系。
1292+ 
1293+Python 对象和插件代码不进入 OM,也不进入模型执行路径,因此不增加 Device 内存和执行态耗时。性能阈值由性能责任人在开发前冻结;没有阈值时只记录观测数据。
1294+ 
1295+## 9. 测试策略
1296+ 
1297+### 9.1 测试边界
1298+ 
1299+| 层次 | 输入 | 输出检查 |
1300+|---|---|---|
1301+| Python UT | descriptor、OnnxNode、Operator 参数 | 冻结 descriptor、异常类型和错误信息。 |
1302+| parser flattener UT | NodeProto | 扁平字段、属性类型和失败状态。 |
1303+| bridge UT | 扁平 Node 值、Operator、Python callback | C++ Operator 状态、Status、handle 失效。 |
1304+| parser ST | ONNX 模型和 Python plugin | 最终 Graph 中 Operator 属性、端口和失败诊断。 |
1305+| online ST | session/aclgrphParseONNX | 初始化时序、幂等和多模型行为。 |
1306+| 接口 Sample | 测试专用 plugin.py 和 ONNX 模型生成器 | 按用户方式执行,检查 Graph、错误和 atc 黑盒结果。 |
1307+ 
1308+首轮不验证 compiler、runtime 或设备数值执行。
1309+ 
1310+竞品扩展接口均提供独立验证能力。PyTorch 使用导出结果验证,Torch-TensorRT 使用 eager 与 TensorRT 对拍。GE 首轮对应提供 descriptor/bridge 分层 UT、真实 parser ST 和 Elu/Sum 接口 Sample。
1311+ 
1312+### 9.2 测试用例
1313+ 
1314+| 测试类别 | 关键测试项 | 测试方法 | 用例类型 |
1315+|---|---|---|---|
1316+| 功能 | descriptor 合法字段 | 构造 source/domain/opsets/target/imply_type。 | Python UT |
1317+| 功能 | Elu alpha 默认值和覆盖 | 测试专用 origin 执行等价 callback。 | parser ST |
1318+| 功能 | Sum 动态输入 | 0、1、多个输入。 | bridge UT + parser ST |
1319+| 功能 | NodeProto 标量和列表属性 | 覆盖 f/i/s/floats/ints/strings。 | flattener UT + bridge UT |
1320+| 功能 | Operator 属性 | bool/int/float/str/list。 | Python UT + bridge UT |
1321+| 功能 | Operator 固定端口 | required、optional 输入和普通输出注册。 | bridge UT + parser ST |
1322+| 异常 | Tensor/Graph/SPARSE、UNDEFINED、未知枚举 | 合成 NodeProto。 | flattener UT |
1323+| 异常 | `ref_attr_name` 非空 | 合成属性引用。 | flattener UT |
1324+| 异常 | callback 异常或返回非 None | 合成 Python callback。 | bridge UT |
1325+| 生命周期 | callback 后保存 Operator | 回调结束后调用 getter/setter。 | bridge UT |
1326+| 冲突 | Python/Python opset 重叠 | 注册重叠 descriptor。 | Python UT |
1327+| 冲突 | C++/Python 同 key | 先注册 C++,再预检查 Python。 | bridge UT + parser ST |
1328+| 冲突 | ParseParamsFn 与 ByOperator map 冲突 | 预置 ByOperator callback。 | bridge UT |
1329+| 注册 | 无 target creator | 验证 Finalize 后 CreateOpParser 成功。 | parser ST |
1330+| 注册 | 已有 target creator | 验证首轮拒绝且不覆盖。 | parser ST |
1331+| 注册 | Finalize/Register 失败 | 验证 coordinator 进入 FAILED。 | bridge UT |
1332+| loader | custom_op/ONNX 同文件 | 验证一次 import 和双 registry 收集。 | parser/runtime ST |
1333+| 初始化 | atc、session plugin manager、AclParserInitialize | 分别验证首次查询前注册和幂等。 | parser ST + online ST |
1334+| ABI | bridge/Python 版本不匹配 | 故意使用不同版本。 | bridge UT |
1335+| 并发 | 多模型并发初始化 | 并发触发同一 registry。 | parser ST |
1336+| 兼容性 | 无 Python plugin | 环境变量为空。 | parser ST |
1337+| 性能 | 无、单、多插件初始化 | 记录时间和 Host 内存。 | 性能 ST |
1338+| 精度 | 不适用 | 本特性不执行数值计算。 | 不适用 |
1339+| 特性交叉 | 静态/动态 Shape ONNX | 比较解析后 TensorDesc 和图结构。 | parser ST |
1340+ 
1341+### 9.3 测试框架
1342+ 
1343+复用 pytest、现有 graph Python UT、parser gtest 和 parser ST,不新增测试框架。测试 plugin 放在测试资源目录,通过测试专用 `ASCEND_CUSTOM_OPP_PATH` 加载,避免污染开发环境。
1344+ 
1345+## 10. 开发计划和门禁
1346+ 
1347+### 10.1 迭代计划
1348+ 
1349+| 阶段 | 内容 | 完成标志 |
1350+|---|---|---|
1351+| 接口冻结 | 5 个公开符号、全部成员、`__all__`、类型声明、Operator handle、初始化锚点、bridge ABI/GIL。 | 公开接口和专项设计评审通过,资料及 ST Sample 目录确认。 |
1352+| 迭代 1 | loader、descriptor、coordinator、ParseParamsFn、OnnxNode、Operator 基础端口注册。 | Elu/Sum 及混合端口 UT/ST 通过。 |
1353+| 迭代 2 | ParseParamsByOperatorFn、ParseOpToGraphFn、TensorDesc、ES、ThresholdedRelu。 | 完整 ThresholdedRelu 示例和 ST 通过。 |
1354+| 迭代 3 | SubgraphPost、稀疏属性、属性引用、特殊输入处理。 | 真实迁移用例和对应测试通过。 |
1355+ 
1356+### 10.2 编码前门禁
1357+ 
1358+| 决策项 | 当前状态 | 阻塞内容 |
1359+|---|---|---|
1360+| Operator 方法准确命名 | 待接口评审 | Operator Python/C API 编码。 |
1361+| Operator handle 所有权 | 待接口评审 | bridge handle 编码。 |
1362+| 完整公开面和资料 | 待接口评审 | onnx_plugin、ImplyType、OnnxNode、OnnxPlugin、Operator、`__all__`、`.pyi`、API 资料和 ST Sample。 |
1363+| `ASCEND_CUSTOM_OPP_PATH` 复用 | 待 SE 评审 | 复用现有路径和 loader 的范围、时序、共存、回归及性能语义。 |
1364+| atc/session 初始化锚点 | 待专项验证 | coordinator 接入。 |
1365+| bridge SO、ABI 和安装归属 | 待专项评审 | bridge 构建和打包。 |
1366+| GIL 和多模型并发 | 待专项评审 | callback 和初始化实现。 |
1367+| ThresholdedRelu 延期 | 待需求方和 SE 确认 | 迭代 1 启动。 |
1368+| 性能阈值 | 待性能责任人确认 | 性能验收,不阻塞功能编码。 |
1369+ 
1370+## 11. 验收标准
1371+ 
1372+1. 插件开发者可以使用 `onnx_plugin(...)``parse_node` 编写 Elu 等价插件。
1373+2. Python plugin 可以从 `ASCEND_CUSTOM_OPP_PATH` 稳定发现,同一模块只 import 一次。
1374+3. atc、session 和 aclgrphParseONNX 都能在首次 parser 查询前完成 creator 和 callback 注册。
1375+4. NodeProto 基础字段和六类常用属性可以正确转换,未支持类型明确失败。
1376+5. callback 可以直接通过 `ge.graph.Operator` 设置属性,并注册 required、optional、普通 output 和默认动态端口。
1377+6. 公开 API 不包含 PluginContext、OperatorView、OperatorUpdate、TensorDescUpdate、DynamicPort 和 ExpandResult。
1378+7. Elu ST 验证 alpha 默认值与覆盖,Sum ST 验证动态输入数量。
1379+8. callback 异常后当前 Operator 不加入 Graph,保存的 Operator 引用不可继续使用。
1380+9. C++/Python 跨 creator、跨 callback map 冲突均拒绝 Python且有明确诊断。
1381+10. custom_op 与 ONNX plugin 可以一次 import 后分别进入各自 registry。
1382+11. 未配置 Python plugin 时,现有 C++ ONNX parser 行为不变。
1383+12. 首轮不修改 graph C++ ABI、AscendIR、OM、compiler 和 runtime。
1384+13. Python UT、bridge UT、parser ST、文档检查和静态检查全部通过。
1385+14. 完成性能观测;存在冻结阈值时必须达到阈值。
1386+15. 首轮全部公开接口、构造限制、`__all__` 和类型声明完成接口评审;API Reference、用户指南、迁移指南及 ST Sample 与实现一致。
1387+16. `ASCEND_CUSTOM_OPP_PATH` 复用经过 SE 评审,明确路径扫描、初始化时序、插件共存、无插件回归和性能影响。
1388+ 
1389+## 附录 A:源码证据索引
1390+ 
1391+| 主题 | 源码位置 |
1392+|---|---|
1393+| atc plugin 注册 | `api/atc/main_impl.cc:1284-1327` |
1394+| session plugin 加载 | `api/session/common/plugin/tbe_plugin_manager.cc:160-178` |
1395+| ACL parser 初始化 | `parser/parser/common/acl_graph_parser_util.cc:247-253` |
1396+| ONNX parser 入口 | `parser/parser/onnx/onnx_parser.cc:58-91` |
1397+| origin type 构造 | `parser/parser/onnx/onnx_parser.cc:412-451` |
1398+| AdapterOpType | `parser/parser/onnx/onnx_parser.cc:453-480` |
1399+| ParseOpParam 和 Graph::AddOp | `parser/parser/onnx/onnx_parser.cc:596-676` |
1400+| parser creator 注册 | `parser/parser/common/op_registration_tbe.cc:40-142` |
1401+| framework adapter creator | `parser/parser/onnx/onnx_custom_parser_adapter.cc:55` |
1402+| OpRegistry::Register | `graph_metadef/register/register.cpp:1050-1092` |
1403+| callback 查询 | `inc/graph_metadef/register/op_registry.h:59-82` |
1404+| ParseOpToGraph 查询 | `parser/parser/common/parser_utils.cc:42-83` |
1405+| ONNX AttributeType | `graph_metadef/proto/onnx/ge_onnx.proto:101-116` |
1406+| ref_attr_name | `graph_metadef/proto/onnx/ge_onnx.proto:121-125` |
1407+| Operator name/type | `inc/graph_metadef/external/graph/operator.h:110-118` |
1408+| Operator TensorDesc | `operator.h:146-182,527` |
1409+| Operator 属性 | `operator.h:229-315` |
1410+| Operator 固定端口(当前 protected) | `operator.h:589-609` |
1411+| Operator 动态端口 | `operator.h:612,618` |
1412+| Python AttrValue | `api/python/ge/ge/graph/_attr.py:273-304` |
1413+| Python loader | `api/python/ge/ge/_internal/plugin_loader.py:26-100` |
1414+| `ASCEND_CUSTOM_OPP_PATH` | `api/python/ge/ge/custom_op/bootstrap.py:21``runtime/custom_op/python_custom_op_bridge_loader.cc:126-134``graph_metadef/base/common/plugin/plugin_manager.cc:461,573-605` |
1415+| custom_op bridge loader | `runtime/custom_op/python_custom_op_bridge_loader.cc:124-167` |
1416+| ES GraphBuilder.create_input | `api/python/ge/ge/es/graph_builder.py:177-199` |
1417+| ES build_and_reset | `api/python/ge/ge/es/graph_builder.py:938-959` |
1418+| ES entry points | `api/python/ge/ge/es/__init__.py:23-40` |
1419+ 
1420+## 附录 B:接口检查结果
1421+ 
1422+| 检查项 | 是否涉及 | 结论 |
1423+|---|---|---|
1424+| 公开接口评审 | 是 | onnx_plugin、ImplyType、OnnxNode、OnnxPlugin、Operator 及其全部公开成员必须评审。 |
1425+| 接口资料 | 是 | 增加 API Reference、用户指南、迁移指南、类型提示和 Elu/Sum/ST Sample。 |
1426+| 原型、返回值、异常 | 是 | 本文给出目标原型,编码前按评审结果冻结。 |
1427+| 现有接口行为 | 是 | 内部 parser/session 初始化增加 bootstrap;无 Python plugin 时行为不变。 |
1428+| 新老组件组合 | 是 | bridge、Python package 和 parser 版本必须匹配。 |
1429+| 调用时序 | 是 | 注册早于首次 factory/registry 查询,Operator 只在 callback 期有效。 |
1430+| 非法调用报错 | 是 | descriptor、属性、handle 和 callback 都有明确错误。 |
1431+| 独立测试 | 是 | Python UT、bridge UT、parser ST、atc/online 系统测试和接口使用 Sample。 |
1432+ 
1433+## 附录 C:官方设计文档检查结果
1434+ 
1435+- [x] 已按 `docs/zh/design/design_document_template.md` 覆盖目的、范围、总体概述、功能需求、非功能需求、性能、接口、软件设计、安全、兼容、DT 和验收标准。
1436+- [x] 已按 `docs/zh/design/cross_feature_check.md` 分析静态 Shape、动态 Shape、动态 Shape 静态子图、离线和在线五类场景。
1437+- [x] 已加载 `docs/zh/design/modules/graph_metadef/ascend-ir.md`,方案复用 Operator、Attr、TensorDesc 和 Graph,不修改 AscendIR 对象模型和序列化。
1438+- [x] 已加载 `docs/zh/design/constraints/graph_metadef.md`,方案遵循稳定性、最小依赖、向后兼容、关注点分离和单线程修改约束。
1439+- [x] 独立 bridge SO 不向 graph_metadef 引入 Python 依赖。
1440+- [x] 动态 Shape 静态子图不新增 v2 到 v1 数据,不修改 DavinciModelCreate 接口。
1441+- [x] 离线 atc 和在线 session 分别设计初始化入口,OM 格式不变。
1442+- [x] 精度测试不适用,因为本特性只转换属性、端口和图结构,不执行数值计算。
1443+ 
1444+## 附录 D:PR review 闭环
1445+ 
1446+| PR review 关注点 | 当前处理 |
1447+|---|---|
1448+| Operator 不要过度封装 | 使用 `ge.graph.Operator` 直接 setter。 |
1449+| 不需要 OperatorView/Update | 删除 View、Update、Patch 类公开设计。 |
1450+| 不需要 DynamicPort | 直接提供动态输入输出方法。 |
1451+| NodeProto 类型识别在 C++ 完成 | 使用 parser-side flattener,Python 不处理 dynamic_cast。 |
1452+| bridge 使用独立 SO | bridge 不编入 graph_metadef。 |
1453+| ES 优先 | 迭代 2 复用 ES,不新增字符串型万能工厂。 |
1454+| C++ 注册优先 | coordinator 预检查并拒绝冲突 Python descriptor。 |
1455+| 调查全部 callback | 按 175/7/74/1 的调用量分期。 |
1456+| 用户示例前置 | Elu/Sum 示例放在接口和使用方式章节。 |
1457+| 分迭代交付 | 首轮 ParseParamsFn,迭代 2 Graph/ByOperator,迭代 3 Subgraph。 |
1458+ 
1459+## 附录 E:竞品接口证据索引
1460+ 
1461+### E.1 调研基线
1462+ 
1463+| 项目 | 本地仓库 | 基线 |
1464+|---|---|---|
1465+| PyTorch | `/mnt/workspace/gitCode/gentle-knight/pytorch` | commit `05dab25851a8b0b2925aed717dc010ddc643280c` |
1466+| Torch-TensorRT | `/mnt/workspace/gitCode/gentle-knight/TensorRT` | commit `8ab7654642750fab50101e04f0407604dca8014e` |
1467+| NVIDIA TensorRT | `/mnt/workspace/gitCode/gentle-knight/TensorRT-core` | commit `a892d22267d9cd2dedc1a0893e6892ac901f6d3d`,VERSION `11.1.0.106` |
1468+ 
1469+### E.2 PyTorch
1470+ 
1471+| 主题 | 证据位置 |
1472+|---|---|
1473+| `torch.onnx.export` | `torch/onnx/__init__.py:65-102` |
1474+| `custom_translation_table` 注入 | `torch/onnx/_internal/exporter/_compat.py:149-159` |
1475+| `ONNXRegistry` | `torch/onnx/_internal/exporter/_registration.py:137-303` |
1476+| `symbolic``symbolic_multi_out` | `torch/onnx/ops/__init__.py:73-279` |
1477+| symbolic 用户文档 | `docs/source/onnx_ops.md:7-16` |
1478+| legacy symbolic | `torch/onnx/_internal/torchscript_exporter/utils.py:1842-1887` |
1479+| legacy 弃用说明 | `docs/source/onnx.md:108-117` |
1480+| `ONNXProgram` | `torch/onnx/_internal/exporter/_onnx_program.py:204-489` |
1481+| 测试和验证 | `torch/onnx/_internal/exporter/_testing.py:18-102``_verification.py:148-212` |
1482+| 阶段化错误 | `torch/onnx/_internal/exporter/_core.py:79-100` |
1483+ 
1484+### E.3 Torch-TensorRT
1485+ 
1486+| 主题 | 证据位置 |
1487+|---|---|
1488+| plugin 路径总览 | `docsrc/tutorials/extensibility/plugins/plugins.rst:6-47` |
1489+| converter 生成 | `docsrc/tutorials/extensibility/plugins/plugins.rst:60-114` |
1490+| converter 调试 | `docsrc/tutorials/extensibility/plugins/plugins.rst:127-148` |
1491+| `cuda_kernel_op``ptx_op` | `py/torch_tensorrt/kernels/_ops.py:29-183` |
1492+| `SameAs``ReduceDims` | `py/torch_tensorrt/kernels/_dsl.py:18-45` |
1493+| 注册校验 | `py/torch_tensorrt/kernels/_validation.py:22-142` |
1494+| 自动 descriptor | `py/torch_tensorrt/dynamo/conversion/plugins/_generate_plugin.py:196-270` |
1495+| 自动 converter | `py/torch_tensorrt/dynamo/conversion/plugins/_generate_plugin_converter.py:74-180` |
1496+| `custom_op` | `py/torch_tensorrt/dynamo/conversion/plugins/_custom_op.py:13-51` |
1497+| eager/TensorRT 对拍 | `tests/py/dynamo/conversion/harness.py:361-410` |
1498+ 
1499+`register``impl``aot_impl``autotune` 来自核心 TensorRT 的 `tensorrt.plugin`
1500+Torch-TensorRT 提供生成和 converter 接入层。
1501+ 
1502+### E.4 NVIDIA TensorRT
1503+ 
1504+| 主题 | 证据位置 |
1505+|---|---|
1506+| ONNX Parser 接口 | `include/NvOnnxParser.h:189-440` |
1507+| Parser Python 绑定 | `python/src/parsers/pyOnnx.cpp:174-208` |
1508+| ParserError 绑定 | `python/src/parsers/pyOnnx.cpp:221-253` |
1509+| QDP 用户说明 | `samples/python/quickly_deployable_plugins/README.md:22-73` |
1510+| 数据依赖 shape | `samples/python/quickly_deployable_plugins/README.md:121-146` |
1511+| ONNX custom node 约定 | `samples/python/quickly_deployable_plugins/README.md:162-183` |
1512+| AOT ONNX 属性 | `samples/python/quickly_deployable_plugins/README.md:239-265` |
1513+| QDP 装饰器 | `python/packaging/bindings_wheel/tensorrt/plugin/_lib.py:371-695` |
1514+| TensorDesc、ShapeExpr | `python/packaging/bindings_wheel/tensorrt/plugin/_tensor.py:226-363,639-937` |
1515+| `size_tensor``from_shape_expr` | `python/packaging/bindings_wheel/tensorrt/plugin/_top_level.py:99-129` |
1516+| QDP 类型校验 | `python/packaging/bindings_wheel/tensorrt/plugin/_validate.py:30-168` |
1517+| Plugin Registry | `python/src/infer/pyPlugin.cpp:3836-3866` |
1518+| Network Plugin | `python/src/infer/pyGraph.cpp:100-137,1113-1118` |
1519+ 
1520+ONNX Parser 的 C++ 实现在未检出的子模块中。
1521+本文使用公开头文件、Python 绑定和官方示例作为接口证据。
@@ -0,0 +1,975 @@
1+# ONNX Plugin Python 化技术方案
2+ 
3+> 文档状态:PR 2 评审后首轮开发计划稿;公开接口编码前仍需专项评审。
4+>
5+> 需求来源:[ONNX Plugin Python 化需求分析](../requirements/onnx_plugin_python_requirement_analysis.md)
6+>
7+> 修订依据:[PR 2 评审决策与开发计划修订要求](onnx_plugin_python_pr_2_revision_requirements.md)
8+>
9+> 竞品证据:[ONNX Plugin Python 化竞品调研](onnx_plugin_python_competitor_research.md)
10+ 
11+## 开发者快速入口
12+ 
13+## 首轮用户界面
14+ 
15+插件开发者只需要声明映射并实现 `parse_node`。以下 Elu 示例覆盖首轮最核心的注册、ONNX 属性读取和 GE Operator 写入:
16+ 
17+```python
18+from ge.graph import Operator
19+from ge.onnx_plugin import ImplyType, OnnxNode, onnx_plugin
20+ 
21+ 
22+elu = onnx_plugin(
23+ source="Elu",
24+ domain="ai.onnx",
25+ opsets=range(8, 19),
26+ target="Elu",
27+ imply_type=ImplyType.TVM,
28+)
29+ 
30+ 
31+@elu.parse_node
32+def parse_elu(node: OnnxNode, target: Operator) -> None:
33+ alpha = node.attrs.get("alpha", 1.0)
34+ if not isinstance(alpha, float):
35+ alpha = 1.0
36+ target.set_attr("alpha", alpha)
37+```
38+ 
39+用户不接触 protobuf、C++ 指针、`Status``OpRegistrationData``OpParserFactory``OpRegistry`。动态输入示例及完整行为见[整体介绍](#整体介绍),公开接口草案见[接口设计](#接口设计)。
40+ 
41+## 能力在 Python 中的体现
42+ 
43+本表直接呼应原需求中“结合 GE 和 metadef,需要重点分析和补齐以下接口”。“延后”表示 Python 接口尚未冻结,不代表删除总体需求。
44+ 
45+| 原需求能力 | GE/metadef C++ 接口 | Python 用户界面 | bridge/框架体现 | 阶段 |
46+|---|---|---|---|---|
47+| 插件注册描述 | `REGISTER_CUSTOM_OP``OpRegistrationData` | `onnx_plugin(source, domain, opsets, target, imply_type)` | descriptor 展开为 framework、origin type、target 和 imply type | 首轮 |
48+| 注册表接入 | `OpRegistrationTbe::Finalize``OpParserFactory``OpRegistry::Register` | 无用户接口,导入插件后自动注册 | coordinator 预检查 creator/全部 callback map,再按 Finalize→Register 提交 | 首轮 |
49+| ONNX 输入 | `ge::onnx::NodeProto``AttributeProto` | `OnnxNode.name/op_type/inputs/outputs/attrs` | parser 侧扁平化,bridge 不直接依赖私有 protobuf ABI | 首轮常用字段/类型 |
50+| ParseParams 回调 | `ParseParamFunc(const Message *, Operator &)` | `@plugin.parse_node`,签名为 `(OnnxNode, Operator) -> None` | C++ wrapper 获取 GIL、调用 Python、将异常转换为 FAILED | 首轮 |
51+| ParseOpToGraph 回调 | `ParseOpToGraphFunc(const Operator &, Graph &)` | 目标形态 `@plugin.expand`,返回 `ge.graph.Graph` | 直接注册 `parse_op_to_graph_fn_map_`,不经过 custom parser adapter | 迭代 2,接口待评审 |
52+| GE Operator 属性 | `Operator::GetAttr/SetAttr` | `target.get_attr/set_attr` | callback-bound `ge.graph.Operator` 复用 `_AttrValue` 转换 | 首轮常用类型 |
53+| required/optional 输入和普通输出 | `InputRegister/OptionalInputRegister/OutputRegister` | `target.register_input/register_optional_input/register_output` | bridge 校验 name 后直接修改当前 target | 首轮 |
54+| 动态输入输出 | `DynamicInputRegister/DynamicOutputRegister` | `target.register_dynamic_input/output` | bridge 校验 name/count 后直接修改当前 target | 首轮默认重载 |
55+| TensorDesc 修改 | `Get/UpdateInputDesc``Get/UpdateOutputDesc` | 复用 `ge.graph.TensorDesc`;Operator getter/update 命名待评审 | 必须先冻结关联 handle、更新同步和失效语义 | 迭代 2 |
56+| GE 子图构建 | `Graph::SetInputs/SetOutputs` | 复用 `ge.es.GraphBuilder` 构建并返回 Graph | `ParseOpToGraphFn` wrapper 接管 Graph | 迭代 2 |
57+| GE IR op 创建 | `op::Data/Identity/Threshold/Mul/...` | 复用 ES 生成式算子,不增加字符串型万能工厂 | Python ES 生成式调用进入已有 GraphBuilder C API | 迭代 2 |
58+ 
59+每个字段、重载、类型和源码位置见[附录 A:C++ 到 Python 详细映射](#附录-ac-到-python-详细映射)。首轮支持矩阵见[范围](#范围),未支持项的失败行为见[错误处理](#错误处理)。
60+ 
61+## 框架主链
62+ 
63+```text
64+ASCEND_CUSTOM_OPP_PATH/plugin.py
65+ -> plugin_loader 单次导入并冻结 Python descriptors
66+ -> registration coordinator 预检查 creator/callback maps
67+ -> OpRegistrationTbe::Finalize 注册 OpParserFactory creator
68+ -> OpRegistry::Register 写入 callback/run-mode maps
69+ -> ONNX parser 创建 OnnxCustomParserAdapter
70+ -> parser 侧扁平化 NodeProto
71+ -> bridge 调用 parse_node(OnnxNode, ge.graph.Operator)
72+ -> 标准 GE Operator 加入 Graph
73+```
74+ 
75+该方案复用现有 parser registry,不建立 Python 专用查找旁路。注册细节见[注册流程](#注册流程),对象边界见[关键数据结构](#关键数据结构)。
76+ 
77+## 原需求分析点与计划追踪
78+ 
79+| 原需求项目 | 本方案结论 | 当前状态 | 详细位置 |
80+|---|---|---|---|
81+| Python 注册链如何接入 | 统一 loader + registration coordinator;同时闭合 creator 和 callback map | 设计已明确,初始化锚点待编码验证 | [插件描述与注册](#插件描述与注册)、[注册流程](#注册流程) |
82+| GraphBuilder 最小集合 | 复用 ES 的 Data/Input、Identity、Threshold、Mul 和 Graph 输出能力,不新增万能工厂 | 调整到迭代 2,准确签名待核验 | [后续迭代](#后续迭代) |
83+| 新增/修改 Python 模块和接口全集 | 公开层只增加 `ge.onnx_plugin``ge.graph.Operator`,内部增加 registry/bootstrap/bridge/coordinator | 首轮清单已列出,方法名待接口评审 | [新增修改接口描述](#新增修改接口描述)、[对子模块的修改](#对子模块的修改) |
84+| Python/C++ 对象生命周期 | descriptor 进程级,OnnxNode/Operator callback 级;Operator 回调结束立即失效 | 设计已明确,handle 实现待评审 | [关键数据结构](#关键数据结构) |
85+| 计划 1:列出接口全集 | 以能力表、公开接口草案和附录 A 交付 | 本文已覆盖 | [接口设计](#接口设计)、[附录 A](#附录-ac-到-python-详细映射) |
86+| 计划 2:ThresholdedRelu 完整示例 | 保留总体目标;先由 Elu/Sum 验证首轮,ES 完整示例进入迭代 2 | 需需求方和 SE 确认延期 | [后续迭代](#后续迭代) |
87+| 计划 3:启动开发 | 先冻结 Operator 方法、初始化锚点、bridge ABI、GIL 和延期记录 | 未满足门禁前不启动编码 | [附录 C](#附录-c性能与内部验证项) |
88+ 
89+## 阅读导航
90+ 
91+| 读者关注点 | 推荐章节 |
92+|---|---|
93+| 插件怎么写、首轮能做什么 | [首轮用户界面](#首轮用户界面)、[整体介绍](#整体介绍) |
94+| C++ 能力如何落到 Python | [能力在 Python 中的体现](#能力在-python-中的体现)、[附录 A](#附录-ac-到-python-详细映射) |
95+| loader、注册和 callback 怎么运行 | [插件发现](#插件发现)、[流程设计](#流程设计) |
96+| 要改哪些模块 | [对子模块的修改](#对子模块的修改) |
97+| 生命周期、错误和并发 | [关键数据结构](#关键数据结构)、[错误处理](#错误处理)、[可靠性](#可靠性) |
98+| 首轮与后续迭代边界 | [范围](#范围)、[后续迭代](#后续迭代) |
99+| 测试和验收 | [DT设计](#dt设计)、[验收标准](#验收标准) |
100+ 
101+## 简介
102+ 
103+## 目的
104+ 
105+本文定义 ONNX Plugin Python 化首轮迭代的软件需求和技术方案。目标读者为 GE parser、GE Python、graph 基础结构和算子插件开发者。
106+ 
107+本文优先回答插件作者最关心的问题:
108+ 
109+1. 首轮 Python 插件如何编写;
110+2. 首轮支持哪些 parser 接口、属性和 Operator 操作;
111+3. Python 插件如何进入现有 ONNX parser 注册与调用链;
112+4. 哪些能力明确延后,以及延后能力是否会冲击首轮架构。
113+ 
114+## 范围
115+ 
116+首轮包括:
117+ 
118+- Python ONNX 插件描述和自动发现;
119+- `ParseParamsFn` 对应的 `parse_node` 回调;
120+- NodeProto 基础字段和常用标量/列表属性转换;
121+- `ge.graph.Operator` 首轮属性 setter、required/optional 输入、普通输出和默认动态输入输出注册;
122+- Python descriptor 到现有 `OpRegistrationData``OpParserFactory``OpRegistry` 的 coordinator/bridge;
123+- Python UT、bridge UT 和 parser ST。
124+ 
125+首轮不包括:
126+ 
127+- `ParseParamsByOperatorFn``ParseOpToGraphFn``ParseSubgraphPostFn`
128+- Tensor/Graph/稀疏属性、UNDEFINED、未知枚举和属性引用;
129+- TensorDesc 读写、输入删除/重排和动态端口高级重载;
130+- ES 图展开、source Operator 写回和子图后处理;
131+- 现有 C++ 插件改造、算子原型定义、Kernel、compiler、runtime、AscendIR 或 OM 格式修改。
132+ 
133+## 总体概述
134+ 
135+## 软件概述
136+ 
137+### 项目介绍
138+ 
139+当前 C++ ONNX 插件通过 `REGISTER_CUSTOM_OP` 构造 `OpRegistrationData``OpRegistrationTbe::Finalize` 从 framework 级 `CustomParserAdapterRegistry` 获取已静态注册的 adapter creator,再按 target Operator type 注册 `OpParserFactory` creator;随后 `OpRegistry::Register` 写入 callback map 和运行模式 map。ONNX parser 先通过 `OpParserFactory::CreateOpParser` 创建 parser,再由 `OnnxCustomParserAdapter` 查找并调用 callback。Python 方案必须同时闭合 creator 和 callback 两条注册链,不能只写入 `OpRegistry`
140+ 
141+本需求不建立第二套 parser registry。Python 插件最终仍被包装成现有 C++ callback 并注册到现有 `OpRegistry`,因此 parser 的查找键、调用位置和失败路径保持不变。
142+ 
143+基于 ops-nn `c1aaaeecb180fa3593175936063defb66b63af01` 和 ops-math `847219162293528c164154ffa8a22ec8b34a50aa` 的本地注册调用扫描,合计为:
144+ 
145+| 回调类型 | 注册调用合计 | 迭代安排 |
146+|---|---:|---|
147+| `ParseParamsFn` | 175 | 迭代 1 |
148+| `ParseParamsByOperatorFn` | 7 | 迭代 2 |
149+| `ParseOpToGraphFn` | 74 | 迭代 2 |
150+| `ParseSubgraphPostFn` | 1 | 迭代 3 |
151+ 
152+统计表示注册调用次数,不等同于算子数。首轮选择调用量最高且链路最短的 `ParseParamsFn`,不以“完整复制 C++ 公共接口”为交付目标。
153+ 
154+### 产品环境介绍
155+ 
156+Python 插件位于 ONNX parser 前端,只在模型解析和编译阶段运行:
157+ 
158+```text
159+自定义 OPP/plugin.py
160+ |
161+ v
162+ge.onnx_plugin descriptor registry
163+ |
164+ v
165+parser-side registration coordinator
166+ | preflight creator/callback maps
167+ v
168+OpRegistrationTbe::Finalize -> OpParserFactory creator
169+ |
170+ v
171+OpRegistry::Register -> callback maps
172+ |
173+ONNX NodeProto -> OnnxCustomParserAdapter -> Python bridge
174+ |
175+ v
176+Python parse_node(OnnxNode, ge.graph.Operator)
177+ |
178+ v
179+标准 GE Operator -> Graph -> 后续 compiler/runtime
180+```
181+ 
182+算子原型必须由算子仓预先注册。Python ONNX 插件只转换前端节点,不定义 target 原型,也不实现 Kernel。
183+ 
184+### 初始化路径与注册时序
185+ 
186+Python 模块发现可以复用 `ASCEND_CUSTOM_OPP_PATH` 和 canonical-path import cache,但 ONNX plugin 的 C++ 注册必须分别覆盖以下入口,不能把 `AclParserInitialize` 当成唯一入口:
187+ 
188+| 场景 | 真实入口 | 现有注册行为 | Python coordinator 要求 |
189+|---|---|---|---|
190+| atc 离线编译 | `api/atc/main_impl.cc:1284-1327``LoadCustomOpLib` | 遍历 `registrationDatas`,调用 `Finalize``Register` | 在 `ParseGraph` 前完成 Python descriptor 发现、预检查和提交 |
191+| session 插件加载 | `api/session/common/plugin/tbe_plugin_manager.cc:160-178` | 先调用 `OpRegistry::Register`,不调用 `Finalize` | 不在此阶段假设 parser creator 已完成;只复用一次 Python import 和 descriptor 收集 |
192+| session ONNX 解析 | `parser/parser/common/acl_graph_parser_util.cc:247-253``AclParserInitialize` | 再调用 `Finalize``Register`,现有返回值被忽略 | 在首次 `OpParserFactory`/`OpRegistry` 查询前完成 coordinator,并检查返回值 |
193+| aclgrphParseONNX | `parser/parser/onnx/onnx_parser.cc:58-91` | 通过 `std::call_once` 调用 parser 初始化 | 保证 Python bootstrap 与一次性初始化语义一致 |
194+ 
195+atc 与 session 的入口可能对同一 registration data 产生不同阶段的 `Register` 调用,因此 coordinator 必须具备进程级幂等状态,不能依赖底层 `Register(false)` 自动发现重复注册。
196+ 
197+### custom_op 与 ONNX plugin 的边界
198+ 
199+现有 `ge.custom_op` Python 加载链由 `runtime/custom_op/python_custom_op_bridge_loader.cc:124-167` 驱动,最终面向 `CustomOpRegistry/OpLibRegistry`;它不是 ONNX parser 的 `OpRegistrationTbe::Finalize -> OpParserFactory -> OpRegistry::Register` 链路。
200+ 
201+首轮只复用以下部分:
202+ 
203+- 同一个 Python interpreter;
204+- `ASCEND_CUSTOM_OPP_PATH` 的路径扫描规则;
205+- canonical-path module cache,保证同一文件只 import 一次。
206+ 
207+首轮不复用 custom_op 的 C++ 注册入口。ONNX plugin 必须由独立 parser coordinator 完成 creator 和 callback map 注册。同一 Python 文件可以声明两类 descriptor,但两类 descriptor 分别进入各自 registry,重复声明和重复提交必须单独报错。
208+ 
209+## 软件功能
210+ 
211+首轮提供以下功能:
212+ 
213+1. 用 Python 声明 source、domain、opset、target 和 imply type;
214+2.`ASCEND_CUSTOM_OPP_PATH` 发现插件模块;
215+3. 绑定一个 `parse_node` 回调;
216+4. 将 NodeProto 转为只含 Python 自然值的 `OnnxNode`
217+5. 将 callback 期 `ge::Operator &` 暴露为 `ge.graph.Operator`
218+6. 通过直接 setter 设置属性,并注册 required、optional、普通 output 和默认动态端口;
219+7. 将 Python 异常转换为 parser 失败,不跨越 C++ ABI。
220+ 
221+## 设计约束
222+ 
223+- `Operator` 位于 `ge.graph`,提供通用图基础能力,不感知 ONNX 业务。
224+- 不新增 `OperatorView``OperatorUpdate``TensorDescUpdate``DynamicPort` 或万能 context。
225+- `OnnxNode` 隐藏 protobuf 指针,只暴露回调需要的只读值;NodeProto 的读取和 proto ABI 处理留在 parser 组件。
226+- Python 不接触 `ge::Operator` 裸指针、C++ 状态码或 registry 对象。
227+- 现有 `ge._internal.plugin_loader` 负责扫描,不新增环境变量和递归规则。
228+- bridge SO 承担 Python 链接和调用,允许依赖现有 graph/registry 接口,但不得向 `graph_metadef` 反向引入 Python 依赖;bridge 不直接链接 GE 私有 protobuf 生成类型。
229+- 图基础结构沿用单线程修改模型;同一 Operator 不允许并发 setter。
230+- 未支持 callback 和类型不预留空实现,避免形成兼容负担。
231+ 
232+## 假设和依赖关系
233+ 
234+- 目标 Operator 原型和实现已安装并可被 GE 加载。
235+- parser 初始化阶段可在首次查询 `OpParserFactory`/`OpRegistry` 前完成 Python 插件注册;准确初始化锚点待编码前冻结,不能只绑定 `Finalize` 或只绑定 session 之外的路径。
236+- 独立 bridge SO 的构建、安装归属、Python 版本匹配和 GIL 策略需在编码前评审;NodeProto 由 parser 侧转换为扁平值后再跨 bridge 边界。
237+- `ge.graph._AttrValue` 现有 bool/int/float/str 和同类型 list 转换可复用。
238+- `ge.graph.Operator` 的方法名、handle 所有权和失效检查需通过公开接口评审。
239+- 性能阈值由 SE/性能责任人在开发前给出;未给出时性能项只做观测。
240+ 
241+## ONNX Plugin Python 化需求分析与设计
242+ 
243+## 整体介绍
244+ 
245+### 首轮用户示例:Elu
246+ 
247+Elu 是首轮主示例。现有 C++ 插件只注册 `ParseParamsFn`,读取一个 FLOAT 属性并调用 `SetAttr`,见 `ops-nn/activation/elu/framework/elu_onnx_plugin.cpp:15-42`。完整 Python 用户代码已放在[开发者快速入口](#首轮用户界面),本节只补充设计结论:用户只看到注册字段、Node 值和目标 Operator,不返回 update 对象,也不接触 protobuf、registry、bridge 或 C++ 状态码。
248+ 
249+生产环境同一个 origin key 只能由一个 C++ 或 Python 插件提供。为避免与已安装 Elu C++ 插件冲突,parser ST 使用测试专用 origin type 执行同等逻辑;Elu 代码作为真实迁移参考。
250+ 
251+### 动态输入示例:Sum
252+ 
253+Sum→AccumulateNV2 现有插件同样只使用 `ParseParamsFn`,并调用默认 `DynamicInputRegister`,见 `ops-math/math/accumulate_nv2/framework/sum_onnx_plugin.cpp:15-46`
254+ 
255+```python
256+sum_plugin = onnx_plugin(
257+ source="Sum",
258+ domain="ai.onnx",
259+ opsets=range(8, 19),
260+ target="AccumulateNV2",
261+ imply_type=ImplyType.TVM,
262+)
263+ 
264+ 
265+@sum_plugin.parse_node
266+def parse_sum(node: OnnxNode, target: Operator) -> None:
267+ input_count = len(node.inputs)
268+ if input_count < 1:
269+ raise ValueError("Sum requires at least one input")
270+ target.register_dynamic_input("x", input_count)
271+ target.set_attr("N", input_count)
272+```
273+ 
274+该示例直接调用 Operator 动态端口方法,不引入 `DynamicPort` 数据类。
275+ 
276+### 三类输入关系
277+ 
278+required、optional、dynamic 是 target 的 IR 端口类型。固定端口通常由 target 原型预先定义,callback 无需重复注册;首轮仍直接开放对应 `Operator` 注册方法,支持确实需要在解析期补充端口的 target。真实原型 `MultiAddRmsNormDynamicQuant` 同时包含三类输入:动态 `x1`、必选 `x2/gamma`、可选 `smooth_scale1/smooth_scale2`,见 `ops-nn/norm/multi_add_rms_norm_dynamic_quant/op_graph/multi_add_rms_norm_dynamic_quant_proto.h:70-83`
279+ 
280+该算子当前没有 ONNX plugin,因此不伪造可运行迁移样例。若未来为其增加映射,回调只需根据源节点实例数调用:
281+ 
282+```python
283+# 该 target 的 required/optional 端口已由原型定义;这里只创建动态 x1 实例。
284+target.register_dynamic_input("x1", dynamic_x1_count)
285+```
286+ 
287+首轮使用 Sum 验证动态实例注册,并使用合成 target 验证 required、optional、普通 output 和 dynamic 的直接注册;不再将固定端口注册延期到迭代 2。
288+ 
289+## 功能需求
290+ 
291+### 插件描述与注册
292+ 
293+1. 介绍
294+ 
295+`onnx_plugin(...)` 创建进程级描述对象。注册字段只声明一次,首轮描述对象只提供 `parse_node` 绑定入口。
296+ 
297+2. 输入
298+ 
299+| 字段 | 类型 | 约束 |
300+|---|---|---|
301+| `source` | str | 非空 ONNX op_type,不包含 domain/opset |
302+| `domain` | str | 非空;用于构造 GE origin key,标准 ONNX descriptor 使用 `ai.onnx` |
303+| `opsets` | Iterable[int] | 非空、正整数、不可重复 |
304+| `target` | str | 非空且 target 原型必须存在 |
305+| `imply_type` | ImplyType | 首轮支持 TVM/AI_CPU/CUSTOM/GELOCAL 枚举映射 |
306+ 
307+3. 处理
308+ 
309+- descriptor 将 `domain + opset + source` 展开为完整 origin type;NodeProto 原始 `domain` 可以为空,parser 会按 opset 上下文补齐 domain,不能要求模型字段一定填写 `ai.onnx`
310+- Python/Python 重复或 opset 重叠在 registry 内直接报错;
311+- parser-side registration coordinator 在任何写入前检查 `OpParserFactory` creator、`ParseParamsFn``ParseParamsByOperatorFn``ParseOpToGraphFn` 和 subgraph callback map;
312+- 预检查通过后按 `OpRegistrationTbe::Finalize` -> `OpRegistry::Register` 顺序提交;两者返回值必须检查,不能复用当前初始化代码中的忽略返回值行为;
313+ - 非 TensorFlow target 没有 creator 时由 `Finalize` 从 framework 级 `CustomParserAdapterRegistry` 获取已静态注册的 adapter creator,并完成 target `om_type``OpParserFactory` creator 注册;creator 已存在时首轮拒绝 Python target,后续需先确认 creator 是否为可复用的 ONNX custom adapter;
314+- C++/Python 任一 creator 或 callback map 冲突时拒绝 Python descriptor,保留 C++,输出包含 `(om_type, origin_type, callback/map)` 的诊断,不静默跳过;
315+- `OpRegistry::Register(..., is_custom_op=false)` 对已有 key 可能静默跳过并返回成功,不能用其返回值判断冲突;冲突诊断和拒绝必须由 coordinator 在调用 `Register` 前完成;
316+- descriptor 批次必须先全部预检查再提交;提交阶段任一步失败则终止 Python ONNX 初始化,不继续解析,并记录已提交状态,直到内部 rollback 或进程级清理机制冻结前不得宣称原子注册;
317+- 首轮 descriptor 未绑定 `parse_node` 时拒绝注册。
318+ 
319+4. 输出
320+ 
321+返回 `OnnxPlugin` 描述对象。用户不能直接读取或修改内部 registry。
322+ 
323+### 插件发现
324+ 
325+1. 介绍
326+ 
327+复用 `ge._internal.plugin_loader` 的扫描能力,从 `ASCEND_CUSTOM_OPP_PATH` 导入 Python 文件或 package。custom_op、pass 和 ONNX plugin 使用同一个进程级 Python runtime 和 canonical-path module cache。
328+ 
329+2. 输入
330+ 
331+环境变量中的路径列表。每项可以是 `.py` 文件或目录;目录只扫描当前层 `.py` 文件和含 `__init__.py` 的直接子目录。
332+ 
333+3. 处理
334+ 
335+- 模块身份按真实路径缓存,不按 plugin kind 生成第二份 module;同一文件允许一次 import 后向不同 registry 分发 descriptor;
336+- 保持排序导入,确保行为可复现;
337+- 插件文件导入异常使 Python ONNX 插件初始化失败,不静默跳过;
338+- loader 只负责一次 import,descriptor 校验和 C++ 注册由各插件 bootstrap 完成;custom_op 和 ONNX plugin 不得各自初始化第二个 Python interpreter;
339+- 首轮明确允许同一文件声明多类插件,但禁止同一 descriptor 重复注册;需要专项 ST 验证 loader import、custom_op bootstrap 和 parser coordinator 的初始化顺序,不把未核实的 `GraphManager::PreRun` 作为 parser 调用链事实。
340+ 
341+4. 输出
342+ 
343+已导入模块集合和已冻结 descriptor 快照。
344+ 
345+5. 环境变量评审门禁
346+ 
347+当前仓库确认使用 `ASCEND_CUSTOM_OPP_PATH`。ONNX plugin 复用该环境变量和共享 `ge._internal.plugin_loader` 的设计,必须在编码前与 SE 对齐:
348+ 
349+- 复用现有 OPP 路径,避免新增 ONNX plugin 专用环境变量和部署入口;
350+- 复用现有 `.py`/package 扫描、路径顺序和 canonical-path 一次 import 语义;
351+- 同一 Python 文件可同时声明 custom_op 和 ONNX plugin,加载一次后分别提交到各自 registry;
352+- atc、session 和 `aclgrphParseONNX` 的 bootstrap 时序必须分别验证;
353+- 环境变量为空或仅包含 C++ 插件时,现有行为和初始化性能必须回归验证。
354+ 
355+SE 未确认前,不新增第二个环境变量,不将复用方案标记为冻结。
356+ 
357+### NodeProto 转换
358+ 
359+1. 介绍
360+ 
361+parser 组件使用 GE 私有 protobuf 类型读取 NodeProto,再将字段转换为 bridge 可消费的扁平值结构;Python bridge 不直接依赖 `google::protobuf::Message *`、生成的 `ge::onnx::NodeProto` 或 GE 私有 protobuf ABI。
362+ 
363+2. 输入
364+ 
365+| NodeProto 内容 | Python 表示 | 首轮状态 |
366+|---|---|---|
367+| name | str | 支持 |
368+| parser 计算出的完整 origin type | str | 支持 |
369+| input/output | list[str] | 支持 |
370+| FLOAT/INT/STRING | float/int/str | 支持 |
371+| FLOATS/INTS/STRINGS | 同类型 list | 支持 |
372+| TENSOR(S)/GRAPH(S) | 无 | 明确报不支持 |
373+| SPARSE/未知枚举 | 无 | 明确报不支持 |
374+| `ref_attr_name` 非空 | 无 | 明确报不支持 |
375+ 
376+3. 处理
377+ 
378+- parser 在 C++ 侧确认 Message 类型并完成字段提取,不由 Python 执行 dynamic_cast;
379+- string 按 UTF-8 解码,失败时报告属性名称;
380+- attrs 以只读 Mapping 暴露,值使用 Python 自然类型;
381+- 遇到 `SPARSE_TENSOR(S)``UNDEFINED`、未知枚举或非空 `ref_attr_name` 时终止当前节点解析,不静默丢弃;当前 GE proto 不存在 `TYPE_PROTO(S)`,不将其列为 GE 属性类型。
382+ 
383+4. 输出
384+ 
385+单次 callback 有效的 `OnnxNode` 值对象。对象不持有 protobuf 指针。
386+ 
387+### parse_node 回调
388+ 
389+1. 介绍
390+ 
391+`parse_node(node, target) -> None` 对应 C++ `ParseParamFunc = Status(const Message *, Operator &)`
392+ 
393+2. 输入
394+ 
395+- `node: OnnxNode`:只读源节点值;
396+- `target: ge.graph.Operator`:当前正在解析、尚未加入 Graph 的目标 Operator。
397+ 
398+ONNX parser 的 `ParseOpParam` 会先查询 `ParseParamsByOperatorFn`,未命中后才进入 `ParseParamsFn`。首轮 coordinator 只注册 `ParseParamsFn`,并在预检查中确认 Python descriptor 不会写入 ByOperator map;否则已有 ByOperator callback 会优先执行,Python `parse_node` 不会被调用。
399+ 
400+3. 处理
401+ 
402+- bridge 获取 GIL 后调用 Python 函数;
403+- callback 直接执行 target setter;
404+- callback 返回值必须为 `None`
405+- Python 异常被 bridge 捕获并转换为 FAILED,错误包含模块、origin type、callback 和字段上下文;
406+- `ParseOpParam` 失败后 `ParseAllNodeProto` 返回,当前 target 不执行 `Graph::AddOp`,因此首轮不建立用户侧事务或回滚模型。
407+ 
408+4. 输出
409+ 
410+成功时 C++ wrapper 返回 SUCCESS,target 的修改进入后续 `Graph::AddOp`;失败时返回 FAILED,目标 Operator 被当前解析流程丢弃。
411+ 
412+### Operator 基础接口
413+ 
414+1. 介绍
415+ 
416+新增 `ge.graph.Operator`,表示 callback 期的 `ge::Operator`。它是 graph 通用基础对象,不包含 ONNX 专用字段。
417+ 
418+2. 输入
419+ 
420+首轮目标接口:
421+ 
422+```python
423+class Operator:
424+ @property
425+ def name(self) -> str: ...
426+ 
427+ @property
428+ def type(self) -> str: ...
429+ 
430+ def get_attr(self, name: str) -> object: ...
431+ def set_attr(self, name: str, value: object) -> None: ...
432+ def register_input(self, name: str) -> None: ...
433+ def register_optional_input(self, name: str) -> None: ...
434+ def register_output(self, name: str) -> None: ...
435+ def register_dynamic_input(self, name: str, count: int) -> None: ...
436+ def register_dynamic_output(self, name: str, count: int) -> None: ...
437+```
438+ 
439+方法名是接口评审目标,编码前需冻结。首轮不公开 TensorDesc、端口属性、连接和推导方法。
440+ 
441+3. 处理
442+ 
443+- bridge 创建 callback-bound、非直接构造的 Python Operator;
444+- Operator 持有受控 handle,不向 Python 返回地址;
445+- handle 只在 callback 期间 active,回调结束后所有方法必须报失效错误;
446+- 对象不支持 copy/deepcopy/pickle;
447+- setter 只允许 parser 调用线程执行;
448+- attr 复用 `_AttrValue` 转换,但 bridge 对首轮类型做白名单校验;
449+- 固定和动态端口 name 必须非空,动态端口 count 必须大于等于 0;bridge 不增加 C++ 接口之外的去重和排序语义;`InputRegister``OptionalInputRegister``OutputRegister` 当前为 protected,需增加不改变对象布局的 public 转发入口或经 graph 基础接口评审认可的等价访问机制,不允许 bridge 直接修改内部 `OpDesc`
450+ 
451+4. 输出
452+ 
453+getter 返回 Python 值;setter 成功返回 `None`,失败抛出带方法和字段上下文的 Python 异常。
454+ 
455+### 后续迭代
456+ 
457+1. 介绍
458+ 
459+后续能力按真实插件需求增加,不提前定义占位接口。
460+ 
461+2. 输入
462+ 
463+新增能力必须提供 C++ 调用链、真实算子仓用例、调用数量和所有权语义。
464+ 
465+3. 处理
466+ 
467+| 迭代 | 能力 | 代表用例 | 约束 |
468+|---|---|---|---|
469+| 2 | ParseOpToGraphFn | ThresholdedRelu、Int8Transpose | 回调返回 Graph,构图复用 ES |
470+| 2 | ParseParamsByOperatorFn | NPUAntiQuant | source/target 均使用 Operator,参数语义区分只读/可写 |
471+| 2 | TensorDesc/Tensor/DataType | NPUAntiQuant、Int8Transpose | 先冻结 handle 所有权和同步语义 |
472+| 3 | ParseSubgraphPostFn | If | 复用 Graph,窄化 framework helper |
473+| 3 | 稀疏/类型属性、输入删除重排 | 真实新增需求 | 不按公共 C++ 方法全集预留 |
474+| 3 | source 写回、ES 内部 desc | Correlation/Multinomial/EyeLike | 单独评审 const 语义和 ES handle |
475+ 
476+4. 输出
477+ 
478+每个迭代更新支持矩阵、用户样例、接口文档和对应 UT/ST。
479+ 
480+迭代 2 的 ThresholdedRelu ES 设计交付必须先完成 API 核验,首轮不实现。目标结构为:
481+ 
482+```python
483+@thresholded_relu.expand
484+def expand_thresholded_relu(source):
485+ builder = GraphBuilder(source.name)
486+ data = builder.create_input(0, name=f"{source.name}_data")
487+ identity = Identity(data)
488+ selected = Threshold(identity, threshold=source.get_attr("alpha", 1.0))
489+ output = Mul(identity, selected)
490+ return builder.build_and_reset(outputs=[output])
491+```
492+ 
493+该代码是迭代 2 的接口形态草案,不作为首轮可运行 API 承诺。编码前必须用现有 ES Python 模块核验 `GraphBuilder``create_input`、生成式算子导入、输入输出设置和 `build_and_reset` 的准确签名;如果某个算子没有生成式入口,必须先补充 ES 能力评审,不能新增字符串型万能算子工厂。
494+ 
495+`ParseOpToGraphFn` 的调用链不同于 `ParseParamsFn``ExpandOneToManyGraph``parser/parser/common/parser_utils.cc` 直接查询 `OpRegistry::GetParseOpToGraphFunc`,不经过 `OnnxCustomParserAdapter`。因此迭代 2 的 coordinator 必须独立验证并注册该 callback map,不能只扩展首轮 parser creator 或 adapter wrapper。
496+ 
497+## 非功能需求
498+ 
499+### 可维护性
500+ 
501+- 公共 API 只导出 `onnx_plugin``ImplyType``OnnxNode``OnnxPlugin``ge.graph.Operator`;registry/bootstrap/bridge helper 放在内部模块。
502+- 注册字段只声明一次,避免多装饰器字符串不一致。
503+- 基础对象扩展优先在 `ge.graph.Operator` 内完成,不新增业务专用镜像对象。
504+- 新 callback 以独立窄入口增加,避免修改既有 callback 签名。
505+ 
506+### 可测试性
507+ 
508+- Python descriptor、OnnxNode 和 Operator 参数校验可独立 UT;
509+- bridge 的类型转换、GIL、异常和失效 handle 可独立 C++ UT;
510+- parser ST 从 ONNX 输入校验最终 Graph 中 Operator 属性和各类端口;
511+- 测试使用专用 origin key,避免与已安装 C++ 插件冲突。
512+ 
513+### 可移植性
514+ 
515+能力只运行在 Host parser,不增加 SoC 条件分支。Python/C++ bridge 必须遵循支持平台的 Python ABI 和打包规则,不依赖 Device API。
516+ 
517+### 可靠性
518+ 
519+- descriptor 在写 registry 前完整校验;
520+- callback 结束后 Operator handle 立即失效,避免悬挂引用;
521+- Python 异常不跨越 C++ ABI;
522+- callback 失败时 target 尚未加入 Graph,不需要回滚已提交图状态;
523+- registry 冻结后 parser 只读,不允许解析期间动态新增注册。
524+ 
525+### 平台化要求
526+ 
527+GE 为 onetrack 部件,代码不得区分芯片。首轮不调用 RTS 或 Device 接口,各平台行为一致。
528+ 
529+### 特性交叉分析
530+ 
531+| 场景 | 适用性 | 分析说明 |
532+|---|---|---|
533+| 静态 Shape | 适用 | Python callback 生成标准 Operator,后续静态编译、内存和执行流程不变 |
534+| 动态 Shape | 适用 | 首轮不修改 TensorDesc;Node/Operator 中已有动态维原样进入后续推导,不涉及 runtime/v2 |
535+| 动态 Shape 静态子图 | 适用 | 能力发生在 parser 前端、图拆分前,不新增 v2→v1 数据,不修改 DavinciModelCreate 接口;因此不新增 runtime lowering 检查 |
536+| 离线场景(atc) | 适用 | 主要使用场景;只增加 parser 初始化和 callback 时间,OM 格式不变 |
537+| 在线场景(框架适配) | 适用 | 仅在线入口实际调用 ONNX parser 时生效;需验证真实 session 初始化入口,不修改 Adapter、Session 或执行接口 |
538+ 
539+在线路径不能假设一定经过 `OpRegistrationTbe::Finalize`。Python 注册锚点不得只绑定 Finalize,编码前必须沿实际调用 ONNX parser 的 session 初始化路径验证,确保注册发生在首次 `OpRegistry` 查询之前。
540+ 
541+## 性能
542+ 
543+### 模型编译时长
544+ 
545+新增耗时来自插件路径扫描、模块导入、descriptor 注册、NodeProto 转换和 Python callback。至少观测:
546+ 
547+- 未配置 Python 插件时的 parser 初始化回归;
548+- 一个插件和多个插件的导入耗时;
549+- 单节点及多节点 callback 平均耗时;
550+- 多模型连续编译时 registry 复用效果。
551+ 
552+阈值由性能责任人在编码前冻结;无阈值时不能宣称性能验收通过。
553+ 
554+### OM大小和加载占用内存
555+ 
556+Python 对象和插件代码不进入 OM。生成的 Operator 使用现有序列化格式,OM 格式不变。进程级 descriptor registry 会增加 Host 常驻内存,需要记录插件数量与内存增长关系。
557+ 
558+### 执行性能
559+ 
560+Python 不进入模型执行路径,不影响已生成模型的执行性能和 Device 内存。首轮不增加 runtime 打点、线程或分支。
561+ 
562+## 接口设计
563+ 
564+### 新增/修改接口描述
565+ 
566+| 模块/接口 | 可见性 | 动作 | 首轮职责 |
567+|---|---|---|---|
568+| `ge.onnx_plugin.onnx_plugin` | 对外 | 新增 | 创建 descriptor |
569+| `ge.onnx_plugin.ImplyType` | 对外 | 新增 | 映射 `domi::ImplyType` |
570+| `ge.onnx_plugin.OnnxNode` | 对外 | 新增 | 只读 NodeProto 值 |
571+| `ge.onnx_plugin.OnnxPlugin` | 对外 | 新增 | 保存已评审注册字段并提供 callback 装饰器;不允许用户直接构造 |
572+| `OnnxPlugin.parse_node` | 对外 | 新增 | 绑定 ParseParamsFn |
573+| `ge.graph.Operator` | 对外 | 新增 | callback 期 Operator 基础操作 |
574+| `ge._internal.plugin_loader` | 内部 | 复用 | 按现有规则导入模块 |
575+| ONNX plugin registry/bootstrap | 内部 | 新增 | 收集、校验、冻结 descriptor |
576+| 独立 Python bridge SO | 组件内 | 新增 | 注册 callback wrapper、接收扁平值、调用 Python;不依赖 GE protobuf 生成类型 |
577+| parser-side registration coordinator | C++ 内部 | 新增 | creator/callback 预检查,协调 Finalize 与 Register,处理冲突和失败 |
578+| `OpParserFactory` | C++ 内部 | 复用/查询 | 创建和查询 ONNX parser creator |
579+| `OpRegistry/OpRegistrationTbe` | C++ 内部 | 复用并检查返回值 | 分别完成 creator 和 callback map 注册 |
580+| ONNX parser/session 初始化 | C++ 内部 | 修改初始化入口 | 覆盖 parser 与 session 实际入口,首次查询 creator/registry 前完成注册 |
581+ 
582+### 公开接口草案
583+ 
584+```python
585+from collections.abc import Callable, Iterable, Mapping
586+from enum import Enum
587+from typing import Any
588+ 
589+from ge.graph import Operator
590+ 
591+ 
592+class ImplyType(Enum):
593+ TVM = "TVM"
594+ AI_CPU = "AI_CPU"
595+ CUSTOM = "CUSTOM"
596+ GELOCAL = "GELOCAL"
597+ 
598+ 
599+# BUILTIN/BUILDIN、CCE、HCCL 不属于首轮 Python ONNX plugin imply type;
600+# INVALID 始终拒绝。是否开放 CCE/HCCL 需后续真实调用链评审。
601+ 
602+ 
603+class OnnxNode:
604+ name: str
605+ op_type: str # parser 生成的完整 origin type,例如 ai.onnx::11::Elu
606+ inputs: tuple[str, ...]
607+ outputs: tuple[str, ...]
608+ attrs: Mapping[str, Any]
609+ 
610+ 
611+class OnnxPlugin:
612+ def parse_node(
613+ self, fn: Callable[[OnnxNode, Operator], None]
614+ ) -> Callable[[OnnxNode, Operator], None]: ...
615+ 
616+ 
617+def onnx_plugin(
618+ *,
619+ source: str,
620+ domain: str,
621+ opsets: Iterable[int],
622+ target: str,
623+ imply_type: ImplyType = ImplyType.TVM,
624+) -> OnnxPlugin: ...
625+```
626+ 
627+首轮导出集合必须随接口评审冻结:`ge.onnx_plugin.__all__` 包含 `ImplyType``OnnxNode``OnnxPlugin``onnx_plugin``ge.graph.__all__` 增加 `Operator`。首轮不新增公开异常类,使用 Python 内置 `TypeError``ValueError``RuntimeError`
628+ 
629+### 接口检查项
630+ 
631+| 检查项 | 子检查项 | 是否涉及 | 结论 |
632+|---|---|---|---|
633+| 接口说明 | 是否需要公开接口评审 | 是 | onnx_plugin、ImplyType、OnnxNode、OnnxPlugin、Operator 及全部公开成员必须评审 |
634+| 环境变量 | 是否需要 SE 专项评审 | 是 | `ASCEND_CUSTOM_OPP_PATH` 复用范围、扫描规则、初始化时序、共存语义和回归性能必须确认 |
635+| 接口说明 | 是否补充资料 | 是 | 增加 API Reference、用户指南、迁移指南、类型提示和 Elu/Sum/ST Sample |
636+| 接口说明 | 原型、返回值、异常是否明确 | 是 | 编码前按最终签名更新 API 文档 |
637+| 接口兼容 | 是否改变现有接口行为 | 否 | 新增 Python 能力,不修改 C++ plugin API |
638+| 接口兼容 | 新老组件组合 | 是 | bridge/Python 包/GE parser 版本必须匹配 |
639+| 接口兼容 | 是否有时序约束 | 是 | 注册早于首次 OpParserFactory/OpRegistry 查询,Operator 仅 callback 期有效 |
640+| 接口约束 | 非法调用是否清晰报错 | 是 | 字段、属性类型、失效 handle 均明确异常 |
641+| 接口约束 | 是否需要独立测试 | 是 | Python UT、bridge UT、parser ST |
642+ 
643+### 接口评审与资料交付
644+ 
645+编码前需要一次性冻结完整公开面,而不是只评审示例中的方法名。评审清单包括:
646+ 
647+- `onnx_plugin` 参数、默认值、返回类型和异常;
648+- `ImplyType` 枚举值及与 `domi::ImplyType` 的映射;
649+- `OnnxNode` 字段类型、只读语义和直接构造限制;
650+- `OnnxPlugin` 类名、直接构造限制和 `parse_node` 装饰器返回值;
651+- `Operator` 属性、方法、类型白名单、失效和线程语义;
652+- `ge.onnx_plugin.__all__``ge.graph.__all__``.pyi` 类型声明。
653+ 
654+接口评审通过后,代码合入前必须增加 API Reference、用户指南、C++ 迁移指南、Elu/Sum 入门 Sample 和 ST/系统测试 Sample。测试 Sample 使用测试专用 domain/source,通过真实 loader、coordinator、bridge 和 parser 验证 Graph,不依赖 registry 私有字段或内部 helper。完整 Sample 见[开发者友好版设计第 3.4-3.6 节](onnx_plugin_python_developer_design.md#34-接口使用-sample属性解析-st)。
655+ 
656+## 软件设计
657+ 
658+### 关键数据结构
659+ 
660+| 数据结构 | 生命周期 | 内容 | 所有权 |
661+|---|---|---|---|
662+| OnnxPlugin descriptor | 进程级 | 注册字段和 parse_node callable | Python registry 持有 |
663+| Frozen descriptor | parser 初始化后进程级 | 校验后的不可变注册快照 | bridge 只读 |
664+| OnnxNode | 单次 callback | NodeProto 基础字段和属性副本 | Python 值对象 |
665+| Operator | 单次 callback | 受控 ge::Operator handle 和 active token | C++ 拥有目标对象,Python 不拥有裸对象 |
666+ 
667+`ge.graph.Node``ge.graph.Operator` 是两个不同的公共对象:`Node` 包装已加入 Graph 的 `ge::GNode`,用于图内节点访问;`Operator` 包装 parser 尚未加入 Graph 的 `ge::Operator`,用于 parser callback 期的原型、属性和端口修改。两者不继承、不共享 handle,但可以复用 `_AttrValue`、错误转换和 TensorDesc 的 Python 基础实现。Operator 的 C API/bridge binding 归 parser/graph Python binding 评审,不把 GNode API 伪装成 Operator API。
668+ 
669+Operator handle 状态机:
670+ 
671+```text
672+CREATED -> ACTIVE(callback) -> INVALID
673+ |
674+ +-> Python exception -> INVALID
675+```
676+ 
677+所有 public 方法先检查 ACTIVE。用户保存 Operator 引用不会延长 C++ 对象生命周期。
678+ 
679+### 关键技术/算法
680+ 
681+本特性不新增计算算法,关键技术为:
682+ 
683+- descriptor 到 `OpRegistrationData` 的字段展开;
684+- NodeProto 到 Python 自然值的类型转换;
685+- callback-bound Operator handle;
686+- C++ wrapper 获取 GIL、调用 Python、转换异常;
687+- 复用现有 registry 和 parser 时序。
688+ 
689+### 流程设计
690+ 
691+#### 注册流程
692+ 
693+```mermaid
694+sequenceDiagram
695+ participant Init as Parser Init
696+ participant Loader as plugin_loader
697+ participant PyReg as Python Registry
698+ participant Bridge as ONNX Python Bridge
699+ participant Coord as Parser Registration Coordinator
700+ participant Finalize as OpRegistrationTbe/Finalize
701+ participant Factory as OpParserFactory
702+ participant Registry as OpRegistry
703+ 
704+ Init->>Loader: 加载 ASCEND_CUSTOM_OPP_PATH
705+ Loader->>PyReg: import plugin.py,执行 onnx_plugin/parse_node
706+ Init->>Bridge: 获取并冻结 descriptors
707+ Init->>Coord: 提交 Python descriptor batch
708+ Coord->>Coord: 预检查 creator 和全部 callback maps
709+ loop 每个 origin type
710+ Coord->>Finalize: Finalize(OpRegistrationData)
711+ Finalize->>Factory: 从 framework creator 注册 target om_type creator
712+ Coord->>Registry: Register(OpRegistrationData)
713+ end
714+ Factory-->>Coord: creator 状态
715+ Registry-->>Coord: callback map 状态
716+ Coord-->>Init: 全部成功或初始化失败
717+```
718+ 
719+#### 回调流程
720+ 
721+```mermaid
722+sequenceDiagram
723+ participant Parser as ONNX Parser
724+ participant Adapter as OnnxCustomParserAdapter
725+ participant Wrapper as C++ Wrapper
726+ participant Python as parse_node
727+ participant Target as ge::Operator
728+ 
729+ Parser->>Adapter: ParseParams(NodeProto, Target)
730+ Adapter->>Wrapper: ParseParamFunc
731+ Wrapper->>Wrapper: 转换 OnnxNode,创建 ACTIVE Operator
732+ Wrapper->>Python: parse_node(node, target)
733+ Python->>Target: set_attr/register_dynamic_input
734+ Python-->>Wrapper: None
735+ Wrapper->>Wrapper: Operator 标记 INVALID
736+ Wrapper-->>Adapter: SUCCESS
737+ Adapter-->>Parser: SUCCESS,后续 Graph.AddOp
738+```
739+ 
740+异常时 wrapper 标记 Operator INVALID、记录 Python traceback 摘要并返回 FAILED;parser 不执行当前 Operator 的 `Graph.AddOp`
741+ 
742+### 对子模块的修改
743+ 
744+| 子模块 | 修改 |
745+|---|---|
746+| `api/python/ge/ge/onnx_plugin/` | 新增公开入口、descriptor 和内部 registry/bootstrap |
747+| `api/python/ge/ge/graph/operator.py` | 新增 callback-bound Operator |
748+| `api/python/ge/ge/graph/__init__.py` | 导出 Operator |
749+| `api/python/ge/ge/_capi/` 或独立 bridge binding | 增加 Operator 和 descriptor 需要的受控接口 |
750+| parser-side NodeProto flattener | 在 parser 内部读取私有 protobuf,输出 bridge 可消费的扁平值 |
751+| parser registration coordinator | 预检查 creator/callback map,协调 Finalize/Register 和冲突 |
752+| parser/session 初始化 | 增加 Python ONNX plugin bootstrap 调用,覆盖实际 session 路径 |
753+| common Python runtime/module cache | 与 custom_op 共用 interpreter 和 canonical-path import cache |
754+| build/package | 增加独立 bridge SO 和 Python package 安装 |
755+| tests | 增加 Python UT、bridge UT、parser ST |
756+ 
757+实现后需要同步更新已有官方文档 `docs/zh/design/modules/ge_python/ge_python.md``ge_python_custom_op_design.md``ge_python_pass_design.md` 中受影响的 Python loader/runtime 说明;若 ONNX plugin 形成独立特性,应新增对应 `docs/zh/design/features/` 特性文档并在本方案中回链。当前仓内没有现成 parser 模块设计文档,不以不存在的路径作为交付项。首轮不修改 graph_metadef C++ 基础结构、Operator C++ ABI 或序列化逻辑。
758+ 
759+### 错误处理
760+ 
761+#### 系统错误
762+ 
763+- Python runtime 或 bridge 初始化失败:终止 Python ONNX plugin 初始化;未启用 Python 插件时不影响原 C++ 路径。
764+- 插件模块导入失败:报告路径、模块名和异常摘要,不继续注册不完整集合。
765+- GIL 获取或 Python 调用失败:当前节点解析失败,不抛异常穿越 C++ ABI。
766+- bridge/Python 包版本不匹配:初始化失败并给出两侧版本。
767+- Finalize/Register 或 creator/callback map 预检查失败:终止本次 Python parser 初始化,不继续提交后续 descriptor;rollback 机制冻结前不标记初始化成功。
768+ 
769+#### 接口错误
770+ 
771+| 错误 | 行为 |
772+|---|---|
773+| 非法 descriptor 字段 | Python 注册阶段抛 ValueError/TypeError |
774+| Python/Python origin 重叠 | 注册阶段抛冲突错误,包含两个模块 |
775+| C++/Python creator 或 callback map 冲突 | 保留 C++,拒绝 Python descriptor,并输出包含 key/map 的诊断 |
776+| 未支持 AttributeProto 类型 | callback 前转换失败,包含属性名和类型 |
777+| callback 返回非 None | callback 失败 |
778+| Operator handle 已失效 | Python RuntimeError |
779+| 动态端口 name/count 非法 | Python TypeError/ValueError |
780+| target 原型不存在 | bridge 注册失败 |
781+ 
782+## 安全检查
783+ 
784+### 编码军规
785+ 
786+- 不暴露裸指针、C++ handle 数值或 Python 对象地址;
787+- 不递归扫描插件目录;
788+- 不执行模型属性中的代码;
789+- 日志不输出 Tensor 内容或完整模型数据;
790+- callback、属性 setter 等高频路径不逐次打印 INFO/ERROR,诊断日志按失败事件输出并遵守基础接口限频要求;
791+- Python traceback 需过滤内部地址和敏感路径;
792+- 所有跨 ABI 异常在 bridge 内转换。
793+ 
794+不暴露裸指针;所有路径先规范化,再按现有 loader 规则限制为文件、单层目录和 package,不执行递归目录扫描;不在日志输出模型 Tensor 内容。C++ 不信任 Python 返回类型、长度和整数范围,写回前必须完整校验。Python 异常不得跨越 C++ ABI,C++ 资源使用 RAII,所有 callback 路径正确释放 GIL guard 和临时对象。
795+ 
796+| 检查项 | 检查项说明 | 是否涉及 | 结论 |
797+|---|---|---|---|
798+| 资源生命周期 | descriptor 进程级,OnnxNode/Operator callback 级 | 是 | handle 失效专项 UT,设计评审 |
799+| 新线程 | 是否创建线程并复制上下文 | 否 | bridge 不新建线程,调用方线程内获取 GIL |
800+ 
801+## 兼容性检查
802+ 
803+- 未配置 Python 插件时,不导入用户模块,不改变现有 C++ ONNX parser 行为;
804+- 现有 C++ plugin API、registry key 和 callback 签名不变;固定端口注册只增加不改变对象布局的 public 访问入口;
805+- Python 生成的 Operator 使用现有 Graph/OM 序列化格式;
806+- 老 OM 在新版本下加载执行不受影响;
807+- Python plugin 属于源代码级扩展,不承诺新插件在不含 bridge 的老版本 GE 上可用;
808+- `ge.graph.Operator` 为新增接口,不修改现有 `Node``Graph``TensorDesc` 语义;Node 与 Operator 的底层 handle 不混用。
809+- online/session 场景必须单独验证初始化入口,不假设 session 路径经过 `AclParserInitialize/Finalize`
810+ 
811+## DT设计
812+ 
813+### 测试边界
814+ 
815+- Python UT 入口:公开 decorator、OnnxNode、Operator 参数校验;出口:descriptor 和异常。
816+- bridge UT 入口:parser 扁平值、Operator/callback;protobuf 字段提取由 parser UT 单独验证;出口:C++ Operator 状态和 Status。
817+- parser ST 入口:测试 ONNX 模型与 Python plugin;出口:最终 Graph 中 Operator 属性、动态端口和失败错误。
818+- 不在首轮验证 compiler、runtime 或设备执行。
819+ 
820+### 测试设计
821+ 
822+| 测试类别 | 关键测试项 | 测试方法 | 用例类型 |
823+|---|---|---|---|
824+| 功能 | descriptor 合法字段 | 构造 source/domain/opsets/target/imply_type | Python UT |
825+| 功能 | Elu FLOAT 默认值和覆盖 | 测试专用 origin 执行 Elu 等价 callback | parser ST |
826+| 功能 | Sum 动态输入数量 | 1 个/多个/0 个输入 | bridge UT + parser ST |
827+| 功能 | 默认动态输出注册 | 合成 callback 注册 0/1/多个输出 | bridge UT |
828+| 功能 | required/optional/普通 output 注册 | 合成 callback 直接调用三类固定端口接口并检查 Graph | bridge UT + parser ST |
829+| 功能 | NodeProto 标量/列表属性 | f/i/s/floats/ints/strings 往返 | bridge UT |
830+| 功能 | Operator 属性类型 | bool/int/float/str/list | Python UT + bridge UT |
831+| 功能 | 对外接口使用 Sample | 测试专用 plugin.py + ONNX 模型生成器,经真实 loader/parser 检查 Graph | parser ST + atc 黑盒 ST |
832+| 功能 | Sample 泛化 | 参数化 domain/opset/属性/输入数/callback 结果/初始化入口 | parser ST + online ST |
833+| 异常 | 未支持 Tensor/Graph/SPARSE、UNDEFINED、未知枚举 | 合成 NodeProto | parser UT |
834+| 异常 | `ref_attr_name` 非空 | 合成函数属性引用 | parser UT |
835+| 异常 | callback 抛异常/返回非 None | 合成 callback | bridge UT |
836+| 异常 | callback 后保存 Operator | 回调结束后调用 setter | Python/bridge UT |
837+| 异常 | Python/Python 重复注册 | 重叠 opset | Python UT |
838+| 兼容 | C++/Python 同 key | 同时注册 | bridge UT + parser ST |
839+| 兼容 | 无 parser creator 的 Python target | preflight 后提交 Finalize/Register | parser ST |
840+| 兼容 | 已有 parser creator 的 Python target | 首轮明确拒绝并保留现有 creator | parser ST |
841+| 兼容 | ParseParamsFn/ParseParamsByOperatorFn 跨 map 冲突 | 预检查全部 callback map | bridge UT |
842+| 兼容 | Finalize/Register 部分失败 | 检查返回值并验证初始化失败状态 | bridge UT |
843+| 兼容 | custom_op/ONNX 同文件 | canonical-path 一次导入和双 registry 收集 | parser/runtime ST |
844+| 兼容 | atc/session/aclgrphParseONNX 三类入口 | 分别验证 bootstrap、Finalize/Register 和首次查询前时序 | parser ST + online ST |
845+| 兼容 | online/session 初始化 | 覆盖 session 入口和首次查询前注册 | online ST |
846+| 兼容 | bridge ABI/Python 版本不匹配 | 版本故意不匹配 | bridge UT |
847+| 兼容 | 多模型并发初始化 | 并发初始化同一 registry | parser ST |
848+| 兼容 | 无 Python 插件 | 环境变量为空 | parser ST |
849+| 性能 | 无/单/多插件初始化 | 记录解析耗时和 Host 内存 | 性能 ST |
850+| 精度 | 不适用 | 本特性只做属性、端口和图结构转换,不实现数值计算;由后续算子执行测试负责数值精度 | 不适用 |
851+| 特性交叉 | 静态/动态 shape ONNX | 比较解析后 TensorDesc | parser ST |
852+ 
853+### 测试框架设计
854+ 
855+复用 pytest、现有 graph Python UT、parser gtest 和 parser ST,不新增测试框架。测试插件放在测试资源目录,通过独立环境变量值加载,避免污染开发机 OPP。
856+ 
857+## 验收标准
858+ 
859+1. `onnx_plugin` 能声明 Elu 等价注册字段并绑定 `parse_node`
860+2. Python 插件从 `ASCEND_CUSTOM_OPP_PATH` 被稳定发现,模块只导入一次,creator 和 callback 注册发生在首次 factory/registry 查询前;
861+3. NodeProto 的基础字段和首轮六类属性转换正确,未支持类型明确失败;
862+4. callback 直接使用 `ge.graph.Operator` 完成属性、required/optional、普通 output 和默认动态端口修改;
863+5. 公开 API 中不存在 `OperatorView``OperatorUpdate``TensorDescUpdate``DynamicPort``ExpandResult`
864+6. Elu 等价 ST 验证 alpha 默认值与覆盖,Sum 等价 ST 验证动态输入数量;
865+7. callback 异常后当前 Operator 不加入 Graph,保存的 Python Operator 引用不可继续使用;
866+8. 未配置 Python 插件时现有 C++ parser 行为不变;
867+9. Python/C++ 跨 creator、跨 callback map 冲突,重复 Python 注册和 target 不存在均有明确诊断;
868+10. 首轮不修改 AscendIR、OM、compiler、runtime 和 graph_metadef C++ ABI;
869+11. 注册及回调 Mermaid 图可解析,Python 示例可通过 AST 检查;
870+12. 性能数据完成观测;若阈值已冻结,则达到阈值后方可验收。
871+13. 所有首轮公开符号、成员、`__all__` 和类型声明完成公开接口评审,API Reference、用户指南、迁移指南及 ST Sample 与评审结果一致。
872+ 
873+## 附录
874+ 
875+## 附录 A:C++ 到 Python 详细映射
876+ 
877+本附录用于编码和接口评审。前部[能力表](#能力在-python-中的体现)只回答用户能做什么,本附录进一步说明每个 Python 行为落到哪个 C++ 接口,以及 bridge 必须做什么。
878+ 
879+**注册描述与提交**
880+ 
881+| C++ 字段/接口 | Python 表达 | 内部转换或调用 | 阶段 | 源码依据 |
882+|---|---|---|---|---|
883+| `REGISTER_CUSTOM_OP` / `OpRegistrationData` | `onnx_plugin(...)` 返回 descriptor | Python registry 冻结 descriptor,bridge 构造 registration data | 首轮 | `inc/external/register/register.h` |
884+| `FrameworkType(ONNX)` | 不暴露参数 | ONNX plugin bootstrap 固定写 ONNX,禁止用户改成其他 framework | 首轮 | `OpRegistrationData::FrameworkType` |
885+| `OriginOpType(...)` | `source + domain + opsets` | 展开为 `domain::version::source` 集合,如 `ai.onnx::11::Elu` | 首轮 | `OpRegistrationData::OriginOpType``onnx_parser.cc:412-451` |
886+| target/OM type | `target` | 写入 `OpRegistrationData` 的 target type,并预检查算子原型 | 首轮 | `OpRegistrationData` 构造和 `GetOmOptype` |
887+| `ImplyType(...)` | `ImplyType.TVM/AI_CPU/CUSTOM/GELOCAL` | 枚举转 `domi::ImplyType`;BUILDIN、CCE、HCCL 首轮拒绝,INVALID 始终拒绝 | 首轮子集 | `metadef/inc/external/graph/types.h:434-443` |
888+| `ParseParamsFn(ParseParamFunc)` | `@plugin.parse_node` | C++ wrapper 保存 Python callable,注册为 `ParseParamFunc` | 首轮 | `inc/external/register/register.h` callback typedef/builder |
889+| `OpRegistrationTbe::Finalize` | 无公开接口 | coordinator 使用 framework creator 注册 target `om_type` creator | 首轮 | `parser/parser/common/op_registration_tbe.cc:40-142``onnx_custom_parser_adapter.cc:55` |
890+| `OpParserFactory::RegisterCreator/CreateOpParser` | 无公开接口 | 注册前预检查 creator;解析时创建 adapter | 首轮 | `op_registration_tbe.cc:115-142``onnx_parser.cc:657-664` |
891+| `OpRegistry::Register` | 无公开接口 | 写入 callback、origin 和 run-mode map,检查返回值 | 首轮 | `graph_metadef/register/register.cpp:1050-1092` |
892+| `GetParseParamFunc(om_type, ori_type)` | 无公开接口 | adapter 查找首轮 Python wrapper | 首轮 | `inc/graph_metadef/register/op_registry.h:59` |
893+ 
894+**回调与 ONNX 输入**
895+ 
896+| C++ 输入/输出 | Python 表达 | bridge 行为 | 首轮类型范围 | 源码依据 |
897+|---|---|---|---|---|
898+| `ParseParamFunc(const Message *, Operator &)` | `(OnnxNode, Operator) -> None` | parser 确认 NodeProto 类型并扁平化;bridge 获取 GIL、调用 callable | 支持 | `register.h` callback typedef;`onnx_custom_parser_adapter.cc` |
899+| `NodeProto::name()` | `node.name: str` | 复制为 Python str | 支持 | `ge_onnx.proto` NodeProto |
900+| parser 重写后的 `NodeProto::op_type()` | `node.op_type: str` | 对非内置 `Input/Constant/FileConstant` 映射的算子暴露完整 origin type;`set_op_type` 在 ParseAllNodeProto 中执行 | 支持 | `onnx_parser.cc:412-451,453-480,639-649,644` |
901+| `NodeProto::input/output` | `node.inputs/outputs: tuple[str, ...]` | 复制重复字段并冻结 | 支持 | `ge_onnx.proto` NodeProto |
902+| `AttributeProto::f/i/s` | `float/int/str` | string UTF-8 解码;失败包含属性名 | 支持 | `ge_onnx.proto:101-116``ref_attr_name` 字段见 `121-125` |
903+| `AttributeProto::floats/ints/strings` | 同类型 Python tuple/list | 保持元素类型一致 | 支持 | `ge_onnx.proto:101-116``ref_attr_name` 字段见 `121-125` |
904+| TENSOR(S)/GRAPH(S)/SPARSE_TENSOR(S) | 无公开值 | callback 前明确失败,不静默丢弃 | 延后 | `ge_onnx.proto:101-116`;`ref_attr_name` 字段见 `121-125` |
905+| `AttributeProto::ref_attr_name` 非空 | 无公开值 | 作为未解析的父作用域引用拒绝 | 延后 | `ge_onnx.proto:121-125` |
906+| C++ `Status` | Python 返回 `None` 或抛异常 | `None -> SUCCESS`;非 None 或异常 -> FAILED,异常不跨 C++ ABI | 支持 | `ParseParamFunc` 返回约定 |
907+ 
908+GE 当前 `AttributeType` 没有 `TYPE_PROTO/TYPE_PROTOS`,因此它们不属于待转换枚举。`OnnxNode` 不持有 `Message *`,回调结束后不存在 protobuf 生命周期依赖。
909+ 
910+**GE Operator 与描述符**
911+ 
912+| `ge::Operator` C++ 接口 | Python 表达 | bridge 约束 | 阶段 | 源码依据 |
913+|---|---|---|---|---|
914+| `GetName()` | `target.name` | 只读 property;每次调用先检查 ACTIVE | 首轮 | `inc/graph_metadef/external/graph/operator.h:110-118` |
915+| `GetOpType()` | `target.type` | 只读 property;返回 target type | 首轮 | `operator.h:115-118` |
916+| `GetAttr/SetAttr` 标量和列表重载 | `get_attr/set_attr` | 首轮白名单 bool/int/float/str 和同类型 list,复用 `_AttrValue` | 首轮子集 | `operator.h:229-315``api/python/ge/ge/graph/_attr.py:273-304` |
917+| Tensor、DataType、NamedAttrs、bytes 等属性重载 | 尚无首轮入口 | 逐类型复用已有 graph 值对象;不得用 `Any` 静默吞类型 | 后续 | `operator.h:279-315` |
918+| `InputRegister(name)` | `register_input(name)` | 当前为 protected;增加不改变布局的 public 转发入口并校验非空 name | 首轮 | `operator.h:589-592` |
919+| `OptionalInputRegister(name)` | `register_optional_input(name)` | 当前为 protected;经 public 转发入口注册 optional 输入 | 首轮 | `operator.h:594-597` |
920+| `OutputRegister(name)` | `register_output(name)` | 当前为 protected;经 public 转发入口注册普通输出 | 首轮 | `operator.h:605-609` |
921+| `DynamicInputRegister(name, num, is_push_back)` | `register_dynamic_input(name, count)` | 首轮固定默认 `is_push_back=true`,校验非空 name 和 count | 首轮默认重载 | `operator.h:612` |
922+| `DynamicOutputRegister(name, num, is_push_back)` | `register_dynamic_output(name, count)` | 首轮固定默认 `is_push_back=true` | 首轮默认重载 | `operator.h:618` |
923+| `Get/UpdateInputDesc` | 复用 `TensorDesc`,Operator 方法名待评审 | 先冻结副本/关联 handle、按名/下标和 update 同步语义 | 迭代 2 | `operator.h:146-170` |
924+| `Get/UpdateOutputDesc` | 复用 `TensorDesc`,Operator 方法名待评审 | 同上 | 迭代 2 | `operator.h:172-182,527` |
925+| `DelInput/InputReorder` 等特殊接口 | 无 | 真实插件需求和调用量评审后再开放 | 按需 | `operator.h` |
926+ 
927+`ge.graph.Node` 包装 `ge::GNode`,不能代替这里的 callback-bound `ge::Operator`。两者只复用 Python 属性转换、TensorDesc 和错误处理代码,不共享 C++ handle。
928+ 
929+**后续 callback 与 ES**
930+ 
931+| C++ 接口 | Python 目标形态 | 接入位置 | 阶段 | 源码依据 |
932+|---|---|---|---|---|
933+| `ParseParamByOpFunc(const Operator &, Operator &)` | 独立窄入口,名称待评审 | `parse_params_by_op_func_map_`;查询优先于 ParseParamsFn | 迭代 2 | `op_registry.h:61``onnx_parser.cc:602-617` |
934+| `ParseOpToGraphFunc(const Operator &, Graph &)` | `@plugin.expand(source) -> Graph` 目标形态 | `parse_op_to_graph_fn_map_``ExpandOneToManyGraph` 直接查询,不经过 adapter | 迭代 2 | `op_registry.h:82``parser_utils.cc:42-83` |
935+| `GraphBuilder.create_input(...)` | `builder.create_input(index, ...)` | 复用现有 ES C API | 迭代 2 | `api/python/ge/ge/es/graph_builder.py:177-199` |
936+| `Graph::SetInputs` | `GraphBuilder.create_input` 产生的 `TensorHolder` 作为图输入 | 输入在 builder 创建阶段绑定 index,不新增 `ctx.graph.inputs` 字符串接口 | 迭代 2 | `graph.h``graph_builder.py:177-199` |
937+| `Graph::SetOutputs` | `builder.build_and_reset(outputs=[tensor_holder, ...])` | outputs 列表按顺序设置 Graph 输出,输出 index 由 builder 管理 | 迭代 2 | `graph.h``graph_builder.py:938-959` |
938+| `GraphBuilder.build_and_reset(outputs)` | `builder.build_and_reset(outputs=[...])` | 设置输出并返回 `ge.graph.Graph` | 迭代 2 | `graph_builder.py:938-959` |
939+| ES 生成式算子插件 | `Identity/Threshold/Mul` 的实际插件命名空间 | 通过 `ge.es` entry points 加载,不新增字符串工厂 | 迭代 2,导入待核验 | `api/python/ge/ge/es/__init__.py:23-40` |
940+| `ParseSubgraphFuncV2` | 独立 subgraph 后处理入口,名称待评审 | subgraph callback map | 迭代 3 | `op_registry.h:67-74` |
941+ 
942+不暴露的内部对象包括 `google::protobuf::Message *``ge::Operator *`、C++ `Status``OpRegistrationData``OpParserFactory``OpRegistry` 和 GIL。它们是 bridge 实现边界,不是 Python 用户接口。
943+ 
944+## 附录 B:后续能力边界
945+ 
946+- TensorDesc 必须先确认 getter 返回副本还是与 Operator 关联的受控对象;不再增加 TensorDescUpdate。
947+- source Operator 写回需单独评审 `const Operator &` 的真实语义,不提供 ExpandResult。
948+- ES 内部算子描述符修改先扩展 ES 的通用 Operator/Node 能力,不增加 ONNX 专用接口。
949+- 子图 IO 映射只提供调用既有 framework helper 的窄入口。
950+- 新属性类型优先使用 Python 自然值或已有 `ge.graph` 对象,只有无自然表示时才评审新值类型。
951+ 
952+## 附录 C:性能与内部验证项
953+ 
954+编码前待冻结:
955+ 
956+- parser 初始化锚点;
957+- bridge SO 构建、安装和版本校验;
958+- GIL 和多模型并发模型;
959+- C++/Python 注册冲突策略已冻结为预检查后拒绝 Python 冲突项;仍需编码验证错误状态和诊断内容;
960+- `ParseOpToGraphFn` 直查 `OpRegistry` 的迭代 2 bridge 接入;
961+- Operator 首轮方法名和 handle 实现;
962+- 性能阈值。
963+ 
964+## 附录 D:设计文档检查结果
965+ 
966+- [x] 已读取并按 `docs/zh/design/design_document_template.md` 覆盖全部模板章节。
967+- [x] 已按 `cross_feature_check.md` 分析静态 Shape、动态 Shape、动态 Shape 静态子图、离线和在线五类场景。
968+- [x] 已加载 `docs/zh/design/modules/graph_metadef/ascend-ir.md`,方案复用现有 Operator/Attr/TensorDesc 语义,不修改 AscendIR 对象模型和序列化。
969+- [x] 已加载 `docs/zh/design/constraints/graph_metadef.md`,新增基础接口遵循稳定性、最小依赖、关注点分离和单线程修改约束。
970+- [x] 方案不向 `graph_metadef` 引入 Python 依赖,不修改 graph C++ ABI、OM、compiler 或 runtime。
971+- [x] 公开用户示例前置,完整映射、内部冻结项和低频能力放入附录。
972+- [x] 已明确 atc、session、aclgrphParseONNX 三类注册入口,并区分 custom_op 的独立 C++ 注册链。
973+- [x] 已明确精度测试对本特性不适用,并将官方 Python 文档同步路径列为实现交付项。
974+- [ ] 待冻结:公开 Operator 方法名、handle 所有权实现和失效检查。
975+- [ ] 待冻结:parser 初始化锚点、bridge SO/ABI、GIL、冲突诊断实现和性能阈值;冲突优先级已冻结为预检查后拒绝 Python。
@@ -0,0 +1,386 @@
1+# ONNX Plugin Python 化 PR 2 评审决策与开发计划修订要求
2+ 
3+> 文档状态:本轮评审、竞品调研和开发分期的单一决策来源。
4+>
5+> 评审来源:https://gitcode.com/gentle-knight/ge/pull/2
6+>
7+> 修订对象:[ONNX Plugin Python 化技术方案](onnx_plugin_python_modification_plan.md)
8+>
9+> 首次记录:2026-07-30;竞品调研与开发计划合并:2026-07-31
10+ 
11+## 1. 修订结论
12+ 
13+本轮修订必须同时完成接口简化、迭代收敛和文档重组:
14+ 
15+1. 公开对象使用 `ge.graph.Operator`,不再建立 `OperatorView``OperatorUpdate` 等镜像模型。
16+2. 回调直接调用 `Operator``TensorDesc` 接口,不再返回 `TensorDescUpdate``DynamicPort` 或其他更新命令对象。
17+3. 注册信息只声明一次;不同 parser callback 使用语义明确的窄入口,不提供万能 context。
18+4. 首轮只支持 `ParseParamsFn` 对应的 `parse_node` 主链,先打通插件发现、注册、NodeProto 转换和目标 Operator 修改。
19+5. `ParseOpToGraphFn``ParseParamsByOperatorFn``ParseSubgraphPostFn` 按实际调用量进入后续迭代,不在首轮预留未实现接口。
20+6. 属性使用 Python 原生值和已有 `ge.graph` 对象;每个迭代只开放已验证类型,未支持类型明确报错。
21+7. 技术方案主体先展示完整用户用例和首轮支持矩阵,C++ 映射、bridge、ABI/GIL、性能和低频能力进入附录。
22+8. 竞品只借鉴高层 API 风格,不照搬 PyTorch 导出机制或 TensorRT kernel plugin 生命周期。
23+9. 首轮设计评审包含两个独立门禁:完整开放对外接口;与 SE 对齐是否复用 `ASCEND_CUSTOM_OPP_PATH` 及其扫描、加载、共存和回归语义。
24+ 
25+## 2. 首轮开发范围
26+ 
27+### 2.1 首轮目标
28+ 
29+首轮成功标准为:
30+ 
31+> Python 插件从既定自定义 OPP 路径被发现并注册;ONNX parser 命中该注册后调用 Python `parse_node`;回调读取 NodeProto 的基础字段和常用属性,直接修改目标 `ge.graph.Operator`;回调成功后 Operator 被加入 Graph,失败时本次解析终止且目标 Operator 不进入 Graph。
32+ 
33+首轮主样例选择 Elu:现有插件只使用 `ParseParamsFn`,读取一个 FLOAT 属性并调用 `SetAttr`,见 `ops-nn/activation/elu/framework/elu_onnx_plugin.cpp:15-42`。动态端口补充样例选择 Sum→AccumulateNV2:同样只使用 `ParseParamsFn`,调用默认 `DynamicInputRegister`,见 `ops-math/math/accumulate_nv2/framework/sum_onnx_plugin.cpp:15-46`。为避免与已安装 C++ 注册冲突,ST 使用测试专用 origin type 执行等价逻辑。ThresholdedRelu 依赖图展开,不作为首轮端到端样例。
34+ 
35+### 2.2 首轮支持矩阵
36+ 
37+| 能力类别 | 接口/类型 | 首轮状态 | 不支持时的行为 | 验证方式 | 后续影响 |
38+|---|---|---|---|---|---|
39+| 插件注册 | `onnx_plugin(...)` 描述对象 | 支持 | 非法字段在写入 C++ registry 前报错 | Python UT + bridge UT | 注册字段保持兼容扩展 |
40+| 注册字段 | `source/domain/opsets/target/imply_type` | 支持 | 缺失、非法 opset 或 imply type 报错 | Python UT | 后续字段保持可选 |
41+| 回调 | `ParseParamsFn` / `parse_node` | 支持 | 回调异常转换为节点解析失败 | bridge UT + parser ST | 首轮唯一 callback |
42+| 回调 | `ParseParamsByOperatorFn` | 延后 | 不提供 `parse_operator` 入口 | 接口不存在性检查 | 迭代 2 |
43+| 回调 | `ParseOpToGraphFn` | 延后 | 不提供 `expand` 入口 | 接口不存在性检查 | 迭代 2,复用 ES |
44+| 回调 | `ParseSubgraphPostFn` | 延后 | 不提供 `subgraph` 入口 | 接口不存在性检查 | 迭代 3 |
45+| ONNX 输入 | name、op_type、inputs、outputs | 支持 | 字段转换失败则解析失败 | bridge UT | 保持只读自然值 |
46+| ONNX 标量属性 | FLOAT、INT、STRING | 支持 | 类型不支持时明确报错 | bridge UT + parser ST | 对应 float/int/str |
47+| ONNX 列表属性 | FLOATS、INTS、STRINGS | 支持 | 混合或未知类型报错 | bridge UT | 对应同类型 list |
48+| ONNX Tensor/Graph 属性 | TENSOR(S)、GRAPH(S) | 延后 | 明确报不支持 | 负向 UT | 迭代 2/3 |
49+| ONNX 其他情况 | SPARSE_TENSOR(S)、UNDEFINED、未知枚举 | 延后 | 明确报不支持 | 负向 UT | 按真实需求引入 |
50+| ONNX 属性引用 | `ref_attr_name` 非空 | 延后 | 明确报不支持,不静默丢弃引用 | 负向 UT | 按函数/子图需求引入 |
51+| Operator 属性 | bool/int/float/str 及同类型 list | 支持 | `_AttrValue` 不支持时抛类型错误 | Python UT + bridge UT | 复用现有 converter |
52+| Operator 属性 | DataType、Tensor 及列表 | 延后 | 明确报不支持 | 负向 UT | 迭代 2 |
53+| 固定端口 | required/optional 输入、普通输出注册 | 支持 | 非法 name 报错 | Python UT + bridge UT + parser ST | 直接调用 Operator 接口 |
54+| 动态端口 | 默认动态输入/输出注册 | 支持 | 非法 name/count 报错 | Python UT + Sum 等价 parser ST | 高级重载后续增加 |
55+| 动态端口高级重载 | ByIndex、append、datatype symbol | 延后 | 不提供入口 | 接口不存在性检查 | 按真实迁移用例增加 |
56+| TensorDesc 读写 | 固定/动态端口描述 | 延后 | 首轮不提供 parser Operator 入口 | 接口不存在性检查 | 所有权冻结后进入迭代 2 |
57+| ES 构图 | GraphBuilder 和生成式算子 | 延后 | 首轮不提供 expand | 接口不存在性检查 | 迭代 2 |
58+| 输入删除/重排 | delete_inputs、input_order | 延后 | 注册字段不接受 | Python 负向 UT | 按实际插件需求增加 |
59+ 
60+### 2.3 首轮非目标
61+ 
62+- 不实现四类 callback 的完整覆盖。
63+- 不实现 TensorDesc 修改、展开图、子图后处理和 source Operator 写回。
64+- 不增加 `OperatorView``OperatorUpdate``TensorDescUpdate``DynamicPort``ExpandResult`
65+- 不扩展 bytes、NamedAttrs、嵌套列表和稀疏属性;当前 GE ONNX proto 不含 TYPE_PROTO(S)。
66+- 不新增字符串型万能算子工厂,图展开后续继续复用 ES。
67+- 不修改 AscendIR、OM 格式、compiler 或 runtime。
68+- 不把 Python 依赖引入 `graph_metadef`
69+ 
70+### 2.4 注册主链闭环要求
71+ 
72+首轮注册不能只写 `OpRegistry`。parser 侧必须提供统一 registration coordinator,按以下顺序处理:
73+ 
74+1. Python 侧冻结整个 descriptor batch;
75+2. coordinator 预检查 target 原型、`OpParserFactory` creator 和全部 callback map;
76+3. Python/Python 重叠或 C++/Python 任一 creator/callback 冲突时,在任何写入前拒绝 Python descriptor;
77+4. 对无 target creator 的 target 调用 `OpRegistrationTbe::Finalize`;Finalize 使用 parser 静态注册的 framework creator 完成 target `om_type` creator 注册;
78+5. creator 成功后调用 `OpRegistry::Register` 写入 callback 和运行模式;
79+6. 两个返回值都必须检查,不能沿用现有初始化路径忽略返回值的行为;
80+7. `OpRegistry::Register(..., is_custom_op=false)` 对已有 key 可能静默跳过并返回成功,不能依赖其返回值发现冲突;冲突诊断和拒绝必须由 coordinator 在 Register 前完成;
81+8. 提交阶段失败时终止 Python ONNX 初始化,不继续解析,也不继续提交后续 descriptor;
82+9. rollback 或进程级清理机制冻结前,不宣称注册具备事务性。
83+ 
84+首轮对“已有 creator 的 Python target”采用拒绝策略,避免无法辨认 creator 类型时注册成功但 callback 永远不被调用。后续如需复用已有 `OnnxCustomParserAdapter` creator,必须先增加可验证的 creator provenance 查询。
85+ 
86+### 2.5 需求基线变更
87+ 
88+原始需求中的 ThresholdedRelu 完整 Python 插件仍是总体交付目标,但按 PR 2 的首轮收敛意见调整到迭代 2:
89+ 
90+| 原始交付内容 | 调整后阶段 |
91+|---|---|
92+| ThresholdedRelu 注册与属性解析 | 迭代 2 完整示例;首轮由 Elu 验证同类属性主链 |
93+| 动态 IO | 首轮由 Sum 验证默认动态输入;ThresholdedRelu 随迭代 2 示例补齐 |
94+| Data→Identity→Threshold→Mul | 迭代 2 设计和实现 |
95+| ES Graph 输入输出设置 | 迭代 2 设计和实现 |
96+| ParseOpToGraphFn | 迭代 2 |
97+ 
98+本次调整属于交付分期变更,不表示取消原始需求。启动代码开发前需要需求方和 SE 对该延期记录确认;未确认前,不得将原始需求标记为全部完成。
99+ 
100+### 2.6 初始化入口基线
101+ 
102+首轮设计不能只以 `AclParserInitialize` 作为注册入口。可信代码链显示:
103+ 
104+| 场景 | 入口 | 对 Python plugin 的要求 |
105+|---|---|---|
106+| atc | `api/atc/main_impl.cc:1284-1327` `LoadCustomOpLib` | `ParseGraph` 前完成 coordinator 注册 |
107+| session 插件加载 | `api/session/common/plugin/tbe_plugin_manager.cc:160-178` | 该阶段只 Register,不假设 creator 已完成 |
108+| session/aclgrphParseONNX | `parser/parser/common/acl_graph_parser_util.cc:247-253` | 首次 parser 查询前完成 Finalize/Register,并检查返回值 |
109+ 
110+`ge.custom_op` 的 Python bridge 面向 `CustomOpRegistry/OpLibRegistry`,不替代 ONNX plugin 的 parser 注册链。两者可以复用 Python interpreter、路径扫描和 canonical-path module cache,但 C++ registration coordinator 必须独立存在。
111+ 
112+### 2.7 外部确认记录
113+ 
114+| 确认项 | 当前状态 | 确认人/日期 | 影响 |
115+|---|---|---|---|
116+| ThresholdedRelu 完整示例延期到迭代 2 | 待需求方和 SE 确认 | 待补充 | 未确认前不启动迭代 1 编码,不标记原始需求全部完成 |
117+| Operator 首轮方法名和 handle 所有权 | 待接口评审 | 待补充 | 未冻结前不进入 bridge 编码 |
118+| 首轮完整公开 API 和资料清单 | 待接口评审 | 待补充 | 未确认 `__all__`、类型声明、API 资料和 ST Sample 前不进入对外接口编码 |
119+| atc/session 初始化锚点和 bridge ABI/GIL | 待专项设计评审 | 待补充 | 未冻结前不进入初始化接入编码 |
120+| `ASCEND_CUSTOM_OPP_PATH` 复用 | 待 SE 专项评审 | 待补充 | 未确认路径扫描、初始化时序、插件共存、无插件回归和性能影响前,不冻结 loader 方案 |
121+ 
122+## 3. 高层 API 风格决策
123+ 
124+### 3.1 竞品证据边界
125+ 
126+| 项目 | 本地基线 | 可确认内容 | 证据限制 |
127+|---|---|---|---|
128+| PyTorch | `2.14.0a0`,commit `05dab25851a8b0b2925aed717dc010ddc643280c` | `symbolic``symbolic_multi_out``custom_translation_table`、公开/内部边界和废弃策略 | 语义是 ONNX 导出,不是 parser plugin |
129+| Torch-TensorRT | commit `8ab7654642750fab50101e04f0407604dca8014e` | schema、自动 converter、注册校验和 QDP 接入 | 语义是 PyTorch custom op 到 TensorRT Plugin |
130+| NVIDIA TensorRT | commit `a892d22267d9cd2dedc1a0893e6892ac901f6d3d`,VERSION `11.1.0.106` | ONNX Parser、ParserError、QDP、TensorDesc/ShapeExpr 和 Plugin Registry | ONNX Parser 的 C++ 实现在未检出的子模块中 |
131+ 
132+完整调研和源码证据见 [ONNX Plugin Python 化竞品调研](onnx_plugin_python_competitor_research.md)。
133+ 
134+### 3.2 逐维度决策
135+ 
136+| 维度 | 决策 | 借鉴内容 | 不采纳内容 | 对 GE 的影响 |
137+|---|---|---|---|---|
138+| 注册与发现 | 部分采纳 | 借鉴 TensorRT 装饰器可读性和 PyTorch 显式绑定 | 不要求每次 atc 手工传 table,不让多个回调重复填写插件名 | OPP 自动发现;registration coordinator 同时完成 creator 和 callback 注册 |
139+| 回调签名 | 采纳 | 参数语义直接出现在函数签名中 | 不使用万能 context | 每类 parser callback 使用独立窄入口 |
140+| 基础对象 | 采纳 | 直接使用 Tensor、TensorDesc 和普通值 | 不创建 View/Update 镜像对象 | 新增 `ge.graph.Operator`,按需扩展 |
141+| 属性表达 | 部分采纳 | 使用 Python 标量和同类型 list | 不继承 PyTorch 不支持 Tensor 属性的永久限制 | 属性范围按迭代开放;当前 GE proto 不包含 TYPE_PROTO(S) |
142+| 动态 IO/Shape | 部分采纳 | 直接表达并复用已有基础对象 | 不复制 `None`、定长 tuple 或新 Shape DSL | 动态端口通过 Operator 接口,shape 复用 TensorDesc |
143+| 高低层入口 | 采纳原则 | 公开层保持窄小,内部实现隐藏 | 首轮不同时设计两套 Python API | bridge、registry、GIL/ABI 全部内部化 |
144+| 用户文档 | 采纳 | examples-first,推荐与废弃路径分开 | 不在主体内联全部内部实现 | 主体短,细节进入附录 |
145+| 版本/废弃 | 采纳 | 文档标记、运行时 warning、迁移示例、移除版本 | 不只从 `__all__` 隐藏,不硬编码版本决定接口形状 | 形成稳定公共 API 演进规则 |
146+ 
147+### 3.3 注册形态
148+ 
149+采用“描述对象 + callback 装饰器”作为技术方案目标形态:
150+ 
151+```python
152+example = onnx_plugin(
153+ source="ExampleOp",
154+ domain="ai.onnx",
155+ opsets=range(10, 19),
156+ target="ExampleTarget",
157+)
158+ 
159+ 
160+@example.parse_node
161+def parse_example(node, target):
162+ target.set_attr("alpha", node.attrs.get("alpha", 1.0))
163+```
164+ 
165+该形态保留未来绑定多个 callback 的扩展位置,但首轮 `OnnxPlugin` 只公开 `parse_node`。注册描述对象不属于 Operator/TensorDesc 等基础数据结构的过度封装。
166+ 
167+### 3.4 Operator 直接修改
168+ 
169+公开回调直接操作 `ge.graph.Operator`
170+ 
171+```python
172+@example.parse_node
173+def parse_example(node, target):
174+ target.set_attr("name", node.name)
175+ target.set_attr("alpha", node.attrs.get("alpha", 1.0))
176+ target.register_input("x")
177+ target.register_optional_input("bias")
178+ target.register_output("y")
179+ target.register_dynamic_input("args", max(0, len(node.inputs) - 2))
180+ target.register_dynamic_output("extra_outputs", max(0, len(node.outputs) - 1))
181+```
182+ 
183+`set_attr` 和五个端口注册方法是方案中的目标命名,编码前仍需通过公开接口评审。固定端口已由 target 原型定义时无需重复注册;对于需要在解析期补充端口的 target,用户直接调用 Operator 方法,不增加端口对象或返回型 update 对象。`InputRegister``OptionalInputRegister``OutputRegister` 当前为 protected,编码时需增加不改变对象布局的 public 转发入口或采用经 graph 基础接口评审认可的等价机制,bridge 不得直接修改内部 `OpDesc`
184+ 
185+### 3.5 首轮公开接口评审清单
186+ 
187+首轮公开接口必须完整列举并整体评审,不能只评审 Elu 示例中出现的符号:
188+ 
189+| 公开接口 | 首轮公开内容 |
190+|---|---|
191+| `ge.onnx_plugin.onnx_plugin` | source、domain、opsets、target、imply_type 参数,返回 `OnnxPlugin` |
192+| `ge.onnx_plugin.ImplyType` | TVM、AI_CPU、CUSTOM、GELOCAL |
193+| `ge.onnx_plugin.OnnxNode` | name、op_type、inputs、outputs、attrs,只读和构造限制 |
194+| `ge.onnx_plugin.OnnxPlugin` | `parse_node`,不公开直接构造和 registry 状态 |
195+| `ge.graph.Operator` | name、type、get_attr、set_attr、register_input、register_optional_input、register_output、register_dynamic_input/output,handle 失效和线程语义 |
196+| 模块导出 | `ge.onnx_plugin.__all__``ge.graph.__all__``.pyi` 类型声明 |
197+ 
198+首轮不新增公开异常类,使用内置 `TypeError``ValueError``RuntimeError`。registry、bootstrap、coordinator、bridge 和 C API 都保持内部可见。
199+ 
200+接口评审通过后必须同步 API Reference、用户指南、C++ 迁移指南、类型提示、Elu/Sum 入门 Sample 和 ST/系统测试 Sample。ST Sample 使用测试专用 domain/source,从插件文件和 ONNX 模型开始经过真实 loader/parser,不能只调用内部 registry 或 bridge helper。
201+ 
202+## 4. 开发分期
203+ 
204+### 4.1 接口冻结阶段
205+ 
206+编码前完成:
207+ 
208+1. 评审 `onnx_plugin` 描述对象和 `parse_node` 签名。
209+2. 评审 ImplyType、OnnxNode、OnnxPlugin、`ge.graph.Operator` 全部首轮成员、构造限制、`__all__` 和类型声明。
210+3. 确认 parser 和 online/session 初始化时调用统一 Python loader、bridge 和 registration coordinator 的锚点。
211+4. 冻结独立 bridge SO 的构建、安装、版本匹配和 GIL 策略;NodeProto 在 parser 侧扁平化,bridge 不直接依赖私有 protobuf。
212+5. 冻结 creator 与全部 callback map 的 Python/C++ 冲突策略、失败状态和错误信息。
213+6. 以 Elu 等价逻辑作为首轮属性 ST,以 Sum 等价逻辑作为动态输入 ST,以合成 target 验证 required、optional 和普通 output 注册。
214+7. 评审接口资料目录和 ST Sample,确认测试专用 origin、模型生成、执行命令、Graph 检查和泛化因子。
215+ 
216+### 4.2 迭代 1:参数解析主链
217+ 
218+实现插件发现、descriptor registry、`ParseParamsFn` wrapper、OnnxNode 转换、Operator 直接 setter、固定端口和默认动态端口注册。首轮只交付第 2.2 节标记为“支持”的能力。
219+ 
220+### 4.3 迭代 2:图展开与描述符
221+ 
222+| 能力 | 代表场景 | 设计方向 |
223+|---|---|---|
224+| `ParseOpToGraphFn` | ThresholdedRelu、Int8Transpose | 回调返回 `ge.graph.Graph`,构图复用 ES |
225+| `ParseParamsByOperatorFn` | NPUAntiQuant | source/target 都使用 `ge.graph.Operator`,通过参数语义区分只读与可写 |
226+| TensorDesc | NPUAntiQuant | 复用 `TensorDesc` setter,并明确同步到所属 Operator 的机制 |
227+| Tensor/DataType 属性 | Int8Transpose 等 | 复用 `ge.graph.Tensor/DataType` 和现有 `_AttrValue` |
228+ 
229+仓内同时包含三类输入的真实原型包括 `MultiAddRmsNormDynamicQuant`:动态 `x1`、必选 `x2/gamma`、可选 `smooth_scale1/smooth_scale2`,见 `ops-nn/norm/multi_add_rms_norm_dynamic_quant/op_graph/multi_add_rms_norm_dynamic_quant_proto.h:70-83`。该算子当前没有 ONNX plugin,因此只用于说明端口职责,不伪造现有插件;首轮使用 Sum 验证动态实例注册,并使用合成 target 验证 required、optional 和普通 output 的直接注册。
230+ 
231+迭代 2 必须在编码前补齐 ThresholdedRelu 的完整 ES 设计示例,明确 `GraphBuilder.create_input``Identity``Threshold``Mul``build_and_reset(outputs=...)` 的实际调用方式。该示例是迭代 2 的设计交付,不扩大首轮代码范围。
232+ 
233+### 4.4 迭代 3:低频与高级能力
234+ 
235+按真实迁移需求评审 `ParseSubgraphPostFn`、输入删除/重排、动态 TensorDesc、稀疏属性、属性引用、source Operator 写回和 ES 内部算子描述符修改。没有真实用例和调用链证据时不新增公开接口。
236+ 
237+## 5. 技术方案重写要求
238+ 
239+### 5.1 主体顺序
240+ 
241+1. 首轮完整用户示例。
242+2. 首轮范围和不支持项。
243+3. 公开 API 及直接 Operator 修改语义。
244+4. 插件发现、注册和 `parse_node` 主流程。
245+5. 模块改动和错误处理。
246+6. 开发分期和非目标。
247+ 
248+### 5.2 附录内容
249+ 
250+| 附录 | 内容 |
251+|---|---|
252+| C++ 映射 | OpRegistrationData、Operator、AttributeProto、Graph/ES 的完整能力表 |
253+| bridge 细节 | SO/ABI、初始化锚点、GIL、生命周期和注册冲突 |
254+| 后续迭代 | 另外三类 callback、TensorDesc、ES、特殊属性和低频能力 |
255+| 测试与性能 | UT/ST 分层、无插件回归、编译时延和 Host 内存 |
256+| 竞品证据 | PyTorch、Torch-TensorRT 和 NVIDIA TensorRT 本地源码路径索引 |
257+| 评审原文 | PR 2 四条行内评论和 mentor 补充意见 |
258+ 
259+## 6. 旧设计处理决定
260+ 
261+| 旧设计 | 处理 | 原因 |
262+|---|---|---|
263+| `OperatorView` | 删除 | 与基础 `Operator` 重复 |
264+| `OperatorUpdate` | 删除 | 回调改为直接 setter |
265+| `TensorDescUpdate` | 删除 | 复用 TensorDesc 和 Operator 接口 |
266+| `DynamicPort` | 删除 | 动态端口通过 Operator 直接注册 |
267+| `DynamicTensorDescRef` | 删除/延后 | 首轮不支持动态 TensorDesc |
268+| `ExpandResult.source_update` | 删除 | source 写回进入后续独立评审 |
269+| bridge 集中 apply | 删除公开语义 | setter 直接写入当前目标 Operator |
270+| 公开事务/回滚承诺 | 删除 | ParseParams 失败后当前 Operator 不加入 Graph,不需要用户侧事务模型 |
271+| 四类 callback 同期交付 | 改为分期 | 调用量为 175/7/74/1,首轮优先最高频且最简单主链 |
272+| 完整属性类型同期交付 | 改为分期 | 首轮只开放已验证常用类型 |
273+ 
274+## 7. 验收标准
275+ 
276+### 7.1 需求与方案
277+ 
278+- [x] 技术方案首轮范围与第 2.2 节一致。
279+- [x] 主体前部提供完整、单屏可理解的用户示例。
280+- [x] 无 `OperatorView``OperatorUpdate``TensorDescUpdate``DynamicPort``ExpandResult` 公开设计。
281+- [x] 公开对象使用 `ge.graph.Operator` 和现有 Python/graph 基础类型。
282+- [x] 未支持 callback、属性和端口能力有明确错误行为或不存在性说明。
283+- [x] 后续能力只说明迭代和影响范围,不提前冻结接口。
284+- [x] 首轮公开接口清单覆盖 onnx_plugin、ImplyType、OnnxNode、OnnxPlugin、Operator、模块导出和类型声明。
285+- [x] 已要求接口评审后补充 API Reference、用户指南、迁移指南和非 UT 的 ST/系统测试 Sample。
286+ 
287+### 7.2 竞品分析
288+ 
289+- [x] PyTorch 分析基于本地 2.14.0a0 源码,未将废弃的 `register_custom_op_symbolic` 作为当前基线。
290+- [x] TensorRT 分析覆盖 Torch-TensorRT 和 NVIDIA TensorRT 核心仓,并标明 ONNX Parser 子模块的证据边界。
291+- [x] 注册、回调、基础对象、属性、动态 IO、分层、文档和废弃策略均有明确决策。
292+ 
293+### 7.3 设计检查
294+ 
295+- [x] 按设计模板覆盖所有章节。
296+- [x] 按 `cross_feature_check.md` 逐项分析五类场景。
297+- [x] 加载并遵守 `ascend-ir.md``graph_metadef.md` 的基础结构约束。
298+- [x] 明确不修改 AscendIR、OM、compiler 和 runtime。
299+- [x] 明确基础图对象单线程修改约束和 Python handle 生命周期。
300+- [ ] `ASCEND_CUSTOM_OPP_PATH` 复用已完成 SE 专项评审,路径扫描、初始化时序、插件共存、无插件回归和性能影响已确认。
301+ 
302+## 8. 待冻结项
303+ 
304+| 决策项 | 阻塞阶段 | 当前要求 |
305+|---|---|---|
306+| Operator 方法准确命名 | 迭代 1 编码 | 公开接口评审确认 |
307+| 完整公开面和资料 | 迭代 1 对外接口编码 | 冻结 5 个公开符号、全部成员、`__all__``.pyi`、API 资料和 ST Sample |
308+| Operator C++ handle 所有权 | 迭代 1 编码 | 回调期有效,不暴露裸指针,不允许越过回调持有 |
309+| parser 初始化锚点 | bridge 编码 | 必须早于首次读取 OpRegistry |
310+| bridge SO/ABI/安装归属 | bridge 编码 | 不把 Python 依赖引入 graph_metadef |
311+| GIL 和多模型并发 | bridge 编码 | 不新建线程,明确调用方并发模型 |
312+| Python/C++ 注册冲突策略 | registry 编码 | 预检查 creator 和全部 callback map;任一冲突均拒绝 Python,保留 C++,输出 key/map 诊断 |
313+| 首轮真实插件样例 | parser ST | 已选 Elu 属性逻辑和 Sum 动态输入逻辑;测试使用专用 origin key |
314+| 性能阈值 | 性能验收 | SE/性能责任人冻结,否则仅观测 |
315+| ThresholdedRelu 延期确认 | 迭代 1 编码前 | 需求方和 SE 确认原始完整示例调整到迭代 2 |
316+ 
317+## 附录 A:评审原文
318+ 
319+### A.1 Operator 命名与归属
320+ 
321+> 可以就叫Operator,放在ge的graph模块下,这样后续可以按需扩展其他场景需要的能力到operator.py,直到跟ge::Operator能力基本一致
322+ 
323+### A.2 直接 setter
324+ 
325+> Operator不要定位为只读+Update才能更改了,这样比较别扭; TensorDescUpdate一样的道理,就提供set接口让用户自己调用,然后更新后的tensordesc和持有它的operator对象
326+ 
327+### A.3 动态端口
328+ 
329+> DynamicPort的封装没有必要,提供一个用例 当ir既有可选,必选,动态输入的时候 这个函数应该怎么写
330+ 
331+### A.4 迭代范围
332+ 
333+> 事情列的挺多,一个迭代做不完,建议你列一个表单,我们当前迭代支持哪些parsr接口,接口里面支持哪些类型的属性设置 ; 其他的暂不支持; 第一个迭代打通流程和涵盖基本的插件注册的写法即可
334+ 
335+### A.5 Mentor 补充意见
336+ 
337+1. Operator、TensorDesc、动态输入输出等基础数据结构不需要过度封装。
338+2. 用户侧 Python 插件完整用例放在文章前部;主体保持连贯精简,细节放入附录并由主体引用。
339+3. 冻结高层用户界面前参考 PyTorch 和 TensorRT 当前 Python 插件风格;按优先级安排迭代,延后项只确定影响边界,不提前细化全部接口。
340+4. 首轮直接支持 `Operator` 的 required、optional、普通 output 和动态端口注册,不增加端口封装。
341+5. 注册枚举和参数使用 `ImplyType` / `imply_type`,不再使用原有的宽泛命名。
342+ 
343+## 附录 B:竞品源码证据索引
344+ 
345+### B.1 PyTorch
346+ 
347+| 主题 | 本地源码位置 |
348+|---|---|
349+| 公开 API 与 export 参数 | `pytorch/torch/onnx/__init__.py:5-151` |
350+| 新旧 exporter 分流 | `pytorch/torch/onnx/__init__.py:273-356` |
351+| symbolic 单/多输出 | `pytorch/torch/onnx/ops/__init__.py:73-279` |
352+| translation table 注入 | `pytorch/torch/onnx/_internal/exporter/_compat.py:149-177` |
353+| translation table 测试 | `pytorch/test/onnx/exporter/test_api.py:443-534` |
354+| symbolic 属性测试 | `pytorch/test/onnx/ops/test_ops.py:94-193` |
355+| 用户文档和废弃 API | `pytorch/docs/source/onnx.md:74-117` |
356+ 
357+### B.2 Torch-TensorRT
358+ 
359+| 主题 | 本地源码位置 |
360+|---|---|
361+| 仓库身份和依赖版本 | `TensorRT/README.md:3-11` |
362+| QDP 推荐路径 | `TensorRT/docsrc/tutorials/extensibility/plugins/plugins.rst:3-56` |
363+| register + impl 样例 | `TensorRT/examples/dynamo/auto_generate_converters.py:100-151` |
364+| register + aot_impl 样例 | `TensorRT/examples/dynamo/aot_plugin.py:95-194` |
365+| schema/注解自动生成 | `TensorRT/py/torch_tensorrt/dynamo/conversion/plugins/_generate_plugin.py:98-270` |
366+| registry、AOT 和多输出 | `TensorRT/py/torch_tensorrt/dynamo/conversion/plugins/_generate_plugin_converter.py:75-180` |
367+| QDP feature gate | `TensorRT/py/torch_tensorrt/_features.py:66-77,156-169` |
368+ 
369+### B.3 NVIDIA TensorRT
370+ 
371+核心 TensorRT 的 ONNX Parser、ParserError、QDP 和 Plugin Registry 证据索引统一维护在 [竞品调研附录 B](onnx_plugin_python_competitor_research.md#附录-b源码证据索引),避免在决策文档重复维护完整路径表。
372+ 
373+## 附录 C:本地实现事实
374+ 
375+- `ge.graph._AttrValue.set_value` 已支持 bool/int/float/str、DataType、Tensor 和对应常用 list,见 `api/python/ge/ge/graph/_attr.py:273-304`
376+- `ge.graph.Node` 已提供 Pythonic attr 和 TensorDesc getter/update 风格,见 `api/python/ge/ge/graph/node.py:195-239,416-480`;新 `Operator` 应复用风格而不是复用 parser 无关的 GNode 语义。
377+- `ge.graph.Node` 包装已入图的 `ge::GNode`,新增 `ge.graph.Operator` 包装 parser callback 期的 `ge::Operator`;两者 handle 和生命周期不同,不能通过 Node API 代替 Operator。
378+- TensorDesc setter 返回 self 并支持链式调用,见 `api/python/ge/ge/graph/tensor_desc.py:195-320`
379+- `ParseParams` 失败后 `ParseAllNodeProto` 直接返回,当前 Operator 不会执行 `graph.AddOp`,见 `parser/parser/onnx/onnx_parser.cc:596-676`
380+- 现有共享 loader 已按环境变量加载 `.py` 或 package,见 `api/python/ge/ge/_internal/plugin_loader.py:26-100`;ONNX 插件应复用扫描能力,不新增环境变量或递归规则。
381+- `AclGraphParserUtil::AclParserInitialize` 当前按 `Finalize``Register` 的顺序注册,见 `parser/parser/common/acl_graph_parser_util.cc:247-253`;Python coordinator 必须检查两者返回值。
382+- `OpRegistrationTbe::Finalize` 负责 parser creator,见 `parser/parser/common/op_registration_tbe.cc:40-142``OpRegistry::Register` 负责 callback/run-mode map,见 `graph_metadef/register/register.cpp:1050-1092`
383+- ONNX parser 先调用 `OpParserFactory::CreateOpParser`,再调用 `ParseParams`,见 `parser/parser/onnx/onnx_parser.cc:657-664`
384+- GE 当前 ONNX `AttributeType` 不含 `TYPE_PROTO(S)`,枚举见 `graph_metadef/proto/onnx/ge_onnx.proto:101-116``ref_attr_name` 是独立属性引用字段,见同文件 `:121-125`
385+- `ConstructOriType` 允许原始 NodeProto domain 为空,并根据 opset 上下文构造 `ai.onnx::version::op_type`,见 `parser/parser/onnx/onnx_parser.cc:412-451`
386+- `ImplyType` 还包含 BUILTIN/BUILDIN、CCE、HCCL、INVALID,见 `metadef/inc/external/graph/types.h:434-443`;首轮排除项必须在方案中解释。
@@ -0,0 +1,198 @@
1+# atc 编译 ONNX 实验记录
2+ 
3+> 日期: 2026-07-26
4+> 环境: CANN 9.1.0 release (目录名 cann-9.2.0) + cann-910b-ops 9.1.0 + GE master + Ascend910B
5+> 目的: 直观理解 atc 解析 ONNX 的完整流程,搞清"解析插件"和"算子实现"两层的边界
6+ 
7+---
8+ 
9+## 关键结论(先看这个)
10+ 
11+### 1. 解析插件 vs 算子实现 —— 两个独立的层(重要认知更新)
12+ 
13+这是本次实验最大的收获。需求文档用 ThresholdedRelu 做示例,容易让人以为"ThresholdedRelu 还没有解析插件,我们要 Python 化它"。**实测发现不是这样**
14+ 
15+| 层 | 职责 | ThresholdedRelu 现状 | 本次需求 Python 化的目标 |
16+|----|------|---------------------|------------------------|
17+| **解析插件** | ONNX 算子 → GE 子图翻译(parser 期) | ✅ ops 包已内置 | ← **这一层** |
18+| **算子实现** | GE 算子怎么在硬件执行(compiler/executor 期) | ❌ Threshold 缺 kernel | 不是这一层(属 custom_op 范畴) |
19+ 
20+**Python 化的实际价值**:让用户能用 Python 写**自定义算子**的解析插件(ops 包没内置的那些),而不是重写已有的内置插件。需求用 ThresholdedRelu 做示例只是因为它直观易懂。
21+ 
22+### 3. ops 包内置的 ONNX 解析插件位置
23+ 
24+ThresholdedRelu 解析插件在 ops 包的 .so 里(不在 GE 仓源码):
25+```
26+$ASCEND_OPP_PATH/built-in/framework/onnx/liboponnx_plugin_nn.so
27+$ASCEND_OPP_PATH/built-in/framework/onnx/liboponnx_plugin_legacy.so
28+```
29+这些 .so 内部用 `REGISTER_CUSTOM_OP` 注册了 ThresholdedRelu 的 `ParseParamsFn` + `ParseOpToGraphFn`
30+ 
31+**呼应 dev-process-notes.md 要点 1**:又一次印证"仓内找不到的,先去 ops 包找"。ThresholdedRelu 解析插件的 C++ 源码不在 GE 仓,但产物在 ops 包 .so 里。
32+ 
33+---
34+ 
35+## 实验 1:Clip v9(内置算子)—— 全程成功
36+ 
37+**模型**: `tests/parser/st/testcase/origin_models/onnx_clip_v9.onnx`
38+- 1 个 `Clip` 节点,origin type = `ai.onnx::9::Clip`
39+- 属性 min=-1.0, max=1.0
40+- 输入 X [3,4,5] float → 输出 Y [3,4,5] float
41+ 
42+**命令**:
43+```bash
44+cd /tmp/opencode/atc_test
45+cp /path/to/ge/tests/parser/st/testcase/origin_models/onnx_clip_v9.onnx .
46+atc --model=./onnx_clip_v9.onnx --framework=5 --output=./clip_v9_test --soc_version=Ascend910B
47+```
48+ 
49+**结果**: `ATC run success`
50+**产物**: `clip_v9_test.om`(12502 字节)
51+ 
52+**说明**: Clip v9 是 GE 内置算子,parser 直接识别,无需自定义插件,全程成功。
53+ 
54+---
55+ 
56+## 实验 2:ThresholdedRelu(需求示例算子)—— 解析成功,编译失败
57+ 
58+**模型**: 自构造 `onnx_thresholded_relu.onnx`
59+- 1 个 `ThresholdedRelu` 节点,origin type = `ai.onnx::11::ThresholdedRelu`
60+- 属性 alpha=1.0
61+- 输入 X [3,4,5] float → 输出 Y [3,4,5] float
62+ 
63+构造脚本:
64+```python
65+import onnx
66+from onnx import helper, TensorProto, OperatorSetIdProto
67+opset = OperatorSetIdProto(); opset.domain=""; opset.version=11
68+graph = helper.make_graph(
69+ nodes=[helper.make_node("ThresholdedRelu", ["X"], ["Y"], name="thr_relu", alpha=1.0)],
70+ name="thr_relu_graph",
71+ inputs=[helper.make_tensor_value_info("X", TensorProto.FLOAT, [3,4,5])],
72+ outputs=[helper.make_tensor_value_info("Y", TensorProto.FLOAT, [3,4,5])],
73+)
74+model = helper.make_model(graph, opset_imports=[opset], producer_name="test")
75+onnx.save(model, "onnx_thresholded_relu.onnx")
76+```
77+ 
78+**命令**:
79+```bash
80+atc --model=./onnx_thresholded_relu.onnx --framework=5 --output=./thr_relu_no_plugin --soc_version=Ascend910B
81+```
82+ 
83+**结果**: `ATC run failed`
84+ 
85+**报错**:
86+```
87+Unsupported_Operator(EZ3003): No supported Ops kernel and engine are found
88+ for [PartitionedCall_thr_relu_threshold_2], optype [Threshold].
89+Possible Cause: The operator is not supported by the system.
90+Solution: 1. Check that the OPP component is installed properly.
91+ 2. Submit an issue to request for the support of this operator type.
92+```
93+ 
94+### 报错信息透露的三件事
95+ 
96+1. **节点名 `PartitionedCall_thr_relu_threshold_2`** —— 说明 ThresholdedRelu 已被解析成 `PartitionedCall`(GE 算子类型),子图里有 `threshold` 节点。这正是需求文档描述的 `Data→Identity→Threshold→Mul` 展开方式。
97+ 
98+2. **解析插件已经存在于 ops 包**(路径见上方"关键结论"第 3 点)。parser 找到了它,调用了它的 ParseParams + ParseOpToGraph。
99+ 
100+3. **失败点在"算子实现"层,不在"解析插件"层**
101+ - ✅ 解析阶段成功:parser 找到内置插件 → 调用 ParseParams(设 alpha 属性)+ ParseOpToGraph(展开子图)→ 生成 PartitionedCall + 子图
102+ - ❌ 编译阶段失败:子图里的 `Threshold` 算子没有 kernel 实现(没有 ops kernel)
103+ 
104+### 链路可视化
105+ 
106+```
107+实验1 (Clip v9, 内置算子):
108+ onnx → parser 找到内置 Clip 解析 → 生成 GE 图 → 编译成功 → OM ✅
109+ 
110+实验2 (ThresholdedRelu):
111+ onnx → parser 找到内置 ThresholdedRelu 解析插件 (在 ops 包 .so)
112+ → ParseParams: ThresholdedRelu → PartitionedCall, 设 alpha 属性
113+ → ParseOpToGraph: 展开 Data→Identity→Threshold→Mul 子图
114+ → 编译阶段: Threshold 算子找不到 kernel → 失败 ❌
115+ (解析插件工作正常, 卡在算子实现层)
116+```
117+ 
118+---
119+ 
120+## 下一步
121+ 
122+- 实验 3:构造一个 ops 包**没有内置解析插件**的 ONNX 算子(如 `com.example::1::MyOp`),跑 atc 看真正的 "PARAM_INVALID: 不认识算子" 报错,这才是真正需要写解析插件的场景。
123+ 
124+---
125+ 
126+## 实验 3:自定义算子 com.example::1::MyCustomOp —— 真正的"无解析插件"场景
127+ 
128+**模型**: 自构造 `onnx_my_custom_op.onnx`
129+- 1 个 `MyCustomOp` 节点,origin type = `com.example::1::MyCustomOp`
130+- 属性 alpha=2.5 (float), beta=10 (int)
131+- 输入 X [3,4,5] float → 输出 Y [3,4,5] float
132+ 
133+**构造脚本**:
134+```python
135+import onnx
136+from onnx import helper, TensorProto, OperatorSetIdProto
137+opset = OperatorSetIdProto(); opset.domain="com.example"; opset.version=1
138+graph = helper.make_graph(
139+ nodes=[helper.make_node("MyCustomOp", ["X"], ["Y"], name="my_op", alpha=2.5, beta=10)],
140+ name="my_custom_op_graph",
141+ inputs=[helper.make_tensor_value_info("X", TensorProto.FLOAT, [3,4,5])],
142+ outputs=[helper.make_tensor_value_info("Y", TensorProto.FLOAT, [3,4,5])],
143+)
144+model = helper.make_model(graph, opset_imports=[opset], producer_name="test")
145+onnx.save(model, "onnx_my_custom_op.onnx") # 跳过 check_model (自定义算子不在 onnx schema)
146+```
147+ 
148+**注意**: atc 不允许多个 opset_import(`The model has 2 --domain_version fields`),所以不能同时带 `ai.onnx` 和 `com.example` 两个 opset,只能用单自定义域。
149+ 
150+**命令**:
151+```bash
152+atc --model=./onnx_my_custom_op.onnx --framework=5 --output=./my_custom_op_no_plugin --soc_version=Ascend910B
153+```
154+ 
155+**结果**: `ATC run failed`
156+ 
157+**报错**:
158+```
159+Not_Supported_Operator(E13010): No operator plugin is registered
160+ for Op: my_op, optype: com.example::1::MyCustomOp.
161+Solution: 1. If the operator is a custom operator, register related deliverables.
162+ 2. If the operator is a built-in operator, install the package that supports this operator version.
163+TraceBack: Model parse to graph failed [onnx_parser.cc:956]
164+ ATC model parse ret fail [omg.cc:838]
165+```
166+ 
167+### 与实验 2 的对比(关键)
168+ 
169+| | 实验 2 (ThresholdedRelu) | 实验 3 (MyCustomOp) |
170+|---|---|---|
171+| 算子 origin type | `ai.onnx::11::ThresholdedRelu` | `com.example::1::MyCustomOp` |
172+| 解析插件存在? | ✅ ops 包内置 | ❌ 没有 |
173+| 失败阶段 | **编译阶段**(解析已成功) | **解析阶段**(根本没解析进去) |
174+| 报错码 | `EZ3003` (Unsupported_Operator) | `E13010` (Not_Supported_Operator) |
175+| 报错位置 | `engine_place.cc:150` | `onnx_parser.cc:956` |
176+| 报错语义 | "找不到算子 kernel 实现" | "没有注册算子插件" |
177+ 
178+**实验 3 才是真正需要写解析插件的场景**。Python 化要解决的就是这个:让用户能用 Python 写一个插件,告诉 parser "遇到 `com.example::1::MyCustomOp` 时怎么办"。
179+ 
180+### 三次实验对应的链路位置
181+ 
182+```
183+[ONNX 模型]
184+
185+
186+[Parser 阶段] ← 实验 3 在这里失败 (没有解析插件, 算子根本进不了 GE 图)
187+ │ 找解析插件 → 调 ParseParams + ParseOpToGraph → 生成 GE 子图
188+
189+[Compiler 阶段] ← 实验 2 在这里失败 (解析成功, 但子图里的 Threshold 算子没有 kernel)
190+ │ 算子编译、引擎分配、内存规划
191+
192+[Executor 阶段] ← 实验 1 走到这里成功 (生成 OM)
193+ │ 模型加载、执行
194+
195+[OM 产物]
196+```
197+ 
198+**Python 化 ONNX Plugin 的目标**:在 Parser 阶段,让用户能用 Python 写解析插件,把实验 3 的失败变成实验 2 的状态(解析成功)。至于实验 2 那种"解析成功但算子缺 kernel"的情况,是 custom_op(算子实现)要解决的,不在本次需求范围内。
@@ -0,0 +1,110 @@
1+# custom_op 样例运行记录
2+ 
3+> 日期: 2026-07-22
4+> 环境: CANN 9.2.0 + cann-910b-ops 9.2.0 + GE master + Ascend910_9362
5+ 
6+## 总览
7+ 
8+| # | 样例 | 类型 | 框架依赖 | 结果 | 说明 |
9+|---|------|------|----------|------|------|
10+| 1 | args_refresh_add_custom (python) | Python | 无 | 通过 | GE Session + Python ES API |
11+| 2 | compilable_add_custom | C++ | 无 | 通过 | AIR -> ATC -> OM -> ACL |
12+| 3 | data_dependent_shape_custom | C++ | 无 | 通过 | GE Session + 动态 shape |
13+| 4 | ascendc_add_custom | Python | PyTorch + torch_npu + TorchAir | 通过 | TorchAir 7.3.0 源码编译安装 |
14+| 5 | triton_add_custom | Python | TensorFlow + Triton-Ascend | 跳过 | TensorFlow/Triton 均未安装 |
15+ 
16+**4/5 样例通过,1 个因缺少框架依赖跳过。**
17+ 
18+---
19+ 
20+## 1. args_refresh_add_custom (python) — 通过
21+ 
22+```bash
23+cd examples/custom_op/args_refresh_add_custom/python
24+bash run.sh
25+```
26+ 
27+**输出**:
28+```
29+[Sample] graph added, graph_id=0
30+[PythonCustomOp] acl.rt.launch_kernel_with_config ret=0
31+[Sample] run_graph finished, outputs=1
32+[Sample] output shape=[1024], dtype=0, format=2
33+[INFO] Python session sample finished.
34+```
35+ 
36+**流程**: bisheng 编译 kernel -> cmake 生成 ES whl -> pip install -> Session.run_graph
37+ 
38+---
39+ 
40+## 2. compilable_add_custom — 通过
41+ 
42+```bash
43+cd examples/custom_op/compilable_add_custom
44+bash run.sh
45+```
46+ 
47+**输出**:
48+```
49+[INFO] Model executed successfully!
50+[INFO] First element of output: 3.000000
51+[INFO] Sample pipeline finished.
52+```
53+ 
54+**流程**: cmake 编译 -> graph_build 导出 AIR -> ATC 转 OM -> model_exec 执行
55+ 
56+**注意**: run.sh 中 `soc_version=Ascend910B1` 硬编码,实际芯片 Ascend910_9362 可正常工作。
57+ 
58+---
59+ 
60+## 3. data_dependent_shape_custom — 通过
61+ 
62+```bash
63+cd examples/custom_op/data_dependent_shape_custom
64+bash run.sh
65+```
66+ 
67+**输出**:
68+```
69+output shape: [4, 1]
70+output values: 0 2 4 7
71+[INFO] Sample pipeline finished.
72+```
73+ 
74+**流程**: cmake 编译 (含 Ascend C kernel) -> Session::RunGraph
75+ 
76+**验证**: 输入 `[true,false,true,false,true,false,false,true]`,输出 4 个 true 的索引 `[0,2,4,7]`,shape `[4,1]` 正确。
77+ 
78+---
79+ 
80+## 4. ascendc_add_custom — 通过
81+ 
82+**依赖检查**:
83+- torch 2.7.1+cpu — 已安装
84+- torch_npu 2.7.1.post4 — 已安装,`torch.npu.is_available()` 返回 True
85+- TorchAir 7.3.0 — 从源码编译安装(gitcode.com/Ascend/torchair, 7.3.0 分支)
86+ 
87+**编译**: cmake + make 成功,生成 `libcust_opapi.so`
88+ 
89+**运行**: `python3 add_custom_test.py`
90+ 
91+**输出**:
92+```
93+Ran 1 test in 2.963s
94+OK
95+```
96+ 
97+**Eager 模式**: 自定义算子在前端执行正确,精度校验通过。
98+**图模式**: TorchAir `get_npu_backend` + `torch.compile` 图模式执行正确,精度校验通过。
99+ 
100+**安装过程**: 详见 [ge-env-setup.md §7.1](../env/ge-env-setup.md#71-torchairascendc_add_custom-依赖)(7.3.0 分支源码编译)
101+ 
102+---
103+ 
104+## 5. triton_add_custom — 跳过 (TensorFlow/Triton 缺失)
105+ 
106+**依赖检查**:
107+- TensorFlow — **未安装**
108+- Triton-Ascend — **未安装**
109+ 
110+**未尝试运行。**
@@ -0,0 +1,445 @@
1+# GE 开发环境完整配置指南
2+ 
3+> 本文记录了从零开始配置 GE (Graph Engine) 开发环境的完整过程,包括踩过的所有坑和最终可用的配置。
4+> 最后更新: 2026-07-22
5+ 
6+---
7+ 
8+## 1. 环境信息
9+ 
10+### 硬件
11+ 
12+| 项目 | 值 |
13+|------|-----|
14+| OS | Ubuntu 20.04.5 LTS (aarch64) |
15+| 芯片 | Ascend910 (npu-smi 显示名) |
16+| SoC 名称 | Ascend910_9362 |
17+| FE 平台名 | ascend910_93 |
18+| NPU Arch | 2201 (dav-2201) |
19+| 驱动版本 | 25.5.5 |
20+| HBM | 65536 MB |
21+ 
22+### 软件
23+ 
24+| 项目 | 值 | 说明 |
25+|------|-----|------|
26+| GE 仓库分支 | master | gitcode.com/cann/ge |
27+| CANN Toolkit | **9.1.0 release** | 见下方"版本真相"说明 |
28+| Ops 包 | cann-910b-ops 9.1.0 | 与 toolkit 同版本 |
29+| Python | 3.11.4 | /opt/buildtools/Python-3.11.4 |
30+| torch | 2.7.1+cpu | pip 包 |
31+| torch_npu | 2.7.1.post4 | pip 包,来源 gitcode.com/ascend/pytorch |
32+| bash | 5.2.37 | 从源码编译升级(Ubuntu 20.04 默认 5.0.17 不够) |
33+ 
34+### CANN 版本真相(重要!)
35+ 
36+**目录名 ≠ 实际版本**。这是最容易误导的地方,务必搞清楚:
37+ 
38+| 安装目录名 | 实际版本 | 构建时间 | 说明 |
39+|-----------|----------|----------|------|
40+| `cann-9.2.0/` | **9.1.0 release** | 2026-07-15 | **当前唯一安装**。目录名叫 9.2.0 是因为 weekly 构建先升了包名,但内部 40+ 子包的 version.info 仍是 9.1.0。仅 pyACL 和 test-ops 标了 9.2.0 |
41+ 
42+> 旧的 `cann-9.1.0-beta.3/` 已于 2026-07-22 删除,释放 10 GB 磁盘空间。
43+ 
44+**当前生效环境**(3 个 custom_op 样例跑通用的环境):
45+```
46+/home/developer/Ascend/cann → cann-9.2.0/cann → cann-9.2.0/cann-9.2.0
47+ASCEND_HOME_PATH = /home/developer/Ascend/cann-9.2.0/cann-9.2.0
48+实际版本 = 9.1.0 release (2026-07-15 构建)
49+```
50+ 
51+**如何自行验证版本**:
52+```bash
53+cat $ASCEND_OPP_PATH/version.info # 查看主版本
54+cat $ASCEND_HOME_PATH/opp/version.info # 同上
55+$ASCEND_HOME_PATH/query_pkg_version.sh # 查看所有子包版本
56+```
57+ 
58+---
59+ 
60+## 2. 从零安装完整步骤
61+ 
62+> 假设是一个全新的 Ubuntu 20.04 aarch64 环境,已有 NPU 驱动(`npu-smi info` 可用),无任何 CANN 安装。
63+ 
64+### 2.1 系统依赖安装
65+ 
66+```bash
67+# 基础编译工具
68+sudo apt-get update
69+sudo apt-get install -y curl wget git cmake make g++ ccache \
70+ autoconf automake libtool gperf openssh-server \
71+ python3-dev zlib1g
72+ 
73+# 升级 bash(Ubuntu 20.04 默认 5.0.17,GE 脚本需要 >= 5.1.16)
74+wget https://ftp.gnu.org/gnu/bash/bash-5.2.37.tar.gz
75+tar xzf bash-5.2.37.tar.gz
76+cd bash-5.2.37
77+./configure && make -j$(nproc)
78+sudo cp bash /usr/local/bin/bash
79+sudo mv /bin/bash /bin/bash.old
80+sudo ln -sf /usr/local/bin/bash /bin/bash
81+cd ..
82+```
83+ 
84+### 2.2 使用官方脚本安装 CANN Toolkit + Ops
85+ 
86+GE 仓库提供了 `scripts/init_env.sh` 一键安装脚本:
87+ 
88+```bash
89+cd /path/to/ge # GE 仓库根目录
90+ 
91+# 安装 toolkit + 910b ops 包(从 master 镜像自动下载最新版本)
92+# --install-path 可自定义安装路径,默认 /usr/local/Ascend
93+bash scripts/init_env.sh --chip-type 910b --install-path /home/developer/Ascend
94+```
95+ 
96+`init_env.sh` 做了什么:
97+1.`https://ascend.devcloud.huaweicloud.com/artifactory/cann-run-mirror/software/master` 获取最新时间戳目录
98+2. 下载 `Ascend-cann-toolkit_<version>_linux-aarch64.run`
99+3. 下载 `Ascend-cann-910b-ops_<version>_linux-aarch64.run`
100+4. 安装 toolkit(`--full` 模式)
101+5. 安装 ops 包(`--install` 模式)
102+6. 安装系统依赖和 Python 测试依赖(pytest, coverage, pybind11)
103+ 
104+**版本说明**: master 镜像的包名可能是 `9.2.0` 或更高,但内部子包 version.info 仍可能是 `9.1.0`。这是 CANN 开发周期的正常现象——**以 version.info 中的实际版本号为准,不要以目录名或包名判断版本**
105+ 
106+### 2.3 手动安装方式(如果 init_env.sh 不可用)
107+ 
108+如果需要手动指定版本或路径:
109+ 
110+```bash
111+# 1. 浏览镜像目录,找到最新时间戳
112+# https://ascend.devcloud.huaweicloud.com/artifactory/cann-run-mirror/software/master/
113+# 时间戳格式如 20260715_000325483
114+ 
115+# 2. 下载 toolkit 和 ops
116+INSTALL_PATH=/home/developer/Ascend
117+CANN_URL="https://ascend.devcloud.huaweicloud.com/artifactory/cann-run-mirror/software/master/<timestamp>"
118+wget "${CANN_URL}/Ascend-cann-toolkit_<version>_linux-aarch64.run"
119+wget "${CANN_URL}/Ascend-cann-910b-ops_<version>_linux-aarch64.run"
120+ 
121+# 3. 安装 toolkit
122+chmod +x Ascend-cann-toolkit_*.run
123+./Ascend-cann-toolkit_*.run --full --install-path=$INSTALL_PATH
124+ 
125+# 4. 安装 ops 包
126+chmod +x Ascend-cann-910b-ops_*.run
127+./Ascend-cann-910b-ops_*.run --install --install-path=$INSTALL_PATH
128+```
129+ 
130+### 2.4 配置环境变量
131+ 
132+安装完成后,CANN 会在安装路径下生成 `cann` 符号链接和 `set_env.sh`
133+ 
134+```bash
135+# 安装后的目录结构(以 --install-path=/home/developer/Ascend 为例):
136+# /home/developer/Ascend/
137+# ├── cann-<version>/
138+# │ ├── cann -> cann-<version> # 内部符号链接
139+# │ └── cann-<version>/
140+# │ ├── set_env.sh # 环境变量脚本
141+# │ ├── lib64/
142+# │ ├── opp/
143+# │ ├── python/
144+# │ └── ...
145+# └── ascend_cann_install.info
146+ 
147+# 将 set_env.sh 写入 .bashrc 实现自动加载
148+echo 'source /home/developer/Ascend/cann/set_env.sh' >> ~/.bashrc
149+ 
150+# 立即生效
151+source /home/developer/Ascend/cann/set_env.sh
152+```
153+ 
154+**注意**: 如果安装路径下没有顶层 `cann` 符号链接(某些安装方式不自动创建),需要手动创建:
155+```bash
156+# 找到实际安装目录名
157+ls /home/developer/Ascend/
158+# 假设是 cann-9.2.0
159+ln -sfn /home/developer/Ascend/cann-9.2.0/cann /home/developer/Ascend/cann
160+```
161+ 
162+### 2.5 Python 依赖
163+ 
164+```bash
165+# GE 编译和运行需要的 Python 包
166+pip install 'pybind11>=2.13.6,<3.0.0' # 3.0.x 不兼容
167+pip install 'setuptools>=59.0.1,<80.0.0' # 82.x 不兼容
168+pip install numpy jinja2 wheel coverage cloudpickle
169+ 
170+# 运行 custom_op 示例需要的包
171+pip install attrs cython decorator sympy cffi pyyaml pathlib2 psutil protobuf==3.20.0 scipy requests absl-py
172+```
173+ 
174+### 2.6 dsa_core workaround(必须)
175+ 
176+**这是当前环境的已知问题,必须执行此步骤,否则 GEInitialize 会失败。**
177+ 
178+```bash
179+sudo ln -sf ascend910b \
180+ ${ASCEND_OPP_PATH}/built-in/op_impl/dsa_core/config/ascend910_93
181+```
182+ 
183+根因分析与排查过程详见 [§坑2](#坑-2-dsa_coreconfigascend910_93-缺失关键问题)。**注意:如果重新安装或升级 CANN,需要重新执行此 workaround。**
184+ 
185+### 2.7 验证安装
186+ 
187+```bash
188+# 新开 shell,验证环境变量
189+echo $ASCEND_HOME_PATH
190+# 应输出: /home/developer/Ascend/cann-<version>/cann-<version>
191+ 
192+# 查看实际版本
193+cat $ASCEND_OPP_PATH/version.info
194+# 应输出: Version=9.1.0 (或更高)
195+ 
196+# 验证 Python 模块
197+python3 -c "import ge.custom_op; print('ge.custom_op OK')"
198+python3 -c "import acl; print('acl OK')"
199+ 
200+# 运行示例验证
201+cd /path/to/ge/examples/custom_op/args_refresh_add_custom/python
202+bash run.sh
203+# 期望最后输出: [INFO] Python session sample finished.
204+```
205+ 
206+---
207+ 
208+## 3. 踩坑记录
209+ 
210+### 坑 1: CANN 版本不匹配
211+ 
212+**现象**: `import ge.custom_op` 失败,`GEInitialize` 报错。
213+ 
214+**原因**: 初始环境安装的是 `cann-9.1.0-beta.3`(2026-06-16 构建),GE master 分支需要更新的版本。9.1.0-beta.3 的 metadef 缺少 `graph/type_id.h``libfe.so` 初始化逻辑与 GE master 不兼容。
215+ 
216+**解决**: 从 master 镜像安装最新 weekly 构建版本(目录名 cann-9.2.0,实际版本 9.1.0 release,2026-07-15 构建)。
217+ 
218+### 坑 2: dsa_core/config/ascend910_93 缺失(关键问题)
219+ 
220+**现象**: `GEInitialize` 失败,报错:
221+```
222+FEOpsKernelInfoStore: Initialize custom and builtin sub-information library failed
223+There is no valid so about OpsKernelInfoStore or GraphOptimizer.
224+PluginManager InvokeAll failed.
225+```
226+ 
227+**根因分析**:
228+ 
229+1. 芯片是 `Ascend910`(SoC `Ascend910_9362`),FE 配置中平台映射为 `ascend910_93`
230+2. `910b-ops` 包安装后,`dsa_core/config/` 目录下只有 `ascend910b`,缺少 `ascend910_93`
231+3. `libfe.so``dsa-builtin` SubOpInfoStore 初始化时找不到 `ascend910_93` 配置目录
232+4. 虽然 `tbe-builtin` 成功加载了 266 个算子,但 `libfe.so`(闭源)在 `dsa-builtin` 失败时整体返回 FAILED
233+5. 导致 `PluginManager::InvokeAll` 失败 → `GEInitialize` 失败
234+ 
235+**日志定位**:
236+```
237+# plog 日志中的关键行(位于 /home/developer/ascend/log/debug/plog/plog-*.log)
238+[WARNING] FE(...) Initialize: The configuration file [.../dsa_core/config/ascend910_93]
239+ for the operational information library [dsa-builtin] does not exist.
240+[ERROR] FE(...) Initialize: FEOpsKernelInfoStore: Initialize custom and builtin
241+ sub-information library failed
242+[ERROR] GE(...) Initialize: ErrorNo: 1343250441(There is no valid so about
243+ OpsKernelInfoStore or GraphOptimizer.) PluginManager InvokeAll failed.
244+```
245+ 
246+**Workaround**:
247+```bash
248+sudo ln -sf ascend910b \
249+ ${ASCEND_OPP_PATH}/built-in/op_impl/dsa_core/config/ascend910_93
250+```
251+ 
252+**注意**: 这是一个 workaround,不是正式修复。此 workaround 已验证可以正常运行全部 3 个无框架依赖的 custom_op 示例。
253+ 
254+### 坑 3: cann 符号链接指向旧版本
255+ 
256+**现象**: 安装了新版本后,新 shell 中环境变量仍然指向旧版本。
257+ 
258+**原因**: `.bashrc` 中有 `source /home/developer/Ascend/cann/set_env.sh`,而 `cann` 符号链接可能指向旧安装。
259+ 
260+**解决**: 修复符号链接:
261+```bash
262+ln -sfn /home/developer/Ascend/cann-<新版本>/cann /home/developer/Ascend/cann
263+```
264+ 
265+### 坑 4: pybind11 版本不兼容
266+ 
267+**现象**: 编译 GE Python 扩展时失败。
268+ 
269+**原因**: pybind11 3.0.x 与 GE 不兼容。
270+ 
271+**解决**:
272+```bash
273+pip install 'pybind11>=2.13.6,<3.0.0'
274+```
275+ 
276+### 坑 5: bash 版本过低
277+ 
278+**现象**: `run.sh` 等 shell 脚本执行异常。
279+ 
280+**原因**: Ubuntu 20.04 默认 bash 5.0.17,GE 脚本需要 bash >= 5.1.16。
281+ 
282+**解决**: 从源码编译 bash 5.2:
283+```bash
284+wget https://ftp.gnu.org/gnu/bash/bash-5.2.37.tar.gz
285+tar xzf bash-5.2.37.tar.gz
286+cd bash-5.2.37
287+./configure && make -j$(nproc)
288+sudo cp bash /usr/local/bin/bash
289+sudo mv /bin/bash /bin/bash.old
290+sudo ln -sf /usr/local/bin/bash /bin/bash
291+```
292+ 
293+### 坑 6: setuptools 版本过高
294+ 
295+**现象**: Python 包构建失败。
296+ 
297+**原因**: setuptools 82.x 不兼容。
298+ 
299+**解决**:
300+```bash
301+pip install 'setuptools>=59.0.1,<80.0.0'
302+```
303+ 
304+### 坑 7: 从源码编译 GE 的依赖问题
305+ 
306+GE 编译需要 metadef、runtime、graph-autofusion 三个依赖包。如果 toolkit 自带的版本不匹配(如使用 beta 版本时),需要从源码编译:
307+ 
308+```bash
309+# metadef
310+git clone https://gitcode.com/cann/metadef.git
311+cd metadef && bash build.sh && cd ..
312+sudo bash metadef/build_out/cann-metadef_*.run --install
313+ 
314+# runtime
315+git clone https://gitcode.com/cann/runtime.git
316+cd runtime && bash build.sh && cd ..
317+sudo bash runtime/build_out/cann-npu-runtime_*.run --install
318+ 
319+# graph-autofusion
320+git clone https://gitcode.com/cann/graph-autofusion.git
321+cd graph-autofusion && bash build.sh && cd ..
322+sudo bash graph-autofusion/build_out/cann-graph-autofusion_*.run --install
323+```
324+ 
325+**注意**: 如果使用 master 镜像的 release 版本(非 beta),通常不需要源码编译这些依赖,toolkit 自带的版本即可。
326+ 
327+---
328+ 
329+## 4. 目录结构参考
330+ 
331+```
332+/home/developer/Ascend/
333+├── cann -> cann-9.2.0/cann # 顶层符号链接(.bashrc source 的入口)
334+├── cann-9.2.0/ # 唯一 CANN 安装
335+│ ├── cann -> cann-9.2.0 # 内部符号链接
336+│ └── cann-9.2.0/ # 实际安装内容(version.info: 9.1.0)
337+│ ├── set_env.sh # 环境变量脚本
338+│ ├── lib64/ # 库文件
339+│ ├── opp/ # 算子库
340+│ │ └── built-in/
341+│ │ └── op_impl/
342+│ │ ├── ai_core/tbe/config/
343+│ │ │ ├── ascend910_93/ # TBE 算子配置(芯片对应,ops包安装)
344+│ │ │ └── ascend910b/
345+│ │ └── dsa_core/config/
346+│ │ ├── ascend910_93 -> ascend910b # workaround 符号链接
347+│ │ └── ascend910b/
348+│ ├── python/ # Python 包(ge, acl 等)
349+│ └── query_pkg_version.sh # 查询所有子包版本
350+├── ascend-toolkit/ # toolkit 入口(符号链接链)
351+│ ├── latest -> ../cann
352+│ └── set_env.sh -> latest/set_env.sh
353+├── 20260722_102631/ # 安装元数据(8KB,可忽略)
354+└── ascend_cann_install.info # 安装信息
355+```
356+ 
357+---
358+ 
359+## 5. 芯片型号映射关系
360+ 
361+| npu-smi 显示 | SoC 名称 | FE 平台名 | NPU Arch | ops 包名 | dav 架构 |
362+|--------------|----------|-----------|----------|----------|----------|
363+| Ascend910 | Ascend910_9362 | ascend910_93 | 2201 | 910b | dav-2201 |
364+ 
365+**关键**: `init_env.sh --chip-type 910b` 安装的 ops 包覆盖了 `ascend910_93` 的 TBE 配置(算子定义),但 `dsa_core` 配置只安装了 `ascend910b`,需要符号链接 workaround。
366+ 
367+---
368+ 
369+## 6. custom_op 样例运行结果
370+ 
371+5 个样例中 4 个通过、1 个因缺框架跳过。完整结果详见 [dev-log/custom_op-samples-results.md](../dev-log/custom_op-samples-results.md)。
372+ 
373+---
374+ 
375+## 7. 框架依赖安装指南
376+ 
377+### 7.1 TorchAir(ascendc_add_custom 依赖)
378+ 
379+**背景**: torch_npu 2.7.1.post4 内部虽然包含了 `torch_npu/dynamo/torchair/` 完整实现(含编译好的 .so),但其 `__init__.py` 使用 `from torchair.xxx import` 顶层包名导入,因此必须安装独立的顶层 `torchair` 包。
380+ 
381+**版本兼容**:
382+ 
383+| TorchAir 版本 | PyTorch | TorchNPU | CANN | Python |
384+|---------------|---------|----------|------|--------|
385+| 7.3.0 (release) | 2.7.1 | 7.3.0 | 8.5.0 | 3.9-3.11 |
386+| master (在研) | 2.6.0+ | 在研 | 在研 | 3.9-3.12 |
387+ 
388+**实际选择**: 7.3.0 分支。master 分支虽然编译成功,但 `__init__.py` 导入了 torch_npu post4 中不存在的 `npu_format_cast_via_cpu` API,导致 import 失败。7.3.0 分支与 torch_npu post4 兼容。
389+ 
390+**安装方式**: 源码编译(不可 pip install)
391+ 
392+```bash
393+source /home/developer/Ascend/cann/set_env.sh
394+ 
395+# 1. 克隆仓库并切换到 7.3.0 分支
396+cd /mnt/workspace/gitCode/gentle-knight/
397+git clone https://gitcode.com/Ascend/torchair.git
398+cd torchair
399+git checkout 7.3.0
400+git submodule update --init --recursive
401+ 
402+# 2. configure(用环境变量避免交互)
403+export TARGET_PYTHON_PATH=$(which python3)
404+export NO_ASCEND_SDK=1
405+bash ./configure
406+ 
407+# 3. 编译安装(一键脚本)
408+bash build_and_install.sh
409+ 
410+# 4. 验证
411+python3 -c "import torchair; print('torchair OK')"
412+python3 -c "from torchair.ge import Tensor, TensorSpec; print('ge OK')"
413+python3 -c "from torchair import register_fx_node_ge_converter, CompilerConfig, get_npu_backend; print('APIs OK')"
414+```
415+ 
416+**注意**: torch_npu 的 `dynamo/__init__.py` 在 `import torch_npu` 时会执行 `sys.modules['torchair'] = _LazyTorchair('torchair')`,用懒加载代理替换 sys.modules 中的 torchair。安装 torchair 7.3.0 后,这个代理会正确转发到 torch_npu 内置的实现(两者 7.3.0 级别功能一致),不会冲突。
417+ 
418+**验证**: ascendc_add_custom 样例 eager 模式 + 图模式全部通过。
419+ 
420+### 7.2 TensorFlow + Triton-Ascend(triton_add_custom 依赖)
421+ 
422+**当前状态**: 暂时跳过。
423+ 
424+**原因**:
425+- triton_add_custom 需要 TF 1.15 或 2.6.5
426+- TF 1.15 只支持 Python 3.7,TF 2.6.5 只支持 Python 3.6-3.9
427+- 当前环境 Python 3.11.4 与两者都不兼容
428+- 需要用 conda 创建独立 Python 3.9 环境才能安装
429+ 
430+**如果未来需要安装**:
431+1. 用 conda 创建 Python 3.9 环境
432+2. 安装 TF 2.6.5 + npu_bridge(CANN TF 框架插件)
433+3. 安装 Triton-Ascend: `pip install triton-ascend==3.2.1 --extra-index-url=https://triton-ascend.osinfra.cn/pypi/simple`
434+4. Triton-Ascend 3.2.1 官方支持 CANN 9.0.0,与当前 9.1.0 可能兼容但未验证
435+ 
436+---
437+ 
438+## 8. 问题追踪
439+ 
440+> 完整问题索引见 [issues.md](issues.md)。此处仅记录待跟进事项。
441+ 
442+- [ ] 向 CANN 社区反馈 `dsa_core/config/ascend910_93` 缺失问题
443+- [x] 安装 TorchAir(7.3.0 分支源码编译,已成功)
444+- [x] 清理 `cann-9.1.0-beta.3`(已删除,释放 10GB)
445+- [x] 安装 Python `onnx` 库(2026-07-26,pip install onnx,protobuf 冲突无实际影响)
@@ -0,0 +1,115 @@
1+# 已知问题与 Workaround
2+ 
3+> 最后更新: 2026-07-22
4+ 
5+## 1. [已解决] dsa_core/config/ascend910_93 缺失导致 GEInitialize 失败
6+ 
7+**影响**: 所有需要 `GEInitialize` 的程序(包括 custom_op 示例、UT/ST 测试等)
8+ 
9+**根因**: `cann-910b-ops` 包未安装 `dsa_core/config/ascend910_93` 目录,而芯片 `Ascend910_9362` 的 FE 平台名为 `ascend910_93``libfe.so`(闭源)在 `dsa-builtin` SubOpInfoStore 初始化失败时整体返回 FAILED,即使 `tbe-builtin` 已成功加载 266 个算子。
10+ 
11+**Workaround**:
12+```bash
13+sudo ln -sf ascend910b \
14+ ${ASCEND_OPP_PATH}/built-in/op_impl/dsa_core/config/ascend910_93
15+```
16+ 
17+**验证**: 3 个 custom_op 示例全部运行成功。
18+ 
19+**状态**: workaround,等待 ops 包修复。
20+ 
21+---
22+ 
23+## 2. [已解决] cann 符号链接指向旧版本
24+ 
25+**影响**: 新 shell 自动加载错误版本的 CANN 环境。
26+ 
27+**根因**: `.bashrc``source /home/developer/Ascend/cann/set_env.sh`,而 `cann` 符号链接指向旧版 `cann-9.1.0-beta.3`
28+ 
29+**修复**:
30+```bash
31+ln -sfn /home/developer/Ascend/cann-9.2.0/cann /home/developer/Ascend/cann
32+```
33+ 
34+---
35+ 
36+## 3. [已解决] CANN 版本名误导
37+ 
38+**问题**: 安装目录名为 `cann-9.2.0`,但实际版本是 9.1.0 release。
39+ 
40+**说明**: 这是 CANN weekly 构建的正常现象——包名先升版本号,内部子包 version.info 还没改。**以 version.info 中的 `Version=` 字段为准,不要以目录名或包名判断版本。**
41+ 
42+**验证方法**:
43+```bash
44+cat $ASCEND_OPP_PATH/version.info
45+$ASCEND_HOME_PATH/query_pkg_version.sh
46+```
47+ 
48+---
49+ 
50+## 4. [已解决] pybind11 3.0.x 不兼容
51+ 
52+**修复**: `pip install 'pybind11>=2.13.6,<3.0.0'`
53+ 
54+---
55+ 
56+## 5. [已解决] bash 5.0.17 不满足要求
57+ 
58+**修复**: 编译安装 bash 5.2.37,替换 `/bin/bash`
59+ 
60+---
61+ 
62+## 6. [已解决] setuptools 82.x 不兼容
63+ 
64+**修复**: `pip install 'setuptools>=59.0.1,<80.0.0'`
65+ 
66+---
67+ 
68+## 7. [已解决] GE 源码编译依赖版本不匹配
69+ 
70+**说明**: 使用 beta 版 toolkit 时,metadef/runtime/ge-compiler/graph-autofusion 版本不够,需从源码编译覆盖。使用 master 镜像 release 版本时通常不需要。
71+ 
72+**注意**: 旧版 `cann-9.1.0-beta.3` 已于 2026-07-22 删除。GE 旧编译缓存 `ge/build/` 也已清除(CMakeCache 中引用了 beta.3 路径)。下次编译 GE 会重新 cmake 配置,自动指向当前 9.2.0 环境。
73+ 
74+---
75+ 
76+## 8. [已解决] TorchAir 安装
77+ 
78+**影响**: ascendc_add_custom 样例图模式无法运行。
79+ 
80+**根因**: torch_npu 2.7.1.post4 内置了 `torch_npu/dynamo/torchair/` 完整实现,但其 `__init__.py` 使用 `from torchair.xxx import` 顶层包名导入,需要独立的顶层 `torchair` 包。pip 无此包,需源码编译。
81+ 
82+**安装过程踩坑**:
83+1. **master 分支编译成功但 import 失败**: master 分支的 `__init__.py` 导入 `npu_format_cast_via_cpu`,但 torch_npu post4 内置版本没有此 API。原因是 master 分支对应"在研版本"的 torch_npu,与 post4 不完全兼容。
84+2. **7.3.0 分支成功**: 7.3.0 分支没有 `npu_format_cast_via_cpu`,与 torch_npu post4 兼容。编译安装后 `import torchair` 及所有 API 均正常。
85+ 
86+**注意**: torch_npu 的 `dynamo/__init__.py:147` 会执行 `sys.modules['torchair'] = _LazyTorchair('torchair')`,劫持 torchair 模块。安装 torchair 后,`_LazyTorchair` 代理会正确转发到真正的 torchair 包(通过 `from . import torchair` 加载内置实现)。两者在 7.3.0 级别下功能一致,不会冲突。
87+ 
88+**最终方案**:
89+```bash
90+git clone https://gitcode.com/Ascend/torchair.git
91+cd torchair && git checkout 7.3.0
92+git submodule update --init --recursive
93+export TARGET_PYTHON_PATH=$(which python3)
94+export NO_ASCEND_SDK=1
95+bash ./configure
96+bash build_and_install.sh
97+```
98+ 
99+**验证**: ascendc_add_custom 样例 eager 模式 + 图模式全部通过 (`Ran 1 test ... OK`)。
100+ 
101+---
102+ 
103+## 9. [跳过] TensorFlow + Triton-Ascend 未安装
104+ 
105+**影响**: triton_add_custom 样例无法运行。
106+ 
107+**原因**: TF 1.15/2.6.5 与当前 Python 3.11.4 不兼容,需 conda 隔离环境。暂不处理。
108+ 
109+---
110+ 
111+## 待跟进
112+ 
113+- [ ] 向 CANN 社区反馈 `dsa_core/config/ascend910_93` 缺失问题
114+- [x] 安装 TorchAir(7.3.0 分支源码编译,已成功)
115+- [x] 清理 `cann-9.1.0-beta.3`(已删除,释放 10GB)
@@ -0,0 +1,259 @@
1+# docs 文档路径引用失效问题记录
2+ 
3+> 检查日期:2026-07-24
4+> 检查范围:`docs/` 目录下全部 2193 个 markdown 文件
5+> 检查方法:脚本批量提取 markdown 链接 `[text](path)` 与行内/代码块中的仓库源码路径,逐个验证文件存在性;对失效路径用 `find` 搜索同名文件、`git log --follow` 查重命名历史确认正确路径。
6+ 
7+## 检查摘要
8+ 
9+| 项目 | 数量 |
10+|------|------|
11+| 扫描 markdown 文件数 | 2193 |
12+| 提取到的路径引用(去重前) | 128 |
13+| 去重后唯一失效路径 | 71 |
14+| 其中:误报(脚本判断错误,实际有效) | 8 |
15+| 其中:代码块说明性路径(非真实引用) | 6 |
16+| 其中:确认路径错误并已找到正确路径 | 38 |
17+| 其中:文件已删除/改名,已确认替代 | 12 |
18+| 其中:设计规划文件 vs 实际实现(已确认) | 7 |
19+ 
20+涉及文件(含 en/zh 双语重复):35 个 markdown 文档。
21+ 
22+---
23+ 
24+## 一、误报(脚本判断错误,实际路径有效)—— 无需修复
25+ 
26+以下路径实际有效,因脚本未正确处理 `.h/.cc` 合并写法或末尾冒号而误报:
27+ 
28+| 误报路径 | 实际情况 |
29+|----------|----------|
30+| `compiler/graph/passes/standard_optimize/constant_folding/constant_folding_pass.h/cc` | `.h``.cc` 均存在 |
31+| `compiler/graph/passes/standard_optimize/constant_folding/dimension_compute_pass.h/cc` | 同上 |
32+| `compiler/graph/passes/standard_optimize/constant_folding/dimension_adjust_pass.h/cc` | 同上 |
33+| `compiler/graph/passes/standard_optimize/constant_folding/replace_with_empty_const_pass.h/cc` | 同上 |
34+| `compiler/graph/passes/standard_optimize/constant_folding/potential_const_taken_effect_pass.h/cc` | 同上 |
35+| `api/session/client/ge_api_v2.cc:` | 文件存在,末尾冒号是"如下"之意 |
36+| `runtime/v1/common/profiling/profiling_init.cc:` | 同上 |
37+| `compiler/graph/build/profiling_task_utils.cc:` | 同上 |
38+ 
39+---
40+ 
41+## 二、代码块说明性路径(非真实文件引用)—— 无需修复
42+ 
43+以下出现在代码块中,是目录/模块说明,非具体文件引用:
44+ 
45+| 路径 | 出现位置 | 说明 |
46+|------|----------|------|
47+| `hybrid/rt2` | datadump.md L29 | 目录说明,实际 hybrid 在 `runtime/v1/hybrid` |
48+| `hybrid/` | runtime.md L31 | 目录说明 |
49+| `runtime/v2/kernel/memory/allocator/scalable_allocator` | memory-constraints.md L132 | 目录说明 |
50+| `runtime/v2/kernel/memory/device/device_allocator` | memory-constraints.md L137 | 目录说明 |
51+| `docs/es/` | es_graph/README.md L56 | 目录说明,实际 ES 文档在 `docs/zh/api/graph_engine_api/{cpp,python}/ge/es` |
52+| `compiler/engines/cpu_engine/aicpu_engine/``hostcpu_engine/` | engine.md L18/L20 | 实际路径为 `compiler/engines/cpu_engine/cpu_engine/aicpu_engine` |
53+ 
54+---
55+ 
56+## 三、docs 文档路径前缀错误(旧前缀 → 新前缀)—— 已确认正确路径
57+ 
58+文档目录在 `61a564e86`(2026-06-17 "refactor: 清理文档目录")等 commit 中重构,旧前缀失效。
59+ 
60+### 3.1 `docs/architecture/` → `docs/zh/design/`
61+ 
62+| 错误路径 | 正确路径 | 出现文件 |
63+|----------|----------|----------|
64+| `docs/architecture/modules/compiler/compiler.md` | `docs/zh/design/modules/compiler/compiler.md` | dynamic_gear.md(zh L525, en L438/L521) |
65+| `docs/architecture/modules/runtime/runtime.md` | `docs/zh/design/modules/runtime/runtime.md` | unknown_shape_executor.md(zh L564, en L564) |
66+| `docs/architecture/constraints/graph_split.md` | `docs/zh/design/constraints/graph_split.md` | graph_splitter.md(zh L362, en L362) |
67+| `docs/architecture/constraints/rt2_runtime.md` | `docs/zh/design/constraints/rt2_runtime.md` | unknown_shape_executor.md(zh L563, en L563) |
68+ 
69+### 3.2 `docs/graph_engine_api/` → `docs/zh/api/graph_engine_api/`
70+ 
71+| 错误路径 | 正确路径 | 出现文件 |
72+|----------|----------|----------|
73+| `docs/graph_engine_api/aclmdlExecute.md` | `docs/zh/api/graph_engine_api/c/acl/aclmdlExecute.md` | memory-constraints.md(zh L72, en L72) |
74+| `docs/graph_engine_api/aclopEngineType.md` | `docs/zh/api/graph_engine_api/c/acl/aclopEngineType.md` | engine.md(zh L92, en L92) |
75+| `docs/graph_engine_api/aclmdlGetInputDynamicGearCount.md` | `docs/zh/api/graph_engine_api/c/acl/aclmdlGetInputDynamicGearCount.md` | dynamic_gear.md(zh L522-524, en L435-437/L518-520) |
76+| `docs/graph_engine_api/aclmdlGetInputDynamicDims.md` | `docs/zh/api/graph_engine_api/c/acl/aclmdlGetInputDynamicDims.md` | 同上 |
77+| `docs/graph_engine_api/aclmdlSetInputDynamicDims.md` | `docs/zh/api/graph_engine_api/c/acl/aclmdlSetInputDynamicDims.md` | 同上 |
78+ 
79+### 3.3 `docs/atc_shape_configuration_guide.md` → 用户指南目录
80+ 
81+| 错误路径 | 正确路径 | 出现文件 |
82+|----------|----------|----------|
83+| `docs/atc_shape_configuration_guide.md` | `docs/zh/user_guides/atc_shape_configuration_guide.md`(zh)/ `docs/en/user_guides/atc_shape_configuration_guide.md`(en) | dynamic_gear.md(zh L519, en L432/L515) |
84+ 
85+### 3.4 `docs/zh/api/graph_engine_api/python/ge/custom_op/` 路径不存在
86+ 
87+| 错误路径 | 正确路径 | 出现文件 |
88+|----------|----------|----------|
89+| `docs/zh/api/graph_engine_api/python/ge/custom_op/` | 该目录不存在;custom_op 文档实际在 `docs/zh/user_guides/custom_op/` | ge_python_custom_op_design.md(zh L305, en L305) |
90+ 
91+---
92+ 
93+## 四、源码文件路径变更(目录重构)—— 已确认正确路径
94+ 
95+源码在多次重构中移动了目录位置。
96+ 
97+### 4.1 `common/` → `base/common/` 或 `compiler/engines/`
98+ 
99+| 错误路径 | 正确路径 | 出现文件 |
100+|----------|----------|----------|
101+| `common/dump/dump_manager.h` | `base/common/dump/dump_manager.h` | datadump.md(zh L65, en L65) |
102+| `common/dump/dump_properties.h` | `base/common/dump/dump_properties.h` | datadump.md(zh L66, en L66) |
103+| `common/dump/exception_dumper.h` | `base/common/dump/exception_dumper.h` | datadump.md(zh L70, en L70) |
104+| `common/engine/base_engine.h` | `compiler/engines/cpu_engine/common/engine/base_engine.h` | engine.md(zh L406, en L406) |
105+ 
106+### 4.2 `compiler/engines/` 内部目录调整
107+ 
108+| 错误路径 | 正确路径 | 出现文件 |
109+|----------|----------|----------|
110+| `compiler/engines/manager/opskernel_manager/ops_kernel_manager.h` | `compiler/engines/nn_engine/inc/ops_store/ops_kernel_manager.h` | engine.md(zh L296, en L296) |
111+| `compiler/engines/nn_engine/utils/common/fe_gentask_utils.h` | `compiler/engines/nn_engine/inc/common/fe_gentask_utils.h` | tiling_sink.md(zh L112, en L112) |
112+| `compiler/engines/nn_engine/optimizer/graph_optimizer/task_builder/superkernel_task_builder.cc` | `compiler/engines/nn_engine/optimizer/ops_kernel_builder/task_builder/superkernel_task_builder.cc` | tiling_sink.md(zh L225, en L225) |
113+ 
114+### 4.3 dflow 模块整体迁移到 `dflow/` 目录
115+ 
116+以下文件在 dflow 模块重构中从各处统一迁移到 `dflow/` 目录下:
117+ 
118+| 错误路径 | 正确路径 | 出现文件 |
119+|----------|----------|----------|
120+| `compiler/session/dflow_api.h` | `dflow/compiler/session/dflow_api.h` | dflow.md L181 |
121+| `base/model/pne_model.h` | `dflow/inc/data_flow/model/pne_model.h` | dflow.md L271 |
122+| `base/model/model_relation.h` | `dflow/base/model/model_relation.h` | dflow.md L294 |
123+| `base/model/flow_model_om_saver.cc` | `dflow/base/model/flow_model_om_saver.cc` | dflow.md L298 |
124+| `base/deploy/deploy_planner.cc` | `dflow/base/deploy/deploy_planner.cc` | dflow.md L306 |
125+| `common/subprocess/subprocess_manager.cc` | `dflow/deployer/common/subprocess/subprocess_manager.cc` | dflow.md L408 |
126+| `common/data_flow/queue/heterogeneous_exchange_service.h` | `dflow/deployer/common/data_flow/queue/heterogeneous_exchange_service.h` | dflow.md L410 |
127+| `compiler/data_flow_graph/data_flow_graph_auto_deployer.cc` | `dflow/compiler/data_flow_graph/data_flow_graph_auto_deployer.cc` | dflow.md L493 |
128+| `compiler/model/flow_model_builder.cc` | `dflow/compiler/model/flow_model_builder.cc` | dflow.md L514 |
129+| `compiler/data_flow_graph/compile_config_json.cc` | `dflow/compiler/data_flow_graph/compile_config_json.cc` | dflow.md L516 |
130+| `base/exec_runtime/execution_runtime.cc` | `dflow/base/exec_runtime/execution_runtime.cc` | dflow.md L612 |
131+| `base/model/`(目录) | `dflow/base/model/` | dflow.md L271 |
132+ 
133+### 4.4 dflow UDF 头文件迁移到 `dflow/udf/inc/external/flow_func/`
134+ 
135+| 错误路径 | 正确路径 | 出现文件 |
136+|----------|----------|----------|
137+| `inc/external/flow_func/meta_flow_func.h` | `dflow/udf/inc/external/flow_func/meta_flow_func.h` | udf.md(L35, L463) |
138+| `inc/external/flow_func/meta_multi_func.h` | `dflow/udf/inc/external/flow_func/meta_multi_func.h` | udf.md(L36, L464) |
139+| `inc/external/flow_func/meta_params.h` | `dflow/udf/inc/external/flow_func/meta_params.h` | udf.md L256 |
140+| `inc/external/flow_func/meta_context.h` | `dflow/udf/inc/external/flow_func/meta_context.h` | udf.md L257 |
141+| `inc/external/flow_func/meta_run_context.h` | `dflow/udf/inc/external/flow_func/meta_run_context.h` | udf.md L258 |
142+| `inc/external/flow_func/flow_msg.h` | `dflow/udf/inc/external/flow_func/flow_msg.h` | udf.md L270 |
143+| `inc/external/flow_func/balance_config.h` | `dflow/udf/inc/external/flow_func/balance_config.h` | udf.md L287 |
144+| `inc/external/flow_func/`(目录) | `dflow/udf/inc/external/flow_func/` | udf.md(L236, L385) |
145+| `common/inner_error_codes.h` | `dflow/udf/common/inner_error_codes.h` | udf.md L413 |
146+ 
147+### 4.5 `inc/graph_metadef/external/graph/types.h` 路径变更
148+ 
149+| 错误路径 | 正确路径 | 出现文件 |
150+|----------|----------|----------|
151+| `inc/graph_metadef/external/graph/types.h` | `inc/framework/executor_c/types.h` | infer_format.md(zh L89, en L89) |
152+ 
153+### 4.6 `api/acl/acl_model/model/model_config.cpp` 改名(OM2 重构)
154+ 
155+| 错误路径 | 正确路径 | 出现文件 |
156+|----------|----------|----------|
157+| `api/acl/acl_model/model/model_config.cpp` | `api/acl/acl_model/model/model_config_om2.cpp`(commit `923e78cfd` "acl_mdl 模块支持 OM2") | external_weight.md(zh L139, en L139) |
158+ 
159+---
160+ 
161+## 五、文件已删除/改名,已确认替代 —— 已确认
162+ 
163+### 5.1 文档被删除/重命名
164+ 
165+| 错误路径 | 正确路径 / 替代 | 确认方式 | 出现文件 |
166+|----------|-----------------|----------|----------|
167+| `docs/graph_engine_api/options参数说明.md` | 已删除,内容分散到 `docs/zh/user_guides/atc_tools/CLI_options/` 目录下多个文件 | commit `61a564e86` | dynamic_gear.md(zh L520, en L433/L516) |
168+| `docs/graph_engine_api/aclgrphBuildModel支持的配置参数.md` | `docs/zh/api/graph_engine_api/cpp/ge/aclgrphBuildModel_config_params/aclgrphbuildmodel_config_params.md` | find 确认 | dynamic_gear.md(zh L521, en L434/L517) |
169+| `docs/graph_engine_api/属性名列表.md` | `docs/zh/api/graph_engine_api/cpp/ge/attribute_name_list.md` | find 确认 | tiling_sink.md zh L425 |
170+| `docs/graph_engine_api/AttributeNameList.md` | 同上 `docs/zh/api/graph_engine_api/cpp/ge/attribute_name_list.md` | find 确认 | tiling_sink.md en L425 |
171+ 
172+### 5.2 源码头文件改名/移动
173+ 
174+| 错误路径 | 正确路径 | 确认方式 | 出现文件 |
175+|----------|----------|----------|----------|
176+| `inc/graph_metadef/exe_graph/runtime/infer_shape_context.h` | `inc/graph_metadef/external/graph/ct_infer_shape_context.h` | find 确认 | infer_shape.md(zh L544, en L542) |
177+| `compiler/graph/fusion/pass/python_fusion_base_pass_pybind_bridge.cc` | `compiler/graph/fusion/pass/python_fusion_base_pass_adapter.cc` | find 确认(commit `12cd0bcc7` 重构) | ge_python_pass_design.md(zh L1713, en L162/L1710) |
178+| `compiler/graph/fusion/pass/python_fusion_base_pass_pybind_bridge.h` | 对应的 `python_fusion_base_pass_adapter.h`(需确认是否同名) | find 确认 .cc 改名 | ge_python_pass_design.md(zh L1714, en L1711) |
179+ 
180+### 5.3 md_link 笔误(含多余空格)
181+ 
182+| 错误路径 | 正确路径 | 出现文件 |
183+|----------|----------|----------|
184+| `dynamic _shape_op_exec_sample_code.md`(链接中含空格) | `dynamic_shape_op_exec_sample_code.md`(去掉空格),实际位于 `docs/zh/api/graph_engine_api/c/acl/dynamic_shape_op_exec_sample_code.md` | single_operator_model_execute.md L7 |
185+ 
186+---
187+ 
188+## 六、设计规划文件 vs 实际实现 —— 已确认,分三种情况
189+ 
190+> `ge_python_pass_design.md` 第 12 节"改造实施"是**实施方案规划**(用"修改如下文件""新增...例如"语言),不是描述已有代码。深查后分三种情况:
191+ 
192+### 6.1 已创建但改名(应更新路径)
193+ 
194+| 错误路径 | 正确路径 | 出现文件 |
195+|----------|----------|----------|
196+| `compiler/graph/fusion/pass/python_fusion_base_pass_bridge_c_api.h` | `compiler/graph/fusion/pass/python_pass_bridge_c_api.h` | ge_python_pass_design.md(zh L1711, en L1708) |
197+| `compiler/graph/fusion/pass/python_fusion_base_pass_bridge_loader.cc` | `compiler/graph/fusion/pass/python_pass_bridge_loader.cc` | ge_python_pass_design.md(zh L1712, en L1709) |
198+| `examples/fusion_pass/python_pass开发指南.md` | `examples/fusion_pass/python_fusion_pass_development_guide.md` | ge_python_pass_design.md(zh L1861, en L1858) |
199+ 
200+### 6.2 规划但从未创建(设计文档中的实施方案,实际未落地)
201+ 
202+以下文件在第 12 节"改造实施"中以"新增...例如"语言规划,但仓库中从未存在。属于设计文档与实际实现的偏差:
203+ 
204+| 错误路径 | 出现文件 | 说明 |
205+|----------|----------|------|
206+| `compiler/graph/fusion/pass/pass_create_context.h` | ge_python_pass_design.md(zh L1773, en L1770) | 第 12.4 节"新增创建期上下文管理文件,例如"——规划文件,实际未创建。可能功能合入了 `pass_registry.cc` 或其他文件 |
207+| `compiler/graph/fusion/pass/pass_create_context.cc` | ge_python_pass_design.md(zh L1774, en L1771) | 同上 |
208+| `api/python/ge/ge_api_c_wrapper/c_match_result.cc` | ge_python_pass_design.md(zh L1707, en L1704) | 第 12.3 节"修改和新增如下文件"——规划文件,`ge_api_c_wrapper/` 目录实际无 match 相关文件 |
209+ 
210+### 6.3 CANN 依赖头文件(路径前缀错误)
211+ 
212+| 错误路径 | 正确路径 | 出现文件 | 说明 |
213+|----------|----------|----------|------|
214+| `inc/graph_metadef/exe_graph/runtime/storage_shape.h` | `exe_graph/runtime/storage_shape.h`(CANN 安装目录下) | infer_shape.md(zh L545, en L543) | 该头文件不在 GE 仓库内,来自 CANN 依赖(`$ASCEND_HOME_PATH/.../include/exe_graph/runtime/storage_shape.h`)。文档错误地加了 `inc/graph_metadef/` 前缀。仓库内多个文件以 `#include "exe_graph/runtime/storage_shape.h"` 引用,通过 CANN include path 解析 |
215+ 
216+---
217+ 
218+## 修复建议
219+ 
220+### 优先级 P0(影响文档间跳转,用户可感知)
221+ 
222+1. **第四节 docs 前缀错误**:将 `docs/architecture/``docs/zh/design/``docs/graph_engine_api/``docs/zh/api/graph_engine_api/c/acl/``cpp/ge/`。这些是文档间的 markdown 引用,修复后可恢复跳转。
223+2. **第五节 5.3 md_link 笔误**:`dynamic _shape_op_exec_sample_code.md` 去掉空格。
224+ 
225+### 优先级 P1(影响代码引用准确性)
226+ 
227+3. **第四节 源码路径变更**:dflow 模块、common/dump、engines 等目录重构后的路径更新。
228+4. **第五节 文件改名**`infer_shape_context.h``python_fusion_base_pass_pybind_bridge` 等。
229+ 
230+### 优先级 P2(设计文档与实际实现偏差)
231+ 
232+5. **第 6.2 节 规划但从未创建的文件**`pass_create_context.h/.cc``c_match_result.cc``ge_python_pass_design.md` 第 12 节"改造实施"中规划的文件名,实际实现时未使用这些名(功能可能合入了其他文件)。建议在设计文档中标注"实际实现见 `pass_registry.cc` 等"或更新为实际文件名。
233+ 
234+### 无需修复
235+ 
236+- 第一节误报(8 个):脚本判断错误,实际路径有效。
237+- 第二节说明性路径(6 个):代码块中的目录说明,非具体文件引用。
238+ 
239+---
240+ 
241+## 附录:检查方法说明
242+ 
243+### 检查脚本
244+ 
245+1. `/tmp/opencode/check_doc_paths_v2.py` - 初版路径提取与验证
246+2. `/tmp/opencode/check_final.py` - 最终版(处理行号后缀、`.h/.cc` 合并写法、反斜杠分隔符)
247+3. `/tmp/opencode/analyze_paths.py` - 失效路径正确路径查找(find + git log)
248+ 
249+### 路径引用提取规则
250+ 
251+- **markdown 链接** `[text](path)`:提取 path,排除 http/ftp/锚点,相对当前文件解析
252+- **行内代码** `` `path` ``:仅检查以仓库源码目录前缀(`compiler/`、`runtime/` 等)开头且含 `/` 的,相对仓库根解析
253+- **代码块路径**:同行内代码规则
254+ 
255+### 已知局限
256+ 
257+- 未检查 `docs/zh/api/graph_engine_api/README.md` 中 1011 个使用反斜杠 `\` 分隔的链接(Windows 风格路径),这些链接在 Linux 上无法跳转,但可能是文档生成工具的产物,需单独评估
258+- 未检查图片引用 `![](path)`
259+- 未检查纯文本中无反引号的路径提及
@@ -0,0 +1,117 @@
1+# Issue 草稿:设计文档中存在大量失效的代码/文档路径引用
2+ 
3+> **提交地址**:https://gitcode.com/cann/ge/issues
4+> **Issue 类型**:Documentation | 文档反馈
5+> **标题**:`[Documentation|文档反馈]: 设计文档中存在大量失效的代码/文档路径引用(目录重构后未同步更新)`
6+ 
7+---
8+ 
9+## Issue 正文
10+ 
11+### 文档链接
12+ 
13+涉及 `docs/zh/design/``docs/en/design/` 下多个设计文档,主要包括:
14+ 
15+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/features/dynamic_gear.md
16+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/features/datadump.md
17+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/features/engine.md
18+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/features/tiling_sink.md
19+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/features/infer_shape.md
20+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/features/graph_splitter.md
21+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/features/unknown_shape_executor.md
22+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/features/external_weight.md
23+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/features/constant_folding.md
24+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/features/ge_local_operator.md
25+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/features/profiling.md
26+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/constraints/memory-constraints.md
27+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/modules/dflow/dflow.md
28+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/modules/dflow/udf.md
29+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/modules/ge_python/ge_python_pass_design.md
30+- https://gitcode.com/cann/ge/blob/master/docs/zh/design/modules/ge_python/ge_python_custom_op_design.md
31+- https://gitcode.com/cann/ge/blob/master/docs/zh/api/graph_engine_api/c/acl/single_operator_model_execute.md
32+ 
33+(以上为中文版,英文版 `docs/en/design/` 下对应文件存在相同问题)
34+ 
35+### 问题文档片段
36+ 
37+经批量检查 `docs/` 目录下全部 markdown 文件,发现设计文档中存在大量失效的代码路径和文档链接引用。这些失效主要源于两次仓库重构未同步更新设计文档:
38+ 
39+**1. 文档目录重构(commit `61a564e8` "清理文档目录")后,设计文档中的旧路径未更新:**
40+ 
41+`dynamic_gear.md` L519-525:
42+```
43+| `docs/atc_shape_configuration_guide.md` | ge.inputShape / ge.dynamicDims 参数说明 |
44+| `docs/graph_engine_api/options参数说明.md` | ge.inputShape / ge.dynamicDims 参数说明 |
45+| `docs/graph_engine_api/aclgrphBuildModel支持的配置参数.md` | 编译参数 DYNAMIC_DIMS ... |
46+| `docs/graph_engine_api/aclmdlGetInputDynamicGearCount.md` | 查询档位数量 API |
47+| `docs/architecture/modules/compiler/compiler.md` | 编译流程详细设计 |
48+```
49+上述路径均不存在,实际已迁移至 `docs/zh/user_guides/``docs/zh/api/graph_engine_api/` 下。
50+ 
51+**2. 源码头文件重命名整改(PR `!3497` "内部头文件重名整改")后,设计文档中的旧文件名未更新:**
52+ 
53+`datadump.md` L65-70:
54+```
55+| `DumpManager` | ... | `common/dump/dump_manager.h` |
56+| `DumpProperties`| ... | `common/dump/dump_properties.h` |
57+| `ExceptionDumper`| ... | `common/dump/exception_dumper.h` |
58+```
59+实际路径已迁移至 `base/common/dump/`
60+ 
61+`dflow.md` L181/L271/L294 等:
62+```
63+`compiler/session/dflow_api.h` -> 实际为 dflow/compiler/session/dflow_api.h
64+`base/model/pne_model.h` -> 实际为 dflow/inc/data_flow/model/pne_model.h
65+`base/model/model_relation.h` -> 实际为 dflow/base/model/model_relation.h
66+```
67+dflow 模块整体迁移到 `dflow/` 目录,但设计文档中仍使用旧路径。
68+ 
69+### 存在的问题
70+ 
71+**问题概述**`docs/zh/design/``docs/en/design/` 下的设计文档中存在大量失效的代码文件路径和文档链接引用,经批量扫描共发现 57 处失效引用,涉及 35 个 markdown 文件(中英文各计)。这些问题导致开发者按设计文档查找代码时无法定位到正确文件。
72+ 
73+**根因**:两次仓库重构未同步更新设计文档:
74+1. 文档目录重构(commit `61a564e8`):`docs/architecture/``docs/zh/design/``docs/graph_engine_api/``docs/zh/api/graph_engine_api/`
75+2. 源码目录重构:dflow 模块整体迁移到 `dflow/` 目录;`common/dump/` 迁移到 `base/common/dump/`;头文件重命名整改(PR `!3497`
76+ 
77+**失效引用分类统计**
78+ 
79+| 问题类型 | 数量 | 说明 |
80+|----------|------|------|
81+| 文档路径前缀错误 | 12 | `docs/architecture/``docs/graph_engine_api/` 等旧前缀 |
82+| 源码目录迁移未同步 | 26 | dflow 迁移、common→base、engines 内部调整 |
83+| 文件改名/删除未同步 | 12 | 头文件重命名整改、文档合并重命名 |
84+| 设计规划与实现偏差 | 3 | 设计文档第12节规划的文件名实际未创建 |
85+| md_link 笔误 | 1 | 链接中含多余空格 |
86+| CANN 依赖路径前缀错误 | 1 | `storage_shape.h` 误加 `inc/graph_metadef/` 前缀 |
87+| 设计文档内部不一致 | 2 | 同一文档内正文用对、附录表格用错 |
88+ 
89+**典型示例**(完整清单可在提交 PR 时一并提供):
90+ 
91+| 失效路径 | 正确路径 | 涉及文件 |
92+|----------|----------|----------|
93+| `docs/architecture/modules/compiler/compiler.md` | `docs/zh/design/modules/compiler/compiler.md` | dynamic_gear.md |
94+| `docs/graph_engine_api/aclmdlExecute.md` | `docs/zh/api/graph_engine_api/c/acl/aclmdlExecute.md` | memory-constraints.md |
95+| `common/dump/dump_manager.h` | `base/common/dump/dump_manager.h` | datadump.md |
96+| `compiler/session/dflow_api.h` | `dflow/compiler/session/dflow_api.h` | dflow.md |
97+| `inc/external/flow_func/meta_flow_func.h` | `dflow/udf/inc/external/flow_func/meta_flow_func.h` | udf.md |
98+| `compiler/engines/manager/opskernel_manager/ops_kernel_manager.h` | `compiler/engines/nn_engine/inc/ops_store/ops_kernel_manager.h` | engine.md |
99+| `compiler/engines/nn_engine/utils/common/fe_gentask_utils.h` | `compiler/engines/nn_engine/inc/common/fe_gentask_utils.h` | tiling_sink.md |
100+| `compiler/graph/fusion/pass/python_fusion_base_pass_pybind_bridge.cc` | `compiler/graph/fusion/pass/python_fusion_base_pass_adapter.cc` | ge_python_pass_design.md |
101+| `dynamic _shape_op_exec_sample_code.md`(含空格) | `dynamic_shape_op_exec_sample_code.md` | single_operator_model_execute.md |
102+ 
103+**建议**:我已完成全部 57 处失效引用的正确路径确认,可提交 PR 进行修复。是否需要我先提交 PR?
104+ 
105+---
106+ 
107+## 提交后操作
108+ 
109+提交 issue 后,在评论框输入 `/assign` 将 issue 分配给自己。
110+ 
111+---
112+ 
113+## 备注
114+ 
115+- 完整的失效路径分析报告(含 git log 确认记录、find 搜索结果)已保存在本地 `hfqx/issue/doc_path_issues.md`
116+- 本 issue 仅覆盖设计文档(`docs/zh/design/``docs/en/design/`)中的路径问题。API 文档(`docs/zh/api/`)中的反斜杠链接问题已在 PR `!4109` 中修复
117+- 检查时已排除以下非问题项:`.h/.cc` 合并写法(实际文件存在)、代码块中的说明性目录路径、含行号后缀的引用
@@ -0,0 +1,55 @@
1+# 开发流程要点
2+ 
3+本文件记录 GE 开发流程中需要特别注意的要点,避免重复踩坑。
4+ 
5+---
6+ 
7+## 要点 1:信息同步 —— 跨包/跨位置核对(2026-07-26 确立)
8+ 
9+### 背景
10+ 
11+CANN 生态目前处于开发阶段,**文档、代码分散在多个位置**,单一信息源不可靠。仅凭一处(如 GE 仓源码)下结论,极易误判。
12+ 
13+### 信息源清单
14+ 
15+| 位置 | 内容 | 可靠性 | 备注 |
16+|------|------|--------|------|
17+| GE 仓源码 (`api/python/ge/`, `parser/`, `graph_metadef/` 等) | GE 自身实现 | 高(但只覆盖 GE 仓) | 找不到的 API 可能在外部包 |
18+| GE 仓 docs (`docs/zh/`) | GE 架构/特性/模块设计文档 | 高 | 设计文档权威,但用户指南可能滞后 |
19+| ops 包 (cann-910b-ops) | 算子定义、ES API 生成产物 (es_math.whl 等) | 高 | **不在 GE 仓**,需单独安装 |
20+| metadef 外部头 (`register/register.h` 等) | 注册宏、回调签名定义 | 高 | **不在 GE 仓**,编译时 `-I` 引入 |
21+| 官网文档 (hiascend.com) | 用户向/开发向文档 | 中(可能有版本滞后或措辞模糊) | 交叉验证用,以仓内为准 |
22+ 
23+### 核心原则
24+ 
25+1. **仓内找不到的 API,先去 ops 包 / metadef 外部头找,再下"不存在"的结论**
26+2. **官方文档与仓内源码冲突时,以仓内源码为准;但仓内源码覆盖不到的(如 ops 包产物),以官方文档为准**
27+3. **给出结论前,明确标注信息源**("GE 仓源码确认" vs "官方文档说明" vs "未交叉验证")
28+ 
29+### 案例:ge.es.math 事件(2026-07-26)
30+ 
31+**经过**:分析 ONNX Plugin Python 化时,AI 助手在 GE 仓源码里 grep 不到 `ge.es.math` 模块,遂下结论"`from ge.es.math import Add` 是编的路径"。用户对照官方文档(atlasag_25_0096)发现该路径明确存在,且需 `pip install es_math-*.whl`
32+ 
33+**真相**`ge.es.math` 是 ops 包经 `gen_esb` 工具生成的产物(`docs/zh/user_guides/es_graph/tools/gen_esb.md` 第 44-46 行确认 `--module_name=math` 生成 `es_math_ops.py`),**不在 GE 仓源码里**,随 ops 包发布。
34+ 
35+**教训**
36+- GE 仓源码只能证明"GE 仓里没有",不能证明"生态里没有"
37+- `gen_esb` 生成的 ES API(`ge.es.math`/`ge.es.nn`/`ge.es.all` 等)都在 ops 包里
38+- metadef 的注册宏(`REGISTER_CUSTOM_OP`)、回调签名(`ParseParamFunc`)都在外部头里
39+- 遇到仓内找不到的 API,先查 `docs/zh/user_guides/es_graph/tools/gen_esb.md` 看是不是生成产物,再查官方文档
40+ 
41+### 跨包核对检查清单
42+ 
43+下结论前过一遍:
44+ 
45+- [ ] 这个 API/类/宏,在 GE 仓源码里能 grep 到吗?
46+- [ ] 如果不能,它是 gen_esb 生成产物吗?(查 `docs/zh/user_guides/es_graph/tools/gen_esb.md`)
47+- [ ] 如果不是,它在 metadef 外部头里吗?(查 `inc/graph_metadef/register/graph_register.h` 的 include 链)
48+- [ ] 如果都不是,官方文档有提到吗?(hiascend.com 搜索)
49+- [ ] 我标注信息源了吗?("GE 仓确认" / "官方文档" / "未验证")
50+ 
51+---
52+ 
53+## 要点 2:(待补充)
54+ 
55+后续开发流程要点持续追加到此文件。
@@ -0,0 +1,115 @@
1+# ONNX Plugin Python 化需求分析
2+ 
3+相关开源仓:ops-nn: https://gitcode.com/cann/ops-nn,GE: https://gitcode.com/cann/ge,metadef: https://gitcode.com/cann/metadef。
4+ 
5+本文基于 `ops-nn/activation/threshold/framework/threshold_relu_onnx_plugin.cpp` 分析。目标不是改造现有 C++ 插件,而是新增一套 Python 化能力,让开发者可以用 Python 编写 ONNX 到 GE IR 的转换逻辑。
6+ 
7+注意ops-nn仓仅是说明当前onnx插件的C++实现而选取的示例说明仓,而并非实际代码开发仓,代码开发主要在GE仓完成
8+ 
9+## 1. 目标能力
10+ 
11+当前 C++ 文件完成了三件事:
12+ 
13+1. 注册 ONNX 原始算子到 GE 目标算子:
14+ `ThresholdedRelu` 注册为 GE 的 `PartitionedCall`
15+2. 解析 ONNX `NodeProto`(一对一):
16+ 读取 `node.name()``alpha` 属性,设置到 `ge::Operator`,并注册动态输入输出。
17+3. 构造 GE 子图(一对多):
18+`Data -> Identity -> Threshold -> Mul` 表达 `ThresholdedRelu`,再设置 `Graph` 输入输出。
19+ 
20+Python 化后的总体目标按当前 API 方向表达如下。首轮只交付 `parse_node``expand` 和 ES 构图属于迭代 2。
21+ 
22+**迭代 1:`parse_node` 属性和动态 IO**
23+ 
24+```python
25+thresholded_relu = onnx_plugin(
26+ source="ThresholdedRelu",
27+ domain="ai.onnx",
28+ opsets=range(10, 19),
29+ target="PartitionedCall",
30+ backend=Backend.TVM,
31+)
32+ 
33+ 
34+@thresholded_relu.parse_node
35+def parse_thresholded_relu(node, target):
36+ target.set_attr("name", node.name)
37+ target.set_attr("alpha", node.attrs.get("alpha", 1.0))
38+ target.set_attr("original_type", node.op_type)
39+ target.register_dynamic_input("x", 1)
40+ target.register_dynamic_output("y", 1)
41+ 
42+ 
43+```
44+ 
45+**迭代 2 草案:`expand` 和 ES 子图(非首轮 API 承诺)**
46+ 
47+准确 ES 签名必须在迭代 2 编码前核验;复用 ES,不新增字符串型万能算子工厂。
48+ 
49+```python
50+@thresholded_relu.expand
51+def expand_thresholded_relu(source):
52+ builder = GraphBuilder(source.name)
53+ data = builder.create_input(0, name=f"{source.name}_data")
54+ identity = Identity(data)
55+ selected = Threshold(identity, threshold=source.get_attr("alpha", 1.0))
56+ output = Mul(identity, selected)
57+ return builder.build_and_reset(outputs=[output])
58+```
59+ 
60+这里的重点是:Python 层要提供“ONNX node 读取、GE Operator 属性设置、动态 IO 注册、GE 子图构建、插件注册描述”这些能力,而不是让开发者直接接触 C++ 指针和 `REGISTER_CUSTOM_OP`
61+ 
62+## 2. 需要 Python 化的 GE 能力
63+ 
64+结合 GE 和 metadef,需要重点分析和补齐以下接口。
65+ 
66+| 能力 | GE 侧类/接口 | Python 化要求 | 技术方案对应 |
67+| --- | --- | --- | --- |
68+| 插件注册描述 | metadef `inc/external/register/register.h` 中的 `REGISTER_CUSTOM_OP``OpRegistrationData` | Python 需能描述 `ge_op``origin_types``framework=ONNX``imply_type`,对应 C++ 的 `FrameworkType``OriginOpType``ImplyType` | `onnx_plugin(source, domain, opsets, target, imply_type)`,见[能力映射](../analysis/onnx_plugin_python_modification_plan.md#能力在-python-中的体现) |
69+| 注册表接入 | `OpRegistrationTbe::Finalize``OpParserFactory``OpRegistry::Register` | Python 注册信息必须先完成 parser creator 注册,再写入 callback/run-mode map;不能只调用 `OpRegistry::Register`,否则 `CreateOpParser` 可能失败 | 用户无感知,由 registration coordinator 自动完成,见[注册流程](../analysis/onnx_plugin_python_modification_plan.md#注册流程) |
70+| ONNX 输入 | metadef `graph_metadef/proto/onnx/ge_onnx.proto` 生成的 `ge.onnx.NodeProto` | Python 暴露 name、origin type、inputs、outputs 和 attrs,隐藏 protobuf 细节 | `OnnxNode`,见[NodeProto 转换](../analysis/onnx_plugin_python_modification_plan.md#nodeproto-转换) |
71+| ParseParams 回调 | metadef `ParseParamFunc = Status(const google::protobuf::Message*, ge::Operator&)` | Python 函数被 bridge 成 GE 可调用回调;用户返回 `None`,bridge 转换为 GE Status | `@plugin.parse_node(node, target)`,见[parse_node 回调](../analysis/onnx_plugin_python_modification_plan.md#parse_node-回调) |
72+| ParseOpToGraph 回调 | metadef `ParseOpToGraphFunc = Status(const ge::Operator&, ge::Graph&)` | Python 表达把单个 GE op 展开成 `ge::Graph` 的逻辑 | 迭代 2 的 `@plugin.expand` 目标形态,见[后续迭代](../analysis/onnx_plugin_python_modification_plan.md#后续迭代) |
73+| GE Operator 属性 | GE `ge::Operator::SetAttr/GetAttr`,路径 `inc/graph_metadef/external/graph/operator.h` | Python 首轮支持常用标量和同类型列表;Tensor/DataType 后续开放 | `target.get_attr/set_attr`,见[Operator 基础接口](../analysis/onnx_plugin_python_modification_plan.md#operator-基础接口) |
74+| 动态输入输出 | `Operator::DynamicInputRegister``DynamicOutputRegister` | Python 直接注册默认动态端口;不引入 `DynamicPort` 或万能 context | `target.register_dynamic_input/output`,见[动态输入示例](../analysis/onnx_plugin_python_modification_plan.md#动态输入示例sum) |
75+| TensorDesc 修改 | `Operator::GetInputDesc/GetOutputDesc/UpdateInputDesc/UpdateOutputDesc``GeTensorDesc::SetFormat/SetOriginFormat/SetDataType` | 复用 `ge.graph.TensorDesc`;先冻结关联 handle 和更新同步语义 | 迭代 2,见[后续能力边界](../analysis/onnx_plugin_python_modification_plan.md#附录-b后续能力边界) |
76+| GE 子图构建 | `ge::Graph::SetInputs/SetOutputs`,路径 `inc/graph_metadef/external/graph/graph.h` | 复用 ES GraphBuilder 创建子图和设置输出 | 迭代 2,见[ThresholdedRelu 草案](../analysis/onnx_plugin_python_modification_plan.md#后续迭代) |
77+| GE IR op 创建 | C++ 当前通过 `op::Data``op::Identity``op::Threshold``op::Mul` 等生成 | 迭代 2 复用现有 ES `GraphBuilder` 和生成式算子;不新增字符串型万能工厂 | 详见[C++ 到 Python 映射](../analysis/onnx_plugin_python_modification_plan.md#附录-ac-到-python-详细映射) |
78+ 
79+**注意**
80+当前GE仓已经有了api/python/ge/ge的python模块,上述能力应该尽量使用ge python模块已有的能力,必要时再添加新增python子模块到ge中
81+ 
82+## 3. 需要进一步打开的分析点
83+ 
84+| 分析点 | 当前结论 | 状态 | 技术方案对应 |
85+|---|---|---|---|
86+| 注册链路如何接入 Python | 复用 `ASCEND_CUSTOM_OPP_PATH` 和统一 loader;coordinator 预检查后按 `Finalize -> Register` 提交 | 主体方案已明确,atc/online/session 初始化锚点待验证 | [框架主链](../analysis/onnx_plugin_python_modification_plan.md#框架主链)、[注册流程](../analysis/onnx_plugin_python_modification_plan.md#注册流程) |
87+| `ASCEND_CUSTOM_OPP_PATH` 是否复用 | 与现有 custom_op 共享路径和 loader,避免新增 ONNX plugin 环境变量 | 待 SE 专项评审,需确认扫描规则、初始化时序、插件共存、无插件回归和性能影响 | [环境变量评审](../analysis/onnx_plugin_python_developer_design.md#47-环境变量评审ascend_custom_opp_path) |
88+| GraphBuilder 最小集合 | 复用 ES 的输入、Identity、Threshold、Mul 和 Graph 输出能力;Resize/TopK 等按真实需求扩展 | 迭代 2,准确 Python 签名待核验 | [后续迭代](../analysis/onnx_plugin_python_modification_plan.md#后续迭代) |
89+| 新增/修改模块和接口全集 | 公开层增加 `ge.onnx_plugin``ge.graph.Operator`;内部增加 registry/bootstrap/bridge/coordinator | 首轮清单已列出,公开方法名待评审 | [接口设计](../analysis/onnx_plugin_python_modification_plan.md#接口设计)、[对子模块的修改](../analysis/onnx_plugin_python_modification_plan.md#对子模块的修改) |
90+| Python/C++ 对象生命周期 | descriptor 进程级;OnnxNode 和 Operator callback 级;Operator 回调结束立即失效 | 设计已明确,handle 实现和 GIL/并发待评审 | [关键数据结构](../analysis/onnx_plugin_python_modification_plan.md#关键数据结构) |
91+ 
92+ 
93+## 4. 计划
94+ 
95+| 顺序 | 原计划 | 当前落实情况 | 启动条件 |
96+|---:|---|---|---|
97+| 1 | 列出新增/修改 Python 模块和接口全集 | 技术方案已提供能力映射、公开接口草案、模块改动表和详细附录 | 公开接口评审确认 Operator 方法名 |
98+| 2 | 给出 ThresholdedRelu 的属性、动态 IO 和子图完整示例 | 属性/动态 IO 分别由 Elu/Sum 在首轮验证;ThresholdedRelu ES 草案已给出,完整可运行示例调整到迭代 2 | 需求方和 SE 确认延期;迭代 2 前核验 ES 准确签名 |
99+| 3 | 启动开发 | 尚未启动 | 完成对外接口评审、环境变量复用 SE 评审、初始化锚点验证、bridge ABI/GIL 冻结和延期确认 |
100+ 
101+详细追踪见技术方案的[原需求分析点与计划追踪](../analysis/onnx_plugin_python_modification_plan.md#原需求分析点与计划追踪)。
102+ 
103+## 5. 需求基线变更记录
104+ 
105+本文件保留原始需求目标,不直接改写为首轮交付承诺。根据 PR 2 评审后的分期决策,交付顺序调整如下:
106+ 
107+| 原始事项 | 当前分期 |
108+|---|---|
109+| Python 插件注册和 ONNX parser 主链 | 迭代 1,使用 Elu 等价逻辑验证 |
110+| 动态输入注册 | 迭代 1,使用 Sum→AccumulateNV2 等价逻辑验证 |
111+| ThresholdedRelu 属性和动态 IO | 迭代 2 |
112+| `Data -> Identity -> Threshold -> Mul` ES 子图 | 迭代 2 |
113+| Graph 输入输出和 `ParseOpToGraphFn` | 迭代 2 |
114+ 
115+该变更只调整实现分期,不取消 ThresholdedRelu 完整交付物。启动迭代 1 编码前,需由需求方和 SE 确认延期安排;确认结果应补充记录在 `hfqx/analysis/onnx_plugin_python_pr_2_revision_requirements.md` 的待确认章节,当前尚未视为已确认。