vLLM大模型推理加速实战:显存优化、关键参数与避坑指南
2026/9/12 11:19:11 网站建设 项目流程

如果你最近正在折腾大模型部署,应该对 vllm 这个词不陌生。它不是一个聊天应用,也不是模型本身,而是一个专门做大模型推理加速与服务的引擎。我最早接触 vllm 是因为一个很现实的问题:用原始代码跑 7B 模型,并发稍微上来一点,显存直接爆掉,响应时间也变得忽快忽慢。后来换成 vllm 部署大模型,同样一张卡,吞吐量提升好几倍,这才真正体会到什么叫“推理框架选对了,能省下好几块显卡的钱”。这篇是 vllm 系列教程的第 4 篇,定位就是快速上手:不管你有没有读过源码,只要照这个流程走,就能在自己机器上把一个可用的大模型推理服务跑起来,并且搞清楚那些绕不开的关键参数到底在干什么。适合刚接触推理加速、想用 vllm 部署模型的人,也适合已经跑起来但老是被各种报错卡住、天天盯着日志发呆的同学。

1. 先搞明白 vLLM 到底解决了什么问题

1.1 一个直白的比喻:显存是仓库,算子是搬运工

很多人听到 PagedAttention、Continuous Batching 这些名词就开始头大,其实它们解决的是一个非常朴素的资源管理问题。我习惯用一个仓库和搬运工的比喻来解释:显存就是你的仓库,模型权重是必须放在仓库里的货,而每次推理产生的 KVCache(键值缓存)是在请求处理过程中临时堆起来的货架。传统推理框架在处理请求时,会为每一个请求提前划出一整块连续仓库区域,不管这个请求最后用不用得完,那块区域都不能给别人用。这就好比你在仓库里预定了一大块区域,结果只放了几箱货,剩下的空位既不能让别的货车临时停靠,也不能挪作他用。

vLLM 的核心创新是把这些临时货架切成固定大小的小块,按需分配给不同请求,用完就回收。这就是 PagedAttention,思想直接借鉴了操作系统里的虚拟内存分页。虚拟内存当年解决的是物理内存不够用的问题,vLLM 用它解决的是 KVCache 碎片化和显存浪费的问题。效果非常明显:在同样一张显卡上,vLLM 能塞进更多的并发请求,显存利用率显著提高。

1.2 vLLM 的三大看家本领

vLLM 能在众多推理框架里冲出来,靠的不是单点优化,而是把几个关键机制组合在一起。第一个就是 PagedAttention,上面已经说过,它把 KVCache 从连续存储变成分页管理,减少内部碎片。第二个是 Continuous Batching,传统的静态批处理要等一批请求全部结束后才能重新组批,而 vLLM 可以在一个请求生成完第一个 token 后立刻腾出位置,把另一个新请求接进来,实现了“你方唱罢我登场”的动态调度,GPU 的利用率自然就上去了。

第三个本领是多卡并行支持。vLLM 原生支持张量并行和流水线并行,简单理解就是把一个大模型的“大脑”切成几份,放在多张显卡上协同推理。张量并行是把每一层的矩阵切分成块,多张卡各算一部分再汇总;流水线并行则是把模型按层切成段,每张卡负责其中一段。对 70B、甚至更大规模的模型来说,没有这套并行机制,单卡物理上就放不下,更别提跑了。

1.3 哪些场景该用 vLLM,哪些场景别强求

搞清楚了 vLLM 解决了什么问题,你就能判断自己是不是真的适合用它。它最擅长的场景是高并发在线推理服务,也就是很多人同时访问你的模型接口,希望每个请求都快速得到回应。这种场景下,Continuous Batching 带来的吞吐提升非常可观。其次,长上下文场景也特别适合 vLLM,因为 PagedAttention 对超长上下文的 KVCache 管理更友好,不会因为一个超长请求就把显存全吃光。

