【AI】CUDA的新编程模型:tile编程模型 的好处

CUDA Tile 不是把"整个并行结构"都交给编译器,而是把 一个 thread block 内部的线程级并行映射交给编译器。

程序员仍然负责更高层的算法分块与数据映射。

可以把它理解为:

text 复制代码
SIMT:
程序员描述"每个线程做什么"

Tile:
程序员描述"整个 block 要对一块数据做什么"
编译器再决定 block 内的线程怎么合作完成

NVIDIA 官方的表述也是:Tile 程序在整个 thread block 的粒度描述对多维 tile 的操作,编译器将这些操作映射到 block 内部的具体硬件线程。程序员指定 grid,而每个 block 实际使用多少线程由编译器决定。(NVIDIA Docs)


1. SIMT 中,程序员管得非常细

传统 CUDA SIMT 的 vector add:

cpp 复制代码
__global__ void vectorAdd(
    const float* A,
    const float* B,
    float* C,
    int N)
{
    int i = blockIdx.x * blockDim.x + threadIdx.x;

    if (i < N) {
        C[i] = A[i] + B[i];
    }
}

程序员必须决定:

text 复制代码
一个 block 用多少线程?
threadIdx.x 对应哪个元素?
如何计算全局下标?
最后一个 block 越界怎么办?
访存能不能合并?
需不需要 shared memory?
哪个线程搬哪个数据?
什么时候 __syncthreads()?
每个 warp 会不会发生 divergence?

例如你写:

cpp 复制代码
vectorAdd<<<ceil(N / 256), 256>>>(...);

其中:

text 复制代码
256 threads/block

是你手工决定的。

再复杂一点,比如 GEMM,你还需要亲自写:

cpp 复制代码
__shared__ float As[16][16];
__shared__ float Bs[16][16];

As[threadIdx.y][threadIdx.x] = ...;
Bs[threadIdx.y][threadIdx.x] = ...;

__syncthreads();

for (int k = 0; k < 16; ++k) {
    acc += As[threadIdx.y][k]
         * Bs[k][threadIdx.x];
}

__syncthreads();

这里你不只是在描述矩阵乘法,还在手工描述:

  • 线程到数据的绑定;
  • global memory 到 shared memory 的搬运;
  • shared memory 布局;
  • block 内同步;
  • 每个线程的累加任务;
  • 每个 warp 的执行行为。

这就是传统 CUDA 性能高,但学习和优化难度也很高的原因。


2. Tile 模型把关注点提升一层

Tile 编程中,你写的是:

cpp 复制代码
auto aTile = load(...);
auto bTile = load(...);

auto cTile = aTile + bTile;

store(..., cTile);

或者 GEMM:

cpp 复制代码
auto aTile = load(A, ...);
auto bTile = load(B, ...);

acc = mma(aTile, bTile, acc);

store(C, acc, ...);

你的表达变成:

text 复制代码
加载一块 A
加载一块 B
对两块数据做矩阵乘加
写回一块 C

而不是:

text 复制代码
thread 0 搬 A[0]
thread 1 搬 A[1]
warp 0 做第一部分
warp 1 做第二部分
线程之间在这里同步

Tile API 提供 elementwise、矩阵乘、归约、reshape、transpose、类型转换等整块操作;编译器负责将这些 tile 操作映射到实际硬件线程,并在适用时映射到 Tensor Core 等硬件。(NVIDIA Docs)


3. 真正发生了什么职责转移

最准确的划分是下面这样。

决策 SIMT Tile
总数据如何切成多个 block 程序员 程序员
grid 有多少个 block 程序员 程序员
每个 block 处理哪块数据 程序员 程序员
tile 的形状 程序员 程序员
算法的循环和数据流 程序员 程序员
block 内多少线程 程序员 编译器
每个线程处理哪些元素 程序员 编译器
tile 操作如何分摊到 warp/thread 程序员 编译器
tile 放寄存器还是 shared memory 大量人工控制 主要由编译器决定
如何使用 Tensor Core/TMA 通常显式、复杂 可由 tile primitive 降低到硬件
是否需要精细线程控制 支持 通常被抽象掉

因此更精确的一句话是:

Tile 编程把 thread block 内部的 execution mapping 从程序员手里移交给编译器,但 workload 到 tile/block 的 mapping 仍然由程序员负责。


