cmake:昇腾CANN构建系统完全指南
《CMake在昇腾CANN算子开发中的核心作用》摘要: CMake是昇腾CANN算子开发的核心构建系统,负责将Ascend C代码编译成NPU可执行的二进制文件。文章通过建筑工地塔吊的比喻,形象说明CMake不直接编译代码,而是调度整个编译过程。主要内容包括:1)CMake三大核心功能模块(算子编译、依赖管理、安装规则);2)详细演示如何通过CMakeLists.txt编译MatMul算子并生成.
前言
你可以把cmake想象成"建筑工地上的塔吊"——它不生产砖块(算子),但它决定砖块怎么摆放(编译顺序)、用哪辆卡车运输(链接库)、运到哪个楼层(安装路径)。
我刚接触CANN算子开发时,以为"写好Ascend C代码就行",结果一编译就报错——不是代码写错了,是CMakeLists.txt写错了。后来才明白,算子开发=30%写代码+70%写构建脚本。
cmake在CANN生态的位置
用一句话说:cmake是昇腾CANN的构建系统,负责把Ascend C代码编译成NPU能执行的二进制文件(.om文件)。
它属于第3层:昇腾计算编译层,跟BiSheng/ATC编译器搭档。
具体依赖关系:
你的Ascend C代码(.cpp)
↓
cmake(构建脚本解释器)
↓
BiSheng/ATC编译器(把C++代码编译成NPU机器码)
↓
.om文件(NPU可执行的二进制文件)
↓
runtime(加载.om文件到NPU执行)
关键点:cmake不编译代码,它只负责"调度编译过程"。就像塔吊不生产砖块,但决定砖块怎么摆放。
核心功能模块
模块一:算子编译(把Ascend C代码编译成.so)
这是cmake最核心的功能。你写的Ascend C代码(.cpp),需要通过cmake编译成共享库(.so文件),才能被AscendCL调用。
示例:编译一个MatMul算子的.so
# CMakeLists.txt(MatMul算子)
cmake_minimum_required(VERSION 3.16)
project(MatMulOp)
# 1. 找AscendCL包(提供头文件和库)
find_package(AscendCL REQUIRED)
# 2. 设置编译选项
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "-O2 -Wall")
# 3. 添加算子源文件
add_library(matmul_op SHARED
matmul_op.cpp # 你的Ascend C代码
)
# 4. 链接AscendCL库
target_include_directories(matmul_op PRIVATE ${AscendCL_INCLUDE_DIRS})
target_link_libraries(matmul_op PRIVATE ${AscendCL_LIBRARIES})
# 5. 设置输出路径
set_target_properties(matmul_op PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
)
代码讲解:
find_package(AscendCL REQUIRED):找CANN的AscendCL包(头文件+库文件),找不到就报错add_library(matmul_op SHARED ...):编译成共享库(.so文件)target_include_directories:添加头文件搜索路径(AscendCL的头文件)target_link_libraries:链接AscendCL的库文件(libascendcl.so)set_target_properties:设置.so文件的输出路径
编译完成后,你会得到lib/libmatmul_op.so,这就是NPU可加载的算子库。
模块二:依赖管理(自动找到catlass、opbase等依赖)
CANN的算子依赖关系很复杂——ops-transformer依赖opbase,catlass依赖ops-nn和ops-blas。
cmake自动帮你管理这些依赖,不用手动指定-I和-L路径。
示例:找到catlass依赖
# CMakeLists.txt(依赖catlass)
cmake_minimum_required(VERSION 3.16)
project(MyOperator)
# 1. 找catlass包
find_package(catlass REQUIRED)
# 2. 添加你的算子源文件
add_library(my_op SHARED my_op.cpp)
# 3. 链接catlass(自动找到头文件和库)
target_include_directories(my_op PRIVATE ${catlass_INCLUDE_DIRS})
target_link_libraries(my_op PRIVATE ${catlass_LIBRARIES})
关键点:find_package(catlass REQUIRED)会自动找到catlass的安装路径(在/usr/local/Ascend/catlass/下),不用你手动指定。
模块三:安装规则(把编译好的算子安装到系统路径)
编译完.so文件后,你需要把它安装到系统路径(比如/usr/local/lib/),这样AscendCL才能找到它。
cmake提供了install()指令,自动帮你做这件事。
示例:安装MatMul算子
# CMakeLists.txt(安装规则)
cmake_minimum_required(VERSION 3.16)
project(MatMulOp)
# ...(前面的编译逻辑)
# 安装规则
install(TARGETS matmul_op
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} # 安装.so到/usr/local/lib/
)
install(FILES matmul_op.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} # 安装头文件到/usr/local/include/
)
安装完后,你可以在系统中直接用这个算子:
# 安装
sudo make install
# 验证安装
ls /usr/local/lib/libmatmul_op.so # 应该存在
ls /usr/local/include/matmul_op.h # 应该存在
怎么用cmake编译你的算子
步骤1:写Ascend C代码
// matmul_op.cpp - MatMul算子的Ascend C实现
#include <ascendc/ascendc.h>
using namespace AscendC;
class MatMulOp {
public:
__device__ void Compute(LocalTensor<fp16> a, LocalTensor<fp16> b, LocalTensor<fp16> c) {
// 分块参数
int tile_m = 128;
int tile_n = 128;
int tile_k = 64;
// 双层循环,按块计算
for (int i = 0; i < a.GetShape()[0]; i += tile_m) {
for (int j = 0; j < b.GetShape()[1]; j += tile_n) {
// 加载A_tile和B_tile到片上内存
LocalTensor<fp16> a_tile = a.Slice(i, tile_m, 0, tile_k);
LocalTensor<fp16> b_tile = b.Slice(0, tile_k, j, tile_n);
// 调用MatMul指令
MatMul<a_tile, b_tile, c.Slice(i, tile_m, j, tile_n>>();
// 同步(等MatMul算完)
Sync();
}
}
}
};
// 算子入口
extern "C" __global__ void matmul_op(KernelHandle *handle, GM_ADDR a, GM_ADDR b, GM_ADDR c) {
MatMulOp op;
LocalTensor<fp16> a_local = ...; // 从HBM加载到片上内存
LocalTensor<fp16> b_local = ...;
LocalTensor<fp16> c_local = ...;
op.Compute(a_local, b_local, c_local);
}
步骤2:写CMakeLists.txt
参考前面的示例,写一个CMakeLists.txt。
步骤3:编译
# 创建build目录
mkdir build && cd build
# 运行cmake(生成Makefile)
cmake ..
# 编译(并行8线程)
make -j8
# 安装
sudo make install
⚠️ 踩坑预警:如果cmake ..报错Could NOT find AscendCL,说明CANN没装好或者setenv.sh没source。重新source一下:
source /usr/local/Ascend/ascend-toolkit/setenv.sh
步骤4:验证
# 验证.so文件是否存在
ls lib/libmatmul_op.so
# 写个测试程序
#include <ascendcl/ascendcl.h>
#include <matmul_op.h>
int main() {
// 初始化NPU
aclrtSetDevice(0);
// 加载算子
void *handle = dlopen("./lib/libmatmul_op.so", RTLD_NOW);
// ...(调用算子的逻辑)
// 释放NPU
aclrtResetDevice(0);
return 0;
}
踩坑实录
我在用cmake编译算子时,踩过这几个坑:
坑1:找不到AscendCL包
报错信息:
Could NOT find AscendCL (missing: AscendCL_INCLUDE_DIR AscendCL_LIBRARY)
原因:CANN没装好,或者setenv.sh没source。
解决方案:
# 重新source CANN环境
source /usr/local/Ascend/ascend-toolkit/setenv.sh
# 验证AscendCL包是否存在
ls /usr/local/Ascend/ascend-toolkit/latest/runtime/include/ascendcl.h
坑2:链接时找不到catlass库
报错信息:
/usr/bin/ld: cannot find -lcatlass
原因:find_package(catlass)没找到catlass的安装路径。
解决方案:手动指定catlass的路径:
# CMakeLists.txt
set(catlass_DIR "/usr/local/Ascend/catlass/cmake")
find_package(catlass REQUIRED)
坑3:编译出来的.so文件,runtime加载时报错
报错信息:
[ERROR] ACL runtime load operator failed: Invalid file format
原因:编译.so文件时,用的CANN版本跟runtime的CANN版本不一致。
解决方案:确保编译环境和运行环境的CANN版本一致:
# 查看编译环境的CANN版本
cat /usr/local/Ascend/ascend-toolkit/latest/version.info
# 查看运行环境的CANN版本
ssh worker-node "cat /usr/local/Ascend/ascend-toolkit/latest/version.info"
# 如果不一致,统一版本
结尾
cmake这个仓库,在昇腾CANN生态里的定位是**“算子开发者的构建系统”**。它不帮你写算子的核心逻辑,但它帮你把"编译+链接+安装"这件事自动化了。
我当初写第一个Ascend C算子时,没用cmake,手动写Makefile,编译脚本写了3天还一堆bug。后来用cmake,重写CMakeLists.txt只花了20分钟,编译一次通过。
如果你在搞算子开发,建议去 https://atomgit.com/cann/cmake 把这个仓库拉下来,先跑一把示例里的hello_world算子。光看文档是学不会cmake的,必须自己写一个CMakeLists.txt,看它怎么把你的.cpp变成.so,你才知道cmake的价值。
仓库:https://atomgit.com/cann/cmake
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐



所有评论(0)