【昇腾CANN训练营】深入cann-ops仓算子编译出包流程

单算子编译出包的疑惑

在昇腾CANN的cann-ops代码仓库中,quickstart.md文档为我们提供了算子编译执行的入门指引。核心操作是通过运行build.sh脚本来完成。如图1所示,脚本提供了编译构建的基本方法。

图1

初次尝试时,如果直接运行 bash build.sh 命令进行所有自定义算子 的完整编译,往往会面临一个现实问题:编译过程耗时非常长。笔者亲测时,就曾因等待时间远超预期而不得不中途放弃。对于日常专注于单个算子开发与调试的场景,这种全量编译的方式显然效率不高。

那么,更优的选择是什么呢?答案是进行单算子编译build.sh脚本提供了 -n 参数来实现这一目的。如图2所示,使用此参数可以指定仅编译特定的算子。

图2

让我们实际操作一下:执行命令 sh build.sh -n "whole_reduce_sum"。如图3所示,命令执行后,目标算子whole_reduce_sum成功完成了编译并生成了对应的算子部署包。这证明单算子编译的方法是可行且有效的。

图3

然而,细心的开发者可能会发现一个看似不太寻常的现象:当我们向上查看编译过程的输出日志时(图4),会发现除了我们指定的whole_reduce_sum算子外,编译信息中还夹杂着其他众多算子的相关内容。这不禁让人产生疑问:明明只指定了一个算子进行编译,为什么编译过程会涉及这么多"无关"信息呢?

图4

要解开这个疑惑,更清晰地理解整个编译流程究竟是如何运作的,我们需要从头梳理一下 build.sh脚本执行时的内在逻辑和关键步骤。

build.sh的执行流程

当我们执行 sh build.sh -n "whole_reduce_sum" 这条命令时,究竟发生了什么呢?让我们一步步拆解其执行脉络:

  1. 参数识别: 脚本捕捉到命令行中的 -n "whole_reduce_sum" 参数信息,将其提取并存入 ascend_op_name 变量中。这意味着它已经明确知晓:本次任务的核心是 whole_reduce_sum 这个特定算子。

  2. 环境准备: 紧接着,脚本调用 set_env 函数,布置所需的运行环境。这包括加载关键的 CANN 软件包的环境脚本,并确认 bisheng 编译工具已就绪可用。

  3. 清理场地: 为了确保构建过程不受旧数据干扰,clean 函数被调用。它移除之前可能遗留的构建目录 (build) 和输出目录 (output),然后为本次编译创建出干净、崭新的工作空间。

  4. 依赖安装: install_json_pkg 函数启动,它的职责是检查并确保 nlohmann_json 第三方库已妥善安装到项目指定的 third_party 目录下,为后续步骤扫清依赖障碍。

  5. 配置核心(关键步骤): 进入构建目录后,流程迎来了关键节点------调用 cmake_config 函数进行 CMake 配置。此时,脚本将我们指定的算子名称指令:-DASCEND_OP_NAME=whole_reduce_sum,作为CUSTOM_OPTION的一部分加入到 CMake 的配置选项中。这一步的意图非常明确:告诉构建系统,本次只需编译名为 whole_reduce_sum 的算子。 (图5)

    图5

  6. 执行构建: 一切准备就绪后,build_package 函数接过接力棒,它驱动 cmake --build 命令,启动针对 whole_reduce_sum 算子的编译过程。

从上述流程看,特别是第5步配置和第6步构建,脚本确实传达了"仅编译whole_reduce_sum"的指令。那么,为什么我们在编译输出日志中(图4)会看到其他算子的信息呢?这可能需要更深入地探查 CMake 内部的运作细节。

从脚本的输出信息中(图6),我们可以清晰地看到最终传递给 cmake 命令的所有选项:

图6

  • CUSTOM_OPTION / extra_option 的内容: (以.结束) -DBUILD_OPEN_PROJECT=ON -DASCEND_OP_NAME=whole_reduce_sum -DASCEND_THIRD_LIB_PATH=/home/mindspore/work/cann-ops/third_party -DCUSTOM_ASCEND_CANN_PACKAGE_PATH=/usr/local/Ascend/ascend-toolkit/latest -DCHECK_COMPATIBLE=false .
  • 从 CMakePresets.json 解析到的 opts 内容: -DCMAKE_BUILD_TYPE=Release -DENABLE_SOURCE_PACKAGE=True -DENABLE_BINARY_PACKAGE=True -DASCEND_COMPUTE_UNIT=ascend910b -DENABLE_TEST=True -Dvendor_name=customize -DASCEND_CANN_PACKAGE_PATH=/home/mindspore/Ascend/ascend-toolkit/latest -DASCEND_PYTHON_EXECUTABLE=python3 -DCMAKE_INSTALL_PREFIX=/home/mindspore/work/cann-ops/build_out -DENABLE_CROSS_COMPILE=False -DCMAKE_CROSS_PLATFORM_COMPILER=/usr/bin/aarch64-linux-gnu-g++

