vLLM生产部署-高性能LLM推理服务
Ollama 让本地跑大模型变得极其简单,但它的设计目标是开发者单机使用,不是高并发服务。当多个用户同时发请求时,Ollama 是串行处理的——一个请求跑完才处理下一个,QPS(Queries Per Second,每秒处理的请求数量)极低,根本撑不住生产流量。
vLLM 生产部署:高性能 LLM 推理服务
1.1 为什么需要 vLLM:先说 Ollama 的局限
Ollama 让本地跑大模型变得极其简单,但它的设计目标是开发者单机使用,不是高并发服务。当多个用户同时发请求时,Ollama 是串行处理的——一个请求跑完才处理下一个,QPS(Queries Per Second,每秒处理的请求数量)极低,根本撑不住生产流量。
vLLM 是目前工业界最主流的开源 LLM 推理引擎,设计目标就是高吞吐量(每秒处理请求数量多)服务。同等硬件下,vLLM 的吞吐量比 Ollama 高出 10-30 倍,能真正支撑生产环境的并发请求。
1.2 核心技术:为什么 vLLM 快
vLLM 通过 PagedAttention 管理 KV Cache 和连续批处理提升推理吞吐量的架构原理
1.2.1 PagedAttention:解决内存碎片问题
LLM 推理中,每个请求都需要维护一个 KV Cache(Key-Value Cache,键值缓存,存储模型在生成过程中已处理过的上下文信息,避免重复计算),记录已生成的 token 的注意力状态。传统方案为每个请求预分配一块连续的内存,问题是:
- 不知道请求最终会生成多少 token,要么预分配过大(浪费),要么不够用(截断)
- 大量细小的内存碎片无法利用,实际可用内存远小于物理内存
PagedAttention 借鉴操作系统的虚拟内存分页机制,把 KV Cache 切成固定大小的页(block),按需分配,不同请求的 KV Cache 可以分布在不连续的内存块中。这让 GPU 显存利用率从 60% 提升到 90%+,同等显存下能同时处理更多请求。
1.2.2 连续批处理(Continuous Batching)
传统批处理(将多个请求打包在一起同时处理,提高 GPU 利用率)等所有请求都完成才处理下一批。Continuous Batching 是流水线模式:某个请求生成完毕后,立即把新请求插入这个槽位,GPU 始终保持满负荷运行。
1.3 部署:Docker 启动 OpenAI 兼容 API
vLLM 提供 OpenAI 兼容的 REST API,现有代码几乎不用改动。
# 前置条件:有 NVIDIA GPU,已安装 CUDA 和 Docker + NVIDIA Container Toolkit
# 部署 Qwen2.5-7B-Instruct
# --model:Hugging Face 模型 ID,首次启动会自动下载
# --tensor-parallel-size:使用几张 GPU,多卡并行
# --max-model-len:最大上下文长度,受显存限制,不能超过模型支持的最大值
# --gpu-memory-utilization:显存利用率,0.9 表示使用 90% 显存给 KV Cache
docker run --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-p 8000:8000 \
--ipc=host \
vllm/vllm-openai:latest \
--model Qwen/Qwen2.5-7B-Instruct \
--tensor-parallel-size 1 \
--max-model-len 8192 \
--gpu-memory-utilization 0.90 \
--served-model-name qwen2.5-7b
如果需要访问 HuggingFace 受限模型(如 LLaMA),需要传入 token:
docker run --gpus all \
-e HUGGING_FACE_HUB_TOKEN=your_hf_token \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-p 8000:8000 \
--ipc=host \
vllm/vllm-openai:latest \
--model meta-llama/Llama-3.1-8B-Instruct \
--tensor-parallel-size 1
验证服务启动:
curl http://localhost:8000/v1/models
# 返回已加载的模型列表
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen2.5-7b",
"messages": [{"role": "user", "content": "你好"}],
"max_tokens": 100
}'
1.4 用 OpenAI SDK 调用 vLLM
因为 vLLM 实现了 OpenAI 兼容 API,只需修改 base_url 和 api_key:
# vllm_client.py
from openai import OpenAI
import asyncio
# api_key 随便填,vLLM 默认不做鉴权(生产环境需要在前置 Nginx 层加鉴权)
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="not-needed",
)
def chat(message: str, model: str = "qwen2.5-7b") -> str:
"""同步调用,适合脚本场景。"""
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": message}],
max_tokens=512,
temperature=0.7,
)
return response.choices[0].message.content
def stream_chat(message: str, model: str = "qwen2.5-7b"):
"""流式调用,适合 Web 服务场景,边生成边返回。"""
stream = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": message}],
max_tokens=512,
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
print()
# vLLM 特有的额外参数(通过 extra_body 传递)
def chat_with_vllm_params(message: str) -> str:
"""
vLLM 支持一些 OpenAI API 没有的参数,通过 extra_body 传递。
best_of:生成 N 个候选,返回最好的一个(适合质量要求高的场景)
use_beam_search:使用束搜索,比贪婪解码质量更高但更慢
"""
response = client.chat.completions.create(
model="qwen2.5-7b",
messages=[{"role": "user", "content": message}],
max_tokens=256,
extra_body={
"best_of": 2, # 生成 2 个候选取最优
"use_beam_search": False,
}
)
return response.choices[0].message.content
if __name__ == "__main__":
print(chat("用一句话解释什么是向量数据库"))
stream_chat("列举 5 个 Python 最佳实践")
1.5 量化部署:显存不够时的解决方案
量化(Quantization)把模型权重从 FP16(每参数 2 字节)压缩到 INT4(0.5 字节),显存占用降低 75%,推理速度略有下降,但精度损失可以接受。
| 量化方式 | 压缩比 | 精度损失 | 推荐场景 |
|---|---|---|---|
| FP16(原始) | 1x | 无 | 显存充足,追求最高精度 |
| GPTQ INT8 | 2x | 极小 | 平衡精度和显存 |
| AWQ INT4 | 4x | 小 | 显存紧张,可接受轻微精度损失 |
| GGUF(llama.cpp) | 2-8x | 小到中 | CPU 推理或极低显存 |
vLLM 原生支持 AWQ 和 GPTQ 量化模型(AWQ 即 Activation-aware Weight Quantization,激活感知权重量化;GPTQ 即 Generative Pre-trained Transformer Quantization,两者都是将模型压缩到 INT4 精度的主流算法,精度优于简单的 INT4 截断):
# 使用 AWQ 量化版 Qwen(HuggingFace 上有现成的量化模型)
docker run --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-p 8000:8000 \
--ipc=host \
vllm/vllm-openai:latest \
--model Qwen/Qwen2.5-7B-Instruct-AWQ \
--quantization awq \
--max-model-len 8192
各主流模型的显存需求(FP16,含 KV Cache):
| 模型 | 参数量 | FP16 显存 | AWQ INT4 显存 |
|---|---|---|---|
| Qwen2.5-7B | 7B | ~18GB | ~6GB |
| Qwen2.5-14B | 14B | ~32GB | ~10GB |
| LLaMA3.1-8B | 8B | ~18GB | ~6GB |
| DeepSeek-R1-7B | 7B | ~18GB | ~6GB |
1.6 多卡并行
单卡不够时,vLLM 支持 Tensor Parallelism(张量并行,将模型的矩阵计算切分到多张 GPU 上同时进行)把模型分布到多张 GPU 上:
# 4 卡并行部署 70B 模型
docker run --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-p 8000:8000 \
--ipc=host \
vllm/vllm-openai:latest \
--model meta-llama/Llama-3.1-70B-Instruct \
--tensor-parallel-size 4 \ # 需要 4 张 GPU
--max-model-len 16384
--tensor-parallel-size 必须是 GPU 数量的因数,通常设为 GPU 数量本身。
1.7 性能调优
# benchmark.py:测试 vLLM 服务的吞吐量
import asyncio
import time
from openai import AsyncOpenAI
client = AsyncOpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
async def single_request(prompt: str) -> float:
"""发送单个请求,返回耗时(秒)。"""
start = time.time()
await client.chat.completions.create(
model="qwen2.5-7b",
messages=[{"role": "user", "content": prompt}],
max_tokens=200,
)
return time.time() - start
async def benchmark(concurrency: int = 10, total_requests: int = 100):
"""
并发压测:同时发送 concurrency 个请求,观察吞吐量和延迟。
通过调整 concurrency,找到服务的最佳并发数。
"""
prompts = ["用 100 字介绍机器学习"] * total_requests
semaphore = asyncio.Semaphore(concurrency) # 控制最大并发数
async def bounded_request(prompt):
async with semaphore:
return await single_request(prompt)
start = time.time()
latencies = await asyncio.gather(*[bounded_request(p) for p in prompts])
total_time = time.time() - start
print(f"并发数: {concurrency}")
print(f"总请求数: {total_requests}")
print(f"总耗时: {total_time:.1f}s")
print(f"吞吐量: {total_requests / total_time:.1f} req/s")
print(f"P50 延迟: {sorted(latencies)[len(latencies)//2]:.1f}s")
print(f"P99 延迟: {sorted(latencies)[int(len(latencies)*0.99)]:.1f}s")
asyncio.run(benchmark(concurrency=20, total_requests=100))
1.8 vLLM vs Ollama 选型
| 维度 | Ollama | vLLM |
|---|---|---|
| 目标场景 | 开发者本地使用 | 生产高并发服务 |
| 安装难度 | 极低(一键安装) | 中(需要 Docker + CUDA) |
| 并发能力 | 低(串行) | 高(Continuous Batching) |
| 显存利用率 | 一般 | 高(PagedAttention) |
| 量化支持 | GGUF | AWQ / GPTQ |
| OpenAI 兼容 | 是 | 是 |
| Windows 支持 | 是 | 否(需要 Linux) |
| 监控和指标 | 无 | 内置 Prometheus 指标 |
1.9 小结
vLLM 的核心优势来自两个技术创新:PagedAttention 把显存利用率从 60% 提升到 90%+,Continuous Batching 让 GPU 始终保持满负荷运行。这两者共同带来了 10-30 倍于 Ollama 的吞吐量提升。
生产部署的关键配置:--tensor-parallel-size 根据 GPU 数量设置,--max-model-len 根据业务需要和显存限制权衡,--gpu-memory-utilization 默认 0.9 通常是最优值,不建议调到更高(留出一些余量给 CUDA 运行时)。
下一章讨论 AI Agent 的安全与对齐问题,这是生产环境中最容易被忽视、但一旦出问题就非常严重的方向。