合并受阻
可以就叫Operator,放在ge的graph模块下,这样后续可以按需扩展其他场景需要的能力到operator.py,直到跟ge::Operator能力基本一致


Operator不要定位为只读+Update才能更改了,这样比较别扭; TensorDescUpdate一样的道理,就提供set接口让用户自己调用,然后更新后的tensordesc和持有它的operator对象


DynamicPort的封装没有必要,提供一个用例 当ir既有可选,必选,动态输入的时候 这个函数应该怎么写


ONNX Plugin Python 化需求分析
相关开源仓:ops-nn: https://gitcode.com/cann/ops-nn,GE: https://gitcode.com/cann/ge,metadef: https://gitcode.com/cann/metadef。
本文基于 ops-nn/activation/threshold/framework/threshold_relu_onnx_plugin.cpp 分析。目标不是改造现有 C++ 插件,而是新增一套 Python 化能力,让开发者可以用 Python 编写 ONNX 到 GE IR 的转换逻辑。
注意ops-nn仓仅是说明当前onnx插件的C++实现而选取的示例说明仓,而并非实际代码开发仓,代码开发主要在GE仓完成
1. 目标能力
当前 C++ 文件完成了三件事:
- 注册 ONNX 原始算子到 GE 目标算子:
ThresholdedRelu注册为 GE 的PartitionedCall。 - 解析 ONNX
NodeProto(一对一):
读取node.name()和alpha属性,设置到ge::Operator,并注册动态输入输出。 - 构造 GE 子图(一对多):
用Data -> Identity -> Threshold -> Mul表达ThresholdedRelu,再设置Graph输入输出。
Python 化后,业务代码期望大致写成(仅是示例,代码设计不要局限于此):
@onnx_plugin.register(
ge_op="PartitionedCall",
origin_types=[f"ai.onnx::{v}::ThresholdedRelu" for v in range(10, 19)],
imply_type="TVM",
)
def thresholded_relu(ctx):
alpha = ctx.onnx.attr("alpha", default=1.0, dtype="float")
ctx.dest.set_attr("name", ctx.onnx.name)
ctx.dest.set_attr("alpha", alpha)
ctx.dest.set_attr("original_type", "ai.onnx::11::ThresholdedRelu")
ctx.dest.dynamic_input("x", 1)
ctx.dest.dynamic_output("y", 1)
x = ctx.graph.data("data1", index=0)
identity = ctx.graph.op("Identity", "identity").input("x", x)
threshold = ctx.graph.op("Threshold", "threshold").input("x", identity).attr("threshold", alpha)
y = ctx.graph.op("Mul", "mul").input("x1", identity).input("x2", threshold)
ctx.graph.inputs([x]).outputs([(y, [0])])
这里的重点是:Python 层要提供“ONNX node 读取、GE Operator 属性设置、动态 IO 注册、GE 子图构建、插件注册描述”这些能力,而不是让开发者直接接触 C++ 指针和 REGISTER_CUSTOM_OP。
2. 需要 Python 化的 GE 能力
结合 GE 和 metadef,需要重点分析和补齐以下接口。
| 能力 | GE 侧类/接口 | Python 化要求 |
|---|---|---|
| 插件注册描述 | metadef inc/external/register/register.h 中的 REGISTER_CUSTOM_OP、OpRegistrationData |
Python 需能描述 ge_op、origin_types、framework=ONNX、imply_type,对应 C++ 的 FrameworkType、OriginOpType、ImplyType |
| 注册表接入 | GE 中 OpRegistry::Register,声明见 inc/graph_metadef/register/op_registry.h,实现见 graph_metadef/register/register.cpp |
Python 注册信息最终要进入 GE parser map,使 GE 能按 ge_op + origin_type 找到回调 |
| ONNX 输入 | metadef graph_metadef/proto/onnx/ge_onnx.proto 生成的 ge.onnx.NodeProto |
Python 需暴露 name、inputs、outputs、attr(name, default, dtype),隐藏 protobuf 细节 |
| ParseParams 回调 | metadef ParseParamFunc = Status(const google::protobuf::Message*, ge::Operator&) |
Python 函数需要被 bridge 成 GE 可调用回调,返回 SUCCESS/FAILED/PARAM_INVALID |
| ParseOpToGraph 回调 | metadef ParseOpToGraphFunc = Status(const ge::Operator&, ge::Graph&) |
Python 需要能表达把单个 GE op 展开成 ge::Graph 的逻辑 |
| GE Operator 属性 | GE ge::Operator::SetAttr/GetAttr,路径 inc/graph_metadef/external/graph/operator.h |
Python 支持 int、float、bool、str、list、Tensor、DataType 等常见属性类型 |
| 动态输入输出 | Operator::DynamicInputRegister、DynamicOutputRegister |
Python 提供 ctx.dest.dynamic_input(name, num)、dynamic_output(name, num) |
| TensorDesc 修改 | Operator::GetInputDesc/GetOutputDesc/UpdateInputDesc/UpdateOutputDesc,GeTensorDesc::SetFormat/SetOriginFormat/SetDataType |
MVP 可暂缓;完整能力需支持 format/dtype 修改,例如 MatMul、AntiQuant 类插件 |
| GE 子图构建 | ge::Graph::SetInputs/SetOutputs,路径 inc/graph_metadef/external/graph/graph.h |
Python 提供 graph builder,能创建 Data/Identity/Threshold/Mul/Const/Cast 等节点并设置输出 index |
| GE IR op 创建 | C++ 当前通过 op::Data、op::Identity、op::Threshold、op::Mul 等生成 |
Python 需要统一的 ctx.graph.op(type, name) 工厂,不能依赖每个 op 都手写 Python 类 |
注意:
当前GE仓已经有了api/python/ge/ge的python模块,上述能力应该尽量使用ge python模块已有的能力,必要时再添加新增python子模块到ge中
3. 需要进一步打开的分析点
第一,注册链路怎么接入 Python。
REGISTER_CUSTOM_OP 在 metadef 的 inc/external/register/register.h 中定义,本质是构造 OpRegistrationData 并通过静态 OpReceiver 收集注册数据。GE 当前 OpRegistry::Register 存的是 C++ 函数对象。Python 能力需要一个 C++ bridge:GE 仍注册 C++ 回调,回调内部按 origin_type 找到 Python 函数并执行。
python的注册考虑使用新的发现机制,比如复用ASCEND_CUSTOM_OPP_PATH(跟SE赵鑫鑫对齐,必要时评审)
第二,Graph builder 的最小集合。
至少需要 Data、Identity、Threshold、Mul、Graph.SetInputs、Graph.SetOutputs。后续扩展到 Resize/TopK 时再补
Const、Cast、Shape、Slice、控制边和 Tensor 常量。
注意:当前实例使用的Graph.SetInputs的构图方式,推荐使用ES构图的方式,ES构图简洁且目前已经有python能力,无需对上述接口做封装
第三,列出来本需求需要修改或者新增的python模块和接口的完整集合
基于前面说的分析和补齐的接口进一步确认
第四,Python 对象和 GE C++ 对象的生命周期。
使用ctypes或者pybind提供python能力,需要注意生命周期的管理
4. 计划
预期依次完成如下事项
- 列出来本需求需要修改或者新增的python模块和接口的完整集合
- 给出来一个 用 Python 写出的
ThresholdedRelu插件,在 ONNX 解析链路中生成的 GE op 属性、动态 IO 和子图结构最终示例 - 启动开发
其中1,2 完成之后找我和SE赵鑫鑫对齐,对齐之后再启动开发


