本文基于vLLM部署+异步高并发压测,完整实现首字延迟TTFT、吞吐、QPS、延迟降幅等核心指标计算,复现"首字延迟降低55%"的量化依据。
一、背景与目标
随着大模型服务落地,高并发吞吐、低首字延迟 成为衡量推理引擎能力的核心指标。vLLM凭借PagedAttention与Continuous Batching,可大幅降低首字响应时间,但性能提升需要标准化压测脚本量化验证。
本文目标:
- 基于
asyncio + aiohttp实现LLM服务异步高并发压测 - 支持流式响应 ,精准计算TTFT(Time To First Token)
- 自动对比基线与优化后性能,计算延迟降低百分比
- 输出QPS、Token吞吐、P90延迟等工业级压测指标
二、核心概念
1. 关键性能指标
- TTFT(首字延迟):发送请求 → 模型返回第一个Token的时间
- Total Latency:请求总耗时(完整响应返回时间)
- QPS:每秒处理请求数
- Token吞吐:每秒生成总Token数
- 延迟降幅 :
(基线延迟 - 优化后延迟) / 基线延迟 * 100%
2. 技术栈
- vLLM:大模型推理引擎,提供OpenAI兼容接口
- aiohttp:异步HTTP客户端,支撑高并发压测
- asyncio:Python异步协程,实现无阻塞请求
- numpy:统计平均、分位数等指标
三、压测脚本完整实现
1. 模块导入与全局配置
python
import aiohttp
import asyncio
import json
import logging
import time
from typing import List, Tuple
import numpy as np
logger = logging.getLogger(__name__)
# 存储压测数据:(输入长度, 输出长度, TTFT, 总延迟)
BENCH_DATA: List[Tuple[int, int, float, float]] = []
# vLLM OpenAI兼容接口地址
API_URL = "http://localhost:8000/v1/chat/completions"
HEADERS = {"Content-Type": "application/json"}
2. 流式请求与TTFT采集
TTFT必须依赖流式响应,普通请求等待完整返回无法获取首字时间戳。
python
async def send_stream_request(session, prompt, prompt_len):
request_start = time.time()
ttft = None # 首字延迟
full_content = []
payload = json.dumps({
"model": "default",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.001,
"max_tokens": 4096,
"top_k": 1,
"frequency_penalty": 2.0,
"stream": True, # 开启流式输出,核心!
})
try:
async with session.post(API_URL, data=payload, headers=HEADERS) as resp:
if resp.status != 200:
logger.error(f"请求异常 {resp.status}: {await resp.text()}")
return
# 逐行解析SSE流
async for line in resp.content:
line = line.strip()
if not line:
continue
# 首包到达时记录TTFT
if ttft is None:
ttft = time.time() - request_start
# 解析流式数据
if line.startswith(b"data: "):
data = line[6:]
if data == b"[DONE]":
break
obj = json.loads(data)
delta = obj["choices"][0]["delta"].get("content", "")
if delta:
full_content.append(delta)
# 记录总耗时与输出长度
total_latency = time.time() - request_start
completion_tokens = len("".join(full_content))
BENCH_DATA.append((prompt_len, completion_tokens, ttft, total_latency))
except Exception as e:
logger.exception("请求执行失败")
3. 并发压测执行器
通过协程+队列控制并发数,避免服务过载,模拟真实高并发场景。
python
class BenchMarkRunner:
def __init__(self, requests: List[str], concurrency: int):
self.concurrency = concurrency # 并发数
self.requests = requests
self.total = len(requests)
self.finished = 0
async def worker(self, queue):
timeout = aiohttp.ClientTimeout(total=10 * 60)
async with aiohttp.ClientSession(timeout=timeout) as session:
while True:
try:
prompt = queue.get_nowait()
except asyncio.QueueEmpty:
break
await send_stream_request(session, prompt, len(prompt))
self.finished += 1
print(f"完成 {self.finished}/{self.total} | TTFT: {BENCH_DATA[-1][2]:.3f}s")
async def run(self):
queue = asyncio.Queue()
for req in self.requests:
queue.put_nowait(req)
# 启动N个协程worker并行压测
tasks = [asyncio.create_task(self.worker(queue)) for _ in range(self.concurrency)]
await asyncio.gather(*tasks)
4. 压测执行与指标统计
封装单次压测逻辑,统一计算核心指标,支持多次压测对比。
python
def run_benchmark(name: str, requests: List[str], concurrency: int):
print(f"\n===== 开始压测:{name} =====")
BENCH_DATA.clear()
start_time = time.time()
asyncio.run(BenchMarkRunner(requests, concurrency).run())
end_time = time.time()
total_time = end_time - start_time
count = len(BENCH_DATA)
# 提取指标
ttft_list = [d[2] for d in BENCH_DATA]
total_lat_list = [d[3] for d in BENCH_DATA]
out_tokens = [d[1] for d in BENCH_DATA]
return {
"name": name,
"total_time": total_time,
"count": count,
"qps": count / total_time,
"avg_ttft": np.mean(ttft_list),
"p90_ttft": np.percentile(ttft_list, 90),
"avg_total_lat": np.mean(total_lat_list),
"token_per_sec": sum(out_tokens) / total_time,
}
5. 性能对比与降幅计算
核心公式:延迟降低百分比 = (基线延迟 - 优化后延迟) / 基线延迟 × 100%
python
def print_summary(old, new):
print("\n" + "=" * 60)
print(f"压测对比结果:{old['name']} → {new['name']}")
print("=" * 60)
# 计算延迟降幅
ttft_drop = (old["avg_ttft"] - new["avg_ttft"]) / old["avg_ttft"] * 100
lat_drop = (old["avg_total_lat"] - new["avg_total_lat"]) / old["avg_total_lat"] * 100
print(f"平均首字延迟(TTFT):{old['avg_ttft']:.4f}s → {new['avg_ttft']:.4f}s | 降低 {ttft_drop:.1f}%")
print(f"P90首字延迟:{old['p90_ttft']:.4f}s → {new['p90_ttft']:.4f}s")
print(f"平均总延迟:{old['avg_total_lat']:.4f}s → {new['avg_total_lat']:.4f}s | 降低 {lat_drop:.1f}%")
print(f"QPS:{old['qps']:.2f} → {new['qps']:.2f}")
print(f"Token吞吐:{old['token_per_sec']:.1f} → {new['token_per_sec']:.1f} token/s")
6. 主函数入口
python
def main():
concurrency = 4 # 自定义并发数
# 加载测试数据集
testset = json.load(open("./data/summary_test.json"))
requests = [item["instruction"] for item in testset]
# 1. 基线压测(如原生HF)
baseline = run_benchmark("基线模型", requests, concurrency)
# 2. vLLM优化后压测
vllm_result = run_benchmark("vLLM优化", requests, concurrency)
# 3. 输出对比报告
print_summary(baseline, vllm_result)
if __name__ == "__main__":
main()
四、脚本使用流程
1. 启动vLLM服务(OpenAI兼容模式)
bash
python -m vllm.entrypoints.openai.api_server \
--model your_model_path \
--port 8000
2. 准备测试数据
在./data/summary_test.json中构造请求:
json
[
{"instruction": "请总结以下文章内容..."},
{"instruction": "实现一个快速排序算法"}
]
3. 执行压测
bash
python llm_benchmark.py
五、典型输出报告
===== 开始压测:基线模型 =====
完成 20/20 | TTFT: 1.182s
===== 开始压测:vLLM优化 =====
完成 20/20 | TTFT: 0.532s
============================================================
压测对比结果:基线模型 → vLLM优化
============================================================
平均首字延迟(TTFT):1.1800s → 0.5310s | 降低 55.0%
P90首字延迟:1.2100s → 0.5500s
平均总延迟:5.2000s → 2.1500s | 降低 58.7%
QPS:1.20 → 2.80
Token吞吐:42.5 → 128.3 token/s
"首字延迟降低55%"即由此计算得出。
六、核心技术要点解析
-
流式请求是TTFT计算的前提
普通同步请求必须等待完整响应返回,无法捕获首字时间戳,必须开启
stream:True。 -
异步并发而非多线程
aiohttp+asyncio实现单线程千万级并发,无GIL开销,压测结果更接近真实极限。
-
延迟降幅标准化计算
以基线为基准,避免"相对速度"误导,直观体现优化效果。
-
P90延迟比平均值更有价值
平均延迟易受极值干扰,P90代表90%请求的性能上限,更适合线上SLA。
七、扩展优化方向
- 接入
tiktoken精准计算Token数,替代字符长度估算 - 增加P99/P999延迟、失败率、超时统计
- 输出CSV/图表报告,支持数据可视化
- 接入vLLM监控指标,统计KV Cache使用率、Batch Size等
- 支持动态加压,探测服务性能瓶颈
八、总结
本文实现的LLM压测脚本,完整覆盖工业级性能评估指标,通过流式响应精准采集首字延迟,通过标准化公式量化性能优化幅度,可直接用于vLLM、TGI、TensorRT-LLM等推理引擎的对比压测,是大模型服务上线前必备的性能验证工具。