编程 LLM 推理的显存战争:从 PagedAttention 到统一内存架构——KV Cache 管理五世代深度解析(2026)

2026-06-15 11:18:23 +0800 CST views 52

LLM 推理的显存战争:从 PagedAttention 到统一内存架构——KV Cache 管理五世代深度解析(2026)

部署大模型时,权重只是问题的一半。另一半,是它的"工作记忆"。

每个在生产环境跑过 LLM 的人都明白这个道理:花几十万元买了 A100/H100,模型权重加载进显存之后,却发现能跑的并发数少得可怜——不是因为算力不够,而是因为KV Cache 吃光了显存。一次 8K 上下文的请求,它的 KV Cache 体积可能比模型本身还大。

这篇文章系统梳理 KV Cache 管理从 2017 年到 2026 年的五个演进阶段,结合 vLLM、SGLang、TensorRT-LLM 三大主流推理框架的架构差异,给出一套完整的判断框架:在不同场景下,如何选择正确的 KV Cache 管理策略


一、为什么 KV Cache 是真正的瓶颈

在深入技术细节之前,先搞清楚 KV Cache 到底是个什么东西,以及它为什么会成为 LLM 推理的核心瓶颈。

1.1 Prefill 与 Decode:两种截然不同的计算模式

LLM 推理分为两个阶段:

Prefill 阶段:一次性处理全部输入 token,并行计算所有 Key 和 Value 向量。这是计算密集型操作,GPU 并行度越高越好。

Decode 阶段:自回归生成,每个新 token 都要对整个序列(包括历史 token)的 Key-Value 做注意力计算。此时 GPU 大部分时间花在从 HBM 读取 KV Cache 上,瓶颈转移到了内存带宽而非算力。

没有 KV Cache,每次生成新 token 都得把整个注意力计算从头重跑一遍——输入 1000 个 token,生成 500 个 output,就是 1000 + 999 + 998 + ... + 501 = 37.5 万次注意力运算,而不是 500 次。有了 KV Cache,每个 decode 步骤只做一次注意力计算,代价是把已算出的 Key-Value 存起来。

1.2 显存占用有多夸张

以 Llama-3–70B、8K 上下文为例,来算一笔账:

KV Cache per token = 2 (K+V) × 80 layers × 8 KV heads × 128 head_dim × 2 bytes (FP16)
                   = 2 × 80 × 8 × 128 × 2
                   = 327,680 bytes ≈ 320 KB per token

For 8K tokens: 320 KB × 8,192 = 2.56 GB per request
For 32 concurrent requests: 2.56 GB × 32 = 81.9 GB

81.9 GB:一整块 A100 80GB 的显存,在 32 个并发请求下连 KV Cache 都装不下,更别说模型权重了。

这就是为什么 KV Cache 管理是 LLM 推理的核心工程问题。理解了这一点,才能理解接下来五个世代演进背后的驱动力。


二、Era 0 → Era 1:连续分配时代(2017 年)

2.1 Pre-GenAI:注意力根本不需要缓存

在 Transformer 出现之前(2017 年以前),深度学习主流是无状态前馈网络:ResNet、YOLO、VGG、Inception。每次推理独立处理输入,步骤之间没有持久状态,KV Cache 的概念根本不存在。

2.2 连续分配:最简单的起点

Transformer 论文(2017)带来了自注意力,也带来了 KV Cache 的需求。早期推理引擎(如原始 HuggingFace Transformers)用最简单的方式实现:

# 连续 KV Cache 的本质:为每个请求预分配 max_seq_len 大小的连续张量
class ContinuousKVCache:
    def __init__(self, num_layers, num_heads, head_dim, max_seq_len):
        # 为每个请求一次性分配一大块
        self.k_cache = torch.zeros(
            num_layers, max_seq_len, num_heads, head_dim
        )
        self.v_cache = torch.zeros(
            num_layers, max_seq_len, num_heads, head_dim
        )
        self.position = 0

    def update(self, new_k, new_v):
        """写入新的 token"""
        start = self.position
        end = start + new_k.shape[1]
        self.k_cache[:, start:end] = new_k
        self.v_cache[:, start:end] = new_v
        self.position = end
        return start  # 返回写入位置

好处:实现简单,相比每个 decode 步骤重算注意力,速度提升巨大。