但也要泼一盆冷水:如果只是自己调试代码、跑一两个脚本做实验,并发数长期为 1,那 vLLM 带来的性能提升就没那么明显,反而因为框架封装多了一层,跑单个请求时延迟可能比直接加载模型更慢。另外,如果你的显存非常小,比如 6GB、8GB 这种,跑超过 13B 的模型还是比较吃力,这时候更重要的是考虑量化和小模型方案,而不是指望推理框架变魔术。先把这些定位想清楚,再往下看安装和启动,就不会被各种预期落差折磨。

2. 快速上手的硬件与安装:别在最容易出错的环节翻车

2.1 硬件需求到底怎么算:一个简单的显存估算公式

安装 vLLM 之前,最该先做的是算清楚自己的显存够不够。这里分享一个快速估算办法:模型权重占用显存 = 模型参数量 × 每个参数占用的字节数。如果是 FP16 精度,每个参数占 2 字节,所以 7B 模型大约需要 14GB 权重空间,13B 模型大约 26GB,70B 模型大约 140GB。再加上 KVCache、激活值、CUDA context 等额外开销,实际占用会比这个数字高不少。

所以,如果你想用 FP16 跑 7B 模型,你至少需要一张 24GB 的显卡(比如 4090、A10、L4 就能凑合),如果只有 16GB 甚至 12GB,建议考虑量化版本,比如 AWQ 或 GPTQ 量化的 4bit 模型,权重直接减半以上,这也解释了为什么现在很多部署教程都在教怎么用量化模型配合 vLLM。再补充一个经验:显存越接近临界值,越不要想着把模型塞得满满当当,给自己留出 10%~15% 的余量,否则跑起来容易遇到随机 OOM。

2.2 安装 vLLM:Python、CUDA、PyTorch 的版本搭配

安装这块是很多人踩坑的重灾区。vLLM 不是一个单独就能跑的包,它依赖 PyTorch、CUDA 运行时环境、各种编译工具链。如果版本不匹配,很容易出现装了用不了、启动就报错的情况。先说最简单的安装方式,用 pip 直接装官方预编译的 wheel 包:

python -m venv vllm-env source vllm-env/bin/activate pip install --upgrade pip pip install vllm

前提是你的 Python 版本要在官方支持的范围内,目前一般是 3.9 到 3.12。CUDA 方面,vLLM 的预编译包会跟着特定 CUDA 版本走,官方文档一般会给出对应的版本说明,比如支持 CUDA 12.1 以上。装完之后可以用一行命令快速验证:

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

如果 import 成功,说明基础依赖没问题。如果卡在编译阶段、或者报红字说找不到 CUDA 工具链,大概率是环境不干净。我个人的建议是:尽量在干净的虚拟环境里装,不要和一堆深度学习项目共用同一个 conda 环境,环境越乱,越难排查。

2.3 Windows 能不能玩 vLLM:实话实说

热词里有人搜“vllm windows 版”,那我就直说了:官方对 Windows 的支持非常有限,绝大多数版本的 vLLM 没有提供 Windows 原生预编译 wheel。Windows 下想跑,最省心的方案是 WSL2,在里面开一个 Ubuntu 环境,然后按 Linux 的方式安装。WSL2 能直接访问宿主机的 GPU,配合最新驱动,跑 vLLM 是完全可行的。我不太建议在 Windows 原生环境里硬碰硬,因为需要自己编译一堆微软工具链,编译时间很长,遇到问题的概率也高。

如果你只是在 Windows 桌面上做做实验,不想折腾 WSL,其实可以先试试 LM Studio 这类图形化推理工具,它把模型下载、加载、聊天界面都打包好了,适合快速体验大模型,但它和 vLLM 是两回事。vLLM 追求的是服务化部署、高并发、可控参数,LM Studio 则更像是本地版的 ChatGPT 客户端。两者目标不同,你要按自己的需求选。

3. 十五分钟跑通第一个推理服务

3.1 一段最简单的启动命令:逐项拆开讲

环境配好之后,跑通 vLLM 服务其实非常快。我以 Qwen/Qwen2.5-7B-Instruct 为例,这是我在本机上测试最多、社区资料也比较全的模型。你先用 huggingface-cli 或者直接让 vLLM 启动时自动下载模型,然后执行下面的命令:

vllm serve Qwen/Qwen2.5-7B-Instruct --host 0.0.0.0 --port 8000 --dtype auto

如果你是在本机测试,--host也可以不写,默认 127.0.0.1;但如果你想让局域网内其他机器访问,就必须要0.0.0.0--port 8000指定服务端口,和 FastAPI 默认风格一致。--dtype auto的意思是让 vLLM 根据模型权重自动选择精度,一般不需要你手选。

启动过程中你会看到一堆日志,其中最关键的是最后一段:模型加载完成、KVCache 分配完成、服务启动。很多新手一看到日志里出现大量路径、版本号就慌,其实只要最后看到类似Application startup completeUvicorn running的字样,就说明服务已经起来了。整个过程中不要 Ctrl+C,也别开两个窗口重复启动,否则会碰上端口占用。

3.2 用一个请求验证服务:curl 和 Python 都试一遍

服务启动后,vLLM 默认提供一个 OpenAI 兼容的接口,路径是/v1/chat/completions/v1/completions。这意味着你之前用 OpenAI SDK 写的代码,只需要把 base_url 改成http://localhost:8000/v1,把 api_key 随便填一个占位符,就能直接跑起来。先用最简单的 curl 命令验证一下:

curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Qwen/Qwen2.5-7B-Instruct", "messages": [{"role": "user", "content": "你好,请用一句话介绍你自己。"}], "temperature": 0.7 }'

这里有个特别容易踩的坑:请求体里的model字段必须和你启动服务时传的模型名匹配,或者和你用--served-model-name指定的别名一致。如果你启动时传的是完整路径Qwen/Qwen2.5-7B-Instruct,那这里的 model 就填同样的字符串。如果你没指定别名,很多人在这一步填qwen2.5-7b-instruct这种小写形式,结果返回 404 或模型不存在,这不是 vLLM 坏了,你是你填错了名字。

Python 侧的调用同样简单,openai 库安装好之后:

from openai import OpenAI client = OpenAI( base_url="http://localhost:8000/v1", api_key="EMPTY", ) response = client.chat.completions.create( model="Qwen/Qwen2.5-7B-Instruct", messages=[ {"role": "user", "content": "用一句话解释什么是 KVCache"} ], temperature=0.7, ) print(response.choices[0].message.content)

如果 curl 返回了正常文本,Python 脚本也能输出内容,说明整个链路已经通了。接下来就可以进入真正的部署参数调优阶段。

3.3 模型文件加载顺序与后台部署的小问题

跑通之后你可能会遇到一类问题:项目目录里明明放着模型文件,启动却报各种奇怪的错误。这通常是因为模型目录不完整。vLLM 加载模型的顺序大致是:先读config.json确定模型结构和参数量,再读tokenizer相关文件,最后加载权重*.safetensors*.bin。这些文件缺一不可。你从一个页面手动下载模型时,很容易漏掉 tokenizer 配置文件,启动时日志就会提示找不到tokenizer_config.json或者tokenizer.json。解决办法很简单:直接去 Hugging Face 模型仓库页看文件列表,把config.jsontokenizer_config.jsontokenizer.jsonvocab.jsonmerges.txt以及权重分片文件全部下齐。

服务跑起来之后,如果不想一直占着终端窗口,可以用 tmux、nohup 或者 systemd 来管理。个人最推荐 tmux,因为你可以随时tmux attach回来查看日志,不像 nohup 把输出写到文件里还要手动 tail。关键日志保留好,后面排查问题会轻松很多。

4. 搞清楚这几个关键参数,才算真正“会用”了

4.1 max-model-len 与显存预估:算给你看

很多教程都是直接叫你加一行--max-model-len 8192,也不解释为什么。这个参数决定了模型最多能处理多长的输入加输出序列,它会直接影响 KVCache 的预留策略。KVCache 的大小和层数、注意力头数、头维度成正比,序列越长、并发越高,KVCache 占用就越大。

