☰
vLLM 推理服务部署与显存调优实战:从安装到高并发优化
2026/10/7 23:33:21 网站建设 项目流程

1. 为什么我最终把推理服务从其他方案迁到了 vLLM

第一次接触 vLLM 是在一个需要同时跑多个并发请求的大模型推理场景里。当时用常规的推理框架,单张卡上并发一上来,吞吐量就断崖式下跌,显存也像漏水的桶一样被 KV Cache 一点点吃光。后来换成 vLLM,同样的硬件、同样的模型,吞吐量直接翻了好几倍,显存占用反而更可控。这个反差让我意识到,vLLM 不是一个"又一个推理框架",而是从底层重新设计了显存管理和请求调度的一整套方案。

vLLM 的核心价值,简单说就是三件事:高吞吐、低显存浪费、易部署。它最出名的技术是PagedAttention,把 KV Cache 像操作系统管理内存分页那样切成块来管理,避免了传统实现里因为预留连续显存而造成的碎片和浪费。再配合Continuous Batching(连续批处理),新请求可以随时插进正在运行的批次里,不用等整批跑完,GPU 利用率被拉得很高。

这篇文章适合谁看?如果你手里有一张或几张 GPU,想把开源大模型跑成一个能对外提供服务的接口,又不想被显存和并发问题反复折磨,那这篇就是写给你的。我会从零开始讲清楚:环境怎么装、服务怎么起、显存怎么调、踩过的坑有哪些。全程按我实际操作的顺序来,不跳步,也不堆砌没用的概念。

需要先说明一点:vLLM 的迭代速度非常快,命令参数和默认行为在不同版本之间会有差异。我下面给出的操作以较新的稳定版本为准,但你在实际执行时,最好先确认自己装的版本号,遇到参数不识别的情况,优先查对应版本的官方文档,而不是照搬旧教程。

2. 装 vLLM 之前,先把环境这关想明白

2.1 硬件与驱动的硬性门槛

vLLM 对硬件不是"能跑就行",它对 GPU 的计算能力和显存有比较明确的要求。我整理了一张表,方便你对照自己的机器判断:

项目最低可用推荐配置说明
GPU 架构计算能力 7.0 及以上8.0 及以上太老的卡很多算子不支持
单卡显存16GB 起24GB 及以上决定能跑多大的模型
驱动版本较新的生产版驱动与 CUDA 版本匹配驱动太旧会报找不到设备
CUDA 版本11.8 / 12.1 等12.1 及以上要和 PyTorch 编译版本对齐
系统内存32GB64GB 及以上加载模型权重时会占用

这里有个特别容易被忽略的点:CUDA 版本、PyTorch 版本、vLLM 版本三者必须对齐。我见过太多人装完之后报undefined symbol或者no kernel image is available,八成就是这三者没对上。vLLM 的 wheel 包在编译时就绑定了特定的 PyTorch 和 CUDA,你如果自己手动升级了 PyTorch,很可能就把 vLLM 搞崩了。

提示:装 vLLM 时不要先手动装 PyTorch 再装 vLLM,让 pip 自己解析依赖,或者严格按官方给出的版本组合来。手动干预依赖是这类报错的头号来源。

2.2 用虚拟环境隔离,别污染系统 Python

我强烈建议用 conda 或者 venv 建一个独立环境。原因很实际:vLLM 依赖的 PyTorch、transformers、numpy 版本都比较挑,一旦和你系统里其他项目的依赖打架,排查起来非常痛苦。

用 conda 的话大致是这样:

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

Python 版本我推荐 3.10 或 3.11,这两个版本在各类依赖上的兼容性最稳。3.12 虽然也能用,但偶尔会遇到某些包还没出对应 wheel 的情况,需要现场编译,费时费力。

2.3 安装命令与国内网络的处理

官方推荐的安装方式就是 pip:

pip install vllm

但如果你在国内,直接从默认源拉取会非常慢,甚至中途断掉。我的做法是换用国内镜像源,同时把超时时间调长:

pip install vllm -i https://pypi.tuna.tsinghua.edu.cn/simple --timeout 120

