news 2026/8/29 20:09:11

LLM性能建模:从第一性原理估算延迟、吞吐与显存

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LLM性能建模:从第一性原理估算延迟、吞吐与显存

大语言模型的性能问题,不能等到部署完成、压测脚本跑完才暴露。很多时候,项目还停留在选型阶段,就需要回答“7B 模型在 A100 上生成一个 token 大概要多久”“8 万 token 上下文会占多少显存”“训练 1 万亿 token 需要多少张卡”。这些问题可以凭经验猜测,也可以回到模型参数、数据类型、序列长度和硬件规格这些最基础的数值,用几条公式推算出延迟、吞吐和显存占用的合理区间。这就是从第一性原理出发的 LLM 性能建模:不依赖完整集群,不做繁琐压测,先从最底层的数量级判断开始。

本文按这条主线展开:先建立估算模型,解释 Prefill 和 Decode 为什么是两种不同瓶颈;再以 7B 模型为例做一次完整计算;随后扩展到训练场景,说明 FLOPs、显存和通信怎么估算;最后给出实测校准方法和工程中常见的估算误区。读完以后,你可以对着任意一个模型配置和 GPU 规格,在 10 分钟内给出第一版性能估算,并知道该在哪个环节用真实测量修正它。

1. 从第一性原理建模,先想清楚要算哪些数

1.1 四个核心问题:时延、吞吐、显存、成本

性能建模不是算一个“性能分数”,而是要回答四个相互关联的问题。

要回答的问题常用指标第一性原理输入
第一个 token 要等多久TTFT(Time To First Token)Prefill 阶段计算量、batch 大小、GPU 算力
输出一个 token 要多久TPOT(Time Per Output Token)Decode 阶段每 token 计算量与权重搬运字节数
每秒能处理多少请求吞吐量batch 大小、Prefill/Decode token 比例、调度方式
最长能支持多少上下文显存峰值权重字节数、KV Cache、激活值、框架预留

这四个问题不是孤立的。显存不足会让 batch 变小,batch 变小会让吞吐下降;上下文变长会让 KV Cache 膨胀,进而压缩 batch 空间。建模的价值在于,把所有变量放在同一个公式体系里,改动一个输入,就能看到它对时延、吞吐和显存的连锁影响。

1.2 为什么需要建模而不是直接压测

压测当然必要,但在很多场景下,压测来得太晚,或者成本太高。

首先是选型阶段。模型还没部署,机器还没租好,团队需要先判断“7B 模型用一张卡能不能跑”“量化到 INT4 后够不够快”。这时没有环境可压测,只能靠估算。

其次是定位问题。压测只能告诉你“服务慢”,建模能告诉你是慢在哪个环节。如果实测 decode 单 token 延迟接近权重搬运的耗时,瓶颈在显存带宽;如果远高于估算值,问题可能在注意力计算、Python 开销、服务框架调度或并发排队上。

再次是框架隔离。压测结果受 vLLM、TGI、SGLang 等框架的预分配、连续批处理、显存碎片影响很大。第一性原理模型算的是“模型本身的下限”,先有下限,才能判断框架有没有把性能发挥出来。

注意,建模不能替代压测。估算给的是数量级和上下界,最终上线前还是要用真实请求做验证。合理做法是“建模给先验,实测做校准,再更新模型”。

1.3 Prefill 与 Decode 的瓶颈完全不同

理解 LLM 推理性能,第一步是分清两个阶段。

Prefill 阶段处理整个输入 prompt。它像一次大的矩阵乘法,输入 token 越多,计算量越大。这个阶段可以充分利用 GPU 的并行算力,通常属于计算密集(compute-bound),表现为高 GPU 利用率、单次耗时随输入长度增长。

Decode 阶段逐 token 生成。每生成一个 token,都需要把模型所有权重读一遍。对大多数 GPU 来说,读取 14GB 权重的耗时,远大于在 14GB 权重上做矩阵乘法的耗时。因此 Decode 阶段通常属于访存密集(memory-bound),表现为 GPU 利用率不高,延迟主要受显存带宽限制。

这解释了为什么“一个 token 要多久”和“第一个 token 要多久”必须分开估算。把两者混在一起,用同一套利用率去算,结果会偏差一个数量级。

2. 三个基础量:FLOPs、内存字节数和算术强度

