Skip to content

⚙️ Task 2 · PyTorch 与资源核算

封面

作者:Winnie | 2026-04-19 | 阅读时间约 15 分钟


📌 先说结论(给没空读完的人)

  • 🧠 这章教你一种「直觉」:每一行代码要能估出它烧多少显存、耗多少算力
  • 🎯 两个公式钉死一切
    • 💸 训练时间 ≈ 6 × 参数量 × token 数 / (FLOPS × MFU)
    • 💾 训练显存 ≈ 16 × 参数量 字节(FP32 + AdamW)
  • 我实测验证了 16 字节公式:阶段 A/B/C 精确到小数点后两位命中
  • 📐 作业题 2 全做完:GPT-2 XL 单卡 A100 训 400K 步 = 6354 天(17 年) —— 教你为什么要分布式

🎬 为什么这章很重要

Task 1 学的是 一个具体算法(BPE 分词器),动手感强、立竿见影。

Task 2 学的是 一套直觉——看完这章,你对着代码要能"报菜名":

"这行 nn.Linear(1024, 1024)参数量 1M、FP32 吃 4MB、训练时吃 16MB、一个 token 6M FLOPs"

这种直觉平时感受不到,但它能让你在老板问"这模型训得起吗"时,30 秒给出一个量级正确的答案,不用跑代码。

CS336 原话很实在 👇

资源核算(Resource Accounting)不是技能,是思维模式。


🗺️ 思维导图

在真开始讲之前,先铺张地图:

⚙️ PyTorch & 资源核算(Task 2)

├── 📐 两个核心公式
│   ├── 💸 时间 = 6·N·tokens / (FLOPS·MFU)
│   └── 💾 显存 = 16·N 字节(FP32 + AdamW)

├── 🔢 为什么是 6?
│   ├── 前向 2× (一乘一加)
│   └── 反向 4× (权重梯度 + 激活梯度)

├── 🧮 为什么是 16?
│   ├── 参数 4 字节
│   ├── 梯度 4 字节
│   └── 优化器 8 字节(AdamW 的 m + v)

├── 🎨 四种数据类型
│   ├── FP32 · 4B · 最稳
│   ├── FP16 · 2B · 范围窄易炸
│   ├── BF16 🏆 · 2B · 主流
│   └── FP8 · 1B · H100+ 才有

├── 🎯 MFU = 实测 / 峰值
│   └── ≥ 50% 就是不错

└── 🔬 作业题 2(adamwAccounting)
    ├── (a) 显存公式 16P + 4A
    ├── (b) 80GB A100 最大 B = 2
    ├── (c) FLOPs/step ≈ 6·B·L·P
    └── (d) 400K 步 ≈ 17 年 ❗

(先用 ASCII 占坑,后面和 Task 1 一起迭代成可交互的 Markmap)


🧠 核心一:为什么是 6 × 参数量 × tokens

Chapter 1 里有个经典的"餐巾纸问题":

1024 张 H100 上训 70B 模型 15T tokens,多久能训完?

想在餐巾纸上解,你只需要两个数字:总工作量硬件算力

总工作量的公式:

$$\text{FLOPs}_{\text{训练}} \approx 6 \times \text{参数量} \times \text{tokens}$$

6 倍从哪儿来?拆开看就一目了然:

阶段做什么FLOPs / token / 参数
🔜 前向每个参数参与一次乘法 + 一次加法
🔙 反向-权重梯度算每个权重对 loss 的偏导
🔙 反向-激活梯度把梯度传回上一层
合计

📝 这里的"2"指的是矩阵乘法里"一乘一加"算两次 FLOPs。

代入餐巾纸问题验算 👇

总 FLOPs = 6 × 70e9 × 15e12 = 6.3e24
单卡算力 = 990e12 FLOPS × 50% MFU = 5e14 FLOPS
1024 卡 = 5.07e17 FLOPS
时间 = 6.3e24 / 5.07e17 = 1.24e7 秒 ≈ 144 天

教材答案 146 天,我算的 144 天——差 2 天是四舍五入。公式立住 ✅


💾 核心二:为什么是 16 × 参数量 字节

