
CANN ops-cv AddExample 算子全解析从算子定义到 aclnn / GE 图模式调用实战【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cvAddExample 是 CANN ops-cv 图像算子库中的示例算子用于完成两个张量的逐元素加法计算y x1 x2覆盖算子定义、shape 推理、tiling 切分、AI Core 核函数实现到上层 API 调用的完整开发链路。本文将以 examples/add_example/README.md 为骨架结合仓库源码深入讲解 AddExample 的参数规格、实现原理并给出 aclnn API 与 GE 图模式两种调用方式的完整编译运行方案帮助读者快速上手在 Ascend NPU 上开发与验证自定义算子。一、产品支持情况AddExample 算子当前支持以下产品形态覆盖从训练到推理的主流 Ascend 硬件平台产品是否支持Ascend 950PR / Ascend 950DT√Atlas A3 训练系列产品 / Atlas A3 推理系列产品√Atlas A2 训练系列产品 / Atlas A2 推理系列产品√从算子定义的源码可以印证这一支持范围add_example_def.cpp 中通过this-AICore().AddConfig(...)为ascend910bAtlas A2/A3 系列对应芯片、ascend910_93与ascend950Ascend 950 系列分别注册了 AI Core 编译配置且三者共用同一份aicoreConfig配置对象。二、功能说明与数学定义AddExample 的功能非常聚焦完成两个输入张量的逐元素加法计算。其计算公式为$$ y x1 x2 $$其中 x1、x2 为两个输入张量y 为输出张量三者形状一致逐元素相加。该算子可视为 element-wise 二元运算的典型示例也是理解 ops-cv 中其他复杂算子的最佳起点。三、参数说明AddExample 共包含两个必选输入和一个必选输出参数规格如下参数名输入/输出/属性描述数据类型数据格式x1输入待进行 AddExample 计算的入参公式中的 x1FLOAT、INT32NDx2输入待进行 AddExample 计算的入参公式中的 x2FLOAT、INT32NDy输出待进行 AddExample 计算的出参公式中的 yFLOAT、INT32ND上述参数规格在算子定义源码中有严格对应add_example_def.cpp 中分别为 x1、x2、y 声明了REQUIRED必选参数类型、{ge::DT_FLOAT, ge::DT_INT32}数据类型集合、FORMAT_ND数据格式并通过.AutoContiguous()开启内存自动连续化。3.1 数据类型约束的底层校验tiling 阶段会对数据类型做二次校验。add_example_tiling.cpp 中定义了supportedDtype {ge::DT_FLOAT, ge::DT_INT32}若输入数据类型不在集合内则直接返回GRAPH_FAILED。同时根据数据类型选择不同的 tiling key 分支见下文源码剖析。3.2 数据格式说明本算子仅支持 NDN 维通用格式。在 GE 图模式调用中test_geir_add_example.cpp 中构造TensorDesc时同样显式指定FORMAT_ND并调用SetFormat(FORMAT_ND)与算子定义保持一致。四、约束说明AddExample 算子无额外约束。从实现层面看唯一的限制来自 tiling 阶段对输入 shape 维度的校验add_example_tiling.cpp 要求 x1、x2、y 的 shape 维度均为 4 维DIMS_LIMIT 4不满足时返回GRAPH_FAILED。因此实际使用中建议将输入规范为 4 维张量例如示例中的{32, 4, 4, 4}标量输入会被EnsureNotScalar转换为{1}形状以保持处理一致性见 add_example_tiling.cpp。五、调用说明总览AddExample 提供两种调用方式调用样例与说明如下调用方式调用样例说明aclnn 调用test_aclnn_add_example参见算子调用完成算子编译和验证图模式调用test_geir_add_example参见算子调用完成算子编译和验证两种方式各有适用场景aclnn 调用通过 Host 侧 C 语言 API 直接驱动算子无需提供算子 IR 定义GE 图模式则基于算子 IRIntermediate Representation以构图方式调用。下文分别给出完整实战方案。六、源码剖析AddExample 的完整实现链路在动手调用之前先理清 AddExample 在仓库中的实现结构这对理解算子编译与运行机制至关重要。仓库中 AddExample 的源码组织如下examples/add_example/ ├── examples/ # 调用样例 │ ├── test_aclnn_add_example.cpp # aclnn 调用样例 │ └── test_geir_add_example.cpp # GE 图模式调用样例 ├── op_graph/ # 图模式相关proto 定义、shape 推理 ├── op_host/ # Host 侧实现 │ ├── add_example_def.cpp # 算子定义输入输出规格、AI Core 配置 │ ├── add_example_infershape.cpp # shape / 数据类型推理 │ └── add_example_tiling.cpp # tiling 分块策略 ├── op_kernel/ # 算子 Kernel 实现AI Core 侧 │ ├── add_example.cpp # kernel 入口 │ ├── add_example.h # 向量逐元素相加实现 │ ├── add_example_tiling_data.h # tiling 数据结构 │ └── add_example_tiling_key.h # tiling key 定义 ├── tests/ # 单测infershape/tiling/kernel ├── CMakeLists.txt └── README.md6.1 算子定义层OpDefadd_example_def.cpp 通过OpDef子类声明算子的接口规格并注册 AI Core 编译配置输入 x1、x2 与输出 y 均声明为必选REQUIRED支持DT_FLOAT、DT_INT32格式为FORMAT_ND且开启AutoContiguousAI Core 配置开启DynamicCompileStaticFlag(true)静态动态编译、DynamicRankSupportFlag(true)动态 rank 支持、DynamicShapeSupportFlag(true)动态 shape 支持、PrecisionReduceFlag(true)精度降低使能并通过ExtendCfgInfo(opFile.value, add_example)指定 kernel 入口文件名最后通过OP_ADD(AddExample)将算子注册到算子信息库。6.2 shape 与数据类型推理层add_example_infershape.cpp 实现推理逻辑InferShapeAddExample从 context 获取输入 x 的 shape将维度数GetDimNum和每个维度的大小GetDim逐一复制到输出 y 的 shape 上即输出 shape 与输入 shape 完全相同InferDataTypeAddExample直接取输入 x1 的数据类型并赋给输出 y即输出数据类型与输入一致通过IMPL_OP_INFERSHAPE(AddExample)注册到系统。6.3 tiling 分块策略层add_example_tiling.cpp 实现 Host 侧的分块策略是 AddExample 性能的关键GetPlatformInfo查询平台信息获取 AI Core 数量GetCoreNumAiv与 UB统一缓冲区大小GetCoreMemSize并校验二者非 0GetShapeAttrsInfo校验输入输出 shape 均为 4 维数据类型在{DT_FLOAT, DT_INT32}内并计算总元素数totalIdxGetWorkspaceSizeAddExample 无需额外 workspace大小固定为 0核心 tiling 计算核间切分优先使用更多核并行blockFactor CeilDiv(totalIdx, coreNum)即每个 AI Core 处理的元素数量核内 UB 切分ubFactor FloorAlign(FloorDiv((ubCanUse / TYPE_SIZE), BUFFER_NUM), ubBlockSize)其中BUFFER_NUM 6对应“2 输入 1 输出”在使能 double buffer 场景下共需的 6 块 UB tensor通过SetBlockDim(usedCoreNum)设置实际使用的核数tiling key 分发FLOAT 类型走ELEMENTWISE_TPL_SCH_MODE_0INT32 类型走ELEMENTWISE_TPL_SCH_MODE_1为不同数据类型选择不同 kernel 实现分支。tiling 数据通过 add_example_tiling_data.h 中定义的AddExampleTilingData结构体包含totalNum、blockFactor、ubFactor三个字段从 Host 侧传递到 Kernel 侧。6.4 Kernel 实现层AI Core 侧add_example.cpp 是 kernel 入口函数根据模板参数schMode对应 tiling key在编译期分派到不同数据类型的实现schMode 0实例化NsAddExample::AddExamplefloatschMode 1实例化NsAddExample::AddExampleint32_t实际计算逻辑在 add_example.h 中采用经典的CopyIn → Compute → CopyOut流水线模式Init根据 tiling 数据计算当前核负责的元素范围blockLength_与 UB 单次处理量ubLength_并通过SetGlobalBuffer将 GM全局内存指针按blockFactor * GetBlockIdx()偏移到当前核的起始位置CopyIn通过DataCopyPad将 GM 数据搬入VECIN队列的 LocalTensorCompute调用 AscendC 向量指令AscendC::Add(zLocal, xLocal, yLocal, currentNum)完成逐元素相加CopyOut将计算结果从VECOUT队列搬回 GM 输出地址Process按ubLength_分片循环处理直到覆盖blockLength_全部数据末片处理余数currentNum。队列均采用BUFFER_NUM 2的双缓冲设计使数据搬运与计算可以重叠执行最大化 AI Core 利用率。七、方式一aclnn API 调用aclnn 调用面向算子提供对应的 C 语言 API前缀为aclnn无需提供 IR 定义调用流程如下图所示aclnn 的调用本质上是两段式接口第一段aclnnAddExampleGetWorkspaceSize计算所需的 workspace 大小并创建 executor第二段aclnnAddExample真正下发执行。完整代码参见 test_aclnn_add_example.cpp其核心骨架如下#include acl/acl.h #include aclnn_add_example.h int main() { // 1. 调用acl进行device/stream初始化 int32_t deviceId 0; aclrtStream stream; auto ret Init(deviceId, stream); // 2. 构造输入与输出此处按 API 接口自定义构造 aclTensor* selfX nullptr; void* selfXDeviceAddr nullptr; std::vectorint64_t selfXShape {32, 4, 4, 4}; std::vectorfloat selfXHostData(2048, 1); ret CreateAclTensor(selfXHostData, selfXShape, selfXDeviceAddr, aclDataType::ACL_FLOAT, selfX); aclTensor* selfY nullptr; // 同理构造 y aclTensor* out nullptr; // 同理构造输出 // 3. 调用第一段接口计算workspace大小并获取executor uint64_t workspaceSize 0; aclOpExecutor* executor; ret aclnnAddExampleGetWorkspaceSize(selfX, selfY, out, workspaceSize, executor); // 根据workspaceSize申请device内存 void* workspaceAddr nullptr; if (workspaceSize static_castuint64_t(0)) { ret aclrtMalloc(workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); } // 4. 调用第二段接口执行算子 ret aclnnAddExample(workspaceAddr, workspaceSize, executor, stream); // 5. 固定写法同步等待任务执行结束 ret aclrtSynchronizeStream(stream); // 6. 获取输出值device - host 拷贝并打印 PrintOutResult(outShape, outDeviceAddr, selfXHostData, selfYHostData); // 7. 释放aclTensor与device资源acl去初始化 aclDestroyTensor(selfX); aclDestroyTensor(selfY); aclDestroyTensor(out); aclrtFree(selfXDeviceAddr); aclrtFree(selfYDeviceAddr); aclrtFree(outDeviceAddr); aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例中CreateAclTensor负责申请 device 侧内存aclrtMalloc、将 Host 数据拷入aclrtMemcpy方向ACL_MEMCPY_HOST_TO_DEVICE、计算连续张量的 strides最后通过aclCreateTensor创建aclTensor对象数据格式为ACL_FORMAT_ND。7.1 aclnn 调用的编译工程在业务应用集成场景下需要在调用脚本同级目录创建 CMakeLists.txt 与 run.sh。由于 AddExample 属于自定义算子应依赖自定义算子包libcust_opapi.so编译关键配置如下完整模板参见 算子调用cmake_minimum_required(VERSION 3.14) project(ACLNN_EXAMPLE) add_compile_options(-stdc11) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ./bin) # 添加可执行文件替换为实际调用算子的*.cpp文件 add_executable(${test_aclnn_op_name} ${test_aclnn_op_name}.cpp) # ASCEND_PATH如遇CANN包路径有误请根据实际路径修改 if(NOT $ENV{ASCEND_HOME_PATH} STREQUAL ) set(ASCEND_PATH $ENV{ASCEND_HOME_PATH}) else() set(ASCEND_PATH /usr/local/Ascend/cann) endif() # 获取自定义算子包名称存在多个自定义算子包时只会使用其中一个 set(VENDORS_DIR ${ASCEND_PATH}/opp/vendors) file(GLOB CUSTOM_DIRS ${VENDORS_DIR}/*) foreach(CUSTOM_DIR ${CUSTOM_DIRS}) if(IS_DIRECTORY ${CUSTOM_DIR}) set(TARGET_SUBDIR ${CUSTOM_DIR}) endif() endforeach() include_directories(${ASCEND_PATH}/include ${TARGET_SUBDIR}/op_api/include) target_link_libraries(${test_aclnn_op_name} PRIVATE ${ASCEND_PATH}/lib64/libascendcl.so ${ASCEND_PATH}/lib64/libnnopbase.so ${TARGET_SUBDIR}/op_api/lib/libcust_opapi.so # 链接自定义算子库文件 )run.sh 脚本负责环境变量加载、cmake 构建与执行if [ -n $ASCEND_INSTALL_PATH ]; then _ASCEND_INSTALL_PATH$ASCEND_INSTALL_PATH elif [ -n $ASCEND_HOME_PATH ]; then _ASCEND_INSTALL_PATH$ASCEND_HOME_PATH else _ASCEND_INSTALL_PATH/usr/local/Ascend/cann fi source ${_ASCEND_INSTALL_PATH}/bin/setenv.bash rm -rf build mkdir -p build cd build cmake ../ -DCMAKE_CXX_COMPILERg -DCMAKE_SKIP_RPATHTRUE make cd bin ./${test_aclnn_op_name} # 替换为实际算子可执行文件名执行bash run.sh后默认在当前执行路径/build/bin下生成可执行文件。示例运行结果如下mean result[2046] is 2.000000 mean result[2047] is 2.000000由于示例中两个输入均以值 1 填充shape 为{32, 4, 4, 4}共 2048 个元素输出每个元素为 1 1 2与预期一致。八、方式二GE 图模式调用GE 图模式基于算子 IRIntermediate Representation定义以构图方式调用算子流程如下图所示完整样例参见 test_geir_add_example.cpp核心流程如下#include graph.h #include ge_api.h #include array_ops.h #include ../op_graph/add_example_proto.h using namespace ge; int main() { // 1. 创建图对象 Graph graph(tc_ge_irrun_test); // 2. 图全局编译选项初始化 std::mapAscendString, AscendString global_options { {ge.exec.deviceId, 0}, {ge.graphRunMode, 1}}; Status ret ge::GEInitialize(global_options); // 3. 创建AddExample算子实例由 add_example_proto.h 提供的 IR 定义生成 auto add1 op::AddExample(add1); // 4. 定义图输入输出向量 std::vectorOperator inputs{}; std::vectorOperator outputs{}; // 5. 准备输入数据宏展开方式处理变量赋值 std::vectorint64_t xShape {32, 4, 4, 4}; ADD_INPUT(1, x1, inDtype, xShape); ADD_INPUT(2, x2, inDtype, xShape); ADD_OUTPUT(1, y, inDtype, xShape); outputs.push_back(add1); // 6. 设置图对象的输入算子和输出算子 graph.SetInputs(inputs).SetOutputs(outputs); // 7. 创建session对象 ge::Session* session new Session(build_options); // 8. session添加图 ret session-AddGraph(graphId, graph, graph_options); // 9. 运行图 ret session-RunGraph(graph_id, input, output); // 10. 释放资源 GEFinalize(); return 0; }8.1 图模式的关键实现细节输入数据生成ADD_INPUT宏通过op::Data创建占位算子GenOnesData按指定 dtype 生成全 1 数据示例中 value 为 2并调用update_input_desc_x更新输入描述、graph.AddOp将占位算子加入图输出描述ADD_OUTPUT宏通过add1.update_output_desc_y(...)为算子输出设置 shape 与 dtype 描述结果落盘RunGraph执行后样例将输入输出分别写入tc_ge_irrun_test_0008_npu_input_{i}.bin与tc_ge_irrun_test_0008_npu_output_{i}.bin文件见 test_geir_add_example.cpp便于离线比对精度图 dumpaclgrphDumpGraph(graph, ./dump, ...)可将图结构 dump 成文本文件便于排查构图问题。8.2 图模式的编译工程GE 图引擎会根据配置好的环境变量自动加载已安装的算子包库文件因此无需区分自定义算子包或内置算子包。关键 CMake 配置完整模板参见 算子调用cmake_minimum_required(VERSION 3.14) project(GE_IR_EXAMPLE) if(NOT $ENV{ASCEND_OPP_PATH} STREQUAL ) get_filename_component(ASCEND_PATH $ENV{ASCEND_OPP_PATH} DIRECTORY) elseif(NOT $ENV{ASCEND_HOME_PATH} STREQUAL ) set(ASCEND_PATH $ENV{ASCEND_HOME_PATH}) else() set(ASCEND_PATH /usr/local/Ascend/cann) endif() set(FWK_INCLUDE_DIR ${ASCEND_PATH}/compiler/include) add_executable(${test_geir_op_name} ${test_geir_op_name}.cpp) find_library(GRAPH_LIBRARY_DIR libgraph.so ${ASCEND_PATH}/compiler/lib64/stub) find_library(GE_RUNNER_LIBRARY_DIR libge_runner.so ${ASCEND_PATH}/compiler/lib64/stub) find_library(GRAPH_BASE_LIBRARY_DIR libgraph_base.so ${ASCEND_PATH}/compiler/lib64) target_link_libraries(${test_geir_op_name} PRIVATE ${GRAPH_LIBRARY_DIR} ${GE_RUNNER_LIBRARY_DIR} ${GRAPH_BASE_LIBRARY_DIR}) target_include_directories(${test_geir_op_name} PRIVATE ${FWK_INCLUDE_DIR}/graph/ ${FWK_INCLUDE_DIR}/ge/ ${ASCEND_PATH}/opp/built-in/op_proto/inc/ ${ASCEND_PATH}/compiler/include)运行bash run.sh后图模式样例成功执行时打印INFO - [XIR]: Finalize ir graph session success九、快速调用无需搭建工程一行命令跑通如果只是快速体验或验证 AddExample 的功能无需手动搭建编译工程。项目根目录的build.sh提供了--run_example参数可直接编译并运行算子样例。基于自定义算子包执行bash build.sh --run_example ${op} ${mode} ${pkg_mode} [--vendor_name${vendor_name}] [--soc${soc_version}] [--experimental] # 示例 bash build.sh --run_example add_example eager cust --vendor_namecustom${op}待执行算子名小写下划线形式此处为add_example${mode}调用方式eager表示 aclnn 调用graph表示图模式调用${pkg_mode}包模式目前仅支持cust自定义算子包${vendor_name}可选与构建的自定义算子包设置一致默认custom${soc_version}可选NPU 型号${experimental}可选执行 experimental 贡献目录下的算子AddExample 不属于该目录无需携带。说明${mode}为graph时无需指定${pkg_mode}和${vendor_name}。基于ops-cv 包执行适用于已合入标准包的算子bash build.sh --run_example ${op} ${mode} [--soc${soc_version}] bash build.sh --run_example add_example eager执行成功后终端会打印算子运行结果形式如下以 quick_op_invocation 中的 grid_sample 输出为例Start to run examples,name:add_example mode:eager Start compile and run examples file: examples/add_example/examples/test_aclnn_add_example.cpp pkg_mode:cust vendor_name:custom resultData[0] is: 2.000000 ...十、测试验证AddExample 在仓库中配套了完整的单元测试可用于验证 shape 推理、tiling 策略与 Kernel 计算正确性Host 侧测试test_add_example_infershape.cpp 与 test_add_example_tiling.cpp分别覆盖 shape/数据类型推理与 tiling 分块计算Kernel 侧测试test_add_example.cpp 通过 gen_data.py 生成输入数据运行 Kernel 后使用 compare_data.py 与标杆结果比对验证浮点与整型路径的计算精度。对于 Ascend 950PR 产品还可通过 Simulator 仿真工具执行算子样例详见仿真指导在无实体设备的环境下完成功能验证。十一、小结本文围绕 examples/add_example/README.md 展开完整梳理了 AddExample 算子的产品支持、功能公式、参数规格、约束条件与两种调用方式并结合仓库源码剖析了从算子定义OpDef、shape 推理InferShape、tiling 分块到 AI Core Kernel 实现的完整链路。AddExample 虽然功能简单却是一个“麻雀虽小、五脏俱全”的模板级算子掌握它的开发与调用流程后即可举一反三在 CANN ops-cv 中开发、验证和集成自己的图像处理与目标检测算子。【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考