从 Bit 到 AGI第九章:GPU

CUDA 是什么

用 C/C++ 在 GPU 上写并行程序的平台与编程模型

作者 David更新于 第 131 篇(共 186 篇)

你已经在 Python 里调用过 model.cuda() 或 device="cuda",但 GPU 上真正跑的是什么?CUDA(Compute Unified Device Architecture)是 NVIDIA 提供的并行计算平台:它让你用扩展过的 C/C++ 写「内核函数」,把成千上万个线程同时派到 GPU 上执行。没有 CUDA(或兼容层如 ROCm),PyTorch 的矩阵乘法就无法直接驱动 NVIDIA 硬件。

2006 年 NVIDIA 发布 CUDA 1.0,降低了 GPGPU(通用 GPU 计算)门槛——此前要用图形 API「伪装」成游戏去算科学计算。Today,几乎所有深度学习框架的 NVIDIA 后端都建立在 CUDA Runtime 与 cuBLAS 之上;读 LLM 论文里的「A100 集群」时,底层就是成千上万张卡各自跑着 CUDA kernel。

直觉

把 GPU 想象成一家拥有数百名工人的工厂,CPU 是坐在办公室里的经理。经理(CPU)负责拆任务、发指令;工人们(GPU 线程)在同一道工序上各处理一小份数据。CUDA 就是这套「派工规则」——规定怎么写工序说明书(kernel)、怎么把工人编组(grid / block / thread)、以及经理和工人之间怎么传材料(host ↔ device 内存拷贝)。

图表加载中…

定义

CUDA 包含三层:

  1. 硬件抽象:GPU 被暴露为「设备(device)」,与运行操作系统和 Python 解释器的「主机(host)」分离。
  2. 编程模型:程序员写 __global__ 内核,启动时用 <<<grid, block>>> 指定线程布局。
  3. 运行时 API:cudaMalloc、cudaMemcpy、cudaLaunchKernel 等,负责内存分配与内核调度。

一个典型的 CUDA 程序流程:

# PyTorch 在底层大致等价于:
# 1. 把 tensor 数据 cudaMemcpy 到 GPU
# 2. 启动 CUDA kernel(如 cuBLAS 的 GEMM)
# 3. 结果留在 device 上,或拷回 host
import torch

x = torch.randn(4096, 4096, device="cuda")
y = torch.randn(4096, 4096, device="cuda")
z = x @ y  # 调用 cuBLAS,不经过 Python 循环

例子

例子 1:向量加法内核

__global__ void vec_add(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];
}
// 启动:<<< (n+255)/256, 256 >>>
// 共 n 个线程,每个算 c[i] = a[i] + b[i]

若 n = 1,048,576(约 100 万元素),CPU 单线程循环需要 100 万次迭代;GPU 一次启动即可让约 100 万个线程并行写 c[i]。

例子 2:PyTorch 与 CUDA 的关系

层级你写的代码实际执行
应用loss.backward()PyTorch autograd
框架torch.matmul(A, B)cuBLAS / cuDNN
驱动CUDA Runtimekernel 启动
硬件—SM 上的 Warp 执行

你几乎从不手写 CUDA kernel,但每个 torch 算子最终都落在 CUDA(或 cuBLAS/cuDNN)上。

量化

  • 线程规模:一块 H100 GPU 可同时驻留数十万线程;一次 kernel 启动的线程总数可达 10610^6 量级。
  • 内存拷贝代价:PCIe 4.0 ×16 约 32 GB/s(见PCIe 篇)。把 14 GB 的 FP16 权重从 CPU 拷到 GPU 约需 1432≈0.44\frac{14}{32} \approx 0.44 秒——这就是为什么推理时权重应常驻显存。
  • 与 CPU 线程对比:CPU 擅长少量复杂线程 + 大缓存;CUDA 擅长海量简单线程 + 高带宽显存(HBM)。

线程层次:Grid、Block、Thread

CUDA 用三维索引组织线程(实践中常用其中一维或二维):

层次含义类比
Grid一次 kernel 启动的全部 block整批订单
Block可协作的线程组(共享 shared memory)一个班组
Thread最小执行单元一名工人

Block 大小有限制(通常每 block 最多 1024 线程),Grid 可以非常大(231−12^{31}-1)。这种层次与硬件映射关系是:一个 block 分配到一个 SM 上执行(可能与其他 block 共享同一 SM),block 内再划分为 Warp。

