硬件基础(二):内存层次结构与互联技术
聊完了计算单元,咱们得聊聊数据怎么喂给这些计算单元。你想想看,GPU 算得再快,数据堵在路上也是白搭。这就像我当年调试一个 8 卡训练任务,发现 GPU 利用率死活上不去,最后定位到是内存带宽成了瓶颈——数据从显存搬到计算单元的路上,堵车了。
这一讲,咱们就深入看看这个「数据搬运」的体系。说白了,就是内存层次结构、芯片间互联,还有分布式存储系统。
一、内存层次结构:HBM、DDR、SRAM
先看一张图,帮你快速建立整体认知。
这张图很直观:越往上,速度越快,但容量越小。越往下,容量越大,但速度越慢。训练大模型时,你得在这两者之间找平衡。
1. SRAM:计算单元的「高速缓存」
SRAM 是离计算单元最近的内存。它有多快?延迟大概 1 纳秒左右。但代价也很明显——贵,而且占芯片面积大。所以 GPU 里的 SRAM 通常只有几十 KB 到几 MB。
我习惯把 SRAM 看作「工作台」。你想想看,你要做一道复杂的菜,工作台上只能放得下几样食材和工具。你得频繁地从冰箱(HBM)里拿东西,放到工作台上处理。
关键点:在 CUDA 编程中,__shared__ 关键字分配的就是 SRAM。合理利用共享内存,能把访存延迟从几百个时钟周期降到个位数。
我的经验:我曾经优化过一个矩阵乘法的 kernel,一开始直接读全局内存,带宽利用率只有 30%。后来改用共享内存做分块(tiling),带宽利用率直接飙到 85% 以上。嗯,这就是 SRAM 的威力。
2. HBM:GPU 的「主存」
HBM(高带宽显存)是现代 GPU 的标配。它通过 3D 堆叠技术,把多个 DRAM 芯片叠在一起,中间用硅通孔(TSV)连接。这样做的好处是——带宽极高。
拿 NVIDIA A100 来说,HBM2e 的带宽是 2 TB/s。H100 的 HBM3 更是达到了 3.35 TB/s。这个数字什么概念?相当于每秒能读取 1000 部高清电影的数据量。
| GPU 型号 | HBM 版本 | 显存容量 | 带宽 |
|---|---|---|---|
| V100 | HBM2 | 16/32 GB | 900 GB/s |
| A100 | HBM2e | 40/80 GB | 2.0 TB/s |
| H100 | HBM3 | 80 GB | 3.35 TB/s |
避坑指南:我曾经遇到过一个情况,模型参数量刚好超过显存容量,结果触发了 CPU 内存回退(host memory fallback),训练速度直接掉了 10 倍。所以,选 GPU 时一定要算好显存余量,留出至少 20% 的 buffer。
3. DDR:系统内存的「大后方」
DDR 是 CPU 的系统内存。它的带宽比 HBM 低一个数量级,但容量可以做得很大。在分布式训练中,DDR 主要用来做数据预处理和缓存。
举个例子,你用 PyTorch 的 DataLoader 加载数据时,数据先读到 DDR 里,然后通过 PCIe 传到 GPU 的 HBM 中。如果 DDR 带宽不够,数据加载就会成为瓶颈。
# 一个常见的性能瓶颈场景
# 假设你的 GPU 利用率只有 60%
# 检查一下是不是数据加载的问题
import torch
from torch.utils.data import DataLoader
# 设置 num_workers 来利用多核 CPU 预加载数据
# 我一般设置为 CPU 核心数的一半
dataloader = DataLoader(dataset, batch_size=64,
num_workers=8, # 关键参数
pin_memory=True) # 锁页内存,加速 CPU->GPU 传输
二、NVLink 与 PCIe:芯片间的「高速公路」
单卡训练已经不够用了,多卡互联是常态。这时候,卡与卡之间的互联带宽就成了关键。
1. PCIe:通用但慢速的互联
PCIe 是 CPU 和 GPU 之间的标准接口。目前主流的是 PCIe 4.0 x16,单向带宽约 16 GB/s。PCIe 5.0 翻倍到 32 GB/s。
但说实话,这个带宽在多卡训练中是不够的。你想想看,一个 7B 参数的模型,光同步梯度就要传 14 GB 的数据(每个参数 4 字节)。用 PCIe 4.0 传一次就要将近 1 秒。
关键点:PCIe 是树形拓扑,所有设备共享带宽。如果你插了 8 张卡,实际每张卡能用的带宽远低于理论值。
2. NVLink:专为 GPU 设计的「高速通道」
NVLink 是 NVIDIA 的私有互联技术。它采用网状拓扑,每张卡直接和其他卡相连,不经过 CPU。延迟低,带宽高。
A100 的 NVLink 3.0 每通道 50 GB/s,总共 12 条通道,双向带宽 600 GB/s。H100 的 NVLink 4.0 更是达到了 900 GB/s。
| 互联技术 | 带宽(单向) | 拓扑 | 适用场景 |
|---|---|---|---|
| PCIe 4.0 x16 | 16 GB/s | 树形 | CPU-GPU 通信 |
| PCIe 5.0 x16 | 32 GB/s | 树形 | CPU-GPU 通信 |
| NVLink 3.0 (A100) | 300 GB/s | 网状 | GPU-GPU 通信 |
| NVLink 4.0 (H100) | 450 GB/s | 网状 | GPU-GPU 通信 |
我的经验:在 8 卡 A100 上做数据并行训练时,我习惯用 torch.distributed 的 NCCL 后端。它会自动利用 NVLink 做梯度 all-reduce,比用 Gloo 后端(走 PCIe)快 5-8 倍。
三、分布式存储系统:数据怎么喂给集群
单机多卡还不够,现在动不动就是几十上百台机器。这时候,数据怎么高效地喂给所有 GPU,就成了大问题。
1. 分布式文件系统
常见的方案有 NFS、Lustre、GPFS 等。它们把多台机器的硬盘组合成一个统一的存储池,所有训练节点都能访问。
但这里有个坑:网络延迟。如果存储节点和计算节点之间走的是千兆以太网,那读取速度可能只有 100 MB/s 左右。对于大模型训练来说,这太慢了。
避坑指南:我曾经在一个 64 卡集群上训练,发现每次 epoch 开始都要等 10 分钟加载数据。后来排查发现,是 NFS 服务器的带宽被其他任务占满了。解决方案是改用本地 SSD 做缓存,或者用高速网络(如 InfiniBand)连接存储。
2. 数据预处理流水线
为了不让数据加载成为瓶颈,我一般会设计一个多级流水线:
- 原始数据:存在分布式文件系统上(如 HDFS 或 S3)
- 预处理:用 CPU 集群把数据预处理成二进制格式(如 TFRecord 或 MMap)
- 缓存:在每台机器的本地 SSD 上缓存一份
- 加载:训练时从本地 SSD 读取,通过 DataLoader 喂给 GPU
# 一个典型的数据流水线配置
# 假设你有 8 台机器,每台 8 张 GPU
# 1. 数据预处理(在 CPU 节点上运行)
# python preprocess.py --input /shared/data/raw --output /shared/data/processed
# 2. 训练时,每个节点先缓存数据到本地
# rsync -avz /shared/data/processed/ /local/ssd/data/
# 3. 训练脚本中设置 DataLoader
dataloader = DataLoader(
dataset,
batch_size=32,
num_workers=4, # 每个 GPU 对应 4 个 worker
prefetch_factor=2, # 预取 2 个 batch
pin_memory=True
)
3. 高性能网络:InfiniBand
当集群规模超过 32 张 GPU 时,我强烈建议上 InfiniBand。它的带宽能达到 200-400 Gbps,延迟只有微秒级。相比之下,千兆以太网只有 1 Gbps,延迟是毫秒级。
说白了,InfiniBand 就是为高性能计算设计的网络。它支持 RDMA(远程直接内存访问),数据可以直接从一个 GPU 的显存传到另一个 GPU 的显存,不经过 CPU 和系统内存。
关键点:在分布式训练中,通信开销占总时间的比例会随着 GPU 数量增加而上升。当 GPU 数量超过 64 张时,通信可能占到 30-50% 的时间。这时候,InfiniBand 和 NVLink 的配合就至关重要了。
好了,这一讲的内容就到这里。内存层次结构、芯片互联、分布式存储,这三者构成了高性能训练的「数据高速公路」。下一讲,咱们聊聊怎么在这条高速公路上跑得更快——也就是分布式训练策略。
想要更多资料,联系可可