ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

LLM并发访问性能瓶颈定位与优化:从指标定义到实践排查

LLM并发访问性能瓶颈定位与优化:从指标定义到实践排查 做LLM服务部署的人迟早都会撞上同一个问题单发请求的时候模型响应又快又稳一旦并发上来整个服务的表现就开始变得不可捉摸——有时候是吞吐上不去有时候是首字时延忽高忽低有时候干脆直接OOM崩溃。很多人第一反应是“加GPU”或者“把并发参数调大”但如果不先把瓶颈定位清楚加卡和调参往往都只是在瞎猜。这篇文章把我这些年实际部署和排查LLM并发访问性能问题用到的完整方法论拆开讲从指标定义、分层定位、压测实操到常见故障速查给出一套可以直接复用的分析流程。内容不挑框架不挑具体硬件核心思路是通用的适合正在做LLM服务落地、模型推理优化或者平台稳定性保障的工程师参考。1. 先搞清楚并发场景下LLM服务到底慢在哪一层1.1 一个请求从进来到返回经过哪些环节LLM服务的调用链路比普通Web服务长得多也复杂得多。一个请求从客户端发出到最终返回完整回答至少要经过这几个环节网关和负载均衡层接收外部请求、做路由、限流、鉴权。这一层通常是普通的网络服务单机扛几千上万个并发连接都没问题但在高吞吐的LLM场景下网关的转发延迟和排队策略反而会成为第一个瓶颈。推理框架的调度层这是LLM服务特有的部分。框架收到请求后要决定把它放在哪个worker上、什么时候放进Batch、排队能容忍多久都由这一层负责。调度策略的好坏直接决定并发上限。模型推理引擎真正执行模型计算的地方。一个请求的生命周期会拆成两个阶段——预填充prefill和逐Token生成decode两个阶段的计算特征完全不同瓶颈表现也完全不同。硬件资源层GPU扛着绝大部分计算CPU负责数据处理、结果采样以及一部分与GPU通信的杂活内存和显存分别决定能放多少请求状态和KV Cache。很多并发性能问题表面上看是“GPU不够快”实际上瓶颈可能藏在第一层的排队策略、第二层的调度逻辑、或者第四层的显存分配上。这也是我一直强调的分析LLM并发瓶颈要按层次拆不要一上来就被GPU利用率数字牵着鼻子走。1.2 并发放大瓶颈的三种典型路径并发本身不会制造问题它只会把原本存在的小问题放大成系统级故障。从实践来看并发访问下的性能劣化基本沿着三条路径发生。路径一是排队效应。服务端处理能力是固定的并发一旦超过处理上限请求就开始排队。排队带来的不只是等待时间变长更麻烦的是尾时延爆炸——只要有几个请求特别长整个系统的TP99和TP99.9都会变得很难看。很多时候并发上不去并不是计算能力不够而是队列管理策略不合理比如用了严格FIFO又没有超时熔断一个大请求把后续所有小请求都堵死。路径二是资源争抢。GPU的计算单元和显存带宽是共享的多个请求同时在跑必然要抢这些资源。LLM的decode阶段本质上是显存带宽密集型操作几个长请求一起生成Token时带宽会被迅速打满这时候再增加并发每个请求的生成速度反而会一起掉下来。这种“越加越慢”的现象会让很多人在指标图前怀疑人生。路径三是框架和系统层的额外开销。Python推理框架里的锁竞争、请求级的内存拷贝、CPU和GPU之间的同步等待、日志和指标采集这些都是在并发上去之后会被显著放大的开销。我见过一个案例压测到30并发时GPU利用率只有40%但CPU已经跑到120%满载最后发现瓶颈居然在框架里大量的JSON序列化和正则匹配上。这种问题加多少卡都没用得先处理代码层开销。这三条路径往往同时存在所以分析时要学会用组合指标去拆而不是只盯着一两个监控图下结论。下面先定指标再讲分层定位的实操方法。2. 指标先行分析性能瓶颈前必须定的核心指标2.1 吞吐与时延LLM场景的指标口径分析并发性能最忌讳的就是没有统一指标就开始调参。先说清楚LLM场景里几个必须定义的指标。吞吐TPS指系统每秒完成的请求数。很多人以为TPS等于并发数除以单请求耗时在普通Web服务里这个公式近似成立但在LLM服务里不成立——因为一个请求不是一次性算完的它会持续占用GPU资源几十秒甚至几分钟。所以LLM服务更常用的口径是“每块GPU每秒生成的Token数”和“同时处理的请求数”前者反映系统的实际产出能力后者反映资源占用情况。这两个指标比单纯的请求级TPS更能说明系统有没有被有效利用。时延要拆成两个来看。TTFT首Token时延是从请求发出到收到第一个Token的时间它主要由prefill阶段和排队延迟决定用户感知最直接。TPOT每Token生成时间是相邻两个Token之间的间隔它决定首Token之后的生成速度也就是用户看到的“打字机”节奏。单请求时TTFT可能只要200毫秒并发到50之后变成3秒这个变化趋势比绝对值更值得关注。我给团队定的基线经验是先压出单请求时的TTFT和TPOT作为理论基准然后以两者不劣化超过50%为界限找到系统能承受的最大并发度。这个50%是经验值不同模型和硬件可以自己调整但一定要有一个量化标准否则你没法判断到底有没有劣化、劣化了多少。2.2 并发度、队列深度与饱和度除了吞吐和时延还有三个指标容易被忽视并发度、队列深度、饱和度。并发度指“同时在处理中”的请求数。注意它和QPS不是一回事QPS是每秒进来的请求数并发度是系统里正在跑的请求数。对LLM这种长耗时服务一个请求占着GPU资源几十秒所以并发度的含义比QPS重要得多。系统里同时跑10个请求和100个请求GPU压力和调度难度完全不同。队列深度指排着队还没有开始处理的请求数。这个指标是排队效应最直接的晴雨表。队列深度持续增长且没有回落趋势说明系统的处理能力已经到顶再压下去只会徒增等待时间。饱和度指关键资源被用到的程度可以是GPU的计算单元占用率、显存带宽占比也可以是CPU某个核心的占用率。饱和度达到80%以上通常要警惕达到95%以上基本可以断定该资源就是当前瓶颈。我一直跟团队说饱和度要找“第一满”的资源因为系统里第一个被占满的资源往往就是短板。2.3 指标采集方案与压测基线指标定了怎么采也有讲究。单机压测阶段建议至少采集四个层面的数据系统层CPU、内存、磁盘IO、网络连接数用系统自带的监控命令就能看注意区分空闲和等待两种状态。GPU层显存占用、计算单元利用率、显存带宽利用率、温度功耗用GPU厂商配套的监控工具看。这里有个坑——显存占用高不代表GPU在工作很多框架会把缓存占满但计算单元利用率可能只有个位数。框架层排队请求数、正在处理的请求数、TTFT和TPOT的分位分布。这些通常要打开推理框架自带的监控接口才能拿到。应用层业务自身的耗时日志、超时率、重试率。别小看这层很多时候问题已经从服务端转移到了客户端比如客户端超时设置太短导致大量重试把服务端活活压垮。压测基线建立也很重要。我一般先跑一个低并发基线比如1到2个并发得到单请求的最佳时延作为上限参考然后按2、5、10、20、50、100逐步加压每个档位稳定跑3到5分钟记录稳态指标而不是毛刺峰值。压测脚本里的请求内容也要模拟真实场景混合不同输入长度、混合短问答和长文生成因为纯短请求和纯长请求压出来的结果差异非常大。2.4 组合指标判别表快速定位瓶颈方向单个指标说明不了问题指标组合才能指向具体瓶颈。我把常见的组合和对应结论整理成一张表排查时可以直接对照现象组合典型结论优先排查方向GPU利用率高 显存占用高 TTFT正常计算能力接近上限模型量化、精简解码逻辑、控制并发度GPU利用率低 显存占用高 TPOT慢可能被缓存或调度卡住检查KV Cache管理、批处理策略GPU利用率低 CPU利用率高瓶颈在预处理或框架开销优化数据处理、关掉多余日志TTFT暴涨 队列深度持续增长处理速度跟不上请求速度降低目标并发、优化prefill批次TPOT正常 整体吞吐上不去调度层没把请求喂饱检查批大小上限、调度策略显存没满但OOM显存碎片化或预留不足开启显存整理、调整缓存上限这张表不绝对但能帮你在拿到一堆监控图之后快速圈定排查范围少走弯路。接下来要做的就是沿着分层思路把瓶颈精确定位到某一个环节。3. 分层拆解从客户端到GPU的瓶颈定位法3.1 入口网关与请求排队最容易忽略的第一道关卡很多人排查LLM性能瓶颈时直接盯着GPU结果在网关层栽了跟头。我印象很深的一次压测时发现并发一高TTFT就开始猛涨GPU利用率却不到30%查了一圈发现是负载均衡策略出了问题新进来的请求被轮询发到多个worker上但每个worker自身的队列已经堆积了很多请求新请求只能排在队尾等着。网关层的排查重点有三个一是负载均衡策略是不是按worker的实际负载来分发而不是简单的轮询二是队列是不是无界队列无界队列在大流量下会无限堆积最终拖垮整个服务三是限流和熔断有没有生效没有限流的话上游突发流量会直接击穿服务。解决办法也比较直接把负载均衡改成按队列深度加权分发给队列设置上限队列满了就直接返回过载响应让客户端走重试策略。这里还要提醒一句网关层的连接数、线程池大小、超时时间都是配套的调了负载均衡策略但没调超时时间可能会导致客户端等不到结果就重试反而放大流量。3.2 框架调度与批处理策略连续批处理为什么是核心LLM推理框架的调度层最关键的机制是批处理策略尤其是连续批处理continuous batching。传统做法是等一批请求全部结束再一起释放这种静态批处理在并发场景下效率很低——一批里只要有一个长请求其他短请求都得陪着等。连续批处理的核心思路是批次不固定只要某个请求生成完了就把它移出批次马上补一个新请求进来。这样GPU的计算单元始终有活干吞吐能提升不少。如果框架的批处理机制没生效你会发现一个很典型的症状GPU计算单元利用率有波动但吞吐一直上不去而且请求越长整体吞吐越低。排查时要确认三件事框架版本是否开启了连续批处理批大小上限是否足够大很多框架默认批上限偏保守请求长度是否差异极大导致批次碎片化。批大小上限设置我要多说一句不是越大越好。批大小增大确实能提高GPU利用率但也会让KV Cache占用快速增长而且一旦某个请求特别长它会拖住整个批次反而把TPOT拉长。调度策略里的另一个关键点是prefill和decode是否混跑。有些框架会把prefill和decode分开处理因为两者计算特征差异很大prefill是计算密集decode是带宽密集。分离调度能避免两类请求互相干扰但实现复杂度高。如果框架支持这个选项值得开起来对比压测一下很多时候能显著改善长尾时延。3.3 推理引擎与算子热点prefill和decode要分开看到了推理引擎这一层就必须要理解prefill和decode的本质差异。prefill阶段要把用户的整个输入一次性喂给模型计算量很大但对延迟的影响是线性的decode阶段则是每次都只生成一个Token需要反复读取模型参数计算量不大但受显存带宽限制严重。用GPU厂商提供的性能分析工具抓算子热点时你会发现prefill阶段的热点集中在矩阵乘法算子bound在计算单元上decode阶段的耗时大头反而在线性层的小矩阵运算上因为每次计算的数据量很小但需要频繁访问显存中的参数量和中间量。拿到算子级别的耗时分布之后优化方向就清楚了prefill快就走量化或者精简模型结构decode慢就走算子融合、减少中间张量读写、或者用更高效的解码实现。还有一个很容易忽略的点采样和后处理算子。有些框架在每次decode之后要做logits处理、采样、解码标记等操作这些在单请求时看不出来并发高了之后如果采样逻辑写得低效CPU端会成为瓶颈。用性能分析器如果抓到很多时间花在采样相关算子可以考虑换更高效的采样实现或者把采样移到GPU上做。3.4 显存与KV Cache并发越高越紧张的一块资源LLM并发服务里显存管理是重中之重而大头就在KV Cache。KV Cache是用来缓存历史Token的注意力键值对的它的大小和请求的上下文长度、并发度直接相关。直观理解就是每个请求在生成过程中都要把已经看过的内容记下来并发请求越多、上下文越长占用的显存就越大。显存层面的瓶颈有两个常见表现。一是KV Cache上限设得太小导致框架频繁把历史上下文丢掉或者强制截断模型回答质量下降不说还会触发重新计算性能雪崩。二是显存碎片化严重多个请求不断进出KV Cache的内存块被拆得七零八落明明剩余总量够但就是分不出连续的大块来于是报OOM。解决碎片化问题的思路是采用分页式缓存管理把KV Cache切成固定大小的块按需分配或者定期做显存整理、重启回收。排查显存瓶颈时不要只看“显存占用率”这一张图要看框架暴露的缓存命中率、缓存重算率、以及请求级的显存分配日志。缓存重算率一旦超过5%基本可以断定KV Cache管理有问题需要调缓存策略或者换框架版本。另外长上下文请求对显存的放大效应也要提前评估别拿平均长度去预估容量要拿P95甚至P99的长度算否则并发稍高就会被长请求打爆显存。3.5 CPU-GPU协作与通信开销多卡场景更容易踩坑当模型被切到多块GPU上做并行推理时通信开销会成为新的瓶颈来源。GPU之间的数据交换走高速互连每传输一次张量都有固定的延迟和带宽成本。并发上来之后多个请求同时触发通信互连带宽会被迅速打满各卡的计算单元反而在空转等待数据。排查多卡通信瓶颈主要看两个数据单卡在执行计算等待通信的时间占比以及互连链路的带宽占用率。如果算力等待通信的时间占比超过30%就要考虑减少通信次数——比如把需要频繁交换的层合并成更大的通信块或者调整并行切分方式。这里有个代价权衡并行度切得越细单卡显存压力越小但通信开销越大。实际部署中并行度不是越高越好要拿同一条压测曲线对比不同并行度下的吞吐和时延找到交叉点。对于7B级别模型很多时候单卡就能跑强行切多卡反而得不偿失只有模型大到单卡放不下或者单卡算力不足时多卡并行才有意义。4. 实操记录一次典型的并发性能劣化排查过程4.1 压测环境与脚本准备光讲方法论有点虚我拿一个实际发生过的排查案例来走一遍完整流程。模拟项目X的背景是某个内部知识库问答服务接入了LLM单块数据中心级GPU部署模型是7B规模的对话模型推理框架用的是某开源推理框架最大上下文长度设置为8K。业务方抱怨白天高峰期经常出现“转圈很久才出第一个字”偶尔直接返回错误。第一步先搭压测环境。压测工具选用开源压测工具脚本里模拟了三种请求短问答输入50个字输出200个字、中等问答输入500个字输出500个字、长文总结输入2000个字输出800个字比例按真实流量设为3:4:3。并发梯度设为1、5、10、20、30、50每个档位稳定压5分钟记录指标。压测之前我特意做了件小事把框架的详细访问日志打开记录每次请求的到达时间、排队开始时间、prefill开始时间、decode开始时间和结束时间。实践证明这个举动价值巨大后面几乎所有排查结论都是从这些时间点里算出来的。4.2 第一轮QPS上不去先看是不是GPU真的满了低并发基线先跑出来的数据并发1时TTFT约180毫秒TPOT约35毫秒也就是每秒大约28个Token这个数字符合7B模型在这张卡上的正常水平。并发加到5一切正常TTFT略涨到220毫秒吞吐在涨。并发加到10奇怪的事情出现了QPS几乎不再增长稳定在每秒钟完成3到4个请求左右TPOT从35毫秒涨到了48毫秒但GPU计算单元利用率只有55%左右。这就出现了我之前说的“GPU没满但性能上不去”的典型情况。按组合指标判别表方向锁定在框架调度层。进一步看框架日志里的时间点发现请求从到达开始到真正进入批次平均等待了1.8秒排队时间比prefill时间还长。原因很快浮出水面框架默认的最大批大小是32但decode阶段的批次实际上一直没有填满因为请求长度差异大很多短请求早就结束批次里面剩下的都是长请求新请求补充不进来。这是我第一轮得到的结论瓶颈在连续批处理的填充效率不在GPU算力。4.3 第二轮提高并发后显存OOM问题在KV Cache把批大小上限从32调到64同时打开框架的连续批处理开关重新压测。这次QPS确实涨了从3.5到了一定水平但并发加到20的时候直接出现了显存OOM整个worker重启线上服务断了十几秒。查看显存监控记录发现进程退出前显存占用率只有75%不是满了才崩的而是分配KV Cache时找不到连续的大块显存。这就是典型的显存碎片化问题。解决方法是调整框架的缓存策略把KV Cache改成按固定块分配同时限制单个请求的最大上下文长度避免个别长请求一次性申请超大块显存。调完之后并发20下OOM消失显存使用稳定在85%左右。这轮排查的教训是OOM不一定等于显存总量不够很可能是分配策略的问题。遇到OOM先别急着加显存先看框架日志里申请的大小和位置再决定是调整缓存策略、限制上下文长度还是真需要升级硬件。4.4 第三轮单请求正常但并发后TTFT抖动剧烈OOM解决之后继续加压并发到30时出现了新的症状TTFT的中位数还算稳定但TP99从300毫秒直接飙到5秒以上而且抖动剧烈忽快忽慢。GPU计算单元利用率还是没到80%看起来不像算力瓶颈。把请求时间点日志拉出来按阶段聚合发现抖动集中在prefill阶段当一批请求里同时进来几个长文总结请求时prefill批次的计算量瞬间暴涨把后续请求的prefill都堵住了。推理框架默认把同一时间到达的请求放进一个批次做prefill如果这批请求都是长输入就产生了“长请求堵短请求”的问题。解决办法是开启框架的prefill和decode分离调度选项prefill请求攒够一定数量才启动避免一个大prefill批次卡住所有decode请求同时设置prefill批次的输入token总量上限超过就拆分。调整之后并发30下TTFT的TP99从5秒降回到1.2秒抖动明显改善。4.5 定位结论与优化落地把三轮排查的结论汇总下来第一轮发现连续批处理填充低效第二轮发现KV Cache碎片化第三轮发现prefill和decode互相干扰。这三个问题都不是“GPU不够快”而是框架调度和显存管理的配置问题。最终优化方案总共四项打开连续批处理批大小上限调到64。KV Cache改为块级分配并设置上限限制单请求最大上下文。开启prefill/decode分离调度限制prefill批次的总输入量。网关层加队列上限和超时熔断防止上游突发流量把框架压垮。优化后重新压测并发50时QPS比初始版本提升了约2.6倍TP99时延从5秒下降到1.4秒GPU计算单元利用率从55%提升到接近80%。整个过程没有换硬件没有改模型纯靠定位分析就把性能挖出一大截。这个案例可以说明一件事LLM并发性能问题的根因绝大多数时候不在模型本身而在模型周围的调度和资源管理。5. 高频问题与排查技巧速查5.1 并发上去了吞吐反而下降这是LLM服务最经典的反直觉现象。核心原因通常是decode阶段的显存带宽被打满所有请求同时在读模型参数带宽分不过来每个请求的生成速度都下降。排查时先看GPU带宽利用率再看TPOT是不是随并发同步上涨。如果是带宽问题方向是降低并发上限、用量化模型减小每次读取的数据量、或者优化算子减少重复读参。也有可能是批处理策略导致的静态批处理下批次里一旦有长请求整个批次释放慢新请求迟迟进不来宏观表现就是并发越高吞吐反而下降。这时候要果断确认连续批处理是否真的在生效。5.2 显存没满但报OOM显存总量剩余很多却报OOM九成是碎片化问题一成是框架内存池管理Bug。解决问题的第一步永远是看框架日志OOM时申请的内存块多大、最大可用连续块多大。如果是碎片化就改用块级缓存分配方式或者定时做显存整理。另外大批量KV Cache申请和释放是碎片产生的根源限制单请求上下文长度能在源头减少超大的连续申请。5.3 Token生成速度远低于预期单个请求TPOT明显慢于理论值时先别怀疑GPU型号不行。按经验顺序排查是不是模型量化位宽太高导致解码效率低是不是采样阶段做了太多CPU和GPU之间的同步是不是框架在decode阶段插了额外的安全检查或日志操作是不是其他请求在共享GPU带宽用GPU监控工具确认一下即可。从实践看TPOT异常多数是同步和调度问题并非真正的算力问题。5.4 时延分布长尾严重TP99和TP50差距悬殊说明存在少数请求被拖住的“堵车”现象。最常驻的三类原因一类是长输入请求占据了prefill批次后续短请求被迫等待一类是请求排队位置靠后赶上系统微突发排队时间过长还有一类是个别慢请求触发了超时重试重试流量又叠加进系统。定位方法就是分阶段统计时延分布看漂移出长尾的到底是排队、prefill还是decode阶段然后对症下药。5.5 多卡扩展后性能不升反降多卡并行不是免费的午餐。模型切分粒度太细、通信次数太多、或者互连带宽本身不够都会导致每加一张卡通信开销吃掉的收益比加卡带来的算力增量还多。排查时重点看通信时间占比和互连链路带宽占用率。我见过不少案例是模型不大但强行把并行度拉满结果性能比单卡还差。这里的经验是模型能在单卡放下且算力够用就不要切多卡必须多卡时对比不同并行度下的实际压测数据再做决定。5.6 排查工具清单与日常监控建议最后整理一份我常用的排查工具清单按用途分类列一下系统层监控操作系统自带性能监控命令重点看CPU的user、system、wait三类时间占比。GPU层监控GPU厂商监控工具重点采集计算单元利用率、显存占用、带宽利用率、温度功耗。进程级分析系统自带的进程追踪工具排查CPU绑定和上下文切换开销。算子级分析GPU厂商性能分析套件和推理框架自带的性能分析器生成算子耗时火焰图。框架指标接口推理框架自带的监控接口采集排队数、处理中请求数、TTFT/TPOT分布、KV Cache使用率。日常监控我建议至少保留这五块看板QPS和并发度、TTFT和TPOT的中位数/TP99、排队深度、GPU利用率与带宽利用率、KV Cache和显存使用率。不要贪多五块看板配合告警阈值足够覆盖90%的并发性能问题。告警阈值可以参考前文说的基线方法以单请求最优值的1.5倍作为告警线持续超过3分钟就触发排查。结尾说实话分析LLM并发访问性能瓶颈这件事真正难的不是用什么高级工具而是有没有一套清晰的拆解思路。我踩过不少坑之后最大的体会是永远先量化再优化先分层再定位先解决调度和显存问题再考虑换硬件。很多团队一遇到并发性能差就申请加卡结果加完发现GPU利用率还是上不去钱白花了。如果你现在正被类似问题困扰建议先花半天时间把框架访问日志打开、把各项指标压测基线跑出来再对照这篇文章的排查流程走一遍。最后再分享一个小技巧每次压测之后把结论写成一段话存档标上“现象-根因-改动-效果”积累一段时间之后你会发现自己判断瓶颈的速度越来越快很多问题看一眼监控图就知道出在哪一层了。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表