最终,这些选项被合并,形成了完整的 CMake 构建配置命令:

bash 复制代码
cmake .. \
  -DCMAKE_BUILD_TYPE=Release \
  -DENABLE_SOURCE_PACKAGE=True \
  -DENABLE_BINARY_PACKAGE=True \
  -DASCEND_COMPUTE_UNIT=ascend910b \
  -DENABLE_TEST=True \
  -Dvendor_name=customize \
  -DASCEND_CANN_PACKAGE_PATH=/home/mindspore/Ascend/ascend-toolkit/latest \
  -DASCEND_PYTHON_EXECUTABLE=python3 \
  -DCMAKE_INSTALL_PREFIX=/home/mindspore/work/cann-ops/build_out \
  -DENABLE_CROSS_COMPILE=False \
  -DCMAKE_CROSS_PLATFORM_COMPILER=/usr/bin/aarch64-linux-gnu-g++ \
  -DBUILD_OPEN_PROJECT=ON \
  -DASCEND_OP_NAME=whole_reduce_sum \  #算子名称
  -DASCEND_THIRD_LIB_PATH=/home/mindspore/work/cann-ops/third_party \
  -DCUSTOM_ASCEND_CANN_PACKAGE_PATH=/usr/local/Ascend/ascend-toolkit/latest \
  -DCHECK_COMPATIBLE=false

在这条冗长的命令中,我们清晰地看到了 -DASCEND_OP_NAME=whole_reduce_sum 这个选项,它确实被准确地传递给了 CMake。**这至少证明,脚本在意图传达"单算子编译"信息这个环节上,是没有问题的。**那么,问题的根源可能藏在 CMake 处理 ASCEND_OP_NAME 这个参数的方式,或者 CMake本身的构建逻辑之中。

CMake构建解析:疑点初现

现在我们需要深入项目的CMake构建描述体系。这套体系由主控文件 CMakeLists.txt 和三个关键支撑文件(config.cmakefunc.cmakeintf.cmake)共同驱动。让我们理清它们的工作脉络:

1. 构建规则总指挥:CMakeLists.txt 此文件主导全局构建流程:

  1. 项目初始化: 确立项目名称为 cann_ops_adv,定义 BUILD_OPEN_PROJECTENABLE_CCACHE 等开关。

  2. 环境配置: 设置默认参数,包括NPU计算单元(ascend910b)、默认算子名称(ALL 、厂商名称(customize)。这里有个关键细节ASCEND_OP_NAME 变量默认值虽是 ALL,但会被我们传入的 -DASCEND_OP_NAME=whole_reduce_sum 覆盖(图7)。

    图7

  3. 加载配置文件: 引入三个配置文件(config.cmakefunc.cmakeintf.cmake)。

  4. 第三方支持: 配置 nlohmann_json 库的集成。

  5. 目标定义: 定义核心库目标(如 op_host_aclnn、opapi、opsproto、optiling 、ops_kernelgenerate_ops_info等)。

  6. 算子扫描(关键环节): 调用 op_add_subdirectory 函数扫描算子目录------此处将决定哪些算子被处理

  7. ACLNN处理: 生成ACLNN相关源文件和头文件。

  8. 构建目标: 自定义构建目标(如 prepare_build、generate_compile_cmd、generate_ops_info等)。

  9. 部署与打包: 设置安装路径和打包参数。

2. 环境配置:config.cmake 此文件专注基础环境检查和配置:

  1. 环境检查: 验证 Python3 和 CANN 工具包的有效性。
  2. 全局开关: 设置影响全流程的构建开关(PREPARE_BUILDENABLE_OPS_HOST 等)。
  3. 路径配置: 定义源码和构建的关键路径。
  4. 参数调优: 根据构建类型调整编译参数。
  5. 编译加速: 启用 CCACHE 缓存加速。
  6. 版本适配: 检查与基础 CANN 的兼容性。
  7. 预处理(关键疑点): 执行 prepare.sh 脚本------这个步骤可能成为突破口。

3. 功能函数:func.cmake 定义多个CMake函数用于算子构建:

  1. 目录扫描 :通过op_add_subdirectory函数扫描算子目录
  2. 依赖处理 :通过op_add_depend_directory处理算子依赖
  3. 编译命令生成 :通过add_compile_cmd_target生成编译命令
  4. 算子信息处理 :通过add_ops_info_target生成算子信息
  5. 编译选项处理 :通过add_ops_compile_optionsadd_ops_tiling_keysadd_opc_config处理编译选项
  6. 源文件复制 :通过add_ops_src_copy处理源文件复制
  7. 二进制编译 :通过add_bin_compile_target处理二进制文件编译
  8. 静态算子处理 :通过add_static_ops处理静态算子
  9. NPU支持 :通过add_npu_support_target生成NPU支持配置

4. 接口文件:intf.cmake

  1. 条件加载: 根据 BUILD_OPEN_PROJECT 开关动态加载接口。
  2. 公共接口: 引入 intf_pub.cmake 定义基础配置。
  3. 测试接口: 按需加载测试相关接口。

5. 公共接口:intf_pub.cmake 定义所有目标共享的编译属性:

  1. 接口目标: 定义 intf_pub 接口库。
  2. 头文件路径: 设置 CANN 头文件包含目录。
  3. 链接配置: 指定库搜索路径和安全加固选项。
  4. 编译设置: 统一编译选项。

梳理完复杂的构建规则网络,一个核心矛盾 愈发清晰:CMakeLists.txt 开头明确定义 ASCEND_OP_NAME 变量已被我们的 -DASCEND_OP_NAME=whole_reduce_sum 覆盖(图7),理论上后续流程应仅处理 whole_reduce_sum 算子,但编译日志中的多算子信息证明事实并非如此。 经过反复验证,疑点指向了 config.cmake 中的预处理环节 ------ prepare.sh 脚本的执行 。这个在环境配置期间立即运行的脚本,可能正是导致 ASCEND_OP_NAME 设置被干扰的关键所在。它是否在CMake变量生效前就锁定了算子范围?(这个不太可能,ASCEND_OP_NAME在CMakeLists.txt的开头就进行了定义。)还是绕过了我们传入的算子名称直接进行操作?

prepare.sh:缺失的算子参数

当我们仔细审视执行 prepare.sh 脚本的完整命令时,发现一个关键问题:

bash 复制代码
bash /home/mindspore/work/cann-ops/cmake/scripts/prepare.sh \
    -s /home/mindspore/work/cann-ops \
    -b /home/mindspore/work/cann-ops/build/prepare_build \
    -p /usr/local/Ascend/ascend-toolkit/latest \
    --autogen-dir /home/mindspore/work/cann-ops/build/autogen \
    --build-open-project ON \
    --binary-out-dir /home/mindspore/work/cann-ops/build/binary \
    --impl-out-dir /home/mindspore/work/cann-ops/build/impl \
    --op-build-tool /usr/local/Ascend/ascend-toolkit/latest/tools/opbuild/op_build \
    --ascend-cmake-dir /usr/local/Ascend/ascend-toolkit/latest/tools/op_project_templates/ascendc/customize/cmake \
    --tiling-key FALSE \
    --ops-compile-options FALSE \
    --check-compatible false \
    --ascend-compute_unit ascend910b \
    --op_debug_config false \
    --third_lib_path /home/mindspore/work/cann-ops/third_party \
    RESULT_VARIABLE result \
    OUTPUT_STRIP_TRAILING_WHITESPACE \
    OUTPUT_VARIABLE PREPARE_BUILD_OUTPUT_VARIABLE

prepare.sh 的众多调用参数中,唯独缺少了我们最关心的 ASCEND_OP_NAME 参数! 用户调用时指定的 whole_reduce_sum 算子名称在这里完全消失了(图8)。

图8

那么,prepare.sh 究竟做了什么?原来,在 prepare.sh 脚本内部,它执行了一个嵌套的 CMake 配置和构建过程(图9):

图9

  1. build/prepare_build 目录下重新进行 CMake 配置
  2. 接着执行 make prepare_build 完成特定构建目标

嵌套 CMake 配置的完整命令:

bash 复制代码
cmake /home/mindspore/work/cann-ops \
  -DBUILD_OPEN_PROJECT=ON \
  -DPREPARE_BUILD=ON \  # 关键标记
  -DCUSTOM_ASCEND_CANN_PACKAGE_PATH=/usr/local/Ascend/ascend-toolkit/latest \
  -DASCEND_AUTOGEN_DIR=/home/mindspore/work/cann-ops/build/autogen \
  -DASCEND_BINARY_OUT_DIR=/home/mindspore/work/cann-ops/build/binary \
  -DASCEND_IMPL_OUT_DIR=/home/mindspore/work/cann-ops/build/impl \
  -DOP_BUILD_TOOL=/usr/local/Ascend/ascend-toolkit/latest/tools/opbuild/op_build \
  -DASCEND_CMAKE_DIR=/usr/local/Ascend/ascend-toolkit/latest/tools/op_project_templates/ascendc/customize/cmake \
  -DCHECK_COMPATIBLE=false \
  -DTILING_KEY=FALSE \
  -DOPS_COMPILE_OPTIONS=FALSE \
  -DASCEND_COMPUTE_UNIT=ascend910b \
  -DOP_DEBUG_CONFIG=false \
  -DASCEND_THIRD_LIB_PATH=/home/mindspore/work/cann-ops/third_party

作为下游,在这组配置参数中,自然也无从获取和传递 -DASCEND_OP_NAME=whole_reduce_sum 。这意味着在 prepare.sh 触发的这次嵌套构建中,ASCEND_OP_NAME 变量将使用其默认值------ ALL(图10)。

图10

问题全貌串联:

  1. 用户调用 sh build.sh -n "whole_reduce_sum",主构建流程中 CMake 正确接收了 -DASCEND_OP_NAME=whole_reduce_sum
  2. 主 CMakeLists.txt 引入 config.cmake
  3. config.cmake 调用 prepare.sh 脚本
  4. prepare.sh 未携带 ASCEND_OP_NAME 参数,启动嵌套 CMake 配置
  5. 嵌套配置中 ASCEND_OP_NAME 缺失,回退到默认值 ALL
  6. 嵌套构建在 ASCEND_OP_NAME=ALL 状态下执行,处理所有算子
  7. 主构建的输出日志流中,用户看到单算子编译却有多算子信息的"异常"现象

细心的读者可能担心:prepare.sh 内部又调用 CMake,而 CMake 会再次加载 config.cmake,是否会导致 prepare.sh 被无限递归调用?

答案是不会的(图11):

图11

  • 主构建中调用 prepare.sh 的条件是 (NOT PREPARE_BUILD AND ENABLE_OPS_KERNEL)
  • 嵌套构建 通过 -DPREPARE_BUILD=ON 明确标记了自身性质
  • 当嵌套构建的 CMake 加载到 config.cmake 时,因 PREPARE_BUILD=ON跳过了执行 prepare.sh 的条件分支
  • 这种设计避免了无限递归,但是通过prepare.sh进行嵌套构建的机制,使得嵌套构建未能继承主构建的算子参数

解决办法

"单算子编译出现多算子信息"的根本原因,在于 prepare.sh 脚本在发起嵌套构建时,未能将用户指定的 ASCEND_OP_NAME 参数传递给内部 CMake 配置。要解决这个问题,需要改造 config.cmake 和 prepare.sh,使其能够传递并接收 ASCEND_OP_NAME 参数。

一个更简单的临时办法是,直接修改CMakeLists.txt里面 ASCEND_OP_NAME 的值为用户传入的值,可以看到执行时间缩短到只有原来的1/3(图12)。

图12

相关推荐
一百天成为python专家1 小时前
数据可视化
开发语言·人工智能·python·机器学习·信息可视化·numpy
金井PRATHAMA1 小时前
主要分布在背侧海马体(dHPC)CA1区域(dCA1)的时空联合细胞对NLP中的深层语义分析的积极影响和启示
人工智能·神经网络·自然语言处理
说私域1 小时前
技术赋能与营销创新:开源链动2+1模式AI智能名片S2B2C商城小程序的流量转化路径研究
人工智能·小程序·开源
倒悬于世4 小时前
开源的语音合成大模型-Cosyvoice使用介绍
人工智能·python·语音识别
pk_xz1234565 小时前
光电二极管探测器电流信号处理与指令输出系统
人工智能·深度学习·数学建模·数据挖掘·信号处理·超分辨率重建
蓝蜂物联网5 小时前
边缘计算网关赋能智慧农业:物联网边缘计算的创新应用与实践
人工智能·物联网·边缘计算
酌沧5 小时前
AI图像编辑能力评测的8大测评集
人工智能
tanak6 小时前
2025年7月23日 AI 今日头条
人工智能·microsoft
爷_6 小时前
字节跳动震撼开源Coze平台!手把手教你本地搭建AI智能体开发环境
前端·人工智能·后端
格林威6 小时前
Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现持械检测(C#代码,UI界面版)
人工智能·深度学习·数码相机·yolo·计算机视觉