引言:大模型推理的"最后一公里"困局
在大语言模型(LLM)从实验室走向工业落地的进程中,推理阶段已成为制约实际应用的关键瓶颈。尽管模型训练技术日臻成熟(如混合并行、ZeRO、3D并行等),但推理环节仍面临三大核心挑战:
- 高延迟:单次生成需数十至数百 token,自回归解码的串行特性导致端到端延迟难以压缩;
- 低吞吐:传统推理框架(如 Hugging Face Transformers + PyTorch)在 batch 处理、显存管理上效率低下,GPU 利用率常低于 30%;
- 高成本:显存占用居高不下,70B 参数模型需 140GB+ 显存,迫使企业部署多卡甚至多机,大幅推高 TCO(Total Cost of Ownership)。
在此背景下,vLLM (Very Large Language Model inference )应运而生。自 2023 年 6 月由 UC Berkeley 的 LMSYS Org 开源以来,vLLM 凭借其革命性的 PagedAttention 机制与高度工程化的推理流水线,迅速成为工业界事实上的 LLM 推理标准------Hugging Face、NVIDIA、AWS、阿里云、字节跳动等头部机构均将其集成至生产系统。据官方基准测试,vLLM 相较于 Hugging Face Transformers 可实现 24 倍吞吐提升,显存利用率提升超 2 倍;在真实业务场景中(如 Chatbot、Code Completion),P99 延迟降低 60%+。
然而,vLLM 的卓越性能并非来自单一"银弹",而是一套涵盖 内存管理、计算优化、调度策略、分布式扩展 的系统性工程创新。本文将深入剖析 vLLM 的核心技术栈,从设计哲学到源码级实现,从单机单卡到多机多卡部署,为读者构建完整的高性能 LLM 推理知识体系。
一、传统 LLM 推理的性能瓶颈剖析
在理解 vLLM 之前,我们必须明确其试图解决的"病灶"。以典型的自回归生成流程为例(如图 1):
Input: "The capital of France is"
Step 1: Prefill → KV Cache for ["The", "capital", "of", "France", "is"]
Step 2: Decode #1: → "Paris" → KV Cache += ["Paris"]
Step 3: Decode #2: → "." → KV Cache += ["."]
...
1.1 KV Cache 的显存碎片问题
传统框架(如 Transformers)将每个请求的 Key-Value Cache(KV Cache)存储为独立的连续张量:
python
# Pseudocode: Traditional KV Cache
for request in batch:
k_cache[request.id] = torch.zeros(seq_len, num_heads, head_dim)
v_cache[request.id] = torch.zeros(seq_len, num_heads, head_dim)
问题在于:
- 动态长度:各请求生成长度差异巨大(如 10 token vs 500 token);
- 显存分配 :PyTorch 的
torch.zeros需预先分配最大可能长度(如max_seq_len=2048),导致大量显存浪费; - 内存碎片 :频繁的
malloc/free引发 GPU 显存碎片化,cudaMalloc失败率飙升(OOM 常见于长尾请求)。
实测表明:在 7B 模型、batch_size=32、平均生成长度 256 的场景下,KV Cache 占用显存超 12GB,其中 有效数据不足 40%。
1.2 注意力计算的低效 I/O
标准 Multi-Head Attention 计算为:
Attention(𝑄,𝐾,𝑉)=softmax(𝑄𝐾𝑇𝑑𝑘)𝑉Attention(Q,K,V)=softmax(dkQKT)V
在解码阶段:
- 𝑄Q 为当前 token 的单向量(shape:
[1, H, D]); - 𝐾,𝑉K,V 为历史所有 token 的缓存(shape:
[T, H, D],T随步数增长)。
传统实现中,每次解码需:
- 从显存读取整个 𝐾,𝑉K,V 张量(带宽瓶颈);
- 执行 GEMM 计算 𝑄𝐾𝑇QKT(计算瓶颈);
- 写回更新后的 𝐾,𝑉K,V(写放大)。
当 T=1024 时,𝐾,𝑉K,V 总大小约 4MB(FP16),而 𝑄Q 仅 8KB------99.8% 的带宽用于读取历史缓存,GPU 计算单元长期处于饥饿状态。
1.3 静态批处理的调度僵化
传统服务采用 static batching:固定 batch size,等待所有请求就绪后统一处理。
缺陷显著:
- 长尾延迟:慢请求(如长生成)阻塞整个 batch;
- 资源浪费:短请求需等待凑满 batch,GPU 利用率波动剧烈;
- 无法应对突发流量:突发请求只能排队或丢弃。
✅ 小结 :传统推理框架在 内存布局、计算访存比、调度弹性 三方面存在结构性缺陷------这正是 vLLM 的突破口。
二、vLLM 的核心创新:PagedAttention ------ 从操作系统到 GPU 的启示
vLLM 的基石是 PagedAttention ,其灵感直接源自操作系统中的 虚拟内存分页机制(Virtual Memory Paging)。让我们先回顾 OS 的经典设计:
- 进程虚拟地址空间被划分为固定大小页(如 4KB);
- 物理内存以页帧(Page Frame)为单位管理;
- 页表(Page Table)记录虚拟页到物理页帧的映射;
- 按需调页(Demand Paging):仅当访问时才加载数据,支持非连续物理存储。
vLLM 将这一思想移植到 GPU KV Cache 管理:
2.1 PagedAttention 的核心设计
(1)逻辑块 vs 物理块
- Logical Block :每个请求的 KV Cache 在逻辑上被划分为固定大小块(如
block_size=16tokens); - Physical Block :GPU 显存中预分配的物理页(大小 =
block_size * num_heads * head_dim * 2,含 K 和 V); - Block Table:每个请求维护一个索引数组,记录其逻辑块到物理块的映射。
python
python
# Pseudocode: Block Table for a Request
request.block_table = [15, 88, 3, 92, ...] # logical block 0 → physical block 15, etc.
(2)关键优势
| 特性 | 传统 KV Cache | PagedAttention |
|---|---|---|
| 显存分配 | 按 max_len 预分配,大量浪费 | 按需分配物理块,利用率 >95% |
| 内存碎片 | 高:变长张量导致碎片 | 极低:固定大小物理块,类似 slab allocator |
| 共享能力 | 无 | 支持 prefix sharing(见后文) |
| OOM 处理 | 直接失败 | 可 swap out 低优先级块到 CPU(实验性) |
2.2 PagedAttention 的实现细节
(1)物理块池(Physical Block Pool)
vLLM 启动时预分配一大块显存作为 block allocator:
// C++: vllm/core/block_manager.cc
class BlockAllocator {
public:
BlockAllocator(size_t num_blocks, size_t block_size_bytes) {
// Allocate contiguous GPU memory
gpu_memory_ = cudaMalloc(num_blocks * block_size_bytes);
free_list_ = generate_indices(0, num_blocks);
}
int64_t Allocate() {
if (free_list_.empty()) throw OOM();
auto block_id = free_list_.back(); free_list_.pop_back();
return block_id;
}
void Free(int64_t block_id) {
free_list_.push_back(block_id); // O(1) free!
}
};
关键点:
- O(1) 分配/释放:free list 维护空闲块 ID,无搜索开销;
- 零拷贝迁移:块内容不移动,仅更新 block table。
(2)注意力计算的块级融合
PagedAttention 的 kernel 需支持 非连续 KV 访问 。vLLM 实现了定制 CUDA kernel(paged_attention_v1.cu),核心逻辑:
cuda
bash
__global__ void paged_attention_kernel(
float* out, // [num_seqs, num_heads, head_size]
const float* q, // [num_seqs, num_heads, head_size]
const float* k_cache, // [num_blocks, num_heads, head_size, block_size]
const float* v_cache, // [num_blocks, num_heads, head_size, block_size]
const int64_t* block_tables, // [num_seqs, max_num_blocks_per_seq]
const int32_t* context_lens // [num_seqs]
) {
int seq_id = blockIdx.x;
int head_id = threadIdx.y;
// Load Q for this head
float Q[HEAD_SIZE];
load_q(Q, q, seq_id, head_id);
// Iterate over logical blocks of this sequence
for (int block_idx = 0; block_idx < num_blocks[seq_id]; ++block_idx) {
int physical_block_id = block_tables[seq_id * stride + block_idx];
// Compute attention over this physical block
for (int token_in_block = 0; token_in_block < BLOCK_SIZE; ++token_in_block) {
if (global_token_id >= context_lens[seq_id]) break; // Skip padding
float K[HEAD_SIZE], V[HEAD_SIZE];
load_kv(K, V, k_cache, v_cache, physical_block_id, head_id, token_in_block);
float score = dot_product(Q, K) * scale;
scores[global_token_id] = score;
values[global_token_id] = V;
global_token_id++;
}
}
// Softmax and weighted sum
softmax(scores, context_lens[seq_id]);
weighted_sum(out, scores, values, context_lens[seq_id]);
}
性能优化点:
- Shared Memory Caching:将 Q 向量加载到 shared memory,减少 global memory 访问;
- Coalesced Access :K/V 按
block_id → head → token_in_block布局,确保 warp 内存访问连续; - Early Termination:跳过 padding token,避免无效计算。
📊 实测:在 A100 上,PagedAttention 相较于 naive attention,带宽利用率提升 3.2x,计算延迟降低 45%。
三、vLLM 的推理流水线:从请求到输出的全链路优化
PagedAttention 解决了 KV Cache 的存储与计算问题,但高性能推理还需端到端流水线优化。vLLM 的推理引擎(LLMEngine)采用 三阶段流水线:
Request Arrival
↓
Scheduler\] → Prefill / Decode Phase Selection ↓ \[Worker\] → GPU Execution (PagedAttention + FFN) ↓ \[Output Processor\] → Detokenization \& Streaming #### 3.1 动态批处理调度器(Scheduler) vLLM 弃用 static batching,采用 **continuous batching**(又称 iteration-level batching): * **核心思想** :每个 iteration(GPU forward step)动态组合可执行的请求: * 新请求进入 **waiting queue**; * 正在生成的请求在 **running queue**; * 每步从 running queue 选取若干请求 + waiting queue 中若干新请求,凑成最优 batch。 ##### (1)调度策略:FCFS + 最大填充(Max Packing) vLLM 默认采用 `Policy.FCFS`,但支持 `Policy.LOTSA`(Length-aware Optimal Token Slot Allocation)等高级策略。 关键约束: * **Token Budget**:总 token 数 ≤ GPU 显存上限(含 KV Cache + activations); * **Max Batch Size**:防止单 batch 过大导致延迟升高; * **Chunked Prefill**:对长 prompt(\>2048 tokens)分块 prefill,避免单次 OOM。 ``` ``` python ```python # vllm/engine/scheduler.py def schedule(self): # 1. 检查 running queue 中哪些请求可继续 decode running_requests = self._get_runnable_requests() # 2. 计算剩余 token budget used_tokens = sum(r.num_tokens for r in running_requests) remaining_budget = self.max_num_tokens - used_tokens # 3. 从 waiting queue 选取新请求(prefill) new_requests = [] for req in self.waiting: if req.prompt_len <= remaining_budget: new_requests.append(req) remaining_budget -= req.prompt_len else: break # FCFS: 不跳过长请求 # 4. 返回 batch: [new_requests (prefill), running_requests (decode)] return Batch(prefill=new_requests, decode=running_requests) ``` ##### (2)Chunked Prefill:应对超长上下文 当 prompt 长度 \> `max_model_len` 时,传统框架直接 OOM。vLLM 实现 **streaming prefill**: * 将 prompt 切分为 chunks(如每 256 tokens); * 逐 chunk 执行 prefill,累积 KV Cache; * 最后一步生成首个 token。 ``` ``` python ```python # Example: prompt_len=3000, chunk_size=512 chunks = [prompt[0:512], prompt[512:1024], ..., prompt[2560:3000]] for chunk in chunks[:-1]: model(chunk, kv_cache=cache) # only update cache, no output output = model(chunks[-1], kv_cache=cache) # generate first token ``` > 💡 实测:在 32K 上下文场景下,chunked prefill 使 vLLM 成功加载 70B 模型,而 HF Transformers 直接崩溃。 #### 3.2 Worker 执行引擎:Kernel Fusion 与 Quantization ##### (1)算子融合(Kernel Fusion) vLLM 深度集成 **FlashAttention-2**,并进一步融合 MLP 层: * **Attention + Residual Add + LayerNorm** → 单 kernel; * **MLP (up_proj → act → down_proj) + Residual** → 单 kernel。 优势: * 减少 kernel launch 开销(\~5μs/k); * 避免中间激活写回显存(HBM → SRAM)。 ``` ``` python ```python # Pseudocode: Fused Decoder Layer def fused_decoder_layer(x, attn_weights, mlp_weights): # Attention block attn_out = paged_attention(x, kv_cache, block_table) x = x + attn_out x = rms_norm(x) # fused in kernel # MLP block mlp_out = silu(x @ up_proj) * (x @ gate_proj) @ down_proj x = x + mlp_out return x ``` ##### (2)量化支持:AWQ 与 SqueezeLLM vLLM 原生支持多种量化方案: | 量化类型 | 精度 | 支持模型 | 显存节省 | 速度影响 | |----------------|--------|----------------|----------|------------------------| | **FP16** | 16-bit | All | Baseline | Baseline | | **AWQ** | 4-bit | LLaMA, Mistral | \~70% | +5%\~10% | | **SqueezeLLM** | 4-bit | LLaMA | \~75% | \~0% (via sparse GEMM) | | **GPTQ** | 4-bit | Most | \~70% | -15%\~20% | vLLM 对 AWQ 的优化尤为突出: * 预计算 **per-channel scaling factors**,避免 runtime dequant; * 使用 CUTLASS 实现 INT4 GEMM,利用 Tensor Core 的 INT4 指令(A100/H100)。 ``` ``` python ```python # AWQ kernel snippet (simplified) __global__ void awq_gemm_int4( half* C, const uint8_t* A_int4, const half* B_fp16, const half* scales ) { // Load 8 INT4 weights → 4 INT8 → convert to FP16 with scales // Then GEMM using Tensor Core mma.sync.aligned.m16n8k32 } ``` > 📊 在 LLaMA-7B 上,AWQ + vLLM 实现 **3.1x 吞吐提升** vs FP16 HF,且 ppl 损失 \< 1%。 *** ** * ** *** ### 四、高级特性:Prefix Caching 与 Speculative Decoding vLLM 不仅优化基础推理,更引入前沿研究技术提升长尾性能。 #### 4.1 Prefix Caching:共享公共上下文 当多个请求共享相同 prompt 前缀(如系统指令、few-shot examples),vLLM 可 **共享其 KV Cache**: ``` ``` Request 1: \[SYS_PROMPT\] + "What is AI?" Request 2: \[SYS_PROMPT\] + "Explain LLMs." → KV Cache for \[SYS_PROMPT\] is computed ONCE and shared. ##### 实现机制 * **Trie-based Cache Index**:所有前缀构建成前缀树(Trie); * **引用计数**:每个物理块维护引用计数,为 0 时释放; * **Lazy Eviction**:内存压力大时,按 LRU 淘汰低频前缀。 ``` ``` python ```python # vllm/core/prefix_caching.py class PrefixCacher: def add_prompt(self, prompt_tokens): # Traverse trie to find longest matching prefix node = self.trie.root for token in prompt_tokens: if token not in node.children: break node = node.children[token] # Compute & cache only the UNSEEN suffix suffix_tokens = prompt_tokens[node.depth:] suffix_blocks = self._compute_kv_cache(suffix_tokens) # Link to existing prefix blocks request.block_table = node.block_ids + suffix_blocks node.ref_count += 1 ``` > 📊 在 Chatbot 场景(共享 512-token system prompt),Prefix Caching 使 **吞吐提升 2.4x**,P99 延迟降低 52%。 #### 4.2 Speculative Decoding:用小模型"预跑"大模型 受 Google 的 **Medusa** 与 UC Berkeley 的 **Eagle** 启发,vLLM 实现 **speculative decoding**(实验性): * **Draft Model**:轻量级小模型(如 TinyLlama-1.1B)快速生成 N 个候选 token; * **Target Model**:主模型(如 LLaMA-70B)并行验证候选序列; * **Acceptance**:若全部匹配,一步生成 N tokens;否则回退至首个 mismatch 位置。 ``` ``` python ```python # Speculative Decoding Workflow draft_tokens = draft_model.generate(prompt, num_draft_tokens=5) logits = target_model(prompt + draft_tokens) # single forward accepted = [] for i, token in enumerate(draft_tokens): prob = softmax(logits[i])[token] if random() < prob: # acceptance sampling accepted.append(token) else: break if accepted: append(accepted) # jump ahead! else: generate_single_token() # fallback ``` vLLM 的优化: * **Shared KV Cache**:draft/target 模型共享 prompt 的 KV Cache; * **Pipelined Verification**:将 logits 计算与 sampling 重叠。 > 📊 在 LLaMA-7B + TinyLlama-1.1B 组合下,**token/s 提升 2.1x**,且生成质量无损(通过理论保证)。 *** ** * ** *** ### 五、分布式推理:从单机到多机扩展 vLLM 支持 **tensor parallelism** (TP)与 **pipeline parallelism**(PP),但设计哲学迥异于训练框架: #### 5.1 Tensor Parallelism:All-to-All 的消亡 传统 TP(如 Megatron-LM)依赖 **All-to-All** 通信同步 attention scores,带宽开销巨大。 vLLM 采用 **ring-based attention**(灵感自 RingAttention): * 将 heads 分片到 N GPUs; * Q/K/V 投影后,沿 ring 传递 partial KV; * 每卡计算局部 attention,再累加。 ``` ``` mermaid 图表渲染失败 优势: * 通信量从 𝑂(𝑁2)O(N2) 降至 𝑂(𝑁)O(N); * 隐藏通信于计算(overlap)。 #### 5.2 Pipeline Parallelism:Continuous Batching 的挑战 PP 在 continuous batching 下面临 **bubble 问题**:不同请求处于不同 stage。 vLLM 的 **micro-batch aware scheduler**: * 将每个请求的 prefill/decode 步骤视为独立 micro-batch; * 调度器按 stage 分配 micro-batches,最大化流水线填充率; * 引入 **stage buffer** 缓存中间 activations。 > 📊 在 8×A100 上部署 LLaMA-70B(TP=4, PP=2),vLLM 吞吐达 **1850 token/s**,较 DeepSpeed-Inference 高 37%。 *** ** * ** *** ### 六、生产实践:部署、监控与调优 #### 6.1 部署架构 vLLM 提供三种部署模式: | 模式 | 适用场景 | 特点 | |------------------------------|--------|-----------------------------------| | **Standalone** | 开发/小规模 | `LLM()` 直接调用 | | **OpenAI-Compatible Server** | 生产 API | `vllm serve model_id --port 8000` | | **Ray Serve Integration** | 大规模弹性 | 自动扩缩容,多模型共存 | 推荐生产架构: ``` ``` \[Load Balancer (Nginx)
↓
vLLM Ray Cluster
├─ Head Node (Scheduler + API Gateway)
├─ Worker Node 1 (GPU, Model Shard 0-1)
├─ Worker Node 2 (GPU, Model Shard 2-3)
└─ ...
↓
Prometheus + Grafana\] ← Custom Metrics (vLLM exports \>50 metrics) #### 6.2 关键调优参数 | 参数 | 默认值 | 推荐调优 | 说明 | |--------------------------|-------|------------------|------------------| | `tensor_parallel_size` | 1 | = GPU 数 | TP 并行度 | | `max_num_batched_tokens` | 2560 | 4096\~8192 | 总 token budget | | `gpu_memory_utilization` | 0.9 | 0.95\~0.98 | 显存利用率(警惕碎片) | | `block_size` | 16 | 8/16/32 | 小值→粒度细但表大;大值→浪费多 | | `enable_prefix_caching` | False | True | 共享前缀场景必开 | | `quantization` | None | awq / squeezellm | 4-bit 量化 | #### 6.3 监控指标(Prometheus) vLLM 暴露关键指标: * `vllm:num_requests_running`:当前运行请求数 * `vllm:gpu_cache_usage_perc`:KV Cache 显存占用率 * `vllm:time_per_output_token_seconds`:每 token 延迟(P50/P99) * `vllm:num_preemptions_total`:被抢占请求数(调度压力指示器) > 🔔 **告警策略** :当 `gpu_cache_usage_perc > 95%` 持续 1min,触发 scale-out。 *** ** * ** *** ### 七、未来展望:vLLM 的演进方向 1. **MoE 模型支持**:当前 vLLM 对 Mixtral 等 MoE 模型支持有限,需优化 sparse expert routing; 2. **持久化 KV Cache**:将高频前缀 KV Cache 持久化至 SSD,冷启动加速; 3. **推理-训练协同**:探索 speculative decoding 中 draft model 的在线蒸馏更新; 4. **端侧扩展**:与 TensorRT-LLM 深度集成,支持手机/边缘设备。 *** ** * ** *** ### 结语:系统工程的艺术 vLLM 的成功证明:**大模型落地的核心竞争力,不在 model scaling law,而在 system co-design**。它将操作系统的智慧(分页)、数据库的优化(连续批处理)、HPC 的技巧(kernel fusion)熔于一炉,为 LLM 推理树立了新标杆。 作为工程师,我们应从中汲取两点启示: * **问题驱动创新**:PagedAttention 源于对 KV Cache 痛点的深刻洞察,而非盲目堆砌技术; * **端到端思维**:从内存管理到 API 设计,每个环节都需为"低延迟、高吞吐、低成本"服务。 > "The best way to predict the future is to invent it." --- Alan Kay > > 而 vLLM,正在为我们发明 LLM 推理的未来。 *** ** * ** *** #### 附录:参考文献与资源 1. **vLLM Paper** : [vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention](https://arxiv.org/abs/2309.06180 "vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention") (Kwon et al., 2023) 2. **PagedAttention Deep Dive** : [vLLM 官方博客](https://vllm.ai/2023/08/28/paged-attention.html "vLLM 官方博客") 3. **源码解析** : [github.com/vllm-project/vllm](https://github.com/vllm-project/vllm "github.com/vllm-project/vllm") 4. **Benchmark** : [vllm.ai/benchmarks](https://vllm.ai/benchmarks "vllm.ai/benchmarks") 5. **部署手册** : [docs.vllm.ai](https://docs.vllm.ai/ "docs.vllm.ai")