Zer0e's Blog

去TM的AI其七:PD架构下的KV Cache优化

字数统计: 3.4k阅读时长: 14 min
2026/08/19 Share

如果说 KV Cache 是大模型推理的”记忆加速器”,那么 PD 分离架构(Prefill-Decode Disaggregation) 就是把这个加速器搬上了高速公路——但同时也带来了一个新问题:记忆本身要运输了。

今天我们就来聊聊:在 PD 分离架构下,KV Cache 面临哪些挑战,又有哪些优化手段?

先回顾:LLM 推理的两个阶段

大模型推理分为两个特征截然不同的阶段:

Prefill 阶段(预填充)

  • 一次性处理用户输入的所有 token
  • 并行计算,生成全部输入 token 的 KV Cache
  • 特点:计算密集型(Compute-bound),GPU 算力拉满

Decode 阶段(解码)

  • 逐个生成输出 token,每步只算 1 个新 token
  • 串行执行,每步都要读取完整的 KV Cache
  • 特点:访存密集型(Memory-bound),瓶颈在显存带宽
flowchart LR
    subgraph Prefill["Prefill 阶段"]
        A1[用户输入 N 个 token] --> B1["并行计算
生成 N 个位置的 KV"] B1 --> C1["计算密集
GPU 利用率高"] end subgraph Decode["Decode 阶段"] A2[逐个生成 token] --> B2["每步读取完整 KV Cache
只算 1 个新 token"] B2 --> C2["访存密集
GPU 利用率低"] end Prefill -->|传递 KV Cache| Decode style Prefill fill:#fff3e0 style Decode fill:#e3f2fd style C1 fill:#ffa726 style C2 fill:#42a5f5,color:#fff

混合部署的尴尬

如果把两个阶段混在同一批 GPU 上跑,会出现典型的互相干扰问题:

  • Prefill 的大 batch 计算一来,Decode 的延迟瞬间飙升(首 token 和出字速度卡顿)
  • Decode 占着 GPU 却只干活一点点,Prefill 排队等资源
flowchart TD
    A[混合部署] --> B1["Prefill 抢占 GPU
Decode 延迟抖动"] A --> B2["Decode 占着 GPU
算力大量空闲"] A --> B3["两种负载特征相反
怎么调参都别扭"] B1 --> C["TTFT 和 TPOT
无法同时优化"] B2 --> C B3 --> C style A fill:#ffebee style C fill:#ef9a9a

于是业界的答案是:拆开。

PD 分离架构:让专业的卡干专业的事

PD 分离(也叫 Prefill-Decode Disaggregation)的核心思想:用不同的 GPU 节点分别承担 Prefill 和 Decode。

flowchart TD
    U[用户请求] --> R[路由层 / Scheduler]

    subgraph P["Prefill 节点池"]
        P1["P 节点 1
高算力配置"] P2["P 节点 2
高算力配置"] end subgraph D["Decode 节点池"] D1["D 节点 1
高带宽配置"] D2["D 节点 2
高带宽配置"] end R -->|输入 token| P P -->|"传输 KV Cache
(RDMA)"| D D -->|流式输出| U style P fill:#fff3e0 style D fill:#e3f2fd style P1 fill:#ffa726 style P2 fill:#ffa726 style D1 fill:#42a5f5,color:#fff style D2 fill:#42a5f5,color:#fff

代表项目:

项目 来源 特点
DistServe 学界(2024) PD 分离的奠基性工作
Splitwise Microsoft 异构机器混合部署 P/D
Mooncake 月之暗面 以 KV Cache 为中心的分离架构,KVCache 池化
vLLM / SGLang 开源社区 均已支持 PD disaggregation

收益很明确:

  • ✅ Prefill 和 Decode 各自独立扩缩容
  • ✅ TTFT(首 token 延迟)和 TPOT(每 token 延迟)可以分别优化
  • ✅ 整体吞吐提升 1.5-2 倍以上

但是:KV Cache 成了”烫手山芋”

PD 分离带来收益的同时,引入了一个新瓶颈:Prefill 算出来的 KV Cache,必须通过网络搬到 Decode 节点上。

KV Cache 到底有多大?

一个请求的 KV Cache 大小:

$$\text{KV Size} = 2 \times L \times H_{kv} \times d \times S \times b$$

其中:

  • $L$:模型层数
  • $H_{kv}$:KV 头数(GQA 场景下远小于注意力头数)
  • $d$:每个头的维度
  • $S$:序列长度
  • $b$:每个元素的字节数(FP16 为 2)
  • 开头的 $2$:K 和 V 各一份

以 LLaMA-70B 为例(80 层、8 个 KV 头、头维度 128、FP16):

$$\text{KV Size per token} = 2 \times 80 \times 8 \times 128 \times 2 = 327680 \text{ B} \approx 320 \text{ KB/token}$$

一个 32K 上下文的请求,KV Cache 高达 约 10 GB!

传输时间估算

$$T_{\text{transfer}} = \frac{\text{KV Size}}{BW_{\text{network}}}$$

网络带宽 传输 10GB 耗时
25 Gbps(普通以太网) ~3.3 秒 ❌ 不可接受
100 Gbps ~0.8 秒 😐 勉强
200 Gbps RDMA ~0.4 秒 🙂
400 Gbps RDMA ~0.2 秒 ✅

结论:KV Cache 的存储与传输,是 PD 架构的头号优化对象。

PD 架构下的 KV Cache 优化全景

flowchart TD
    A[PD 架构 KV Cache 优化] --> B1["① Prefix Cache
能不算就不算"] A --> B2["② 多级缓存分层
能放近就放近"] A --> B3["③ 传输优化
能快搬就快搬"] A --> B4["④ 缓存感知调度
能命中就命中"] A --> B5["⑤ KV 压缩
能变小就变小"] B1 --> C["减少重复计算
降低传输量"] B2 --> C B3 --> C B4 --> C B5 --> C style A fill:#ff7043,color:#fff style B1 fill:#66bb6a,color:#fff style B2 fill:#42a5f5,color:#fff style B3 fill:#ffa726 style B4 fill:#ab47bc,color:#fff style B5 fill:#26c6da style C fill:#e8f5e9

下面逐个展开,重点是 Prefix Cache。

优化一:Prefix Cache(前缀缓存)

核心洞察:大量请求的前缀是重复的

真实业务中,请求的输入前缀重合率高得惊人:

flowchart TD
    A["典型请求结构"] --> B1["System Prompt
几千 token,所有请求一样"] A --> B2["Few-shot 示例
一批请求一样"] A --> B3["RAG 检索文档
热门文档反复出现"] A --> B4["多轮对话历史
本轮包含上一轮全部"] A --> B5["用户新问题
真正独特的部分"] B1 --> C["前缀部分可复用
只有尾部需要计算"] B2 --> C B3 --> C B4 --> C style A fill:#e3f2fd style B5 fill:#ef9a9a style C fill:#66bb6a,color:#fff

Prefix Cache 的思想:把已经算过的前缀 KV Cache 存下来,下次相同前缀直接复用,Prefill 只需要计算没命中的尾部。

工作原理:按块哈希

KV Cache 不能按”整句文本”缓存(差一个字就全废),而是按 固定大小的 token 块(block) 缓存,典型 block size 为 16:

1
2
3
输入 token:  [t₁  t₂  ...  t₁₆ | t₁₇ ... t₃₂ | t₃₃ ... t₄₀]
└── block 0 ──┘ └─ block 1 ─┘ └─ block 2 ─┘
hash: 0x3f8a hash: 0xb21c hash: 0x91de

每个 block 的哈希由其所有前缀 token + 本块 token 共同决定:

$$\text{hash}_i = H(\text{hash}_{i-1},\ t_{(i-1)B+1}, …, t_{iB})$$

这样保证:两个请求只要前缀 token 逐个相同,对应 block 的哈希一定相同,KV Cache 完全可复用(注意:是 token 级精确匹配,语义相近没用)。

进阶:RadixAttention(基数树索引)

SGLang 提出的 RadixAttention 用基数树(Radix Tree)管理所有请求的前缀,天然支持:

  • 任意长度前缀的共享(不必对齐到整个 prompt)
  • 自动化的 LRU 淘汰
  • 分叉与合并(多轮对话、思维树搜索等场景)
flowchart TD
    R["Radix Tree 根节点"] --> N1["System Prompt
共享 KV"] N1 --> N2["用户A: 对话历史
轮次1"] N1 --> N3["用户B: RAG文档X"] N2 --> N4["用户A: 轮次2
复用轮次1 KV"] N3 --> N5["其他请求引用文档X
直接命中"] style R fill:#ff7043,color:#fff style N1 fill:#66bb6a,color:#fff style N2 fill:#42a5f5,color:#fff style N3 fill:#42a5f5,color:#fff style N4 fill:#a5d6a7 style N5 fill:#a5d6a7

效果有多猛?

场景 前缀命中率 Prefill 计算节省
统一 System Prompt 的 Agent 90%+ TTFT 降低 60-80%
多轮对话 70-90% 随轮次递增
RAG 热门文档问答 50-80% 视文档热度
Few-shot 批量评测 95%+ 几乎只剩尾部计算

Prefix Cache 在 PD 架构下的特殊价值

在单体架构里,Prefix Cache 只是”省点计算”;但在 PD 分离架构下,它的价值被翻倍放大:

flowchart LR
    A[请求命中 Prefix Cache] --> B1["① Prefill 少算
只算尾部 token"] A --> B2["② 需要传输的 KV 变少
只传新增部分"] A --> B3["③ Decode 节点可本地命中
干脆不用传"] B1 --> C["算力省了"] B2 --> C B3 --> C style A fill:#66bb6a,color:#fff style C fill:#ffa726

命中前缀的 KV 根本不需要从 P 节点搬到 D 节点——既省算力,又省带宽,还降延迟,一箭三雕。

优化二:多级缓存分层存储

PD 架构下 KV Cache 的”居住地”不再只有 GPU 显存,而是形成层级体系:

flowchart TD
    L1["L1: GPU HBM
最快最贵,容量小
存放当前请求 + 热点前缀"] --> L2["L2: 主机 DRAM
便宜大碗,百GB级
Prefix Cache 主战场"] L2 --> L3["L3: 本地 SSD / 分布式存储
TB级,跨节点共享
全局 KVCache 池"] L3 -->|预取/加载| L2 L2 -->|加载| L1 style L1 fill:#ef9a9a style L2 fill:#ffcc80 style L3 fill:#a5d6a7

Mooncake 的做法最具代表性:把整个集群的空闲内存组成一个 KVCache 池(Pool):

  • P 节点算完的 KV,可以直接写入池子,D 节点就近读取
  • 同一个前缀的 KV 在池中全局可见,任何节点都能命中
  • 配合 RDMA,DRAM 池的读取速度足以覆盖大部分 Decode 启动需求

优化三:KV 传输优化

就算没命中缓存,传输本身也能优化:

1. RDMA 高速传输

绕过 CPU 和内核协议栈,GPU/DMA 直接读写远端内存,延迟微秒级。

2. 传输与计算重叠(Overlap)

1
2
3
传统串行:  [Prefill 计算] → [KV 传输] → [Decode 开始]
重叠优化: [Prefill 计算] → [Decode 开始]
[边算边传,逐层传输]

Prefill 每算完一层的 KV 就立刻异步发送,Decode 端先收到先就位,把传输时间藏进计算时间里。

3. 传输量化

传输前把 KV 压成 FP8/INT8,落地后再解压(或直接用低精度参与计算),带宽需求直接减半甚至四分之一:

$$T_{\text{transfer}}^{\text{FP8}} = \frac{1}{2} T_{\text{transfer}}^{\text{FP16}}$$

4. 按需传输

Decode 阶段第一步其实只需要最后几层的 KV 先就位(因为逐层计算),可以做细粒度、分优先级的传输调度。

优化四:缓存感知调度(Cache-Aware Scheduling)

Prefix Cache 能不能命中,调度器说了算。如果同一个前缀的请求被随机打散到不同节点,缓存命中率会惨不忍睹。

策略:把请求路由到”最可能命中前缀”的节点。

flowchart TD
    A[新请求到达] --> B["计算前缀哈希"]
    B --> C{查全局缓存索引}
    C -->|节点X有该前缀KV| D["路由到节点X
直接命中"] C -->|都没有| E["按负载均衡选节点
并注册新前缀"] D --> F["命中率最大化"] E --> F style A fill:#bbdefb style C fill:#ce93d8 style D fill:#66bb6a,color:#fff style F fill:#ffa726

SGLang 的 cache-aware routing、Mooncake 的全局 KVCache 索引,本质都是在做这件事:让缓存跟着请求走,让请求奔着缓存去。

优化五:KV 本体压缩

从源头减小 KV Cache 体积,传输和存储压力同步下降:

技术 原理 压缩效果
GQA / MQA 多个 Q 头共享 KV 头 KV 减少 4-8 倍
MLA 低秩联合压缩 KV KV 减少一个数量级
KV 量化 FP16 → FP8/INT8/INT4 2-4 倍
稀疏/驱逐 只保留重要 token 的 KV 视策略而定

这些是模型层面的优化,与 PD 架构正交但收益叠加——KV 越小,PD 之间的搬运成本越低,分离架构越划算。

组合拳:一次请求的完整旅程

把上面的优化串起来,看一个命中 Prefix Cache 的请求在 PD 架构中如何流转:

flowchart TD
    A[用户请求] --> B[调度器计算前缀哈希]
    B --> C{全局索引查找}
    C -->|"命中: P节点2有前缀KV"| D[路由到 P 节点 2]
    D --> E["Prefill 只计算尾部新 token"]
    E --> F{D 节点本地池
是否已有该前缀?} F -->|是| G["零传输
D 节点直接读池"] F -->|否| H["RDMA 只传增量 KV
FP8 压缩 + 逐层重叠"] G --> I[Decode 流式输出] H --> I I --> J["新生成的 KV 写回缓存池
供下一轮对话复用"] style B fill:#ce93d8 style E fill:#ffa726 style G fill:#66bb6a,color:#fff style H fill:#42a5f5,color:#fff style J fill:#a5d6a7

实际收益参考

以 Mooncake 公开数据与业界实践为参考:

优化组合 TTFT 改善 吞吐改善
仅 PD 分离 -20~30% +50~100%
PD 分离 + Prefix Cache -50~80% +100~200%
+ 多级缓存池 + RDMA -70%+ +200%+
+ 传输量化/重叠 进一步抹平传输开销 视带宽而定

注:具体数值与模型规模、请求前缀重合度、网络配置强相关,以上为典型区间。

总结

PD 分离架构把 Prefill 和 Decode 拆开,换来了独立扩展和极致优化,但代价是 KV Cache 必须流动起来。围绕这个核心矛盾,业界的优化思路可以浓缩成五句话:

flowchart TD
    A[PD 架构 KV Cache 优化] --> B1["能不算就不算
Prefix Cache"] A --> B2["能放近就放近
多级缓存分层"] A --> B3["能快搬就快搬
RDMA + 重叠 + 量化"] A --> B4["能命中就命中
缓存感知调度"] A --> B5["能变小就变小
MLA / 量化 / GQA"] B1 --> C["KV Cache 从'副产物'
变成'核心资产'"] B2 --> C B3 --> C B4 --> C B5 --> C style A fill:#ff7043,color:#fff style C fill:#66bb6a,color:#fff

核心要点回顾:

概念 作用 一句话理解
PD 分离 P/D 独立部署扩展 让专业的卡干专业的事
Prefix Cache 复用重复前缀的 KV 背过的课文不再重背
RadixAttention 基数树管理前缀 前缀共享的”目录索引”
多级缓存池 HBM→DRAM→SSD 分层 KV 的”物流仓储网络”
缓存感知调度 请求奔着缓存去 货不动,让订单找货

一句话总结:在 PD 架构时代,KV Cache 不再是推理的”副产物”,而是整个系统围绕其设计的核心资产——谁能把 KV Cache 存得好、搬得快、命中得多,谁的推理成本就低得多。

参考资料

  1. Zhong, Y., et al. (2024). “DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving”
  2. Patel, P., et al. (2024). “Splitwise: Efficient Generative LLM Inference Using Phase Splitting”
  3. Qin, R., et al. (2024). “Mooncake: A KVCache-centric Disaggregated Architecture for LLM Serving”
  4. Zheng, L., et al. (2024). “SGLang: Efficient Execution of Structured Language Model Programs” (RadixAttention)
  5. vLLM Documentation: Automatic Prefix Caching
  6. DeepSeek-V2 Technical Report: Multi-head Latent Attention
CATALOG
  1. 1. 先回顾:LLM 推理的两个阶段
    1. 1.1. Prefill 阶段(预填充)
    2. 1.2. Decode 阶段(解码)
    3. 1.3. 混合部署的尴尬
  2. 2. PD 分离架构:让专业的卡干专业的事
  3. 3. 但是:KV Cache 成了”烫手山芋”
    1. 3.1. KV Cache 到底有多大?
    2. 3.2. 传输时间估算
  4. 4. PD 架构下的 KV Cache 优化全景
  5. 5. 优化一:Prefix Cache(前缀缓存)
    1. 5.1. 核心洞察:大量请求的前缀是重复的
    2. 5.2. 工作原理:按块哈希
    3. 5.3. 进阶:RadixAttention(基数树索引)
    4. 5.4. 效果有多猛?
    5. 5.5. Prefix Cache 在 PD 架构下的特殊价值
  6. 6. 优化二:多级缓存分层存储
  7. 7. 优化三:KV 传输优化
    1. 7.1. 1. RDMA 高速传输
    2. 7.2. 2. 传输与计算重叠(Overlap)
    3. 7.3. 3. 传输量化
    4. 7.4. 4. 按需传输
  8. 8. 优化四:缓存感知调度(Cache-Aware Scheduling)
  9. 9. 优化五:KV 本体压缩
  10. 10. 组合拳:一次请求的完整旅程
  11. 11. 实际收益参考
  12. 12. 总结
  13. 13. 参考资料