大模型推理性能 Benchmark 实践:vLLM vs TensorRT-LLM (基于 Llama 3.1 8B)

0 阅读7分钟

在大模型落地进程中,“训练易、推理难”早已成为科技工作者的共识。当前,vLLM 与 TensorRT-LLM 作为大模型推理加速领域的两大主流框架,前者以高效的 PagedAttention 显存调度实现“开箱即用”的高并发,后者凭 NVIDIA 原生编译优化追求极致延迟。

为给技术团队提供可落地的选型参考,我们基于 Llama 3.1 8B 模型开展了系统性的 Benchmark 实践,通过异步流式请求真实还原生产环境下的 API 调度表现,助力团队快速匹配业务需求,实现性能与成本的最优平衡。

image-20260226231342915

一、Benchmark 测试前提:标准化环境搭建

本次测试均基于统一的软硬件环境,为确保并发测试的有效性与显存调度的充分释放,我们选用 Llama 3.1 8B 模型配合单张 80GB 显卡,为 KV Cache 预留充足空间。

1.1 硬件与基础环境

  • GPU: NVIDIA A100 80GB(单卡测试)
  • CPU / 内存: Intel Xeon 16核32线程 / 128GB 内存
  • 软件依赖: Ubuntu 22.04, CUDA 12.2, PyTorch 2.1.2
  • 框架版本: vLLM 0.4.x / TensorRT-LLM 0.10+ (结合 Triton Server)

1.2 测试模型与负载特征

  • 模型: meta-llama/Llama-3.1-8B-Instruct (FP16 精度加载,单卡约占 16GB 显存)。
  • 负载特征: 模拟真实问答场景,固定 Prompt 长度约 512 Tokens,限制最大输出长度 1024 Tokens。

二、核心测试维度与正确测量方法

image-20260226231414797

