已合并
feat: 接入ONNX Plugin Python bridge到真实ParseParamsFn #4455
gentle-knight创建于 16 天前
feat: 接入ONNX Plugin Python bridge到真实ParseParamsFn #4455
已合并
共 28 个文件变更+1199-95
| @@ -379,6 +379,7 @@ if ((CMAKE_BUILD_TYPE MATCHES GCOV) OR ENABLE_GE_DT) | |||
| 379 | ut_libge_symbol_infer_utest | 379 | ut_libge_symbol_infer_utest |
| 380 | ut_libge_others_utest | 380 | ut_libge_others_utest |
| 381 | ut_fusion_pass_executor_utest | 381 | ut_fusion_pass_executor_utest |
| 382 | + ut_onnx_plugin_bridge_utest | ||
| 382 | ut_libge_distinct_load_utest | 383 | ut_libge_distinct_load_utest |
| 383 | ut_libge_kernel_utest | 384 | ut_libge_kernel_utest |
| 384 | ut_libge_label_maker_utest | 385 | ut_libge_label_maker_utest |
| @@ -55,6 +55,7 @@ | |||
| 55 | 55 | ||
| 56 | 56 | ||
| 57 | 57 | ||
| 58 | + | ||
| 58 | 59 | ||
| 59 | namespace { | 60 | namespace { |
| 60 | using json = nlohmann::json; | 61 | using 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 | |||
| 36 | set(GE_PYTHON_PASS_BRIDGE_ABI_VERSION 1) | 36 | set(GE_PYTHON_PASS_BRIDGE_ABI_VERSION 1) |
| 37 | set(GE_PYTHON_RUNTIME_NATIVE_ABI_VERSION 1) | 37 | set(GE_PYTHON_RUNTIME_NATIVE_ABI_VERSION 1) |
| 38 | set(GE_PYTHON_CUSTOM_OP_BRIDGE_ABI_VERSION 1) | 38 | set(GE_PYTHON_CUSTOM_OP_BRIDGE_ABI_VERSION 1) |
| 39 | +set(GE_PYTHON_ONNX_PLUGIN_BRIDGE_ABI_VERSION 1) | ||
| 39 | set(GE_PYTHON_PASS_ARTIFACT_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/python_pass_artifact_manifest.json) | 40 | set(GE_PYTHON_PASS_ARTIFACT_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/python_pass_artifact_manifest.json) |
| 40 | set(GE_PYTHON_RUNTIME_ARTIFACT_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/python_runtime_artifact_manifest.json) | 41 | set(GE_PYTHON_RUNTIME_ARTIFACT_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/python_runtime_artifact_manifest.json) |
| 41 | set(GE_PYTHON_CUSTOM_OP_ARTIFACT_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/python_custom_op_artifact_manifest.json) | 42 | set(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) | ||
| 42 | set(GE_PYTHON_PASS_NATIVE_WHEEL | 44 | set(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) |
| 44 | set(GE_PYTHON_PASS_NATIVE_WHEEL ${GE_PYTHON_PASS_NATIVE_WHEEL} CACHE INTERNAL "GE python pass native artifact wheel") | 46 | set(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 | +) | ||
| 111 | set(GE_PYTHON_PASS_FALLBACK_CODEGEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/python_pass_fallback_codegen) | 125 | set(GE_PYTHON_PASS_FALLBACK_CODEGEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/python_pass_fallback_codegen) |
| 112 | set(GE_PYTHON_PASS_FALLBACK_CODEGEN_CONFIG | 126 | set(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_bindings | 201 | && ${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 | 202 | && ${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 | 203 | && ${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.json | 206 | && 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.so | 207 | && 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.json | 209 | && 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.so | 210 | && 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.so | 211 | && 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 | 212 | + && 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_codegen | 216 | && ${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_wrapper | 227 | 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_bridge | 228 | _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 | ) |
| 216 | add_custom_command( | 236 | add_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 | + | ||
| 25 | def load_and_get_onnx_plugin_descriptors() -> list: | 29 | def 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 | ||
| 15 | from __future__ import annotations | 15 | from __future__ import annotations |
| 16 | 16 | ||
| 17 | +from pathlib import Path | ||
| 17 | from importlib import import_module | 18 | from 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 | ||
| 21 | OnnxNode = _native.OnnxNode | 42 | OnnxNode = _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, setup | 13 | +from setuptools import find_namespace_packages, setup |
| 14 | 14 | ||
| 15 | setup( | 15 | setup( |
| 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 | 45 | ||
| 46 | 46 | ||
| 47 | 47 | ||
| 48 | + | ||
| 48 | 49 | ||
| 49 | 50 | ||
| 50 | 51 | ||
| @@ -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 Finalize | 410 | // 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 | ||
| 39 | set(SRC_LIST | 39 | set(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 | 61 | ||
| 62 | 62 | ||
| 63 | 63 | ||
| 64 | + | ||
| 64 | 65 | ||
| 65 | 66 | ||
| 66 | 67 | ||
| @@ -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 | 54 | ||
| 55 | 55 | ||
| 56 | 56 | ||
| 57 | + | ||
| 57 | 58 | ||
| 58 | namespace { | 59 | namespace { |
| 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) | |||
| 152 | add_subdirectory(parser/func_to_graph) | 152 | add_subdirectory(parser/func_to_graph) |
| 153 | add_subdirectory(parser/onnx) | 153 | add_subdirectory(parser/onnx) |
| 154 | 154 | ||
| 155 | -install(TARGETS _caffe_parser parser_common fmk_onnx_parser fmk_parser func2graph parser_headers | 155 | +install(TARGETS _caffe_parser parser_common fmk_onnx_parser ge_python_onnx_plugin_bridge fmk_parser func2graph parser_headers |
| 156 | EXPORT parser-targets | 156 | 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 ############ |
| 23 | add_library(fmk_onnx_parser SHARED ${SRC_LIST}) | 23 | add_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 | + | ||
| 25 | add_dependencies(fmk_onnx_parser | 72 | add_dependencies(fmk_onnx_parser |
| 26 | parser_protos | 73 | parser_protos |
| 27 | ) | 74 | ) |
| @@ -47,6 +94,7 @@ target_include_directories(fmk_onnx_parser PRIVATE | |||
| 47 | ${AIR_CODE_DIR}/inc/parser | 94 | ${AIR_CODE_DIR}/inc/parser |
| 48 | ${AIR_CODE_DIR}/parser/parser | 95 | ${AIR_CODE_DIR}/parser/parser |
| 49 | ${AIR_CODE_DIR}/parser/ | 96 | ${AIR_CODE_DIR}/parser/ |
| 97 | + ${AIR_CODE_DIR}/base | ||
| 50 | ) | 98 | ) |
| 51 | 99 | ||
| 52 | target_link_options(fmk_onnx_parser PRIVATE | 100 | target_link_options(fmk_onnx_parser PRIVATE |
| @@ -64,6 +112,7 @@ target_link_libraries(fmk_onnx_parser | |||
| 64 | c_sec | 112 | c_sec |
| 65 | parser_common | 113 | parser_common |
| 66 | graph | 114 | graph |
| 115 | + ge_common | ||
| 67 | unified_dlog | 116 | unified_dlog |
| 68 | -Wl,--as-needed | 117 | -Wl,--as-needed |
| 69 | json | 118 | json |
| @@ -33,6 +33,7 @@ | |||
| 33 | 33 | ||
| 34 | 34 | ||
| 35 | 35 | ||
| 36 | + | ||
| 36 | 37 | ||
| 37 | 38 | ||
| 38 | 39 | ||
| @@ -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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 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() { | ||
| 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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +extern "C" __attribute__((visibility("default"))) ge::Status InitOnnxPluginBridge(); | ||
| 17 | + | ||
| 18 | + | ||
| @@ -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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 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 | + | ||
| @@ -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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +namespace ge { | ||
| 17 | + | ||
| 18 | +__attribute__((visibility("default"))) Status LoadOnnxPythonPluginBridge(); | ||
| 19 | +__attribute__((visibility("default"))) void UnloadOnnxPythonPluginBridge(); | ||
| 20 | + | ||
| 21 | +} // namespace ge | ||
| 22 | + | ||
| 23 | + | ||
| @@ -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 | fi | 402 | fi |
| 402 | if [[ "X$ENABLE_RT2_UT" = "Xon" ]]; then | 403 | 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_utest | 1312 | # libge_label_maker_utest |
| 1213 | add_executable(ut_libge_label_maker_utest | 1313 | add_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 | - | ||
| 19 | import ge.graph as graph_api | 15 | import ge.graph as graph_api |
| 20 | import ge.onnx_plugin as onnx_plugin_api | 16 | import ge.onnx_plugin as onnx_plugin_api |
| 17 | +import pytest | ||
| 21 | from ge.graph import Operator | 18 | from ge.graph import Operator |
| 22 | from ge.onnx_plugin import onnx_plugin | 19 | from ge.onnx_plugin import onnx_plugin |
| 23 | -from ge.onnx_plugin._bridge import call_parse_node | 20 | +from ge.onnx_plugin._bridge import _InvalidParseNodeReturn, call_parse_node |
| 24 | from ge.onnx_plugin.registry import clear_registered_onnx_plugins | 21 | from ge.onnx_plugin.registry import clear_registered_onnx_plugins |
| 25 | from python_onnx_plugin_test_utils import FakeOperatorCapi | 22 | from 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 | - | ||
| 53 | def test_public_exports_match_pr1_support_matrix(): | 39 | def 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 | - | ||
| 78 | - def parse_elu(node, target): | ||
| 79 | - target.set_attr("alpha", node.attrs.get("alpha", 1.0)) | ||
| 80 | - | ||
| 81 | - | ||
| 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 | - | ||
| 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 | ||
| 135 | def test_call_parse_node_rejects_unknown_origin_without_creating_operator( | 59 | def 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, target | 99 | del node, target |
| 176 | return return_value | 100 | 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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 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 | + | ||
| @@ -12,6 +12,8 @@ | |||
| 12 | 12 | ||
| 13 | 13 | ||
| 14 | 14 | ||
| 15 | + | ||
| 16 | + | ||
| 15 | 17 | ||
| 16 | 18 | ||
| 17 | 19 | ||
| @@ -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) | |||
| 15 | include_directories(${AIR_CODE_DIR}/parser) | 15 | include_directories(${AIR_CODE_DIR}/parser) |
| 16 | include_directories(${AIR_CODE_DIR}/parser/parser) | 16 | include_directories(${AIR_CODE_DIR}/parser/parser) |
| 17 | include_directories(${AIR_CODE_DIR}/parser/onnx) | 17 | include_directories(${AIR_CODE_DIR}/parser/onnx) |
| 18 | +include_directories(${AIR_CODE_DIR}/base) | ||
| 18 | include_directories(${AIR_CODE_DIR}/tests/parser) | 19 | include_directories(${AIR_CODE_DIR}/tests/parser) |
| 20 | +include_directories(${AIR_CODE_DIR}/tests/ge/ut/ge) | ||
| 19 | include_directories(${AIR_CODE_DIR}/inc/parser) | 21 | include_directories(${AIR_CODE_DIR}/inc/parser) |
| 20 | include_directories(${AIR_CODE_DIR}/inc/parser/external) | 22 | include_directories(${AIR_CODE_DIR}/inc/parser/external) |
| 21 | include_directories(${AIR_CODE_DIR}/inc/graph_metadef) | 23 | include_directories(${AIR_CODE_DIR}/inc/graph_metadef) |
| @@ -23,6 +25,7 @@ include_directories(${METADEF_DIR}/inc) | |||
| 23 | include_directories(${METADEF_DIR}/inc/external) | 25 | include_directories(${METADEF_DIR}/inc/external) |
| 24 | include_directories(${METADEF_DIR}/inc/register) | 26 | include_directories(${METADEF_DIR}/inc/register) |
| 25 | include_directories(${METADEF_DIR}/pkg_inc) | 27 | include_directories(${METADEF_DIR}/pkg_inc) |
| 28 | +include_directories(${pybind11_INCLUDE_DIR}) | ||
| 26 | 29 | ||
| 27 | 30 | ||
| 28 | set(PARSER_ST_FILES | 31 | set(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 | + | ||
| 44 | target_compile_options(st_parser PRIVATE | 49 | target_compile_options(st_parser PRIVATE |
| 45 | -g | 50 | -g |
| 46 | -fno-access-control | 51 | -fno-access-control |
| @@ -50,6 +55,7 @@ target_compile_options(st_parser PRIVATE | |||
| 50 | target_compile_definitions(st_parser PRIVATE | 55 | target_compile_definitions(st_parser PRIVATE |
| 51 | google=ascend_private | 56 | 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 | ||
| 55 | target_link_libraries(st_parser PRIVATE | 61 | target_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 -ldl | 78 | GTestShared::gtest GTestShared::gtest_main parser_slog_stub ascend_protobuf c_sec -lrt -ldl |
| 73 | -Wl,--no-as-needed unified_dlog -Wl,--as-needed | 79 | -Wl,--no-as-needed unified_dlog -Wl,--as-needed |
| 74 | # Workaround for undefined references | 80 | # Workaround for undefined references |
| 81 | + ge_common | ||
| 75 | ge_common_base | 82 | ge_common_base |
| 83 | + ge_python_embed | ||
| 84 | + pybind_options | ||
| 76 | ascendcl_stub | 85 | ascendcl_stub |
| 77 | -lgcov | 86 | -lgcov |
| 78 | ) | 87 | ) |
| @@ -11,11 +11,14 @@ | |||
| 11 | 11 | ||
| 12 | 12 | ||
| 13 | 13 | ||
| 14 | + | ||
| 15 | + | ||
| 14 | using namespace std; | 16 | using namespace std; |
| 15 | 17 | ||
| 16 | int main(int argc, char **argv) { | 18 | int 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 | 11 | ||
| 12 | + | ||
| 13 | + | ||
| 12 | 14 | ||
| 15 | + | ||
| 13 | 16 | ||
| 14 | 17 | ||
| 18 | + | ||
| 15 | 19 | ||
| 16 | 20 | ||
| 17 | 21 | ||
| 18 | 22 | ||
| 23 | + | ||
| 19 | 24 | ||
| 20 | 25 | ||
| 21 | 26 | ||
| @@ -25,8 +30,14 @@ | |||
| 25 | 30 | ||
| 26 | 31 | ||
| 27 | 32 | ||
| 33 | + | ||
| 34 | + | ||
| 28 | 35 | ||
| 29 | namespace ge { | 36 | namespace 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 | + | ||
| 30 | class STestOnnxParser : public testing::Test { | 41 | class 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 ge | 493 | } // namespace ge |
| @@ -30,6 +30,7 @@ | |||
| 30 | 30 | ||
| 31 | 31 | ||
| 32 | 32 | ||
| 33 | + | ||
| 33 | 34 | ||
| 34 | namespace ge { | 35 | namespace ge { |
| 35 | class UtestOnnxParser : public testing::Test { | 36 | class UtestOnnxParser : public testing::Test { |
不需要finialize吗