第8篇:高性能生产部署——vLLM 部署本地大模型(PagedAttention 原理 + 实操)

本文为「本地大模型部署系列」第8篇,完整系列持续更新中。


前言

本篇是整个系列技术含量最高的一篇,也是最贴近真实生产场景的一篇。读完你不仅能跑起 vLLM,还能在面试中清晰讲出 PagedAttention——这是很多工程师的知识盲区。

在前几篇里,我们用 Ollama 实现了「一键拉起模型」,用 llama.cpp 实现了「低配机器也能跑」,用 FastChat 实现了「多模型统一 API 入口」。但如果你面对的是这样的场景:

  • 生产环境同时有 50+ 并发用户 请求模型
  • 公司要求接口 P95 延迟 < 2 秒
  • 服务器是 A100/H100,不能让昂贵的 GPU 闲着
  • 需要对外提供 OpenAI 兼容的企业级 API 服务

那前面的工具都不够用了——你需要 vLLM

vLLM 是目前工业界公认的 高吞吐 LLM 推理引擎首选,由 UC Berkeley Sky Lab 开源。它解决的核心问题是:如何让一块 GPU 同时服务尽可能多的请求,同时保持低延迟

本篇学完你将掌握

  • PagedAttention 核心原理(能在面试中清晰讲出来)
  • vLLM 完整部署流程(pip 安装 → 启动服务 → API 调用)
  • 流式输出、量化模型加载、多 GPU 并行的完整实现
  • 高并发压测脚本,直观感受吞吐量提升
  • 与 Ollama / llama.cpp / FastChat 的性能对比数据
  • 踩坑解决方案 + 生产场景下的工程选型逻辑

一、核心原理极简讲解

要用好 vLLM,首先得搞清楚它为什么快——这不是纸面功夫,而是调参、排错、面试的底气来源。这一节不讲数学公式,只讲工程视角下你必须懂的核心逻辑。

1.1 vLLM 的定位

先建立整体认知,再深入核心机制。

vLLM(very Large Language Model serving)是一个专为高吞吐 LLM 推理设计的引擎,核心特性:

特性 说明
PagedAttention 革命性 KV Cache 管理,大幅减少显存浪费
Continuous Batching 动态批处理,GPU 利用率趋近 100%
OpenAI 兼容 API 开箱即用,一行代码切换
量化支持 FP16 / BF16 / AWQ / GPTQ / SqueezeLLM
多卡并行 Tensor Parallelism 原生支持

一句话定位:Ollama 是给个人用的,vLLM 是给生产用的。


1.2 PagedAttention:核心原理(面试必考)

定位清楚了,下面进入最关键的技术原理。PagedAttention 是 vLLM 性能碾压同类框架的根本原因,也是面试中被问到「vLLM 为什么快」时的标准答案。

这是 vLLM 最重要的技术创新,也是它碾压其他框架的根本原因。

传统 KV Cache 的问题

大模型在推理时,每个 token 生成都需要用到之前所有 token 的 Key/Value 矩阵(即 KV Cache)。传统做法是预先为每个请求分配一块连续的显存空间

1
2
3
请求A: [████████████████████________] 预分配 2048 token 空间,实际用了 300 token
请求B: [█████████████████████████___] 预分配 2048 token 空间,实际用了 600 token
请求C: [等待中,但没有连续大块显存了...]

这带来三个致命问题:

  1. 预分配浪费:不知道请求最终生成多长,只能按最大长度预留,大量空间闲置
  2. 碎片化严重:请求结束后释放的空间东一块西一块,无法被新请求利用
  3. 并发上限低:显存浪费 → 能同时处理的请求数极少 → 吞吐量低

典型数据:传统方式 KV Cache 显存利用率通常仅有 20%~40%,大量显存被浪费。

PagedAttention 解决方案:停车场类比

想象一个停车场管理系统

  • 传统方式:来了一辆车,先给它”预订” 10 个车位(因为不知道它会带几辆跟随车),哪怕最后只用了 2 个,剩下 8 个也空着等它。
  • PagedAttention 方式:车位按需分配,用完一个再给一个,停车场利用率接近 100%。

技术层面的映射

  • 物理块(Physical Block):显存中固定大小的存储单元(类似停车位),每块存储固定数量 token 的 KV Cache(默认 16 个 token)
  • 逻辑块(Logical Block):请求视角的连续地址空间(类似车牌号→车位号的映射表)
  • 按需分配:请求每生成 16 个 token 才申请一个新物理块,不提前占用
  • 共享机制:多个请求如果有相同前缀(如 System Prompt),可以共享同一组物理块,零拷贝复用
1
2
3
逻辑视图(请求A眼中):    [Block 0][Block 1][Block 2]...
↕ ↕ ↕
物理视图(实际显存): [物理块7][物理块2][物理块15]...(非连续,随机分布)

为什么能提升 2-4x 吞吐量

