已合并
feat: 支持 Python 自定义算子原型与实现桥接注册 #4320
lfz2812创建于 16 天前
feat: 支持 Python 自定义算子原型与实现桥接注册 #4320
已合并
共 31 个文件变更+3045-444
| @@ -21,7 +21,11 @@ from typing import Dict, Optional | |||
| 21 | from ._ir_types import InputType, OutputType | 21 | from ._ir_types import InputType, OutputType |
| 22 | from ._signature import _get_runtime_attr_spec, _validate_args_signature | 22 | from ._signature import _get_runtime_attr_spec, _validate_args_signature |
| 23 | from .base import EagerOpExecutionContext | 23 | from .base import EagerOpExecutionContext |
| 24 | -from .bootstrap import get_registered_op_impls, load_custom_op_plugins | 24 | +from .bootstrap import ( |
| 25 | + get_registered_op_impls, | ||
| 26 | + get_registered_op_protos, | ||
| 27 | + load_custom_op_plugins, | ||
| 28 | +) | ||
| 25 | from .context import _declare_launch_args_ctx_scope, _execute_ctx_scope | 29 | from .context import _declare_launch_args_ctx_scope, _execute_ctx_scope |
| 26 | from .registry import ( | 30 | from .registry import ( |
| 27 | INTERFACE_ANNOTATED_ARGS, | 31 | INTERFACE_ANNOTATED_ARGS, |
| @@ -46,6 +50,14 @@ def load_and_get_op_impl_descriptors() -> list: | |||
| 46 | return get_registered_op_impls() | 50 | return get_registered_op_impls() |
| 47 | 51 | ||
| 48 | 52 | ||
| 53 | +def load_and_get_op_descriptors() -> dict: | ||
| 54 | + load_custom_op_plugins() | ||
| 55 | + return { | ||
| 56 | + "protos": get_registered_op_protos(), | ||
| 57 | + "impls": get_registered_op_impls(), | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + | ||
| 49 | def _get_holder(instance_id: str) -> _OpImplHolder: | 61 | def _get_holder(instance_id: str) -> _OpImplHolder: |
| 50 | with _HOLDER_LOCK: | 62 | with _HOLDER_LOCK: |
| 51 | holder = _OP_IMPL_HOLDERS.get(instance_id) | 63 | holder = _OP_IMPL_HOLDERS.get(instance_id) |
| @@ -41,6 +41,7 @@ | |||
| 41 | 41 | ||
| 42 | 42 | ||
| 43 | 43 | ||
| 44 | + | ||
| 44 | 45 | ||
| 45 | 46 | ||
| 46 | 47 | ||
| @@ -1533,44 +1534,6 @@ ge::Status GeSessionGraphDebugJSONPrint(ge::Session &session, uint32_t graph_id, | |||
| 1533 | ge::Status GetRegisteredIrDef(const char *op_type, std::vector<std::pair<ge::AscendString, ge::AscendString>> &inputs, | 1534 | ge::Status GetRegisteredIrDef(const char *op_type, std::vector<std::pair<ge::AscendString, ge::AscendString>> &inputs, |
| 1534 | std::vector<std::pair<ge::AscendString, ge::AscendString>> &outputs, | 1535 | std::vector<std::pair<ge::AscendString, ge::AscendString>> &outputs, |
| 1535 | std::vector<std::pair<ge::AscendString, ge::AscendString>> &attrs) { | 1536 | std::vector<std::pair<ge::AscendString, ge::AscendString>> &attrs) { |
| 1536 | - GE_ASSERT_NOTNULL(op_type); | 1537 | + return GetRegisteredIrDefFromGraph(op_type, inputs, outputs, attrs); |
| 1537 | - const auto op = ge::OperatorFactory::CreateOperator("_", op_type); | ||
| 1538 | - GE_WARN_ASSERT(!op.IsEmpty(), "No operator found for type: %s", op_type); | ||
| 1539 | - const auto desc = ge::OpDescUtils::GetOpDescFromOperator(op); | ||
| 1540 | - | ||
| 1541 | - static const auto kInputTypeString = []() { | ||
| 1542 | - std::map<ge::IrInputType, ge::AscendString> typeStr; | ||
| 1543 | - typeStr[ge::IrInputType::kIrInputRequired] = "required"; | ||
| 1544 | - typeStr[ge::IrInputType::kIrInputOptional] = "optional"; | ||
| 1545 | - typeStr[ge::IrInputType::kIrInputDynamic] = "dynamic"; | ||
| 1546 | - return typeStr; | ||
| 1547 | - }(); | ||
| 1548 | - | ||
| 1549 | - static const auto kOutputTypeString = []() { | ||
| 1550 | - std::map<ge::IrOutputType, ge::AscendString> typeStr; | ||
| 1551 | - typeStr[ge::IrOutputType::kIrOutputRequired] = "required"; | ||
| 1552 | - typeStr[ge::IrOutputType::kIrOutputDynamic] = "dynamic"; | ||
| 1553 | - return typeStr; | ||
| 1554 | - }(); | ||
| 1555 | - | ||
| 1556 | - GE_ASSERT_NOTNULL(desc, "Failed to get OpDesc from operator: %s", op_type); | ||
| 1557 | - for (const auto &name2type : desc->GetIrInputs()) { | ||
| 1558 | - auto iter = kInputTypeString.find(name2type.second); | ||
| 1559 | - GE_ASSERT(iter != kInputTypeString.end(), "Unknown input type: %d for operator: %s", name2type.second, op_type); | ||
| 1560 | - inputs.emplace_back(ConvertToAscendString(name2type.first), iter->second); | ||
| 1561 | - } | ||
| 1562 | - for (const auto &name2type : desc->GetIrOutputs()) { | ||
| 1563 | - auto iter = kOutputTypeString.find(name2type.second); | ||
| 1564 | - GE_ASSERT(iter != kOutputTypeString.end(), "Unknown output type: %d for operator: %s", name2type.second, op_type); | ||
| 1565 | - outputs.emplace_back(ConvertToAscendString(name2type.first), iter->second); | ||
| 1566 | - } | ||
| 1567 | - | ||
| 1568 | - std::map<ge::AscendString, ge::AscendString> attrs_and_types; | ||
| 1569 | - GE_ASSERT_GRAPH_SUCCESS(op.GetAllIrAttrNamesAndTypes(attrs_and_types), | ||
| 1570 | - "Failed to get attr names and types for operator: %s", op_type); | ||
| 1571 | - for (const auto &attr : desc->GetIrAttrNames()) { | ||
| 1572 | - attrs.emplace_back(ConvertToAscendString(attr), attrs_and_types[ConvertToAscendString(attr)]); | ||
| 1573 | - } | ||
| 1574 | - return ge::SUCCESS; | ||
| 1575 | } | 1538 | } |
| 1576 | } | 1539 | } |
| @@ -38,6 +38,7 @@ | |||
| 38 | 38 | ||
| 39 | 39 | ||
| 40 | 40 | ||
| 41 | + | ||
| 41 | 42 | ||
| 42 | 43 | ||
| 43 | 44 | ||
| @@ -1163,44 +1164,6 @@ bool IsIrRepSupport(const char *rep) { | |||
| 1163 | ge::Status GetRegisteredIrDef(const char *op_type, std::vector<std::pair<ge::AscendString, ge::AscendString>> &inputs, | 1164 | ge::Status GetRegisteredIrDef(const char *op_type, std::vector<std::pair<ge::AscendString, ge::AscendString>> &inputs, |
| 1164 | std::vector<std::pair<ge::AscendString, ge::AscendString>> &outputs, | 1165 | std::vector<std::pair<ge::AscendString, ge::AscendString>> &outputs, |
| 1165 | std::vector<std::pair<ge::AscendString, ge::AscendString>> &attrs) { | 1166 | std::vector<std::pair<ge::AscendString, ge::AscendString>> &attrs) { |
| 1166 | - GE_ASSERT_NOTNULL(op_type); | 1167 | + return GetRegisteredIrDefFromGraph(op_type, inputs, outputs, attrs); |
| 1167 | - const auto op = ge::OperatorFactory::CreateOperator("_", op_type); | ||
| 1168 | - GE_WARN_ASSERT(!op.IsEmpty(), "No operator found for type: %s", op_type); | ||
| 1169 | - const auto desc = ge::OpDescUtils::GetOpDescFromOperator(op); | ||
| 1170 | - | ||
| 1171 | - static const auto kInputTypeString = []() { | ||
| 1172 | - std::map<ge::IrInputType, ge::AscendString> typeStr; | ||
| 1173 | - typeStr[ge::IrInputType::kIrInputRequired] = "required"; | ||
| 1174 | - typeStr[ge::IrInputType::kIrInputOptional] = "optional"; | ||
| 1175 | - typeStr[ge::IrInputType::kIrInputDynamic] = "dynamic"; | ||
| 1176 | - return typeStr; | ||
| 1177 | - }(); | ||
| 1178 | - | ||
| 1179 | - static const auto kOutputTypeString = []() { | ||
| 1180 | - std::map<ge::IrOutputType, ge::AscendString> typeStr; | ||
| 1181 | - typeStr[ge::IrOutputType::kIrOutputRequired] = "required"; | ||
| 1182 | - typeStr[ge::IrOutputType::kIrOutputDynamic] = "dynamic"; | ||
| 1183 | - return typeStr; | ||
| 1184 | - }(); | ||
| 1185 | - | ||
| 1186 | - GE_ASSERT_NOTNULL(desc, "Failed to get OpDesc from operator: %s", op_type); | ||
| 1187 | - for (const auto &name2type : desc->GetIrInputs()) { | ||
| 1188 | - auto iter = kInputTypeString.find(name2type.second); | ||
| 1189 | - GE_ASSERT(iter != kInputTypeString.end(), "Unknown input type: %d for operator: %s", name2type.second, op_type); | ||
| 1190 | - inputs.emplace_back(ConvertToAscendString(name2type.first), iter->second); | ||
| 1191 | - } | ||
| 1192 | - for (const auto &name2type : desc->GetIrOutputs()) { | ||
| 1193 | - auto iter = kOutputTypeString.find(name2type.second); | ||
| 1194 | - GE_ASSERT(iter != kOutputTypeString.end(), "Unknown output type: %d for operator: %s", name2type.second, op_type); | ||
| 1195 | - outputs.emplace_back(ConvertToAscendString(name2type.first), iter->second); | ||
| 1196 | - } | ||
| 1197 | - | ||
| 1198 | - std::map<ge::AscendString, ge::AscendString> attrs_and_types; | ||
| 1199 | - GE_ASSERT_GRAPH_SUCCESS(op.GetAllIrAttrNamesAndTypes(attrs_and_types), | ||
| 1200 | - "Failed to get attr names and types for operator: %s", op_type); | ||
| 1201 | - for (const auto &attr : desc->GetIrAttrNames()) { | ||
| 1202 | - attrs.emplace_back(ConvertToAscendString(attr), attrs_and_types[ConvertToAscendString(attr)]); | ||
| 1203 | - } | ||
| 1204 | - return ge::SUCCESS; | ||
| 1205 | } | 1168 | } |
| 1206 | } | 1169 | } |
| @@ -821,7 +821,7 @@ Note: `EagerOpExecutionContext` and `AnnotatedArgsContext` are provided by `_ge_ | |||
| 821 | 821 | ||
| 822 | #### Module Positioning | 822 | #### Module Positioning |
| 823 | 823 | ||
| 824 | -The long-term goal of the Python custom operator is to support users in describing custom operator prototypes and implementing custom operator capabilities in Python. Callable `execute` and `declare_launch_args` methods are now reflected from the implementation class to detect execution capability and declarative static-graph address-refresh capability, respectively. User classes are not required to inherit from `BaseCustomOp` or `EagerExecuteOp`, while existing inheritance-based implementations remain compatible. The execution entry supports both the legacy `execute(ctx)` form and a schema-bound form whose inputs and attributes are bound from canonical IR in declaration order. | 824 | +The long-term goal of the Python custom operator is to support users in describing custom operator prototypes and implementing custom operator capabilities in Python. Callable `execute` and `declare_launch_args` methods are now reflected from the implementation class to detect execution capability and declarative static-graph address-refresh capability, respectively. User classes are not required to inherit from `BaseCustomOp` or `EagerExecuteOp`, while existing inheritance-based implementations remain compatible. The execution entry supports both the legacy `execute(ctx)` form and a schema-bound form whose inputs and attributes are bound from canonical IR in declaration order. The current stage also registers Python prototypes with `OperatorFactory`, but does not invoke Python `infer_meta` or provide compile-time or RT2 Meta inference. |
| 825 | 825 | ||
| 826 | #### Runtime Native Artifact Selection | 826 | #### Runtime Native Artifact Selection |
| 827 | 827 | ||
| @@ -965,6 +965,14 @@ Within one AnnotatedArgs task-plan lifecycle, `declare_launch_args` is invoked e | |||
| 965 | - `bootstrap.py` scans paths and dynamically loads Python modules | 965 | - `bootstrap.py` scans paths and dynamically loads Python modules |
| 966 | - Supports single `.py` files, `.py` files in plain directories, and Python packages containing `__init__.py` | 966 | - Supports single `.py` files, `.py` files in plain directories, and Python packages containing `__init__.py` |
| 967 | 967 | ||
| 968 | +**Prototype registration**: | ||
| 969 | + | ||
| 970 | +- `register_op` collects required, optional, and dynamic inputs, 12 attribute types, required and dynamic outputs, and `mutates_args`. | ||
| 971 | +- In one loading transaction, the bridge first registers Python prototype creators and collects the effective canonical IR, then registers implementation runtime entries and Adapter creators. | ||
| 972 | +- Python proto-only, Python proto with implementation, C++ proto with Python implementation, and prototype-free legacy implementations are supported. A schema-bound implementation without a prototype is rejected. | ||
| 973 | +- A Python prototype may replace a built-in prototype with the same name. Registration fails when a loaded C++ or Python custom operator already owns that name. | ||
| 974 | +- A failed batch is rolled back in reverse order: Adapter creators, implementation runtime entries, and prototype creators. Unloading removes only objects owned by the current loader. | ||
| 975 | + | ||
| 968 | **Usage sample**: | 976 | **Usage sample**: |
| 969 | 977 | ||
| 970 | ```python | 978 | ```python |
| @@ -19,7 +19,7 @@ The long-term goal of Python custom operators is to let users describe custom op | |||
| 19 | - Python users implement the compile-time `AnnotatedArgsOp` callback through `declare_launch_args` and declare kernel launch arguments with `AnnotatedArgsContext`, `AnnotatedKernelArgs`, and `AnnotatedKernelLaunchInfo`. | 19 | - Python users implement the compile-time `AnnotatedArgsOp` callback through `declare_launch_args` and declare kernel launch arguments with `AnnotatedArgsContext`, `AnnotatedKernelArgs`, and `AnnotatedKernelLaunchInfo`. |
| 20 | - `ge.runtime` provides runtime data structures required by the context for return values or input parameters, such as `Tensor`, `StorageShape`, `StorageFormat`, `Shape`, and `TensorPlacement`. | 20 | - `ge.runtime` provides runtime data structures required by the context for return values or input parameters, such as `Tensor`, `StorageShape`, `StorageFormat`, `Shape`, and `TensorPlacement`. |
| 21 | 21 | ||
| 22 | -V2 plans to add Python prototype and Meta inference capabilities on top of the V1 execution path. | 22 | +V2 extends the V1 execution path with Python prototype and Meta inference capabilities. The current stage provides Python prototype creators, Adapter registration transactions, and ownership management, but does not yet invoke Python `infer_meta`. |
| 23 | 23 | ||
| 24 | In V2, the Python function decorated with `register_op` performs Meta inference and is referred to as `infer_meta` throughout this document; the function itself does not have to be named `infer_meta`. Following the operator prototype, it receives input `TensorDesc` objects, including optional and dynamic inputs, together with attribute values, and returns one or more `TensorDesc` objects describing output shape and data type. It neither reads input Tensor data nor executes the operator kernel. | 24 | In V2, the Python function decorated with `register_op` performs Meta inference and is referred to as `infer_meta` throughout this document; the function itself does not have to be named `infer_meta`. Following the operator prototype, it receives input `TensorDesc` objects, including optional and dynamic inputs, together with attribute values, and returns one or more `TensorDesc` objects describing output shape and data type. It neither reads input Tensor data nor executes the operator kernel. |
| 25 | 25 | ||
| @@ -71,6 +71,7 @@ The actual module boundaries are as follows: | |||
| 71 | | Runtime loader | `runtime/custom_op/custom_op_loader.cc` | Unified loading of C++ custom ops and Python custom ops | | 71 | | Runtime loader | `runtime/custom_op/custom_op_loader.cc` | Unified loading of C++ custom ops and Python custom ops | |
| 72 | | Bridge loader | `runtime/custom_op/python_custom_op_bridge_loader.cc` | Artifact selection, loading `libge_python_custom_op_bridge.so`, and creator registration | | 72 | | Bridge loader | `runtime/custom_op/python_custom_op_bridge_loader.cc` | Artifact selection, loading `libge_python_custom_op_bridge.so`, and creator registration | |
| 73 | | Pybind bridge | `runtime/custom_op/python_custom_op_pybind_bridge.cc` | Importing the Python bridge module, creating holders, and calling back `execute` / `declare_launch_args` | | 73 | | Pybind bridge | `runtime/custom_op/python_custom_op_pybind_bridge.cc` | Importing the Python bridge module, creating holders, and calling back `execute` / `declare_launch_args` | |
| 74 | +| Proto runtime | `runtime/custom_op/python_custom_op_proto.*` | Deep-copying C POD prototypes and registering `OperatorFactory` creators | | ||
| 74 | | Adapter | `runtime/custom_op/python_custom_op_adapter.*` | Serving as a C++ `BaseCustomOp` instance to access the existing runtime | | 75 | | Adapter | `runtime/custom_op/python_custom_op_adapter.*` | Serving as a C++ `BaseCustomOp` instance to access the existing runtime | |
| 75 | | Capability helper | `inc/graph_metadef/graph/custom_op/` | `CustomOpCapability` and `CustomOpCast<T>` | | 76 | | Capability helper | `inc/graph_metadef/graph/custom_op/` | `CustomOpCapability` and `CustomOpCast<T>` | |
| 76 | 77 | ||
| @@ -80,6 +81,7 @@ V1 functions include: | |||
| 80 | 81 | ||
| 81 | - `@register_op_impl(op_type=...)` registers a Python custom operator implementation. | 82 | - `@register_op_impl(op_type=...)` registers a Python custom operator implementation. |
| 82 | - `@register_op(op_type=..., mutates_args=...)` collects a custom operator prototype from a Python function signature. | 83 | - `@register_op(op_type=..., mutates_args=...)` collects a custom operator prototype from a Python function signature. |
| 84 | +- The bridge registers Python prototypes as `OperatorFactory` creators and collects canonical IR from the effective creators. It does not invoke `infer_meta` at this stage. | ||
| 83 | - `register_op_impl` reflects callable `execute` and `declare_launch_args` methods and declares the corresponding capabilities without requiring inheritance from `BaseCustomOp`, `EagerExecuteOp`, or `AnnotatedArgsOp`. | 85 | - `register_op_impl` reflects callable `execute` and `declare_launch_args` methods and declares the corresponding capabilities without requiring inheritance from `BaseCustomOp`, `EagerExecuteOp`, or `AnnotatedArgsOp`. |
| 84 | - The legacy `execute(self, ctx)` form directly receives an `EagerOpExecutionContext`; the schema-bound form receives inputs and attributes assembled from canonical IR. | 86 | - The legacy `execute(self, ctx)` form directly receives an `EagerOpExecutionContext`; the schema-bound form receives inputs and attributes assembled from canonical IR. |
| 85 | - `EagerOpExecutionContext` supports input and output tensor queries, dynamic input instance counts, runtime attribute access, output and workspace allocation, and stream retrieval. | 87 | - `EagerOpExecutionContext` supports input and output tensor queries, dynamic input instance counts, runtime attribute access, output and workspace allocation, and stream retrieval. |
| @@ -96,6 +98,8 @@ V1 functions include: | |||
| 96 | - The return value of the Python `execute` method is not used as a status code. A normal return indicates success, and an exception indicates failure. | 98 | - The return value of the Python `execute` method is not used as a status code. A normal return indicates success, and an exception indicates failure. |
| 97 | - The Python custom op currently declares `EagerExecuteOp` and `AnnotatedArgsOp` capabilities. Other C++ capability interfaces are retained as overrides in the adapter but are treated as unsupported. | 99 | - The Python custom op currently declares `EagerExecuteOp` and `AnnotatedArgsOp` capabilities. Other C++ capability interfaces are retained as overrides in the adapter but are treated as unsupported. |
| 98 | - The schema-bound form depends on canonical IR from the existing operator prototype. While loading descriptors, the bridge collects canonical IR and calls `validate_op_impl_descriptor` before holder creation or any business callback to validate schema-bound signatures once. `execute` validates IR inputs and attributes, excludes IR outputs from its parameters, and does not constrain its return annotation or return value. `declare_launch_args` validates inputs, outputs, and attributes and requires a `None` return annotation. Runtime callbacks only assemble arguments and invoke business methods; they do not validate signatures, and validation state does not enter the holder lifecycle. | 100 | - The schema-bound form depends on canonical IR from the existing operator prototype. While loading descriptors, the bridge collects canonical IR and calls `validate_op_impl_descriptor` before holder creation or any business callback to validate schema-bound signatures once. `execute` validates IR inputs and attributes, excludes IR outputs from its parameters, and does not constrain its return annotation or return value. `declare_launch_args` validates inputs, outputs, and attributes and requires a `None` return annotation. Runtime callbacks only assemble arguments and invoke business methods; they do not validate signatures, and validation state does not enter the holder lifecycle. |
| 101 | +- Cross-SO prototype and Adapter descriptors are synchronously borrowed C POD views. Runtime callbacks must finish validation and deep copying before returning. | ||
| 102 | +- A Python prototype may replace a built-in prototype. If `CustomOpFactory` already contains a C++ or Python custom operator with the same name, registration reports a custom-operator conflict. | ||
| 99 | - A schema-bound callback obtains the current context through `get_execute_ctx()`. The binding is valid only in the dynamic scope of that callback. | 103 | - A schema-bound callback obtains the current context through `get_execute_ctx()`. The binding is valid only in the dynamic scope of that callback. |
| 100 | - The Python custom op native/bridge is related to the Python ABI at build time. Cross-Python minor version compatibility is not guaranteed. | 104 | - The Python custom op native/bridge is related to the Python ABI at build time. Cross-Python minor version compatibility is not guaranteed. |
| 101 | - The bridge C ABI remains v1. The `execute` and `declare_launch_args` callbacks pass only the holder and corresponding context. The bridge queries canonical IR through the public run-package API instead of passing a private ABI projection. | 105 | - The bridge C ABI remains v1. The `execute` and `declare_launch_args` callbacks pass only the holder and corresponding context. The bridge queries canonical IR through the public run-package API instead of passing a private ABI projection. |
| @@ -325,14 +329,16 @@ Python custom op loading is managed by `runtime/custom_op` to avoid direct Pytho | |||
| 325 | - `NeedLoadPythonCustomOps()` returns true only when Python files or packages are found under `ASCEND_CUSTOM_OPP_PATH`. | 329 | - `NeedLoadPythonCustomOps()` returns true only when Python files or packages are found under `ASCEND_CUSTOM_OPP_PATH`. |
| 326 | - `LoadPythonCustomOps()` resolves the loaded Python runtime key and selects the bridge/native artifact under `custom_op/python_custom_op_artifacts/<python_tag>-<platform>`. | 330 | - `LoadPythonCustomOps()` resolves the loaded Python runtime key and selects the bridge/native artifact under `custom_op/python_custom_op_artifacts/<python_tag>-<platform>`. |
| 327 | - `libge_python_custom_op_bridge.so` exposes C ABI v1 through `GeGetPythonCustomOpBridgeApi()`. | 331 | - `libge_python_custom_op_bridge.so` exposes C ABI v1 through `GeGetPythonCustomOpBridgeApi()`. |
| 328 | -- The bridge imports `_ge_custom_op_native` and `ge.custom_op._bridge`, registers descriptors, and creates Python holders for each adapter. | 332 | +- The bridge imports `_ge_custom_op_native` and `ge.custom_op._bridge` and obtains one prototype/implementation snapshot. It registers all prototypes first, then validates and registers Adapters. |
| 333 | +- `CustomOpLoader::LoadCustomOps()` records whether Python custom ops are loaded, so repeated lifecycle load requests return success without invoking the bridge registration entry again. The dynamic `LoadPythonCustomOpsIfNeeded()` path intentionally does not use this flag, allowing newly added Python custom op paths to be discovered during runtime. The lower-level `LoadPythonCustomOps()` function performs one bridge registration attempt; callers are responsible for invoking `UnloadPythonCustomOps()` after a failed attempt so that partial registrations are cleaned up. | ||
| 334 | +- `UnloadPythonCustomOps()` removes registered Adapter creators, clears the Python custom-op runtime registry in one operation, and then removes registered proto creators. It does not perform per-entry runtime unregistration or maintain pending-cleanup state in the bridge loader. | ||
| 329 | - `UnloadCustomOps()` uses an `active_users_` reference count to manage the lifecycle: each `LoadCustomOps()` increments the count by 1, each `UnloadCustomOps()` decrements it by 1, and Python custom ops are only unloaded (holders/registry cleaned up and bridge closed) when the count reaches zero. `ShutdownCustomOpsForProcess()` is retained as a compatibility wrapper that internally calls `UnloadCustomOps()`. | 335 | - `UnloadCustomOps()` uses an `active_users_` reference count to manage the lifecycle: each `LoadCustomOps()` increments the count by 1, each `UnloadCustomOps()` decrements it by 1, and Python custom ops are only unloaded (holders/registry cleaned up and bridge closed) when the count reaches zero. `ShutdownCustomOpsForProcess()` is retained as a compatibility wrapper that internally calls `UnloadCustomOps()`. |
| 330 | 336 | ||
| 331 | **Output** | 337 | **Output** |
| 332 | 338 | ||
| 333 | -- Python descriptors are registered as `CustomOpFactory` creators. | 339 | +- Python prototypes are registered as `OperatorFactory` creators, and Python implementations are registered as `CustomOpFactory` creators. |
| 334 | - When the adapter is destructed, the Python holder is destroyed and the runtime registry entry is released. | 340 | - When the adapter is destructed, the Python holder is destroyed and the runtime registry entry is released. |
| 335 | -- The runtime registry does not allow unregistration while active adapters exist. | 341 | +- Individual runtime registry unregistration remains guarded by active adapters; process unload removes Adapter creators first and clears the registry in bulk. |
| 336 | 342 | ||
| 337 | ### 3.3 Non-Functional Requirements | 343 | ### 3.3 Non-Functional Requirements |
| 338 | 344 | ||
| @@ -450,21 +456,21 @@ The Python-version-sensitive bridge uses the official `GetRegisteredIrDef` signa | |||
| 450 | 456 | ||
| 451 | Runtime symbol resolution is used because `libge_runner.so`/`libge_runner_v2.so` already depend on `custom_op_runtime`, while the bridge is dynamically loaded by `custom_op_runtime`; directly linking the bridge back to a runner would create a reverse SO dependency. The function signature and data types are still governed by official public run-package headers, with only symbol binding deferred until after the bridge is loaded. | 457 | Runtime symbol resolution is used because `libge_runner.so`/`libge_runner_v2.so` already depend on `custom_op_runtime`, while the bridge is dynamically loaded by `custom_op_runtime`; directly linking the bridge back to a runner would create a reverse SO dependency. The function signature and data types are still governed by official public run-package headers, with only symbol binding deferred until after the bridge is loaded. |
| 452 | 458 | ||
| 453 | -#### PythonCustomOpDescriptor | 459 | +#### PythonCustomOpProto and PythonCustomOpAdapterDescriptor |
| 454 | 460 | ||
| 455 | -The C++ runtime uses `PythonCustomOpDescriptor`: | 461 | +The C++ runtime stores an owning prototype and an Adapter descriptor separately; the Adapter descriptor stores only the implementation key: |
| 456 | 462 | ||
| 457 | ```cpp | 463 | ```cpp |
| 458 | -struct PythonCustomOpDescriptor { | 464 | +struct PythonCustomOpAdapterDescriptor { |
| 459 | - std::string descriptor_key; | ||
| 460 | std::string op_type; | 465 | std::string op_type; |
| 466 | + std::string impl_descriptor_key; | ||
| 461 | CustomOpCapabilityMask capabilities{0U}; | 467 | CustomOpCapabilityMask capabilities{0U}; |
| 462 | }; | 468 | }; |
| 463 | ``` | 469 | ``` |
| 464 | 470 | ||
| 465 | -#### PythonCustomOpCallbacks | 471 | +#### PythonCustomOpAdapterCallbacks |
| 466 | 472 | ||
| 467 | -The bridge registers create/destroy/execute/declare_launch_args callbacks to the runtime. `IsValid()` accepts `kEagerExecute`, `kAnnotatedArgs`, or both; it requires create/destroy and verifies execute/declare_launch_args according to the capability mask. | 473 | +The bridge registers create/destroy/execute/declare_launch_args callbacks to the runtime. Schema-bound signature validation is not a C++ callback; the bridge calls `validate_op_impl_descriptor` internally before registering an Adapter. `IsValid()` accepts `kEagerExecute`, `kAnnotatedArgs`, or both; it requires create/destroy and verifies execute/declare_launch_args according to the capability mask. |
| 468 | 474 | ||
| 469 | #### BorrowedEagerOpExecutionContext | 475 | #### BorrowedEagerOpExecutionContext |
| 470 | 476 | ||
| @@ -481,6 +487,7 @@ The native binding holds a `gert::AnnotatedArgsContext *` and an independent val | |||
| 481 | - **Capability reflection**: The registry applies `getattr` and `callable` checks to the implementation class, maps `execute` to `eager_execute`, and maps `declare_launch_args` to `annotated_args`. The inheritance hierarchy of the Python user class does not participate in capability detection. | 487 | - **Capability reflection**: The registry applies `getattr` and `callable` checks to the implementation class, maps `execute` to `eager_execute`, and maps `declare_launch_args` to `annotated_args`. The inheritance hierarchy of the Python user class does not participate in capability detection. |
| 482 | - **Capability filtering**: `CustomOpCast<T>()` first identifies `CustomOpCapabilityProvider` and then checks the bitmask to determine whether the target interface is supported. | 488 | - **Capability filtering**: `CustomOpCast<T>()` first identifies `CustomOpCapabilityProvider` and then checks the bitmask to determine whether the target interface is supported. |
| 483 | - **IR argument assembly**: The bridge queries canonical IR through the public run-package API during descriptor loading for signature validation, then queries it again at holder creation and owns the resulting runtime snapshot. Callbacks read required, optional, and dynamic inputs/outputs and typed runtime attributes in that IR order, then construct positional and keyword arguments. | 489 | - **IR argument assembly**: The bridge queries canonical IR through the public run-package API during descriptor loading for signature validation, then queries it again at holder creation and owns the resulting runtime snapshot. Callbacks read required, optional, and dynamic inputs/outputs and typed runtime attributes in that IR order, then construct positional and keyword arguments. |
| 490 | +- **Registration transaction**: The runtime synchronously deep-copies prototype C POD data and registers its creator, collects canonical IR, and finally registers the implementation runtime entry and Adapter creator. If any step fails, the upper-level loader invokes unload to roll the completed steps back in reverse order. | ||
| 484 | - **Callback signature validation**: While loading a descriptor, the bridge calls `validate_op_impl_descriptor` to validate schema-bound signatures once, before holder creation or any business callback. For `execute`, it validates the total parameter count, the positional form and supplied type annotations of inputs, and the keyword-only form, names, and supplied type annotations of attributes. Outputs, the return annotation, and the return value are not validated. `declare_launch_args` validates inputs, outputs, attributes, and a `None` return annotation. Runtime callbacks do not validate signatures, and validation state does not enter the holder lifecycle. | 491 | - **Callback signature validation**: While loading a descriptor, the bridge calls `validate_op_impl_descriptor` to validate schema-bound signatures once, before holder creation or any business callback. For `execute`, it validates the total parameter count, the positional form and supplied type annotations of inputs, and the keyword-only form, names, and supplied type annotations of attributes. Outputs, the return annotation, and the return value are not validated. `declare_launch_args` validates inputs, outputs, attributes, and a `None` return annotation. Runtime callbacks do not validate signatures, and validation state does not enter the holder lifecycle. |
| 485 | - **Holder lifecycle**: The C++ adapter owns `PythonCustomOpHolder`. The Python side uses `_OP_IMPL_HOLDERS` to save instances by `instance_id`. When the adapter is destructed, the Python holder is destroyed. | 492 | - **Holder lifecycle**: The C++ adapter owns `PythonCustomOpHolder`. The Python side uses `_OP_IMPL_HOLDERS` to save instances by `instance_id`. When the adapter is destructed, the Python holder is destroyed. |
| 486 | - **Context binding**: Schema-bound callbacks establish dynamic scopes with `ContextVar`, and `get_execute_ctx()` / `get_declare_launch_args_ctx()` read their corresponding bindings. Resetting the token restores the outer context after nested invocation. | 493 | - **Context binding**: Schema-bound callbacks establish dynamic scopes with `ContextVar`, and `get_execute_ctx()` / `get_declare_launch_args_ctx()` read their corresponding bindings. Resetting the token restores the outer context after nested invocation. |
| @@ -500,10 +507,11 @@ Entry EnsureReady() | |||
| 500 | -> BuildPrebuiltBridgeLibraryCandidates() | 507 | -> BuildPrebuiltBridgeLibraryCandidates() |
| 501 | -> dlopen libge_python_custom_op_bridge.so | 508 | -> dlopen libge_python_custom_op_bridge.so |
| 502 | -> set_artifact_config(native_module_path) | 509 | -> set_artifact_config(native_module_path) |
| 503 | - -> load_and_get_op_impl_descriptors() | 510 | + -> load_and_get_op_descriptors() |
| 504 | - -> Collect canonical IR and call validate_op_impl_descriptor | ||
| 505 | -> register_custom_ops(registrar) | 511 | -> register_custom_ops(registrar) |
| 506 | - -> CustomOpFactory::RegisterCustomOpCreator() | 512 | + -> Register Python prototype creators and collect canonical IR |
| 513 | + -> Call validate_op_impl_descriptor | ||
| 514 | + -> Register implementation runtime entries and Adapter creators | ||
| 507 | ``` | 515 | ``` |
| 508 | 516 | ||
| 509 | #### PreRun Idempotent Reload Process | 517 | #### PreRun Idempotent Reload Process |
| @@ -521,7 +529,7 @@ This path ensures that when the user sets the Python custom op path only after i | |||
| 521 | ```text | 529 | ```text |
| 522 | CustomOpRegistry::CreateOrGetCustomOp(op_type) | 530 | CustomOpRegistry::CreateOrGetCustomOp(op_type) |
| 523 | -> PythonCustomOpAdapter(desc) | 531 | -> PythonCustomOpAdapter(desc) |
| 524 | - -> PythonCustomOpHolder(desc) | 532 | + -> PythonCustomOpImplHolder(desc) |
| 525 | -> callbacks.create(desc) | 533 | -> callbacks.create(desc) |
| 526 | -> The bridge resolves the public GetRegisteredIrDef symbol and queries canonical IR | 534 | -> The bridge resolves the public GetRegisteredIrDef symbol and queries canonical IR |
| 527 | -> The bridge holder stores PythonCustomOpIrMeta without validating signatures again | 535 | -> The bridge holder stores PythonCustomOpIrMeta without validating signatures again |
| @@ -745,7 +745,7 @@ custom_op/ | |||
| 745 | 745 | ||
| 746 | #### 模块定位 | 746 | #### 模块定位 |
| 747 | 747 | ||
| 748 | -Python 自定义算子的长期目标是支持用户使用 Python 描述自定义算子原型,并实现自定义算子的各类能力。当前通过反射实现类上的可调用 `execute` 和 `declare_launch_args` 方法,分别识别执行能力和静态图声明式地址刷新能力,不要求用户类继承 `BaseCustomOp` 或 `EagerExecuteOp`;已有继承写法继续兼容。执行入口同时支持 `execute(ctx)` 兼容形式和按照 canonical IR 输入、属性顺序绑定的 schema-bound 形式。 | 748 | +Python 自定义算子的长期目标是支持用户使用 Python 描述自定义算子原型,并实现自定义算子的各类能力。当前通过反射实现类上的可调用 `execute` 和 `declare_launch_args` 方法,分别识别执行能力和静态图声明式地址刷新能力,不要求用户类继承 `BaseCustomOp` 或 `EagerExecuteOp`;已有继承写法继续兼容。执行入口同时支持 `execute(ctx)` 兼容形式和按照 canonical IR 输入、属性顺序绑定的 schema-bound 形式。当前阶段还将 Python 原型注册到 `OperatorFactory`,但不调用 Python `infer_meta`,也不提供编译期或 RT2 Meta 推导。 |
| 749 | 749 | ||
| 750 | #### 运行时 native artifact 选择 | 750 | #### 运行时 native artifact 选择 |
| 751 | 751 | ||
| @@ -884,6 +884,14 @@ class AnnotatedAddCustom: | |||
| 884 | - `bootstrap.py` 负责扫描路径并动态加载 Python 模块 | 884 | - `bootstrap.py` 负责扫描路径并动态加载 Python 模块 |
| 885 | - 支持单个 `.py` 文件、普通目录下的 `.py` 文件和包含 `__init__.py` 的 Python 包 | 885 | - 支持单个 `.py` 文件、普通目录下的 `.py` 文件和包含 `__init__.py` 的 Python 包 |
| 886 | 886 | ||
| 887 | +**原型注册**: | ||
| 888 | + | ||
| 889 | +- `register_op` 收集 required/optional/dynamic input、12 类 attr、required/dynamic output 和 `mutates_args` | ||
| 890 | +- bridge 在同一加载事务中先注册 Python proto creator、收集生效的 canonical IR,再注册 impl runtime entry 和 Adapter creator | ||
| 891 | +- 支持 Python proto-only、Python proto + impl、C++ proto + Python impl、无 proto legacy impl;无 proto schema-bound impl 注册失败 | ||
| 892 | +- Python 原型可以覆盖同名内置原型;与已加载的同名 C++/Python 自定义算子冲突时注册失败 | ||
| 893 | +- 批次失败按 Adapter creator、impl runtime entry、proto creator 的逆序回滚;卸载只清理当前 loader 持有的对象 | ||
| 894 | + | ||
| 887 | **使用示例**: | 895 | **使用示例**: |
| 888 | ```python | 896 | ```python |
| 889 | from ge.custom_op import get_execute_ctx, register_op_impl | 897 | from ge.custom_op import get_execute_ctx, register_op_impl |
| @@ -19,7 +19,7 @@ Python 自定义算子的完整定位是支持用户用 Python 描述自定义 | |||
| 19 | - Python 用户通过 `declare_launch_args` 实现 `AnnotatedArgsOp` 编译期回调,使用 `AnnotatedArgsContext`、`AnnotatedKernelArgs` 和 `AnnotatedKernelLaunchInfo` 声明 kernel 启动参数。 | 19 | - Python 用户通过 `declare_launch_args` 实现 `AnnotatedArgsOp` 编译期回调,使用 `AnnotatedArgsContext`、`AnnotatedKernelArgs` 和 `AnnotatedKernelLaunchInfo` 声明 kernel 启动参数。 |
| 20 | - `ge.runtime` 提供 context 返回或入参所需的 `Tensor`、`StorageShape`、`StorageFormat`、`Shape`、`TensorPlacement` 等运行时数据结构。 | 20 | - `ge.runtime` 提供 context 返回或入参所需的 `Tensor`、`StorageShape`、`StorageFormat`、`Shape`、`TensorPlacement` 等运行时数据结构。 |
| 21 | 21 | ||
| 22 | -V2 计划在 V1 执行能力的基础上扩展 Python 原型和 Meta 推导能力。 | 22 | +V2 在 V1 执行能力的基础上扩展 Python 原型和 Meta 推导能力。当前阶段已经实现 Python 原型 creator、Adapter 注册事务和所有权管理,但尚不调用 Python `infer_meta`。 |
| 23 | 23 | ||
| 24 | V2 中,被 `register_op` 装饰的 Python 函数负责 Meta 推导,本文统一称为 `infer_meta`,但不要求函数名必须是 `infer_meta`。该函数按照算子原型接收输入 `TensorDesc`(包括可选输入和动态输入)及属性值,返回一个或多个描述输出 shape 和 data type 的 `TensorDesc`;它不读取输入 Tensor 数据,也不执行算子 kernel。 | 24 | V2 中,被 `register_op` 装饰的 Python 函数负责 Meta 推导,本文统一称为 `infer_meta`,但不要求函数名必须是 `infer_meta`。该函数按照算子原型接收输入 `TensorDesc`(包括可选输入和动态输入)及属性值,返回一个或多个描述输出 shape 和 data type 的 `TensorDesc`;它不读取输入 Tensor 数据,也不执行算子 kernel。 |
| 25 | 25 | ||
| @@ -71,6 +71,7 @@ Python custom op 是 GE Python 体系的一部分,与 Python pass 共享以下 | |||
| 71 | | Runtime loader | `runtime/custom_op/custom_op_loader.cc` | 统一加载 C++ custom op 和 Python custom op | | 71 | | Runtime loader | `runtime/custom_op/custom_op_loader.cc` | 统一加载 C++ custom op 和 Python custom op | |
| 72 | | Bridge loader | `runtime/custom_op/python_custom_op_bridge_loader.cc` | 选择 artifact、加载 `libge_python_custom_op_bridge.so`、注册 creator | | 72 | | Bridge loader | `runtime/custom_op/python_custom_op_bridge_loader.cc` | 选择 artifact、加载 `libge_python_custom_op_bridge.so`、注册 creator | |
| 73 | | Pybind bridge | `runtime/custom_op/python_custom_op_pybind_bridge.cc` | 导入 Python bridge 模块、创建 holder、回调 `execute` / `declare_launch_args` | | 73 | | Pybind bridge | `runtime/custom_op/python_custom_op_pybind_bridge.cc` | 导入 Python bridge 模块、创建 holder、回调 `execute` / `declare_launch_args` | |
| 74 | +| Proto runtime | `runtime/custom_op/python_custom_op_proto.*` | 深拷贝 C POD 原型并注册 `OperatorFactory` creator | | ||
| 74 | | Adapter | `runtime/custom_op/python_custom_op_adapter.*` | 作为 C++ `BaseCustomOp` 实例接入现有运行时 | | 75 | | Adapter | `runtime/custom_op/python_custom_op_adapter.*` | 作为 C++ `BaseCustomOp` 实例接入现有运行时 | |
| 75 | | Capability helper | `inc/graph_metadef/graph/custom_op/` | `CustomOpCapability` 和 `CustomOpCast<T>` | | 76 | | Capability helper | `inc/graph_metadef/graph/custom_op/` | `CustomOpCapability` 和 `CustomOpCast<T>` | |
| 76 | 77 | ||
| @@ -80,6 +81,7 @@ V1 功能包括: | |||
| 80 | 81 | ||
| 81 | - `@register_op_impl(op_type=...)` 注册 Python 自定义算子实现。 | 82 | - `@register_op_impl(op_type=...)` 注册 Python 自定义算子实现。 |
| 82 | - `@register_op(op_type=..., mutates_args=...)` 根据 Python 函数签名收集自定义算子原型。 | 83 | - `@register_op(op_type=..., mutates_args=...)` 根据 Python 函数签名收集自定义算子原型。 |
| 84 | +- bridge 将 Python 原型同步注册为 `OperatorFactory` creator,并从生效 creator 收集 canonical IR;当前不调用 `infer_meta`。 | ||
| 83 | - `register_op_impl` 反射实现类上的可调用 `execute`、`declare_launch_args` 方法并声明对应能力,不要求继承 `BaseCustomOp`、`EagerExecuteOp` 或 `AnnotatedArgsOp`。 | 85 | - `register_op_impl` 反射实现类上的可调用 `execute`、`declare_launch_args` 方法并声明对应能力,不要求继承 `BaseCustomOp`、`EagerExecuteOp` 或 `AnnotatedArgsOp`。 |
| 84 | - `execute(self, ctx)` 兼容形式直接接收 `EagerOpExecutionContext`;schema-bound 形式接收按 canonical IR 组装的输入和属性。 | 86 | - `execute(self, ctx)` 兼容形式直接接收 `EagerOpExecutionContext`;schema-bound 形式接收按 canonical IR 组装的输入和属性。 |
| 85 | - `EagerOpExecutionContext` 支持输入输出 tensor 查询、动态输入实例数、运行时属性读取、输出/工作区分配和 stream 获取。 | 87 | - `EagerOpExecutionContext` 支持输入输出 tensor 查询、动态输入实例数、运行时属性读取、输出/工作区分配和 stream 获取。 |
| @@ -96,6 +98,8 @@ V1 功能包括: | |||
| 96 | - Python `execute` 的返回值当前不作为状态码使用;正常返回表示成功,抛出异常表示失败。 | 98 | - Python `execute` 的返回值当前不作为状态码使用;正常返回表示成功,抛出异常表示失败。 |
| 97 | - Python custom op 当前声明 `EagerExecuteOp` 和 `AnnotatedArgsOp` capability;其它 C++ 能力接口由 adapter 保留 override 但按不支持处理。 | 99 | - Python custom op 当前声明 `EagerExecuteOp` 和 `AnnotatedArgsOp` capability;其它 C++ 能力接口由 adapter 保留 override 但按不支持处理。 |
| 98 | - schema-bound 形式依赖已有算子原型的 canonical IR。bridge 加载 descriptor 时收集 canonical IR,并在创建 holder 和调用业务 callback 之前调用 `validate_op_impl_descriptor`,一次性校验 schema-bound 签名:`execute` 校验 IR 输入和属性,不把输出参数纳入签名,也不限制返回注解或返回值;`declare_launch_args` 校验输入、输出、属性并要求 `-> None`。runtime callback 只组装实参并调用业务方法,不再校验签名;校验结果属于 descriptor 加载阶段,不进入 holder 生命周期。 | 100 | - schema-bound 形式依赖已有算子原型的 canonical IR。bridge 加载 descriptor 时收集 canonical IR,并在创建 holder 和调用业务 callback 之前调用 `validate_op_impl_descriptor`,一次性校验 schema-bound 签名:`execute` 校验 IR 输入和属性,不把输出参数纳入签名,也不限制返回注解或返回值;`declare_launch_args` 校验输入、输出、属性并要求 `-> None`。runtime callback 只组装实参并调用业务方法,不再校验签名;校验结果属于 descriptor 加载阶段,不进入 holder 生命周期。 |
| 101 | +- 跨 SO 的 proto/Adapter descriptor 是同步借用的 C POD view,runtime callback 返回前必须完成校验和深拷贝。 | ||
| 102 | +- Python 原型允许覆盖内置原型;若 `CustomOpFactory` 已存在同名 C++ 或 Python 自定义算子,则视为自定义算子冲突。 | ||
| 99 | - schema-bound 回调通过 `get_execute_ctx()` 获取当前 context;该绑定只在当前回调动态作用域内有效。 | 103 | - schema-bound 回调通过 `get_execute_ctx()` 获取当前 context;该绑定只在当前回调动态作用域内有效。 |
| 100 | - Python custom op native/bridge 与构建时 Python ABI 相关,不提供跨 Python minor version 兼容承诺。 | 104 | - Python custom op native/bridge 与构建时 Python ABI 相关,不提供跨 Python minor version 兼容承诺。 |
| 101 | - bridge C ABI 保持为 v1,`execute` 和 `declare_launch_args` 回调只传 holder 与对应 context;canonical IR 由 bridge 通过 run 包公共接口查询,不通过私有 ABI 投影传递。 | 105 | - bridge C ABI 保持为 v1,`execute` 和 `declare_launch_args` 回调只传 holder 与对应 context;canonical IR 由 bridge 通过 run 包公共接口查询,不通过私有 ABI 投影传递。 |
| @@ -325,14 +329,16 @@ Python custom op 加载由 `runtime/custom_op` 管理,避免 `graph_metadef/re | |||
| 325 | - `NeedLoadPythonCustomOps()` 仅在 `ASCEND_CUSTOM_OPP_PATH` 下发现 Python 文件或包时返回 true。 | 329 | - `NeedLoadPythonCustomOps()` 仅在 `ASCEND_CUSTOM_OPP_PATH` 下发现 Python 文件或包时返回 true。 |
| 326 | - `LoadPythonCustomOps()` 解析已加载 Python runtime key,选择 `custom_op/python_custom_op_artifacts/<python_tag>-<platform>` 下的 bridge/native artifact。 | 330 | - `LoadPythonCustomOps()` 解析已加载 Python runtime key,选择 `custom_op/python_custom_op_artifacts/<python_tag>-<platform>` 下的 bridge/native artifact。 |
| 327 | - `libge_python_custom_op_bridge.so` 通过 `GeGetPythonCustomOpBridgeApi()` 暴露 C ABI v1。 | 331 | - `libge_python_custom_op_bridge.so` 通过 `GeGetPythonCustomOpBridgeApi()` 暴露 C ABI v1。 |
| 328 | -- bridge 导入 `_ge_custom_op_native` 和 `ge.custom_op._bridge`,注册 descriptor,并为每个 adapter 创建 Python holder。 | 332 | +- bridge 导入 `_ge_custom_op_native` 和 `ge.custom_op._bridge`,一次获取 proto/impl snapshot;先注册全部 proto,再校验并注册 Adapter。 |
| 333 | +- `CustomOpLoader::LoadCustomOps()` 记录 Python custom op 是否已经加载,因此生命周期加载请求重复调用时会直接返回成功,不重复调用 bridge 注册入口。动态 `LoadPythonCustomOpsIfNeeded()` 路径不使用该状态,运行期间可以继续发现新增加的 Python custom op 路径。底层 `LoadPythonCustomOps()` 负责执行一次 bridge 注册尝试;注册失败后由调用方调用 `UnloadPythonCustomOps()` 清理本次产生的部分注册。 | ||
| 334 | +- `UnloadPythonCustomOps()` 先移除已注册的 Adapter creator,再一次性清理 Python 自定义算子 runtime registry,最后清理已注册的 proto creator。bridge loader 不再逐项注销 runtime entry,也不维护待清理状态。 | ||
| 329 | - `UnloadCustomOps()` 采用 `active_users_` 引用计数管理生命周期:每次 `LoadCustomOps()` 使计数 +1,每次 `UnloadCustomOps()` 使计数 -1,仅当计数归零时才卸载 Python custom op、清理 Python holder/registry 并关闭 bridge。`ShutdownCustomOpsForProcess()` 作为兼容 wrapper 保留,内部调用 `UnloadCustomOps()`。 | 335 | - `UnloadCustomOps()` 采用 `active_users_` 引用计数管理生命周期:每次 `LoadCustomOps()` 使计数 +1,每次 `UnloadCustomOps()` 使计数 -1,仅当计数归零时才卸载 Python custom op、清理 Python holder/registry 并关闭 bridge。`ShutdownCustomOpsForProcess()` 作为兼容 wrapper 保留,内部调用 `UnloadCustomOps()`。 |
| 330 | 336 | ||
| 331 | **输出** | 337 | **输出** |
| 332 | 338 | ||
| 333 | -- Python descriptor 注册为 `CustomOpFactory` creator。 | 339 | +- Python proto 注册为 `OperatorFactory` creator,Python impl 注册为 `CustomOpFactory` creator。 |
| 334 | - adapter 析构时销毁 Python holder,并 release runtime registry entry。 | 340 | - adapter 析构时销毁 Python holder,并 release runtime registry entry。 |
| 335 | -- active adapter 存在时 runtime registry 不允许 unregister。 | 341 | +- 单项 runtime registry 注销仍受 active adapter 保护;进程卸载时先移除 Adapter creator,再批量清理 registry。 |
| 336 | 342 | ||
| 337 | ### 3.3 非功能需求 | 343 | ### 3.3 非功能需求 |
| 338 | 344 | ||
| @@ -450,21 +456,21 @@ Python 版本敏感的 bridge 使用 run 包公开头文件中 `GetRegisteredIrD | |||
| 450 | 456 | ||
| 451 | 这里采用运行时符号解析,是因为 `libge_runner.so`/`libge_runner_v2.so` 已依赖 `custom_op_runtime`,而 bridge 由 `custom_op_runtime` 动态加载;若 bridge 再直接链接 runner,会形成反向 SO 依赖。该方式仍以 run 包正式公共头文件约束函数签名和数据类型,只把符号绑定推迟到 bridge 加载后执行。 | 457 | 这里采用运行时符号解析,是因为 `libge_runner.so`/`libge_runner_v2.so` 已依赖 `custom_op_runtime`,而 bridge 由 `custom_op_runtime` 动态加载;若 bridge 再直接链接 runner,会形成反向 SO 依赖。该方式仍以 run 包正式公共头文件约束函数签名和数据类型,只把符号绑定推迟到 bridge 加载后执行。 |
| 452 | 458 | ||
| 453 | -#### PythonCustomOpDescriptor | 459 | +#### PythonCustomOpProto 与 PythonCustomOpAdapterDescriptor |
| 454 | 460 | ||
| 455 | -C++ runtime 使用 `PythonCustomOpDescriptor`: | 461 | +C++ runtime 分别保存 owning proto 和 Adapter descriptor;Adapter descriptor 只保存实现 key: |
| 456 | 462 | ||
| 457 | ```cpp | 463 | ```cpp |
| 458 | -struct PythonCustomOpDescriptor { | 464 | +struct PythonCustomOpAdapterDescriptor { |
| 459 | - std::string descriptor_key; | ||
| 460 | std::string op_type; | 465 | std::string op_type; |
| 466 | + std::string impl_descriptor_key; | ||
| 461 | CustomOpCapabilityMask capabilities{0U}; | 467 | CustomOpCapabilityMask capabilities{0U}; |
| 462 | }; | 468 | }; |
| 463 | ``` | 469 | ``` |
| 464 | 470 | ||
| 465 | -#### PythonCustomOpCallbacks | 471 | +#### PythonCustomOpAdapterCallbacks |
| 466 | 472 | ||
| 467 | -bridge 向 runtime 注册 create/destroy/execute/declare_launch_args 回调。`IsValid()` 接受 `kEagerExecute`、`kAnnotatedArgs` 或两者组合,要求 create/destroy 非空,并按 capability 校验 execute/declare_launch_args 回调。 | 473 | +bridge 向 runtime 注册 create/destroy/execute/declare_launch_args 回调。schema-bound 签名校验不是 C++ callback,而是 bridge 在注册 Adapter 前内部调用 `validate_op_impl_descriptor` 完成。`IsValid()` 接受 `kEagerExecute`、`kAnnotatedArgs` 或两者组合,要求 create/destroy 非空,并按 capability 校验 execute/declare_launch_args 回调。 |
| 468 | 474 | ||
| 469 | #### BorrowedEagerOpExecutionContext | 475 | #### BorrowedEagerOpExecutionContext |
| 470 | 476 | ||
| @@ -481,6 +487,7 @@ native binding 保存 `gert::AnnotatedArgsContext *` 和独立 validity 标记 | |||
| 481 | - **能力反射**:registry 对实现 class 执行 `getattr` 和 `callable` 检查,把 `execute` 映射为 `eager_execute`,把 `declare_launch_args` 映射为 `annotated_args`;Python 用户类的继承关系不参与能力判断。 | 487 | - **能力反射**:registry 对实现 class 执行 `getattr` 和 `callable` 检查,把 `execute` 映射为 `eager_execute`,把 `declare_launch_args` 映射为 `annotated_args`;Python 用户类的继承关系不参与能力判断。 |
| 482 | - **capability 过滤**:`CustomOpCast<T>()` 先识别 `CustomOpCapabilityProvider`,再按 bitmask 判断是否支持目标接口。 | 488 | - **capability 过滤**:`CustomOpCast<T>()` 先识别 `CustomOpCapabilityProvider`,再按 bitmask 判断是否支持目标接口。 |
| 483 | - **IR 实参组装**:bridge 在 descriptor 加载阶段通过 run 包公共接口查询 canonical IR 以校验签名,holder 创建时再次查询并持有运行期 IR 快照;runtime callback 按该快照的 IR 顺序读取 required/optional/dynamic 输入输出和 typed runtime attrs,分别构造 positional arguments 和 keyword arguments。 | 489 | - **IR 实参组装**:bridge 在 descriptor 加载阶段通过 run 包公共接口查询 canonical IR 以校验签名,holder 创建时再次查询并持有运行期 IR 快照;runtime callback 按该快照的 IR 顺序读取 required/optional/dynamic 输入输出和 typed runtime attrs,分别构造 positional arguments 和 keyword arguments。 |
| 490 | +- **注册事务**:先同步深拷贝 proto C POD 并注册 creator,再收集 canonical IR,最后注册 impl runtime entry 和 Adapter creator;任一步失败由上层 loader 调用卸载,按相反顺序回滚已完成的步骤。 | ||
| 484 | - **回调签名校验**:bridge 加载 descriptor 时调用 `validate_op_impl_descriptor` 一次性校验 schema-bound 签名,并在创建 holder 和业务 callback 之前完成。`execute` 校验总参数数量、输入的位置形式及已提供的类型注解,以及属性的 keyword-only 形式、名称和已提供的类型注解;输出参数、返回注解和返回值不参与校验。`declare_launch_args` 校验输入、输出、属性及 `None` 返回注解。runtime callback 不再校验签名,校验状态也不进入 holder 生命周期。 | 491 | - **回调签名校验**:bridge 加载 descriptor 时调用 `validate_op_impl_descriptor` 一次性校验 schema-bound 签名,并在创建 holder 和业务 callback 之前完成。`execute` 校验总参数数量、输入的位置形式及已提供的类型注解,以及属性的 keyword-only 形式、名称和已提供的类型注解;输出参数、返回注解和返回值不参与校验。`declare_launch_args` 校验输入、输出、属性及 `None` 返回注解。runtime callback 不再校验签名,校验状态也不进入 holder 生命周期。 |
| 485 | - **holder 生命周期**:C++ adapter 拥有 `PythonCustomOpHolder`,Python 侧 `_OP_IMPL_HOLDERS` 以 `instance_id` 保存实例;adapter 析构时销毁 Python holder。 | 492 | - **holder 生命周期**:C++ adapter 拥有 `PythonCustomOpHolder`,Python 侧 `_OP_IMPL_HOLDERS` 以 `instance_id` 保存实例;adapter 析构时销毁 Python holder。 |
| 486 | - **上下文绑定**:schema-bound 回调使用 `ContextVar` 建立动态作用域,`get_execute_ctx()` / `get_declare_launch_args_ctx()` 读取对应绑定;token reset 支持嵌套调用后恢复外层 context。 | 493 | - **上下文绑定**:schema-bound 回调使用 `ContextVar` 建立动态作用域,`get_execute_ctx()` / `get_declare_launch_args_ctx()` 读取对应绑定;token reset 支持嵌套调用后恢复外层 context。 |
| @@ -500,10 +507,11 @@ native binding 保存 `gert::AnnotatedArgsContext *` 和独立 validity 标记 | |||
| 500 | -> BuildPrebuiltBridgeLibraryCandidates() | 507 | -> BuildPrebuiltBridgeLibraryCandidates() |
| 501 | -> dlopen libge_python_custom_op_bridge.so | 508 | -> dlopen libge_python_custom_op_bridge.so |
| 502 | -> set_artifact_config(native_module_path) | 509 | -> set_artifact_config(native_module_path) |
| 503 | - -> load_and_get_op_impl_descriptors() | 510 | + -> load_and_get_op_descriptors() |
| 504 | - -> 收集 canonical IR 并调用 validate_op_impl_descriptor | ||
| 505 | -> register_custom_ops(registrar) | 511 | -> register_custom_ops(registrar) |
| 506 | - -> CustomOpFactory::RegisterCustomOpCreator() | 512 | + -> 注册 Python proto creator 并收集 canonical IR |
| 513 | + -> 调用 validate_op_impl_descriptor | ||
| 514 | + -> 注册 impl runtime entry 和 Adapter creator | ||
| 507 | ``` | 515 | ``` |
| 508 | 516 | ||
| 509 | #### PreRun 幂等补加载流程 | 517 | #### PreRun 幂等补加载流程 |
| @@ -521,7 +529,7 @@ GraphManager::PreRun() | |||
| 521 | ```text | 529 | ```text |
| 522 | CustomOpRegistry::CreateOrGetCustomOp(op_type) | 530 | CustomOpRegistry::CreateOrGetCustomOp(op_type) |
| 523 | -> PythonCustomOpAdapter(desc) | 531 | -> PythonCustomOpAdapter(desc) |
| 524 | - -> PythonCustomOpHolder(desc) | 532 | + -> PythonCustomOpImplHolder(desc) |
| 525 | -> callbacks.create(desc) | 533 | -> callbacks.create(desc) |
| 526 | -> bridge 解析 GetRegisteredIrDef 公共符号并查询 canonical IR | 534 | -> bridge 解析 GetRegisteredIrDef 公共符号并查询 canonical IR |
| 527 | -> bridge holder 保存 PythonCustomOpIrMeta;不再校验签名 | 535 | -> bridge holder 保存 PythonCustomOpIrMeta;不再校验签名 |
| @@ -116,6 +116,7 @@ SET(GRAPH_SOURCE_LIST | |||
| 116 | "context/runtime_inference_context.cc" | 116 | "context/runtime_inference_context.cc" |
| 117 | "refiner/shape_refiner.cc" | 117 | "refiner/shape_refiner.cc" |
| 118 | "ir/ir_definitions_recover.cc" | 118 | "ir/ir_definitions_recover.cc" |
| 119 | + "ir/ir_definitions_query.cc" | ||
| 119 | "opsproto/opsproto_manager.cc" | 120 | "opsproto/opsproto_manager.cc" |
| 120 | "utils/op_desc_utils.cc" | 121 | "utils/op_desc_utils.cc" |
| 121 | "utils/tuning_utils.cc" | 122 | "utils/tuning_utils.cc" |
| @@ -0,0 +1,66 @@ | |||
| 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 | +extern "C" VISIBILITY_EXPORT ge::Status GetRegisteredIrDefFromGraph( | ||
| 20 | + const char *op_type, std::vector<std::pair<ge::AscendString, ge::AscendString>> &inputs, | ||
| 21 | + std::vector<std::pair<ge::AscendString, ge::AscendString>> &outputs, | ||
| 22 | + std::vector<std::pair<ge::AscendString, ge::AscendString>> &attrs) { | ||
| 23 | + GE_ASSERT_NOTNULL(op_type); | ||
| 24 | + const auto op = ge::OperatorFactory::CreateOperator("_", op_type); | ||
| 25 | + GE_WARN_ASSERT(!op.IsEmpty(), "No operator found for type: %s", op_type); | ||
| 26 | + const auto desc = ge::OpDescUtils::GetOpDescFromOperator(op); | ||
| 27 | + | ||
| 28 | + static const auto kInputTypeString = []() { | ||
| 29 | + std::map<ge::IrInputType, ge::AscendString> type_str; | ||
| 30 | + type_str[ge::IrInputType::kIrInputRequired] = "required"; | ||
| 31 | + type_str[ge::IrInputType::kIrInputOptional] = "optional"; | ||
| 32 | + type_str[ge::IrInputType::kIrInputDynamic] = "dynamic"; | ||
| 33 | + return type_str; | ||
| 34 | + }(); | ||
| 35 | + | ||
| 36 | + static const auto kOutputTypeString = []() { | ||
| 37 | + std::map<ge::IrOutputType, ge::AscendString> type_str; | ||
| 38 | + type_str[ge::IrOutputType::kIrOutputRequired] = "required"; | ||
| 39 | + type_str[ge::IrOutputType::kIrOutputDynamic] = "dynamic"; | ||
| 40 | + return type_str; | ||
| 41 | + }(); | ||
| 42 | + | ||
| 43 | + GE_ASSERT_NOTNULL(desc, "Failed to get OpDesc from operator: %s", op_type); | ||
| 44 | + for (const auto &name2type : desc->GetIrInputs()) { | ||
| 45 | + const auto iter = kInputTypeString.find(name2type.second); | ||
| 46 | + GE_ASSERT(iter != kInputTypeString.end(), "Unknown input type: %d for operator: %s", name2type.second, op_type); | ||
| 47 | + inputs.emplace_back(ge::AscendString(name2type.first.c_str()), iter->second); | ||
| 48 | + } | ||
| 49 | + for (const auto &name2type : desc->GetIrOutputs()) { | ||
| 50 | + const auto iter = kOutputTypeString.find(name2type.second); | ||
| 51 | + GE_ASSERT(iter != kOutputTypeString.end(), "Unknown output type: %d for operator: %s", name2type.second, op_type); | ||
| 52 | + outputs.emplace_back(ge::AscendString(name2type.first.c_str()), iter->second); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + std::map<ge::AscendString, ge::AscendString> attrs_and_types; | ||
| 56 | + GE_ASSERT_GRAPH_SUCCESS(op.GetAllIrAttrNamesAndTypes(attrs_and_types), | ||
| 57 | + "Failed to get attr names and types for operator: %s", op_type); | ||
| 58 | + for (const auto &attr : desc->GetIrAttrNames()) { | ||
| 59 | + const auto attr_name = ge::AscendString(attr.c_str()); | ||
| 60 | + const auto iter = attrs_and_types.find(attr_name); | ||
| 61 | + GE_ASSERT(iter != attrs_and_types.end(), "Failed to get attr type for operator: %s, attr: %s", op_type, | ||
| 62 | + attr.c_str()); | ||
| 63 | + attrs.emplace_back(attr_name, iter->second); | ||
| 64 | + } | ||
| 65 | + return ge::SUCCESS; | ||
| 66 | +} | ||
| @@ -0,0 +1,26 @@ | |||
| 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 | +extern "C" VISIBILITY_EXPORT ge::Status GetRegisteredIrDefFromGraph( | ||
| 22 | + const char *op_type, std::vector<std::pair<ge::AscendString, ge::AscendString>> &inputs, | ||
| 23 | + std::vector<std::pair<ge::AscendString, ge::AscendString>> &outputs, | ||
| 24 | + std::vector<std::pair<ge::AscendString, ge::AscendString>> &attrs); | ||
| 25 | + | ||
| 26 | + | ||
| @@ -12,6 +12,7 @@ add_library(custom_op_runtime SHARED | |||
| 12 | "custom_op_loader.cc" | 12 | "custom_op_loader.cc" |
| 13 | "python_custom_op_adapter.cc" | 13 | "python_custom_op_adapter.cc" |
| 14 | "python_custom_op_bridge_loader.cc" | 14 | "python_custom_op_bridge_loader.cc" |
| 15 | + "python_custom_op_proto.cc" | ||
| 15 | ) | 16 | ) |
| 16 | 17 | ||
| 17 | target_compile_definitions(custom_op_runtime PRIVATE | 18 | target_compile_definitions(custom_op_runtime PRIVATE |
| @@ -54,6 +55,7 @@ target_link_libraries(custom_op_runtime | |||
| 54 | ) | 55 | ) |
| 55 | 56 | ||
| 56 | add_library(ge_python_custom_op_bridge SHARED | 57 | add_library(ge_python_custom_op_bridge SHARED |
| 58 | + "python_custom_op_bridge_descriptors.cc" | ||
| 57 | "python_custom_op_pybind_bridge.cc" | 59 | "python_custom_op_pybind_bridge.cc" |
| 58 | ) | 60 | ) |
| 59 | 61 | ||
| @@ -21,12 +21,12 @@ | |||
| 21 | namespace ge { | 21 | namespace ge { |
| 22 | namespace custom_op { | 22 | namespace custom_op { |
| 23 | namespace { | 23 | namespace { |
| 24 | -struct PythonCustomOpRuntimeEntry { | 24 | +struct PythonCustomOpImplRuntimeEntry { |
| 25 | - explicit PythonCustomOpRuntimeEntry(PythonCustomOpDescriptor d, PythonCustomOpCallbacks cb) | 25 | + explicit PythonCustomOpImplRuntimeEntry(PythonCustomOpAdapterDescriptor d, PythonCustomOpAdapterCallbacks cb) |
| 26 | : desc(std::move(d)), callbacks(cb) {} | 26 | : desc(std::move(d)), callbacks(cb) {} |
| 27 | 27 | ||
| 28 | - PythonCustomOpDescriptor desc; | 28 | + PythonCustomOpAdapterDescriptor desc; |
| 29 | - PythonCustomOpCallbacks callbacks; | 29 | + PythonCustomOpAdapterCallbacks callbacks; |
| 30 | // A descriptor_key maps to one shared runtime entry. Multiple adapters may | 30 | // A descriptor_key maps to one shared runtime entry. Multiple adapters may |
| 31 | // reference it, while each adapter owns one Python custom op instance. | 31 | // reference it, while each adapter owns one Python custom op instance. |
| 32 | // active_adapter_count prevents unregister while callbacks are still in use. | 32 | // active_adapter_count prevents unregister while callbacks are still in use. |
| @@ -34,27 +34,27 @@ struct PythonCustomOpRuntimeEntry { | |||
| 34 | std::mutex mutex; | 34 | std::mutex mutex; |
| 35 | }; | 35 | }; |
| 36 | 36 | ||
| 37 | -class PythonCustomOpRuntimeRegistryImpl { | 37 | +class PythonCustomOpImplRuntimeRegistryImpl { |
| 38 | public: | 38 | public: |
| 39 | - bool Register(const PythonCustomOpDescriptor &desc, const PythonCustomOpCallbacks &callbacks) { | 39 | + bool Register(const PythonCustomOpAdapterDescriptor &desc, const PythonCustomOpAdapterCallbacks &callbacks) { |
| 40 | - if (desc.descriptor_key.empty() || desc.op_type.empty() || (!callbacks.IsValid(desc.capabilities))) { | 40 | + if (desc.impl_descriptor_key.empty() || desc.op_type.empty() || (!callbacks.IsValid(desc.capabilities))) { |
| 41 | - GELOGW("Register python custom op runtime failed, descriptor key[%s], op type[%s].", desc.descriptor_key.c_str(), | 41 | + GELOGW("Register python custom op runtime failed, descriptor key[%s], op type[%s].", |
| 42 | - desc.op_type.c_str()); | 42 | + desc.impl_descriptor_key.c_str(), desc.op_type.c_str()); |
| 43 | return false; | 43 | return false; |
| 44 | } | 44 | } |
| 45 | 45 | ||
| 46 | std::lock_guard<std::mutex> lock(mutex_); | 46 | std::lock_guard<std::mutex> lock(mutex_); |
| 47 | - if (descriptor_key_to_runtime_entry_.find(desc.descriptor_key) != descriptor_key_to_runtime_entry_.cend()) { | 47 | + if (descriptor_key_to_runtime_entry_.find(desc.impl_descriptor_key) != descriptor_key_to_runtime_entry_.cend()) { |
| 48 | - GELOGW("Python custom op runtime descriptor key[%s] has already registered.", desc.descriptor_key.c_str()); | 48 | + GELOGW("Python custom op runtime descriptor key[%s] has already registered.", desc.impl_descriptor_key.c_str()); |
| 49 | return false; | 49 | return false; |
| 50 | } | 50 | } |
| 51 | - auto runtime_entry = ComGraphMakeShared<PythonCustomOpRuntimeEntry>(desc, callbacks); | 51 | + auto runtime_entry = ComGraphMakeShared<PythonCustomOpImplRuntimeEntry>(desc, callbacks); |
| 52 | if (runtime_entry == nullptr) { | 52 | if (runtime_entry == nullptr) { |
| 53 | GELOGE(GRAPH_FAILED, "Create python custom op runtime entry failed, descriptor key[%s], op type[%s].", | 53 | GELOGE(GRAPH_FAILED, "Create python custom op runtime entry failed, descriptor key[%s], op type[%s].", |
| 54 | - desc.descriptor_key.c_str(), desc.op_type.c_str()); | 54 | + desc.impl_descriptor_key.c_str(), desc.op_type.c_str()); |
| 55 | return false; | 55 | return false; |
| 56 | } | 56 | } |
| 57 | - descriptor_key_to_runtime_entry_.emplace(desc.descriptor_key, std::move(runtime_entry)); | 57 | + descriptor_key_to_runtime_entry_.emplace(desc.impl_descriptor_key, std::move(runtime_entry)); |
| 58 | return true; | 58 | return true; |
| 59 | } | 59 | } |
| 60 | 60 | ||
| @@ -74,22 +74,31 @@ class PythonCustomOpRuntimeRegistryImpl { | |||
| 74 | return true; | 74 | return true; |
| 75 | } | 75 | } |
| 76 | 76 | ||
| 77 | - bool Acquire(const PythonCustomOpDescriptor &desc, PythonCustomOpCallbacks &callbacks) { | 77 | + bool Acquire(const PythonCustomOpAdapterDescriptor &desc, PythonCustomOpAdapterCallbacks &callbacks) { |
| 78 | - auto runtime_entry = Get(desc.descriptor_key); | 78 | + const std::lock_guard<std::mutex> map_lock(mutex_); |
| 79 | - if (runtime_entry == nullptr) { | 79 | + const auto iter = descriptor_key_to_runtime_entry_.find(desc.impl_descriptor_key); |
| 80 | + if (iter == descriptor_key_to_runtime_entry_.cend()) { | ||
| 80 | GELOGW("Acquire python custom op runtime failed because descriptor key[%s] is not registered.", | 81 | GELOGW("Acquire python custom op runtime failed because descriptor key[%s] is not registered.", |
| 81 | - desc.descriptor_key.c_str()); | 82 | + desc.impl_descriptor_key.c_str()); |
| 82 | return false; | 83 | return false; |
| 83 | } | 84 | } |
| 84 | 85 | ||
| 85 | - std::lock_guard<std::mutex> lock(runtime_entry->mutex); | 86 | + const auto &runtime_entry = iter->second; |
| 87 | + std::lock_guard<std::mutex> runtime_lock(runtime_entry->mutex); | ||
| 88 | + if ((runtime_entry->desc.op_type != desc.op_type) || | ||
| 89 | + (runtime_entry->desc.impl_descriptor_key != desc.impl_descriptor_key) || | ||
| 90 | + (runtime_entry->desc.capabilities != desc.capabilities)) { | ||
| 91 | + GELOGW("Acquire python custom op runtime descriptor mismatch, descriptor key[%s].", | ||
| 92 | + desc.impl_descriptor_key.c_str()); | ||
| 93 | + return false; | ||
| 94 | + } | ||
| 86 | ++runtime_entry->active_adapter_count; | 95 | ++runtime_entry->active_adapter_count; |
| 87 | callbacks = runtime_entry->callbacks; | 96 | callbacks = runtime_entry->callbacks; |
| 88 | return true; | 97 | return true; |
| 89 | } | 98 | } |
| 90 | 99 | ||
| 91 | - void Release(const PythonCustomOpDescriptor &desc) { | 100 | + void Release(const PythonCustomOpAdapterDescriptor &desc) { |
| 92 | - auto runtime_entry = Get(desc.descriptor_key); | 101 | + auto runtime_entry = Get(desc.impl_descriptor_key); |
| 93 | if (runtime_entry == nullptr) { | 102 | if (runtime_entry == nullptr) { |
| 94 | return; | 103 | return; |
| 95 | } | 104 | } |
| @@ -107,7 +116,7 @@ class PythonCustomOpRuntimeRegistryImpl { | |||
| 107 | } | 116 | } |
| 108 | 117 | ||
| 109 | private: | 118 | private: |
| 110 | - std::shared_ptr<PythonCustomOpRuntimeEntry> Get(const std::string &descriptor_key) { | 119 | + std::shared_ptr<PythonCustomOpImplRuntimeEntry> Get(const std::string &descriptor_key) { |
| 111 | std::lock_guard<std::mutex> lock(mutex_); | 120 | std::lock_guard<std::mutex> lock(mutex_); |
| 112 | const auto iter = descriptor_key_to_runtime_entry_.find(descriptor_key); | 121 | const auto iter = descriptor_key_to_runtime_entry_.find(descriptor_key); |
| 113 | if (iter == descriptor_key_to_runtime_entry_.cend()) { | 122 | if (iter == descriptor_key_to_runtime_entry_.cend()) { |
| @@ -117,85 +126,95 @@ class PythonCustomOpRuntimeRegistryImpl { | |||
| 117 | } | 126 | } |
| 118 | 127 | ||
| 119 | std::mutex mutex_; | 128 | std::mutex mutex_; |
| 120 | - std::map<std::string, std::shared_ptr<PythonCustomOpRuntimeEntry>> descriptor_key_to_runtime_entry_; | 129 | + std::map<std::string, std::shared_ptr<PythonCustomOpImplRuntimeEntry>> descriptor_key_to_runtime_entry_; |
| 121 | }; | 130 | }; |
| 122 | 131 | ||
| 123 | -PythonCustomOpRuntimeRegistryImpl &GetPythonCustomOpRuntimeRegistryImpl() { | 132 | +PythonCustomOpImplRuntimeRegistryImpl &GetPythonCustomOpImplRuntimeRegistryImpl() { |
| 124 | - static PythonCustomOpRuntimeRegistryImpl runtime_registry; | 133 | + static PythonCustomOpImplRuntimeRegistryImpl runtime_registry; |
| 125 | return runtime_registry; | 134 | return runtime_registry; |
| 126 | } | 135 | } |
| 136 | + | ||
| 127 | } // namespace | 137 | } // namespace |
| 128 | 138 | ||
| 129 | -PythonCustomOpHolder::PythonCustomOpHolder(const PythonCustomOpDescriptor &desc) : desc_(desc) { | 139 | +PythonCustomOpImplHolder::PythonCustomOpImplHolder(const PythonCustomOpAdapterDescriptor &desc) : desc_(desc) { |
| 130 | - if (!PythonCustomOpRuntimeRegistry::GetInstance().Acquire(desc_, callbacks_)) { | 140 | + if (!PythonCustomOpImplRuntimeRegistry::GetInstance().Acquire(desc_, callbacks_)) { |
| 131 | return; | 141 | return; |
| 132 | } | 142 | } |
| 133 | - if (callbacks_.create == nullptr) { | 143 | + if (callbacks_.create_impl_holder == nullptr) { |
| 134 | - PythonCustomOpRuntimeRegistry::GetInstance().Release(desc_); | 144 | + PythonCustomOpImplRuntimeRegistry::GetInstance().Release(desc_); |
| 135 | return; | 145 | return; |
| 136 | } | 146 | } |
| 137 | - holder_ = callbacks_.create(&desc_); | 147 | + const PythonCustomOpAdapterDescriptorView descriptor_view = { |
| 148 | + {desc_.op_type.data(), desc_.op_type.size()}, | ||
| 149 | + {desc_.impl_descriptor_key.data(), desc_.impl_descriptor_key.size()}, | ||
| 150 | + desc_.capabilities}; | ||
| 151 | + holder_ = callbacks_.create_impl_holder(&descriptor_view); | ||
| 138 | if (holder_ == nullptr) { | 152 | if (holder_ == nullptr) { |
| 139 | - PythonCustomOpRuntimeRegistry::GetInstance().Release(desc_); | 153 | + PythonCustomOpImplRuntimeRegistry::GetInstance().Release(desc_); |
| 140 | return; | 154 | return; |
| 141 | } | 155 | } |
| 142 | valid_ = true; | 156 | valid_ = true; |
| 143 | } | 157 | } |
| 144 | 158 | ||
| 145 | -PythonCustomOpHolder::~PythonCustomOpHolder() { | 159 | +PythonCustomOpImplHolder::~PythonCustomOpImplHolder() { |
| 146 | if (valid_) { | 160 | if (valid_) { |
| 147 | - if ((holder_ != nullptr) && (callbacks_.destroy != nullptr)) { | 161 | + if ((holder_ != nullptr) && (callbacks_.destroy_impl_holder != nullptr)) { |
| 148 | - callbacks_.destroy(holder_); | 162 | + callbacks_.destroy_impl_holder(holder_); |
| 149 | holder_ = nullptr; | 163 | holder_ = nullptr; |
| 150 | } | 164 | } |
| 151 | - PythonCustomOpRuntimeRegistry::GetInstance().Release(desc_); | 165 | + PythonCustomOpImplRuntimeRegistry::GetInstance().Release(desc_); |
| 152 | } | 166 | } |
| 153 | } | 167 | } |
| 154 | 168 | ||
| 155 | -bool PythonCustomOpHolder::IsValid() const { | 169 | +bool PythonCustomOpImplHolder::IsValid() const { |
| 156 | return valid_; | 170 | return valid_; |
| 157 | } | 171 | } |
| 158 | 172 | ||
| 159 | -void *PythonCustomOpHolder::GetHolder() const { | 173 | +void *PythonCustomOpImplHolder::GetHolder() const { |
| 160 | return holder_; | 174 | return holder_; |
| 161 | } | 175 | } |
| 162 | 176 | ||
| 163 | -const PythonCustomOpCallbacks &PythonCustomOpHolder::GetCallbacks() const { | 177 | +const PythonCustomOpAdapterCallbacks &PythonCustomOpImplHolder::GetCallbacks() const { |
| 164 | return callbacks_; | 178 | return callbacks_; |
| 165 | } | 179 | } |
| 166 | 180 | ||
| 167 | -const PythonCustomOpDescriptor &PythonCustomOpHolder::GetDescriptor() const { | 181 | +const PythonCustomOpAdapterDescriptor &PythonCustomOpImplHolder::GetDescriptor() const { |
| 168 | return desc_; | 182 | return desc_; |
| 169 | } | 183 | } |
| 170 | 184 | ||
| 171 | -PythonCustomOpRuntimeRegistry &PythonCustomOpRuntimeRegistry::GetInstance() { | 185 | +PythonCustomOpImplRuntimeRegistry &PythonCustomOpImplRuntimeRegistry::GetInstance() { |
| 172 | - static PythonCustomOpRuntimeRegistry runtime_registry; | 186 | + static PythonCustomOpImplRuntimeRegistry runtime_registry; |
| 173 | return runtime_registry; | 187 | return runtime_registry; |
| 174 | } | 188 | } |
| 175 | 189 | ||
| 176 | -bool PythonCustomOpRuntimeRegistry::Register(const PythonCustomOpDescriptor &desc, | 190 | +bool PythonCustomOpImplRuntimeRegistry::Register(const PythonCustomOpAdapterDescriptor &desc, |
| 177 | - const PythonCustomOpCallbacks &callbacks) { | 191 | + const PythonCustomOpAdapterCallbacks &callbacks) { |
| 178 | - return GetPythonCustomOpRuntimeRegistryImpl().Register(desc, callbacks); | 192 | + return GetPythonCustomOpImplRuntimeRegistryImpl().Register(desc, callbacks); |
| 179 | } | 193 | } |
| 180 | 194 | ||
| 181 | -bool PythonCustomOpRuntimeRegistry::Unregister(const std::string &descriptor_key) { | 195 | +bool PythonCustomOpImplRuntimeRegistry::Unregister(const std::string &descriptor_key) { |
| 182 | - return GetPythonCustomOpRuntimeRegistryImpl().Unregister(descriptor_key); | 196 | + return GetPythonCustomOpImplRuntimeRegistryImpl().Unregister(descriptor_key); |
| 183 | } | 197 | } |
| 184 | 198 | ||
| 185 | -bool PythonCustomOpRuntimeRegistry::Acquire(const PythonCustomOpDescriptor &desc, PythonCustomOpCallbacks &callbacks) { | 199 | +bool PythonCustomOpImplRuntimeRegistry::Acquire(const PythonCustomOpAdapterDescriptor &desc, |
| 186 | - return GetPythonCustomOpRuntimeRegistryImpl().Acquire(desc, callbacks); | 200 | + PythonCustomOpAdapterCallbacks &callbacks) { |
| 201 | + return GetPythonCustomOpImplRuntimeRegistryImpl().Acquire(desc, callbacks); | ||
| 187 | } | 202 | } |
| 188 | 203 | ||
| 189 | -void PythonCustomOpRuntimeRegistry::Release(const PythonCustomOpDescriptor &desc) { | 204 | +void PythonCustomOpImplRuntimeRegistry::Release(const PythonCustomOpAdapterDescriptor &desc) { |
| 190 | - GetPythonCustomOpRuntimeRegistryImpl().Release(desc); | 205 | + GetPythonCustomOpImplRuntimeRegistryImpl().Release(desc); |
| 191 | } | 206 | } |
| 192 | 207 | ||
| 193 | -void PythonCustomOpRuntimeRegistry::Clear() { | 208 | +void PythonCustomOpImplRuntimeRegistry::Clear() { |
| 194 | - GetPythonCustomOpRuntimeRegistryImpl().Clear(); | 209 | + GetPythonCustomOpImplRuntimeRegistryImpl().Clear(); |
| 195 | } | 210 | } |
| 196 | 211 | ||
| 197 | -PythonCustomOpAdapter::PythonCustomOpAdapter(PythonCustomOpDescriptor desc) | 212 | +void ClearPythonCustomOpRuntimeRegistry() { |
| 198 | - : desc_(std::move(desc)), holder_(new (std::nothrow) PythonCustomOpHolder(desc_)) {} | 213 | + PythonCustomOpImplRuntimeRegistry::GetInstance().Clear(); |
| 214 | +} | ||
| 215 | + | ||
| 216 | +PythonCustomOpAdapter::PythonCustomOpAdapter(PythonCustomOpAdapterDescriptor desc) | ||
| 217 | + : desc_(std::move(desc)), holder_(new (std::nothrow) PythonCustomOpImplHolder(desc_)) {} | ||
| 199 | 218 | ||
| 200 | PythonCustomOpAdapter::~PythonCustomOpAdapter() = default; | 219 | PythonCustomOpAdapter::~PythonCustomOpAdapter() = default; |
| 201 | 220 | ||
| @@ -214,7 +233,7 @@ graphStatus PythonCustomOpAdapter::Execute(gert::EagerOpExecutionContext *ctx) { | |||
| 214 | if ((holder_ == nullptr) || (!holder_->IsValid()) || (holder_->GetHolder() == nullptr) || | 233 | if ((holder_ == nullptr) || (!holder_->IsValid()) || (holder_->GetHolder() == nullptr) || |
| 215 | (holder_->GetCallbacks().execute == nullptr)) { | 234 | (holder_->GetCallbacks().execute == nullptr)) { |
| 216 | GELOGE(GRAPH_FAILED, "Python custom op adapter is invalid, descriptor key[%s], op type[%s].", | 235 | GELOGE(GRAPH_FAILED, "Python custom op adapter is invalid, descriptor key[%s], op type[%s].", |
| 217 | - desc_.descriptor_key.c_str(), desc_.op_type.c_str()); | 236 | + desc_.impl_descriptor_key.c_str(), desc_.op_type.c_str()); |
| 218 | return GRAPH_FAILED; | 237 | return GRAPH_FAILED; |
| 219 | } | 238 | } |
| 220 | return holder_->GetCallbacks().execute(holder_->GetHolder(), ctx); | 239 | return holder_->GetCallbacks().execute(holder_->GetHolder(), ctx); |
| @@ -227,7 +246,7 @@ graphStatus PythonCustomOpAdapter::DeclareLaunchArgs(gert::AnnotatedArgsContext | |||
| 227 | if ((holder_ == nullptr) || (!holder_->IsValid()) || (holder_->GetHolder() == nullptr) || | 246 | if ((holder_ == nullptr) || (!holder_->IsValid()) || (holder_->GetHolder() == nullptr) || |
| 228 | (holder_->GetCallbacks().declare_launch_args == nullptr)) { | 247 | (holder_->GetCallbacks().declare_launch_args == nullptr)) { |
| 229 | GELOGE(GRAPH_FAILED, "Python custom op adapter is invalid, descriptor key[%s], op type[%s].", | 248 | GELOGE(GRAPH_FAILED, "Python custom op adapter is invalid, descriptor key[%s], op type[%s].", |
| 230 | - desc_.descriptor_key.c_str(), desc_.op_type.c_str()); | 249 | + desc_.impl_descriptor_key.c_str(), desc_.op_type.c_str()); |
| 231 | return GRAPH_FAILED; | 250 | return GRAPH_FAILED; |
| 232 | } | 251 | } |
| 233 | return holder_->GetCallbacks().declare_launch_args(holder_->GetHolder(), &ctx); | 252 | return holder_->GetCallbacks().declare_launch_args(holder_->GetHolder(), &ctx); |
| @@ -269,8 +288,5 @@ graphStatus PythonCustomOpAdapter::ReportUnsupported(CustomOpCapability capabili | |||
| 269 | return GRAPH_FAILED; | 288 | return GRAPH_FAILED; |
| 270 | } | 289 | } |
| 271 | 290 | ||
| 272 | -void ClearPythonCustomOpRuntimeRegistry() { | ||
| 273 | - PythonCustomOpRuntimeRegistry::GetInstance().Clear(); | ||
| 274 | -} | ||
| 275 | } // namespace custom_op | 291 | } // namespace custom_op |
| 276 | } // namespace ge | 292 | } // namespace ge |
| @@ -20,37 +20,43 @@ | |||
| 20 | 20 | ||
| 21 | namespace ge { | 21 | namespace ge { |
| 22 | namespace custom_op { | 22 | namespace custom_op { |
| 23 | -class PythonCustomOpRuntimeRegistry { | 23 | +struct PythonCustomOpAdapterDescriptor { |
| 24 | - public: | 24 | + std::string op_type; |
| 25 | - static PythonCustomOpRuntimeRegistry &GetInstance(); | 25 | + std::string impl_descriptor_key; |
| 26 | + CustomOpCapabilityMask capabilities{0U}; | ||
| 27 | +}; | ||
| 26 | 28 | ||
| 27 | - static bool Register(const PythonCustomOpDescriptor &desc, const PythonCustomOpCallbacks &callbacks); | 29 | +class PythonCustomOpImplRuntimeRegistry { |
| 30 | + public: | ||
| 31 | + static PythonCustomOpImplRuntimeRegistry &GetInstance(); | ||
| 32 | + | ||
| 33 | + static bool Register(const PythonCustomOpAdapterDescriptor &desc, const PythonCustomOpAdapterCallbacks &callbacks); | ||
| 28 | static bool Unregister(const std::string &descriptor_key); | 34 | static bool Unregister(const std::string &descriptor_key); |
| 29 | - static bool Acquire(const PythonCustomOpDescriptor &desc, PythonCustomOpCallbacks &callbacks); | 35 | + static bool Acquire(const PythonCustomOpAdapterDescriptor &desc, PythonCustomOpAdapterCallbacks &callbacks); |
| 30 | - static void Release(const PythonCustomOpDescriptor &desc); | 36 | + static void Release(const PythonCustomOpAdapterDescriptor &desc); |
| 31 | static void Clear(); | 37 | static void Clear(); |
| 32 | 38 | ||
| 33 | private: | 39 | private: |
| 34 | - PythonCustomOpRuntimeRegistry() = default; | 40 | + PythonCustomOpImplRuntimeRegistry() = default; |
| 35 | - ~PythonCustomOpRuntimeRegistry() = default; | 41 | + ~PythonCustomOpImplRuntimeRegistry() = default; |
| 36 | }; | 42 | }; |
| 37 | 43 | ||
| 38 | -class PythonCustomOpHolder { | 44 | +class PythonCustomOpImplHolder { |
| 39 | public: | 45 | public: |
| 40 | - explicit PythonCustomOpHolder(const PythonCustomOpDescriptor &desc); | 46 | + explicit PythonCustomOpImplHolder(const PythonCustomOpAdapterDescriptor &desc); |
| 41 | - ~PythonCustomOpHolder(); | 47 | + ~PythonCustomOpImplHolder(); |
| 42 | 48 | ||
| 43 | - PythonCustomOpHolder(const PythonCustomOpHolder &) = delete; | 49 | + PythonCustomOpImplHolder(const PythonCustomOpImplHolder &) = delete; |
| 44 | - PythonCustomOpHolder &operator=(const PythonCustomOpHolder &) = delete; | 50 | + PythonCustomOpImplHolder &operator=(const PythonCustomOpImplHolder &) = delete; |
| 45 | 51 | ||
| 46 | bool IsValid() const; | 52 | bool IsValid() const; |
| 47 | void *GetHolder() const; | 53 | void *GetHolder() const; |
| 48 | - const PythonCustomOpCallbacks &GetCallbacks() const; | 54 | + const PythonCustomOpAdapterCallbacks &GetCallbacks() const; |
| 49 | - const PythonCustomOpDescriptor &GetDescriptor() const; | 55 | + const PythonCustomOpAdapterDescriptor &GetDescriptor() const; |
| 50 | 56 | ||
| 51 | private: | 57 | private: |
| 52 | - PythonCustomOpDescriptor desc_; | 58 | + PythonCustomOpAdapterDescriptor desc_; |
| 53 | - PythonCustomOpCallbacks callbacks_; | 59 | + PythonCustomOpAdapterCallbacks callbacks_; |
| 54 | void *holder_{nullptr}; | 60 | void *holder_{nullptr}; |
| 55 | bool valid_{false}; | 61 | bool valid_{false}; |
| 56 | }; | 62 | }; |
| @@ -63,7 +69,7 @@ class PythonCustomOpAdapter final : public EagerExecuteOp, | |||
| 63 | public ArgsUpdater, | 69 | public ArgsUpdater, |
| 64 | public CustomOpCapabilityProvider { | 70 | public CustomOpCapabilityProvider { |
| 65 | public: | 71 | public: |
| 66 | - explicit PythonCustomOpAdapter(PythonCustomOpDescriptor desc); | 72 | + explicit PythonCustomOpAdapter(PythonCustomOpAdapterDescriptor desc); |
| 67 | ~PythonCustomOpAdapter() override; | 73 | ~PythonCustomOpAdapter() override; |
| 68 | 74 | ||
| 69 | bool IsValid() const; | 75 | bool IsValid() const; |
| @@ -81,8 +87,8 @@ class PythonCustomOpAdapter final : public EagerExecuteOp, | |||
| 81 | private: | 87 | private: |
| 82 | graphStatus ReportUnsupported(CustomOpCapability capability, const char *method_name) const; | 88 | graphStatus ReportUnsupported(CustomOpCapability capability, const char *method_name) const; |
| 83 | 89 | ||
| 84 | - PythonCustomOpDescriptor desc_; | 90 | + PythonCustomOpAdapterDescriptor desc_; |
| 85 | - std::unique_ptr<PythonCustomOpHolder> holder_; | 91 | + std::unique_ptr<PythonCustomOpImplHolder> holder_; |
| 86 | }; | 92 | }; |
| 87 | 93 | ||
| 88 | void ClearPythonCustomOpRuntimeRegistry(); | 94 | void ClearPythonCustomOpRuntimeRegistry(); |
| @@ -15,11 +15,14 @@ | |||
| 15 | 15 | ||
| 16 | namespace ge { | 16 | namespace ge { |
| 17 | namespace custom_op { | 17 | namespace custom_op { |
| 18 | -struct PythonCustomOpDescriptor; | 18 | +struct PythonCustomOpProtoDescriptorView; |
| 19 | -struct PythonCustomOpCallbacks; | 19 | +struct PythonCustomOpAdapterDescriptorView; |
| 20 | +struct PythonCustomOpAdapterCallbacks; | ||
| 20 | 21 | ||
| 21 | struct PythonCustomOpRegistrar { | 22 | struct PythonCustomOpRegistrar { |
| 22 | - bool (*register_custom_op)(const PythonCustomOpDescriptor *desc, const PythonCustomOpCallbacks *callbacks); | 23 | + bool (*register_op_proto)(const PythonCustomOpProtoDescriptorView *desc); |
| 24 | + bool (*register_op_adapter)(const PythonCustomOpAdapterDescriptorView *desc, | ||
| 25 | + const PythonCustomOpAdapterCallbacks *callbacks); | ||
| 23 | }; | 26 | }; |
| 24 | 27 | ||
| 25 | struct PythonCustomOpBridgeArtifactConfig { | 28 | struct PythonCustomOpBridgeArtifactConfig { |
| @@ -0,0 +1,328 @@ | |||
| 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 | +namespace ge { | ||
| 24 | +namespace custom_op { | ||
| 25 | +namespace py = pybind11; | ||
| 26 | +namespace { | ||
| 27 | +constexpr const char *kInterfaceAnnotatedArgs = "annotated_args"; | ||
| 28 | +constexpr const char *kInterfaceEagerExecute = "eager_execute"; | ||
| 29 | + | ||
| 30 | +PythonCustomOpStringView MakeStringView(const std::string &value) { | ||
| 31 | + return PythonCustomOpStringView{value.data(), value.size()}; | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +bool IsStrictInt(const py::handle &value) { | ||
| 35 | + return py::isinstance<py::int_>(value) && (!py::isinstance<py::bool_>(value)); | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +bool IsStrictFloat(const py::handle &value) { | ||
| 39 | + return py::isinstance<py::float_>(value); | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +Status ParseAttrKind(const std::string &type, uint32_t &kind) { | ||
| 43 | + static const std::map<std::string, uint32_t> kAttrKinds = { | ||
| 44 | + {"VT_INT", kPythonAttrInt}, | ||
| 45 | + {"VT_FLOAT", kPythonAttrFloat}, | ||
| 46 | + {"VT_BOOL", kPythonAttrBool}, | ||
| 47 | + {"VT_STRING", kPythonAttrString}, | ||
| 48 | + {"VT_DATA_TYPE", kPythonAttrDataType}, | ||
| 49 | + {"VT_TENSOR", kPythonAttrTensor}, | ||
| 50 | + {"VT_LIST_INT", kPythonAttrListInt}, | ||
| 51 | + {"VT_LIST_FLOAT", kPythonAttrListFloat}, | ||
| 52 | + {"VT_LIST_BOOL", kPythonAttrListBool}, | ||
| 53 | + {"VT_LIST_STRING", kPythonAttrListString}, | ||
| 54 | + {"VT_LIST_DATA_TYPE", kPythonAttrListDataType}, | ||
| 55 | + {"VT_LIST_LIST_INT", kPythonAttrListListInt}, | ||
| 56 | + }; | ||
| 57 | + const auto iter = kAttrKinds.find(type); | ||
| 58 | + if (iter == kAttrKinds.cend()) { | ||
| 59 | + return FAILED; | ||
| 60 | + } | ||
| 61 | + kind = iter->second; | ||
| 62 | + return SUCCESS; | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +Status ParseInterfaces(const py::object &interfaces_obj, CustomOpCapabilityMask &capabilities) { | ||
| 66 | + capabilities = 0U; | ||
| 67 | + for (const auto &item : interfaces_obj.cast<py::list>()) { | ||
| 68 | + const std::string interface_name = py::str(item); | ||
| 69 | + if (interface_name == kInterfaceEagerExecute) { | ||
| 70 | + AddCustomOpCapability(capabilities, CustomOpCapability::kEagerExecute); | ||
| 71 | + } else if (interface_name == kInterfaceAnnotatedArgs) { | ||
| 72 | + AddCustomOpCapability(capabilities, CustomOpCapability::kAnnotatedArgs); | ||
| 73 | + } else { | ||
| 74 | + GELOGE(FAILED, "Unsupported python custom op interface[%s].", interface_name.c_str()); | ||
| 75 | + return FAILED; | ||
| 76 | + } | ||
| 77 | + } | ||
| 78 | + return capabilities == 0U ? FAILED : SUCCESS; | ||
| 79 | +} | ||
| 80 | +} // namespace | ||
| 81 | + | ||
| 82 | +Status ProtoAttrStorage::Parse(const py::dict &item) { | ||
| 83 | + try { | ||
| 84 | + name = py::str(item["name"]); | ||
| 85 | + type = py::str(item["type"]); | ||
| 86 | + if (ParseAttrKind(type, kind) != SUCCESS) { | ||
| 87 | + return FAILED; | ||
| 88 | + } | ||
| 89 | + const auto required_obj = item["is_required"]; | ||
| 90 | + if (!py::isinstance<py::bool_>(required_obj)) { | ||
| 91 | + return FAILED; | ||
| 92 | + } | ||
| 93 | + is_required = required_obj.cast<bool>(); | ||
| 94 | + const auto value = item["default"]; | ||
| 95 | + if (is_required) { | ||
| 96 | + return value.is_none() ? SUCCESS : FAILED; | ||
| 97 | + } | ||
| 98 | + if (value.is_none() || (kind == kPythonAttrTensor)) { | ||
| 99 | + return FAILED; | ||
| 100 | + } | ||
| 101 | + switch (kind) { | ||
| 102 | + case kPythonAttrInt: | ||
| 103 | + if (!IsStrictInt(value)) { | ||
| 104 | + return FAILED; | ||
| 105 | + } | ||
| 106 | + int_value = value.cast<int64_t>(); | ||
| 107 | + return SUCCESS; | ||
| 108 | + case kPythonAttrFloat: | ||
| 109 | + if (!IsStrictFloat(value)) { | ||
| 110 | + return FAILED; | ||
| 111 | + } | ||
| 112 | + float_value = value.cast<double>(); | ||
| 113 | + return SUCCESS; | ||
| 114 | + case kPythonAttrBool: | ||
| 115 | + if (!py::isinstance<py::bool_>(value)) { | ||
| 116 | + return FAILED; | ||
| 117 | + } | ||
| 118 | + bool_value = value.cast<bool>() ? 1U : 0U; | ||
| 119 | + return SUCCESS; | ||
| 120 | + case kPythonAttrString: | ||
| 121 | + if (!py::isinstance<py::str>(value)) { | ||
| 122 | + return FAILED; | ||
| 123 | + } | ||
| 124 | + string_value = py::str(value); | ||
| 125 | + return SUCCESS; | ||
| 126 | + case kPythonAttrDataType: | ||
| 127 | + if (!IsStrictInt(value)) { | ||
| 128 | + return FAILED; | ||
| 129 | + } | ||
| 130 | + data_type_value = value.cast<int32_t>(); | ||
| 131 | + return SUCCESS; | ||
| 132 | + default: | ||
| 133 | + return ParseList(value); | ||
| 134 | + } | ||
| 135 | + } catch (const py::error_already_set &err) { | ||
| 136 | + GELOGE(FAILED, "Parse python custom op attr failed: %s", err.what()); | ||
| 137 | + return FAILED; | ||
| 138 | + } catch (const std::exception &err) { | ||
| 139 | + GELOGE(FAILED, "Parse python custom op attr failed: %s", err.what()); | ||
| 140 | + return FAILED; | ||
| 141 | + } | ||
| 142 | +} | ||
| 143 | + | ||
| 144 | +Status ProtoAttrStorage::ParseList(const py::handle &value) { | ||
| 145 | + if (!py::isinstance<py::list>(value)) { | ||
| 146 | + return FAILED; | ||
| 147 | + } | ||
| 148 | + const py::list values = py::reinterpret_borrow<py::list>(value); | ||
| 149 | + for (const auto &element : values) { | ||
| 150 | + switch (kind) { | ||
| 151 | + case kPythonAttrListInt: | ||
| 152 | + if (!IsStrictInt(element)) { | ||
| 153 | + return FAILED; | ||
| 154 | + } | ||
| 155 | + list_int_values.emplace_back(element.cast<int64_t>()); | ||
| 156 | + break; | ||
| 157 | + case kPythonAttrListFloat: | ||
| 158 | + if (!IsStrictFloat(element)) { | ||
| 159 | + return FAILED; | ||
| 160 | + } | ||
| 161 | + list_float_values.emplace_back(element.cast<double>()); | ||
| 162 | + break; | ||
| 163 | + case kPythonAttrListBool: | ||
| 164 | + if (!py::isinstance<py::bool_>(element)) { | ||
| 165 | + return FAILED; | ||
| 166 | + } | ||
| 167 | + list_bool_values.emplace_back(element.cast<bool>() ? 1U : 0U); | ||
| 168 | + break; | ||
| 169 | + case kPythonAttrListString: | ||
| 170 | + if (!py::isinstance<py::str>(element)) { | ||
| 171 | + return FAILED; | ||
| 172 | + } | ||
| 173 | + list_string_values.emplace_back(py::str(element)); | ||
| 174 | + break; | ||
| 175 | + case kPythonAttrListDataType: | ||
| 176 | + if (!IsStrictInt(element)) { | ||
| 177 | + return FAILED; | ||
| 178 | + } | ||
| 179 | + list_data_type_values.emplace_back(element.cast<int32_t>()); | ||
| 180 | + break; | ||
| 181 | + case kPythonAttrListListInt: { | ||
| 182 | + if (!py::isinstance<py::list>(element)) { | ||
| 183 | + return FAILED; | ||
| 184 | + } | ||
| 185 | + std::vector<int64_t> row; | ||
| 186 | + for (const auto &row_element : element.cast<py::list>()) { | ||
| 187 | + if (!IsStrictInt(row_element)) { | ||
| 188 | + return FAILED; | ||
| 189 | + } | ||
| 190 | + row.emplace_back(row_element.cast<int64_t>()); | ||
| 191 | + } | ||
| 192 | + list_list_int_values.emplace_back(std::move(row)); | ||
| 193 | + break; | ||
| 194 | + } | ||
| 195 | + default: | ||
| 196 | + return FAILED; | ||
| 197 | + } | ||
| 198 | + } | ||
| 199 | + return SUCCESS; | ||
| 200 | +} | ||
| 201 | + | ||
| 202 | +PythonCustomOpProtoAttrView ProtoAttrStorage::BuildView() { | ||
| 203 | + PythonCustomOpAttrDefaultView default_view{}; | ||
| 204 | + default_view.has_value = is_required ? 0U : 1U; | ||
| 205 | + default_view.int_value = int_value; | ||
| 206 | + default_view.float_value = float_value; | ||
| 207 | + default_view.bool_value = bool_value; | ||
| 208 | + default_view.string_value = MakeStringView(string_value); | ||
| 209 | + default_view.data_type_value = data_type_value; | ||
| 210 | + default_view.list_int_values = list_int_values.empty() ? nullptr : list_int_values.data(); | ||
| 211 | + default_view.list_float_values = list_float_values.empty() ? nullptr : list_float_values.data(); | ||
| 212 | + default_view.list_bool_values = list_bool_values.empty() ? nullptr : list_bool_values.data(); | ||
| 213 | + list_string_views.clear(); | ||
| 214 | + list_string_views.reserve(list_string_values.size()); | ||
| 215 | + for (const auto &value : list_string_values) { | ||
| 216 | + list_string_views.emplace_back(MakeStringView(value)); | ||
| 217 | + } | ||
| 218 | + default_view.list_string_values = list_string_views.empty() ? nullptr : list_string_views.data(); | ||
| 219 | + default_view.list_data_type_values = list_data_type_values.empty() ? nullptr : list_data_type_values.data(); | ||
| 220 | + list_list_int_views.clear(); | ||
| 221 | + list_list_int_views.reserve(list_list_int_values.size()); | ||
| 222 | + for (const auto &row : list_list_int_values) { | ||
| 223 | + list_list_int_views.emplace_back(PythonCustomOpInt64ArrayView{row.empty() ? nullptr : row.data(), row.size()}); | ||
| 224 | + } | ||
| 225 | + default_view.list_list_int_values = list_list_int_views.empty() ? nullptr : list_list_int_views.data(); | ||
| 226 | + switch (kind) { | ||
| 227 | + case kPythonAttrListInt: | ||
| 228 | + default_view.count = list_int_values.size(); | ||
| 229 | + break; | ||
| 230 | + case kPythonAttrListFloat: | ||
| 231 | + default_view.count = list_float_values.size(); | ||
| 232 | + break; | ||
| 233 | + case kPythonAttrListBool: | ||
| 234 | + default_view.count = list_bool_values.size(); | ||
| 235 | + break; | ||
| 236 | + case kPythonAttrListString: | ||
| 237 | + default_view.count = list_string_values.size(); | ||
| 238 | + break; | ||
| 239 | + case kPythonAttrListDataType: | ||
| 240 | + default_view.count = list_data_type_values.size(); | ||
| 241 | + break; | ||
| 242 | + case kPythonAttrListListInt: | ||
| 243 | + default_view.count = list_list_int_values.size(); | ||
| 244 | + break; | ||
| 245 | + default: | ||
| 246 | + break; | ||
| 247 | + } | ||
| 248 | + return PythonCustomOpProtoAttrView{MakeStringView(name), kind, static_cast<uint8_t>(is_required ? 1U : 0U), | ||
| 249 | + default_view}; | ||
| 250 | +} | ||
| 251 | + | ||
| 252 | +Status ProtoDescriptorStorage::Parse(const py::dict &dict) { | ||
| 253 | + try { | ||
| 254 | + descriptor_key = py::str(dict["descriptor_key"]); | ||
| 255 | + op_type = py::str(dict["op_type"]); | ||
| 256 | + for (const auto &item : dict["inputs"].cast<py::list>()) { | ||
| 257 | + const auto input = item.cast<py::dict>(); | ||
| 258 | + inputs.emplace_back(ProtoInputStorage{py::str(input["name"]), input["kind"].cast<uint32_t>()}); | ||
| 259 | + } | ||
| 260 | + for (const auto &item : dict["attrs"].cast<py::list>()) { | ||
| 261 | + ProtoAttrStorage attr; | ||
| 262 | + if (attr.Parse(item.cast<py::dict>()) != SUCCESS) { | ||
| 263 | + return FAILED; | ||
| 264 | + } | ||
| 265 | + attrs.emplace_back(std::move(attr)); | ||
| 266 | + } | ||
| 267 | + for (const auto &item : dict["outputs"].cast<py::list>()) { | ||
| 268 | + const auto output = item.cast<py::dict>(); | ||
| 269 | + outputs.emplace_back(ProtoOutputStorage{py::str(output["name"]), output["kind"].cast<uint32_t>()}); | ||
| 270 | + } | ||
| 271 | + } catch (const py::error_already_set &err) { | ||
| 272 | + GELOGE(FAILED, "Parse python custom op proto descriptor failed: %s", err.what()); | ||
| 273 | + return FAILED; | ||
| 274 | + } catch (const std::exception &err) { | ||
| 275 | + GELOGE(FAILED, "Parse python custom op proto descriptor failed: %s", err.what()); | ||
| 276 | + return FAILED; | ||
| 277 | + } | ||
| 278 | + return SUCCESS; | ||
| 279 | +} | ||
| 280 | + | ||
| 281 | +PythonCustomOpProtoDescriptorView ProtoDescriptorStorage::BuildView() { | ||
| 282 | + input_views.clear(); | ||
| 283 | + input_views.reserve(inputs.size()); | ||
| 284 | + for (const auto &input : inputs) { | ||
| 285 | + input_views.emplace_back(PythonCustomOpProtoInputView{MakeStringView(input.name), input.kind}); | ||
| 286 | + } | ||
| 287 | + attr_views.clear(); | ||
| 288 | + attr_views.reserve(attrs.size()); | ||
| 289 | + for (auto &attr : attrs) { | ||
| 290 | + attr_views.emplace_back(attr.BuildView()); | ||
| 291 | + } | ||
| 292 | + output_views.clear(); | ||
| 293 | + output_views.reserve(outputs.size()); | ||
| 294 | + for (const auto &output : outputs) { | ||
| 295 | + output_views.emplace_back(PythonCustomOpProtoOutputView{MakeStringView(output.name), output.kind}); | ||
| 296 | + } | ||
| 297 | + return PythonCustomOpProtoDescriptorView{ | ||
| 298 | + MakeStringView(descriptor_key), | ||
| 299 | + MakeStringView(op_type), | ||
| 300 | + input_views.empty() ? nullptr : input_views.data(), | ||
| 301 | + input_views.size(), | ||
| 302 | + attr_views.empty() ? nullptr : attr_views.data(), | ||
| 303 | + attr_views.size(), | ||
| 304 | + output_views.empty() ? nullptr : output_views.data(), | ||
| 305 | + output_views.size(), | ||
| 306 | + }; | ||
| 307 | +} | ||
| 308 | + | ||
| 309 | +Status AdapterDescriptorStorage::Parse(const py::dict &dict) { | ||
| 310 | + try { | ||
| 311 | + op_type = py::str(dict["op_type"]); | ||
| 312 | + impl_descriptor_key = py::str(dict["descriptor_key"]); | ||
| 313 | + return ParseInterfaces(dict["interfaces"], capabilities); | ||
| 314 | + } catch (const py::error_already_set &err) { | ||
| 315 | + GELOGE(FAILED, "Parse python custom op adapter descriptor failed: %s", err.what()); | ||
| 316 | + return FAILED; | ||
| 317 | + } catch (const std::exception &err) { | ||
| 318 | + GELOGE(FAILED, "Parse python custom op adapter descriptor failed: %s", err.what()); | ||
| 319 | + return FAILED; | ||
| 320 | + } | ||
| 321 | +} | ||
| 322 | + | ||
| 323 | +PythonCustomOpAdapterDescriptorView AdapterDescriptorStorage::BuildView() const { | ||
| 324 | + return PythonCustomOpAdapterDescriptorView{MakeStringView(op_type), MakeStringView(impl_descriptor_key), | ||
| 325 | + capabilities}; | ||
| 326 | +} | ||
| 327 | +} // namespace custom_op | ||
| 328 | +} // namespace ge | ||
| @@ -0,0 +1,82 @@ | |||
| 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 | +namespace ge { | ||
| 22 | +namespace custom_op { | ||
| 23 | +struct ProtoInputStorage { | ||
| 24 | + std::string name; | ||
| 25 | + uint32_t kind{0U}; | ||
| 26 | +}; | ||
| 27 | + | ||
| 28 | +struct ProtoOutputStorage { | ||
| 29 | + std::string name; | ||
| 30 | + uint32_t kind{0U}; | ||
| 31 | +}; | ||
| 32 | + | ||
| 33 | +struct ProtoAttrStorage { | ||
| 34 | + std::string name; | ||
| 35 | + std::string type; | ||
| 36 | + uint32_t kind{0U}; | ||
| 37 | + bool is_required{true}; | ||
| 38 | + int64_t int_value{0}; | ||
| 39 | + double float_value{0.0}; | ||
| 40 | + uint8_t bool_value{0U}; | ||
| 41 | + std::string string_value; | ||
| 42 | + int32_t data_type_value{0}; | ||
| 43 | + std::vector<int64_t> list_int_values; | ||
| 44 | + std::vector<double> list_float_values; | ||
| 45 | + std::vector<uint8_t> list_bool_values; | ||
| 46 | + std::vector<std::string> list_string_values; | ||
| 47 | + std::vector<PythonCustomOpStringView> list_string_views; | ||
| 48 | + std::vector<int32_t> list_data_type_values; | ||
| 49 | + std::vector<std::vector<int64_t>> list_list_int_values; | ||
| 50 | + std::vector<PythonCustomOpInt64ArrayView> list_list_int_views; | ||
| 51 | + | ||
| 52 | + Status Parse(const pybind11::dict &item); | ||
| 53 | + Status ParseList(const pybind11::handle &value); | ||
| 54 | + PythonCustomOpProtoAttrView BuildView(); | ||
| 55 | +}; | ||
| 56 | + | ||
| 57 | +struct ProtoDescriptorStorage { | ||
| 58 | + std::string descriptor_key; | ||
| 59 | + std::string op_type; | ||
| 60 | + std::vector<ProtoInputStorage> inputs; | ||
| 61 | + std::vector<ProtoAttrStorage> attrs; | ||
| 62 | + std::vector<ProtoOutputStorage> outputs; | ||
| 63 | + std::vector<PythonCustomOpProtoInputView> input_views; | ||
| 64 | + std::vector<PythonCustomOpProtoAttrView> attr_views; | ||
| 65 | + std::vector<PythonCustomOpProtoOutputView> output_views; | ||
| 66 | + | ||
| 67 | + Status Parse(const pybind11::dict &dict); | ||
| 68 | + PythonCustomOpProtoDescriptorView BuildView(); | ||
| 69 | +}; | ||
| 70 | + | ||
| 71 | +struct AdapterDescriptorStorage { | ||
| 72 | + std::string op_type; | ||
| 73 | + std::string impl_descriptor_key; | ||
| 74 | + CustomOpCapabilityMask capabilities{0U}; | ||
| 75 | + | ||
| 76 | + Status Parse(const pybind11::dict &dict); | ||
| 77 | + PythonCustomOpAdapterDescriptorView BuildView() const; | ||
| 78 | +}; | ||
| 79 | +} // namespace custom_op | ||
| 80 | +} // namespace ge | ||
| 81 | + | ||
| 82 | + | ||
| @@ -16,10 +16,12 @@ | |||
| 16 | 16 | ||
| 17 | 17 | ||
| 18 | 18 | ||
| 19 | + | ||
| 19 | 20 | ||
| 20 | 21 | ||
| 21 | 22 | ||
| 22 | 23 | ||
| 24 | + | ||
| 23 | 25 | ||
| 24 | 26 | ||
| 25 | 27 | ||
| @@ -32,6 +34,7 @@ | |||
| 32 | 34 | ||
| 33 | 35 | ||
| 34 | 36 | ||
| 37 | + | ||
| 35 | 38 | ||
| 36 | namespace ge { | 39 | namespace ge { |
| 37 | namespace custom_op { | 40 | namespace custom_op { |
| @@ -174,12 +177,11 @@ class PythonCustomOpBridgeLoader { | |||
| 174 | void Unload() { | 177 | void Unload() { |
| 175 | std::lock_guard<std::mutex> lock(mutex_); | 178 | std::lock_guard<std::mutex> lock(mutex_); |
| 176 | GELOGI("Unload python custom ops with bridge library[%s].", loaded_path_.c_str()); | 179 | GELOGI("Unload python custom ops with bridge library[%s].", loaded_path_.c_str()); |
| 177 | - CustomOpFactory::RemoveCustomOps(GetRegisteredOpTypes()); | 180 | + ClearPythonCustomOpRegistrations(); |
| 181 | + ClearRegisteredState(); | ||
| 178 | if ((api_ != nullptr) && (api_->reset_bridge_state != nullptr)) { | 182 | if ((api_ != nullptr) && (api_->reset_bridge_state != nullptr)) { |
| 179 | api_->reset_bridge_state(); | 183 | api_->reset_bridge_state(); |
| 180 | } | 184 | } |
| 181 | - ClearPythonCustomOpRuntimeRegistry(); | ||
| 182 | - ClearRegisteredState(); | ||
| 183 | } | 185 | } |
| 184 | 186 | ||
| 185 | void ShutdownForProcess() { | 187 | void ShutdownForProcess() { |
| @@ -200,9 +202,26 @@ class PythonCustomOpBridgeLoader { | |||
| 200 | } | 202 | } |
| 201 | 203 | ||
| 202 | private: | 204 | private: |
| 205 | + void ClearPythonCustomOpRegistrations() { | ||
| 206 | + std::vector<AscendString> adapter_op_types; | ||
| 207 | + adapter_op_types.reserve(registered_op_type_to_impl_descriptor_key_.size()); | ||
| 208 | + for (const auto &item : registered_op_type_to_impl_descriptor_key_) { | ||
| 209 | + adapter_op_types.emplace_back(item.first.c_str()); | ||
| 210 | + } | ||
| 211 | + CustomOpFactory::RemoveCustomOps(adapter_op_types); | ||
| 212 | + ClearPythonCustomOpRuntimeRegistry(); | ||
| 213 | + std::vector<std::string> proto_op_types; | ||
| 214 | + proto_op_types.reserve(registered_op_type_to_proto_key_.size()); | ||
| 215 | + for (const auto &item : registered_op_type_to_proto_key_) { | ||
| 216 | + proto_op_types.emplace_back(item.first); | ||
| 217 | + } | ||
| 218 | + UnregisterPythonCustomOpProtos(proto_op_types); | ||
| 219 | + } | ||
| 220 | + | ||
| 203 | Status RegisterCustomOpsFromBridge() { | 221 | Status RegisterCustomOpsFromBridge() { |
| 204 | static constexpr PythonCustomOpRegistrar kRegistrar = { | 222 | static constexpr PythonCustomOpRegistrar kRegistrar = { |
| 205 | - &RegisterCustomOpFromBridge, | 223 | + &RegisterOpProtoFromBridge, |
| 224 | + &RegisterOpAdapterFromBridge, | ||
| 206 | }; | 225 | }; |
| 207 | GELOGI("Register python custom ops with bridge library[%s].", loaded_path_.c_str()); | 226 | GELOGI("Register python custom ops with bridge library[%s].", loaded_path_.c_str()); |
| 208 | const auto ret = api_->register_custom_ops(&kRegistrar); | 227 | const auto ret = api_->register_custom_ops(&kRegistrar); |
| @@ -213,29 +232,93 @@ class PythonCustomOpBridgeLoader { | |||
| 213 | return SUCCESS; | 232 | return SUCCESS; |
| 214 | } | 233 | } |
| 215 | 234 | ||
| 216 | - static bool RegisterCustomOpFromBridge(const PythonCustomOpDescriptor *desc, | 235 | + static bool RegisterOpProtoFromBridge(const PythonCustomOpProtoDescriptorView *desc) { |
| 217 | - const PythonCustomOpCallbacks *callbacks) { | 236 | + if (desc == nullptr) { |
| 237 | + return false; | ||
| 238 | + } | ||
| 239 | + return GetInstance().RegisterOpProto(*desc); | ||
| 240 | + } | ||
| 241 | + | ||
| 242 | + static bool RegisterOpAdapterFromBridge(const PythonCustomOpAdapterDescriptorView *desc, | ||
| 243 | + const PythonCustomOpAdapterCallbacks *callbacks) { | ||
| 218 | if ((desc == nullptr) || (callbacks == nullptr)) { | 244 | if ((desc == nullptr) || (callbacks == nullptr)) { |
| 219 | return false; | 245 | return false; |
| 220 | } | 246 | } |
| 221 | - return GetInstance().RegisterCustomOp(*desc, *callbacks); | 247 | + return GetInstance().RegisterOpAdapter(*desc, *callbacks); |
| 222 | } | 248 | } |
| 223 | 249 | ||
| 224 | - bool RegisterCustomOp(const PythonCustomOpDescriptor &desc, const PythonCustomOpCallbacks &callbacks) { | 250 | + bool RegisterOpProto(const PythonCustomOpProtoDescriptorView &view) { |
| 225 | - const auto registered_op_type_iter = registered_op_type_to_descriptor_key_.find(desc.op_type); | 251 | + PythonCustomOpProto proto; |
| 226 | - if (registered_op_type_iter != registered_op_type_to_descriptor_key_.end()) { | 252 | + if (ParsePythonCustomOpProto(view, proto) != GRAPH_SUCCESS) { |
| 227 | - if (registered_op_type_iter->second == desc.descriptor_key) { | 253 | + GELOGE(FAILED, "[Parse][PythonCustomOpProto] failed."); |
| 254 | + return false; | ||
| 255 | + } | ||
| 256 | + const auto existing = registered_op_type_to_proto_key_.find(proto.op_type); | ||
| 257 | + if (existing != registered_op_type_to_proto_key_.cend()) { | ||
| 258 | + if (existing->second == proto.descriptor_key) { | ||
| 228 | return true; | 259 | return true; |
| 229 | } | 260 | } |
| 230 | GELOGE(FAILED, | 261 | GELOGE(FAILED, |
| 231 | - "[Check][PythonCustomOp]Op type[%s] has been registered with descriptor key[%s] by python custom op, " | 262 | + "Python custom op proto conflict, op type[%s], existing source[descriptor key:%s], " |
| 232 | - "current descriptor key is[%s].", | 263 | + "current source[descriptor key:%s].", |
| 233 | - desc.op_type.c_str(), registered_op_type_iter->second.c_str(), desc.descriptor_key.c_str()); | 264 | + proto.op_type.c_str(), existing->second.c_str(), proto.descriptor_key.c_str()); |
| 234 | return false; | 265 | return false; |
| 235 | } | 266 | } |
| 236 | - if (!PythonCustomOpRuntimeRegistry::Register(desc, callbacks)) { | 267 | + if (RegisterPythonCustomOpProto(proto) != GRAPH_SUCCESS) { |
| 237 | - GELOGE(FAILED, "[Register][PythonCustomOpRuntimeRegistry] failed, descriptor key[%s], op type[%s].", | 268 | + GELOGE(FAILED, "[Register][PythonCustomOpProto] failed, descriptor key[%s], op type[%s].", |
| 238 | - desc.descriptor_key.c_str(), desc.op_type.c_str()); | 269 | + proto.descriptor_key.c_str(), proto.op_type.c_str()); |
| 270 | + return false; | ||
| 271 | + } | ||
| 272 | + (void)registered_op_type_to_proto_key_.emplace(proto.op_type, proto.descriptor_key); | ||
| 273 | + return true; | ||
| 274 | + } | ||
| 275 | + | ||
| 276 | + static bool CopyStringView(const PythonCustomOpStringView &view, const bool allow_empty, std::string &value) { | ||
| 277 | + if ((view.size != 0U) && (view.data == nullptr)) { | ||
| 278 | + return false; | ||
| 279 | + } | ||
| 280 | + value.assign(view.data == nullptr ? "" : view.data, view.size); | ||
| 281 | + return (allow_empty || (!value.empty())) && (value.find('\0') == std::string::npos); | ||
| 282 | + } | ||
| 283 | + | ||
| 284 | + static bool ParseAdapterDescriptor(const PythonCustomOpAdapterDescriptorView &view, | ||
| 285 | + PythonCustomOpAdapterDescriptor &desc) { | ||
| 286 | + if ((!CopyStringView(view.op_type, false, desc.op_type)) || | ||
| 287 | + (!CopyStringView(view.impl_descriptor_key, false, desc.impl_descriptor_key))) { | ||
| 288 | + return false; | ||
| 289 | + } | ||
| 290 | + desc.capabilities = view.capabilities; | ||
| 291 | + return true; | ||
| 292 | + } | ||
| 293 | + | ||
| 294 | + bool RegisterOpAdapter(const PythonCustomOpAdapterDescriptorView &view, | ||
| 295 | + const PythonCustomOpAdapterCallbacks &callbacks) { | ||
| 296 | + PythonCustomOpAdapterDescriptor desc; | ||
| 297 | + if (!ParseAdapterDescriptor(view, desc)) { | ||
| 298 | + GELOGE(FAILED, "[Parse][PythonCustomOpAdapter] failed."); | ||
| 299 | + return false; | ||
| 300 | + } | ||
| 301 | + const auto existing = registered_op_type_to_impl_descriptor_key_.find(desc.op_type); | ||
| 302 | + if (existing != registered_op_type_to_impl_descriptor_key_.cend()) { | ||
| 303 | + if (existing->second == desc.impl_descriptor_key) { | ||
| 304 | + return true; | ||
| 305 | + } | ||
| 306 | + GELOGE(FAILED, | ||
| 307 | + "Python custom op adapter conflict, op type[%s], existing source[impl key:%s], " | ||
| 308 | + "current source[impl key:%s].", | ||
| 309 | + desc.op_type.c_str(), existing->second.c_str(), desc.impl_descriptor_key.c_str()); | ||
| 310 | + return false; | ||
| 311 | + } | ||
| 312 | + if (CustomOpFactory::IsExistOp(AscendString(desc.op_type.c_str()))) { | ||
| 313 | + GELOGE(FAILED, | ||
| 314 | + "[Check][PythonCustomOpAdapter]Op type[%s] conflicts, existing source[CustomOpFactory creator], " | ||
| 315 | + "current source[Python impl descriptor key:%s].", | ||
| 316 | + desc.op_type.c_str(), desc.impl_descriptor_key.c_str()); | ||
| 317 | + return false; | ||
| 318 | + } | ||
| 319 | + if (!PythonCustomOpImplRuntimeRegistry::Register(desc, callbacks)) { | ||
| 320 | + GELOGE(FAILED, "[Register][PythonCustomOpImplRuntimeRegistry] failed, descriptor key[%s], op type[%s].", | ||
| 321 | + desc.impl_descriptor_key.c_str(), desc.op_type.c_str()); | ||
| 239 | return false; | 322 | return false; |
| 240 | } | 323 | } |
| 241 | 324 | ||
| @@ -249,28 +332,20 @@ class PythonCustomOpBridgeLoader { | |||
| 249 | return std::unique_ptr<BaseCustomOp>(adapter); | 332 | return std::unique_ptr<BaseCustomOp>(adapter); |
| 250 | }); | 333 | }); |
| 251 | if (ret != GRAPH_SUCCESS) { | 334 | if (ret != GRAPH_SUCCESS) { |
| 252 | - (void)PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key); | 335 | + (void)PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key); |
| 253 | GELOGE(FAILED, "[Register][PythonCustomOpCreator] failed, descriptor key[%s], op type[%s].", | 336 | GELOGE(FAILED, "[Register][PythonCustomOpCreator] failed, descriptor key[%s], op type[%s].", |
| 254 | - desc.descriptor_key.c_str(), desc.op_type.c_str()); | 337 | + desc.impl_descriptor_key.c_str(), desc.op_type.c_str()); |
| 255 | return false; | 338 | return false; |
| 256 | } | 339 | } |
| 257 | - registered_op_type_to_descriptor_key_[desc.op_type] = desc.descriptor_key; | 340 | + (void)registered_op_type_to_impl_descriptor_key_.emplace(desc.op_type, desc.impl_descriptor_key); |
| 258 | - GELOGI("Python custom op[%s] is registered from bridge, descriptor key[%s].", desc.op_type.c_str(), | 341 | + GELOGI("Python custom op[%s] adapter is registered, impl key[%s].", desc.op_type.c_str(), |
| 259 | - desc.descriptor_key.c_str()); | 342 | + desc.impl_descriptor_key.c_str()); |
| 260 | return true; | 343 | return true; |
| 261 | } | 344 | } |
| 262 | 345 | ||
| 263 | void ClearRegisteredState() { | 346 | void ClearRegisteredState() { |
| 264 | - registered_op_type_to_descriptor_key_.clear(); | 347 | + registered_op_type_to_proto_key_.clear(); |
| 265 | - } | 348 | + registered_op_type_to_impl_descriptor_key_.clear(); |
| 266 | - | ||
| 267 | - std::vector<AscendString> GetRegisteredOpTypes() const { | ||
| 268 | - std::vector<AscendString> op_types; | ||
| 269 | - op_types.reserve(registered_op_type_to_descriptor_key_.size()); | ||
| 270 | - for (const auto &item : registered_op_type_to_descriptor_key_) { | ||
| 271 | - op_types.emplace_back(AscendString(item.first.c_str())); | ||
| 272 | - } | ||
| 273 | - return op_types; | ||
| 274 | } | 349 | } |
| 275 | 350 | ||
| 276 | Status EnsureLoaded() { | 351 | Status EnsureLoaded() { |
| @@ -333,7 +408,8 @@ class PythonCustomOpBridgeLoader { | |||
| 333 | void *handle_{nullptr}; | 408 | void *handle_{nullptr}; |
| 334 | const PythonCustomOpBridgeApi *api_{nullptr}; | 409 | const PythonCustomOpBridgeApi *api_{nullptr}; |
| 335 | std::string loaded_path_; | 410 | std::string loaded_path_; |
| 336 | - std::map<std::string, std::string> registered_op_type_to_descriptor_key_; | 411 | + std::map<std::string, std::string> registered_op_type_to_proto_key_; |
| 412 | + std::map<std::string, std::string> registered_op_type_to_impl_descriptor_key_; | ||
| 337 | }; | 413 | }; |
| 338 | } // namespace | 414 | } // namespace |
| 339 | 415 | ||
| @@ -11,8 +11,8 @@ | |||
| 11 | 11 | ||
| 12 | 12 | ||
| 13 | 13 | ||
| 14 | + | ||
| 14 | 15 | ||
| 15 | - | ||
| 16 | 16 | ||
| 17 | 17 | ||
| 18 | 18 | ||
| @@ -24,25 +24,102 @@ class EagerOpExecutionContext; | |||
| 24 | 24 | ||
| 25 | namespace ge { | 25 | namespace ge { |
| 26 | namespace custom_op { | 26 | namespace custom_op { |
| 27 | -struct PythonCustomOpDescriptor { | 27 | +struct PythonCustomOpStringView { |
| 28 | - std::string descriptor_key; | 28 | + const char *data; |
| 29 | - std::string op_type; | 29 | + size_t size; |
| 30 | - CustomOpCapabilityMask capabilities{0U}; | ||
| 31 | }; | 30 | }; |
| 32 | 31 | ||
| 33 | -// IR 原型由 Python bridge 通过 run 包正式公开的 GetRegisteredIrDef 接口获取并在 bridge 内部缓存, | 32 | +enum PythonCustomOpProtoInputKind : uint32_t { |
| 34 | -// 不再通过 runtime/bridge callback 传递 IR 的私有 POD 投影。 | 33 | + kPythonInputRequired = 0U, |
| 34 | + kPythonInputOptional = 1U, | ||
| 35 | + kPythonInputDynamic = 2U, | ||
| 36 | +}; | ||
| 35 | 37 | ||
| 36 | -using PythonCustomOpHolderCreateFn = void *(*)(const PythonCustomOpDescriptor *desc); | 38 | +enum PythonCustomOpProtoOutputKind : uint32_t { |
| 37 | -using PythonCustomOpHolderDestroyFn = void (*)(void *holder); | 39 | + kPythonOutputRequired = 0U, |
| 38 | -using PythonCustomOpExecuteFn = graphStatus (*)(const void *holder, gert::EagerOpExecutionContext *ctx); | 40 | + kPythonOutputDynamic = 1U, |
| 39 | -using PythonCustomOpDeclareLaunchArgsFn = graphStatus (*)(const void *holder, gert::AnnotatedArgsContext *ctx); | 41 | +}; |
| 40 | 42 | ||
| 41 | -struct PythonCustomOpCallbacks { | 43 | +enum PythonCustomOpProtoAttrKind : uint32_t { |
| 42 | - PythonCustomOpHolderCreateFn create{nullptr}; | 44 | + kPythonAttrInt = 0U, |
| 43 | - PythonCustomOpHolderDestroyFn destroy{nullptr}; | 45 | + kPythonAttrFloat = 1U, |
| 44 | - PythonCustomOpExecuteFn execute{nullptr}; | 46 | + kPythonAttrBool = 2U, |
| 45 | - PythonCustomOpDeclareLaunchArgsFn declare_launch_args{nullptr}; | 47 | + kPythonAttrString = 3U, |
| 48 | + kPythonAttrDataType = 4U, | ||
| 49 | + kPythonAttrTensor = 5U, | ||
| 50 | + kPythonAttrListInt = 6U, | ||
| 51 | + kPythonAttrListFloat = 7U, | ||
| 52 | + kPythonAttrListBool = 8U, | ||
| 53 | + kPythonAttrListString = 9U, | ||
| 54 | + kPythonAttrListDataType = 10U, | ||
| 55 | + kPythonAttrListListInt = 11U, | ||
| 56 | +}; | ||
| 57 | + | ||
| 58 | +struct PythonCustomOpInt64ArrayView { | ||
| 59 | + const int64_t *data; | ||
| 60 | + size_t count; | ||
| 61 | +}; | ||
| 62 | + | ||
| 63 | +struct PythonCustomOpAttrDefaultView { | ||
| 64 | + uint8_t has_value; | ||
| 65 | + int64_t int_value; | ||
| 66 | + double float_value; | ||
| 67 | + uint8_t bool_value; | ||
| 68 | + PythonCustomOpStringView string_value; | ||
| 69 | + int32_t data_type_value; | ||
| 70 | + const int64_t *list_int_values; | ||
| 71 | + const double *list_float_values; | ||
| 72 | + const uint8_t *list_bool_values; | ||
| 73 | + const PythonCustomOpStringView *list_string_values; | ||
| 74 | + const int32_t *list_data_type_values; | ||
| 75 | + const PythonCustomOpInt64ArrayView *list_list_int_values; | ||
| 76 | + size_t count; | ||
| 77 | +}; | ||
| 78 | + | ||
| 79 | +struct PythonCustomOpProtoInputView { | ||
| 80 | + PythonCustomOpStringView name; | ||
| 81 | + uint32_t kind; | ||
| 82 | +}; | ||
| 83 | + | ||
| 84 | +struct PythonCustomOpProtoAttrView { | ||
| 85 | + PythonCustomOpStringView name; | ||
| 86 | + uint32_t kind; | ||
| 87 | + uint8_t is_required; | ||
| 88 | + PythonCustomOpAttrDefaultView default_value; | ||
| 89 | +}; | ||
| 90 | + | ||
| 91 | +struct PythonCustomOpProtoOutputView { | ||
| 92 | + PythonCustomOpStringView name; | ||
| 93 | + uint32_t kind; | ||
| 94 | +}; | ||
| 95 | + | ||
| 96 | +struct PythonCustomOpProtoDescriptorView { | ||
| 97 | + PythonCustomOpStringView descriptor_key; | ||
| 98 | + PythonCustomOpStringView op_type; | ||
| 99 | + const PythonCustomOpProtoInputView *inputs; | ||
| 100 | + size_t input_count; | ||
| 101 | + const PythonCustomOpProtoAttrView *attrs; | ||
| 102 | + size_t attr_count; | ||
| 103 | + const PythonCustomOpProtoOutputView *outputs; | ||
| 104 | + size_t output_count; | ||
| 105 | +}; | ||
| 106 | + | ||
| 107 | +struct PythonCustomOpAdapterDescriptorView { | ||
| 108 | + PythonCustomOpStringView op_type; | ||
| 109 | + PythonCustomOpStringView impl_descriptor_key; | ||
| 110 | + CustomOpCapabilityMask capabilities; | ||
| 111 | +}; | ||
| 112 | + | ||
| 113 | +using PythonCustomOpImplHolderCreateFn = void *(*)(const PythonCustomOpAdapterDescriptorView *desc); | ||
| 114 | +using PythonCustomOpImplHolderDestroyFn = void (*)(void *holder); | ||
| 115 | +using PythonCustomOpImplExecuteFn = graphStatus (*)(const void *holder, gert::EagerOpExecutionContext *ctx); | ||
| 116 | +using PythonCustomOpImplDeclareLaunchArgsFn = graphStatus (*)(const void *holder, gert::AnnotatedArgsContext *ctx); | ||
| 117 | + | ||
| 118 | +struct PythonCustomOpAdapterCallbacks { | ||
| 119 | + PythonCustomOpImplHolderCreateFn create_impl_holder{nullptr}; | ||
| 120 | + PythonCustomOpImplHolderDestroyFn destroy_impl_holder{nullptr}; | ||
| 121 | + PythonCustomOpImplExecuteFn execute{nullptr}; | ||
| 122 | + PythonCustomOpImplDeclareLaunchArgsFn declare_launch_args{nullptr}; | ||
| 46 | 123 | ||
| 47 | bool IsValid(CustomOpCapabilityMask capabilities) const { | 124 | bool IsValid(CustomOpCapabilityMask capabilities) const { |
| 48 | const auto supported_capabilities = static_cast<CustomOpCapabilityMask>(CustomOpCapability::kEagerExecute) | | 125 | const auto supported_capabilities = static_cast<CustomOpCapabilityMask>(CustomOpCapability::kEagerExecute) | |
| @@ -50,7 +127,7 @@ struct PythonCustomOpCallbacks { | |||
| 50 | if ((capabilities == 0U) || ((capabilities & (~supported_capabilities)) != 0U)) { | 127 | if ((capabilities == 0U) || ((capabilities & (~supported_capabilities)) != 0U)) { |
| 51 | return false; | 128 | return false; |
| 52 | } | 129 | } |
| 53 | - if ((create == nullptr) || (destroy == nullptr)) { | 130 | + if ((create_impl_holder == nullptr) || (destroy_impl_holder == nullptr)) { |
| 54 | return false; | 131 | return false; |
| 55 | } | 132 | } |
| 56 | if (HasCustomOpCapability(capabilities, CustomOpCapability::kEagerExecute) && (execute == nullptr)) { | 133 | if (HasCustomOpCapability(capabilities, CustomOpCapability::kEagerExecute) && (execute == nullptr)) { |
| @@ -0,0 +1,484 @@ | |||
| 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 | +namespace ge { | ||
| 30 | +namespace custom_op { | ||
| 31 | +namespace { | ||
| 32 | +bool CopyString(const PythonCustomOpStringView &view, const bool allow_empty, std::string &value) { | ||
| 33 | + if ((view.size != 0U) && (view.data == nullptr)) { | ||
| 34 | + return false; | ||
| 35 | + } | ||
| 36 | + value.assign(view.data == nullptr ? "" : view.data, view.size); | ||
| 37 | + return (allow_empty || (!value.empty())) && (value.find('\0') == std::string::npos); | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +bool IsValidArray(const void *data, const size_t count) { | ||
| 41 | + return (count == 0U) || (data != nullptr); | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +bool IsValidDataType(const int32_t value) { | ||
| 45 | + return (value >= 0) && (value < static_cast<int32_t>(ge::DT_MAX)); | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +graphStatus ConvertInputKind(const uint32_t kind, ge::IrInputType &converted) { | ||
| 49 | + switch (kind) { | ||
| 50 | + case kPythonInputRequired: | ||
| 51 | + converted = ge::kIrInputRequired; | ||
| 52 | + return GRAPH_SUCCESS; | ||
| 53 | + case kPythonInputOptional: | ||
| 54 | + converted = ge::kIrInputOptional; | ||
| 55 | + return GRAPH_SUCCESS; | ||
| 56 | + case kPythonInputDynamic: | ||
| 57 | + converted = ge::kIrInputDynamic; | ||
| 58 | + return GRAPH_SUCCESS; | ||
| 59 | + default: | ||
| 60 | + return GRAPH_PARAM_INVALID; | ||
| 61 | + } | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +graphStatus ConvertOutputKind(const uint32_t kind, ge::IrOutputType &converted) { | ||
| 65 | + switch (kind) { | ||
| 66 | + case kPythonOutputRequired: | ||
| 67 | + converted = ge::kIrOutputRequired; | ||
| 68 | + return GRAPH_SUCCESS; | ||
| 69 | + case kPythonOutputDynamic: | ||
| 70 | + converted = ge::kIrOutputDynamic; | ||
| 71 | + return GRAPH_SUCCESS; | ||
| 72 | + default: | ||
| 73 | + return GRAPH_PARAM_INVALID; | ||
| 74 | + } | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +const char *GetRequiredAttrToken(const uint32_t kind) { | ||
| 78 | + static const std::map<uint32_t, const char *> kTokens = { | ||
| 79 | + {kPythonAttrInt, "Int"}, | ||
| 80 | + {kPythonAttrFloat, "Float"}, | ||
| 81 | + {kPythonAttrBool, "Bool"}, | ||
| 82 | + {kPythonAttrString, "String"}, | ||
| 83 | + {kPythonAttrDataType, "Type"}, | ||
| 84 | + {kPythonAttrTensor, "Tensor"}, | ||
| 85 | + {kPythonAttrListInt, "ListInt"}, | ||
| 86 | + {kPythonAttrListFloat, "ListFloat"}, | ||
| 87 | + {kPythonAttrListBool, "ListBool"}, | ||
| 88 | + {kPythonAttrListString, "ListString"}, | ||
| 89 | + {kPythonAttrListDataType, "ListType"}, | ||
| 90 | + {kPythonAttrListListInt, "ListListInt"}, | ||
| 91 | + }; | ||
| 92 | + const auto iter = kTokens.find(kind); | ||
| 93 | + return (iter == kTokens.cend()) ? nullptr : iter->second; | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +graphStatus ParseStringArray(const PythonCustomOpStringView *data, const size_t count, | ||
| 97 | + std::vector<std::string> &values) { | ||
| 98 | + if (!IsValidArray(data, count)) { | ||
| 99 | + return GRAPH_PARAM_INVALID; | ||
| 100 | + } | ||
| 101 | + values.reserve(count); | ||
| 102 | + for (size_t i = 0U; i < count; ++i) { | ||
| 103 | + std::string value; | ||
| 104 | + if (!CopyString(data[i], true, value)) { | ||
| 105 | + return GRAPH_PARAM_INVALID; | ||
| 106 | + } | ||
| 107 | + values.emplace_back(std::move(value)); | ||
| 108 | + } | ||
| 109 | + return GRAPH_SUCCESS; | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +graphStatus ParseOptionalAttrDefinition(const PythonCustomOpProtoAttrView &view, PythonCustomOpAttr &attr) { | ||
| 113 | + const auto &source = view.default_value; | ||
| 114 | + auto &definition = attr.default_definition; | ||
| 115 | + switch (view.kind) { | ||
| 116 | + case kPythonAttrInt: | ||
| 117 | + definition.int_value = source.int_value; | ||
| 118 | + return GRAPH_SUCCESS; | ||
| 119 | + case kPythonAttrFloat: { | ||
| 120 | + if (std::isfinite(source.float_value) && | ||
| 121 | + (std::fabs(source.float_value) > static_cast<double>(std::numeric_limits<float32_t>::max()))) { | ||
| 122 | + return GRAPH_PARAM_INVALID; | ||
| 123 | + } | ||
| 124 | + definition.float_value = source.float_value; | ||
| 125 | + return GRAPH_SUCCESS; | ||
| 126 | + } | ||
| 127 | + case kPythonAttrBool: | ||
| 128 | + if (source.bool_value > 1U) { | ||
| 129 | + return GRAPH_PARAM_INVALID; | ||
| 130 | + } | ||
| 131 | + definition.bool_value = source.bool_value != 0U; | ||
| 132 | + return GRAPH_SUCCESS; | ||
| 133 | + case kPythonAttrString: { | ||
| 134 | + if (!CopyString(source.string_value, true, definition.string_value)) { | ||
| 135 | + return GRAPH_PARAM_INVALID; | ||
| 136 | + } | ||
| 137 | + return GRAPH_SUCCESS; | ||
| 138 | + } | ||
| 139 | + case kPythonAttrDataType: | ||
| 140 | + if (!IsValidDataType(source.data_type_value)) { | ||
| 141 | + return GRAPH_PARAM_INVALID; | ||
| 142 | + } | ||
| 143 | + definition.data_type_value = source.data_type_value; | ||
| 144 | + return GRAPH_SUCCESS; | ||
| 145 | + case kPythonAttrTensor: | ||
| 146 | + return GRAPH_PARAM_INVALID; | ||
| 147 | + case kPythonAttrListInt: | ||
| 148 | + if (!IsValidArray(source.list_int_values, source.count)) { | ||
| 149 | + return GRAPH_PARAM_INVALID; | ||
| 150 | + } | ||
| 151 | + if (source.count != 0U) { | ||
| 152 | + definition.list_int_values.assign(source.list_int_values, source.list_int_values + source.count); | ||
| 153 | + } | ||
| 154 | + return GRAPH_SUCCESS; | ||
| 155 | + case kPythonAttrListFloat: { | ||
| 156 | + if (!IsValidArray(source.list_float_values, source.count)) { | ||
| 157 | + return GRAPH_PARAM_INVALID; | ||
| 158 | + } | ||
| 159 | + if (source.count != 0U) { | ||
| 160 | + definition.list_float_values.assign(source.list_float_values, source.list_float_values + source.count); | ||
| 161 | + } | ||
| 162 | + for (const auto value : definition.list_float_values) { | ||
| 163 | + if (std::isfinite(value) && (std::fabs(value) > static_cast<double>(std::numeric_limits<float32_t>::max()))) { | ||
| 164 | + return GRAPH_PARAM_INVALID; | ||
| 165 | + } | ||
| 166 | + } | ||
| 167 | + return GRAPH_SUCCESS; | ||
| 168 | + } | ||
| 169 | + case kPythonAttrListBool: { | ||
| 170 | + if (!IsValidArray(source.list_bool_values, source.count)) { | ||
| 171 | + return GRAPH_PARAM_INVALID; | ||
| 172 | + } | ||
| 173 | + definition.list_bool_values.reserve(source.count); | ||
| 174 | + for (size_t i = 0U; i < source.count; ++i) { | ||
| 175 | + if (source.list_bool_values[i] > 1U) { | ||
| 176 | + return GRAPH_PARAM_INVALID; | ||
| 177 | + } | ||
| 178 | + definition.list_bool_values.emplace_back(source.list_bool_values[i] != 0U); | ||
| 179 | + } | ||
| 180 | + return GRAPH_SUCCESS; | ||
| 181 | + } | ||
| 182 | + case kPythonAttrListString: { | ||
| 183 | + return ParseStringArray(source.list_string_values, source.count, definition.list_string_values); | ||
| 184 | + } | ||
| 185 | + case kPythonAttrListDataType: { | ||
| 186 | + if (!IsValidArray(source.list_data_type_values, source.count)) { | ||
| 187 | + return GRAPH_PARAM_INVALID; | ||
| 188 | + } | ||
| 189 | + definition.list_data_type_values.reserve(source.count); | ||
| 190 | + for (size_t i = 0U; i < source.count; ++i) { | ||
| 191 | + if (!IsValidDataType(source.list_data_type_values[i])) { | ||
| 192 | + return GRAPH_PARAM_INVALID; | ||
| 193 | + } | ||
| 194 | + definition.list_data_type_values.emplace_back(source.list_data_type_values[i]); | ||
| 195 | + } | ||
| 196 | + return GRAPH_SUCCESS; | ||
| 197 | + } | ||
| 198 | + case kPythonAttrListListInt: | ||
| 199 | + if (!IsValidArray(source.list_list_int_values, source.count)) { | ||
| 200 | + return GRAPH_PARAM_INVALID; | ||
| 201 | + } | ||
| 202 | + definition.list_list_int_values.reserve(source.count); | ||
| 203 | + for (size_t i = 0U; i < source.count; ++i) { | ||
| 204 | + const auto &row = source.list_list_int_values[i]; | ||
| 205 | + if (!IsValidArray(row.data, row.count)) { | ||
| 206 | + return GRAPH_PARAM_INVALID; | ||
| 207 | + } | ||
| 208 | + if (row.count == 0U) { | ||
| 209 | + definition.list_list_int_values.emplace_back(); | ||
| 210 | + } else { | ||
| 211 | + definition.list_list_int_values.emplace_back(row.data, row.data + row.count); | ||
| 212 | + } | ||
| 213 | + } | ||
| 214 | + return GRAPH_SUCCESS; | ||
| 215 | + default: | ||
| 216 | + return GRAPH_PARAM_INVALID; | ||
| 217 | + } | ||
| 218 | +} | ||
| 219 | + | ||
| 220 | +graphStatus MaterializeOptionalAttrValue(const PythonCustomOpAttr &attr, ge::AttrValue &value) { | ||
| 221 | + const auto &definition = attr.default_definition; | ||
| 222 | + switch (attr.kind) { | ||
| 223 | + case kPythonAttrInt: | ||
| 224 | + return value.SetAttrValue(definition.int_value); | ||
| 225 | + case kPythonAttrFloat: | ||
| 226 | + return value.SetAttrValue(static_cast<float32_t>(definition.float_value)); | ||
| 227 | + case kPythonAttrBool: | ||
| 228 | + return value.SetAttrValue(definition.bool_value); | ||
| 229 | + case kPythonAttrString: | ||
| 230 | + return value.SetAttrValue(AscendString(definition.string_value.c_str())); | ||
| 231 | + case kPythonAttrDataType: | ||
| 232 | + return value.SetAttrValue(static_cast<ge::DataType>(definition.data_type_value)); | ||
| 233 | + case kPythonAttrListInt: | ||
| 234 | + return value.SetAttrValue(definition.list_int_values); | ||
| 235 | + case kPythonAttrListFloat: { | ||
| 236 | + std::vector<float32_t> values; | ||
| 237 | + values.reserve(definition.list_float_values.size()); | ||
| 238 | + for (const auto item : definition.list_float_values) { | ||
| 239 | + values.emplace_back(static_cast<float32_t>(item)); | ||
| 240 | + } | ||
| 241 | + return value.SetAttrValue(values); | ||
| 242 | + } | ||
| 243 | + case kPythonAttrListBool: | ||
| 244 | + return value.SetAttrValue(definition.list_bool_values); | ||
| 245 | + case kPythonAttrListString: { | ||
| 246 | + std::vector<AscendString> values; | ||
| 247 | + values.reserve(definition.list_string_values.size()); | ||
| 248 | + for (const auto &item : definition.list_string_values) { | ||
| 249 | + values.emplace_back(item.c_str()); | ||
| 250 | + } | ||
| 251 | + return value.SetAttrValue(values); | ||
| 252 | + } | ||
| 253 | + case kPythonAttrListDataType: { | ||
| 254 | + std::vector<ge::DataType> values; | ||
| 255 | + values.reserve(definition.list_data_type_values.size()); | ||
| 256 | + for (const auto item : definition.list_data_type_values) { | ||
| 257 | + values.emplace_back(static_cast<ge::DataType>(item)); | ||
| 258 | + } | ||
| 259 | + return value.SetAttrValue(values); | ||
| 260 | + } | ||
| 261 | + case kPythonAttrListListInt: | ||
| 262 | + return value.SetAttrValue(definition.list_list_int_values); | ||
| 263 | + default: | ||
| 264 | + return GRAPH_PARAM_INVALID; | ||
| 265 | + } | ||
| 266 | +} | ||
| 267 | + | ||
| 268 | +bool SameDouble(const double lhs, const double rhs) { | ||
| 269 | + return ((lhs <= rhs) && (lhs >= rhs)) || (std::isnan(lhs) && std::isnan(rhs)); | ||
| 270 | +} | ||
| 271 | + | ||
| 272 | +bool SameDoubleVector(const std::vector<double> &lhs, const std::vector<double> &rhs) { | ||
| 273 | + if (lhs.size() != rhs.size()) { | ||
| 274 | + return false; | ||
| 275 | + } | ||
| 276 | + for (size_t i = 0U; i < lhs.size(); ++i) { | ||
| 277 | + if (!SameDouble(lhs[i], rhs[i])) { | ||
| 278 | + return false; | ||
| 279 | + } | ||
| 280 | + } | ||
| 281 | + return true; | ||
| 282 | +} | ||
| 283 | + | ||
| 284 | +bool SameDefault(const PythonCustomOpAttr &lhs, const PythonCustomOpAttr &rhs) { | ||
| 285 | + const auto &left = lhs.default_definition; | ||
| 286 | + const auto &right = rhs.default_definition; | ||
| 287 | + switch (lhs.kind) { | ||
| 288 | + case kPythonAttrInt: | ||
| 289 | + return left.int_value == right.int_value; | ||
| 290 | + case kPythonAttrFloat: | ||
| 291 | + return SameDouble(left.float_value, right.float_value); | ||
| 292 | + case kPythonAttrBool: | ||
| 293 | + return left.bool_value == right.bool_value; | ||
| 294 | + case kPythonAttrString: | ||
| 295 | + return left.string_value == right.string_value; | ||
| 296 | + case kPythonAttrDataType: | ||
| 297 | + return left.data_type_value == right.data_type_value; | ||
| 298 | + case kPythonAttrListInt: | ||
| 299 | + return left.list_int_values == right.list_int_values; | ||
| 300 | + case kPythonAttrListFloat: | ||
| 301 | + return SameDoubleVector(left.list_float_values, right.list_float_values); | ||
| 302 | + case kPythonAttrListBool: | ||
| 303 | + return left.list_bool_values == right.list_bool_values; | ||
| 304 | + case kPythonAttrListString: | ||
| 305 | + return left.list_string_values == right.list_string_values; | ||
| 306 | + case kPythonAttrListDataType: | ||
| 307 | + return left.list_data_type_values == right.list_data_type_values; | ||
| 308 | + case kPythonAttrListListInt: | ||
| 309 | + return left.list_list_int_values == right.list_list_int_values; | ||
| 310 | + case kPythonAttrTensor: | ||
| 311 | + return true; | ||
| 312 | + default: | ||
| 313 | + return false; | ||
| 314 | + } | ||
| 315 | +} | ||
| 316 | + | ||
| 317 | +class PythonCustomOpProtoOperator final : public ge::Operator { | ||
| 318 | + public: | ||
| 319 | + PythonCustomOpProtoOperator(const AscendString &name, const AscendString &type) : Operator(name, type) {} | ||
| 320 | + | ||
| 321 | + using Operator::InputRegister; | ||
| 322 | + using Operator::OptionalInputRegister; | ||
| 323 | + using Operator::OutputRegister; | ||
| 324 | + using Operator::RequiredAttrWithTypeRegister; | ||
| 325 | +}; | ||
| 326 | + | ||
| 327 | +ge::Operator CreateOperatorFromProto(const AscendString &name, const PythonCustomOpProto &proto) { | ||
| 328 | + PythonCustomOpProtoOperator op(name, AscendString(proto.op_type.c_str())); | ||
| 329 | + for (const auto &input : proto.inputs) { | ||
| 330 | + switch (input.kind) { | ||
| 331 | + case ge::kIrInputRequired: | ||
| 332 | + op.InputRegister(input.name.c_str()); | ||
| 333 | + break; | ||
| 334 | + case ge::kIrInputOptional: | ||
| 335 | + op.OptionalInputRegister(input.name.c_str()); | ||
| 336 | + break; | ||
| 337 | + case ge::kIrInputDynamic: | ||
| 338 | + op.DynamicInputRegister(input.name.c_str(), 0U); | ||
| 339 | + break; | ||
| 340 | + default: | ||
| 341 | + break; | ||
| 342 | + } | ||
| 343 | + } | ||
| 344 | + for (const auto &output : proto.outputs) { | ||
| 345 | + if (output.kind == ge::kIrOutputRequired) { | ||
| 346 | + op.OutputRegister(output.name.c_str()); | ||
| 347 | + } else if (output.kind == ge::kIrOutputDynamic) { | ||
| 348 | + op.DynamicOutputRegister(output.name.c_str(), 0U); | ||
| 349 | + } | ||
| 350 | + } | ||
| 351 | + for (const auto &attr : proto.attrs) { | ||
| 352 | + if (attr.is_required) { | ||
| 353 | + op.RequiredAttrWithTypeRegister(attr.name.c_str(), GetRequiredAttrToken(attr.kind)); | ||
| 354 | + } else { | ||
| 355 | + ge::AttrValue default_value; | ||
| 356 | + if (MaterializeOptionalAttrValue(attr, default_value) != GRAPH_SUCCESS) { | ||
| 357 | + GELOGE(GRAPH_FAILED, "Materialize python custom op attr[%s] default failed.", attr.name.c_str()); | ||
| 358 | + return ge::Operator(); | ||
| 359 | + } | ||
| 360 | + op.AttrRegister(attr.name.c_str(), default_value); | ||
| 361 | + } | ||
| 362 | + } | ||
| 363 | + return op; | ||
| 364 | +} | ||
| 365 | + | ||
| 366 | +} // namespace | ||
| 367 | + | ||
| 368 | +graphStatus ParsePythonCustomOpProto(const PythonCustomOpProtoDescriptorView &view, PythonCustomOpProto &proto) { | ||
| 369 | + if ((!IsValidArray(view.inputs, view.input_count)) || (!IsValidArray(view.attrs, view.attr_count)) || | ||
| 370 | + (!IsValidArray(view.outputs, view.output_count))) { | ||
| 371 | + return GRAPH_PARAM_INVALID; | ||
| 372 | + } | ||
| 373 | + PythonCustomOpProto parsed; | ||
| 374 | + if ((!CopyString(view.descriptor_key, false, parsed.descriptor_key)) || | ||
| 375 | + (!CopyString(view.op_type, false, parsed.op_type))) { | ||
| 376 | + return GRAPH_PARAM_INVALID; | ||
| 377 | + } | ||
| 378 | + | ||
| 379 | + std::set<std::string> input_names; | ||
| 380 | + parsed.inputs.reserve(view.input_count); | ||
| 381 | + for (size_t i = 0U; i < view.input_count; ++i) { | ||
| 382 | + PythonCustomOpInput input; | ||
| 383 | + if ((!CopyString(view.inputs[i].name, false, input.name)) || (!input_names.insert(input.name).second) || | ||
| 384 | + (ConvertInputKind(view.inputs[i].kind, input.kind) != GRAPH_SUCCESS)) { | ||
| 385 | + return GRAPH_PARAM_INVALID; | ||
| 386 | + } | ||
| 387 | + parsed.inputs.emplace_back(std::move(input)); | ||
| 388 | + } | ||
| 389 | + | ||
| 390 | + std::set<std::string> attr_names; | ||
| 391 | + parsed.attrs.reserve(view.attr_count); | ||
| 392 | + for (size_t i = 0U; i < view.attr_count; ++i) { | ||
| 393 | + const auto &source = view.attrs[i]; | ||
| 394 | + PythonCustomOpAttr attr; | ||
| 395 | + if ((!CopyString(source.name, false, attr.name)) || (!attr_names.insert(attr.name).second) || | ||
| 396 | + (source.is_required > 1U) || (source.default_value.has_value > 1U)) { | ||
| 397 | + return GRAPH_PARAM_INVALID; | ||
| 398 | + } | ||
| 399 | + if (GetRequiredAttrToken(source.kind) == nullptr) { | ||
| 400 | + return GRAPH_PARAM_INVALID; | ||
| 401 | + } | ||
| 402 | + attr.kind = source.kind; | ||
| 403 | + attr.is_required = source.is_required != 0U; | ||
| 404 | + if (attr.is_required == (source.default_value.has_value != 0U)) { | ||
| 405 | + return GRAPH_PARAM_INVALID; | ||
| 406 | + } | ||
| 407 | + if ((!attr.is_required) && (ParseOptionalAttrDefinition(source, attr) != GRAPH_SUCCESS)) { | ||
| 408 | + return GRAPH_PARAM_INVALID; | ||
| 409 | + } | ||
| 410 | + parsed.attrs.emplace_back(std::move(attr)); | ||
| 411 | + } | ||
| 412 | + | ||
| 413 | + std::set<std::string> output_names; | ||
| 414 | + parsed.outputs.reserve(view.output_count); | ||
| 415 | + for (size_t i = 0U; i < view.output_count; ++i) { | ||
| 416 | + PythonCustomOpOutput output; | ||
| 417 | + if ((!CopyString(view.outputs[i].name, false, output.name)) || (!output_names.insert(output.name).second) || | ||
| 418 | + (ConvertOutputKind(view.outputs[i].kind, output.kind) != GRAPH_SUCCESS)) { | ||
| 419 | + return GRAPH_PARAM_INVALID; | ||
| 420 | + } | ||
| 421 | + parsed.outputs.emplace_back(std::move(output)); | ||
| 422 | + } | ||
| 423 | + proto = std::move(parsed); | ||
| 424 | + return GRAPH_SUCCESS; | ||
| 425 | +} | ||
| 426 | + | ||
| 427 | +bool IsSamePythonCustomOpProto(const PythonCustomOpProto &lhs, const PythonCustomOpProto &rhs) { | ||
| 428 | + if ((lhs.descriptor_key != rhs.descriptor_key) || (lhs.op_type != rhs.op_type) || | ||
| 429 | + (lhs.inputs.size() != rhs.inputs.size()) || (lhs.attrs.size() != rhs.attrs.size()) || | ||
| 430 | + (lhs.outputs.size() != rhs.outputs.size())) { | ||
| 431 | + return false; | ||
| 432 | + } | ||
| 433 | + for (size_t i = 0U; i < lhs.inputs.size(); ++i) { | ||
| 434 | + if ((lhs.inputs[i].name != rhs.inputs[i].name) || (lhs.inputs[i].kind != rhs.inputs[i].kind)) { | ||
| 435 | + return false; | ||
| 436 | + } | ||
| 437 | + } | ||
| 438 | + for (size_t i = 0U; i < lhs.attrs.size(); ++i) { | ||
| 439 | + const auto &left = lhs.attrs[i]; | ||
| 440 | + const auto &right = rhs.attrs[i]; | ||
| 441 | + if ((left.name != right.name) || (left.kind != right.kind) || (left.is_required != right.is_required) || | ||
| 442 | + ((!left.is_required) && (!SameDefault(left, right)))) { | ||
| 443 | + return false; | ||
| 444 | + } | ||
| 445 | + } | ||
| 446 | + for (size_t i = 0U; i < lhs.outputs.size(); ++i) { | ||
| 447 | + if ((lhs.outputs[i].name != rhs.outputs[i].name) || (lhs.outputs[i].kind != rhs.outputs[i].kind)) { | ||
| 448 | + return false; | ||
| 449 | + } | ||
| 450 | + } | ||
| 451 | + return true; | ||
| 452 | +} | ||
| 453 | + | ||
| 454 | +graphStatus RegisterPythonCustomOpProto(const PythonCustomOpProto &proto) { | ||
调用的时机可以保证覆盖同名的内置算子原型吗? ![]() ![]() | |||
| 455 | + if (CustomOpFactory::IsExistOp(AscendString(proto.op_type.c_str()))) { | ||
| 456 | + GELOGE(GRAPH_FAILED, | ||
| 457 | + "Python custom op proto conflict, op type[%s], existing source[CustomOpFactory creator], " | ||
| 458 | + "current source[Python descriptor key:%s].", | ||
| 459 | + proto.op_type.c_str(), proto.descriptor_key.c_str()); | ||
| 460 | + return GRAPH_FAILED; | ||
| 461 | + } | ||
| 462 | + const auto owned_proto = ComGraphMakeShared<const PythonCustomOpProto>(proto); | ||
| 463 | + if (owned_proto == nullptr) { | ||
| 464 | + GELOGE(GRAPH_FAILED, "Create python custom op proto failed, descriptor key[%s], op type[%s].", | ||
| 465 | + proto.descriptor_key.c_str(), proto.op_type.c_str()); | ||
| 466 | + return GRAPH_FAILED; | ||
| 467 | + } | ||
| 468 | + const OpCreatorV2 creator = [owned_proto](const AscendString &name) -> Operator { | ||
| 469 | + return CreateOperatorFromProto(name, *owned_proto); | ||
| 470 | + }; | ||
| 471 | + OperatorFactoryImpl::SetRegisterOverridable(true); | ||
| 472 | + const auto ret = OperatorFactoryImpl::RegisterOperatorCreator(proto.op_type, creator); | ||
| 473 | + OperatorFactoryImpl::SetRegisterOverridable(false); | ||
| 474 | + if (ret != GRAPH_SUCCESS) { | ||
| 475 | + return ret; | ||
| 476 | + } | ||
| 477 | + return GRAPH_SUCCESS; | ||
| 478 | +} | ||
| 479 | + | ||
| 480 | +void UnregisterPythonCustomOpProtos(const std::vector<std::string> &op_types) { | ||
| 481 | + OperatorFactoryImpl::RemoveCustomOpCreators(op_types); | ||
| 482 | +} | ||
| 483 | +} // namespace custom_op | ||
| 484 | +} // namespace ge | ||
| @@ -0,0 +1,70 @@ | |||
| 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 | +namespace ge { | ||
| 23 | +namespace custom_op { | ||
| 24 | +struct PythonCustomOpAttrDefault { | ||
| 25 | + int64_t int_value{0}; | ||
| 26 | + double float_value{0.0}; | ||
| 27 | + bool bool_value{false}; | ||
| 28 | + std::string string_value; | ||
| 29 | + int32_t data_type_value{0}; | ||
| 30 | + std::vector<int64_t> list_int_values; | ||
| 31 | + std::vector<double> list_float_values; | ||
| 32 | + std::vector<bool> list_bool_values; | ||
| 33 | + std::vector<std::string> list_string_values; | ||
| 34 | + std::vector<int32_t> list_data_type_values; | ||
| 35 | + std::vector<std::vector<int64_t>> list_list_int_values; | ||
| 36 | +}; | ||
| 37 | + | ||
| 38 | +struct PythonCustomOpInput { | ||
| 39 | + std::string name; | ||
| 40 | + ge::IrInputType kind{ge::kIrInputRequired}; | ||
| 41 | +}; | ||
| 42 | + | ||
| 43 | +struct PythonCustomOpAttr { | ||
| 44 | + std::string name; | ||
| 45 | + uint32_t kind{kPythonAttrInt}; | ||
| 46 | + bool is_required{true}; | ||
| 47 | + PythonCustomOpAttrDefault default_definition; | ||
| 48 | +}; | ||
| 49 | + | ||
| 50 | +struct PythonCustomOpOutput { | ||
| 51 | + std::string name; | ||
| 52 | + ge::IrOutputType kind{ge::kIrOutputRequired}; | ||
| 53 | +}; | ||
| 54 | + | ||
| 55 | +struct PythonCustomOpProto { | ||
| 56 | + std::string descriptor_key; | ||
| 57 | + std::string op_type; | ||
| 58 | + std::vector<PythonCustomOpInput> inputs; | ||
| 59 | + std::vector<PythonCustomOpAttr> attrs; | ||
| 60 | + std::vector<PythonCustomOpOutput> outputs; | ||
| 61 | +}; | ||
| 62 | + | ||
| 63 | +graphStatus ParsePythonCustomOpProto(const PythonCustomOpProtoDescriptorView &view, PythonCustomOpProto &proto); | ||
| 64 | +bool IsSamePythonCustomOpProto(const PythonCustomOpProto &lhs, const PythonCustomOpProto &rhs); | ||
| 65 | +graphStatus RegisterPythonCustomOpProto(const PythonCustomOpProto &proto); | ||
| 66 | +void UnregisterPythonCustomOpProtos(const std::vector<std::string> &op_types); | ||
| 67 | +} // namespace custom_op | ||
| 68 | +} // namespace ge | ||
| 69 | + | ||
| 70 | + | ||
| @@ -30,10 +30,11 @@ | |||
| 30 | 30 | ||
| 31 | 31 | ||
| 32 | 32 | ||
| 33 | -#include "ge/ge_api_v2.h" | 33 | +#include "graph/utils/ir_definitions_query.h" |
| 34 | 34 | ||
| 35 | 35 | ||
| 36 | 36 | ||
| 37 | + | ||
| 37 | 38 | ||
| 38 | 39 | ||
| 39 | 40 | ||
| @@ -45,15 +46,11 @@ namespace py = pybind11; | |||
| 45 | namespace { | 46 | namespace { |
| 46 | constexpr const char *kBridgeModuleName = "ge.custom_op._bridge"; | 47 | constexpr const char *kBridgeModuleName = "ge.custom_op._bridge"; |
| 47 | constexpr const char *kCustomOpModuleName = "ge.custom_op"; | 48 | constexpr const char *kCustomOpModuleName = "ge.custom_op"; |
| 49 | +constexpr const char *kCustomOpProtoModuleName = "ge.custom_op.proto"; | ||
| 48 | constexpr const char *kCustomOpNativeModuleName = "ge.custom_op._ge_custom_op_native"; | 50 | constexpr const char *kCustomOpNativeModuleName = "ge.custom_op._ge_custom_op_native"; |
| 49 | constexpr const char *kEnvCustomOppPath = "ASCEND_CUSTOM_OPP_PATH"; | 51 | constexpr const char *kEnvCustomOppPath = "ASCEND_CUSTOM_OPP_PATH"; |
| 50 | -constexpr const char *kInterfaceAnnotatedArgs = "annotated_args"; | 52 | +constexpr const char *kGetRegisteredIrDefFromGraphSymbol = "GetRegisteredIrDefFromGraph"; |
| 51 | -constexpr const char *kInterfaceEagerExecute = "eager_execute"; | 53 | +constexpr const char *kGraphLibraryName = "libgraph.so"; |
| 52 | -constexpr const char *kGetRegisteredIrDefSymbol = "GetRegisteredIrDef"; | ||
| 53 | -constexpr const char *kRunnerLibraryNames[] = { | ||
| 54 | - "libge_runner_v2.so", | ||
| 55 | - "libge_runner.so", | ||
| 56 | -}; | ||
| 57 | 54 | ||
| 58 | struct PythonCustomOpIrInputMeta { | 55 | struct PythonCustomOpIrInputMeta { |
| 59 | std::string name; | 56 | std::string name; |
| @@ -77,37 +74,33 @@ struct PythonCustomOpIrMeta { | |||
| 77 | std::vector<PythonCustomOpIrOutputMeta> outputs; | 74 | std::vector<PythonCustomOpIrOutputMeta> outputs; |
| 78 | }; | 75 | }; |
| 79 | 76 | ||
| 80 | -using GetRegisteredIrDefFn = decltype(&::GetRegisteredIrDef); | 77 | +using GetRegisteredIrDefFromGraphFn = decltype(&::GetRegisteredIrDefFromGraph); |
| 81 | 78 | ||
| 82 | -// runner 已依赖 custom_op_runtime,bridge 又由 custom_op_runtime 动态加载,因此 bridge 不能反向硬链接 runner。 | 79 | +bool GetRegisteredIrDefFromLoadedGraph(const char *op_type, |
| 83 | -// Python 可能以 RTLD_LOCAL 加载 runner 依赖组,这里使用正式公共头文件约束函数签名, | 80 | + std::vector<std::pair<ge::AscendString, ge::AscendString>> &inputs, |
| 84 | -// 并通过 RTLD_NOLOAD 获取已加载的 runner handle 查询公共符号,不重复加载或提升其全局可见性。 | 81 | + std::vector<std::pair<ge::AscendString, ge::AscendString>> &outputs, |
| 85 | -bool GetRegisteredIrDefFromLoadedRunner(const char *op_type, | 82 | + std::vector<std::pair<ge::AscendString, ge::AscendString>> &attrs) { |
| 86 | - std::vector<std::pair<ge::AscendString, ge::AscendString>> &inputs, | 83 | + void *handle = dlopen(kGraphLibraryName, RTLD_NOW | RTLD_NOLOAD); |
| 87 | - std::vector<std::pair<ge::AscendString, ge::AscendString>> &outputs, | 84 | + if (handle == nullptr) { |
| 88 | - std::vector<std::pair<ge::AscendString, ge::AscendString>> &attrs) { | 85 | + GELOGE(FAILED, "Graph library is not loaded when querying IR for custom op[%s].", op_type); |
| 89 | - for (const auto *runner_library : kRunnerLibraryNames) { | 86 | + return false; |
| 90 | - void *handle = dlopen(runner_library, RTLD_NOW | RTLD_NOLOAD); | ||
| 91 | - if (handle == nullptr) { | ||
| 92 | - continue; | ||
| 93 | - } | ||
| 94 | - | ||
| 95 | - (void)dlerror(); | ||
| 96 | - void *symbol = dlsym(handle, kGetRegisteredIrDefSymbol); | ||
| 97 | - const char *error = dlerror(); | ||
| 98 | - if ((symbol == nullptr) || (error != nullptr)) { | ||
| 99 | - (void)dlclose(handle); | ||
| 100 | - continue; | ||
| 101 | - } | ||
| 102 | - | ||
| 103 | - const auto get_registered_ir_def = reinterpret_cast<GetRegisteredIrDefFn>(symbol); | ||
| 104 | - const auto ret = get_registered_ir_def(op_type, inputs, outputs, attrs); | ||
| 105 | - (void)dlclose(handle); | ||
| 106 | - GE_ASSERT_SUCCESS(ret, "GetRegisteredIrDef failed for custom op[%s].", op_type); | ||
| 107 | - return true; | ||
| 108 | } | 87 | } |
| 109 | - | 88 | + (void)dlerror(); |
| 110 | - GE_ASSERT_TRUE(false, "No loaded run package exports %s for custom op[%s].", kGetRegisteredIrDefSymbol, op_type); | 89 | + void *symbol = dlsym(handle, kGetRegisteredIrDefFromGraphSymbol); |
| 90 | + const char *error = dlerror(); | ||
| 91 | + if ((symbol == nullptr) || (error != nullptr)) { | ||
| 92 | + (void)dlclose(handle); | ||
| 93 | + GELOGE(FAILED, "Failed to find graph IR query symbol[%s].", kGetRegisteredIrDefFromGraphSymbol); | ||
| 94 | + return false; | ||
| 95 | + } | ||
| 96 | + const auto get_registered_ir_def = reinterpret_cast<GetRegisteredIrDefFromGraphFn>(symbol); | ||
| 97 | + const auto ret = get_registered_ir_def(op_type, inputs, outputs, attrs); | ||
| 98 | + (void)dlclose(handle); | ||
| 99 | + if (ret != ge::SUCCESS) { | ||
| 100 | + GELOGE(ret, "GetRegisteredIrDefFromGraph failed for custom op[%s].", op_type); | ||
| 101 | + return false; | ||
| 102 | + } | ||
| 103 | + return true; | ||
| 111 | } | 104 | } |
| 112 | 105 | ||
| 113 | bool CopyAscendString(const ge::AscendString &value, const char *field_name, std::string &result) { | 106 | bool CopyAscendString(const ge::AscendString &value, const char *field_name, std::string &result) { |
| @@ -157,7 +150,7 @@ std::unique_ptr<PythonCustomOpIrMeta> CollectPythonCustomOpIrMeta(const std::str | |||
| 157 | std::vector<std::pair<ge::AscendString, ge::AscendString>> inputs; | 150 | std::vector<std::pair<ge::AscendString, ge::AscendString>> inputs; |
| 158 | std::vector<std::pair<ge::AscendString, ge::AscendString>> outputs; | 151 | std::vector<std::pair<ge::AscendString, ge::AscendString>> outputs; |
| 159 | std::vector<std::pair<ge::AscendString, ge::AscendString>> attrs; | 152 | std::vector<std::pair<ge::AscendString, ge::AscendString>> attrs; |
| 160 | - GE_ASSERT_TRUE(GetRegisteredIrDefFromLoadedRunner(op_type.c_str(), inputs, outputs, attrs)); | 153 | + GE_ASSERT_TRUE(GetRegisteredIrDefFromLoadedGraph(op_type.c_str(), inputs, outputs, attrs)); |
| 161 | 154 | ||
| 162 | auto ir_meta = std::unique_ptr<PythonCustomOpIrMeta>(new (std::nothrow) PythonCustomOpIrMeta()); | 155 | auto ir_meta = std::unique_ptr<PythonCustomOpIrMeta>(new (std::nothrow) PythonCustomOpIrMeta()); |
| 163 | GE_ASSERT_NOTNULL(ir_meta, "Allocate IR meta failed for custom op[%s].", op_type.c_str()); | 156 | GE_ASSERT_NOTNULL(ir_meta, "Allocate IR meta failed for custom op[%s].", op_type.c_str()); |
| @@ -200,17 +193,12 @@ struct PythonCustomOpBridgeHolder { | |||
| 200 | std::unique_ptr<PythonCustomOpIrMeta> ir_meta; | 193 | std::unique_ptr<PythonCustomOpIrMeta> ir_meta; |
| 201 | }; | 194 | }; |
| 202 | 195 | ||
| 203 | -CustomOpCapabilityMask ParseInterfaces(const py::object &interfaces_obj) { | 196 | +bool CopyStringView(const PythonCustomOpStringView &view, std::string &value) { |
| 204 | - CustomOpCapabilityMask capabilities = 0U; | 197 | + if ((view.size != 0U) && (view.data == nullptr)) { |
| 205 | - for (const auto &item : interfaces_obj.cast<py::list>()) { | 198 | + return false; |
| 206 | - const std::string interface_name = py::str(item); | ||
| 207 | - if (interface_name == kInterfaceEagerExecute) { | ||
| 208 | - AddCustomOpCapability(capabilities, CustomOpCapability::kEagerExecute); | ||
| 209 | - } else if (interface_name == kInterfaceAnnotatedArgs) { | ||
| 210 | - AddCustomOpCapability(capabilities, CustomOpCapability::kAnnotatedArgs); | ||
| 211 | - } | ||
| 212 | } | 199 | } |
| 213 | - return capabilities; | 200 | + value.assign(view.data == nullptr ? "" : view.data, view.size); |
| 201 | + return (!value.empty()) && (value.find('\0') == std::string::npos); | ||
| 214 | } | 202 | } |
| 215 | 203 | ||
| 216 | class PythonCustomOpPybindBridge { | 204 | class PythonCustomOpPybindBridge { |
| @@ -244,49 +232,30 @@ class PythonCustomOpPybindBridge { | |||
| 244 | py::gil_scoped_acquire gil; | 232 | py::gil_scoped_acquire gil; |
| 245 | py::object descriptors_obj; | 233 | py::object descriptors_obj; |
| 246 | try { | 234 | try { |
| 247 | - descriptors_obj = bridge_module_.attr("load_and_get_op_impl_descriptors")(); | 235 | + descriptors_obj = bridge_module_.attr("load_and_get_op_descriptors")(); |
| 248 | } catch (const py::error_already_set &err) { | 236 | } catch (const py::error_already_set &err) { |
| 249 | GELOGE(FAILED, "Load python custom op descriptors failed: %s", err.what()); | 237 | GELOGE(FAILED, "Load python custom op descriptors failed: %s", err.what()); |
| 250 | return FAILED; | 238 | return FAILED; |
| 251 | } | 239 | } |
| 252 | 240 | ||
| 253 | - const py::list descriptor_list = descriptors_obj.cast<py::list>(); | 241 | + py::dict descriptors; |
| 254 | - std::vector<PythonCustomOpDescriptor> descriptors; | 242 | + try { |
| 255 | - descriptors.reserve(descriptor_list.size()); | 243 | + descriptors = descriptors_obj.cast<py::dict>(); |
| 256 | - for (const auto &item : descriptor_list) { | 244 | + } catch (const py::error_already_set &err) { |
| 257 | - PythonCustomOpDescriptor desc; | 245 | + GELOGE(FAILED, "Parse python custom op descriptor snapshot failed: %s", err.what()); |
| 258 | - const auto parse_ret = ParseDescriptor(item.cast<py::dict>(), desc); | 246 | + return FAILED; |
| 259 | - if (parse_ret != SUCCESS) { | 247 | + } catch (const std::exception &err) { |
| 260 | - GELOGE(parse_ret, "Parse python custom op descriptor failed."); | 248 | + GELOGE(FAILED, "Parse python custom op descriptor snapshot failed: %s", err.what()); |
| 261 | - return parse_ret; | 249 | + return FAILED; |
| 262 | - } | ||
| 263 | - auto ir_meta = CollectPythonCustomOpIrMeta(desc.op_type); | ||
| 264 | - try { | ||
| 265 | - const bool validated = | ||
| 266 | - bridge_module_.attr("validate_op_impl_descriptor")(desc.descriptor_key, BuildPythonIrMeta(ir_meta.get())) | ||
| 267 | - .cast<bool>(); | ||
| 268 | - if (!validated) { | ||
| 269 | - GELOGE(FAILED, "Validate python custom op[%s] descriptor[%s] failed.", desc.op_type.c_str(), | ||
| 270 | - desc.descriptor_key.c_str()); | ||
| 271 | - return FAILED; | ||
| 272 | - } | ||
| 273 | - } catch (const py::error_already_set &err) { | ||
| 274 | - GELOGE(FAILED, "Validate python custom op[%s] descriptor[%s] failed: %s", desc.op_type.c_str(), | ||
| 275 | - desc.descriptor_key.c_str(), err.what()); | ||
| 276 | - return FAILED; | ||
| 277 | - } | ||
| 278 | - descriptors.emplace_back(std::move(desc)); | ||
| 279 | } | 250 | } |
| 280 | 251 | ||
| 281 | - const auto callbacks = GetCallbacks(); | 252 | + if ((registrar.register_op_proto == nullptr) || (registrar.register_op_adapter == nullptr)) { |
| 282 | - for (const auto &desc : descriptors) { | 253 | + return FAILED; |
| 283 | - if ((registrar.register_custom_op == nullptr) || (!registrar.register_custom_op(&desc, &callbacks))) { | ||
| 284 | - GELOGE(FAILED, "Register python custom op[%s] failed.", desc.op_type.c_str()); | ||
| 285 | - return FAILED; | ||
| 286 | - } | ||
| 287 | - GELOGI("Python custom op[%s] is registered from pybind bridge.", desc.op_type.c_str()); | ||
| 288 | } | 254 | } |
| 289 | - return SUCCESS; | 255 | + if (CollectAndRegisterProtoDescriptors(descriptors, registrar) != SUCCESS) { |
| 256 | + return FAILED; | ||
| 257 | + } | ||
| 258 | + return CollectAndRegisterImplDescriptorsWithCheck(descriptors, registrar); | ||
| 290 | } | 259 | } |
| 291 | 260 | ||
| 292 | void ResetBridgeState() { | 261 | void ResetBridgeState() { |
| @@ -318,30 +287,74 @@ class PythonCustomOpPybindBridge { | |||
| 318 | owns_interpreter_ = false; | 287 | owns_interpreter_ = false; |
| 319 | } | 288 | } |
| 320 | 289 | ||
| 321 | - void *CreateHolder(const PythonCustomOpDescriptor &desc) { | 290 | + void *CreateImplHolder(const PythonCustomOpAdapterDescriptorView *desc_view) { |
| 291 | + if (desc_view == nullptr) { | ||
| 292 | + return nullptr; | ||
| 293 | + } | ||
| 294 | + std::string descriptor_key; | ||
| 295 | + std::string op_type; | ||
| 296 | + if ((!CopyStringView(desc_view->impl_descriptor_key, descriptor_key)) || | ||
| 297 | + (!CopyStringView(desc_view->op_type, op_type))) { | ||
| 298 | + GELOGW("Create python custom op holder failed because adapter descriptor view is invalid."); | ||
| 299 | + return nullptr; | ||
| 300 | + } | ||
| 322 | if (EnsureBridgeReady() != SUCCESS) { | 301 | if (EnsureBridgeReady() != SUCCESS) { |
| 323 | GELOGW("Prepare python custom op bridge failed when creating holder."); | 302 | GELOGW("Prepare python custom op bridge failed when creating holder."); |
| 324 | return nullptr; | 303 | return nullptr; |
| 325 | } | 304 | } |
| 326 | py::gil_scoped_acquire gil; | 305 | py::gil_scoped_acquire gil; |
| 327 | - const std::string instance_id = BuildInstanceId(desc.descriptor_key); | 306 | + const std::string instance_id = BuildInstanceId(descriptor_key); |
| 328 | - auto ir_meta = CollectPythonCustomOpIrMeta(desc.op_type); | 307 | + auto ir_meta = CollectPythonCustomOpIrMeta(op_type); |
这里要修改一下CollectPythonCustomOpIrMeta的内部实现了,因为这个函数本来是服务于execute的,这意味着必然是图执行时,所以这个函数依赖的runner的so一定是被加载了的,当前扩散到图编译时,那么比如atc流程是没有人加载runner的so导致这个函数不能正常工作; 建议``` graph.so: 实现 IR 查询逻辑 导出一个内部、稳定的 extern "C" 符号 libge_runner*.so: 保持公开 GetRegisteredIrDef API 内部调用 graph 层实现 Python bridge: dlopen("libgraph.so", RTLD_NOW | RTLD_NOLOAD) dlsym("内部 IR 查询符号") 通过函数指针调用
![]() ![]() | |||
| 329 | try { | 308 | try { |
| 330 | - const bool created = bridge_module_.attr("create_op_impl_holder")(instance_id, desc.descriptor_key).cast<bool>(); | 309 | + const bool created = bridge_module_.attr("create_op_impl_holder")(instance_id, descriptor_key).cast<bool>(); |
| 331 | if (!created) { | 310 | if (!created) { |
| 332 | - GELOGW("Create python custom op holder failed, descriptor key[%s], instance id[%s].", | 311 | + GELOGW("Create python custom op holder failed, descriptor key[%s], instance id[%s].", descriptor_key.c_str(), |
| 333 | - desc.descriptor_key.c_str(), instance_id.c_str()); | 312 | + instance_id.c_str()); |
| 334 | return nullptr; | 313 | return nullptr; |
| 335 | } | 314 | } |
| 336 | } catch (const py::error_already_set &err) { | 315 | } catch (const py::error_already_set &err) { |
| 337 | - GELOGW("Create python custom op holder failed, descriptor key[%s], instance id[%s]: %s", | 316 | + GELOGW("Create python custom op holder failed, descriptor key[%s], instance id[%s]: %s", descriptor_key.c_str(), |
| 338 | - desc.descriptor_key.c_str(), instance_id.c_str(), err.what()); | 317 | + instance_id.c_str(), err.what()); |
| 318 | + return nullptr; | ||
| 319 | + } catch (const std::exception &err) { | ||
| 320 | + GELOGW("Create python custom op holder failed, descriptor key[%s], instance id[%s]: %s", descriptor_key.c_str(), | ||
| 321 | + instance_id.c_str(), err.what()); | ||
| 322 | + return nullptr; | ||
| 323 | + } catch (...) { | ||
| 324 | + GELOGW("Create python custom op holder failed with unknown exception, descriptor key[%s], instance id[%s].", | ||
| 325 | + descriptor_key.c_str(), instance_id.c_str()); | ||
| 339 | return nullptr; | 326 | return nullptr; |
| 340 | } | 327 | } |
| 341 | - return new (std::nothrow) PythonCustomOpBridgeHolder{desc.descriptor_key, instance_id, std::move(ir_meta)}; | 328 | + return new (std::nothrow) PythonCustomOpBridgeHolder{descriptor_key, instance_id, std::move(ir_meta)}; |
| 342 | } | 329 | } |
| 343 | 330 | ||
| 344 | - void DestroyHolder(PythonCustomOpBridgeHolder *holder) { | 331 | + bool ValidateImpl(const PythonCustomOpAdapterDescriptorView *desc_view) { |
| 332 | + if (desc_view == nullptr) { | ||
| 333 | + return false; | ||
| 334 | + } | ||
| 335 | + std::string descriptor_key; | ||
| 336 | + std::string op_type; | ||
| 337 | + if ((!CopyStringView(desc_view->impl_descriptor_key, descriptor_key)) || | ||
| 338 | + (!CopyStringView(desc_view->op_type, op_type))) { | ||
| 339 | + return false; | ||
| 340 | + } | ||
| 341 | + py::gil_scoped_acquire gil; | ||
| 342 | + try { | ||
| 343 | + const auto ir_meta = CollectPythonCustomOpIrMeta(op_type); | ||
| 344 | + return bridge_module_.attr("validate_op_impl_descriptor")(descriptor_key, BuildPythonIrMeta(ir_meta.get())) | ||
之前是在注册的时候进行校验的,为什么现在放到了callback校验?注册的时候一次性校验比较友好,放callback每次回调都需要校验 ![]() ![]() | |||
| 345 | + .cast<bool>(); | ||
| 346 | + } catch (const py::error_already_set &err) { | ||
| 347 | + GELOGE(FAILED, "Validate python custom op impl failed, descriptor key[%s]: %s", descriptor_key.c_str(), | ||
| 348 | + err.what()); | ||
| 349 | + return false; | ||
| 350 | + } catch (const std::exception &err) { | ||
| 351 | + GELOGE(FAILED, "Validate python custom op impl failed, descriptor key[%s]: %s", descriptor_key.c_str(), | ||
| 352 | + err.what()); | ||
| 353 | + return false; | ||
| 354 | + } | ||
| 355 | + } | ||
| 356 | + | ||
| 357 | + void DestroyImplHolder(PythonCustomOpBridgeHolder *holder) { | ||
| 345 | if (holder == nullptr) { | 358 | if (holder == nullptr) { |
| 346 | return; | 359 | return; |
| 347 | } | 360 | } |
| @@ -353,6 +366,12 @@ class PythonCustomOpPybindBridge { | |||
| 353 | } catch (const py::error_already_set &err) { | 366 | } catch (const py::error_already_set &err) { |
| 354 | GELOGW("Destroy python custom op holder failed, descriptor key[%s], instance id[%s]: %s", | 367 | GELOGW("Destroy python custom op holder failed, descriptor key[%s], instance id[%s]: %s", |
| 355 | holder->descriptor_key.c_str(), holder->instance_id.c_str(), err.what()); | 368 | holder->descriptor_key.c_str(), holder->instance_id.c_str(), err.what()); |
| 369 | + } catch (const std::exception &err) { | ||
| 370 | + GELOGW("Destroy python custom op holder failed, descriptor key[%s], instance id[%s]: %s", | ||
| 371 | + holder->descriptor_key.c_str(), holder->instance_id.c_str(), err.what()); | ||
| 372 | + } catch (...) { | ||
| 373 | + GELOGW("Destroy python custom op holder failed with unknown exception, descriptor key[%s], instance id[%s].", | ||
| 374 | + holder->descriptor_key.c_str(), holder->instance_id.c_str()); | ||
| 356 | } | 375 | } |
| 357 | } | 376 | } |
| 358 | delete holder; | 377 | delete holder; |
| @@ -416,15 +435,68 @@ class PythonCustomOpPybindBridge { | |||
| 416 | } catch (const py::error_already_set &err) { | 435 | } catch (const py::error_already_set &err) { |
| 417 | GELOGE(GRAPH_FAILED, "DeclareLaunchArgs python custom op failed, descriptor key[%s], instance id[%s]: %s", | 436 | GELOGE(GRAPH_FAILED, "DeclareLaunchArgs python custom op failed, descriptor key[%s], instance id[%s]: %s", |
| 418 | holder->descriptor_key.c_str(), holder->instance_id.c_str(), err.what()); | 437 | holder->descriptor_key.c_str(), holder->instance_id.c_str(), err.what()); |
| 419 | - return GRAPH_FAILED; | ||
| 420 | } catch (const std::exception &err) { | 438 | } catch (const std::exception &err) { |
| 421 | GELOGE(GRAPH_FAILED, "DeclareLaunchArgs python custom op failed, descriptor key[%s], instance id[%s]: %s", | 439 | GELOGE(GRAPH_FAILED, "DeclareLaunchArgs python custom op failed, descriptor key[%s], instance id[%s]: %s", |
| 422 | holder->descriptor_key.c_str(), holder->instance_id.c_str(), err.what()); | 440 | holder->descriptor_key.c_str(), holder->instance_id.c_str(), err.what()); |
| 423 | - return GRAPH_FAILED; | ||
| 424 | } | 441 | } |
| 442 | + return GRAPH_FAILED; | ||
| 425 | } | 443 | } |
| 426 | 444 | ||
| 427 | private: | 445 | private: |
| 446 | + Status CollectAndRegisterProtoDescriptors(const py::dict &descriptors, const PythonCustomOpRegistrar ®istrar) { | ||
| 447 | + try { | ||
| 448 | + for (const auto &item : descriptors["protos"].cast<py::list>()) { | ||
| 449 | + ProtoDescriptorStorage proto; | ||
| 450 | + if (proto.Parse(item.cast<py::dict>()) != SUCCESS) { | ||
| 451 | + return FAILED; | ||
| 452 | + } | ||
| 453 | + const auto view = proto.BuildView(); | ||
| 454 | + if (!registrar.register_op_proto(&view)) { | ||
| 455 | + GELOGE(FAILED, "Register python custom op proto[%s] failed.", proto.op_type.c_str()); | ||
| 456 | + return FAILED; | ||
| 457 | + } | ||
| 458 | + GELOGI("Python custom op proto[%s] is registered from pybind bridge.", proto.op_type.c_str()); | ||
| 459 | + } | ||
| 460 | + } catch (const py::error_already_set &err) { | ||
| 461 | + GELOGE(FAILED, "Collect python custom op proto descriptors failed: %s", err.what()); | ||
| 462 | + return FAILED; | ||
| 463 | + } catch (const std::exception &err) { | ||
| 464 | + GELOGE(FAILED, "Collect python custom op proto descriptors failed: %s", err.what()); | ||
| 465 | + return FAILED; | ||
| 466 | + } | ||
| 467 | + return SUCCESS; | ||
| 468 | + } | ||
| 469 | + | ||
| 470 | + Status CollectAndRegisterImplDescriptorsWithCheck(const py::dict &descriptors, | ||
| 471 | + const PythonCustomOpRegistrar ®istrar) { | ||
| 472 | + const auto callbacks = GetCallbacks(); | ||
| 473 | + try { | ||
| 474 | + for (const auto &item : descriptors["impls"].cast<py::list>()) { | ||
| 475 | + AdapterDescriptorStorage adapter; | ||
| 476 | + if (adapter.Parse(item.cast<py::dict>()) != SUCCESS) { | ||
| 477 | + return FAILED; | ||
| 478 | + } | ||
| 479 | + const auto view = adapter.BuildView(); | ||
| 480 | + if (!ValidateImpl(&view)) { | ||
| 481 | + GELOGE(FAILED, "Validate python custom op adapter[%s] failed.", adapter.op_type.c_str()); | ||
| 482 | + return FAILED; | ||
| 483 | + } | ||
| 484 | + if (!registrar.register_op_adapter(&view, &callbacks)) { | ||
| 485 | + GELOGE(FAILED, "Register python custom op adapter[%s] failed.", adapter.op_type.c_str()); | ||
| 486 | + return FAILED; | ||
| 487 | + } | ||
| 488 | + GELOGI("Python custom op adapter[%s] is registered from pybind bridge.", adapter.op_type.c_str()); | ||
| 489 | + } | ||
| 490 | + } catch (const py::error_already_set &err) { | ||
| 491 | + GELOGE(FAILED, "Collect python custom op impl descriptors failed: %s", err.what()); | ||
| 492 | + return FAILED; | ||
| 493 | + } catch (const std::exception &err) { | ||
| 494 | + GELOGE(FAILED, "Collect python custom op impl descriptors failed: %s", err.what()); | ||
| 495 | + return FAILED; | ||
| 496 | + } | ||
| 497 | + return SUCCESS; | ||
| 498 | + } | ||
| 499 | + | ||
| 428 | Status EnsureBridgeReady() { | 500 | Status EnsureBridgeReady() { |
| 429 | std::lock_guard<std::mutex> lock(mutex_); | 501 | std::lock_guard<std::mutex> lock(mutex_); |
| 430 | if (Py_IsInitialized() == 0) { | 502 | if (Py_IsInitialized() == 0) { |
| @@ -498,6 +570,7 @@ class PythonCustomOpPybindBridge { | |||
| 498 | (void)bridge_module_.attr("clear_op_impl_holders")(); | 570 | (void)bridge_module_.attr("clear_op_impl_holders")(); |
| 499 | (void)bridge_module_.attr("clear_loaded_op_impl_modules")(); | 571 | (void)bridge_module_.attr("clear_loaded_op_impl_modules")(); |
| 500 | (void)py::module_::import(kCustomOpModuleName).attr("clear_registered_op_impls")(); | 572 | (void)py::module_::import(kCustomOpModuleName).attr("clear_registered_op_impls")(); |
| 573 | + (void)py::module_::import(kCustomOpProtoModuleName).attr("clear_registered_op_protos")(); | ||
| 501 | } catch (const py::error_already_set &err) { | 574 | } catch (const py::error_already_set &err) { |
| 502 | GELOGW("Reset python custom op bridge state failed: %s", err.what()); | 575 | GELOGW("Reset python custom op bridge state failed: %s", err.what()); |
| 503 | } | 576 | } |
| @@ -535,22 +608,6 @@ class PythonCustomOpPybindBridge { | |||
| 535 | return oss.str(); | 608 | return oss.str(); |
| 536 | } | 609 | } |
| 537 | 610 | ||
| 538 | - static Status ParseDescriptor(const py::dict &descriptor_dict, PythonCustomOpDescriptor &desc) { | ||
| 539 | - try { | ||
| 540 | - desc.descriptor_key = py::str(descriptor_dict["descriptor_key"]); | ||
| 541 | - desc.op_type = py::str(descriptor_dict["op_type"]); | ||
| 542 | - desc.capabilities = ParseInterfaces(descriptor_dict["interfaces"]); | ||
| 543 | - if (desc.capabilities == 0U) { | ||
| 544 | - GELOGE(FAILED, "Python custom op[%s] has no supported interface.", desc.op_type.c_str()); | ||
| 545 | - return FAILED; | ||
| 546 | - } | ||
| 547 | - } catch (const py::error_already_set &err) { | ||
| 548 | - GELOGE(FAILED, "Parse python custom op descriptor failed: %s", err.what()); | ||
| 549 | - return FAILED; | ||
| 550 | - } | ||
| 551 | - return (!desc.descriptor_key.empty() && !desc.op_type.empty()) ? SUCCESS : FAILED; | ||
| 552 | - } | ||
| 553 | - | ||
| 554 | static py::object BuildPythonIrMeta(const PythonCustomOpIrMeta *ir_meta) { | 611 | static py::object BuildPythonIrMeta(const PythonCustomOpIrMeta *ir_meta) { |
| 555 | if (ir_meta == nullptr) { | 612 | if (ir_meta == nullptr) { |
| 556 | return py::none(); | 613 | return py::none(); |
| @@ -613,16 +670,13 @@ class PythonCustomOpPybindBridge { | |||
| 613 | return GRAPH_FAILED; | 670 | return GRAPH_FAILED; |
| 614 | } | 671 | } |
| 615 | 672 | ||
| 616 | - static PythonCustomOpCallbacks GetCallbacks() { | 673 | + static PythonCustomOpAdapterCallbacks GetCallbacks() { |
| 617 | - PythonCustomOpCallbacks callbacks; | 674 | + PythonCustomOpAdapterCallbacks callbacks; |
| 618 | - callbacks.create = [](const PythonCustomOpDescriptor *desc) -> void * { | 675 | + callbacks.create_impl_holder = [](const PythonCustomOpAdapterDescriptorView *desc) -> void * { |
| 619 | - if (desc == nullptr) { | 676 | + return PythonCustomOpPybindBridge::GetInstance().CreateImplHolder(desc); |
| 620 | - return nullptr; | ||
| 621 | - } | ||
| 622 | - return PythonCustomOpPybindBridge::GetInstance().CreateHolder(*desc); | ||
| 623 | }; | 677 | }; |
| 624 | - callbacks.destroy = [](void *holder) { | 678 | + callbacks.destroy_impl_holder = [](void *holder) { |
| 625 | - PythonCustomOpPybindBridge::GetInstance().DestroyHolder(static_cast<PythonCustomOpBridgeHolder *>(holder)); | 679 | + PythonCustomOpPybindBridge::GetInstance().DestroyImplHolder(static_cast<PythonCustomOpBridgeHolder *>(holder)); |
| 626 | }; | 680 | }; |
| 627 | callbacks.execute = [](const void *holder, gert::EagerOpExecutionContext *ctx) -> graphStatus { | 681 | callbacks.execute = [](const void *holder, gert::EagerOpExecutionContext *ctx) -> graphStatus { |
| 628 | return PythonCustomOpPybindBridge::GetInstance().Execute(static_cast<const PythonCustomOpBridgeHolder *>(holder), | 682 | return PythonCustomOpPybindBridge::GetInstance().Execute(static_cast<const PythonCustomOpBridgeHolder *>(holder), |
| @@ -11,7 +11,6 @@ | |||
| 11 | 11 | ||
| 12 | 12 | ||
| 13 | 13 | ||
| 14 | - | ||
| 15 | 14 | ||
| 16 | 15 | ||
| 17 | 16 | ||
| @@ -59,6 +58,7 @@ | |||
| 59 | 58 | ||
| 60 | 59 | ||
| 61 | 60 | ||
| 61 | + | ||
| 62 | 62 | ||
| 63 | 63 | ||
| 64 | 64 | ||
| @@ -183,13 +183,19 @@ from ge.custom_op import ( | |||
| 183 | AnnotatedKernelLaunchInfo, | 183 | AnnotatedKernelLaunchInfo, |
| 184 | EagerExecuteOp, | 184 | EagerExecuteOp, |
| 185 | get_declare_launch_args_ctx, | 185 | get_declare_launch_args_ctx, |
| 186 | + register_op, | ||
| 186 | register_op_impl, | 187 | register_op_impl, |
| 187 | ) | 188 | ) |
| 188 | -from ge.runtime import Tensor | 189 | +from ge.runtime import Tensor, TensorDesc |
| 189 | 190 | ||
| 190 | MARKER_FILE = r')PY"; | 191 | MARKER_FILE = r')PY"; |
| 191 | constexpr char kSharedPybindEagerCustomOpForSt[] = R"PY(' | 192 | constexpr char kSharedPybindEagerCustomOpForSt[] = R"PY(' |
| 192 | 193 | ||
| 194 | +@register_op(op_type=')PY"; | ||
| 195 | +constexpr char kSharedPybindEagerCustomOpImplForSt[] = R"PY(') | ||
| 196 | +def infer_meta(x: TensorDesc, *, axis: int = 0) -> TensorDesc: | ||
| 197 | + return x | ||
| 198 | + | ||
| 193 | @register_op_impl(op_type=')PY"; | 199 | @register_op_impl(op_type=')PY"; |
| 194 | constexpr char kSharedPybindAnnotatedArgsPrefixForSt[] = R"PY(') | 200 | constexpr char kSharedPybindAnnotatedArgsPrefixForSt[] = R"PY(') |
| 195 | class StPythonPybindRemoveCoverageCustomOp(EagerExecuteOp): | 201 | class StPythonPybindRemoveCoverageCustomOp(EagerExecuteOp): |
| @@ -450,8 +456,9 @@ void EnsureSharedPybindCustomOpFileForSt() { | |||
| 450 | std::call_once(once, []() { | 456 | std::call_once(once, []() { |
| 451 | const auto python_file = std::string(kSharedPybindCustomOpPreambleForSt) + | 457 | const auto python_file = std::string(kSharedPybindCustomOpPreambleForSt) + |
| 452 | GetSharedPybindCustomOpMarkerFilePathForSt() + kSharedPybindEagerCustomOpForSt + | 458 | GetSharedPybindCustomOpMarkerFilePathForSt() + kSharedPybindEagerCustomOpForSt + |
| 453 | - kPythonCustomOpTypeForSt + kSharedPybindAnnotatedArgsPrefixForSt + | 459 | + kPythonCustomOpTypeForSt + kSharedPybindEagerCustomOpImplForSt + kPythonCustomOpTypeForSt + |
| 454 | - kPythonAnnotatedArgsOpTypeForSt + kSharedPybindAnnotatedArgsBodyForSt; | 460 | + kSharedPybindAnnotatedArgsPrefixForSt + kPythonAnnotatedArgsOpTypeForSt + |
| 461 | + kSharedPybindAnnotatedArgsBodyForSt; | ||
| 455 | WriteTextFileForCustomOpSt(GetSharedPybindCustomOpFilePathForSt(), python_file); | 462 | WriteTextFileForCustomOpSt(GetSharedPybindCustomOpFilePathForSt(), python_file); |
| 456 | }); | 463 | }); |
| 457 | } | 464 | } |
| @@ -1759,10 +1766,11 @@ TEST_F(CustomOpRefreshTest, eager_only_op_with_malloc_read_only_dev_args) { | |||
| 1759 | * 1. 构造一个合法legacy实现和一个属性名与REG_OP定义不一致的Python实现。 | 1766 | * 1. 构造一个合法legacy实现和一个属性名与REG_OP定义不一致的Python实现。 |
| 1760 | * 测试步骤: | 1767 | * 测试步骤: |
| 1761 | * 1. 通过LoadPythonCustomOps加载Python实现。 | 1768 | * 1. 通过LoadPythonCustomOps加载Python实现。 |
| 1762 | - * 2. 查询CustomOpFactory中是否存在该Python自定义算子creator。 | 1769 | + * 2. 调用UnloadPythonCustomOps清理失败注册产生的部分状态。 |
| 1770 | + * 3. 查询CustomOpFactory中是否存在该Python自定义算子creator。 | ||
| 1763 | * 预期结果: | 1771 | * 预期结果: |
| 1764 | * 1. 注册阶段签名校验失败,LoadPythonCustomOps返回FAILED。 | 1772 | * 1. 注册阶段签名校验失败,LoadPythonCustomOps返回FAILED。 |
| 1765 | - * 2. CustomOpFactory中不存在合法或非法Python自定义算子creator。 | 1773 | + * 2. 卸载后CustomOpFactory中不存在合法或非法Python自定义算子creator。 |
| 1766 | */ | 1774 | */ |
| 1767 | TEST_F(CustomOpFactoryStTest, PythonCustomOpLoaderRejectsInvalidSignatureDuringRegistration) { | 1775 | TEST_F(CustomOpFactoryStTest, PythonCustomOpLoaderRejectsInvalidSignatureDuringRegistration) { |
| 1768 | EnsureInvalidSignaturePybindCustomOpFileForSt(); | 1776 | EnsureInvalidSignaturePybindCustomOpFileForSt(); |
| @@ -1771,25 +1779,25 @@ TEST_F(CustomOpFactoryStTest, PythonCustomOpLoaderRejectsInvalidSignatureDuringR | |||
| 1771 | 1779 | ||
| 1772 | ASSERT_EQ(GePythonRuntimeManager::Instance().EnsureReady(), SUCCESS); | 1780 | ASSERT_EQ(GePythonRuntimeManager::Instance().EnsureReady(), SUCCESS); |
| 1773 | EXPECT_EQ(custom_op::LoadPythonCustomOps(), FAILED); | 1781 | EXPECT_EQ(custom_op::LoadPythonCustomOps(), FAILED); |
| 1782 | + custom_op::UnloadPythonCustomOps(); | ||
| 1774 | EXPECT_EQ(CustomOpFactory::CreateOrGetCustomOp(AscendString(kPythonCustomOpTypeForSt)), nullptr); | 1783 | EXPECT_EQ(CustomOpFactory::CreateOrGetCustomOp(AscendString(kPythonCustomOpTypeForSt)), nullptr); |
| 1775 | EXPECT_EQ(CustomOpFactory::CreateOrGetCustomOp(AscendString(kPythonAnnotatedArgsBadAttrOpTypeForSt)), nullptr); | 1784 | EXPECT_EQ(CustomOpFactory::CreateOrGetCustomOp(AscendString(kPythonAnnotatedArgsBadAttrOpTypeForSt)), nullptr); |
| 1776 | - custom_op::UnloadPythonCustomOps(); | ||
| 1777 | } | 1785 | } |
| 1778 | 1786 | ||
| 1779 | /** | 1787 | /** |
| 1780 | - * 用例描述:测试Python自定义算子通过loader注册、执行后,可以按类型移除注册信息和已创建实例。 | 1788 | + * 用例描述:测试Python自定义算子原型和实现通过loader注册、执行后,可以按类型移除注册信息。 |
| 1781 | * 预置条件: | 1789 | * 预置条件: |
| 1782 | - * 1. 构造Python自定义算子实现文件,并配置到ASCEND_CUSTOM_OPP_PATH。 | 1790 | + * 1. 构造Python自定义算子原型和实现文件,并配置到ASCEND_CUSTOM_OPP_PATH。 |
| 1783 | * 测试步骤: | 1791 | * 测试步骤: |
| 1784 | - * 1. 通过LoadPythonCustomOps加载Python实现并注册到CustomOpFactory。 | 1792 | + * 1. 通过LoadPythonCustomOps将原型和实现分别注册到OperatorFactory和CustomOpFactory。 |
| 1785 | - * 2. 通过CustomOpFactory创建Python自定义算子实例并执行。 | 1793 | + * 2. 校验原型creator生效,并通过CustomOpFactory创建Python自定义算子实例执行。 |
| 1786 | * 3. 校验Python execute写入的标记文件。 | 1794 | * 3. 校验Python execute写入的标记文件。 |
| 1787 | * 4. 调用UnloadPythonCustomOps移除该算子并清理runtime descriptor。 | 1795 | * 4. 调用UnloadPythonCustomOps移除该算子并清理runtime descriptor。 |
| 1788 | * 预期结果: | 1796 | * 预期结果: |
| 1789 | * 1. Python自定义算子execute被成功调用。 | 1797 | * 1. Python自定义算子execute被成功调用。 |
| 1790 | - * 2. creator被移除,后续查询不存在,再次创建返回空指针,runtime descriptor可成功注销。 | 1798 | + * 2. 原型和实现creator均被移除,后续查询不存在,再次创建返回空指针。 |
| 1791 | */ | 1799 | */ |
| 1792 | -TEST_F(CustomOpFactoryStTest, remove_python_custom_ops_clears_creator_and_created_instance) { | 1800 | +TEST_F(CustomOpFactoryStTest, register_and_remove_python_custom_op_proto_and_impl) { |
| 1793 | EnsureSharedPybindCustomOpFileForSt(); | 1801 | EnsureSharedPybindCustomOpFileForSt(); |
| 1794 | const auto &marker_file = GetSharedPybindCustomOpMarkerFilePathForSt(); | 1802 | const auto &marker_file = GetSharedPybindCustomOpMarkerFilePathForSt(); |
| 1795 | (void)remove(marker_file.c_str()); | 1803 | (void)remove(marker_file.c_str()); |
| @@ -1799,6 +1807,9 @@ TEST_F(CustomOpFactoryStTest, remove_python_custom_ops_clears_creator_and_create | |||
| 1799 | ASSERT_EQ(custom_op::LoadPythonCustomOps(), SUCCESS); | 1807 | ASSERT_EQ(custom_op::LoadPythonCustomOps(), SUCCESS); |
| 1800 | ScopedLoadedPythonCustomOpsForSt loaded_python_custom_ops; | 1808 | ScopedLoadedPythonCustomOpsForSt loaded_python_custom_ops; |
| 1801 | 1809 | ||
| 1810 | + ASSERT_TRUE(OperatorFactory::IsExistOp(kPythonCustomOpTypeForSt)); | ||
| 1811 | + EXPECT_FALSE(OperatorFactory::CreateOperator("st_python_proto", kPythonCustomOpTypeForSt).IsEmpty()); | ||
| 1812 | + | ||
| 1802 | const AscendString op_type(kPythonCustomOpTypeForSt); | 1813 | const AscendString op_type(kPythonCustomOpTypeForSt); |
| 1803 | ASSERT_TRUE(CustomOpFactory::IsExistOp(op_type)); | 1814 | ASSERT_TRUE(CustomOpFactory::IsExistOp(op_type)); |
| 1804 | auto *op = CustomOpFactory::CreateOrGetCustomOp(op_type); | 1815 | auto *op = CustomOpFactory::CreateOrGetCustomOp(op_type); |
| @@ -1813,6 +1824,7 @@ TEST_F(CustomOpFactoryStTest, remove_python_custom_ops_clears_creator_and_create | |||
| 1813 | custom_op::UnloadPythonCustomOps(); | 1824 | custom_op::UnloadPythonCustomOps(); |
| 1814 | loaded_python_custom_ops.Dismiss(); | 1825 | loaded_python_custom_ops.Dismiss(); |
| 1815 | 1826 | ||
| 1827 | + EXPECT_FALSE(OperatorFactory::IsExistOp(kPythonCustomOpTypeForSt)); | ||
| 1816 | EXPECT_FALSE(CustomOpFactory::IsExistOp(op_type)); | 1828 | EXPECT_FALSE(CustomOpFactory::IsExistOp(op_type)); |
| 1817 | EXPECT_EQ(CustomOpFactory::CreateOrGetCustomOp(op_type), nullptr); | 1829 | EXPECT_EQ(CustomOpFactory::CreateOrGetCustomOp(op_type), nullptr); |
| 1818 | } | 1830 | } |
| @@ -401,13 +401,6 @@ set(BASE_COMMON_TEST_FILES | |||
| 401 | "common/ge_root_model_unittest.cc" | 401 | "common/ge_root_model_unittest.cc" |
| 402 | "common/om2_utils_unittest.cc" | 402 | "common/om2_utils_unittest.cc" |
| 403 | "common/om2_model_data_unittest.cc" | 403 | "common/om2_model_data_unittest.cc" |
| 404 | - "common/om2_task_node_map_unittest.cc" | ||
| 405 | - "common/om2_task_args_refresh_type_classifier_unittest.cc" | ||
| 406 | - "common/om2_model_args_utils_unittest.cc" | ||
| 407 | - "common/om2_task_args_io_addrs_updater_unittest.cc" | ||
| 408 | - "common/om2_memory_app_type_classifier_unittest.cc" | ||
| 409 | - "common/om2_model_args_layout_planner_unittest.cc" | ||
| 410 | - "common/om2_model_args_manager_unittest.cc" | ||
| 411 | "common/thread_pool_unittest.cc" | 404 | "common/thread_pool_unittest.cc" |
| 412 | "common/nano_dbg_data_unittest.cc" | 405 | "common/nano_dbg_data_unittest.cc" |
| 413 | "common/nano_model_save_helper_unittest.cc" | 406 | "common/nano_model_save_helper_unittest.cc" |
| @@ -481,6 +474,8 @@ set(MULTI_PARTS_TEST_FILES | |||
| 481 | "common/helper/custom_op_registry_builder_unittest.cc" | 474 | "common/helper/custom_op_registry_builder_unittest.cc" |
| 482 | "runtime/custom_op/python_custom_op_ir_meta_unittest.cc" | 475 | "runtime/custom_op/python_custom_op_ir_meta_unittest.cc" |
| 483 | "runtime/custom_op/custom_op_loader_unittest.cc" | 476 | "runtime/custom_op/custom_op_loader_unittest.cc" |
| 477 | + "runtime/custom_op/python_custom_op_bridge_loader_unittest.cc" | ||
| 478 | + "runtime/custom_op/python_custom_op_proto_unittest.cc" | ||
| 484 | "graph_ir/ge_custom_op_factory_unittest.cc" | 479 | "graph_ir/ge_custom_op_factory_unittest.cc" |
| 485 | "graph_ir/ge_custom_op_pull_registry_unittest.cc" | 480 | "graph_ir/ge_custom_op_pull_registry_unittest.cc" |
| 486 | "graph_ir/ge_operator_factory_unittest.cc" | 481 | "graph_ir/ge_operator_factory_unittest.cc" |
| @@ -699,6 +694,30 @@ target_link_libraries(ge_ut_common PUBLIC | |||
| 699 | 694 | ||
| 700 | # ut binary | 695 | # ut binary |
| 701 | file(GLOB_RECURSE JIT_EXECUTION_SRC_FILES CONFIGURE_DEPENDS "${AIR_CODE_DIR}/api/session/jit_execution/*.cc") | 696 | file(GLOB_RECURSE JIT_EXECUTION_SRC_FILES CONFIGURE_DEPENDS "${AIR_CODE_DIR}/api/session/jit_execution/*.cc") |
| 697 | +add_library(python_custom_op_bridge_loader_ut_fake SHARED | ||
| 698 | + "runtime/custom_op/python_custom_op_bridge_loader_fake_bridge.cc" | ||
| 699 | +) | ||
| 700 | + | ||
| 701 | +target_compile_definitions(python_custom_op_bridge_loader_ut_fake PRIVATE | ||
| 702 | + google=ascend_private | ||
| 703 | +) | ||
| 704 | + | ||
| 705 | +target_compile_options(python_custom_op_bridge_loader_ut_fake PRIVATE | ||
| 706 | + ${AIR_COMMON_DYNAMIC_COMPILE_OPTION} | ||
| 707 | + -Werror=format | ||
| 708 | + -Wall -Wfloat-equal -Werror | ||
| 709 | +) | ||
| 710 | + | ||
| 711 | +target_include_directories(python_custom_op_bridge_loader_ut_fake PRIVATE | ||
| 712 | + ${AIR_CODE_DIR} | ||
| 713 | + ${AIR_CODE_DIR}/inc/graph_metadef | ||
| 714 | +) | ||
| 715 | + | ||
| 716 | +target_link_libraries(python_custom_op_bridge_loader_ut_fake PRIVATE | ||
| 717 | + ge_intf_pub | ||
| 718 | + ge_metadef_headers | ||
| 719 | +) | ||
| 720 | + | ||
| 702 | # libge_mutiparts_utest | 721 | # libge_mutiparts_utest |
| 703 | add_executable(ut_libge_multiparts_utest | 722 | add_executable(ut_libge_multiparts_utest |
| 704 | ${COMMON_TEST_FILES} | 723 | ${COMMON_TEST_FILES} |
| @@ -717,6 +736,11 @@ target_compile_options(ut_libge_multiparts_utest PRIVATE | |||
| 717 | target_compile_definitions(ut_libge_multiparts_utest PRIVATE | 736 | target_compile_definitions(ut_libge_multiparts_utest PRIVATE |
| 718 | google=ascend_private | 737 | google=ascend_private |
| 719 | FUNC_VISIBILITY | 738 | FUNC_VISIBILITY |
| 739 | + PYTHON_CUSTOM_OP_LOADER_UT_FAKE_BRIDGE_PATH="$<TARGET_FILE:python_custom_op_bridge_loader_ut_fake>" | ||
| 740 | +) | ||
| 741 | + | ||
| 742 | +add_dependencies(ut_libge_multiparts_utest | ||
| 743 | + python_custom_op_bridge_loader_ut_fake | ||
| 720 | ) | 744 | ) |
| 721 | 745 | ||
| 722 | target_include_directories(ut_libge_multiparts_utest PRIVATE | 746 | target_include_directories(ut_libge_multiparts_utest PRIVATE |
| @@ -107,8 +107,14 @@ std::atomic_uint32_t g_python_annotated_args_declare_count{0U}; | |||
| 107 | 107 | ||
| 108 | struct MockPythonAnnotatedArgsHolder {}; | 108 | struct MockPythonAnnotatedArgsHolder {}; |
| 109 | 109 | ||
| 110 | -void *CreateMockPythonAnnotatedArgsHolder(const custom_op::PythonCustomOpDescriptor *desc) { | 110 | +bool ValidateMockPythonAnnotatedArgs(const custom_op::PythonCustomOpAdapterDescriptorView *desc) { |
| 111 | - return (desc == nullptr) ? nullptr : new (std::nothrow) MockPythonAnnotatedArgsHolder(); | 111 | + return (desc != nullptr) && (desc->impl_descriptor_key.data != nullptr) && (desc->impl_descriptor_key.size != 0U); |
| 112 | +} | ||
| 113 | + | ||
| 114 | +void *CreateMockPythonAnnotatedArgsHolder(const custom_op::PythonCustomOpAdapterDescriptorView *desc) { | ||
| 115 | + return ((desc == nullptr) || (desc->impl_descriptor_key.data == nullptr)) ? nullptr | ||
| 116 | + : new (std::nothrow) | ||
| 117 | + MockPythonAnnotatedArgsHolder(); | ||
| 112 | } | 118 | } |
| 113 | 119 | ||
| 114 | void DestroyMockPythonAnnotatedArgsHolder(void *holder) { | 120 | void DestroyMockPythonAnnotatedArgsHolder(void *holder) { |
| @@ -1091,16 +1097,16 @@ TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskDeclaresAnnotatedArgsAndFillsK | |||
| 1091 | TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskUsesPythonAnnotatedArgsAdapter) { | 1097 | TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskUsesPythonAnnotatedArgsAdapter) { |
| 1092 | GetThreadLocalContext().SetGraphOption({{ge::SOC_VERSION, "Ascend910B"}}); | 1098 | GetThreadLocalContext().SetGraphOption({{ge::SOC_VERSION, "Ascend910B"}}); |
| 1093 | const std::string kTestOpType = "TestPythonAnnotatedArgsCustomOp_BuilderTest"; | 1099 | const std::string kTestOpType = "TestPythonAnnotatedArgsCustomOp_BuilderTest"; |
| 1094 | - custom_op::PythonCustomOpDescriptor desc; | 1100 | + custom_op::PythonCustomOpAdapterDescriptor desc; |
| 1095 | - desc.descriptor_key = "python_annotated_args_custom_engine"; | 1101 | + desc.impl_descriptor_key = "python_annotated_args_custom_engine"; |
| 1096 | desc.op_type = kTestOpType; | 1102 | desc.op_type = kTestOpType; |
| 1097 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kAnnotatedArgs); | 1103 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kAnnotatedArgs); |
| 1098 | 1104 | ||
| 1099 | - custom_op::PythonCustomOpCallbacks callbacks; | 1105 | + custom_op::PythonCustomOpAdapterCallbacks callbacks; |
| 1100 | - callbacks.create = CreateMockPythonAnnotatedArgsHolder; | 1106 | + callbacks.create_impl_holder = CreateMockPythonAnnotatedArgsHolder; |
| 1101 | - callbacks.destroy = DestroyMockPythonAnnotatedArgsHolder; | 1107 | + callbacks.destroy_impl_holder = DestroyMockPythonAnnotatedArgsHolder; |
| 1102 | callbacks.declare_launch_args = DeclareMockPythonAnnotatedArgs; | 1108 | callbacks.declare_launch_args = DeclareMockPythonAnnotatedArgs; |
| 1103 | - ASSERT_TRUE(custom_op::PythonCustomOpRuntimeRegistry::Register(desc, callbacks)); | 1109 | + ASSERT_TRUE(custom_op::PythonCustomOpImplRuntimeRegistry::Register(desc, callbacks)); |
| 1104 | const auto creator = [desc]() -> std::unique_ptr<BaseCustomOp> { | 1110 | const auto creator = [desc]() -> std::unique_ptr<BaseCustomOp> { |
| 1105 | auto adapter = std::make_unique<custom_op::PythonCustomOpAdapter>(desc); | 1111 | auto adapter = std::make_unique<custom_op::PythonCustomOpAdapter>(desc); |
| 1106 | if (!adapter->IsValid()) { | 1112 | if (!adapter->IsValid()) { |
| @@ -1125,7 +1131,7 @@ TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskUsesPythonAnnotatedArgsAdapter | |||
| 1125 | ExpectPythonAnnotatedArgsKernel(tasks[0].kernel()); | 1131 | ExpectPythonAnnotatedArgsKernel(tasks[0].kernel()); |
| 1126 | 1132 | ||
| 1127 | CustomOpFactory::RemoveCustomOps({AscendString(kTestOpType.c_str())}); | 1133 | CustomOpFactory::RemoveCustomOps({AscendString(kTestOpType.c_str())}); |
| 1128 | - EXPECT_TRUE(custom_op::PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | 1134 | + EXPECT_TRUE(custom_op::PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key)); |
| 1129 | } | 1135 | } |
| 1130 | 1136 | ||
| 1131 | TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskOnNonMobileSocDeclaresAnnotatedArgsOp) { | 1137 | TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskOnNonMobileSocDeclaresAnnotatedArgsOp) { |
| @@ -0,0 +1,67 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +# -*- coding: utf-8 -*- | ||
| 3 | +# ----------------------------------------------------------------------------------------------------------- | ||
| 4 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 5 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 6 | +# CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 7 | +# Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 8 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 9 | +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 10 | +# See LICENSE in the root of the software repository for the full text of the License. | ||
| 11 | +# ----------------------------------------------------------------------------------------------------------- | ||
| 12 | + | ||
| 13 | +"""Pytest coverage for Python custom op artifact-set discovery.""" | ||
| 14 | + | ||
| 15 | +import json | ||
| 16 | + | ||
| 17 | +from ge._internal.artifact_utils import current_platform_tag, current_python_tag | ||
| 18 | +from ge.custom_op import _artifact_utils as artifact_utils | ||
| 19 | + | ||
| 20 | + | ||
| 21 | +def _write_artifact_set(root, *, bridge_abi=None): | ||
| 22 | + artifact_dir = root / f"{current_python_tag()}-{current_platform_tag()}" | ||
| 23 | + artifact_dir.mkdir(parents=True) | ||
| 24 | + (artifact_dir / "libge_python_custom_op_bridge.so").touch() | ||
| 25 | + (artifact_dir / "_ge_custom_op_native.so").touch() | ||
| 26 | + manifest = { | ||
| 27 | + "python_tag": current_python_tag(), | ||
| 28 | + "platform": current_platform_tag(), | ||
| 29 | + "bridge_abi": ( | ||
| 30 | + artifact_utils.BRIDGE_ABI_VERSION if bridge_abi is None else bridge_abi | ||
| 31 | + ), | ||
| 32 | + "artifacts": { | ||
| 33 | + "bridge": "libge_python_custom_op_bridge.so", | ||
| 34 | + "native": "_ge_custom_op_native.so", | ||
| 35 | + }, | ||
| 36 | + } | ||
| 37 | + (artifact_dir / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8") | ||
| 38 | + return artifact_dir | ||
| 39 | + | ||
| 40 | + | ||
| 41 | +def test_find_prebuilt_custom_op_artifact_requires_complete_set(tmp_path, monkeypatch): | ||
| 42 | + root = tmp_path / "python_custom_op_artifacts" | ||
| 43 | + monkeypatch.setattr(artifact_utils, "artifacts_root", lambda: root) | ||
| 44 | + artifact_dir = _write_artifact_set(root) | ||
| 45 | + | ||
| 46 | + artifact = artifact_utils.find_prebuilt_artifact() | ||
| 47 | + | ||
| 48 | + assert artifact is not None | ||
| 49 | + assert artifact.bridge_path == artifact_dir / "libge_python_custom_op_bridge.so" | ||
| 50 | + assert artifact.native_path == artifact_dir / "_ge_custom_op_native.so" | ||
| 51 | + | ||
| 52 | + | ||
| 53 | +def test_find_prebuilt_custom_op_artifact_rejects_incomplete_set(tmp_path, monkeypatch): | ||
| 54 | + root = tmp_path / "python_custom_op_artifacts" | ||
| 55 | + monkeypatch.setattr(artifact_utils, "artifacts_root", lambda: root) | ||
| 56 | + artifact_dir = _write_artifact_set(root) | ||
| 57 | + (artifact_dir / "_ge_custom_op_native.so").unlink() | ||
| 58 | + | ||
| 59 | + assert artifact_utils.find_prebuilt_artifact() is None | ||
| 60 | + | ||
| 61 | + | ||
| 62 | +def test_find_prebuilt_custom_op_artifact_rejects_abi_mismatch(tmp_path, monkeypatch): | ||
| 63 | + root = tmp_path / "python_custom_op_artifacts" | ||
| 64 | + monkeypatch.setattr(artifact_utils, "artifacts_root", lambda: root) | ||
| 65 | + _write_artifact_set(root, bridge_abi=artifact_utils.BRIDGE_ABI_VERSION + 1) | ||
| 66 | + | ||
| 67 | + assert artifact_utils.find_prebuilt_artifact() is None | ||
| @@ -24,6 +24,8 @@ try: | |||
| 24 | bridge = importlib.import_module("ge.custom_op._bridge") | 24 | bridge = importlib.import_module("ge.custom_op._bridge") |
| 25 | custom_op = importlib.import_module("ge.custom_op") | 25 | custom_op = importlib.import_module("ge.custom_op") |
| 26 | ir_types = importlib.import_module("ge.custom_op._ir_types") | 26 | ir_types = importlib.import_module("ge.custom_op._ir_types") |
| 27 | + proto = importlib.import_module("ge.custom_op.proto") | ||
| 28 | + runtime = importlib.import_module("ge.runtime") | ||
| 27 | from ge.runtime import Tensor | 29 | from ge.runtime import Tensor |
| 28 | except ImportError as exc: | 30 | except ImportError as exc: |
| 29 | pytest.skip(f"无法导入 Python custom op 相关模块: {exc}", allow_module_level=True) | 31 | pytest.skip(f"无法导入 Python custom op 相关模块: {exc}", allow_module_level=True) |
| @@ -33,12 +35,14 @@ except ImportError as exc: | |||
| 33 | def clear_python_custom_op_runtime(monkeypatch): | 35 | def clear_python_custom_op_runtime(monkeypatch): |
| 34 | monkeypatch.delenv(bootstrap.ENV_PY_CUSTOM_OP_PATH, raising=False) | 36 | monkeypatch.delenv(bootstrap.ENV_PY_CUSTOM_OP_PATH, raising=False) |
| 35 | custom_op.clear_registered_op_impls() | 37 | custom_op.clear_registered_op_impls() |
| 38 | + proto.clear_registered_op_protos() | ||
| 36 | bridge.clear_op_impl_holders() | 39 | bridge.clear_op_impl_holders() |
| 37 | bridge.clear_loaded_op_impl_modules() | 40 | bridge.clear_loaded_op_impl_modules() |
| 38 | yield | 41 | yield |
| 39 | bridge.clear_op_impl_holders() | 42 | bridge.clear_op_impl_holders() |
| 40 | bridge.clear_loaded_op_impl_modules() | 43 | bridge.clear_loaded_op_impl_modules() |
| 41 | custom_op.clear_registered_op_impls() | 44 | custom_op.clear_registered_op_impls() |
| 45 | + proto.clear_registered_op_protos() | ||
| 42 | 46 | ||
| 43 | 47 | ||
| 44 | def _write_custom_op_module( | 48 | def _write_custom_op_module( |
| @@ -138,6 +142,33 @@ def test_bridge_validate_op_impl_descriptor_rejects_unknown_descriptor(): | |||
| 138 | bridge.validate_op_impl_descriptor("missing-descriptor", None) | 142 | bridge.validate_op_impl_descriptor("missing-descriptor", None) |
| 139 | 143 | ||
| 140 | 144 | ||
| 145 | +def test_bridge_descriptor_snapshot_contains_proto_and_impl(): | ||
| 146 | + | ||
| 147 | + def infer_meta(x: runtime.TensorDesc, *, alpha: float = 1.0) -> runtime.TensorDesc: | ||
| 148 | + raise AssertionError("prototype registration must not execute infer_meta") | ||
| 149 | + | ||
| 150 | + | ||
| 151 | + class SnapshotCustom(custom_op.EagerExecuteOp): | ||
| 152 | + def execute(self, x: Tensor, *, alpha: float) -> None: | ||
| 153 | + pass | ||
| 154 | + | ||
| 155 | + snapshot = bridge.load_and_get_op_descriptors() | ||
| 156 | + | ||
| 157 | + assert [item["op_type"] for item in snapshot["protos"]] == ["SnapshotCustom"] | ||
| 158 | + assert [item["op_type"] for item in snapshot["impls"]] == ["SnapshotCustom"] | ||
| 159 | + | ||
| 160 | + | ||
| 161 | +def test_bridge_descriptor_snapshot_supports_proto_only(): | ||
| 162 | + | ||
| 163 | + def infer_meta(x: runtime.TensorDesc) -> runtime.TensorDesc: | ||
| 164 | + raise AssertionError("prototype registration must not execute infer_meta") | ||
| 165 | + | ||
| 166 | + snapshot = bridge.load_and_get_op_descriptors() | ||
| 167 | + | ||
| 168 | + assert [item["op_type"] for item in snapshot["protos"]] == ["ProtoOnlyCustom"] | ||
| 169 | + assert snapshot["impls"] == [] | ||
| 170 | + | ||
| 171 | + | ||
| 141 | def test_register_op_impl_exports_descriptor_dict(): | 172 | def test_register_op_impl_exports_descriptor_dict(): |
| 142 | 173 | ||
| 143 | class AddCustom(custom_op.EagerExecuteOp): | 174 | class AddCustom(custom_op.EagerExecuteOp): |
| @@ -85,8 +85,8 @@ struct MockPythonCustomOpHolder { | |||
| 85 | bool executed{false}; | 85 | bool executed{false}; |
| 86 | }; | 86 | }; |
| 87 | 87 | ||
| 88 | -void *CreateMockPythonCustomOpHolder(const PythonCustomOpDescriptor *desc) { | 88 | +void *CreateMockPythonCustomOpHolder(const PythonCustomOpAdapterDescriptorView *desc) { |
| 89 | - if (desc == nullptr) { | 89 | + if ((desc == nullptr) || (desc->impl_descriptor_key.data == nullptr)) { |
| 90 | return nullptr; | 90 | return nullptr; |
| 91 | } | 91 | } |
| 92 | return new (std::nothrow) MockPythonCustomOpHolder(); | 92 | return new (std::nothrow) MockPythonCustomOpHolder(); |
| @@ -111,6 +111,14 @@ graphStatus DeclareMockPythonCustomOp(const void *holder, gert::AnnotatedArgsCon | |||
| 111 | return (holder == nullptr) ? GRAPH_FAILED : GRAPH_SUCCESS; | 111 | return (holder == nullptr) ? GRAPH_FAILED : GRAPH_SUCCESS; |
| 112 | } | 112 | } |
| 113 | 113 | ||
| 114 | +void *FailCreatePythonCustomOpHolder(const PythonCustomOpAdapterDescriptorView *) { | ||
| 115 | + return nullptr; | ||
| 116 | +} | ||
| 117 | + | ||
| 118 | +graphStatus FailExecutePythonCustomOp(const void *, gert::EagerOpExecutionContext *) { | ||
| 119 | + return GRAPH_FAILED; | ||
| 120 | +} | ||
| 121 | + | ||
| 114 | std::vector<uint8_t> BuildCustomOpPartition(const std::string &name, const std::vector<uint8_t> &bin) { | 122 | std::vector<uint8_t> BuildCustomOpPartition(const std::string &name, const std::vector<uint8_t> &bin) { |
| 115 | ge::CustomKernelItemHeader header{ge::kCustomKernelItemMagic, static_cast<uint32_t>(name.size()), | 123 | ge::CustomKernelItemHeader header{ge::kCustomKernelItemMagic, static_cast<uint32_t>(name.size()), |
| 116 | static_cast<uint32_t>(bin.size())}; | 124 | static_cast<uint32_t>(bin.size())}; |
| @@ -225,17 +233,17 @@ TEST(UtestCustomOpCast, falls_back_to_dynamic_cast_for_cpp_custom_op) { | |||
| 225 | } | 233 | } |
| 226 | 234 | ||
| 227 | TEST(UtestCustomOpCast, filters_python_adapter_by_capability) { | 235 | TEST(UtestCustomOpCast, filters_python_adapter_by_capability) { |
| 228 | - PythonCustomOpDescriptor desc; | 236 | + PythonCustomOpAdapterDescriptor desc; |
| 229 | - desc.descriptor_key = "python_adapter_eager_only"; | 237 | + desc.impl_descriptor_key = "python_adapter_eager_only"; |
| 230 | desc.op_type = "PythonAdapterEagerOnly"; | 238 | desc.op_type = "PythonAdapterEagerOnly"; |
| 231 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); | 239 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); |
| 232 | 240 | ||
| 233 | - PythonCustomOpCallbacks callbacks; | 241 | + PythonCustomOpAdapterCallbacks callbacks; |
| 234 | - callbacks.create = CreateMockPythonCustomOpHolder; | 242 | + callbacks.create_impl_holder = CreateMockPythonCustomOpHolder; |
| 235 | - callbacks.destroy = DestroyMockPythonCustomOpHolder; | 243 | + callbacks.destroy_impl_holder = DestroyMockPythonCustomOpHolder; |
| 236 | callbacks.execute = ExecuteMockPythonCustomOp; | 244 | callbacks.execute = ExecuteMockPythonCustomOp; |
| 237 | 245 | ||
| 238 | - ASSERT_TRUE(PythonCustomOpRuntimeRegistry::Register(desc, callbacks)); | 246 | + ASSERT_TRUE(PythonCustomOpImplRuntimeRegistry::Register(desc, callbacks)); |
| 239 | { | 247 | { |
| 240 | PythonCustomOpAdapter adapter(desc); | 248 | PythonCustomOpAdapter adapter(desc); |
| 241 | EXPECT_TRUE(adapter.IsValid()); | 249 | EXPECT_TRUE(adapter.IsValid()); |
| @@ -250,24 +258,23 @@ TEST(UtestCustomOpCast, filters_python_adapter_by_capability) { | |||
| 250 | 258 | ||
| 251 | EXPECT_EQ(GRAPH_SUCCESS, CustomOpCast<EagerExecuteOp>(base)->Execute(nullptr)); | 259 | EXPECT_EQ(GRAPH_SUCCESS, CustomOpCast<EagerExecuteOp>(base)->Execute(nullptr)); |
| 252 | EXPECT_EQ(GRAPH_FAILED, adapter.Compile(nullptr)); | 260 | EXPECT_EQ(GRAPH_FAILED, adapter.Compile(nullptr)); |
| 253 | - EXPECT_FALSE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | 261 | + EXPECT_FALSE(PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key)); |
| 254 | - PythonCustomOpRuntimeRegistry::Clear(); | ||
| 255 | } | 262 | } |
| 256 | - EXPECT_FALSE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | 263 | + EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key)); |
| 257 | } | 264 | } |
| 258 | 265 | ||
| 259 | TEST(UtestCustomOpCast, filters_python_adapter_annotated_args_by_capability) { | 266 | TEST(UtestCustomOpCast, filters_python_adapter_annotated_args_by_capability) { |
| 260 | - PythonCustomOpDescriptor desc; | 267 | + PythonCustomOpAdapterDescriptor desc; |
| 261 | - desc.descriptor_key = "python_adapter_annotated_args_only"; | 268 | + desc.impl_descriptor_key = "python_adapter_annotated_args_only"; |
| 262 | desc.op_type = "PythonAdapterAnnotatedArgsOnly"; | 269 | desc.op_type = "PythonAdapterAnnotatedArgsOnly"; |
| 263 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kAnnotatedArgs); | 270 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kAnnotatedArgs); |
| 264 | 271 | ||
| 265 | - PythonCustomOpCallbacks callbacks; | 272 | + PythonCustomOpAdapterCallbacks callbacks; |
| 266 | - callbacks.create = CreateMockPythonCustomOpHolder; | 273 | + callbacks.create_impl_holder = CreateMockPythonCustomOpHolder; |
| 267 | - callbacks.destroy = DestroyMockPythonCustomOpHolder; | 274 | + callbacks.destroy_impl_holder = DestroyMockPythonCustomOpHolder; |
| 268 | callbacks.declare_launch_args = DeclareMockPythonCustomOp; | 275 | callbacks.declare_launch_args = DeclareMockPythonCustomOp; |
| 269 | 276 | ||
| 270 | - ASSERT_TRUE(PythonCustomOpRuntimeRegistry::Register(desc, callbacks)); | 277 | + ASSERT_TRUE(PythonCustomOpImplRuntimeRegistry::Register(desc, callbacks)); |
| 271 | { | 278 | { |
| 272 | PythonCustomOpAdapter adapter(desc); | 279 | PythonCustomOpAdapter adapter(desc); |
| 273 | EXPECT_TRUE(adapter.IsValid()); | 280 | EXPECT_TRUE(adapter.IsValid()); |
| @@ -277,23 +284,23 @@ TEST(UtestCustomOpCast, filters_python_adapter_annotated_args_by_capability) { | |||
| 277 | EXPECT_NE(nullptr, CustomOpCast<AnnotatedArgsOp>(base)); | 284 | EXPECT_NE(nullptr, CustomOpCast<AnnotatedArgsOp>(base)); |
| 278 | EXPECT_EQ(nullptr, CustomOpCast<CompilableOp>(base)); | 285 | EXPECT_EQ(nullptr, CustomOpCast<CompilableOp>(base)); |
| 279 | } | 286 | } |
| 280 | - EXPECT_TRUE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | 287 | + EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key)); |
| 281 | } | 288 | } |
| 282 | 289 | ||
| 283 | TEST(UtestCustomOpCast, exposes_each_python_adapter_capability_in_dual_mode) { | 290 | TEST(UtestCustomOpCast, exposes_each_python_adapter_capability_in_dual_mode) { |
| 284 | - PythonCustomOpDescriptor desc; | 291 | + PythonCustomOpAdapterDescriptor desc; |
| 285 | - desc.descriptor_key = "python_adapter_dual_capability"; | 292 | + desc.impl_descriptor_key = "python_adapter_dual_capability"; |
| 286 | desc.op_type = "PythonAdapterDualCapability"; | 293 | desc.op_type = "PythonAdapterDualCapability"; |
| 287 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); | 294 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); |
| 288 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kAnnotatedArgs); | 295 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kAnnotatedArgs); |
| 289 | 296 | ||
| 290 | - PythonCustomOpCallbacks callbacks; | 297 | + PythonCustomOpAdapterCallbacks callbacks; |
| 291 | - callbacks.create = CreateMockPythonCustomOpHolder; | 298 | + callbacks.create_impl_holder = CreateMockPythonCustomOpHolder; |
| 292 | - callbacks.destroy = DestroyMockPythonCustomOpHolder; | 299 | + callbacks.destroy_impl_holder = DestroyMockPythonCustomOpHolder; |
| 293 | callbacks.execute = ExecuteMockPythonCustomOp; | 300 | callbacks.execute = ExecuteMockPythonCustomOp; |
| 294 | callbacks.declare_launch_args = DeclareMockPythonCustomOp; | 301 | callbacks.declare_launch_args = DeclareMockPythonCustomOp; |
| 295 | 302 | ||
| 296 | - ASSERT_TRUE(PythonCustomOpRuntimeRegistry::Register(desc, callbacks)); | 303 | + ASSERT_TRUE(PythonCustomOpImplRuntimeRegistry::Register(desc, callbacks)); |
| 297 | { | 304 | { |
| 298 | PythonCustomOpAdapter adapter(desc); | 305 | PythonCustomOpAdapter adapter(desc); |
| 299 | EXPECT_TRUE(adapter.IsValid()); | 306 | EXPECT_TRUE(adapter.IsValid()); |
| @@ -301,20 +308,74 @@ TEST(UtestCustomOpCast, exposes_each_python_adapter_capability_in_dual_mode) { | |||
| 301 | EXPECT_NE(nullptr, CustomOpCast<EagerExecuteOp>(base)); | 308 | EXPECT_NE(nullptr, CustomOpCast<EagerExecuteOp>(base)); |
| 302 | EXPECT_NE(nullptr, CustomOpCast<AnnotatedArgsOp>(base)); | 309 | EXPECT_NE(nullptr, CustomOpCast<AnnotatedArgsOp>(base)); |
| 303 | } | 310 | } |
| 304 | - EXPECT_TRUE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | 311 | + EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key)); |
| 305 | } | 312 | } |
| 306 | 313 | ||
| 307 | TEST(UtestCustomOpCast, rejects_unsupported_python_adapter_capability) { | 314 | TEST(UtestCustomOpCast, rejects_unsupported_python_adapter_capability) { |
| 308 | - PythonCustomOpDescriptor desc; | 315 | + PythonCustomOpAdapterDescriptor desc; |
| 309 | - desc.descriptor_key = "python_adapter_shape_unsupported"; | 316 | + desc.impl_descriptor_key = "python_adapter_shape_unsupported"; |
| 310 | desc.op_type = "PythonAdapterShapeUnsupported"; | 317 | desc.op_type = "PythonAdapterShapeUnsupported"; |
| 311 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kShapeInfer); | 318 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kShapeInfer); |
| 312 | 319 | ||
| 313 | - PythonCustomOpCallbacks callbacks; | 320 | + PythonCustomOpAdapterCallbacks callbacks; |
| 314 | - callbacks.create = CreateMockPythonCustomOpHolder; | 321 | + callbacks.create_impl_holder = CreateMockPythonCustomOpHolder; |
| 315 | - callbacks.destroy = DestroyMockPythonCustomOpHolder; | 322 | + callbacks.destroy_impl_holder = DestroyMockPythonCustomOpHolder; |
| 316 | 323 | ||
| 317 | - EXPECT_FALSE(PythonCustomOpRuntimeRegistry::Register(desc, callbacks)); | 324 | + EXPECT_FALSE(PythonCustomOpImplRuntimeRegistry::Register(desc, callbacks)); |
| 325 | +} | ||
| 326 | + | ||
| 327 | +TEST(UtestCustomOpCast, rejects_python_adapter_with_missing_impl_key_or_callback) { | ||
| 328 | + PythonCustomOpAdapterDescriptor desc; | ||
| 329 | + desc.impl_descriptor_key = "python_adapter_required_fields"; | ||
| 330 | + desc.op_type = "PythonAdapterRequiredFields"; | ||
| 331 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); | ||
| 332 | + | ||
| 333 | + PythonCustomOpAdapterCallbacks callbacks; | ||
| 334 | + callbacks.create_impl_holder = CreateMockPythonCustomOpHolder; | ||
| 335 | + callbacks.destroy_impl_holder = DestroyMockPythonCustomOpHolder; | ||
| 336 | + callbacks.execute = ExecuteMockPythonCustomOp; | ||
| 337 | + auto invalid_callbacks = callbacks; | ||
| 338 | + invalid_callbacks.create_impl_holder = nullptr; | ||
| 339 | + EXPECT_FALSE(invalid_callbacks.IsValid(desc.capabilities)); | ||
| 340 | + invalid_callbacks = callbacks; | ||
| 341 | + invalid_callbacks.destroy_impl_holder = nullptr; | ||
| 342 | + EXPECT_FALSE(invalid_callbacks.IsValid(desc.capabilities)); | ||
| 343 | + invalid_callbacks = callbacks; | ||
| 344 | + invalid_callbacks.execute = nullptr; | ||
| 345 | + EXPECT_FALSE(invalid_callbacks.IsValid(desc.capabilities)); | ||
| 346 | +} | ||
| 347 | + | ||
| 348 | +TEST(UtestCustomOpCast, releases_runtime_lease_when_holder_creation_fails) { | ||
| 349 | + PythonCustomOpAdapterCallbacks callbacks; | ||
| 350 | + callbacks.create_impl_holder = CreateMockPythonCustomOpHolder; | ||
| 351 | + callbacks.destroy_impl_holder = DestroyMockPythonCustomOpHolder; | ||
| 352 | + callbacks.execute = ExecuteMockPythonCustomOp; | ||
| 353 | + PythonCustomOpAdapterDescriptor create_desc; | ||
| 354 | + create_desc.impl_descriptor_key = "python_adapter_fail_create"; | ||
| 355 | + create_desc.op_type = "PythonAdapterFailCreate"; | ||
| 356 | + AddCustomOpCapability(create_desc.capabilities, CustomOpCapability::kEagerExecute); | ||
| 357 | + callbacks.create_impl_holder = FailCreatePythonCustomOpHolder; | ||
| 358 | + ASSERT_TRUE(PythonCustomOpImplRuntimeRegistry::Register(create_desc, callbacks)); | ||
| 359 | + { | ||
| 360 | + PythonCustomOpAdapter adapter(create_desc); | ||
| 361 | + EXPECT_FALSE(adapter.IsValid()); | ||
| 362 | + } | ||
| 363 | + EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Unregister(create_desc.impl_descriptor_key)); | ||
| 364 | + | ||
| 365 | + PythonCustomOpAdapterDescriptor execute_desc; | ||
| 366 | + execute_desc.impl_descriptor_key = "python_adapter_fail_execute"; | ||
| 367 | + execute_desc.op_type = "PythonAdapterFailExecute"; | ||
| 368 | + AddCustomOpCapability(execute_desc.capabilities, CustomOpCapability::kEagerExecute); | ||
| 369 | + callbacks.create_impl_holder = CreateMockPythonCustomOpHolder; | ||
| 370 | + callbacks.destroy_impl_holder = DestroyMockPythonCustomOpHolder; | ||
| 371 | + callbacks.execute = FailExecutePythonCustomOp; | ||
| 372 | + ASSERT_TRUE(PythonCustomOpImplRuntimeRegistry::Register(execute_desc, callbacks)); | ||
| 373 | + { | ||
| 374 | + PythonCustomOpAdapter adapter(execute_desc); | ||
| 375 | + ASSERT_TRUE(adapter.IsValid()); | ||
| 376 | + EXPECT_EQ(adapter.Execute(nullptr), GRAPH_FAILED); | ||
| 377 | + } | ||
| 378 | + EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Unregister(execute_desc.impl_descriptor_key)); | ||
| 318 | } | 379 | } |
| 319 | 380 | ||
| 320 | TEST(UtestCustomOpRegistry, load_custom_ops_partition_deserializes_registered_portable_op) { | 381 | TEST(UtestCustomOpRegistry, load_custom_ops_partition_deserializes_registered_portable_op) { |
| @@ -0,0 +1,193 @@ | |||
| 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 | +namespace ge { | ||
| 20 | +namespace custom_op { | ||
| 21 | +namespace { | ||
| 22 | +constexpr const char *kScenarioEnvName = "GE_PYTHON_CUSTOM_OP_LOADER_UT_SCENARIO"; | ||
| 23 | +constexpr const char *kMultiProtoFailure = "multi_proto_failure"; | ||
| 24 | +constexpr const char *kAdapterFailure = "adapter_failure"; | ||
| 25 | +constexpr const char *kSuccess = "success"; | ||
| 26 | +constexpr const char *kCppProtoImpl = "cpp_proto_impl"; | ||
| 27 | + | ||
| 28 | +constexpr const char *kMultiProtoOp = "PythonLoaderMultiProtoRollbackUt"; | ||
| 29 | +constexpr const char *kAdapterOpA = "PythonLoaderAdapterRollbackAUt"; | ||
| 30 | +constexpr const char *kAdapterOpB = "PythonLoaderAdapterRollbackBUt"; | ||
| 31 | +constexpr const char *kSuccessOp = "PythonLoaderSuccessUt"; | ||
| 32 | +constexpr const char *kCppProtoOp = "PythonLoaderCppProtoOwnershipUt"; | ||
| 33 | + | ||
| 34 | +constexpr const char *kMultiProtoKey = "loader_ut:multi_proto"; | ||
| 35 | +constexpr const char *kAdapterProtoKeyA = "loader_ut:adapter_proto_a"; | ||
| 36 | +constexpr const char *kAdapterProtoKeyB = "loader_ut:adapter_proto_b"; | ||
| 37 | +constexpr const char *kAdapterImplKeyA = "loader_ut:adapter_impl_a"; | ||
| 38 | +constexpr const char *kAdapterImplKeyB = "loader_ut:adapter_impl_b"; | ||
| 39 | +constexpr const char *kSuccessProtoKey = "loader_ut:success_proto"; | ||
| 40 | +constexpr const char *kSuccessImplKey = "loader_ut:success_impl"; | ||
| 41 | +constexpr const char *kCppProtoImplKey = "loader_ut:cpp_proto_impl"; | ||
| 42 | + | ||
| 43 | +std::atomic<uint32_t> g_register_count{0U}; | ||
| 44 | +std::atomic<uint32_t> g_reset_count{0U}; | ||
| 45 | +int g_holder = 0; | ||
| 46 | + | ||
| 47 | +PythonCustomOpStringView StringView(const char *value) { | ||
| 48 | + return PythonCustomOpStringView{value, (value == nullptr) ? 0U : std::strlen(value)}; | ||
| 49 | +} | ||
| 50 | + | ||
| 51 | +PythonCustomOpProtoDescriptorView MakeProto(const char *descriptor_key, const char *op_type) { | ||
| 52 | + return PythonCustomOpProtoDescriptorView{ | ||
| 53 | + StringView(descriptor_key), StringView(op_type), nullptr, 0U, nullptr, 0U, nullptr, 0U}; | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +PythonCustomOpAdapterDescriptorView MakeAdapter(const char *op_type, const char *impl_key) { | ||
| 57 | + return PythonCustomOpAdapterDescriptorView{StringView(op_type), StringView(impl_key), | ||
| 58 | + static_cast<CustomOpCapabilityMask>(CustomOpCapability::kEagerExecute)}; | ||
| 59 | +} | ||
| 60 | + | ||
| 61 | +void *CreateImplHolder(const PythonCustomOpAdapterDescriptorView *desc) { | ||
| 62 | + return (desc == nullptr) ? nullptr : &g_holder; | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +void DestroyImplHolder(void *holder) { | ||
| 66 | + (void)holder; | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +graphStatus Execute(const void *holder, gert::EagerOpExecutionContext *ctx) { | ||
| 70 | + (void)ctx; | ||
| 71 | + return (holder == nullptr) ? GRAPH_FAILED : GRAPH_SUCCESS; | ||
| 72 | +} | ||
| 73 | + | ||
| 74 | +PythonCustomOpAdapterCallbacks MakeCallbacks(const bool reject) { | ||
| 75 | + PythonCustomOpAdapterCallbacks callbacks; | ||
| 76 | + callbacks.create_impl_holder = &CreateImplHolder; | ||
| 77 | + callbacks.destroy_impl_holder = &DestroyImplHolder; | ||
| 78 | + callbacks.execute = &Execute; | ||
| 79 | + if (reject) { | ||
| 80 | + callbacks.create_impl_holder = nullptr; | ||
| 81 | + } | ||
| 82 | + (void)reject; | ||
| 83 | + return callbacks; | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +Status SetArtifactConfig(const PythonCustomOpBridgeArtifactConfig *config) { | ||
| 87 | + if ((config == nullptr) || (config->artifact_root == nullptr) || (config->native_module_path == nullptr)) { | ||
| 88 | + return static_cast<Status>(GRAPH_FAILED); | ||
| 89 | + } | ||
| 90 | + return static_cast<Status>(GRAPH_SUCCESS); | ||
| 91 | +} | ||
| 92 | + | ||
| 93 | +Status RegisterMultiProtoFailure(const PythonCustomOpRegistrar ®istrar) { | ||
| 94 | + const auto first = MakeProto(kMultiProtoKey, kMultiProtoOp); | ||
| 95 | + if (!registrar.register_op_proto(&first)) { | ||
| 96 | + return static_cast<Status>(GRAPH_FAILED); | ||
| 97 | + } | ||
| 98 | + const auto invalid = MakeProto("loader_ut:invalid_proto", ""); | ||
| 99 | + return static_cast<Status>(registrar.register_op_proto(&invalid) ? GRAPH_SUCCESS : GRAPH_FAILED); | ||
| 100 | +} | ||
| 101 | + | ||
| 102 | +Status RegisterAdapterFailure(const PythonCustomOpRegistrar ®istrar) { | ||
| 103 | + const auto proto_a = MakeProto(kAdapterProtoKeyA, kAdapterOpA); | ||
| 104 | + const auto proto_b = MakeProto(kAdapterProtoKeyB, kAdapterOpB); | ||
| 105 | + if ((!registrar.register_op_proto(&proto_a)) || (!registrar.register_op_proto(&proto_b))) { | ||
| 106 | + return static_cast<Status>(GRAPH_FAILED); | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + const auto callbacks = MakeCallbacks(false); | ||
| 110 | + const auto adapter_a = MakeAdapter(kAdapterOpA, kAdapterImplKeyA); | ||
| 111 | + if (!registrar.register_op_adapter(&adapter_a, &callbacks)) { | ||
| 112 | + return static_cast<Status>(GRAPH_FAILED); | ||
| 113 | + } | ||
| 114 | + const auto rejecting_callbacks = MakeCallbacks(true); | ||
| 115 | + const auto adapter_b = MakeAdapter(kAdapterOpB, kAdapterImplKeyB); | ||
| 116 | + return static_cast<Status>(registrar.register_op_adapter(&adapter_b, &rejecting_callbacks) ? GRAPH_SUCCESS | ||
| 117 | + : GRAPH_FAILED); | ||
| 118 | +} | ||
| 119 | + | ||
| 120 | +Status RegisterSuccess(const PythonCustomOpRegistrar ®istrar) { | ||
| 121 | + const auto proto = MakeProto(kSuccessProtoKey, kSuccessOp); | ||
| 122 | + if (!registrar.register_op_proto(&proto)) { | ||
| 123 | + return static_cast<Status>(GRAPH_FAILED); | ||
| 124 | + } | ||
| 125 | + const auto callbacks = MakeCallbacks(false); | ||
| 126 | + const auto adapter = MakeAdapter(kSuccessOp, kSuccessImplKey); | ||
| 127 | + return static_cast<Status>(registrar.register_op_adapter(&adapter, &callbacks) ? GRAPH_SUCCESS : GRAPH_FAILED); | ||
| 128 | +} | ||
| 129 | + | ||
| 130 | +Status RegisterCppProtoImpl(const PythonCustomOpRegistrar ®istrar) { | ||
| 131 | + const auto callbacks = MakeCallbacks(false); | ||
| 132 | + const auto adapter = MakeAdapter(kCppProtoOp, kCppProtoImplKey); | ||
| 133 | + return static_cast<Status>(registrar.register_op_adapter(&adapter, &callbacks) ? GRAPH_SUCCESS : GRAPH_FAILED); | ||
| 134 | +} | ||
| 135 | + | ||
| 136 | +Status RegisterCustomOps(const PythonCustomOpRegistrar *registrar) { | ||
| 137 | + ++g_register_count; | ||
| 138 | + if ((registrar == nullptr) || (registrar->register_op_proto == nullptr) || | ||
| 139 | + (registrar->register_op_adapter == nullptr)) { | ||
| 140 | + return static_cast<Status>(GRAPH_FAILED); | ||
| 141 | + } | ||
| 142 | + const char *scenario = std::getenv(kScenarioEnvName); | ||
| 143 | + if (scenario == nullptr) { | ||
| 144 | + return static_cast<Status>(GRAPH_FAILED); | ||
| 145 | + } | ||
| 146 | + if (std::strcmp(scenario, kMultiProtoFailure) == 0) { | ||
| 147 | + return RegisterMultiProtoFailure(*registrar); | ||
| 148 | + } | ||
| 149 | + if (std::strcmp(scenario, kAdapterFailure) == 0) { | ||
| 150 | + return RegisterAdapterFailure(*registrar); | ||
| 151 | + } | ||
| 152 | + if (std::strcmp(scenario, kSuccess) == 0) { | ||
| 153 | + return RegisterSuccess(*registrar); | ||
| 154 | + } | ||
| 155 | + if (std::strcmp(scenario, kCppProtoImpl) == 0) { | ||
| 156 | + return RegisterCppProtoImpl(*registrar); | ||
| 157 | + } | ||
| 158 | + return static_cast<Status>(GRAPH_FAILED); | ||
| 159 | +} | ||
| 160 | + | ||
| 161 | +void ResetBridgeState() { | ||
| 162 | + ++g_reset_count; | ||
| 163 | +} | ||
| 164 | + | ||
| 165 | +void ShutdownBridge() {} | ||
| 166 | +} // namespace | ||
| 167 | +} // namespace custom_op | ||
| 168 | +} // namespace ge | ||
| 169 | + | ||
| 170 | +extern "C" __attribute__((visibility("default"))) uint32_t GePythonCustomOpLoaderUtGetRegisterCount() { | ||
| 171 | + return ge::custom_op::g_register_count.load(); | ||
| 172 | +} | ||
| 173 | + | ||
| 174 | +extern "C" __attribute__((visibility("default"))) uint32_t GePythonCustomOpLoaderUtGetResetCount() { | ||
| 175 | + return ge::custom_op::g_reset_count.load(); | ||
| 176 | +} | ||
| 177 | + | ||
| 178 | +extern "C" __attribute__((visibility("default"))) void GePythonCustomOpLoaderUtResetCounters() { | ||
| 179 | + ge::custom_op::g_register_count.store(0U); | ||
| 180 | + ge::custom_op::g_reset_count.store(0U); | ||
| 181 | +} | ||
| 182 | + | ||
| 183 | +extern "C" __attribute__((visibility("default"))) const ge::custom_op::PythonCustomOpBridgeApi * | ||
| 184 | +GeGetPythonCustomOpBridgeApi() { | ||
| 185 | + static const ge::custom_op::PythonCustomOpBridgeApi kBridgeApi = { | ||
| 186 | + ge::custom_op::kPythonCustomOpBridgeAbiVersion, | ||
| 187 | + &ge::custom_op::SetArtifactConfig, | ||
| 188 | + &ge::custom_op::RegisterCustomOps, | ||
| 189 | + &ge::custom_op::ResetBridgeState, | ||
| 190 | + &ge::custom_op::ShutdownBridge, | ||
| 191 | + }; | ||
| 192 | + return &kBridgeApi; | ||
| 193 | +} | ||
| @@ -0,0 +1,463 @@ | |||
| 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 | + | ||
| 35 | + | ||
| 36 | + | ||
| 37 | + | ||
| 38 | + | ||
| 39 | +namespace { | ||
| 40 | +constexpr const char *kScenarioEnvName = "GE_PYTHON_CUSTOM_OP_LOADER_UT_SCENARIO"; | ||
| 41 | +constexpr const char *kPythonPathEnvName = "PYTHONPATH"; | ||
| 42 | +bool g_fake_python_initialized = false; | ||
| 43 | +bool g_fake_python_threads_initialized = false; | ||
| 44 | +} // namespace | ||
| 45 | + | ||
| 46 | +extern "C" int Py_IsInitialized() { | ||
| 47 | + return g_fake_python_initialized ? 1 : 0; | ||
| 48 | +} | ||
| 49 | + | ||
| 50 | +extern "C" const char *Py_GetVersion() { | ||
| 51 | + return "3.11.0"; | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +extern "C" void Py_Initialize() { | ||
| 55 | + g_fake_python_initialized = true; | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +extern "C" void Py_Finalize() { | ||
| 59 | + g_fake_python_initialized = false; | ||
| 60 | + g_fake_python_threads_initialized = false; | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +extern "C" void PyEval_InitThreads() { | ||
| 64 | + g_fake_python_threads_initialized = true; | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | +extern "C" int PyEval_ThreadsInitialized() { | ||
| 68 | + return g_fake_python_threads_initialized ? 1 : 0; | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +extern "C" void *PyEval_SaveThread() { | ||
| 72 | + return reinterpret_cast<void *>(0x100); | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | +extern "C" void PyEval_RestoreThread(void *thread_state) { | ||
| 76 | + (void)thread_state; | ||
| 77 | +} | ||
| 78 | + | ||
| 79 | +extern "C" int PyGILState_Check() { | ||
| 80 | + return 1; | ||
| 81 | +} | ||
| 82 | + | ||
| 83 | +namespace ge { | ||
| 84 | +namespace custom_op { | ||
| 85 | +namespace { | ||
| 86 | +constexpr const char *kMultiProtoFailure = "multi_proto_failure"; | ||
| 87 | +constexpr const char *kAdapterFailure = "adapter_failure"; | ||
| 88 | +constexpr const char *kSuccess = "success"; | ||
| 89 | +constexpr const char *kCppProtoImpl = "cpp_proto_impl"; | ||
| 90 | + | ||
| 91 | +constexpr const char *kMultiProtoOp = "PythonLoaderMultiProtoRollbackUt"; | ||
| 92 | +constexpr const char *kAdapterOpA = "PythonLoaderAdapterRollbackAUt"; | ||
| 93 | +constexpr const char *kAdapterOpB = "PythonLoaderAdapterRollbackBUt"; | ||
| 94 | +constexpr const char *kSuccessOp = "PythonLoaderSuccessUt"; | ||
| 95 | +constexpr const char *kCppProtoOp = "PythonLoaderCppProtoOwnershipUt"; | ||
| 96 | + | ||
| 97 | +constexpr const char *kAdapterImplKeyA = "loader_ut:adapter_impl_a"; | ||
| 98 | +constexpr const char *kAdapterImplKeyB = "loader_ut:adapter_impl_b"; | ||
| 99 | +constexpr const char *kSuccessImplKey = "loader_ut:success_impl"; | ||
| 100 | +constexpr const char *kCppProtoImplKey = "loader_ut:cpp_proto_impl"; | ||
| 101 | + | ||
| 102 | +constexpr const char *kFakeBridgeArtifactName = "libge_python_custom_op_loader_ut_fake.so"; | ||
| 103 | +constexpr const char *kFakeNativeArtifactName = "_ge_custom_op_loader_ut_fake.so"; | ||
| 104 | + | ||
| 105 | +class PreexistingSuccessCustomOp final : public BaseCustomOp {}; | ||
| 106 | + | ||
| 107 | +class ScopedEnvVar { | ||
| 108 | + public: | ||
| 109 | + ScopedEnvVar(const char *name, const std::string &value) : name_(name) { | ||
| 110 | + const char *old_value = std::getenv(name); | ||
| 111 | + if (old_value != nullptr) { | ||
| 112 | + old_value_ = old_value; | ||
| 113 | + has_old_value_ = true; | ||
| 114 | + } | ||
| 115 | + (void)setenv(name_.c_str(), value.c_str(), 1); | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + ~ScopedEnvVar() { | ||
| 119 | + if (has_old_value_) { | ||
| 120 | + (void)setenv(name_.c_str(), old_value_.c_str(), 1); | ||
| 121 | + } else { | ||
| 122 | + (void)unsetenv(name_.c_str()); | ||
| 123 | + } | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + ScopedEnvVar(const ScopedEnvVar &) = delete; | ||
| 127 | + ScopedEnvVar &operator=(const ScopedEnvVar &) = delete; | ||
| 128 | + | ||
| 129 | + private: | ||
| 130 | + std::string name_; | ||
| 131 | + std::string old_value_; | ||
| 132 | + bool has_old_value_{false}; | ||
| 133 | +}; | ||
| 134 | + | ||
| 135 | +class ScopedArtifactTree { | ||
| 136 | + public: | ||
| 137 | + bool Prepare(const std::string &bridge_target, const std::string &python_tag) { | ||
| 138 | + if (bridge_target.empty() || python_tag.empty() || (access(bridge_target.c_str(), R_OK) != 0)) { | ||
| 139 | + return false; | ||
| 140 | + } | ||
| 141 | + char dir_template[] = "/tmp/ge_python_custom_op_loader_ut_XXXXXX"; | ||
| 142 | + const char *created_dir = mkdtemp(dir_template); | ||
| 143 | + if (created_dir == nullptr) { | ||
| 144 | + return false; | ||
| 145 | + } | ||
| 146 | + root_ = created_dir; | ||
| 147 | + const std::vector<std::string> dirs = { | ||
| 148 | + Path("site-packages"), | ||
| 149 | + Path("site-packages/ge"), | ||
| 150 | + Path("site-packages/ge/custom_op"), | ||
| 151 | + Path("site-packages/ge/custom_op/python_custom_op_artifacts"), | ||
| 152 | + ArtifactRoot(), | ||
| 153 | + }; | ||
| 154 | + for (const auto &dir : dirs) { | ||
| 155 | + if ((mkdir(dir.c_str(), 0700) != 0) && (errno != EEXIST)) { | ||
| 156 | + return false; | ||
| 157 | + } | ||
| 158 | + dirs_.emplace_back(dir); | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + const auto bridge_path = ArtifactRoot() + "/" + kFakeBridgeArtifactName; | ||
| 162 | + if (symlink(bridge_target.c_str(), bridge_path.c_str()) != 0) { | ||
| 163 | + return false; | ||
| 164 | + } | ||
| 165 | + files_.emplace_back(bridge_path); | ||
| 166 | + if (!WriteFile(ArtifactRoot() + "/" + kFakeNativeArtifactName, "fake native artifact")) { | ||
| 167 | + return false; | ||
| 168 | + } | ||
| 169 | + const std::string manifest = | ||
| 170 | + "{\n" | ||
| 171 | + " \"python_tag\": \"" + | ||
| 172 | + python_tag + | ||
| 173 | + "\",\n" | ||
| 174 | + " \"platform\": \"" + | ||
| 175 | + python_artifact::CurrentPlatformTag() + | ||
| 176 | + "\",\n" | ||
| 177 | + " \"bridge_abi\": 1,\n" | ||
| 178 | + " \"artifacts\": {\n" | ||
| 179 | + " \"bridge\": \"" + | ||
| 180 | + kFakeBridgeArtifactName + | ||
| 181 | + "\",\n" | ||
| 182 | + " \"native\": \"" + | ||
| 183 | + kFakeNativeArtifactName + | ||
| 184 | + "\"\n" | ||
| 185 | + " }\n" | ||
| 186 | + "}\n"; | ||
| 187 | + return WriteFile(ArtifactRoot() + "/manifest.json", manifest); | ||
| 188 | + } | ||
| 189 | + | ||
| 190 | + ~ScopedArtifactTree() { | ||
| 191 | + for (auto iter = files_.rbegin(); iter != files_.rend(); ++iter) { | ||
| 192 | + (void)remove(iter->c_str()); | ||
| 193 | + } | ||
| 194 | + for (auto iter = dirs_.rbegin(); iter != dirs_.rend(); ++iter) { | ||
| 195 | + (void)rmdir(iter->c_str()); | ||
| 196 | + } | ||
| 197 | + if (!root_.empty()) { | ||
| 198 | + (void)rmdir(root_.c_str()); | ||
| 199 | + } | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + std::string PythonPath() const { | ||
| 203 | + return Path("site-packages"); | ||
| 204 | + } | ||
| 205 | + | ||
| 206 | + ScopedArtifactTree(const ScopedArtifactTree &) = delete; | ||
| 207 | + ScopedArtifactTree &operator=(const ScopedArtifactTree &) = delete; | ||
| 208 | + ScopedArtifactTree() = default; | ||
| 209 | + | ||
| 210 | + private: | ||
| 211 | + std::string Path(const std::string &relative_path) const { | ||
| 212 | + return root_ + "/" + relative_path; | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + std::string ArtifactRoot() const { | ||
| 216 | + return Path("site-packages/ge/custom_op/python_custom_op_artifacts/fake"); | ||
| 217 | + } | ||
| 218 | + | ||
| 219 | + bool WriteFile(const std::string &path, const std::string &content) { | ||
| 220 | + std::ofstream output(path, std::ios::out | std::ios::trunc); | ||
| 221 | + if (!output.is_open()) { | ||
| 222 | + return false; | ||
| 223 | + } | ||
| 224 | + files_.emplace_back(path); | ||
| 225 | + output << content; | ||
| 226 | + output.close(); | ||
| 227 | + return output.good(); | ||
| 228 | + } | ||
| 229 | + | ||
| 230 | + std::string root_; | ||
| 231 | + std::vector<std::string> files_; | ||
| 232 | + std::vector<std::string> dirs_; | ||
| 233 | +}; | ||
| 234 | + | ||
| 235 | +PythonCustomOpAdapterDescriptor MakeAdapterDescriptor(const char *op_type, const char *impl_key) { | ||
| 236 | + PythonCustomOpAdapterDescriptor desc; | ||
| 237 | + desc.op_type = op_type; | ||
| 238 | + desc.impl_descriptor_key = impl_key; | ||
| 239 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); | ||
| 240 | + return desc; | ||
| 241 | +} | ||
| 242 | + | ||
| 243 | +class ScopedRuntimeLease { | ||
| 244 | + public: | ||
| 245 | + explicit ScopedRuntimeLease(PythonCustomOpAdapterDescriptor desc) : desc_(std::move(desc)) {} | ||
| 246 | + | ||
| 247 | + bool Acquire() { | ||
| 248 | + active_ = PythonCustomOpImplRuntimeRegistry::Acquire(desc_, callbacks_); | ||
| 249 | + return active_; | ||
| 250 | + } | ||
| 251 | + | ||
| 252 | + void Release() { | ||
| 253 | + if (active_) { | ||
| 254 | + PythonCustomOpImplRuntimeRegistry::Release(desc_); | ||
| 255 | + active_ = false; | ||
| 256 | + } | ||
| 257 | + } | ||
| 258 | + | ||
| 259 | + ~ScopedRuntimeLease() { | ||
| 260 | + Release(); | ||
| 261 | + } | ||
| 262 | + | ||
| 263 | + private: | ||
| 264 | + PythonCustomOpAdapterDescriptor desc_; | ||
| 265 | + PythonCustomOpAdapterCallbacks callbacks_; | ||
| 266 | + bool active_{false}; | ||
| 267 | +}; | ||
| 268 | + | ||
| 269 | +uint32_t ReadFakeBridgeCounter(const char *symbol) { | ||
| 270 | + using GetCounterFn = uint32_t (*)(); | ||
| 271 | + auto *get_counter = reinterpret_cast<GetCounterFn>(dlsym(RTLD_DEFAULT, symbol)); | ||
| 272 | + if (get_counter == nullptr) { | ||
| 273 | + ADD_FAILURE() << "fake bridge counter symbol is missing: " << symbol; | ||
| 274 | + return 0U; | ||
| 275 | + } | ||
| 276 | + return get_counter(); | ||
| 277 | +} | ||
| 278 | + | ||
| 279 | +uint32_t GetRegisterCount() { | ||
| 280 | + return ReadFakeBridgeCounter("GePythonCustomOpLoaderUtGetRegisterCount"); | ||
| 281 | +} | ||
| 282 | + | ||
| 283 | +uint32_t GetResetCount() { | ||
| 284 | + return ReadFakeBridgeCounter("GePythonCustomOpLoaderUtGetResetCount"); | ||
| 285 | +} | ||
| 286 | + | ||
| 287 | +void ResetFakeBridgeCountersIfLoaded() { | ||
| 288 | + using ResetCountersFn = void (*)(); | ||
| 289 | + auto *reset_counters = | ||
| 290 | + reinterpret_cast<ResetCountersFn>(dlsym(RTLD_DEFAULT, "GePythonCustomOpLoaderUtResetCounters")); | ||
| 291 | + if (reset_counters != nullptr) { | ||
| 292 | + reset_counters(); | ||
| 293 | + } | ||
| 294 | +} | ||
| 295 | + | ||
| 296 | +std::vector<AscendString> AllAdapterOpTypes() { | ||
| 297 | + return {AscendString(kAdapterOpA), AscendString(kAdapterOpB), AscendString(kSuccessOp), AscendString(kCppProtoOp)}; | ||
| 298 | +} | ||
| 299 | + | ||
| 300 | +std::vector<std::string> AllProtoOpTypes() { | ||
| 301 | + return {kMultiProtoOp, kAdapterOpA, kAdapterOpB, kSuccessOp, kCppProtoOp}; | ||
| 302 | +} | ||
| 303 | + | ||
| 304 | +void ClearKnownRegistrationState() { | ||
| 305 | + CustomOpFactory::RemoveCustomOps(AllAdapterOpTypes()); | ||
| 306 | + ClearPythonCustomOpRuntimeRegistry(); | ||
| 307 | + OperatorFactoryImpl::RemoveCustomOpCreators(AllProtoOpTypes()); | ||
| 308 | +} | ||
| 309 | + | ||
| 310 | +class PythonCustomOpBridgeLoaderTest : public testing::Test { | ||
| 311 | + protected: | ||
| 312 | + void SetUp() override { | ||
| 313 | + ShutdownPythonCustomOpsForProcess(); | ||
| 314 | + ClearKnownRegistrationState(); | ||
| 315 | + ResetFakeBridgeCountersIfLoaded(); | ||
| 316 | + Py_Initialize(); | ||
| 317 | + const auto runtime_key = python_artifact::ResolveLoadedPythonRuntimeKey(); | ||
| 318 | + ASSERT_TRUE(runtime_key.has_python_symbols); | ||
| 319 | + ASSERT_TRUE(runtime_key.is_initialized); | ||
| 320 | + ASSERT_FALSE(runtime_key.python_tag.empty()); | ||
| 321 | + ASSERT_TRUE(artifact_tree_.Prepare(PYTHON_CUSTOM_OP_LOADER_UT_FAKE_BRIDGE_PATH, runtime_key.python_tag)); | ||
| 322 | + python_path_env_ = std::make_unique<ScopedEnvVar>(kPythonPathEnvName, artifact_tree_.PythonPath()); | ||
| 323 | + } | ||
| 324 | + | ||
| 325 | + void TearDown() override { | ||
| 326 | + ShutdownPythonCustomOpsForProcess(); | ||
| 327 | + ClearKnownRegistrationState(); | ||
| 328 | + scenario_env_.reset(); | ||
| 329 | + python_path_env_.reset(); | ||
| 330 | + Py_Finalize(); | ||
| 331 | + } | ||
| 332 | + | ||
| 333 | + void SetScenario(const char *scenario) { | ||
| 334 | + scenario_env_ = std::make_unique<ScopedEnvVar>(kScenarioEnvName, scenario); | ||
| 335 | + } | ||
| 336 | + | ||
| 337 | + private: | ||
| 338 | + ScopedArtifactTree artifact_tree_; | ||
| 339 | + std::unique_ptr<ScopedEnvVar> python_path_env_; | ||
| 340 | + std::unique_ptr<ScopedEnvVar> scenario_env_; | ||
| 341 | +}; | ||
| 342 | + | ||
| 343 | +TEST_F(PythonCustomOpBridgeLoaderTest, keeps_partial_proto_registration_until_unload_after_proto_failure) { | ||
| 344 | + SetScenario(kMultiProtoFailure); | ||
| 345 | + | ||
| 346 | + EXPECT_EQ(LoadPythonCustomOps(), FAILED); | ||
| 347 | + EXPECT_TRUE(OperatorFactory::IsExistOp(kMultiProtoOp)); | ||
| 348 | + UnloadPythonCustomOps(); | ||
| 349 | + EXPECT_FALSE(OperatorFactory::IsExistOp(kMultiProtoOp)); | ||
| 350 | + EXPECT_EQ(GetRegisterCount(), 1U); | ||
| 351 | + EXPECT_EQ(GetResetCount(), 1U); | ||
| 352 | +} | ||
| 353 | + | ||
| 354 | +TEST_F(PythonCustomOpBridgeLoaderTest, rolls_back_proto_adapter_and_runtime_entry_after_adapter_failure) { | ||
| 355 | + SetScenario(kAdapterFailure); | ||
| 356 | + | ||
| 357 | + EXPECT_EQ(LoadPythonCustomOps(), FAILED); | ||
| 358 | + EXPECT_TRUE(OperatorFactory::IsExistOp(kAdapterOpA)); | ||
| 359 | + EXPECT_TRUE(OperatorFactory::IsExistOp(kAdapterOpB)); | ||
| 360 | + EXPECT_TRUE(CustomOpFactory::IsExistOp(AscendString(kAdapterOpA))); | ||
| 361 | + UnloadPythonCustomOps(); | ||
| 362 | + EXPECT_FALSE(OperatorFactory::IsExistOp(kAdapterOpA)); | ||
| 363 | + EXPECT_FALSE(OperatorFactory::IsExistOp(kAdapterOpB)); | ||
| 364 | + EXPECT_FALSE(CustomOpFactory::IsExistOp(AscendString(kAdapterOpA))); | ||
| 365 | + | ||
| 366 | + const auto desc = MakeAdapterDescriptor(kAdapterOpA, kAdapterImplKeyA); | ||
| 367 | + PythonCustomOpAdapterCallbacks callbacks; | ||
| 368 | + const bool acquired = PythonCustomOpImplRuntimeRegistry::Acquire(desc, callbacks); | ||
| 369 | + if (acquired) { | ||
| 370 | + PythonCustomOpImplRuntimeRegistry::Release(desc); | ||
| 371 | + } | ||
| 372 | + EXPECT_FALSE(acquired); | ||
| 373 | + EXPECT_EQ(GetRegisterCount(), 1U); | ||
| 374 | + EXPECT_EQ(GetResetCount(), 1U); | ||
| 375 | +} | ||
| 376 | + | ||
| 377 | +TEST_F(PythonCustomOpBridgeLoaderTest, adapter_conflict_preserves_preexisting_cpp_custom_op_creator) { | ||
| 378 | + ASSERT_EQ(CustomOpFactory::RegisterCustomOpCreator( | ||
| 379 | + AscendString(kSuccessOp), | ||
| 380 | + []() -> std::unique_ptr<BaseCustomOp> { return std::make_unique<PreexistingSuccessCustomOp>(); }), | ||
| 381 | + GRAPH_SUCCESS); | ||
| 382 | + SetScenario(kSuccess); | ||
| 383 | + | ||
| 384 | + EXPECT_EQ(LoadPythonCustomOps(), FAILED); | ||
| 385 | + UnloadPythonCustomOps(); | ||
| 386 | + EXPECT_FALSE(OperatorFactory::IsExistOp(kSuccessOp)); | ||
| 387 | + EXPECT_TRUE(CustomOpFactory::IsExistOp(AscendString(kSuccessOp))); | ||
| 388 | + EXPECT_NE(dynamic_cast<PreexistingSuccessCustomOp *>(CustomOpFactory::CreateOrGetCustomOp(AscendString(kSuccessOp))), | ||
| 389 | + nullptr); | ||
| 390 | + | ||
| 391 | + PythonCustomOpAdapterCallbacks callbacks; | ||
| 392 | + const auto desc = MakeAdapterDescriptor(kSuccessOp, kSuccessImplKey); | ||
| 393 | + EXPECT_FALSE(PythonCustomOpImplRuntimeRegistry::Acquire(desc, callbacks)); | ||
| 394 | + EXPECT_EQ(GetRegisterCount(), 1U); | ||
| 395 | + EXPECT_EQ(GetResetCount(), 1U); | ||
| 396 | +} | ||
| 397 | + | ||
| 398 | +TEST_F(PythonCustomOpBridgeLoaderTest, direct_load_registers_each_call_and_unload_allows_reload) { | ||
| 399 | + SetScenario(kSuccess); | ||
| 400 | + | ||
| 401 | + ASSERT_EQ(LoadPythonCustomOps(), SUCCESS); | ||
| 402 | + EXPECT_EQ(LoadPythonCustomOps(), SUCCESS); | ||
| 403 | + EXPECT_EQ(GetRegisterCount(), 2U); | ||
| 404 | + EXPECT_TRUE(OperatorFactory::IsExistOp(kSuccessOp)); | ||
| 405 | + EXPECT_TRUE(CustomOpFactory::IsExistOp(AscendString(kSuccessOp))); | ||
| 406 | + EXPECT_NE(CustomOpFactory::CreateOrGetCustomOp(AscendString(kSuccessOp)), nullptr); | ||
| 407 | + | ||
| 408 | + UnloadPythonCustomOps(); | ||
| 409 | + EXPECT_FALSE(OperatorFactory::IsExistOp(kSuccessOp)); | ||
| 410 | + EXPECT_FALSE(CustomOpFactory::IsExistOp(AscendString(kSuccessOp))); | ||
| 411 | + EXPECT_EQ(GetResetCount(), 1U); | ||
| 412 | + | ||
| 413 | + ASSERT_EQ(LoadPythonCustomOps(), SUCCESS); | ||
| 414 | + EXPECT_EQ(GetRegisterCount(), 3U); | ||
| 415 | + EXPECT_TRUE(OperatorFactory::IsExistOp(kSuccessOp)); | ||
| 416 | + UnloadPythonCustomOps(); | ||
| 417 | + EXPECT_FALSE(OperatorFactory::IsExistOp(kSuccessOp)); | ||
| 418 | + EXPECT_EQ(GetResetCount(), 2U); | ||
| 419 | + | ||
| 420 | + PythonCustomOpAdapterCallbacks callbacks; | ||
| 421 | + const auto desc = MakeAdapterDescriptor(kSuccessOp, kSuccessImplKey); | ||
| 422 | + EXPECT_FALSE(PythonCustomOpImplRuntimeRegistry::Acquire(desc, callbacks)); | ||
| 423 | +} | ||
| 424 | + | ||
| 425 | +TEST_F(PythonCustomOpBridgeLoaderTest, unload_clears_runtime_registry_with_active_runtime_lease) { | ||
| 426 | + SetScenario(kSuccess); | ||
| 427 | + ASSERT_EQ(LoadPythonCustomOps(), SUCCESS); | ||
| 428 | + | ||
| 429 | + ScopedRuntimeLease lease(MakeAdapterDescriptor(kSuccessOp, kSuccessImplKey)); | ||
| 430 | + ASSERT_TRUE(lease.Acquire()); | ||
| 431 | + UnloadPythonCustomOps(); | ||
| 432 | + EXPECT_FALSE(CustomOpFactory::IsExistOp(AscendString(kSuccessOp))); | ||
| 433 | + EXPECT_FALSE(OperatorFactory::IsExistOp(kSuccessOp)); | ||
| 434 | + EXPECT_EQ(GetResetCount(), 1U); | ||
| 435 | + lease.Release(); | ||
| 436 | + | ||
| 437 | + PythonCustomOpAdapterCallbacks callbacks; | ||
| 438 | + const auto desc = MakeAdapterDescriptor(kSuccessOp, kSuccessImplKey); | ||
| 439 | + EXPECT_FALSE(PythonCustomOpImplRuntimeRegistry::Acquire(desc, callbacks)); | ||
| 440 | +} | ||
| 441 | + | ||
| 442 | +TEST_F(PythonCustomOpBridgeLoaderTest, unload_preserves_preexisting_cpp_proto_used_by_python_impl) { | ||
| 443 | + const auto creator = [](const AscendString &name) -> Operator { return Operator(name, AscendString(kCppProtoOp)); }; | ||
| 444 | + ASSERT_EQ(OperatorFactoryImpl::RegisterOperatorCreator(kCppProtoOp, creator), GRAPH_SUCCESS); | ||
| 445 | + SetScenario(kCppProtoImpl); | ||
| 446 | + | ||
| 447 | + ASSERT_EQ(LoadPythonCustomOps(), SUCCESS); | ||
| 448 | + EXPECT_TRUE(OperatorFactory::IsExistOp(kCppProtoOp)); | ||
| 449 | + EXPECT_TRUE(CustomOpFactory::IsExistOp(AscendString(kCppProtoOp))); | ||
| 450 | + | ||
| 451 | + UnloadPythonCustomOps(); | ||
| 452 | + EXPECT_TRUE(OperatorFactory::IsExistOp(kCppProtoOp)); | ||
| 453 | + EXPECT_FALSE(OperatorFactory::CreateOperator("cpp_proto_instance", kCppProtoOp).IsEmpty()); | ||
| 454 | + EXPECT_FALSE(CustomOpFactory::IsExistOp(AscendString(kCppProtoOp))); | ||
| 455 | + EXPECT_EQ(GetResetCount(), 1U); | ||
| 456 | + | ||
| 457 | + PythonCustomOpAdapterCallbacks callbacks; | ||
| 458 | + const auto desc = MakeAdapterDescriptor(kCppProtoOp, kCppProtoImplKey); | ||
| 459 | + EXPECT_FALSE(PythonCustomOpImplRuntimeRegistry::Acquire(desc, callbacks)); | ||
| 460 | +} | ||
| 461 | +} // namespace | ||
| 462 | +} // namespace custom_op | ||
| 463 | +} // namespace ge | ||
| @@ -26,8 +26,10 @@ namespace bridge_loader = ::ge::python_bridge_loader; | |||
| 26 | 26 | ||
| 27 | struct MockPythonCustomOpHolder {}; | 27 | struct MockPythonCustomOpHolder {}; |
| 28 | 28 | ||
| 29 | -void *CreateMockPythonCustomOpHolder(const PythonCustomOpDescriptor *desc) { | 29 | +void *CreateMockPythonCustomOpHolder(const PythonCustomOpAdapterDescriptorView *desc) { |
| 30 | - return (desc == nullptr) ? nullptr : new (std::nothrow) MockPythonCustomOpHolder(); | 30 | + return ((desc == nullptr) || (desc->impl_descriptor_key.data == nullptr)) ? nullptr |
| 31 | + : new (std::nothrow) | ||
| 32 | + MockPythonCustomOpHolder(); | ||
| 31 | } | 33 | } |
| 32 | 34 | ||
| 33 | void DestroyMockPythonCustomOpHolder(void *holder) { | 35 | void DestroyMockPythonCustomOpHolder(void *holder) { |
| @@ -145,60 +147,60 @@ bridge_loader::BridgeLoadStatus LoadMockPythonCustomOpBridge( | |||
| 145 | } // namespace | 147 | } // namespace |
| 146 | 148 | ||
| 147 | TEST(PythonCustomOpAdapter, forwards_execute_without_ir_meta_pod) { | 149 | TEST(PythonCustomOpAdapter, forwards_execute_without_ir_meta_pod) { |
| 148 | - PythonCustomOpDescriptor desc; | 150 | + PythonCustomOpAdapterDescriptor desc; |
| 149 | - desc.descriptor_key = "python_adapter_without_ir_meta"; | 151 | + desc.impl_descriptor_key = "python_adapter_without_ir_meta"; |
| 150 | desc.op_type = "PythonCustomOpAdapterUt"; | 152 | desc.op_type = "PythonCustomOpAdapterUt"; |
| 151 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); | 153 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); |
| 152 | 154 | ||
| 153 | - PythonCustomOpCallbacks callbacks; | 155 | + PythonCustomOpAdapterCallbacks callbacks; |
| 154 | - callbacks.create = CreateMockPythonCustomOpHolder; | 156 | + callbacks.create_impl_holder = CreateMockPythonCustomOpHolder; |
| 155 | - callbacks.destroy = DestroyMockPythonCustomOpHolder; | 157 | + callbacks.destroy_impl_holder = DestroyMockPythonCustomOpHolder; |
| 156 | callbacks.execute = ExecuteMockPythonCustomOp; | 158 | callbacks.execute = ExecuteMockPythonCustomOp; |
| 157 | 159 | ||
| 158 | - ASSERT_TRUE(PythonCustomOpRuntimeRegistry::Register(desc, callbacks)); | 160 | + ASSERT_TRUE(PythonCustomOpImplRuntimeRegistry::Register(desc, callbacks)); |
| 159 | { | 161 | { |
| 160 | PythonCustomOpAdapter adapter(desc); | 162 | PythonCustomOpAdapter adapter(desc); |
| 161 | ASSERT_TRUE(adapter.IsValid()); | 163 | ASSERT_TRUE(adapter.IsValid()); |
| 162 | EXPECT_EQ(adapter.Execute(nullptr), GRAPH_SUCCESS); | 164 | EXPECT_EQ(adapter.Execute(nullptr), GRAPH_SUCCESS); |
| 163 | } | 165 | } |
| 164 | - EXPECT_TRUE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | 166 | + EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key)); |
| 165 | } | 167 | } |
| 166 | 168 | ||
| 167 | TEST(PythonCustomOpAdapter, keeps_legacy_execute_without_registered_ir) { | 169 | TEST(PythonCustomOpAdapter, keeps_legacy_execute_without_registered_ir) { |
| 168 | - PythonCustomOpDescriptor desc; | 170 | + PythonCustomOpAdapterDescriptor desc; |
| 169 | - desc.descriptor_key = "python_adapter_legacy_without_ir"; | 171 | + desc.impl_descriptor_key = "python_adapter_legacy_without_ir"; |
| 170 | desc.op_type = "PythonCustomOpLegacyWithoutIrUt"; | 172 | desc.op_type = "PythonCustomOpLegacyWithoutIrUt"; |
| 171 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); | 173 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); |
| 172 | 174 | ||
| 173 | - PythonCustomOpCallbacks callbacks; | 175 | + PythonCustomOpAdapterCallbacks callbacks; |
| 174 | - callbacks.create = CreateMockPythonCustomOpHolder; | 176 | + callbacks.create_impl_holder = CreateMockPythonCustomOpHolder; |
| 175 | - callbacks.destroy = DestroyMockPythonCustomOpHolder; | 177 | + callbacks.destroy_impl_holder = DestroyMockPythonCustomOpHolder; |
| 176 | callbacks.execute = ExecuteMockPythonCustomOp; | 178 | callbacks.execute = ExecuteMockPythonCustomOp; |
| 177 | 179 | ||
| 178 | - ASSERT_TRUE(PythonCustomOpRuntimeRegistry::Register(desc, callbacks)); | 180 | + ASSERT_TRUE(PythonCustomOpImplRuntimeRegistry::Register(desc, callbacks)); |
| 179 | { | 181 | { |
| 180 | PythonCustomOpAdapter adapter(desc); | 182 | PythonCustomOpAdapter adapter(desc); |
| 181 | ASSERT_TRUE(adapter.IsValid()); | 183 | ASSERT_TRUE(adapter.IsValid()); |
| 182 | EXPECT_EQ(adapter.Execute(nullptr), GRAPH_SUCCESS); | 184 | EXPECT_EQ(adapter.Execute(nullptr), GRAPH_SUCCESS); |
| 183 | } | 185 | } |
| 184 | - EXPECT_TRUE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | 186 | + EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key)); |
| 185 | } | 187 | } |
| 186 | 188 | ||
| 187 | TEST(PythonCustomOpAdapter, validates_annotated_args_callback_by_capability) { | 189 | TEST(PythonCustomOpAdapter, validates_annotated_args_callback_by_capability) { |
| 188 | - PythonCustomOpDescriptor desc; | 190 | + PythonCustomOpAdapterDescriptor desc; |
| 189 | - desc.descriptor_key = "python_adapter_annotated_args_callback"; | 191 | + desc.impl_descriptor_key = "python_adapter_annotated_args_callback"; |
| 190 | desc.op_type = "PythonCustomOpIrMetaUt"; | 192 | desc.op_type = "PythonCustomOpIrMetaUt"; |
| 191 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kAnnotatedArgs); | 193 | AddCustomOpCapability(desc.capabilities, CustomOpCapability::kAnnotatedArgs); |
| 192 | 194 | ||
| 193 | - PythonCustomOpCallbacks callbacks; | 195 | + PythonCustomOpAdapterCallbacks callbacks; |
| 194 | - callbacks.create = CreateMockPythonCustomOpHolder; | 196 | + callbacks.create_impl_holder = CreateMockPythonCustomOpHolder; |
| 195 | - callbacks.destroy = DestroyMockPythonCustomOpHolder; | 197 | + callbacks.destroy_impl_holder = DestroyMockPythonCustomOpHolder; |
| 196 | EXPECT_FALSE(callbacks.IsValid(desc.capabilities)); | 198 | EXPECT_FALSE(callbacks.IsValid(desc.capabilities)); |
| 197 | 199 | ||
| 198 | callbacks.declare_launch_args = DeclareMockPythonCustomOp; | 200 | callbacks.declare_launch_args = DeclareMockPythonCustomOp; |
| 199 | EXPECT_TRUE(callbacks.IsValid(desc.capabilities)); | 201 | EXPECT_TRUE(callbacks.IsValid(desc.capabilities)); |
| 200 | - EXPECT_TRUE(PythonCustomOpRuntimeRegistry::Register(desc, callbacks)); | 202 | + EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Register(desc, callbacks)); |
| 201 | - EXPECT_TRUE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | 203 | + EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key)); |
| 202 | } | 204 | } |
| 203 | 205 | ||
| 204 | TEST(PythonCustomOpBridgeAbi, rejects_mismatched_abi_before_registration_and_accepts_current) { | 206 | TEST(PythonCustomOpBridgeAbi, rejects_mismatched_abi_before_registration_and_accepts_current) { |
| @@ -218,5 +220,6 @@ TEST(PythonCustomOpBridgeAbi, rejects_mismatched_abi_before_registration_and_acc | |||
| 218 | EXPECT_EQ(loaded_bridge.api->register_custom_ops(nullptr), SUCCESS); | 220 | EXPECT_EQ(loaded_bridge.api->register_custom_ops(nullptr), SUCCESS); |
| 219 | EXPECT_EQ(g_mock_python_custom_op_bridge_load_state.register_count, 1U); | 221 | EXPECT_EQ(g_mock_python_custom_op_bridge_load_state.register_count, 1U); |
| 220 | } | 222 | } |
| 223 | + | ||
| 221 | } // namespace custom_op | 224 | } // namespace custom_op |
| 222 | } // namespace ge | 225 | } // namespace ge |
| @@ -0,0 +1,480 @@ | |||
| 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 | +namespace ge { | ||
| 26 | +namespace custom_op { | ||
| 27 | +namespace { | ||
| 28 | +class PythonProtoCustomOpCollision : public BaseCustomOp {}; | ||
| 29 | + | ||
| 30 | +PythonCustomOpStringView StringView(const char *value) { | ||
| 31 | + return PythonCustomOpStringView{value, strlen(value)}; | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +PythonCustomOpProtoDescriptorView MakeProtoView(const char *descriptor_key, const char *op_type, | ||
| 35 | + const PythonCustomOpProtoInputView *inputs, const size_t input_count, | ||
| 36 | + const PythonCustomOpProtoAttrView *attrs, const size_t attr_count, | ||
| 37 | + const PythonCustomOpProtoOutputView *outputs, | ||
| 38 | + const size_t output_count) { | ||
| 39 | + return PythonCustomOpProtoDescriptorView{ | ||
| 40 | + StringView(descriptor_key), StringView(op_type), inputs, input_count, attrs, attr_count, outputs, output_count, | ||
| 41 | + }; | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +PythonCustomOpProtoAttrView RequiredAttr(const char *name, const uint32_t kind) { | ||
| 45 | + PythonCustomOpAttrDefaultView default_value{}; | ||
| 46 | + return PythonCustomOpProtoAttrView{StringView(name), kind, 1U, default_value}; | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +PythonCustomOpProtoAttrView OptionalAttr(const char *name, const uint32_t kind, | ||
| 50 | + const PythonCustomOpAttrDefaultView &default_value) { | ||
| 51 | + return PythonCustomOpProtoAttrView{StringView(name), kind, 0U, default_value}; | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +struct AttrViewStorage { | ||
| 55 | + std::vector<int64_t> list_int{1, 2}; | ||
| 56 | + std::vector<double> list_float{1.5, 2.5}; | ||
| 57 | + std::vector<uint8_t> list_bool{1U, 0U}; | ||
| 58 | + std::vector<PythonCustomOpStringView> list_string{StringView("a"), StringView("b")}; | ||
| 59 | + std::vector<int32_t> list_data_type{static_cast<int32_t>(DT_FLOAT), static_cast<int32_t>(DT_INT32)}; | ||
| 60 | + std::vector<std::vector<int64_t>> list_list_int_storage{{1, 2}, {3}}; | ||
| 61 | + std::vector<PythonCustomOpInt64ArrayView> list_list_int; | ||
| 62 | + std::vector<PythonCustomOpProtoAttrView> attrs; | ||
| 63 | + | ||
| 64 | + AttrViewStorage() { | ||
| 65 | + for (const auto &row : list_list_int_storage) { | ||
| 66 | + list_list_int.emplace_back(PythonCustomOpInt64ArrayView{row.data(), row.size()}); | ||
| 67 | + } | ||
| 68 | + AddScalarAttrs(); | ||
| 69 | + AddListAttrs(); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + void AddScalarAttrs() { | ||
| 73 | + attrs.emplace_back(RequiredAttr("tensor_attr", kPythonAttrTensor)); | ||
| 74 | + PythonCustomOpAttrDefaultView value{}; | ||
| 75 | + value.has_value = 1U; | ||
| 76 | + value.int_value = 9; | ||
| 77 | + attrs.emplace_back(OptionalAttr("int_attr", kPythonAttrInt, value)); | ||
| 78 | + value = {}; | ||
| 79 | + value.has_value = 1U; | ||
| 80 | + value.float_value = 1.25; | ||
| 81 | + attrs.emplace_back(OptionalAttr("float_attr", kPythonAttrFloat, value)); | ||
| 82 | + value = {}; | ||
| 83 | + value.has_value = 1U; | ||
| 84 | + value.bool_value = 1U; | ||
| 85 | + attrs.emplace_back(OptionalAttr("bool_attr", kPythonAttrBool, value)); | ||
| 86 | + value = {}; | ||
| 87 | + value.has_value = 1U; | ||
| 88 | + value.string_value = StringView("value"); | ||
| 89 | + attrs.emplace_back(OptionalAttr("string_attr", kPythonAttrString, value)); | ||
| 90 | + value = {}; | ||
| 91 | + value.has_value = 1U; | ||
| 92 | + value.data_type_value = static_cast<int32_t>(DT_FLOAT16); | ||
| 93 | + attrs.emplace_back(OptionalAttr("data_type_attr", kPythonAttrDataType, value)); | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + void AddListAttrs() { | ||
| 97 | + AddListAttr("list_int_attr", kPythonAttrListInt, list_int.data()); | ||
| 98 | + AddListAttr("list_float_attr", kPythonAttrListFloat, list_float.data()); | ||
| 99 | + AddListAttr("list_bool_attr", kPythonAttrListBool, list_bool.data()); | ||
| 100 | + AddListAttr("list_string_attr", kPythonAttrListString, list_string.data()); | ||
| 101 | + AddListAttr("list_data_type_attr", kPythonAttrListDataType, list_data_type.data()); | ||
| 102 | + AddListAttr("list_list_int_attr", kPythonAttrListListInt, list_list_int.data()); | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + template <typename T> | ||
| 106 | + void AddListAttr(const char *name, const uint32_t kind, const T *data) { | ||
| 107 | + PythonCustomOpAttrDefaultView value{}; | ||
| 108 | + value.has_value = 1U; | ||
| 109 | + value.count = 2U; | ||
| 110 | + SetListData(value, data); | ||
| 111 | + attrs.emplace_back(OptionalAttr(name, kind, value)); | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + static void SetListData(PythonCustomOpAttrDefaultView &value, const int64_t *data) { | ||
| 115 | + value.list_int_values = data; | ||
| 116 | + } | ||
| 117 | + static void SetListData(PythonCustomOpAttrDefaultView &value, const double *data) { | ||
| 118 | + value.list_float_values = data; | ||
| 119 | + } | ||
| 120 | + static void SetListData(PythonCustomOpAttrDefaultView &value, const uint8_t *data) { | ||
| 121 | + value.list_bool_values = data; | ||
| 122 | + } | ||
| 123 | + static void SetListData(PythonCustomOpAttrDefaultView &value, const PythonCustomOpStringView *data) { | ||
| 124 | + value.list_string_values = data; | ||
| 125 | + } | ||
| 126 | + static void SetListData(PythonCustomOpAttrDefaultView &value, const int32_t *data) { | ||
| 127 | + value.list_data_type_values = data; | ||
| 128 | + } | ||
| 129 | + static void SetListData(PythonCustomOpAttrDefaultView &value, const PythonCustomOpInt64ArrayView *data) { | ||
| 130 | + value.list_list_int_values = data; | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + void MutateSource() { | ||
| 134 | + attrs[1].default_value.int_value = 99; | ||
| 135 | + attrs[2].default_value.float_value = 9.0; | ||
| 136 | + attrs[3].default_value.bool_value = 0U; | ||
| 137 | + attrs[4].default_value.string_value = StringView("changed"); | ||
| 138 | + attrs[5].default_value.data_type_value = static_cast<int32_t>(DT_INT64); | ||
| 139 | + list_int[0] = 99; | ||
| 140 | + list_float[0] = 9.0; | ||
| 141 | + list_bool[0] = 0U; | ||
| 142 | + list_string[0] = StringView("changed"); | ||
| 143 | + list_data_type[0] = static_cast<int32_t>(DT_INT64); | ||
| 144 | + list_list_int_storage[0][0] = 99; | ||
| 145 | + } | ||
| 146 | +}; | ||
| 147 | + | ||
| 148 | +void ExpectAllIrAttrTypes(const Operator &op) { | ||
| 149 | + std::map<AscendString, AscendString> ir_attr_types; | ||
| 150 | + ASSERT_EQ(op.GetAllIrAttrNamesAndTypes(ir_attr_types), GRAPH_SUCCESS); | ||
| 151 | + std::map<std::string, std::string> actual_ir_attr_types; | ||
| 152 | + for (const auto &item : ir_attr_types) { | ||
| 153 | + ASSERT_NE(item.first.GetString(), nullptr); | ||
| 154 | + ASSERT_NE(item.second.GetString(), nullptr); | ||
| 155 | + actual_ir_attr_types.emplace(item.first.GetString(), item.second.GetString()); | ||
| 156 | + } | ||
| 157 | + EXPECT_EQ(actual_ir_attr_types, (std::map<std::string, std::string>{{"tensor_attr", "VT_TENSOR"}, | ||
| 158 | + {"int_attr", "VT_INT"}, | ||
| 159 | + {"float_attr", "VT_FLOAT"}, | ||
| 160 | + {"bool_attr", "VT_BOOL"}, | ||
| 161 | + {"string_attr", "VT_STRING"}, | ||
| 162 | + {"data_type_attr", "VT_DATA_TYPE"}, | ||
| 163 | + {"list_int_attr", "VT_LIST_INT"}, | ||
| 164 | + {"list_float_attr", "VT_LIST_FLOAT"}, | ||
| 165 | + {"list_bool_attr", "VT_LIST_BOOL"}, | ||
| 166 | + {"list_string_attr", "VT_LIST_STRING"}, | ||
| 167 | + {"list_data_type_attr", "VT_LIST_DATA_TYPE"}, | ||
| 168 | + {"list_list_int_attr", "VT_LIST_LIST_INT"}})); | ||
| 169 | +} | ||
| 170 | + | ||
| 171 | +void ExpectScalarDefaultAttrs(const Operator &op) { | ||
| 172 | + AttrValue tensor_default; | ||
| 173 | + EXPECT_NE(op.GetAttr("tensor_attr", tensor_default), GRAPH_SUCCESS); | ||
| 174 | + int64_t int_value = 0; | ||
| 175 | + EXPECT_EQ(op.GetAttr("int_attr", int_value), GRAPH_SUCCESS); | ||
| 176 | + EXPECT_EQ(int_value, 9); | ||
| 177 | + float32_t float_value = 0.0F; | ||
| 178 | + EXPECT_EQ(op.GetAttr("float_attr", float_value), GRAPH_SUCCESS); | ||
| 179 | + EXPECT_FLOAT_EQ(float_value, 1.25F); | ||
| 180 | + bool bool_value = false; | ||
| 181 | + EXPECT_EQ(op.GetAttr("bool_attr", bool_value), GRAPH_SUCCESS); | ||
| 182 | + EXPECT_TRUE(bool_value); | ||
| 183 | + std::string string_value; | ||
| 184 | + EXPECT_EQ(op.GetAttr("string_attr", string_value), GRAPH_SUCCESS); | ||
| 185 | + EXPECT_EQ(string_value, "value"); | ||
| 186 | + DataType data_type_value = DT_UNDEFINED; | ||
| 187 | + EXPECT_EQ(op.GetAttr("data_type_attr", data_type_value), GRAPH_SUCCESS); | ||
| 188 | + EXPECT_EQ(data_type_value, DT_FLOAT16); | ||
| 189 | +} | ||
| 190 | + | ||
| 191 | +void ExpectListDefaultAttrs(const Operator &op) { | ||
| 192 | + std::vector<int64_t> list_int_value; | ||
| 193 | + EXPECT_EQ(op.GetAttr("list_int_attr", list_int_value), GRAPH_SUCCESS); | ||
| 194 | + EXPECT_EQ(list_int_value, std::vector<int64_t>({1, 2})); | ||
| 195 | + std::vector<float32_t> list_float_value; | ||
| 196 | + EXPECT_EQ(op.GetAttr("list_float_attr", list_float_value), GRAPH_SUCCESS); | ||
| 197 | + EXPECT_EQ(list_float_value, std::vector<float32_t>({1.5F, 2.5F})); | ||
| 198 | + std::vector<bool> list_bool_value; | ||
| 199 | + EXPECT_EQ(op.GetAttr("list_bool_attr", list_bool_value), GRAPH_SUCCESS); | ||
| 200 | + EXPECT_EQ(list_bool_value, std::vector<bool>({true, false})); | ||
| 201 | + std::vector<std::string> list_string_value; | ||
| 202 | + EXPECT_EQ(op.GetAttr("list_string_attr", list_string_value), GRAPH_SUCCESS); | ||
| 203 | + EXPECT_EQ(list_string_value, std::vector<std::string>({"a", "b"})); | ||
| 204 | + std::vector<DataType> list_data_type_value; | ||
| 205 | + EXPECT_EQ(op.GetAttr("list_data_type_attr", list_data_type_value), GRAPH_SUCCESS); | ||
| 206 | + EXPECT_EQ(list_data_type_value, std::vector<DataType>({DT_FLOAT, DT_INT32})); | ||
| 207 | + std::vector<std::vector<int64_t>> list_list_int_value; | ||
| 208 | + EXPECT_EQ(op.GetAttr("list_list_int_attr", list_list_int_value), GRAPH_SUCCESS); | ||
| 209 | + EXPECT_EQ(list_list_int_value, std::vector<std::vector<int64_t>>({{1, 2}, {3}})); | ||
| 210 | +} | ||
| 211 | +} // namespace | ||
| 212 | + | ||
| 213 | +TEST(PythonCustomOpProto, registers_creator_with_owned_definition) { | ||
| 214 | + std::string required_name = "required_input"; | ||
| 215 | + const PythonCustomOpProtoInputView inputs[] = { | ||
| 216 | + {PythonCustomOpStringView{required_name.data(), required_name.size()}, kPythonInputRequired}, | ||
| 217 | + {StringView("optional_input"), kPythonInputOptional}, | ||
| 218 | + {StringView("dynamic_input"), kPythonInputDynamic}, | ||
| 219 | + }; | ||
| 220 | + PythonCustomOpAttrDefaultView int_default{}; | ||
| 221 | + int_default.has_value = 1U; | ||
| 222 | + int_default.int_value = 7; | ||
| 223 | + const PythonCustomOpProtoAttrView attrs[] = { | ||
| 224 | + OptionalAttr("axis", kPythonAttrInt, int_default), | ||
| 225 | + RequiredAttr("scale", kPythonAttrFloat), | ||
| 226 | + }; | ||
| 227 | + const PythonCustomOpProtoOutputView outputs[] = { | ||
| 228 | + {StringView("required_input"), kPythonOutputRequired}, | ||
| 229 | + {StringView("dynamic_output"), kPythonOutputDynamic}, | ||
| 230 | + }; | ||
| 231 | + const auto view = MakeProtoView("test_module:infer_meta:PythonProtoRegisterUt", "PythonProtoRegisterUt", inputs, 3U, | ||
| 232 | + attrs, 2U, outputs, 2U); | ||
| 233 | + | ||
| 234 | + PythonCustomOpProto proto; | ||
| 235 | + ASSERT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_SUCCESS); | ||
| 236 | + required_name.assign("mutated_source"); | ||
| 237 | + ASSERT_EQ(proto.inputs[0].name, "required_input"); | ||
| 238 | + ASSERT_EQ(RegisterPythonCustomOpProto(proto), GRAPH_SUCCESS); | ||
| 239 | + ASSERT_TRUE(OperatorFactory::IsExistOp("PythonProtoRegisterUt")); | ||
| 240 | + proto.inputs[0].name = "mutated_proto"; | ||
| 241 | + proto.attrs[0].default_definition.int_value = 99; | ||
| 242 | + | ||
| 243 | + const auto op = OperatorFactory::CreateOperator("instance", "PythonProtoRegisterUt"); | ||
| 244 | + const auto op_desc = OpDescUtils::GetOpDescFromOperator(op); | ||
| 245 | + ASSERT_NE(op_desc, nullptr); | ||
| 246 | + const auto &ir_inputs = op_desc->GetIrInputs(); | ||
| 247 | + ASSERT_EQ(ir_inputs.size(), 3U); | ||
| 248 | + EXPECT_EQ(ir_inputs[0], std::make_pair(std::string("required_input"), kIrInputRequired)); | ||
| 249 | + EXPECT_EQ(ir_inputs[1], std::make_pair(std::string("optional_input"), kIrInputOptional)); | ||
| 250 | + EXPECT_EQ(ir_inputs[2], std::make_pair(std::string("dynamic_input"), kIrInputDynamic)); | ||
| 251 | + const auto &ir_outputs = op_desc->GetIrOutputs(); | ||
| 252 | + ASSERT_EQ(ir_outputs.size(), 2U); | ||
| 253 | + EXPECT_EQ(ir_outputs[0], std::make_pair(std::string("required_input"), kIrOutputRequired)); | ||
| 254 | + EXPECT_EQ(ir_outputs[1], std::make_pair(std::string("dynamic_output"), kIrOutputDynamic)); | ||
| 255 | + EXPECT_EQ(op_desc->GetIrAttrNames(), std::vector<std::string>({"axis", "scale"})); | ||
| 256 | + int64_t axis = 0; | ||
| 257 | + EXPECT_EQ(op.GetAttr("axis", axis), GRAPH_SUCCESS); | ||
| 258 | + EXPECT_EQ(axis, 7); | ||
| 259 | + UnregisterPythonCustomOpProtos({"PythonProtoRegisterUt"}); | ||
| 260 | + EXPECT_FALSE(OperatorFactory::IsExistOp("PythonProtoRegisterUt")); | ||
| 261 | +} | ||
| 262 | + | ||
| 263 | +TEST(PythonCustomOpProto, materializes_all_supported_attr_kinds_and_defaults) { | ||
| 264 | + AttrViewStorage storage; | ||
| 265 | + const auto view = MakeProtoView("test_module:infer_meta:PythonProtoAttrsUt", "PythonProtoAttrsUt", nullptr, 0U, | ||
| 266 | + storage.attrs.data(), storage.attrs.size(), nullptr, 0U); | ||
| 267 | + PythonCustomOpProto proto; | ||
| 268 | + ASSERT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_SUCCESS); | ||
| 269 | + ASSERT_EQ(proto.attrs.size(), 12U); | ||
| 270 | + EXPECT_TRUE(proto.attrs[0].is_required); | ||
| 271 | + EXPECT_EQ(proto.attrs[0].kind, kPythonAttrTensor); | ||
| 272 | + EXPECT_EQ(proto.attrs[1].default_definition.int_value, 9); | ||
| 273 | + EXPECT_EQ(proto.attrs[4].default_definition.string_value, "value"); | ||
| 274 | + EXPECT_EQ(proto.attrs[6].default_definition.list_int_values, std::vector<int64_t>({1, 2})); | ||
| 275 | + EXPECT_EQ(proto.attrs[9].default_definition.list_string_values, std::vector<std::string>({"a", "b"})); | ||
| 276 | + EXPECT_EQ(proto.attrs[11].default_definition.list_list_int_values, std::vector<std::vector<int64_t>>({{1, 2}, {3}})); | ||
| 277 | + storage.MutateSource(); | ||
| 278 | + ASSERT_EQ(RegisterPythonCustomOpProto(proto), GRAPH_SUCCESS); | ||
| 279 | + const auto op = OperatorFactory::CreateOperator("instance", "PythonProtoAttrsUt"); | ||
| 280 | + const auto op_desc = OpDescUtils::GetOpDescFromOperator(op); | ||
| 281 | + ASSERT_NE(op_desc, nullptr); | ||
| 282 | + EXPECT_EQ(op_desc->GetIrAttrNames(), | ||
| 283 | + std::vector<std::string>({"tensor_attr", "int_attr", "float_attr", "bool_attr", "string_attr", | ||
| 284 | + "data_type_attr", "list_int_attr", "list_float_attr", "list_bool_attr", | ||
| 285 | + "list_string_attr", "list_data_type_attr", "list_list_int_attr"})); | ||
| 286 | + ExpectAllIrAttrTypes(op); | ||
| 287 | + ExpectScalarDefaultAttrs(op); | ||
| 288 | + ExpectListDefaultAttrs(op); | ||
| 289 | + UnregisterPythonCustomOpProtos({"PythonProtoAttrsUt"}); | ||
| 290 | + EXPECT_FALSE(OperatorFactory::IsExistOp("PythonProtoAttrsUt")); | ||
| 291 | +} | ||
| 292 | + | ||
| 293 | +TEST(PythonCustomOpProto, rejects_optional_attr_without_default) { | ||
| 294 | + PythonCustomOpAttrDefaultView default_value{}; | ||
| 295 | + const auto attr = OptionalAttr("axis", kPythonAttrInt, default_value); | ||
| 296 | + const auto view = MakeProtoView("test_module:infer_meta:PythonProtoInvalidUt", "PythonProtoInvalidUt", nullptr, 0U, | ||
| 297 | + &attr, 1U, nullptr, 0U); | ||
| 298 | + PythonCustomOpProto proto; | ||
| 299 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 300 | +} | ||
| 301 | + | ||
| 302 | +TEST(PythonCustomOpProto, rejects_invalid_descriptor_and_array_views) { | ||
| 303 | + PythonCustomOpProto proto; | ||
| 304 | + auto view = MakeProtoView("descriptor", "PythonProtoInvalidPodUt", nullptr, 0U, nullptr, 0U, nullptr, 0U); | ||
| 305 | + view.descriptor_key = StringView(""); | ||
| 306 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 307 | + | ||
| 308 | + view = MakeProtoView("descriptor", "PythonProtoInvalidPodUt", nullptr, 0U, nullptr, 0U, nullptr, 0U); | ||
| 309 | + view.op_type = PythonCustomOpStringView{nullptr, 1U}; | ||
| 310 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 311 | + | ||
| 312 | + const char embedded_null[] = {'b', 'a', 'd', '\0', 't', 'y', 'p', 'e'}; | ||
| 313 | + view = MakeProtoView("descriptor", "PythonProtoInvalidPodUt", nullptr, 0U, nullptr, 0U, nullptr, 0U); | ||
| 314 | + view.op_type = PythonCustomOpStringView{embedded_null, sizeof(embedded_null)}; | ||
| 315 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 316 | + | ||
| 317 | + view = MakeProtoView("descriptor", "PythonProtoInvalidPodUt", nullptr, 1U, nullptr, 0U, nullptr, 0U); | ||
| 318 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 319 | + view = MakeProtoView("descriptor", "PythonProtoInvalidPodUt", nullptr, 0U, nullptr, 1U, nullptr, 0U); | ||
| 320 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 321 | + view = MakeProtoView("descriptor", "PythonProtoInvalidPodUt", nullptr, 0U, nullptr, 0U, nullptr, 1U); | ||
| 322 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 323 | + | ||
| 324 | + const PythonCustomOpProtoInputView invalid_input_name = {PythonCustomOpStringView{nullptr, 1U}, kPythonInputRequired}; | ||
| 325 | + view = MakeProtoView("descriptor", "PythonProtoInvalidPodUt", &invalid_input_name, 1U, nullptr, 0U, nullptr, 0U); | ||
| 326 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 327 | + | ||
| 328 | + PythonCustomOpAttrDefaultView invalid_list_default{}; | ||
| 329 | + invalid_list_default.has_value = 1U; | ||
| 330 | + invalid_list_default.list_int_values = nullptr; | ||
| 331 | + invalid_list_default.count = 1U; | ||
| 332 | + const auto invalid_list_attr = OptionalAttr("axes", kPythonAttrListInt, invalid_list_default); | ||
| 333 | + view = MakeProtoView("descriptor", "PythonProtoInvalidPodUt", nullptr, 0U, &invalid_list_attr, 1U, nullptr, 0U); | ||
| 334 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 335 | + | ||
| 336 | + const PythonCustomOpInt64ArrayView invalid_row[] = {{nullptr, 1U}}; | ||
| 337 | + PythonCustomOpAttrDefaultView invalid_nested_list_default{}; | ||
| 338 | + invalid_nested_list_default.has_value = 1U; | ||
| 339 | + invalid_nested_list_default.list_list_int_values = invalid_row; | ||
| 340 | + invalid_nested_list_default.count = 1U; | ||
| 341 | + const auto invalid_nested_list_attr = OptionalAttr("axes", kPythonAttrListListInt, invalid_nested_list_default); | ||
| 342 | + view = | ||
| 343 | + MakeProtoView("descriptor", "PythonProtoInvalidPodUt", nullptr, 0U, &invalid_nested_list_attr, 1U, nullptr, 0U); | ||
| 344 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 345 | +} | ||
| 346 | + | ||
| 347 | +TEST(PythonCustomOpProto, rejects_invalid_kinds_and_attr_defaults) { | ||
| 348 | + PythonCustomOpProto proto; | ||
| 349 | + const PythonCustomOpProtoInputView invalid_input = {StringView("x"), kPythonInputDynamic + 1U}; | ||
| 350 | + auto view = MakeProtoView("descriptor", "PythonProtoInvalidKindUt", &invalid_input, 1U, nullptr, 0U, nullptr, 0U); | ||
| 351 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 352 | + | ||
| 353 | + const PythonCustomOpProtoOutputView invalid_output = {StringView("y"), kPythonOutputDynamic + 1U}; | ||
| 354 | + view = MakeProtoView("descriptor", "PythonProtoInvalidKindUt", nullptr, 0U, nullptr, 0U, &invalid_output, 1U); | ||
| 355 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 356 | + | ||
| 357 | + const auto invalid_attr_kind = RequiredAttr("axis", kPythonAttrListListInt + 1U); | ||
| 358 | + view = MakeProtoView("descriptor", "PythonProtoInvalidKindUt", nullptr, 0U, &invalid_attr_kind, 1U, nullptr, 0U); | ||
| 359 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 360 | + | ||
| 361 | + PythonCustomOpAttrDefaultView default_value{}; | ||
| 362 | + default_value.has_value = 1U; | ||
| 363 | + default_value.bool_value = 2U; | ||
| 364 | + auto invalid_attr = OptionalAttr("flag", kPythonAttrBool, default_value); | ||
| 365 | + view = MakeProtoView("descriptor", "PythonProtoInvalidKindUt", nullptr, 0U, &invalid_attr, 1U, nullptr, 0U); | ||
| 366 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 367 | + | ||
| 368 | + const uint8_t invalid_bool_list[] = {0U, 2U}; | ||
| 369 | + default_value = {}; | ||
| 370 | + default_value.has_value = 1U; | ||
| 371 | + default_value.list_bool_values = invalid_bool_list; | ||
| 372 | + default_value.count = 2U; | ||
| 373 | + invalid_attr = OptionalAttr("flags", kPythonAttrListBool, default_value); | ||
| 374 | + view = MakeProtoView("descriptor", "PythonProtoInvalidKindUt", nullptr, 0U, &invalid_attr, 1U, nullptr, 0U); | ||
| 375 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 376 | + | ||
| 377 | + default_value = {}; | ||
| 378 | + default_value.has_value = 1U; | ||
| 379 | + default_value.data_type_value = static_cast<int32_t>(DT_MAX); | ||
| 380 | + invalid_attr = OptionalAttr("data_type", kPythonAttrDataType, default_value); | ||
| 381 | + view = MakeProtoView("descriptor", "PythonProtoInvalidKindUt", nullptr, 0U, &invalid_attr, 1U, nullptr, 0U); | ||
| 382 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 383 | + | ||
| 384 | + const int32_t invalid_data_type_list[] = {static_cast<int32_t>(DT_FLOAT), -1}; | ||
| 385 | + default_value = {}; | ||
| 386 | + default_value.has_value = 1U; | ||
| 387 | + default_value.list_data_type_values = invalid_data_type_list; | ||
| 388 | + default_value.count = 2U; | ||
| 389 | + invalid_attr = OptionalAttr("data_types", kPythonAttrListDataType, default_value); | ||
| 390 | + view = MakeProtoView("descriptor", "PythonProtoInvalidKindUt", nullptr, 0U, &invalid_attr, 1U, nullptr, 0U); | ||
| 391 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 392 | + | ||
| 393 | + default_value = {}; | ||
| 394 | + default_value.has_value = 1U; | ||
| 395 | + invalid_attr = OptionalAttr("tensor", kPythonAttrTensor, default_value); | ||
| 396 | + view = MakeProtoView("descriptor", "PythonProtoInvalidKindUt", nullptr, 0U, &invalid_attr, 1U, nullptr, 0U); | ||
| 397 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 398 | +} | ||
| 399 | + | ||
| 400 | +TEST(PythonCustomOpProto, rejects_duplicate_ir_names) { | ||
| 401 | + PythonCustomOpProto proto; | ||
| 402 | + const PythonCustomOpProtoInputView duplicate_inputs[] = {{StringView("x"), kPythonInputRequired}, | ||
| 403 | + {StringView("x"), kPythonInputOptional}}; | ||
| 404 | + auto view = MakeProtoView("descriptor", "PythonProtoDuplicateUt", duplicate_inputs, 2U, nullptr, 0U, nullptr, 0U); | ||
| 405 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 406 | + | ||
| 407 | + const PythonCustomOpProtoAttrView duplicate_attrs[] = {RequiredAttr("axis", kPythonAttrInt), | ||
| 408 | + RequiredAttr("axis", kPythonAttrFloat)}; | ||
| 409 | + view = MakeProtoView("descriptor", "PythonProtoDuplicateUt", nullptr, 0U, duplicate_attrs, 2U, nullptr, 0U); | ||
| 410 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 411 | + | ||
| 412 | + const PythonCustomOpProtoOutputView duplicate_outputs[] = {{StringView("y"), kPythonOutputRequired}, | ||
| 413 | + {StringView("y"), kPythonOutputDynamic}}; | ||
| 414 | + view = MakeProtoView("descriptor", "PythonProtoDuplicateUt", nullptr, 0U, nullptr, 0U, duplicate_outputs, 2U); | ||
| 415 | + EXPECT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_PARAM_INVALID); | ||
| 416 | +} | ||
| 417 | + | ||
| 418 | +TEST(PythonCustomOpProto, compares_definitions_idempotently_including_nan) { | ||
| 419 | + const PythonCustomOpProtoInputView inputs[] = {{StringView("x"), kPythonInputRequired}}; | ||
| 420 | + const PythonCustomOpProtoOutputView outputs[] = {{StringView("y"), kPythonOutputRequired}}; | ||
| 421 | + const double nan = std::numeric_limits<double>::quiet_NaN(); | ||
| 422 | + const double list_float[] = {1.0, nan}; | ||
| 423 | + PythonCustomOpAttrDefaultView scalar_default{}; | ||
| 424 | + scalar_default.has_value = 1U; | ||
| 425 | + scalar_default.float_value = nan; | ||
| 426 | + PythonCustomOpAttrDefaultView list_default{}; | ||
| 427 | + list_default.has_value = 1U; | ||
| 428 | + list_default.list_float_values = list_float; | ||
| 429 | + list_default.count = 2U; | ||
| 430 | + const PythonCustomOpProtoAttrView attrs[] = { | ||
| 431 | + OptionalAttr("scale", kPythonAttrFloat, scalar_default), | ||
| 432 | + OptionalAttr("scales", kPythonAttrListFloat, list_default), | ||
| 433 | + }; | ||
| 434 | + const auto view = MakeProtoView("descriptor", "PythonProtoIdempotentUt", inputs, 1U, attrs, 2U, outputs, 1U); | ||
| 435 | + PythonCustomOpProto lhs; | ||
| 436 | + PythonCustomOpProto rhs; | ||
| 437 | + ASSERT_EQ(ParsePythonCustomOpProto(view, lhs), GRAPH_SUCCESS); | ||
| 438 | + ASSERT_EQ(ParsePythonCustomOpProto(view, rhs), GRAPH_SUCCESS); | ||
| 439 | + EXPECT_TRUE(IsSamePythonCustomOpProto(lhs, rhs)); | ||
| 440 | + | ||
| 441 | + auto changed = rhs; | ||
| 442 | + changed.descriptor_key = "other_descriptor"; | ||
| 443 | + EXPECT_FALSE(IsSamePythonCustomOpProto(lhs, changed)); | ||
| 444 | + changed = rhs; | ||
| 445 | + changed.op_type = "OtherOpType"; | ||
| 446 | + EXPECT_FALSE(IsSamePythonCustomOpProto(lhs, changed)); | ||
| 447 | + changed = rhs; | ||
| 448 | + changed.inputs[0].kind = kIrInputOptional; | ||
| 449 | + EXPECT_FALSE(IsSamePythonCustomOpProto(lhs, changed)); | ||
| 450 | + changed = rhs; | ||
| 451 | + changed.attrs[0].default_definition.float_value = 1.0; | ||
| 452 | + EXPECT_FALSE(IsSamePythonCustomOpProto(lhs, changed)); | ||
| 453 | + changed = rhs; | ||
| 454 | + changed.attrs[1].default_definition.list_float_values[1] = 2.0; | ||
| 455 | + EXPECT_FALSE(IsSamePythonCustomOpProto(lhs, changed)); | ||
| 456 | + changed = rhs; | ||
| 457 | + changed.outputs[0].name = "other_output"; | ||
| 458 | + EXPECT_FALSE(IsSamePythonCustomOpProto(lhs, changed)); | ||
| 459 | +} | ||
| 460 | + | ||
| 461 | +TEST(PythonCustomOpProto, rejects_existing_custom_op_creator) { | ||
| 462 | + constexpr const char *kOpType = "PythonProtoCustomOpCollisionUt"; | ||
| 463 | + ASSERT_EQ(CustomOpFactory::RegisterCustomOpCreator(AscendString(kOpType), | ||
| 464 | + []() { return std::make_unique<PythonProtoCustomOpCollision>(); }), | ||
| 465 | + GRAPH_SUCCESS); | ||
| 466 | + const PythonCustomOpProtoInputView inputs[] = {{StringView("replacement"), kPythonInputRequired}}; | ||
| 467 | + const PythonCustomOpProtoOutputView outputs[] = {{StringView("replacement_output"), kPythonOutputRequired}}; | ||
| 468 | + const auto view = MakeProtoView("test_module:infer_meta:PythonProtoCustomOpCollisionUt", kOpType, inputs, 1U, nullptr, | ||
| 469 | + 0U, outputs, 1U); | ||
| 470 | + PythonCustomOpProto proto; | ||
| 471 | + ASSERT_EQ(ParsePythonCustomOpProto(view, proto), GRAPH_SUCCESS); | ||
| 472 | + | ||
| 473 | + EXPECT_EQ(RegisterPythonCustomOpProto(proto), GRAPH_FAILED); | ||
| 474 | + EXPECT_FALSE(OperatorFactory::IsExistOp(kOpType)); | ||
| 475 | + EXPECT_TRUE(CustomOpFactory::IsExistOp(AscendString(kOpType))); | ||
| 476 | + CustomOpFactory::RemoveCustomOps({AscendString(kOpType)}); | ||
| 477 | +} | ||
| 478 | + | ||
| 479 | +} // namespace custom_op | ||
| 480 | +} // namespace ge | ||


是不是在原来的逻辑基础上补充原型的清理逻辑就行呀?