已合并
feat: Python化可编译自定义算子 #4388
shangdf创建于 21 天前
feat: Python化可编译自定义算子 #4388
已合并
共 44 个文件变更+1981-162
| @@ -46,6 +46,7 @@ target_link_libraries(_ge_custom_op_native PRIVATE | |||
| 46 | lowering | 46 | lowering |
| 47 | register | 47 | register |
| 48 | opp_registry | 48 | opp_registry |
| 49 | + platform | ||
| 49 | -Wl,--as-needed | 50 | -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_scope | 30 | +from .context import ( |
| 31 | + _compile_ctx_scope, | ||
| 32 | + _declare_launch_args_ctx_scope, | ||
| 33 | + _execute_ctx_scope, | ||
| 34 | +) | ||
| 31 | from .registry import ( | 35 | from .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 args | 229 | 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 | + | ||
| 275 | def clear_op_impl_holders() -> None: | 312 | def 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 | + | ||
| 168 | class WorkspaceAddr: | 192 | class 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 | ||
| 42 | EagerOpExecutionContext = _native.EagerOpExecutionContext | 44 | EagerOpExecutionContext = _native.EagerOpExecutionContext |
| 45 | +CompilePlatformInfo = _native.CompilePlatformInfo | ||
| 43 | AnnotatedArgsContext = _native.AnnotatedArgsContext | 46 | AnnotatedArgsContext = _native.AnnotatedArgsContext |
| 44 | AnnotatedKernelArgs = _native.AnnotatedKernelArgs | 47 | AnnotatedKernelArgs = _native.AnnotatedKernelArgs |
| 45 | AnnotatedKernelLaunchInfo = _native.AnnotatedKernelLaunchInfo | 48 | AnnotatedKernelLaunchInfo = _native.AnnotatedKernelLaunchInfo |
| 49 | +OpCompileContext = _native.OpCompileContext | ||
| 46 | WorkspaceAddr = _native.WorkspaceAddr | 50 | WorkspaceAddr = _native.WorkspaceAddr |
| 47 | InferMetaContext = _native.InferMetaContext | 51 | InferMetaContext = _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 | |||
| 19 | from dataclasses import dataclass | 19 | from dataclasses import dataclass |
| 20 | from typing import Iterator, Optional | 20 | from typing import Iterator, Optional |
| 21 | 21 | ||
| 22 | -from ._native import AnnotatedArgsContext, EagerOpExecutionContext | 22 | +from ._native import ( |
| 23 | + AnnotatedArgsContext, | ||
| 24 | + CompilePlatformInfo, | ||
| 25 | + EagerOpExecutionContext, | ||
| 26 | + OpCompileContext, | ||
| 27 | +) | ||
| 23 | 28 | ||
| 24 | 29 | ||
| 25 | 30 | ||
| @@ -86,3 +91,42 @@ def _declare_launch_args_ctx_scope(ctx: AnnotatedArgsContext) -> Iterator[None]: | |||
| 86 | finally: | 91 | finally: |
| 87 | binding.active = False | 92 | binding.active = False |
| 88 | _CURRENT_DECLARE_LAUNCH_ARGS_CONTEXT.reset(token) | 93 | _CURRENT_DECLARE_LAUNCH_ARGS_CONTEXT.reset(token) |
| 94 | + | ||
| 95 | + | ||
| 96 | + | ||
| 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 | + | ||
| 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 | 11 | ||
| 12 | 12 | ||
| 13 | 13 | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 14 | 17 | ||
| 15 | 18 | ||
| 16 | 19 | ||
| 17 | 20 | ||
| 18 | 21 | ||
| 22 | + | ||
| 19 | 23 | ||
| 20 | 24 | ||
| 21 | 25 | ||
| @@ -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 | + | ||
| 144 | BorrowedEagerOpExecutionContext BorrowEagerOpExecutionContext(uintptr_t ctx_handle) { | 371 | BorrowedEagerOpExecutionContext 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_native | 722 | } // namespace python_custom_op_native |
| 472 | } // namespace ge | 723 | } // namespace ge |
| @@ -18,6 +18,7 @@ namespace python_custom_op_native { | |||
| 18 | 18 | ||
| 19 | void BindEagerOpExecutionContext(py::module_ &m); | 19 | void BindEagerOpExecutionContext(py::module_ &m); |
| 20 | void BindAnnotatedArgsContext(py::module_ &m); | 20 | void BindAnnotatedArgsContext(py::module_ &m); |
| 21 | +void BindOpCompileContext(py::module_ &m); | ||
| 21 | void BindInferMetaContext(py::module_ &m); | 22 | void BindInferMetaContext(py::module_ &m); |
| 22 | 23 | ||
| 23 | } // namespace python_custom_op_native | 24 | } // namespace python_custom_op_native |
| @@ -14,6 +14,7 @@ namespace ge { | |||
| 14 | PYBIND11_MODULE(_ge_custom_op_native, m) { | 14 | PYBIND11_MODULE(_ge_custom_op_native, m) { |
| 15 | python_custom_op_native::BindEagerOpExecutionContext(m); | 15 | python_custom_op_native::BindEagerOpExecutionContext(m); |
| 16 | python_custom_op_native::BindAnnotatedArgsContext(m); | 16 | python_custom_op_native::BindAnnotatedArgsContext(m); |
| 17 | + python_custom_op_native::BindOpCompileContext(m); | ||
| 17 | python_custom_op_native::BindInferMetaContext(m); | 18 | python_custom_op_native::BindInferMetaContext(m); |
| 18 | } | 19 | } |
| 19 | } // namespace ge | 20 | } // namespace ge |
| @@ -113,7 +113,10 @@ inline py::list BuildIntListList(const gert::ContinuousVectorVector *value) { | |||
| 113 | class BorrowedRuntimeAttrs { | 113 | class 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_native | 182 | } // namespace python_custom_op_native |
| 176 | } // namespace ge | 183 | } // namespace ge |
| @@ -18,9 +18,11 @@ from dataclasses import dataclass, field | |||
| 18 | from typing import Any, Dict, List, Optional, Type | 18 | from typing import Any, Dict, List, Optional, Type |
| 19 | 19 | ||
| 20 | INTERFACE_EAGER_EXECUTE = "eager_execute" | 20 | INTERFACE_EAGER_EXECUTE = "eager_execute" |
| 21 | +INTERFACE_COMPILABLE = "compilable" | ||
| 21 | INTERFACE_ANNOTATED_ARGS = "annotated_args" | 22 | INTERFACE_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 | ||
| 96 | def _collect_interfaces(cls: Type[Any]) -> List[str]: | 98 | def _collect_interfaces(cls: Type[Any]) -> List[str]: |
| 97 | - return [ | 99 | + interfaces = [] |
| 98 | - name | 100 | + for name, method_name in _INTERFACE_SPECS: |
| 99 | - for name, method_name in _INTERFACE_SPECS | 101 | + 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 | ||
| 104 | def _get_interfaces(cls: Type[Any]) -> List[str]: | 116 | def _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 | ||
| 104 | class PYBIND11_EXPORT NativeShape : public NativeObjectBase<gert::Shape> { | 140 | class 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 | ||
| 154 | class PYBIND11_EXPORT NativeStorageShape : public NativeObjectBase<gert::StorageShape> { | 199 | class 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 | ||
| 188 | struct NativeTensorDescValue { | 248 | struct 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 | ||
| 295 | class PYBIND11_EXPORT NativeStorageFormat : public NativeObjectBase<gert::StorageFormat> { | 371 | class 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 | ||
| 339 | class PYBIND11_EXPORT NativeTensor : public NativeObjectBase<gert::Tensor> { | 428 | class 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 | |||
| 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_native | 507 | } // namespace python_runtime_native |
| @@ -807,7 +807,7 @@ custom_op/ | |||
| 807 | ├── proto.py # Python custom operator prototype parser, descriptors, and registry | 807 | ├── proto.py # Python custom operator prototype parser, descriptors, and registry |
| 808 | ├── registry.py # Python custom operator implementation registry and decorators | 808 | ├── registry.py # Python custom operator implementation registry and decorators |
| 809 | ├── bootstrap.py # Plugin discovery and loading | 809 | ├── bootstrap.py # Plugin discovery and loading |
| 810 | -├── context.py # Current execution context binding for schema-bound execute | 810 | +├── 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-export | 812 | ├── _native.py # Native module loading and re-export |
| 813 | ├── _artifact_utils.py # Runtime artifact selection helper | 813 | ├── _artifact_utils.py # Runtime artifact selection helper |
| @@ -816,11 +816,11 @@ custom_op/ | |||
| 816 | ``` | 816 | ``` |
| 817 | 817 | ||
| 818 | Note: Files prefixed with underscores are internal modules in the Python style. | 818 | Note: 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 Positioning | 821 | #### 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 Selection | 825 | #### Runtime Native Artifact Selection |
| 826 | 826 | ||
| @@ -906,7 +906,15 @@ The index accepted by `append_input(instance_index, tensor)` and `append_output( | |||
| 906 | 906 | ||
| 907 | Within 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. | 907 | Within 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 Class | 909 | +##### 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 type | 926 | - `op_type` - Custom operator type |
| 919 | - `module_name` - Associated module name | 927 | - `module_name` - Associated module name |
| 920 | - `class_name` - Class name | 928 | - `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 reference | 930 | - `cls` - Python implementation class reference |
| 923 | 931 | ||
| 924 | #### Registration and Discovery | 932 | #### 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` objects | 936 | - `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 | ||
| 22 | V2 extends the V1 execution path with Python prototype and Meta inference capabilities. The current stage provides Python prototype creators, Adapter registration transactions, ownership management, and a Python `infer_meta` callback connected to both compile-time and RT2 dynamic-shape inference. | 23 | V2 extends the V1 execution path with Python prototype and Meta inference capabilities. The current stage provides Python prototype creators, Adapter registration transactions, ownership management, and a Python `infer_meta` callback connected to both compile-time and RT2 dynamic-shape inference. |
| @@ -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 Design | 118 | ## 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 Registration | 217 | +#### 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 Interface | 269 | +#### 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 Detection | 319 | +#### 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 Lifecycle | 343 | +#### 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 Design | 518 | ### 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 Errors | 598 | #### 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 Boundaries | 643 | ### 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) | ||
这个地方的缩进是不是有问题?多缩了一些 ![]() ![]() | |||
| 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) | ||
1162行的缩进也有问题,应该和1161行是并列的吧 ![]() ![]() | |||
| 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 | +## 函数原型 | ||
不符合API的模版规范,缺少参数说明,约束说明,调用示例,如果无,则就在下面写无,但是不能没有 下面所有的API还要补充产品支持情况 ![]() ![]() | |||
| 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 | +``` | ||
Adocs/zh/api/graph_engine_api/python/ge/custom_op/CompilePlatformInfo/get_platform_resource_group.md+31-0
| @@ -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 | ||
| 19 | 无 | 19 | 无 |
| 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 launch参数。 | 9 | +注册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 | ```python | 34 | ```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-export | 737 | ├── _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 | ||
| 22 | V2 在 V1 执行能力的基础上扩展 Python 原型和 Meta 推导能力。当前阶段已经实现 Python 原型 creator、Adapter 注册事务和所有权管理,并打通编译期与 RT2 动态 Shape 的 Python `infer_meta` 调用链。 | 23 | V2 在 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 | ||
| 300 | graphStatus PythonCustomOpAdapter::Compile(gert::OpCompileContext *ctx) { | 300 | graphStatus 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 | ||
| 305 | graphStatus PythonCustomOpAdapter::InferShape(gert::InferShapeContext *ctx) { | 317 | graphStatus PythonCustomOpAdapter::InferShape(gert::InferShapeContext *ctx) { |
| @@ -26,6 +26,7 @@ namespace py = pybind11; | |||
| 26 | namespace { | 26 | namespace { |
| 27 | constexpr const char *kInterfaceAnnotatedArgs = "annotated_args"; | 27 | constexpr const char *kInterfaceAnnotatedArgs = "annotated_args"; |
| 28 | constexpr const char *kInterfaceEagerExecute = "eager_execute"; | 28 | constexpr const char *kInterfaceEagerExecute = "eager_execute"; |
| 29 | +constexpr const char *kInterfaceCompilable = "compilable"; | ||
| 29 | 30 | ||
| 30 | PythonCustomOpStringView MakeStringView(const std::string &value) { | 31 | PythonCustomOpStringView 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 @@ | |||
| 21 | namespace gert { | 21 | namespace gert { |
| 22 | class AnnotatedArgsContext; | 22 | class AnnotatedArgsContext; |
| 23 | class EagerOpExecutionContext; | 23 | class EagerOpExecutionContext; |
| 24 | +class OpCompileContext; | ||
| 24 | class InferShapeContext; | 25 | class InferShapeContext; |
| 25 | class StorageShape; | 26 | class StorageShape; |
| 26 | } // namespace gert | 27 | } // namespace gert |
| @@ -131,15 +132,18 @@ using PythonCustomOpImplHolderCreateFn = void *(*)(const PythonCustomOpAdapterDe | |||
| 131 | using PythonCustomOpImplHolderDestroyFn = void (*)(void *holder); | 132 | using PythonCustomOpImplHolderDestroyFn = void (*)(void *holder); |
| 132 | using PythonCustomOpImplExecuteFn = graphStatus (*)(const void *holder, gert::EagerOpExecutionContext *ctx); | 133 | using PythonCustomOpImplExecuteFn = graphStatus (*)(const void *holder, gert::EagerOpExecutionContext *ctx); |
| 133 | using PythonCustomOpImplDeclareLaunchArgsFn = graphStatus (*)(const void *holder, gert::AnnotatedArgsContext *ctx); | 134 | using PythonCustomOpImplDeclareLaunchArgsFn = graphStatus (*)(const void *holder, gert::AnnotatedArgsContext *ctx); |
| 135 | +using PythonCustomOpImplCompileFn = graphStatus (*)(const void *holder, gert::OpCompileContext *ctx); | ||
| 134 | struct PythonCustomOpAdapterCallbacks { | 136 | struct 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 | 18 | ||
| 19 | 19 | ||
| 20 | 20 | ||
| 21 | + | ||
| 21 | 22 | ||
| 22 | 23 | ||
| 23 | 24 | ||
| @@ -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 ®istrar) { | 575 | Status CollectAndRegisterProtoDescriptors(const py::dict &descriptors, const PythonCustomOpRegistrar ®istrar) { |
| 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 | + | ||
| 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 ge | 93 | } // namespace ge |
| 88 | 94 | ||
| 89 | namespace ge { | 95 | namespace ge { |
| @@ -181,6 +187,7 @@ void **args_table = nullptr; | |||
| 181 | constexpr const char *kPythonCustomOpTypeForSt = "StPythonPybindRemoveCoverageCustomOp"; | 187 | constexpr const char *kPythonCustomOpTypeForSt = "StPythonPybindRemoveCoverageCustomOp"; |
| 182 | constexpr const char *kPythonAnnotatedArgsOpTypeForSt = "StPythonAnnotatedArgsCustomOp"; | 188 | constexpr const char *kPythonAnnotatedArgsOpTypeForSt = "StPythonAnnotatedArgsCustomOp"; |
| 183 | constexpr const char *kPythonAnnotatedArgsBadAttrOpTypeForSt = "StPythonAnnotatedArgsBadAttrCustomOp"; | 189 | constexpr const char *kPythonAnnotatedArgsBadAttrOpTypeForSt = "StPythonAnnotatedArgsBadAttrCustomOp"; |
| 190 | +constexpr const char *kPythonCompilableOpTypeForSt = "StPythonCompilableCustomOp"; | ||
| 184 | constexpr const char *kPythonRt2InferMetaOpTypeForSt = "StPythonRt2InferMetaCustomOp"; | 191 | constexpr const char *kPythonRt2InferMetaOpTypeForSt = "StPythonRt2InferMetaCustomOp"; |
| 185 | constexpr const char *kInferMetaCoverageOpTypeForSt = "StInferMetaCoverageCustomOp"; | 192 | constexpr const char *kInferMetaCoverageOpTypeForSt = "StInferMetaCoverageCustomOp"; |
| 186 | constexpr const char *kEnvPythonCustomOpPath = "ASCEND_CUSTOM_OPP_PATH"; | 193 | constexpr const char *kEnvPythonCustomOpPath = "ASCEND_CUSTOM_OPP_PATH"; |
| @@ -188,6 +195,8 @@ constexpr const char *kEnvPythonPath = "PYTHONPATH"; | |||
| 188 | constexpr char kSharedPybindCustomOpPreambleForSt[] = R"PY(from pathlib import Path | 195 | constexpr char kSharedPybindCustomOpPreambleForSt[] = R"PY(from pathlib import Path |
| 189 | from ge.custom_op import ( | 196 | from 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 | pass | 297 | 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"; | ||
| 290 | constexpr char kInvalidSignaturePybindPreambleForSt[] = R"PY(from ge.custom_op import register_op_impl | 358 | constexpr char kInvalidSignaturePybindPreambleForSt[] = R"PY(from ge.custom_op import register_op_impl |
| 291 | from ge.runtime import Tensor | 359 | from 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 | + | ||
| 480 | const std::string &GetRt2InferMetaCustomOpFilePathForSt() { | 554 | const 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 | + | ||
| 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 | + | ||
| 141 | + class CompilableOnly: | ||
| 142 | + def compile(self) -> None: | ||
| 143 | + pass | ||
| 144 | + | ||
| 145 | + assert CompilableOnly.__ge_op_impl_descriptor__.interfaces == ["compilable"] | ||
| 146 | + | ||
| 147 | + | ||
| 148 | + | ||
| 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 | + | ||
| 164 | + def compile() -> None: | ||
| 165 | + called.append(method_kind) | ||
| 166 | + else: | ||
| 167 | + | ||
| 168 | + class CompileImpl: | ||
| 169 | + | ||
| 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 | + | ||
| 187 | + | ||
| 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 | + | ||
| 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 | + | ||
| 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 | + | ||
| 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 | + | ||
| 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 | + | ||
| 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 | + | ||
| 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 | + | ||
| 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 | + | ||
| 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 | + | ||
| 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(): | |||
| 322 | def test_register_op_impl_rejects_class_without_supported_method(): | 322 | def 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 | 328 | ||
| @@ -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 | + | ||
| 114 | void *FailCreatePythonCustomOpHolder(const PythonCustomOpAdapterDescriptorView *) { | 118 | void *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 | + | ||
| 314 | TEST(UtestCustomOpCast, rejects_unsupported_python_adapter_capability) { | 342 | TEST(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 | 16 | ||
| 17 | 17 | ||
| 18 | + | ||
| 18 | 19 | ||
| 19 | 20 | ||
| 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 | + | ||
| 49 | struct MockPythonCustomOpBridgeLoadState { | 54 | struct 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 | + | ||
| 206 | TEST(PythonCustomOpBridgeAbi, rejects_mismatched_abi_before_registration_and_accepts_current) { | 235 | TEST(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); |


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