最近在折腾大模型本地部署,前后试了好几套推理框架,最后稳稳落在一套组合上:用VLLM作为推理后端,部署Qwen、GLM这几个开源大模型。这篇文章就是把这段时间踩过的坑、调过的参、验证过的方案,一次性整理出来。
如果你正准备在自己的服务器上跑一个能对外提供API服务的开源大模型,或者想搞清楚VLLM到底怎么用、为什么社区里这么多人推它,那这篇应该能帮你省掉不少弯路。我尽量把从环境准备、模型选型、启动配置到性能调优、问题排障的完整链路讲清楚,适合有一定Linux和Python基础、但对大模型推理部署还不太熟的开发者参考。
1. 部署方案与模型选型思路
1.1 推理框架选型:为什么最后选了VLLM
大模型推理框架目前可选的不算少,比如最常见的HuggingFace Transformers原生加载、FasterTransformer、TGI(Text Generation Inference)、TensorRT-LLM,以及后来居上的VLLM。如果只是追求“能跑起来”,用Transformers写个脚本其实几分钟就能出demo,但问题在于它吃显存太狠、并发能力太弱。一次推理占满整卡,请求一多就排队,在实际工程场景里基本不可用。
VLLM的核心优势在于两件事:PagedAttention和Continuous Batching。PagedAttention借鉴了操作系统虚拟内存的分页思想,把KV Cache拆成固定大小的块来管理,减少显存碎片,理论上能把显存利用率拉高很多。Continuous Batching则解决了传统静态批处理“一个批次必须全部完成后才能处理下一批”的低效问题,让请求动态进出批次,吞吐量提升非常明显。实测下来,同样一张卡,用Transformers可能只能同时处理几个请求,换成VLLM之后并发稳定到几十路没有太大压力。
另外,VLLM的安装和部署足够简单。它提供了一个OpenAI兼容的API服务,起一个服务端,业务侧几乎照抄OpenAI SDK就能接入,不用为每个模型写一套推理代码。这种“低改造迁移”的特性,是选它的一个重要加分项。
1.2 模型选择维度的拆解
部署目标如果是给业务提供通用对话能力,Qwen和GLM都是很合适的选择。它们官方提供了多种参数规模的版本,从几十亿参数到几百亿参数的都有。选型主要看三个维度。
第一是效果底线。中文场景下,Qwen和GLM都是国内开源社区里中文能力表现靠前的模型,日常问答、信息抽取、文本改写这类任务完全能胜任。
第二是硬件边界。这部分是最实际的约束,显存大小直接决定了你能跑什么参数量的模型。一张24G显存的卡,跑7B~14B量级、4bit量化后的模型比较舒服;如果要上70B量级模型,基本得考虑多卡张量并行。
第三是推理效率。同参数量下,不同系列的模型结构有差异,实际吞吐量也会有明显区别。这也解释了为什么部署时要先测模型、再定框架,而不是反过来。
为了便于决策,我习惯把模型、显存、精度粗略对应起来,如下表所示(这是常见经验的参考值,具体以实测为准)。
| 参数量级 | 精度 | 预计显存占用 | 可运行的典型情况 |
|---|---|---|---|
| 7B~8B | BF16 | 16G~18G | 24G单卡可跑,留余量给KV Cache |
| 7B~8B | AWQ 4bit | 6G~8G | 20G以上显存均可流畅运行 |
| 14B | BF16 | 28G~32G | 32G单卡勉强,建议多卡 |
| 14B | AWQ 4bit | 10G~12G | 24G单卡比较充裕 |
| 32B~72B | AWQ 4bit | 24G~40G+ | 建议双卡或以上张量并行 |
注意:显存占用不仅包括模型权重,还包括推理过程中动态增长的KV Cache。部署时不能只算权重大小,否则一跑长文本就直接OOM。
2. 环境准备与依赖安装
2.1 GPU驱动与基础环境的检查清单
在安装VLLM之前,先确认一下机器的基础环境。这步看似简单,但很多人装了半天装不上,最后发现是CUDA版本和驱动对不上。
建议按顺序检查:
- 操作系统版本。Ubuntu 20.04和22.04是我用得比较稳的。CentOS和Debian也能装,但会遇到一些系统库缺失的额外问题。
- GPU驱动。执行
nvidia-smi确认驱动已安装,且版本不能太老。 - CUDA版本。VLLM依赖PyTorch的CUDA运行时。不需要单独装完整CUDA Toolkit,但如果有独立安装的CUDA,务必让
nvcc -V和nvidia-smi里的驱动版本文档能够相互兼容。 - Python版本。3.10是目前兼容性最稳的选择。
如果跑nvidia-smi正常但VLLM启动时提示找不到CUDA,大概率是PyTorch的CUDA版本和驱动不匹配,优先升级驱动而不是去折腾旧的运行库了。
2.2 VLLM安装与多版本兼容问题的规避方式
VLLM的版本更新速度很快,不同版本对CUDA、PyTorch甚至GPU架构的适配要求都不一样。最省事的方式是创建一个干净的conda环境,然后用pip按版本安装。
实际用下来,这类部署最稳的组合是:
conda create -n vllm python=3.10 -y conda activate vllm # 安装指定版本的vllm pip install vllm==0.6.3为什么强调指定版本?因为直接pip install vllm拉最新版,往往需要配套更新PyTorch、transformers等一堆依赖库,容易把环境搞乱。毕竟部署环境里往往还跑着其他训练任务,贸然升级全局的PyTorch会让项目间互相打架。就我个人而言,用固定版本号加独立conda环境,出问题的概率小很多。
装完之后,先跑一次Python导入验证,避免用起来才发现问题:
python -c "import vllm; print(vllm.__version__)"如果报缺少libcuda.so之类的错误,一般路径是确认CUDA运行库和驱动版本,而不是重装VLLM本身。
2.3 模型权重获取与本地化存储
模型下载是部署流程里最容易被忽视的一环。Qwen和GLM系列模型体积都不小,7B级别的模型光权重就有十几个G,下载时间取决于带宽。
下载时有几个建议:
- 优先从国内可稳定访问的模型仓库下载,选国内镜像渠道(modelscope是个比较省心的选择)。
- 把模型统一放在一个独立的模型目录里,比如
/data/models,方便后续多个服务复用。 - 下载完成后,确认目录里包含
config.json、tokenizer.json和权重文件。如果权重文件不完整,VLLM启动时会直接报错。
以Qwen为例,下载后目录结构大致如下:
/data/models/Qwen2.5-7B-Instruct ├── config.json ├── generation_config.json ├── merges.txt ├── model-00001-of-00002.safetensors ├── model-00002-of-00002.safetensors ├── model.safetensors.index.json ├── tokenizer.json ├── tokenizer_config.json └── vocab.json提示:不同模型仓库拉下来的文件可能略有差异,但只要包含config.json、tokenizer文件、权重文件这三类核心文件,一般都能正常加载。
3. VLLM核心配置与服务启动
3.1 OpenAI兼容API服务启动参数解析
VLLM用得最多的方式就是启动OpenAI兼容的API Server。一条命令把服务端跑起来,业务侧就可以像调用OpenAI接口一样调用本地模型。下面这条命令是我常用的基础启动模版:
python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-7B-Instruct \ --served-model-name qwen7b \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.9 \ --max-model-len 32768 \ --dtype bfloat16 \ --host 0.0.0.0 \ --port 8000这里面的几个参数非常关键。
--model指定模型路径。可以传本地路径,也可以传模型仓库的模型ID。传本地路径的优点是不依赖网络,启动更快更稳定。
--served-model-name用来给本地模型起一个对外名称。这个名称会出现在API的model字段里。业务侧请求时可以自由选择,起到别名的作用,很实用。
--tensor-parallel-size是张量并行数。单卡设为1,多卡设为卡数。设置为2时会自动做跨卡模型切分,显存和计算压力分摊到两张卡上。这里要注意的是,并行数必须能被模型注意力头数等结构整除,否则启动会阶段性地报维度错误。
--gpu-memory-utilization是显存利用率上限。设成0.9意味着允许VLLM最多占用90%的显存,留出一部分余量给模型加载和其他进程。对于24G单卡跑7B模型,建议设为0.9或0.95,既保证KV Cache空间,又不至于完全打满导致其他任务OOM。
--max-model-len是模型支持的最大上下文长度。这个参数直接影响KV Cache的预分配策略。设得越大,显存预留越多,能处理的超长文本也越多。但如果设得超过模型本身训练长度,超出部分生成的文本质量会急剧下降,这需要额外留意。一般按模型原生的上下文长度设置即可。
--dtype bfloat16表示用BF16精度推理。现在主流新显卡对BF16支持很好,数值稳定性比FP16更好。如果是老显卡不支持BF16,可以改成--dtype float16。
3.2 并发和性能关键参数的配置经验
除了启动基本参数,还有几个和并发性能直接相关的参数,实际调整时很考验经验。
--max-num-seqs是单个批次最多容纳的序列数。PagedAttention和Continuous Batching让多个请求共享同一个批次,这样可以尽可能占满算力。设得太小,并发提升不上去;设得太大,大批次下每个请求的响应时延会明显上升。对于7B模型配24G显存,我通常设在64到128之间。
--max-num-batched-tokens控制每个批次最多的tokens数量。VLLM每步推理前会从等待队列里取出符合条件的请求来组batch,这个参数限制了单步送入GPU的最大总token数。设得过大可能超显存,设得过小吞吐上不去。参考策略是先保持默认真跑几个压测请求,再根据显存余量和响应延迟微调。
--kv-cache-dtype在某些新版本里可以设成fp8,能进一步省显存。但对未经过充分验证的模型,精度上可能存在波动,谨慎开启。
另外一个容易被忽略的参数是--trust-remote-code。部分模型架构需要加载模块来自定义代码,没有这个参数启动时会出现加载中断这类问题。如果模型仓库里带了非标准Python类的建模文件,启动命令里记得加上:
--trust-remote-code3.3 多卡部署时的并行策略调优
当单卡显存放不下模型时,就需要多卡部署,这时候部署思路会发生一点变化。
多卡部署主要分张量并行和流水线并行两种。VLLM目前用得最多的还是张量并行--tensor-parallel-size。它的核心是把模型的层内计算拆到多张卡上同时做。以一张14B模型为例,如果2张24G卡跑BF16精度,权重部分大约需要28G~32G。用2卡张量并行后,单卡只承担约一半权重,再叠加推理时的KV Cache,总体压力就降下来了。
多卡环境下要注意两点:
第一,卡与卡之间的通信延迟影响很大。建议先看看机器里的GPU拓扑。如果两张卡不是走独立的PCIe通道或NVLink直连,通信会成为瓶颈,吞吐量可能不升反降。机器上的nvidia-smi topo -m命令可以直接看出来。
第二,gpu-memory-utilization参数在多卡下仍然按整卡算,不要单卡留太少。比如2张24G卡,各设0.92,实际可用就是22G左右每张,对KV Cache更友好。
4. 模型精度选择与显存优化
4.1 BF16、FP16还是量化
推理精度直接影响显存占用和效果,需要结合实际场景做取舍。
FP16和BF16都可以作为常规默认选项。BF16的指数位更多,数值表示范围更大,训练和推理中梯度以及中间激活值不容易溢出,所以如果显卡支持,我基本都先试BF16。
量化能显著降低显存和计算压力。当前主流方法是AWQ和GPTQ。以7B模型为例,BF16权重大约占14G,4bit量化后权重可压缩至5G左右,直接让24G卡变得非常宽裕。
但有一点要注意:量化后的模型输出质量会有轻微波动。在数学推理、代码生成这类对精确度敏感的任务上,差异可能比较明显。如果业务对生成质量要求高,建议保留一个BF16版本和一个量化版本分别压测对比。
4.2 KV Cache的显存分配逻辑
KV Cache是我觉得VLLM部署里最值得下功夫理解的概念。
大模型生成时,每生成一个token,都需要把之前所有token对应的Key和Value信息缓存下来。模型不是从头重新计算一遍历史内容,而是依赖这些缓存。也就是说,并发请求越多、上下文越长,KV Cache占的显存越大。
VLLM通过PagedAttention把KV Cache按固定大小分块,按需分配。同时显存利用率参数gpu-memory-utilization决定了VLLM把多大比例的显存预留给模型和KV Cache。如果模型权重占了许多空间,留给KV Cache的就少,并发和长文本能力就受影响。反过来,如果模型权重本身很小,KV Cache空间充足,就能开更高并发、支持更长的上下文。
启动时VLLM日志会打印KV Cache的当前分配情况,比如下面这样:
(GPUCacheManager) GPU available memory: 22.3 GiB (GPUCacheManager) Maximum KV cache size: 17.8 GiB如果KV Cache过小导致并发上不去,可行的解决路径是:
- 降模型精度,比如从BF16切换到4bit量化,给KV Cache腾地方。
- 升
gpu-memory-utilization。 - 限制
max-model-len,减少长上下文场景下的缓存压力。
4.3 实测显存配置参考
这里整理一份我在不同显存条件下跑7B模型的常用启动参数组合,供参考。
| 显存 | 精度 | gpu-memory-utilization | max-model-len | 备注 |
|---|---|---|---|---|
| 12G | AWQ 4bit | 0.95 | 8192 | 显存紧张,长文本受限 |
| 24G | BF16 | 0.9 | 32768 | 日常推荐组合 |
| 24G | AWQ 4bit | 0.95 | 65536 | 长文本场景更充裕 |
| 32G | BF16 | 0.9 | 65536 | 更宽裕,适合大并发 |
这些组合能用,但不代表最优。最终还是要结合自己的请求长度、并发量、响应时间要求来调整。
5. 客户端调用与业务接入
5.1 用标准OpenAI SDK完成接入
VLLM的API Server启动后,默认在8000端口提供/v1路径的接口,完全兼容OpenAI接口规范。官方服务起来后,业务侧改动极小。以Python为例,只需要把base_url指向本地服务就行。
from openai import OpenAI client = OpenAI( api_key="EMPTY", base_url="http://127.0.0.1:8000/v1" ) response = client.chat.completions.create( model="qwen7b", messages=[ {"role": "system", "content": "你是一个专业的文字助理。"}, {"role": "user", "content": "请帮我整理一份本周工作总结提纲。"} ], temperature=0.7, max_tokens=1024, stream=False ) print(response.choices[0].message.content)这里有几个小细节要说一下。
api_key随便填一个字符串就行。因为VLLM本地服务默认不做鉴权,只要非空即可,比如"EMPTY"。
model传的值必须和启动时--served-model-name保持一致。如果启动时没设这个参数,默认就是--model路径对应的模型名称。
stream参数可以按需开启。开启流式之后,用同一个客户端直接处理增量返回即可,响应体验更接近ChatGPT的打字机效果。对内部系统来说,流式和非流式都是值得做好的能力,不少业务场景其实偏好流式输出。
5.2 并发请求与流式输出的工程化体验
实际生产环境中,往往不会一个一个请求地串行访问。服务端一旦起了并发,就需要在客户端侧处理好连接池和超时参数。
OpenAI SDK本身支持多线程调用。高并发时建议复用同一个client对象,而不是每次请求新建一个连接。也可以配合HTTPClient自定义连接池大小。
流式输出的代码改动也很小:
stream = client.chat.completions.create( model="qwen7b", messages=messages, stream=True ) for chunk in stream: delta = chunk.choices[0].delta.content if delta: print(delta, end="", flush=True)实际使用中发现,VLLM在处理流式请求时首token延迟普遍较低。要注意的是,max_tokens一定要设置,防止模型生成陷入无限循环。对于本地部署模型,建议同时设置业务层的超时时间,这样接口出现异常时可以快速失败。
5.3 多模型共存的Namespace配置
VLLM支持在同一个服务里部署多个模型,通过--model参数传多个模型路径即可。这样一个服务可以对外提供Qwen、GLM等多个模型接口,业务侧按model字段自由选择,非常灵活。
python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-7B-Instruct \ --model /data/models/GLM-4-9B-Chat \ --served-model-name qwen7b glm4-9b \ --tensor-parallel-size 1不过要注意显存规划。多模型同时加载到显存,KV Cache的空间会被进一步压缩。如果请求并发较高,建议还是分开两个服务进程部署,避免互相争抢显卡资源。
6. 性能摸底与调优实战
6.1 吞吐指标怎么看
部署完成只是第一步,更重要的是确认服务能不能扛住业务流量。性能摸底我比较关注几个指标:
- 吞吐量(Tokens/s):每秒生成的token数。这个数值直接反映整体处理能力。
- 首Token延迟(TTFT):从请求发出到返回第一个token的时间。流式交互场景很看重这个。
- 并发能力:同时稳定处理的请求数。
VLLM启动日志里自带了一些统计信息,调用接口之后也会输出模型的全部指标到日志。如果想要更精细的指标,可以用Prometheus+Grafana监控,也可以写一段压测脚本统计响应时延。
6.2 基于压测数据的参数调整流程
压测时我习惯用并发脚本模拟真实请求:不设固定QPS,而是同时发起N个请求,统计整体平均延迟和生成速度。之后根据结果调整参数。
如果发现某个压测结果吞吐量上不去,可调整项优先级一般是:
- 增大
max-num-seqs,让单批次容纳更多请求。 - 提高
gpu-memory-utilization,给KV Cache更多空间。 - 检查
max-model-len是否远超实际请求长度,过大意味着KV Cache预分配浪费。 - 看请求数据里是否包含大量system提示词,这些也会占用上下文长度。
- 如果单卡仍然受限,考虑张量并行扩展到多卡。
实测中最容易忽略的是max-model-len。很多人启动时习惯把它设到模型支持的最大值,但业务场景往往只有几百到几千token。如果设为32768,每个请求KV Cache可能预留一大块空间,并发稍高就显存紧张。建议先根据业务实际请求长度设置,再动态调整。
6.3 生产可用性的经验清单
经过多次部署实验,我整理了一份面向生产环境的配置和经验清单:
- 服务通过systemd进程守护托管,设置自动重启。
- 启动命令保存在独立的配置文件中,避免人为修改和误操作。
- 客户端设置连接超时和读超时,避免服务异常时业务侧长时间阻塞。
- GPU卡上留足显存余量,不要
gpu-memory-utilization=1.0,否则偶发的显存峰值会直接OOM。 - 定期观察
nvidia-smi的显存占用和温度。
7. 常见问题与排障实录
7.1 启动阶段典型报错排查
启动时的报错大多是环境或参数配置的问题,把最常见的列成了一个速查表。
| 报错现象 | 常见原因 | 解决办法 |
|---|---|---|
CUDA error: out of memory | 显存不足 | 降低gpu-memory-utilization、缩小max-model-len、用量化模型 |
ValueError: tensor parallel size ... | 张量并行数不匹配模型结构 | 调整并行数为模型可整除的数值 |
ModuleNotFoundError | 自定义模型代码未加载 | 加--trust-remote-code |
KeyError: 'qwen2' | VLLM版本过旧,不支持新架构 | 升级VLLM版本 |
| 启动后无法访问8000端口 | 防火墙或绑定了127.0.0.1 | 启动时--host 0.0.0.0,并检查防火墙端口 |
其中比较隐蔽的是第二种情况。不同模型的注意力头数不一样,把7B模型用--tensor-parallel-size 2跑在两张卡上,某些结构下会直接在启动时报错。建议多卡前先查清楚模型的head数目与并行数的整除关系。
7.2 运行期稳定性和显存泄漏排查
运行期最痛苦的问题是长时间服务后显存逐步增长,最终OOM或者请求变慢。遇到这类问题,我的排查步骤是:
先通过nvidia-smi观察显存曲线。如果VLLM进程的显存占用持续上涨,优先检查PyTorch缓存机制。正常情况下VLLM会复用显存,但如果某个请求走了不同的执行路径,可能触发新的显存分配。
再检查日志里是否有异常请求。某些超长文本请求可能触发了高显存水位,虽然不是泄漏,但会让显存占用看起来很高。
如果一切正常但显存仍持续上升,建议在显卡驱动和VLLM版本上寻找已知问题,并升级到相对较新的稳定版本。
7.3 输出质量异常的原因分析
有几次部署完发现生成内容明显变差:回答重复、逻辑混乱、甚至出现非中文乱码。排查下来主要有几个原因。
一是量化精度损失。AWQ/GPTQ这类4bit量化对敏感任务影响大,建议保留BF16版本做对比。
二是temperature等参数设置问题。本地模型和模型版本对参数敏感程度不一样,较低温度对于事实性任务更合适,过高的temperature会导致发散。
三是上下文窗口超限。如果max-model-len设得超过模型原生长度,模型虽然在窗内仍然能输出,但注意力可能严重失效,生成内容前后矛盾。
7.4 两个觉得特别值得说的细节
这些细节第一次部署时挺容易忽略,但影响又不小。
第一,模型目录里不要放多余的非模型文件。比如把下载脚本、说明文档和权重文件混在一个目录。VLLM加载时有时会自动扫描目录,多余文件可能导致加载失败或行为异常。
第二,served-model-name在模型启动时只需要写一次,客户端每次请求都要带着匹配的名称。如果启动后改了几次名称,各种旧客户端有可能会请求失败并残留内存,排查起来很头疼,不如一开始就定好命名规范。
8. 从单卡到多服务的扩展方向
部署跑通之后,通常很快会遇到扩容需求。这里分享几种扩展方向。
单卡升级是第一步。如果当前用24G卡跑7B模型,优先考虑换40G以上显存的卡或加卡做张量并行,能明显提升并发和上下文处理能力。
业务侧引入模型路由。现在很多团队在网关层维护一个模型注册表,把不同任务路由到不同模型。比如代码任务走Qwen,普通问答走GLM,控制成本的同时也能兼顾效果。
引入缓存层。对于高频常见问题,可以在服务和模型之间加一层语义缓存。相同或相似问题直接返回缓存结果,在大流量场景下能减少GPU压力的同时大幅降低响应延迟,是很实用的优化手段。
我的总体体验是:VLLM配合Qwen、GLM这套开源模型,基本上能以较低的成本构建一个稳定、可扩展的本地模型推理服务。任何部署都不是参数抄一遍就能收工的,建议拿到生产环境后,以自己的业务数据为样本,反复压测并观察显存和时延数据。部署踩坑不可怕,只要环境分明、参数可查、日志在案,问题基本都是可以被定位和解决的。