
故事的开始:英伟达这是要踩踏谁?
2025 年 12 月 5 日,NVIDIA 扔了一颗炸弹:CUDA 13.1 发布,带来了一个叫 CUDA Tile 的新玩意儿。
官方的原话是:
“This is the largest and most comprehensive update to the CUDA platform since it was invented two decades ago.”
(这是 CUDA 发明 20 年来最大、最全面的一次更新。)
翻译成人话就是:GPU 编程,要变天了。
作为一个写了多年 Python 的「臭编程的」,我一直觉得 CUDA 是"别人家的技术"——那是炼丹师和科学家的事,跟我这种 for 循环选手没半毛钱关系。
然而,NVIDIA 这次更新的核心理念是:
“Focus on your algorithm, CUDA Tile handles the hardware.”
(你只管写算法,硬件的事我来搞定。)
我:???这是在 cue 我?
于是我花了两天时间啃文档、跑 Demo,把这件事从"我不配"搞到了"就这?"——现在写成这篇文章,带你一起上车。
先搞清楚:CUDA 到底是个啥?为什么以前这么劝退?
如果你完全不了解 CUDA,30 秒速成:
| 概念 | 大白话解释 |
|---|---|
| GPU | 显卡,有几千个小核心,擅长同时做一堆简单任务 |
| CPU | 你的处理器,几个大核心,擅长处理复杂任务 |
| CUDA | NVIDIA 发明的一套"指挥 GPU 干活"的语言和工具 |
| Kernel | 你写给 GPU 跑的代码(不是操作系统那个内核) |
传统 CUDA 编程有多劝退?
传统 CUDA 用的是 SIMT(单指令多线程) 模型,意思是:
- 你得手动管理几千个线程
- 你得自己安排哪个线程干什么活
- 你得自己处理内存同步,否则数据打架
- 你得针对不同 GPU 型号做手动优化
- 你还得学 C/C++(Python 选手:告辞)
举个例子,传统 CUDA 写一个向量加法,代码大概长这样(C++):
__global__ void vectorAdd(float *a, float *b, float *c, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x; // 你得算这个鬼索引
if (i < n) { // 你得防越界
c[i] = a[i] + b[i];
}
}
// 调用时你还得算 block 和 thread 的数量
int blockSize = 256;
int numBlocks = (n + blockSize - 1) / blockSize; // 这啥?
vectorAdd<<<numBlocks, blockSize>>>(a, b, c, n); // 这又是啥语法??
Python 程序员看到这里:🙂 ➡️ 😐 ➡️ 🤯 ➡️ 🚪🏃
这就是为什么 CUDA 入门门槛这么高——你得同时理解硬件架构、并行计算、内存模型、C++ 语法……
重磅更新:CUDA Tile 来了,Python 选手的春天
CUDA 13.1 带来的 CUDA Tile 模型,核心思想是:
别再管线程了,你就操作"数据块"(Tile)就行,剩下的我来。
新旧模式对比
| 维度 | 传统 SIMT | 新 Tile 模型 |
|---|---|---|
| 编程单位 | 线程(Thread) | 数据块(Tile) |
| 心智负担 | 手动管理几千个线程 | 只需思考数据怎么切块 |
| 硬件优化 | 针对每代 GPU 手动调参 | 自动适配,跨代通用 |
| Tensor Core | 手动调用,非常复杂 | 自动利用,透明加速 |
| 语言支持 | C/C++ 为主 | Python 优先(cuTile) |
| 性能 | 手动优化才能拉满 | Blackwell GPU 上最高 4x 提升 |
划重点:Python 优先!自动优化!不用管硬件细节!