ONNX Plugin Python 接口方案评审
文档说明
本文用于评审 GE ONNX Plugin 的 Python 公开接口。文档首先说明现有 C++ ONNX 到 GE IR 的链路,再比较 Python Plugin 的候选接口,最后给出当前推荐的用户界面。
本次评审范围包括:
- Python Plugin 组织方式;
- Python Plugin 发现方式
1. GE C++ plugin组织形式
当前 C++ Plugin 的典型写法如下:
REGISTER_CUSTOM_OP("Elu")
.FrameworkType(domi::ONNX)
.OriginOpType({
"ai.onnx::8::Elu",
"ai.onnx::9::Elu",
"ai.onnx::10::Elu",
"ai.onnx::11::Elu"
})
.ImplyType(domi::ImplyType::TVM)
.ParseParamsFn(ParseEluParams);
注册字段及其消费者如下:
| C++ 注册字段 | 保存内容 | ONNX 链路消费者 | 作用 |
|---|---|---|---|
FrameworkType(ONNX) |
framework 枚举 | 初始化入口、OpParserFactory |
选择 ONNX 注册数据和 parser factory。 |
OriginOpType |
完整 source type 集合 | OpRegistry::Register、AdapterOpType |
建立 source 到 target 的映射并参与 callback 查找。 |
REGISTER_CUSTOM_OP("Elu") |
GE target type | Finalize、OpParserFactory、OperatorFactory |
注册 parser creator并创建 target Operator。 |
ParseParamsFn |
parser callback | OnnxCustomParserAdapter |
读取 source 节点并补充 target Operator。 |
ImplyType |
声明性运行模式 | OpRegistry::Register、初始化日志 |
写入 op_run_mode_map_;ONNX parser 不读取该值。 |
2. 本次评审细节
2.2 Python Plugin 组织方式
2.2.1 方案一:单装饰器
@onnx_plugin(
source="Elu",
domain="ai.onnx",
opsets=range(8, 19),
target="Elu",
)
def parse_elu(node: OnnxNode, target: Operator) -> None:
target.set_attr("alpha", node.attrs.get("alpha", 1.0))
优点是首轮代码最短。主要问题是 descriptor 与一种 callback 直接绑定。后续增加一对多分解、ByOperator 或 subgraph callback 时,需要增加新的顶层装饰器或重复填写 source/domain/opsets/target。
a
2.2.2 方案二:descriptor + callback
elu = onnx_plugin(
source="Elu",
domain="ai.onnx",
opsets=range(8, 19),
target="Elu",
)
@elu.parse_node
def parse_elu(node: OnnxNode, target: Operator) -> None:
target.set_attr("alpha", node.attrs.get("alpha", 1.0))
# 一对多场景
@elu.decompose
def elu_decompose(source, builder):
...
descriptor 集中保存稳定的 source 到 target 描述,不同 callback 作为独立成员扩展。首轮只公开 parse_node,未来增加 decompose 等能力时不需要改变 descriptor 构造方式,也不需要重复注册字段。
2.2.3 方案三:class
@onnx_plugin(
source="Elu",
domain="ai.onnx",
opsets=range(8, 19),
target="Elu",
)
class EluPlugin:
def parse_node(self, node: OnnxNode, target: Operator) -> None:
target.set_attr("alpha", node.attrs.get("alpha", 1.0))
def decompose(self, source, builder):
...
class 可以聚合多个方法,但会为无状态 parser callback 引入实例、self 和对象生命周期,用户还需要理解框架何时创建 Plugin 实例。
2.2.4 方案对比
| 方案 | 首轮易用性 | 后续 callback 扩展 | 状态和生命周期 | 结论 |
|---|---|---|---|---|
| 单装饰器 | 最短 | 容易重复字段或增加顶层装饰器 | 无额外状态 | 不推荐 |
| descriptor + callback | 多一个 descriptor 变量 | descriptor 不变,增加成员即可 | descriptor 进程级,callback 普通函数 | 推荐 |
| class | 方法聚合 | 可以增加方法 | 引入不必要实例状态 | 不推荐 |
2.2.5 ImplyType 处理结论
源码核验确认注册 ImplyType 在 ONNX 链路中没有功能性消费者。Python descriptor 不提供 imply_type 参数,模块不导出 ImplyType Enum,内部保持 OpRegistrationData 默认 BUILDIN。该值不影响 ONNX 解析、target 创建和引擎选择。
后续只有在出现明确 ONNX 消费者和用户语义时,才通过独立公开接口评审新增该字段。
2.3 Python Plugin 发现方式
2.3.1 方案一:新增 ONNX 专用变量
export ASCEND_ONNX_PLUGIN_PATH=/path/to/plugin.py
该方案语义独立,但增加产品级环境变量、部署入口和文档。一个文件同时声明 Python custom_op 和 ONNX Plugin 时还需要重复配置。
2.3.2 方案二:复用现有直接扫描
export ASCEND_CUSTOM_OPP_PATH=/path/to/plugin.py
该方案改动最小,但会继续混合 OPP root、单 Python 文件、普通目录和 package 语义,普通 OPP root 下的无关 Python 文件可能被扫描。
推荐方案二
3. 推荐用户界面
3.1 Elu 示例
from ge.graph import Operator
from ge.onnx_plugin import OnnxNode, onnx_plugin
elu = onnx_plugin(
source="Elu",
domain="ai.onnx",
opsets=range(8, 19),
target="Elu",
)
@elu.parse_node
def parse_elu(node: OnnxNode, target: Operator) -> None:
alpha = node.attrs.get("alpha", 1.0)
if not isinstance(alpha, float):
raise TypeError("Elu alpha must be float")
target.set_attr("alpha", alpha)
用户只读取 source 属性并修改已有 target
3.2 后续 callback 扩展
descriptor + callback 允许未来增加独立转换阶段:
# 示意接口,名称、参数和返回值不属于首轮承诺。
@elu.decompose
def elu_decompose(source, builder):
...
首轮 OnnxPlugin 只公开 parse_node,不提前加入 decompose 占位方法。
4竞品用户界面
4.1 PyTorch ONNX Exporter
program = torch.onnx.export(
model,
args,
dynamo=True,
custom_translation_table={
torch.ops.aten.add.Tensor: custom_add,
},
)
其方向是 PyTorch/FX 到 ONNX,与本需求方向相反。可有限参考普通 Python callable 和诊断方式。
4.2 TensorRT ONNX Parser
parser = trt.OnnxParser(network, logger)
if not parser.parse(model_bytes):
for index in range(parser.num_errors):
print(parser.get_error(index))
TensorRT 的 ONNX 到 Network 映射位于内部 C++ importer,Python 用户不能注册新的节点映射。可参考 Parser/Network 分层和节点级错误。
4.3 TensorRT Python Plugin
@trtp.register("example::identity")
def identity_desc(inp: trtp.TensorDesc) -> trtp.TensorDesc:
return inp.like()
@trtp.impl("example::identity")
def identity_impl(inp, outputs, stream) -> None:
launch_identity(inp, outputs[0], stream)
该接口定义自定义层的 shape、dtype 和运行时 Kernel,不是 ONNX source 到已有 GE Operator 的映射。
4.4 onnx2torch
模型用户入口:
from onnx2torch import convert
torch_model = convert("model.onnx")
converter 开发者入口:
@add_converter(operation_type="Elu", version=6)
def convert_elu(node: OnnxNode, graph: OnnxGraph) -> OperationConverterResult:
return OperationConverterResult(
torch_module=nn.ELU(alpha=node.attributes.get("alpha", 1.0)),
onnx_mapping=onnx_mapping_from_node(node),
)
onnx2torch 从零创建 PyTorch Module 和 FX Graph,因此需要 OnnxGraph、OperationConverterResult 和 OnnxMapping。GE parser 已经创建 target Operator并负责入图和连边,不需要照搬这些对象。
4.5 横向结论
| 对象 | 转换方向 | 用户 callback 的主要职责 | 对本方案的参考 |
|---|---|---|---|
| PyTorch ONNX Exporter | PyTorch/FX -> ONNX | 创建 ONNX 表达 | callable 和诊断 |
| TensorRT ONNX Parser | ONNX -> TensorRT Network | Python 不可扩展节点映射 | Parser 分层和错误 |
| TensorRT Python Plugin | Custom Node -> Plugin Layer | shape/dtype/Kernel | 装饰器形式 |
| onnx2torch | ONNX -> PyTorch FX/Module | 创建 Module 和 mapping | 只读节点和属性默认值写法 |
| GE Python ONNX Plugin | ONNX -> 已有 GE target Operator | 补充属性和实例端口 | 本次设计主体 |


Pull Request
描述
将 cann/ge PR 4151 的有效文档内容迁移到个人 fork
gentle-knight/ge的 develop 分支,并纳入已完成复核的 ONNX Plugin C++ 链路调研与 Python 化方案修订。迁移保留 12 个有效 Markdown 文件;明确排除框架理解 walkthrough 文档
hfqx/analysis/ge_project_framework_understanding.md,并同步删除 README 中两处索引;同时排除无法通过 JSON/OAT 校验的零字节hfqx/dev-log/learning_record.ipynb。变更类型
关联的Issue
无。迁移自 cann/ge PR 4151。
如何测试
测试结果:所有本地链接有效;Mermaid 5/5 解析通过;可执行 Python 示例 AST 通过;pre-commit、codespell、detect-private-key 与 OAT 全部通过;五路迁移复核无阻塞项。
核对清单
其他信息
目标仓库:
gentle-knight/ge源分支:
docs/onnx-plugin-pr-4151-to-develop目标分支:
develop旧 PR 4151 与误建 PR 4204 将在本 PR 的远端文件范围验证通过后关闭。