维度 传统方式 PagedAttention
显存利用率 20%~40% > 90%
碎片化 严重 几乎为零
同时处理请求数 受限 大幅提升
前缀共享 不支持 支持(CoW 机制)

显存利用率从 30% 提升到 90%,意味着同等显存能塞入的请求数提升了 3 倍,吞吐量自然随之提升。


1.3 Continuous Batching:连续批处理

PagedAttention 解决了「显存如何高效利用」的问题,但光有显存还不够——还要让 GPU 的计算单元永远不闲着。这就是 Continuous Batching 要解决的问题。

传统 Static Batching 的问题:

1
2
3
4
Batch:[请求A-500token][请求B-50token][请求C-300token]
████████████████████████████████████████████ A还在生成...
███████ B已经完成,但要等A完成才能释放槽位!
████████████████ C还在生成...

请求 B 早就完成了,但 GPU 在傻等请求 A,这段时间 GPU 计算能力完全浪费。

Continuous Batching(vLLM 采用)

1
2
3
时刻1: [请求A][请求B][请求C]
时刻2: [请求A][B已完成→替换为请求D][请求C]
时刻3: [请求A][请求D][C已完成→替换为请求E]

GPU 永远满载,空闲率趋近于零。这也是 vLLM 在高并发场景下吞吐量远超静态批处理框架的核心原因。


1.4 支持的模型格式

原理层面搞清楚了,最后补一个实用信息:vLLM 支持哪些模型格式。这直接决定你能不能把手头的模型加载进来。

格式 说明 典型场景
FP16 / BF16 半精度浮点,精度最高 A100/H100 高精度场景
AWQ 激活感知量化,4-bit,精度损失极小 显存受限 + 质量要求高
GPTQ 训练后量化,4-bit/8-bit 模型丰富,选择多
SqueezeLLM 稀疏量化 研究场景

二、环境 & 前置依赖

原理搞清楚了,接下来搭环境。vLLM 对环境要求比 Ollama 严格不少,提前配对能省掉大量排错时间——尤其是 Windows 用户,这里有个天坑需要提前知道。

2.1 系统要求

先看硬性门槛,不满足就无法安装。

项目 要求 说明
操作系统 Linux(首选)/ macOS(部分功能) Windows 需通过 WSL2 或 Docker
Python 3.9 ~ 3.12 推荐 3.10 / 3.11
CUDA 12.1 及以上 CUDA 11.x 部分版本兼容,但不推荐
GPU 驱动 >= 525.x nvidia-smi 确认
显存 最低 8GB VRAM 7B FP16 需要 ~14GB,推荐 16GB+

Windows 用户注意:vLLM 不原生支持 Windows,必须使用 WSL2(Windows Subsystem for Linux)或 Docker。后文踩坑章节有详细方案。

2.2 推荐硬件配置

系统要求确认后,再对照自己的 GPU 档位,看能跑哪类模型。

场景 推荐 GPU 可部署模型
个人开发测试 RTX 3090/4090(24GB) 7B FP16,13B AWQ
团队服务 A10G(24GB)× 1 7B FP16,13B AWQ/GPTQ
企业生产 A100(80GB)/ H100 70B FP16,各类量化大模型
多卡扩展 2-8 × A100 超大模型 Tensor Parallel

2.3 CUDA 版本确认

硬件确认没问题后,最后一步是验证 CUDA 版本——这是 vLLM 安装失败的最高频原因,先查清楚再装。

1
2
3
4
5
6
7
8
# 确认 CUDA 版本
nvidia-smi
nvcc --version

# 确认 Python 版本
python --version

# 推荐组合:CUDA 12.1 + Python 3.10/3.11

三、Step-by-Step 实操流程

环境准备好了,正式进入动手环节。这一节按「安装 → 验证 → 启动服务 → 调用 → 高级用法」的顺序推进,每一步都可以独立复制执行,跟着走一遍就能跑通完整链路。

3.1 安装 vLLM

根据你的环境选择合适的安装方式,大多数情况下 pip 安装就够用,遇到版本冲突再考虑其他方式。

方式一:pip 安装(推荐,最简单)

1
2
3
4
5
6
7
8
9
# 创建专用 conda 环境
conda create -n vllm python=3.11 -y
conda activate vllm

# 安装 vLLM(自动匹配 CUDA 版本)
pip install vllm

# 验证安装
python -c "import vllm; print(vllm.__version__)"

方式二:指定 CUDA 版本安装

1
2
3
4
5
# CUDA 12.1
pip install vllm --extra-index-url https://download.pytorch.org/whl/cu121

# CUDA 12.4
pip install vllm --extra-index-url https://download.pytorch.org/whl/cu124

方式三:Docker 安装(Windows / 环境隔离首选)

1
2
3
4
5
6
7
8
9
10
11
12
# 拉取官方镜像(包含完整 CUDA 环境)
docker pull vllm/vllm-openai:latest

