前置讲 P3:GPU、显存与云端实验

Large Language Models: Methods and Applications · 预备知识 3。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

定位:可选的前置讲,对应原课的 HW0(申请 AWS 账号、拿到课程的云端算力额度)。建议在第 13 讲(优化与并行训练)之前读;如果你打算在云上做课程项目,越早读越好。 核心问题:训练一个 7B 模型要多少显存、多少 GPU 小时?推理为什么慢?在云上做实验怎样不丢进度、不超预算? 配套代码:code/prereq3/(3 个模块 + 4 个脚本 + 一份云端实验检查清单,全部在 CPU 上几秒内跑完) 本讲的写法:原课没有对应的课次,内容按”在云上做 LLM 实验之前必须会算的账”来组织。GPU 的规格取自公开的产品规格;模型参数量由公开配置计算,并和 PyTorch 逐个核对;计时类的数字来自作者的机器(苹果芯片的笔记本),换一台机器会不同。本讲不给任何具体的云服务价格——价格变化太快,一律当作需要你自己查的参数。


目录


0. 导读

原课的第一个作业(HW0)只有一件事:用学校邮箱注册 AWS 账号、提交账号 ID,课程会给每个人一笔云端算力额度,之后的作业和项目都在上面跑 GPU。作业说明里还特意提醒:额度超支、账号被盗用,费用可能要自己承担。

这件”行政小事”背后其实是一整套必须会算的账。在你按下”启动实例”之前,应该能回答:

  1. 我的模型放得进这块 GPU 吗?——要会算显存;
  2. 训练要跑多久、花多少钱?——要会算FLOPs 和 GPU 小时;
  3. 机器中途被收回怎么办?结果怎样才能复现?——要有实验工程的习惯。

这一篇把这三笔账讲清楚,每一笔都配一个小程序,并且尽量用真实的 PyTorch 模型核对公式。

学完这一篇你应该能:

  1. 从结构超参数精确算出一个 Transformer 的参数量;
  2. 估计训练和推理的显存,知道”16 字节 / 参数”从哪来、激活值和 KV 缓存怎样增长;
  3. 用 6ND 估计训练 FLOPs,换算成 GPU 小时和花费;理解推理为什么常常被内存带宽卡住;
  4. 列出在云上做实验的主要风险,写出能精确断点续训的训练代码。

1. 为什么是 GPU

神经网络的计算绝大部分是矩阵乘法:C=ABC = AB 里每个元素都是一次独立的点积,成千上万个元素可以同时算。CPU 为”快速完成一件复杂的事”设计(少量强大的核心、大缓存、复杂的分支预测);GPU 为”同时完成大量简单的事”设计(上万个简单的计算单元、为矩阵乘法专门设计的张量核心、带宽极高的显存)。

两块数据中心 GPU 的公开规格:

GPU显存显存带宽BF16 稠密算力
NVIDIA A100(80 GB,SXM)80 GB约 2.0 TB/s312 TFLOP/s
NVIDIA H100(80 GB,SXM)80 GB约 3.35 TB/s989 TFLOP/s

云上常见的较小的卡:T4(16 GB)、A10G(24 GB);消费级显卡常见 8~24 GB。

注意算力和带宽的比例:H100 每秒能做 989 万亿次浮点运算,却只能读 3.35 万亿字节——每读一个字节,要做约 300 次运算才不会让计算单元闲着。矩阵乘法(每个数被反复使用)能做到这一点;逐元素运算(每个数只用一次)做不到。这就是”内存墙”:很多操作的速度不由算力决定,而由搬运数据的速度决定。§6.4 会看到它怎样决定了 LLM 推理的速度。(本仓库 CMU10-423 前置讲 P3 用 roofline 模型详细讲了这一点。)


2. 每个数占几个字节

显存账的第一步是”参数个数 × 每个参数的字节数”。代码 01:

格式字节最大值最小正规数机器精度 eps有效十进制位
FP3243.4e381.2e-381.2e-76.9
FP1626.55e46.1e-59.8e-43.0
BF1623.4e381.2e-387.8e-32.1
FP8(e4m3)/ INT81

