草稿
[WIP]【PR】: 迁移 ONNX Plugin 文档至 develop #2
gentle-knight创建于 7月30日
[WIP]【PR】: 迁移 ONNX Plugin 文档至 develop #2
草稿
共 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 | +``` | ||
Ahfqx/analysis/archive/onnx_plugin_python_previous_20260730/onnx_plugin_cpp_capability_inventory.md+1181-0
| @@ -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). | ||
Ahfqx/analysis/archive/onnx_plugin_python_previous_20260730/onnx_plugin_python_modification_plan.md+440-0
| @@ -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 ®_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 ®_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 | +- 未检查图片引用 `` | ||
| 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` 的待确认章节,当前尚未视为已确认。 | ||