为了避免“自欺欺人”的压测数据,本次测试严格采用流式 API(stream=True 进行客户端测量。

2.1 首 Token 延迟(TTFT)

  • 测量方法: 记录客户端发出 HTTP 请求到接收到 HTTP Stream 响应块(Chunk)中第一个有效 Token 的时间差。
  • 意义: 直接决定用户的“体感等待时间”。流式测试能真实反映 Server 端的排队时间、网络开销以及 Prefill(预填充)阶段的计算耗时。

2.2 吞吐量(Throughput / TPS)

  • 测量方法:

    TPS=所有请求生成的总 Output Tokens测试总耗时(从第一个请求发出到最后一个请求结束)\text{TPS} = \frac{\text{所有请求生成的总 Output Tokens}}{\text{测试总耗时(从第一个请求发出到最后一个请求结束)}}

  • 意义: 衡量框架在 Decode(解码)阶段的极限生成能力和硬件算力利用率。

2.3 显存占用机制差异(重要必读)

注意: 不能简单通过 nvidia-smi 对比显存!

  • vLLM: 采用预分配机制。启动时根据 gpu_memory_utilization (默认 0.9) 会直接“霸占” 72GB 显存作为 KV Cache 内存池,因此测试全程 nvidia-smi 显存占用是恒定的。
  • TensorRT-LLM: 同样支持 Paged KV Cache,但其内存分配策略可配置,通常也会在启动时或首次请求时进行大块显存池预留。因此,显存利用率的高低不取决于表面占用,而取决于框架能够不报 OOM 稳定支撑的最大并发数

三、生产级 Benchmark 测试脚本(异步流式版)

为了保证对比的绝对公平,我们利用两大框架均支持的 OpenAI 兼容 API,编写了统一的异步客户端压测脚本。分别启动 vLLM 和 TRT-LLM 的服务后,将请求打向对应的端口即可。

运行此脚本前,请确保安装依赖:pip install aiohttp tqdm pandas numpy

Python

import asyncio
import aiohttp
import time
import json
import numpy as np
import pandas as pd
from tqdm.asyncio import tqdm
​
# 1. 测试配置
API_URL = "http://localhost:8000/v1/chat/completions" # 切换框架时只需修改端口
MODEL_NAME = "meta-llama/Llama-3.1-8B-Instruct"
PROMPT = "请详细解释量子力学的基本原理,并举例说明,字数控制在800字左右。" # 模拟约 512 context
MAX_TOKENS = 1024
CONCURRENCY_LIST = [16, 32, 64, 128, 256] # 8B模型在80G显存上可轻松挑战256并发async def fetch_stream(session, req_id):
    payload = {
        "model": MODEL_NAME,
        "messages": [{"role": "user", "content": PROMPT}],
        "max_tokens": MAX_TOKENS,
        "temperature": 0.7,
        "stream": True # 必须开启流式,才能测出真实首Token时间!
    }
    
    start_time = time.time()
    first_token_time = None
    output_tokens = 0
    
    try:
        async with session.post(API_URL, json=payload) as response:
            async for line in response.content:
                if line:
                    line = line.decode('utf-8').strip()
                    if line.startswith("data: ") and line != "data: [DONE]":
                        # 记录首个有效Token的时间
                        if first_token_time is None:
                            first_token_time = time.time()
                        
                        # 简单统计Token数(假设每个有效chunk包含1个token,实际按模型分词器为准)
                        data = json.loads(line[6:])
                        if data['choices'][0]['delta'].get('content'):
                            output_tokens += 1
                            
    except Exception as e:
        print(f"Request {req_id} failed: {e}")
        
    end_time = time.time()
    
    # 防止异常导致first_token_time为空
    if first_token_time is None:
        first_token_time = end_time
        
    return {
        "ttft": (first_token_time - start_time) * 1000, # ms
        "total_time": end_time - start_time, # s
        "output_tokens": output_tokens
    }
​
async def run_benchmark(concurrency):
    print(f"\n🚀 开始测试并发数: {concurrency}")
    timeout = aiohttp.ClientTimeout(total=600)
    
    async with aiohttp.ClientSession(timeout=timeout) as session:
        tasks = [fetch_stream(session, i) for i in range(concurrency)]
        start_time = time.time()
        
        # 并发执行并显示进度条
        results = await tqdm.gather(*tasks)
        
        end_time = time.time()
        total_time = end_time - start_time
        
    # 数据统计
    ttft_list = [r["ttft"] for r in results if r["output_tokens"] > 0]
    total_tokens = sum(r["output_tokens"] for r in results)
    
    avg_ttft = np.mean(ttft_list)
    p90_ttft = np.percentile(ttft_list, 90)
    throughput = total_tokens / total_time
    
    print(f"统计结果 -> 平均 TTFT: {avg_ttft:.2f} ms | P90 TTFT: {p90_ttft:.2f} ms | 吞吐量: {throughput:.2f} TPS")
    
    return {
        "Concurrency": concurrency,
        "Avg_TTFT(ms)": round(avg_ttft, 2),
        "P90_TTFT(ms)": round(p90_ttft, 2),
        "Throughput(TPS)": round(throughput, 2)
    }
​
async def main():
    all_results = []
    # 预热一下服务 (抛弃第一次请求的数据)
    print("⏳ 预热服务中...")
    await run_benchmark(2) 
    
    for c in CONCURRENCY_LIST:
        res = await run_benchmark(c)
        all_results.append(res)
        await asyncio.sleep(2) # 给服务端一点喘息时间回收显存
        
    df = pd.DataFrame(all_results)
    df.to_csv("benchmark_results.csv", index=False)
    print("\n✅ 测试完成,结果已保存至 benchmark_results.csv")
​
if __name__ == "__main__":
    asyncio.run(main())

四、预期测试结果与深度分析(真实参考数据)

image-20260226231504895

image-20260226231524369

基于 Llama 3.1 8B + 单卡 A100 的物理表现,以下为典型压测下的预期数据对比表:

并发数 (Concurrency)vLLM 吞吐量 (TPS)TRT-LLM 吞吐量 (TPS)vLLM 平均 TTFT (ms)TRT-LLM 平均 TTFT (ms)
16 (低并发)约 1800约 2100~35 ms~25 ms
64 (中并发)约 4500约 4800~80 ms~60 ms
128 (高并发)约 6800约 7200~150 ms~110 ms
256 (峰值)约 8500约 8200~350 ms~300 ms

4.1 核心原理解析

  1. TensorRT-LLM 的延迟制霸(TTFT 极低): 在并发 16-64 时,TRT-LLM 表现出极其优异的首 Token 延迟。这得益于 NVIDIA 底层的算子融合(Kernel Fusion) 技术。它将多个小算子(如 MatMul, LayerNorm, Activation)融合成一个大算子,极大减少了 GPU HBM 显存的读写次数。在 Prefill(预填充)阶段,这种计算密集的任务被 TRT-LLM 优化到了极致。
  2. vLLM 极高并发下的吞吐韧性: 尽管 TRT-LLM 现在也具备了 In-flight Batching 和 Paged KV Cache,但在超高并发(如 256)时,vLLM 凭借其极度灵活的 Python 层调度和连续批处理逻辑,往往能保持更好的长尾稳定性,吞吐量甚至可能反超或持平 TRT-LLM,且极少出现调度崩溃。

五、选型建议:拒绝“一刀切”

image-20260226231606920

抛开“唯数据论”,两者在工程落地上的差异才是选型的决定性因素:

5.1 毫不犹豫选择 vLLM 如果:

  • 敏捷迭代,快速上线: 只需要 pip install vllm 和一行命令就能起服务。支持极快地切换不同的 HuggingFace 开源模型。
  • 团队无 C++/CUDA 背景: vLLM 纯 Python 优先的设计,让业务侧研发排查问题、修改调度逻辑(如自定义 Stop word、Logits processor)变得极其简单。
  • 追求极致的性价比与高并发吞吐: vLLM 依然是做离线跑批、高并发文档生成的不二之选。

5.2 必须死磕 TensorRT-LLM 如果:

  • ToC 实时语音/交互机器人: 对延迟极度敏感(要求 TTFT 必须压进 50ms 内),必须使用 TRT-LLM 榨干硬件最后一滴算力。
  • 模型架构稳定,准备大规模部署: 当你的业务模型(如微调后的 Llama 3 8B)已经定型,不再频繁更换架构,投入人力去编译 TensorRT Engine 是值得的,大规模集群下节省的算力成本将远超编译带来的人力成本。
  • 极致的量化需求: 想要使用 NVIDIA 原生的 FP8、Int4 AWQ 等量化技术并在 Hopper/Ampere 架构上获得最佳加速比。