坏处:内存占用按 max_seq_len × batch_size 线性增长,而非跟随实际序列长度。大多数请求远短于最大长度,内部碎片严重。

实测数据令人震惊:在连续分配系统中,已分配的 KV Cache 内存只有 20%–38% 真正存储了有用的 token 状态,其余全部浪费在填充和碎片上。

2.3 连续分配的致命问题

# 问题1:碎片化
# max_seq_len=32768,但 99% 的请求实际只用到 1024 tokens
# 剩余 31584 个位置全是零填充——浪费 96%

# 问题2:并发受限
# 模型权重 70B (FP16) = 140 GB → 需要 2× A100 80GB
# KV Cache 80GB / 2.56 GB = 最多 31 个并发请求(8K上下文)
# 实际上因为碎片,并发数往往更低

# 问题3:不同请求之间无法共享任何内存
# 两个请求恰好用了相同的 system prompt
# 也要各自计算并存储一套 KV Cache

这直接推动了 PagedAttention 的诞生。


三、Era 2:分页注意力——规则改变者(2023 年)

3.1 PagedAttention 的核心思想

2023 年,UC Berkeley 的 vLLM 团队从操作系统借来一个经典思路:带分页的虚拟内存

核心做法:将 KV Cache 切分为固定大小的页(block),随序列增长按需分配,而非一次性为每个请求开辟一大块连续内存。一个 block table 将逻辑页映射到物理内存——和操作系统页表将虚拟地址映射到物理 RAM 的原理完全一致。

逻辑视角(每个请求看到的):
[block_0][block_1][block_2][...]
    ↓        ↓        ↓
物理视角(实际 GPU 显存布局):
[physical_3][physical_7][physical_1]  ← 可能物理上不连续

3.2 vLLM PagedAttention 实现原理

# vLLM block manager 核心逻辑
class BlockTable:
    """
    逻辑块表:记录每个逻辑 block 到物理块的映射
    这是 PagedAttention 的核心数据结构
    """
    def __init__(self, block_size: int = 16):
        self.block_size = block_size
        # 逻辑块索引 → 物理块索引
        self.block_mapping: dict[int, int] = {}
        # 物理块引用计数(用于引用计数式 GC)
        self.ref_count: dict[int, int] = {}
        # 空闲物理块队列
        self.free_blocks: Queue = Queue()

    def allocate(self, num_tokens: int) -> list[int]:
        """按需分配物理块,返回物理块 ID 列表"""
        num_blocks_needed = ceil(num_tokens / self.block_size)
        physical_blocks = []
        for _ in range(num_blocks_needed):
            if self.free_blocks.empty():
                raise OutOfMemoryError("No free GPU blocks")
            block_id = self.free_blocks.get()
            self.ref_count[block_id] = 1
            physical_blocks.append(block_id)
        return physical_blocks

    def append(self, block_id: int):
        """追加一个新物理块到映射"""
        logical_block_id = len(self.block_mapping)
        self.block_mapping[logical_block_id] = block_id
        self.ref_count[block_id] = 1

3.3 PagedAttention Kernel 的实现

// PagedAttention CUDA kernel 核心逻辑
// 每个 token 一个 thread block
template<typename scalar_t, int BLOCK_SIZE>
__global__ void paged_attention_kernel(
    const scalar_t* __restrict__ k,      // 当前 token 的 K 向量
    const scalar_t* __restrict__ q,      // Query 向量
    const scalar_t* __restrict__ v,      // 当前 token 的 V 向量
    const int* __restrict__ block_tables, // 逻辑→物理块映射表
    const int num_blocks_per_seq,        // 每个序列的 block 数
    const float scale,
    scalar_t* __restrict__ output
) {
    // 关键优化:不是顺序遍历所有历史 token
    // 而是按物理块遍历,只加载真正有数据的块
    for (int block_idx = 0; block_idx < num_blocks_per_seq; block_idx++) {
        int physical_block_id = block_tables[block_idx];
        
        // 加载整个物理块(16 或 32 个 token 的 K/V)
        __shared__ float k_block[BLOCK_SIZE][HEAD_DIM];
        __shared__ float v_block[BLOCK_SIZE][HEAD_DIM];
        
        load_block(k_block, physical_block_id);
        load_block(v_block, physical_block_id);
        
        // 对块内所有 token 计算注意力
        for (int i = 0; i < BLOCK_SIZE; i++) {
            float qk = scale * dot(q, k_block[i]);
            float attn = expf(qk - max_qk);  // online softmax
            acc += attn * v_block[i];
            // 在线 softmax,避免 exp 溢出
        }
    }
}

