导语

不知道你有没有遇到过这种情况:手里有一张 24G 显存的显卡,跑一个 7B 的对话模型,压测下来 QPS 只有个位数,nvidia-smi 一看 GPU 利用率常年徘徊在个位数。钱花在硬件上,算力却白白浪费——这类高吞吐推理难题,vLLM 正是为把显存和 GPU 同时吃满而设计的。

问题往往不在模型,而在推理框架。自回归生成时,传统做法会给每个请求预留一整段最大长度的键值缓存,结果大量显存被碎片吃掉,batch size 稍大就 OOM,GPU 大部分时间在"等数据"。

本文带你把这件事做对:先讲清它为什么快(PagedAttention 与连续批处理),再给出可一键复制的部署命令,以及 OpenAI SDK 零改造迁移代码,最后落到量化、多 LoRA 与生产落地清单。照着做完,你的 GPU 利用率可以稳定拉满。

声明:本文基于个人使用体验,非商业推广。

摘要:本文以"原理 → 部署 → 调用 → 进阶 → 生产"为主线,讲解如何用 vLLM 搭建高吞吐、显存高效的大模型推理服务,并暴露 OpenAI 兼容 API,使现有业务代码零改造迁移。包含 4 段可运行代码与 6 张原理配图,覆盖 PagedAttention、continuous batching、量化部署与生产排查清单。

高吞吐推理:小黑推满车 token 穿过 GPU 机房

一、为什么需要 vLLM:高吞吐推理的痛点与核心原理

我们先看传统推理框架的显存浪费从哪来。

在大语言模型的自回归生成过程中,每生成一个新 token,都要用到前面所有 token 的 Key/Value 向量,这部分被缓存下来、反复复用,就叫 KV cache(键值缓存)。它是推理阶段显存占用的大头,长度随上下文线性增长。

传统框架的做法很"粗暴":为每个请求按最大序列长度(max_length)一次性划好一块连续显存。问题是绝大多数请求根本用不到那么长——短提问只占了一小块,剩下一大半成了内部碎片;不同请求长度不一,碎片之间又无法互相借用,显存利用率常常只有 20% 左右。

PagedAttention 借用了操作系统分页管理的思想:它把 KV cache 切成固定大小的 block(页),像虚拟内存一样按需分配、动态映射。一个请求的 KV cache 不需要在物理显存上连续,缺多少补多少页,还能在不同请求间共享同一份前缀页。结果是显存利用率从约 20% 提升到 90% 以上。

continuous batching(连续批处理) 解决的是"等齐批"的浪费。老式 batch 要等一个 batch 里所有请求都生成完才能进下一批,短请求会被长请求拖住。连续批处理改为在 token 维度动态拼批:哪个请求产出了新 token,就立刻把空出的 slot 让给新请求,GPU 几乎始终有活干。

把 PagedAttention 与 continuous batching 合在一起看:前者让同样大小的显存能塞进更多请求,后者让 GPU 在每个时刻都有新 token 可算,二者叠加才能把显存和算力同时吃满。再加上 prefix caching(前缀缓存),当多个请求共享同样的系统提示或上下文前缀时,KV cache 可以直接复用,不必重复计算,对客服、Agent 等多轮对话场景尤其有效。

维度传统自回归框架该推理引擎
显存利用率约 20%90%+
批处理方式请求级等齐批token 级连续批
典型 QPS(7B/单卡)个位数数十到上百
KV cache 管理连续预分配分页按需

除此之外,该引擎还提供 prefix caching(复用相同系统提示的缓存)、多种量化后端,以及对 NVIDIA / AMD / 华为昇腾等多硬件的支持,对外则统一暴露一套 OpenAI 兼容接口。原理清楚后,部署其实很简单。

PagedAttention:小黑把 KV cache 拆成可分页活页

二、环境准备与一键部署 vLLM 推理服务

部署前确认三件事:一张支持 CUDA 的 GPU、Python 3.10 及以上、已安装对应版本的 CUDA 驱动。

安装建议用 uv(一个极快的 Python 包管理器),也可以直接 pip。如果你不想处理本地 CUDA 依赖,官方还提供了预装环境的 Docker 镜像,一条命令就能拉起。

