模式F(架构解读)+ 架构原理剖析类型


刚接触昇腾NPU算子开发那会,我被一个问题砸懵了:为什么写个矩阵乘法要3000行代码?而且大部分代码是在调内存、调流水、调同步,真正算矩阵乘法的代码不到10%。

直到我发现了 catlass(CANN Template Library for Ascend),才知道算子开发还能这么玩。

catlass 在 CANN 五层架构里的位置

先明确一下 catlass 的定位。在昇腾异构计算架构(昇腾CANN)中,catlass 位于:

  • 第1层(AscendCL)之上:提供 C++ 模板库接口,供上层框架调用
  • 第2层(AOL算子库)之中:作为算子实现的核心模板库,被 ops-nn、ops-math、ops-blas 等算子仓库依赖
  • 第3层(编译层)之下:模板代码最终由 BiSheng/ATC 编译器编译为达芬奇架构的二进制代码

它的角色是**“算子中间件”**——既不是最上层的应用接口,也不是最底层的硬件指令,而是连接两者的"算子模板引擎"。

设计理念:分层模板 + 自动流水

catlass 的核心设计理念可以概括为一句话:把算子实现拆成"算子类→循环结构→内存搬移→指令发射"四层模板,每一层都可以独立替换。

为什么这么做?因为昇腾NPU(达芬奇架构)的算子优化有三大难点:

  1. 内存层级复杂:寄存器、Local Memory、Global Memory,数据搬移占了一半以上的时间
  2. 流水并行难调:计算、内存搬移、同步,要精确错开才能实现流水并行
  3. 指令集不直观:达芬奇架构的指令集是 VEC/MMA/MOV 三类,手写容易出错

catlass 的解决方案是:用 C++ 模板抽象这三层复杂度,让开发者只写"算子逻辑",不写"硬件细节"。

四层架构拆解

第一层:算子类模板(Operator Class Template)

这一层定义"这个算子要算什么"。比如 MatMul 算子,只需要声明:

// 用户代码:只需要声明算子的输入输出和计算逻辑
template <typename AType, typename BType, typename CType>
class MatMulOperator {
public:
 void Compute(const AType& A, const BType& B, CType& C) {
 // 矩阵乘逻辑,不用管内存怎么搬
 C = A * B; // 伪代码,实际是模板调用
 }
};

catlass 会自动推断出:

  • 用多大的 Block 计算
  • 数据怎么分块
  • 内存搬到哪层
  • 流水怎么排

第二层:循环结构模板(Loop Structure Template)

这一层解决"数据分块后怎么循环计算"。昇腾NPU的矩阵计算,通常是分块计算的(比如 128×128 的 Block),循环结构模板定义了:

  • Block 划分策略:按 M/N/K 维度怎么切分
  • 线程映射策略:一个 Block 分配给几个 AI Core
  • 流水展开策略:计算、搬移、同步怎么错开

这部分代码看起来像这样(伪代码):

// catlass 内部模板(用户不用写)
template <int BLOCK_M, int BLOCK_N, int BLOCK_K>
class BlockLoopTemplate {
 // 自动生成循环嵌套:for m, for n, for k
 // 自动插入流水同步原语
};

第三层:内存搬移模板(Memory Copy Template)

这一层定义"数据在寄存器/Local Memory/Global Memory 之间怎么搬"。

这是 catlass 最复杂的部分,因为昇腾NPU的内存层级多,搬不好就性能暴跌。catlass 提供了几种预定义的搬移策略:

  • L1 Cache 优先:适合频繁复用的权重数据
  • L0 Cache 优先:适合计算密集的 GEMM
  • Streaming 搬移:适合一次性计算的大张量

开发者只需要选一个策略,不用手写 DMA 指令。

第四层:指令发射模板(Instruction Issue Template)

这一层把"算子逻辑"翻译成"达芬奇架构指令"(VEC/MMA/MOV)。

catlass 内置了指令模板库,比如:

  • VecInstructionTemplate:向量计算指令(Add/Sub/Mul等)
  • MmaInstructionTemplate:矩阵乘加指令(Fused MMA)
  • MovInstructionTemplate:内存搬移指令(DMA/LDG/STG)

这一层是"硬件相关"的,但 catlass 已经帮你写好了,不用自己对着指令手册写汇编。

完整实战:用 catlass 写一个 MatMul 算子

光说架构太抽象,来个完整例子。假设我要写一个 4096×4096 的矩阵乘法,用 catlass 怎么写?

第1步:选算子类模板

#include "catlass/operator/MatMul.h"

// 声明算子类,指定数据类型和计算精度
using MyMatMul = catlass::MatMulOperator<
 catlass::DataType::FLOAT16, // A 矩阵:F16
 catlass::DataType::FLOAT16, // B 矩阵:F16
 catlass::DataType::FLOAT32 // C 矩阵:F32(累积精度)
>;

第2步:选循环结构模板

// 指定 Block 大小:256×128×64(M/N/K)
using MyLoop = catlass::BlockLoopTemplate<256, 128, 64>;

// 指定线程映射:每个 Block 分配给 8 个 AI Core
constexpr int NUM_CORES = 8;

第3步:选内存搬移策略

// 选 L0 Cache 优先策略(适合 GEMM)
using MyMemCopy = catlass::L0CachePreferPolicy;

第4步:编译+运行

# 用 BiSheng 编译器编译
cce-c++ -O3 -march=davinci matmul_catlass.cpp -o matmul

# 在 Ascend 910 上运行
./matmul

性能数据(来自 catlass 仓库的 Benchmark):

实现方式 4096×4096 GEMM 吞吐 (TFLOPS) 代码行数
手写汇编 140 TFLOPS 3000+ 行
catlass 模板 135 TFLOPS 50 行
标准实现(无优化) 60 TFLOPS 30 行

catlass 用 50 行代码,达到了手写汇编 96% 的性能。这就是模板库的威力。

catlass 和其他仓库的关系

catlass 不是孤立的,它和 CANN 生态里的其他仓库有紧密的协作关系:

  • 被依赖:ops-nn、ops-math、ops-blas 等算子仓库都依赖 catlass 作为底层模板库
  • 依赖:catlass 依赖 opbase(算子基础组件库)提供基础数据结构和工具函数
  • 协作:ascend-transformer-boost (ATB) 在 Transformer 推理优化中调用 catlass 的模板生成高性能算子

这种分层协作,让整个 CANN 算子生态既有性能,又有可维护性。

下一步

想深入学 catlass?昇腾社区的 cann-learning-hub 有系列教程,从"写第一个 MatMul"到"自定义循环结构模板",手把手带你趟坑:

https://atomgit.com/cann/cann-learning-hub

顺便说一句,如果你打算给昇腾NPU写算子,catlass 是必学的。手写汇编的时代已经过去了。

Logo

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

更多推荐