从 ops-nn 到 cann-recipes-*,几乎所有 CANN 开源仓库都用 CMake 做构建系统。cann-cmake 仓库提供一套标准的 CMake 模块——FindCANN.cmake(找到 CANN 安装路径)、AscendCCore.cmake(Ascend C 编译规则)、AscendKernel.cmake(kernel 编译和链接)——让开发者的 CMakeLists.txt 从 200 行缩减到 20 行。

FindCANN.cmake:自动发现 CANN 安装

传统做法是手动设 ASCEND_HOME_PATH 环境变量,CMake 脚本里写死路径。cann-cmake 的 FindCANN 自动发现。

# CMakeLists.txt(最少配置)
cmake_minimum_required(VERSION 3.16)
project(my_ascend_op LANGUAGES CXX)

# 引入 CANN CMake 模块
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
include(FindCANN)

# FindCANN 自动做了这些事:
# 1. 搜索 $ASCEND_HOME_PATH 环境变量(用户自定义)
# 2. 搜索 /usr/local/Ascend(默认安装路径)
# 3. 设置 CANN_INCLUDE_DIRS(头文件路径)
# 4. 设置 CANN_LIBRARIES(库文件路径)
# 5. 检查 CANN 版本(通过 version.txt)
# 6. 如果找不到 → 报错 + 提示安装

message(STATUS "CANN version: ${CANN_VERSION}")
message(STATUS "CANN include: ${CANN_INCLUDE_DIRS}")
message(STATUS "CANN libs: ${CANN_LIBRARIES}")

# 版本检查(有些特性只在 8.0+ 可用)
if(CANN_VERSION VERSION_LESS "8.0.0")
    message(FATAL_ERROR "CANN 8.0+ required (flash attention and MC2)")
endif()

FindCANN 内部逻辑:

# FindCANN.cmake 核心实现
# 搜索优先级:$ASCEND_HOME_PATH > /usr/local/Ascend > pkg-config

find_path(CANN_ROOT_DIR
    NAMES version.cfg
    PATHS
        $ENV{ASCEND_HOME_PATH}
        /usr/local/Ascend
        /opt/Ascend
    PATH_SUFFIXES
        ascend-toolkit/latest
        ascend-toolkit/8.0.0
)

if(NOT CANN_ROOT_DIR)
    message(FATAL_ERROR
        "CANN not found. Install CANN toolkit or set ASCEND_HOME_PATH.\n"
        "Download: https://www.hiascend.com/software/cann")
endif()

# 提取版本信息
file(READ "${CANN_ROOT_DIR}/version.cfg" CANN_VER_STR)
string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" CANN_VERSION "${CANN_VER_STR}")

# 自动找到子目录
set(CANN_INCLUDE_DIRS
    "${CANN_ROOT_DIR}/include"
    "${CANN_ROOT_DIR}/include/ascendc"
    "${CANN_ROOT_DIR}/opp/built-in/op_proto/custom"  # 算子原型
)
set(CANN_LIBRARIES
    "${CANN_ROOT_DIR}/lib64/libascendcl.so"
    "${CANN_ROOT_DIR}/lib64/libge_executor.so"
    "${CANN_ROOT_DIR}/lib64/libascend_hal.so"
)

find_package_handle_standard_args(CANN
    REQUIRED_VARS CANN_ROOT_DIR CANN_INCLUDE_DIRS CANN_LIBRARIES
    VERSION_VAR CANN_VERSION
)

AscendCCore.cmake:Ascend C 源代码编译

Ascend C 的 kernel 代码(.cpp 文件,且内部用 Ascend C API)需要 TIKC 编译器编译,不是普通的 C++ 编译器。AscendCCore 封装了 tikcc 的调用规则。

# 引入 Ascend C 编译规则
include(AscendCCore)

# 定义 Ascend C kernel 源文件
ascendc_add_library(my_ops STATIC
    kernels/matmul_tiling.cpp
    kernels/softmax_fusion.cpp
    kernels/gelu_activation.cpp
    LINK_LIBRARIES
        ${CANN_LIBRARIES}
    COMPILE_OPTIONS
        -DBLOCK_DIM=32
        -DMATMUL_TILE_M=16
        -DMATMUL_TILE_N=16
)