# 运行容器(挂载模型目录)
docker run --runtime nvidia --gpus all \
-v /path/to/models:/models \
-p 8000:8000 \
--ipc=host \
vllm/vllm-openai:latest \
--model /models/Qwen2.5-7B-Instruct \
--host 0.0.0.0 \
--port 8000

方式四:源码编译(需要自定义 kernel 时使用)

1
2
3
git clone https://github.com/vllm-project/vllm.git
cd vllm
pip install -e . # 开发模式安装,编译时间约 10-30 分钟

3.2 离线推理快速体验

安装成功后,不要急着上 API 服务,先用离线推理跑一把——这是最快的验证方式,能确认模型加载和 GPU 调用都正常,避免在 API 层排查时绕弯路。

安装完成后,先用 Python 脚本跑一下离线推理,验证环境是否正常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# offline_inference.py
from vllm import LLM, SamplingParams

# 加载模型(首次运行会下载模型)
# 如果本地已有模型,直接填本地路径
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct", # 或本地路径如 "/models/Qwen2.5-7B-Instruct"
gpu_memory_utilization=0.85, # 显存利用率,留 15% 给系统
max_model_len=4096, # 最大序列长度
)

# 采样参数
sampling_params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=512,
)

# 批量推理(vLLM 天然支持批量,无需循环)
prompts = [
"请用一句话解释什么是大语言模型。",
"Python 和 Go 语言各自的优势是什么?",
"写一段简短的自我介绍。",
]

outputs = llm.generate(prompts, sampling_params)

for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt!r}")
print(f"Output: {generated_text!r}")
print("-" * 50)
1
python offline_inference.py

看到模型输出即表示环境正常,可以进入下一步。


3.3 启动 OpenAI 兼容 API 服务

离线推理验证模型能正常工作后,下一步是把它包装成 API 服务——这才是生产场景的标准用法,也是后续所有调用示例的前提。

vLLM 内置了 vllm serve 命令,一行启动 OpenAI 兼容服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 基础启动
vllm serve Qwen/Qwen2.5-7B-Instruct

# 完整参数版(推荐生产使用)
vllm serve /path/to/Qwen2.5-7B-Instruct \
--host 0.0.0.0 \
--port 8000 \
--served-model-name qwen2.5-7b \
--gpu-memory-utilization 0.90 \
--max-model-len 8192 \
--max-num-seqs 256 \
--dtype auto \
--trust-remote-code

启动成功日志关键字:

1
2
3
4
INFO:     Started server process [xxxxx]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000

3.4 API 调用验证

服务启动后,先用最简单的方式验证它真的在响应,再写复杂代码。养成「先 curl 验通,再写 SDK」的习惯能节省大量调试时间。

方式一:curl 验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看可用模型
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": 200,
"temperature": 0.7
}'

方式二:Python openai 库调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# api_call.py
from openai import OpenAI

client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="not-needed", # vLLM 默认不需要 API key
)

response = client.chat.completions.create(
model="qwen2.5-7b",
messages=[
{"role": "system", "content": "你是一个专业的 AI 助手。"},
{"role": "user", "content": "请解释一下什么是 PagedAttention?"},
],
max_tokens=500,
temperature=0.7,
)

print(response.choices[0].message.content)
print(f"\n--- Token 使用 ---")
print(f"输入 tokens: {response.usage.prompt_tokens}")
print(f"输出 tokens: {response.usage.completion_tokens}")

3.5 流式输出实现(完整示例)

同步调用验证没问题后,进入对话产品最常用的模式——流式输出。流式输出能让用户看到「打字机效果」,显著降低感知延迟,是生产环境的标配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# streaming_output.py
from openai import OpenAI
import time

client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="not-needed",
)

def stream_chat(prompt: str, model: str = "qwen2.5-7b") -> str:
"""流式输出并实时打印"""
print(f"用户: {prompt}")
print("助手: ", end="", flush=True)

full_response = ""
start_time = time.time()

try:
stream = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
max_tokens=1024,
temperature=0.7,
stream=True, # 开启流式
)

for chunk in stream:
if chunk.choices[0].delta.content is not None:
content = chunk.choices[0].delta.content
print(content, end="", flush=True)
full_response += content

elapsed = time.time() - start_time
token_count = len(full_response) # 粗略估算
print(f"\n\n[耗时: {elapsed:.2f}s | 约 {token_count} 字符]")

except Exception as e:
print(f"\n[错误] 流式输出失败: {e}")

return full_response


if __name__ == "__main__":
stream_chat("用通俗的语言解释大语言模型是如何生成文字的,要求举个生活中的例子。")

3.6 加载量化模型(AWQ / GPTQ)

FP16 模型跑通了,但如果你的显存不够宽裕,或者想在同一张卡上多跑几个实例,就需要量化模型了。vLLM 对主流量化格式有原生支持,性能损失极小。

vLLM 对量化模型有原生支持,指定 --quantization 参数即可:

AWQ 模型(推荐,精度损失最小)