4. 它最重要的好处不只是"少写代码"

4.1 降低并行编程难度

传统 SIMT 程序中,很多代码其实不是算法本身,而是在描述硬件执行细节:

text 复制代码
threadIdx
warp lane
shared memory
barrier
vectorized load
bank conflict
coalescing

Tile 编程把它们隐藏在:

cpp 复制代码
load tile
tile operation
store tile

后面。

于是程序员可以更多关注:

text 复制代码
数据是什么形状?
应该切成多大的块?
这块数据要做什么运算?
多个 K tile 如何累加?

这恰好就是你学 AI accelerator mapping 时使用的抽象层次。


4.2 编译器更容易识别你的真实意图

SIMT 代码中,编译器看到的是大量标量线程操作:

cpp 复制代码
x = A[i];
y = B[i];
z = x + y;

它需要从很多线程的标量行为中推断:

text 复制代码
这些线程共同在执行一个向量加法

Tile 代码直接告诉编译器:

cpp 复制代码
cTile = aTile + bTile;

或者:

cpp 复制代码
acc = mma(aTile, bTile, acc);

也就是说,程序直接保留了更高层的运算语义。

从编译器角度看:

text 复制代码
SIMT IR:
很多线程级 load、multiply、add、store

Tile IR:
load tile → matrix multiply-accumulate → store tile

后者更容易针对具体 GPU 架构选择:

  • Tensor Core 指令;
  • Tensor Memory Accelerator;
  • 合适的线程数量;
  • 寄存器或 shared-memory 存放;
  • load、compute、store 的调度方式。

官方文档明确提到,Tile 的一个目标就是更简单地使用 TMA 和 Tensor Core 等较新的硬件性能能力;规则 tile-space load 在支持的硬件上可以被编译器降低为 TMA 操作。(NVIDIA Docs)


4.3 提高跨代 GPU 的可移植性

传统高性能 SIMT kernel 往往会绑定某一代硬件的细节:

text 复制代码
某代 GPU 的 warp 数量
某种 Tensor Core 指令形状
某种 shared-memory pipeline
某种寄存器组织
某种 TMA 指令

GPU 架构升级后,旧 kernel 虽然可能还能运行,但不一定仍是最佳映射。

Tile 编程的目标是让相同的 tile 级源码在不同 GPU 架构上,由编译器重新选择 block 内部的线程和硬件映射。NVIDIA 文档明确说,因为线程级决策留给编译器,相同 tile kernel 可以在不同 GPU 架构上运行,而不必修改源码。(NVIDIA Docs)

理想状态下是:

text 复制代码
相同源码:

A_tile @ B_tile

在较早 GPU 上
    ↓
编译成适合该架构的 Tensor Core / thread mapping

在较新 GPU 上
    ↓
编译成新的 Tensor Core + TMA mapping

这个思路类似于:

text 复制代码
C/C++ 程序员不再手工给每条指令分配 CPU pipeline
而是描述运算,让编译器生成机器指令

只不过 Tile 把这种抽象进一步推进到了 GPU block 内部的并行执行层。


4.4 简化同步和控制流推理

在传统 SIMT 中,一个 block 内有很多逻辑线程:

text 复制代码
thread 0
thread 1
...
thread 255

你必须考虑:

  • 哪个线程先执行;
  • 数据是否已经写入 shared memory;
  • 是否需要 __syncthreads()
  • 分支是否导致 warp divergence。

Tile 模型从程序员视角把每个 tile block 表示为一条逻辑控制流。整个 block 对 tile 操作,编译器将操作并行分配给实际线程。因此在 Tile 语言层面没有传统的 warp divergence 概念;标量操作由单个线程执行,tile 操作由 block 中的线程集体并行执行。(NVIDIA Docs)

例如:

cpp 复制代码
if (condition) {
    cTile = aTile + bTile;
}

你思考的是:

text 复制代码
整个 block 是否执行这个 tile 加法

而不是:

text 复制代码
warp 中哪几个 lane 进入 if
哪几个 lane 被 mask

这能显著减少并发正确性问题。


5. 但它绝不是"写一句矩阵乘,编译器什么都帮你做好"

这是最需要避免的误解。

程序员仍然需要决定:

5.1 Tile 大小

例如 GEMM:

text 复制代码
C tile = 128 × 128
A tile = 128 × 32
B tile = 32 × 128

还是:

text 复制代码
C tile = 64 × 128
A tile = 64 × 64
B tile = 64 × 128

不同 tile 大小仍然会影响:

  • 数据复用;
  • 寄存器压力;
  • shared-memory 消耗;
  • occupancy;
  • 边界浪费;
  • Tensor Core 利用率。

而且当前 Tile 模型要求 tile 的各个维度在编译期已知,并且是 2 的幂。(NVIDIA Docs)


5.2 Grid 如何覆盖数据

例如:

cpp 复制代码
int blocks_x = ceil_div(N, TILE_N);
int blocks_y = ceil_div(M, TILE_M);

kernel<<<dim3(blocks_x, blocks_y), 1>>>(...);

仍然是程序员决定:

text 复制代码
一个 block 对应 C 中哪个 tile
grid.x 映射 N
grid.y 映射 M

Tile 编译器不会自动把整个任意程序切成最优 grid。


5.3 算法和 dataflow

对于 GEMM:

cpp 复制代码
for (int kTile = 0; kTile < numKTiles; ++kTile) {
    auto a = load(A, ...);
    auto b = load(B, ...);
    acc = mma(a, b, acc);
}

这里依然是你决定:

text 复制代码
沿 K 维循环
每次加载多大的 A/B tile
partial sum 在 block 内累加
什么时候写回 C

也就是说:

编译器负责 tile 内的空间展开 ,程序员仍负责 tile 之间的时间与空间组织

这与你学 DNN accelerator mapper 非常相似:

text 复制代码
程序员/mapper:
选择 loop order、tile size、空间绑定

底层编译器:
把 tile primitive 映射到硬件执行单元

5.4 仍然需要性能提示

Tile 编程也不是完全不需要硬件知识。

官方文档仍建议在 C++ Tile kernel 中使用:

cpp 复制代码
__restrict__

以及:

cpp 复制代码
ct::assume_aligned(ptr, 16_ic)

帮助编译器确认数组不重叠、指针对齐,从而生成更好的内存操作。如果编译器无法证明指针不重叠,就必须采取更保守的 load/store 调度。(NVIDIA Docs)

这说明 Tile 的模式不是:

text 复制代码
程序员完全不需要管性能

而是:

text 复制代码
程序员提供高层结构、形状、布局和约束
编译器负责较低层的线程与硬件映射

6. 为什么 vectorAdd 体现不出 Tile 的主要价值

vector add:

text 复制代码
读 A
读 B
加一次
写 C

几乎没有:

  • 数据复用;
  • 矩阵乘;
  • 多级 reduction;
  • shared-memory blocking;
  • Tensor Core;
  • 复杂流水。

它通常主要受内存带宽限制。

所以把:

cpp 复制代码
C[i] = A[i] + B[i];

改成:

cpp 复制代码
cTile = aTile + bTile;

主要收益是代码表达更高层,不一定获得明显性能提升。

Tile 真正有吸引力的场景是:

text 复制代码
GEMM
Attention
卷积
Reduction
Transpose
Softmax
Norm
多维数据搬运
Tensor Core 计算
TMA 搬运

因为这些场景过去需要程序员亲自处理大量:

text 复制代码
warp/thread mapping
shared-memory tile
异步搬运
barrier
Tensor Core fragment
pipeline stage

Tile 模型试图把其中相当一部分交给编译器和内置 primitive。


7. 它和 Triton 的思想很接近

从编程思想上看,可以把它理解为:

text 复制代码
传统 CUDA SIMT:
线程中心编程

CUDA Tile / Triton 类模型:
数据块中心编程

传统 CUDA 问:

当前线程的 threadIdx.x 是多少?这个线程负责哪个元素?

Tile 模型问:

当前 block 对应哪个数据 tile?对整个 tile 执行什么操作?

例如:

text 复制代码
SIMT 心智模型:

thread 37
  读取 A[37]
  读取 B[37]
  计算 C[37]


Tile 心智模型:

block 3
  读取 A[384:512]
  读取 B[384:512]
  整块相加
  写回 C[384:512]

这是一种从:

text 复制代码
execution-centric
以执行线程为中心

转向:

text 复制代码
data-centric
以数据块和算子为中心

