SGLang 全局 KV 缓存重用详解 | AI生成和翻译
问题: 上述 SGLang 技术描述是什么意思?请解释上文提到的全局 KV 缓存复用、内存分配器调整以及性能结果。
回答:
这描述的是 SGLang 的上下文并行(CP)+ 全局 KV 缓存重新分片特性——一种在多个 GPU 秩之间共享 KV 缓存而非每个秩维护独立缓存的方式。下面逐层解析。
背景:正在解决什么问题?
在标准 LLM 服务中,KV 缓存(注意力机制中具体化的 K 和 V 张量)按每个 GPU 秩本地存储。使用张量并行(TP=8)时,每个秩持有其自身序列分片的 KV 缓存分片。这意味着:
- 秩 0 上的缓存命中 ≠ 可由秩 1 使用
- 前缀复用是局部的——跨请求共享仅限于秩本地
- 长序列需要较大的每秩 KV 缓存预算
目标是使 KV 缓存能够在 CP 秩之间全局复用。
第 1 部分:Radix 缓存 → 全局缓存树
SGLang 现有的前缀缓存使用Radix 树(基于 token 序列的字典树):
Root
├── [sys_prompt_tokens] → Node A (rc=2, last_access=T1)
│ ├── [user_tokens_1] → Node B
│ └── [user_tokens_2] → Node C
每个节点存储:
- 该前缀片段的 Token ID
- 指向 KV 页面的指针
- 引用计数(使用中时防止驱逐)
- 最后访问时间(LRU 驱逐)
CP 扩展为每个页面增加了一个字段:CP Owner(哪个秩持有此 KV 页面)。
@dataclass
class KVPage:
token_ids: List[int]
kv_data: Tensor # 实际的 K,V 值
cp_owner: int # 新增:哪个 CP 秩拥有此页面
ref_count: int
last_access: float
现在 Radix 树变成了一个全局索引——每个秩都能看到整棵树,并知道:
- 哪些页面是本地的(cp_owner == self.rank)
- 哪些页面是远程的(cp_owner != self.rank,但可通过 NVLink/RDMA 获取)
这保持了 SPMD(单程序多数据)设计:每个秩运行相同的树插入/查找代码。CP Owner Manager 决定放置策略(复制冗余页面,分片唯一页面)。
第 2 部分:驱逐——领导秩模式
驱逐是 SPMD 唯一失效的地方,因为不同秩的内存压力不同。解决方案:
R0(领导)遍历全局树
→ 选择要驱逐的页面(LRU 策略)
→ 通过 AllReduce/Broadcast 在 CP 组内广播决策
所有秩执行相同的驱逐操作
→ 树在所有秩之间保持一致
这是一种经典的通过单一领导者实现分布式共识模式——简单、正确,避免了分裂脑。缺点是 R0 负载略高,但驱逐不频繁,因此可以接受。
第 3 部分:内存分配器——集体分配
当 CP 沿序列维度拆分序列时,每个秩的 token 数量是不均匀的(填充或参差不齐的批次)。因此:
Rank 0: 1024 tokens → 需要 N 个页面
Rank 1: 987 tokens → 需要 M 个页面
在分配之前,它们进行集体通信(all_reduce 自由页面数量):
local_free = allocator.free_pages()
global_min_free = dist.all_reduce(local_free, op=MIN)
if global_min_free < required:
trigger_eviction()
# 重复直到所有秩都有足够空间
然后分配逻辑有 3 种情况:
1. 本地缓存命中 → 无操作,直接复用
2. 远程缓存命中 → 分配本地页面,从远程秩拉取 KV
3. 缓存未命中 → 分配并从头计算
关键洞察:这在页面抽象层面上操作,因此与以下因素无关:
- 模型架构(MLA、GQA、MHA 都只是生成 KV 页面)
- 数据类型(BF16、FP8、FP4——只影响页面大小)
- 注意力变体(DeepSeek MLA 的压缩 KV 仍然以相同方式分页)
第 4 部分:性能数据(DeepSeek 235B 在 H100 上)
| 配置 | TTFT(p50) | 说明 |
|---|---|---|
| TP=8, EP=8(基线) | 631ms | 标准配置 |
| Attn CP=2, MoE DP=2, EP=4 | 452ms | -28% |
| Attn CP=2, EP=8 | ~630ms | 尚未改善 |
| 2节点, 16xH100, 32K 序列 | 764ms | 弱扩展,不理想 |
为什么 CP=2 + EP=4 胜出:
- CP 减少了注意力通信——每个秩处理一半序列,因此注意力全连接更小
- EP=4(更小的专家并行)——MoE 负载不均衡随着 EP 秩增加而恶化;EP=4 以更小的偏斜分布专家
为什么 CP=2 + EP=8 尚未改善:通信减少的代码路径尚未合并——CP 开销抵消了收益。
为什么 2节点 32K 是 764ms(而非 ~631ms):KV 缓存 all-gather 仍在关键路径上——通信尚未与计算重叠。
第 5 部分:KV 缓存重新分片——核心优势
重新分片 = 当请求命中由不同 CP 秩拥有的缓存 KV 页面时,不是重新计算,而是拉取并重新分发该页面。
结果(TP=4,不同 CP):
| 指标 | 效果 |
|---|---|
| 精度 | 无变化(完全相同值) |
| KV 缓存内存使用 | -50%(跨秩去重) |
| 缓存命中率 | +50% 更多可能命中 |
| 8K 吞吐量 | -7%(all-gather 开销) |
| 16K 吞吐量 | -21%(80% 来自 KV all-gather,20% 来自调度器) |
16K 的分解很重要:
- 21% 损失中的 80% = KV all-gather 延迟 → 可通过计算-通信重叠完全消除
- 21% 损失中的 20% = 调度器 CPU 开销 → 可通过 CPU 优化 + 异步调度器修复
因此,重叠后的理论稳态开销仅为约 4%——那就是调度器成本。
总结心智模型
CP 之前:
Rank 0: [KV_A, KV_B, ...] ← 隔离
Rank 1: [KV_C, KV_D, ...] ← 隔离
全局 KV 重新分片之后:
全局 Radix 树(所有秩可见):
prefix_1 → page_id=42, cp_owner=0
prefix_2 → page_id=43, cp_owner=1
任何秩都可以通过从拥有秩获取来服务任何前缀
→ 内存减少 50%(无冗余复制)
→ 对于长上下文服务,全局缓存命中率显著提升
这本质上是在 GPU HBM 之上构建一个分布式 KV 存储(类似于 Redis Cluster,但用于注意力 KV 张量,具有 LRU 驱逐和前缀感知路由),并直接集成到 SGLang 的调度器中。