# ascendc_add_library 的底层操作:
# 1. 用 tikcc(TIK 编译器)把 .cpp → .o(Ascend C → 二进制)
# 2. 设置正确的目标架构(--soc-version=ascend910)
# 3. 链接 runtime 和 driver 库
# 4. 生成适用于动态加载的 .so

# 等价的手动命令(ascendc_add_library 内部执行)
# tikcc kernels/matmul_tiling.cpp \
#     --target=ascend910 \
#     --opt-level=3 \
#     -I${CANN_INCLUDE_DIRS} \
#     -c -o matmul_tiling.o

关键:tikcc 是 CANN 的 Ascend C 编译器。它和 GCC 是不同的工具链——GCC 编译的是给 CPU 执行的代码,tikcc 编译的是给 NPU 执行的代码。ascendc_add_library 自动选择正确的编译器。

AscendKernel.cmake:多架构 kernel 编译

同一份 Ascend C 代码在不同 NPU 架构上需要不同的编译优化——Ascend 910 (达芬奇) 和 Ascend 950DT (下一代) 的 L1 缓存大小不同,tile 大小也不同。AscendKernel 支持多架构编译。

include(AscendKernel)

# 为多架构编译 kernel
ascend_multi_arch_kernel(my_ops_kernel
    SOURCES
        kernels/matmul.cpp
    ARCHITECTURES
        ascend910
        ascend950  # 可选:下一代架构编译
    COMPILE_FLAGS_ascend910
        -DL1_CACHE_SIZE=32KB
        -DTILE_M=16
    COMPILE_FLAGS_ascend950
        -DL1_CACHE_SIZE=64KB   # 更大的 L1 → 更大的 tile
        -DTILE_M=32
)

# 生成的文件结构:
# build/
# ├── ascend910/
# │   └── my_ops_kernel.so   (Architecture 910 optimized)
# ├── ascend950/
# │   └── my_ops_kernel.so   (Architecture 950 optimized)
# └── generic/
#     └── my_ops_kernel.so   (通用实现,动态选择)

运行时自动选择对应架构的 kernel:

// CANN runtime 自动选择对应硬件架构的 .so
// 不需要手动判断 NPU 型号
auto* kernel = AscendRuntime::LoadKernel("my_ops_kernel");
// runtime 内部:
// if (npu_arch == "Ascend910") → load ascend910/my_ops_kernel.so
// if (npu_arch == "Ascend950") → load ascend950/my_ops_kernel.so

ops-proto 自动生成

CANN 的算子库都有 ops-proto 目录——算子的 Protobuf 定义(输入输出描述)。cmake 仓库提供了 Protobuf 编译规则。

# 自动生成算子 Proto,不需要手动编译
include(AscendOpsProto)

ascend_ops_proto_generate(MY_OPS
    PROTO_FILES
        ops-proto/matmul.proto
        ops-proto/softmax.proto
        ops-proto/gelu.proto
    OUTPUT_DIR
        ${CMAKE_BINARY_DIR}/generated
)

# 生成的 C++ 文件:
# generated/matmul.pb.h  ← 包含 MatMulOp 的 Protobuf 结构
# generated/matmul.pb.cc
# generated/softmax.pb.h
# ...

# 链接到最终的 .so
target_link_libraries(my_ops PRIVATE MY_OPS_PROTO)

算子的 Proto 定义包含输入输出 tensor 的类型和形状信息——ge(图引擎)用这些信息做图优化。

完整项目的 CMakeLists.txt

组合 FindCANN + AscendCCore + AscendKernel + AscendOpsProto 四件套:

cmake_minimum_required(VERSION 3.16)
project(ascend-ops-project VERSION 1.0.0 LANGUAGES CXX)

# 引入 CANN CMake 模块(从 cann-cmake 仓库)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/cann-cmake")
include(FindCANN)
include(AscendCCore)
include(AscendKernel)
include(AscendOpsProto)

# 版本检查
if(CANN_VERSION VERSION_LESS "8.0.0")
    message(FATAL_ERROR "CANN 8.0+ required")