1
2
3
4
5
6
# 启动 AWQ 量化模型
vllm serve /path/to/Qwen2.5-7B-Instruct-AWQ \
--quantization awq \
--gpu-memory-utilization 0.90 \
--max-model-len 8192 \
--dtype auto

GPTQ 模型

1
2
3
4
vllm serve /path/to/Qwen2.5-7B-Instruct-GPTQ-Int4 \
--quantization gptq \
--gpu-memory-utilization 0.90 \
--max-model-len 8192

Python 离线加载量化模型

1
2
3
4
5
6
7
8
9
10
from vllm import LLM, SamplingParams

# AWQ 量化模型
llm = LLM(
model="/path/to/Qwen2.5-7B-Instruct-AWQ",
quantization="awq",
gpu_memory_utilization=0.85,
max_model_len=8192,
dtype="auto",
)

显存对比:7B FP16 约需 14GB,7B AWQ-4bit 约需 4-5GB,同一块 24GB 显卡 AWQ 可以多跑 2-3 个实例。


3.7 多 GPU 部署(Tensor Parallelism)

单卡跑通后,如果模型太大(如 70B)单卡放不下,或者需要更高的吞吐上限,就该上多卡并行了。vLLM 的 Tensor Parallelism 配置极简,加一个参数搞定。

当模型太大单卡放不下,或需要更高吞吐时,使用 Tensor Parallelism:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2 卡并行(将模型张量切分到 2 张 GPU)
vllm serve /path/to/Llama-3-70B-Instruct \
--tensor-parallel-size 2 \
--gpu-memory-utilization 0.90 \
--max-model-len 8192

# 4 卡并行
vllm serve /path/to/Llama-3-70B-Instruct \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.90

# 指定使用哪几张 GPU
CUDA_VISIBLE_DEVICES=0,1,2,3 vllm serve /path/to/model \
--tensor-parallel-size 4

Python 方式

1
2
3
4
5
6
7
8
from vllm import LLM

llm = LLM(
model="/path/to/Llama-3-70B-Instruct",
tensor_parallel_size=4, # 4 卡并行
gpu_memory_utilization=0.90,
max_model_len=8192,
)

3.8 高并发压测脚本

多 GPU 配好后,是时候用数据验证一下 vLLM 的并发能力了。下面这个压测脚本可以直接跑,输出吞吐量和延迟分位数,让你直观感受到 PagedAttention + Continuous Batching 的效果。

用这个脚本模拟并发请求,直观看到 vLLM 的高吞吐能力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# benchmark_concurrent.py
import asyncio
import aiohttp
import time
import json
from typing import List

API_URL = "http://localhost:8000/v1/chat/completions"
MODEL_NAME = "qwen2.5-7b"

TEST_PROMPTS = [
"请用50字介绍人工智能的发展历史。",
"Python中的装饰器是什么,请举例说明。",
"解释一下什么是RESTful API。",
"什么是Docker容器化技术?",
"简述机器学习和深度学习的区别。",
]

async def single_request(session: aiohttp.ClientSession, prompt: str, request_id: int):
"""发送单个请求并记录时间"""
payload = {
"model": MODEL_NAME,
"messages": [{"role": "user", "content": prompt}],
"max_tokens": 200,
"temperature": 0.7,
}

start = time.time()
try:
async with session.post(API_URL, json=payload) as resp:
result = await resp.json()
elapsed = time.time() - start
tokens = result.get("usage", {}).get("completion_tokens", 0)
return {
"id": request_id,
"success": True,
"latency": elapsed,
"tokens": tokens,
"tps": tokens / elapsed if elapsed > 0 else 0,
}
except Exception as e:
return {"id": request_id, "success": False, "error": str(e), "latency": time.time() - start}

async def benchmark(concurrency: int = 10, total_requests: int = 50):
"""并发压测主函数"""
print(f"\n{'='*50}")
print(f"并发数: {concurrency} | 总请求数: {total_requests}")
print(f"{'='*50}")

prompts = [TEST_PROMPTS[i % len(TEST_PROMPTS)] for i in range(total_requests)]
results = []

connector = aiohttp.TCPConnector(limit=concurrency)
async with aiohttp.ClientSession(connector=connector) as session:
# 分批发送,每批 concurrency 个
overall_start = time.time()
semaphore = asyncio.Semaphore(concurrency)

async def limited_request(prompt, req_id):
async with semaphore:
return await single_request(session, prompt, req_id)

tasks = [limited_request(p, i) for i, p in enumerate(prompts)]
results = await asyncio.gather(*tasks)

overall_elapsed = time.time() - overall_start

# 统计结果
successful = [r for r in results if r.get("success")]
failed = [r for r in results if not r.get("success")]
latencies = [r["latency"] for r in successful]
total_tokens = sum(r["tokens"] for r in successful)

