vLLM推理加速深度解读:PagedAttention如何提升10x吞吐量

vLLM推理加速深度解读:PagedAttention如何提升10x吞吐量

1. 引言

在大语言模型(Large Language Models, LLMs)快速发展的今天,推理效率已成为制约模型落地的关键瓶颈。GPT-4、LLaMA、Claude等模型虽然能力强大,但推理成本高昂、延迟较长,严重限制了商业化应用。统计显示,部署一个ChatGPT级别的服务,推理成本占据总运营成本的70%以上。

vLLM(Very Fast LLM Inference)是UC Berkeley提出的高性能LLM推理引擎,通过革命性的PagedAttention技术,将LLM推理吞吐量提升了2-24倍,同时大幅降低内存占用。这项技术已被广泛应用于OpenAI API、Anthropic Claude、阿里通义千问等主流AI服务。

vLLM的核心创新在于:将操作系统的虚拟内存管理思想应用到LLM推理的KV Cache管理中,解决了传统推理系统中内存碎片化和预分配浪费的问题。本文将深入剖析vLLM的技术原理、实现细节及最新进展。


2. 核心原理

2.1 LLM推理的内存瓶颈

在理解vLLM之前,我们需要先理解LLM推理过程中的关键挑战:KV Cache。

在Transformer架构中,自注意力机制计算公式为:

Attention(Q, K, V) = softmax\left(\frac{QK^T}{\sqrt{d_k}}\right)V

其中:

  • Q (Query):当前token的查询向量

  • K (Key):所有历史token的键向量

  • V (Value):所有历史token的值向量

  • d_k:键向量的维度

在自回归生成过程中,每生成一个新token,都需要访问之前所有token的Key和Value向量。为避免重复计算,系统会将历史的K、V向量缓存在GPU显存中,这就是KV Cache。

对于一个LLaMA-13B模型(40层、40个注意力头、每头维度128),单个token的KV Cache大小为:

KV\_Size = 2 \times n_{layers} \times n_{heads} \times d_{head} \times sizeof(fp16)

= 2 \times 40 \times 40 \times 128 \times 2\ bytes = 819.2\ KB

生成1024个token需要约800MB显存!对于批量推理(batch size=32),KV Cache占用可达25GB,远超模型参数本身(13B参数约26GB)。

2.2 传统推理系统的问题

问题1:内存预分配浪费

传统系统(如HuggingFace Transformers)采用静态预分配策略:为每个请求预留最大序列长度(如2048 tokens)的KV Cache空间。但实际生成长度往往远小于最大长度(平均约200-500 tokens),导致60-80%的显存被浪费。

问题2:内存碎片化

不同请求的生成长度各异,动态分配会产生大量内存碎片。当碎片累积到一定程度,即使总空闲显存充足,也无法分配连续的大块内存,导致Out-of-Memory错误。

问题3:动态Batching困难

传统系统难以高效实现Continuous Batching(连续批处理):当一个请求完成时,无法立即释放显存并加入新请求,导致GPU利用率低下。

2.3 PagedAttention:虚拟内存启发

vLLM的核心创新PagedAttention受操作系统虚拟内存管理启发:

虚拟内存的核心思想:

  • 将物理内存划分为固定大小的页(Page)

  • 程序使用虚拟地址,由页表映射到物理页

  • 支持非连续的物理内存分配

  • 按需分配,减少浪费

PagedAttention的设计:

paged-attention-algorithm.png
  1. KV Cache分页:将KV Cache划分为固定大小的Block(如16或32个token)

  2. 逻辑地址与物理地址分离:

    • 逻辑地址:请求视角的连续KV Cache地址

    • 物理地址:GPU显存中的实际存储位置

    • 通过Block Table(类似页表)建立映射

  3. 按需分配:请求生成时动态分配Block,避免预分配浪费

  4. 内存共享:不同请求可共享相同的Prompt Block(如系统提示词),节省显存

数学上,PagedAttention的注意力计算修改为:

Attention(Q, K_{paged}, V_{paged}) = softmax\left(\frac{Q \cdot K_{paged}^T}{\sqrt{d_k}}\right)V_{paged}

其中 K*{paged} 和 V*{paged} 不再是连续存储,而是通过Block Table索引:

K_{paged}[i] = K_{physical}[BlockTable[i \div B]][i \mod B]

  • B:Block大小(token数量)

  • BlockTable:逻辑Block到物理Block的映射表

  • K_{physical}:物理显存中的KV Cache

2.4 架构设计

vLLM采用分离式架构:

  • Scheduler(调度器):管理请求队列,决定哪些请求参与本次推理

  • Block Manager(块管理器):负责Block的分配、回收和共享

  • Worker(工作进程):执行实际的推理计算

  • KV Cache Manager:维护Block Table和物理显存

vllm-architecture-diagram.png

整体流程:

  1. 用户请求到达 → Scheduler加入队列

  2. Scheduler根据可用Block决定本轮batch

  3. Block Manager分配Block并更新Block Table

  4. Worker执行PagedAttention计算

  5. 生成完成 → 回收Block → 调度新请求


3. 技术演进

3.1 早期LLM推理系统

FasterTransformer (NVIDIA, 2021)

  • 首次针对LLM推理优化

  • 采用静态内存预分配

  • 支持Tensor并行和Pipeline并行

  • 问题:内存利用率低(<30%)

Orca (Microsoft, 2022)

  • 提出Iteration-level Batching

  • 允许不同请求有不同的生成长度

  • 仍存在内存碎片化问题

3.2 vLLM的诞生

vLLM (UC Berkeley, 2023.6) [Kwon et al., SOSP 2023]

论文:Efficient Memory Management for Large Language Model Serving with PagedAttention

核心贡献:

  1. 首次提出PagedAttention算法

  2. 实现KV Cache的非连续存储

  3. Block级别的内存管理

  4. 实验证明:吞吐量提升2-24倍

实验数据(LLaMA-13B, A100 GPU):

  • 内存利用率:从35%提升至90%

  • 吞吐量:2297 tokens/s(相比HF的221 tokens/s提升10.4倍)

  • 支持的并发请求数:从5个提升至50个

3.3 后续优化

vLLM v0.2 (2023.10)

  • 支持Prefix Caching:共享相同Prompt的KV Cache

  • 支持Chunked Prefill:将长Prompt分块处理

  • 引入Speculative Decoding:推测解码加速

vLLM v0.3 (2024.2)

  • 支持Multi-LoRA:同时服务多个LoRA适配器

  • 引入Automatic Prefix Caching (APC)

  • 支持FP8量化


4. 实现细节

4.1 Block管理器实现

Block数据结构:

class Block:
    def __init__(self, block_id: int, block_size: int):
        self.block_id = block_id        # 物理Block ID
        self.ref_count = 0               # 引用计数
        self.tokens = []                 # 存储的token IDs
        self.is_full = False             # 是否已满
        
class BlockTable:
    def __init__(self):
        self.logical_to_physical = {}   # 逻辑块 -> 物理块映射
        self.physical_blocks = []        # 所有物理块池

Block分配算法:

def allocate_block(request_id: int) -> Block:
    """为请求分配一个新Block"""
    # 1. 从空闲池中获取Block
    if free_blocks:
        block = free_blocks.pop()
    else:
        # 2. 显存不足,执行Eviction
        block = evict_least_recent_block()
    
    # 3. 更新Block Table
    logical_id = len(block_tables[request_id])
    block_tables[request_id][logical_id] = block.block_id
    
    # 4. 增加引用计数
    block.ref_count += 1
    return block

def share_block(src_request_id: int, dst_request_id: int, 
                logical_block_id: int):
    """共享Block(用于Prefix Caching)"""
    physical_id = block_tables[src_request_id][logical_block_id]
    block_tables[dst_request_id].append(physical_id)
    physical_blocks[physical_id].ref_count += 1

4.2 PagedAttention CUDA Kernel

核心计算挑战:K、V不再连续存储,需要在CUDA kernel中处理映射。

优化策略:

  1. Block-wise计算:按Block遍历,每个Block内仍是连续的

  2. Shared Memory缓存:将Block Table加载到Shared Memory

  3. Warp-level优化:一个Warp处理一个Query head

简化的CUDA伪代码:

