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 output2.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-5x5. 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 内存
其中 是层数, 是 KV head 数, 是 head 维度, 是字节数, 是序列长度。
11.2 Attention 计算(FlashAttention)
FlashAttention 通过分块计算和在线 softmax 归一化,将内存复杂度从 降低到 。
11.3 Batch 吞吐量
其中 是 batch size, 是平均生成长度。
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 → 中国生态: lmdeploy12.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%推荐阅读
- PagedAttention Paper(Kwon et al., 2023)—— vLLM 核心论文
- FlashAttention Paper(Dao et al., 2022)—— 高效注意力实现
- vLLM Documentation—— 官方文档与教程
- Continuous Batching—— Orca 论文中的迭代级调度
参考资料
- Kwon, W., et al. (2023). “Efficient Memory Management for Large Language Model Serving with PagedAttention.” SOSP.
- Dao, T., et al. (2022). “FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.” NeurIPS.
- Yu, G. I., et al. (2022). “Orca: A Distributed Serving System for Transformer-Based Generative Models.” OSDI.
- vLLM Team. “vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention.” GitHub.
- Chen, L., et al. (2023). “FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning.” ICLR.
- Shah, A., et al. (2023). “Efficiently Serving Large Language Models.” Blog.
- NVIDIA. “CUDA Graphs Documentation.” NVIDIA Developer.
- Huang, Y., et al. (2024). “Tensor Parallelism in Large Language Model Inference.” arXiv.
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
vLLM 深度解析:高效 LLM 推理引擎的架构与实践
https://aiattnstudio.link/posts/vllm/
