vLLM 深度解析:高效 LLM 推理引擎的架构与实践

6386 字
32 分钟
vLLM 深度解析:高效 LLM 推理引擎的架构与实践

1. 引言:LLM 推理的挑战#

1.1 LLM 推理的两阶段#

大语言模型推理分为两个阶段:

┌─────────────────────────────────────────────────────────────┐
│ LLM 推理两阶段 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 阶段 1:Prefill(填充) │
│ ───────────────────────────────────────────────────────── │
│ 输入: [用户输入 tokens] │
│ 输出: [所有 token 的 hidden states + KV cache] │
│ 特点: 高度并行,适合 GPU 计算 │
│ 耗时: 与输入长度成正比 │
│ │
│ 阶段 2:Decode(解码) │
│ ───────────────────────────────────────────────────────── │
│ 输入: [已生成 tokens + KV cache] │
│ 输出: [下一个 token] │
│ 特点: 自回归,逐 token 生成 │
│ 耗时: 与输出长度成正比(主要瓶颈) │
│ │
└─────────────────────────────────────────────────────────────┘

1.2 传统推理的问题#

class TraditionalInferenceIssues:
"""
传统推理的问题
"""
def kv_cache_issues(self):
"""
KV Cache 的问题
"""
return {
"memory_inefficiency": "预先分配大块连续内存,大量浪费",
"fragmentation": "不同请求长度差异导致内存碎片",
"no_sharing": "相同前缀的请求无法共享 KV cache",
"oom": "长序列请求经常 OOM",
}
def batching_issues(self):
"""
静态 Batching 的问题
"""
return {
"padding_waste": "短序列需要 padding 到最大长度",
"waiting_time": "等待所有请求到达才能开始",
"gpu_underutilization": "padding 浪费大量计算",
"poor_tail_latency": "短请求被迫等待长请求",
}
def memory_size_estimate(self):
"""
KV Cache 内存估算
以 LLaMA-2 7B 为例:
"""
return {
"model_params": "7B params",
"hidden_size": 4096,
"num_layers": 32,
"num_heads": 32,
"head_dim": 128,
"kv_cache_per_token_fp16": "2 × layers × 2 × hidden_size × bytes",
"calculation": "2 × 32 × 2 × 4096 × 2 = 1 MB per token",
"example": {
"context_4k_tokens": "4 GB for KV cache",
"context_8k_tokens": "8 GB for KV cache",
"total_7b_model": "14 GB weights + 8 GB KV = 22 GB",
"gpu_memory": "RTX 3090 has 24 GB total",
"problem": "Barely fits, no room for others",
},
}

1.3 vLLM 的核心创新#

┌─────────────────────────────────────────────────────────────┐
│ vLLM 核心创新 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. PagedAttention │
│ → 受操作系统分页启发 │
│ → 动态管理 KV Cache 内存 │
│ → 消除内存碎片 │
│ │
│ 2. Continuous Batching │
│ → 动态添加新请求到批次 │
│ → 最大化 GPU 利用率 │
│ → 显著降低 latency │
│ │
│ 3. 高效 CUDA Kernel │
│ → 专为 PagedAttention 优化 │
│ → FlashAttention 集成 │
│ → CUDA Graph 加速 │
│ │
│ 4. Tensor Parallelism │
│ → 多 GPU 分布式推理 │
│ → 模型并行与服务并行 │
│ │
│ 性能提升(对比 HuggingFace): │
│ → 吞吐量提升 24x │
│ → 延迟降低 2.2x │
│ → 显存占用降低 60% │
│ │
└─────────────────────────────────────────────────────────────┘

1.4 本系列文章关联#

文章关联
模型量化深度解析INT8/INT4 量化与压缩
推理时扩展测试时计算与推理优化
后训练深度解析SFT/RLHF 训练优化

2. PagedAttention 机制#

2.1 动机:操作系统的分页管理#

class PagedAttentionMotivation:
"""
PagedAttention 的动机
受操作系统虚拟内存分页启发
"""
def os_paging_concept(self):
"""
操作系统分页概念
"""
return """
OS 虚拟内存管理:
物理内存:
┌──────┬──────┬──────┬──────┬──────┬──────┐
│ Page │ Page │ Page │ Page │ Page │ ... │
│ 0 │ 1 │ 2 │ 3 │ 4 │ │
└──────┴──────┴──────┴──────┴──────┴──────┘
虚拟内存到物理内存的映射(Page Table):
┌────────────┬────────────┐
│ Virtual │ Physical │
├────────────┼────────────┤
│ Page 0 │ Page 2 │
│ Page 1 │ Page 5 │
│ Page 2 │ Page 0 │
│ ... │ ... │
└────────────┴────────────┘
优势:
→ 物理内存可以非连续
→ 按需分配,按需加载
→ 共享内存(如 fork)
→ 虚拟内存大小可 > 物理内存
"""
def llm_paging_analogy(self):
"""
LLM 推理中的对应
"""
return """
vLLM 中的类比:
KV Cache = OS 中的内存页
Block Table = OS 中的页表
OS: 进程虚拟地址 → 物理内存
vLLM: token 位置 → KV cache block
Block 0: [token 0, token 1, ..., token 15]
Block 1: [token 16, token 17, ..., token 31]
...
"""

2.2 PagedAttention 实现#