__global__ void paged_attention_kernel(
    float* Q,                    // [num_heads, head_size]
    float* K_cache,              // [num_blocks, block_size, num_heads, head_size]
    float* V_cache,              // [num_blocks, block_size, num_heads, head_size]
    int* block_table,            // [max_blocks]
    float* output,               // [num_heads, head_size]
    int num_blocks,
    int block_size
) {
    int head_id = blockIdx.x;
    int thread_id = threadIdx.x;
    
    // 加载Block Table到Shared Memory
    __shared__ int block_table_shared[MAX_BLOCKS];
    if (thread_id < num_blocks) {
        block_table_shared[thread_id] = block_table[thread_id];
    }
    __syncthreads();
    
    float local_sum = 0.0;
    
    // 遍历所有Block
    for (int block_idx = 0; block_idx < num_blocks; block_idx++) {
        int physical_block_id = block_table_shared[block_idx];
        
        // 遍历Block内的token
        for (int token_idx = 0; token_idx < block_size; token_idx++) {
            // 计算注意力分数
            float score = 0.0;
            for (int i = thread_id; i < head_size; i += blockDim.x) {
                score += Q[head_id * head_size + i] *
                         K_cache[physical_block_id][token_idx][head_id][i];
            }
            // Warp reduction求和
            score = warp_reduce_sum(score);
            
            // 累加到输出
            local_sum += score * V_cache[physical_block_id][token_idx][head_id][thread_id];
        }
    }
    
    output[head_id * head_size + thread_id] = local_sum;
}

4.3 Continuous Batching实现

vLLM的一大亮点是请求级调度:每次forward后重新调度。

class Scheduler:
    def schedule(self) -> Batch:
        """每次调度决定本轮batch"""
        selected_requests = []
        total_blocks_needed = 0
        
        # 1. 优先调度正在生成的请求(避免饥饿)
        for req in running_requests:
            blocks_needed = 1 if req.last_block_is_full else 0
            if total_blocks_needed + blocks_needed <= free_blocks:
                selected_requests.append(req)
                total_blocks_needed += blocks_needed
        
        # 2. 尽可能添加等待队列中的请求
        for req in waiting_requests:
            # 估算Prefill需要的Block数
            prompt_blocks = ceil(req.prompt_length / BLOCK_SIZE)
            if total_blocks_needed + prompt_blocks <= free_blocks:
                selected_requests.append(req)
                total_blocks_needed += prompt_blocks
            else:
                break  # 显存不足,停止添加
        
        return Batch(selected_requests)

关键技术点:

  1. Prefill-Decode分离:Prefill(处理Prompt)和Decode(生成token)可在同一batch

  2. 动态Batch大小:根据显存实时调整,最大化吞吐

  3. Preemption机制:显存不足时可暂停低优先级请求

4.4 Prefix Caching优化

当多个请求共享相同Prefix(如系统提示词)时,可复用KV Cache。

实现原理:

  1. 计算Prefix的哈希值:hash(token_ids[:prefix_length])

  2. 维护Prefix Cache Table:hash -> List[physical_block_ids]

  3. 新请求到达时,查找匹配的Prefix,直接复用Block(仅增加引用计数)

收益分析:

  • 对于固定系统提示词(如100 tokens),可节省100/2048 ≈ 5%显存

  • 在对话场景中,历史对话可作为Prefix,节省更多


5. 应用场景

5.1 在线服务部署

典型场景:ChatGPT式对话服务

  • 高并发(数千QPS)

  • 不同请求长度差异大(10-2000 tokens)

  • 需要低延迟(<1秒首token)

vLLM优势:

  • Continuous Batching确保高吞吐

  • PagedAttention减少显存浪费,支持更多并发

  • Prefix Caching加速系统提示词处理

实际案例:

  • Anthropic Claude API:使用vLLM作为推理引擎

  • 阿里通义千问:基于vLLM定制的推理系统

  • Together.ai:提供vLLM托管服务

5.2 批量离线推理

场景:数据标注、内容生成

  • 需要处理数百万条数据

  • 允许一定延迟

  • 追求最大吞吐量

vLLM配置:

  • 增大Block Size(如64 tokens)减少索引开销

  • 禁用Prefix Caching(离线数据少重复)

  • 最大化Batch Size

5.3 与其他系统对比

