Back to blog

GPU 架构:内存层次、CUDA 与张量核心

Michael Brenndoerfer《GPU Architecture:Memory Hierarchy, CUDA and Tensor Cores》全文中文翻译。从第一性原理讲解 GPU 内存层次与带宽阶梯、CUDA 核心与 SM/warp/SIMT 执行模型、张量核心 WMMA 与混合精度、Roofline 模型、GPU 规格解读、消费级 vs 数据中心 GPU,以及内存/带宽/精度/功耗四大实际约束,附 PyTorch 代码示例。

GPU 架构:内存层次、CUDA 与张量核心

本文是 Michael Brenndoerfer《GPU Architecture: Memory Hierarchy, CUDA and Tensor Cores》(2026,深度学习系统教材章节)的完整中文翻译。以 Hopper/H100 为主要示例。

原文:https://mbrenndoerfer.com/writing/gpu-architecture-memory-hierarchy-cuda-tensor-cores

与 Blackwell 系列的关系:本文讲的是通用 GPU 架构原理(以 H100 为例);Blackwell GPU 架构综述则是具体新架构的实测取舍。


你在本书中遇到的每一个大语言模型都是在 GPU 上训练的。GPT-4 需要数万块 GPU 连续运行数月;Llama 3 70B 用约 15,000 块 H100 训练了数周。然而 GPU 硬件常常被当作黑盒:你调用 model.cuda(),训练循环变快了,然后就不再深究。理解 GPU 内部究竟发生了什么,能让你从硬件的使用者变成能够推理训练瓶颈、内存约束和性能优化的人。

本章讲解使 GPU 加速深度学习成为可能的硬件基础。你将学到:GPU 内存层次如何决定什么数据放在快速内存还是慢速内存;CUDA 核心如何执行驱动每一次矩阵乘法的浮点运算;张量核心如何通过专门化矩阵运算达到峰值吞吐;以及如何解读 GPU 规格表来选择硬件、理解性能宣称。这些概念直接适用于后续章节的每一个训练决策——从 batch size 选择、混合精度训练,到跨越数千设备的分布式策略。

这里的视角刻意采用「硬件优先」。你在全书中学到的技术——自注意力、层归一化、前馈网络——最终都以 GPU 计算单元上的一系列操作来执行。理解硬件能揭示为什么某些设计选择主导了现代 LLM 架构:为什么大矩阵乘法优于分支密集的代码;为什么 GELU 这类激活函数以核融合而非顺序操作实现;为什么序列长度历来是扩展代价最高的维度。


为什么 GPU 主导深度学习

深度学习负载由一种操作主导:矩阵乘法。训练一个 Transformer 在每次前向和反向传播中涉及数千次矩阵乘法。每一次注意力计算、前馈网络中的每一次线性投影、每一次触及学习权重矩阵的 embedding 查表,都归约为通用矩阵乘法。朴素的 CPU 也能做这些乘法,但 CPU 是为串行、延迟敏感的负载优化的,而非大规模并行。

关键洞见是:矩阵乘法是**尴尬并行(embarrassingly parallel)**的。要计算乘积 C=AB\mathbf{C} = \mathbf{A}\mathbf{B},其中 A∈Rm×k\mathbf{A} \in \mathbb{R}^{m \times k}、B∈Rk×n\mathbf{B} \in \mathbb{R}^{k \times n},每个输出元素 CijC_{ij} 为:

Cij=∑l=1kAil⋅BljC_{ij} = \sum_{l=1}^{k} A_{il} \cdot B_{lj}

其中:

  • CijC_{ij}:输出矩阵第 ii 行、第 jj 列的元素
  • AilA_{il}:左矩阵第 ii 行、第 ll 列位置的元素
  • BljB_{lj}:右矩阵第 jj 列、第 ll 行位置的元素
  • kk:共享的内维,也称收缩维(contraction dimension)

每个输出元素 CijC_{ij} 都可以独立于其他所有输出元素计算。共有 m×nm \times n 个这样的元素,每个需要 kk 次乘加运算。一颗 16 核 CPU 可以同时算 16 个,而现代 GPU 可以同时算数万个。

这种并行度的差异不只是「更快」:它是一种完全不同的计算范式。矩阵乘法上 1000 倍的加速,改变了哪些模型根本可训练。那些理论上有趣但在 CPU 上实际不可行的架构,在 GPU 上成了现代 AI 的主力。Transformer 本身,凭其二次方注意力复杂度和巨大的权重矩阵,如果训练必须在 CPU 上进行,就永远不会成为主流。

矩阵乘法的并行性也干净地映射到 GPU 的物理结构上。GPU 不是一个强大的处理单元,而是数千个更简单单元的集合。每个单元处理计算的一小块,所有块同时推进。结果是:总工作量完成所需的时间,大致等于单个 CPU 核处理一个元素的时间,但所有元素同时完成。


CPU vs GPU 设计哲学

CPU 和 GPU 的设计哲学反映了根本不同的工程优先级,由各自设计所要处理的负载类型塑造。

CPU 为延迟优化:尽可能快地执行单条复杂指令流。它们把大部分硅片面积用于缓存、分支预测器、乱序执行单元和推测执行硬件。这些机制最小化每条单独指令的完成时间。现代 CPU 核能以极高的速度和灵活性执行单线程,处理复杂控制流、不规则内存访问模式和数据相关分支的计算。这正是通用软件所需要的:Web 服务器、编译器、操作系统和数据库都依赖 CPU 高效处理多变、不可预测指令序列的能力。

GPU 为吞吐优化:同时执行许多简单指令流,即便每条单独的流更慢也在所不惜。GPU 核(NVIDIA 术语称 CUDA 核)比 CPU 核简单得多。它无法做分支预测或乱序执行,而是与数千个同类核锁步执行指令。这种简单性意味着,在只能容纳几十个 CPU 核的同一块硅片上,可以塞进数千个 GPU 核。

这一取舍鲜明而深远。如果你要对一个 1000 元素的列表排序,CPU 更快,因为排序算法是串行且延迟敏感的。如果你要把两个 4096×4096 矩阵相乘,GPU 以巨大优势胜出,因为每个输出元素都能并行计算。深度学习训练几乎完全属于第二类问题:大规模、规则、可预测、彼此独立的计算。

这不只是历史的偶然。Transformer 架构就是与 GPU 硬件协同设计的。注意力机制、线性投影、前馈扩展——这些都是能饱和 GPU 并行度的矩阵运算。理解硬件有助于理解架构为何如此。

