技术预备知识

上一篇: 引言与项目背景 | 下一篇: 整体架构设计


技术预备知识

在深入 Mooncake 的设计细节之前,本章将介绍必要的技术背景知识,包括 Transformer 架构、KVCache 原理、LLM 推理的两阶段特性,以及 RDMA 网络基础。

2.1 Transformer 架构与 KVCache 原理

2.1.1 Transformer 的 Attention 机制回顾

现代大语言模型(如 GPT、LLaMA、Kimi)都基于 Transformer 架构,其核心是 自注意力机制(Self-Attention)

对于输入序列 $X \in \mathbb{R}^{n \times d}$(n 个 token,每个 token 的维度为 d),自注意力的计算过程如下:

graph LR X["输入 X"] --> Q["Q = X * W_Q"] X --> K["K = X * W_K"] X --> V["V = X * W_V"] Q --> Attn["Attention"] K --> Attn V --> Attn Attn --> O["输出 O"]

计算公式

$$Q = XW_Q, \quad K = XW_K, \quad V = XW_V$$

$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$

其中:

  • $W_Q, W_K, W_V$ 是可学习的投影矩阵
  • $d_k$ 是 Key 向量的维度
  • softmax 操作确保注意力权重归一化

2.1.2 自回归生成与 KVCache

LLM 采用**自回归(Autoregressive)**方式生成文本:每次只生成一个 token,新 token 依赖于之前所有 token 的信息。

朴素实现的问题

生成第 n 个 token 时:
  需要计算 Q_n = x_n · W_Q  (只需当前 token)
  需要计算 K_{1:n} = X_{1:n} · W_K  (需要所有历史 token)
  需要计算 V_{1:n} = X_{1:n} · W_V  (需要所有历史 token)

每生成一个新 token,都需要重新计算所有历史 token 的 K 和 V,造成大量重复计算。

KVCache 优化

将历史 token 的 K 和 V 缓存起来,生成新 token 时只需:

  1. 计算新 token 的 $q_n, k_n, v_n$
  2. 将 $k_n, v_n$ 追加到 KVCache
  3. 使用 $q_n$ 与完整的 $K_{1:n}, V_{1:n}$ 计算注意力
graph TB subgraph prefill["Prefill 阶段"] P1["输入 tokens X_{1:m}"] --> P2["计算 K_{1:m}, V_{1:m}"] P2 --> P3["存入 KVCache"] P2 --> P4["计算 Attention"] P4 --> P5["生成第一个 output token"] end subgraph decode["Decode 阶段 (循环)"] D1["新 token x_n"] --> D2["计算 k_n, v_n"] D2 --> D3["追加到 KVCache"] D3 --> D4["K_{1:n}, V_{1:n}"] D1 --> D5["计算 q_n"] D5 --> D6["Attention with KVCache"] D4 --> D6 D6 --> D7["生成下一个 token"] end P5 --> D1 D7 -.-> D1

2.1.3 KVCache 的内存占用分析

对于一个典型的 LLM(以 LLaMA3-70B 为例):

参数 符号 LLaMA3-70B
层数 $l$ 80
模型维度 $d$ 8192
注意力头数 $n_{head}$ 64
KV 头数 (GQA) $n_{kv}$ 8
数据类型 - BFloat16 (2 bytes)

单个 token 的 KVCache 大小

$$\text{KVCache per token} = l \times 2 \times \frac{d}{n_{head}/n_{kv}} \times \text{sizeof(dtype)}$$

$$= 80 \times 2 \times \frac{8192}{64/8} \times 2 = 80 \times 2 \times 1024 \times 2 = 327,680 \text{ bytes} \approx 320 \text{ KB}$$

不同上下文长度的 KVCache 总大小

上下文长度 KVCache 大小
4K tokens 1.28 GB
32K tokens 10.24 GB
128K tokens 40.96 GB
1M tokens 320 GB

这解释了为什么长上下文推理对内存需求如此之高。

2.1.4 Prefix Caching 的原理