拿 7B 模型为例,假设它内部有几十亿参数、30 层左右的 Transformer 层,如果你把max-model-len从 4096 拉到 32768,KVCache 占用的显存会成倍增长。启动日志里通常会有这么一行信息,告诉你为 KVCache 分配了多少 GiB,比如Allocation of 10.0 GiB for KV cache。如果分配过小,说明你的模型权重已经占用了太多显存,KVCache 区域被压缩得厉害,并发一高就容易排队等待,甚至直接报错。

我自己的建议是:根据实际业务中最长的请求来决定这个值。如果只是做普通客服问答,输入最多几百字,输出最多一千字,那max-model-len设 4096 或 8192 完全够用,没必要无脑拉高到 32K,否则显存都拿去喂 KVCache 了,反而挤占了模型的并发能力。

4.2 gpu-memory-utilization:留给 KVCache 多少余粮

--gpu-memory-utilization是用来控制 vLLM 最多能用多少比例显存的重要参数,默认是 0.9,也就是允许 vLLM 占满 90% 的显存。它影响的是权重加载完之后的剩余显存去向:一部分给 KVCache,一部分给计算过程中的临时激活值,剩下的留给 CUDA context 和 PyTorch 内部开销。

我在 24GB 显卡上跑 7B 模型时,经常把--gpu-memory-utilization设为 0.92 甚至 0.95,因为 7B 权重只占一小半,剩下的空间要给 KVCache 留足余量,才能支撑高并发。但在只有 16GB 显存、还跑量化模型的机器上,我会保守一点,设为 0.85。这个数值不是越大越好,如果设成 0.99,PyTorch、CUDA context 连一点缓冲都没有,一旦请求波动,很容易直接 OOM;设得太低,比如 0.7,又会发现服务倒是稳定,但并发稍微一高,KVCache 不够,请求开始排队,吞吐上不去。建议先按 0.9 跑,观察日志里 KVCache 的分配情况,再微调。

4.3 enforce-eager、quantization、trust-remote-code 这几个隐藏的坑

--enforce-eager是我特别想提醒新手的一个参数。vLLM 默认启用 CUDA Graph 优化,把模型执行图提前编译好,推理时省去反复调用内核的开销。代价是启动时会额外花几十秒到几分钟来编译,显存占用也会稍高。如果你的显存很紧张,启动老报 OOM,可以加上--enforce-eager试试,它会关闭 CUDA Graph,启动更快、显存占用更低,但单请求推理吞吐会有明显下降。等调试稳定了,再把参数去掉恢复正常模式。

再来说--quantization。如果你下载的是 AWQ 或 GPTQ 量化模型,必须在启动时告诉 vLLM 用对应的量化方式。比如模型目录里有量化配置文件,你可以在启动命令里写--quantization awq--quantization gptq。如果懒得手动指定,一些模型目录自带量化配置,vLLM 能自动识别,但不是所有模型都能,所以启动后如果看到算子加载失败的日志,优先检查是不是量化格式没对上。

--trust-remote-code这个参数也容易被忽略。它其实是在说“我信任这个模型仓库里的自定义 Python 代码”,因为有些模型在 Hugging Face 上会放一些自定义的模型实现文件,不加载这些代码就无法初始化模型。如果你用的是比较新的指令微调模型,启动时经常提示需要加这个参数,直接加上就好,前提是你确认模型来源可信。

4.4 并发、前缀缓存和服务别名:生产环境最常用的几个参数

生产环境里,还有几个参数几乎天天要用,我用一个表格总结一下:

参数作用我的常用值
--max-num-seqs限制同时处理的序列数量,保护显存和延迟64~256
--enable-prefix-caching开启前缀缓存,相似请求可以复用 KVCache开启
--served-model-name给服务暴露一个别名,方便前端统一调用"qwen7b"之类
--tensor-parallel-size多卡张量并行,传显卡数量单卡为 1
--host/--port服务监听地址和端口0.0.0.0/8000