if latencies:
latencies.sort()
p50 = latencies[len(latencies) // 2]
p95 = latencies[int(len(latencies) * 0.95)]
p99 = latencies[int(len(latencies) * 0.99)]

print(f"成功请求: {len(successful)} / {total_requests}")
print(f"失败请求: {len(failed)}")
print(f"总耗时: {overall_elapsed:.2f}s")
print(f"总 Tokens: {total_tokens}")
print(f"吞吐量: {total_tokens / overall_elapsed:.1f} tokens/s")
print(f"请求吞吐: {len(successful) / overall_elapsed:.1f} req/s")
print(f"延迟 P50: {p50*1000:.0f}ms")
print(f"延迟 P95: {p95*1000:.0f}ms")
print(f"延迟 P99: {p99*1000:.0f}ms")

if __name__ == "__main__":
# 安装依赖:pip install aiohttp
for concurrency in [1, 5, 10, 20, 50]:
asyncio.run(benchmark(concurrency=concurrency, total_requests=concurrency * 5))
1
2
pip install aiohttp
python benchmark_concurrent.py

3.9 与其他框架性能对比测试

自测脚本跑完有了直觉后,如果需要更标准化的对比数据,可以用 vLLM 官方的 benchmark 工具,复现社区公认的性能基准。

使用 vLLM 官方 benchmark 工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 安装 benchmark 依赖
pip install vllm[benchmarks]

# 使用 vLLM 自带 benchmark 脚本
python -m vllm.entrypoints.openai.api_server \
--model /path/to/model & # 后台启动服务

# benchmark(需要 ShareGPT 数据集)
wget https://huggingface.co/datasets/anon8231489123/ShareGPT_Vicuna_unfiltered/resolve/main/ShareGPT_V3_unfiltered_cleaned_split.json

python benchmarks/benchmark_serving.py \
--backend vllm \
--model qwen2.5-7b \
--dataset-name sharegpt \
--dataset-path ShareGPT_V3_unfiltered_cleaned_split.json \
--request-rate 10 \
--num-prompts 100

四、关键参数详解

基本跑通之后,想要榨干性能、稳定服务就需要精细调参了。vLLM 的参数体系比较完整,这里按用途分组梳理,重点看性能参数和优化参数。

4.1 服务参数

这些是启动服务的基础配置,生产环境必须设置 --api-key,不然任何人都能调用你的服务。

1
2
3
4
5
6
vllm serve <model_path_or_name> \
--host 0.0.0.0 \ # 监听地址,0.0.0.0 允许外部访问
--port 8000 \ # 监听端口
--served-model-name mymodel \ # API 调用时使用的模型名(可与实际名不同)
--trust-remote-code \ # 允许执行模型仓库中的自定义代码(部分模型需要)
--api-key your-secret-key # 设置 API 鉴权 key(生产必须设置)

4.2 性能参数(重点)

这组参数直接决定服务能跑多快、能扛多高并发,也是遇到 OOM 时最先要动的地方。

1
2
3
4
5
6
7
--tensor-parallel-size 2 \     # 多卡张量并行数,必须等于 GPU 数量
--gpu-memory-utilization 0.90 \ # 显存利用率(0.0-1.0),默认 0.9
# 降低可防 OOM,升高可提升并发上限
--max-model-len 8192 \ # 最大序列长度(输入+输出的总 token 数)
# 降低可节省显存,适合显存不足时
--max-num-seqs 256 \ # 最大并发序列数(同时处理的请求上限)
--max-num-batched-tokens 32768 # 每个 batch 最大 token 数,影响吞吐和延迟

4.3 量化参数

如果你加载的是量化模型,或者想控制精度和加载速度,这里的参数需要配合使用。

1
2
3
4
5
--quantization awq \    # 量化方式:awq / gptq / squeezellm / fp8
--dtype auto \ # 数据类型:auto / float16 / bfloat16 / float32
# auto 会自动选择(推荐)
--load-format auto # 模型加载格式:auto / pt / safetensors / npcache
# safetensors 加载速度更快

4.4 优化参数

这组参数是进阶调优的重点,尤其是 --enable-prefix-caching,在 RAG 和多轮对话场景下效果非常显著,强烈建议开启

1
2
3
4
5
--enable-prefix-caching \    # 开启前缀缓存(相同 System Prompt 的请求共享 KV Cache)
# 对 RAG、多轮对话场景有显著提升
--enable-chunked-prefill \ # 开启 Chunked Prefill(将长 Prefill 拆分,减少首 token 延迟)
--swap-space 4 \ # CPU 交换空间(GB),当 GPU 显存不足时将 KV Cache 换出到 CPU
--block-size 16 # KV Cache 物理块大小(token 数),默认 16

4.5 参数调优速查

遇到具体问题不知道动哪个参数?查这张表,对症下药。

问题 调整参数 调整方向
OOM / 显存溢出 --gpu-memory-utilization 降低(如 0.85→0.75)
OOM / 序列太长 --max-model-len 缩短(如 8192→4096)
并发不足 --max-num-seqs 提高
首 token 延迟高 --enable-chunked-prefill 开启
多轮/RAG 场景 --enable-prefix-caching 开启
模型加载慢 --load-format safetensors 切换格式

五、性能对比数据

参数调好后,来看看 vLLM 相比其他框架到底快多少——用数据说话,不靠感觉。以下数据基于公开 Benchmark 报告和社区测试结果,标注为参考值/典型值,实际因硬件、模型、负载不同会有差异。

5.1 vLLM vs 其他框架吞吐量对比

先看最直观的横向对比:同样的硬件、同样的模型,各框架能跑多快?

测试环境:A100 80GB,Llama-2-13B FP16,并发 50,请求参数:输入 512 tokens,输出 256 tokens

框架 吞吐量(tokens/s) P50 延迟 P95 延迟 说明
vLLM ~4200 ~1.2s ~2.1s PagedAttention + Continuous Batching
FastChat ~1800 ~2.8s ~5.2s HuggingFace Transformers 后端
Text-Generation-WebUI ~1200 ~3.5s ~7.0s 单请求处理
Ollama ~900 ~4.2s ~9.0s 面向个人使用,非并发优化
llama.cpp(CPU) ~150 ~25s ~50s CPU 推理,低硬件门槛

结论:高并发场景下 vLLM 吞吐量是 Ollama 的 4-5 倍,是 FastChat 的 2-3 倍。

5.2 不同并发数下的 vLLM 表现

横向比完,再看 vLLM 自身在不同并发压力下的表现曲线——这组数据能帮你判断你的业务量级下 GPU 资源是否被充分利用。

测试环境:RTX 4090 24GB,Qwen2.5-7B-Instruct FP16,输出 200 tokens

并发数 吞吐量(tokens/s) P50 延迟 P95 延迟 GPU 利用率
1 ~150 ~1.3s ~1.5s ~30%
5 ~650 ~1.5s ~2.0s ~65%
10 ~1100 ~1.8s ~2.5s ~85%
20 ~1600 ~2.4s ~3.5s ~95%
50 ~1900 ~4.5s ~7.0s ~98%

规律:并发越高,吞吐量越大(GPU 越充分利用),但单请求延迟也随之增加。

5.3 gpu-memory-utilization 对并发上限的影响

并发曲线看完,再看一个关键调参项:--gpu-memory-utilization 设多少才合适?这组数据能给你一个直观参考。

测试:RTX 4090 24GB,7B FP16 模型

--gpu-memory-utilization 可用 KV Cache 空间 最大并发序列数 说明
0.70 ~4GB ~30 保守,稳定优先
0.85 ~7GB ~60 推荐默认值
0.90 ~9GB ~80 生产推荐
0.95 ~11GB ~100 激进,有 OOM 风险

5.4 量化模型 vs FP16 在 vLLM 上的性能差异

最后一组数据:量化到底损失多少性能?这是很多人在「省显存」和「保精度」之间纠结时最需要的参考。

测试:A100 80GB,Qwen2.5-7B,并发 20

模型精度 显存占用 吞吐量(tokens/s) 精度损失 推荐场景
FP16 ~14GB ~2200 精度要求高
BF16 ~14GB ~2400 极小 A100/H100 首选
AWQ-4bit ~4.5GB ~1900 极小(<1%) 显存受限首选
GPTQ-4bit ~4.5GB ~1700 小(~1-2%) 模型丰富时使用
GPTQ-8bit ~8GB ~2000 极小 精度/显存平衡

六、踩坑记录 & 问题解决

性能虽好,但 vLLM 的部署门槛比 Ollama 高不少,坑也集中在环境配置和显存管理上。以下是实际部署中最高频的问题,遇到直接对号入座。

坑1:Windows 不支持,安装直接报错

症状pip install vllm 在 Windows PowerShell 中报编译错误,提示缺少 Linux 相关头文件。

原因:vLLM 依赖 Linux 特有的 CUDA 接口和内存管理机制,不支持原生 Windows。

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 方案一:使用 WSL2(推荐)
# 1. 安装 WSL2
wsl --install
wsl --set-default-version 2

# 2. 进入 WSL2 后安装 CUDA Toolkit(Ubuntu)
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
sudo apt-get -y install cuda-toolkit-12-4

# 3. 在 WSL2 中安装 vLLM
pip install vllm

# 方案二:Docker(最简单,环境隔离)
docker pull vllm/vllm-openai:latest
# 参考 3.1 节 Docker 命令

坑2:CUDA 版本不匹配导致安装失败

症状pip install vllm 完成但 import vllmCUDA errorundefined symbol

排查步骤

1
2
3
4
5
6
7
8
9
10
11
# 查看系统 CUDA 版本
nvidia-smi | grep "CUDA Version"

# 查看 PyTorch 使用的 CUDA 版本
python -c "import torch; print(torch.version.cuda)"

# 两个版本必须兼容!
# 如果不一致,重装 PyTorch:
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
# 然后重装 vLLM:
pip install vllm --no-build-isolation

坑3:OOM(显存溢出)

症状:启动时报 CUDA out of memory 或运行中突然崩溃。

排查与解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 方法1:降低显存利用率(最直接)
vllm serve model --gpu-memory-utilization 0.75

# 方法2:缩短最大序列长度(释放 KV Cache 空间)
vllm serve model --max-model-len 4096

# 方法3:减少最大并发数
vllm serve model --max-num-seqs 64

# 方法4:换量化模型(最有效,显存直接减半)
vllm serve model-awq --quantization awq

# 方法5:查看实时显存使用
watch -n 1 nvidia-smi

显存需求速查表

模型 FP16 显存 AWQ-4bit 显存
7B ~14GB ~5GB
13B ~26GB ~8GB
34B ~68GB ~20GB
70B ~140GB(需多卡) ~40GB

坑4:模型加载极慢

症状vllm serve 启动后长时间停在 Loading model weights...,超过 5 分钟。

原因与解决

1
2
3
4
5
6
7
8
9
10
11
12
13
# 原因1:模型文件是 .bin 格式(PyTorch 旧格式),加载慢
# 解决:指定使用 safetensors(更快的格式)
vllm serve model --load-format safetensors

# 原因2:模型在机械硬盘上,IO 太慢
# 解决:把模型拷到 SSD

# 原因3:首次加载需要编译 CUDA Kernel
# 解决:首次较慢是正常的,第二次会快很多(有缓存)
# 查看编译缓存:ls ~/.cache/vllm/

# 原因4:模型需要 trust-remote-code 但没加参数
vllm serve model --trust-remote-code

坑5:某些模型不支持(架构未适配)

症状:报 ValueError: Model architectures ['XxxForCausalLM'] are not supported

解决

1
2
3
4
5
6
7
8
# 查看 vLLM 支持的所有模型架构
python -c "from vllm.model_executor.models import ModelRegistry; print(ModelRegistry.get_supported_archs())"

# 检查 vLLM 版本,新版本支持更多模型
pip install --upgrade vllm

# 查看官方支持模型列表
# https://docs.vllm.ai/en/latest/models/supported_models.html

坑6:流式输出卡顿或中断

症状stream=True 时,输出一半就停了,或者 chunk 很久才来一个。

排查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 检查是否有超时设置
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="not-needed",
timeout=120.0, # 增加超时时间
)

