定位:可选的前置讲,对应原课的 HW0(申请 AWS 账号、拿到课程的云端算力额度)。建议在第 13 讲(优化与并行训练)之前读;如果你打算在云上做课程项目,越早读越好。 核心问题:训练一个 7B 模型要多少显存、多少 GPU 小时?推理为什么慢?在云上做实验怎样不丢进度、不超预算? 配套代码:code/prereq3/(3 个模块 + 4 个脚本 + 一份云端实验检查清单,全部在 CPU 上几秒内跑完) 本讲的写法:原课没有对应的课次,内容按”在云上做 LLM 实验之前必须会算的账”来组织。GPU 的规格取自公开的产品规格;模型参数量由公开配置计算,并和 PyTorch 逐个核对;计时类的数字来自作者的机器(苹果芯片的笔记本),换一台机器会不同。本讲不给任何具体的云服务价格——价格变化太快,一律当作需要你自己查的参数。
目录
- 0. 导读
- 1. 为什么是 GPU
- 2. 每个数占几个字节
- 3. 参数量怎么算
- 4. 训练的显存账
- 5. 推理的显存账
- 6. 算力、时间与钱
- 7. 在云上做实验
- 8. 实验工程:可复现与断点续训
- 9. 小结、常见误区、练习
0. 导读
原课的第一个作业(HW0)只有一件事:用学校邮箱注册 AWS 账号、提交账号 ID,课程会给每个人一笔云端算力额度,之后的作业和项目都在上面跑 GPU。作业说明里还特意提醒:额度超支、账号被盗用,费用可能要自己承担。
这件”行政小事”背后其实是一整套必须会算的账。在你按下”启动实例”之前,应该能回答:
- 我的模型放得进这块 GPU 吗?——要会算显存;
- 训练要跑多久、花多少钱?——要会算FLOPs 和 GPU 小时;
- 机器中途被收回怎么办?结果怎样才能复现?——要有实验工程的习惯。
这一篇把这三笔账讲清楚,每一笔都配一个小程序,并且尽量用真实的 PyTorch 模型核对公式。
学完这一篇你应该能:
- 从结构超参数精确算出一个 Transformer 的参数量;
- 估计训练和推理的显存,知道”16 字节 / 参数”从哪来、激活值和 KV 缓存怎样增长;
- 用 6ND 估计训练 FLOPs,换算成 GPU 小时和花费;理解推理为什么常常被内存带宽卡住;
- 列出在云上做实验的主要风险,写出能精确断点续训的训练代码。
1. 为什么是 GPU
神经网络的计算绝大部分是矩阵乘法: 里每个元素都是一次独立的点积,成千上万个元素可以同时算。CPU 为”快速完成一件复杂的事”设计(少量强大的核心、大缓存、复杂的分支预测);GPU 为”同时完成大量简单的事”设计(上万个简单的计算单元、为矩阵乘法专门设计的张量核心、带宽极高的显存)。
两块数据中心 GPU 的公开规格:
| GPU | 显存 | 显存带宽 | BF16 稠密算力 |
|---|---|---|---|
| NVIDIA A100(80 GB,SXM) | 80 GB | 约 2.0 TB/s | 312 TFLOP/s |
| NVIDIA H100(80 GB,SXM) | 80 GB | 约 3.35 TB/s | 989 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 | 有效十进制位 |
|---|---|---|---|---|---|
| FP32 | 4 | 3.4e38 | 1.2e-38 | 1.2e-7 | 6.9 |
| FP16 | 2 | 6.55e4 | 6.1e-5 | 9.8e-4 | 3.0 |
| BF16 | 2 | 3.4e38 | 1.2e-38 | 7.8e-3 | 2.1 |
| FP8(e4m3)/ INT8 | 1 |
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 位有效数字。
| 原始值 | FP32 | FP16 | BF16 |
|---|---|---|---|
| 1/3 | 0.333333 | 0.333252 | 0.333984 |
| 1000.1 | 1000.1 | 1000 | 1000 |
| 70000 | 70000 | inf(溢出) | 70144 |
| 1e-8 | 1e-8 | 0(下溢) | 1.00117e-8 |
训练时的梯度经常非常小: 存成 FP16 变成 ——中间那个梯度直接消失了。所以 FP16 训练需要损失缩放(先把损失乘一个大数,让梯度落进 FP16 的范围,更新前再除回来)。BF16 的范围和 FP32 一样,不需要损失缩放,这是今天大模型训练普遍用 BF16 的主要原因。它的代价是精度低,所以主权重、累加仍然用 FP32(P1 §3.4 看到过 FP32 累加的误差,BF16 下更严重)。
量化:推理时可以把权重进一步压到 INT8、INT4。最简单的对称量化是 ,。代码 01 §3 对一个 256 × 256 的权重矩阵:
| 权重矩阵 | 量化方式 | 权重相对误差 | 输出 相对误差 |
|---|---|---|---|
| 正常() | 整个矩阵一个 scale | 0.0108 | 0.0111 |
| 正常 | 每行一个 scale | 0.0070 | 0.0074 |
| 有一个离群值 = 2.0 | 整个矩阵一个 scale | 0.2116 | 0.1857 |
| 有一个离群值 = 2.0 | 每行一个 scale | 0.0147 | 0.0083 |
正常的权重量化到 INT8,误差约 1%。但只要有一个离群值,整个矩阵的 scale 就被它撑大约 30 倍,其余权重全挤进很少几个量化级别里,误差暴增到约 20%。按行(或更细的分组)量化,把离群值的影响限制在一行之内。真实 LLM 的激活值里确实有这样的离群维度,这是 LLM.int8()、GPTQ、AWQ 等方法要专门处理的问题(第 19 讲)。
3. 参数量怎么算
一个 decoder-only Transformer 的参数由结构超参数完全决定( = 隐藏维度, = 层数, = 词表大小, = MLP 中间层宽度, / = 查询头数 / KV 头数,):
| 部件 | 参数量(不含偏置) |
|---|---|
| 词嵌入 | |
| 每层注意力 | :;: |
| 每层 MLP | GELU(GPT-2):;SwiGLU(Llama): |
| 归一化 | LayerNorm 每处 ,RMSNorm 每处 ;每层 2 处,最后 1 处 |
| 输出层 | 与嵌入共享时为 0,否则 |
| 位置嵌入 | GPT-2 有 ;Llama 用 RoPE,没有参数 |
对标准结构(,,GELU),每层约 ,所以 ——值得记住的经验公式。
mem_calc.py 实现了完整的公式,代码 02 §1 用 tiny_gpt.py 里的真实 PyTorch 模型逐个核对:小 GPT-2 式模型、小 Llama 式模型(含 GQA)、以及完整的 GPT-2 small(124,439,808 个参数),全部一致。再用公开的配置计算:
| 模型 | 总参数 | 嵌入 + 输出层 | 占比 | 注意力 | MLP |
|---|---|---|---|---|---|
| GPT-2 small | 124,439,808 | 39.4M | 32% | 28.3M | 56.7M |
| Llama 2 7B | 6,738,415,616 | 262.1M | 4% | 2.15B | 4.33B |
| Llama 3 8B | 8,030,261,248 | 1.05B | 13% | 1.34B | 5.64B |
| Llama 2 70B | 68,976,648,192 | 524.3M | 1% | 12.08B | 56.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 一阶矩 | 4 | 4 |
| Adam 二阶矩 | 4 | 4 |
| 合计 | 16 字节 | 16 字节 |
代码 02 §2 在真实的优化器上量了一下:SGD(无动量)没有状态,SGD + 动量的状态等于权重大小,Adam 的状态是权重的 2 倍。
一个反直觉的结论:混合精度 + Adam 和纯 FP32 + Adam 一样是 16 字节 / 参数。混合精度省的是激活值的显存和计算时间(张量核心的 BF16 算力远高于 FP32),而不是参数相关的这部分。(P1 §5.4 解释了为什么几乎都用 Adam;第 13 讲会讲 8 位优化器等进一步压缩优化器状态的方法。)
代码 02 §3(不含激活值):
| 模型 | FP32 + Adam | 混合精度 + Adam | FP32 + SGD | 只推理(BF16) |
|---|---|---|---|---|
| GPT-2 small | 1.9 GiB | 1.9 GiB | 949 MiB | 237 MiB |
| Llama 2 7B | 100.4 GiB | 100.4 GiB | 50.2 GiB | 12.6 GiB |
| Llama 3 8B | 119.7 GiB | 119.7 GiB | 59.8 GiB | 15.0 GiB |
| Llama 2 70B | 1.0 TiB | 1.0 TiB | 514 GiB | 128.5 GiB |
一块 80 GiB 的 GPU 连 7B 模型的”参数 + 梯度 + 优化器状态”都放不下,更别说激活值了。
4.2 激活值
反向传播需要前向过程中的中间结果(P1 §4.5),它们叫激活值。和参数不同,激活值随批大小、序列长度、层数增长。代码 02 §4 在小模型(,8 个头,4 层)上用 saved_tensors_hooks 实测(FP32):
- 批大小:B = 4、8、16 时分别是 19.5、39.0、78.0 MiB——正比于 ;
- 层数: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。拟合"",T = 512 时平方项占 48%——它来自每层每个头的 注意力矩阵。
激活检查点(activation checkpointing):前向时只保存每个块的输入,反向时把块的前向重算一遍。B = 8、T = 256 时激活值从 204.2 MiB 降到 19.8 MiB(省 90%),代价是多做一次前向,训练大约慢三分之一。
真实模型的量级可以用 Korthikanti et al.(2022)的公式估计(每层约 字节,16 位激活):
| 模型 | 设置 | 普通注意力 | 不存注意力矩阵(FlashAttention) |
|---|---|---|---|
| GPT-2 small | B = 1,T = 1024 | 1.0 GiB | 306 MiB |
| Llama 2 7B | B = 1,T = 4096 | 97 GiB | 17 GiB |
| Llama 3 8B | B = 1,T = 8192 | 354 GiB | 34 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 缓存。生成第 个 token 时,注意力要用到之前所有 token 在每一层的 K、V 向量;把它们存下来就不必每一步重算(第 19 讲)。
代码 02 §5(BF16):
| 模型 | 每个 token 的 KV 缓存 | 批 1、4K | 批 16、4K | 批 16、32K |
|---|---|---|---|---|
| Llama 2 7B(32 个 KV 头) | 512 KiB | 2.0 GiB | 32 GiB | 256 GiB |
| Llama 3 8B(8 个 KV 头,GQA) | 128 KiB | 0.5 GiB | 8 GiB | 64 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:训练要算多少次
训练一个 个参数的模型、处理 个 token,浮点运算次数约为
为什么是 6?对一个线性层 ( 有 个参数),每个 token:
- 前向:每个参数做一次乘法、一次加法,2 FLOPs;
- 反向:要算对输入的梯度 (2 FLOPs/参数)和对权重的梯度 (2 FLOPs/参数),共 4。
合计 6。这里的 应该只算参与矩阵乘法的参数(注意力、MLP 的权重矩阵和输出层;嵌入查表不做乘法)。此外,注意力本身( 和”注意力 × “)的计算量和序列长度成正比,不属于任何参数,要另外加上 每 token;它和主项之比约为 。
代码 03 §1 用 PyTorch 的 FlopCounterMode(拦截每一次矩阵乘法并计数)核对:
| 配置 | 实测 FLOPs | 6ND | 6ND + 注意力项 |
|---|---|---|---|
| , | 3.010e9 | 2.809e9 | 3.010e9 |
| , | 2.251e10 | 2.090e10 | 2.251e10 |
| , | 1.094e11 | 8.360e10 | 1.094e11 |
| , | 8.368e10 | 8.046e10 | 8.368e10 |
加上注意力项后和实测完全一致。、 时注意力项已经占了约四分之一;对 的大模型, 时约占 17%,所以”6ND”是一个很好的一阶近似,长上下文时要修正。
6.2 MFU:实际用上了多少
MFU(model FLOPs utilization)= 实际达到的有效算力 / 峰值算力。它受很多因素影响:矩阵的大小和形状、逐元素运算和框架开销、通信、数据加载。大规模训练的 MFU 通常在 30%~60%。
代码 03 §2 在作者的笔记本上(单线程,FP32)测了一下:1024 × 1024 矩阵乘法约 3 TFLOP/s(苹果芯片上 PyTorch 会调用系统的矩阵加速单元;普通 CPU 上这个数可能小一两个数量级)。训练三个小模型(批 8,T = 128): 时 MFU 约 8%, 约 17%, 约 33%。模型越小,矩阵越小,MFU 越低——计算单元大部分时间在等数据和框架开销。(这些计时数字每台机器、每次运行都不同。)
6.3 外推:训练真实模型要多久
假设 H100、MFU 40%(代码 03 §3):
| 模型 | token 数 | 训练 FLOPs | H100 GPU 小时 | 1 块卡 | 1024 块卡 |
|---|---|---|---|---|---|
| GPT-2 small | 100 亿(示例) | 8.5e18 | 6 | 0.2 天 | — |
| Llama 2 7B | 2 万亿 | 9.2e22 | 约 6.5 万 | 7.4 年 | 2.6 天 |
| Llama 3 8B | 15 万亿 | 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 的计算( 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.396126 | 0 |
| 忘了恢复优化器状态 | 1.394935 | 0.23 |
| 忘了恢复学习率调度器 | 1.406189 | 0.13 |
| 忘了恢复数据抽样的随机数 | 1.400403 | 0.06 |
只要检查点里保存了完整的训练状态——参数、优化器状态(Adam 的 、)、调度器进度、步数、数据抽样的随机数状态——续训的结果和一口气跑完逐位相同。
少存任何一项,结果都会偏离:忘了优化器状态,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 小结
- GPU 为大规模并行的矩阵乘法而生;它的算力远超带宽,很多操作被”搬数据”而不是”算”卡住。
- 显存从”参数个数 × 字节数”算起。BF16 和 FP16 都是 2 字节:BF16 范围大、精度低,训练更常用。量化要小心离群值。
- 参数量由配置精确决定,标准结构约 ;公式和 PyTorch、和官方报告完全一致。
- 训练:混合精度 + Adam 每参数 16 字节(和纯 FP32 一样),8B 模型约 120 GiB;激活值随批大小、层数线性增长,随序列长度平方增长,可用 FlashAttention 和激活检查点压缩。
- 推理:权重 + KV 缓存;批和上下文大时 KV 缓存超过权重,GQA 能显著缩小它。
- 训练 FLOPs ≈ 6ND(长上下文加注意力项);时间 = FLOPs ÷(卡数 × 峰值 × MFU);花费 = GPU 小时 × 单价。推理被内存带宽卡住,批处理是关键。
- 云上:安全、预算、配额、竞价实例、持久存储、用完即关。
- 实验工程:配置指纹、JSONL 日志、完整且原子的检查点;少存一项状态,续训就会悄悄得到另一个模型。
9.2 常见误区
- “混合精度训练省一半显存”:参数相关的部分一点没省(都是 16 字节 / 参数),省的是激活值和计算时间。
- “7B 模型 BF16 是 14 GB,所以 16 GB 的卡就能微调”:那只是权重。全参数微调要再加梯度和优化器状态(约 100 GiB)以及激活值。
- “推理慢是因为 GPU 算得不够快”:批大小为 1 时,主要是读权重太慢(内存带宽),算力大部分闲着。
- “保存了 model.state_dict() 就能续训”:还要优化器、调度器、步数和随机数状态,否则续训得到的是另一个模型。
- “停止实例就不收钱了”:挂着的云盘、快照、存储桶仍在收费。
- “6ND 对任何上下文长度都准”:长上下文时注意力项(约 )不可忽略。
9.3 练习
练习 1(参数量):用 §3 的公式手算 Llama 2 7B 的参数量(,,,,,SwiGLU,RMSNorm,无偏置,不共享嵌入,无位置嵌入)。
参考答案
每层注意力:;每层 MLP:;每层两个 RMSNorm:。每层合计 ,32 层 。嵌入和输出层各 ,最后的 RMSNorm 。总计 ,和代码、和官方报告一致。
练习 2(优化器状态):如果把 Adam 的 和 都改用 8 位存储(8 位优化器),混合精度训练每个参数要多少字节?8B 模型能省多少?
参考答案
(BF16 权重)+ (梯度)+ (FP32 主权重)+ + = 10 字节,比 16 字节省 6 字节 / 参数。8B 模型: 字节 ≈ 45 GiB,从约 120 GiB 降到约 75 GiB。
练习 3(KV 缓存):Llama 2 70B 有 80 层、8 个 KV 头、每头 128 维。BF16 下每个 token 的 KV 缓存多大?单个请求、32K 上下文呢?
参考答案
每个 token: 字节 = 320 KiB。32K(32768)个 token: 字节 = 10 GiB。如果没有 GQA(64 个 KV 头),会是 80 GiB——一个请求就占满一块 H100。
练习 4(6ND):GPT-3 有 1750 亿参数,训练了 3000 亿个 token。估计训练 FLOPs,以及在 H100、MFU 40% 下需要多少 GPU 小时。
参考答案
FLOPs(GPT-3 论文报告的数字是 )。GPU 小时 万。(GPT-3 当年是在 V100 上训练的,它的半精度张量核心算力约 125 TFLOP/s,约为 H100 的八分之一。)
练习 5(推理的批处理):§6.4 中,Llama 3 8B 在 H100 上批大小为 64 时,每一步还是被内存带宽卡住吗?大约多大的批才会让计算成为瓶颈?(忽略 KV 缓存的读取。)
参考答案
每一步读一次权重:4.79 ms;计算 64 个 token: ms。仍然是读权重更慢,所以还是带宽瓶颈,但每个 token 的平均成本降到了 1/64。两者相等时批大小约为 。实际中 KV 缓存的读取会随批大小和上下文增长,交叉点会更早到来(第 19 讲)。
练习 6(激活检查点的代价):为什么说激活检查点让训练”大约慢三分之一”?
参考答案
按 FLOPs 计,前向约 1 份、反向约 2 份(§6.1 的 2 : 4),一步训练共 3 份。激活检查点在反向时把每个块的前向重算一遍,多了 1 份,变成 4 份,即慢约 。实际中可以只对部分层做检查点,或者只重算便宜的部分(“选择性重算”,Korthikanti et al. 2022),在显存和速度之间折中。
练习 7(断点续训):§8.2 里”忘了恢复数据抽样的随机数”,数据分布并没有变,为什么最终模型会不同?这一项和”忘了恢复优化器状态”相比,哪个更危险?
参考答案
随机梯度下降的轨迹取决于具体抽到了哪些批:换一串随机数,就是另一条轨迹,最终停在另一组参数上(数值上相近但不相同)。它不改变训练的”期望行为”,主要危害是不可复现。忘了优化器状态更危险:Adam 的二阶矩 从 0 开始,前几步的有效步长会突然变得很大(偏差校正只能部分缓解),在大模型上可能直接造成损失尖峰甚至发散;而且 、 占了检查点的大部分体积,“为了省空间不存优化器状态”是一个很常见的错误。
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 讲(高效推理)会在这一篇的账本基础上,讲怎样把放不下的模型切到多块卡上、怎样把推理做快。