--enable-prefix-caching是个容易被低估的优化。如果业务里有很多用户共享同一个系统提示词,前缀相同的部分会被缓存下来,后续请求可以复用 KVCache,省掉重复计算,对降低首 token 延迟非常明显。--served-model-name也很有用,因为实际部署中前端只关心一个固定的模型名,不希望以后换模型还要改前端配置,这时候给服务起个别名就是最简单的解耦方式。

5. 高频踩坑排查链路:从报错到定位的全过程

5.1 CUDA out of memory 不只是调小 max-model-len

OOM 应该是新手遇到最多的问题。很多人第一反应是把max-model-len调小,这确实有用,但只是治标。OOM 的根源可能是显存被历史占满,之前启动的服务没有完全退出,也可能是gpu-memory-utilization设置过高,导致请求波动时没有缓冲空间。我遇到 OOM 时,一般会按下面这个顺序排查:

  1. 先跑nvidia-smi看当前显存是不是已经有进程占着。如果之前启动 vLLM 的终端被你直接关闭,但进程没被杀死,显存就会一直被占用,这时候kill -9掉对应进程再重新启动。
  2. 如果显存是干净的,再看启动日志里 KVCache 分配了多少。如果权重+KV Cache 已经接近显存上限,就考虑降并发、降低gpu-memory-utilization或开启量化。
  3. 在环境变量里加一句PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True,可以让 PyTorch 的显存分配更灵活,减少碎片,实测在某些模型上能缓解 OOM。
  4. 最后才考虑调小max-model-len,因为它会牺牲模型处理长文本的能力。

这套链路我反复用过很多次,绝大多数字符串 OOM 都能解决掉。记住一个原则:不要一上来就重启,先看显存被谁吃了。

5.2 版本错配导致的启动崩溃:一套可复现的排查流程

vLLM 社区的版本更新非常快,几乎隔几周就会发一个新版本,每个版本对 PyTorch、CUDA 的依赖都可能不一样。最典型的报错是启动时出现ModuleNotFoundError: No module named 'vllm._C',或者一堆和 CUDA 算子相关的红色堆栈。这种十有八九是环境里的 PyTorch 版本和 vLLM 编译时使用的版本不一致。

我的处理流程是:先看 vLLM 当前版本对应的 requirements 文件需要哪些版本依赖,然后卸载掉环境中已有的 vllm、torch,全部重装。更稳妥的做法是直接用官方 Docker 镜像,比如vllm/vllm-openai,镜像里已经打包好了所有依赖,不需要你自己操心版本搭配。如果在 Windows 上折腾,就先确认 WSL2 里的 CUDA 驱动版本和容器里的 CUDA 兼容性,这一步能省去后面大量调试时间。热词里有人搜“vllm构建需要多长时间”,我多说一句:不要轻易尝试从源码编译,除非官方没有你这个平台或 CUDA 版本的预编译包,否则海外下载依赖加编译的时间可能让你怀疑人生。

5.3 日志里的 nccl 信息到底意味着什么

很多人在启动多卡推理时,会在日志里看到一行类似[pynccl.py:113] vllm is using nccl==2.30.7的内容,以为发生了错误。我第一次看到也愣了一下,后来才确认这只是一条信息日志,意思是 vLLM 正在使用 NCCL 作为多卡通信库。NCCL 对多卡并行来说非常重要,尤其是张量并行时,每张卡之间需要频繁同步中间计算结果,NCCL 就是负责这条通信管道的核心库。

如果在设置--tensor-parallel-size 2之后,启动过程卡住、报 NCCL 超时或初始化失败,先检查显卡之间的通信拓扑。可以用nvidia-smi topo -m看两张卡是不是通过 NVLink/NVSwitch 连接,如果是普通 PCIe 链路,速度会慢一些,但一般也能用。遇到某些特殊环境,比如虚拟化平台或者异构卡混插,可能还要设NCCL_P2P_DISABLE=1强制走共享内存或网络通信,虽然性能会降,但至少能跑起来。碰到多卡问题时,先把nvidia-smi输出和 vLLM 启动日志放一起看,比盲目改参数靠谱得多。