Llama 3 8B(8,030,261,248 个参数)光存权重就要:FP32 29.9 GiB,BF16 15.0 GiB,INT8 7.5 GiB,INT4 3.7 GiB。

FP16 和 BF16 的区别:都是 2 字节,但比特分配不同。FP16 有 5 位指数、10 位尾数——精度较好,范围小(最大 65504);BF16 有 8 位指数、7 位尾数——范围和 FP32 一样,精度只有 2~3 位有效数字。

原始值FP32FP16BF16
1/30.3333330.3332520.333984
1000.11000.110001000
7000070000inf(溢出)70144
1e-81e-80(下溢)1.00117e-8

训练时的梯度经常非常小:[10−6,2×10−8,2×10−5][10^{-6}, 2 \times 10^{-8}, 2 \times 10^{-5}] 存成 FP16 变成 [1.01×10−6,0,2×10−5][1.01 \times 10^{-6}, \mathbf{0}, 2 \times 10^{-5}]——中间那个梯度直接消失了。所以 FP16 训练需要损失缩放(先把损失乘一个大数,让梯度落进 FP16 的范围,更新前再除回来)。BF16 的范围和 FP32 一样,不需要损失缩放,这是今天大模型训练普遍用 BF16 的主要原因。它的代价是精度低,所以主权重、累加仍然用 FP32(P1 §3.4 看到过 FP32 累加的误差,BF16 下更严重)。

量化:推理时可以把权重进一步压到 INT8、INT4。最简单的对称量化是 scale=max⁡∣W∣/127\text{scale} = \max|W| / 127,q=round(W/scale)q = \text{round}(W / \text{scale})。代码 01 §3 对一个 256 × 256 的权重矩阵:

权重矩阵量化方式权重相对误差输出 WxWx 相对误差
正常(N(0,0.022)\mathcal{N}(0, 0.02^2))整个矩阵一个 scale0.01080.0111
正常每行一个 scale0.00700.0074
有一个离群值 = 2.0整个矩阵一个 scale0.21160.1857
有一个离群值 = 2.0每行一个 scale0.01470.0083

正常的权重量化到 INT8,误差约 1%。但只要有一个离群值,整个矩阵的 scale 就被它撑大约 30 倍,其余权重全挤进很少几个量化级别里,误差暴增到约 20%。按行(或更细的分组)量化,把离群值的影响限制在一行之内。真实 LLM 的激活值里确实有这样的离群维度,这是 LLM.int8()、GPTQ、AWQ 等方法要专门处理的问题(第 19 讲)。


3. 参数量怎么算

一个 decoder-only Transformer 的参数由结构超参数完全决定(dd = 隐藏维度,LL = 层数,VV = 词表大小,dffd_{ff} = MLP 中间层宽度,hh / hkvh_{kv} = 查询头数 / KV 头数,dh=d/hd_h = d / h):

部件参数量(不含偏置)
词嵌入V⋅dV \cdot d
每层注意力Q,OQ, O:2⋅d⋅hdh2 \cdot d \cdot h d_h;K,VK, V:2⋅d⋅hkvdh2 \cdot d \cdot h_{kv} d_h
每层 MLPGELU(GPT-2):2⋅d⋅dff2 \cdot d \cdot d_{ff};SwiGLU(Llama):3⋅d⋅dff3 \cdot d \cdot d_{ff}
归一化LayerNorm 每处 2d2d,RMSNorm 每处 dd;每层 2 处,最后 1 处
输出层与嵌入共享时为 0,否则 V⋅dV \cdot d
位置嵌入GPT-2 有 Tmax⁡⋅dT_{\max} \cdot d;Llama 用 RoPE,没有参数

对标准结构(hkv=hh_{kv} = h,dff=4dd_{ff} = 4d,GELU),每层约 12d212 d^2,所以 N≈12Ld2+VdN \approx 12 L d^2 + V d——值得记住的经验公式。

mem_calc.py 实现了完整的公式,代码 02 §1 用 tiny_gpt.py 里的真实 PyTorch 模型逐个核对:小 GPT-2 式模型、小 Llama 式模型(含 GQA)、以及完整的 GPT-2 small(124,439,808 个参数),全部一致。再用公开的配置计算:

模型总参数嵌入 + 输出层占比注意力MLP
GPT-2 small124,439,80839.4M32%28.3M56.7M
Llama 2 7B6,738,415,616262.1M4%2.15B4.33B
Llama 3 8B8,030,261,2481.05B13%1.34B5.64B
Llama 2 70B68,976,648,192524.3M1%12.08B56.37B

三个 Llama 的结果和官方报告的参数量完全相同——参数量不是需要”查”的东西,而是可以从配置算出来的东西。两个规律:小模型里嵌入层占比很大(GPT-2 small 约 1/3);模型越大,参数越集中在 MLP(70B 超过 80%)。Llama 3 的词表从 3.2 万扩大到 12.8 万,嵌入层又变得不可忽视。


4. 训练的显存账

4.1 每个参数 16 字节

训练时,每个参数要存的不只是它自己:

项目混合精度(BF16)+ Adam纯 FP32 + Adam
权重2(BF16)4
梯度2(BF16)4
FP32 主权重4—
Adam 一阶矩 mm44
Adam 二阶矩 vv44
合计16 字节16 字节

代码 02 §2 在真实的优化器上量了一下:SGD(无动量)没有状态,SGD + 动量的状态等于权重大小,Adam 的状态是权重的 2 倍。

一个反直觉的结论:混合精度 + Adam 和纯 FP32 + Adam 一样是 16 字节 / 参数。混合精度省的是激活值的显存和计算时间(张量核心的 BF16 算力远高于 FP32),而不是参数相关的这部分。(P1 §5.4 解释了为什么几乎都用 Adam;第 13 讲会讲 8 位优化器等进一步压缩优化器状态的方法。)

代码 02 §3(不含激活值):

模型FP32 + Adam混合精度 + AdamFP32 + SGD只推理(BF16)
GPT-2 small1.9 GiB1.9 GiB949 MiB237 MiB
Llama 2 7B100.4 GiB100.4 GiB50.2 GiB12.6 GiB
Llama 3 8B119.7 GiB119.7 GiB59.8 GiB15.0 GiB
Llama 2 70B1.0 TiB1.0 TiB514 GiB128.5 GiB

一块 80 GiB 的 GPU 连 7B 模型的”参数 + 梯度 + 优化器状态”都放不下,更别说激活值了。

4.2 激活值

反向传播需要前向过程中的中间结果(P1 §4.5),它们叫激活值。和参数不同,激活值随批大小、序列长度、层数增长。代码 02 §4 在小模型(d=256d = 256,8 个头,4 层)上用 saved_tensors_hooks 实测(FP32):

  • 批大小:B = 4、8、16 时分别是 19.5、39.0、78.0 MiB——正比于 BB;
  • 层数:L = 2、4、8 时分别是 21.0、39.0、75.1 MiB——每加一层增加相同的量;
  • 序列长度(B = 4):T = 32、64、128、256、512 时分别是 9.3、19.5、43.1、102.2、269.0 MiB。拟合"a⋅T+c⋅T2a \cdot T + c \cdot T^2",T = 512 时平方项占 48%——它来自每层每个头的 T×TT \times T 注意力矩阵。

激活检查点(activation checkpointing):前向时只保存每个块的输入,反向时把块的前向重算一遍。B = 8、T = 256 时激活值从 204.2 MiB 降到 19.8 MiB(省 90%),代价是多做一次前向,训练大约慢三分之一。

真实模型的量级可以用 Korthikanti et al.(2022)的公式估计(每层约 sbh(34+5as/h)s b h (34 + 5 a s / h) 字节,16 位激活):

模型设置普通注意力不存注意力矩阵(FlashAttention)
GPT-2 smallB = 1,T = 10241.0 GiB306 MiB
Llama 2 7BB = 1,T = 409697 GiB17 GiB
Llama 3 8BB = 1,T = 8192354 GiB34 GiB

长序列时,注意力矩阵是激活值的绝对大头。FlashAttention(第 19 讲)通过分块计算不存这个矩阵,激活检查点再砍掉剩下的大部分。这些是估计值,常数取决于实现细节;精确值要实测。