关键洞察:自回归语言模型具有前缀无关性——相同前缀的 KVCache 完全相同,与后续内容无关。

请求 A: "请分析这份报告:[报告内容]... 给出总结"
请求 B: "请分析这份报告:[报告内容]... 给出要点"

共同前缀: "请分析这份报告:[报告内容]..."

如果请求 A 先执行,其前缀部分的 KVCache 可以被缓存。当请求 B 到达时,可以直接复用这部分 KVCache,只需计算不同后缀部分。

Prefix Caching 的工作流程

sequenceDiagram participant R1 as 请求 A participant Cache as KVCache 缓存 participant GPU as GPU participant R2 as 请求 B R1->>GPU: 发送完整 prompt GPU->>GPU: Prefill 计算 GPU->>Cache: 存储 KVCache (hash: prefix_A) GPU-->>R1: 返回输出 Note over R2,Cache: 请求 B 到达,前缀与 A 相同 R2->>Cache: 查询 prefix hash Cache-->>R2: 命中!返回 KVCache 位置 R2->>GPU: 只发送不同的后缀 Cache->>GPU: 加载缓存的 KVCache GPU->>GPU: 只计算后缀部分 GPU-->>R2: 返回输出

2.2 LLM 推理的两阶段特性

LLM 推理可以明确分为两个阶段:PrefillDecode。这两个阶段具有截然不同的计算特性。

2.2.1 Prefill 阶段

特点

  • 处理所有输入 tokens(可能数万甚至数十万)
  • 计算密集型(Compute-bound)
  • 高度并行化(所有 token 可同时计算)
  • 输出:第一个生成的 token + 完整的 KVCache

计算复杂度

$$T_{prefill} \propto l \times (an^2d + bnd^2)$$

其中 $n$ 是输入长度。注意 $n^2$ 项来自注意力计算,这使得长上下文的 Prefill 时间急剧增长。

GPU 利用率:由于高并行度,Prefill 阶段可以充分利用 GPU 的计算能力,MFU(Model FLOPs Utilization)较高。

2.2.2 Decode 阶段

特点

  • 每次只生成一个 token
  • 内存密集型(Memory-bound)
  • 无法并行化(自回归依赖)
  • 需要访问完整的 KVCache

计算复杂度

$$T_{decode} \propto l \times d^2 + l \times nd$$

第一项是 MLP 计算,第二项是与 KVCache 的注意力计算。由于每次只处理一个 token,GPU 计算单元严重闲置。

GPU 利用率:单个请求的 Decode 阶段 MFU 很低。优化方法是使用 Continuous Batching,将多个请求的 Decode 合并执行。

2.2.3 两阶段对比