# 确认 stream 参数生效
stream = client.chat.completions.create(
model="qwen2.5-7b",
messages=[...],
stream=True,
stream_options={"include_usage": True}, # vLLM >= 0.4.0 支持
)

# 检查服务端日志
# 流式输出中断通常是网络中间件(Nginx)缓冲导致
# Nginx 配置添加:
# proxy_buffering off;
# proxy_cache off;

七、场景适配 & 优劣分析

了解了 vLLM 的能力边界和常见坑点后,最后做个收尾——什么时候该用 vLLM,什么时候换其他方案,用决策逻辑而不是感觉来选型。

7.1 什么时候该用 vLLM

vLLM 的最佳战场

场景 说明
高并发 API 服务 10+ 并发请求,vLLM 吞吐量远超其他方案
企业级生产部署 需要稳定性、监控、OpenAI 兼容接口
资源最大化利用 贵的 GPU(A100/H100),不能让它闲着
多用户场景 团队共用模型服务器,多人同时调用
RAG / 知识库场景 prefix caching 大幅降低重复 System Prompt 的开销

7.2 什么时候不该用 vLLM

知道该用的场景,同样重要的是知道不该强行上 vLLM 的场景,避免用锤子找钉子。

场景 推荐替代 原因
CPU 推理 llama.cpp vLLM 只支持 NVIDIA GPU
低显存设备(<8GB) llama.cpp / Ollama 最低显存要求较高
Windows 原生 Ollama vLLM 不支持 Windows
快速个人测试 Ollama vLLM 环境配置复杂
调试/可视化 Text Generation WebUI vLLM 无 GUI

