vLLM 生产化部署:从启动参数到高并发服务治理
摘要: 前两篇讲了 vLLM 为什么快(PagedAttention)和怎么对比(vs Transformers)。这篇回答"上了生产怎么配":每一个启动参数的语义、吞吐与延迟怎么取舍、KV Cache 与量化怎么省显存、OpenAI 兼容 API 怎么调、Prometheus 指标怎么盯、多副本怎么负载均衡。全部命令可直接复现。
0. 实验目标
- 掌握
vllm serve核心启动参数的真实语义(不是背参数表) - 用并发压测理解 continuous batching 下吞吐/延迟的取舍
- 实测 KV Cache 量化 + Prefix Caching 的显存与首 token 收益
- 完成服务化:OpenAI 兼容 API、流式、并发请求
- 落地生产三件套:监控(Prometheus)+ 限流 + 多副本负载均衡
1. 最小启动:一行命令背后的参数语义
bash
# 生产基线:Qwen2.5-7B-Instruct,单卡 A100/H100 80G
vllm serve Qwen/Qwen2.5-7B-Instruct \
--host 0.0.0.0 --port 8000 \
--gpu-memory-utilization 0.90 \
--max-model-len 8192 \
--tensor-parallel-size 1 \
--served-model-name qwen2.5-7b逐段拆解:
--gpu-memory-utilization 0.90:允许 vLLM 用掉 90% 显存(留给 KV Cache)。不要设 1.0——要给 CUDA context、驱动和偶发峰值留余量,否则并发稍高就 OOM。--max-model-len 8192:决定 KV Cache 按多长序列预留。设得越长,单请求可处理越长,但能同时批的请求数越少。业务用不到 32K 就设 8K,别白白牺牲吞吐。--tensor-parallel-size:跨卡张量并行。7B 单卡 80G 完全够,设 1;70B 或 34B 需要 2/4/8。超过单卡显存再开 TP,TP 会引入通信开销,别为"看起来并行"而并行。--served-model-name:对外暴露的模型名。方便网关按别名路由,也避免模型名带org/前缀的坑。
2. 吞吐 vs 延迟:continuous batching 的旋钮
bash
vllm serve Qwen/Qwen2.5-7B-Instruct \
--gpu-memory-utilization 0.90 \
--max-model-len 8192 \
--max-num-seqs 256 \
--max-num-batched-tokens 8192 \
--enable-prefix-caching原理讲解:
- vLLM 的 continuous batching(连续批处理):不是等一个 batch 全部跑完再进下一个,而是每步生成后立刻把已完成的请求移出、把新请求移入。这让 GPU 始终满载。
--max-num-seqs:单次调度最多同时在飞的序列数。越大吞吐越高,但每个请求的排队延迟也越高。256 是 7B 在 80G 卡上比较均衡的值;对延迟敏感的交互场景可以降到 64-128。--max-num-batched-tokens:单步最多处理的 token 总数。和max-num-seqs一起构成吞吐上限的"双阀门"。
实验结果(7B · 80G 卡 · 256 并发压测):
| 配置 | 吞吐(token/s) | P50 首 token 延迟 | 备注 |
|---|---|---|---|
--max-num-seqs 64 | ~2.1k | 低(交互友好) | 延迟优先 |
--max-num-seqs 256 | ~3.8k | 中 | 吞吐优先 |
--max-num-seqs 256 + 前缀缓存 | ~4.6k | 低 | 长 prompt 场景收益大 |
数值为同卡同模型的相对量级参考,绝对值随模型/卡/负载变化,但"
max-num-seqs越大吞吐越高、延迟越差"的方向是稳定的。
3. KV Cache 与量化:显存是硬约束
bash
# 8K 上下文 + KV Cache 量化,长上下文场景显存大头在 KV
vllm serve Qwen/Qwen2.5-7B-Instruct \
--gpu-memory-utilization 0.90 \
--max-model-len 16384 \
--kv-cache-dtype fp8 \
--quantization awq \
--dtype auto逐段拆解:
--kv-cache-dtype fp8:KV Cache 用 FP8 存储。长上下文(16K+)下 KV 占显存的一半以上,量化后显存占用降 ~50%,可服务并发数几乎翻倍。注意:FP8 量化需要 H100 等支持 FP8 的卡(或转成fp8_e4m3在受支持硬件的模拟路径)。--quantization awq/--dtype auto:模型权重 AWQ 4bit 量化 + 自动推断权重 dtype。显存不够 8B 模型时优先权重量化;显存够(80G 跑 7B)就不必为省几个 GB 牺牲精度。- Prefix Caching(前缀缓存):
--enable-prefix-caching。RAG 场景所有请求共享同一段文档前缀时,前缀的 KV 直接复用,首 token 延迟和计算量大幅下降(实测长 prompt 场景吞吐可再涨 20-40%)。
4. 服务化:OpenAI 兼容 API 一把梭
python
# 与 OpenAI SDK 完全兼容
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8000/v1",
api_key="EMPTY", # vLLM 本地服务默认不校验 key
)
# 流式:生产必须用流式,用户感知的"首字延迟"远低于非流式
resp = client.chat.completions.create(
model="qwen2.5-7b",
messages=[{"role": "user", "content": "解释 continuous batching"}],
stream=True,
max_tokens=512,
)
for chunk in resp:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)工程启示:
api_key="EMPTY":vLLM 默认不鉴权,不要直接暴露公网,必须放网关后面(见第 6 节)。- 流式接口返回 SSE,前端/网关要支持
text/event-stream,nginx 记得关缓冲(proxy_buffering off)。 - 通过 OpenAI 兼容层,LangChain / LlamaIndex / 自研 Agent 全部零改动接入——这是 vLLM 成为事实标准的关键。
5. 并发压测:先把吞吐基线打出来
bash
# 并发 32、每请求 128 tokens,观察吞吐与延迟分布
python3 - <<'EOF'
import asyncio, time
from openai import AsyncOpenAI
client = AsyncOpenAI(base_url="http://127.0.0.1:8000/v1", api_key="EMPTY")
async def one(i):
t0 = time.perf_counter()
r = await client.chat.completions.create(
model="qwen2.5-7b",
messages=[{"role": "user", "content": f"用三句话解释 vLLM(编号 {i})"}],
max_tokens=128,
)
return time.perf_counter() - t0, len(r.choices[0].message.content)
async def main():
t0 = time.perf_counter()
results = await asyncio.gather(*[one(i) for i in range(32)])
total = time.perf_counter() - t0
lat = sorted(x[0] for x in results)
toks = sum(x[1] for x in results)
print(f"32 并发总耗时 {total:.1f}s, 吞吐 {toks/total:.0f} token/s")
print(f"P50 延迟 {lat[len(lat)//2]*1000:.0f}ms, P95 {lat[int(len(lat)*0.95)]*1000:.0f}ms")
asyncio.run(main())
EOF实验结果(示例输出):
32 并发总耗时 3.4s, 吞吐 1203 token/s
P50 延迟 2140ms, P95 2870ms吞吐=吞吐、延迟=延迟,两个指标要分开读:P95 高通常意味着排队(
max-num-seqs偏大或请求过长),先看监控里的队列长度再调参数。
6. 生产三件套:监控 + 限流 + 多副本
6.1 Prometheus 指标
bash
# vLLM 原生暴露 Prometheus 格式指标,默认 /metrics
curl -s http://127.0.0.1:8000/metrics | grep -E "num_requests_running|num_requests_waiting|gpu_cache_usage_perc" | head -5关键指标(接入 Prometheus + Grafana 面板):
| 指标 | 含义 | 报警阈值建议 |
|---|---|---|
vllm:num_requests_waiting | 排队请求数 | >50 持续 5min 告警 |
vllm:gpu_cache_usage_perc | KV Cache 使用率 | >95% 告警(将 OOM) |
vllm:generation_tokens_total | 生成 token 速率 | 吞吐基线监控 |
vllm:time_to_first_token_seconds | 首 token 延迟 | P95 > 3s 告警 |
6.2 nginx 网关:鉴权 + 限流 + 负载均衡
nginx
upstream vllm_backend {
server 10.0.0.11:8000 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8000 max_fails=3 fail_timeout=30s;
}
server {
listen 443 ssl;
server_name llm-gateway.example.com;
# 简单鉴权:外部只认网关,vLLM 本体不对外
auth_request /auth;
location /v1/ {
proxy_pass http://vllm_backend;
proxy_buffering off; # SSE 流式必须关缓冲
proxy_read_timeout 300s; # 长生成请求不掐断
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
limit_req zone=llm_per_ip burst=20 nodelay; # 按 IP 限流
}
}工程启示:
- vLLM 实例永不直接暴露公网,统一走网关:鉴权、限流、TLS、多副本路由都在这一层做。
- 多副本横向扩展:同一模型起 N 个 vLLM 实例(同参数、不同端口),网关轮询/最少连接分发。注意:多副本不共享 KV Cache,RAG 前缀缓存的收益只在单实例内有效;高并发大流量再考虑 Prefix Cache 协调方案。
- 滚动更新:先起新副本、健康检查通过(
/health200)再加入 upstream,再摘旧副本,避免推理服务中断。
7. 工程启示汇总(可直接抄进团队规范)
--gpu-memory-utilization别设 1.0,0.85-0.92 留余量防 OOM。--max-model-len按真实业务设,过长直接砍并发吞吐。--max-num-seqs是吞吐/延迟的主动旋钮:交互场景 64-128,吞吐场景 256+。- 长上下文必开
--kv-cache-dtype fp8(H100+),显存省一半、并发翻倍。 - RAG 场景必开
--enable-prefix-caching,长 prompt 吞吐涨 20-40%。 - 流式是生产默认(用户首字体验 + 网关
proxy_buffering off)。 - vLLM 永不裸奔公网:鉴权/限流/负载均衡全在网关层。
- 盯三个数:排队数、KV Cache 使用率、首 token P95——它们分别对应容量、显存、体验。