已合并
feat: run Python custom op infer_meta at compile time and RT2 #4414
lfz2812创建于 11 天前
feat: run Python custom op infer_meta at compile time and RT2 #4414
已合并
共 42 个文件变更+2149-274
| @@ -11,11 +11,13 @@ | |||
| 11 | set(_ge_custom_op_native_sources | 11 | set(_ge_custom_op_native_sources |
| 12 | ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/module.cc | 12 | ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/module.cc |
| 13 | ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/context_binding.cc | 13 | ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/context_binding.cc |
| 14 | + ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/infer_meta_context_binding.cc | ||
| 14 | ) | 15 | ) |
| 15 | 16 | ||
| 16 | set(_ge_custom_op_native_headers | 17 | set(_ge_custom_op_native_headers |
| 17 | ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/binding_common.h | 18 | ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/binding_common.h |
| 18 | ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/custom_op_bindings.h | 19 | ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/custom_op_bindings.h |
| 20 | + ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/runtime_attrs_binding.h | ||
| 19 | ) | 21 | ) |
| 20 | 22 | ||
| 21 | set(GE_PYTHON_CUSTOM_OP_NATIVE_SOURCES ${_ge_custom_op_native_sources} PARENT_SCOPE) | 23 | set(GE_PYTHON_CUSTOM_OP_NATIVE_SOURCES ${_ge_custom_op_native_sources} PARENT_SCOPE) |
| @@ -19,6 +19,7 @@ __all__ = [ | |||
| 19 | "AnnotatedKernelLaunchInfo", | 19 | "AnnotatedKernelLaunchInfo", |
| 20 | "EagerExecuteOp", | 20 | "EagerExecuteOp", |
| 21 | "EagerOpExecutionContext", | 21 | "EagerOpExecutionContext", |
| 22 | + "InferMetaContext", | ||
| 22 | "WorkspaceAddr", | 23 | "WorkspaceAddr", |
| 23 | "clear_registered_op_impls", | 24 | "clear_registered_op_impls", |
| 24 | "get_declare_launch_args_ctx", | 25 | "get_declare_launch_args_ctx", |
| @@ -37,6 +38,7 @@ _LAZY_EXPORTS = { | |||
| 37 | "AnnotatedKernelLaunchInfo": "._native", | 38 | "AnnotatedKernelLaunchInfo": "._native", |
| 38 | "EagerExecuteOp": ".base", | 39 | "EagerExecuteOp": ".base", |
| 39 | "EagerOpExecutionContext": ".base", | 40 | "EagerOpExecutionContext": ".base", |
| 41 | + "InferMetaContext": "._native", | ||
| 40 | "clear_registered_op_impls": ".registry", | 42 | "clear_registered_op_impls": ".registry", |
| 41 | "get_declare_launch_args_ctx": ".context", | 43 | "get_declare_launch_args_ctx": ".context", |
| 42 | "get_execute_ctx": ".context", | 44 | "get_execute_ctx": ".context", |
| @@ -19,6 +19,7 @@ from dataclasses import dataclass | |||
| 19 | from typing import Dict, Optional | 19 | from typing import Dict, Optional |
| 20 | 20 | ||
| 21 | from ._ir_types import InputType, OutputType | 21 | from ._ir_types import InputType, OutputType |
| 22 | +from ._infer_meta import call_infer_meta # noqa: F401 | ||
| 22 | from ._signature import _get_runtime_attr_spec, _validate_args_signature | 23 | from ._signature import _get_runtime_attr_spec, _validate_args_signature |
| 23 | from .base import EagerOpExecutionContext | 24 | from .base import EagerOpExecutionContext |
| 24 | from .bootstrap import ( | 25 | from .bootstrap import ( |
| @@ -13,13 +13,14 @@ from __future__ import annotations | |||
| 13 | from typing import List, Optional | 13 | from typing import List, Optional |
| 14 | 14 | ||
| 15 | from ge.graph.types import DataType | 15 | from ge.graph.types import DataType |
| 16 | -from ge.runtime import StorageFormat, StorageShape, Tensor | 16 | +from ge.runtime import StorageFormat, StorageShape, Tensor, TensorDesc |
| 17 | 17 | ||
| 18 | __all__: List[str] = [ | 18 | __all__: List[str] = [ |
| 19 | "AnnotatedArgsContext", | 19 | "AnnotatedArgsContext", |
| 20 | "AnnotatedKernelArgs", | 20 | "AnnotatedKernelArgs", |
| 21 | "AnnotatedKernelLaunchInfo", | 21 | "AnnotatedKernelLaunchInfo", |
| 22 | "EagerOpExecutionContext", | 22 | "EagerOpExecutionContext", |
| 23 | + "InferMetaContext", | ||
| 23 | "WorkspaceAddr", | 24 | "WorkspaceAddr", |
| 24 | ] | 25 | ] |
| 25 | 26 | ||
| @@ -211,3 +212,26 @@ class AnnotatedArgsContext: | |||
| 211 | launch_info: AnnotatedKernelLaunchInfo, | 212 | launch_info: AnnotatedKernelLaunchInfo, |
| 212 | args: AnnotatedKernelArgs, | 213 | args: AnnotatedKernelArgs, |
| 213 | ) -> None: ... | 214 | ) -> None: ... |
| 215 | + | ||
| 216 | + | ||
| 217 | +class InferMetaContext: | ||
| 218 | + """Borrowed context for the Python infer_meta callback. | ||
| 219 | + | ||
| 220 | + Input readers return ``TensorDesc`` objects containing shape and dtype. | ||
| 221 | + The context is only valid during the infer_meta callback; calling | ||
| 222 | + ``_invalidate()`` expires all derived borrowed views. | ||
| 223 | + """ | ||
| 224 | + | ||
| 225 | + def get_required_input_tensor(self, ir_index: int) -> TensorDesc: ... | ||
| 226 | + | ||
| 227 | + def get_optional_input_tensor(self, ir_index: int) -> Optional[TensorDesc]: ... | ||
| 228 | + | ||
| 229 | + def get_dynamic_input_num(self, ir_index: int) -> int: ... | ||
| 230 | + | ||
| 231 | + def get_dynamic_input_tensor(self, ir_index: int, relative_index: int) -> TensorDesc: ... | ||
| 232 | + | ||
| 233 | + def get_attrs(self) -> RuntimeAttrs: ... | ||
| 234 | + | ||
| 235 | + def get_dynamic_output_num(self, ir_index: int) -> int: ... | ||
| 236 | + | ||
| 237 | + def _invalidate(self) -> None: ... | ||
| @@ -0,0 +1,140 @@ | |||
| 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 | +"""Python infer_meta callback helpers for GE custom operators.""" | ||
| 14 | + | ||
| 15 | +from typing import Optional | ||
| 16 | + | ||
| 17 | +from ._ir_types import InputType, OutputType | ||
| 18 | +from ._signature import _get_runtime_attr_spec | ||
| 19 | +from .proto import get_registered_op_proto_by_op_type | ||
| 20 | + | ||
| 21 | + | ||
| 22 | +def _build_infer_inputs(ctx, ir_inputs: list) -> list: | ||
| 23 | + args = [] | ||
| 24 | + for ir_index, item in enumerate(ir_inputs): | ||
| 25 | + kind = item["kind"] | ||
| 26 | + if kind == InputType.REQUIRED: | ||
| 27 | + args.append(ctx.get_required_input_tensor(ir_index)) | ||
| 28 | + elif kind == InputType.OPTIONAL: | ||
| 29 | + args.append(ctx.get_optional_input_tensor(ir_index)) | ||
| 30 | + elif kind == InputType.DYNAMIC: | ||
| 31 | + instance_num = ctx.get_dynamic_input_num(ir_index) | ||
| 32 | + descs = [] | ||
| 33 | + for relative_index in range(instance_num): | ||
| 34 | + descs.append(ctx.get_dynamic_input_tensor(ir_index, relative_index)) | ||
| 35 | + args.append(descs) | ||
| 36 | + return args | ||
| 37 | + | ||
| 38 | + | ||
| 39 | +def _read_infer_attr(attrs, index: int, ir_type: str): | ||
| 40 | + getter_name, _ = _get_runtime_attr_spec(ir_type, index) | ||
| 41 | + return getattr(attrs, getter_name)(index) | ||
| 42 | + | ||
| 43 | + | ||
| 44 | +def _build_infer_attrs(ctx, ir_attrs: list) -> dict: | ||
| 45 | + if not ir_attrs: | ||
| 46 | + return {} | ||
| 47 | + attrs = ctx.get_attrs() | ||
| 48 | + return { | ||
| 49 | + item["name"]: _read_infer_attr(attrs, index, item["type"]) | ||
| 50 | + for index, item in enumerate(ir_attrs) | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + | ||
| 54 | +def _validate_tensor_desc(desc, output_index: int) -> None: | ||
| 55 | + from ge.runtime import TensorDesc | ||
| 56 | + | ||
| 57 | + if not isinstance(desc, TensorDesc): | ||
| 58 | + raise TypeError( | ||
| 59 | + f"infer_meta output[{output_index}] must be TensorDesc, " | ||
| 60 | + f"got {type(desc).__name__}" | ||
| 61 | + ) | ||
| 62 | + | ||
| 63 | + | ||
| 64 | +def _flatten_infer_outputs(ir_outputs: list, result) -> tuple: | ||
| 65 | + if not ir_outputs: | ||
| 66 | + return [], [] | ||
| 67 | + | ||
| 68 | + if len(ir_outputs) == 1: | ||
| 69 | + kind = ir_outputs[0]["kind"] | ||
| 70 | + if kind == OutputType.REQUIRED: | ||
| 71 | + _validate_tensor_desc(result, 0) | ||
| 72 | + return [result], [1] | ||
| 73 | + if kind == OutputType.DYNAMIC: | ||
| 74 | + if not isinstance(result, (list, tuple)): | ||
| 75 | + raise TypeError( | ||
| 76 | + f"infer_meta output[0] is dynamic, must return list, " | ||
| 77 | + f"got {type(result).__name__}" | ||
| 78 | + ) | ||
| 79 | + for i, desc in enumerate(result): | ||
| 80 | + _validate_tensor_desc(desc, i) | ||
| 81 | + flattened = list(result) | ||
| 82 | + return flattened, [len(flattened)] | ||
| 83 | + | ||
| 84 | + if not isinstance(result, (list, tuple)): | ||
| 85 | + raise TypeError( | ||
| 86 | + "infer_meta must return list/tuple for multiple outputs, " | ||
| 87 | + f"got {type(result).__name__}" | ||
| 88 | + ) | ||
| 89 | + if len(result) != len(ir_outputs): | ||
| 90 | + raise TypeError( | ||
| 91 | + f"infer_meta return count {len(result)} != output count {len(ir_outputs)}" | ||
| 92 | + ) | ||
| 93 | + flattened = [] | ||
| 94 | + slot_sizes = [] | ||
| 95 | + for ir_index, (item, desc) in enumerate(zip(ir_outputs, result)): | ||
| 96 | + kind = item["kind"] | ||
| 97 | + if kind == OutputType.REQUIRED: | ||
| 98 | + _validate_tensor_desc(desc, ir_index) | ||
| 99 | + flattened.append(desc) | ||
| 100 | + slot_sizes.append(1) | ||
| 101 | + elif kind == OutputType.DYNAMIC: | ||
| 102 | + if not isinstance(desc, (list, tuple)): | ||
| 103 | + raise TypeError( | ||
| 104 | + f"infer_meta output[{ir_index}] is dynamic, must return list" | ||
| 105 | + ) | ||
| 106 | + for i, d in enumerate(desc): | ||
| 107 | + _validate_tensor_desc(d, ir_index) | ||
| 108 | + flattened.extend(desc) | ||
| 109 | + slot_sizes.append(len(desc)) | ||
| 110 | + return flattened, slot_sizes | ||
| 111 | + | ||
| 112 | + | ||
| 113 | +def call_infer_meta(op_type: str, ir_meta: Optional[dict], ctx) -> list: | ||
| 114 | + try: | ||
| 115 | + proto = get_registered_op_proto_by_op_type(op_type) | ||
| 116 | + infer_func = proto.infer_func | ||
| 117 | + args = _build_infer_inputs(ctx, ir_meta["inputs"]) | ||
| 118 | + kwargs = _build_infer_attrs(ctx, ir_meta["attrs"]) | ||
| 119 | + result = infer_func(*args, **kwargs) | ||
| 120 | + flattened, slot_sizes = _flatten_infer_outputs(ir_meta["outputs"], result) | ||
| 121 | + for ir_index, item in enumerate(ir_meta["outputs"]): | ||
| 122 | + kind = item["kind"] | ||
| 123 | + if kind == OutputType.DYNAMIC: | ||
| 124 | + instance_num = ctx.get_dynamic_output_num(ir_index) | ||
| 125 | + actual_num = slot_sizes[ir_index] | ||
| 126 | + if instance_num != actual_num: | ||
| 127 | + raise TypeError( | ||
| 128 | + f"infer_meta dynamic output[{ir_index}] instance count mismatch: " | ||
| 129 | + f"expected {instance_num}, got {actual_num}" | ||
| 130 | + ) | ||
| 131 | + return [ | ||
| 132 | + ( | ||
| 133 | + list(desc.shape.origin_shape.dims), | ||
| 134 | + list(desc.shape.storage_shape.dims), | ||
| 135 | + int(desc.data_type), | ||
| 136 | + ) | ||
| 137 | + for desc in flattened | ||
| 138 | + ] | ||
| 139 | + finally: | ||
| 140 | + ctx._invalidate() | ||
| @@ -19,6 +19,7 @@ __all__ = [ | |||
| 19 | "AnnotatedKernelArgs", | 19 | "AnnotatedKernelArgs", |
| 20 | "AnnotatedKernelLaunchInfo", | 20 | "AnnotatedKernelLaunchInfo", |
| 21 | "EagerOpExecutionContext", | 21 | "EagerOpExecutionContext", |
| 22 | + "InferMetaContext", | ||
| 22 | "WorkspaceAddr", | 23 | "WorkspaceAddr", |
| 23 | ] | 24 | ] |
| 24 | 25 | ||
| @@ -43,3 +44,4 @@ AnnotatedArgsContext = _native.AnnotatedArgsContext | |||
| 43 | AnnotatedKernelArgs = _native.AnnotatedKernelArgs | 44 | AnnotatedKernelArgs = _native.AnnotatedKernelArgs |
| 44 | AnnotatedKernelLaunchInfo = _native.AnnotatedKernelLaunchInfo | 45 | AnnotatedKernelLaunchInfo = _native.AnnotatedKernelLaunchInfo |
| 45 | WorkspaceAddr = _native.WorkspaceAddr | 46 | WorkspaceAddr = _native.WorkspaceAddr |
| 47 | +InferMetaContext = _native.InferMetaContext | ||
| @@ -10,14 +10,12 @@ | |||
| 10 | 10 | ||
| 11 | 11 | ||
| 12 | 12 | ||
| 13 | - | ||
| 14 | 13 | ||
| 15 | -#include "exe_graph/runtime/runtime_attrs.h" | 14 | +#include "runtime_attrs_binding.h" |
| 16 | 15 | ||
| 17 | 16 | ||
| 18 | 17 | ||
| 19 | 18 | ||
| 20 | - | ||
| 21 | 19 | ||
| 22 | 20 | ||
| 23 | 21 | ||
| @@ -29,157 +27,6 @@ namespace python_custom_op_native { | |||
| 29 | namespace { | 27 | namespace { |
| 30 | namespace runtime_native = ::ge::python_runtime_native; | 28 | namespace runtime_native = ::ge::python_runtime_native; |
| 31 | 29 | ||
| 32 | -template <typename T> | ||
| 33 | -const T *GetRequiredAttr(const gert::RuntimeAttrs *attrs, const size_t index, const char *type_name) { | ||
| 34 | - const auto *value = attrs->GetAttrPointer<T>(index); | ||
| 35 | - if (value == nullptr) { | ||
| 36 | - throw std::runtime_error(std::string("Failed to get runtime attr type[") + type_name + "] at index[" + | ||
| 37 | - std::to_string(index) + "]"); | ||
| 38 | - } | ||
| 39 | - return value; | ||
| 40 | -} | ||
| 41 | - | ||
| 42 | -template <typename T> | ||
| 43 | -py::list BuildTypedList(const gert::TypedContinuousVector<T> *value) { | ||
| 44 | - if (value == nullptr) { | ||
| 45 | - throw std::runtime_error("Runtime attr list is null"); | ||
| 46 | - } | ||
| 47 | - py::list result; | ||
| 48 | - const auto *data = value->GetData(); | ||
| 49 | - for (size_t index = 0U; index < value->GetSize(); ++index) { | ||
| 50 | - result.append(data[index]); | ||
| 51 | - } | ||
| 52 | - return result; | ||
| 53 | -} | ||
| 54 | - | ||
| 55 | -py::list BuildDataTypeList(const gert::ContinuousVector *value) { | ||
| 56 | - if (value == nullptr) { | ||
| 57 | - throw std::runtime_error("Runtime attr data type list is null"); | ||
| 58 | - } | ||
| 59 | - py::list result; | ||
| 60 | - const auto *data = static_cast<const ge::DataType *>(value->GetData()); | ||
| 61 | - for (size_t index = 0U; index < value->GetSize(); ++index) { | ||
| 62 | - result.append(runtime_native::MakeGraphTypeEnum("DataType", static_cast<int32_t>(data[index]))); | ||
| 63 | - } | ||
| 64 | - return result; | ||
| 65 | -} | ||
| 66 | - | ||
| 67 | -py::list BuildBoolList(const gert::ContinuousVector *value) { | ||
| 68 | - if (value == nullptr) { | ||
| 69 | - throw std::runtime_error("Runtime attr bool list is null"); | ||
| 70 | - } | ||
| 71 | - py::list result; | ||
| 72 | - const auto *data = static_cast<const uint8_t *>(value->GetData()); | ||
| 73 | - for (size_t index = 0U; index < value->GetSize(); ++index) { | ||
| 74 | - result.append(data[index] != 0U); | ||
| 75 | - } | ||
| 76 | - return result; | ||
| 77 | -} | ||
| 78 | - | ||
| 79 | -py::list BuildStringList(const gert::ContinuousVector *value) { | ||
| 80 | - if (value == nullptr) { | ||
| 81 | - throw std::runtime_error("Runtime attr string list is null"); | ||
| 82 | - } | ||
| 83 | - py::list result; | ||
| 84 | - const auto *data = static_cast<const char *>(value->GetData()); | ||
| 85 | - for (size_t index = 0U; index < value->GetSize(); ++index) { | ||
| 86 | - result.append(py::str(data)); | ||
| 87 | - data += std::strlen(data) + 1U; | ||
| 88 | - } | ||
| 89 | - return result; | ||
| 90 | -} | ||
| 91 | - | ||
| 92 | -py::list BuildIntListList(const gert::ContinuousVectorVector *value) { | ||
| 93 | - if (value == nullptr) { | ||
| 94 | - throw std::runtime_error("Runtime attr nested int list is null"); | ||
| 95 | - } | ||
| 96 | - py::list result; | ||
| 97 | - for (size_t index = 0U; index < value->GetSize(); ++index) { | ||
| 98 | - const auto *inner = value->Get(index); | ||
| 99 | - if (inner == nullptr) { | ||
| 100 | - throw std::runtime_error("Runtime attr nested int list element is null"); | ||
| 101 | - } | ||
| 102 | - const auto *data = static_cast<const int64_t *>(inner->GetData()); | ||
| 103 | - py::list inner_result; | ||
| 104 | - for (size_t inner_index = 0U; inner_index < inner->GetSize(); ++inner_index) { | ||
| 105 | - inner_result.append(data[inner_index]); | ||
| 106 | - } | ||
| 107 | - result.append(std::move(inner_result)); | ||
| 108 | - } | ||
| 109 | - return result; | ||
| 110 | -} | ||
| 111 | - | ||
| 112 | -class BorrowedRuntimeAttrs { | ||
| 113 | - public: | ||
| 114 | - BorrowedRuntimeAttrs(const gert::RuntimeAttrs *attrs, std::shared_ptr<bool> valid) | ||
| 115 | - : attrs_(attrs), valid_(std::move(valid)) {} | ||
| 116 | - | ||
| 117 | - int64_t GetInt(size_t index) const { | ||
| 118 | - return *GetRequiredAttr<int64_t>(Get(), index, "VT_INT"); | ||
| 119 | - } | ||
| 120 | - | ||
| 121 | - float GetFloat(size_t index) const { | ||
| 122 | - return *GetRequiredAttr<float>(Get(), index, "VT_FLOAT"); | ||
| 123 | - } | ||
| 124 | - | ||
| 125 | - bool GetBool(size_t index) const { | ||
| 126 | - return *GetRequiredAttr<bool>(Get(), index, "VT_BOOL"); | ||
| 127 | - } | ||
| 128 | - | ||
| 129 | - std::string GetStr(size_t index) const { | ||
| 130 | - return GetRequiredAttr<char>(Get(), index, "VT_STRING"); | ||
| 131 | - } | ||
| 132 | - | ||
| 133 | - py::object GetDataType(size_t index) const { | ||
| 134 | - const auto value = *GetRequiredAttr<ge::DataType>(Get(), index, "VT_DATA_TYPE"); | ||
| 135 | - return runtime_native::MakeGraphTypeEnum("DataType", static_cast<int32_t>(value)); | ||
| 136 | - } | ||
| 137 | - | ||
| 138 | - py::object GetTensor(size_t index) const { | ||
| 139 | - const auto *tensor = GetRequiredAttr<gert::Tensor>(Get(), index, "VT_TENSOR"); | ||
| 140 | - return py::cast(runtime_native::NativeTensor::Borrow(const_cast<gert::Tensor *>(tensor), valid_)); | ||
| 141 | - } | ||
| 142 | - | ||
| 143 | - py::list GetListInt(size_t index) const { | ||
| 144 | - return BuildTypedList(Get()->GetListInt(index)); | ||
| 145 | - } | ||
| 146 | - | ||
| 147 | - py::list GetListFloat(size_t index) const { | ||
| 148 | - return BuildTypedList(Get()->GetListFloat(index)); | ||
| 149 | - } | ||
| 150 | - | ||
| 151 | - py::list GetListBool(size_t index) const { | ||
| 152 | - return BuildBoolList(GetRequiredAttr<gert::ContinuousVector>(Get(), index, "VT_LIST_BOOL")); | ||
| 153 | - } | ||
| 154 | - | ||
| 155 | - py::list GetListStr(size_t index) const { | ||
| 156 | - return BuildStringList(GetRequiredAttr<gert::ContinuousVector>(Get(), index, "VT_LIST_STRING")); | ||
| 157 | - } | ||
| 158 | - | ||
| 159 | - py::list GetListDataType(size_t index) const { | ||
| 160 | - return BuildDataTypeList(GetRequiredAttr<gert::ContinuousVector>(Get(), index, "VT_LIST_DATA_TYPE")); | ||
| 161 | - } | ||
| 162 | - | ||
| 163 | - py::list GetListListInt(size_t index) const { | ||
| 164 | - return BuildIntListList(Get()->GetListListInt(index)); | ||
| 165 | - } | ||
| 166 | - | ||
| 167 | - size_t GetAttrNum() const { | ||
| 168 | - return Get()->GetAttrNum(); | ||
| 169 | - } | ||
| 170 | - | ||
| 171 | - private: | ||
| 172 | - const gert::RuntimeAttrs *Get() const { | ||
| 173 | - if ((valid_ == nullptr) || (!(*valid_)) || (attrs_ == nullptr)) { | ||
| 174 | - throw std::runtime_error("Borrowed runtime attrs have expired"); | ||
| 175 | - } | ||
| 176 | - return attrs_; | ||
| 177 | - } | ||
| 178 | - | ||
| 179 | - const gert::RuntimeAttrs *attrs_{nullptr}; | ||
| 180 | - std::shared_ptr<bool> valid_; | ||
| 181 | -}; | ||
| 182 | - | ||
| 183 | class BorrowedEagerOpExecutionContext { | 30 | class BorrowedEagerOpExecutionContext { |
| 184 | public: | 31 | public: |
| 185 | explicit BorrowedEagerOpExecutionContext(gert::EagerOpExecutionContext *ctx) | 32 | explicit BorrowedEagerOpExecutionContext(gert::EagerOpExecutionContext *ctx) |
| @@ -18,6 +18,7 @@ namespace python_custom_op_native { | |||
| 18 | 18 | ||
| 19 | void BindEagerOpExecutionContext(py::module_ &m); | 19 | void BindEagerOpExecutionContext(py::module_ &m); |
| 20 | void BindAnnotatedArgsContext(py::module_ &m); | 20 | void BindAnnotatedArgsContext(py::module_ &m); |
| 21 | +void BindInferMetaContext(py::module_ &m); | ||
| 21 | 22 | ||
| 22 | } // namespace python_custom_op_native | 23 | } // namespace python_custom_op_native |
| 23 | } // namespace ge | 24 | } // namespace ge |
| @@ -0,0 +1,178 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software; you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | +namespace ge { | ||
| 27 | +namespace python_custom_op_native { | ||
| 28 | +namespace { | ||
| 29 | +namespace runtime_native = ::ge::python_runtime_native; | ||
| 30 | + | ||
| 31 | +class BorrowedInferMetaContext { | ||
| 32 | + public: | ||
| 33 | + explicit BorrowedInferMetaContext(gert::InferShapeContext *ctx) : ctx_(ctx), valid_(std::make_shared<bool>(true)) {} | ||
| 34 | + | ||
| 35 | + py::object GetRequiredInputTensor(size_t ir_index) const { | ||
| 36 | + const auto *shape = GetDynamicInputShapePointer(ir_index, 0U); | ||
| 37 | + if (shape == nullptr) { | ||
| 38 | + throw std::runtime_error("Failed to get required input tensor at ir index " + std::to_string(ir_index)); | ||
| 39 | + } | ||
| 40 | + return MakeTensorDesc(shape, GetInputDataTypeByIr(ir_index, 0U, true)); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + py::object GetOptionalInputTensor(size_t ir_index) const { | ||
| 44 | + const auto *ins_info = Get()->GetIrInputInstanceInfo(ir_index); | ||
| 45 | + if ((ins_info == nullptr) || (ins_info->GetInstanceNum() == 0U)) { | ||
| 46 | + return py::none(); | ||
| 47 | + } | ||
| 48 | + const auto *shape = GetInputShape(ins_info->GetInstanceStart()); | ||
| 49 | + if (shape == nullptr) { | ||
| 50 | + return py::none(); | ||
| 51 | + } | ||
| 52 | + return MakeTensorDesc(shape, GetInputDataTypeByIr(ir_index, 0U, false)); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + size_t GetDynamicInputNum(size_t ir_index) const { | ||
| 56 | + const auto *ins_info = Get()->GetIrInputInstanceInfo(ir_index); | ||
| 57 | + if (ins_info == nullptr) { | ||
| 58 | + throw std::runtime_error("Failed to get dynamic input instance info at ir index " + std::to_string(ir_index)); | ||
| 59 | + } | ||
| 60 | + return ins_info->GetInstanceNum(); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + py::object GetDynamicInputTensor(size_t ir_index, size_t relative_index) const { | ||
| 64 | + const auto *shape = GetDynamicInputShapePointer(ir_index, relative_index); | ||
| 65 | + if (shape == nullptr) { | ||
| 66 | + throw std::runtime_error("Failed to get dynamic input tensor at ir index " + std::to_string(ir_index) + | ||
| 67 | + ", relative index " + std::to_string(relative_index)); | ||
| 68 | + } | ||
| 69 | + return MakeTensorDesc(shape, GetInputDataTypeByIr(ir_index, relative_index, true)); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + py::object GetAttrs() const { | ||
| 73 | + const auto *attrs = Get()->GetAttrs(); | ||
| 74 | + if (attrs == nullptr) { | ||
| 75 | + throw std::runtime_error("Failed to get runtime attrs"); | ||
| 76 | + } | ||
| 77 | + return py::cast(BorrowedRuntimeAttrs(attrs, valid_)); | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + size_t GetDynamicOutputNum(size_t ir_index) const { | ||
| 81 | + const auto *ins_info = Get()->GetIrOutputInstanceInfo(ir_index); | ||
| 82 | + if (ins_info == nullptr) { | ||
| 83 | + throw std::runtime_error("Failed to get dynamic output instance info at ir index " + std::to_string(ir_index)); | ||
| 84 | + } | ||
| 85 | + return ins_info->GetInstanceNum(); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + void Invalidate() { | ||
| 89 | + if (valid_ != nullptr) { | ||
| 90 | + *valid_ = false; | ||
| 91 | + } | ||
| 92 | + ctx_ = nullptr; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + private: | ||
| 96 | + gert::InferShapeContext *Get() const { | ||
| 97 | + if ((valid_ == nullptr) || (!(*valid_)) || (ctx_ == nullptr)) { | ||
| 98 | + throw std::runtime_error("Borrowed infer shape context has expired"); | ||
| 99 | + } | ||
| 100 | + return ctx_; | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + const gert::Shape *GetInputShape(size_t flat_index) const { | ||
| 104 | + return Get()->GetInputShape(flat_index); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + const gert::Shape *GetDynamicInputShapePointer(size_t ir_index, size_t relative_index) const { | ||
| 108 | + const auto *ins_info = Get()->GetIrInputInstanceInfo(ir_index); | ||
| 109 | + if (ins_info == nullptr) { | ||
| 110 | + return nullptr; | ||
| 111 | + } | ||
| 112 | + const auto start = ins_info->GetInstanceStart(); | ||
| 113 | + if ((ins_info->GetInstanceNum() == 0U) || (relative_index >= ins_info->GetInstanceNum())) { | ||
| 114 | + return nullptr; | ||
| 115 | + } | ||
| 116 | + return GetInputShape(start + relative_index); | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + int32_t GetInputDataTypeByIr(size_t ir_index, size_t relative_index, bool required) const { | ||
| 120 | + const auto *ins_info = Get()->GetIrInputInstanceInfo(ir_index); | ||
| 121 | + if (ins_info == nullptr) { | ||
| 122 | + if (required) { | ||
| 123 | + throw std::runtime_error("Failed to get input instance info at ir index " + std::to_string(ir_index)); | ||
| 124 | + } | ||
| 125 | + return -1; | ||
| 126 | + } | ||
| 127 | + const auto start = ins_info->GetInstanceStart(); | ||
| 128 | + if ((ins_info->GetInstanceNum() == 0U) || (relative_index >= ins_info->GetInstanceNum())) { | ||
| 129 | + if (required) { | ||
| 130 | + throw std::runtime_error("Failed to get input data type at ir index " + std::to_string(ir_index)); | ||
| 131 | + } | ||
| 132 | + return -1; | ||
| 133 | + } | ||
| 134 | + const auto *desc = Get()->GetInputDesc(start + relative_index); | ||
| 135 | + if (desc == nullptr) { | ||
| 136 | + if (required) { | ||
| 137 | + throw std::runtime_error("Failed to get input desc at ir index " + std::to_string(ir_index)); | ||
| 138 | + } | ||
| 139 | + return -1; | ||
| 140 | + } | ||
| 141 | + return static_cast<int32_t>(desc->GetDataType()); | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + py::object MakeTensorDesc(const gert::Shape *shape, int32_t data_type) const { | ||
| 145 | + const auto shape_dims = runtime_native::ShapeToDims(*shape); | ||
| 146 | + const auto shape_obj = py::cast(runtime_native::NativeStorageShape(shape_dims, shape_dims)); | ||
| 147 | + const auto data_type_obj = runtime_native::MakeGraphTypeEnum("DataType", data_type); | ||
| 148 | + return py::cast(runtime_native::NativeTensorDesc(shape_obj, data_type_obj)); | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + gert::InferShapeContext *ctx_{nullptr}; | ||
| 152 | + std::shared_ptr<bool> valid_; | ||
| 153 | +}; | ||
| 154 | + | ||
| 155 | +BorrowedInferMetaContext BorrowInferMetaContext(uintptr_t ctx_handle) { | ||
| 156 | + if (ctx_handle == 0U) { | ||
| 157 | + throw std::invalid_argument("ctx_handle is null"); | ||
| 158 | + } | ||
| 159 | + return BorrowedInferMetaContext(reinterpret_cast<gert::InferShapeContext *>(ctx_handle)); | ||
| 160 | +} | ||
| 161 | + | ||
| 162 | +} // namespace | ||
| 163 | + | ||
| 164 | +void BindInferMetaContext(py::module_ &m) { | ||
| 165 | + py::class_<BorrowedInferMetaContext>(m, "InferMetaContext", "Borrowed context for Python infer_meta") | ||
| 166 | + .def("get_required_input_tensor", &BorrowedInferMetaContext::GetRequiredInputTensor, py::arg("ir_index")) | ||
| 167 | + .def("get_optional_input_tensor", &BorrowedInferMetaContext::GetOptionalInputTensor, py::arg("ir_index")) | ||
| 168 | + .def("get_dynamic_input_num", &BorrowedInferMetaContext::GetDynamicInputNum, py::arg("ir_index")) | ||
| 169 | + .def("get_dynamic_input_tensor", &BorrowedInferMetaContext::GetDynamicInputTensor, py::arg("ir_index"), | ||
| 170 | + py::arg("relative_index")) | ||
| 171 | + .def("get_attrs", &BorrowedInferMetaContext::GetAttrs) | ||
| 172 | + .def("get_dynamic_output_num", &BorrowedInferMetaContext::GetDynamicOutputNum, py::arg("ir_index")) | ||
| 173 | + .def("_invalidate", &BorrowedInferMetaContext::Invalidate); | ||
| 174 | + m.def("_borrow_infer_meta_context", &BorrowInferMetaContext, py::arg("ctx_handle")); | ||
| 175 | +} | ||
| 176 | + | ||
| 177 | +} // namespace python_custom_op_native | ||
| 178 | +} // namespace ge | ||
| @@ -14,5 +14,6 @@ namespace ge { | |||
| 14 | PYBIND11_MODULE(_ge_custom_op_native, m) { | 14 | PYBIND11_MODULE(_ge_custom_op_native, m) { |
| 15 | python_custom_op_native::BindEagerOpExecutionContext(m); | 15 | python_custom_op_native::BindEagerOpExecutionContext(m); |
| 16 | python_custom_op_native::BindAnnotatedArgsContext(m); | 16 | python_custom_op_native::BindAnnotatedArgsContext(m); |
| 17 | + python_custom_op_native::BindInferMetaContext(m); | ||
| 17 | } | 18 | } |
| 18 | } // namespace ge | 19 | } // namespace ge |
| @@ -0,0 +1,178 @@ | |||
| 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 | +namespace ge { | ||
| 28 | +namespace python_custom_op_native { | ||
| 29 | +namespace runtime_attrs_binding_detail { | ||
| 30 | +namespace runtime_native = ::ge::python_runtime_native; | ||
| 31 | + | ||
| 32 | +template <typename T> | ||
| 33 | +inline const T *GetRequiredAttr(const gert::RuntimeAttrs *attrs, size_t index, const char *type_name) { | ||
| 34 | + const auto *value = attrs->GetAttrPointer<T>(index); | ||
| 35 | + if (value == nullptr) { | ||
| 36 | + throw std::runtime_error(std::string("Failed to get runtime attr type[") + type_name + "] at index[" + | ||
| 37 | + std::to_string(index) + "]"); | ||
| 38 | + } | ||
| 39 | + return value; | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +template <typename T> | ||
| 43 | +inline py::list BuildTypedList(const gert::TypedContinuousVector<T> *value) { | ||
| 44 | + if (value == nullptr) { | ||
| 45 | + throw std::runtime_error("Runtime attr list is null"); | ||
| 46 | + } | ||
| 47 | + py::list result; | ||
| 48 | + const auto *data = value->GetData(); | ||
| 49 | + for (size_t index = 0U; index < value->GetSize(); ++index) { | ||
| 50 | + result.append(data[index]); | ||
| 51 | + } | ||
| 52 | + return result; | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +inline py::list BuildDataTypeList(const gert::ContinuousVector *value) { | ||
| 56 | + if (value == nullptr) { | ||
| 57 | + throw std::runtime_error("Runtime attr data type list is null"); | ||
| 58 | + } | ||
| 59 | + py::list result; | ||
| 60 | + const auto *data = static_cast<const ge::DataType *>(value->GetData()); | ||
| 61 | + for (size_t index = 0U; index < value->GetSize(); ++index) { | ||
| 62 | + result.append(runtime_native::MakeGraphTypeEnum("DataType", static_cast<int32_t>(data[index]))); | ||
| 63 | + } | ||
| 64 | + return result; | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | +inline py::list BuildBoolList(const gert::ContinuousVector *value) { | ||
| 68 | + if (value == nullptr) { | ||
| 69 | + throw std::runtime_error("Runtime attr bool list is null"); | ||
| 70 | + } | ||
| 71 | + py::list result; | ||
| 72 | + const auto *data = static_cast<const uint8_t *>(value->GetData()); | ||
| 73 | + for (size_t index = 0U; index < value->GetSize(); ++index) { | ||
| 74 | + result.append(data[index] != 0U); | ||
| 75 | + } | ||
| 76 | + return result; | ||
| 77 | +} | ||
| 78 | + | ||
| 79 | +inline py::list BuildStringList(const gert::ContinuousVector *value) { | ||
| 80 | + if (value == nullptr) { | ||
| 81 | + throw std::runtime_error("Runtime attr string list is null"); | ||
| 82 | + } | ||
| 83 | + py::list result; | ||
| 84 | + const auto *data = static_cast<const char *>(value->GetData()); | ||
| 85 | + for (size_t index = 0U; index < value->GetSize(); ++index) { | ||
| 86 | + result.append(py::str(data)); | ||
| 87 | + data += std::strlen(data) + 1U; | ||
| 88 | + } | ||
| 89 | + return result; | ||
| 90 | +} | ||
| 91 | + | ||
| 92 | +inline py::list BuildIntListList(const gert::ContinuousVectorVector *value) { | ||
| 93 | + if (value == nullptr) { | ||
| 94 | + throw std::runtime_error("Runtime attr nested int list is null"); | ||
| 95 | + } | ||
| 96 | + py::list result; | ||
| 97 | + for (size_t index = 0U; index < value->GetSize(); ++index) { | ||
| 98 | + const auto *inner = value->Get(index); | ||
| 99 | + if (inner == nullptr) { | ||
| 100 | + throw std::runtime_error("Runtime attr nested int list element is null"); | ||
| 101 | + } | ||
| 102 | + const auto *data = static_cast<const int64_t *>(inner->GetData()); | ||
| 103 | + py::list inner_result; | ||
| 104 | + for (size_t inner_index = 0U; inner_index < inner->GetSize(); ++inner_index) { | ||
| 105 | + inner_result.append(data[inner_index]); | ||
| 106 | + } | ||
| 107 | + result.append(std::move(inner_result)); | ||
| 108 | + } | ||
| 109 | + return result; | ||
| 110 | +} | ||
| 111 | +} // namespace runtime_attrs_binding_detail | ||
| 112 | + | ||
| 113 | +class BorrowedRuntimeAttrs { | ||
| 114 | + public: | ||
| 115 | + BorrowedRuntimeAttrs(const gert::RuntimeAttrs *attrs, std::shared_ptr<bool> valid) | ||
| 116 | + : attrs_(attrs), valid_(std::move(valid)) {} | ||
| 117 | + | ||
| 118 | + int64_t GetInt(size_t index) const { | ||
| 119 | + return *runtime_attrs_binding_detail::GetRequiredAttr<int64_t>(Get(), index, "VT_INT"); | ||
| 120 | + } | ||
| 121 | + float GetFloat(size_t index) const { | ||
| 122 | + return *runtime_attrs_binding_detail::GetRequiredAttr<float>(Get(), index, "VT_FLOAT"); | ||
| 123 | + } | ||
| 124 | + bool GetBool(size_t index) const { | ||
| 125 | + return *runtime_attrs_binding_detail::GetRequiredAttr<bool>(Get(), index, "VT_BOOL"); | ||
| 126 | + } | ||
| 127 | + std::string GetStr(size_t index) const { | ||
| 128 | + return runtime_attrs_binding_detail::GetRequiredAttr<char>(Get(), index, "VT_STRING"); | ||
| 129 | + } | ||
| 130 | + py::object GetDataType(size_t index) const { | ||
| 131 | + const auto value = *runtime_attrs_binding_detail::GetRequiredAttr<ge::DataType>(Get(), index, "VT_DATA_TYPE"); | ||
| 132 | + return runtime_attrs_binding_detail::runtime_native::MakeGraphTypeEnum("DataType", static_cast<int32_t>(value)); | ||
| 133 | + } | ||
| 134 | + py::object GetTensor(size_t index) const { | ||
| 135 | + const auto *tensor = runtime_attrs_binding_detail::GetRequiredAttr<gert::Tensor>(Get(), index, "VT_TENSOR"); | ||
| 136 | + return py::cast( | ||
| 137 | + runtime_attrs_binding_detail::runtime_native::NativeTensor::Borrow(const_cast<gert::Tensor *>(tensor), valid_)); | ||
| 138 | + } | ||
| 139 | + py::list GetListInt(size_t index) const { | ||
| 140 | + return runtime_attrs_binding_detail::BuildTypedList(Get()->GetListInt(index)); | ||
| 141 | + } | ||
| 142 | + py::list GetListFloat(size_t index) const { | ||
| 143 | + return runtime_attrs_binding_detail::BuildTypedList(Get()->GetListFloat(index)); | ||
| 144 | + } | ||
| 145 | + py::list GetListBool(size_t index) const { | ||
| 146 | + return runtime_attrs_binding_detail::BuildBoolList( | ||
| 147 | + runtime_attrs_binding_detail::GetRequiredAttr<gert::ContinuousVector>(Get(), index, "VT_LIST_BOOL")); | ||
| 148 | + } | ||
| 149 | + py::list GetListStr(size_t index) const { | ||
| 150 | + return runtime_attrs_binding_detail::BuildStringList( | ||
| 151 | + runtime_attrs_binding_detail::GetRequiredAttr<gert::ContinuousVector>(Get(), index, "VT_LIST_STRING")); | ||
| 152 | + } | ||
| 153 | + py::list GetListDataType(size_t index) const { | ||
| 154 | + return runtime_attrs_binding_detail::BuildDataTypeList( | ||
| 155 | + runtime_attrs_binding_detail::GetRequiredAttr<gert::ContinuousVector>(Get(), index, "VT_LIST_DATA_TYPE")); | ||
| 156 | + } | ||
| 157 | + py::list GetListListInt(size_t index) const { | ||
| 158 | + return runtime_attrs_binding_detail::BuildIntListList(Get()->GetListListInt(index)); | ||
| 159 | + } | ||
| 160 | + size_t GetAttrNum() const { | ||
| 161 | + return Get()->GetAttrNum(); | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + private: | ||
| 165 | + const gert::RuntimeAttrs *Get() const { | ||
| 166 | + if ((valid_ == nullptr) || (!(*valid_)) || (attrs_ == nullptr)) { | ||
| 167 | + throw std::runtime_error("Borrowed runtime attrs have expired"); | ||
| 168 | + } | ||
| 169 | + return attrs_; | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + const gert::RuntimeAttrs *attrs_{nullptr}; | ||
| 173 | + std::shared_ptr<bool> valid_; | ||
| 174 | +}; | ||
| 175 | +} // namespace python_custom_op_native | ||
| 176 | +} // namespace ge | ||
| 177 | + | ||
| 178 | + | ||
| @@ -200,6 +200,10 @@ class _OpProtoRegistry: | |||
| 200 | with self._lock: | 200 | with self._lock: |
| 201 | return self._descriptor_key_to_desc.get(descriptor_key) | 201 | return self._descriptor_key_to_desc.get(descriptor_key) |
| 202 | 202 | ||
| 203 | + def get_by_op_type(self, op_type: str) -> Optional[OpProtoDescriptor]: | ||
| 204 | + with self._lock: | ||
| 205 | + return self._op_type_to_desc.get(op_type) | ||
| 206 | + | ||
| 203 | def get_all(self) -> List[OpProtoDescriptor]: | 207 | def get_all(self) -> List[OpProtoDescriptor]: |
| 204 | with self._lock: | 208 | with self._lock: |
| 205 | return sorted(self._op_type_to_desc.values(), key=lambda item: item.op_type) | 209 | return sorted(self._op_type_to_desc.values(), key=lambda item: item.op_type) |
| @@ -543,3 +547,7 @@ def get_registered_op_proto_by_descriptor_key( | |||
| 543 | descriptor_key: str, | 547 | descriptor_key: str, |
| 544 | ) -> Optional[OpProtoDescriptor]: | 548 | ) -> Optional[OpProtoDescriptor]: |
| 545 | return _OP_PROTO_REGISTRY.get_by_descriptor_key(descriptor_key) | 549 | return _OP_PROTO_REGISTRY.get_by_descriptor_key(descriptor_key) |
| 550 | + | ||
| 551 | + | ||
| 552 | +def get_registered_op_proto_by_op_type(op_type: str) -> Optional[OpProtoDescriptor]: | ||
| 553 | + return _OP_PROTO_REGISTRY.get_by_op_type(op_type) | ||
| @@ -817,11 +817,11 @@ custom_op/ | |||
| 817 | ``` | 817 | ``` |
| 818 | 818 | ||
| 819 | Note: Files prefixed with underscores are internal modules in the Python style. | 819 | Note: Files prefixed with underscores are internal modules in the Python style. |
| 820 | -Note: `EagerOpExecutionContext` and `AnnotatedArgsContext` are provided by `_ge_custom_op_native.so` as native-backed implementations. Runtime data structures such as `Tensor`, `StorageShape`, `StorageFormat`, `Shape`, and `TensorPlacement` returned or received during execution are provided by the `ge.runtime` module. | 820 | +Note: `EagerOpExecutionContext`, `AnnotatedArgsContext`, and `InferShapeContext` are provided by `_ge_custom_op_native.so` as native-backed implementations. Runtime data structures such as `Tensor`, `TensorDesc`, `StorageShape`, `StorageFormat`, `Shape`, and `TensorPlacement` returned or received during execution or an `infer_meta` callback are provided by the `ge.runtime` module. |
| 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. The current stage also registers Python prototypes with `OperatorFactory`, but does not invoke Python `infer_meta` or provide compile-time or RT2 Meta inference. | 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. After a Python prototype is registered with `OperatorFactory` through `register_op`, the compile-time and RT2 dynamic-shape paths invoke the same Python `infer_meta` callback. Compile-time inference writes output shape, dtype, and origin dtype; RT2 updates output shape only. |
| 825 | 825 | ||
| 826 | #### Runtime Native Artifact Selection | 826 | #### Runtime Native Artifact Selection |
| 827 | 827 | ||
| @@ -956,7 +956,7 @@ Within one AnnotatedArgs task-plan lifecycle, `declare_launch_args` is invoked e | |||
| 956 | 956 | ||
| 957 | **Decorators**: | 957 | **Decorators**: |
| 958 | 958 | ||
| 959 | -- `register_op(op_type, mutates_args=())` - Declares and collects a Python custom operator prototype from annotations on the decorated function | 959 | +- `register_op(op_type, mutates_args=())` - Declares and collects a Python custom operator prototype from annotations on the decorated function; the decorated function also serves as the `infer_meta` callback and returns output `TensorDesc` objects |
| 960 | - `register_op_impl(op_type)` - Registers a Python implementation class and reflects its callable methods into a capability list; `execute` maps to `eager_execute`, and `declare_launch_args` maps to `annotated_args` | 960 | - `register_op_impl(op_type)` - Registers a Python implementation class and reflects its callable methods into a capability list; `execute` maps to `eager_execute`, and `declare_launch_args` maps to `annotated_args` |
| 961 | 961 | ||
| 962 | **Discovery mechanism**: | 962 | **Discovery mechanism**: |
| @@ -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 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`. | 22 | +V2 extends the V1 execution path with Python prototype and Meta inference capabilities. The current stage provides Python prototype creators, Adapter registration transactions, ownership management, and a Python `infer_meta` callback connected to both compile-time and RT2 dynamic-shape inference. |
| 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 | ||
| @@ -67,7 +67,7 @@ The actual module boundaries are as follows: | |||
| 67 | |--------|----------|----------------| | 67 | |--------|----------|----------------| |
| 68 | | Python API | `api/python/ge/ge/custom_op/` | Implementation method reflection, registration, compatibility base classes, plugin discovery, bridge helper | | 68 | | Python API | `api/python/ge/ge/custom_op/` | Implementation method reflection, registration, compatibility base classes, plugin discovery, bridge helper | |
| 69 | | Runtime types | `api/python/ge/ge/runtime/` | Runtime data structures such as `Tensor`, `StorageShape`, and `StorageFormat` | | 69 | | Runtime types | `api/python/ge/ge/runtime/` | Runtime data structures such as `Tensor`, `StorageShape`, and `StorageFormat` | |
| 70 | -| Native context | `api/python/ge/ge/custom_op/native_bindings/` | `_ge_custom_op_native`, binding Eager and AnnotatedArgs contexts, argument builders, and `RuntimeAttrs` | | 70 | +| Native context | `api/python/ge/ge/custom_op/native_bindings/` | `_ge_custom_op_native`, binding Eager, AnnotatedArgs, and InferShape contexts, argument builders, and `RuntimeAttrs` | |
| 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` | |
| @@ -108,7 +108,7 @@ V1 functions include: | |||
| 108 | 108 | ||
| 109 | - The GE / ATC / Executor entry calls `GePythonRuntimeManager::EnsureReady()` before `LoadCustomOps()`. If interpreter initialization fails, the system continues with a warning according to the existing entry strategy. | 109 | - The GE / ATC / Executor entry calls `GePythonRuntimeManager::EnsureReady()` before `LoadCustomOps()`. If interpreter initialization fails, the system continues with a warning according to the existing entry strategy. |
| 110 | - Python entries in `ASCEND_CUSTOM_OPP_PATH` are `.py` files, non-underscore-prefixed `.py` files one level deep in a directory, or package directories with `__init__.py`. | 110 | - Python entries in `ASCEND_CUSTOM_OPP_PATH` are `.py` files, non-underscore-prefixed `.py` files one level deep in a directory, or package directories with `__init__.py`. |
| 111 | -- In V1, operator prototypes and shape/dtype inference are still provided by users through the existing C++ / OPP method. | 111 | +- For operators that do not use Python `register_op`, the V1 compatibility path still obtains the operator prototype and shape/dtype inference through the existing C++ / OPP method. Python `register_op` operators use the V2 `infer_meta` path. |
| 112 | - The Python custom op sample depends on the ACL Python runtime and a Python environment that matches the run package. | 112 | - The Python custom op sample depends on the ACL Python runtime and a Python environment that matches the run package. |
| 113 | - `declare_launch_args` depends on the Python registration environment only during compilation. A new OM stores the selected refresh mode in `_custom_task_args_mode` and the launch layout in `args_format`; static-model loading uses the explicit mode and does not load the Python implementation again. An OM without the attribute keeps the legacy registry lookup and `args_format` fallback. | 113 | - `declare_launch_args` depends on the Python registration environment only during compilation. A new OM stores the selected refresh mode in `_custom_task_args_mode` and the launch layout in `args_format`; static-model loading uses the explicit mode and does not load the Python implementation again. An OM without the attribute keeps the legacy registry lookup and `args_format` fallback. |
| 114 | 114 | ||
| @@ -251,7 +251,7 @@ Each descriptor contains at least: | |||
| 251 | 251 | ||
| 252 | **Introduction** | 252 | **Introduction** |
| 253 | 253 | ||
| 254 | -`_ge_custom_op_native` binds `EagerOpExecutionContext`, `AnnotatedArgsContext`, `AnnotatedKernelArgs`, `AnnotatedKernelLaunchInfo`, and `RuntimeAttrs`. Types such as `Tensor`, `StorageShape`, and `StorageFormat` returned by context methods are provided by `ge.runtime`. | 254 | +`_ge_custom_op_native` binds `EagerOpExecutionContext`, `AnnotatedArgsContext`, `InferShapeContext`, `AnnotatedKernelArgs`, `AnnotatedKernelLaunchInfo`, and `RuntimeAttrs`. Types such as `Tensor`, `TensorDesc`, `StorageShape`, and `StorageFormat` returned by context methods are provided by `ge.runtime`. |
| 255 | 255 | ||
| 256 | **Input** | 256 | **Input** |
| 257 | 257 | ||
| @@ -276,6 +276,8 @@ The bridge layer injects the Python borrowed view at the execution entry. | |||
| 276 | | `get_output_tensor(index)` | Obtains the output `Tensor` specified by index | | 276 | | `get_output_tensor(index)` | Obtains the output `Tensor` specified by index | |
| 277 | | `get_stream()` | Obtains the address integer of the associated execution stream | | 277 | | `get_stream()` | Obtains the address integer of the associated execution stream | |
| 278 | 278 | ||
| 279 | +`InferShapeContext` is used by the `register_op` decorated function when it runs as `infer_meta`. It reads shapes and data types for required, optional, and dynamic inputs, reads typed runtime attributes, and queries dynamic output instance counts. This context is valid only during the current `infer_meta` callback; output shapes and data types are carried together by the `TensorDesc` objects returned from `infer_meta`. | ||
| 280 | + | ||
| 279 | `AnnotatedArgsContext` exposes workspace allocation, stream-id query, kernel-argument builder creation, and launch addition. Its input/output tensor and attribute queries are used by the internal schema-bound assembly logic. `AnnotatedKernelArgs` exposes `append_input`, `append_output`, `append_workspace`, and `append_scalar`. | 281 | `AnnotatedArgsContext` exposes workspace allocation, stream-id query, kernel-argument builder creation, and launch addition. Its input/output tensor and attribute queries are used by the internal schema-bound assembly logic. `AnnotatedKernelArgs` exposes `append_input`, `append_output`, `append_workspace`, and `append_scalar`. |
| 280 | 282 | ||
| 281 | `RuntimeAttrs` provides the following typed readers by attribute IR index: | 283 | `RuntimeAttrs` provides the following typed readers by attribute IR index: |
| @@ -289,6 +291,7 @@ The bridge layer injects the Python borrowed view at the execution entry. | |||
| 289 | **Output** | 291 | **Output** |
| 290 | 292 | ||
| 291 | - Tensor-related methods return `ge.runtime.Tensor`. | 293 | - Tensor-related methods return `ge.runtime.Tensor`. |
| 294 | +- `infer_meta` inputs and return values use `ge.runtime.TensorDesc`; `TensorDesc.shape` uses `StorageShape`, and `TensorDesc.data_type` uses `ge.graph.DataType`. | ||
| 292 | - Shape and format input parameters use `ge.runtime.StorageShape` and `ge.runtime.StorageFormat`. | 295 | - Shape and format input parameters use `ge.runtime.StorageShape` and `ge.runtime.StorageFormat`. |
| 293 | - dtype uses `ge.graph.DataType`. | 296 | - dtype uses `ge.graph.DataType`. |
| 294 | - Stream and workspace addresses are represented as Python `int`. | 297 | - Stream and workspace addresses are represented as Python `int`. |
| @@ -405,6 +408,7 @@ For the Python external API, refer to `docs/zh/api/graph_engine_api/python/ge/cu | |||
| 405 | | `EagerExecuteOp` | Compatibility Eager execution base class; new implementations may use a plain class | | 408 | | `EagerExecuteOp` | Compatibility Eager execution base class; new implementations may use a plain class | |
| 406 | | `execute` | User-implemented execution entry supporting both legacy and schema-bound forms | | 409 | | `execute` | User-implemented execution entry supporting both legacy and schema-bound forms | |
| 407 | | `EagerOpExecutionContext` | Execution context borrowed view | | 410 | | `EagerOpExecutionContext` | Execution context borrowed view | |
| 411 | +| `InferShapeContext` | Input metadata access context for `infer_meta` callbacks | | ||
| 408 | | `RuntimeAttrs` | Attribute borrowed view returned by `EagerOpExecutionContext.get_attrs()` | | 412 | | `RuntimeAttrs` | Attribute borrowed view returned by `EagerOpExecutionContext.get_attrs()` | |
| 409 | | `get_execute_ctx` | Obtains the execution context of the active schema-bound callback | | 413 | | `get_execute_ctx` | Obtains the execution context of the active schema-bound callback | |
| 410 | | `register_op` | Declares and collects a Python custom operator prototype | | 414 | | `register_op` | Declares and collects a Python custom operator prototype | |
| @@ -741,11 +741,11 @@ custom_op/ | |||
| 741 | └── native_bindings/ # _ge_custom_op_native.so 的 pybind11 绑定实现 | 741 | └── native_bindings/ # _ge_custom_op_native.so 的 pybind11 绑定实现 |
| 742 | ``` | 742 | ``` |
| 743 | 注:下划线开头的为 Python 风格下的对内模块。 | 743 | 注:下划线开头的为 Python 风格下的对内模块。 |
| 744 | -注:`EagerOpExecutionContext` 和 `AnnotatedArgsContext` 由 `_ge_custom_op_native.so` 提供 native-backed 实现;执行期返回或接收的 `Tensor`、`StorageShape`、`StorageFormat`、`Shape`、`TensorPlacement` 等运行时数据结构由 `ge.runtime` 模块提供。 | 744 | +注:`EagerOpExecutionContext`、`AnnotatedArgsContext` 和 `InferShapeContext` 由 `_ge_custom_op_native.so` 提供 native-backed 实现;执行期或 `infer_meta` 回调中返回、接收的 `Tensor`、`TensorDesc`、`StorageShape`、`StorageFormat`、`Shape`、`TensorPlacement` 等运行时数据结构由 `ge.runtime` 模块提供。 |
| 745 | 745 | ||
| 746 | #### 模块定位 | 746 | #### 模块定位 |
| 747 | 747 | ||
| 748 | -Python 自定义算子的长期目标是支持用户使用 Python 描述自定义算子原型,并实现自定义算子的各类能力。当前通过反射实现类上的可调用 `execute` 和 `declare_launch_args` 方法,分别识别执行能力和静态图声明式地址刷新能力,不要求用户类继承 `BaseCustomOp` 或 `EagerExecuteOp`;已有继承写法继续兼容。执行入口同时支持 `execute(ctx)` 兼容形式和按照 canonical IR 输入、属性顺序绑定的 schema-bound 形式。当前阶段还将 Python 原型注册到 `OperatorFactory`,但不调用 Python `infer_meta`,也不提供编译期或 RT2 Meta 推导。 | 748 | +Python 自定义算子的长期目标是支持用户使用 Python 描述自定义算子原型,并实现自定义算子的各类能力。当前通过反射实现类上的可调用 `execute` 和 `declare_launch_args` 方法,分别识别执行能力和静态图声明式地址刷新能力,不要求用户类继承 `BaseCustomOp` 或 `EagerExecuteOp`;已有继承写法继续兼容。执行入口同时支持 `execute(ctx)` 兼容形式和按照 canonical IR 输入、属性顺序绑定的 schema-bound 形式。Python 原型通过 `register_op` 注册到 `OperatorFactory` 后,编译期和 RT2 动态 Shape 路径会调用同一 Python `infer_meta` 回调:编译期回写输出 shape、dtype 和 origin dtype,RT2 运行期只回写输出 shape。 |
| 749 | 749 | ||
| 750 | #### 运行时 native artifact 选择 | 750 | #### 运行时 native artifact 选择 |
| 751 | 751 | ||
| @@ -875,7 +875,7 @@ class AnnotatedAddCustom: | |||
| 875 | #### 注册与发现 | 875 | #### 注册与发现 |
| 876 | 876 | ||
| 877 | **装饰器**: | 877 | **装饰器**: |
| 878 | -- `register_op(op_type, mutates_args=())` - 根据被装饰函数的类型标注声明并收集 Python 自定义算子原型 | 878 | +- `register_op(op_type, mutates_args=())` - 根据被装饰函数的类型标注声明并收集 Python 自定义算子原型;被装饰函数同时作为 `infer_meta` 回调,返回输出 `TensorDesc` |
| 879 | - `register_op_impl(op_type)` - 注册 Python 实现类,并反射其可调用方法生成能力列表;`execute` 对应 `eager_execute`,`declare_launch_args` 对应 `annotated_args` | 879 | - `register_op_impl(op_type)` - 注册 Python 实现类,并反射其可调用方法生成能力列表;`execute` 对应 `eager_execute`,`declare_launch_args` 对应 `annotated_args` |
| 880 | 880 | ||
| 881 | **发现机制**: | 881 | **发现机制**: |
| @@ -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 推导能力。当前阶段已经实现 Python 原型 creator、Adapter 注册事务和所有权管理,但尚不调用 Python `infer_meta`。 | 22 | +V2 在 V1 执行能力的基础上扩展 Python 原型和 Meta 推导能力。当前阶段已经实现 Python 原型 creator、Adapter 注册事务和所有权管理,并打通编译期与 RT2 动态 Shape 的 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 | ||
| @@ -67,7 +67,7 @@ Python custom op 是 GE Python 体系的一部分,与 Python pass 共享以下 | |||
| 67 | |------|------|------| | 67 | |------|------|------| |
| 68 | | Python API | `api/python/ge/ge/custom_op/` | 实现方法反射、注册实现、兼容基类、插件发现、bridge helper | | 68 | | Python API | `api/python/ge/ge/custom_op/` | 实现方法反射、注册实现、兼容基类、插件发现、bridge helper | |
| 69 | | Runtime types | `api/python/ge/ge/runtime/` | `Tensor`、`StorageShape`、`StorageFormat` 等运行时数据结构 | | 69 | | Runtime types | `api/python/ge/ge/runtime/` | `Tensor`、`StorageShape`、`StorageFormat` 等运行时数据结构 | |
| 70 | -| Native context | `api/python/ge/ge/custom_op/native_bindings/` | `_ge_custom_op_native`,绑定 Eager 和 AnnotatedArgs context、参数 builder 及 `RuntimeAttrs` | | 70 | +| Native context | `api/python/ge/ge/custom_op/native_bindings/` | `_ge_custom_op_native`,绑定 Eager、AnnotatedArgs、InferShape context、参数 builder 及 `RuntimeAttrs` | |
| 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` | |
| @@ -81,7 +81,7 @@ V1 功能包括: | |||
| 81 | 81 | ||
| 82 | - `@register_op_impl(op_type=...)` 注册 Python 自定义算子实现。 | 82 | - `@register_op_impl(op_type=...)` 注册 Python 自定义算子实现。 |
| 83 | - `@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`。 | 84 | +- bridge 将 Python 原型同步注册为 `OperatorFactory` creator,并从生效 creator 收集 canonical IR;编译期和 RT2 通过统一 callback 调用 `infer_meta`。 |
| 85 | - `register_op_impl` 反射实现类上的可调用 `execute`、`declare_launch_args` 方法并声明对应能力,不要求继承 `BaseCustomOp`、`EagerExecuteOp` 或 `AnnotatedArgsOp`。 | 85 | - `register_op_impl` 反射实现类上的可调用 `execute`、`declare_launch_args` 方法并声明对应能力,不要求继承 `BaseCustomOp`、`EagerExecuteOp` 或 `AnnotatedArgsOp`。 |
| 86 | - `execute(self, ctx)` 兼容形式直接接收 `EagerOpExecutionContext`;schema-bound 形式接收按 canonical IR 组装的输入和属性。 | 86 | - `execute(self, ctx)` 兼容形式直接接收 `EagerOpExecutionContext`;schema-bound 形式接收按 canonical IR 组装的输入和属性。 |
| 87 | - `EagerOpExecutionContext` 支持输入输出 tensor 查询、动态输入实例数、运行时属性读取、输出/工作区分配和 stream 获取。 | 87 | - `EagerOpExecutionContext` 支持输入输出 tensor 查询、动态输入实例数、运行时属性读取、输出/工作区分配和 stream 获取。 |
| @@ -108,7 +108,7 @@ V1 功能包括: | |||
| 108 | 108 | ||
| 109 | - GE / ATC / Executor 入口在 `LoadCustomOps()` 前调用 `GePythonRuntimeManager::EnsureReady()`,解释器初始化失败按现有入口策略告警继续。 | 109 | - GE / ATC / Executor 入口在 `LoadCustomOps()` 前调用 `GePythonRuntimeManager::EnsureReady()`,解释器初始化失败按现有入口策略告警继续。 |
| 110 | - `ASCEND_CUSTOM_OPP_PATH` 中的 Python 入口是 `.py` 文件,或目录下一层非 `_` 开头 `.py` 文件,或带 `__init__.py` 的包目录。 | 110 | - `ASCEND_CUSTOM_OPP_PATH` 中的 Python 入口是 `.py` 文件,或目录下一层非 `_` 开头 `.py` 文件,或带 `__init__.py` 的包目录。 |
| 111 | -- V1 阶段算子原型、shape/dtype 推导等仍由用户按现有 C++ / OPP 方式提供。 | 111 | +- 对未使用 Python `register_op` 的算子,V1 兼容路径中的算子原型和 shape/dtype 推导仍由用户按现有 C++ / OPP 方式提供;Python `register_op` 算子使用本 V2 的 `infer_meta` 路径。 |
| 112 | - Python custom op 样例依赖 ACL Python runtime 和与 run 包匹配的 Python 环境。 | 112 | - Python custom op 样例依赖 ACL Python runtime 和与 run 包匹配的 Python 环境。 |
| 113 | - `declare_launch_args` 仅在编译期依赖 Python 注册环境。新 OM 通过 `_custom_task_args_mode` 保存最终选择的刷新方式,并通过 `args_format` 保存 launch 布局;静态模型加载时以显式模式为准,不需要再次加载 Python 实现。没有该属性的旧 OM 保留 registry 查询和 `args_format` 兼容兜底。 | 113 | - `declare_launch_args` 仅在编译期依赖 Python 注册环境。新 OM 通过 `_custom_task_args_mode` 保存最终选择的刷新方式,并通过 `args_format` 保存 launch 布局;静态模型加载时以显式模式为准,不需要再次加载 Python 实现。没有该属性的旧 OM 保留 registry 查询和 `args_format` 兼容兜底。 |
| 114 | 114 | ||
| @@ -251,7 +251,7 @@ Python custom op 使用 `@register_op_impl(op_type=...)` 装饰器注册实现 | |||
| 251 | 251 | ||
| 252 | **介绍** | 252 | **介绍** |
| 253 | 253 | ||
| 254 | -`_ge_custom_op_native` 绑定 `EagerOpExecutionContext`、`AnnotatedArgsContext`、`AnnotatedKernelArgs`、`AnnotatedKernelLaunchInfo` 和 `RuntimeAttrs`。context 方法返回的 `Tensor`、`StorageShape`、`StorageFormat` 等类型由 `ge.runtime` 提供。 | 254 | +`_ge_custom_op_native` 绑定 `EagerOpExecutionContext`、`AnnotatedArgsContext`、`InferShapeContext`、`AnnotatedKernelArgs`、`AnnotatedKernelLaunchInfo` 和 `RuntimeAttrs`。context 方法返回的 `Tensor`、`TensorDesc`、`StorageShape`、`StorageFormat` 等类型由 `ge.runtime` 提供。 |
| 255 | 255 | ||
| 256 | **输入** | 256 | **输入** |
| 257 | 257 | ||
| @@ -276,6 +276,8 @@ Python custom op 使用 `@register_op_impl(op_type=...)` 装饰器注册实现 | |||
| 276 | | `get_output_tensor(index)` | 获取 index 指定的输出 `Tensor` | | 276 | | `get_output_tensor(index)` | 获取 index 指定的输出 `Tensor` | |
| 277 | | `get_stream()` | 获取所属执行流地址整数 | | 277 | | `get_stream()` | 获取所属执行流地址整数 | |
| 278 | 278 | ||
| 279 | +`InferShapeContext` 供 `register_op` 装饰函数执行 `infer_meta` 时使用:读取 required、optional、dynamic 输入的 shape 和 data type,读取 typed runtime attrs,并查询 dynamic output 实例数。该 context 只在当前 `infer_meta` 回调内有效;输出 shape 和 data type 由 `infer_meta` 返回的 `TensorDesc` 统一承载。 | ||
| 280 | + | ||
| 279 | `AnnotatedArgsContext` 暴露 workspace 申请、stream id 查询、kernel 参数 builder 创建和 launch 添加能力;输入输出 tensor 与属性查询由内部 schema-bound 组装逻辑使用。`AnnotatedKernelArgs` 暴露 `append_input`、`append_output`、`append_workspace` 和 `append_scalar`。 | 281 | `AnnotatedArgsContext` 暴露 workspace 申请、stream id 查询、kernel 参数 builder 创建和 launch 添加能力;输入输出 tensor 与属性查询由内部 schema-bound 组装逻辑使用。`AnnotatedKernelArgs` 暴露 `append_input`、`append_output`、`append_workspace` 和 `append_scalar`。 |
| 280 | 282 | ||
| 281 | `RuntimeAttrs` 按属性 IR index 提供以下 typed reader: | 283 | `RuntimeAttrs` 按属性 IR index 提供以下 typed reader: |
| @@ -289,6 +291,7 @@ Python custom op 使用 `@register_op_impl(op_type=...)` 装饰器注册实现 | |||
| 289 | **输出** | 291 | **输出** |
| 290 | 292 | ||
| 291 | - tensor 相关方法返回 `ge.runtime.Tensor`。 | 293 | - tensor 相关方法返回 `ge.runtime.Tensor`。 |
| 294 | +- `infer_meta` 的输入和返回值使用 `ge.runtime.TensorDesc`;`TensorDesc.shape` 使用 `StorageShape`,`TensorDesc.data_type` 使用 `ge.graph.DataType`。 | ||
| 292 | - shape/format 入参使用 `ge.runtime.StorageShape`、`ge.runtime.StorageFormat`。 | 295 | - shape/format 入参使用 `ge.runtime.StorageShape`、`ge.runtime.StorageFormat`。 |
| 293 | - dtype 使用 `ge.graph.DataType`。 | 296 | - dtype 使用 `ge.graph.DataType`。 |
| 294 | - stream、workspace 地址以 Python `int` 表示。 | 297 | - stream、workspace 地址以 Python `int` 表示。 |
| @@ -405,6 +408,7 @@ Python 对外 API 见 `docs/zh/api/graph_engine_api/python/ge/custom_op/`。当 | |||
| 405 | | `EagerExecuteOp` | 兼容已有实现的 Eager 执行基类,新实现可使用普通 class | | 408 | | `EagerExecuteOp` | 兼容已有实现的 Eager 执行基类,新实现可使用普通 class | |
| 406 | | `execute` | 用户实现的执行入口,支持 legacy 和 schema-bound 两种形式 | | 409 | | `execute` | 用户实现的执行入口,支持 legacy 和 schema-bound 两种形式 | |
| 407 | | `EagerOpExecutionContext` | 执行上下文 borrowed view | | 410 | | `EagerOpExecutionContext` | 执行上下文 borrowed view | |
| 411 | +| `InferShapeContext` | `infer_meta` 回调的输入元信息读取上下文 | | ||
| 408 | | `RuntimeAttrs` | `EagerOpExecutionContext.get_attrs()` 返回的属性 borrowed view | | 412 | | `RuntimeAttrs` | `EagerOpExecutionContext.get_attrs()` 返回的属性 borrowed view | |
| 409 | | `get_execute_ctx` | 获取当前 schema-bound 回调的执行上下文 | | 413 | | `get_execute_ctx` | 获取当前 schema-bound 回调的执行上下文 | |
| 410 | | `register_op` | 声明并收集 Python 自定义算子原型 | | 414 | | `register_op` | 声明并收集 Python 自定义算子原型 | |
| @@ -635,7 +639,9 @@ PythonCustomOpAdapter::DeclareLaunchArgs(ctx) | |||
| 635 | | 功能 | descriptor 加载阶段的 schema-bound `declare_launch_args` 签名校验,以及 runtime 输入输出/属性组装、返回值校验、实例平铺 index 及 builder 消费语义 | Python pytest fake/native context | UT | | 639 | | 功能 | descriptor 加载阶段的 schema-bound `declare_launch_args` 签名校验,以及 runtime 输入输出/属性组装、返回值校验、实例平铺 index 及 builder 消费语义 | Python pytest fake/native context | UT | |
| 636 | | 功能 | `get_execute_ctx()` / `get_declare_launch_args_ctx()` 回调内访问、异常清理和嵌套调用恢复 | Python pytest | UT | | 640 | | 功能 | `get_execute_ctx()` / `get_declare_launch_args_ctx()` 回调内访问、异常清理和嵌套调用恢复 | Python pytest | UT | |
| 637 | | 功能 | bridge descriptor 获取、holder 创建/销毁、不可调用方法拦截和 context 失效 | Python pytest | UT | | 641 | | 功能 | bridge descriptor 获取、holder 创建/销毁、不可调用方法拦截和 context 失效 | Python pytest | UT | |
| 638 | -| 功能 | canonical IR 查询缓存、bridge ABI v1 和 adapter execute/declare 转发 | C++ gtest | UT | | 642 | +| 功能 | canonical IR 查询缓存、bridge ABI v1 和 adapter execute/declare/infer-meta 转发 | C++ gtest | UT | |
| 643 | +| 功能 | 编译期第 N 个输出失败、返回数量不匹配时原始输出元信息不变,以及 dynamic/required 多输出完整提交 | Graph Metadef gtest | UT | | ||
| 644 | +| 功能 | Python infer-meta 使用真实 RT2 InferShape kernel,并通过 native RuntimeAttrs 读取 12 类属性 | C++/Python 联合回调 | ST | | ||
| 639 | | 功能 | capability bitmask 和 `CustomOpCast<T>()` 行为 | C++ gtest | UT | | 645 | | 功能 | capability bitmask 和 `CustomOpCast<T>()` 行为 | C++ gtest | UT | |
| 640 | | 功能 | loader 在无 Python 入口时跳过,有入口时加载 bridge | C++ gtest / stub | UT | | 646 | | 功能 | loader 在无 Python 入口时跳过,有入口时加载 bridge | C++ gtest / stub | UT | |
| 641 | | 兼容性 | C++ custom op 裸能力继承仍可正常 cast | C++ gtest | UT | | 647 | | 兼容性 | C++ custom op 裸能力继承仍可正常 cast | C++ gtest | UT | |
| @@ -58,6 +58,7 @@ IsInferFormatV2RegisteredFunc OperatorFactoryImpl::is_infer_format_v2_registered | |||
| 58 | IsInferShapeV2RegisteredFunc OperatorFactoryImpl::is_infer_shape_v2_registered_func_ = nullptr; | 58 | IsInferShapeV2RegisteredFunc OperatorFactoryImpl::is_infer_shape_v2_registered_func_ = nullptr; |
| 59 | CustomOpInferShapeFunc OperatorFactoryImpl::custom_op_infer_shape_func_ = nullptr; | 59 | CustomOpInferShapeFunc OperatorFactoryImpl::custom_op_infer_shape_func_ = nullptr; |
| 60 | CustomOpInferDataTypeFunc OperatorFactoryImpl::custom_op_infer_datatype_func_ = nullptr; | 60 | CustomOpInferDataTypeFunc OperatorFactoryImpl::custom_op_infer_datatype_func_ = nullptr; |
| 61 | +CustomOpInferMetaFunc OperatorFactoryImpl::custom_op_infer_meta_func_ = nullptr; | ||
| 61 | 62 | ||
| 62 | Operator OperatorFactoryImpl::CreateOperator(const std::string &operator_name, const std::string &operator_type) { | 63 | Operator OperatorFactoryImpl::CreateOperator(const std::string &operator_name, const std::string &operator_type) { |
| 63 | if (operator_creators_v2_ != nullptr) { | 64 | if (operator_creators_v2_ != nullptr) { |
| @@ -458,6 +459,17 @@ CustomOpInferDataTypeFunc OperatorFactoryImpl::GetCustomOpInferDataTypeFunc() { | |||
| 458 | return custom_op_infer_datatype_func_; | 459 | return custom_op_infer_datatype_func_; |
| 459 | } | 460 | } |
| 460 | 461 | ||
| 462 | +void OperatorFactoryImpl::RegisterCustomOpInferMetaFunc(CustomOpInferMetaFunc const custom_op_infer_meta_func) { | ||
| 463 | + if (custom_op_infer_meta_func_ == nullptr) { | ||
| 464 | + GELOGI("operator custom op infer meta func init"); | ||
| 465 | + custom_op_infer_meta_func_ = custom_op_infer_meta_func; | ||
| 466 | + } | ||
| 467 | +} | ||
| 468 | + | ||
| 469 | +CustomOpInferMetaFunc OperatorFactoryImpl::GetCustomOpInferMetaFunc() { | ||
| 470 | + return custom_op_infer_meta_func_; | ||
| 471 | +} | ||
| 472 | + | ||
| 461 | void OperatorFactoryImpl::ReleaseRegInfo() { | 473 | void OperatorFactoryImpl::ReleaseRegInfo() { |
| 462 | ReleaseOpsRegInfo(); | 474 | ReleaseOpsRegInfo(); |
| 463 | } | 475 | } |
| @@ -27,6 +27,7 @@ | |||
| 27 | 27 | ||
| 28 | 28 | ||
| 29 | 29 | ||
| 30 | + | ||
| 30 | 31 | ||
| 31 | 32 | ||
| 32 | namespace ge { | 33 | namespace ge { |
| @@ -116,6 +117,13 @@ graphStatus OpDescUtilsEx::InferCustomOpShape(const OpDescPtr &op_desc, Operator | |||
| 116 | GELOGI("[%s][%s] Infer Custom op shape.", op_desc->GetNamePtr(), op_desc->GetTypePtr()); | 117 | GELOGI("[%s][%s] Infer Custom op shape.", op_desc->GetNamePtr(), op_desc->GetTypePtr()); |
| 117 | 118 | ||
| 118 | auto custom_op = CustomOpFactory::CreateOrGetCustomOp(AscendString(op_desc->GetType().c_str())); | 119 | auto custom_op = CustomOpFactory::CreateOrGetCustomOp(AscendString(op_desc->GetType().c_str())); |
| 120 | + auto *infer_meta_provider = CustomOpCast<CustomOpInferMetaProvider>(custom_op); | ||
| 121 | + if (infer_meta_provider != nullptr) { | ||
| 122 | + const auto custom_op_infer_meta_func = OperatorFactoryImpl::GetCustomOpInferMetaFunc(); | ||
| 123 | + GE_ASSERT_NOTNULL(custom_op_infer_meta_func); | ||
| 124 | + return custom_op_infer_meta_func(op, op_desc.get(), infer_meta_provider); | ||
| 125 | + } | ||
| 126 | + | ||
| 119 | auto shape_infer_op = CustomOpCast<ShapeInferOp>(custom_op); | 127 | auto shape_infer_op = CustomOpCast<ShapeInferOp>(custom_op); |
| 120 | if (shape_infer_op != nullptr) { | 128 | if (shape_infer_op != nullptr) { |
| 121 | const auto custom_op_infer_datatype_func = OperatorFactoryImpl::GetCustomOpInferDataTypeFunc(); | 129 | const auto custom_op_infer_datatype_func = OperatorFactoryImpl::GetCustomOpInferDataTypeFunc(); |
| @@ -20,7 +20,7 @@ | |||
| 20 | 20 | ||
| 21 | 21 | ||
| 22 | 22 | ||
| 23 | -#include "graph/custom_op.h" | 23 | +#include "graph/custom_op/infer_meta.h" |
| 24 | 24 | ||
| 25 | namespace gert { | 25 | namespace gert { |
| 26 | namespace { | 26 | namespace { |
| @@ -311,15 +311,19 @@ ge::graphStatus ConstructInferShapeRangeContextOutputs( | |||
| 311 | return ge::GRAPH_SUCCESS; | 311 | return ge::GRAPH_SUCCESS; |
| 312 | } | 312 | } |
| 313 | 313 | ||
| 314 | +void UpdateGeShape(const gert::Shape &src, ge::GeShape &dst) { | ||
| 315 | + dst.SetDimNum(src.GetDimNum()); | ||
| 316 | + for (size_t dim = 0UL; dim < src.GetDimNum(); ++dim) { | ||
| 317 | + (void)dst.SetDim(dim, src.GetDim(dim)); | ||
| 318 | + } | ||
| 319 | +} | ||
| 320 | + | ||
| 314 | ge::graphStatus UpdateOpDescOutShape(const ge::OpDescPtr &op_desc, gert::InferShapeContext *infer_shape_ctx) { | 321 | ge::graphStatus UpdateOpDescOutShape(const ge::OpDescPtr &op_desc, gert::InferShapeContext *infer_shape_ctx) { |
| 315 | for (size_t index = 0UL; index < op_desc->GetOutputsSize(); index++) { | 322 | for (size_t index = 0UL; index < op_desc->GetOutputsSize(); index++) { |
| 316 | auto &dst_out_shape = op_desc->MutableOutputDesc(static_cast<uint32_t>(index))->MutableShape(); | 323 | auto &dst_out_shape = op_desc->MutableOutputDesc(static_cast<uint32_t>(index))->MutableShape(); |
| 317 | const auto *shape = infer_shape_ctx->GetOutputShape(index); | 324 | const auto *shape = infer_shape_ctx->GetOutputShape(index); |
| 318 | GE_ASSERT_NOTNULL(shape); | 325 | GE_ASSERT_NOTNULL(shape); |
| 319 | - dst_out_shape.SetDimNum(shape->GetDimNum()); | 326 | + UpdateGeShape(*shape, dst_out_shape); |
| 320 | - for (size_t dim = 0UL; dim < shape->GetDimNum(); dim++) { | ||
| 321 | - (void)dst_out_shape.SetDim(dim, shape->GetDim(dim)); | ||
| 322 | - } | ||
| 323 | op_desc->MutableOutputDesc(static_cast<uint32_t>(index))->SetOriginShape(dst_out_shape); | 327 | op_desc->MutableOutputDesc(static_cast<uint32_t>(index))->SetOriginShape(dst_out_shape); |
| 324 | } | 328 | } |
| 325 | return ge::GRAPH_SUCCESS; | 329 | return ge::GRAPH_SUCCESS; |
| @@ -673,6 +677,37 @@ ge::graphStatus CustomOpInferDataTypeOnCompile(ge::ShapeInferOp *shape_infer_op, | |||
| 673 | } | 677 | } |
| 674 | return ge::GRAPH_SUCCESS; | 678 | return ge::GRAPH_SUCCESS; |
| 675 | } | 679 | } |
| 680 | + | ||
| 681 | +ge::graphStatus CommitCustomOpInferMetaResult(const ge::OpDescPtr &staged_op_desc, ge::OpDesc *op_desc, | ||
| 682 | + ge::NodeShapeTransUtils &transformer, | ||
| 683 | + const ge::CustomOpInferMetaResult &infer_meta_result) { | ||
| 684 | + if (infer_meta_result.outputs.size() != staged_op_desc->GetOutputsSize()) { | ||
| 685 | + GELOGE(ge::GRAPH_FAILED, "Custom op[%s] infer_meta result count[%zu] does not match output count[%zu].", | ||
| 686 | + op_desc->GetName().c_str(), infer_meta_result.outputs.size(), op_desc->GetOutputsSize()); | ||
| 687 | + return ge::GRAPH_FAILED; | ||
| 688 | + } | ||
| 689 | + for (size_t i = 0UL; i < staged_op_desc->GetOutputsSize(); ++i) { | ||
| 690 | + const auto &output_meta = infer_meta_result.outputs[i]; | ||
| 691 | + auto out_desc = staged_op_desc->MutableOutputDesc(static_cast<uint32_t>(i)); | ||
| 692 | + auto &shape = out_desc->MutableShape(); | ||
| 693 | + UpdateGeShape(output_meta.shape.GetStorageShape(), shape); | ||
| 694 | + ge::GeShape origin_shape; | ||
| 695 | + UpdateGeShape(output_meta.shape.GetOriginShape(), origin_shape); | ||
| 696 | + out_desc->SetOriginShape(origin_shape); | ||
| 697 | + out_desc->SetDataType(output_meta.data_type); | ||
| 698 | + out_desc->SetOriginDataType(output_meta.data_type); | ||
| 699 | + } | ||
| 700 | + GE_CHK_BOOL_RET_STATUS(transformer.UpdateFormatAndShape(), ge::GRAPH_FAILED, | ||
| 701 | + "Failed to update format and shape for %s", op_desc->GetNamePtr()); | ||
| 702 | + for (size_t i = 0UL; i < staged_op_desc->GetOutputsSize(); ++i) { | ||
| 703 | + const auto staged_output = staged_op_desc->GetOutputDescPtr(static_cast<uint32_t>(i)); | ||
| 704 | + auto output = op_desc->MutableOutputDesc(static_cast<uint32_t>(i)); | ||
| 705 | + GE_ASSERT_NOTNULL(staged_output); | ||
| 706 | + GE_ASSERT_NOTNULL(output); | ||
| 707 | + *output = *staged_output; | ||
| 708 | + } | ||
| 709 | + return ge::GRAPH_SUCCESS; | ||
| 710 | +} | ||
| 676 | } // namespace | 711 | } // namespace |
| 677 | 712 | ||
| 678 | ge::graphStatus InferShapeRangeOnCompile(const ge::Operator &op, const ge::OpDescPtr &op_desc) { | 713 | ge::graphStatus InferShapeRangeOnCompile(const ge::Operator &op, const ge::OpDescPtr &op_desc) { |
| @@ -701,6 +736,36 @@ ge::graphStatus InferShapeRangeOnCompile(const ge::Operator &op, const ge::OpDes | |||
| 701 | return ge::GRAPH_SUCCESS; | 736 | return ge::GRAPH_SUCCESS; |
| 702 | } | 737 | } |
| 703 | 738 | ||
| 739 | +ge::graphStatus CustomOpInferMetaOnCompile(const ge::Operator &op, ge::OpDesc *op_desc, | ||
| 740 | + ge::CustomOpInferMetaProvider *infer_meta_provider) { | ||
| 741 | + GE_ASSERT_NOTNULL(op_desc); | ||
| 742 | + GE_ASSERT_NOTNULL(infer_meta_provider); | ||
| 743 | + const auto op_desc_ptr = op_desc->shared_from_this(); | ||
| 744 | + const auto staged_op_desc = ge::ComGraphMakeShared<ge::OpDesc>(*op_desc_ptr); | ||
| 745 | + GE_ASSERT_NOTNULL(staged_op_desc, "Failed to create staged op desc for %s", op_desc->GetName().c_str()); | ||
| 746 | + ge::NodeShapeTransUtils transformer(staged_op_desc); | ||
| 747 | + std::vector<std::unique_ptr<uint8_t[]>> inputs_holder; | ||
| 748 | + std::vector<std::unique_ptr<uint8_t[]>> outputs_holder; | ||
| 749 | + std::vector<std::unique_ptr<ge::Tensor>> ge_tensors_holder; | ||
| 750 | + auto ret = PrepareInferShapeCompileContext(op, staged_op_desc, transformer, inputs_holder, ge_tensors_holder); | ||
| 751 | + if (ret == ge::GRAPH_PARAM_INVALID) { | ||
| 752 | + return ret; | ||
| 753 | + } | ||
| 754 | + GE_ASSERT_GRAPH_SUCCESS(ret, "[Construct][InferMetaContextInputs] failed, op_desc[%s]", op_desc->GetName().c_str()); | ||
| 755 | + GE_ASSERT_GRAPH_SUCCESS(ConstructCompileKernelContextOutputs(staged_op_desc, outputs_holder), | ||
| 756 | + "[Construct][InferMetaContextOutputs] failed, op_desc[%s]", op_desc->GetName().c_str()); | ||
| 757 | + const auto kernel_context_holder = gert::KernelRunContextBuilder() | ||
| 758 | + .Inputs(GetInputs(op, inputs_holder)) | ||
| 759 | + .Outputs(GetOutputs(outputs_holder)) | ||
| 760 | + .Build(staged_op_desc); | ||
| 761 | + auto infer_shape_ctx = reinterpret_cast<gert::InferShapeContext *>(kernel_context_holder.context_); | ||
| 762 | + | ||
| 763 | + ge::CustomOpInferMetaResult infer_meta_result; | ||
| 764 | + ret = infer_meta_provider->InferMeta(infer_shape_ctx, &infer_meta_result); | ||
| 765 | + GE_CHK_STATUS_RET(ret, "[Call][CustomOpInferMeta] failed, op_desc[%s], ret[%d]", op_desc->GetName().c_str(), ret); | ||
| 766 | + return CommitCustomOpInferMetaResult(staged_op_desc, op_desc, transformer, infer_meta_result); | ||
| 767 | +} | ||
| 768 | + | ||
| 704 | ge::graphStatus InferShapeOnCompile(const ge::Operator &op, const ge::OpDescPtr &op_desc) { | 769 | ge::graphStatus InferShapeOnCompile(const ge::Operator &op, const ge::OpDescPtr &op_desc) { |
| 705 | const auto *const space_registry = | 770 | const auto *const space_registry = |
| 706 | DefaultOpImplSpaceRegistryV2::GetInstance() | 771 | DefaultOpImplSpaceRegistryV2::GetInstance() |
| @@ -867,6 +932,7 @@ class CompileAdaptFunctionsRegister { | |||
| 867 | (void)ge::OperatorFactoryImpl::RegisterIsInferShapeV2RegisteredFunc(&gert::IsInferShapeV2Registered); | 932 | (void)ge::OperatorFactoryImpl::RegisterIsInferShapeV2RegisteredFunc(&gert::IsInferShapeV2Registered); |
| 868 | (void)ge::OperatorFactoryImpl::RegisterCustomOpInferShapeFunc(&CustomOpInferShapeOnCompile); | 933 | (void)ge::OperatorFactoryImpl::RegisterCustomOpInferShapeFunc(&CustomOpInferShapeOnCompile); |
| 869 | (void)ge::OperatorFactoryImpl::RegisterCustomOpInferDataTypeFunc(&CustomOpInferDataTypeOnCompile); | 934 | (void)ge::OperatorFactoryImpl::RegisterCustomOpInferDataTypeFunc(&CustomOpInferDataTypeOnCompile); |
| 935 | + (void)ge::OperatorFactoryImpl::RegisterCustomOpInferMetaFunc(&CustomOpInferMetaOnCompile); | ||
| 870 | } | 936 | } |
| 871 | }; | 937 | }; |
| 872 | static CompileAdaptFunctionsRegister VAR_UNUSED g_register_adapt_funcs; | 938 | static CompileAdaptFunctionsRegister VAR_UNUSED g_register_adapt_funcs; |
| @@ -21,6 +21,7 @@ enum class CustomOpCapability : uint32_t { | |||
| 21 | kPortable = 1U << 3U, | 21 | kPortable = 1U << 3U, |
| 22 | kArgsUpdater = 1U << 4U, | 22 | kArgsUpdater = 1U << 4U, |
| 23 | kAnnotatedArgs = 1U << 5U, | 23 | kAnnotatedArgs = 1U << 5U, |
| 24 | + kInferMeta = 1U << 6U, | ||
| 24 | }; | 25 | }; |
| 25 | 26 | ||
| 26 | using CustomOpCapabilityMask = uint32_t; | 27 | using CustomOpCapabilityMask = uint32_t; |
| @@ -13,6 +13,7 @@ | |||
| 13 | 13 | ||
| 14 | 14 | ||
| 15 | 15 | ||
| 16 | + | ||
| 16 | 17 | ||
| 17 | namespace ge { | 18 | namespace ge { |
| 18 | class CustomOpCapabilityProvider { | 19 | class CustomOpCapabilityProvider { |
| @@ -39,6 +40,11 @@ struct CustomOpCapabilityTrait<ShapeInferOp> { | |||
| 39 | static constexpr CustomOpCapability kCapability = CustomOpCapability::kShapeInfer; | 40 | static constexpr CustomOpCapability kCapability = CustomOpCapability::kShapeInfer; |
| 40 | }; | 41 | }; |
| 41 | 42 | ||
| 43 | +template <> | ||
| 44 | +struct CustomOpCapabilityTrait<CustomOpInferMetaProvider> { | ||
| 45 | + static constexpr CustomOpCapability kCapability = CustomOpCapability::kInferMeta; | ||
| 46 | +}; | ||
| 47 | + | ||
| 42 | template <> | 48 | template <> |
| 43 | struct CustomOpCapabilityTrait<PortableOp> { | 49 | struct CustomOpCapabilityTrait<PortableOp> { |
| 44 | static constexpr CustomOpCapability kCapability = CustomOpCapability::kPortable; | 50 | static constexpr CustomOpCapability kCapability = CustomOpCapability::kPortable; |
| @@ -0,0 +1,53 @@ | |||
| 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 | +struct CustomOpInferMetaOutput { | ||
| 24 | + gert::StorageShape shape; | ||
| 25 | + ge::DataType data_type{ge::DT_UNDEFINED}; | ||
| 26 | +}; | ||
| 27 | + | ||
| 28 | +struct CustomOpInferMetaResult { | ||
| 29 | + std::vector<CustomOpInferMetaOutput> outputs; | ||
| 30 | +}; | ||
| 31 | + | ||
| 32 | +/** | ||
| 33 | + * Python 自定义算子的 infer_meta 窄接口。 | ||
| 34 | + * PythonCustomOpAdapter 显式继承本接口,编译期通过 | ||
| 35 | + * dynamic_cast<CustomOpInferMetaProvider *> 识别 Python infer 路径,能力由 kInferMeta 标记,RT2 复用 | ||
| 36 | + * ShapeInferOp::InferShape。 | ||
| 37 | + * 普通 C++ 自定义算子不实现该接口,继续走原有 infer dtype / infer shape 两段调用。 | ||
| 38 | + */ | ||
| 39 | +class CustomOpInferMetaProvider : virtual public BaseCustomOp { | ||
| 40 | + public: | ||
| 41 | + ~CustomOpInferMetaProvider() override = default; | ||
| 42 | + /** | ||
| 43 | + * 一次推导所有 output shape 和 dtype。 | ||
| 44 | + * 调用方负责在校验整体成功后按当前场景提交结果。 | ||
| 45 | + * @param ctx InferShapeContext 借用视图 | ||
| 46 | + * @param result output meta 临时结果,调用方拥有 | ||
| 47 | + * @return GRAPH_SUCCESS 表示成功 | ||
| 48 | + */ | ||
| 49 | + virtual graphStatus InferMeta(gert::InferShapeContext *ctx, CustomOpInferMetaResult *result) = 0; | ||
| 50 | +}; | ||
| 51 | +} // namespace ge | ||
| 52 | + | ||
| 53 | + | ||
| @@ -23,6 +23,7 @@ | |||
| 23 | 23 | ||
| 24 | namespace ge { | 24 | namespace ge { |
| 25 | class ShapeInferOp; | 25 | class ShapeInferOp; |
| 26 | +class CustomOpInferMetaProvider; | ||
| 26 | using InferShapeV2Func = uint32_t (*)(const ge::Operator &op, const OpDescPtr &); | 27 | using InferShapeV2Func = uint32_t (*)(const ge::Operator &op, const OpDescPtr &); |
| 27 | using InferDataTypeFunc = uint32_t (*)(const OpDescPtr &); | 28 | using InferDataTypeFunc = uint32_t (*)(const OpDescPtr &); |
| 28 | using InferShapeRangeFunc = uint32_t (*)(const ge::Operator &op, const OpDescPtr &); | 29 | using InferShapeRangeFunc = uint32_t (*)(const ge::Operator &op, const OpDescPtr &); |
| @@ -31,6 +32,7 @@ using IsInferFormatV2RegisteredFunc = bool (*)(const OpDescPtr &); | |||
| 31 | using IsInferShapeV2RegisteredFunc = bool (*)(const OpDescPtr &); | 32 | using IsInferShapeV2RegisteredFunc = bool (*)(const OpDescPtr &); |
| 32 | using CustomOpInferShapeFunc = uint32_t (*)(ShapeInferOp *, const Operator &, const OpDescPtr &); | 33 | using CustomOpInferShapeFunc = uint32_t (*)(ShapeInferOp *, const Operator &, const OpDescPtr &); |
| 33 | using CustomOpInferDataTypeFunc = uint32_t (*)(ShapeInferOp *, const OpDescPtr &); | 34 | using CustomOpInferDataTypeFunc = uint32_t (*)(ShapeInferOp *, const OpDescPtr &); |
| 35 | +using CustomOpInferMetaFunc = uint32_t (*)(const Operator &, OpDesc *, CustomOpInferMetaProvider *); | ||
| 34 | 36 | ||
| 35 | struct InferValueRangePara { | 37 | struct InferValueRangePara { |
| 36 | public: | 38 | public: |
| @@ -136,6 +138,10 @@ class GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY OperatorFactoryImpl { | |||
| 136 | 138 | ||
| 137 | static CustomOpInferDataTypeFunc GetCustomOpInferDataTypeFunc(); | 139 | static CustomOpInferDataTypeFunc GetCustomOpInferDataTypeFunc(); |
| 138 | 140 | ||
| 141 | + static void RegisterCustomOpInferMetaFunc(CustomOpInferMetaFunc const custom_op_infer_meta_func); | ||
| 142 | + | ||
| 143 | + static CustomOpInferMetaFunc GetCustomOpInferMetaFunc(); | ||
| 144 | + | ||
| 139 | static void ReleaseRegInfo(); | 145 | static void ReleaseRegInfo(); |
| 140 | 146 | ||
| 141 | static void RemoveCustomOpCreators(const std::vector<std::string> &op_types); | 147 | static void RemoveCustomOpCreators(const std::vector<std::string> &op_types); |
| @@ -161,6 +167,7 @@ class GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY OperatorFactoryImpl { | |||
| 161 | static IsInferShapeV2RegisteredFunc is_infer_shape_v2_registered_func_; | 167 | static IsInferShapeV2RegisteredFunc is_infer_shape_v2_registered_func_; |
| 162 | static CustomOpInferShapeFunc custom_op_infer_shape_func_; | 168 | static CustomOpInferShapeFunc custom_op_infer_shape_func_; |
| 163 | static CustomOpInferDataTypeFunc custom_op_infer_datatype_func_; | 169 | static CustomOpInferDataTypeFunc custom_op_infer_datatype_func_; |
| 170 | + static CustomOpInferMetaFunc custom_op_infer_meta_func_; | ||
| 164 | }; | 171 | }; |
| 165 | } // namespace ge | 172 | } // namespace ge |
| 166 | 173 | ||
| @@ -17,19 +17,41 @@ | |||
| 17 | 17 | ||
| 18 | 18 | ||
| 19 | 19 | ||
| 20 | + | ||
| 20 | 21 | ||
| 21 | namespace ge { | 22 | namespace ge { |
| 22 | namespace custom_op { | 23 | namespace custom_op { |
| 23 | namespace { | 24 | namespace { |
| 25 | +graphStatus InvokePythonCustomOpInferMeta(PythonCustomOpInferMetaFn infer_meta, const std::string &op_type, | ||
| 26 | + gert::InferShapeContext *ctx, CustomOpInferMetaResult *result) { | ||
| 27 | + if ((infer_meta == nullptr) || (ctx == nullptr) || (result == nullptr)) { | ||
| 28 | + return GRAPH_FAILED; | ||
| 29 | + } | ||
| 30 | + result->outputs.clear(); | ||
| 31 | + result->outputs.resize(ctx->GetComputeNodeOutputNum()); | ||
| 32 | + std::vector<PythonCustomOpInferMetaOutputView> output_views; | ||
| 33 | + output_views.reserve(result->outputs.size()); | ||
| 34 | + for (auto &output : result->outputs) { | ||
| 35 | + output_views.emplace_back(PythonCustomOpInferMetaOutputView{&output.shape, ge::DT_UNDEFINED}); | ||
| 36 | + } | ||
| 37 | + const PythonCustomOpStringView op_type_view{op_type.data(), op_type.size()}; | ||
| 38 | + PythonCustomOpInferMetaResultView result_view{output_views.data(), output_views.size()}; | ||
| 39 | + const auto ret = infer_meta(&op_type_view, ctx, &result_view); | ||
| 40 | + if (ret != GRAPH_SUCCESS) { | ||
| 41 | + return ret; | ||
| 42 | + } | ||
| 43 | + for (size_t i = 0U; i < output_views.size(); ++i) { | ||
| 44 | + result->outputs[i].data_type = output_views[i].data_type; | ||
| 45 | + } | ||
| 46 | + return GRAPH_SUCCESS; | ||
| 47 | +} | ||
| 48 | + | ||
| 24 | struct PythonCustomOpImplRuntimeEntry { | 49 | struct PythonCustomOpImplRuntimeEntry { |
| 25 | explicit PythonCustomOpImplRuntimeEntry(PythonCustomOpAdapterDescriptor d, PythonCustomOpAdapterCallbacks cb) | 50 | explicit PythonCustomOpImplRuntimeEntry(PythonCustomOpAdapterDescriptor d, PythonCustomOpAdapterCallbacks cb) |
| 26 | : desc(std::move(d)), callbacks(cb) {} | 51 | : desc(std::move(d)), callbacks(cb) {} |
| 27 | 52 | ||
| 28 | PythonCustomOpAdapterDescriptor desc; | 53 | PythonCustomOpAdapterDescriptor desc; |
| 29 | PythonCustomOpAdapterCallbacks callbacks; | 54 | PythonCustomOpAdapterCallbacks callbacks; |
| 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. | ||
| 32 | - // active_adapter_count prevents unregister while callbacks are still in use. | ||
| 33 | size_t active_adapter_count{0U}; | 55 | size_t active_adapter_count{0U}; |
| 34 | std::mutex mutex; | 56 | std::mutex mutex; |
| 35 | }; | 57 | }; |
| @@ -137,6 +159,8 @@ PythonCustomOpImplRuntimeRegistryImpl &GetPythonCustomOpImplRuntimeRegistryImpl( | |||
| 137 | } // namespace | 159 | } // namespace |
| 138 | 160 | ||
| 139 | PythonCustomOpImplHolder::PythonCustomOpImplHolder(const PythonCustomOpAdapterDescriptor &desc) : desc_(desc) { | 161 | PythonCustomOpImplHolder::PythonCustomOpImplHolder(const PythonCustomOpAdapterDescriptor &desc) : desc_(desc) { |
| 162 | + desc_.capabilities &= ~static_cast<CustomOpCapabilityMask>(CustomOpCapability::kShapeInfer); | ||
| 163 | + desc_.capabilities &= ~static_cast<CustomOpCapabilityMask>(CustomOpCapability::kInferMeta); | ||
| 140 | if (!PythonCustomOpImplRuntimeRegistry::GetInstance().Acquire(desc_, callbacks_)) { | 164 | if (!PythonCustomOpImplRuntimeRegistry::GetInstance().Acquire(desc_, callbacks_)) { |
| 141 | return; | 165 | return; |
| 142 | } | 166 | } |
| @@ -214,16 +238,37 @@ void ClearPythonCustomOpRuntimeRegistry() { | |||
| 214 | } | 238 | } |
| 215 | 239 | ||
| 216 | PythonCustomOpAdapter::PythonCustomOpAdapter(PythonCustomOpAdapterDescriptor desc) | 240 | PythonCustomOpAdapter::PythonCustomOpAdapter(PythonCustomOpAdapterDescriptor desc) |
| 217 | - : desc_(std::move(desc)), holder_(new (std::nothrow) PythonCustomOpImplHolder(desc_)) {} | 241 | + : op_type_(desc.op_type), |
| 242 | + impl_descriptor_key_(desc.impl_descriptor_key), | ||
| 243 | + capabilities_(desc.capabilities), | ||
| 244 | + infer_meta_(desc.infer_meta) { | ||
| 245 | + if (!desc.impl_descriptor_key.empty()) { | ||
| 246 | + holder_ = std::make_unique<PythonCustomOpImplHolder>(desc); | ||
| 247 | + } | ||
| 248 | +} | ||
| 218 | 249 | ||
| 219 | PythonCustomOpAdapter::~PythonCustomOpAdapter() = default; | 250 | PythonCustomOpAdapter::~PythonCustomOpAdapter() = default; |
| 220 | 251 | ||
| 221 | bool PythonCustomOpAdapter::IsValid() const { | 252 | bool PythonCustomOpAdapter::IsValid() const { |
| 222 | - return (holder_ != nullptr) && holder_->IsValid(); | 253 | + if (capabilities_ == 0U) { |
| 254 | + return false; | ||
| 255 | + } | ||
| 256 | + if ((HasCustomOpCapability(capabilities_, CustomOpCapability::kShapeInfer) || | ||
| 257 | + HasCustomOpCapability(capabilities_, CustomOpCapability::kInferMeta)) && | ||
| 258 | + (infer_meta_ == nullptr)) { | ||
| 259 | + return false; | ||
| 260 | + } | ||
| 261 | + const auto infer_capabilities = static_cast<CustomOpCapabilityMask>(CustomOpCapability::kShapeInfer) | | ||
| 262 | + static_cast<CustomOpCapabilityMask>(CustomOpCapability::kInferMeta); | ||
| 263 | + const auto impl_capabilities = capabilities_ & ~infer_capabilities; | ||
| 264 | + if (impl_capabilities == 0U) { | ||
| 265 | + return impl_descriptor_key_.empty() && (holder_ == nullptr); | ||
| 266 | + } | ||
| 267 | + return (!impl_descriptor_key_.empty()) && (holder_ != nullptr) && holder_->IsValid(); | ||
| 223 | } | 268 | } |
| 224 | 269 | ||
| 225 | bool PythonCustomOpAdapter::HasCapability(CustomOpCapability capability) const { | 270 | bool PythonCustomOpAdapter::HasCapability(CustomOpCapability capability) const { |
| 226 | - return HasCustomOpCapability(desc_.capabilities, capability); | 271 | + return HasCustomOpCapability(capabilities_, capability); |
| 227 | } | 272 | } |
| 228 | 273 | ||
| 229 | graphStatus PythonCustomOpAdapter::Execute(gert::EagerOpExecutionContext *ctx) { | 274 | graphStatus PythonCustomOpAdapter::Execute(gert::EagerOpExecutionContext *ctx) { |
| @@ -233,7 +278,7 @@ graphStatus PythonCustomOpAdapter::Execute(gert::EagerOpExecutionContext *ctx) { | |||
| 233 | if ((holder_ == nullptr) || (!holder_->IsValid()) || (holder_->GetHolder() == nullptr) || | 278 | if ((holder_ == nullptr) || (!holder_->IsValid()) || (holder_->GetHolder() == nullptr) || |
| 234 | (holder_->GetCallbacks().execute == nullptr)) { | 279 | (holder_->GetCallbacks().execute == nullptr)) { |
| 235 | GELOGE(GRAPH_FAILED, "Python custom op adapter is invalid, descriptor key[%s], op type[%s].", | 280 | GELOGE(GRAPH_FAILED, "Python custom op adapter is invalid, descriptor key[%s], op type[%s].", |
| 236 | - desc_.impl_descriptor_key.c_str(), desc_.op_type.c_str()); | 281 | + impl_descriptor_key_.c_str(), op_type_.c_str()); |
| 237 | return GRAPH_FAILED; | 282 | return GRAPH_FAILED; |
| 238 | } | 283 | } |
| 239 | return holder_->GetCallbacks().execute(holder_->GetHolder(), ctx); | 284 | return holder_->GetCallbacks().execute(holder_->GetHolder(), ctx); |
| @@ -246,7 +291,7 @@ graphStatus PythonCustomOpAdapter::DeclareLaunchArgs(gert::AnnotatedArgsContext | |||
| 246 | if ((holder_ == nullptr) || (!holder_->IsValid()) || (holder_->GetHolder() == nullptr) || | 291 | if ((holder_ == nullptr) || (!holder_->IsValid()) || (holder_->GetHolder() == nullptr) || |
| 247 | (holder_->GetCallbacks().declare_launch_args == nullptr)) { | 292 | (holder_->GetCallbacks().declare_launch_args == nullptr)) { |
| 248 | GELOGE(GRAPH_FAILED, "Python custom op adapter is invalid, descriptor key[%s], op type[%s].", | 293 | GELOGE(GRAPH_FAILED, "Python custom op adapter is invalid, descriptor key[%s], op type[%s].", |
| 249 | - desc_.impl_descriptor_key.c_str(), desc_.op_type.c_str()); | 294 | + impl_descriptor_key_.c_str(), op_type_.c_str()); |
| 250 | return GRAPH_FAILED; | 295 | return GRAPH_FAILED; |
| 251 | } | 296 | } |
| 252 | return holder_->GetCallbacks().declare_launch_args(holder_->GetHolder(), &ctx); | 297 | return holder_->GetCallbacks().declare_launch_args(holder_->GetHolder(), &ctx); |
| @@ -258,8 +303,18 @@ graphStatus PythonCustomOpAdapter::Compile(gert::OpCompileContext *ctx) { | |||
| 258 | } | 303 | } |
| 259 | 304 | ||
| 260 | graphStatus PythonCustomOpAdapter::InferShape(gert::InferShapeContext *ctx) { | 305 | graphStatus PythonCustomOpAdapter::InferShape(gert::InferShapeContext *ctx) { |
| 261 | - (void)ctx; | 306 | + if (!HasCapability(CustomOpCapability::kShapeInfer) || (infer_meta_ == nullptr)) { |
| 262 | - return ReportUnsupported(CustomOpCapability::kShapeInfer, "InferShape"); | 307 | + return ReportUnsupported(CustomOpCapability::kShapeInfer, "InferShape"); |
| 308 | + } | ||
| 309 | + CustomOpInferMetaResult result; | ||
| 310 | + const auto ret = InferMeta(ctx, &result); | ||
| 311 | + if (ret != GRAPH_SUCCESS) { | ||
| 312 | + return ret; | ||
| 313 | + } | ||
| 314 | + for (size_t i = 0U; i < result.outputs.size(); ++i) { | ||
| 315 | + *ctx->GetOutputShape(i) = result.outputs[i].shape.GetStorageShape(); | ||
| 316 | + } | ||
| 317 | + return GRAPH_SUCCESS; | ||
| 263 | } | 318 | } |
| 264 | 319 | ||
| 265 | graphStatus PythonCustomOpAdapter::InferDataType(gert::InferDataTypeContext *ctx) { | 320 | graphStatus PythonCustomOpAdapter::InferDataType(gert::InferDataTypeContext *ctx) { |
| @@ -267,6 +322,19 @@ graphStatus PythonCustomOpAdapter::InferDataType(gert::InferDataTypeContext *ctx | |||
| 267 | return ReportUnsupported(CustomOpCapability::kShapeInfer, "InferDataType"); | 322 | return ReportUnsupported(CustomOpCapability::kShapeInfer, "InferDataType"); |
| 268 | } | 323 | } |
| 269 | 324 | ||
| 325 | +graphStatus PythonCustomOpAdapter::InferMeta(gert::InferShapeContext *ctx, CustomOpInferMetaResult *result) { | ||
| 326 | + if ((ctx == nullptr) || (result == nullptr)) { | ||
| 327 | + GELOGE(GRAPH_FAILED, "Python custom op infer_meta context or result is null, op type[%s].", op_type_.c_str()); | ||
| 328 | + return GRAPH_FAILED; | ||
| 329 | + } | ||
| 330 | + if (!HasCapability(CustomOpCapability::kInferMeta) || (infer_meta_ == nullptr)) { | ||
| 331 | + GELOGE(GRAPH_FAILED, "Python custom op infer_meta capability or callback is not registered, op type[%s].", | ||
| 332 | + op_type_.c_str()); | ||
| 333 | + return GRAPH_FAILED; | ||
| 334 | + } | ||
| 335 | + return InvokePythonCustomOpInferMeta(infer_meta_, op_type_, ctx, result); | ||
| 336 | +} | ||
| 337 | + | ||
| 270 | graphStatus PythonCustomOpAdapter::Serialize(std::vector<uint8_t> &buffer) { | 338 | graphStatus PythonCustomOpAdapter::Serialize(std::vector<uint8_t> &buffer) { |
| 271 | buffer.clear(); | 339 | buffer.clear(); |
| 272 | return ReportUnsupported(CustomOpCapability::kPortable, "Serialize"); | 340 | return ReportUnsupported(CustomOpCapability::kPortable, "Serialize"); |
| @@ -283,7 +351,7 @@ graphStatus PythonCustomOpAdapter::UpdateHostArgs(gert::UpdateArgsContext *ctx) | |||
| 283 | } | 351 | } |
| 284 | 352 | ||
| 285 | graphStatus PythonCustomOpAdapter::ReportUnsupported(CustomOpCapability capability, const char *method_name) const { | 353 | graphStatus PythonCustomOpAdapter::ReportUnsupported(CustomOpCapability capability, const char *method_name) const { |
| 286 | - GELOGE(GRAPH_FAILED, "Python custom op[%s] does not support %s capability[%u].", desc_.op_type.c_str(), method_name, | 354 | + GELOGE(GRAPH_FAILED, "Python custom op[%s] does not support %s capability[%u].", op_type_.c_str(), method_name, |
| 287 | static_cast<uint32_t>(capability)); | 355 | static_cast<uint32_t>(capability)); |
| 288 | return GRAPH_FAILED; | 356 | return GRAPH_FAILED; |
| 289 | } | 357 | } |
| @@ -16,6 +16,7 @@ | |||
| 16 | 16 | ||
| 17 | 17 | ||
| 18 | 18 | ||
| 19 | + | ||
| 19 | 20 | ||
| 20 | 21 | ||
| 21 | namespace ge { | 22 | namespace ge { |
| @@ -24,6 +25,7 @@ struct PythonCustomOpAdapterDescriptor { | |||
| 24 | std::string op_type; | 25 | std::string op_type; |
| 25 | std::string impl_descriptor_key; | 26 | std::string impl_descriptor_key; |
| 26 | CustomOpCapabilityMask capabilities{0U}; | 27 | CustomOpCapabilityMask capabilities{0U}; |
| 28 | + PythonCustomOpInferMetaFn infer_meta{nullptr}; | ||
| 27 | }; | 29 | }; |
| 28 | 30 | ||
| 29 | class PythonCustomOpImplRuntimeRegistry { | 31 | class PythonCustomOpImplRuntimeRegistry { |
| @@ -67,7 +69,8 @@ class PythonCustomOpAdapter final : public EagerExecuteOp, | |||
| 67 | public ShapeInferOp, | 69 | public ShapeInferOp, |
| 68 | public PortableOp, | 70 | public PortableOp, |
| 69 | public ArgsUpdater, | 71 | public ArgsUpdater, |
| 70 | - public CustomOpCapabilityProvider { | 72 | + public CustomOpCapabilityProvider, |
| 73 | + public CustomOpInferMetaProvider { | ||
| 71 | public: | 74 | public: |
| 72 | explicit PythonCustomOpAdapter(PythonCustomOpAdapterDescriptor desc); | 75 | explicit PythonCustomOpAdapter(PythonCustomOpAdapterDescriptor desc); |
| 73 | ~PythonCustomOpAdapter() override; | 76 | ~PythonCustomOpAdapter() override; |
| @@ -83,11 +86,15 @@ class PythonCustomOpAdapter final : public EagerExecuteOp, | |||
| 83 | graphStatus Serialize(std::vector<uint8_t> &buffer) override; | 86 | graphStatus Serialize(std::vector<uint8_t> &buffer) override; |
| 84 | graphStatus Deserialize(const std::vector<uint8_t> &buffer) override; | 87 | graphStatus Deserialize(const std::vector<uint8_t> &buffer) override; |
| 85 | graphStatus UpdateHostArgs(gert::UpdateArgsContext *ctx) override; | 88 | graphStatus UpdateHostArgs(gert::UpdateArgsContext *ctx) override; |
| 89 | + graphStatus InferMeta(gert::InferShapeContext *ctx, CustomOpInferMetaResult *result) override; | ||
| 86 | 90 | ||
| 87 | private: | 91 | private: |
| 88 | graphStatus ReportUnsupported(CustomOpCapability capability, const char *method_name) const; | 92 | graphStatus ReportUnsupported(CustomOpCapability capability, const char *method_name) const; |
| 89 | 93 | ||
| 90 | - PythonCustomOpAdapterDescriptor desc_; | 94 | + std::string op_type_; |
| 95 | + std::string impl_descriptor_key_; | ||
| 96 | + CustomOpCapabilityMask capabilities_{0U}; | ||
| 97 | + PythonCustomOpInferMetaFn infer_meta_{nullptr}; | ||
| 91 | std::unique_ptr<PythonCustomOpImplHolder> holder_; | 98 | std::unique_ptr<PythonCustomOpImplHolder> holder_; |
| 92 | }; | 99 | }; |
| 93 | 100 | ||
| @@ -21,8 +21,8 @@ struct PythonCustomOpAdapterCallbacks; | |||
| 21 | 21 | ||
| 22 | struct PythonCustomOpRegistrar { | 22 | struct PythonCustomOpRegistrar { |
| 23 | bool (*register_op_proto)(const PythonCustomOpProtoDescriptorView *desc); | 23 | bool (*register_op_proto)(const PythonCustomOpProtoDescriptorView *desc); |
| 24 | - bool (*register_op_adapter)(const PythonCustomOpAdapterDescriptorView *desc, | 24 | + bool (*register_op_impl)(const PythonCustomOpAdapterDescriptorView *desc, |
| 25 | - const PythonCustomOpAdapterCallbacks *callbacks); | 25 | + const PythonCustomOpAdapterCallbacks *callbacks); |
| 26 | }; | 26 | }; |
| 27 | 27 | ||
| 28 | struct PythonCustomOpBridgeArtifactConfig { | 28 | struct PythonCustomOpBridgeArtifactConfig { |
| @@ -303,6 +303,7 @@ PythonCustomOpProtoDescriptorView ProtoDescriptorStorage::BuildView() { | |||
| 303 | attr_views.size(), | 303 | attr_views.size(), |
| 304 | output_views.empty() ? nullptr : output_views.data(), | 304 | output_views.empty() ? nullptr : output_views.data(), |
| 305 | output_views.size(), | 305 | output_views.size(), |
| 306 | + nullptr, | ||
| 306 | }; | 307 | }; |
| 307 | } | 308 | } |
| 308 | 309 | ||
| @@ -20,6 +20,7 @@ | |||
| 20 | 20 | ||
| 21 | 21 | ||
| 22 | 22 | ||
| 23 | + | ||
| 23 | 24 | ||
| 24 | 25 | ||
| 25 | 26 | ||
| @@ -145,6 +146,16 @@ bool IsBridgeApiValid(const PythonCustomOpBridgeApi *api, const uint32_t expecte | |||
| 145 | (api->shutdown_bridge != nullptr); | 146 | (api->shutdown_bridge != nullptr); |
| 146 | } | 147 | } |
| 147 | 148 | ||
| 149 | +struct PythonCustomOpRegistrationEntry { | ||
| 150 | + std::string op_type; | ||
| 151 | + std::string proto_descriptor_key; | ||
| 152 | + bool has_proto{false}; | ||
| 153 | + PythonCustomOpInferMetaFn infer_meta{nullptr}; | ||
| 154 | + bool has_impl{false}; | ||
| 155 | + PythonCustomOpAdapterDescriptor impl_desc; | ||
| 156 | + PythonCustomOpAdapterCallbacks callbacks; | ||
| 157 | +}; | ||
| 158 | + | ||
| 148 | bridge_loader::BridgeLoadDependencies BuildBridgeLoadDependencies() { | 159 | bridge_loader::BridgeLoadDependencies BuildBridgeLoadDependencies() { |
| 149 | return bridge_loader::BridgeLoadDependencies{ | 160 | return bridge_loader::BridgeLoadDependencies{ |
| 150 | &RealPath, | 161 | &RealPath, |
| @@ -204,16 +215,18 @@ class PythonCustomOpBridgeLoader { | |||
| 204 | private: | 215 | private: |
| 205 | void ClearPythonCustomOpRegistrations() { | 216 | void ClearPythonCustomOpRegistrations() { |
| 206 | std::vector<AscendString> adapter_op_types; | 217 | std::vector<AscendString> adapter_op_types; |
| 207 | - adapter_op_types.reserve(registered_op_type_to_impl_descriptor_key_.size()); | 218 | + adapter_op_types.reserve(registered_op_type_to_adapter_.size()); |
| 208 | - for (const auto &item : registered_op_type_to_impl_descriptor_key_) { | 219 | + for (const auto &op_type : registered_op_type_to_adapter_) { |
| 209 | - adapter_op_types.emplace_back(item.first.c_str()); | 220 | + adapter_op_types.emplace_back(op_type.c_str()); |
| 210 | } | 221 | } |
| 211 | CustomOpFactory::RemoveCustomOps(adapter_op_types); | 222 | CustomOpFactory::RemoveCustomOps(adapter_op_types); |
| 212 | ClearPythonCustomOpRuntimeRegistry(); | 223 | ClearPythonCustomOpRuntimeRegistry(); |
| 213 | std::vector<std::string> proto_op_types; | 224 | std::vector<std::string> proto_op_types; |
| 214 | - proto_op_types.reserve(registered_op_type_to_proto_key_.size()); | 225 | + proto_op_types.reserve(python_custom_op_registrations_.size()); |
| 215 | - for (const auto &item : registered_op_type_to_proto_key_) { | 226 | + for (const auto &item : python_custom_op_registrations_) { |
| 216 | - proto_op_types.emplace_back(item.first); | 227 | + if (item.second.has_proto) { |
| 228 | + proto_op_types.emplace_back(item.first); | ||
| 229 | + } | ||
| 217 | } | 230 | } |
| 218 | UnregisterPythonCustomOpProtos(proto_op_types); | 231 | UnregisterPythonCustomOpProtos(proto_op_types); |
| 219 | } | 232 | } |
| @@ -221,7 +234,7 @@ class PythonCustomOpBridgeLoader { | |||
| 221 | Status RegisterCustomOpsFromBridge() { | 234 | Status RegisterCustomOpsFromBridge() { |
| 222 | static constexpr PythonCustomOpRegistrar kRegistrar = { | 235 | static constexpr PythonCustomOpRegistrar kRegistrar = { |
| 223 | &RegisterOpProtoFromBridge, | 236 | &RegisterOpProtoFromBridge, |
| 224 | - &RegisterOpAdapterFromBridge, | 237 | + &RegisterOpImplFromBridge, |
| 225 | }; | 238 | }; |
| 226 | GELOGI("Register python custom ops with bridge library[%s].", loaded_path_.c_str()); | 239 | GELOGI("Register python custom ops with bridge library[%s].", loaded_path_.c_str()); |
| 227 | const auto ret = api_->register_custom_ops(&kRegistrar); | 240 | const auto ret = api_->register_custom_ops(&kRegistrar); |
| @@ -229,7 +242,7 @@ class PythonCustomOpBridgeLoader { | |||
| 229 | GELOGE(ret, "[Register][PythonCustomOps] failed with bridge library[%s].", loaded_path_.c_str()); | 242 | GELOGE(ret, "[Register][PythonCustomOps] failed with bridge library[%s].", loaded_path_.c_str()); |
| 230 | return ret; | 243 | return ret; |
| 231 | } | 244 | } |
| 232 | - return SUCCESS; | 245 | + return CommitPythonCustomOpRegistrations(); |
| 233 | } | 246 | } |
| 234 | 247 | ||
| 235 | static bool RegisterOpProtoFromBridge(const PythonCustomOpProtoDescriptorView *desc) { | 248 | static bool RegisterOpProtoFromBridge(const PythonCustomOpProtoDescriptorView *desc) { |
| @@ -239,12 +252,12 @@ class PythonCustomOpBridgeLoader { | |||
| 239 | return GetInstance().RegisterOpProto(*desc); | 252 | return GetInstance().RegisterOpProto(*desc); |
| 240 | } | 253 | } |
| 241 | 254 | ||
| 242 | - static bool RegisterOpAdapterFromBridge(const PythonCustomOpAdapterDescriptorView *desc, | 255 | + static bool RegisterOpImplFromBridge(const PythonCustomOpAdapterDescriptorView *desc, |
| 243 | - const PythonCustomOpAdapterCallbacks *callbacks) { | 256 | + const PythonCustomOpAdapterCallbacks *callbacks) { |
| 244 | if ((desc == nullptr) || (callbacks == nullptr)) { | 257 | if ((desc == nullptr) || (callbacks == nullptr)) { |
| 245 | return false; | 258 | return false; |
| 246 | } | 259 | } |
| 247 | - return GetInstance().RegisterOpAdapter(*desc, *callbacks); | 260 | + return GetInstance().RegisterOpImpl(*desc, *callbacks); |
| 248 | } | 261 | } |
| 249 | 262 | ||
| 250 | bool RegisterOpProto(const PythonCustomOpProtoDescriptorView &view) { | 263 | bool RegisterOpProto(const PythonCustomOpProtoDescriptorView &view) { |
| @@ -253,15 +266,18 @@ class PythonCustomOpBridgeLoader { | |||
| 253 | GELOGE(FAILED, "[Parse][PythonCustomOpProto] failed."); | 266 | GELOGE(FAILED, "[Parse][PythonCustomOpProto] failed."); |
| 254 | return false; | 267 | return false; |
| 255 | } | 268 | } |
| 256 | - const auto existing = registered_op_type_to_proto_key_.find(proto.op_type); | 269 | + auto ®istration = python_custom_op_registrations_[proto.op_type]; |
| 257 | - if (existing != registered_op_type_to_proto_key_.cend()) { | 270 | + if (registration.op_type.empty()) { |
| 258 | - if (existing->second == proto.descriptor_key) { | 271 | + registration.op_type = proto.op_type; |
| 272 | + } | ||
| 273 | + if (registration.has_proto) { | ||
| 274 | + if (registration.proto_descriptor_key == proto.descriptor_key) { | ||
| 259 | return true; | 275 | return true; |
| 260 | } | 276 | } |
| 261 | GELOGE(FAILED, | 277 | GELOGE(FAILED, |
| 262 | "Python custom op proto conflict, op type[%s], existing source[descriptor key:%s], " | 278 | "Python custom op proto conflict, op type[%s], existing source[descriptor key:%s], " |
| 263 | "current source[descriptor key:%s].", | 279 | "current source[descriptor key:%s].", |
| 264 | - proto.op_type.c_str(), existing->second.c_str(), proto.descriptor_key.c_str()); | 280 | + proto.op_type.c_str(), registration.proto_descriptor_key.c_str(), proto.descriptor_key.c_str()); |
| 265 | return false; | 281 | return false; |
| 266 | } | 282 | } |
| 267 | if (RegisterPythonCustomOpProto(proto) != GRAPH_SUCCESS) { | 283 | if (RegisterPythonCustomOpProto(proto) != GRAPH_SUCCESS) { |
| @@ -269,7 +285,9 @@ class PythonCustomOpBridgeLoader { | |||
| 269 | proto.descriptor_key.c_str(), proto.op_type.c_str()); | 285 | proto.descriptor_key.c_str(), proto.op_type.c_str()); |
| 270 | return false; | 286 | return false; |
| 271 | } | 287 | } |
| 272 | - (void)registered_op_type_to_proto_key_.emplace(proto.op_type, proto.descriptor_key); | 288 | + registration.proto_descriptor_key = proto.descriptor_key; |
| 289 | + registration.has_proto = true; | ||
| 290 | + registration.infer_meta = proto.infer_meta; | ||
| 273 | return true; | 291 | return true; |
| 274 | } | 292 | } |
| 275 | 293 | ||
| @@ -281,8 +299,7 @@ class PythonCustomOpBridgeLoader { | |||
| 281 | return (allow_empty || (!value.empty())) && (value.find('\0') == std::string::npos); | 299 | return (allow_empty || (!value.empty())) && (value.find('\0') == std::string::npos); |
| 282 | } | 300 | } |
| 283 | 301 | ||
| 284 | - static bool ParseAdapterDescriptor(const PythonCustomOpAdapterDescriptorView &view, | 302 | + bool ParseAdapterDescriptor(const PythonCustomOpAdapterDescriptorView &view, PythonCustomOpAdapterDescriptor &desc) { |
| 285 | - PythonCustomOpAdapterDescriptor &desc) { | ||
| 286 | if ((!CopyStringView(view.op_type, false, desc.op_type)) || | 303 | if ((!CopyStringView(view.op_type, false, desc.op_type)) || |
| 287 | (!CopyStringView(view.impl_descriptor_key, false, desc.impl_descriptor_key))) { | 304 | (!CopyStringView(view.impl_descriptor_key, false, desc.impl_descriptor_key))) { |
| 288 | return false; | 305 | return false; |
| @@ -291,22 +308,26 @@ class PythonCustomOpBridgeLoader { | |||
| 291 | return true; | 308 | return true; |
| 292 | } | 309 | } |
| 293 | 310 | ||
| 294 | - bool RegisterOpAdapter(const PythonCustomOpAdapterDescriptorView &view, | 311 | + bool RegisterOpImpl(const PythonCustomOpAdapterDescriptorView &view, |
| 295 | - const PythonCustomOpAdapterCallbacks &callbacks) { | 312 | + const PythonCustomOpAdapterCallbacks &callbacks) { |
| 296 | PythonCustomOpAdapterDescriptor desc; | 313 | PythonCustomOpAdapterDescriptor desc; |
| 297 | if (!ParseAdapterDescriptor(view, desc)) { | 314 | if (!ParseAdapterDescriptor(view, desc)) { |
| 298 | GELOGE(FAILED, "[Parse][PythonCustomOpAdapter] failed."); | 315 | GELOGE(FAILED, "[Parse][PythonCustomOpAdapter] failed."); |
| 299 | return false; | 316 | return false; |
| 300 | } | 317 | } |
| 301 | - const auto existing = registered_op_type_to_impl_descriptor_key_.find(desc.op_type); | 318 | + auto ®istration = python_custom_op_registrations_[desc.op_type]; |
| 302 | - if (existing != registered_op_type_to_impl_descriptor_key_.cend()) { | 319 | + if (registration.op_type.empty()) { |
| 303 | - if (existing->second == desc.impl_descriptor_key) { | 320 | + registration.op_type = desc.op_type; |
| 321 | + } | ||
| 322 | + if (registration.has_impl) { | ||
| 323 | + if (registration.impl_desc.impl_descriptor_key == desc.impl_descriptor_key) { | ||
| 304 | return true; | 324 | return true; |
| 305 | } | 325 | } |
| 306 | GELOGE(FAILED, | 326 | GELOGE(FAILED, |
| 307 | "Python custom op adapter conflict, op type[%s], existing source[impl key:%s], " | 327 | "Python custom op adapter conflict, op type[%s], existing source[impl key:%s], " |
| 308 | "current source[impl key:%s].", | 328 | "current source[impl key:%s].", |
| 309 | - desc.op_type.c_str(), existing->second.c_str(), desc.impl_descriptor_key.c_str()); | 329 | + desc.op_type.c_str(), registration.impl_desc.impl_descriptor_key.c_str(), |
| 330 | + desc.impl_descriptor_key.c_str()); | ||
| 310 | return false; | 331 | return false; |
| 311 | } | 332 | } |
| 312 | if (CustomOpFactory::IsExistOp(AscendString(desc.op_type.c_str()))) { | 333 | if (CustomOpFactory::IsExistOp(AscendString(desc.op_type.c_str()))) { |
| @@ -316,36 +337,110 @@ class PythonCustomOpBridgeLoader { | |||
| 316 | desc.op_type.c_str(), desc.impl_descriptor_key.c_str()); | 337 | desc.op_type.c_str(), desc.impl_descriptor_key.c_str()); |
| 317 | return false; | 338 | return false; |
| 318 | } | 339 | } |
| 319 | - if (!PythonCustomOpImplRuntimeRegistry::Register(desc, callbacks)) { | 340 | + if (!callbacks.IsValid(desc.capabilities)) { |
| 320 | - GELOGE(FAILED, "[Register][PythonCustomOpImplRuntimeRegistry] failed, descriptor key[%s], op type[%s].", | 341 | + GELOGE(FAILED, "Invalid python custom op implementation, descriptor key[%s], op type[%s].", |
| 321 | desc.impl_descriptor_key.c_str(), desc.op_type.c_str()); | 342 | desc.impl_descriptor_key.c_str(), desc.op_type.c_str()); |
| 322 | return false; | 343 | return false; |
| 323 | } | 344 | } |
| 345 | + registration.has_impl = true; | ||
| 346 | + registration.impl_desc = desc; | ||
| 347 | + registration.callbacks = callbacks; | ||
| 348 | + return true; | ||
| 349 | + } | ||
| 324 | 350 | ||
| 351 | + Status RegisterPythonCustomOpImpls() { | ||
| 352 | + for (const auto &item : python_custom_op_registrations_) { | ||
| 353 | + const auto ®istration = item.second; | ||
| 354 | + if (!registration.has_impl || | ||
| 355 | + (registered_op_type_to_adapter_.find(registration.op_type) != registered_op_type_to_adapter_.cend())) { | ||
| 356 | + continue; | ||
| 357 | + } | ||
| 358 | + if (!PythonCustomOpImplRuntimeRegistry::Register(registration.impl_desc, registration.callbacks)) { | ||
| 359 | + GELOGE(FAILED, "[Register][PythonCustomOpImplRuntimeRegistry] failed, descriptor key[%s], op type[%s].", | ||
| 360 | + registration.impl_desc.impl_descriptor_key.c_str(), registration.op_type.c_str()); | ||
| 361 | + return FAILED; | ||
| 362 | + } | ||
| 363 | + } | ||
| 364 | + return SUCCESS; | ||
| 365 | + } | ||
| 366 | + | ||
| 367 | + bool BuildAdapterDescriptor(const PythonCustomOpRegistrationEntry ®istration, | ||
| 368 | + PythonCustomOpAdapterDescriptor &desc) { | ||
| 369 | + desc.op_type = registration.op_type; | ||
| 370 | + if (registration.has_impl) { | ||
| 371 | + desc = registration.impl_desc; | ||
| 372 | + } | ||
| 373 | + if (registration.has_proto) { | ||
| 374 | + desc.infer_meta = registration.infer_meta; | ||
| 375 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kShapeInfer); | ||
| 376 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kInferMeta); | ||
| 377 | + } | ||
| 378 | + const auto infer_capabilities = static_cast<CustomOpCapabilityMask>(CustomOpCapability::kShapeInfer) | | ||
| 379 | + static_cast<CustomOpCapabilityMask>(CustomOpCapability::kInferMeta); | ||
| 380 | + const auto impl_capabilities = desc.capabilities & ~infer_capabilities; | ||
| 381 | + if ((desc.capabilities == 0U) || | ||
| 382 | + (HasCustomOpCapability(desc.capabilities, CustomOpCapability::kInferMeta) && (desc.infer_meta == nullptr)) || | ||
| 383 | + ((impl_capabilities != 0U) && desc.impl_descriptor_key.empty()) || | ||
| 384 | + ((impl_capabilities == 0U) && !desc.impl_descriptor_key.empty())) { | ||
| 385 | + GELOGE(FAILED, "Invalid python custom op adapter descriptor, op type[%s].", registration.op_type.c_str()); | ||
| 386 | + return false; | ||
| 387 | + } | ||
| 388 | + return true; | ||
| 389 | + } | ||
| 390 | + | ||
| 391 | + Status RegisterPythonCustomOpCreator(const PythonCustomOpAdapterDescriptor &desc) { | ||
| 325 | const auto ret = CustomOpFactory::RegisterCustomOpCreator( | 392 | const auto ret = CustomOpFactory::RegisterCustomOpCreator( |
| 326 | AscendString(desc.op_type.c_str()), [registered_desc = desc]() -> std::unique_ptr<BaseCustomOp> { | 393 | AscendString(desc.op_type.c_str()), [registered_desc = desc]() -> std::unique_ptr<BaseCustomOp> { |
| 327 | auto *adapter = new (std::nothrow) PythonCustomOpAdapter(registered_desc); | 394 | auto *adapter = new (std::nothrow) PythonCustomOpAdapter(registered_desc); |
| 328 | if ((adapter == nullptr) || (!adapter->IsValid())) { | 395 | if ((adapter == nullptr) || (!adapter->IsValid())) { |
| 329 | delete adapter; | 396 | delete adapter; |
| 330 | - return nullptr; | 397 | + return std::unique_ptr<BaseCustomOp>(); |
| 331 | } | 398 | } |
| 332 | return std::unique_ptr<BaseCustomOp>(adapter); | 399 | return std::unique_ptr<BaseCustomOp>(adapter); |
| 333 | }); | 400 | }); |
| 334 | if (ret != GRAPH_SUCCESS) { | 401 | if (ret != GRAPH_SUCCESS) { |
| 335 | - (void)PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key); | 402 | + GELOGE(ret, "Register python custom op creator failed, op type[%s].", desc.op_type.c_str()); |
| 336 | - GELOGE(FAILED, "[Register][PythonCustomOpCreator] failed, descriptor key[%s], op type[%s].", | 403 | + return FAILED; |
| 337 | - desc.impl_descriptor_key.c_str(), desc.op_type.c_str()); | ||
| 338 | - return false; | ||
| 339 | } | 404 | } |
| 340 | - (void)registered_op_type_to_impl_descriptor_key_.emplace(desc.op_type, desc.impl_descriptor_key); | 405 | + registered_op_type_to_adapter_.insert(desc.op_type); |
| 341 | - GELOGI("Python custom op[%s] adapter is registered, impl key[%s].", desc.op_type.c_str(), | 406 | + GELOGI("Python custom op creator is registered, op type[%s].", desc.op_type.c_str()); |
| 342 | - desc.impl_descriptor_key.c_str()); | 407 | + return SUCCESS; |
| 343 | - return true; | 408 | + } |
| 409 | + | ||
| 410 | + Status RegisterPythonCustomOpCreators() { | ||
| 411 | + for (const auto &item : python_custom_op_registrations_) { | ||
| 412 | + const auto ®istration = item.second; | ||
| 413 | + const auto &op_type = registration.op_type; | ||
| 414 | + if (registered_op_type_to_adapter_.find(op_type) != registered_op_type_to_adapter_.cend()) { | ||
| 415 | + continue; | ||
| 416 | + } | ||
| 417 | + if (CustomOpFactory::IsExistOp(AscendString(op_type.c_str()))) { | ||
| 418 | + GELOGE(FAILED, "Python custom op conflict, op type[%s], existing source[CustomOpFactory creator].", | ||
| 419 | + op_type.c_str()); | ||
| 420 | + return FAILED; | ||
| 421 | + } | ||
| 422 | + | ||
| 423 | + PythonCustomOpAdapterDescriptor desc; | ||
| 424 | + if (!BuildAdapterDescriptor(registration, desc)) { | ||
| 425 | + return FAILED; | ||
| 426 | + } | ||
| 427 | + if (RegisterPythonCustomOpCreator(desc) != SUCCESS) { | ||
| 428 | + return FAILED; | ||
| 429 | + } | ||
| 430 | + } | ||
| 431 | + return SUCCESS; | ||
| 432 | + } | ||
| 433 | + | ||
| 434 | + Status CommitPythonCustomOpRegistrations() { | ||
| 435 | + if (RegisterPythonCustomOpImpls() != SUCCESS) { | ||
| 436 | + return FAILED; | ||
| 437 | + } | ||
| 438 | + return RegisterPythonCustomOpCreators(); | ||
| 344 | } | 439 | } |
| 345 | 440 | ||
| 346 | void ClearRegisteredState() { | 441 | void ClearRegisteredState() { |
| 347 | - registered_op_type_to_proto_key_.clear(); | 442 | + python_custom_op_registrations_.clear(); |
| 348 | - registered_op_type_to_impl_descriptor_key_.clear(); | 443 | + registered_op_type_to_adapter_.clear(); |
| 349 | } | 444 | } |
| 350 | 445 | ||
| 351 | Status EnsureLoaded() { | 446 | Status EnsureLoaded() { |
| @@ -408,8 +503,8 @@ class PythonCustomOpBridgeLoader { | |||
| 408 | void *handle_{nullptr}; | 503 | void *handle_{nullptr}; |
| 409 | const PythonCustomOpBridgeApi *api_{nullptr}; | 504 | const PythonCustomOpBridgeApi *api_{nullptr}; |
| 410 | std::string loaded_path_; | 505 | std::string loaded_path_; |
| 411 | - std::map<std::string, std::string> registered_op_type_to_proto_key_; | 506 | + std::map<std::string, PythonCustomOpRegistrationEntry> python_custom_op_registrations_; |
| 412 | - std::map<std::string, std::string> registered_op_type_to_impl_descriptor_key_; | 507 | + std::set<std::string> registered_op_type_to_adapter_; |
| 413 | }; | 508 | }; |
| 414 | } // namespace | 509 | } // namespace |
| 415 | 510 | ||
| @@ -16,10 +16,13 @@ | |||
| 16 | 16 | ||
| 17 | 17 | ||
| 18 | 18 | ||
| 19 | + | ||
| 19 | 20 | ||
| 20 | namespace gert { | 21 | namespace gert { |
| 21 | class AnnotatedArgsContext; | 22 | class AnnotatedArgsContext; |
| 22 | class EagerOpExecutionContext; | 23 | class EagerOpExecutionContext; |
| 24 | +class InferShapeContext; | ||
| 25 | +class StorageShape; | ||
| 23 | } // namespace gert | 26 | } // namespace gert |
| 24 | 27 | ||
| 25 | namespace ge { | 28 | namespace ge { |
| @@ -93,6 +96,19 @@ struct PythonCustomOpProtoOutputView { | |||
| 93 | uint32_t kind; | 96 | uint32_t kind; |
| 94 | }; | 97 | }; |
| 95 | 98 | ||
| 99 | +struct PythonCustomOpInferMetaOutputView { | ||
| 100 | + gert::StorageShape *shape; | ||
| 101 | + ge::DataType data_type; | ||
| 102 | +}; | ||
| 103 | + | ||
| 104 | +struct PythonCustomOpInferMetaResultView { | ||
| 105 | + PythonCustomOpInferMetaOutputView *outputs; | ||
| 106 | + size_t output_count; | ||
| 107 | +}; | ||
| 108 | + | ||
| 109 | +using PythonCustomOpInferMetaFn = graphStatus (*)(const PythonCustomOpStringView *op_type, gert::InferShapeContext *ctx, | ||
| 110 | + PythonCustomOpInferMetaResultView *result); | ||
| 111 | + | ||
| 96 | struct PythonCustomOpProtoDescriptorView { | 112 | struct PythonCustomOpProtoDescriptorView { |
| 97 | PythonCustomOpStringView descriptor_key; | 113 | PythonCustomOpStringView descriptor_key; |
| 98 | PythonCustomOpStringView op_type; | 114 | PythonCustomOpStringView op_type; |
| @@ -102,6 +118,7 @@ struct PythonCustomOpProtoDescriptorView { | |||
| 102 | size_t attr_count; | 118 | size_t attr_count; |
| 103 | const PythonCustomOpProtoOutputView *outputs; | 119 | const PythonCustomOpProtoOutputView *outputs; |
| 104 | size_t output_count; | 120 | size_t output_count; |
| 121 | + PythonCustomOpInferMetaFn infer_meta; | ||
| 105 | }; | 122 | }; |
| 106 | 123 | ||
| 107 | struct PythonCustomOpAdapterDescriptorView { | 124 | struct PythonCustomOpAdapterDescriptorView { |
| @@ -114,12 +131,12 @@ using PythonCustomOpImplHolderCreateFn = void *(*)(const PythonCustomOpAdapterDe | |||
| 114 | using PythonCustomOpImplHolderDestroyFn = void (*)(void *holder); | 131 | using PythonCustomOpImplHolderDestroyFn = void (*)(void *holder); |
| 115 | using PythonCustomOpImplExecuteFn = graphStatus (*)(const void *holder, gert::EagerOpExecutionContext *ctx); | 132 | using PythonCustomOpImplExecuteFn = graphStatus (*)(const void *holder, gert::EagerOpExecutionContext *ctx); |
| 116 | using PythonCustomOpImplDeclareLaunchArgsFn = graphStatus (*)(const void *holder, gert::AnnotatedArgsContext *ctx); | 133 | using PythonCustomOpImplDeclareLaunchArgsFn = graphStatus (*)(const void *holder, gert::AnnotatedArgsContext *ctx); |
| 117 | - | ||
| 118 | struct PythonCustomOpAdapterCallbacks { | 134 | struct PythonCustomOpAdapterCallbacks { |
| 119 | PythonCustomOpImplHolderCreateFn create_impl_holder{nullptr}; | 135 | PythonCustomOpImplHolderCreateFn create_impl_holder{nullptr}; |
| 120 | PythonCustomOpImplHolderDestroyFn destroy_impl_holder{nullptr}; | 136 | PythonCustomOpImplHolderDestroyFn destroy_impl_holder{nullptr}; |
| 121 | PythonCustomOpImplExecuteFn execute{nullptr}; | 137 | PythonCustomOpImplExecuteFn execute{nullptr}; |
| 122 | PythonCustomOpImplDeclareLaunchArgsFn declare_launch_args{nullptr}; | 138 | PythonCustomOpImplDeclareLaunchArgsFn declare_launch_args{nullptr}; |
| 139 | + PythonCustomOpInferMetaFn infer_meta{nullptr}; | ||
| 123 | 140 | ||
| 124 | bool IsValid(CustomOpCapabilityMask capabilities) const { | 141 | bool IsValid(CustomOpCapabilityMask capabilities) const { |
| 125 | const auto supported_capabilities = static_cast<CustomOpCapabilityMask>(CustomOpCapability::kEagerExecute) | | 142 | const auto supported_capabilities = static_cast<CustomOpCapabilityMask>(CustomOpCapability::kEagerExecute) | |
| @@ -365,38 +365,30 @@ ge::Operator CreateOperatorFromProto(const AscendString &name, const PythonCusto | |||
| 365 | 365 | ||
| 366 | } // namespace | 366 | } // namespace |
| 367 | 367 | ||
| 368 | -graphStatus ParsePythonCustomOpProto(const PythonCustomOpProtoDescriptorView &view, PythonCustomOpProto &proto) { | 368 | +namespace { |
| 369 | - if ((!IsValidArray(view.inputs, view.input_count)) || (!IsValidArray(view.attrs, view.attr_count)) || | 369 | +graphStatus ParseProtoInputs(const PythonCustomOpProtoDescriptorView &view, PythonCustomOpProto &proto) { |
| 370 | - (!IsValidArray(view.outputs, view.output_count))) { | 370 | + std::set<std::string> names; |
| 371 | - return GRAPH_PARAM_INVALID; | 371 | + proto.inputs.reserve(view.input_count); |
| 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) { | 372 | for (size_t i = 0U; i < view.input_count; ++i) { |
| 382 | PythonCustomOpInput input; | 373 | PythonCustomOpInput input; |
| 383 | - if ((!CopyString(view.inputs[i].name, false, input.name)) || (!input_names.insert(input.name).second) || | 374 | + if ((!CopyString(view.inputs[i].name, false, input.name)) || (!names.insert(input.name).second) || |
| 384 | (ConvertInputKind(view.inputs[i].kind, input.kind) != GRAPH_SUCCESS)) { | 375 | (ConvertInputKind(view.inputs[i].kind, input.kind) != GRAPH_SUCCESS)) { |
| 385 | return GRAPH_PARAM_INVALID; | 376 | return GRAPH_PARAM_INVALID; |
| 386 | } | 377 | } |
| 387 | - parsed.inputs.emplace_back(std::move(input)); | 378 | + proto.inputs.emplace_back(std::move(input)); |
| 388 | } | 379 | } |
| 380 | + return GRAPH_SUCCESS; | ||
| 381 | +} | ||
| 389 | 382 | ||
| 390 | - std::set<std::string> attr_names; | 383 | +graphStatus ParseProtoAttrs(const PythonCustomOpProtoDescriptorView &view, PythonCustomOpProto &proto) { |
| 391 | - parsed.attrs.reserve(view.attr_count); | 384 | + std::set<std::string> names; |
| 385 | + proto.attrs.reserve(view.attr_count); | ||
| 392 | for (size_t i = 0U; i < view.attr_count; ++i) { | 386 | for (size_t i = 0U; i < view.attr_count; ++i) { |
| 393 | const auto &source = view.attrs[i]; | 387 | const auto &source = view.attrs[i]; |
| 394 | PythonCustomOpAttr attr; | 388 | PythonCustomOpAttr attr; |
| 395 | - if ((!CopyString(source.name, false, attr.name)) || (!attr_names.insert(attr.name).second) || | 389 | + if ((!CopyString(source.name, false, attr.name)) || (!names.insert(attr.name).second) || |
| 396 | - (source.is_required > 1U) || (source.default_value.has_value > 1U)) { | 390 | + (source.is_required > 1U) || (source.default_value.has_value > 1U) || |
| 397 | - return GRAPH_PARAM_INVALID; | 391 | + (GetRequiredAttrToken(source.kind) == nullptr)) { |
| 398 | - } | ||
| 399 | - if (GetRequiredAttrToken(source.kind) == nullptr) { | ||
| 400 | return GRAPH_PARAM_INVALID; | 392 | return GRAPH_PARAM_INVALID; |
| 401 | } | 393 | } |
| 402 | attr.kind = source.kind; | 394 | attr.kind = source.kind; |
| @@ -407,18 +399,40 @@ graphStatus ParsePythonCustomOpProto(const PythonCustomOpProtoDescriptorView &vi | |||
| 407 | if ((!attr.is_required) && (ParseOptionalAttrDefinition(source, attr) != GRAPH_SUCCESS)) { | 399 | if ((!attr.is_required) && (ParseOptionalAttrDefinition(source, attr) != GRAPH_SUCCESS)) { |
| 408 | return GRAPH_PARAM_INVALID; | 400 | return GRAPH_PARAM_INVALID; |
| 409 | } | 401 | } |
| 410 | - parsed.attrs.emplace_back(std::move(attr)); | 402 | + proto.attrs.emplace_back(std::move(attr)); |
| 411 | } | 403 | } |
| 404 | + return GRAPH_SUCCESS; | ||
| 405 | +} | ||
| 412 | 406 | ||
| 413 | - std::set<std::string> output_names; | 407 | +graphStatus ParseProtoOutputs(const PythonCustomOpProtoDescriptorView &view, PythonCustomOpProto &proto) { |
| 414 | - parsed.outputs.reserve(view.output_count); | 408 | + std::set<std::string> names; |
| 409 | + proto.outputs.reserve(view.output_count); | ||
| 415 | for (size_t i = 0U; i < view.output_count; ++i) { | 410 | for (size_t i = 0U; i < view.output_count; ++i) { |
| 416 | PythonCustomOpOutput output; | 411 | PythonCustomOpOutput output; |
| 417 | - if ((!CopyString(view.outputs[i].name, false, output.name)) || (!output_names.insert(output.name).second) || | 412 | + if ((!CopyString(view.outputs[i].name, false, output.name)) || (!names.insert(output.name).second) || |
| 418 | (ConvertOutputKind(view.outputs[i].kind, output.kind) != GRAPH_SUCCESS)) { | 413 | (ConvertOutputKind(view.outputs[i].kind, output.kind) != GRAPH_SUCCESS)) { |
| 419 | return GRAPH_PARAM_INVALID; | 414 | return GRAPH_PARAM_INVALID; |
| 420 | } | 415 | } |
| 421 | - parsed.outputs.emplace_back(std::move(output)); | 416 | + proto.outputs.emplace_back(std::move(output)); |
| 417 | + } | ||
| 418 | + return GRAPH_SUCCESS; | ||
| 419 | +} | ||
| 420 | +} // namespace | ||
| 421 | + | ||
| 422 | +graphStatus ParsePythonCustomOpProto(const PythonCustomOpProtoDescriptorView &view, PythonCustomOpProto &proto) { | ||
| 423 | + if ((!IsValidArray(view.inputs, view.input_count)) || (!IsValidArray(view.attrs, view.attr_count)) || | ||
| 424 | + (!IsValidArray(view.outputs, view.output_count))) { | ||
| 425 | + return GRAPH_PARAM_INVALID; | ||
| 426 | + } | ||
| 427 | + PythonCustomOpProto parsed; | ||
| 428 | + if ((!CopyString(view.descriptor_key, false, parsed.descriptor_key)) || | ||
| 429 | + (!CopyString(view.op_type, false, parsed.op_type))) { | ||
| 430 | + return GRAPH_PARAM_INVALID; | ||
| 431 | + } | ||
| 432 | + parsed.infer_meta = view.infer_meta; | ||
| 433 | + if ((ParseProtoInputs(view, parsed) != GRAPH_SUCCESS) || (ParseProtoAttrs(view, parsed) != GRAPH_SUCCESS) || | ||
| 434 | + (ParseProtoOutputs(view, parsed) != GRAPH_SUCCESS)) { | ||
| 435 | + return GRAPH_PARAM_INVALID; | ||
| 422 | } | 436 | } |
| 423 | proto = std::move(parsed); | 437 | proto = std::move(parsed); |
| 424 | return GRAPH_SUCCESS; | 438 | return GRAPH_SUCCESS; |
| @@ -58,6 +58,7 @@ struct PythonCustomOpProto { | |||
| 58 | std::vector<PythonCustomOpInput> inputs; | 58 | std::vector<PythonCustomOpInput> inputs; |
| 59 | std::vector<PythonCustomOpAttr> attrs; | 59 | std::vector<PythonCustomOpAttr> attrs; |
| 60 | std::vector<PythonCustomOpOutput> outputs; | 60 | std::vector<PythonCustomOpOutput> outputs; |
| 61 | + PythonCustomOpInferMetaFn infer_meta{nullptr}; | ||
| 61 | }; | 62 | }; |
| 62 | 63 | ||
| 63 | graphStatus ParsePythonCustomOpProto(const PythonCustomOpProtoDescriptorView &view, PythonCustomOpProto &proto); | 64 | graphStatus ParsePythonCustomOpProto(const PythonCustomOpProtoDescriptorView &view, PythonCustomOpProto &proto); |
| @@ -30,6 +30,7 @@ | |||
| 30 | 30 | ||
| 31 | 31 | ||
| 32 | 32 | ||
| 33 | + | ||
| 33 | 34 | ||
| 34 | 35 | ||
| 35 | 36 | ||
| @@ -201,6 +202,37 @@ bool CopyStringView(const PythonCustomOpStringView &view, std::string &value) { | |||
| 201 | return (!value.empty()) && (value.find('\0') == std::string::npos); | 202 | return (!value.empty()) && (value.find('\0') == std::string::npos); |
| 202 | } | 203 | } |
| 203 | 204 | ||
| 205 | +graphStatus ParseInferMetaOutputs(const py::list &output_metas, const size_t expected_count, | ||
| 206 | + std::vector<CustomOpInferMetaOutput> &outputs) { | ||
| 207 | + if (output_metas.size() != expected_count) { | ||
| 208 | + return GRAPH_FAILED; | ||
| 209 | + } | ||
| 210 | + outputs.reserve(expected_count); | ||
| 211 | + for (size_t i = 0U; i < expected_count; ++i) { | ||
| 212 | + const auto output_meta = output_metas[i].cast<py::tuple>(); | ||
| 213 | + const auto origin_dims = output_meta[0].cast<std::vector<int64_t>>(); | ||
| 214 | + const auto storage_dims = output_meta[1].cast<std::vector<int64_t>>(); | ||
| 215 | + CustomOpInferMetaOutput output; | ||
| 216 | + for (const auto dim : origin_dims) { | ||
| 217 | + (void)output.shape.MutableOriginShape().AppendDim(dim); | ||
| 218 | + } | ||
| 219 | + for (const auto dim : storage_dims) { | ||
| 220 | + (void)output.shape.MutableStorageShape().AppendDim(dim); | ||
| 221 | + } | ||
| 222 | + output.data_type = static_cast<ge::DataType>(output_meta[2].cast<int32_t>()); | ||
| 223 | + outputs.emplace_back(std::move(output)); | ||
| 224 | + } | ||
| 225 | + return GRAPH_SUCCESS; | ||
| 226 | +} | ||
| 227 | + | ||
| 228 | +void AssignInferMetaOutputs(std::vector<CustomOpInferMetaOutput> &outputs, | ||
| 229 | + PythonCustomOpInferMetaResultView *result_view) { | ||
| 230 | + for (size_t i = 0U; i < result_view->output_count; ++i) { | ||
| 231 | + *result_view->outputs[i].shape = std::move(outputs[i].shape); | ||
| 232 | + result_view->outputs[i].data_type = outputs[i].data_type; | ||
| 233 | + } | ||
| 234 | +} | ||
| 235 | + | ||
| 204 | class PythonCustomOpPybindBridge { | 236 | class PythonCustomOpPybindBridge { |
| 205 | public: | 237 | public: |
| 206 | static PythonCustomOpPybindBridge &GetInstance() { | 238 | static PythonCustomOpPybindBridge &GetInstance() { |
| @@ -249,7 +281,7 @@ class PythonCustomOpPybindBridge { | |||
| 249 | return FAILED; | 281 | return FAILED; |
| 250 | } | 282 | } |
| 251 | 283 | ||
| 252 | - if ((registrar.register_op_proto == nullptr) || (registrar.register_op_adapter == nullptr)) { | 284 | + if ((registrar.register_op_proto == nullptr) || (registrar.register_op_impl == nullptr)) { |
| 253 | return FAILED; | 285 | return FAILED; |
| 254 | } | 286 | } |
| 255 | if (CollectAndRegisterProtoDescriptors(descriptors, registrar) != SUCCESS) { | 287 | if (CollectAndRegisterProtoDescriptors(descriptors, registrar) != SUCCESS) { |
| @@ -410,6 +442,53 @@ class PythonCustomOpPybindBridge { | |||
| 410 | } | 442 | } |
| 411 | } | 443 | } |
| 412 | 444 | ||
| 445 | + graphStatus InferMeta(const std::string &op_type, gert::InferShapeContext *ctx, | ||
| 446 | + PythonCustomOpInferMetaResultView *result_view) { | ||
| 447 | + if ((ctx == nullptr) || (result_view == nullptr) || | ||
| 448 | + ((result_view->output_count != 0U) && (result_view->outputs == nullptr))) { | ||
| 449 | + GELOGE(GRAPH_FAILED, "Python custom op infer_meta context or result is null, op type[%s].", op_type.c_str()); | ||
| 450 | + return GRAPH_FAILED; | ||
| 451 | + } | ||
| 452 | + const auto prepare_ret = EnsureBridgeReady(); | ||
| 453 | + if (prepare_ret != SUCCESS) { | ||
| 454 | + GELOGE(prepare_ret, "Prepare python custom op bridge failed for infer_meta, op type[%s].", op_type.c_str()); | ||
| 455 | + return GRAPH_FAILED; | ||
| 456 | + } | ||
| 457 | + py::gil_scoped_acquire gil; | ||
| 458 | + try { | ||
| 459 | + py::object ir_meta_obj = py::none(); | ||
| 460 | + const auto ir_meta = CollectPythonCustomOpIrMeta(op_type); | ||
| 461 | + if (ir_meta != nullptr) { | ||
| 462 | + ir_meta_obj = BuildPythonIrMeta(ir_meta.get()); | ||
| 463 | + } | ||
| 464 | + py::object infer_ctx = BuildPythonInferMetaContext(ctx); | ||
| 465 | + py::object ret = bridge_module_.attr("call_infer_meta")(py::str(op_type), ir_meta_obj, infer_ctx); | ||
| 466 | + if (ret.is_none()) { | ||
| 467 | + GELOGE(GRAPH_FAILED, "Python custom op infer_meta returned None, op type[%s].", op_type.c_str()); | ||
| 468 | + return GRAPH_FAILED; | ||
| 469 | + } | ||
| 470 | + py::list output_metas = ret.cast<py::list>(); | ||
| 471 | + std::vector<CustomOpInferMetaOutput> outputs; | ||
| 472 | + if (ParseInferMetaOutputs(output_metas, result_view->output_count, outputs) != GRAPH_SUCCESS) { | ||
| 473 | + GELOGE(GRAPH_FAILED, | ||
| 474 | + "Python custom op infer_meta result count[%zu] does not match output count[%zu], op type[%s].", | ||
| 475 | + output_metas.size(), result_view->output_count, op_type.c_str()); | ||
| 476 | + return GRAPH_FAILED; | ||
| 477 | + } | ||
| 478 | + AssignInferMetaOutputs(outputs, result_view); | ||
| 479 | + return GRAPH_SUCCESS; | ||
| 480 | + } catch (const py::error_already_set &err) { | ||
| 481 | + GELOGE(GRAPH_FAILED, "Python custom op infer_meta failed, op type[%s]: %s", op_type.c_str(), err.what()); | ||
| 482 | + return GRAPH_FAILED; | ||
| 483 | + } catch (const std::exception &err) { | ||
| 484 | + GELOGE(GRAPH_FAILED, "Python custom op infer_meta failed, op type[%s]: %s", op_type.c_str(), err.what()); | ||
| 485 | + return GRAPH_FAILED; | ||
| 486 | + } catch (...) { | ||
| 487 | + GELOGE(GRAPH_FAILED, "Python custom op infer_meta failed with unknown exception, op type[%s].", op_type.c_str()); | ||
| 488 | + return GRAPH_FAILED; | ||
| 489 | + } | ||
| 490 | + } | ||
| 491 | + | ||
| 413 | graphStatus DeclareLaunchArgs(const PythonCustomOpBridgeHolder *holder, gert::AnnotatedArgsContext *ctx) { | 492 | graphStatus DeclareLaunchArgs(const PythonCustomOpBridgeHolder *holder, gert::AnnotatedArgsContext *ctx) { |
| 414 | if ((holder == nullptr) || (ctx == nullptr)) { | 493 | if ((holder == nullptr) || (ctx == nullptr)) { |
| 415 | GELOGE(GRAPH_FAILED, "Python custom op bridge holder or context is null."); | 494 | GELOGE(GRAPH_FAILED, "Python custom op bridge holder or context is null."); |
| @@ -444,13 +523,15 @@ class PythonCustomOpPybindBridge { | |||
| 444 | 523 | ||
| 445 | private: | 524 | private: |
| 446 | Status CollectAndRegisterProtoDescriptors(const py::dict &descriptors, const PythonCustomOpRegistrar ®istrar) { | 525 | Status CollectAndRegisterProtoDescriptors(const py::dict &descriptors, const PythonCustomOpRegistrar ®istrar) { |
| 526 | + const auto callbacks = GetCallbacks(); | ||
| 447 | try { | 527 | try { |
| 448 | for (const auto &item : descriptors["protos"].cast<py::list>()) { | 528 | for (const auto &item : descriptors["protos"].cast<py::list>()) { |
| 449 | ProtoDescriptorStorage proto; | 529 | ProtoDescriptorStorage proto; |
| 450 | if (proto.Parse(item.cast<py::dict>()) != SUCCESS) { | 530 | if (proto.Parse(item.cast<py::dict>()) != SUCCESS) { |
| 451 | return FAILED; | 531 | return FAILED; |
| 452 | } | 532 | } |
| 453 | - const auto view = proto.BuildView(); | 533 | + auto view = proto.BuildView(); |
| 534 | + view.infer_meta = callbacks.infer_meta; | ||
| 454 | if (!registrar.register_op_proto(&view)) { | 535 | if (!registrar.register_op_proto(&view)) { |
| 455 | GELOGE(FAILED, "Register python custom op proto[%s] failed.", proto.op_type.c_str()); | 536 | GELOGE(FAILED, "Register python custom op proto[%s] failed.", proto.op_type.c_str()); |
| 456 | return FAILED; | 537 | return FAILED; |
| @@ -481,7 +562,7 @@ class PythonCustomOpPybindBridge { | |||
| 481 | GELOGE(FAILED, "Validate python custom op adapter[%s] failed.", adapter.op_type.c_str()); | 562 | GELOGE(FAILED, "Validate python custom op adapter[%s] failed.", adapter.op_type.c_str()); |
| 482 | return FAILED; | 563 | return FAILED; |
| 483 | } | 564 | } |
| 484 | - if (!registrar.register_op_adapter(&view, &callbacks)) { | 565 | + if (!registrar.register_op_impl(&view, &callbacks)) { |
| 485 | GELOGE(FAILED, "Register python custom op adapter[%s] failed.", adapter.op_type.c_str()); | 566 | GELOGE(FAILED, "Register python custom op adapter[%s] failed.", adapter.op_type.c_str()); |
| 486 | return FAILED; | 567 | return FAILED; |
| 487 | } | 568 | } |
| @@ -655,6 +736,11 @@ class PythonCustomOpPybindBridge { | |||
| 655 | return native_module.attr("_borrow_annotated_args_context")(py::int_(reinterpret_cast<uintptr_t>(ctx))); | 736 | return native_module.attr("_borrow_annotated_args_context")(py::int_(reinterpret_cast<uintptr_t>(ctx))); |
| 656 | } | 737 | } |
| 657 | 738 | ||
| 739 | + static py::object BuildPythonInferMetaContext(gert::InferShapeContext *ctx) { | ||
| 740 | + py::module_ native_module = py::module_::import(kCustomOpNativeModuleName); | ||
| 741 | + return native_module.attr("_borrow_infer_meta_context")(py::int_(reinterpret_cast<uintptr_t>(ctx))); | ||
| 742 | + } | ||
| 743 | + | ||
| 658 | static graphStatus TranslateStatusLike(const py::object &result) { | 744 | static graphStatus TranslateStatusLike(const py::object &result) { |
| 659 | if (result.is_none()) { | 745 | if (result.is_none()) { |
| 660 | return GRAPH_SUCCESS; | 746 | return GRAPH_SUCCESS; |
| @@ -686,6 +772,14 @@ class PythonCustomOpPybindBridge { | |||
| 686 | return PythonCustomOpPybindBridge::GetInstance().DeclareLaunchArgs( | 772 | return PythonCustomOpPybindBridge::GetInstance().DeclareLaunchArgs( |
| 687 | static_cast<const PythonCustomOpBridgeHolder *>(holder), ctx); | 773 | static_cast<const PythonCustomOpBridgeHolder *>(holder), ctx); |
| 688 | }; | 774 | }; |
| 775 | + callbacks.infer_meta = [](const PythonCustomOpStringView *op_type, gert::InferShapeContext *ctx, | ||
| 776 | + PythonCustomOpInferMetaResultView *result) -> graphStatus { | ||
| 777 | + if ((op_type == nullptr) || ((op_type->size != 0U) && (op_type->data == nullptr))) { | ||
| 778 | + return GRAPH_FAILED; | ||
| 779 | + } | ||
| 780 | + const std::string op_type_value(op_type->data == nullptr ? "" : op_type->data, op_type->size); | ||
| 781 | + return PythonCustomOpPybindBridge::GetInstance().InferMeta(op_type_value, ctx, result); | ||
| 782 | + }; | ||
| 689 | return callbacks; | 783 | return callbacks; |
| 690 | } | 784 | } |
| 691 | 785 | ||
| @@ -56,6 +56,7 @@ | |||
| 56 | 56 | ||
| 57 | 57 | ||
| 58 | 58 | ||
| 59 | + | ||
| 59 | 60 | ||
| 60 | 61 | ||
| 61 | 62 | ||
| @@ -66,6 +67,10 @@ | |||
| 66 | 67 | ||
| 67 | 68 | ||
| 68 | 69 | ||
| 70 | + | ||
| 71 | + | ||
| 72 | + | ||
| 73 | + | ||
| 69 | 74 | ||
| 70 | namespace ge { | 75 | namespace ge { |
| 71 | REG_OP(StPythonAnnotatedArgsCustomOp) | 76 | REG_OP(StPythonAnnotatedArgsCustomOp) |
| @@ -176,6 +181,8 @@ void **args_table = nullptr; | |||
| 176 | constexpr const char *kPythonCustomOpTypeForSt = "StPythonPybindRemoveCoverageCustomOp"; | 181 | constexpr const char *kPythonCustomOpTypeForSt = "StPythonPybindRemoveCoverageCustomOp"; |
| 177 | constexpr const char *kPythonAnnotatedArgsOpTypeForSt = "StPythonAnnotatedArgsCustomOp"; | 182 | constexpr const char *kPythonAnnotatedArgsOpTypeForSt = "StPythonAnnotatedArgsCustomOp"; |
| 178 | constexpr const char *kPythonAnnotatedArgsBadAttrOpTypeForSt = "StPythonAnnotatedArgsBadAttrCustomOp"; | 183 | constexpr const char *kPythonAnnotatedArgsBadAttrOpTypeForSt = "StPythonAnnotatedArgsBadAttrCustomOp"; |
| 184 | +constexpr const char *kPythonRt2InferMetaOpTypeForSt = "StPythonRt2InferMetaCustomOp"; | ||
| 185 | +constexpr const char *kInferMetaCoverageOpTypeForSt = "StInferMetaCoverageCustomOp"; | ||
| 179 | constexpr const char *kEnvPythonCustomOpPath = "ASCEND_CUSTOM_OPP_PATH"; | 186 | constexpr const char *kEnvPythonCustomOpPath = "ASCEND_CUSTOM_OPP_PATH"; |
| 180 | constexpr const char *kEnvPythonPath = "PYTHONPATH"; | 187 | constexpr const char *kEnvPythonPath = "PYTHONPATH"; |
| 181 | constexpr char kSharedPybindCustomOpPreambleForSt[] = R"PY(from pathlib import Path | 188 | constexpr char kSharedPybindCustomOpPreambleForSt[] = R"PY(from pathlib import Path |
| @@ -291,6 +298,32 @@ class StPythonValidBeforeInvalidCustomOp: | |||
| 291 | pass | 298 | pass |
| 292 | 299 | ||
| 293 | @register_op_impl(op_type=')PY"; | 300 | @register_op_impl(op_type=')PY"; |
| 301 | +constexpr char kRt2InferMetaPreambleForSt[] = R"PY(from pathlib import Path | ||
| 302 | +from typing import List | ||
| 303 | + | ||
| 304 | +from ge.custom_op import register_op | ||
| 305 | +from ge.graph import DataType | ||
| 306 | +from ge.runtime import StorageShape, Tensor, TensorDesc | ||
| 307 | + | ||
| 308 | +MARKER_FILE = r')PY"; | ||
| 309 | +constexpr char kRt2InferMetaOpTypePrefixForSt[] = R"PY(' | ||
| 310 | + | ||
| 311 | +@register_op(op_type=')PY"; | ||
| 312 | +constexpr char kRt2InferMetaFunctionForSt[] = R"PY(') | ||
| 313 | +def infer_meta(x: TensorDesc, *, attr_int: int, attr_float: float, attr_bool: bool, attr_str: str, | ||
| 314 | + attr_dtype: DataType, attr_tensor: Tensor, attr_list_int: List[int], | ||
| 315 | + attr_list_float: List[float], attr_list_bool: List[bool], attr_list_str: List[str], | ||
| 316 | + attr_list_dtype: List[DataType], attr_list_list_int: List[List[int]]) -> TensorDesc: | ||
| 317 | + if (attr_int != 7 or abs(attr_float - 1.5) > 1e-6 or not attr_bool or attr_str != 'native' or | ||
| 318 | + attr_dtype != DataType.DT_INT32 or attr_tensor.data_type != DataType.DT_FLOAT16 or | ||
| 319 | + attr_list_int != [1, 2] or attr_list_float != [2.5, 3.5] or attr_list_bool != [True, False] or | ||
| 320 | + attr_list_str != ['a', 'b'] or attr_list_dtype != [DataType.DT_FLOAT, DataType.DT_INT32] or | ||
| 321 | + attr_list_list_int != [[3, 4], [5]]): | ||
| 322 | + raise AssertionError('native RuntimeAttrs values were not decoded correctly') | ||
| 323 | + dims = list(x.shape.storage_shape.dims) | ||
| 324 | + Path(MARKER_FILE).write_text(str(dims), encoding='utf-8') | ||
| 325 | + return TensorDesc(StorageShape([dims[0], attr_int], [dims[0], attr_int]), attr_dtype) | ||
| 326 | +)PY"; | ||
| 294 | 327 | ||
| 295 | class ScopedTempDirForCustomOpSt { | 328 | class ScopedTempDirForCustomOpSt { |
| 296 | public: | 329 | public: |
| @@ -445,6 +478,18 @@ const std::string &GetSharedPybindCustomOpMarkerFilePathForSt() { | |||
| 445 | return path; | 478 | return path; |
| 446 | } | 479 | } |
| 447 | 480 | ||
| 481 | +const std::string &GetRt2InferMetaCustomOpFilePathForSt() { | ||
| 482 | + static ScopedTempDirForCustomOpSt dir; | ||
| 483 | + static const std::string path = dir.CreateFilePath("rt2_infer_meta_custom_op.py"); | ||
| 484 | + return path; | ||
| 485 | +} | ||
| 486 | + | ||
| 487 | +const std::string &GetRt2InferMetaMarkerFilePathForSt() { | ||
| 488 | + static ScopedTempDirForCustomOpSt dir; | ||
| 489 | + static const std::string path = dir.CreateFilePath("rt2_infer_meta_marker.txt"); | ||
| 490 | + return path; | ||
| 491 | +} | ||
| 492 | + | ||
| 448 | const std::string &GetInvalidSignaturePybindCustomOpFilePathForSt() { | 493 | const std::string &GetInvalidSignaturePybindCustomOpFilePathForSt() { |
| 449 | static ScopedTempDirForCustomOpSt dir; | 494 | static ScopedTempDirForCustomOpSt dir; |
| 450 | static const std::string path = dir.CreateFilePath("pybind_invalid_signature_custom_op.py"); | 495 | static const std::string path = dir.CreateFilePath("pybind_invalid_signature_custom_op.py"); |
| @@ -473,6 +518,16 @@ void EnsureInvalidSignaturePybindCustomOpFileForSt() { | |||
| 473 | }); | 518 | }); |
| 474 | } | 519 | } |
| 475 | 520 | ||
| 521 | +void EnsureRt2InferMetaCustomOpFileForSt() { | ||
| 522 | + static std::once_flag once; | ||
| 523 | + std::call_once(once, []() { | ||
| 524 | + const auto python_file = std::string(kRt2InferMetaPreambleForSt) + GetRt2InferMetaMarkerFilePathForSt() + | ||
| 525 | + kRt2InferMetaOpTypePrefixForSt + kPythonRt2InferMetaOpTypeForSt + | ||
| 526 | + kRt2InferMetaFunctionForSt; | ||
| 527 | + WriteTextFileForCustomOpSt(GetRt2InferMetaCustomOpFilePathForSt(), python_file); | ||
| 528 | + }); | ||
| 529 | +} | ||
| 530 | + | ||
| 476 | class ScopedLoadedPythonCustomOpsForSt { | 531 | class ScopedLoadedPythonCustomOpsForSt { |
| 477 | public: | 532 | public: |
| 478 | ~ScopedLoadedPythonCustomOpsForSt() { | 533 | ~ScopedLoadedPythonCustomOpsForSt() { |
| @@ -632,6 +687,16 @@ class CustomOpFactoryStTest : public testing::Test { | |||
| 632 | bool has_python_path_bak_{false}; | 687 | bool has_python_path_bak_{false}; |
| 633 | }; | 688 | }; |
| 634 | 689 | ||
| 690 | +class InferMetaCoverageCustomOpForSt final : public CustomOpInferMetaProvider { | ||
| 691 | + public: | ||
| 692 | + graphStatus InferMeta(gert::InferShapeContext *, CustomOpInferMetaResult *result) override { | ||
| 693 | + result->outputs.resize(1U); | ||
| 694 | + result->outputs[0U].shape = gert::StorageShape{{4, 5}, {4, 5}}; | ||
| 695 | + result->outputs[0U].data_type = DT_FLOAT; | ||
| 696 | + return GRAPH_SUCCESS; | ||
| 697 | + } | ||
| 698 | +}; | ||
| 699 | + | ||
| 635 | class TestBaseCustomOp : public EagerExecuteOp { | 700 | class TestBaseCustomOp : public EagerExecuteOp { |
| 636 | public: | 701 | public: |
| 637 | graphStatus Execute(gert::EagerOpExecutionContext *ctx) override { | 702 | graphStatus Execute(gert::EagerOpExecutionContext *ctx) override { |
| @@ -1829,6 +1894,84 @@ TEST_F(CustomOpFactoryStTest, register_and_remove_python_custom_op_proto_and_imp | |||
| 1829 | EXPECT_EQ(CustomOpFactory::CreateOrGetCustomOp(op_type), nullptr); | 1894 | EXPECT_EQ(CustomOpFactory::CreateOrGetCustomOp(op_type), nullptr); |
| 1830 | } | 1895 | } |
| 1831 | 1896 | ||
| 1897 | +/** | ||
| 1898 | + * 验证 Python infer_meta 通过真实 RT2 InferShape kernel 使用运行时 shape, | ||
| 1899 | + * 并从 native RuntimeAttrs 读取设计支持的全部 12 类属性。 | ||
| 1900 | + */ | ||
| 1901 | +TEST_F(CustomOpFactoryStTest, PythonCustomOpInferMetaRunsThroughRt2WithNativeAttrs) { | ||
| 1902 | + EnsureRt2InferMetaCustomOpFileForSt(); | ||
| 1903 | + const auto &marker_file = GetRt2InferMetaMarkerFilePathForSt(); | ||
| 1904 | + (void)remove(marker_file.c_str()); | ||
| 1905 | + ScopedEnvVarForCustomOpSt scoped_custom_opp_path(kEnvPythonCustomOpPath, GetRt2InferMetaCustomOpFilePathForSt()); | ||
| 1906 | + | ||
| 1907 | + ASSERT_EQ(GePythonRuntimeManager::Instance().EnsureReady(), SUCCESS); | ||
| 1908 | + ASSERT_EQ(custom_op::LoadPythonCustomOps(), SUCCESS); | ||
| 1909 | + ScopedLoadedPythonCustomOpsForSt loaded_python_custom_ops; | ||
| 1910 | + | ||
| 1911 | + auto *base_op = CustomOpFactory::CreateOrGetCustomOp(AscendString(kPythonRt2InferMetaOpTypeForSt)); | ||
| 1912 | + ASSERT_NE(base_op, nullptr); | ||
| 1913 | + | ||
| 1914 | + gert::StorageShape input_shape({7, 13}, {7, 13}); | ||
| 1915 | + gert::Tensor output; | ||
| 1916 | + auto attr_tensor = FakeGeTensorHolder() | ||
| 1917 | + .DataType(DT_FLOAT16) | ||
| 1918 | + .OriginFormat(FORMAT_ND) | ||
| 1919 | + .StorageFormat(FORMAT_ND) | ||
| 1920 | + .OriginShape({1}) | ||
| 1921 | + .StorageShape({1}) | ||
| 1922 | + .Build(); | ||
| 1923 | + auto infer_shape_func = kernel::InferCustomOpShapeFromInput; | ||
| 1924 | + auto run_context = | ||
| 1925 | + gert::KernelRunContextFaker() | ||
| 1926 | + .KernelIONum(3, 1) | ||
| 1927 | + .NodeIoNum(1, 1) | ||
| 1928 | + .IrInputNum(1) | ||
| 1929 | + .NodeInputTd(0, DT_FLOAT16, FORMAT_ND, FORMAT_ND) | ||
| 1930 | + .NodeOutputTd(0, DT_INT32, FORMAT_ND, FORMAT_ND) | ||
| 1931 | + .NodeAttrs({{"attr_int", AnyValue::CreateFrom<int64_t>(7)}, | ||
| 1932 | + {"attr_float", AnyValue::CreateFrom<float>(1.5F)}, | ||
| 1933 | + {"attr_bool", AnyValue::CreateFrom<bool>(true)}, | ||
| 1934 | + {"attr_str", AnyValue::CreateFrom<std::string>("native")}, | ||
| 1935 | + {"attr_dtype", AnyValue::CreateFrom<DataType>(DT_INT32)}, | ||
| 1936 | + {"attr_tensor", AnyValue::CreateFrom<GeTensor>(*attr_tensor)}, | ||
| 1937 | + {"attr_list_int", AnyValue::CreateFrom<std::vector<int64_t>>({1, 2})}, | ||
| 1938 | + {"attr_list_float", AnyValue::CreateFrom<std::vector<float>>({2.5F, 3.5F})}, | ||
| 1939 | + {"attr_list_bool", AnyValue::CreateFrom<std::vector<bool>>({true, false})}, | ||
| 1940 | + {"attr_list_str", AnyValue::CreateFrom<std::vector<std::string>>({"a", "b"})}, | ||
| 1941 | + {"attr_list_dtype", AnyValue::CreateFrom<std::vector<DataType>>({DT_FLOAT, DT_INT32})}, | ||
| 1942 | + {"attr_list_list_int", AnyValue::CreateFrom<std::vector<std::vector<int64_t>>>({{3, 4}, {5}})}}) | ||
| 1943 | + .Inputs({&input_shape, base_op, reinterpret_cast<void *>(infer_shape_func)}) | ||
| 1944 | + .Outputs({&output}) | ||
| 1945 | + .Build(); | ||
| 1946 | + | ||
| 1947 | + const auto funcs = gert::KernelRegistry::GetInstance().FindKernelFuncs("InferShape"); | ||
| 1948 | + ASSERT_NE(funcs, nullptr); | ||
| 1949 | + ASSERT_EQ(funcs->run_func(run_context), GRAPH_SUCCESS); | ||
| 1950 | + EXPECT_EQ(output.GetOriginShape(), gert::Shape({7, 7})); | ||
| 1951 | + EXPECT_EQ(ReadTextFileForCustomOpSt(marker_file), "[7, 13]"); | ||
| 1952 | +} | ||
| 1953 | + | ||
| 1954 | +TEST_F(CustomOpFactoryStTest, CustomOpInferMetaCompilePath) { | ||
| 1955 | + ASSERT_EQ(CustomOpFactory::RegisterCustomOpCreator( | ||
| 1956 | + AscendString(kInferMetaCoverageOpTypeForSt), | ||
| 1957 | + []() -> std::unique_ptr<BaseCustomOp> { return std::make_unique<InferMetaCoverageCustomOpForSt>(); }), | ||
| 1958 | + GRAPH_SUCCESS); | ||
| 1959 | + | ||
| 1960 | + auto op_desc = std::make_shared<OpDesc>("st_infer_meta", kInferMetaCoverageOpTypeForSt); | ||
| 1961 | + ASSERT_EQ(op_desc->AddInputDesc(GeTensorDesc(GeShape({2, 3}), FORMAT_ND, DT_FLOAT)), GRAPH_SUCCESS); | ||
| 1962 | + ASSERT_EQ(op_desc->AddOutputDesc(GeTensorDesc(GeShape({1}), FORMAT_ND, DT_UNDEFINED)), GRAPH_SUCCESS); | ||
| 1963 | + op_desc->AppendIrInput("x", kIrInputRequired); | ||
| 1964 | + op_desc->AppendIrOutput("y", kIrOutputRequired); | ||
| 1965 | + op_desc->AddInferFunc([](Operator &) { return GRAPH_SUCCESS; }); | ||
| 1966 | + auto op = OpDescUtils::CreateOperatorFromOpDesc(op_desc); | ||
| 1967 | + | ||
| 1968 | + EXPECT_EQ(op.InferShapeAndType(), GRAPH_SUCCESS); | ||
| 1969 | + EXPECT_EQ(op_desc->GetOutputDesc(0U).GetShape(), GeShape({4, 5})); | ||
| 1970 | + EXPECT_EQ(op_desc->GetOutputDesc(0U).GetDataType(), DT_FLOAT); | ||
| 1971 | + | ||
| 1972 | + CustomOpFactory::RemoveCustomOps({AscendString(kInferMetaCoverageOpTypeForSt)}); | ||
| 1973 | +} | ||
| 1974 | + | ||
| 1832 | TEST_F(CustomOpFactoryStTest, PythonAnnotatedArgsCustomOpLoaderGeneratesTaskDef) { | 1975 | TEST_F(CustomOpFactoryStTest, PythonAnnotatedArgsCustomOpLoaderGeneratesTaskDef) { |
| 1833 | EnsureSharedPybindCustomOpFileForSt(); | 1976 | EnsureSharedPybindCustomOpFileForSt(); |
| 1834 | ScopedEnvVarForCustomOpSt scoped_custom_opp_path(kEnvPythonCustomOpPath, GetSharedPybindCustomOpFilePathForSt()); | 1977 | ScopedEnvVarForCustomOpSt scoped_custom_opp_path(kEnvPythonCustomOpPath, GetSharedPybindCustomOpFilePathForSt()); |
| @@ -476,6 +476,7 @@ set(MULTI_PARTS_TEST_FILES | |||
| 476 | "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" | 477 | "runtime/custom_op/python_custom_op_bridge_loader_unittest.cc" |
| 478 | "runtime/custom_op/python_custom_op_proto_unittest.cc" | 478 | "runtime/custom_op/python_custom_op_proto_unittest.cc" |
| 479 | + "runtime/custom_op/python_custom_op_infer_meta_unittest.cc" | ||
| 479 | "graph_ir/ge_custom_op_factory_unittest.cc" | 480 | "graph_ir/ge_custom_op_factory_unittest.cc" |
| 480 | "graph_ir/ge_custom_op_pull_registry_unittest.cc" | 481 | "graph_ir/ge_custom_op_pull_registry_unittest.cc" |
| 481 | "graph_ir/ge_operator_factory_unittest.cc" | 482 | "graph_ir/ge_operator_factory_unittest.cc" |
| @@ -491,6 +491,7 @@ def test_native_module_exposes_annotated_args_types(): | |||
| 491 | "AnnotatedArgsContext", | 491 | "AnnotatedArgsContext", |
| 492 | "AnnotatedKernelArgs", | 492 | "AnnotatedKernelArgs", |
| 493 | "AnnotatedKernelLaunchInfo", | 493 | "AnnotatedKernelLaunchInfo", |
| 494 | + "InferMetaContext", | ||
| 494 | "WorkspaceAddr", | 495 | "WorkspaceAddr", |
| 495 | ): | 496 | ): |
| 496 | assert hasattr(native_module, type_name) | 497 | assert hasattr(native_module, type_name) |
| @@ -505,7 +506,9 @@ def test_native_module_exposes_annotated_args_types(): | |||
| 505 | def test_public_stub_hides_bridge_private_attr_readers(): | 506 | def test_public_stub_hides_bridge_private_attr_readers(): |
| 506 | stub_path = Path(custom_op.__file__).with_name("_ge_custom_op_native.pyi") | 507 | stub_path = Path(custom_op.__file__).with_name("_ge_custom_op_native.pyi") |
| 507 | stub_text = stub_path.read_text(encoding="utf-8") | 508 | stub_text = stub_path.read_text(encoding="utf-8") |
| 508 | - annotated_context_stub = stub_text.split("class AnnotatedArgsContext:", 1)[1] | 509 | + annotated_context_stub = stub_text.split("class AnnotatedArgsContext:", 1)[1].split( |
| 510 | + "class InferMetaContext:", 1 | ||
| 511 | + )[0] | ||
| 509 | assert "def _get_attrs" not in annotated_context_stub | 512 | assert "def _get_attrs" not in annotated_context_stub |
| 510 | assert "def get_attrs" not in annotated_context_stub | 513 | assert "def get_attrs" not in annotated_context_stub |
| 511 | assert "def get_attr" not in annotated_context_stub | 514 | assert "def get_attr" not in annotated_context_stub |
| @@ -0,0 +1,468 @@ | |||
| 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 infer_meta callback.""" | ||
| 14 | + | ||
| 15 | +import importlib | ||
| 16 | +from typing import List, Optional, Tuple | ||
| 17 | + | ||
| 18 | +import pytest | ||
| 19 | + | ||
| 20 | +proto = importlib.import_module("ge.custom_op.proto") | ||
| 21 | +bridge = importlib.import_module("ge.custom_op._bridge") | ||
| 22 | +runtime = importlib.import_module("ge.runtime") | ||
| 23 | +runtime_native = importlib.import_module("ge.runtime._native") | ||
| 24 | +graph = importlib.import_module("ge.graph") | ||
| 25 | + | ||
| 26 | +DataType = graph.DataType | ||
| 27 | +TensorDesc = runtime.TensorDesc | ||
| 28 | +InputType = proto.InputType | ||
| 29 | +OutputType = proto.OutputType | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def clear_registries(): | ||
| 34 | + proto.clear_registered_op_protos() | ||
| 35 | + yield | ||
| 36 | + proto.clear_registered_op_protos() | ||
| 37 | + | ||
| 38 | + | ||
| 39 | +class FakeInferMetaContext: | ||
| 40 | + """Minimal fake of native InferMetaContext for Python-side testing.""" | ||
| 41 | + | ||
| 42 | + def __init__(self, ir_inputs, ir_outputs, ir_attrs=None): | ||
| 43 | + self._ir_inputs = ir_inputs | ||
| 44 | + self._ir_outputs = ir_outputs | ||
| 45 | + self._ir_attrs = ir_attrs or [] | ||
| 46 | + self._input_shapes = [] | ||
| 47 | + self._input_dtypes = [] | ||
| 48 | + self._dynamic_input_counts = [] | ||
| 49 | + self._dynamic_output_counts = [] | ||
| 50 | + self._invalidated = False | ||
| 51 | + | ||
| 52 | + def set_input_shape(self, ir_index, shape, dtype): | ||
| 53 | + self._input_shapes.insert(ir_index, shape) | ||
| 54 | + self._input_dtypes.insert(ir_index, dtype) | ||
| 55 | + | ||
| 56 | + def set_dynamic_input(self, ir_index, shapes, dtypes): | ||
| 57 | + while len(self._dynamic_input_counts) <= ir_index: | ||
| 58 | + self._dynamic_input_counts.append(0) | ||
| 59 | + self._dynamic_input_counts[ir_index] = len(shapes) | ||
| 60 | + for s, d in zip(shapes, dtypes): | ||
| 61 | + self._input_shapes.append(s) | ||
| 62 | + self._input_dtypes.append(d) | ||
| 63 | + | ||
| 64 | + def set_dynamic_output_count(self, ir_index, count): | ||
| 65 | + while len(self._dynamic_output_counts) <= ir_index: | ||
| 66 | + self._dynamic_output_counts.append(0) | ||
| 67 | + self._dynamic_output_counts[ir_index] = count | ||
| 68 | + | ||
| 69 | + def get_required_input_tensor(self, ir_index): | ||
| 70 | + return TensorDesc( | ||
| 71 | + self._input_shapes[ir_index], DataType(self._input_dtypes[ir_index]) | ||
| 72 | + ) | ||
| 73 | + | ||
| 74 | + def get_optional_input_tensor(self, ir_index): | ||
| 75 | + if ir_index >= len(self._input_shapes) or self._input_shapes[ir_index] is None: | ||
| 76 | + return None | ||
| 77 | + return TensorDesc( | ||
| 78 | + self._input_shapes[ir_index], DataType(self._input_dtypes[ir_index]) | ||
| 79 | + ) | ||
| 80 | + | ||
| 81 | + def get_dynamic_input_num(self, ir_index): | ||
| 82 | + return ( | ||
| 83 | + self._dynamic_input_counts[ir_index] | ||
| 84 | + if ir_index < len(self._dynamic_input_counts) | ||
| 85 | + else 0 | ||
| 86 | + ) | ||
| 87 | + | ||
| 88 | + def get_dynamic_input_tensor(self, ir_index, relative_index): | ||
| 89 | + start = sum(self._dynamic_input_counts[:ir_index]) if ir_index > 0 else 0 | ||
| 90 | + return TensorDesc( | ||
| 91 | + self._input_shapes[start + relative_index], | ||
| 92 | + DataType(self._input_dtypes[start + relative_index]), | ||
| 93 | + ) | ||
| 94 | + | ||
| 95 | + def get_attrs(self): | ||
| 96 | + return FakeRuntimeAttrs(self._ir_attrs) | ||
| 97 | + | ||
| 98 | + def get_dynamic_output_num(self, ir_index): | ||
| 99 | + return ( | ||
| 100 | + self._dynamic_output_counts[ir_index] | ||
| 101 | + if ir_index < len(self._dynamic_output_counts) | ||
| 102 | + else 1 | ||
| 103 | + ) | ||
| 104 | + | ||
| 105 | + def _invalidate(self): | ||
| 106 | + self._invalidated = True | ||
| 107 | + | ||
| 108 | + | ||
| 109 | +class FakeRuntimeAttrs: | ||
| 110 | + def __init__(self, ir_attrs): | ||
| 111 | + self._ir_attrs = ir_attrs | ||
| 112 | + self._values = {} | ||
| 113 | + for i, attr in enumerate(ir_attrs): | ||
| 114 | + self._values[i] = attr.get("default", None) | ||
| 115 | + | ||
| 116 | + def get_int(self, index): | ||
| 117 | + return self._values.get(index, 0) | ||
| 118 | + | ||
| 119 | + def get_float(self, index): | ||
| 120 | + return self._values.get(index, 0.0) | ||
| 121 | + | ||
| 122 | + def get_bool(self, index): | ||
| 123 | + return self._values.get(index, False) | ||
| 124 | + | ||
| 125 | + def get_str(self, index): | ||
| 126 | + return self._values.get(index, "") | ||
| 127 | + | ||
| 128 | + def get_data_type(self, index): | ||
| 129 | + return self._values.get(index, DataType.DT_FLOAT) | ||
| 130 | + | ||
| 131 | + def get_tensor(self, index): | ||
| 132 | + return self._values.get(index, None) | ||
| 133 | + | ||
| 134 | + def get_list_int(self, index): | ||
| 135 | + return self._values.get(index, []) | ||
| 136 | + | ||
| 137 | + def get_list_float(self, index): | ||
| 138 | + return self._values.get(index, []) | ||
| 139 | + | ||
| 140 | + def get_list_bool(self, index): | ||
| 141 | + return self._values.get(index, []) | ||
| 142 | + | ||
| 143 | + def get_list_str(self, index): | ||
| 144 | + return self._values.get(index, []) | ||
| 145 | + | ||
| 146 | + def get_list_data_type(self, index): | ||
| 147 | + return self._values.get(index, []) | ||
| 148 | + | ||
| 149 | + def get_list_list_int(self, index): | ||
| 150 | + return self._values.get(index, []) | ||
| 151 | + | ||
| 152 | + | ||
| 153 | +def _make_ir_meta(inputs, outputs, attrs=None): | ||
| 154 | + return { | ||
| 155 | + "inputs": inputs, | ||
| 156 | + "outputs": outputs, | ||
| 157 | + "attrs": attrs or [], | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + | ||
| 161 | +def _make_ir_input(name, kind): | ||
| 162 | + return {"name": name, "kind": int(kind)} | ||
| 163 | + | ||
| 164 | + | ||
| 165 | +def _make_ir_output(name, kind): | ||
| 166 | + return {"name": name, "kind": int(kind)} | ||
| 167 | + | ||
| 168 | + | ||
| 169 | +def _make_ir_attr(name, type_str, default=None): | ||
| 170 | + return {"name": name, "type": type_str, "default": default} | ||
| 171 | + | ||
| 172 | + | ||
| 173 | +def _output_dtypes(outputs): | ||
| 174 | + return [output[2] for output in outputs] | ||
| 175 | + | ||
| 176 | + | ||
| 177 | +# --------------------------------------------------------------------------- | ||
| 178 | +# Basic infer_meta: single required input, single required output | ||
| 179 | +# --------------------------------------------------------------------------- | ||
| 180 | + | ||
| 181 | + | ||
| 182 | +def test_call_infer_meta_required_input_output(): | ||
| 183 | + | ||
| 184 | + def infer_meta(x: TensorDesc) -> TensorDesc: | ||
| 185 | + return x | ||
| 186 | + | ||
| 187 | + ir_meta = _make_ir_meta( | ||
| 188 | + [_make_ir_input("x", InputType.REQUIRED)], | ||
| 189 | + [_make_ir_output("output0", OutputType.REQUIRED)], | ||
| 190 | + ) | ||
| 191 | + ctx = FakeInferMetaContext(ir_meta["inputs"], ir_meta["outputs"]) | ||
| 192 | + ctx.set_input_shape( | ||
| 193 | + 0, runtime_native.StorageShape([2, 3], [2, 3]), int(DataType.DT_FLOAT) | ||
| 194 | + ) | ||
| 195 | + | ||
| 196 | + outputs = bridge.call_infer_meta("InferMetaBasic", ir_meta, ctx) | ||
| 197 | + assert _output_dtypes(outputs) == [int(DataType.DT_FLOAT)] | ||
| 198 | + assert outputs[0][1] == [2, 3] | ||
| 199 | + | ||
| 200 | + | ||
| 201 | +def test_call_infer_meta_with_attrs(): | ||
| 202 | + | ||
| 203 | + def infer_meta(x: TensorDesc, *, axis: int, scale: float = 2.0) -> TensorDesc: | ||
| 204 | + return x | ||
| 205 | + | ||
| 206 | + ir_meta = _make_ir_meta( | ||
| 207 | + [_make_ir_input("x", InputType.REQUIRED)], | ||
| 208 | + [_make_ir_output("output0", OutputType.REQUIRED)], | ||
| 209 | + [ | ||
| 210 | + _make_ir_attr("axis", "VT_INT"), | ||
| 211 | + _make_ir_attr("scale", "VT_FLOAT", 2.0), | ||
| 212 | + ], | ||
| 213 | + ) | ||
| 214 | + ctx = FakeInferMetaContext(ir_meta["inputs"], ir_meta["outputs"]) | ||
| 215 | + ctx.set_input_shape( | ||
| 216 | + 0, runtime_native.StorageShape([4], [4]), int(DataType.DT_FLOAT16) | ||
| 217 | + ) | ||
| 218 | + | ||
| 219 | + outputs = bridge.call_infer_meta("InferMetaAttrs", ir_meta, ctx) | ||
| 220 | + assert _output_dtypes(outputs) == [int(DataType.DT_FLOAT16)] | ||
| 221 | + | ||
| 222 | + | ||
| 223 | +def test_call_infer_meta_zero_output(): | ||
| 224 | + | ||
| 225 | + def infer_meta(x: TensorDesc) -> None: | ||
| 226 | + return None | ||
| 227 | + | ||
| 228 | + ir_meta = _make_ir_meta( | ||
| 229 | + [_make_ir_input("x", InputType.REQUIRED)], | ||
| 230 | + [], | ||
| 231 | + ) | ||
| 232 | + ctx = FakeInferMetaContext(ir_meta["inputs"], ir_meta["outputs"]) | ||
| 233 | + ctx.set_input_shape( | ||
| 234 | + 0, runtime_native.StorageShape([1], [1]), int(DataType.DT_FLOAT) | ||
| 235 | + ) | ||
| 236 | + | ||
| 237 | + outputs = bridge.call_infer_meta("InferMetaZeroOutput", ir_meta, ctx) | ||
| 238 | + assert outputs == [] | ||
| 239 | + | ||
| 240 | + | ||
| 241 | +def test_call_infer_meta_multiple_outputs(): | ||
| 242 | + | ||
| 243 | + def infer_meta(x: TensorDesc) -> Tuple[TensorDesc, TensorDesc]: | ||
| 244 | + return x, x | ||
| 245 | + | ||
| 246 | + ir_meta = _make_ir_meta( | ||
| 247 | + [_make_ir_input("x", InputType.REQUIRED)], | ||
| 248 | + [ | ||
| 249 | + _make_ir_output("output0", OutputType.REQUIRED), | ||
| 250 | + _make_ir_output("output1", OutputType.REQUIRED), | ||
| 251 | + ], | ||
| 252 | + ) | ||
| 253 | + ctx = FakeInferMetaContext(ir_meta["inputs"], ir_meta["outputs"]) | ||
| 254 | + ctx.set_input_shape( | ||
| 255 | + 0, runtime_native.StorageShape([2, 2], [2, 2]), int(DataType.DT_INT32) | ||
| 256 | + ) | ||
| 257 | + | ||
| 258 | + outputs = bridge.call_infer_meta("InferMetaMultiOutput", ir_meta, ctx) | ||
| 259 | + assert _output_dtypes(outputs) == [int(DataType.DT_INT32), int(DataType.DT_INT32)] | ||
| 260 | + assert outputs[0][1] == [2, 2] | ||
| 261 | + assert outputs[1][1] == [2, 2] | ||
| 262 | + | ||
| 263 | + | ||
| 264 | +def test_call_infer_meta_dynamic_input(): | ||
| 265 | + | ||
| 266 | + def infer_meta(x: TensorDesc, ys: List[TensorDesc]) -> TensorDesc: | ||
| 267 | + return x | ||
| 268 | + | ||
| 269 | + ir_meta = _make_ir_meta( | ||
| 270 | + [ | ||
| 271 | + _make_ir_input("x", InputType.REQUIRED), | ||
| 272 | + _make_ir_input("ys", InputType.DYNAMIC), | ||
| 273 | + ], | ||
| 274 | + [_make_ir_output("output0", OutputType.REQUIRED)], | ||
| 275 | + ) | ||
| 276 | + ctx = FakeInferMetaContext(ir_meta["inputs"], ir_meta["outputs"]) | ||
| 277 | + ctx.set_input_shape( | ||
| 278 | + 0, runtime_native.StorageShape([3], [3]), int(DataType.DT_FLOAT) | ||
| 279 | + ) | ||
| 280 | + ctx.set_dynamic_input( | ||
| 281 | + 1, | ||
| 282 | + [ | ||
| 283 | + runtime_native.StorageShape([1], [1]), | ||
| 284 | + runtime_native.StorageShape([2], [2]), | ||
| 285 | + ], | ||
| 286 | + [int(DataType.DT_FLOAT), int(DataType.DT_FLOAT)], | ||
| 287 | + ) | ||
| 288 | + | ||
| 289 | + outputs = bridge.call_infer_meta("InferMetaDynInput", ir_meta, ctx) | ||
| 290 | + assert _output_dtypes(outputs) == [int(DataType.DT_FLOAT)] | ||
| 291 | + | ||
| 292 | + | ||
| 293 | +def test_call_infer_meta_optional_input(): | ||
| 294 | + | ||
| 295 | + def infer_meta(x: TensorDesc, y: Optional[TensorDesc]) -> TensorDesc: | ||
| 296 | + return x | ||
| 297 | + | ||
| 298 | + ir_meta = _make_ir_meta( | ||
| 299 | + [ | ||
| 300 | + _make_ir_input("x", InputType.REQUIRED), | ||
| 301 | + _make_ir_input("y", InputType.OPTIONAL), | ||
| 302 | + ], | ||
| 303 | + [_make_ir_output("output0", OutputType.REQUIRED)], | ||
| 304 | + ) | ||
| 305 | + ctx = FakeInferMetaContext(ir_meta["inputs"], ir_meta["outputs"]) | ||
| 306 | + ctx.set_input_shape(0, runtime_native.StorageShape([5], [5]), int(DataType.DT_BOOL)) | ||
| 307 | + ctx.set_input_shape(1, None, None) | ||
| 308 | + | ||
| 309 | + outputs = bridge.call_infer_meta("InferMetaOptInput", ir_meta, ctx) | ||
| 310 | + assert _output_dtypes(outputs) == [int(DataType.DT_BOOL)] | ||
| 311 | + | ||
| 312 | + | ||
| 313 | +def test_call_infer_meta_dynamic_output(): | ||
| 314 | + | ||
| 315 | + def infer_meta(x: TensorDesc) -> List[TensorDesc]: | ||
| 316 | + return [x, x] | ||
| 317 | + | ||
| 318 | + ir_meta = _make_ir_meta( | ||
| 319 | + [_make_ir_input("x", InputType.REQUIRED)], | ||
| 320 | + [_make_ir_output("output0", OutputType.DYNAMIC)], | ||
| 321 | + ) | ||
| 322 | + ctx = FakeInferMetaContext(ir_meta["inputs"], ir_meta["outputs"]) | ||
| 323 | + ctx.set_input_shape( | ||
| 324 | + 0, runtime_native.StorageShape([8], [8]), int(DataType.DT_FLOAT16) | ||
| 325 | + ) | ||
| 326 | + ctx.set_dynamic_output_count(0, 2) | ||
| 327 | + | ||
| 328 | + outputs = bridge.call_infer_meta("InferMetaDynOutput", ir_meta, ctx) | ||
| 329 | + assert _output_dtypes(outputs) == [ | ||
| 330 | + int(DataType.DT_FLOAT16), | ||
| 331 | + int(DataType.DT_FLOAT16), | ||
| 332 | + ] | ||
| 333 | + | ||
| 334 | + | ||
| 335 | +def test_call_infer_meta_dynamic_output_followed_by_required_output(): | ||
| 336 | + | ||
| 337 | + def infer_meta(x: TensorDesc) -> Tuple[List[TensorDesc], TensorDesc]: | ||
| 338 | + return [x, x], x | ||
| 339 | + | ||
| 340 | + ir_meta = _make_ir_meta( | ||
| 341 | + [_make_ir_input("x", InputType.REQUIRED)], | ||
| 342 | + [ | ||
| 343 | + _make_ir_output("dynamic", OutputType.DYNAMIC), | ||
| 344 | + _make_ir_output("required", OutputType.REQUIRED), | ||
| 345 | + ], | ||
| 346 | + ) | ||
| 347 | + ctx = FakeInferMetaContext(ir_meta["inputs"], ir_meta["outputs"]) | ||
| 348 | + ctx.set_input_shape( | ||
| 349 | + 0, runtime_native.StorageShape([8], [8]), int(DataType.DT_FLOAT16) | ||
| 350 | + ) | ||
| 351 | + ctx.set_dynamic_output_count(0, 2) | ||
| 352 | + | ||
| 353 | + outputs = bridge.call_infer_meta("InferMetaDynamicThenRequired", ir_meta, ctx) | ||
| 354 | + assert _output_dtypes(outputs) == [int(DataType.DT_FLOAT16)] * 3 | ||
| 355 | + assert len(outputs) == 3 | ||
| 356 | + | ||
| 357 | + | ||
| 358 | +# --------------------------------------------------------------------------- | ||
| 359 | +# Error cases | ||
| 360 | +# --------------------------------------------------------------------------- | ||
| 361 | + | ||
| 362 | + | ||
| 363 | +def test_call_infer_meta_wrong_return_type(): | ||
| 364 | + | ||
| 365 | + def infer_meta(x: TensorDesc) -> TensorDesc: | ||
| 366 | + return 42 | ||
| 367 | + | ||
| 368 | + ir_meta = _make_ir_meta( | ||
| 369 | + [_make_ir_input("x", InputType.REQUIRED)], | ||
| 370 | + [_make_ir_output("output0", OutputType.REQUIRED)], | ||
| 371 | + ) | ||
| 372 | + ctx = FakeInferMetaContext(ir_meta["inputs"], ir_meta["outputs"]) | ||
| 373 | + ctx.set_input_shape( | ||
| 374 | + 0, runtime_native.StorageShape([1], [1]), int(DataType.DT_FLOAT) | ||
| 375 | + ) | ||
| 376 | + | ||
| 377 | + with pytest.raises(TypeError, match="must be TensorDesc"): | ||
| 378 | + bridge.call_infer_meta("InferMetaBadReturn", ir_meta, ctx) | ||
| 379 | + | ||
| 380 | + | ||
| 381 | +def test_call_infer_meta_return_count_mismatch(): | ||
| 382 | + | ||
| 383 | + def infer_meta(x: TensorDesc) -> Tuple[TensorDesc, TensorDesc]: | ||
| 384 | + return x | ||
| 385 | + | ||
| 386 | + ir_meta = _make_ir_meta( | ||
| 387 | + [_make_ir_input("x", InputType.REQUIRED)], | ||
| 388 | + [ | ||
| 389 | + _make_ir_output("output0", OutputType.REQUIRED), | ||
| 390 | + _make_ir_output("output1", OutputType.REQUIRED), | ||
| 391 | + ], | ||
| 392 | + ) | ||
| 393 | + ctx = FakeInferMetaContext(ir_meta["inputs"], ir_meta["outputs"]) | ||
| 394 | + ctx.set_input_shape( | ||
| 395 | + 0, runtime_native.StorageShape([1], [1]), int(DataType.DT_FLOAT) | ||
| 396 | + ) | ||
| 397 | + | ||
| 398 | + with pytest.raises(TypeError, match="must return list/tuple for multiple outputs"): | ||
| 399 | + bridge.call_infer_meta("InferMetaCountMismatch", ir_meta, ctx) | ||
| 400 | + | ||
| 401 | + | ||
| 402 | +def test_call_infer_meta_dynamic_output_count_mismatch(): | ||
| 403 | + | ||
| 404 | + def infer_meta(x: TensorDesc) -> List[TensorDesc]: | ||
| 405 | + return [x, x, x] | ||
| 406 | + | ||
| 407 | + ir_meta = _make_ir_meta( | ||
| 408 | + [_make_ir_input("x", InputType.REQUIRED)], | ||
| 409 | + [_make_ir_output("output0", OutputType.DYNAMIC)], | ||
| 410 | + ) | ||
| 411 | + ctx = FakeInferMetaContext(ir_meta["inputs"], ir_meta["outputs"]) | ||
| 412 | + ctx.set_input_shape( | ||
| 413 | + 0, runtime_native.StorageShape([1], [1]), int(DataType.DT_FLOAT) | ||
| 414 | + ) | ||
| 415 | + ctx.set_dynamic_output_count(0, 2) | ||
| 416 | + | ||
| 417 | + with pytest.raises(TypeError, match="instance count mismatch"): | ||
| 418 | + bridge.call_infer_meta("InferMetaDynOutputMismatch", ir_meta, ctx) | ||
| 419 | + | ||
| 420 | + | ||
| 421 | +def test_call_infer_meta_context_invalidated_after_call(): | ||
| 422 | + | ||
| 423 | + def infer_meta(x: TensorDesc) -> TensorDesc: | ||
| 424 | + return x | ||
| 425 | + | ||
| 426 | + ir_meta = _make_ir_meta( | ||
| 427 | + [_make_ir_input("x", InputType.REQUIRED)], | ||
| 428 | + [_make_ir_output("output0", OutputType.REQUIRED)], | ||
| 429 | + ) | ||
| 430 | + ctx = FakeInferMetaContext(ir_meta["inputs"], ir_meta["outputs"]) | ||
| 431 | + ctx.set_input_shape( | ||
| 432 | + 0, runtime_native.StorageShape([1], [1]), int(DataType.DT_FLOAT) | ||
| 433 | + ) | ||
| 434 | + | ||
| 435 | + bridge.call_infer_meta("InferMetaInvalidate", ir_meta, ctx) | ||
| 436 | + assert ctx._invalidated is True | ||
| 437 | + | ||
| 438 | + | ||
| 439 | +def test_get_registered_op_proto_by_op_type(): | ||
| 440 | + | ||
| 441 | + def infer_meta() -> None: | ||
| 442 | + return None | ||
| 443 | + | ||
| 444 | + found = proto.get_registered_op_proto_by_op_type("ProtoByOpType") | ||
| 445 | + assert found is not None | ||
| 446 | + assert found.op_type == "ProtoByOpType" | ||
| 447 | + | ||
| 448 | + missing = proto.get_registered_op_proto_by_op_type("NonExistent") | ||
| 449 | + assert missing is None | ||
| 450 | + | ||
| 451 | + | ||
| 452 | +def test_call_infer_meta_changes_dtype(): | ||
| 453 | + | ||
| 454 | + def infer_meta(x: TensorDesc) -> TensorDesc: | ||
| 455 | + result = TensorDesc(x.shape, DataType.DT_INT32) | ||
| 456 | + return result | ||
| 457 | + | ||
| 458 | + ir_meta = _make_ir_meta( | ||
| 459 | + [_make_ir_input("x", InputType.REQUIRED)], | ||
| 460 | + [_make_ir_output("output0", OutputType.REQUIRED)], | ||
| 461 | + ) | ||
| 462 | + ctx = FakeInferMetaContext(ir_meta["inputs"], ir_meta["outputs"]) | ||
| 463 | + ctx.set_input_shape( | ||
| 464 | + 0, runtime_native.StorageShape([2, 3], [2, 3]), int(DataType.DT_FLOAT) | ||
| 465 | + ) | ||
| 466 | + | ||
| 467 | + outputs = bridge.call_infer_meta("InferMetaChangeDtype", ir_meta, ctx) | ||
| 468 | + assert _output_dtypes(outputs) == [int(DataType.DT_INT32)] | ||
| @@ -48,9 +48,14 @@ PythonCustomOpStringView StringView(const char *value) { | |||
| 48 | return PythonCustomOpStringView{value, (value == nullptr) ? 0U : std::strlen(value)}; | 48 | return PythonCustomOpStringView{value, (value == nullptr) ? 0U : std::strlen(value)}; |
| 49 | } | 49 | } |
| 50 | 50 | ||
| 51 | +graphStatus InferMeta(const PythonCustomOpStringView *, gert::InferShapeContext *, | ||
| 52 | + PythonCustomOpInferMetaResultView *) { | ||
| 53 | + return GRAPH_SUCCESS; | ||
| 54 | +} | ||
| 55 | + | ||
| 51 | PythonCustomOpProtoDescriptorView MakeProto(const char *descriptor_key, const char *op_type) { | 56 | PythonCustomOpProtoDescriptorView MakeProto(const char *descriptor_key, const char *op_type) { |
| 52 | return PythonCustomOpProtoDescriptorView{ | 57 | return PythonCustomOpProtoDescriptorView{ |
| 53 | - StringView(descriptor_key), StringView(op_type), nullptr, 0U, nullptr, 0U, nullptr, 0U}; | 58 | + StringView(descriptor_key), StringView(op_type), nullptr, 0U, nullptr, 0U, nullptr, 0U, &InferMeta}; |
| 54 | } | 59 | } |
| 55 | 60 | ||
| 56 | PythonCustomOpAdapterDescriptorView MakeAdapter(const char *op_type, const char *impl_key) { | 61 | PythonCustomOpAdapterDescriptorView MakeAdapter(const char *op_type, const char *impl_key) { |
| @@ -108,13 +113,13 @@ Status RegisterAdapterFailure(const PythonCustomOpRegistrar ®istrar) { | |||
| 108 | 113 | ||
| 109 | const auto callbacks = MakeCallbacks(false); | 114 | const auto callbacks = MakeCallbacks(false); |
| 110 | const auto adapter_a = MakeAdapter(kAdapterOpA, kAdapterImplKeyA); | 115 | const auto adapter_a = MakeAdapter(kAdapterOpA, kAdapterImplKeyA); |
| 111 | - if (!registrar.register_op_adapter(&adapter_a, &callbacks)) { | 116 | + if (!registrar.register_op_impl(&adapter_a, &callbacks)) { |
| 112 | return static_cast<Status>(GRAPH_FAILED); | 117 | return static_cast<Status>(GRAPH_FAILED); |
| 113 | } | 118 | } |
| 114 | const auto rejecting_callbacks = MakeCallbacks(true); | 119 | const auto rejecting_callbacks = MakeCallbacks(true); |
| 115 | const auto adapter_b = MakeAdapter(kAdapterOpB, kAdapterImplKeyB); | 120 | const auto adapter_b = MakeAdapter(kAdapterOpB, kAdapterImplKeyB); |
| 116 | - return static_cast<Status>(registrar.register_op_adapter(&adapter_b, &rejecting_callbacks) ? GRAPH_SUCCESS | 121 | + return static_cast<Status>(registrar.register_op_impl(&adapter_b, &rejecting_callbacks) ? GRAPH_SUCCESS |
| 117 | - : GRAPH_FAILED); | 122 | + : GRAPH_FAILED); |
| 118 | } | 123 | } |
| 119 | 124 | ||
| 120 | Status RegisterSuccess(const PythonCustomOpRegistrar ®istrar) { | 125 | Status RegisterSuccess(const PythonCustomOpRegistrar ®istrar) { |
| @@ -124,19 +129,18 @@ Status RegisterSuccess(const PythonCustomOpRegistrar ®istrar) { | |||
| 124 | } | 129 | } |
| 125 | const auto callbacks = MakeCallbacks(false); | 130 | const auto callbacks = MakeCallbacks(false); |
| 126 | const auto adapter = MakeAdapter(kSuccessOp, kSuccessImplKey); | 131 | const auto adapter = MakeAdapter(kSuccessOp, kSuccessImplKey); |
| 127 | - return static_cast<Status>(registrar.register_op_adapter(&adapter, &callbacks) ? GRAPH_SUCCESS : GRAPH_FAILED); | 132 | + return static_cast<Status>(registrar.register_op_impl(&adapter, &callbacks) ? GRAPH_SUCCESS : GRAPH_FAILED); |
| 128 | } | 133 | } |
| 129 | 134 | ||
| 130 | Status RegisterCppProtoImpl(const PythonCustomOpRegistrar ®istrar) { | 135 | Status RegisterCppProtoImpl(const PythonCustomOpRegistrar ®istrar) { |
| 131 | const auto callbacks = MakeCallbacks(false); | 136 | const auto callbacks = MakeCallbacks(false); |
| 132 | const auto adapter = MakeAdapter(kCppProtoOp, kCppProtoImplKey); | 137 | const auto adapter = MakeAdapter(kCppProtoOp, kCppProtoImplKey); |
| 133 | - return static_cast<Status>(registrar.register_op_adapter(&adapter, &callbacks) ? GRAPH_SUCCESS : GRAPH_FAILED); | 138 | + return static_cast<Status>(registrar.register_op_impl(&adapter, &callbacks) ? GRAPH_SUCCESS : GRAPH_FAILED); |
| 134 | } | 139 | } |
| 135 | 140 | ||
| 136 | Status RegisterCustomOps(const PythonCustomOpRegistrar *registrar) { | 141 | Status RegisterCustomOps(const PythonCustomOpRegistrar *registrar) { |
| 137 | ++g_register_count; | 142 | ++g_register_count; |
| 138 | - if ((registrar == nullptr) || (registrar->register_op_proto == nullptr) || | 143 | + if ((registrar == nullptr) || (registrar->register_op_proto == nullptr) || (registrar->register_op_impl == nullptr)) { |
| 139 | - (registrar->register_op_adapter == nullptr)) { | ||
| 140 | return static_cast<Status>(GRAPH_FAILED); | 144 | return static_cast<Status>(GRAPH_FAILED); |
| 141 | } | 145 | } |
| 142 | const char *scenario = std::getenv(kScenarioEnvName); | 146 | const char *scenario = std::getenv(kScenarioEnvName); |
| @@ -345,19 +345,20 @@ TEST_F(PythonCustomOpBridgeLoaderTest, keeps_partial_proto_registration_until_un | |||
| 345 | 345 | ||
| 346 | EXPECT_EQ(LoadPythonCustomOps(), FAILED); | 346 | EXPECT_EQ(LoadPythonCustomOps(), FAILED); |
| 347 | EXPECT_TRUE(OperatorFactory::IsExistOp(kMultiProtoOp)); | 347 | EXPECT_TRUE(OperatorFactory::IsExistOp(kMultiProtoOp)); |
| 348 | + EXPECT_FALSE(CustomOpFactory::IsExistOp(AscendString(kMultiProtoOp))); | ||
| 348 | UnloadPythonCustomOps(); | 349 | UnloadPythonCustomOps(); |
| 349 | EXPECT_FALSE(OperatorFactory::IsExistOp(kMultiProtoOp)); | 350 | EXPECT_FALSE(OperatorFactory::IsExistOp(kMultiProtoOp)); |
| 350 | EXPECT_EQ(GetRegisterCount(), 1U); | 351 | EXPECT_EQ(GetRegisterCount(), 1U); |
| 351 | EXPECT_EQ(GetResetCount(), 1U); | 352 | EXPECT_EQ(GetResetCount(), 1U); |
| 352 | } | 353 | } |
| 353 | 354 | ||
| 354 | -TEST_F(PythonCustomOpBridgeLoaderTest, rolls_back_proto_adapter_and_runtime_entry_after_adapter_failure) { | 355 | +TEST_F(PythonCustomOpBridgeLoaderTest, does_not_register_adapter_creator_when_impl_registration_fails) { |
| 355 | SetScenario(kAdapterFailure); | 356 | SetScenario(kAdapterFailure); |
| 356 | 357 | ||
| 357 | EXPECT_EQ(LoadPythonCustomOps(), FAILED); | 358 | EXPECT_EQ(LoadPythonCustomOps(), FAILED); |
| 358 | EXPECT_TRUE(OperatorFactory::IsExistOp(kAdapterOpA)); | 359 | EXPECT_TRUE(OperatorFactory::IsExistOp(kAdapterOpA)); |
| 359 | EXPECT_TRUE(OperatorFactory::IsExistOp(kAdapterOpB)); | 360 | EXPECT_TRUE(OperatorFactory::IsExistOp(kAdapterOpB)); |
| 360 | - EXPECT_TRUE(CustomOpFactory::IsExistOp(AscendString(kAdapterOpA))); | 361 | + EXPECT_FALSE(CustomOpFactory::IsExistOp(AscendString(kAdapterOpA))); |
| 361 | UnloadPythonCustomOps(); | 362 | UnloadPythonCustomOps(); |
| 362 | EXPECT_FALSE(OperatorFactory::IsExistOp(kAdapterOpA)); | 363 | EXPECT_FALSE(OperatorFactory::IsExistOp(kAdapterOpA)); |
| 363 | EXPECT_FALSE(OperatorFactory::IsExistOp(kAdapterOpB)); | 364 | EXPECT_FALSE(OperatorFactory::IsExistOp(kAdapterOpB)); |
| @@ -403,7 +404,16 @@ TEST_F(PythonCustomOpBridgeLoaderTest, direct_load_registers_each_call_and_unloa | |||
| 403 | EXPECT_EQ(GetRegisterCount(), 2U); | 404 | EXPECT_EQ(GetRegisterCount(), 2U); |
| 404 | EXPECT_TRUE(OperatorFactory::IsExistOp(kSuccessOp)); | 405 | EXPECT_TRUE(OperatorFactory::IsExistOp(kSuccessOp)); |
| 405 | EXPECT_TRUE(CustomOpFactory::IsExistOp(AscendString(kSuccessOp))); | 406 | EXPECT_TRUE(CustomOpFactory::IsExistOp(AscendString(kSuccessOp))); |
| 406 | - EXPECT_NE(CustomOpFactory::CreateOrGetCustomOp(AscendString(kSuccessOp)), nullptr); | 407 | + auto *custom_op = CustomOpFactory::CreateOrGetCustomOp(AscendString(kSuccessOp)); |
| 408 | + ASSERT_NE(custom_op, nullptr); | ||
| 409 | + EXPECT_NE(CustomOpCast<ShapeInferOp>(custom_op), nullptr); | ||
| 410 | + auto *eager_op = CustomOpCast<EagerExecuteOp>(custom_op); | ||
| 411 | + ASSERT_NE(eager_op, nullptr); | ||
| 412 | + EXPECT_EQ(eager_op->Execute(nullptr), GRAPH_SUCCESS); | ||
| 413 | + PythonCustomOpAdapterCallbacks loaded_callbacks; | ||
| 414 | + const auto loaded_desc = MakeAdapterDescriptor(kSuccessOp, kSuccessImplKey); | ||
| 415 | + ASSERT_TRUE(PythonCustomOpImplRuntimeRegistry::Acquire(loaded_desc, loaded_callbacks)); | ||
| 416 | + PythonCustomOpImplRuntimeRegistry::Release(loaded_desc); | ||
| 407 | 417 | ||
| 408 | UnloadPythonCustomOps(); | 418 | UnloadPythonCustomOps(); |
| 409 | EXPECT_FALSE(OperatorFactory::IsExistOp(kSuccessOp)); | 419 | EXPECT_FALSE(OperatorFactory::IsExistOp(kSuccessOp)); |
| @@ -0,0 +1,227 @@ | |||
| 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 | +namespace { | ||
| 25 | + | ||
| 26 | +graphStatus g_infer_meta_call_count = 0; | ||
| 27 | +graphStatus g_infer_meta_return_value = GRAPH_SUCCESS; | ||
| 28 | + | ||
| 29 | +graphStatus FakeInferMetaBridge(const std::string &op_type, gert::InferShapeContext *ctx, | ||
| 30 | + CustomOpInferMetaResult *result) { | ||
| 31 | + (void)op_type; | ||
| 32 | + (void)ctx; | ||
| 33 | + ++g_infer_meta_call_count; | ||
| 34 | + if (result != nullptr) { | ||
| 35 | + result->outputs.resize(2U); | ||
| 36 | + result->outputs[0U].shape = gert::StorageShape{{2}, {2}}; | ||
| 37 | + result->outputs[0U].data_type = DT_FLOAT; | ||
| 38 | + result->outputs[1U].shape = gert::StorageShape{{3}, {3}}; | ||
| 39 | + result->outputs[1U].data_type = DT_INT32; | ||
| 40 | + } | ||
| 41 | + return g_infer_meta_return_value; | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +graphStatus FakePythonInferMetaBridge(const PythonCustomOpStringView *, gert::InferShapeContext *, | ||
| 45 | + PythonCustomOpInferMetaResultView *) { | ||
| 46 | + return GRAPH_SUCCESS; | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +class InferMetaProviderTestOp final : public CustomOpInferMetaProvider { | ||
| 50 | + public: | ||
| 51 | + graphStatus InferMeta(gert::InferShapeContext *ctx, CustomOpInferMetaResult *result) override { | ||
| 52 | + return FakeInferMetaBridge("InferMetaProviderTestOp", ctx, result); | ||
| 53 | + } | ||
| 54 | +}; | ||
| 55 | + | ||
| 56 | +class NonInferMetaTestOp final : public BaseCustomOp {}; | ||
| 57 | + | ||
| 58 | +class PythonCustomOpInferMetaProviderTest : public testing::Test { | ||
| 59 | + protected: | ||
| 60 | + void SetUp() override { | ||
| 61 | + g_infer_meta_call_count = 0; | ||
| 62 | + g_infer_meta_return_value = GRAPH_SUCCESS; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + void TearDown() override { | ||
| 66 | + CustomOpFactory::RemoveCustomOps({AscendString("InferMetaProviderTestOp")}); | ||
| 67 | + CustomOpFactory::RemoveCustomOps({AscendString("NonInferMetaTestOp")}); | ||
| 68 | + CustomOpFactory::RemoveCustomOps({AscendString("PythonInferOnlyUt")}); | ||
| 69 | + } | ||
| 70 | +}; | ||
| 71 | + | ||
| 72 | +TEST_F(PythonCustomOpInferMetaProviderTest, dynamic_cast_identifies_infer_meta_provider) { | ||
| 73 | + ASSERT_EQ(CustomOpFactory::RegisterCustomOpCreator( | ||
| 74 | + AscendString("InferMetaProviderTestOp"), | ||
| 75 | + []() -> std::unique_ptr<BaseCustomOp> { return std::make_unique<InferMetaProviderTestOp>(); }), | ||
| 76 | + GRAPH_SUCCESS); | ||
| 77 | + | ||
| 78 | + auto op = CustomOpFactory::CreateOrGetCustomOp(AscendString("InferMetaProviderTestOp")); | ||
| 79 | + ASSERT_NE(op, nullptr); | ||
| 80 | + | ||
| 81 | + auto *provider = dynamic_cast<CustomOpInferMetaProvider *>(op); | ||
| 82 | + EXPECT_NE(provider, nullptr); | ||
| 83 | + | ||
| 84 | + CustomOpInferMetaResult result; | ||
| 85 | + auto ret = provider->InferMeta(nullptr, &result); | ||
| 86 | + EXPECT_EQ(ret, GRAPH_SUCCESS); | ||
| 87 | + EXPECT_EQ(g_infer_meta_call_count, 1); | ||
| 88 | + EXPECT_EQ(result.outputs.size(), 2U); | ||
| 89 | + EXPECT_EQ(result.outputs[0].shape.GetStorageShape(), gert::Shape({2})); | ||
| 90 | + EXPECT_EQ(result.outputs[1].shape.GetStorageShape(), gert::Shape({3})); | ||
| 91 | + EXPECT_EQ(result.outputs[0].data_type, DT_FLOAT); | ||
| 92 | + EXPECT_EQ(result.outputs[1].data_type, DT_INT32); | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +TEST_F(PythonCustomOpInferMetaProviderTest, dynamic_cast_returns_null_for_non_provider) { | ||
| 96 | + ASSERT_EQ(CustomOpFactory::RegisterCustomOpCreator( | ||
| 97 | + AscendString("NonInferMetaTestOp"), | ||
| 98 | + []() -> std::unique_ptr<BaseCustomOp> { return std::make_unique<NonInferMetaTestOp>(); }), | ||
| 99 | + GRAPH_SUCCESS); | ||
| 100 | + | ||
| 101 | + auto op = CustomOpFactory::CreateOrGetCustomOp(AscendString("NonInferMetaTestOp")); | ||
| 102 | + ASSERT_NE(op, nullptr); | ||
| 103 | + | ||
| 104 | + auto *provider = dynamic_cast<CustomOpInferMetaProvider *>(op); | ||
| 105 | + EXPECT_EQ(provider, nullptr); | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | +TEST_F(PythonCustomOpInferMetaProviderTest, custom_op_cast_preserves_shape_infer_op_fallback) { | ||
| 109 | + ASSERT_EQ(CustomOpFactory::RegisterCustomOpCreator( | ||
| 110 | + AscendString("NonInferMetaTestOp"), | ||
| 111 | + []() -> std::unique_ptr<BaseCustomOp> { return std::make_unique<NonInferMetaTestOp>(); }), | ||
| 112 | + GRAPH_SUCCESS); | ||
| 113 | + | ||
| 114 | + auto op = CustomOpFactory::CreateOrGetCustomOp(AscendString("NonInferMetaTestOp")); | ||
| 115 | + ASSERT_NE(op, nullptr); | ||
| 116 | + | ||
| 117 | + auto *shape_infer = CustomOpCast<ShapeInferOp>(op); | ||
| 118 | + EXPECT_EQ(shape_infer, nullptr); | ||
| 119 | +} | ||
| 120 | + | ||
| 121 | +TEST_F(PythonCustomOpInferMetaProviderTest, infer_meta_returns_failure_propagates) { | ||
| 122 | + ASSERT_EQ(CustomOpFactory::RegisterCustomOpCreator( | ||
| 123 | + AscendString("InferMetaProviderTestOp"), | ||
| 124 | + []() -> std::unique_ptr<BaseCustomOp> { return std::make_unique<InferMetaProviderTestOp>(); }), | ||
| 125 | + GRAPH_SUCCESS); | ||
| 126 | + | ||
| 127 | + auto op = CustomOpFactory::CreateOrGetCustomOp(AscendString("InferMetaProviderTestOp")); | ||
| 128 | + ASSERT_NE(op, nullptr); | ||
| 129 | + auto *provider = dynamic_cast<CustomOpInferMetaProvider *>(op); | ||
| 130 | + ASSERT_NE(provider, nullptr); | ||
| 131 | + | ||
| 132 | + g_infer_meta_return_value = GRAPH_FAILED; | ||
| 133 | + CustomOpInferMetaResult result; | ||
| 134 | + auto ret = provider->InferMeta(nullptr, &result); | ||
| 135 | + EXPECT_EQ(ret, GRAPH_FAILED); | ||
| 136 | +} | ||
| 137 | + | ||
| 138 | +TEST_F(PythonCustomOpInferMetaProviderTest, infer_meta_result_default_is_empty) { | ||
| 139 | + CustomOpInferMetaResult result; | ||
| 140 | + EXPECT_TRUE(result.outputs.empty()); | ||
| 141 | +} | ||
| 142 | + | ||
| 143 | +TEST_F(PythonCustomOpInferMetaProviderTest, python_custom_op_adapter_inherits_infer_meta_provider) { | ||
| 144 | + PythonCustomOpAdapterDescriptor desc; | ||
| 145 | + desc.op_type = "PythonAdapterInferMetaUt"; | ||
| 146 | + desc.impl_descriptor_key = "ut:python_adapter_infer_meta"; | ||
| 147 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); | ||
| 148 | + | ||
| 149 | + PythonCustomOpAdapterCallbacks callbacks; | ||
| 150 | + callbacks.create_impl_holder = [](const PythonCustomOpAdapterDescriptorView *) -> void * { return new int(1); }; | ||
| 151 | + callbacks.destroy_impl_holder = [](void *holder) { delete static_cast<int *>(holder); }; | ||
| 152 | + callbacks.execute = [](const void *, gert::EagerOpExecutionContext *) -> graphStatus { return GRAPH_SUCCESS; }; | ||
| 153 | + | ||
| 154 | + ASSERT_TRUE(PythonCustomOpImplRuntimeRegistry::Register(desc, callbacks)); | ||
| 155 | + | ||
| 156 | + PythonCustomOpAdapter adapter(desc); | ||
| 157 | + ASSERT_TRUE(adapter.IsValid()); | ||
| 158 | + | ||
| 159 | + auto *provider = dynamic_cast<CustomOpInferMetaProvider *>(&adapter); | ||
| 160 | + EXPECT_NE(provider, nullptr); | ||
| 161 | + EXPECT_EQ(CustomOpCast<CustomOpInferMetaProvider>(&adapter), nullptr); | ||
| 162 | + EXPECT_EQ(CustomOpCast<ShapeInferOp>(&adapter), nullptr); | ||
| 163 | + | ||
| 164 | + PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key); | ||
| 165 | +} | ||
| 166 | + | ||
| 167 | +TEST_F(PythonCustomOpInferMetaProviderTest, python_custom_op_adapter_infer_only_via_descriptor) { | ||
| 168 | + PythonCustomOpAdapterDescriptor desc; | ||
| 169 | + desc.op_type = "PythonInferOnlyUt"; | ||
| 170 | + desc.infer_meta = &FakePythonInferMetaBridge; | ||
| 171 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kShapeInfer); | ||
| 172 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kInferMeta); | ||
| 173 | + | ||
| 174 | + PythonCustomOpAdapter adapter(desc); | ||
| 175 | + EXPECT_TRUE(adapter.IsValid()); | ||
| 176 | + | ||
| 177 | + auto *provider = dynamic_cast<CustomOpInferMetaProvider *>(&adapter); | ||
| 178 | + EXPECT_NE(provider, nullptr); | ||
| 179 | + EXPECT_NE(CustomOpCast<CustomOpInferMetaProvider>(&adapter), nullptr); | ||
| 180 | + auto *shape_infer = CustomOpCast<ShapeInferOp>(&adapter); | ||
| 181 | + EXPECT_NE(shape_infer, nullptr); | ||
| 182 | +} | ||
| 183 | + | ||
| 184 | +TEST_F(PythonCustomOpInferMetaProviderTest, python_custom_op_adapter_infer_only_registered_as_custom_op_creator) { | ||
| 185 | + ASSERT_EQ(CustomOpFactory::RegisterCustomOpCreator( | ||
| 186 | + AscendString("PythonInferOnlyUt"), | ||
| 187 | + []() -> std::unique_ptr<BaseCustomOp> { | ||
| 188 | + PythonCustomOpAdapterDescriptor desc; | ||
| 189 | + desc.op_type = "PythonInferOnlyUt"; | ||
| 190 | + desc.infer_meta = &FakePythonInferMetaBridge; | ||
| 191 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kShapeInfer); | ||
| 192 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kInferMeta); | ||
| 193 | + return std::make_unique<PythonCustomOpAdapter>(desc); | ||
| 194 | + }), | ||
| 195 | + GRAPH_SUCCESS); | ||
| 196 | + auto op = CustomOpFactory::CreateOrGetCustomOp(AscendString("PythonInferOnlyUt")); | ||
| 197 | + ASSERT_NE(op, nullptr); | ||
| 198 | + EXPECT_NE(dynamic_cast<CustomOpInferMetaProvider *>(op), nullptr); | ||
| 199 | + EXPECT_NE(dynamic_cast<ShapeInferOp *>(op), nullptr); | ||
| 200 | +} | ||
| 201 | + | ||
| 202 | +TEST_F(PythonCustomOpInferMetaProviderTest, python_custom_op_adapter_infer_meta_fails_without_callback) { | ||
| 203 | + PythonCustomOpAdapterDescriptor desc; | ||
| 204 | + desc.op_type = "PythonAdapterInferMetaNoCallbackUt"; | ||
| 205 | + desc.impl_descriptor_key = "ut:python_adapter_infer_meta_no_callback"; | ||
| 206 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); | ||
| 207 | + | ||
| 208 | + PythonCustomOpAdapterCallbacks callbacks; | ||
| 209 | + callbacks.create_impl_holder = [](const PythonCustomOpAdapterDescriptorView *) -> void * { return new int(1); }; | ||
| 210 | + callbacks.destroy_impl_holder = [](void *holder) { delete static_cast<int *>(holder); }; | ||
| 211 | + callbacks.execute = [](const void *, gert::EagerOpExecutionContext *) -> graphStatus { return GRAPH_SUCCESS; }; | ||
| 212 | + | ||
| 213 | + ASSERT_TRUE(PythonCustomOpImplRuntimeRegistry::Register(desc, callbacks)); | ||
| 214 | + | ||
| 215 | + PythonCustomOpAdapter adapter(desc); | ||
| 216 | + ASSERT_TRUE(adapter.IsValid()); | ||
| 217 | + | ||
| 218 | + CustomOpInferMetaResult result; | ||
| 219 | + auto ret = adapter.InferMeta(nullptr, &result); | ||
| 220 | + EXPECT_EQ(ret, GRAPH_FAILED); | ||
| 221 | + | ||
| 222 | + PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key); | ||
| 223 | +} | ||
| 224 | + | ||
| 225 | +} // namespace | ||
| 226 | +} // namespace custom_op | ||
| 227 | +} // namespace ge | ||
| @@ -37,7 +37,15 @@ PythonCustomOpProtoDescriptorView MakeProtoView(const char *descriptor_key, cons | |||
| 37 | const PythonCustomOpProtoOutputView *outputs, | 37 | const PythonCustomOpProtoOutputView *outputs, |
| 38 | const size_t output_count) { | 38 | const size_t output_count) { |
| 39 | return PythonCustomOpProtoDescriptorView{ | 39 | return PythonCustomOpProtoDescriptorView{ |
| 40 | - StringView(descriptor_key), StringView(op_type), inputs, input_count, attrs, attr_count, outputs, output_count, | 40 | + StringView(descriptor_key), |
| 41 | + StringView(op_type), | ||
| 42 | + inputs, | ||
| 43 | + input_count, | ||
| 44 | + attrs, | ||
| 45 | + attr_count, | ||
| 46 | + outputs, | ||
| 47 | + output_count, | ||
| 48 | + nullptr, | ||
| 41 | }; | 49 | }; |
| 42 | } | 50 | } |
| 43 | 51 | ||
| @@ -65,6 +65,7 @@ RETURN_STATEMENTS = { | |||
| 65 | "SubgraphBuilder": " return nullptr;", | 65 | "SubgraphBuilder": " return nullptr;", |
| 66 | "OperatorImplPtr": " return nullptr;", | 66 | "OperatorImplPtr": " return nullptr;", |
| 67 | "CustomOpRegistryPtr": " return nullptr;", | 67 | "CustomOpRegistryPtr": " return nullptr;", |
| 68 | + "CustomOpInferMetaFunc": " return nullptr;", | ||
| 68 | "OutHandler": " return nullptr;", | 69 | "OutHandler": " return nullptr;", |
| 69 | "std::vector<int64_t>": " return {};", | 70 | "std::vector<int64_t>": " return {};", |
| 70 | "std::vector<std::string>": " return {};", | 71 | "std::vector<std::string>": " return {};", |
| @@ -34,6 +34,8 @@ | |||
| 34 | 34 | ||
| 35 | 35 | ||
| 36 | 36 | ||
| 37 | + | ||
| 38 | + | ||
| 37 | 39 | ||
| 38 | namespace ge { | 40 | namespace ge { |
| 39 | REG_OP(Const) | 41 | REG_OP(Const) |
| @@ -71,6 +73,83 @@ static const CustomOpCreatorRegister g_custom_shape_infer_add_register( | |||
| 71 | "CustomShapeInferAdd", | 73 | "CustomShapeInferAdd", |
| 72 | []() -> std::unique_ptr<BaseCustomOp> { return std::make_unique<CustomShapeInferAddImpl>(); }); | 74 | []() -> std::unique_ptr<BaseCustomOp> { return std::make_unique<CustomShapeInferAddImpl>(); }); |
| 73 | 75 | ||
| 76 | +enum class InferMetaAtomicMode { kSuccess, kFailOnSecondOutput, kResultCountMismatch }; | ||
| 77 | +InferMetaAtomicMode g_infer_meta_atomic_mode = InferMetaAtomicMode::kSuccess; | ||
| 78 | + | ||
| 79 | +class CustomInferMetaAtomicImpl : public CustomOpInferMetaProvider { | ||
| 80 | + public: | ||
| 81 | + graphStatus InferMeta(gert::InferShapeContext *ctx, CustomOpInferMetaResult *result) override { | ||
| 82 | + for (size_t i = 0U; i < ctx->GetComputeNodeOutputNum(); ++i) { | ||
| 83 | + CustomOpInferMetaOutput output; | ||
| 84 | + output.shape = gert::StorageShape{{static_cast<int64_t>(i + 2U), static_cast<int64_t>(i + 10U)}, | ||
| 85 | + {static_cast<int64_t>(i + 2U), static_cast<int64_t>(i + 10U)}}; | ||
| 86 | + output.data_type = (i == 0U) ? DT_FLOAT : ((i == 1U) ? DT_INT32 : DT_BOOL); | ||
| 87 | + result->outputs.emplace_back(std::move(output)); | ||
| 88 | + if ((g_infer_meta_atomic_mode == InferMetaAtomicMode::kFailOnSecondOutput) && (i == 1U)) { | ||
| 89 | + return GRAPH_FAILED; | ||
| 90 | + } | ||
| 91 | + } | ||
| 92 | + if (g_infer_meta_atomic_mode == InferMetaAtomicMode::kResultCountMismatch) { | ||
| 93 | + result->outputs.pop_back(); | ||
| 94 | + } | ||
| 95 | + return GRAPH_SUCCESS; | ||
| 96 | + } | ||
| 97 | +}; | ||
| 98 | + | ||
| 99 | +static const CustomOpCreatorRegister g_custom_infer_meta_atomic_register( | ||
| 100 | + "CustomInferMetaAtomic", | ||
| 101 | + []() -> std::unique_ptr<BaseCustomOp> { return std::make_unique<CustomInferMetaAtomicImpl>(); }); | ||
| 102 | + | ||
| 103 | +REG_OP(CustomInferMetaAtomic) | ||
| 104 | + .INPUT(x, TensorType::ALL()) | ||
| 105 | + .DYNAMIC_OUTPUT(ys, TensorType::ALL()) | ||
| 106 | + .OUTPUT(z, TensorType::ALL()) | ||
| 107 | + .OP_END_FACTORY_REG(CustomInferMetaAtomic); | ||
| 108 | + | ||
| 109 | +std::pair<Operator, OpDescPtr> CreateCustomInferMetaAtomicOp(const std::string &name) { | ||
| 110 | + auto op = op::CustomInferMetaAtomic(name); | ||
| 111 | + op.create_dynamic_output_ys(2U); | ||
| 112 | + auto op_desc = OpDescUtils::GetOpDescFromOperator(op); | ||
| 113 | + if (op_desc == nullptr) { | ||
| 114 | + return {op, nullptr}; | ||
| 115 | + } | ||
| 116 | + GeTensorDesc input_desc(GeShape({4, 5}), FORMAT_ND, DT_FLOAT16); | ||
| 117 | + input_desc.SetOriginShape(GeShape({4, 5})); | ||
| 118 | + input_desc.SetOriginDataType(DT_FLOAT16); | ||
| 119 | + (void)op_desc->UpdateInputDesc(0U, input_desc); | ||
| 120 | + for (size_t i = 0U; i < op_desc->GetOutputsSize(); ++i) { | ||
| 121 | + GeTensorDesc output_desc(GeShape({static_cast<int64_t>(100U + i)}), FORMAT_ND, DT_FLOAT16); | ||
| 122 | + output_desc.SetOriginShape(GeShape({static_cast<int64_t>(200U + i)})); | ||
| 123 | + output_desc.SetOriginDataType(DT_FLOAT16); | ||
| 124 | + (void)op_desc->UpdateOutputDesc(static_cast<uint32_t>(i), output_desc); | ||
| 125 | + } | ||
| 126 | + return {op, op_desc}; | ||
| 127 | +} | ||
| 128 | + | ||
| 129 | +void ExpectOutputMetaUnchanged(const OpDescPtr &op_desc, const std::vector<GeTensorDesc> &before) { | ||
| 130 | + ASSERT_NE(op_desc, nullptr); | ||
| 131 | + ASSERT_EQ(op_desc->GetOutputsSize(), before.size()); | ||
| 132 | + for (size_t i = 0U; i < before.size(); ++i) { | ||
| 133 | + const auto &actual = op_desc->GetOutputDesc(static_cast<uint32_t>(i)); | ||
| 134 | + EXPECT_EQ(actual.GetShape(), before[i].GetShape()); | ||
| 135 | + EXPECT_EQ(actual.GetOriginShape(), before[i].GetOriginShape()); | ||
| 136 | + EXPECT_EQ(actual.GetDataType(), before[i].GetDataType()); | ||
| 137 | + EXPECT_EQ(actual.GetOriginDataType(), before[i].GetOriginDataType()); | ||
| 138 | + } | ||
| 139 | +} | ||
| 140 | + | ||
| 141 | +std::vector<GeTensorDesc> GetOutputMeta(const OpDescPtr &op_desc) { | ||
| 142 | + std::vector<GeTensorDesc> outputs; | ||
| 143 | + if (op_desc == nullptr) { | ||
| 144 | + return outputs; | ||
| 145 | + } | ||
| 146 | + outputs.reserve(op_desc->GetOutputsSize()); | ||
| 147 | + for (size_t i = 0U; i < op_desc->GetOutputsSize(); ++i) { | ||
| 148 | + outputs.emplace_back(op_desc->GetOutputDesc(static_cast<uint32_t>(i))); | ||
| 149 | + } | ||
| 150 | + return outputs; | ||
| 151 | +} | ||
| 152 | + | ||
| 74 | class ShapeInferenceUT : public testing::Test {}; | 153 | class ShapeInferenceUT : public testing::Test {}; |
| 75 | // infer from output | 154 | // infer from output |
| 76 | REG_OP(FixIOOp_OutputIsFix) | 155 | REG_OP(FixIOOp_OutputIsFix) |
| @@ -1490,6 +1569,93 @@ REG_OP(TwoOptionalInputsOp) | |||
| 1490 | EXPECT_EQ(op_desc->GetOutputDesc(0U).GetOriginDataType(), DT_INT64); | 1569 | EXPECT_EQ(op_desc->GetOutputDesc(0U).GetOriginDataType(), DT_INT64); |
| 1491 | } | 1570 | } |
| 1492 | 1571 | ||
| 1572 | + TEST_F(ShapeInferenceUT, CustomOpInferMetaFailureOnSecondOutputKeepsAllOutputMeta) { | ||
| 1573 | + auto op_and_desc = CreateCustomInferMetaAtomicOp("infer_meta_second_output_failure"); | ||
| 1574 | + auto &op = op_and_desc.first; | ||
| 1575 | + const auto &op_desc = op_and_desc.second; | ||
| 1576 | + ASSERT_NE(op_desc, nullptr); | ||
| 1577 | + const auto before = GetOutputMeta(op_desc); | ||
| 1578 | + | ||
| 1579 | + gert::SpaceRegistryFaker::CreateDefaultSpaceRegistryImpl2(true); | ||
| 1580 | + const auto space_registry = gert::DefaultOpImplSpaceRegistryV2::GetInstance().GetSpaceRegistry(); | ||
| 1581 | + ASSERT_NE(space_registry, nullptr); | ||
| 1582 | + (void)space_registry->CreateOrGetOpImpl("CustomInferMetaAtomic"); | ||
| 1583 | + | ||
| 1584 | + g_infer_meta_atomic_mode = InferMetaAtomicMode::kFailOnSecondOutput; | ||
| 1585 | + EXPECT_EQ(OpDescUtilsEx::CallInferFunc(op_desc, op), GRAPH_FAILED); | ||
| 1586 | + ExpectOutputMetaUnchanged(op_desc, before); | ||
| 1587 | + g_infer_meta_atomic_mode = InferMetaAtomicMode::kSuccess; | ||
| 1588 | + } | ||
| 1589 | + | ||
| 1590 | + TEST_F(ShapeInferenceUT, CustomOpInferMetaResultCountMismatchKeepsAllOutputMeta) { | ||
| 1591 | + auto op_and_desc = CreateCustomInferMetaAtomicOp("infer_meta_result_count_mismatch"); | ||
| 1592 | + auto &op = op_and_desc.first; | ||
| 1593 | + const auto &op_desc = op_and_desc.second; | ||
| 1594 | + ASSERT_NE(op_desc, nullptr); | ||
| 1595 | + const auto before = GetOutputMeta(op_desc); | ||
| 1596 | + | ||
| 1597 | + gert::SpaceRegistryFaker::CreateDefaultSpaceRegistryImpl2(true); | ||
| 1598 | + const auto space_registry = gert::DefaultOpImplSpaceRegistryV2::GetInstance().GetSpaceRegistry(); | ||
| 1599 | + ASSERT_NE(space_registry, nullptr); | ||
| 1600 | + (void)space_registry->CreateOrGetOpImpl("CustomInferMetaAtomic"); | ||
| 1601 | + | ||
| 1602 | + g_infer_meta_atomic_mode = InferMetaAtomicMode::kResultCountMismatch; | ||
| 1603 | + EXPECT_EQ(OpDescUtilsEx::CallInferFunc(op_desc, op), GRAPH_FAILED); | ||
| 1604 | + ExpectOutputMetaUnchanged(op_desc, before); | ||
| 1605 | + g_infer_meta_atomic_mode = InferMetaAtomicMode::kSuccess; | ||
| 1606 | + } | ||
| 1607 | + | ||
| 1608 | + TEST_F(ShapeInferenceUT, CustomOpInferMetaCommitsDynamicAndRequiredOutputsTogether) { | ||
| 1609 | + auto op_and_desc = CreateCustomInferMetaAtomicOp("infer_meta_complete_commit"); | ||
| 1610 | + auto &op = op_and_desc.first; | ||
| 1611 | + const auto &op_desc = op_and_desc.second; | ||
| 1612 | + ASSERT_NE(op_desc, nullptr); | ||
| 1613 | + | ||
| 1614 | + gert::SpaceRegistryFaker::CreateDefaultSpaceRegistryImpl2(true); | ||
| 1615 | + const auto space_registry = gert::DefaultOpImplSpaceRegistryV2::GetInstance().GetSpaceRegistry(); | ||
| 1616 | + ASSERT_NE(space_registry, nullptr); | ||
| 1617 | + (void)space_registry->CreateOrGetOpImpl("CustomInferMetaAtomic"); | ||
| 1618 | + | ||
| 1619 | + g_infer_meta_atomic_mode = InferMetaAtomicMode::kSuccess; | ||
| 1620 | + ASSERT_EQ(OpDescUtilsEx::CallInferFunc(op_desc, op), GRAPH_SUCCESS); | ||
| 1621 | + const std::vector<DataType> expected_dtypes = {DT_FLOAT, DT_INT32, DT_BOOL}; | ||
| 1622 | + ASSERT_EQ(op_desc->GetOutputsSize(), expected_dtypes.size()); | ||
| 1623 | + for (size_t i = 0U; i < expected_dtypes.size(); ++i) { | ||
| 1624 | + const auto &output_desc = op_desc->GetOutputDesc(static_cast<uint32_t>(i)); | ||
| 1625 | + EXPECT_EQ(output_desc.GetShape().GetDims(), | ||
| 1626 | + std::vector<int64_t>({static_cast<int64_t>(i + 2U), static_cast<int64_t>(i + 10U)})); | ||
| 1627 | + EXPECT_EQ(output_desc.GetOriginShape(), output_desc.GetShape()); | ||
| 1628 | + EXPECT_EQ(output_desc.GetDataType(), expected_dtypes[i]); | ||
| 1629 | + EXPECT_EQ(output_desc.GetOriginDataType(), expected_dtypes[i]); | ||
| 1630 | + } | ||
| 1631 | + } | ||
| 1632 | + | ||
| 1633 | + TEST_F(ShapeInferenceUT, CustomOpInferMetaStagedDescKeepsOriginalWhenFormatUpdateFails) { | ||
| 1634 | + auto op_and_desc = CreateCustomInferMetaAtomicOp("infer_meta_format_failure"); | ||
| 1635 | + const auto &op_desc = op_and_desc.second; | ||
| 1636 | + ASSERT_NE(op_desc, nullptr); | ||
| 1637 | + GeTensorDesc transformed_desc(GeShape({3, 224, 224}), FORMAT_NCHW, DT_FLOAT16); | ||
| 1638 | + transformed_desc.SetOriginShape(GeShape({3, 224, 224})); | ||
| 1639 | + transformed_desc.SetOriginFormat(FORMAT_ND); | ||
| 1640 | + transformed_desc.SetOriginDataType(DT_FLOAT16); | ||
| 1641 | + ASSERT_EQ(op_desc->UpdateOutputDesc(0U, transformed_desc), GRAPH_SUCCESS); | ||
| 1642 | + const auto original_output = op_desc->GetOutputDesc(0U); | ||
| 1643 | + | ||
| 1644 | + const auto staged_op_desc = std::make_shared<OpDesc>(*op_desc); | ||
| 1645 | + ASSERT_NE(staged_op_desc, nullptr); | ||
| 1646 | + NodeShapeTransUtils transformer(staged_op_desc); | ||
| 1647 | + ASSERT_TRUE(transformer.Init()); | ||
| 1648 | + ASSERT_TRUE(transformer.CatchFormatAndShape()); | ||
| 1649 | + staged_op_desc->MutableOutputDesc(0U)->SetFormat(FORMAT_HWCN); | ||
| 1650 | + EXPECT_FALSE(transformer.UpdateFormatAndShape()); | ||
| 1651 | + | ||
| 1652 | + const auto &actual = op_desc->GetOutputDesc(0U); | ||
| 1653 | + EXPECT_EQ(actual.GetShape(), original_output.GetShape()); | ||
| 1654 | + EXPECT_EQ(actual.GetOriginShape(), original_output.GetOriginShape()); | ||
| 1655 | + EXPECT_EQ(actual.GetDataType(), original_output.GetDataType()); | ||
| 1656 | + EXPECT_EQ(actual.GetOriginDataType(), original_output.GetOriginDataType()); | ||
| 1657 | + } | ||
| 1658 | + | ||
| 1493 | TEST_F(ShapeInferenceUT, CallInferFunc_CustomOp_Without_InferShape_Success) { | 1659 | TEST_F(ShapeInferenceUT, CallInferFunc_CustomOp_Without_InferShape_Success) { |
| 1494 | auto op = OperatorFactory::CreateOperator("test1", "CustomAdd"); | 1660 | auto op = OperatorFactory::CreateOperator("test1", "CustomAdd"); |
| 1495 | auto op_desc = OpDescUtils::GetOpDescFromOperator(op); | 1661 | auto op_desc = OpDescUtils::GetOpDescFromOperator(op); |
RegisterOpImpl 原来的 if (CustomOpFactory::IsExistOp(AscendString(desc.op_type.c_str()))) 判断最好保留在这里,放CommitPythonCustomOpRegistrations虽然逻辑没有问题,但是太滞后了