7.3 框架终极对比

适用场景有了感知后,再用一张完整的横向对比表,把系列里出现的所有框架放在一起,一眼看出各自的位置。

维度 Ollama llama.cpp Text-Gen-WebUI FastChat vLLM
上手难度 ⭐(最简单) ⭐⭐⭐ ⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
高并发性能 ⭐⭐ ⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐
显存利用率 ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐
Windows 支持 ❌(需 WSL2)
CPU 推理 ⚠️
OpenAI 兼容 ⚠️
生产就绪 ⭐⭐⭐ ⭐⭐ ⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

7.4 什么时候该从 Ollama 迁移到 vLLM

很多团队是从 Ollama 起步的,迁移到 vLLM 是个很常见的演进路径。下面给出明确的迁移触发条件,不需要主观判断,对号入座就行。

以下任意一条成立,就该考虑迁移:

  1. 并发用户数 持续超过 5 人
  2. 接口 P95 延迟 超过业务容忍上限(通常 3-5 秒)
  3. GPU 利用率 低于 60%(资源浪费)
  4. 业务要求 精确的 Token 级计费或限流
  5. 需要同时管理 多个版本模型的灰度发布

迁移成本低:vLLM 完全兼容 OpenAI 接口,代码层面只需把 base_url 从 Ollama 的端口改为 vLLM 的端口,业务代码零修改。