Host 与 Device 内存分离是 CUDA 的第一课。你在 Python 里创建的普通 tensor 在 CPU 上;.cuda() 或 device="cuda" 会在 GPU 上 新分配 一块 HBM 并拷贝数据。之后两次 matmul 若输入已在 device 上,就不再经过 PCIe——这是推理服务把模型 load 到 GPU 后常驻的原因。

CUDA 生态:不止于「写 kernel」

大多数 AI 工程师从不手写 __global__,但每天都在用 CUDA 生态:

  • cuBLAS:Level-3 BLAS(矩阵乘),PyTorch torch.matmul 的底层之一。
  • cuDNN:卷积、RNN、Normalization 等深度学习原语。
  • NCCL:多 GPU 集合通信(AllReduce 等),分布式训练的基石。
  • CUDA Graph:把多次 kernel 启动录制成图,减少 CPU launch 开销——vLLM 等框架大量使用。

从 第一章 SIMD 到 CUDA:CPU SIMD 是 显式 4/8/16 路向量;CUDA 是 隐式 32 路 Warp SIMT。二者哲学不同,但都在回答「如何用一条指令处理多份数据」。

常见误区

误区 1:「CUDA = GPU」 — CUDA 是 NVIDIA 的软件栈;AMD GPU 用 ROCm/HIP,Apple 用 Metal。说「放到 CUDA 上」通常指「放到 NVIDIA GPU 上」。

误区 2:「.cuda() 之后一切都很快」 — 若数据仍在 CPU 内存,每次前向都要 cudaMemcpy,PCIe 会成为瓶颈。应确保模型和张量创建在 device="cuda" 上。

误区 3:「写 CUDA kernel 一定比库快」 — cuBLAS、FlashAttention 等库经过多年优化,手写 kernel 很难超越,除非你有非常特殊的算子。

动手试一试

在装有 NVIDIA GPU 的机器上:

python -c "
import torch
print('CUDA available:', torch.cuda.is_available())
print('Device:', torch.cuda.get_device_name(0))
print('Capability:', torch.cuda.get_device_capability(0))
"

观察输出中的 Compute Capability(如 9.0 对应 Hopper/H100),它决定你能用哪些 Tensor Core 指令。

再试一次最小「真·CUDA」体验(需本机安装 CUDA Toolkit 与 nvcc):

# 若已安装 samples
cuda-install-samples-12.x.sh ~
cd ~/NVIDIA_CUDA-12.x_Samples/0_Introduction/vectorAdd
make
./vectorAdd

输出会显示 GPU 上向量加法耗时远小于 CPU 参考实现——与 PyTorch 高层 API 背后是同一套执行模型。

与 LLM 训练推理的对应关系

你在 PyTorch 里写的CUDA 层发生的事
tensor.cuda()cudaMalloc + cudaMemcpy H2D
torch.matmulcuBLAS cublasGemmEx(Tensor Core 路径)
DistributedDataParallelNCCL AllReduce + 多 stream
torch.cuda.synchronize()等待 device 队列清空

理解这一列,debug OOM、慢拷贝、多卡 hang 时才知道该查 驱动 / NCCL / PCIe / NVLink 哪一层。

回顾与自测

Q:PyTorch 算子在 GPU 上执行,是否等于写了 CUDA?
A:是间接的。PyTorch 调用 cuBLAS/cuDNN 等预编译 CUDA 库;只有自定义算子或 torch.utils.cpp_extension 才手写 kernel。

Q:为什么第一次 cuda() 慢、后面快?
A:首次包含 H2D 拷贝与 CUDA context 初始化;后续若 tensor 已在 device,主要只剩 kernel 时间。

Q:没有 NVIDIA 显卡能学 CUDA 吗?
A:可以读概念;实践需 GPU 或 Colab。Apple Silicon 用 MPS,语法类似 device="mps" 但底层不是 CUDA。

Q:CUDA 版本和驱动不匹配会怎样?
A:PyTorch 安装页会指定最低驱动;nvidia-smi 右上角驱动版本应 ≥ 要求,否则 kernel 无法加载或性能降级。

小结

CUDA 是连接「C/C++ 并行程序」与「NVIDIA GPU 硬件」的桥梁:内核 + 线程层次 + 内存管理。PyTorch、TensorFlow、vLLM 等框架都建立在 CUDA 之上。理解 CUDA 的 host/device 分离,是理解后续 HBM 带宽、Warp 调度、Tensor Core 的前提。

下一篇:HBM 是什么 — 显存为什么比 DDR 快一个数量级。

本页目录