已合并
feat: Python化可编译自定义算子 #4388
shangdf创建于 21 天前
feat: Python化可编译自定义算子 #4388
已合并
shangdf创建于 21 天前
44 个文件变更+1981-162
@@ -46,6 +46,7 @@ target_link_libraries(_ge_custom_op_native PRIVATE
46 lowering46 lowering
47 register47 register
48 opp_registry48 opp_registry
49+ platform
49 -Wl,--as-needed50 -Wl,--as-needed
50)51)
51 52 
@@ -17,10 +17,14 @@ __all__ = [
17 "AnnotatedKernelArgs",17 "AnnotatedKernelArgs",
18 "AnnotatedKernelLaunchInfo",18 "AnnotatedKernelLaunchInfo",
19 "EagerOpExecutionContext",19 "EagerOpExecutionContext",
20+ "CompilePlatformInfo",
21+ "OpCompileContext",
20 "InferMetaContext",22 "InferMetaContext",
21 "WorkspaceAddr",23 "WorkspaceAddr",
22 "clear_registered_op_impls",24 "clear_registered_op_impls",
23 "get_declare_launch_args_ctx",25 "get_declare_launch_args_ctx",
26+ "get_compile_ctx",
27+ "get_compile_platform_info",
24 "get_execute_ctx",28 "get_execute_ctx",
25 "get_registered_op_impl_by_descriptor_key",29 "get_registered_op_impl_by_descriptor_key",
26 "get_registered_op_impl_dicts",30 "get_registered_op_impl_dicts",
@@ -37,6 +41,8 @@ _LAZY_EXPORTS = {
37 "InferMetaContext": "._native",41 "InferMetaContext": "._native",
38 "clear_registered_op_impls": ".registry",42 "clear_registered_op_impls": ".registry",
39 "get_declare_launch_args_ctx": ".context",43 "get_declare_launch_args_ctx": ".context",
44+ "get_compile_ctx": ".context",
45+ "get_compile_platform_info": ".context",
40 "get_execute_ctx": ".context",46 "get_execute_ctx": ".context",
41 "get_registered_op_impl_by_descriptor_key": ".registry",47 "get_registered_op_impl_by_descriptor_key": ".registry",
42 "get_registered_op_impl_dicts": ".registry",48 "get_registered_op_impl_dicts": ".registry",
@@ -44,6 +50,8 @@ _LAZY_EXPORTS = {
44 "register_op": ".proto",50 "register_op": ".proto",
45 "register_op_impl": ".registry",51 "register_op_impl": ".registry",
46 "WorkspaceAddr": "._native",52 "WorkspaceAddr": "._native",
53+ "OpCompileContext": "._native",
54+ "CompilePlatformInfo": "._native",
47}55}
48 56 
49 57 
@@ -27,9 +27,14 @@ from .bootstrap import (
27 get_registered_op_protos,27 get_registered_op_protos,
28 load_custom_op_plugins,28 load_custom_op_plugins,
29)29)
30-from .context import _declare_launch_args_ctx_scope, _execute_ctx_scope30+from .context import (
31+ _compile_ctx_scope,
32+ _declare_launch_args_ctx_scope,
33+ _execute_ctx_scope,
34+)
31from .registry import (35from .registry import (
32 INTERFACE_ANNOTATED_ARGS,36 INTERFACE_ANNOTATED_ARGS,
37+ INTERFACE_COMPILABLE,
33 INTERFACE_EAGER_EXECUTE,38 INTERFACE_EAGER_EXECUTE,
34 get_registered_op_impl_by_descriptor_key,39 get_registered_op_impl_by_descriptor_key,
35)40)
@@ -107,7 +112,10 @@ def _get_callback_for_signature(cls, method_name: str):
107 return getattr(cls, method_name)112 return getattr(cls, method_name)
108 113 
109 114 
110-def validate_op_impl_descriptor(descriptor_key: str, ir_meta: Optional[dict]) -> bool:115+def validate_op_impl_descriptor(
116+ descriptor_key: str,
117+ ir_meta: Optional[dict],
118+) -> bool:
111 descriptor = get_registered_op_impl_by_descriptor_key(descriptor_key)119 descriptor = get_registered_op_impl_by_descriptor_key(descriptor_key)
112 if descriptor is None:120 if descriptor is None:
113 raise KeyError(f"python op impl descriptor_key not found: {descriptor_key}")121 raise KeyError(f"python op impl descriptor_key not found: {descriptor_key}")
@@ -120,6 +128,12 @@ def validate_op_impl_descriptor(descriptor_key: str, ir_meta: Optional[dict]) ->
120 method = _get_callback_for_signature(descriptor.cls, "execute")128 method = _get_callback_for_signature(descriptor.cls, "execute")
121 _validate_args_signature(method, ir_meta, descriptor, method_name="execute")129 _validate_args_signature(method, ir_meta, descriptor, method_name="execute")
122 130 
131+ if INTERFACE_COMPILABLE in descriptor.interfaces:
132+ if ir_meta is None:
133+ raise RuntimeError("canonical IR not found for schema-bound compile")
134+ method = _get_callback_for_signature(descriptor.cls, "compile")
135+ _validate_args_signature(method, ir_meta, descriptor, method_name="compile")
136+ 
123 if INTERFACE_ANNOTATED_ARGS in descriptor.interfaces:137 if INTERFACE_ANNOTATED_ARGS in descriptor.interfaces:
124 if ir_meta is None:138 if ir_meta is None:
125 raise RuntimeError(139 raise RuntimeError(
@@ -186,7 +200,7 @@ def _build_execute_attrs(ctx: EagerOpExecutionContext, ir_attrs: list) -> dict:
186 }200 }
187 201 
188 202 
189-def _build_declare_inputs(ctx, ir_inputs: list) -> list:203+def _build_schema_inputs(ctx, ir_inputs: list) -> list:
190 return _build_inputs(204 return _build_inputs(
191 ir_inputs,205 ir_inputs,
192 ctx._get_required_input_tensor,206 ctx._get_required_input_tensor,
@@ -196,7 +210,7 @@ def _build_declare_inputs(ctx, ir_inputs: list) -> list:
196 )210 )
197 211 
198 212 
199-def _build_declare_outputs(ctx, ir_outputs: list) -> list:213+def _build_schema_outputs(ctx, ir_outputs: list) -> list:
200 args = []214 args = []
201 for ir_index, item in enumerate(ir_outputs):215 for ir_index, item in enumerate(ir_outputs):
202 kind = item["kind"]216 kind = item["kind"]
@@ -215,7 +229,7 @@ def _build_declare_outputs(ctx, ir_outputs: list) -> list:
215 return args229 return args
216 230 
217 231 
218-def _build_declare_attrs(ctx, ir_attrs: list) -> dict:232+def _build_schema_attrs(ctx, ir_attrs: list) -> dict:
219 if not ir_attrs:233 if not ir_attrs:
220 return {}234 return {}
221 attrs = ctx._get_attrs()235 attrs = ctx._get_attrs()
@@ -261,9 +275,9 @@ def call_declare_launch_args(instance_id: str, ir_meta: Optional[dict], ctx) ->
261 raise RuntimeError(275 raise RuntimeError(
262 "canonical IR not found for schema-bound declare_launch_args"276 "canonical IR not found for schema-bound declare_launch_args"
263 )277 )
264- args = _build_declare_inputs(ctx, ir_meta["inputs"])278+ args = _build_schema_inputs(ctx, ir_meta["inputs"])
265- args.extend(_build_declare_outputs(ctx, ir_meta["outputs"]))279+ args.extend(_build_schema_outputs(ctx, ir_meta["outputs"]))
266- kwargs = _build_declare_attrs(ctx, ir_meta["attrs"])280+ kwargs = _build_schema_attrs(ctx, ir_meta["attrs"])
267 with _declare_launch_args_ctx_scope(ctx):281 with _declare_launch_args_ctx_scope(ctx):
268 result = method(*args, **kwargs)282 result = method(*args, **kwargs)
269 if result is not None:283 if result is not None:
@@ -272,6 +286,29 @@ def call_declare_launch_args(instance_id: str, ir_meta: Optional[dict], ctx) ->
272 ctx._invalidate()286 ctx._invalidate()
273 287 
274 288 
289+def call_compile(instance_id: str, ir_meta: Optional[dict], ctx) -> None:
290+ """Invoke a schema-bound Python compile callback."""
291+ 
292+ try:
293+ holder = _get_holder(instance_id)
294+ method = getattr(holder.instance, "compile", None)
295+ if not callable(method):
296+ raise TypeError(f"python op impl does not implement compile: {instance_id}")
297+ if ir_meta is None:
298+ raise RuntimeError("canonical IR not found for schema-bound compile")
299+ descriptor = holder.instance.__ge_op_impl_descriptor__
300+ _validate_args_signature(method, ir_meta, descriptor, method_name="compile")
301+ args = _build_schema_inputs(ctx, ir_meta["inputs"])
302+ args.extend(_build_schema_outputs(ctx, ir_meta["outputs"]))
303+ kwargs = _build_schema_attrs(ctx, ir_meta["attrs"])
304+ with _compile_ctx_scope(ctx):
305+ result = method(*args, **kwargs)
306+ if result is not None:
307+ raise TypeError("compile must return None")
308+ finally:
309+ ctx._invalidate()
310+ 
311+ 
275def clear_op_impl_holders() -> None:312def clear_op_impl_holders() -> None:
276 with _HOLDER_LOCK:313 with _HOLDER_LOCK:
277 _OP_IMPL_HOLDERS.clear()314 _OP_IMPL_HOLDERS.clear()
@@ -20,6 +20,8 @@ __all__: List[str] = [
20 "AnnotatedKernelArgs",20 "AnnotatedKernelArgs",
21 "AnnotatedKernelLaunchInfo",21 "AnnotatedKernelLaunchInfo",
22 "EagerOpExecutionContext",22 "EagerOpExecutionContext",
23+ "CompilePlatformInfo",
24+ "OpCompileContext",
23 "InferMetaContext",25 "InferMetaContext",
24 "WorkspaceAddr",26 "WorkspaceAddr",
25]27]
@@ -165,6 +167,28 @@ class EagerOpExecutionContext:
165 ...167 ...
166 168 
167 169 
170+class OpCompileContext:
171+ """Borrowed read-only view of ``gert::OpCompileContext``."""
172+ 
173+ def get_option(self, option_key: str) -> str: ...
174+ 
175+ def _get_platform_info(self) -> CompilePlatformInfo: ...
176+ 
177+ 
178+class CompilePlatformInfo:
179+ """Borrowed platform information view available during ``compile``."""
180+ 
181+ def get_platform_resource(self, group: str, key: str) -> str: ...
182+ 
183+ def get_platform_resource_group(self, group: str) -> dict[str, str]: ...
184+ 
185+ def get_core_num(self, core_type: Optional[str] = None) -> int: ...
186+ 
187+ def get_soc_version(self) -> str: ...
188+ 
189+ def get_ai_core_num(self) -> int: ...
190+ 
191+ 
168class WorkspaceAddr:192class WorkspaceAddr:
169 """Borrowed workspace address allocated by ``AnnotatedArgsContext``."""193 """Borrowed workspace address allocated by ``AnnotatedArgsContext``."""
170 194 
@@ -19,6 +19,8 @@ __all__ = [
19 "AnnotatedKernelArgs",19 "AnnotatedKernelArgs",
20 "AnnotatedKernelLaunchInfo",20 "AnnotatedKernelLaunchInfo",
21 "EagerOpExecutionContext",21 "EagerOpExecutionContext",
22+ "CompilePlatformInfo",
23+ "OpCompileContext",
22 "InferMetaContext",24 "InferMetaContext",
23 "WorkspaceAddr",25 "WorkspaceAddr",
24]26]
@@ -40,8 +42,10 @@ def _load_native_module():
40_native = _load_native_module()42_native = _load_native_module()
41 43 
42EagerOpExecutionContext = _native.EagerOpExecutionContext44EagerOpExecutionContext = _native.EagerOpExecutionContext
45+CompilePlatformInfo = _native.CompilePlatformInfo
43AnnotatedArgsContext = _native.AnnotatedArgsContext46AnnotatedArgsContext = _native.AnnotatedArgsContext
44AnnotatedKernelArgs = _native.AnnotatedKernelArgs47AnnotatedKernelArgs = _native.AnnotatedKernelArgs
45AnnotatedKernelLaunchInfo = _native.AnnotatedKernelLaunchInfo48AnnotatedKernelLaunchInfo = _native.AnnotatedKernelLaunchInfo
49+OpCompileContext = _native.OpCompileContext
46WorkspaceAddr = _native.WorkspaceAddr50WorkspaceAddr = _native.WorkspaceAddr
47InferMetaContext = _native.InferMetaContext51InferMetaContext = _native.InferMetaContext
@@ -155,7 +155,7 @@ def _validate_args_signature(
155 *,155 *,
156 method_name: str = "declare_launch_args",156 method_name: str = "declare_launch_args",
157) -> None:157) -> None:
158- if method_name not in ("execute", "declare_launch_args"):158+ if method_name not in ("execute", "compile", "declare_launch_args"):
159 raise ValueError(f"unsupported schema callback: {method_name}")159 raise ValueError(f"unsupported schema callback: {method_name}")
160 signature = inspect.signature(method)160 signature = inspect.signature(method)
161 parameters = list(signature.parameters.values())161 parameters = list(signature.parameters.values())
@@ -172,7 +172,7 @@ def _validate_args_signature(
172 )172 )
173 173 
174 ir_inputs = ir_meta["inputs"]174 ir_inputs = ir_meta["inputs"]
175- ir_outputs = ir_meta["outputs"] if method_name == "declare_launch_args" else []175+ ir_outputs = ir_meta["outputs"] if method_name != "execute" else []
176 ir_attrs = ir_meta["attrs"]176 ir_attrs = ir_meta["attrs"]
177 positional_count = len(ir_inputs) + len(ir_outputs)177 positional_count = len(ir_inputs) + len(ir_outputs)
178 expected_count = positional_count + len(ir_attrs)178 expected_count = positional_count + len(ir_attrs)
@@ -181,7 +181,7 @@ def _validate_args_signature(
181 descriptor,181 descriptor,
182 method_name,182 method_name,
183 f"{positional_count} positional "183 f"{positional_count} positional "
184- f"{'input/output' if method_name == 'declare_launch_args' else 'input'} "184+ f"{'input/output' if method_name != 'execute' else 'input'} "
185 "parameters followed by "185 "parameters followed by "
186 f"{len(ir_attrs)} keyword-only attrs",186 f"{len(ir_attrs)} keyword-only attrs",
187 f"{len(parameters)} parameters",187 f"{len(parameters)} parameters",
@@ -19,7 +19,12 @@ from contextvars import ContextVar
19from dataclasses import dataclass19from dataclasses import dataclass
20from typing import Iterator, Optional20from typing import Iterator, Optional
21 21 
22-from ._native import AnnotatedArgsContext, EagerOpExecutionContext22+from ._native import (
23+ AnnotatedArgsContext,
24+ CompilePlatformInfo,
25+ EagerOpExecutionContext,
26+ OpCompileContext,
27+)
23 28 
24 29 
25@dataclass30@dataclass
@@ -86,3 +91,42 @@ def _declare_launch_args_ctx_scope(ctx: AnnotatedArgsContext) -> Iterator[None]:
86 finally:91 finally:
87 binding.active = False92 binding.active = False
88 _CURRENT_DECLARE_LAUNCH_ARGS_CONTEXT.reset(token)93 _CURRENT_DECLARE_LAUNCH_ARGS_CONTEXT.reset(token)
94+ 
95+ 
96+@dataclass
97+class _CompileContextBinding:
98+ ctx: OpCompileContext
99+ active: bool = True
100+ 
101+ 
102+_CURRENT_COMPILE_CONTEXT: ContextVar[Optional[_CompileContextBinding]] = ContextVar(
103+ "ge_custom_op_compile_context", default=None
104+)
105+ 
106+ 
107+def get_compile_ctx() -> OpCompileContext:
108+ """Return the borrowed context of the active schema-bound compile callback."""
109+ 
110+ binding = _CURRENT_COMPILE_CONTEXT.get()
111+ if binding is None or not binding.active:
112+ raise RuntimeError(
113+ "get_compile_ctx() is only available inside schema-bound compile"
114+ )
115+ return binding.ctx
116+ 
117+ 
118+def get_compile_platform_info() -> CompilePlatformInfo:
119+ """Return the platform information view of the active compile callback."""
120+ 
121+ return get_compile_ctx()._get_platform_info()
122+ 
123+ 
124+@contextmanager
125+def _compile_ctx_scope(ctx: OpCompileContext) -> Iterator[None]:
126+ binding = _CompileContextBinding(ctx=ctx)
127+ token = _CURRENT_COMPILE_CONTEXT.set(binding)
128+ try:
129+ yield
130+ finally:
131+ binding.active = False
132+ _CURRENT_COMPILE_CONTEXT.reset(token)
@@ -11,11 +11,15 @@
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/eager_op_execution_context.h"13#include "exe_graph/runtime/eager_op_execution_context.h"
14+#include "exe_graph/runtime/op_compile_context.h"
15+#include "graph/ascend_string.h"
16+#include "platform/platform_infos_def.h"
14#include "runtime_attrs_binding.h"17#include "runtime_attrs_binding.h"
15#include "runtime/native_bindings/runtime_type_wrappers.h"18#include "runtime/native_bindings/runtime_type_wrappers.h"
16 19 
17#include <cstddef>20#include <cstddef>
18#include <cstdint>21#include <cstdint>
22+#include <map>
19#include <memory>23#include <memory>
20#include <stdexcept>24#include <stdexcept>
21#include <string>25#include <string>
@@ -141,6 +145,229 @@ class BorrowedEagerOpExecutionContext {
141 std::shared_ptr<bool> valid_;145 std::shared_ptr<bool> valid_;
142};146};
143 147 
148+class BorrowedCompilePlatformInfo {
149+ public:
150+ BorrowedCompilePlatformInfo(gert::OpCompileContext *ctx, std::shared_ptr<bool> active)
151+ : ctx_(ctx), active_(std::move(active)) {}
152+ 
153+ std::string GetPlatformResource(const py::object &group_obj, const py::object &key_obj) const {
154+ EnsureActiveContext();
155+ const auto group = RequireString(group_obj, "group");
156+ const auto key = RequireString(key_obj, "key");
157+ if (group.empty()) {
158+ throw std::invalid_argument("platform resource group must not be empty");
159+ }
160+ if (key.empty()) {
161+ throw std::invalid_argument("platform resource key must not be empty");
162+ }
163+ EnsurePlatformSnapshot();
164+ std::string value;
165+ if (!platform_info_.GetPlatformResWithLock(group, key, value)) {
166+ throw py::key_error(group + ":" + key);
167+ }
168+ return value;
169+ }
170+ 
171+ std::map<std::string, std::string> GetPlatformResourceGroup(const py::object &group_obj) const {
172+ EnsureActiveContext();
173+ const auto group = RequireString(group_obj, "group");
174+ if (group.empty()) {
175+ throw std::invalid_argument("platform resource group must not be empty");
176+ }
177+ EnsurePlatformSnapshot();
178+ std::map<std::string, std::string> values;
179+ if (!platform_info_.GetPlatformResWithLock(group, values)) {
180+ throw py::key_error(group);
181+ }
182+ return values;
183+ }
184+ 
185+ uint32_t GetCoreNum(const py::object &core_type) const {
186+ EnsureActiveContext();
187+ if (core_type.is_none()) {
188+ EnsurePlatformSnapshot();
189+ return platform_info_.GetCoreNumWithLock();
190+ }
191+ if (!py::isinstance<py::str>(core_type)) {
192+ throw py::type_error("core_type must be a string or None");
193+ }
194+ const auto value = core_type.cast<std::string>();
195+ if (value.empty()) {
196+ throw std::invalid_argument("core_type must not be empty");
197+ }
198+ EnsurePlatformSnapshot();
199+ return platform_info_.GetCoreNumByType(value);
200+ }
201+ 
202+ std::string GetSocVersion() const {
203+ EnsureActiveContext();
204+ EnsurePlatformSnapshot();
205+ return optional_infos_.GetSocVersion();
206+ }
207+ 
208+ uint32_t GetAiCoreNum() const {
209+ EnsureActiveContext();
210+ EnsurePlatformSnapshot();
211+ return optional_infos_.GetAICoreNum();
212+ }
213+ 
214+ private:
215+ static std::string RequireString(const py::object &value, const char *name) {
216+ if (!py::isinstance<py::str>(value)) {
217+ throw py::type_error(std::string(name) + " must be a string");
218+ }
219+ return value.cast<std::string>();
220+ }
221+ 
222+ void EnsureActiveContext() const {
223+ if ((active_ == nullptr) || (!(*active_)) || (ctx_ == nullptr)) {
224+ throw std::runtime_error("Borrowed native object has expired");
225+ }
226+ }
227+ 
228+ void EnsurePlatformSnapshot() const {
229+ EnsureActiveContext();
230+ if (platform_initialized_) {
231+ if (platform_failed_) {
232+ throw std::runtime_error("Failed to get platform infos");
233+ }
234+ return;
235+ }
236+ platform_initialized_ = true;
237+ const auto ret = ctx_->GetPlatformInfos(platform_info_, optional_infos_);
238+ if (ret != GRAPH_SUCCESS) {
239+ platform_failed_ = true;
240+ throw std::runtime_error("Failed to get platform infos");
241+ }
242+ }
243+ 
244+ gert::OpCompileContext *ctx_{nullptr};
245+ std::shared_ptr<bool> active_;
246+ mutable bool platform_initialized_{false};
247+ mutable bool platform_failed_{false};
248+ mutable fe::PlatFormInfos platform_info_;
249+ mutable fe::OptionalInfos optional_infos_;
250+};
251+ 
252+class BorrowedOpCompileContext {
253+ public:
254+ explicit BorrowedOpCompileContext(gert::OpCompileContext *ctx) : ctx_(ctx), active_(std::make_shared<bool>(true)) {}
255+ 
256+ py::object GetRequiredInputTensor(size_t ir_index) const {
257+ return CastRequiredTensor(Get()->GetRequiredInputTensor(ir_index), "Failed to get required input tensor");
258+ }
259+ 
260+ py::object GetOptionalInputTensor(size_t ir_index) const {
261+ const auto *tensor = Get()->GetOptionalInputTensor(ir_index);
262+ return (tensor == nullptr) ? py::none() : CastTensor(tensor);
263+ }
264+ 
265+ size_t GetDynamicInputNum(size_t ir_index) const {
266+ const auto *instance_info = Get()->GetIrInputInstanceInfo(ir_index);
267+ if (instance_info == nullptr) {
268+ throw std::runtime_error("Failed to get dynamic input instance info");
269+ }
270+ return instance_info->GetInstanceNum();
271+ }
272+ 
273+ py::object GetDynamicInputTensor(size_t ir_index, size_t relative_index) const {
274+ return CastRequiredTensor(Get()->GetDynamicInputTensor(ir_index, relative_index),
275+ "Failed to get dynamic input tensor");
276+ }
277+ 
278+ py::object GetRequiredOutputTensor(size_t ir_index) const {
279+ return CastRequiredTensor(Get()->GetRequiredOutputTensor(ir_index), "Failed to get required output tensor");
280+ }
281+ 
282+ size_t GetDynamicOutputNum(size_t ir_index) const {
283+ const auto *instance_info = Get()->GetIrOutputInstanceInfo(ir_index);
284+ if (instance_info == nullptr) {
285+ throw std::runtime_error("Failed to get dynamic output instance info");
286+ }
287+ return instance_info->GetInstanceNum();
288+ }
289+ 
290+ py::object GetDynamicOutputTensor(size_t ir_index, size_t relative_index) const {
291+ return CastRequiredTensor(Get()->GetDynamicOutputTensor(ir_index, relative_index),
292+ "Failed to get dynamic output tensor");
293+ }
294+ 
295+ py::object GetAttrs() const {
296+ const auto *attrs = Get()->GetAttrs();
297+ if (attrs == nullptr) {
298+ throw std::runtime_error("Failed to get runtime attrs");
299+ }
300+ return py::cast(BorrowedRuntimeAttrs(attrs, active_, true));
301+ }
302+ 
303+ std::string GetOption(const py::object &option_key_obj) const {
304+ EnsureActiveContext();
305+ const auto option_key = RequireString(option_key_obj, "option_key");
306+ if (option_key.empty()) {
307+ throw std::invalid_argument("option_key must not be empty");
308+ }
309+ ge::AscendString option;
310+ const auto ret = Get()->GetOption(ge::AscendString(option_key.c_str()), option);
311+ if (ret != GRAPH_SUCCESS) {
312+ throw py::key_error(option_key);
313+ }
314+ const auto *option_value = option.GetString();
315+ return (option_value == nullptr) ? std::string() : std::string(option_value, option.GetLength());
316+ }
317+ 
318+ BorrowedCompilePlatformInfo GetPlatformInfo() const {
319+ EnsureActiveContext();
320+ return BorrowedCompilePlatformInfo(ctx_, active_);
321+ }
322+ 
323+ void Invalidate() {
324+ if (active_ != nullptr) {
325+ *active_ = false;
326+ }
327+ ctx_ = nullptr;
328+ }
329+ 
330+ private:
331+ static std::string RequireString(const py::object &value, const char *name) {
332+ if (!py::isinstance<py::str>(value)) {
333+ throw py::type_error(std::string(name) + " must be a string");
334+ }
335+ return value.cast<std::string>();
336+ }
337+ 
338+ gert::OpCompileContext *Get() const {
339+ EnsureActiveContext();
340+ return ctx_;
341+ }
342+ 
343+ void EnsureActiveContext() const {
344+ if ((active_ == nullptr) || (!(*active_)) || (ctx_ == nullptr)) {
345+ throw std::runtime_error("Borrowed native object has expired");
346+ }
347+ }
348+ 
349+ py::object CastTensor(const gert::Tensor *tensor) const {
350+ return py::cast(runtime_native::NativeTensor::Borrow(tensor, active_));
351+ }
352+ 
353+ py::object CastRequiredTensor(const gert::Tensor *tensor, const char *message) const {
354+ if (tensor == nullptr) {
355+ throw std::runtime_error(message);
356+ }
357+ return CastTensor(tensor);
358+ }
359+ 
360+ gert::OpCompileContext *ctx_{nullptr};
361+ std::shared_ptr<bool> active_;
362+};
363+ 
364+BorrowedOpCompileContext BorrowOpCompileContext(uintptr_t ctx_handle) {
365+ if (ctx_handle == 0U) {
366+ throw std::invalid_argument("ctx_handle is null");
367+ }
368+ return BorrowedOpCompileContext(reinterpret_cast<gert::OpCompileContext *>(ctx_handle));
369+}
370+ 
144BorrowedEagerOpExecutionContext BorrowEagerOpExecutionContext(uintptr_t ctx_handle) {371BorrowedEagerOpExecutionContext BorrowEagerOpExecutionContext(uintptr_t ctx_handle) {
145 if (ctx_handle == 0U) {372 if (ctx_handle == 0U) {
146 throw std::invalid_argument("ctx_handle is null");373 throw std::invalid_argument("ctx_handle is null");
@@ -468,5 +695,29 @@ void BindAnnotatedArgsContext(py::module_ &m) {
468 m.def("_borrow_annotated_args_context", &BorrowAnnotatedArgsContext, py::arg("ctx_handle"));695 m.def("_borrow_annotated_args_context", &BorrowAnnotatedArgsContext, py::arg("ctx_handle"));
469}696}
470 697 
698+void BindOpCompileContext(py::module_ &m) {
699+ py::class_<BorrowedOpCompileContext>(m, "OpCompileContext", "Borrowed view of gert::OpCompileContext")
700+ .def("get_option", &BorrowedOpCompileContext::GetOption, py::arg("option_key"))
701+ .def("_get_platform_info", &BorrowedOpCompileContext::GetPlatformInfo)
702+ .def("_get_required_input_tensor", &BorrowedOpCompileContext::GetRequiredInputTensor, py::arg("ir_index"))
703+ .def("_get_optional_input_tensor", &BorrowedOpCompileContext::GetOptionalInputTensor, py::arg("ir_index"))
704+ .def("_get_dynamic_input_num", &BorrowedOpCompileContext::GetDynamicInputNum, py::arg("ir_index"))
705+ .def("_get_dynamic_input_tensor", &BorrowedOpCompileContext::GetDynamicInputTensor, py::arg("ir_index"),
706+ py::arg("relative_index"))
707+ .def("_get_required_output_tensor", &BorrowedOpCompileContext::GetRequiredOutputTensor, py::arg("ir_index"))
708+ .def("_get_dynamic_output_num", &BorrowedOpCompileContext::GetDynamicOutputNum, py::arg("ir_index"))
709+ .def("_get_dynamic_output_tensor", &BorrowedOpCompileContext::GetDynamicOutputTensor, py::arg("ir_index"),
710+ py::arg("relative_index"))
711+ .def("_get_attrs", &BorrowedOpCompileContext::GetAttrs)
712+ .def("_invalidate", &BorrowedOpCompileContext::Invalidate);
713+ py::class_<BorrowedCompilePlatformInfo>(m, "CompilePlatformInfo", "Borrowed platform information for compile")
714+ .def("get_platform_resource", &BorrowedCompilePlatformInfo::GetPlatformResource, py::arg("group"), py::arg("key"))
715+ .def("get_platform_resource_group", &BorrowedCompilePlatformInfo::GetPlatformResourceGroup, py::arg("group"))
716+ .def("get_core_num", &BorrowedCompilePlatformInfo::GetCoreNum, py::arg("core_type") = py::none())
717+ .def("get_soc_version", &BorrowedCompilePlatformInfo::GetSocVersion)
718+ .def("get_ai_core_num", &BorrowedCompilePlatformInfo::GetAiCoreNum);
719+ m.def("_borrow_op_compile_context", &BorrowOpCompileContext, py::arg("ctx_handle"));
720+}
721+ 
471} // namespace python_custom_op_native722} // namespace python_custom_op_native
472} // namespace ge723} // namespace ge
@@ -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 BindOpCompileContext(py::module_ &m);
21void BindInferMetaContext(py::module_ &m);22void BindInferMetaContext(py::module_ &m);
22 23 
23} // namespace python_custom_op_native24} // namespace python_custom_op_native
@@ -14,6 +14,7 @@ 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::BindOpCompileContext(m);
17 python_custom_op_native::BindInferMetaContext(m);18 python_custom_op_native::BindInferMetaContext(m);
18}19}
19} // namespace ge20} // namespace ge
@@ -113,7 +113,10 @@ inline py::list BuildIntListList(const gert::ContinuousVectorVector *value) {
113class BorrowedRuntimeAttrs {113class BorrowedRuntimeAttrs {
114 public:114 public:
115 BorrowedRuntimeAttrs(const gert::RuntimeAttrs *attrs, std::shared_ptr<bool> valid)115 BorrowedRuntimeAttrs(const gert::RuntimeAttrs *attrs, std::shared_ptr<bool> valid)
116- : attrs_(attrs), valid_(std::move(valid)) {}116+ : BorrowedRuntimeAttrs(attrs, std::move(valid), false) {}
117+ 
118+ BorrowedRuntimeAttrs(const gert::RuntimeAttrs *attrs, std::shared_ptr<bool> valid, bool read_only)
119+ : attrs_(attrs), valid_(std::move(valid)), read_only_(read_only) {}
117 120 
118 int64_t GetInt(size_t index) const {121 int64_t GetInt(size_t index) const {
119 return *runtime_attrs_binding_detail::GetRequiredAttr<int64_t>(Get(), index, "VT_INT");122 return *runtime_attrs_binding_detail::GetRequiredAttr<int64_t>(Get(), index, "VT_INT");
@@ -133,6 +136,9 @@ class BorrowedRuntimeAttrs {
133 }136 }
134 py::object GetTensor(size_t index) const {137 py::object GetTensor(size_t index) const {
135 const auto *tensor = runtime_attrs_binding_detail::GetRequiredAttr<gert::Tensor>(Get(), index, "VT_TENSOR");138 const auto *tensor = runtime_attrs_binding_detail::GetRequiredAttr<gert::Tensor>(Get(), index, "VT_TENSOR");
139+ if (read_only_) {
140+ return py::cast(runtime_attrs_binding_detail::runtime_native::NativeTensor::Borrow(tensor, valid_));
141+ }
136 return py::cast(142 return py::cast(
137 runtime_attrs_binding_detail::runtime_native::NativeTensor::Borrow(const_cast<gert::Tensor *>(tensor), valid_));143 runtime_attrs_binding_detail::runtime_native::NativeTensor::Borrow(const_cast<gert::Tensor *>(tensor), valid_));
138 }144 }
@@ -171,6 +177,7 @@ class BorrowedRuntimeAttrs {
171 177 
172 const gert::RuntimeAttrs *attrs_{nullptr};178 const gert::RuntimeAttrs *attrs_{nullptr};
173 std::shared_ptr<bool> valid_;179 std::shared_ptr<bool> valid_;
180+ bool read_only_{false};
174};181};
175} // namespace python_custom_op_native182} // namespace python_custom_op_native
176} // namespace ge183} // namespace ge
@@ -18,9 +18,11 @@ from dataclasses import dataclass, field
18from typing import Any, Dict, List, Optional, Type18from typing import Any, Dict, List, Optional, Type
19 19 
20INTERFACE_EAGER_EXECUTE = "eager_execute"20INTERFACE_EAGER_EXECUTE = "eager_execute"
21+INTERFACE_COMPILABLE = "compilable"
21INTERFACE_ANNOTATED_ARGS = "annotated_args"22INTERFACE_ANNOTATED_ARGS = "annotated_args"
22_INTERFACE_SPECS = (23_INTERFACE_SPECS = (
23 (INTERFACE_EAGER_EXECUTE, "execute"),24 (INTERFACE_EAGER_EXECUTE, "execute"),
25+ (INTERFACE_COMPILABLE, "compile"),
24 (INTERFACE_ANNOTATED_ARGS, "declare_launch_args"),26 (INTERFACE_ANNOTATED_ARGS, "declare_launch_args"),
25)27)
26 28 
@@ -94,11 +96,21 @@ def _normalize_op_type(op_type: str) -> str:
94 96 
95 97 
96def _collect_interfaces(cls: Type[Any]) -> List[str]:98def _collect_interfaces(cls: Type[Any]) -> List[str]:
97- return [99+ interfaces = []
98- name100+ for name, method_name in _INTERFACE_SPECS:
99- for name, method_name in _INTERFACE_SPECS101+ method = getattr(cls, method_name, None)
100- if callable(getattr(cls, method_name, None))102+ # Keep legacy interface discovery behavior for execute and
101- ]103+ # declare_launch_args. Compile is schema-bound and must reject an
104+ # explicitly declared non-callable callback at registration time.
105+ if (
106+ name == INTERFACE_COMPILABLE
107+ and hasattr(cls, method_name)
108+ and not callable(method)
109+ ):
110+ raise TypeError(f"{method_name} must be callable")
111+ if callable(method):
112+ interfaces.append(name)
113+ return interfaces
102 114 
103 115 
104def _get_interfaces(cls: Type[Any]) -> List[str]:116def _get_interfaces(cls: Type[Any]) -> List[str]:
@@ -65,11 +65,16 @@ class NativeObjectBase {
65 }65 }
66 }66 }
67 67 
68- T *Get() const {68+ const T *Get() const {
69 EnsureValid();69 EnsureValid();
70 return ptr_;70 return ptr_;
71 }71 }
72 72 
73+ bool IsReadOnly() const {
74+ EnsureValid();
75+ return read_only_;
76+ }
77+ 
73 const std::shared_ptr<bool> &GetValidity() const {78 const std::shared_ptr<bool> &GetValidity() const {
74 EnsureValid();79 EnsureValid();
75 return valid_;80 return valid_;
@@ -77,15 +82,37 @@ class NativeObjectBase {
77 82 
78 protected:83 protected:
79 explicit NativeObjectBase(std::unique_ptr<T> owned)84 explicit NativeObjectBase(std::unique_ptr<T> owned)
80- : owned_(std::move(owned)), ptr_(owned_.get()), valid_(std::make_shared<bool>(true)), owns_validity_(true) {}85+ : owned_(std::move(owned)),
86+ ptr_(owned_.get()),
87+ mutable_ptr_(owned_.get()),
88+ valid_(std::make_shared<bool>(true)),
89+ owns_validity_(true) {}
81 90 
82- NativeObjectBase(T *ptr, std::shared_ptr<bool> valid) : ptr_(ptr), valid_(std::move(valid)), owns_validity_(false) {}91+ NativeObjectBase(std::unique_ptr<T> owned, std::shared_ptr<bool> valid, const bool read_only)
92+ : owned_(std::move(owned)),
93+ ptr_(owned_.get()),
94+ valid_(std::move(valid)),
95+ owns_validity_(false),
96+ read_only_(read_only) {}
97+ 
98+ void EnsureMutable() const {
99+ EnsureValid();
100+ if (read_only_ || (mutable_ptr_ == nullptr)) {
101+ throw std::runtime_error("borrowed object is read-only");
102+ }
103+ }
104+ 
105+ T *MutableGet() const {
106+ EnsureMutable();
107+ return mutable_ptr_;
108+ }
83 109 
84 void Invalidate() {110 void Invalidate() {
85 if (valid_ != nullptr) {111 if (valid_ != nullptr) {
86 *valid_ = false;112 *valid_ = false;
87 }113 }
88 ptr_ = nullptr;114 ptr_ = nullptr;
115+ mutable_ptr_ = nullptr;
89 }116 }
90 117 
91 void EnsureValid() const {118 void EnsureValid() const {
@@ -96,14 +123,30 @@ class NativeObjectBase {
96 123 
97 private:124 private:
98 std::unique_ptr<T> owned_;125 std::unique_ptr<T> owned_;
99- T *ptr_{nullptr};126+ const T *ptr_{nullptr};
127+ T *mutable_ptr_{nullptr};
100 std::shared_ptr<bool> valid_;128 std::shared_ptr<bool> valid_;
101 bool owns_validity_{false};129 bool owns_validity_{false};
130+ bool read_only_{false};
131+ 
132+ protected:
133+ NativeObjectBase(T *ptr, std::shared_ptr<bool> valid)
134+ : ptr_(ptr), mutable_ptr_(ptr), valid_(std::move(valid)), owns_validity_(false) {}
135+ 
136+ NativeObjectBase(const T *ptr, std::shared_ptr<bool> valid)
137+ : ptr_(ptr), valid_(std::move(valid)), owns_validity_(false), read_only_(true) {}
102};138};
103 139 
104class PYBIND11_EXPORT NativeShape : public NativeObjectBase<gert::Shape> {140class PYBIND11_EXPORT NativeShape : public NativeObjectBase<gert::Shape> {
105 public:141 public:
106- static NativeShape Borrow(gert::Shape *ptr, std::shared_ptr<bool> valid) {142+ static NativeShape Borrow(gert::Shape *ptr, std::shared_ptr<bool> valid, const bool read_only = false) {
143+ if (read_only) {
144+ return NativeShape(static_cast<const gert::Shape *>(ptr), std::move(valid));
145+ }
146+ return NativeShape(ptr, std::move(valid));
147+ }
148+ 
149+ static NativeShape Borrow(const gert::Shape *ptr, std::shared_ptr<bool> valid) {
107 return NativeShape(ptr, std::move(valid));150 return NativeShape(ptr, std::move(valid));
108 }151 }
109 152 
@@ -124,14 +167,15 @@ class PYBIND11_EXPORT NativeShape : public NativeObjectBase<gert::Shape> {
124 }167 }
125 168 
126 void SetDim(size_t index, int64_t value) const {169 void SetDim(size_t index, int64_t value) const {
127- if (index >= Get()->GetDimNum()) {170+ auto *shape = MutableGet();
171+ if (index >= shape->GetDimNum()) {
128 throw std::invalid_argument("shape dimension index out of range");172 throw std::invalid_argument("shape dimension index out of range");
129 }173 }
130- Get()->SetDim(index, value);174+ shape->SetDim(index, value);
131 }175 }
132 176 
133 NativeShape &AppendDim(int64_t value) {177 NativeShape &AppendDim(int64_t value) {
134- (void)Get()->AppendDim(value);178+ (void)MutableGet()->AppendDim(value);
135 return *this;179 return *this;
136 }180 }
137 181 
@@ -144,11 +188,12 @@ class PYBIND11_EXPORT NativeShape : public NativeObjectBase<gert::Shape> {
144 }188 }
145 189 
146 void SetScalar() const {190 void SetScalar() const {
147- Get()->SetScalar();191+ MutableGet()->SetScalar();
148 }192 }
149 193 
150 private:194 private:
151 NativeShape(gert::Shape *ptr, std::shared_ptr<bool> valid) : NativeObjectBase(ptr, std::move(valid)) {}195 NativeShape(gert::Shape *ptr, std::shared_ptr<bool> valid) : NativeObjectBase(ptr, std::move(valid)) {}
196+ NativeShape(const gert::Shape *ptr, std::shared_ptr<bool> valid) : NativeObjectBase(ptr, std::move(valid)) {}
152};197};
153 198 
154class PYBIND11_EXPORT NativeStorageShape : public NativeObjectBase<gert::StorageShape> {199class PYBIND11_EXPORT NativeStorageShape : public NativeObjectBase<gert::StorageShape> {
@@ -157,32 +202,47 @@ class PYBIND11_EXPORT NativeStorageShape : public NativeObjectBase<gert::Storage
157 202 
158 NativeStorageShape(const std::vector<int64_t> &origin_shape, const std::vector<int64_t> &storage_shape)203 NativeStorageShape(const std::vector<int64_t> &origin_shape, const std::vector<int64_t> &storage_shape)
159 : NativeObjectBase(std::make_unique<gert::StorageShape>()) {204 : NativeObjectBase(std::make_unique<gert::StorageShape>()) {
160- Get()->MutableOriginShape() = DimsToShape(origin_shape);205+ MutableGet()->MutableOriginShape() = DimsToShape(origin_shape);
161- Get()->MutableStorageShape() = DimsToShape(storage_shape);206+ MutableGet()->MutableStorageShape() = DimsToShape(storage_shape);
162 }207 }
163 208 
164- static NativeStorageShape Borrow(gert::StorageShape *ptr, std::shared_ptr<bool> valid) {209+ static NativeStorageShape Borrow(gert::StorageShape *ptr, std::shared_ptr<bool> valid, const bool read_only = false) {
210+ if (read_only) {
211+ return NativeStorageShape(static_cast<const gert::StorageShape *>(ptr), std::move(valid));
212+ }
213+ return NativeStorageShape(ptr, std::move(valid));
214+ }
215+ 
216+ static NativeStorageShape Borrow(const gert::StorageShape *ptr, std::shared_ptr<bool> valid) {
165 return NativeStorageShape(ptr, std::move(valid));217 return NativeStorageShape(ptr, std::move(valid));
166 }218 }
167 219 
168 NativeShape GetOriginShape() const {220 NativeShape GetOriginShape() const {
169- return NativeShape::Borrow(&Get()->MutableOriginShape(), GetValidity());221+ if (IsReadOnly()) {
222+ return NativeShape::Borrow(&Get()->GetOriginShape(), GetValidity());
223+ }
224+ return NativeShape::Borrow(&MutableGet()->MutableOriginShape(), GetValidity());
170 }225 }
171 226 
172 NativeShape GetStorageShape() const {227 NativeShape GetStorageShape() const {
173- return NativeShape::Borrow(&Get()->MutableStorageShape(), GetValidity());228+ if (IsReadOnly()) {
229+ return NativeShape::Borrow(&Get()->GetStorageShape(), GetValidity());
230+ }
231+ return NativeShape::Borrow(&MutableGet()->MutableStorageShape(), GetValidity());
174 }232 }
175 233 
176 void SetOriginShape(const NativeShape &shape) const {234 void SetOriginShape(const NativeShape &shape) const {
177- Get()->MutableOriginShape() = *shape.Get();235+ MutableGet()->MutableOriginShape() = *shape.Get();
178 }236 }
179 237 
180 void SetStorageShape(const NativeShape &shape) const {238 void SetStorageShape(const NativeShape &shape) const {
181- Get()->MutableStorageShape() = *shape.Get();239+ MutableGet()->MutableStorageShape() = *shape.Get();
182 }240 }
183 241 
184 private:242 private:
185 NativeStorageShape(gert::StorageShape *ptr, std::shared_ptr<bool> valid) : NativeObjectBase(ptr, std::move(valid)) {}243 NativeStorageShape(gert::StorageShape *ptr, std::shared_ptr<bool> valid) : NativeObjectBase(ptr, std::move(valid)) {}
244+ NativeStorageShape(const gert::StorageShape *ptr, std::shared_ptr<bool> valid)
245+ : NativeObjectBase(ptr, std::move(valid)) {}
186};246};
187 247 
188struct NativeTensorDescValue {248struct NativeTensorDescValue {
@@ -196,11 +256,11 @@ class PYBIND11_EXPORT NativeTensorDesc : public NativeObjectBase<NativeTensorDes
196 : NativeObjectBase(CreateValue(shape, data_type)) {}256 : NativeObjectBase(CreateValue(shape, data_type)) {}
197 257 
198 NativeStorageShape GetShape() const {258 NativeStorageShape GetShape() const {
199- return NativeStorageShape::Borrow(&Get()->shape, GetValidity());259+ return NativeStorageShape::Borrow(&MutableGet()->shape, GetValidity());
200 }260 }
201 261 
202 void SetShape(const py::object &shape) {262 void SetShape(const py::object &shape) {
203- Get()->shape = ParseShape(shape);263+ MutableGet()->shape = ParseShape(shape);
204 }264 }
205 265 
206 py::object GetDataType() const {266 py::object GetDataType() const {
@@ -208,7 +268,7 @@ class PYBIND11_EXPORT NativeTensorDesc : public NativeObjectBase<NativeTensorDes
208 }268 }
209 269 
210 void SetDataType(const py::object &data_type) {270 void SetDataType(const py::object &data_type) {
211- Get()->data_type = ParseDataType(data_type);271+ MutableGet()->data_type = ParseDataType(data_type);
212 }272 }
213 273 
214 private:274 private:
@@ -271,10 +331,22 @@ class PYBIND11_EXPORT NativeExpandDimsType : public NativeObjectBase<gert::Expan
271 331 
272 explicit NativeExpandDimsType(int64_t rule) : NativeObjectBase(std::make_unique<gert::ExpandDimsType>(rule)) {}332 explicit NativeExpandDimsType(int64_t rule) : NativeObjectBase(std::make_unique<gert::ExpandDimsType>(rule)) {}
273 333 
274- static NativeExpandDimsType Borrow(gert::ExpandDimsType *ptr, std::shared_ptr<bool> valid) {334+ static NativeExpandDimsType Borrow(gert::ExpandDimsType *ptr, std::shared_ptr<bool> valid,
335+ const bool read_only = false) {
336+ if (read_only) {
337+ return NativeExpandDimsType(static_cast<const gert::ExpandDimsType *>(ptr), std::move(valid));
338+ }
275 return NativeExpandDimsType(ptr, std::move(valid));339 return NativeExpandDimsType(ptr, std::move(valid));
276 }340 }
277 341 
342+ static NativeExpandDimsType Borrow(const gert::ExpandDimsType *ptr, std::shared_ptr<bool> valid) {
343+ return NativeExpandDimsType(ptr, std::move(valid));
344+ }
345+ 
346+ static NativeExpandDimsType Snapshot(const gert::ExpandDimsType &value, std::shared_ptr<bool> valid) {
347+ return NativeExpandDimsType(std::make_unique<gert::ExpandDimsType>(value), std::move(valid));
348+ }
349+ 
278 uint64_t GetFullSize() const {350 uint64_t GetFullSize() const {
279 return Get()->GetFullSize();351 return Get()->GetFullSize();
280 }352 }
@@ -284,12 +356,16 @@ class PYBIND11_EXPORT NativeExpandDimsType : public NativeObjectBase<gert::Expan
284 }356 }
285 357 
286 void SetExpandIndex(uint64_t index) const {358 void SetExpandIndex(uint64_t index) const {
287- Get()->SetExpandIndex(index);359+ MutableGet()->SetExpandIndex(index);
288 }360 }
289 361 
290 private:362 private:
291 NativeExpandDimsType(gert::ExpandDimsType *ptr, std::shared_ptr<bool> valid)363 NativeExpandDimsType(gert::ExpandDimsType *ptr, std::shared_ptr<bool> valid)
292 : NativeObjectBase(ptr, std::move(valid)) {}364 : NativeObjectBase(ptr, std::move(valid)) {}
365+ NativeExpandDimsType(const gert::ExpandDimsType *ptr, std::shared_ptr<bool> valid)
366+ : NativeObjectBase(ptr, std::move(valid)) {}
367+ NativeExpandDimsType(std::unique_ptr<gert::ExpandDimsType> value, std::shared_ptr<bool> valid)
368+ : NativeObjectBase(std::move(value), std::move(valid), true) {}
293};369};
294 370 
295class PYBIND11_EXPORT NativeStorageFormat : public NativeObjectBase<gert::StorageFormat> {371class PYBIND11_EXPORT NativeStorageFormat : public NativeObjectBase<gert::StorageFormat> {
@@ -303,7 +379,15 @@ class PYBIND11_EXPORT NativeStorageFormat : public NativeObjectBase<gert::Storag
303 static_cast<ge::Format>(origin_format), static_cast<ge::Format>(storage_format), *expand_dims_type.Get())) {379 static_cast<ge::Format>(origin_format), static_cast<ge::Format>(storage_format), *expand_dims_type.Get())) {
304 }380 }
305 381 
306- static NativeStorageFormat Borrow(gert::StorageFormat *ptr, std::shared_ptr<bool> valid) {382+ static NativeStorageFormat Borrow(gert::StorageFormat *ptr, std::shared_ptr<bool> valid,
383+ const bool read_only = false) {
384+ if (read_only) {
385+ return NativeStorageFormat(static_cast<const gert::StorageFormat *>(ptr), std::move(valid));
386+ }
387+ return NativeStorageFormat(ptr, std::move(valid));
388+ }
389+ 
390+ static NativeStorageFormat Borrow(const gert::StorageFormat *ptr, std::shared_ptr<bool> valid) {
307 return NativeStorageFormat(ptr, std::move(valid));391 return NativeStorageFormat(ptr, std::move(valid));
308 }392 }
309 393 
@@ -316,24 +400,29 @@ class PYBIND11_EXPORT NativeStorageFormat : public NativeObjectBase<gert::Storag
316 }400 }
317 401 
318 NativeExpandDimsType GetExpandDimsType() const {402 NativeExpandDimsType GetExpandDimsType() const {
319- return NativeExpandDimsType::Borrow(&Get()->MutableExpandDimsType(), GetValidity());403+ if (IsReadOnly()) {
404+ return NativeExpandDimsType::Snapshot(Get()->GetExpandDimsType(), GetValidity());
405+ }
406+ return NativeExpandDimsType::Borrow(&MutableGet()->MutableExpandDimsType(), GetValidity());
320 }407 }
321 408 
322 void SetOriginFormat(int32_t origin_format) const {409 void SetOriginFormat(int32_t origin_format) const {
323- Get()->SetOriginFormat(static_cast<ge::Format>(origin_format));410+ MutableGet()->SetOriginFormat(static_cast<ge::Format>(origin_format));
324 }411 }
325 412 
326 void SetStorageFormat(int32_t storage_format) const {413 void SetStorageFormat(int32_t storage_format) const {
327- Get()->SetStorageFormat(static_cast<ge::Format>(storage_format));414+ MutableGet()->SetStorageFormat(static_cast<ge::Format>(storage_format));
328 }415 }
329 416 
330 void SetExpandDimsType(const NativeExpandDimsType &expand_dims_type) const {417 void SetExpandDimsType(const NativeExpandDimsType &expand_dims_type) const {
331- Get()->SetExpandDimsType(*expand_dims_type.Get());418+ MutableGet()->SetExpandDimsType(*expand_dims_type.Get());
332 }419 }
333 420 
334 private:421 private:
335 NativeStorageFormat(gert::StorageFormat *ptr, std::shared_ptr<bool> valid)422 NativeStorageFormat(gert::StorageFormat *ptr, std::shared_ptr<bool> valid)
336 : NativeObjectBase(ptr, std::move(valid)) {}423 : NativeObjectBase(ptr, std::move(valid)) {}
424+ NativeStorageFormat(const gert::StorageFormat *ptr, std::shared_ptr<bool> valid)
425+ : NativeObjectBase(ptr, std::move(valid)) {}
337};426};
338 427 
339class PYBIND11_EXPORT NativeTensor : public NativeObjectBase<gert::Tensor> {428class PYBIND11_EXPORT NativeTensor : public NativeObjectBase<gert::Tensor> {
@@ -342,6 +431,10 @@ class PYBIND11_EXPORT NativeTensor : public NativeObjectBase<gert::Tensor> {
342 return NativeTensor(ptr, std::move(valid));431 return NativeTensor(ptr, std::move(valid));
343 }432 }
344 433 
434+ static NativeTensor Borrow(const gert::Tensor *ptr, std::shared_ptr<bool> valid) {
435+ return NativeTensor(ptr, std::move(valid));
436+ }
437+ 
345 uintptr_t GetAddr() const {438 uintptr_t GetAddr() const {
346 return reinterpret_cast<uintptr_t>(Get()->GetAddr());439 return reinterpret_cast<uintptr_t>(Get()->GetAddr());
347 }440 }
@@ -355,19 +448,32 @@ class PYBIND11_EXPORT NativeTensor : public NativeObjectBase<gert::Tensor> {
355 }448 }
356 449 
357 NativeStorageShape GetShape() const {450 NativeStorageShape GetShape() const {
358- return NativeStorageShape::Borrow(&Get()->GetShape(), GetValidity());451+ if (IsReadOnly()) {
452+ return NativeStorageShape::Borrow(&Get()->GetShape(), GetValidity());
453+ }
454+ return NativeStorageShape::Borrow(&MutableGet()->GetShape(), GetValidity());
359 }455 }
360 456 
361 NativeShape GetStorageShape() const {457 NativeShape GetStorageShape() const {
362- return NativeShape::Borrow(&Get()->MutableStorageShape(), GetValidity());458+ // Compile callbacks borrow const tensors, while execute callbacks retain mutable tensor views.
459+ if (IsReadOnly()) {
C
CChang-an-HW15 天前

为什么会加这个条件分支?

likedislike
shangdf
14 天前 评论:
shangdf
14 天前 评论:
shangdf
14 天前 评论:
460+ return NativeShape::Borrow(&Get()->GetStorageShape(), GetValidity());
461+ }
462+ return NativeShape::Borrow(&MutableGet()->MutableStorageShape(), GetValidity());
363 }463 }
364 464 
365 NativeShape GetOriginShape() const {465 NativeShape GetOriginShape() const {
366- return NativeShape::Borrow(&Get()->MutableOriginShape(), GetValidity());466+ if (IsReadOnly()) {
467+ return NativeShape::Borrow(&Get()->GetOriginShape(), GetValidity());
468+ }
469+ return NativeShape::Borrow(&MutableGet()->MutableOriginShape(), GetValidity());
367 }470 }
368 471 
369 NativeStorageFormat GetFormat() const {472 NativeStorageFormat GetFormat() const {
370- return NativeStorageFormat::Borrow(&Get()->MutableFormat(), GetValidity());473+ if (IsReadOnly()) {
474+ return NativeStorageFormat::Borrow(&Get()->GetFormat(), GetValidity());
475+ }
476+ return NativeStorageFormat::Borrow(&MutableGet()->MutableFormat(), GetValidity());
371 }477 }
372 478 
373 py::object GetStorageFormat() const {479 py::object GetStorageFormat() const {
@@ -379,7 +485,10 @@ class PYBIND11_EXPORT NativeTensor : public NativeObjectBase<gert::Tensor> {
379 }485 }
380 486 
381 NativeExpandDimsType GetExpandDimsType() const {487 NativeExpandDimsType GetExpandDimsType() const {
382- return NativeExpandDimsType::Borrow(&Get()->MutableFormat().MutableExpandDimsType(), GetValidity());488+ if (IsReadOnly()) {
489+ return NativeExpandDimsType::Snapshot(Get()->GetFormat().GetExpandDimsType(), GetValidity());
490+ }
491+ return NativeExpandDimsType::Borrow(&MutableGet()->MutableFormat().MutableExpandDimsType(), GetValidity());
383 }492 }
384 493 
385 py::object GetDataType() const {494 py::object GetDataType() const {
@@ -392,6 +501,7 @@ class PYBIND11_EXPORT NativeTensor : public NativeObjectBase<gert::Tensor> {
392 501 
393 private:502 private:
394 NativeTensor(gert::Tensor *ptr, std::shared_ptr<bool> valid) : NativeObjectBase(ptr, std::move(valid)) {}503 NativeTensor(gert::Tensor *ptr, std::shared_ptr<bool> valid) : NativeObjectBase(ptr, std::move(valid)) {}
504+ NativeTensor(const gert::Tensor *ptr, std::shared_ptr<bool> valid) : NativeObjectBase(ptr, std::move(valid)) {}
395};505};
396 506 
397} // namespace python_runtime_native507} // namespace python_runtime_native
@@ -807,7 +807,7 @@ custom_op/
807├── proto.py # Python custom operator prototype parser, descriptors, and registry807├── proto.py # Python custom operator prototype parser, descriptors, and registry
808├── registry.py # Python custom operator implementation registry and decorators808├── registry.py # Python custom operator implementation registry and decorators
809├── bootstrap.py # Plugin discovery and loading809├── bootstrap.py # Plugin discovery and loading
810-├── context.py # Current execution context binding for schema-bound execute810+├── context.py # Current execution context binding for schema-bound execute/compile
811├── _bridge.py # Bridge runtime helper (instance management, for C++ bridge .so callbacks)811├── _bridge.py # Bridge runtime helper (instance management, for C++ bridge .so callbacks)
812├── _native.py # Native module loading and re-export812├── _native.py # Native module loading and re-export
813├── _artifact_utils.py # Runtime artifact selection helper813├── _artifact_utils.py # Runtime artifact selection helper
@@ -816,11 +816,11 @@ custom_op/
816```816```
817 817 
818Note: Files prefixed with underscores are internal modules in the Python style.818Note: Files prefixed with underscores are internal modules in the Python style.
819-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.819+Note: `EagerOpExecutionContext`, `OpCompileContext`, `CompilePlatformInfo`, `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, compilation, or an `infer_meta` callback are provided by the `ge.runtime` module.
820 820 
821#### Module Positioning821#### Module Positioning
822 822 
823-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 do not inherit from capability base classes. The execution entry uses a schema-bound form whose inputs and attributes are bound from canonical IR in declaration order; callbacks access the execution context through `get_execute_ctx()`. 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.823+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`, `compile`, and `declare_launch_args` methods are now reflected from the implementation class to detect execution capability, graph-compilation capability, and declarative static-graph address-refresh capability, respectively. User classes do not inherit from capability base classes. The execution entry uses a schema-bound form whose inputs and attributes are bound from canonical IR in declaration order; callbacks access the execution context through `get_execute_ctx()`, while `compile` and `declare_launch_args` bind inputs, outputs, and attributes from canonical IR. 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.
824 824 
825#### Runtime Native Artifact Selection825#### Runtime Native Artifact Selection
826 826 
@@ -906,7 +906,15 @@ The index accepted by `append_input(instance_index, tensor)` and `append_output(
906 906 
907Within one AnnotatedArgs task-plan lifecycle, `declare_launch_args` is invoked exactly once and the resulting task plan is cached. Later generation phases only materialize the cached task plan from the current `RunContext` and do not call back into Python. A new task-plan lifecycle performs a new declaration. Borrowed objects from one callback must not be reused across callbacks. Compilation stores the selected refresh mode in `_custom_task_args_mode`; model loading treats it as the source of truth, while OMs without the attribute retain the legacy registry lookup and `args_format` fallback. The model execution path does not invoke Python.907Within one AnnotatedArgs task-plan lifecycle, `declare_launch_args` is invoked exactly once and the resulting task plan is cached. Later generation phases only materialize the cached task plan from the current `RunContext` and do not call back into Python. A new task-plan lifecycle performs a new declaration. Borrowed objects from one callback must not be reused across callbacks. Compilation stores the selected refresh mode in `_custom_task_args_mode`; model loading treats it as the source of truth, while OMs without the attribute retain the legacy registry lookup and `args_format` fallback. The model execution path does not invoke Python.
908 908 
909-##### 5. OpImplDescriptor Data Class909+##### 5. Python Compile and Compile Context
910+ 
911+**File location**: `context.py`, `_native.py`, `_ge_custom_op_native.pyi`
912+ 
913+`compile` is a graph-compilation callback and supports only the schema-bound form. GE passes inputs and outputs as positional arguments and attributes as keyword-only arguments according to the Ascend IR operator prototype. Both the return annotation and return value must be `None`. The callback uses `get_compile_ctx()` to query compile options and `get_compile_platform_info()` to query platform resources, core counts, and SoC information.
914+ 
915+`OpCompileContext`, `CompilePlatformInfo`, input and output `Tensor` objects, and attribute views are borrowed objects valid only during the callback and become invalid after it returns or raises. The compile callback is not invoked during model loading or execution, and the Python implementation, instance state, and kernel binary are not written to the OM.
916+ 
917+##### 6. OpImplDescriptor Data Class
910 918 
911**File location**: `registry.py`919**File location**: `registry.py`
912 920 
@@ -918,7 +926,7 @@ Within one AnnotatedArgs task-plan lifecycle, `declare_launch_args` is invoked e
918- `op_type` - Custom operator type926- `op_type` - Custom operator type
919- `module_name` - Associated module name927- `module_name` - Associated module name
920- `class_name` - Class name928- `class_name` - Class name
921-- `interfaces` - Capability interface list; it may contain `"eager_execute"` and `"annotated_args"`929+- `interfaces` - Capability interface list; it may contain `"eager_execute"`, `"compilable"`, and `"annotated_args"`
922- `cls` - Python implementation class reference930- `cls` - Python implementation class reference
923 931 
924#### Registration and Discovery932#### Registration and Discovery
@@ -926,7 +934,7 @@ Within one AnnotatedArgs task-plan lifecycle, `declare_launch_args` is invoked e
926**Decorators**:934**Decorators**:
927 935 
928- `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` objects936- `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
929-- `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`937+- `register_op_impl(op_type)` - Registers a Python implementation class and reflects its callable methods into a capability list; `execute` maps to `eager_execute`, `compile` maps to `compilable`, and `declare_launch_args` maps to `annotated_args`
930 938 
931**Discovery mechanism**:939**Discovery mechanism**:
932 940 
@@ -17,6 +17,7 @@ The long-term goal of Python custom operators is to let users describe custom op
17- The C++ runtime accesses the existing `CustomOpFactory` / `CustomOpRegistry` through `PythonCustomOpAdapter`.17- The C++ runtime accesses the existing `CustomOpFactory` / `CustomOpRegistry` through `PythonCustomOpAdapter`.
18- The Python native module `_ge_custom_op_native` provides `EagerOpExecutionContext` and `RuntimeAttrs` borrowed views.18- The Python native module `_ge_custom_op_native` provides `EagerOpExecutionContext` and `RuntimeAttrs` borrowed views.
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+- Python users implement the graph-compilation callback through schema-bound `compile`, and query compilation context and platform information through `get_compile_ctx()` and `get_compile_platform_info()`.
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- `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 22 
22V2 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.23V2 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.
@@ -37,7 +38,7 @@ The following items remain outside the V2 scope:
37- Python does not directly expose C++ `BaseCustomOp` capability interfaces such as `ShapeInferOp`, `CompilableOp`, `PortableOp`, and `ArgsUpdater`; V2 provides Meta inference through the `infer_meta` callback instead.38- Python does not directly expose C++ `BaseCustomOp` capability interfaces such as `ShapeInferOp`, `CompilableOp`, `PortableOp`, and `ArgsUpdater`; V2 provides Meta inference through the `infer_meta` callback instead.
38- Data-dependent inference that reads input Tensor data.39- Data-dependent inference that reads input Tensor data.
39- InferShapeRange, format inference, symbolic inference, and shape-rule generation.40- InferShapeRange, format inference, symbolic inference, and shape-rule generation.
40-- Python argument binding for `compile`, `serialize`, and `deserialize`, as well as ES API generation.41+- Python argument binding for `serialize` and `deserialize`, as well as ES API generation.
41- New schema-bound `execute` features.42- New schema-bound `execute` features.
42- Python custom operator serialization and deserialization with OM and cross-process loading.43- Python custom operator serialization and deserialization with OM and cross-process loading.
43- External encapsulation of `KernelArgs` / `MallocReadOnlyDevArgs` on the Python side.44- External encapsulation of `KernelArgs` / `MallocReadOnlyDevArgs` on the Python side.
@@ -67,10 +68,10 @@ The actual module boundaries are as follows:
67|--------|----------|----------------|68|--------|----------|----------------|
68| Python API | `api/python/ge/ge/custom_op/` | Implementation method reflection, registration, plugin discovery, bridge helper |69| Python API | `api/python/ge/ge/custom_op/` | Implementation method reflection, registration, plugin discovery, bridge helper |
69| Runtime types | `api/python/ge/ge/runtime/` | Runtime data structures such as `Tensor`, `StorageShape`, and `StorageFormat` |70| 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, AnnotatedArgs, and InferShape contexts, argument builders, and `RuntimeAttrs` |71+| Native context | `api/python/ge/ge/custom_op/native_bindings/` | `_ge_custom_op_native`, binding Eager, Compile, 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 |72| 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 |73| 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` |74+| Pybind bridge | `runtime/custom_op/python_custom_op_pybind_bridge.cc` | Importing the Python bridge module, creating holders, and calling back `execute` / `compile` / `declare_launch_args` |
74| Proto runtime | `runtime/custom_op/python_custom_op_proto.*` | Deep-copying C POD prototypes and registering `OperatorFactory` creators |75| Proto runtime | `runtime/custom_op/python_custom_op_proto.*` | Deep-copying C POD prototypes and registering `OperatorFactory` creators |
75| Adapter | `runtime/custom_op/python_custom_op_adapter.*` | Serving as a C++ `BaseCustomOp` instance to access the existing runtime |76| Adapter | `runtime/custom_op/python_custom_op_adapter.*` | Serving as a C++ `BaseCustomOp` instance to access the existing runtime |
76| Capability helper | `inc/graph_metadef/graph/custom_op/` | `CustomOpCapability` and `CustomOpCast<T>` |77| Capability helper | `inc/graph_metadef/graph/custom_op/` | `CustomOpCapability` and `CustomOpCast<T>` |
@@ -82,10 +83,11 @@ V1 functions include:
82- `@register_op_impl(op_type=...)` registers a Python custom operator implementation.83- `@register_op_impl(op_type=...)` registers a Python custom operator implementation.
83- `@register_op(op_type=..., mutates_args=...)` collects a custom operator prototype from a Python function signature.84- `@register_op(op_type=..., mutates_args=...)` collects a custom operator prototype from a Python function signature.
84- The bridge registers Python prototypes as `OperatorFactory` creators and collects canonical IR from the effective creators. It does not invoke `infer_meta` at this stage.85- The bridge registers Python prototypes as `OperatorFactory` creators and collects canonical IR from the effective creators. It does not invoke `infer_meta` at this stage.
85-- `register_op_impl` reflects callable `execute` and `declare_launch_args` methods and declares the corresponding capabilities without requiring inheritance from any capability base class.86+- `register_op_impl` reflects callable `execute`, `compile`, and `declare_launch_args` methods and declares the corresponding capabilities without requiring inheritance from any capability base class.
86- The schema-bound `execute` form receives inputs and attributes assembled from canonical IR; callbacks use `get_execute_ctx()` when they need the execution context.87- The schema-bound `execute` form receives inputs and attributes assembled from canonical IR; callbacks use `get_execute_ctx()` when they need the execution context.
87- `EagerOpExecutionContext` supports input and output tensor queries, dynamic input instance counts, runtime attribute access, output and workspace allocation, and stream retrieval.88- `EagerOpExecutionContext` supports input and output tensor queries, dynamic input instance counts, runtime attribute access, output and workspace allocation, and stream retrieval.
88- `declare_launch_args` binds inputs and outputs as positional arguments and attributes as keyword-only arguments from canonical IR. The callback uses `get_declare_launch_args_ctx()` to create an argument builder, allocate workspace, and add kernel launches. The index passed to `append_input` / `append_output` is the flattened input/output instance index of the compute node.89- `declare_launch_args` binds inputs and outputs as positional arguments and attributes as keyword-only arguments from canonical IR. The callback uses `get_declare_launch_args_ctx()` to create an argument builder, allocate workspace, and add kernel launches. The index passed to `append_input` / `append_output` is the flattened input/output instance index of the compute node.
90+- `compile` assembles inputs, outputs, and attributes from canonical IR, queries compile options through `get_compile_ctx()`, and queries platform resources, core counts, and SoC information through `get_compile_platform_info()`. This callback is used only during graph compilation; model loading and execution do not invoke it.
89- `ASCEND_CUSTOM_OPP_PATH` carries both the existing C++ custom op OPP paths and the Python custom op file or package paths.91- `ASCEND_CUSTOM_OPP_PATH` carries both the existing C++ custom op OPP paths and the Python custom op file or package paths.
90- GE initialization and `GraphManager::PreRun()` idempotently load Python custom ops when needed, so that the corresponding op type is visible before `OpsKernelInfo` is refreshed.92- GE initialization and `GraphManager::PreRun()` idempotently load Python custom ops when needed, so that the corresponding op type is visible before `OpsKernelInfo` is refreshed.
91 93 
@@ -96,11 +98,11 @@ V1 functions include:
96- Python entry failures affect loading only when Python custom op entries actually exist. The loading is skipped when no Python file or package is present.98- Python entry failures affect loading only when Python custom op entries actually exist. The loading is skipped when no Python file or package is present.
97- `EagerOpExecutionContext`, `AnnotatedArgsContext`, `RuntimeAttrs`, and borrowed views such as `Tensor` objects returned through them can be used only within the current callback. An `AnnotatedKernelArgs` object cannot be reused after `add_launch` consumes it.99- `EagerOpExecutionContext`, `AnnotatedArgsContext`, `RuntimeAttrs`, and borrowed views such as `Tensor` objects returned through them can be used only within the current callback. An `AnnotatedKernelArgs` object cannot be reused after `add_launch` consumes it.
98- The Python `execute` and `declare_launch_args` methods must return `None`. A normal `None` return indicates success, and an exception indicates failure.100- The Python `execute` and `declare_launch_args` methods must return `None`. A normal `None` return indicates success, and an exception indicates failure.
99-- The C++ adapter for Python custom ops currently declares `EagerExecuteOp` and `AnnotatedArgsOp` capabilities. Other C++ capability interfaces are retained as overrides in the adapter but are treated as unsupported.101+- The C++ adapter for Python custom ops currently declares `EagerExecuteOp`, `CompilableOp`, and `AnnotatedArgsOp` capabilities. Other C++ capability interfaces are retained as overrides in the adapter but are treated as unsupported.
100-- The schema-bound form depends on canonical IR from the existing operator prototype. While loading descriptors, the bridge collects canonical IR and calls `validate_op_impl_descriptor` before holder creation or any business callback to validate schema-bound signatures once. Both callbacks require an explicit `None` return annotation and a `None` runtime return. `execute` validates IR inputs and attributes and excludes IR outputs from its parameters. `declare_launch_args` additionally validates outputs. Runtime callbacks only assemble arguments, invoke business methods, and check the runtime return; they do not validate signatures, and validation state does not enter the holder lifecycle.102+- The schema-bound form depends on canonical IR from the existing operator prototype. While loading descriptors, the bridge collects canonical IR and calls `validate_op_impl_descriptor` before holder creation or any business callback to validate schema-bound signatures once. `execute` validates IR inputs and attributes and excludes IR outputs from its parameters; `compile` and `declare_launch_args` validate inputs, outputs, and attributes and require an explicit `None` return annotation. All three callbacks must return `None` at runtime. Runtime callbacks only assemble arguments, invoke business methods, and check the runtime return; they do not validate signatures, and validation state does not enter the holder lifecycle.
101- Cross-SO prototype and Adapter descriptors are synchronously borrowed C POD views. Runtime callbacks must finish validation and deep copying before returning.103- Cross-SO prototype and Adapter descriptors are synchronously borrowed C POD views. Runtime callbacks must finish validation and deep copying before returning.
102- A Python prototype may replace a built-in prototype. If `CustomOpFactory` already contains a C++ or Python custom operator with the same name, registration reports a custom-operator conflict.104- A Python prototype may replace a built-in prototype. If `CustomOpFactory` already contains a C++ or Python custom operator with the same name, registration reports a custom-operator conflict.
103-- A schema-bound callback obtains the current context through `get_execute_ctx()`. The binding is valid only in the dynamic scope of that callback.105+- A schema-bound callback obtains the current context through `get_execute_ctx()`, `get_compile_ctx()`, or `get_declare_launch_args_ctx()`. The binding is valid only in the dynamic scope of that callback.
104- The Python custom op native/bridge is related to the Python ABI at build time. Cross-Python minor version compatibility is not guaranteed.106- The Python custom op native/bridge is related to the Python ABI at build time. Cross-Python minor version compatibility is not guaranteed.
105- The bridge C ABI remains v1. The `execute` and `declare_launch_args` callbacks pass only the holder and corresponding context. The bridge queries canonical IR through the public run-package API instead of passing a private ABI projection.107- The bridge C ABI remains v1. The `execute` and `declare_launch_args` callbacks pass only the holder and corresponding context. The bridge queries canonical IR through the public run-package API instead of passing a private ABI projection.
106 108 
@@ -111,6 +113,7 @@ V1 functions include:
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.113- 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.114- 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.115- `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.
116+- `compile` runs during graph compilation. Its compilation context and platform information are valid only during the callback; the Python implementation, instance state, and kernel binaries are not saved to OM.
114 117 
115## 3. Feature Requirement Analysis and Design118## 3. Feature Requirement Analysis and Design
116 119 
@@ -211,7 +214,28 @@ Users implement `declare_launch_args` to declare the ordered arguments for each
211- Compilation stores the selected refresh mode in `_custom_task_args_mode` and the launch argument format in `args_format`.214- Compilation stores the selected refresh mode in `_custom_task_args_mode` and the launch argument format in `args_format`.
212- Static-model loading selects the refresh path from the explicit mode and consumes `args_format` for AnnotatedArgs without calling Python. Only an old OM without the attribute uses the registry and `args_format` fallback.215- Static-model loading selects the refresh path from the explicit mode and consumes `args_format` for AnnotatedArgs without calling Python. Only an old OM without the attribute uses the registry and `args_format` fallback.
213 216 
214-#### 3.2.3 Plugin Discovery and Registration217+#### 3.2.3 Python Compile Graph-Compilation Interface
218+ 
219+**Introduction**
220+ 
221+Users implement a `compile` method to declare graph-compilation capability. The method supports only the schema-bound form, and GE passes inputs, outputs, and attributes in order according to the Ascend IR operator prototype.
222+ 
223+**Input**
224+ 
225+- Inputs and outputs are positional arguments. Required inputs and outputs use `Tensor`, optional inputs use `Optional[Tensor]`, and dynamic inputs and outputs use `List[Tensor]`.
226+- Attributes are keyword-only arguments whose names, order, and types match the Ascend IR operator prototype.
227+ 
228+**Processing**
229+ 
230+- During descriptor loading, the bridge validates the `compile` signature. During the callback, `get_compile_ctx()` provides compile-option lookup and `get_compile_platform_info()` provides platform-resource, core-count, and SoC queries.
231+- `OpCompileContext`, `CompilePlatformInfo`, input and output `Tensor` objects, and attribute views become invalid when the callback returns or raises. The return value must be `None`.
232+ 
233+**Output**
234+ 
235+- `compile` is invoked only during graph compilation; model loading and execution do not invoke it.
236+- The Python implementation, instance state, and kernel binary are not written to OM.
237+ 
238+#### 3.2.4 Plugin Discovery and Registration
215 239 
216**Introduction**240**Introduction**
217 241 
@@ -242,7 +266,7 @@ Each descriptor contains at least:
242| `class_name` | Python class name |266| `class_name` | Python class name |
243| `interfaces` | Capability list containing `"eager_execute"`, `"annotated_args"`, or both |267| `interfaces` | Capability list containing `"eager_execute"`, `"annotated_args"`, or both |
244 268 
245-#### 3.2.4 Native Context Interface269+#### 3.2.5 Native Context Interface
246 270 
247**Introduction**271**Introduction**
248 272 
@@ -292,7 +316,7 @@ The bridge layer injects the Python borrowed view at the execution entry.
292- Stream and workspace addresses are represented as Python `int`.316- Stream and workspace addresses are represented as Python `int`.
293- `RuntimeAttrs` and borrowed objects returned from it expire with the current context.317- `RuntimeAttrs` and borrowed objects returned from it expire with the current context.
294 318 
295-#### 3.2.5 C++ Adapter and Capability Detection319+#### 3.2.6 C++ Adapter and Capability Detection
296 320 
297**Introduction**321**Introduction**
298 322 
@@ -306,16 +330,17 @@ Existing C++ custom ops express capabilities through interface inheritance. The
306**Processing**330**Processing**
307 331 
308- `PythonCustomOpAdapter` inherits `EagerExecuteOp`, `AnnotatedArgsOp`, `CompilableOp`, `ShapeInferOp`, `PortableOp`, `ArgsUpdater`, and `CustomOpCapabilityProvider`.332- `PythonCustomOpAdapter` inherits `EagerExecuteOp`, `AnnotatedArgsOp`, `CompilableOp`, `ShapeInferOp`, `PortableOp`, `ArgsUpdater`, and `CustomOpCapabilityProvider`.
309-- `PythonCustomOpCallbacks::IsValid()` accepts `kEagerExecute`, `kAnnotatedArgs`, or both and verifies that each capability has its corresponding callback.333+- `PythonCustomOpCallbacks::IsValid()` accepts `kEagerExecute`, `kCompilable`, `kAnnotatedArgs`, and their combinations, and verifies that each capability has its corresponding callback.
310- The internal GE capability detection uses `CustomOpCast<T>()`. For regular C++ custom ops, it degrades to `dynamic_cast<T *>`. For the Python adapter, it checks the bitmask first.334- The internal GE capability detection uses `CustomOpCast<T>()`. For regular C++ custom ops, it degrades to `dynamic_cast<T *>`. For the Python adapter, it checks the bitmask first.
311 335 
312**Output**336**Output**
313 337 
314- When `kEagerExecute` is supported, `Execute(ctx)` forwards to Python.338- When `kEagerExecute` is supported, `Execute(ctx)` forwards to Python.
339+- When `kCompilable` is supported, `Compile(ctx)` forwards to Python.
315- When `kAnnotatedArgs` is supported, `DeclareLaunchArgs(ctx)` forwards to Python.340- When `kAnnotatedArgs` is supported, `DeclareLaunchArgs(ctx)` forwards to Python.
316-- Unsupported `Compile`, `InferShape`, `InferDataType`, `Serialize`, `Deserialize`, and `UpdateHostArgs` return `GRAPH_FAILED` and log a message.341+- Unsupported `InferShape`, `InferDataType`, `Serialize`, `Deserialize`, and `UpdateHostArgs` return `GRAPH_FAILED` and log a message.
317 342 
318-#### 3.2.6 Loading, Unloading, and Lifecycle343+#### 3.2.7 Loading, Unloading, and Lifecycle
319 344 
320**Introduction**345**Introduction**
321 346 
@@ -481,14 +506,14 @@ The native binding holds a `gert::AnnotatedArgsContext *` and an independent val
481 506 
482- **Plugin discovery**: Reuses `ge._internal.plugin_loader` and splits environment variables by `os.pathsep`. Files are imported by dynamic module name, and directories are imported as one-level `.py` files and packages.507- **Plugin discovery**: Reuses `ge._internal.plugin_loader` and splits environment variables by `os.pathsep`. Files are imported by dynamic module name, and directories are imported as one-level `.py` files and packages.
483- **Artifact selection**: Reuses `python_artifact_utils` and `python_bridge_loader_utils` and matches `python_custom_op_artifacts` by the loaded Python runtime key.508- **Artifact selection**: Reuses `python_artifact_utils` and `python_bridge_loader_utils` and matches `python_custom_op_artifacts` by the loaded Python runtime key.
484-- **Capability reflection**: The registry applies `getattr` and `callable` checks to the implementation class, maps `execute` to `eager_execute`, and maps `declare_launch_args` to `annotated_args`. The inheritance hierarchy of the Python user class does not participate in capability detection.509+- **Capability reflection**: The registry applies `getattr` and `callable` checks to the implementation class, maps `execute` to `eager_execute`, `compile` to `compilable`, and `declare_launch_args` to `annotated_args`. The inheritance hierarchy of the Python user class does not participate in capability detection.
485- **Capability filtering**: `CustomOpCast<T>()` first identifies `CustomOpCapabilityProvider` and then checks the bitmask to determine whether the target interface is supported.510- **Capability filtering**: `CustomOpCast<T>()` first identifies `CustomOpCapabilityProvider` and then checks the bitmask to determine whether the target interface is supported.
486- **IR argument assembly**: The bridge queries canonical IR through the public run-package API during descriptor loading for signature validation, then queries it again at holder creation and owns the resulting runtime snapshot. Callbacks read required, optional, and dynamic inputs/outputs and typed runtime attributes in that IR order, then construct positional and keyword arguments.511- **IR argument assembly**: The bridge queries canonical IR through the public run-package API during descriptor loading for signature validation, then queries it again at holder creation and owns the resulting runtime snapshot. Callbacks read required, optional, and dynamic inputs/outputs and typed runtime attributes in that IR order, then construct positional and keyword arguments.
487- **Registration transaction**: The runtime synchronously deep-copies prototype C POD data and registers its creator, collects canonical IR, and finally registers the implementation runtime entry and Adapter creator. If any step fails, the upper-level loader invokes unload to roll the completed steps back in reverse order.512- **Registration transaction**: The runtime synchronously deep-copies prototype C POD data and registers its creator, collects canonical IR, and finally registers the implementation runtime entry and Adapter creator. If any step fails, the upper-level loader invokes unload to roll the completed steps back in reverse order.
488-- **Callback signature validation**: While loading a descriptor, the bridge calls `validate_op_impl_descriptor` to validate schema-bound signatures once, before holder creation or any business callback. Both callbacks require a `None` return annotation. For `execute`, it validates the total parameter count, the positional form and supplied type annotations of inputs, and the keyword-only form, names, and supplied type annotations of attributes. Outputs are excluded. `declare_launch_args` additionally validates outputs. Runtime callbacks do not validate signatures, but they check the runtime return value for `None`, and validation state does not enter the holder lifecycle.513+- **Callback signature validation**: While loading a descriptor, the bridge calls `validate_op_impl_descriptor` to validate schema-bound signatures once, before holder creation or any business callback. For `execute`, it validates the total parameter count, the positional form and supplied type annotations of inputs, and the keyword-only form, names, and supplied type annotations of attributes. `compile` and `declare_launch_args` additionally validate outputs; all three callbacks require a `None` return annotation. Runtime callbacks do not validate signatures, but they check the runtime return value for `None`, and validation state does not enter the holder lifecycle.
489- **Holder lifecycle**: The C++ adapter owns `PythonCustomOpHolder`. The Python side uses `_OP_IMPL_HOLDERS` to save instances by `instance_id`. When the adapter is destructed, the Python holder is destroyed.514- **Holder lifecycle**: The C++ adapter owns `PythonCustomOpHolder`. The Python side uses `_OP_IMPL_HOLDERS` to save instances by `instance_id`. When the adapter is destructed, the Python holder is destroyed.
490-- **Context binding**: Schema-bound callbacks establish dynamic scopes with `ContextVar`, and `get_execute_ctx()` / `get_declare_launch_args_ctx()` read their corresponding bindings. Resetting the token restores the outer context after nested invocation.515+- **Context binding**: Schema-bound callbacks establish dynamic scopes with `ContextVar`, and `get_execute_ctx()`, `get_compile_ctx()` / `get_compile_platform_info()`, and `get_declare_launch_args_ctx()` read their corresponding bindings. Resetting the token restores the outer context after nested invocation.
491-- **Context invalidation**: Both execute and declare bridge calls use `finally` to ensure context invalidation and do not rely on the user returning normally.516+- **Context invalidation**: Execute, compile, and declare bridge calls use `finally` to ensure context invalidation and do not rely on the user returning normally.
492 517 
493### 6.3 Process Design518### 6.3 Process Design
494 519 
@@ -552,7 +577,7 @@ PythonCustomOpAdapter::DeclareLaunchArgs(ctx)
552 577 
553- `api/python/ge/ge/custom_op/`: Added the Python custom op API, registry, bootstrap, bridge helper, a separate schema-callback signature-validation module, and native context binding.578- `api/python/ge/ge/custom_op/`: Added the Python custom op API, registry, bootstrap, bridge helper, a separate schema-callback signature-validation module, and native context binding.
554- `api/python/ge/ge/runtime/`: Provides runtime tensor/shape/format types for reuse by the custom op context.579- `api/python/ge/ge/runtime/`: Provides runtime tensor/shape/format types for reuse by the custom op context.
555-- `runtime/custom_op/`: Added the Python bridge loader and adapter while retaining bridge C ABI v1. The adapter forwards Eager and AnnotatedArgs callbacks, and the bridge obtains and caches canonical IR through the public run-package API.580+- `runtime/custom_op/`: Added the Python bridge loader and adapter while retaining bridge C ABI v1. The adapter forwards Eager, Compile, and AnnotatedArgs callbacks, and the bridge obtains and caches canonical IR through the public run-package API.
556- `inc/graph_metadef/graph/custom_op/`: Added capability and cast helper.581- `inc/graph_metadef/graph/custom_op/`: Added capability and cast helper.
557- GE initialization entry: Ensures the Python runtime is attempted to be ready before `LoadCustomOps()`. On failure, a warning is logged and execution continues. The Python custom op loader performs a hard fail only when Python entries actually exist.582- GE initialization entry: Ensures the Python runtime is attempted to be ready before `LoadCustomOps()`. On failure, a warning is logged and execution continues. The Python custom op loader performs a hard fail only when Python entries actually exist.
558- `compiler/graph/manager/graph_manager.cc`: `PreRun()` idempotently loads Python custom ops before refreshing ops kernel information.583- `compiler/graph/manager/graph_manager.cc`: `PreRun()` idempotently loads Python custom ops before refreshing ops kernel information.
@@ -566,7 +591,8 @@ PythonCustomOpAdapter::DeclareLaunchArgs(ctx)
566- Holder creation failure: The adapter is deemed invalid and the execution path fails.591- Holder creation failure: The adapter is deemed invalid and the execution path fails.
567- Context query, output allocation, or workspace allocation failure: The native binding raises `RuntimeError`.592- Context query, output allocation, or workspace allocation failure: The native binding raises `RuntimeError`.
568- Failure to collect canonical IR required by a schema-bound descriptor causes descriptor loading/registration to fail before any holder or callback context is created.593- Failure to collect canonical IR required by a schema-bound descriptor causes descriptor loading/registration to fail before any holder or callback context is created.
569-- A schema-bound `execute` or `declare_launch_args` signature that does not match canonical IR causes `validate_op_impl_descriptor` to raise `TypeError` during descriptor loading/registration; no business callback runs.594+- A schema-bound `execute`, `compile`, or `declare_launch_args` signature that does not match canonical IR causes `validate_op_impl_descriptor` to raise `TypeError` during descriptor loading/registration; no business callback runs.
595+- A `compile` without canonical IR, with a non-`None` return, or with an expired context causes the Python bridge/native binding to raise and terminate graph compilation.
570- A non-`None` `declare_launch_args` return, reuse of a consumed `AnnotatedKernelArgs`, or an out-of-range flattened input/output instance index causes the Python bridge/native binding to raise and terminate compilation.596- A non-`None` `declare_launch_args` return, reuse of a consumed `AnnotatedKernelArgs`, or an out-of-range flattened input/output instance index causes the Python bridge/native binding to raise and terminate compilation.
571 597 
572#### Interface Errors598#### Interface Errors
@@ -578,6 +604,7 @@ PythonCustomOpAdapter::DeclareLaunchArgs(ctx)
578- Duplicate `op_type` or `descriptor_key`: `ValueError` is raised.604- Duplicate `op_type` or `descriptor_key`: `ValueError` is raised.
579- A schema-bound `execute` has no canonical IR: descriptor loading/registration fails before a callback context is created.605- A schema-bound `execute` has no canonical IR: descriptor loading/registration fails before a callback context is created.
580- `get_execute_ctx()` is called outside a schema-bound callback: `RuntimeError` is raised.606- `get_execute_ctx()` is called outside a schema-bound callback: `RuntimeError` is raised.
607+- `get_compile_ctx()` or `get_compile_platform_info()` is called outside a `compile` callback: `RuntimeError` is raised.
581- `get_declare_launch_args_ctx()` is called outside its callback: `RuntimeError` is raised.608- `get_declare_launch_args_ctx()` is called outside its callback: `RuntimeError` is raised.
582- Accessing a borrowed view after expiration: `RuntimeError` is raised.609- Accessing a borrowed view after expiration: `RuntimeError` is raised.
583 610 
@@ -616,7 +643,7 @@ The implementation follows the existing Python pass and GE runtime style:
616### 9.1 Test Boundaries643### 9.1 Test Boundaries
617 644 
618- Python API test entries: `ge.custom_op`, `ge.custom_op.proto`, `ge.custom_op._bridge`, `ge.custom_op.bootstrap`.645- Python API test entries: `ge.custom_op`, `ge.custom_op.proto`, `ge.custom_op._bridge`, `ge.custom_op.bootstrap`.
619-- Native context test entries: Eager/AnnotatedArgs borrowed contexts, `AnnotatedKernelArgs`, and launch-info methods.646+- Native context test entries: Eager/Compile/AnnotatedArgs borrowed contexts, `AnnotatedKernelArgs`, and launch-info methods.
620- C++ test entries: `CustomOpCast<T>`, `PythonCustomOpAdapter`, `AnnotatedKernelArgs`, `CustomTaskInfo`, `LoadPythonCustomOps()`, `LoadCustomOps()`/`UnloadCustomOps()`, and `ShutdownCustomOpsForProcess()`.647- C++ test entries: `CustomOpCast<T>`, `PythonCustomOpAdapter`, `AnnotatedKernelArgs`, `CustomTaskInfo`, `LoadPythonCustomOps()`, `LoadCustomOps()`/`UnloadCustomOps()`, and `ShutdownCustomOpsForProcess()`.
621- End-to-end sample entry: `examples/custom_op/annotated_args_refresh_add_custom/python/run.sh`.648- End-to-end sample entry: `examples/custom_op/annotated_args_refresh_add_custom/python/run.sh`.
622 649 
@@ -629,6 +656,7 @@ The implementation follows the existing Python pass and GE runtime style:
629| Function | Schema-bound required/optional/dynamic input and typed attribute assembly, with `get_execute_ctx()` scope | Python pytest fake context | UT |656| Function | Schema-bound required/optional/dynamic input and typed attribute assembly, with `get_execute_ctx()` scope | Python pytest fake context | UT |
630| Function | Schema-bound `execute` input/attribute signature validation during descriptor loading, output/return compatibility, and no repeated runtime validation | Python pytest fake context | UT |657| Function | Schema-bound `execute` input/attribute signature validation during descriptor loading, output/return compatibility, and no repeated runtime validation | Python pytest fake context | UT |
631| Function | Schema-bound `declare_launch_args` signature validation during descriptor loading, plus runtime input/output and attribute assembly, return validation, flattened instance indices, and builder consumption | Python pytest fake/native context | UT |658| Function | Schema-bound `declare_launch_args` signature validation during descriptor loading, plus runtime input/output and attribute assembly, return validation, flattened instance indices, and builder consumption | Python pytest fake/native context | UT |
659+| Function | Schema-bound `compile` input/output/attribute assembly, signature validation, compilation context and platform queries, return validation, and context invalidation | Python pytest fake/native context | UT |
632| Function | `get_execute_ctx()` / `get_declare_launch_args_ctx()` access in callbacks, exception cleanup, and nested invocation restoration | Python pytest | UT |660| Function | `get_execute_ctx()` / `get_declare_launch_args_ctx()` access in callbacks, exception cleanup, and nested invocation restoration | Python pytest | UT |
633| Function | Bridge descriptor retrieval, holder creation/destruction, non-callable method rejection, and context invalidation | Python pytest | UT |661| Function | Bridge descriptor retrieval, holder creation/destruction, non-callable method rejection, and context invalidation | Python pytest | UT |
634| Function | Canonical IR lookup/cache, bridge ABI v1, and adapter execute/declare forwarding | C++ gtest | UT |662| Function | Canonical IR lookup/cache, bridge ABI v1, and adapter execute/declare forwarding | C++ gtest | UT |
@@ -970,6 +970,18 @@
970 - [简介](python/ge/custom_op/AnnotatedKernelLaunchInfo/overview.md)970 - [简介](python/ge/custom_op/AnnotatedKernelLaunchInfo/overview.md)
971 - [\_\_init\_\_](python/ge/custom_op/AnnotatedKernelLaunchInfo/__init__.md)971 - [\_\_init\_\_](python/ge/custom_op/AnnotatedKernelLaunchInfo/__init__.md)
972 972 
973+ - [OpCompileContext](python/ge/custom_op/OpCompileContext/OpCompileContext.md)
974+ - [简介](python/ge/custom_op/OpCompileContext/overview.md)
975+ - [get\_option](python/ge/custom_op/OpCompileContext/get_option.md)
976+ 
977+ - [CompilePlatformInfo](python/ge/custom_op/CompilePlatformInfo/CompilePlatformInfo.md)
978+ - [简介](python/ge/custom_op/CompilePlatformInfo/overview.md)
979+ - [get\_platform\_resource](python/ge/custom_op/CompilePlatformInfo/get_platform_resource.md)
980+ - [get\_platform\_resource\_group](python/ge/custom_op/CompilePlatformInfo/get_platform_resource_group.md)
981+ - [get\_core\_num](python/ge/custom_op/CompilePlatformInfo/get_core_num.md)
982+ - [get\_soc\_version](python/ge/custom_op/CompilePlatformInfo/get_soc_version.md)
983+ - [get\_ai\_core\_num](python/ge/custom_op/CompilePlatformInfo/get_ai_core_num.md)
984+ 
973 - [DecomposePass](python/ge/passes/DecomposePass/DecomposePass.md)985 - [DecomposePass](python/ge/passes/DecomposePass/DecomposePass.md)
974 - [简介](python/ge/passes/DecomposePass/overview.md)986 - [简介](python/ge/passes/DecomposePass/overview.md)
975 - [meet\_requirements](python/ge/passes/DecomposePass/meet_requirements.md)987 - [meet\_requirements](python/ge/passes/DecomposePass/meet_requirements.md)
@@ -1144,6 +1156,8 @@
1144 - [create\_pattern](python/ge/passes/create_pattern.md)1156 - [create\_pattern](python/ge/passes/create_pattern.md)
1145 - [get\_declare\_launch\_args\_ctx](python/ge/custom_op/get_declare_launch_args_ctx.md)1157 - [get\_declare\_launch\_args\_ctx](python/ge/custom_op/get_declare_launch_args_ctx.md)
1146 - [get\_execute\_ctx](python/ge/custom_op/get_execute_ctx.md)1158 - [get\_execute\_ctx](python/ge/custom_op/get_execute_ctx.md)
1159+ - [get\_compile\_ctx](python/ge/custom_op/get_compile_ctx.md)
Sophia1213
Sophia1213Sophia121311 天前

这个地方的缩进是不是有问题?多缩了一些

likedislike
shangdf
11 天前 评论:
shangdf
11 天前 评论:
1160+ - [get\_compile\_platform\_info](python/ge/custom_op/get_compile_platform_info.md)
1147 - [get\_registered\_passes](python/ge/passes/get_registered_passes.md)1161 - [get\_registered\_passes](python/ge/passes/get_registered_passes.md)
1148 - [get\_registered\_pass\_dicts](python/ge/passes/get_registered_pass_dicts.md)1162 - [get\_registered\_pass\_dicts](python/ge/passes/get_registered_pass_dicts.md)
1149 - [get\_registered\_pass\_by\_descriptor\_key](python/ge/passes/get_registered_pass_by_descriptor_key.md)1163 - [get\_registered\_pass\_by\_descriptor\_key](python/ge/passes/get_registered_pass_by_descriptor_key.md)
@@ -1154,6 +1168,8 @@
1154 - [register\_fusion\_pass](python/ge/passes/register_fusion_pass.md)1168 - [register\_fusion\_pass](python/ge/passes/register_fusion_pass.md)
1155 - [register\_op](python/ge/custom_op/register_op.md)1169 - [register\_op](python/ge/custom_op/register_op.md)
1156 - [register\_op\_impl](python/ge/custom_op/register_op_impl.md)1170 - [register\_op\_impl](python/ge/custom_op/register_op_impl.md)
1171+ - [compile](python/ge/custom_op/compile.md)
Sophia1213
Sophia1213Sophia121311 天前

1162行的缩进也有问题,应该和1161行是并列的吧

likedislike
shangdf
11 天前 评论:
shangdf
11 天前 评论:
1172+ - [declare\_launch\_args](python/ge/custom_op/declare_launch_args.md)
1157 - [report\_fuse](python/ge/passes/report_fuse.md)1173 - [report\_fuse](python/ge/passes/report_fuse.md)
1158 1174 
1159 - [AttrValueType](python/ge/AttrValueType.md)1175 - [AttrValueType](python/ge/AttrValueType.md)
@@ -0,0 +1,25 @@
1+# CompilePlatformInfo
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 功能说明
8+ 
9+编译期平台信息只读视图。
10+ 
11+## 函数原型
12+ 
13+
14+ 
15+## 参数说明
16+ 
17+
18+ 
19+## 约束说明
20+ 
21+只能在当前同步`compile`回调内使用。
22+ 
23+## 调用示例
24+ 
25+
@@ -0,0 +1,29 @@
1+# get\_ai\_core\_num
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 功能说明
8+ 
9+查询当前编译平台的AI Core数量。
10+ 
11+## 函数原型
Sophia1213
Sophia1213Sophia121311 天前

不符合API的模版规范,缺少参数说明,约束说明,调用示例,如果无,则就在下面写无,但是不能没有 下面所有的API还要补充产品支持情况

likedislike
shangdf
11 天前 评论:
shangdf
11 天前 评论:
12+ 
13+```python
14+get_ai_core_num() -> int
15+```
16+ 
17+## 参数说明
18+ 
19+
20+ 
21+## 约束说明
22+ 
23+只能在当前同步`compile`回调内调用。
24+ 
25+## 调用示例
26+ 
27+```python
28+core_num = platform.get_ai_core_num()
29+```
@@ -0,0 +1,31 @@
1+# get\_core\_num
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 功能说明
8+ 
9+查询平台核数。
10+ 
11+## 函数原型
12+ 
13+```python
14+get_core_num(core_type: str | None = None) -> int
15+```
16+ 
17+## 参数说明
18+ 
19+| 参数名 | 输入/输出 | 描述 |
20+| :--- | :--- | :--- |
21+| core_type | 输入 | 可选的非空核类型名称。为`None`时查询平台默认核数。 |
22+ 
23+## 约束说明
24+ 
25+只能在当前同步`compile`回调内调用。
26+ 
27+## 调用示例
28+ 
29+```python
30+core_num = platform.get_core_num()
31+```
@@ -0,0 +1,32 @@
1+# get\_platform\_resource
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 功能说明
8+ 
9+查询平台资源组中的单个字段。
10+ 
11+## 函数原型
12+ 
13+```python
14+get_platform_resource(group: str, key: str) -> str
15+```
16+ 
17+## 参数说明
18+ 
19+| 参数名 | 输入/输出 | 描述 |
20+| :--- | :--- | :--- |
21+| group | 输入 | 非空平台资源组名称。 |
22+| key | 输入 | 非空字段名称。 |
23+ 
24+## 约束说明
25+ 
26+只能在当前同步`compile`回调内调用。
27+ 
28+## 调用示例
29+ 
30+```python
31+resource = platform.get_platform_resource("ai_core", "count")
32+```
@@ -0,0 +1,31 @@
1+# get\_platform\_resource\_group
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 功能说明
8+ 
9+查询一个平台资源组。
10+ 
11+## 函数原型
12+ 
13+```python
14+get_platform_resource_group(group: str) -> dict[str, str]
15+```
16+ 
17+## 参数说明
18+ 
19+| 参数名 | 输入/输出 | 描述 |
20+| :--- | :--- | :--- |
21+| group | 输入 | 非空平台资源组名称。 |
22+ 
23+## 约束说明
24+ 
25+只能在当前同步`compile`回调内调用。
26+ 
27+## 调用示例
28+ 
29+```python
30+resources = platform.get_platform_resource_group("ai_core")
31+```
@@ -0,0 +1,29 @@
1+# get\_soc\_version
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 功能说明
8+ 
9+查询当前编译平台的SoC版本。
10+ 
11+## 函数原型
12+ 
13+```python
14+get_soc_version() -> str
15+```
16+ 
17+## 参数说明
18+ 
19+
20+ 
21+## 约束说明
22+ 
23+只能在当前同步`compile`回调内调用。
24+ 
25+## 调用示例
26+ 
27+```python
28+soc_version = platform.get_soc_version()
29+```
@@ -0,0 +1,27 @@
1+# 简介
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 功能说明
8+ 
9+`CompilePlatformInfo`是当前schema-bound`compile`回调的平台信息只读视图,由`get_compile_platform_info()`返回,用户不能直接构造。
10+ 
11+该对象仅在当前`compile`回调内有效;回调返回或抛出异常后,再调用任何方法都会抛出`RuntimeError`
12+ 
13+## 函数原型
14+ 
15+
16+ 
17+## 参数说明
18+ 
19+
20+ 
21+## 约束说明
22+ 
23+该对象只能在当前同步`compile`回调内使用。
24+ 
25+## 调用示例
26+ 
27+
@@ -0,0 +1,25 @@
1+# OpCompileContext
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 功能说明
8+ 
9+图编译上下文只读视图。
10+ 
11+## 函数原型
12+ 
13+
14+ 
15+## 参数说明
16+ 
17+
18+ 
19+## 约束说明
20+ 
21+只能在当前同步`compile`回调内使用。
22+ 
23+## 调用示例
24+ 
25+
@@ -0,0 +1,31 @@
1+# get\_option
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 功能说明
8+ 
9+查询当前图编译上下文中的option。
10+ 
11+## 函数原型
12+ 
13+```python
14+get_option(option_key: str) -> str
15+```
16+ 
17+## 参数说明
18+ 
19+| 参数名 | 输入/输出 | 描述 |
20+| :--- | :--- | :--- |
21+| option_key | 输入 | 非空option名称。 |
22+ 
23+## 约束说明
24+ 
25+只能在当前同步`compile`回调内调用。
26+ 
27+## 调用示例
28+ 
29+```python
30+option = ctx.get_option("custom.compile.option")
31+```
@@ -0,0 +1,29 @@
1+# 简介
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 功能说明
8+ 
9+`OpCompileContext`是当前schema-bound`compile`回调的只读编译上下文,由[get_compile_ctx](../get_compile_ctx.md)返回,用户不能直接构造。
10+ 
11+该对象提供编译option查询。它是仅在当前回调内有效的借用视图;回调返回或抛出异常后,再调用任何方法都会抛出`RuntimeError`。平台资源、核数和SoC信息请通过[get_compile_platform_info()](../get_compile_platform_info.md)获取。
12+ 
13+`OpCompileContext`只查询编译环境,不保存用户的编译结果。用户可以在实现实例中暂存结果,但GE不会序列化、恢复或回滚这些Python状态。
14+ 
15+## 函数原型
16+ 
17+
18+ 
19+## 参数说明
20+ 
21+
22+ 
23+## 约束说明
24+ 
25+该对象只能在当前同步`compile`回调内使用。
26+ 
27+## 调用示例
28+ 
29+
@@ -0,0 +1,63 @@
1+# compile
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 功能说明
8+ 
9+Python自定义算子的图编译期回调。该回调依赖GE注册的Ascend IR算子原型;原生算子通过GE的`REG_OP`注册,Python算子原型由bridge同步注册。算子原型定义输入、输出和属性的名称、顺序、类型及输入输出类别。将实现类通过[register_op_impl](register_op_impl.md)注册并提供可调用的`compile`方法后,GE在图编译阶段调用该方法。回调可以读取schema参数,并通过[get_compile_ctx](get_compile_ctx.md)和[get_compile_platform_info](get_compile_platform_info.md)查询编译环境及平台信息。
10+ 
11+`compile`回调只用于图编译,不在模型加载或模型执行阶段调用。
12+ 
13+## 函数原型
14+ 
15+```python
16+def compile(self, input_0, ..., output_0, ..., *, attr_0, ...) -> None
17+```
18+ 
19+上面的参数名仅表示参数位置。实际参数数量和属性名称由算子的Ascend IR算子原型决定。
20+ 
21+## 参数说明
22+ 
23+GE按Ascend IR算子原型绑定参数,顺序如下:
24+ 
25+| 参数 | 绑定规则 |
26+| :--- | :--- |
27+| 输入参数 | 位于参数列表前部。required input传入`Tensor`,optional input传入`Optional[Tensor]`,dynamic input传入`List[Tensor]`。 |
28+| 输出参数 | 位于所有输入参数之后。required output传入`Tensor`,dynamic output传入`List[Tensor]`。 |
29+| 属性参数 | 位于所有输入、输出参数之后,必须使用keyword-only参数;参数名称、顺序和类型与Ascend IR算子原型一致。 |
30+ 
31+参数类型由Ascend IR算子原型决定。参数提供类型注解时,注解必须与对应的输入、输出或属性类型一致。
32+ 
33+## 约束说明
34+ 
35+- `compile`只能以schema-bound形式使用。算子必须存在Ascend IR算子原型;否则在校验或调用时抛出`RuntimeError`
36+- 回调不得声明可变位置参数或可变关键字参数。输入、输出和属性的数量、顺序或属性名称不匹配时,抛出`TypeError`
37+- 回调返回值必须为`None`,并且必须声明`-> None`返回注解。
38+- `get_compile_ctx()``get_compile_platform_info()`只能在当前同步`compile`回调内调用。返回的上下文、平台信息、输入输出`Tensor`及Tensor属性视图均为借用对象,回调返回或抛出异常后失效。
39+- 编译上下文中的字符串、整数和`dict`等查询结果为Python值副本,可以在回调结束后继续使用。
40+- `compile`可以与同一实现类上的`execute``declare_launch_args`能力组合注册;各回调的上下文和生命周期相互独立。
41+ 
42+## 调用示例
43+ 
44+```python
45+from ge.custom_op import (
46+ get_compile_ctx,
47+ get_compile_platform_info,
48+ register_op_impl,
49+)
50+from ge.runtime import Tensor
51+ 
52+ 
53+@register_op_impl(op_type="AddCustom")
54+class AddCustom:
55+ def compile(self, x: Tensor, y: Tensor, z: Tensor, *, alpha: int) -> None:
56+ compile_ctx = get_compile_ctx()
57+ platform = get_compile_platform_info()
58+ option = compile_ctx.get_option("custom.compile.option")
59+ soc_version = platform.get_soc_version()
60+ core_num = platform.get_ai_core_num()
61+ # 根据输入、输出、属性及编译环境完成自定义编译逻辑。
62+ _ = (x, y, z, alpha, option, soc_version, core_num)
63+```
@@ -0,0 +1,71 @@
1+# declare\_launch\_args
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 功能说明
8+ 
9+Python自定义算子的声明式kernel启动参数回调。将实现类通过[register_op_impl](register_op_impl.md)注册并提供可调用的`declare_launch_args`方法后,GE根据Ascend IR算子原型组装输入、输出和属性参数,并在编译阶段调用该方法。回调通过[get_declare_launch_args_ctx](get_declare_launch_args_ctx.md)创建`AnnotatedKernelArgs`、申请workspace,并提交`AnnotatedKernelLaunchInfo`
10+ 
11+## 函数原型
12+ 
13+```python
14+def declare_launch_args(self, input_0, ..., output_0, ..., *, attr_0, ...) -> None
15+```
16+ 
17+上面的参数名仅表示参数位置。实际参数数量和属性名称由算子的Ascend IR算子原型决定。
18+ 
19+## 参数说明
20+ 
21+GE按Ascend IR算子原型绑定参数,顺序如下:
22+ 
23+| 参数 | 绑定规则 |
24+| :--- | :--- |
25+| 输入参数 | 位于参数列表前部。required input传入`Tensor`,optional input传入`Optional[Tensor]`,dynamic input传入`List[Tensor]`。 |
26+| 输出参数 | 位于所有输入参数之后。required output传入`Tensor`,dynamic output传入`List[Tensor]`。 |
27+| 属性参数 | 位于所有输入、输出参数之后,必须使用keyword-only参数;参数名称、顺序和类型与Ascend IR算子原型一致。 |
28+ 
29+参数类型由Ascend IR算子原型决定。参数提供类型注解时,注解必须与对应的输入、输出或属性类型一致。
30+ 
31+## 约束说明
32+ 
33+- `declare_launch_args`只能以schema-bound形式使用。算子必须存在Ascend IR算子原型;否则在校验或调用时抛出`RuntimeError`
34+- 回调不得声明可变位置参数或可变关键字参数。输入、输出和属性的数量、顺序或属性名称不匹配时,抛出`TypeError`
35+- 回调返回值必须为`None`,并且必须声明`-> None`返回注解。
36+- `get_declare_launch_args_ctx()`只能在当前同步`declare_launch_args`回调内调用。返回的`AnnotatedArgsContext``AnnotatedKernelArgs``WorkspaceAddr`及其派生的借用对象只能在当前回调内使用,回调返回或抛出异常后失效。
37+- `AnnotatedKernelArgs.append_input``append_output``instance_index`使用当前计算节点输入、输出的实例平铺索引;动态输入或输出展开后的实例使用连续索引。
38+- 调用`AnnotatedArgsContext.add_launch`后,传入的`AnnotatedKernelArgs`会被消费,不能再次使用。
39+ 
40+## 调用示例
41+ 
42+```python
43+from ge.custom_op import (
44+ AnnotatedKernelLaunchInfo,
45+ get_declare_launch_args_ctx,
46+ register_op_impl,
47+)
48+from ge.runtime import Tensor
49+ 
50+ 
51+kernel_bin = b"..."
52+ 
53+ 
54+@register_op_impl(op_type="AnnotatedAddCustom")
55+class AnnotatedAddCustom:
56+ def declare_launch_args(self, x1: Tensor, x2: Tensor, y: Tensor) -> None:
57+ ctx = get_declare_launch_args_ctx()
58+ args = ctx.create_kernel_args()
59+ args.append_input(0, x1)
60+ args.append_input(1, x2)
61+ args.append_output(0, y)
62+ ctx.add_launch(
63+ AnnotatedKernelLaunchInfo(
64+ kernel_name="add_custom",
65+ kernel_bin=kernel_bin,
66+ block_dim=8,
67+ stream_id=ctx.get_stream_id(),
68+ ),
69+ args,
70+ )
71+```
@@ -0,0 +1,37 @@
1+# get\_compile\_ctx
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 功能说明
8+ 
9+获取当前[compile](compile.md)回调的只读编译上下文。
10+ 
11+## 函数原型
12+ 
13+```python
14+get_compile_ctx() -> OpCompileContext
15+```
16+ 
17+## 参数说明
18+ 
19+
20+ 
21+## 约束说明
22+ 
23+- 只能在当前同步`compile`回调内调用;回调外调用时抛出`RuntimeError`
24+- 返回对象及由schema参数取得的`Tensor`、Tensor属性均为借用视图,回调返回或抛出异常后失效。
25+- 字符串、整数和`dict`等查询结果是Python值副本,可以在回调结束后继续使用。
26+- 该接口只用于图编译阶段,不在模型加载或执行阶段调用。
27+ 
28+## 调用示例
29+ 
30+```python
31+from ge.custom_op import get_compile_ctx
32+ 
33+ 
34+def compile(self, x, y, *, alpha: int) -> None:
35+ ctx = get_compile_ctx()
36+ option = ctx.get_option("custom.compile.option")
37+```
@@ -0,0 +1,35 @@
1+# get\_compile\_platform\_info
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 功能说明
8+ 
9+获取当前schema-bound`compile`回调的平台信息只读视图。
10+ 
11+## 函数原型
12+ 
13+```python
14+get_compile_platform_info() -> CompilePlatformInfo
15+```
16+ 
17+## 参数说明
18+ 
19+
20+ 
21+## 约束说明
22+ 
23+- 只能在当前同步`compile`回调内调用;回调外调用时抛出`RuntimeError`
24+- 返回对象在回调返回或抛出异常后失效。
25+ 
26+## 调用示例
27+ 
28+```python
29+from ge.custom_op import get_compile_platform_info
30+ 
31+ 
32+def compile(self, x, y) -> None:
33+ platform = get_compile_platform_info()
34+ soc_version = platform.get_soc_version()
35+```
@@ -6,7 +6,7 @@
6 6 
7## 功能说明7## 功能说明
8 8 
9-获取当前`declare_launch_args`回调的声明式参数上下文。通过返回的`AnnotatedArgsContext`可以申请逻辑workspace、获取stream标识、创建`AnnotatedKernelArgs`,并提交`AnnotatedKernelLaunchInfo`和kernel参数。9+获取当前[declare_launch_args](declare_launch_args.md)回调的声明式参数上下文。通过返回的`AnnotatedArgsContext`可以申请逻辑workspace、获取stream标识、创建`AnnotatedKernelArgs`,并提交`AnnotatedKernelLaunchInfo`和kernel参数。
10 10 
11## 函数原型11## 函数原型
12 12 
@@ -18,27 +18,20 @@ get_declare_launch_args_ctx() -> AnnotatedArgsContext
18 18 
1919
20 20 
21-## 返回值说明
22- 
23-| 类型 | 说明 |
24-| :--- | :--- |
25-| AnnotatedArgsContext | 当前`declare_launch_args`回调的借用上下文。 |
26- 
27-## 调用示例
28- 
29-```python
30-from ge.custom_op import get_declare_launch_args_ctx
31-from ge.runtime import Tensor
32- 
33- 
34-def declare_launch_args(self, x1: Tensor, x2: Tensor, y: Tensor) -> None:
35- ctx = get_declare_launch_args_ctx()
36- args = ctx.create_kernel_args()
37-```
38- 
39## 约束说明21## 约束说明
40 22 
41- 此接口只能在当前`declare_launch_args`回调内调用。回调外调用抛出`RuntimeError`23- 此接口只能在当前`declare_launch_args`回调内调用。回调外调用抛出`RuntimeError`
42- `AnnotatedArgsContext`是借用对象,仅在当前回调内有效。由其创建的`AnnotatedKernelArgs`和申请得到的`WorkspaceAddr`也只能在当前回调内使用。24- `AnnotatedArgsContext`是借用对象,仅在当前回调内有效。由其创建的`AnnotatedKernelArgs`和申请得到的`WorkspaceAddr`也只能在当前回调内使用。
43- `AnnotatedKernelLaunchInfo`保存kernel名称、二进制、block数和stream标识。调用`AnnotatedArgsContext.add_launch`时会消费传入的`AnnotatedKernelArgs`25- `AnnotatedKernelLaunchInfo`保存kernel名称、二进制、block数和stream标识。调用`AnnotatedArgsContext.add_launch`时会消费传入的`AnnotatedKernelArgs`
44- `AnnotatedKernelArgs.append_input``append_output``instance_index`分别使用当前计算节点输入、输出的实例平铺索引。26- `AnnotatedKernelArgs.append_input``append_output``instance_index`分别使用当前计算节点输入、输出的实例平铺索引。
27+ 
28+## 调用示例
29+ 
30+```python
31+from ge.custom_op import get_declare_launch_args_ctx
32+ 
33+ 
34+def declare_launch_args(self, x1, x2, y) -> None:
35+ ctx = get_declare_launch_args_ctx()
36+ args = ctx.create_kernel_args()
37+```
@@ -6,7 +6,7 @@
6 6 
7## 功能说明7## 功能说明
8 8 
9-注册Python自定义算子实现类。实现的可调用`declare_launch_args`方法会注册为`annotated_args`能力。GE在静态图编译阶段调用该能力,由实现类声明kernel launch9+注册Python自定义算子实现类。装饰器反射的可调用方法:`execute`、`compile`、`declare_launch_args`分别声明`eager_execute`、`compilable`、`annotated_args`能力。三种能力可以组合使用具体回调约束见[compile](compile.md)和[declare_launch_args](declare_launch_args.md)
10 10 
11## 函数原型11## 函数原型
12 12 
@@ -20,49 +20,23 @@ register_op_impl(*, op_type: str) -> callable
20| :--- | :--- | :--- |20| :--- | :--- | :--- |
21| op_type | 输入 | 自定义算子类型。必须是非空字符串,且在实现注册表中唯一。 |21| op_type | 输入 | 自定义算子类型。必须是非空字符串,且在实现注册表中唯一。 |
22 22 
23-## 返回值说明23+## 约束说明
24 24 
25-|型 | 说明 |25+- 被装饰对象必须是具体,并且至少实现一个受支持的可调用能力方法。当前受支持的方法为`execute`、`compile`和`declare_launch_args`。
26-| :--- | :--- |26+- `op_type`不合法、被装饰对象不是具体类,或实现类未提供受支持的可调用能力方法时,抛出`TypeError`。`op_type`重复注册发生冲突时,抛出`ValueError`。
27-| callable | 返回类装饰器。装饰器注册实现类后返回该类并设置`__ge_op_impl_descriptor__`属性。 |27+- 注册阶段只收集实现类的能力,不校验`declare_launch_args`的业务参数签名。在可获得Ascend IR算子原型的实现描述符校验阶段,`declare_launch_args`的参数按Ascend IR算子原型中输入、输出、仅限关键字属性的顺序绑定
28+- `compile`只支持schema-bound形式:参数按Ascend IR算子原型的输入、输出顺序绑定,属性使用名称、顺序与Ascend IR算子原型一致的keyword-only参数,返回注解和返回值均必须为`None`。它在图编译阶段调用;回调中通过[get_compile_ctx](get_compile_ctx.md)查询编译环境。
29+- `declare_launch_args`的必选输入和必选输出参数类型为`Tensor`,可选输入参数类型为`Optional[Tensor]`,动态输入和动态输出参数类型为`List[Tensor]`。属性参数必须为仅限关键字参数,并与Ascend IR算子原型中的属性名称和类型一致。
30+- `declare_launch_args`的返回注解和返回值均必须为`None`。签名或返回值不符合要求时,抛出`TypeError`
28 31 
29## 调用示例32## 调用示例
30 33 
31```python34```python
32-from ge.custom_op import (35+from ge.custom_op import register_op_impl
33- AnnotatedKernelLaunchInfo,
34- get_declare_launch_args_ctx,
35- register_op_impl,
36-)
37-from ge.runtime import Tensor
38 36 
39 37 
40-kernel_bin = b"..."38+@register_op_impl(op_type="AddCustom")
41- 39+class AddCustom:
42- 40+ def execute(self, x, y):
43-@register_op_impl(op_type="AnnotatedAddCustom")41+ return x + y
44-class AnnotatedAddCustom:
45- def declare_launch_args(self, x1: Tensor, x2: Tensor, y: Tensor) -> None:
46- ctx = get_declare_launch_args_ctx()
47- args = ctx.create_kernel_args()
48- args.append_input(0, x1)
49- args.append_input(1, x2)
50- args.append_output(0, y)
51- ctx.add_launch(
52- AnnotatedKernelLaunchInfo(
53- kernel_name="add_custom",
54- kernel_bin=kernel_bin,
55- block_dim=8,
56- stream_id=ctx.get_stream_id(),
57- ),
58- args,
59- )
60```42```
61- 
62-## 约束说明
63- 
64-- 被装饰对象必须是具体类,并且至少实现一个受支持的可调用能力方法。当前受支持的方法为`execute``declare_launch_args`;后者映射为`annotated_args`能力。
65-- `op_type`不合法、被装饰对象不是具体类,或实现类未提供受支持的可调用能力方法时,抛出`TypeError``op_type`重复注册发生冲突时,抛出`ValueError`
66-- 注册阶段只收集实现类的能力,不校验`declare_launch_args`的业务参数签名。在可获得canonical IR后的实现描述符校验阶段,`declare_launch_args`的参数按IR中输入、输出、仅限关键字属性的顺序绑定。
67-- `declare_launch_args`的必选输入和必选输出参数类型为`Tensor`,可选输入参数类型为`Optional[Tensor]`,动态输入和动态输出参数类型为`List[Tensor]`。属性参数必须为仅限关键字参数,并与canonical IR中的属性名称和类型一致。
68-- `declare_launch_args`的返回注解和返回值均必须为`None`。签名或返回值不符合要求时,抛出`TypeError`
@@ -732,7 +732,7 @@ custom_op/
732├── proto.py # Python 自定义算子原型解析、描述符和注册中心732├── proto.py # Python 自定义算子原型解析、描述符和注册中心
733├── registry.py # Python 自定义算子实现注册中心与装饰器733├── registry.py # Python 自定义算子实现注册中心与装饰器
734├── bootstrap.py # 插件发现与加载734├── bootstrap.py # 插件发现与加载
735-├── context.py # schema-bound execute 的当前执行上下文绑定735+├── context.py # schema-bound execute/compile 的当前执行上下文绑定
736├── _bridge.py # Bridge 运行时辅助(实例管理,供 C++ bridge .so 回调)736├── _bridge.py # Bridge 运行时辅助(实例管理,供 C++ bridge .so 回调)
737├── _native.py # native module 装载与 re-export737├── _native.py # native module 装载与 re-export
738├── _artifact_utils.py # 运行时 artifact 选择辅助738├── _artifact_utils.py # 运行时 artifact 选择辅助
@@ -740,11 +740,11 @@ custom_op/
740└── native_bindings/ # _ge_custom_op_native.so 的 pybind11 绑定实现740└── native_bindings/ # _ge_custom_op_native.so 的 pybind11 绑定实现
741```741```
742注:下划线开头的为 Python 风格下的对内模块。742注:下划线开头的为 Python 风格下的对内模块。
743-注:`EagerOpExecutionContext`、`AnnotatedArgsContext` 和 `InferShapeContext` 由 `_ge_custom_op_native.so` 提供 native-backed 实现;执行期或 `infer_meta` 回调中返回、接收的 `Tensor`、`TensorDesc`、`StorageShape`、`StorageFormat`、`Shape`、`TensorPlacement` 等运行时数据结构由 `ge.runtime` 模块提供。743+注:`EagerOpExecutionContext`、`OpCompileContext`、`CompilePlatformInfo`、`AnnotatedArgsContext` 和 `InferShapeContext` 由 `_ge_custom_op_native.so` 提供 native-backed 实现;执行期、编译期或 `infer_meta` 回调中返回、接收的 `Tensor`、`TensorDesc`、`StorageShape`、`StorageFormat`、`Shape`、`TensorPlacement` 等运行时数据结构由 `ge.runtime` 模块提供。
744 744 
745#### 模块定位745#### 模块定位
746 746 
747-Python 自定义算子的长期目标是支持用户使用 Python 描述自定义算子原型,并实现自定义算子的各类能力。当前通过反射实现类上的可调用 `execute` 和 `declare_launch_args` 方法,分别识别执行能力和静态图声明式地址刷新能力,不要求用户类继承任何能力基类。执行入口统一按照 canonical IR 输入、属性顺序绑定,执行上下文通过 `get_execute_ctx()` 在回调内访问。Python 原型通过 `register_op` 注册到 `OperatorFactory` 后,编译期和 RT2 动态 Shape 路径会调用同一 Python `infer_meta` 回调:编译期回写输出 shape、dtype 和 origin dtype,RT2 运行期只回写输出 shape。747+Python 自定义算子的长期目标是支持用户使用 Python 描述自定义算子原型,并实现自定义算子的各类能力。当前通过反射实现类上的可调用 `execute`、`compile` 和 `declare_launch_args` 方法,分别识别执行能力、图编译能力和静态图声明式地址刷新能力,不要求用户类继承任何能力基类。执行入口统一按照 canonical IR 输入、属性顺序绑定,执行上下文通过 `get_execute_ctx()` 在回调内访问;`compile` 和 `declare_launch_args` 按 canonical IR 绑定输入、输出和属性。Python 原型通过 `register_op` 注册到 `OperatorFactory` 后,编译期和 RT2 动态 Shape 路径会调用同一 Python `infer_meta` 回调:编译期回写输出 shape、dtype 和 origin dtype,RT2 运行期只回写输出 shape。
748 748 
749#### 运行时 native artifact 选择749#### 运行时 native artifact 选择
750 750 
@@ -829,7 +829,15 @@ class AnnotatedAddCustom:
829 829 
830同一 AnnotatedArgs task-plan 生命周期只调用一次 `declare_launch_args`,并缓存本次声明形成的 task plan;后续生成阶段只根据当前 `RunContext` 物化缓存的 task plan,不再回调 Python。新的 task-plan 生命周期会重新调用声明方法。每次回调中的 borrowed object 只能在该次回调内使用,不得跨回调复用。编译期把最终选择的刷新方式保存到 `_custom_task_args_mode`,模型加载时以该属性为第一事实来源;没有该属性的旧 OM 保留 registry 查询和 `args_format` 兼容兜底。模型执行路径不调用 Python。830同一 AnnotatedArgs task-plan 生命周期只调用一次 `declare_launch_args`,并缓存本次声明形成的 task plan;后续生成阶段只根据当前 `RunContext` 物化缓存的 task plan,不再回调 Python。新的 task-plan 生命周期会重新调用声明方法。每次回调中的 borrowed object 只能在该次回调内使用,不得跨回调复用。编译期把最终选择的刷新方式保存到 `_custom_task_args_mode`,模型加载时以该属性为第一事实来源;没有该属性的旧 OM 保留 registry 查询和 `args_format` 兼容兜底。模型执行路径不调用 Python。
831 831 
832-##### 5. OpImplDescriptor 数据类832+##### 5. Python Compile 与编译上下文
833+ 
834+**文件位置**: `context.py``_native.py``_ge_custom_op_native.pyi`
835+ 
836+`compile`是图编译期回调,只支持schema-bound形式。GE根据Ascend IR算子原型将输入、输出作为位置参数,将属性作为keyword-only参数传入;返回注解和返回值都必须为`None`。回调通过`get_compile_ctx()`查询编译option,通过`get_compile_platform_info()`查询平台资源、核数和SoC信息。
837+ 
838+`OpCompileContext``CompilePlatformInfo`、输入输出`Tensor`和属性视图都是当前回调内有效的借用对象,回调返回或抛出异常后失效。编译回调不在模型加载和执行阶段调用,Python实现、实例状态和kernel binary不写入OM。
839+ 
840+##### 6. OpImplDescriptor 数据类
833 841 
834**文件位置**: `registry.py`842**文件位置**: `registry.py`
835 843 
@@ -841,14 +849,14 @@ class AnnotatedAddCustom:
841- `op_type` - 自定义算子类型849- `op_type` - 自定义算子类型
842- `module_name` - 所属模块名850- `module_name` - 所属模块名
843- `class_name` - 类名851- `class_name` - 类名
844-- `interfaces` - 能力接口列表,可包含 `"eager_execute"` 和 `"annotated_args"`852+- `interfaces` - 能力接口列表,可包含 `"eager_execute"`、`"compilable"` 和 `"annotated_args"`
845- `cls` - Python 实现类引用853- `cls` - Python 实现类引用
846 854 
847#### 注册与发现855#### 注册与发现
848 856 
849**装饰器**:857**装饰器**:
850- `register_op(op_type, mutates_args=())` - 根据被装饰函数的类型标注声明并收集 Python 自定义算子原型;被装饰函数同时作为 `infer_meta` 回调,返回输出 `TensorDesc`858- `register_op(op_type, mutates_args=())` - 根据被装饰函数的类型标注声明并收集 Python 自定义算子原型;被装饰函数同时作为 `infer_meta` 回调,返回输出 `TensorDesc`
851-- `register_op_impl(op_type)` - 注册 Python 实现类,并反射其可调用方法生成能力列表;`execute` 对应 `eager_execute`,`declare_launch_args` 对应 `annotated_args`859+- `register_op_impl(op_type)` - 注册 Python 实现类,并反射其可调用方法生成能力列表;`execute` 对应 `eager_execute`,`compile` 对应 `compilable`,`declare_launch_args` 对应 `annotated_args`
852 860 
853**发现机制**:861**发现机制**:
854 862 
@@ -17,6 +17,7 @@ Python 自定义算子的完整定位是支持用户用 Python 描述自定义
17- C++ runtime 通过 `PythonCustomOpAdapter` 接入现有 `CustomOpFactory` / `CustomOpRegistry`17- C++ runtime 通过 `PythonCustomOpAdapter` 接入现有 `CustomOpFactory` / `CustomOpRegistry`
18- Python native module `_ge_custom_op_native` 提供 `EagerOpExecutionContext``RuntimeAttrs` borrowed view。18- Python native module `_ge_custom_op_native` 提供 `EagerOpExecutionContext``RuntimeAttrs` borrowed view。
19- Python 用户通过 `declare_launch_args` 实现 `AnnotatedArgsOp` 编译期回调,使用 `AnnotatedArgsContext``AnnotatedKernelArgs``AnnotatedKernelLaunchInfo` 声明 kernel 启动参数。19- Python 用户通过 `declare_launch_args` 实现 `AnnotatedArgsOp` 编译期回调,使用 `AnnotatedArgsContext``AnnotatedKernelArgs``AnnotatedKernelLaunchInfo` 声明 kernel 启动参数。
20+- Python 用户通过 schema-bound `compile` 实现图编译期回调,并通过 `get_compile_ctx()``get_compile_platform_info()` 查询编译上下文及平台信息。
20- `ge.runtime` 提供 context 返回或入参所需的 `Tensor``StorageShape``StorageFormat``Shape``TensorPlacement` 等运行时数据结构。21- `ge.runtime` 提供 context 返回或入参所需的 `Tensor``StorageShape``StorageFormat``Shape``TensorPlacement` 等运行时数据结构。
21 22 
22V2 在 V1 执行能力的基础上扩展 Python 原型和 Meta 推导能力。当前阶段已经实现 Python 原型 creator、Adapter 注册事务和所有权管理,并打通编译期与 RT2 动态 Shape 的 Python `infer_meta` 调用链。23V2 在 V1 执行能力的基础上扩展 Python 原型和 Meta 推导能力。当前阶段已经实现 Python 原型 creator、Adapter 注册事务和所有权管理,并打通编译期与 RT2 动态 Shape 的 Python `infer_meta` 调用链。
@@ -37,7 +38,7 @@ V2 完成后仍不覆盖以下内容:
37- 不向 Python 直接暴露 `ShapeInferOp``CompilableOp``PortableOp``ArgsUpdater` 等 C++ `BaseCustomOp` 能力接口;V2 的 Meta 推导通过 `infer_meta` 回调提供。38- 不向 Python 直接暴露 `ShapeInferOp``CompilableOp``PortableOp``ArgsUpdater` 等 C++ `BaseCustomOp` 能力接口;V2 的 Meta 推导通过 `infer_meta` 回调提供。
38- 读取输入 Tensor 数据的 data-dependent infer。39- 读取输入 Tensor 数据的 data-dependent infer。
39- InferShapeRange、format、符号化推导和 shape rule 生成。40- InferShapeRange、format、符号化推导和 shape rule 生成。
40-- Python `compile`、`serialize`、`deserialize` 参数绑定,以及 ES API 自动生成。41+- Python `serialize`、`deserialize` 参数绑定,以及 ES API 自动生成。
41- 对 schema-bound `execute` 做新的功能扩展。42- 对 schema-bound `execute` 做新的功能扩展。
42- Python 自定义算子随 OM 序列化、反序列化和跨进程加载。43- Python 自定义算子随 OM 序列化、反序列化和跨进程加载。
43- Python 侧 `KernelArgs` / `MallocReadOnlyDevArgs` 对外封装。44- Python 侧 `KernelArgs` / `MallocReadOnlyDevArgs` 对外封装。
@@ -67,10 +68,10 @@ Python custom op 是 GE Python 体系的一部分,与 Python pass 共享以下
67|------|------|------|68|------|------|------|
68| Python API | `api/python/ge/ge/custom_op/` | 实现方法反射、注册实现、插件发现、bridge helper |69| Python API | `api/python/ge/ge/custom_op/` | 实现方法反射、注册实现、插件发现、bridge helper |
69| Runtime types | `api/python/ge/ge/runtime/` | `Tensor``StorageShape``StorageFormat` 等运行时数据结构 |70| Runtime types | `api/python/ge/ge/runtime/` | `Tensor``StorageShape``StorageFormat` 等运行时数据结构 |
70-| Native context | `api/python/ge/ge/custom_op/native_bindings/` | `_ge_custom_op_native`,绑定 Eager、AnnotatedArgs、InferShape context、参数 builder 及 `RuntimeAttrs` |71+| Native context | `api/python/ge/ge/custom_op/native_bindings/` | `_ge_custom_op_native`,绑定 Eager、Compile、AnnotatedArgs、InferShape context、参数 builder 及 `RuntimeAttrs` |
71| Runtime loader | `runtime/custom_op/custom_op_loader.cc` | 统一加载 C++ custom op 和 Python custom op |72| 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 |73| 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` |74+| Pybind bridge | `runtime/custom_op/python_custom_op_pybind_bridge.cc` | 导入 Python bridge 模块、创建 holder、回调 `execute` / `compile` / `declare_launch_args` |
74| Proto runtime | `runtime/custom_op/python_custom_op_proto.*` | 深拷贝 C POD 原型并注册 `OperatorFactory` creator |75| Proto runtime | `runtime/custom_op/python_custom_op_proto.*` | 深拷贝 C POD 原型并注册 `OperatorFactory` creator |
75| Adapter | `runtime/custom_op/python_custom_op_adapter.*` | 作为 C++ `BaseCustomOp` 实例接入现有运行时 |76| Adapter | `runtime/custom_op/python_custom_op_adapter.*` | 作为 C++ `BaseCustomOp` 实例接入现有运行时 |
76| Capability helper | `inc/graph_metadef/graph/custom_op/` | `CustomOpCapability``CustomOpCast<T>` |77| Capability helper | `inc/graph_metadef/graph/custom_op/` | `CustomOpCapability``CustomOpCast<T>` |
@@ -82,10 +83,11 @@ V1 功能包括:
82- `@register_op_impl(op_type=...)` 注册 Python 自定义算子实现。83- `@register_op_impl(op_type=...)` 注册 Python 自定义算子实现。
83- `@register_op(op_type=..., mutates_args=...)` 根据 Python 函数签名收集自定义算子原型。84- `@register_op(op_type=..., mutates_args=...)` 根据 Python 函数签名收集自定义算子原型。
84- bridge 将 Python 原型同步注册为 `OperatorFactory` creator,并从生效 creator 收集 canonical IR;编译期和 RT2 通过统一 callback 调用 `infer_meta`85- bridge 将 Python 原型同步注册为 `OperatorFactory` creator,并从生效 creator 收集 canonical IR;编译期和 RT2 通过统一 callback 调用 `infer_meta`
85-- `register_op_impl` 反射实现类上的可调用 `execute`、`declare_launch_args` 方法并声明对应能力,不要求继承任何能力基类。86+- `register_op_impl` 反射实现类上的可调用 `execute`、`compile`、`declare_launch_args` 方法并声明对应能力,不要求继承任何能力基类。
86- `execute` 接收按 canonical IR 组装的输入和属性;上下文通过 `get_execute_ctx()` 获取。87- `execute` 接收按 canonical IR 组装的输入和属性;上下文通过 `get_execute_ctx()` 获取。
87- `EagerOpExecutionContext` 支持输入输出 tensor 查询、动态输入实例数、运行时属性读取、输出/工作区分配和 stream 获取。88- `EagerOpExecutionContext` 支持输入输出 tensor 查询、动态输入实例数、运行时属性读取、输出/工作区分配和 stream 获取。
88- `declare_launch_args` 支持按 canonical IR 将输入和输出组装为位置参数,并将属性组装为 keyword-only 参数;回调通过 `get_declare_launch_args_ctx()` 创建参数 builder、申请 workspace、添加 kernel launch。`append_input` / `append_output` 的 index 使用计算节点输入输出的实例平铺 index。89- `declare_launch_args` 支持按 canonical IR 将输入和输出组装为位置参数,并将属性组装为 keyword-only 参数;回调通过 `get_declare_launch_args_ctx()` 创建参数 builder、申请 workspace、添加 kernel launch。`append_input` / `append_output` 的 index 使用计算节点输入输出的实例平铺 index。
90+- `compile` 按 canonical IR 组装输入、输出和属性,通过 `get_compile_ctx()` 查询编译option,通过 `get_compile_platform_info()` 查询平台资源、核数和SoC信息;该回调只用于图编译,模型加载和执行阶段不调用。
89- `ASCEND_CUSTOM_OPP_PATH` 同时承载现有 C++ custom op OPP 路径和 Python custom op 文件/包路径。91- `ASCEND_CUSTOM_OPP_PATH` 同时承载现有 C++ custom op OPP 路径和 Python custom op 文件/包路径。
90- GE 初始化和 `GraphManager::PreRun()` 会在需要时幂等加载 Python custom op,使 `OpsKernelInfo` 刷新前能看到对应 op type。92- GE 初始化和 `GraphManager::PreRun()` 会在需要时幂等加载 Python custom op,使 `OpsKernelInfo` 刷新前能看到对应 op type。
91 93 
@@ -96,11 +98,11 @@ V1 功能包括:
96- Python 入口失败只在实际存在 Python custom op 入口时影响加载;没有 Python 文件/包时直接跳过。98- Python 入口失败只在实际存在 Python custom op 入口时影响加载;没有 Python 文件/包时直接跳过。
97- `EagerOpExecutionContext``AnnotatedArgsContext``RuntimeAttrs` 以及由它们返回的 `Tensor` 等 borrowed view 只能在当前回调内使用;`AnnotatedKernelArgs``add_launch` 消费后不可复用。99- `EagerOpExecutionContext``AnnotatedArgsContext``RuntimeAttrs` 以及由它们返回的 `Tensor` 等 borrowed view 只能在当前回调内使用;`AnnotatedKernelArgs``add_launch` 消费后不可复用。
98- Python `execute` 的返回值当前不作为状态码使用;正常返回表示成功,抛出异常表示失败。100- Python `execute` 的返回值当前不作为状态码使用;正常返回表示成功,抛出异常表示失败。
99-- Python custom op 当前由 C++ adapter 声明 `EagerExecuteOp` 和 `AnnotatedArgsOp` capability;其它 C++ 能力接口由 adapter 保留 override 但按不支持处理。101+- Python custom op 当前由 C++ adapter 声明 `EagerExecuteOp`、`CompilableOp` 和 `AnnotatedArgsOp` capability;其它 C++ 能力接口由 adapter 保留 override 但按不支持处理。
100-- schema-bound 形式依赖已有算子原型的 canonical IR。bridge 加载 descriptor 时收集 canonical IR,并在创建 holder 和调用业务 callback 之前调用 `validate_op_impl_descriptor`,一次性校验 schema-bound 签名:`execute` 校验 IR 输入和属性,不把输出参数纳入签名;两个 callback 都必须显式声明 `-> None`实际返回值也必须为 `None`;`declare_launch_args` 额外校验输出参数。runtime callback 只组装实参并调用业务方法,不再校验签名;校验结果属于 descriptor 加载阶段,不进入 holder 生命周期。102+- schema-bound 形式依赖已有算子原型的 canonical IR。bridge 加载 descriptor 时收集 canonical IR,并在创建 holder 和调用业务 callback 之前调用 `validate_op_impl_descriptor`,一次性校验 schema-bound 签名:`execute` 校验 IR 输入和属性,不把输出参数纳入签名;`compile` `declare_launch_args` 校验输入、输出、属性并要求显式声明 `-> None`;三个 callback 的实际返回值也必须为`None`。runtime callback 只组装实参并调用业务方法,不再校验签名;校验结果属于 descriptor 加载阶段,不进入 holder 生命周期。
101- 跨 SO 的 proto/Adapter descriptor 是同步借用的 C POD view,runtime callback 返回前必须完成校验和深拷贝。103- 跨 SO 的 proto/Adapter descriptor 是同步借用的 C POD view,runtime callback 返回前必须完成校验和深拷贝。
102- Python 原型允许覆盖内置原型;若 `CustomOpFactory` 已存在同名 C++ 或 Python 自定义算子,则视为自定义算子冲突。104- Python 原型允许覆盖内置原型;若 `CustomOpFactory` 已存在同名 C++ 或 Python 自定义算子,则视为自定义算子冲突。
103-- schema-bound 回调通过 `get_execute_ctx()` 获取当前 context;该绑定只在当前回调动态作用域内有效。105+- schema-bound 回调分别通过 `get_execute_ctx()`、`get_compile_ctx()` 或 `get_declare_launch_args_ctx()` 获取当前 context;该绑定只在当前回调动态作用域内有效。
104- Python custom op native/bridge 与构建时 Python ABI 相关,不提供跨 Python minor version 兼容承诺。106- Python custom op native/bridge 与构建时 Python ABI 相关,不提供跨 Python minor version 兼容承诺。
105- bridge C ABI 保持为 v1,`execute``declare_launch_args` 回调只传 holder 与对应 context;canonical IR 由 bridge 通过 run 包公共接口查询,不通过私有 ABI 投影传递。107- bridge C ABI 保持为 v1,`execute``declare_launch_args` 回调只传 holder 与对应 context;canonical IR 由 bridge 通过 run 包公共接口查询,不通过私有 ABI 投影传递。
106 108 
@@ -111,6 +113,7 @@ V1 功能包括:
111- 对未使用 Python `register_op` 的算子,V1 兼容路径中的算子原型和 shape/dtype 推导仍由用户按现有 C++ / OPP 方式提供;Python `register_op` 算子使用本 V2 的 `infer_meta` 路径。113- 对未使用 Python `register_op` 的算子,V1 兼容路径中的算子原型和 shape/dtype 推导仍由用户按现有 C++ / OPP 方式提供;Python `register_op` 算子使用本 V2 的 `infer_meta` 路径。
112- Python custom op 样例依赖 ACL Python runtime 和与 run 包匹配的 Python 环境。114- 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` 兼容兜底。115- `declare_launch_args` 仅在编译期依赖 Python 注册环境。新 OM 通过 `_custom_task_args_mode` 保存最终选择的刷新方式,并通过 `args_format` 保存 launch 布局;静态模型加载时以显式模式为准,不需要再次加载 Python 实现。没有该属性的旧 OM 保留 registry 查询和 `args_format` 兼容兜底。
116+- `compile` 在图编译阶段执行,编译上下文和平台信息只在当前回调内有效;不把 Python 实现、实例状态或 kernel binary 保存到 OM。
114 117 
115## 3. 特性需求分析与设计118## 3. 特性需求分析与设计
116 119 
@@ -211,7 +214,28 @@ def execute(self, x, optional_y, dynamic_z, *, alpha, axes) -> None:
211- 编译结果将最终选择的刷新方式保存到 `_custom_task_args_mode`,并将 launch 参数格式保存到 `args_format`214- 编译结果将最终选择的刷新方式保存到 `_custom_task_args_mode`,并将 launch 参数格式保存到 `args_format`
212- 静态模型加载阶段根据显式模式选择刷新路径;AnnotatedArgs 路径消费 `args_format` 且不回调 Python。只有没有该属性的旧 OM 才查询 registry,并使用 `args_format` 兼容兜底。215- 静态模型加载阶段根据显式模式选择刷新路径;AnnotatedArgs 路径消费 `args_format` 且不回调 Python。只有没有该属性的旧 OM 才查询 registry,并使用 `args_format` 兼容兜底。
213 216 
214-#### 3.2.3 插件发现与注册217+#### 3.2.3 Python Compile 图编译接口
218+ 
219+**介绍**
220+ 
221+用户实现`compile`方法声明图编译能力。该方法只支持schema-bound形式,GE根据Ascend IR算子原型按顺序传入输入、输出和属性参数。
222+ 
223+**输入**
224+ 
225+- 输入和输出作为位置参数传入,required input/output使用`Tensor`,optional input使用`Optional[Tensor]`,dynamic input/output使用`List[Tensor]`
226+- 属性作为keyword-only参数传入,名称、顺序和类型与Ascend IR算子原型一致。
227+ 
228+**处理**
229+ 
230+- bridge在descriptor加载阶段校验`compile`方法签名,并在回调中通过`get_compile_ctx()`提供编译option查询,通过`get_compile_platform_info()`提供平台资源、核数和SoC查询。
231+- 回调返回或抛出异常后,`OpCompileContext``CompilePlatformInfo`、输入输出`Tensor`和属性视图失效;返回值必须为`None`
232+ 
233+**输出**
234+ 
235+- `compile`只在图编译阶段调用,不在模型加载和执行阶段调用。
236+- Python实现、实例状态和kernel binary不写入OM。
237+ 
238+#### 3.2.4 插件发现与注册
215 239 
216**介绍**240**介绍**
217 241 
@@ -242,7 +266,7 @@ Python custom op 使用 `@register_op_impl(op_type=...)` 装饰器注册实现
242| `class_name` | Python 类名 |266| `class_name` | Python 类名 |
243| `interfaces` | 能力列表,可包含 `"eager_execute"``"annotated_args"` 或两者 |267| `interfaces` | 能力列表,可包含 `"eager_execute"``"annotated_args"` 或两者 |
244 268 
245-#### 3.2.4 Native Context 接口269+#### 3.2.5 Native Context 接口
246 270 
247**介绍**271**介绍**
248 272 
@@ -292,7 +316,7 @@ Python custom op 使用 `@register_op_impl(op_type=...)` 装饰器注册实现
292- stream、workspace 地址以 Python `int` 表示。316- stream、workspace 地址以 Python `int` 表示。
293- `RuntimeAttrs` 及其返回的 borrowed 对象随当前 context 一起失效。317- `RuntimeAttrs` 及其返回的 borrowed 对象随当前 context 一起失效。
294 318 
295-#### 3.2.5 C++ Adapter 与能力检测319+#### 3.2.6 C++ Adapter 与能力检测
296 320 
297**介绍**321**介绍**
298 322 
@@ -306,16 +330,17 @@ Python custom op 使用 `@register_op_impl(op_type=...)` 装饰器注册实现
306**处理**330**处理**
307 331 
308- `PythonCustomOpAdapter` 继承 `EagerExecuteOp``AnnotatedArgsOp``CompilableOp``ShapeInferOp``PortableOp``ArgsUpdater``CustomOpCapabilityProvider`332- `PythonCustomOpAdapter` 继承 `EagerExecuteOp``AnnotatedArgsOp``CompilableOp``ShapeInferOp``PortableOp``ArgsUpdater``CustomOpCapabilityProvider`
309-- 当前 `PythonCustomOpCallbacks::IsValid()` 接受 `kEagerExecute`、`kAnnotatedArgs` 或两者组合,并校验能力对应的 callback 非空。333+- 当前 `PythonCustomOpCallbacks::IsValid()` 接受 `kEagerExecute`、`kCompilable`、`kAnnotatedArgs` 及其组合,并校验能力对应的 callback 非空。
310- GE 内部能力检测使用 `CustomOpCast<T>()`。普通 C++ custom op 退化为 `dynamic_cast<T *>`,Python adapter 先检查 bitmask。334- GE 内部能力检测使用 `CustomOpCast<T>()`。普通 C++ custom op 退化为 `dynamic_cast<T *>`,Python adapter 先检查 bitmask。
311 335 
312**输出**336**输出**
313 337 
314- 支持 `kEagerExecute` 时,`Execute(ctx)` 转发到 Python。338- 支持 `kEagerExecute` 时,`Execute(ctx)` 转发到 Python。
339+- 支持 `kCompilable` 时,`Compile(ctx)` 转发到 Python。
315- 支持 `kAnnotatedArgs` 时,`DeclareLaunchArgs(ctx)` 转发到 Python。340- 支持 `kAnnotatedArgs` 时,`DeclareLaunchArgs(ctx)` 转发到 Python。
316-- 不支持的 `Compile`、`InferShape`、`InferDataType`、`Serialize`、`Deserialize`、`UpdateHostArgs` 返回 `GRAPH_FAILED` 并记录日志。341+- 不支持的 `InferShape`、`InferDataType`、`Serialize`、`Deserialize`、`UpdateHostArgs` 返回 `GRAPH_FAILED` 并记录日志。
317 342 
318-#### 3.2.6 加载、卸载与生命周期343+#### 3.2.7 加载、卸载与生命周期
319 344 
320**介绍**345**介绍**
321 346 
@@ -482,13 +507,14 @@ native binding 保存 `gert::AnnotatedArgsContext *` 和独立 validity 标记
482- **插件发现**:复用 `ge._internal.plugin_loader`,按 `os.pathsep` 切分环境变量;文件按动态模块名导入,目录按一层 `.py` 文件和 package 导入。507- **插件发现**:复用 `ge._internal.plugin_loader`,按 `os.pathsep` 切分环境变量;文件按动态模块名导入,目录按一层 `.py` 文件和 package 导入。
483- **artifact 选择**:复用 `python_artifact_utils``python_bridge_loader_utils`,按已加载 Python runtime key 匹配 `python_custom_op_artifacts`508- **artifact 选择**:复用 `python_artifact_utils``python_bridge_loader_utils`,按已加载 Python runtime key 匹配 `python_custom_op_artifacts`
484- **能力反射**:registry 对实现 class 执行 `getattr``callable` 检查,把 `execute` 映射为 `eager_execute`,把 `declare_launch_args` 映射为 `annotated_args`;Python 用户类的继承关系不参与能力判断。509- **能力反射**:registry 对实现 class 执行 `getattr``callable` 检查,把 `execute` 映射为 `eager_execute`,把 `declare_launch_args` 映射为 `annotated_args`;Python 用户类的继承关系不参与能力判断。
510+- **能力反射**:registry 对实现 class 执行 `getattr``callable` 检查,把 `execute` 映射为 `eager_execute`,把 `compile` 映射为 `compilable`,把 `declare_launch_args` 映射为 `annotated_args`;Python 用户类的继承关系不参与能力判断。
485- **capability 过滤**`CustomOpCast<T>()` 先识别 `CustomOpCapabilityProvider`,再按 bitmask 判断是否支持目标接口。511- **capability 过滤**`CustomOpCast<T>()` 先识别 `CustomOpCapabilityProvider`,再按 bitmask 判断是否支持目标接口。
486- **IR 实参组装**:bridge 在 descriptor 加载阶段通过 run 包公共接口查询 canonical IR 以校验签名,holder 创建时再次查询并持有运行期 IR 快照;runtime callback 按该快照的 IR 顺序读取 required/optional/dynamic 输入输出和 typed runtime attrs,分别构造 positional arguments 和 keyword arguments。512- **IR 实参组装**:bridge 在 descriptor 加载阶段通过 run 包公共接口查询 canonical IR 以校验签名,holder 创建时再次查询并持有运行期 IR 快照;runtime callback 按该快照的 IR 顺序读取 required/optional/dynamic 输入输出和 typed runtime attrs,分别构造 positional arguments 和 keyword arguments。
487- **注册事务**:先同步深拷贝 proto C POD 并注册 creator,再收集 canonical IR,最后注册 impl runtime entry 和 Adapter creator;任一步失败由上层 loader 调用卸载,按相反顺序回滚已完成的步骤。513- **注册事务**:先同步深拷贝 proto C POD 并注册 creator,再收集 canonical IR,最后注册 impl runtime entry 和 Adapter creator;任一步失败由上层 loader 调用卸载,按相反顺序回滚已完成的步骤。
488-- **回调签名校验**:bridge 加载 descriptor 时调用 `validate_op_impl_descriptor` 一次性校验 schema-bound 签名,并在创建 holder 和业务 callback 之前完成。两个 callback 都校验返回注解为 `None`;`execute` 校验总参数数量、输入的位置形式及已提供的类型注解,以及属性的 keyword-only 形式、名称和已提供的类型注解;`declare_launch_args` 额外校验输出参数。runtime callback 不再校验签名,但会检查实际返回值必须为 `None`,校验状态也不进入 holder 生命周期。514+- **回调签名校验**:bridge 加载 descriptor 时调用 `validate_op_impl_descriptor` 一次性校验 schema-bound 签名,并在创建 holder 和业务 callback 之前完成。`execute` 校验总参数数量、输入的位置形式及已提供的类型注解,以及属性的 keyword-only 形式、名称和已提供的类型注解;`compile` 和 `declare_launch_args` 额外校验输出参数,三个 callback 都校验返回注解为 `None`。runtime callback 不再校验签名,但会检查实际返回值必须为`None`,校验状态也不进入 holder 生命周期。
489- **holder 生命周期**:C++ adapter 拥有 `PythonCustomOpHolder`,Python 侧 `_OP_IMPL_HOLDERS``instance_id` 保存实例;adapter 析构时销毁 Python holder。515- **holder 生命周期**:C++ adapter 拥有 `PythonCustomOpHolder`,Python 侧 `_OP_IMPL_HOLDERS``instance_id` 保存实例;adapter 析构时销毁 Python holder。
490-- **上下文绑定**:schema-bound 回调使用 `ContextVar` 建立动态作用域,`get_execute_ctx()` / `get_declare_launch_args_ctx()` 读取对应绑定;token reset 支持嵌套调用后恢复外层 context。516+- **上下文绑定**:schema-bound 回调使用 `ContextVar` 建立动态作用域,`get_execute_ctx()`、`get_compile_ctx()` / `get_compile_platform_info()` 和 `get_declare_launch_args_ctx()` 读取对应绑定;token reset 支持嵌套调用后恢复外层 context。
491-- **上下文失效**:bridge 的 execute/declare 调用都使用 `finally` 确保 context 失效,不依赖用户正常返回。517+- **上下文失效**:bridge 的 execute/compile/declare 调用都使用 `finally` 确保 context 失效,不依赖用户正常返回。
492 518 
493### 6.3 流程设计519### 6.3 流程设计
494 520 
@@ -552,7 +578,7 @@ PythonCustomOpAdapter::DeclareLaunchArgs(ctx)
552 578 
553- `api/python/ge/ge/custom_op/`:新增 Python custom op API、registry、bootstrap、bridge helper、独立的 schema callback 签名校验模块和 native context binding。579- `api/python/ge/ge/custom_op/`:新增 Python custom op API、registry、bootstrap、bridge helper、独立的 schema callback 签名校验模块和 native context binding。
554- `api/python/ge/ge/runtime/`:提供 runtime tensor/shape/format 类型,供 custom op context 复用。580- `api/python/ge/ge/runtime/`:提供 runtime tensor/shape/format 类型,供 custom op context 复用。
555-- `runtime/custom_op/`:新增 Python bridge loader 和 adapter,同时保持 bridge C ABI v1;adapter 转发 Eager/AnnotatedArgs 类回调,canonical IR 由 bridge 通过 run 包公共接口获取并缓存。581+- `runtime/custom_op/`:新增 Python bridge loader 和 adapter,同时保持 bridge C ABI v1;adapter 转发 Eager、Compile、AnnotatedArgs 类回调,canonical IR 由 bridge 通过 run 包公共接口获取并缓存。
556- `inc/graph_metadef/graph/custom_op/`:新增 capability 和 cast helper。582- `inc/graph_metadef/graph/custom_op/`:新增 capability 和 cast helper。
557- GE 初始化入口:在 `LoadCustomOps()` 前确保 Python runtime 尝试 ready;失败告警继续,由 Python custom op loader 在确有 Python 入口时再做 hard fail。583- GE 初始化入口:在 `LoadCustomOps()` 前确保 Python runtime 尝试 ready;失败告警继续,由 Python custom op loader 在确有 Python 入口时再做 hard fail。
558- `compiler/graph/manager/graph_manager.cc``PreRun()` 刷新 ops kernel 信息前幂等加载 Python custom op。584- `compiler/graph/manager/graph_manager.cc``PreRun()` 刷新 ops kernel 信息前幂等加载 Python custom op。
@@ -566,7 +592,8 @@ PythonCustomOpAdapter::DeclareLaunchArgs(ctx)
566- holder 创建失败:adapter 判定无效,执行路径失败。592- holder 创建失败:adapter 判定无效,执行路径失败。
567- context 查询、输出分配或 workspace 分配失败:native binding 抛 `RuntimeError`593- context 查询、输出分配或 workspace 分配失败:native binding 抛 `RuntimeError`
568- schema-bound descriptor 所需 canonical IR 收集失败:bridge 在 descriptor 加载/注册阶段返回失败,此时尚未创建 holder 或 callback context。594- schema-bound descriptor 所需 canonical IR 收集失败:bridge 在 descriptor 加载/注册阶段返回失败,此时尚未创建 holder 或 callback context。
569-- schema-bound `execute` 或 `declare_launch_args` 签名与 canonical IR 不匹配:`validate_op_impl_descriptor` 在 descriptor 加载/注册阶段抛 `TypeError`,业务 callback 不会执行。595+- schema-bound `execute`、`compile` 或 `declare_launch_args` 签名与 canonical IR 不匹配:`validate_op_impl_descriptor` 在 descriptor 加载/注册阶段抛 `TypeError`,业务 callback 不会执行。
596+- `compile`缺失canonical IR、返回非`None`或访问过期context:Python bridge/native binding抛异常并终止图编译。
570- `declare_launch_args` 返回非 `None`、重复消费 `AnnotatedKernelArgs`,或输入输出实例平铺 index 越界:Python bridge/native binding 抛异常并终止编译。597- `declare_launch_args` 返回非 `None`、重复消费 `AnnotatedKernelArgs`,或输入输出实例平铺 index 越界:Python bridge/native binding 抛异常并终止编译。
571 598 
572#### 接口错误599#### 接口错误
@@ -578,6 +605,7 @@ PythonCustomOpAdapter::DeclareLaunchArgs(ctx)
578- 重复 `op_type``descriptor_key`:抛 `ValueError`605- 重复 `op_type``descriptor_key`:抛 `ValueError`
579- schema-bound `execute` 缺少 canonical IR:descriptor 加载/注册失败,尚未创建 callback context。606- schema-bound `execute` 缺少 canonical IR:descriptor 加载/注册失败,尚未创建 callback context。
580- `get_execute_ctx()` 在 schema-bound 回调外调用:抛 `RuntimeError`607- `get_execute_ctx()` 在 schema-bound 回调外调用:抛 `RuntimeError`
608+- `get_compile_ctx()``get_compile_platform_info()``compile` 回调外调用:抛 `RuntimeError`
581- `get_declare_launch_args_ctx()` 在回调外调用:抛 `RuntimeError`609- `get_declare_launch_args_ctx()` 在回调外调用:抛 `RuntimeError`
582- borrowed view 过期后访问:抛 `RuntimeError`610- borrowed view 过期后访问:抛 `RuntimeError`
583 611 
@@ -616,7 +644,7 @@ PythonCustomOpAdapter::DeclareLaunchArgs(ctx)
616### 9.1 测试边界644### 9.1 测试边界
617 645 
618- Python API 测试入口:`ge.custom_op``ge.custom_op.proto``ge.custom_op._bridge``ge.custom_op.bootstrap`646- Python API 测试入口:`ge.custom_op``ge.custom_op.proto``ge.custom_op._bridge``ge.custom_op.bootstrap`
619-- Native context 测试入口:Eager/AnnotatedArgs borrowed context、`AnnotatedKernelArgs` 和 launch info 方法。647+- Native context 测试入口:Eager/Compile/AnnotatedArgs borrowed context、`AnnotatedKernelArgs` 和 launch info 方法。
620- C++ 测试入口:`CustomOpCast<T>``PythonCustomOpAdapter``AnnotatedKernelArgs``CustomTaskInfo``LoadPythonCustomOps()``LoadCustomOps()`/`UnloadCustomOps()``ShutdownCustomOpsForProcess()`648- C++ 测试入口:`CustomOpCast<T>``PythonCustomOpAdapter``AnnotatedKernelArgs``CustomTaskInfo``LoadPythonCustomOps()``LoadCustomOps()`/`UnloadCustomOps()``ShutdownCustomOpsForProcess()`
621- 端到端样例入口:`examples/custom_op/annotated_args_refresh_add_custom/python/run.sh`649- 端到端样例入口:`examples/custom_op/annotated_args_refresh_add_custom/python/run.sh`
622 650 
@@ -629,6 +657,7 @@ PythonCustomOpAdapter::DeclareLaunchArgs(ctx)
629| 功能 | schema-bound required/optional/dynamic 输入和 typed attrs 组装、`get_execute_ctx()` 作用域 | Python pytest fake context | UT |657| 功能 | schema-bound required/optional/dynamic 输入和 typed attrs 组装、`get_execute_ctx()` 作用域 | Python pytest fake context | UT |
630| 功能 | descriptor 加载阶段的 schema-bound `execute` 输入/属性签名校验、输出/返回兼容,以及 runtime callback 不重复校验 | Python pytest fake context | UT |658| 功能 | descriptor 加载阶段的 schema-bound `execute` 输入/属性签名校验、输出/返回兼容,以及 runtime callback 不重复校验 | Python pytest fake context | UT |
631| 功能 | descriptor 加载阶段的 schema-bound `declare_launch_args` 签名校验,以及 runtime 输入输出/属性组装、返回值校验、实例平铺 index 及 builder 消费语义 | Python pytest fake/native context | UT |659| 功能 | descriptor 加载阶段的 schema-bound `declare_launch_args` 签名校验,以及 runtime 输入输出/属性组装、返回值校验、实例平铺 index 及 builder 消费语义 | Python pytest fake/native context | UT |
660+| 功能 | `compile` 的 schema-bound 输入输出属性组装、签名校验、编译上下文和平台信息查询、返回值及 context 失效语义 | Python pytest fake/native context | UT |
632| 功能 | `get_execute_ctx()` / `get_declare_launch_args_ctx()` 回调内访问、异常清理和嵌套调用恢复 | Python pytest | UT |661| 功能 | `get_execute_ctx()` / `get_declare_launch_args_ctx()` 回调内访问、异常清理和嵌套调用恢复 | Python pytest | UT |
633| 功能 | bridge descriptor 获取、holder 创建/销毁、不可调用方法拦截和 context 失效 | Python pytest | UT |662| 功能 | bridge descriptor 获取、holder 创建/销毁、不可调用方法拦截和 context 失效 | Python pytest | UT |
634| 功能 | canonical IR 查询缓存、bridge ABI v1 和 adapter execute/declare/infer-meta 转发 | C++ gtest | UT |663| 功能 | canonical IR 查询缓存、bridge ABI v1 和 adapter execute/declare/infer-meta 转发 | C++ gtest | UT |
@@ -298,8 +298,20 @@ graphStatus PythonCustomOpAdapter::DeclareLaunchArgs(gert::AnnotatedArgsContext
298}298}
299 299 
300graphStatus PythonCustomOpAdapter::Compile(gert::OpCompileContext *ctx) {300graphStatus PythonCustomOpAdapter::Compile(gert::OpCompileContext *ctx) {
301- (void)ctx;301+ if (!HasCapability(CustomOpCapability::kCompilable)) {
302- return ReportUnsupported(CustomOpCapability::kCompilable, "Compile");302+ return ReportUnsupported(CustomOpCapability::kCompilable, "Compile");
303+ }
304+ if ((holder_ == nullptr) || (!holder_->IsValid()) || (holder_->GetHolder() == nullptr) ||
305+ (holder_->GetCallbacks().compile_impl == nullptr)) {
306+ GELOGE(GRAPH_FAILED, "Python custom op adapter is invalid, descriptor key[%s], op type[%s].",
307+ impl_descriptor_key_.c_str(), op_type_.c_str());
308+ return GRAPH_FAILED;
309+ }
310+ if (ctx == nullptr) {
311+ GELOGE(GRAPH_FAILED, "Python custom op[%s] compile context is null.", op_type_.c_str());
312+ return GRAPH_FAILED;
313+ }
314+ return holder_->GetCallbacks().compile_impl(holder_->GetHolder(), ctx);
303}315}
304 316 
305graphStatus PythonCustomOpAdapter::InferShape(gert::InferShapeContext *ctx) {317graphStatus PythonCustomOpAdapter::InferShape(gert::InferShapeContext *ctx) {
@@ -26,6 +26,7 @@ namespace py = pybind11;
26namespace {26namespace {
27constexpr const char *kInterfaceAnnotatedArgs = "annotated_args";27constexpr const char *kInterfaceAnnotatedArgs = "annotated_args";
28constexpr const char *kInterfaceEagerExecute = "eager_execute";28constexpr const char *kInterfaceEagerExecute = "eager_execute";
29+constexpr const char *kInterfaceCompilable = "compilable";
29 30 
30PythonCustomOpStringView MakeStringView(const std::string &value) {31PythonCustomOpStringView MakeStringView(const std::string &value) {
31 return PythonCustomOpStringView{value.data(), value.size()};32 return PythonCustomOpStringView{value.data(), value.size()};
@@ -70,6 +71,8 @@ Status ParseInterfaces(const py::object &interfaces_obj, CustomOpCapabilityMask
70 AddCustomOpCapability(capabilities, CustomOpCapability::kEagerExecute);71 AddCustomOpCapability(capabilities, CustomOpCapability::kEagerExecute);
71 } else if (interface_name == kInterfaceAnnotatedArgs) {72 } else if (interface_name == kInterfaceAnnotatedArgs) {
72 AddCustomOpCapability(capabilities, CustomOpCapability::kAnnotatedArgs);73 AddCustomOpCapability(capabilities, CustomOpCapability::kAnnotatedArgs);
74+ } else if (interface_name == kInterfaceCompilable) {
75+ AddCustomOpCapability(capabilities, CustomOpCapability::kCompilable);
73 } else {76 } else {
74 GELOGE(FAILED, "Unsupported python custom op interface[%s].", interface_name.c_str());77 GELOGE(FAILED, "Unsupported python custom op interface[%s].", interface_name.c_str());
75 return FAILED;78 return FAILED;
@@ -21,6 +21,7 @@
21namespace gert {21namespace gert {
22class AnnotatedArgsContext;22class AnnotatedArgsContext;
23class EagerOpExecutionContext;23class EagerOpExecutionContext;
24+class OpCompileContext;
24class InferShapeContext;25class InferShapeContext;
25class StorageShape;26class StorageShape;
26} // namespace gert27} // namespace gert
@@ -131,15 +132,18 @@ using PythonCustomOpImplHolderCreateFn = void *(*)(const PythonCustomOpAdapterDe
131using PythonCustomOpImplHolderDestroyFn = void (*)(void *holder);132using PythonCustomOpImplHolderDestroyFn = void (*)(void *holder);
132using PythonCustomOpImplExecuteFn = graphStatus (*)(const void *holder, gert::EagerOpExecutionContext *ctx);133using PythonCustomOpImplExecuteFn = graphStatus (*)(const void *holder, gert::EagerOpExecutionContext *ctx);
133using PythonCustomOpImplDeclareLaunchArgsFn = graphStatus (*)(const void *holder, gert::AnnotatedArgsContext *ctx);134using PythonCustomOpImplDeclareLaunchArgsFn = graphStatus (*)(const void *holder, gert::AnnotatedArgsContext *ctx);
135+using PythonCustomOpImplCompileFn = graphStatus (*)(const void *holder, gert::OpCompileContext *ctx);
134struct PythonCustomOpAdapterCallbacks {136struct PythonCustomOpAdapterCallbacks {
135 PythonCustomOpImplHolderCreateFn create_impl_holder{nullptr};137 PythonCustomOpImplHolderCreateFn create_impl_holder{nullptr};
136 PythonCustomOpImplHolderDestroyFn destroy_impl_holder{nullptr};138 PythonCustomOpImplHolderDestroyFn destroy_impl_holder{nullptr};
137 PythonCustomOpImplExecuteFn execute{nullptr};139 PythonCustomOpImplExecuteFn execute{nullptr};
138 PythonCustomOpImplDeclareLaunchArgsFn declare_launch_args{nullptr};140 PythonCustomOpImplDeclareLaunchArgsFn declare_launch_args{nullptr};
141+ PythonCustomOpImplCompileFn compile_impl{nullptr};
139 PythonCustomOpInferMetaFn infer_meta{nullptr};142 PythonCustomOpInferMetaFn infer_meta{nullptr};
140 143 
141 bool IsValid(CustomOpCapabilityMask capabilities) const {144 bool IsValid(CustomOpCapabilityMask capabilities) const {
142 const auto supported_capabilities = static_cast<CustomOpCapabilityMask>(CustomOpCapability::kEagerExecute) |145 const auto supported_capabilities = static_cast<CustomOpCapabilityMask>(CustomOpCapability::kEagerExecute) |
146+ static_cast<CustomOpCapabilityMask>(CustomOpCapability::kCompilable) |
143 static_cast<CustomOpCapabilityMask>(CustomOpCapability::kAnnotatedArgs);147 static_cast<CustomOpCapabilityMask>(CustomOpCapability::kAnnotatedArgs);
144 if ((capabilities == 0U) || ((capabilities & (~supported_capabilities)) != 0U)) {148 if ((capabilities == 0U) || ((capabilities & (~supported_capabilities)) != 0U)) {
145 return false;149 return false;
@@ -153,6 +157,9 @@ struct PythonCustomOpAdapterCallbacks {
153 if (HasCustomOpCapability(capabilities, CustomOpCapability::kAnnotatedArgs) && (declare_launch_args == nullptr)) {157 if (HasCustomOpCapability(capabilities, CustomOpCapability::kAnnotatedArgs) && (declare_launch_args == nullptr)) {
154 return false;158 return false;
155 }159 }
160+ if (HasCustomOpCapability(capabilities, CustomOpCapability::kCompilable) && (compile_impl == nullptr)) {
161+ return false;
162+ }
156 return true;163 return true;
157 }164 }
158};165};
@@ -18,6 +18,7 @@
18#include <dlfcn.h>18#include <dlfcn.h>
19 19 
20#include <atomic>20#include <atomic>
21+#include <cstdint>
21#include <cstdlib>22#include <cstdlib>
22#include <memory>23#include <memory>
23#include <mutex>24#include <mutex>
@@ -526,6 +527,50 @@ class PythonCustomOpPybindBridge {
526 return GRAPH_FAILED;527 return GRAPH_FAILED;
527 }528 }
528 529 
530+ graphStatus Compile(const PythonCustomOpBridgeHolder *holder, gert::OpCompileContext *ctx) {
531+ if ((holder == nullptr) || (ctx == nullptr)) {
532+ GELOGE(GRAPH_FAILED, "Python custom op bridge holder or compile context is null.");
533+ return GRAPH_FAILED;
534+ }
535+ const auto prepare_ret = EnsureBridgeReady();
536+ if (prepare_ret != SUCCESS) {
537+ GELOGE(prepare_ret, "Prepare python custom op bridge failed.");
538+ return GRAPH_FAILED;
539+ }
540+ py::gil_scoped_acquire gil;
541+ py::object compile_ctx = py::none();
542+ try {
543+ const bool created =
544+ bridge_module_.attr("create_op_impl_holder")(holder->instance_id, holder->descriptor_key).cast<bool>();
545+ if (!created) {
546+ GELOGE(GRAPH_FAILED, "Ensure python custom op holder failed, descriptor key[%s], instance id[%s].",
547+ holder->descriptor_key.c_str(), holder->instance_id.c_str());
548+ return GRAPH_FAILED;
549+ }
550+ // Build and validate the canonical metadata before creating a borrowed
551+ // native context. If either conversion fails, there is no borrowed
552+ // object that needs cleanup.
553+ py::object python_ir_meta = BuildPythonIrMeta(holder->ir_meta.get());
554+ py::module_ native_module = py::module_::import(kCustomOpNativeModuleName);
555+ compile_ctx = native_module.attr("_borrow_op_compile_context")(py::int_(reinterpret_cast<uintptr_t>(ctx)));
556+ py::object result =
557+ bridge_module_.attr("call_compile")(holder->instance_id, std::move(python_ir_meta), compile_ctx);
558+ return TranslateStatusLike(result);
559+ } catch (const py::error_already_set &err) {
560+ const std::string error_message = err.what();
561+ InvalidateBorrowedCompileContext(compile_ctx);
562+ GELOGE(GRAPH_FAILED, "Compile python custom op failed, descriptor key[%s], instance id[%s]: %s",
563+ holder->descriptor_key.c_str(), holder->instance_id.c_str(), error_message.c_str());
564+ return GRAPH_FAILED;
565+ } catch (const std::exception &err) {
566+ const std::string error_message = err.what();
567+ InvalidateBorrowedCompileContext(compile_ctx);
568+ GELOGE(GRAPH_FAILED, "Compile python custom op failed, descriptor key[%s], instance id[%s]: %s",
569+ holder->descriptor_key.c_str(), holder->instance_id.c_str(), error_message.c_str());
570+ return GRAPH_FAILED;
571+ }
572+ }
573+ 
529 private:574 private:
530 Status CollectAndRegisterProtoDescriptors(const py::dict &descriptors, const PythonCustomOpRegistrar &registrar) {575 Status CollectAndRegisterProtoDescriptors(const py::dict &descriptors, const PythonCustomOpRegistrar &registrar) {
531 const auto callbacks = GetCallbacks();576 const auto callbacks = GetCallbacks();
@@ -736,6 +781,21 @@ class PythonCustomOpPybindBridge {
736 return native_module.attr("_borrow_eager_op_execution_context")(py::int_(reinterpret_cast<uintptr_t>(ctx)));781 return native_module.attr("_borrow_eager_op_execution_context")(py::int_(reinterpret_cast<uintptr_t>(ctx)));
737 }782 }
738 783 
784+ static void InvalidateBorrowedCompileContext(const py::object &ctx) noexcept {
785+ if (ctx.is_none()) {
786+ return;
787+ }
788+ try {
789+ ctx.attr("_invalidate")();
790+ } catch (const py::error_already_set &) {
791+ // Preserve the original callback error; cleanup is best effort because
792+ // the native wrapper may already have invalidated itself.
793+ PyErr_Clear();
794+ } catch (...) {
795+ // Do not let cleanup mask the original bridge failure.
796+ }
797+ }
798+ 
739 static py::object BuildPythonAnnotatedArgsContext(gert::AnnotatedArgsContext *ctx) {799 static py::object BuildPythonAnnotatedArgsContext(gert::AnnotatedArgsContext *ctx) {
740 py::module_ native_module = py::module_::import(kCustomOpNativeModuleName);800 py::module_ native_module = py::module_::import(kCustomOpNativeModuleName);
741 return native_module.attr("_borrow_annotated_args_context")(py::int_(reinterpret_cast<uintptr_t>(ctx)));801 return native_module.attr("_borrow_annotated_args_context")(py::int_(reinterpret_cast<uintptr_t>(ctx)));
@@ -777,6 +837,10 @@ class PythonCustomOpPybindBridge {
777 return PythonCustomOpPybindBridge::GetInstance().DeclareLaunchArgs(837 return PythonCustomOpPybindBridge::GetInstance().DeclareLaunchArgs(
778 static_cast<const PythonCustomOpBridgeHolder *>(holder), ctx);838 static_cast<const PythonCustomOpBridgeHolder *>(holder), ctx);
779 };839 };
840+ callbacks.compile_impl = [](const void *holder, gert::OpCompileContext *ctx) -> graphStatus {
841+ return PythonCustomOpPybindBridge::GetInstance().Compile(static_cast<const PythonCustomOpBridgeHolder *>(holder),
842+ ctx);
843+ };
780 callbacks.infer_meta = [](const PythonCustomOpStringView *op_type, gert::InferShapeContext *ctx,844 callbacks.infer_meta = [](const PythonCustomOpStringView *op_type, gert::InferShapeContext *ctx,
781 PythonCustomOpInferMetaResultView *result) -> graphStatus {845 PythonCustomOpInferMetaResultView *result) -> graphStatus {
782 if ((op_type == nullptr) || ((op_type->size != 0U) && (op_type->data == nullptr))) {846 if ((op_type == nullptr) || ((op_type->size != 0U) && (op_type->data == nullptr))) {
@@ -0,0 +1,19 @@
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 "platform_info.h"
12+ 
13+std::string fe::OptionalInfos::GetSocVersion() {
14+ return "Ascend910B";
15+}
16+ 
17+uint32_t fe::OptionalInfos::GetAICoreNum() {
18+ return 32U;
19+}
@@ -84,6 +84,12 @@ REG_OP(StPythonAnnotatedArgsBadAttrCustomOp)
84 .OUTPUT(z, TensorType::ALL())84 .OUTPUT(z, TensorType::ALL())
85 .REQUIRED_ATTR(alpha, Int)85 .REQUIRED_ATTR(alpha, Int)
86 .OP_END_FACTORY_REG(StPythonAnnotatedArgsBadAttrCustomOp);86 .OP_END_FACTORY_REG(StPythonAnnotatedArgsBadAttrCustomOp);
87+ 
88+REG_OP(StPythonCompilableCustomOp)
89+ .INPUT(x, TensorType::ALL())
90+ .OUTPUT(z, TensorType::ALL())
91+ .REQUIRED_ATTR(bias, Int)
92+ .OP_END_FACTORY_REG(StPythonCompilableCustomOp);
87} // namespace ge93} // namespace ge
88 94 
89namespace ge {95namespace ge {
@@ -181,6 +187,7 @@ void **args_table = nullptr;
181constexpr const char *kPythonCustomOpTypeForSt = "StPythonPybindRemoveCoverageCustomOp";187constexpr const char *kPythonCustomOpTypeForSt = "StPythonPybindRemoveCoverageCustomOp";
182constexpr const char *kPythonAnnotatedArgsOpTypeForSt = "StPythonAnnotatedArgsCustomOp";188constexpr const char *kPythonAnnotatedArgsOpTypeForSt = "StPythonAnnotatedArgsCustomOp";
183constexpr const char *kPythonAnnotatedArgsBadAttrOpTypeForSt = "StPythonAnnotatedArgsBadAttrCustomOp";189constexpr const char *kPythonAnnotatedArgsBadAttrOpTypeForSt = "StPythonAnnotatedArgsBadAttrCustomOp";
190+constexpr const char *kPythonCompilableOpTypeForSt = "StPythonCompilableCustomOp";
184constexpr const char *kPythonRt2InferMetaOpTypeForSt = "StPythonRt2InferMetaCustomOp";191constexpr const char *kPythonRt2InferMetaOpTypeForSt = "StPythonRt2InferMetaCustomOp";
185constexpr const char *kInferMetaCoverageOpTypeForSt = "StInferMetaCoverageCustomOp";192constexpr const char *kInferMetaCoverageOpTypeForSt = "StInferMetaCoverageCustomOp";
186constexpr const char *kEnvPythonCustomOpPath = "ASCEND_CUSTOM_OPP_PATH";193constexpr const char *kEnvPythonCustomOpPath = "ASCEND_CUSTOM_OPP_PATH";
@@ -188,6 +195,8 @@ constexpr const char *kEnvPythonPath = "PYTHONPATH";
188constexpr char kSharedPybindCustomOpPreambleForSt[] = R"PY(from pathlib import Path195constexpr char kSharedPybindCustomOpPreambleForSt[] = R"PY(from pathlib import Path
189from ge.custom_op import (196from ge.custom_op import (
190 AnnotatedKernelLaunchInfo,197 AnnotatedKernelLaunchInfo,
198+ get_compile_ctx,
199+ get_compile_platform_info,
191 get_declare_launch_args_ctx,200 get_declare_launch_args_ctx,
192 register_op,201 register_op,
193 register_op_impl,202 register_op_impl,
@@ -287,6 +296,65 @@ class StPythonAnnotatedArgsBadAttrCustomOp:
287 def declare_launch_args(self, x: Tensor, z: Tensor, *, beta: int) -> None:296 def declare_launch_args(self, x: Tensor, z: Tensor, *, beta: int) -> None:
288 pass297 pass
289)PY";298)PY";
299+constexpr char kSharedPybindCompilablePreambleForSt[] = R"PY(
300+COMPILE_MARKER_FILE = r')PY";
301+constexpr char kSharedPybindCompilablePrefixForSt[] = R"PY('
302+ 
303+PREVIOUS_COMPILE_OBJECTS = None
304+ 
305+@register_op_impl(op_type=')PY";
306+constexpr char kSharedPybindCompilableBodyForSt[] = R"PY(')
307+class StPythonCompilableCustomOp:
308+ def compile(self, x: Tensor, z: Tensor, *, bias: int) -> None:
309+ global PREVIOUS_COMPILE_OBJECTS
310+ if PREVIOUS_COMPILE_OBJECTS is not None:
311+ previous_ctx, previous_platform, previous_tensor, previous_attrs = PREVIOUS_COMPILE_OBJECTS
312+ for access in (
313+ previous_platform.get_soc_version,
314+ lambda: previous_tensor.storage_shape.dims,
315+ lambda: previous_attrs.get_int(0),
316+ ):
317+ try:
318+ access()
319+ except RuntimeError:
320+ pass
321+ else:
322+ raise AssertionError('compile borrowed object did not expire')
323+ ctx = get_compile_ctx()
324+ platform = get_compile_platform_info()
325+ if ctx._get_required_input_tensor(0).storage_shape.dims != x.storage_shape.dims:
326+ raise AssertionError('compile input tensor metadata mismatch')
327+ if ctx._get_attrs().get_int(0) != bias:
328+ raise AssertionError('compile attribute mismatch')
329+ for mutate in (
330+ lambda: x.shape.origin_shape.set_dim(0, 2),
331+ lambda: x.shape.set_storage_shape(x.shape.storage_shape),
332+ lambda: x.format.set_storage_format(2),
333+ lambda: x.expand_dims_type.set_expand_index(0),
334+ ):
335+ try:
336+ mutate()
337+ except RuntimeError:
338+ pass
339+ else:
340+ raise AssertionError('compile tensor metadata must be read-only')
341+ if ctx.get_option('st.python.compile.option') != 'enabled':
342+ raise AssertionError('compile option mismatch')
343+ if platform.get_platform_resource('version', 'NpuArch') != '2201':
344+ raise AssertionError('platform resource mismatch')
345+ if platform.get_platform_resource_group('SoCInfo')['ai_core_cnt'] != '24':
346+ raise AssertionError('platform resource group mismatch')
347+ if platform.get_core_num() != 8:
348+ raise AssertionError('core number mismatch')
349+ if platform.get_core_num('AiCore') != 8:
350+ raise AssertionError('AiCore number mismatch')
351+ if platform.get_soc_version() != 'Ascend910B':
352+ raise AssertionError('SoC version mismatch')
353+ if platform.get_ai_core_num() != 32:
354+ raise AssertionError('AI core number mismatch')
355+ PREVIOUS_COMPILE_OBJECTS = (ctx, platform, x, ctx._get_attrs())
356+ Path(COMPILE_MARKER_FILE).write_text('compiled', encoding='utf-8')
357+)PY";
290constexpr char kInvalidSignaturePybindPreambleForSt[] = R"PY(from ge.custom_op import register_op_impl358constexpr char kInvalidSignaturePybindPreambleForSt[] = R"PY(from ge.custom_op import register_op_impl
291from ge.runtime import Tensor359from ge.runtime import Tensor
292 360 
@@ -477,6 +545,12 @@ const std::string &GetSharedPybindCustomOpMarkerFilePathForSt() {
477 return path;545 return path;
478}546}
479 547 
548+const std::string &GetSharedPybindCustomOpCompileMarkerFilePathForSt() {
549+ static ScopedTempDirForCustomOpSt dir;
550+ static const std::string path = dir.CreateFilePath("pybind_custom_op_compile_marker.txt");
551+ return path;
552+}
553+ 
480const std::string &GetRt2InferMetaCustomOpFilePathForSt() {554const std::string &GetRt2InferMetaCustomOpFilePathForSt() {
481 static ScopedTempDirForCustomOpSt dir;555 static ScopedTempDirForCustomOpSt dir;
482 static const std::string path = dir.CreateFilePath("rt2_infer_meta_custom_op.py");556 static const std::string path = dir.CreateFilePath("rt2_infer_meta_custom_op.py");
@@ -502,7 +576,9 @@ void EnsureSharedPybindCustomOpFileForSt() {
502 GetSharedPybindCustomOpMarkerFilePathForSt() + kSharedPybindEagerCustomOpForSt +576 GetSharedPybindCustomOpMarkerFilePathForSt() + kSharedPybindEagerCustomOpForSt +
503 kPythonCustomOpTypeForSt + kSharedPybindEagerCustomOpImplForSt + kPythonCustomOpTypeForSt +577 kPythonCustomOpTypeForSt + kSharedPybindEagerCustomOpImplForSt + kPythonCustomOpTypeForSt +
504 kSharedPybindAnnotatedArgsPrefixForSt + kPythonAnnotatedArgsOpTypeForSt +578 kSharedPybindAnnotatedArgsPrefixForSt + kPythonAnnotatedArgsOpTypeForSt +
505- kSharedPybindAnnotatedArgsBodyForSt;579+ kSharedPybindAnnotatedArgsBodyForSt + kSharedPybindCompilablePreambleForSt +
580+ GetSharedPybindCustomOpCompileMarkerFilePathForSt() + kSharedPybindCompilablePrefixForSt +
581+ kPythonCompilableOpTypeForSt + kSharedPybindCompilableBodyForSt;
506 WriteTextFileForCustomOpSt(GetSharedPybindCustomOpFilePathForSt(), python_file);582 WriteTextFileForCustomOpSt(GetSharedPybindCustomOpFilePathForSt(), python_file);
507 });583 });
508}584}
@@ -1906,6 +1982,46 @@ TEST_F(CustomOpFactoryStTest, register_and_remove_python_custom_op_proto_and_imp
1906 EXPECT_EQ(CustomOpFactory::CreateOrGetCustomOp(op_type), nullptr);1982 EXPECT_EQ(CustomOpFactory::CreateOrGetCustomOp(op_type), nullptr);
1907}1983}
1908 1984 
1985+TEST_F(CustomOpFactoryStTest, PythonCompilableCustomOpRealCallbackCompiles) {
1986+ EnsureSharedPybindCustomOpFileForSt();
1987+ const auto &marker_file = GetSharedPybindCustomOpCompileMarkerFilePathForSt();
1988+ (void)remove(marker_file.c_str());
1989+ ScopedEnvVarForCustomOpSt scoped_custom_opp_path(kEnvPythonCustomOpPath, GetSharedPybindCustomOpFilePathForSt());
1990+ ScopedGraphOptionsForCustomOpSt scoped_graph_options(
1991+ std::map<std::string, std::string>{{"st.python.compile.option", "enabled"}});
1992+ 
1993+ ASSERT_EQ(GePythonRuntimeManager::Instance().EnsureReady(), SUCCESS);
1994+ ASSERT_EQ(custom_op::LoadPythonCustomOps(), SUCCESS);
1995+ ScopedLoadedPythonCustomOpsForSt loaded_python_custom_ops;
1996+ 
1997+ const AscendString op_type(kPythonCompilableOpTypeForSt);
1998+ ASSERT_TRUE(CustomOpFactory::IsExistOp(op_type));
1999+ auto *const base_op = CustomOpFactory::CreateOrGetCustomOp(op_type);
2000+ ASSERT_NE(base_op, nullptr);
2001+ EXPECT_NE(CustomOpCast<CompilableOp>(base_op), nullptr);
2002+ 
2003+ auto graph = std::make_shared<ComputeGraph>("st_python_compilable_graph");
2004+ ASSERT_NE(graph, nullptr);
2005+ auto op_desc = std::make_shared<OpDesc>("st_python_compilable_node", kPythonCompilableOpTypeForSt);
2006+ ASSERT_NE(op_desc, nullptr);
2007+ op_desc->AppendIrInput("x", kIrInputRequired);
2008+ op_desc->AppendIrOutput("z", kIrOutputRequired);
2009+ op_desc->AppendIrAttrName("bias");
2010+ ASSERT_TRUE(AttrUtils::SetInt(op_desc, "bias", 4));
2011+ GeTensorDesc input_desc(GeShape({1, 16}), FORMAT_ND, DT_FLOAT16);
2012+ input_desc.SetOriginShape(GeShape({1, 16}));
2013+ GeTensorDesc output_desc(GeShape({1, 16}), FORMAT_ND, DT_FLOAT16);
2014+ output_desc.SetOriginShape(GeShape({1, 16}));
2015+ ASSERT_EQ(op_desc->AddInputDesc("x", input_desc), GRAPH_SUCCESS);
2016+ ASSERT_EQ(op_desc->AddOutputDesc("z", output_desc), GRAPH_SUCCESS);
2017+ ASSERT_NE(graph->AddNode(op_desc), nullptr);
2018+ 
2019+ CustomGraphOptimizer optimizer;
2020+ ASSERT_EQ(optimizer.OptimizeWholeGraph(*graph), SUCCESS);
2021+ ASSERT_EQ(optimizer.OptimizeWholeGraph(*graph), SUCCESS);
2022+ EXPECT_EQ(ReadTextFileForCustomOpSt(marker_file), "compiled");
2023+}
2024+ 
1909/**2025/**
1910 * 验证 Python infer_meta 通过真实 RT2 InferShape kernel 使用运行时 shape,2026 * 验证 Python infer_meta 通过真实 RT2 InferShape kernel 使用运行时 shape,
1911 * 并从 native RuntimeAttrs 读取设计支持的全部 12 类属性。2027 * 并从 native RuntimeAttrs 读取设计支持的全部 12 类属性。
@@ -0,0 +1,490 @@
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 schema-bound Python custom-op compile callbacks."""
14+ 
15+import contextvars
16+import importlib
17+from typing import List, Optional
18+ 
19+import pytest
20+ 
21+try:
22+ bridge = importlib.import_module("ge.custom_op._bridge")
23+ context = importlib.import_module("ge.custom_op.context")
24+ custom_op = importlib.import_module("ge.custom_op")
25+ from ge.runtime import Tensor
26+except (ImportError, OSError) as exc:
27+ pytest.skip(f"无法导入 Python custom op 相关模块: {exc}", allow_module_level=True)
28+ 
29+ 
30+@pytest.fixture(autouse=True)
31+def clear_python_custom_op_runtime():
32+ custom_op.clear_registered_op_impls()
33+ bridge.clear_op_impl_holders()
34+ yield
35+ bridge.clear_op_impl_holders()
36+ custom_op.clear_registered_op_impls()
37+ 
38+ 
39+class _FakeRuntimeAttrs:
40+ def __init__(self):
41+ self.calls = []
42+ 
43+ def __getattr__(self, name):
44+ if not name.startswith("get_"):
45+ raise AttributeError(name)
46+ 
47+ def getter(index):
48+ self.calls.append((name, index))
49+ return (name, index)
50+ 
51+ return getter
52+ 
53+ 
54+class _FakeCompilePlatformInfo:
55+ def get_soc_version(self):
56+ return "Ascend910B"
57+ 
58+ 
59+class _FakeCompileContext:
60+ def __init__(self):
61+ self.invalidated = False
62+ self.attrs = _FakeRuntimeAttrs()
63+ self.platform_info = _FakeCompilePlatformInfo()
64+ self.attrs_requested = False
65+ self.calls = []
66+ 
67+ def _get_required_input_tensor(self, ir_index):
68+ self.calls.append(("required_input", ir_index))
69+ return ("required_input", ir_index)
70+ 
71+ def _get_optional_input_tensor(self, ir_index):
72+ self.calls.append(("optional_input", ir_index))
73+ return None
74+ 
75+ def _get_dynamic_input_num(self, ir_index):
76+ self.calls.append(("dynamic_input_num", ir_index))
77+ return 2
78+ 
79+ def _get_dynamic_input_tensor(self, ir_index, relative_index):
80+ self.calls.append(("dynamic_input", ir_index, relative_index))
81+ return ("dynamic_input", ir_index, relative_index)
82+ 
83+ def _get_required_output_tensor(self, ir_index):
84+ self.calls.append(("required_output", ir_index))
85+ return ("required_output", ir_index)
86+ 
87+ def _get_dynamic_output_num(self, ir_index):
88+ self.calls.append(("dynamic_output_num", ir_index))
89+ return 2
90+ 
91+ def _get_dynamic_output_tensor(self, ir_index, relative_index):
92+ self.calls.append(("dynamic_output", ir_index, relative_index))
93+ return ("dynamic_output", ir_index, relative_index)
94+ 
95+ def _get_attrs(self):
96+ self.attrs_requested = True
97+ return self.attrs
98+ 
99+ def _get_platform_info(self):
100+ return self.platform_info
101+ 
102+ def _invalidate(self):
103+ self.invalidated = True
104+ 
105+ 
106+def _full_ir_meta(op_type: str) -> dict:
107+ return {
108+ "op_type": op_type,
109+ "inputs": [
110+ {"name": "first", "kind": 0},
111+ {"name": "maybe", "kind": 1},
112+ {"name": "many", "kind": 2},
113+ ],
114+ "attrs": [{"name": "alpha", "type": "VT_INT"}],
115+ "outputs": [
116+ {"name": "result", "kind": 0},
117+ {"name": "result_many", "kind": 1},
118+ ],
119+ }
120+ 
121+ 
122+def _create_holder(instance_id: str, op_type: str) -> None:
123+ descriptors = {}
124+ for item in bridge.load_and_get_op_impl_descriptors():
125+ descriptors[item["op_type"]] = item
126+ assert bridge.create_op_impl_holder(
127+ instance_id, descriptors[op_type]["descriptor_key"]
128+ )
129+ 
130+ 
131+def _get_descriptor_key(op_type: str) -> str:
132+ return next(
133+ item["descriptor_key"]
134+ for item in bridge.load_and_get_op_impl_descriptors()
135+ if item["op_type"] == op_type
136+ )
137+ 
138+ 
139+def test_callable_compile_declares_capability():
140+ @custom_op.register_op_impl(op_type="CompilableOnly")
141+ class CompilableOnly:
142+ def compile(self) -> None:
143+ pass
144+ 
145+ assert CompilableOnly.__ge_op_impl_descriptor__.interfaces == ["compilable"]
146+ 
147+ 
148+@pytest.mark.parametrize("method_kind", ["inherited", "staticmethod", "classmethod"])
149+def test_compile_supports_inherited_static_and_class_methods(method_kind):
150+ called = []
151+ 
152+ if method_kind == "inherited":
153+ 
154+ class BaseCompile:
155+ def compile(self) -> None:
156+ called.append(method_kind)
157+ 
158+ class CompileImpl(BaseCompile):
159+ pass
160+ elif method_kind == "staticmethod":
161+ 
162+ class CompileImpl:
163+ @staticmethod
164+ def compile() -> None:
165+ called.append(method_kind)
166+ else:
167+ 
168+ class CompileImpl:
169+ @classmethod
170+ def compile(cls) -> None:
171+ called.append(method_kind)
172+ 
173+ op_type = f"CompileMethod{method_kind}"
174+ compile_impl = custom_op.register_op_impl(op_type=op_type)(CompileImpl)
175+ assert compile_impl.__ge_op_impl_descriptor__.interfaces == ["compilable"]
176+ 
177+ ctx = _FakeCompileContext()
178+ ir_meta = {"op_type": op_type, "inputs": [], "attrs": [], "outputs": []}
179+ assert bridge.validate_op_impl_descriptor(_get_descriptor_key(op_type), ir_meta)
180+ _create_holder(f"{op_type}#1", op_type)
181+ assert bridge.call_compile(f"{op_type}#1", ir_meta, ctx) is None
182+ assert called == [method_kind]
183+ assert ctx.invalidated is True
184+ 
185+ 
186+@pytest.mark.parametrize("method_name", ["compile"])
187+@pytest.mark.parametrize("method_value", [None, 1])
188+def test_non_callable_interface_method_is_rejected_at_registration(
189+ method_name, method_value
190+):
191+ class BadInterface:
192+ pass
193+ 
194+ setattr(BadInterface, method_name, method_value)
195+ with pytest.raises(TypeError, match=f"{method_name} must be callable"):
196+ custom_op.register_op_impl(op_type=f"Bad{method_name}")(BadInterface)
197+ 
198+ 
199+def test_compile_context_scope_is_nested_and_invalidated():
200+ outer = object()
201+ inner = object()
202+ copied_contexts = []
203+ 
204+ with context._compile_ctx_scope(outer):
205+ assert custom_op.get_compile_ctx() is outer
206+ with context._compile_ctx_scope(inner):
207+ assert custom_op.get_compile_ctx() is inner
208+ copied_contexts.append(contextvars.copy_context())
209+ assert custom_op.get_compile_ctx() is outer
210+ 
211+ with pytest.raises(
212+ RuntimeError, match="only available inside schema-bound compile"
213+ ):
214+ custom_op.get_compile_ctx()
215+ with pytest.raises(
216+ RuntimeError, match="only available inside schema-bound compile"
217+ ):
218+ copied_contexts[0].run(custom_op.get_compile_ctx)
219+ 
220+ 
221+def test_get_compile_ctx_is_unavailable_outside_callback():
222+ with pytest.raises(
223+ RuntimeError, match="only available inside schema-bound compile"
224+ ):
225+ custom_op.get_compile_ctx()
226+ 
227+ 
228+def test_compile_platform_info_is_separate_from_compile_context():
229+ ctx = _FakeCompileContext()
230+ 
231+ with context._compile_ctx_scope(ctx):
232+ assert custom_op.get_compile_platform_info() is ctx.platform_info
233+ assert not hasattr(custom_op.get_compile_ctx(), "get_soc_version")
234+ 
235+ 
236+def test_call_compile_binds_inputs_outputs_attrs_and_context():
237+ seen = []
238+ 
239+ @custom_op.register_op_impl(op_type="SchemaCompile")
240+ class SchemaCompile:
241+ def compile(
242+ self,
243+ first: Tensor,
244+ maybe: Optional[Tensor],
245+ many: List[Tensor],
246+ result: Tensor,
247+ result_many: list[Tensor],
248+ *,
249+ alpha: int,
250+ ) -> None:
251+ seen.append(
252+ (
253+ first,
254+ maybe,
255+ many,
256+ result,
257+ result_many,
258+ alpha,
259+ custom_op.get_compile_ctx(),
260+ )
261+ )
262+ 
263+ ctx = _FakeCompileContext()
264+ assert bridge.validate_op_impl_descriptor(
265+ _get_descriptor_key("SchemaCompile"), _full_ir_meta("SchemaCompile")
266+ )
267+ _create_holder("SchemaCompile#1", "SchemaCompile")
268+ assert (
269+ bridge.call_compile("SchemaCompile#1", _full_ir_meta("SchemaCompile"), ctx)
270+ is None
271+ )
272+ 
273+ assert seen == [
274+ (
275+ ("required_input", 0),
276+ None,
277+ [("dynamic_input", 2, 0), ("dynamic_input", 2, 1)],
278+ ("required_output", 0),
279+ [("dynamic_output", 1, 0), ("dynamic_output", 1, 1)],
280+ ("get_int", 0),
281+ ctx,
282+ )
283+ ]
284+ assert ctx.invalidated is True
285+ assert ctx.attrs.calls == [("get_int", 0)]
286+ 
287+ 
288+def test_call_compile_binds_all_canonical_attr_types():
289+ seen = []
290+ 
291+ @custom_op.register_op_impl(op_type="AllAttrCompile")
292+ class AllAttrCompile:
293+ def compile(
294+ self,
295+ *,
296+ int_attr,
297+ float_attr,
298+ bool_attr,
299+ string_attr,
300+ data_type_attr,
301+ tensor_attr,
302+ list_int_attr,
303+ list_float_attr,
304+ list_bool_attr,
305+ list_string_attr,
306+ list_data_type_attr,
307+ list_list_int_attr,
308+ ) -> None:
309+ seen.append(
310+ [
311+ int_attr,
312+ float_attr,
313+ bool_attr,
314+ string_attr,
315+ data_type_attr,
316+ tensor_attr,
317+ list_int_attr,
318+ list_float_attr,
319+ list_bool_attr,
320+ list_string_attr,
321+ list_data_type_attr,
322+ list_list_int_attr,
323+ ]
324+ )
325+ 
326+ attr_specs = [
327+ ("int_attr", "VT_INT", "get_int"),
328+ ("float_attr", "VT_FLOAT", "get_float"),
329+ ("bool_attr", "VT_BOOL", "get_bool"),
330+ ("string_attr", "VT_STRING", "get_str"),
331+ ("data_type_attr", "VT_DATA_TYPE", "get_data_type"),
332+ ("tensor_attr", "VT_TENSOR", "get_tensor"),
333+ ("list_int_attr", "VT_LIST_INT", "get_list_int"),
334+ ("list_float_attr", "VT_LIST_FLOAT", "get_list_float"),
335+ ("list_bool_attr", "VT_LIST_BOOL", "get_list_bool"),
336+ ("list_string_attr", "VT_LIST_STRING", "get_list_str"),
337+ ("list_data_type_attr", "VT_LIST_DATA_TYPE", "get_list_data_type"),
338+ ("list_list_int_attr", "VT_LIST_LIST_INT", "get_list_list_int"),
339+ ]
340+ ir_meta = {
341+ "op_type": "AllAttrCompile",
342+ "inputs": [],
343+ "attrs": [{"name": name, "type": ir_type} for name, ir_type, _ in attr_specs],
344+ "outputs": [],
345+ }
346+ ctx = _FakeCompileContext()
347+ _create_holder("AllAttrCompile#1", "AllAttrCompile")
348+ assert bridge.call_compile("AllAttrCompile#1", ir_meta, ctx) is None
349+ 
350+ expected_values = []
351+ for index, (_, _, getter_name) in enumerate(attr_specs):
352+ expected_values.append((getter_name, index))
353+ assert seen == [expected_values]
354+ assert ctx.attrs.calls == expected_values
355+ assert ctx.invalidated is True
356+ 
357+ 
358+@pytest.mark.parametrize(
359+ ("method_body", "expected"),
360+ [
361+ ("def compile(self, first, *, alpha) -> None: pass", "expected 5 positional"),
362+ (
363+ "def compile(self, first, maybe, many, result, result_many, alpha) -> None: pass",
364+ "keyword-only",
365+ ),
366+ (
367+ "def compile(self, first, maybe, many, result, result_many, *, beta) -> None: pass",
368+ "expected attr name",
369+ ),
370+ (
371+ "def compile(self, first, maybe, many, result, result_many, *args, alpha) -> None: pass",
372+ "variadic",
373+ ),
374+ (
375+ "def compile(self, first, maybe, many, result, result_many, *, alpha) -> int: pass",
376+ "expected None",
377+ ),
378+ ],
379+)
380+def test_call_compile_rejects_invalid_signature(method_body, expected):
381+ namespace = {}
382+ exec(method_body, namespace)
383+ method = namespace["compile"]
384+ 
385+ op_type = f"InvalidCompile{abs(hash((method_body, expected)))}"
386+ 
387+ @custom_op.register_op_impl(op_type=op_type)
388+ class InvalidCompile:
389+ compile = method
390+ 
391+ with pytest.raises(TypeError) as exc_info:
392+ bridge.validate_op_impl_descriptor(
393+ _get_descriptor_key(op_type), _full_ir_meta(op_type)
394+ )
395+ 
396+ message = str(exc_info.value)
397+ assert op_type in message
398+ assert "compile" in message
399+ assert "expected" in message
400+ assert "actual" in message
401+ assert expected in message
402+ 
403+ 
404+def test_call_compile_rejects_missing_schema_and_non_none_result():
405+ @custom_op.register_op_impl(op_type="MissingSchemaCompile")
406+ class MissingSchemaCompile:
407+ def compile(self) -> None:
408+ return None
409+ 
410+ ctx = _FakeCompileContext()
411+ _create_holder("MissingSchemaCompile#1", "MissingSchemaCompile")
412+ with pytest.raises(RuntimeError, match="canonical IR not found.*compile"):
413+ bridge.call_compile("MissingSchemaCompile#1", None, ctx)
414+ assert ctx.invalidated is True
415+ 
416+ 
417+def test_compile_descriptor_validation_requires_schema():
418+ @custom_op.register_op_impl(op_type="OfflineCompileOnly")
419+ class OfflineCompileOnly:
420+ def compile(self, first, *, alpha) -> None:
421+ pass
422+ 
423+ descriptor_key = _get_descriptor_key("OfflineCompileOnly")
424+ with pytest.raises(RuntimeError, match="canonical IR not found.*compile"):
425+ bridge.validate_op_impl_descriptor(descriptor_key, None)
426+ 
427+ @custom_op.register_op_impl(op_type="NonNoneCompile")
428+ class NonNoneCompile:
429+ def compile(self) -> None:
430+ return True
431+ 
432+ ctx = _FakeCompileContext()
433+ _create_holder("NonNoneCompile#1", "NonNoneCompile")
434+ with pytest.raises(TypeError, match="compile must return None"):
435+ bridge.call_compile(
436+ "NonNoneCompile#1",
437+ {"op_type": "NonNoneCompile", "inputs": [], "attrs": [], "outputs": []},
438+ ctx,
439+ )
440+ assert ctx.invalidated is True
441+ 
442+ 
443+def test_legacy_compile_context_signature_is_rejected():
444+ @custom_op.register_op_impl(op_type="LegacyCompile")
445+ class LegacyCompile:
446+ def compile(self, ctx) -> None:
447+ pass
448+ 
449+ fake = _FakeCompileContext()
450+ _create_holder("LegacyCompile#1", "LegacyCompile")
451+ with pytest.raises(TypeError, match="expected 0 positional"):
452+ bridge.call_compile(
453+ "LegacyCompile#1",
454+ {"op_type": "LegacyCompile", "inputs": [], "attrs": [], "outputs": []},
455+ fake,
456+ )
457+ assert fake.invalidated is True
458+ 
459+ 
460+def test_compile_context_is_deactivated_after_exception():
461+ copied_contexts = []
462+ 
463+ @custom_op.register_op_impl(op_type="FailingCompile")
464+ class FailingCompile:
465+ def compile(self) -> None:
466+ assert custom_op.get_compile_ctx() is not None
467+ copied_contexts.append(contextvars.copy_context())
468+ raise ValueError("compile failed")
469+ 
470+ fake = _FakeCompileContext()
471+ _create_holder("FailingCompile#1", "FailingCompile")
472+ with pytest.raises(ValueError, match="compile failed"):
473+ bridge.call_compile(
474+ "FailingCompile#1",
475+ {"op_type": "FailingCompile", "inputs": [], "attrs": [], "outputs": []},
476+ fake,
477+ )
478+ assert fake.invalidated is True
479+ with pytest.raises(
480+ RuntimeError, match="only available inside schema-bound compile"
481+ ):
482+ copied_contexts[0].run(custom_op.get_compile_ctx)
483+ 
484+ 
485+def test_native_module_exposes_compile_context_type_when_available():
486+ native_module = importlib.import_module("ge.custom_op._native")._native
487+ assert hasattr(native_module, "OpCompileContext")
488+ assert hasattr(native_module, "CompilePlatformInfo")
489+ assert not hasattr(native_module.OpCompileContext, "get_soc_version")
490+ assert hasattr(native_module, "_borrow_op_compile_context")
@@ -322,7 +322,7 @@ def test_register_op_impl_supports_plain_class_with_execute():
322def test_register_op_impl_rejects_class_without_supported_method():322def test_register_op_impl_rejects_class_without_supported_method():
323 with pytest.raises(323 with pytest.raises(
324 TypeError,324 TypeError,
325- match=r"BaseOnlyCustom' must implement at least one supported method: execute, declare_launch_args",325+ match=r"BaseOnlyCustom' must implement at least one supported method: execute, compile, declare_launch_args",
326 ):326 ):
327 327 
328 @custom_op.register_op_impl(op_type="BaseOnlyCustom")328 @custom_op.register_op_impl(op_type="BaseOnlyCustom")
@@ -111,6 +111,10 @@ graphStatus DeclareMockPythonCustomOp(const void *holder, gert::AnnotatedArgsCon
111 return (holder == nullptr) ? GRAPH_FAILED : GRAPH_SUCCESS;111 return (holder == nullptr) ? GRAPH_FAILED : GRAPH_SUCCESS;
112}112}
113 113 
114+graphStatus CompileMockPythonCustomOp(const void *holder, gert::OpCompileContext *ctx) {
115+ return ((holder != nullptr) && (ctx != nullptr)) ? GRAPH_SUCCESS : GRAPH_FAILED;
116+}
117+ 
114void *FailCreatePythonCustomOpHolder(const PythonCustomOpAdapterDescriptorView *) {118void *FailCreatePythonCustomOpHolder(const PythonCustomOpAdapterDescriptorView *) {
115 return nullptr;119 return nullptr;
116}120}
@@ -311,6 +315,30 @@ TEST(UtestCustomOpCast, exposes_each_python_adapter_capability_in_dual_mode) {
311 EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key));315 EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key));
312}316}
313 317 
318+TEST(UtestCustomOpCast, exposes_python_adapter_compilable_capability) {
319+ PythonCustomOpAdapterDescriptor desc;
320+ desc.impl_descriptor_key = "python_adapter_compilable";
321+ desc.op_type = "PythonAdapterCompilable";
322+ AddCustomOpCapability(desc.capabilities, CustomOpCapability::kCompilable);
323+ 
324+ PythonCustomOpAdapterCallbacks callbacks;
325+ callbacks.create_impl_holder = CreateMockPythonCustomOpHolder;
326+ callbacks.destroy_impl_holder = DestroyMockPythonCustomOpHolder;
327+ callbacks.compile_impl = CompileMockPythonCustomOp;
328+ 
329+ ASSERT_TRUE(PythonCustomOpImplRuntimeRegistry::Register(desc, callbacks));
330+ {
331+ PythonCustomOpAdapter adapter(desc);
332+ EXPECT_TRUE(adapter.IsValid());
333+ 
334+ BaseCustomOp *base = &adapter;
335+ EXPECT_EQ(nullptr, CustomOpCast<EagerExecuteOp>(base));
336+ EXPECT_NE(nullptr, CustomOpCast<CompilableOp>(base));
337+ EXPECT_EQ(GRAPH_FAILED, CustomOpCast<CompilableOp>(base)->Compile(nullptr));
338+ }
339+ EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key));
340+}
341+ 
314TEST(UtestCustomOpCast, rejects_unsupported_python_adapter_capability) {342TEST(UtestCustomOpCast, rejects_unsupported_python_adapter_capability) {
315 PythonCustomOpAdapterDescriptor desc;343 PythonCustomOpAdapterDescriptor desc;
316 desc.impl_descriptor_key = "python_adapter_shape_unsupported";344 desc.impl_descriptor_key = "python_adapter_shape_unsupported";
@@ -15,6 +15,7 @@
15 15 
16#include "common/python_runtime/python_artifact_utils.h"16#include "common/python_runtime/python_artifact_utils.h"
17#include "common/python_runtime/python_bridge_loader_utils.h"17#include "common/python_runtime/python_bridge_loader_utils.h"
18+#include "exe_graph/runtime/op_compile_context.h"
18#include "runtime/custom_op/python_custom_op_adapter.h"19#include "runtime/custom_op/python_custom_op_adapter.h"
19#include "runtime/custom_op/python_custom_op_bridge_c_api.h"20#include "runtime/custom_op/python_custom_op_bridge_c_api.h"
20 21 
@@ -46,6 +47,10 @@ graphStatus DeclareMockPythonCustomOp(const void *holder, gert::AnnotatedArgsCon
46 return ((holder != nullptr) && (ctx != nullptr)) ? GRAPH_SUCCESS : GRAPH_FAILED;47 return ((holder != nullptr) && (ctx != nullptr)) ? GRAPH_SUCCESS : GRAPH_FAILED;
47}48}
48 49 
50+graphStatus CompileMockPythonCustomOp(const void *holder, gert::OpCompileContext *ctx) {
51+ return ((holder != nullptr) && (ctx != nullptr)) ? GRAPH_SUCCESS : GRAPH_FAILED;
52+}
53+ 
49struct MockPythonCustomOpBridgeLoadState {54struct MockPythonCustomOpBridgeLoadState {
50 PythonCustomOpBridgeApi api{};55 PythonCustomOpBridgeApi api{};
51 const PythonCustomOpBridgeApi *api_to_return{nullptr};56 const PythonCustomOpBridgeApi *api_to_return{nullptr};
@@ -203,6 +208,30 @@ TEST(PythonCustomOpAdapter, validates_annotated_args_callback_by_capability) {
203 EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key));208 EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key));
204}209}
205 210 
211+TEST(PythonCustomOpAdapter, validates_and_forwards_compile_callback_by_capability) {
212+ PythonCustomOpAdapterDescriptor desc;
213+ desc.impl_descriptor_key = "python_adapter_compile_callback";
214+ desc.op_type = "PythonCustomOpCompileUt";
215+ AddCustomOpCapability(desc.capabilities, CustomOpCapability::kCompilable);
216+ 
217+ PythonCustomOpAdapterCallbacks callbacks;
218+ callbacks.create_impl_holder = CreateMockPythonCustomOpHolder;
219+ callbacks.destroy_impl_holder = DestroyMockPythonCustomOpHolder;
220+ EXPECT_FALSE(callbacks.IsValid(desc.capabilities));
221+ 
222+ callbacks.compile_impl = CompileMockPythonCustomOp;
223+ EXPECT_TRUE(callbacks.IsValid(desc.capabilities));
224+ ASSERT_TRUE(PythonCustomOpImplRuntimeRegistry::Register(desc, callbacks));
225+ {
226+ PythonCustomOpAdapter adapter(desc);
227+ ASSERT_TRUE(adapter.IsValid());
228+ gert::OpCompileContext ctx;
229+ EXPECT_EQ(adapter.Compile(&ctx), GRAPH_SUCCESS);
230+ EXPECT_EQ(adapter.Compile(nullptr), GRAPH_FAILED);
231+ }
232+ EXPECT_TRUE(PythonCustomOpImplRuntimeRegistry::Unregister(desc.impl_descriptor_key));
233+}
234+ 
206TEST(PythonCustomOpBridgeAbi, rejects_mismatched_abi_before_registration_and_accepts_current) {235TEST(PythonCustomOpBridgeAbi, rejects_mismatched_abi_before_registration_and_accepts_current) {
207 bridge_loader::LoadedBridgeCandidate<PythonCustomOpBridgeApi> loaded_bridge;236 bridge_loader::LoadedBridgeCandidate<PythonCustomOpBridgeApi> loaded_bridge;
208 ResetMockPythonCustomOpBridgeLoadState(kPythonCustomOpBridgeAbiVersion + 1U);237 ResetMockPythonCustomOpBridgeLoadState(kPythonCustomOpBridgeAbiVersion + 1U);