2.1 计算量:Transformer 前向推理的 2NT 近似

对于 decoder-only 的 Transformer,业界常用的经验公式是:处理 T 个 token 的前向推理,计算量约为 2NT。

其中 N 是模型参数量,T 是处理的 token 数。这个近似把多头注意力、MLP、LayerNorm 等所有计算全部折算进去。对 1B 以上的模型,它的误差通常在可接受范围内;但对非常小的模型和极长序列,注意力分数计算 O(T²d) 这一项会变大,需要单独补上。

按这个公式,一次 Prefill 输入 T 个 token,计算量约为 2NT。Decode 阶段每生成一个 token,计算量约为 2N。前者是整段输入一起算,后者是每 token 单独算,这是两者时延差距的计算层原因。

2.2 内存量:权重、KV Cache 与激活值

显存占用主要来自四部分:权重、KV Cache、激活值和框架开销。其中权重和 KV Cache 可以用公式直接估算。

权重内存等于参数量乘以每参数字节数,取决于数据类型。

数据类型每参数字节数常见场景
FP324训练主权重、优化器状态
FP162推理权重、部分训练
BF162训练主流、推理权重
FP81新一代 GPU 上的推理与训练加速
INT81推理量化
INT40.5推理量化,显存压力显著下降

例如 7B 模型,FP16 权重是 14GB,INT4 量化后约 3.5GB。数据类型直接改变权重搬运量,也就直接改变 Decode 阶段的理论时延,这是后续估算的核心参数。

KV Cache 的估算公式是:

KV Cache 字节数 = 2 × 层数 × KV 头数 × 每头维度 × 序列长度 × 每元素字节数

对于多头注意力,KV 头数 × 每头维度等于隐藏维度 d_model,公式可以简化为每 token 约 2 × 层数 × d_model × 字节数。以 32 层、d_model 4096、FP16 为例,每个 token 的 KV Cache 约 0.5MB。2048 token 的输入需要约 1GB,32768 token 需要约 16GB。这还只是一个请求,batch 为 8 时直接翻 8 倍。

激活值在 Prefill 阶段占比很高,具体取决于 batch、序列长度、层数、隐藏维度和是否使用激活重计算。它不像 KV Cache 那样能用简单公式一算到底,通常用显存统计工具实测确认。

2.3 算术强度:用 Roofline 思路判断瓶颈

算出 FLOPs 和字节数之后,需要判断瓶颈落在算力还是带宽上。这时用算术强度(Arithmetic Intensity):每搬运 1 字节数据,能完成多少次浮点运算。

算术强度 = FLOPs / 内存字节数

Prefill 阶段处理 T 个 token,计算量是 2NT,权重和 KV Cache 的写入量约为 N × 字节数加 KV 字节数。当 T 较大时,算术强度随 T 增长,通常远高于 GPU 的“平衡点”,属于计算密集。

Decode 阶段每 token 计算量是 2N,需要搬运的权重字节数是 N × 字节数。算术强度约等于 2 除以字节数。FP16 时约为 1,远低于平衡点,属于访存密集。

这个判断很重要:Decode 阶段无论 GPU 算力多高,只要显存带宽不变,延迟就基本固定在“权重字节数 / 有效带宽”附近。给 7B 模型换更快的 GPU,如果带宽提升不大,Decode 速度也不会明显提升。

3. 手算一次 7B 模型的推理性能

3.1 确定输入参数

下面用一个典型配置做完整计算。所有数字都是公开规格和常见假设,实际项目中需要替换成自己的硬件型号。

参数取值说明
模型参数量 N7e97B 级别
数据类型FP16每参数 2 字节
GPUA100 80GBF16/FP16 峰值算力约 312 TFLOPS,HBM 带宽约 2 TB/s
计算利用率50%大矩阵乘法达不到理论峰值
带宽利用率80%顺序读权重时相对容易接近峰值
Prefill 输入长度2048单请求

这些假设代表“合理的乐观估计”。实际运行时,计算利用率可能是 30% 到 60%,带宽利用率可能是 50% 到 85%,所以算出来的应该是区间而不是精确值。

3.2 先算 Decode,再算 Prefill

先算 Decode,因为它决定了大模型交互的“手感”。

权重字节数:

7e9 × 2 = 14 GB

计算时间:

2 × 7e9 = 14e9 FLOPs 14e9 / (312e12 × 0.5) ≈ 0.09 ms