训练一个参数要存多少东西?很多人只算"参数本身 4 字节"就算完了。

训练时,显存里住着 四位房客 🏠:

房客字节干嘛的
🔢 参数4模型权重本身(FP32)
📉 梯度4反向传播算出来的 .grad,和参数等大
🎯 优化器状态·m4AdamW 的一阶矩(梯度移动平均)
🎯 优化器状态·v4AdamW 的二阶矩(梯度平方移动平均)
合计16

这就是 16 × 参数量 的由来。一个常见翻车点:

🎭 推理跑得好好的,一上训练就 OOM —— 因为推理只要 4 字节/参数,训练要 16 字节,翻了 4 倍


🧪 实战一:亲手量一遍 16 字节

光看公式不过瘾,我构造了一个 2048×2048 的线性层(4.2M 参数),三阶段递进量它的显存。

python
model = nn.Linear(2048, 2048, bias=False)   # 4.2M 参数
optimizer = torch.optim.AdamW(model.parameters())

# 阶段 A:刚创建 ——————— 只有参数
# 阶段 B:loss.backward() 后 —— 参数 + 梯度出现
# 阶段 C:optimizer.step() 后 —— 优化器状态出现

实测结果 👇

阶段参数梯度优化器合计字节/参数
A · 刚创建16.00 MB0016 MB4
B · 反向传播后16.00 MB16.00 MB032 MB8
C · 优化一步后16.00 MB16.00 MB32.00 MB64 MB16

💎 最 tricky 的一点:优化器状态是 "隐形" 的——阶段 A、B 都看不到它,只有 optimizer.step() 跑完一步才"凭空"冒出来,一下子翻倍

很多"明明估算够用,一跑就 OOM"的翻车,根源就在这——你没算优化器状态那 8 字节


🎨 顺带:不同数据类型的内存

dtype字节模型大小(4.2M 参数)
FP32416 MB
FP1628 MB
BF16 🏆28 MB

FP16 和 BF16 内存一样,为啥 BF16 赢?

🪜 "要范围,不要精度"

FP16 指数位只有 5 位,训练中 1e-8 这种小数会直接下溢变成 0梯度消失了就不训了

BF16 截断尾数但保留 FP32 的 8 位指数位 —— 范围一样宽,稳定性接近 FP32。

现代 LLM 训练几乎清一色 BF16。


📐 实战二:作业题 2 全做完 · adamwAccounting

CS336 Assignment 1 · Problem adamwAccounting(2 points)

对一个 GPT-2 XL(vocab 50257 / context 1024 / 48 层 / d_model 1600 / 25 头 / d_ff 6400),用 FP32 和 AdamW 训练。

🅰️ (a) 峰值显存代数表达式

参数量 P 拆成 4 部分:

P = 2·V·D          (token embed + LM head)
  + D              (final RMSNorm)
  + N·(2D + 4D² + 3·D·F)   (N 层 block)

每层 block 里:2D(两个 RMSNorm 的 gain 参数)+ 4D²(W_Q/W_K/W_V/W_O)+ 3D·F(SwiGLU 的三个矩阵)。

代入 → P ≈ 2.13 B 参数

激活 A(B) 每 batch 的元素数:

每 block = 8·B·L·D + 2·B·H·L² + 2·B·L·F
总激活 = N · 每block + B·L·D + 2·B·L·V

总显存公式

$$\boxed{\text{memory}(B) = 16P + 4 \cdot A(B) \text{ 字节}}$$

(= 4 倍的"参数 + 梯度 + 优化器 + 激活")


🅱️ (b) 80 GB A100 的最大 batch_size

固定开销(参数 + 梯度 + 优化器):

16 × 2.13 B = 34.03 GB   (还没跑数据呢,先占了一大半)

剩给激活的预算:80 - 34.03 = 45.97 GB

每 batch 的激活占用:

4 × A_per_sample = 4 × 3.88 G = 15.52 GB/batch

所以:

$$\text{显存}(B) \approx 15.52 \cdot B + 34.03 \text{ GB}$$

