ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

vLLM推理引擎实战:从部署调优到23倍性能提升

vLLM推理引擎实战:从部署调优到23倍性能提升 1. 为什么我最终把推理引擎换成了 vLLM1.1 从一次压测说起23 倍差距到底出在哪先说结论这个“23 倍”不是营销数字是我自己在一台单卡 24G 显存的机器上用同一份模型权重、同一批请求、同一套并发压测脚本跑出来的对比结果。对照组是当时最顺手的 HuggingFace Transformers 原生generate()推理实验组是 vLLM。测试模型是一个 7B 级别的中文对话模型输入长度 512输出长度 256并发 32连续打 200 条请求。原生方案吞吐大概在每秒 3 到 4 个请求vLLM 直接干到每秒 70 以上峰值接近 80。这个倍数不是线性叠加出来的而是几个机制叠加后的结果。很多人第一次看到这个数字会怀疑是不是测试方法有问题。我一开始也怀疑所以专门做了交叉验证换模型、换输入长度、换并发数甚至把 vLLM 的gpu_memory_utilization从 0.9 调到 0.8 再跑一遍倍数会波动但基本都落在 15 到 25 倍这个区间。所以“23 倍”是一个有代表性的中位数不是极限值。那这 23 倍到底从哪来核心是三件事PagedAttention 的显存管理、连续批处理continuous batching、KV Cache 的高效复用。下面我一个个拆。先说 KV Cache。大模型自回归生成时每生成一个 token都要把之前所有 token 的 Key 和 Value 缓存下来避免重复计算。原生实现里这块缓存是按“最大可能长度”预分配的连续显存比如你设max_length2048那不管实际输出多长每条请求都先占掉 2048 长度的空间。并发一上来显存瞬间被这些“预留但没用满”的空间吃光只能排队。vLLM 的 PagedAttention 把 KV Cache 切成固定大小的 block像操作系统管理内存页一样按需分配用多少给多少碎片率极低。同样 24G 显存原生方案可能只能塞 4 到 6 条并发vLLM 能塞 30 条以上。并发数上去了GPU 利用率自然就上去了。再说连续批处理。原生批处理是“静态批”凑齐一批请求一起跑完再跑下一批。问题是同一批里有的请求输出 20 个 token 就结束了有的要输出 500 个短的必须等长的GPU 在等待期间大量空转。vLLM 是“连续批”某个请求一结束立刻把排队的新请求塞进这个空位batch 始终是满的。这个机制对吞吐的提升非常直接尤其在请求长度参差不齐的真实场景里效果比 PagedAttention 还明显。最后是 KV Cache 复用。多轮对话、few-shot 提示、系统提示词固定这些场景前缀部分是完全一样的。vLLM 支持前缀缓存prefix caching相同前缀只算一次后续请求直接命中缓存。我实测过一个客服场景系统提示词有 800 多 token开启前缀缓存后首 token 延迟从 400ms 降到 90ms 左右。所以这 23 倍不是单一技术带来的是“显存利用率提升 GPU 空转减少 重复计算消除”三件事乘在一起的结果。理解了这一点你才知道后面调参该往哪个方向使劲。1.2 vLLM 到底解决了谁的痛点vLLM 不是给所有人准备的。如果你只是本地跑个模型自己聊天一天问不了几十句那 Ollama 或者 LM Studio 更省事双击就能用没必要折腾 vLLM。vLLM 的价值在“服务化”和“高并发”这两个词上。具体来说这几类人最该上 vLLM要做 API 服务的人你有一个模型想对外提供 OpenAI 兼容接口让多个客户端同时调用这时候吞吐和并发就是命根子。要做批量推理的人比如一次性跑几千条数据做标注、做数据清洗、做评测原生方案可能要跑一整天vLLM 几个小时就完事。显存紧张但并发要求高的人单卡 24G 想服务 7B 甚至 14B 模型原生方案并发上不去vLLM 的显存管理能让你多塞好几倍的请求。要做多轮对话或 RAG 的人前缀缓存和 KV 复用对这类场景是刚需能显著降低延迟。反过来说如果你追求的是“零配置、开箱即用”或者你的机器是纯 CPU、没有像样的 GPU那 vLLM 的收益会大打折扣甚至跑不起来。这一点我在后面“纯 CPU 模式”那一节会专门讲。1.3 和 Ollama、SGLang 这些方案的取舍热词里出现了vllm ollama、sglang和vllm说明很多人在这几个方案之间纠结。我自己的使用体感是这样的方案定位上手难度并发吞吐适合场景Ollama个人本地体验极低低单人聊天、快速试模型vLLM生产级推理服务中等极高API 服务、批量推理、高并发SGLang高性能推理 复杂编排中等偏高极高结构化输出、复杂 prompt 流程LM Studio桌面端图形化极低低非技术用户、演示Ollama 底层其实也做了不少优化但它的设计目标是“让个人用户无痛跑模型”所以在并发调度、显存精细管理上不如 vLLM 激进。你拿 Ollama 做单机多卡部署或者拿它扛几十路并发会很吃力。SGLang 和 vLLM 是同一梯队的SGLang 在结构化生成比如强制 JSON 输出、正则约束和 RadixAttention 上有优势vLLM 在生态成熟度、模型支持广度、社区文档上更稳。我的建议是先上 vLLM把服务跑通遇到结构化输出的强需求再考虑 SGLang不要一上来就选最复杂的。2. 部署前的环境准备与选型决策2.1 硬件怎么选显存、卡数、互联部署 vLLM 第一件事是算显存账。模型权重占多少、KV Cache 占多少、激活值占多少这三块加起来不能超过可用显存。模型权重的粗算公式是参数量 × 精度字节数。7B 模型 FP16 大约 14GINT8 大约 7GINT4 大约 3.5G。14B FP16 约 28G单张 24G 卡放不下要么量化要么上多卡。这里有个坑很多人以为 24G 卡能跑 14B FP16结果加载到一半就 OOM因为除了权重还有 KV Cache 和框架开销。KV Cache 的估算稍微复杂一点公式是KV Cache 大小 2 × 层数 × 注意力头数 × head_dim × 序列长度 × 并发数 × 精度字节数以 7B 模型为例32 层、32 头、head_dim 128FP16 下每个 token 的 KV 占用约 0.5MB。如果你要支持 4096 序列长度、并发 32那就是 0.5MB × 4096 × 32 ≈ 64G。这个数字一看就超了所以实际部署时要么降并发要么降序列长度要么用量化。vLLM 的gpu_memory_utilization参数就是用来控制“留给 KV Cache 多少显存”的默认 0.9意思是 90% 显存给模型和缓存剩下 10% 留给系统和框架。多卡部署的话卡间互联很关键。NVLink 的卡比如 A100、H100做张量并行效率很高PCIe 互联的卡比如 4090做张量并行会有通信瓶颈这时候数据并行可能更划算。热词里有vllm 单机多卡部署我后面会专门讲张量并行和数据并行的选择。2.2 软件栈CUDA、PyTorch、Python 版本匹配vLLM 对版本很敏感装错版本是最常见的翻车原因。我踩过的坑包括CUDA 12.1 配了为 CUDA 11.8 编译的 PyTorch结果 import 就报错Python 3.12 装了老版本 vLLM依赖解析直接失败。我的建议是锁定一套经过验证的组合。以目前主流的环境为例CUDA 12.1 或 12.4PyTorch 2.3 到 2.5Python 3.10 或 3.113.12 部分依赖还没跟上vLLM 0.6.x 及以上安装命令我一般这样写# 先建独立环境别污染系统 Python conda create -n vllm python3.11 -y conda activate vllm # 装 PyTorch注意 CUDA 版本要和驱动匹配 pip install torch2.4.0 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 # 装 vLLM pip install vllm0.6.3注意不要用pip install vllm不带版本号它会拉最新版可能和你现有的 PyTorch 冲突。生产环境一定要锁版本。如果你在 Windows 上情况会复杂一些。热词里有windows vllm安裝和vllm 0.29 wsl2说明不少人在 Windows 上折腾。我的直接建议是Windows 原生装 vLLM 体验很差优先用 WSL2。WSL2 里跑 Linux 环境CUDA 直通做得不错vLLM 能正常用。原生 Windows 的话很多依赖比如 flash-attn编译困难容易卡在安装环节。2.3 模型从哪来权重格式与下载vLLM 支持 HuggingFace 格式的权重也支持 safetensors。下载方式有两种一是用huggingface-cli二是让 vLLM 启动时自动拉。我推荐提前下好因为自动拉如果网络抖动启动会失败。# 提前下载到本地目录 huggingface-cli download Qwen/Qwen2.5-7B-Instruct --local-dir ./models/qwen2.5-7b提示模型目录里要有config.json、tokenizer.json、*.safetensors这些文件。如果缺 configvLLM 会报model class not found之类的错热词里那个model class minimaxh3modularpipeline not found就是这类问题通常是模型架构不被当前 vLLM 版本支持或者 config 里的architectures字段写错了。对于比较新的模型比如热词里提到的qwen3.6-27b、minimax-h3、deepseek v4.1 flash一定要先确认你装的 vLLM 版本是否支持。vLLM 的模型支持列表更新很快但新模型往往要等一两个版本。遇到不支持的要么升级 vLLM要么等社区适配。3. 核心参数调优把 23 倍真正跑出来3.1 启动参数逐个拆解vLLM 的启动命令看起来简单但每个参数都影响性能和稳定性。我拿一个典型的单卡 7B 部署命令来拆python -m vllm.entrypoints.openai.api_server \ --model ./models/qwen2.5-7b \ --served-model-name qwen2.5-7b \ --host 0.0.0.0 \ --port 8000 \ --gpu-memory-utilization 0.9 \ --max-model-len 8192 \ --max-num-seqs 64 \ --tensor-parallel-size 1 \ --enable-prefix-caching \ --dtype auto--gpu-memory-utilization 0.9这是最关键的参数之一。它决定 vLLM 能用多少显存。设太高比如 0.95容易 OOM设太低比如 0.7KV Cache 空间不够并发上不去。我的经验是 0.85 到 0.92 之间比较稳具体看你的卡上还跑不跑别的东西。如果卡是独占的0.9 没问题如果还要留显存给别的进程降到 0.8。--max-model-len 8192模型支持的最大序列长度。这个值直接决定 KV Cache 的单请求占用。设得越大能支持的上下文越长但并发数会下降。很多人无脑设 32768结果并发掉到个位数。正确做法是按你的实际业务需求设。如果业务里最长对话就 4000 token设 8192 足够没必要设 32768。--max-num-seqs 64同时处理的最大请求数。这个值和显存、序列长度是联动的。显存够、序列短可以设大反之设小。我一般从 32 开始试逐步往上加观察显存和吞吐。--enable-prefix-caching开启前缀缓存。多轮对话、固定系统提示词的场景必开能显著降首 token 延迟。但要注意如果每个请求的前缀都不一样开了反而有额外开销因为要维护缓存索引。--dtype auto让 vLLM 自动判断精度。一般会选 FP16 或 BF16。如果你的卡支持 BF16比如 A100、H100、4090优先 BF16数值稳定性更好。3.2 显存不够怎么办量化与并行显存不够有三条路量化、张量并行、降序列长度。我按推荐顺序说。量化是最省事的。vLLM 支持 AWQ、GPTQ、FP8 等量化格式。AWQ 4bit 能把 7B 模型压到 4G 左右14B 压到 8G 左右质量损失很小。用法是直接加载量化后的权重python -m vllm.entrypoints.openai.api_server \ --model ./models/qwen2.5-7b-awq \ --quantization awq \ --gpu-memory-utilization 0.9注意量化模型要下对应的量化权重不能拿 FP16 权重硬套--quantization awq会报错。张量并行适合多卡。--tensor-parallel-size 2表示把模型切到两张卡上。切分后每张卡只存一半权重显存压力减半。但张量并行有通信开销卡间互联越快越好。NVLink 的卡开张量并行很香PCIe 的卡要谨慎可能并行后反而变慢。数据并行是另一种思路每张卡跑一个完整的模型副本请求分发到不同卡上。vLLM 本身对数据并行的支持不如张量并行直接通常要配合外部负载均衡。如果你的卡是 PCIe 互联数据并行往往比张量并行更划算。3.3 并发与吞吐的平衡点怎么找调参的本质是在“并发数”和“单请求延迟”之间找平衡。并发越高吞吐越大但每个请求的延迟也会上升因为 GPU 要分时处理更多请求。我的调参方法是固定其他参数只动--max-num-seqs从 16 开始每次翻倍记录吞吐和 P99 延迟。画出来大概是这样一条曲线并发从 16 到 64吞吐快速上升延迟缓慢上升从 64 到 128吞吐上升变缓延迟快速上升超过 128吞吐基本不涨延迟爆炸。那个“吞吐上升变缓、延迟开始陡增”的拐点就是你的最佳并发数。这个拐点因模型、卡、序列长度而异没有万能值。7B 模型在 24G 卡上序列 4096拐点通常在 48 到 80 之间。14B 模型可能只有 16 到 32。实操心得不要迷信别人给的参数一定要自己压测。我见过有人直接抄网上的--max-num-seqs 256结果显存直接爆掉服务起不来。4. 完整实操从零把服务跑起来4.1 单卡部署全流程假设你有一张 24G 卡要部署一个 7B 模型提供 OpenAI 兼容接口。完整流程如下。第一步确认环境nvidia-smi # 看驱动和 CUDA 版本 python -c import torch; print(torch.__version__, torch.cuda.is_available())第二步建环境装依赖前面 2.2 节已经给过命令。第三步下载模型huggingface-cli download Qwen/Qwen2.5-7B-Instruct --local-dir ./models/qwen2.5-7b第四步启动服务python -m vllm.entrypoints.openai.api_server \ --model ./models/qwen2.5-7b \ --served-model-name qwen2.5-7b \ --host 0.0.0.0 \ --port 8000 \ --gpu-memory-utilization 0.9 \ --max-model-len 8192 \ --max-num-seqs 64 \ --enable-prefix-caching启动日志里会打印显存分配情况、KV Cache block 数量、支持的并发数。看到Application startup complete就说明起来了。第五步验证curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen2.5-7b, messages: [{role: user, content: 你好介绍一下你自己}], max_tokens: 128 }能正常返回就说明服务通了。4.2 单机多卡部署的两种姿势热词里vllm 单机多卡部署是高频问题。假设你有两张 24G 卡要部署 14B 模型。姿势一张量并行。把模型切到两张卡上python -m vllm.entrypoints.openai.api_server \ --model ./models/qwen2.5-14b \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.9 \ --max-model-len 8192这种方式下两张卡协同处理同一个请求显存压力均摊。适合 NVLink 互联的卡。姿势二数据并行。起两个 vLLM 实例各占一张卡前面挂个负载均衡# 实例 1 CUDA_VISIBLE_DEVICES0 python -m vllm.entrypoints.openai.api_server \ --model ./models/qwen2.5-7b --port 8001 --gpu-memory-utilization 0.9 # 实例 2 CUDA_VISIBLE_DEVICES1 python -m vllm.entrypoints.openai.api_server \ --model ./models/qwen2.5-7b --port 8002 --gpu-memory-utilization 0.9然后用 Nginx 或别的负载均衡把请求分发到 8001 和 8002。这种方式适合 PCIe 互联的卡因为两个实例之间没有通信开销。怎么选我的经验是NVLink 卡优先张量并行PCIe 卡优先数据并行。张量并行的通信量很大PCIe 带宽扛不住会拖慢整体速度。4.3 纯 CPU 模式能不能用热词里有vllm 纯cpu 模式说明有人想在没 GPU 的机器上跑。我的结论是能跑但不推荐。vLLM 确实支持 CPU 后端但性能非常有限。7B 模型在纯 CPU 上吞吐可能只有每秒零点几个 token比 GPU 慢几十倍。而且 CPU 模式对内存要求很高7B FP16 要 14G 以上内存加上 KV Cache32G 内存的机器都很紧张。如果你真的只有 CPU我的建议是要么用 llama.cpp 这类专门为 CPU 优化的方案要么直接上云 GPU。vLLM 的 CPU 模式更适合做功能验证不适合生产。4.4 用 Docker 一键部署生产环境我强烈建议用 Docker环境隔离干净迁移方便。vLLM 官方有镜像docker run --gpus all \ -v ./models:/models \ -p 8000:8000 \ --shm-size 16g \ vllm/vllm-openai:latest \ --model /models/qwen2.5-7b \ --gpu-memory-utilization 0.9 \ --max-model-len 8192注意--shm-size一定要设大默认的 64M 会导致多进程通信失败。我一般设 16g。这个坑我踩过服务启动到一半报共享内存错误排查了半天。5. 常见报错与排查速查表5.1 启动阶段报错报错信息原因解决CUDA out of memory显存不够降gpu-memory-utilization、降max-model-len、用量化model class not found模型架构不支持升级 vLLM或换支持的模型No module named vllm._C安装不完整重装 vLLM确认 CUDA 版本匹配Address already in use端口被占换端口或 kill 掉占用进程shared memory相关错误shm 太小Docker 加--shm-size 16g5.2 运行阶段问题问题一首 token 延迟很高。原因通常是没开前缀缓存或者 prompt 太长。解决开--enable-prefix-caching检查 prompt 是否有多余内容。问题二吞吐上不去。先看 GPU 利用率如果只有 30% 到 50%说明并发不够调大--max-num-seqs。如果 GPU 利用率已经 90% 以上说明到瓶颈了要么加卡要么量化。问题三输出乱码或重复。通常是精度问题或采样参数问题。检查--dtype是否设成了 FP16 但模型是 BF16 训练的或者 temperature、top_p 设得不合理。问题四多卡部署后反而变慢。大概率是张量并行的通信瓶颈。PCIe 卡换成数据并行试试。5.3 独家避坑技巧启动前先nvidia-smi看显存确认没有别的进程占着卡。我遇到过残留进程占着 10G 显存导致 vLLM 起不来。模型路径用绝对路径相对路径在某些启动方式下会解析错误。日志级别调到 INFO 以上vLLM 启动时会打印显存分配和 KV Cache 信息这些信息对调参很有用。压测用真实数据别用固定长度的假数据。真实请求长度参差不齐才能测出连续批处理的真实效果。监控显存和 GPU 利用率用nvidia-smi -l 1或者更专业的监控工具观察服务运行时的资源曲线。6. 性能压测怎么复现那 23 倍6.1 压测工具与脚本复现性能对比需要一套可重复的压测脚本。我用的是 Python 的aiohttp做异步并发请求import asyncio import aiohttp import time async def send_request(session, url, payload): async with session.post(url, jsonpayload) as resp: return await resp.json() async def benchmark(url, concurrency, total): payload { model: qwen2.5-7b, messages: [{role: user, content: 写一段200字的产品介绍}], max_tokens: 256 } async with aiohttp.ClientSession() as session: start time.time() tasks [] for _ in range(total): tasks.append(send_request(session, url, payload)) if len(tasks) concurrency: await asyncio.gather(*tasks) tasks [] if tasks: await asyncio.gather(*tasks) elapsed time.time() - start print(f并发 {concurrency}总请求 {total}耗时 {elapsed:.2f}s吞吐 {total/elapsed:.2f} req/s) asyncio.run(benchmark(http://localhost:8000/v1/chat/completions, 32, 200))这个脚本能测出吞吐但测延迟分布要更复杂一点需要记录每个请求的耗时。我一般会加一个 P50、P95、P99 的统计。6.2 对比测试的公平性要让对比有意义必须控制变量同一份模型权重不要一个用 FP16 一个用 AWQ。同一批输入数据长度分布要一致。同一台机器同一张卡。同样的输出长度max_tokens设一样。预热后再测第一次请求有编译和缓存开销要排除掉。我见过有人拿 vLLM 的预热后数据和原生方案的冷启动数据比那倍数能到 50 倍但不公平。正确做法是两边都预热都跑稳定态。6.3 结果解读与瓶颈定位跑出数据后怎么判断瓶颈在哪我的方法是这样GPU 利用率低、显存占用低瓶颈在调度并发不够调大max-num-seqs。GPU 利用率高、显存占用高到硬件瓶颈了考虑量化或加卡。GPU 利用率高、显存占用低可能是计算瓶颈检查是否开了 flash-attn。延迟高但吞吐正常可能是单请求处理慢检查序列长度和模型大小。那 23 倍是怎么来的原生方案在并发 32 时GPU 利用率只有 40% 左右大量时间在等显存和等批处理。vLLM 在同样并发下GPU 利用率能到 90% 以上显存利用率也高。两者一对比吞吐差距就出来了。7. 进阶玩法与后续扩展7.1 前缀缓存与多轮对话优化多轮对话场景前缀缓存是必开的。但有个细节vLLM 的前缀缓存是基于 block 的只有完整 block 的前缀才能命中。如果你的系统提示词长度不是 block 大小的整数倍命中率会打折。block 大小默认是 16可以通过--block-size调整。我一般保持默认因为调了可能影响其他优化。7.2 和 LoRA 微调结合热词里有lora微调实战教程qwen说明有人想把微调和推理串起来。vLLM 支持动态加载 LoRA 适配器不用把 LoRA 合并进基础模型python -m vllm.entrypoints.openai.api_server \ --model ./models/qwen2.5-7b \ --enable-lora \ --lora-modules my-lora./loras/my-lora这样基础模型只加载一份多个 LoRA 共享显存利用率很高。适合一个基础模型服务多个微调任务的场景。7.3 结构化输出与工具调用vLLM 支持 guided decoding可以强制模型输出 JSON、正则匹配的内容。这对做 Agent、做数据抽取很有用。用法是在请求里加guided_json或guided_regex参数。这个功能在需要严格格式的场景下比让模型自由发挥再解析要可靠得多。7.4 监控与运维生产环境要监控几个指标请求量、延迟分布、GPU 利用率、显存占用、KV Cache 命中率。vLLM 暴露了 Prometheus 格式的 metrics可以接 Grafana 做可视化。我一般会盯 P99 延迟和 KV Cache 命中率前者反映用户体验后者反映前缀缓存的效果。最后分享一个我自己的体会vLLM 的调参没有一劳永逸的配置业务变了、模型换了、请求模式变了最佳参数都会变。我现在的做法是把启动参数做成配置文件每次换场景就重新压测一遍找到新的拐点。这个过程听起来麻烦但比线上出问题再救火要省心得多。踩过几次 OOM 和延迟爆炸的坑之后我现在宁可多花半小时压测也不愿意拍脑袋填参数。
返回列表