资讯动态

AI系统性能测试指南:从GPU显存到KV Cache的压测实战

发布时间:2026/9/8 19:17:58 来源:尧图企业网站定制
把一套AI系统交到你手里说今天要做性能测试。很多人下意识打开JMeter录个脚本发几百个请求看着吞吐量跑起来就完事——这套路测传统Web服务好使但放在AI系统上大概率会翻车。为什么因为AI系统的性能测试核心瓶颈根本不在网络和普通CPU逻辑上而在模型推理、GPU显存、KV Cache这些以前没怎么接触过的地方。你拿测订单接口的思路去测一个LLM服务测出来的数字要么虚高得离谱要么根本复现不出来线上用户一多立刻原形毕露。这篇文章我想把我自己做AI系统性能测试的完整思路、工具选型和踩坑记录整理出来。用我实际跑过的压测流程讲清楚AI系统该测哪些指标、怎么设计场景、怎么定位瓶颈。适合正在做AI平台或应用性能测试的测试工程师、后端开发、SRE也适合准备性能测试岗位面试、想搞明白AI压测和传统压测到底差在哪的同学。1. 先搞清楚AI系统的性能测试测的和以前不一样1.1 性能和资源模型完全不同先回想一下传统Web服务的压测请求进来经过网关、应用层、数据库大部分耗时在IO和业务逻辑上CPU和内存是主要关注资源。你可以用并发数模拟用户用QPS评估吞吐用响应时间评估体验这套模型很成熟。AI系统完全不是这么回事。以LLM服务为例一次请求进来后不是简单返回一个结果而是要做自回归生成——也就是一个Token一个Token地往外蹦。整个推理过程分为两个阶段预填充阶段处理你输入的整段文本解码阶段逐Token生成输出。这两个阶段的耗时特征完全不一样预填充阶段吃算力和显存带宽解码阶段才是真正的“逐字输出”。这就带来一个很实际的问题同样是一个“响应时间”AI系统必须拆成“首Token时间”和“剩余Token生成时间”来看。我见过不少测试同学只盯着接口总耗时结果发现同样的总耗时可能一个请求90%的时间都在等首Token另一个请求是首Token很快但后面生成很慢——这两个问题背后的瓶颈完全不是一个地方。另外AI系统的性能天花板往往不在CPU而在GPU。显存更是硬约束模型权重占一份推理过程中的激活值占一份KV Cache又占一份。并发一高显存先爆你还没看到CPU和网络的瓶颈服务已经OOM了。这是很多做传统压测的人上来就懵的地方。1.2 AI系统特有的性能指标我在实际项目中给AI系统做性能测试时重点看这几个指标大家可以对着这份清单建立自己的指标体系指标名称单位含义为什么重要RPS / QPS请求/秒系统每秒能处理的请求数容量规划和弹性伸缩的核心依据首Token延迟TTFT毫秒从发出请求到收到第一个Token的时间直接影响用户感知的“流畅度”也是排查排队问题的第一抓手每Token生成耗时TPOT毫秒/Token生成一个Token的平均耗时决定输出速度和整体吞吐受模型大小、量化方式、并发影响明显端到端延迟P50/P95/P99毫秒从发请求到完整响应的时间传统指标但在AI系统里必须结合输入输出长度一起看并发数个同时处理的请求数量并不是越高越好超过GPU实际并行能力后延迟会急剧恶化GPU利用率%GPU计算核心的使用率判断算力是否打满压测时这个数字上不去说明瓶颈不在GPU显存占用GB模型、KV Cache、激活值的总和决定能支撑多大并发也是最常见的崩溃原因Batch Size个每个推理批次处理的请求数动态批处理是AI服务吞吐的核心手段但会带来额外延迟如果你测的是RAG链路或者多模态服务可能还要加上向量检索耗时、文档解析耗时这些。但上面这份清单是AI系统压测的地基任何时候都绕不开。1.3 回归到问题本身为什么要专门做AI性能测试说到底做性能测试不是为了出一个漂亮的报告而是回答三个问题这系统能扛多大流量、大流量时体验会差到什么程度、瓶颈在哪、值不值得花钱扩资源。举个例子。我在一次版本回归测试里发现同样的并发下新版模型的TTFT比旧版涨了40%。原因不是服务代码变了而是新模型上下文长度翻倍预填充阶段的计算量暴涨。如果不做专项性能测试这个隐患上了生产才会暴露到时候排查成本高得离谱。AI系统还有一个特点模型一变、推理框架一变、GPU型号一变性能特征就全变了。同一个模型在A100和4090上是两种表现同一块卡上vLLM和原生PyTorch Serving的性能差距可能是数量级的。所以AI系统的性能测试不是一次性的工作而是每次模型升级、框架升级、硬件变更都得重新跑一遍的必要流程。这也是为什么现在性能测试岗位的面试题里AI系统的测试设计越来越多。面试官真正想看的是你有没有建立这套“AI性能测试≠传统压测”的认知。2. 测试前必须定清楚环境、模型配置和数据2.1 构建可复现的测试环境做AI系统压测第一步不是装JMeter而是把测试环境固定下来。这里的“固定”包含三层意思。第一层是硬件。你得明确用哪块GPU、多少张卡、显存多大是单机多卡还是分布式推理。不同硬件下性能差好几倍所以压测报告里第一行就得写清楚硬件配置。第二层是推理框架和版本。同一个模型跑在vLLM、TensorRT-LLM、SGLang、原生HuggingFace上结果天差地别。我自己的经验是做线上容量评估压测环境必须和生产环境用同一个推理框架、同一个版本、同一套启动参数否则压测结果没有参考价值。第三层是显存预算评估。我习惯在压测前先估算一下当前配置能支撑多少个并发请求。大致的显存占用模型是总显存 模型权重显存 KV Cache显存 激活值显存。模型权重藏一眼模型文件大小就行KV Cache的大头粗略算一个Token大概需要2×层数×隐层维度×2字节FP16的显存然后乘以总Token数输入输出和并发请求数。很粗略但能帮你判断会不会一压就爆。注意并发数不是你想压多少就压多少显存才是第一约束。压测前先算一下最大理论并发否则第一轮压测就会OOM。2.2 影响测试结果的关键参数必须先固定AI系统的性能测试结果极其依赖参数配置同一个服务参数不同结果能差出一倍以上。我在每次压测前都会把这些参数固定下来写进测试方案里模型版本和精度FP16、INT8、INT4量化。量化后推理速度可能提升很大但精度和效果会打折扣。请求参数中的max_tokens。这是个大坑如果把max_tokens设得很大即使实际请求输出很短推理框架也可能预留大量KV Cache导致并发能力下降。温度、top_p等采样参数。对性能影响不大但会影响输出长度分布导致延迟数据失真所以也要固定。流式stream还是非流式。流式响应下首Token时间会明显降低但总耗时会变长两种模式不能混在一起统计。上下文长度限制max_model_len。这个决定KV Cache的上限直接决定显存占用和并发上限。我见过最典型的问题压测人员做对比测试时第一轮max_tokens设128第二轮设4096然后对着两个报告分析“为什么性能下降这么多”——其实根本不是性能问题是参数压根没对齐。所以压测报告里必须把模型参数表贴上去每条请求用了什么参数也要能追溯。2.3 测试数据集怎么构造才不会虚高很多团队压测AI系统时直接用线上抓的几个请求反复重放。这样做简单但结果严重失真。原因在于输入长度直接影响预填充阶段的耗时输出长度直接影响解码阶段的累计耗时。如果你压测数据集里全是短输入、短输出测出来的吞吐量会非常高但一上生产就被打回原形。我自己的做法是按线上真实请求的分布来构造数据集重点是统计两个维度输入Token长度的分布比如30%在100~500、50%在500~2000、20%在2000和输出Token长度的分布。然后用这些统计特征生成一批测试请求覆盖典型的短文本、中等文本、长文本场景。如果你连线上数据都没有那就按业务常识来估算。做客服问答的输入输出一般几百Token做文档总结的输入可能几千Token做代码补全的输入可能中等但输出波动很大。宁可多测几组不同长度也不要全用同一份数据。我压测时通常会准备三组数据集短文本组、混合组、长文本组分开统计这样能看出服务在哪种负载下最先扛不住。3. 压测工具选型不要一上来就JMeter3.1 各工具适用场景对比压测AI系统工具选型很关键。很多团队默认用JMeter但JMeter不是万能的。我按自己的使用经验做个对比工具适用场景优势局限性JMeter走HTTP接口的端到端压测、链路验收生态成熟、图表丰富、团队上手成本低对SSE流式响应支持弱、脚本维护麻烦、长连接高并发下容易成为瓶颈Locust自定义用户行为的场景压测、Python生态团队脚本灵活、可分布式压测、协程并发效率高需要写Python代码非开发同学上手略重wrk / ghz纯HTTP/gRPC接口的快速吞吐摸底单机就能打出很高并发、资源占用低不支持复杂业务场景编排vLLM自带的Benchmark脚本专项测试单模型/单框架的推理性能和推理框架深度集成能直接测出TTFT、TPOT等指标只适用于vLLM无法覆盖完整业务链路我个人的建议是分层测试先用vLLM自带的benchmark脚本做模型层面的“单机性能底数”再用JMeter或Locust做业务链路的端到端压测。这样既能看到模型本身的极限也能看到整个服务在真实负载下的表现。3.2 JMeter压测AI接口的配置要点如果你的团队已经统一用JMeter那测AI接口时有几个配置要点需要注意。首先是协议层面的配置。AI服务一般走HTTP请求体是JSON格式类似Chat接口。你需要在JMeter里添加HTTP请求采样器填好协议、域名、端口、路径Body Data里放好JSON模板。有一点容易被忽略JMeter的HTTP请求默认没有设置超时时间的话遇到生成时间长的请求可能会一直等下去压测进程会越来越重。我习惯在HTTP Request的Timeout字段里设置连接超时5秒、响应超时120秒具体值根据你的场景调整。其次是参数的动态化。压测时每条请求的输入不能完全相同否则系统会走缓存数据失真。可以在JMeter里用CSV Data Set Config读入测试数据每条线程取一条不同的输入文本。第三点是流式接口的问题。如果线上使用的是流式返回JMeter对SSEServer-Sent Events的支持是比较弱的。你会看到采样器迟迟不结束因为连接一直开着等事件流。一个变通方案是改用JSR223采样器写Groovy脚本通过Java的HTTP客户端直接读取流式响应但说实话如果只是做压测我更推荐直接用Python脚本后面章节会详细说。注意JMeter压测AI接口时线程数不代表真正的并发推理数。AI服务一般有动态批处理机制进来的请求会排队分批进入GPU。线程数更多是模拟“排队用户数”而不是“GPU同时处理的请求数”。看结果时不要直接拿线程数等于并发推理数来分析。3.3 一个适合生成式接口的Python压测脚本对于生成式AI接口我实际用得最多的其实是一个简单的Python压测脚本几十行搞定打完即用。它的好处是能自由控制流式响应的读取方式能精确记录TTFT和TPOT还不会像JMeter那样在高并发下自身先崩掉。import concurrent.futures import json import random import time import requests # 配置区 API_URL http://127.0.0.1:8000/v1/chat/completions CONCURRENCY 20 # 并发数 TOTAL_REQUESTS 200 # 总请求数 MAX_NEW_TOKENS 512 # 固定生成长度上限 # 读取测试数据每行一条输入文本 with open(test_prompts.txt, r, encodingutf-8) as f: prompts [line.strip() for line in f if line.strip()] def send_one_request(prompt: str): payload { model: your-model, messages: [{role: user, content: prompt}], max_tokens: MAX_NEW_TOKENS, temperature: 0.7, stream: False, } start time.perf_counter() resp requests.post(API_URL, jsonpayload, timeout180) resp.raise_for_status() total_latency time.perf_counter() - start content try: content resp.json()[choices][0][message][content] except Exception: pass # 粗略估算输出Token数实际最好由服务端返回的usage字段提供 output_tokens len(content) return { total_latency: total_latency, output_tokens: output_tokens, total_tokens: output_tokens len(prompt), status: resp.status_code, } def run(): # 随机打乱避免热点请求集中在同一时间 sample_prompts [random.choice(prompts) for _ in range(TOTAL_REQUESTS)] latencies [] ttft_records [] with concurrent.futures.ThreadPoolExecutor(max_workersCONCURRENCY) as executor: futures [executor.submit(send_one_request, p) for p in sample_prompts] for future in concurrent.futures.as_completed(futures): try: result future.result() latencies.append(result[total_latency]) except Exception as exc: print(f请求失败: {exc}) latencies.sort() n len(latencies) print(f完成请求数: {n}) print(f平均延迟: {sum(latencies) / n:.2f}s) print(fP50延迟: {latencies[int(n * 0.50)]:.2f}s) print(fP95延迟: {latencies[int(n * 0.95)]:.2f}s) print(fP99延迟: {latencies[int(n * 0.99)]:.2f}s) print(f吞吐量(RPS): {n / sum(latencies):.2f}) if __name__ __main__: run()这段脚本的几个设计点我说一下原因。第一用ThreadPoolExecutor而不是纯异步。因为压测目标本身是IO密集型等GPU推理多线程足够模拟并发而且代码直观好改。第二每条请求从测试集中随机抽取输入避免热点缓存。如果你要测试前缀缓存命中那就要反过来设计成固定前缀。第三脚本里统计了平均、P50、P95、P99延迟。做AI系统压测只看平均值会掩盖长尾问题。我见过一个服务平均延迟1.2秒看着还行但P99已经8秒了——这种情况必须抓出来。如果你要测的是流式接口脚本要改一下请求里加stream: true然后按行读取响应流记录从发出请求到收到第一行数据的时间这就是TTFT。后续每行数据间隔时间用来计算TPOT。这个改造不复杂但对排查体验问题非常有用。4. 完整实操从单请求基准到容量压测4.1 第一步单请求验证基准整个压测流程我建议按“先单请求再低并发再容量探测最后长稳”的顺序来。直接上高并发是很多新手最爱犯的错误——GPU利用率上不去、延迟乱跳你根本分不清是脚本问题、数据问题还是配置问题。先发几个单请求确认服务正常顺手记录基线数据。我习惯用curl先跑一遍curl -X POST http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: your-model, messages: [{role: user, content: 用一句话介绍量子计算}], max_tokens: 256, stream: true }加上-w参数可以统计耗时但我一般直接看返回里的usage字段拿Token数然后算出单请求的生成速度。这一步的意义是确认服务正常、确认输出质量没问题、拿到一个基础的“该模型最快能跑多快”的参考值。如果你的单请求生成速度就已经很慢那后面压测大概率不是并发问题而是模型或者硬件本身太吃力了。4.2 第二步监控必须先到位性能测试跑起来之后你不能只盯着压测脚本的数字GPU侧的状态必须同步监控。我压测时必看的监控项有这么几个GPU利用率用nvidia-smi或Prometheus的DCGM采集——判断算力是否打满。显存占用——判断是否接近OOM以及KV Cache的波动情况。GPU温度与功耗——长时间压测下温度过高会触发降频性能会突然下滑。服务端日志中的推理队列长度——如果队列积压说明已经到瓶颈了。网络连接数和错误数——排查是否被网关或连接池限制。我强烈建议在压测前把监控采集命令先跑起来持续性地记录数据。压测结束之后再回头对齐时间线很多“诡异现象”其实都能从监控曲线里找到答案。比如延迟飙升的那几分钟很可能就是GPU利用率刚好达到100%的时间点。# 至少开一个终端挂着这个命令 watch -n 1 nvidia-smi如果你有Prometheus和Grafana那更好把GPU指标和业务指标画在同一个时间轴上定位问题会快很多。4.3 第三步设计并执行分层压测我自己把AI系统的压测拆成三种场景每种场景的目的不一样。第一种是梯度加压场景从10并发开始逐步加到20、50、100、200每档持续3~5分钟。目的是找到系统的吞吐拐点和延迟拐点。这里的判断标准是当并发继续增加但吞吐量不再上升或者P95延迟突然大幅跳升这个点就是系统的“甜点区”到“过载区”的分界线。第二种是容量探测场景在找到的甜点区附近做更细粒度扫描比如50、75、100、125并发看哪个并发下吞吐量最高同时延迟还能保持稳定。这个数字直接用于线上容量规划和限流阈值的设定。第三种是长稳测试场景用甜点区并发持续跑30分钟以上重点看显存是否有缓慢增长泄漏迹象、延迟是否随时间漂移、生成速度是否因KV Cache碎片化而下降。AI服务长稳问题很常见尤其是长时间高并发之后显存碎片化会导致能服务的请求数越来越小。压测执行时每一轮之间我习惯留至少2分钟的冷却时间让GPU温度降下来也便于区分是服务问题还是上一轮负载残留的影响。4.4 第四步数据记录与结果判断每轮压测跑完我会把结果整理成一张统一格式的表轮次并发数输入Token均值输出Token均值RPSP50延迟P95延迟P99延迟GPU利用率显存峰值失败率这张表看起来简单但能回答大部分问题。比如RPS和延迟同时上升恭喜系统还有余力如果RPS不再涨但延迟还在涨说明系统进入了排队区用户体验会急剧下降如果GPU利用率已经接近100%但RPS偏低瓶颈在模型推理本身如果GPU利用率只有50%但延迟已经很高那瓶颈可能在CPU、网络或者框架本身。这里有一个从性能测试岗位面试题里经常出现的经典问题“假设压测发现QPS上不去你如何排查”我的思路就是先看这张表如果GPU没打满往上游查——CPU、内存、网络、中间件如果GPU打满了往下游查——模型参数、推理框架配置、量化方案、显存带宽。有了监控数据和这张表排查方向一下就清晰了。5. 常见问题与排查技巧实录5.1 GPU利用率上不去先别急着加并发这是我被问得最多的一个问题并发已经加到很高了GPU利用率还在40%晃悠是不是并发不够不一定。GPU利用率上不去常见的真实原因有这么几种请求还没到GPU就堵住了。可能是网关、负载均衡、鉴权服务成了瓶颈或者线程池、连接池被打满。这不是算力不够是流量进不来。推理框架没开动态批处理。多个请求到达后如果没有做Continuous BatchingGPU会在请求间隙闲置利用率自然上不去。显存不够支撑更大的Batch。显存已经占满了新请求进不来GPU算力吃不满。这是最尴尬的因为显存是硬约束你只能降并发或者换更大显存的卡。输入输出太短。每个请求的Token总量太少GPU还没来得及把算力拉起来就结束了大量时间耗在调度和启动上。我自己的排查顺序是先看压测脚本所在机器有没有成为瓶颈再看网络的连接数然后看推理服务的Batch Size有没有打起来最后才看显存。大多数情况下GPU利用率上不去不是算力问题而是上游或者调度问题。5.2 延迟突然飙升排队、动态批处理与长尾请求AI系统压测时延迟随并发增长不是线性的而是呈现一个明显的拐点。在拐点之前延迟温和上涨过了拐点延迟会急剧跳升。这个拐点通常出现在动态批处理的Batch Size达到上限、请求开始排队的时候。我压测时最常遇到的延迟分布是P50很低P95还行但P99高得离谱。查到最后往往是长尾请求——输入特别长或者输出特别长的个例。一个2000 Token的输入请求预填充时间可能是短文本请求的几倍甚至十几倍它会拖慢同一批次里所有请求让一批请求的延迟集体飙升。遇到这种情况我的处理方法是把压测数据集按输入长度分桶统计单独压测长文本组和短文本组。如果你的业务真实场景就是长短混合那不要试图消除长尾而是应该关注这个长尾是否符合预期——如果P99延迟比P95高出一大截检查是不是有请求超时重试、或者某个输入长度超出了一般水平。5.3 显存OOMKV Cache才是真正的显存杀手传统服务压测内存溢出是因为并发大每个请求占一点内存AI系统压测显存溢出往往是因为KV Cache的增长超出了预期。KV Cache大小和两个因素强相关并发请求数、每个请求的Token总数输入Token 输出Token。假设单Token KV Cache占用是1MB一个请求输入输出合计4096 Token单请求就要占4GB显存20个并发就是80GB。这就是为什么max_tokens设得高时并发稍微一上去就OOM。我排查显存OOM的经验是先看服务日志里OOM发生时的max_model_len和实际并发数然后算一下理论KV Cache峰值。如果理论值接近显存上限那就不是bug是容量规划问题——要么调低max_tokens或max_model_len要么减小并发要么换更大显存的卡。如果理论值远低于显存上限但还是OOM那就要查显存碎片化和框架的内存管理问题了。注意千万不要把“压测时的高并发”和“显存容量”分开看这两个在AI系统里是一体的。任何AI系统性能测试显存监控都是第一优先级比CPU、网络都重要。5.4 我在实战中沉淀的避坑清单最后整理几条我自己踩过坑之后才真正记住的经验希望能帮大家少走弯路。第一条测试数据一定要接近线上分布。我在早期压测时偷懒全用短文本测结果压测报告显示吞吐量高得惊人上线后实际吞吐只有三分之一。原因是真实请求里有一半以上的长文本预填充阶段的耗时远高于短文本。后来我养成了习惯每次压测前必须统计线上输入输出长度的分布生成混合数据集。第二条流式接口不要用JMeter硬测。如果你负责的AI应用走的是流式返回用JMeter默认的HTTP采样器去压会碰到响应迟迟不结束、取样器卡死的问题。要么改用JSR223脚本处理SSE要么直接换成Python或者Locust。我在团队里推过一套Python压测脚本之后大家就再也没回头用JMeter测流式接口了。第三条压测前先跑一轮单请求基线。这句话我说过很多次但每次强调都有必要。没有基线你后面所有的对比分析都没有参照物。而且单请求也能暴露很多基础问题——比如服务启动后的冷启动耗时、首请求特别慢、请求体格式不对这些问题在高并发下会被掩盖反而更容易误导人。第四条报告里必须写清楚模型参数和实验配置。AI系统压测报告不像传统报告你光写“某日某时并发200RPS 30”远远不够。不写模型版本、max_tokens、输入长度分布、GPU型号的报告根本无法复现也不具备对比价值。我在团队里要求每份压测报告必须附上环境配置表和模型参数表否则报告打回重写。第五条压测不是一次性任务是持续工程。模型升级要跑、推理框架升级要跑、GPU换卡要跑、线上流量模型变了也要跑。我把压测脚本和数据集保存在项目中配合CI/CD做成定时任务每次有变更动作就自动触发一轮基准测试对比历史数据看有没有性能回退。这个习惯帮我们抓出过不止一次“模型版本升级后TTFT翻倍”的隐藏问题。我从传统接口压测转到AI系统性能测试踩得最深的一次坑是某次压测QPS上不去我以为是并发不够拼命往上加线程结果显存直接被KV Cache撑爆整个服务OOM重启。后来把max_model_len调低、控制了这个模型的典型输出长度才算把问题解决。那次以后我彻底明白了一件事——AI系统的性能测试第一原则永远是先理解模型和显存的关系再谈并发和工具。先算显存再看数据分布最后才上并发工具。这套方法我沿用至今也建议每个刚入坑AI性能测试的同学先从这三个词开始TTFT、TPOT、KV Cache。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价