编译过程调试
本文是扩展内容,介绍算子工程编译过程中的调试方法。算子工程编译涉及代码生成、Host侧编译、核函数(Kernel)侧编译、打包部署四个阶段,默认输出通常不足以定位根因,下文按阶段介绍调试手段,并附常见错误的诊断方法。
生成代码调试
npu_op_code_gen在cmake configure阶段自动生成aclnn调用代码和算子原型定义代码。生成产物位于ASCEND_AUTOGEN_PATH目录,默认是<CMAKE_BINARY_DIR>/autogen;按本文示例使用build_out时,对应目录为build_out/autogen。当原型定义有误或生成结果与预期不符时,需要检查这些文件。
生成产物目录
以AddCustom工程为例,配置完成后build_out/autogen/下会生成aclnn调用代码、算子原型定义代码和若干编译配置文件。下方仅列出排查代码生成问题时常用的关键产物;多算子工程会为每个算子生成对应的aclnn_<op>.cpp和aclnn_<op>.h。
build_out/autogen/
├── aclnn_add_custom.cpp // aclnn调用代码实现
├── aclnn_add_custom.h // aclnn调用代码头文件
├── op_proto.cc // 算子原型定义代码(入图用)
├── op_proto.h // 算子原型定义头文件
├── aic-<soc>-ops-info.ini // 指定芯片型号的算子信息
├── custom_compile_options.ini
├── custom_opc_options.ini
└── ...
常见问题排查场景
| 问题 | 查看什么 |
|---|---|
| aclnn接口参数与原型定义不匹配 | 检查aclnn_<op>.h中的函数签名,确认输入输出和属性参数是否正确映射。 |
| 入图场景算子注册信息异常 | 检查op_proto.cc中的注册宏,确认OpType、dtype/format是否与Host侧定义一致。 |
| 某个算子的调用代码未生成 | 检查npu_op_code_gen的SRC参数是否包含了该算子的Host侧源文件。 |
| JOIN_OP_DEF相关芯片型号异常 | JOIN_OP_DEF=True时算子构建阶段不再显式传入ASCEND_SOC_SERIES或ASCEND_COMPUTE_UNIT,需结合configure日志、op_proto.cc中的AddConfig和CMakePresets.json中的芯片型号确认目标型号是否被覆盖。 |
强制重新生成
修改文件后,如果怀疑历史产物残留导致问题,可以手动清理后重新configure:
rm -rf build_out/autogen
cmake -S . -B build_out --preset=default
Host侧编译调试
Host侧构建时,默认只输出编译目标的摘要信息,不显示实际执行的编译和链接命令。通过VERBOSE机制可以打印完整的命令行,用于排查头文件路径、链接库、编译选项等问题。
方式一:构建时开启verbose
在构建命令中指定verbose,打印每条编译和链接命令:
cmake --build build_out --target binary package --verbose -j$(nproc)
输出示例:
[ 30%] Building CXX object op_host/CMakeFiles/cust_opapi.dir/__/autogen/aclnn_add_custom.cpp.o
cd /data/asc-devkit/AddCustom/build_out/op_host && /usr/bin/c++ -D_FORTIFY_SOURCE=2 -D_GLIBCXX_USE_CXX11_ABI=0 -I/usr/local/Ascend/cann-9.1.0/include -fvisibility=hidden -fPIC -fvisibility-inlines-hidden -O2 -fstack-protector-strong -MD -MT op_host/CMakeFiles/cust_opapi.dir/__/autogen/aclnn_add_custom.cpp.o -MF CMakeFiles/cust_opapi.dir/__/autogen/aclnn_add_custom.cpp.o.d -o CMakeFiles/cust_opapi.dir/__/autogen/aclnn_add_custom.cpp.o -c /data/asc-devkit/AddCustom/build_out/autogen/aclnn_add_custom.cpp
每行输出包含完整的编译器调用,可以看到:
- 实际的编译器路径(如
/usr/bin/c++)。 - 宏定义(
-D)和头文件搜索路径(-I)。 - 编译选项(如
-fvisibility=hidden、-g、-O2)。 - 链接库路径(
-L)和链接库(-l)。
方式二:cmake配置CMAKE_VERBOSE_MAKEFILE
在cmake configure阶段开启verbose,后续所有make调用都会自动打印完整命令:
cmake -S . -B build_out --preset=default -DCMAKE_VERBOSE_MAKEFILE=ON
cmake --build build_out --target binary package -j$(nproc)
与方式一效果相同,区别在于设置持久化在CMakeCache.txt中,无需每次构建时重复指定。
常见问题排查场景
| 问题 | 查看什么 |
|---|---|
| 头文件找不到 | -I路径是否包含目标头文件所在目录。 |
| 链接undefined reference | -L和-l是否包含缺失符号所在的库。 |
核函数(Kernel)侧编译调试
核函数(Kernel)侧由编译框架为每个算子调用毕昇编译器,默认不保留中间产物。通过--save-temp-files选项可以保留每次编译的中间文件,用于排查预处理、编译、链接各阶段的问题。
开启方法
在核函数(Kernel)侧CMakeLists.txt中,通过npu_op_kernel_options添加--save-temp-files选项:
# 为所有算子开启
npu_op_kernel_options(ascendc_kernels ALL OPTIONS --save-temp-files)
# 仅对特定算子开启
npu_op_kernel_options(ascendc_kernels AddCustom OPTIONS --save-temp-files)
也可以同时添加-g生成调试信息,配合使用:
npu_op_kernel_options(ascendc_kernels ALL OPTIONS --save-temp-files -g)
保留的中间产物
开启后,编译框架会在构建目录下保留每个核函数(Kernel)编译过程的中间文件。例如本文在ascendxxyy上验证的AddCustom工程中,临时文件位于:
build_out/op_kernel/AddCustom_ascendxxyy/kernel_*/kernel_meta_<kernel_name>/kernel_meta/
实际目录名会随OpType、芯片型号和核函数(Kernel)名称变化。常见文件包括:
- 预处理文件(
.i):预处理后的源码,可用于确认宏展开是否正确、头文件是否被正确包含。 - 目标文件(
.o):链接前的目标文件,可用于确认符号是否正确导出。 - 编译日志(
.log):单个核函数(Kernel)的编译日志,可用于查看编译器调用和诊断信息。 - 生成源码(
*_kernel.cpp、*_tiling_data.h等):编译框架生成的核函数(Kernel)源码和Tiling数据结构文件。
常见问题排查场景
| 问题 | 查看什么 |
|---|---|
| 宏定义未生效 | 检查.i文件,确认宏是否被正确展开。 |
| 头文件包含缺失 | 检查.i文件,确认目标头文件的内容是否被引入。 |
| 链接符号缺失 | 检查.o文件的符号表(nm命令),确认目标符号是否存在。 |
部分芯片编译失败处理
当ASCEND_SOC_SERIES或ASCEND_COMPUTE_UNIT配置了多个芯片型号时,核函数(Kernel)编译会对每个芯片独立执行一轮。如果某个芯片的核函数(Kernel)编译失败,默认行为是终止整个构建流程。
ASCEND_SKIP_FAILED_COMPUTE_UNIT选项允许跳过单个芯片的编译失败,继续编译其余芯片。适用场景:
- 算子对某些芯片型号尚不完全适配,但其他型号已可正常使用。
npu_op_code_gen的JOIN_OP_DEF参数设为False时,用户通过compute_unit自行设置芯片型号,算子对其中部分型号缺少适配。
配置方法:在CMakeLists.txt中设置:
set(ASCEND_SKIP_FAILED_COMPUTE_UNIT TRUE)
或在cmake命令行中传入:
cmake -S . -B build_out --preset=default -DASCEND_SKIP_FAILED_COMPUTE_UNIT=TRUE
开启该选项后,部分AI处理器型号对应的算子编译失败时,编译流程可跳过失败项并继续编译其他型号;关闭时,遇到失败会影响其他型号的编译流程。若需要定位被跳过的失败项,建议同时配置ASCENDC_BUILD_LOG_DIR保存编译日志。
打包与部署调试
打包阶段将编译产物组织为.run安装包,部署阶段将算子包安装到目标环境。打包或安装出问题时,检查中间产物和打包命令即可定位。
查看打包命令
与Host侧调试一样,使用VERBOSE机制可以看到实际的打包命令:
cmake --build build_out --target package --verbose -j$(nproc)
输出示例:
Run CPack packaging tool...
/opt/buildtools/cmake-3.20.5-linux-aarch64/bin/cpack --config ./CPackConfig.cmake
可以看到cpack调用过程,用于排查打包配置问题。
检查编译产物目录
在打包或安装前,先检查编译产物目录,确认单算子API头文件、动态库、核函数(Kernel)二进制和Tiling库等是否已生成。不同编译方式下目录结构会有差异,下方仅列出排查时常用的关键路径。
TYPE RUN模式下,原始构建目录中主要检查中间产物:
build_out/
├── autogen/
│ ├── aclnn_add_custom.h
│ └── ...
├── op_host/
│ ├── libcust_opapi.so
│ ├── libcust_opmaster_rt2.0.so
│ ├── libcust_opsproto_rt2.0.so
│ └── ...
├── op_kernel/ascendc_kernels/binary/
│ ├── ascendxxyy/
│ │ └── add_custom/
│ ├── config/ascendxxyy/
│ ├── dynamic/
│ └── ...
└── ...
执行package目标后,CPack staging目录中会出现接近安装后形态的vendors/<vendor_name>/目录,可重点检查op_api/、op_impl/、op_proto/:
build_out/_CPack_Packages/Linux/External/custom_opp_*.run/packages/vendors/<vendor_name>/
├── op_api/
│ ├── include/aclnn_<op>.h
│ └── lib/libcust_opapi.so
├── op_impl/
│ ├── ai_core/tbe/
│ └── ...
├── op_proto/
│ ├── inc/op_proto.h
│ └── lib/linux/aarch64/libcust_opsproto_rt2.0.so
└── ...
TYPE SHARED或TYPE STATIC安装模式下,产物通常位于${CMAKE_INSTALL_PREFIX}/include和${CMAKE_INSTALL_PREFIX}/lib。
检查要点:
build_out/autogen/或staging目录的op_api/include/下是否生成aclnn_<op>.h。build_out/op_host/或staging目录的op_api/lib/下是否生成libcust_opapi.so。- staging目录或安装目录的
op_impl下是否包含目标芯片型号的核函数(Kernel)二进制和Tiling动态库。 - 如果使用SHARED/STATIC模式,
ENABLE_BINARY_PACKAGE必须为True。
安装后验证内容
安装.run包后,可在安装目录下检查部署结果。默认安装场景检查:
ls ${INSTALL_DIR}/opp/vendors/<vendor_name>/op_api
ls ${INSTALL_DIR}/opp/vendors/<vendor_name>/op_impl
ls ${INSTALL_DIR}/opp/vendors/<vendor_name>/op_proto # 可选
指定目录安装场景检查:
ls <path>/vendors/<vendor_name>/op_api
ls <path>/vendors/<vendor_name>/op_impl
ls <path>/vendors/<vendor_name>/op_proto # 可选
常见问题排查场景
| 问题 | 查看什么 |
|---|---|
package目标失败 | cmake --build build_out --target package --verbose输出中cpack的命令和报错信息。 |
| SHARED/STATIC模式下构建或安装报错 | npu_op_package CONFIG中ENABLE_BINARY_PACKAGE是否为True。 |
| INSTALL_PATH生成产物位置不对 | npu_op_package CONFIG中INSTALL_PATH配置是否指向预期目录。 |
| 安装后算子未被识别 | 指定目录安装时是否执行了source <path>/vendors/<vendor_name>/bin/set_env.bash,默认安装时opp/vendors/config.ini中优先级配置是否正确。 |
编译日志存盘
默认情况下,核函数(Kernel)侧编译过程中的中间日志不会保留到磁盘,编译失败时终端仅打印异常抛出信息。通过设置ASCENDC_BUILD_LOG_DIR环境变量,可将每个编译阶段的日志写入指定目录,便于事后回溯。
开启方法:在执行cmake或build.sh之前设置环境变量:
export ASCENDC_BUILD_LOG_DIR=/home/build_log/
编译框架会在其下按芯片型号创建子目录。例如编译ascendxxyy和ascendxxyy_2两个芯片时,日志目录结构为:
/home/build_log/
├── ascendxxyy/
└── ascendxxyy_2/
日志命名规则:开启日志存盘后,编译框架会根据编译结果调整日志文件后缀:
- 编译成功:对应日志文件后缀会添加
_success。 - 编译失败:终端会打印错误信息和对应日志文件路径,日志文件后缀会添加
_error。
从日志定位错误:
- 在终端报错中查看失败日志的具体路径,或在
ASCENDC_BUILD_LOG_DIR对应芯片子目录下搜索带_error后缀的日志文件。 - 打开失败日志,查看毕昇编译器输出的错误信息。
- 根据错误中的文件名、行号或API提示回到源码修正。
说明
开启ASCEND_SKIP_FAILED_COMPUTE_UNIT时,建议同时开启ASCENDC_BUILD_LOG_DIR,两者配合使用可以完整保留每个失败核函数(Kernel)的详细编译日志,便于逐个修复。