【图】CPU vs GPU 硅片分配对比:CPU 把大多数晶体管用于控制逻辑、缓存和分支预测,仅小部分用于实际计算;GPU 则反转此比例——大多数硅片用于算术单元,控制开销极小。

这种设计差异的后果体现在峰值算术吞吐上。高端 CPU 单精度浮点可能提供 1–2 TFLOP/s,而现代数据中心 GPU 在同样精度下提供数百 TFLOP/s,用专门的张量核心单元时接近 1000 TFLOP/s。GPU 达成这一点不是靠更高时钟(GPU 通常运行在 1–2 GHz,与 CPU 相近),而是靠并行运行远多得多的算术单元。


GPU 内存层次

内存访问是 GPU 负载中最常见的瓶颈。理解内存层次对于理解操作为何运行得快或慢、以及为何 FlashAttention 这类优化能带来如此戏剧性的加速至关重要。

GPU 拥有多级内存系统,映射了 CPU 的缓存层次,但大小、速度和访问模式非常不同。GPU 编程的根本挑战是让算术单元持续有数据可算。当算术单元闲置等待内存时,你就在浪费付了钱的硬件。内存层次的存在,是为了弥合 GPU 处理数据的速度与数据从主内存池到达的速度之间的巨大鸿沟。

【图】NVIDIA H100 SXM 内存层次(对数刻度带宽):带宽每向下一级约降一个数量级,从寄存器级近 70,000 GB/s 到全局 HBM 的 3.35 TB/s。共享内存与全局内存之间的巨大差距,是高性能 GPU 内核采用分块(tiling)与数据复用的主要动因。

寄存器(Registers)

寄存器是 GPU 上最快的存储。每个 CUDA 核有自己的一组寄存器,为运行其上的线程私有。寄存器访问在单个时钟周期内完成,没有任何延迟惩罚。计算中的每个中间值在被计算时都存在寄存器里:点积的部分和、临时索引变量、写回内存前的激活函数输出——执行期间它们都占用寄存器空间。

问题在于容量。每个流式多处理器(SM,GPU 的基本处理单元)有固定的寄存器文件,现代 NVIDIA GPU 上通常为 256 KB。这个寄存器预算被 SM 上同时运行的所有线程共享。如果一个内核每线程使用很多寄存器,能并发运行的线程就更少,从而降低所谓的占用率(occupancy),进而削弱 GPU 通过线程切换隐藏内存延迟的能力。

**寄存器溢出(register spilling)**发生在内核需要的寄存器超过可用数量时。编译器在编译期检测到这一点,把多余数据移到本地内存——它物理上位于慢速全局内存而非片上。溢出把单周期寄存器访问变成需要数百周期的昂贵全局内存加载。这是复杂内核中意外性能退化最常见的来源之一。当你 profile 一个内核,发现其内存流量远高于算法预期时,寄存器溢出往往是罪魁祸首。

寄存器使用与占用率的关系是定量的。假设一个 SM 能支持 2048 个线程、有 256 KB 寄存器文件。若每线程用 32 个 4 字节寄存器,即每线程 128 字节,2048 线程共需 256 KB,恰好在最大占用率下填满寄存器文件。若每线程用 64 个寄存器,则只能运行 1024 个线程,占用率减半。GPU profiling 工具会报告每内核的寄存器用量,理解这个数字有助于预测占用率。

共享内存与 L1 缓存

寄存器之下一级是共享内存和 L1 缓存,它们共享同一物理 SRAM,但可配置为在两种功能间以不同方式划分空间。在 NVIDIA Ampere 和 Hopper GPU 上,每个 SM 有最多 228 KB 可配置的 L1/共享内存,外加额外 32 KB 固定 L1 缓存。

共享内存由程序员显式管理。CUDA 内核可在启动时分配一部分共享内存,用作同一线程块内所有线程都能访问的快速便签本。这使共享内存非常适合两件事:同块内线程间通信,以及复用多个线程都需要的数据。经典用例是矩阵乘法分块:一块线程协作地把输入矩阵的一小块 tile 从慢速全局内存加载到共享内存,每个线程多次读取该 tile 以计算多个输出元素。昂贵的全局内存加载只发生一次,但数据被使用多次。这是手工优化 GEMM 内核和 cuBLAS 这类库背后的根本原理。

L1 缓存由硬件管理、透明运作。像 CPU 缓存一样,它存储最近访问的全局内存数据,并在无需回到全局内存的情况下满足对相同地址的后续访问。与共享内存不同,你不显式编程它,而是设计可能受益于缓存的访问模式:读取邻近地址(空间局部性),使为一个线程加载的数据被相邻线程复用;随时间复用相同地址(时间局部性),使缓存数据在再次需要前不被逐出。

H100 上共享内存与全局内存的带宽差异每 SM 约为 10 倍:约 33 TB/s 对 3.35 TB/s。这种不对称正是内核作者在分块策略上投入大量精力的原因。一个本会多次从全局内存读取相同数据的操作,可被重新设计为将其读入共享内存一次、在快速层中复用。

L2 缓存

L2 缓存位于 L1 与全局内存之间,被芯片上所有 SM 共享。这种芯片级共享使 L2 的用途不同于 L1。当多个 SM 恰好需要相同数据(如跨 batch 广播的权重矩阵)时,L2 可以服务所有 SM,无需每个 SM 独立从全局内存取数。在 H100 上,L2 为 50 MB,大到足以容纳小模型权重的可观一部分。H100 的 L2 带宽在整颗芯片上约为 12 TB/s。

L2 充当抵御无法通过共享内存优化的重复全局内存访问的第一道防线。即便内核代码中没有显式分块策略,硬件管理的 L2 缓存也为有中等数据复用的负载提供自动收益。关键限制是:50 MB 虽可观,却被现代 LLM 数十亿参数所矮化。大多数训练负载对权重访问的 L2 命中率很低,因为权重太大装不下、访问模式又太分散。

全局内存(HBM)

全局内存就是从业者日常口中说「GPU 内存」时所指的东西。现代训练 GPU 上,全局内存使用高带宽内存(HBM)——一种 3D 堆叠 DRAM 技术,通过垂直堆叠多个内存 die 并用数千条又短又宽的互连相连,达到远高于传统 GDDR 内存的带宽。

H100 SXM 有 80 GB HBM3、3.35 TB/s 带宽;A100 SXM 有 80 GB HBM2e、2 TB/s 带宽。对比典型 CPU 的 DDR5 内存约 100 GB/s:GPU 全局内存顺序读取快 20–30 倍。正是这惊人的带宽,使得对中等序列长度做注意力(尽管要访问完整的 n×nn \times n 注意力矩阵)成为可行。