3.4 震惊业界的性能数据

vLLM 论文给出的数字改变了行业:

指标连续分配PagedAttention
碎片率60%–80%< 4%
内存利用率20%–38%> 96%
并发请求数几十个数百至数千
吞吐量提升baseline2–4x

3.5 前缀缓存:PagedAttention 的隐藏宝藏

PagedAttention 还打开了前缀缓存的大门。SGLang 的 RadixAttention 正是基于此。

多个请求如果共享同一前缀(系统提示词、共享文档、RAG 上下文),对应的 KV Cache 物理块可以直接复用,无需重新计算。这对多轮对话和 RAG 场景来说是巨大的吞吐量倍增器。

# SGLang RadixAttention:基数树管理 KV Cache 复用
# 共享相同前缀的请求在基数树中共享节点
from sglang import model_interface, sched

class RadixAttention:
    """
    用基数树(Radix Tree)管理所有请求的 KV Cache
    相同前缀的请求复用同一个树节点
    """
    def __init__(self):
        self.root = RadixNode()
        self.lru_counter = 0
        
    def cache_fan(self, req_prefix: str, kv_cache: torch.Tensor) -> Node:
        """缓存一个请求的前缀 KV"""
        # 在基数树中查找或创建节点
        node = self.root
        for char in req_prefix:
            if char not in node.children:
                node.children[char] = RadixNode(parent=node)
            node = node.children[char]
        
        node.kv_cache = kv_cache
        node.lru_counter = self.lru_counter
        self.lru_counter += 1
        return node
    
    def find_cache(self, req_prefix: str) -> Optional[torch.Tensor]:
        """查找已缓存的前缀 KV(O(前缀长度) 查找)"""
        node = self.root
        for char in req_prefix:
            if char not in node.children:
                return None
            node = node.children[char]
        return node.kv_cache if node.kv_cache is not None else None

3.6 vLLM vs SGLang:两个框架的取舍

两个框架都支持前缀缓存,但实现路径不同:

特性vLLMSGLang
前缀缓存实现block 级别哈希匹配RadixAttention 基数树
缓存复用策略静态前缀匹配跨多次生成调用自动复用
复杂多调用场景(Agent、思维链)一般更优
简单聊天场景更优良好
架构复杂度较低较高

四、Era 3:异构 KV Cache——多模态时代的挑战(2024 年)

4.1 为什么异构缓存成为必须

2024 年模型架构快速分化,单一类型的 KV Cache 管理已无法满足需求:

异构缓存需求的具体来源:

# 场景1:投机解码
class SpeculativeDecoding:
    """
    投机解码:小型草稿模型一次提出多个候选 token
    大型目标模型批量验证
    每个模型维护独立的 KV Cache
    """
    draft_kv = KVCache(...)   # 草稿模型 KV Cache
    target_kv = KVCache(...)  # 目标模型 KV Cache
    # 两者形状、大小完全不同

# 场景2:视觉语言模型(VLM)
class VisionLanguageModel:
    """
    QwenVL / InternVL 等多模态模型
    图像 token 和文本 token 的 KV 形状完全不同
    """
    image_kv = KVCache(shape=[num_image_tokens, hidden])   # 图像嵌入 KV
    text_kv  = KVCache(shape=[num_text_tokens,  hidden])   # 文本 token KV
    # 图像 KV 可能长达 4096 tokens(高分辨率图),而文本只有几百

# 场景3:滑动窗口注意力(SWA)
class SlidingWindowAttention:
    """
    Gemma 2/3、Ministral 等模型只关注最近 window_size 个 token
    超出窗口的历史 KV 需要淘汰策略
    """
    active_kv  = KVCache(...)  # 窗口内的活跃 KV
    evicted_kv = KVCache(...)  # 已淘汰但可能回溯使用的 KV

