1. 大模型计算指标到底在算什么
第一次接触“大模型计算指标”这个概念,很多人会以为它只是几张性能跑分表,或者某个框架里自带的监控面板。实际干过一轮训练和推理优化之后你会发现,这套指标体系更像是给模型做体检的化验单——它不告诉你模型聪不聪明,但能告诉你它“跑得累不累、喘得凶不凶、吃得多不多”。我最初做推理服务压测的时候,就是因为只看吞吐量不看显存占用曲线,结果上线第二天就被OOM打脸,从那以后才老老实实把计算指标当成一等公民来对待。
所谓大模型计算指标,核心是围绕算力消耗、内存占用、通信开销、时间延迟这四个维度展开的一组量化数据。它解决的是一个非常实际的问题:同样一个模型,为什么在A卡上能跑出每秒三千token,换到B卡就只剩八百?为什么训练loss正常下降,但GPU利用率始终卡在40%上不去?这些问题的答案,全都藏在计算指标里。适合阅读这篇内容的人包括:正在做模型训练调优的工程师、负责推理服务部署的后端开发、需要做硬件选型和成本估算的技术决策者,以及任何想搞清楚“大模型到底吃多少资源”的从业者。哪怕你只是刚接触这个领域,只要跟着把每个指标的含义和测量方法过一遍,也能建立起一套可复用的分析框架。
我写这篇东西的出发点很简单:网上讲大模型原理的文章铺天盖地,但真正把计算指标拆开揉碎、告诉你每个数字怎么来的、怎么测的、怎么用的内容少得可怜。很多团队在汇报时列一堆FLOPS和显存数字,但被问到“这个数字是怎么算出来的”就支支吾吾。所以下面我会从设计思路讲到实操细节,把踩过的坑和总结的技巧都摊开来说。
2. 核心指标体系的设计逻辑与选型考量
2.1 为什么是这四个维度而不是别的
大模型的计算指标不是拍脑袋定的,它对应的是硬件资源的四种瓶颈类型。算力对应计算单元(比如GPU的CUDA核心或Tensor Core),内存对应显存和内存带宽,通信对应卡间互联和网络带宽,时间则是前三者综合作用的外在表现。这四个维度基本覆盖了从单卡到集群的所有资源约束场景。
我见过一些团队只盯着FLOPS看,觉得算力越大越好,结果买了一批高算力卡回来发现显存不够,模型根本放不下。也见过只关注显存的,选了超大显存的卡但互联带宽拉胯,做张量并行时通信成了瓶颈,多卡加速比惨不忍睹。所以这四个维度必须放在一起看,缺一个都会导致选型或优化方向跑偏。
从测量角度来说,这四个维度也有层次关系。时间指标(延迟、吞吐)是最外层、最容易观测的,但它的波动受太多因素影响,单独看容易误判。算力和内存指标是中间层,能解释时间指标为什么好或不好。通信指标则是在分布式场景下才会凸显出来。我的习惯是先看时间指标定位问题,再往下钻到算力、内存、通信去找根因。
2.2 训练指标和推理指标的差异
训练和推理虽然都跑在同样的硬件上,但计算指标的侧重点完全不同。训练阶段最关心的是吞吐量(每秒处理多少样本或token)和算力利用率(MFU,Model FLOPS Utilization),因为训练时间直接等于成本。推理阶段则更关注首token延迟(TTFT)和每token延迟(TPOT),因为这直接影响用户体验。
这里有个很容易混淆的点:训练时的MFU和推理时的算力利用率不是一回事。训练是计算密集型,MFU能到40%以上就算不错了;推理是内存带宽密集型,decode阶段算力利用率往往只有个位数百分比,这时候盯着算力利用率看毫无意义,应该看的是内存带宽利用率。
注意:不要拿训练阶段的指标基准去衡量推理性能,两者的瓶颈根本不在一个地方。我见过有人用训练时的MFU来评估推理优化效果,结果优化了半天发现指标纹丝不动,因为瓶颈压根不在算力上。
2.3 指标采集的粒度选择
采集粒度是个容易被忽视但很关键的设计决策。粒度太粗(比如只看整个训练任务的平均值),会掩盖掉很多瞬时的异常;粒度太细(比如每毫秒采一次),数据量爆炸不说,采集本身还会影响性能。
我的经验是分三层采集:宏观层按训练步或推理请求统计,用于看整体趋势;中观层按算子或层统计,用于定位瓶颈在哪个模块;微观层按CUDA kernel统计,用于做极致的kernel级优化。大部分场景下中观层就够用了,只有需要做深度优化时才下钻到微观层。
采集工具方面,训练场景常用的是框架自带的profiler(比如PyTorch的torch.profiler),推理场景则更多依赖服务框架的metrics接口加上硬件厂商提供的监控工具。不管用什么工具,核心原则是采集开销要控制在总计算时间的5%以内,否则测出来的数据本身就失真了。
3. 核心指标详解与计算方法
3.1 算力指标:FLOPS与MFU
FLOPS(Floating Point Operations Per Second)是衡量算力的基础单位,但要注意区分理论峰值FLOPS和实际达成FLOPS。理论峰值是硬件规格书上写的数字,实际达成则取决于你的模型和优化水平。
计算一个模型的理论计算量有个常用公式。对于Transformer类模型,前向传播的计算量大约是:
FLOPs ≈ 2 × 参数量 × token数训练时因为包含前向和反向,大约是前向的3倍:
训练FLOPs ≈ 6 × 参数量 × token数这个公式是估算值,实际会因为注意力机制的平方复杂度、激活重计算等因素有偏差,但用来做量级估算足够了。
MFU的计算就是实际达成的FLOPS除以硬件理论峰值:
MFU = 实际FLOPS / 理论峰值FLOPS举个例子,某张卡的理论峰值是300 TFLOPS(这里指特定精度下的数值),你训练时实测每秒处理了1000个token,模型参数量是70B,那么实际FLOPS大约是6 × 70e9 × 1000 = 4.2e14 = 420 TFLOPS,MFU就是420/300,超过1了说明我的举例数字有问题——这正好说明一个常见坑:理论峰值要按实际使用的精度来算。如果用FP16训练,就要用FP16的峰值;如果用BF16,就用BF16的峰值。不同精度的峰值可能差一倍甚至更多。
实操心得:算MFU之前先确认三件事——模型实际参数量(不是配置文件里写的,是实际参与计算的)、实际使用的计算精度、硬件的对应精度峰值。这三个有一个搞错,算出来的MFU就没有参考价值。
3.2 内存指标:显存占用与带宽利用率
显存占用是很多人最关心的指标,因为它直接决定模型能不能跑起来。显存占用主要分四块:模型参数、梯度、优化器状态、激活值。
模型参数占用很好算,参数量乘以每个参数的字节数。FP16是2字节,FP32是4字节,INT8是1字节。一个70B参数的模型,FP16下光参数就要140GB,单卡根本放不下,所以必须做模型并行或量化。
梯度占用和参数一样大(训练时),优化器状态如果是Adam,需要存储一阶矩和二阶矩,又是参数量的两倍。所以训练一个70B模型,光参数+梯度+优化器状态就要140+140+280=560GB,这还没算激活值。这就是为什么大模型训练必须用分布式。
激活值占用和batch size、序列长度强相关,而且不同层的激活值大小不一样。有个粗略估算方法:激活值占用大约是 batch_size × seq_len × hidden_size × 层数 × 精度字节数 × 系数。这个系数取决于具体实现,一般在2到10之间。
内存带宽利用率是推理场景的核心指标。decode阶段每生成一个token,都需要把整个模型参数从显存读一遍,所以理论上的token生成速度上限是:
最大token/s = 显存带宽 / 模型参数字节数比如显存带宽是2TB/s,模型是FP16的7B(14GB),那么理论上限就是2000/14 ≈ 143 token/s。实际能达到60%到80%就算优化得很好了。
3.3 通信指标:带宽与通信量
分布式训练和推理中,通信开销往往是隐藏的杀手。通信指标主要看两个:通信量(传了多少数据)和通信带宽(传得多快)。
数据并行的通信量相对好算,每步需要同步梯度,通信量等于模型参数量乘以精度字节数。70B模型FP16下每步要同步140GB的梯度,如果卡间带宽是100GB/s,光通信就要1.4秒,这还没算计算时间。
张量并行的通信量更大,因为每层的前向和反向都需要做all-reduce或all-gather。通信量和层数、hidden_size都相关,具体计算比较复杂,但有个经验规律:张量并行的通信量通常是数据并行的数倍。
流水线并行的通信量最小,只在阶段之间传递激活值,通信量大约是 batch_size × seq_len × hidden_size × 精度字节数。但流水线并行有气泡问题,需要配合微批次来缓解。
注意:通信指标不能只看带宽,还要看通信效率。同样传140GB数据,用NVLink和用普通网络,实际耗时可能差十倍。选型时一定要确认卡间互联的实际带宽,不要只看标称值。
3.4 时间指标:延迟与吞吐
时间指标是最直观的,但也是最容易误读的。吞吐量(throughput)和延迟(latency)往往是一对矛盾体,增大batch size能提升吞吐但会增加延迟,减小batch size能降低延迟但吞吐上不去。
推理场景下,延迟要拆成首token延迟(TTFT)和每token延迟(TPOT)。TTFT主要受prefill阶段影响,和输入长度强相关;TPOT主要受decode阶段影响,和模型大小、显存带宽相关。这两个指标要分开优化,因为瓶颈不同。
训练场景下,主要看每步耗时(step time)和每秒样本数(samples/s)。每步耗时又可以分为计算时间、通信时间、数据加载时间。优化时要先定位哪部分占比最大,再针对性处理。
有个容易被忽视的指标是时间抖动(jitter)。平均延迟看起来很好,但P99延迟很高,说明存在长尾问题。在线上服务中,P99延迟往往比平均延迟更重要,因为用户对卡顿的感知是看最差的那几次。
4. 实操测量与优化流程
4.1 训练场景的指标采集实操
训练场景我习惯用PyTorch的profiler配合自定义的metrics hook。具体做法是在训练循环里插入计时点和显存查询:
import torch import time # 记录开始时间和显存 torch.cuda.synchronize() start_time = time.time() start_mem = torch.cuda.memory_allocated() # 训练步 output = model(input_ids) loss = criterion(output, labels) loss.backward() optimizer.step() optimizer.zero_grad() # 记录结束时间和显存 torch.cuda.synchronize() end_time = time.time() end_mem = torch.cuda.memory_allocated() step_time = end_time - start_time peak_mem = torch.cuda.max_memory_allocated()这里有个关键点:一定要在计时前后调用torch.cuda.synchronize()。因为CUDA操作是异步的,不同步的话计时结果完全不准。我早期就犯过这个错误,测出来的step time比实际少了一半,还以为是优化见效了。
采集到的数据要按训练步记录下来,画成曲线看趋势。正常情况下step time应该在一个小范围内波动,如果出现周期性的大幅波动,可能是数据加载或通信出了问题。显存占用应该是稳定的,如果持续增长,大概率是内存泄漏。
4.2 推理场景的压测方法
推理压测比训练复杂,因为要模拟真实的请求模式。我一般分三步走:单请求基准测试、固定并发压测、变并发压测。
单请求基准测试是发一个请求,测TTFT和TPOT,这是最理想情况下的性能。固定并发压测是同时发N个请求,看吞吐量和延迟的变化。变并发压测是模拟真实流量,并发数随时间变化,看系统的稳定性。
压测工具可以用开源的负载测试框架,也可以自己写脚本。关键是要控制变量:输入长度、输出长度、并发数、请求间隔都要明确记录。我见过有人压测时不记录输入输出长度,结果两次测试结果差了一倍,排查半天才发现是输入长度不同导致的。
压测时还要监控显存碎片。长时间运行后,显存碎片会导致明明有足够空闲显存却分配不出连续空间。监控方法是定期查询显存的分配和保留情况,如果保留显存远大于分配显存,说明碎片严重。
4.3 基于指标的优化决策树
拿到指标数据后,怎么决定优化方向?我总结了一个简单的决策流程:
| 现象 | 可能瓶颈 | 优化方向 |
|---|---|---|
| 算力利用率低,显存带宽利用率高 | 内存带宽 | 量化、减少访存、算子融合 |
| 算力利用率高,显存带宽利用率低 | 算力 | 增加并行度、使用更快精度 |
| 通信时间占比高 | 通信 | 调整并行策略、梯度压缩、通信重叠 |
| 显存占用高但利用率低 | 显存管理 | 激活重计算、梯度累积、显存池化 |
| 延迟抖动大 | 调度或数据加载 | 预取、批处理优化、异步加载 |
这个表不是绝对的,但能覆盖大部分常见场景。实际排查时,先用profiler拿到各部分的耗时占比,再对照这个表找方向。
实操心得:优化之前一定要先建立基线。没有基线的优化就像蒙眼开车,你不知道自己是在前进还是在原地打转。基线要记录完整的指标数据,包括硬件配置、软件版本、模型配置、输入输出规格,否则后续对比没有意义。
4.4 一个完整的优化案例拆解
假设我们有一个7B模型的推理服务,实测TPOT是50ms,目标是降到20ms。先采集指标:显存带宽利用率85%,算力利用率5%,显存占用14GB(FP16)。
分析:算力利用率极低,显存带宽利用率很高,典型的内存带宽瓶颈。TPOT的理论下限是模型大小除以显存带宽,14GB除以2TB/s等于7ms,所以还有优化空间。
第一步优化:做INT8量化。模型大小降到7GB,理论TPOT下限降到3.5ms。实测TPOT降到30ms,显存带宽利用率还是85%左右。说明量化生效了,但还没达到理论上限。
第二步优化:做算子融合。把多个小算子合并成一个大算子,减少kernel启动开销和中间结果的访存。实测TPOT降到22ms。
第三步优化:调整batch size和调度策略。发现当前batch size下GPU有等待时间,增大batch size后TPOT降到18ms,但TTFT略有上升。最终在延迟和吞吐之间取了个平衡点。
这个案例说明,优化是个迭代过程,每步都要重新采集指标看瓶颈是否转移。不要指望一步到位,也不要盲目套用别人的优化方案,因为瓶颈可能完全不同。
5. 常见问题与排查技巧实录
5.1 指标数据不可信的几种情况
情况一:计时不同步。前面提过,CUDA异步执行导致计时偏短。解决方法是在计时点前后加synchronize,或者用CUDA event来计时。
情况二:profiler开销过大。有些profiler会记录每个kernel的详细信息,开销可能达到20%以上。这时候测出来的性能不代表真实性能。解决方法是先用低开销的粗粒度采集定位大致范围,再针对特定区域开高开销的细粒度采集。
情况三:预热不充分。第一次运行会因为各种初始化(CUDA上下文、内存分配、kernel编译)而特别慢。必须预热足够多的步数,等指标稳定后再开始记录。我一般预热10到20步。
情况四:后台干扰。同一台机器上跑着其他任务,或者有监控进程在采样,都会影响指标。压测时尽量独占机器,关掉不必要的后台服务。
5.2 显存溢出但显示显存充足
这是最让人头疼的问题之一。nvidia-smi显示还有空闲显存,但程序就是报OOM。原因通常是显存碎片。PyTorch的缓存分配器会预留大块显存,如果请求的连续空间比最大的空闲块大,即使总空闲显存足够也会失败。
解决方法有几个:设置PYTORCH_CUDA_ALLOC_CONF环境变量调整分配策略;在训练循环中定期调用torch.cuda.empty_cache()释放未使用的缓存;或者改用更节省显存的实现方式,比如激活重计算。
注意:
empty_cache()会释放缓存,但也会导致下次分配变慢。不要在每步都调用,只在显存紧张时调用。
5.3 多卡加速比不达预期
多卡训练时,理想情况下N卡应该带来N倍加速,但实际往往只有60%到80%。原因通常是通信开销和负载不均衡。
排查步骤:先看通信时间占比,如果超过20%,说明通信是瓶颈。再看各卡的利用率是否均衡,如果某张卡利用率明显偏低,说明负载不均衡。最后看通信和计算是否重叠,如果没有重叠,通信时间就是纯开销。
优化方向:调整并行策略(数据并行、张量并行、流水线并行的组合);使用梯度累积减少通信频率;开启通信和计算的重叠(比如在反向传播时提前开始梯度同步)。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 算力利用率低 | 内存带宽瓶颈或数据加载慢 | 看显存带宽利用率和数据加载时间 | 量化、算子融合、预取数据 |
| 显存OOM但有空闲 | 显存碎片 | 查最大空闲块大小 | 调整分配策略、定期清缓存 |
| 多卡加速比低 | 通信瓶颈或负载不均 | 看通信时间占比和各卡利用率 | 调整并行策略、通信重叠 |
| 延迟抖动大 | 调度问题或GC | 看P99和P50的差距 | 优化调度、减少动态分配 |
| 吞吐上不去 | batch size太小或调度开销大 | 看GPU空闲时间 | 增大batch、批处理请求 |
| 训练loss异常 | 数值精度问题 | 看梯度范数和激活值范围 | 调整精度、梯度裁剪 |
5.5 几个容易被忽视的细节
细节一:精度对指标的影响。FP16和BF16的算力峰值可能不同,显存占用相同但数值稳定性不同。做指标对比时一定要确认精度一致。
细节二:序列长度的影响。注意力机制的计算量随序列长度平方增长,所以长序列场景的指标和短序列完全不同。压测时要覆盖实际业务中的序列长度分布。
细节三:冷启动和热启动的差异。推理服务刚启动时,模型权重还在内存里没加载到显存,第一次请求会特别慢。生产环境要考虑预热机制。
细节四:不同框架的指标口径不同。同样叫“吞吐量”,有的框架算的是输入token数,有的算的是输出token数,有的算的是请求数。对比时要统一口径。
6. 指标体系的落地与持续迭代
6.1 建立指标看板
零散的指标数据价值有限,要把它变成持续可观测的看板。训练场景我一般记录:step time、samples/s、MFU、显存占用、通信时间占比、loss曲线。推理场景记录:QPS、TTFT、TPOT、P99延迟、显存占用、batch size分布。
看板的作用是发现趋势和异常。比如step time突然变大,可能是数据加载出了问题;TPOT逐渐上升,可能是显存碎片积累。有了看板就能在问题恶化之前介入。
6.2 指标驱动的容量规划
有了历史指标数据,就能做容量规划。比如根据当前的QPS和TPOT,推算需要多少张卡才能支撑目标流量。计算方法是:目标QPS乘以每请求的计算量,除以单卡的处理能力,再留一定的冗余。
容量规划时要注意峰值和均值的差异。按均值规划会导致高峰期过载,按峰值规划会导致资源浪费。我一般按P95流量来规划,再配合弹性伸缩应对突发流量。
6.3 持续优化的节奏
指标优化不是一次性的工作,而是持续迭代的过程。我的节奏是:每周看一次指标趋势,每月做一次深度分析,每季度做一次全面优化。深度分析时会用profiler做细粒度采集,全面优化时会尝试新的量化方案、并行策略或硬件配置。
每次优化后都要更新基线,否则后续对比会混乱。基线数据要存档,包括当时的软硬件配置和模型配置,方便回溯。
6.4 团队协作中的指标规范
如果是团队协作,指标的定义和采集方法要统一。我见过不同团队用不同的口径算MFU,汇报时数字对不上,扯了半天才发现是算法不同。所以最好在团队内建立指标规范文档,明确每个指标的定义、计算公式、采集方法和正常范围。
规范文档不用很复杂,一张表就够:指标名称、定义、公式、采集工具、正常范围、异常处理。新成员入职时先看这个文档,能省很多沟通成本。
7. 一些个人体会
做指标优化这些年,最大的感受是:不要迷信任何单一指标。我见过MFU很高但实际训练很慢的,因为通信时间没算进去;也见过显存占用很低但推理很慢的,因为频繁在CPU和GPU之间倒腾数据。指标要组合看,要结合具体场景看。
另一个体会是:优化要有优先级。不是所有指标都值得优化,要先搞清楚业务最关心什么。如果是离线训练,吞吐量最重要;如果是在线推理,延迟最重要。把精力花在关键指标上,比全面撒网效果好得多。
最后说个实用的技巧:遇到性能问题先别急着改代码,先把指标数据完整采集一遍。很多时候数据一看,问题就清楚了。盲目改代码往往是在治标不治本,甚至引入新问题。指标是眼睛,没有眼睛的优化就是瞎折腾。