尽管带宽惊人,全局内存仍是层次中最慢的一级,且差距很大。计算吞吐与内存带宽之比决定内核是计算受限还是内存受限。当计算吞吐远高于内存所能供给时,GPU 核就闲置等待数据。这个比值称为算术强度(arithmetic intensity),是 GPU 性能分析的核心概念。

算术强度衡量每访问一字节内存执行多少计算:

Arithmetic Intensity=FLOPBytes accessed\text{Arithmetic Intensity} = \frac{\text{FLOP}}{\text{Bytes accessed}}

其中 FLOP 统计内核执行的总浮点运算数,Bytes accessed 统计从全局内存读写的总字节数。高算术强度的操作(如大矩阵乘法)是计算受限的,因为 GPU 能在不等内存的情况下让算术单元保持繁忙;低算术强度的操作(如逐元素激活)是内存受限的,因为 GPU 在耗尽算术吞吐前就耗尽了内存带宽。

张量核心大幅提升计算吞吐,把计算-内存比推得更高,使更多操作变成内存受限。H100 张量核心产出近 1000 TFLOP/s,意味着即便中等复杂度的操作,也必须达到高算术强度才能保持计算受限。

内存容量与模型规模

全局内存容量决定哪些模型能装进单块 GPU。一个 FP32 参数占 4 字节,BF16 占 2 字节。但训练所需内存远多于模型参数本身。训练内存预算包含四个不同部分:

  • 参数:BF16 每参数 2 字节,FP32 每参数 4 字节
  • 梯度:与参数同大小;反向传播中每个参数累积一个梯度
  • 优化器状态:Adam 每参数存两个额外张量(一阶矩估计 mm 与二阶矩估计 vv),各为 FP32,合计每参数 8 字节
  • 激活:每层的输出,存下来供反向传播使用;大小取决于 batch size、序列长度和模型维度

对于用 Adam 混合精度训练(参数与梯度用 BF16、优化器状态用 FP32)的 70 亿参数模型,权重、梯度和优化器状态的最小内存需求为:

Memorymodel≈Nparams×(2+2+8) bytes\text{Memory}_{\text{model}} \approx N_{\text{params}} \times (2 + 2 + 8)\text{ bytes}

Memorymodel≈7×109×12=84 GB\text{Memory}_{\text{model}} \approx 7 \times 10^9 \times 12 = 84\text{ GB}

其中第一个 2 字节项覆盖 BF16 参数,第二个 2 字节项覆盖 BF16 梯度,8 字节项覆盖 FP32 Adam 状态。这在还没加上任何激活之前,就已超过单块 H100 的 80 GB 容量。加上一批序列的激活会让情况明显更糟。这种内存压力正是分布式训练存在的首要原因:不是为了训练更快,而是为了能够训练。

对推理而言情况更乐观。你只需要参数,不需要梯度或优化器状态。一个 7B BF16 模型推理需 14 GB,轻松装进单块 H100。这就是为什么推理有时能在比训练所需小得多的硬件上运行。


CUDA 核心与 SM 架构

NVIDIA GPU 的计算核心是流式多处理器(SM)。理解 SM 架构能解释 GPU 如何并行执行数千个操作,以及为什么某些编程模式高效而另一些不然。

流式多处理器

SM 是一个自包含的处理单元,拥有自己的计算核、寄存器文件、共享内存、指令调度器和 warp 派发器。现代 GPU 有许多 SM:H100 SXM 有 132 个,A100 有 108 个,RTX 4090 有 128 个。当你从 PyTorch 启动一个 CUDA 内核(每当你对 CUDA 张量调用一个操作时都会自动发生)时,其线程块被分布到可用的 SM 上。每个块完全运行在一个 SM 上,但若资源允许,多个块可以同时运行在同一 SM 上。

每个 SM 包含多种类型的计算核,各专用于一类操作:

  • CUDA 核(FP32 单元):用于单精度浮点运算,是通用主力。
  • FP64 单元:用于双精度运算。数据中心 GPU 含大量 FP64 能力;消费级 GPU 大幅削减它,因为大多数深度学习不需要。
  • INT32 单元:用于整数运算,用在索引计算和某些量化推理负载中。
  • 张量核心:用于混合精度矩阵乘加运算,下一节详述。
  • 特殊功能单元(SFU):用于正弦、余弦、平方根倒数等超越函数,出现在激活函数和注意力计算中。

在 H100 SM 上有 128 个 CUDA 核(FP32 单元)。跨 132 个 SM,共 16,896 个 CUDA 核。每个核每时钟周期能做一次乘加运算(两次浮点运算)。在 1.98 GHz boost 时钟下,峰值 FP32 CUDA 核吞吐约 67 TFLOP/s,与 NVIDIA 官方规格相符。

Warp 与 SIMT 执行

GPU 不独立执行单个线程。线程被组织成 32 线程的 warp,warp 中所有线程同时对不同数据执行相同指令。这种执行模型称为 SIMT:单指令多线程(Single Instruction, Multiple Threads)。

SIMT 既是 GPU 效率的来源,也是其最重要的约束。效率来自指令取指与译码开销被摊到 32 个线程上:取一条指令、派发 32 个操作,比为 32 个独立 CPU 核取 32 条独立指令高效 32 倍。约束是:warp 中全部 32 个线程必须在同一时刻执行相同指令。

SIMT 意味着分支在 GPU 上的处理方式与 CPU 不同。当 warp 中线程走不同分支(例如一半线程满足 if 条件、一半不满足)时,warp 会串行化:先执行 true 分支、屏蔽不参与的线程(它们不做工作但消耗时间),再执行 false 分支、屏蔽另一批线程。这称为 warp 分支发散(warp divergence)。最坏情况下,当全部 32 个线程走不同路径时,吞吐降至最大值的 1/32。

优化良好的 GPU 代码通过确保同 warp 内线程遵循相同控制流来最小化分支发散。带填充 token 的 Transformer 推理是一处可能出现发散的地方:若同 warp 内线程处理带不同填充掩码的 token,其执行路径就会发散。精心的 batching 策略把长度相近的序列分组以减轻此效应。

【图】warp 分支发散对有效吞吐的影响:当 warp 中全部 32 线程走同一分支,利用率 100%;任何发散都强制串行执行各分支;最大性能损失发生在线程在两个分支间均分时,有效吞吐减半至 50%。

占用率与延迟隐藏

GPU 性能的一条根本原则是:延迟通过并发来隐藏,而非通过缓存来消除。当一个 warp 发出需 400–800 周期完成的内存加载(一次到全局 HBM 的典型往返)时,SM 不会停顿。相反,它立即切换到另一个已准备好执行当前指令的 warp。若可用 warp 足够多,SM 永不闲置:一个 warp 等内存时,数十个其他 warp 继续计算。

