已合并
feat: 用native bindings替换ONNX plugin Python objects #4421
feat: 用native bindings替换ONNX plugin Python objects #4421
已合并
gentle-knight创建于 8 天前
19 个文件变更+755-306
Mapi/python/ge/CMakeLists.txt+5-1
@@ -12,6 +12,7 @@ add_subdirectory(ge_api_c_wrapper)
12add_subdirectory(ge/passes)12add_subdirectory(ge/passes)
13add_subdirectory(ge/runtime)13add_subdirectory(ge/runtime)
14add_subdirectory(ge/custom_op)14add_subdirectory(ge/custom_op)
15+add_subdirectory(ge/onnx_plugin)
15file(GLOB_RECURSE GE_API_C_WRAPPER CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.py")16file(GLOB_RECURSE GE_API_C_WRAPPER CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.py")
16 17 
17execute_process(18execute_process(
@@ -184,6 +185,8 @@ add_custom_command(
184 && ${CMAKE_COMMAND} -E remove_directory ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/runtime/python_runtime_artifacts185 && ${CMAKE_COMMAND} -E remove_directory ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/runtime/python_runtime_artifacts
185 && ${CMAKE_COMMAND} -E remove ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/custom_op/CMakeLists.txt186 && ${CMAKE_COMMAND} -E remove ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/custom_op/CMakeLists.txt
186 && ${CMAKE_COMMAND} -E remove_directory ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/custom_op/native_bindings187 && ${CMAKE_COMMAND} -E remove_directory ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/custom_op/native_bindings
188+ && ${CMAKE_COMMAND} -E remove ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/onnx_plugin/CMakeLists.txt
189+ && ${CMAKE_COMMAND} -E remove_directory ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/onnx_plugin/native_bindings
187 && mkdir -p ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/runtime/python_runtime_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}190 && mkdir -p ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/runtime/python_runtime_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}
188 && cp ${GE_PYTHON_RUNTIME_ARTIFACT_MANIFEST} ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/runtime/python_runtime_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/manifest.json191 && cp ${GE_PYTHON_RUNTIME_ARTIFACT_MANIFEST} ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/runtime/python_runtime_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/manifest.json
189 && cp $<TARGET_FILE:_ge_runtime_native> ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/runtime/python_runtime_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/_ge_runtime_native.so192 && cp $<TARGET_FILE:_ge_runtime_native> ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/runtime/python_runtime_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/_ge_runtime_native.so
@@ -191,6 +194,7 @@ add_custom_command(
191 && cp ${GE_PYTHON_CUSTOM_OP_ARTIFACT_MANIFEST} ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/custom_op/python_custom_op_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/manifest.json194 && cp ${GE_PYTHON_CUSTOM_OP_ARTIFACT_MANIFEST} ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/custom_op/python_custom_op_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/manifest.json
192 && cp $<TARGET_FILE:ge_python_custom_op_bridge> ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/custom_op/python_custom_op_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/libge_python_custom_op_bridge.so195 && cp $<TARGET_FILE:ge_python_custom_op_bridge> ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/custom_op/python_custom_op_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/libge_python_custom_op_bridge.so
193 && cp $<TARGET_FILE:_ge_custom_op_native> ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/custom_op/python_custom_op_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/_ge_custom_op_native.so196 && cp $<TARGET_FILE:_ge_custom_op_native> ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/custom_op/python_custom_op_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/_ge_custom_op_native.so
197+ && cp $<TARGET_FILE:_ge_onnx_plugin_native> ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/onnx_plugin/_ge_onnx_plugin_native.so
194 && ${CMAKE_COMMAND} -E copy_directory ${GE_PYTHON_PASS_FALLBACK_CODEGEN_DIR} ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/passes/fallback_codegen198 && ${CMAKE_COMMAND} -E copy_directory ${GE_PYTHON_PASS_FALLBACK_CODEGEN_DIR} ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/passes/fallback_codegen
195 && find ${GE_PYTHON_MAIN_WHEEL_DIR}/ge -name __pycache__ -type d -exec rm -rf {} +199 && find ${GE_PYTHON_MAIN_WHEEL_DIR}/ge -name __pycache__ -type d -exec rm -rf {} +
196 && cp $<TARGET_FILE:graph_wrapper> ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/_capi/200 && cp $<TARGET_FILE:graph_wrapper> ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/_capi/
@@ -203,7 +207,7 @@ add_custom_command(
203 && cp -f dist/ge_py-0.0.1-py3-none-any.whl ${GE_PYTHON_MAIN_WHEEL}207 && cp -f dist/ge_py-0.0.1-py3-none-any.whl ${GE_PYTHON_MAIN_WHEEL}
204 && echo "Build ge-py wheel package end"208 && echo "Build ge-py wheel package end"
205 DEPENDS eager_style_graph_builder_base ge_api_c_wrapper graph_wrapper209 DEPENDS eager_style_graph_builder_base ge_api_c_wrapper graph_wrapper
206- _ge_runtime_native _ge_custom_op_native ge_python_custom_op_bridge210+ _ge_runtime_native _ge_custom_op_native _ge_onnx_plugin_native ge_python_custom_op_bridge
207 ${GE_API_C_WRAPPER}211 ${GE_API_C_WRAPPER}
208 ${GE_PYTHON_RUNTIME_ARTIFACT_MANIFEST}212 ${GE_PYTHON_RUNTIME_ARTIFACT_MANIFEST}
209 ${GE_PYTHON_CUSTOM_OP_ARTIFACT_MANIFEST}213 ${GE_PYTHON_CUSTOM_OP_ARTIFACT_MANIFEST}
Mapi/python/ge/ge/_capi/pygraph_wrapper.py+33-0
@@ -377,6 +377,39 @@ graph_lib.GeApiWrapper_GNode_GetOutputDesc.argtypes = [c_void_p, c_int32]
377graph_lib.GeApiWrapper_GNode_UpdateOutputDesc.restype = c_int377graph_lib.GeApiWrapper_GNode_UpdateOutputDesc.restype = c_int
378graph_lib.GeApiWrapper_GNode_UpdateOutputDesc.argtypes = [c_void_p, c_int32, c_void_p]378graph_lib.GeApiWrapper_GNode_UpdateOutputDesc.argtypes = [c_void_p, c_int32, c_void_p]
379 379 
380+# ============ Borrowed Operator C API ============
381+graph_lib.GeApiWrapper_Operator_GetName.restype = c_char_ptr
382+graph_lib.GeApiWrapper_Operator_GetName.argtypes = [c_void_p]
383+ 
384+graph_lib.GeApiWrapper_Operator_GetType.restype = c_char_ptr
385+graph_lib.GeApiWrapper_Operator_GetType.argtypes = [c_void_p]
386+ 
387+graph_lib.GeApiWrapper_Operator_SetAttr.restype = c_int
388+graph_lib.GeApiWrapper_Operator_SetAttr.argtypes = [c_void_p, c_char_p, c_void_p]
389+ 
390+graph_lib.GeApiWrapper_Operator_InputRegister.restype = c_int
391+graph_lib.GeApiWrapper_Operator_InputRegister.argtypes = [c_void_p, c_char_p]
392+ 
393+graph_lib.GeApiWrapper_Operator_OptionalInputRegister.restype = c_int
394+graph_lib.GeApiWrapper_Operator_OptionalInputRegister.argtypes = [c_void_p, c_char_p]
395+ 
396+graph_lib.GeApiWrapper_Operator_OutputRegister.restype = c_int
397+graph_lib.GeApiWrapper_Operator_OutputRegister.argtypes = [c_void_p, c_char_p]
398+ 
399+graph_lib.GeApiWrapper_Operator_DynamicInputRegister.restype = c_int
400+graph_lib.GeApiWrapper_Operator_DynamicInputRegister.argtypes = [
401+ c_void_p,
402+ c_char_p,
403+ c_uint32,
404+]
405+ 
406+graph_lib.GeApiWrapper_Operator_DynamicOutputRegister.restype = c_int
407+graph_lib.GeApiWrapper_Operator_DynamicOutputRegister.argtypes = [
408+ c_void_p,
409+ c_char_p,
410+ c_uint32,
411+]
412+ 
380 413 
381# ============ TensorDesc C API ============414# ============ TensorDesc C API ============
382graph_lib.GeApiWrapper_TensorDesc_Create.restype = c_void_p415graph_lib.GeApiWrapper_TensorDesc_Create.restype = c_void_p
Mapi/python/ge/ge/graph/operator.py+84-14
@@ -12,11 +12,23 @@
12 12 
13"""GE operator object for reading and updating definition information."""13"""GE operator object for reading and updating definition information."""
14 14 
15+from __future__ import annotations
16+ 
17+import ctypes
18+ 
19+from ge._capi.pygraph_wrapper import graph_lib
20+ 
21+from ._attr import _AttrValue
22+ 
15_OPERATOR_FACTORY_TOKEN = object()23_OPERATOR_FACTORY_TOKEN = object()
16 24 
17 25 
18class Operator:26class Operator:
19- """GE operator borrowed for the duration of a callback."""27+ """GE operator borrowed for the duration of a callback.
28+ 
29+ The ctypes handle is borrowed from the C++ callback owner and is never
30+ created or destroyed by this wrapper.
31+ """
20 32 
21 __slots__ = ("_handle", "_valid")33 __slots__ = ("_handle", "_valid")
22 34 
@@ -25,6 +37,11 @@ class Operator:
25 raise RuntimeError("Operator objects should not be created directly.")37 raise RuntimeError("Operator objects should not be created directly.")
26 if handle is None:38 if handle is None:
27 raise ValueError("Operator handle cannot be None")39 raise ValueError("Operator handle cannot be None")
40+ 
41+ if isinstance(handle, int):
42+ handle = ctypes.c_void_p(handle)
43+ if not isinstance(handle, ctypes.c_void_p) or not handle:
44+ raise ValueError("Operator handle cannot be null")
28 self._handle = handle45 self._handle = handle
29 self._valid = True46 self._valid = True
30 47 
@@ -41,7 +58,7 @@ class Operator:
41 if not self._valid:58 if not self._valid:
42 return59 return
43 self._valid = False60 self._valid = False
44- self._handle.invalidate()61+ self._handle = ctypes.c_void_p()
45 62 
46 @staticmethod63 @staticmethod
47 def _validate_name(name: str, kind: str) -> None:64 def _validate_name(name: str, kind: str) -> None:
@@ -51,22 +68,50 @@ class Operator:
51 @property68 @property
52 def name(self) -> str:69 def name(self) -> str:
53 self._ensure_valid()70 self._ensure_valid()
54- return self._handle.get_name()71+ return self._get_string(graph_lib.GeApiWrapper_Operator_GetName)
55 72 
56 @property73 @property
57 def type(self) -> str:74 def type(self) -> str:
58 self._ensure_valid()75 self._ensure_valid()
59- return self._handle.get_type()76+ return self._get_string(graph_lib.GeApiWrapper_Operator_GetType)
60 77 
61 def set_attr(self, name: str, value: object) -> None:78 def set_attr(self, name: str, value: object) -> None:
62 self._ensure_valid()79 self._ensure_valid()
63 self._validate_name(name, "attribute")80 self._validate_name(name, "attribute")
64- if type(value) is int:81+ 
65- if value < -(1 << 63) or value >= 1 << 63:82+ attr_value = _AttrValue()
66- raise ValueError("Operator int attribute must be in int64 range")83+ attr_value.set_value(value)
67- elif type(value) is not float:84+ ret = graph_lib.GeApiWrapper_Operator_SetAttr(
68- raise TypeError("Operator set_attr only supports int and float values")85+ self._handle, name.encode("utf-8"), attr_value._av_ptr
69- self._handle.set_attr(name, value)86+ )
87+ if ret != 0:
88+ raise RuntimeError(
89+ f"Failed to set attribute '{name}' on Operator {self.name}"
90+ )
91+ 
92+ def register_input(self, name: str) -> None:
93+ self._register_port(
94+ name,
95+ "input",
96+ "register_input",
97+ graph_lib.GeApiWrapper_Operator_InputRegister,
98+ )
99+ 
100+ def register_optional_input(self, name: str) -> None:
101+ self._register_port(
102+ name,
103+ "optional input",
104+ "register_optional_input",
105+ graph_lib.GeApiWrapper_Operator_OptionalInputRegister,
106+ )
107+ 
108+ def register_output(self, name: str) -> None:
109+ self._register_port(
110+ name,
111+ "output",
112+ "register_output",
113+ graph_lib.GeApiWrapper_Operator_OutputRegister,
114+ )
70 115 
71 def register_dynamic_input(self, name: str, count: int) -> None:116 def register_dynamic_input(self, name: str, count: int) -> None:
72 self._register_dynamic_port(name, count, is_input=True)117 self._register_dynamic_port(name, count, is_input=True)
@@ -74,6 +119,15 @@ class Operator:
74 def register_dynamic_output(self, name: str, count: int) -> None:119 def register_dynamic_output(self, name: str, count: int) -> None:
75 self._register_dynamic_port(name, count, is_input=False)120 self._register_dynamic_port(name, count, is_input=False)
76 121 
122+ def _register_port(self, name: str, kind: str, method_name: str, c_func) -> None:
123+ self._ensure_valid()
124+ self._validate_name(name, kind)
125+ ret = c_func(self._handle, name.encode("utf-8"))
126+ if ret != 0:
127+ raise RuntimeError(
128+ f"Failed to {method_name} '{name}' on Operator {self.name}"
129+ )
130+ 
77 def _register_dynamic_port(self, name: str, count: int, *, is_input: bool) -> None:131 def _register_dynamic_port(self, name: str, count: int, *, is_input: bool) -> None:
78 self._ensure_valid()132 self._ensure_valid()
79 self._validate_name(name, "dynamic port")133 self._validate_name(name, "dynamic port")
@@ -81,10 +135,26 @@ class Operator:
81 raise TypeError("Operator dynamic port count must be an integer")135 raise TypeError("Operator dynamic port count must be an integer")
82 if count < 0 or count >= 1 << 32:136 if count < 0 or count >= 1 << 32:
83 raise ValueError("Operator dynamic port count must be in uint32 range")137 raise ValueError("Operator dynamic port count must be in uint32 range")
84- if is_input:138+ c_func = (
85- self._handle.register_dynamic_input(name, count)139+ graph_lib.GeApiWrapper_Operator_DynamicInputRegister
86- else:140+ if is_input
87- self._handle.register_dynamic_output(name, count)141+ else graph_lib.GeApiWrapper_Operator_DynamicOutputRegister
142+ )
143+ ret = c_func(self._handle, name.encode("utf-8"), ctypes.c_uint32(count))
144+ if ret != 0:
145+ direction = "input" if is_input else "output"
146+ raise RuntimeError(
147+ f"Failed to register dynamic {direction} '{name}' on Operator {self.name}"
148+ )
149+ 
150+ def _get_string(self, c_func) -> str:
151+ c_str = c_func(self._handle)
152+ if not c_str:
153+ raise RuntimeError("Failed to get Operator name or type")
154+ try:
155+ return ctypes.string_at(c_str).decode("utf-8")
156+ finally:
157+ graph_lib.GeApiWrapper_FreeString(c_str)
88 158 
89 def _ensure_valid(self) -> None:159 def _ensure_valid(self) -> None:
90 if not self._valid:160 if not self._valid:
Aapi/python/ge/ge/onnx_plugin/CMakeLists.txt+49-0
@@ -0,0 +1,49 @@
1+# -----------------------------------------------------------------------------------------------------------
2+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+# CANN Open Software License Agreement Version 2.0 (the "License").
5+# Please refer to the License for the specific language governing permissions and limitations under the License.
6+# -----------------------------------------------------------------------------------------------------------
7+ 
8+set(_ge_onnx_plugin_native_sources
9+ ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/module.cc
10+ ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/onnx_node_binding.cc
11+)
12+ 
13+add_library(_ge_onnx_plugin_native MODULE ${_ge_onnx_plugin_native_sources})
14+target_include_directories(_ge_onnx_plugin_native PRIVATE
15+ ${HI_PYTHON_INC}
16+ ${pybind11_INCLUDE_DIR}
17+ ${AIR_CODE_DIR}/inc/external
18+ ${METADEF_DIR}/inc/external
19+ ${METADEF_DIR}/pkg_inc
20+ ${CMAKE_SOURCE_DIR}/inc/graph_metadef/external
21+ ${CMAKE_BINARY_DIR}/proto/metadef_protos
22+)
23+ 
24+target_link_libraries(_ge_onnx_plugin_native PRIVATE
25+ $<BUILD_INTERFACE:ge_intf_pub>
26+ $<BUILD_INTERFACE:ascend_protobuf>
27+ graph_base
28+ $<BUILD_INTERFACE:slog_headers>
29+ $<BUILD_INTERFACE:mmpa_headers>
30+ $<BUILD_INTERFACE:pybind_options>
31+)
32+ 
33+set_target_properties(_ge_onnx_plugin_native
34+ PROPERTIES
35+ PREFIX ""
36+)
37+ 
38+target_compile_definitions(_ge_onnx_plugin_native PRIVATE
39+ google=ascend_private
40+ PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF
41+)
42+ 
43+target_compile_options(_ge_onnx_plugin_native PRIVATE
44+ $<$<CONFIG:Release>:-O2>
45+)
46+ 
47+target_link_options(_ge_onnx_plugin_native PRIVATE
48+ $<$<CONFIG:Release>:-s>
49+)
Mapi/python/ge/ge/onnx_plugin/__init__.py+1-1
@@ -12,7 +12,7 @@
12 12 
13"""Python ONNX Plugin public package."""13"""Python ONNX Plugin public package."""
14 14 
15-from .onnx_node import OnnxNode15+from ._native import OnnxNode
16from .plugin import OnnxPlugin, onnx_plugin16from .plugin import OnnxPlugin, onnx_plugin
17 17 
18__all__ = ["OnnxNode", "OnnxPlugin", "onnx_plugin"]18__all__ = ["OnnxNode", "OnnxPlugin", "onnx_plugin"]
Mapi/python/ge/ge/onnx_plugin/_bridge.py+8-5
@@ -15,7 +15,7 @@
15from ge.graph.operator import create_operator15from ge.graph.operator import create_operator
16 16 
17from .bootstrap import load_onnx_plugins17from .bootstrap import load_onnx_plugins
18-from .onnx_node import create_onnx_node18+from ._native import OnnxNode
19from .registry import (19from .registry import (
20 get_registered_onnx_plugin_by_origin_type,20 get_registered_onnx_plugin_by_origin_type,
21 get_registered_onnx_plugin_dicts,21 get_registered_onnx_plugin_dicts,
@@ -27,15 +27,18 @@ def load_and_get_onnx_plugin_descriptors() -> list:
27 return get_registered_onnx_plugin_dicts()27 return get_registered_onnx_plugin_dicts()
28 28 
29 29 
30-def call_parse_node(origin_type: str, node_values: dict, operator_backend) -> None:30+def call_parse_node(origin_type: str, node: OnnxNode, operator_handle) -> None:
31- """Dispatch one flattened ONNX node to its registered parse_node callback."""31+ """Dispatch one parser-owned ONNX node to its registered parse_node callback.
32+ 
33+ ``node`` and ``operator_handle`` are borrowed objects supplied by the C++
34+ bridge for the duration of the callback.
35+ """
32 36 
33 descriptor = get_registered_onnx_plugin_by_origin_type(origin_type)37 descriptor = get_registered_onnx_plugin_by_origin_type(origin_type)
34 if descriptor is None:38 if descriptor is None:
35 raise KeyError(f"python ONNX Plugin is not registered: {origin_type}")39 raise KeyError(f"python ONNX Plugin is not registered: {origin_type}")
36 40 
37- node = create_onnx_node(**node_values)41+ with create_operator(operator_handle) as target:
38- with create_operator(operator_backend) as target:
39 result = descriptor.parser_node(node, target)42 result = descriptor.parser_node(node, target)
40 if result is not None:43 if result is not None:
41 raise TypeError("ONNX Plugin parse_node callback must return None")44 raise TypeError("ONNX Plugin parse_node callback must return None")
Aapi/python/ge/ge/onnx_plugin/_native.py+21-0
@@ -0,0 +1,21 @@
1+#!/usr/bin/env python3
2+# -*- coding: utf-8 -*-
3+# -----------------------------------------------------------------------------------------------------------
4+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
5+# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
6+# CANN Open Software License Agreement Version 2.0 (the "License").
7+# Please refer to the License for details. You may not use this file except in compliance with the License.
8+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
9+# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
10+# See LICENSE in the root of the software repository for the full text of the License.
11+# -----------------------------------------------------------------------------------------------------------
12+ 
13+"""Load the native ONNX plugin value module."""
14+ 
15+from __future__ import annotations
16+ 
17+from importlib import import_module
18+ 
19+_native = import_module("ge.onnx_plugin._ge_onnx_plugin_native")
20+ 
21+OnnxNode = _native.OnnxNode
Aapi/python/ge/ge/onnx_plugin/native_bindings/binding_common.h+30-0
@@ -0,0 +1,30 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#ifndef API_PYTHON_GE_GE_ONNX_PLUGIN_NATIVE_BINDINGS_BINDING_COMMON_H_
12+#define API_PYTHON_GE_GE_ONNX_PLUGIN_NATIVE_BINDINGS_BINDING_COMMON_H_
13+ 
14+#include "pybind11/detail/common.h"
15+#ifdef ASCEND_CI_LIMITED_PY37
16+#undef PyCFunction_NewEx
17+#endif
18+#include "pybind11/pybind11.h"
19+#include "pybind11/stl.h"
20+ 
21+#undef PYBIND11_CHECK_PYTHON_VERSION
22+#define PYBIND11_CHECK_PYTHON_VERSION
23+ 
24+namespace ge {
25+namespace python_onnx_plugin_native {
26+namespace py = pybind11;
27+} // namespace python_onnx_plugin_native
28+} // namespace ge
29+ 
30+#endif // API_PYTHON_GE_GE_ONNX_PLUGIN_NATIVE_BINDINGS_BINDING_COMMON_H_
Aapi/python/ge/ge/onnx_plugin/native_bindings/module.cc+17-0
@@ -0,0 +1,17 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "onnx_plugin_bindings.h"
12+ 
13+namespace ge {
14+PYBIND11_MODULE(_ge_onnx_plugin_native, module) {
15+ python_onnx_plugin_native::BindOnnxNode(module);
16+}
17+} // namespace ge
Aapi/python/ge/ge/onnx_plugin/native_bindings/onnx_node_binding.cc+92-0
@@ -0,0 +1,92 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "onnx_plugin_bindings.h"
12+#include "proto/onnx/ge_onnx.pb.h"
13+ 
14+namespace ge {
15+namespace python_onnx_plugin_native {
16+namespace {
17+ 
18+py::object ToPythonAttribute(const ge::onnx::AttributeProto &attribute) {
19+ switch (attribute.type()) {
20+ case ge::onnx::AttributeProto_AttributeType_FLOAT:
21+ return py::float_(attribute.f());
22+ case ge::onnx::AttributeProto_AttributeType_INT:
23+ return py::int_(attribute.i());
24+ case ge::onnx::AttributeProto_AttributeType_STRING:
25+ return py::str(attribute.s());
26+ case ge::onnx::AttributeProto_AttributeType_FLOATS: {
27+ py::list values;
28+ for (int index = 0; index < attribute.floats_size(); ++index) {
29+ values.append(attribute.floats(index));
30+ }
31+ return values;
32+ }
33+ case ge::onnx::AttributeProto_AttributeType_INTS: {
34+ py::list values;
35+ for (int index = 0; index < attribute.ints_size(); ++index) {
36+ values.append(attribute.ints(index));
37+ }
38+ return values;
39+ }
40+ case ge::onnx::AttributeProto_AttributeType_STRINGS: {
41+ py::list values;
42+ for (int index = 0; index < attribute.strings_size(); ++index) {
43+ values.append(py::str(attribute.strings(index)));
44+ }
45+ return values;
46+ }
47+ default:
48+ throw py::type_error("OnnxNode attrs only supports int, float, string and homogeneous lists");
49+ }
50+}
51+ 
52+py::tuple GetNodeInputs(const ge::onnx::NodeProto &node_proto) {
53+ py::tuple result(node_proto.input_size());
54+ for (int index = 0; index < node_proto.input_size(); ++index) {
55+ result[index] = node_proto.input(index);
56+ }
57+ return result;
58+}
59+ 
60+py::tuple GetNodeOutputs(const ge::onnx::NodeProto &node_proto) {
61+ py::tuple result(node_proto.output_size());
62+ for (int index = 0; index < node_proto.output_size(); ++index) {
63+ result[index] = node_proto.output(index);
64+ }
65+ return result;
66+}
67+ 
68+py::object GetNodeAttrs(const ge::onnx::NodeProto &node_proto) {
69+ py::dict attrs;
70+ for (int index = 0; index < node_proto.attribute_size(); ++index) {
71+ const auto &attribute = node_proto.attribute(index);
72+ if (attribute.name().empty()) {
73+ throw py::value_error("OnnxNode attribute name must be a non-empty string");
74+ }
75+ attrs[py::str(attribute.name())] = ToPythonAttribute(attribute);
76+ }
77+ return py::module_::import("types").attr("MappingProxyType")(attrs);
78+}
79+ 
80+} // namespace
81+ 
82+void BindOnnxNode(py::module_ &module) {
83+ py::class_<onnx::NodeProto>(module, "OnnxNode")
84+ .def_property_readonly("name", &onnx::NodeProto::name)
85+ .def_property_readonly("origin_type", &onnx::NodeProto::op_type)
86+ .def_property_readonly("inputs", &GetNodeInputs)
87+ .def_property_readonly("outputs", &GetNodeOutputs)
88+ .def_property_readonly("attrs", &GetNodeAttrs);
89+}
90+ 
91+} // namespace python_onnx_plugin_native
92+} // namespace ge
Aapi/python/ge/ge/onnx_plugin/native_bindings/onnx_plugin_bindings.h+24-0
@@ -0,0 +1,24 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#ifndef API_PYTHON_GE_GE_ONNX_PLUGIN_NATIVE_BINDINGS_ONNX_PLUGIN_BINDINGS_H_
12+#define API_PYTHON_GE_GE_ONNX_PLUGIN_NATIVE_BINDINGS_ONNX_PLUGIN_BINDINGS_H_
13+ 
14+#include "binding_common.h"
15+ 
16+namespace ge {
17+namespace python_onnx_plugin_native {
18+ 
19+void BindOnnxNode(py::module_ &module);
GengChao
GengChaoGengChao8 天前

头文件的文件名改一下 改成onnx_puglin_bindings.h ; 当前cann仓不允许有同名文件,所有bindings.h太容易跟别的文件重名

likedislike
gentle-knight
gentle-knight
7 天前 评论:
20+ 
21+} // namespace python_onnx_plugin_native
22+} // namespace ge
23+ 
24+#endif // API_PYTHON_GE_GE_ONNX_PLUGIN_NATIVE_BINDINGS_ONNX_PLUGIN_BINDINGS_H_
Dapi/python/ge/ge/onnx_plugin/onnx_node.py+0-114
@@ -1,114 +0,0 @@
1-#!/usr/bin/env python3
2-# -*- coding: utf-8 -*-
3-# -----------------------------------------------------------------------------------------------------------
4-# Copyright (c) 2026 Huawei Technologies Co., Ltd.
5-# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
6-# CANN Open Software License Agreement Version 2.0 (the "License").
7-# Please refer to the License for details. You may not use this file except in compliance with the License.
8-# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
9-# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
10-# See LICENSE in the root of the software repository for the full text of the License.
11-# -----------------------------------------------------------------------------------------------------------
12- 
13-"""Read-only ONNX node values exposed to Python parser callbacks."""
14- 
15-from types import MappingProxyType
16-from typing import Mapping, Sequence
17- 
18-_ONNX_NODE_FACTORY_TOKEN = object()
19- 
20- 
21-class OnnxNode:
22- """Flattened ONNX source node created by the parser bridge."""
23- 
24- __slots__ = ("_attrs", "_inputs", "_name", "_origin_type", "_outputs")
25- 
26- def __init__(
27- self,
28- *,
29- name=None,
30- origin_type=None,
31- inputs=None,
32- outputs=None,
33- attrs=None,
34- token=None,
35- ) -> None:
36- if token is not _ONNX_NODE_FACTORY_TOKEN:
37- raise RuntimeError("OnnxNode objects should not be created directly.")
38- if not isinstance(name, str):
39- raise TypeError("OnnxNode name must be a string")
40- if not isinstance(origin_type, str) or not origin_type:
41- raise TypeError("OnnxNode origin_type must be a non-empty string")
42- normalized_inputs = self._normalize_names(inputs, "inputs")
43- normalized_outputs = self._normalize_names(outputs, "outputs")
44- normalized_attrs = self._normalize_attrs(attrs)
45- 
46- object.__setattr__(self, "_name", name)
47- object.__setattr__(self, "_origin_type", origin_type)
48- object.__setattr__(self, "_inputs", normalized_inputs)
49- object.__setattr__(self, "_outputs", normalized_outputs)
50- object.__setattr__(self, "_attrs", MappingProxyType(normalized_attrs))
51- 
52- def __setattr__(self, name, value) -> None:
53- raise AttributeError("OnnxNode is read-only")
54- 
55- @staticmethod
56- def _normalize_names(values: Sequence[str], field_name: str) -> tuple:
57- if isinstance(values, (str, bytes)) or not isinstance(values, Sequence):
58- raise TypeError(f"OnnxNode {field_name} must be a sequence of strings")
59- if any(not isinstance(value, str) for value in values):
60- raise TypeError(f"OnnxNode {field_name} must contain only strings")
61- return tuple(values)
62- 
63- @staticmethod
64- def _normalize_attrs(attrs: Mapping[str, object]) -> dict:
65- if not isinstance(attrs, Mapping):
66- raise TypeError("OnnxNode attrs must be a mapping")
67- normalized = {}
68- for name, value in attrs.items():
69- if not isinstance(name, str) or not name:
70- raise TypeError("OnnxNode attribute name must be a non-empty string")
71- if type(value) not in (int, float):
72- raise TypeError("OnnxNode attrs only supports int and float values")
73- normalized[name] = value
74- return normalized
75- 
76- @property
77- def name(self) -> str:
78- return self._name
79- 
80- @property
81- def origin_type(self) -> str:
82- return self._origin_type
83- 
84- @property
85- def inputs(self) -> tuple:
86- return self._inputs
87- 
88- @property
89- def outputs(self) -> tuple:
90- return self._outputs
91- 
92- @property
93- def attrs(self) -> Mapping[str, object]:
94- return self._attrs
95- 
96- 
97-def create_onnx_node(
98- *,
99- name: str,
100- origin_type: str,
101- inputs: Sequence[str],
102- outputs: Sequence[str],
103- attrs: Mapping[str, object],
104-) -> OnnxNode:
105- """Create an OnnxNode for internal bridge use."""
106- 
107- return OnnxNode(
108- name=name,
109- origin_type=origin_type,
110- inputs=inputs,
111- outputs=outputs,
112- attrs=attrs,
113- token=_ONNX_NODE_FACTORY_TOKEN,
114- )
Mapi/python/ge/ge_api_c_wrapper/CMakeLists.txt+1-0
@@ -4,6 +4,7 @@ set(GRAPH_WRAPPER_SRCS
4 "${GE_API_C_WRAPPER_DIR}/c_anyvalue.cc"4 "${GE_API_C_WRAPPER_DIR}/c_anyvalue.cc"
5 "${GE_API_C_WRAPPER_DIR}/c_graph.cc"5 "${GE_API_C_WRAPPER_DIR}/c_graph.cc"
6 "${GE_API_C_WRAPPER_DIR}/c_gnode.cc"6 "${GE_API_C_WRAPPER_DIR}/c_gnode.cc"
7+ "${GE_API_C_WRAPPER_DIR}/c_operator.cc"
7 "${GE_API_C_WRAPPER_DIR}/c_tensor.cc"8 "${GE_API_C_WRAPPER_DIR}/c_tensor.cc"
8 "${GE_API_C_WRAPPER_DIR}/c_tensor_desc.cc"9 "${GE_API_C_WRAPPER_DIR}/c_tensor_desc.cc"
9)10)
Aapi/python/ge/ge_api_c_wrapper/c_operator.cc+111-0
@@ -0,0 +1,111 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "graph/attr_value.h"
12+#include "graph/operator.h"
13+#include "common/checker.h"
14+#include "ge_api_c_wrapper_utils.h"
15+ 
16+#include <utility>
17+ 
18+using namespace ge;
19+using namespace ge::c_wrapper;
20+ 
21+namespace ge {
22+class AnyOperator : public Operator {
23+ public:
24+ explicit AnyOperator(const Operator &op) : Operator(op) {}
25+ 
26+ using Operator::InputRegister;
27+ using Operator::OptionalInputRegister;
28+ using Operator::OutputRegister;
29+ 
30+ static void RegisterInput(Operator *op, const char_t *name) {
31+ AnyOperator any_operator(*op);
32+ any_operator.InputRegister(name, "");
33+ }
34+ 
35+ static void RegisterOptionalInput(Operator *op, const char_t *name) {
36+ AnyOperator any_operator(*op);
37+ any_operator.OptionalInputRegister(name, "");
38+ }
39+ 
40+ static void RegisterOutput(Operator *op, const char_t *name) {
41+ AnyOperator any_operator(*op);
42+ any_operator.OutputRegister(name, "");
43+ }
44+};
45+} // namespace ge
46+ 
47+#ifdef __cplusplus
48+extern "C" {
49+#endif
50+ 
51+const char *GeApiWrapper_Operator_GetName(const Operator *op) {
52+ GE_ASSERT_NOTNULL(op);
53+ AscendString name;
54+ GE_ASSERT_GRAPH_SUCCESS(op->GetName(name));
55+ return AscendStringToChar(name);
56+}
57+ 
58+const char *GeApiWrapper_Operator_GetType(const Operator *op) {
59+ GE_ASSERT_NOTNULL(op);
60+ AscendString type;
61+ GE_ASSERT_GRAPH_SUCCESS(op->GetOpType(type));
62+ return AscendStringToChar(type);
63+}
64+ 
65+graphStatus GeApiWrapper_Operator_SetAttr(Operator *op, const char *key, void *attr_value) {
66+ GE_ASSERT_NOTNULL(op);
67+ GE_ASSERT_NOTNULL(key);
68+ GE_ASSERT_NOTNULL(attr_value);
69+ auto *av = static_cast<AttrValue *>(attr_value);
70+ (void)op->SetAttr(key, std::move(*av));
71+ return GRAPH_SUCCESS;
72+}
73+ 
74+graphStatus GeApiWrapper_Operator_InputRegister(Operator *op, const char *name) {
75+ GE_ASSERT_NOTNULL(op);
76+ GE_ASSERT_NOTNULL(name);
77+ AnyOperator::RegisterInput(op, name);
78+ return GRAPH_SUCCESS;
79+}
80+ 
81+graphStatus GeApiWrapper_Operator_OptionalInputRegister(Operator *op, const char *name) {
82+ GE_ASSERT_NOTNULL(op);
83+ GE_ASSERT_NOTNULL(name);
84+ AnyOperator::RegisterOptionalInput(op, name);
85+ return GRAPH_SUCCESS;
86+}
87+ 
88+graphStatus GeApiWrapper_Operator_OutputRegister(Operator *op, const char *name) {
89+ GE_ASSERT_NOTNULL(op);
90+ GE_ASSERT_NOTNULL(name);
91+ AnyOperator::RegisterOutput(op, name);
92+ return GRAPH_SUCCESS;
93+}
94+ 
95+graphStatus GeApiWrapper_Operator_DynamicInputRegister(Operator *op, const char *name, uint32_t count) {
96+ GE_ASSERT_NOTNULL(op);
97+ GE_ASSERT_NOTNULL(name);
98+ op->DynamicInputRegister(name, count, "", true);
99+ return GRAPH_SUCCESS;
100+}
101+ 
102+graphStatus GeApiWrapper_Operator_DynamicOutputRegister(Operator *op, const char *name, uint32_t count) {
103+ GE_ASSERT_NOTNULL(op);
104+ GE_ASSERT_NOTNULL(name);
105+ op->DynamicOutputRegister(name, count, "", true);
106+ return GRAPH_SUCCESS;
107+}
108+ 
109+#ifdef __cplusplus
110+}
111+#endif
Mapi/python/ge/ge_api_c_wrapper/ge_api_c_wrapper_utils.h+17-0
@@ -21,6 +21,10 @@
21#include "ge_common/ge_common_api_types.h"21#include "ge_common/ge_common_api_types.h"
22#include "ge/eager_style_graph_builder/c/esb_funcs.h"22#include "ge/eager_style_graph_builder/c/esb_funcs.h"
23 23 
24+namespace ge {
25+class Operator;
26+}
27+ 
24namespace ge {28namespace ge {
25namespace c_wrapper {29namespace c_wrapper {
26 30 
@@ -253,6 +257,19 @@ ge::GNode **GeApiWrapper_Graph_GetAllNodes(const ge::Graph *graph, size_t *node_
253ge::GNode **GeApiWrapper_Graph_GetDirectNode(const ge::Graph *graph, size_t *node_num);257ge::GNode **GeApiWrapper_Graph_GetDirectNode(const ge::Graph *graph, size_t *node_num);
254ge::graphStatus GeApiWrapper_Graph_Dump_To_Onnx(ge::Graph *graph, const char *path, const char *suffix);258ge::graphStatus GeApiWrapper_Graph_Dump_To_Onnx(ge::Graph *graph, const char *path, const char *suffix);
255ge::Graph **GeApiWrapper_Graph_GetAllSubgraphs(const ge::Graph *graph, size_t *subgraph_num);259ge::Graph **GeApiWrapper_Graph_GetAllSubgraphs(const ge::Graph *graph, size_t *subgraph_num);
260+// Operator wrappers borrow the C++ object; they never create or destroy it.
261+// GetName/GetType return an allocated string or nullptr on failure. Mutating
262+// wrappers return GRAPH_SUCCESS after argument checks and a non-zero status
263+// for invalid arguments; the underlying SetAttr/port APIs return a reference
264+// or void and therefore do not expose an additional operation status.
265+const char *GeApiWrapper_Operator_GetName(const ge::Operator *op);
266+const char *GeApiWrapper_Operator_GetType(const ge::Operator *op);
267+ge::graphStatus GeApiWrapper_Operator_SetAttr(ge::Operator *op, const char *key, void *attr_value);
268+ge::graphStatus GeApiWrapper_Operator_InputRegister(ge::Operator *op, const char *name);
269+ge::graphStatus GeApiWrapper_Operator_OptionalInputRegister(ge::Operator *op, const char *name);
270+ge::graphStatus GeApiWrapper_Operator_OutputRegister(ge::Operator *op, const char *name);
271+ge::graphStatus GeApiWrapper_Operator_DynamicInputRegister(ge::Operator *op, const char *name, uint32_t count);
272+ge::graphStatus GeApiWrapper_Operator_DynamicOutputRegister(ge::Operator *op, const char *name, uint32_t count);
256ge::Graph *GeApiWrapper_Graph_GetSubGraph(const ge::Graph *graph, const char *name);273ge::Graph *GeApiWrapper_Graph_GetSubGraph(const ge::Graph *graph, const char *name);
257ge::graphStatus GeApiWrapper_Graph_AddSubGraph(ge::Graph *graph, const ge::Graph *subgraph);274ge::graphStatus GeApiWrapper_Graph_AddSubGraph(ge::Graph *graph, const ge::Graph *subgraph);
258ge::graphStatus GeApiWrapper_Graph_RemoveSubgraph(ge::Graph *graph, const char *name);275ge::graphStatus GeApiWrapper_Graph_RemoveSubgraph(ge::Graph *graph, const char *name);
Mtests/ge/ut/ge/graph/pyge_tests/python_onnx_plugin_bootstrap_test.py+1-1
@@ -156,7 +156,7 @@ def test_canonical_package_is_imported_once_for_symlink_alias(tmp_path, monkeypa
156 156 
157 157 
158def test_shared_path_is_imported_once_across_plugin_kinds(tmp_path, monkeypatch):158def test_shared_path_is_imported_once_across_plugin_kinds(tmp_path, monkeypatch):
159- module_path = tmp_path / "mixed_plugin.py"159+ module_path = tmp_path / "ge_py_mixed_plugin.py"
160 module_path.write_text(160 module_path.write_text(
161 textwrap.dedent("""161 textwrap.dedent("""
162 from ge.custom_op import EagerExecuteOp, register_op_impl162 from ge.custom_op import EagerExecuteOp, register_op_impl
Mtests/ge/ut/ge/graph/pyge_tests/python_onnx_plugin_bridge_test.py+46-85
@@ -12,42 +12,24 @@
12 12 
13"""Contract tests for ONNX Plugin Python callback dispatch."""13"""Contract tests for ONNX Plugin Python callback dispatch."""
14 14 
15+from types import SimpleNamespace
16+ 
15import pytest17import pytest
16 18 
17import ge.graph as graph_api19import ge.graph as graph_api
18import ge.onnx_plugin as onnx_plugin_api20import ge.onnx_plugin as onnx_plugin_api
19from ge.graph import Operator21from ge.graph import Operator
20-from ge.onnx_plugin import OnnxNode, onnx_plugin22+from ge.onnx_plugin import onnx_plugin
21from ge.onnx_plugin._bridge import call_parse_node23from ge.onnx_plugin._bridge import call_parse_node
22from ge.onnx_plugin.registry import clear_registered_onnx_plugins24from ge.onnx_plugin.registry import clear_registered_onnx_plugins
25+from python_onnx_plugin_test_utils import FakeOperatorCapi
23 26 
24 27 
25-class _FakeOperatorBackend:28+@pytest.fixture
26- def __init__(self):29+def operator_capi(monkeypatch):
27- self.attrs = {}30+ capi = FakeOperatorCapi()
28- self.dynamic_inputs = []31+ capi.install(monkeypatch)
29- self.invalidated = False32+ return capi
30- 
31- @staticmethod
32- def get_name():
33- return "target"
34- 
35- @staticmethod
36- def get_type():
37- return "TargetOp"
38- 
39- @staticmethod
40- def register_dynamic_output(name, count):
41- del name, count
42- 
43- def set_attr(self, name, value):
44- self.attrs[name] = value
45- 
46- def register_dynamic_input(self, name, count):
47- self.dynamic_inputs.append((name, count))
48- 
49- def invalidate(self):
50- self.invalidated = True
51 33 
52 34 
53@pytest.fixture(autouse=True)35@pytest.fixture(autouse=True)
@@ -57,14 +39,15 @@ def clear_registry():
57 clear_registered_onnx_plugins()39 clear_registered_onnx_plugins()
58 40 
59 41 
60-def _node_values(origin_type="test.domain::1::Source"):42+def _node(origin_type="test.domain::1::Source", attrs=None):
61- return {43+ attrs = {"alpha": 0.5} if attrs is None else attrs
62- "name": "source",44+ return SimpleNamespace(
63- "origin_type": origin_type,45+ name="source",
64- "inputs": ["x0", "x1"],46+ origin_type=origin_type,
65- "outputs": ["y"],47+ inputs=("x0", "x1"),
66- "attrs": {"alpha": 0.5},48+ outputs=("y",),
67- }49+ attrs=attrs,
50+ )
68 51 
69 52 
70def test_public_exports_match_pr1_support_matrix():53def test_public_exports_match_pr1_support_matrix():
@@ -77,16 +60,13 @@ def test_unsupported_pr1_interfaces_are_not_exposed():
77 assert not hasattr(onnx_plugin_api.OnnxPlugin, name)60 assert not hasattr(onnx_plugin_api.OnnxPlugin, name)
78 for name in (61 for name in (
79 "get_attr",62 "get_attr",
80- "register_input",
81- "register_optional_input",
82- "register_output",
83 "update_input_desc",63 "update_input_desc",
84 "update_output_desc",64 "update_output_desc",
85 ):65 ):
86 assert not hasattr(Operator, name)66 assert not hasattr(Operator, name)
87 67 
88 68 
89-def test_elu_and_sum_equivalent_callbacks():69+def test_elu_and_sum_equivalent_callbacks(operator_capi):
90 elu = onnx_plugin(70 elu = onnx_plugin(
91 source="EluSource", domain="test.domain", opsets=(1,), target="EluTarget"71 source="EluSource", domain="test.domain", opsets=(1,), target="EluTarget"
92 )72 )
@@ -106,43 +86,31 @@ def test_elu_and_sum_equivalent_callbacks():
106 target.register_dynamic_input("x", count)86 target.register_dynamic_input("x", count)
107 target.set_attr("N", count)87 target.set_attr("N", count)
108 88 
109- elu_backend = _FakeOperatorBackend()
110 call_parse_node(89 call_parse_node(
111 "test.domain::1::EluSource",90 "test.domain::1::EluSource",
112- {91+ _node("test.domain::1::EluSource", attrs={}),
113- "name": "elu",92+ operator_capi.handle,
114- "origin_type": "test.domain::1::EluSource",
115- "inputs": ["x"],
116- "outputs": ["y"],
117- "attrs": {},
118- },
119- elu_backend,
120 )93 )
121- sum_backend = _FakeOperatorBackend()94+ elu_attrs = dict(operator_capi.attrs)
95+ operator_capi.attrs.clear()
96+ operator_capi.dynamic_inputs.clear()
122 call_parse_node(97 call_parse_node(
123 "test.domain::1::SumSource",98 "test.domain::1::SumSource",
124- {99+ _node("test.domain::1::SumSource", attrs={}),
125- "name": "sum",100+ operator_capi.handle,
126- "origin_type": "test.domain::1::SumSource",
127- "inputs": ["x0", "x1", "x2"],
128- "outputs": ["y"],
129- "attrs": {},
130- },
131- sum_backend,
132 )101 )
133 102 
134- assert elu_backend.attrs == {"alpha": 1.0}103+ assert elu_attrs == {"alpha": 1.0}
135- assert sum_backend.attrs == {"N": 3}104+ assert operator_capi.attrs == {"N": 2}
136- assert sum_backend.dynamic_inputs == [("x", 3)]105+ assert operator_capi.dynamic_inputs == [("x", 2)]
137- assert elu_backend.invalidated is True
138- assert sum_backend.invalidated is True
139 106 
140 107 
141-def test_call_parse_node_dispatches_objects_and_mutations():108+def test_call_parse_node_dispatches_objects_and_mutations(operator_capi):
142 plugin = onnx_plugin(109 plugin = onnx_plugin(
143 source="Source", domain="test.domain", opsets=(1,), target="TargetOp"110 source="Source", domain="test.domain", opsets=(1,), target="TargetOp"
144 )111 )
145 seen = {}112 seen = {}
113+ node = _node()
146 114 
147 @plugin.parse_node115 @plugin.parse_node
148 def parse_source(node, target):116 def parse_source(node, target):
@@ -152,34 +120,30 @@ def test_call_parse_node_dispatches_objects_and_mutations():
152 target.set_attr("N", len(node.inputs))120 target.set_attr("N", len(node.inputs))
153 target.register_dynamic_input("x", len(node.inputs))121 target.register_dynamic_input("x", len(node.inputs))
154 122 
155- backend = _FakeOperatorBackend()123+ result = call_parse_node("test.domain::1::Source", node, operator_capi.handle)
156- result = call_parse_node("test.domain::1::Source", _node_values(), backend)
157 124 
158 assert result is None125 assert result is None
159- assert isinstance(seen["node"], OnnxNode)126+ assert seen["node"] is node
160 assert isinstance(seen["target"], Operator)127 assert isinstance(seen["target"], Operator)
161 assert seen["node"].origin_type == "test.domain::1::Source"128 assert seen["node"].origin_type == "test.domain::1::Source"
162- assert backend.attrs == {"alpha": 0.5, "N": 2}129+ assert operator_capi.attrs == {"alpha": 0.5, "N": 2}
163- assert backend.dynamic_inputs == [("x", 2)]130+ assert operator_capi.dynamic_inputs == [("x", 2)]
164- assert backend.invalidated is True
165 with pytest.raises(RuntimeError, match="only valid inside parse_node"):131 with pytest.raises(RuntimeError, match="only valid inside parse_node"):
166 _ = seen["target"].name132 _ = seen["target"].name
167 133 
168 134 
169-def test_call_parse_node_rejects_unknown_origin_without_creating_operator():135+def test_call_parse_node_rejects_unknown_origin_without_creating_operator(
170- backend = _FakeOperatorBackend()136+ operator_capi,
171- 137+):
172 with pytest.raises(KeyError, match="not registered.*test.domain::1::Missing"):138 with pytest.raises(KeyError, match="not registered.*test.domain::1::Missing"):
173 call_parse_node(139 call_parse_node(
174 "test.domain::1::Missing",140 "test.domain::1::Missing",
175- _node_values("test.domain::1::Missing"),141+ _node("test.domain::1::Missing"),
176- backend,142+ operator_capi.handle,
177 )143 )
178 144 
179- assert backend.invalidated is False
180 145 
181- 146+def test_call_parse_node_invalidates_operator_when_callback_raises(operator_capi):
182-def test_call_parse_node_invalidates_operator_when_callback_raises():
183 plugin = onnx_plugin(147 plugin = onnx_plugin(
184 source="Source", domain="test.domain", opsets=(1,), target="TargetOp"148 source="Source", domain="test.domain", opsets=(1,), target="TargetOp"
185 )149 )
@@ -191,17 +155,17 @@ def test_call_parse_node_invalidates_operator_when_callback_raises():
191 seen["target"] = target155 seen["target"] = target
192 raise LookupError("callback failed")156 raise LookupError("callback failed")
193 157 
194- backend = _FakeOperatorBackend()
195 with pytest.raises(LookupError, match="callback failed"):158 with pytest.raises(LookupError, match="callback failed"):
196- call_parse_node("test.domain::1::Source", _node_values(), backend)159+ call_parse_node("test.domain::1::Source", _node(), operator_capi.handle)
197 160 
198- assert backend.invalidated is True
199 with pytest.raises(RuntimeError, match="only valid inside parse_node"):161 with pytest.raises(RuntimeError, match="only valid inside parse_node"):
200 seen["target"].set_attr("N", 1)162 seen["target"].set_attr("N", 1)
201 163 
202 164 
203@pytest.mark.parametrize("return_value", [False, 0, "", object()])165@pytest.mark.parametrize("return_value", [False, 0, "", object()])
204-def test_call_parse_node_rejects_non_none_return_and_invalidates(return_value):166+def test_call_parse_node_rejects_non_none_return_and_invalidates(
167+ return_value, operator_capi
168+):
205 plugin = onnx_plugin(169 plugin = onnx_plugin(
206 source="Source", domain="test.domain", opsets=(1,), target="TargetOp"170 source="Source", domain="test.domain", opsets=(1,), target="TargetOp"
207 )171 )
@@ -211,8 +175,5 @@ def test_call_parse_node_rejects_non_none_return_and_invalidates(return_value):
211 del node, target175 del node, target
212 return return_value176 return return_value
213 177 
214- backend = _FakeOperatorBackend()
215 with pytest.raises(TypeError, match="must return None"):178 with pytest.raises(TypeError, match="must return None"):
216- call_parse_node("test.domain::1::Source", _node_values(), backend)179+ call_parse_node("test.domain::1::Source", _node(), operator_capi.handle)
217- 
218- assert backend.invalidated is True
Mtests/ge/ut/ge/graph/pyge_tests/python_onnx_plugin_objects_test.py+101-85
@@ -13,125 +13,141 @@
13"""Contract tests for ONNX Plugin Python source and target objects."""13"""Contract tests for ONNX Plugin Python source and target objects."""
14 14 
15import copy15import copy
16+import ctypes
16 17 
17import pytest18import pytest
18 19 
19from ge.graph import Operator20from ge.graph import Operator
21+from ge.graph._attr import _AttrValue
22+from ge.graph.operator import graph_lib
20from ge.graph.operator import create_operator23from ge.graph.operator import create_operator
21from ge.onnx_plugin import OnnxNode24from ge.onnx_plugin import OnnxNode
22-from ge.onnx_plugin.onnx_node import create_onnx_node25+from ge.onnx_plugin import _native
26+from python_onnx_plugin_test_utils import FakeOperatorCapi
23 27 
24 28 
25-class _FakeOperatorBackend:29+@pytest.fixture
26- def __init__(self):30+def operator_capi(monkeypatch):
27- self.attrs = {}31+ capi = FakeOperatorCapi()
28- self.dynamic_inputs = []32+ capi.install(monkeypatch)
29- self.dynamic_outputs = []33+ return capi
30- self.invalidated = False
31- 
32- @staticmethod
33- def get_name():
34- return "target_node"
35- 
36- @staticmethod
37- def get_type():
38- return "TargetOp"
39- 
40- def set_attr(self, name, value):
41- self.attrs[name] = value
42- 
43- def register_dynamic_input(self, name, count):
44- self.dynamic_inputs.append((name, count))
45- 
46- def register_dynamic_output(self, name, count):
47- self.dynamic_outputs.append((name, count))
48- 
49- def invalidate(self):
50- self.invalidated = True
51 34 
52 35 
53-def test_onnx_node_exposes_immutable_flattened_values():36+def test_onnx_node_is_only_created_by_native_bridge():
54- attrs = {"alpha": 1.0, "axis": 1}37+ assert not hasattr(_native, "create_onnx_node")
55- node = create_onnx_node(
56- name="elu",
57- origin_type="ai.onnx::13::Elu",
58- inputs=["x", ""],
59- outputs=["y"],
60- attrs=attrs,
61- )
62- attrs["alpha"] = 2.0
63- 
64- assert node.name == "elu"
65- assert node.origin_type == "ai.onnx::13::Elu"
66- assert node.inputs == ("x", "")
67- assert node.outputs == ("y",)
68- assert dict(node.attrs) == {"alpha": 1.0, "axis": 1}
69- 
70- with pytest.raises(AttributeError, match="read-only"):
71- node.name = "changed"
72 with pytest.raises(TypeError):38 with pytest.raises(TypeError):
73- node.attrs["alpha"] = 3.0
74- 
75- 
76-def test_onnx_node_cannot_be_created_by_plugin_author():
77- with pytest.raises(RuntimeError, match="should not be created directly"):
78 OnnxNode()39 OnnxNode()
79 40 
80 41 
81-@pytest.mark.parametrize("value", [True, "value", [1], None])42+def test_operator_mutates_borrowed_handle(operator_capi):
82-def test_onnx_node_rejects_unsupported_attribute_values(value):43+ target = create_operator(operator_capi.handle)
83- with pytest.raises(TypeError, match="only supports int and float"):
84- create_onnx_node(
85- name="node",
86- origin_type="test.domain::1::Source",
87- inputs=[],
88- outputs=[],
89- attrs={"value": value},
90- )
91- 
92- 
93-def test_operator_mutates_callback_backend():
94- backend = _FakeOperatorBackend()
95- target = create_operator(backend)
96 44 
97 assert target.name == "target_node"45 assert target.name == "target_node"
98 assert target.type == "TargetOp"46 assert target.type == "TargetOp"
99 47 
100 target.set_attr("alpha", 1.0)48 target.set_attr("alpha", 1.0)
101 target.set_attr("N", 2)49 target.set_attr("N", 2)
50+ target.set_attr("mode", "nearest")
51+ target.set_attr("axes", [1, 2])
52+ target.register_input("x")
53+ target.register_optional_input("bias")
54+ target.register_output("y")
102 target.register_dynamic_input("x", 2)55 target.register_dynamic_input("x", 2)
103 target.register_dynamic_output("y", 1)56 target.register_dynamic_output("y", 1)
104 57 
105- assert backend.attrs == {"alpha": 1.0, "N": 2}58+ assert operator_capi.attrs == {
106- assert backend.dynamic_inputs == [("x", 2)]59+ "alpha": 1.0,
107- assert backend.dynamic_outputs == [("y", 1)]60+ "N": 2,
61+ "mode": "nearest",
62+ "axes": [1, 2],
63+ }
64+ assert operator_capi.inputs == ["x"]
65+ assert operator_capi.optional_inputs == ["bias"]
66+ assert operator_capi.outputs == ["y"]
67+ assert operator_capi.dynamic_inputs == [("x", 2)]
68+ assert operator_capi.dynamic_outputs == [("y", 1)]
69+ 
70+ 
71+def test_operator_uses_borrowed_ctypes_handle(monkeypatch):
72+ calls = []
73+ buffers = []
74+ 
75+ def string_value(value):
76+ buffer = ctypes.create_string_buffer(value)
77+ buffers.append(buffer)
78+ return ctypes.cast(buffer, ctypes.POINTER(ctypes.c_char))
79+ 
80+ monkeypatch.setattr(
81+ graph_lib,
82+ "GeApiWrapper_Operator_GetName",
83+ lambda handle: string_value(b"target"),
84+ )
85+ monkeypatch.setattr(
atomgit-bot
atomgit-botatomgit-bot8 天前

🟠 High Priority

test_operator_uses_borrowed_ctypes_handle 在第 155 行把 GeApiWrapper_AttrValue_Create 打成返回假指针 ctypes.c_void_p(1)(该打桩有效,因为 _AttrValue.__init__ 是在调用时动态访问 graph_lib.GeApiWrapper_AttrValue_Create),第 157 行又试图打桩 GeApiWrapper_AttrValue_SetListInt。但 ge.graph._attr 在模块导入时通过 set_list_int = _create_list_setter(ctypes.c_int64, graph_lib.GeApiWrapper_AttrValue_SetListInt, int, "integers")原始 _FuncPtr 捕获进了闭包(见 _attr.py 第 504 行与 _create_list_setter 第 97-118 行),因此对 graph_lib.GeApiWrapper_AttrValue_SetListInt 属性的替换不会影响 _AttrValue.set_list_int。→ 第 179 行 target.set_attr("axes", [1, 2]) 会走 operator.pyattr_value.set_value([1,2]) → set_list_int → setter_func(self._av_ptr, arr, 2),用假指针 c_void_p(1) 调用真实的 GeApiWrapper_AttrValue_SetListInt,几乎必然触发段错误(解引用地址 0x1),至少也意味着该测试并未真正隔离 C 调用,打桩形同虚设。建议直接打桩 _AttrValue.set_list_int(或 set_value)而不是 graph_lib 上的属性。

建议:将第 157 行对 graph_lib.GeApiWrapper_AttrValue_SetListInt 的 monkeypatch 改为对 _AttrValue.set_list_int 打桩(该闭包在导入时已捕获原始 _FuncPtr,直接替换 graph_lib 属性无效),例如:

likedislike
gentle-knight
gentle-knight
8 天前 评论:
gentle-knight
gentle-knight
7 天前 评论:
86+ graph_lib,
87+ "GeApiWrapper_Operator_GetType",
88+ lambda handle: string_value(b"TargetOp"),
89+ )
90+ monkeypatch.setattr(graph_lib, "GeApiWrapper_FreeString", lambda value: None)
91+ monkeypatch.setattr(
92+ graph_lib, "GeApiWrapper_AttrValue_Create", lambda: ctypes.c_void_p(1)
93+ )
94+ monkeypatch.setattr(graph_lib, "GeApiWrapper_AttrValue_Destroy", lambda value: None)
95+ monkeypatch.setattr(_AttrValue, "set_list_int", lambda self, value: True)
96+ 
97+ def record(name):
98+ def call(*args):
99+ calls.append(name)
100+ return 0
101+ 
102+ return call
103+ 
104+ for name in (
105+ "GeApiWrapper_Operator_SetAttr",
106+ "GeApiWrapper_Operator_InputRegister",
107+ "GeApiWrapper_Operator_OptionalInputRegister",
108+ "GeApiWrapper_Operator_OutputRegister",
109+ "GeApiWrapper_Operator_DynamicInputRegister",
110+ "GeApiWrapper_Operator_DynamicOutputRegister",
111+ ):
112+ monkeypatch.setattr(graph_lib, name, record(name))
113+ 
114+ with create_operator(ctypes.c_void_p(0x123)) as target:
115+ assert target.name == "target"
116+ assert target.type == "TargetOp"
117+ target.set_attr("axes", [1, 2])
118+ target.register_input("x")
119+ target.register_optional_input("bias")
120+ target.register_output("y")
121+ target.register_dynamic_input("args", 2)
122+ target.register_dynamic_output("outs", 1)
123+ 
124+ assert calls == [
125+ "GeApiWrapper_Operator_SetAttr",
126+ "GeApiWrapper_Operator_InputRegister",
127+ "GeApiWrapper_Operator_OptionalInputRegister",
128+ "GeApiWrapper_Operator_OutputRegister",
129+ "GeApiWrapper_Operator_DynamicInputRegister",
130+ "GeApiWrapper_Operator_DynamicOutputRegister",
131+ ]
132+ with pytest.raises(RuntimeError, match="only valid inside parse_node"):
133+ _ = target.name
108 134 
109 135 
110def test_operator_cannot_be_created_or_copied_by_plugin_author():136def test_operator_cannot_be_created_or_copied_by_plugin_author():
111 with pytest.raises(RuntimeError, match="should not be created directly"):137 with pytest.raises(RuntimeError, match="should not be created directly"):
112 Operator()138 Operator()
113 139 
114- target = create_operator(_FakeOperatorBackend())140+ target = create_operator(ctypes.c_void_p(0x123))
115 with pytest.raises(RuntimeError, match="does not support copy"):141 with pytest.raises(RuntimeError, match="does not support copy"):
116 copy.copy(target)142 copy.copy(target)
117 with pytest.raises(RuntimeError, match="does not support deepcopy"):143 with pytest.raises(RuntimeError, match="does not support deepcopy"):
118 copy.deepcopy(target)144 copy.deepcopy(target)
119 145 
120 146 
121-@pytest.mark.parametrize("value", [True, "value", [1], None])147+@pytest.mark.parametrize("handle", [None, 0, ctypes.c_void_p()])
122-def test_operator_rejects_unsupported_attribute_values(value):148+def test_operator_rejects_null_borrowed_handle(handle):
123- target = create_operator(_FakeOperatorBackend())149+ with pytest.raises(ValueError, match="handle cannot be (None|null)"):
124- 150+ create_operator(handle)
125- with pytest.raises(TypeError, match="only supports int and float"):
126- target.set_attr("value", value)
127- 
128- 
129-@pytest.mark.parametrize("value", [-(1 << 63) - 1, 1 << 63])
130-def test_operator_rejects_integer_attribute_outside_int64(value):
131- target = create_operator(_FakeOperatorBackend())
132- 
133- with pytest.raises(ValueError, match="int64 range"):
134- target.set_attr("value", value)
135 151 
136 152 
137@pytest.mark.parametrize(153@pytest.mark.parametrize(
@@ -144,7 +160,7 @@ def test_operator_rejects_integer_attribute_outside_int64(value):
144 ],160 ],
145)161)
146def test_operator_validates_dynamic_port_count(count, exception):162def test_operator_validates_dynamic_port_count(count, exception):
147- target = create_operator(_FakeOperatorBackend())163+ target = create_operator(ctypes.c_void_p(0x123))
148 164 
149 with pytest.raises(exception, match="count"):165 with pytest.raises(exception, match="count"):
150 target.register_dynamic_input("x", count)166 target.register_dynamic_input("x", count)
Atests/ge/ut/ge/graph/pyge_tests/python_onnx_plugin_test_utils.py+114-0
@@ -0,0 +1,114 @@
1+#!/usr/bin/env python3
2+# -*- coding: utf-8 -*-
3+# -----------------------------------------------------------------------------------------------------------
4+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
5+# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
6+# CANN Open Software License Agreement Version 2.0 (the "License").
7+# Please refer to the License for details. You may not use this file except in compliance with the License.
8+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
9+# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
10+# See LICENSE in the root of the software repository for the full text of the License.
11+# -----------------------------------------------------------------------------------------------------------
12+ 
13+"""Small ctypes boundary stub shared by ONNX plugin Python tests."""
14+ 
15+import ctypes
16+ 
17+from ge.graph._attr import _AttrValue
18+from ge.graph.operator import graph_lib
19+ 
20+ 
21+class FakeOperatorCapi:
22+ def __init__(self):
23+ self.handle = ctypes.c_void_p(0x123)
24+ self.attrs = {}
25+ self.inputs = []
26+ self.optional_inputs = []
27+ self.outputs = []
28+ self.dynamic_inputs = []
29+ self.dynamic_outputs = []
30+ self._attr_values = {}
31+ self._next_attr = 1
32+ self._buffers = []
33+ 
34+ def install(self, monkeypatch):
35+ monkeypatch.setattr(
36+ graph_lib,
37+ "GeApiWrapper_Operator_GetName",
38+ lambda handle: self._string(b"target_node"),
39+ )
40+ monkeypatch.setattr(
41+ graph_lib,
42+ "GeApiWrapper_Operator_GetType",
43+ lambda handle: self._string(b"TargetOp"),
44+ )
45+ monkeypatch.setattr(graph_lib, "GeApiWrapper_FreeString", lambda value: None)
46+ monkeypatch.setattr(
47+ graph_lib, "GeApiWrapper_AttrValue_Create", self._create_attr
48+ )
49+ monkeypatch.setattr(
50+ graph_lib, "GeApiWrapper_AttrValue_Destroy", lambda value: None
51+ )
52+ 
53+ def set_attr_value(attr_value, value):
54+ self._attr_values[attr_value._av_ptr.value] = value
55+ 
56+ monkeypatch.setattr(_AttrValue, "set_value", set_attr_value)
57+ monkeypatch.setattr(
58+ graph_lib, "GeApiWrapper_Operator_SetAttr", self._set_operator_attr
59+ )
60+ monkeypatch.setattr(
61+ graph_lib, "GeApiWrapper_Operator_InputRegister", self._register_input
62+ )
63+ monkeypatch.setattr(
64+ graph_lib,
65+ "GeApiWrapper_Operator_OptionalInputRegister",
66+ self._register_optional_input,
67+ )
68+ monkeypatch.setattr(
69+ graph_lib, "GeApiWrapper_Operator_OutputRegister", self._register_output
70+ )
71+ monkeypatch.setattr(
72+ graph_lib,
73+ "GeApiWrapper_Operator_DynamicInputRegister",
74+ self._register_dynamic_input,
75+ )
76+ monkeypatch.setattr(
77+ graph_lib,
78+ "GeApiWrapper_Operator_DynamicOutputRegister",
79+ self._register_dynamic_output,
80+ )
81+ 
82+ def _string(self, value):
83+ buffer = ctypes.create_string_buffer(value)
84+ self._buffers.append(buffer)
85+ return ctypes.cast(buffer, ctypes.POINTER(ctypes.c_char))
86+ 
87+ def _create_attr(self):
88+ value = ctypes.c_void_p(self._next_attr)
89+ self._next_attr += 1
90+ return value
91+ 
92+ def _set_operator_attr(self, handle, name, attr_value):
93+ self.attrs[name.decode("utf-8")] = self._attr_values[attr_value.value]
94+ return 0
95+ 
96+ def _register_input(self, handle, name):
97+ self.inputs.append(name.decode("utf-8"))
98+ return 0
99+ 
100+ def _register_optional_input(self, handle, name):
101+ self.optional_inputs.append(name.decode("utf-8"))
102+ return 0
103+ 
104+ def _register_output(self, handle, name):
105+ self.outputs.append(name.decode("utf-8"))
106+ return 0
107+ 
108+ def _register_dynamic_input(self, handle, name, count):
109+ self.dynamic_inputs.append((name.decode("utf-8"), count.value))
110+ return 0
111+ 
112+ def _register_dynamic_output(self, handle, name, count):
113+ self.dynamic_outputs.append((name.decode("utf-8"), count.value))
114+ return 0