SGLang PD
开源 PD 分离的组件设计与 Heterogeneous TP 如何提升跨机 KV 传输吞吐
核心要点:
- 四层组件:mini-LB (接入) + MooncakeKVBootstrapServer (控制) + KV transfer backend (数据) + prefill/decode server (计算)
- mini-LB 定位:够用就行的 PD 调度器,不维护全局 prefix index,中等规模适用,Rust LB 在路上
- DP attention + PD:同时解决 MLA 省显存 + DP Attention Imbalance 两个问题
- Heterogeneous TP + Staging Buffer:把"per-token 小 RDMA"折叠成"一次大 RDMA", 2-5× 吞吐提升;不推荐 MLA
- 三种 backend:Mooncake (多线程 + socket 通知) / NIXL (全异步) / ASCEND (华为 NPU)
- LMSYS 96×H100 实测:DeepSeek V3 prefill 57.6k tok/s/node, decode 22.3k tok/s/node,约 $0.20 / 1M output
SGLang 是 LMSYS Org 主导的开源 LLM 推理引擎,PD 分离能力从 2025 年 3 月起在 Issue #4655 中按"先基础框架后特性"的方式逐步落地,并在 2025 年 5 月 LMSYS 的 96×H100 大规模 EP 实测中作为关键组件公开亮相。本文专门梳理 SGLang 内部 PD 模块的实现细节。PD 分离的基础原理见 9.2 Prefill/Decode 分离原理, Mooncake / Dynamo 系统设计分别见 9.4 Mooncake / 9.6 NVIDIA Dynamo。
架构组件怎么切分
核心问题:控制面 + 数据面 + 接入面 + 计算面四层怎么协同?请求路径是什么?
SGLang 的 PD 实现按四层切分:
| 层 | 组件 | 启动方式 | 主要职责 |
|---|---|---|---|
| 接入面 | mini-LB (sglang_router) | python -m sglang_router.launch_router --pd-disaggregation | 接收外部请求,按 PD 策略拆分给 prefill / decode |
| 控制面 | MooncakeKVBootstrapServer | 由 prefill / decode server 内部启动,无需独立进程 | 端点发现 + 握手;NIXL 后端也复用它 |
| 数据面 | KV transfer backend | 由 --disaggregation-transfer-backend 选择 | 跨节点搬 KV cache (Mooncake / NIXL / ASCEND) |
| 计算面 | prefill server / decode server | --disaggregation-mode prefill 或 decode | 各自跑前向,KV 通过数据面同步 |
@tbl-sglang-layers SGLang PD 四层组件
请求路径 (以单节点 Llama-3.1-8B 配置为例):
- 客户端把请求发到 mini-LB (默认 :8000)
- mini-LB 选一对 (prefill, decode) 实例,把 prompt 发到 prefill server (:30000)
- Prefill server 算 KV cache,通过 KV transfer backend 把 KV 推给 decode server (:30001)
- Decode server 收到 KV 后开始逐 token 生成,结果通过 mini-LB 流回客户端
mini-LB / DP attention / PD 怎么耦合
核心问题:mini-LB 的定位和局限?DP attention 怎么和 PD 配合解决两个问题?
mini-LB 定位与局限
mini-LB 是 SGLang Router 中的一种调度模式,开关是 --pd-disaggregation。功能定位是"够用就行" — 支持按 round-robin 或 cache-aware 把请求分发到 prefill / decode 池,但不维护 Mooncake Conductor 那样的全局 prefix cache index。Issue #4655 路线图中明确写着"Rust PD Load Balancer (PO2)"作为后续替代品,目标是支撑生产规模。
实践上 mini-LB 适合中等规模 (10 实例以下) 部署,更大规模通常需要外置 Dynamo / Mooncake Conductor 或自研 router。
DP attention 与 PD 的耦合
DP attention (--enable-dp-attention) 让每个 DP rank 在 attention 阶段维护自己的 KV cache、不再跨 TP rank 复制。这一特性原本是为 DeepSeek V3 MLA (Multi-head Latent Attention) 省显存设计,但与 PD 分离结合时还解决另一个问题 — DP Attention Imbalance: colocated 部署下,一个 worker 在做 prefill 而同 DP 组里另一个在做 decode,因为 attention 是同步通信,慢的拖累快的;PD 分离后两边天然解耦,DP attention 不再受混合 workload 干扰。
Heterogeneous TP 怎么解决 KV layout 不匹配
核心问题:prefill TP 与 decode TP 不一致时怎么传 KV? GPU staging buffer 怎么把 RTT-bound 变 BW-bound?
为什么需要
Prefill 是 compute-bound,倾向用大 TP 把矩阵乘做快;decode 是 memory-bound,倾向用小 TP 把 batch 拼大、HBM 带宽利用率拉满。Colocated 部署里两边强制同 TP,必有一边吃亏;PD 分离原本应该让两边各取所需,但 KV cache 在两侧的物理 layout 不同 — TP=4 的 prefill 把 KV head 切成 4 份,TP=1 的 decode 期望它们是一份连续的 — 直接 RDMA 复制会得到错位的 KV。
朴素实现的做法是"按 token 切片,逐切片传输",每 token 都发一次小消息,在高并发下被 RDMA RTT 主导。
GPU staging buffer 的解法
SGLang 的方案是引入一个 GPU 显存上的 staging buffer 做两次重排:
- Prefill 侧 gather:每个 prefill TP rank 把自己负责的那部分 KV head 切片拷贝到本 rank 的 staging buffer,然后跨 rank 做一次 all-gather 到 rank 0 的连续缓冲区
- Bulk RDMA transfer: rank 0 把整块缓冲区一次性 RDMA write 到 decode 侧的环形 pool
- Decode 侧 scatter:decode worker 从 pool 里取出连续块,按本地 KV cache pages layout 散列写入
环境变量控制:
| 变量 | 默认 | 含义 |
|---|---|---|
SGLANG_DISAGG_STAGING_BUFFER | 0 | 1 启用 staging buffer |
SGLANG_DISAGG_STAGING_BUFFER_SIZE_MB | 64 | 每个 prefill worker 的 staging buffer 大小 |
SGLANG_DISAGG_STAGING_POOL_SIZE_MB | 4096 | Decode 侧的环形 pool 总大小 |
@tbl-sglang-staging Heterogeneous TP 的环境变量
收益与限制
官方文档声称在高并发场景下 staging buffer 相比 per-token 切片方案给出 2–5× 吞吐提升 — 这个数字来源于把"多次小 RDMA"折叠成"一次大 RDMA",把传输从 RTT-bound 转回 BW-bound (详见 9.7 KV cache 跨节点传输瓶颈)。
但有一个明确的限制:不推荐用于 MLA 模型 (DeepSeek V2 / V3)。原因是 MLA 的 KV 已经被低秩压缩,单 token 的 KV 体积本身就小,staging buffer 引入的 gather/scatter 反而成了额外开销。DeepSeek 部署因此通常用同 TP 配置 + DP attention,而不是 heterogeneous TP。
KV transfer 三种 backend 怎么选
核心问题:Mooncake / NIXL / ASCEND backend 异步模型 / 适用场景 / 安装差异是什么?
| Backend | 引入时间 | 异步模型 | 适用场景 | 安装 |
|---|---|---|---|---|
| Mooncake | 2025-04-10 首次集成 | 多线程 + socket 通知 | 通用 RDMA/NVLink;与 Mooncake Store 联调 | uv pip install mooncake-transfer-engine |
| NIXL | 2025-04-21 PR #5477 合入 | 全异步 (内置通知) | UCX / LIBFABRIC fabric;不依赖额外发现服务 | pip install nixl |
| ASCEND | 2025 年下半年 | 基于 memfabric-hybrid | 华为 NPU 集群 | pip install memfabric-hybrid==1.0.0 |
@tbl-sglang-backends KV transfer backend 对比
Mooncake backend
SGLang 引用 Mooncake 的 Transfer Engine 库 (独立项目) 作为底层数据通道,但不强依赖完整 Mooncake 系统 (Conductor / Store 都不需要)。该 backend 用多线程做 sender/receiver,靠 socket 通知传输完成。配置项:
SGLANG_DISAGGREGATION_THREAD_POOL_SIZE:每 TP rank 的 worker 线程数,默认在 4–12 间动态调节SGLANG_DISAGGREGATION_QUEUE_SIZE:并行传输队列数,默认 4SGLANG_DISAGGREGATION_BOOTSTRAP_TIMEOUT:请求初始化超时,默认 300s (可拉长到 600s 应对大量并发握手)
针对同节点内 GPU 互联,可通过环境变量切到 NVLink 模式:
# 通用 NVLink
export SGLANG_MOONCAKE_CUSTOM_MEM_POOL=NVLINK
export MC_FORCE_MNNVL=True
# 节点内 NVLink
export SGLANG_MOONCAKE_CUSTOM_MEM_POOL=INTRA_NODE_NVLINK
export MC_INTRANODE_NVLINK=true
NIXL backend
NIXL (NVIDIA 推出的传输库) 由 PR #5477 (2025-04-21 合入) 引入,与 Mooncake backend 的关键差异在异步模型:NIXL 把 sender/receiver 都做成异步,直接利用 NIXL 内置的通知机制判断传输完成,去掉了 Mooncake backend 里 socket 通知 + 多线程的耦合。受益是更低的同步开销、更简洁的实现,代价是要求 fabric 支持 UCX 或 LIBFABRIC。
后端选择由环境变量控制:
# 默认 UCX, 可切到 LIBFABRIC
export SGLANG_DISAGGREGATION_NIXL_BACKEND=LIBFABRIC
NIXL backend 复用 MooncakeKVBootstrapServer 做端点发现 — 这是 SGLang 的一个工程取舍:bootstrap 协议本身够简单,没必要为每个 backend 重写一份。
ASCEND backend
针对华为 Ascend NPU 集群,依赖 memfabric-hybrid==1.0.0。可选地与 Mooncake backend 混用 (ENABLE_ASCEND_TRANSFER_WITH_MOONCAKE=true),由 Mooncake 处理 RDMA、memfabric 处理 NPU 侧 DMA。
部署形态有哪些 XpYd 配置
核心问题:1P1D / Heterogeneous TP / 多节点 DeepSeek V3 三种典型形态各自怎么启动?
单节点同构 1P1D (最简)
适合本地开发与小模型验证。Prefill / decode 各占一块 GPU, mini-LB 在 :8000 转发:
# Prefill on GPU 0
python -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-8B-Instruct \
--disaggregation-mode prefill \
--port 30000 --disaggregation-ib-device mlx5_roce0
# Decode on GPU 1
python -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-8B-Instruct \
--disaggregation-mode decode \
--port 30001 --base-gpu-id 1 --disaggregation-ib-device mlx5_roce0
# mini-LB
python -m sglang_router.launch_router --pd-disaggregation \
--prefill http://127.0.0.1:30000 \
--decode http://127.0.0.1:30001 \
--host 0.0.0.0 --port 8000
Heterogeneous TP 1P1D (Prefill TP4 + Decode TP4/DP4)
适合"prefill 用大 TP 算快、decode 用 DP 拼大 batch"的场景:
# Prefill: TP=4
python -m sglang.launch_server \
--disaggregation-mode prefill \
--tp 4 --port 30000 \
--disaggregation-ib-device mlx5_1,mlx5_2
# Decode: TP=4 但 DP=4 (每 DP rank TP=1) + DP attention
python -m sglang.launch_server \
--disaggregation-mode decode \
--tp 4 --dp 4 --enable-dp-attention \
--port 30001 \
--disaggregation-ib-device mlx5_3,mlx5_4
注意必须先 export SGLANG_DISAGG_STAGING_BUFFER=1 才能让两侧 layout 正确对齐。
多节点 DeepSeek V3 (2P+2D,大规模 EP)
LMSYS 2025-05 实测部署的简化版 (实际为 4P+9D 共 12 节点):
# Prefill node 0 (参与节点 1 同样以 --node-rank 1 启动)
python -m sglang.launch_server \
--model-path deepseek-ai/DeepSeek-V3-0324 \
--disaggregation-mode prefill \
--tp-size 16 --dp-size 8 --enable-dp-attention \
--moe-a2a-backend deepep \
--dist-init-addr ${prefill_master_ip}:5000 \
--nnodes 2 --node-rank 0 --port 30000
# Decode node 0
python -m sglang.launch_server \
--model-path deepseek-ai/DeepSeek-V3-0324 \
--disaggregation-mode decode \
--tp-size 16 --dp-size 8 --enable-dp-attention \
--max-running-requests 128 \
--dist-init-addr ${decode_master_ip}:5000 \
--nnodes 2 --node-rank 0 --port 30001
DeepEP 是 DeepSeek 开源的 MoE all-to-all 通信库,--moe-a2a-backend deepep 切换到 DeepEP normal/low-latency dispatch。
LMSYS 96×H100 实测关键数据
核心问题:DeepSeek V3 96 H100 部署的 prefill / decode 吞吐和成本是什么?
LMSYS 在 12 节点 (96×H100) 上部署 DeepSeek V3, prefill 4 节点 EP32、decode 9 节点 EP72 (含 1 节点服务弹性),公开吞吐:
| 阶段 | 配置 | 单节点 token/s | 对比基线 |
|---|---|---|---|
| Prefill | 4 节点 EP32, 1K input | 57,674 | 3.3× over TP16 |
| Prefill | 4 节点 EP32, 2K input | 54,543 | — |
| Prefill | 4 节点 EP32, 4K input | 50,302 | — |
| Decode | 9 节点 EP72, 2K input | 22,282 | 5.2× over TP16 |
| Decode | 9 节点 EP72, with MTP | 17,373 | — |
@tbl-sglang-h100 LMSYS 96×H100 DeepSeek V3 实测
折算成本约 $0.20 / 1M output tokens,约为 DeepSeek 官方 Chat API 价格的 1/5。这个数字常被引用为"SGLang PD + 大规模 EP 是开源栈下迄今最完整的 DeepSeek 部署方案"的依据。
SGLang PD 跟 Mooncake 怎么分工
核心问题:两者是替代还是互补?在 KV pool / 调度 / 传输库三个维度的差异?
| 维度 | SGLang PD | Mooncake |
|---|---|---|
| 项目定位 | 推理引擎内置 PD 能力 | 独立 KVCache 中心化服务架构 |
| Prefill / Decode 之间 | mini-LB 选 (P, D) pair, KV 点对点传 | Conductor 选 (P, D) pair, KV 也走点对点 |
| KV cache 共享 | 不主动池化,KV 只在本次请求的 (P, D) 之间流转 | 池化到 Mooncake Store (DRAM/SSD),跨请求复用 |
| Prefix cache 索引 | 各 prefill 实例本地维护 (基于 RadixAttention) | Conductor 维护全局 prefix cache table |
| Cache-aware 调度 | mini-LB 简单策略;Rust LB 在做 | Conductor 综合 (cache hit, transfer time, queue) cost function |
| KV 传输库 | Mooncake Transfer Engine / NIXL / ASCEND (可切换) | Mooncake Transfer Engine (自家) |
| 关系 | 使用 Mooncake Transfer Engine 作为可选后端 | 同时贡献 Transfer Engine 给 SGLang / vLLM 使用 |
| 适用规模 | 中等规模 (mini-LB) 到大规模 (外置 LB) | 生产级超大规模 (Kimi 在线服务) |
@tbl-sglang-vs-mooncake SGLang PD vs Mooncake 分工对比
简单说:Mooncake 是一套完整的 PD + KV 池化系统,SGLang PD 是引擎内置的 PD 能力。两者并非二选一 — SGLang 可以以 Mooncake Transfer Engine 为数据通道,未来 Conductor RFC (#977) 通过后,SGLang 还能把自己的 RadixAttention prefix tree 注册到 Mooncake 的全局 indexer,形成"SGLang 出 engine、Mooncake 出 KV pool 与全局调度"的组合。
Takeaway
| 知识点 | 核心结论 |
|---|---|
| 四层组件 | mini-LB + MooncakeKVBootstrapServer + KV backend + prefill/decode server |
| mini-LB 局限 | 不维护全局 prefix index,中等规模适用,Rust LB 在路上 |
| DP attention + PD | 同时解决 MLA 省显存 + DP Attention Imbalance 两个问题 |
| Heterogeneous TP | Staging Buffer 把小 RDMA 折叠为大 RDMA, 2-5× 吞吐;不推荐 MLA |
| 三种 backend | Mooncake (多线程) / NIXL (全异步) / ASCEND (华为 NPU) |
| LMSYS 96×H100 | prefill 57.6k tok/s/node, decode 22.3k tok/s/node,约 $0.20 / 1M output |
| 与 Mooncake 互补 | SGLang 出 engine + Mooncake 出 KV pool 与全局调度,不是二选一 |