占用率衡量活跃 warp 数与 SM 所能支持的最大 warp 数之比。H100 SM 最多支持 64 个活跃 warp。更高占用率通常意味着更好的延迟隐藏,因为有更多 warp 填补停顿 warp 留下的空隙。然而占用率受三种资源约束:

  • 寄存器文件:每线程寄存器越多,固定寄存器预算下能容纳的线程越少,占用率越低
  • 共享内存:每线程块分配的共享内存越多,能同时运行在 SM 上的块越少
  • 线程数:现代 GPU 上每块最多 1024 线程,若资源允许每 SM 可承载多个块

占用率与性能的关系并不简单。最大占用率不总意味着最大性能。一旦存在足够 warp 隐藏主导延迟源,再加 warp 的收益递减。若算术强度足够高使计算单元保持繁忙,一个 50% 占用率的内核也可能达到最大吞吐的 95%。理解你内核的瓶颈(内存延迟、内存带宽还是计算吞吐)决定了应多激进地为占用率优化。

一个有用的心智模型:把 warp 想成工厂里操作多台机器的工人。若机器有很长的设置时间(内存延迟),你需要很多工人,这样一个工人等机器设置完成时,其他人在用别的机器。但若机器运行很快(高算术强度),你只需要足够多的工人在任一时刻让所有机器繁忙。超过这个阈值再塞工人,只会让他们争抢同样有限的机器。

线程块结构与网格启动

当 PyTorch 启动一个 CUDA 内核时,整个计算被组织成一个线程块的网格(grid)。每个块含固定数量的线程(最多 1024),共享同一共享内存的访问并能彼此同步。网格中的块独立执行,可按任意顺序调度到各 SM 上。

这种两级结构(块的网格、线程的块)并非任意。它反映了硬件中可用的两级并行:SM 级并行(多个 SM 同时运行不同块)和 SM 内并行(一个块内多个 warp 运行在 SM 的计算单元上)。高效 GPU 代码同时利用两级。

对矩阵乘法而言,标准线程块结构让每个块负责计算输出矩阵的一个 tile。块内线程协作把输入矩阵的 tile 加载到共享内存,然后每个线程用共享内存中的数据计算输出 tile 的一个或多个元素。这一结构自然地把分块算法映射到两级硬件并行上。


张量核心(Tensor Cores)

CUDA 核执行标准标量浮点运算:每核每周期一次乘或一次加。张量核心是完全不同类型的计算单元,专为矩阵乘加运算设计。它们以单条 warp 级指令计算整个小矩阵乘积,而非要求为每个元素做单独的标量操作,从而为该操作带来戏剧性更高的吞吐。

NVIDIA Volta(2017)引入张量核心,对 LLM 训练的重要性不亚于 Transformer 架构本身。没有张量核心,BF16 矩阵乘法的计算吞吐将被限制在 FP32 CUDA 核速率。有了张量核心,吞吐提升 7–15 倍,直接使今天所见规模的模型训练成为可能。

Warp 矩阵乘加(WMMA)

张量核心在矩阵的小 tile 上计算以下操作:

D=A⋅B+C\mathbf{D} = \mathbf{A} \cdot \mathbf{B} + \mathbf{C}

其中:

  • A\mathbf{A}:低精度(FP16 或 BF16)的小输入矩阵 tile(如 16×1616 \times 16)
  • B\mathbf{B}:另一个低精度的 16×1616 \times 16 输入矩阵 tile
  • C\mathbf{C}:高精度(FP32)的 16×1616 \times 16 累加器矩阵 tile
  • D\mathbf{D}:输出矩阵 tile,同样以 FP32 累加和存储

张量核心以单条指令、在一个 warp 的层面上计算这个融合乘加。在 H100 上,每个 SM 有 4 个张量核心单元,它们对稠密 FP16/BF16 操作合计提供每 SM 494 TFLOP/s(带稀疏加速达 989 TFLOP/s)。对比整颗芯片 FP32 CUDA 核的 67 TFLOP/s——这约 7 倍的加速来自硬件专门化:张量核心不是灵活的标量算术单元,而是固定功能电路,以远少于标量核所需的时钟周期实现矩阵乘加融合操作。

操作 D=A⋅B+C\mathbf{D} = \mathbf{A} \cdot \mathbf{B} + \mathbf{C} 具有张量核心硬件所利用的特定结构。对一个 16×1616 \times 16 tile,计算 A⋅B\mathbf{A} \cdot \mathbf{B} 需要 16×16×16=409616 \times 16 \times 16 = 4096 次乘加运算(每次产生 2 FLOP)。硬件以流水线方式同时执行所有这些。关键洞见是:这一特定操作(小 tile 矩阵乘加)在深度学习中如此常见,以至于为它专门投入硬件极具成本效益。

大矩阵乘法(如把一个 4096×40964096 \times 4096 权重矩阵与一批激活相乘)通过把大操作分块成许多小的 16×1616 \times 16 张量核心操作、再累加来计算。cuBLAS 和 PyTorch 的 autograd 引擎自动处理这种分块。

混合精度与累加

在 FP16 或 BF16 中计算、在 FP32 中累加,是混合精度训练稳定性的核心。让我们理解为何这很重要。

FP16 有 5 位指数、10 位尾数,覆盖约 6×10−86 \times 10^{-8} 到 6550465504 的值。FP32 有 8 位指数、23 位尾数,覆盖约 1.2×10−381.2 \times 10^{-38} 到 3.4×10383.4 \times 10^{38}。在点积中累加数千个乘积时,舍入误差会累积。若每个乘积略有偏差、累加又在 FP16 中进行,舍入误差会复合并可能污染结果。在 FP32 中累加为运行和多给 13 位尾数精度,即便每个单独乘积以 FP16 计算,累加结果也保持准确。

**BF16(Brain Float 16)**使用不同的位布局:8 位指数、7 位尾数。它的指数与 FP32 完全一致,给它相同的动态范围。代价是降低的尾数精度(7 位对 FP32 的 23 位)。对梯度计算而言——梯度的尺度变化巨大,但其精确尾数值不如其大致量级重要——BF16 比 FP16 数值上更稳定。FP16 常需要 loss scaling(把 loss 乘以一个大常数,将梯度量级移入可表示范围),而 BF16 训练通常完全无需 loss scaling。这一实际优势使 BF16 成为现代 LLM 训练的默认精度。

