Skip to content

第 13 章 benchmark、MFU 与吞吐口径 ​

13.1 bench.py 故意删掉了什么 ​

bench.py 保留取批、前向、反向和优化器更新,去掉验证、checkpoint、学习率日程和 DDP 管理。默认仍是 12 层、12 头、768 宽、长度 1024、词表 50304 的模型。配置解析器能覆盖已定义的全局变量,却没有提供 n_layer 等全局开关;不能用 --n_layer=2 就把原版 bench 变成小模型。本书没有运行默认大 benchmark。bench.py

real_data=True 时,它只打开训练 memmap 一次,get_batch(split) 忽略 split 参数,始终从训练数据取窗,并无条件使用 pin_memory 和设备搬运。这与 train.py 每次重建映射、区分 CPU/CUDA 的实现不同。real_data=False 则预先生成固定随机 x/y,每次返回同一对张量,减少数据路径干扰,但测到的任务也不同。

简单计时分两段,先 10 步预热,再 20 步测量;阶段前后调用 torch.cuda.synchronize(),保证排队的 CUDA 工作完成再读时间。即使指定 device=cpu,这个无条件 CUDA 调用仍使原脚本不是直接可用的 CPU benchmark。不能把本书的小 CPU 计时写成原 bench.py 性能。

13.2 每秒 token 数需要说明分子和分母 ​

若 S 次更新,每次处理 K 个训练 token,总用时 Δt,训练吞吐为 SK/Δt。分子应说明是否含所有 rank、是否含累积微批次;分母应说明是否含数据读取、验证、保存、编译和日志。生成吞吐通常只数新生成 token,而且逐 token 自回归,不能直接与并行训练吞吐比较。

bench 的计时循环包含取批、优化器、loss.item() 和打印,因此它测的是一段具体循环,而非纯矩阵内核。真实数据与固定数据两种模式之间的差值也不一定全部等于“磁盘瓶颈”,缓存、传输、同步都可能参与。可靠对照应固定 batch、长度、精度、模型、编译和机器状态。

13.3 FLOPs 公式从哪里来 ​

estimate_mfu(fwdbwd_per_iter, dt) 使用

Ftoken=6N+12LHQT=6N+12LCT,

其中 N 是减掉位置嵌入后的参数数,Q=C/H 为每头宽度,T 使用配置 block_size。再乘 T 得到每条序列的前后向 FLOPs,乘 fwdbwd_per_iter 得到本次计时覆盖的序列总数,除以秒数得到 FLOPs/s。MFU 实现

6N 的直觉是多数权重矩阵每 token 前向约两次浮点操作,反向约前向两倍,合计约六次。注意力中的 QK 和 AV 还有随上下文长度增长的工作量,第二项作补充。这个计算把乘和加计作两个 FLOP;若硬件资料用 MAC 为单位,直接代入会差一个倍数。它忽略或简化了归一化、softmax、激活、优化器与实际内核细节。

MFU 再除以源码硬编码的 312×1012,这是所选 A100 BF16 峰值口径。换 CPU、其他 GPU、精度或稀疏峰值,分母应重新定义。把 CPU 实验打印出的微小 MFU 当作“CPU 利用率”没有意义;它只是除错了比较对象。

13.4 DDP 的 MFU 分子应与分母匹配 ​

训练循环传 batch_size * 本地累积次数,估算单 rank 的工作量,再与单卡峰值比较。只有各 rank 工作量相等、用时代表共同速度时,这才可近似理解为整体同构集群的平均利用率。不能把全局 token 工作量除以单卡峰值,否则数值会随卡数虚增;也不能本地工作量除以全机峰值,否则虚降。

dt 的测量不一定等于严格同步的 GPU 计算窗口,前五个本地迭代也不计算 MFU,后续使用 0.9 旧值加 0.1 新值平滑。初始 running_mfu=-1 会打印 -100%,这是“尚未测量”的哨兵值,不是负利用率。

若实际输入长度小于 config.block_size,估算仍用配置长度,FLOPs 会高估。测试 crop 后应确认 config 已更新。若运行激活重计算,真实执行 FLOPs 可增加,而“模型理论工作量”不一定随之增加;MFU 与硬件 FLOPs 利用率的口径也应分开。

13.5 profiler 能回答另一类问题 ​

profile=True 时设置 5 步等待、5 步预热、5 步活动记录;活动包括 CPU 和 CUDA,输出 TensorBoard trace。with_flops=True 请求估计部分算子 FLOPs,record_shapes=False、profile_memory=False、with_stack=False 关闭部分额外记录。关闭堆栈减少开销,却也减少定位信息;不能既不记录形状和显存,又希望报告回答所有显存问题。

torch.compile 的首次编译成本应与稳态收益分开。README 中约 250ms 到 135ms 的数字是 2022 年末某组历史实验,不是当前机器通用承诺。编译还可能改变 state dict 包装前缀,恢复代码因此有清理逻辑。性能变化、数值误差和保存兼容性应分别测试。

补充练习。 两份报告分别写“100000 token/s”和“50% MFU”,能直接判断谁更快吗?

答案。 不能。需要模型规模、上下文、全局/本地口径、硬件及峰值定义。较大模型即使 token/s 较低,也可能执行更多 FLOPs 并达到更高 MFU。先把分子分母补齐,再比较相同任务。

13.6 benchmark 的全部默认开关 ​

字段固定源码默认值含义及边界
batch_size12每进程每微步序列数;不是全局 batch
block_size1024每序列长度;限制窗口和位置表
biasFalse此处新建模型的 Linear/LN 是否带偏置;不走预训练导入
real_dataTrueTrue 读 OWT;False 复用固定随机张量
seed1337初始化抽样 RNG;跨环境不保证逐位一致
device'cuda'benchmark 的单进程设备;此入口没有 DDP 分支
dtype支持 CUDA BF16 则 bfloat16,否则 float16自动混合精度选择;CPU 不使用 autocast
compileTrue是否 torch.compile;本书关闭,不测编译收益
profileFalse启用 CPU/CUDA trace;否则预热后简单计时

模型层数、头数和宽度在构造处写死;表中不包含它们,是因为它们不是这个入口支持的命令行配置键。

本章来源 ​

保留各书来源与许可 · 示例运行条件以正文为准