☰
vLLM实战指南:从安装到显存调优,轻松部署大模型推理服务
2026/10/7 23:39:55 网站建设 项目流程

模型部署最让人头疼的几件事,其实翻来覆去就那么几个:安装半天装不上、启动起来就闪退、显存莫名其妙爆掉。而围绕vLLM把这些点理顺,基本就解决了一多半问题。这篇文章就是带着你从零开始,把vLLM的安装、启动和显存调优完整过一遍,中间穿插我自己踩过的坑和实测数据,看完能直接上手。

先说这内容适合谁:手里有一块6GB以上显存的NVIDIA显卡、想把开源模型跑起来做接口服务或者本地验证的人,以及已经能跑起来但经常OOM、想优化吞吐和显存占用的同学。vLLM是目前开源推理框架里速度最快的那一档,核心卖点是PagedAttention和Continuous Batching,这两个机制下面细讲。准备工作不复杂,但顺序错了会很痛苦,所以先别急着输命令。

1. 动手之前,先把环境盘清楚

1.1 硬件与驱动门槛:先看这三样

vLLM和普通Python库不一样,它底层直接吃CUDA,对显卡、驱动、Python版本的要求都比较具体。我见过太多人上来就pip install vllm,装倒是装成功了,一启动就报错,回头一看驱动是老的,白折腾。

开始之前先确认三件事:

  • 显卡型号和显存大小。NVIDIA的卡最省心,Ampere架构(30系、A100、A10等)和Ada架构(40系、L20、L40等)支持最完整。如果只有6GB显存,能跑7B模型,但序列长度和并发要做限制;12GB以上体验会舒服很多。
  • 驱动版本。直接在终端跑nvidia-smi看右上角的CUDA Version,这个不是说你装了CUDA工具包,而是指驱动支持的最高CUDA版本。vLLM的预编译包通常要求驱动支持CUDA 12.1或12.4以上,建议驱动不要太老。
  • 操作系统的环境。Linux是最顺的;Windows用户建议直接走WSL2,微软官方支持的子系统,不用双系统,也不用担心VMware显卡透传这些问题。

我个人的建议是先跑一遍nvidia-smi和nvcc --version,把结果截图或者写下来。很多问题排查到最后,根源就是这里不对齐。注意nvidia-smi里的CUDA Version和nvcc -V显示的版本可能不一样,前者是驱动带的,后者是工具包安装的,vLLM编译和运行主要看前者。

1.2 Python虚拟环境:这一步千万别省

vLLM的依赖锁得比较死,如果直接装进系统Python,很容易把torch、transformers这些包搞得乱七八糟。你想象一下,电脑上跑了三个项目,一个要torch 2.1,一个要torch 2.4,全塞在一起,今天装一个库把另一个项目搞挂是常有的事。所以用虚拟环境是铁律,推荐用conda,或者Python自带的venv也行。

conda create -n vllm python=3.10 -y conda activate vllm

Python版本建议3.9到3.12,3.10和3.11最稳。我实测下来3.10踩坑最少,碰到某些编译报错的概率低一些。装完后顺手把pip升级一下,避免旧pip解析依赖时脑溢血。

python -m pip install --upgrade pip

这里还隐藏着一个容易忽略的点:先别急着装vLLM,先确认你的torch环境是不是干净的。有些机器上已经装了其他版本torch,vLLM安装时检测到会提示冲突或直接降级,这时候干脆把虚拟环境删了重建,别省那一两分钟。新建虚拟环境里没有任何torch是最干净的状态。

2. 安装vLLM:pip、源码编译与Windows实战

2.1 pip安装:五分钟内跑通

如果你的显卡型号不算太老,系统是Linux或者WSL2,pip install vllm是最快的一条路。它会自动拉取对应的预编译wheel,把torch、transformers、xformers这一串依赖一并装好,整个流程一般在五分钟左右,取决于下载速度。

pip install vllm

装完验证一下,这一步必须做:

python -c "import vllm; print(vllm.__version__)"

如果正常打印出版本号,比如0.7.2,安装这关就过了。没有报错不代表一定能用,所以紧接着看一眼GPU是否可见:

python -c "import pynvml; pynvml.nvmlInit(); print(pynvml.nvmlDeviceGetName(pynvml.nvmlDeviceGetHandleByIndex(0)))"

这条能打印出显卡名称,说明pynvml和驱动的通路没问题。很多装完导入不报错、一推理就报CUDA driver version is insufficient的人,就是GPU这一层没确认。

需要注意的点:pip安装的版本是预编译好的,CPU指令集、CUDA运行时和你本机的匹配程度有限。如果你的GPU太新而wheel里的CUDA版本偏旧,可能会出现no kernel image available for execution on the device。遇到这种情况,往下看编译安装,或者升级pip源里的vLLM版本。还有一个常见坑是下载速度慢导致超时,可以给pip配一个镜像源,这属于常规操作:

pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

2.2 源码编译安装:适合谁的折腾方案

如果你需要最新特性、或者你的显卡架构比较新、又或者你想跑自定义模型结构,源码编译是绕不开的。它的本质是在你机器上现场编译CUDA算子,所以耗时久,但对硬件的适配是最贴合的。

git clone https://github.com/vllm-project/vllm.git cd vllm pip install -e .

编译前务必备一份依赖清单,手动先装好torch和对应的CUDA toolkit。我踩过的一个坑是直接跑pip install -e .,编译到一半报缺少ninja,虽然pip会自动装,但某些环境下ninja版本很旧导致编译任务调度有问题。建议提前单独装:

pip install ninja cmake wheel

编译过程视机器性能而定,十几分钟到四十分钟都有可能。看到Successfully installed vllm就结束了。编译过程中我最常用的一条命令是nvidia-smi,因为编译时GPU占用虽然不高,但CPU和内存会吃很多,最好中途别开太多任务。

源码编译的隐藏风险是环境变量没配对。CUDA_HOME、PATH里的nvcc都要正确指向同一个CUDA版本。如果机器上装了好几个CUDA,很容易出现编译用的nvcc是11.8,运行时驱动匹配却是12.4的情况,最后报错找半天也不知道哪里不对。所以编译前先执行which nvcc和echo $CUDA_HOME确认统一。

2.3 Windows用户到底怎么办

官方对Windows原生支持很有限,这是vLLM的老大难问题。如果你想直接在Windows命令行里装,大概率会碰壁,因为很多算子需要Linux下的GCC和CUDA工具链配合编译,Windows下不是不行,但坑极多,我不建议普通用户尝试。

最省心的方案是装WSL2,在WSL里用Ubuntu 22.04或者24.04,然后按照Linux的流程安装即可。WSL2的显卡驱动是直接共享Windows驱动的,所以只要Windows下的nvidia-smi能看到显卡,WSL里也能看到,不需要额外装驱动。注意在WSL里装CUDA工具包时,选择WSL版本的安装包,而不是原生的Linux版本。

至于有些社区分享的Windows原生安装方法,能不能用?据我了解有一部分情况能跑通,但问题是性能损耗和依赖冲突远超出预期,而且出了问题很难在官方库找到答案。我自己的经验是:如果有条件,直接在Linux真机或者云GPU实例上跑;如果本地是Windows,就老老实实用WSL2,时间和心情都能省不少。

2.4 安装完成后的自检清单

装完不是直接跑模型,先按下面几步快速自检,可以规避掉后面80%的启动问题:

  • vllm --help能不能正常输出参数列表。
  • python -c "import vllm; print(vllm.__version__)"版本是否正常。
  • 执行一次极小模型的推理测试,比如从局域网你能访问到的模型仓库下载一个几百MB的小模型,或者用本地已有的模型文件跑通一次完整生成。
  • 观察日志里有没有CUDA相关警告,比如torch not compiled with CUDA enabled。

很多人在第一步自检就翻车,原因多半是torch和CUDA版本不匹配。检查方法是:在Python里执行import torch; print(torch.cuda.is_available()),如果返回False,说明torch装成了CPU版,需要重新安装对应的CUDA版torch。这个不用想太复杂,直接按vLLM要求的torch版本来就好。

