ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

vLLM 部署实战:从显存爆炸到高并发推理的调优指南

vLLM 部署实战:从显存爆炸到高并发推理的调优指南 1. 为什么我最终选择了 vLLM 而不是其他推理框架1.1 从一次显存爆炸说起去年年底我接手了一个私有化部署的项目客户给了一台单卡 A100 40G 的机器要求跑一个 7B 级别的对话模型同时还要留出余量给业务侧的向量检索服务。我一开始用的是最顺手的那套方案模型加载进去显存直接吃掉 28Gbatch size 稍微开大一点就 OOM。当时我的第一反应是量化但量化之后效果掉得厉害客户那边过不了评测。后来我换了个思路问题可能不在模型本身而在推理框架的显存管理方式上。传统方案里 KV Cache 是按最大序列长度预分配的不管你实际输入多长那块显存先占住再说。7B 模型在 fp16 下权重占 14G 左右剩下的 26G 里如果按 4096 的最大长度预分配 KV Cache很容易就吃掉一大半。这就是典型的显存碎片化 预分配浪费。vLLM 解决这个问题的核心武器叫PagedAttention。你可以把它理解成操作系统里的虚拟内存分页机制把 KV Cache 切成固定大小的 block按需分配不再要求连续显存。这样一来显存利用率能从传统方案的 20% 到 40% 直接拉到 90% 以上。我实测下来同一个模型同一张卡vLLM 能把并发吞吐做到原来的三到五倍这个差距在真实业务里就是能不能接住流量的区别。1.2 vLLM 到底适合谁先说清楚定位免得你走弯路。vLLM 是一个高吞吐的 LLM 推理和服务引擎它的强项是并发场景下的吞吐量不是单条请求的最低延迟。如果你的场景是一个人对着一个模型聊天那用 Ollama 或者 LM Studio 更省事图形界面点两下就跑起来了。但如果你要面对的是几十上百个用户同时打请求过来或者要做批量离线推理那 vLLM 的连续批处理Continuous Batching就是刚需。这里插一句很多人会拿 vLLM、SGLang、Ollama 三者对比。我的经验是这样Ollama 适合本地开发和快速验证开箱即用SGLang 在结构化输出和复杂推理链路上有优势vLLM 则是通用高吞吐场景下最稳的选择生态成熟、文档全、社区活跃。选型没有绝对的对错关键看你的瓶颈在哪。1.3 这篇文章会带你走完哪些路我打算把整个流程拆成四块环境准备与安装、服务启动与参数配置、显存调优的实战方法、以及踩坑排查。每一块我都会给出具体的命令、参数含义和背后的计算逻辑不是那种复制粘贴就能跑的流水账而是让你明白每个参数为什么这么设。看完之后你应该能做到在一台干净的 Linux 机器上从零把 vLLM 跑起来并且根据自己显卡的显存大小算出合理的配置。2. 安装前的环境准备与依赖梳理2.1 硬件与驱动的硬性门槛vLLM 对硬件有明确要求这个绕不过去。首先是 GPU必须是 NVIDIA 的卡计算能力 7.0 及以上也就是 Volta 架构之后常见的 V100、T4、A10、A100、H100、RTX 30/40 系都支持。AMD 的卡 vLLM 也有支持但生态没那么成熟这里不展开。驱动方面我建议 CUDA 驱动版本至少 12.1 以上。为什么强调这个因为 vLLM 的预编译 wheel 包是针对特定 CUDA 版本编译的驱动太老会导致加载时报符号找不到的错误。你可以用下面这条命令确认当前环境nvidia-smi输出里重点看两处右上角的CUDA Version这是驱动支持的最高 CUDA 版本不是已安装的以及显卡型号和显存大小。显存大小直接决定了你后面能跑多大的模型这个数字要记牢。提示nvidia-smi显示的 CUDA Version 是驱动能支持的上限实际运行时用的是 PyTorch 自带的 CUDA runtime两者不是一回事别搞混。2.2 Python 环境与虚拟环境隔离Python 版本我推荐 3.10 或 3.11这两个版本在 vLLM 的兼容性测试里覆盖最全。3.12 也能跑但偶尔会遇到某些依赖包还没出对应 wheel 的情况需要现场编译比较折腾。3.9 及以下就别用了很多新特性不支持。虚拟环境这一步千万别省。我见过太多人直接在系统 Python 里 pip install结果把系统包搞乱后面想回退都难。用 conda 或者 venv 都行我个人习惯 conda因为管理多版本 CUDA 环境方便conda create -n vllm python3.11 -y conda activate vllm创建完之后确认一下 pip 的版本顺手升级到最新python -m pip install --upgrade pip2.3 安装 vLLM 的两种路径安装 vLLM 有两条路直接 pip 装预编译包或者从源码编译。99% 的人应该走第一条路。路径一pip 直接安装pip install vllm这条命令会自动拉取匹配你 CUDA 版本的 wheel 包。但这里有个坑pip 默认会装最新版而最新版可能对应的是最新的 CUDA 版本。如果你的驱动比较老装完启动会报错。这时候你需要指定版本比如pip install vllm0.6.3版本号怎么选去 vLLM 的官方 release 页面看每个版本对应的 CUDA 版本和 PyTorch 版本挑一个和你驱动匹配的。我一般会先看 PyTorch 的版本要求因为 vLLM 是构建在 PyTorch 之上的。路径二从源码编译只有两种情况需要走这条路一是你要改 vLLM 的源码做二次开发二是你的硬件或 CUDA 版本太特殊没有现成的 wheel。源码编译对环境的完整性要求很高需要装 CUDA Toolkit、ninja、cmake 等一堆东西编译时间动辄半小时以上。除非必要不建议新手尝试。2.4 验证安装是否成功装完之后别急着跑模型先做个最小验证python -c import vllm; print(vllm.__version__)能打印出版本号就说明基础安装没问题。如果报ImportError或者undefined symbol之类的错误大概率是 CUDA 版本不匹配回到上一步重新选版本。注意如果你在 Windows 上想跑 vLLM官方是不支持原生 Windows 的必须走 WSL2。社区里有人折腾过 Windows 社区版但坑很多不建议在生产环境用。老老实实用 Linux省心。3. 启动服务从命令行到参数详解3.1 最简启动命令长什么样vLLM 装好之后启动一个 OpenAI 兼容的 API 服务只需要一行命令vllm serve Qwen/Qwen2.5-7B-Instruct这条命令背后做了几件事从 HuggingFace 下载模型权重如果本地没有缓存、加载模型到 GPU、启动一个 HTTP 服务默认监听 8000 端口提供和 OpenAI API 完全兼容的接口。也就是说你原来调 OpenAI 的代码只要把 base_url 改成http://localhost:8000/v1几乎不用改别的就能跑。模型名称这里写的是 HuggingFace 上的仓库 ID。如果你已经把模型下载到本地了直接写本地路径也行vllm serve /data/models/Qwen2.5-7B-Instruct3.2 那些你必须搞懂的启动参数光跑起来不够参数调不对性能和显存都会出问题。我把最关键的几个参数拎出来讲。--tensor-parallel-size简称 TP这个参数控制模型在几张卡上做张量并行。如果你只有一张卡保持默认 1 就行。如果是两张卡跑一个 70B 的模型就设成 2。注意 TP 的大小必须能整除模型的注意力头数否则会报错。比如模型有 32 个注意力头TP 设成 3 就不行。--gpu-memory-utilization这是显存调优的核心参数取值 0 到 1默认 0.9。它表示 vLLM 允许使用单卡总显存的百分比。为什么默认是 0.9 而不是 1.0因为要留一点余量给 CUDA context、临时缓冲区和其他进程。如果你这张卡是独占的可以调到 0.95如果还要跑别的服务就得往下调比如 0.7 或 0.8。--max-model-len模型支持的最大上下文长度。这个值设得越大KV Cache 占的显存越多。很多模型标称支持 32K 甚至 128K 上下文但你的显存不一定扛得住。启动时如果不指定vLLM 会尝试用模型配置里的最大值这时候很容易 OOM。我的建议是按实际业务需要设业务只需要 4K 上下文就别设 32K。--max-num-seqs同时处理的最大请求数也就是并发批大小。这个值越大吞吐越高但显存占用也越大。默认值通常是 256实际能跑多少取决于你的显存和序列长度。--dtype数据类型可选auto、half、float16、bfloat16、float32。默认auto会跟随模型配置。A100 及以上建议用bfloat16数值稳定性更好老卡用float16。3.3 一个生产级的启动示例把上面的参数组合起来我给一个实际项目里用过的启动命令vllm serve /data/models/Qwen2.5-7B-Instruct \ --host 0.0.0.0 \ --port 8000 \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.85 \ --max-model-len 8192 \ --max-num-seqs 128 \ --dtype bfloat16 \ --served-model-name qwen-7b逐个解释一下我的考量。--host 0.0.0.0是为了让局域网内其他机器也能访问如果只在本机用写127.0.0.1更安全。--gpu-memory-utilization 0.85是因为这台机器上还跑了一个小的 embedding 服务得留显存。--max-model-len 8192是业务侧确认过的最大输入长度没必要开更大。--served-model-name是给模型起个别名调用的时候用这个别名方便以后换模型不改客户端代码。3.4 启动过程发生了什么命令敲下去之后终端会刷一堆日志。我带你读懂几个关键节点。首先是加载 tokenizer 和模型配置然后是权重加载。这一步会显示进度条7B 模型大概几十秒70B 模型可能要几分钟。权重加载完之后vLLM 会做一次profiling run也就是跑一个假请求来测量显存占用然后根据--gpu-memory-utilization反推能分配多少 block 给 KV Cache。日志里会打印类似这样的信息GPU KV cache size: 123,456 tokens Maximum concurrency for 8192 tokens per request: 15.07x这行信息非常有用。它告诉你当前配置下 KV Cache 能存多少 token以及在 8192 长度下能支持多少倍并发。如果这个并发数太低说明你的显存不够或者 max-model-len 设太大了需要调整。最后看到Uvicorn running on http://0.0.0.0:8000就说明服务起来了。4. 显存调优把每一 MB 都花在刀刃上4.1 先算清楚显存都花在哪了调优的前提是知道钱花哪了。vLLM 的显存占用主要分三块第一块是模型权重。这部分是固定的7B 模型 fp16 约 14Gbf16 也是 14Gint8 量化约 7Gint4 约 3.5G。计算公式很简单参数量 × 每参数字节数。7B × 2 字节 14G。第二块是KV Cache。这是动态的也是调优空间最大的部分。每个 token 的 KV Cache 大小可以用这个公式估算每 token KV 大小 2 × 层数 × 注意力头数 × 头维度 × 数据类型字节数以 Qwen2.5-7B 为例28 层28 个注意力头GQA 下 KV 头数是 4头维度 128bf16 是 2 字节。算下来每 token 约 0.11 MB。8192 个 token 就是约 900 MB。如果并发 128那就是 115G显然一张 40G 的卡放不下。所以实际能支持的并发远小于 128这就是为什么启动日志里的并发数很重要。第三块是激活值和临时缓冲区。这部分相对小但也不能忽略通常留 1 到 2G 余量。4.2 gpu-memory-utilization 到底怎么设这个参数的本质是告诉 vLLM 你可以用这么多显存剩下的我自己留着。vLLM 会先扣掉模型权重和激活值的开销剩下的全部拿来做 KV Cache。设太高的风险是 OOM因为 CUDA 在运行过程中会有一些不可预测的临时分配。设太低的代价是 KV Cache 变小并发上不去。我的经验值是场景建议值理由显卡独占只跑 vLLM0.90 - 0.95榨干显存最大化并发同卡还有其他小服务0.70 - 0.85给其他进程留空间多卡 TP卡间通信频繁0.85 - 0.90NCCL 通信需要缓冲区调试阶段0.60 - 0.70留足余量方便排查提示如果你不确定从 0.8 开始试跑起来看日志里的并发数再逐步往上加。每次加 0.05观察是否稳定。4.3 max-model-len 与并发的权衡这是最容易被忽视的调优点。很多人觉得上下文越长越好直接拉满 32K结果并发掉到个位数服务基本没法用。核心矛盾在于KV Cache 总容量是固定的序列长度和并发数是此消彼长的关系。总 token 容量 序列长度 × 并发数。你把序列长度翻倍并发就减半。所以正确的做法是先问业务侧单条请求最长可能多长如果业务是客服问答输入加输出一般不超过 2K那就设 4096 绰绰有余。如果是文档摘要可能要 8K 或 16K。设成业务实际需要的 1.5 倍作为缓冲就够了不要盲目拉满。如果确实需要长上下文又要高并发那就只能上量化或者加卡。量化的代价是精度损失加卡的代价是成本这个取舍得根据项目实际情况定。4.4 量化显存不够时的救命稻草当显存实在不够又不想加卡时量化是唯一的选择。vLLM 支持几种量化方式AWQ和GPTQ是训练后量化需要模型本身有对应的量化版本。HuggingFace 上很多模型都有-AWQ或-GPTQ后缀的版本直接下载就能用。4bit 量化能把 7B 模型的权重压到 4G 左右显存占用大幅下降。FP8是较新的方案在 H100 等支持 FP8 的卡上效果很好精度损失比 4bit 小但需要硬件支持。启动量化模型很简单模型路径指向量化版本即可vLLM 会自动识别量化配置vllm serve /data/models/Qwen2.5-7B-Instruct-AWQ \ --quantization awq \ --gpu-memory-utilization 0.9 \ --max-model-len 8192我的实测经验是AWQ 4bit 在对话任务上效果损失很小肉眼几乎看不出差别但在需要精确计算或代码生成的任务上会有可感知的下降。所以量化不是万能的得看业务能不能接受。4.5 一个真实的调优案例说个具体的。之前有个项目单卡 A100 40G要跑 Qwen2.5-14B业务要求 8K 上下文并发至少 20。第一步算权重14B × 2 字节 28G。40G 减去 28G只剩 12G 给 KV Cache 和激活值。激活值留 2GKV Cache 只有 10G。第二步算每 token KV 大小。14B 模型 48 层KV 头数 8头维度 128bf162 × 48 × 8 × 128 × 2 196608 字节 ≈ 0.19 MB/token。第三步算并发。10G / 0.19MB 约 52000 token。8192 长度下52000 / 8192 ≈ 6.3 并发。离要求的 20 差得远。结论这个配置下 fp16 根本达不到要求。解决方案有两个一是上 AWQ 4bit 量化权重降到 7GKV Cache 能到 30G并发能到 19 左右勉强够二是加一张卡做 TP2每张卡权重 14GKV Cache 空间翻倍。最后客户选了量化方案因为加卡成本太高。这个案例说明调优不是拍脑袋设参数而是要先把账算清楚。算完你就知道瓶颈在哪该往哪个方向优化。5. 常见问题排查与避坑实录5.1 启动就 OOM 怎么办这是最高频的问题。报错信息通常是torch.cuda.OutOfMemoryError。排查顺序如下先看是不是--max-model-len设太大了。很多人不设这个参数vLLM 就用模型配置里的最大值比如 32K直接爆显存。解决办法是显式指定一个合理的值。再看--gpu-memory-utilization是不是太高。如果设了 0.95试着降到 0.85。如果还不行检查是不是有其他进程占着显存。用nvidia-smi看有没有残留的 Python 进程有的话 kill 掉。最后考虑量化或者换更小的模型。5.2 模型下载慢或失败HuggingFace 在国内访问不稳定这是老问题了。解决办法是提前用huggingface-cli download把模型拉到本地或者用镜像站。下载的时候指定本地目录huggingface-cli download Qwen/Qwen2.5-7B-Instruct --local-dir /data/models/Qwen2.5-7B-Instruct下载完之后启动时直接指向本地路径就不会再联网了。5.3 服务起来了但请求报错常见的有几类。一是模型名称对不上客户端请求里的 model 字段必须和--served-model-name一致。二是请求的 max_tokens 加上输入长度超过了--max-model-len会被拒绝。三是并发太高触发了限流可以调大--max-num-seqs。我整理了一个速查表报错现象可能原因解决方向CUDA out of memory显存不足降 max-model-len 或 gpu-memory-utilizationModel not found模型名不匹配检查 served-model-nameContext length exceeded输入超长调大 max-model-len 或截断输入Connection refused服务没起来检查端口占用和启动日志请求超时并发过高或序列过长调低并发或缩短输出5.4 性能不如预期如果吞吐上不去先看启动日志里的并发数。并发数低说明 KV Cache 不够要么加显存要么减序列长度。如果并发数够但吞吐还是低检查是不是--max-num-seqs设太小了默认 256 一般够用但如果你的请求都很短可以适当调大。还有一个容易被忽略的点客户端是不是串行发请求的。vLLM 的连续批处理只有在多个请求同时到达时才能发挥作用。如果你用一个 for 循环一条一条发那再强的框架也救不了。压测的时候记得用并发工具比如wrk或者自己写多线程脚本。5.5 几个我踩过的坑第一个坑在 WSL2 里跑 vLLM显存识别不对。WSL2 的显存是动态分配的nvidia-smi显示的显存和实际可用不一致导致gpu-memory-utilization算不准。生产环境还是老老实实用原生 Linux。第二个坑多卡 TP 时卡间通信拖慢速度。如果卡之间不是 NVLink 而是 PCIeTP 的加速比会打折扣。TP2 可能只有 1.6 倍加速TP4 可能只有 2.5 倍。这时候要考虑是不是用流水线并行更合适。第三个坑模型权重格式不兼容。有些模型是 safetensors 格式有些是 bin 格式vLLM 都支持但如果模型目录里同时有两种格式的文件可能会加载出错。清理一下只留一种。第四个坑升级 vLLM 版本后配置失效。vLLM 迭代很快某些参数在新版本里被重命名或废弃了。升级前先看 release notes别盲目pip install -U。6. 关于部署这件事我最后想说的写了这么多其实核心就一句话vLLM 的调优本质是显存的分配艺术。你得先算清楚权重占多少、KV Cache 要多少、激活值留多少然后在这个约束下找并发和延迟的平衡点。没有一套参数能通吃所有场景别人的最优配置搬到你这可能就是灾难。我个人的习惯是每上一个新模型先花十分钟把账算一遍用纸笔或者计算器都行算出理论上的并发上限然后再启动服务验证。这样心里有底出了问题也知道往哪个方向调。比盲目试参数高效得多。另外提醒一句vLLM 的版本更新非常快几乎每个月都有新特性。但生产环境不要追新选一个稳定版本验证没问题之后就锁死等有明确需求再升级。我见过太多因为升级框架导致线上服务挂掉的案例血的教训。如果你在部署过程中遇到什么奇怪的问题欢迎在评论区交流。我踩过的坑基本都写在这了希望能帮你少走点弯路。
返回列表