🏠 总目录📚 本教程 GPU 是什么
📑 本页目录(点开跳转)

02 · GPU 到底是什么

26 分钟 | ⭐ 不需要写 CUDA,但要知道它长什么样


🎯 一句话

CPU 是几个博士生,GPU 是几千个小学生问题不在于谁更聪明,而在于你的任务能不能拆成几千份一模一样的小活。 深度学习恰好完美符合 —— 这就是 GPU 统治这个领域的全部原因。


🆚 一、CPU vs GPU

CPU: GPU:

CPUGPU巨大的缓存复杂的分支预测 · 乱序执行少数几个「很聪明」的核几千个「很笨但很多」的核
⭐ 省下来的晶体管去哪了:GPU 不做分支预测、不做乱序执行,把面积全换成了核心和寄存器 —— 代价是它靠切换线程掩盖内存延迟,而不是靠缓存。
CPU GPU
核心数 8 ~ 128 几千个
单核性能
设计目标 降低延迟(把一件事尽快做完) 提高吞吐(同时做很多事)⭐
擅长 分支多、依赖强的逻辑 大量相同的、独立的运算
缓存 大(几十 MB) 小(每 SM 几百 KB)

🔑 一个关键的设计取舍GPU 把省下来的晶体管(不做分支预测、不做乱序执行)全用来堆核心和寄存器。 代价是:它靠"切换到别的线程"来掩盖内存延迟,而不是靠缓存。


🏗️ 二、GPU 的层次结构

   GPU
   ├─ SM(流多处理器)× 100+        ← 真正干活的单元
   │   ├─ CUDA Core   (FP32 通用运算)
   │   ├─ Tensor Core (⭐ 矩阵乘专用,深度学习的主力)
   │   ├─ 寄存器      (最快,每 SM 几百 KB)
   │   └─ Shared Memory / L1(几百 KB,⭐ FlashAttention 靠它)
   ├─ L2 Cache(几十 MB,全卡共享)
   └─ HBM 显存(40 / 80 / 141 GB)  ← 慢,但容量大

⭐ Tensor Core:为什么它改变了一切

   CUDA Core:一次做一个乘加   a×b+c
   Tensor Core:一次做一整个【小矩阵乘】 D = A×B + C(比如 4×4)

   → 单位时间的矩阵乘吞吐高【一个数量级】

   A100 的对照:
   FP32(CUDA Core)        19.5 TFLOPS
   TF32(Tensor Core)      156  TFLOPS   ⭐ 8 倍
   BF16/FP16(Tensor Core) 312  TFLOPS   ⭐ 16 倍
   INT8(Tensor Core)      624  TOPS

🔑 这解释了第 6 章混合精度为什么是"免费的午餐"不是因为半精度算得快,而是因为半精度才能用上 Tensor Core。

⚠️ 但 Tensor Core 有使用条件:矩阵维度要对齐(通常是 8 或 16 的倍数)。 维度是 4095 而不是 4096,可能直接掉回 CUDA Core —— 这是个真实的坑。


🧵 三、执行模型:warp 和它的两个坑

   线程(thread)
     └─ 32 个线程打包成一个 【warp】   ⭐ 这是真正的调度单位
          └─ 若干 warp 组成一个 block(跑在同一个 SM 上)
               └─ 若干 block 组成 grid

   ⭐ 一个 warp 里的 32 个线程【必须执行同一条指令】(SIMT)

坑 ① warp divergence(分支发散)

   if (threadIdx.x % 2 == 0)  A();
   else                        B();

   → 同一个 warp 里既有走 A 的又有走 B 的
   → 硬件只能【先跑 A(走 B 的线程闲置),再跑 B】
   → 耗时翻倍 💀

   ⭐ 这就是为什么 GPU 不擅长分支多的逻辑

