从 ops-nn 到 cann-recipes-*几乎所有 CANN 开源仓库都用 CMake 做构建系统。cann-cmake 仓库提供一套标准的 CMake 模块——FindCANN.cmake找到 CANN 安装路径、AscendCCore.cmakeAscend C 编译规则、AscendKernel.cmakekernel 编译和链接——让开发者的 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.cmakeAscend 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_DIM32 -DMATMUL_TILE_M16 -DMATMUL_TILE_N16 ) # ascendc_add_library 的底层操作 # 1. 用 tikccTIK 编译器把 .cpp → .oAscend C → 二进制 # 2. 设置正确的目标架构--soc-versionascend910 # 3. 链接 runtime 和 driver 库 # 4. 生成适用于动态加载的 .so # 等价的手动命令ascendc_add_library 内部执行 # tikcc kernels/matmul_tiling.cpp \ # --targetascend910 \ # --opt-level3 \ # -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_SIZE32KB -DTILE_M16 COMPILE_FLAGS_ascend950 -DL1_CACHE_SIZE64KB # 更大的 L1 → 更大的 tile -DTILE_M32 ) # 生成的文件结构 # 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*kernelAscendRuntime::LoadKernel(my_ops_kernel);// runtime 内部// if (npu_arch Ascend910) → load ascend910/my_ops_kernel.so// if (npu_arch Ascend950) → load ascend950/my_ops_kernel.soops-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_DIM32 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_library的COMPILE_OPTIONS用-D传递编译时参数。但-DTILE_M16在 tikcc 里是TILE_M16——tikcc 的预处理宏有特殊语法不能和 GCC 混用。错误ascendc_add_library(my_ops STATIC kernels/matmul.cpp COMPILE_OPTIONS -DTILE_M16 -DTILE_N16 # GCC 风格的预定义宏 )tikcc 不接受空格式的-D——报unknown compiler flag。正确ascendc_add_library(my_ops STATIC kernels/matmul.cpp COMPILE_DEFINITIONS TILE_M16 TILE_N16 # ascenc_add_library 内部会根据这些 DEFINITIONS 生成 tikcc 风格的 # --defineTILE_M16 --defineTILE_N16 )踩坑三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 路径——标准化消除了这些重复劳动。