4.3 一块 80 GiB 的 GPU 能做什么

代码 02 §6(不含激活值):

任务需要结论
8B 推理(BF16,批 1,8K 上下文)16.0 GiB放得下
8B 全参数微调(混合精度 + Adam)119.7 GiB放不下(至少 2 块)
8B LoRA 微调(冻结的 BF16 权重 + 2000 万参数的适配器)15.3 GiB放得下
70B 推理(BF16)128.5 GiB放不下(至少 2 块)
70B 推理(INT4 权重)32.1 GiB放得下

放不下时有三条路:把参数、梯度、优化器状态切分到多块卡上(ZeRO、FSDP,第 13 讲);只训练一小部分参数(LoRA,第 6 讲);用更少的字节存权重(量化,第 19 讲)。原课的作业正是沿着这些方向设计的。


5. 推理的显存账

推理不需要梯度和优化器状态,但多了一项:KV 缓存。生成第 tt 个 token 时,注意力要用到之前所有 token 在每一层的 K、V 向量;把它们存下来就不必每一步重算(第 19 讲)。

KV 缓存字节=2×L×hkv×dh×T×B×每个数的字节\text{KV 缓存字节} = 2 \times L \times h_{kv} \times d_h \times T \times B \times \text{每个数的字节}

代码 02 §5(BF16):

模型每个 token 的 KV 缓存批 1、4K批 16、4K批 16、32K
Llama 2 7B(32 个 KV 头)512 KiB2.0 GiB32 GiB256 GiB
Llama 3 8B(8 个 KV 头,GQA)128 KiB0.5 GiB8 GiB64 GiB

(权重分别是 12.6 GiB 和 15.0 GiB。)批大小和上下文一大,KV 缓存就超过权重本身。Llama 3 用分组查询注意力(GQA,8 个 KV 头服务 32 个查询头),KV 缓存缩小到 1/4——这正是 GQA 被发明的原因(第 4 讲)。长上下文(第 21 讲)和推理服务(第 19 讲)的很多技术,都是在和 KV 缓存的大小作斗争。


6. 算力、时间与钱

6.1 6ND:训练要算多少次

训练一个 NN 个参数的模型、处理 DD 个 token,浮点运算次数约为

C≈6NDC \approx 6 N D

为什么是 6?对一个线性层 y=Wxy = Wx(WW 有 NWN_W 个参数),每个 token:

  • 前向:每个参数做一次乘法、一次加法,2 FLOPs;
  • 反向:要算对输入的梯度 ∂L/∂x=W⊤δ\partial L / \partial x = W^\top \delta(2 FLOPs/参数)和对权重的梯度 ∂L/∂W=δx⊤\partial L / \partial W = \delta x^\top(2 FLOPs/参数),共 4。

合计 6。这里的 NN 应该只算参与矩阵乘法的参数(注意力、MLP 的权重矩阵和输出层;嵌入查表不做乘法)。此外,注意力本身(QK⊤QK^\top 和”注意力 × VV“)的计算量和序列长度成正比,不属于任何参数,要另外加上 12LTd12 L T d 每 token;它和主项之比约为 T/(6d)T / (6d)。

代码 03 §1 用 PyTorch 的 FlopCounterMode(拦截每一次矩阵乘法并计数)核对:

配置实测 FLOPs6ND6ND + 注意力项
d=128d = 128,T=64T = 643.010e92.809e93.010e9
d=256d = 256,T=128T = 1282.251e102.090e102.251e10
d=256d = 256,T=512T = 5121.094e118.360e101.094e11
d=512d = 512,T=128T = 1288.368e108.046e108.368e10

加上注意力项后和实测完全一致。T=512T = 512、d=256d = 256 时注意力项已经占了约四分之一;对 d=4096d = 4096 的大模型,T=4096T = 4096 时约占 17%,所以”6ND”是一个很好的一阶近似,长上下文时要修正。

6.2 MFU:实际用上了多少

训练时间=CGPU 数×单卡峰值×MFU\text{训练时间} = \frac{C}{\text{GPU 数} \times \text{单卡峰值} \times \text{MFU}}