# 安装(Python 3.10+,建议用 uv)
uv pip install vllm

# 若用 pip
# pip install vllm

# 或使用 Docker(已含 CUDA / PyTorch 依赖)
# docker run --runtime nvidia --gpus all -p 8000:8000 vllm/vllm-openai:latest \
#   --model Qwen/Qwen2.5-7B-Instruct

启动只需要一条命令。下面以 Qwen2.5-7B-Instruct 为例,暴露默认的 OpenAI 兼容 /v1 端点:

# 启动 OpenAI 兼容 API 服务(以 Qwen2.5-7B-Instruct 为例)
vllm serve Qwen/Qwen2.5-7B-Instruct \
  --host 0.0.0.0 \
  --port 8000 \
  --gpu-memory-utilization 0.90 \
  --max-model-len 8192 \
  --dtype auto \
  --enable-prefix-caching \
  --api-key EMPTY

几个常用参数说明:--gpu-memory-utilization 0.90 表示最多占用 90% 显存(留余量防 OOM);--max-model-len 是模型支持的大上下文长度;--enable-prefix-caching 对多轮对话里相同的系统提示有明显加速;--api-key EMPTY 是关闭鉴权(生产环境务必换成真实 key 或用反向代理鉴权)。

服务起来后用一条 curl 验证:

# 验证服务已起来
curl http://localhost:8000/v1/models

返回里能看到模型名即代表成功。也可以直接浏览器打开 http://localhost:8000/docs 查看自动生成的接口文档。

遇到 “chat template not found” 类报错时,通常是模型自带的对话模板没有自动识别。你可以在启动时显式指定 --chat-template 参数,指向本地一个 jinja 模板文件;大多数 Instruct 模型在 HuggingFace 仓库里会自带 tokenizer_config.json 中的模板,升级模型文件或框架版本通常也能解决。

参数作用建议
–host/–port监听地址与端口0.0.0.0:8000
–gpu-memory-utilization显存占用上限0.85~0.92
–max-model-len大上下文长度按显存与需求
–dtype计算精度auto
–enable-prefix-caching前缀缓存多轮对话开启

一行命令启动并兼容 OpenAI 接口

三、用 OpenAI SDK 调用 vLLM 服务(无缝迁移)

如果你的业务原本基于 OpenAI Python SDK 开发,迁移到自托管模型几乎零成本。唯一要改的只有一行:把 base_url 指向本地服务地址。

from openai import OpenAI

# 唯一改动:base_url 指向本地推理服务,api_key 随意填
client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="EMPTY",
)

# 非流式
resp = client.chat.completions.create(
    model="Qwen/Qwen2.5-7B-Instruct",
    messages=[{"role": "user", "content": "用一句话解释 PagedAttention。"}],
    temperature=0.7,
)
print(resp.choices[0].message.content)

# 流式(降低首字延迟,适合对话)
stream = client.chat.completions.create(
    model="Qwen/Qwen2.5-7B-Instruct",
    messages=[{"role": "user", "content": "写一个快速排序。"}],
    stream=True,
)
for chunk in stream:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

注意 model 字段要填你启动时的模型名(HuggingFace 仓库名或本地路径)。非流式一次返回完整结果,适合后台任务;把 stream=True 打开后,token 逐个到达,首字延迟(TTFT)显著下降,对话体验更顺。你还可以通过 max_tokens 限制单条返回长度,避免长输出拖慢整体 batch。

如果你不想引入 SDK,也可以直接用 curl 对照:

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen2.5-7B-Instruct",
    "messages": [{"role": "user", "content": "你好"}],
    "stream": false
  }'

并发方面,服务端会自动用 continuous batching 把同时到达的请求合并进同一个 batch,所以客户端直接并发发请求即可,不需要自己攒 batch。

业务代码零改造接入本地服务

四、进阶实战:批处理、量化与多模型/LoRA

当单条请求跑通后,真正的收益在批处理与压缩。

并发批处理:下面这段客户端代码并发提交 20 条 prompt,服务端的连续批处理会把它们拼成一批高效计算:

import concurrent.futures
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")
prompts = [f"把第 {i} 句话翻译成英文:今天天气真好" for i in range(20)]

