自部署 LLM 的并发压测:QPS 卡在 1.5,瓶颈到底在 CPU 还是显存?
导读:这篇记录了对自部署 LLM 对话接口从 1 并发加压到 100 并发的阶梯压测:QPS 无论怎么加压都稳定卡在 1.5 左右,50 并发起开始报"连接被关闭",100 并发时成功率崩到 42.86%。看似是模型算力不够,但资源监控显示显存/内存全程恒定、CPU 最高只到约 40%,结论是瓶颈在 HTTP 并发处理层而非 GPU/显存,并给出连接池、并发上限、超时与 keep-alive 等优先优化方向。适合评估自部署大模型服务并发上限的测试工程师与后端开发者阅读。
本文记录一次对自部署大模型对话接口的 JMeter 阶梯压测过程:从 1 并发一路加压到 100 并发,QPS 始终徘徊在 1.5 左右,直到 50 并发开始成片报"连接被关闭"。看似是模型算力不够,资源监控却给出了相反的答案。以下为完整过程与结论,数据已做脱敏示意。
背景与压测目标
自部署一个大模型对话服务后,需要评估其并发能力能否支撑未来线上业务。本次压测要回答两个问题:
- 接口目前的吞吐上限(QPS)和响应延迟是多少?
- 如果存在性能瓶颈,瓶颈究竟在模型算力(GPU/显存),还是在并发处理(HTTP 层)?
压测环境
| 项目 | 说明 |
|---|---|
| 被测对象 | 自部署 LLM 对话接口(OpenAI 兼容格式 /v1/chat/completions) |
| 压测工具 | JMeter(线程组按并发阶梯加压) |
| 场景策略 | 1 → 10 → 20 → 30 → 50 → 100 并发,每档持续 3 分钟 |
| 被测服务器 | 测试环境服务器,内存 30G、显存 21.5G |
| 施压机 | 本地机器(压测期间同步采集 1 分钟粒度监控) |
要点:被测的测试环境机器配置与线上机器完全一致,且未部署其他软件,因此本次压测结论可外推到线上环境。
机器部署后的初始状态
模型部署完毕后,机器初始资源占用如下:
- 内存、显存默认占用已达约 80%;
- 压测全程内存/显存数值几乎无变化(显存约 15.8G、内存约 23.9G);
- 压测时仅 CPU 有明显变化,从约 0% 升至约 40%。
也就是说,加压过程几乎不额外消耗显存与内存,模型推理并未把 GPU/显存打满。
阶梯压测数据
| 并发数 | 时长 | 服务端报错 | 成功率 | 平均响应时间 | 90% 响应时间 | QPS/吞吐量 | 显存 | 内存 |
|---|---|---|---|---|---|---|---|---|
| 1 | 3 min | 否 | 100% | 724 ms | 1 162 ms | 1.4 | 15.8G | 23.9G |
| 10 | 3 min | 否 | 100% | 6 761 ms | 10 672 ms | 1.5 | 15.8G | 23.9G |
| 20 | 3 min | 否 | 100% | 11 690 ms | 15 955 ms | 1.7 | 15.8G | 23.9G |
| 30 | 3 min | 否 | 100% | 18 474 ms | 24 319 ms | 1.5 | 15.8G | 23.9G |
| 50 | 3 min | 是 | 96.9% | 29 731 ms | 40 306 ms | 1.6 | 15.8G | 23.9G |
| 100 | 3 min | 是 | 42.86% | 46 216 ms | 60 369 ms | 1.9 | 15.8G | 23.9G |
从数据可以读出几个关键事实:
- QPS 天花板极低:无论并发怎么加,QPS 始终在 1.4~1.9 之间徘徊,稳定卡在 1.5 左右;
- 响应时间线性恶化:从 1 并发的 724 ms 一路涨到 50 并发的 29.7 s,90% 响应时间甚至突破 40 s;
- 50 并发是明显拐点:成功率从 100% 掉到 96.9%,服务端开始报错;到 100 并发时成功率崩到 42.86%,几乎不可用;
- 资源侧却"纹丝不动":显存/内存恒定,CPU 最高也只到 40%。
异常分析:50 并发开始报"连接被关闭"
从 50 并发起,服务端开始返回连接层异常,JMeter 侧堆栈如下:
org.apache.http.ConnectionClosedException: Premature end of chunk coded message body: closing chunk expected
at org.apache.http.impl.io.ChunkedInputStream.getChunkSize(ChunkedInputStream.java:263)
at org.apache.http.impl.io.ChunkedInputStream.nextChunk(ChunkedInputStream.java:222)
at org.apache.http.impl.io.ChunkedInputStream.read(ChunkedInputStream.java:183)
at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:135)
at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:148)
at org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase.readResponse(HTTPSamplerBase.java:1906)
at org.apache.jmeter.protocol.http.sampler.HTTPAbstractImpl.readResponse(HTTPAbstractImpl.java:477)
at org.apache.jmeter.protocol.http.sampler.HTTPHC4Impl.sample(HTTPHC4Impl.java:588)
at org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy.sample(HTTPSamplerProxy.java:67)
at org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase.sample(HTTPSamplerBase.java:1282)
at org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase.sample(HTTPSamplerBase.java:1271)
at org.apache.jmeter.threads.JMeterThread.doSampling(JMeterThread.java:627)
at org.apache.jmeter.threads.JMeterThread.executeSamplePackage(JMeterThread.java:551)
at org.apache.jmeter.threads.JMeterThread.processSampler(JMeterThread.java:490)
at org.apache.jmeter.threads.JMeterThread.run(JMeterThread.java:257)
at java.lang.Thread.run(Unknown Source)
Premature end of chunk coded message body(响应体分块传输被提前截断)意味着:服务器在处理不过来时,在响应尚未写完就把 HTTP 连接关闭了。这类异常指向的是服务端 HTTP 并发处理能力——连接数上限、线程池、超时/keep-alive 配置等——而不是模型计算本身。
结论:瓶颈在并发处理(HTTP 层),而非 GPU/显存
综合压测数据与资源监控,结论非常清晰:
- 接口目前不支持高并发:QPS 约 1.5 即为上限,50 并发开始报错、响应时间高达 40 s,需优化后才可接入业务使用;
- 瓶颈在 HTTP 层而非模型算力:显存、内存全程稳定(占用约 80% 无变化),CPU 仅从 0% 升到 40%,GPU/显存远未打满——真正被卡死的是 HTTP 并发处理;
- 优化方向:优先提升 HTTP 层的并发能力(连接池、并发上限、超时与 keep-alive、服务线程数等),才能满足未来线上业务使用场景;
- 模型侧仍有空间:CPU 占用只到 40%,说明模型推理侧尚有约六成余量,在 HTTP 层优化到位后,接口性能还有进一步挖潜的空间。
附:LLM 服务压测的通用注意点
- 压测必须同步盯资源监控。当显存/内存恒定、QPS 不再随并发提升、响应时间却成倍增长时,先怀疑并发处理层,而不是 GPU。
- 阶梯加压更容易定位拐点。本次在 30 → 50 并发之间,成功率从 100% 掉到 96.9%、90% 响应时间越过 40 s,拐点精确可查;单点压测很难暴露这种边界。
- 重视"连接被关闭"这类异常。
ConnectionClosedException优先排查服务端连接数、超时、keep-alive 等 HTTP 配置,顺序应先于模型侧优化。 - 环境对齐结论才有外推价值。只有测试环境机器配置与线上一致、且未部署其他软件时,压测结论才能直接代表线上表现。