class PagedAttention:
"""
PagedAttention 实现
"""
def __init__(self, block_size=16):
self.block_size = block_size
self.block_tables = {} # 存储每个序列的 block 映射
def allocate_kv_blocks(self, max_blocks_per_seq):
"""
分配 KV blocks
类似于 OS 的物理页分配
"""
# 从空闲池获取 blocks
available_blocks = self.get_available_blocks()
if len(available_blocks) < max_blocks_per_seq:
# 需要 evict 某些序列的 blocks
self.evict_least_recently_used()
available_blocks = self.get_available_blocks()
allocated_blocks = available_blocks[:max_blocks_per_seq]
return allocated_blocks
def paged_attention_forward(
self,
query, # (batch_size, num_heads, head_dim)
key_cache, # 存储在多个 blocks 中
value_cache,
block_tables, # token_idx -> block_idx 映射
seq_lens,
):
"""
PagedAttention 前向传播
核心思想:
1. 将 KV cache 存储在非连续的 blocks 中
2. 通过 block table 动态映射
3. 注意力计算时按需获取
"""
import torch
batch_size, num_heads, head_dim = query.shape
max_seq_len = seq_lens.max()
# 输出
output = torch.zeros_like(query)
# 对每个 head 单独计算
for head_idx in range(num_heads):
q = query[:, head_idx, :] # (batch_size, head_dim)
# 分块计算 attention
for block_idx in range(self.num_blocks):
# 获取该 block 的 key 和 value
k = self.get_key_block(head_idx, block_idx)
v = self.get_value_block(head_idx, block_idx)
# 计算 attention scores
scores = q @ k.transpose(-2, -1) # (batch_size, 1, block_size)
scores = scores / (head_dim ** 0.5)
scores = torch.softmax(scores, dim=-1)
# 加权求和
attn_out = scores @ v # (batch_size, 1, head_dim)
# 累加到输出
output[:, head_idx, :] += attn_out.squeeze(1)
return output

2.3 Block Table 管理#

class BlockTableManagement:
"""
Block Table 管理
"""
def block_table_structure(self):
"""
Block Table 结构
对于序列 [0, 1, 2, ..., 67](68 tokens)
block_size = 16
"""
return {
"num_tokens": 68,
"num_blocks": 5, # ceil(68/16) = 5
"block_table": [
# physical_block_id
7, # tokens [0-15] -> physical block 7
3, # tokens [16-31] -> physical block 3
9, # tokens [32-47] -> physical block 9
2, # tokens [48-63] -> physical block 2
12, # tokens [64-67] -> physical block 12
],
"metadata": {
"num_full_blocks": 4,
"num_partial_tokens": 4,
},
}
def dynamic_allocation(self):
"""
动态分配示例
"""
return """
请求 1: 生成 100 tokens
Block Table 1: [0, 1, 2, 3, 4, 5, 6]
请求 2: 生成 50 tokens
Block Table 2: [7, 8, 9, 10, 11]
请求 3: 生成 200 tokens (中途追加)
Block Table 3: [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
物理内存:
Block 0-6: 请求 1
Block 7-11: 请求 2
Block 12-24: 请求 3
关键点:
→ 请求可以随时扩展 blocks
→ 不同请求的 blocks 可以交错
→ 最大化内存利用率
"""
def prefix_sharing(self):
"""
前缀共享
"""
return """
场景:多个请求有相同的系统提示
请求 1: [System Prompt] + [User Query 1]
请求 2: [System Prompt] + [User Query 2]
请求 3: [System Prompt] + [User Query 3]
vLLM 优化:
System Prompt 的 KV blocks 可以共享!
Block Table 1: [prefix_blocks] + [0, 1, 2] # 独有部分
Block Table 2: [prefix_blocks] + [3, 4, 5] # 独有部分
Block Table 3: [prefix_blocks] + [6, 7, 8] # 独有部分
内存节省:
→ 原来:3 × len(System Prompt) × KV_size
→ 现在:1 × len(System Prompt) × KV_size + 3 × len(Unique) × KV_size
实际效果:prefix 越长,节省越多
"""

3. KV Cache 管理#

3.1 传统 KV Cache 的问题#

class TraditionalKVCachingIssues:
"""
传统 KV Cache 管理的问题
"""
def preallocated_problem(self):
"""
预分配的问题
假设 max_seq_len = 8192, block_size = 16
"""
return {
"preallocated_blocks": "8192 / 16 = 512 blocks per sequence",
"per_sequence_memory": "512 × 16 × 128 × 2 × 2 bytes ≈ 2.6 MB",
"problem": "即使只生成 100 tokens,也浪费 412 blocks",
"utilization": "100 tokens / 8192 max = 1.2% utilization",
}
def fragmentation_analysis(self):
"""
内存碎片分析
"""
return """
场景:同时处理多个不同长度的请求
请求 1: 需要 100 tokens (预留 8192)
请求 2: 需要 500 tokens (预留 8192)
请求 3: 需要 2000 tokens (预留 8192)
请求 4: 需要 50 tokens (预留 8192)
总预留:4 × 8192 × 2 bytes = 64 KB per layer
实际使用:2550 × 2 bytes = 5 KB per layer
利用率:5 / 64 = 7.8%
GPU 内存浪费:
→ Internal fragmentation: 预留但未使用
→ External fragmentation: 可用但不连续
"""

3.2 vLLM 的 KV Cache 管理#