3. 启动推理:从命令行到Python调用

3.1 OpenAI兼容接口:一条命令把服务跑起来

vLLM最方便的地方,是它自带OpenAI兼容的HTTP服务。这意味着你不需要写任何Web框架代码,也不用理解FastAPI内部逻辑,一条命令就能把模型变成一个标准的API服务,然后像调用OpenAI接口一样调用它。

以社区常见的Qwen/Qwen2.5-7B-Instruct为例:

vllm serve Qwen/Qwen2.5-7B-Instruct \ --port 8000 \ --gpu-memory-utilization 0.85 \ --max-model-len 8192 \ --dtype bfloat16

启动后看到类似INFO: Application startup complete和Uvicorn running on http://0.0.0.0:8000,就说明服务起来了。第一次启动会比较慢,因为要下载模型文件、加载权重、构建KV Cache。当你看到Finished loading the model这一行日志,恭喜,模型已经加载到显存里了。

这时候用curl验证一下接口是否真的可用:

curl http://127.0.0.1:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Qwen/Qwen2.5-7B-Instruct", "messages": [{"role": "user", "content": "你好,简要介绍一下你自己"}], "max_tokens": 256 }'

返回里有choices和content字段,说明接口完全正常。这一套流程跑通后,你想部署DeepSeek的蒸馏版模型,比如社区里很火的DeepSeek-R1-Distill-Qwen-7B,就是把模型路径换一下的事,其余参数和不一致的地方基本不用动。

3.2 启动参数逐句拆解

你会发现启动命令里那串参数每个都在起作用,我挑几个最有影响的展开说。

--gpu-memory-utilization是最关键的显存控制参数。它的含义是“vLLM最多占用多大比例的显存”,默认值是0.9。比如一张16GB的显卡,0.85意味着vLLM预留约13.6GB可用,除了放模型权重,还要给KV Cache留空间。调太低了浪费显存,调太高了容易和其他的显存占用冲突,导致启动时直接OOM。这个参数我们下一章细讲。

--max-model-len控制的是模型能处理的最大上下文长度。它的大小直接决定KV Cache的分配上限。上下文越长,同样一块显存能同时处理的请求数就越少。如果不知道模型训练时的上下文窗口,可以先设成4096试跑,确认无误再往上加。

--dtype指定权重精度,常用的是float16和bfloat16。30系和40系显卡对bfloat16支持都很好,但不同型号表现略有差异,如果遇到数值异常,换回float16试试。老一点的显卡比如20系,bfloat16支持得不好,建议直接用float16。

还有个参数--tensor-parallel-size,多卡用户会用到。它把模型切分到多张显卡上并行推理。两张卡就设2,四张卡就设4,但前提是卡之间的NVLink或者PCIe带宽够,否则通信开销可能抵消并行收益,甚至更慢。

3.3 Python调用:流式输出与原生接口

服务跑起来之后,实际业务代码里一般用OpenAI SDK来对接。先装一下:

pip install openai

然后写一个流式对话脚本:

from openai import OpenAI client = OpenAI( base_url="http://127.0.0.1:8000/v1", api_key="EMPTY", ) response = client.chat.completions.create( model="Qwen/Qwen2.5-7B-Instruct", messages=[{"role": "user", "content": "给我讲一个数据库索引的小故事"}], stream=True, ) for chunk in response: delta = chunk.choices[0].delta.content if delta: print(delta, end="", flush=True)

为什么推荐流式?因为大模型生成是逐token的,一段几百字的回答在非流式模式下要等全部生成完才返回,用户体验就是“卡住十几秒没反应”,流式则能像打字机一样一个字一个字蹦出来,体感好得多。

如果你不想走HTTP,也可以直接在Python进程里调用vLLM的原生接口:

from vllm import LLM, SamplingParams llm = LLM( model="Qwen/Qwen2.5-7B-Instruct", gpu_memory_utilization=0.85, max_model_len=8192, ) params = SamplingParams(temperature=0.7, max_tokens=512) outputs = llm.generate(["请用一句话解释什么是分页"], params) print(outputs[0].outputs[0].text)

