1. 大模型性能测试到底在测什么标题写了三万字长文但说实话字数从来不是重点。我这里真正想做的是把大模型性能测试这件事从理念到实操完整地捋一遍让看完的人哪怕之前完全没接触过这方面也能直接上手搭一套可用的压测方案。先交代一下背景。我过去半年一直在做某智能客服项目的大模型服务性能保障从模型选型到上线压测再到线上容量规划全程都在跟“性能”两个字打交道。最开始的阶段我犯过一个几乎所有测试同行都会犯的错误拿传统Web接口压测的思路直接往大模型服务上套。用JMeter开几百个线程对着接口一顿猛打看响应时间、看吞吐量然后发现数据完全看不懂——P99忽高忽低GPU时而跑满时而空闲吞吐量的计算口径和业务对不上。后来才意识到生成式模型的服务模式和传统接口在本质上就是两码事测试方法论也得重新搭建。大模型性能测试说白了就是在不同负载条件下量化模型服务“生不生成得出来、生成得快不快、生成了多少、资源撑不撑得住”这四件事。我见过很多团队把大模型压测等同于“并发多少、QPS多少”这其实是本末倒置。对于基于Token生成的大模型服务最核心的性能实体是Token而不是请求。一个请求可能生成一句话也可能生成两千字用传统RPS思路去衡量相当于只看车流量却不看每辆车装了多少货数据必然失真。还有一点我必须开篇就讲清楚大模型性能测试不是一次性交付物它是持续伴随模型迭代、推理引擎升级、量化方案调整的常态化工作。我所在的团队目前维持着一套每周自动跑的压测流水线每次模型版本更新、每次引擎参数调整都会触发一轮基准测试和数据对比。这不是“测一遍就完事”的工程而是一条需要长期维护的数据基线。这篇文章会从指标定义、工具选型、流程设计、问题排查四个维度展开每一部分都是我在实际项目中验证过的做法可以直接照着抄。2. 核心指标拆解别被一堆术语绕晕很多刚接触大模型压测的人一上来就被TTFT、TPOT、E2E Latency、QPS、TPS、并发数、显存占用这些术语砸晕了。其实这些指标并不复杂只是它们相互之间的关系需要理顺。我这里用一个生活中特别直观的场景来类比你去餐厅点菜。2.1 延迟类指标TTFT、TPOT、端到端延迟你坐下点完菜到第一道菜端上桌的时间就是TTFT。在大模型推理里TTFT指的是从发送请求到收到第一个Token的耗时。为什么单拎这一个指标出来因为大模型服务在生成第一个Token之前需要先处理你输入的整段Prompt这个过程叫Prefill阶段是典型的计算密集型任务。Prompt越长TTFT越长。在实际产品里如果TTFT超过3到5秒用户的体感就会明显变差总觉得“AI在转圈圈”。第一道菜之后每上一道菜的间隔时间就是TPOT。TPOT全称Time Per Output Token指生成每个输出Token的平均耗时。这个阶段叫Decode阶段大模型逐Token生成内容每一步都要依赖前一步的结果无法并行。TPOT直接决定了用户看到文字流式输出的速度感。举个例子如果TPOT是50毫秒那么每秒输出约20个Token大概每秒十几个汉字属于比较流畅的体验如果TPOT飙到200毫秒每秒只有5个Token用户就会觉得输出“一个字一个字往外蹦”。端到端延迟就好理解了从点菜到吃完结账的全部时间。在大模型场景里就是从发起请求到收到完整响应的总耗时。它约等于TTFT加上TPOT乘以输出Token数量。这三者之间有个很关键的关系同样的端到端延迟可能是由完全不同的TTFT和TPOT组合出来的。一个2秒的端到端时延可以是TTFT 1.5秒加TPOT 25毫秒生成20个Token也可以是TTFT 300毫秒加TPOT 85毫秒生成20个Token。前者是“半天不说话一说话就刹不住”后者是“马上开口但语速偏慢”。产品体验上会有明显差异所以测试报告里必须把三个指标分开列合并成一个平均值是自欺欺人。2.2 吞吐类指标QPS、TPS、并发数吞吐类指标同样不能只看一个维度。QPS是每秒完成的请求数TPS则是每秒生成的Token数。这两者的关系可以类比成物流行业的“车次”和“货运量”。一辆车跑一趟不管装1个包裹还是1000个包裹都算一次QPS但货运量TPS差别巨大。在模型评测中就是为了避免“车次多但货量少”的假象才必须把Token吞吐单独统计。我举个实际例子某次压测中我们对比了两套配置配置A的QPS是12TPS是2100配置B的QPS只有8TPS却高达3200。你光看QPS会得出配置A更好的结论。但如果在真实业务里一个用户的单次请求平均要生成300个Token那么配置A每秒只能服务7个这样的用户配置B能服务10个。TPS才是真正决定业务容量的指标。还有一个不得不提的概念是并发数。在GPU推理场景里并发数的含义比传统Web架构复杂得多。大模型推理引擎通常采用连续批处理技术也就是在Decode阶段不同请求的Token生成过程会被动态拼到同一个Batch里执行GPU矩阵运算。并发用户一多引擎会自动把请求凑成更大的BatchGPU利用率随之提升但单个请求的TPOT也会因为排队和Batch变大而上升。所以并发数的测试不能孤立进行要配合延迟指标一起看找到“吞吐上升、延迟还能接受”的临界点。这个临界点就是我们常说的饱和度拐点。2.3 资源类指标显存、GPU利用率、功耗除了服务端指标资源指标是压测必须同步采集的。大模型推理和传统接口测试最大的不同在于GPU是核心资源而显存是最大瓶颈。先说显存。模型权重、KV Cache、临时计算缓冲都会占用显存。其中KV Cache是在推理过程中为每个请求缓存的注意力键值对长短Prompt消耗的KV Cache差异极大而且是动态增长的。我们在压测时必须监控显存占用曲线看它是否接近甚至触顶。显存一旦打满引擎就会报OOM甚至直接把请求Abort掉。再说GPU利用率。这里有个容易误读的细节——利用率的数字好看不代表性能好。Prefill阶段计算密集GPU利用率能冲到90%以上但Decode阶段受限于显存带宽利用率往往只有40%到60%。所以观察GPU利用率时必须让压测持续时间足够长并且和请求阶段数据对应起来看否则容易被CPU侧和GPU侧的时间错位迷惑。我在实践中习惯让压测稳定跑5到10分钟以上再用平均值配合时间序列曲线一起分析。功耗和温度同样值得记录。满负载压测下GPU温度过高会触发降频功耗上不去了性能反而掉下来。某些机型在持续高负载下跑20分钟性能会下降10%到15%这都是热降频惹的祸。所以压测报告的“可信区间”应该标注出温度数据不然你在早上冷机状态测出来的性能数字到了下午满载机房可能完全复现不出来。下表是我在做压测报告时固定记录的核心指标清单指标类别指标名称含义关键观察点延迟TTFT首Token延迟P50、P95分位数注意长Prompt场景延迟TPOT每Token生成耗时直接决定流式输出体验延迟E2E Latency端到端总耗时TTFT TPOT × Token数吞吐QPS每秒完成请求数参考指标不能单独决策吞吐TPS每秒生成Token数业务容量评估的核心依据资源GPU显存显存峰值与均值是否接近容量上限资源GPU利用率计算单元利用程度分Prefill/Decode阶段看资源功耗/温度功率与核心温度排查降频问题3. 测试工具与环境准备指标定义清楚了下一步就是解决“拿什么测”的问题。工具选型上我踩过不少坑目前打磨出了一套比较稳定的组合方案下面分三块说推理引擎、压测工具、环境参数。3.1 推理引擎的选择大模型性能测试必须先决定用什么推理引擎来承载模型。同一个模型在不同引擎上的性能差距可以达到一倍以上这不是小事。目前主流的引擎有vLLM、SGLang、TensorRT-LLM、TGI等各自有各自的优势场景。我个人的建议是如果项目没有特殊约束优先用vLLM。理由很实在——生态最成熟、文档最全、社区最活跃支持OpenAI兼容的API格式压测工具可以直接对接HTTP接口不用走复杂的SDK。SGLang在部分高吞吐场景下表现更优但配置和调试门槛更高TensorRT-LLM在单卡极致性能和贪心解码场景有优势但要用TensorRT的图优化流程部署复杂度明显更高TGI则是HuggingFace系的方案如果你本来就在HF生态里转选它也不差。引擎选定后量化方案也要在压测前定好。实践中我用得最多的是FP16/BF16基准、INT8和INT4量化三种配置。量化能显著降低显存占用、提升并发能力但会引入精度损失。性能测试的作用之一就是量化前后对比延迟和吞吐协助业务判断“量化的收益值不值得精度的代价”。我们曾经在某项目里对比过一个模型INT4量化后TPS提升了近一倍但P99延迟劣化了20%最后综合业务方意见还是选了INT8的中间路线。3.2 压测工具选型从自带脚本到自研压测客户端压测工具这块我把它分成三个档次来选。第一档快速验证。直接用vLLM自带的benchmark_serving.py脚本它可以模拟多用户并发请求统计TTFT、TPOT、E2E延迟和吞吐还内置了ShareGPT等公开数据集来模拟真实Prompt分布。改几个参数就能跑适合配置变更后的快速基准回归。缺点是统计维度偏固定定制化不足数据导出也比较受限。第二档常规负载测试。用Locust或者k6。这两个都支持HTTP压测Locust用Python写脚本k6用JavaScript。大家如果熟悉传统压测工具JMeter也能用但在大模型场景里JMeter的线程模型和并发控制方式反而不够灵活特别是模拟流式响应和动态Token速率时非常别扭。我个人更倾向Locust因为可以直接在脚本里解析流式响应计算Token到达时间实现自定义指标上报。第三档生产级精细化压测。到了这个阶段自研压测客户端基本是必须的。我们需要精确控制Prompt长度分布、输出Token长度、请求到达模式、超时策略、流式与非流式混合比例还要把指标直接写入时序数据库用于后续分析。这些需求用通用压测工具拼装也能实现但代码会非常别扭不如自己写一套。我自己维护的压测客户端大概500行Python基于aiohttp实现后续章节会展示核心代码。3.3 环境搭建与关键参数环境这块有一个大坑一定要避开压测机和GPU服务器必须在同一内网并预留足够的网络带宽。我们最开始在某云上测试压测客户端和GPU实例不在同一可用区结果P99延迟里有两三百毫秒是网络开销白测了一轮。后来改成同VPC的独立压测机网络延迟降到微秒级数据才真正反映出模型服务本身的性能。服务端启动参数直接决定性能天花板这里重点说三个参数。第一个是--max-model-len控制模型支持的最大上下文长度。这个值设置得越大KV Cache的预留空间越大能同时处理的请求数就越少。如果业务实际用不到4K上下文就别傻乎乎配32K白白牺牲并发能力。第二个是--max-num-seqs控制单批次最多同时处理的序列数。这个值就像一个餐厅的最大餐位设置太小吞吐上不去设置太大单个请求的延迟会恶化。一般从默认值开始通过压测逐步调优。第三个是--gpu-memory-utilization控制推理引擎最多使用多少比例的GPU显存。默认是0.9但如果你还要用同一张卡跑评测程序或者加载其他模型就要把这个值调低。这个参数直接决定了KV Cache的上限也就决定了能支撑的最大并发数。再强调一个细节大模型服务建议放在Docker容器里运行启动容器时加--ipchost参数。很多引擎的多进程通信依赖共享内存默认容器IPC隔离会导致显存无法共享压测时莫名OOM。这个坑我们踩过一次排查了半天最后发现只是少了个启动参数。4. 完整压测流程实操工具和环境就绪后进入正题一套完整的压测到底怎么跑。这里我把流程拆解成四步分别是测试方案设计、压测脚本编写、测试执行、数据采集。每一步我在实际项目中都趟出过经验教训。4.1 测试方案设计先想清楚再动手不做方案设计直接开压测是很多团队的通病。拿到的数据要么不知道怎么解释要么覆盖不到核心场景最终只能返工。大模型压测方案里至少要明确五件事。第一Prompt长度分布。真实业务里用户输入长短差异极大。客服系统的用户提问平均50字左右代码生成场景的Prompt可能上万Token。压测时不能只用一个固定长度最好按业务统计的真实分布来构造数据集。没有真实数据的情况下可以用ShareGPT数据集抽样后调整长度来模拟。第二输出长度控制。这是最容易被忽略的点。大模型生成的Token数是动态的如果不对输出长度做约束每次请求生成的Token数可能从50到500乱跳测试结果方差极大。常见的做法是设置max_tokens和min_tokens让模型生成固定长度的输出。我们把输出长度控制在一个范围内比如固定200 Token测出来的TPOT才具有可比性。第三并发模型。需要模拟真实的用户到达过程是均匀到达、突发到达还是阶梯式增长。容量评估推荐用阶梯式增长从并发1开始逐步增加到2、4、8、16、32每个档位稳定跑3分钟观察延迟和吞吐的拐点。这样能画出完整的性能曲线而不是只测一个点。第四压测时长。大模型压测对时长有硬性要求。预热阶段至少1到2分钟让推理引擎充分填充KV Cache的预分配空间达到稳态。正式采集阶段每个场景至少5分钟。时长太短数据都在波动期没有参考价值。第五请求类型。不同业务的请求类型差异很大。对话场景用/v1/chat/completions接口文档处理场景可能同时有Embedding请求。不同类型的请求对性能的影响不同比如Embedding请求没有Decode阶段纯Prefill和对话请求混跑时资源竞争模式又不一样。方案里必须明确主测类型和混合比例。4.2 压测脚本编写一个可复用的核心模型自研压测脚本时核心实现就是一个可以控制并发、收集指标、计算统计值的异步客户端。我贴一个曾经在项目中验证过的最小可用版本核心片段。import asyncio import time import json import statistics import aiohttp class LLMPerfTester: def __init__(self, base_url, api_keyNone): self.base_url base_url self.headers { Content-Type: application/json, Authorization: fBearer {api_key} if api_key else } self.metrics [] async def single_request(self, session, prompt, max_tokens): payload { model: test-model, messages: [{role: user, content: prompt}], max_tokens: max_tokens, min_tokens: max_tokens, temperature: 0.0, stream: False } start time.perf_counter() t_first None output_tokens 0 async with session.post( self.base_url /v1/chat/completions, jsonpayload, headersself.headers ) as resp: # 如果开启流式这里要逐行读取SSE数据计算首Token时间 data await resp.json() t_first time.perf_counter() - start if usage in data: output_tokens data[usage][completion_tokens] end time.perf_counter() - start self.metrics.append({ e2e: end, ttft: t_first, output_tokens: output_tokens, tpot: (end - t_first) / max(output_tokens - 1, 1) }) async def run_load(self, prompts, max_tokens, concurrency, duration): async with aiohttp.ClientSession() as session: tasks [] start_time time.time() idx 0 while time.time() - start_time duration: while len(tasks) concurrency: prompt prompts[idx % len(prompts)] tasks.append(asyncio.create_task( self.single_request(session, prompt, max_tokens) )) idx 1 done, tasks await asyncio.wait(tasks, return_whenasyncio.FIRST_COMPLETED) for task in done: if task.exception(): print(f请求失败: {task.exception()}) task.result()这段代码最核心的设计在于使用了asyncio.wait的FIRST_COMPLETED模式每完成一个请求就立刻补充一个新请求保证系统始终保持目标并发数。注释处想提醒一个细节开启stream为True时响应体是SSE格式需要边读边记录首次收到的数据块时间来得到准确的TTFT。非流式模式虽然也能从JSON里拿到响应时间但对于首Token延迟的判断是拿不到的。4.3 测试执行与数据采集脚本就绪后执行阶段有几个实操惯性必须养成。压测开始前先跑一次单请求连通性测试确认接口可用、Token速率正常。然后检查nvidia-smi的初始状态记录GPU温度初值。接着做预热用低并发跑2分钟目的是让引擎完成显存预分配让模型权重充分加载到显存中。正式测试时按方案中的并发阶梯逐档执行。每一档完成之后不要立刻开始下一档中间留30秒左右的数据落盘和状态恢复时间。这里有个容易被忽略的点高并发档位结束后KV Cache和连接池可能还有残留直接跑下一档会影响下一档的前几十秒数据导致数据污染。资源数据采集方面我推荐双通道方案。简单快速的需求用nvidia-smi dmon -c 300这个命令直接采样GPU指标。正式项目建议上Prometheus加DCGM Exporter把GPU利用率、显存、温度、功耗等指标统一采集到时序数据库中压测期间同时打点测试结束后按时间对齐服务端指标和资源指标。还有一个操作纪律每一轮压测生成的原始数据、参数配置、引擎版本、模型版本、量化方式、服务端参数全部记录到测试报告里格式固定、字段完整。我见过太多团队调了个参数跑出数据后过两周就忘了当时用的哪个配置导致对比完全失去意义。版本管理的严谨程度直接影响性能测试数据的可信度。5. 结果分析与性能调优数据采回来只是第一步真正的功夫在于怎么解读。同样是两组数据有人能一眼看出瓶颈在KV Cache还是模型计算有人只能干瞪眼。这里分享我自己的分析方法和调优路径。5.1 数据可视化与瓶颈定位我的分析习惯是先把数据画成两张图并发-延迟曲线和并发-吞吐曲线然后叠加GPU资源利用率。很多问题一眼就能看出来。如果QPS和TPS随并发上升但增速放缓同时P99延迟曲线出现明显翘尾说明系统正在接近饱和拐点。这个拐点对应的并发数就是当前配置下的“最大可用承载”。反过来如果并发上升了但TPS纹丝不动那问题出在别的环节比如服务端单请求处理逻辑存在串行瓶颈、网络带宽不够或者引擎里的连续批处理没生效。有次我们压测某模型服务并发从16加到32TPS却几乎没涨P99倒是翻了一倍。排查下来发现Y模型服务的输出去掉了SSE流式所有请求都攒到完整生成完毕才返回响应长请求占用了服务线程池后面的请求一直在排队。改成流式输出后同样是32并发TPS直接提升了60%。这就是图里看不出来的业务层问题通过延迟分布曲线能定位到线程排队迹象进而顺着排查服务端代码才找到根因。GPU资源数据同样会说话。如果显存占用率在80%以下但GPU利用率已经接近100%说明瓶颈在计算端优先考虑量化、换更大算力卡、或者优化Prefill阶段的计算效率。如果GPU利用率只有30%但显存快满了那瓶颈在显存容量KV Cache已经在频繁驱逐旧请求缓存这时候优先考虑缩短上下文、优化批次大小、或者扩大显存。判定依据非常简单资源利用率顶到哪一个瓶颈就在哪一个。5.2 关键对比实验方法与调优方向定位到瓶颈后需要通过控制变量的方式验证调优效果。控制变量原则在性能调优里必须严格执行一次只能改一个变量否则结果无法归因。最常见的三个调优方向第一个是引擎参数调优。调节max-num-seqs、gpu-memory-utilization、调度策略。比如max-num-seqs从默认的256改到128延迟可能明显下降而吞吐损失很小gpu-memory-utilization从0.9降到0.8可能会导致KV Cache空间不足并发稍高就出现请求排队。这些参数没有一个万能最优解只能通过压测对比找出业务场景下的最佳折中。第二个是模型输入侧优化比如Prompt压缩和系统提示词精简。我们在某项目中发现把一段1200字的系统提示词压缩到400字TTFT平均下降35%对QPS几乎无副作用。大模型服务和传统服务一样请求越小处理越快区别在于大模型的输入处理成本不是线性的Prompt压缩的收益往往被严重低估。第三个是推理侧优化比如FlashAttention、PagedAttention等注意力优化技术是否启用。在支持这些特性的推理引擎里它们对长上下文场景的提升非常显著。做对比实验时直接用同一份数据集、同一并发档位分别跑开和关两个版本十分钟内就能得到一组明确的收益数据。我在实际项目中总结过一个经验调优的优先级永远是“能改配置解决的问题先不碰代码能改模型侧问题的先不碰推理框架”。配置参数调整成本最低、风险最小、最可控模型侧的Prompt压缩和量化则要更谨慎因为它们直接影响业务效果。至于改推理引擎代码这种深水区除非项目组里有专门做推理优化的同学否则不建议普通测试团队去尝试。6. 常见问题与排查实录这一章是整篇文章里我自己觉得最“值钱”的部分。大模型压测中高频出现的问题很多排查过程和结论都不在官方文档里属于“不踩一次根本想不到”的类型。整理成速查表加实录的形式方便大家按图索骥。6.1 高频问题速查表问题现象可能原因排查思路与解法并发一上来就报OOMgpu-memory-utilization过高或max-model-len过大调低显存利用率缩短上下文上限查看KV Cache占用请求被Abort无错误响应KV Cache空间不足引擎驱逐请求缓存降低并发缩短Prompt减少max-num-seqsTPOT随运行时间不断增加GPU温度升高触发降频监控温度曲线压测环境降温缩短满载时长压测数据方差极大输出长度未控制或预热不充分固定min_tokens/max_tokens延长预热时间显存没用满但性能上不去引擎连续批处理失效或请求串行处理检查引擎日志的批处理统计开启流式模式Docker部署环境下莫名OOM缺少--ipchost参数启动容器时加上共享内存参数首Token延迟低于预期但E2E高Prompt过长导致Prefill耗时优化Prompt长度或测试固定短Prompt单独对比P99极不稳定出现长尾排队请求优先级问题或网络抖动检查服务端排队队列长度确认压测机和服务器同一网络6.2 三个真实排查过程实录挑三个印象最深的排查过程分享一下它们对应的都是特别容易误判的场景。第一个是“输出Token数量波动导致TPS假象”。某次压测中我们发现TPS从2800跳到了5000看起来性能提升明显但检查原始数据后发现问题后一轮测试的平均输出Token数从200涨到了350。TPS计算的分母是时间分子是Token数Token数变多了TPS自然涨但单位时间的请求完成数QPS反而下降了。这是典型的口径陷阱。从此我们要求所有TPS对比必须同时记录平均输出长度保证数据具备可比性。第二个是“并发上去后P99恶化但P50纹丝不动”。延迟分布出现典型的双峰形态大部分请求很快少量请求慢得离谱。这个现象容易让人误判为网络问题实际上我们用tcpdump抓包后确认耗时全部落在服务端处理上。进一步查引擎指标发现是某个请求的Prompt特别长进入Prefill阶段后占用了大量GPU计算资源导致后面的小请求全部排队。这提醒我们大模型压测的数据集不能只关注平均长度Prompt长度的长尾分布直接影响系统稳定性的测试结论。测试数据集里必须有意识地包含5%左右的长Prompt样本。第三个是“床尾效应”——高并发测试结束后立刻跑下一档数据全废。原因在于上一档结束时引擎缓存了上一批请求的KV Cache显存还处于高占用状态。我一档测试结束后没有等待缓存自然淘汰立刻开了下一档低并发导致低并发档位的前半段一直在做缓存淘汰延迟数据全部偏高。此后我养成了两档测试之间至少等待1分钟的硬习惯宁可测试总时长多几分钟也不让数据带上上一档的污染。6.3 给初学者的三条建议如果看完这篇文章你还只是一个刚准备开始做大模型压测的测试工程师我特别想说三点。第一不要一上来就追求复杂工具。先用vLLM自带脚本把你手上的模型跑出一组基线数据理解TTFT、TPOT和TPS之间的关系。把这组数据读懂比盲目上多复杂的压测平台都重要。第二一定要从业务场景出发设计指标。你去测一个业务模型前先和产品聊清楚用户能接受的等待上限是多少流式输出要达到多流畅高峰并发大概多少这些业务输入直接决定了你的测试通过标准和指标权重。脱离了业务约束的性能测试测出来的数字只是自娱自乐。第三建立自己的基线数据仓库。把你的每一次压测结果、配置信息、模型版本固化下来时间长了就是团队最宝贵的资产。性能回归、容量规划、资源预算全部依赖这份历史数据。我们团队现在做任何模型的版本升级都先跑两周前的同配置基线做对比几分钟就知道新版是优化还是回退。我在实际项目中最大的体会就是大模型性能测试真正难的不是技术而是把技术人员从“对着指标发呆”的被动状态拉回到“用数据指导决策”的主动状态。每一项指标背后都对应着一个业务决策每一个数据异常背后都藏着一个代码或参数问题。把这些关系理清楚测试就不再是交付物而是整个AI产品迭代链条里不可或缺的仪表盘。这篇文章里的方法、代码和教训都是我一步步踩出来的希望对正在做或者准备做大模型性能测试的人有所帮助。