发表时间: 2025-05 · Blog post by The SGLang Team (lmsys.org)
原文: https://lmsys.org/blog/2025-05-05-large-scale-ep
作者/机构:The SGLang Team
一句话结论
本文通过在 SGLang 框架中引入预填充-解码分离(PD Disaggregation)与大规模专家并行(EP)等优化,在 96 张 H100 GPU 上成功部署 DeepSeek-V3,实现了单节点每秒 52.3k 输入和 22.3k 输出 token 的吞吐量,成为首个在大规模下几乎追平 DeepSeek 官方性能报告的开源实现。
要解决什么问题
DeepSeek 独特的混合专家(MoE)和多头潜在注意力(MLA)架构在传统推理系统中面临严重的扩展与显存卡点。首先,传统的预填充与解码混合调度机制会导致新来的预填充任务频繁打断解码任务,且无法兼容 DeepEP(专为 MoE 设计的通信库)的调度模式——DeepEP 在预填充期需要高吞吐的常规调度,在解码期需要支持 CUDA Graph 的低延迟调度,两者无法在同一通信组内共存。其次,在处理稠密 FFN 层时,若采用常规的张量并行(TP),由于其中间维度(18,432)无法被高并行度整除,会引发严重的显存碎片化和计算不对齐,且每层需要两次 All-Reduce,通信开销极大。最后,在稀疏 FFN 层采用专家并行(EP)时,由于不同 token 对专家的调用频率差异巨大,会导致严重的 GPU 负载不均衡,部分设备闲置等待,拖慢整体流水线并拉高显存峰值。
怎么做的
核心思路是通过架构解耦、定制化并行策略与计算通信重叠来彻底释放硬件算力。首先,系统实现了预填充-解码分离(PD Disaggregation),将两个阶段分配给独立的服务器。预填充服务器计算完 KV Cache 后,通过 RDMA(远程直接内存访问)非阻塞地传输给解码服务器。这不仅消除了任务打断,还让预填充和解码能分别使用 DeepEP 的常规调度和低延迟调度,且完美兼容 DP Attention(一种消除跨设备 KV Cache 冗余的数据并行注意力机制)。其次,针对稠密 FFN 和 LM Head,系统放弃了纯 TP,改用数据并行(DP)。作者推导了单层 Transformer 在 DP 和 TP 混合下的显存占用,当假设 $\text{DP}=\text{TP}$ 时,使显存最小化的最优 TP 维度为:
由于预填充期 $k=0$ 且 token 数量极大,最优 TP 往往小于 3。因此采用 DP 不仅更省显存、避免了碎片化,还将通信开销从两次 All-Reduce 降级为一次 Reduce-Scatter 和一次 All-Gather。第三,引入双批次重叠(TBO)机制,将一个批次拆分为两个微批次,让计算(如注意力层)与 DeepEP 的通信分发过程相互掩盖;为了防止预填充期的通信阻塞 CPU,系统强制在发起通信前先向 GPU 提交计算任务。最后,引入专家并行负载均衡器(EPLB),在原有的 256 个专家外增加 32 个冗余专家(共 288 个),通过复制高频专家或打包低频专家来抹平 GPU 间的负载差异;并辅以 DisposableTensor 工具,绕过 Python 引用计数,在张量用完后立即释放 CUDA 显存。
效果如何
实验在 12 个节点(共 96 张 H100 GPU,InfiniBand 网络)上部署 DeepSeek-V3。对比基线包括:SGLang 结合 TP16 的纯张量并行方案、SGLang 结合 PD 分离与默认 EPLB 方案、SGLang 结合模拟多 Token 预测(MTP)方案,以及 DeepSeek 官方公布的 Profile 数据。在预填充阶段(4 节点,EP32,4K 上下文),本方法单节点吞吐量达 50,302 tokens/s,比 TP16 基线提升 3.3 倍,若假设负载绝对均衡,仅落后官方数据 5.6%。在解码阶段(9 节点,EP72,2K 上下文,批大小 256),单节点吞吐量达 22,282 tokens/s,比 TP16 基线提升 5.2 倍;在模拟 MTP 场景下也达到了 17,373 tokens/s,仅落后官方 6.6%。本地部署成本折合每百万输出 token 仅 0.20 美元,约为官方 API 的五分之一。作者也指出了当前局限:为了追求极致吞吐,首字延迟(TTFT)高达 2 到 5 秒,token 间延迟约 100 毫秒,暂不适合对实时性要求极高的场景;此外,受限于 96 张 GPU 的总显存,目前无法支持超长上下文,且系统暂不支持灵活配置大于 1 的 TP 维度。
DeepSeek 作为一个备受欢迎的开源大型语言模型(LLM),因其强大的性能而受到赞誉。然而,其庞大的参数规模以及采用多头潜在注意力(Multi-head Latent Attention, MLA)和混合专家(Mixture of Experts, MoE)的独特架构,要求具备先进的系统才能实现高效的大规模服务。本文阐述了 SGLang 团队如何通过系统优化,使 SGLang 的推理系统性能与 DeepSeek 官方报告的性能相匹配。
本文的核心贡献与创新点如下:
高效的并行化对于管理 DeepSeek 架构的计算复杂性和内存需求至关重要。本节概述了优化关键组件(注意力层、稠密前馈网络、稀疏前馈网络和语言模型头)的方法。每个组件都利用了定制的并行策略,以增强可扩展性、内存效率和性能。
注意力层
DeepSeek 采用多头潜在注意力(MLA)来有效建模输入序列中的复杂依赖关系。为了优化这一机制,作者实现了 DP Attention(数据并行注意力),这是一种消除跨设备 KV 缓存重复的数据并行策略,显著降低了内存开销。该方法在 SGLang v0.4 中首次引入,现已扩展为支持混合数据和张量并行,从而为高效处理小批量数据提供了灵活性。
稠密前馈网络(Dense FFNs)
尽管 DeepSeek-V3 仅使用了三个稠密 FFN 层,但其计算会显著增加峰值内存使用量,如果不加以精心管理,可能会导致系统崩溃。为了解决这个问题,作者采用了数据并行(DP)而非张量并行(TP),其优势如下:
下图左侧展示了 DP 稠密 FFN 与 DP attention 的集成。用户可以通过设置 --moe-dense-tp-size=1 来启用此功能。
稀疏前馈网络(Sparse FFNs)
在 DeepSeek-V3 的混合专家(MoE)架构中,稀疏 FFN 需要大量的专家权重,造成了严重的内存瓶颈。为了解决这个问题,作者实现了专家并行(EP),将专家权重分布在多个设备上。这种方法在保持高性能的同时有效地扩展了内存容量,尽管它也带来了诸如不规则的 all-to-all 通信和工作负载不平衡等挑战。上图右侧展示了使用 DeepEP 框架的 EP 实现,后续章节将提供有关 EP 设计和优化的进一步细节。
语言模型头(LM Head)
LM head 计算大型词表上的输出概率,这是一项资源密集型操作,传统上通过词表并行(vocabulary parallelism)来处理,以聚合来自 TP 组的 token logits。为了增强可扩展性和效率,作者采用了数据并行(DP),这与处理稠密 FFN 的策略一致。这减少了内存开销并简化了跨设备的通信,提供了一个更精简的解决方案。
LLM 推理包含两个截然不同的阶段:Prefill(预填充)和 Decode(解码)。Prefill 阶段是计算密集型的,处理整个输入序列;而 Decode 阶段是内存密集型的,管理用于 token 生成的键值(KV)缓存。传统上,这两个阶段在统一的引擎中处理,Prefill 和 Decode 批次的联合调度会引入低效性。为了应对这些挑战,SGLang 引入了 Prefill 和 Decode (PD) 分离。
统一调度的缺陷
将 Prefill 和 Decode 批次放在一起处理的传统统一引擎会导致三个重大问题:
PD 分离通过分离这两个阶段解决了上述问题,从而能够为每个阶段进行量身定制的优化。
实现细节
SGLang 中的 PD 分离设计如下图所示,它在 Prefill 服务器和 Decode 服务器之间交错执行:
在接收到输入请求后,工作流程如下进行:
这种分离确保了每个阶段都在最佳条件下运行,最大化了 GPU 资源的利用率。为了进一步提升性能,该实现包含了:
基于 DeepEP 的专家并行
DeepEP 是由 DeepSeek 团队实现的一个通信库,旨在简化 MoE 模型中的 EP。它解决了跨多个 GPU 将 token 高效路由到特定专家的挑战。通过提供优化的通信内核,DeepEP 降低了延迟并提高了吞吐量,使其成为大规模推理任务的理想选择。
DeepEP 提供两种专门的调度模式以应对不同的工作负载需求:
在 SGLang 中,DeepEP 的集成提供了自动模式(auto mode),可根据工作负载在两种调度模式之间动态选择。然而,如果没有 PD 分离,自动模式会面临一个限制:它无法在同一个通信组内同时支持正常调度(用于 Prefill)和低延迟调度(用于 Decode)。这种限制阻碍了其与 DP attention 的兼容性,而 DP attention 对于内存高效的推理至关重要。各模式的兼容性如下表所示:
| 模式 | 长输入 | 长输出 | DP Attention | CUDA Graph |
|---|---|---|---|---|
| Normal | ✅ | ❌ | ✅ | ❌ |
| Low-Latency | ❌ | ✅ | ✅ | ✅ |
| Auto | ✅ | ✅ | ❌ | ✅ |
PD 分离通过分离 Prefill 和 Decode 阶段解决了这一问题,允许在 Prefill 阶段使用正常调度,在 Decode 阶段使用低延迟调度,且两者都在 DP attention 下运行。这种集成优化了资源利用率,并通过使调度模式与每个阶段的特定需求保持一致来提高整体性能。
DeepGEMM 集成
DeepGEMM 是 DeepSeek 团队开发的另一个高效库,专门用于优化 MoE 模型中的计算。它提供了两个专门的函数来处理与 MoE 相关的矩阵乘法(Grouped GEMMs),每个函数都针对推理过程的不同阶段进行了定制。
DeepGEMM 与 DeepEP 的调度模式平滑集成:
SGLang 还集成了张量并行下的 DeepGEMM 用于 MoE 计算。此外,DeepGEMM 提供了一个高效的通用 GeMM 内核,可以通过将环境变量 SGL_ENABLE_JIT_DEEPGEMM 设置为 1 在 SGLang 中激活,为非 MoE 操作提供更高的计算效率。
双批次重叠 (Two-batch Overlap, TBO)
在多节点环境中,有限的通信带宽会显著增加整体延迟。为了应对这一挑战,作者遵循 DeepSeek 的系统设计实现了双批次重叠 (TBO)。TBO 将单个批次拆分为两个微批次(micro-batches),允许计算和通信重叠,这也通过将有效批量大小减半来降低峰值内存使用。然而,将 TBO 投入实践带来了特定的实现困难。
实现挑战:
尽管 DeepSeek 发布了 TBO 的设计框架,但存在两个轻微的实现挑战。
用于简洁实现的抽象化:
为了创建一个更易于维护和可重用的代码库,作者使用了一个由操作(operations)和让出点(yield points)组成的抽象层。这种方法简化了开发,允许编写代码时就像处理单个微批次一样,同时通过插入 yield points 战略性地暂停执行,以便让其他微批次继续。它消除了代码重复,减少了对变量后缀的潜在需求,并有效地管理了某些执行在层末尾完成而其他执行尚未完成的情况。此外,它还支持以最少的代码更改轻松适应不同的重叠区域选择或未来的增强功能(如三批次重叠)。以下是该方法的简明演示:
operations = [
self._forward_attn,
YieldOperation(), # 暂停执行以让出给其他微批次
self._forward_dispatch,
self._forward_mlp,
YieldOperation(), # 另一个暂停点
self._forward_combine,
]
# 处理单个微批次而无需重复代码
def _forward_attn(self, state):
state.hidden_states = self.self_attn(state.hidden_states, ...)
Prefill 重叠实现:
作者优化了 Prefill 阶段的启动顺序,以避免通过 DeepEP 中的 dispatch 操作阻塞 CPU,即使在使用其异步模式时也是如此。具体而言:
为了优化,作者优先将计算任务提交给 GPU,然后再启动阻塞 CPU 的通信。这确保了 GPU 在通信期间保持活跃。如下图所示,具有正确启动顺序的 TBO(以粗边框标示)避免了由阻塞 CPU 的操作(即正常调度)引起的流水线气泡。
专家并行负载均衡器 (EPLB)
在 MoE 模型中,EP 通常会导致跨 GPU 的工作负载分布不均。这种不平衡迫使系统等待最慢的 GPU 计算或通信,浪费了计算周期,并由于专家激活而增加了内存使用。随着 GPU 数量(EP 大小)的增加,不平衡问题变得更加严重。
为了解决这个问题,DeepSeek 开发了 专家并行负载均衡器 (EPLB)。EPLB 将专家分布统计数据作为输入,并计算出专家的最佳排列以最小化不平衡。用户可以分配冗余专家(例如,32 个额外专家),当与原始的 256 个专家结合时,会创建一个包含 288 个专家的池。这个池允许 EPLB 战略性地放置或复制专家——例如,将最常用的专家复制多次,或者将一个中等使用的专家与极少使用的专家组合在一个 GPU 上。
除了平衡工作负载之外,EPLB 还为并行设计提供了更大的灵活性。使用原始的 256 个专家,并行大小被限制为 2 的幂。EPLB 使用 288 个专家,支持更多样化的配置,例如并行大小为 12 或 72。
在下图中,作者通过模拟展示了规模和 EPLB 算法对不平衡问题的影响。GPU 均衡度计算为 GPU 之间 MoE 层的平均计算时间与最大计算时间之比,并使用 GPU 的 token 数量来估计其计算时间。可以看出,当系统随节点数量扩展时,利用率会下降,而启用 EPLB 可显著提高利用率。
真实服务中的 EPLB:
为了使 EPLB 有效,输入分布必须与实际服务工作负载紧密匹配。两种策略增强了这种一致性:
重平衡的实现:
SGLang 分三个阶段实现专家重平衡,以确保效率并将中断降至最低:
端到端性能
为了适应不同的工作负载需求,实验独立评估了 Prefill (P) 和 Decode (D) 阶段,假设非测试阶段资源无限,以隔离并最大化测试节点上的负载(与 DeepSeek 的设置一致)。
Profile 结果分析
消融实验:双批次重叠 (TBO)
消融实验:EPLB
一次性张量 (Disposable Tensor)
在 PyTorch 中,由于持久的对象引用,内存管理可能具有挑战性,特别是在 CUDA 内存稀缺的 GPU 密集型工作流中。例如,当一个张量在函数外部被引用时,函数内的 del 操作无法释放其内存,这会增加峰值内存使用,导致性能下降或 OOM。
SGLang 引入了 DisposableTensor 类(torch.Tensor 的子类)来解决此问题。它引入了一个 dispose() 方法来显式且立即释放张量的内存,绕过 Python 的引用计数限制。其工作原理如下:
def ffn(hidden_state: torch.Tensor, linear1: nn.Linear, linear2: nn.Linear):
intermediate_state = linear1(hidden_state)
hidden_state.dispose() # 立即释放 CUDA 内存
return linear2(nn.ReLU(intermediate_state))
# 将张量包装在 DisposableTensor 中
hidden_state = DisposableTensor(hidden_state)
hidden_state = ffn(hidden_state, linear1, linear2)
通过调用 dispose(),计算完成后 CUDA 内存会立即被释放,从而降低峰值内存使用率。
专家工作负载提取与模拟
SGLang 包含用于分析和模拟 MoE 模型中专家工作负载分布的工具集:
通过利用 PD 分离、EP 以及精心设计的并行架构,团队在 SGLang 中以卓越的性能成功复现了 DeepSeek 的推理框架。该开源实现实现了每节点每秒 52.3k 输入 token 和 22.3k 输出 token 的性能,展示了 SGLang 在大规模 LLM 推理中的强大能力。
局限性与未来工作:
本文的附录部分列出了大量与 SGLang 核心库以及 DeepEP 通信库相关的 GitHub Pull Requests(PRs)。这些 PR 涵盖了从并行架构设计、PD 分离实现、DeepGEMM/DeepEP 集成到 TBO 和 EPLB 优化的所有代码提交记录(如 PR #1970, #2925, #4068 等,以及 DeepEP#142),为开发者和社区提供了完整的重现路径和技术实现细节。