# 场景4:混合架构模型
class HybridArchitectureModel:
    """
    同一模型中组合多种层类型:
    - 滑动窗口层 + 全注意力层(Gemma 2/3)
    - Mamba SSM 层 + 全注意力层(Jamba、Bamba)
    - 局部分块层 + 全注意力层(Llama 4)
    每种层的 KV Cache 形状完全不一样
    """
    sliding_window_kv = KVCache(...)  # 滑动窗口
    full_attention_kv = KVCache(...)  # 全注意力
    mamba_state_kv    = KVCache(...)  # 状态空间模型(本质不同)

4.2 Jenga 论文的量化数据

Jenga 论文给出了令人警醒的量化数据:

模型                 统一管理内存浪费    Jenga 优化后
LLaMA 3.2 11B Vision     79.6%          减少 79.6%
Gemma-2 (27B)            25.0%          减少 25.0%
Ministral (8B)           56.25%         减少 56.25%

如果把所有层按统一方式管理,Llama 3.2 Vision 的 GPU 显存有近 80% 在白白浪费。

4.3 异构缓存的工程困境

# 当前主流框架的妥协方案:分离管理器
class HeterogeneousCache:
    """
    vLLM 等框架的现状:多套独立的 KV Cache 管理器
    每个管理器只管一种类型的 KV
    """
    def __init__(self):
        self.normal_kv_manager = PagedAttentionManager()    # 普通文本 KV
        self.vision_kv_manager = VisionCacheManager()       # 视觉嵌入 KV
        self.mamba_kv_manager  = StateSpaceManager()       # Mamba 状态
    
    def allocate(self, kv_type: str, size: int):
        if kv_type == "text":
            return self.normal_kv_manager.allocate(size)
        elif kv_type == "vision":
            return self.vision_kv_manager.allocate(size)
        elif kv_type == "mamba":
            return self.mamba_kv_manager.allocate(size)
    
    # 问题:三个管理器各自维护自己的内存池
    # 不同池之间无法共享空闲空间 → 整体碎片率反而上升

异构缓存带来的麻烦是系统性的:多个独立管理器之间的内存碎片、启动时难以预测内存分配、前缀缓存按类型各自实现导致命中率下降、功能组合复杂度急剧上升。


五、Era 4:分布式 KV Cache——超越单机的挑战(2025+)

5.1 背景:单机已不够用

当模型规模持续增长,单 GPU 甚至单节点已不足以承载 KV Cache。分布式推理成为生产环境的必然选择。

5.2 解耦推理:Prefill 和 Decode 分家

DistServe 的核心提案:将 Prefill 和 Decode 阶段部署到不同的 GPU 实例上。

# DistServe 架构:计算密集型 vs 内存密集型分离
class DecoupledInference:
    def __init__(self, prefill_config, decode_config):
        # Prefill 实例:计算密集 → 需要更高算力密度
        self.prefill_engine = PrefillEngine(
            tensor_parallel=8,  # 8 卡并行
            num_prefill_stages=4
        )
        # Decode 实例:内存密集 → 关注显存容量和带宽
        self.decode_engine = DecodeEngine(
            tensor_parallel=1,  # 单卡可跑更多并发
            batch_size=256      # 大 batch 提高吞吐
        )
    
    def forward(self, input_tokens: list[int]):
        # Step 1: Prefill → 输出 KV Cache
        kv_cache = self.prefill_engine.prefill(input_tokens)
        
        # Step 2: 将 KV Cache 传输到 Decode 实例
        # 关键问题:KV Cache 的传输效率
        transfer_time = self._transfer_kv(kv_cache, 
                                          self.prefill_engine,
                                          self.decode_engine)
        
        # Step 3: Decode 实例执行生成
        return self.decode_engine.decode(kv_cache)

DistServe 实测数据:与共置系统相比,请求处理量提升 4.48 倍(或在同等吞吐下 SLO 收紧 10.2 倍)。

5.3 vLLM Encoder Disaggregation:视觉编码器解耦

# vLLM 的多模态解耦方案
class MultimodalDisaggregation:
    """
    视觉编码器从 LLM 解耦为独立可扩展服务
    消除编码器与解码器之间的干扰
    实测 goodput 提升 2–2.5 倍
    """
    def __init__(self):
        self.image_encoder = VisionEncoderService(
            replicas=4,  # 独立扩缩容
            auto_scale=True
        )
        self.llm_engine = LLMEngine(
            num_gpus=8
        )
    
    def process_image_request(self, image, prompt):
        # 图像编码独立扩展
        image_embedding = self.image_encoder.encode(image)
        
        # 嵌入送入 LLM 解码
        return self.llm_engine.generate(
            prompt, 
            image_embedding=image_embedding
        )

