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% |
| 并发请求数 | 几十个 | 数百至数千 |
| 吞吐量提升 | baseline | 2–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:两个框架的取舍
两个框架都支持前缀缓存,但实现路径不同:
| 特性 | vLLM | SGLang |
|---|---|---|
| 前缀缓存实现 | 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 三大框架深度对比
| 维度 | vLLM | SGLang | TensorRT-LLM |
|---|---|---|---|
| 底层基础 | PagedAttention | PagedAttention + RadixAttention | PagedAttention(自研 kernel) |
| 前缀缓存 | block 哈希匹配 | RadixAttention 树 | 有限支持 |
| 异构缓存 | 分离管理器 | CUDA VM 统一池 | 依赖 TensorRT 配置 |
| 分布式 | vLLM Stack(早期) | 支持解耦部署 | NVIDIA NIM 集成 |
| 多模态 | 编码器解耦(v0.5) | 多模态 Pipeline 原生 | DNNL 后端 |
| 性能(吞吐) | 高 | 最高(复杂场景) | 最高(H100 上) |
| 易用性 | ★★★★★ | ★★★★ | ★★★ |
| 适用场景 | 通用、高并发 | Agent、多轮、RAG | NVIDIA 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 1 | 2017 | 连续 KV Cache | LLM 推理入门,但无法规模化 |
| Era 2 | 2023 | PagedAttention + 前缀缓存 | 改变规则的生产级基础 |
| Era 3 | 2024 | 异构缓存管理 | 多模态和混合架构必备 |
| Era 4 | 2025+ | 分布式 + 分层 KV | 大规模生产的必经之路 |
| Era 5 | 2025+ | 统一混合内存 | 理论最优,工程仍在探索 |
核心洞察:KV Cache 管理的演进和操作系统内存管理的历史惊人地相似——从连续分配到虚拟内存、分页,再到分布式共享内存。区别在于操作系统花了 40 年走完这条路,KV Cache 管理在 8 年内就走完了,背后的驱动力是 LLM 负载的爆发式增长。
对于正在构建 LLM 基础设施的工程团队来说,理解这些演进阶段没有可选项——后面所有工作都建立在这个基础之上。选择框架、判断扩容策略、评估成本,都离不开对 KV Cache 管理机制的深刻理解。
这篇文章给出了判断框架,但最终的选择永远需要结合具体的业务场景、模型架构和成本约束。KV Cache 战争远未结束,下一个突破可能就在下个季度。
本文覆盖 vLLM、SGLang、TensorRT-LLM、NVIDIA Dynamo、Mooncake、Jenga 等主流框架,KV Cache 管理演进五个世代完整解析。