cat > test_concurrency.py <<'PY'
import asyncio
import aiohttp
import time
import statistics
URL = "http://127.0.0.1:9025/v1/chat/completions"
MODEL = "Qwen3.5"
# 总请求数量
TOTAL_REQUESTS = 100
# 并发数
CONCURRENCY = 10
# 每个请求最大输出 token
MAX_TOKENS = 1024
PROMPT = """小明有10个苹果,先给小红3个,又买了5个,最后给小刚4个,请问小明还剩多少个苹果?请说明计算过程。"""
async def request(session, request_id):
payload = {
"model": MODEL,
"messages": [
{
"role": "user",
"content": PROMPT
}
],
"temperature": 0.6,
"max_tokens": MAX_TOKENS,
"stream": True
}
start = time.perf_counter()
try:
async with session.post(
URL,
json=payload,
timeout=aiohttp.ClientTimeout(total=300)
) as response:
text = await response.text()
elapsed = time.perf_counter() - start
if response.status == 200:
return {
"id": request_id,
"success": True,
"time": elapsed,
"status": response.status,
"error": None
}
return {
"id": request_id,
"success": False,
"time": elapsed,
"status": response.status,
"error": text[:500]
}
except Exception as e:
elapsed = time.perf_counter() - start
return {
"id": request_id,
"success": False,
"time": elapsed,
"status": 0,
"error": str(e)
}
async def worker(semaphore, session, request_id):
async with semaphore:
return await request(session, request_id)
async def main():
print("=" * 60)
print("vLLM 并发测试")
print("=" * 60)
print(f"URL : {URL}")
print(f"Model : {MODEL}")
print(f"Total : {TOTAL_REQUESTS}")
print(f"Concurrency : {CONCURRENCY}")
print(f"Max tokens : {MAX_TOKENS}")
print("=" * 60)
semaphore = asyncio.Semaphore(CONCURRENCY)
connector = aiohttp.TCPConnector(
limit=CONCURRENCY,
limit_per_host=CONCURRENCY
)
async with aiohttp.ClientSession(
connector=connector
) as session:
start_all = time.perf_counter()
tasks = [
asyncio.create_task(
worker(semaphore, session, i + 1)
)
for i in range(TOTAL_REQUESTS)
]
results = await asyncio.gather(*tasks)
total_time = time.perf_counter() - start_all
success = [r for r in results if r["success"]]
failed = [r for r in results if not r["success"]]
times = [r["time"] for r in success]
print()
print("=" * 60)
print("测试结果")
print("=" * 60)
print(f"总请求数 : {TOTAL_REQUESTS}")
print(f"成功请求 : {len(success)}")
print(f"失败请求 : {len(failed)}")
print(f"成功率 : {len(success) / TOTAL_REQUESTS * 100:.2f}%")
print(f"总耗时 : {total_time:.2f} 秒")
if success:
print(f"平均响应时间 : {statistics.mean(times):.2f} 秒")
print(f"最小响应时间 : {min(times):.2f} 秒")
print(f"最大响应时间 : {max(times):.2f} 秒")
sorted_times = sorted(times)
def percentile(data, p):
index = int(len(data) * p)
index = min(index, len(data) - 1)
return data[index]
print(f"P50 : {percentile(sorted_times, 0.50):.2f} 秒")
print(f"P95 : {percentile(sorted_times, 0.95):.2f} 秒")
print(f"P99 : {percentile(sorted_times, 0.99):.2f} 秒")
print(f"请求吞吐 : {len(success) / total_time:.2f} req/s")
print("=" * 60)
if failed:
print()
print("失败请求:")
for r in failed[:10]:
print(
f"Request {r['id']} "
f"status={r['status']} "
f"error={r['error']}"
)
if __name__ == "__main__":
asyncio.run(main())
PY