Mooncake 与分离式 LLM 推理:KV Cache 传输、缓存与 Prefill-Decode 解耦深度工程实战
当 vLLM 和 SGLang 将单体式推理引擎优化到物理极限,下一步的性能跃迁来自架构层面的范式转变——将 Prefill 与 Decode 彻底分离,让 KV Cache 成为可流动的计算中间产物。Mooncake、Splitwise、DistServe 等分离式架构正在重新定义 LLM 推理的性能边界。
一、单体式推理引擎的架构瓶颈
理解分离式架构的价值,需要先看清传统推理引擎的"阿喀琉斯之踵"。
1.1 Prefill-Decode 资源竞争的根源
LLM 推理分为两个阶段:
| 阶段 | 计算特征 | 内存模式 | 瓶颈 |
|---|---|---|---|
| Prefill | 整 Prompt 并行处理,计算密集 | 一次性分配全部 KV Cache | GPU 算力利用率 |
| Decode | 逐 Token 自回归生成,访存密集 | 持续追加 KV Cache,读取历史全部 | 显存带宽 |
这两个阶段的资源需求存在根本性矛盾:Prefill 需要大量算力并行处理,Decode 需要高带宽访存逐 Token 读取。将它们绑定在同一 GPU 上执行,必然导致资源争抢。
关键问题: Prefill 阶段的长 Prompt 处理会阻塞队列中其他请求的 Decode,造成排队延迟(Queueing Delay);而 Decode 阶段的 "停顿" 又让算力空转。
单体式引擎的请求调度:
Request A: [===Prefill===][D][D][D][D][D][D]
Request B: [===Prefill===][D][D][D]... ← 被A阻塞
Request C: [===Prefill===]... ← 被A、B阻塞
↑ 严重排队
1.2 KV Cache 的内存困境
随着上下文长度扩展(128K → 1M Token),KV Cache 的内存占用呈二次方增长。以 70B 模型为例:
# KV Cache 内存占用计算示例
def kv_cache_memory(hidden_size, num_layers, num_heads, head_dim,
seq_len, batch_size, dtype_bytes=2):
"""
计算 KV Cache 的显存占用(GiB)
"""
# 每个 token 的 KV 大小 = 2(K+V) * num_layers * num_heads * head_dim * dtype_bytes
kv_per_token = 2 * num_layers * num_heads * head_dim * dtype_bytes
# 当前 batch 的总 KV Cache 大小
total_tokens = seq_len * batch_size
total_bytes = kv_per_token * total_tokens
return total_bytes / (1024 ** 3)
# Llama-3-70B 参数示例
memory = kv_cache_memory(
hidden_size=8192,
num_layers=80,
num_heads=64, # GQA: 8个KV head, 实际存储8个
head_dim=128,
seq_len=131072, # 128K 上下文
batch_size=32
)
print(f"KV Cache: {memory:.2f} GiB") # 输出: KV Cache: 32.00 GiB
更严重的问题: 长上下文的请求会 "钉" 在 GPU 上直到生成完毕,期间 KV Cache 无法释放,导致其他请求无法分配显存。
1.3 分离式架构的核心洞察
┌─────────────────────────────────────────────────────────────┐
│ 分离式推理架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Prefill 集群 KV Cache 传输 Decode 集群 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ GPU 0 │ ──────▶ │ KV Store │ ──────▶ │ GPU 0 │ │
│ │ H100 80G │ ①生成 │ 分布式缓存 │ ③加载 │ H100 80G │ │
│ │ 高算力 │ KV传输 │ Mooncake │ Decode │ 高带宽 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ② Prefill 完成,GPU释放 ④ 多Token生成 │
│ 立即处理下一个请求 持续读取KV Cache │
│ │
└─────────────────────────────────────────────────────────────┘
核心思想:让 Prefill 和 Decode 各自运行在为其特性优化的硬件上,KV Cache 成为可流动的计算中间产物。
二、Mooncake 架构深度拆解
Mooncake 是清华大学团队提出的分离式 LLM 服务架构,其核心创新在于将 KV Cache 的管理提升到独立的中间件层。
2.1 整体架构设计
┌──────────────┐
│ 请求调度器 │
│ (Router) │
└──────┬───────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Prefill 池 │ │ Prefill 池 │ │ Prefill 池 │
│ Worker 0 │ │ Worker 1 │ │ Worker N │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└───────────────┼───────────────┘
▼
┌─────────────────────┐
│ KV Cache Store │
│ (基于 CPU 内存的 │
│ 分布式缓存池) │
└──────────┬──────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Decode 池 │ │ Decode 池 │ │ Decode 池 │
│ Worker 0 │ │ Worker 1 │ │ Worker N │
└─────────────┘ └─────────────┘ └─────────────┘
2.2 KV Cache 传输协议
Mooncake 的关键技术在于 KV Cache 的高效传输。它采用了以下策略:
# KV Cache 传输的核心流程(伪代码)
class KVCacheManager:
"""管理 KV Cache 的生成、传输与缓存"""
def __init__(self, device="cpu"):
self.cache_store = CPUKeyValueStore() # CPU 内存缓存
self.transfer_engine = TransferEngine()
def prefill_and_transfer(self, request, prefill_worker):
"""
Prefill Worker 执行:
1. 执行 Prefill 计算,生成 KV Cache
2. 将 KV Cache 传输到 KV Store
"""
# 执行前向传播
kv_cache = prefill_worker.forward(request.input_ids)
# 序列化 KV Cache 用于传输
serialized = self._serialize_kv(kv_cache)
# 传输到分布式 KV Store
cache_key = f"req_{request.id}"
self.transfer_engine.put(
key=cache_key,
data=serialized,
target_nodes=self._select_cache_nodes()
)
return kv_cache
def load_and_decode(self, kv_keys, decode_worker):
"""
Decode Worker 执行:
1. 从 KV Store 加载 KV Cache
2. 执行 Decode 生成
"""
# 从 KV Store 获取 KV Cache
kv_data = self.transfer_engine.get(kv_keys)
kv_cache = self._deserialize_kv(kv_data)
# 设置到 decode worker
decode_worker.load_kv_cache(kv_cache)
# 开始自回归生成
return decode_worker.generate()
def _select_cache_nodes(self):
"""基于负载均衡和局部性选择缓存节点"""
# 考虑因素:节点负载、网络拓扑、后续 Decode 位置
return self.cache_store.select_optimal_nodes()
2.3 基于 CPU 内存的 KV Store 创新
Mooncake 的核心洞察:KV Cache 的存储不应该占用宝贵的 GPU 显存,而应该放在大容量、低成本的 CPU 内存中。
┌────────────────────────────────────────────────────────────┐
│ KV Cache 存储层次对比 │
├────────────────────────────────────────────────────────────┤
│ │
│ 存储层级 容量 带宽 成本/GB │
│ ───────────────────────────────────────────────────── │
│ HBM (GPU显存) 80GB/GPU 3.35 TB/s ~$8-10 │
│ DDR5 (CPU内存) 512GB-2TB 80-120 GB/s ~$3-5 │
│ NVMe SSD 4-30TB 7-14 GB/s ~$0.08 │
│ RDMA 网络传输 — 400 Gbps — │
│ │
│ Mooncake 策略: │
│ • 热 KV Cache → CPU 内存 │
│ • 温 KV Cache → NVMe SSD │
│ • 基于 LRU 的缓存淘汰策略 │
│ • 利用 RDMA 实现跨节点低延迟传输 │
│ │
└────────────────────────────────────────────────────────────┘
为什么 CPU 内存是最佳选择?
- 容量优势:单节点 CPU 内存可达 1-2TB,远超 GPU HBM(80GB)
- 成本优势:CPU 内存每 GB 成本仅为 GPU 显存的 1/3 到 1/2
- 无需 GPU 驻留:KV Cache 不需要在 Prefill 完成后继续占用 GPU
- 便于共享:多个 Decode Worker 可以共享同一份 KV Cache
2.4 KV Cache 传输的性能分析
# KV Cache 传输时间计算示例
def estimate_transfer_time(model_size, seq_len, bandwidth_gbps):
"""
估算 KV Cache 传输时间
假设:Llama-3-70B, 32层, KV=8头, head_dim=128, FP16
每 Token KV 大小 ≈ 2 * 32 * 8 * 128 * 2 = 131072 bytes ≈ 128KB
"""
kv_per_token = 128 * 1024 # bytes
# 序列化后的总大小(原始大小 + 元数据开销 ≈ 105%)
total_size = seq_len * kv_per_token * 1.05
# 网络传输时间
transfer_time = total_size / (bandwidth_gbps * 125000 / 8) # bytes / (Gbps -> GB/s)
return transfer_time
# 场景对比
seq_lens = [1024, 4096, 16384, 65536]
for seq_len in seq_lens:
time = estimate_transfer_time("70B", seq_len, 400) # 400Gbps RDMA
print(f"Seq {seq_len:6d}: {time*1000:8.2f} ms")
# 输出:
# Seq 1024: 2.75 ms
# Seq 4096: 10.99 ms
# Seq 16384: 43.95 ms
# Seq 65536: 175.80 ms
关键发现: 即使对于 64K 上下文长度,400Gbps RDMA 网络下 KV Cache 传输也仅需约 176ms,远小于 Decode 阶段的时间(可能数分钟)。
三、工程实战:KV Cache 传输优化
3.1 RDMA 高效传输实现
import pyverbs
from pyverbs.device import Context
from pyverbs.mr import MR
from pyverbs.qp import QP, QPCap, QPInitAttr
from pyverbs.cq import CQ
class RDMAKVCacheTransfer:
"""基于 RDMA 的 KV Cache 传输引擎"""
def __init__(self, ib_device, port_num=1):
# 初始化 RDMA 设备
self.ctx = Context(name=ib_device)
self.pd = self.ctx.allocate_pd()
self.cq = CQ(self.ctx, 100)
# 配置 QP 参数
cap = QPCap(max_send_wr=100, max_recv_wr=100,
max_send_sge=1, max_recv_sge=1)
init_attr = QPInitAttr(qp_type='RC', # Reliable Connection
send_cq=self.cq, recv_cq=self.cq,
cap=cap)
self.qp = QP(self.pd, init_attr)
# 注册内存区域
self.mr = None
def register_kv_buffer(self, kv_tensor):
"""注册 KV Cache 缓冲区"""
# KV Tensor: [num_layers, 2, seq_len, num_kv_heads, head_dim]
buf = kv_tensor.numpy().tobytes()
self.mr = MR(self.pd, buf, access='LOCAL_WRITE | REMOTE_READ')
return self.mr.lkey, self.mr.rkey
def post_rdma_read(self, local_addr, remote_addr, length, rkey):
"""发起 RDMA Read 操作(从远端拉取 KV Cache)"""
sge = pyverbs.sge.SGE(addr=local_addr, length=length,
lkey=self.mr.lkey)
wr = pyverbs.wr.SendWR(
opcode='RDMA_READ', # RDMA Read 由本地发起,远端无需参与
sge=[sge],
rkey=rkey,
remote_addr=remote_addr
)
self.qp.post_send(wr)
def post_rdma_write(self, local_addr, remote_addr, length, rkey):
"""发起 RDMA Write 操作(推送 KV Cache 到远端)"""
sge = pyverbs.sge.SGE(addr=local_addr, length=length,
lkey=self.mr.lkey)
wr = pyverbs.wr.SendWR(
opcode='RDMA_WRITE', # RDMA Write 单边操作,远端无感知
sge=[sge],
rkey=rkey,
remote_addr=remote_addr
)
self.qp.post_send(wr)
3.2 KV Cache 压缩与量化
为了进一步降低传输开销,可以对 KV Cache 进行压缩:
import torch
import torch.nn.functional as F
class KVCacheCompressor:
"""KV Cache 压缩器"""
@staticmethod
def quantize_kv_cache(kv_cache, bits=8):
"""
将 KV Cache 从 FP16 量化到低比特
Args:
kv_cache: [num_layers, 2, seq_len, num_kv_heads, head_dim]
bits: 目标位宽 (4/8)
"""
orig_dtype = kv_cache.dtype
device = kv_cache.device
if bits == 8:
# INT8 量化
quantized = torch.quantize_per_tensor(
kv_cache.reshape(-1, kv_cache.shape[-1]),
scale=0.1,
zero_point=0,
dtype=torch.qint8
)
return quantized
elif bits == 4:
# INT4 量化(需要 packing)
# 使用 GPTQ 风格的量化
kv_norm = kv_cache.abs().amax(dim=-1, keepdim=True)
scale = kv_norm / 7.0 # INT4 范围 [-7, 7]
quantized = (kv_cache / scale).round().clamp(-7, 7).to(torch.int4)
return quantized, scale
return kv_cache
@staticmethod
def head_wise_pruning(kv_cache, pruning_ratio=0.5):
"""
基于重要性的 KV 头剪枝
原理:并非所有 KV 头对注意力计算同等重要
"""
num_kv_heads = kv_cache.shape[3]
# 计算每个头的重要性(L2范数作为代理指标)
head_importance = kv_cache.pow(2).sum(dim=[2, 4]) # [num_layers, 2, num_kv_heads]
# 选择 Top-K 个头
k = int(num_kv_heads * (1 - pruning_ratio))
_, top_indices = head_importance.topk(k, dim=-1)
# 只保留重要的头
pruned_cache = kv_cache.index_select(dim=3, index=top_indices[0,0])
return pruned_cache, top_indices
@staticmethod
def sliding_window_compression(kv_cache, window_size=1024):
"""
滑动窗口压缩:只保留最近的 KV Cache,历史部分做 Pooling
类似 StreamingLLM 的策略
"""
seq_len = kv_cache.shape[2]
if seq_len <= window_size:
return kv_cache
# 保留前若干 token(注意力汇聚点)+ 最近窗口
attention_sinks = kv_cache[:, :, :4, :, :] # 前4个 token
recent_window = kv_cache[:, :, -window_size:, :, :] # 最近窗口
# 压缩中间部分:平均池化
middle = kv_cache[:, :, 4:-window_size, :, :]
middle_pooled = F.avg_pool1d(
middle.reshape(-1, middle.shape[-1]),
kernel_size=middle.shape[2],
stride=middle.shape[2]
).reshape(middle.shape[0], middle.shape[1], 1, -1)
return torch.cat([attention_sinks, middle_pooled, recent_window], dim=2)
3.3 传输-计算流水线
import asyncio
from concurrent.futures import ThreadPoolExecutor
class PipelinedKVTransfer:
"""传输-计算流水线:Prefill 和 KV 传输并行执行"""
def __init__(self, prefill_engine, transfer_engine, kv_store):
self.prefill_engine = prefill_engine
self.transfer_engine = transfer_engine
self.kv_store = kv_store
self.executor = ThreadPoolExecutor(max_workers=4)
async def prefill_with_streaming_transfer(self, input_ids, request_id):
"""
流式 Prefill:边计算边传输 KV Cache
关键优化:不必等 Prefill 完成才开始传输,而是每计算完一层就传出该层
"""
kv_chunks = []
transfer_tasks = []
# 启动 Prefill 计算
for layer_idx, layer in enumerate(self.prefill_engine.model.layers):
# 前向传播当前层
hidden = layer(hidden, kv_cache=kv_chunks)
# 提取当前层的 KV Cache
current_kv = layer.attention.get_kv_cache()
# 异步传输当前层的 KV(不等待完成)
transfer_task = asyncio.create_task(
self._transfer_layer_kv(layer_idx, current_kv, request_id)
)
transfer_tasks.append(transfer_task)
# 等待所有传输完成
await asyncio.gather(*transfer_tasks)
# 通知 Decode Worker KV 已就绪
await self.kv_store.mark_ready(request_id)
return request_id
async def _transfer_layer_kv(self, layer_idx, kv_data, request_id):
"""传输单层的 KV Cache"""
# 序列化
serialized = self._serialize(layer_idx, kv_data)
# 异步发送到 KV Store
await self.transfer_engine.async_send(
key=f"req_{request_id}_layer_{layer_idx}",
data=serialized
)
async def _serialize(self, layer_idx, kv_data):
"""序列化 KV Cache 数据"""
return {
"layer": layer_idx,
"k": kv_data[0].numpy().tobytes(),
"v": kv_data[1].numpy().tobytes(),
"shape": kv_data[0].shape,
"timestamp": time.time()
}
四、生产环境部署实践
4.1 集群架构设计
┌──────────────────────────────────────────────────────────────────┐
│ 分离式 LLM 推理集群 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 负载均衡层 (HAProxy/Envoy) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ Prefill 节点 1 │ │ Prefill 节点 2 │ │ Prefill 节点 N │ │
│ │ A100 80GB × 8 │ │ A100 80GB × 8 │ │ A100 80GB × 8 │ │
│ │ 高算力,低内存 │ │ 高算力,低内存 │ │ 高算力,低内存 │ │
│ │ 64GB DDR5 │ │ 64GB DDR5 │ │ 64GB DDR5 │ │
│ └───────┬────────┘ └───────┬────────┘ └───────┬────────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ KV Cache 存储 │ │
│ │ 分布式内存池 │ │
│ │ RDMA 互联 │ │
│ └────────┬────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ Decode 节点 1 │ │ Decode 节点 2 │ │ Decode 节点 N │ │
│ │ H100 80GB × 8 │ │ H100 80GB × 8 │ │ H100 80GB × 8 │ │
│ │ 高带宽,低算力 │ │ 高带宽,低算力 │ │ 高带宽,低算力 │ │
│ │ 256GB DDR5 │ │ 256GB DDR5 │ │ 256GB DDR5 │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
4.2 关键性能指标对比
┌──────────────────────────────────────────────────────────────────┐
│ 分离式 vs 单体式推理引擎性能对比 │
├────────────────┬───────────────────┬──────────────────────────────┤
│ 指标 │ 单体式 (vLLM) │ 分离式 (Mooncake) │
├────────────────┼───────────────────┼──────────────────────────────┤
│ TTFT (首Token) │ 150-500ms │ 50-150ms │
│ │ 长Prompt阻塞 │ Prefill集群并行 │
├────────────────┼───────────────────┼──────────────────────────────┤
│ TBT (Token间隔) │ 30-80ms │ 10-30ms │
│ │ 与Prefill争抢 │ 独占GPU资源 │
├────────────────┼───────────────────┼──────────────────────────────┤
│ 吞吐量 │ 中等 │ 高 (2-4x) │
│ │ 资源利用率低 │ 各阶段资源独立优化 │
├────────────────┼───────────────────┼──────────────────────────────┤
│ GPU利用率 │ 40-60% │ 70-90% │
│ │ 阶段切换开销 │ 持续满载运行 │
├────────────────┼───────────────────┼──────────────────────────────┤
│ KV Cache成本 │ 占用HBM │ 释放到CPU内存 │
│ │ 昂贵且有限 │ 廉价且大容量 │
├────────────────┼───────────────────┼──────────────────────────────┤
│ 复杂度 │ 低 │ 高 │
│ │ 单进程调度 │ 分布式协调 │
├────────────────┼───────────────────┼──────────────────────────────┤
│ 适用场景 │ 中小规模部署 │ 大规模生产环境 │
│ │ 延迟不敏感 │ 高吞吐、低TTFT │
└────────────────┴───────────────────┴──────────────────────────────┘
4.3 实际部署中的工程挑战
挑战一:网络延迟导致的 Decode 启动延迟
# 解决方案:Prefill 预取 + KV Cache 缓存
class KVCachePrefetcher:
"""Prefetch KV Cache 到本地内存,减少 Decode 等待"""
def __init__(self, kv_store, cache_size_gb=64):
self.local_cache = LRUCache(maxsize=cache_size_gb * 1024**3)
self.kv_store = kv_store
async def prefetch_for_upcoming_requests(self, request_queue):
"""为即将到来的请求预取 KV Cache"""
for request in request_queue.peek(n=5): # 预取前5个请求
if not self.local_cache.contains(request.id):
# 从远程 KV Store 预取
kv_data = await self.kv_store.fetch(request.id)
self.local_cache.put(request.id, kv_data)
def get_with_fallback(self, request_id):
"""本地缓存未命中时回退到远程获取"""
data = self.local_cache.get(request_id)
if data is None:
# 同步获取(有高延迟风险)
data = self.kv_store.sync_fetch(request_id)
return data
挑战二:Prefill-Decode 比例动态平衡
class AdaptiveScaler:
"""自适应扩缩容:根据 Prefill/Decode 负载动态调整资源"""
def __init__(self, min_prefill_workers=1, max_prefill_workers=8,
min_decode_workers=1, max_decode_workers=16):
self.min_prefill = min_prefill_workers
self.max_prefill = max_prefill_workers
self.min_decode = min_decode_workers
self.max_decode = max_decode_workers
# 当前 Worker 数量
self.current_prefill_workers = min_prefill_workers
self.current_decode_workers = min_decode_workers
def evaluate_scaling(self, prefill_queue_depth, decode_queue_depth,
avg_prefill_time, avg_decode_time):
"""基于队列深度和延迟评估是否需要扩缩容"""
# Prefill 队列积压 → 增加 Prefill Worker
if prefill_queue_depth > 5 and self.current_prefill_workers < self.max_prefill:
self._scale_prefill(up=True)
# Decode 队列积压 → 增加 Decode Worker
elif decode_queue_depth > 10 and self.current_decode_workers < self.max_decode:
self._scale_decode(up=True)
# 资源空闲 → 缩容以节约成本
elif prefill_queue_depth == 0 and self.current_prefill_workers > self.min_prefill:
self._scale_prefill(up=False)
挑战三:KV Cache 一致性
class VersionedKVStore:
"""版本化 KV Store:保证 KV Cache 的一致性"""
def __init__(self):
self.store = {}
self.version_counter = 0
self.locks = defaultdict(asyncio.Lock)
async def put(self, key, data, ttl=300):
"""写入 KV Cache 并附带版本信息"""
version = self._next_version()
entry = {
"data": data,
"version": version,
"timestamp": time.time(),
"ttl": ttl,
"checksum": hashlib.md5(data).hexdigest()
}
async with self.locks[key]:
self.store[key] = entry
return version
async def get(self, key, min_version=None):
"""读取 KV Cache,可指定最低版本"""
async with self.locks[key]:
entry = self.store.get(key)
if entry is None:
raise KVCacheMiss(f"Key {key} not found")
if self._is_expired(entry):
del self.store[key]
raise KVCacheMiss(f"Key {key} expired")
if min_version and entry["version"] < min_version:
raise KVCacheVersionMismatch(
f"Required version {min_version}, got {entry['version']}"
)
# 校验数据完整性
if not self._verify_checksum(entry):
raise KVCacheCorrupted(f"Key {key} checksum mismatch")
return entry["data"]
五、前沿进展与未来方向
5.1 分离式架构的开源生态
| 项目 | 架构特点 | KV 管理策略 | 适用场景 |
|---|---|---|---|
| Mooncake | 经典分离式 | CPU 内存分布式 KV Store | 通用推理 |
| DistServe | 分离式 + Chunked Prefill | 分块 KV Cache 缓存 | 在线服务 |
| Splitwise | 异构分离 | HBM + DRAM 分级存储 | 云端部署 |
| AttentionStore | 全局 KV 内存池 | 跨请求 KV 共享 | RAG 场景 |
| Mooncake (Store) | 分布式 KV Store | 多级缓存 + RDMA | 大规模集群 |
5.2 KV Cache 复用与共享
在多轮对话和 RAG 场景下,不同请求之间存在大量重复的 KV Cache:
┌─────────────────────────────────────────────────────────────┐
│ KV Cache 共享策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 场景 1: 多轮对话 │
│ ┌───────────────────────────────────────────────┐ │
│ │ System Prompt [共享] ←─────────────┐ │ │
│ │ User Message 1 [独特] │ │ │
│ │ Assistant Reply 1 [独特] │ │ │
│ │ User Message 2 [独特] ─── 复用 System Prompt │ │
│ │ Assistant Reply 2 [独特] │ 的 KV Cache │ │
│ └───────────────────────────────────────────────┘ │
│ │
│ 场景 2: RAG (Retrieval-Augmented Generation) │
│ ┌───────────────────────────────────────────────┐ │
│ │ Document Chunk 1 [共享KV] ◄── 多个用户请求 │ │
│ │ Document Chunk 2 [共享KV] 复用相同文档的 │ │
│ │ User Query [独特] KV Cache │ │
│ └───────────────────────────────────────────────┘ │
│ │
│ 收益: 避免重复计算 KV,提升吞吐、降低 TTFT │
└─────────────────────────────────────────────────────────────┘
5.3 与推测解码(Speculative Decoding)的协同
分离式架构可以与推测解码协同工作:
class SpeculativeDecodeWithDistKV:
"""在分离式架构中使用推测解码"""
def generate_with_speculative(self, request_id, draft_model, target_model):
"""
工作流:
1. Prefill Worker 生成 KV Cache
2. KV Cache 传输到 Decode Worker
3. Decode Worker 使用小模型快速推测生成
4. 目标模型验证推测结果
5. 接受/拒绝推测 token
"""
# 加载 KV Cache Decode
kv_cache = self.kv_store.get(request_id)
while True:
# 小模型快速生成 K 个推测 token
draft_tokens = draft_model.generate(
kv_cache=kv_cache,
num_draft=5
)
# 目标模型验证
verify_results = target_model.verify(
kv_cache=kv_cache,
draft_tokens=draft_tokens
)
# 接受验证通过的 token
accepted_tokens = [t for t, r in zip(draft_tokens, verify_results)
if r.is_accepted]
yield accepted_tokens
if len(accepted_tokens) < len(draft_tokens):
# 推测失败,从第一个拒绝位置重新开始
break
六、总结与思考
分离式 LLM 推理架构代表了推理引擎设计从 "单机优化" 到 "系统设计" 的范式转变。它的核心价值在于:
- 资源隔离:Prefill 和 Decode 各自运行在最优硬件上,互不干扰
- 成本优化:KV Cache 从昂贵的 HBM 迁移到低成本 CPU 内存
- 弹性扩展:Prefill/Decode 集群可根据负载独立扩缩容
- 性能提升:TTFT 降低 50-70%,吞吐量提升 2-4 倍
但分离式架构也带来了额外的工程复杂度:KV Cache 传输延迟、分布式一致性、缓存淘汰策略等。因此,它更适合大规模生产环境(日均百万请求以上),中小规模场景下 vLLM/SGLang 的单体式优化仍有竞争力。
一句话总结: 分离式架构不是让单机更快,而是让集群更聪明——把对的计算放在对的地方,让数据在对的时机流动。
相关延伸阅读:
- vLLM PagedAttention 深度工程实战
- LLM Inference PD Disaggregation 架构演进
- KV Cache 内存管理:从 Sliding Window 到 H2O
- RDMA 网络编程:从 Verbs 到代码实现

发表评论 取消回复