从0到1实现LLM服务极限压测:精准计算首字延迟(TTFT)与性能优化百分比

本文基于vLLM部署+异步高并发压测,完整实现首字延迟TTFT、吞吐、QPS、延迟降幅等核心指标计算,复现"首字延迟降低55%"的量化依据。

一、背景与目标

随着大模型服务落地,高并发吞吐、低首字延迟 成为衡量推理引擎能力的核心指标。vLLM凭借PagedAttention与Continuous Batching,可大幅降低首字响应时间,但性能提升需要标准化压测脚本量化验证

本文目标:

  1. 基于asyncio + aiohttp实现LLM服务异步高并发压测
  2. 支持流式响应 ,精准计算TTFT(Time To First Token)
  3. 自动对比基线与优化后性能,计算延迟降低百分比
  4. 输出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%"即由此计算得出

六、核心技术要点解析

  1. 流式请求是TTFT计算的前提

    普通同步请求必须等待完整响应返回,无法捕获首字时间戳,必须开启stream:True

  2. 异步并发而非多线程

    aiohttp+asyncio实现单线程千万级并发,无GIL开销,压测结果更接近真实极限。

  3. 延迟降幅标准化计算

    以基线为基准,避免"相对速度"误导,直观体现优化效果。

  4. P90延迟比平均值更有价值

    平均延迟易受极值干扰,P90代表90%请求的性能上限,更适合线上SLA。

七、扩展优化方向

  1. 接入tiktoken精准计算Token数,替代字符长度估算
  2. 增加P99/P999延迟、失败率、超时统计
  3. 输出CSV/图表报告,支持数据可视化
  4. 接入vLLM监控指标,统计KV Cache使用率、Batch Size等
  5. 支持动态加压,探测服务性能瓶颈

八、总结

本文实现的LLM压测脚本,完整覆盖工业级性能评估指标,通过流式响应精准采集首字延迟,通过标准化公式量化性能优化幅度,可直接用于vLLM、TGI、TensorRT-LLM等推理引擎的对比压测,是大模型服务上线前必备的性能验证工具。

相关推荐
黄宝良3 小时前
FreeSWITCH入门到精通系列(七):FreeSWITCH 性能优化与调优实战
性能优化
MU在掘金916954 小时前
一个CLI工具的架构是怎么搭起来的
性能优化·开源
Beginner x_u6 小时前
前端八股整理(工程化 01)|Git 常见命令、rebase/merge、pull/fetch 与前端性能优化
前端·性能优化·git 常见命令
南村群童欺我老无力.7 小时前
鸿蒙ForEach渲染列表的唯一性约束与性能优化
华为·性能优化·harmonyos
CSharp精选营7 小时前
.NET 11 Preview 3 发布:C# 15 union 类型终补齐,Kestrel 暴增 40%
云原生·性能优化·ai开发·.net11·csharp15
2501_9160088919 小时前
深入解析iOS应用启动性能优化策略与实践
android·ios·性能优化·小程序·uni-app·cocoa·iphone
子牙老师21 小时前
软件虚拟化 vs 硬件虚拟化
linux·性能优化·云计算
一只fish1 天前
SQL 性能优化实战:从入门到极致的七重境界
数据库·sql·性能优化
kyriewen1 天前
React性能优化:从“卡成狗”到“丝般顺滑”的5个秘诀
前端·react.js·性能优化