class VLLMKVCaching:
"""
vLLM 的 KV Cache 管理
"""
def memory_pool(self):
"""
内存池管理
"""
return """
vLLM 将 GPU 内存划分为:
┌──────────────────────────────────────────────────────────┐
│ GPU Memory │
├──────────────────────────────────────────────────────────┤
│ Model Weights │ KV Cache Pool │ Free Space │
│ (固定大小) │ (动态管理) │ │
└──────────────────────────────────────────────────────────┘
KV Cache Pool:
┌────────┬────────┬────────┬────────┬────────┬────────┐
│ Block │ Block │ Block │ Block │ Block │ ... │
│ 0 │ 1 │ 2 │ 3 │ 4 │ │
└────────┴────────┴────────┴────────┴────────┴────────┘
特点:
→ 预先分配固定大小的 blocks
→ 按需分配给请求
→ 可以随时分配和释放
"""
def eviction_policy(self):
"""
驱逐策略
"""
return {
"lru": "Least Recently Used(最近最少使用)",
"fifo": "First In First Out",
"random": "随机驱逐",
"recommendation": "LRU 通常效果最好",
"lru_reason": "近期使用的序列可能还会继续生成",
}
def swap_strategy(self):
"""
CPU-GPU Swap 策略
当 GPU 内存不足时,将部分 KV cache 换出到 CPU
"""
return """
场景:处理超长序列
1. GPU KV Cache 空间不足
2. 将部分 older blocks 换出到 CPU RAM
3. 继续处理新 tokens
4. 需要时再换回 GPU
权衡:
→ Swap 开销:PCIe 带宽
→ 但比 OOM 好得多
→ 可以处理超长序列
配置:
--gpu-memory-utilization 0.9 # 使用 90% GPU 内存
--num-swap-blocks 1000 # 允许交换的 blocks 数
"""

3.3 内存计算#

class KVCacheMemoryCalculation:
"""
KV Cache 内存计算
"""
def per_layer_memory(self, model_config):
"""
单层 KV Cache 内存
对于 LLaMA-2 7B:
"""
return {
"layers": 32,
"hidden_size": 4096,
"num_kv_heads": 32, # GQA: 8 query heads, 32 kv heads
"head_dim": 128,
"bytes_per_param": 2, # FP16
"per_token_kv": "2 × num_kv_heads × head_dim × bytes",
"calculation": "2 × 32 × 128 × 2 = 16384 bytes = 16 KB",
"per_token_total": "layers × per_token_kv",
"total": "32 × 16 KB = 512 KB per token",
"examples": {
"1k_tokens": "512 MB",
"4k_tokens": "2 GB",
"8k_tokens": "4 GB",
"32k_tokens": "16 GB",
},
}
def total_inference_memory(self, model_size, kv_cache_tokens):
"""
总推理内存
"""
kv_memory = 512 * kv_cache_tokens # KB, for 7B model
return {
"model_weights": f"{model_size} GB (FP16)",
"kv_cache": f"{kv_memory / 1e6:.1f} GB ({kv_cache_tokens} tokens)",
"activation_memory": "0.5-2 GB (取决于 batch size)",
"overhead": "0.5 GB (框架开销)",
"total_for_7b_8k": "14 + 4 + 1 + 0.5 = 19.5 GB",
}

4. Continuous Batching#

4.1 静态 Batching 的问题#

class StaticBatchingProblems:
"""
静态 Batching 的问题
"""
def padded_batching(self):
"""
Padding 导致的浪费
"""
return """
静态 Batching 示意:
Batch of 4 requests:
Request 1: [tok1, tok2, tok3, tok4, ...] (长度 50)
Request 2: [tok1, tok2, ...] (长度 10) ← PADDING
Request 3: [tok1, tok2, tok3, ...] (长度 30) ← PADDING
Request 4: [tok1, tok2, tok3, tok4, tok5] (长度 5) ← PADDING
最大长度: 50
实际使用: 50 tokens
浪费: 45 tokens worth of computation
GPU 利用率: 50 / (50×4) = 12.5%
"""
def waiting_problem(self):
"""
等待问题
"""
return """
时间线示意:
时间 →→→→
Request 1: [████████████████████████████] (长请求)
Request 2: [███] 等待... (短请求)
Request 3: [████] 等待... (短请求)
问题:
→ 短请求必须等待长请求完成
→ P99 latency 很高
→ GPU 资源被长请求阻塞
"""

4.2 Continuous Batching 实现#

class ContinuousBatching:
"""
Continuous Batching 实现
也称为 Dynamic Batching 或 Iteration-level Scheduling
"""
def __init__(self, model, max_batch_size=32):
self.model = model
self.max_batch_size = max_batch_size
self.waiting_requests = [] # 等待处理的请求
self.running_requests = [] # 正在处理的请求
def scheduling_loop(self):
"""
调度循环
每 iteration 执行一次
"""
while True:
# Step 1: 添加新请求到运行批次
self._add_new_requests()
# Step 2: 执行一次推理
finished = self._run_step()
# Step 3: 移除完成的请求
self._remove_finished(finished)
# Step 4: 分配资源
self._allocate_kv_blocks()
# 如果没有请求了,退出
if len(self.running_requests) == 0 and len(self.waiting_requests) == 0:
break
def _add_new_requests(self):
"""
添加新请求
"""
# 从等待队列中取请求
while (
len(self.running_requests) < self.max_batch_size
and len(self.waiting_requests) > 0
):
new_request = self.waiting_requests.pop(0)
self.running_requests.append(new_request)
# 分配初始 KV blocks
self._allocate_initial_blocks(new_request)
def _run_step(self):
"""
执行一步推理
"""
# 批量执行
batch_queries = [req.current_query for req in self.running_requests]
batch_outputs = self.model.forward(batch_queries)
finished = []
for i, (req, output) in enumerate(zip(self.running_requests, batch_outputs)):
# 更新请求状态
req.append_token(output.token)
req.current_query = output.token
# 检查是否完成
if self._is_finished(req):
finished.append(req)
return finished
def _allocate_kv_blocks(self):
"""
动态分配 KV blocks
"""
for req in self.running_requests:
if req.need_new_block():
# 分配新 block
block = self.memory_pool.allocate()
req.block_table.append(block)

4.3 Continuous Batching 效果#

