已合并
feat: 接入ONNX Plugin Python bridge到真实ParseParamsFn #4455
feat: 接入ONNX Plugin Python bridge到真实ParseParamsFn #4455
已合并
gentle-knight创建于 16 天前
28 个文件变更+1199-95
@@ -379,6 +379,7 @@ if ((CMAKE_BUILD_TYPE MATCHES GCOV) OR ENABLE_GE_DT)
379 ut_libge_symbol_infer_utest379 ut_libge_symbol_infer_utest
380 ut_libge_others_utest380 ut_libge_others_utest
381 ut_fusion_pass_executor_utest381 ut_fusion_pass_executor_utest
382+ ut_onnx_plugin_bridge_utest
382 ut_libge_distinct_load_utest383 ut_libge_distinct_load_utest
383 ut_libge_kernel_utest384 ut_libge_kernel_utest
384 ut_libge_label_maker_utest385 ut_libge_label_maker_utest
@@ -55,6 +55,7 @@
55#include "register/optimization_option_registry.h"55#include "register/optimization_option_registry.h"
56#include "register/amct_registry.h"56#include "register/amct_registry.h"
57#include "runtime/custom_op/custom_op_loader.h"57#include "runtime/custom_op/custom_op_loader.h"
58+#include "parser/parser/onnx/python_onnx_plugin_bridge/onnx_plugin_bridge_loader.h"
58 59 
59namespace {60namespace {
60using json = nlohmann::json;61using json = nlohmann::json;
@@ -2367,6 +2368,7 @@ int32_t main_impl(int32_t argc, char *argv[]) {
2367 GE_MAKE_GUARD(release_python_resources, []() {2368 GE_MAKE_GUARD(release_python_resources, []() {
2368 (void)ge::fusion::UnloadPassPlugins();2369 (void)ge::fusion::UnloadPassPlugins();
2369 (void)ge::custom_op::UnloadCustomOps();2370 (void)ge::custom_op::UnloadCustomOps();
2371+ ge::UnloadOnnxPythonPluginBridge();
2370 (void)GePythonRuntimeManager::Instance().ShutdownProcess();2372 (void)GePythonRuntimeManager::Instance().ShutdownProcess();
2371 });2373 });
2372 2374 
@@ -36,9 +36,11 @@ set(GE_PYTHON_PASS_ARTIFACT_DIR
36set(GE_PYTHON_PASS_BRIDGE_ABI_VERSION 1)36set(GE_PYTHON_PASS_BRIDGE_ABI_VERSION 1)
37set(GE_PYTHON_RUNTIME_NATIVE_ABI_VERSION 1)37set(GE_PYTHON_RUNTIME_NATIVE_ABI_VERSION 1)
38set(GE_PYTHON_CUSTOM_OP_BRIDGE_ABI_VERSION 1)38set(GE_PYTHON_CUSTOM_OP_BRIDGE_ABI_VERSION 1)
39+set(GE_PYTHON_ONNX_PLUGIN_BRIDGE_ABI_VERSION 1)
39set(GE_PYTHON_PASS_ARTIFACT_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/python_pass_artifact_manifest.json)40set(GE_PYTHON_PASS_ARTIFACT_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/python_pass_artifact_manifest.json)
40set(GE_PYTHON_RUNTIME_ARTIFACT_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/python_runtime_artifact_manifest.json)41set(GE_PYTHON_RUNTIME_ARTIFACT_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/python_runtime_artifact_manifest.json)
41set(GE_PYTHON_CUSTOM_OP_ARTIFACT_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/python_custom_op_artifact_manifest.json)42set(GE_PYTHON_CUSTOM_OP_ARTIFACT_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/python_custom_op_artifact_manifest.json)
43+set(GE_PYTHON_ONNX_PLUGIN_ARTIFACT_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/python_onnx_plugin_artifact_manifest.json)
42set(GE_PYTHON_PASS_NATIVE_WHEEL44set(GE_PYTHON_PASS_NATIVE_WHEEL
43 ${CMAKE_CURRENT_BINARY_DIR}/ge_py_pass_bridge-0.0.1-${GE_PYTHON_ARTIFACT_PYTHON_TAG}-${GE_PYTHON_ARTIFACT_PYTHON_TAG}-${GE_PYTHON_PASS_WHEEL_PLATFORM}.whl)45 ${CMAKE_CURRENT_BINARY_DIR}/ge_py_pass_bridge-0.0.1-${GE_PYTHON_ARTIFACT_PYTHON_TAG}-${GE_PYTHON_ARTIFACT_PYTHON_TAG}-${GE_PYTHON_PASS_WHEEL_PLATFORM}.whl)
44set(GE_PYTHON_PASS_NATIVE_WHEEL ${GE_PYTHON_PASS_NATIVE_WHEEL} CACHE INTERNAL "GE python pass native artifact wheel")46set(GE_PYTHON_PASS_NATIVE_WHEEL ${GE_PYTHON_PASS_NATIVE_WHEEL} CACHE INTERNAL "GE python pass native artifact wheel")
@@ -108,6 +110,18 @@ file(WRITE ${GE_PYTHON_CUSTOM_OP_ARTIFACT_MANIFEST}
108 " }\n"110 " }\n"
109 "}\n"111 "}\n"
110)112)
113+file(WRITE ${GE_PYTHON_ONNX_PLUGIN_ARTIFACT_MANIFEST}
114+ "{\n"
115+ " \"python_tag\": \"${GE_PYTHON_ARTIFACT_PYTHON_TAG}\",\n"
116+ " \"python_version\": \"${GE_PYTHON_ARTIFACT_PYTHON_VERSION}\",\n"
117+ " \"platform\": \"${GE_PYTHON_ARTIFACT_PLATFORM_TAG}\",\n"
118+ " \"bridge_abi\": ${GE_PYTHON_ONNX_PLUGIN_BRIDGE_ABI_VERSION},\n"
119+ " \"artifacts\": {\n"
120+ " \"bridge\": \"libge_python_onnx_plugin_bridge.so\",\n"
121+ " \"native\": \"_ge_onnx_plugin_native.so\"\n"
122+ " }\n"
123+ "}\n"
124+)
111set(GE_PYTHON_PASS_FALLBACK_CODEGEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/python_pass_fallback_codegen)125set(GE_PYTHON_PASS_FALLBACK_CODEGEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/python_pass_fallback_codegen)
112set(GE_PYTHON_PASS_FALLBACK_CODEGEN_CONFIG126set(GE_PYTHON_PASS_FALLBACK_CODEGEN_CONFIG
113 ${GE_PYTHON_PASS_FALLBACK_CODEGEN_DIR}/build_config.json)127 ${GE_PYTHON_PASS_FALLBACK_CODEGEN_DIR}/build_config.json)
@@ -187,6 +201,7 @@ add_custom_command(
187 && ${CMAKE_COMMAND} -E remove_directory ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/custom_op/native_bindings201 && ${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.txt202 && ${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_bindings203 && ${CMAKE_COMMAND} -E remove_directory ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/onnx_plugin/native_bindings
204+ && ${CMAKE_COMMAND} -E remove ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/onnx_plugin/_ge_onnx_plugin_native.so
190 && mkdir -p ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/runtime/python_runtime_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}205 && mkdir -p ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/runtime/python_runtime_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}
191 && cp ${GE_PYTHON_RUNTIME_ARTIFACT_MANIFEST} ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/runtime/python_runtime_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/manifest.json206 && cp ${GE_PYTHON_RUNTIME_ARTIFACT_MANIFEST} ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/runtime/python_runtime_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/manifest.json
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.so207 && 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
@@ -194,7 +209,10 @@ add_custom_command(
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.json209 && 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
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.so210 && 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
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.so211 && 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.so212+ && mkdir -p ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/onnx_plugin/python_onnx_plugin_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}
213+ && cp ${GE_PYTHON_ONNX_PLUGIN_ARTIFACT_MANIFEST} ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/onnx_plugin/python_onnx_plugin_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/manifest.json
214+ && cp $<TARGET_FILE:ge_python_onnx_plugin_bridge> ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/onnx_plugin/python_onnx_plugin_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/libge_python_onnx_plugin_bridge.so
215+ && cp $<TARGET_FILE:_ge_onnx_plugin_native> ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/onnx_plugin/python_onnx_plugin_artifacts/${GE_PYTHON_ARTIFACT_SET_NAME}/_ge_onnx_plugin_native.so
198 && ${CMAKE_COMMAND} -E copy_directory ${GE_PYTHON_PASS_FALLBACK_CODEGEN_DIR} ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/passes/fallback_codegen216 && ${CMAKE_COMMAND} -E copy_directory ${GE_PYTHON_PASS_FALLBACK_CODEGEN_DIR} ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/passes/fallback_codegen
199 && find ${GE_PYTHON_MAIN_WHEEL_DIR}/ge -name __pycache__ -type d -exec rm -rf {} +217 && find ${GE_PYTHON_MAIN_WHEEL_DIR}/ge -name __pycache__ -type d -exec rm -rf {} +
200 && cp $<TARGET_FILE:graph_wrapper> ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/_capi/218 && cp $<TARGET_FILE:graph_wrapper> ${GE_PYTHON_MAIN_WHEEL_DIR}/ge/_capi/
@@ -208,9 +226,11 @@ add_custom_command(
208 && echo "Build ge-py wheel package end"226 && echo "Build ge-py wheel package end"
209 DEPENDS eager_style_graph_builder_base ge_api_c_wrapper graph_wrapper227 DEPENDS eager_style_graph_builder_base ge_api_c_wrapper graph_wrapper
210 _ge_runtime_native _ge_custom_op_native _ge_onnx_plugin_native ge_python_custom_op_bridge228 _ge_runtime_native _ge_custom_op_native _ge_onnx_plugin_native ge_python_custom_op_bridge
229+ ge_python_onnx_plugin_bridge
211 ${GE_API_C_WRAPPER}230 ${GE_API_C_WRAPPER}
212 ${GE_PYTHON_RUNTIME_ARTIFACT_MANIFEST}231 ${GE_PYTHON_RUNTIME_ARTIFACT_MANIFEST}
213 ${GE_PYTHON_CUSTOM_OP_ARTIFACT_MANIFEST}232 ${GE_PYTHON_CUSTOM_OP_ARTIFACT_MANIFEST}
233+ ${GE_PYTHON_ONNX_PLUGIN_ARTIFACT_MANIFEST}
214 ${GE_PYTHON_PASS_FALLBACK_CODEGEN_CONFIG}234 ${GE_PYTHON_PASS_FALLBACK_CODEGEN_CONFIG}
215 )235 )
216add_custom_command(236add_custom_command(
@@ -22,6 +22,10 @@ from .registry import (
22)22)
23 23 
24 24 
25+class _InvalidParseNodeReturn(TypeError):
26+ """Internal marker for a parse_node callback returning a non-None value."""
27+ 
28+ 
25def load_and_get_onnx_plugin_descriptors() -> list:29def load_and_get_onnx_plugin_descriptors() -> list:
26 load_onnx_plugins()30 load_onnx_plugins()
27 return get_registered_onnx_plugin_dicts()31 return get_registered_onnx_plugin_dicts()
@@ -41,4 +45,6 @@ def call_parse_node(origin_type: str, node: OnnxNode, operator_handle) -> None:
41 with create_operator(operator_handle) as target:45 with create_operator(operator_handle) as target:
42 result = descriptor.parser_node(node, target)46 result = descriptor.parser_node(node, target)
43 if result is not None:47 if result is not None:
44- raise TypeError("ONNX Plugin parse_node callback must return None")48+ raise _InvalidParseNodeReturn(
49+ "ONNX Plugin parse_node callback must return None"
50+ )
@@ -14,8 +14,29 @@
14 14 
15from __future__ import annotations15from __future__ import annotations
16 16 
17+from pathlib import Path
17from importlib import import_module18from importlib import import_module
18 19 
19-_native = import_module("ge.onnx_plugin._ge_onnx_plugin_native")20+from ge._internal.artifact_utils import (
21+ find_compatible_artifact,
22+ iter_artifacts,
23+ load_bridge_artifact_manifest,
24+ load_module_from_path,
25+)
26+ 
27+_BRIDGE_ABI_VERSION = 1
28+_ARTIFACTS_ROOT = Path(__file__).resolve().parent / "python_onnx_plugin_artifacts"
29+_NATIVE_MODULE_NAME = "ge.onnx_plugin._ge_onnx_plugin_native"
30+ 
31+ 
32+def _load_native_module():
33+ artifacts = iter_artifacts(_ARTIFACTS_ROOT, load_bridge_artifact_manifest)
34+ artifact = find_compatible_artifact(artifacts, _BRIDGE_ABI_VERSION)
35+ if artifact is not None:
36+ return load_module_from_path(_NATIVE_MODULE_NAME, artifact.native_path)
37+ return import_module(_NATIVE_MODULE_NAME)
38+ 
39+ 
40+_native = _load_native_module()
20 41 
21OnnxNode = _native.OnnxNode42OnnxNode = _native.OnnxNode
@@ -10,14 +10,15 @@
10# See LICENSE in the root of the software repository for the full text of the License.10# See LICENSE in the root of the software repository for the full text of the License.
11# -----------------------------------------------------------------------------------------------------------11# -----------------------------------------------------------------------------------------------------------
12 12 
13-from setuptools import find_packages, setup13+from setuptools import find_namespace_packages, setup
14 14 
15setup(15setup(
16 name="ge-py",16 name="ge-py",
17 version="0.0.1",17 version="0.0.1",
18 description="GraphEngine python api",18 description="GraphEngine python api",
19- packages=find_packages(),19+ packages=find_namespace_packages(include=["ge", "ge.*"]),
20 include_package_data=True,20 include_package_data=True,
21+ package_data={"ge.onnx_plugin": ["python_onnx_plugin_artifacts/*/*"]},
21 entry_points={22 entry_points={
22 "ge.es.plugins": [],23 "ge.es.plugins": [],
23 },24 },
@@ -45,6 +45,7 @@
45#include "proto/ge_api.pb.h"45#include "proto/ge_api.pb.h"
46#include "register/op_registry.h"46#include "register/op_registry.h"
47#include "runtime/custom_op/custom_op_loader.h"47#include "runtime/custom_op/custom_op_loader.h"
48+#include "parser/parser/onnx/python_onnx_plugin_bridge/onnx_plugin_bridge_loader.h"
48#include "runtime/v2/core/debug/kernel_tracing.h"49#include "runtime/v2/core/debug/kernel_tracing.h"
49#include "session/session_manager.h"50#include "session/session_manager.h"
50#include "session/ge_session_impl.h"51#include "session/ge_session_impl.h"
@@ -266,6 +267,7 @@ static Status GEInitializeImpl(const std::map<std::string, std::string> &options
266 GE_DISMISSABLE_GUARD(release_python_resources, ([]() {267 GE_DISMISSABLE_GUARD(release_python_resources, ([]() {
267 (void)fusion::UnloadPassPlugins();268 (void)fusion::UnloadPassPlugins();
268 (void)ge::custom_op::UnloadCustomOps();269 (void)ge::custom_op::UnloadCustomOps();
270+ ge::UnloadOnnxPythonPluginBridge();
269 (void)GePythonRuntimeManager::Instance().ShutdownProcess();271 (void)GePythonRuntimeManager::Instance().ShutdownProcess();
270 }));272 }));
271 273 
@@ -404,6 +406,7 @@ Status GEFinalizeV2() {
404 // 这里是 GE 的进程级 finalization,额外负责显式关闭 Python bridge so。406 // 这里是 GE 的进程级 finalization,额外负责显式关闭 Python bridge so。
405 (void)fusion::UnloadPassPlugins();407 (void)fusion::UnloadPassPlugins();
406 (void)custom_op::UnloadCustomOps();408 (void)custom_op::UnloadCustomOps();
409+ UnloadOnnxPythonPluginBridge();
407 // call Finalize410 // call Finalize
408 (void)GeExecutor::FinalizeEx();411 (void)GeExecutor::FinalizeEx();
409 Status ret = SUCCESS;412 Status ret = SUCCESS;
@@ -38,6 +38,7 @@ set(SRC_FORMAT_TRANSFERS
38 38 
39set(SRC_LIST39set(SRC_LIST
40 "common/python_runtime/ge_python_runtime_manager.cc"40 "common/python_runtime/ge_python_runtime_manager.cc"
41+ "${AIR_CODE_DIR}/parser/parser/onnx/python_onnx_plugin_bridge/onnx_plugin_bridge_loader.cc"
41 "common/b_cast/b_cast.cc"42 "common/b_cast/b_cast.cc"
42 "common/plugin/runtime_plugin_loader.cc"43 "common/plugin/runtime_plugin_loader.cc"
43 "common/plugin/plugin_caller.cc"44 "common/plugin/plugin_caller.cc"
@@ -61,6 +61,7 @@
61#include "graph/fusion/pass/pass_plugin_loader.h"61#include "graph/fusion/pass/pass_plugin_loader.h"
62#include "common/python_runtime/ge_python_runtime_manager.h"62#include "common/python_runtime/ge_python_runtime_manager.h"
63#include "runtime/custom_op/custom_op_loader.h"63#include "runtime/custom_op/custom_op_loader.h"
64+#include "parser/parser/onnx/python_onnx_plugin_bridge/onnx_plugin_bridge_loader.h"
64#include "graph/operator_factory_impl.h"65#include "graph/operator_factory_impl.h"
65#include "base/err_msg.h"66#include "base/err_msg.h"
66#include "base/err_mgr.h"67#include "base/err_mgr.h"
@@ -441,6 +442,7 @@ static graphStatus aclgrphBuildInitializeImpl(std::map<std::string, std::string>
441 GE_DISMISSABLE_GUARD(release_python_resources, []() {442 GE_DISMISSABLE_GUARD(release_python_resources, []() {
442 (void)fusion::UnloadPassPlugins();443 (void)fusion::UnloadPassPlugins();
443 (void)ge::custom_op::UnloadCustomOps();444 (void)ge::custom_op::UnloadCustomOps();
445+ ge::UnloadOnnxPythonPluginBridge();
444 (void)GePythonRuntimeManager::Instance().ShutdownProcess();446 (void)GePythonRuntimeManager::Instance().ShutdownProcess();
445 });447 });
446 GE_ASSERT_SUCCESS(ge::custom_op::LoadCustomOps());448 GE_ASSERT_SUCCESS(ge::custom_op::LoadCustomOps());
@@ -488,6 +490,7 @@ void aclgrphBuildFinalize() {
488 // ge_ir_build 生命周期结束时显式关闭 Python bridge so,避免进程退出前长期悬挂。490 // ge_ir_build 生命周期结束时显式关闭 Python bridge so,避免进程退出前长期悬挂。
489 (void)fusion::UnloadPassPlugins();491 (void)fusion::UnloadPassPlugins();
490 (void)custom_op::UnloadCustomOps();492 (void)custom_op::UnloadCustomOps();
493+ UnloadOnnxPythonPluginBridge();
491 if (ge::GELib::GetInstance() != nullptr && ge::GELib::GetInstance()->InitFlag()) {494 if (ge::GELib::GetInstance() != nullptr && ge::GELib::GetInstance()->InitFlag()) {
492 (void)ge::GELib::GetInstance()->Finalize();495 (void)ge::GELib::GetInstance()->Finalize();
493 } else {496 } else {
@@ -54,6 +54,7 @@
54#include "graph/fusion/pass/pass_plugin_loader.h"54#include "graph/fusion/pass/pass_plugin_loader.h"
55#include "common/python_runtime/ge_python_runtime_manager.h"55#include "common/python_runtime/ge_python_runtime_manager.h"
56#include "runtime/custom_op/custom_op_loader.h"56#include "runtime/custom_op/custom_op_loader.h"
57+#include "parser/parser/onnx/python_onnx_plugin_bridge/onnx_plugin_bridge_loader.h"
57 58 
58namespace {59namespace {
59 60 
@@ -510,6 +511,7 @@ Status GeGenerator::Finalize() {
510 (void)fusion::UnloadPassPlugins();511 (void)fusion::UnloadPassPlugins();
511 }512 }
512 (void)custom_op::UnloadCustomOps();513 (void)custom_op::UnloadCustomOps();
514+ ge::UnloadOnnxPythonPluginBridge();
513 Status ret = impl_->graph_manager_.Finalize();515 Status ret = impl_->graph_manager_.Finalize();
514 if (ret != SUCCESS) {516 if (ret != SUCCESS) {
515 GELOGE(GE_GENERATOR_GRAPH_MANAGER_FINALIZE_FAILED, "[Call][Finalize] Graph manager finalize failed.");517 GELOGE(GE_GENERATOR_GRAPH_MANAGER_FINALIZE_FAILED, "[Call][Finalize] Graph manager finalize failed.");
@@ -152,7 +152,7 @@ add_subdirectory(parser/common)
152add_subdirectory(parser/func_to_graph)152add_subdirectory(parser/func_to_graph)
153add_subdirectory(parser/onnx)153add_subdirectory(parser/onnx)
154 154 
155-install(TARGETS _caffe_parser parser_common fmk_onnx_parser fmk_parser func2graph parser_headers155+install(TARGETS _caffe_parser parser_common fmk_onnx_parser ge_python_onnx_plugin_bridge fmk_parser func2graph parser_headers
156 EXPORT parser-targets156 EXPORT parser-targets
157 LIBRARY DESTINATION ${INSTALL_LIBRARY_DIR} ${INSTALL_OPTIONAL}157 LIBRARY DESTINATION ${INSTALL_LIBRARY_DIR} ${INSTALL_OPTIONAL}
158 ARCHIVE DESTINATION ${INSTALL_LIBRARY_DIR} ${INSTALL_OPTIONAL}158 ARCHIVE DESTINATION ${INSTALL_LIBRARY_DIR} ${INSTALL_OPTIONAL}
@@ -22,6 +22,53 @@ set(SRC_LIST
22############ libfmk_onnx_parser.so ############22############ libfmk_onnx_parser.so ############
23add_library(fmk_onnx_parser SHARED ${SRC_LIST})23add_library(fmk_onnx_parser SHARED ${SRC_LIST})
24 24 
25+add_library(ge_python_onnx_plugin_bridge SHARED
26+ "python_onnx_plugin_bridge/onnx_plugin_bridge.cc"
27+)
28+ 
29+add_dependencies(ge_python_onnx_plugin_bridge
30+ parser_protos
31+)
32+ 
33+target_compile_definitions(ge_python_onnx_plugin_bridge PRIVATE
34+ PROTOBUF_INLINE_NOT_IN_HEADERS=0
35+ google=ascend_private
36+)
37+ 
38+target_compile_options(ge_python_onnx_plugin_bridge PRIVATE
39+ $<$<BOOL:${ENABLE_GCOV}>:--coverage>
40+)
41+ 
42+target_include_directories(ge_python_onnx_plugin_bridge PRIVATE
43+ ${CMAKE_BINARY_DIR}
44+ ${CMAKE_BINARY_DIR}/proto/parser_protos
45+ ${AIR_CODE_DIR}
46+ ${AIR_CODE_DIR}/inc
47+ ${AIR_CODE_DIR}/inc/graph_metadef
48+ ${AIR_CODE_DIR}/parser
49+ ${AIR_CODE_DIR}/base
50+ ${HI_PYTHON_INC}
51+ ${pybind11_INCLUDE_DIR}
52+)
53+ 
54+target_link_libraries(ge_python_onnx_plugin_bridge PRIVATE
55+ c_sec_headers
56+ c_sec
57+ ge_intf_pub
58+ parser_common
59+ register
60+ graph
61+ ge_common
62+ ascend_protobuf
63+ ge_python_embed
64+ pybind_options
65+ unified_dlog
66+ error_manager
67+ runtime_headers
68+ -ldl
69+ $<$<BOOL:${ENABLE_GCOV}>:--coverage>
70+)
71+ 
25add_dependencies(fmk_onnx_parser72add_dependencies(fmk_onnx_parser
26 parser_protos73 parser_protos
27)74)
@@ -47,6 +94,7 @@ target_include_directories(fmk_onnx_parser PRIVATE
47 ${AIR_CODE_DIR}/inc/parser94 ${AIR_CODE_DIR}/inc/parser
48 ${AIR_CODE_DIR}/parser/parser95 ${AIR_CODE_DIR}/parser/parser
49 ${AIR_CODE_DIR}/parser/96 ${AIR_CODE_DIR}/parser/
97+ ${AIR_CODE_DIR}/base
50)98)
51 99 
52target_link_options(fmk_onnx_parser PRIVATE100target_link_options(fmk_onnx_parser PRIVATE
@@ -64,6 +112,7 @@ target_link_libraries(fmk_onnx_parser
64 c_sec112 c_sec
65 parser_common113 parser_common
66 graph114 graph
115+ ge_common
67 unified_dlog116 unified_dlog
68 -Wl,--as-needed117 -Wl,--as-needed
69 json118 json
@@ -33,6 +33,7 @@
33#include "parser/common/parser_utils.h"33#include "parser/common/parser_utils.h"
34#include "parser/common/prototype_pass_manager.h"34#include "parser/common/prototype_pass_manager.h"
35#include "parser/onnx/onnx_custom_parser_adapter.h"35#include "parser/onnx/onnx_custom_parser_adapter.h"
36+#include "parser/onnx/python_onnx_plugin_bridge/onnx_plugin_bridge_loader.h"
36#include "parser/onnx/onnx_util.h"37#include "parser/onnx/onnx_util.h"
37#include "register/op_registry.h"38#include "register/op_registry.h"
38#include "register/register_fmk_types.h"39#include "register/register_fmk_types.h"
@@ -59,19 +60,24 @@ graphStatus PrepareBeforeParse(AclGraphParserUtil &acl_graph_parse_util,
59 const std::map<AscendString, AscendString> &parser_params, ge::Graph &graph,60 const std::map<AscendString, AscendString> &parser_params, ge::Graph &graph,
60 std::shared_ptr<domi::ModelParser> &model_parser) {61 std::shared_ptr<domi::ModelParser> &model_parser) {
61 GetParserContext().type = domi::ONNX;62 GetParserContext().type = domi::ONNX;
62- Status init_ret = ge::SUCCESS;63+ static Status acl_parser_init_ret = ge::SUCCESS;
63 static std::once_flag flag;64 static std::once_flag flag;
64- std::call_once(flag, [&init_ret, &acl_graph_parse_util]() {65+ std::call_once(flag, [&acl_graph_parse_util]() {
65 std::map<string, string> options;66 std::map<string, string> options;
66 options.insert(std::pair<string, string>(string(ge::FRAMEWORK_TYPE), to_string(domi::ONNX)));67 options.insert(std::pair<string, string>(string(ge::FRAMEWORK_TYPE), to_string(domi::ONNX)));
67- init_ret = acl_graph_parse_util.AclParserInitialize(options);68+ acl_parser_init_ret = acl_graph_parse_util.AclParserInitialize(options);
68 });69 });
69 70 
70- if (init_ret != ge::SUCCESS) {71+ if (acl_parser_init_ret != ge::SUCCESS) {
71 REPORT_INNER_ERR_MSG("E19999", "AclParserInitialize failed.");72 REPORT_INNER_ERR_MSG("E19999", "AclParserInitialize failed.");
72 GELOGE(ge::FAILED, "[Init][AclParser] failed.");73 GELOGE(ge::FAILED, "[Init][AclParser] failed.");
73 return ge::FAILED;74 return ge::FAILED;
74 }75 }
76+ if (LoadOnnxPythonPluginBridge() != ge::SUCCESS) {
77+ REPORT_INNER_ERR_MSG("E19999", "LoadOnnxPythonPluginBridge failed.");
78+ GELOGE(ge::FAILED, "[Init][OnnxPythonPluginBridge] failed.");
79+ return ge::FAILED;
80+ }
75 81 
76 string output_name;82 string output_name;
77 if (acl_graph_parse_util.ParseParamsBeforeGraph(parser_params, output_name) != ge::SUCCESS) {83 if (acl_graph_parse_util.ParseParamsBeforeGraph(parser_params, output_name) != ge::SUCCESS) {
@@ -0,0 +1,265 @@
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_bridge.h"
12+ 
13+#include "onnx_plugin_bridge_c_api.h"
14+ 
15+#include "Python.h"
16+#include "pybind11/embed.h"
17+#include "pybind11/stl.h"
18+ 
19+#include "common/python_runtime/ge_python_runtime_manager.h"
20+#include "framework/common/debug/ge_log.h"
21+#include "graph/operator.h"
22+#include "parser/common/op_registration_tbe.h"
23+#include "parser/common/op_parser_factory.h"
24+#include "proto/onnx/ge_onnx.pb.h"
25+#include "register/op_registry.h"
26+#include "register/register_fmk_types.h"
27+ 
28+#include <cstdlib>
29+#include <stdexcept>
30+#include <mutex>
31+#include <string>
32+#include <vector>
33+ 
34+namespace ge {
35+namespace {
36+namespace py = pybind11;
37+ 
38+constexpr const char *kBridgeModuleName = "ge.onnx_plugin._bridge";
39+constexpr const char *kNativeModuleName = "ge.onnx_plugin._ge_onnx_plugin_native";
40+constexpr const char *kPluginPathEnv = "ASCEND_CUSTOM_OPP_PATH";
41+ 
42+class OnnxPluginBridge {
43+ public:
44+ static OnnxPluginBridge &Instance() {
45+ static OnnxPluginBridge bridge;
46+ return bridge;
47+ }
48+ 
49+ Status SetArtifactConfig(const onnx_plugin_bridge::PythonOnnxPluginBridgeArtifactConfig *config) {
50+ std::lock_guard<std::mutex> lock(mutex_);
51+ if ((config == nullptr) || (config->native_module_path == nullptr) || (config->native_module_path[0] == '\0')) {
52+ // LCOV_EXCL_START
53+ GELOGE(PARAM_INVALID, "Invalid Python ONNX plugin bridge artifact config.");
54+ return PARAM_INVALID;
55+ // LCOV_EXCL_STOP
56+ }
57+ native_module_path_ = config->native_module_path;
58+ return SUCCESS;
59+ }
60+ 
61+ Status Initialize() {
GengChao
GengChaoGengChao14 天前

不需要finialize吗

likedislike
gentle-knight
gentle-knight
11 天前 评论:
62+ std::lock_guard<std::mutex> lock(mutex_);
63+ if (initialized_) {
64+ return SUCCESS;
65+ }
66+ py::gil_scoped_acquire gil;
67+ try {
68+ SyncPluginPathUnlocked();
69+ LoadNativeModuleUnlocked();
70+ bridge_module_ = py::module_::import(kBridgeModuleName);
71+ invalid_return_exception_ = bridge_module_.attr("_InvalidParseNodeReturn");
72+ const py::object descriptors = bridge_module_.attr("load_and_get_onnx_plugin_descriptors")();
73+ for (const py::handle item : descriptors) {
74+ const py::dict descriptor = py::reinterpret_borrow<py::dict>(item);
75+ const auto target = py::cast<std::string>(descriptor["target"]);
76+ const auto origins = py::cast<std::vector<std::string>>(descriptor["origin_types"]);
77+ for (const auto &origin : origins) {
78+ if (!RegisterDescriptor(target, origin)) {
79+ // LCOV_EXCL_START
80+ GELOGE(FAILED, "Register Python ONNX plugin failed, target[%s], origin[%s].", target.c_str(),
81+ origin.c_str());
82+ ResetBridgeStateUnlocked();
83+ return FAILED;
84+ // LCOV_EXCL_STOP
85+ }
86+ }
87+ }
88+ // LCOV_EXCL_START
89+ } catch (const py::error_already_set &error) {
90+ GELOGE(FAILED, "Load Python ONNX plugins failed: %s", error.what());
91+ ResetBridgeStateUnlocked();
92+ return FAILED;
93+ } catch (const std::exception &error) {
94+ GELOGE(FAILED, "Register Python ONNX plugins failed: %s", error.what());
95+ ResetBridgeStateUnlocked();
96+ return FAILED;
97+ }
98+ // LCOV_EXCL_STOP
99+ initialized_ = true;
100+ return SUCCESS;
101+ }
102+ 
103+ void ResetBridgeState() {
104+ std::lock_guard<std::mutex> lock(mutex_);
105+ if (Py_IsInitialized() == 0) {
106+ // LCOV_EXCL_START
107+ (void)bridge_module_.release();
108+ (void)invalid_return_exception_.release();
109+ initialized_ = false;
110+ return;
111+ // LCOV_EXCL_STOP
112+ }
113+ py::gil_scoped_acquire gil;
114+ ResetBridgeStateUnlocked();
115+ }
116+ 
117+ Status ParseParams(const google::protobuf::Message *message, Operator &operator_dest) {
118+ if ((message == nullptr) || (message->GetTypeName() != ge::onnx::NodeProto::descriptor()->full_name())) {
119+ GELOGE(PARAM_INVALID, "Python ONNX plugin received an invalid NodeProto message.");
120+ return PARAM_INVALID;
121+ }
122+ 
123+ const auto *node = static_cast<const ge::onnx::NodeProto *>(message);
124+ std::lock_guard<std::mutex> lock(mutex_);
125+ if (!initialized_) {
126+ GELOGE(FAILED, "Python ONNX plugin bridge is not initialized.");
127+ return FAILED;
128+ }
129+ py::gil_scoped_acquire gil;
130+ try {
131+ const py::object python_node = py::cast(node, py::return_value_policy::reference);
132+ const auto handle = reinterpret_cast<uintptr_t>(&operator_dest);
133+ (void)bridge_module_.attr("call_parse_node")(node->op_type(), python_node, handle);
134+ return SUCCESS;
135+ } catch (const py::error_already_set &error) {
136+ if (error.matches(invalid_return_exception_.ptr())) {
137+ GELOGE(PARAM_INVALID, "Python ONNX plugin parse_node returned an invalid value.");
138+ return PARAM_INVALID;
139+ }
140+ GELOGE(FAILED, "Python ONNX plugin parse_node failed: %s", error.what());
141+ return FAILED;
142+ // LCOV_EXCL_START
143+ } catch (const std::exception &error) {
144+ GELOGE(FAILED, "Python ONNX plugin bridge failed: %s", error.what());
145+ return FAILED;
146+ }
147+ // LCOV_EXCL_STOP
148+ }
149+ 
150+ private:
151+ void SyncPluginPathUnlocked() {
152+ const char *plugin_path = std::getenv(kPluginPathEnv);
153+ const std::string plugin_path_value = (plugin_path == nullptr) ? std::string() : plugin_path;
154+ const py::object environ = py::module_::import("os").attr("environ");
155+ (void)environ.attr("pop")(kPluginPathEnv, py::none());
156+ if (!plugin_path_value.empty()) {
157+ environ[kPluginPathEnv] = py::str(plugin_path_value);
158+ }
159+ }
160+ 
161+ void LoadNativeModuleUnlocked() {
162+ const py::dict modules = py::module_::import("sys").attr("modules");
163+ if (!modules.attr("get")(py::str(kNativeModuleName), py::none()).is_none()) {
164+ return;
165+ }
166+ if (native_module_path_.empty()) {
167+ (void)py::module_::import(kNativeModuleName);
168+ return;
169+ }
170+ 
171+ const py::module_ importlib_util = py::module_::import("importlib.util");
172+ const py::object spec = importlib_util.attr("spec_from_file_location")(kNativeModuleName, native_module_path_);
173+ if (spec.is_none()) {
174+ throw std::runtime_error("Create Python ONNX native module spec failed");
175+ }
176+ const py::object module = importlib_util.attr("module_from_spec")(spec);
177+ modules[kNativeModuleName] = module;
178+ try {
179+ spec.attr("loader").attr("exec_module")(module);
180+ } catch (...) {
181+ (void)modules.attr("pop")(kNativeModuleName, py::none());
182+ throw;
183+ }
184+ }
185+ 
186+ void ResetBridgeStateUnlocked() {
187+ bridge_module_ = py::object();
188+ invalid_return_exception_ = py::object();
189+ initialized_ = false;
190+ }
191+ 
192+ bool RegisterDescriptor(const std::string &target, const std::string &origin) {
193+ std::string registered_target;
194+ if (domi::OpRegistry::Instance()->GetOmTypeByOriOpType(origin, registered_target)) {
195+ if (registered_target != target) {
196+ GELOGW("Skip Python ONNX plugin for origin[%s], existing registration maps it to target[%s].", origin.c_str(),
197+ registered_target.c_str());
198+ } else {
199+ GELOGI("Skip duplicate Python ONNX plugin registration for target[%s], origin[%s].", target.c_str(),
200+ origin.c_str());
201+ }
202+ return true;
203+ }
204+ const domi::ParseParamFunc parse_params = [](const google::protobuf::Message *message,
205+ Operator &operator_dest) -> Status {
206+ return OnnxPluginBridge::Instance().ParseParams(message, operator_dest);
207+ };
208+ OpRegistrationData registration(target.c_str());
209+ registration.FrameworkType(domi::ONNX).OriginOpType(origin.c_str()).ParseParamsFn(parse_params);
210+ const auto parser_factory = OpParserFactory::Instance(domi::ONNX);
211+ if (parser_factory == nullptr) {
212+ GELOGE(FAILED, "Get ONNX parser factory failed, target[%s], origin[%s].", target.c_str(), origin.c_str());
213+ return false;
214+ }
215+ if (!parser_factory->OpParserIsRegistered(target) && !OpRegistrationTbe::Instance()->Finalize(registration)) {
216+ GELOGE(FAILED, "Finalize Python ONNX plugin registration failed, target[%s], origin[%s].", target.c_str(),
217+ origin.c_str());
218+ return false;
219+ }
220+ if (!domi::OpRegistry::Instance()->Register(registration)) {
221+ GELOGE(FAILED, "Register Python ONNX plugin failed, target[%s], origin[%s].", target.c_str(), origin.c_str());
222+ return false;
223+ }
224+ return true;
225+ }
226+ 
227+ std::mutex mutex_;
228+ bool initialized_ = false;
229+ std::string native_module_path_;
230+ py::object bridge_module_;
231+ py::object invalid_return_exception_;
232+};
233+ 
234+} // namespace
235+ 
236+extern "C" Status SetOnnxPluginBridgeArtifactConfig(
237+ const onnx_plugin_bridge::PythonOnnxPluginBridgeArtifactConfig *config) {
238+ return OnnxPluginBridge::Instance().SetArtifactConfig(config);
239+}
240+ 
241+extern "C" Status RegisterOnnxPluginBridgePlugins() {
242+ return OnnxPluginBridge::Instance().Initialize();
243+}
244+ 
245+extern "C" void ResetOnnxPluginBridgeState() {
246+ OnnxPluginBridge::Instance().ResetBridgeState();
247+}
248+ 
249+extern "C" Status InitOnnxPluginBridge() {
250+ if (GePythonRuntimeManager::Instance().EnsureReady() != SUCCESS) {
251+ return FAILED;
252+ }
253+ return OnnxPluginBridge::Instance().Initialize();
254+}
255+ 
256+extern "C" const onnx_plugin_bridge::PythonOnnxPluginBridgeApi *GeGetPythonOnnxPluginBridgeApi() {
257+ static const onnx_plugin_bridge::PythonOnnxPluginBridgeApi api = {
258+ onnx_plugin_bridge::kPythonOnnxPluginBridgeAbiVersion,
259+ &SetOnnxPluginBridgeArtifactConfig,
260+ &RegisterOnnxPluginBridgePlugins,
261+ &ResetOnnxPluginBridgeState,
262+ };
263+ return &api;
264+}
265+} // namespace ge
@@ -0,0 +1,18 @@
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 PARSER_PARSER_ONNX_PYTHON_ONNX_PLUGIN_BRIDGE_ONNX_PLUGIN_BRIDGE_H_
12+#define PARSER_PARSER_ONNX_PYTHON_ONNX_PLUGIN_BRIDGE_ONNX_PLUGIN_BRIDGE_H_
13+ 
14+#include "ge/ge_api_types.h"
15+ 
16+extern "C" __attribute__((visibility("default"))) ge::Status InitOnnxPluginBridge();
17+ 
18+#endif // PARSER_PARSER_ONNX_PYTHON_ONNX_PLUGIN_BRIDGE_ONNX_PLUGIN_BRIDGE_H_
@@ -0,0 +1,40 @@
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 PARSER_PARSER_ONNX_PYTHON_ONNX_PLUGIN_BRIDGE_ONNX_PLUGIN_BRIDGE_C_API_H_
12+#define PARSER_PARSER_ONNX_PYTHON_ONNX_PLUGIN_BRIDGE_ONNX_PLUGIN_BRIDGE_C_API_H_
13+ 
14+#include "ge/ge_api_types.h"
15+ 
16+namespace ge {
17+namespace onnx_plugin_bridge {
18+ 
19+struct PythonOnnxPluginBridgeArtifactConfig {
20+ const char *artifact_root;
21+ const char *native_module_path;
22+};
23+ 
24+struct PythonOnnxPluginBridgeApi {
25+ uint32_t abi_version;
26+ Status (*set_artifact_config)(const PythonOnnxPluginBridgeArtifactConfig *config);
27+ Status (*register_plugins)();
28+ void (*reset_bridge_state)();
29+};
30+ 
31+constexpr uint32_t kPythonOnnxPluginBridgeAbiVersion = 1U;
32+constexpr const char *kPythonOnnxPluginBridgeGetApiSymbol = "GeGetPythonOnnxPluginBridgeApi";
33+ 
34+} // namespace onnx_plugin_bridge
35+} // namespace ge
36+ 
37+extern "C" __attribute__((visibility("default"))) const ge::onnx_plugin_bridge::PythonOnnxPluginBridgeApi *
38+GeGetPythonOnnxPluginBridgeApi();
39+ 
40+#endif // PARSER_PARSER_ONNX_PYTHON_ONNX_PLUGIN_BRIDGE_ONNX_PLUGIN_BRIDGE_C_API_H_
@@ -0,0 +1,156 @@
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 "parser/parser/onnx/python_onnx_plugin_bridge/onnx_plugin_bridge_loader.h"
12+ 
13+#include <dlfcn.h>
14+ 
15+#include <cstdlib>
16+#include <mutex>
17+#include <string>
18+ 
19+#include "common/python_runtime/ge_python_runtime_manager.h"
20+#include "common/python_runtime/python_artifact_utils.h"
21+#include "common/python_runtime/python_bridge_loader_utils.h"
22+#include "framework/common/debug/ge_log.h"
23+#include "graph_metadef/graph/utils/file_utils.h"
24+#include "parser/parser/onnx/python_onnx_plugin_bridge/onnx_plugin_bridge_c_api.h"
25+ 
26+namespace ge {
27+namespace {
28+ 
29+constexpr const char *kOnnxPluginArtifactsRelativePath = "onnx_plugin/python_onnx_plugin_artifacts";
30+ 
31+namespace artifact = ::ge::python_artifact;
32+namespace bridge_loader = ::ge::python_bridge_loader;
33+namespace onnx_bridge = ::ge::onnx_plugin_bridge;
34+ 
35+std::string GetLoaderLibraryPath() {
36+ Dl_info dl_info{};
37+ if ((dladdr(reinterpret_cast<void *>(&LoadOnnxPythonPluginBridge), &dl_info) == 0) ||
38+ (dl_info.dli_fname == nullptr) || (dl_info.dli_fname[0] == '\0')) {
39+ return "";
40+ }
41+ const auto real_path = RealPath(dl_info.dli_fname);
42+ return real_path.empty() ? std::string(dl_info.dli_fname) : real_path;
43+}
44+ 
45+bool IsBridgeApiValid(const onnx_bridge::PythonOnnxPluginBridgeApi *api, const uint32_t expected_abi) {
46+ return (api != nullptr) && (api->abi_version == expected_abi) && (api->set_artifact_config != nullptr) &&
47+ (api->register_plugins != nullptr) && (api->reset_bridge_state != nullptr);
48+}
49+ 
50+bridge_loader::BridgeLoadDependencies BuildBridgeLoadDependencies() {
51+ return bridge_loader::BridgeLoadDependencies{
52+ &RealPath,
53+ &dlopen,
54+ &dlclose,
55+ &dlsym,
56+ &artifact::ResolveLoadedPythonRuntimeKey,
57+ onnx_bridge::kPythonOnnxPluginBridgeGetApiSymbol,
58+ onnx_bridge::kPythonOnnxPluginBridgeAbiVersion,
59+ RTLD_NOW | RTLD_GLOBAL,
60+ };
61+}
62+ 
63+class OnnxPluginBridgeLoader {
64+ public:
65+ static OnnxPluginBridgeLoader &Instance() {
66+ static OnnxPluginBridgeLoader loader;
67+ return loader;
68+ }
69+ 
70+ Status Load() {
71+ if (!NeedLoad()) {
72+ return SUCCESS;
73+ }
74+ if (GePythonRuntimeManager::Instance().EnsureReady() != SUCCESS) {
75+ GELOGE(FAILED, "Prepare Python runtime for ONNX plugin bridge failed.");
76+ return FAILED;
77+ }
78+ 
79+ std::lock_guard<std::mutex> lock(mutex_);
80+ if (EnsureLoaded() != SUCCESS) {
81+ return FAILED;
82+ }
83+ const auto ret = api_->register_plugins();
84+ if (ret == SUCCESS) {
85+ bridge_active_ = true;
86+ }
87+ return ret;
88+ }
89+ 
90+ void Unload() {
91+ std::lock_guard<std::mutex> lock(mutex_);
92+ if ((api_ == nullptr) || !bridge_active_) {
93+ return;
94+ }
95+ api_->reset_bridge_state();
96+ bridge_active_ = false;
97+ }
98+ 
99+ private:
100+ bool NeedLoad() const {
101+ const char *plugin_path = std::getenv("ASCEND_CUSTOM_OPP_PATH");
102+ return (plugin_path != nullptr) && (plugin_path[0] != '\0');
103+ }
104+ 
105+ Status EnsureLoaded() {
106+ if (api_ != nullptr) {
107+ return SUCCESS;
108+ }
109+ 
110+ const auto runtime_key = artifact::ResolveLoadedPythonRuntimeKey();
111+ const auto loader_library_path = GetLoaderLibraryPath();
112+ const auto dependencies = BuildBridgeLoadDependencies();
113+ const auto candidates = artifact::BuildPrebuiltBridgeLibraryCandidates(
114+ runtime_key, loader_library_path, kOnnxPluginArtifactsRelativePath,
115+ onnx_bridge::kPythonOnnxPluginBridgeAbiVersion);
116+ for (const auto &candidate : candidates) {
117+ bridge_loader::LoadedBridgeCandidate<onnx_bridge::PythonOnnxPluginBridgeApi> loaded_bridge;
118+ const auto status = bridge_loader::TryLoadBridgeCandidate<onnx_bridge::PythonOnnxPluginBridgeApi,
119+ onnx_bridge::PythonOnnxPluginBridgeArtifactConfig>(
120+ runtime_key, candidate, dependencies, &IsBridgeApiValid, loaded_bridge);
121+ if (status != bridge_loader::BridgeLoadStatus::kSuccess) {
122+ GELOGW("Skip ONNX Python plugin bridge candidate[%s], status[%s].", candidate.bridge_path.c_str(),
123+ bridge_loader::BridgeLoadStatusToString(status));
124+ continue;
125+ }
126+ api_ = loaded_bridge.api;
127+ GELOGI("Load ONNX Python plugin bridge from [%s] success.", loaded_bridge.real_path.c_str());
128+ return SUCCESS;
129+ }
130+ const auto manifests =
131+ artifact::BuildArtifactManifestCandidates(loader_library_path, kOnnxPluginArtifactsRelativePath);
132+ const char *python_path = std::getenv(artifact::kPythonPathEnvName);
133+ GELOGE(FAILED,
134+ "No compatible ONNX Python plugin bridge artifact found for runtime[%s], loader[%s], "
135+ "PYTHONPATH[%s], manifests[%zu].",
136+ runtime_key.ToString().c_str(), loader_library_path.c_str(), python_path == nullptr ? "" : python_path,
137+ manifests.size());
138+ return FAILED;
139+ }
140+ 
141+ std::mutex mutex_;
142+ const onnx_bridge::PythonOnnxPluginBridgeApi *api_{nullptr};
143+ bool bridge_active_{false};
144+};
145+ 
146+} // namespace
147+ 
148+Status LoadOnnxPythonPluginBridge() {
149+ return OnnxPluginBridgeLoader::Instance().Load();
150+}
151+ 
152+void UnloadOnnxPythonPluginBridge() {
153+ OnnxPluginBridgeLoader::Instance().Unload();
154+}
155+ 
156+} // namespace ge
@@ -0,0 +1,23 @@
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 PARSER_PARSER_ONNX_PYTHON_ONNX_PLUGIN_BRIDGE_ONNX_PLUGIN_BRIDGE_LOADER_H_
12+#define PARSER_PARSER_ONNX_PYTHON_ONNX_PLUGIN_BRIDGE_ONNX_PLUGIN_BRIDGE_LOADER_H_
13+ 
14+#include "ge/ge_api_types.h"
15+ 
16+namespace ge {
17+ 
18+__attribute__((visibility("default"))) Status LoadOnnxPythonPluginBridge();
19+__attribute__((visibility("default"))) void UnloadOnnxPythonPluginBridge();
20+ 
21+} // namespace ge
22+ 
23+#endif // PARSER_PARSER_ONNX_PYTHON_ONNX_PLUGIN_BRIDGE_ONNX_PLUGIN_BRIDGE_LOADER_H_
@@ -398,6 +398,7 @@ if [[ "X$ENABLE_GE_UT" = "Xon" ]] || [[ "X$ENABLE_RT2_UT" = "Xon" ]] || [[ "X$EN
398 COV_DIRS+=("${BUILD_PATH}/graph_metadef")398 COV_DIRS+=("${BUILD_PATH}/graph_metadef")
399 COV_DIRS+=("${BUILD_PATH}/compiler")399 COV_DIRS+=("${BUILD_PATH}/compiler")
400 COV_DIRS+=("${BUILD_PATH}/runtime/v1")400 COV_DIRS+=("${BUILD_PATH}/runtime/v1")
401+ COV_DIRS+=("${BUILD_PATH}/parser")
401 fi402 fi
402 if [[ "X$ENABLE_RT2_UT" = "Xon" ]]; then403 if [[ "X$ENABLE_RT2_UT" = "Xon" ]]; then
403 echo "[TEST GE RT] Begin to run tests with leaks check"404 echo "[TEST GE RT] Begin to run tests with leaks check"
@@ -1209,6 +1209,106 @@ set_tests_properties(ut_fusion_pass_executor_utest PROPERTIES
1209 ENVIRONMENT "PYTHONPATH=${FUSION_PASS_PY_INSTALL_DIR}"1209 ENVIRONMENT "PYTHONPATH=${FUSION_PASS_PY_INSTALL_DIR}"
1210)1210)
1211 1211 
1212+# ut_onnx_plugin_bridge_utest
1213+add_executable(ut_onnx_plugin_bridge_utest
1214+ "onnx_plugin_bridge_unittest.cc"
1215+)
1216+ 
1217+add_dependencies(ut_onnx_plugin_bridge_utest
1218+ ge_python_onnx_plugin_bridge
1219+)
1220+ 
1221+target_compile_options(ut_onnx_plugin_bridge_utest PRIVATE
1222+ -Werror=format
1223+ -Wno-deprecated-declarations
1224+ -Wall -Wfloat-equal -Werror
1225+ -Wno-subobject-linkage
1226+)
1227+ 
1228+target_include_directories(ut_onnx_plugin_bridge_utest PRIVATE
1229+ ${AIR_CODE_DIR}/tests/framework/ge_runtime_stub/include
1230+ ${ASCEND_INSTALL_PATH}/include
1231+ ${ASCGEN_DIR}/inc
1232+ ${AIR_CODE_DIR}/parser
1233+ ${AIR_CODE_DIR}/parser/parser
1234+ ${AIR_CODE_DIR}/inc/parser
1235+ ${AIR_CODE_DIR}/inc/parser/external
1236+ ${pybind11_INCLUDE_DIR}
1237+)
1238+ 
1239+target_compile_definitions(ut_onnx_plugin_bridge_utest PRIVATE
1240+ google=ascend_private
1241+ ONNX_PYTHON_PLUGIN_BRIDGE_PATH="$<TARGET_FILE:ge_python_onnx_plugin_bridge>"
1242+)
1243+ 
1244+target_link_options(ut_onnx_plugin_bridge_utest PRIVATE
1245+ -Wl,--disable-new-dtags
1246+ -Wl,-rpath,${CMAKE_CURRENT_LIST_DIR}
1247+ -Wl,-rpath,${CMAKE_BINARY_DIR}/parser/parser/onnx
1248+ -rdynamic
1249+ -Wl,-Bsymbolic
1250+)
1251+ 
1252+target_link_libraries(ut_onnx_plugin_bridge_utest
1253+ intf_llt_pub
1254+ ge_metadef_headers
1255+ ascendcl_stub
1256+ ascend_hal_stub
1257+ ${AIR_COMMON_LINK_OPTION}
1258+ ge_compiler
1259+ gert
1260+ register
1261+ graph
1262+ graph_base
1263+ fmk_onnx_parser
1264+ parser_common
1265+ unified_dlog
1266+ -Wl,-z,muldefs
1267+ -Wl,--whole-archive
1268+ ge_runtime_stub
1269+ rt2_registry_static
1270+ -Wl,--no-whole-archive
1271+ ge_running_env
1272+ ${COMMON_SHARED_LIBRARIES}
1273+ ge_python_embed
1274+ pybind_options
1275+ GTestShared::gtest
1276+ GTestShared::gtest_main
1277+ -lrt
1278+ -ldl
1279+)
1280+ 
1281+# Install ge_py wheel for onnx plugin bridge test
1282+set(ONNX_PLUGIN_PY_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/onnx_plugin_py_install)
1283+ 
1284+add_custom_command(
1285+ OUTPUT ${ONNX_PLUGIN_PY_INSTALL_DIR}/.installed
1286+ COMMAND ${CMAKE_COMMAND} -E remove_directory ${ONNX_PLUGIN_PY_INSTALL_DIR}
1287+ COMMAND ${CMAKE_COMMAND} -E make_directory ${ONNX_PLUGIN_PY_INSTALL_DIR}
1288+ COMMAND ${HI_PYTHON} -m pip install --target ${ONNX_PLUGIN_PY_INSTALL_DIR}
1289+ ${CMAKE_BINARY_DIR}/api/python/ge/ge_py-0.0.1-py3-none-any.whl
1290+ --force-reinstall
1291+ COMMAND ${HI_PYTHON} -m pip install --target ${ONNX_PLUGIN_PY_INSTALL_DIR}
1292+ ${CMAKE_BINARY_DIR}/tests/ge/ut/ge/graph/eager_style_graph_builder/graph_construction_test/output/whl/es_ut_test-1.0.0-py3-none-any.whl --force-reinstall
1293+ COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:eager_style_graph_builder_base> ${ONNX_PLUGIN_PY_INSTALL_DIR}/ge/_capi/
1294+ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/tests/ge/ut/ge/graph/eager_style_graph_builder/graph_construction_test/output/lib64/libes_ut_test.so ${ONNX_PLUGIN_PY_INSTALL_DIR}/ge/_capi/
1295+ COMMAND ${CMAKE_COMMAND} -E touch ${ONNX_PLUGIN_PY_INSTALL_DIR}/.installed
1296+ DEPENDS ge_python_main_wheel eager_style_graph_builder_base es_ut_test
1297+ COMMENT "Installing ge-py wheel for onnx_plugin_bridge_utest"
1298+)
1299+ 
1300+add_custom_target(install_ge_py_for_onnx_plugin_bridge_ut DEPENDS
1301+ ${ONNX_PLUGIN_PY_INSTALL_DIR}/.installed)
1302+ 
1303+add_dependencies(ut_onnx_plugin_bridge_utest install_ge_py_for_onnx_plugin_bridge_ut)
1304+ 
1305+add_test(NAME ut_onnx_plugin_bridge_utest COMMAND ut_onnx_plugin_bridge_utest
1306+ --gtest_output=xml:${CMAKE_INSTALL_PREFIX}/report/ut/ut_onnx_plugin_bridge_utest.xml)
1307+set_tests_properties(ut_onnx_plugin_bridge_utest PROPERTIES
1308+ LABELS "ut;ge_common;ut_onnx_plugin_bridge_utest"
1309+ ENVIRONMENT "PYTHONPATH=${ONNX_PLUGIN_PY_INSTALL_DIR};ASAN_OPTIONS=detect_leaks=0;LSAN_OPTIONS=detect_leaks=0:exitcode=0"
1310+)
1311+ 
1212# libge_label_maker_utest1312# libge_label_maker_utest
1213add_executable(ut_libge_label_maker_utest1313add_executable(ut_libge_label_maker_utest
1214 ${COMMON_TEST_FILES}1314 ${COMMON_TEST_FILES}
@@ -12,15 +12,12 @@
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- 
17-import pytest
18- 
19import ge.graph as graph_api15import ge.graph as graph_api
20import ge.onnx_plugin as onnx_plugin_api16import ge.onnx_plugin as onnx_plugin_api
17+import pytest
21from ge.graph import Operator18from ge.graph import Operator
22from ge.onnx_plugin import onnx_plugin19from ge.onnx_plugin import onnx_plugin
23-from ge.onnx_plugin._bridge import call_parse_node20+from ge.onnx_plugin._bridge import _InvalidParseNodeReturn, call_parse_node
24from ge.onnx_plugin.registry import clear_registered_onnx_plugins21from ge.onnx_plugin.registry import clear_registered_onnx_plugins
25from python_onnx_plugin_test_utils import FakeOperatorCapi22from python_onnx_plugin_test_utils import FakeOperatorCapi
26 23 
@@ -39,17 +36,6 @@ def clear_registry():
39 clear_registered_onnx_plugins()36 clear_registered_onnx_plugins()
40 37 
41 38 
42-def _node(origin_type="test.domain::1::Source", attrs=None):
43- attrs = {"alpha": 0.5} if attrs is None else attrs
44- return SimpleNamespace(
45- name="source",
46- origin_type=origin_type,
47- inputs=("x0", "x1"),
48- outputs=("y",),
49- attrs=attrs,
50- )
51- 
52- 
53def test_public_exports_match_pr1_support_matrix():39def test_public_exports_match_pr1_support_matrix():
54 assert onnx_plugin_api.__all__ == ["OnnxNode", "OnnxPlugin", "onnx_plugin"]40 assert onnx_plugin_api.__all__ == ["OnnxNode", "OnnxPlugin", "onnx_plugin"]
55 assert "Operator" in graph_api.__all__41 assert "Operator" in graph_api.__all__
@@ -66,70 +52,8 @@ def test_unsupported_pr1_interfaces_are_not_exposed():
66 assert not hasattr(Operator, name)52 assert not hasattr(Operator, name)
67 53 
68 54 
69-def test_elu_and_sum_equivalent_callbacks(operator_capi):55+def test_invalid_parse_node_return_is_type_error_subclass():
70- elu = onnx_plugin(56+ assert issubclass(_InvalidParseNodeReturn, TypeError)
71- source="EluSource", domain="test.domain", opsets=(1,), target="EluTarget"
72- )
73- sum_plugin = onnx_plugin(
74- source="SumSource", domain="test.domain", opsets=(1,), target="SumTarget"
75- )
76- 
77- @elu.parse_node
78- def parse_elu(node, target):
79- target.set_attr("alpha", node.attrs.get("alpha", 1.0))
80- 
81- @sum_plugin.parse_node
82- def parse_sum(node, target):
83- count = len(node.inputs)
84- if count == 0:
85- raise ValueError("Sum requires at least one input")
86- target.register_dynamic_input("x", count)
87- target.set_attr("N", count)
88- 
89- call_parse_node(
90- "test.domain::1::EluSource",
91- _node("test.domain::1::EluSource", attrs={}),
92- operator_capi.handle,
93- )
94- elu_attrs = dict(operator_capi.attrs)
95- operator_capi.attrs.clear()
96- operator_capi.dynamic_inputs.clear()
97- call_parse_node(
98- "test.domain::1::SumSource",
99- _node("test.domain::1::SumSource", attrs={}),
100- operator_capi.handle,
101- )
102- 
103- assert elu_attrs == {"alpha": 1.0}
104- assert operator_capi.attrs == {"N": 2}
105- assert operator_capi.dynamic_inputs == [("x", 2)]
106- 
107- 
108-def test_call_parse_node_dispatches_objects_and_mutations(operator_capi):
109- plugin = onnx_plugin(
110- source="Source", domain="test.domain", opsets=(1,), target="TargetOp"
111- )
112- seen = {}
113- node = _node()
114- 
115- @plugin.parse_node
116- def parse_source(node, target):
117- seen["node"] = node
118- seen["target"] = target
119- target.set_attr("alpha", node.attrs["alpha"])
120- target.set_attr("N", len(node.inputs))
121- target.register_dynamic_input("x", len(node.inputs))
122- 
123- result = call_parse_node("test.domain::1::Source", node, operator_capi.handle)
124- 
125- assert result is None
126- assert seen["node"] is node
127- assert isinstance(seen["target"], Operator)
128- assert seen["node"].origin_type == "test.domain::1::Source"
129- assert operator_capi.attrs == {"alpha": 0.5, "N": 2}
130- assert operator_capi.dynamic_inputs == [("x", 2)]
131- with pytest.raises(RuntimeError, match="only valid inside parse_node"):
132- _ = seen["target"].name
133 57 
134 58 
135def test_call_parse_node_rejects_unknown_origin_without_creating_operator(59def test_call_parse_node_rejects_unknown_origin_without_creating_operator(
@@ -138,7 +62,7 @@ def test_call_parse_node_rejects_unknown_origin_without_creating_operator(
138 with pytest.raises(KeyError, match="not registered.*test.domain::1::Missing"):62 with pytest.raises(KeyError, match="not registered.*test.domain::1::Missing"):
139 call_parse_node(63 call_parse_node(
140 "test.domain::1::Missing",64 "test.domain::1::Missing",
141- _node("test.domain::1::Missing"),65+ None,
142 operator_capi.handle,66 operator_capi.handle,
143 )67 )
144 68 
@@ -156,7 +80,7 @@ def test_call_parse_node_invalidates_operator_when_callback_raises(operator_capi
156 raise LookupError("callback failed")80 raise LookupError("callback failed")
157 81 
158 with pytest.raises(LookupError, match="callback failed"):82 with pytest.raises(LookupError, match="callback failed"):
159- call_parse_node("test.domain::1::Source", _node(), operator_capi.handle)83+ call_parse_node("test.domain::1::Source", None, operator_capi.handle)
160 84 
161 with pytest.raises(RuntimeError, match="only valid inside parse_node"):85 with pytest.raises(RuntimeError, match="only valid inside parse_node"):
162 seen["target"].set_attr("N", 1)86 seen["target"].set_attr("N", 1)
@@ -175,5 +99,5 @@ def test_call_parse_node_rejects_non_none_return_and_invalidates(
175 del node, target99 del node, target
176 return return_value100 return return_value
177 101 
178- with pytest.raises(TypeError, match="must return None"):102+ with pytest.raises(_InvalidParseNodeReturn, match="must return None"):
179- call_parse_node("test.domain::1::Source", _node(), operator_capi.handle)103+ call_parse_node("test.domain::1::Source", None, operator_capi.handle)
@@ -0,0 +1,232 @@
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 <dlfcn.h>
12+#include <gtest/gtest.h>
13+#include <cstdlib>
14+#include <string>
15+ 
16+#include "common/python_runtime/ge_python_runtime_manager.h"
17+#include "framework/omg/parser/parser_factory.h"
18+#include "common/ge_common/ge_inner_error_codes.h"
19+#include "ge/ge_api_error_codes.h"
20+#include "graph/operator.h"
21+#include "graph/utils/attr_utils.h"
22+#include "parser/common/op_registration_tbe.h"
23+#include "parser/onnx/python_onnx_plugin_bridge/onnx_plugin_bridge_c_api.h"
24+#include "parser/onnx/python_onnx_plugin_bridge/onnx_plugin_bridge_loader.h"
25+#include "proto/onnx/ge_onnx.pb.h"
26+#include "register/op_registry.h"
27+#include "register/register_fmk_types.h"
28+#include "onnx_plugin_test_helper.h"
29+#include "pybind11/embed.h"
30+#include "pybind11/eval.h"
31+ 
32+namespace ge {
33+namespace {
34+namespace py = pybind11;
35+ 
36+Status ParseCppPriority(const google::protobuf::Message *, Operator &op_dest) {
37+ op_dest.SetAttr("source", "cpp");
38+ return SUCCESS;
39+}
40+ 
41+class ScopedBadSyntaxPlugin {
42+ public:
43+ ScopedBadSyntaxPlugin() {
44+ py::gil_scoped_acquire gil;
45+ py::exec(R"PY(
46+import ge.onnx_plugin.bootstrap as _bootstrap
47+_bootstrap._mde_original_loader = _bootstrap.load_plugins_from_env
48+def _mde_bad_loader(*args, **kwargs):
49+ compile("def broken(:\n pass\n", "<memory-bad-plugin>", "exec")
50+_bootstrap.load_plugins_from_env = _mde_bad_loader
51+)PY");
52+ }
53+ 
54+ ~ScopedBadSyntaxPlugin() {
55+ try {
56+ py::gil_scoped_acquire gil;
57+ py::exec(R"PY(
58+import ge.onnx_plugin.bootstrap as _bootstrap
59+_bootstrap.load_plugins_from_env = _bootstrap._mde_original_loader
60+del _bootstrap._mde_original_loader
61+)PY");
62+ } catch (...) {
63+ ADD_FAILURE() << "Failed to restore the ONNX plugin loader.";
64+ }
65+ }
66+};
67+ 
68+} // namespace
69+ 
70+using onnx_plugin_test::ScopedInMemoryPlugin;
71+ 
72+TEST(OnnxPythonPluginBridge, ResetBeforePythonRuntimeDoesNotCrash) {
73+ void *bridge = dlopen(ONNX_PYTHON_PLUGIN_BRIDGE_PATH, RTLD_NOW | RTLD_GLOBAL);
74+ ASSERT_NE(bridge, nullptr);
75+ using ResetBridgeFunc = void (*)();
76+ const auto reset_bridge = reinterpret_cast<ResetBridgeFunc>(dlsym(bridge, "ResetOnnxPluginBridgeState"));
77+ ASSERT_NE(reset_bridge, nullptr);
78+ reset_bridge();
79+}
80+ 
81+TEST(OnnxPythonPluginBridge, InitializeFailsOnBadSyntaxPlugin) {
82+ ASSERT_EQ(GePythonRuntimeManager::Instance().EnsureReady(), SUCCESS);
83+ 
84+ void *bridge = dlopen(ONNX_PYTHON_PLUGIN_BRIDGE_PATH, RTLD_NOW | RTLD_GLOBAL);
85+ ASSERT_NE(bridge, nullptr);
86+ using InitBridgeFunc = Status (*)();
87+ const auto init_bridge = reinterpret_cast<InitBridgeFunc>(dlsym(bridge, "InitOnnxPluginBridge"));
88+ ASSERT_NE(init_bridge, nullptr);
89+ 
90+ ASSERT_EQ(unsetenv("ASCEND_CUSTOM_OPP_PATH"), 0);
91+ EXPECT_EQ(init_bridge(), SUCCESS);
92+ using ResetBridgeFunc = void (*)();
93+ const auto reset_bridge = reinterpret_cast<ResetBridgeFunc>(dlsym(bridge, "ResetOnnxPluginBridgeState"));
94+ ASSERT_NE(reset_bridge, nullptr);
95+ reset_bridge();
96+ 
97+ std::string native_module_path;
98+ {
99+ py::gil_scoped_acquire gil;
100+ const auto native_module = py::module_::import("ge.onnx_plugin._ge_onnx_plugin_native");
101+ native_module_path = py::str(native_module.attr("__file__"));
102+ const auto modules = py::module_::import("sys").attr("modules");
103+ (void)modules.attr("pop")("ge.onnx_plugin._ge_onnx_plugin_native", py::none());
104+ }
105+ using SetConfigFunc = Status (*)(const onnx_plugin_bridge::PythonOnnxPluginBridgeArtifactConfig *);
106+ const auto set_config = reinterpret_cast<SetConfigFunc>(dlsym(bridge, "SetOnnxPluginBridgeArtifactConfig"));
107+ ASSERT_NE(set_config, nullptr);
108+ onnx_plugin_bridge::PythonOnnxPluginBridgeArtifactConfig config{nullptr, native_module_path.c_str()};
109+ ASSERT_EQ(set_config(&config), SUCCESS);
110+ 
111+ ScopedBadSyntaxPlugin bad_syntax_plugin;
112+ ASSERT_EQ(setenv("ASCEND_CUSTOM_OPP_PATH", "__mde_bad_syntax_plugin_in_memory__", 1), 0);
113+ EXPECT_NE(init_bridge(), SUCCESS);
114+ unsetenv("ASCEND_CUSTOM_OPP_PATH");
115+}
116+ 
117+TEST(OnnxPythonPluginBridge, ParseParamsUsesNativeNodeAndPreservesCppPriority) {
118+ ScopedInMemoryPlugin in_memory_plugin;
119+ ASSERT_EQ(setenv("ASCEND_CUSTOM_OPP_PATH", "__ge_py_onnx_plugin_in_memory__", 1), 0);
120+ domi::OpRegistrationData cpp_registration("BridgePriorityTarget");
121+ cpp_registration.FrameworkType(domi::ONNX)
122+ .OriginOpType("test.domain::1::BridgePriority")
123+ .ParseParamsFn(ParseCppPriority);
124+ (void)domi::OpRegTbeParserFactory::Instance()->Finalize(cpp_registration);
125+ ASSERT_TRUE(domi::OpRegistry::Instance()->Register(cpp_registration));
126+ 
127+ void *bridge = dlopen(ONNX_PYTHON_PLUGIN_BRIDGE_PATH, RTLD_NOW | RTLD_GLOBAL);
128+ ASSERT_NE(bridge, nullptr);
129+ using InitBridgeFunc = Status (*)();
130+ const auto init_bridge = reinterpret_cast<InitBridgeFunc>(dlsym(bridge, "InitOnnxPluginBridge"));
131+ ASSERT_NE(init_bridge, nullptr);
132+ ASSERT_EQ(init_bridge(), SUCCESS);
133+ ASSERT_EQ(init_bridge(), SUCCESS);
134+ 
135+ ge::onnx::NodeProto node;
136+ node.set_name("bridge_node");
137+ node.set_op_type("test.domain::1::BridgeElu");
138+ node.add_input("x");
139+ auto *alpha = node.add_attribute();
140+ alpha->set_name("alpha");
141+ alpha->set_type(ge::onnx::AttributeProto_AttributeType_FLOAT);
142+ alpha->set_f(0.5F);
143+ 
144+ Operator op("bridge_node", "BridgeEluTarget");
145+ const auto parse_elu = domi::OpRegistry::Instance()->GetParseParamFunc("BridgeEluTarget", node.op_type());
146+ ASSERT_NE(parse_elu, nullptr);
147+ EXPECT_EQ(parse_elu(&node, op), SUCCESS);
148+ float alpha_value = 0.0F;
149+ EXPECT_EQ(op.GetAttr("alpha", alpha_value), GRAPH_SUCCESS);
150+ EXPECT_FLOAT_EQ(alpha_value, 0.5F);
151+ 
152+ node.set_op_type("test.domain::1::BridgeError");
153+ Operator error_op("error_node", "BridgeErrorTarget");
154+ const auto parse_error = domi::OpRegistry::Instance()->GetParseParamFunc("BridgeErrorTarget", node.op_type());
155+ ASSERT_NE(parse_error, nullptr);
156+ EXPECT_EQ(parse_error(&node, error_op), FAILED);
157+ 
158+ node.set_op_type("test.domain::1::BridgeReturn");
159+ Operator return_op("return_node", "BridgeReturnTarget");
160+ const auto parse_return = domi::OpRegistry::Instance()->GetParseParamFunc("BridgeReturnTarget", node.op_type());
161+ ASSERT_NE(parse_return, nullptr);
162+ EXPECT_NE(parse_return(&node, return_op), SUCCESS);
163+ 
164+ node.set_op_type("test.domain::1::BridgePriority");
165+ Operator priority_op("priority_node", "BridgePriorityTarget");
166+ const auto parse_priority = domi::OpRegistry::Instance()->GetParseParamFunc("BridgePriorityTarget", node.op_type());
167+ ASSERT_NE(parse_priority, nullptr);
168+ EXPECT_EQ(parse_priority(&node, priority_op), SUCCESS);
169+ std::string source;
170+ EXPECT_EQ(priority_op.GetAttr("source", source), GRAPH_SUCCESS);
171+ EXPECT_EQ(source, "cpp");
172+ 
173+ Operator null_op("null_node", "BridgeEluTarget");
174+ EXPECT_NE(parse_elu(nullptr, null_op), SUCCESS);
175+ 
176+ ge::onnx::AttributeProto wrong_msg;
177+ EXPECT_NE(parse_elu(&wrong_msg, null_op), SUCCESS);
178+ 
179+ using ResetBridgeFunc = void (*)();
180+ const auto reset_bridge = reinterpret_cast<ResetBridgeFunc>(dlsym(bridge, "ResetOnnxPluginBridgeState"));
181+ ASSERT_NE(reset_bridge, nullptr);
182+ reset_bridge();
183+ EXPECT_NE(parse_elu(&node, priority_op), SUCCESS);
184+}
185+ 
186+TEST(OnnxPythonPluginBridge, LoadThroughCommonLoader) {
187+ const char *old_plugin_path = std::getenv("ASCEND_CUSTOM_OPP_PATH");
188+ const std::string old_plugin_path_value = old_plugin_path == nullptr ? "" : old_plugin_path;
189+ const bool had_plugin_path = old_plugin_path != nullptr;
190+ const char *old_python_path = std::getenv("PYTHONPATH");
191+ const std::string old_python_path_value = old_python_path == nullptr ? "" : old_python_path;
192+ const bool had_python_path = old_python_path != nullptr;
193+ 
194+ ASSERT_EQ(unsetenv("ASCEND_CUSTOM_OPP_PATH"), 0);
195+ EXPECT_EQ(LoadOnnxPythonPluginBridge(), SUCCESS);
196+ ASSERT_EQ(setenv("ASCEND_CUSTOM_OPP_PATH", "__ge_py_onnx_plugin_in_memory__", 1), 0);
197+ ASSERT_EQ(setenv("PYTHONPATH", "", 1), 0);
198+ EXPECT_EQ(LoadOnnxPythonPluginBridge(), FAILED);
199+ 
200+ if (had_python_path) {
201+ ASSERT_EQ(setenv("PYTHONPATH", old_python_path_value.c_str(), 1), 0);
202+ } else {
203+ ASSERT_EQ(unsetenv("PYTHONPATH"), 0);
204+ }
205+ ScopedInMemoryPlugin in_memory_plugin;
206+ ASSERT_EQ(LoadOnnxPythonPluginBridge(), SUCCESS);
207+ ASSERT_EQ(LoadOnnxPythonPluginBridge(), SUCCESS);
208+ UnloadOnnxPythonPluginBridge();
209+ ASSERT_EQ(LoadOnnxPythonPluginBridge(), SUCCESS);
210+ UnloadOnnxPythonPluginBridge();
211+ 
212+ if (had_plugin_path) {
213+ ASSERT_EQ(setenv("ASCEND_CUSTOM_OPP_PATH", old_plugin_path_value.c_str(), 1), 0);
214+ } else {
215+ ASSERT_EQ(unsetenv("ASCEND_CUSTOM_OPP_PATH"), 0);
216+ }
217+}
218+ 
219+TEST(OnnxPythonPluginBridge, RejectsInvalidArtifactConfig) {
220+ void *bridge = dlopen(ONNX_PYTHON_PLUGIN_BRIDGE_PATH, RTLD_NOW | RTLD_GLOBAL);
221+ ASSERT_NE(bridge, nullptr);
222+ using SetConfigFunc = Status (*)(const onnx_plugin_bridge::PythonOnnxPluginBridgeArtifactConfig *);
223+ const auto set_config = reinterpret_cast<SetConfigFunc>(dlsym(bridge, "SetOnnxPluginBridgeArtifactConfig"));
224+ ASSERT_NE(set_config, nullptr);
225+ EXPECT_EQ(set_config(nullptr), ge::PARAM_INVALID);
226+ onnx_plugin_bridge::PythonOnnxPluginBridgeArtifactConfig config{nullptr, nullptr};
227+ EXPECT_EQ(set_config(&config), ge::PARAM_INVALID);
228+ config.native_module_path = "";
229+ EXPECT_EQ(set_config(&config), ge::PARAM_INVALID);
230+}
231+ 
232+} // namespace ge
@@ -0,0 +1,108 @@
1+/**
2+ * Copyright (c) 2025 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 TESTS_GE_UT_GE_ONNX_PLUGIN_TEST_HELPER_H_
12+#define TESTS_GE_UT_GE_ONNX_PLUGIN_TEST_HELPER_H_
13+ 
14+#include <gtest/gtest.h>
15+ 
16+#include "pybind11/embed.h"
17+#include "pybind11/eval.h"
18+ 
19+namespace ge {
20+namespace onnx_plugin_test {
21+namespace py = pybind11;
22+ 
23+constexpr const char *kInMemoryPluginSource = R"PY(
24+from ge.onnx_plugin import onnx_plugin
25+ 
26+elu = onnx_plugin(
27+ source="BridgeElu", domain="test.domain", opsets=(1,), target="BridgeEluTarget"
28+)
29+@elu.parse_node
30+def parse_elu(node, target):
31+ target.set_attr("alpha", node.attrs["alpha"])
32+ 
33+error = onnx_plugin(
34+ source="BridgeError", domain="test.domain", opsets=(1,), target="BridgeErrorTarget"
35+)
36+ 
37+@error.parse_node
38+def parse_error(node, target):
39+ del node, target
40+ raise RuntimeError("python callback failed")
41+returned = onnx_plugin(
42+ source="BridgeReturn", domain="test.domain", opsets=(1,), target="BridgeReturnTarget"
43+)
44+ 
45+@returned.parse_node
46+def parse_returned(node, target):
47+ del node, target
48+ return False
49+ 
50+priority = onnx_plugin(
51+ source="BridgePriority", domain="test.domain", opsets=(1,), target="BridgePriorityTarget"
52+)
53+ 
54+@priority.parse_node
55+def parse_priority(node, target):
56+ del node
57+ target.set_attr("source", "python")
58+)PY";
59+ 
60+class ScopedInMemoryPlugin {
61+ public:
62+ ScopedInMemoryPlugin() {
63+ py::gil_scoped_acquire gil;
64+ py::dict scope;
65+ scope["module_name"] = py::str("_ge_py_onnx_plugin_in_memory");
66+ scope["source"] = py::str(kInMemoryPluginSource);
67+ py::exec(R"PY(
68+import sys
69+import ge.onnx_plugin.bootstrap as _bootstrap
70+module = sys.modules.get(module_name)
71+if module is None:
72+ module = __import__("types").ModuleType(module_name)
73+ module.__file__ = "<memory-onnx-plugin>"
74+ exec(compile(source, module.__file__, "exec"), module.__dict__)
75+ sys.modules[module.__name__] = module
76+_bootstrap._mde_memory_original_loader = _bootstrap.load_plugins_from_env
77+_bootstrap.load_plugins_from_env = lambda *args, **kwargs: [module]
78+)PY",
79+ scope, scope);
80+ module_ = py::reinterpret_borrow<py::object>(scope["module"]);
81+ }
82+ 
83+ ~ScopedInMemoryPlugin() {
84+ try {
85+ py::gil_scoped_acquire gil;
86+ py::dict scope;
87+ scope["module"] = module_;
88+ py::exec(R"PY(
89+import sys
90+import ge.onnx_plugin.bootstrap as _bootstrap
91+_bootstrap.load_plugins_from_env = _bootstrap._mde_memory_original_loader
92+del _bootstrap._mde_memory_original_loader
93+)PY",
94+ scope, scope);
95+ (void)module_.release();
96+ } catch (...) {
97+ ADD_FAILURE() << "Failed to restore the ONNX plugin loader.";
98+ }
99+ }
100+ 
101+ private:
102+ py::object module_;
103+};
104+ 
105+} // namespace onnx_plugin_test
106+} // namespace ge
107+ 
108+#endif // TESTS_GE_UT_GE_ONNX_PLUGIN_TEST_HELPER_H_
@@ -12,6 +12,8 @@
12#define PARSER_TESTS_DEPENDS_MMPA_SRC_MMAP_STUB_H_12#define PARSER_TESTS_DEPENDS_MMPA_SRC_MMAP_STUB_H_
13 13 
14#include "mmpa/mmpa_api.h"14#include "mmpa/mmpa_api.h"
15+#include <cstring>
16+#include <cstdlib>
15#include <memory>17#include <memory>
16 18 
17#include <iostream>19#include <iostream>
@@ -31,7 +33,15 @@ class MmpaStubApi {
31 }33 }
32 34 
33 virtual INT32 mmRealPath(const CHAR *path, CHAR *realPath, INT32 realPathLen) {35 virtual INT32 mmRealPath(const CHAR *path, CHAR *realPath, INT32 realPathLen) {
34- return 0;36+ if ((path == nullptr) || (realPath == nullptr) || (realPathLen < MMPA_MAX_PATH)) {
37+ return EN_INVALID_PARAM;
38+ }
39+ CHAR resolved_path[PATH_MAX] = {};
40+ if (::realpath(path, resolved_path) == nullptr) {
41+ return EN_ERROR;
42+ }
43+ const auto ret = strncpy_s(realPath, realPathLen, resolved_path, std::strlen(resolved_path));
44+ return (ret == EOK) ? EN_OK : EN_ERROR;
35 }45 }
36};46};
37 47 
@@ -15,7 +15,9 @@ include_directories(${CMAKE_BINARY_DIR}/proto/ge)
15include_directories(${AIR_CODE_DIR}/parser)15include_directories(${AIR_CODE_DIR}/parser)
16include_directories(${AIR_CODE_DIR}/parser/parser)16include_directories(${AIR_CODE_DIR}/parser/parser)
17include_directories(${AIR_CODE_DIR}/parser/onnx)17include_directories(${AIR_CODE_DIR}/parser/onnx)
18+include_directories(${AIR_CODE_DIR}/base)
18include_directories(${AIR_CODE_DIR}/tests/parser)19include_directories(${AIR_CODE_DIR}/tests/parser)
20+include_directories(${AIR_CODE_DIR}/tests/ge/ut/ge)
19include_directories(${AIR_CODE_DIR}/inc/parser)21include_directories(${AIR_CODE_DIR}/inc/parser)
20include_directories(${AIR_CODE_DIR}/inc/parser/external)22include_directories(${AIR_CODE_DIR}/inc/parser/external)
21include_directories(${AIR_CODE_DIR}/inc/graph_metadef)23include_directories(${AIR_CODE_DIR}/inc/graph_metadef)
@@ -23,6 +25,7 @@ include_directories(${METADEF_DIR}/inc)
23include_directories(${METADEF_DIR}/inc/external)25include_directories(${METADEF_DIR}/inc/external)
24include_directories(${METADEF_DIR}/inc/register)26include_directories(${METADEF_DIR}/inc/register)
25include_directories(${METADEF_DIR}/pkg_inc)27include_directories(${METADEF_DIR}/pkg_inc)
28+include_directories(${pybind11_INCLUDE_DIR})
26 29 
27 30 
28set(PARSER_ST_FILES31set(PARSER_ST_FILES
@@ -41,6 +44,8 @@ add_executable(st_parser
41 ${PARSER_ST_FILES}44 ${PARSER_ST_FILES}
42)45)
43 46 
47+add_dependencies(st_parser ge_python_onnx_plugin_bridge install_ge_py_for_st_compile_test)
48+ 
44target_compile_options(st_parser PRIVATE49target_compile_options(st_parser PRIVATE
45 -g50 -g
46 -fno-access-control51 -fno-access-control
@@ -50,6 +55,7 @@ target_compile_options(st_parser PRIVATE
50target_compile_definitions(st_parser PRIVATE55target_compile_definitions(st_parser PRIVATE
51 google=ascend_private56 google=ascend_private
52 CMAKE_BINARY_DIR=\"${CMAKE_BINARY_DIR}\"57 CMAKE_BINARY_DIR=\"${CMAKE_BINARY_DIR}\"
58+ ONNX_PLUGIN_PY_INSTALL_DIR=\"${CMAKE_BINARY_DIR}/tests/ge/st/testcase/st_fusion_pass_py_install\"
53)59)
54 60 
55target_link_libraries(st_parser PRIVATE61target_link_libraries(st_parser PRIVATE
@@ -72,7 +78,10 @@ target_link_libraries(st_parser PRIVATE
72 GTestShared::gtest GTestShared::gtest_main parser_slog_stub ascend_protobuf c_sec -lrt -ldl78 GTestShared::gtest GTestShared::gtest_main parser_slog_stub ascend_protobuf c_sec -lrt -ldl
73 -Wl,--no-as-needed unified_dlog -Wl,--as-needed79 -Wl,--no-as-needed unified_dlog -Wl,--as-needed
74 # Workaround for undefined references80 # Workaround for undefined references
81+ ge_common
75 ge_common_base82 ge_common_base
83+ ge_python_embed
84+ pybind_options
76 ascendcl_stub85 ascendcl_stub
77 -lgcov86 -lgcov
78)87)
@@ -11,11 +11,14 @@
11#include <iostream>11#include <iostream>
12#include <gtest/gtest.h>12#include <gtest/gtest.h>
13 13 
14+#include "parser/onnx/python_onnx_plugin_bridge/onnx_plugin_bridge_loader.h"
15+ 
14using namespace std;16using namespace std;
15 17 
16int main(int argc, char **argv) {18int main(int argc, char **argv) {
17 testing::InitGoogleTest(&argc, argv);19 testing::InitGoogleTest(&argc, argv);
18 int ret = RUN_ALL_TESTS();20 int ret = RUN_ALL_TESTS();
21+ ge::UnloadOnnxPythonPluginBridge();
19 std::cout << "Finish parser st." << std::endl;22 std::cout << "Finish parser st." << std::endl;
20 return ret;23 return ret;
21}24}
@@ -9,13 +9,18 @@
9 */9 */
10 10 
11#include <gtest/gtest.h>11#include <gtest/gtest.h>
12+#include <cstdlib>
13+#include <dlfcn.h>
12#include <iostream>14#include <iostream>
15+#include <string>
13#include "parser/common/op_parser_factory.h"16#include "parser/common/op_parser_factory.h"
14#include "graph/operator_reg.h"17#include "graph/operator_reg.h"
18+#include "graph/utils/attr_utils.h"
15#include "graph/utils/graph_utils_ex.h"19#include "graph/utils/graph_utils_ex.h"
16#include "register/op_registry.h"20#include "register/op_registry.h"
17#include "parser/common/op_registration_tbe.h"21#include "parser/common/op_registration_tbe.h"
18#include "parser/onnx_parser.h"22#include "parser/onnx_parser.h"
23+#include "parser/onnx/python_onnx_plugin_bridge/onnx_plugin_bridge_loader.h"
19#include "st/parser_st_utils.h"24#include "st/parser_st_utils.h"
20#include "ge/ge_api_types.h"25#include "ge/ge_api_types.h"
21#include "depends/ops_stub/ops_stub.h"26#include "depends/ops_stub/ops_stub.h"
@@ -25,8 +30,14 @@
25#include "common/ge_common/ge_types.h"30#include "common/ge_common/ge_types.h"
26#include "parser/onnx/onnx_parser_internal.h"31#include "parser/onnx/onnx_parser_internal.h"
27#include "parser/onnx/onnx_file_constant_parser.h"32#include "parser/onnx/onnx_file_constant_parser.h"
33+#include "common/python_runtime/ge_python_runtime_manager.h"
34+#include "onnx_plugin_test_helper.h"
28 35 
29namespace ge {36namespace ge {
37+REG_OP(BridgeEluTarget).INPUT(x, TensorType::ALL()).OUTPUT(y, TensorType::ALL()).OP_END_FACTORY_REG(BridgeEluTarget);
38+ 
39+using onnx_plugin_test::ScopedInMemoryPlugin;
40+ 
30class STestOnnxParser : public testing::Test {41class STestOnnxParser : public testing::Test {
31 protected:42 protected:
32 void SetUp() {43 void SetUp() {
@@ -391,4 +402,92 @@ TEST_F(STestOnnxParser, onnx_parser_int4_const_int32_data) {
391 EXPECT_EQ(modelParser.ModelParseToGraph(model_proto, graph), SUCCESS);402 EXPECT_EQ(modelParser.ModelParseToGraph(model_proto, graph), SUCCESS);
392 VerifyInt4ConstantNode(graph);403 VerifyInt4ConstantNode(graph);
393}404}
405+ 
406+ge::onnx::ModelProto CreateBridgePluginModel() {
407+ ge::onnx::ModelProto plugin_model;
408+ auto *plugin_graph = plugin_model.mutable_graph();
409+ auto *plugin_input = plugin_graph->add_input();
410+ plugin_input->set_name("X");
411+ plugin_input->mutable_type()->mutable_tensor_type()->set_elem_type(ge::onnx::TensorProto_DataType_FLOAT);
412+ auto *plugin_output = plugin_graph->add_output();
413+ plugin_output->set_name("Y");
414+ plugin_output->mutable_type()->mutable_tensor_type()->set_elem_type(ge::onnx::TensorProto_DataType_FLOAT);
415+ auto *plugin_node = plugin_graph->add_node();
416+ plugin_node->set_name("bridge_elu");
417+ plugin_node->set_domain("test.domain");
418+ plugin_node->set_op_type("BridgeElu");
419+ plugin_node->add_input("X");
420+ plugin_node->add_output("Y");
421+ auto *plugin_alpha = plugin_node->add_attribute();
422+ plugin_alpha->set_name("alpha");
423+ plugin_alpha->set_type(ge::onnx::AttributeProto_AttributeType_FLOAT);
424+ plugin_alpha->set_f(0.5F);
425+ auto *plugin_opset = plugin_model.add_opset_import();
426+ plugin_opset->set_domain("test.domain");
427+ plugin_opset->set_version(1);
428+ return plugin_model;
429+}
430+void VerifyBridgePluginNode(const ge::Graph &plugin_result) {
431+ const auto plugin_compute_graph = ge::GraphUtilsEx::GetComputeGraph(plugin_result);
432+ ASSERT_NE(plugin_compute_graph, nullptr);
433+ const auto parsed_plugin_node = plugin_compute_graph->FindNode("bridge_elu");
434+ ASSERT_NE(parsed_plugin_node, nullptr);
435+ float parsed_alpha = 0.0F;
436+ ASSERT_TRUE(ge::AttrUtils::GetFloat(parsed_plugin_node->GetOpDesc(), "alpha", parsed_alpha));
437+ EXPECT_FLOAT_EQ(parsed_alpha, 0.5F);
438+}
439+void VerifyBridgePluginCallbacks() {
440+ ge::onnx::NodeProto node;
441+ node.set_op_type("test.domain::1::BridgeElu");
442+ auto *alpha = node.add_attribute();
443+ alpha->set_name("alpha");
444+ alpha->set_type(ge::onnx::AttributeProto_AttributeType_FLOAT);
445+ alpha->set_f(0.5F);
446+ Operator op("bridge_node", "BridgeEluTarget");
447+ const auto parse_elu = domi::OpRegistry::Instance()->GetParseParamFunc("BridgeEluTarget", node.op_type());
448+ ASSERT_NE(parse_elu, nullptr);
449+ EXPECT_EQ(parse_elu(&node, op), SUCCESS);
450+ EXPECT_NE(parse_elu(nullptr, op), SUCCESS);
451+ ge::onnx::AttributeProto wrong_msg;
452+ EXPECT_NE(parse_elu(&wrong_msg, op), SUCCESS);
453+ node.set_op_type("test.domain::1::BridgeError");
454+ const auto parse_error = domi::OpRegistry::Instance()->GetParseParamFunc("BridgeErrorTarget", node.op_type());
455+ ASSERT_NE(parse_error, nullptr);
456+ EXPECT_EQ(parse_error(&node, op), FAILED);
457+ node.set_op_type("test.domain::1::BridgeReturn");
458+ const auto parse_return = domi::OpRegistry::Instance()->GetParseParamFunc("BridgeReturnTarget", node.op_type());
459+ ASSERT_NE(parse_return, nullptr);
460+ EXPECT_NE(parse_return(&node, op), SUCCESS);
461+ 
462+ using InitBridgeFunc = Status (*)();
463+ const auto init_bridge = reinterpret_cast<InitBridgeFunc>(dlsym(RTLD_DEFAULT, "InitOnnxPluginBridge"));
464+ ASSERT_NE(init_bridge, nullptr);
465+ EXPECT_EQ(init_bridge(), SUCCESS);
466+ EXPECT_EQ(LoadOnnxPythonPluginBridge(), SUCCESS);
467+ using ResetBridgeFunc = void (*)();
468+ const auto reset_bridge = reinterpret_cast<ResetBridgeFunc>(dlsym(RTLD_DEFAULT, "ResetOnnxPluginBridgeState"));
469+ ASSERT_NE(reset_bridge, nullptr);
470+ reset_bridge();
471+ EXPECT_NE(parse_elu(&node, op), SUCCESS);
472+}
473+TEST_F(STestOnnxParser, onnx_python_plugin_bridge_parse) {
474+ ASSERT_EQ(setenv("PYTHONPATH", ONNX_PLUGIN_PY_INSTALL_DIR, 1), 0);
475+ ASSERT_EQ(GePythonRuntimeManager::Instance().EnsureReady(), SUCCESS);
476+ ScopedInMemoryPlugin in_memory_plugin;
477+ ASSERT_EQ(setenv("ASCEND_CUSTOM_OPP_PATH", "__ge_py_onnx_plugin_in_memory__", 1), 0);
478+ std::string case_dir = __FILE__;
479+ case_dir = case_dir.substr(0, case_dir.find_last_of("/"));
480+ std::map<ge::AscendString, ge::AscendString> parser_params;
481+ ge::Graph graph;
482+ ASSERT_EQ(ge::aclgrphParseONNX((case_dir + "/origin_models/onnx_conv2d.onnx").c_str(), parser_params, graph),
483+ GRAPH_SUCCESS);
484+ OnnxModelParser plugin_parser;
485+ ge::Graph plugin_result;
486+ ASSERT_EQ(plugin_parser.ModelParseToGraph(CreateBridgePluginModel(), plugin_result), SUCCESS);
487+ VerifyBridgePluginNode(plugin_result);
488+ VerifyBridgePluginCallbacks();
489+ 
490+ unsetenv("ASCEND_CUSTOM_OPP_PATH");
491+ unsetenv("PYTHONPATH");
492+}
394} // namespace ge493} // namespace ge
@@ -30,6 +30,7 @@
30#include "graph/utils/attr_utils.h"30#include "graph/utils/attr_utils.h"
31#include "graph/debug/ge_attr_define.h"31#include "graph/debug/ge_attr_define.h"
32#include "graph/utils/graph_utils.h"32#include "graph/utils/graph_utils.h"
33+#include "proto/onnx/ge_onnx.pb.h"
33 34 
34namespace ge {35namespace ge {
35class UtestOnnxParser : public testing::Test {36class UtestOnnxParser : public testing::Test {