已合并
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
已合并
lfz2812创建于 11 天前
42 个文件变更+2149-274
@@ -11,11 +11,13 @@
11set(_ge_custom_op_native_sources11set(_ge_custom_op_native_sources
12 ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/module.cc12 ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/module.cc
13 ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/context_binding.cc13 ${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 
16set(_ge_custom_op_native_headers17set(_ge_custom_op_native_headers
17 ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/binding_common.h18 ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/binding_common.h
18 ${CMAKE_CURRENT_SOURCE_DIR}/native_bindings/custom_op_bindings.h19 ${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 
21set(GE_PYTHON_CUSTOM_OP_NATIVE_SOURCES ${_ge_custom_op_native_sources} PARENT_SCOPE)23set(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
19from typing import Dict, Optional19from typing import Dict, Optional
20 20 
21from ._ir_types import InputType, OutputType21from ._ir_types import InputType, OutputType
22+from ._infer_meta import call_infer_meta # noqa: F401
22from ._signature import _get_runtime_attr_spec, _validate_args_signature23from ._signature import _get_runtime_attr_spec, _validate_args_signature
23from .base import EagerOpExecutionContext24from .base import EagerOpExecutionContext
24from .bootstrap import (25from .bootstrap import (
@@ -13,13 +13,14 @@ from __future__ import annotations
13from typing import List, Optional13from typing import List, Optional
14 14 
15from ge.graph.types import DataType15from ge.graph.types import DataType
16-from ge.runtime import StorageFormat, StorageShape, Tensor16+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
43AnnotatedKernelArgs = _native.AnnotatedKernelArgs44AnnotatedKernelArgs = _native.AnnotatedKernelArgs
44AnnotatedKernelLaunchInfo = _native.AnnotatedKernelLaunchInfo45AnnotatedKernelLaunchInfo = _native.AnnotatedKernelLaunchInfo
45WorkspaceAddr = _native.WorkspaceAddr46WorkspaceAddr = _native.WorkspaceAddr
47+InferMetaContext = _native.InferMetaContext
@@ -10,14 +10,12 @@
10 10 
11#include "custom_op_bindings.h"11#include "custom_op_bindings.h"
12#include "exe_graph/runtime/annotated_args_context.h"12#include "exe_graph/runtime/annotated_args_context.h"
13-#include "exe_graph/runtime/continuous_vector.h"
14#include "exe_graph/runtime/eager_op_execution_context.h"13#include "exe_graph/runtime/eager_op_execution_context.h"
15-#include "exe_graph/runtime/runtime_attrs.h"14+#include "runtime_attrs_binding.h"
16#include "runtime/native_bindings/runtime_type_wrappers.h"15#include "runtime/native_bindings/runtime_type_wrappers.h"
17 16 
18#include <cstddef>17#include <cstddef>
19#include <cstdint>18#include <cstdint>
20-#include <cstring>
21#include <memory>19#include <memory>
22#include <stdexcept>20#include <stdexcept>
23#include <string>21#include <string>
@@ -29,157 +27,6 @@ namespace python_custom_op_native {
29namespace {27namespace {
30namespace runtime_native = ::ge::python_runtime_native;28namespace 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- 
183class BorrowedEagerOpExecutionContext {30class 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 
19void BindEagerOpExecutionContext(py::module_ &m);19void BindEagerOpExecutionContext(py::module_ &m);
20void BindAnnotatedArgsContext(py::module_ &m);20void BindAnnotatedArgsContext(py::module_ &m);
21+void BindInferMetaContext(py::module_ &m);
21 22 
22} // namespace python_custom_op_native23} // namespace python_custom_op_native
23} // namespace ge24} // 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+#include "custom_op_bindings.h"
12+#include "exe_graph/runtime/extended_kernel_context.h"
13+#include "exe_graph/runtime/infer_shape_context.h"
14+#include "exe_graph/runtime/storage_shape.h"
15+#include "runtime_attrs_binding.h"
16+#include "runtime/native_bindings/runtime_type_wrappers.h"
17+ 
18+#include <cstddef>
19+#include <cstdint>
20+#include <memory>
21+#include <stdexcept>
22+#include <string>
23+#include <utility>
24+#include <vector>
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 {
14PYBIND11_MODULE(_ge_custom_op_native, m) {14PYBIND11_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 ge19} // 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+#ifndef API_PYTHON_GE_GE_CUSTOM_OP_NATIVE_BINDINGS_RUNTIME_ATTRS_BINDING_H_
12+#define API_PYTHON_GE_GE_CUSTOM_OP_NATIVE_BINDINGS_RUNTIME_ATTRS_BINDING_H_
13+ 
14+#include "binding_common.h"
15+#include "exe_graph/runtime/continuous_vector.h"
16+#include "exe_graph/runtime/runtime_attrs.h"
17+#include "runtime/native_bindings/runtime_type_wrappers.h"
18+ 
19+#include <cstddef>
20+#include <cstdint>
21+#include <cstring>
22+#include <memory>
23+#include <stdexcept>
24+#include <string>
25+#include <utility>
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+#endif // API_PYTHON_GE_GE_CUSTOM_OP_NATIVE_BINDINGS_RUNTIME_ATTRS_BINDING_H_
@@ -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 
819Note: Files prefixed with underscores are internal modules in the Python style.819Note: 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 Positioning822#### 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 Selection826#### 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 function959+- `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 
24In 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.24In 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 
24V2 中,被 `register_op` 装饰的 Python 函数负责 Meta 推导,本文统一称为 `infer_meta`,但不要求函数名必须是 `infer_meta`。该函数按照算子原型接收输入 `TensorDesc`(包括可选输入和动态输入)及属性值,返回一个或多个描述输出 shape 和 data type 的 `TensorDesc`;它不读取输入 Tensor 数据,也不执行算子 kernel。24V2 中,被 `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`,绑定 EagerAnnotatedArgs context、参数 builder 及 `RuntimeAttrs` |70+| Native context | `api/python/ge/ge/custom_op/native_bindings/` | `_ge_custom_op_native`,绑定 EagerAnnotatedArgs、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
58IsInferShapeV2RegisteredFunc OperatorFactoryImpl::is_infer_shape_v2_registered_func_ = nullptr;58IsInferShapeV2RegisteredFunc OperatorFactoryImpl::is_infer_shape_v2_registered_func_ = nullptr;
59CustomOpInferShapeFunc OperatorFactoryImpl::custom_op_infer_shape_func_ = nullptr;59CustomOpInferShapeFunc OperatorFactoryImpl::custom_op_infer_shape_func_ = nullptr;
60CustomOpInferDataTypeFunc OperatorFactoryImpl::custom_op_infer_datatype_func_ = nullptr;60CustomOpInferDataTypeFunc OperatorFactoryImpl::custom_op_infer_datatype_func_ = nullptr;
61+CustomOpInferMetaFunc OperatorFactoryImpl::custom_op_infer_meta_func_ = nullptr;
61 62 
62Operator OperatorFactoryImpl::CreateOperator(const std::string &operator_name, const std::string &operator_type) {63Operator 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+ 
461void OperatorFactoryImpl::ReleaseRegInfo() {473void OperatorFactoryImpl::ReleaseRegInfo() {
462 ReleaseOpsRegInfo();474 ReleaseOpsRegInfo();
463}475}
@@ -27,6 +27,7 @@
27#include "debug/ge_op_types.h"27#include "debug/ge_op_types.h"
28#include "mmpa/mmpa_api.h"28#include "mmpa/mmpa_api.h"
29#include "graph/custom_op/cast.h"29#include "graph/custom_op/cast.h"
30+#include "graph/custom_op/infer_meta.h"
30#include "graph/custom_op_factory.h"31#include "graph/custom_op_factory.h"
31 32 
32namespace ge {33namespace 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#include "base/registry/op_impl_space_registry_v2.h"20#include "base/registry/op_impl_space_registry_v2.h"
21#include "common/checker.h"21#include "common/checker.h"
22#include "graph/utils/inference_rule.h"22#include "graph/utils/inference_rule.h"
23-#include "graph/custom_op.h"23+#include "graph/custom_op/infer_meta.h"
24 24 
25namespace gert {25namespace gert {
26namespace {26namespace {
@@ -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+ 
314ge::graphStatus UpdateOpDescOutShape(const ge::OpDescPtr &op_desc, gert::InferShapeContext *infer_shape_ctx) {321ge::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} // namespace711} // namespace
677 712 
678ge::graphStatus InferShapeRangeOnCompile(const ge::Operator &op, const ge::OpDescPtr &op_desc) {713ge::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+ 
704ge::graphStatus InferShapeOnCompile(const ge::Operator &op, const ge::OpDescPtr &op_desc) {769ge::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};
872static CompileAdaptFunctionsRegister VAR_UNUSED g_register_adapt_funcs;938static 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 
26using CustomOpCapabilityMask = uint32_t;27using CustomOpCapabilityMask = uint32_t;
@@ -13,6 +13,7 @@
13 13 
14#include "graph/custom_op.h"14#include "graph/custom_op.h"
15#include "graph/custom_op/capability.h"15#include "graph/custom_op/capability.h"
16+#include "graph/custom_op/infer_meta.h"
16 17 
17namespace ge {18namespace ge {
18class CustomOpCapabilityProvider {19class 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+ 
42template <>48template <>
43struct CustomOpCapabilityTrait<PortableOp> {49struct 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+#ifndef METADEF_CXX_INC_GRAPH_CUSTOM_OP_INFER_META_H_
12+#define METADEF_CXX_INC_GRAPH_CUSTOM_OP_INFER_META_H_
13+ 
14+#include <vector>
15+ 
16+#include "exe_graph/runtime/infer_shape_context.h"
17+#include "exe_graph/runtime/storage_shape.h"
18+#include "graph/custom_op.h"
19+#include "graph/error_codes.h"
20+#include "graph/types.h"
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+#endif // METADEF_CXX_INC_GRAPH_CUSTOM_OP_INFER_META_H_
@@ -23,6 +23,7 @@
23 23 
24namespace ge {24namespace ge {
25class ShapeInferOp;25class ShapeInferOp;
26+class CustomOpInferMetaProvider;
26using InferShapeV2Func = uint32_t (*)(const ge::Operator &op, const OpDescPtr &);27using InferShapeV2Func = uint32_t (*)(const ge::Operator &op, const OpDescPtr &);
27using InferDataTypeFunc = uint32_t (*)(const OpDescPtr &);28using InferDataTypeFunc = uint32_t (*)(const OpDescPtr &);
28using InferShapeRangeFunc = uint32_t (*)(const ge::Operator &op, const OpDescPtr &);29using InferShapeRangeFunc = uint32_t (*)(const ge::Operator &op, const OpDescPtr &);
@@ -31,6 +32,7 @@ using IsInferFormatV2RegisteredFunc = bool (*)(const OpDescPtr &);
31using IsInferShapeV2RegisteredFunc = bool (*)(const OpDescPtr &);32using IsInferShapeV2RegisteredFunc = bool (*)(const OpDescPtr &);
32using CustomOpInferShapeFunc = uint32_t (*)(ShapeInferOp *, const Operator &, const OpDescPtr &);33using CustomOpInferShapeFunc = uint32_t (*)(ShapeInferOp *, const Operator &, const OpDescPtr &);
33using CustomOpInferDataTypeFunc = uint32_t (*)(ShapeInferOp *, const OpDescPtr &);34using CustomOpInferDataTypeFunc = uint32_t (*)(ShapeInferOp *, const OpDescPtr &);
35+using CustomOpInferMetaFunc = uint32_t (*)(const Operator &, OpDesc *, CustomOpInferMetaProvider *);
34 36 
35struct InferValueRangePara {37struct 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 ge172} // namespace ge
166 173 
@@ -17,19 +17,41 @@
17 17 
18#include "framework/common/debug/ge_log.h"18#include "framework/common/debug/ge_log.h"
19#include "graph_metadef/graph/debug/ge_util.h"19#include "graph_metadef/graph/debug/ge_util.h"
20+#include "graph/custom_op/infer_meta.h"
20 21 
21namespace ge {22namespace ge {
22namespace custom_op {23namespace custom_op {
23namespace {24namespace {
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+ 
24struct PythonCustomOpImplRuntimeEntry {49struct 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} // namespace159} // namespace
138 160 
139PythonCustomOpImplHolder::PythonCustomOpImplHolder(const PythonCustomOpAdapterDescriptor &desc) : desc_(desc) {161PythonCustomOpImplHolder::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 
216PythonCustomOpAdapter::PythonCustomOpAdapter(PythonCustomOpAdapterDescriptor desc)240PythonCustomOpAdapter::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 
219PythonCustomOpAdapter::~PythonCustomOpAdapter() = default;250PythonCustomOpAdapter::~PythonCustomOpAdapter() = default;
220 251 
221bool PythonCustomOpAdapter::IsValid() const {252bool 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 
225bool PythonCustomOpAdapter::HasCapability(CustomOpCapability capability) const {270bool PythonCustomOpAdapter::HasCapability(CustomOpCapability capability) const {
226- return HasCustomOpCapability(desc_.capabilities, capability);271+ return HasCustomOpCapability(capabilities_, capability);
227}272}
228 273 
229graphStatus PythonCustomOpAdapter::Execute(gert::EagerOpExecutionContext *ctx) {274graphStatus 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 
260graphStatus PythonCustomOpAdapter::InferShape(gert::InferShapeContext *ctx) {305graphStatus 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 
265graphStatus PythonCustomOpAdapter::InferDataType(gert::InferDataTypeContext *ctx) {320graphStatus 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+ 
270graphStatus PythonCustomOpAdapter::Serialize(std::vector<uint8_t> &buffer) {338graphStatus 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 
285graphStatus PythonCustomOpAdapter::ReportUnsupported(CustomOpCapability capability, const char *method_name) const {353graphStatus 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#include <vector>16#include <vector>
17 17 
18#include "graph/custom_op/cast.h"18#include "graph/custom_op/cast.h"
19+#include "graph/custom_op/infer_meta.h"
19#include "runtime/custom_op/python_custom_op_bridge_types.h"20#include "runtime/custom_op/python_custom_op_bridge_types.h"
20 21 
21namespace ge {22namespace 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 
29class PythonCustomOpImplRuntimeRegistry {31class 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 
22struct PythonCustomOpRegistrar {22struct 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 
28struct PythonCustomOpBridgeArtifactConfig {28struct 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#include <map>20#include <map>
21#include <mutex>21#include <mutex>
22#include <new>22#include <new>
23+#include <set>
23#include <string>24#include <string>
24#include <utility>25#include <utility>
25#include <vector>26#include <vector>
@@ -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+ 
148bridge_loader::BridgeLoadDependencies BuildBridgeLoadDependencies() {159bridge_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 &registration = 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 &registration = 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)) {
duhua
duhuaduhua8 天前

RegisterOpImpl 原来的 if (CustomOpFactory::IsExistOp(AscendString(desc.op_type.c_str()))) 判断最好保留在这里,放CommitPythonCustomOpRegistrations虽然逻辑没有问题,但是太滞后了

likedislike
lfz2812
7 天前 评论:
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 &registration = 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 &registration,
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 &registration = 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} // namespace509} // namespace
415 510 
@@ -16,10 +16,13 @@
16 16 
17#include "graph/custom_op/capability.h"17#include "graph/custom_op/capability.h"
18#include "graph/error_codes.h"18#include "graph/error_codes.h"
19+#include "graph/types.h"
19 20 
20namespace gert {21namespace gert {
21class AnnotatedArgsContext;22class AnnotatedArgsContext;
22class EagerOpExecutionContext;23class EagerOpExecutionContext;
24+class InferShapeContext;
25+class StorageShape;
23} // namespace gert26} // namespace gert
24 27 
25namespace ge {28namespace 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+ 
96struct PythonCustomOpProtoDescriptorView {112struct 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 
107struct PythonCustomOpAdapterDescriptorView {124struct PythonCustomOpAdapterDescriptorView {
@@ -114,12 +131,12 @@ using PythonCustomOpImplHolderCreateFn = void *(*)(const PythonCustomOpAdapterDe
114using PythonCustomOpImplHolderDestroyFn = void (*)(void *holder);131using PythonCustomOpImplHolderDestroyFn = void (*)(void *holder);
115using PythonCustomOpImplExecuteFn = graphStatus (*)(const void *holder, gert::EagerOpExecutionContext *ctx);132using PythonCustomOpImplExecuteFn = graphStatus (*)(const void *holder, gert::EagerOpExecutionContext *ctx);
116using PythonCustomOpImplDeclareLaunchArgsFn = graphStatus (*)(const void *holder, gert::AnnotatedArgsContext *ctx);133using PythonCustomOpImplDeclareLaunchArgsFn = graphStatus (*)(const void *holder, gert::AnnotatedArgsContext *ctx);
117- 
118struct PythonCustomOpAdapterCallbacks {134struct 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} // namespace366} // 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 
63graphStatus ParsePythonCustomOpProto(const PythonCustomOpProtoDescriptorView &view, PythonCustomOpProto &proto);64graphStatus ParsePythonCustomOpProto(const PythonCustomOpProtoDescriptorView &view, PythonCustomOpProto &proto);
@@ -30,6 +30,7 @@
30 30 
31#include "common/checker.h"31#include "common/checker.h"
32#include "common/ge_common/debug/ge_log.h"32#include "common/ge_common/debug/ge_log.h"
33+#include "graph/custom_op/infer_meta.h"
33#include "graph/utils/ir_definitions_query.h"34#include "graph/utils/ir_definitions_query.h"
34#include "graph/operator_factory.h"35#include "graph/operator_factory.h"
35#include "pybind11/embed.h"36#include "pybind11/embed.h"
@@ -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+ 
204class PythonCustomOpPybindBridge {236class 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 &registrar) {525 Status CollectAndRegisterProtoDescriptors(const py::dict &descriptors, const PythonCustomOpRegistrar &registrar) {
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#include "engines/custom_engine/custom_ops_kernel_builder.h"56#include "engines/custom_engine/custom_ops_kernel_builder.h"
57#include "graph/compute_graph.h"57#include "graph/compute_graph.h"
58#include "graph/custom_op/cast.h"58#include "graph/custom_op/cast.h"
59+#include "graph/custom_op/infer_meta.h"
59#include "graph/custom_op_factory.h"60#include "graph/custom_op_factory.h"
60#include "graph/custom_op.h"61#include "graph/custom_op.h"
61#include "graph/operator_factory.h"62#include "graph/operator_factory.h"
@@ -66,6 +67,10 @@
66#include "common/python_runtime/ge_python_runtime_manager.h"67#include "common/python_runtime/ge_python_runtime_manager.h"
67#include "runtime/custom_op/custom_op_loader.h"68#include "runtime/custom_op/custom_op_loader.h"
68#include "runtime/custom_op/python_custom_op_bridge_loader.h"69#include "runtime/custom_op/python_custom_op_bridge_loader.h"
70+#include "exe_graph/runtime/storage_shape.h"
71+#include "faker/kernel_run_context_facker.h"
72+#include "register/kernel_registry.h"
73+#include "runtime/v2/kernel/common_kernel_impl/infer_shape.h"
69 74 
70namespace ge {75namespace ge {
71REG_OP(StPythonAnnotatedArgsCustomOp)76REG_OP(StPythonAnnotatedArgsCustomOp)
@@ -176,6 +181,8 @@ void **args_table = nullptr;
176constexpr const char *kPythonCustomOpTypeForSt = "StPythonPybindRemoveCoverageCustomOp";181constexpr const char *kPythonCustomOpTypeForSt = "StPythonPybindRemoveCoverageCustomOp";
177constexpr const char *kPythonAnnotatedArgsOpTypeForSt = "StPythonAnnotatedArgsCustomOp";182constexpr const char *kPythonAnnotatedArgsOpTypeForSt = "StPythonAnnotatedArgsCustomOp";
178constexpr const char *kPythonAnnotatedArgsBadAttrOpTypeForSt = "StPythonAnnotatedArgsBadAttrCustomOp";183constexpr const char *kPythonAnnotatedArgsBadAttrOpTypeForSt = "StPythonAnnotatedArgsBadAttrCustomOp";
184+constexpr const char *kPythonRt2InferMetaOpTypeForSt = "StPythonRt2InferMetaCustomOp";
185+constexpr const char *kInferMetaCoverageOpTypeForSt = "StInferMetaCoverageCustomOp";
179constexpr const char *kEnvPythonCustomOpPath = "ASCEND_CUSTOM_OPP_PATH";186constexpr const char *kEnvPythonCustomOpPath = "ASCEND_CUSTOM_OPP_PATH";
180constexpr const char *kEnvPythonPath = "PYTHONPATH";187constexpr const char *kEnvPythonPath = "PYTHONPATH";
181constexpr char kSharedPybindCustomOpPreambleForSt[] = R"PY(from pathlib import Path188constexpr char kSharedPybindCustomOpPreambleForSt[] = R"PY(from pathlib import Path
@@ -291,6 +298,32 @@ class StPythonValidBeforeInvalidCustomOp:
291 pass298 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 
295class ScopedTempDirForCustomOpSt {328class 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+ 
448const std::string &GetInvalidSignaturePybindCustomOpFilePathForSt() {493const 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+ 
476class ScopedLoadedPythonCustomOpsForSt {531class 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+ 
635class TestBaseCustomOp : public EagerExecuteOp {700class 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+ 
1832TEST_F(CustomOpFactoryStTest, PythonAnnotatedArgsCustomOpLoaderGeneratesTaskDef) {1975TEST_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():
505def test_public_stub_hides_bridge_private_attr_readers():506def 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_stub512 assert "def _get_attrs" not in annotated_context_stub
510 assert "def get_attrs" not in annotated_context_stub513 assert "def get_attrs" not in annotated_context_stub
511 assert "def get_attr" not in annotated_context_stub514 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+@pytest.fixture(autouse=True)
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+ @proto.register_op(op_type="InferMetaBasic")
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+ @proto.register_op(op_type="InferMetaAttrs")
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+ @proto.register_op(op_type="InferMetaZeroOutput")
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+ @proto.register_op(op_type="InferMetaMultiOutput")
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+ @proto.register_op(op_type="InferMetaDynInput")
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+ @proto.register_op(op_type="InferMetaOptInput")
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+ @proto.register_op(op_type="InferMetaDynOutput")
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+ @proto.register_op(op_type="InferMetaDynamicThenRequired")
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+ @proto.register_op(op_type="InferMetaBadReturn")
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+ @proto.register_op(op_type="InferMetaCountMismatch")
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+ @proto.register_op(op_type="InferMetaDynOutputMismatch")
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+ @proto.register_op(op_type="InferMetaInvalidate")
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+ @proto.register_op(op_type="ProtoByOpType")
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+ @proto.register_op(op_type="InferMetaChangeDtype")
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+ 
51PythonCustomOpProtoDescriptorView MakeProto(const char *descriptor_key, const char *op_type) {56PythonCustomOpProtoDescriptorView 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 
56PythonCustomOpAdapterDescriptorView MakeAdapter(const char *op_type, const char *impl_key) {61PythonCustomOpAdapterDescriptorView MakeAdapter(const char *op_type, const char *impl_key) {
@@ -108,13 +113,13 @@ Status RegisterAdapterFailure(const PythonCustomOpRegistrar &registrar) {
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_SUCCESS121+ return static_cast<Status>(registrar.register_op_impl(&adapter_b, &rejecting_callbacks) ? GRAPH_SUCCESS
117- : GRAPH_FAILED);122+ : GRAPH_FAILED);
118}123}
119 124 
120Status RegisterSuccess(const PythonCustomOpRegistrar &registrar) {125Status RegisterSuccess(const PythonCustomOpRegistrar &registrar) {
@@ -124,19 +129,18 @@ Status RegisterSuccess(const PythonCustomOpRegistrar &registrar) {
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 
130Status RegisterCppProtoImpl(const PythonCustomOpRegistrar &registrar) {135Status RegisterCppProtoImpl(const PythonCustomOpRegistrar &registrar) {
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 
136Status RegisterCustomOps(const PythonCustomOpRegistrar *registrar) {141Status 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+#include <gtest/gtest.h>
12+ 
13+#include <new>
14+#include <string>
15+ 
16+#include "graph/custom_op/infer_meta.h"
17+#include "graph/custom_op/cast.h"
18+#include "graph/custom_op_factory.h"
19+#include "graph/operator_factory_impl.h"
20+#include "runtime/custom_op/python_custom_op_adapter.h"
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#include "graph/utils/tensor_adapter.h"34#include "graph/utils/tensor_adapter.h"
35#include "faker/space_registry_faker.h"35#include "faker/space_registry_faker.h"
36#include "graph/custom_op_factory.h"36#include "graph/custom_op_factory.h"
37+#include "graph/custom_op/infer_meta.h"
38+#include "graph/utils/transformer_utils.h"
37 39 
38namespace ge {40namespace ge {
39REG_OP(Const)41REG_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+ 
74class ShapeInferenceUT : public testing::Test {};153class ShapeInferenceUT : public testing::Test {};
75// infer from output154// infer from output
76REG_OP(FixIOOp_OutputIsFix)155REG_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);