Continuous Batching vs 静态 Batching:
静态 Batching(batch_size=4):
┌─────────────────────────────────────────────────────────────┐
│ Time →→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→│
│ │
│ Iter 1: [R1][R2][R3][R4] (R2,R3,R4 padding) │
│ Iter 2: [R1][ - ][ - ] (R2,R3,R4 已完成) │
│ Iter 3: [R1][R5][R6][ - ] (R5,R6 填补) │
│ Iter 4: [R1][R5][R6][ - ] │
│ ... │
│ │
│ 问题:GPU 利用率低,P99 延迟高 │
└─────────────────────────────────────────────────────────────┘
Continuous Batching:
┌─────────────────────────────────────────────────────────────┐
│ Time →→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→│
│ │
│ Iter 1: [R1][R2][R3][R4] │
│ Iter 2: [R1][R5][R6][R7] ← R2,R3,R4 完成,R5,R6,R7 加入 │
│ Iter 3: [R1][R5][R6][R7] │
│ Iter 4: [R1][R5][R8][R9] ← R6,R7 完成,R8,R9 加入 │
│ ... │
│ │
│ 优势:GPU 始终满载,P99 延迟低 │
└─────────────────────────────────────────────────────────────┘
性能提升:
→ 吞吐量提升 5-10x
→ P99 latency 降低 3-5x

5. Tensor Parallelism#

5.1 模型并行基础#

class TensorParallelism:
"""
Tensor Parallelism 实现
"""
def tensor_parallel_concept(self):
"""
Tensor 并行概念
将单层的权重矩阵切分到多个 GPU
"""
return """
示例:Matrix Multiplication Y = XW
单 GPU:
┌─────────────────────┐
│ X (b, d) │
│ × │
│ W (d, d) │
│ = │
│ Y (b, d) │
└─────────────────────┘
2-GPU Tensor Parallel:
┌──────────────┬──────────────┐
│ GPU 0 │ GPU 1 │
│ X (b, d) │ X (b, d) │ ← 复制输入
│ × │ × │
│ W0 (d, d/2) │ W1 (d, d/2) │ ← 切分权重
│ = │ = │
│ Y0 (b, d/2) │ Y1 (b, d/2) │ ← 部分结果
│ ↓ │ ↓ │
│ └── AllReduce ──┘ │
│ ↓ │
│ Y (b, d) │
└─────────────────────────────┘
"""
def column_parallel(self):
"""
Column Parallel (用于 QKV 投影)
"""
return """
QKV Linear Layer:
W_qkv: (d_model, 3 * d_model)
切分:
GPU 0: W_q0, W_k0, W_v0 → 计算 Q0, K0, V0
GPU 1: W_q1, W_k1, W_v1 → 计算 Q1, K1, V1
输出拼接后正常
"""
def row_parallel(self):
"""
Row Parallel (用于输出投影)
"""
return """
Output Linear Layer:
W_o: (d_model, d_model)
切分:
GPU 0: W_o0 (d/2, d) → 计算 Y0 = X0 @ W_o0
GPU 1: W_o1 (d/2, d) → 计算 Y1 = X1 @ W_o1
AllReduce 求和
"""

5.2 vLLM 的 Tensor Parallelism#

class VLLMTensorParallel:
"""
vLLM Tensor Parallelism
"""
def multi_gpu_setup(self):
"""
多 GPU 设置
"""
return """
启动 4-GPU 推理:
python -m vllm.entrypoints.openai.api_server \\
--model meta-llama/Llama-2-70b-hf \\
--tensor-parallel-size 4 \\
--gpu-memory-utilization 0.9
vLLM 会:
1. 将模型权重切分到 4 个 GPU
2. 使用 NCCL 进行 GPU 间通信
3. 协调调度所有 GPU
"""
def communication_pattern(self):
"""
通信模式
"""
return {
"forward_pass": {
"all_reduce": "拼接各 GPU 的部分结果",
"collective": "NCCL AllReduce",
"bandwidth": "NVLink (400+ GB/s)",
},
"attention": {
"query_distribution": "Q 分布到各 GPU",
"key_value_broadcast": "K,V 需要广播",
"attention_output": "各 GPU 计算部分 attention",
},
"memory_bandwidth": "NVLink 带宽是关键瓶颈",
}
def memory_distribution(self):
"""
内存分布
"""
return {
"single_gpu_70b": "140 GB (FP16)",
"per_gpu_70b_tp4": "35 GB + KV cache",
"kv_cache_per_gpu": "取决于 batch size",
"nvlink_benefit": "4-GPU 比单 GPU 快 ~3.5x",
"bandwidth_limitation": "超过 8-GPU,增益递减",
}

5.3 Pipeline Parallelism 补充#

class PipelineParallelism:
"""
Pipeline Parallelism(作为 Tensor Parallelism 的补充)
"""
def layer_distribution(self):
"""
按层分布
适用于超大型模型(>100B)
"""
return """
8-GPU Pipeline Parallel 示例:
GPU 0: Layers 0-9
GPU 1: Layers 10-19
GPU 2: Layers 20-29
GPU 3: Layers 30-39
GPU 4: Layers 40-49
GPU 5: Layers 50-59
GPU 6: Layers 60-69
GPU 7: Layers 70-79
前向: 数据流经所有 GPU
反向: 梯度反向流回
"""
def vllm_combined(self):
"""
vLLM 组合并行
"""
return """
组合使用 Tensor + Pipeline Parallelism:
Total GPUs: 16
Tensor Parallelism: 4 (TP)
Pipeline Parallelism: 4 (PP)
模型:
- 80 layers
- 每 GPU: 80 / 4 = 20 layers
- 每 layer: 4-way tensor parallel
配置示例:
--tensor-parallel-size 4 \\
--pipeline-parallel-size 4
"""