坑 ② 非合并访存(uncoalesced access)

   ✅ 合并访存:warp 里 32 个线程访问【连续】的地址
      → 硬件合并成 1 次内存事务

   ❌ 非合并:访问跳跃的地址(比如按列访问行主序矩阵)
      → 可能变成 32 次独立事务 → 有效带宽掉到 1/32 💀

   ⭐ 这就是为什么【张量的内存布局(contiguous)很重要】
# PyTorch 里的真实影响
import torch
x = torch.randn(4096, 4096, device='cuda')
y = x.t()                      # 转置只改 stride,没搬数据
z = y @ w                      # ⚠️ 非连续布局,可能触发隐式拷贝或慢路径

y = x.t().contiguous()         # ⭐ 显式重排一次,后续访问都是合并的

💡 为什么"切换线程"能掩盖延迟: 当一个 warp 在等 HBM 数据(几百个周期)时,SM 立刻切换到另一个就绪的 warp。 只要同时驻留的 warp 足够多(occupancy 高),延迟就被填满了。 —— 这也是为什么 batch 太小时 GPU 效率低:warp 不够多,掩盖不住延迟。


📊 四、几张主流卡的关键数字

A100 80G H100 SXM RTX 4090
BF16 算力 312 TFLOPS 989 TFLOPS 165 TFLOPS
FP8 算力 1979 TFLOPS
显存 80 GB 80 GB 24 GB ⚠️
显存带宽 2.0 TB/s 3.35 TB/s 1.0 TB/s
卡间互联 NVLink 600 GB/s NVLink 900 GB/s ⚠️ 仅 PCIe ~64GB/s

🔑 看最后两行,不要只看算力4090 的算力有 A100 一半,但显存只有 24GB,卡间互联只有 PCIe。 → 单卡跑小模型很划算,多卡训练大模型基本不可用第 12 章会解释为什么张量并行离不开 NVLink)。

💡 H100 的 FP8 值得注意:算力再翻一倍。 但 FP8 的动态范围极窄,需要 per-tensor scaling 才能训得稳(第 6 章)。


🧰 五、你实际会碰到的抽象层

   你写的:       PyTorch (torch.matmul)
                     ↓
   框架调用的:    cuBLAS / cuDNN / CUTLASS  ← NVIDIA 的高度优化库
                     ↓
   或者:          自定义 kernel(CUDA / Triton)← 第 7 章
                     ↓
   驱动 → 硬件

实践建议99% 的情况下你不需要写 CUDA。 先确认瓶颈真的在某个算子上(第 4 章教你怎么测), 再考虑用 Triton(Python 语法,门槛低得多)—— 第 7 章

# 三个你一定会用到的命令
nvidia-smi                    # 看显存占用、利用率、温度、功耗
nvidia-smi topo -m            # ⭐ 看卡间互联拓扑(NVLink 还是 PCIe)
nvidia-smi dmon -s pucm       # 持续监控

⚠️ nvidia-smi 的 "GPU-Util" 极具误导性: 它只表示"过去一段时间内有 kernel 在跑"的百分比, 不代表算力被用满。一个只用了 5% 算力的 kernel 一直跑,它也显示 100%。 真正该看的是 MFU(第 4 章)。


🔗 和站内其他章的关系

相关的地方 这里的位置
ML 基础第 15 章 .to('cuda') 现在你知道那边有什么了
第 1 章 算力涨得比带宽快 HBM 就是那个瓶颈
第 3 章 详细讲 HBM / L2 / Shared Memory 的层次
第 6 章 混合精度 为了用上 Tensor Core
第 8 章 FlashAttention 靠 Shared Memory 避免往返 HBM

✅ 检查点

  1. CPU 和 GPU 的设计目标分别是什么?
  2. GPU 靠什么掩盖内存延迟?这对 batch size 有什么含义?
  3. Tensor Core 和 CUDA Core 的区别?A100 上差多少倍?
  4. 混合精度为什么快?(真正的原因)
  5. 什么是 warp?warp divergence 为什么慢?
  6. 什么是非合并访存?它和 PyTorch 里的什么操作有关?
  7. 4090 为什么不适合多卡训练大模型?
  8. 为什么 nvidia-smi 的 GPU-Util 具有误导性?