endif()

# 算子 Proto 生成
ascend_ops_proto_generate(OPS_PROTO
    PROTO_FILES
        ${CMAKE_CURRENT_SOURCE_DIR}/ops-proto/*.proto
    OUTPUT_DIR
        ${CMAKE_BINARY_DIR}/generated/proto
)

# Ascend C kernel 编译
ascendc_add_library(ops_kernels STATIC
    ${CMAKE_CURRENT_SOURCE_DIR}/kernels/*.cpp
    COMPILE_OPTIONS
        -DBLOCK_DIM=32
    LINK_LIBRARIES
        OPS_PROTO
        ${CANN_LIBRARIES}
)

# 多架构编译
ascend_multi_arch_kernel(ops_kernels_opt
    SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/kernels/matmul_tuned.cpp
    ARCHITECTURES
        ascend910
        ascend950DT  # Atlas A2 服务器
)

# 最终产物
add_executable(ops_test test/main.cpp)
target_link_libraries(ops_test PRIVATE ops_kernels ops_kernels_opt)

踩坑一:FindCANN 在新版安装路径下的版本文件路径

CANN 8.5 的安装路径变了:旧版 /usr/local/Ascend/ascend-toolkit/8.0.0/version.cfg,新版 /usr/local/Ascend/ascend-toolkit/latest/version.cfg。FindCANN.cmake 如果只搜固定路径,在新版 CANN 上会搜不到。

修复:在 FindCANN.cmake 里同时搜索 */latest/*/8.x.x/

# 同时搜索 latest 和带版本号的路径
find_path(CANN_ROOT_DIR
    NAMES version.cfg
    PATHS
        "${ASCEND_HOME}/ascend-toolkit/latest"
        "${ASCEND_HOME}/ascend-toolkit/8.5.0"
        "${ASCEND_HOME}/ascend-toolkit/8.0.0"
        "${ASCEND_HOME}/ascend-toolkit"
    DOC "Root directory of CANN toolkit"
)

踩坑二:Ascend C 编译的预处理器宏不展开

ascendc_add_libraryCOMPILE_OPTIONS-D 传递编译时参数。但 -DTILE_M=16 在 tikcc 里是 TILE_M=16——tikcc 的预处理宏有特殊语法(不能和 GCC 混用)。

错误

ascendc_add_library(my_ops STATIC
    kernels/matmul.cpp
    COMPILE_OPTIONS
        -DTILE_M=16 -DTILE_N=16  # GCC 风格的预定义宏
)

tikcc 不接受空格式的 -D——报 unknown compiler flag

正确

ascendc_add_library(my_ops STATIC
    kernels/matmul.cpp
    COMPILE_DEFINITIONS
        TILE_M=16
        TILE_N=16
        # ascenc_add_library 内部会根据这些 DEFINITIONS 生成 tikcc 风格的
        # --define=TILE_M=16 --define=TILE_N=16
)

踩坑三:Protobuf 编译依赖遗漏

ascend_ops_proto_generate 自动生成的 .pb.cc 文件需要链接 Protobuf 运行时库。但默认不链接——因为假设系统中已经全局安装了 libprotobuf。

修复:在 CMakeLists.txt 里显式加 find_package(Protobuf)

find_package(Protobuf REQUIRED)

ascend_ops_proto_generate(OPS_PROTO
    PROTO_FILES ${CMAKE_CURRENT_SOURCE_DIR}/ops-proto/*.proto
    OUTPUT_DIR ${CMAKE_BINARY_DIR}/generated/proto
)

target_link_libraries(ops_kernels PRIVATE
    OPS_PROTO
    protobuf::libprotobuf   # ← 必须显式链接
    ${CANN_LIBRARIES}
)

如果不加 protobuf::libprotobuf,链接时会报 undefined reference to google::protobuf::...


cann-cmake 的价值不在复杂的构建逻辑——在于标准化。所有 CANN 开源仓库用同一套 CMake 模块(FindCANN + AscendCCore + AscendOpProto),开发者的 CMakeLists.txt 写 20 行就够。对着镜像的 CANN 版本找不同的 FindCANN 路径——标准化消除了这些重复劳动。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