6. Prefix Caching#

6.1 共享前缀问题#

class PrefixCachingProblem:
"""
共享前缀问题
"""
def common_scenario(self):
"""
常见场景
"""
return """
LLM 服务的常见模式:
System Prompt:
"你是一个有用的AI助手..."
(500 tokens)
用户请求:
Request 1: [System] + [User Query 1]
Request 2: [System] + [User Query 2]
Request 3: [System] + [User Query 3]
...
问题:
→ System Prompt 的 KV cache 需要重复计算 N 次
→ 浪费大量计算和内存
→ 在高并发服务中尤其严重
"""
def traditional_waste(self):
"""
传统方式浪费
"""
return {
"system_prompt_tokens": 500,
"concurrent_requests": 100,
"repeated_computation": "500 × 99 = 49,500 tokens",
"kv_cache_waste": "49.5x memory usage for prefix",
}

6.2 vLLM Prefix Caching#

class VLLMPrefixCaching:
"""
vLLM Prefix Caching 实现
"""
def hash_based_caching(self):
"""
基于哈希的缓存
"""
return """
vLLM 的 Prefix Caching 策略:
1. 计算 KV blocks 的哈希值
┌────────────┬────────────┐
│ Block Hash│ KV Content │
├────────────┼────────────┤
│ hash([0-15])│ tokens[0-15] │
│ hash([16-31])│ tokens[16-31] │
└────────────┴────────────┘
2. 使用 hash 作为 cache key
┌────────────┬────────────┐
│ Hash │ Cached │
├────────────┼────────────┤
│ abc123 │ ✓ │
│ def456 │ ✓ │
│ ghi789 │ ✗ │
└────────────┴────────────┘
3. 新请求到来时:
- 计算其前缀的哈希
- 检查 cache 中是否存在
- 命中则跳过计算
"""
def block_table_with_hash(self):
"""
带哈希的 Block Table
"""
return """
Block Table 结构:
Sequence: [prefix] + [A] + [B] + [C]
Block Table:
┌───────┬─────────────┬────────┐
│Block #│ Physical ID │ Hash │
├───────┼─────────────┼────────┤
│ 0 │ 7 │ abc123 │ ← prefix block
│ 1 │ 3 │ def456 │
│ 2 │ 9 │ ghi789 │
│ 3 │ 2 │ jkl012 │
└───────┴─────────────┴────────┘
缓存命中:
- 如果新请求的前缀是 [prefix] + [A]
→ blocks 0, 1 命中,直接复用
→ 只需计算 [B] + [C]
"""
def cache_key_calculation(self):
"""
Cache Key 计算
"""
return '''
def compute_block_hash(tokens):
"""计算 block 的哈希值"""
import hashlib
# 使用 tokens 内容计算哈希
content = str(tokens)
hash_value = hashlib.sha256(content.encode()).hexdigest()[:16]
return hash_value
def check_cache(hashes):
"""检查缓存命中"""
cached_blocks = {}
for hash_value in hashes:
if hash_value in cached_blocks:
# 命中
yield cached_blocks[hash_value]
else:
# 未命中,需要计算
break
'''

6.3 自动前缀识别#

class AutomaticPrefixDetection:
"""
自动前缀检测
"""
def prompt_identification(self):
"""
Prompt 识别
"""
return """
vLLM 自动识别共享前缀:
1. 系统 Prompt 识别
┌────────────────────────────────────────┐
│ System: "你是AI助手..." │
│ User 1: "问题1" │
│ User 2: "问题2" │
│ User 3: "问题3" │
└────────────────────────────────────────┘
→ System 部分的 KV 全部共享
2. 对话历史识别
┌────────────────────────────────────────┐
│ [History 1] + "回答1" │
│ [History 1] + "回答2" + "问题3" │
└────────────────────────────────────────┘
→ History 1 共享
3. Few-shot Examples 识别
┌────────────────────────────────────────┐
│ Example 1: Q + A │
│ Example 2: Q + A │
│ Example 3: Q (待回答) │
└────────────────────────────────────────┘
→ Examples 共享
"""
def performance_impact(self):
"""
性能影响
"""
return {
"system_prompt_500_tokens": {
"without_cache": "500 tokens × 100 req = 50,000 compute",
"with_cache": "500 tokens × 1 + 少量 = ~500 compute",
"speedup": "100x",
},
"few_shot_1k_tokens": {
"without_cache": "1000 tokens × 1000 req = 1M compute",
"with_cache": "1000 tokens × 1 + 少量 = ~1K compute",
"speedup": "1000x",
},
}

7. 高效 CUDA Kernels#

7.1 FlashAttention 集成#

class FlashAttentionIntegration:
"""
FlashAttention 集成
"""
def flash_attention_benefits(self):
"""
FlashAttention 的优势
"""
return {
"memory_efficiency": "O(N) 而非 O(N²)",
"speed": "2-4x faster than standard attention",
"no_explicit_cache": "无需保存完整 attention matrix",
"numerical_stability": "更好的数值稳定性",
}
def vllm_integration(self):
"""
vLLM 集成
"""
return """
vLLM 的 attention 计算:
1. 使用 FlashAttention 作为后端
2. 结合 PagedAttention 的 block 管理
3. 两者结合实现高效推理
配置:
--attention-backend FLASH_ATTN # 或 FLASH_INFER
内部实现:
def paged_attention_with_flash(
query, key_cache, value_cache, block_tables, ...
):
# FlashAttention kernel
# 但 key/value 来自分页的 cache blocks
return flash_attn_varlen_func(
q, # (total_tokens, num_heads, head_dim)
kv_cache, # 来自 block table
...
)
"""