👀 答案
  1. CPU 降低延迟(把一件事尽快做完),GPU 提高吞吐(同时做很多事)。GPU 把不做分支预测/乱序执行省下的晶体管全用来堆核心。
  2. 切换到别的 warp——一个 warp 等 HBM 时立刻切到另一个就绪的。含义:batch 太小时驻留 warp 不够多,掩盖不住延迟,GPU 效率低
  3. CUDA Core 一次做一个乘加,Tensor Core 一次做一整个小矩阵乘。A100 上 FP32 是 19.5 TFLOPS,BF16 Tensor Core 是 312 TFLOPS,16 倍
  4. 不是因为半精度算得快,而是因为半精度才能用上 Tensor Core。⚠️ 且要求矩阵维度对齐(8 或 16 的倍数),4095 而不是 4096 可能掉回 CUDA Core。
  5. 32 个线程打包成的调度单位,一个 warp 里所有线程必须执行同一条指令(SIMT)。divergence 时硬件只能先跑一个分支(另一批线程闲置)再跑另一个,耗时翻倍
  6. warp 里的线程访问跳跃的地址(如按列访问行主序矩阵),无法合并成一次内存事务,有效带宽可能掉到 1/32。和 PyTorch 里张量是否 contiguous 直接相关——转置只改 stride 不搬数据,后续访问可能走慢路径。
  7. 因为显存只有 24GB,且卡间互联只有 PCIe(~64GB/s)没有 NVLink。张量并行需要极高的卡间带宽,PCIe 撑不住。
  8. 它只表示"过去一段时间内有 kernel 在跑"的百分比,不代表算力被用满。一个只用 5% 算力的 kernel 一直跑也显示 100%。真正该看 MFU

🛑 可以停在这里

走神救援

CPU 是几个博士生(降低延迟),GPU 是几千个小学生(提高吞吐);GPU 把不做分支预测/乱序执行省下的晶体管全堆成核心,靠切换 warp 而不是靠缓存来掩盖内存延迟 → ⭐batch 太小时驻留 warp 不够,掩盖不住延迟,效率低层次:SM(含 CUDA Core / Tensor Core / 寄存器 / Shared Memory 几百KB)→ L2(几十MB)→ HBM(80GB,慢)。⭐Tensor Core 一次做一整个小矩阵乘,A100 上 FP32 19.5 TFLOPS vs BF16 312 TFLOPS(16倍) → ⭐混合精度快的真正原因不是"半精度算得快",而是半精度才能用上 Tensor Core;⚠️要求维度对齐(4095 而非 4096 可能掉回 CUDA Core)。执行模型:32 线程 = 1 warp,必须执行同一条指令(SIMT);两个坑:⚠️warp divergence(同 warp 走不同分支 → 先跑一个再跑另一个,耗时翻倍,所以 GPU 不擅长分支)、⚠️非合并访存(访问跳跃地址 → 有效带宽掉到 1/32,所以 PyTorch 里 contiguous 很重要)。关键数字:A100 312 TFLOPS / 2.0TB/s / NVLink 600GB/s;H100 989 TFLOPS(FP8 翻倍到 1979)/ 3.35TB/s / NVLink 900GB/s;⭐4090 算力不差但显存仅 24GB 且只有 PCIe 无 NVLink → 多卡训练大模型基本不可用。⚠️⭐nvidia-smi 的 GPU-Util 极具误导性——它只说"有 kernel 在跑",一个只用 5% 算力的 kernel 也显示 100%,真正该看 MFU。99% 的情况不需要写 CUDA,真要写就用 Triton

下一节 👉 03-显存与带宽墙.md ⭐⭐

打卡记录保存在你的浏览器里,首页能看到总进度