正文
摘要
主要了解PD分离概念、KVcache的存储格式、应用方式,以及缓存命中是怎么回事。
PD分离简介
LLM模型在处理一段请求时,主要进行了Prefill(预填充)、Decode(解码)两个阶段。
- Prefill:在将输入prompt转为token后,需要计算每个token与其他token的注意力分数,维度为N x N,如果输入token很多,计算的矩阵将会非常大(例如1000 x 1000),有矩阵就可以并行,这一阶段的输出最终将作为KVcache缓存起来。这是一个计算密集型任务。
- Decode:LLM是自回归模型,需要用之前token预测下一token。虽然模型每次只预测一个新 token,但每次都需要查看之前生成过的内容(内存需求大)。然后再用这个 token 去生成下一个。这属于内存密集型任务。
由于Prefill和Decode的特性,如果放在同一台GPU设备中,很难兼顾 计算性能强+内存大,因此可以进行PD分离,分别设立Prefill集群和Decode集群。
PD分离的核心
- Prefill 和 Decode 拆开,放到不同的集群
- Prefill 集群:算力强,适合批量处理输入。
- Decode 集群:内存优化,适合管理多个并发的生成任务。
- Prefill 的输出是 KV Cache
- KV Cache = Key/Value 缓存(注意是注意力层中的中间结果),在后续生成时不用重复计算,可以直接复用。
- Prefill 完成后,把 KV Cache + 第一个生成的 token 传给 Decode 集群
- Decode 再从 KV Cache 状态继续逐步生成 token。
- Prefill 阶段和 Decode 阶段必须通过高速网络传 KV Cache,这需要超高速网络(例如 InfiniBand、NVLink)。
NVLink
KVcache的使用
- 在Decode阶段中,会在Prefill阶段计算得出的KVcache基础上进行next token生成。
- 单个会话窗口中,进行多轮对话时,缓存命中。
缓存命中
在调用大模型API经常发现,命中缓存费用一般只有不命中缓存费用的几分之一。这意味着命中了缓存,API成本较低。其中缓存指的就是KVcache。
是否命中KVcache,取决于token的前缀是否相同。
命中KVcache的几种情况:
- ** 完全命中(Full Hit):**新请求 = 旧请求的前缀 或 完全相同。
例如:
句子1:我,喜欢,北京,这座,城市。
句子2:我,喜欢,北京
- 部分前缀命中(Prefix Hit): **新请求和旧请求开头一部分相同**,之后不一样。
例如:
句子1:我,喜欢,北京,这座,城市。
句子2:我,喜欢,上海,和,北京
“我”、”喜欢”属于公共前缀, 直接复用这两个 token 的 KV Cache。从分叉点开始(”上海”)重新计算 KV Cache,并继续往后更新。
- 后缀相同但前缀不同(Suffix Overlap): 新请求和旧请求只有后半部分一样。
这种属于未命中。 ❌ 不能复用旧的 KV Cache。 虽然都有”喜欢”、”北京”,但其在两个请求的上下文不同,即Attention不同,语义可能不同。 必须从头算起,重新构建完整 KV Cache。
句子1:我,喜欢,北京,这座,城市。
句子2: 他,也,喜欢,北京,这座,城市。
多轮对话中的KVcache
- 自己猜的:KVcache不是所有人共用一组缓存。分离的单位通常为一个会话窗口或同一用户(以下用同一窗口举例)。因此,只有在同一会话窗口内,KVcache才生效,关了窗口重新问,则无缓存,需要从头构建。
- 在同一窗口进行了的多轮对话,每次对话都会产生一个单独的KVcache。
例如:
句子1:我喜欢北京。
句子2:我喜欢上海。
句子3:我不喜欢北京。
这会缓存3个KVcache List,在新的请求到来时,通过前缀匹配算法,选择最匹配的KVcache。
- KVcache一般存储在GPU、CPU中,方便读写。
| KVcache存储情况 | 存放位置 | 特点 |
|---|---|---|
| 单机 GPU / CPU | RAM / 显存 | 快速访问,常规使用 |
| 多 GPU 分布式 | 各 GPU 显存 / 分布式 KV store | 支持流水线并行 / 模型并行 |
| 长上下文 / 断点续生成 | 磁盘 / 数据库 | 可恢复,但速度慢,不常用 |