6. vLLM、SGLang 与其它方案怎么选,以及下一步能从哪里继续深挖

6.1 vLLM 和 SGLang 的螺丝对螺丝对比

热词里有人搜“sglang和vllm”,说明很多人在这两个框架之间摇摆。SGLang 是另一个高性能推理框架,它在调度和前缀缓存上做了很多激进设计,RadixAttention 机制能把公共前缀的 KVCache 复用到极致。如果你有大量请求都带着长长的系统提示词,或者模型应用里有复杂的多轮工具调用,SGLang 的吞吐优势会很突出。

但 vLLM 也不是吃素的。它的生态成熟度最高,各种第三方库、监控工具、一键集成基本都会优先兼容 vLLM 的 OpenAI 风格接口,遇到问题能在社区里搜到大量答案。对我个人来说,如果没有做复杂 Agent 工作流和极致吞吐压测,我一般会默认选 vLLM,因为稳定、省心、资料多。如果你的业务场景真的很吃前缀缓存和多轮交互,比如代码补全、复杂问答机器人,可以拿 SGLang 做一轮 benchmark 对比,再决定要不要迁移。框架选型从来不是“谁好谁坏”的问题,而是“哪个更匹配你的负载”。

6.2 用 benchmark 验证效果,不要凭感觉优化

很多人跑通 vLLM 之后,觉得“响应挺快”就没下文了。真要优化性能,还是得量化。vLLM 仓库自带 benchmark 脚本,常见的有benchmarks/benchmark_serving.py,用来压测在线服务的吞吐和延迟。新版本里也提供了更便捷的vllm bench serve子命令。用法大致是:

python benchmarks/benchmark_serving.py \ --backend vllm \ --model Qwen/Qwen2.5-7B-Instruct \ --base-url http://localhost:8000/v1 \ --num-prompts 200 \ --request-rate 10

跑完之后会输出一堆指标,最值得关注的是 Output token throughput,也就是每秒生成的 token 数量,以及 TTFT(Time To First Token),也就是用户发出请求后多久看到第一个字。TTFT 直接影响对话的“跟手”感,吞吐则直接影响单位时间能服务多少用户。我一般会用不同的--request-rate压低测和高测,画一条吞吐随并发变化的曲线,找到服务的饱和点。这样调参就不是拍脑袋了。

6.3 下一步深挖方向:前缀缓存、LoRA、多模态与结构化输出

跑通基础服务之后,可以往上深入的方向其实很多。如果你对前缀缓存感兴趣,就用--enable-prefix-caching打开它,对比开关前后的 TTFT 数据;如果要做多用户个性化,可以研究 vLLM 的 LoRA 适配能力,多个低秩权重可以同时驻留显存,不用切换模型;如果业务需要从文档里抽取结构化字段,vLLM 对 JSON structured output 的支持也越来越好,可以在接口里直接用response_format控制输出格式。多模态模型方面,vLLM 也开始支持视觉语言模型,但配置会比文本模型复杂一些,需要留意官方对各视觉编码器的支持状态。

我个人觉得,快速上手的终点不是“能输出一段文本”,而是“知道每一步日志在告诉你什么”。比如启动日志里的 KVCache 分配量、吞吐压测里的指标、显存监控曲线,这些才是真正帮你做决策的信息。

6.4 最后留一个实测小技巧

本机玩的时候,如果显存不够又不想换模型,可以试试把--dtype改成--dtype half,对大多数模型来说就是使用 FP16 精度,在支持半精度计算的显卡上能省一半显存。如果整个模型都放不下,再考虑下载 4bit 量化版,用--quantization awq--quantization gptq加载。顺序不要搞反,否则你会在日志里看到一堆算子不支持的报错,容易劝退。

每次改完参数,都建议先启动一个最小请求验证,再去跑并发压测。这样能更快定位到底是模型加载的问题,还是参数设置的问题,而不是一头扎进一堆日志里越看越乱。

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

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

立即咨询