的变化。


8. 为什么 NVIDIA 现在才推这种模式

现代 GPU 的高性能路径越来越复杂:

text 复制代码
Tensor Core
TMA
异步 copy
多级 pipeline
warp specialization
cluster
distributed shared memory

如果每出现一种硬件能力,都要求普通 CUDA 程序员手写:

text 复制代码
新 intrinsic
新 barrier
新 shared-memory layout
新 warp 分工

那么:

  • 编程门槛会越来越高;
  • 代码会绑定某一代 GPU;
  • 软件跟不上硬件演进;
  • 同一个算子需要维护大量架构特化版本。

Tile 模型试图在程序员和 GPU 微架构之间增加一层:

text 复制代码
算法 / tile 表达
        ↓
CUDA Tile IR
        ↓
架构相关的编译优化
        ↓
threads / warps / Tensor Core / TMA

CUDA Tile C++ 和 cuTile Python 共用 CUDA Tile IR 后端,这也说明 NVIDIA 是在建立一个比传统 thread-level CUDA 更高层的编译表示。(NVIDIA Docs)


9. 它不会取代 SIMT

NVIDIA 明确说明 Tile 和 SIMT 会共存,选择是 per-kernel 的。Tile 不会替代 SIMT;当算法需要细粒度线程控制、特殊 warp 协作或某些高度定制优化时,SIMT 仍然必要。(NVIDIA Docs)

可以预期两种模式分别适合:

text 复制代码
Tile 更适合:
规则张量计算
矩阵运算
规则 reduction
结构化数据搬运
算子开发
快速适配新硬件

SIMT 更适合:
不规则图算法
复杂 per-thread 分支
精细 warp specialization
特殊同步协议
手工追求极限性能
不规则 scatter/gather

因此它不是:

text 复制代码
旧 CUDA 被新 CUDA 淘汰

而是:

text 复制代码
CUDA 增加了一个更高层的 kernel 编程入口

10. 一句话总结

你的理解基本正确,但可以再精确一点:

CUDA Tile 的主要价值,是让程序员从手工指定 block 内每个 thread/warp 如何搬数据、计算和同步中释放出来,改为描述 tile 级的数据与运算;编译器负责把 tile primitive 映射到具体线程、寄存器、shared memory、Tensor Core 和 TMA。

但程序员仍必须负责:

text 复制代码
workload 如何切 tile
tile 多大
grid 如何组织
数据布局是什么
tile 之间如何循环与复用
选择什么算法和 dataflow

所以不是"编译器替你做并行算法设计",而是:

text 复制代码
程序员设计并行算法与 tile mapping
        +
编译器完成 tile 内部的线程级实现

从 AI 芯片架构的角度,它相当于把编程接口从:

text 复制代码
直接编写 PE/线程的每拍行为

提升为:

text 复制代码
描述一个 compute tile 的输入、输出和算子
让 mapper/compiler 绑定到底层执行资源
相关推荐
Linguwen1 小时前
外贸GEO01|GEO是什么?生成式引擎优化,AI时代的新流量密码
人工智能
物质波波波1 小时前
WS-RPE:面向边缘物理AI实时特征值计算的硬件工作窃取调度器与冗余PE激活架构
人工智能·fpga开发·架构·系统架构·硬件架构
起个名字好难啊这也被占用了1 小时前
LangGraphjs可中断可恢复的AI工作流
人工智能·ai编程
@Mr_LiuYang2 小时前
状态栏动态上下文信息追加到Agent --《深入理解 AI Agent :设计原理与工程实践》实验2-8
人工智能·大模型·动态上下文·状态栏信息追加
碳基猿2 小时前
新媒体运营的终局:从“内容创作”走向“运营系统竞争”
人工智能·新媒体运营·产品运营·新媒体矩阵·多账号管理·矩阵分发·矩阵运营方法论
阿里云大数据AI技术2 小时前
阿里云 EMR Daft AI Function:用 DataFrame 表达式搞定大模型调用与多模态向量化
人工智能·spark
jerryinwuhan2 小时前
鱼苗投放检测系统设计
人工智能
阿里云大数据AI技术2 小时前
官宣|Apache Fluss 毕业成为顶级项目,湖流一体开启 Agentic Lake 全面实时化时代
人工智能·flink