5.4 KV Cache 感知路由:NVIDIA Dynamo

# NVIDIA Dynamo:KV Cache 感知的请求路由
class KVCacheAwareRouter:
    """
    核心思想:请求路由器优先将请求转发到
    已经持有相关 KV Cache 的实例上
    在集群层面最大化前缀缓存命中率
    """
    def __init__(self, cluster_state: ClusterState):
        self.cluster = cluster_state  # 全集群 KV Cache 视图
    
    def route(self, request: Request) -> str:
        # 查找持有最多相关 KV Cache 的实例
        best_instance = None
        best_hit_rate = -1
        
        for instance in self.cluster.instances:
            hit_rate = instance.get_prefix_hit_rate(
                request.system_prompt
            )
            if hit_rate > best_hit_rate:
                best_hit_rate = hit_rate
                best_instance = instance
        
        return best_instance.id
    
    # 收益:相同 system prompt 的请求 → 路由到同一实例
    # → 前缀缓存命中率 ↑ → Prefill 计算 ↓ → 延迟 ↓ → 成本 ↓

5.5 分层 KV Cache:Mooncake 的 KV Centered 架构

Moonshot AI 的 Mooncake 采用了一种精妙的思路:以 KV Cache 为中心的解耦架构

# Mooncake 分层 KV Cache 架构
class LayeredKVCache:
    """
    冷 KV 页从 GPU HBM 溢出到 CPU DRAM 或 SSD
    热 KV 页留在 GPU 上
    
    关键洞察:从低层级加载/写回 KV 的延迟
    可以和 GPU 计算重叠,从而被完全隐藏
    """
    GPU_HBM_SIZE_GB = 80   # A100
    KV_PER_TOKEN_GB = 0.00032  # 320 KB
    
    def __init__(self, max_tokens_in_gpu: int):
        self.gpu_kv = torch.zeros(max_tokens_in_gpu, self.KV_PER_TOKEN_GB)
        self.cpu_kv = torch.zeros(max_tokens_in_gpu * 10, self.KV_PER_TOKEN_GB)
        self.access_pattern = AccessPattern()  # LRU 或更复杂的预测
    
    def write(self, token_id: int, kv: torch.Tensor):
        """写入 KV,按热度分层"""
        if self.gpu_kv.space_available():
            self.gpu_kv[token_id] = kv
            self.access_pattern.record_access(token_id)
        else:
            # GPU 满了,淘汰最冷的页到 CPU
            victim = self.access_pattern.find_coldest()
            self._migrate_to_cpu(victim)
            self.gpu_kv[token_id] = kv
    
    def read(self, token_id: int) -> torch.Tensor:
        """读取 KV,自动处理跨层访问"""
        if token_id in self.gpu_kv:
            return self.gpu_kv[token_id]
        elif token_id in self.cpu_kv:
            # 冷热迁移:读入 GPU(与计算重叠)
            kv = self.cpu_kv[token_id]
            self._prefetch_to_gpu(token_id)  # 后台异步执行
            return kv

Mooncake 带来的性能提升:长上下文场景下吞吐量最高提升 525%,在 Kimi 真实负载中请求处理量多出 75%

5.6 分布式推理的工程挑战

挑战清单:
□ 投机解码、VLM 等优化手段和分布式推理尚未完全兼容
□ 部署需要相当的专业知识(InfiniBand、RoCE 网络配置)
□ 网络本身成为瓶颈(KV Cache 跨节点传输延迟)
□ 故障转移、落后者节点(straggler)处理
□ 自动扩缩容与 KV Cache 状态迁移
□ Kubernetes 原生方案(NVIDIA Dynamo、llm-d、AIBrix)仍在早期

六、Era 5:统一混合 KV Cache——终极目标(2025+)

6.1 核心理念:一个池子管所有

当前前沿工作的方向是构建统一内存系统:异构 KV 类型共享同一个内存池,而非各自维护独立的分配器。贯穿其中的主题是可组合性——每一项优化都应当能和其他任意优化叠加使用。