原生接口适合做离线批量推理或者嵌入到自己的pipeline里,没有网络开销,少了HTTP解析的损耗。要注意的是,原生接口和HTTP服务是两种使用方式,不能同时混着开,除非你在代码里用多进程分别初始化。

3.4 启动过程中的典型卡点排查

启动命令敲下去,最常见的就是模型下载卡住。vLLM会从模型仓库拉取权重,如果网络环境不好,会在下载阶段等很久。这一类的处理办法是提前设置模型下载的镜像端点,例如:

export HF_ENDPOINT=https://hf-mirror.com

设完再重新执行启动命令,它会走国内可达的镜像地址,下载速度会好很多。如果设完依然是蜗牛速度,建议先把模型下载到本地,然后用本地路径启动,例如:

vllm serve /data/models/qwen2.5-7b-instruct --port 8000

本地路径启动还能规避一个坑:部分模型仓库里的权重文件名不标准,导致自动下载时解析失败,本地路径则可以直接加载。

另一个常见卡点是端口被占。默认8000端口很容易被别的服务占用,启动报错信息里通常直接写了[Errno 98] Address already in use,这时候换个端口就行。也有一种情况是启动后日志一直停在某一步不往下走,大概率是权重加载卡在磁盘IO,特别是从机械硬盘加载上百GB模型时,不用焦虑,再等等。

4. 显存调优:榨干每一兆显存

4.1 先搞懂显存到底花在哪了

说到调优,首先得知道显存被谁吃了。vLLM推理时,显存消耗主要来自四个部分:

  • 模型权重:7B模型用FP16存,就是约14GB;INT4量化后能压到约4GB。
  • KV Cache:每处理一个token,每个注意力头都要缓存Key和Value,序列越长、并发越高,占用越大。
  • 激活值和中间buffer:前向计算过程中的临时张量。
  • CUDA context和框架自身的开销:torch/CUDA初始化就会固定吃掉一部分显存,这块无法避免。

PagedAttention是vLLM的核心创新,它把KV Cache分成固定大小的块(block),类似操作系统里的内存分页。这样一来,显存碎片化问题少了很多,而且不需要预先给整条序列分配连续空间,只动态给实际用到的部分分配即可。这就是为什么vLLM能做到高并发、高吞吐,能同时容纳很多条长序列。

理解了这个,显存调优的本质就很清晰了:在权重和KV Cache之间找到平衡。权重是刚性需求,模型换不了就得一直占着;KV Cache是弹性需求,决定了你能同时跑多少请求、多大并发、多长上下文。

4.2 核心调优参数:从原理到实战

最核心的参数是--gpu-memory-utilization。默认0.9,也就是90%的显存都会交给vLLM调度。如果你只跑一个模型、没有其他程序占显存,0.9是合理值;如果想留一点显存跑其他服务,就降到0.7左右。我用一张24GB的卡实测,7B模型FP16权重约占14GB,剩下约8GB归KV Cache,跑8192上下文、并发32是很轻松的。

vllm serve Qwen/Qwen2.5-7B-Instruct \ --gpu-memory-utilization 0.9 \ --max-model-len 8192 \ --max-num-seqs 64

--max-num-seqs控制的是同时进入批处理的请求数上限。调高它,吞吐量可以上一个台阶,但每个请求能分到的KV Cache会变少,极端情况下长序列请求会被拒掉;调低它,单个请求的响应更稳定,延迟更低。一个粗略的经验:序列长度中等(2K-4K)的场景,32到64是甜点值;序列很长(16K以上)的场景,建议压到16左右。

--max-model-len也要跟着场景走。如果你的业务大多数是短文本问答,没必要把上限设到32K,因为vLLM会按上限预留KV Cache,即使实际不用,也相当于占了显存。反过来,短文本场景把max-model-len设短一点,等于变向腾出了更多并发空间。

量化是更激进的显存节省手段。AWQ和GPTQ可以把7B模型压到4GB附近,FP8则介于FP16和4bit之间。启动时加--quantization awq并给到对应的权重仓库即可。要注意的是,量化会带来一定的精度损失,且不同模型的损失程度不一样,我自己的经验是7B以下模型量化后能明显感到回答质量下降,大一点的模型比如13B或32B量化后损失相对小。