def ask(p):
    r = client.chat.completions.create(
        model="Qwen/Qwen2.5-7B-Instruct",
        messages=[{"role": "user", "content": p}],
    )
    return r.choices[0].message.content

# 并发提交,服务端用 continuous batching 自动合并
with concurrent.futures.ThreadPoolExecutor(max_workers=20) as ex:
    results = list(ex.map(ask, prompts))
print(f"完成 {len(results)} 条请求")

观察服务日志或 /metrics 里的 QPS 与显存曲线,你会看到 GPU 利用率明显比单条请求更高。

量化部署:当显存吃紧或想进一步提吞吐,可以用量化权重启动。以 AWQ 为例:

# 假设已准备好 AWQ 量化权重(如 Qwen/Qwen2.5-7B-Instruct-AWQ)
vllm serve Qwen/Qwen2.5-7B-Instruct-AWQ \
  --host 0.0.0.0 --port 8000 \
  --quantization awq \
  --gpu-memory-utilization 0.90 \
  --max-model-len 8192 \
  --dtype auto \
  --enable-prefix-caching

量化方式各有适用场景,对比如下:

量化方式显存节省适用条件
AWQ高需提前量化好的权重
GPTQ高需提前量化好的权重
FP8中需 Hopper 及以上架构(如 H100)

多 LoRA:通过 --enable-lora 加运行时 /v1/load_lora_adapter 可在同一服务里切换多个微调适配器。但官方文档注明该能力主要面向本地开发;生产上更稳妥的是为每个模型或适配器起独立实例,或用多模型部署。

prefix caching 已在前面开启,对"系统提示固定 + 多轮对话"的场景收益明显,建议常开。

量化给大模型瘦身降显存

五、生产落地 Checklist 与常见问题排查

要上线到生产,建议逐项核对下面清单:

  1. 显存估算:用 --gpu-memory-utilization 和 --max-model-len 做压测,确认峰值不 OOM。
  2. 健康检查:请求 GET /health 做探针,接入 K8s liveness/readiness。
  3. 鉴权与限流:用反向代理(如 Nginx)加 API key 与速率限制,别把裸服务暴露公网。
  4. 监控:盯住 QPS、TTFT(首字延迟)、TPOT(每 token 延迟)与显存占用。该引擎默认在 /metrics 暴露 Prometheus 指标,可以直接接入 Grafana 或自建的监控面板。
  5. 日志:打开 --log-level INFO 观察 batch 合并情况;遇到 OOM 时优先看显存峰值与当前 batch 长度。
  6. 多卡扩展:单机多卡用 --tensor-parallel-size;横向扩用 Ray Serve 或 V1 引擎的多副本。

常见报错对照:

现象可能原因处理
CUDA out of memory显存不足降低 utilization / 减小 max-model-len / 用量化
端口被占用8000 已被占换 --port
chat template 不匹配模板缺失加 --chat-template 指定
模型下载慢网络/源配镜像源或预下载到本地
请求卡住无返回batch 内出现极长输出限制 max_tokens / 开启 timeout / 拆分请求

收尾给一个"下一步":当你把 PagedAttention、连续批处理、量化三块串起来,一个 7B 模型在单卡上跑出数十倍吞吐并不罕见。建议从量化 + prefix caching 两个低风险的优化入手,再逐步压测上线。如果业务量继续增长,优先横向扩展多个无状态实例,而不是无限调大单卡的 batch size,这样故障域更小、回滚也更快。

相关阅读:如果你在显存与性能之间权衡,可延伸阅读 大模型量化部署从入门到实战(站内文章,互为补充)。

生产落地逐项打勾清单

总结

本文从传统推理框架的显存浪费讲起,拆解了 PagedAttention 与 continuous batching 为何能拉满 GPU;随后给出 vllm serve 一键部署、OpenAI SDK 零改造调用、并发批处理与量化部署四段可运行代码;后用生产 checklist 与排查表收尾。核心结论:高吞吐的关键不在堆硬件,而在让显存和 GPU 时刻"有活干"。


参考资料

  • 官网:https://vllm.ai(官方文档见站点内 docs 栏目)

© 2026 | 转载请注明出处

更多推荐