这里有个经验:vLLM 的 wheel 包体积不小,加上它依赖的 PyTorch 动辄几个 G,整个安装过程下载量很大。如果你网络不稳定,建议分步来——先单独把 torch 装好(用镜像源),再装 vllm,这样断了也不用从头再来。

装完之后一定要验证一下:

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

能打印出版本号,说明基本环境没问题。如果这一步就报错,别急着往下走,先把报错信息看仔细,通常是 CUDA 或 PyTorch 的问题。

2.4 关于 Windows 用户要提前知道的事

vLLM 的原生支持是面向 Linux 的,Windows 上直接 pip 安装经常会卡在编译环节。热词里出现的"vllm windows 社区版"其实反映的就是这个痛点。我的建议很直接:如果你要在 Windows 上用 vLLM,优先考虑 WSL2,在 WSL 里按 Linux 的方式装,成功率最高。实在要用原生 Windows,就得做好折腾编译工具链的准备,性价比不高。

3. 把服务跑起来:启动命令背后的每个参数都值得说清楚

3.1 最小可用启动命令

先把最简单的跑通,再谈优化。假设你本地已经下载好了模型权重,比如放在/models/Qwen目录下,那么一条命令就能起服务:

python -m vllm.entrypoints.openai.api_server \ --model /models/Qwen \ --served-model-name qwen \ --host 0.0.0.0 \ --port 8000

这条命令起的是一个兼容 OpenAI 接口的服务,也就是说,任何原本调用 OpenAI API 的客户端,只要把 base_url 改成你的地址,就能直接对接。这是 vLLM 特别讨喜的一点,迁移成本几乎为零。

几个参数解释一下:--model指向模型路径,可以是本地目录,也可以是 Hugging Face 上的模型名;--served-model-name是你对外暴露的模型名,客户端请求时用的就是这个名字;--host 0.0.0.0表示监听所有网卡,方便局域网内其他机器访问。

3.2 显存相关的关键参数

真正决定你能不能跑起来、能跑多快的,是显存参数。最核心的是这个:

--gpu-memory-utilization 0.9

这个参数控制 vLLM 最多能用掉单卡显存的百分比,默认是 0.9。为什么不是 1.0?因为你要给系统、CUDA 上下文、以及一些临时张量留出余量。设成 1.0 很容易在运行中 OOM。我一般从 0.85 到 0.9 之间试,如果模型大、并发高,就往下调一点。

另一个关键参数是:

--max-model-len 8192

它限制单个请求的最大上下文长度。这个值直接决定了 KV Cache 的峰值占用。很多人启动就 OOM,就是因为模型默认的最大长度太大(比如 32K),而显存根本撑不住。把 max-model-len 调小,是解决启动 OOM 最有效的手段之一。

还有一个常被忽略的:

--tensor-parallel-size 2

这是张量并行度,等于你用几张卡来分摊一个模型。单卡就设 1,两张卡设 2。注意它要求卡数能被整除,而且卡之间最好有高速互联,否则通信开销会拖慢整体速度。

3.3 显存不够时的几种降级策略

我把实际用过的降级手段按优先级列一下,从代价最小到最大:

  1. 降低 max-model-len:最直接,代价是支持的上下文变短。
  2. 降低 gpu-memory-utilization:给系统留更多余量,但可用 KV Cache 变少,并发能力下降。
  3. 启用量化:比如用 AWQ 或 GPTQ 量化后的权重,显存占用能降到原来的三分之一到一半。
  4. 开启 CPU 卸载:--cpu-offload-gb可以把部分权重放到内存,但速度会明显变慢。
  5. 换更小的模型:实在不行就降规格,这是最后的办法。

这里我要强调一个反直觉的点:显存调优不是把利用率拉满就好。我曾经把 gpu-memory-utilization 设到 0.95,结果服务跑一段时间后偶发 OOM,排查半天才发现是某些请求的输入特别长,KV Cache 瞬间膨胀。后来降到 0.88,稳定运行再没出过问题。所以留余量不是浪费,是给突发流量买保险。

3.4 验证服务是否正常

服务起来后,别急着接业务,先用 curl 测一下:

curl http://localhost:8000/v1/models