4.3 实战案例:小显存硬跑大模型

以单张24GB显卡部署32B模型为例,FP16的32B体重直接吃满64GB,显然塞不下。这时候两个思路:一是量化,二是极端限制KV Cache。

用AWQ 4bit量化后的32B模型,权重约17GB,24GB显存勉强装下,剩下约7GB给KV Cache,但并发和上下文长度都会受限。我的调法是:

vllm serve 模型路径 \ --quantization awq \ --gpu-memory-utilization 0.92 \ --max-model-len 4096 \ --max-num-seqs 8 \ --enforce-eager

--enforce-eager是我加进去的一个开关,它让vLLM跳过CUDA graph的优化,虽然牺牲一点性能,但能省下几百MB到1GB的显存,在小显存场景非常值得。反过来,显存充足的时候不推荐加这个参数,因为CUDA graph带来的加速收益很明显。

我实际跑下来的数据做个对比:同一张24GB卡,同样的模型和请求负载,不调优启动直接爆显存闪退;按上面参数调完,稳定运行,单请求平均延迟增加并不夸张,这已经很符合本地开发或小规模服务的预期了。

4.4 用日志判断调优是否到位

调优不能靠猜,vLLM的启动日志里其实把所有信息都写清楚了。重点看这几行:

  • GPU KV cache size:分配了多少显存给KV Cache。
  • Maximum concurrency:当前配置下能支持的最大并发请求数。
  • Total number of tokens:KV Cache能容纳的总token数。

举个例子,一行日志显示GPU KV cache size: 6.42 GB、Maximum concurrency: 128,说明当前配置大约能容纳128个并发的短请求。如果你嫌并发太低,可以回调--gpu-memory-utilization或者调短--max-model-len;如果显存还有富余,就完全没有必要降参数。这套反馈循环,比在网上找现成参数组合要靠谱得多。

同样地,如果日志里出现RuntimeError: CUDA out of memory,并不是代码写错了,纯粹是分配策略太激进。优先检查三件事:gpu_memory_utilization是不是超过了实际空闲显存的比例、max_model_len是不是设得太大、max-num-seqs是不是太高。从上到下逐个调低,基本都能救回来。

5. 常见问题速查与排坑手记

5.1 启动失败与进程闪退

这类问题占排障里的大头,我把高频症状和对应解法整理成表,方便直接对照:

症状原因解法
CUDA driver version is insufficient驱动太旧,跟不上vLLM依赖的CUDA版本升级NVIDIA驱动,或安装对旧驱动兼容的vLLM版本
no kernel image available for execution显卡架构太新,预编译wheel里没有对应SM架构改用源码编译安装,或者选择支持新架构的新版本
RuntimeError: CUDA out of memory显存分配超限降低gpu-memory-utilization、max-model-len或max-num-seqs
Address already in use端口被占用换--port,或杀掉占用进程
启动后无日志,进程直接退出显卡驱动异常或显存不足执行nvidia-smi确认GPU状态,关闭无关占显存进程
AssertionError: Torch not compiled with CUDA enabledtorch装成CPU版重装CUDA版的torch,再装vLLM

闪退的问题特别容易发生在Windows用户刚换了WSL的环节,常见原因是WSL里装了CPU版的torch。提前执行python -c "import torch; print(torch.cuda.is_available())",能省下重装vLLM的十几分钟。

5.2 推理速度慢

启动没问题,但生成token很慢,这类问题更让人烦躁。先看是不是真的在用GPU推理:

watch -n 1 nvidia-smi

如果GPU利用率很低,CPU却很高,大概率是模型在CPU上跑,检查torch的CUDA是否可用。如果GPU利用率正常但单token生成延迟依然高,那就得考虑并发策略了。vLLM的Continuous Batching机制会让多个请求同时在GPU上跑,单请求反而未必最快,但整体吞吐会高很多。所以小流量场景下感觉“慢”,其实是它在优先服务并发请求。

