跳到主要内容

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 prefilldecode各自跑前向,KV 通过数据面同步

@tbl-sglang-layers SGLang PD 四层组件

请求路径 (以单节点 Llama-3.1-8B 配置为例):

  1. 客户端把请求发到 mini-LB (默认 :8000)
  2. mini-LB 选一对 (prefill, decode) 实例,把 prompt 发到 prefill server (:30000)
  3. Prefill server 算 KV cache,通过 KV transfer backend 把 KV 推给 decode server (:30001)
  4. 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 做两次重排:

  1. Prefill 侧 gather:每个 prefill TP rank 把自己负责的那部分 KV head 切片拷贝到本 rank 的 staging buffer,然后跨 rank 做一次 all-gather 到 rank 0 的连续缓冲区
  2. Bulk RDMA transfer: rank 0 把整块缓冲区一次性 RDMA write 到 decode 侧的环形 pool
  3. Decode 侧 scatter:decode worker 从 pool 里取出连续块,按本地 KV cache pages layout 散列写入

环境变量控制

变量默认含义
SGLANG_DISAGG_STAGING_BUFFER01 启用 staging buffer
SGLANG_DISAGG_STAGING_BUFFER_SIZE_MB64每个 prefill worker 的 staging buffer 大小
SGLANG_DISAGG_STAGING_POOL_SIZE_MB4096Decode 侧的环形 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引入时间异步模型适用场景安装
Mooncake2025-04-10 首次集成多线程 + socket 通知通用 RDMA/NVLink;与 Mooncake Store 联调uv pip install mooncake-transfer-engine
NIXL2025-04-21 PR #5477 合入全异步 (内置通知)UCX / LIBFABRIC fabric;不依赖额外发现服务pip install nixl
ASCEND2025 年下半年基于 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:并行传输队列数,默认 4
  • SGLANG_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对比基线
Prefill4 节点 EP32, 1K input57,6743.3× over TP16
Prefill4 节点 EP32, 2K input54,543
Prefill4 节点 EP32, 4K input50,302
Decode9 节点 EP72, 2K input22,2825.2× over TP16
Decode9 节点 EP72, with MTP17,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 PDMooncake
项目定位推理引擎内置 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 TPStaging Buffer 把小 RDMA 折叠为大 RDMA, 2-5× 吞吐;不推荐 MLA
三种 backendMooncake (多线程) / NIXL (全异步) / ASCEND (华为 NPU)
LMSYS 96×H100prefill 57.6k tok/s/node, decode 22.3k tok/s/node,约 $0.20 / 1M output
与 Mooncake 互补SGLang 出 engine + Mooncake 出 KV pool 与全局调度,不是二选一

参考资料