vLLM 部署与推理优化实战:从启动参数到显存/KV 调优
搞大模型部署的人基本都踩过同一个坑:模型好不容易加载进去了,一压并发吞吐就掉,显存要么爆、要么大片闲置。说到底,LLM 在线服务的瓶颈不在算力,在显存管理和调度。vLLM 就是冲着这个瓶颈来的——它把显存当操作系统内存一样分页管理,硬生生把吞吐量拉到了 HuggingFace Transformers 的 24 倍。
这篇文章解决一件事:让你在生产环境(4×A100 80G)里把 vLLM 用明白。覆盖五个点——vLLM 是什么、启动参数怎么配、显存怎么省、KV Cache 怎么调、PagedAttention 到底是什么原理。基准环境以 vLLM 0.29.x(2026 年 9 月,V1 引擎为默认)为准,具体参数默认值以官方文档为准。
一、先厘清:vLLM 是什么,推理到底慢在哪
vLLM 是 UC Berkeley Sky Computing Lab 开源的 LLM 推理与服务引擎,论文发在 SOSP 2023,核心贡献是 PagedAttention。它目前由 2000+ 贡献者维护,支持 200 多种模型架构,NVIDIA/AMD/Intel/昇腾/TPU 都跑得动,还提供 OpenAI 兼容 API。
要理解它为什么快,得先搞懂自回归解码的 KV Cache这个东西——它既是推理的核心机制,也是后面「KV 调优」和「PagedAttention」两章的地基,这里先讲透。
1.1 自回归生成,为什么必须缓存 K 和 V
LLM 生成 token 是逐字蹦的:给定前文,预测下一个 token,然后把它拼回去,再预测再下一个。每一步都做一次完整的 Transformer 前向。
注意力机制里,每个 token 会算出三样东西:Query(Q)、Key(K)、Value(V)。算下一个 token 时,当前 token 要和之前所有 token做注意力。关键点来了——之前那些 token 的 K 和 V 在上一步已经算过了,只要上下文没变,结果就不会变。如果每一步都把整段序列重新算一遍 K/V,那生成第 N 个 token 时就要重复计算前 N-1 个 token 的 K/V,纯属浪费。
所以推理引擎会把历史 token 的 K 和 V 张量缓存在显存里,下一步直接复用,只算新 token 的那一份。这些缓存的键值张量就叫KV Cache。
1.2 KV Cache 的两个麻烦
KV Cache 有两个让传统引擎头疼的特性:
- 大:官方博客给过一个数,LLaMA-13B 单个序列的 KV Cache 最高能到 1.7GB。
- 动态且不可预测:它的大小取决于序列长度,而序列长度在请求来之前根本不知道。
传统引擎(HF Transformers、早期 TGI)的做法是给每个请求预先分配一块连续显存,大小按最大可能长度预留。结果就是:请求实际只用了一半,另一半预留着浪费。vLLM 团队测下来,这种碎片化和过度预留会让系统浪费掉 60%–80% 的显存。
浪费显存意味着同样一张卡能同时服务的请求更少,GPU 利用率上不去,吞吐自然低。vLLM 的答案就是 PagedAttention——第七章细讲,先记住结论:它把浪费压到了4% 以下。
一句话总结:推理慢的根因是显存管理——KV Cache 又大又动态,传统预分配浪费 60–80% 显存,vLLM 用分页管理解决了它。
二、环境说明与前置条件
本文所有命令和参数以这套环境为准:
| 组件 | 版本/规格 | 用途 |
|---|---|---|
| GPU | 4× NVIDIA A100 80GB(NVLink 互联) | 单节点多卡推理 |
| vLLM | 0.29.x(V1 引擎默认) | 推理引擎 |
| CUDA | 12.x | 驱动与内核 |
| Python | 3.10+ | 运行环境 |
| 示例模型 | Qwen2.5-72B-Instruct / Llama-3.1-70B | 70B 级权重 |
安装两种方式,生产环境更推荐 Docker(环境一致、好迁移):
# 方式一:pip(开发/单机调试)pipinstallvllm# 方式二:官方镜像(生产推荐)dockerrun--runtimenvidia--gpusall\-v~/.cache/huggingface:/root/.cache/huggingface\-p8000:8000--ipc=host\vllm/vllm-openai:latest\--modelQwen/Qwen2.5-72B-Instruct--ipc=host别漏,共享内存不够会让多卡推理直接崩,这是个高频坑。
三、部署:4×A100 多卡张量并行
3.1 单卡还是多卡
官方给出的选型规则很直白:
| 场景 | 方案 | 参数 |
|---|---|---|
| 模型单卡装得下 | 单卡推理,不折腾分布式 | 默认 |
| 单卡装不下、单节点装得下 | 张量并行(TP) | --tensor-parallel-size 4 |
| 单节点装不下 | TP + 流水线并行(PP) | --tensor-parallel-size 8 --pipeline-parallel-size 2 |
70B 级模型 FP16 权重约 140GB,单张 80G 卡放不下,4 张加起来 320GB,绰绰有余。所以走 TP=4,把每一层的权重矩阵按列切分到 4 张卡上,每张卡只持有四分之一权重,层内通过 NVLink 通信。
单节点内 vLLM 默认用 Python multiprocessing 做分布式后端,不用额外装 Ray;只有跨节点才需要 Ray。
3.2 起服务(可照抄)
exportCUDA_VISIBLE_DEVICES=0,1,2,3 vllm serve Qwen/Qwen2.5-72B-Instruct\--host0.0.0.0\--port8000\--tensor-parallel-size4\--dtypeauto\--max-model-len32768\--gpu-memory-utilization0.90vllm serve是 V1 起 OpenAI 兼容服务的统一入口(老命令python -m vllm.entrypoints.openai.api_server仍可用但已不建议)。
3.3 验证:看日志里的两个数
启动时盯两行日志:
INFO ... [kv_cache_utils.py] GPU KV cache size: 643,232 tokens INFO ... [kv_cache_utils.py] Maximum concurrency for 40,960 tokens per request: 15.70x INFO ... model loaded to GPU in 34.2 secondsGPU KV cache size是当前配置下 KV Cache 能塞的总 token 数。Maximum concurrency ... 15.70x是估算的并发倍数:每个请求按 40960 token 算,能同时服务约 15 个请求。- 看到
model loaded to GPU且没有 OOM,说明权重落卡成功。
✅ 成功标志:日志出现model loaded to GPU,且/metrics(Prometheus)能拉到指标,curl http://localhost:8000/v1/models返回模型列表。
四、启动参数详解
参数背后都是钱——显存和吞吐。这里按「管什么」分组讲,最后给一张速查表。
4.1 模型加载类
--model:HuggingFace 模型名或本地路径,唯一必填。--dtype:权重和激活的数据类型,auto/half(FP16) /bfloat16/float32。默认auto会自动选——FP32/FP16 模型走 FP16,BF16 模型走 BF16。AWQ 量化时官方建议配half。--max-model-len:上下文最大长度。不指定则从模型 config 自动推导。这是省显存的第一大旋钮(见第五章)。--trust-remote-code:允许执行模型仓库里的自定义代码。私有/魔改模型常需要,生产环境要评估安全风险再开。--load-format:权重加载格式,auto优先 safetensors。冷启动慢可试npcache(numpy 缓存加速二次加载)。
4.2 显存与并发类(核心调优区)
--gpu-memory-utilization:给模型执行器用的显存比例,范围 0~1,默认0.92。这是每个实例独立的上限——同一张卡跑两个实例,各设 0.5 就互不干扰。KV Cache 的大小就是由它决定的。--max-num-seqs:单次迭代能同时处理的最大序列数,直接决定并发上限和显存占用。⚠️ 官方文档特意说明:这个默认值「主要是为了方便测试」,实际使用应在EngineArgs.create_engine_config中设置——既然官方都不把它当生产配置,你就该显式定它,别依赖版本自带的数值。--max-num-batched-tokens:单次迭代最多处理的 token 数。值越大 prefill 吞吐越高但单请求延迟越差;值越小交互响应越跟手。是「吞吐 vs 延迟」的核心旋钮。与--max-num-seqs同理,官方也建议在实际使用中显式设置,别依赖默认值。--kv-cache-memory-bytes:手动指定每卡 KV Cache 的字节数。不设时由gpu-memory-utilization自动推断;一旦设置,gpu-memory-utilization会被忽略。需要精细控制显存分配时用它。--cpu-offload-gb:每张卡把多少 GiB 权重 offload 到 CPU,默认 0。可以当「虚拟加显存」用,但要求 CPU-GPU 带宽高,否则每个 forward 都卡在搬运上。
4.3 并行类
--tensor-parallel-size/-tp:张量并行副本数,多卡推理核心参数,默认 1。--pipeline-parallel-size/-pp:流水线阶段数,跨节点或卡数不整除模型时用。--distributed-executor-backend:mp(单机多进程,默认)/ray(跨节点)。
4.4 量化与缓存类
--quantization/-q:权重量化方法,awq/gptq/fp8等,配合预量化模型使用。--kv-cache-dtype:KV Cache 存储精度,默认auto(跟随模型数据类型)。可选项比早年丰富得多,除fp8/fp8_e4m3/fp8_e5m2外,还有fp8_ds_mla、fp8_inc、nvfp4以及多种 per-token-head 方案。设fp8可直接省一半 KV 显存(第五章细讲)。--block-size:每个 KV Cache 块的 token 数。调小块能减少尾部浪费,但块表更大、调度开销略增。--enable-prefix-caching:前缀缓存。V1 默认已开启——多个请求共享同一段系统提示词/RAG 文档时,前缀 KV 只算一次,RAG 场景通常带来 30–50% 吞吐提升,零代码改动。
4.5 参数速查表
| 参数 | 默认值 | 作用 | 典型调优方向 |
|---|---|---|---|
--gpu-memory-utilization | 0.92 | 显存占用上限,决定 KV Cache 容量 | 单实例压满 0.90~0.95;多实例调低 |
--max-num-seqs | 官方建议显式设置 | 并发序列数上限 | 大模型/长上下文降到 64~256 防 OOM |
--max-num-batched-tokens | 官方建议显式设置 | 单迭代 token 上限 | 交互场景调低保 TTFT |
--max-model-len | 模型默认 | 上下文长度 | 按业务实际需求调低,省显存 |
--tensor-parallel-size | 1 | 张量并行卡数 | 单节点卡数填满 |
--kv-cache-dtype | auto | KV 存储精度 | 设 fp8 省一半 |
--quantization | 无 | 权重量化 | AWQ/FP8 省权重显存 |
--block-size | 平台默认 | 分页块大小 | 长上下文可调大 |
--kv-cache-memory-bytes | 由显存比例推断 | 手动指定 KV Cache 容量 | 需要精细控制时用 |
五、显存优化:先搞懂显存都花在哪
优化显存,前提是知道显存被谁吃了。推理时一张卡的显存大致分成四块:
| 构成 | 说明 | 能否优化 |
|---|---|---|
| 模型权重 | 参数本身,FP16 下 70B ≈ 140GB | ✅ 量化 |
| KV Cache | 动态增长,随并发和上下文膨胀 | ✅ 精度/上限 |
| 激活值 | 前向计算的中间结果,短生命周期 | ⚠️ 空间有限 |
| 系统预留 | CUDA context、驱动、碎片 | ❌ 基本动不了 |
优化手段按「哪个构成」对号入座:
5.1 权重:量化
权重是最大头。AWQ 4-bit 能把权重从 FP16 压缩到约四分之一(官方文档口径:文件体积减少约 70%),代价是极小精度损失。用现成预量化模型即可:
vllm serve hugging-quants/Meta-Llama-3.1-70B-Instruct-AWQ-INT4\--quantizationawq\--dtypeauto\--tensor-parallel-size4FP8 权重在 Hopper 卡上有硬件加速,70B 级常见做法是直接上 FP8 量化模型(--quantization fp8),省一半权重显存还能吃到算力红利。
5.2 KV Cache:降精度
KV Cache 是第二大头,且随并发线性膨胀。--kv-cache-dtype fp8能把 KV 存储精度从 FP16 压到 FP8,省约 50% 显存,换来更长上下文或更高并发:
vllm serve Qwen/Qwen2.5-72B-Instruct\--tensor-parallel-size4\--kv-cache-dtype fp8不校准直接用也行——官方基准测试用的就是 scale 全为 1.0 的最简配置,并明确说明这是「精度最差情况」,校准后只会更好。
⚠️ 注意:--calculate-kv-scales容易被误用。它需要配合校准数据路径才能生效,单独传它是空操作,只会在启动日志里打一条q_scale=1.0的警告;在 GDN/Mamba 这类混合模型上还可能引发静默的输出损坏。要追求精度,正路是用 llm-compressor 走官方校准流程。
5.3 上限控制:让 KV Cache 别乱涨
KV Cache 总容量 = 显存预留 × 单 token KV 大小,而并发需求 =max-num-seqs × max-model-len。这两个参数直接框定了 KV Cache 的峰值:
--max-model-len别无脑拉满 128K。业务里 90% 请求可能就 8K,设 32768 已经富余,省下的显存全给了并发。--max-num-seqs按真实 QPS 收敛。这个值官方建议显式指定,低并发场景降到 128 甚至 64,能释放大量预留给并发的显存。
5.4 兜底:offload 与 swap
显存实在不够,两个兜底手段:
--kv-cache-memory-bytes:手动圈定 KV Cache 的可用容量,把上限卡死,避免它无节制膨胀挤爆其他部分。--cpu-offload-gb 10:把部分权重放 CPU,相当于「虚拟扩卡」。官方文档明确提醒:每个 forward 都要从 CPU 内存实时搬运部分权重到 GPU,因此依赖高速 CPU-GPU 互连。带宽是硬伤,吞吐会明显掉,只适合非实时场景。
⚠️ 注意:V1 引擎已移除 KV Cache 换出(swap)机制,老教程里的--swap-space在 V1 下不再适用。官方给出的替代思路是:被抢占的请求重新计算时,大部分 prompt token 可以靠前缀缓存直接跳过,从而避免真正的重算。照旧教程配 swap 会白忙一场。
一句话总结:权重靠量化,KV 靠降精度(fp8),峰值靠max-model-len+max-num-seqs双上限,实在不够再 offload。
六、KV Cache 调优:先搞懂再动手
KV Cache 在 1.1 节已经讲过「为什么存在」,这里补上「怎么算」和「怎么调」。
6.1 一个序列的 KV Cache 有多大
公式:
每 token KV Cache = 2 × 层数 × KV头数 × 头维度 × 单元素字节数「2」是因为 K 和 V 各一份。以 Llama-3.1-70B 量级(约 80 层、8 个 KV 头、头维度 128、FP16)估算,单 token 约 320KB。乘上序列长度和并发数就是总量:
KV 总显存 ≈ 每 token KV 大小 × 平均序列长度 × 并发请求数这就是为什么「并发上不去」往往不是卡不够,而是 KV Cache 塞满了。具体数值以模型 config.json 为准,这里只给量级。
6.2 调优手段优先级
| 手段 | 效果 | 代价 | 建议 |
|---|---|---|---|
--kv-cache-dtype fp8 | KV 显存减半 | 极小精度损失 | 🔴 首选,几乎白捡 |
调低--max-model-len | 直接砍峰值 | 截断超长请求 | 按业务 99 分位长度设 |
调低--max-num-seqs | 降低并发预留 | 吞吐上限下降 | 按真实 QPS 收敛 |
--block-size调优 | 减少尾部浪费 | 块表开销 | 长上下文场景试 32 |
--enable-prefix-caching | 共享前缀只算一次 | 哈希开销 | V1 默认已开,别关 |
--kv-cache-memory-bytes | 精确圈定 KV 容量 | 需自行估算 | 显存需精细分配时用 |
6.3 吞吐 vs 延迟:两个方向的配法
KV 调优没有「最优」,只有「你要什么」。两套典型配置:
# 高吞吐:压满显存、拉高并发(离线批处理/高 QPS API)vllm serve meta-llama/Meta-Llama-3.1-70B\--tensor-parallel-size4\--max-num-seqs512\--max-model-len8192\--quantizationfp8\--gpu-memory-utilization0.90# 低延迟:控制并发、保首 token 延迟(交互/语音类)vllm serve meta-llama/Meta-Llama-3.1-70B\--tensor-parallel-size4\--max-num-seqs64\--max-model-len4096\--gpu-memory-utilization0.85\--block-size16前者要的是 tokens/秒,后者要的是首 token 时间(TTFT)和字间延迟(ITL)稳住。⚠️ 没有一套参数通吃,压测后用真实流量调。
七、PagedAttention:像操作系统一样管显存
前面反复提到「传统引擎浪费 60–80% 显存」,这里把原理讲透。
7.1 传统 KV Cache 的问题:碎片化和过度预留
传统做法给每个请求预分配一整块连续显存,大小按最大可能生成长度算。两个后果:
- 内部碎片:请求提前结束,剩下的预留空间没人用,空着。
- 外部碎片:不同请求长度不一,连续块之间留下无法利用的碎隙。
叠加起来,浪费高达 60–80%。这就是 PagedAttention 要解决的。
7.2 分页思想
PagedAttention 的灵感直接来自操作系统的虚拟内存分页。核心就一句话:KV Cache 不要求连续存储。
具体做法:把每个序列的 KV Cache 切成固定大小的块(block),每块装固定数量 token 的 K/V。块之间在物理内存里不需要挨着。做注意力计算时,内核通过一张**块表(block table)**按需定位、取用这些块。
映射关系记牢这三组类比:
| 操作系统 | PagedAttention |
|---|---|
| 页(page) | 块(block) |
| 字节(byte) | token |
| 进程(process) | 序列 |
序列的连续逻辑块通过块表映射到不连续的物理块,物理块随新 token 生成按需分配。这样一来:
- 不用提前预分配,浪费只发生在每个序列的最后一个块里。
- 实测内存浪费压到4% 以下,接近最优。
显存利用率上去了,同样一张卡能同时塞更多序列,GPU 利用率跟着涨,吞吐自然起飞——这就是 vLLM 比 HF 快 24 倍的根本原因。
7.3 顺带的红利:内存共享
分页还带来了一个额外好处——跨序列共享。并行采样(一个 prompt 出多个结果)或 beam search 里,多个输出序列共享同一个 prompt 的 KV。通过块表,不同序列的逻辑块可以映射到同一个物理块,prompt 的 KV 只存一份。
共享的安全性靠引用计数+写时复制(Copy-on-Write)保证:某序列要往共享块里写新 token 时,先复制一份再写,互不污染。官方数据显示,这套机制让并行采样/beam search 的内存开销降低最高 55%,转化为2.2 倍的吞吐提升——没有 PagedAttention,这些高级采样在生产环境根本玩不转。
一句话总结:PagedAttention 把 KV Cache 切成不连续的分页块,按需分配 + 引用计数共享,把显存浪费从 60–80% 压到 4% 以下。
八、总结:你真正需要记住的 N 件事
- vLLM 快的根因是显存管理,不是魔法:PagedAttention 把 KV Cache 分页管理,浪费从 60–80% 压到 4% 以下。
- 单节点多卡走张量并行:
--tensor-parallel-size填满节点卡数,4×A100 就填 4,默认 mp 后端,跨节点才上 Ray。 - 省显存三件套:权重量化(AWQ/FP8)、KV 降精度(
--kv-cache-dtype fp8省一半)、双上限收敛(--max-model-len+--max-num-seqs)。 max-num-seqs与max-num-batched-tokens的默认值,官方明确说是「为方便测试」,生产环境务必显式设置,别依赖版本自带数值。- 没有通吃参数:高吞吐和低延迟是两套配法,用真实流量压测后定。
- 前缀缓存 V1 默认开,RAG/多轮对话别手贱关掉,白拿 30–50% 吞吐。
验证清单
vllm serve启动后,日志出现model loaded to GPU,无 OOM 报错- 启动日志里
GPU KV cache size与Maximum concurrency符合预期 curl http://localhost:8000/v1/models返回模型列表- Prometheus
/metrics能拉到吞吐、延迟、KV Cache 利用率指标 - 用真实流量压测,确认
--max-num-seqs与--max-model-len不触发 OOM - 开启
--kv-cache-dtype fp8后,做一轮精度回归(对比关键样例输出)
参考资源
- vLLM 官方文档(引擎参数):https://docs.vllm.ai/en/latest/serving/engine_args.html
- vLLM 并行与扩展:https://docs.vllm.ai/en/latest/serving/parallelism_scaling.html
- vLLM 官方博客(PagedAttention 发布):https://blog.vllm.ai/2023/06/20/vllm.html
- PagedAttention 论文(SOSP 2023):https://arxiv.org/abs/2309.06180
- vLLM V1 用户指南:https://docs.vllm.ai/en/latest/getting-started/v1-user-guide.html
- 量化 KV Cache 文档:https://docs.vllm.ai/en/latest/features/quantization/quantized_kvcache.html