跳到主要内容
昇腾AI芯片与算子开发

3.1 Ascend C 编程模型:核函数、Tiling 与 UB

掌握 Ascend C 的核心编程模型——核函数、Tiling 数据切分、Global Memory 与 Unified Buffer 的两级内存视图,理解昇腾算子编程与 CUDA 的本质差异

Ascend C 编程模型 Tiling 核函数

Ascend C 是昇腾的算子开发语言,可以理解成「面向昇腾 NPU 的 CUDA」。但它解决的不是「如何组织线程」,而是「如何组织数据流」。这一章建立 Ascend C 的三大核心心智模型:核函数、Tiling、两级内存视图。

📑 目录


1. Ascend C 到底是什么

Ascend C 是昇腾提供的、基于 C++ 的算子编程语言,开发者用它编写「在 AI Core 上运行的核函数」,并管理数据搬运、Tiling 与同步。

  • 对标 CUDA:都是「用高层语言写底层算子」。
  • 但范式不同:CUDA 是「线程 → 块 → 网格」的 SIMT 模型;Ascend C 是「核函数 + Tiling + 显式数据搬运」的数据流模型。
  • 由 CANN 工具链编译:Ascend C 源码经编译器生成 AI Core 可执行代码。

为什么昇腾不用「线程模型」?因为昇腾的计算单元是「专用化」的(Cube 管矩阵、Vector 管向量、Scalar 管控制),而且 AI Core 内部的计算依赖「数据先在片上 Buffer 就位」。线程模型适合「灵活调度一堆同质线程」,而昇腾的模型更像「编排一台流水线机器」。

2. 核心概念一:核函数(Kernel Function)

核函数(Kernel Function)是跑在 AI Core 上的计算函数,是 Ascend C 算子的执行主体。

关键认知:

  • 核函数用 __global__ 类似的关键字/宏标记(Ascend C 中通过特定关键字声明为核函数)。
  • 一个核函数在多个 AI Core 上并行执行(类似 CUDA 的 grid 跨 SM 并行)。
  • 核函数内部使用 Ascend C 提供的高级接口:数据搬运(DataCopy)、矢量计算(Add、Exp…)、矩阵计算(Matmul)等。
  • 核函数由 Host 侧发起调用(通过 ACLRT_LAUNCH_KERNEL 之类的机制下发),执行是异步的。
Host 侧(CPU)
   │  下发核函数 + Tiling 参数
   ▼
AI Core 侧:多核并行执行核函数
   │  DataCopy 搬数据 → 计算 → DataCopy 写回
   ▼
Host 侧:同步等待完成

3. 核心概念二:Tiling(数据切分)

Tiling 是把大张量切成能装进片上 Buffer(UB/L0)的小块,逐块计算的过程。

为什么必须 Tiling?因为:

  • HBM 里可能放着几 GB 的张量;
  • 而 UB 只有几十~几百 KB;
  • 所以一次只能搬一小块上来算,算完搬走,再来下一块。

Tiling 是昇腾算子开发里最需要动脑子的部分:

Tiling 维度要决策的事
块大小(Block)每次搬多少数据进 UB,是否恰好填满 UB
循环组织外层循环遍历所有块,内层处理单块
边界处理张量尺寸不整除块大小时,最后一块怎么补/怎么截
多核切分把整个张量分成几份分给不同的 AI Core

Tiling 参数通常在 Host 侧计算,再通过参数传给核函数——所以 Ascend C 算子代码里经常同时存在「Host 侧 Tiling 计算」和「Device 侧核函数」两部分。

4. 核心概念三:两级内存视图

Ascend C 的编程内存视图可以简化为两层:

  • Global Memory(GM):对应 HBM,容量大、访问慢。数据住在这里。
  • Unified Buffer(UB):AI Core 内的计算工作台,容量小、速度快。计算在这里发生。

数据流永远是:

GM ──DataCopy──▶ UB ──计算──▶ UB ──DataCopy──▶ GM

(矩阵计算则会经过 L0A/L0B/L0C,见 3.4。)

这条「先搬后算、算完搬回」的强制路径,是 Ascend C 与 CUDA 最大的体验差异。 CUDA 里写 a[i] + b[i] 编译器会处理缓存;Ascend C 里你得显式说「把数据搬到 UB,算,再搬回」。

5. 一个最小 Add 算子的骨架

先看整体骨架,细节在 3.2 展开:

// 核函数:在 AI Core 上执行
__global__ void addKernel(const __gm__ float *a,
                          const __gm__ float *b,
                          __gm__ float *c,
                          const int totalLen) {
    // 1. 确定本核处理的数据范围(多核切分)
    // 2. 循环处理 Tiling 后的每个 Block
    for (int i = 0; i < numBlocks; i++) {
        // 2.1 DataCopy: GM → UB(搬入 a、b 分块)
        // 2.2 矢量计算:Add(aUb, bUb, cUb, blockLen)   // 在 UB 上
        // 2.3 DataCopy: UB → GM(搬出结果)
    }
}

Host 侧部分负责:分配 GM 内存、计算 Tiling 参数、下发核函数、同步。

6. Ascend C 与 CUDA 的编程范式对比

维度CUDAAscend C
并行模型Thread/Block/Grid(SIMT)核函数 + 多 AI Core
最小执行单元Warp(32 线程)单核处理的数据块
数据搬运隐式缓存 + 可选手动共享内存强制显式 DataCopy
访存视图全局/共享/寄存器GM / UB / L0A/L0B/L0C
分块靠线程索引隐式划分显式 Tiling 参数
同步__syncthreads()队列/缓冲区同步(EnQue/DeQue)
典型代码结构一个 kernel 函数Host Tiling + Device 核函数

核心差异一句话:CUDA 让「线程」去拿数据,Ascend C 让「数据」被主动搬到位。

7. 学习路径建议

  1. 先吃透 3.2 的矢量 Add 算子——它能覆盖 Tiling、DataCopy、矢量计算、写回的全部要素,是昇腾算子开发的「Hello World」。
  2. 再做 Softmax 类规约算子——引入块内规约与更复杂的 Tiling。
  3. 再做 GEMM(3.4)——切换到矩阵编程,理解 Cube 的 Tiling 逻辑。
  4. 最后学习 msprof(3.5)——用数据说话,验证每一步优化。

总结

  • Ascend C 是昇腾的算子编程语言,三大核心心智:核函数、Tiling、两级内存视图(GM/UB)。
  • 数据流强制路径:GM → UB → 计算 → GM,搬运是显式且必不可少的。
  • Tiling 决定「数据怎么切、怎么循环」,是算子性能的最大杠杆。
  • 与 CUDA 同构但不相同:理解了「线程模型」到「数据流模型」的转变,Ascend C 就入门了。

延伸阅读