Skip to content

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_percKV 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 协调方案。
  • 滚动更新:先起新副本、健康检查通过(/health 200)再加入 upstream,再摘旧副本,避免推理服务中断。

7. 工程启示汇总(可直接抄进团队规范) ​

  1. --gpu-memory-utilization 别设 1.0,0.85-0.92 留余量防 OOM。
  2. --max-model-len 按真实业务设,过长直接砍并发吞吐。
  3. --max-num-seqs 是吞吐/延迟的主动旋钮:交互场景 64-128,吞吐场景 256+。
  4. 长上下文必开 --kv-cache-dtype fp8(H100+),显存省一半、并发翻倍。
  5. RAG 场景必开 --enable-prefix-caching,长 prompt 吞吐涨 20-40%。
  6. 流式是生产默认(用户首字体验 + 网关 proxy_buffering off)。
  7. vLLM 永不裸奔公网:鉴权/限流/负载均衡全在网关层。
  8. 盯三个数:排队数、KV Cache 使用率、首 token P95——它们分别对应容量、显存、体验。

8. 延伸阅读 ​

AI-HPC 智算超算专家 · Contact: openaihpc@gmail.com