**TF32(TensorFloat-32)**是 Ampere 的创新,连接了 FP32 与 BF16。它使用 FP32 的指数(8 位)和 10 位尾数(多于 BF16 的 7 位),内部装进 19 位格式。TF32 操作接受 FP32 输入,在张量核心上以 TF32 精度内部计算,返回 FP32 输出。无需改代码;当操作 FP32 张量时,PyTorch 和 CUDA 库自动使用 TF32,对多数训练场景在精度影响极小的情况下带来相对 FP32 CUDA 核约 10 倍的加速。

张量核心代际

NVIDIA 在张量核心设计上快速迭代,每一代增加新精度并大幅提升吞吐:

代GPU峰值 FP16 稠密 (TFLOP/s)新增精度
第 1 代Volta (V100)125FP16
第 2 代Turing (T4)130INT8、INT4
第 3 代Ampere (A100)312BF16、TF32、FP64
第 4 代Hopper (H100)494(稀疏 989)FP8

Turing 加入 INT8 为推理期量化打开了张量核心加速。Ampere 加入 BF16 是关键的训练使能者。Hopper 引入的 FP8 从训练稳定性角度仍在成熟中(需非常精细的缩放),但其相对 FP16/BF16 的 2 倍吞吐优势,使它对大规模预训练越来越有吸引力。

结构化稀疏

H100 张量核心支持细粒度结构化稀疏,具体是 2:4 稀疏模式——矩阵中每连续 4 个值必须恰好有 2 个为零。满足此模式时,硬件可跳过零乘数对、有效地把吞吐翻倍,这解释了 H100 规格中 494 TFLOP/s(稠密)与 989 TFLOP/s(2:4 模式稀疏)的差异。

结构化剪枝技术可在训练期间或之后强加此模式,同时恢复大部分原始模型精度。过程包括:在每组 4 个中识别两个最大幅值的权重,将较小的两个置零,然后微调以恢复精度。所得模型恰好有 50% 的权重参数按所需模式置零,无任何软件开销即可启用完整硬件稀疏加速。

【图】H100 SXM 各计算类型峰值吞吐对比:FP32 CUDA 核提供 67 TFLOP/s 作为基线。张量核心随更低精度格式大幅扩展吞吐,INT8 达 1,979 TFLOP/s、FP8 达 3,958 TFLOP/s。FP16/BF16 条反映稠密张量核心速率 989 TFLOP/s,用结构化 2:4 稀疏可翻倍至此值。


GPU 规格与规格表解读

为训练选择硬件或评估新 GPU 型号时,几项关键规格决定其适用性。理解每个数字的含义,能让你穿透营销话术、做出明智的硬件决策。

关键规格解释

**内存容量(GB)**为模型规模设定硬上限。用 Adam 混合精度训练,权重、梯度和优化器状态每参数约需 12–16 字节,外加随 batch size 和序列长度扩展的激活内存。BF16 推理每参数需 2 字节。这些数字直接决定模型能装进一块 GPU 还是需要多 GPU 配置。

**内存带宽(TB/s)**决定内存受限操作的吞吐。逐元素激活、层归一化、embedding 查表和 softmax 都是内存受限的:GPU 处理数据的速度快于从全局内存加载它的速度。对这些操作,带宽翻倍则执行时间减半。这解释了为何 H100 的 3.35 TB/s 带宽(比 A100 的 2.0 TB/s 高 67%)对由小 batch 内存受限操作主导的推理负载特别有价值。

**峰值 FP16/BF16 吞吐(TFLOP/s)**代表用张量核心的每秒最大浮点运算数,是训练吞吐的主导指标。在 GPU 间做同类对比时,始终比较稠密数字(不含结构化稀疏),因为稀疏吞吐需要特定权重模式,可能不适用于你的负载。

**峰值 FP32 吞吐(TFLOP/s)**对需要 FP32 的操作重要:Adam 中的优化器更新(一阶、二阶矩估计)、跨微批的梯度累积,以及某些归一化操作。混合精度训练中,约一半计算时间涉及 FP16/BF16 矩阵乘法,一半涉及 FP32 开销操作。

**NVLink 带宽(GB/s)**支配多 GPU 配置中的 GPU 间通信。NVLink 不经 CPU 直连 GPU,实现比 PCIe 高 5–10 倍的带宽。H100 每 GPU 提供 900 GB/s 双向 NVLink 带宽。高 NVLink 带宽对张量并行至关重要——模型层被切分到多 GPU、每次操作后须通信中间激活。对带梯度同步的数据并行,较低的 GPU 间带宽更可容忍,因为通信发生得较少。

**TDP(热设计功耗,瓦)**设定最大持续功耗。单机架 8 块 H100 SXM 仅 GPU 就抽取约 5.6 kW,另加 2–3 kW 用于网络和计算开销。大型训练集群抽取兆瓦级功率。TDP 决定硬件价格之外的总拥有成本,包括供电基础设施、散热系统和电费。对本地部署,供电约束往往比空间约束更早限制 GPU 密度。

【图】GPU 规格对比:横跨训练导向的数据中心卡(A100、H100、H200)和高端消费卡(RTX 4090、RTX 3090)。内存带宽(左上)、峰值 FP16 张量核心吞吐(右上)、内存容量(左下)、TDP(右下)是评估训练适用性最重要的四项指标。H200 以 141 GB 内存容量脱颖而出,几乎是其前代的两倍。


Roofline 模型与算术强度

Roofline 模型提供了一个有原则的框架,用于理解一个给定操作是计算受限还是内存受限,以及各种情形下的最大可达性能。它得名于其在双对数图上的特征形状:从左侧上升的斜线(内存受限区)和右侧的水平天花板(计算受限区)。两线相交的**脊点(ridge point)**代表成为计算受限所需的最小算术强度。

对带 FP16 张量核心的 H100 SXM,脊点为:

Ridge Point=Peak FP16 ThroughputMemory Bandwidth=989×10123.35×1012≈295 FLOP/byte\text{Ridge Point} = \frac{\text{Peak FP16 Throughput}}{\text{Memory Bandwidth}} = \frac{989 \times 10^{12}}{3.35 \times 10^{12}} \approx 295\text{ FLOP/byte}

这意味着一个操作每从全局内存读一字节,必须执行至少 295 次浮点运算,才能是计算受限而非内存受限。大矩阵乘法轻松超过此阈值。两个 4096×40964096 \times 4096 FP16 矩阵相乘读取约 64 MB 数据,但执行 2×40963≈1372 \times 4096^3 \approx 137 十亿 FLOP,给出:

Arithmetic IntensityMatMul=137×10964×106≈2140 FLOP/byte\text{Arithmetic Intensity}_{\text{MatMul}} = \frac{137 \times 10^9}{64 \times 10^6} \approx 2140\text{ FLOP/byte}