$$B_{\max} = \left\lfloor \frac{45.97}{15.52} \right\rfloor = \boxed{2}$$

🤯 一个 batch 要 15.52 GB 激活 —— 比很多人想象的可怕得多。 这就是为啥做大模型时 batch_size=1 很常见,都靠梯度累积(gradient accumulation)凑有效 batch。


🅲 (c) AdamW 一步的 FLOPs

矩阵乘法占 FLOPs 的 90%+,其他(LayerNorm/softmax/AdamW 更新本身)可忽略。

$$\boxed{\text{FLOPs}_{\text{每步}} \approx 6 \cdot B \cdot L \cdot P}$$

  • 前向 2·B·L·P(每参数每 token 一乘一加)
  • 反向 4·B·L·P(权重梯度 + 激活梯度各 2·B·L·P
  • AdamW update ~10·P(相对 6·B·L·P 可忽略)

代入 B=1024, L=1024, P=2.13B

FLOPs/step = 6 × 1024 × 1024 × 2.13e9 ≈ 1.34e16 = 13.4 PFLOPs

🅳 (d) 单卡 A100 + 50% MFU 训 400K 步要几天

A100 FP32 峰值 = 19.5 TFLOPS,50% MFU → 有效 9.75 TFLOPS。

总 FLOPs = 13.4 PFLOPs × 400,000 = 5.35e21
时间 = 5.35e21 / 9.75e12 = 5.49e8 秒
     = 6354 天
     ≈ 17.4 年 🤯

$$\boxed{\text{训练时间} \approx 6354 \text{ 天} \approx 17.4 \text{ 年}}$$

这就是重点:一张 A100 训不了 GPT-2 XL,必须分布式

卡数时间
117.4 年
6499 天
25625 天
10246.2 天

这道题其实是下一章 Scaling Laws / 分布式 的"开胃菜" —— 让你从数字上感受到"一张卡是不够的"。


🪞 连回 Task 1 + Harness Engineering

⚠️ 又是 N=1 的直觉联想,不是方法论。

Task 1(BPE 分词器) 教的是:在有限的 token 预算里做自适应压缩

  • 常用搭配 → 合并成大 token
  • 罕见搭配 → 退回字符

Task 2(资源核算) 教的是:在有限的算力/显存预算里做规划

  • 什么能放 GPU / 什么留 CPU
  • 什么用 BF16 / 什么必须 FP32
  • 什么跑 batch_size=8 / 什么只能 batch_size=1 靠梯度累积

再往上一层,Harness Engineering(我在搞的多 Agent 方向)本质也是预算规划:

  • 什么任务合并成一个 skill 调用
  • 什么任务拆分到不同 Agent
  • 什么信息放 context / 什么塞 memory

🎯 三件事同构:都是在「预算约束下做结构化决策」

这章让我第一次觉得 —— 工程师的核心能力不是"会写代码",而是"会算账"。会算账的工程师,才敢承诺 deadline。


📋 打卡清单

  • [x] Chapter 3 全章精读(1260 行)
  • [x] 关键公式内化(6× FLOPs / 16× 内存)
  • [x] Demo 代码跑通(3 阶段验证 16 字节公式)
  • [x] 数据类型对比(FP32 / FP16 / BF16 实测)
  • [x] 餐巾纸算一遍 H100/70B(144 天 vs 教材 146 天 ✅)
  • [x] 作业题 2 全做完(a/b/c/d 四小题 + 代数表达式 + 数值答案)
  • [x] 打卡博客发布 ← 就是这篇
  • [ ] 作业题 1(Transformer FLOPs 分解)(下次补)
  • [ ] Chapter 4 Transformer 架构(下次)

🌱 反思

Task 1 打卡时我感受到的是"跑通了一个算法"的小成就感。

Task 2 感受到的是另一种东西 —— "能在纸上算出答案"的安心

在跑那个"单卡 A100 训 17 年"的计算时,我意识到一件事:很多「不行」和「能行」的区别,不在运气,在你算不算得出来

不为证明什么,就是想每一行代码都算得清账 🔧


Winnie · 2026-04-19

慢慢迭代,不追求完美。