vLLM 深度解析:LLM 推理性能的终极引擎——从 PagedAttention 到生产级部署的完整技术内幕
一、为什么 vLLM 是 2026 年 LLM 部署的事实标准
如果你在过去一年里部署过大语言模型,那你大概率用过 vLLM。这个由 UC Berkeley 团队开源的推理引擎,已经成为 LLM 服务部署领域的事实标准——从初创公司到科技巨头,从学术实验室到生产环境,几乎所有人都在用它。
为什么?因为它解决了一个极其关键的问题:GPU 显存利用率。
在 vLLM 之前,部署 LLM 的主流方案(如 HuggingFace Transformers 的原生推理、TGI、FastChat 等)都有一个共同的痛点——KV Cache 的显存管理效率极低。传统的方案为每个请求预分配一块固定大小的连续显存空间来存储 KV Cache,这就导致了两个严重的问题:
- 内部碎片:请求实际使用的 KV Cache 大小会随着生成过程逐渐增长,但预分配的空间必须按最大可能长度来预留。大量预分配的显存被浪费。
- 外部碎片:请求结束后释放的空间大小不一,导致可用显存被打碎成无数小块,新的请求即使总显存够用,也找不到足够大的连续空间。
这两个问题直接导致了一个令人沮丧的现象:明明 GPU 还有 50% 的显存空闲,但推理服务却报 OOM(Out of Memory)错误。
vLLM 的核心创新——PagedAttention,从根本上解决了这个问题。它的思路直接借鉴了操作系统中的虚拟内存和分页机制,将 KV Cache 切分成固定大小的"页"(Block),不再要求连续存储,从而彻底消除了外部碎片,并大幅降低了内部碎片。
在 2026 年的今天,vLLM 已经进化到了 0.7.x 版本,支持的功能包括连续批处理(Continuous Batching)、前缀缓存(Prefix Caching)、多 LoRA 适配器、量化推理(GPTQ、AWQ、FP8)、多 GPU 张量并行和流水线并行等。同时,AMD 也推出了 vLLM-ATOM 插件,在 AMD Instinct GPU 上实现了对 DeepSeek-R1、Kimi-K2 等模型的推理加速。
这篇文章,我们将从底层原理到生产实战,全面拆解 vLLM 的技术架构。
二、PagedAttention:重新发明 KV Cache 的内存管理
2.1 传统方案的困境
要理解 PagedAttention 的精妙之处,我们首先需要理解传统方案的问题。
在 Transformer 的自注意力计算中,每个层的 Key 和 Value 向量需要被缓存下来,供后续的生成步骤使用。这就是所谓的 KV Cache。对于一个具有 L 层、隐藏维度为 d、注意力头数为 h 的模型,每个 token 的 KV Cache 大小为:
KV Cache per token = 2 × L × d × sizeof(dtype)
以 LLaMA-2-70B(80 层,8192 维度,BF16 精度)为例,每个 token 的 KV Cache 大小约为:
2 × 80 × 8192 × 2 bytes = 2.5 MB/token
如果序列长度为 4096,单个请求的 KV Cache 就需要约 10 GB。一个 A100-80G 显卡,理论上最多同时服务 8 个这样的请求——但实际中,由于内存碎片,往往只能服务 3-4 个。
传统方案的内存分配方式是:
请求1: [=======已使用======---------预留---------]
请求2: [=====已使用=====-----------预留-----------]
请求3: [=======已使用=======-------预留--------]
每个请求的 KV Cache 是一段连续的显存,按最大序列长度预分配。随着生成进行,"已使用"部分逐渐增长,"预留"部分逐渐缩小,但总的分配大小不变。
当请求结束时,释放的空间大小取决于该请求实际使用的 token 数。久而久之,显存变成了这样:
[已用][空][已用][空空][已用][空][已用][空空空][已用][空]
新的请求需要一块大的连续空间,但可用的空间被打碎了。这就是外部碎片。
2.2 PagedAttention 的解决方案
PagedAttention 借鉴了操作系统分页内存管理的思想:
页式分配:将 KV Cache 切分成固定大小的 Block(页),每个 Block 包含固定数量的 token 的 KV 向量。例如,Block 大小为 16 个 token。
页表映射:为每个请求维护一个页表(Block Table),记录该请求的 KV Cache 存储在哪些 Block 中。这些 Block 不需要物理连续。
按需分配:请求开始时只分配少量 Block,随着生成进行,按需分配新的 Block。请求结束时,Block 被回收,可以立即被其他请求使用。
虚拟地址空间(请求视角):
请求1: Block[0] → Block[3] → Block[7] → ...
请求2: Block[1] → Block[4] → Block[8] → ...
物理显存(实际存储):
Block[0][Block[1]][Block[2]][Block[3]][Block[4]][Block[5]...
↑请求1 ↑请求2 ↑请求1 ↑请求1
这种方式有几个关键优势:
- 消除外部碎片:Block 大小固定,任何空闲 Block 都可以被任何请求使用,不存在"找不到连续空间"的问题。
- 减少内部碎片:只有最后一个 Block 可能不满,其他 Block 都是满的。最大浪费不超过一个 Block 的大小(如 16 个 token 的空间)。
- 内存共享:对于共享相同前缀的请求(如系统提示词),可以使用 Copy-on-Write 机制共享 Block,进一步节省显存。
2.3 PagedAttention 的实现细节
在 vLLM 的实现中,PagedAttention 通过自定义的 CUDA Kernel 来实现。传统的注意力计算 Kernel 假设 Key 和 Value 是连续存储的,而 PagedAttention 需要根据页表进行间接寻址。
vLLM 实现了一个特殊的 paged_attention CUDA Kernel,它的工作流程如下:
# vLLM 内部的简化逻辑(伪代码)
def paged_attention_kernel(
query, # [num_heads, head_dim]
key_cache, # [num_blocks, block_size, num_heads, head_dim]
value_cache, # [num_blocks, block_size, num_heads, head_dim]
block_tables, # [max_num_blocks_per_seq] - 页表
context_length, # 当前序列长度
):
output = zeros(num_heads, head_dim)
for block_idx in range(num_blocks_needed):
# 通过页表查找物理 Block
physical_block = block_tables[block_idx]
# 从物理 Block 中读取 Key 和 Value
k = key_cache[physical_block] # [block_size, num_heads, head_dim]
v = value_cache[physical_block]
# 执行注意力计算
output += attention(query, k, v)
return output
这个 Kernel 的关键优化点在于:
- 使用 GPU 的共享内存(Shared Memory)来缓存当前 Block 的 Key 和 Value,减少全局内存访问次数
- 支持 Flash Attention 风格的 tiling 技术,减少 HBM(高带宽内存)的读写次数
- 使用 Warp 级别的并行来处理不同的注意力头
2.4 前缀缓存(Prefix Caching)
vLLM 从 0.4.0 版本开始引入了自动前缀缓存(Automatic Prefix Caching)。这是一个基于 PagedAttention 架构的自然扩展。
在许多实际场景中,多个请求会共享相同的系统提示词(System Prompt)。例如:
系统提示词:你是一个专业的代码助手,请用 Python 回答以下问题...(500 tokens)
用户问题1:如何实现一个LRU缓存?(30 tokens)
用户问题2:如何解析CSV文件?(25 tokens)
传统方案中,每个请求都需要为这 500 tokens 的系统提示词单独计算和存储 KV Cache。有了前缀缓存,vLLM 可以:
- 在第一个请求处理完系统提示词后,将对应的 KV Cache Block 标记为"可共享"
- 后续请求如果具有相同的前缀,直接复用这些 Block(使用引用计数)
- 当所有引用该 Block 的请求完成后,才释放 Block
# vLLM 前缀缓存的工作方式(概念示意)
# 请求1 处理系统提示词后:
block_table_req1 = [Block(0), Block(1), ..., Block(31)] # 系统提示的 32 个 Block
# Block 0-31 被标记为可共享,引用计数 = 1
# 请求2 具有相同系统提示词:
block_table_req2 = [Block(0), Block(1), ..., Block(31), Block(32)]
# Block 0-31 的引用计数增加到 2
# Block 32 是请求2特有内容的 Block
这个优化在系统提示词较长的场景中效果尤为显著。实际测试表明,对于包含 1000+ token 系统提示词的对话场景,前缀缓存可以提升 2-3 倍的吞吐量。
三、连续批处理:告别静态批处理的效率陷阱
3.1 静态批处理的问题
传统的推理服务通常使用静态批处理(Static Batching):将一批请求打包在一起送入模型,等所有请求都生成完毕后,再处理下一批。
时间轴 →
请求1: [======预填充======][生成][生成][生成][生成][生成][生成]
请求2: [====预填充====][生成][生成][生成][生成][等待..........]
请求3: [=======预填充=======][生成][生成][生成][等待............]
批次结束 ←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←
问题很明显:短请求必须等待长请求完成,GPU 大量时间在空转。
3.2 连续批处理(Iteration-level Scheduling)
vLLM 实现了迭代级别的调度(Iteration-level Scheduling),也叫连续批处理。核心思想是:在每次迭代中,调度器都会检查是否有新的请求可以加入当前批次,以及是否有已完成的请求可以移出。
时间轴 →
迭代1: [请求1预填充][请求2预填充][请求3预填充]
迭代2: [请求1生成][请求2生成][请求3生成]
迭代3: [请求1生成][请求2生成][请求3生成][请求4预填充] ← 新请求立即加入
迭代4: [请求1生成][请求2完成✓][请求3生成][请求4生成] ← 请求2完成,立即移出
迭代5: [请求1生成][请求3生成][请求4生成][请求5预填充] ← 新请求再次加入
这种方式的效果是:
- GPU 利用率大幅提升:几乎每个迭代都在处理满负荷的计算
- 首 Token 延迟降低:新请求不需要等待当前批次完成
- 吞吐量提升:单位时间内处理更多请求
vLLM 的调度器在每次迭代中执行以下逻辑:
# vLLM 调度器的简化逻辑
def schedule():
# 1. 从等待队列中挑选可以加入当前批次的请求
for request in waiting_queue:
if can_allocate(request):
running_queue.add(request)
allocate_blocks(request)
# 2. 对运行中的请求执行一次前向计算
for request in running_queue:
if request.is_prefill:
execute_prefill(request)
else:
execute_decode(request)
# 3. 移除已完成的请求,释放 Block
for request in running_queue:
if request.is_finished:
running_queue.remove(request)
free_blocks(request)
# 4. 处理内存压力:如果 GPU KV Cache 不够,将请求交换到 CPU
if gpu_cache_usage > threshold:
swap_to_cpu(low_priority_requests)
elif gpu_cache_usage < threshold and swapped_requests:
swap_from_gpu(swapped_requests)
3.3 Chunked Prefill:预填充的碎片化
在 vLLM 0.3.0+ 中引入了 Chunked Prefill 机制。传统的预填充(Prefill)阶段需要一次性处理整个输入序列,对于长输入(如长文档总结),这会导致:
- 单次预填充占用大量显存,可能挤占其他请求的空间
- 预填充期间,其他生成中的请求必须等待
Chunked Prefill 将长输入的预填充切分成多个 Chunk,每个迭代只处理一个 Chunk:
请求(输入 4096 tokens):
迭代1: [Chunk1: tokens 0-1023] + [其他请求的生成]
迭代2: [Chunk2: tokens 1024-2047] + [其他请求的生成]
迭代3: [Chunk3: tokens 2048-3071] + [其他请求的生成]
迭代4: [Chunk4: tokens 3072-4095] + [其他请求的生成]
迭代5: [生成token 1] + [其他请求的生成]
这样,长输入的请求不会"霸占"整个 GPU,其他短请求可以穿插执行。
四、vLLM 架构全景:从 API 到 GPU Kernel
4.1 整体架构
vLLM 的架构可以分为以下几层:
┌─────────────────────────────────────────────────┐
│ API Layer │
│ OpenAI Compatible API / HTTP / gRPC │
├─────────────────────────────────────────────────┤
│ Frontend │
│ Request Parser / Tokenizer / Detokenizer │
├─────────────────────────────────────────────────┤
│ Scheduler │
│ Priority Queue / Block Manager / Swapper │
├─────────────────────────────────────────────────┤
│ Model Runner │
│ Prefill Engine / Decode Engine / Workers │
├─────────────────────────────────────────────────┤
│ Backend / Kernels │
│ PagedAttention / Quantization / Sampling │
├─────────────────────────────────────────────────┤
│ GPU / Hardware │
│ CUDA / ROCm / TensorRT / cuDNN │
└─────────────────────────────────────────────────┘
4.2 API 层:OpenAI 兼容
vLLM 提供了与 OpenAI API 完全兼容的服务端,这意味着你可以用任何支持 OpenAI API 的客户端直接对接 vLLM:
from openai import OpenAI
# 直接替换 base_url,其他代码不变
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="not-needed" # vLLM 默认不需要 API Key
)
response = client.chat.completions.create(
model="Qwen/Qwen3-8B",
messages=[
{"role": "system", "content": "你是一个专业的代码助手"},
{"role": "user", "content": "用Python实现一个高效的LRU缓存"}
],
max_tokens=2048,
temperature=0.7,
)
print(response.choices[0].message.content)
支持的 API 包括:
POST /v1/chat/completions- 对话补全POST /v1/completions- 文本补全POST /v1/embeddings- 向量嵌入GET /v1/models- 模型列表POST /v1/tokenize//detokenize- Token 化
4.3 Block Manager:显存管理的中枢
Block Manager 是 vLLM 的核心组件,负责管理 GPU 和 CPU 上的 KV Cache Block:
class BlockSpaceManager:
def __init__(self, block_size, num_gpu_blocks, num_cpu_blocks):
self.block_size = block_size # 每个 Block 的 token 数(默认 16)
self.num_gpu_blocks = num_gpu_blocks # GPU 上可用的 Block 总数
self.num_cpu_blocks = num_cpu_blocks # CPU 上可用的 Block 总数
# 空闲 Block 列表(用位图或 Free List 管理)
self.free_gpu_blocks = list(range(num_gpu_blocks))
self.free_cpu_blocks = list(range(num_cpu_blocks))
def can_allocate(self, num_required_blocks):
"""检查是否有足够的空闲 Block"""
return len(self.free_gpu_blocks) >= num_required_blocks
def allocate(self, num_blocks):
"""分配指定数量的 Block"""
allocated = self.free_gpu_blocks[:num_blocks]
self.free_gpu_blocks = self.free_gpu_blocks[num_blocks:]
return allocated
def free(self, blocks):
"""释放 Block"""
self.free_gpu_blocks.extend(blocks)
def swap_out(self, blocks):
"""将 Block 从 GPU 交换到 CPU"""
# 实际执行 GPU→CPU 的数据拷贝
self.free_gpu_blocks.extend(blocks)
allocated_cpu = self.free_cpu_blocks[:len(blocks)]
self.free_cpu_blocks = self.free_cpu_blocks[len(blocks):]
return allocated_cpu
def swap_in(self, blocks):
"""将 Block 从 CPU 交换回 GPU"""
self.free_cpu_blocks.extend(blocks)
allocated_gpu = self.free_gpu_blocks[:len(blocks)]
self.free_gpu_blocks = self.free_gpu_blocks[len(blocks):]
return allocated_gpu
4.4 GPU KV Cache 容量计算
启动 vLLM 时,引擎会自动计算 GPU 可用多少 KV Cache Block:
可用 KV Cache 显存 = GPU 总显存 - 模型参数占用 - 框架运行开销
可用 Block 数量 = 可用 KV Cache 显存 / Block 大小
Block 大小的计算公式:
Block 大小 (bytes) = block_size × num_layers × 2 × num_heads × head_dim × sizeof(dtype)
以 LLaMA-2-70B(BF16)为例,block_size=16:
Block 大小 = 16 × 80 × 2 × 64 × 128 × 2 = 16,777,216 bytes ≈ 16 MB
在 A100-80G 上,扣除模型参数(约 140GB → 需要 2 张 A100)后,每张卡大约可以分配 2000+ 个 Block,对应 32000+ tokens 的 KV Cache。
五、量化推理:在精度和速度之间找到平衡
5.1 vLLM 支持的量化方案
vLLM 支持多种量化方案,从训练后量化(PTQ)到混合精度:
| 量化方案 | 精度 | 显存节省 | 性能影响 | 适用场景 |
|---|---|---|---|---|
| FP16/BF16 | 16-bit | 基准 | 基准 | 默认选择 |
| FP8 | 8-bit | ~50% | <1% 精度损失 | H100/Ada Lovelace GPU |
| GPTQ | 4-bit | ~75% | 1-3% 精度损失 | 推理部署 |
| AWQ | 4-bit | ~75% | 1-2% 精度损失 | 推理部署 |
| INT8 (W8A8) | 8-bit | ~50% | <1% 精度损失 | 通用场景 |
| KV Cache 量化 | 8-bit | KV Cache ~50% | 较小影响 | 长序列场景 |
5.2 FP8 量化:新一代 GPU 的杀手锏
FP8 是 NVIDIA H100(Hopper)和 Ada Lovelace(RTX 4090)架构引入的新数据格式,提供 E4M3(用于前向传播)和 E5M2(用于反向传播)两种编码方式。
在 vLLM 中启用 FP8 量化非常简单:
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-8B \
--quantization fp8 \
--dtype float16
FP8 的优势在于:
- 硬件原生支持,计算速度几乎不降低
- 精度损失极小(<1%),对大部分任务几乎无感知
- 显存占用减半,可以在同等硬件上部署更大的模型或服务更多并发
5.3 AWQ:激活感知的 4-bit 量化
AWQ(Activation-Aware Weight Quantization)的核心洞察是:不是所有权重对模型输出都同样重要。AWQ 通过分析激活值来识别"重要"的权重通道,对这些通道保持较高精度,其他通道进行更激进的量化。
# 使用 AWQ 量化模型
python -m vllm.entrypoints.openai.api_server \
--model TheBloke/Llama-2-70B-AWQ \
--quantization awq \
--dtype float16
5.4 KV Cache 量化
对于长序列场景,vLLM 支持 KV Cache 的 INT8 量化,可以将 KV Cache 的显存占用减半:
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-8B \
--kv-cache-dtype float8 \
--enforce-eager
六、多 GPU 并行:张量并行与流水线并行
6.1 张量并行(Tensor Parallelism)
张量并行是将模型的每一层参数切分到多个 GPU 上,每个 GPU 计算一部分,然后通过通信合并结果。
在 vLLM 中启用张量并行:
# 在 4 张 A100 上部署 LLaMA-70B
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-70B \
--tensor-parallel-size 4
vLLM 使用 Megatron-LM 风格的张量并行实现:
- 矩阵乘法沿列或行切分
- 注意力计算中,QKV 投影沿列切分,输出投影沿行切分
- 使用 NCCL 进行 GPU 间通信
张量并行的效率高度依赖于 GPU 间的通信带宽。在 NVLink 连接的服务器上(如 DGX A100),张量并行的效率可以接近线性。在 PCIe 连接的环境下,通信开销会显著增加。
实践建议:
- 单机 8 卡(NVLink):TP=8,效果最佳
- 单机 4 卡(NVLink):TP=4
- 跨机部署:尽量避免跨机 TP,改用流水线并行
6.2 流水线并行(Pipeline Parallelism)
流水线并行将模型的不同层分配到不同的 GPU 上,数据依次流过各个阶段。
# 将 LLaMA-70B 分到 2 个节点,每个节点 4 张卡
# 节点1(TP=4, PP stage 0)
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-70B \
--tensor-parallel-size 4 \
--pipeline-parallel-size 2 \
--pipeline-parallel-rank 0
# 节点2(TP=4, PP stage 1)
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-70B \
--tensor-parallel-size 4 \
--pipeline-parallel-size 2 \
--pipeline-parallel-rank 1
流水线并行的挑战在于"气泡"(Bubble)问题——当一个阶段在计算时,其他阶段在等待。vLLM 通过微批次(Micro-batch)技术来减少气泡:
GPU 0: [微批次1层1-20][微批次2层1-20][微批次3层1-20][等待...]
GPU 1: [等待..][微批次1层21-40][微批次2层21-40][微批次3层21-40]
七、性能调优实战:从入门到精通
7.1 基础参数调优
以下是最重要的启动参数及其调优策略:
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen3-8B \
--served-model-name qwen3-8b \
--max-model-len 8192 \ # 最大序列长度
--max-num-batched-tokens 32768 \ # 单批次最大 token 数
--max-num-seqs 256 \ # 最大并发请求数
--gpu-memory-utilization 0.90 \ # GPU 显存使用上限(默认 0.9)
--swap-space 4 \ # CPU 交换空间(GB)
--enable-prefix-caching \ # 启用前缀缓存
--dtype float16 # 数据类型
参数调优指南:
--max-num-batched-tokens
- 控制 GPU 算力的利用上限
- 太小:GPU 利用率低,吞吐量低
- 太大:显存不足,触发 OOM
- 调优策略:从 2048 开始,逐步增加直到 GPU 计算利用率 > 90%
--max-num-seqs
- 控制最大并发请求数
- 影响延迟(请求数越多,单请求等待时间越长)
- 调优策略:根据业务 SLA 的延迟要求来设置
--gpu-memory-utilization
- 控制显存使用上限(默认 0.9)
- 留 10% 显存给 CUDA 上下文和其他运行时开销
- 如果显存充裕,可以设到 0.95
7.2 监控指标解读
vLLM 内置了 Prometheus 指标导出,以下是关键指标:
# 核心性能指标
vllm:num_requests_running # GPU 上正在运行的请求数
vllm:num_requests_waiting # 排队等待的请求数
vllm:num_requests_swapped # 被交换到 CPU 的请求数
# 缓存指标
vllm:gpu_cache_usage_perc # GPU KV Cache 使用率
vllm:cpu_cache_usage_perc # CPU KV Cache 使用率
vllm:cpu_cache_usage_perc # 前缀缓存命中率
# 延迟指标
vllm:e2e_request_latency_seconds # 端到端请求延迟
vllm:time_to_first_token_seconds # 首 Token 延迟(TTFT)
vllm:time_per_output_token_seconds # 每 Token 输出时间(TPOT)
vllm:generation_throughput_tokens_per_sec # 生成吞吐量
故障排查指南:
| 症状 | 关键指标 | 可能原因 | 解决方案 |
|---|---|---|---|
| TTFT 高 | num_requests_running 飙高 | 批次太大,预填充排队 | 降低 max_num_seqs |
| TPOT 高 | gpu_cache_usage_perc > 95% | KV Cache 不足,频繁换入换出 | 增加 GPU 或启用 KV 量化 |
| 吞吐量低 | num_requests_waiting 持续 > 0 | 调度瓶颈 | 增加 max_num_batched_tokens |
| OOM | - | 显存不足 | 降低 max_model_len 或使用量化 |
| 延迟抖动 | num_requests_swapped 频繁变化 | GPU↔CPU 交换 | 增加 swap_space 或 GPU 数量 |
7.3 搭建 Prometheus + Grafana 监控
#!/bin/bash
# docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
depends_on:
- prometheus
volumes:
prometheus_data:
grafana_data:
EOF
cat > prometheus.yml << 'EOF'
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'vllm'
static_configs:
- targets: ['host.docker.internal:8000']
EOF
docker compose up -d
八、生产级部署架构
8.1 Docker 部署
# 拉取官方镜像
docker pull vllm/vllm-openai:latest
# 启动服务
docker run --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-p 8000:8000 \
--ipc=host \
vllm/vllm-openai:latest \
--model Qwen/Qwen3-8B \
--served-model-name qwen3-8b \
--max-model-len 8192 \
--gpu-memory-utilization 0.92 \
--enable-prefix-caching \
--dtype float16
重要:--ipc=host 不能省略。vLLM 使用 PyTorch 的多进程数据加载器,需要足够的共享内存。默认的 Docker 共享内存(64MB)会导致服务崩溃。
8.2 Kubernetes 部署
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-qwen3-8b
spec:
replicas: 2
selector:
matchLabels:
app: vllm
template:
metadata:
labels:
app: vllm
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
command:
- --model
- Qwen/Qwen3-8B
- --served-model-name
- qwen3-8b
- --max-model-len
- "8192"
- --gpu-memory-utilization
- "0.92"
- --enable-prefix-caching
resources:
limits:
nvidia.com/gpu: 1
memory: "32Gi"
ports:
- containerPort: 8000
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 120
periodSeconds: 30
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 300
periodSeconds: 60
volumeMounts:
- name: model-cache
mountPath: /root/.cache/huggingface
volumes:
- name: model-cache
persistentVolumeClaim:
claimName: model-cache-pvc
---
apiVersion: v1
kind: Service
metadata:
name: vllm-service
spec:
selector:
app: vllm
ports:
- port: 80
targetPort: 8000
type: LoadBalancer
8.3 多模型部署与 LoRA 适配器
vLLM 支持通过 LoRA(Low-Rank Adaptation)在同一个基础模型上服务多个微调版本:
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-8B \
--enable-lora \
--lora-modules \
lora-code=/path/to/code-lora \
lora-math=/path/to/math-lora \
lora-legal=/path/to/legal-lora \
--max-lora-rank 16
调用时指定 LoRA 适配器:
response = client.chat.completions.create(
model="meta-llama/Meta-Llama-3-8B",
messages=[...],
extra_body={"lora_name": "lora-code"}
)
这种方式的优势是:
- 只需加载一次基础模型
- 不同 LoRA 适配器共享 KV Cache Block
- 切换适配器的开销极小(毫秒级)
8.4 vLLM-ATOM:AMD GPU 的推理加速
2026 年 5 月,AMD 推出了 vLLM-ATOM 插件,为 AMD Instinct GPU 提供推理优化。该插件的核心特性:
- 零代码修改:不改动 vLLM 的 API 和工作流
- 自动优化:后台自动接管计算优化
- 模型支持:DeepSeek-R1、Kimi-K2、gpt-oss-120B 等
这标志着 vLLM 生态从纯 NVIDIA 生态扩展到了 AMD ROCm 生态,对于需要降低 GPU 采购成本的团队来说是个好消息。
九、vLLM vs 其他推理框架对比
| 特性 | vLLM | TGI (HuggingFace) | SGLang | LMDeploy |
|---|---|---|---|---|
| PagedAttention | ✅ | ✅ | ✅ | ✅ |
| 连续批处理 | ✅ | ✅ | ✅ | ✅ |
| 前缀缓存 | ✅ | ✅ | ✅ | ❌ |
| 张量并行 | ✅ | ✅ | ✅ | ✅ |
| 流水线并行 | ✅ | ✅ | ✅ | ❌ |
| 多 LoRA | ✅ | ❌ | ✅ | ✅ |
| 量化(GPTQ/AWQ/FP8) | ✅ | ✅ | ✅ | ✅ |
| Chunked Prefill | ✅ | ✅ | ✅ | ❌ |
| CUDA Graph | ✅ | ❌ | ✅ | ✅ |
| AMD ROCm 支持 | ✅ | ❌ | ❌ | ✅ |
| Speculative Decoding | ✅ | ✅ | ✅ | ❌ |
| RadixAttention | ❌ | ❌ | ✅ | ❌ |
| OpenAI 兼容 API | ✅ | ✅ | ✅ | ❌ |
| 社区活跃度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
选择建议:
- 通用部署首选:vLLM —— 功能最全面,社区最活跃
- 需要 RadixAttention(细粒度缓存):SGLang
- 纯 NVIDIA 生态 + HuggingFace 集成:TGI
- 国产模型(如 InternLM):LMDeploy
十、性能基准测试
10.1 基准测试方法
"""
vLLM 性能基准测试脚本
使用 locust 进行压力测试
"""
from locust import HttpUser, task, between
class LLMUser(HttpUser):
wait_time = between(1, 3)
@task
def chat_completion(self):
self.client.post("/v1/chat/completions", json={
"model": "qwen3-8b",
"messages": [
{"role": "system", "content": "你是一个专业的编程助手。"},
{"role": "user", "content": "请解释 Go 语言中 goroutine 的调度机制。"}
],
"max_tokens": 512,
"temperature": 0.7
})
10.2 参考性能数据
以下是基于公开基准测试的参考数据(单卡 A100-80G,BF16):
| 模型 | 并发数 | 吞吐量 (tokens/s) | TTFT (ms) | TPOT (ms) |
|---|---|---|---|---|
| LLaMA-3-8B | 1 | 2,100 | 15 | 0.48 |
| LLaMA-3-8B | 32 | 12,500 | 25 | 0.39 |
| LLaMA-3-8B | 128 | 18,200 | 35 | 0.38 |
| LLaMA-3-70B (TP=4) | 1 | 1,800 | 45 | 0.56 |
| LLaMA-3-70B (TP=4) | 32 | 8,900 | 60 | 0.42 |
| Qwen3-8B | 32 | 11,800 | 20 | 0.41 |
| Qwen3-32B (TP=2) | 32 | 9,200 | 50 | 0.45 |
十一、常见陷阱与解决方案
陷阱 1:OOM 错误
症状:torch.cuda.OutOfMemoryError: CUDA out of memory
排查步骤:
# 1. 降低最大序列长度
--max-model-len 4096 # 从 8192 降到 4096
# 2. 降低显存使用上限
--gpu-memory-utilization 0.85 # 从 0.9 降到 0.85
# 3. 使用量化
--quantization awq # 或 fp8
# 4. 增加张量并行
--tensor-parallel-size 2 # 从 1 增加到 2
# 5. 启用 KV Cache 量化
--kv-cache-dtype float8
陷阱 2:首 Token 延迟过高
症状:首个 Token 返回时间 > 5 秒
排查方向:
- 检查
num_requests_running是否过高 - 检查是否有大量长预填充请求在排队
- 启用 Chunked Prefill:
--enable-chunked-prefill - 降低
max_num_seqs以减少批处理大小
陷阱 3:吞吐量不达预期
排查方向:
- 检查 GPU 利用率:
nvidia-smi查看 Volatile GPU-Util - 如果利用率低,增加
max_num_batched_tokens - 检查前缀缓存命中率,如果不命中,检查请求是否真的共享前缀
- 确保
--enforce-eager未启用(CUDA Graph 可以提升 10-30% 吞吐)
陷阱 4:Docker 部署崩溃
症状:容器启动后立即退出
最常见原因:忘记 --ipc=host
# 正确
docker run --gpus all --ipc=host -p 8000:8000 vllm/vllm-openai:latest ...
# 错误(缺少 --ipc=host)
docker run --gpus all -p 8000:8000 vllm/vllm-openai:latest ...
十二、总结与展望
vLLM 从 2023 年开源至今,已经从一个学术项目成长为 LLM 推理部署的基础设施。它的成功不仅仅是技术上的——PagedAttention 的优雅设计、OpenAI 兼容 API 的务实选择、以及活跃的开源社区,共同构成了 vLLM 的核心竞争力。
在 2026 年,我们可以看到几个明确的发展趋势:
异构计算支持:随着 AMD vLLM-ATOM 的推出,vLLM 正在从 NVIDIA-only 扩展到多硬件平台,这将降低部署成本
更智能的调度:从简单的 FIFO 调度到基于优先级、SLA 感知的智能调度,vLLM 的调度器会越来越"聪明"
推理成本持续下降:通过 FP8/INT4 量化、KV Cache 压缩、投机解码(Speculative Decoding)等技术,每 token 的推理成本正在快速下降
长上下文优化:随着模型上下文窗口的扩展(128K、1M tokens),KV Cache 的管理效率变得越来越重要,PagedAttention 的优势会更加明显
Agent 场景的深度优化:AI Agent 的多轮对话、工具调用等场景对推理引擎提出了新的要求,vLLM 正在针对性地优化这些场景
对于正在或即将部署 LLM 服务的团队来说,vLLM 不是一个可选项——它是必选项。理解它的原理、掌握它的调优方法、建立完善的监控体系,是每一个 LLM 基础设施工程师的必修课。
参考资料
- vLLM GitHub 仓库:https://github.com/vllm-project/vllm
- PagedAttention 论文:《Efficient Memory Management for Large Language Model Serving with PagedAttention》
- vLLM 官方文档:https://docs.vllm.ai
- AMD vLLM-ATOM 公告:https://www.amd.com/en/developer/vllm-atom.html