MFU(model FLOPs utilization)= 实际达到的有效算力 / 峰值算力。它受很多因素影响:矩阵的大小和形状、逐元素运算和框架开销、通信、数据加载。大规模训练的 MFU 通常在 30%~60%。

代码 03 §2 在作者的笔记本上(单线程,FP32)测了一下:1024 × 1024 矩阵乘法约 3 TFLOP/s(苹果芯片上 PyTorch 会调用系统的矩阵加速单元;普通 CPU 上这个数可能小一两个数量级)。训练三个小模型(批 8,T = 128):d=128d = 128 时 MFU 约 8%,d=256d = 256 约 17%,d=512d = 512 约 33%。模型越小,矩阵越小,MFU 越低——计算单元大部分时间在等数据和框架开销。(这些计时数字每台机器、每次运行都不同。)

6.3 外推:训练真实模型要多久

假设 H100、MFU 40%(代码 03 §3):

模型token 数训练 FLOPsH100 GPU 小时1 块卡1024 块卡
GPT-2 small100 亿(示例)8.5e1860.2 天—
Llama 2 7B2 万亿9.2e22约 6.5 万7.4 年2.6 天
Llama 3 8B15 万亿8.7e23约 61 万70 年25 天

(token 数取公开报告的量级。)同样的 GPT-2 small,在作者的笔记本上按”实测矩阵乘法速度的一半”这种乐观假设也要约两个月。这就是原课要申请云端 GPU 额度的原因,也是本讲义的代码只训练极小模型的原因。

花费 = GPU 小时 × 单价。单价随云厂商、地区、计费方式(按需、预留、竞价)差别很大,而且变化很快,请以你查到的实际报价为准。只为了感受量级:如果每 GPU 小时 2 美元(纯属示意),GPT-2 small 这个例子是十几美元,Llama 3 8B 这样的预训练是上百万美元。课程项目里真正会花钱的,通常是微调和大量评估,而不是从零预训练。

6.4 推理:被内存带宽卡住

生成每一个新 token 时,都要把全部权重从显存里读一遍。代码 03 §4:Llama 3 8B 的 BF16 权重 16.1 GB,H100 的带宽 3.35 TB/s,读一遍要 4.79 ms;而这个 token 的计算(2N2N FLOPs)在峰值算力下只要 15.2 µs。读权重比做计算慢约 300 倍——批大小为 1 时,生成速度最多约 209 token/秒,计算单元 99% 的时间在等数据。

这就是 §1 的”内存墙”。解决办法是批处理:把很多请求拼在一起,读一次权重就能服务一整批 token,直到批大到让计算重新成为瓶颈(这里约为 300)。量化(权重更小,读得更快)、KV 缓存的高效管理(PagedAttention)、推测解码,都是围绕这一点展开的(第 19 讲)。


7. 在云上做实验

云端实验检查清单 列出了具体条目,这里讲每一类背后的”为什么”。

账号与安全。云账号绑定了信用卡或课程额度,一旦访问密钥泄漏(最常见的方式是被提交进公开的代码仓库),会被自动扫描、拿去开大量机器。所以:主账号开启多因素认证、日常用权限受限的子账号;密钥放在环境变量或密钥管理服务里,永远不进代码仓库。原课 HW0 的说明也提到,账号被盗用产生的费用可能要自己承担。

预算与配额。设置费用告警(50%、80%、100%)。新账号的 GPU 配额常常是 0 或很小(按 vCPU 计),需要提前申请,可能要等几天——不要等到作业截止前一天才发现开不了 GPU 实例。

选实例。先用 §4、§5 的账确定显存需求,再选卡和卡数。按需实例稳定但贵;竞价实例(spot)便宜很多,但随时可能被收回(AWS 会提前约两分钟通知)。用竞价实例,训练代码就必须能断点续训(§8)。数据和实例放在同一个区域:跨区域、流出云端的数据传输要单独收费。

存储。分清”随实例消失”的本地盘和持久的云盘、对象存储。检查点和结果必须写到持久存储上,否则实例一终止就全没了。

