vLLM 部署与推理优化实战:从启动参数到显存/KV 调优

搞大模型部署的人基本都踩过同一个坑:模型好不容易加载进去了,一压并发吞吐就掉,显存要么爆、要么大片闲置。说到底,LLM 在线服务的瓶颈不在算力,在显存管理和调度。vLLM 就是冲着这个瓶颈来的——它把显存当操作系统内存一样分页管理,硬生生把吞吐量拉到了 HuggingFace Transformers 的 24 倍。

这篇文章解决一件事:让你在生产环境(4×A100 80G)里把 vLLM 用明白。覆盖五个点——vLLM 是什么、启动参数怎么配、显存怎么省、KV Cache 怎么调、PagedAttention 到底是什么原理。基准环境以 vLLM 0.27.x(2026 年 8 月,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 用分页管理解决了它。


二、环境说明与前置条件

本文所有命令和参数以这套环境为准:

组件版本/规格用途
GPU4× NVIDIA A100 80GB(NVLink 互联)单节点多卡推理
vLLM0.27.x(V1 引擎默认)推理引擎
CUDA12.x驱动与内核
Python3.10+运行环境
示例模型Qwen2.5-72B-Instruct / Llama-3.1-70B70B 级权重

安装两种方式,生产环境更推荐 Docker(环境一致、好迁移):

# 方式一:pip(开发/单机调试)
pip install vllm

# 方式二:官方镜像(生产推荐)
docker run --runtime nvidia --gpus all \
  -v ~/.cache/huggingface:/root/.cache/huggingface \
  -p 8000:8000 --ipc=host \
  vllm/vllm-openai:latest \
  --model Qwen/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 起服务(可照抄)

export CUDA_VISIBLE_DEVICES=0,1,2,3

vllm serve Qwen/Qwen2.5-72B-Instruct \
  --host 0.0.0.0 \
  --port 8000 \
  --tensor-parallel-size 4 \
  --dtype auto \
  --max-model-len 32768 \
  --gpu-memory-utilization 0.90

vllm 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 seconds
  • GPU 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.9。这是每个实例独立的上限——同一张卡跑两个实例,各设 0.5 就互不干扰。KV Cache 的大小就是由它决定的。
  • --max-num-seqs:单次迭代能同时处理的最大序列数,直接决定并发上限和显存占用。⚠️ V1 默认值是 1024(V0 是 256),升级后如果不显式调低,可能突然 OOM——很多从旧版迁过来的人栽在这。
  • --max-num-batched-tokens:单次迭代最多处理的 token 数,默认 8192(在线服务)。值越大 prefill 吞吐越高但单请求延迟越差;值越小交互响应越跟手。是「吞吐 vs 延迟」的核心旋钮。
  • --swap-space:每张卡的 CPU 交换空间,默认 4 GiB。显存不够时把 KV Cache 块换到内存里兜底,代价是慢。
  • --cpu-offload-gb:每张卡把多少 GiB 权重 offload 到 CPU,默认 0。可以当「虚拟加显存」用,但要求 CPU-GPU 带宽高,否则每个 forward 都卡在搬运上。

4.3 并行类

  • --tensor-parallel-size / -tp:张量并行副本数,多卡推理核心参数,默认 1。
  • --pipeline-parallel-size / -pp:流水线阶段数,跨节点或卡数不整除模型时用。
  • --distributed-executor-backendmp(单机多进程,默认)/ ray(跨节点)。

4.4 量化与缓存类

  • --quantization / -q:权重量化方法,awq / gptq / fp8 等,配合预量化模型使用。
  • --kv-cache-dtype:KV Cache 存储精度,auto / fp8 / fp8_e4m3 / fp8_e5m2。设 fp8 直接省一半 KV 显存(第五章细讲)。
  • --block-size:每个 KV Cache 块的 token 数,CUDA 上最大 32。调小块能减少尾部浪费,但块表更大、调度开销略增。
  • --enable-prefix-caching:前缀缓存。V1 默认已开启——多个请求共享同一段系统提示词/RAG 文档时,前缀 KV 只算一次,RAG 场景通常带来 30–50% 吞吐提升,零代码改动。

4.5 参数速查表

参数默认值作用典型调优方向
--gpu-memory-utilization0.9显存占用上限,决定 KV Cache 容量单实例压满 0.90~0.95;多实例调低
--max-num-seqs1024 (V1)并发序列数上限大模型/长上下文降到 64~256 防 OOM
--max-num-batched-tokens8192单迭代 token 上限交互场景调低保 TTFT
--max-model-len模型默认上下文长度按业务实际需求调低,省显存
--tensor-parallel-size1张量并行卡数单节点卡数填满
--kv-cache-dtypeautoKV 存储精度设 fp8 省一半
--quantization权重量化AWQ/FP8 省权重显存
--block-size平台默认分页块大小长上下文可调大
--swap-space4CPU 交换空间 GiBOOM 兜底

五、显存优化:先搞懂显存都花在哪

优化显存,前提是知道显存被谁吃了。推理时一张卡的显存大致分成四块:

构成说明能否优化
模型权重参数本身,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 \
  --quantization awq \
  --dtype auto \
  --tensor-parallel-size 4

FP8 权重在 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-size 4 \
  --kv-cache-dtype fp8

不校准直接用也行(scale 默认 1.0),但官方推荐开 --calculate-kv-scales 让它在 warmup 时用一批随机 token 自动估缩放因子,精度更稳;追求极致精度才用 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 收敛。V1 默认 1024,低并发场景降到 128 甚至 64,能释放大量预留给并发的显存。

5.4 兜底:offload 与 swap

显存实在不够,两个兜底手段:

  • --swap-space 8:把 KV Cache 块换到 CPU 内存,极端长请求能撑住,代价是延迟。
  • --cpu-offload-gb 10:把部分权重放 CPU,相当于「虚拟扩卡」。但 CPU-GPU 带宽是硬伤,吞吐会明显掉,只适合非实时场景。

一句话总结:权重靠量化,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 fp8KV 显存减半极小精度损失🔴 首选,几乎白捡
调低 --max-model-len直接砍峰值截断超长请求按业务 99 分位长度设
调低 --max-num-seqs降低并发预留吞吐上限下降按真实 QPS 收敛
--block-size 调优减少尾部浪费块表开销长上下文场景试 32
--enable-prefix-caching共享前缀只算一次哈希开销V1 默认已开,别关

6.3 吞吐 vs 延迟:两个方向的配法

KV 调优没有「最优」,只有「你要什么」。两套典型配置:

# 高吞吐:压满显存、拉高并发(离线批处理/高 QPS API)
vllm serve meta-llama/Meta-Llama-3.1-70B \
  --tensor-parallel-size 4 \
  --max-num-seqs 512 \
  --max-model-len 8192 \
  --quantization fp8 \
  --gpu-memory-utilization 0.90
# 低延迟:控制并发、保首 token 延迟(交互/语音类)
vllm serve meta-llama/Meta-Llama-3.1-70B \
  --tensor-parallel-size 4 \
  --max-num-seqs 64 \
  --max-model-len 4096 \
  --gpu-memory-utilization 0.85 \
  --block-size 16

前者要的是 tokens/秒,后者要的是首 token 时间(TTFT)和字间延迟(ITL)稳住。⚠️ 没有一套参数通吃,压测后用真实流量调。


7、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 件事

  1. vLLM 快的根因是显存管理,不是魔法:PagedAttention 把 KV Cache 分页管理,浪费从 60–80% 压到 4% 以下。
  2. 单节点多卡走张量并行--tensor-parallel-size 填满节点卡数,4×A100 就填 4,默认 mp 后端,跨节点才上 Ray。
  3. 省显存三件套:权重量化(AWQ/FP8)、KV 降精度(--kv-cache-dtype fp8 省一半)、双上限收敛(--max-model-len + --max-num-seqs)。
  4. V1 的默认 max-num-seqs 是 1024(V0 是 256),升级后别忘显式调低,否则突然 OOM。
  5. 没有通吃参数:高吞吐和低延迟是两套配法,用真实流量压测后定。
  6. 前缀缓存 V1 默认开,RAG/多轮对话别手贱关掉,白拿 30–50% 吞吐。

验证清单

  • vllm serve 启动后,日志出现 model loaded to GPU,无 OOM 报错
  • 启动日志里 GPU KV cache sizeMaximum 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
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