| @@ -12,6 +12,7 @@ add_subdirectory(ge_api_c_wrapper) | |||
| 12 | add_subdirectory(ge/passes) | 12 | add_subdirectory(ge/passes) |
| 13 | add_subdirectory(ge/runtime) | 13 | add_subdirectory(ge/runtime) |
| 14 | add_subdirectory(ge/custom_op) | 14 | add_subdirectory(ge/custom_op) |
| 15 | +add_subdirectory(ge/onnx_plugin) | ||
| 15 | file(GLOB_RECURSE GE_API_C_WRAPPER CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.py") | 16 | file(GLOB_RECURSE GE_API_C_WRAPPER CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.py") |
| 16 | 17 | ||
| 17 | execute_process( | 18 | execute_process( |
| @@ -184,6 +185,8 @@ add_custom_command( | |||
| 184 | && ${CMAKE_COMMAND} -E remove_directory ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/runtime/python_runtime_artifacts | 185 | && ${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.txt | 186 | && ${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_bindings | 187 | && ${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.json | 191 | && 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.so | 192 | && 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.json | 194 | && 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.so | 195 | && 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.so | 196 | && 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_codegen | 198 | && ${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_wrapper | 209 | DEPENDS eager_style_graph_builder_base ge_api_c_wrapper graph_wrapper |
| 206 | - _ge_runtime_native _ge_custom_op_native ge_python_custom_op_bridge | 210 | + _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} |
| @@ -377,6 +377,39 @@ graph_lib.GeApiWrapper_GNode_GetOutputDesc.argtypes = [c_void_p, c_int32] | |||
| 377 | graph_lib.GeApiWrapper_GNode_UpdateOutputDesc.restype = c_int | 377 | graph_lib.GeApiWrapper_GNode_UpdateOutputDesc.restype = c_int |
| 378 | graph_lib.GeApiWrapper_GNode_UpdateOutputDesc.argtypes = [c_void_p, c_int32, c_void_p] | 378 | graph_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 ============ |
| 382 | graph_lib.GeApiWrapper_TensorDesc_Create.restype = c_void_p | 415 | graph_lib.GeApiWrapper_TensorDesc_Create.restype = c_void_p |
| @@ -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 | ||
| 18 | class Operator: | 26 | class 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 = handle | 45 | self._handle = handle |
| 29 | self._valid = True | 46 | self._valid = True |
| 30 | 47 | ||
| @@ -41,7 +58,7 @@ class Operator: | |||
| 41 | if not self._valid: | 58 | if not self._valid: |
| 42 | return | 59 | return |
| 43 | self._valid = False | 60 | self._valid = False |
| 44 | - self._handle.invalidate() | 61 | + self._handle = ctypes.c_void_p() |
| 45 | 62 | ||
| 46 | 63 | ||
| 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 | 68 | ||
| 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 | 73 | ||
| 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: |
| @@ -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 | +) | ||
| @@ -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 OnnxNode | 15 | +from ._native import OnnxNode |
| 16 | from .plugin import OnnxPlugin, onnx_plugin | 16 | from .plugin import OnnxPlugin, onnx_plugin |
| 17 | 17 | ||
| 18 | __all__ = ["OnnxNode", "OnnxPlugin", "onnx_plugin"] | 18 | __all__ = ["OnnxNode", "OnnxPlugin", "onnx_plugin"] |
| @@ -15,7 +15,7 @@ | |||
| 15 | from ge.graph.operator import create_operator | 15 | from ge.graph.operator import create_operator |
| 16 | 16 | ||
| 17 | from .bootstrap import load_onnx_plugins | 17 | from .bootstrap import load_onnx_plugins |
| 18 | -from .onnx_node import create_onnx_node | 18 | +from ._native import OnnxNode |
| 19 | from .registry import ( | 19 | from .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") |
| @@ -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 | ||
| @@ -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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 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 | + | ||
| @@ -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 | + | ||
| 12 | + | ||
| 13 | +namespace ge { | ||
| 14 | +PYBIND11_MODULE(_ge_onnx_plugin_native, module) { | ||
| 15 | + python_onnx_plugin_native::BindOnnxNode(module); | ||
| 16 | +} | ||
| 17 | +} // namespace ge | ||
| @@ -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 | + | ||
| 12 | + | ||
| 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 | ||
| @@ -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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +namespace ge { | ||
| 17 | +namespace python_onnx_plugin_native { | ||
| 18 | + | ||
| 19 | +void BindOnnxNode(py::module_ &module); | ||
| 20 | + | ||
| 21 | +} // namespace python_onnx_plugin_native | ||
| 22 | +} // namespace ge | ||
| 23 | + | ||
| 24 | + | ||
| @@ -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 | - | ||
| 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 | - | ||
| 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 | - | ||
| 77 | - def name(self) -> str: | ||
| 78 | - return self._name | ||
| 79 | - | ||
| 80 | - | ||
| 81 | - def origin_type(self) -> str: | ||
| 82 | - return self._origin_type | ||
| 83 | - | ||
| 84 | - | ||
| 85 | - def inputs(self) -> tuple: | ||
| 86 | - return self._inputs | ||
| 87 | - | ||
| 88 | - | ||
| 89 | - def outputs(self) -> tuple: | ||
| 90 | - return self._outputs | ||
| 91 | - | ||
| 92 | - | ||
| 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 | - ) | ||
| @@ -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 | ) |
| @@ -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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 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 | + | ||
| 48 | +extern "C" { | ||
| 49 | + | ||
| 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 | + | ||
| 110 | +} | ||
| 111 | + | ||
| @@ -21,6 +21,10 @@ | |||
| 21 | 21 | ||
| 22 | 22 | ||
| 23 | 23 | ||
| 24 | +namespace ge { | ||
| 25 | +class Operator; | ||
| 26 | +} | ||
| 27 | + | ||
| 24 | namespace ge { | 28 | namespace ge { |
| 25 | namespace c_wrapper { | 29 | namespace c_wrapper { |
| 26 | 30 | ||
| @@ -253,6 +257,19 @@ ge::GNode **GeApiWrapper_Graph_GetAllNodes(const ge::Graph *graph, size_t *node_ | |||
| 253 | ge::GNode **GeApiWrapper_Graph_GetDirectNode(const ge::Graph *graph, size_t *node_num); | 257 | ge::GNode **GeApiWrapper_Graph_GetDirectNode(const ge::Graph *graph, size_t *node_num); |
| 254 | ge::graphStatus GeApiWrapper_Graph_Dump_To_Onnx(ge::Graph *graph, const char *path, const char *suffix); | 258 | ge::graphStatus GeApiWrapper_Graph_Dump_To_Onnx(ge::Graph *graph, const char *path, const char *suffix); |
| 255 | ge::Graph **GeApiWrapper_Graph_GetAllSubgraphs(const ge::Graph *graph, size_t *subgraph_num); | 259 | ge::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); | ||
| 256 | ge::Graph *GeApiWrapper_Graph_GetSubGraph(const ge::Graph *graph, const char *name); | 273 | ge::Graph *GeApiWrapper_Graph_GetSubGraph(const ge::Graph *graph, const char *name); |
| 257 | ge::graphStatus GeApiWrapper_Graph_AddSubGraph(ge::Graph *graph, const ge::Graph *subgraph); | 274 | ge::graphStatus GeApiWrapper_Graph_AddSubGraph(ge::Graph *graph, const ge::Graph *subgraph); |
| 258 | ge::graphStatus GeApiWrapper_Graph_RemoveSubgraph(ge::Graph *graph, const char *name); | 275 | ge::graphStatus GeApiWrapper_Graph_RemoveSubgraph(ge::Graph *graph, const char *name); |
| @@ -156,7 +156,7 @@ def test_canonical_package_is_imported_once_for_symlink_alias(tmp_path, monkeypa | |||
| 156 | 156 | ||
| 157 | 157 | ||
| 158 | def test_shared_path_is_imported_once_across_plugin_kinds(tmp_path, monkeypatch): | 158 | def 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_impl | 162 | from ge.custom_op import EagerExecuteOp, register_op_impl |
| @@ -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 | + | ||
| 15 | import pytest | 17 | import pytest |
| 16 | 18 | ||
| 17 | import ge.graph as graph_api | 19 | import ge.graph as graph_api |
| 18 | import ge.onnx_plugin as onnx_plugin_api | 20 | import ge.onnx_plugin as onnx_plugin_api |
| 19 | from ge.graph import Operator | 21 | from ge.graph import Operator |
| 20 | -from ge.onnx_plugin import OnnxNode, onnx_plugin | 22 | +from ge.onnx_plugin import onnx_plugin |
| 21 | from ge.onnx_plugin._bridge import call_parse_node | 23 | from ge.onnx_plugin._bridge import call_parse_node |
| 22 | from ge.onnx_plugin.registry import clear_registered_onnx_plugins | 24 | from 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 = False | 32 | + return capi |
| 30 | - | ||
| 31 | - | ||
| 32 | - def get_name(): | ||
| 33 | - return "target" | ||
| 34 | - | ||
| 35 | - | ||
| 36 | - def get_type(): | ||
| 37 | - return "TargetOp" | ||
| 38 | - | ||
| 39 | - | ||
| 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 | 35 | ||
| @@ -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 | ||
| 70 | def test_public_exports_match_pr1_support_matrix(): | 53 | def 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 | 115 | ||
| 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 None | 125 | 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"].name | 132 | _ = 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"] = target | 155 | 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 | 165 | ||
| 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, target | 175 | del node, target |
| 212 | return return_value | 176 | 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 | ||
| @@ -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 | ||
| 15 | import copy | 15 | import copy |
| 16 | +import ctypes | ||
| 16 | 17 | ||
| 17 | import pytest | 18 | import pytest |
| 18 | 19 | ||
| 19 | from ge.graph import Operator | 20 | from ge.graph import Operator |
| 21 | +from ge.graph._attr import _AttrValue | ||
| 22 | +from ge.graph.operator import graph_lib | ||
| 20 | from ge.graph.operator import create_operator | 23 | from ge.graph.operator import create_operator |
| 21 | from ge.onnx_plugin import OnnxNode | 24 | from ge.onnx_plugin import OnnxNode |
| 22 | -from ge.onnx_plugin.onnx_node import create_onnx_node | 25 | +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 | - | ||
| 33 | - def get_name(): | ||
| 34 | - return "target_node" | ||
| 35 | - | ||
| 36 | - | ||
| 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( | ||
🟠 High Priority
建议:将第 157 行对 ![]() ![]() gentle-knight 8 天前 评论: 8 天前 评论: | |||
| 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 | ||
| 110 | def test_operator_cannot_be_created_or_copied_by_plugin_author(): | 136 | def 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 | - | ||
| 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 | 153 | ||
| @@ -144,7 +160,7 @@ def test_operator_rejects_integer_attribute_outside_int64(value): | |||
| 144 | ], | 160 | ], |
| 145 | ) | 161 | ) |
| 146 | def test_operator_validates_dynamic_port_count(count, exception): | 162 | def 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) |
| @@ -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 | ||


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