运行中。长任务放在 tmux 里,SSH 断开不影响训练。用 nvidia-smi 看 GPU 利用率:长期很低,通常意味着数据加载或 CPU 预处理跟不上,你在为闲置的 GPU 付钱。

结束后。最常见的”意外账单”来自忘了关的资源。停止的实例不再收计算费,但挂载的云盘仍按容量收费;确定不用了就终止并删除云盘、快照、存储桶。第二天再看一眼账单。

先在本地跑通。上云之前,在 CPU 上用极小的配置把”数据 → 训练 → 评估 → 保存 → 续训”整条流程跑通。云上的每一分钟都在计费,调试应该发生在便宜的地方。


8. 实验工程:可复现与断点续训

8.1 运行目录与配置指纹

跑过十几个实验之后,最常见的混乱是”这个结果是用哪组超参数跑出来的?“。harness.py 的做法:把配置字典按键排序后转成 JSON,取哈希的前 10 位作为配置指纹,每个实验一个以指纹命名的运行目录,里面放配置、日志、检查点。同一组配置永远得到同一个指纹(键的顺序无关),改一个超参数(学习率 3e-3 → 1e-3)指纹就变(11584a6a20 → 7e54a06db0),不同的实验不会互相覆盖。

日志写成 JSON Lines:每一步一行 JSON,追加写入、立即落盘。训练中断时已经写下的记录完好无损,之后几行代码就能读进来画曲线。

8.2 断点续训:要存什么

代码 04 训练第 1 讲的窗口语言模型(600 步,预热 + 余弦学习率,每 100 步存一次检查点),并在第 250 步”崩溃”(模拟竞价实例被回收),然后从最近的检查点(第 200 步)续训到 600 步:

续训方式最终验证损失与一口气跑完的最大参数差
一口气跑完(参照)1.396126—
完整恢复(参数 + 优化器 + 调度器 + 随机数)1.3961260
忘了恢复优化器状态1.3949350.23
忘了恢复学习率调度器1.4061890.13
忘了恢复数据抽样的随机数1.4004030.06

只要检查点里保存了完整的训练状态——参数、优化器状态(Adam 的 mm、vv)、调度器进度、步数、数据抽样的随机数状态——续训的结果和一口气跑完逐位相同。

少存任何一项,结果都会偏离:忘了优化器状态,Adam 的动量和步长估计从零开始;忘了调度器,学习率又从预热重新开始;忘了随机数状态,之后抽到的批完全不同。注意它们都能正常运行、不报错,最终损失也只差一点点(“忘了优化器状态”这一行甚至碰巧略低)——你得到的是一个不同的模型,而不一定是更差的模型。这正是这类 bug 难以发现的原因:没有任何信号告诉你续训出了问题,直到你发现两次”相同”的实验结果对不上。

另外两个细节:

  • 检查点间隔 = 你愿意损失的最大工作量:第 201~249 步的计算白做了。间隔太长损失大,太短则保存本身占用时间和存储(大模型的检查点有几百 GB)。
  • 原子写入:先写到临时文件,再用 os.replace 一步改名。否则恰好在写检查点时被杀掉,会留下一个写了一半的坏文件——如果你还删掉了旧的检查点,就什么都没有了。

8.3 可复现的层次

  • 同一台机器、同一份代码逐位复现:固定所有随机种子,CPU 上用单线程。本讲义的所有脚本都做到了这一点。
  • GPU 上逐位复现:很多 GPU 运算(例如原子加法的累加顺序)默认不是确定性的;需要 torch.use_deterministic_algorithms(True) 等设置,可能更慢,有些运算甚至不支持。
  • “统计意义上”的复现:大多数时候更实际的做法是用几个不同的种子各跑一次,报告平均值和波动范围。如果你的改进小于种子之间的波动,它多半不是真的改进(第 5 讲会讨论评估的统计问题)。

同时记录代码版本(git commit)、依赖版本(pip freeze)、GPU 型号和配置指纹,几个月后你才能回答”这个数字是怎么来的”。


9. 小结、常见误区、练习