6.2 Jenga:LCM 尺寸对齐

Jenga 提出了两级内存分配器,核心思路是取不同嵌入尺寸的**最小公倍数(LCM)**作为"大页"尺寸。

# Jenga 两级内存分配器
class JengaAllocator:
    """
    不同 KV 形状 → 统一大页池 → 零碎片
    
    核心公式:LCM(256, 384) = 768
    - 图像 token KV: 256 字节
    - 文本 token KV: 384 字节
    - 大页尺寸: 768 字节(刚好容纳两者,不浪费)
    """
    def __init__(self, kv_shapes: list[tuple]):
        # Step 1: 计算所有 KV 形状的 LCM 作为大页尺寸
        self.page_size = self._compute_lcm(
            [shape[1] for shape in kv_shapes]  # 取每个 KV 的第二个维度
        )
        # Step 2: 按 LCM 页大小分配物理内存
        self.physical_memory = self._allocate_aligned(
            self.page_size * NUM_PAGES
        )
        
    def _compute_lcm(self, sizes: list[int]) -> int:
        """计算最小公倍数"""
        from math import gcd
        def lcm(a, b): return a * b // gcd(a, b)
        result = sizes[0]
        for s in sizes[1:]:
            result = lcm(result, s)
        return result
    
    def allocate(self, kv_shape: tuple) -> int:
        """
        分配 KV 内存,自动对齐到 LCM 大页
        不同形状的 KV 在同一池中不产生碎片
        """
        required_size = kv_shape[1]  # 嵌入维度
        num_pages = ceil(required_size / self.page_size)
        # 分配 num_pages 个大页
        return self._alloc_pages(num_pages)
    
    def free(self, handle: int):
        """归还大页到池"""
        self._free_pages(handle)

Jenga 量化数据:

  • GPU 内存利用率最高改善 79.6%
  • 吞吐量最高提升 4.92 倍(平均 1.80 倍)

6.3 SGLang:CUDA 虚拟内存

SGLang 则采用了另一个方法:利用 CUDA Virtual Memory API 动态重映射设备内存。

# SGLang CUDA 虚拟内存方案
"""
CUDA Virtual Memory API 允许在虚拟地址空间中保持连续,
而物理内存分散在不同位置。SGLang 用这个特性实现:
- KV 页在虚拟空间连续 → 方便索引和访问
- 物理上分散 → 允许动态碎片整理
- 弹性内存池 → 运行时调整不同池类型之间的比例
"""
class SGLangElasticMemory:
    def __init__(self, total_size_gb: int):
        # 分配一个巨大的虚拟地址空间
        self.virtual_ptr, self.physical_ptrs = cuMemMapAllocate(
            size_bytes=total_size_gb * (1024**3)
        )
        self.pools = {
            "mamba": ElasticPool("mamba"),
            "kv_cache": ElasticPool("kv_cache"),
            "temporary": ElasticPool("temp")
        }
    
    def map_pages(self, pool_type: str, num_pages: int):
        """运行时动态调整不同池的容量"""
        pool = self.pools[pool_type]
        pool.expand(num_pages)
    
    # 优势:在不移动现有数据的情况下重新平衡内存池
    # 传统方案需要拷贝大量 KV 数据才能碎片整理
    # CUDA VM 只需要修改页表条目

SGLang 2026 年 Q1 路线图明确把功能可组合性列为核心目标:要在解耦部署中跨多节点对混合 VLM 执行投机解码。

6.4 统一内存架构的可组合性挑战

# 可组合性是 Era 5 的核心目标
# 理想情况:每项优化都能和其他任意优化叠加

class ComposableOptimization:
    """
    当我们把所有优化叠加时:
    
    PagedAttention (Era 2)  ← 所有框架的基础
        ↓
    + 异构缓存 (Era 3)       ← 不同层类型共享块池
        ↓
    + 前缀缓存 (Era 2+)      ← RadixAttention 跨异构层复用
        ↓
    + 分布式推理 (Era 4)     ← KV Cache 跨节点分片
        ↓
    + 分层内存 (Era 4+)      ← GPU/CPU/SSD KV 分层
        ↓
    + LCM 对齐 (Era 5)       ← 异构层统一池管理
    
    理论上:每层叠加都能带来额外收益
    实际上:很多组合存在冲突,需要精心设计接口
    """
    
    # 冲突示例:分布式 + 前缀缓存
    # 前缀缓存在单节点上通过 RadixAttention 维护
    # 分布式推理中前缀缓存在集群层面需要全局协调
    # → 需要 KV Cache 感知的全局路由(NVIDIA Dynamo)

