做训练优化的朋友,几乎都绕不开一个词:MFU(Model FLOPs Utilization,模型浮点算力利用率)。我们经常拿着 NVIDIA 的规格表看 GPU 峰值算力,比如多少 TFLOPS、多少 BFLOPS,然后估算“7B 模型跑一个 step 应该用多少秒”,但等到真正上线一测,往往比理论慢两三倍。问题多数不在硬件,而在你对 MFU 的理解方式。这篇文章就专门拆解,怎么用 GPU 峰值算力这把“尺子”,结合 MFU 去评估一条训练任务的真实速度,以及怎么把误差一步步压到可控范围。适合刚接触大模型训练、想搞懂训练耗时预算,或者正在做性能调优的人。
1. MFU 究竟在算什么账
1.1 MFU 的定义和边界
MFU 的正式定义很朴素:在一次训练过程中,GPU 上真正用于模型计算的浮点操作数,除以同一时间内 GPU 能达到的理论峰值浮点操作数。打个比方,你租了一辆极速 300 km/h 的跑车,但城市通勤平均只跑到 60 km/h,利用率就是 20%。MFU 就是深度学习里的“平均车速”。
这里有几个边界要划清楚。MFU 关注的是“计算单元”的利用程度,它不直接指显存带宽利用率,也不直接指显存占用率。很多时候显存塞得满满当当,MBU(Memory Bandwidth Utilization)爆表,但 MFU 反而很低,因为模型在等数据从显存搬运回寄存器,计算单元在干站着。所以讨论训练速度,MFU 和 MBU 要分开看,这篇文章重点在前者。
另外,MFU 是一个无单位比例,不是一个绝对值。A100 上跑到 45% 的 MFU,和 H100 上跑到 45%,绝对算力完全不同,但由于各自分母不同,它们反映的“把硬件榨干的程度”是同一个刻度。想要横向对比两套训练方案,用 MFU 比用单纯的 TFLOPS 更公平。
1.2 为什么训练速度不能只看 BATCH_SIZE
很多人一开始估算训练时长,习惯套一个公式:总训练 token 数 ÷(global batch size × 每秒能跑多少 step)。听起来没问题,但它把很多变量压成了一个黑盒。真正到了 GPU 上,每秒能跑多少 step 由三个变量决定:模型本身的浮点计算量、硬件的峰值算力、以及 MFU。
举个例子,同是 7B 模型,同是 1M token 的 global batch,在 A100 上可能每 step 跑 300 秒,在 H100 上可能是 150 秒。差别来自峰值算力,也来自 MFU。H100 的显存带宽、互联速度和 Tensor Core 微架构都不同,MFU 往往也不同。所以你直接照搬别人论文里的“每秒 token 数”来估自己的任务,大概率翻车,因为你们连算力利用水平都不一样。
用 MFU 把算力利用固定成一个人为设定的参考值(比如 40%),再去反推训练速度,能在方案阶段就给出合理预期。这也是做预算、买卡、评估要不要上多机并行时最实用的一套估算框架。
2. GPU 峰值算力:看规格表之前先看清口径
2.1 一张规格表里藏着三种“峰值”
GPU 厂商宣传的“峰值算力”往往有三套打法。最常见的是 Tensor Core 的稠密算力,对应 FP16/BF16,这是大模型训练最常用到的口径。第二套是稀疏算力,利用矩阵中一半元素为 0 的特性,把算力翻倍,但如果你不用 2:4 结构化稀疏,这个数字永远达不到。第三套是 FP32/FP64 的普通 CUDA Core 算力,做科学计算或某些显存拷贝类算子才会接近。
你拿一张 RTX 4060 Laptop GPU 去看参数,会发现它的 FP16 Tensor Core 算力和 FP32 差别不小;换成 A100 或 H100,BF16 与 TF32 的差距同样明显。所以评估训练速度前,第一步不是看“多少 TFLOPS”,而是确认你训练的混精度类型到底对应哪一行。训练大模型通常用 bf16,那我建议所有估算都锁定 BF16 dense 那行数字。
顺带提醒,很多消费级显卡的规格表里写的是“峰值 FP16”,不少还是带稀疏加速的数字。比如某些宣传页写着 300+ TFLOPS,去掉稀疏后实际只有一半。买不到数据中心卡、拿消费卡做实验时,尤其要看清楚标的是不是 dense。
2.2 从 TFLOPS 到训练速度的换算链
从峰值算力推出训练速度,中间隔着一条换算链。核心公式可以写成:
某一阶段每秒实际浮点能力 = 峰值算力 × MFU
然后结合该阶段所需的总浮点操作数,就能算出耗时。比如某层矩阵乘需要 1 PFLOPs 的计算量,GPU 峰值是 312 TFLOPS,MFU 期望是 45%,那么每秒实际算力是 140.4 TFLOPS,该层耗时约 7.12 秒。
这条换算链有一个关键假设:MFU 是可以被预估和调控的。实际训练里的 MFU 由 kernel 实现、显存带宽、通信占比、batch size 等多重因素决定,很难一开始就拍脑袋定死。更稳妥的方法是用一个“参考 MFU”区间:小 batch、单机单卡实验通常 30%~45%;数据并行、模型并行都优化得不错时,40%~55%;如果混入了大量交互式调试、小文件读取,MFU 可能掉到 15% 以下。
我在下面第三部分会基于 A100/BF16 的 312 TFLOPS,手把手把 7B 模型的单个 step 时间推出来。这个流程不依赖具体框架,PyTorch、Megatron-LM 还是 DeepSpeed 都能套。
3. 用一张卡推算训练速度的实操流程
3.1 先数清楚一个 step 需要的 FLOPs
估算第一步,数清楚训练一个 step 要产生多少浮点计算。对大语言模型,业界有一个经验常数:每个 token 每参数量约需要 6 次浮点操作。这个数字拆开看是前向 2NFLOPs、反向 4NFLOPs,反向比前向翻倍的原因是要额外计算梯度。
所以一个包含 B 个 token 的训练 step,总计算量约等于:
TotalFLOPs ≈ 6 × 参数量 × B
比如 7B 模型,假设 global batch 是 1M token,代入就是 6 × 7×10⁹ × 10⁶ = 42×10¹⁵,也就是 42 PFLOPs。这里的 1M token 怎么来的?如果你的每条序列是 2048 token,global batch 是 512 条序列,那 token 数就是 2048×512=1,048,576,约等于 1M。
必须强调,这个 6N 只是估算。实际 Transformer 里 embedding、LayerNorm、激活值计算、attention mask 等操作都会增加一些边角计算量,所以 6N 偏理想。但用它来做前期评估和容量规划,误差完全可接受;真要精确到个位数,得跑 profiling 工具。
3.2 代入峰值算力和 MFU 目标值
现在把硬件参数带进来。以单张 A100 80GB SXM 为例,BF16 Tensor Core 稠密峰值是 312 TFLOPS。假设我设参考 MFU 为 40%,意思是平均只有 40% 的峰值算力真正变成了模型计算的浮点结果。那单卡每秒实际能提供的算力是:
312 TFLOPS × 0.40 = 124.8 TFLOPS
回到刚才的 42 PFLOPs,单卡跑完这个 step 的时间是:
42×10¹⁵ ÷ (124.8×10¹²) ≈ 336.5 秒
如果我有 8 张卡,采用纯数据并行,每个 step 被切成 8 份,理论上每卡只需要处理 5.25 PFLOPs,时间变成约 42 秒。但这里必须做两个修正:一是通信开销,gradient allreduce 会让每轮 step 多几秒;二是全局 batch 不变,数据并行下每卡 micro-batch 变小,kernel 效率和 MFU 会被影响。所以 8 卡跑 42 秒是理想值,实际通常在 45~60 秒之间。
拿到这个数字后,整个训练总时长就好推了。假设总共要训练 200B token,global batch 为 1M token,则需要 200,000 步;单卡理论时间 = 200,000×336.5 秒 ≈ 778 天,8 卡约 97 天。这就是为什么现在训练大模型几乎不可能靠单卡完成——不是显存不够,是算力换时间太不划算。
3.3 多机多卡时的修正项
真正进入多机多卡,简单的除法就不够用了。需要修正的第一项是通信与计算的重叠率。NCCL allreduce 通常可以和下一轮前向计算重叠,但重叠比例不是 100%。第二项是梯度压缩和混合精度通信策略,FP32 梯度转 BF16 再通信,通信量减半,但对收敛有细微影响。第三项是并行策略本身:张量并行会把大矩阵乘法切到多卡,计算量不变,但引入 allreduce 结果通信;流水线并行则会有 bubble 空转,MFU 天然比纯数据并行低。
实践里我习惯把多卡扩展用下面的思路快速估算:
预估耗时 ≈ 单卡耗时 ÷ 卡数 × 扩展损耗系数
扩展损耗系数在小规模(2~8 卡)大约 1.05~1.15,在跨机大规模(64 卡以上)可能到 1.3~1.6,具体取决于网络拓扑。NVIDIA DGX 这类 NVLink 高速互联节点损耗小;普通万兆以太网做数据并行,通信很可能成为瓶颈,MFU 骨折式下跌。
我在早期踩过一个典型坑:用 4 台 8 卡机器跑千兆网互联,纯数据并行训练 13B 模型,实测吞吐只有理论 256 卡的 15%。排了半天发现瓶颈全在跨机 allreduce,每 step 通信时间比计算时间还长。所以估算训练速度时,精力应该放在了解机器拓扑,而不是一味堆卡数。
4. 跑通之前先避坑:MFU 常见误区
4.1 MFU 为什么可能超过 100%
第一次跑 profiling 看到单算子 MFU 132% 时,很多人会愣住。MFU 超 100% 不代表芯片突破了物理法则,最常见原因是分母算小了。比如规格表用的是某个频率下的理论峰值,而实际运行中 GPU 自动超频或功耗墙放开,瞬时频率比标称高;又比如用了稀疏算力专门优化过的算子,但规格表分母用的是稠密值。
另一种更伤脑筋的情况是“不同阶段的 FLOPs 定义不一致”。PyTorch Profiler 默认统计的 FLOPs 可能只算矩阵乘法,而你自己手算时把 attention 里所有乘加都算了;两边分子分母不统一,比例自然失真。所以我建议团队内部统一口径:要么全部用理论公式 6N 算,要么全部用 profiler 报告值,别混着用。
4.2 算力利用率低的十个隐藏原因
我总结了十个在真实环境里反复出现的元凶,每一种都能把 MFU 干到 20% 以下:
- 数据加载有瓶颈,GPU 每轮都在等 CPU 搬运数据。表现是 GPU 利用率锯齿状波动。
- 单算子 kernel 没调优。比如小 batch 下逐元素算子占比高,Tensor Core 根本使不上劲。
- 显存带宽受限。频繁读写大矩阵时,计算单元有空闲,但显存通道已经跑满。
- 通信和计算没有重叠。allreduce 占了整整一段串行时间。
- 频繁同步。每次 loss 计算、日志打印、checkpoint 都做了一次全局 barrier。
- 并行策略与模型结构不匹配。张量并行切分点导致大量结果汇聚通信。
- 大量小 kernel 启动。每秒钟成千上万次 kernel launch,CPU 调度跟不上。
- 非计算算子的比例过高。LayerNorm、残差连接、mask、indexing 都可能不触发 Tensor Core。
- 功耗与散热限制。GPU 温度过高触发降频,峰值算力瞬间缩水。
- 框架的显存碎片分配导致内存搬家,类似 GPU 内存对齐损失,也会偷走 MFU。
遇到 MFU 明显低于预期,我一般按“先看利用率曲线,再看 kernel 耗时表,最后看通信时间轴”的顺序排查,把问题定位到具体某类原因之后,再针对性优化。
4.3 实测中分辨“理论速度”和“有效速度”
最后再做一次区分。理论速度是把峰值算力、MFU、FLOPs 全部按理想值代入得到的纸面数字;有效速度是你在真实环境里用 wall-clock time 测出来的吞吐。两者差距超过 50%,先别急着调内核,大概率卡在 IO、通信或 CPU 预处理上。
我的习惯是在每次训练启动时固定跑一个 5 分钟的短测试:记录 logs/sec(每秒消费的 token 数),再用该值反推 MFU。如果真实 MFU 离预期超过 10 个百分点,就停下来查原因。这个短测试成本很低,但能让你在几十小时的长训练开始前就发现全局性问题。
5. 我的经验:把 MFU 当作风向标而不是 KPI
5.1 预估之后,如何校准 MAF
让估算真正可靠的,不是一开始就把常数调准,而是建立一条“预估—实测—迭代修正”的闭环。我每次训练新模型都会做一张记录表,写清楚:模型参数量、global batch、单 step 实际耗时、profiler 报告的 FLOPs、反推出的 MFU。几次跑下来,手头就有自己这套软硬件组合的“经验 MFU”,之后再给新任务做速度评估,准确率会高很多。
值得多花一点时间的还有显存带宽。MFU 本身不计算显存带宽利用率,但训练大模型时它经常是实际瓶颈。用类似方式,可以额外算一下 MBU:模型每 step 的显存读写字节数 ÷(被训练的卡数×显存带宽×耗时)。只有 MFU 和 MBU 都放在一起看,才看得清训练卡在“计算不够快”还是“数据喂不上”上。
5.2 最后分享一个连带技巧:用 MFU 反查问题替换
我曾经处理过一次速度下降问题:同一份训练代码,跑了两天后每 step 时间从 80 秒变成 118 秒。当时只看曲线很难判断原因,后面用 MFU 一算,从 44% 降到了 30%。把问题拆成“算力变少了”和“时间变多了”两种方向,再对比显存占用,发现是数据 pipeline 的 pre-fetch 线程被日志库阻塞,导致 GPU 每轮空转。修好后,MFU 立刻回到 43%。
这类场景对我触动挺大:MFU 不是一个挂在 dashboard 上让人欣赏的数字,而是一个能告诉你怎么分配排查精力的信号。GPU 峰值算力固在那儿不会变,但利用率的每一个百分比波动,背后都藏着一笔值得算清楚的账。希望这篇基于实际踩坑梳理的估算方法,能帮你在下一次训练任务里少熬夜、少踩坑,把预算和机器都用在刀刃上。