系统 吞吐量 (tokens/s) 内存利用率 支持特性
HuggingFace Transformers 221 35% 基础推理
FasterTransformer 456 42% 张量并行
TensorRT-LLM 1124 58% 算子融合、INT8
vLLM 2297 90% PagedAttention、Continuous Batching
vLLM + Prefix Caching 3145 95% 共享Prefix、APC
vllm-performance-comparison.png

(测试条件:LLaMA-13B, A100 40GB, Batch Size = 32)

选型建议:

  • 简单推理、低并发:HuggingFace Transformers

  • 追求极致延迟、单请求:TensorRT-LLM

  • 高并发在线服务:vLLM

  • 对话场景、固定Prompt:vLLM + Prefix Caching


6. 最新研究

6.1 2024年重要进展

SGLang (2024.3) [Zheng et al., arXiv 2024]

  • 扩展vLLM,支持结构化生成(JSON Schema、正则表达式约束)

  • RadixAttention:基于Radix Tree的更高效Prefix Caching

  • 吞吐量再提升3-5倍

Splitwise (2024.5) [Patel et al., arXiv 2024]

  • Prefill和Decode分离到不同GPU

  • Prefill用高显存GPU(A100),Decode用多张低显存GPU(4090)

  • 成本降低40%

DistServe (2024.7) [Liu et al., OSDI 2024]

  • 跨机器的分布式PagedAttention

  • 支持万亿参数模型推理

  • Block在机器间迁移和共享

6.2 开源项目推荐

  1. vLLM官方仓库

  2. SGLang

  3. TensorRT-LLM + vLLM

    • NVIDIA官方支持vLLM backend

    • 结合TensorRT的算子优化和vLLM的内存管理

6.3 未来研究方向

方向1:异构硬件支持

  • 当前vLLM主要针对NVIDIA GPU

  • AMD GPU(ROCm)、华为昇腾、海光DCU适配

  • NPU/TPU的PagedAttention实现

方向2:长文本优化

  • 当前Block Size固定(16-32 tokens)

  • 长文本(100K+ tokens)下Block数量爆炸

  • 研究可变Block Size或层级Block结构

方向3:多模态扩展

  • 图像、音频的KV Cache管理

  • Vision Transformer的PagedAttention

  • 跨模态的Prefix Caching

方向4:端侧推理

  • 手机、IoT设备的内存更紧张

  • MNN、NCNN集成PagedAttention

  • Block在DRAM和Flash间调度


7. 总结

vLLM通过PagedAttention技术,将操作系统的虚拟内存管理思想创造性地应用到LLM推理中,解决了KV Cache管理的核心痛点。其核心创新包括:

  1. KV Cache分页存储:打破连续内存限制,消除碎片化

  2. 按需分配:避免预分配浪费,内存利用率从35%提升至90%

  3. Continuous Batching:请求级动态调度,吞吐量提升10倍

  4. Prefix Caching:共享相同Prompt,进一步节省显存

vLLM已成为LLM推理的事实标准,被OpenAI、Anthropic、阿里等顶级AI公司采用。对于AI工程师而言,深入理解vLLM的原理不仅有助于优化推理服务,更能启发我们:跨领域的技术迁移往往能带来革命性突破。

学习建议:

  1. 阅读vLLM论文(SOSP 2023),理解核心算法

  2. 实践:在自己的项目中部署vLLM,对比HuggingFace的性能差异

  3. 阅读源码:重点关注core/block_manager.py和CUDA kernel实现

  4. 关注最新进展:订阅vLLM GitHub仓库,跟踪SGLang、DistServe等项目

在LLM落地的道路上,推理优化是不可逾越的关键一环。掌握vLLM,就是掌握了LLM工程化的核心竞争力。


参考文献:

  1. Kwon et al., "Efficient Memory Management for Large Language Model Serving with PagedAttention", SOSP 2023

  2. Zheng et al., "SGLang: Efficient Execution of Structured Language Model Programs", arXiv 2024

  3. Patel et al., "Splitwise: Efficient Generative LLM Inference Using Phase Splitting", arXiv 2024

  4. Liu et al., "DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving", OSDI 2024

  5. Yu et al., "Orca: A Distributed Serving System for Transformer-Based Generative Models", OSDI 2022

  6. Vaswani et al., "Attention Is All You Need", NeurIPS 2017

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容