7.2 CUDA Graphs#

class CUDAGraphs:
"""
CUDA Graphs 优化
"""
def what_is_cuda_graph(self):
"""
CUDA Graphs 是什么
"""
return """
CUDA Graphs (CUDA 10+):
问题:
CPU-GPU 通信开销大
每个 kernel 启动有延迟
小型 kernel 启动开销明显
解决:
将多个 kernel 打包成一个 "graph"
一次性启动整个 graph
减少 CPU-GPU 通信
效果:
→ 减少 20-30% 的 kernel 启动开销
→ 对小 batch size 尤其有效
→ decode 阶段效果明显
"""
def vllm_cuda_graph(self):
"""
vLLM 中的 CUDA Graphs
"""
return """
vLLM 为不同的 prefill/decode 场景预编译 graphs:
1. Prefill Graphs
- 捕获 prefill 阶段的所有 kernel
- 捕获不同输入长度的变体
2. Decode Graphs
- 捕获单步 decode 的所有 kernel
- 对于 batch_size=1 优化
3. Batch Decode Graphs
- 捕获不同 batch_size 的 decode
限制:
→ 输入 shape 必须固定
→ vLLM 使用 padding 和 reshape 来支持
配置:
--enforce-eager # 禁用 CUDA graphs(调试用)
"""

7.3 其他优化#

class OtherOptimizations:
"""
其他优化
"""
def weight_only_quantization(self):
"""
权重量化推理
"""
return """
Weight-Only Quantization:
模型权重量化,激活值保持 FP16/BF16
支持格式:
- INT8 (W8A16)
- INT4 (W4A16) - 显存减半
效果:
- 70B 模型: 140GB → 70GB
- 性能损失 < 1%
配置:
--quantization awq # 或 gptq, squeezellm
"""
def speculative_decoding(self):
"""
投机解码
"""
return """
Speculative Decoding:
使用小模型生成多个候选 tokens
大模型验证并修正
流程:
1. 小模型生成 k 个候选 token
2. 大模型并行验证
3. 接受所有一致的 tokens
效果:
→ decode 加速 2-3x
→ 保持输出质量
vLLM 支持:
--speculative-model <model_name>
--speculative-draft-tokens 5
"""
def grouped_attention(self):
"""
分组注意力(GQA/MQA)
"""
return """
Grouped Query Attention (GQA):
减少 KV cache 大小
MQA: 1 个 KV head
GQA: 4-8 个 KV heads
MHA: 32 个 KV heads
vLLM 优化:
- 高效实现 GQA
- 减少内存访问
- 加速 decode
"""

8. 实战部署#

8.1 快速启动#

class QuickStart:
"""
快速启动 vLLM 服务
"""
def basic_server(self):
"""
基本服务器
"""
return """
# 安装
pip install vllm
# 启动 OpenAI 兼容 API
python -m vllm.entrypoints.openai.api_server \\
--model meta-llama/Llama-2-7b-hf \\
--trust-remote-code \\
--gpu-memory-utilization 0.9
# 测试
curl http://localhost:8000/v1/completions \\
-H "Content-Type: application/json" \\
-d '{
"model": "meta-llama/Llama-2-7b-hf",
"prompt": "The capital of France is",
"max_tokens": 50
}'
"""
def hf_backend(self):
"""
HuggingFace 模型
"""
return """
# 直接加载 HuggingFace 模型
python -m vllm.entrypoints.openai.api_server \\
--model mistralai/Mistral-7B-Instruct-v0.2 \\
--tokenizer mistralai/Mistral-7B-Instruct-v0.2 \\
--task auto
# 支持的模型自动检测
# 自动选择合适的 architecture
"""

8.2 性能调优#

class PerformanceTuning:
"""
性能调优
"""
def critical_params(self):
"""
关键参数
"""
return {
"--tensor-parallel-size": {
"description": "Tensor parallel GPU 数",
"default": 1,
"7b_model": "1 (单卡 24GB 足够)",
"70b_model": "4 (需要多卡)",
"70b_tp8": "8 (更快但需要更多卡)",
},
"--gpu-memory-utilization": {
"description": "GPU 内存使用比例",
"default": 0.9,
"tuning": "0.85 for more KV cache headroom",
"tradeoff": "高利用率 = 更多 batch,但可能 OOM",
},
"--max-model-len": {
"description": "最大序列长度",
"default": "模型预设",
"increase": "支持更长上下文,但增加内存",
"calculation": "max_len × kv_cache_per_token × layers",
},
"--block-size": {
"description": "KV cache block 大小",
"default": 16,
"tuning": "16 是最佳平衡",
"note": "过大增加碎片,过小增加管理开销",
},
}
def advanced_config(self):
"""
高级配置
"""
return """
# 高级配置示例
python -m vllm.entrypoints.openai.api_server \\
--model meta-llama/Llama-2-70b-hf \\
\\
# 并行配置
--tensor-parallel-size 4 \\
--pipeline-parallel-size 1 \\
\\
# 内存配置
--gpu-memory-utilization 0.92 \\
--max-model-len 8192 \\
--max-num-batched-tokens 32768 \\
--max-num-seqs 256 \\
\\
# 量化配置
--quantization fp8 \\
--enforce-eager \\
\\
# 其他
--download-dir ./model_cache \\
--dtype half \\
--enforce-ranked-decoder
# 监控
# --metrics-backend prometheus
"""
def batch_size_tuning(self):
"""
Batch Size 调优
"""
return {
"max_num_seqs": "同时处理的最大请求数",
"max_num_batched_tokens": "batch 中总 token 数上限",
"throughput_mode": {
"max_num_seqs": 256,
"max_num_batched_tokens": 8192,
"适合": "高吞吐场景",
},
"latency_mode": {
"max_num_seqs": 32,
"max_num_batched_tokens": 2048,
"适合": "低延迟场景",
},
}