带宽时间:

14e9 / (2e12 × 0.8) ≈ 8.75 ms

取两者最大值,Decode 单 token 延迟约 8.75ms,折算约 114 token/s。可见计算时间不到带宽时间的 1/100,瓶颈完全在显存带宽。

再算 Prefill。输入 2048 token 的总计算量:

2 × 7e9 × 2048 = 2.87e13 FLOPs

按 156 TFLOPS 有效算力:

2.87e13 / 1.56e14 ≈ 0.18 s

也就是说,TTFT 大约在 0.2 秒量级,不含网络传输和调度排队。如果把它平均到每个 token,大约是 0.09ms,看起来很快,但用户感知的是整段输入处理完之后才开始输出,所以 Prefill 300ms 和 Decode 10ms 必须分开看。

KV Cache 显存:

2 × 32 × 4096 × 2 = 0.5 MB/token 2048 token → 约 1 GB 32768 token → 约 16 GB

权重 14GB 加上 16GB KV Cache,再加上激活值和 CUDA 上下文,80GB 显存勉强够单请求跑 32K 上下文。这也是为什么长上下文场景必须考虑 KV Cache 量化或 GQA 结构。

3.3 把估算过程写成可复用脚本

手算只能验证一次,实际项目中会把公式写成函数,方便批量对比不同模型、不同 GPU、不同精度。

