所有的架构设计和代码优化,最终都要在压力测试的烈火中接受检验。对于DeepSeek推理服务,我们不能简单地用 ab 或 wrk 这种针对静态网页的工具来测,因为大模型的请求是长连接,且计算负载与Prompt长度高度相关。
Locust 是一个基于Python的开源压测工具,它允许我们编写Python代码来模拟真实用户的行为,非常适合测试复杂的AI接口。本文将介绍如何使用Locust对DeepSeek服务进行全方位的性能与稳定性验证。
1. 为什么不能用ab/wrk?
ab (Apache Bench) 和 wrk 是Web服务器压测的神器,但在LLM场景下,它们有几个致命缺陷:
- 无法模拟流式响应 :它们只关注HTTP状态码和整体耗时,无法解析SSE流,无法统计 TTFT (Time To First Token) 这一关键指标。
- 请求内容静态 :大模型对输入长度极其敏感。处理10个Token和1000个Token的负载天差地别。
ab只能发送固定的Payload,无法模拟真实世界中长短不一的对话分布。 - 缺乏思考时间 :真实用户在发完一句话后,会阅读、思考、再发下一句。
wrk是无脑轰炸,这会导致并发压力虚高,无法反映真实的系统承载能力(Capacity)。
2. 模拟真实负载:Locustfile编写指南
我们需要模拟真实的业务流量分布。根据OpenAI的公开数据,典型的对话长度分布服从 泊松分布。
- 10%的用户只发短问题(<50 Token)。
- 80%的用户进行中等长度对话(500 Token)。
- 10%的用户上传长文档(>5k Token)。
创建一个 locustfile.py:
python
from locust import HttpUser, task, between, events
import json
import time
import random
class DeepSeekUser(HttpUser):
# 模拟用户思考时间:1到5秒之间
wait_time = between(1, 5)
@task(10) # 权重10,短对话
def chat_short(self):
self.send_request(prompt_len=50, output_len=100)
@task(1) # 权重1,长文档
def chat_long(self):
self.send_request(prompt_len=5000, output_len=500)
def send_request(self, prompt_len, output_len):
# 构造伪数据,实际测试中可以使用真实语料库
prompt = "test " * prompt_len
payload = {
"prompt": prompt,
"max_new_tokens": output_len,
"temperature": 0.7,
"stream": True # 开启流式
}
start_time = time.time()
first_token_time = None
token_count = 0
# 使用catch_response手动处理结果
with self.client.post("/generate", json=payload, stream=True, catch_response=True) as response:
if response.status_code != 200:
response.failure(f"Status code: {response.status_code}")
return
# 模拟接收流式响应
try:
for line in response.iter_lines():
if not line: continue
# 记录首字时间
if not first_token_time:
first_token_time = time.time()
ttft = (first_token_time - start_time) * 1000
# 自定义上报TTFT指标
events.request.fire(
request_type="grpc",
name="TTFT",
response_time=ttft,
response_length=0
)
token_count += 1
total_time = time.time() - start_time
# 计算生成速度
tps = token_count / (total_time - (first_token_time - start_time))
except Exception as e:
response.failure(f"Stream error: {e}")
3. 核心指标解读与分析
运行压测命令:locust -f locustfile.py --host http://localhost:8000 --headless -u 50 -r 1
在控制台或Web UI中,我们需要重点关注以下指标,并学会透过数据看本质:
3.1 RPS vs TPS
- RPS (Requests Per Second):对于长任务,RPS可能很低(比如0.5)。这不代表性能差,因为一个Request可能持续20秒。
- TPS (Tokens Per Second) :这是衡量LLM服务吞吐量的黄金指标 。需要在服务端统计,或者像上面代码那样在Locust端估算。
- 正常曲线:随着并发用户数增加,TPS应该线性增长,直到达到显存带宽瓶颈,然后趋于平稳。
- 异常曲线 :如果并发增加,TPS反而下降,说明发生了严重的 资源争抢(如Python GIL锁竞争、Cache Thrashing)。
3.2 Latency: P99 vs Average
- 平均延迟:毫无意义,千万别看。因为长短任务混杂,平均值会被长任务拉高,掩盖了短任务的性能问题。
- P99 Latency :尾部延迟。如果P99飙升,说明系统内部出现了 排队(Queuing) 。
- 排队原因:Dynamic Batching的等待队列满了,或者是KV Cache显存不足导致Swap。
3.3 Failure Rate
- Timeout:Nginx或客户端设置的超时时间太短。
- Connection Reset:服务端进程崩溃(OOM)。
- 503 Service Unavailable:负载均衡器主动拒绝了请求(熔断)。
4. 稳定性测试(Soak Testing)
除了测极限性能(Stress Test),还需要测 长期稳定性。让Locust以中等负载(比如60%峰值)连续运行24小时。
重点观察对象
- 显存泄漏(Memory Leak)
- 使用
npu-smi info监控。显存占用应该在一定范围内波动。如果发现显存占用随时间呈现锯齿状上升,且低谷越来越高,说明有Tensor未释放。
- 使用
- 僵尸进程
- 检查
ps -ef | grep python。是否有处理完请求但未退出的孤儿进程。
- 检查
- 温度墙(Thermal Throttling)
- 长时间满载可能导致NPU温度过高(>80度),触发硬件降频。这会导致推理速度突然变慢。
5. 混沌测试(Chaos Testing)
进阶玩家还可以尝试"搞破坏",验证系统的高可用性(HA)。
- 断网演练 :在压测过程中,模拟网络丢包(
iptables -I INPUT -m statistic --mode random --probability 0.1 -j DROP),看客户端SDK是否能自动重连。 - 杀节点:随机Kill掉一个推理Pod,看负载均衡器能否快速(<3秒)剔除故障节点,并将流量转移到健康节点。
- 显存碎片化模拟 :发送大量长度极其怪异的请求(如
[1, 8192, 3, 4000]),通过极端分布测试PagedAttention的内存碎片整理能力。
6. 总结
压测不是为了生成一份漂亮的报告,而是为了 发现系统的崩溃点(Breaking Point)。
- 如果不测TTFT,你就不知道用户等待首字的焦虑。
- 如果不测长文档并发,你就不知道显存什么时候会OOM。
- 如果不做24小时稳定性测试,你就不知道内存泄漏会在深夜搞垮服务。
通过基于Locust的全方位实战验证,我们不仅能摸清DeepSeek服务的性能边界(Capacity Planning),还能提前暴露那些只在极端并发下才会出现的Race Condition和资源竞争Bug,确保上线即稳如磐石。