9.1 小结

  1. GPU 为大规模并行的矩阵乘法而生;它的算力远超带宽,很多操作被”搬数据”而不是”算”卡住。
  2. 显存从”参数个数 × 字节数”算起。BF16 和 FP16 都是 2 字节:BF16 范围大、精度低,训练更常用。量化要小心离群值。
  3. 参数量由配置精确决定,标准结构约 12Ld2+Vd12Ld^2 + Vd;公式和 PyTorch、和官方报告完全一致。
  4. 训练:混合精度 + Adam 每参数 16 字节(和纯 FP32 一样),8B 模型约 120 GiB;激活值随批大小、层数线性增长,随序列长度平方增长,可用 FlashAttention 和激活检查点压缩。
  5. 推理:权重 + KV 缓存;批和上下文大时 KV 缓存超过权重,GQA 能显著缩小它。
  6. 训练 FLOPs ≈ 6ND(长上下文加注意力项);时间 = FLOPs ÷(卡数 × 峰值 × MFU);花费 = GPU 小时 × 单价。推理被内存带宽卡住,批处理是关键。
  7. 云上:安全、预算、配额、竞价实例、持久存储、用完即关。
  8. 实验工程:配置指纹、JSONL 日志、完整且原子的检查点;少存一项状态,续训就会悄悄得到另一个模型。

9.2 常见误区

  • “混合精度训练省一半显存”:参数相关的部分一点没省(都是 16 字节 / 参数),省的是激活值和计算时间。
  • “7B 模型 BF16 是 14 GB,所以 16 GB 的卡就能微调”:那只是权重。全参数微调要再加梯度和优化器状态(约 100 GiB)以及激活值。
  • “推理慢是因为 GPU 算得不够快”:批大小为 1 时,主要是读权重太慢(内存带宽),算力大部分闲着。
  • “保存了 model.state_dict() 就能续训”:还要优化器、调度器、步数和随机数状态,否则续训得到的是另一个模型。
  • “停止实例就不收钱了”:挂着的云盘、快照、存储桶仍在收费。
  • “6ND 对任何上下文长度都准”:长上下文时注意力项(约 T/6dT/6d)不可忽略。

9.3 练习

练习 1(参数量):用 §3 的公式手算 Llama 2 7B 的参数量(V=32000V = 32000,d=4096d = 4096,L=32L = 32,h=hkv=32h = h_{kv} = 32,dff=11008d_{ff} = 11008,SwiGLU,RMSNorm,无偏置,不共享嵌入,无位置嵌入)。

参考答案

每层注意力:4×40962=67,108,8644 \times 4096^2 = 67{,}108{,}864;每层 MLP:3×4096×11008=135,266,3043 \times 4096 \times 11008 = 135{,}266{,}304;每层两个 RMSNorm:81928192。每层合计 202,383,360202{,}383{,}360,32 层 6,476,267,5206{,}476{,}267{,}520。嵌入和输出层各 32000×4096=131,072,00032000 \times 4096 = 131{,}072{,}000,最后的 RMSNorm 40964096。总计 6,476,267,520+262,144,000+4,096=6,738,415,6166{,}476{,}267{,}520 + 262{,}144{,}000 + 4{,}096 = 6{,}738{,}415{,}616,和代码、和官方报告一致。

练习 2(优化器状态):如果把 Adam 的 mm 和 vv 都改用 8 位存储(8 位优化器),混合精度训练每个参数要多少字节?8B 模型能省多少?

参考答案

22(BF16 权重)+ 22(梯度)+ 44(FP32 主权重)+ 11 + 11 = 10 字节,比 16 字节省 6 字节 / 参数。8B 模型:8.03×109×6≈4.8×10108.03 \times 10^9 \times 6 \approx 4.8 \times 10^{10} 字节 ≈ 45 GiB,从约 120 GiB 降到约 75 GiB。

练习 3(KV 缓存):Llama 2 70B 有 80 层、8 个 KV 头、每头 128 维。BF16 下每个 token 的 KV 缓存多大?单个请求、32K 上下文呢?

参考答案

