Merged
feat: 添加自定义算子IR元信息收集能力 #4172
GengChaoAdd on Jul 28
feat: 添加自定义算子IR元信息收集能力 #4172
Merged
Total: 12 files changed+493-12
| @@ -15,7 +15,7 @@ | |||
| 15 | import sys | 15 | import sys |
| 16 | import threading | 16 | import threading |
| 17 | from dataclasses import dataclass | 17 | from dataclasses import dataclass |
| 18 | -from typing import Dict | 18 | +from typing import Dict, Optional |
| 19 | 19 | ||
| 20 | from .base import BaseCustomOp, EagerExecuteOp, EagerOpExecutionContext | 20 | from .base import BaseCustomOp, EagerExecuteOp, EagerOpExecutionContext |
| 21 | from .bootstrap import get_registered_op_impls, load_custom_op_plugins | 21 | from .bootstrap import get_registered_op_impls, load_custom_op_plugins |
| @@ -75,7 +75,12 @@ def destroy_op_impl_holder(instance_id: str) -> bool: | |||
| 75 | return _OP_IMPL_HOLDERS.pop(instance_id, None) is not None | 75 | return _OP_IMPL_HOLDERS.pop(instance_id, None) is not None |
| 76 | 76 | ||
| 77 | 77 | ||
| 78 | -def call_execute(instance_id: str, ctx: EagerOpExecutionContext) -> None: | 78 | +def call_execute( |
| 79 | + instance_id: str, | ||
| 80 | + ir_meta: Optional[dict], | ||
| 81 | + ctx: EagerOpExecutionContext, | ||
| 82 | +) -> None: | ||
| 83 | + del ir_meta | ||
| 79 | try: | 84 | try: |
| 80 | custom_op = _get_eager_execute_op(instance_id) | 85 | custom_op = _get_eager_execute_op(instance_id) |
| 81 | custom_op.execute(ctx) | 86 | custom_op.execute(ctx) |
| @@ -12,6 +12,7 @@ add_library(custom_op_runtime SHARED | |||
| 12 | "custom_op_loader.cc" | 12 | "custom_op_loader.cc" |
| 13 | "python_custom_op_adapter.cc" | 13 | "python_custom_op_adapter.cc" |
| 14 | "python_custom_op_bridge_loader.cc" | 14 | "python_custom_op_bridge_loader.cc" |
| 15 | + "python_custom_op_ir_meta.cc" | ||
| 15 | ) | 16 | ) |
| 16 | 17 | ||
| 17 | target_compile_definitions(custom_op_runtime PRIVATE | 18 | target_compile_definitions(custom_op_runtime PRIVATE |
| @@ -15,12 +15,106 @@ | |||
| 15 | #include <new> | 15 | #include <new> |
| 16 | #include <utility> | 16 | #include <utility> |
| 17 | 17 | ||
| 18 | +#include "common/checker.h" | ||
| 18 | #include "framework/common/debug/ge_log.h" | 19 | #include "framework/common/debug/ge_log.h" |
| 20 | +#include "graph/operator_factory.h" | ||
| 19 | #include "graph_metadef/graph/debug/ge_util.h" | 21 | #include "graph_metadef/graph/debug/ge_util.h" |
| 22 | +#include "runtime/custom_op/python_custom_op_ir_meta.h" | ||
| 20 | 23 | ||
| 21 | namespace ge { | 24 | namespace ge { |
| 22 | namespace custom_op { | 25 | namespace custom_op { |
| 26 | +class PythonCustomOpIrMetaViewStorage { | ||
| 27 | + public: | ||
| 28 | + graphStatus Initialize(const std::string &op_type) { | ||
| 29 | + GE_ASSERT_GRAPH_SUCCESS(CollectCustomOpIrMeta(op_type, ir_meta_)); | ||
| 30 | + | ||
| 31 | + input_views_.reserve(ir_meta_.inputs.size()); | ||
| 32 | + for (const auto &input : ir_meta_.inputs) { | ||
| 33 | + uint32_t kind = 0U; | ||
| 34 | + GE_ASSERT_GRAPH_SUCCESS(ConvertInputKind(input.kind, kind)); | ||
| 35 | + input_views_.emplace_back(PythonCustomOpIrInputView{input.name.c_str(), kind}); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + attr_views_.reserve(ir_meta_.attrs.size()); | ||
| 39 | + for (const auto &attr : ir_meta_.attrs) { | ||
| 40 | + attr_views_.emplace_back(PythonCustomOpIrAttrView{attr.name.c_str(), attr.type.c_str()}); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + output_views_.reserve(ir_meta_.outputs.size()); | ||
| 44 | + for (const auto &output : ir_meta_.outputs) { | ||
| 45 | + uint32_t kind = 0U; | ||
| 46 | + GE_ASSERT_GRAPH_SUCCESS(ConvertOutputKind(output.kind, kind)); | ||
| 47 | + output_views_.emplace_back(PythonCustomOpIrOutputView{output.name.c_str(), kind}); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + view_ = PythonCustomOpIrMetaView{ | ||
| 51 | + ir_meta_.op_type.c_str(), input_views_.empty() ? nullptr : input_views_.data(), | ||
| 52 | + input_views_.size(), attr_views_.empty() ? nullptr : attr_views_.data(), | ||
| 53 | + attr_views_.size(), output_views_.empty() ? nullptr : output_views_.data(), | ||
| 54 | + output_views_.size(), | ||
| 55 | + }; | ||
| 56 | + return GRAPH_SUCCESS; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + const PythonCustomOpIrMetaView &GetView() const { | ||
| 60 | + return view_; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + private: | ||
| 64 | + static graphStatus ConvertInputKind(const ge::IrInputType kind, uint32_t &value) { | ||
| 65 | + switch (kind) { | ||
| 66 | + case ge::kIrInputRequired: | ||
| 67 | + value = 0U; | ||
| 68 | + return GRAPH_SUCCESS; | ||
| 69 | + case ge::kIrInputOptional: | ||
| 70 | + value = 1U; | ||
| 71 | + return GRAPH_SUCCESS; | ||
| 72 | + case ge::kIrInputDynamic: | ||
| 73 | + value = 2U; | ||
| 74 | + return GRAPH_SUCCESS; | ||
| 75 | + default: | ||
| 76 | + GE_ASSERT_TRUE(false, "Unsupported custom op IR input kind[%u].", static_cast<uint32_t>(kind)); | ||
| 77 | + } | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + static graphStatus ConvertOutputKind(const ge::IrOutputType kind, uint32_t &value) { | ||
| 81 | + switch (kind) { | ||
| 82 | + case ge::kIrOutputRequired: | ||
| 83 | + value = 0U; | ||
| 84 | + return GRAPH_SUCCESS; | ||
| 85 | + case ge::kIrOutputDynamic: | ||
| 86 | + value = 1U; | ||
| 87 | + return GRAPH_SUCCESS; | ||
| 88 | + default: | ||
| 89 | + GE_ASSERT_TRUE(false, "Unsupported custom op IR output kind[%u].", static_cast<uint32_t>(kind)); | ||
| 90 | + } | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + CustomOpIrMeta ir_meta_; | ||
| 94 | + std::vector<PythonCustomOpIrInputView> input_views_; | ||
| 95 | + std::vector<PythonCustomOpIrAttrView> attr_views_; | ||
| 96 | + std::vector<PythonCustomOpIrOutputView> output_views_; | ||
| 97 | + PythonCustomOpIrMetaView view_{}; | ||
| 98 | +}; | ||
| 99 | + | ||
| 23 | namespace { | 100 | namespace { |
| 101 | +std::unique_ptr<PythonCustomOpIrMetaViewStorage> CreateIrMetaViewStorage(const std::string &op_type) { | ||
| 102 | + if (!OperatorFactory::IsExistOp(op_type.c_str())) { | ||
| 103 | + return nullptr; | ||
| 104 | + } | ||
| 105 | + auto storage = std::unique_ptr<PythonCustomOpIrMetaViewStorage>(new (std::nothrow) PythonCustomOpIrMetaViewStorage()); | ||
| 106 | + if (storage == nullptr) { | ||
| 107 | + GELOGE(GRAPH_FAILED, "Create custom op IR meta view storage failed, op type[%s].", op_type.c_str()); | ||
| 108 | + return nullptr; | ||
| 109 | + } | ||
| 110 | + const auto ret = storage->Initialize(op_type); | ||
| 111 | + if (ret != GRAPH_SUCCESS) { | ||
| 112 | + GELOGE(ret, "Initialize custom op IR meta view storage failed, op type[%s].", op_type.c_str()); | ||
| 113 | + return nullptr; | ||
| 114 | + } | ||
| 115 | + return storage; | ||
| 116 | +} | ||
| 117 | + | ||
| 24 | struct PythonCustomOpRuntimeEntry { | 118 | struct PythonCustomOpRuntimeEntry { |
| 25 | explicit PythonCustomOpRuntimeEntry(PythonCustomOpDescriptor d, PythonCustomOpCallbacks cb) | 119 | explicit PythonCustomOpRuntimeEntry(PythonCustomOpDescriptor d, PythonCustomOpCallbacks cb) |
| 26 | : desc(std::move(d)), callbacks(cb) {} | 120 | : desc(std::move(d)), callbacks(cb) {} |
| @@ -195,7 +289,9 @@ void PythonCustomOpRuntimeRegistry::Clear() { | |||
| 195 | } | 289 | } |
| 196 | 290 | ||
| 197 | PythonCustomOpAdapter::PythonCustomOpAdapter(PythonCustomOpDescriptor desc) | 291 | PythonCustomOpAdapter::PythonCustomOpAdapter(PythonCustomOpDescriptor desc) |
| 198 | - : desc_(std::move(desc)), holder_(new (std::nothrow) PythonCustomOpHolder(desc_)) {} | 292 | + : desc_(std::move(desc)), |
| 293 | + ir_meta_(CreateIrMetaViewStorage(desc_.op_type)), | ||
| 294 | + holder_(new (std::nothrow) PythonCustomOpHolder(desc_)) {} | ||
| 199 | 295 | ||
| 200 | PythonCustomOpAdapter::~PythonCustomOpAdapter() = default; | 296 | PythonCustomOpAdapter::~PythonCustomOpAdapter() = default; |
| 201 | 297 | ||
| @@ -217,7 +313,8 @@ graphStatus PythonCustomOpAdapter::Execute(gert::EagerOpExecutionContext *ctx) { | |||
| 217 | desc_.descriptor_key.c_str(), desc_.op_type.c_str()); | 313 | desc_.descriptor_key.c_str(), desc_.op_type.c_str()); |
| 218 | return GRAPH_FAILED; | 314 | return GRAPH_FAILED; |
| 219 | } | 315 | } |
| 220 | - return holder_->GetCallbacks().execute(holder_->GetHolder(), ctx); | 316 | + const auto *ir_meta = (ir_meta_ == nullptr) ? nullptr : &ir_meta_->GetView(); |
| 317 | + return holder_->GetCallbacks().execute(holder_->GetHolder(), ctx, ir_meta); | ||
| 221 | } | 318 | } |
| 222 | 319 | ||
| 223 | graphStatus PythonCustomOpAdapter::Compile(gert::OpCompileContext *ctx) { | 320 | graphStatus PythonCustomOpAdapter::Compile(gert::OpCompileContext *ctx) { |
| @@ -20,6 +20,8 @@ | |||
| 20 | 20 | ||
| 21 | namespace ge { | 21 | namespace ge { |
| 22 | namespace custom_op { | 22 | namespace custom_op { |
| 23 | +class PythonCustomOpIrMetaViewStorage; | ||
| 24 | + | ||
| 23 | class PythonCustomOpRuntimeRegistry { | 25 | class PythonCustomOpRuntimeRegistry { |
| 24 | public: | 26 | public: |
| 25 | static PythonCustomOpRuntimeRegistry &GetInstance(); | 27 | static PythonCustomOpRuntimeRegistry &GetInstance(); |
| @@ -80,6 +82,7 @@ class PythonCustomOpAdapter final : public EagerExecuteOp, | |||
| 80 | graphStatus ReportUnsupported(CustomOpCapability capability, const char *method_name) const; | 82 | graphStatus ReportUnsupported(CustomOpCapability capability, const char *method_name) const; |
| 81 | 83 | ||
| 82 | PythonCustomOpDescriptor desc_; | 84 | PythonCustomOpDescriptor desc_; |
| 85 | + std::unique_ptr<PythonCustomOpIrMetaViewStorage> ir_meta_; | ||
| 83 | std::unique_ptr<PythonCustomOpHolder> holder_; | 86 | std::unique_ptr<PythonCustomOpHolder> holder_; |
| 84 | }; | 87 | }; |
| 85 | 88 | ||
| @@ -11,6 +11,8 @@ | |||
| 11 | #ifndef CANN_GRAPH_ENGINE_RUNTIME_CUSTOM_OP_PYTHON_CUSTOM_OP_BRIDGE_TYPES_H_ | 11 | #ifndef CANN_GRAPH_ENGINE_RUNTIME_CUSTOM_OP_PYTHON_CUSTOM_OP_BRIDGE_TYPES_H_ |
| 12 | #define CANN_GRAPH_ENGINE_RUNTIME_CUSTOM_OP_PYTHON_CUSTOM_OP_BRIDGE_TYPES_H_ | 12 | #define CANN_GRAPH_ENGINE_RUNTIME_CUSTOM_OP_PYTHON_CUSTOM_OP_BRIDGE_TYPES_H_ |
| 13 | 13 | ||
| 14 | +#include <cstddef> | ||
| 15 | +#include <cstdint> | ||
| 14 | #include <string> | 16 | #include <string> |
| 15 | 17 | ||
| 16 | #include "graph/custom_op/capability.h" | 18 | #include "graph/custom_op/capability.h" |
| @@ -28,9 +30,39 @@ struct PythonCustomOpDescriptor { | |||
| 28 | CustomOpCapabilityMask capabilities{0U}; | 30 | CustomOpCapabilityMask capabilities{0U}; |
| 29 | }; | 31 | }; |
| 30 | 32 | ||
| 33 | +// 该借用式 POD view 是临时的 bridge 边界。CollectCustomOpIrMeta 当前依赖 runtime 私有接口, | ||
| 34 | +// Python 版本敏感的 bridge/codegen 只能依赖 run 包的公开头文件,因此由 custom_op_runtime 持有 IR 快照并 | ||
| 35 | +// 通过该 view 暴露。后续 collector 所需接口全部成为 run 包稳定公开 API 后,应将 collector 迁移到 | ||
| 36 | +// bridge/codegen 侧并删除 POD 投影,改为通过正式公共头文件和符号表达依赖,不再维护私有 callback/POD 协议。 | ||
| 37 | +struct PythonCustomOpIrInputView { | ||
| 38 | + const char *name; | ||
| 39 | + uint32_t kind; | ||
| 40 | +}; | ||
| 41 | + | ||
| 42 | +struct PythonCustomOpIrAttrView { | ||
| 43 | + const char *name; | ||
| 44 | + const char *type; | ||
| 45 | +}; | ||
| 46 | + | ||
| 47 | +struct PythonCustomOpIrOutputView { | ||
| 48 | + const char *name; | ||
| 49 | + uint32_t kind; | ||
| 50 | +}; | ||
| 51 | + | ||
| 52 | +struct PythonCustomOpIrMetaView { | ||
| 53 | + const char *op_type; | ||
| 54 | + const PythonCustomOpIrInputView *inputs; | ||
| 55 | + size_t input_count; | ||
| 56 | + const PythonCustomOpIrAttrView *attrs; | ||
| 57 | + size_t attr_count; | ||
| 58 | + const PythonCustomOpIrOutputView *outputs; | ||
| 59 | + size_t output_count; | ||
| 60 | +}; | ||
| 61 | + | ||
| 31 | using PythonCustomOpHolderCreateFn = void *(*)(const PythonCustomOpDescriptor *desc); | 62 | using PythonCustomOpHolderCreateFn = void *(*)(const PythonCustomOpDescriptor *desc); |
| 32 | using PythonCustomOpHolderDestroyFn = void (*)(void *holder); | 63 | using PythonCustomOpHolderDestroyFn = void (*)(void *holder); |
| 33 | -using PythonCustomOpExecuteFn = graphStatus (*)(const void *holder, gert::EagerOpExecutionContext *ctx); | 64 | +using PythonCustomOpExecuteFn = graphStatus (*)(const void *holder, gert::EagerOpExecutionContext *ctx, |
| 65 | + const PythonCustomOpIrMetaView *ir_meta); | ||
| 34 | 66 | ||
| 35 | struct PythonCustomOpCallbacks { | 67 | struct PythonCustomOpCallbacks { |
| 36 | PythonCustomOpHolderCreateFn create{nullptr}; | 68 | PythonCustomOpHolderCreateFn create{nullptr}; |
| @@ -0,0 +1,64 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +#include "runtime/custom_op/python_custom_op_ir_meta.h" | ||
| 12 | + | ||
| 13 | +#include <map> | ||
| 14 | +#include <utility> | ||
| 15 | + | ||
| 16 | +#include "common/checker.h" | ||
| 17 | +#include "graph/ascend_string.h" | ||
| 18 | +#include "graph/operator_factory.h" | ||
| 19 | +#include "graph/utils/op_desc_utils.h" | ||
| 20 | + | ||
| 21 | +namespace ge { | ||
| 22 | +namespace custom_op { | ||
| 23 | +graphStatus CollectCustomOpIrMeta(const std::string &op_type, CustomOpIrMeta &ir_meta) { | ||
| 24 | + GE_ASSERT_TRUE(!op_type.empty(), "Collect custom op IR meta failed because op type is empty."); | ||
| 25 | + GE_ASSERT_TRUE(OperatorFactory::IsExistOp(op_type.c_str()), | ||
| 26 | + "Collect custom op IR meta failed because op type[%s] is not registered.", op_type.c_str()); | ||
| 27 | + | ||
| 28 | + const auto op = OperatorFactory::CreateOperator(op_type.c_str(), op_type.c_str()); | ||
| 29 | + const auto op_desc = OpDescUtils::GetOpDescFromOperator(op); | ||
| 30 | + GE_ASSERT_NOTNULL(op_desc, "Collect custom op IR meta failed to get op desc, op type[%s].", op_type.c_str()); | ||
| 31 | + | ||
| 32 | + std::map<AscendString, AscendString> attr_types; | ||
| 33 | + GE_ASSERT_GRAPH_SUCCESS(op.GetAllIrAttrNamesAndTypes(attr_types), | ||
| 34 | + "Collect custom op IR meta failed to get attr types, op type[%s].", op_type.c_str()); | ||
| 35 | + | ||
| 36 | + CustomOpIrMeta collected; | ||
| 37 | + collected.op_type = op_type; | ||
| 38 | + const auto &ir_inputs = op_desc->GetIrInputs(); | ||
| 39 | + collected.inputs.reserve(ir_inputs.size()); | ||
| 40 | + for (const auto &input : ir_inputs) { | ||
| 41 | + collected.inputs.emplace_back(CustomOpIrInputMeta{input.first, input.second}); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + const auto &ir_attr_names = op_desc->GetIrAttrNames(); | ||
| 45 | + collected.attrs.reserve(ir_attr_names.size()); | ||
| 46 | + for (const auto &attr_name : ir_attr_names) { | ||
| 47 | + const auto attr_iter = attr_types.find(AscendString(attr_name.c_str())); | ||
| 48 | + GE_ASSERT_TRUE((attr_iter != attr_types.cend()) && (attr_iter->second.GetString() != nullptr), | ||
| 49 | + "Collect custom op IR meta failed to get type of attr[%s], op type[%s].", attr_name.c_str(), | ||
| 50 | + op_type.c_str()); | ||
| 51 | + collected.attrs.emplace_back(CustomOpIrAttrMeta{attr_name, attr_iter->second.GetString()}); | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + const auto &ir_outputs = op_desc->GetIrOutputs(); | ||
| 55 | + collected.outputs.reserve(ir_outputs.size()); | ||
| 56 | + for (const auto &output : ir_outputs) { | ||
| 57 | + collected.outputs.emplace_back(CustomOpIrOutputMeta{output.first, output.second}); | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + ir_meta = std::move(collected); | ||
| 61 | + return GRAPH_SUCCESS; | ||
| 62 | +} | ||
| 63 | +} // namespace custom_op | ||
| 64 | +} // namespace ge | ||
| @@ -0,0 +1,48 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +#ifndef CANN_GRAPH_ENGINE_RUNTIME_CUSTOM_OP_PYTHON_CUSTOM_OP_IR_META_H_ | ||
| 12 | +#define CANN_GRAPH_ENGINE_RUNTIME_CUSTOM_OP_PYTHON_CUSTOM_OP_IR_META_H_ | ||
| 13 | + | ||
| 14 | +#include <string> | ||
| 15 | +#include <vector> | ||
| 16 | + | ||
| 17 | +#include "graph/error_codes.h" | ||
| 18 | +#include "graph/op_desc.h" | ||
| 19 | + | ||
| 20 | +namespace ge { | ||
| 21 | +namespace custom_op { | ||
| 22 | +struct CustomOpIrInputMeta { | ||
| 23 | + std::string name; | ||
| 24 | + ge::IrInputType kind; | ||
| 25 | +}; | ||
| 26 | + | ||
| 27 | +struct CustomOpIrAttrMeta { | ||
| 28 | + std::string name; | ||
| 29 | + std::string type; | ||
| 30 | +}; | ||
| 31 | + | ||
| 32 | +struct CustomOpIrOutputMeta { | ||
| 33 | + std::string name; | ||
| 34 | + ge::IrOutputType kind; | ||
| 35 | +}; | ||
| 36 | + | ||
| 37 | +struct CustomOpIrMeta { | ||
| 38 | + std::string op_type; | ||
| 39 | + std::vector<CustomOpIrInputMeta> inputs; | ||
| 40 | + std::vector<CustomOpIrAttrMeta> attrs; | ||
| 41 | + std::vector<CustomOpIrOutputMeta> outputs; | ||
| 42 | +}; | ||
| 43 | + | ||
| 44 | +graphStatus CollectCustomOpIrMeta(const std::string &op_type, CustomOpIrMeta &ir_meta); | ||
| 45 | +} // namespace custom_op | ||
| 46 | +} // namespace ge | ||
| 47 | + | ||
| 48 | +#endif // CANN_GRAPH_ENGINE_RUNTIME_CUSTOM_OP_PYTHON_CUSTOM_OP_IR_META_H_ | ||
| @@ -22,6 +22,7 @@ | |||
| 22 | #include <sstream> | 22 | #include <sstream> |
| 23 | #include <stdexcept> | 23 | #include <stdexcept> |
| 24 | #include <string> | 24 | #include <string> |
| 25 | +#include <utility> | ||
| 25 | 26 | ||
| 26 | #include "common/ge_common/debug/ge_log.h" | 27 | #include "common/ge_common/debug/ge_log.h" |
| 27 | #include "pybind11/embed.h" | 28 | #include "pybind11/embed.h" |
| @@ -180,7 +181,8 @@ class PythonCustomOpPybindBridge { | |||
| 180 | delete holder; | 181 | delete holder; |
| 181 | } | 182 | } |
| 182 | 183 | ||
| 183 | - graphStatus Execute(const PythonCustomOpBridgeHolder *holder, gert::EagerOpExecutionContext *ctx) { | 184 | + graphStatus Execute(const PythonCustomOpBridgeHolder *holder, gert::EagerOpExecutionContext *ctx, |
| 185 | + const PythonCustomOpIrMetaView *ir_meta) { | ||
| 184 | if ((holder == nullptr) || (ctx == nullptr)) { | 186 | if ((holder == nullptr) || (ctx == nullptr)) { |
| 185 | GELOGE(GRAPH_FAILED, "Python custom op bridge holder or context is null."); | 187 | GELOGE(GRAPH_FAILED, "Python custom op bridge holder or context is null."); |
| 186 | return GRAPH_FAILED; | 188 | return GRAPH_FAILED; |
| @@ -199,12 +201,17 @@ class PythonCustomOpPybindBridge { | |||
| 199 | holder->descriptor_key.c_str(), holder->instance_id.c_str()); | 201 | holder->descriptor_key.c_str(), holder->instance_id.c_str()); |
| 200 | return GRAPH_FAILED; | 202 | return GRAPH_FAILED; |
| 201 | } | 203 | } |
| 202 | - py::object result = bridge_module_.attr("call_execute")(holder->instance_id, BuildPythonContext(ctx)); | 204 | + py::object result = |
| 205 | + bridge_module_.attr("call_execute")(holder->instance_id, BuildPythonIrMeta(ir_meta), BuildPythonContext(ctx)); | ||
| 203 | return TranslateStatusLike(result); | 206 | return TranslateStatusLike(result); |
| 204 | } catch (const py::error_already_set &err) { | 207 | } catch (const py::error_already_set &err) { |
| 205 | GELOGE(GRAPH_FAILED, "Execute python custom op failed, descriptor key[%s], instance id[%s]: %s", | 208 | GELOGE(GRAPH_FAILED, "Execute python custom op failed, descriptor key[%s], instance id[%s]: %s", |
| 206 | holder->descriptor_key.c_str(), holder->instance_id.c_str(), err.what()); | 209 | holder->descriptor_key.c_str(), holder->instance_id.c_str(), err.what()); |
| 207 | return GRAPH_FAILED; | 210 | return GRAPH_FAILED; |
| 211 | + } catch (const std::exception &err) { | ||
| 212 | + GELOGE(GRAPH_FAILED, "Execute python custom op failed, descriptor key[%s], instance id[%s]: %s", | ||
| 213 | + holder->descriptor_key.c_str(), holder->instance_id.c_str(), err.what()); | ||
| 214 | + return GRAPH_FAILED; | ||
| 208 | } | 215 | } |
| 209 | } | 216 | } |
| 210 | 217 | ||
| @@ -335,6 +342,60 @@ class PythonCustomOpPybindBridge { | |||
| 335 | return (!desc.descriptor_key.empty() && !desc.op_type.empty()) ? SUCCESS : FAILED; | 342 | return (!desc.descriptor_key.empty() && !desc.op_type.empty()) ? SUCCESS : FAILED; |
| 336 | } | 343 | } |
| 337 | 344 | ||
| 345 | + static py::object BuildPythonIrMeta(const PythonCustomOpIrMetaView *ir_meta) { | ||
| 346 | + if (ir_meta == nullptr) { | ||
| 347 | + return py::none(); | ||
| 348 | + } | ||
| 349 | + if ((ir_meta->op_type == nullptr) || ((ir_meta->input_count != 0U) && (ir_meta->inputs == nullptr)) || | ||
| 350 | + ((ir_meta->attr_count != 0U) && (ir_meta->attrs == nullptr)) || | ||
| 351 | + ((ir_meta->output_count != 0U) && (ir_meta->outputs == nullptr))) { | ||
| 352 | + throw std::runtime_error("invalid custom op IR meta view"); | ||
| 353 | + } | ||
| 354 | + | ||
| 355 | + py::list inputs; | ||
| 356 | + for (size_t i = 0U; i < ir_meta->input_count; ++i) { | ||
| 357 | + const auto &input = ir_meta->inputs[i]; | ||
| 358 | + if (input.name == nullptr) { | ||
| 359 | + throw std::runtime_error("invalid custom op IR input view"); | ||
| 360 | + } | ||
| 361 | + py::dict item; | ||
| 362 | + item["name"] = py::str(input.name); | ||
| 363 | + item["kind"] = py::int_(input.kind); | ||
| 364 | + inputs.append(std::move(item)); | ||
| 365 | + } | ||
| 366 | + | ||
| 367 | + py::list attrs; | ||
| 368 | + for (size_t i = 0U; i < ir_meta->attr_count; ++i) { | ||
| 369 | + const auto &attr = ir_meta->attrs[i]; | ||
| 370 | + if ((attr.name == nullptr) || (attr.type == nullptr)) { | ||
| 371 | + throw std::runtime_error("invalid custom op IR attr view"); | ||
| 372 | + } | ||
| 373 | + py::dict item; | ||
| 374 | + item["name"] = py::str(attr.name); | ||
| 375 | + item["type"] = py::str(attr.type); | ||
| 376 | + attrs.append(std::move(item)); | ||
| 377 | + } | ||
| 378 | + | ||
| 379 | + py::list outputs; | ||
| 380 | + for (size_t i = 0U; i < ir_meta->output_count; ++i) { | ||
| 381 | + const auto &output = ir_meta->outputs[i]; | ||
| 382 | + if (output.name == nullptr) { | ||
| 383 | + throw std::runtime_error("invalid custom op IR output view"); | ||
| 384 | + } | ||
| 385 | + py::dict item; | ||
| 386 | + item["name"] = py::str(output.name); | ||
| 387 | + item["kind"] = py::int_(output.kind); | ||
| 388 | + outputs.append(std::move(item)); | ||
| 389 | + } | ||
| 390 | + | ||
| 391 | + py::dict result; | ||
| 392 | + result["op_type"] = py::str(ir_meta->op_type); | ||
| 393 | + result["inputs"] = std::move(inputs); | ||
| 394 | + result["attrs"] = std::move(attrs); | ||
| 395 | + result["outputs"] = std::move(outputs); | ||
| 396 | + return result; | ||
| 397 | + } | ||
| 398 | + | ||
| 338 | static py::object BuildPythonContext(gert::EagerOpExecutionContext *ctx) { | 399 | static py::object BuildPythonContext(gert::EagerOpExecutionContext *ctx) { |
| 339 | py::module_ native_module = py::module_::import(kCustomOpNativeModuleName); | 400 | py::module_ native_module = py::module_::import(kCustomOpNativeModuleName); |
| 340 | return native_module.attr("_borrow_eager_op_execution_context")(py::int_(reinterpret_cast<uintptr_t>(ctx))); | 401 | return native_module.attr("_borrow_eager_op_execution_context")(py::int_(reinterpret_cast<uintptr_t>(ctx))); |
| @@ -366,9 +427,10 @@ class PythonCustomOpPybindBridge { | |||
| 366 | callbacks.destroy = [](void *holder) { | 427 | callbacks.destroy = [](void *holder) { |
| 367 | PythonCustomOpPybindBridge::GetInstance().DestroyHolder(static_cast<PythonCustomOpBridgeHolder *>(holder)); | 428 | PythonCustomOpPybindBridge::GetInstance().DestroyHolder(static_cast<PythonCustomOpBridgeHolder *>(holder)); |
| 368 | }; | 429 | }; |
| 369 | - callbacks.execute = [](const void *holder, gert::EagerOpExecutionContext *ctx) -> graphStatus { | 430 | + callbacks.execute = [](const void *holder, gert::EagerOpExecutionContext *ctx, |
| 431 | + const PythonCustomOpIrMetaView *ir_meta) -> graphStatus { | ||
| 370 | return PythonCustomOpPybindBridge::GetInstance().Execute(static_cast<const PythonCustomOpBridgeHolder *>(holder), | 432 | return PythonCustomOpPybindBridge::GetInstance().Execute(static_cast<const PythonCustomOpBridgeHolder *>(holder), |
| 371 | - ctx); | 433 | + ctx, ir_meta); |
| 372 | }; | 434 | }; |
| 373 | return callbacks; | 435 | return callbacks; |
| 374 | } | 436 | } |
| @@ -468,6 +468,7 @@ set(MULTI_PARTS_TEST_FILES | |||
| 468 | "common/dnnengines_unittest.cc" | 468 | "common/dnnengines_unittest.cc" |
| 469 | "common/engine_manage_unittest.cc" | 469 | "common/engine_manage_unittest.cc" |
| 470 | "common/helper/custom_op_registry_builder_unittest.cc" | 470 | "common/helper/custom_op_registry_builder_unittest.cc" |
| 471 | + "runtime/custom_op/python_custom_op_ir_meta_unittest.cc" | ||
| 471 | "graph_ir/ge_custom_op_factory_unittest.cc" | 472 | "graph_ir/ge_custom_op_factory_unittest.cc" |
| 472 | "graph_ir/ge_custom_op_pull_registry_unittest.cc" | 473 | "graph_ir/ge_custom_op_pull_registry_unittest.cc" |
| 473 | "graph_ir/ge_operator_factory_unittest.cc" | 474 | "graph_ir/ge_operator_factory_unittest.cc" |
| @@ -237,7 +237,13 @@ def test_bridge_custom_op_holder_and_execute(): | |||
| 237 | descriptor_key = descriptors[0]["descriptor_key"] | 237 | descriptor_key = descriptors[0]["descriptor_key"] |
| 238 | ctx = _FakeEagerContext() | 238 | ctx = _FakeEagerContext() |
| 239 | assert bridge.create_op_impl_holder(instance_id, descriptor_key) is True | 239 | assert bridge.create_op_impl_holder(instance_id, descriptor_key) is True |
| 240 | - assert bridge.call_execute(instance_id, ctx) is None | 240 | + ir_meta = { |
| 241 | + "op_type": "AddCustom", | ||
| 242 | + "inputs": [{"name": "x", "kind": 0}], | ||
| 243 | + "attrs": [{"name": "alpha", "type": "VT_FLOAT"}], | ||
| 244 | + "outputs": [{"name": "y", "kind": 0}], | ||
| 245 | + } | ||
| 246 | + assert bridge.call_execute(instance_id, ir_meta, ctx) is None | ||
| 241 | assert ctx.invalidated is True | 247 | assert ctx.invalidated is True |
| 242 | assert bridge.destroy_op_impl_holder(instance_id) is True | 248 | assert bridge.destroy_op_impl_holder(instance_id) is True |
| 243 | 249 | ||
| @@ -252,7 +258,7 @@ def test_bridge_call_execute_ignores_return_value(): | |||
| 252 | instance_id = "ReturnCustom#1" | 258 | instance_id = "ReturnCustom#1" |
| 253 | ctx = _FakeEagerContext() | 259 | ctx = _FakeEagerContext() |
| 254 | assert bridge.create_op_impl_holder(instance_id, descriptor_key) is True | 260 | assert bridge.create_op_impl_holder(instance_id, descriptor_key) is True |
| 255 | - assert bridge.call_execute(instance_id, ctx) is None | 261 | + assert bridge.call_execute(instance_id, None, ctx) is None |
| 256 | assert ctx.invalidated is True | 262 | assert ctx.invalidated is True |
| 257 | assert bridge.destroy_op_impl_holder(instance_id) is True | 263 | assert bridge.destroy_op_impl_holder(instance_id) is True |
| 258 | 264 | ||
| @@ -96,8 +96,10 @@ void DestroyMockPythonCustomOpHolder(void *holder) { | |||
| 96 | delete static_cast<MockPythonCustomOpHolder *>(holder); | 96 | delete static_cast<MockPythonCustomOpHolder *>(holder); |
| 97 | } | 97 | } |
| 98 | 98 | ||
| 99 | -graphStatus ExecuteMockPythonCustomOp(const void *holder, gert::EagerOpExecutionContext *ctx) { | 99 | +graphStatus ExecuteMockPythonCustomOp(const void *holder, gert::EagerOpExecutionContext *ctx, |
| 100 | + const PythonCustomOpIrMetaView *ir_meta) { | ||
| 100 | (void)ctx; | 101 | (void)ctx; |
| 102 | + (void)ir_meta; | ||
| 101 | auto *mock_holder = const_cast<MockPythonCustomOpHolder *>(static_cast<const MockPythonCustomOpHolder *>(holder)); | 103 | auto *mock_holder = const_cast<MockPythonCustomOpHolder *>(static_cast<const MockPythonCustomOpHolder *>(holder)); |
| 102 | if (mock_holder == nullptr) { | 104 | if (mock_holder == nullptr) { |
| 103 | return GRAPH_FAILED; | 105 | return GRAPH_FAILED; |
| @@ -0,0 +1,160 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +#include <gtest/gtest.h> | ||
| 12 | + | ||
| 13 | +#include <new> | ||
| 14 | + | ||
| 15 | +#include "graph/operator_reg.h" | ||
| 16 | +#include "runtime/custom_op/python_custom_op_adapter.h" | ||
| 17 | +#include "runtime/custom_op/python_custom_op_ir_meta.h" | ||
| 18 | + | ||
| 19 | +namespace ge { | ||
| 20 | +REG_OP(PythonCustomOpIrMetaUt) | ||
| 21 | + .INPUT(required_input, TensorType::ALL()) | ||
| 22 | + .OPTIONAL_INPUT(optional_input, TensorType::ALL()) | ||
| 23 | + .DYNAMIC_INPUT(dynamic_input, TensorType::ALL()) | ||
| 24 | + .OUTPUT(required_output, TensorType::ALL()) | ||
| 25 | + .DYNAMIC_OUTPUT(dynamic_output, TensorType::ALL()) | ||
| 26 | + .ATTR(z_attr, Int, 0) | ||
| 27 | + .REQUIRED_ATTR(a_attr, Float) | ||
| 28 | + .OP_END_FACTORY_REG(PythonCustomOpIrMetaUt); | ||
| 29 | +} // namespace ge | ||
| 30 | + | ||
| 31 | +namespace ge { | ||
| 32 | +namespace custom_op { | ||
| 33 | +namespace { | ||
| 34 | +struct MockPythonCustomOpHolder {}; | ||
| 35 | + | ||
| 36 | +void *CreateMockPythonCustomOpHolder(const PythonCustomOpDescriptor *desc) { | ||
| 37 | + return (desc == nullptr) ? nullptr : new (std::nothrow) MockPythonCustomOpHolder(); | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +void DestroyMockPythonCustomOpHolder(void *holder) { | ||
| 41 | + delete static_cast<MockPythonCustomOpHolder *>(holder); | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +graphStatus ExecuteMockPythonCustomOpWithIrMeta(const void *holder, gert::EagerOpExecutionContext *ctx, | ||
| 45 | + const PythonCustomOpIrMetaView *ir_meta) { | ||
| 46 | + (void)ctx; | ||
| 47 | + if ((holder == nullptr) || (ir_meta == nullptr)) { | ||
| 48 | + ADD_FAILURE() << "Python custom op holder or IR meta view is null"; | ||
| 49 | + return GRAPH_FAILED; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + EXPECT_STREQ(ir_meta->op_type, "PythonCustomOpIrMetaUt"); | ||
| 53 | + EXPECT_EQ(ir_meta->input_count, 3U); | ||
| 54 | + EXPECT_EQ(ir_meta->attr_count, 2U); | ||
| 55 | + EXPECT_EQ(ir_meta->output_count, 2U); | ||
| 56 | + if ((ir_meta->input_count != 3U) || (ir_meta->attr_count != 2U) || (ir_meta->output_count != 2U)) { | ||
| 57 | + return GRAPH_FAILED; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + EXPECT_STREQ(ir_meta->inputs[0].name, "required_input"); | ||
| 61 | + EXPECT_EQ(ir_meta->inputs[0].kind, 0U); | ||
| 62 | + EXPECT_STREQ(ir_meta->inputs[1].name, "optional_input"); | ||
| 63 | + EXPECT_EQ(ir_meta->inputs[1].kind, 1U); | ||
| 64 | + EXPECT_STREQ(ir_meta->inputs[2].name, "dynamic_input"); | ||
| 65 | + EXPECT_EQ(ir_meta->inputs[2].kind, 2U); | ||
| 66 | + EXPECT_STREQ(ir_meta->attrs[0].name, "z_attr"); | ||
| 67 | + EXPECT_STREQ(ir_meta->attrs[0].type, "VT_INT"); | ||
| 68 | + EXPECT_STREQ(ir_meta->attrs[1].name, "a_attr"); | ||
| 69 | + EXPECT_STREQ(ir_meta->attrs[1].type, "VT_FLOAT"); | ||
| 70 | + EXPECT_STREQ(ir_meta->outputs[0].name, "required_output"); | ||
| 71 | + EXPECT_EQ(ir_meta->outputs[0].kind, 0U); | ||
| 72 | + EXPECT_STREQ(ir_meta->outputs[1].name, "dynamic_output"); | ||
| 73 | + EXPECT_EQ(ir_meta->outputs[1].kind, 1U); | ||
| 74 | + return GRAPH_SUCCESS; | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +graphStatus ExecuteMockLegacyPythonCustomOp(const void *holder, gert::EagerOpExecutionContext *ctx, | ||
| 78 | + const PythonCustomOpIrMetaView *ir_meta) { | ||
| 79 | + (void)ctx; | ||
| 80 | + EXPECT_NE(holder, nullptr); | ||
| 81 | + EXPECT_EQ(ir_meta, nullptr); | ||
| 82 | + return ((holder != nullptr) && (ir_meta == nullptr)) ? GRAPH_SUCCESS : GRAPH_FAILED; | ||
| 83 | +} | ||
| 84 | +} // namespace | ||
| 85 | + | ||
| 86 | +TEST(PythonCustomOpIrMeta, collects_ir_in_definition_order) { | ||
| 87 | + CustomOpIrMeta ir_meta; | ||
| 88 | + ASSERT_EQ(CollectCustomOpIrMeta("PythonCustomOpIrMetaUt", ir_meta), GRAPH_SUCCESS); | ||
| 89 | + | ||
| 90 | + EXPECT_EQ(ir_meta.op_type, "PythonCustomOpIrMetaUt"); | ||
| 91 | + ASSERT_EQ(ir_meta.inputs.size(), 3U); | ||
| 92 | + EXPECT_EQ(ir_meta.inputs[0].name, "required_input"); | ||
| 93 | + EXPECT_EQ(ir_meta.inputs[0].kind, kIrInputRequired); | ||
| 94 | + EXPECT_EQ(ir_meta.inputs[1].name, "optional_input"); | ||
| 95 | + EXPECT_EQ(ir_meta.inputs[1].kind, kIrInputOptional); | ||
| 96 | + EXPECT_EQ(ir_meta.inputs[2].name, "dynamic_input"); | ||
| 97 | + EXPECT_EQ(ir_meta.inputs[2].kind, kIrInputDynamic); | ||
| 98 | + | ||
| 99 | + ASSERT_EQ(ir_meta.attrs.size(), 2U); | ||
| 100 | + EXPECT_EQ(ir_meta.attrs[0].name, "z_attr"); | ||
| 101 | + EXPECT_EQ(ir_meta.attrs[0].type, "VT_INT"); | ||
| 102 | + EXPECT_EQ(ir_meta.attrs[1].name, "a_attr"); | ||
| 103 | + EXPECT_EQ(ir_meta.attrs[1].type, "VT_FLOAT"); | ||
| 104 | + | ||
| 105 | + ASSERT_EQ(ir_meta.outputs.size(), 2U); | ||
| 106 | + EXPECT_EQ(ir_meta.outputs[0].name, "required_output"); | ||
| 107 | + EXPECT_EQ(ir_meta.outputs[0].kind, kIrOutputRequired); | ||
| 108 | + EXPECT_EQ(ir_meta.outputs[1].name, "dynamic_output"); | ||
| 109 | + EXPECT_EQ(ir_meta.outputs[1].kind, kIrOutputDynamic); | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +TEST(PythonCustomOpIrMeta, rejects_unregistered_op_without_overwriting_output) { | ||
| 113 | + CustomOpIrMeta ir_meta; | ||
| 114 | + ir_meta.op_type = "keep_me"; | ||
| 115 | + | ||
| 116 | + EXPECT_NE(CollectCustomOpIrMeta("PythonCustomOpIrMetaMissingUt", ir_meta), GRAPH_SUCCESS); | ||
| 117 | + EXPECT_EQ(ir_meta.op_type, "keep_me"); | ||
| 118 | +} | ||
| 119 | + | ||
| 120 | +TEST(PythonCustomOpIrMeta, forwards_pod_view_to_python_callback) { | ||
| 121 | + PythonCustomOpDescriptor desc; | ||
| 122 | + desc.descriptor_key = "python_adapter_ir_meta"; | ||
| 123 | + desc.op_type = "PythonCustomOpIrMetaUt"; | ||
| 124 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); | ||
| 125 | + | ||
| 126 | + PythonCustomOpCallbacks callbacks; | ||
| 127 | + callbacks.create = CreateMockPythonCustomOpHolder; | ||
| 128 | + callbacks.destroy = DestroyMockPythonCustomOpHolder; | ||
| 129 | + callbacks.execute = ExecuteMockPythonCustomOpWithIrMeta; | ||
| 130 | + | ||
| 131 | + ASSERT_TRUE(PythonCustomOpRuntimeRegistry::Register(desc, callbacks)); | ||
| 132 | + { | ||
| 133 | + PythonCustomOpAdapter adapter(desc); | ||
| 134 | + ASSERT_TRUE(adapter.IsValid()); | ||
| 135 | + EXPECT_EQ(adapter.Execute(nullptr), GRAPH_SUCCESS); | ||
| 136 | + } | ||
| 137 | + EXPECT_TRUE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | ||
| 138 | +} | ||
| 139 | + | ||
| 140 | +TEST(PythonCustomOpIrMeta, keeps_legacy_execute_without_registered_ir) { | ||
| 141 | + PythonCustomOpDescriptor desc; | ||
| 142 | + desc.descriptor_key = "python_adapter_legacy_without_ir"; | ||
| 143 | + desc.op_type = "PythonCustomOpLegacyWithoutIrUt"; | ||
| 144 | + AddCustomOpCapability(desc.capabilities, CustomOpCapability::kEagerExecute); | ||
| 145 | + | ||
| 146 | + PythonCustomOpCallbacks callbacks; | ||
| 147 | + callbacks.create = CreateMockPythonCustomOpHolder; | ||
| 148 | + callbacks.destroy = DestroyMockPythonCustomOpHolder; | ||
| 149 | + callbacks.execute = ExecuteMockLegacyPythonCustomOp; | ||
| 150 | + | ||
| 151 | + ASSERT_TRUE(PythonCustomOpRuntimeRegistry::Register(desc, callbacks)); | ||
| 152 | + { | ||
| 153 | + PythonCustomOpAdapter adapter(desc); | ||
| 154 | + ASSERT_TRUE(adapter.IsValid()); | ||
| 155 | + EXPECT_EQ(adapter.Execute(nullptr), GRAPH_SUCCESS); | ||
| 156 | + } | ||
| 157 | + EXPECT_TRUE(PythonCustomOpRuntimeRegistry::Unregister(desc.descriptor_key)); | ||
| 158 | +} | ||
| 159 | +} // namespace custom_op | ||
| 160 | +} // namespace ge | ||