单算子编译出包的疑惑
在昇腾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"
这条命令时,究竟发生了什么呢?让我们一步步拆解其执行脉络:
-
参数识别: 脚本捕捉到命令行中的
-n "whole_reduce_sum"
参数信息,将其提取并存入ascend_op_name
变量中。这意味着它已经明确知晓:本次任务的核心是whole_reduce_sum
这个特定算子。 -
环境准备: 紧接着,脚本调用
set_env
函数,布置所需的运行环境。这包括加载关键的 CANN 软件包的环境脚本,并确认bisheng
编译工具已就绪可用。 -
清理场地: 为了确保构建过程不受旧数据干扰,
clean
函数被调用。它移除之前可能遗留的构建目录 (build
) 和输出目录 (output
),然后为本次编译创建出干净、崭新的工作空间。 -
依赖安装:
install_json_pkg
函数启动,它的职责是检查并确保nlohmann_json
第三方库已妥善安装到项目指定的third_party
目录下,为后续步骤扫清依赖障碍。 -
配置核心(关键步骤): 进入构建目录后,流程迎来了关键节点------调用
cmake_config
函数进行 CMake 配置。此时,脚本将我们指定的算子名称指令:-DASCEND_OP_NAME=whole_reduce_sum
,作为CUSTOM_OPTION的一部分加入到 CMake 的配置选项中。这一步的意图非常明确:告诉构建系统,本次只需编译名为whole_reduce_sum
的算子。 (图5)图5
-
执行构建: 一切准备就绪后,
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.cmake
、func.cmake
、intf.cmake
)共同驱动。让我们理清它们的工作脉络:
1. 构建规则总指挥:CMakeLists.txt 此文件主导全局构建流程:
-
项目初始化: 确立项目名称为
cann_ops_adv
,定义BUILD_OPEN_PROJECT
、ENABLE_CCACHE
等开关。 -
环境配置: 设置默认参数,包括NPU计算单元(
ascend910b
)、默认算子名称(ALL
) 、厂商名称(customize
)。这里有个关键细节 :ASCEND_OP_NAME
变量默认值虽是ALL
,但会被我们传入的-DASCEND_OP_NAME=whole_reduce_sum
覆盖(图7)。图7
-
加载配置文件: 引入三个配置文件(
config.cmake
、func.cmake
、intf.cmake
)。 -
第三方支持: 配置
nlohmann_json
库的集成。 -
目标定义: 定义核心库目标(如
op_host_aclnn
、opapi、opsproto、optiling 、ops_kernel
、generate_ops_info
等)。 -
算子扫描(关键环节): 调用
op_add_subdirectory
函数扫描算子目录------此处将决定哪些算子被处理。 -
ACLNN处理: 生成ACLNN相关源文件和头文件。
-
构建目标: 自定义构建目标(如
prepare_build
、generate_compile_cmd、generate_ops_info等)。 -
部署与打包: 设置安装路径和打包参数。
2. 环境配置:config.cmake 此文件专注基础环境检查和配置:
- 环境检查: 验证 Python3 和 CANN 工具包的有效性。
- 全局开关: 设置影响全流程的构建开关(
PREPARE_BUILD
、ENABLE_OPS_HOST
等)。 - 路径配置: 定义源码和构建的关键路径。
- 参数调优: 根据构建类型调整编译参数。
- 编译加速: 启用
CCACHE
缓存加速。 - 版本适配: 检查与基础 CANN 的兼容性。
- 预处理(关键疑点): 执行
prepare.sh
脚本------这个步骤可能成为突破口。
3. 功能函数:func.cmake 定义多个CMake函数用于算子构建:
- 目录扫描 :通过
op_add_subdirectory
函数扫描算子目录 - 依赖处理 :通过
op_add_depend_directory
处理算子依赖 - 编译命令生成 :通过
add_compile_cmd_target
生成编译命令 - 算子信息处理 :通过
add_ops_info_target
生成算子信息 - 编译选项处理 :通过
add_ops_compile_options
、add_ops_tiling_keys
、add_opc_config
处理编译选项 - 源文件复制 :通过
add_ops_src_copy
处理源文件复制 - 二进制编译 :通过
add_bin_compile_target
处理二进制文件编译 - 静态算子处理 :通过
add_static_ops
处理静态算子 - NPU支持 :通过
add_npu_support_target
生成NPU支持配置
4. 接口文件:intf.cmake
- 条件加载: 根据
BUILD_OPEN_PROJECT
开关动态加载接口。 - 公共接口: 引入
intf_pub.cmake
定义基础配置。 - 测试接口: 按需加载测试相关接口。
5. 公共接口:intf_pub.cmake 定义所有目标共享的编译属性:
- 接口目标: 定义
intf_pub
接口库。 - 头文件路径: 设置 CANN 头文件包含目录。
- 链接配置: 指定库搜索路径和安全加固选项。
- 编译设置: 统一编译选项。
梳理完复杂的构建规则网络,一个核心矛盾 愈发清晰: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
- 在
build/prepare_build
目录下重新进行 CMake 配置 - 接着执行
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
问题全貌串联:
- 用户调用
sh build.sh -n "whole_reduce_sum"
,主构建流程中 CMake 正确接收了-DASCEND_OP_NAME=whole_reduce_sum
- 主 CMakeLists.txt 引入 config.cmake
- config.cmake 调用 prepare.sh 脚本
- prepare.sh 未携带 ASCEND_OP_NAME 参数,启动嵌套 CMake 配置
- 嵌套配置中
ASCEND_OP_NAME
缺失,回退到默认值 ALL - 嵌套构建在
ASCEND_OP_NAME=ALL
状态下执行,处理所有算子 - 主构建的输出日志流中,用户看到单算子编译却有多算子信息的"异常"现象
细心的读者可能担心: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