def estimate_llm_inference( n_params: float, # 模型参数量,例如 7e9 seq_prefill: int, # Prefill 阶段输入 token 数 n_layers: int, d_model: int, kv_bytes_per_elem: int = 2, # KV Cache 每元素字节数,FP16 为 2 weight_bytes_per_param: float = 2.0, # 权重每参数字节数 gpu_fp16_flops: float = 312e12, # GPU 峰值算力 gpu_bandwidth_bps: float = 2e12, # 显存带宽 compute_util: float = 0.5, # 计算利用率 bandwidth_util: float = 0.8, # 带宽利用率 ): flops_prefill = 2.0 * n_params * seq_prefill flops_per_token_decode = 2.0 * n_params weight_bytes = n_params * weight_bytes_per_param prefill_time_s = flops_prefill / (gpu_fp16_flops * compute_util) decode_compute_s = flops_per_token_decode / (gpu_fp16_flops * compute_util) decode_memory_s = weight_bytes / (gpu_bandwidth_bps * bandwidth_util) decode_time_s = max(decode_compute_s, decode_memory_s) kv_per_token = 2.0 * n_layers * d_model * kv_bytes_per_elem kv_bytes = kv_per_token * seq_prefill return { "prefill_time_s": prefill_time_s, "ttft_s": prefill_time_s, "decode_ms_per_token": decode_time_s * 1000, "decode_tokens_per_s": 1.0 / decode_time_s, "kv_cache_gb": kv_bytes / 1e9, } result = estimate_llm_inference( n_params=7e9, seq_prefill=2048, n_layers=32, d_model=4096, ) for k, v in result.items(): print(f"{k}: {v:.3f}" if isinstance(v, float) else f"{k}: {v}")

运行后得到一组估算值:

prefill_time_s: 0.184 ttft_s: 0.184 decode_ms_per_token: 8.750 decode_tokens_per_s: 114.286 kv_cache_gb: 1.074

脚本的核心逻辑就是“计算时间和带宽时间取最大值”。这个脚本没有考虑注意力计算的额外耗时、连续批处理对带宽的复用、以及多请求并发时的调度开销,所以它给出的更适合作为下限参考。

4. 从推理扩展到训练:FLOPs、显存与通信

4.1 训练总计算量约为 6NT

推理前向传播约 2NT,训练还要算反向传播。反向传播的计算量约为前向传播的两倍,因此训练一个 epoch 的总计算量约为:

总的训练 FLOPs ≈ 6 × N × T

这里的 T 是所有训练样本的 token 总数。以 7B 模型训练 1 万亿 token 为例:

6 × 7e9 × 1e12 = 4.2e22 FLOPs

假设在 8 张 A100 上训练,每张卡有效算力约 156 TFLOPS:

4.2e22 / (8 × 1.56e14) ≈ 3.37e7 秒 ≈ 390 天

这个结果说明,7B 模型在 8 张 A100 上训练 1 万亿 token,需要一年以上。如果把 GPU 数量提升到 64 张,约 49 天。这个数量级判断足以在项目立项阶段排除不合理的算力方案。

4.2 训练显存:模型状态、梯度和激活

训练显存比推理复杂得多。除了权重,还要保存梯度和优化器状态。混合精度 Adam 训练时,每个参数大约需要:

每参数字节数
FP16 权重2
FP16 梯度2
FP32 主权重4
Adam 一阶动量 m4
Adam 二阶动量 v4
合计16

7B 模型的模型状态约 112GB,远超过单张 A100 的 80GB。这就是为什么训练 7B 模型必须做分布式并行或 ZeRO 分片,而不像推理那样一张卡放权重就够了。

激活值在训练时同样很大,尤其在大 batch 和长序列场景。激活重计算(activation checkpointing)用额外一次前向传播换回显存,是一种典型的“用算力换空间”取舍,训练脚本里通常会开启。

4.3 分布式训练中通信和分片策略的影响

模型状态放不下时,需要用数据并行加 ZeRO 分片。不同策略的显存节省和通信成本差异明显。

方案每卡保存的模型状态每步通信量
DDP(数据并行)每卡完整副本一次全量梯度 AllReduce
ZeRO-1优化器状态分片梯度 ReduceScatter + 参数 AllGather
ZeRO-2优化器状态 + 梯度分片同上,通信略增
ZeRO-3参数、梯度、优化器全部分片每层前向/反向额外 AllGather,通信量最高

DDP 的通信量约为权重字节数的两倍。7B 模型 FP16 梯度约 14GB,一次 AllReduce 实际传输约 28GB。在 NVLink 带宽约 50GB/s 量级的集群上,这是几十毫秒到几百毫秒量级的开销;小模型加多卡时,通信甚至可能超过计算时间。

从第一性原理估算训练性能,不能只看 GPU 算力,必须把“模型状态放不放得下”和“通信要多久”一起算进去。如果模型状态超过单卡显存,先决定分片策略,再算每步耗时。

5. 用实测校准估算,而不是停留在纸面

5.1 实测需要采集哪些指标

建模的价值在于可修正。拿到估算值之后,需要跑一组小规模测量,把估算和实际对齐。

最需要采集的指标是:

指标采集方式用途
Prefill 耗时单次 forward 计时校准计算利用率
Decode 单 token 耗时逐 token 生成计时校准带宽利用率
显存峰值nvidia-smi 或 torch.cuda.max_memory_allocated校验 KV Cache 和激活估算
GPU 利用率采样工具判断是否像预期那样 Prefill 高、Decode 低

采样 GPU 利用率可以用命令行工具:

nvidia-smi --query-gpu=utilization.gpu,memory.used --format=csv -l 1

想要看每个 kernel 的耗时分布,可以在 CUDA 环境下使用 nsys 和 ncu。nsys profile看整体时间线,ncu --set full看每个 kernel 的算力、带宽和利用率。这些工具输出的实际瓶颈,往往能直接验证估算时假设的利用率是否有偏差。

5.2 一个最小测量脚本

在没有服务框架的情况下,先用 Transformers 跑一个最小测量脚本,把 Prefill 和 Decode 分开计时。

import time import torch from transformers import AutoModelForCausalLM, AutoTokenizer device = "cuda" model_name = "your-org/your-7b-model" # 替换成实际模型 tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.float16 ).to(device).eval() prompt = "The capital of France is " * 200 inputs = tokenizer(prompt, return_tensors="pt").to(device) # warmup,先触发 CUDA kernel 和显存分配 with torch.no_grad(): model(**inputs, use_cache=True) torch.cuda.synchronize() # Prefill 计时 start = time.perf_counter() with torch.no_grad(): out = model(**inputs, use_cache=True) torch.cuda.synchronize() prefill_s = time.perf_counter() - start # Decode 计时:逐 token 生成 input_ids = out.logits.argmax(dim=-1)[:, -1:] start = time.perf_counter() with torch.no_grad(): for _ in range(128): out = model(input_ids, use_cache=True) input_ids = out.logits.argmax(dim=-1)[:, -1:] torch.cuda.synchronize() decode_s = time.perf_counter() - start print(f"prefill: {prefill_s:.3f}s for {inputs['input_ids'].shape[1]} tokens") print(f"decode: {decode_s / 128 * 1000:.3f} ms/token")

脚本的关键是 warmup 和torch.cuda.synchronize()。没有 warmup,第一次 forward 会包含 CUDA context 初始化和显存分配,测出的时间偏大;没有 synchronize,测到的是 GPU 异步执行之前的时间,几乎总是偏小。

5.3 估算值和实测值对不上时从哪里找原因

实测结果与估算不一致很常见。校准的思路不是直接改公式,而是反推“哪个假设错了”。

估算明显快于实测,优先检查以下方向:

  • 计算利用率没有达到 50%,小 batch 时大矩阵乘法无法喂饱 GPU。
  • Decode 阶段不是单纯的权重搬运,还要读取 KV Cache,序列越长,KV 读取开销越大。
  • LayerNorm、RMSNorm、RoPE、注意力 softmax 这些非矩阵乘算子占用大量 launch 时间。
  • Python 和 PyTorch 调度开销在短序列上占比很高。
  • 显存带宽实际利用率低于 80%。

实测快于估算,可能原因:

  • 权重被量化成了更低精度,实际搬运字节数小于估算。
  • 使用了 GQA,KV Cache 显著缩小,Decode 阶段带宽压力降低。
  • batch 大于 1,多个请求共享权重复用,摊薄了权重搬运成本。

校准的最终结果,是把公式里的利用率参数修正成“这台机器、这个模型、这个 batch 下的实测值”。以后换模型规模、换 GPU 时,再拿同一套校准后的参数去估算,准确度会明显提升。

6. 五个常见的性能估算误区

6.1 误区对照表

下面是工程实践里最容易出现的五类估算错误。每一条都可以对照自己的估算过程检查一遍。

| 误区 | 典型现象 | 根因 | 正确处理 | | --- | --- | --- |

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 20:07:59

高温如何“吃掉”电力冗余?从电厂停运到数据中心运维的工程链条

高温天气让多个欧洲国家的电厂相继停运,电网进入高压状态。这类新闻看起来像宏观经济或气候议题,但对做基础设施的工程师来说,它其实是一个典型的“物理环境约束击穿系统余量”案例:发电能力下降、电网调节能力收缩、空调负荷暴增…

作者头像 李华
网站建设 2026/8/29 20:07:54

可逆不可学习样本:深度学习时代的版权保护新思路

数据版权问题在深度学习时代变得越来越尖锐。如果你负责过数据平台或者模型训练流水线,大概率会遇到这样一类矛盾:数据作者希望自己的图片、文本、音频不被未授权的大模型随意“吃掉”,而合法购买数据的算法团队又必须能够正常训练。两边都有…

作者头像 李华
网站建设 2026/8/29 20:06:39

GA优化BP神经网络的工程实践与避坑指南

简介:BP神经网络作为经典前馈网络,依赖梯度下降进行权重更新,但在小样本、高噪声或类别不平衡场景中易陷入局部极小、收敛不稳定。遗传算法(GA)作为一种无梯度的全局优化方法,可有效弥补BP在权重空间搜索能…

作者头像 李华
网站建设 2026/8/29 20:06:27

一张图能不能既学会画画,又学会修图,还认得出埃菲尔铁塔?

你有没有想过一个问题:AI画图模型学"怎么画一只猫"和学"怎么把这只猫的颜色改成白色",这两件事之间到底有没有关系?按照过去几年大部分团队的做法,答案是没关系。文生图(T2I,也就是"文字生成…

作者头像 李华
网站建设 2026/8/29 20:04:02

最大流算法详解:从Edmonds-Karp到最小割定理的实战指南

1. 项目概述:从水管网络到信息高速公路 想象一下,你所在的城市有一个庞大的自来水供水网络。水源地是几个大型水库,而千家万户则是用水终端。连接水库和用户之间的,是粗细不一、错综复杂的输水管道,每条管道在单位时间…

作者头像 李华
网站建设 2026/8/29 20:02:15

写给Java面试者:如何系统整理项目经验与知识盲区

“你连这个坑都没踩过,也好意思说做过秒杀系统?”面试官的这句话,像一根针扎在每个靠背题撑场的候选人心里。大多数Java面试者的困境不在于技术不够深,而在于项目经验像一盘散沙,知识盲区像一片黑洞。你明明参与了核心…

作者头像 李华