这是脊点的 7 倍:计算深度计算受限。增加更多内存带宽不会让它更快;只有更快的张量核心才行。

与之对比一个逐元素操作,如对 4096×40964096 \times 4096 FP16 矩阵应用 ReLU。它读取 32 MB 数据、执行约 1600 万 FLOP(每元素一次比较、一次取最大),给出:

Arithmetic IntensityReLU=16×10632×106=0.5 FLOP/byte\text{Arithmetic Intensity}_{\text{ReLU}} = \frac{16 \times 10^6}{32 \times 10^6} = 0.5\text{ FLOP/byte}

这是脊点的 1/590。该操作完全内存受限。张量核心无关紧要:ReLU 不是矩阵乘加,用不了它们。更多张量核心吞吐对加速 ReLU 毫无用处;只有更高内存带宽才有帮助。

这一分析对架构设计有直接影响。内存受限区的操作常可被融合在一起:不必从 HBM 读数据、应用 ReLU、写回、再读回来应用 dropout,你可以把这些融合成一个内核,读一次、应用所有变换、再写回。这就是 FlashAttention 和基于 Triton 的 Transformer 操作实现这类核融合库对真实训练吞吐如此重要的原因。

【图】H100 SXM 的 Roofline 模型(用 FP16 张量核心吞吐和 HBM3 内存带宽):脊点(约 295 FLOP/byte)左侧的操作内存受限、受内存带宽限制;右侧的操作计算受限、受张量核心吞吐限制。关键 Transformer 操作按其近似算术强度放置,显示在典型条件下只有大矩阵乘法是计算受限的。


比较消费级与数据中心 GPU

规格表揭示了消费级与数据中心 GPU 之间超越原始数字的结构性差异。

消费级 GPU(如 RTX 4090 和 3090)为游戏、内容创作和准专业负载设计。它们有不错的 FP16 张量核心吞吐(4090 为 165 TFLOP/s)和适度内存(24 GB GDDR6X),但缺 NVLink、FP64 吞吐削减、使用消费级散热方案。对单 GPU 微调或最多约 100 亿参数模型的推理,它们提供出色的性价比。RTX 4090 能以 BF16 运行 7B 模型,甚至用 llama.cpp 等工具运行量化 13B 模型。

数据中心 GPU(A100、H100、H200)为多 GPU 横向扩展设计。它们更高的 TDP(H100 为 700W,对 4090 的 450W)伴随更健壮的散热管理、纠错内存(ECC)和高速 NVLink 互连。ECC 内存对长训练运行很重要:权重或梯度中一个不被纠正的单比特错误可能污染整个训练运行,可能浪费数周计算。NVLink 使得训练装不进单块 GPU 内存的模型所需的张量并行策略成为可能。

H200 代表内存容量的重大飞跃:141 GB HBM3e、4.8 TB/s 带宽。这一扩展内存使得在单块 GPU 上装下更大模型成为可能,减少了对某些多 GPU 通信模式的需求,简化了超大模型在推理时的部署。


代码实现

理解 GPU 硬件在你能直接测量它时最有用。PyTorch 提供全面工具来检查 GPU 内存使用并基准测试操作性能。

检查 GPU 内存与设备属性

先从探索 GPU 内存容量和设备特性开始:

import torch
# 检查 CUDA 是否可用并检查设备属性
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Device: {device}")
if torch.cuda.is_available():
    props = torch.cuda.get_device_properties(0)
    total_memory_gb = props.total_memory / 1024**3
    print(f"GPU: {props.name}")
    print(f"Total memory: {total_memory_gb:.1f} GB")
    print(f"SM count: {props.multi_processor_count}")
    print(f"Max threads per SM: {props.max_threads_per_multi_processor}")
    print(f"Max threads per block: {props.max_threads_per_block}")
    print(f"Warp size: {props.warp_size}")
    print(f"Major/minor compute capability: {props.major}.{props.minor}")

输出(H100 代表值):

Warp size: 32(标准 NVIDIA GPU 值)
Compute capability: 8.0+ 才支持 BF16 张量核心

warp size 为 32 是烙进 CUDA 硬件的根本常量。每个启动配置都应把它当作硬约束:线程块大小为 32 的倍数,确保没有 warp 被非活跃线程部分填充。例如 48 线程的块会创建两个 warp:一个有 32 个活跃线程,一个有 16 个活跃、16 个非活跃。第二个 warp 仍占用 SM 资源却只提供一半吞吐。填充到 64 线程可消除此浪费。

compute capability 数字编码了哪些硬件特性可用。7.0(Volta)引入 FP16 张量核心;8.0(Ampere)加入 BF16 和 TF32 张量核心支持;9.0(Hopper)加入 FP8 支持和 Transformer 专用操作的新架构特性。

测量模型组件的内存占用

计算常见模型组件的内存占用和完整训练开销:

import torch
def memory_of_tensor(shape, dtype=torch.float32):
    """返回给定形状和 dtype 的张量内存(MB)。"""
    dtype_bytes = {
        torch.float32: 4,
        torch.float16: 2,
        torch.bfloat16: 2,
        torch.int8: 1,
    }
    n_elements = 1
    for s in shape:
        n_elements *= s
    return n_elements * dtype_bytes.get(dtype, 4) / 1024**2

# LLaMA 风格 Transformer 层的组件
d_model = 4096   # 隐藏维度
n_heads = 32     # 注意力头
d_ff = 16384     # FFN 隐藏维度(4× d_model)
# 注意力权重矩阵(Q、K、V、O 投影)
qkv_shape = (d_model, d_model)
attn_params = 4 * memory_of_tensor(qkv_shape, torch.bfloat16)
# FFN 权重矩阵(两个线性层,可选门控)
ffn_w1_shape = (d_model, d_ff)
ffn_w2_shape = (d_ff, d_model)
ffn_params = memory_of_tensor(ffn_w1_shape, torch.bfloat16) + memory_of_tensor(ffn_w2_shape, torch.bfloat16)
total_layer = attn_params + ffn_params

输出:

Transformer 层:d_model=4096, d_ff=16384, n_heads=32
注意力权重 (Q+K+V+O 投影): 128.0 MB (BF16)
FFN 权重 (W1+W2): 256.0 MB (BF16)
每层合计: 384.0 MB (BF16)
32 层模型(仅权重,BF16): 12.0 GB

训练内存分解:
BF16 权重: 12.0 GB
BF16 梯度: 12.0 GB
Adam 一阶矩 (FP32): 24.0 GB
Adam 二阶矩 (FP32): 24.0 GB
合计(无激活): 72.0 GB

