掘金 人工智能 07月26日 10:15
【昇腾CANN训练营】深入cann-ops仓算子编译出包流程
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

昇腾CANN算子编译过程中,使用build.sh脚本进行单算子编译时,会发现编译日志中出现大量非目标算子信息,引发疑惑。本文深入剖析了build.sh的执行流程,揭示了问题根源在于config.cmake中调用的prepare.sh脚本在执行嵌套CMake配置时,未能正确传递用户指定的ASCEND_OP_NAME参数,导致嵌套构建回退至默认的ALL模式,处理所有算子。文章详细梳理了CMakeLists.txt、config.cmake、func.cmake等关键文件间的交互逻辑,并最终指出解决办法是改造脚本以传递算子参数,或直接修改CMakeLists.txt中的默认值以实现单算子的高效编译。

📦 **单算子编译的困惑与初步验证**:在昇腾CANN的算子开发中,直接运行build.sh进行全量编译耗时过长,因此采用build.sh -n <算子名> 进行单算子编译。虽然指定了如"whole_reduce_sum"的算子,但编译日志中却出现了其他众多算子的信息,这与预期不符,需要深入探究原因。

⚙️ **build.sh执行流程解析与疑点定位**:文章详细分解了build.sh的执行过程,包括参数识别、环境准备、清理、依赖安装、CMake配置(传递-DASCEND_OP_NAME=whole_reduce_sum)及构建。尽管脚本在传递算子名称指令上无误,但日志中的多算子信息表明问题可能出在CMake内部处理或更早的预处理环节。

🛠️ **prepare.sh脚本的“失误”**:通过对CMakeLists.txt、config.cmake、func.cmake等文件的分析,发现config.cmake在环境配置阶段会调用prepare.sh脚本。更关键的是,prepare.sh在执行其内部嵌套的CMake配置时,并未携带用户指定的ASCEND_OP_NAME参数,导致嵌套构建时该参数回退为默认值ALL,从而处理了所有算子,造成了日志中的信息干扰。

💡 **解决之道:传递算子参数或修改默认值**:问题的根本在于prepare.sh未能将ASCEND_OP_NAME参数传递给嵌套的CMake配置。解决方案包括改造config.cmake和prepare.sh以实现参数的正确传递,或者采取一个更简便的临时措施——直接修改CMakeLists.txt中ASCEND_OP_NAME的默认值,使其与用户传入的参数一致,实测可将编译时间缩短至原来的1/3。

单算子编译出包的疑惑

在昇腾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

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

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此文件主导全局构建流程:

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

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

    图7

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

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

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

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

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

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

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

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

    环境检查: 验证 Python3 和 CANN 工具包的有效性。全局开关: 设置影响全流程的构建开关(PREPARE_BUILDENABLE_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_optionsadd_ops_tiling_keysadd_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 /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 配置的完整命令:

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.cmakeconfig.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 脚本在发起嵌套构建时,未能将用户指定的 ASCEND_OP_NAME 参数传递给内部 CMake 配置。要解决这个问题,需要改造 config.cmake 和 prepare.sh,使其能够传递并接收 ASCEND_OP_NAME 参数。

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

图12

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

昇腾CANN 算子编译 build.sh ASCEND_OP_NAME CMake
相关文章