能返回模型列表,说明服务活着。再发一个补全请求:

curl http://localhost:8000/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen", "prompt": "你好,请介绍一下你自己", "max_tokens": 128 }'

如果能看到正常的生成结果,恭喜,最小闭环打通了。这一步看着简单,但它是后面所有调优的基础,一定要先确认它稳。

4. 显存调优的实战思路:从"能跑"到"跑得好"

4.1 先搞清楚显存到底被谁吃了

调优的前提是知道显存花在哪。vLLM 的显存大致分三块:模型权重、KV Cache、激活值和临时缓冲。模型权重是固定的,KV Cache 是动态的,临时缓冲则和批大小、序列长度相关。

启动日志里其实会打印这些信息,比如 "GPU memory usage" 和 "KV cache blocks" 之类的字样。我建议你养成看启动日志的习惯,它会告诉你:模型权重占了多少、还剩多少给 KV Cache、能容纳多少个 block。这些数字是调优的直接依据。

一个粗略的估算公式是:KV Cache 显存 ≈ 2 × 层数 × 注意力头数 × 头维度 × 序列长度 × 批大小 × 数据类型字节数。你不需要精确算,但要有个概念——序列长度和批大小是乘数关系,任何一个翻倍,KV Cache 就翻倍。这就是为什么长上下文和高并发很难同时满足。

4.2 量化:显存不够时最划算的一招

如果你的卡显存有限,又想跑稍大的模型,量化几乎是必选项。vLLM 对 AWQ 和 GPTQ 的支持都比较好。用法上,你只需要加载量化后的权重:

python -m vllm.entrypoints.openai.api_server \ --model /models/Qwen-AWQ \ --quantization awq \ --gpu-memory-utilization 0.9

量化带来的收益很直观:一个原本需要 24GB 显存的模型,量化后可能 12GB 就能跑。代价是精度会有轻微损失,但在大多数对话和生成任务上,这种损失几乎感知不到。

我的经验是:优先选 AWQ,它在推理速度和精度保持上比较均衡,而且社区里现成的量化权重多,不用自己折腾。GPTQ 也可以,但不同实现之间的兼容性偶尔会有小问题。

4.3 批处理与并发的平衡

vLLM 的 Continuous Batching 是它的看家本领,但并发不是越高越好。并发太高,KV Cache 被占满,新请求就得排队,延迟反而上升。这里有个实用的观察指标:看服务的排队情况。如果请求经常在队列里等很久,说明并发超过了显存能承载的上限。

控制并发的参数是:

--max-num-seqs 256

它限制同时处理的最大序列数。默认值通常够用,但在显存紧张时,把它调小能让服务更稳定。我一般会结合--max-model-len一起调:上下文短,就可以多放一些并发;上下文长,就得减少并发。

4.4 一个真实的调优案例

说个具体的。有次我要在一张 24GB 的卡上跑一个 7B 的模型,要求支持 8K 上下文。第一次启动直接 OOM。我的排查顺序是这样的:

第一步,把 max-model-len 从默认值降到 8192,还是 OOM。第二步,把 gpu-memory-utilization 从 0.9 降到 0.85,勉强能起,但并发一高就崩。第三步,换成 AWQ 量化权重,显存占用直接降到一半左右,这时候再把 max-model-len 调回 8192,gpu-memory-utilization 设 0.9,稳定运行,并发也上去了。

整个过程的核心逻辑就是:先定位瓶颈是权重还是 KV Cache,再决定是量化还是缩上下文。如果权重就占了大头,量化最有效;如果权重不大但 KV Cache 爆了,那就缩上下文或降并发。

5. 那些文档里不会写、但一定会遇到的坑

5.1 启动卡住不动,也不报错

这个现象很常见,尤其是第一次加载模型时。vLLM 要从磁盘读权重、初始化 CUDA、编译一些算子,这个过程可能持续几分钟。如果你看到日志停在某一行不动,先别急着 Ctrl+C,等一等。判断方法是看 GPU 利用率,用nvidia-smi观察,如果显存占用在涨,说明它在干活。

真正卡死的情况通常是:模型路径写错、权重文件损坏、或者显存不够在反复重试。这时候日志里一般会有线索,仔细翻一翻。

