已合并
feat: 新增 HostCPU 自定义算子示例及文档 #4602
duhua创建于 9 天前
feat: 新增 HostCPU 自定义算子示例及文档 #4602
已合并
duhua创建于 9 天前
54 个文件变更+3092-32
@@ -229,23 +229,26 @@ This is constant folding main entry Pass, processing nodes with all inputs as co
2293. **Memory Priority Strategy Check**2293. **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 worthwhile230 - 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 implementation234+ - 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 Kernel238+ - 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 operators240+ - **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 
2425. **Folding Replacement**2455. **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 identification247 - Newly created Const node will be marked `_is_from_constant_folding=true`, for subsequent flow identification
245 248 
2466. **Performance Statistics**2496. **Performance Statistics**
247- - Separately record AICPU kernel and GE built-in kernel folding time and call count250+ - 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 
250Pass registration macro:253Pass registration macro:
251 254 
@@ -357,7 +360,7 @@ Related attributes and tool classes:
357Mechanism flow:360Mechanism flow:
358 361 
359```362```
360-DimensionComputePass / DimensionComputePass363+ConstantFoldingPass / DimensionComputePass
361364
362 │ (node partial inputs non-constant)365 │ (node partial inputs non-constant)
363366
@@ -335,6 +335,8 @@ flowchart TD
335 335 
336Engine 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.336Engine 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+ 
338Engine reassignment (`ReAssignEngine()`) implements through strategy pattern. `EngineReAssignPass` is the strategy interface, currently with two implementations:340Engine 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 scenarios342- `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)
Sophia1213
Sophia1213Sophia12138 天前

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

likedislike
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+## 约束说明
Sophia1213
Sophia1213Sophia12138 天前

后续新增的接口,都建议补充调用示例,调用示例放在约束说明下面

likedislike
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,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,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```python30```python
35-from ge.custom_op import register_op_impl31+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")
39class AddCustom:36class AddCustom:
40- def execute(self, x, y):37+ def execute(self, x: Tensor, y: Tensor) -> None:
41- return x + y38+ 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)
2263. **内存优先策略检查**2263. **内存优先策略检查**
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 Kernel238 - 若 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 
2436. **性能统计**2466. **性能统计**
244- - 分别记录 AICPUGE 内置内核的折叠耗时和调用次数247+ - 分别记录 GE置 Kernel HostCpu 算子(Host CPU 自定义算子 / AICPU kernel)常量折叠的折叠耗时和调用次数
245 -`GraphManager::OptimizeStage1_2` 中汇总输出性能追踪日志248 -`GraphManager::OptimizeStage1_2` 中汇总输出性能追踪日志
246 249 
247Pass 注册宏:250Pass 注册宏:
@@ -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+#include "add_custom_ir.h"
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+#ifndef EXAMPLES_CUSTOM_OP_HOST_CPU_ADD_CUSTOM_CONSTANT_FOLDING_GE_ADD_CUSTOM_IR_H_
12+#define EXAMPLES_CUSTOM_OP_HOST_CPU_ADD_CUSTOM_CONSTANT_FOLDING_GE_ADD_CUSTOM_IR_H_
13+ 
14+#include "graph/operator_reg.h"
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+#endif // EXAMPLES_CUSTOM_OP_HOST_CPU_ADD_CUSTOM_CONSTANT_FOLDING_GE_ADD_CUSTOM_IR_H_
@@ -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+#include <iostream>
12+ 
13+#include "add_custom_ir.h"
14+#include "graph/custom_op.h"
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+#include <cmath>
12+#include <iostream>
13+#include <map>
14+#include <memory>
15+#include <vector>
16+ 
17+#include "add_custom_ir.h"
18+#include "ge/es_graph_builder.h"
19+#include "es_custom_ops.h"
20+#include "ge/ge_api.h"
21+#include "graph.h"
22+#include "tensor.h"
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+#include <iostream>
12+ 
13+#include "acl/acl.h"
14+#include "graph/custom_op.h"
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+#include <cmath>
12+#include <iostream>
13+#include <map>
14+#include <memory>
15+#include <vector>
16+ 
17+#include "es_Add.h"
18+#include "es_Sub.h"
19+#include "ge/es_graph_builder.h"
20+#include "ge/ge_api.h"
21+#include "graph.h"
22+#include "tensor.h"
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+#include "add_custom_ir.h"
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+#ifndef EXAMPLES_CUSTOM_OP_HOST_CPU_ADD_CUSTOM_OFFLINE_GE_ADD_CUSTOM_IR_H_
12+#define EXAMPLES_CUSTOM_OP_HOST_CPU_ADD_CUSTOM_OFFLINE_GE_ADD_CUSTOM_IR_H_
13+ 
14+#include "graph/operator_reg.h"
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+#endif // EXAMPLES_CUSTOM_OP_HOST_CPU_ADD_CUSTOM_OFFLINE_GE_ADD_CUSTOM_IR_H_
@@ -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+#include <iostream>
12+ 
13+#include "add_custom_ir.h"
14+#include "graph/custom_op.h"
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+#include <iostream>
12+#include <memory>
13+ 
14+#include "add_custom_ir.h"
15+#include "es_custom_ops.h"
16+#include "ge/es_graph_builder.h"
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+#include <cmath>
12+#include <iostream>
13+#include <vector>
14+#include "acl/acl.h"
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+}