还有一类情况是模型输入特别长,每次生成前都要先做预填充(prefill),这个过程是计算密集型的,没法走流式优化。如果业务里总出现超长输入,试一下用--max-model-len限制输入长度,或者从模型侧把输入做截断,响应会明显变快。

5.3 接口超时与并发异常

HTTP服务起来后,并发一高就出现超时或报错,通常不是vLLM挂了,而是并发参数和显存容量不匹配。打开日志看两个关键数字:Maximum concurrency和当前实际并发。如果实际并发已经顶到上限而请求还在进来,新请求会排队,超过了排队的耐性就表现为超时。

应对方式要么是提高gpu-memory-utilization里留给KV Cache的空间,要么是调整--max-num-seqs。需要注意的是,max-num-seqs不要盲目拉到很高,它会放大显存波动,某个长序列请求进来后把KV Cache吃满,后面所有请求都遭殃。比较稳的做法是先用默认值跑,观察日志里的实际分配和空闲显存,再一点一点往上加。

还有一个容易被忽略的点:临时目录空间不足。vLLM在加载大模型时会在临时目录写入一些中间文件,如果用的是系统默认的/tmp且空间只剩几个G,加载一个30多G的量化模型就可能报No space left on device。解决办法是把临时目录指到磁盘空间大的位置:

export TMPDIR=/data/tmp

我在一台配置比较奇怪的机器上就栽过这个跟头,日志一直停在权重加载阶段,排查半天,最后发现是临时目录满了,把TMPDIR指走就秒加载完毕。这种问题不属于显存范畴,但很容易和显存问题混淆。

5.4 模型输出异常

模型跑起来了,但生成的内容质量堪忧,或者出现大量重复输出,这通常不是vLLM的问题,而是推理参数没调对。SamplingParams里的temperature、top_p、repetition_penalty直接影响输出质量。vLLM默认参数相对保守,输出会比较平均。我自己做本地问答应用时,常用的组合是:

params = SamplingParams( temperature=0.7, top_p=0.9, repetition_penalty=1.05, max_tokens=1024, )

temperature太高容易胡说八道,太低容易照本宣科;repetition_penalty大于1能降低重复率,但设到1.2以上又会显得语句僵硬。这几项没有绝对正确的值,按场景和模型调几组对比一下,就能找到手感。

还有一个隐藏问题是模型上下文被截断。当你输入的内容超出模型实际窗口,vLLM不会报错,而是静默截掉前面或后面的部分,这会导致回答“前言不搭后语”。排查方法是开启日志里的提示词记录,或者自己用短文本分批测试,把模型窗口的极限摸清楚,别盲目相信模型卡上的参数描述。

经验补充:几个少有人提的调优习惯

最后再聊几个我长期跑vLLM养成的习惯,不写进官方文档,但很实用。

一个是每换一个环境就建立一套“启动基准”。比如同样一个7B模型,记录下默认配置下的显存占用、首token延迟和并发上限。以后任何一次调参,都拿这套基准来衡量,而不是凭感觉。这一步养成习惯,排查问题的时候效率能翻倍。

另一个是不要把模型路径散落在各处。建议在一个固定目录下管理本地模型,用目录名标注精度和版本,比如qwen25-7b-instruct-fp16和qwen25-7b-instruct-awq。启动命令里写本地路径不仅避开下载问题,也让日志里出现模型信息时一目了然。

第三个是善用在线API和离线推理的切换。开发调试阶段跑HTTP服务,配合OpenAI SDK非常顺手;但批量评测、压测或者批处理文档时,直接用原生Python接口,能省掉HTTP的序列化开销。两种方式代码结构差别不大,但各有所长。

根据我个人的使用感受,vLLM最值得花时间的部分其实不在安装,而在理解显存分配。把gpu_memory_utilization、max_model_len、max_num_seqs这三者的关系吃透,你就能在任意显卡上快速找到适合自己业务的那组参数。这也是为什么这篇文章花了最大篇幅在讲这块。刚开始折腾的时候,我也曾经因为显存不足反复重启,后来把日志里的GPU KV cache size和Maximum concurrency当成调试仪表盘之后,一切才真正变得可控。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询