5.2 端口被占用

--port 8000如果被别的服务占了,启动会失败。换个端口就行,比如 8001。这个坑很小,但第一次遇到会懵一下,因为报错信息不一定直白。

5.3 模型下载慢或下载失败

如果你直接用 Hugging Face 的模型名,vLLM 会去联网下载。国内网络下这一步经常超时。我的做法是提前用工具把权重下到本地,然后--model指向本地目录。这样启动时不再依赖网络,稳定得多。

5.4 多卡启动时的常见问题

用--tensor-parallel-size多卡时,最容易出问题的是卡之间的可见性。要确保CUDA_VISIBLE_DEVICES设置正确,而且所有卡型号一致。混插不同型号的卡,vLLM 很可能起不来,或者性能被最慢的那张拖累。

注意:多卡并行不是简单地把卡数加上去就更快。通信开销会随卡数增加,2 卡的加速比通常不是 2 倍,可能只有 1.6 到 1.8 倍。卡越多,边际收益越低。

5.5 版本升级后的参数失效

前面提过,vLLM 迭代快。我有次升级版本后,原来能用的参数报"unrecognized arguments"。解决办法就是查新版本的帮助:

python -m vllm.entrypoints.openai.api_server --help

把帮助信息里对应的新参数名找出来替换。养成升级后先看 help 的习惯,能省很多时间。

6. 让服务真正可用:接口对接与日常维护

6.1 用 OpenAI 客户端直接对接

服务起来后,对接非常简单。以 Python 为例:

from openai import OpenAI client = OpenAI( base_url="http://localhost:8000/v1", api_key="EMPTY" ) resp = client.chat.completions.create( model="qwen", messages=[{"role": "user", "content": "帮我写一段自我介绍"}] ) print(resp.choices[0].message.content)

注意api_key随便填一个非空字符串就行,vLLM 默认不校验。model要和你启动时的--served-model-name一致,否则会报找不到模型。

6.2 日志与监控

服务跑起来只是开始,日常维护更重要。vLLM 会输出请求日志,包括处理的 token 数、耗时等。我建议把这些日志收集起来,重点关注两个指标:首 token 延迟和吞吐量。首 token 延迟反映用户等待感,吞吐量反映资源利用效率。这两个指标一旦恶化,往往意味着显存或并发到了瓶颈。

如果要做更细的监控,可以接 Prometheus,vLLM 暴露了 metrics 接口。不过对大多数个人和小团队场景,看日志加nvidia-smi就够用了。

6.3 优雅重启与版本管理

生产环境里,服务不能随便 kill。我一般会记录当前使用的启动命令和版本号,写在一个脚本里。升级或调整参数时,先起一个新实例在另一个端口,验证没问题后再切换流量,最后停掉旧实例。这样能做到几乎无感更新。

7. 我踩过几次坑之后总结的几条经验

第一条,永远先跑最小配置。不要一上来就把参数拉满,先用默认值把服务跑通,再一项一项调。这样出问题时,你能快速定位是哪个参数引起的。

第二条,显存利用率留 10% 到 15% 的余量。这不是保守,是给突发长请求和系统开销留空间。我见过太多为了榨干显存而把服务搞得不稳定的例子。

第三条,量化权重优先于缩上下文。如果显存不够,先考虑量化,因为它对使用体验的影响最小。缩上下文会直接限制你能处理的任务类型。

第四条,把启动命令脚本化。参数一多,手敲容易出错,而且不利于复现。写成一个 shell 脚本,注释清楚每个参数的作用,下次调整时一目了然。

第五条,关注版本兼容性。CUDA、PyTorch、vLLM 三者的版本组合,最好固定下来,不要频繁升级。升级前先在测试环境验证,确认没问题再上生产。

最后分享一个小技巧:如果你不确定某个显存参数该设多少,可以先用一个很小的值启动,然后逐步往上加,同时观察日志里的 KV Cache block 数量。找到一个既能满足并发、又不会 OOM 的平衡点,这个点往往比理论计算出来的更靠谱,因为它是你实际硬件和负载下的真实结果。

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

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

立即咨询