8.3 监控与调试#

class MonitoringDebugging:
"""
监控与调试
"""
def metrics(self):
"""
可用指标
"""
return """
vLLM 暴露 Prometheus 指标:
# 服务指标
vllm:num_requests_total
vllm:num_requests_succeeded
vllm:num_requests_failed
# 延迟指标
vllm:request_latency_seconds
vllm:prefill_latency_seconds
vllm:decode_latency_seconds
# 吞吐指标
vllm:generated_tokens_total
vllm:input_tokens_total
# GPU 指标
vllm:gpu_cache_usage
vllm:num_available_blocks
访问:
http://localhost:8000/metrics
"""
def debugging_commands(self):
"""
调试命令
"""
return """
# 查看 GPU 利用率
nvidia-smi -l 1
# 查看 vLLM 日志
# vLLM 输出详细日志
# 禁用 CUDA graphs(更容易调试)
--enforce-eager
# 禁用 prefix caching(测试缓存效果)
--disable-preemption
# 使用 eager 模式(不使用 CUDA graphs)
--enforce-eager
"""

9. OpenAI 兼容 API#

9.1 Chat Completions API#

class OpenAIAPI:
"""
OpenAI 兼容 API
"""
def chat_completions(self):
"""
Chat Completions
"""
return '''
# Python client
from openai import OpenAI
client = OpenAI(
api_key="EMPTY",
base_url="http://localhost:8000/v1",
)
response = client.chat.completions.create(
model="meta-llama/Llama-2-7b-hf",
messages=[
{"role": "system", "content": "你是AI助手。"},
{"role": "user", "content": "解释量子计算。"},
],
temperature=0.7,
max_tokens=500,
)
print(response.choices[0].message.content)
'''
def completions_api(self):
"""
Completions API
"""
return '''
# 直接补全
response = client.completions.create(
model="meta-llama/Llama-2-7b-hf",
prompt="量子计算是一种",
max_tokens=100,
temperature=0.8,
)
print(response.choices[0].text)
'''
def embedding_api(self):
"""
Embedding API
"""
return '''
# 嵌入向量(如果模型支持)
response = client.embeddings.create(
model="sentence-transformers/all-MiniLM-L6-v2",
input="Hello, world!",
)
print(response.data[0].embedding)
'''

9.2 Streaming#

class StreamingAPI:
"""
Streaming 支持
"""
def streaming_chat(self):
"""
流式聊天
"""
return '''
# 流式响应
stream = client.chat.completions.create(
model="meta-llama/Llama-2-7b-hf",
messages=[{"role": "user", "content": "写一首诗"}],
stream=True,
max_tokens=200,
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
'''
def use_cases(self):
"""
Streaming 适用场景
"""
return {
"chatbot": "实时显示 AI 响应",
"long_content": "减少等待感知时间",
"real_time": "需要实时反馈的应用",
}

10. 生产环境最佳实践#

10.1 部署架构#

class DeploymentArchitecture:
"""
生产部署架构
"""
def single_node(self):
"""
单节点部署
"""
return """
单节点 4-GPU 部署:
┌─────────────────────────────────────────────┐
│ Load Balancer │
│ (nginx / traefik) │
└──────────┬──────────┬──────────┬────────────┘
│ │ │
┌────▼───┐ ┌────▼───┐ ┌────▼───┐ ┌────▼───┐
│ vLLM │ │ vLLM │ │ vLLM │ │ vLLM │
│ GPU 0 │ │ GPU 1 │ │ GPU 2 │ │ GPU 3 │
│ :8000 │ │ :8001 │ │ :8002 │ │ :8003 │
└────────┘ └────────┘ └────────┘ └────────┘
vLLM 内部已经处理多 GPU
每个实例只用 1 个 GPU
"""
def multi_node(self):
"""
多节点部署
"""
return """
多节点部署:
Node 0 (4x A100): vLLM TP=4
Node 1 (4x A100): vLLM TP=4
Node 2 (4x A100): vLLM TP=4
Node 3 (4x A100): vLLM TP=4
使用 Ray 作为协调器:
ray start --head
ray start --node-ip-address=<node_ip>
vLLM 自动管理多节点分布式推理
"""
def kubernetes(self):
"""
Kubernetes 部署
"""
return """
# Deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-server
spec:
replicas: 2
template:
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
resources:
limits:
nvidia.com/gpu: 4
args:
- --model=meta-llama/Llama-2-70b-hf
- --tensor-parallel-size=4
- --gpu-memory-utilization=0.9
ports:
- containerPort: 8000
"""

10.2 高可用配置#

class HighAvailability:
"""
高可用配置
"""
def health_check(self):
"""
健康检查
"""
return """
# Liveness Probe
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
# Readiness Probe
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 5
# 健康检查端点
GET /health
Response: {"status": "ok", "model": "..."}
"""
def rate_limiting(self):
"""
速率限制
"""
return """
# 使用 nginx 限流
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /v1/completions {
limit_req zone=api burst=20;
}
}
"""
def autoscaling(self):
"""
自动扩缩容
"""
return """
# KEDA + 自定义指标
# 扩缩容规则
triggers:
- type: prometheus
metadata:
metricName: vllm_pending_requests
query: sum(vllm:num_requests_waiting)
threshold: "10"
"""

10.3 成本优化#

