vLLM大模型推理引擎实战:从环境配置到性能调优指南
1. vLLM到底解决了什么问题先搞懂你为什么要用它先说个实际场景。假设你自己有台显卡不错的机器比如一张A100或者RTX 4090想跑Meta的Llama 3.1 8B或者Qwen2.5这类开源大模型。你用HuggingFace Transformers直接加载模型跑推理发几个请求显存占满并发一上来延迟直接飙到几十秒GPU利用率还不到10%。这时候你大概率会想这些大模型项目根本不是给单机用户玩的吧其实不是硬件不行是推理引擎没选对。vLLM就是目前最主流的开源大模型推理引擎之一。它的核心卖点是PagedAttention这套机制把KV Cache切成小块来管理类似操作系统里的虚拟内存分页。效果就是显存利用率能提升数倍吞吐量在标准硬件上比原生Transformers脚本能高出几倍甚至一个数量级。我自己实测下来同样一张A100上跑Llama 3.1 8BTransformers脚本的吞吐大概是几十个token/s换成vLLM之后直接冲到2000 token/s配合continuous batching并发请求越多优势越明显。所以这个教程适合谁三种人。第一种刚接触大模型推理想在本地快速跑起来一个开源模型服务第二种已经在用Transformers做推理但觉得太慢太吃显存第三种想把自己训练或微调好的模型部署成OpenAI兼容API给前端或业务系统调用。只要你属于其中一类这篇教程能帮你少踩不少坑。2. 环境准备装之前先把这几件事确认好2.1 硬件和系统要求别拍脑袋vLLM目前的主力运行环境是Linux NVIDIA GPU这一点要放在最前面说。网上有人折腾Windows版社区也确实有些workaround但效果都不算理想CUDA生态、显存管理、NCCL通信在Linux下才是完全体。硬件方面我的建议很简单模型规模显存最低要求推荐GPU日常是否可用1B~3B6GBRTX 3060 / 4060很流畅7B~9B8B16GBRTX 4090 / A10基本舒适13B~14B24GBRTX 3090 / A5000能跑并发别拉太高70B80GBA100 / H100建议多卡或量化注意这是推理的要求如果你还想在vLLM里做LoRA Adapter热加载显存要再放宽。另外一个容易被忽略的点是CPU内存。模型加载时CPU内存也要够尤其用--dtype float16加载70B模型时CPU内存建议128GB起步否则加载过程直接OOM。还有一个冷门但重要的问题PCIe带宽和CPU核心数。vLLM的tokenization和调度需要CPU参与如果你用的是4核心的老CPU即使显卡很强预处理也可能成为瓶颈。这套引擎是一次请求进来后CPU做tokenize然后交给GPU做prefill和decode。CPU太弱会拖后腿。2.2 Python环境与CUDA版本选择vLLM对CUDA的版本要求不算苛刻但也不是随便装的。目前主流的vLLM 0.6.x到0.8.x版本要求Python 3.9以上推荐3.10到3.12。CUDA 12.1是最稳的组合我踩过CUDA 11.8的坑部分算子在编译时会有兼容性问题。强烈建议用conda或venv建独立环境不要直接装在系统Python里。vLLM依赖的包很多很容易跟PyTorch其他项目冲突。# 创建独立虚拟环境 conda create -n vllm python3.11 conda activate vllm # 安装CUDA版PyTorch注意cuda版本号要跟本机驱动配套 pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 # 安装vLLM pip install vllm装完验证一下python -c import vllm; print(vllm.__version__)如果输出了版本号比如0.8.4说明基础环境没问题。这里特别说一下不要用pip install vllm直接覆盖已有环境的torch版本。vLLM会拉起自己匹配的torch如果环境里已经有别的torch版本极容易冲突。要么从零建环境要么用官方Docker镜像。Docker方案其实更适合生产环境docker pull vllm/vllm-openai:latest docker run --runtime nvidia --gpus all -p 8000:8000 \ vllm/vllm-openai:latest \ --model Qwen/Qwen2.5-7B-Instruct但如果你是本机开发调试conda环境更灵活。3. 核心思路拆解为什么vLLM能比Transformers快这么多3.1 PagedAttention核心原理一次讲透先聊KV Cache的背景。Transformer结构里每生成一个token都要计算当前token对之前所有token的注意力。这些历史token的Key和Value矩阵如果每次重新计算计算量会随着序列长度线性爆炸。所以推理框架会把它们缓存下来这个缓存就是KV Cache。KV Cache有多大对于7B模型每个token大概需要几百KB到几MB的显存空间。生成长度为2048的回复KV Cache可能占到几GB显存。传统PyTorch实现会预先分配连续的显存块不够就扩容扩容时会重新分配内存并拷贝既浪费又慢。vLLM的PagedAttention把KV Cache切成固定大小的块默认--block-size 16表示每个块存16个token的KV。这些块用类似页表的结构管理可以离散地分布在显存里不需要连续。这样显存碎片几乎被消灭了利用率能到90%以上。而且PagedAttention还带来了一个副产品细粒度的显存共享。多个采样序列如果共享部分前缀比如系统提示词、few-shot示例它们的KV Cache物理块可以共享进一步省显存。这也就是大家常说的Prefix Caching功能在多轮对话和批量请求场景下效果拔群。3.2 Continuous Batching吞吐提升的另一个引擎传统Batching是静态的一批请求必须全部完成后才开始下一批。假设一批里有5个请求3个已经答完了剩下2个还在慢慢生成那前3个的计算资源就空转。vLLM的Continuous Batching是在token级别动态调度的。某个请求生成了一个完整回答新请求立刻就能补进来不同请求生成长度不同也按实际进度分别调度。这种机制让GPU几乎一直在干活吞吐量大涨。这就是为什么官方文档一直强调vLLM的吞吐优势在高并发场景下更明显。你只发一个请求它可能也就比Transformers快一点但并发到16个甚至32个请求差距就是几倍甚至十几倍。理解了这两个核心机制后面配置参数的时候你就知道哪些参数是干嘛的不会乱调。3.3 与Transformers的对比一张表看明白对比维度HuggingFace TransformersvLLMKV Cache管理动态分配显存碎片多PagedAttention离散分块批处理机制静态batch同步等待连续批处理token级调度吞吐量8B模型A100几十~几百 token/s1000~3000 token/sOpenAI兼容API需要自己写服务内置直接启动量化支持依赖bitsandbytesGPTQ、AWQ、FP8等原生支持多卡推理需要自己写自动用TP和PP切分开发友好度高灵活中参数多但默认值合理这不是说Transformers不好它的定位是训练和灵活研究。但如果目标是服务化部署和高吞吐推理vLLM是当前更合适的选择。4. 快速上手实操从模型下载到对接API的完整流程4.1 第一个命令启动一个模型服务从vLLM 0.4.x之后最推荐的启动方式就是用vllm serve命令它会直接拉起一个OpenAI兼容的HTTP服务。模型可以是HuggingFace Hub上的模型ID也可以是本地已经下载好的路径。先试一个最小配置把Qwen2.5-7B-Instruct跑起来vllm serve Qwen/Qwen2.5-7B-Instruct \ --served-model-name qwen2.5-7b \ --gpu-memory-utilization 0.9 \ --max-model-len 8192 \ --port 8000看到日志里出现Uvicorn running on http://0.0.0.0:8000说明服务已经起来了。这里解释下三个参数--served-model-name给模型起个对外名称否则默认用模型ID。这个名称会在调用时用到。--gpu-memory-utilization允许vLLM使用的显存比例。0.9意味着留10%给模型加载和碎片。如果你机器上还要跑其他任务可以降到0.7左右。--max-model-len能处理的最大上下文长度。如果显存不够这个值要调小否则启动时直接报显存错误。4.2 用curl和Python发起首次请求服务启动后先用curl快速验证curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen2.5-7b, messages: [ {role: user, content: 用一句话介绍你自己} ], max_tokens: 128, temperature: 0.7 }返回的JSON结构里choices[0].message.content就是模型生成的回复usage字段会列出prompt_tokens、completion_tokens和total_tokens。Python端用OpenAI SDK直接调from openai import OpenAI client OpenAI( base_urlhttp://localhost:8000/v1, api_keyEMPTY ) resp client.chat.completions.create( modelqwen2.5-7b, messages[ {role: system, content: 你是专业的技术顾问}, {role: user, content: 帮我解释一下什么是PagedAttention} ], max_tokens512, temperature0.6 ) print(resp.choices[0].message.content)api_key传EMPTY是因为vLLM默认不做鉴权只要格式符合OpenAI规范就行。如果你的服务暴露在公网一定要在启动参数或反向代理层加上认证否则别人能随便调用你的模型账单和显存都会很难看。4.3 使用本地模型文件的启动顺序很多人的模型是通过ModelScope或者HF镜像下载到本地的不是直接从HF Hub拉。这种情况下启动时要换成路径wget -P /data/models/ https://xxx/qwen2.5-7b-instruct-safetensors/ vllm serve /data/models/qwen2.5-7b-instruct \ --served-model-name local-qwen \ --trust-remote-code注意目录下必须有完整的config.json、tokenizer.json、tokenizer_config.json和模型权重文件。缺一个都可能报错。用--trust-remote-code是因为部分模型的自定义代码需要执行如果模型来源可信就加上否则风险自担。4.4 离线批量推理怎么搞如果你不是要起HTTP服务而是想写脚本批量处理几条文本vLLM也提供了离线接口from vllm import LLM, SamplingParams llm LLM(model/data/models/qwen2.5-7b-instruct, gpu_memory_utilization0.9) prompts [解释一下什么是attention, 写一段Python快排, 总结今天天气] sampling_params SamplingParams(temperature0.7, max_tokens256) outputs llm.generate(prompts, sampling_params) for output in outputs: print(output.prompt, , output.outputs[0].text)这里LLM类在初始化时就完成了模型加载和显存分配。注意generate方法返回的结果里每个output包含原始输入和所有采样结果多生成了几个候选时用output.outputs[1]这种下标去取即可。5. 关键参数详解跳过这一节你后面会吃亏5.1 显存与序列长度参数很多人第一次启动vLLM时会报错提示显存不足大概率是--max-model-len设置过大。这个参数不只是一个上限它直接决定了KV Cache的预分配大小。计算公式大概是KV Cache显存 ≈ 层数 × 注意力头数 × (头维度 × 2) × max_model_len × batch_size × 每个缓存的字节数虽然我们不需要每次都手算但心里要有数max_model_len翻倍KV Cache显存几乎翻倍。保守策略是先设一个能跑的较小值比如4096确认服务起来了再逐步加大。显存利用率参数也可以用--max-num-seqs来限制单个batch最大请求数通常在4到256之间。并发不高时把它调小能降低显存压力。5.2 量化与模型精度vLLM原生支持多种量化格式。我测试过的经验是量化格式显存节省质量损失说明FP16/BF16不省无默认速度最快GPTQ约50%可接受需先量化或用已量化模型AWQ约50%可接受AWQ激活感知量化效果通常比GPTQ稳FP8约50%低需要在Hopper或Ada架构GPU上才高效启动量化模型就是在模型路径上选好量化版本vLLM会自动识别。比如vllm serve Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4 \ --quantization gptq \ --max-model-len 8192这里--quantization gptq也可以省略vLLM会读取模型config里的quantization_config自动判断。有一种情况你要额外小心FP8在非Ada/Ampere架构上跑得并不快。虽然节省显存但如果GPU不支持FP8的硬件加速吞吐反而会更低。5.3 调度参数怎么调vLLM有几个调度相关的参数容易被忽略--max-num-batched-tokens控制一次prefill阶段能处理的最大token数默认是4096或8192。调大了吞吐可能增加但也会增加延迟因为一小批请求要等更多请求一起进入prefill。--max-num-seqs单batch的并发序列上限。--enable-prefix-caching启用前缀缓存。如果你的请求共享系统提示词打开之后显存和响应时间都会改善。我个人调试经验是延迟敏感型任务--max-num-seqs设16左右--max-num-batched-tokens设2048响应更快吞吐敏感型任务--max-num-seqs设64或更高--max-num-batched-tokens设8192以上GPU利用率更高。5.4 多卡并行Tensor Parallelism单卡放不下模型时可以多卡切分。vLLM的--tensor-parallel-size参数简称TP。vllm serve Qwen/Qwen2.5-14B-Instruct \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.9比如两张A100 40GB想跑70B模型TP设为2模型权重会被切分到两张卡上并行计算。这里有几件事必须提前确认多卡之间的通信需要用到NCCL。日志里出现[pynccl.py:113] vllm is using nccl2.30.7是正常现象不用慌。但如果NCCL带宽不对tp_size越大性能反而越差。两张卡建议是同一型号。如果一张A100、一张V100速度会被慢卡拖垮。TP大小必须能整除注意力头数。如果你开TP4但模型只有2个注意力头启动直接报错。一般项目里不会遇到除非模型特别小。6. 性能压测与生产部署技巧6.1 用vLLM自带的benchmark工具做压测vLLM仓库内置了一套benchmark脚本功能比很多脚本自己写的压测工具要完善。位置在benchmarks/benchmark_serving.py。用法git clone https://github.com/vllm-project/vllm cd vllm/benchmarks python benchmark_serving.py \ --backend vllm \ --model Qwen/Qwen2.5-7B-Instruct \ --tokenizer Qwen/Qwen2.5-7B-Instruct \ --dataset-path ./ShareGPT_V3_unfiltered_cleaned_split.json \ --num-prompts 300 \ --request-rate 20 \ --max-concurrency 50 \ --port 8000输出会给你几个关键指标吞吐量requests/s和tokens/s、TTFT首token延迟、TPOT每生成一个token的耗时。这些指标比你自己写个循环去数响应时间要标准得多。注意ShareGPT_V3数据集需要先下载。如果网络不方便官方也支持用--dataset-path传一个自定义JSONL文件。准备方法不复杂每一行是{prompt: ..., canonical: [...]}这种结构就行。跑压测有个原则生产环境跑压测要和实际负载一致。如果你线上请求的平均上下文是2000 token压测时就不能全用极短的prompt。长短混合才能暴露显存碎片和batch调度的真实瓶颈。6.2 调整调度参数前后的性能对比我做过一次典型的调优实验环境是A100 40GB、Qwen2.5-7B-Instruct、并发请求数50数据集是ShareGPT的200条真实对话。默认参数跑出来的结果是吞吐约1100 tokens/sTTFT中位数约480ms。改动如下vllm serve Qwen/Qwen2.5-7B-Instruct \ --max-num-seqs 64 \ --max-num-batched-tokens 8192 \ --enable-prefix-caching调整之后吞吐上升到约1800 tokens/sTTFT中位数降到约260ms因为前缀缓存减少了重复prefill的计算量。当然这个数据在不同模型和GPU上会有差异但趋势是明确的合理提高max-num-seqs配合前缀缓存收益通常比较明显。如果你还想要更细的观测打开--verbose或配合Prometheus监控vLLM会暴露/metrics接口里面有vllm:num_requests_running、vllm:gpu_cache_usage_perc这类指标。生产环境一定要盯缓存使用率我在实际运维中发现gpu_cache_usage_perc接近100%时新请求会排队等待延迟就会明显上升。6.3 并发窗口和error handling时的注意事项当一个请求因为超长输入或模型OOM被拒绝时vLLM会返回一个HTTP错误码。OpenAI SDK客户端要处理两类常见错误429当前请求过多或队列溢出。可以做指数退避重试。400请求参数非法比如上下文超长。生产部署时前端推荐加一层Nginx或网关做负载均衡、请求超时控制。vLLM本身不提供鉴权和速率限制直接暴露到公网风险很大。另外一点--served-model-name在起服务时设定了对外模型名但实际业务方可能传了不同的模型名。如果你希望完全不管模型名可以启用--skip-tokenizer-init之类的配置但大部分场景下建议让前端代理去统一替换。7. 常见报错与避坑指南7.1 启动阶段的报错我梳理了几个实际中遇到最多的问题报错/表现根因解法CUDA out of memory显存不够加载模型或分配KV Cache调低--max-model-len或--gpu-memory-utilizationValueError: The models max seq len is larger than...模型config里max_position_embeddings太大显式指定--max-model-len覆盖AssertionError: tensor parallel size should be...TP大小与模型结构不匹配检查模型注意力头数降低TP服务起来了但收不到响应端口被占用或防火墙未放行换端口或检查安全组有一个很“阴间”的报错值得单独说模型在本地能加载但一发起请求就卡住不动。这种情况八成是CPU核心数不够或CPU内存挤占导致调度线程无法及时完成tokenizer处理。用top看一下CPU是不是打满了再考虑给vLLM的启动容器加CPU限额。7.2 显存泄漏和响应不定时的排查思路vLLM本身的显存管理已经做得很好普通用下来不用太担心泄漏。但如果你用PagedAttention的前缀缓存同时模型使用了自定义的forward方法有可能出现缓存失效或异常。这时候建议关闭前缀缓存对比以下。还有一个容易踩的坑不要和别的训练进程共用GPU。vLLM启动时会尽量占满指定比例的显存但如果你用CUDA_VISIBLE_DEVICES指定了某张卡而这张卡上还有一个训练任务在跑两者会互相挤占显存最终都变慢甚至OOM。最好的做法是物理隔离或确保训练结束后再启动服务。7.3 vLLM、SGLang、LM Studio到底怎么选现在开源推理框架不止vLLM很多人会问SGLang、LM Studio和vLLM的差异。简单说LM Studio是面向桌面用户的图形化工具双击就启动模型适合本地体验和调试但它更多是个人工具不是标准的生产级服务引擎。vLLM是通用型和性能均衡性最好的方案支持面广、生态完善、OpenAI兼容API成熟适合生产部署。SGLang在部分场景尤其极端长并发和结构化输出下吞吐更优但配置和API相对于vLLM更“硬核”一些。我给他的建议是个人本机调试用LM Studio快速体验生产或需要自部署API先选vLLM等遇到SGLang明显更优的具体场景再迁移不迟。8. 从快速上手到进阶后续可以继续深挖的方向当你用vLLM把一个模型成功跑起来之后梳理后续方向时大概有几个重点LoRA微调热加载vLLM支持--lora-modules可以动态加载不同任务的LoRA Adapter多个Adapter共享同一个底模节省显存的同时又能服务不同垂直场景。多模态模型现在vLLM也支持Qwen2-VL、LLaVA等视觉模型API不再是纯文本的chat接口而是支持image_url输入。模型并行如果是超大模型--pipeline-parallel-size跟TP配合使用可以进一步突破单机多卡的限制当然这需要更仔细的NCCL网络规划。这些方向我都会在后面几篇里专门写。我不建议你在没有彻底掌握基础启动和参数调优之前就冲过去先把当前这步走稳了跑熟了再往更复杂的场景走反而更快。