这一计算把内存问题具体化了。权重本身只是总训练内存的一小部分。FP32 优化器状态是 BF16 权重的两倍,成为主导项。而且我们还没加上激活内存——对 4096 维模型、一批 8 条长度 2048 的序列,激活会额外增加许多 GB。这就是为什么梯度检查点(反向传播时重算激活而非存下来)在大模型训练中几乎普遍存在:它用计算换内存。

基准测试矩阵乘法性能

测量张量核心性能如何随矩阵尺寸扩展,揭示问题规模与硬件效率的关系:

import torch
import time
def benchmark_matmul(m, k, n, dtype=torch.float16, warmup=5, iters=20):
    """基准测试矩阵乘法并返回达到的 TFLOP/s。"""
    if not torch.cuda.is_available():
        return None
    a = torch.randn(m, k, dtype=dtype, device="cuda")
    b = torch.randn(k, n, dtype=dtype, device="cuda")
    # 预热以确保 JIT 编译和设备初始化完成
    for _ in range(warmup):
        c = torch.mm(a, b)
    torch.cuda.synchronize()
    start = time.perf_counter()
    for _ in range(iters):
        c = torch.mm(a, b)
    torch.cuda.synchronize()
    elapsed = time.perf_counter() - start
    avg_time = elapsed / iters
    flops = 2 * m * k * n  # 每次乘加计为 2 FLOP
    tflops = flops / avg_time / 1e12
    return tflops

# 测试从小(低效)到大(近峰值)的矩阵尺寸
sizes = [256, 512, 1024, 2048, 4096, 8192]

输出(H100 代表值):

Size | FP16 TFLOP/s | FP32 TFLOP/s | Speedup
--------------------------------------------------
256  | 0.1   | 0.1  | 2.0x
512  | 0.8   | 0.4  | 2.0x
1024 | 4.2   | 2.1  | 2.0x
2048 | 85.3  | 22.1 | 3.9x
4096 | 280.1 | 55.3 | 5.1x
8192 | 420.8 | 61.2 | 6.9x

基准测试揭示了一个重要模式:张量核心吞吐强烈依赖尺寸。小矩阵无法饱和硬件,只达到峰值吞吐的很小一部分。256×256 情形约比大矩阵所达低 1000 倍。这是因为每个 SM 都需被分配工作,而小矩阵产生的线程块不足以同时填满所有 SM。此外,对小矩阵而言,内核启动和同步开销相对计算时间变得显著。

这一发现有直接实际意义。用很小 batch size 训练是低效的,因为梯度噪声增大,且矩阵维度(batch size × 序列长度 × 模型维度)变得太小填不满 GPU。现代训练配方用梯度累积模拟大 batch,即便单 GPU 内存有限:在应用一步优化器前跨多次前向累积梯度,保持有效 batch 足够大以维持张量核心效率。

【图】方阵矩阵乘法吞吐 vs 矩阵尺寸(FP16 张量核心 与 FP32 CUDA 核):FP16 吞吐在 1024×1024 以下的小矩阵近乎为零,之后陡升,在 4096×4096 以上趋于平稳;FP32 遵循相同模式但绝对值更低。两者接近各自硬件天花板时,FP16 与 FP32 的加速比随矩阵尺寸增大。

分析内存受限 vs 计算受限操作

直接比较内存受限与计算受限操作的性能,验证 Roofline 模型的预测:

import torch
import time
def benchmark_op(fn, warmup=5, iters=20):
    """计时一个 GPU 操作,返回每次迭代的毫秒数。"""
    if not torch.cuda.is_available():
        return None
    for _ in range(warmup):
        fn()
    torch.cuda.synchronize()
    start = time.perf_counter()
    for _ in range(iters):
        fn()
    torch.cuda.synchronize()
    return (time.perf_counter() - start) / iters * 1000

n = 4096
device_str = "cuda" if torch.cuda.is_available() else "cpu"
x = torch.randn(n, n, dtype=torch.float16, device=device_str)
a = torch.randn(n, n, dtype=torch.float16, device=device_str)
b = torch.randn(n, n, dtype=torch.float16, device=device_str)
# 内存受限:逐元素 ReLU 读写整个矩阵
relu_time = benchmark_op(lambda: torch.relu(x))
# 计算受限:矩阵乘法有很高的算术强度
matmul_time = benchmark_op(lambda: torch.mm(a, b))
# 内存受限:softmax 涉及对数据多次遍历
softmax_time = benchmark_op(lambda: torch.softmax(x, dim=-1))

输出(H100 代表值,4096×4096 FP16):

ReLU  (内存受限): 0.045 ms | 2980 GB/s 有效带宽
Softmax (内存受限): 0.120 ms | 2240 GB/s 有效带宽
MatMul (计算受限): 1.820 ms | 298.3 TFLOP/s

测量结果证实了 Roofline 模型。内存受限操作(ReLU、softmax)逼近硬件内存带宽天花板,而矩阵乘法逼近张量核心计算天花板。注意内存受限的 4096×4096 ReLU 的绝对时间比矩阵乘法短得多:内存受限操作快速完成不是因为它们每字节快,而是因为它们每字节做的工作很少。矩阵乘法绝对耗时更长,但把那些相同字节处理得高效得多。

这一区别对实际优化很重要。当你 profile 一个训练步、发现大部分墙钟时间花在矩阵乘法上,你很可能计算受限,改进之路是更高的张量核心利用率(更大 batch、更好的分块)。当归一化层和激活函数主导时,你内存受限,改进之路是核融合(减少全局内存读写次数)。

训练期间监控 GPU 内存

实现一个简单内存追踪器,观察前向传播期间的分配模式:

import torch
import torch.nn as nn
def get_gpu_memory_mb():
    """返回已分配和已保留的 GPU 内存(MB)。"""
    if not torch.cuda.is_available():
        return 0, 0
    allocated = torch.cuda.memory_allocated() / 1024**2
    reserved = torch.cuda.memory_reserved() / 1024**2
    return allocated, reserved

# 构建一个小的 Transformer 风格模型用于演示
class SimpleTransformerLayer(nn.Module):
    def __init__(self, d_model=512, n_heads=8, d_ff=2048):
        super().__init__()
        self.attn = nn.MultiheadAttention(d_model, n_heads, batch_first=True)
        self.ff1 = nn.Linear(d_model, d_ff)
        self.ff2 = nn.Linear(d_ff, d_model)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.gelu = nn.GELU()
    def forward(self, x):
        attn_out, _ = self.attn(x, x, x)
        x = self.norm1(x + attn_out)
        ff_out = self.ff2(self.gelu(self.ff1(x)))
        return self.norm2(x + ff_out)

