草稿
[WIP]【PR】: 迁移 ONNX Plugin 文档至 develop #2
gentle-knight创建于 28 天前
[WIP]【PR】: 迁移 ONNX Plugin 文档至 develop #2
草稿
gentle-knight创建于 28 天前
gentle-knight
gentle-knight成员
28 天前

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。

如何测试

  1. 校验相对 develop 仅包含 12 个预期文档文件,且无 walkthrough 与空 notebook。
  2. 校验 README 本地链接、5 张 Mermaid 图、Python 示例 AST,并执行全量 pre-commit/OAT。

测试结果:所有本地链接有效;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 的远端文件范围验证通过后关闭。

likedislike
合并受阻
GengChao
GengChao28 天前进行代码检视1
hfqx/analysis/onnx_plugin_python_modification_plan.md
已过期
@@ -0,0 +188,4 @@
188+ 
189+| C++ 接口/属性 | 当前插件用途 | Python 对应 | 动作与状态 |
190+|---|---|---|---|
191+| `Operator::GetName/GetOpType` | 读取节点名和 target 类型 | `OperatorView.name/type` | 新增只读视图 |
GengChao
GengChao28 天前评论:

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

likedislike
GengChao
GengChao28 天前进行代码检视1
hfqx/analysis/onnx_plugin_python_modification_plan.md
已过期
@@ -0,0 +531,4 @@
531+ )
532+ output_desc = target.output_desc(0)
533+ output_desc.set_data_type(DataType(dst_dtype))
534+ return OperatorUpdate(
GengChao
GengChao28 天前评论:

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

likedislike
GengChao
GengChao28 天前进行代码检视1
hfqx/analysis/onnx_plugin_python_modification_plan.md
已过期
@@ -0,0 +488,4 @@
488+ "alpha": node.attrs.get("alpha", 1.0),
489+ "original_type": "ai.onnx::11::ThresholdedRelu",
490+ },
491+ dynamic_inputs=(DynamicPort("x", 1),),
GengChao
GengChao28 天前评论:

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

likedislike
GengChao
GengChao28 天前进行代码检视1
hfqx/analysis/onnx_plugin_python_modification_plan.md
已过期
@@ -0,0 +647,4 @@
647+ 
648+### 基于调研结果对原计划的修订
649+ 
650+| 原计划问题 | 本次修订 |
GengChao
GengChao28 天前评论:

事情列的挺多,一个迭代做不完,建议你列一个表单,我们当前迭代支持哪些parsr接口,接口里面支持哪些类型的属性设置 ; 其他的暂不支持; 第一个迭代打通流程和涵盖基本的插件注册的写法即可

likedislike
gentle-knightgentle-knight成员
27 天前 推送  1 个提交:ae4a7f1f-docs: 完善 ONNX Plugin Python 化设计文档
gentle-knightgentle-knight成员
27 天前 推送  1 个提交:71872be6-docs: 从 PR 中移除串讲稿
gentle-knightgentle-knight成员
27 天前 强制推送  1 个提交:331aa1ff-docs: 完善 ONNX Plugin Python 化设计文档
gentle-knightgentle-knight成员
27 天前 推送  1 个提交:81849177-docs: 补充公开接口评审与 ST Sample
gentle-knightgentle-knight成员
27 天前 推送  1 个提交:7a649bf7-docs: 完善 ImplyType 和端口注册设计
gentle-knightgentle-knight成员
24 天前 推送  1 个提交:caa645a9-docs: 补充 ONNX Plugin 竞品调研
gentle-knightgentle-knight成员
24 天前 推送  1 个提交:20558035-docs: 增加环境变量复用评审门禁
gentle-knightgentle-knight成员
15 天前 设置为草稿状态
gentle-knight
gentle-knight成员
13 天前 评论:

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++ 文件完成了三件事:

  1. 注册 ONNX 原始算子到 GE 目标算子:
    ThresholdedRelu 注册为 GE 的 PartitionedCall
  2. 解析 ONNX NodeProto(一对一):
    读取 node.name()alpha 属性,设置到 ge::Operator,并注册动态输入输出。
  3. 构造 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_OPOpRegistrationData Python 需能描述 ge_oporigin_typesframework=ONNXimply_type,对应 C++ 的 FrameworkTypeOriginOpTypeImplyType
注册表接入 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 需暴露 nameinputsoutputsattr(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::DynamicInputRegisterDynamicOutputRegister Python 提供 ctx.dest.dynamic_input(name, num)dynamic_output(name, num)
TensorDesc 修改 Operator::GetInputDesc/GetOutputDesc/UpdateInputDesc/UpdateOutputDescGeTensorDesc::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::Dataop::Identityop::Thresholdop::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 的最小集合。
至少需要 DataIdentityThresholdMulGraph.SetInputsGraph.SetOutputs。后续扩展到 Resize/TopK 时再补
ConstCastShapeSlice、控制边和 Tensor 常量。
注意:当前实例使用的Graph.SetInputs的构图方式,推荐使用ES构图的方式,ES构图简洁且目前已经有python能力,无需对上述接口做封装

第三,列出来本需求需要修改或者新增的python模块和接口的完整集合
基于前面说的分析和补齐的接口进一步确认

第四,Python 对象和 GE C++ 对象的生命周期。
使用ctypes或者pybind提供python能力,需要注意生命周期的管理

4. 计划

预期依次完成如下事项

  1. 列出来本需求需要修改或者新增的python模块和接口的完整集合
  2. 给出来一个 用 Python 写出的 ThresholdedRelu 插件,在 ONNX 解析链路中生成的 GE op 属性、动态 IO 和子图结构最终示例
  3. 启动开发

其中1,2 完成之后找我和SE赵鑫鑫对齐,对齐之后再启动开发

likedislike
gentle-knight
gentle-knight成员
13 天前 评论:

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::RegisterAdapterOpType 建立 source 到 target 的映射并参与 callback 查找。
REGISTER_CUSTOM_OP("Elu") GE target type FinalizeOpParserFactoryOperatorFactory 注册 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,因此需要 OnnxGraphOperationConverterResultOnnxMapping。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 补充属性和实例端口 本次设计主体
likedislike