每个 token:2×80×8×128×2=327,6802 \times 80 \times 8 \times 128 \times 2 = 327{,}680 字节 = 320 KiB。32K(32768)个 token:327,680×32,768=10,737,418,240327{,}680 \times 32{,}768 = 10{,}737{,}418{,}240 字节 = 10 GiB。如果没有 GQA(64 个 KV 头),会是 80 GiB——一个请求就占满一块 H100。

练习 4(6ND):GPT-3 有 1750 亿参数,训练了 3000 亿个 token。估计训练 FLOPs,以及在 H100、MFU 40% 下需要多少 GPU 小时。

参考答案

6×1.75×1011×3×1011=3.15×10236 \times 1.75 \times 10^{11} \times 3 \times 10^{11} = 3.15 \times 10^{23} FLOPs(GPT-3 论文报告的数字是 3.14×10233.14 \times 10^{23})。GPU 小时 =3.15×1023/(989×1012×0.4)/3600≈22= 3.15 \times 10^{23} / (989 \times 10^{12} \times 0.4) / 3600 \approx 22 万。(GPT-3 当年是在 V100 上训练的,它的半精度张量核心算力约 125 TFLOP/s,约为 H100 的八分之一。)

练习 5(推理的批处理):§6.4 中,Llama 3 8B 在 H100 上批大小为 64 时,每一步还是被内存带宽卡住吗?大约多大的批才会让计算成为瓶颈?(忽略 KV 缓存的读取。)

参考答案

每一步读一次权重:4.79 ms;计算 64 个 token:64×15.2 μs≈0.9764 \times 15.2\ \mu s \approx 0.97 ms。仍然是读权重更慢,所以还是带宽瓶颈,但每个 token 的平均成本降到了 1/64。两者相等时批大小约为 4.79/0.0152≈3154.79 / 0.0152 \approx 315。实际中 KV 缓存的读取会随批大小和上下文增长,交叉点会更早到来(第 19 讲)。

练习 6(激活检查点的代价):为什么说激活检查点让训练”大约慢三分之一”?

参考答案

按 FLOPs 计,前向约 1 份、反向约 2 份(§6.1 的 2 : 4),一步训练共 3 份。激活检查点在反向时把每个块的前向重算一遍,多了 1 份,变成 4 份,即慢约 4/3−1≈33%4/3 - 1 \approx 33\%。实际中可以只对部分层做检查点,或者只重算便宜的部分(“选择性重算”,Korthikanti et al. 2022),在显存和速度之间折中。

练习 7(断点续训):§8.2 里”忘了恢复数据抽样的随机数”,数据分布并没有变,为什么最终模型会不同?这一项和”忘了恢复优化器状态”相比,哪个更危险?

参考答案

随机梯度下降的轨迹取决于具体抽到了哪些批:换一串随机数,就是另一条轨迹,最终停在另一组参数上(数值上相近但不相同)。它不改变训练的”期望行为”,主要危害是不可复现。忘了优化器状态更危险:Adam 的二阶矩 vv 从 0 开始,前几步的有效步长会突然变得很大(偏差校正只能部分缓解),在大模型上可能直接造成损失尖峰甚至发散;而且 mm、vv 占了检查点的大部分体积,“为了省空间不存优化器状态”是一个很常见的错误。

9.4 延伸阅读

  • Kaplan et al. (2020), Scaling Laws for Neural Language Models(6ND 与计算量的经典推导;第 12 讲的阅读材料)
  • Korthikanti et al. (2022), Reducing Activation Recomputation in Large Transformer Models(激活值的显存公式与选择性重算)
  • Rajbhandari et al. (2020), ZeRO: Memory Optimizations Toward Training Trillion Parameter Models(第 13 讲的阅读材料)
  • Dettmers et al. (2022), LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale(离群值与量化)
  • Llama 3 技术报告 The Llama 3 Herd of Models (2024) 中关于基础设施、训练中断与检查点的章节
  • 本仓库 CMU10-423 前置讲 P1(浮点格式与显存核算)、P3(GPU 与 roofline)、P4(多卡通信)

9.5 接下来

回到 第 1 讲;第 13 讲(优化与并行训练)和第 19 讲(高效推理)会在这一篇的账本基础上,讲怎样把放不下的模型切到多块卡上、怎样把推理做快。

← 返回课程目录