Meta 的生成式广告推荐模型 GEM,是 Instagram 和 Facebook 广告推荐背后的基础模型。它的训练已经进入 LLM 规模:使用数千张最新一代 GPU,并在训练 FLOPs 扩大 4 倍的同时,将端到端训练效率提高一倍,使模型 FLOPs 利用率(Model FLOPs Utilization,MFU)达到 20%–25%。
这组数字值得关注的地方,不只是“用了更多 GPU”。在数千卡集群里,计算、通信、数据读取、检查点和故障恢复都会放大成系统瓶颈。规模扩大 4 倍之后还能提升端到端效率,意味着优化目标必须覆盖完整训练链路,而不能只盯着某个算子的峰值性能。
先读懂 20%–25% MFU
MFU 衡量的是模型实际执行的有效浮点运算量,相对于 GPU 理论峰值算力的比例。可以将它简化为:
MFU = 每秒完成的模型 FLOPs / 集群每秒理论峰值 FLOPs
假设一次训练 step 对每个样本需要 F FLOPs,全局 batch size 为 B,step 时间为 T 秒,集群包含 N 张 GPU,每张 GPU 的理论峰值为 P FLOPs/s,那么:
MFU = (F × B / T) / (N × P)
MFU 并不等于 GPU utilization。监控面板上的 GPU utilization 很高,只能说明设备大部分时间在执行任务;这些任务可能包含低效 kernel、数据搬运或无法充分利用 Tensor Core 的操作。MFU 更接近一个业务问题:购买的理论算力中,有多少真正转化成了模型训练所需的计算。
同样需要区分“稳定 step 的 MFU”和“端到端效率”。如果基准测试跳过初始化、数据加载、评估、检查点与故障恢复,得到的数字可能很好看,却无法代表一次完整训练任务。GEM 报告的是端到端训练效率提升,因此 20%–25% 的意义比单个 kernel 或短时间窗口的峰值更大。
四倍训练 FLOPs,不等于简单增加四倍 GPU
训练 FLOPs 扩大 4 倍,描述的是整个训练任务的计算规模变化。它可能来自更大的模型、更多训练样本、更长训练周期,或这些因素的组合;来源摘要没有披露具体构成,因此不能据此推断 GEM 的参数量、batch size 或训练时长。
无论 FLOPs 从哪里增加,分布式训练都会遇到几个共同约束:
- 通信比例上升:梯度同步、参数交换或跨设备重分布会占用网络时间。
- 慢节点拖累全局 step:同步训练的速度由最慢的 worker 决定,尾延迟会随着节点数增加而更明显。
- 数据链路必须同步扩容:数千张 GPU 消耗数据的速度很快,数据解码、特征读取和远程存储容易让 GPU 等待。
- 故障成为常态:卡数和训练时间越大,单卡、主机、网络及存储故障越可能发生,恢复时间会直接侵蚀端到端效率。
- 检查点成本增加:模型状态、优化器状态以及其他训练元数据的写入量会随规模增长。
因此,不能仅凭 GEM 达到 20%–25% MFU,就推断 Meta 使用了某一种并行策略或特定通信算法。来源摘要确认的是结果:训练 FLOPs 扩大 4 倍,端到端效率翻倍,并扩展到数千张最新一代 GPU。具体技术组合需要以完整披露为准。
可以这样实践:用统一脚本测量训练 MFU
下面是一个可直接运行的 MFU 计算器。它不是 GEM 的内部工具,而是根据公开指标定义构造的最小实践示例。运行前需要准备四个数据:每个 step 的模型 FLOPs、平均 step 时间、GPU 数量,以及单卡在当前训练精度下的理论峰值。
将以下内容保存为 mfu.py:
#!/usr/bin/env python3
import argparse
def positive(value: str) -> float:
number = float(value)
if number <= 0:
raise argparse.ArgumentTypeError("value must be greater than zero")
return number
parser = argparse.ArgumentParser(description="Calculate distributed training MFU")
parser.add_argument("--model-flops-per-step", type=positive, required=True)
parser.add_argument("--step-seconds", type=positive, required=True)
parser.add_argument("--gpus", type=int, required=True)
parser.add_argument("--peak-tflops-per-gpu", type=positive, required=True)
parser.add_argument("--samples-per-step", type=int, default=0)
args = parser.parse_args()
if args.gpus <= 0 or args.samples_per_step < 0:
parser.error("gpus must be positive and samples-per-step cannot be negative")
model_tflops_per_second = args.model_flops_per_step / args.step_seconds / 1e12
cluster_peak_tflops = args.gpus * args.peak_tflops_per_gpu
mfu = model_tflops_per_second / cluster_peak_tflops
print(f"Model throughput: {model_tflops_per_second:,.2f} TFLOP/s")
print(f"Cluster peak: {cluster_peak_tflops:,.2f} TFLOP/s")
print(f"MFU: {mfu:.2%}")
if args.samples_per_step:
print(f"Sample throughput: {args.samples_per_step / args.step_seconds:,.2f} samples/s")
示例命令中的数值只是演示,必须替换为自己的模型分析结果和硬件规格:
python3 mfu.py \
--model-flops-per-step 2.4e18 \
--step-seconds 1.2 \
--gpus 128 \
--peak-tflops-per-gpu 80 \
--samples-per-step 8192
这组示例会得到约 19.53% MFU。model-flops-per-step 应来自模型 FLOPs 分析器或训练框架 profiler,而不是直接使用 GPU profiler 报告的全部硬件操作数。peak-tflops-per-gpu 还要与实际数据类型匹配,例如 BF16、FP16 和 FP8 的理论峰值并不相同,也要明确是否使用稀疏峰值。
为了测量端到端效率,可以分别记录两个时间窗口:
steady_state_mfu = 有效模型 FLOPs / 稳态 step 时间 / 理论峰值
end_to_end_mfu = 全任务有效模型 FLOPs / 作业总墙钟时间 / 理论峰值
第二个指标应包含初始化、周期性评估、检查点、可恢复故障以及必要的数据准备。两者之间的差距,就是系统层优化最值得追踪的空间。
扩容时应建立什么样的验证闭环
对于计划从几十张卡扩展到数百或数千张卡的团队,可以把验收拆成四类指标:
- 模型进度:每秒样本数、每秒 token 或有效训练 FLOPs,以及相同样本量下的损失曲线。
- 计算效率:稳态 MFU、端到端 MFU、关键 kernel 时间和 GPU 空闲比例。
- 扩展效率:GPU 数量增加后,吞吐提升与理想线性提升的比值。
- 运行可靠性:失败频率、平均恢复时间、检查点耗时以及因慢节点造成的 step 尾延迟。
优化时一次只改变一个主要变量,并保留相同的模型配置、数据范围和统计窗口。否则,batch size、精度、激活重计算或并行方式的变化可能让两次 MFU 结果失去可比性。
GEM 的结果说明,大规模推荐模型训练已经需要用 LLM 基础设施的方式来经营:既看模型计算,也看通信、数据、存储和恢复。20%–25% MFU 不是一个可以孤立复制的参数,而是完整系统协同后的结果。团队采用类似方法时,最重要的是先建立可信的端到端基线,再按等待时间占比排序瓶颈;只有这样,扩容带来的 GPU 才会转化成训练进度,而不是更昂贵的空闲时间。