七、生产环境选型指南

7.1 决策树:按场景选择

你的场景是什么?
│
├─ 标准文本 LLM(聊天、补全)
│   └─ Era 2 足够 → vLLM 或 SGLang
│       └─ 有共享 system prompt → 开启前缀缓存
│
├─ 多模态模型(VLM)
│   └─ Era 3 → 关注框架对视觉嵌入的处理
│       └─ 图像密集 → 考虑 vLLM 编码器解耦(Era 4)
│
├─ 混合架构模型(Gemma 3、Jamba、Llama 4)
│   └─ Era 5 直接相关 → SGLang CUDA VM 方案或 Jenga LCM
│
├─ 大规模高吞吐量生产(百卡以上)
│   └─ Era 4 必选 → DistServe 解耦 + Dynamo 路由 + Mooncake 分层
│
└─ 长上下文(100K+ tokens)
    └─ Era 4 + 分层内存 → GPU→CPU KV 溢出不可或缺

7.2 三大框架深度对比

维度vLLMSGLangTensorRT-LLM
底层基础PagedAttentionPagedAttention + RadixAttentionPagedAttention(自研 kernel)
前缀缓存block 哈希匹配RadixAttention 树有限支持
异构缓存分离管理器CUDA VM 统一池依赖 TensorRT 配置
分布式vLLM Stack(早期)支持解耦部署NVIDIA NIM 集成
多模态编码器解耦(v0.5)多模态 Pipeline 原生DNNL 后端
性能(吞吐)最高(复杂场景)最高(H100 上)
易用性★★★★★★★★★★★★
适用场景通用、高并发Agent、多轮、RAGNVIDIA GPU 深度优化

7.3 实际部署代码示例

vLLM 部署:简单场景

from vllm import LLM, SamplingParams

# 生产级 vLLM 部署配置
llm = LLM(
    model="meta-llama/Llama-3.3-70B-Instruct",
    tensor_parallel_size=2,          # 两卡并行
    gpu_memory_utilization=0.90,     # 90% 显存用于 KV Cache
    max_num_seqs=256,                # 最大并发序列数
    block_size=16,                   # PagedAttention block 大小
    enable_prefix_caching=True,      # 开启前缀缓存(Era 2 优化)
)

sampling_params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=2048,
)

# 批量请求利用前缀缓存
outputs = llm.generate(prompts_batch, sampling_params)
# 相同 system prompt 的请求 → KV Cache 自动复用

SGLang 部署:复杂 Agent 场景

from sglang import function, gen

@function
async def multi_turn_agent(messages: list[dict]):
    """
    SGLang 的程序化调用:
    在多轮对话中自然组合工具调用
    RadixAttention 自动维护跨轮次的 KV Cache 复用
    """
    # 轮次 1:模型思考并决定调用工具
    tool_call = await gen("tool", 
        "根据用户问题,决定调用哪个工具(可选:search, calculator, db_query)"
    )
    
    if tool_call.used:
        # 工具结果注入上下文 → 触发新 KV 计算
        context = f"工具 {tool_call.name} 返回:{tool_call.result}"
        
        # 轮次 2:基于工具结果继续生成
        response = await gen("response",
            f"基于以下工具返回的结果回答:{context}\n用户问题:{messages[-1]['content']}"
        )
    else:
        response = await gen("response", 
            messages[-1]["content"]
        )
    
    return {"response": response, "tool_used": tool_call.used}

# RadixAttention 自动识别相同前缀(工具调用前的系统 prompt)
# 多个并发请求共享这段 KV → 吞吐量倍增

TensorRT-LLM 部署:极致性能

# TensorRT-LLM:从模型编译到推理
from tensorrt_llm import LLM, BuilderConfig

llm = LLM(
    model="meta-llama/Llama-3.3-70B-Instruct",
    precision="fp8",           # FP8 量化,进一步压缩 KV Cache
    tensor_parallel=2,
    enable_attention_peeking=True,  # PagedAttention 等效
)

