公司动态

LLM性能建模:从算力、带宽到延迟与吞吐量的估算方法

📅 2026/8/30 22:47:55
LLM性能建模:从算力、带宽到延迟与吞吐量的估算方法
1. 为什么性能建模比“跑个 benchmark”更重要很多人评估 LLM 部署方案时第一反应是把模型跑起来测一下延时和吞吐量看看快不快完事。这种方式不能说是错的但它有一个天然缺陷你只能回答“这台机器、这个框架、现在这批请求下跑得怎么样”却回答不了“如果换更长的序列怎么办”“如果并发翻倍会怎样”“如果换一个厂商的 GPU 卡会不会崩”这些问题。而这些问题恰恰是设计一个真实 LLM 应用时最需要提前回答的。所以现在越来越多做 LLM 工程化的人开始从“跑 benchmark”转向“做性能建模”Performance Modeling。它的思路是不先看机器而是先理解 LLM 推理过程中哪些因素在决定性能然后用一个数学模型把性能估出来。这样做的好处非常明显买卡之前就能估算出大概需要什么样的硬件。换框架、换部署方案时可以先做理论对比不用每次全量实测。定位性能瓶颈时能快速判断是算力不够、显存不够、还是框架调度开销太大。这篇文章就从第一性原理出发把 LLM 性能建模的底层逻辑拆开讲清楚。不堆公式但把公式为什么长这样讲明白。你读完以后至少能回答三个问题LLM 的算力需求由什么决定为什么批量大小和序列长度对性能影响这么大GPU 指标表上的 TFLOPS、HBM 带宽、显存容量到底应该怎么看2. 建模之前先理解 LLM 推理的本质要对 LLM 性能建模首先要理解 Transformer 语言模型在推理时到底在做什么。一句话概括模型在逐个 token 地预测下一个 token每个 token 都经过一次完整的前向传播。所有关于性能的讨论都建立在这句话之上。所以不管你用的是 GPT 系列、Qwen、Llama 还是 DeepSeek推理阶段的计算模式和瓶颈本质上遵循同一套逻辑。2.1 从“计算”的角度看一次前向传播要算多少东西LLM 模型的核心组成部分包括 Embedding、多个 Transformer 层、以及最后的输出层。每个 Transformer 层里又有 Attention 和 FFN前馈网络两部分。推理时每生成一个 token都要把这些层全部跑一遍。如果只看 FLOPs浮点运算次数一次前向传播的计算量大致由两部分构成Attention 部分与序列长度密切相关计算量约为4 * n_layers * hidden_size * sequence_length^2量级这里不展开精确推导重点看它跟序列长度的平方挂钩。FFN 部分与模型参数量密切相关计算量约为2 * n_layers * hidden_size_intermediate * sequence_length * 2量级也就是和参数量呈线性关系。这里有一个很多新手容易忽略的结论当序列很短时FFN 的计算占主导当序列越来越长Attention 的计算占比会快速上升因为它是序列长度的平方关系。这个特性直接影响部署策略。处理短文本任务和长文档任务瓶颈所在完全不同。2.2 从“访存”的角度看LLM 推理其实是“带宽游戏”计算是一回事但数据搬运是另一回事。GPU 工作时需要把参数从显存搬到计算单元。模型参数量越大每次 forward 需要搬运的数据量就越大。这里就出现了一个 LLM 推理特有的现象被称为Memory-Bound内存受限在小 batch 或单并发场景下GPU 的计算单元大量时间在“等数据”而不是“算数据”。打个比方假设你要从仓库里搬一万箱货到计算车间加工。如果一次只搬一箱那整个流程的瓶颈就是搬运时间而不是加工时间。LLM 的逐 token 生成过程如果把 batch size 设成 1就相当于一次搬一箱——GPU 的算力根本吃不满。所以理解 LLM 性能建模的第二个关键点必须同时考虑算力和访存带宽不能只看 FLOPS。2.3 一个容易混淆的点Prefill 和 DecodeLLM 推理还有一个重要的阶段划分很多入门资料忽略了它但它直接影响性能模型的结构。Prefill预填充阶段用户输入的一段 prompt 一次性进入模型模型并行处理所有输入 token。这个阶段特点是计算密集Compute-Bound因为可以充分利用 GPU 算力。Decode解码阶段模型逐 token 生成输出每生成一个 token都要把上一次生成的 token 拼接进来作为输入。这个阶段在低并发下往往是内存带宽受限Memory-Bound。这两个阶段性能特征不同建模的公式也不同。如果你的应用是问答型、少轮对话型Prefill 占比不大但如果是长文档分析、代码生成、长输出总结Decode 阶段就是大头带宽和显存的影响会非常明显。理解到这个层面性能建模的逻辑就不是“一个公式走天下”而是分阶段、分场景建立模型。3. LLM 性能建模中的四个核心参数无论性能模型写得复杂还是简单有几个核心参数是所有模型都绕不开的。这里逐个讲清楚它们是什么、怎么估算、对性能有什么影响。3.1 参数量N模型的参数总量通常以十亿Billion为单位。Llama 3 8B 就是大约 80 亿参数。参数量直接决定模型权重占用的显存大小存储维度。单次前向传播的计算量下限计算维度。单次推理需要搬运的数据量带宽维度。从参数量可以估算出模型至少需要多少显存。以 FP16半精度为例每 10 亿参数约占 2GB 显存。8B 模型权重就需要约 16GB。再加上 KV Cache、激活值、中间结果实际需求会更高。3.2 批大小Batch Size批大小是 LLM 推理性能建模里最关键的杠杆之一。算力角度批大小越大GPU 计算单元的利用率越高因为同样的模型权重可以被多个请求复用。显存角度批大小越大KV Cache 占用的显存越多。这里有一个平衡批量大会提升吞吐量但会推高显存压力降低单请求延迟。在实际部署里批量大小通常是框架自动或半自动控制的它会根据当前显存余量动态调整。理解了这一点你就明白为什么很多推理服务有max_batch_size和max_tokens这类配置它们本质上是在帮你划定性能模型的边界条件。3.3 序列长度Sequence Length序列长度对两个东西产生直接影响计算量尤其是 Attention 部分和 KV Cache 大小。Attention 计算量随序列长度平方增长。KV Cache 大小随序列长度线性增长。输出 token 越多Decode 阶段重复迭代的次数越多。所以序列长度是 LLM 应用实际落地时不得不认真对待的参数。固定 prompt 很短但单次输出很长的场景和固定输出很短但上下文很长的场景性能模型会给出完全不同的结论。3.4 GPU 的算力与显存带宽这个参数是模型设计阶段必须填入的硬件约束。不同 GPU 的规格差异极大影响到整个模型的输出结果。算力用 TFLOPS 表示代表每秒可以执行的浮点运算次数有 FP16、FP32、BF16 等不同精度下的指标。显存带宽用 GB/s 表示代表每秒可以从显存读取多少数据。实际建模时还需要注意一个细节厂商标称的峰值 TFLOPS 是理论上限实际利用率通常在 40% 到 70% 之间取决于算子实现、模型结构和框架优化程度。如果直接用理论峰值去算估算出的性能会偏乐观。建议引入一个“利用率系数”概念不同框架可以取不同范围一般保守估计取 0.5 左右。4. 最小可用数学模型延迟与吞吐量估算现在可以进入正题如何基于第一性原理建立一个最小可用的性能模型。这个模型不求完全精确但要能回答实际部署中的多数问题。4.1 计算一个 token 的最小算力需求要估算延迟和吞吐量先要估算一次前向传播需要多少 FLOPs。对于一个仅有解码器Decoder-only的模型不考虑词表投影等细节每个 token 的前向传播计算量可以近似为FLOPs_per_token ≈ 2 * N其中 N 是模型参数量。这个近似公式的含义是处理一个 token需要的浮点运算次数大约是参数量的 2 倍。你无需深入推导算子级别的细节只需要记住这个结论即可。8B 模型处理一个 token 大约需要 16 GFLOPs。4.2 估算 Prefill 阶段耗时Prefill 阶段处理输入序列的 $S_{prompt}$ 个 token总计算量约为FLOPs_prefill 2 * N * S_prompt假设 GPU 的 FP16 算力为 $C$单位 TFLOPS利用率为 $U$那么理论上 Prefill 耗时约为Time_prefill ≈ (2 * N * S_prompt) / (C * U * 10^12)N 和 $S_{prompt}$ 越大、算力越低Prefill 时间越长。对于长文档场景这个公式能帮你判断单个请求的“首 token 延迟”大概是多少。4.3 估算 Decode 阶段耗时Decode 阶段逐 token 生成共生成 $S_{gen}$ 个 token每个 token 都需要整次前向传播。但如果逐 token 算算力会发现结果偏乐观——因为实际 Decode 阶段往往被带宽限制。所以更实用的办法是使用带宽视角Time_decode ≈ (模型权重显存大小 KV Cache 单步增量) * S_gen / (带宽 * 利用率)这里模型权重显存大小在 FP16 下约为2 * NGB。例如 8B 模型约 16GB 权重。单步 KV Cache 增量取决于模型结构、层数、注意力头数、batch size 等可以在运行时通过torch.cuda.max_memory_allocated()或框架自带 profiling 工具观测先不展开。如果暂时不关心 KV Cache 的精确值也可以直接使用计算视角Time_decode ≈ (2 * N * S_gen) / (C * U * 10^12)但要注意这个公式只有在批量较大、计算密集时才相对准确在 batch size 1 时估算结果会明显偏快。4.4 吞吐量估算吞吐量Throughput通常定义为每秒处理的 token 数Throughput ≈ (S_prompt S_gen) / (Time_prefill Time_decode)如果服务是并发多请求吞吐量还要乘以并行度。但最小模型可以先基于单请求来推导再扩展。4.5 动手算一个例子假设在单张 H 系列 GPU 上部署一个 8B 模型设定如下参数模型参数量 N 8BFP16 算力 C 400 TFLOPS假设值实际以官方规格为准利用率 U 0.5Prompt 长度 $S_{prompt}$ 1024生成长度 $S_{gen}$ 512Prefill 计算量FLOPs_prefill 2 * 8e9 * 1024 ≈ 1.6e13Prefill 耗时Time_prefill ≈ 1.6e13 / (400e12 * 0.5) ≈ 0.08sDecode 耗时按计算视角FLOPs_decode 2 * 8e9 * 512 ≈ 8.2e12 Time_decode ≈ 8.2e12 / (400e12 * 0.5) ≈ 0.041s总耗时约 0.12s也就是说这个配置下单请求大概 120ms 完成 1536 个 token预期吞吐约 12800 token/s。这个数值看起来很乐观但实际部署时受限于带宽、显存访问、KV Cache 开销、框架调度等因素真实值通常会低一些。所以做性能建模时建议把利用率系数再往下调一调或者用实际测试数据去校正。5. 引入精度参数FP16 / BF16 / FP32 对性能建模的影响LLM 性能建模还有一个不能忽略的维度数据精度。热搜词里频繁出现 FP16、FP32、BF16 的讨论这不是偶然因为精度直接影响模型的显存占用、计算速度和输出稳定性。5.1 三种精度的核心差异精度类型占用位数指数位尾数位主要特点FP3232位8位23位精度高适合训练参数更新但显存和计算开销大FP1616位5位10位推理常用显存减半但小数值时精度不稳定BF1616位8位7位与 FP32 动态范围一致尾数精度低训练和推理都常用5.2 精度对性能模型的影响精度主要影响三个方面显存占用同一个 8B 模型FP32 权重约占 32GBFP16/BF16 约占 16GB。算力上限GPU 在 FP16/BF16 下的 TFLOPS 通常是 FP32 的数倍。这意味着使用低精度推理时理论上限更高。数值稳定性FP16 在遇到极小数值时可能出现溢出但推理阶段通常可以通过输出层使用更高精度来规避。所以在做性能建模时精度参数应该作为一个显式输入。如果你看到某张卡在 FP16 下的算力很高但你的应用因为稳定性原因必须用 FP32那算力指标就应该按 FP32 来算。这个判断直接决定了你的硬件选型方向。5.3 实际项目中的精度选择建议推理部署默认优先使用 FP16 或 BF16显存减半、速度更快绝大多数应用不受影响。需要高稳定性的场景如某些金融、科学计算类输出输出层可以保留 FP32。混合精度推理一些框架支持不同层用不同精度可以按层配置这需要更多 profiling 数据支撑。6. 性能建模在真实工程场景中的应用讲了这么多公式和概念接下来回答更实际的问题这些东西怎么用在日常开发里6.1 场景一硬件选型时提前估算公司要上线一个 AI 助手目标是用 7B 模型支撑 50 并发。你可以用上面的模型先估算每请求的 prompt 和输出长度大致多少单卡能不能放下权重和 KV Cache在目标并发下显存是否够用如果单卡压力太大是否需要考虑多卡切分或减少并发这套流程不依赖真实硬件在公司还没买卡的时候就能提供决策依据。6.2 场景二定位性能瓶颈时快速判断方向如果线上服务变慢了第一步不是盲目调参而是先用模型做个粗算是算力瓶颈看 GPU 利用率是否打满。是带宽瓶颈看 HBM 带宽利用率是否过高。是显存瓶颈看是否存在显存不足导致的换入换出。是框架调度瓶颈看单请求延迟和理论估算的偏差是否过大。这个思路可以帮助你快速缩小范围避免浪费时间在错误的优化方向上。6.3 场景三评估框架优化效果很多推理框架宣称“吞吐量提升 X 倍”。得到性能模型后你可以先做一次理论基线估算再用实际跑出来的数据对比。如果框架实测结果明显优于理论模型要检查是不是 batch size、精度或硬件配置不同导致的如果明显劣于理论模型说明框架调度或算子优化可能存在改进空间。性能模型的作用在此刻体现得最明显它是一个坐标系让每次优化都有参照物。7. 实操用 Python 和 nvml 验证性能模型纯理论容易让人心里没底。下面用一个最小 Python 示例把性能建模和真实 GPU 数据采集串起来。即使你没有真实部署经验也能理解整个验证链路如何闭环。环境要求可以运行 Python 的机器已安装pynvml库。如果没有 GPU也可以先写代码逻辑跑通以后再在目标机器上运行。安装 pynvmlpip install pynvml采集 GPU 基础指标import pynvml pynvml.nvmlInit() handle pynvml.nvmlDeviceGetHandleByIndex(0) # 获取设备基本信息 name pynvml.nvmlDeviceGetName(handle) print(fGPU 名称: {name}) # 获取显存信息 mem_info pynvml.nvmlDeviceGetMemoryInfo(handle) print(f显存总量: {mem_info.total / 1024**3:.2f} GB) print(f显存已用: {mem_info.used / 1024**3:.2f} GB) print(f显存剩余: {mem_info.free / 1024**3:.2f} GB) pynvml.nvmlShutdown()再用一个简单的计时工具记录推理耗时import time def time_llm_inference(prompt: str, model, tokenizer, max_new_tokens: int 128): inputs tokenizer(prompt, return_tensorspt).to(cuda) start time.time() with torch.no_grad(): outputs model.generate(**inputs, max_new_tokensmax_new_tokens) elapsed time.time() - start generated_tokens outputs.shape[1] - inputs[input_ids].shape[1] print(f生成 token 数: {generated_tokens}) print(f总耗时: {elapsed:.2f}s) print(f平均吞吐: {generated_tokens / elapsed:.2f} token/s) return elapsed把理论估算和实际耗时放在一起对比def compare_model_with_theory(elapsed, total_tokens, model_params_b, gpu_tflops, utilization0.5): # 理论耗时计算 flops_needed 2 * model_params_b * 1e9 * total_tokens theoretical_time flops_needed / (gpu_tflops * 1e12 * utilization) print(f实际吞吐: {total_tokens / elapsed:.2f} token/s) print(f理论吞吐: {total_tokens / theoretical_time:.2f} token/s) print(f实际/理论 效率: {total_tokens / elapsed / (total_tokens / theoretical_time) * 100:.2f}%)这段代码的作用不是替代完整 profiling 工具而是帮你建立一套“先理论估算再实测对比”的工作习惯。把每次实验的 GPU 型号、精度、batch size、序列长度、实际耗时记录下来慢慢就能形成属于自己项目的性能基线库。8. 常见问题与排查思路问题现象可能原因排查方式解决方案理论估算性能远高于实际框架调度开销大查看 GPU 利用率是否偏低调整 batch size、换更优推理框架单请求延迟很高低并发下带宽受限观察显存带宽利用率指标合并请求、增加并行度批量加大后吞吐不升反降显存不足导致换入换出查看显存使用趋势降低 batch size、检查 KV Cache 策略显存不够但权重只占少量KV Cache 占用过高统计 KV Cache 显存启用 PagedAttention 或限制 max_tokens无法读取性能面板数据注册表权限或驱动兼容问题检查驱动与工具版本以官方工具输出为准必要时用 torch 侧统计8.1 补充说明为什么“看着没问题”但性能数据不理想很多人在排查性能问题时只盯着 GPU 利用率这是一个容易踩的坑。GPU 利用率高不代表瓶颈是算力也可能是线程调度、数据搬运不够高效导致的“假忙”。排查时建议综合看四类指标算力利用率、显存带宽利用率、显存占用、端到端延迟。这样才能准确判断瓶颈到底发生在哪个环节。9. 最佳实践与工程建议9.1 从最小模型起步不要一上来就对 70B 模型做复杂建模。先用一个小模型如 1B 或 3B把整个建模和验证链路跑通熟悉模型对性能的影响规律再迁移到大模型上。这能帮你减少大量无效调试时间。9.2 建立性能基线库每次实验都记录以下几个字段模型名称 | 精度 | 参数量 | GPU型号 | batch size | prompt长度 | 输出长度 | 实际吞吐 | 理论吞吐 | 效率积累几十条实验数据后你会发现自己已经能很快地对某个新配置做出预判。9.3 留意推理框架版本差异同一个模型在不同框架中的性能可能差距很大甚至同一个框架的不同版本也可能有明显变化。在做性能建模时建议固定框架版本并在文档中标注。如果跨版本对比一定要把版本信息写清楚否则结论很容易失真。9.4 序列长度分布比平均值更重要实际业务中请求的 prompt 长度和输出长度往往呈长尾分布。如果只用平均值做性能估算可能会导致极端场景下超时或显存溢出。更稳妥的做法是统计 P50、P95、P99 分位长度用分位数据进行压力估算。9.5 生产环境监控要落到性能模型上上线后不要只盯接口平均延时可以把性能模型公式嵌入监控系统动态对比“当前负载下的理论预期性能”和“实际性能”。一旦偏差超出阈值就自动告警。这是一种对性能漂移非常敏感的生产监控方法。10. 关于 LLM 性能建模的一些额外想法最后补充几个容易被忽略但很实用的认知。10.1 性能建模是工程语言如果你只是自己“感觉”某个方案不错说服不了团队但如果你说“根据模型批大小 32 时预计吞吐从 200 提升到 350 token/s”决策就变得非常清晰。性能建模不一定追求精确到个位数它的价值在于提供一个可讨论、可验证的工程语言。10.2 建立“分层建模”思维第一层是硬件层GPU 算力、带宽、显存。 第二层是模型层参数量、层数、注意力头数、精度。 第三层是框架层算子实现、batch 策略、PagedAttention 等优化。 第四层是业务层prompt 分布、输出长度、并发模型、SLA 约束。一上来只做第四层的估算往往会忽略底层约束只做第一层又难以回答业务问题。好的性能建模是从底层到上层逐层收紧的。10.3 模型不是越大越好是“够用且可部署”才好前几年大家拼的是模型效果现在越来越多团队意识到一个能稳定部署、延迟可控、成本可承担的模型才是生产环境真正需要的模型。性能建模恰恰是帮你在效果与部署成本之间做出理性权衡的工具。例如一个 7B 模型如果通过量化、裁剪和推理优化达到接近 13B 模型的效果但延迟只有后者的三分之一那么它在实际业务中可能更有竞争力。11. 总结与下一步实践建议这篇文章的核心是把 LLM 性能建模从“玄学 Benchmark”变成“可计算的工程问题”。重点梳理了四个底层参数参数量、批大小、序列长度、GPU 算力与带宽并基于它们搭建了一个最小可用的延迟与吞吐量估算模型。同时介绍了 FP16/BF16/FP32 精度选择对性能建模的影响以及如何用真实 GPU 数据和 Python 脚本验证模型准确性。读到这里建议你按这样的顺序做一次实践选择你当前正在用的模型记录参数量和部署精度。查阅目标 GPU 的 FP16 算力和显存带宽规格。用文章里的公式估算不同 batch size、不同序列长度下的吞吐量。在真实环境中跑一组小实验记录实际数据。比较实际值和估算值的偏差记录误差范围。把这一步做完你对于“这个模型应该配什么卡、能支撑多少并发、瓶颈在哪”的理解会比看十篇 benchmark 报告都要深入。如果后续想继续深入可以研究 KV Cache 的显存精确计算、PagedAttention 原理、连续批处理Continuous Batching对吞吐量的影响以及多卡并行TP/PP/DP场景下的性能建模方法。这些内容建立在本文的第一性原理之上方向不会偏。