这意味着什么?意味着你只需要告诉 GPU:
“把这块数据切成 16x16 的小块,每块做个加法。”
至于怎么分配线程、怎么用 Tensor Core、怎么优化内存访问——CUDA 自己搞定。
来个 Hello World:Python 写 GPU 内核可以这么简单
安装环境
# 1. 确保你有 CUDA Toolkit 13.1+
# 从 https://developer.nvidia.com/cuda-toolkit 下载
# 2. Python 3.10+
# 3. 安装 cuTile
pip install cuda-tile
# 4. 安装依赖
pip install cupy-cuda12x numpy
第一个 Tile Kernel:向量加法
import cupy as cp
import numpy as np
import cuda.tile as ct
@ct.kernel
def vector_add(a, b, c, tile_size: ct.Constant[int]):
"""向量加法:c = a + b"""
# 获取当前 block 的 ID(就像"我是第几组")
pid = ct.bid(0)
# 从全局内存加载一个 tile(一小块数据)
a_tile = ct.load(a, index=(pid,), shape=(tile_size,))
b_tile = ct.load(b, index=(pid,), shape=(tile_size,))
# 直接做加法——就这么简单!
result = a_tile + b_tile
# 把结果写回去
ct.store(c, index=(pid,), tile=result)
def main():
# 准备数据
vector_size = 4096
tile_size = 16 # 每个 tile 处理 16 个元素
# 计算需要多少个 block(tile 数量)
grid = (ct.cdiv(vector_size, tile_size), 1, 1)
# 创建测试数据(用 CuPy,GPU 上的 NumPy)
a = cp.random.uniform(-1, 1, vector_size).astype(cp.float32)
b = cp.random.uniform(-1, 1, vector_size).astype(cp.float32)
c = cp.zeros_like(a)
# 启动 kernel!
ct.launch(
cp.cuda.get_current_stream(),
grid,
vector_add,
(a, b, c, tile_size)
)
# 验证结果
expected = cp.asnumpy(a) + cp.asnumpy(b)
actual = cp.asnumpy(c)
np.testing.assert_array_almost_equal(actual, expected)
print("向量加法成功!GPU 和 CPU 结果一致!")
if __name__ == "__main__":
main()
跑起来!
python vector_add.py
# 输出:向量加法成功!GPU 和 CPU 结果一致!
就这?
对,就这。
没有 blockIdx.x * blockDim.x + threadIdx.x,没有手动算 block 数量,没有 C++ 语法。
你就像在写 NumPy 一样,只不过这玩意儿是跑在 GPU 上的。

