大语言模型训练完成后,推理部署是AIGC应用落地的最后一公里。同一模型在不同推理引擎上的吞吐量可能相差3-5倍。vLLM和TGI(Text Generation Inference)是当前使用最广泛的两款开源推理引擎,本文通过实际部署和压测数据对比两者在延迟、吞吐量、显存利用率上的差异,并给出生产环境的优化配置方案。
vLLM PagedAttention架构与连续批处理原理
vLLM的核心创新是PagedAttention机制。传统KV Cache为每个请求预分配连续显存块,当请求长度差异大时显存碎片严重,利用率通常不到60%。PagedAttention将KV Cache按固定大小(通常16个token)分页存储,通过页表映射实现非连续物理内存的虚拟连续访问,显存利用率提升到96%以上。
连续批处理(Continuous Batching)是vLLM的另一关键特性。传统静态批处理需等待同一批次所有请求生成完毕才释放资源,长请求拖累短请求。连续批处理在每个token生成步骤后检查是否有请求已完成,已完成请求立即移出批次,新请求动态加入,GPU利用率始终保持在高位。
安装部署与模型加载
# vLLM安装
pip install vllm
# 启动API服务(兼容OpenAI API格式)
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-8B-Instruct \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.90 \
--max-model-len 4096 \
--port 8000 \
--host 0.0.0.0
# TGI安装(使用Docker)
docker run --gpus all --shm-size 1g \
-p 8080:80 \
-v /data/models:/models \
ghcr.io/huggingface/text-generation-inference:2.3.0 \
--model-id meta-llama/Meta-Llama-3-8B-Instruct \
--max-input-length 1024 \
--max-total-tokens 4096 \
--num-shard 1
vLLM以pip包形式安装更轻量,TGI推荐Docker部署避免环境冲突。两者均支持多GPU张量并行,tensor-parallel-size参数指定GPU数量。gpu-memory-utilization控制vLLM可用显存比例,0.90是安全上限,过高可能导致OOM。
OpenAI兼容API调用与流式输出
import openai
import time
# vLLM客户端(API格式与OpenAI完全兼容)
client = openai.OpenAI(
base_url="http://localhost:8000/v1",
api_key=" EMPTY"
)
# 非流式请求
def benchmark_sync(prompt, max_tokens=512, n=1):
start = time.time()
response = client.completions.create(
model="meta-llama/Meta-Llama-3-8B-Instruct",
prompt=prompt,
max_tokens=max_tokens,
n=n,
temperature=0.7,
)
elapsed = time.time() - start
total_tokens = sum(len(c.text.split()) for c in response.choices)
return {
"latency_s": round(elapsed, 3),
"tokens": total_tokens,
"tps": round(total_tokens / elapsed, 1)
}
# 流式请求
def stream_generate(prompt, max_tokens=256):
stream = client.completions.create(
model="meta-llama/Meta-Llama-3-8B-Instruct",
prompt=prompt,
max_tokens=max_tokens,
stream=True,
temperature=0.7,
)
first_token_time = None
start = time.time()
tokens = 0
for chunk in stream:
if chunk.choices[0].text:
if first_token_time is None:
first_token_time = time.time() - start
tokens += 1
total_time = time.time() - start
return {
"ttft_s": round(first_token_time, 3),
"total_s": round(total_time, 3),
"tps": round(tokens / total_time, 1)
}
result = benchmark_sync("解释Transformer架构中的多头注意力机制", max_tokens=512)
print(f"同步: {result}")
stream_result = stream_generate("用Python实现快速排序算法并解释原理", max_tokens=256)
print(f"流式: TTFT={stream_result['ttft_s']}s, TPS={stream_result['tps']}")
TTFT(Time To First Token)是流式场景的关键指标,反映用户等待首个token的时间。TPS(Tokens Per Second)衡量生成速度。这两个指标在智能对话系统中直接影响用户体验。
并发压测与吞吐量对比
使用asyncio模拟多用户并发请求,对比两个引擎的吞吐量:
import asyncio
import aiohttp
import time
async def send_request(session, url, prompt, idx):
payload = {
"model": "meta-llama/Meta-Llama-3-8B-Instruct",
"prompt": prompt,
"max_tokens": 256,
"temperature": 0.7
}
start = time.time()
async with session.post(f"{url}/v1/completions", json=payload) as resp:
data = await resp.json()
elapsed = time.time() - start
return {"idx": idx, "latency": elapsed, "status": resp.status}
async def benchmark_concurrent(url, num_requests=50, concurrency=10):
prompts = [
"写一个Python冒泡排序",
"解释什么是Docker容器",
"描述RESTful API设计原则",
"分析快速排序的时间复杂度",
"说明Redis持久化机制"
] * (num_requests // 5 + 1)
semaphore = asyncio.Semaphore(concurrency)
async with aiohttp.ClientSession() as session:
async def limited_request(idx):
async with semaphore:
return await send_request(session, url, prompts[idx], idx)
start = time.time()
results = await asyncio.gather(*[limited_request(i) for i in range(num_requests)])
total_time = time.time() - start
latencies = [r["latency"] for r in results if r["status"] == 200]
return {
"total_requests": num_requests,
"concurrency": concurrency,
"total_time_s": round(total_time, 2),
"avg_latency_s": round(sum(latencies) / len(latencies), 3),
"p99_latency_s": round(sorted(latencies)[int(len(latencies)*0.99)], 3),
"throughput_rps": round(num_requests / total_time, 1)
}
# 运行压测
result = asyncio.run(benchmark_concurrent(
"http://localhost:8000",
num_requests=100,
concurrency=20
))
print(result)
在A100 40GB上对Llama-3-8B的基准测试数据(max_tokens=256,concurrency=20):
– vLLM:吞吐量约4200 tokens/s,平均延迟3.1s,P99延迟4.8s,显存利用率94%
– TGI:吞吐量约3100 tokens/s,平均延迟4.5s,P99延迟7.2s,显存利用率78%
vLLM在吞吐量上领先约35%,P99延迟低33%。优势随并发数增加而扩大,concurrency=50时vLLM吞吐量可达TGI的1.8倍。TGI在低并发(concurrency=1-5)下延迟略优,因为vLLM的批处理调度有固定开销。
核心参数调优指南
gpu-memory-utilization:默认0.90。多进程共享GPU时降低至0.80-0.85,预留显存给CUDA context和其他进程。独占GPU可设为0.92-0.95。
max-model-len:模型支持的最大上下文长度。Llama-3-8B原生支持8K,超过模型能力需启用RoPE扩展或YaRN。增大此值消耗更多显存用于KV Cache池,在4K和8K间选择时应匹配实际业务需求。
swap-space:CPU内存中用于KV Cache换出的空间大小(GB)。当GPU显存不足时,vLLM将非活跃请求的KV Cache换出到CPU内存。4-8GB通常足够,过大会挤压系统其他进程内存。
enforce-eager:禁用CUDA Graph优化。调试时开启便于定位错误,生产环境关闭以获得最佳性能。CUDA Graph消除每次推理的kernel launch开销,在短序列生成时提升约15%吞吐量。
模型量化与显存压缩
AWQ和GPTQ量化可将8B模型显存占用从16GB降至4-6GB,使消费级显卡也能部署。vLLM原生支持AWQ量化模型加载:
python -m vllm.entrypoints.openai.api_server \
--model TheBloke/Meta-Llama-3-8B-Instruct-AWQ \
--quantization awq \
--gpu-memory-utilization 0.85 \
--max-model-len 4096
AWQ量化后吞吐量下降约8-12%,但显存占用减少60%以上。在显存受限场景下这是合理的取舍。FP8量化(需要H100/A100 80GB)的性能损失更小(3-5%),是数据中心的优选方案。
生产环境部署架构建议
单实例vLLM进程受限于单机GPU数量。多机部署时使用Nginx或Envoy做负载均衡,按请求轮询分发到多个vLLM实例。流式请求需配置Nginx的proxy_buffering off,否则缓冲会破坏流式体验。
健康检查端点 /health 返回200表示模型加载完成可接受请求。Kubernetes部署时配置readinessProbe指向此端点,livenessProbe检测 /health 确保进程存活。滚动更新时新Pod就绪后再终止旧Pod,避免请求中断。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vllm-yu-tgi-tui-li-yin-qing-bu-shu-dui-bi-da-mo-xing-gao/