class CostOptimization:
"""
成本优化
"""
def instance_selection(self):
"""
实例选择
"""
return {
"7b_model": {
"min_gpu": "RTX 3090 24GB, A10G 24GB",
"recommended": "A10G or L4",
"price_per_hour": "$1.5-2.5",
},
"13b_model": {
"min_gpu": "A100 40GB, A6000 48GB",
"recommended": "A100 40GB",
"price_per_hour": "$3-4",
},
"70b_model": {
"min_gpu": "A100 80GB × 4",
"recommended": "A100 80GB × 4",
"price_per_hour": "$12-15",
},
}
def quantization_savings(self):
"""
量化节省
"""
return {
"fp16_70b": "140 GB GPU memory",
"int8_70b": "70 GB GPU memory",
"int4_70b": "35 GB GPU memory",
"fp16_to_int8": "50% memory reduction",
"int8_to_int4": "50% memory reduction",
"inference_savings": "可以用更少的 GPU",
}

11. 核心公式汇总#

11.1 KV Cache 内存#

MKV=2×L×Nkv×D×B×SM_{\text{KV}} = 2 \times L \times N_{\text{kv}} \times D \times B \times S

其中 LL 是层数,NkvN_{\text{kv}} 是 KV head 数,DD 是 head 维度,BB 是字节数,SS 是序列长度。

11.2 Attention 计算(FlashAttention)#

Attention(Q,K,V)i=j=1Nexp(qikjTd)vjj=1Nexp(qikjTd)\text{Attention}(\mathbf{Q}, \mathbf{K}, \mathbf{V})_i = \frac{\sum_{j=1}^{N} \exp\left(\frac{\mathbf{q}_i \mathbf{k}_j^T}{\sqrt{d}}\right) \mathbf{v}_j}{\sum_{j=1}^{N} \exp\left(\frac{\mathbf{q}_i \mathbf{k}_j^T}{\sqrt{d}}\right)}

FlashAttention 通过分块计算和在线 softmax 归一化,将内存复杂度从 O(N2)O(N^2) 降低到 O(N)O(N)

11.3 Batch 吞吐量#

Tbatch=B×SavgLatencyper_iterT_{\text{batch}} = \frac{B \times S_{\text{avg}}}{\text{Latency}_{\text{per\_iter}}}

其中 BB 是 batch size,SavgS_{\text{avg}} 是平均生成长度。


12. 总结#

12.1 vLLM 核心优势#

┌─────────────────────────────────────────────────────────────┐
│ vLLM 核心优势 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. PagedAttention │
│ → 消除 KV Cache 内存碎片 │
│ → 显存利用率提升 60% │
│ → 支持超长上下文 │
│ │
│ 2. Continuous Batching │
│ → GPU 利用率最大化 │
│ → 吞吐量提升 10-24x │
│ → P99 延迟显著降低 │
│ │
│ 3. Prefix Caching │
│ → 共享系统提示的计算 │
│ → 高并发场景效果显著 │
│ │
│ 4. 高效 CUDA Kernels │
│ → FlashAttention 集成 │
│ → CUDA Graphs 加速 │
│ → 多 GPU Tensor Parallelism │
│ │
└─────────────────────────────────────────────────────────────┘

12.2 选型指南#

vLLM vs 其他推理引擎:
vLLM:
✓ 最高吞吐量
✓ PagedAttention 内存优化
✓ OpenAI 兼容 API
✓ 活跃社区
✗ 仅 GPU
✗ 部分模型支持有限
Text Generation Inference (TGI):
✓ 优秀的长序列支持
✓ FlashDecoding 优化
✓ HuggingFace 深度集成
✗ 吞吐量略低
lmdeploy (TurboMind):
✓ 中国团队优化
✓ 高并发
✓ 量化优化好
选择建议:
→ 高吞吐场景: vLLM
→ 长序列场景: TGI
→ 中国生态: lmdeploy

12.3 性能对比#

性能对比(7B 模型,RTX 3090):
指标 HF Transformers vLLM
─────────────────────────────────────────────────
吞吐量 (tok/s) ~30 ~800
P50 latency (ms) ~150 ~50
P99 latency (ms) ~500 ~120
显存占用 (GB) ~20 ~12
Max batch size 4 64
─────────────────────────────────────────────────
vLLM 吞吐量提升: ~26x
显存节省: ~40%
推荐阅读
  1. PagedAttention Paper(Kwon et al., 2023)—— vLLM 核心论文
  2. FlashAttention Paper(Dao et al., 2022)—— 高效注意力实现
  3. vLLM Documentation—— 官方文档与教程
  4. Continuous Batching—— Orca 论文中的迭代级调度

参考资料#

  1. Kwon, W., et al. (2023). “Efficient Memory Management for Large Language Model Serving with PagedAttention.” SOSP.
  2. Dao, T., et al. (2022). “FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.” NeurIPS.
  3. Yu, G. I., et al. (2022). “Orca: A Distributed Serving System for Transformer-Based Generative Models.” OSDI.
  4. vLLM Team. “vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention.” GitHub.
  5. Chen, L., et al. (2023). “FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning.” ICLR.
  6. Shah, A., et al. (2023). “Efficiently Serving Large Language Models.” Blog.
  7. NVIDIA. “CUDA Graphs Documentation.” NVIDIA Developer.
  8. Huang, Y., et al. (2024). “Tensor Parallelism in Large Language Model Inference.” arXiv.

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

vLLM 深度解析:高效 LLM 推理引擎的架构与实践
https://aiattnstudio.link/posts/vllm/
作者
Federico
发布于
2026-07-16
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author

Federico

AI Research Lab

Hello, I'm Federico.

关于实验室 / About
公告

欢迎来到Federico的个人博客

分类
标签
站点统计
57文章
7分类
404标签