前言

你可以把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"
)

代码讲解

  1. find_package(AscendCL REQUIRED):找CANN的AscendCL包(头文件+库文件),找不到就报错
  2. add_library(matmul_op SHARED ...):编译成共享库(.so文件)
  3. target_include_directories:添加头文件搜索路径(AscendCL的头文件)
  4. target_link_libraries:链接AscendCL的库文件(libascendcl.so)
  5. set_target_properties:设置.so文件的输出路径

编译完成后,你会得到lib/libmatmul_op.so,这就是NPU可加载的算子库。

模块二:依赖管理(自动找到catlass、opbase等依赖)

CANN的算子依赖关系很复杂——ops-transformer依赖opbasecatlass依赖ops-nnops-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

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