1. 为什么我劝你先搞清楚 vLLM 到底在解决什么问题
很多人第一次接触 vLLM,是被"高吞吐""PagedAttention""显存利用率翻倍"这些词吸引过来的,结果装完之后发现——模型是跑起来了,但显存占用比预期高一大截,并发一上来就 OOM,或者启动参数调了半天吞吐反而更差。问题不在于 vLLM 不好用,而在于大多数人跳过了"它到底在解决什么"这一步,直接抄命令,自然踩坑。
vLLM 的核心价值,说白了就一件事:让同一张显卡在推理时同时服务更多请求,并且把显存浪费降到最低。传统推理框架在处理变长序列时,会为每个请求预分配一块连续的 KV Cache 显存,按最大长度来算。这就导致两个问题:一是短请求浪费了大量预留空间,二是显存碎片化严重,能塞下的并发数远低于理论值。vLLM 用 PagedAttention 把 KV Cache 切成固定大小的块(block),像操作系统管理内存页一样按需分配,显存利用率能从常见的 20%~40% 拉到 80% 以上。
这个机制带来的直接好处是:同样的卡,吞吐能翻几倍。但代价是——它对显存的计算方式和你直觉里的"模型多大就占多大"完全不是一回事。模型权重、KV Cache、激活值、框架自身开销,这几块要分开算,而且 KV Cache 的大小跟你的max_model_len、gpu_memory_utilization、并发数强相关。这就是为什么"安装启动"看起来简单,"显存调优"才是真正拉开差距的地方。
这篇文章面向的是准备在自己机器或服务器上从零把 vLLM 跑起来的人:不管你是想部署一个本地对话服务,还是想拿它做批量推理,或者是想对比它和 Ollama、LM Studio 这类工具的差异。我会把安装、启动、显存调优这条链路完整走一遍,重点讲清楚每个参数背后的账是怎么算的,以及我在实际部署中踩过的那些坑。
需要先说明一点:vLLM 的主力运行环境是 Linux,官方对 Windows 的原生支持一直比较有限,社区虽然有 Windows 相关的尝试,但生产环境我还是强烈建议走 Linux 或者容器。下面涉及路径、命令的地方,我会以 Linux 为主,Windows 用户走 WSL2 或容器基本能对应上。
2. 安装前的环境盘点:别急着 pip install
2.1 先确认你的卡和驱动撑不撑得住
vLLM 对硬件是有门槛的,不是随便一张卡就能跑。核心要求是CUDA 计算能力 7.0 及以上的 NVIDIA 显卡。翻译成人话:V100、T4、A10、A100、H100、RTX 20 系及以后的消费卡基本都行,但像 GTX 10 系(Pascal 架构,计算能力 6.x)就不在官方支持范围内,强行跑大概率报错或者性能惨不忍睹。
驱动和 CUDA 版本这块,是新手最容易翻车的地方。vLLM 的 wheel 包在编译时绑定了特定的 CUDA 版本,你本地的驱动必须能兼容它。判断方法很简单,先看驱动:
nvidia-smi右上角会显示CUDA Version: 12.x,注意这个数字是驱动支持的最高 CUDA 版本,不是你已经安装的 CUDA 版本。只要这个数字大于等于 vLLM 要求的版本,通常就没问题。比如你要装的是基于 CUDA 12.1 编译的 vLLM,驱动显示 12.4,那可以直接用,不需要单独去装 CUDA Toolkit。
这里有个常见误区:很多人以为必须装完整版 CUDA Toolkit 才能跑 vLLM。其实不是。vLLM 的预编译 wheel 里已经带了所需的 CUDA 运行时库,你只要有匹配的驱动就行。只有当你需要从源码编译,或者要自己编译某些算子时,才需要完整的 Toolkit。
提示:如果你用的是较新的卡(比如 RTX 40 系、H100),驱动尽量更新到比较新的版本,老驱动配新卡经常出现算子不兼容的问题。
2.2 Python 环境和依赖的取舍
vLLM 官方推荐Python 3.9 到 3.12。我个人的经验是优先选 3.10 或 3.11,这两个版本在各类依赖的兼容性上最稳。3.12 也能用,但偶尔会遇到某些依赖还没跟上。
环境隔离这件事必须做,别往系统 Python 里直接装。用 conda 或者 venv 都行:
conda create -n vllm python=3.11 -y conda activate vllm或者用 venv:
python3.11 -m venv vllm-env source vllm-env/bin/activate为什么要隔离?因为 vLLM 会锁定一批特定版本的依赖,比如torch、transformers、xformers这些。如果你系统里已经装了别的版本,pip 解析依赖时可能给你降级或升级,把别的项目搞崩。隔离环境是最省心的做法。
还有一个容易被忽略的点:pip 版本要够新。老版本 pip 在解析 vLLM 这种依赖复杂的包时,可能选错版本或者卡在依赖回溯上。先升级:
pip install --upgrade pip2.3 安装方式的选择:pip 还是容器
vLLM 提供两种主流安装方式,各有适用场景。
pip 直接安装适合快速验证和开发调试:
pip install vllm这条命令会拉取最新稳定版。如果你想指定版本,比如避开某个有 bug 的版本:
pip install vllm==0.6.3容器方式适合生产部署,尤其是需要固定环境、多机一致的场景。官方镜像在 Docker Hub 上:
docker run --gpus all -it --rm \ -v ~/.cache/huggingface:/root/.cache/huggingface \ vllm/vllm-openai:latest容器方式的好处是环境完全隔离,不用担心宿主机 Python 被污染,也不用操心 CUDA 版本匹配。坏处是镜像体积大(几个 GB),首次拉取慢,而且挂载模型缓存目录这一步如果忘了,每次重启都要重新下载模型。
我自己的习惯是:开发调试用 pip,正式部署用容器。下面显存调优的部分两种方式都适用,只是参数传递形式不同。
3. 启动服务:从一条命令到一套可用配置
3.1 最小启动命令长什么样
装完之后,最简启动方式是用 vLLM 自带的 OpenAI 兼容服务:
vllm serve Qwen/Qwen2.5-7B-Instruct这条命令会做几件事:从 Hugging Face 下载模型(如果本地没有缓存)、加载权重到显存、启动一个监听 8000 端口的 HTTP 服务,接口格式和 OpenAI 的 API 兼容。启动成功后,你可以直接用 curl 测试:
curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Qwen/Qwen2.5-7B-Instruct", "messages": [{"role": "user", "content": "你好"}] }'看起来很简单,但这条命令背后有一堆默认参数在起作用,而这些默认值往往不是最优的。比如默认gpu_memory_utilization是 0.9,意味着 vLLM 会尝试占用 90% 的显存;默认max_model_len会取模型配置里的最大值,可能是 32768 甚至更长。这两个默认值组合起来,在小显存卡上很容易直接 OOM。
3.2 几个必须理解的启动参数
启动参数决定了服务的行为和资源占用,我把最关键的几个拆开讲。
--model:模型路径或 Hugging Face 上的模型 ID。可以是本地目录,也可以是仓库名。如果模型需要登录才能下载(比如某些 gated model),要先huggingface-cli login。
--max-model-len:模型能处理的最大序列长度(输入+输出)。这个参数直接决定 KV Cache 的上限。设得越大,单请求能处理的上下文越长,但显存占用也越高。很多人不管三七二十一设成模型支持的最大值,结果显存不够。正确做法是按你的实际业务需求设——如果你的对话场景平均上下文就 4K,那设 8192 完全够用,没必要设 32768。
--gpu-memory-utilization:vLLM 允许占用的显存比例,默认 0.9。这个值不是越大越好。留一点余量给激活值波动和框架开销是必要的。显存紧张的卡上,我一般会降到 0.85 甚至 0.8 来换取稳定性。
--tensor-parallel-size:张量并行度,多卡部署时用。单卡设 1,双卡设 2,以此类推。注意这个值必须能整除模型的注意力头数,否则会报错。
--dtype:权重加载的数据类型,可选auto、half、float16、bfloat16。默认auto会读模型配置。如果你的卡支持 bfloat16(Ampere 架构及以后),优先用它,数值稳定性比 float16 好。
--max-num-seqs:同时处理的最大请求数。这个参数和 KV Cache 大小互相制约,后面调优部分会详细讲。
一个相对稳妥的启动命令大概长这样:
vllm serve Qwen/Qwen2.5-7B-Instruct \ --max-model-len 8192 \ --gpu-memory-utilization 0.85 \ --dtype bfloat16 \ --max-num-seqs 64 \ --port 80003.3 模型下载慢和缓存管理
国内环境下从 Hugging Face 直接下载模型经常很慢甚至超时。解决办法是配置镜像源,通过环境变量指定:
export HF_ENDPOINT=https://hf-mirror.com这个变量在启动 vLLM 前设置好,下载就会走镜像。已经下载过的模型会缓存在~/.cache/huggingface/hub下,下次启动直接读缓存,不再联网。
如果你想把缓存放到别的盘(比如系统盘空间不够),设置:
export HF_HOME=/data/huggingface这样缓存、配置都会落到指定目录。容器部署时记得把这个目录挂载进去,否则容器一删缓存就没了,下次还得重下。
注意:模型下载中断后重新下载,有时候会因为残留的临时文件导致校验失败。遇到这种情况,把对应模型目录下的
blobs和snapshots里不完整的部分清掉再重试,比整个删掉重下省时间。
4. 显存调优:把每一 MB 都算清楚
4.1 显存到底被谁吃掉了
调优的前提是知道显存花在哪。vLLM 运行时,显存主要被四部分占用:
| 占用项 | 说明 | 是否可控 |
|---|---|---|
| 模型权重 | 参数量 × 每参数字节数 | 由模型和 dtype 决定 |
| KV Cache | 缓存注意力键值,随序列长度和并发增长 | 强可控,调优主战场 |
| 激活值 | 前向计算中间结果 | 部分可控,随 batch 波动 |
| 框架开销 | CUDA context、通信缓冲等 | 基本固定 |
先算模型权重。以 7B 模型为例,bfloat16 每个参数 2 字节,权重约 14GB。如果是 13B 模型,约 26GB。这一步是硬开销,省不掉(除非用量化)。
然后是 KV Cache。它的计算公式是:
KV Cache 大小 = 2 × 层数 × 注意力头数 × 头维度 × 序列长度 × 并发数 × 每元素字节数其中 2 是因为要存 Key 和 Value 两份。以 Qwen2.5-7B 为例,28 层,28 个注意力头(GQA 下 KV 头数是 4),头维度 128。假设序列长度 8192,并发 64,bfloat16:
2 × 28 × 4 × 128 × 8192 × 64 × 2 字节 ≈ 30GB这个数字很吓人。也就是说 7B 模型权重 14GB 加上 KV Cache 30GB,已经 44GB 了,一张 48GB 的卡勉强够,24GB 的卡根本放不下。这就是为什么max_model_len和max_num_seqs不能乱设——它们直接乘进 KV Cache 的公式里。
4.2 gpu_memory_utilization 的真实含义
很多人以为--gpu-memory-utilization 0.9是"用 90% 显存",这个理解只对了一半。它的准确含义是:vLLM 会按这个比例预留显存,其中一部分给权重,剩下的全部划给 KV Cache。
vLLM 启动时会先加载权重,然后根据剩余显存和这个比例,反推能分配多少 block 给 KV Cache。如果算下来 KV Cache 的 block 数不够支撑你设的max_num_seqs,它会启动失败并提示你降低参数。
所以调这个参数的逻辑是:显存越紧张,这个值越要留余量。设 0.9 意味着只剩 10% 给激活值和框架开销,遇到长序列或者大 batch,激活值一涨就 OOM。我一般这样设:
- 显存充裕(权重占比 < 50%):0.9
- 显存中等(权重占比 50%~70%):0.85
- 显存紧张(权重占比 > 70%):0.8 或更低
4.3 用 max_model_len 和 max_num_seqs 做平衡
这两个参数是显存调优的核心杠杆,它们的关系是此消彼长。
max_model_len决定单请求能有多长,max_num_seqs决定同时能跑多少请求。两者相乘大致决定了 KV Cache 的峰值需求。在显存固定的情况下,你要根据业务特点做取舍:
- 长文本场景(文档问答、长对话):优先保
max_model_len,把max_num_seqs降下来。比如设max_model_len=16384、max_num_seqs=16。 - 高并发短请求场景(分类、短问答):优先保
max_num_seqs,max_model_len设小一点。比如max_model_len=2048、max_num_seqs=256。
怎么知道设多少合适?vLLM 启动日志里会打印 KV Cache 的 block 数和能支持的最大并发,这是最直接的参考。启动后看日志里类似这样的输出:
GPU KV cache size: 100,000 tokens Maximum concurrency for 8,192 tokens per request: 12.21x这个Maximum concurrency就是当前配置下理论能并发的请求数。如果你的max_num_seqs设得比它大,实际会被这个上限卡住。
4.4 量化:显存不够时的救命稻草
当模型权重本身就占满了显存,再怎么调 KV Cache 也没用,这时候只能上量化。vLLM 支持多种量化方案,常用的有:
- AWQ:4bit 权重量化,精度损失小,适合大多数场景
- GPTQ:同样是 4bit,生态成熟,模型选择多
- FP8:8bit,需要 Hopper 或 Ada 架构的卡支持
以 7B 模型为例,bfloat16 权重 14GB,换成 AWQ 4bit 后约 4GB,直接省出 10GB 给 KV Cache。代价是精度略有下降,但在大多数对话和问答任务上感知不明显。
使用量化模型很简单,直接指定量化后的模型 ID 即可:
vllm serve Qwen/Qwen2.5-7B-Instruct-AWQ \ --quantization awq \ --max-model-len 8192提示:量化不是万能的。如果你的任务对数值精度极其敏感(比如某些数学推理、代码生成),4bit 量化可能带来可感知的质量下降。这种情况优先考虑换更大显存的卡,而不是硬上量化。
5. 实测中那些文档不会告诉你的坑
5.1 启动卡在加载权重不动
第一次启动时,如果卡在 "Loading model weights" 很久没反应,八成是在下载模型。这时候别急着 Ctrl+C,先看网络。如果确认是下载慢,按前面说的配镜像源。如果模型已经下载过还卡住,可能是缓存损坏,清掉重下。
还有一种情况是显存不足导致加载失败,但报错信息不明显。这时候把--gpu-memory-utilization调低,或者先用一个更小的模型验证环境是否正常。
5.2 并发一上来就 OOM
这是最典型的显存调优问题。表现是单请求正常,压测时显存飙升然后进程被杀。原因通常是max_num_seqs设得太大,或者max_model_len设得超过了实际需求。
排查思路:先看启动日志里的Maximum concurrency,把max_num_seqs设成不超过这个值。然后观察实际运行时的显存曲线,如果峰值接近上限,继续往下调。别指望一次调到位,这是个迭代过程。
5.3 多卡部署时的张量并行陷阱
多卡跑的时候,--tensor-parallel-size必须和实际使用的卡数一致,而且这个值要能整除模型的注意力头数。比如模型有 28 个头,你设tensor-parallel-size=8就会报错,因为 28 不能被 8 整除。这种情况要么改成 4 或 7,要么换模型。
另外多卡之间的通信开销不可忽略。如果卡之间不是 NVLink 而是走 PCIe,张量并行的加速比会打折扣,有时候还不如单卡跑小模型。
5.4 容器里跑找不到 GPU
容器部署时最常见的错误是docker run忘了加--gpus all,或者宿主机没装 NVIDIA Container Toolkit。前者加上参数即可,后者需要先安装 toolkit 并重启 docker 服务。验证方法是进容器后跑nvidia-smi,能正常输出就说明 GPU 透传成功。
还有一个隐蔽的坑:容器里的 CUDA 版本和宿主机驱动不匹配。虽然容器自带 CUDA 运行时,但它仍然依赖宿主机的驱动。如果宿主机驱动太老,容器里的新版本 CUDA 会报 "CUDA driver version is insufficient"。解决办法是升级宿主机驱动,而不是在容器里折腾。
6. 把服务真正用起来:接口调用与监控
6.1 OpenAI 兼容接口的实用细节
vLLM 的 OpenAI 兼容接口基本可以直接替换 OpenAI 的调用。Python 客户端这样写:
from openai import OpenAI client = OpenAI( base_url="http://localhost:8000/v1", api_key="EMPTY" # vLLM 默认不校验,随便填 ) resp = client.chat.completions.create( model="Qwen/Qwen2.5-7B-Instruct", messages=[{"role": "user", "content": "解释一下 PagedAttention"}], temperature=0.7, max_tokens=512 ) print(resp.choices[0].message.content)几个实用参数:stream=True开启流式输出,适合对话场景;max_tokens控制生成长度,注意它和max_model_len的关系是"输入长度 + max_tokens ≤ max_model_len";stop可以指定停止词。
6.2 用 metrics 观察服务状态
vLLM 默认在/metrics端点暴露 Prometheus 格式的指标,包括请求数、延迟、KV Cache 使用率、运行中的请求数等。这些指标对调优非常有用。
比如vllm:gpu_cache_usage_perc反映 KV Cache 的实时占用率。如果它长期接近 100%,说明显存吃紧,该考虑降并发或者上量化了。如果长期很低,说明资源浪费,可以适当提高max_num_seqs提升吞吐。
配合 Grafana 可以搭一个简单的监控面板,把吞吐、延迟、显存占用画出来,调优时心里有数。这套组合在生产环境基本是标配。
6.3 和 Ollama、LM Studio 的定位差异
经常有人问该用 vLLM 还是 Ollama。简单说:Ollama 和 LM Studio 面向个人本地使用,开箱即用,但吞吐和并发能力有限;vLLM 面向服务和批量场景,配置复杂但性能强得多。
如果你只是自己一个人偶尔跑跑模型,Ollama 更省事。如果你要对外提供服务、要处理并发请求、要做批量推理,vLLM 是更合适的选择。两者不是替代关系,而是不同场景的工具。
我在实际部署中的体会是,vLLM 的学习曲线主要集中在前期的显存调优上,一旦把参数摸清楚,后面就非常稳定。真正花时间的不是安装,而是理解"为什么这个参数要这么设"。把 KV Cache 的计算逻辑搞明白,大部分 OOM 和性能问题都能自己定位。最后分享一个小技巧:调参时把max_model_len和max_num_seqs先设保守值跑通,再逐步往上加,每次只动一个参数并观察日志和监控,比一次性堆参数然后反复试错高效得多。