已合并
feat: 新增 HostCPU 自定义算子示例及文档 #4602
duhua创建于 9 天前
feat: 新增 HostCPU 自定义算子示例及文档 #4602
已合并
共 54 个文件变更+3092-32
| @@ -229,23 +229,26 @@ This is constant folding main entry Pass, processing nodes with all inputs as co | |||
| 229 | 3. **Memory Priority Strategy Check** | 229 | 3. **Memory Priority Strategy Check** |
| 230 | - When MemoryPriority strategy configured, if input constant node Shape large (>8) and shared by multiple downstream, then skip folding. Because folding copies constant data once, in memory constrained scenario not worthwhile | 230 | - When MemoryPriority strategy configured, if input constant node Shape large (>8) and shared by multiple downstream, then skip folding. Because folding copies constant data once, in memory constrained scenario not worthwhile |
| 231 | 231 | ||
| 232 | -4. **Computation Execution (Two-level Fallback Strategy)** | 232 | +4. **Computation Execution (Three-level Strategy)** |
| 233 | - - **First level: AICPU operator kernel** (`ComputeWithHostCpuKernel`) | 233 | + - **First level: Host CPU custom op** (`ComputeWithHostCpuCustomOp`) |
| 234 | - - Try through `aicpu_ascend_kernel` engine get operator Host CPU implementation | 234 | + - Check whether current operator registered Host CPU backend custom implementation through `CustomOpFactory::IsExistOp(op_type, OpBackend::kHostCPU)` |
| 235 | - - Create operator instance through `OpKernelRegistry`, execute by `HostCpuEngine` | 235 | + - Get instance through `CreateOrGetCustomOp(op_type, kHostCPU)` and call `HostCpuExecuteOp::Execute` |
| 236 | - - This level supports widest operator types, runtime loads `libconstant_folding_ops.so` | 236 | + - **Second level: AICPU operator kernel** (`ComputeWithHostCpuKernel`) |
| 237 | - - **Second level: GE built-in kernel** (`ComputeWithBuiltInKernel`) | 237 | + - Try through `aicpu_ascend_kernel` engine get operator Host CPU implementation |
| 238 | - - If AICPU does not support this operator, fallback to GE built-in Host Kernel | 238 | + - Create operator instance through `OpKernelRegistry`, execute by `HostCpuEngine` |
| 239 | - - Through `KernelFactory` lookup registered Kernel by operator type (`folding_pass::GetKernelByType`) | 239 | + - This level supports widest operator types, runtime loads `libconstant_folding_ops.so` |
| 240 | - - GE built-in Kernel covers about 40 common operators | 240 | + - **Third level: GE built-in kernel** (`ComputeWithBuiltInKernel`) |
| 241 | + - If AICPU does not support this operator, fallback to GE built-in Host Kernel | ||
| 242 | + - Through `KernelFactory` lookup registered Kernel by operator type (`folding_pass::GetKernelByType`) | ||
| 243 | + - GE built-in Kernel covers about 40 common operators | ||
| 241 | 244 | ||
| 242 | 5. **Folding Replacement** | 245 | 5. **Folding Replacement** |
| 243 | - After computation success, complete graph structure transformation by `FoldingPass::Folding` | 246 | - After computation success, complete graph structure transformation by `FoldingPass::Folding` |
| 244 | - Newly created Const node will be marked `_is_from_constant_folding=true`, for subsequent flow identification | 247 | - Newly created Const node will be marked `_is_from_constant_folding=true`, for subsequent flow identification |
| 245 | 248 | ||
| 246 | 6. **Performance Statistics** | 249 | 6. **Performance Statistics** |
| 247 | - - Separately record AICPU kernel and GE built-in kernel folding time and call count | 250 | + - Separately record external operator side constant folding (Host CPU custom op / AICPU Host CPU kernel) and GE built-in kernel folding time and call count |
| 248 | - - Summary output performance trace log in `GraphManager::OptimizeStage1_2` | 251 | + - Summary output performance trace log in `GraphManager::OptimizeStage1_2` |
| 249 | 252 | ||
| 250 | Pass registration macro: | 253 | Pass registration macro: |
| 251 | 254 | ||
| @@ -357,7 +360,7 @@ Related attributes and tool classes: | |||
| 357 | Mechanism flow: | 360 | Mechanism flow: |
| 358 | 361 | ||
| 359 | ``` | 362 | ``` |
| 360 | -DimensionComputePass / DimensionComputePass | 363 | +ConstantFoldingPass / DimensionComputePass |
| 361 | │ | 364 | │ |
| 362 | │ (node partial inputs non-constant) | 365 | │ (node partial inputs non-constant) |
| 363 | ▼ | 366 | ▼ |
| @@ -335,6 +335,8 @@ flowchart TD | |||
| 335 | 335 | ||
| 336 | Engine selection uses a greedy strategy - iterates from high to low priority, and the first engine that passes `CheckSupported()` is selected. Uses a thread pool (default 16 threads) to concurrently select engines for nodes, protects shared data through mutex. | 336 | Engine selection uses a greedy strategy - iterates from high to low priority, and the first engine that passes `CheckSupported()` is selected. Uses a thread pool (default 16 threads) to concurrently select engines for nodes, protects shared data through mutex. |
| 337 | 337 | ||
| 338 | +On top of the engine selection logic described above, custom engines have the highest priority. For example, the device custom operator engine (`DNN_VM_CUSTOM`) takes precedence over other Device engines. A custom operator implementation registered for Host CPU takes precedence over the general Host CPU engine (`DNN_VM_HOST_CPU`). | ||
| 339 | + | ||
| 338 | Engine reassignment (`ReAssignEngine()`) implements through strategy pattern. `EngineReAssignPass` is the strategy interface, currently with two implementations: | 340 | Engine reassignment (`ReAssignEngine()`) implements through strategy pattern. `EngineReAssignPass` is the strategy interface, currently with two implementations: |
| 339 | 341 | ||
| 340 | - `DynamicDataFlowEngineReassignPass`: Engine reassignment in dynamic data flow scenarios | 342 | - `DynamicDataFlowEngineReassignPass`: Engine reassignment in dynamic data flow scenarios |
| @@ -402,6 +402,20 @@ | |||
| 402 | - [GetOutputTensor](cpp/gert/EagerOpExecutionContext/GetOutputTensor.md) | 402 | - [GetOutputTensor](cpp/gert/EagerOpExecutionContext/GetOutputTensor.md) |
| 403 | - [MallocReadOnlyDevArgs](cpp/gert/EagerOpExecutionContext/MallocReadOnlyDevArgs.md) | 403 | - [MallocReadOnlyDevArgs](cpp/gert/EagerOpExecutionContext/MallocReadOnlyDevArgs.md) |
| 404 | 404 | ||
| 405 | + - [HostCpuExecuteOp](cpp/ge/HostCpuExecuteOp/HostCpuExecuteOp.md) | ||
| 406 | + - [简介](cpp/ge/HostCpuExecuteOp/overview.md) | ||
| 407 | + - [Execute](cpp/ge/HostCpuExecuteOp/Execute.md) | ||
| 408 | + | ||
| 409 | + - [HostCpuOpExecutionContext](cpp/gert/HostCpuOpExecutionContext/HostCpuOpExecutionContext.md) | ||
| 410 | + - [简介](cpp/gert/HostCpuOpExecutionContext/overview.md) | ||
| 411 | + - [GetDynamicInputTensor](cpp/gert/HostCpuOpExecutionContext/GetDynamicInputTensor.md) | ||
| 412 | + - [GetInputTensor](cpp/gert/HostCpuOpExecutionContext/GetInputTensor.md) | ||
| 413 | + - [GetOptionalInputTensor](cpp/gert/HostCpuOpExecutionContext/GetOptionalInputTensor.md) | ||
| 414 | + - [GetOutputTensor](cpp/gert/HostCpuOpExecutionContext/GetOutputTensor.md) | ||
| 415 | + - [GetRequiredInputTensor](cpp/gert/HostCpuOpExecutionContext/GetRequiredInputTensor.md) | ||
| 416 | + - [MakeOutputRefInput](cpp/gert/HostCpuOpExecutionContext/MakeOutputRefInput.md) | ||
| 417 | + - [MallocOutputTensor](cpp/gert/HostCpuOpExecutionContext/MallocOutputTensor.md) | ||
| 418 | + | ||
| 405 | - [ExternalWeightDesc](cpp/ge/ExternalWeightDesc/ExternalWeightDesc.md) | 419 | - [ExternalWeightDesc](cpp/ge/ExternalWeightDesc/ExternalWeightDesc.md) |
| 406 | - [简介](cpp/ge/ExternalWeightDesc/overview.md) | 420 | - [简介](cpp/ge/ExternalWeightDesc/overview.md) |
| 407 | - [ExternalWeightDesc构造函数和析构函数](cpp/ge/ExternalWeightDesc/ExternalWeightDesc_constructor_and_destructor.md) | 421 | - [ExternalWeightDesc构造函数和析构函数](cpp/ge/ExternalWeightDesc/ExternalWeightDesc_constructor_and_destructor.md) |
| @@ -804,6 +818,7 @@ | |||
| 804 | - [INFER\_FORMAT\_FUNC\_REG](cpp/ge/INFER_FORMAT_FUNC_REG.md) | 818 | - [INFER\_FORMAT\_FUNC\_REG](cpp/ge/INFER_FORMAT_FUNC_REG.md) |
| 805 | - [INFER\_FUNC\_REG](cpp/ge/INFER_FUNC_REG.md) | 819 | - [INFER\_FUNC\_REG](cpp/ge/INFER_FUNC_REG.md) |
| 806 | - [REG\_AUTO\_MAPPING\_OP](cpp/ge/REG_AUTO_MAPPING_OP.md) | 820 | - [REG\_AUTO\_MAPPING\_OP](cpp/ge/REG_AUTO_MAPPING_OP.md) |
| 821 | + - [REG\_OP\_BACKEND](cpp/ge/REG_OP_BACKEND.md) | ||
| 807 | - [REG\_FUSION\_PASS](cpp/ge/fusion/REG_FUSION_PASS.md) | 822 | - [REG\_FUSION\_PASS](cpp/ge/fusion/REG_FUSION_PASS.md) |
| 808 | - [REG\_DECOMPOSE\_PASS](cpp/ge/REG_DECOMPOSE_PASS.md) | 823 | - [REG\_DECOMPOSE\_PASS](cpp/ge/REG_DECOMPOSE_PASS.md) |
| 809 | - [REGISTER\_CUSTOM\_PASS](cpp/ge/REGISTER_CUSTOM_PASS.md) | 824 | - [REGISTER\_CUSTOM\_PASS](cpp/ge/REGISTER_CUSTOM_PASS.md) |
| @@ -815,6 +830,7 @@ | |||
| 815 | - [DumpFormat](cpp/ge/DumpFormat.md) | 830 | - [DumpFormat](cpp/ge/DumpFormat.md) |
| 816 | - [HiddenInputSubType](cpp/ge/HiddenInputSubType.md) | 831 | - [HiddenInputSubType](cpp/ge/HiddenInputSubType.md) |
| 817 | - [MemoryType](cpp/ge/MemoryType.md) | 832 | - [MemoryType](cpp/ge/MemoryType.md) |
| 833 | + - [OpBackend](cpp/ge/OpBackend.md) | ||
| 818 | - [ProfDataTypeConfig](cpp/ge/ProfDataTypeConfig.md) | 834 | - [ProfDataTypeConfig](cpp/ge/ProfDataTypeConfig.md) |
| 819 | - [ProfilingAicoreMetrics](cpp/ge/ProfilingAicoreMetrics.md) | 835 | - [ProfilingAicoreMetrics](cpp/ge/ProfilingAicoreMetrics.md) |
| 820 | - [aclgrphBuildInitialize_config_params](cpp/ge/aclgrphBuildInitialize_config_params/aclgrphbuildinitialize_config_params.md) | 836 | - [aclgrphBuildInitialize_config_params](cpp/ge/aclgrphBuildInitialize_config_params/aclgrphbuildinitialize_config_params.md) |
| @@ -0,0 +1,65 @@ | |||
| 1 | +# Execute | ||
| 2 | + | ||
| 3 | +## 产品支持情况 | ||
| 4 | + | ||
| 5 | +全量芯片支持。 | ||
| 6 | + | ||
| 7 | +## 头文件/库文件 | ||
| 8 | + | ||
| 9 | +- 头文件:\#include <graph/custom\_op.h\> | ||
| 10 | +- 库文件:liblowering.so | ||
| 11 | + | ||
| 12 | +## 功能说明 | ||
| 13 | + | ||
| 14 | +Host CPU自定义算子的执行函数。 | ||
| 15 | + | ||
| 16 | +## 函数原型 | ||
| 17 | + | ||
| 18 | +```c++ | ||
| 19 | +virtual graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) = 0 | ||
| 20 | +``` | ||
| 21 | + | ||
| 22 | +## 参数说明 | ||
| 23 | + | ||
| 24 | +| 参数名 | 输入/输出 | 说明 | | ||
| 25 | +| --- | --- |--------------------------------------------------------------| | ||
| 26 | +| ctx | 输入 | 执行时上下文,可通过上下文获取input tensor,分配输出内存等。 | | ||
| 27 | + | ||
| 28 | +## 返回值说明 | ||
| 29 | + | ||
| 30 | +| 参数名 | 类型 | 说明 | | ||
| 31 | +| --- | --- | --- | | ||
| 32 | +| - | graphStatus | `GRAPH_SUCCESS(0)`:执行成功;其他值:执行失败。 | | ||
| 33 | + | ||
| 34 | +## 约束说明 | ||
后续新增的接口,都建议补充调用示例,调用示例放在约束说明下面 ![]() ![]() | |||
| 35 | + | ||
| 36 | +无 | ||
| 37 | + | ||
| 38 | +## 调用示例 | ||
| 39 | + | ||
| 40 | +以下示例在自定义算子的Execute中读取两个输入,申请一个输出并完成Host CPU计算。 | ||
| 41 | + | ||
| 42 | +```c++ | ||
| 43 | +#include "graph/custom_op.h" | ||
| 44 | + | ||
| 45 | +class AddHostCpu final : public ge::HostCpuExecuteOp { | ||
| 46 | + public: | ||
| 47 | + ge::graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) override { | ||
| 48 | + const auto *x = ctx->GetInputTensor(0U); | ||
| 49 | + const auto *y = ctx->GetInputTensor(1U); | ||
| 50 | + if ((x == nullptr) || (y == nullptr)) { | ||
| 51 | + return ge::GRAPH_FAILED; | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + auto *z = ctx->MallocOutputTensor(0U, x->GetShape(), x->GetFormat(), x->GetDataType()); | ||
| 55 | + if (z == nullptr) { | ||
| 56 | + return ge::GRAPH_FAILED; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + // 根据x、y完成Host CPU计算。 | ||
| 60 | + return ge::GRAPH_SUCCESS; | ||
| 61 | + } | ||
| 62 | +}; | ||
| 63 | + | ||
| 64 | +REG_OP_BACKEND(AddHostCpu, "Add", ge::OpBackend::kHostCPU); | ||
| 65 | +``` | ||
| @@ -0,0 +1 @@ | |||
| 1 | +# HostCpuExecuteOp | ||
| @@ -0,0 +1,15 @@ | |||
| 1 | +# 简介 | ||
| 2 | + | ||
| 3 | +自定义算子的基类,用于在host实现自定义的操作,通常可在常量折叠或运行时Host CPU调度场景完成计算。 | ||
| 4 | + | ||
| 5 | +## 需要包含的头文件 | ||
| 6 | + | ||
| 7 | +```c++ | ||
| 8 | +#include <graph/custom_op.h> | ||
| 9 | +``` | ||
| 10 | + | ||
| 11 | +## Public成员函数 | ||
| 12 | + | ||
| 13 | +```c++ | ||
| 14 | +virtual graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) = 0 | ||
| 15 | +``` | ||
| @@ -0,0 +1,15 @@ | |||
| 1 | +# OpBackend | ||
| 2 | + | ||
| 3 | +自定义算子后端类型枚举,头文件位于CANN软件安装后文件存储路径下的include/graph/custom\_op.h。 | ||
| 4 | + | ||
| 5 | +```c++ | ||
| 6 | +enum class OpBackend : uint32_t { | ||
| 7 | + kDevice = 0, | ||
| 8 | + kHostCPU = 1, | ||
| 9 | +}; | ||
| 10 | +``` | ||
| 11 | + | ||
| 12 | +各枚举项说明如下: | ||
| 13 | + | ||
| 14 | +- kDevice:Device后端。 | ||
| 15 | +- kHostCPU:Host CPU后端。 | ||
| @@ -22,7 +22,7 @@ REG_AUTO_MAPPING_OP(custom_op_class) | |||
| 22 | 22 | ||
| 23 | | 参数名 | 输入/输出 | 描述 | | 23 | | 参数名 | 输入/输出 | 描述 | |
| 24 | | --- | --- | --- | | 24 | | --- | --- | --- | |
| 25 | -| custom_op_class | 输入 | 自定义算子名称。 | | 25 | +| custom_op_class | 输入 | 自定义算子实现类。 | |
| 26 | 26 | ||
| 27 | ## 返回值说明 | 27 | ## 返回值说明 |
| 28 | 28 | ||
| @@ -30,4 +30,4 @@ REG_AUTO_MAPPING_OP(custom_op_class) | |||
| 30 | 30 | ||
| 31 | ## 约束说明 | 31 | ## 约束说明 |
| 32 | 32 | ||
| 33 | -无 | 33 | +- REG\_AUTO\_MAPPING\_OP默认使用Device后端,并将custom_op_class对应的类名作为算子类型。需要显式指定算子类型或后端时,请使用[REG\_OP\_BACKEND](./REG_OP_BACKEND.md)。 |
| @@ -0,0 +1,51 @@ | |||
| 1 | +# REG\_OP\_BACKEND | ||
| 2 | + | ||
| 3 | +## 产品支持情况 | ||
| 4 | + | ||
| 5 | +全量芯片支持。 | ||
| 6 | + | ||
| 7 | +## 头文件 | ||
| 8 | + | ||
| 9 | +\#include <graph/custom\_op.h\> | ||
| 10 | + | ||
| 11 | +## 功能说明 | ||
| 12 | + | ||
| 13 | +开发人员可以选择将自定义算子实现类注册到指定的算子类型和后端,由框架在编译最开始调用REG\_OP\_BACKEND进行自定义算子注册。 | ||
| 14 | + | ||
| 15 | +## 函数原型 | ||
| 16 | + | ||
| 17 | +```c++ | ||
| 18 | +REG_OP_BACKEND(custom_op_class, op_type, backend) | ||
| 19 | +``` | ||
| 20 | + | ||
| 21 | +## 参数说明 | ||
| 22 | + | ||
| 23 | +| 参数名 | 输入/输出 | 描述 | | ||
| 24 | +| --- | --- |---------------------------------------------------------------------------------------------| | ||
| 25 | +| custom_op_class | 输入 | 自定义算子实现类。 | | ||
| 26 | +| op_type | 输入 | 注册的算子类型名称。 | | ||
| 27 | +| backend | 输入 | 自定义算子后端类型,为枚举类[OpBackend](./OpBackend.md)。 | | ||
| 28 | + | ||
| 29 | +## 返回值说明 | ||
| 30 | + | ||
| 31 | +无 | ||
| 32 | + | ||
| 33 | +## 约束说明 | ||
| 34 | + | ||
| 35 | +- 同一个op_type可以分别注册不同backend的自定义算子实现,同一个backend下只能注册一个自定义算子实现。 | ||
| 36 | + | ||
| 37 | +## 调用示例 | ||
| 38 | + | ||
| 39 | +```c++ | ||
| 40 | +#include "graph/custom_op.h" | ||
| 41 | + | ||
| 42 | +class AddHostCpu final : public ge::HostCpuExecuteOp { | ||
| 43 | + public: | ||
| 44 | + ge::graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) override { | ||
| 45 | + // Host CPU执行逻辑 | ||
| 46 | + return ge::GRAPH_SUCCESS; | ||
| 47 | + } | ||
| 48 | +}; | ||
| 49 | + | ||
| 50 | +REG_OP_BACKEND(AddHostCpu, "Add", ge::OpBackend::kHostCPU); | ||
| 51 | +``` | ||
| @@ -0,0 +1,51 @@ | |||
| 1 | +# GetDynamicInputTensor | ||
| 2 | + | ||
| 3 | +## 产品支持情况 | ||
| 4 | + | ||
| 5 | +全量芯片支持。 | ||
| 6 | + | ||
| 7 | +## 头文件/库文件 | ||
| 8 | + | ||
| 9 | +- 头文件:\#include <exe\_graph/runtime/host\_cpu\_op\_execution\_context.h\> | ||
| 10 | +- 库文件:liblowering.so | ||
| 11 | + | ||
| 12 | +## 功能说明 | ||
| 13 | + | ||
| 14 | +根据算子IR原型定义,获取DYNAMIC_INPUT类型的输入Tensor指针。 | ||
| 15 | + | ||
| 16 | +## 函数原型 | ||
| 17 | + | ||
| 18 | +```c++ | ||
| 19 | +const Tensor *GetDynamicInputTensor(size_t ir_index, size_t relative_index) const | ||
| 20 | +``` | ||
| 21 | + | ||
| 22 | +## 参数说明 | ||
| 23 | + | ||
| 24 | +| 参数名 | 输入/输出 | 说明 | | ||
| 25 | +| --- | --- |-------------------------------------| | ||
| 26 | +| ir_index | 输入 | IR原型定义中的index。 | | ||
| 27 | +| relative_index | 输入 | 该输入实例化后的相对index,例如某个DYNAMIC_INPUT实例化了3个输入,那么relative_index的有效范围是[0,2]。 | | ||
| 28 | + | ||
| 29 | +## 返回值说明 | ||
| 30 | + | ||
| 31 | +Tensor指针,异常时返回空指针。 | ||
| 32 | + | ||
| 33 | +## 约束说明 | ||
| 34 | + | ||
| 35 | +无 | ||
| 36 | + | ||
| 37 | +## 调用示例 | ||
| 38 | + | ||
| 39 | +以下片段位于`HostCpuExecuteOp::Execute`实现中,获取算子IR原型中index为0的`DYNAMIC_INPUT`,其相对索引为1的实例化输入。 | ||
| 40 | + | ||
| 41 | +```c++ | ||
| 42 | +ge::graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) override { | ||
| 43 | + const gert::Tensor *input = ctx->GetDynamicInputTensor(0U, 1U); | ||
| 44 | + if (input == nullptr) { | ||
| 45 | + return ge::GRAPH_FAILED; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + // 使用input获取动态输入Tensor的描述信息或数据。 | ||
| 49 | + return ge::GRAPH_SUCCESS; | ||
| 50 | +} | ||
| 51 | +``` | ||
| @@ -0,0 +1,50 @@ | |||
| 1 | +# GetInputTensor | ||
| 2 | + | ||
| 3 | +## 产品支持情况 | ||
| 4 | + | ||
| 5 | +全量芯片支持。 | ||
| 6 | + | ||
| 7 | +## 头文件/库文件 | ||
| 8 | + | ||
| 9 | +- 头文件:\#include <exe\_graph/runtime/host\_cpu\_op\_execution\_context.h\> | ||
| 10 | +- 库文件:liblowering.so | ||
| 11 | + | ||
| 12 | +## 功能说明 | ||
| 13 | + | ||
| 14 | +根据输入index,获取输入tensor指针。 | ||
| 15 | + | ||
| 16 | +## 函数原型 | ||
| 17 | + | ||
| 18 | +```c++ | ||
| 19 | +const Tensor *GetInputTensor(size_t index) const | ||
| 20 | +``` | ||
| 21 | + | ||
| 22 | +## 参数说明 | ||
| 23 | + | ||
| 24 | +| 参数名 | 输入/输出 | 说明 | | ||
| 25 | +| --- | --- | --- | | ||
| 26 | +| index | 输入 | 输入index。 | | ||
| 27 | + | ||
| 28 | +## 返回值说明 | ||
| 29 | + | ||
| 30 | +Tensor指针,异常时返回空指针。 | ||
| 31 | + | ||
| 32 | +## 约束说明 | ||
| 33 | + | ||
| 34 | +无 | ||
| 35 | + | ||
| 36 | +## 调用示例 | ||
| 37 | + | ||
| 38 | +以下片段位于`HostCpuExecuteOp::Execute`实现中,获取第0个输入Tensor。 | ||
| 39 | + | ||
| 40 | +```c++ | ||
| 41 | +ge::graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) override { | ||
| 42 | + const gert::Tensor *input = ctx->GetInputTensor(0U); | ||
| 43 | + if (input == nullptr) { | ||
| 44 | + return ge::GRAPH_FAILED; | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + // 使用input获取输入Tensor的描述信息或数据。 | ||
| 48 | + return ge::GRAPH_SUCCESS; | ||
| 49 | +} | ||
| 50 | +``` | ||
| @@ -0,0 +1,49 @@ | |||
| 1 | +# GetOptionalInputTensor | ||
| 2 | + | ||
| 3 | +## 产品支持情况 | ||
| 4 | + | ||
| 5 | +全量芯片支持。 | ||
| 6 | + | ||
| 7 | +## 头文件/库文件 | ||
| 8 | + | ||
| 9 | +- 头文件:\#include <exe\_graph/runtime/host\_cpu\_op\_execution\_context.h\> | ||
| 10 | +- 库文件:liblowering.so | ||
| 11 | + | ||
| 12 | +## 功能说明 | ||
| 13 | + | ||
| 14 | +基于算子IR原型定义,获取OPTIONAL_INPUT类型的输入tensor指针。 | ||
| 15 | + | ||
| 16 | +## 函数原型 | ||
| 17 | + | ||
| 18 | +```c++ | ||
| 19 | +const Tensor *GetOptionalInputTensor(size_t ir_index) const | ||
| 20 | +``` | ||
| 21 | + | ||
| 22 | +## 参数说明 | ||
| 23 | + | ||
| 24 | +| 参数名 | 输入/输出 | 说明 | | ||
| 25 | +| --- | --- | --- | | ||
| 26 | +| ir_index | 输入 | IR原型定义中的index。 | | ||
| 27 | + | ||
| 28 | +## 返回值说明 | ||
| 29 | + | ||
| 30 | +Tensor指针,异常时返回空指针。 | ||
| 31 | + | ||
| 32 | +## 约束说明 | ||
| 33 | + | ||
| 34 | +无 | ||
| 35 | + | ||
| 36 | +## 调用示例 | ||
| 37 | + | ||
| 38 | +以下片段位于`HostCpuExecuteOp::Execute`实现中,获取算子IR原型中index为1的`OPTIONAL_INPUT`输入。未提供可选输入时,返回空指针。 | ||
| 39 | + | ||
| 40 | +```c++ | ||
| 41 | +ge::graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) override { | ||
| 42 | + const gert::Tensor *optional_input = ctx->GetOptionalInputTensor(1U); | ||
| 43 | + if (optional_input != nullptr) { | ||
| 44 | + // 使用optional_input完成可选输入相关的计算。 | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + return ge::GRAPH_SUCCESS; | ||
| 48 | +} | ||
| 49 | +``` | ||
| @@ -0,0 +1,50 @@ | |||
| 1 | +# GetOutputTensor | ||
| 2 | + | ||
| 3 | +## 产品支持情况 | ||
| 4 | + | ||
| 5 | +全量芯片支持。 | ||
| 6 | + | ||
| 7 | +## 头文件/库文件 | ||
| 8 | + | ||
| 9 | +- 头文件:\#include <exe\_graph/runtime/host\_cpu\_op\_execution\_context.h\> | ||
| 10 | +- 库文件:liblowering.so | ||
| 11 | + | ||
| 12 | +## 功能说明 | ||
| 13 | + | ||
| 14 | +获取index指定的输出Tensor指针。 | ||
| 15 | + | ||
| 16 | +## 函数原型 | ||
| 17 | + | ||
| 18 | +```c++ | ||
| 19 | +const Tensor *GetOutputTensor(size_t index) const | ||
| 20 | +``` | ||
| 21 | + | ||
| 22 | +## 参数说明 | ||
| 23 | + | ||
| 24 | +| 参数名 | 输入/输出 | 说明 | | ||
| 25 | +| --- | --- | --- | | ||
| 26 | +| index | 输入 | 输出索引。 | | ||
| 27 | + | ||
| 28 | +## 返回值说明 | ||
| 29 | + | ||
| 30 | +输出Tensor指针,异常时返回空指针。 | ||
| 31 | + | ||
| 32 | +## 约束说明 | ||
| 33 | + | ||
| 34 | +无 | ||
| 35 | + | ||
| 36 | +## 调用示例 | ||
| 37 | + | ||
| 38 | +以下片段位于`HostCpuExecuteOp::Execute`实现中,获取第0个输出Tensor,并在使用前检查返回值。 | ||
| 39 | + | ||
| 40 | +```c++ | ||
| 41 | +ge::graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) override { | ||
| 42 | + const gert::Tensor *output = ctx->GetOutputTensor(0U); | ||
| 43 | + if (output == nullptr) { | ||
| 44 | + return ge::GRAPH_FAILED; | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + // 使用output获取输出Tensor的描述信息或数据。 | ||
| 48 | + return ge::GRAPH_SUCCESS; | ||
| 49 | +} | ||
| 50 | +``` | ||
| @@ -0,0 +1,50 @@ | |||
| 1 | +# GetRequiredInputTensor | ||
| 2 | + | ||
| 3 | +## 产品支持情况 | ||
| 4 | + | ||
| 5 | +全量芯片支持。 | ||
| 6 | + | ||
| 7 | +## 头文件/库文件 | ||
| 8 | + | ||
| 9 | +- 头文件:\#include <exe\_graph/runtime/host\_cpu\_op\_execution\_context.h\> | ||
| 10 | +- 库文件:liblowering.so | ||
| 11 | + | ||
| 12 | +## 功能说明 | ||
| 13 | + | ||
| 14 | +基于算子IR原型定义,获取REQUIRED\_INPUT类型的输入Tensor指针。 | ||
| 15 | + | ||
| 16 | +## 函数原型 | ||
| 17 | + | ||
| 18 | +```c++ | ||
| 19 | +const Tensor *GetRequiredInputTensor(size_t ir_index) const | ||
| 20 | +``` | ||
| 21 | + | ||
| 22 | +## 参数说明 | ||
| 23 | + | ||
| 24 | +| 参数名 | 输入/输出 | 说明 | | ||
| 25 | +| --- | --- | --- | | ||
| 26 | +| ir_index | 输入 | IR原型定义中的index。 | | ||
| 27 | + | ||
| 28 | +## 返回值说明 | ||
| 29 | + | ||
| 30 | +Tensor指针,异常时返回空指针。 | ||
| 31 | + | ||
| 32 | +## 约束说明 | ||
| 33 | + | ||
| 34 | +无 | ||
| 35 | + | ||
| 36 | +## 调用示例 | ||
| 37 | + | ||
| 38 | +以下片段位于`HostCpuExecuteOp::Execute`实现中,获取算子IR原型中index为0的`REQUIRED_INPUT`输入。 | ||
| 39 | + | ||
| 40 | +```c++ | ||
| 41 | +ge::graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) override { | ||
| 42 | + const gert::Tensor *input = ctx->GetRequiredInputTensor(0U); | ||
| 43 | + if (input == nullptr) { | ||
| 44 | + return ge::GRAPH_FAILED; | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + // 使用input获取输入Tensor的描述信息或数据。 | ||
| 48 | + return ge::GRAPH_SUCCESS; | ||
| 49 | +} | ||
| 50 | +``` | ||
| @@ -0,0 +1 @@ | |||
| 1 | +# HostCpuOpExecutionContext | ||
| @@ -0,0 +1,50 @@ | |||
| 1 | +# MakeOutputRefInput | ||
| 2 | + | ||
| 3 | +## 产品支持情况 | ||
| 4 | + | ||
| 5 | +全量芯片支持。 | ||
| 6 | + | ||
| 7 | +## 头文件/库文件 | ||
| 8 | + | ||
| 9 | +- 头文件:\#include <exe\_graph/runtime/host\_cpu\_op\_execution\_context.h\> | ||
| 10 | +- 库文件:liblowering.so | ||
| 11 | + | ||
| 12 | +## 功能说明 | ||
| 13 | + | ||
| 14 | +指定某输出的内存地址引用自某个输入。 | ||
| 15 | + | ||
| 16 | +## 函数原型 | ||
| 17 | + | ||
| 18 | +```c++ | ||
| 19 | +Tensor *MakeOutputRefInput(size_t output_index, size_t input_index) | ||
| 20 | +``` | ||
| 21 | + | ||
| 22 | +## 参数说明 | ||
| 23 | + | ||
| 24 | +| 参数名 | 输入/输出 | 说明 | | ||
| 25 | +| --- | --- | --- | | ||
| 26 | +| output_index | 输入 | 输出索引。 | | ||
| 27 | +| input_index | 输入 | 输入索引。 | | ||
| 28 | + | ||
| 29 | +## 返回值说明 | ||
| 30 | + | ||
| 31 | +output\_index对应的输出Tensor指针。 | ||
| 32 | + | ||
| 33 | +## 约束说明 | ||
| 34 | + | ||
| 35 | +无 | ||
| 36 | + | ||
| 37 | +## 调用示例 | ||
| 38 | + | ||
| 39 | +以下片段位于`HostCpuExecuteOp::Execute`实现中,将第0个输出设置为引用第0个输入的内存地址,并检查返回值。 | ||
| 40 | + | ||
| 41 | +```c++ | ||
| 42 | +ge::graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) override { | ||
| 43 | + gert::Tensor *output = ctx->MakeOutputRefInput(0U, 0U); | ||
| 44 | + if (output == nullptr) { | ||
| 45 | + return ge::GRAPH_FAILED; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + return ge::GRAPH_SUCCESS; | ||
| 49 | +} | ||
| 50 | +``` | ||
| @@ -0,0 +1,60 @@ | |||
| 1 | +# MallocOutputTensor | ||
| 2 | + | ||
| 3 | +## 产品支持情况 | ||
| 4 | + | ||
| 5 | +全量芯片支持。 | ||
| 6 | + | ||
| 7 | +## 头文件/库文件 | ||
| 8 | + | ||
| 9 | +- 头文件:\#include <exe\_graph/runtime/host\_cpu\_op\_execution\_context.h\> | ||
| 10 | +- 库文件:liblowering.so | ||
| 11 | + | ||
| 12 | +## 功能说明 | ||
| 13 | + | ||
| 14 | +为某个输出Tensor申请Hsot内存,同时初始化输出Tensor的基本信息。 | ||
| 15 | + | ||
| 16 | +该输出Tensor的内存由Context构造方管理。接口调用者不需要主动释放。 | ||
| 17 | + | ||
| 18 | +## 函数原型 | ||
| 19 | + | ||
| 20 | +```c++ | ||
| 21 | +Tensor *MallocOutputTensor(size_t index, const StorageShape &shape, const StorageFormat &format, ge::DataType dtype) | ||
| 22 | +``` | ||
| 23 | + | ||
| 24 | +## 参数说明 | ||
| 25 | + | ||
| 26 | +| 参数名 | 输入/输出 | 说明 | | ||
| 27 | +| --- | --- | --- | | ||
| 28 | +| index | 输入 | 输出索引。 | | ||
| 29 | +| shape | 输入 | 输出tensor的shape。 | | ||
| 30 | +| format | 输入 | 输出tensor的format。 | | ||
| 31 | +| dtype | 输入 | 输出tensor的data type。 | | ||
| 32 | + | ||
| 33 | +## 返回值说明 | ||
| 34 | + | ||
| 35 | +Tensor指针,异常时返回空指针。 | ||
| 36 | + | ||
| 37 | +## 约束说明 | ||
| 38 | + | ||
| 39 | +无 | ||
| 40 | + | ||
| 41 | +## 调用示例 | ||
| 42 | + | ||
| 43 | +以下片段位于`HostCpuExecuteOp::Execute`实现中,将第0个输入的Shape、Format和DataType用于申请第0个输出Tensor。 | ||
| 44 | + | ||
| 45 | +```c++ | ||
| 46 | +ge::graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) override { | ||
| 47 | + const gert::Tensor *input = ctx->GetInputTensor(0U); | ||
| 48 | + if (input == nullptr) { | ||
| 49 | + return ge::GRAPH_FAILED; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + gert::Tensor *output = | ||
| 53 | + ctx->MallocOutputTensor(0U, input->GetShape(), input->GetFormat(), input->GetDataType()); | ||
| 54 | + if (output == nullptr) { | ||
| 55 | + return ge::GRAPH_FAILED; | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + return ge::GRAPH_SUCCESS; | ||
| 59 | +} | ||
| 60 | +``` | ||
| @@ -0,0 +1,23 @@ | |||
| 1 | +# 简介 | ||
| 2 | + | ||
| 3 | +用于执行算子的上下文环境。 | ||
| 4 | + | ||
| 5 | +为算子在host上运行时提供输入输出管理、内存分配等运行时支持。 | ||
| 6 | + | ||
| 7 | +## 需要包含的头文件 | ||
| 8 | + | ||
| 9 | +```c++ | ||
| 10 | +#include <exe_graph/runtime/host_cpu_op_execution_context.h> | ||
| 11 | +``` | ||
| 12 | + | ||
| 13 | +## Public成员函数 | ||
| 14 | + | ||
| 15 | +```c++ | ||
| 16 | +const Tensor *GetInputTensor(size_t index) const | ||
| 17 | +const Tensor *GetRequiredInputTensor(size_t ir_index) const | ||
| 18 | +const Tensor *GetOptionalInputTensor(size_t ir_index) const | ||
| 19 | +const Tensor *GetDynamicInputTensor(size_t ir_index, size_t relative_index) const | ||
| 20 | +const Tensor *GetOutputTensor(size_t index) const | ||
| 21 | +Tensor *MallocOutputTensor(size_t index, const StorageShape &shape, const StorageFormat &format, ge::DataType dtype) | ||
| 22 | +Tensor *MakeOutputRefInput(size_t output_index, size_t input_index) | ||
| 23 | +``` | ||
| @@ -68,6 +68,7 @@ GE图引擎接口头文件在如下目录: | |||
| 68 | | acl/acl_op.h | ACL单算子描述、执行及算子属性接口。 | libacl_op_executor.so(公开门面;内部实现依赖不作为应用直链接口) | | 68 | | acl/acl_op.h | ACL单算子描述、执行及算子属性接口。 | libacl_op_executor.so(公开门面;内部实现依赖不作为应用直链接口) | |
| 69 | | acl/ops/acl_cblas.h | ACL CBLAS/矩阵计算接口。 | libacl_cblas.so | | 69 | | acl/ops/acl_cblas.h | ACL CBLAS/矩阵计算接口。 | libacl_cblas.so | |
| 70 | | exe_graph/runtime/eager_op_execution_context.h | Eager算子执行时的输入、输出、Stream和Workspace上下文。 | liblowering.so | | 70 | | exe_graph/runtime/eager_op_execution_context.h | Eager算子执行时的输入、输出、Stream和Workspace上下文。 | liblowering.so | |
| 71 | +| exe_graph/runtime/host_cpu_op_execution_context.h | Host CPU算子执行时的输入、输出和Host内存管理上下文。 | liblowering.so | | ||
| 71 | | exe_graph/runtime/op_compile_context.h | 算子编译、Tiling和Shape推导上下文。 | liblowering.so | | 72 | | exe_graph/runtime/op_compile_context.h | 算子编译、Tiling和Shape推导上下文。 | liblowering.so | |
| 72 | | graph/graph.h | GE Graph创建、增删节点、输入输出与图属性接口。 | libgraph.so | | 73 | | graph/graph.h | GE Graph创建、增删节点、输入输出与图属性接口。 | libgraph.so | |
| 73 | | graph/ct_infer_shape_range_context.h | 编译期Shape Range推导上下文。 | Header-only接口;主要消费库libgraph.so | | 74 | | graph/ct_infer_shape_range_context.h | 编译期Shape Range推导上下文。 | Header-only接口;主要消费库libgraph.so | |
| @@ -0,0 +1,53 @@ | |||
| 1 | +# execute | ||
| 2 | + | ||
| 3 | +## 产品支持情况 | ||
| 4 | + | ||
| 5 | +全量芯片支持。 | ||
| 6 | + | ||
| 7 | +## 功能说明 | ||
| 8 | + | ||
| 9 | +Python自定义算子的运行时执行回调。将实现类通过[register_op_impl](register_op_impl.md)注册并提供可调用的`execute`方法后,GE根据算子原型组装输入和属性参数,并在算子执行阶段调用该方法。 | ||
| 10 | + | ||
| 11 | +`execute`回调不接收输出参数,也不通过返回值传递输出。可通过[get_execute_ctx](get_execute_ctx.md)获取`EagerOpExecutionContext`,申请输出内存或建立输出与输入之间的Ref关系。 | ||
| 12 | + | ||
| 13 | +## 函数原型 | ||
| 14 | + | ||
| 15 | +`execute`方法的签名遵循以下模式: | ||
| 16 | + | ||
| 17 | +```python | ||
| 18 | +def execute(self, input_0, ..., *, attr_0, ...) -> None | ||
| 19 | +``` | ||
| 20 | + | ||
| 21 | +上面的参数名仅表示参数位置。实际参数数量和属性名称由算子的原型决定。 | ||
| 22 | + | ||
| 23 | +## 参数说明 | ||
| 24 | + | ||
| 25 | +| 参数 | 绑定规则 | | ||
| 26 | +| :--- |:-------------------------------------| | ||
| 27 | +| 输入参数 | 位于参数列表前部。required input传入`Tensor`,optional input传入`Optional[Tensor]`,dynamic input传入`List[Tensor]`。 | | ||
| 28 | +| 属性参数 | 位于所有输入参数之后,必须使用keyword-only参数;参数名称、顺序和类型与算子原型一致。 | | ||
| 29 | + | ||
| 30 | +参数类型由算子原型决定。参数提供类型注解时,注解必须与对应的输入或属性类型一致。 | ||
| 31 | + | ||
| 32 | +## 约束说明 | ||
| 33 | + | ||
| 34 | +- `execute`只能以schema-bound形式使用。算子必须存在算子原型;否则在校验或调用时抛出`RuntimeError`。 | ||
| 35 | +- 回调不得声明可变位置参数或可变关键字参数。输入和属性的数量、顺序或属性名称不匹配时,抛出`TypeError`。 | ||
| 36 | +- `execute`无需为输入参数指定类型提示;但是,任何指定的类型提示都将根据算子原型进行验证,以确保一致性。回调必须显式声明`-> None`返回注解,并且返回值必须为`None`。 | ||
| 37 | +- 回调返回值必须为`None`,并且必须声明`-> None`返回注解。 | ||
| 38 | +- `get_execute_ctx()`只能在当前同步`execute`回调内调用。返回的`EagerOpExecutionContext`、`Tensor`和`RuntimeAttrs`等借用对象只能在当前回调内使用,回调返回或抛出异常后失效。 | ||
| 39 | + | ||
| 40 | +## 调用示例 | ||
| 41 | + | ||
| 42 | +```python | ||
| 43 | +from ge.custom_op import get_execute_ctx, register_op_impl | ||
| 44 | +from ge.runtime import Tensor | ||
| 45 | + | ||
| 46 | + | ||
| 47 | +@register_op_impl(op_type="AddPythonCustomOp") | ||
| 48 | +class AddPythonCustomOp: | ||
| 49 | + def execute(self, x: Tensor, y: Tensor) -> None: | ||
| 50 | + ctx = get_execute_ctx() | ||
| 51 | + output = ctx.malloc_output_tensor(0, x.shape, x.format, x.data_type) | ||
| 52 | + # 使用x、y、output和ctx.get_stream()发起当前算子的执行。 | ||
| 53 | +``` | ||
| @@ -6,7 +6,7 @@ | |||
| 6 | 6 | ||
| 7 | ## 功能说明 | 7 | ## 功能说明 |
| 8 | 8 | ||
| 9 | -注册Python自定义算子实现类。装饰器反射类上的可调用方法:`execute`、`compile`、`declare_launch_args`分别声明`eager_execute`、`compilable`、`annotated_args`能力。三种能力可以组合使用,具体回调约束参见[compile](compile.md)和[declare_launch_args](declare_launch_args.md)。 | 9 | +注册Python自定义算子实现类。支持实现`execute`、`compile`和`declare_launch_args`方法,这些方法可以组合使用。 |
| 10 | 10 | ||
| 11 | ## 函数原型 | 11 | ## 函数原型 |
| 12 | 12 | ||
| @@ -22,21 +22,20 @@ register_op_impl(*, op_type: str) -> callable | |||
| 22 | 22 | ||
| 23 | ## 约束说明 | 23 | ## 约束说明 |
| 24 | 24 | ||
| 25 | -- 被装饰对象必须是具体类,并且至少实现一个受支持的可调用能力方法。当前受支持的方法为`execute`、`compile`和`declare_launch_args`。 | 25 | +- 注册的实现类必须是具体类,并且至少实现一个受支持的方法。具体回调约束参见[execute](execute.md)、[compile](compile.md)和[declare_launch_args](declare_launch_args.md)。 |
| 26 | -- `op_type`不合法、被装饰对象不是具体类,或实现类未提供受支持的可调用能力方法时,抛出`TypeError`。`op_type`重复注册发生冲突时,抛出`ValueError`。 | 26 | +- `op_type`不合法、注册的实现类不是具体类,或实现类未提供受支持的方法时,抛出`TypeError`。`op_type`重复注册发生冲突时,抛出`ValueError`。 |
| 27 | -- 注册阶段只收集实现类的能力,不校验`declare_launch_args`的业务参数签名。在可获得Ascend IR算子原型后的实现描述符校验阶段,`declare_launch_args`的参数按Ascend IR算子原型中输入、输出、仅限关键字属性的顺序绑定。 | ||
| 28 | -- `compile`只支持schema-bound形式:参数按Ascend IR算子原型的输入、输出顺序绑定,属性使用名称、顺序与Ascend IR算子原型一致的keyword-only参数,返回注解和返回值均必须为`None`。它在图编译阶段调用;回调中通过[get_compile_ctx](get_compile_ctx.md)查询编译环境。 | ||
| 29 | -- `declare_launch_args`的必选输入和必选输出参数类型为`Tensor`,可选输入参数类型为`Optional[Tensor]`,动态输入和动态输出参数类型为`List[Tensor]`。属性参数必须为仅限关键字参数,并与Ascend IR算子原型中的属性名称和类型一致。 | ||
| 30 | -- `declare_launch_args`的返回注解和返回值均必须为`None`。签名或返回值不符合要求时,抛出`TypeError`。 | ||
| 31 | 27 | ||
| 32 | ## 调用示例 | 28 | ## 调用示例 |
| 33 | 29 | ||
| 34 | ```python | 30 | ```python |
| 35 | -from ge.custom_op import register_op_impl | 31 | +from ge.custom_op import get_execute_ctx, register_op_impl |
| 32 | +from ge.runtime import Tensor | ||
| 36 | 33 | ||
| 37 | 34 | ||
| 38 | @register_op_impl(op_type="AddCustom") | 35 | @register_op_impl(op_type="AddCustom") |
| 39 | class AddCustom: | 36 | class AddCustom: |
| 40 | - def execute(self, x, y): | 37 | + def execute(self, x: Tensor, y: Tensor) -> None: |
| 41 | - return x + y | 38 | + ctx = get_execute_ctx() |
| 39 | + output = ctx.malloc_output_tensor(0, x.shape, x.format, x.data_type) | ||
| 40 | + # 使用x、y、output和ctx.get_stream()发起当前算子的执行。 | ||
| 42 | ``` | 41 | ``` |
| @@ -226,12 +226,15 @@ REG_OPTION(OO_CONSTANT_FOLDING) | |||
| 226 | 3. **内存优先策略检查** | 226 | 3. **内存优先策略检查** |
| 227 | - 当配置了 MemoryPriority 策略时,若输入常量节点的 Shape 较大(>8)且被多个下游共享,则跳过折叠。因为折叠会复制一份常量数据,在内存受限场景下得不偿失 | 227 | - 当配置了 MemoryPriority 策略时,若输入常量节点的 Shape 较大(>8)且被多个下游共享,则跳过折叠。因为折叠会复制一份常量数据,在内存受限场景下得不偿失 |
| 228 | 228 | ||
| 229 | -4. **计算执行(两级回退策略)** | 229 | +4. **计算执行(三级策略)** |
| 230 | - - **第一级:AICPU 算子内核**(`ComputeWithHostCpuKernel`) | 230 | + - **第一级:Host CPU 自定义算子**(`ComputeWithHostCpuCustomOp`) |
| 231 | + - 通过 `CustomOpFactory::IsExistOp(op_type, OpBackend::kHostCPU)` 判断当前算子是否注册 Host CPU backend 自定义实现 | ||
| 232 | + - 通过 `CreateOrGetCustomOp(op_type, kHostCPU)` 获取实例并调用 `HostCpuExecuteOp::Execute` | ||
| 233 | + - **第二级:AICPU 算子内核**(`ComputeWithHostCpuKernel`) | ||
| 231 | - 尝试通过 `aicpu_ascend_kernel` 引擎获取算子的 Host CPU 实现 | 234 | - 尝试通过 `aicpu_ascend_kernel` 引擎获取算子的 Host CPU 实现 |
| 232 | - 通过 `OpKernelRegistry` 创建算子实例,由 `HostCpuEngine` 执行 | 235 | - 通过 `OpKernelRegistry` 创建算子实例,由 `HostCpuEngine` 执行 |
| 233 | - 这一级支持最广泛的算子类型,运行时加载 `libconstant_folding_ops.so` | 236 | - 这一级支持最广泛的算子类型,运行时加载 `libconstant_folding_ops.so` |
| 234 | - - **第二级:GE 内置内核**(`ComputeWithBuiltInKernel`) | 237 | + - **第三级:GE 内置内核**(`ComputeWithBuiltInKernel`) |
| 235 | - 若 AICPU 不支持该算子,回退到 GE 内置的 Host Kernel | 238 | - 若 AICPU 不支持该算子,回退到 GE 内置的 Host Kernel |
| 236 | - 通过 `KernelFactory` 按算子类型查找注册的 Kernel(`folding_pass::GetKernelByType`) | 239 | - 通过 `KernelFactory` 按算子类型查找注册的 Kernel(`folding_pass::GetKernelByType`) |
| 237 | - GE 内置 Kernel 覆盖了约 40 种常见算子 | 240 | - GE 内置 Kernel 覆盖了约 40 种常见算子 |
| @@ -241,7 +244,7 @@ REG_OPTION(OO_CONSTANT_FOLDING) | |||
| 241 | - 新建的 Const 节点会被标记 `_is_from_constant_folding=true`,用于后续流程识别 | 244 | - 新建的 Const 节点会被标记 `_is_from_constant_folding=true`,用于后续流程识别 |
| 242 | 245 | ||
| 243 | 6. **性能统计** | 246 | 6. **性能统计** |
| 244 | - - 分别记录 AICPU 内核和 GE 内置内核的折叠耗时和调用次数 | 247 | + - 分别记录 GE 内置 Kernel 和 HostCpu 算子(Host CPU 自定义算子 / AICPU kernel)常量折叠的折叠耗时和调用次数 |
| 245 | - 在 `GraphManager::OptimizeStage1_2` 中汇总输出性能追踪日志 | 248 | - 在 `GraphManager::OptimizeStage1_2` 中汇总输出性能追踪日志 |
| 246 | 249 | ||
| 247 | Pass 注册宏: | 250 | Pass 注册宏: |
| @@ -335,6 +335,8 @@ flowchart TD | |||
| 335 | 335 | ||
| 336 | 引擎选择采用贪心策略——按优先级从高到低遍历,第一个 `CheckSupported()` 通过的引擎被选中。使用线程池(默认 16 线程)并行为节点选择引擎,通过 mutex 保护共享数据。 | 336 | 引擎选择采用贪心策略——按优先级从高到低遍历,第一个 `CheckSupported()` 通过的引擎被选中。使用线程池(默认 16 线程)并行为节点选择引擎,通过 mutex 保护共享数据。 |
| 337 | 337 | ||
| 338 | +在遵从以上引擎选择的逻辑之上,自定义引擎优先级最高。如 device 自定义算子引擎(DNN_VM_CUSTOM)优先于其他 Device 引擎;注册 Host CPU 实现的自定义算子优先于通用 Host CPU 引擎(DNN_VM_HOST_CPU)。 | ||
| 339 | + | ||
| 338 | 引擎重分配(`ReAssignEngine()`)通过策略模式实现。`EngineReAssignPass` 是策略接口,当前有两种实现: | 340 | 引擎重分配(`ReAssignEngine()`)通过策略模式实现。`EngineReAssignPass` 是策略接口,当前有两种实现: |
| 339 | 341 | ||
| 340 | - `DynamicDataFlowEngineReassignPass`:动态数据流场景下的引擎重分配 | 342 | - `DynamicDataFlowEngineReassignPass`:动态数据流场景下的引擎重分配 |
| @@ -9,6 +9,7 @@ | |||
| 9 | | `ascendc_add_custom` | Ascend C 算子通过 GE 入图 | PyTorch + TorchAir | Ascend C | CMake编译 | 不涉及 | [README](./ascendc_add_custom/README.md) | | 9 | | `ascendc_add_custom` | Ascend C 算子通过 GE 入图 | PyTorch + TorchAir | Ascend C | CMake编译 | 不涉及 | [README](./ascendc_add_custom/README.md) | |
| 10 | | `triton_add_custom` | Triton 算子通过 GE 入图 | TensorFlow | Triton | 预编译为 `npubin` | 不涉及 | [README](./triton_add_custom/README.md) | | 10 | | `triton_add_custom` | Triton 算子通过 GE 入图 | TensorFlow | Triton | 预编译为 `npubin` | 不涉及 | [README](./triton_add_custom/README.md) | |
| 11 | | `compilable_add_custom` | Ascend C 算子通过 GE 入图并生成 om离线模型 | GE + ATC离线编译 | Ascend C | RTC算子运行时编译 | 支持模型下沉到 om离线模型 | [README](./compilable_add_custom/README.md) | | 11 | | `compilable_add_custom` | Ascend C 算子通过 GE 入图并生成 om离线模型 | GE + ATC离线编译 | Ascend C | RTC算子运行时编译 | 支持模型下沉到 om离线模型 | [README](./compilable_add_custom/README.md) | |
| 12 | +| `host_cpu_add_custom` | HostCpu Add 样例(常量折叠 + 运行时 host 调度) | GE 在线执行 | C++ | C++ 直接编译 | 不涉及 | [README](./host_cpu_add_custom/README.md) | | ||
| 12 | | `python_compilable_add_custom` | Python 算子在在线图编译和 ATC 离线编译阶段生成 kernel,并验证在线执行与 OM 执行 | GE 在线执行 + ATC 离线编译 | Python + Ascend C | Python compile 回调中调用 BiSheng | 支持 OM 脱离 Python 插件执行 | [README](./python_compilable_add_custom/README.md) | | 13 | | `python_compilable_add_custom` | Python 算子在在线图编译和 ATC 离线编译阶段生成 kernel,并验证在线执行与 OM 执行 | GE 在线执行 + ATC 离线编译 | Python + Ascend C | Python compile 回调中调用 BiSheng | 支持 OM 脱离 Python 插件执行 | [README](./python_compilable_add_custom/README.md) | |
| 13 | | `data_dependent_shape_custom` | 数据依赖 shape 算子 | GE | Ascend C | CMake编译 | 不涉及 | [README](data_dependent_shape_custom/README.md) | | 14 | | `data_dependent_shape_custom` | 数据依赖 shape 算子 | GE | Ascend C | CMake编译 | 不涉及 | [README](data_dependent_shape_custom/README.md) | |
| 14 | | `args_refresh_add_custom` | ArgsUpdater 地址刷新 + MallocReadOnlyDevArgs + 性能对比 | GE 在线执行 | Ascend C | RTC 运行时编译 | 在线地址刷新性能对比 | [README](./args_refresh_add_custom/cpp/README.md) | | 15 | | `args_refresh_add_custom` | ArgsUpdater 地址刷新 + MallocReadOnlyDevArgs + 性能对比 | GE 在线执行 | Ascend C | RTC 运行时编译 | 在线地址刷新性能对比 | [README](./args_refresh_add_custom/cpp/README.md) | |
| @@ -31,6 +32,7 @@ GE 原生构图场景还需要提供 `REG_OP` proto 头文件,描述算子的 | |||
| 31 | |------------------------|--------------------------------------------------------------| | 32 | |------------------------|--------------------------------------------------------------| |
| 32 | | `class BaseCustomOp` | 自定义算子能力接口的公共基类,用户实现类按需组合继承其他能力接口。 | | 33 | | `class BaseCustomOp` | 自定义算子能力接口的公共基类,用户实现类按需组合继承其他能力接口。 | |
| 33 | | `class EagerExecuteOp` | 运行时执行能力,可获取输入 Tensor、申请输出 Tensor、申请 workspace 并发起 kernel 调用。 | | 34 | | `class EagerExecuteOp` | 运行时执行能力,可获取输入 Tensor、申请输出 Tensor、申请 workspace 并发起 kernel 调用。 | |
| 35 | +| `class HostCpuExecuteOp` | Host 侧执行能力,可获取输入 Tensor、申请输出 Tensor,并在常量折叠或 Host CPU 运行时完成计算。 | | ||
| 34 | | `class ArgsUpdater` | 回调式地址刷新能力,I/O 地址变化时由 GE 回调 `UpdateHostArgs` 更新已有 args buffer。 | | 36 | | `class ArgsUpdater` | 回调式地址刷新能力,I/O 地址变化时由 GE 回调 `UpdateHostArgs` 更新已有 args buffer。 | |
| 35 | | `class AnnotatedArgsOp` | 声明式地址刷新能力,编译期通过 `DeclareLaunchArgs` 标注输入、输出和 workspace 地址槽位,由 GE 生成任务并完成地址刷新。 | | 37 | | `class AnnotatedArgsOp` | 声明式地址刷新能力,编译期通过 `DeclareLaunchArgs` 标注输入、输出和 workspace 地址槽位,由 GE 生成任务并完成地址刷新。 | |
| 36 | | `class ShapeInferOp` | Shape / DataType 推导能力,用于在编译或构图阶段设置输出描述。 | | 38 | | `class ShapeInferOp` | Shape / DataType 推导能力,用于在编译或构图阶段设置输出描述。 | |
| @@ -43,6 +45,7 @@ GE 原生构图场景还需要提供 `REG_OP` proto 头文件,描述算子的 | |||
| 43 | 45 | ||
| 44 | | 场景 | 推荐实现 | | 46 | | 场景 | 推荐实现 | |
| 45 | |------------------------|-----------------------------------------------------------------------| | 47 | |------------------------|-----------------------------------------------------------------------| |
| 48 | +| HostCpu 常量折叠 | `HostCpuExecuteOp` + `ShapeInferOp(可选)` | | ||
| 46 | | 动态图在线执行 | `EagerExecuteOp` + `ShapeInferOp(可选)` | | 49 | | 动态图在线执行 | `EagerExecuteOp` + `ShapeInferOp(可选)` | |
| 47 | | 动态图在线执行 + 算子在线编译 | `EagerExecuteOp` + `CompilableOp` + `ShapeInferOp(可选)` | | 50 | | 动态图在线执行 + 算子在线编译 | `EagerExecuteOp` + `CompilableOp` + `ShapeInferOp(可选)` | |
| 48 | | 静态图离线下沉OM模型执行 + 算子在线编译 | `EagerExecuteOp` + `CompilableOp` + `ShapeInferOp(可选)` + `PortableOp` | | 51 | | 静态图离线下沉OM模型执行 + 算子在线编译 | `EagerExecuteOp` + `CompilableOp` + `ShapeInferOp(可选)` + `PortableOp` | |
| @@ -9,6 +9,7 @@ This directory provides samples related to custom operator graph integration, co | |||
| 9 | | `ascendc_add_custom` | Ascend C operator enters graph through GE | PyTorch + TorchAir | Ascend C | CMake compilation | Not involved | [README](./ascendc_add_custom/README_en.md) | | 9 | | `ascendc_add_custom` | Ascend C operator enters graph through GE | PyTorch + TorchAir | Ascend C | CMake compilation | Not involved | [README](./ascendc_add_custom/README_en.md) | |
| 10 | | `triton_add_custom` | Triton operator enters graph through GE | TensorFlow | Triton | Pre-compiled as `npubin` | Not involved | [README](./triton_add_custom/README_en.md) | | 10 | | `triton_add_custom` | Triton operator enters graph through GE | TensorFlow | Triton | Pre-compiled as `npubin` | Not involved | [README](./triton_add_custom/README_en.md) | |
| 11 | | `compilable_add_custom` | Ascend C operator enters graph through GE and generates om offline model | GE + ATC offline compilation | Ascend C | RTC operator runtime compilation | Supports model sink to om offline model | [README](./compilable_add_custom/README_en.md) | | 11 | | `compilable_add_custom` | Ascend C operator enters graph through GE and generates om offline model | GE + ATC offline compilation | Ascend C | RTC operator runtime compilation | Supports model sink to om offline model | [README](./compilable_add_custom/README_en.md) | |
| 12 | +| `host_cpu_add_custom` | HostCpu Add sample with constant folding and runtime host scheduling | GE online execution | C++ | Direct C++ compilation | Not involved | [README](./host_cpu_add_custom/README_en.md) | | ||
| 12 | | `python_compilable_add_custom` | Python operator compiles a kernel in online graph compilation and offline ATC compilation, then verifies online and OM execution | GE online execution + ATC offline compilation | Python + Ascend C | BiSheng invoked by the Python compile callback | OM executes without the Python plugin | [README](./python_compilable_add_custom/README_en.md) | | 13 | | `python_compilable_add_custom` | Python operator compiles a kernel in online graph compilation and offline ATC compilation, then verifies online and OM execution | GE online execution + ATC offline compilation | Python + Ascend C | BiSheng invoked by the Python compile callback | OM executes without the Python plugin | [README](./python_compilable_add_custom/README_en.md) | |
| 13 | | `data_dependent_shape_custom` | Data dependent shape operator | GE | Ascend C | CMake compilation | Not involved | [README](data_dependent_shape_custom/README_en.md) | | 14 | | `data_dependent_shape_custom` | Data dependent shape operator | GE | Ascend C | CMake compilation | Not involved | [README](data_dependent_shape_custom/README_en.md) | |
| 14 | | `args_refresh_add_custom` | ArgsUpdater address refresh + MallocReadOnlyDevArgs + performance comparison | GE online execution | Ascend C | RTC runtime compilation | Online address refresh performance comparison | [README](./args_refresh_add_custom/cpp/README_en.md) | | 15 | | `args_refresh_add_custom` | ArgsUpdater address refresh + MallocReadOnlyDevArgs + performance comparison | GE online execution | Ascend C | RTC runtime compilation | Online address refresh performance comparison | [README](./args_refresh_add_custom/cpp/README_en.md) | |
| @@ -31,6 +32,7 @@ Currently provided interface functionality: | |||
| 31 | |------------------|---------| | 32 | |------------------|---------| |
| 32 | | `class BaseCustomOp` | Common base class for custom operator capability interfaces, user implementation classes combine and inherit other capability interfaces as needed. | | 33 | | `class BaseCustomOp` | Common base class for custom operator capability interfaces, user implementation classes combine and inherit other capability interfaces as needed. | |
| 33 | | `class EagerExecuteOp` | Runtime execution capability, can get input Tensor, allocate output Tensor, allocate workspace and initiate kernel call. | | 34 | | `class EagerExecuteOp` | Runtime execution capability, can get input Tensor, allocate output Tensor, allocate workspace and initiate kernel call. | |
| 35 | +| `class HostCpuExecuteOp` | Host-side execution capability, can get input Tensor, allocate output Tensor, and compute during constant folding or Host CPU runtime. | | ||
| 34 | | `class ArgsUpdater` | Callback-based args address refresh capability. When I/O addresses change, GE invokes `UpdateHostArgs` to update the existing args buffer. | | 36 | | `class ArgsUpdater` | Callback-based args address refresh capability. When I/O addresses change, GE invokes `UpdateHostArgs` to update the existing args buffer. | |
| 35 | | `class AnnotatedArgsOp` | Declarative kernel launch and args layout capability. At compile time, `DeclareLaunchArgs` annotates input, output, and workspace address slots so GE can generate tasks and refresh addresses. | | 37 | | `class AnnotatedArgsOp` | Declarative kernel launch and args layout capability. At compile time, `DeclareLaunchArgs` annotates input, output, and workspace address slots so GE can generate tasks and refresh addresses. | |
| 36 | | `class ShapeInferOp` | Shape / DataType derivation capability, used to set output description during compilation or graph composition phase. | | 38 | | `class ShapeInferOp` | Shape / DataType derivation capability, used to set output description during compilation or graph composition phase. | |
| @@ -43,6 +45,7 @@ Interface combination selection by scenario: | |||
| 43 | 45 | ||
| 44 | | Scenario | Recommended Implementation | | 46 | | Scenario | Recommended Implementation | |
| 45 | |---------|---------------------------| | 47 | |---------|---------------------------| |
| 48 | +| HostCpu constant folding | `HostCpuExecuteOp` + `ShapeInferOp(optional)` | | ||
| 46 | | Dynamic graph online execution | `EagerExecuteOp` + `ShapeInferOp(optional)` | | 49 | | Dynamic graph online execution | `EagerExecuteOp` + `ShapeInferOp(optional)` | |
| 47 | | Dynamic graph online execution + operator online compilation | `EagerExecuteOp` + `CompilableOp` + `ShapeInferOp(optional)` | | 50 | | Dynamic graph online execution + operator online compilation | `EagerExecuteOp` + `CompilableOp` + `ShapeInferOp(optional)` | |
| 48 | | Static graph offline sink OM model execution + operator online compilation | `EagerExecuteOp` + `CompilableOp` + `ShapeInferOp(optional)` + `PortableOp` | | 51 | | Static graph offline sink OM model execution + operator online compilation | `EagerExecuteOp` + `CompilableOp` + `ShapeInferOp(optional)` + `PortableOp` | |
| @@ -0,0 +1,7 @@ | |||
| 1 | +# HostCpu Add 自定义算子样例 | ||
| 2 | + | ||
| 3 | +本目录包含三个子场景: | ||
| 4 | + | ||
| 5 | +- [offline](./offline/README.md):ES 构图 + ATC 转 OM + ACL 加载执行,演示离线编译和部署全流程。 | ||
| 6 | +- [constant_folding](./constant_folding/README.md):ES 构图 + 常量折叠,验证编译期 HostCpu 执行。 | ||
| 7 | +- [host_scheduling](./host_scheduling/README.md):动态 shape + 小 shape,验证 `HostcpuEngineUpdatePass` 将内置算子调度到 HostCpu。 | ||
| @@ -0,0 +1,7 @@ | |||
| 1 | +# HostCpu Add Custom Op Samples | ||
| 2 | + | ||
| 3 | +This directory contains three sub-scenarios that share the `AddCustom` operator definition: | ||
| 4 | + | ||
| 5 | +- [offline](./offline/README_en.md): ES graph construction + ATC to OM + ACL load and execute, demonstrating the full offline compilation and deployment flow. | ||
| 6 | +- [constant_folding](./constant_folding/README_en.md): ES graph construction with constant folding, so HostCpu runs during compilation. | ||
| 7 | +- [host_scheduling](./host_scheduling/README_en.md): dynamic shape with a small shape, so `HostcpuEngineUpdatePass` schedules the built-in op to HostCpu. | ||
| @@ -0,0 +1,168 @@ | |||
| 1 | +cmake_minimum_required(VERSION 3.16) | ||
| 2 | +project(host_cpu_add_custom_constant_folding LANGUAGES CXX) | ||
| 3 | + | ||
| 4 | +option(HOST_CPU_ADD_BUILD_CUSTOM_OP "Build libcust_opapi.so" ON) | ||
| 5 | +option(HOST_CPU_ADD_BUILD_SESSION_RUN "Build session_run" ON) | ||
| 6 | + | ||
| 7 | +set(CMAKE_CXX_STANDARD 17) | ||
| 8 | +set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
| 9 | +set(CMAKE_CXX_EXTENSIONS OFF) | ||
| 10 | + | ||
| 11 | +if(NOT CMAKE_BUILD_TYPE) | ||
| 12 | + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) | ||
| 13 | +endif() | ||
| 14 | + | ||
| 15 | +set(COMMON_COMPILE_OPTIONS | ||
| 16 | + -Wall | ||
| 17 | + -Wextra | ||
| 18 | + -Wno-unused-parameter | ||
| 19 | +) | ||
| 20 | + | ||
| 21 | +set(ES_OUTPUT_DIR "${CMAKE_BINARY_DIR}/es_output") | ||
| 22 | +file(MAKE_DIRECTORY "${ES_OUTPUT_DIR}") | ||
| 23 | + | ||
| 24 | +set(PROJECT_OUTPUT_DIR "${CMAKE_SOURCE_DIR}/output") | ||
| 25 | +file(MAKE_DIRECTORY "${PROJECT_OUTPUT_DIR}") | ||
| 26 | +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") | ||
| 27 | + set(OPP_OS_TYPE "windows") | ||
| 28 | +else() | ||
| 29 | + set(OPP_OS_TYPE "linux") | ||
| 30 | +endif() | ||
| 31 | + | ||
| 32 | +string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" CMAKE_SYSTEM_PROCESSOR_LOWER) | ||
| 33 | +if(CMAKE_SYSTEM_PROCESSOR_LOWER MATCHES "^(aarch64|arm64)$") | ||
| 34 | + set(OPP_CPU_TYPE "aarch64") | ||
| 35 | +elseif(CMAKE_SYSTEM_PROCESSOR_LOWER MATCHES "^(x86_64|amd64)$") | ||
| 36 | + set(OPP_CPU_TYPE "x86_64") | ||
| 37 | +else() | ||
| 38 | + set(OPP_CPU_TYPE "${CMAKE_SYSTEM_PROCESSOR_LOWER}") | ||
| 39 | +endif() | ||
| 40 | + | ||
| 41 | +set(CUSTOM_OP_OUTPUT_DIR "${PROJECT_OUTPUT_DIR}/op_graph/lib/${OPP_OS_TYPE}/${OPP_CPU_TYPE}") | ||
| 42 | +file(MAKE_DIRECTORY "${CUSTOM_OP_OUTPUT_DIR}") | ||
| 43 | +set(CUSTOM_OP_INCLUDE_DIR "${PROJECT_OUTPUT_DIR}/op_graph/include") | ||
| 44 | +file(MAKE_DIRECTORY "${CUSTOM_OP_INCLUDE_DIR}") | ||
| 45 | +set(OP_PROTO_HEADER_SOURCE_FILE "${CMAKE_SOURCE_DIR}/ge/add_custom_ir.h") | ||
| 46 | +configure_file("${OP_PROTO_HEADER_SOURCE_FILE}" "${CUSTOM_OP_INCLUDE_DIR}/add_custom_ir.h" COPYONLY) | ||
| 47 | + | ||
| 48 | +set(ASCEND_HOME_PATH_OVERRIDE "" CACHE PATH "Optional ASCEND_HOME_PATH override") | ||
| 49 | +if(ASCEND_HOME_PATH_OVERRIDE) | ||
| 50 | + set(ASCEND_HOME_PATH "${ASCEND_HOME_PATH_OVERRIDE}") | ||
| 51 | +else() | ||
| 52 | + set(ASCEND_HOME_PATH "$ENV{ASCEND_HOME_PATH}") | ||
| 53 | +endif() | ||
| 54 | + | ||
| 55 | +if(ASCEND_HOME_PATH) | ||
| 56 | + message(STATUS "ASCEND_HOME_PATH: ${ASCEND_HOME_PATH}") | ||
| 57 | + list(APPEND CMAKE_MODULE_PATH "${ASCEND_HOME_PATH}/include/ge/cmake") | ||
| 58 | + find_package(GenerateEsPackage REQUIRED) | ||
| 59 | + | ||
| 60 | + add_library(add_custom_op_proto SHARED | ||
| 61 | + ge/add_custom_ir.cc | ||
| 62 | + ) | ||
| 63 | + target_compile_options(add_custom_op_proto PRIVATE | ||
| 64 | + -fvisibility=hidden | ||
| 65 | + ) | ||
| 66 | + target_compile_definitions(add_custom_op_proto PRIVATE | ||
| 67 | + _GLIBCXX_USE_CXX11_ABI=0 | ||
| 68 | + OP_PROTO_LIB | ||
| 69 | + ) | ||
| 70 | + target_include_directories(add_custom_op_proto PRIVATE | ||
| 71 | + "${CMAKE_SOURCE_DIR}/ge" | ||
| 72 | + "${ASCEND_HOME_PATH}/include" | ||
| 73 | + "${ASCEND_HOME_PATH}/include/graph" | ||
| 74 | + "${ASCEND_HOME_PATH}/include/register" | ||
| 75 | + "${ASCEND_HOME_PATH}/include/external" | ||
| 76 | + ) | ||
| 77 | + | ||
| 78 | + add_es_library( | ||
| 79 | + ES_LINKABLE_AND_ALL_TARGET es_custom | ||
| 80 | + OPP_PROTO_TARGET add_custom_op_proto | ||
| 81 | + OUTPUT_PATH ${ES_OUTPUT_DIR} | ||
| 82 | + ) | ||
| 83 | +else() | ||
| 84 | + message(WARNING "ASCEND_HOME_PATH is empty. Configure succeeds, but compilation requires a valid CANN toolkit path.") | ||
| 85 | +endif() | ||
| 86 | + | ||
| 87 | +if(HOST_CPU_ADD_BUILD_CUSTOM_OP) | ||
| 88 | + add_library(cust_opapi SHARED | ||
| 89 | + ge/custom_op.cpp | ||
| 90 | + ) | ||
| 91 | + target_compile_options(cust_opapi PRIVATE ${COMMON_COMPILE_OPTIONS}) | ||
| 92 | + target_compile_definitions(cust_opapi PRIVATE | ||
| 93 | + _GLIBCXX_USE_CXX11_ABI=0 | ||
| 94 | + ) | ||
| 95 | + | ||
| 96 | + if(ASCEND_HOME_PATH) | ||
| 97 | + target_include_directories(cust_opapi PRIVATE | ||
| 98 | + "${CMAKE_SOURCE_DIR}/ge" | ||
| 99 | + "${ASCEND_HOME_PATH}/include" | ||
| 100 | + "${ASCEND_HOME_PATH}/include/graph" | ||
| 101 | + "${ASCEND_HOME_PATH}/include/register" | ||
| 102 | + "${ASCEND_HOME_PATH}/include/external" | ||
| 103 | + ) | ||
| 104 | + target_link_directories(cust_opapi PRIVATE "${ASCEND_HOME_PATH}/lib64") | ||
| 105 | + target_link_libraries(cust_opapi PRIVATE | ||
| 106 | + -Wl,--no-as-needed | ||
| 107 | + ascendcl | ||
| 108 | + register | ||
| 109 | + gert | ||
| 110 | + pthread | ||
| 111 | + dl | ||
| 112 | + -Wl,--as-needed | ||
| 113 | + ) | ||
| 114 | + endif() | ||
| 115 | + | ||
| 116 | + set_target_properties(cust_opapi PROPERTIES | ||
| 117 | + OUTPUT_NAME "cust_opapi" | ||
| 118 | + LIBRARY_OUTPUT_DIRECTORY "${CUSTOM_OP_OUTPUT_DIR}" | ||
| 119 | + RUNTIME_OUTPUT_DIRECTORY "${CUSTOM_OP_OUTPUT_DIR}" | ||
| 120 | + ARCHIVE_OUTPUT_DIRECTORY "${CUSTOM_OP_OUTPUT_DIR}" | ||
| 121 | + ) | ||
| 122 | + | ||
| 123 | + install(FILES "${OP_PROTO_HEADER_SOURCE_FILE}" | ||
| 124 | + DESTINATION "${CUSTOM_OP_INCLUDE_DIR}" | ||
| 125 | + ) | ||
| 126 | +endif() | ||
| 127 | + | ||
| 128 | +if(HOST_CPU_ADD_BUILD_SESSION_RUN) | ||
| 129 | + add_executable(host_cpu_add_custom_constant_folding_session_run | ||
| 130 | + session_run/main.cc | ||
| 131 | + ) | ||
| 132 | + target_compile_options(host_cpu_add_custom_constant_folding_session_run PRIVATE ${COMMON_COMPILE_OPTIONS}) | ||
| 133 | + target_compile_definitions(host_cpu_add_custom_constant_folding_session_run PRIVATE | ||
| 134 | + _GLIBCXX_USE_CXX11_ABI=0 | ||
| 135 | + ) | ||
| 136 | + | ||
| 137 | + if(ASCEND_HOME_PATH) | ||
| 138 | + target_include_directories(host_cpu_add_custom_constant_folding_session_run PRIVATE | ||
| 139 | + "${CUSTOM_OP_INCLUDE_DIR}" | ||
| 140 | + "${ES_OUTPUT_DIR}/include/es_custom" | ||
| 141 | + "${CMAKE_SOURCE_DIR}/ge" | ||
| 142 | + "${ASCEND_HOME_PATH}/include" | ||
| 143 | + "${ASCEND_HOME_PATH}/include/graph" | ||
| 144 | + "${ASCEND_HOME_PATH}/include/ge" | ||
| 145 | + "${ASCEND_HOME_PATH}/opp/built-in/op_proto/inc" | ||
| 146 | + ) | ||
| 147 | + target_link_directories(host_cpu_add_custom_constant_folding_session_run PRIVATE "${ASCEND_HOME_PATH}/lib64") | ||
| 148 | + target_link_libraries(host_cpu_add_custom_constant_folding_session_run PRIVATE | ||
| 149 | + -Wl,--no-as-needed | ||
| 150 | + es_math | ||
| 151 | + -Wl,--as-needed | ||
| 152 | + es_custom | ||
| 153 | + graph | ||
| 154 | + ge_runner | ||
| 155 | + ge_compiler | ||
| 156 | + ascendcl | ||
| 157 | + graph_base | ||
| 158 | + c_sec | ||
| 159 | + ) | ||
| 160 | + add_dependencies(host_cpu_add_custom_constant_folding_session_run es_custom) | ||
| 161 | + endif() | ||
| 162 | + | ||
| 163 | + set_target_properties(host_cpu_add_custom_constant_folding_session_run PROPERTIES | ||
| 164 | + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" | ||
| 165 | + BUILD_RPATH "$ORIGIN:${ES_OUTPUT_DIR}/lib64" | ||
| 166 | + INSTALL_RPATH "$ORIGIN:${ES_OUTPUT_DIR}/lib64" | ||
| 167 | + ) | ||
| 168 | +endif() | ||
| @@ -0,0 +1,77 @@ | |||
| 1 | +# HostCpu 常量折叠 Add 自定义算子在线样例 | ||
| 2 | + | ||
| 3 | +## 样例概述 | ||
| 4 | + | ||
| 5 | +本样例定义一个最小 `AddCustom` 自定义算子,验证 HostCpu 自定义算子如何接入常量折叠。图中使用 `EsGraphBuilder::CreateConst` 构图,两个 `Const` 节点直接喂给 `AddCustom`,启用常量折叠后在编译期完成计算。 | ||
| 6 | + | ||
| 7 | +## 前置依赖 | ||
| 8 | + | ||
| 9 | +- 参考[安装指导](../../../../docs/zh/quick_install.md)完成 `toolkit` 和 `ops` 包安装。 | ||
| 10 | +- 设置环境变量(假设包安装在 `/usr/local/Ascend/`): | ||
| 11 | + ```bash | ||
| 12 | + source /usr/local/Ascend/cann/set_env.sh | ||
| 13 | + ``` | ||
| 14 | + | ||
| 15 | +## 快速运行 | ||
| 16 | + | ||
| 17 | +在 `examples/custom_op/host_cpu_add_custom/constant_folding` 目录下执行: | ||
| 18 | + | ||
| 19 | +```bash | ||
| 20 | +bash run.sh | ||
| 21 | +``` | ||
| 22 | + | ||
| 23 | +脚本会完成 configure、build、install,并生成 `libes_custom.so`。运行成功时,终端应打印: | ||
| 24 | + | ||
| 25 | +```text | ||
| 26 | +HostCpuExecuteOp::Execute for AddCustom | ||
| 27 | +output shape: [1] | ||
| 28 | +output values: 3 | ||
| 29 | +``` | ||
| 30 | + | ||
| 31 | +### Dump 图验证 | ||
| 32 | + | ||
| 33 | +开启 dump 图后,可以直观验证常量折叠是否生效: | ||
| 34 | + | ||
| 35 | +```bash | ||
| 36 | +export DUMP_GE_GRAPH=2 | ||
| 37 | +``` | ||
| 38 | + | ||
| 39 | +打开 `ge_proto_*_AfterInfershape.pbtxt`,图中应不再包含 `AddCustom` 节点(已被折叠为 `Const`)。 | ||
| 40 | + | ||
| 41 | +### 日志验证 | ||
| 42 | + | ||
| 43 | +```bash | ||
| 44 | +export ASCEND_SLOG_PRINT_TO_STDOUT=1 | ||
| 45 | +export ASCEND_GLOBAL_LOG_LEVEL=0 | ||
| 46 | +``` | ||
| 47 | + | ||
| 48 | +在日志中搜索 `Constant folding computation for node`,可看到 `return code: 0` 表示计算成功。 | ||
| 49 | + | ||
| 50 | +## 关键文件 | ||
| 51 | + | ||
| 52 | +```text | ||
| 53 | +constant_folding | ||
| 54 | +├── CMakeLists.txt | ||
| 55 | +├── run.sh | ||
| 56 | +├── ge | ||
| 57 | +│ ├── add_custom_ir.h // AddCustom 原型定义 | ||
| 58 | +│ ├── add_custom_ir.cc // 编译 AddCustom 原型,生成 ES custom API | ||
| 59 | +│ └── custom_op.cpp // HostCpuExecuteOp / ShapeInferOp 实现 | ||
| 60 | +└── session_run | ||
| 61 | + └── main.cc // ES 构图并调用 Session::RunGraph | ||
| 62 | +``` | ||
| 63 | + | ||
| 64 | +## 实现步骤 | ||
| 65 | + | ||
| 66 | +`ge/custom_op.cpp` 中 `AddCustom` 的实现是本样例的核心: | ||
| 67 | + | ||
| 68 | +- `HostCpuExecuteOp::Execute` 在 host 侧完成 float 加法,被 `ConstantFoldingPass` 在编译期调用。 | ||
| 69 | +- `AddCustom` 仅注册 `kHostCPU` backend,不提供 device/Eager 实现。 | ||
| 70 | +- `ShapeInferOp` 将输出 shape 和 dtype 设为与输入一致。 | ||
| 71 | +- `Session::GEInitialize` 使用 GE 默认优化配置:默认优化级别为 `O3`,常量折叠默认为开启,使 `ConstantFoldingPass` 在编译期识别常量输入并调用 HostCpu 实现。 | ||
| 72 | + | ||
| 73 | +## 注意事项 | ||
| 74 | + | ||
| 75 | +- 本样例只覆盖常量折叠链路,运行时 host 调度样例见 `../host_scheduling`,离线 OM 样例见 `../offline`。 | ||
| 76 | +- `AddCustom` 仅实现最小 float32 Add,主要用于验证 HostCpu 常量折叠路径。 | ||
| 77 | +- `ASCEND_CUSTOM_OPP_PATH` 会在 `run.sh` 中自动追加当前样例的 `output/`。 | ||
| @@ -0,0 +1,85 @@ | |||
| 1 | +# HostCpu Constant Folding Add Custom Op Online Sample | ||
| 2 | + | ||
| 3 | +## Overview | ||
| 4 | + | ||
| 5 | +This sample defines a minimal `AddCustom` custom operator to demonstrate how a HostCpu custom op participates in constant folding. The graph uses `EsGraphBuilder::CreateConst` for construction, where two `Const` nodes feed into `AddCustom`, and constant folding computes the result at compile time. | ||
| 6 | + | ||
| 7 | +## Prerequisites | ||
| 8 | + | ||
| 9 | +- Follow the [Installation Guide](../../../../docs/en/quick_install.md) to install the `toolkit` and `ops` packages. | ||
| 10 | +- Set the environment variables (assuming that the packages are installed in `/usr/local/Ascend/`): | ||
| 11 | + ```bash | ||
| 12 | + source /usr/local/Ascend/cann/set_env.sh | ||
| 13 | + ``` | ||
| 14 | + | ||
| 15 | +## Quick Run | ||
| 16 | + | ||
| 17 | +Run in `examples/custom_op/host_cpu_add_custom/constant_folding`: | ||
| 18 | + | ||
| 19 | +```bash | ||
| 20 | +bash run.sh | ||
| 21 | +``` | ||
| 22 | + | ||
| 23 | +The script configures, builds, installs, and generates `libes_custom.so`. Expected output includes: | ||
| 24 | + | ||
| 25 | +```text | ||
| 26 | +HostCpuExecuteOp::Execute for AddCustom | ||
| 27 | +output shape: [1] | ||
| 28 | +output values: 3 | ||
| 29 | +``` | ||
| 30 | + | ||
| 31 | +If constant folding does not hit, this HostCPU-only operator does not enter the device Eager execution path. | ||
| 32 | + | ||
| 33 | +### Dump Graph Verification | ||
| 34 | + | ||
| 35 | +Enable graph dumping to visually verify constant folding: | ||
| 36 | + | ||
| 37 | +```bash | ||
| 38 | +export DUMP_GE_GRAPH=2 | ||
| 39 | +cd build | ||
| 40 | +./host_cpu_add_custom_constant_folding_session_run | ||
| 41 | +cd .. | ||
| 42 | +``` | ||
| 43 | + | ||
| 44 | +Open `ge_proto_*_AfterInfershape.pbtxt` — the graph should no longer contain the `AddCustom` node (folded into `Const`). | ||
| 45 | + | ||
| 46 | +### Log Verification | ||
| 47 | + | ||
| 48 | +```bash | ||
| 49 | +export ASCEND_SLOG_PRINT_TO_STDOUT=1 | ||
| 50 | +export ASCEND_GLOBAL_LOG_LEVEL=0 | ||
| 51 | +cd build | ||
| 52 | +./host_cpu_add_custom_constant_folding_session_run | ||
| 53 | +cd .. | ||
| 54 | +``` | ||
| 55 | + | ||
| 56 | +Search for `Constant folding computation for node` in the logs — `return code: 0` indicates success. | ||
| 57 | + | ||
| 58 | +## Key Files | ||
| 59 | + | ||
| 60 | +```text | ||
| 61 | +constant_folding | ||
| 62 | +├── CMakeLists.txt | ||
| 63 | +├── run.sh | ||
| 64 | +├── ge | ||
| 65 | +│ ├── add_custom_ir.h // AddCustom operation prototype | ||
| 66 | +│ ├── add_custom_ir.cc // Compiles the AddCustom operation prototype and generates the ES custom API | ||
| 67 | +│ └── custom_op.cpp // HostCpuExecuteOp / ShapeInferOp implementation | ||
| 68 | +└── session_run | ||
| 69 | + └── main.cc // ES graph construction and Session::RunGraph | ||
| 70 | +``` | ||
| 71 | + | ||
| 72 | +## Implementation Steps | ||
| 73 | + | ||
| 74 | +`AddCustom` in `ge/custom_op.cpp` is the core implementation: | ||
| 75 | + | ||
| 76 | +- `HostCpuExecuteOp::Execute` performs float addition on the host side, called by `ConstantFoldingPass` at compile time. | ||
| 77 | +- `AddCustom` registers only the `kHostCPU` backend and provides no device/Eager implementation. | ||
| 78 | +- `ShapeInferOp` copies input shape and dtype to the output. | ||
| 79 | +- `Session::GEInitialize` uses the GE default optimization settings: the default optimization level is `O3`, and constant folding is enabled by default. `ConstantFoldingPass` can therefore detect constant inputs and invoke the HostCpu implementation. | ||
| 80 | + | ||
| 81 | +## Notes | ||
| 82 | + | ||
| 83 | +- This sample only covers the constant-folding path; the runtime host scheduling sample lives in `../host_scheduling`, and the offline OM sample in `../offline`. | ||
| 84 | +- `AddCustom` is intentionally minimal and float32-only so the HostCpu constant-folding path stays easy to verify. | ||
| 85 | +- `ASCEND_CUSTOM_OPP_PATH` is appended automatically by `run.sh` with this sample's `output/`. | ||
| @@ -0,0 +1,13 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | +namespace ge {} // namespace ge | ||
| @@ -0,0 +1,26 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +namespace ge { | ||
| 17 | +REG_OP(AddCustom) | ||
| 18 | + .INPUT(x, "T") | ||
| 19 | + .INPUT(y, "T") | ||
| 20 | + .OUTPUT(z, "T") | ||
| 21 | + .DATATYPE(T, TensorType({DT_FLOAT, DT_INT32, DT_INT64, DT_FLOAT16, DT_INT16, DT_INT8, DT_UINT8, DT_DOUBLE, | ||
| 22 | + DT_COMPLEX128, DT_COMPLEX64, DT_STRING})) | ||
| 23 | + .OP_END_FACTORY_REG(AddCustom); | ||
| 24 | +} // namespace ge | ||
| 25 | + | ||
| 26 | + | ||
| @@ -0,0 +1,71 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +namespace { | ||
| 17 | +constexpr size_t kInputIndexX = 0U; | ||
| 18 | +constexpr size_t kInputIndexY = 1U; | ||
| 19 | +constexpr size_t kOutputIndexZ = 0U; | ||
| 20 | +} // namespace | ||
| 21 | + | ||
| 22 | +namespace ge { | ||
| 23 | +class AddCustom final : public HostCpuExecuteOp, public ShapeInferOp { | ||
| 24 | + public: | ||
| 25 | + graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) override { | ||
| 26 | + std::cout << "HostCpuExecuteOp::Execute for AddCustom" << std::endl; | ||
| 27 | + | ||
| 28 | + const gert::Tensor *input_x = ctx->GetInputTensor(kInputIndexX); | ||
| 29 | + const gert::Tensor *input_y = ctx->GetInputTensor(kInputIndexY); | ||
| 30 | + if ((input_x == nullptr) || (input_y == nullptr)) { | ||
| 31 | + std::cerr << "GetInputTensor failed, input_x=" << input_x << ", input_y=" << input_y << std::endl; | ||
| 32 | + return GRAPH_FAILED; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + gert::Tensor *output_z = | ||
| 36 | + ctx->MallocOutputTensor(kOutputIndexZ, input_x->GetShape(), input_x->GetFormat(), input_x->GetDataType()); | ||
| 37 | + if (output_z == nullptr) { | ||
| 38 | + std::cerr << "MallocOutputTensor failed" << std::endl; | ||
| 39 | + return GRAPH_FAILED; | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + const float *x = input_x->GetData<float>(); | ||
| 43 | + const float *y = input_y->GetData<float>(); | ||
| 44 | + float *z = output_z->GetData<float>(); | ||
| 45 | + const int64_t shape_size = input_x->GetStorageShape().GetShapeSize(); | ||
| 46 | + for (size_t i = 0U; i < shape_size; ++i) { | ||
| 47 | + z[i] = x[i] + y[i]; | ||
| 48 | + } | ||
| 49 | + return GRAPH_SUCCESS; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + graphStatus InferShape(gert::InferShapeContext *ctx) override { | ||
| 53 | + std::cout << "InferShape for AddCustom" << std::endl; | ||
| 54 | + const gert::Shape *input_shape = ctx->GetInputShape(kInputIndexX); | ||
| 55 | + gert::Shape *output_shape = ctx->GetOutputShape(kOutputIndexZ); | ||
| 56 | + if ((input_shape == nullptr) || (output_shape == nullptr)) { | ||
| 57 | + std::cerr << "InferShape failed, input_shape=" << input_shape << ", output_shape=" << output_shape << std::endl; | ||
| 58 | + return GRAPH_FAILED; | ||
| 59 | + } | ||
| 60 | + *output_shape = *input_shape; | ||
| 61 | + return GRAPH_SUCCESS; | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + graphStatus InferDataType(gert::InferDataTypeContext *ctx) override { | ||
| 65 | + std::cout << "InferDataType for AddCustom" << std::endl; | ||
| 66 | + return ctx->SetOutputDataType(kOutputIndexZ, ctx->GetInputDataType(kInputIndexX)); | ||
| 67 | + } | ||
| 68 | +}; | ||
| 69 | + | ||
| 70 | +REG_OP_BACKEND(AddCustom, "AddCustom", ge::OpBackend::kHostCPU); | ||
| 71 | +} // namespace ge | ||
| @@ -0,0 +1,126 @@ | |||
| 1 | +#!/usr/bin/env bash | ||
| 2 | +# ----------------------------------------------------------------------------------------------------------- | ||
| 3 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 4 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 5 | +# CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 6 | +# Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 7 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 8 | +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 9 | +# See LICENSE in the root of the software repository for the full text of the License. | ||
| 10 | +# ----------------------------------------------------------------------------------------------------------- | ||
| 11 | + | ||
| 12 | +set -euo pipefail | ||
| 13 | + | ||
| 14 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| 15 | +PROJECT_DIR="${SCRIPT_DIR}" | ||
| 16 | +BUILD_DIR="${PROJECT_DIR}/build" | ||
| 17 | +OUTPUT_DIR="${PROJECT_DIR}/output" | ||
| 18 | + | ||
| 19 | +info() { | ||
| 20 | + echo "[INFO] $*" | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +error() { | ||
| 24 | + echo "[ERROR] $*" >&2 | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +detect_opp_os_dir() { | ||
| 28 | + local os_name | ||
| 29 | + os_name="$(uname -s | tr '[:upper:]' '[:lower:]')" | ||
| 30 | + case "${os_name}" in | ||
| 31 | + mingw*|msys*|cygwin*) echo "windows" ;; | ||
| 32 | + *) echo "linux" ;; | ||
| 33 | + esac | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +detect_opp_arch_dir() { | ||
| 37 | + local arch_name | ||
| 38 | + arch_name="$(uname -m | tr '[:upper:]' '[:lower:]')" | ||
| 39 | + case "${arch_name}" in | ||
| 40 | + aarch64|arm64) echo "aarch64" ;; | ||
| 41 | + x86_64|amd64) echo "x86_64" ;; | ||
| 42 | + *) echo "${arch_name}" ;; | ||
| 43 | + esac | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +get_custom_op_library_name() { | ||
| 47 | + if [[ "$(detect_opp_os_dir)" == "windows" ]]; then | ||
| 48 | + echo "cust_opapi.dll" | ||
| 49 | + return | ||
| 50 | + fi | ||
| 51 | + echo "libcust_opapi.so" | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +detect_jobs() { | ||
| 55 | + if command -v nproc >/dev/null 2>&1; then | ||
| 56 | + nproc | ||
| 57 | + return | ||
| 58 | + fi | ||
| 59 | + echo 8 | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +usage() { | ||
| 63 | + cat <<'EOF' | ||
| 64 | +Usage: | ||
| 65 | + bash run.sh | ||
| 66 | + | ||
| 67 | +Options: | ||
| 68 | + -h, --help 显示帮助信息 | ||
| 69 | +EOF | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +while [[ $# -gt 0 ]]; do | ||
| 73 | + case "$1" in | ||
| 74 | + -h|--help) | ||
| 75 | + usage | ||
| 76 | + exit 0 | ||
| 77 | + ;; | ||
| 78 | + *) | ||
| 79 | + error "Unknown option: $1" | ||
| 80 | + usage | ||
| 81 | + exit 1 | ||
| 82 | + ;; | ||
| 83 | + esac | ||
| 84 | + shift | ||
| 85 | +done | ||
| 86 | + | ||
| 87 | +if [[ -z "${ASCEND_HOME_PATH:-}" ]]; then | ||
| 88 | + error "ASCEND_HOME_PATH is empty. Please source CANN set_env.sh first." | ||
| 89 | + exit 1 | ||
| 90 | +fi | ||
| 91 | + | ||
| 92 | +CUSTOM_OP_DIR="${OUTPUT_DIR}/op_graph/lib/$(detect_opp_os_dir)/$(detect_opp_arch_dir)" | ||
| 93 | +CUSTOM_OP_LIBRARY_PATH="${CUSTOM_OP_DIR}/$(get_custom_op_library_name)" | ||
| 94 | +CUSTOM_OP_PROTO_HEADER_PATH="${OUTPUT_DIR}/op_graph/include/add_custom_ir.h" | ||
| 95 | +ES_CUSTOM_LIBRARY_PATH="${BUILD_DIR}/es_output/lib64/libes_custom.so" | ||
| 96 | + | ||
| 97 | +mkdir -p "${BUILD_DIR}" "${OUTPUT_DIR}" "${CUSTOM_OP_DIR}" | ||
| 98 | +JOBS="$(detect_jobs)" | ||
| 99 | + | ||
| 100 | +info "Step 1/2: configure and build sample targets" | ||
| 101 | +cmake -S "${PROJECT_DIR}" -B "${BUILD_DIR}" -DCMAKE_BUILD_TYPE=Release | ||
| 102 | +cmake --build "${BUILD_DIR}" -j"${JOBS}" | ||
| 103 | +cmake --install "${BUILD_DIR}" | ||
| 104 | +export ASCEND_CUSTOM_OPP_PATH="${OUTPUT_DIR}:${ASCEND_CUSTOM_OPP_PATH:-}" | ||
| 105 | +info "ASCEND_CUSTOM_OPP_PATH=${ASCEND_CUSTOM_OPP_PATH}" | ||
| 106 | + | ||
| 107 | +if [[ ! -f "${CUSTOM_OP_LIBRARY_PATH}" ]]; then | ||
| 108 | + error "Custom op library was not generated: ${CUSTOM_OP_LIBRARY_PATH}" | ||
| 109 | + exit 1 | ||
| 110 | +fi | ||
| 111 | +if [[ ! -f "${CUSTOM_OP_PROTO_HEADER_PATH}" ]]; then | ||
| 112 | + error "Custom op proto header was not generated: ${CUSTOM_OP_PROTO_HEADER_PATH}" | ||
| 113 | + exit 1 | ||
| 114 | +fi | ||
| 115 | +if [[ ! -f "${ES_CUSTOM_LIBRARY_PATH}" ]]; then | ||
| 116 | + error "ES custom api library was not generated: ${ES_CUSTOM_LIBRARY_PATH}" | ||
| 117 | + exit 1 | ||
| 118 | +fi | ||
| 119 | + | ||
| 120 | +info "Step 2/2: run Session::RunGraph sample" | ||
| 121 | +( | ||
| 122 | + cd "${BUILD_DIR}" | ||
| 123 | + ./host_cpu_add_custom_constant_folding_session_run | ||
| 124 | +) | ||
| 125 | + | ||
| 126 | +info "Sample pipeline finished." | ||
| @@ -0,0 +1,133 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +using namespace ge; | ||
| 25 | +using namespace ge::es; | ||
| 26 | + | ||
| 27 | +namespace { | ||
| 28 | +constexpr uint32_t kGraphId = 0U; | ||
| 29 | +constexpr float kLeftValue = 1.0f; | ||
| 30 | +constexpr float kRightValue = 2.0f; | ||
| 31 | +constexpr float kExpectedValue = 3.0f; | ||
| 32 | + | ||
| 33 | +std::unique_ptr<ge::Graph> BuildGraph() { | ||
| 34 | + auto graph_builder = std::make_unique<EsGraphBuilder>("HostCpuAddCustomConstantFoldingGraph"); | ||
| 35 | + auto left = graph_builder->CreateConst(std::vector<float>{kLeftValue}, std::vector<int64_t>{1}); | ||
| 36 | + auto right = graph_builder->CreateConst(std::vector<float>{kRightValue}, std::vector<int64_t>{1}); | ||
| 37 | + auto add = es::AddCustom(left, right); | ||
| 38 | + (void)graph_builder->SetOutput(add, 0); | ||
| 39 | + return graph_builder->BuildAndReset(); | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +void PrintOutputTensor(const ge::Tensor &output_tensor) { | ||
| 43 | + const auto tensor_desc = output_tensor.GetTensorDesc(); | ||
| 44 | + const auto shape = tensor_desc.GetShape(); | ||
| 45 | + const auto dims = shape.GetDims(); | ||
| 46 | + std::cout << "output shape: ["; | ||
| 47 | + for (size_t i = 0U; i < dims.size(); ++i) { | ||
| 48 | + if (i != 0U) { | ||
| 49 | + std::cout << ", "; | ||
| 50 | + } | ||
| 51 | + std::cout << dims[i]; | ||
| 52 | + } | ||
| 53 | + std::cout << "]" << std::endl; | ||
| 54 | + | ||
| 55 | + const size_t element_count = static_cast<size_t>(output_tensor.GetSize() / sizeof(float)); | ||
| 56 | + const auto *output_data = reinterpret_cast<const float *>(output_tensor.GetData()); | ||
| 57 | + std::cout << "output values:"; | ||
| 58 | + for (size_t i = 0U; i < element_count; ++i) { | ||
| 59 | + std::cout << " " << output_data[i]; | ||
| 60 | + } | ||
| 61 | + std::cout << std::endl; | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +bool VerifyOutput(const ge::Tensor &output_tensor) { | ||
| 65 | + const auto *output_data = reinterpret_cast<const float *>(output_tensor.GetData()); | ||
| 66 | + if (output_data == nullptr) { | ||
| 67 | + return false; | ||
| 68 | + } | ||
| 69 | + const size_t element_count = static_cast<size_t>(output_tensor.GetSize() / sizeof(float)); | ||
| 70 | + if (element_count != 1U) { | ||
| 71 | + return false; | ||
| 72 | + } | ||
| 73 | + return std::fabs(output_data[0] - kExpectedValue) < 1e-6f; | ||
| 74 | +} | ||
| 75 | +} // namespace | ||
| 76 | + | ||
| 77 | +int main(int argc, char *argv[]) { | ||
| 78 | + (void)argc; | ||
| 79 | + (void)argv; | ||
| 80 | + | ||
| 81 | + std::map<ge::AscendString, ge::AscendString> options = { | ||
| 82 | + {"ge.exec.deviceId", "0"}, | ||
| 83 | + }; | ||
| 84 | + | ||
| 85 | + const auto init_ret = ge::GEInitialize(options); | ||
| 86 | + if (init_ret != ge::SUCCESS) { | ||
| 87 | + std::cerr << "GEInitialize failed, ret: " << init_ret << std::endl; | ||
| 88 | + return 1; | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + int ret_code = 0; | ||
| 92 | + { | ||
| 93 | + ge::Session session(options); | ||
| 94 | + auto graph = BuildGraph(); | ||
| 95 | + if (graph == nullptr) { | ||
| 96 | + std::cerr << "BuildGraph failed" << std::endl; | ||
| 97 | + (void)ge::GEFinalize(); | ||
| 98 | + return 1; | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + const auto add_graph_ret = session.AddGraph(kGraphId, *graph); | ||
| 102 | + if (add_graph_ret != ge::SUCCESS) { | ||
| 103 | + std::cerr << "AddGraph failed, ret: " << add_graph_ret << std::endl; | ||
| 104 | + ret_code = 1; | ||
| 105 | + } else { | ||
| 106 | + std::vector<ge::Tensor> inputs; | ||
| 107 | + std::vector<ge::Tensor> outputs; | ||
| 108 | + const auto run_ret = session.RunGraph(kGraphId, inputs, outputs); | ||
| 109 | + if (run_ret != ge::SUCCESS) { | ||
| 110 | + std::cerr << "RunGraph failed, ret: " << run_ret << std::endl; | ||
| 111 | + ret_code = 1; | ||
| 112 | + } else if (outputs.empty()) { | ||
| 113 | + std::cerr << "RunGraph success but outputs is empty" << std::endl; | ||
| 114 | + ret_code = 1; | ||
| 115 | + } else { | ||
| 116 | + PrintOutputTensor(outputs[0]); | ||
| 117 | + if (!VerifyOutput(outputs[0])) { | ||
| 118 | + std::cerr << "Output verification failed" << std::endl; | ||
| 119 | + ret_code = 1; | ||
| 120 | + } | ||
| 121 | + } | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + (void)session.RemoveGraph(kGraphId); | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + const auto finalize_ret = ge::GEFinalize(); | ||
| 128 | + if (finalize_ret != ge::SUCCESS) { | ||
| 129 | + std::cerr << "GEFinalize failed, ret: " << finalize_ret << std::endl; | ||
| 130 | + return 1; | ||
| 131 | + } | ||
| 132 | + return ret_code; | ||
| 133 | +} | ||
| @@ -0,0 +1,120 @@ | |||
| 1 | +cmake_minimum_required(VERSION 3.16) | ||
| 2 | +project(host_cpu_add_custom_host_scheduling LANGUAGES CXX) | ||
| 3 | + | ||
| 4 | +option(HOST_CPU_ADD_BUILD_CUSTOM_OP "Build libcust_opapi.so" ON) | ||
| 5 | +option(HOST_CPU_ADD_BUILD_SESSION_RUN "Build session_run" ON) | ||
| 6 | + | ||
| 7 | +set(CMAKE_CXX_STANDARD 17) | ||
| 8 | +set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
| 9 | +set(CMAKE_CXX_EXTENSIONS OFF) | ||
| 10 | + | ||
| 11 | +if(NOT CMAKE_BUILD_TYPE) | ||
| 12 | + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) | ||
| 13 | +endif() | ||
| 14 | + | ||
| 15 | +set(COMMON_COMPILE_OPTIONS | ||
| 16 | + -Wall | ||
| 17 | + -Wextra | ||
| 18 | + -Wno-unused-parameter | ||
| 19 | +) | ||
| 20 | + | ||
| 21 | +set(PROJECT_OUTPUT_DIR "${CMAKE_SOURCE_DIR}/output") | ||
| 22 | +file(MAKE_DIRECTORY "${PROJECT_OUTPUT_DIR}") | ||
| 23 | +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") | ||
| 24 | + set(OPP_OS_TYPE "windows") | ||
| 25 | +else() | ||
| 26 | + set(OPP_OS_TYPE "linux") | ||
| 27 | +endif() | ||
| 28 | + | ||
| 29 | +string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" CMAKE_SYSTEM_PROCESSOR_LOWER) | ||
| 30 | +if(CMAKE_SYSTEM_PROCESSOR_LOWER MATCHES "^(aarch64|arm64)$") | ||
| 31 | + set(OPP_CPU_TYPE "aarch64") | ||
| 32 | +elseif(CMAKE_SYSTEM_PROCESSOR_LOWER MATCHES "^(x86_64|amd64)$") | ||
| 33 | + set(OPP_CPU_TYPE "x86_64") | ||
| 34 | +else() | ||
| 35 | + set(OPP_CPU_TYPE "${CMAKE_SYSTEM_PROCESSOR_LOWER}") | ||
| 36 | +endif() | ||
| 37 | + | ||
| 38 | +set(CUSTOM_OP_OUTPUT_DIR "${PROJECT_OUTPUT_DIR}/op_graph/lib/${OPP_OS_TYPE}/${OPP_CPU_TYPE}") | ||
| 39 | +file(MAKE_DIRECTORY "${CUSTOM_OP_OUTPUT_DIR}") | ||
| 40 | + | ||
| 41 | +set(ASCEND_HOME_PATH_OVERRIDE "" CACHE PATH "Optional ASCEND_HOME_PATH override") | ||
| 42 | +if(ASCEND_HOME_PATH_OVERRIDE) | ||
| 43 | + set(ASCEND_HOME_PATH "${ASCEND_HOME_PATH_OVERRIDE}") | ||
| 44 | +else() | ||
| 45 | + set(ASCEND_HOME_PATH "$ENV{ASCEND_HOME_PATH}") | ||
| 46 | +endif() | ||
| 47 | + | ||
| 48 | +if(HOST_CPU_ADD_BUILD_CUSTOM_OP) | ||
| 49 | + add_library(cust_opapi SHARED | ||
| 50 | + ge/custom_op.cpp | ||
| 51 | + ) | ||
| 52 | + target_compile_options(cust_opapi PRIVATE ${COMMON_COMPILE_OPTIONS}) | ||
| 53 | + target_compile_definitions(cust_opapi PRIVATE | ||
| 54 | + _GLIBCXX_USE_CXX11_ABI=0 | ||
| 55 | + ) | ||
| 56 | + | ||
| 57 | + if(ASCEND_HOME_PATH) | ||
| 58 | + message(STATUS "ASCEND_HOME_PATH: ${ASCEND_HOME_PATH}") | ||
| 59 | + target_include_directories(cust_opapi PRIVATE | ||
| 60 | + "${ASCEND_HOME_PATH}/include" | ||
| 61 | + "${ASCEND_HOME_PATH}/include/graph" | ||
| 62 | + "${ASCEND_HOME_PATH}/include/register" | ||
| 63 | + "${ASCEND_HOME_PATH}/include/external" | ||
| 64 | + ) | ||
| 65 | + target_link_directories(cust_opapi PRIVATE "${ASCEND_HOME_PATH}/lib64") | ||
| 66 | + target_link_libraries(cust_opapi PRIVATE | ||
| 67 | + -Wl,--no-as-needed | ||
| 68 | + ascendcl | ||
| 69 | + register | ||
| 70 | + gert | ||
| 71 | + pthread | ||
| 72 | + dl | ||
| 73 | + -Wl,--as-needed | ||
| 74 | + ) | ||
| 75 | + endif() | ||
| 76 | + | ||
| 77 | + set_target_properties(cust_opapi PROPERTIES | ||
| 78 | + OUTPUT_NAME "cust_opapi" | ||
| 79 | + LIBRARY_OUTPUT_DIRECTORY "${CUSTOM_OP_OUTPUT_DIR}" | ||
| 80 | + RUNTIME_OUTPUT_DIRECTORY "${CUSTOM_OP_OUTPUT_DIR}" | ||
| 81 | + ARCHIVE_OUTPUT_DIRECTORY "${CUSTOM_OP_OUTPUT_DIR}" | ||
| 82 | + ) | ||
| 83 | +endif() | ||
| 84 | + | ||
| 85 | +if(HOST_CPU_ADD_BUILD_SESSION_RUN) | ||
| 86 | + add_executable(host_cpu_add_custom_host_scheduling_session_run | ||
| 87 | + session_run/main.cc | ||
| 88 | + ) | ||
| 89 | + target_compile_options(host_cpu_add_custom_host_scheduling_session_run PRIVATE ${COMMON_COMPILE_OPTIONS}) | ||
| 90 | + target_compile_definitions(host_cpu_add_custom_host_scheduling_session_run PRIVATE | ||
| 91 | + _GLIBCXX_USE_CXX11_ABI=0 | ||
| 92 | + ) | ||
| 93 | + | ||
| 94 | + if(ASCEND_HOME_PATH) | ||
| 95 | + target_include_directories(host_cpu_add_custom_host_scheduling_session_run PRIVATE | ||
| 96 | + "${ASCEND_HOME_PATH}/include" | ||
| 97 | + "${ASCEND_HOME_PATH}/include/graph" | ||
| 98 | + "${ASCEND_HOME_PATH}/include/ge" | ||
| 99 | + "${ASCEND_HOME_PATH}/include/es/es_math" | ||
| 100 | + "${ASCEND_HOME_PATH}/opp/built-in/op_proto/inc" | ||
| 101 | + ) | ||
| 102 | + target_link_directories(host_cpu_add_custom_host_scheduling_session_run PRIVATE "${ASCEND_HOME_PATH}/lib64") | ||
| 103 | + target_link_libraries(host_cpu_add_custom_host_scheduling_session_run PRIVATE | ||
| 104 | + -Wl,--no-as-needed | ||
| 105 | + es_math | ||
| 106 | + eager_style_graph_builder_base | ||
| 107 | + -Wl,--as-needed | ||
| 108 | + graph | ||
| 109 | + ge_runner | ||
| 110 | + ge_compiler | ||
| 111 | + ascendcl | ||
| 112 | + graph_base | ||
| 113 | + c_sec | ||
| 114 | + ) | ||
| 115 | + endif() | ||
| 116 | + | ||
| 117 | + set_target_properties(host_cpu_add_custom_host_scheduling_session_run PROPERTIES | ||
| 118 | + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" | ||
| 119 | + ) | ||
| 120 | +endif() | ||
| @@ -0,0 +1,73 @@ | |||
| 1 | +# HostCpu Host 调度 Add 自定义算子样例 | ||
| 2 | + | ||
| 3 | +## 样例概述 | ||
| 4 | + | ||
| 5 | +本样例为内置 `Add` 算子注册 `HostCpuExecuteOp` 和 `ShapeInferOp`,验证运行时 `HostcpuEngineUpdatePass` 将内置算子调度到 HostCpu 执行,分别覆盖 HostCpu 命中和 AICore 执行两种场景。 | ||
| 6 | + | ||
| 7 | +## 前置依赖 | ||
| 8 | + | ||
| 9 | +- 参考[安装指导](../../../../docs/zh/quick_install.md)完成 `toolkit` 和 `ops` 包安装。 | ||
| 10 | +- 设置环境变量(假设包安装在 `/usr/local/Ascend/`): | ||
| 11 | + ```bash | ||
| 12 | + source /usr/local/Ascend/cann/set_env.sh | ||
| 13 | + ``` | ||
| 14 | + | ||
| 15 | +## 快速运行 | ||
| 16 | + | ||
| 17 | +在 `examples/custom_op/host_cpu_add_custom/host_scheduling` 目录下执行: | ||
| 18 | + | ||
| 19 | +```bash | ||
| 20 | +bash run.sh | ||
| 21 | +``` | ||
| 22 | + | ||
| 23 | +默认运行两个场景。也可通过 `--scenario` 参数指定单个场景: | ||
| 24 | + | ||
| 25 | +```bash | ||
| 26 | +bash run.sh --scenario=host # 仅运行场景1 | ||
| 27 | +bash run.sh --scenario=aicore # 仅运行场景2 | ||
| 28 | +bash run.sh --scenario=all # 运行两个场景(默认) | ||
| 29 | +``` | ||
| 30 | + | ||
| 31 | +脚本会完成 configure、build、install。运行成功时,终端应打印: | ||
| 32 | + | ||
| 33 | +```text | ||
| 34 | +=== Scenario1: HostCpu Custom (Sub + Add + dynamic Sub) === | ||
| 35 | +[ShapeInferOp] InferDataType for Add | ||
| 36 | +[ShapeInferOp] InferShape for Add | ||
| 37 | +[HostCpuExecuteOp] Execute for Add | ||
| 38 | +output shape: [4] | ||
| 39 | +output values (first 4): 6 8 10 12 | ||
| 40 | + | ||
| 41 | +=== Scenario2: AiCore (Data input + large shape + static graph) === | ||
| 42 | +[ShapeInferOp] InferDataType for Add | ||
| 43 | +[ShapeInferOp] InferShape for Add | ||
| 44 | +output shape: [1024] | ||
| 45 | +output values (first 10): 6 8 10 12 14 16 18 20 22 24 | ||
| 46 | +``` | ||
| 47 | + | ||
| 48 | +## 关键文件 | ||
| 49 | + | ||
| 50 | +```text | ||
| 51 | +host_scheduling | ||
| 52 | +├── CMakeLists.txt | ||
| 53 | +├── run.sh | ||
| 54 | +├── ge | ||
| 55 | +│ └── custom_op.cpp // 为内置 Add 注册 HostCpuExecuteOp / ShapeInferOp | ||
| 56 | +└── session_run | ||
| 57 | + └── main.cc // 两个场景的 ES 构图与 Session::RunGraph | ||
| 58 | +``` | ||
| 59 | + | ||
| 60 | +## 实现步骤 | ||
| 61 | + | ||
| 62 | +`ge/custom_op.cpp` 中 `AddHostCpu` 的实现是本样例的核心: | ||
| 63 | + | ||
| 64 | +- `HostCpuExecuteOp::Execute` 在 host 侧完成 float 向量加法。 | ||
| 65 | +- `ShapeInferOp` 将输出 shape 和 dtype 设为与输入一致。 | ||
| 66 | +- 通过 `REG_OP_BACKEND(AddHostCpu, "Add", OpBackend::kHostCPU)` 绑定到内置 Add,仅注册 kHostCPU 后端。 | ||
| 67 | +- 场景1 中,`HostcpuEngineUpdatePass` 检测到 Add 的输入输出 shape 小(4 <= 8),将其标记为 HostCpu 执行。 | ||
| 68 | +- 场景2 中,静态图 + 大 shape,`HostcpuEngineUpdatePass` 不触发,Add 正常走 AICore。 | ||
| 69 | + | ||
| 70 | +## 注意事项 | ||
| 71 | + | ||
| 72 | +- 本样例只覆盖运行时 host 调度链路,常量折叠样例见 `../constant_folding`,离线 OM 样例见 `../offline`。 | ||
| 73 | +- `run.sh` 会将 `output/` 追加到 `ASCEND_CUSTOM_OPP_PATH`。 | ||
| @@ -0,0 +1,73 @@ | |||
| 1 | +# HostCpu Host Scheduling Add Custom Op Sample | ||
| 2 | + | ||
| 3 | +## Overview | ||
| 4 | + | ||
| 5 | +This sample registers `HostCpuExecuteOp` and `ShapeInferOp` for the built-in `Add` operator, validating that `HostcpuEngineUpdatePass` schedules the built-in op to HostCpu at runtime. It covers HostCpu-hit and AICore-execution scenarios. | ||
| 6 | + | ||
| 7 | +## Prerequisites | ||
| 8 | + | ||
| 9 | +- Refer to the [Installation Guide](../../../../docs/en/quick_install.md) to install the `toolkit` and `ops` packages. | ||
| 10 | +- Set the environment variables (assuming that the packages are installed in `/usr/local/Ascend/`): | ||
| 11 | + ```bash | ||
| 12 | + source /usr/local/Ascend/cann/set_env.sh | ||
| 13 | + ``` | ||
| 14 | + | ||
| 15 | +## Quick Run | ||
| 16 | + | ||
| 17 | +Run in `examples/custom_op/host_cpu_add_custom/host_scheduling`: | ||
| 18 | + | ||
| 19 | +```bash | ||
| 20 | +bash run.sh | ||
| 21 | +``` | ||
| 22 | + | ||
| 23 | +By default, both scenarios are run. You can also specify a single scenario: | ||
| 24 | + | ||
| 25 | +```bash | ||
| 26 | +bash run.sh --scenario=host # Run scenario 1 only | ||
| 27 | +bash run.sh --scenario=aicore # Run scenario 2 only | ||
| 28 | +bash run.sh --scenario=all # Run both scenarios (default) | ||
| 29 | +``` | ||
| 30 | + | ||
| 31 | +The script configures, builds, and installs. Expected output includes: | ||
| 32 | + | ||
| 33 | +```text | ||
| 34 | +=== Scenario1: HostCpu Custom (Sub + Add + dynamic Sub) === | ||
| 35 | +[ShapeInferOp] InferDataType for Add | ||
| 36 | +[ShapeInferOp] InferShape for Add | ||
| 37 | +[HostCpuExecuteOp] Execute for Add | ||
| 38 | +output shape: [4] | ||
| 39 | +output values (first 4): 6 8 10 12 | ||
| 40 | + | ||
| 41 | +=== Scenario2: AiCore (Data input + large shape + static graph) === | ||
| 42 | +[ShapeInferOp] InferDataType for Add | ||
| 43 | +[ShapeInferOp] InferShape for Add | ||
| 44 | +output shape: [1024] | ||
| 45 | +output values (first 10): 6 8 10 12 14 16 18 20 22 24 | ||
| 46 | +``` | ||
| 47 | + | ||
| 48 | +## Key Files | ||
| 49 | + | ||
| 50 | +```text | ||
| 51 | +host_scheduling | ||
| 52 | +├── CMakeLists.txt | ||
| 53 | +├── run.sh | ||
| 54 | +├── ge | ||
| 55 | +│ └── custom_op.cpp // Registers HostCpuExecuteOp / ShapeInferOp for built-in Add | ||
| 56 | +└── session_run | ||
| 57 | + └── main.cc // Two scenarios with ES graph construction and Session::RunGraph | ||
| 58 | +``` | ||
| 59 | + | ||
| 60 | +## Implementation Steps | ||
| 61 | + | ||
| 62 | +`AddHostCpu` in `ge/custom_op.cpp` is the core implementation: | ||
| 63 | + | ||
| 64 | +- `HostCpuExecuteOp::Execute` performs float vector addition on the host side. | ||
| 65 | +- `ShapeInferOp` copies input shape and dtype to the output. | ||
| 66 | +- Binds to the built-in Add via `REG_OP_BACKEND(AddHostCpu, "Add", OpBackend::kHostCPU)`, registering only kHostCPU backend. | ||
| 67 | +- In Scenario 1, `HostcpuEngineUpdatePass` detects that Add's input/output shapes are small (4 <= 8) and marks it for HostCpu execution. | ||
| 68 | +- In Scenario 2, static graph with large shape means `HostcpuEngineUpdatePass` does not trigger, and Add runs on AICore normally. | ||
| 69 | + | ||
| 70 | +## Notes | ||
| 71 | + | ||
| 72 | +- This sample only covers the runtime host scheduling path; the constant-folding sample lives in `../constant_folding`, and the offline OM sample in `../offline`. | ||
| 73 | +- `run.sh` appends `output/` to `ASCEND_CUSTOM_OPP_PATH`. | ||
| @@ -0,0 +1,93 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +namespace { | ||
| 17 | +constexpr size_t kInputIndexX = 0U; | ||
| 18 | +constexpr size_t kInputIndexY = 1U; | ||
| 19 | +constexpr size_t kOutputIndexZ = 0U; | ||
| 20 | + | ||
| 21 | +template <typename T> | ||
| 22 | +void AddSameType(const T *x, const T *y, T *z, const int64_t size) { | ||
| 23 | + for (int64_t i = 0; i < size; ++i) { | ||
| 24 | + z[i] = x[i] + y[i]; | ||
| 25 | + } | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +void AddFloat16(const uint16_t *x, const uint16_t *y, uint16_t *z, const int64_t size) { | ||
| 29 | + for (int64_t i = 0; i < size; ++i) { | ||
| 30 | + z[i] = aclFloatToFloat16(aclFloat16ToFloat(x[i]) + aclFloat16ToFloat(y[i])); | ||
| 31 | + } | ||
| 32 | +} | ||
| 33 | +} // namespace | ||
| 34 | + | ||
| 35 | +namespace ge { | ||
| 36 | +class AddHostCpu final : public HostCpuExecuteOp, public ShapeInferOp { | ||
| 37 | + public: | ||
| 38 | + graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) override { | ||
| 39 | + std::cout << "[HostCpuExecuteOp] Execute for Add" << std::endl; | ||
| 40 | + | ||
| 41 | + const gert::Tensor *input_x = ctx->GetInputTensor(kInputIndexX); | ||
| 42 | + const gert::Tensor *input_y = ctx->GetInputTensor(kInputIndexY); | ||
| 43 | + if ((input_x == nullptr) || (input_y == nullptr)) { | ||
| 44 | + std::cerr << "GetInputTensor failed, input_x=" << input_x << ", input_y=" << input_y << std::endl; | ||
| 45 | + return GRAPH_FAILED; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + gert::Tensor *output_z = | ||
| 49 | + ctx->MallocOutputTensor(kOutputIndexZ, input_x->GetShape(), input_x->GetFormat(), input_x->GetDataType()); | ||
| 50 | + if (output_z == nullptr) { | ||
| 51 | + std::cerr << "MallocOutputTensor failed" << std::endl; | ||
| 52 | + return GRAPH_FAILED; | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + const int64_t shape_size = input_x->GetStorageShape().GetShapeSize(); | ||
| 56 | + switch (input_x->GetDataType()) { | ||
| 57 | + case DT_FLOAT: { | ||
| 58 | + AddSameType(input_x->GetData<float>(), input_y->GetData<float>(), output_z->GetData<float>(), shape_size); | ||
| 59 | + break; | ||
| 60 | + } | ||
| 61 | + case DT_FLOAT16: { | ||
| 62 | + AddFloat16(input_x->GetData<uint16_t>(), input_y->GetData<uint16_t>(), output_z->GetData<uint16_t>(), | ||
| 63 | + shape_size); | ||
| 64 | + break; | ||
| 65 | + } | ||
| 66 | + default: { | ||
| 67 | + std::cerr << "Unsupported Add data type: " << input_x->GetDataType() << std::endl; | ||
| 68 | + return GRAPH_FAILED; | ||
| 69 | + } | ||
| 70 | + } | ||
| 71 | + return GRAPH_SUCCESS; | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + graphStatus InferShape(gert::InferShapeContext *ctx) override { | ||
| 75 | + std::cout << "[ShapeInferOp] InferShape for Add" << std::endl; | ||
| 76 | + const gert::Shape *input_shape = ctx->GetInputShape(kInputIndexX); | ||
| 77 | + gert::Shape *output_shape = ctx->GetOutputShape(kOutputIndexZ); | ||
| 78 | + if ((input_shape == nullptr) || (output_shape == nullptr)) { | ||
| 79 | + std::cerr << "InferShape failed, input_shape=" << input_shape << ", output_shape=" << output_shape << std::endl; | ||
| 80 | + return GRAPH_FAILED; | ||
| 81 | + } | ||
| 82 | + *output_shape = *input_shape; | ||
| 83 | + return GRAPH_SUCCESS; | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + graphStatus InferDataType(gert::InferDataTypeContext *ctx) override { | ||
| 87 | + std::cout << "[ShapeInferOp] InferDataType for Add" << std::endl; | ||
| 88 | + return ctx->SetOutputDataType(kOutputIndexZ, ctx->GetInputDataType(kInputIndexX)); | ||
| 89 | + } | ||
| 90 | +}; | ||
| 91 | + | ||
| 92 | +REG_OP_BACKEND(AddHostCpu, "Add", ge::OpBackend::kHostCPU); | ||
| 93 | +} // namespace ge | ||
| @@ -0,0 +1,130 @@ | |||
| 1 | +#!/usr/bin/env bash | ||
| 2 | +# ----------------------------------------------------------------------------------------------------------- | ||
| 3 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 4 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 5 | +# CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 6 | +# Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 7 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 8 | +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 9 | +# See LICENSE in the root of the software repository for the full text of the License. | ||
| 10 | +# ----------------------------------------------------------------------------------------------------------- | ||
| 11 | + | ||
| 12 | +set -euo pipefail | ||
| 13 | + | ||
| 14 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| 15 | +PROJECT_DIR="${SCRIPT_DIR}" | ||
| 16 | +BUILD_DIR="${PROJECT_DIR}/build" | ||
| 17 | +OUTPUT_DIR="${PROJECT_DIR}/output" | ||
| 18 | + | ||
| 19 | +info() { | ||
| 20 | + echo "[INFO] $*" | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +error() { | ||
| 24 | + echo "[ERROR] $*" >&2 | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +detect_opp_os_dir() { | ||
| 28 | + local os_name | ||
| 29 | + os_name="$(uname -s | tr '[:upper:]' '[:lower:]')" | ||
| 30 | + case "${os_name}" in | ||
| 31 | + mingw*|msys*|cygwin*) echo "windows" ;; | ||
| 32 | + *) echo "linux" ;; | ||
| 33 | + esac | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +detect_opp_arch_dir() { | ||
| 37 | + local arch_name | ||
| 38 | + arch_name="$(uname -m | tr '[:upper:]' '[:lower:]')" | ||
| 39 | + case "${arch_name}" in | ||
| 40 | + aarch64|arm64) echo "aarch64" ;; | ||
| 41 | + x86_64|amd64) echo "x86_64" ;; | ||
| 42 | + *) echo "${arch_name}" ;; | ||
| 43 | + esac | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +get_custom_op_library_name() { | ||
| 47 | + if [[ "$(detect_opp_os_dir)" == "windows" ]]; then | ||
| 48 | + echo "cust_opapi.dll" | ||
| 49 | + return | ||
| 50 | + fi | ||
| 51 | + echo "libcust_opapi.so" | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +detect_jobs() { | ||
| 55 | + if command -v nproc >/dev/null 2>&1; then | ||
| 56 | + nproc | ||
| 57 | + return | ||
| 58 | + fi | ||
| 59 | + echo 8 | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +SCENARIO="all" | ||
| 63 | + | ||
| 64 | +usage() { | ||
| 65 | + cat <<'EOF' | ||
| 66 | +Usage: | ||
| 67 | + bash run.sh [OPTIONS] | ||
| 68 | + | ||
| 69 | +Options: | ||
| 70 | + --scenario=SCENARIO 运行场景: all (默认), host 或 aicore | ||
| 71 | + all: 运行两个场景 | ||
| 72 | + host: 场景1 - HostCpu 自定义算子 (Const + 小 shape + 动态 Reshape) | ||
| 73 | + aicore: 场景2 - AICore 内置算子 (Data 输入 + 大 shape + 静态图) | ||
| 74 | + -h, --help 显示帮助信息 | ||
| 75 | +EOF | ||
| 76 | +} | ||
| 77 | + | ||
| 78 | +while [[ $# -gt 0 ]]; do | ||
| 79 | + case "$1" in | ||
| 80 | + --scenario=*) | ||
| 81 | + SCENARIO="${1#*=}" | ||
| 82 | + if [[ "${SCENARIO}" != "all" && "${SCENARIO}" != "host" && "${SCENARIO}" != "aicore" ]]; then | ||
| 83 | + error "Invalid scenario: ${SCENARIO}. Must be 'all', 'host' or 'aicore'." | ||
| 84 | + usage | ||
| 85 | + exit 1 | ||
| 86 | + fi | ||
| 87 | + ;; | ||
| 88 | + -h|--help) | ||
| 89 | + usage | ||
| 90 | + exit 0 | ||
| 91 | + ;; | ||
| 92 | + *) | ||
| 93 | + error "Unknown option: $1" | ||
| 94 | + usage | ||
| 95 | + exit 1 | ||
| 96 | + ;; | ||
| 97 | + esac | ||
| 98 | + shift | ||
| 99 | +done | ||
| 100 | + | ||
| 101 | +if [[ -z "${ASCEND_HOME_PATH:-}" ]]; then | ||
| 102 | + error "ASCEND_HOME_PATH is empty. Please source CANN set_env.sh first." | ||
| 103 | + exit 1 | ||
| 104 | +fi | ||
| 105 | + | ||
| 106 | +CUSTOM_OP_DIR="${OUTPUT_DIR}/op_graph/lib/$(detect_opp_os_dir)/$(detect_opp_arch_dir)" | ||
| 107 | +CUSTOM_OP_LIBRARY_PATH="${CUSTOM_OP_DIR}/$(get_custom_op_library_name)" | ||
| 108 | + | ||
| 109 | +mkdir -p "${BUILD_DIR}" "${OUTPUT_DIR}" "${CUSTOM_OP_DIR}" | ||
| 110 | +JOBS="$(detect_jobs)" | ||
| 111 | + | ||
| 112 | +info "Step 1/2: configure and build sample targets" | ||
| 113 | +cmake -S "${PROJECT_DIR}" -B "${BUILD_DIR}" -DCMAKE_BUILD_TYPE=Release | ||
| 114 | +cmake --build "${BUILD_DIR}" -j"${JOBS}" | ||
| 115 | +cmake --install "${BUILD_DIR}" | ||
| 116 | +export ASCEND_CUSTOM_OPP_PATH="${OUTPUT_DIR}:${ASCEND_CUSTOM_OPP_PATH:-}" | ||
| 117 | +info "ASCEND_CUSTOM_OPP_PATH=${ASCEND_CUSTOM_OPP_PATH}" | ||
| 118 | + | ||
| 119 | +if [[ ! -f "${CUSTOM_OP_LIBRARY_PATH}" ]]; then | ||
| 120 | + error "Custom op library was not generated: ${CUSTOM_OP_LIBRARY_PATH}" | ||
| 121 | + exit 1 | ||
| 122 | +fi | ||
| 123 | + | ||
| 124 | +info "Step 2/2: run Session::RunGraph sample (scenario: ${SCENARIO})" | ||
| 125 | +( | ||
| 126 | + cd "${BUILD_DIR}" | ||
| 127 | + ./host_cpu_add_custom_host_scheduling_session_run --scenario="${SCENARIO}" | ||
| 128 | +) | ||
| 129 | + | ||
| 130 | +info "Sample pipeline finished." | ||
| @@ -0,0 +1,309 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +using namespace ge; | ||
| 25 | +using namespace ge::es; | ||
| 26 | + | ||
| 27 | +namespace { | ||
| 28 | +constexpr uint32_t kHostCpuGraphId = 0U; | ||
| 29 | +constexpr uint32_t kAiCoreGraphId = 1U; | ||
| 30 | +constexpr size_t kSmallElementCount = 4U; | ||
| 31 | +constexpr size_t kLargeElementCount = 1024U; | ||
| 32 | +constexpr float kExpectedSmallValues[kSmallElementCount] = {6.0f, 8.0f, 10.0f, 12.0f}; | ||
| 33 | +constexpr const char *const kAttrHostTensor = "_host_tensor"; | ||
| 34 | +constexpr const char *const kAttrGraphUnknownFlag = "_graph_unknown_flag"; | ||
| 35 | + | ||
| 36 | +bool MakeInputTensor(const std::vector<float> &values, ge::Tensor &tensor) { | ||
| 37 | + const ge::Shape shape({static_cast<int64_t>(values.size())}); | ||
| 38 | + tensor = ge::Tensor(ge::TensorDesc(shape, ge::FORMAT_ND, ge::DT_FLOAT)); | ||
| 39 | + if (tensor.SetData(reinterpret_cast<const uint8_t *>(values.data()), values.size() * sizeof(float)) != | ||
| 40 | + ge::GRAPH_SUCCESS) { | ||
| 41 | + std::cerr << "SetData failed" << std::endl; | ||
| 42 | + return false; | ||
| 43 | + } | ||
| 44 | + return true; | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +std::unique_ptr<ge::Graph> BuildSmallDataGraph(const char *name, size_t element_count) { | ||
| 48 | + auto graph_builder = std::make_unique<EsGraphBuilder>(name); | ||
| 49 | + auto x = graph_builder->CreateInput(0, "data_x", ge::DT_FLOAT, ge::FORMAT_ND, {static_cast<int64_t>(element_count)}); | ||
| 50 | + auto y = graph_builder->CreateInput(1, "data_y", ge::DT_FLOAT, ge::FORMAT_ND, {static_cast<int64_t>(element_count)}); | ||
| 51 | + (void)x.SetAttrForNode(kAttrHostTensor, true); | ||
| 52 | + (void)y.SetAttrForNode(kAttrHostTensor, true); | ||
| 53 | + const auto sub_before_add = es::Sub(x, y); | ||
| 54 | + const auto add = es::Add(sub_before_add, y); | ||
| 55 | + auto dynamic_sub_input = graph_builder->CreateInput(2, "dynamic_sub_input", ge::DT_FLOAT, ge::FORMAT_ND, {-1}); | ||
| 56 | + const auto sub_after_add = es::Sub(add, dynamic_sub_input); | ||
| 57 | + (void)graph_builder->SetOutput(sub_after_add, 0); | ||
| 58 | + (void)graph_builder->SetAttr(kAttrGraphUnknownFlag, true); | ||
| 59 | + return graph_builder->BuildAndReset(); | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +std::unique_ptr<ge::Graph> BuildLargeDataGraph(const char *name, size_t element_count) { | ||
| 63 | + auto graph_builder = std::make_unique<EsGraphBuilder>(name); | ||
| 64 | + auto x = graph_builder->CreateInput(0, "data_x", ge::DT_FLOAT, ge::FORMAT_ND, {static_cast<int64_t>(element_count)}); | ||
| 65 | + auto y = graph_builder->CreateInput(1, "data_y", ge::DT_FLOAT, ge::FORMAT_ND, {static_cast<int64_t>(element_count)}); | ||
| 66 | + auto add = es::Add(x, y); | ||
| 67 | + (void)graph_builder->SetOutput(add, 0); | ||
| 68 | + return graph_builder->BuildAndReset(); | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +void PrintOutputTensor(const ge::Tensor &output_tensor) { | ||
| 72 | + const auto tensor_desc = output_tensor.GetTensorDesc(); | ||
| 73 | + const auto shape = tensor_desc.GetShape(); | ||
| 74 | + const auto dims = shape.GetDims(); | ||
| 75 | + std::cout << "output shape: ["; | ||
| 76 | + for (size_t i = 0U; i < dims.size(); ++i) { | ||
| 77 | + if (i != 0U) { | ||
| 78 | + std::cout << ", "; | ||
| 79 | + } | ||
| 80 | + std::cout << dims[i]; | ||
| 81 | + } | ||
| 82 | + std::cout << "]" << std::endl; | ||
| 83 | + | ||
| 84 | + const size_t element_count = static_cast<size_t>(output_tensor.GetSize() / sizeof(float)); | ||
| 85 | + const auto *output_data = reinterpret_cast<const float *>(output_tensor.GetData()); | ||
| 86 | + std::cout << "output values (first " << std::min(element_count, static_cast<size_t>(10U)) << "):"; | ||
| 87 | + for (size_t i = 0U; i < std::min(element_count, static_cast<size_t>(10U)); ++i) { | ||
| 88 | + std::cout << " " << output_data[i]; | ||
| 89 | + } | ||
| 90 | + std::cout << std::endl; | ||
| 91 | +} | ||
| 92 | + | ||
| 93 | +bool VerifyOutput(const ge::Tensor &output_tensor, const float *expected, size_t element_count) { | ||
| 94 | + const auto *output_data = reinterpret_cast<const float *>(output_tensor.GetData()); | ||
| 95 | + if (output_data == nullptr) { | ||
| 96 | + return false; | ||
| 97 | + } | ||
| 98 | + const size_t actual_count = static_cast<size_t>(output_tensor.GetSize() / sizeof(float)); | ||
| 99 | + if (actual_count != element_count) { | ||
| 100 | + std::cerr << "Element count mismatch: expected " << element_count << ", got " << actual_count << std::endl; | ||
| 101 | + return false; | ||
| 102 | + } | ||
| 103 | + for (size_t i = 0U; i < element_count; ++i) { | ||
| 104 | + if (std::fabs(output_data[i] - expected[i]) > 1e-5f) { | ||
| 105 | + std::cerr << "Value mismatch at index " << i << ": expected " << expected[i] << ", got " << output_data[i] | ||
| 106 | + << std::endl; | ||
| 107 | + return false; | ||
| 108 | + } | ||
| 109 | + } | ||
| 110 | + return true; | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +bool PrepareHostCpuInputs(std::vector<ge::Tensor> &inputs) { | ||
| 114 | + std::vector<float> x_values(kSmallElementCount); | ||
| 115 | + std::vector<float> y_values(kSmallElementCount); | ||
| 116 | + std::vector<float> dynamic_sub_values(kSmallElementCount); | ||
| 117 | + for (size_t i = 0U; i < kSmallElementCount; ++i) { | ||
| 118 | + x_values[i] = static_cast<float>(i + 1U); | ||
| 119 | + y_values[i] = static_cast<float>(i + 5U); | ||
| 120 | + dynamic_sub_values[i] = -y_values[i]; | ||
| 121 | + } | ||
| 122 | + ge::Tensor input_x; | ||
| 123 | + ge::Tensor input_y; | ||
| 124 | + ge::Tensor input_dynamic_sub; | ||
| 125 | + if (!MakeInputTensor(x_values, input_x) || !MakeInputTensor(y_values, input_y) || | ||
| 126 | + !MakeInputTensor(dynamic_sub_values, input_dynamic_sub)) { | ||
| 127 | + return false; | ||
| 128 | + } | ||
| 129 | + inputs.push_back(input_x); | ||
| 130 | + inputs.push_back(input_y); | ||
| 131 | + inputs.push_back(input_dynamic_sub); | ||
| 132 | + return true; | ||
| 133 | +} | ||
| 134 | + | ||
| 135 | +bool RunHostCpuScenario(ge::Session &session) { | ||
| 136 | + std::cout << "\n=== Scenario1: HostCpu Custom (Sub + Add + dynamic Sub) ===" << std::endl; | ||
| 137 | + | ||
| 138 | + auto graph = BuildSmallDataGraph("HostCpuDataGraph", kSmallElementCount); | ||
| 139 | + if (graph == nullptr) { | ||
| 140 | + std::cerr << "BuildSmallDataGraph failed" << std::endl; | ||
| 141 | + return false; | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + const auto add_ret = session.AddGraph(kHostCpuGraphId, *graph); | ||
| 145 | + if (add_ret != ge::SUCCESS) { | ||
| 146 | + std::cerr << "AddGraph failed, ret: " << add_ret << std::endl; | ||
| 147 | + return false; | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + std::vector<ge::Tensor> inputs; | ||
| 151 | + std::vector<ge::Tensor> outputs; | ||
| 152 | + if (!PrepareHostCpuInputs(inputs)) { | ||
| 153 | + (void)session.RemoveGraph(kHostCpuGraphId); | ||
| 154 | + return false; | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + const auto run_ret = session.RunGraph(kHostCpuGraphId, inputs, outputs); | ||
| 158 | + if (run_ret != ge::SUCCESS) { | ||
| 159 | + std::cerr << "RunGraph failed, ret: " << run_ret << std::endl; | ||
| 160 | + (void)session.RemoveGraph(kHostCpuGraphId); | ||
| 161 | + return false; | ||
| 162 | + } | ||
| 163 | + if (outputs.empty()) { | ||
| 164 | + std::cerr << "RunGraph success but outputs is empty" << std::endl; | ||
| 165 | + (void)session.RemoveGraph(kHostCpuGraphId); | ||
| 166 | + return false; | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + PrintOutputTensor(outputs[0]); | ||
| 170 | + const bool verified = VerifyOutput(outputs[0], kExpectedSmallValues, kSmallElementCount); | ||
| 171 | + if (!verified) { | ||
| 172 | + std::cerr << "Output verification failed" << std::endl; | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + (void)session.RemoveGraph(kHostCpuGraphId); | ||
| 176 | + return verified; | ||
| 177 | +} | ||
| 178 | + | ||
| 179 | +bool RunAiCoreScenario(ge::Session &session) { | ||
| 180 | + std::cout << "\n=== Scenario2: AiCore (Data input + large shape + static graph) ===" << std::endl; | ||
| 181 | + | ||
| 182 | + auto graph = BuildLargeDataGraph("AiCoreInputGraph", kLargeElementCount); | ||
| 183 | + if (graph == nullptr) { | ||
| 184 | + std::cerr << "BuildLargeDataGraph failed" << std::endl; | ||
| 185 | + return false; | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + const auto add_ret = session.AddGraph(kAiCoreGraphId, *graph); | ||
| 189 | + if (add_ret != ge::SUCCESS) { | ||
| 190 | + std::cerr << "AddGraph failed, ret: " << add_ret << std::endl; | ||
| 191 | + return false; | ||
| 192 | + } | ||
| 193 | + | ||
| 194 | + std::vector<float> x_values(kLargeElementCount); | ||
| 195 | + std::vector<float> y_values(kLargeElementCount); | ||
| 196 | + std::vector<float> expected(kLargeElementCount); | ||
| 197 | + for (size_t i = 0U; i < kLargeElementCount; ++i) { | ||
| 198 | + x_values[i] = static_cast<float>(i + 1U); | ||
| 199 | + y_values[i] = static_cast<float>(i + 5U); | ||
| 200 | + expected[i] = x_values[i] + y_values[i]; | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | + std::vector<ge::Tensor> inputs; | ||
| 204 | + std::vector<ge::Tensor> outputs; | ||
| 205 | + ge::Tensor input_x; | ||
| 206 | + ge::Tensor input_y; | ||
| 207 | + if (!MakeInputTensor(x_values, input_x) || !MakeInputTensor(y_values, input_y)) { | ||
| 208 | + (void)session.RemoveGraph(kAiCoreGraphId); | ||
| 209 | + return false; | ||
| 210 | + } | ||
| 211 | + inputs.push_back(input_x); | ||
| 212 | + inputs.push_back(input_y); | ||
| 213 | + | ||
| 214 | + const auto run_ret = session.RunGraph(kAiCoreGraphId, inputs, outputs); | ||
| 215 | + if (run_ret != ge::SUCCESS) { | ||
| 216 | + std::cerr << "RunGraph failed, ret: " << run_ret << std::endl; | ||
| 217 | + (void)session.RemoveGraph(kAiCoreGraphId); | ||
| 218 | + return false; | ||
| 219 | + } | ||
| 220 | + if (outputs.empty()) { | ||
| 221 | + std::cerr << "RunGraph success but outputs is empty" << std::endl; | ||
| 222 | + (void)session.RemoveGraph(kAiCoreGraphId); | ||
| 223 | + return false; | ||
| 224 | + } | ||
| 225 | + | ||
| 226 | + PrintOutputTensor(outputs[0]); | ||
| 227 | + const bool verified = VerifyOutput(outputs[0], expected.data(), kLargeElementCount); | ||
| 228 | + if (!verified) { | ||
| 229 | + std::cerr << "Output verification failed" << std::endl; | ||
| 230 | + } | ||
| 231 | + | ||
| 232 | + (void)session.RemoveGraph(kAiCoreGraphId); | ||
| 233 | + return verified; | ||
| 234 | +} | ||
| 235 | +} // namespace | ||
| 236 | + | ||
| 237 | +namespace { | ||
| 238 | +constexpr const char *const kScenarioAll = "all"; | ||
| 239 | +constexpr const char *const kScenarioHost = "host"; | ||
| 240 | +constexpr const char *const kScenarioAiCore = "aicore"; | ||
| 241 | +constexpr char kScenarioOptionPrefix[] = "--scenario="; | ||
| 242 | + | ||
| 243 | +void PrintUsage(const char *prog_name) { | ||
| 244 | + std::cout << "Usage: " << prog_name << " [--scenario=all|host|aicore]" << std::endl; | ||
| 245 | + std::cout << " --scenario=all (default) Run both scenarios" << std::endl; | ||
| 246 | + std::cout << " --scenario=host Run HostCpu custom op scenario" << std::endl; | ||
| 247 | + std::cout << " --scenario=aicore Run AICore built-in op scenario" << std::endl; | ||
| 248 | +} | ||
| 249 | + | ||
| 250 | +int RunScenarios(const std::string &scenario) { | ||
| 251 | + std::map<ge::AscendString, ge::AscendString> options = { | ||
| 252 | + {"ge.exec.deviceId", "0"}, | ||
| 253 | + {ge::OO_LEVEL, "O3"}, | ||
| 254 | + }; | ||
| 255 | + | ||
| 256 | + const auto init_ret = ge::GEInitialize(options); | ||
| 257 | + if (init_ret != ge::SUCCESS) { | ||
| 258 | + std::cerr << "GEInitialize failed, ret: " << init_ret << std::endl; | ||
| 259 | + return 1; | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + int ret_code = 0; | ||
| 263 | + { | ||
| 264 | + ge::Session session(options); | ||
| 265 | + | ||
| 266 | + if (scenario == kScenarioAll || scenario == kScenarioHost) { | ||
| 267 | + if (!RunHostCpuScenario(session)) { | ||
| 268 | + ret_code = 1; | ||
| 269 | + } | ||
| 270 | + } | ||
| 271 | + | ||
| 272 | + if (scenario == kScenarioAll || scenario == kScenarioAiCore) { | ||
| 273 | + if (!RunAiCoreScenario(session)) { | ||
| 274 | + ret_code = 1; | ||
| 275 | + } | ||
| 276 | + } | ||
| 277 | + } | ||
| 278 | + | ||
| 279 | + const auto finalize_ret = ge::GEFinalize(); | ||
| 280 | + if (finalize_ret != ge::SUCCESS) { | ||
| 281 | + std::cerr << "GEFinalize failed, ret: " << finalize_ret << std::endl; | ||
| 282 | + return 1; | ||
| 283 | + } | ||
| 284 | + return ret_code; | ||
| 285 | +} | ||
| 286 | + | ||
| 287 | +} // namespace | ||
| 288 | + | ||
| 289 | +int main(int argc, char *argv[]) { | ||
| 290 | + std::string scenario = kScenarioAll; | ||
| 291 | + for (int i = 1; i < argc; ++i) { | ||
| 292 | + std::string arg = argv[i]; | ||
| 293 | + if (arg.rfind(kScenarioOptionPrefix, 0) == 0) { | ||
| 294 | + scenario = arg.substr(std::char_traits<char>::length(kScenarioOptionPrefix)); | ||
| 295 | + break; | ||
| 296 | + } | ||
| 297 | + if (arg == "-h" || arg == "--help") { | ||
| 298 | + PrintUsage(argv[0]); | ||
| 299 | + return 0; | ||
| 300 | + } | ||
| 301 | + } | ||
| 302 | + if (scenario != kScenarioAll && scenario != kScenarioHost && scenario != kScenarioAiCore) { | ||
| 303 | + std::cerr << "Invalid scenario: " << scenario << ". Must be 'all', 'host' or 'aicore'." << std::endl; | ||
| 304 | + PrintUsage(argv[0]); | ||
| 305 | + return 1; | ||
| 306 | + } | ||
| 307 | + std::cout << "Running scenario: " << scenario << std::endl; | ||
| 308 | + return RunScenarios(scenario); | ||
| 309 | +} | ||
| @@ -0,0 +1,192 @@ | |||
| 1 | +cmake_minimum_required(VERSION 3.16) | ||
| 2 | +project(host_cpu_add_custom_offline LANGUAGES CXX) | ||
| 3 | + | ||
| 4 | +option(HOST_CPU_ADD_BUILD_CUSTOM_OP "Build libcust_opapi.so" ON) | ||
| 5 | +option(HOST_CPU_ADD_BUILD_GRAPH_BUILD "Build graph_build for AIR generation" ON) | ||
| 6 | +option(HOST_CPU_ADD_BUILD_MODEL_EXEC "Build model_exec for OM execution" ON) | ||
| 7 | + | ||
| 8 | +set(CMAKE_CXX_STANDARD 17) | ||
| 9 | +set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
| 10 | +set(CMAKE_CXX_EXTENSIONS OFF) | ||
| 11 | + | ||
| 12 | +if(NOT CMAKE_BUILD_TYPE) | ||
| 13 | + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) | ||
| 14 | +endif() | ||
| 15 | + | ||
| 16 | +set(COMMON_COMPILE_OPTIONS | ||
| 17 | + -Wall | ||
| 18 | + -Wextra | ||
| 19 | + -Wno-unused-parameter | ||
| 20 | +) | ||
| 21 | + | ||
| 22 | +set(ES_OUTPUT_DIR "${CMAKE_BINARY_DIR}/es_output") | ||
| 23 | +file(MAKE_DIRECTORY "${ES_OUTPUT_DIR}") | ||
| 24 | + | ||
| 25 | +set(PROJECT_OUTPUT_DIR "${CMAKE_SOURCE_DIR}/output") | ||
| 26 | +file(MAKE_DIRECTORY "${PROJECT_OUTPUT_DIR}") | ||
| 27 | +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") | ||
| 28 | + set(OPP_OS_TYPE "windows") | ||
| 29 | +else() | ||
| 30 | + set(OPP_OS_TYPE "linux") | ||
| 31 | +endif() | ||
| 32 | + | ||
| 33 | +string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" CMAKE_SYSTEM_PROCESSOR_LOWER) | ||
| 34 | +if(CMAKE_SYSTEM_PROCESSOR_LOWER MATCHES "^(aarch64|arm64)$") | ||
| 35 | + set(OPP_CPU_TYPE "aarch64") | ||
| 36 | +elseif(CMAKE_SYSTEM_PROCESSOR_LOWER MATCHES "^(x86_64|amd64)$") | ||
| 37 | + set(OPP_CPU_TYPE "x86_64") | ||
| 38 | +else() | ||
| 39 | + set(OPP_CPU_TYPE "${CMAKE_SYSTEM_PROCESSOR_LOWER}") | ||
| 40 | +endif() | ||
| 41 | + | ||
| 42 | +set(CUSTOM_OP_OUTPUT_DIR "${PROJECT_OUTPUT_DIR}/op_graph/lib/${OPP_OS_TYPE}/${OPP_CPU_TYPE}") | ||
| 43 | +file(MAKE_DIRECTORY "${CUSTOM_OP_OUTPUT_DIR}") | ||
| 44 | +set(CUSTOM_OP_INCLUDE_DIR "${PROJECT_OUTPUT_DIR}/op_graph/include") | ||
| 45 | +file(MAKE_DIRECTORY "${CUSTOM_OP_INCLUDE_DIR}") | ||
| 46 | +set(OP_PROTO_HEADER_SOURCE_FILE "${CMAKE_SOURCE_DIR}/ge/add_custom_ir.h") | ||
| 47 | +configure_file("${OP_PROTO_HEADER_SOURCE_FILE}" "${CUSTOM_OP_INCLUDE_DIR}/add_custom_ir.h" COPYONLY) | ||
| 48 | + | ||
| 49 | +set(ASCEND_HOME_PATH_OVERRIDE "" CACHE PATH "Optional ASCEND_HOME_PATH override") | ||
| 50 | +if(ASCEND_HOME_PATH_OVERRIDE) | ||
| 51 | + set(ASCEND_HOME_PATH "${ASCEND_HOME_PATH_OVERRIDE}") | ||
| 52 | +else() | ||
| 53 | + set(ASCEND_HOME_PATH "$ENV{ASCEND_HOME_PATH}") | ||
| 54 | +endif() | ||
| 55 | + | ||
| 56 | +if(ASCEND_HOME_PATH) | ||
| 57 | + message(STATUS "ASCEND_HOME_PATH: ${ASCEND_HOME_PATH}") | ||
| 58 | + list(APPEND CMAKE_MODULE_PATH "${ASCEND_HOME_PATH}/include/ge/cmake") | ||
| 59 | + find_package(GenerateEsPackage REQUIRED) | ||
| 60 | + | ||
| 61 | + add_library(add_custom_op_proto SHARED | ||
| 62 | + ge/add_custom_ir.cc | ||
| 63 | + ) | ||
| 64 | + target_compile_options(add_custom_op_proto PRIVATE | ||
| 65 | + -fvisibility=hidden | ||
| 66 | + ) | ||
| 67 | + target_compile_definitions(add_custom_op_proto PRIVATE | ||
| 68 | + _GLIBCXX_USE_CXX11_ABI=0 | ||
| 69 | + OP_PROTO_LIB | ||
| 70 | + ) | ||
| 71 | + target_include_directories(add_custom_op_proto PRIVATE | ||
| 72 | + "${CUSTOM_OP_INCLUDE_DIR}" | ||
| 73 | + "${ASCEND_HOME_PATH}/include" | ||
| 74 | + "${ASCEND_HOME_PATH}/include/graph" | ||
| 75 | + "${ASCEND_HOME_PATH}/include/register" | ||
| 76 | + "${ASCEND_HOME_PATH}/include/external" | ||
| 77 | + ) | ||
| 78 | + | ||
| 79 | + add_es_library( | ||
| 80 | + ES_LINKABLE_AND_ALL_TARGET es_custom | ||
| 81 | + OPP_PROTO_TARGET add_custom_op_proto | ||
| 82 | + OUTPUT_PATH ${ES_OUTPUT_DIR} | ||
| 83 | + ) | ||
| 84 | +else() | ||
| 85 | + message(WARNING "ASCEND_HOME_PATH is empty. Configure succeeds, but compilation requires a valid CANN toolkit path.") | ||
| 86 | +endif() | ||
| 87 | + | ||
| 88 | +if(HOST_CPU_ADD_BUILD_CUSTOM_OP) | ||
| 89 | + add_library(cust_opapi SHARED | ||
| 90 | + ge/custom_op.cpp | ||
| 91 | + ) | ||
| 92 | + target_compile_options(cust_opapi PRIVATE ${COMMON_COMPILE_OPTIONS}) | ||
| 93 | + target_compile_definitions(cust_opapi PRIVATE | ||
| 94 | + _GLIBCXX_USE_CXX11_ABI=0 | ||
| 95 | + ) | ||
| 96 | + | ||
| 97 | + if(ASCEND_HOME_PATH) | ||
| 98 | + target_include_directories(cust_opapi PRIVATE | ||
| 99 | + "${CUSTOM_OP_INCLUDE_DIR}" | ||
| 100 | + "${ASCEND_HOME_PATH}/include" | ||
| 101 | + "${ASCEND_HOME_PATH}/include/graph" | ||
| 102 | + "${ASCEND_HOME_PATH}/include/register" | ||
| 103 | + "${ASCEND_HOME_PATH}/include/external" | ||
| 104 | + ) | ||
| 105 | + target_link_directories(cust_opapi PRIVATE "${ASCEND_HOME_PATH}/lib64") | ||
| 106 | + target_link_libraries(cust_opapi PRIVATE | ||
| 107 | + -Wl,--no-as-needed | ||
| 108 | + ascendcl | ||
| 109 | + register | ||
| 110 | + gert | ||
| 111 | + custom_op_registry_static | ||
| 112 | + pthread | ||
| 113 | + dl | ||
| 114 | + -Wl,--as-needed | ||
| 115 | + ) | ||
| 116 | + endif() | ||
| 117 | + | ||
| 118 | + set_target_properties(cust_opapi PROPERTIES | ||
| 119 | + OUTPUT_NAME "cust_opapi" | ||
| 120 | + LIBRARY_OUTPUT_DIRECTORY "${CUSTOM_OP_OUTPUT_DIR}" | ||
| 121 | + RUNTIME_OUTPUT_DIRECTORY "${CUSTOM_OP_OUTPUT_DIR}" | ||
| 122 | + ARCHIVE_OUTPUT_DIRECTORY "${CUSTOM_OP_OUTPUT_DIR}" | ||
| 123 | + ) | ||
| 124 | + | ||
| 125 | + install(FILES "${OP_PROTO_HEADER_SOURCE_FILE}" | ||
| 126 | + DESTINATION "${CUSTOM_OP_INCLUDE_DIR}" | ||
| 127 | + ) | ||
| 128 | +endif() | ||
| 129 | + | ||
| 130 | +if(HOST_CPU_ADD_BUILD_GRAPH_BUILD) | ||
| 131 | + add_executable(single_add_graph_build | ||
| 132 | + graph_build/main.cc | ||
| 133 | + ) | ||
| 134 | + target_compile_options(single_add_graph_build PRIVATE ${COMMON_COMPILE_OPTIONS}) | ||
| 135 | + target_compile_definitions(single_add_graph_build PRIVATE | ||
| 136 | + _GLIBCXX_USE_CXX11_ABI=0 | ||
| 137 | + ) | ||
| 138 | + | ||
| 139 | + if(ASCEND_HOME_PATH) | ||
| 140 | + target_include_directories(single_add_graph_build PRIVATE | ||
| 141 | + "${CUSTOM_OP_INCLUDE_DIR}" | ||
| 142 | + "${ES_OUTPUT_DIR}/include/es_custom" | ||
| 143 | + "${ASCEND_HOME_PATH}/include" | ||
| 144 | + "${ASCEND_HOME_PATH}/include/graph" | ||
| 145 | + "${ASCEND_HOME_PATH}/include/ge" | ||
| 146 | + "${ASCEND_HOME_PATH}/opp/built-in/op_proto/inc" | ||
| 147 | + ) | ||
| 148 | + target_link_directories(single_add_graph_build PRIVATE "${ASCEND_HOME_PATH}/lib64") | ||
| 149 | + target_link_libraries(single_add_graph_build PRIVATE | ||
| 150 | + -Wl,--no-as-needed | ||
| 151 | + es_math | ||
| 152 | + -Wl,--as-needed | ||
| 153 | + es_custom | ||
| 154 | + graph | ||
| 155 | + ge_compiler | ||
| 156 | + ascendcl | ||
| 157 | + graph_base | ||
| 158 | + c_sec | ||
| 159 | + ) | ||
| 160 | + add_dependencies(single_add_graph_build es_custom) | ||
| 161 | + endif() | ||
| 162 | + | ||
| 163 | + set_target_properties(single_add_graph_build PROPERTIES | ||
| 164 | + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" | ||
| 165 | + BUILD_RPATH "$ORIGIN:${ES_OUTPUT_DIR}/lib64" | ||
| 166 | + INSTALL_RPATH "$ORIGIN:${ES_OUTPUT_DIR}/lib64" | ||
| 167 | + ) | ||
| 168 | +endif() | ||
| 169 | + | ||
| 170 | +if(HOST_CPU_ADD_BUILD_MODEL_EXEC) | ||
| 171 | + add_executable(single_add_model_exec | ||
| 172 | + model_exec/main.cc | ||
| 173 | + ) | ||
| 174 | + target_compile_options(single_add_model_exec PRIVATE ${COMMON_COMPILE_OPTIONS}) | ||
| 175 | + target_compile_definitions(single_add_model_exec PRIVATE | ||
| 176 | + _GLIBCXX_USE_CXX11_ABI=0 | ||
| 177 | + ) | ||
| 178 | + | ||
| 179 | + if(ASCEND_HOME_PATH) | ||
| 180 | + target_include_directories(single_add_model_exec PRIVATE | ||
| 181 | + "${ASCEND_HOME_PATH}/include" | ||
| 182 | + ) | ||
| 183 | + target_link_directories(single_add_model_exec PRIVATE "${ASCEND_HOME_PATH}/lib64") | ||
| 184 | + target_link_libraries(single_add_model_exec PRIVATE | ||
| 185 | + ascendcl | ||
| 186 | + ) | ||
| 187 | + endif() | ||
| 188 | + | ||
| 189 | + set_target_properties(single_add_model_exec PROPERTIES | ||
| 190 | + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" | ||
| 191 | + ) | ||
| 192 | +endif() | ||
| @@ -0,0 +1,68 @@ | |||
| 1 | +# HostCpu AddCustom 自定义算子离线 OM 样例 | ||
| 2 | + | ||
| 3 | +## 样例概述 | ||
| 4 | + | ||
| 5 | +本离线样例演示 `AddCustom` 自定义算子的离线编译和部署流程:使用 ES API 构图生成 `.air`,通过 ATC 转换为 `.om`,再用 ACL API 加载执行。 | ||
| 6 | + | ||
| 7 | +## 前置依赖 | ||
| 8 | + | ||
| 9 | +- 参考[安装指导](../../../../docs/zh/quick_install.md)完成 `toolkit` 和 `ops` 包安装。 | ||
| 10 | +- 设置环境变量(假设包安装在 `/usr/local/Ascend/`): | ||
| 11 | + ```bash | ||
| 12 | + source /usr/local/Ascend/cann/set_env.sh | ||
| 13 | + ``` | ||
| 14 | + | ||
| 15 | +## 快速运行 | ||
| 16 | + | ||
| 17 | +在 `examples/custom_op/host_cpu_add_custom/offline` 目录执行: | ||
| 18 | + | ||
| 19 | +```bash | ||
| 20 | +bash run.sh | ||
| 21 | +``` | ||
| 22 | + | ||
| 23 | +脚本会完成以下步骤: | ||
| 24 | + | ||
| 25 | +1. 构建 `output/op_graph/lib/<os>/<arch>/libcust_opapi.so`。 | ||
| 26 | +2. 运行 `single_add_graph_build` 生成 `output/single_add.air`。 | ||
| 27 | +3. 调用 `atc` 生成 `output/single_add_<os>_<arch>.om`。 | ||
| 28 | +4. 运行 `single_add_model_exec` 加载并执行 OM。 | ||
| 29 | + | ||
| 30 | +运行成功时,终端应打印: | ||
| 31 | + | ||
| 32 | +```text | ||
| 33 | +[HostCpuExecuteOp] Execute for AddCustom | ||
| 34 | +[INFO] Model executed successfully! | ||
| 35 | +output values: 6 8 10 12 | ||
| 36 | +[INFO] Output verification passed! | ||
| 37 | +``` | ||
| 38 | + | ||
| 39 | +## 关键文件 | ||
| 40 | + | ||
| 41 | +```text | ||
| 42 | +offline | ||
| 43 | +├── CMakeLists.txt | ||
| 44 | +├── run.sh | ||
| 45 | +├── ge | ||
| 46 | +│ ├── add_custom_ir.h // AddCustom 原型定义 | ||
| 47 | +│ ├── add_custom_ir.cc // 编译 AddCustom 原型 | ||
| 48 | +│ └── custom_op.cpp // HostCpuExecuteOp / ShapeInferOp / PortableOp 实现 | ||
| 49 | +├── graph_build | ||
| 50 | +│ └── main.cc // ES 构图并导出 AIR | ||
| 51 | +└── model_exec | ||
| 52 | + └── main.cc // ACL 加载并执行 OM | ||
| 53 | +``` | ||
| 54 | + | ||
| 55 | +## 实现步骤 | ||
| 56 | + | ||
| 57 | +`ge/custom_op.cpp` 中 `AddCustom` 的实现是本样例的核心: | ||
| 58 | + | ||
| 59 | +- `HostCpuExecuteOp::Execute` 在 host 侧完成 float 向量加法。 | ||
| 60 | +- `ShapeInferOp` 将输出 shape 和 dtype 设为与输入一致。 | ||
| 61 | +- `PortableOp::Serialize/Deserialize` 提供离线 OM 所需的实例数据持久化实现。 | ||
| 62 | +- 通过 `REG_OP_BACKEND(AddCustom, "AddCustom", ge::OpBackend::kHostCPU)` 注册 kHostCPU backend。 | ||
| 63 | + | ||
| 64 | +## 注意事项 | ||
| 65 | + | ||
| 66 | +- `run.sh` 默认使用 `--soc_version=Ascend910B1`,如需适配其他环境请按实际硬件修改。 | ||
| 67 | +- 图输入 shape 固定为 `[4]` float32,输入数据为 `[1,2,3,4]` + `[5,6,7,8]`,期望输出 `[6,8,10,12]`。 | ||
| 68 | +- `ASCEND_CUSTOM_OPP_PATH` 会在 `run.sh` 中自动追加当前样例的 `output/`。 | ||
| @@ -0,0 +1,68 @@ | |||
| 1 | +# HostCpu AddCustom Custom Op Offline OM Sample | ||
| 2 | + | ||
| 3 | +## Overview | ||
| 4 | + | ||
| 5 | +This offline sample demonstrates the offline compilation and deployment flow for the `AddCustom` custom operator: building a graph with ES API to generate `.air`, converting to `.om` via ATC, then loading and executing through ACL API. | ||
| 6 | + | ||
| 7 | +## Prerequisites | ||
| 8 | + | ||
| 9 | +- Refer to the [Installation Guide](../../../../docs/en/quick_install.md) to install the `toolkit` and `ops` packages. | ||
| 10 | +- Set the environment variables (assuming that the packages are installed in `/usr/local/Ascend/`): | ||
| 11 | + ```bash | ||
| 12 | + source /usr/local/Ascend/cann/set_env.sh | ||
| 13 | + ``` | ||
| 14 | + | ||
| 15 | +## Quick Run | ||
| 16 | + | ||
| 17 | +Run in `examples/custom_op/host_cpu_add_custom/offline`: | ||
| 18 | + | ||
| 19 | +```bash | ||
| 20 | +bash run.sh | ||
| 21 | +``` | ||
| 22 | + | ||
| 23 | +The script will: | ||
| 24 | + | ||
| 25 | +1. Build `output/op_graph/lib/<os>/<arch>/libcust_opapi.so`. | ||
| 26 | +2. Run `single_add_graph_build` to generate `output/single_add.air`. | ||
| 27 | +3. Run `atc` to generate `output/single_add_<os>_<arch>.om`. | ||
| 28 | +4. Run `single_add_model_exec` to load and execute the OM. | ||
| 29 | + | ||
| 30 | +Expected output includes: | ||
| 31 | + | ||
| 32 | +```text | ||
| 33 | +[HostCpuExecuteOp] Execute for AddCustom | ||
| 34 | +[INFO] Model executed successfully! | ||
| 35 | +output values: 6 8 10 12 | ||
| 36 | +[INFO] Output verification passed! | ||
| 37 | +``` | ||
| 38 | + | ||
| 39 | +## Key Files | ||
| 40 | + | ||
| 41 | +```text | ||
| 42 | +offline | ||
| 43 | +├── CMakeLists.txt | ||
| 44 | +├── run.sh | ||
| 45 | +├── ge | ||
| 46 | +│ ├── add_custom_ir.h // AddCustom operation prototype | ||
| 47 | +│ ├── add_custom_ir.cc // Compiles the AddCustom operation prototype | ||
| 48 | +│ └── custom_op.cpp // HostCpuExecuteOp / ShapeInferOp / PortableOp implementation | ||
| 49 | +├── graph_build | ||
| 50 | +│ └── main.cc // Builds graph and exports AIR | ||
| 51 | +└── model_exec | ||
| 52 | + └── main.cc // Loads and executes OM through ACL | ||
| 53 | +``` | ||
| 54 | + | ||
| 55 | +## Implementation Steps | ||
| 56 | + | ||
| 57 | +`AddCustom` in `ge/custom_op.cpp` is the core implementation: | ||
| 58 | + | ||
| 59 | +- `HostCpuExecuteOp::Execute` performs float vector addition on the host side. | ||
| 60 | +- `ShapeInferOp` copies input shape and dtype to the output. | ||
| 61 | +- `PortableOp::Serialize/Deserialize` provide the instance persistence implementation required by offline OM. | ||
| 62 | +- Registers only kHostCPU backend via `REG_OP_BACKEND(AddCustom, "AddCustom", ge::OpBackend::kHostCPU)`. | ||
| 63 | + | ||
| 64 | +## Notes | ||
| 65 | + | ||
| 66 | +- `run.sh` uses `--soc_version=Ascend910B1` by default. Adjust it for your hardware if needed. | ||
| 67 | +- The graph input shape is fixed to `[4]` float32, with input data `[1,2,3,4]` + `[5,6,7,8]` and expected output `[6,8,10,12]`. | ||
| 68 | +- `ASCEND_CUSTOM_OPP_PATH` is appended automatically by `run.sh` with this sample's `output/`. | ||
| @@ -0,0 +1,13 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | +namespace ge {} | ||
| @@ -0,0 +1,26 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +namespace ge { | ||
| 17 | +REG_OP(AddCustom) | ||
| 18 | + .INPUT(x, "T") | ||
| 19 | + .INPUT(y, "T") | ||
| 20 | + .OUTPUT(z, "T") | ||
| 21 | + .DATATYPE(T, TensorType({DT_FLOAT, DT_INT32, DT_INT64, DT_FLOAT16, DT_INT16, DT_INT8, DT_UINT8, DT_DOUBLE, | ||
| 22 | + DT_COMPLEX128, DT_COMPLEX64, DT_STRING})) | ||
| 23 | + .OP_END_FACTORY_REG(AddCustom); | ||
| 24 | +} // namespace ge | ||
| 25 | + | ||
| 26 | + | ||
| @@ -0,0 +1,82 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +namespace { | ||
| 17 | +constexpr size_t kInputIndexX = 0U; | ||
| 18 | +constexpr size_t kInputIndexY = 1U; | ||
| 19 | +constexpr size_t kOutputIndexZ = 0U; | ||
| 20 | +} // namespace | ||
| 21 | + | ||
| 22 | +namespace ge { | ||
| 23 | +class AddCustom final : public HostCpuExecuteOp, public ShapeInferOp, public PortableOp { | ||
| 24 | + public: | ||
| 25 | + graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) override { | ||
| 26 | + std::cout << "[HostCpuExecuteOp] Execute for AddCustom" << std::endl; | ||
| 27 | + | ||
| 28 | + const gert::Tensor *input_x = ctx->GetInputTensor(kInputIndexX); | ||
| 29 | + const gert::Tensor *input_y = ctx->GetInputTensor(kInputIndexY); | ||
| 30 | + if ((input_x == nullptr) || (input_y == nullptr)) { | ||
| 31 | + std::cerr << "GetInputTensor failed, input_x=" << input_x << ", input_y=" << input_y << std::endl; | ||
| 32 | + return GRAPH_FAILED; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + gert::Tensor *output_z = | ||
| 36 | + ctx->MallocOutputTensor(kOutputIndexZ, input_x->GetShape(), input_x->GetFormat(), input_x->GetDataType()); | ||
| 37 | + if (output_z == nullptr) { | ||
| 38 | + std::cerr << "MallocOutputTensor failed" << std::endl; | ||
| 39 | + return GRAPH_FAILED; | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + const float *x = input_x->GetData<float>(); | ||
| 43 | + const float *y = input_y->GetData<float>(); | ||
| 44 | + float *z = output_z->GetData<float>(); | ||
| 45 | + const int64_t shape_size = input_x->GetStorageShape().GetShapeSize(); | ||
| 46 | + for (int64_t i = 0; i < shape_size; ++i) { | ||
| 47 | + z[i] = x[i] + y[i]; | ||
| 48 | + } | ||
| 49 | + return GRAPH_SUCCESS; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + graphStatus InferShape(gert::InferShapeContext *ctx) override { | ||
| 53 | + std::cout << "[ShapeInferOp] InferShape for AddCustom" << std::endl; | ||
| 54 | + const gert::Shape *input_shape = ctx->GetInputShape(kInputIndexX); | ||
| 55 | + gert::Shape *output_shape = ctx->GetOutputShape(kOutputIndexZ); | ||
| 56 | + if ((input_shape == nullptr) || (output_shape == nullptr)) { | ||
| 57 | + return GRAPH_FAILED; | ||
| 58 | + } | ||
| 59 | + *output_shape = *input_shape; | ||
| 60 | + return GRAPH_SUCCESS; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + graphStatus InferDataType(gert::InferDataTypeContext *ctx) override { | ||
| 64 | + std::cout << "[ShapeInferOp] InferDataType for AddCustom" << std::endl; | ||
| 65 | + return ctx->SetOutputDataType(kOutputIndexZ, ctx->GetInputDataType(kInputIndexX)); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + graphStatus Serialize(std::vector<uint8_t> &buffer) override { | ||
| 69 | + std::cout << "[PortableOp] Serialize for AddCustom" << std::endl; | ||
| 70 | + buffer = {0U}; | ||
| 71 | + return GRAPH_SUCCESS; | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + graphStatus Deserialize(const std::vector<uint8_t> &buffer) override { | ||
| 75 | + std::cout << "[PortableOp] Deserialize for AddCustom" << std::endl; | ||
| 76 | + (void)buffer; | ||
| 77 | + return GRAPH_SUCCESS; | ||
| 78 | + } | ||
| 79 | +}; | ||
| 80 | + | ||
| 81 | +REG_OP_BACKEND(AddCustom, "AddCustom", ge::OpBackend::kHostCPU); | ||
| 82 | +} // namespace ge | ||
| @@ -0,0 +1,46 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | +namespace { | ||
| 19 | +constexpr const char *const kAirFileName = "./single_add.air"; | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +int main(int argc, char *argv[]) { | ||
| 23 | + (void)argc; | ||
| 24 | + (void)argv; | ||
| 25 | + | ||
| 26 | + std::cout << "========== Graph Build Start ==========" << std::endl; | ||
| 27 | + | ||
| 28 | + auto graph_builder = std::make_unique<ge::es::EsGraphBuilder>("SingleAddOffline"); | ||
| 29 | + auto x = graph_builder->CreateInput(0, "data_x", ge::DT_FLOAT, ge::FORMAT_ND, {4}); | ||
| 30 | + auto y = graph_builder->CreateInput(1, "data_y", ge::DT_FLOAT, ge::FORMAT_ND, {4}); | ||
| 31 | + auto add = ge::es::AddCustom(x, y); | ||
| 32 | + (void)graph_builder->SetOutput(add, 0); | ||
| 33 | + auto graph = graph_builder->BuildAndReset(); | ||
| 34 | + if (graph == nullptr) { | ||
| 35 | + std::cerr << "BuildAndReset failed" << std::endl; | ||
| 36 | + return 1; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + if (graph->SaveToFile(kAirFileName) != ge::GRAPH_SUCCESS) { | ||
| 40 | + std::cerr << "SaveToFile failed: " << kAirFileName << std::endl; | ||
| 41 | + return 1; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + std::cout << "========== Generate " << kAirFileName << " Success! ==========" << std::endl; | ||
| 45 | + return 0; | ||
| 46 | +} | ||
| @@ -0,0 +1,255 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +namespace { | ||
| 17 | +constexpr int kExpectedArgc = 2; | ||
| 18 | +constexpr size_t kElementCount = 4U; | ||
| 19 | +constexpr float kExpectedValues[kElementCount] = {6.0f, 8.0f, 10.0f, 12.0f}; | ||
| 20 | + | ||
| 21 | +bool ReleaseDataset(aclmdlDataset *dataset) { | ||
| 22 | + if (dataset == nullptr) { | ||
| 23 | + return true; | ||
| 24 | + } | ||
| 25 | + bool success = true; | ||
| 26 | + const size_t bufferCount = aclmdlGetDatasetNumBuffers(dataset); | ||
| 27 | + for (size_t i = 0U; i < bufferCount; ++i) { | ||
| 28 | + aclDataBuffer *dataBuffer = aclmdlGetDatasetBuffer(dataset, i); | ||
| 29 | + if (dataBuffer == nullptr) { | ||
| 30 | + std::cerr << "[ERROR] aclmdlGetDatasetBuffer failed, index: " << i << std::endl; | ||
| 31 | + success = false; | ||
| 32 | + continue; | ||
| 33 | + } | ||
| 34 | + void *deviceAddr = aclGetDataBufferAddr(dataBuffer); | ||
| 35 | + if (deviceAddr != nullptr) { | ||
| 36 | + if (aclrtFree(deviceAddr) != ACL_SUCCESS) { | ||
| 37 | + std::cerr << "[ERROR] aclrtFree failed, index: " << i << std::endl; | ||
| 38 | + success = false; | ||
| 39 | + } | ||
| 40 | + } | ||
| 41 | + if (aclDestroyDataBuffer(dataBuffer) != ACL_SUCCESS) { | ||
| 42 | + std::cerr << "[ERROR] aclDestroyDataBuffer failed, index: " << i << std::endl; | ||
| 43 | + success = false; | ||
| 44 | + } | ||
| 45 | + } | ||
| 46 | + if (aclmdlDestroyDataset(dataset) != ACL_SUCCESS) { | ||
| 47 | + std::cerr << "[ERROR] aclmdlDestroyDataset failed" << std::endl; | ||
| 48 | + success = false; | ||
| 49 | + } | ||
| 50 | + return success; | ||
| 51 | +} | ||
| 52 | +} // namespace | ||
| 53 | + | ||
| 54 | +int main(int argc, char *argv[]) { | ||
| 55 | + if (argc != kExpectedArgc) { | ||
| 56 | + std::cerr << "[ERROR] Usage: " << argv[0] << " <model_path>" << std::endl; | ||
| 57 | + return 1; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + const char *modelPath = argv[1]; | ||
| 61 | + const int32_t deviceId = 0; | ||
| 62 | + int result = 1; | ||
| 63 | + bool aclInitialized = false; | ||
| 64 | + bool deviceSet = false; | ||
| 65 | + bool modelLoaded = false; | ||
| 66 | + uint32_t modelId = 0U; | ||
| 67 | + aclmdlDesc *modelDesc = nullptr; | ||
| 68 | + aclmdlDataset *inputDataset = nullptr; | ||
| 69 | + aclmdlDataset *outputDataset = nullptr; | ||
| 70 | + | ||
| 71 | + do { | ||
| 72 | + aclError ret = aclInit(nullptr); | ||
| 73 | + if (ret != ACL_SUCCESS) { | ||
| 74 | + std::cerr << "[ERROR] aclInit failed, error code: " << ret << std::endl; | ||
| 75 | + break; | ||
| 76 | + } | ||
| 77 | + aclInitialized = true; | ||
| 78 | + | ||
| 79 | + ret = aclrtSetDevice(deviceId); | ||
| 80 | + if (ret != ACL_SUCCESS) { | ||
| 81 | + std::cerr << "[ERROR] aclrtSetDevice failed, error code: " << ret << std::endl; | ||
| 82 | + break; | ||
| 83 | + } | ||
| 84 | + deviceSet = true; | ||
| 85 | + | ||
| 86 | + ret = aclmdlLoadFromFile(modelPath, &modelId); | ||
| 87 | + if (ret != ACL_SUCCESS) { | ||
| 88 | + std::cerr << "[ERROR] aclmdlLoadFromFile failed, error code: " << ret << std::endl; | ||
| 89 | + break; | ||
| 90 | + } | ||
| 91 | + modelLoaded = true; | ||
| 92 | + std::cout << "[INFO] Model loaded successfully: " << modelPath << std::endl; | ||
| 93 | + | ||
| 94 | + modelDesc = aclmdlCreateDesc(); | ||
| 95 | + if (modelDesc == nullptr) { | ||
| 96 | + std::cerr << "[ERROR] aclmdlCreateDesc failed" << std::endl; | ||
| 97 | + break; | ||
| 98 | + } | ||
| 99 | + ret = aclmdlGetDesc(modelDesc, modelId); | ||
| 100 | + if (ret != ACL_SUCCESS) { | ||
| 101 | + std::cerr << "[ERROR] aclmdlGetDesc failed, error code: " << ret << std::endl; | ||
| 102 | + break; | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + inputDataset = aclmdlCreateDataset(); | ||
| 106 | + if (inputDataset == nullptr) { | ||
| 107 | + std::cerr << "[ERROR] aclmdlCreateDataset for input failed" << std::endl; | ||
| 108 | + break; | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + const size_t numInputs = aclmdlGetNumInputs(modelDesc); | ||
| 112 | + std::cout << "[INFO] Number of inputs: " << numInputs << std::endl; | ||
| 113 | + | ||
| 114 | + std::vector<std::vector<float>> inputData = {{1.0f, 2.0f, 3.0f, 4.0f}, {5.0f, 6.0f, 7.0f, 8.0f}}; | ||
| 115 | + | ||
| 116 | + bool inputsReady = true; | ||
| 117 | + for (size_t i = 0U; i < numInputs; ++i) { | ||
| 118 | + const size_t bufferSize = aclmdlGetInputSizeByIndex(modelDesc, i); | ||
| 119 | + void *devPtr = nullptr; | ||
| 120 | + ret = aclrtMalloc(&devPtr, bufferSize, ACL_MEM_MALLOC_NORMAL_ONLY); | ||
| 121 | + if (ret != ACL_SUCCESS) { | ||
| 122 | + std::cerr << "[ERROR] aclrtMalloc for input " << i << " failed, error code: " << ret << std::endl; | ||
| 123 | + inputsReady = false; | ||
| 124 | + break; | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + const float *hostData = (i < inputData.size()) ? inputData[i].data() : inputData[0].data(); | ||
| 128 | + ret = aclrtMemcpy(devPtr, bufferSize, hostData, bufferSize, ACL_MEMCPY_HOST_TO_DEVICE); | ||
| 129 | + if (ret != ACL_SUCCESS) { | ||
| 130 | + std::cerr << "[ERROR] aclrtMemcpy for input " << i << " failed, error code: " << ret << std::endl; | ||
| 131 | + (void)aclrtFree(devPtr); | ||
| 132 | + inputsReady = false; | ||
| 133 | + break; | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + aclDataBuffer *inputBuffer = aclCreateDataBuffer(devPtr, bufferSize); | ||
| 137 | + if (inputBuffer == nullptr) { | ||
| 138 | + std::cerr << "[ERROR] aclCreateDataBuffer for input " << i << " failed" << std::endl; | ||
| 139 | + (void)aclrtFree(devPtr); | ||
| 140 | + inputsReady = false; | ||
| 141 | + break; | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + ret = aclmdlAddDatasetBuffer(inputDataset, inputBuffer); | ||
| 145 | + if (ret != ACL_SUCCESS) { | ||
| 146 | + std::cerr << "[ERROR] aclmdlAddDatasetBuffer for input " << i << " failed, error code: " << ret << std::endl; | ||
| 147 | + (void)aclrtFree(devPtr); | ||
| 148 | + (void)aclDestroyDataBuffer(inputBuffer); | ||
| 149 | + inputsReady = false; | ||
| 150 | + break; | ||
| 151 | + } | ||
| 152 | + } | ||
| 153 | + if (!inputsReady) { | ||
| 154 | + break; | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + outputDataset = aclmdlCreateDataset(); | ||
| 158 | + if (outputDataset == nullptr) { | ||
| 159 | + std::cerr << "[ERROR] aclmdlCreateDataset for output failed" << std::endl; | ||
| 160 | + break; | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + const size_t outputSize = aclmdlGetOutputSizeByIndex(modelDesc, 0U); | ||
| 164 | + void *devPtrOut = nullptr; | ||
| 165 | + ret = aclrtMalloc(&devPtrOut, outputSize, ACL_MEM_MALLOC_NORMAL_ONLY); | ||
| 166 | + if (ret != ACL_SUCCESS) { | ||
| 167 | + std::cerr << "[ERROR] aclrtMalloc for output failed, error code: " << ret << std::endl; | ||
| 168 | + break; | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + aclDataBuffer *outputBuffer = aclCreateDataBuffer(devPtrOut, outputSize); | ||
| 172 | + if (outputBuffer == nullptr) { | ||
| 173 | + std::cerr << "[ERROR] aclCreateDataBuffer for output failed" << std::endl; | ||
| 174 | + (void)aclrtFree(devPtrOut); | ||
| 175 | + break; | ||
| 176 | + } | ||
| 177 | + | ||
| 178 | + ret = aclmdlAddDatasetBuffer(outputDataset, outputBuffer); | ||
| 179 | + if (ret != ACL_SUCCESS) { | ||
| 180 | + std::cerr << "[ERROR] aclmdlAddDatasetBuffer for output failed, error code: " << ret << std::endl; | ||
| 181 | + (void)aclrtFree(devPtrOut); | ||
| 182 | + (void)aclDestroyDataBuffer(outputBuffer); | ||
| 183 | + break; | ||
| 184 | + } | ||
| 185 | + | ||
| 186 | + ret = aclmdlExecute(modelId, inputDataset, outputDataset); | ||
| 187 | + if (ret != ACL_SUCCESS) { | ||
| 188 | + std::cerr << "[ERROR] aclmdlExecute failed, error code: " << ret << std::endl; | ||
| 189 | + break; | ||
| 190 | + } | ||
| 191 | + std::cout << "[INFO] Model executed successfully!" << std::endl; | ||
| 192 | + | ||
| 193 | + std::vector<float> hostOutput(kElementCount); | ||
| 194 | + ret = aclrtMemcpy(hostOutput.data(), outputSize, devPtrOut, outputSize, ACL_MEMCPY_DEVICE_TO_HOST); | ||
| 195 | + if (ret != ACL_SUCCESS) { | ||
| 196 | + std::cerr << "[ERROR] aclrtMemcpy for output failed, error code: " << ret << std::endl; | ||
| 197 | + break; | ||
| 198 | + } | ||
| 199 | + | ||
| 200 | + std::cout << "output values:"; | ||
| 201 | + for (size_t i = 0U; i < kElementCount; ++i) { | ||
| 202 | + std::cout << " " << hostOutput[i]; | ||
| 203 | + } | ||
| 204 | + std::cout << std::endl; | ||
| 205 | + | ||
| 206 | + bool verified = true; | ||
| 207 | + for (size_t i = 0U; i < kElementCount; ++i) { | ||
| 208 | + if (std::fabs(hostOutput[i] - kExpectedValues[i]) > 1e-5f) { | ||
| 209 | + std::cerr << "[ERROR] Value mismatch at index " << i << ": expected " << kExpectedValues[i] << ", got " | ||
| 210 | + << hostOutput[i] << std::endl; | ||
| 211 | + verified = false; | ||
| 212 | + } | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + if (verified) { | ||
| 216 | + std::cout << "[INFO] Output verification passed!" << std::endl; | ||
| 217 | + result = 0; | ||
| 218 | + } else { | ||
| 219 | + std::cerr << "[ERROR] Output verification failed!" << std::endl; | ||
| 220 | + } | ||
| 221 | + } while (false); | ||
| 222 | + | ||
| 223 | + if (!ReleaseDataset(outputDataset)) { | ||
| 224 | + result = 1; | ||
| 225 | + } | ||
| 226 | + if (!ReleaseDataset(inputDataset)) { | ||
| 227 | + result = 1; | ||
| 228 | + } | ||
| 229 | + if (modelDesc != nullptr) { | ||
| 230 | + if (aclmdlDestroyDesc(modelDesc) != ACL_SUCCESS) { | ||
| 231 | + std::cerr << "[ERROR] aclmdlDestroyDesc failed" << std::endl; | ||
| 232 | + result = 1; | ||
| 233 | + } | ||
| 234 | + } | ||
| 235 | + if (modelLoaded) { | ||
| 236 | + if (aclmdlUnload(modelId) != ACL_SUCCESS) { | ||
| 237 | + std::cerr << "[ERROR] aclmdlUnload failed" << std::endl; | ||
| 238 | + result = 1; | ||
| 239 | + } | ||
| 240 | + } | ||
| 241 | + if (deviceSet) { | ||
| 242 | + if (aclrtResetDevice(deviceId) != ACL_SUCCESS) { | ||
| 243 | + std::cerr << "[ERROR] aclrtResetDevice failed" << std::endl; | ||
| 244 | + result = 1; | ||
| 245 | + } | ||
| 246 | + } | ||
| 247 | + if (aclInitialized) { | ||
| 248 | + if (aclFinalize() != ACL_SUCCESS) { | ||
| 249 | + std::cerr << "[ERROR] aclFinalize failed" << std::endl; | ||
| 250 | + result = 1; | ||
| 251 | + } | ||
| 252 | + } | ||
| 253 | + | ||
| 254 | + return result; | ||
| 255 | +} | ||


417行的接口,是不是放在415行上面比较好,按照字母序排列?