再来一个:矩阵乘法,自动用上 Tensor Core
矩阵乘法是深度学习的核心操作,传统 CUDA 写起来非常复杂(要考虑分块、共享内存、Tensor Core 调用……)。
用 cuTile:
import cupy as cp
import numpy as np
import cuda.tile as ct
@ct.kernel
def matmul(
a, b, c,
M: ct.Constant[int],
N: ct.Constant[int],
K: ct.Constant[int],
BLOCK_M: ct.Constant[int],
BLOCK_N: ct.Constant[int],
BLOCK_K: ct.Constant[int],
):
"""矩阵乘法:C = A @ B"""
# 获取当前 block 负责的输出位置
pid_m = ct.bid(0)
pid_n = ct.bid(1)
# 初始化累加器(这个 tile 会存中间结果)
acc = ct.zeros((BLOCK_M, BLOCK_N), dtype=ct.float32)
# 分块计算:沿着 K 维度迭代
for k in range(0, K, BLOCK_K):
# 加载 A 和 B 的子块
a_tile = ct.load(a, index=(pid_m, k // BLOCK_K), shape=(BLOCK_M, BLOCK_K))
b_tile = ct.load(b, index=(k // BLOCK_K, pid_n), shape=(BLOCK_K, BLOCK_N))
# 矩阵乘法累加——自动使用 Tensor Core!
acc = ct.dot(a_tile, b_tile, acc)
# 写回结果
ct.store(c, index=(pid_m, pid_n), tile=acc)
def main():
M, N, K = 1024, 1024, 1024
BLOCK_M, BLOCK_N, BLOCK_K = 64, 64, 32
# 创建矩阵
a = cp.random.randn(M, K).astype(cp.float16)
b = cp.random.randn(K, N).astype(cp.float16)
c = cp.zeros((M, N), dtype=cp.float32)
# 计算 grid 大小
grid = (M // BLOCK_M, N // BLOCK_N, 1)
# 启动!
ct.launch(
cp.cuda.get_current_stream(),
grid,
matmul,
(a, b, c, M, N, K, BLOCK_M, BLOCK_N, BLOCK_K)
)
# 验证
expected = cp.asnumpy(a.astype(cp.float32) @ b.astype(cp.float32))
actual = cp.asnumpy(c)
np.testing.assert_allclose(actual, expected, rtol=1e-2)
print("矩阵乘法成功!自动使用了 Tensor Core!")
if __name__ == "__main__":
main()
注意这一行:
acc = ct.dot(a_tile, b_tile, acc)
它会自动使用 Tensor Core 加速。在传统 CUDA 里,调用 Tensor Core 需要用 wmma 或 mma 指令,代码量翻好几倍,还得对着硬件手册查参数。
现在?一个 ct.dot() 完事。
硬件门槛:目前只支持 Blackwell
坏消息来了:cuTile 目前只支持 NVIDIA Blackwell 架构的 GPU。
| 架构 | 计算能力 | 代表型号 | cuTile 支持 |
|---|---|---|---|
| Ampere | 8.x | RTX 3090, A100 | 暂不支持 |
| Ada Lovelace | 8.9 | RTX 4090 | 暂不支持 |
| Hopper | 9.0 | H100 | 暂不支持 |
| Blackwell | 10.x / 12.x | RTX 5090, B100 | 支持 |
消费级选择
| 型号 | 显存 | 国行价格 | 适合场景 |
|---|---|---|---|
| RTX 5090 | 32GB GDDR7 | ¥16,499 | 旗舰,跑大模型 |
| RTX 5080 | 16GB GDDR7 | ¥8,299 | 主流,性价比之选 |
| RTX 5070 Ti | 16GB | 待定 | 入门 |
好消息:NVIDIA 承诺未来会支持更多架构,而且 cuTile 语言本身会开源。
所以现在的策略是:
- 有 Blackwell GPU:直接上车,体验新时代
- 没有 Blackwell:先学概念和代码风格,等硬件支持
核心概念速成:5 分钟理解 Tile 编程
三个核心概念
| 概念 | 解释 | 类比 |
|---|---|---|
| Tile | 一小块数据,是计算的基本单位 | 就像你切披萨,每块就是一个 Tile |
| Kernel | 在每个 Tile 上执行的函数 | 就是"怎么吃这块披萨"的方法 |
| Grid | Tile 的排列方式(几行几列) | 整个披萨被切成了多少块 |
Tile 的约束
- 维度必须是 2 的幂:16, 32, 64, 128…
- 形状必须是编译时常量:不能动态变化
- Tile 是不可变的:操作后返回新 Tile
常用操作
# 加载数据
tile = ct.load(array, index=(i, j), shape=(M, N))
# 算术运算(逐元素)
c = a + b
c = a * b
c = ct.exp(a)
c = ct.sqrt(a)
# 矩阵乘法(自动用 Tensor Core)
c = ct.dot(a, b)
c = ct.dot(a, b, acc) # 带累加器
# 归约操作
sum_val = ct.sum(tile)
max_val = ct.max(tile)
# 形状操作
reshaped = ct.reshape(tile, (new_m, new_n))
transposed = ct.transpose(tile)
# 存储结果
ct.store(array, index=(i, j), tile=tile)
实战案例:Softmax 深度学习必备操作
Softmax 是 Transformer 的核心组件之一。传统实现需要考虑数值稳定性(减最大值),用 cuTile 写起来很直观:
import cupy as cp
import numpy as np
import cuda.tile as ct
@ct.kernel
def softmax_kernel(
input_arr,
output_arr,
BLOCK_SIZE: ct.Constant[int],
):
"""Softmax:对每一行做归一化"""
# 每个 block 处理一行
row_id = ct.bid(0)
# 加载一行数据
row_tile = ct.load(input_arr, index=(row_id,), shape=(BLOCK_SIZE,))
# 数值稳定性:减去最大值
max_val = ct.max(row_tile)
row_tile = row_tile - max_val
# 计算 exp
exp_tile = ct.exp(row_tile)
# 求和
sum_val = ct.sum(exp_tile)
# 归一化
result = exp_tile / sum_val
# 写回
ct.store(output_arr, index=(row_id,), tile=result)
def main():
batch_size = 1024
seq_len = 128 # 必须是 2 的幂
# 创建输入
x = cp.random.randn(batch_size, seq_len).astype(cp.float32)
y = cp.zeros_like(x)
# 启动 kernel
grid = (batch_size, 1, 1)
ct.launch(
cp.cuda.get_current_stream(),
grid,
softmax_kernel,
(x, y, seq_len)
)
# 验证:和 PyTorch 的结果对比
import torch
x_torch = torch.tensor(cp.asnumpy(x))
expected = torch.nn.functional.softmax(x_torch, dim=-1).numpy()
actual = cp.asnumpy(y)
np.testing.assert_allclose(actual, expected, rtol=1e-5)
print("Softmax 实现正确!")
# 检查:每行和为 1
row_sums = actual.sum(axis=1)
print(f"每行和(应该都是 1.0): {row_sums[:5]}")
if __name__ == "__main__":
main()
看看这个实现多清晰:
- 加载一行
- 减最大值
- 算 exp
- 求和
- 除以和
- 写回
完全是数学公式的直接翻译,不用关心任何线程管理的事。
避坑指南:新手容易踩的 5 个坑
坑 1:Tile 维度必须是 2 的幂
# 错误:100 不是 2 的幂
a_tile = ct.load(a, index=(pid,), shape=(100,))
# 正确:使用 128
a_tile = ct.load(a, index=(pid,), shape=(128,))
坑 2:忘记数据类型转换
# cuTile 对类型敏感,float16 和 float32 不能直接混用
a = cp.array([1, 2, 3], dtype=cp.float16)
b = cp.array([1, 2, 3], dtype=cp.float32)
# 可能出错
c = a_tile + b_tile
# 显式转换
a = a.astype(cp.float32)
坑 3:Grid 大小算错
vector_size = 4096
tile_size = 64
# 错误:用普通除法
grid = (vector_size / tile_size, 1, 1) # 得到 float!
# 正确:用整除或 cdiv
grid = (vector_size // tile_size, 1, 1)
# 或者用 ct.cdiv(向上取整,更安全)
grid = (ct.cdiv(vector_size, tile_size), 1, 1)
坑 4:不知道怎么调试
cuTile 支持用 NVIDIA Nsight Compute 进行性能分析:
# 生成性能报告
ncu -o my_kernel_profile --set detailed python my_script.py
# 用 Nsight Compute UI 打开查看
ncu-ui my_kernel_profile.ncu-rep
坑 5:硬件不支持时的报错
如果你在非 Blackwell GPU 上运行,会得到类似这样的错误:
RuntimeError: CUDA Tile requires compute capability 10.x or 12.x
目前没有绕过方法,只能等 NVIDIA 更新支持。
开发者准备清单
硬件准备
| 项目 | 最低要求 | 推荐配置 |
|---|---|---|
| GPU | Blackwell 架构 (CC 10.x/12.x) | RTX 5080 / RTX 5090 |
| 驱动 | R580+ | R590+(完整调试支持) |
| 显存 | 16GB | 32GB |
软件准备
# 检查 CUDA 版本
nvcc --version # 需要 13.1+
# 检查驱动版本
nvidia-smi # 需要 R580+
# 检查 Python 版本
python --version # 需要 3.10+
# 安装依赖
pip install cuda-tile cupy-cuda12x numpy
学习资源
| 资源 | 链接 |
|---|---|
| cuTile 官方文档 | https://docs.nvidia.com/cuda/cutile-python/ |
| 快速入门 | https://docs.nvidia.com/cuda/cutile-python/quickstart.html |
| GitHub 仓库 | https://github.com/NVIDIA/cutile-python |
| NVIDIA 技术博客 | https://developer.nvidia.com/blog/simplify-gpu-programming-with-nvidia-cuda-tile-in-python/ |
可拿走不谢:cuTile 项目模板
创建一个 cutile_template.py,以后直接拿来改:
"""
cuTile 项目模板
使用方法:复制这个文件,修改 kernel 函数和 main 函数
"""
import cupy as cp
import numpy as np
import cuda.tile as ct
# ============ 在这里定义你的 kernel ============
@ct.kernel
def my_kernel(
input_arr,
output_arr,
TILE_SIZE: ct.Constant[int],
):
"""
你的 kernel 描述
参数:
input_arr: 输入数组
output_arr: 输出数组
TILE_SIZE: 每个 tile 的大小(必须是 2 的幂)
"""
# 获取 block ID
pid = ct.bid(0)
# 加载数据
tile = ct.load(input_arr, index=(pid,), shape=(TILE_SIZE,))
# ============ 在这里写你的计算逻辑 ============
result = tile * 2 # 示例:乘以 2
# ============================================
# 存储结果
ct.store(output_arr, index=(pid,), tile=result)
def main():
# ============ 配置参数 ============
data_size = 4096
tile_size = 64 # 必须是 2 的幂
# ============ 准备数据 ============
input_data = cp.random.randn(data_size).astype(cp.float32)
output_data = cp.zeros_like(input_data)
# ============ 计算 grid 大小 ============
grid = (ct.cdiv(data_size, tile_size), 1, 1)
# ============ 启动 kernel ============
ct.launch(
cp.cuda.get_current_stream(),
grid,
my_kernel,
(input_data, output_data, tile_size)
)
# ============ 验证结果 ============
expected = cp.asnumpy(input_data) * 2 # 对应你的计算逻辑
actual = cp.asnumpy(output_data)
np.testing.assert_allclose(actual, expected, rtol=1e-5)
print("Kernel 执行成功!")
# 打印部分结果
print(f"输入前 5 个: {cp.asnumpy(input_data)[:5]}")
print(f"输出前 5 个: {actual[:5]}")
if __name__ == "__main__":
main()
GPU 编程的"iPhone 时刻"
回顾一下 CUDA Tile 带来的变化:
| 以前 | 现在 |
|---|---|
| 学 C++ 语法 | Python 直接写 |
| 手动管理线程 | 操作数据块就行 |
| 针对每代 GPU 优化 | 自动适配,跨代通用 |
| 手动调用 Tensor Core | 自动使用,透明加速 |
| 门槛高得劝退 | 门槛踩到地板 |
这让我想起了 iPhone 发布的那一刻——史蒂夫·乔布斯说:“我们重新发明了手机。”
NVIDIA 可能不会这么说,但他们实际上在做类似的事:
重新定义 GPU 编程的交互方式。
以前,GPU 编程是少数精英的专属技能。现在,任何会写 Python 的人都能上手。
当然,CUDA Tile 目前还有限制(只支持 Blackwell、功能还在完善中),但方向已经非常明确:
让更多人能够使用 GPU 的算力。
如果你是 Python 开发者,并且对 AI、科学计算、高性能计算有兴趣,现在是最好的上车时机:
- 学习 cuTile 的概念和 API
- 关注硬件更新(等 Blackwell 普及或支持更多架构)
- 准备好,迎接 GPU 编程民主化的新时代
参考资料:
237

被折叠的 条评论
为什么被折叠?