输出(示意):

Batch: 16 sequences x 256 tokens x 512 dims
模型参数: 12.0 MB (FP32)
在 GPU 上你会看到:
  ~8 MB 输入张量
  ~50-100 MB 前向传播期间的激活
  ~8-16 MB 反向传播期间的梯度

前向与反向传播期间的内存分解揭示了为何激活内存随 batch size 和序列长度增长。为反向传播存储的激活必须保留每个中间计算的输出,以便反向传播时计算梯度。对一个带长上下文的 32 层模型,这些激活可轻易超过权重内存 5–10 倍,使**激活重算(梯度检查点)**成为大规模训练的标准做法。


局限与实际影响

GPU 硬件为深度学习提供了卓越性能,但若干实际约束塑造了模型实际如何训练和部署。

内存容量是训练大模型的绑定约束。 单块 GPU 能装下的与最先进模型所需之间的差距,随每一代模型而拉大。单块 80 GB 的 H100 装不下 GPT-4 级模型,连推理都不行,遑论训练。这催生了分布式训练策略:张量并行(把单个权重矩阵切分到多 GPU,每次矩阵乘法后需 GPU 间通信)、流水线并行(把不同层分配到不同 GPU,需层间激活传输)、数据并行(运行整个模型的多个副本,需梯度同步)。每种策略都用通信开销换内存容量,选择大规模训练硬件时 NVLink 带宽规格变得与计算吞吐同等重要。关于分布式训练的章节详述这些策略。

内存带宽天花板影响的操作比多数从业者意识到的更多。 张量核心对矩阵乘法的主导,造就了这样一种局面:矩阵运算的计算能力远超内存能为非矩阵操作供给的量。归一化层、激活函数、embedding 查表和长序列的注意力打分计算,在当前硬件上全都内存受限。核融合——把多个内存受限操作合并进单个 GPU 内核,一次读入数据、应用所有变换后再写回——是恢复效率的主要技术之一。FlashAttention、Unsloth 和基于 Triton 的内核实现,大体都是此原理的应用。全量物化注意力矩阵的自注意力的算术强度低到即便在中等序列长度也内存受限,这正是 FlashAttention 分块方法的首要动机。

精度格局正在快速演进,正确选择取决于负载。 BF16 因其稳定性和良好内存效率成为预训练标准。FP8 提供内存和带宽用量再降 2 倍,但引入量化挑战:以 FP8 计算的梯度精度非常有限,需要精细的 per-tensor 或 per-block 缩放因子。INT8 量化已成为推理标准,但需要训练后校准来设定量化阈值。理解训练的哪些部分需要 FP32(优化器状态、loss scaling、梯度累积)、哪些可安全用更低精度(前向激活、权重存储),是高效大规模训练日益重要的技能。

功耗与散热在规模上日益成为限制因素。 单块 H100 SXM GPU 抽取高达 700W。8 块 H100 的一个机架仅 GPU 本身就抽取 5.6 kW,还没算网络、服务器和散热基础设施。大型训练集群抽取数十兆瓦。供电和热管理的实际约束往往比 GPU 本身成本更限制集群密度。这解释了业界对液冷系统和专用 AI 数据中心(其功率密度是传统数据中心基础设施无法支撑的)的投资,也解释了为何总拥有成本(硬件加电力加设备寿命期内的散热)可达硬件采购价的 3–5 倍,使经济分析从简单的 compute-per-dollar 比较转移开。

消费级 GPU 对研究负载呈现真实取舍。 RTX 4090 及类似消费卡以 H100 一小部分的价格提供出色的单 GPU 性能。对单 GPU 微调、推理优化和最多约 7B 参数模型的实验研究,它们提供强劲价值。然而它们缺 NVLink 连接,使多 GPU 扩展效率较低。它们的 FP64 吞吐被大幅削减(RTX 4090 为 FP32 的 1/64,而 A100 为 1/2),这对多数训练无关,但对某些科学计算重要。它们的 24 GB 内存对量化 13B 模型推理够用,但限制训练到小模型,或需要激进的梯度检查点和小 batch size。对运行大规模实验或从头训练模型的研究者,数据中心 GPU 的特性和内存容量很快就值回成本。


总结

GPU 架构通过大规模并行和专用硬件使现代深度学习成为可能。你在本章看到的硬件约束和能力,直接推动了后续章节涵盖的训练技术。

本章要点:

  • GPU 内存层次从快速小容量的寄存器,经共享内存和 L1 缓存,到 L2 缓存,最终到全局 HBM 内存。带宽在每一级约降一个数量级。有效的内核设计在快速片上内存层最大化数据复用,以避免在较慢的片外层出现带宽瓶颈。

  • CUDA 核每周期对一对标量值执行一次乘加。它们被组织进流式多处理器,后者在 SIMT 模型下以 32 线程(warp)为组锁步执行。warp 分支发散和低占用率是利用率不足的主要来源,两者都源于没有围绕 32 线程 warp 粒度设计代码。

  • 张量核心以单条 warp 指令、在 16×16 tile 层面加速矩阵乘加运算 D=A⋅B+C\mathbf{D} = \mathbf{A} \cdot \mathbf{B} + \mathbf{C}。H100 上 FP16 和 BF16 张量核心稠密模式提供 989 TFLOP/s,约为 FP32 CUDA 核速率的 15 倍。混合精度训练用张量核心做矩阵乘法(FP16/BF16),同时在 FP32 累加以保数值稳定。

  • Roofline 模型提供了理解一个操作是计算受限还是内存受限的有原则的框架。对 H100,脊点约 295 FLOP/byte。大矩阵乘法在约 2000 FLOP/byte 处是计算受限的。ReLU 这类逐元素操作在 0.5 FLOP/byte 处是内存受限的。核融合是改进内存受限操作效率的主要工具。

  • 训练的关键 GPU 规格包括内存容量(模型能装多大)、内存带宽(内存受限操作的吞吐)、峰值 FP16 吞吐(计算受限矩阵操作的吞吐)、NVLink 带宽(张量并行的多 GPU 通信速度)和 TDP(功耗与散热要求)。

后续关于训练基础设施的章节直接建立在这些硬件基础之上。分布式训练策略围绕 GPU 内存限制和 GPU 间通信带宽设计。混合精度训练利用这里描述的张量核心能力。梯度检查点解决代码示例中量化的激活内存问题。理解硬件使所有这些更高层决策变得清晰,让你能从第一性原理推理性能。


参考