GPU 硬件基础——为什么 GPU 比 CPU 快,显存为什么总是不够 / GPU Hardware, Parallel Throughput, and Memory Capacity
📅 创建时间:2026-06-02 🏷️ 标签:#GPU #CUDA #显存 #Tensor-Core #HBM #NVLink #Roofline 📚 前置知识:[[00-aiinfra-overview]](AI Infra 全景) 📚 相关知识:[[02-distributed-training]](分布式训练) [[03-memory-optimization]](显存优化)
场景:PyTorch 报 CUDA OOM,但 nvidia-smi 显示显存还有 20GB
┌─────────────────────────────────────────────────────────────┐
│ │
│ 凌晨 2 点,你跑训练脚本。 │
│ │
│ PyTorch 输出: │
│ RuntimeError: CUDA out of memory. │
│ Tried to allocate 2.0 GiB (GPU 0; 80.00 GiB total capacity; │
│ 59.87 GiB already allocated; 20.13 GiB free; 80.00 GiB │
│ total) │
│ │
│ 你的第一反应: │
│ → nvidia-smi 看一眼 → 59.87 GiB allocated, 20.13 GiB free │
│ → 明明还有 20GB 显存,PyTorch 怎么就 OOM 了? │
│ │
│ 排查过程: │
│ → PyTorch 显存分配器预留了 CUDA 缓存 │
│ → 你要分配 2GB,但显存碎片化,最大半连续空间只有 1.5GB │
│ → OOM 了 │
│ │
│ 理解 GPU 硬件和显存管理,是排查这类问题的前提。 │
│ │
└─────────────────────────────────────────────────────────────┘第1节:CPU vs GPU——为什么深度学习用 GPU
CPU 的设计哲学:通用性
┌─────────────────────────────────────────────────────────────┐
│ CPU 架构:少而强的核心 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ CPU Chip │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐│ │
│ │ │ Core 0 │ │ Core 1 │ │ Core 2 │ │ Core 3 ││ │
│ │ │ (ALU) │ │ (ALU) │ │ (ALU) │ │ (ALU) ││ │
│ │ │ + Cache│ │ + Cache│ │ + Cache│ │ + Cache││ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘│ │
│ │ ↑ ↑ ↑ ↑ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Shared L3 Cache │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ ↑ │ │
│ │ DRAM Memory │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ CPU 特点: │
│ • 少量核心(8~128 核),每个核心很强 │
│ • 大容量缓存(L1/L2/L3),减少内存访问 │
│ • 复杂分支预测和乱序执行 │
│ • 适合:单线程性能、复杂逻辑、串行任务 │
│ │
└─────────────────────────────────────────────────────────────┘GPU 的设计哲学:并行化
┌─────────────────────────────────────────────────────────────┐
│ GPU 架构:多而专的核心 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ GPU Chip │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ SM 0 │ │ SM 1 │ ... ... │ SM N │ │ │
│ │ │(128核) │ │(128核) │ │(128核) │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ HBM2 / HBM3 显存 │ │ │
│ │ │ (80GB, 2TB/s) │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ A100/H100 规格: │
│ • 108 个 SM(A100)/ 132 个 SM(H100) │
│ • 每个 SM 128 个 CUDA Core │
│ • 合计 13824 / 16896 个 CUDA Core │
│ • 显存带宽:2TB/s(vs CPU DDR5 的 ~100GB/s) │
│ │
└─────────────────────────────────────────────────────────────┘对比:CPU vs GPU
┌─────────────────────────────────────────────────────────────┐
│ CPU vs GPU 关键指标对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ │ 指标 │ CPU (AMD EPYC) │ GPU (H100) │
│ ├────────────────┼───────────────────┼────────────────────┤
│ │ 核心数 │ 64 核 │ 16896 核 │
│ ├────────────────┼───────────────────┼────────────────────┤
│ │ 峰值算力 │ ~2 TFLOPS │ ~989 TFLOPS(FP8) │
│ ├────────────────┼───────────────────┼────────────────────┤
│ │ 显存带宽 │ ~200 GB/s │ ~3.35 TB/s │
│ ├────────────────┼───────────────────┼────────────────────┤
│ │ 访存功耗 │ 占主导 │ 占主导 │
│ ├────────────────┼───────────────────┼────────────────────┤
│ │ 适用场景 │ 串行逻辑 │ 大规模并行计算 │
│ │
│ 结论:深度学习是"大量相同运算并行执行", │
│ GPU 的架构天然契合这个模式。 │
│ │
└─────────────────────────────────────────────────────────────┘第2节:CUDA 最小必要知识——GPU 是怎么执行计算的
GPU 线程组织:Grid / Block / Thread
┌─────────────────────────────────────────────────────────────┐
│ CUDA 线程组织层次 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Grid(整个任务) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Block 0 │ │ Block 1 │ │ Block N │ ... │ │
│ │ │ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │ │ │
│ │ │ │ Thread│ │ │ │ Thread│ │ │ │ Thread│ │ │ │
│ │ │ │(执行) │ │ │ │(执行) │ │ │ │(执行) │ │ │ │
│ │ │ └──────┘ │ │ └──────┘ │ │ └──────┘ │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 例子:矩阵乘法 C = A × B │
│ 每个 Thread 计算 C 的一个元素 │
│ Block(0,0) 的 Thread(0,0) 计算 C[0][0] │
│ Block(0,0) 的 Thread(0,1) 计算 C[0][1] │
│ ... │
│ 所有 Thread 同时执行,并行计算所有元素 │
│ │
│ 对训练的意义: │
│ → Attention 计算中的 QK^T:每个 (i,j) 可以由一个 Thread 算│
│ → 所有位置同时计算 → O(n^2) 并行 │
│ │
└─────────────────────────────────────────────────────────────┘GPU 内存层级——理解显存管理的关键
┌─────────────────────────────────────────────────────────────┐
│ GPU 内存层级(由快到慢,由贵到便宜) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Register(寄存器) │ │
│ │ 每个 Thread 私有,极快(~1 cycle) │ │
│ │ 大小:~256 KB / SM │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↑ 100x │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Shared Memory(共享内存) │ │
│ │ 每个 SM 内线程共享,极快(~1 cycle) │ │
│ │ 大小:~192 KB / SM │ │
│ │ 类似 CPU 的 L1 Cache,但可被程序员控制 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↑ 10x │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ L1/L2 Cache(片上缓存) │ │
│ │ SM 间共享,较快(~50 cycle) │ │
│ │ L2 Cache 大小:H100 = 50MB / A100 = 40MB │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↑ 10x │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ HBM(显存,High Bandwidth Memory) │ │
│ │ GPU 芯片外部,H100 = 80GB / 3.35 TB/s │ │
│ │ 延迟:~500 cycle │ │
│ │ 容量受限、成本高——这就是为什么显存总是"不够用" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 训练中的关键: │
│ → Activation(中间计算结果)必须在 HBM 和 L2 Cache 之间倒 │
│ → 显存放不下 → Activation 溢出 → OOM │
│ → Gradient Checkpointing 就是用计算换 L2 Cache 空间 │
│ │
└─────────────────────────────────────────────────────────────┘Tensor Core——专门为矩阵计算而生
┌─────────────────────────────────────────────────────────────┐
│ CUDA Core vs Tensor Core │
├─────────────────────────────────────────────────────────────┤
│ │
│ CUDA Core(标量计算): │
│ → 1 个 Core,1 个时钟周期,执行 1 次 FMA(乘加) │
│ → A100: 13824 个 CUDA Core │
│ → 适合:逐元素操作(ReLU、Dropout) │
│ │
│ Tensor Core(矩阵计算): │
│ → 1 个 Tensor Core,1 个时钟周期,执行 4×4×4 FMA │
│ → A100: 432 个 Tensor Core │
│ → 适合:矩阵乘法——这是深度学习最核心的计算 │
│ │
│ 矩阵乘法例子:计算 Y = W × X │
│ │
│ CUDA Core 方式(逐元素): │
│ for i in range(N): │
│ for j in range(M): │
│ Y[i][j] = sum(W[i][k] * X[k][j] for k in range(K)) │
│ → N×M×K 次乘加操作,串行执行 │
│ │
│ Tensor Core 方式(矩阵整体): │
│ → W(4×K) 和 X(K×4) 一起送入 Tensor Core │
│ → 1 个时钟周期完成整个子矩阵乘 │
│ → 吞吐量提升 8~16 倍 │
│ │
│ H100 的 Tensor Core(第五代): │
│ → FP8 精度:989 TFLOPS(稀疏后可达 1978 TFLOPS) │
│ → Transformer Engine 利用 FP8 加速 LLM 训练 │
│ │
└─────────────────────────────────────────────────────────────┘Host-Device 通信——CPU 和 GPU 之间的数据传输
┌─────────────────────────────────────────────────────────────┐
│ CPU(Host)和 GPU(Device)通信 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────┐ PCIe/NVLink ┌────────────┐ │
│ │ CPU │ ════════════════════════════▶ │ GPU │ │
│ │ System RAM │ ◀════════════════════════════ │ HBM │ │
│ └────────────────┘ (慢,约 50 GB/s) └────────────┘ │
│ │
│ 问题:PCIe 带宽远低于 GPU 内部带宽 │
│ → GPU 内部 HBM:3.35 TB/s │
│ → CPU ↔ GPU 传输:~50 GB/s(PCIe 4.0 x16) │
│ → 差距 67 倍! │
│ │
│ 训练中的影响: │
│ 1. 数据加载:CPU 从磁盘读数据 → 拷贝到 GPU │
│ → 必须用 DataLoader + 预取(prefetch)掩盖这个开销 │
│ 2. GPU 间通信:分布式训练时梯度同步 │
│ → 必须用 NVLink(节点内)或 InfiniBand(节点间) │
│ → PCIe 带宽不够,会严重拖后腿 │
│ 3. CPU Offload:把部分数据临时放到 CPU 内存 │
│ → 只有在显存实在不够时才会这样做,开销很大 │
│ │
│ 优化原则:尽量减少 Host ↔ Device 的数据传输 │
│ │
└─────────────────────────────────────────────────────────────┘CUDA Stream——让不同的操作并行起来
┌─────────────────────────────────────────────────────────────┐
│ CUDA Stream:并行执行的基础 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 默认情况:所有操作在默认 Stream 中顺序执行 │
│ 优点:简单 │
│ 缺点:数据加载和 GPU 计算无法重叠 │
│ │
│ Stream 0(默认,计算) Stream 1(数据加载) │
│ ┌────────────────────────┐ ┌────────────────────────┐ │
│ │ Forward → Backward → │ │ 加载下一批数据 │ │
│ │ Optimizer → │ │ │ │
│ └────────────────────────┘ └────────────────────────┘ │
│ ↑ 数据加载和计算重叠 → 总时间大幅缩短 │
│ │
│ PyTorch 实现: │
│ cuda_stream = torch.cuda.Stream() │
│ with torch.cuda.stream(cuda_stream): │
│ # 数据加载到这个 stream │
│ data = next_batch() │
│ # 主 stream 执行计算,同时数据加载在 cuda_stream 执行 │
│ output = model(data) │
│ │
│ 训练中的典型应用: │
│ → DataLoader 多 worker 预取 │
│ → Checkpoint 保存和训练并行 │
│ → 通信和计算重叠(下一个 micro-batch 计算 + 当前通信) │
│ │
└─────────────────────────────────────────────────────────────┘第3节:GPU 集群拓扑——多卡和多机是怎么连接的
单机 8 卡拓扑
┌─────────────────────────────────────────────────────────────┐
│ 单机 8 卡 GPU 连接拓扑(A100/H100) │
├─────────────────────────────────────────────────────────────┤
│ │
│ NVLink:每个 GPU 和其他所有 GPU 直接互联 │
│ │
│ GPU 0 ──NVLink── GPU 1 ──NVLink── GPU 2 ──NVLink── GPU 3 │
│ │ │ │ │ │
│ NVLink NVLink NVLink NVLink │
│ │ │ │ │ │
│ GPU 4 ──NVLink── GPU 5 ──NVLink── GPU 6 ──NVLink── GPU 7 │
│ │
│ H100 NVLink:每张卡有 18 根 NVLink,每个方向 900 GB/s │
│ A100 NVLink:每张卡有 12 根 NVLink,每个方向 300 GB/s │
│ │
│ 全互联:任意两张卡之间可以直接通信,无需经过其他卡 │
│ → 张量并行(TP)需要 GPU 间高带宽通信,必须在同一节点内 │
│ │
│ NVSwitch:节点内的全速交换机 │
│ → 如果卡数超过 NVLink 直连数量,通过 NVSwitch 互联 │
│ │
└─────────────────────────────────────────────────────────────┘多机集群拓扑——RDMA 和 InfiniBand
┌─────────────────────────────────────────────────────────────┐
│ 多机集群网络拓扑 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 节点内(单机 8 卡): │
│ NVLink:900 GB/s × 双向 │
│ → 延迟:~1 μs │
│ → 用于:梯度同步(AllReduce) │
│ │
│ 节点间: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 方案 1:InfiniBand HDR(推荐) │ │
│ │ → 400 Gbps(单向)/ 800 Gbps(双向) │ │
│ │ → 延迟:~2 μs │ │
│ │ → NCCL 原生支持 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 方案 2:RoCE(RDMA over Converged Ethernet) │ │
│ │ → 也叫 iWARP / RoCEv2 │ │
│ │ → 成本低,但需要网络设备支持 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 方案 3:普通以太网(不推荐) │ │
│ │ → 延迟高、CPU 开销大 │ │
│ │ → 只适合小规模实验 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 带宽对比: │
│ NVLink(节点内):900 GB/s = 7.2 Tbps │
│ IB HDR(节点间):400 Gbps │
│ 以太网(普通):25~100 Gbps │
│ │
│ 训练影响: │
│ → Tensor 并行需要 GPU 间高带宽 → 必须 NVLink(同节点) │
│ → 数据并行需要梯度同步 → IB / RoCE 比以太网快 10 倍 │
│ │
└─────────────────────────────────────────────────────────────┘第4节:显存管理——为什么显存总是不够
PyTorch 显存分配器的工作原理
┌─────────────────────────────────────────────────────────────┐
│ PyTorch CUDA 显存分配器 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题:nvidia-smi 显示还有 20GB 显存,但 PyTorch 报 OOM │
│ │
│ 原因:PyTorch 显存分配器和 nvidia-smi 看到的不同步 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PyTorch 显存分配器行为: │ │
│ │ │ │
│ │ 申请 100MB → CUDA 分配器 查找空闲空间 │ │
│ │ → 找到一块 500MB 的空闲块 │ │
│ │ → 分配 100MB,剩余 400MB 碎片化 │ │
│ │ → 下次申请 300MB 时,最大半连续空间只有 400MB │ │
│ │ → 可以分配,但碎片化严重 │ │
│ │ → 继续申请 2GB,碎片不够 → OOM │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ PyTorch 会缓存已释放的显存(避免反复 malloc/free) │
│ → nvidia-smi 看到的是 total allocated(含缓存) │
│ → PyTorch 看到的是 usable free(可分配的连续空间) │
│ │
│ 查看 PyTorch 显存真实使用: │
│ torch.cuda.memory_summary(device=0) # 详细报告 │
│ torch.cuda.empty_cache() # 清理 CUDA 缓存(不释放 nvidia-smi 显示的空间)│
│ │
└─────────────────────────────────────────────────────────────┘训练时显存都花在哪里了
┌─────────────────────────────────────────────────────────────┐
│ 训练时显存消耗分解 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 以 Llama-3-8B 为例,BF16 精度,单卡 A100 80GB: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 模型参数(Model Weights) │ │
│ │ 8B × 2 bytes(BF16)= 16 GB │ │
│ │ 梯度(Gradients) │ │
│ │ 8B × 2 bytes(BF16)= 16 GB │ │
│ │ 优化器状态(Optimizer States) │ │
│ │ 8B × 4 bytes(FP32)= 32 GB │ │
│ │ │ │
│ │ 小计:16 + 16 + 32 = 64 GB │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 2. Activation(中间激活值) │ │
│ │ 每个 Transformer 层: │ │
│ │ Q/K/V/O 投影的中间结果 │ │
│ │ Attention Score 矩阵(seq_len × seq_len) │ │
│ │ 8B 模型 ≈ 32 层 │ │
│ │ Batch=1, seq_len=4096: │ │
│ │ Activation ≈ 2 GB / layer × 32 = 64 GB │ │
│ │ 随 Batch Size 和 Sequence Length 线性增长 │ │
│ │ │ │
│ │ ⚠️ 这是最容易导致 OOM 的部分 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 3. 其他开销 │ │
│ │ 临时 buffer、碎片、缓存 ≈ 2~5 GB │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 总计:64 + 64 + 5 = 133 GB → 80GB 单卡放不下! │
│ 必须用分布式策略 + 优化技术 │
│ │
└─────────────────────────────────────────────────────────────┘Roofline Model——理解 GPU 的真实瓶颈
┌─────────────────────────────────────────────────────────────┐
│ Roofline Model:算力 vs 带宽哪个是瓶颈 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 横轴:计算强度(Arithmetic Intensity) │
│ = 浮点运算次数 / 字节数(从显存读取的数据量) │
│ │
│ 纵轴:实际性能(TFLOPS) │
│ │
│ A100 FP16 峰值:312 TFLOPS │
│ A100 HBM 带宽:2 TB/s │
│ │
│ 1000 │ ╱── Peak (312 TFLOPS) │
│ │ ╱ │
│ TFLO │ ╱ │
│ PS │ ╱ │
│ │ ╱ │
│ │ Roofline ╱ │
│ │ ╱ slope = Bandwidth (2 TB/s) │
│ │ ╱ │
│ │ ╱ │
│ │ ╱ │
│ │ ╱ │
│ 10 │──────╱─────────────────────────────────────────────── │
│ │ ╱ │
│ │ ╱ │
│ └─────────────────────────────────────────────────── │
│ 低算力强度 高算力强度 │
│ (内存瓶颈) (计算瓶颈) │
│ │
│ 应用到 LLM 训练: │
│ → Attention 计算:算力强度低 → 带宽瓶颈 │
│ → FFN 计算:算力强度高 → 计算瓶颈 │
│ → 混合精度 BF16:减轻计算瓶颈,放大带宽瓶颈 │
│ │
│ 优化方向: │
│ → 带宽瓶颈:提升带宽(换 HBM3)、减少显存访问(融合算子) │
│ → 计算瓶颈:提升算力(换 H100)、减少精度损失(FP8) │
│ │
└─────────────────────────────────────────────────────────────┘升华:GPU 硬件的工程启示
┌─────────────────────────────────────────────────────────────┐
│ 从 GPU 硬件看 AI Infra 的设计哲学 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 带宽比算力更稀缺 │
│ → 3.35 TB/s 的显存带宽 vs 989 TFLOPS 的算力 │
│ → 大量优化工作在减少显存访问,而不是提升计算 │
│ → FlashAttention:用 SRAM 换 HBM 带宽 │
│ → 算子融合:减少中间结果的显存读写 │
│ │
│ 2. 显存容量是最硬的约束 │
│ → 80GB HBM 是物理上限,无法通过软件绕过 │
│ → 所有分布式策略的出发点都是"显存不够怎么分" │
│ → 量化/剪枝/LoRA:都是在显存约束下求最优解 │
│ │
│ 3. 通信带宽决定并行策略的选择 │
│ → NVLink(同节点内)vs IB(跨节点)差距 10 倍 │
│ → 张量并行必须用 NVLink,跨节点只能用数据并行 │
│ → 这是硬件拓扑决定算法选择的典型例子 │
│ │
│ 4. 碎片化是 OOM 的隐形杀手 │
│ → 不要只看 nvidia-smi 的总量,要看 PyTorch 的实际可用 │
│ → 避免频繁创建/销毁大 tensor,用显存池管理 │
│ │
│ 一句话总结: │
│ GPU 硬件特性决定了 AI Infra 的所有设计边界。 │
│ 理解硬件,才能理解为什么框架做这样的选择。 │
│ │
└─────────────────────────────────────────────────────────────┘"AI 可查 vs 必须理解"清单
AI 可查:
✅ 不同 GPU 型号(A100/H100/H200/B200)的详细参数对比
✅ PyTorch 显存分配的详细配置(expand/garbage collection 参数)
✅ CUDA Stream 的具体 API 文档
必须理解:
🔴 GPU vs CPU 的设计哲学(并行通用 vs 串行专用)
🔴 GPU 内存层级:Register → Shared Memory → L1/L2 Cache → HBM
🔴 为什么显存容量是最硬的约束(Llama-3-8B 需要 133GB vs 单卡 80GB)
🔴 PyTorch OOM 的常见原因(碎片化 vs 真的不够用)
🔴 Roofline Model:算力瓶颈 vs 带宽瓶颈
🔴 NVLink vs InfiniBand vs PCIe 的带宽差异,以及对并行策略的影响学习状态:🟡 开始学习