已合并
feat: 自定义算子 AnnotatedArgsOp 地址刷新接口 Python 化 #4254
shangdf创建于 22 天前
feat: 自定义算子 AnnotatedArgsOp 地址刷新接口 Python 化 #4254
已合并
共 27 个文件变更+2579-73
| @@ -14,9 +14,14 @@ | |||
| 14 | 14 | ||
| 15 | __all__ = [ | 15 | __all__ = [ |
| 16 | "BaseCustomOp", | 16 | "BaseCustomOp", |
| 17 | + "AnnotatedArgsContext", | ||
| 18 | + "AnnotatedKernelArgs", | ||
| 19 | + "AnnotatedKernelLaunchInfo", | ||
| 17 | "EagerExecuteOp", | 20 | "EagerExecuteOp", |
| 18 | "EagerOpExecutionContext", | 21 | "EagerOpExecutionContext", |
| 22 | + "WorkspaceAddr", | ||
| 19 | "clear_registered_op_impls", | 23 | "clear_registered_op_impls", |
| 24 | + "get_declare_launch_args_ctx", | ||
| 20 | "get_execute_ctx", | 25 | "get_execute_ctx", |
| 21 | "get_registered_op_impl_by_descriptor_key", | 26 | "get_registered_op_impl_by_descriptor_key", |
| 22 | "get_registered_op_impl_dicts", | 27 | "get_registered_op_impl_dicts", |
| @@ -27,15 +32,20 @@ __all__ = [ | |||
| 27 | 32 | ||
| 28 | _LAZY_EXPORTS = { | 33 | _LAZY_EXPORTS = { |
| 29 | "BaseCustomOp": ".base", | 34 | "BaseCustomOp": ".base", |
| 35 | + "AnnotatedArgsContext": "._native", | ||
| 36 | + "AnnotatedKernelArgs": "._native", | ||
| 37 | + "AnnotatedKernelLaunchInfo": "._native", | ||
| 30 | "EagerExecuteOp": ".base", | 38 | "EagerExecuteOp": ".base", |
| 31 | "EagerOpExecutionContext": ".base", | 39 | "EagerOpExecutionContext": ".base", |
| 32 | "clear_registered_op_impls": ".registry", | 40 | "clear_registered_op_impls": ".registry", |
| 41 | + "get_declare_launch_args_ctx": ".context", | ||
| 33 | "get_execute_ctx": ".context", | 42 | "get_execute_ctx": ".context", |
| 34 | "get_registered_op_impl_by_descriptor_key": ".registry", | 43 | "get_registered_op_impl_by_descriptor_key": ".registry", |
| 35 | "get_registered_op_impl_dicts": ".registry", | 44 | "get_registered_op_impl_dicts": ".registry", |
| 36 | "get_registered_op_impls": ".registry", | 45 | "get_registered_op_impls": ".registry", |
| 37 | "register_op": ".proto", | 46 | "register_op": ".proto", |
| 38 | "register_op_impl": ".registry", | 47 | "register_op_impl": ".registry", |
| 48 | + "WorkspaceAddr": "._native", | ||
| 39 | } | 49 | } |
| 40 | 50 | ||
| 41 | 51 | ||
| @@ -18,11 +18,16 @@ import threading | |||
| 18 | from dataclasses import dataclass | 18 | from dataclasses import dataclass |
| 19 | from typing import Dict, Optional | 19 | from typing import Dict, Optional |
| 20 | 20 | ||
| 21 | -from ._ir_types import AttrType, InputType | 21 | +from ._ir_types import InputType, OutputType |
| 22 | +from ._signature import _get_runtime_attr_spec, _validate_args_signature | ||
| 22 | from .base import EagerOpExecutionContext | 23 | from .base import EagerOpExecutionContext |
| 23 | from .bootstrap import get_registered_op_impls, load_custom_op_plugins | 24 | from .bootstrap import get_registered_op_impls, load_custom_op_plugins |
| 24 | -from .context import _execute_ctx_scope | 25 | +from .context import _declare_launch_args_ctx_scope, _execute_ctx_scope |
| 25 | -from .registry import get_registered_op_impl_by_descriptor_key | 26 | +from .registry import ( |
| 27 | + INTERFACE_ANNOTATED_ARGS, | ||
| 28 | + INTERFACE_EAGER_EXECUTE, | ||
| 29 | + get_registered_op_impl_by_descriptor_key, | ||
| 30 | +) | ||
| 26 | 31 | ||
| 27 | 32 | ||
| 28 | 33 | ||
| @@ -35,21 +40,6 @@ class _OpImplHolder: | |||
| 35 | _HOLDER_LOCK = threading.RLock() | 40 | _HOLDER_LOCK = threading.RLock() |
| 36 | _OP_IMPL_HOLDERS: Dict[str, _OpImplHolder] = {} | 41 | _OP_IMPL_HOLDERS: Dict[str, _OpImplHolder] = {} |
| 37 | 42 | ||
| 38 | -_RUNTIME_ATTR_GETTERS = { | ||
| 39 | - AttrType.INT: "get_int", | ||
| 40 | - AttrType.FLOAT: "get_float", | ||
| 41 | - AttrType.BOOL: "get_bool", | ||
| 42 | - AttrType.STRING: "get_str", | ||
| 43 | - AttrType.DATA_TYPE: "get_data_type", | ||
| 44 | - AttrType.TENSOR: "get_tensor", | ||
| 45 | - AttrType.LIST_INT: "get_list_int", | ||
| 46 | - AttrType.LIST_FLOAT: "get_list_float", | ||
| 47 | - AttrType.LIST_BOOL: "get_list_bool", | ||
| 48 | - AttrType.LIST_STRING: "get_list_str", | ||
| 49 | - AttrType.LIST_DATA_TYPE: "get_list_data_type", | ||
| 50 | - AttrType.LIST_LIST_INT: "get_list_list_int", | ||
| 51 | -} | ||
| 52 | - | ||
| 53 | 43 | ||
| 54 | def load_and_get_op_impl_descriptors() -> list: | 44 | def load_and_get_op_impl_descriptors() -> list: |
| 55 | load_custom_op_plugins() | 45 | load_custom_op_plugins() |
| @@ -64,13 +54,17 @@ def _get_holder(instance_id: str) -> _OpImplHolder: | |||
| 64 | return holder | 54 | return holder |
| 65 | 55 | ||
| 66 | 56 | ||
| 67 | -def _get_eager_execute_op(instance_id: str) -> object: | 57 | +def _get_eager_execute_holder(instance_id: str) -> _OpImplHolder: |
| 68 | - instance = _get_holder(instance_id).instance | 58 | + holder = _get_holder(instance_id) |
| 69 | - if not callable(getattr(instance, "execute", None)): | 59 | + if not callable(getattr(holder.instance, "execute", None)): |
| 70 | raise TypeError( | 60 | raise TypeError( |
| 71 | f"python op impl does not implement callable execute: {instance_id}" | 61 | f"python op impl does not implement callable execute: {instance_id}" |
| 72 | ) | 62 | ) |
| 73 | - return instance | 63 | + return holder |
| 64 | + | ||
| 65 | + | ||
| 66 | +def _get_eager_execute_op(instance_id: str) -> object: | ||
| 67 | + return _get_eager_execute_holder(instance_id).instance | ||
| 74 | 68 | ||
| 75 | 69 | ||
| 76 | def create_op_impl_holder(instance_id: str, descriptor_key: str) -> bool: | 70 | def create_op_impl_holder(instance_id: str, descriptor_key: str) -> bool: |
| @@ -106,19 +100,63 @@ def _is_legacy_execute(method) -> bool: | |||
| 106 | ) | 100 | ) |
| 107 | 101 | ||
| 108 | 102 | ||
| 109 | -def _build_execute_inputs(ctx: EagerOpExecutionContext, ir_inputs: list) -> list: | 103 | +def _get_callback_for_signature(cls, method_name: str): |
| 104 | + method = inspect.getattr_static(cls, method_name) | ||
| 105 | + if isinstance(method, staticmethod): | ||
| 106 | + return method.__func__ | ||
| 107 | + if isinstance(method, classmethod): | ||
| 108 | + return method.__get__(None, cls) | ||
| 109 | + if inspect.isfunction(method): | ||
| 110 | + return method.__get__(object(), cls) | ||
| 111 | + return getattr(cls, method_name) | ||
| 112 | + | ||
| 113 | + | ||
| 114 | +def validate_op_impl_descriptor(descriptor_key: str, ir_meta: Optional[dict]) -> bool: | ||
| 115 | + descriptor = get_registered_op_impl_by_descriptor_key(descriptor_key) | ||
| 116 | + if descriptor is None: | ||
| 117 | + raise KeyError(f"python op impl descriptor_key not found: {descriptor_key}") | ||
| 118 | + | ||
| 119 | + if INTERFACE_EAGER_EXECUTE in descriptor.interfaces: | ||
| 120 | + method = _get_callback_for_signature(descriptor.cls, "execute") | ||
| 121 | + if not _is_legacy_execute(method): | ||
| 122 | + if ir_meta is None: | ||
| 123 | + raise RuntimeError( | ||
| 124 | + "canonical IR not found for schema-bound execute: " | ||
| 125 | + f"{descriptor.op_type}" | ||
| 126 | + ) | ||
| 127 | + _validate_args_signature(method, ir_meta, descriptor, method_name="execute") | ||
| 128 | + | ||
| 129 | + if INTERFACE_ANNOTATED_ARGS in descriptor.interfaces: | ||
| 130 | + if ir_meta is None: | ||
| 131 | + raise RuntimeError( | ||
| 132 | + "canonical IR not found for schema-bound declare_launch_args" | ||
| 133 | + ) | ||
| 134 | + method = _get_callback_for_signature(descriptor.cls, "declare_launch_args") | ||
| 135 | + _validate_args_signature( | ||
| 136 | + method, ir_meta, descriptor, method_name="declare_launch_args" | ||
| 137 | + ) | ||
| 138 | + return True | ||
| 139 | + | ||
| 140 | + | ||
| 141 | +def _build_inputs( | ||
| 142 | + ir_inputs: list, | ||
| 143 | + get_required_input, | ||
| 144 | + get_optional_input, | ||
| 145 | + get_dynamic_input_num, | ||
| 146 | + get_dynamic_input, | ||
| 147 | +) -> list: | ||
| 110 | args = [] | 148 | args = [] |
| 111 | for ir_index, item in enumerate(ir_inputs): | 149 | for ir_index, item in enumerate(ir_inputs): |
| 112 | kind = item["kind"] | 150 | kind = item["kind"] |
| 113 | if kind == InputType.REQUIRED: | 151 | if kind == InputType.REQUIRED: |
| 114 | - args.append(ctx.get_required_input_tensor(ir_index)) | 152 | + args.append(get_required_input(ir_index)) |
| 115 | elif kind == InputType.OPTIONAL: | 153 | elif kind == InputType.OPTIONAL: |
| 116 | - args.append(ctx.get_optional_input_tensor(ir_index)) | 154 | + args.append(get_optional_input(ir_index)) |
| 117 | elif kind == InputType.DYNAMIC: | 155 | elif kind == InputType.DYNAMIC: |
| 118 | - instance_num = ctx.get_dynamic_input_num(ir_index) | 156 | + instance_num = get_dynamic_input_num(ir_index) |
| 119 | args.append( | 157 | args.append( |
| 120 | [ | 158 | [ |
| 121 | - ctx.get_dynamic_input_tensor(ir_index, relative_index) | 159 | + get_dynamic_input(ir_index, relative_index) |
| 122 | for relative_index in range(instance_num) | 160 | for relative_index in range(instance_num) |
| 123 | ] | 161 | ] |
| 124 | ) | 162 | ) |
| @@ -129,12 +167,18 @@ def _build_execute_inputs(ctx: EagerOpExecutionContext, ir_inputs: list) -> list | |||
| 129 | return args | 167 | return args |
| 130 | 168 | ||
| 131 | 169 | ||
| 170 | +def _build_execute_inputs(ctx: EagerOpExecutionContext, ir_inputs: list) -> list: | ||
| 171 | + return _build_inputs( | ||
| 172 | + ir_inputs, | ||
| 173 | + ctx.get_required_input_tensor, | ||
| 174 | + ctx.get_optional_input_tensor, | ||
| 175 | + ctx.get_dynamic_input_num, | ||
| 176 | + ctx.get_dynamic_input_tensor, | ||
| 177 | + ) | ||
| 178 | + | ||
| 179 | + | ||
| 132 | def _read_runtime_attr(attrs, index: int, ir_type: str): | 180 | def _read_runtime_attr(attrs, index: int, ir_type: str): |
| 133 | - getter_name = _RUNTIME_ATTR_GETTERS.get(ir_type) | 181 | + getter_name, _ = _get_runtime_attr_spec(ir_type, index) |
| 134 | - if getter_name is None: | ||
| 135 | - raise ValueError( | ||
| 136 | - f"unsupported custom op runtime attr type: {ir_type}, attr index: {index}" | ||
| 137 | - ) | ||
| 138 | return getattr(attrs, getter_name)(index) | 182 | return getattr(attrs, getter_name)(index) |
| 139 | 183 | ||
| 140 | 184 | ||
| @@ -148,13 +192,53 @@ def _build_execute_attrs(ctx: EagerOpExecutionContext, ir_attrs: list) -> dict: | |||
| 148 | } | 192 | } |
| 149 | 193 | ||
| 150 | 194 | ||
| 195 | +def _build_declare_inputs(ctx, ir_inputs: list) -> list: | ||
| 196 | + return _build_inputs( | ||
| 197 | + ir_inputs, | ||
| 198 | + ctx._get_required_input_tensor, | ||
| 199 | + ctx._get_optional_input_tensor, | ||
| 200 | + ctx._get_dynamic_input_num, | ||
| 201 | + ctx._get_dynamic_input_tensor, | ||
| 202 | + ) | ||
| 203 | + | ||
| 204 | + | ||
| 205 | +def _build_declare_outputs(ctx, ir_outputs: list) -> list: | ||
| 206 | + args = [] | ||
| 207 | + for ir_index, item in enumerate(ir_outputs): | ||
| 208 | + kind = item["kind"] | ||
| 209 | + if kind == OutputType.REQUIRED: | ||
| 210 | + args.append(ctx._get_required_output_tensor(ir_index)) | ||
| 211 | + elif kind == OutputType.DYNAMIC: | ||
| 212 | + instance_num = ctx._get_dynamic_output_num(ir_index) | ||
| 213 | + args.append( | ||
| 214 | + [ | ||
| 215 | + ctx._get_dynamic_output_tensor(ir_index, relative_index) | ||
| 216 | + for relative_index in range(instance_num) | ||
| 217 | + ] | ||
| 218 | + ) | ||
| 219 | + else: | ||
| 220 | + raise ValueError(f"unsupported custom op IR output kind: {kind}") | ||
| 221 | + return args | ||
| 222 | + | ||
| 223 | + | ||
| 224 | +def _build_declare_attrs(ctx, ir_attrs: list) -> dict: | ||
| 225 | + if not ir_attrs: | ||
| 226 | + return {} | ||
| 227 | + attrs = ctx._get_attrs() | ||
| 228 | + return { | ||
| 229 | + item["name"]: _read_runtime_attr(attrs, index, item["type"]) | ||
| 230 | + for index, item in enumerate(ir_attrs) | ||
| 231 | + } | ||
| 232 | + | ||
| 233 | + | ||
| 151 | def call_execute( | 234 | def call_execute( |
| 152 | instance_id: str, | 235 | instance_id: str, |
| 153 | ir_meta: Optional[dict], | 236 | ir_meta: Optional[dict], |
| 154 | ctx: EagerOpExecutionContext, | 237 | ctx: EagerOpExecutionContext, |
| 155 | ) -> None: | 238 | ) -> None: |
| 156 | try: | 239 | try: |
| 157 | - custom_op = _get_eager_execute_op(instance_id) | 240 | + holder = _get_eager_execute_holder(instance_id) |
| 241 | + custom_op = holder.instance | ||
| 158 | method = custom_op.execute | 242 | method = custom_op.execute |
| 159 | if _is_legacy_execute(method): | 243 | if _is_legacy_execute(method): |
| 160 | method(ctx) | 244 | method(ctx) |
| @@ -172,6 +256,29 @@ def call_execute( | |||
| 172 | ctx._invalidate() | 256 | ctx._invalidate() |
| 173 | 257 | ||
| 174 | 258 | ||
| 259 | +def call_declare_launch_args(instance_id: str, ir_meta: Optional[dict], ctx) -> None: | ||
| 260 | + try: | ||
| 261 | + holder = _get_holder(instance_id) | ||
| 262 | + method = getattr(holder.instance, "declare_launch_args", None) | ||
| 263 | + if not callable(method): | ||
| 264 | + raise TypeError( | ||
| 265 | + f"python op impl does not implement declare_launch_args: {instance_id}" | ||
| 266 | + ) | ||
| 267 | + if ir_meta is None: | ||
| 268 | + raise RuntimeError( | ||
| 269 | + "canonical IR not found for schema-bound declare_launch_args" | ||
| 270 | + ) | ||
| 271 | + args = _build_declare_inputs(ctx, ir_meta["inputs"]) | ||
| 272 | + args.extend(_build_declare_outputs(ctx, ir_meta["outputs"])) | ||
| 273 | + kwargs = _build_declare_attrs(ctx, ir_meta["attrs"]) | ||
| 274 | + with _declare_launch_args_ctx_scope(ctx): | ||
| 275 | + result = method(*args, **kwargs) | ||
| 276 | + if result is not None: | ||
| 277 | + raise TypeError("declare_launch_args must return None") | ||
| 278 | + finally: | ||
| 279 | + ctx._invalidate() | ||
| 280 | + | ||
| 281 | + | ||
| 175 | def clear_op_impl_holders() -> None: | 282 | def clear_op_impl_holders() -> None: |
| 176 | with _HOLDER_LOCK: | 283 | with _HOLDER_LOCK: |
| 177 | _OP_IMPL_HOLDERS.clear() | 284 | _OP_IMPL_HOLDERS.clear() |
| @@ -16,7 +16,11 @@ from ge.graph.types import DataType | |||
| 16 | from ge.runtime import StorageFormat, StorageShape, Tensor | 16 | from ge.runtime import StorageFormat, StorageShape, Tensor |
| 17 | 17 | ||
| 18 | __all__: List[str] = [ | 18 | __all__: List[str] = [ |
| 19 | + "AnnotatedArgsContext", | ||
| 20 | + "AnnotatedKernelArgs", | ||
| 21 | + "AnnotatedKernelLaunchInfo", | ||
| 19 | "EagerOpExecutionContext", | 22 | "EagerOpExecutionContext", |
| 23 | + "WorkspaceAddr", | ||
| 20 | ] | 24 | ] |
| 21 | 25 | ||
| 22 | 26 | ||
| @@ -156,3 +160,54 @@ class EagerOpExecutionContext: | |||
| 156 | GE bridge only; user custom op code should not call this method. | 160 | GE bridge only; user custom op code should not call this method. |
| 157 | """ | 161 | """ |
| 158 | ... | 162 | ... |
| 163 | + | ||
| 164 | + | ||
| 165 | +class WorkspaceAddr: | ||
| 166 | + """Borrowed workspace address allocated by ``AnnotatedArgsContext``.""" | ||
| 167 | + | ||
| 168 | + | ||
| 169 | + def index(self) -> int: ... | ||
| 170 | + | ||
| 171 | + | ||
| 172 | + def addr(self) -> int: ... | ||
| 173 | + | ||
| 174 | + | ||
| 175 | +class AnnotatedKernelLaunchInfo: | ||
| 176 | + """Owned kernel launch metadata used by ``AnnotatedArgsContext.add_launch``.""" | ||
| 177 | + | ||
| 178 | + def __init__( | ||
| 179 | + self, | ||
| 180 | + *, | ||
| 181 | + kernel_name: str, | ||
| 182 | + kernel_bin: bytes, | ||
| 183 | + block_dim: int, | ||
| 184 | + stream_id: int, | ||
| 185 | + ) -> None: ... | ||
| 186 | + | ||
| 187 | + | ||
| 188 | +class AnnotatedKernelArgs: | ||
| 189 | + """Borrowed builder for one annotated kernel launch's argument sequence.""" | ||
| 190 | + | ||
| 191 | + def append_input(self, instance_index: int, tensor: Tensor) -> None: ... | ||
| 192 | + | ||
| 193 | + def append_output(self, instance_index: int, tensor: Tensor) -> None: ... | ||
| 194 | + | ||
| 195 | + def append_workspace(self, workspace: WorkspaceAddr) -> None: ... | ||
| 196 | + | ||
| 197 | + def append_scalar(self, value: int) -> None: ... | ||
| 198 | + | ||
| 199 | + | ||
| 200 | +class AnnotatedArgsContext: | ||
| 201 | + """Borrowed declaration context available only in ``declare_launch_args``.""" | ||
| 202 | + | ||
| 203 | + def malloc_workspace(self, size: int) -> WorkspaceAddr: ... | ||
| 204 | + | ||
| 205 | + def get_stream_id(self) -> int: ... | ||
| 206 | + | ||
| 207 | + def create_kernel_args(self) -> AnnotatedKernelArgs: ... | ||
| 208 | + | ||
| 209 | + def add_launch( | ||
| 210 | + self, | ||
| 211 | + launch_info: AnnotatedKernelLaunchInfo, | ||
| 212 | + args: AnnotatedKernelArgs, | ||
| 213 | + ) -> None: ... | ||
| @@ -15,7 +15,11 @@ | |||
| 15 | from __future__ import annotations | 15 | from __future__ import annotations |
| 16 | 16 | ||
| 17 | __all__ = [ | 17 | __all__ = [ |
| 18 | + "AnnotatedArgsContext", | ||
| 19 | + "AnnotatedKernelArgs", | ||
| 20 | + "AnnotatedKernelLaunchInfo", | ||
| 18 | "EagerOpExecutionContext", | 21 | "EagerOpExecutionContext", |
| 22 | + "WorkspaceAddr", | ||
| 19 | ] | 23 | ] |
| 20 | 24 | ||
| 21 | from importlib import import_module | 25 | from importlib import import_module |
| @@ -35,3 +39,7 @@ def _load_native_module(): | |||
| 35 | _native = _load_native_module() | 39 | _native = _load_native_module() |
| 36 | 40 | ||
| 37 | EagerOpExecutionContext = _native.EagerOpExecutionContext | 41 | EagerOpExecutionContext = _native.EagerOpExecutionContext |
| 42 | +AnnotatedArgsContext = _native.AnnotatedArgsContext | ||
| 43 | +AnnotatedKernelArgs = _native.AnnotatedKernelArgs | ||
| 44 | +AnnotatedKernelLaunchInfo = _native.AnnotatedKernelLaunchInfo | ||
| 45 | +WorkspaceAddr = _native.WorkspaceAddr | ||
| @@ -0,0 +1,271 @@ | |||
| 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 | +"""Schema-bound callback signature validation and runtime attribute metadata.""" | ||
| 14 | + | ||
| 15 | +import inspect | ||
| 16 | +import types | ||
| 17 | +import typing | ||
| 18 | + | ||
| 19 | +from ge.graph import DataType | ||
| 20 | +from ge.runtime import Tensor | ||
| 21 | + | ||
| 22 | +from ._ir_types import AttrType, InputType, OutputType | ||
| 23 | + | ||
| 24 | + | ||
| 25 | +_POSITIONAL_KINDS = ( | ||
| 26 | + inspect.Parameter.POSITIONAL_ONLY, | ||
| 27 | + inspect.Parameter.POSITIONAL_OR_KEYWORD, | ||
| 28 | +) | ||
| 29 | +_GET_ORIGIN = getattr( | ||
| 30 | + typing, "get_origin", lambda value: getattr(value, "__origin__", None) | ||
| 31 | +) | ||
| 32 | +_GET_ARGS = getattr(typing, "get_args", lambda value: getattr(value, "__args__", ())) | ||
| 33 | +_UNION_ORIGINS = {typing.Union} | ||
| 34 | +_PEP604_UNION = getattr(types, "UnionType", None) | ||
| 35 | +if _PEP604_UNION is not None: | ||
| 36 | + _UNION_ORIGINS.add(_PEP604_UNION) | ||
| 37 | + | ||
| 38 | +_RUNTIME_ATTR_SPECS = { | ||
| 39 | + AttrType.INT: ("get_int", int), | ||
| 40 | + AttrType.FLOAT: ("get_float", float), | ||
| 41 | + AttrType.BOOL: ("get_bool", bool), | ||
| 42 | + AttrType.STRING: ("get_str", str), | ||
| 43 | + AttrType.DATA_TYPE: ("get_data_type", DataType), | ||
| 44 | + AttrType.TENSOR: ("get_tensor", Tensor), | ||
| 45 | + AttrType.LIST_INT: ("get_list_int", list[int]), | ||
| 46 | + AttrType.LIST_FLOAT: ("get_list_float", list[float]), | ||
| 47 | + AttrType.LIST_BOOL: ("get_list_bool", list[bool]), | ||
| 48 | + AttrType.LIST_STRING: ("get_list_str", list[str]), | ||
| 49 | + AttrType.LIST_DATA_TYPE: ("get_list_data_type", list[DataType]), | ||
| 50 | + AttrType.LIST_LIST_INT: ("get_list_list_int", list[list[int]]), | ||
| 51 | +} | ||
| 52 | + | ||
| 53 | + | ||
| 54 | +def _signature_error( | ||
| 55 | + descriptor, method_name: str, expected: str, actual: str | ||
| 56 | +) -> TypeError: | ||
| 57 | + return TypeError( | ||
| 58 | + f"invalid {method_name} signature for op type " | ||
| 59 | + f"{descriptor.op_type}, descriptor key {descriptor.descriptor_key}, " | ||
| 60 | + f"method {method_name}: expected {expected}, actual {actual}" | ||
| 61 | + ) | ||
| 62 | + | ||
| 63 | + | ||
| 64 | +def _normalize_annotation(annotation): | ||
| 65 | + if annotation is None: | ||
| 66 | + return type(None) | ||
| 67 | + origin = _GET_ORIGIN(annotation) | ||
| 68 | + args = _GET_ARGS(annotation) | ||
| 69 | + if origin is list: | ||
| 70 | + return ("list", tuple(_normalize_annotation(arg) for arg in args)) | ||
| 71 | + if origin in _UNION_ORIGINS: | ||
| 72 | + return ("union", frozenset(_normalize_annotation(arg) for arg in args)) | ||
| 73 | + return annotation | ||
| 74 | + | ||
| 75 | + | ||
| 76 | +def _get_expected_input_annotation(kind: int): | ||
| 77 | + if kind == InputType.REQUIRED: | ||
| 78 | + return Tensor | ||
| 79 | + if kind == InputType.OPTIONAL: | ||
| 80 | + return typing.Optional[Tensor] | ||
| 81 | + if kind == InputType.DYNAMIC: | ||
| 82 | + return list[Tensor] | ||
| 83 | + raise ValueError(f"unsupported custom op IR input kind: {kind}") | ||
| 84 | + | ||
| 85 | + | ||
| 86 | +def _get_expected_output_annotation(kind: int): | ||
| 87 | + if kind == OutputType.REQUIRED: | ||
| 88 | + return Tensor | ||
| 89 | + if kind == OutputType.DYNAMIC: | ||
| 90 | + return list[Tensor] | ||
| 91 | + raise ValueError(f"unsupported custom op IR output kind: {kind}") | ||
| 92 | + | ||
| 93 | + | ||
| 94 | +def _get_runtime_attr_spec(ir_type: str, index: int): | ||
| 95 | + spec = _RUNTIME_ATTR_SPECS.get(ir_type) | ||
| 96 | + if spec is None: | ||
| 97 | + raise ValueError( | ||
| 98 | + f"unsupported custom op runtime attr type: {ir_type}, attr index: {index}" | ||
| 99 | + ) | ||
| 100 | + return spec | ||
| 101 | + | ||
| 102 | + | ||
| 103 | +def _get_type_hints(method, descriptor, method_name: str) -> dict: | ||
| 104 | + try: | ||
| 105 | + if getattr(method, "__no_type_check__", False): | ||
| 106 | + return {} | ||
| 107 | + if method_name == "execute": | ||
| 108 | + target = getattr(method, "__func__", method) | ||
| 109 | + annotations = dict(getattr(target, "__annotations__", {})) | ||
| 110 | + annotations.pop("return", None) | ||
| 111 | + if not annotations: | ||
| 112 | + return {} | ||
| 113 | + | ||
| 114 | + def annotation_source(): | ||
| 115 | + pass | ||
| 116 | + | ||
| 117 | + annotation_source.__annotations__ = annotations | ||
| 118 | + return typing.get_type_hints( | ||
| 119 | + annotation_source, | ||
| 120 | + globalns=getattr(target, "__globals__", None), | ||
| 121 | + ) | ||
| 122 | + return typing.get_type_hints(method) | ||
| 123 | + except (NameError, TypeError, AttributeError) as exc: | ||
| 124 | + raise _signature_error( | ||
| 125 | + descriptor, | ||
| 126 | + method_name, | ||
| 127 | + "resolvable type annotations", | ||
| 128 | + f"type hint resolution failed: {exc}", | ||
| 129 | + ) from exc | ||
| 130 | + | ||
| 131 | + | ||
| 132 | +def _validate_annotation( | ||
| 133 | + parameter, | ||
| 134 | + expected, | ||
| 135 | + hints: dict, | ||
| 136 | + descriptor, | ||
| 137 | + method_name: str, | ||
| 138 | + position: str, | ||
| 139 | +) -> None: | ||
| 140 | + if parameter.annotation is inspect.Parameter.empty: | ||
| 141 | + return | ||
| 142 | + actual = hints.get(parameter.name, parameter.annotation) | ||
| 143 | + if _normalize_annotation(actual) != _normalize_annotation(expected): | ||
| 144 | + raise _signature_error( | ||
| 145 | + descriptor, | ||
| 146 | + method_name, | ||
| 147 | + f"{position} annotation {_normalize_annotation(expected)!r}", | ||
| 148 | + f"{_normalize_annotation(actual)!r}", | ||
| 149 | + ) | ||
| 150 | + | ||
| 151 | + | ||
| 152 | +def _validate_args_signature( | ||
| 153 | + method, | ||
| 154 | + ir_meta: dict, | ||
| 155 | + descriptor, | ||
| 156 | + *, | ||
| 157 | + method_name: str = "declare_launch_args", | ||
| 158 | +) -> None: | ||
| 159 | + if method_name not in ("execute", "declare_launch_args"): | ||
| 160 | + raise ValueError(f"unsupported schema callback: {method_name}") | ||
| 161 | + signature = inspect.signature(method) | ||
| 162 | + parameters = list(signature.parameters.values()) | ||
| 163 | + for parameter in parameters: | ||
| 164 | + if parameter.kind in ( | ||
| 165 | + inspect.Parameter.VAR_POSITIONAL, | ||
| 166 | + inspect.Parameter.VAR_KEYWORD, | ||
| 167 | + ): | ||
| 168 | + raise _signature_error( | ||
| 169 | + descriptor, | ||
| 170 | + method_name, | ||
| 171 | + "no variadic parameters", | ||
| 172 | + f"variadic parameter {parameter.name}", | ||
| 173 | + ) | ||
| 174 | + | ||
| 175 | + ir_inputs = ir_meta["inputs"] | ||
| 176 | + ir_outputs = ir_meta["outputs"] if method_name == "declare_launch_args" else [] | ||
| 177 | + ir_attrs = ir_meta["attrs"] | ||
| 178 | + positional_count = len(ir_inputs) + len(ir_outputs) | ||
| 179 | + expected_count = positional_count + len(ir_attrs) | ||
| 180 | + if len(parameters) != expected_count: | ||
| 181 | + raise _signature_error( | ||
| 182 | + descriptor, | ||
| 183 | + method_name, | ||
| 184 | + f"{positional_count} positional " | ||
| 185 | + f"{'input/output' if method_name == 'declare_launch_args' else 'input'} " | ||
| 186 | + "parameters followed by " | ||
| 187 | + f"{len(ir_attrs)} keyword-only attrs", | ||
| 188 | + f"{len(parameters)} parameters", | ||
| 189 | + ) | ||
| 190 | + | ||
| 191 | + hints = _get_type_hints(method, descriptor, method_name) | ||
| 192 | + for index, item in enumerate(ir_inputs): | ||
| 193 | + parameter = parameters[index] | ||
| 194 | + if parameter.kind not in _POSITIONAL_KINDS: | ||
| 195 | + raise _signature_error( | ||
| 196 | + descriptor, | ||
| 197 | + method_name, | ||
| 198 | + f"positional input parameter at index {index}", | ||
| 199 | + f"parameter {parameter.name} kind {parameter.kind.name}", | ||
| 200 | + ) | ||
| 201 | + _validate_annotation( | ||
| 202 | + parameter, | ||
| 203 | + _get_expected_input_annotation(item["kind"]), | ||
| 204 | + hints, | ||
| 205 | + descriptor, | ||
| 206 | + method_name, | ||
| 207 | + f"input parameter at index {index}", | ||
| 208 | + ) | ||
| 209 | + | ||
| 210 | + for output_index, item in enumerate(ir_outputs): | ||
| 211 | + parameter_index = len(ir_inputs) + output_index | ||
| 212 | + parameter = parameters[parameter_index] | ||
| 213 | + if parameter.kind not in _POSITIONAL_KINDS: | ||
| 214 | + raise _signature_error( | ||
| 215 | + descriptor, | ||
| 216 | + method_name, | ||
| 217 | + f"positional output parameter at index {output_index}", | ||
| 218 | + f"parameter {parameter.name} kind {parameter.kind.name}", | ||
| 219 | + ) | ||
| 220 | + _validate_annotation( | ||
| 221 | + parameter, | ||
| 222 | + _get_expected_output_annotation(item["kind"]), | ||
| 223 | + hints, | ||
| 224 | + descriptor, | ||
| 225 | + method_name, | ||
| 226 | + f"output parameter at index {output_index}", | ||
| 227 | + ) | ||
| 228 | + | ||
| 229 | + for attr_index, item in enumerate(ir_attrs): | ||
| 230 | + parameter = parameters[positional_count + attr_index] | ||
| 231 | + if parameter.kind is not inspect.Parameter.KEYWORD_ONLY: | ||
| 232 | + raise _signature_error( | ||
| 233 | + descriptor, | ||
| 234 | + method_name, | ||
| 235 | + f"keyword-only attr parameter {item['name']}", | ||
| 236 | + f"parameter {parameter.name} kind {parameter.kind.name}", | ||
| 237 | + ) | ||
| 238 | + if parameter.name != item["name"]: | ||
| 239 | + raise _signature_error( | ||
| 240 | + descriptor, | ||
| 241 | + method_name, | ||
| 242 | + f"attr name {item['name']} at index {attr_index}", | ||
| 243 | + f"attr name {parameter.name}", | ||
| 244 | + ) | ||
| 245 | + _, expected_annotation = _get_runtime_attr_spec(item["type"], attr_index) | ||
| 246 | + _validate_annotation( | ||
| 247 | + parameter, | ||
| 248 | + expected_annotation, | ||
| 249 | + hints, | ||
| 250 | + descriptor, | ||
| 251 | + method_name, | ||
| 252 | + f"attr parameter {item['name']}", | ||
| 253 | + ) | ||
| 254 | + | ||
| 255 | + if method_name == "execute": | ||
| 256 | + return | ||
| 257 | + if signature.return_annotation is inspect.Signature.empty: | ||
| 258 | + raise _signature_error( | ||
| 259 | + descriptor, | ||
| 260 | + method_name, | ||
| 261 | + "None return annotation", | ||
| 262 | + "missing return annotation", | ||
| 263 | + ) | ||
| 264 | + return_annotation = hints.get("return", signature.return_annotation) | ||
| 265 | + if _normalize_annotation(return_annotation) is not type(None): | ||
| 266 | + raise _signature_error( | ||
| 267 | + descriptor, | ||
| 268 | + method_name, | ||
| 269 | + "None return annotation", | ||
| 270 | + repr(_normalize_annotation(return_annotation)), | ||
| 271 | + ) | ||
| @@ -12,12 +12,14 @@ | |||
| 12 | 12 | ||
| 13 | """Execution context access for schema-bound Python custom ops.""" | 13 | """Execution context access for schema-bound Python custom ops.""" |
| 14 | 14 | ||
| 15 | +from __future__ import annotations | ||
| 16 | + | ||
| 15 | from contextlib import contextmanager | 17 | from contextlib import contextmanager |
| 16 | from contextvars import ContextVar | 18 | from contextvars import ContextVar |
| 17 | from dataclasses import dataclass | 19 | from dataclasses import dataclass |
| 18 | from typing import Iterator, Optional | 20 | from typing import Iterator, Optional |
| 19 | 21 | ||
| 20 | -from ._native import EagerOpExecutionContext | 22 | +from ._native import AnnotatedArgsContext, EagerOpExecutionContext |
| 21 | 23 | ||
| 22 | 24 | ||
| 23 | 25 | ||
| @@ -51,3 +53,36 @@ def _execute_ctx_scope(ctx: EagerOpExecutionContext) -> Iterator[None]: | |||
| 51 | finally: | 53 | finally: |
| 52 | binding.active = False | 54 | binding.active = False |
| 53 | _CURRENT_EXECUTE_CONTEXT.reset(token) | 55 | _CURRENT_EXECUTE_CONTEXT.reset(token) |
| 56 | + | ||
| 57 | + | ||
| 58 | + | ||
| 59 | +class _DeclareLaunchArgsContextBinding: | ||
| 60 | + ctx: AnnotatedArgsContext | ||
| 61 | + active: bool = True | ||
| 62 | + | ||
| 63 | + | ||
| 64 | +_CURRENT_DECLARE_LAUNCH_ARGS_CONTEXT: ContextVar[ | ||
| 65 | + Optional[_DeclareLaunchArgsContextBinding] | ||
| 66 | +] = ContextVar("ge_custom_op_declare_launch_args_context", default=None) | ||
| 67 | + | ||
| 68 | + | ||
| 69 | +def get_declare_launch_args_ctx() -> AnnotatedArgsContext: | ||
| 70 | + """Return the borrowed context of the active declare_launch_args callback.""" | ||
| 71 | + | ||
| 72 | + binding = _CURRENT_DECLARE_LAUNCH_ARGS_CONTEXT.get() | ||
| 73 | + if binding is None or not binding.active: | ||
| 74 | + raise RuntimeError( | ||
| 75 | + "get_declare_launch_args_ctx() is only available inside declare_launch_args" | ||
| 76 | + ) | ||
| 77 | + return binding.ctx | ||
| 78 | + | ||
| 79 | + | ||
| 80 | + | ||
| 81 | +def _declare_launch_args_ctx_scope(ctx: AnnotatedArgsContext) -> Iterator[None]: | ||
| 82 | + binding = _DeclareLaunchArgsContextBinding(ctx=ctx) | ||
| 83 | + token = _CURRENT_DECLARE_LAUNCH_ARGS_CONTEXT.set(binding) | ||
| 84 | + try: | ||
| 85 | + yield | ||
| 86 | + finally: | ||
| 87 | + binding.active = False | ||
| 88 | + _CURRENT_DECLARE_LAUNCH_ARGS_CONTEXT.reset(token) | ||
| @@ -17,6 +17,7 @@ namespace ge { | |||
| 17 | namespace python_custom_op_native { | 17 | 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 | 21 | ||
| 21 | } // namespace python_custom_op_native | 22 | } // namespace python_custom_op_native |
| 22 | } // namespace ge | 23 | } // namespace ge |
| @@ -9,6 +9,7 @@ | |||
| 9 | */ | 9 | */ |
| 10 | 10 | ||
| 11 | 11 | ||
| 12 | + | ||
| 12 | 13 | ||
| 13 | 14 | ||
| 14 | 15 | ||
| @@ -21,6 +22,7 @@ | |||
| 21 | 22 | ||
| 22 | 23 | ||
| 23 | 24 | ||
| 25 | + | ||
| 24 | 26 | ||
| 25 | namespace ge { | 27 | namespace ge { |
| 26 | namespace python_custom_op_native { | 28 | namespace python_custom_op_native { |
| @@ -299,6 +301,253 @@ BorrowedEagerOpExecutionContext BorrowEagerOpExecutionContext(uintptr_t ctx_hand | |||
| 299 | return BorrowedEagerOpExecutionContext(reinterpret_cast<gert::EagerOpExecutionContext *>(ctx_handle)); | 301 | return BorrowedEagerOpExecutionContext(reinterpret_cast<gert::EagerOpExecutionContext *>(ctx_handle)); |
| 300 | } | 302 | } |
| 301 | 303 | ||
| 304 | +void EnsureActive(const std::shared_ptr<bool> &active) { | ||
| 305 | + if ((active == nullptr) || (!(*active))) { | ||
| 306 | + throw std::runtime_error("Borrowed native object has expired"); | ||
| 307 | + } | ||
| 308 | +} | ||
| 309 | + | ||
| 310 | +class NativeWorkspaceAddr { | ||
| 311 | + public: | ||
| 312 | + NativeWorkspaceAddr(gert::WorkspaceAddr workspace, std::shared_ptr<bool> active) | ||
| 313 | + : workspace_(workspace), active_(std::move(active)) {} | ||
| 314 | + | ||
| 315 | + uint32_t GetIndex() const { | ||
| 316 | + return Get().index; | ||
| 317 | + } | ||
| 318 | + | ||
| 319 | + uintptr_t GetAddr() const { | ||
| 320 | + return reinterpret_cast<uintptr_t>(Get().addr); | ||
| 321 | + } | ||
| 322 | + | ||
| 323 | + const gert::WorkspaceAddr &Get() const { | ||
| 324 | + EnsureActive(active_); | ||
| 325 | + return workspace_; | ||
| 326 | + } | ||
| 327 | + | ||
| 328 | + private: | ||
| 329 | + gert::WorkspaceAddr workspace_{}; | ||
| 330 | + std::shared_ptr<bool> active_; | ||
| 331 | +}; | ||
| 332 | + | ||
| 333 | +class NativeAnnotatedKernelLaunchInfo { | ||
| 334 | + public: | ||
| 335 | + NativeAnnotatedKernelLaunchInfo(std::string kernel_name, const py::bytes &kernel_bin, uint32_t block_dim, | ||
| 336 | + uint32_t stream_id) | ||
| 337 | + : kernel_name_(std::move(kernel_name)), block_dim_(block_dim), stream_id_(stream_id) { | ||
| 338 | + const std::string kernel_bin_str = kernel_bin; | ||
| 339 | + kernel_bin_.assign(kernel_bin_str.cbegin(), kernel_bin_str.cend()); | ||
| 340 | + if (kernel_name_.empty()) { | ||
| 341 | + throw std::invalid_argument("kernel_name must not be empty"); | ||
| 342 | + } | ||
| 343 | + if (kernel_bin_.empty()) { | ||
| 344 | + throw std::invalid_argument("kernel_bin must not be empty"); | ||
| 345 | + } | ||
| 346 | + if (block_dim_ == 0U) { | ||
| 347 | + throw std::invalid_argument("block_dim must be greater than zero"); | ||
| 348 | + } | ||
| 349 | + } | ||
| 350 | + | ||
| 351 | + gert::AnnotatedKernelLaunchInfo GetView() const { | ||
| 352 | + return gert::AnnotatedKernelLaunchInfo{kernel_name_.c_str(), kernel_bin_.data(), kernel_bin_.size(), block_dim_, | ||
| 353 | + stream_id_}; | ||
| 354 | + } | ||
| 355 | + | ||
| 356 | + uint32_t GetStreamId() const { | ||
| 357 | + return stream_id_; | ||
| 358 | + } | ||
| 359 | + | ||
| 360 | + private: | ||
| 361 | + std::string kernel_name_; | ||
| 362 | + std::vector<uint8_t> kernel_bin_; | ||
| 363 | + uint32_t block_dim_{0U}; | ||
| 364 | + uint32_t stream_id_{0U}; | ||
| 365 | +}; | ||
| 366 | + | ||
| 367 | +class NativeAnnotatedKernelArgs { | ||
| 368 | + public: | ||
| 369 | + NativeAnnotatedKernelArgs(gert::AnnotatedArgsContext *ctx, std::shared_ptr<bool> active) | ||
| 370 | + : ctx_(ctx), active_(std::move(active)) {} | ||
| 371 | + NativeAnnotatedKernelArgs(const NativeAnnotatedKernelArgs &) = delete; | ||
| 372 | + NativeAnnotatedKernelArgs &operator=(const NativeAnnotatedKernelArgs &) = delete; | ||
| 373 | + NativeAnnotatedKernelArgs(NativeAnnotatedKernelArgs &&) = default; | ||
| 374 | + NativeAnnotatedKernelArgs &operator=(NativeAnnotatedKernelArgs &&) = default; | ||
| 375 | + | ||
| 376 | + void AppendInput(uint32_t instance_index, const runtime_native::NativeTensor &tensor) { | ||
| 377 | + const auto input_num = GetContext()->GetComputeNodeInputNum(); | ||
| 378 | + if (instance_index >= input_num) { | ||
| 379 | + throw std::out_of_range("input instance index " + std::to_string(instance_index) + | ||
| 380 | + " is out of range for input count " + std::to_string(input_num)); | ||
| 381 | + } | ||
| 382 | + Append(gert::InputAddr{instance_index, tensor.Get()->GetAddr()}, "append input"); | ||
| 383 | + } | ||
| 384 | + | ||
| 385 | + void AppendOutput(uint32_t instance_index, const runtime_native::NativeTensor &tensor) { | ||
| 386 | + const auto output_num = GetContext()->GetComputeNodeOutputNum(); | ||
| 387 | + if (instance_index >= output_num) { | ||
| 388 | + throw std::out_of_range("output instance index " + std::to_string(instance_index) + | ||
| 389 | + " is out of range for output count " + std::to_string(output_num)); | ||
| 390 | + } | ||
| 391 | + Append(gert::OutputAddr{instance_index, tensor.Get()->GetAddr()}, "append output"); | ||
| 392 | + } | ||
| 393 | + | ||
| 394 | + void AppendWorkspace(const NativeWorkspaceAddr &workspace) { | ||
| 395 | + Append(workspace.Get(), "append workspace"); | ||
| 396 | + } | ||
| 397 | + | ||
| 398 | + void AppendScalar(uint64_t value) { | ||
| 399 | + Append(value, "append scalar"); | ||
| 400 | + } | ||
| 401 | + | ||
| 402 | + gert::AnnotatedKernelArgs Take() { | ||
| 403 | + (void)GetContext(); | ||
| 404 | + consumed_ = true; | ||
| 405 | + return std::move(args_); | ||
| 406 | + } | ||
| 407 | + | ||
| 408 | + private: | ||
| 409 | + gert::AnnotatedArgsContext *GetContext() const { | ||
| 410 | + EnsureActive(active_); | ||
| 411 | + if ((ctx_ == nullptr) || consumed_) { | ||
| 412 | + throw std::runtime_error("AnnotatedKernelArgs has been consumed"); | ||
| 413 | + } | ||
| 414 | + return ctx_; | ||
| 415 | + } | ||
| 416 | + | ||
| 417 | + template <typename T> | ||
| 418 | + void Append(const T &arg, const char *operation) { | ||
| 419 | + (void)GetContext(); | ||
| 420 | + if (args_.AppendArg(arg) != GRAPH_SUCCESS) { | ||
| 421 | + throw std::runtime_error(std::string("Failed to ") + operation); | ||
| 422 | + } | ||
| 423 | + } | ||
| 424 | + | ||
| 425 | + gert::AnnotatedArgsContext *ctx_{nullptr}; | ||
| 426 | + std::shared_ptr<bool> active_; | ||
| 427 | + gert::AnnotatedKernelArgs args_; | ||
| 428 | + bool consumed_{false}; | ||
| 429 | +}; | ||
| 430 | + | ||
| 431 | +class BorrowedAnnotatedArgsContext { | ||
| 432 | + public: | ||
| 433 | + explicit BorrowedAnnotatedArgsContext(gert::AnnotatedArgsContext *ctx) | ||
| 434 | + : ctx_(ctx), active_(std::make_shared<bool>(true)) {} | ||
| 435 | + | ||
| 436 | + py::object GetRequiredInputTensor(size_t ir_index) const { | ||
| 437 | + return CastRequiredTensor(Get()->GetRequiredInputTensor(ir_index), "Failed to get required input tensor"); | ||
| 438 | + } | ||
| 439 | + | ||
| 440 | + py::object GetOptionalInputTensor(size_t ir_index) const { | ||
| 441 | + const auto *tensor = Get()->GetOptionalInputTensor(ir_index); | ||
| 442 | + return (tensor == nullptr) ? py::none() : CastTensor(tensor); | ||
| 443 | + } | ||
| 444 | + | ||
| 445 | + size_t GetDynamicInputNum(size_t ir_index) const { | ||
| 446 | + const auto *instance_info = Get()->GetIrInputInstanceInfo(ir_index); | ||
| 447 | + if (instance_info == nullptr) { | ||
| 448 | + throw std::runtime_error("Failed to get dynamic input instance info"); | ||
| 449 | + } | ||
| 450 | + return instance_info->GetInstanceNum(); | ||
| 451 | + } | ||
| 452 | + | ||
| 453 | + py::object GetDynamicInputTensor(size_t ir_index, size_t relative_index) const { | ||
| 454 | + return CastRequiredTensor(Get()->GetDynamicInputTensor(ir_index, relative_index), | ||
| 455 | + "Failed to get dynamic input tensor"); | ||
| 456 | + } | ||
| 457 | + | ||
| 458 | + py::object GetRequiredOutputTensor(size_t ir_index) const { | ||
| 459 | + return CastRequiredTensor(Get()->GetRequiredOutputTensor(ir_index), "Failed to get required output tensor"); | ||
| 460 | + } | ||
| 461 | + | ||
| 462 | + size_t GetDynamicOutputNum(size_t ir_index) const { | ||
| 463 | + const auto *instance_info = Get()->GetIrOutputInstanceInfo(ir_index); | ||
| 464 | + if (instance_info == nullptr) { | ||
| 465 | + throw std::runtime_error("Failed to get dynamic output instance info"); | ||
| 466 | + } | ||
| 467 | + return instance_info->GetInstanceNum(); | ||
| 468 | + } | ||
| 469 | + | ||
| 470 | + py::object GetDynamicOutputTensor(size_t ir_index, size_t relative_index) const { | ||
| 471 | + return CastRequiredTensor(Get()->GetDynamicOutputTensor(ir_index, relative_index), | ||
| 472 | + "Failed to get dynamic output tensor"); | ||
| 473 | + } | ||
| 474 | + | ||
| 475 | + py::object GetAttrs() const { | ||
| 476 | + const auto *attrs = Get()->GetAttrs(); | ||
| 477 | + if (attrs == nullptr) { | ||
| 478 | + throw std::runtime_error("Failed to get runtime attrs"); | ||
| 479 | + } | ||
| 480 | + return py::cast(BorrowedRuntimeAttrs(attrs, active_)); | ||
| 481 | + } | ||
| 482 | + | ||
| 483 | + NativeWorkspaceAddr MallocWorkspace(size_t size) const { | ||
| 484 | + if (size == 0U) { | ||
| 485 | + throw std::invalid_argument("workspace size must be greater than zero"); | ||
| 486 | + } | ||
| 487 | + const auto workspace = Get()->MallocWorkSpace(size); | ||
| 488 | + if (workspace.addr == nullptr) { | ||
| 489 | + throw std::runtime_error("Failed to malloc workspace"); | ||
| 490 | + } | ||
| 491 | + return NativeWorkspaceAddr(workspace, active_); | ||
| 492 | + } | ||
| 493 | + | ||
| 494 | + uint32_t GetStreamId() const { | ||
| 495 | + return Get()->GetStreamId(); | ||
| 496 | + } | ||
| 497 | + | ||
| 498 | + NativeAnnotatedKernelArgs CreateKernelArgs() const { | ||
| 499 | + return NativeAnnotatedKernelArgs(Get(), active_); | ||
| 500 | + } | ||
| 501 | + | ||
| 502 | + void AddLaunch(const NativeAnnotatedKernelLaunchInfo &launch_info, NativeAnnotatedKernelArgs &args) const { | ||
| 503 | + auto *ctx = Get(); | ||
| 504 | + if (launch_info.GetStreamId() != ctx->GetStreamId()) { | ||
| 505 | + throw std::invalid_argument("launch stream_id does not match current context stream_id"); | ||
| 506 | + } | ||
| 507 | + auto native_args = args.Take(); | ||
| 508 | + if (ctx->AddLaunch(launch_info.GetView(), std::move(native_args)) != GRAPH_SUCCESS) { | ||
| 509 | + throw std::runtime_error("Failed to add annotated kernel launch"); | ||
| 510 | + } | ||
| 511 | + } | ||
| 512 | + | ||
| 513 | + void Invalidate() { | ||
| 514 | + if (active_ != nullptr) { | ||
| 515 | + *active_ = false; | ||
| 516 | + } | ||
| 517 | + ctx_ = nullptr; | ||
| 518 | + } | ||
| 519 | + | ||
| 520 | + private: | ||
| 521 | + gert::AnnotatedArgsContext *Get() const { | ||
| 522 | + EnsureActive(active_); | ||
| 523 | + if (ctx_ == nullptr) { | ||
| 524 | + throw std::runtime_error("Borrowed native object has expired"); | ||
| 525 | + } | ||
| 526 | + return ctx_; | ||
| 527 | + } | ||
| 528 | + | ||
| 529 | + py::object CastTensor(const gert::Tensor *tensor) const { | ||
| 530 | + return py::cast(runtime_native::NativeTensor::Borrow(const_cast<gert::Tensor *>(tensor), active_)); | ||
| 531 | + } | ||
| 532 | + | ||
| 533 | + py::object CastRequiredTensor(const gert::Tensor *tensor, const char *message) const { | ||
| 534 | + if (tensor == nullptr) { | ||
| 535 | + throw std::runtime_error(message); | ||
| 536 | + } | ||
| 537 | + return CastTensor(tensor); | ||
| 538 | + } | ||
| 539 | + | ||
| 540 | + gert::AnnotatedArgsContext *ctx_{nullptr}; | ||
| 541 | + std::shared_ptr<bool> active_; | ||
| 542 | +}; | ||
| 543 | + | ||
| 544 | +BorrowedAnnotatedArgsContext BorrowAnnotatedArgsContext(uintptr_t ctx_handle) { | ||
| 545 | + if (ctx_handle == 0U) { | ||
| 546 | + throw std::invalid_argument("ctx_handle is null"); | ||
| 547 | + } | ||
| 548 | + return BorrowedAnnotatedArgsContext(reinterpret_cast<gert::AnnotatedArgsContext *>(ctx_handle)); | ||
| 549 | +} | ||
| 550 | + | ||
| 302 | } // namespace | 551 | } // namespace |
| 303 | 552 | ||
| 304 | void BindEagerOpExecutionContext(py::module_ &m) { | 553 | void BindEagerOpExecutionContext(py::module_ &m) { |
| @@ -338,5 +587,39 @@ void BindEagerOpExecutionContext(py::module_ &m) { | |||
| 338 | m.def("_borrow_eager_op_execution_context", &BorrowEagerOpExecutionContext, py::arg("ctx_handle")); | 587 | m.def("_borrow_eager_op_execution_context", &BorrowEagerOpExecutionContext, py::arg("ctx_handle")); |
| 339 | } | 588 | } |
| 340 | 589 | ||
| 590 | +void BindAnnotatedArgsContext(py::module_ &m) { | ||
| 591 | + py::class_<NativeWorkspaceAddr>(m, "WorkspaceAddr", "Borrowed annotated workspace address") | ||
| 592 | + .def_property_readonly("index", &NativeWorkspaceAddr::GetIndex) | ||
| 593 | + .def_property_readonly("addr", &NativeWorkspaceAddr::GetAddr); | ||
| 594 | + | ||
| 595 | + py::class_<NativeAnnotatedKernelLaunchInfo>(m, "AnnotatedKernelLaunchInfo", "Owned kernel launch metadata") | ||
| 596 | + .def(py::init<std::string, const py::bytes &, uint32_t, uint32_t>(), py::kw_only(), py::arg("kernel_name"), | ||
| 597 | + py::arg("kernel_bin"), py::arg("block_dim"), py::arg("stream_id")); | ||
| 598 | + | ||
| 599 | + py::class_<NativeAnnotatedKernelArgs>(m, "AnnotatedKernelArgs", "Borrowed annotated kernel args builder") | ||
| 600 | + .def("append_input", &NativeAnnotatedKernelArgs::AppendInput, py::arg("instance_index"), py::arg("tensor")) | ||
| 601 | + .def("append_output", &NativeAnnotatedKernelArgs::AppendOutput, py::arg("instance_index"), py::arg("tensor")) | ||
| 602 | + .def("append_workspace", &NativeAnnotatedKernelArgs::AppendWorkspace, py::arg("workspace")) | ||
| 603 | + .def("append_scalar", &NativeAnnotatedKernelArgs::AppendScalar, py::arg("value")); | ||
| 604 | + | ||
| 605 | + py::class_<BorrowedAnnotatedArgsContext>(m, "AnnotatedArgsContext", "Borrowed view of gert::AnnotatedArgsContext") | ||
| 606 | + .def("_get_required_input_tensor", &BorrowedAnnotatedArgsContext::GetRequiredInputTensor, py::arg("ir_index")) | ||
| 607 | + .def("_get_optional_input_tensor", &BorrowedAnnotatedArgsContext::GetOptionalInputTensor, py::arg("ir_index")) | ||
| 608 | + .def("_get_dynamic_input_num", &BorrowedAnnotatedArgsContext::GetDynamicInputNum, py::arg("ir_index")) | ||
| 609 | + .def("_get_dynamic_input_tensor", &BorrowedAnnotatedArgsContext::GetDynamicInputTensor, py::arg("ir_index"), | ||
| 610 | + py::arg("relative_index")) | ||
| 611 | + .def("_get_required_output_tensor", &BorrowedAnnotatedArgsContext::GetRequiredOutputTensor, py::arg("ir_index")) | ||
| 612 | + .def("_get_dynamic_output_num", &BorrowedAnnotatedArgsContext::GetDynamicOutputNum, py::arg("ir_index")) | ||
| 613 | + .def("_get_dynamic_output_tensor", &BorrowedAnnotatedArgsContext::GetDynamicOutputTensor, py::arg("ir_index"), | ||
| 614 | + py::arg("relative_index")) | ||
| 615 | + .def("_get_attrs", &BorrowedAnnotatedArgsContext::GetAttrs) | ||
| 616 | + .def("malloc_workspace", &BorrowedAnnotatedArgsContext::MallocWorkspace, py::arg("size")) | ||
| 617 | + .def("get_stream_id", &BorrowedAnnotatedArgsContext::GetStreamId) | ||
| 618 | + .def("create_kernel_args", &BorrowedAnnotatedArgsContext::CreateKernelArgs) | ||
| 619 | + .def("add_launch", &BorrowedAnnotatedArgsContext::AddLaunch, py::arg("launch_info"), py::arg("args")) | ||
| 620 | + .def("_invalidate", &BorrowedAnnotatedArgsContext::Invalidate); | ||
| 621 | + m.def("_borrow_annotated_args_context", &BorrowAnnotatedArgsContext, py::arg("ctx_handle")); | ||
| 622 | +} | ||
| 623 | + | ||
| 341 | } // namespace python_custom_op_native | 624 | } // namespace python_custom_op_native |
| 342 | } // namespace ge | 625 | } // namespace ge |
| @@ -13,5 +13,6 @@ | |||
| 13 | namespace ge { | 13 | 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 | } | 17 | } |
| 17 | } // namespace ge | 18 | } // namespace ge |
| @@ -18,7 +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_SPECS = ((INTERFACE_EAGER_EXECUTE, "execute"),) | 21 | +INTERFACE_ANNOTATED_ARGS = "annotated_args" |
| 22 | +_INTERFACE_SPECS = ( | ||
| 23 | + (INTERFACE_EAGER_EXECUTE, "execute"), | ||
| 24 | + (INTERFACE_ANNOTATED_ARGS, "declare_launch_args"), | ||
| 25 | +) | ||
| 22 | 26 | ||
| 23 | 27 | ||
| 24 | 28 | ||
| @@ -38,6 +38,8 @@ | |||
| 38 | 38 | ||
| 39 | 39 | ||
| 40 | 40 | ||
| 41 | + | ||
| 42 | + | ||
| 41 | 43 | ||
| 42 | 44 | ||
| 43 | 45 | ||
| @@ -491,7 +493,7 @@ Status GetAnnotatedArgsOp(const OpDescPtr &op_desc, AnnotatedArgsOp *&annotated_ | |||
| 491 | auto *const base_custom_op = CustomOpFactory::CreateOrGetCustomOp(AscendString(op_desc->GetTypePtr())); | 493 | auto *const base_custom_op = CustomOpFactory::CreateOrGetCustomOp(AscendString(op_desc->GetTypePtr())); |
| 492 | GE_ASSERT_NOTNULL(base_custom_op, "Create custom op failed, op_name:%s, op_type:%s", op_desc->GetNamePtr(), | 494 | GE_ASSERT_NOTNULL(base_custom_op, "Create custom op failed, op_name:%s, op_type:%s", op_desc->GetNamePtr(), |
| 493 | op_desc->GetTypePtr()); | 495 | op_desc->GetTypePtr()); |
| 494 | - annotated_args_op = dynamic_cast<AnnotatedArgsOp *>(base_custom_op); | 496 | + annotated_args_op = CustomOpCast<AnnotatedArgsOp>(base_custom_op); |
C | |||
| 495 | GE_ASSERT_NOTNULL(annotated_args_op, "Custom op does not implement AnnotatedArgsOp, op_name:%s, op_type:%s", | 497 | GE_ASSERT_NOTNULL(annotated_args_op, "Custom op does not implement AnnotatedArgsOp, op_name:%s, op_type:%s", |
| 496 | op_desc->GetNamePtr(), op_desc->GetTypePtr()); | 498 | op_desc->GetNamePtr(), op_desc->GetTypePtr()); |
| 497 | return SUCCESS; | 499 | return SUCCESS; |
| @@ -761,11 +763,14 @@ Status FillBasicCustomKernelTask(const Node &node, domi::TaskDef &task_def) { | |||
| 761 | } | 763 | } |
| 762 | 764 | ||
| 763 | Status GenerateBasicCustomKernelTask(const Node &node, const OpDescPtr &op_desc, const std::string &soc_version, | 765 | Status GenerateBasicCustomKernelTask(const Node &node, const OpDescPtr &op_desc, const std::string &soc_version, |
| 764 | - std::vector<domi::TaskDef> &tasks) { | 766 | + const CustomTaskArgsMode args_mode, std::vector<domi::TaskDef> &tasks) { |
| 765 | GELOGI("Custom op %s(%s) generate basic custom kernel task, soc_version: %s", op_desc->GetNamePtr(), | 767 | GELOGI("Custom op %s(%s) generate basic custom kernel task, soc_version: %s", op_desc->GetNamePtr(), |
| 766 | op_desc->GetTypePtr(), soc_version.c_str()); | 768 | op_desc->GetTypePtr(), soc_version.c_str()); |
| 767 | domi::TaskDef task_def = {}; | 769 | domi::TaskDef task_def = {}; |
| 768 | GE_ASSERT_SUCCESS(FillBasicCustomKernelTask(node, task_def)); | 770 | GE_ASSERT_SUCCESS(FillBasicCustomKernelTask(node, task_def)); |
| 771 | + GE_ASSERT_TRUE(AttrUtils::SetInt(op_desc, ATTR_NAME_CUSTOM_TASK_ARGS_MODE, static_cast<int64_t>(args_mode)), | ||
| 772 | + "Set %s failed for custom op %s(%s).", ATTR_NAME_CUSTOM_TASK_ARGS_MODE.c_str(), op_desc->GetNamePtr(), | ||
| 773 | + op_desc->GetTypePtr()); | ||
| 769 | tasks.push_back(task_def); | 774 | tasks.push_back(task_def); |
| 770 | return SUCCESS; | 775 | return SUCCESS; |
| 771 | } | 776 | } |
| @@ -818,15 +823,24 @@ Status CustomOpsKernelBuilder::GenerateTask(const Node &node, RunContext &contex | |||
| 818 | 823 | ||
| 819 | const auto *const owner_graph = node.GetOwnerComputeGraphBarePtr(); | 824 | const auto *const owner_graph = node.GetOwnerComputeGraphBarePtr(); |
| 820 | GE_ASSERT_NOTNULL(owner_graph, "Owner graph of custom op %s(%s) is null.", node.GetNamePtr(), node.GetTypePtr()); | 825 | GE_ASSERT_NOTNULL(owner_graph, "Owner graph of custom op %s(%s) is null.", node.GetNamePtr(), node.GetTypePtr()); |
| 826 | + ArgsRefreshStrategy args_refresh_strategy = ArgsRefreshStrategy::kNone; | ||
| 821 | if (!owner_graph->GetGraphUnknownFlag()) { | 827 | if (!owner_graph->GetGraphUnknownFlag()) { |
| 822 | const bool is_mobile_omc = IsMobileSocVersion(soc_version); | 828 | const bool is_mobile_omc = IsMobileSocVersion(soc_version); |
| 823 | - const auto args_refresh_strategy = CustomOpFactory::GetArgsRefreshStrategy(AscendString(op_desc->GetTypePtr())); | 829 | + args_refresh_strategy = CustomOpFactory::GetArgsRefreshStrategy(AscendString(op_desc->GetTypePtr())); |
| 824 | if (args_refresh_strategy == ArgsRefreshStrategy::kAnnotatedArgs) { | 830 | if (args_refresh_strategy == ArgsRefreshStrategy::kAnnotatedArgs) { |
| 825 | // 端侧场景下的自定义算子只支持单任务的 AnnotatedArgsOp 生成方式,非端侧场景下的自定义算子支持多任务的 | 831 | // 端侧场景下的自定义算子只支持单任务的 AnnotatedArgsOp 生成方式,非端侧场景下的自定义算子支持多任务的 |
| 826 | // AnnotatedArgsOp 生成方式 | 832 | // AnnotatedArgsOp 生成方式 |
| 827 | const auto mode = | 833 | const auto mode = |
| 828 | is_mobile_omc ? OfflineLaunchGenMode::kMobileLegacySingleTask : OfflineLaunchGenMode::kStandardOmMultiTask; | 834 | is_mobile_omc ? OfflineLaunchGenMode::kMobileLegacySingleTask : OfflineLaunchGenMode::kStandardOmMultiTask; |
| 829 | - return GenerateAnnotatedArgsTask(node, op_desc, context, mode, tasks); | 835 | + const auto status = GenerateAnnotatedArgsTask(node, op_desc, context, mode, tasks); |
| 836 | + if (status != SUCCESS) { | ||
| 837 | + return status; | ||
| 838 | + } | ||
| 839 | + GE_ASSERT_TRUE(AttrUtils::SetInt(op_desc, ATTR_NAME_CUSTOM_TASK_ARGS_MODE, | ||
| 840 | + static_cast<int64_t>(CustomTaskArgsMode::kAnnotatedArgs)), | ||
| 841 | + "Set %s failed for custom op %s(%s).", ATTR_NAME_CUSTOM_TASK_ARGS_MODE.c_str(), | ||
| 842 | + op_desc->GetNamePtr(), op_desc->GetTypePtr()); | ||
| 843 | + return SUCCESS; | ||
| 830 | } | 844 | } |
| 831 | // 端侧场景下的自定义算子必须实现 AnnotatedArgsOp 接口,其他生成方式不支持 | 845 | // 端侧场景下的自定义算子必须实现 AnnotatedArgsOp 接口,其他生成方式不支持 |
| 832 | if (is_mobile_omc) { | 846 | if (is_mobile_omc) { |
| @@ -837,7 +851,10 @@ Status CustomOpsKernelBuilder::GenerateTask(const Node &node, RunContext &contex | |||
| 837 | } | 851 | } |
| 838 | // 全部动态图场景的自定义算子或者在非端侧场景中没有实现 AnnotatedArgsOp 的自定义算子,使用最基础的 CustomKernelTask | 852 | // 全部动态图场景的自定义算子或者在非端侧场景中没有实现 AnnotatedArgsOp 的自定义算子,使用最基础的 CustomKernelTask |
| 839 | // 生成方式 | 853 | // 生成方式 |
| 840 | - return GenerateBasicCustomKernelTask(node, op_desc, soc_version, tasks); | 854 | + const auto basic_args_mode = (args_refresh_strategy == ArgsRefreshStrategy::kUpdateCallback) |
| 855 | + ? CustomTaskArgsMode::kUpdateCallback | ||
| 856 | + : CustomTaskArgsMode::kNone; | ||
| 857 | + return GenerateBasicCustomKernelTask(node, op_desc, soc_version, basic_args_mode, tasks); | ||
| 841 | } | 858 | } |
| 842 | } // namespace custom | 859 | } // namespace custom |
| 843 | } // namespace ge | 860 | } // namespace ge |
| @@ -1273,6 +1273,7 @@ const std::string ATTR_NAME_CUBE_VECTOR_CORE_TYPE = "_cube_vector_core_type"; | |||
| 1273 | const std::string ATTR_NAME_CACHE_PERSIST = "_cache_persist"; | 1273 | const std::string ATTR_NAME_CACHE_PERSIST = "_cache_persist"; |
| 1274 | const std::string ATTR_NAME_ALIAS_ENGINE_NAME = "_alias_engine_name"; | 1274 | const std::string ATTR_NAME_ALIAS_ENGINE_NAME = "_alias_engine_name"; |
| 1275 | const std::string ATTR_NAME_KERNEL_NAMES_PREFIX = "_kernel_names_prefix"; | 1275 | const std::string ATTR_NAME_KERNEL_NAMES_PREFIX = "_kernel_names_prefix"; |
| 1276 | +const std::string ATTR_NAME_CUSTOM_TASK_ARGS_MODE = "_custom_task_args_mode"; | ||
| 1276 | const std::string ATTR_NAME_FFTS_SUB_TASK_TENSOR_SIZE = "_ffts_sub_task_tensor_size"; | 1277 | const std::string ATTR_NAME_FFTS_SUB_TASK_TENSOR_SIZE = "_ffts_sub_task_tensor_size"; |
| 1277 | const std::string ATTR_NAME_FFTS_SUB_TASK_TENSOR_OFFSETS = "_ffts_sub_task_tensor_offsets"; | 1278 | const std::string ATTR_NAME_FFTS_SUB_TASK_TENSOR_OFFSETS = "_ffts_sub_task_tensor_offsets"; |
| 1278 | const std::string ATTR_NAME_IS_FFTS_UNSUPPORTED = "_is_ffts_unsupported"; | 1279 | const std::string ATTR_NAME_IS_FFTS_UNSUPPORTED = "_is_ffts_unsupported"; |
| @@ -11,12 +11,22 @@ | |||
| 11 | 11 | ||
| 12 | 12 | ||
| 13 | 13 | ||
| 14 | + | ||
| 15 | + | ||
| 14 | namespace ge { | 16 | namespace ge { |
| 15 | enum class ArgsRefreshStrategy { | 17 | enum class ArgsRefreshStrategy { |
| 16 | kNone = 0, | 18 | kNone = 0, |
| 17 | kAnnotatedArgs, | 19 | kAnnotatedArgs, |
| 18 | kUpdateCallback, | 20 | kUpdateCallback, |
| 19 | }; | 21 | }; |
| 22 | + | ||
| 23 | +// Serialized in ATTR_NAME_CUSTOM_TASK_ARGS_MODE. Keep the numeric values stable for OM compatibility. | ||
| 24 | +enum class CustomTaskArgsMode : int64_t { | ||
| 25 | + kUnspecified = 0, | ||
| 26 | + kNone = 1, | ||
| 27 | + kAnnotatedArgs = 2, | ||
| 28 | + kUpdateCallback = 3, | ||
| 29 | +}; | ||
| 20 | } // namespace ge | 30 | } // namespace ge |
| 21 | 31 | ||
| 22 | 32 | ||
| @@ -1275,6 +1275,7 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAM | |||
| 1275 | GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_CACHE_PERSIST; | 1275 | GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_CACHE_PERSIST; |
| 1276 | GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_ALIAS_ENGINE_NAME; | 1276 | GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_ALIAS_ENGINE_NAME; |
| 1277 | GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_KERNEL_NAMES_PREFIX; | 1277 | GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_KERNEL_NAMES_PREFIX; |
| 1278 | +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_CUSTOM_TASK_ARGS_MODE; | ||
| 1278 | GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_FFTS_SUB_TASK_TENSOR_SIZE; | 1279 | GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_FFTS_SUB_TASK_TENSOR_SIZE; |
| 1279 | GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_FFTS_SUB_TASK_TENSOR_OFFSETS; | 1280 | GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_FFTS_SUB_TASK_TENSOR_OFFSETS; |
| 1280 | GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_IS_FFTS_UNSUPPORTED; | 1281 | GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_IS_FFTS_UNSUPPORTED; |
| @@ -220,6 +220,19 @@ graphStatus PythonCustomOpAdapter::Execute(gert::EagerOpExecutionContext *ctx) { | |||
| 220 | return holder_->GetCallbacks().execute(holder_->GetHolder(), ctx); | 220 | return holder_->GetCallbacks().execute(holder_->GetHolder(), ctx); |
| 221 | } | 221 | } |
| 222 | 222 | ||
| 223 | +graphStatus PythonCustomOpAdapter::DeclareLaunchArgs(gert::AnnotatedArgsContext &ctx) { | ||
| 224 | + if (!HasCapability(CustomOpCapability::kAnnotatedArgs)) { | ||
| 225 | + return ReportUnsupported(CustomOpCapability::kAnnotatedArgs, "DeclareLaunchArgs"); | ||
| 226 | + } | ||
| 227 | + if ((holder_ == nullptr) || (!holder_->IsValid()) || (holder_->GetHolder() == nullptr) || | ||
| 228 | + (holder_->GetCallbacks().declare_launch_args == nullptr)) { | ||
| 229 | + GELOGE(GRAPH_FAILED, "Python custom op adapter is invalid, descriptor key[%s], op type[%s].", | ||
| 230 | + desc_.descriptor_key.c_str(), desc_.op_type.c_str()); | ||
| 231 | + return GRAPH_FAILED; | ||
| 232 | + } | ||
| 233 | + return holder_->GetCallbacks().declare_launch_args(holder_->GetHolder(), &ctx); | ||
| 234 | +} | ||
| 235 | + | ||
| 223 | graphStatus PythonCustomOpAdapter::Compile(gert::OpCompileContext *ctx) { | 236 | graphStatus PythonCustomOpAdapter::Compile(gert::OpCompileContext *ctx) { |
| 224 | (void)ctx; | 237 | (void)ctx; |
| 225 | return ReportUnsupported(CustomOpCapability::kCompilable, "Compile"); | 238 | return ReportUnsupported(CustomOpCapability::kCompilable, "Compile"); |
| @@ -56,6 +56,7 @@ class PythonCustomOpHolder { | |||
| 56 | }; | 56 | }; |
| 57 | 57 | ||
| 58 | class PythonCustomOpAdapter final : public EagerExecuteOp, | 58 | class PythonCustomOpAdapter final : public EagerExecuteOp, |
| 59 | + public AnnotatedArgsOp, | ||
| 59 | public CompilableOp, | 60 | public CompilableOp, |
| 60 | public ShapeInferOp, | 61 | public ShapeInferOp, |
| 61 | public PortableOp, | 62 | public PortableOp, |
| @@ -69,6 +70,7 @@ class PythonCustomOpAdapter final : public EagerExecuteOp, | |||
| 69 | bool HasCapability(CustomOpCapability capability) const override; | 70 | bool HasCapability(CustomOpCapability capability) const override; |
| 70 | 71 | ||
| 71 | graphStatus Execute(gert::EagerOpExecutionContext *ctx) override; | 72 | graphStatus Execute(gert::EagerOpExecutionContext *ctx) override; |
| 73 | + graphStatus DeclareLaunchArgs(gert::AnnotatedArgsContext &ctx) override; | ||
| 72 | graphStatus Compile(gert::OpCompileContext *ctx) override; | 74 | graphStatus Compile(gert::OpCompileContext *ctx) override; |
| 73 | graphStatus InferShape(gert::InferShapeContext *ctx) override; | 75 | graphStatus InferShape(gert::InferShapeContext *ctx) override; |
| 74 | graphStatus InferDataType(gert::InferDataTypeContext *ctx) override; | 76 | graphStatus InferDataType(gert::InferDataTypeContext *ctx) override; |
| @@ -18,8 +18,9 @@ | |||
| 18 | 18 | ||
| 19 | 19 | ||
| 20 | namespace gert { | 20 | namespace gert { |
| 21 | +class AnnotatedArgsContext; | ||
| 21 | class EagerOpExecutionContext; | 22 | class EagerOpExecutionContext; |
| 22 | -} | 23 | +} // namespace gert |
| 23 | 24 | ||
| 24 | namespace ge { | 25 | namespace ge { |
| 25 | namespace custom_op { | 26 | namespace custom_op { |
| @@ -35,14 +36,17 @@ struct PythonCustomOpDescriptor { | |||
| 35 | using PythonCustomOpHolderCreateFn = void *(*)(const PythonCustomOpDescriptor *desc); | 36 | using PythonCustomOpHolderCreateFn = void *(*)(const PythonCustomOpDescriptor *desc); |
| 36 | using PythonCustomOpHolderDestroyFn = void (*)(void *holder); | 37 | using PythonCustomOpHolderDestroyFn = void (*)(void *holder); |
| 37 | using PythonCustomOpExecuteFn = graphStatus (*)(const void *holder, gert::EagerOpExecutionContext *ctx); | 38 | using PythonCustomOpExecuteFn = graphStatus (*)(const void *holder, gert::EagerOpExecutionContext *ctx); |
| 39 | +using PythonCustomOpDeclareLaunchArgsFn = graphStatus (*)(const void *holder, gert::AnnotatedArgsContext *ctx); | ||
| 38 | 40 | ||
| 39 | struct PythonCustomOpCallbacks { | 41 | struct PythonCustomOpCallbacks { |
| 40 | PythonCustomOpHolderCreateFn create{nullptr}; | 42 | PythonCustomOpHolderCreateFn create{nullptr}; |
| 41 | PythonCustomOpHolderDestroyFn destroy{nullptr}; | 43 | PythonCustomOpHolderDestroyFn destroy{nullptr}; |
| 42 | PythonCustomOpExecuteFn execute{nullptr}; | 44 | PythonCustomOpExecuteFn execute{nullptr}; |
| 45 | + PythonCustomOpDeclareLaunchArgsFn declare_launch_args{nullptr}; | ||
| 43 | 46 | ||
| 44 | bool IsValid(CustomOpCapabilityMask capabilities) const { | 47 | bool IsValid(CustomOpCapabilityMask capabilities) const { |
| 45 | - const auto supported_capabilities = static_cast<CustomOpCapabilityMask>(CustomOpCapability::kEagerExecute); | 48 | + const auto supported_capabilities = static_cast<CustomOpCapabilityMask>(CustomOpCapability::kEagerExecute) | |
| 49 | + static_cast<CustomOpCapabilityMask>(CustomOpCapability::kAnnotatedArgs); | ||
| 46 | if ((capabilities == 0U) || ((capabilities & (~supported_capabilities)) != 0U)) { | 50 | if ((capabilities == 0U) || ((capabilities & (~supported_capabilities)) != 0U)) { |
| 47 | return false; | 51 | return false; |
| 48 | } | 52 | } |
| @@ -52,6 +56,9 @@ struct PythonCustomOpCallbacks { | |||
| 52 | if (HasCustomOpCapability(capabilities, CustomOpCapability::kEagerExecute) && (execute == nullptr)) { | 56 | if (HasCustomOpCapability(capabilities, CustomOpCapability::kEagerExecute) && (execute == nullptr)) { |
| 53 | return false; | 57 | return false; |
| 54 | } | 58 | } |
| 59 | + if (HasCustomOpCapability(capabilities, CustomOpCapability::kAnnotatedArgs) && (declare_launch_args == nullptr)) { | ||
| 60 | + return false; | ||
| 61 | + } | ||
| 55 | return true; | 62 | return true; |
| 56 | } | 63 | } |
| 57 | }; | 64 | }; |
| @@ -47,6 +47,7 @@ constexpr const char *kBridgeModuleName = "ge.custom_op._bridge"; | |||
| 47 | constexpr const char *kCustomOpModuleName = "ge.custom_op"; | 47 | constexpr const char *kCustomOpModuleName = "ge.custom_op"; |
| 48 | constexpr const char *kCustomOpNativeModuleName = "ge.custom_op._ge_custom_op_native"; | 48 | constexpr const char *kCustomOpNativeModuleName = "ge.custom_op._ge_custom_op_native"; |
| 49 | constexpr const char *kEnvCustomOppPath = "ASCEND_CUSTOM_OPP_PATH"; | 49 | constexpr const char *kEnvCustomOppPath = "ASCEND_CUSTOM_OPP_PATH"; |
| 50 | +constexpr const char *kInterfaceAnnotatedArgs = "annotated_args"; | ||
| 50 | constexpr const char *kInterfaceEagerExecute = "eager_execute"; | 51 | constexpr const char *kInterfaceEagerExecute = "eager_execute"; |
| 51 | constexpr const char *kGetRegisteredIrDefSymbol = "GetRegisteredIrDef"; | 52 | constexpr const char *kGetRegisteredIrDefSymbol = "GetRegisteredIrDef"; |
| 52 | constexpr const char *kRunnerLibraryNames[] = { | 53 | constexpr const char *kRunnerLibraryNames[] = { |
| @@ -205,6 +206,8 @@ CustomOpCapabilityMask ParseInterfaces(const py::object &interfaces_obj) { | |||
| 205 | const std::string interface_name = py::str(item); | 206 | const std::string interface_name = py::str(item); |
| 206 | if (interface_name == kInterfaceEagerExecute) { | 207 | if (interface_name == kInterfaceEagerExecute) { |
| 207 | AddCustomOpCapability(capabilities, CustomOpCapability::kEagerExecute); | 208 | AddCustomOpCapability(capabilities, CustomOpCapability::kEagerExecute); |
| 209 | + } else if (interface_name == kInterfaceAnnotatedArgs) { | ||
| 210 | + AddCustomOpCapability(capabilities, CustomOpCapability::kAnnotatedArgs); | ||
| 208 | } | 211 | } |
| 209 | } | 212 | } |
| 210 | return capabilities; | 213 | return capabilities; |
| @@ -248,6 +251,8 @@ class PythonCustomOpPybindBridge { | |||
| 248 | } | 251 | } |
| 249 | 252 | ||
| 250 | const py::list descriptor_list = descriptors_obj.cast<py::list>(); | 253 | const py::list descriptor_list = descriptors_obj.cast<py::list>(); |
| 254 | + std::vector<PythonCustomOpDescriptor> descriptors; | ||
| 255 | + descriptors.reserve(descriptor_list.size()); | ||
| 251 | for (const auto &item : descriptor_list) { | 256 | for (const auto &item : descriptor_list) { |
| 252 | PythonCustomOpDescriptor desc; | 257 | PythonCustomOpDescriptor desc; |
| 253 | const auto parse_ret = ParseDescriptor(item.cast<py::dict>(), desc); | 258 | const auto parse_ret = ParseDescriptor(item.cast<py::dict>(), desc); |
| @@ -255,7 +260,26 @@ class PythonCustomOpPybindBridge { | |||
| 255 | GELOGE(parse_ret, "Parse python custom op descriptor failed."); | 260 | GELOGE(parse_ret, "Parse python custom op descriptor failed."); |
| 256 | return parse_ret; | 261 | return parse_ret; |
| 257 | } | 262 | } |
| 258 | - const auto callbacks = GetCallbacks(); | 263 | + auto ir_meta = CollectPythonCustomOpIrMeta(desc.op_type); |
| 264 | + try { | ||
| 265 | + const bool validated = | ||
| 266 | + bridge_module_.attr("validate_op_impl_descriptor")(desc.descriptor_key, BuildPythonIrMeta(ir_meta.get())) | ||
| 267 | + .cast<bool>(); | ||
| 268 | + if (!validated) { | ||
| 269 | + GELOGE(FAILED, "Validate python custom op[%s] descriptor[%s] failed.", desc.op_type.c_str(), | ||
| 270 | + desc.descriptor_key.c_str()); | ||
| 271 | + return FAILED; | ||
| 272 | + } | ||
| 273 | + } catch (const py::error_already_set &err) { | ||
| 274 | + GELOGE(FAILED, "Validate python custom op[%s] descriptor[%s] failed: %s", desc.op_type.c_str(), | ||
| 275 | + desc.descriptor_key.c_str(), err.what()); | ||
| 276 | + return FAILED; | ||
| 277 | + } | ||
| 278 | + descriptors.emplace_back(std::move(desc)); | ||
| 279 | + } | ||
| 280 | + | ||
| 281 | + const auto callbacks = GetCallbacks(); | ||
| 282 | + for (const auto &desc : descriptors) { | ||
| 259 | if ((registrar.register_custom_op == nullptr) || (!registrar.register_custom_op(&desc, &callbacks))) { | 283 | if ((registrar.register_custom_op == nullptr) || (!registrar.register_custom_op(&desc, &callbacks))) { |
| 260 | GELOGE(FAILED, "Register python custom op[%s] failed.", desc.op_type.c_str()); | 284 | GELOGE(FAILED, "Register python custom op[%s] failed.", desc.op_type.c_str()); |
| 261 | return FAILED; | 285 | return FAILED; |
| @@ -367,6 +391,39 @@ class PythonCustomOpPybindBridge { | |||
| 367 | } | 391 | } |
| 368 | } | 392 | } |
| 369 | 393 | ||
| 394 | + graphStatus DeclareLaunchArgs(const PythonCustomOpBridgeHolder *holder, gert::AnnotatedArgsContext *ctx) { | ||
| 395 | + if ((holder == nullptr) || (ctx == nullptr)) { | ||
| 396 | + GELOGE(GRAPH_FAILED, "Python custom op bridge holder or context is null."); | ||
| 397 | + return GRAPH_FAILED; | ||
| 398 | + } | ||
| 399 | + const auto prepare_ret = EnsureBridgeReady(); | ||
| 400 | + if (prepare_ret != SUCCESS) { | ||
| 401 | + GELOGE(prepare_ret, "Prepare python custom op bridge failed."); | ||
| 402 | + return GRAPH_FAILED; | ||
| 403 | + } | ||
| 404 | + py::gil_scoped_acquire gil; | ||
| 405 | + try { | ||
| 406 | + const bool created = | ||
| 407 | + bridge_module_.attr("create_op_impl_holder")(holder->instance_id, holder->descriptor_key).cast<bool>(); | ||
| 408 | + if (!created) { | ||
| 409 | + GELOGE(GRAPH_FAILED, "Ensure python custom op holder failed, descriptor key[%s], instance id[%s].", | ||
| 410 | + holder->descriptor_key.c_str(), holder->instance_id.c_str()); | ||
| 411 | + return GRAPH_FAILED; | ||
| 412 | + } | ||
| 413 | + py::object result = bridge_module_.attr("call_declare_launch_args")( | ||
| 414 | + holder->instance_id, BuildPythonIrMeta(holder->ir_meta.get()), BuildPythonAnnotatedArgsContext(ctx)); | ||
| 415 | + return TranslateStatusLike(result); | ||
| 416 | + } catch (const py::error_already_set &err) { | ||
| 417 | + GELOGE(GRAPH_FAILED, "DeclareLaunchArgs python custom op failed, descriptor key[%s], instance id[%s]: %s", | ||
| 418 | + holder->descriptor_key.c_str(), holder->instance_id.c_str(), err.what()); | ||
| 419 | + return GRAPH_FAILED; | ||
| 420 | + } catch (const std::exception &err) { | ||
| 421 | + GELOGE(GRAPH_FAILED, "DeclareLaunchArgs python custom op failed, descriptor key[%s], instance id[%s]: %s", | ||
| 422 | + holder->descriptor_key.c_str(), holder->instance_id.c_str(), err.what()); | ||
| 423 | + return GRAPH_FAILED; | ||
| 424 | + } | ||
| 425 | + } | ||
| 426 | + | ||
| 370 | private: | 427 | private: |
| 371 | Status EnsureBridgeReady() { | 428 | Status EnsureBridgeReady() { |
| 372 | std::lock_guard<std::mutex> lock(mutex_); | 429 | std::lock_guard<std::mutex> lock(mutex_); |
| @@ -536,6 +593,11 @@ class PythonCustomOpPybindBridge { | |||
| 536 | return native_module.attr("_borrow_eager_op_execution_context")(py::int_(reinterpret_cast<uintptr_t>(ctx))); | 593 | return native_module.attr("_borrow_eager_op_execution_context")(py::int_(reinterpret_cast<uintptr_t>(ctx))); |
| 537 | } | 594 | } |
| 538 | 595 | ||
| 596 | + static py::object BuildPythonAnnotatedArgsContext(gert::AnnotatedArgsContext *ctx) { | ||
| 597 | + py::module_ native_module = py::module_::import(kCustomOpNativeModuleName); | ||
| 598 | + return native_module.attr("_borrow_annotated_args_context")(py::int_(reinterpret_cast<uintptr_t>(ctx))); | ||
| 599 | + } | ||
| 600 | + | ||
| 539 | static graphStatus TranslateStatusLike(const py::object &result) { | 601 | static graphStatus TranslateStatusLike(const py::object &result) { |
| 540 | if (result.is_none()) { | 602 | if (result.is_none()) { |
| 541 | return GRAPH_SUCCESS; | 603 | return GRAPH_SUCCESS; |
| @@ -566,6 +628,10 @@ class PythonCustomOpPybindBridge { | |||
| 566 | return PythonCustomOpPybindBridge::GetInstance().Execute(static_cast<const PythonCustomOpBridgeHolder *>(holder), | 628 | return PythonCustomOpPybindBridge::GetInstance().Execute(static_cast<const PythonCustomOpBridgeHolder *>(holder), |
| 567 | ctx); | 629 | ctx); |
| 568 | }; | 630 | }; |
| 631 | + callbacks.declare_launch_args = [](const void *holder, gert::AnnotatedArgsContext *ctx) -> graphStatus { | ||
| 632 | + return PythonCustomOpPybindBridge::GetInstance().DeclareLaunchArgs( | ||
| 633 | + static_cast<const PythonCustomOpBridgeHolder *>(holder), ctx); | ||
| 634 | + }; | ||
| 569 | return callbacks; | 635 | return callbacks; |
| 570 | } | 636 | } |
| 571 | 637 | ||
| @@ -174,6 +174,54 @@ Status GetCustomKernelBinaryMagic(const OpDescPtr &op_desc, int32_t &binary_magi | |||
| 174 | std::string MakeCustomKernelBinName(const uint32_t model_id, const OpDescPtr &op_desc, const std::string &kernel_name) { | 174 | std::string MakeCustomKernelBinName(const uint32_t model_id, const OpDescPtr &op_desc, const std::string &kernel_name) { |
| 175 | return std::to_string(model_id) + "_" + op_desc->GetName() + "_" + kernel_name; | 175 | return std::to_string(model_id) + "_" + op_desc->GetName() + "_" + kernel_name; |
| 176 | } | 176 | } |
| 177 | + | ||
| 178 | +ArgsRefreshStrategy GetLegacyCustomTaskArgsRefreshStrategy(const AscendString &op_type, | ||
| 179 | + const domi::KernelContext &context, | ||
| 180 | + const CustomOpRegistryPtr &custom_op_registry) { | ||
| 181 | + const auto registry_strategy = custom_op_registry->GetArgsRefreshStrategy(op_type); | ||
| 182 | + if ((registry_strategy == ArgsRefreshStrategy::kNone) && (!context.args_format().empty())) { | ||
| 183 | + return ArgsRefreshStrategy::kAnnotatedArgs; | ||
| 184 | + } | ||
| 185 | + return registry_strategy; | ||
| 186 | +} | ||
| 187 | + | ||
| 188 | +Status GetCustomTaskArgsRefreshStrategy(const OpDescPtr &op_desc, const domi::KernelContext &context, | ||
| 189 | + const CustomOpRegistryPtr &custom_op_registry, | ||
| 190 | + ArgsRefreshStrategy &args_refresh_strategy) { | ||
| 191 | + GE_ASSERT_NOTNULL(op_desc); | ||
| 192 | + GE_ASSERT_NOTNULL(custom_op_registry); | ||
| 193 | + const AscendString op_type(op_desc->GetTypePtr()); | ||
| 194 | + if (!op_desc->HasAttr(ATTR_NAME_CUSTOM_TASK_ARGS_MODE)) { | ||
| 195 | + args_refresh_strategy = GetLegacyCustomTaskArgsRefreshStrategy(op_type, context, custom_op_registry); | ||
| 196 | + return SUCCESS; | ||
| 197 | + } | ||
| 198 | + | ||
| 199 | + int64_t args_mode = 0; | ||
| 200 | + GE_ASSERT_TRUE(AttrUtils::GetInt(op_desc, ATTR_NAME_CUSTOM_TASK_ARGS_MODE, args_mode), | ||
| 201 | + "[CUSTOM OP] get %s failed for op %s(%s).", ATTR_NAME_CUSTOM_TASK_ARGS_MODE.c_str(), | ||
| 202 | + op_desc->GetNamePtr(), op_desc->GetTypePtr()); | ||
| 203 | + switch (static_cast<CustomTaskArgsMode>(args_mode)) { | ||
| 204 | + case CustomTaskArgsMode::kUnspecified: | ||
| 205 | + args_refresh_strategy = GetLegacyCustomTaskArgsRefreshStrategy(op_type, context, custom_op_registry); | ||
| 206 | + return SUCCESS; | ||
| 207 | + case CustomTaskArgsMode::kNone: | ||
| 208 | + args_refresh_strategy = ArgsRefreshStrategy::kNone; | ||
| 209 | + return SUCCESS; | ||
| 210 | + case CustomTaskArgsMode::kAnnotatedArgs: | ||
| 211 | + args_refresh_strategy = ArgsRefreshStrategy::kAnnotatedArgs; | ||
| 212 | + return SUCCESS; | ||
| 213 | + case CustomTaskArgsMode::kUpdateCallback: | ||
| 214 | + GE_ASSERT_TRUE(custom_op_registry->GetArgsRefreshStrategy(op_type) == ArgsRefreshStrategy::kUpdateCallback, | ||
| 215 | + "[CUSTOM OP] update callback is not registered for op %s(%s).", op_desc->GetNamePtr(), | ||
| 216 | + op_desc->GetTypePtr()); | ||
| 217 | + args_refresh_strategy = ArgsRefreshStrategy::kUpdateCallback; | ||
| 218 | + return SUCCESS; | ||
| 219 | + default: | ||
| 220 | + GELOGE(PARAM_INVALID, "[CUSTOM OP] invalid %s value %" PRId64 " for op %s(%s).", | ||
| 221 | + ATTR_NAME_CUSTOM_TASK_ARGS_MODE.c_str(), args_mode, op_desc->GetNamePtr(), op_desc->GetTypePtr()); | ||
| 222 | + return PARAM_INVALID; | ||
| 223 | + } | ||
| 224 | +} | ||
| 177 | } // namespace | 225 | } // namespace |
| 178 | 226 | ||
| 179 | void CustomTaskInfo::SetCustomDumpInfo(const DumpProperties &dump_properties, DumpOp &dump_op) const { | 227 | void CustomTaskInfo::SetCustomDumpInfo(const DumpProperties &dump_properties, DumpOp &dump_op) const { |
| @@ -281,11 +329,10 @@ Status CustomTaskInfo::ParseTaskRunParam(const domi::TaskDef &task_def, DavinciM | |||
| 281 | workspace_addrs_ = ModelUtils::GetWorkspaceDataAddrsValue(rts_param, op_desc_, workspace_mem_types_); | 329 | workspace_addrs_ = ModelUtils::GetWorkspaceDataAddrsValue(rts_param, op_desc_, workspace_mem_types_); |
| 282 | GE_ASSERT_SUCCESS(ValidateIoWorkspaceAddrAndMemTypeSizes()); | 330 | GE_ASSERT_SUCCESS(ValidateIoWorkspaceAddrAndMemTypeSizes()); |
| 283 | 331 | ||
| 284 | - AscendString op_type(op_desc_->GetType().c_str()); | ||
| 285 | const auto &custom_op_registry = davinci_model->GetCustomOpRegistry(); | 332 | const auto &custom_op_registry = davinci_model->GetCustomOpRegistry(); |
| 286 | GE_ASSERT_NOTNULL(custom_op_registry, "[CUSTOM OP] custom op registry is nullptr for op %s.", | 333 | GE_ASSERT_NOTNULL(custom_op_registry, "[CUSTOM OP] custom op registry is nullptr for op %s.", |
| 287 | op_desc_->GetName().c_str()); | 334 | op_desc_->GetName().c_str()); |
| 288 | - args_refresh_strategy_ = custom_op_registry->GetArgsRefreshStrategy(op_type); | 335 | + GE_ASSERT_SUCCESS(GetCustomTaskArgsRefreshStrategy(op_desc_, context, custom_op_registry, args_refresh_strategy_)); |
| 289 | is_args_refreshable_ = args_refresh_strategy_ != ArgsRefreshStrategy::kNone; | 336 | is_args_refreshable_ = args_refresh_strategy_ != ArgsRefreshStrategy::kNone; |
| 290 | 337 | ||
| 291 | if (args_refresh_strategy_ == ArgsRefreshStrategy::kAnnotatedArgs) { | 338 | if (args_refresh_strategy_ == ArgsRefreshStrategy::kAnnotatedArgs) { |
| @@ -253,6 +253,8 @@ run_pyge_pytests() { | |||
| 253 | PYGE_SRC_PATH=${BASEPATH}/api/python/ge | 253 | PYGE_SRC_PATH=${BASEPATH}/api/python/ge |
| 254 | PYGE_COVERAGE_RCFILE=${BUILD_PATH}/pyge.coveragerc | 254 | PYGE_COVERAGE_RCFILE=${BUILD_PATH}/pyge.coveragerc |
| 255 | ORIGINAL_LD_LIBRARY_PATH=$LD_LIBRARY_PATH | 255 | ORIGINAL_LD_LIBRARY_PATH=$LD_LIBRARY_PATH |
| 256 | + ORIGINAL_LD_PRELOAD=$LD_PRELOAD | ||
| 257 | + PYGE_USE_LIBSTDCXX=$(LD_PRELOAD= gcc -print-file-name=libstdc++.so.6) | ||
| 256 | cat > ${PYGE_COVERAGE_RCFILE} <<EOF | 258 | cat > ${PYGE_COVERAGE_RCFILE} <<EOF |
| 257 | [run] | 259 | [run] |
| 258 | source = | 260 | source = |
| @@ -268,8 +270,10 @@ EOF | |||
| 268 | # 先走安装目录,确保 ge.passes 能导到安装产物里的 _ge_pass_native.so; | 270 | # 先走安装目录,确保 ge.passes 能导到安装产物里的 _ge_pass_native.so; |
| 269 | # 覆盖率通过 coveragerc 的 [paths] 映射回源码目录。 | 271 | # 覆盖率通过 coveragerc 的 [paths] 映射回源码目录。 |
| 270 | export PYTHONPATH=${PYGE_INSTALL_PATH}:${PYGE_SRC_PATH}:$PYTHON_ORIGINAL_PATH | 272 | export PYTHONPATH=${PYGE_INSTALL_PATH}:${PYGE_SRC_PATH}:$PYTHON_ORIGINAL_PATH |
| 273 | + export LD_PRELOAD=${USE_ASAN}:${PYGE_USE_LIBSTDCXX} | ||
| 271 | ASAN_OPTIONS=detect_leaks=0:detect_container_overflow=0 ${HI_PYTHON} -m coverage run --rcfile=${PYGE_COVERAGE_RCFILE} --data-file=coverage_pyge -m pytest ${BASEPATH}/tests/ge/ut/ge/graph/pyge_tests/*_test.py -vv -rs -s | 274 | ASAN_OPTIONS=detect_leaks=0:detect_container_overflow=0 ${HI_PYTHON} -m coverage run --rcfile=${PYGE_COVERAGE_RCFILE} --data-file=coverage_pyge -m pytest ${BASEPATH}/tests/ge/ut/ge/graph/pyge_tests/*_test.py -vv -rs -s |
| 272 | export LD_LIBRARY_PATH=${ORIGINAL_LD_LIBRARY_PATH} | 275 | export LD_LIBRARY_PATH=${ORIGINAL_LD_LIBRARY_PATH} |
| 276 | + export LD_PRELOAD=${ORIGINAL_LD_PRELOAD} | ||
| 273 | export PYTHONPATH=$PYTHON_ORIGINAL_PATH | 277 | export PYTHONPATH=$PYTHON_ORIGINAL_PATH |
| 274 | } | 278 | } |
| 275 | 279 | ||
| @@ -13,6 +13,7 @@ | |||
| 13 | 13 | ||
| 14 | 14 | ||
| 15 | 15 | ||
| 16 | + | ||
| 16 | 17 | ||
| 17 | 18 | ||
| 18 | 19 | ||
| @@ -29,6 +30,8 @@ | |||
| 29 | 30 | ||
| 30 | 31 | ||
| 31 | 32 | ||
| 33 | + | ||
| 34 | + | ||
| 32 | 35 | ||
| 33 | 36 | ||
| 34 | 37 | ||
| @@ -50,12 +53,33 @@ | |||
| 50 | 53 | ||
| 51 | 54 | ||
| 52 | 55 | ||
| 56 | + | ||
| 57 | + | ||
| 58 | + | ||
| 53 | 59 | ||
| 54 | 60 | ||
| 61 | + | ||
| 62 | + | ||
| 63 | + | ||
| 64 | + | ||
| 55 | 65 | ||
| 56 | 66 | ||
| 57 | 67 | ||
| 58 | 68 | ||
| 69 | +namespace ge { | ||
| 70 | +REG_OP(StPythonAnnotatedArgsCustomOp) | ||
| 71 | + .INPUT(x, TensorType::ALL()) | ||
| 72 | + .OUTPUT(z, TensorType::ALL()) | ||
| 73 | + .REQUIRED_ATTR(alpha, Int) | ||
| 74 | + .OP_END_FACTORY_REG(StPythonAnnotatedArgsCustomOp); | ||
| 75 | + | ||
| 76 | +REG_OP(StPythonAnnotatedArgsBadAttrCustomOp) | ||
| 77 | + .INPUT(x, TensorType::ALL()) | ||
| 78 | + .OUTPUT(z, TensorType::ALL()) | ||
| 79 | + .REQUIRED_ATTR(alpha, Int) | ||
| 80 | + .OP_END_FACTORY_REG(StPythonAnnotatedArgsBadAttrCustomOp); | ||
| 81 | +} // namespace ge | ||
| 82 | + | ||
| 59 | namespace ge { | 83 | namespace ge { |
| 60 | using namespace gert; | 84 | using namespace gert; |
| 61 | namespace { | 85 | namespace { |
| @@ -149,8 +173,117 @@ void MockGenerateTask() { | |||
| 149 | void *output_addr = nullptr; | 173 | void *output_addr = nullptr; |
| 150 | void **args_table = nullptr; | 174 | void **args_table = nullptr; |
| 151 | constexpr const char *kPythonCustomOpTypeForSt = "StPythonPybindRemoveCoverageCustomOp"; | 175 | constexpr const char *kPythonCustomOpTypeForSt = "StPythonPybindRemoveCoverageCustomOp"; |
| 176 | +constexpr const char *kPythonAnnotatedArgsOpTypeForSt = "StPythonAnnotatedArgsCustomOp"; | ||
| 177 | +constexpr const char *kPythonAnnotatedArgsBadAttrOpTypeForSt = "StPythonAnnotatedArgsBadAttrCustomOp"; | ||
| 152 | constexpr const char *kEnvPythonCustomOpPath = "ASCEND_CUSTOM_OPP_PATH"; | 178 | constexpr const char *kEnvPythonCustomOpPath = "ASCEND_CUSTOM_OPP_PATH"; |
| 153 | constexpr const char *kEnvPythonPath = "PYTHONPATH"; | 179 | constexpr const char *kEnvPythonPath = "PYTHONPATH"; |
| 180 | +constexpr char kSharedPybindCustomOpPreambleForSt[] = R"PY(from pathlib import Path | ||
| 181 | +from ge.custom_op import ( | ||
| 182 | + AnnotatedKernelLaunchInfo, | ||
| 183 | + EagerExecuteOp, | ||
| 184 | + get_declare_launch_args_ctx, | ||
| 185 | + register_op_impl, | ||
| 186 | +) | ||
| 187 | +from ge.runtime import Tensor | ||
| 188 | + | ||
| 189 | +MARKER_FILE = r')PY"; | ||
| 190 | +constexpr char kSharedPybindEagerCustomOpForSt[] = R"PY(' | ||
| 191 | + | ||
| 192 | +@register_op_impl(op_type=')PY"; | ||
| 193 | +constexpr char kSharedPybindAnnotatedArgsPrefixForSt[] = R"PY(') | ||
| 194 | +class StPythonPybindRemoveCoverageCustomOp(EagerExecuteOp): | ||
| 195 | + def execute(self, ctx): | ||
| 196 | + Path(MARKER_FILE).write_text('executed', encoding='utf-8') | ||
| 197 | + | ||
| 198 | +@register_op_impl(op_type=')PY"; | ||
| 199 | +constexpr char kSharedPybindAnnotatedArgsBodyForSt[] = R"PY(') | ||
| 200 | +class StPythonAnnotatedArgsCustomOp: | ||
| 201 | + def __init__(self): | ||
| 202 | + self.saved = None | ||
| 203 | + | ||
| 204 | + def declare_launch_args(self, x: Tensor, z: Tensor, *, alpha: int) -> None: | ||
| 205 | + ctx = get_declare_launch_args_ctx() | ||
| 206 | + args = ctx.create_kernel_args() | ||
| 207 | + if alpha == 1: | ||
| 208 | + args.append_input(2, x) | ||
| 209 | + if alpha == 2: | ||
| 210 | + args.append_input(-1, x) | ||
| 211 | + if alpha == 3: | ||
| 212 | + args.append_scalar(-1) | ||
| 213 | + if alpha == 9: | ||
| 214 | + try: | ||
| 215 | + args.append_input(2, x) | ||
| 216 | + except IndexError as error: | ||
| 217 | + if 'index 2' not in str(error): | ||
| 218 | + raise AssertionError('input index is missing from error') | ||
| 219 | + else: | ||
| 220 | + raise AssertionError('invalid input index was accepted') | ||
| 221 | + try: | ||
| 222 | + args.append_output(3, z) | ||
| 223 | + except IndexError as error: | ||
| 224 | + if 'index 3' not in str(error): | ||
| 225 | + raise AssertionError('output index is missing from error') | ||
| 226 | + else: | ||
| 227 | + raise AssertionError('invalid output index was accepted') | ||
| 228 | + if alpha == 7: | ||
| 229 | + saved_tensor, saved_workspace, saved_args = self.saved | ||
| 230 | + for access in ( | ||
| 231 | + lambda: saved_tensor.addr, | ||
| 232 | + lambda: saved_workspace.index, | ||
| 233 | + lambda: saved_args.append_scalar(0), | ||
| 234 | + ): | ||
| 235 | + try: | ||
| 236 | + access() | ||
| 237 | + except RuntimeError: | ||
| 238 | + pass | ||
| 239 | + else: | ||
| 240 | + raise AssertionError('borrowed DLA object did not expire') | ||
| 241 | + args.append_input(0, x) | ||
| 242 | + args.append_output(0, z) | ||
| 243 | + args.append_scalar(alpha) | ||
| 244 | + workspace = None | ||
| 245 | + if alpha == 6: | ||
| 246 | + workspace = ctx.malloc_workspace(64) | ||
| 247 | + for name in ('index', 'addr'): | ||
| 248 | + try: | ||
| 249 | + setattr(workspace, name, 0) | ||
| 250 | + except AttributeError: | ||
| 251 | + pass | ||
| 252 | + else: | ||
| 253 | + raise AssertionError('workspace property is writable') | ||
| 254 | + args.append_workspace(workspace) | ||
| 255 | + stream_id = ctx.get_stream_id() | ||
| 256 | + if alpha == 4: | ||
| 257 | + stream_id += 1 | ||
| 258 | + ctx.add_launch( | ||
| 259 | + AnnotatedKernelLaunchInfo( | ||
| 260 | + kernel_name='st_python_dla', | ||
| 261 | + kernel_bin=b'\x01\x02', | ||
| 262 | + block_dim=1, | ||
| 263 | + stream_id=stream_id, | ||
| 264 | + ), | ||
| 265 | + args, | ||
| 266 | + ) | ||
| 267 | + if alpha == 5: | ||
| 268 | + args.append_scalar(0) | ||
| 269 | + if alpha == 6: | ||
| 270 | + self.saved = (x, workspace, args) | ||
| 271 | +)PY"; | ||
| 272 | +constexpr char kSharedPybindBadAttrCustomOpForSt[] = R"PY(') | ||
| 273 | +class StPythonAnnotatedArgsBadAttrCustomOp: | ||
| 274 | + def declare_launch_args(self, x: Tensor, z: Tensor, *, beta: int) -> None: | ||
| 275 | + pass | ||
| 276 | +)PY"; | ||
| 277 | +constexpr char kInvalidSignaturePybindPreambleForSt[] = R"PY(from ge.custom_op import register_op_impl | ||
| 278 | +from ge.runtime import Tensor | ||
| 279 | + | ||
| 280 | +@register_op_impl(op_type=')PY"; | ||
| 281 | +constexpr char kValidBeforeInvalidPybindCustomOpForSt[] = R"PY(') | ||
| 282 | +class StPythonValidBeforeInvalidCustomOp: | ||
| 283 | + def execute(self, ctx): | ||
| 284 | + pass | ||
| 285 | + | ||
| 286 | +@register_op_impl(op_type=')PY"; | ||
| 154 | 287 | ||
| 155 | class ScopedTempDirForCustomOpSt { | 288 | class ScopedTempDirForCustomOpSt { |
| 156 | public: | 289 | public: |
| @@ -210,6 +343,76 @@ class ScopedEnvVarForCustomOpSt { | |||
| 210 | bool has_old_value_{false}; | 343 | bool has_old_value_{false}; |
| 211 | }; | 344 | }; |
| 212 | 345 | ||
| 346 | +class ScopedGraphOptionsForCustomOpSt { | ||
| 347 | + public: | ||
| 348 | + explicit ScopedGraphOptionsForCustomOpSt(const std::map<std::string, std::string> &options) | ||
| 349 | + : old_options_(GetThreadLocalContext().GetAllGraphOptions()) { | ||
| 350 | + GetThreadLocalContext().SetGraphOption(options); | ||
| 351 | + } | ||
| 352 | + | ||
| 353 | + ~ScopedGraphOptionsForCustomOpSt() { | ||
| 354 | + GetThreadLocalContext().SetGraphOption(old_options_); | ||
| 355 | + } | ||
| 356 | + | ||
| 357 | + private: | ||
| 358 | + std::map<std::string, std::string> old_options_; | ||
| 359 | +}; | ||
| 360 | + | ||
| 361 | +Status GeneratePythonAnnotatedArgsTaskForSt(const char *const op_type, const int64_t alpha, | ||
| 362 | + const std::string &soc_version, std::vector<domi::TaskDef> &tasks, | ||
| 363 | + const bool is_unknown_shape = false) { | ||
| 364 | + const std::map<std::string, std::string> graph_options = {{SOC_VERSION, soc_version}}; | ||
| 365 | + ScopedGraphOptionsForCustomOpSt scoped_graph_options(graph_options); | ||
| 366 | + auto graph = std::make_shared<ComputeGraph>(std::string("st_python_annotated_args_") + std::to_string(alpha)); | ||
| 367 | + GE_ASSERT_NOTNULL(graph); | ||
| 368 | + graph->SetGraphUnknownFlag(is_unknown_shape); | ||
| 369 | + auto op_desc = std::make_shared<OpDesc>("st_python_annotated_args_node", op_type); | ||
| 370 | + GE_ASSERT_NOTNULL(op_desc); | ||
| 371 | + op_desc->SetId(7); | ||
| 372 | + op_desc->SetStreamId(3); | ||
| 373 | + op_desc->AppendIrInput("x", kIrInputRequired); | ||
| 374 | + op_desc->AppendIrOutput("z", kIrOutputRequired); | ||
| 375 | + op_desc->AppendIrAttrName("alpha"); | ||
| 376 | + GE_ASSERT_TRUE(AttrUtils::SetInt(op_desc, "alpha", alpha)); | ||
| 377 | + GeTensorDesc input_desc(GeShape({1, 16}), FORMAT_ND, DT_FLOAT16); | ||
| 378 | + input_desc.SetOriginShape(GeShape({1, 16})); | ||
| 379 | + GeTensorDesc output_desc(GeShape({1, 16}), FORMAT_ND, DT_FLOAT16); | ||
| 380 | + output_desc.SetOriginShape(GeShape({1, 16})); | ||
| 381 | + GE_ASSERT_GRAPH_SUCCESS(op_desc->AddInputDesc("x", input_desc)); | ||
| 382 | + GE_ASSERT_GRAPH_SUCCESS(op_desc->AddOutputDesc("z", output_desc)); | ||
| 383 | + op_desc->SetInputOffset({1024}); | ||
| 384 | + op_desc->SetOutputOffset({2048}); | ||
| 385 | + const auto node = graph->AddNode(op_desc); | ||
| 386 | + GE_ASSERT_NOTNULL(node); | ||
| 387 | + | ||
| 388 | + RunContext run_context = {}; | ||
| 389 | + run_context.dataMemBase = reinterpret_cast<uint8_t *>(0x80000000UL); | ||
| 390 | + run_context.dataMemSize = 4096U; | ||
| 391 | + custom::CustomOpsKernelBuilder builder; | ||
| 392 | + return builder.GenerateTask(*node, run_context, tasks); | ||
| 393 | +} | ||
| 394 | + | ||
| 395 | +Status AddPythonAnnotatedArgsOpToModelForSt(DavinciModel &model, const uint32_t op_index, OpDescPtr &op_desc) { | ||
| 396 | + op_desc = std::make_shared<OpDesc>("st_python_annotated_args_loaded", kPythonAnnotatedArgsOpTypeForSt); | ||
| 397 | + GE_ASSERT_NOTNULL(op_desc); | ||
| 398 | + op_desc->SetId(op_index); | ||
| 399 | + op_desc->SetStreamId(3); | ||
| 400 | + op_desc->AppendIrInput("x", kIrInputRequired); | ||
| 401 | + op_desc->AppendIrOutput("z", kIrOutputRequired); | ||
| 402 | + GeTensorDesc input_desc(GeShape({1, 16}), FORMAT_ND, DT_FLOAT16); | ||
| 403 | + input_desc.SetOriginShape(GeShape({1, 16})); | ||
| 404 | + GeTensorDesc output_desc(GeShape({1, 16}), FORMAT_ND, DT_FLOAT16); | ||
| 405 | + output_desc.SetOriginShape(GeShape({1, 16})); | ||
| 406 | + GE_ASSERT_GRAPH_SUCCESS(op_desc->AddInputDesc("x", input_desc)); | ||
| 407 | + GE_ASSERT_GRAPH_SUCCESS(op_desc->AddOutputDesc("z", output_desc)); | ||
| 408 | + op_desc->SetInputOffset({1024}); | ||
| 409 | + op_desc->SetOutputOffset({2048}); | ||
| 410 | + GE_ASSERT_TRUE(AttrUtils::SetStr(op_desc, TVM_ATTR_NAME_MAGIC, "RT_DEV_BINARY_MAGIC_ELF_AIVEC")); | ||
| 411 | + model.op_list_[op_index] = op_desc; | ||
| 412 | + model.SetCustomOpRegistry(CustomOpFactory::GetGlobalRegistryPtr()); | ||
| 413 | + return SUCCESS; | ||
| 414 | +} | ||
| 415 | + | ||
| 213 | void WriteTextFileForCustomOpSt(const std::string &file_path, const std::string &content) { | 416 | void WriteTextFileForCustomOpSt(const std::string &file_path, const std::string &content) { |
| 214 | std::ofstream file(file_path, std::ios::out | std::ios::trunc); | 417 | std::ofstream file(file_path, std::ios::out | std::ios::trunc); |
| 215 | ASSERT_TRUE(file.is_open()); | 418 | ASSERT_TRUE(file.is_open()); |
| @@ -235,21 +438,30 @@ const std::string &GetSharedPybindCustomOpMarkerFilePathForSt() { | |||
| 235 | return path; | 438 | return path; |
| 236 | } | 439 | } |
| 237 | 440 | ||
| 441 | +const std::string &GetInvalidSignaturePybindCustomOpFilePathForSt() { | ||
| 442 | + static ScopedTempDirForCustomOpSt dir; | ||
| 443 | + static const std::string path = dir.CreateFilePath("pybind_invalid_signature_custom_op.py"); | ||
| 444 | + return path; | ||
| 445 | +} | ||
| 446 | + | ||
| 238 | void EnsureSharedPybindCustomOpFileForSt() { | 447 | void EnsureSharedPybindCustomOpFileForSt() { |
| 239 | static std::once_flag once; | 448 | static std::once_flag once; |
| 240 | std::call_once(once, []() { | 449 | std::call_once(once, []() { |
| 241 | - WriteTextFileForCustomOpSt(GetSharedPybindCustomOpFilePathForSt(), | 450 | + const auto python_file = std::string(kSharedPybindCustomOpPreambleForSt) + |
| 242 | - "from pathlib import Path\n" | 451 | + GetSharedPybindCustomOpMarkerFilePathForSt() + kSharedPybindEagerCustomOpForSt + |
| 243 | - "from ge.custom_op import EagerExecuteOp, register_op_impl\n\n" | 452 | + kPythonCustomOpTypeForSt + kSharedPybindAnnotatedArgsPrefixForSt + |
| 244 | - "MARKER_FILE = r'" + | 453 | + kPythonAnnotatedArgsOpTypeForSt + kSharedPybindAnnotatedArgsBodyForSt; |
| 245 | - GetSharedPybindCustomOpMarkerFilePathForSt() + | 454 | + WriteTextFileForCustomOpSt(GetSharedPybindCustomOpFilePathForSt(), python_file); |
| 246 | - "'\n\n" | 455 | + }); |
| 247 | - "@register_op_impl(op_type='" + | 456 | +} |
| 248 | - std::string(kPythonCustomOpTypeForSt) + | 457 | + |
| 249 | - "')\n" | 458 | +void EnsureInvalidSignaturePybindCustomOpFileForSt() { |
| 250 | - "class StPythonPybindRemoveCoverageCustomOp(EagerExecuteOp):\n" | 459 | + static std::once_flag once; |
| 251 | - " def execute(self, ctx):\n" | 460 | + std::call_once(once, []() { |
| 252 | - " Path(MARKER_FILE).write_text('executed', encoding='utf-8')\n"); | 461 | + const auto python_file = std::string(kInvalidSignaturePybindPreambleForSt) + kPythonCustomOpTypeForSt + |
| 462 | + kValidBeforeInvalidPybindCustomOpForSt + kPythonAnnotatedArgsBadAttrOpTypeForSt + | ||
| 463 | + kSharedPybindBadAttrCustomOpForSt; | ||
| 464 | + WriteTextFileForCustomOpSt(GetInvalidSignaturePybindCustomOpFilePathForSt(), python_file); | ||
| 253 | }); | 465 | }); |
| 254 | } | 466 | } |
| 255 | 467 | ||
| @@ -1540,6 +1752,29 @@ TEST_F(CustomOpRefreshTest, eager_only_op_with_malloc_read_only_dev_args) { | |||
| 1540 | ReInitGe(); | 1752 | ReInitGe(); |
| 1541 | } | 1753 | } |
| 1542 | 1754 | ||
| 1755 | +/** | ||
| 1756 | + * 用例描述:测试Python自定义算子在注册阶段拒绝与IR不匹配的回调签名。 | ||
| 1757 | + * 预置条件: | ||
| 1758 | + * 1. 构造一个合法legacy实现和一个属性名与REG_OP定义不一致的Python实现。 | ||
| 1759 | + * 测试步骤: | ||
| 1760 | + * 1. 通过LoadPythonCustomOps加载Python实现。 | ||
| 1761 | + * 2. 查询CustomOpFactory中是否存在该Python自定义算子creator。 | ||
| 1762 | + * 预期结果: | ||
| 1763 | + * 1. 注册阶段签名校验失败,LoadPythonCustomOps返回FAILED。 | ||
| 1764 | + * 2. CustomOpFactory中不存在合法或非法Python自定义算子creator。 | ||
| 1765 | + */ | ||
| 1766 | +TEST_F(CustomOpFactoryStTest, PythonCustomOpLoaderRejectsInvalidSignatureDuringRegistration) { | ||
| 1767 | + EnsureInvalidSignaturePybindCustomOpFileForSt(); | ||
| 1768 | + ScopedEnvVarForCustomOpSt scoped_custom_opp_path(kEnvPythonCustomOpPath, | ||
| 1769 | + GetInvalidSignaturePybindCustomOpFilePathForSt()); | ||
| 1770 | + | ||
| 1771 | + ASSERT_EQ(GePythonRuntimeManager::Instance().EnsureReady(), SUCCESS); | ||
| 1772 | + EXPECT_EQ(custom_op::LoadPythonCustomOps(), FAILED); | ||
| 1773 | + EXPECT_EQ(CustomOpFactory::CreateOrGetCustomOp(AscendString(kPythonCustomOpTypeForSt)), nullptr); | ||
| 1774 | + EXPECT_EQ(CustomOpFactory::CreateOrGetCustomOp(AscendString(kPythonAnnotatedArgsBadAttrOpTypeForSt)), nullptr); | ||
| 1775 | + custom_op::UnloadPythonCustomOps(); | ||
| 1776 | +} | ||
| 1777 | + | ||
| 1543 | /** | 1778 | /** |
| 1544 | * 用例描述:测试Python自定义算子通过loader注册、执行后,可以按类型移除注册信息和已创建实例。 | 1779 | * 用例描述:测试Python自定义算子通过loader注册、执行后,可以按类型移除注册信息和已创建实例。 |
| 1545 | * 预置条件: | 1780 | * 预置条件: |
| @@ -1581,6 +1816,165 @@ TEST_F(CustomOpFactoryStTest, remove_python_custom_ops_clears_creator_and_create | |||
| 1581 | EXPECT_EQ(CustomOpFactory::CreateOrGetCustomOp(op_type), nullptr); | 1816 | EXPECT_EQ(CustomOpFactory::CreateOrGetCustomOp(op_type), nullptr); |
| 1582 | } | 1817 | } |
| 1583 | 1818 | ||
| 1819 | +TEST_F(CustomOpFactoryStTest, PythonAnnotatedArgsCustomOpLoaderGeneratesTaskDef) { | ||
| 1820 | + EnsureSharedPybindCustomOpFileForSt(); | ||
| 1821 | + ScopedEnvVarForCustomOpSt scoped_custom_opp_path(kEnvPythonCustomOpPath, GetSharedPybindCustomOpFilePathForSt()); | ||
| 1822 | + | ||
| 1823 | + ASSERT_EQ(GePythonRuntimeManager::Instance().EnsureReady(), SUCCESS); | ||
| 1824 | + ASSERT_EQ(custom_op::LoadPythonCustomOps(), SUCCESS); | ||
| 1825 | + ScopedLoadedPythonCustomOpsForSt loaded_python_custom_ops; | ||
| 1826 | + | ||
| 1827 | + const AscendString op_type(kPythonAnnotatedArgsOpTypeForSt); | ||
| 1828 | + ASSERT_TRUE(CustomOpFactory::IsExistOp(op_type)); | ||
| 1829 | + auto *const base_op = CustomOpFactory::CreateOrGetCustomOp(op_type); | ||
| 1830 | + ASSERT_NE(base_op, nullptr); | ||
| 1831 | + EXPECT_NE(CustomOpCast<AnnotatedArgsOp>(base_op), nullptr); | ||
| 1832 | + EXPECT_EQ(CustomOpCast<EagerExecuteOp>(base_op), nullptr); | ||
| 1833 | + | ||
| 1834 | + std::vector<domi::TaskDef> tasks; | ||
| 1835 | + ASSERT_EQ(GeneratePythonAnnotatedArgsTaskForSt(kPythonAnnotatedArgsOpTypeForSt, 0, "Ascend910B", tasks), SUCCESS); | ||
| 1836 | + | ||
| 1837 | + ASSERT_EQ(tasks.size(), 1U); | ||
| 1838 | + const auto &task = tasks[0]; | ||
| 1839 | + EXPECT_EQ(task.stream_id(), 3U); | ||
| 1840 | + const auto &kernel = task.kernel(); | ||
| 1841 | + EXPECT_EQ(kernel.kernel_name(), "st_python_dla"); | ||
| 1842 | + EXPECT_EQ(kernel.stub_func(), "st_python_dla"); | ||
| 1843 | + EXPECT_EQ(kernel.block_dim(), 1U); | ||
| 1844 | + EXPECT_EQ(kernel.args_size(), 24U); | ||
| 1845 | + std::vector<ArgDesc> arg_descs; | ||
| 1846 | + ASSERT_EQ(ArgsFormatDescUtils::Parse(kernel.context().args_format(), arg_descs), GRAPH_SUCCESS); | ||
| 1847 | + ASSERT_EQ(arg_descs.size(), 3U); | ||
| 1848 | + EXPECT_EQ(arg_descs[0].addr_type, AddrType::INPUT_INSTANCE); | ||
| 1849 | + EXPECT_EQ(arg_descs[0].ir_idx, 0); | ||
| 1850 | + EXPECT_EQ(arg_descs[1].addr_type, AddrType::OUTPUT_INSTANCE); | ||
| 1851 | + EXPECT_EQ(arg_descs[1].ir_idx, 0); | ||
| 1852 | + EXPECT_EQ(arg_descs[2].addr_type, AddrType::CUSTOM_VALUE); | ||
| 1853 | + | ||
| 1854 | + custom_op::UnloadPythonCustomOps(); | ||
| 1855 | + loaded_python_custom_ops.Dismiss(); | ||
| 1856 | + EXPECT_FALSE(CustomOpFactory::IsExistOp(op_type)); | ||
| 1857 | +} | ||
| 1858 | + | ||
| 1859 | +TEST_F(CustomOpFactoryStTest, PythonAnnotatedArgsUnknownShapeGeneratesBasicTaskDef) { | ||
| 1860 | + EnsureSharedPybindCustomOpFileForSt(); | ||
| 1861 | + ScopedEnvVarForCustomOpSt scoped_custom_opp_path(kEnvPythonCustomOpPath, GetSharedPybindCustomOpFilePathForSt()); | ||
| 1862 | + | ||
| 1863 | + ASSERT_EQ(GePythonRuntimeManager::Instance().EnsureReady(), SUCCESS); | ||
| 1864 | + ASSERT_EQ(custom_op::LoadPythonCustomOps(), SUCCESS); | ||
| 1865 | + ScopedLoadedPythonCustomOpsForSt loaded_python_custom_ops; | ||
| 1866 | + | ||
| 1867 | + std::vector<domi::TaskDef> tasks; | ||
| 1868 | + ASSERT_EQ(GeneratePythonAnnotatedArgsTaskForSt(kPythonAnnotatedArgsOpTypeForSt, 0, "Ascend910B", tasks, true), | ||
| 1869 | + SUCCESS); | ||
| 1870 | + | ||
| 1871 | + ASSERT_EQ(tasks.size(), 1U); | ||
| 1872 | + const auto &task = tasks[0]; | ||
| 1873 | + EXPECT_EQ(task.stream_id(), 3U); | ||
| 1874 | + EXPECT_EQ(task.type(), static_cast<uint32_t>(ModelTaskType::MODEL_TASK_CUSTOM_KERNEL)); | ||
| 1875 | + EXPECT_EQ(task.sqe_num(), 5U); | ||
| 1876 | + EXPECT_EQ(task.kernel().context().op_index(), 0U); | ||
| 1877 | + EXPECT_TRUE(task.kernel().kernel_name().empty()); | ||
| 1878 | + EXPECT_TRUE(task.kernel().context().args_format().empty()); | ||
| 1879 | +} | ||
| 1880 | + | ||
| 1881 | +TEST_F(CustomOpFactoryStTest, PythonAnnotatedArgsRealCallbackRejectsInvalidNativeUsage) { | ||
| 1882 | + EnsureSharedPybindCustomOpFileForSt(); | ||
| 1883 | + ScopedEnvVarForCustomOpSt scoped_custom_opp_path(kEnvPythonCustomOpPath, GetSharedPybindCustomOpFilePathForSt()); | ||
| 1884 | + | ||
| 1885 | + ASSERT_EQ(GePythonRuntimeManager::Instance().EnsureReady(), SUCCESS); | ||
| 1886 | + ASSERT_EQ(custom_op::LoadPythonCustomOps(), SUCCESS); | ||
| 1887 | + ScopedLoadedPythonCustomOpsForSt loaded_python_custom_ops; | ||
| 1888 | + | ||
| 1889 | + for (const int64_t alpha : {1, 2, 3, 4, 5}) { | ||
| 1890 | + std::vector<domi::TaskDef> tasks; | ||
| 1891 | + EXPECT_NE(GeneratePythonAnnotatedArgsTaskForSt(kPythonAnnotatedArgsOpTypeForSt, alpha, "Ascend910B", tasks), | ||
| 1892 | + SUCCESS) | ||
| 1893 | + << "alpha=" << alpha; | ||
| 1894 | + EXPECT_TRUE(tasks.empty()) << "alpha=" << alpha; | ||
| 1895 | + } | ||
| 1896 | + | ||
| 1897 | + std::vector<domi::TaskDef> index_message_tasks; | ||
| 1898 | + EXPECT_EQ(GeneratePythonAnnotatedArgsTaskForSt(kPythonAnnotatedArgsOpTypeForSt, 9, "Ascend910B", index_message_tasks), | ||
| 1899 | + SUCCESS); | ||
| 1900 | + EXPECT_EQ(index_message_tasks.size(), 1U); | ||
| 1901 | +} | ||
| 1902 | + | ||
| 1903 | +TEST_F(CustomOpFactoryStTest, PythonAnnotatedArgsRealCallbackEnforcesBorrowedLifetime) { | ||
| 1904 | + EnsureSharedPybindCustomOpFileForSt(); | ||
| 1905 | + ScopedEnvVarForCustomOpSt scoped_custom_opp_path(kEnvPythonCustomOpPath, GetSharedPybindCustomOpFilePathForSt()); | ||
| 1906 | + | ||
| 1907 | + ASSERT_EQ(GePythonRuntimeManager::Instance().EnsureReady(), SUCCESS); | ||
| 1908 | + ASSERT_EQ(custom_op::LoadPythonCustomOps(), SUCCESS); | ||
| 1909 | + ScopedLoadedPythonCustomOpsForSt loaded_python_custom_ops; | ||
| 1910 | + | ||
| 1911 | + std::vector<domi::TaskDef> capture_tasks; | ||
| 1912 | + ASSERT_EQ(GeneratePythonAnnotatedArgsTaskForSt(kPythonAnnotatedArgsOpTypeForSt, 6, "Ascend910B", capture_tasks), | ||
| 1913 | + SUCCESS); | ||
| 1914 | + ASSERT_EQ(capture_tasks.size(), 1U); | ||
| 1915 | + EXPECT_EQ(capture_tasks[0].kernel().args_size(), 32U); | ||
| 1916 | + | ||
| 1917 | + std::vector<domi::TaskDef> verify_tasks; | ||
| 1918 | + ASSERT_EQ(GeneratePythonAnnotatedArgsTaskForSt(kPythonAnnotatedArgsOpTypeForSt, 7, "Ascend910B", verify_tasks), | ||
| 1919 | + SUCCESS); | ||
| 1920 | + ASSERT_EQ(verify_tasks.size(), 1U); | ||
| 1921 | + EXPECT_EQ(verify_tasks[0].kernel().args_size(), 24U); | ||
| 1922 | +} | ||
| 1923 | + | ||
| 1924 | +TEST_F(CustomOpFactoryStTest, PythonAnnotatedArgsMobileOmcAcceptsSingleLaunch) { | ||
| 1925 | + EnsureSharedPybindCustomOpFileForSt(); | ||
| 1926 | + ScopedEnvVarForCustomOpSt scoped_custom_opp_path(kEnvPythonCustomOpPath, GetSharedPybindCustomOpFilePathForSt()); | ||
| 1927 | + | ||
| 1928 | + ASSERT_EQ(GePythonRuntimeManager::Instance().EnsureReady(), SUCCESS); | ||
| 1929 | + ASSERT_EQ(custom_op::LoadPythonCustomOps(), SUCCESS); | ||
| 1930 | + ScopedLoadedPythonCustomOpsForSt loaded_python_custom_ops; | ||
| 1931 | + | ||
| 1932 | + std::vector<domi::TaskDef> tasks; | ||
| 1933 | + ASSERT_EQ(GeneratePythonAnnotatedArgsTaskForSt(kPythonAnnotatedArgsOpTypeForSt, 8, "KirinX90", tasks), SUCCESS); | ||
| 1934 | + ASSERT_EQ(tasks.size(), 1U); | ||
| 1935 | + EXPECT_EQ(tasks[0].type(), static_cast<uint32_t>(ModelTaskType::MODEL_TASK_CUSTOM_KERNEL)); | ||
| 1936 | + EXPECT_EQ(tasks[0].kernel().kernel_name(), "st_python_dla"); | ||
| 1937 | + EXPECT_EQ(tasks[0].kernel().args_size(), 24U); | ||
| 1938 | + const auto op_index = tasks[0].kernel().context().op_index(); | ||
| 1939 | + | ||
| 1940 | + DavinciModel model(0, nullptr); | ||
| 1941 | + std::vector<uint8_t> feature_mem(4096U, 0U); | ||
| 1942 | + model.runtime_param_.mem_base = reinterpret_cast<uintptr_t>(feature_mem.data()); | ||
| 1943 | + model.runtime_param_.mem_size = feature_mem.size(); | ||
| 1944 | + OpDescPtr op_desc; | ||
| 1945 | + ASSERT_EQ(AddPythonAnnotatedArgsOpToModelForSt(model, op_index, op_desc), SUCCESS); | ||
| 1946 | + ASSERT_EQ(model.GetOpByIndex(op_index), op_desc); | ||
| 1947 | + | ||
| 1948 | + auto ge_model = MakeShared<GeModel>(); | ||
| 1949 | + ASSERT_NE(ge_model, nullptr); | ||
| 1950 | + std::vector<char> kernel_bin = {0x01, 0x02}; | ||
| 1951 | + ge_model->GetTBEKernelStore().AddTBEKernel(MakeShared<OpKernelBin>("st_python_dla", std::move(kernel_bin))); | ||
| 1952 | + model.ge_model_ = ge_model; | ||
| 1953 | + | ||
| 1954 | + const uint64_t input_addr = model.runtime_param_.mem_base + 1024U; | ||
| 1955 | + const uint64_t output_addr = model.runtime_param_.mem_base + 2048U; | ||
| 1956 | + model.logical_mem_allocations_.push_back({0U, input_addr, 32U, MemAllocation::INPUT, 0U, 0U, 0U, 32U}); | ||
| 1957 | + model.logical_mem_allocations_.push_back({1U, output_addr, 32U, MemAllocation::OUTPUT, 0U, 0U, 0U, 0U}); | ||
| 1958 | + model.reusable_stream_allocator_ = ReusableStreamAllocator::Create(); | ||
| 1959 | + std::vector<rtStream_t> streams(4U, nullptr); | ||
| 1960 | + for (auto &stream : streams) { | ||
| 1961 | + ASSERT_EQ(model.reusable_stream_allocator_->GetOrCreateRtStream(stream, 0U, 0, 0U), SUCCESS); | ||
| 1962 | + } | ||
| 1963 | + model.stream_list_ = streams; | ||
| 1964 | + | ||
| 1965 | + CustomTaskInfo task_info; | ||
| 1966 | + TaskRunParam task_run_param; | ||
| 1967 | + ASSERT_EQ(task_info.ParseTaskRunParam(tasks[0], &model, task_run_param), SUCCESS); | ||
| 1968 | + std::vector<uint8_t> loaded_args(tasks[0].kernel().args().cbegin(), tasks[0].kernel().args().cend()); | ||
| 1969 | + PisToArgs args; | ||
| 1970 | + args[static_cast<size_t>(ArgsPlacement::kArgsPlacementHbm)].dev_addr = reinterpret_cast<uint64_t>(loaded_args.data()); | ||
| 1971 | + IowAddrs iow_addrs; | ||
| 1972 | + iow_addrs.input_logic_addrs = {{input_addr, static_cast<uint64_t>(MemoryAppType::kMemoryTypeFeatureMap)}}; | ||
| 1973 | + iow_addrs.output_logic_addrs = {{output_addr, static_cast<uint64_t>(MemoryAppType::kMemoryTypeFeatureMap)}}; | ||
| 1974 | + ASSERT_EQ(task_info.Init(tasks[0], &model, args, {}, iow_addrs), SUCCESS); | ||
| 1975 | + EXPECT_EQ(task_info.Distribute(), SUCCESS); | ||
| 1976 | +} | ||
| 1977 | + | ||
| 1584 | TEST_F(CustomOpFactoryStTest, load_python_custom_ops_if_needed_fails_for_missing_python_file) { | 1978 | TEST_F(CustomOpFactoryStTest, load_python_custom_ops_if_needed_fails_for_missing_python_file) { |
| 1585 | ScopedTempDirForCustomOpSt temp_dir; | 1979 | ScopedTempDirForCustomOpSt temp_dir; |
| 1586 | const auto missing_python_file = temp_dir.FilePath("missing_custom_op.py"); | 1980 | const auto missing_python_file = temp_dir.FilePath("missing_custom_op.py"); |
| @@ -24,11 +24,13 @@ | |||
| 24 | 24 | ||
| 25 | 25 | ||
| 26 | 26 | ||
| 27 | + | ||
| 27 | 28 | ||
| 28 | 29 | ||
| 29 | 30 | ||
| 30 | 31 | ||
| 31 | 32 | ||
| 33 | + | ||
| 32 | 34 | ||
| 33 | 35 | ||
| 34 | 36 | ||
| @@ -37,8 +39,17 @@ | |||
| 37 | 39 | ||
| 38 | 40 | ||
| 39 | 41 | ||
| 42 | + | ||
| 40 | 43 | ||
| 41 | 44 | ||
| 45 | +namespace ge { | ||
| 46 | +REG_OP(TestPythonAnnotatedArgsCustomOp_BuilderTest) | ||
| 47 | + .INPUT(x, TensorType::ALL()) | ||
| 48 | + .INPUT(w, TensorType::ALL()) | ||
| 49 | + .OUTPUT(y, TensorType::ALL()) | ||
| 50 | + .OP_END_FACTORY_REG(TestPythonAnnotatedArgsCustomOp_BuilderTest); | ||
| 51 | +} // namespace ge | ||
| 52 | + | ||
| 42 | namespace ge { | 53 | namespace ge { |
| 43 | namespace custom { | 54 | namespace custom { |
| 44 | 55 | ||
| @@ -92,6 +103,41 @@ class MockPortableCustomOp : public PortableOp { | |||
| 92 | } | 103 | } |
| 93 | }; | 104 | }; |
| 94 | 105 | ||
| 106 | +std::atomic_uint32_t g_python_annotated_args_declare_count{0U}; | ||
| 107 | + | ||
| 108 | +struct MockPythonAnnotatedArgsHolder {}; | ||
| 109 | + | ||
| 110 | +void *CreateMockPythonAnnotatedArgsHolder(const custom_op::PythonCustomOpDescriptor *desc) { | ||
| 111 | + return (desc == nullptr) ? nullptr : new (std::nothrow) MockPythonAnnotatedArgsHolder(); | ||
| 112 | +} | ||
| 113 | + | ||
| 114 | +void DestroyMockPythonAnnotatedArgsHolder(void *holder) { | ||
| 115 | + delete static_cast<MockPythonAnnotatedArgsHolder *>(holder); | ||
| 116 | +} | ||
| 117 | + | ||
| 118 | +graphStatus DeclareMockPythonAnnotatedArgs(const void *holder, gert::AnnotatedArgsContext *ctx) { | ||
| 119 | + if ((holder == nullptr) || (ctx == nullptr)) { | ||
| 120 | + return GRAPH_FAILED; | ||
| 121 | + } | ||
| 122 | + const auto *x = ctx->GetInputTensor(0U); | ||
| 123 | + const auto *w = ctx->GetInputTensor(1U); | ||
| 124 | + const auto *y = ctx->GetOutputTensor(0U); | ||
| 125 | + if ((x == nullptr) || (w == nullptr) || (y == nullptr)) { | ||
| 126 | + return GRAPH_FAILED; | ||
| 127 | + } | ||
| 128 | + ++g_python_annotated_args_declare_count; | ||
| 129 | + const auto workspace = ctx->MallocWorkSpace(64U); | ||
| 130 | + if (workspace.addr == nullptr) { | ||
| 131 | + return GRAPH_FAILED; | ||
| 132 | + } | ||
| 133 | + gert::AnnotatedKernelArgs args(gert::InputAddr{0U, x->GetAddr()}, gert::InputAddr{1U, w->GetAddr()}, | ||
| 134 | + gert::OutputAddr{0U, y->GetAddr()}, workspace, uint64_t{7U}); | ||
| 135 | + static const uint8_t kBin[] = {0x91U, 0x92U}; | ||
| 136 | + return ctx->AddLaunch( | ||
| 137 | + gert::AnnotatedKernelLaunchInfo{"python_annotated_args_ut", kBin, sizeof(kBin), 1U, ctx->GetStreamId()}, | ||
| 138 | + std::move(args)); | ||
| 139 | +} | ||
| 140 | + | ||
| 95 | std::atomic_bool g_compile_context_output_called{false}; | 141 | std::atomic_bool g_compile_context_output_called{false}; |
| 96 | constexpr uintptr_t kLogicDataMemBase = 0x80000000UL; | 142 | constexpr uintptr_t kLogicDataMemBase = 0x80000000UL; |
| 97 | constexpr uintptr_t kLogicWeightMemBase = 0x90000000UL; | 143 | constexpr uintptr_t kLogicWeightMemBase = 0x90000000UL; |
| @@ -108,6 +154,29 @@ uint16_t ReadUint16Slot(const std::string &args, const size_t index) { | |||
| 108 | return value; | 154 | return value; |
| 109 | } | 155 | } |
| 110 | 156 | ||
| 157 | +void ExpectPythonAnnotatedArgsKernel(const domi::KernelDef &kernel) { | ||
| 158 | + EXPECT_EQ(kernel.kernel_name(), "python_annotated_args_ut"); | ||
| 159 | + EXPECT_EQ(kernel.args_size(), 40U); | ||
| 160 | + EXPECT_EQ(ReadUint64Slot(kernel.args(), 0U), static_cast<uint64_t>(kLogicDataMemBase + 1024U)); | ||
| 161 | + EXPECT_EQ(ReadUint64Slot(kernel.args(), 1U), static_cast<uint64_t>(kLogicWeightMemBase + 4096U)); | ||
| 162 | + EXPECT_EQ(ReadUint64Slot(kernel.args(), 2U), static_cast<uint64_t>(kLogicDataMemBase + 2048U)); | ||
| 163 | + EXPECT_EQ(ReadUint64Slot(kernel.args(), 3U), static_cast<uint64_t>(kLogicDataMemBase + 4096U)); | ||
| 164 | + EXPECT_EQ(ReadUint64Slot(kernel.args(), 4U), 7U); | ||
| 165 | + | ||
| 166 | + std::vector<ArgDesc> arg_descs; | ||
| 167 | + ASSERT_EQ(ArgsFormatDescUtils::Parse(kernel.context().args_format(), arg_descs), GRAPH_SUCCESS); | ||
| 168 | + ASSERT_EQ(arg_descs.size(), 5U); | ||
| 169 | + EXPECT_EQ(arg_descs[0].addr_type, AddrType::INPUT_INSTANCE); | ||
| 170 | + EXPECT_EQ(arg_descs[0].ir_idx, 0); | ||
| 171 | + EXPECT_EQ(arg_descs[1].addr_type, AddrType::INPUT_INSTANCE); | ||
| 172 | + EXPECT_EQ(arg_descs[1].ir_idx, 1); | ||
| 173 | + EXPECT_EQ(arg_descs[2].addr_type, AddrType::OUTPUT_INSTANCE); | ||
| 174 | + EXPECT_EQ(arg_descs[2].ir_idx, 0); | ||
| 175 | + EXPECT_EQ(arg_descs[3].addr_type, AddrType::WORKSPACE); | ||
| 176 | + EXPECT_EQ(arg_descs[3].ir_idx, 0); | ||
| 177 | + EXPECT_EQ(arg_descs[4].addr_type, AddrType::CUSTOM_VALUE); | ||
| 178 | +} | ||
| 179 | + | ||
| 111 | class MockCompileContextOutputOp : public EagerExecuteOp, public CompilableOp { | 180 | class MockCompileContextOutputOp : public EagerExecuteOp, public CompilableOp { |
| 112 | public: | 181 | public: |
| 113 | graphStatus Execute(gert::EagerOpExecutionContext *ctx) override { | 182 | graphStatus Execute(gert::EagerOpExecutionContext *ctx) override { |
| @@ -991,6 +1060,9 @@ TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskDeclaresAnnotatedArgsAndFillsK | |||
| 991 | EXPECT_EQ(parsed_arg_descs[3].addr_type, AddrType::CUSTOM_VALUE); | 1060 | EXPECT_EQ(parsed_arg_descs[3].addr_type, AddrType::CUSTOM_VALUE); |
| 992 | 1061 | ||
| 993 | auto op_desc = node->GetOpDesc(); | 1062 | auto op_desc = node->GetOpDesc(); |
| 1063 | + int64_t task_args_mode = -1; | ||
| 1064 | + ASSERT_TRUE(AttrUtils::GetInt(op_desc, ATTR_NAME_CUSTOM_TASK_ARGS_MODE, task_args_mode)); | ||
| 1065 | + EXPECT_EQ(task_args_mode, static_cast<int64_t>(CustomTaskArgsMode::kAnnotatedArgs)); | ||
| 994 | std::vector<std::string> prefixes; | 1066 | std::vector<std::string> prefixes; |
| 995 | EXPECT_FALSE(AttrUtils::GetListStr(op_desc, ATTR_NAME_KERNEL_NAMES_PREFIX, prefixes)); | 1067 | EXPECT_FALSE(AttrUtils::GetListStr(op_desc, ATTR_NAME_KERNEL_NAMES_PREFIX, prefixes)); |
| 996 | const std::string prefixed_attr = "_custom_launch_0_"; | 1068 | const std::string prefixed_attr = "_custom_launch_0_"; |
| @@ -1016,6 +1088,46 @@ TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskDeclaresAnnotatedArgsAndFillsK | |||
| 1016 | EXPECT_EQ(std::memcmp(tbe_kernel->GetBinData(), expected_bin, sizeof(expected_bin)), 0); | 1088 | EXPECT_EQ(std::memcmp(tbe_kernel->GetBinData(), expected_bin, sizeof(expected_bin)), 0); |
| 1017 | } | 1089 | } |
| 1018 | 1090 | ||
| 1091 | +TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskUsesPythonAnnotatedArgsAdapter) { | ||
| 1092 | + GetThreadLocalContext().SetGraphOption({{ge::SOC_VERSION, "Ascend910B"}}); | ||
| 1093 | + const std::string kTestOpType = "TestPythonAnnotatedArgsCustomOp_BuilderTest"; | ||
| 1094 | + custom_op::PythonCustomOpDescriptor desc; | ||
| 1095 | + desc.descriptor_key = "python_annotated_args_custom_engine"; | ||
| 1096 | + desc.op_type = kTestOpType; | ||
| 1097 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kAnnotatedArgs); | ||
| 1098 | + | ||
| 1099 | + custom_op::PythonCustomOpCallbacks callbacks; | ||
| 1100 | + callbacks.create = CreateMockPythonAnnotatedArgsHolder; | ||
| 1101 | + callbacks.destroy = DestroyMockPythonAnnotatedArgsHolder; | ||
| 1102 | + callbacks.declare_launch_args = DeclareMockPythonAnnotatedArgs; | ||
| 1103 | + ASSERT_TRUE(custom_op::PythonCustomOpRuntimeRegistry::Register(desc, callbacks)); | ||
| 1104 | + const auto creator = [desc]() -> std::unique_ptr<BaseCustomOp> { | ||
| 1105 | + auto adapter = std::make_unique<custom_op::PythonCustomOpAdapter>(desc); | ||
| 1106 | + if (!adapter->IsValid()) { | ||
| 1107 | + return nullptr; | ||
| 1108 | + } | ||
| 1109 | + return adapter; | ||
| 1110 | + }; | ||
| 1111 | + ASSERT_EQ(CustomOpFactory::RegisterCustomOpCreator(AscendString(kTestOpType.c_str()), creator), GRAPH_SUCCESS); | ||
| 1112 | + | ||
| 1113 | + ComputeGraphPtr graph; | ||
| 1114 | + auto node = BuildStaticCustomNodeWithConstInput(kTestOpType, graph); | ||
| 1115 | + ASSERT_NE(node, nullptr); | ||
| 1116 | + EXPECT_EQ(CustomOpFactory::GetArgsRefreshStrategy(AscendString(kTestOpType.c_str())), | ||
| 1117 | + ArgsRefreshStrategy::kAnnotatedArgs); | ||
| 1118 | + | ||
| 1119 | + g_python_annotated_args_declare_count.store(0U); | ||
| 1120 | + std::vector<domi::TaskDef> tasks; | ||
| 1121 | + ASSERT_EQ(GenerateTaskForNode(node, tasks), SUCCESS); | ||
| 1122 | + ASSERT_EQ(tasks.size(), 1U); | ||
| 1123 | + EXPECT_EQ(g_python_annotated_args_declare_count.load(), 1U); | ||
| 1124 | + | ||
| 1125 | + ExpectPythonAnnotatedArgsKernel(tasks[0].kernel()); | ||
| 1126 | + | ||
| 1127 | + CustomOpFactory::RemoveCustomOps({AscendString(kTestOpType.c_str())}); | ||
| 1128 | + EXPECT_TRUE(custom_op::PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | ||
| 1129 | +} | ||
| 1130 | + | ||
| 1019 | TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskOnNonMobileSocDeclaresAnnotatedArgsOp) { | 1131 | TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskOnNonMobileSocDeclaresAnnotatedArgsOp) { |
| 1020 | GetThreadLocalContext().SetGraphOption({{ge::SOC_VERSION, "Ascend910B"}}); | 1132 | GetThreadLocalContext().SetGraphOption({{ge::SOC_VERSION, "Ascend910B"}}); |
| 1021 | 1133 | ||
| @@ -1346,6 +1458,9 @@ TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskOnNonMobileSocFillsBasicCustom | |||
| 1346 | EXPECT_EQ(task.type(), static_cast<uint32_t>(ModelTaskType::MODEL_TASK_CUSTOM_KERNEL)); | 1458 | EXPECT_EQ(task.type(), static_cast<uint32_t>(ModelTaskType::MODEL_TASK_CUSTOM_KERNEL)); |
| 1347 | EXPECT_EQ(task.sqe_num(), 5U); | 1459 | EXPECT_EQ(task.sqe_num(), 5U); |
| 1348 | EXPECT_EQ(task.kernel().context().op_index(), 7); | 1460 | EXPECT_EQ(task.kernel().context().op_index(), 7); |
| 1461 | + int64_t task_args_mode = -1; | ||
| 1462 | + ASSERT_TRUE(AttrUtils::GetInt(node->GetOpDesc(), ATTR_NAME_CUSTOM_TASK_ARGS_MODE, task_args_mode)); | ||
| 1463 | + EXPECT_EQ(task_args_mode, static_cast<int64_t>(CustomTaskArgsMode::kNone)); | ||
| 1349 | } | 1464 | } |
| 1350 | 1465 | ||
| 1351 | TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskOnNonMobileSocUsesUpdateCallbackWhenBothRefreshInterfacesExist) { | 1466 | TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskOnNonMobileSocUsesUpdateCallbackWhenBothRefreshInterfacesExist) { |
| @@ -1375,6 +1490,9 @@ TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskOnNonMobileSocUsesUpdateCallba | |||
| 1375 | EXPECT_TRUE(task.kernel().kernel_name().empty()); | 1490 | EXPECT_TRUE(task.kernel().kernel_name().empty()); |
| 1376 | EXPECT_TRUE(task.kernel().context().args_format().empty()); | 1491 | EXPECT_TRUE(task.kernel().context().args_format().empty()); |
| 1377 | EXPECT_EQ(g_both_refresh_interfaces_declare_count.load(), 0U); | 1492 | EXPECT_EQ(g_both_refresh_interfaces_declare_count.load(), 0U); |
| 1493 | + int64_t task_args_mode = -1; | ||
| 1494 | + ASSERT_TRUE(AttrUtils::GetInt(node->GetOpDesc(), ATTR_NAME_CUSTOM_TASK_ARGS_MODE, task_args_mode)); | ||
| 1495 | + EXPECT_EQ(task_args_mode, static_cast<int64_t>(CustomTaskArgsMode::kUpdateCallback)); | ||
| 1378 | } | 1496 | } |
| 1379 | 1497 | ||
| 1380 | TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskOnMobileSocRejectsUpdateCallbackWhenBothRefreshInterfacesExist) { | 1498 | TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskOnMobileSocRejectsUpdateCallbackWhenBothRefreshInterfacesExist) { |
| @@ -1852,6 +1970,9 @@ TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskOnUnknownGraphDoesNotValidateE | |||
| 1852 | EXPECT_EQ(task.kernel().context().op_index(), 7); | 1970 | EXPECT_EQ(task.kernel().context().op_index(), 7); |
| 1853 | EXPECT_TRUE(task.kernel().kernel_name().empty()); | 1971 | EXPECT_TRUE(task.kernel().kernel_name().empty()); |
| 1854 | EXPECT_TRUE(task.kernel().context().args_format().empty()); | 1972 | EXPECT_TRUE(task.kernel().context().args_format().empty()); |
| 1973 | + int64_t task_args_mode = -1; | ||
| 1974 | + ASSERT_TRUE(AttrUtils::GetInt(node->GetOpDesc(), ATTR_NAME_CUSTOM_TASK_ARGS_MODE, task_args_mode)); | ||
| 1975 | + EXPECT_EQ(task_args_mode, static_cast<int64_t>(CustomTaskArgsMode::kNone)); | ||
| 1855 | } | 1976 | } |
| 1856 | 1977 | ||
| 1857 | TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskOnUnknownGraphUsesBasicTaskForAnnotatedEagerOp) { | 1978 | TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskOnUnknownGraphUsesBasicTaskForAnnotatedEagerOp) { |
| @@ -1891,6 +2012,9 @@ TEST_F(UtestCustomOpsKernelInfoStore, GenerateTaskOnUnknownGraphUsesBasicTaskFor | |||
| 1891 | EXPECT_EQ(task.kernel().context().op_index(), 7); | 2012 | EXPECT_EQ(task.kernel().context().op_index(), 7); |
| 1892 | EXPECT_TRUE(task.kernel().kernel_name().empty()); | 2013 | EXPECT_TRUE(task.kernel().kernel_name().empty()); |
| 1893 | EXPECT_TRUE(task.kernel().context().args_format().empty()); | 2014 | EXPECT_TRUE(task.kernel().context().args_format().empty()); |
| 2015 | + int64_t task_args_mode = -1; | ||
| 2016 | + ASSERT_TRUE(AttrUtils::GetInt(node->GetOpDesc(), ATTR_NAME_CUSTOM_TASK_ARGS_MODE, task_args_mode)); | ||
| 2017 | + EXPECT_EQ(task_args_mode, static_cast<int64_t>(CustomTaskArgsMode::kNone)); | ||
| 1894 | } | 2018 | } |
| 1895 | } | 2019 | } |
| 1896 | 2020 | ||
| @@ -28,6 +28,7 @@ | |||
| 28 | 28 | ||
| 29 | 29 | ||
| 30 | 30 | ||
| 31 | + | ||
| 31 | 32 | ||
| 32 | 33 | ||
| 33 | 34 | ||
| @@ -1061,6 +1062,8 @@ TEST_F(UtestCustomTaskInfoE2E, ParseTaskRunParam_ArgsUpdater_SupportRefreshTrue) | |||
| 1061 | 1062 | ||
| 1062 | DavinciModel model(0, nullptr); | 1063 | DavinciModel model(0, nullptr); |
| 1063 | const auto op_desc = CreateOpDesc(op_type, op_type, 1, 1); | 1064 | const auto op_desc = CreateOpDesc(op_type, op_type, 1, 1); |
| 1065 | + ASSERT_TRUE(AttrUtils::SetInt(op_desc, ATTR_NAME_CUSTOM_TASK_ARGS_MODE, | ||
| 1066 | + static_cast<int64_t>(CustomTaskArgsMode::kUpdateCallback))); | ||
| 1064 | SetUpMinimalDavinciModel(model, op_desc); | 1067 | SetUpMinimalDavinciModel(model, op_desc); |
| 1065 | 1068 | ||
| 1066 | domi::TaskDef task_def; | 1069 | domi::TaskDef task_def; |
| @@ -1095,7 +1098,15 @@ TEST_F(UtestCustomTaskInfoE2E, ParseTaskRunParam_AnnotatedArgsOp_UsesAnnotatedAr | |||
| 1095 | 1098 | ||
| 1096 | DavinciModel model(0, nullptr); | 1099 | DavinciModel model(0, nullptr); |
| 1097 | const auto op_desc = CreateOpDesc(op_type, op_type, 1, 1); | 1100 | const auto op_desc = CreateOpDesc(op_type, op_type, 1, 1); |
| 1101 | + ASSERT_TRUE(AttrUtils::SetInt(op_desc, ATTR_NAME_CUSTOM_TASK_ARGS_MODE, | ||
| 1102 | + static_cast<int64_t>(CustomTaskArgsMode::kAnnotatedArgs))); | ||
| 1098 | SetUpMinimalDavinciModel(model, op_desc); | 1103 | SetUpMinimalDavinciModel(model, op_desc); |
| 1104 | + auto registry = std::make_shared<CustomOpRegistry>(); | ||
| 1105 | + ASSERT_EQ(registry->RegisterCreator( | ||
| 1106 | + op_type.c_str(), | ||
| 1107 | + []() -> std::unique_ptr<BaseCustomOp> { return std::make_unique<TestArgsUpdaterCustomOp>(); }), | ||
| 1108 | + GRAPH_SUCCESS); | ||
| 1109 | + model.SetCustomOpRegistry(registry); | ||
| 1099 | 1110 | ||
| 1100 | domi::TaskDef task_def; | 1111 | domi::TaskDef task_def; |
| 1101 | FillAnnotatedArgsTaskDef(task_def, op_desc->GetId(), BuildInputOutputCustomArgDescs(), {0ULL, 0x40ULL, 0x1234ULL}); | 1112 | FillAnnotatedArgsTaskDef(task_def, op_desc->GetId(), BuildInputOutputCustomArgDescs(), {0ULL, 0x40ULL, 0x1234ULL}); |
| @@ -1112,6 +1123,107 @@ TEST_F(UtestCustomTaskInfoE2E, ParseTaskRunParam_AnnotatedArgsOp_UsesAnnotatedAr | |||
| 1112 | model.runtime_param_.mem_base = 0U; | 1123 | model.runtime_param_.mem_base = 0U; |
| 1113 | } | 1124 | } |
| 1114 | 1125 | ||
| 1126 | +TEST_F(UtestCustomTaskInfoE2E, ParseTaskRunParam_AnnotatedArgsTaskDef_UsesTaskMetadataWithoutRegistryCreator) { | ||
| 1127 | + const std::string op_type = GenerateUniqueOpType(); | ||
| 1128 | + DavinciModel model(0, nullptr); | ||
| 1129 | + const auto op_desc = CreateOpDesc(op_type, op_type, 1, 1); | ||
| 1130 | + SetUpMinimalDavinciModel(model, op_desc); | ||
| 1131 | + model.SetCustomOpRegistry(std::make_shared<CustomOpRegistry>()); | ||
| 1132 | + | ||
| 1133 | + domi::TaskDef task_def; | ||
| 1134 | + FillAnnotatedArgsTaskDef(task_def, op_desc->GetId(), BuildInputOutputCustomArgDescs(), {0ULL, 0x40ULL, 0x1234ULL}); | ||
| 1135 | + | ||
| 1136 | + CustomTaskInfo task_info; | ||
| 1137 | + TaskRunParam task_run_param; | ||
| 1138 | + EXPECT_EQ(task_info.ParseTaskRunParam(task_def, &model, task_run_param), SUCCESS); | ||
| 1139 | + EXPECT_EQ(task_info.GetArgsRefreshStrategy(), ArgsRefreshStrategy::kAnnotatedArgs); | ||
| 1140 | + EXPECT_FALSE(task_info.NeedReserveArgsTable()); | ||
| 1141 | + ASSERT_FALSE(task_run_param.parsed_input_addrs.empty()); | ||
| 1142 | + EXPECT_TRUE(task_run_param.parsed_input_addrs[0].support_refresh); | ||
| 1143 | + | ||
| 1144 | + model.runtime_param_.mem_base = 0U; | ||
| 1145 | +} | ||
| 1146 | + | ||
| 1147 | +TEST_F(UtestCustomTaskInfoE2E, ParseTaskRunParam_ExplicitNoneMode_IgnoresLegacyArgsFormat) { | ||
| 1148 | + const std::string op_type = GenerateUniqueOpType(); | ||
| 1149 | + DavinciModel model(0, nullptr); | ||
| 1150 | + const auto op_desc = CreateOpDesc(op_type, op_type, 1, 1); | ||
| 1151 | + ASSERT_TRUE( | ||
| 1152 | + AttrUtils::SetInt(op_desc, ATTR_NAME_CUSTOM_TASK_ARGS_MODE, static_cast<int64_t>(CustomTaskArgsMode::kNone))); | ||
| 1153 | + SetUpMinimalDavinciModel(model, op_desc); | ||
| 1154 | + model.SetCustomOpRegistry(std::make_shared<CustomOpRegistry>()); | ||
| 1155 | + | ||
| 1156 | + domi::TaskDef task_def; | ||
| 1157 | + FillAnnotatedArgsTaskDef(task_def, op_desc->GetId(), BuildInputOutputCustomArgDescs(), {0ULL, 0x40ULL, 0x1234ULL}); | ||
| 1158 | + | ||
| 1159 | + CustomTaskInfo task_info; | ||
| 1160 | + TaskRunParam task_run_param; | ||
| 1161 | + EXPECT_EQ(task_info.ParseTaskRunParam(task_def, &model, task_run_param), SUCCESS); | ||
| 1162 | + EXPECT_EQ(task_info.GetArgsRefreshStrategy(), ArgsRefreshStrategy::kNone); | ||
| 1163 | + ASSERT_FALSE(task_run_param.parsed_input_addrs.empty()); | ||
| 1164 | + EXPECT_FALSE(task_run_param.parsed_input_addrs[0].support_refresh); | ||
| 1165 | + | ||
| 1166 | + model.runtime_param_.mem_base = 0U; | ||
| 1167 | +} | ||
| 1168 | + | ||
| 1169 | +TEST_F(UtestCustomTaskInfoE2E, ParseTaskRunParam_UnspecifiedMode_UsesLegacyArgsFormatFallback) { | ||
| 1170 | + const std::string op_type = GenerateUniqueOpType(); | ||
| 1171 | + DavinciModel model(0, nullptr); | ||
| 1172 | + const auto op_desc = CreateOpDesc(op_type, op_type, 1, 1); | ||
| 1173 | + ASSERT_TRUE(AttrUtils::SetInt(op_desc, ATTR_NAME_CUSTOM_TASK_ARGS_MODE, | ||
| 1174 | + static_cast<int64_t>(CustomTaskArgsMode::kUnspecified))); | ||
| 1175 | + SetUpMinimalDavinciModel(model, op_desc); | ||
| 1176 | + model.SetCustomOpRegistry(std::make_shared<CustomOpRegistry>()); | ||
| 1177 | + | ||
| 1178 | + domi::TaskDef task_def; | ||
| 1179 | + FillAnnotatedArgsTaskDef(task_def, op_desc->GetId(), BuildInputOutputCustomArgDescs(), {0ULL, 0x40ULL, 0x1234ULL}); | ||
| 1180 | + | ||
| 1181 | + CustomTaskInfo task_info; | ||
| 1182 | + TaskRunParam task_run_param; | ||
| 1183 | + EXPECT_EQ(task_info.ParseTaskRunParam(task_def, &model, task_run_param), SUCCESS); | ||
| 1184 | + EXPECT_EQ(task_info.GetArgsRefreshStrategy(), ArgsRefreshStrategy::kAnnotatedArgs); | ||
| 1185 | + | ||
| 1186 | + model.runtime_param_.mem_base = 0U; | ||
| 1187 | +} | ||
| 1188 | + | ||
| 1189 | +TEST_F(UtestCustomTaskInfoE2E, ParseTaskRunParam_ExplicitUpdateCallbackWithoutRegistryCreator_Fails) { | ||
| 1190 | + const std::string op_type = GenerateUniqueOpType(); | ||
| 1191 | + DavinciModel model(0, nullptr); | ||
| 1192 | + const auto op_desc = CreateOpDesc(op_type, op_type, 1, 1); | ||
| 1193 | + ASSERT_TRUE(AttrUtils::SetInt(op_desc, ATTR_NAME_CUSTOM_TASK_ARGS_MODE, | ||
| 1194 | + static_cast<int64_t>(CustomTaskArgsMode::kUpdateCallback))); | ||
| 1195 | + SetUpMinimalDavinciModel(model, op_desc); | ||
| 1196 | + model.SetCustomOpRegistry(std::make_shared<CustomOpRegistry>()); | ||
| 1197 | + | ||
| 1198 | + domi::TaskDef task_def; | ||
| 1199 | + task_def.set_type(static_cast<uint32_t>(ModelTaskType::MODEL_TASK_CUSTOM_KERNEL)); | ||
| 1200 | + task_def.mutable_kernel()->mutable_context()->set_op_index(op_desc->GetId()); | ||
| 1201 | + | ||
| 1202 | + CustomTaskInfo task_info; | ||
| 1203 | + TaskRunParam task_run_param; | ||
| 1204 | + EXPECT_NE(task_info.ParseTaskRunParam(task_def, &model, task_run_param), SUCCESS); | ||
| 1205 | + | ||
| 1206 | + model.runtime_param_.mem_base = 0U; | ||
| 1207 | +} | ||
| 1208 | + | ||
| 1209 | +TEST_F(UtestCustomTaskInfoE2E, ParseTaskRunParam_InvalidExplicitMode_Fails) { | ||
| 1210 | + const std::string op_type = GenerateUniqueOpType(); | ||
| 1211 | + DavinciModel model(0, nullptr); | ||
| 1212 | + const auto op_desc = CreateOpDesc(op_type, op_type, 1, 1); | ||
| 1213 | + ASSERT_TRUE(AttrUtils::SetInt(op_desc, ATTR_NAME_CUSTOM_TASK_ARGS_MODE, 99)); | ||
| 1214 | + SetUpMinimalDavinciModel(model, op_desc); | ||
| 1215 | + model.SetCustomOpRegistry(std::make_shared<CustomOpRegistry>()); | ||
| 1216 | + | ||
| 1217 | + domi::TaskDef task_def; | ||
| 1218 | + FillAnnotatedArgsTaskDef(task_def, op_desc->GetId(), BuildInputOutputCustomArgDescs(), {0ULL, 0x40ULL, 0x1234ULL}); | ||
| 1219 | + | ||
| 1220 | + CustomTaskInfo task_info; | ||
| 1221 | + TaskRunParam task_run_param; | ||
| 1222 | + EXPECT_NE(task_info.ParseTaskRunParam(task_def, &model, task_run_param), SUCCESS); | ||
| 1223 | + | ||
| 1224 | + model.runtime_param_.mem_base = 0U; | ||
| 1225 | +} | ||
| 1226 | + | ||
| 1115 | TEST_F(UtestCustomTaskInfoE2E, ParseTaskRunParam_EagerOnly_SupportRefreshFalse) { | 1227 | TEST_F(UtestCustomTaskInfoE2E, ParseTaskRunParam_EagerOnly_SupportRefreshFalse) { |
| 1116 | std::string op_type = GenerateUniqueOpType(); | 1228 | std::string op_type = GenerateUniqueOpType(); |
| 1117 | CustomOpFactory::RegisterCustomOpCreator( | 1229 | CustomOpFactory::RegisterCustomOpCreator( |
| @@ -1873,12 +1985,10 @@ TEST_F(UtestCustomTaskInfoE2E, Distribute_NonTensorInput_D2HToHost) { | |||
| 1873 | auto space_registries = gert::SpaceRegistryFaker().BuildMainSpaceRegistryArray(); | 1985 | auto space_registries = gert::SpaceRegistryFaker().BuildMainSpaceRegistryArray(); |
| 1874 | model.SetSpaceRegistries(space_registries); | 1986 | model.SetSpaceRegistries(space_registries); |
| 1875 | 1987 | ||
| 1876 | - std::vector<ArgDesc> arg_descs; | ||
| 1877 | - ArgsFormatDescUtils::Append(arg_descs, AddrType::INPUT, 0); | ||
| 1878 | - ArgsFormatDescUtils::Append(arg_descs, AddrType::INPUT, 1); | ||
| 1879 | - ArgsFormatDescUtils::Append(arg_descs, AddrType::OUTPUT, 0); | ||
| 1880 | domi::TaskDef task_def; | 1988 | domi::TaskDef task_def; |
| 1881 | - FillAnnotatedArgsTaskDef(task_def, op_desc->GetId(), arg_descs, {0ULL, 0x40ULL, 0x80ULL}); | 1989 | + task_def.set_type(static_cast<uint32_t>(ModelTaskType::MODEL_TASK_CUSTOM_KERNEL)); |
| 1990 | + task_def.set_stream_id(0); | ||
| 1991 | + task_def.mutable_kernel()->mutable_context()->set_op_index(op_desc->GetId()); | ||
| 1882 | 1992 | ||
| 1883 | CustomTaskInfo task_info; | 1993 | CustomTaskInfo task_info; |
| 1884 | TaskRunParam task_run_param; | 1994 | TaskRunParam task_run_param; |
| @@ -1917,12 +2027,10 @@ TEST_F(UtestCustomTaskInfoE2E, Distribute_WithoutInputKinds_AllDeviceHbm) { | |||
| 1917 | auto space_registries = gert::SpaceRegistryFaker().BuildMainSpaceRegistryArray(); | 2027 | auto space_registries = gert::SpaceRegistryFaker().BuildMainSpaceRegistryArray(); |
| 1918 | model.SetSpaceRegistries(space_registries); | 2028 | model.SetSpaceRegistries(space_registries); |
| 1919 | 2029 | ||
| 1920 | - std::vector<ArgDesc> arg_descs; | ||
| 1921 | - ArgsFormatDescUtils::Append(arg_descs, AddrType::INPUT, 0); | ||
| 1922 | - ArgsFormatDescUtils::Append(arg_descs, AddrType::INPUT, 1); | ||
| 1923 | - ArgsFormatDescUtils::Append(arg_descs, AddrType::OUTPUT, 0); | ||
| 1924 | domi::TaskDef task_def; | 2030 | domi::TaskDef task_def; |
| 1925 | - FillAnnotatedArgsTaskDef(task_def, op_desc->GetId(), arg_descs, {0ULL, 0x40ULL, 0x80ULL}); | 2031 | + task_def.set_type(static_cast<uint32_t>(ModelTaskType::MODEL_TASK_CUSTOM_KERNEL)); |
| 2032 | + task_def.set_stream_id(0); | ||
| 2033 | + task_def.mutable_kernel()->mutable_context()->set_op_index(op_desc->GetId()); | ||
| 1926 | 2034 | ||
| 1927 | CustomTaskInfo task_info; | 2035 | CustomTaskInfo task_info; |
| 1928 | TaskRunParam task_run_param; | 2036 | TaskRunParam task_run_param; |
| @@ -1958,12 +2066,10 @@ TEST_F(UtestCustomTaskInfoE2E, Distribute_WithExplicitNonTensorKindBase_D2HOnlyF | |||
| 1958 | auto space_registries = gert::SpaceRegistryFaker().BuildMainSpaceRegistryArray(); | 2066 | auto space_registries = gert::SpaceRegistryFaker().BuildMainSpaceRegistryArray(); |
| 1959 | model.SetSpaceRegistries(space_registries); | 2067 | model.SetSpaceRegistries(space_registries); |
| 1960 | 2068 | ||
| 1961 | - std::vector<ArgDesc> arg_descs; | ||
| 1962 | - ArgsFormatDescUtils::Append(arg_descs, AddrType::INPUT, 0); | ||
| 1963 | - ArgsFormatDescUtils::Append(arg_descs, AddrType::INPUT, 1); | ||
| 1964 | - ArgsFormatDescUtils::Append(arg_descs, AddrType::OUTPUT, 0); | ||
| 1965 | domi::TaskDef task_def; | 2069 | domi::TaskDef task_def; |
| 1966 | - FillAnnotatedArgsTaskDef(task_def, op_desc->GetId(), arg_descs, {0ULL, 0x40ULL, 0x80ULL}); | 2070 | + task_def.set_type(static_cast<uint32_t>(ModelTaskType::MODEL_TASK_CUSTOM_KERNEL)); |
| 2071 | + task_def.set_stream_id(0); | ||
| 2072 | + task_def.mutable_kernel()->mutable_context()->set_op_index(op_desc->GetId()); | ||
| 1967 | 2073 | ||
| 1968 | CustomTaskInfo task_info; | 2074 | CustomTaskInfo task_info; |
| 1969 | TaskRunParam task_run_param; | 2075 | TaskRunParam task_run_param; |
| @@ -0,0 +1,531 @@ | |||
| 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 declare_launch_args callbacks.""" | ||
| 14 | + | ||
| 15 | +import contextvars | ||
| 16 | +import importlib | ||
| 17 | +from pathlib import Path | ||
| 18 | +from types import SimpleNamespace | ||
| 19 | +from typing import List, Optional | ||
| 20 | + | ||
| 21 | +import pytest | ||
| 22 | + | ||
| 23 | +try: | ||
| 24 | + bridge = importlib.import_module("ge.custom_op._bridge") | ||
| 25 | + context = importlib.import_module("ge.custom_op.context") | ||
| 26 | + custom_op = importlib.import_module("ge.custom_op") | ||
| 27 | + from ge.graph import DataType | ||
| 28 | + from ge.runtime import Tensor | ||
| 29 | +except ImportError as exc: | ||
| 30 | + pytest.skip(f"无法导入 Python custom op 相关模块: {exc}", allow_module_level=True) | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | +def clear_python_custom_op_runtime(): | ||
| 35 | + custom_op.clear_registered_op_impls() | ||
| 36 | + bridge.clear_op_impl_holders() | ||
| 37 | + yield | ||
| 38 | + bridge.clear_op_impl_holders() | ||
| 39 | + custom_op.clear_registered_op_impls() | ||
| 40 | + | ||
| 41 | + | ||
| 42 | +class _FakeRuntimeAttrs: | ||
| 43 | + def __init__(self): | ||
| 44 | + self.calls = [] | ||
| 45 | + | ||
| 46 | + def __getattr__(self, name): | ||
| 47 | + if not name.startswith("get_"): | ||
| 48 | + raise AttributeError(name) | ||
| 49 | + return lambda index: self._record(name, index) | ||
| 50 | + | ||
| 51 | + def _record(self, name, index): | ||
| 52 | + self.calls.append((name, index)) | ||
| 53 | + return (name, index) | ||
| 54 | + | ||
| 55 | + | ||
| 56 | +class _FakeDlaContext: | ||
| 57 | + def __init__(self): | ||
| 58 | + self.invalidated = False | ||
| 59 | + self.attrs_requested = False | ||
| 60 | + self.attrs = _FakeRuntimeAttrs() | ||
| 61 | + self.calls = [] | ||
| 62 | + | ||
| 63 | + def _get_required_input_tensor(self, ir_index): | ||
| 64 | + self.calls.append(("required_input", ir_index)) | ||
| 65 | + return ("required_input", ir_index) | ||
| 66 | + | ||
| 67 | + def _get_optional_input_tensor(self, ir_index): | ||
| 68 | + self.calls.append(("optional_input", ir_index)) | ||
| 69 | + return None | ||
| 70 | + | ||
| 71 | + def _get_dynamic_input_num(self, ir_index): | ||
| 72 | + self.calls.append(("dynamic_input_num", ir_index)) | ||
| 73 | + return 2 | ||
| 74 | + | ||
| 75 | + def _get_dynamic_input_tensor(self, ir_index, relative_index): | ||
| 76 | + self.calls.append(("dynamic_input", ir_index, relative_index)) | ||
| 77 | + return ("dynamic_input", ir_index, relative_index) | ||
| 78 | + | ||
| 79 | + def _get_required_output_tensor(self, ir_index): | ||
| 80 | + self.calls.append(("required_output", ir_index)) | ||
| 81 | + return ("required_output", ir_index) | ||
| 82 | + | ||
| 83 | + def _get_dynamic_output_num(self, ir_index): | ||
| 84 | + self.calls.append(("dynamic_output_num", ir_index)) | ||
| 85 | + return 2 | ||
| 86 | + | ||
| 87 | + def _get_dynamic_output_tensor(self, ir_index, relative_index): | ||
| 88 | + self.calls.append(("dynamic_output", ir_index, relative_index)) | ||
| 89 | + return ("dynamic_output", ir_index, relative_index) | ||
| 90 | + | ||
| 91 | + def _get_attrs(self): | ||
| 92 | + self.attrs_requested = True | ||
| 93 | + return self.attrs | ||
| 94 | + | ||
| 95 | + def _invalidate(self): | ||
| 96 | + self.invalidated = True | ||
| 97 | + | ||
| 98 | + | ||
| 99 | +def _create_holder(instance_id: str, op_type: str) -> None: | ||
| 100 | + descriptors = { | ||
| 101 | + item["op_type"]: item for item in bridge.load_and_get_op_impl_descriptors() | ||
| 102 | + } | ||
| 103 | + assert bridge.create_op_impl_holder( | ||
| 104 | + instance_id, descriptors[op_type]["descriptor_key"] | ||
| 105 | + ) | ||
| 106 | + | ||
| 107 | + | ||
| 108 | +def _get_descriptor_key(op_type: str) -> str: | ||
| 109 | + return next( | ||
| 110 | + item["descriptor_key"] | ||
| 111 | + for item in bridge.load_and_get_op_impl_descriptors() | ||
| 112 | + if item["op_type"] == op_type | ||
| 113 | + ) | ||
| 114 | + | ||
| 115 | + | ||
| 116 | +def _full_ir_meta(op_type: str) -> dict: | ||
| 117 | + return { | ||
| 118 | + "op_type": op_type, | ||
| 119 | + "inputs": [ | ||
| 120 | + {"name": "first", "kind": 0}, | ||
| 121 | + {"name": "maybe", "kind": 1}, | ||
| 122 | + {"name": "many", "kind": 2}, | ||
| 123 | + ], | ||
| 124 | + "attrs": [{"name": "alpha", "type": "VT_INT"}], | ||
| 125 | + "outputs": [ | ||
| 126 | + {"name": "result", "kind": 0}, | ||
| 127 | + {"name": "result_many", "kind": 1}, | ||
| 128 | + ], | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + | ||
| 132 | +def test_plain_class_declares_annotated_args_capability(): | ||
| 133 | + | ||
| 134 | + class DlaOnlyCustom: | ||
| 135 | + def declare_launch_args(self) -> None: | ||
| 136 | + pass | ||
| 137 | + | ||
| 138 | + assert DlaOnlyCustom.__ge_op_impl_descriptor__.interfaces == ["annotated_args"] | ||
| 139 | + | ||
| 140 | + | ||
| 141 | +def test_plain_execute_and_declare_registers_both_capabilities(): | ||
| 142 | + | ||
| 143 | + class PlainExecuteAndDlaCustom: | ||
| 144 | + def execute(self): | ||
| 145 | + pass | ||
| 146 | + | ||
| 147 | + def declare_launch_args(self) -> None: | ||
| 148 | + pass | ||
| 149 | + | ||
| 150 | + assert PlainExecuteAndDlaCustom.__ge_op_impl_descriptor__.interfaces == [ | ||
| 151 | + "eager_execute", | ||
| 152 | + "annotated_args", | ||
| 153 | + ] | ||
| 154 | + | ||
| 155 | + | ||
| 156 | +def test_dual_capability_is_registered_in_stable_order(): | ||
| 157 | + | ||
| 158 | + class DualCapabilityCustom(custom_op.EagerExecuteOp): | ||
| 159 | + def execute(self, ctx): | ||
| 160 | + pass | ||
| 161 | + | ||
| 162 | + def declare_launch_args(self) -> None: | ||
| 163 | + pass | ||
| 164 | + | ||
| 165 | + assert DualCapabilityCustom.__ge_op_impl_descriptor__.interfaces == [ | ||
| 166 | + "eager_execute", | ||
| 167 | + "annotated_args", | ||
| 168 | + ] | ||
| 169 | + | ||
| 170 | + | ||
| 171 | +def test_non_callable_declare_launch_args_does_not_satisfy_registration(): | ||
| 172 | + with pytest.raises(TypeError, match="must implement at least one supported method"): | ||
| 173 | + | ||
| 174 | + | ||
| 175 | + class BadDlaCustom: | ||
| 176 | + declare_launch_args = 1 | ||
| 177 | + | ||
| 178 | + | ||
| 179 | +def test_non_callable_declare_launch_args_is_ignored_with_valid_execute(): | ||
| 180 | + | ||
| 181 | + class ExecuteWithBadDlaCustom: | ||
| 182 | + declare_launch_args = 1 | ||
| 183 | + | ||
| 184 | + def execute(self): | ||
| 185 | + pass | ||
| 186 | + | ||
| 187 | + assert ExecuteWithBadDlaCustom.__ge_op_impl_descriptor__.interfaces == [ | ||
| 188 | + "eager_execute" | ||
| 189 | + ] | ||
| 190 | + | ||
| 191 | + | ||
| 192 | +def test_get_declare_launch_args_ctx_is_unavailable_outside_callback(): | ||
| 193 | + with pytest.raises(RuntimeError, match="only available inside declare_launch_args"): | ||
| 194 | + custom_op.get_declare_launch_args_ctx() | ||
| 195 | + | ||
| 196 | + | ||
| 197 | +def test_declare_launch_args_context_scope_restores_and_invalidates(): | ||
| 198 | + outer = object() | ||
| 199 | + inner = object() | ||
| 200 | + copied_contexts = [] | ||
| 201 | + context_scope = getattr(context, "_declare_launch_args_ctx_scope") | ||
| 202 | + | ||
| 203 | + with context_scope(outer): | ||
| 204 | + assert custom_op.get_declare_launch_args_ctx() is outer | ||
| 205 | + with context_scope(inner): | ||
| 206 | + assert custom_op.get_declare_launch_args_ctx() is inner | ||
| 207 | + copied_contexts.append(contextvars.copy_context()) | ||
| 208 | + assert custom_op.get_declare_launch_args_ctx() is outer | ||
| 209 | + | ||
| 210 | + with pytest.raises(RuntimeError, match="only available inside declare_launch_args"): | ||
| 211 | + custom_op.get_declare_launch_args_ctx() | ||
| 212 | + with pytest.raises(RuntimeError, match="only available inside declare_launch_args"): | ||
| 213 | + copied_contexts[0].run(custom_op.get_declare_launch_args_ctx) | ||
| 214 | + | ||
| 215 | + | ||
| 216 | +def test_call_declare_launch_args_binds_schema_arguments_and_context(): | ||
| 217 | + seen = [] | ||
| 218 | + | ||
| 219 | + | ||
| 220 | + class SchemaDlaCustom: | ||
| 221 | + def declare_launch_args( | ||
| 222 | + self, | ||
| 223 | + first: Tensor, | ||
| 224 | + maybe: Optional[Tensor], | ||
| 225 | + many: List[Tensor], | ||
| 226 | + result: Tensor, | ||
| 227 | + result_many: list[Tensor], | ||
| 228 | + *, | ||
| 229 | + alpha: int, | ||
| 230 | + ) -> None: | ||
| 231 | + _ = self | ||
| 232 | + seen.append( | ||
| 233 | + ( | ||
| 234 | + first, | ||
| 235 | + maybe, | ||
| 236 | + many, | ||
| 237 | + result, | ||
| 238 | + result_many, | ||
| 239 | + alpha, | ||
| 240 | + custom_op.get_declare_launch_args_ctx(), | ||
| 241 | + ) | ||
| 242 | + ) | ||
| 243 | + | ||
| 244 | + ctx = _FakeDlaContext() | ||
| 245 | + descriptor_key = _get_descriptor_key("SchemaDlaCustom") | ||
| 246 | + assert ( | ||
| 247 | + bridge.validate_op_impl_descriptor( | ||
| 248 | + descriptor_key, _full_ir_meta("SchemaDlaCustom") | ||
| 249 | + ) | ||
| 250 | + is True | ||
| 251 | + ) | ||
| 252 | + _create_holder("SchemaDlaCustom#1", "SchemaDlaCustom") | ||
| 253 | + | ||
| 254 | + assert ( | ||
| 255 | + bridge.call_declare_launch_args( | ||
| 256 | + "SchemaDlaCustom#1", _full_ir_meta("SchemaDlaCustom"), ctx | ||
| 257 | + ) | ||
| 258 | + is None | ||
| 259 | + ) | ||
| 260 | + assert seen == [ | ||
| 261 | + ( | ||
| 262 | + ("required_input", 0), | ||
| 263 | + None, | ||
| 264 | + [("dynamic_input", 2, 0), ("dynamic_input", 2, 1)], | ||
| 265 | + ("required_output", 0), | ||
| 266 | + [("dynamic_output", 1, 0), ("dynamic_output", 1, 1)], | ||
| 267 | + ("get_int", 0), | ||
| 268 | + ctx, | ||
| 269 | + ) | ||
| 270 | + ] | ||
| 271 | + assert ctx.calls == [ | ||
| 272 | + ("required_input", 0), | ||
| 273 | + ("optional_input", 1), | ||
| 274 | + ("dynamic_input_num", 2), | ||
| 275 | + ("dynamic_input", 2, 0), | ||
| 276 | + ("dynamic_input", 2, 1), | ||
| 277 | + ("required_output", 0), | ||
| 278 | + ("dynamic_output_num", 1), | ||
| 279 | + ("dynamic_output", 1, 0), | ||
| 280 | + ("dynamic_output", 1, 1), | ||
| 281 | + ] | ||
| 282 | + assert ctx.attrs.calls == [("get_int", 0)] | ||
| 283 | + assert ctx.invalidated is True | ||
| 284 | + | ||
| 285 | + | ||
| 286 | + | ||
| 287 | + "method_body, expected", | ||
| 288 | + [ | ||
| 289 | + ( | ||
| 290 | + "def declare_launch_args(self, first, *, alpha) -> None: pass", | ||
| 291 | + "expected 5 positional", | ||
| 292 | + ), | ||
| 293 | + ( | ||
| 294 | + "def declare_launch_args(self, first, maybe, many, result, result_many, alpha) -> None: pass", | ||
| 295 | + "keyword-only", | ||
| 296 | + ), | ||
| 297 | + ( | ||
| 298 | + "def declare_launch_args(self, first, maybe, many, result, result_many, *, beta) -> None: pass", | ||
| 299 | + "expected attr name", | ||
| 300 | + ), | ||
| 301 | + ( | ||
| 302 | + "def declare_launch_args(self, first, maybe, many, result, result_many, *args, alpha) -> None: pass", | ||
| 303 | + "variadic", | ||
| 304 | + ), | ||
| 305 | + ( | ||
| 306 | + "def declare_launch_args(self, first, maybe, many, result, result_many, *, alpha) -> int: pass", | ||
| 307 | + "expected None", | ||
| 308 | + ), | ||
| 309 | + ], | ||
| 310 | +) | ||
| 311 | +def test_validate_op_impl_descriptor_rejects_invalid_declare_signature( | ||
| 312 | + method_body, expected | ||
| 313 | +): | ||
| 314 | + namespace = {} | ||
| 315 | + exec(method_body, namespace) | ||
| 316 | + method = namespace.get("declare_launch_args") | ||
| 317 | + assert method is not None | ||
| 318 | + | ||
| 319 | + | ||
| 320 | + class InvalidSignature: | ||
| 321 | + declare_launch_args = method | ||
| 322 | + | ||
| 323 | + op_type = InvalidSignature.__ge_op_impl_descriptor__.op_type | ||
| 324 | + descriptor_key = _get_descriptor_key(op_type) | ||
| 325 | + | ||
| 326 | + with pytest.raises(TypeError) as exc_info: | ||
| 327 | + bridge.validate_op_impl_descriptor(descriptor_key, _full_ir_meta(op_type)) | ||
| 328 | + | ||
| 329 | + message = str(exc_info.value) | ||
| 330 | + assert op_type in message | ||
| 331 | + assert InvalidSignature.__ge_op_impl_descriptor__.descriptor_key in message | ||
| 332 | + assert "declare_launch_args" in message | ||
| 333 | + assert "expected" in message | ||
| 334 | + assert "actual" in message | ||
| 335 | + assert expected in message | ||
| 336 | + | ||
| 337 | + | ||
| 338 | +def test_call_declare_launch_args_does_not_validate_signature_at_runtime( | ||
| 339 | + monkeypatch, | ||
| 340 | +): | ||
| 341 | + called = [] | ||
| 342 | + | ||
| 343 | + | ||
| 344 | + class RegistrationValidatedDlaCustom: | ||
| 345 | + | ||
| 346 | + def declare_launch_args( | ||
| 347 | + first: Tensor, | ||
| 348 | + maybe: Optional[Tensor], | ||
| 349 | + many: List[Tensor], | ||
| 350 | + result: Tensor, | ||
| 351 | + result_many: list[Tensor], | ||
| 352 | + *, | ||
| 353 | + alpha: int, | ||
| 354 | + ) -> None: | ||
| 355 | + called.append((first, maybe, many, result, result_many, alpha)) | ||
| 356 | + | ||
| 357 | + op_type = "RegistrationValidatedDlaCustom" | ||
| 358 | + descriptor_key = _get_descriptor_key(op_type) | ||
| 359 | + ir_meta = _full_ir_meta(op_type) | ||
| 360 | + assert bridge.validate_op_impl_descriptor(descriptor_key, ir_meta) is True | ||
| 361 | + assert bridge.create_op_impl_holder(f"{op_type}#1", descriptor_key) is True | ||
| 362 | + | ||
| 363 | + def fail_on_runtime_validation(*args, **kwargs): | ||
| 364 | + _ = (args, kwargs) | ||
| 365 | + pytest.fail("declare_launch_args must not validate its signature at runtime") | ||
| 366 | + | ||
| 367 | + monkeypatch.setattr(bridge, "_validate_args_signature", fail_on_runtime_validation) | ||
| 368 | + ctx = _FakeDlaContext() | ||
| 369 | + bridge.call_declare_launch_args(f"{op_type}#1", ir_meta, ctx) | ||
| 370 | + | ||
| 371 | + assert len(called) == 1 | ||
| 372 | + assert ctx.invalidated is True | ||
| 373 | + | ||
| 374 | + | ||
| 375 | +def test_validate_op_impl_descriptor_requires_canonical_ir_for_declare(): | ||
| 376 | + | ||
| 377 | + class MissingRegistrationDlaSchemaCustom: | ||
| 378 | + def declare_launch_args(self, x: Tensor) -> None: | ||
| 379 | + pass | ||
| 380 | + | ||
| 381 | + with pytest.raises( | ||
| 382 | + RuntimeError, | ||
| 383 | + match="canonical IR not found for schema-bound declare_launch_args", | ||
| 384 | + ): | ||
| 385 | + bridge.validate_op_impl_descriptor( | ||
| 386 | + _get_descriptor_key("MissingRegistrationDlaSchemaCustom"), None | ||
| 387 | + ) | ||
| 388 | + | ||
| 389 | + | ||
| 390 | +def test_call_declare_launch_args_rejects_missing_schema_and_non_none_result(): | ||
| 391 | + | ||
| 392 | + class MissingSchemaDlaCustom: | ||
| 393 | + def declare_launch_args(self) -> None: | ||
| 394 | + _ = self | ||
| 395 | + return True | ||
| 396 | + | ||
| 397 | + ctx = _FakeDlaContext() | ||
| 398 | + _create_holder("MissingSchemaDlaCustom#1", "MissingSchemaDlaCustom") | ||
| 399 | + with pytest.raises( | ||
| 400 | + RuntimeError, match="canonical IR not found.*declare_launch_args" | ||
| 401 | + ): | ||
| 402 | + bridge.call_declare_launch_args("MissingSchemaDlaCustom#1", None, ctx) | ||
| 403 | + assert ctx.invalidated is True | ||
| 404 | + | ||
| 405 | + | ||
| 406 | + | ||
| 407 | + ("ir_type", "annotation"), | ||
| 408 | + [ | ||
| 409 | + ("VT_INT", int), | ||
| 410 | + ("VT_FLOAT", float), | ||
| 411 | + ("VT_BOOL", bool), | ||
| 412 | + ("VT_STRING", str), | ||
| 413 | + ("VT_DATA_TYPE", DataType), | ||
| 414 | + ("VT_TENSOR", Tensor), | ||
| 415 | + ("VT_LIST_INT", List[int]), | ||
| 416 | + ("VT_LIST_FLOAT", list[float]), | ||
| 417 | + ("VT_LIST_BOOL", List[bool]), | ||
| 418 | + ("VT_LIST_STRING", list[str]), | ||
| 419 | + ("VT_LIST_DATA_TYPE", List[DataType]), | ||
| 420 | + ("VT_LIST_LIST_INT", list[list[int]]), | ||
| 421 | + ], | ||
| 422 | +) | ||
| 423 | +def test_signature_accepts_canonical_attr_annotations(ir_type, annotation): | ||
| 424 | + def declare_launch_args(*, alpha) -> None: | ||
| 425 | + pass | ||
| 426 | + | ||
| 427 | + declare_launch_args.__annotations__ = {"alpha": annotation, "return": None} | ||
| 428 | + descriptor = SimpleNamespace( | ||
| 429 | + op_type="AttrAnnotationCustom", descriptor_key="attr-annotation" | ||
| 430 | + ) | ||
| 431 | + validate_args_signature = getattr(bridge, "_validate_args_signature") | ||
| 432 | + validate_args_signature( | ||
| 433 | + declare_launch_args, | ||
| 434 | + { | ||
| 435 | + "op_type": "AttrAnnotationCustom", | ||
| 436 | + "inputs": [], | ||
| 437 | + "attrs": [{"name": "alpha", "type": ir_type}], | ||
| 438 | + "outputs": [], | ||
| 439 | + }, | ||
| 440 | + descriptor, | ||
| 441 | + method_name="declare_launch_args", | ||
| 442 | + ) | ||
| 443 | + | ||
| 444 | + | ||
| 445 | +def test_signature_rejects_noncanonical_tensor_annotation(): | ||
| 446 | + | ||
| 447 | + class WrongTensorAnnotationCustom: | ||
| 448 | + def declare_launch_args(self, x: list[Tensor]) -> None: | ||
| 449 | + pass | ||
| 450 | + | ||
| 451 | + descriptor_key = _get_descriptor_key("WrongTensorAnnotationCustom") | ||
| 452 | + with pytest.raises(TypeError) as exc_info: | ||
| 453 | + bridge.validate_op_impl_descriptor( | ||
| 454 | + descriptor_key, | ||
| 455 | + { | ||
| 456 | + "op_type": "WrongTensorAnnotationCustom", | ||
| 457 | + "inputs": [{"name": "x", "kind": 0}], | ||
| 458 | + "attrs": [], | ||
| 459 | + "outputs": [], | ||
| 460 | + }, | ||
| 461 | + ) | ||
| 462 | + assert "WrongTensorAnnotationCustom" in str(exc_info.value) | ||
| 463 | + assert "expected" in str(exc_info.value) | ||
| 464 | + assert "actual" in str(exc_info.value) | ||
| 465 | + | ||
| 466 | + | ||
| 467 | + class NonNoneResultDlaCustom: | ||
| 468 | + def declare_launch_args(self) -> None: | ||
| 469 | + _ = self | ||
| 470 | + return True | ||
| 471 | + | ||
| 472 | + ctx = _FakeDlaContext() | ||
| 473 | + _create_holder("NonNoneResultDlaCustom#1", "NonNoneResultDlaCustom") | ||
| 474 | + with pytest.raises(TypeError, match="declare_launch_args must return None"): | ||
| 475 | + bridge.call_declare_launch_args( | ||
| 476 | + "NonNoneResultDlaCustom#1", | ||
| 477 | + { | ||
| 478 | + "op_type": "NonNoneResultDlaCustom", | ||
| 479 | + "inputs": [], | ||
| 480 | + "attrs": [], | ||
| 481 | + "outputs": [], | ||
| 482 | + }, | ||
| 483 | + ctx, | ||
| 484 | + ) | ||
| 485 | + assert ctx.invalidated is True | ||
| 486 | + | ||
| 487 | + | ||
| 488 | +def test_native_module_exposes_annotated_args_types(): | ||
| 489 | + native_module = getattr(importlib.import_module("ge.custom_op._native"), "_native") | ||
| 490 | + for type_name in ( | ||
| 491 | + "AnnotatedArgsContext", | ||
| 492 | + "AnnotatedKernelArgs", | ||
| 493 | + "AnnotatedKernelLaunchInfo", | ||
| 494 | + "WorkspaceAddr", | ||
| 495 | + ): | ||
| 496 | + assert hasattr(native_module, type_name) | ||
| 497 | + with pytest.raises(TypeError): | ||
| 498 | + native_module.AnnotatedArgsContext() | ||
| 499 | + with pytest.raises(TypeError): | ||
| 500 | + native_module.AnnotatedKernelArgs() | ||
| 501 | + with pytest.raises(TypeError): | ||
| 502 | + native_module.WorkspaceAddr() | ||
| 503 | + | ||
| 504 | + | ||
| 505 | +def test_public_stub_hides_bridge_private_attr_readers(): | ||
| 506 | + stub_path = Path(custom_op.__file__).with_name("_ge_custom_op_native.pyi") | ||
| 507 | + stub_text = stub_path.read_text(encoding="utf-8") | ||
| 508 | + annotated_context_stub = stub_text.split("class AnnotatedArgsContext:", 1)[1] | ||
| 509 | + assert "def _get_attrs" not in annotated_context_stub | ||
| 510 | + assert "def get_attrs" not in annotated_context_stub | ||
| 511 | + assert "def get_attr" not in annotated_context_stub | ||
| 512 | + assert "get_attrs" not in custom_op.__all__ | ||
| 513 | + | ||
| 514 | + | ||
| 515 | +def test_python_artifact_uses_bridge_abi_v1(): | ||
| 516 | + artifact_utils = importlib.import_module("ge.custom_op._artifact_utils") | ||
| 517 | + assert artifact_utils.BRIDGE_ABI_VERSION == 1 | ||
| 518 | + | ||
| 519 | + | ||
| 520 | +def test_launch_info_owns_and_validates_fields(): | ||
| 521 | + info = custom_op.AnnotatedKernelLaunchInfo( | ||
| 522 | + kernel_name="add_custom", | ||
| 523 | + kernel_bin=b"\x01\x02", | ||
| 524 | + block_dim=1, | ||
| 525 | + stream_id=0, | ||
| 526 | + ) | ||
| 527 | + assert info is not None | ||
| 528 | + with pytest.raises(ValueError, match="kernel_name"): | ||
| 529 | + custom_op.AnnotatedKernelLaunchInfo( | ||
| 530 | + kernel_name="", kernel_bin=b"\x01", block_dim=1, stream_id=0 | ||
| 531 | + ) | ||
| @@ -24,6 +24,7 @@ try: | |||
| 24 | bridge = importlib.import_module("ge.custom_op._bridge") | 24 | bridge = importlib.import_module("ge.custom_op._bridge") |
| 25 | custom_op = importlib.import_module("ge.custom_op") | 25 | custom_op = importlib.import_module("ge.custom_op") |
| 26 | ir_types = importlib.import_module("ge.custom_op._ir_types") | 26 | ir_types = importlib.import_module("ge.custom_op._ir_types") |
| 27 | + from ge.runtime import Tensor | ||
| 27 | except ImportError as exc: | 28 | except ImportError as exc: |
| 28 | pytest.skip(f"无法导入 Python custom op 相关模块: {exc}", allow_module_level=True) | 29 | pytest.skip(f"无法导入 Python custom op 相关模块: {exc}", allow_module_level=True) |
| 29 | 30 | ||
| @@ -125,6 +126,18 @@ def _create_holder(instance_id: str, op_type: str) -> None: | |||
| 125 | ) | 126 | ) |
| 126 | 127 | ||
| 127 | 128 | ||
| 129 | +def _get_descriptor_key(op_type: str) -> str: | ||
| 130 | + descriptors = { | ||
| 131 | + item["op_type"]: item for item in bridge.load_and_get_op_impl_descriptors() | ||
| 132 | + } | ||
| 133 | + return descriptors[op_type]["descriptor_key"] | ||
| 134 | + | ||
| 135 | + | ||
| 136 | +def test_bridge_validate_op_impl_descriptor_rejects_unknown_descriptor(): | ||
| 137 | + with pytest.raises(KeyError, match="descriptor_key not found"): | ||
| 138 | + bridge.validate_op_impl_descriptor("missing-descriptor", None) | ||
| 139 | + | ||
| 140 | + | ||
| 128 | def test_register_op_impl_exports_descriptor_dict(): | 141 | def test_register_op_impl_exports_descriptor_dict(): |
| 129 | 142 | ||
| 130 | class AddCustom(custom_op.EagerExecuteOp): | 143 | class AddCustom(custom_op.EagerExecuteOp): |
| @@ -179,6 +192,61 @@ def test_ctx_execute_keeps_context_argument(): | |||
| 179 | assert instance.seen_ctx is ctx | 192 | assert instance.seen_ctx is ctx |
| 180 | 193 | ||
| 181 | 194 | ||
| 195 | +def test_bridge_validates_descriptor_signature_without_constructing_instance(): | ||
| 196 | + constructed = [] | ||
| 197 | + | ||
| 198 | + | ||
| 199 | + class RegistrationValidatedCustom(custom_op.EagerExecuteOp): | ||
| 200 | + def __init__(self): | ||
| 201 | + constructed.append(True) | ||
| 202 | + | ||
| 203 | + def execute(self, x: Tensor, *, alpha: float): | ||
| 204 | + pass | ||
| 205 | + | ||
| 206 | + assert ( | ||
| 207 | + bridge.validate_op_impl_descriptor( | ||
| 208 | + _get_descriptor_key("RegistrationValidatedCustom"), | ||
| 209 | + { | ||
| 210 | + "op_type": "RegistrationValidatedCustom", | ||
| 211 | + "inputs": [{"name": "x", "kind": ir_types.InputType.REQUIRED}], | ||
| 212 | + "attrs": [{"name": "alpha", "type": ir_types.AttrType.FLOAT}], | ||
| 213 | + "outputs": [], | ||
| 214 | + }, | ||
| 215 | + ) | ||
| 216 | + is True | ||
| 217 | + ) | ||
| 218 | + assert constructed == [] | ||
| 219 | + | ||
| 220 | + | ||
| 221 | +def test_bridge_validates_legacy_execute_without_canonical_ir(): | ||
| 222 | + | ||
| 223 | + class RegistrationLegacyCustom(custom_op.EagerExecuteOp): | ||
| 224 | + def execute(self, ctx): | ||
| 225 | + pass | ||
| 226 | + | ||
| 227 | + assert ( | ||
| 228 | + bridge.validate_op_impl_descriptor( | ||
| 229 | + _get_descriptor_key("RegistrationLegacyCustom"), None | ||
| 230 | + ) | ||
| 231 | + is True | ||
| 232 | + ) | ||
| 233 | + | ||
| 234 | + | ||
| 235 | +def test_bridge_requires_canonical_ir_to_validate_schema_execute(): | ||
| 236 | + | ||
| 237 | + class MissingRegistrationSchemaCustom(custom_op.EagerExecuteOp): | ||
| 238 | + def execute(self, x): | ||
| 239 | + pass | ||
| 240 | + | ||
| 241 | + with pytest.raises( | ||
| 242 | + RuntimeError, | ||
| 243 | + match="canonical IR not found for schema-bound execute", | ||
| 244 | + ): | ||
| 245 | + bridge.validate_op_impl_descriptor( | ||
| 246 | + _get_descriptor_key("MissingRegistrationSchemaCustom"), None | ||
| 247 | + ) | ||
| 248 | + | ||
| 249 | + | ||
| 182 | def test_register_op_impl_rejects_duplicate_op_type(): | 250 | def test_register_op_impl_rejects_duplicate_op_type(): |
| 183 | 251 | ||
| 184 | class AddCustom(custom_op.EagerExecuteOp): | 252 | class AddCustom(custom_op.EagerExecuteOp): |
| @@ -220,7 +288,7 @@ def test_register_op_impl_supports_plain_class_with_execute(): | |||
| 220 | def test_register_op_impl_rejects_class_without_supported_method(): | 288 | def test_register_op_impl_rejects_class_without_supported_method(): |
| 221 | with pytest.raises( | 289 | with pytest.raises( |
| 222 | TypeError, | 290 | TypeError, |
| 223 | - match=r"BaseOnlyCustom' must implement at least one supported method: execute", | 291 | + match=r"BaseOnlyCustom' must implement at least one supported method: execute, declare_launch_args", |
| 224 | ): | 292 | ): |
| 225 | 293 | ||
| 226 | 294 | ||
| @@ -331,6 +399,7 @@ def test_bridge_call_execute_binds_schema_inputs_and_attrs(method_kind): | |||
| 331 | "outputs": [], | 399 | "outputs": [], |
| 332 | } | 400 | } |
| 333 | 401 | ||
| 402 | + assert bridge.validate_op_impl_descriptor(descriptor_key, ir_meta) is True | ||
| 334 | assert bridge.create_op_impl_holder(instance_id, descriptor_key) is True | 403 | assert bridge.create_op_impl_holder(instance_id, descriptor_key) is True |
| 335 | assert bridge.call_execute(instance_id, ir_meta, ctx) is None | 404 | assert bridge.call_execute(instance_id, ir_meta, ctx) is None |
| 336 | assert called == [ | 405 | assert called == [ |
| @@ -357,6 +426,111 @@ def test_bridge_call_execute_binds_schema_inputs_and_attrs(method_kind): | |||
| 357 | assert ctx.invalidated is True | 426 | assert ctx.invalidated is True |
| 358 | 427 | ||
| 359 | 428 | ||
| 429 | +def test_bridge_validate_op_impl_descriptor_rejects_mismatched_attr_name(): | ||
| 430 | + | ||
| 431 | + class WrongExecuteAttrNameCustom(custom_op.EagerExecuteOp): | ||
| 432 | + def execute(self, x, *, beta): | ||
| 433 | + pass | ||
| 434 | + | ||
| 435 | + with pytest.raises( | ||
| 436 | + TypeError, | ||
| 437 | + match=r"invalid execute signature.*expected attr name alpha.*actual attr name beta", | ||
| 438 | + ): | ||
| 439 | + bridge.validate_op_impl_descriptor( | ||
| 440 | + _get_descriptor_key("WrongExecuteAttrNameCustom"), | ||
| 441 | + { | ||
| 442 | + "op_type": "WrongExecuteAttrNameCustom", | ||
| 443 | + "inputs": [{"name": "x", "kind": ir_types.InputType.REQUIRED}], | ||
| 444 | + "attrs": [{"name": "alpha", "type": ir_types.AttrType.FLOAT}], | ||
| 445 | + "outputs": [], | ||
| 446 | + }, | ||
| 447 | + ) | ||
| 448 | + | ||
| 449 | + | ||
| 450 | +def test_bridge_validate_op_impl_descriptor_rejects_mismatched_input_annotation(): | ||
| 451 | + | ||
| 452 | + class WrongExecuteAnnotationCustom(custom_op.EagerExecuteOp): | ||
| 453 | + def execute(self, x: list[Tensor], *, alpha: float): | ||
| 454 | + pass | ||
| 455 | + | ||
| 456 | + with pytest.raises( | ||
| 457 | + TypeError, | ||
| 458 | + match=r"invalid execute signature.*input parameter at index 0 annotation", | ||
| 459 | + ): | ||
| 460 | + bridge.validate_op_impl_descriptor( | ||
| 461 | + _get_descriptor_key("WrongExecuteAnnotationCustom"), | ||
| 462 | + { | ||
| 463 | + "op_type": "WrongExecuteAnnotationCustom", | ||
| 464 | + "inputs": [{"name": "x", "kind": ir_types.InputType.REQUIRED}], | ||
| 465 | + "attrs": [{"name": "alpha", "type": ir_types.AttrType.FLOAT}], | ||
| 466 | + "outputs": [], | ||
| 467 | + }, | ||
| 468 | + ) | ||
| 469 | + | ||
| 470 | + | ||
| 471 | +def test_bridge_call_execute_ignores_ir_outputs_and_return_annotation(): | ||
| 472 | + called = [] | ||
| 473 | + | ||
| 474 | + | ||
| 475 | + class CompatibleExecuteSignatureCustom(custom_op.EagerExecuteOp): | ||
| 476 | + def execute( | ||
| 477 | + self, x: Tensor, *, alpha: float | ||
| 478 | + ) -> "CompatibleExecuteSignatureCustom": | ||
| 479 | + called.append((x, alpha)) | ||
| 480 | + return True | ||
| 481 | + | ||
| 482 | + instance_id = "CompatibleExecuteSignatureCustom#1" | ||
| 483 | + ctx = _FakeEagerContext() | ||
| 484 | + _create_holder(instance_id, "CompatibleExecuteSignatureCustom") | ||
| 485 | + | ||
| 486 | + assert ( | ||
| 487 | + bridge.call_execute( | ||
| 488 | + instance_id, | ||
| 489 | + { | ||
| 490 | + "op_type": "CompatibleExecuteSignatureCustom", | ||
| 491 | + "inputs": [{"name": "x", "kind": ir_types.InputType.REQUIRED}], | ||
| 492 | + "attrs": [{"name": "alpha", "type": ir_types.AttrType.FLOAT}], | ||
| 493 | + "outputs": [{"name": "y", "kind": ir_types.OutputType.REQUIRED}], | ||
| 494 | + }, | ||
| 495 | + ctx, | ||
| 496 | + ) | ||
| 497 | + is None | ||
| 498 | + ) | ||
| 499 | + | ||
| 500 | + assert called == [(("required", 0), ("get_float", 0))] | ||
| 501 | + assert ctx.invalidated is True | ||
| 502 | + | ||
| 503 | + | ||
| 504 | +def test_bridge_call_execute_does_not_validate_signature_at_runtime(monkeypatch): | ||
| 505 | + execute_calls = [] | ||
| 506 | + | ||
| 507 | + | ||
| 508 | + class CachedExecuteSignatureCustom(custom_op.EagerExecuteOp): | ||
| 509 | + def execute(self, x: Tensor): | ||
| 510 | + execute_calls.append(x) | ||
| 511 | + | ||
| 512 | + descriptor_key = _get_descriptor_key("CachedExecuteSignatureCustom") | ||
| 513 | + instance_id = "CachedExecuteSignatureCustom#1" | ||
| 514 | + ir_meta = { | ||
| 515 | + "op_type": "CachedExecuteSignatureCustom", | ||
| 516 | + "inputs": [{"name": "x", "kind": ir_types.InputType.REQUIRED}], | ||
| 517 | + "attrs": [], | ||
| 518 | + "outputs": [], | ||
| 519 | + } | ||
| 520 | + | ||
| 521 | + assert bridge.validate_op_impl_descriptor(descriptor_key, ir_meta) is True | ||
| 522 | + assert bridge.create_op_impl_holder(instance_id, descriptor_key) is True | ||
| 523 | + | ||
| 524 | + def fail_on_runtime_validation(*args, **kwargs): | ||
| 525 | + _ = (args, kwargs) | ||
| 526 | + pytest.fail("schema execute must not validate its signature at runtime") | ||
| 527 | + | ||
| 528 | + monkeypatch.setattr(bridge, "_validate_args_signature", fail_on_runtime_validation) | ||
| 529 | + bridge.call_execute(instance_id, ir_meta, _FakeEagerContext()) | ||
| 530 | + | ||
| 531 | + assert execute_calls == [("required", 0)] | ||
| 532 | + | ||
| 533 | + | ||
| 360 | def test_schema_bound_execute_can_get_current_context(): | 534 | def test_schema_bound_execute_can_get_current_context(): |
| 361 | called = [] | 535 | called = [] |
| 362 | 536 | ||
| @@ -411,6 +585,41 @@ def test_get_execute_ctx_is_not_bound_for_legacy_execute(): | |||
| 411 | assert ctx.invalidated is True | 585 | assert ctx.invalidated is True |
| 412 | 586 | ||
| 413 | 587 | ||
| 588 | +def test_bridge_call_execute_rechecks_legacy_signature_after_method_change(): | ||
| 589 | + calls = [] | ||
| 590 | + | ||
| 591 | + | ||
| 592 | + class ReplaceableExecuteCustom(custom_op.EagerExecuteOp): | ||
| 593 | + def execute(self, ctx): | ||
| 594 | + calls.append(("legacy", ctx)) | ||
| 595 | + | ||
| 596 | + instance_id = "ReplaceableExecuteCustom#1" | ||
| 597 | + legacy_ctx = _FakeEagerContext() | ||
| 598 | + _create_holder(instance_id, "ReplaceableExecuteCustom") | ||
| 599 | + | ||
| 600 | + bridge.call_execute(instance_id, None, legacy_ctx) | ||
| 601 | + | ||
| 602 | + def schema_execute(self, x): | ||
| 603 | + calls.append(("schema", x)) | ||
| 604 | + | ||
| 605 | + ReplaceableExecuteCustom.execute = schema_execute | ||
| 606 | + schema_ctx = _FakeEagerContext() | ||
| 607 | + bridge.call_execute( | ||
| 608 | + instance_id, | ||
| 609 | + { | ||
| 610 | + "op_type": "ReplaceableExecuteCustom", | ||
| 611 | + "inputs": [{"name": "x", "kind": ir_types.InputType.REQUIRED}], | ||
| 612 | + "attrs": [], | ||
| 613 | + "outputs": [], | ||
| 614 | + }, | ||
| 615 | + schema_ctx, | ||
| 616 | + ) | ||
| 617 | + | ||
| 618 | + assert calls == [("legacy", legacy_ctx), ("schema", ("required", 0))] | ||
| 619 | + assert legacy_ctx.invalidated is True | ||
| 620 | + assert schema_ctx.invalidated is True | ||
| 621 | + | ||
| 622 | + | ||
| 414 | def test_schema_execute_context_is_deactivated_after_exception(): | 623 | def test_schema_execute_context_is_deactivated_after_exception(): |
| 415 | copied_contexts = [] | 624 | copied_contexts = [] |
| 416 | 625 | ||
| @@ -106,6 +106,11 @@ graphStatus ExecuteMockPythonCustomOp(const void *holder, gert::EagerOpExecution | |||
| 106 | return GRAPH_SUCCESS; | 106 | return GRAPH_SUCCESS; |
| 107 | } | 107 | } |
| 108 | 108 | ||
| 109 | +graphStatus DeclareMockPythonCustomOp(const void *holder, gert::AnnotatedArgsContext *ctx) { | ||
| 110 | + (void)ctx; | ||
| 111 | + return (holder == nullptr) ? GRAPH_FAILED : GRAPH_SUCCESS; | ||
| 112 | +} | ||
| 113 | + | ||
| 109 | std::vector<uint8_t> BuildCustomOpPartition(const std::string &name, const std::vector<uint8_t> &bin) { | 114 | std::vector<uint8_t> BuildCustomOpPartition(const std::string &name, const std::vector<uint8_t> &bin) { |
| 110 | ge::CustomKernelItemHeader header{ge::kCustomKernelItemMagic, static_cast<uint32_t>(name.size()), | 115 | ge::CustomKernelItemHeader header{ge::kCustomKernelItemMagic, static_cast<uint32_t>(name.size()), |
| 111 | static_cast<uint32_t>(bin.size())}; | 116 | static_cast<uint32_t>(bin.size())}; |
| @@ -237,6 +242,7 @@ TEST(UtestCustomOpCast, filters_python_adapter_by_capability) { | |||
| 237 | 242 | ||
| 238 | BaseCustomOp *base = &adapter; | 243 | BaseCustomOp *base = &adapter; |
| 239 | EXPECT_NE(nullptr, CustomOpCast<EagerExecuteOp>(base)); | 244 | EXPECT_NE(nullptr, CustomOpCast<EagerExecuteOp>(base)); |
| 245 | + EXPECT_EQ(nullptr, CustomOpCast<AnnotatedArgsOp>(base)); | ||
| 240 | EXPECT_EQ(nullptr, CustomOpCast<CompilableOp>(base)); | 246 | EXPECT_EQ(nullptr, CustomOpCast<CompilableOp>(base)); |
| 241 | EXPECT_EQ(nullptr, CustomOpCast<ShapeInferOp>(base)); | 247 | EXPECT_EQ(nullptr, CustomOpCast<ShapeInferOp>(base)); |
| 242 | EXPECT_EQ(nullptr, CustomOpCast<PortableOp>(base)); | 248 | EXPECT_EQ(nullptr, CustomOpCast<PortableOp>(base)); |
| @@ -250,6 +256,54 @@ TEST(UtestCustomOpCast, filters_python_adapter_by_capability) { | |||
| 250 | EXPECT_FALSE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | 256 | EXPECT_FALSE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); |
| 251 | } | 257 | } |
| 252 | 258 | ||
| 259 | +TEST(UtestCustomOpCast, filters_python_adapter_annotated_args_by_capability) { | ||
| 260 | + PythonCustomOpDescriptor desc; | ||
| 261 | + desc.descriptor_key = "python_adapter_annotated_args_only"; | ||
| 262 | + desc.op_type = "PythonAdapterAnnotatedArgsOnly"; | ||
| 263 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kAnnotatedArgs); | ||
| 264 | + | ||
| 265 | + PythonCustomOpCallbacks callbacks; | ||
| 266 | + callbacks.create = CreateMockPythonCustomOpHolder; | ||
| 267 | + callbacks.destroy = DestroyMockPythonCustomOpHolder; | ||
| 268 | + callbacks.declare_launch_args = DeclareMockPythonCustomOp; | ||
| 269 | + | ||
| 270 | + ASSERT_TRUE(PythonCustomOpRuntimeRegistry::Register(desc, callbacks)); | ||
| 271 | + { | ||
| 272 | + PythonCustomOpAdapter adapter(desc); | ||
| 273 | + EXPECT_TRUE(adapter.IsValid()); | ||
| 274 | + | ||
| 275 | + BaseCustomOp *base = &adapter; | ||
| 276 | + EXPECT_EQ(nullptr, CustomOpCast<EagerExecuteOp>(base)); | ||
| 277 | + EXPECT_NE(nullptr, CustomOpCast<AnnotatedArgsOp>(base)); | ||
| 278 | + EXPECT_EQ(nullptr, CustomOpCast<CompilableOp>(base)); | ||
| 279 | + } | ||
| 280 | + EXPECT_TRUE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | ||
| 281 | +} | ||
| 282 | + | ||
| 283 | +TEST(UtestCustomOpCast, exposes_each_python_adapter_capability_in_dual_mode) { | ||
| 284 | + PythonCustomOpDescriptor desc; | ||
| 285 | + desc.descriptor_key = "python_adapter_dual_capability"; | ||
| 286 | + desc.op_type = "PythonAdapterDualCapability"; | ||
| 287 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); | ||
| 288 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kAnnotatedArgs); | ||
| 289 | + | ||
| 290 | + PythonCustomOpCallbacks callbacks; | ||
| 291 | + callbacks.create = CreateMockPythonCustomOpHolder; | ||
| 292 | + callbacks.destroy = DestroyMockPythonCustomOpHolder; | ||
| 293 | + callbacks.execute = ExecuteMockPythonCustomOp; | ||
| 294 | + callbacks.declare_launch_args = DeclareMockPythonCustomOp; | ||
| 295 | + | ||
| 296 | + ASSERT_TRUE(PythonCustomOpRuntimeRegistry::Register(desc, callbacks)); | ||
| 297 | + { | ||
| 298 | + PythonCustomOpAdapter adapter(desc); | ||
| 299 | + EXPECT_TRUE(adapter.IsValid()); | ||
| 300 | + BaseCustomOp *base = &adapter; | ||
| 301 | + EXPECT_NE(nullptr, CustomOpCast<EagerExecuteOp>(base)); | ||
| 302 | + EXPECT_NE(nullptr, CustomOpCast<AnnotatedArgsOp>(base)); | ||
| 303 | + } | ||
| 304 | + EXPECT_TRUE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | ||
| 305 | +} | ||
| 306 | + | ||
| 253 | TEST(UtestCustomOpCast, rejects_unsupported_python_adapter_capability) { | 307 | TEST(UtestCustomOpCast, rejects_unsupported_python_adapter_capability) { |
| 254 | PythonCustomOpDescriptor desc; | 308 | PythonCustomOpDescriptor desc; |
| 255 | desc.descriptor_key = "python_adapter_shape_unsupported"; | 309 | desc.descriptor_key = "python_adapter_shape_unsupported"; |
| @@ -11,12 +11,19 @@ | |||
| 11 | 11 | ||
| 12 | 12 | ||
| 13 | 13 | ||
| 14 | + | ||
| 14 | 15 | ||
| 16 | + | ||
| 17 | + | ||
| 15 | 18 | ||
| 19 | + | ||
| 16 | 20 | ||
| 17 | namespace ge { | 21 | namespace ge { |
| 18 | namespace custom_op { | 22 | namespace custom_op { |
| 19 | namespace { | 23 | namespace { |
| 24 | +namespace artifact = ::ge::python_artifact; | ||
| 25 | +namespace bridge_loader = ::ge::python_bridge_loader; | ||
| 26 | + | ||
| 20 | struct MockPythonCustomOpHolder {}; | 27 | struct MockPythonCustomOpHolder {}; |
| 21 | 28 | ||
| 22 | void *CreateMockPythonCustomOpHolder(const PythonCustomOpDescriptor *desc) { | 29 | void *CreateMockPythonCustomOpHolder(const PythonCustomOpDescriptor *desc) { |
| @@ -32,6 +39,109 @@ graphStatus ExecuteMockPythonCustomOp(const void *holder, gert::EagerOpExecution | |||
| 32 | EXPECT_NE(holder, nullptr); | 39 | EXPECT_NE(holder, nullptr); |
| 33 | return (holder != nullptr) ? GRAPH_SUCCESS : GRAPH_FAILED; | 40 | return (holder != nullptr) ? GRAPH_SUCCESS : GRAPH_FAILED; |
| 34 | } | 41 | } |
| 42 | + | ||
| 43 | +graphStatus DeclareMockPythonCustomOp(const void *holder, gert::AnnotatedArgsContext *ctx) { | ||
| 44 | + return ((holder != nullptr) && (ctx != nullptr)) ? GRAPH_SUCCESS : GRAPH_FAILED; | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +struct MockPythonCustomOpBridgeLoadState { | ||
| 48 | + PythonCustomOpBridgeApi api{}; | ||
| 49 | + const PythonCustomOpBridgeApi *api_to_return{nullptr}; | ||
| 50 | + uint32_t close_count{0U}; | ||
| 51 | + uint32_t set_config_count{0U}; | ||
| 52 | + uint32_t register_count{0U}; | ||
| 53 | +}; | ||
| 54 | + | ||
| 55 | +int g_mock_python_custom_op_bridge_handle = 0; | ||
| 56 | +MockPythonCustomOpBridgeLoadState g_mock_python_custom_op_bridge_load_state; | ||
| 57 | + | ||
| 58 | +Status SetMockPythonCustomOpArtifactConfig(const PythonCustomOpBridgeArtifactConfig *config) { | ||
| 59 | + if ((config == nullptr) || (config->artifact_root == nullptr) || (config->native_module_path == nullptr)) { | ||
| 60 | + return FAILED; | ||
| 61 | + } | ||
| 62 | + ++g_mock_python_custom_op_bridge_load_state.set_config_count; | ||
| 63 | + return SUCCESS; | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +Status RegisterMockPythonCustomOps(const PythonCustomOpRegistrar *registrar) { | ||
| 67 | + (void)registrar; | ||
| 68 | + ++g_mock_python_custom_op_bridge_load_state.register_count; | ||
| 69 | + return SUCCESS; | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +void ResetMockPythonCustomOpBridgeState() {} | ||
| 73 | + | ||
| 74 | +void ShutdownMockPythonCustomOpBridge() {} | ||
| 75 | + | ||
| 76 | +const PythonCustomOpBridgeApi *GetMockPythonCustomOpBridgeApi() { | ||
| 77 | + return g_mock_python_custom_op_bridge_load_state.api_to_return; | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +std::string ResolveMockPythonCustomOpBridgePath(const char *path) { | ||
| 81 | + return (path == nullptr) ? std::string() : std::string("/tmp/libge_python_custom_op_bridge.so"); | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +void *OpenMockPythonCustomOpBridge(const char *path, int flags) { | ||
| 85 | + (void)flags; | ||
| 86 | + return (path == nullptr) ? nullptr : static_cast<void *>(&g_mock_python_custom_op_bridge_handle); | ||
| 87 | +} | ||
| 88 | + | ||
| 89 | +int CloseMockPythonCustomOpBridge(void *handle) { | ||
| 90 | + if (handle != nullptr) { | ||
| 91 | + ++g_mock_python_custom_op_bridge_load_state.close_count; | ||
| 92 | + } | ||
| 93 | + return 0; | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +void *LookupMockPythonCustomOpBridgeSymbol(void *handle, const char *symbol) { | ||
| 97 | + if ((handle == nullptr) || (symbol == nullptr)) { | ||
| 98 | + return nullptr; | ||
| 99 | + } | ||
| 100 | + return reinterpret_cast<void *>(&GetMockPythonCustomOpBridgeApi); | ||
| 101 | +} | ||
| 102 | + | ||
| 103 | +artifact::PythonRuntimeKey ResolveMockPythonRuntimeKey() { | ||
| 104 | + return {}; | ||
| 105 | +} | ||
| 106 | + | ||
| 107 | +bool IsMockPythonCustomOpBridgeApiValid(const PythonCustomOpBridgeApi *api, const uint32_t expected_abi) { | ||
| 108 | + return (api != nullptr) && (api->abi_version == expected_abi) && (api->set_artifact_config != nullptr) && | ||
| 109 | + (api->register_custom_ops != nullptr) && (api->reset_bridge_state != nullptr) && | ||
| 110 | + (api->shutdown_bridge != nullptr); | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +bridge_loader::BridgeLoadDependencies MakeMockPythonCustomOpBridgeLoadDependencies() { | ||
| 114 | + return bridge_loader::BridgeLoadDependencies{ | ||
| 115 | + &ResolveMockPythonCustomOpBridgePath, &OpenMockPythonCustomOpBridge, | ||
| 116 | + &CloseMockPythonCustomOpBridge, &LookupMockPythonCustomOpBridgeSymbol, | ||
| 117 | + &ResolveMockPythonRuntimeKey, kPythonCustomOpBridgeGetApiSymbol, | ||
| 118 | + kPythonCustomOpBridgeAbiVersion, 0, | ||
| 119 | + }; | ||
| 120 | +} | ||
| 121 | + | ||
| 122 | +void ResetMockPythonCustomOpBridgeLoadState(const uint32_t abi_version) { | ||
| 123 | + g_mock_python_custom_op_bridge_load_state = MockPythonCustomOpBridgeLoadState{}; | ||
| 124 | + g_mock_python_custom_op_bridge_load_state.api = PythonCustomOpBridgeApi{ | ||
| 125 | + abi_version, | ||
| 126 | + &SetMockPythonCustomOpArtifactConfig, | ||
| 127 | + &RegisterMockPythonCustomOps, | ||
| 128 | + &ResetMockPythonCustomOpBridgeState, | ||
| 129 | + &ShutdownMockPythonCustomOpBridge, | ||
| 130 | + }; | ||
| 131 | + g_mock_python_custom_op_bridge_load_state.api_to_return = &g_mock_python_custom_op_bridge_load_state.api; | ||
| 132 | +} | ||
| 133 | + | ||
| 134 | +bridge_loader::BridgeLoadStatus LoadMockPythonCustomOpBridge( | ||
| 135 | + bridge_loader::LoadedBridgeCandidate<PythonCustomOpBridgeApi> &loaded_bridge) { | ||
| 136 | + const artifact::BridgeLibraryCandidate candidate{ | ||
| 137 | + "libge_python_custom_op_bridge.so", | ||
| 138 | + "/tmp/custom_op/python_custom_op_artifacts/cp311-linux-aarch64", | ||
| 139 | + "/tmp/custom_op/python_custom_op_artifacts/cp311-linux-aarch64/_ge_custom_op_native.so", | ||
| 140 | + }; | ||
| 141 | + return bridge_loader::TryLoadBridgeCandidate<PythonCustomOpBridgeApi, PythonCustomOpBridgeArtifactConfig>( | ||
| 142 | + artifact::PythonRuntimeKey{}, candidate, MakeMockPythonCustomOpBridgeLoadDependencies(), | ||
| 143 | + &IsMockPythonCustomOpBridgeApiValid, loaded_bridge); | ||
| 144 | +} | ||
| 35 | } // namespace | 145 | } // namespace |
| 36 | 146 | ||
| 37 | TEST(PythonCustomOpAdapter, forwards_execute_without_ir_meta_pod) { | 147 | TEST(PythonCustomOpAdapter, forwards_execute_without_ir_meta_pod) { |
| @@ -73,5 +183,40 @@ TEST(PythonCustomOpAdapter, keeps_legacy_execute_without_registered_ir) { | |||
| 73 | } | 183 | } |
| 74 | EXPECT_TRUE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | 184 | EXPECT_TRUE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); |
| 75 | } | 185 | } |
| 186 | + | ||
| 187 | +TEST(PythonCustomOpAdapter, validates_annotated_args_callback_by_capability) { | ||
| 188 | + PythonCustomOpDescriptor desc; | ||
| 189 | + desc.descriptor_key = "python_adapter_annotated_args_callback"; | ||
| 190 | + desc.op_type = "PythonCustomOpIrMetaUt"; | ||
| 191 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kAnnotatedArgs); | ||
| 192 | + | ||
| 193 | + PythonCustomOpCallbacks callbacks; | ||
| 194 | + callbacks.create = CreateMockPythonCustomOpHolder; | ||
| 195 | + callbacks.destroy = DestroyMockPythonCustomOpHolder; | ||
| 196 | + EXPECT_FALSE(callbacks.IsValid(desc.capabilities)); | ||
| 197 | + | ||
| 198 | + callbacks.declare_launch_args = DeclareMockPythonCustomOp; | ||
| 199 | + EXPECT_TRUE(callbacks.IsValid(desc.capabilities)); | ||
| 200 | + EXPECT_TRUE(PythonCustomOpRuntimeRegistry::Register(desc, callbacks)); | ||
| 201 | + EXPECT_TRUE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | ||
| 202 | +} | ||
| 203 | + | ||
| 204 | +TEST(PythonCustomOpBridgeAbi, rejects_mismatched_abi_before_registration_and_accepts_current) { | ||
| 205 | + bridge_loader::LoadedBridgeCandidate<PythonCustomOpBridgeApi> loaded_bridge; | ||
| 206 | + ResetMockPythonCustomOpBridgeLoadState(kPythonCustomOpBridgeAbiVersion + 1U); | ||
| 207 | + | ||
| 208 | + EXPECT_EQ(LoadMockPythonCustomOpBridge(loaded_bridge), bridge_loader::BridgeLoadStatus::kInvalidApi); | ||
| 209 | + EXPECT_EQ(g_mock_python_custom_op_bridge_load_state.close_count, 1U); | ||
| 210 | + EXPECT_EQ(g_mock_python_custom_op_bridge_load_state.set_config_count, 0U); | ||
| 211 | + EXPECT_EQ(g_mock_python_custom_op_bridge_load_state.register_count, 0U); | ||
| 212 | + | ||
| 213 | + ResetMockPythonCustomOpBridgeLoadState(kPythonCustomOpBridgeAbiVersion); | ||
| 214 | + ASSERT_EQ(LoadMockPythonCustomOpBridge(loaded_bridge), bridge_loader::BridgeLoadStatus::kSuccess); | ||
| 215 | + EXPECT_EQ(g_mock_python_custom_op_bridge_load_state.close_count, 0U); | ||
| 216 | + EXPECT_EQ(g_mock_python_custom_op_bridge_load_state.set_config_count, 1U); | ||
| 217 | + ASSERT_NE(loaded_bridge.api, nullptr); | ||
| 218 | + EXPECT_EQ(loaded_bridge.api->register_custom_ops(nullptr), SUCCESS); | ||
| 219 | + EXPECT_EQ(g_mock_python_custom_op_bridge_load_state.register_count, 1U); | ||
| 220 | +} | ||
| 76 | } // namespace custom_op | 221 | } // namespace custom_op |
| 77 | } // namespace ge | 222 | } // namespace ge |


只保留这部分修改即可,其余部分不在交付范围可认为已完成