八、本篇小结

跑完这一套下来,对 vLLM 的掌握程度应该已经从「听说过」升级到「能用、会调、会讲原理」了。这里做一个系统性的收尾,方便后续快速复习。

8.1 核心知识点复盘

知识点 核心要点
PagedAttention 操作系统分页内存管理思想 → 按需分配 KV Cache 物理块,显存利用率从 30% → 90%
Continuous Batching 请求完成立即补位,GPU 永远满载,对比静态批处理吞吐量提升 2-5x
量化支持 AWQ 精度损失最小,GPTQ 模型最丰富,FP16/BF16 精度最高
多卡并行 --tensor-parallel-size N,模型张量切分到 N 张 GPU
前缀缓存 --enable-prefix-caching,相同 System Prompt 共享 KV Cache,RAG 必开

8.2 命令速查表

知识点复盘完,这里把全文最常用的命令集中整理,方便后续使用时快速查找,不用再翻全文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 安装
pip install vllm

# 基础启动
vllm serve <model> --host 0.0.0.0 --port 8000

# 生产启动(完整参数)
vllm serve <model> \
--host 0.0.0.0 --port 8000 \
--served-model-name mymodel \
--gpu-memory-utilization 0.90 \
--max-model-len 8192 \
--max-num-seqs 256 \
--enable-prefix-caching \
--trust-remote-code

# AWQ 量化模型
vllm serve <awq-model> --quantization awq

# 多卡并行
vllm serve <model> --tensor-parallel-size 2

# Docker 方式
docker run --gpus all -p 8000:8000 -v /models:/models \
vllm/vllm-openai:latest --model /models/<model-name>

8.3 PagedAttention 面试回答模板

命令速查备用,最后是这篇文章含金量最高的部分——PagedAttention 的面试标准答案。无论是 30 秒快问还是 2 分钟深聊,下面两个版本都能覆盖。

30 秒版(适合简历筛选、电话面试)

PagedAttention 是 vLLM 提出的 KV Cache 管理机制,核心思想借鉴操作系统的虚拟内存分页管理。传统方案会为每个请求预先分配一大块连续显存存 KV Cache,导致大量空间浪费,显存利用率只有 20-40%。PagedAttention 将 KV Cache 切成固定大小的物理块,按需分配,用一个映射表维护逻辑地址到物理块的对应关系,使显存利用率提升到 90% 以上,从而让同等显存能服务更多并发请求,整体吞吐量提升 2-4 倍。

2 分钟版(适合技术面试深问)

PagedAttention 解决的核心问题是:LLM 推理时 KV Cache 占用显存的低效问题。

背景:大模型自回归生成时,需要缓存所有历史 token 的 Key/Value 矩阵(即 KV Cache)。传统实现中,每个请求在启动时就预分配一块连续的最大显存空间(比如 2048 token 对应的空间),但实际上很多请求可能只生成 200 token 就结束了,剩余空间完全浪费。加上请求结束后释放的碎片化空间,整体利用率只有 20-40%。

PagedAttention 的解决思路:把 KV Cache 切分成固定大小的物理块(默认 16 token/块),类似操作系统的内存页。每个请求维护一张逻辑块号→物理块号的映射表(类似页表),按需申请物理块,请求生成 16 个 token 才申请一个新块,不提前占用。此外,有相同前缀(如相同 System Prompt)的多个请求可以共享同一组物理块,进一步减少显存占用。

效果:显存利用率从 20-40% 提升到 90% 以上,同等显存下并发请求数大幅提升,配合 Continuous Batching(请求完成立即补位),整体吞吐量提升 2-4 倍,这是 vLLM 在高并发场景下碾压其他框架的根本原因。


8.4 选型决策树

面试模板之后,用这棵决策树给本篇画上句号:拿到任何一个部署需求,顺着分支往下走,终点就是最合适的方案。

1
2
3
4
5
6
7
8
有 GPU 且需要对外提供 API 服务?
├── 否 → Ollama(个人使用)/ llama.cpp(CPU 或低显存)
└── 是
├── Windows 原生且不想配 WSL2 → Ollama / FastChat
├── 并发 < 5,快速搭建 → Ollama
├── 需要 Web UI 界面 → Text Generation WebUI
├── 多模型管理 + 团队使用 → FastChat
└── 高并发 + 性能优先 + 生产环境 → vLLM ✅

本文为「本地大模型部署系列」第8篇,完整系列持续更新中。