# 配置 Prefill/Decode 解耦(Era 4 基础)
config = BuilderConfig()
config.enable_decoupled_mode = True  # 分离预填充和解码阶段

outputs = llm.generate(prompts)
# 在 H100 上实测:TensorRT-LLM 的 decode 阶段吞吐量
# 比 vLLM 高出 30-50%(得益于手写 CUDA kernel 和 FP8 支持)

八、2026 年最新进展:框架融合与新玩家

8.1 vLLM 0.5 的重大更新

vLLM 0.5(2026 年初)带来了多项关键更新:

# vLLM 0.5 重大新特性

# 1. 统一异构缓存层(Era 3→5 过渡)
llm = LLM(
    model="...",
    heterogeneous_cache=True,  # 统一管理不同类型的 KV Cache
    lcm_aware=True,         # 启用 LCM 对齐(Era 5 早期支持)
)

# 2. Encoder Disaggregation 正式版(Era 4)
llm = LLM(
    model="Qwen/Qwen2-VL-72B-Instruct",
    encoder_disaggregation=True,  # 视觉编码器独立服务
    encoder_replicas=2,
)

# 3. FP8 KV Cache(实验性)
# 在 FP8 量化模型上直接缓存 FP8 格式的 KV
# 显存占用再降 50%(相对于 FP16 KV Cache)

8.2 SGLang 2026 Q1 路线图

SGLang 路线图的核心主题是可组合性

  • 跨节点投机解码:在分布式推理中使用投机解码
  • 多模态流水线重构:统一视觉和语言 KV Cache 池
  • CUDA VM 生产级支持:弹性池从实验转正

8.3 新玩家:Mooncake 与 NVIDIA Dynamo 的互补

Mooncake(Moonshot AI)     → KV Cache 分层(GPU→CPU→SSD)
NVIDIA Dynamo               → KV Cache 感知路由(集群层面)
vLLM Production Stack       → Kubernetes 原生部署

三者可以组合使用:
DYNAMo(路由) + Mooncake(分层)+ vLLM(引擎)
= Era 4 完整实现

九、总结:五个世代的演进规律

回顾 KV Cache 管理的五年演进,可以总结出清晰的规律:

世代时间核心突破对工程师的意义
Era 0<2017无状态推理传统 ML 推理思维
Era 12017连续 KV CacheLLM 推理入门,但无法规模化
Era 22023PagedAttention + 前缀缓存改变规则的生产级基础
Era 32024异构缓存管理多模态和混合架构必备
Era 42025+分布式 + 分层 KV大规模生产的必经之路
Era 52025+统一混合内存理论最优,工程仍在探索

核心洞察:KV Cache 管理的演进和操作系统内存管理的历史惊人地相似——从连续分配到虚拟内存、分页,再到分布式共享内存。区别在于操作系统花了 40 年走完这条路,KV Cache 管理在 8 年内就走完了,背后的驱动力是 LLM 负载的爆发式增长。

对于正在构建 LLM 基础设施的工程团队来说,理解这些演进阶段没有可选项——后面所有工作都建立在这个基础之上。选择框架、判断扩容策略、评估成本,都离不开对 KV Cache 管理机制的深刻理解。

这篇文章给出了判断框架,但最终的选择永远需要结合具体的业务场景、模型架构和成本约束。KV Cache 战争远未结束,下一个突破可能就在下个季度。


本文覆盖 vLLM、SGLang、TensorRT-LLM、NVIDIA Dynamo、Mooncake、Jenga 等主流框架,KV Cache 管理演进五个世代完整解析。

推荐文章

Golang - 使用 GoFakeIt 生成 Mock 数据
2024-11-18 15:51:22 +0800 CST
MySQL死锁 - 更新插入导致死锁
2024-11-19 05:53:50 +0800 CST
ElasticSearch 结构
2024-11-18 10:05:24 +0800 CST
jQuery `$.extend()` 用法总结
2024-11-19 02:12:45 +0800 CST
WebSocket在消息推送中的应用代码
2024-11-18 21:46:05 +0800 CST
任务管理工具的HTML
2025-01-20 22:36:11 +0800 CST
程序员茄子在线接单