特性 Prefill Decode
处理 token 数 多(输入长度) 单个
计算类型 计算密集型 内存密集型
并行度
GPU 利用率 低(需 batching)
主要瓶颈 计算吞吐量 内存带宽
优化目标 TTFT TBT
graph TB subgraph prefill_char["Prefill 阶段特性"] P1["大量输入 tokens"] P2["并行计算"] P3["计算密集"] P4["高 GPU 利用率"] P1 --> P2 --> P3 --> P4 end subgraph decode_char["Decode 阶段特性"] D1["单个 token"] D2["顺序生成"] D3["内存密集"] D4["低 GPU 利用率"] D1 --> D2 --> D3 --> D4 end subgraph opt_strategy["优化策略"] O1["Prefill - 缓存复用
减少计算量"] O2["Decode - Continuous Batching
提高并行度"] end P4 --> O1 D4 --> O2

2.2.4 为什么需要 P/D 分离

传统架构将 Prefill 和 Decode 耦合在同一节点,导致:

  1. 相互干扰:长 Prefill 请求阻塞 Decode 请求,造成 TBT 超时
  2. 资源配置困难:无法针对不同阶段优化资源配置
  3. 弹性伸缩受限:无法独立扩缩容 Prefill 或 Decode 能力

P/D 分离的优势

graph LR subgraph coupled["耦合架构"] C1["请求"] --> C2["节点"] C2 --> C3["Prefill"] C2 --> C4["Decode"] C3 -.相互干扰.-> C4 end subgraph disagg["分离架构"] S1["请求"] --> S2["调度器"] S2 --> S3["Prefill 池"] S2 --> S4["Decode 池"] S3 --> S5["KVCache 传输"] S5 --> S4 end
  • Prefill 节点可以专注于最大化计算吞吐
  • Decode 节点可以专注于保证 TBT SLO
  • 可以根据负载特征独立调整 P/D 比例

2.3 RDMA 网络基础

Mooncake 的高性能 KVCache 传输依赖于 RDMA(Remote Direct Memory Access)网络。本节介绍 RDMA 的基本概念。

2.3.1 什么是 RDMA

RDMA 允许一台计算机直接访问另一台计算机的内存,绕过操作系统内核和 CPU,实现极低延迟和高带宽的数据传输。

graph TB subgraph tcpip["传统 TCP/IP"] T1["应用程序"] --> T2["Socket API"] T2 --> T3["TCP/IP 协议栈"] T3 --> T4["内核缓冲区"] T4 --> T5["网卡驱动"] T5 --> T6["网卡"] end subgraph rdma["RDMA"] R1["应用程序"] --> R2["RDMA Verbs"] R2 --> R3["网卡"] R3 -.直接访问.-> R4["远程内存"] end

RDMA 的优势

特性 TCP/IP RDMA
CPU 参与 每个数据包 仅初始化
内存拷贝 多次 零拷贝
延迟 ~100us ~1-2us
带宽利用率 ~50-70% ~95%+

2.3.2 RDMA 的主要实现

技术 网络类型 特点
InfiniBand 专用网络 最高性能,需专用硬件
RoCE v2 以太网 基于以太网,需支持 ECN
iWARP 以太网 兼容性好,性能较低
eRDMA 云网络 阿里云等云厂商提供

Mooncake 主要使用 InfiniBandRoCE,因为它们在 GPU 集群中最为常见。

2.3.3 RDMA 核心概念

1. 保护域(Protection Domain, PD)

PD 是 RDMA 资源的安全边界,同一 PD 内的资源可以相互访问。

2. 内存区域(Memory Region, MR)

应用程序必须先向 RDMA 子系统注册内存区域,获得 lkey(本地访问)和 rkey(远程访问)。

// 内存注册示例
struct ibv_mr *mr = ibv_reg_mr(pd, addr, length,
    IBV_ACCESS_LOCAL_WRITE |
    IBV_ACCESS_REMOTE_WRITE |
    IBV_ACCESS_REMOTE_READ);
uint32_t lkey = mr->lkey;  // 本地访问密钥
uint32_t rkey = mr->rkey;  // 远程访问密钥

3. 队列对(Queue Pair, QP)

QP 是 RDMA 通信的端点,包含:

  • Send Queue (SQ):发送工作请求
  • Receive Queue (RQ):接收工作请求
graph LR subgraph local["本地节点"] SQ1["Send Queue"] RQ1["Receive Queue"] CQ1["Completion Queue"] end subgraph remote["远程节点"] SQ2["Send Queue"] RQ2["Receive Queue"] CQ2["Completion Queue"] end SQ1 -->|"RDMA Write"| RQ2 SQ2 -->|"RDMA Read"| RQ1

4. 完成队列(Completion Queue, CQ)

当 RDMA 操作完成时,结果被放入 CQ,应用程序轮询 CQ 获取完成通知。

5. RDMA 操作类型

操作 描述 是否需要远程 CPU
RDMA Write 写入远程内存
RDMA Read 读取远程内存
Send/Recv 消息传递 是(需预先 post recv)

Mooncake 主要使用 RDMA WriteRDMA Read 进行 KVCache 传输,因为它们完全绕过远程 CPU。

2.3.4 GPU Direct RDMA

GPU Direct RDMA (GDR) 允许网卡直接访问 GPU 显存,无需经过 CPU 内存中转:

graph LR subgraph no_gdr["无 GDR"] G1["GPU A"] -->|"PCIe"| C1["CPU 内存"] C1 -->|"RDMA"| C2["CPU 内存"] C2 -->|"PCIe"| G2["GPU B"] end subgraph with_gdr["有 GDR"] G3["GPU A"] -->|"直接 RDMA"| G4["GPU B"] end

GDR 的优势

  • 减少数据拷贝
  • 降低延迟
  • 释放 CPU 内存带宽

限制

  • 需要 GPU 和网卡在同一 PCIe 域
  • 需要特定驱动支持

Mooncake 的 Transfer Engine 会自动检测 GDR 支持情况,并选择最优传输路径。

2.3.5 多网卡聚合

现代 GPU 服务器通常配备多块 RDMA 网卡。例如,NVIDIA HGX 系统中:

  • 8xA800 配备 8x100Gbps 或 4x200Gbps 网卡
  • 8xH800 配备 8x400Gbps 网卡

聚合带宽可达 800Gbps - 3200Gbps(100-400 GB/s),与 DDR5 内存带宽相当。

Mooncake 的 Transfer Engine 实现了拓扑感知的多网卡聚合,能够:

  1. 自动发现所有可用网卡
  2. 根据数据位置选择最优网卡
  3. 将大传输分割到多网卡并行执行

2.4 相关系统与技术

2.4.1 vLLM 与 PagedAttention

vLLM 是当前最流行的开源 LLM 推理系统,其核心创新是 PagedAttention

  • 将 KVCache 分成固定大小的页(block)
  • 使用类似操作系统的页表管理
  • 支持 Prefix Caching(本地)

Mooncake 借鉴了 PagedAttention 的分页思想,但将其扩展到分布式场景。

2.4.2 Continuous Batching

Continuous Batching(持续批处理)是 Decode 阶段的关键优化:

  • 不等待所有请求完成再开始新批次
  • 每个 iteration 动态添加新请求、移除已完成请求
  • 显著提高 GPU 利用率
graph TB subgraph static_batch["静态 Batching"] S1["Batch 1 - A, B, C"] --> S2["等待全部完成"] S2 --> S3["Batch 2 - D, E, F"] end subgraph cont_batch["Continuous Batching"] C1["Iter 1 - A, B, C"] C2["Iter 2 - A, B, D"] C3["Iter 3 - A, D, E"] C4["Iter 4 - D, E, F"] C1 --> C2 C2 --> C3 C3 --> C4 end

2.4.3 Chunked Prefill

Chunked Prefill 将长 Prefill 请求分成多个小块执行:

  • 减少单次 Prefill 对 Decode 的干扰
  • 缺点:无法完全消除干扰,且降低 Prefill MFU

Mooncake 选择 P/D 分离而非 Chunked Prefill,因为前者能更彻底地解决干扰问题。

2.4.4 Sequence Parallelism

Sequence Parallelism (SP) 将长序列分布到多个节点并行处理:

  • 支持超长上下文
  • 缺点:需要频繁的跨节点通信(每层 2 次 AllReduce)

Mooncake 提出的 Chunked Pipeline Parallelism (CPP) 通过流水线方式减少通信开销。

2.5 本章小结

本章介绍了理解 Mooncake 所需的技术背景:

  1. KVCache 是 LLM 推理的核心数据结构,大小与上下文长度成正比
  2. Prefix Caching 可以显著减少重复计算,但需要大容量全局缓存
  3. Prefill/Decode 分离 可以消除两阶段的相互干扰
  4. RDMA 提供了高带宽、低延迟的跨节点数据传输能力
  5. GPU Direct RDMA 进一步减少了数据拷贝开销

有了这些背景知识,我们将在下一章深入 Mooncake 的系统架构设计。


上一篇: 引言与项目背景 | 下一篇: 整体架构设计