通过多节点DeepSeek部署实现更低延迟与更高吞吐量
多GPU部署同时提升MoE模型性能与扩展性
在大多数系统中,延迟和吞吐量往往是相互冲突的目标,需要在设计和部署过程中进行权衡。例如,在稠密大语言模型中,增加批大小(batch size)可以提高吞吐量,但也会增加延迟;在单机内增加张量并行(tensor parallelism)可以降低延迟,但会减少副本数量,导致吞吐量降低。
像DeepSeek-V3/R1这样的专家混合(MoE)模型最近展示了出色的模型能力和运行效率。例如,DeepSeek-V3/R1模型总参数量为671B,但在推理过程中每个token仅使用37B参数。这种模型架构为推理系统带来了机遇与挑战。
本文证明,与传统系统相反,像DeepSeek-V3/R1这样的MoE模型在大多数场景下,通过在多节点部署中使用更多的GPU,可以同时实现更高的吞吐量和更低的延迟。
部署架构
由于模型拥有大量的小型专家,部署必须跨多个设备进行。我们考虑了在单个节点上使用8xH200 GPU的单节点部署,以及在8xH100 GPU上进行的多节点部署。
这两种部署架构均利用了数据并行(Data Parallelism),并通过我们的内部请求调度器进行编排。数据并行实施涉及启动多个推理引擎实例,每个实例独立运行以服务和维护请求。通过GRPC与引擎交互的请求调度器负责尽可能均匀地分发请求,同时促进KV重用,将具有部分匹配前缀的请求发送到包含缓存的服务器。引擎实例不跨越多个节点。它们可以选择使用张量并行来跨多个设备分片注意力机制。这些实例在单节点情况下通过NVLink互连,或在多节点情况下通过InfiniBand互连,以分发和收集专家。
单节点部署配置在小批次下可提供卓越的延迟表现;然而,在高负载条件下,性能会迅速下降。
为了部署服务引擎,我们为每个节点启动一个pod,其中托管多个引擎实例。PyTorch负责设置分布式通信并协商NVSHMEM的初始化。对于通信,我们依赖于之前一篇博客文章中描述的自定义CUDA内核。这两种部署的实施几乎相同,模型会根据实现专家并行(expert parallelism)的结构选择正确的内核来使用。
并行化技术
在深入探讨性能对比之前,必须先了解使部署像DeepSeek-V3/R1这样的大型MoE模型成为可能的关键并行化策略。
张量并行
在LLM推理中,张量并行(TP)通常用于减少每个GPU的内存使用量和计算量,从而降低延迟。通常,我们可以沿行或列维度对注意力层和MLP层中的Linear投影进行分片,并沿注意力头维度对注意力操作进行分片。
利用TP,Llama-3架构在跨GPU的Linear投影和注意力操作中没有重复计算,这是一种理想的分片方法。然而,在DeepSeek-V3/R1模型中,TP无法实现这一点。
DeepSeek-V3/R1模型使用多头潜在注意力(MLA)。一个MLA层首先使用Linear投影kv_a_proj来计算潜在向量,然后使用另一个Linear投影kv_b_proj将其转换为每个注意力头的空间。由于所有注意力头共享相同的潜在向量,TP无法对潜在向量进行分片,因此所有TP Rank需要复制kv_a_proj和kv_b_proj的参数和计算。同样,由于MLA将潜在向量存储在KV缓存中,每个TP Rank存储一份相同的KV缓存副本。
尽管MLA中存在一些重复,但张量并行仍然可以部分减少计算需求,这使其对于需要高输出速度的场景具有价值。
专家并行
DeepSeek-V3/R1模型用MoE层替换了MLP层。一个MoE层有256个路由专家和一个共享专家。每个token被分发到8个不同的路由专家进行计算,并将结果加权求和。每个token也在共享专家中进行计算,结果会与来自路由专家的结果相加。
专家并行(EP)是MoE层的典型分片方法,每个GPU管理256 / EP个路由专家,同时维护共享专家的一个副本。与TP相比,EP的优势在于它可以将计算分配到更多的GPU上,从而减少每个GPU的计算和内存使用量。
在执行专家计算之前,所有GPU需要执行AllToAll通信,将token分发到相应专家所在的GPU上;在专家计算之后,需要另一次AllToAll通信来收集来自不同GPU的计算结果并执行加权求和。我们使用NVSHMEM实现了这两个AllToAll通信内核(Dispatch和Combine)的优化版本。在我们之前的博客文章中,我们详细介绍了其实施过程,我们的内核已经在GitHub上开源。
数据并行
通过EP,我们可以将MoE计算分布在128个甚至更多的GPU上。然而,MLA计算无法通过EP进行分区。此时,我们可以引入数据并行(DP)。每个DP组都有一个完整的MLA层副本。每个DP组接受不同的输入并独立执行MLA层计算。
MLA层的DP和TP可以组合,即一个DP组被拆分为多个TP Rank。MoE层的EP可以与MLA层的DP/TP组合。EP = DP * TP。例如,在16台机器上,EP128 DP32 TP4意味着将路由专家分布在128个GPU上,每4个GPU组成一个DP组,总共有32个独立的DP组。
单节点与多节点对比
DeepSeek的671B参数超过了单台8-GPU H100机器(80 GB * 8)的内存容量,但单台8-GPU H200机器可以完全容纳整个模型(141 GB * 8)。使用EP8 DP8 TP1配置,模型每个GPU大约使用100 GB内存,留出约40 GB用于KV缓存和其他中间结果。一个token占用70,272字节的KV缓存。假设每个请求有5,000个token,每个GPU可以容纳大约100个请求。
我们希望了解在不同配置下,单节点和多节点部署之间的性能差异。我们使用了一台H200机器进行单节点部署,最多使用16台H100机器进行多节点部署。对于每个部署环境,我们使用了TP 1、2、4、8的组合,以及每GPU批大小1、2、4、8、16、32、64、128的组合。我们假设每个请求的KV缓存长度为5,000个token。我们还假设多token预测(MTP)预测1个额外token(即每个请求的查询长度为2),并保守地假设接受率为60%。下图显示了不同配置的吞吐量和输出速度。
横轴代表每个请求的输出速度(tokens/s)。纵轴使用对数刻度显示每台机器的吞吐量(tokens/s)。我们用不同颜色的线条标出了每个EP配置的帕累托前沿(Pareto Frontier)。
在对输出速度要求极高的场景中,使用单节点EP8 DP1 TP8且批大小为1,可以实现超过100 tokens/s的输出速度,但吞吐量极低,等同于输出速度。在这种情况下,整个批次只有2个token,最多可分发给2*8=16个专家,最多激活57B参数。
在80-40 tokens/s的输出速度范围内,随着吞吐量增加,输出速度会显著下降。相比之下,在相同的输出速度下,EP128的吞吐量比单节点部署高出约5倍。
这一现象可以通过分析单节点部署的行为来解释:增加批大小直接导致激活专家的数量增加。当批大小为1时,每个GPU平均激活的专家数量为2 * 8 / 8 = 2。当批大小足够大时,所有专家都被激活,意味着每个GPU激活256 / 8 = 32个专家。激活更多的专家意味着GPU需要从内存中读取更多参数,显著增加了内存带宽压力。由于大语言模型的解码阶段已经受限于内存带宽而非计算性能,在单节点部署中增加批大小会显著降低输出速度。
对比四种多节点部署配置(EP16、EP32、EP64和EP128)发现,更高的EP值将帕累托前沿向吞吐量和输出速度同时提升的方向移动。
使用更高的EP值意味着每个GPU分配的专家较少。例如,EP128意味着每个GPU负责256 / 128 = 2个专家,因此内存带宽压力显著降低。换句话说,通过使用更大的EP值,我们有效地获得了更多的内存带宽。当每GPU批大小小于64时,增加批大小不会显著影响专家计算速度,因为增加输入数量不会显著增加内存带宽压力。因此,我们观察到在使用EP128时,增加批大小对输出速度的影响并不显著。
有趣的是,在更大的批大小(每GPU 64个请求)下,我们观察到了一个新现象:单节点部署的吞吐量略高于多节点部署。部分原因是节点内NVLink的带宽高于节点间InfiniBand的带宽。另一部分是由于我们实施中的局限性。稍后我们将更详细地分析这一现象。
由于内存容量限制,EP8 DP8 TP1配置无法达到每GPU 128的批大小,因此在追求更高吞吐量的场景中,多节点部署仍然是更好的选择。
计算与通信重叠
正如上面关于专家并行所简要介绍的,在MoE层通信期间,GPU处于空闲状态。为了减少浪费并降低延迟,我们需要寻找数据独立的计算任务来填充这段空闲时间。
上图的上半部分显示了一层的计算流程。MoE计算依赖于Dispatch,下一层的计算依赖于Combine的结果。
我们将共享专家放置在每个GPU上。这样,共享专家计算就不需要AllToAll通信。因此,我们可以在Dispatch Send之后立即进行共享专家计算,然后等待Dispatch Recv完成。我们将这种重叠方案称为“Dispatch Overlap”。
Dispatch Overlap提供了直接的实施方式和广泛的适用性。该技术可以在所有EP大小和批大小下隐藏共享专家计算时间。
为了进一步增加计算和通信的重叠,我们使用了DeepSeek技术报告中提到的微批处理(micro batching)来打破数据依赖。如图下半部分所示,我们将一个Transformer层的计算分为5个阶段:
阶段 1:InputNorm, QKVProj, AppendKV, BMM
阶段 2:BMM, Attn, OProj, PostNorm, Gate
阶段 3:Dispatch Send, Shared Expert
阶段 4:Dispatch Recv, MoE, Combine Send
阶段 5:Combine Recv
在前3个稠密Transformer层中,我们使用整个批次。在随后的58个MoE Transformer层中,我们将批次均匀划分为两个微批次。这两个微批次交替执行,偏移3个阶段。由于这两个微批次之间没有数据依赖,我们可以在Dispatch Send之后和Combine Send之后切换到另一个微批次的计算。
延迟分解
接下来,我们通过实验比较重叠的影响,并比较单节点部署EP8与多节点部署EP128之间的性能差异。为了便于比较,我们在接下来的实验中使用H100 GPU。我们使用了TP1、每GPU 128的批大小、每个请求2的查询长度和5000的KV缓存长度。
上图显示了在一个MoE Transformer层上花费的总时间以及不同类型内核的延迟占比。除Dispatch、Combine和GroupGEMM外,由于批大小相同,其他内核在EP8、EP128 NoOverlap和EP128 DispatchOverlap系列中的执行时间应该是相等的。
重叠(Overlapping)
首先比较三种重叠方法的效果。NoOverlap总共花费2667µs,DispatchOverlap花费2651µs,节省了16µs,仅占0.6%。MicroBatch显示出非常显著的改善,仅花费1896µs,实现了29%的加速。Dispatch和Combine的时间都显著减少。Dispatch从593µs减少到367µs,Combine从1012µs减少到237µs。
请注意,对于计算内核,将128的批大小拆分为两个64的批次会增加总执行时间。因此,虽然通信花费的时间减少了1001µs,但总时间仅减少了771µs。我们将在下一节中使用Roofline模型解释原因。
因此,微批处理并不总是能提高性能。
上图显示了对于批大小为4-128的Microbatch相比DispatchOverlap的性能提升。当批大小小于32时,Microbatch会使性能下降5%-40%。当批大小大于或等于32时,Microbatch可使性能提高10%-35%。
EP8与EP128对比
让我们回到上一张图,对比EP8和EP128 Microbatch。EP8总共花费1802µs,略少于EP128的1896µs。除了上面提到的Microbatch带来的内核执行时间增加外,主要差异在于用于MoE计算的GroupGEMM以及两个通信内核(Dispatch和Combine)。
EP8的GroupGEMM花费555µs,而EP128的GroupGEMM花费270µs,减少了一半。这是多节点部署的核心优势。
不幸的是,通信花费的时间增加了213µs,这极大地抵消了GroupGEMM的优势。在我们通信内核的单独性能测试中,我们发现它们只能达到Infiniband带宽的一半。我们将继续优化我们的通信内核。
另一个显著滞后的内核是GEMM。Microbatch使GEMM增加了95µs。我们将在下面的Roofline部分更深入地分析GEMM。我们认为当前的GEMM实施尚未达到最佳性能。
Roofline模型
Roofline模型是分析内核性能的一个好工具。其横轴是算术强度(Arithmetic Intensity),即浮点运算数(FLOP)与内存I/O字节数之比。横轴值可以直接根据内核的语义计算得出。纵轴表示实现的性能,通过将FLOP除以基准测试延迟来计算。
内核性能的理论上限直接取决于GPU的规格。H100的FP8峰值性能为1979 TFLOP/s,在Roofline模型中表现为一条水平线。H100的内存带宽为3.35 TB/s,表现为一条穿过原点的直线的斜率。这两条线分别给出了计算密集型和内存密集型内核的性能限制。
下面我们讨论GroupGEMM和GEMM内核的性能。
GroupGEMM
MoE中的GroupGEMM内核执行以下计算:共有g个组,第i个组有m_i个token,执行[m_i, k] x [k, n] -> [m_i, n]的矩阵乘法。在性能测试中,我们假设每组中的token数量相同,记为m_i = m。那么GroupGEMM的FLOP计数为2 * g * m * k * n,内存I/O字节数为g * (m * k + n * k + m * n)。
在DeepSeek-V3/R1模型中,有256个专家,每个token被分发到8个专家进行计算。假设批大小为128,查询长度为2,使用EP128 DP128配置,每个专家平均接收到的token数量(即m)为128 * 2 * 8 * 128 / 256 = 1024。同样,我们可以计算其他配置和批大小的m。
我们使用DeepGEMM的GroupGEMM实施进行性能测试。测试点涵盖了TP1下EP8、EP16、EP32、EP64、EP128配置与1-128批大小的组合。
上图显示了不同EP配置下GroupGEMM的Roofline模型。不同的EP对应不同数量的组。该图显示了几乎重叠的性能线,表明GroupGEMM性能主要由token总数(表示为g * m)决定。
星号标记了每个EP配置下每GPU 128批大小对应的数据点。比较这些星号数据点,我们可以看到,随着EP增加(同时DP同步增加),每个专家的token数量m也增加。在EP8时,m=128,而在EP128时,m=2048。
随着m增加,算术强度也增加。在大多数配置中,GroupGEMM受内存带宽限制,因此增加m可提高性能。
GEMM
GEMM内核对应模型中的Linear投影,例如Q/K/V/O投影。对于[m, k] x [k, n] -> [m, n]的矩阵乘法,FLOP计数为2 * m * k * n,内存I/O字节数为m * k + n * k + m * n。我们还可以测试批大小1-128的延迟。
上图显示了不同EP配置下GEMM的Roofline模型。我们可以看到GEMM性能受内存带宽限制。随着批大小增加,算术强度也增加,从而提高了性能。
Microbatch
使用微批处理时,我们将批次均匀地分为两部分。从上面的两张图中,我们可以看到当m变为m/2时,矩阵乘法的效率会降低。因此,执行两次大小为m/2的矩阵乘法比执行一次大小为m的矩阵乘法花费的时间更长。
多token预测
在整篇文章中,我们假设使用多token预测(MTP)进行投机解码。MTP将每个请求的查询长度从1更改为2。对于矩阵乘法,这相当于将m更改为m * 2,从而提高了矩阵乘法效率。另一方面,如果我们绘制MLA的Roofline模型,会发现增加查询长度会显著提高MLA内核效率。
因此,MTP的使用在模型效率中起着重要作用。
实现与优化
在本节中,我们将介绍DeepSeek-V3/R1模型的一些实施和优化细节。
量化
DeepSeek-V3/R1使用逐块(per-block)量化方案在FP8上原生训练,权重静态量化,激活动态量化。量化比例因子并非静态按通道或矩阵计算,而是在激活的128元素向量和矩阵的128x128元素块上计算,限制了因量化导致的精度下降。
在Perplexity,我们依赖CUDA和Triton内核的混合来支持推理,其中CUDA用于性能最敏感且不常修改的内核(如注意力机制和GEMM),而Triton则实现了广泛的激活、归一化和实用程序内核。Triton使我们能够快速使内核适应块量化方案。
对于Linear和MoE层,我们将Deep GEMM内核与我们自己的Triton GEMM内核混合使用,因为我们注意到对于某些矩阵维度和小批大小,Split-K可提供更低的延迟。如果未量化层执行(M, K) x (K, N)乘法,则块量化需要(M x ceil_div(K, 128)) x (ceil_div(K, 128), ceil_div(N, 128))的缩放因子。对于块量化,激活的缩放因子是动态计算的,而不是预先校准的。由于激活缩放因子仅沿K维度聚合,而不沿M维度聚合,内核仅需稍作改动即可支持该方案。
DeepSeek-V3/R1使用的SiLU激活函数需要进行重大更改以支持CUDA图、块量化和动态路由的token计数。块量化可能会带来问题,因为它引入了水平归约(reductions),但内核已经将激活沿着它们的隐藏维度分块为1024个元素。在一个块内,待量化的张量被进一步划分为128个元素的小块以计算最大绝对值,Triton生成高效的跨warp最大值归约,增加了极小的开销。
为了在CUDA图下支持MoE路由,内核必须感知路由信息,指示每个专家的token数量,而不是根据为容纳token计数上限而分配的缓冲区大小来调度工作。我们无法根据输入张量维度拆分问题,因此我们启动固定数量的持久化内核,这些内核读取路由信息以确定填充了多少token,并动态地在它们之间分配处理激活的工作。
我们已经将部分内核向上游合并到了FlashInfer项目中,未来我们将开源更多的代码。
MLA层
我们使用FlashInfer进行MLA计算。FlashInfer支持灵活的页表设置和极高的性能。
我们将q_a_proj和kv_a_proj融合为一个qkv_a_proj。延迟从15.4 µs + 14.8 µs = 30.2 µs减少到16.7 µs。
我们将kv_b_proj分解为两个矩阵k_b_proj和v_b_proj。我们编写了一个FP8块量化BMM内核用于与这两个矩阵相关的计算。
Cuda图
Cuda图可以显著减少内核启动开销,这对于性能至关重要。我们为每个批大小创建一个Cuda图。
在开发我们的AllToAll内核之前,我们使用torch.all_to_all_single()进行AllToAll通信。该操作要求所有GPU使用相同的批大小。然而,不同的DP组可能会运行不同的批大小。
为确保all_to_all_single()与使用不同批大小的不同DP组兼容,我们在每次模型运行前首先使用allreduce()操作来获取所有DP组中的最大批大小。然后,我们让所有DP组使用此批大小运行。
尽管这种方法确保了我们可以使用Cuda图,但它有三个缺点。首先,它需要额外的allreduce()操作。其次,具有较小批大小的DP组被迫进行填充。第三,它使我们的实施代码变得复杂。
在实施我们自己的AllToAll内核后,我们不再要求所有GPU使用相同的批大小。因此,我们不再需要执行额外的allreduce()操作或填充批大小。
MoE路由器
MoE路由器是在Triton中实施的,依赖于从标准库派生的修改后的排序算法,该算法同时跟踪已排序元素的索引。该实施在所有MoE模型中共享,因为Mixtral路由是DeepSeek路由的一个特例,其中Top-K组与所有专家的组相同。稀疏内核直接使用Top-K索引和分数,而依赖于all-to-all的稠密Dispatch/Combine方案则要求路由信息按专家聚合,而不是按token基础。
未来工作
在未来的工作中,我们计划进一步优化DeepSeek模型的性能。
下一个最重要的优化是预填充分解(Prefill Disaggregation)。DeepSeek-V3/R1模型的预填充(Prefill)阶段和解码(Decode)阶段具有截然不同的计算特性。两者都可以使用不同的优化策略和部署方案。
对于MLA层,在解码阶段,我们使用矩阵吸收(Matrix Absorption)来减少MLA计算的FLOP计数。在预填充阶段,首先将潜在向量投影到K/V空间,然后以多头注意力(MHA)形式进行计算,效果会更好。
如果预填充和解码在同一个GPU上运行,为了减少预填充对解码输出速度的影响,我们通常使用分块预填充(chunked prefill)将查询划分为多个预填充块。由于KV缓存存储了潜在向量,将MLA转换为MHA形式变得困难。
对于MoE层,在解码阶段,我们使用尽可能大的EP和DP,以增加每个专家的输入token数量,从而提高GroupGEMM性能。在预填充阶段,由于token数量已经足够大,GroupGEMM已经是计算受限的。因此,对于预填充,我们可以使用较小的EP和DP。
如果预填充和解码在同一个GPU上运行,只要有任何DP组正在执行预填充,所有GPU上的MoE层延迟就会增加,从而显著影响解码输出速度。
除了预填充分解外,我们还计划优化以下方面:
AllToAll性能:我们的AllToAll内核目前只能达到Infiniband带宽的1/3。我们将继续优化该内核。
EAGLE风格的投机解码:在上面的数据中,我们假设使用投机解码预测1个token。EAGLE可以使用树结构预测多个token,提高接受长度,这可以显著增加输出速度。
GEMM内核:在之前展示的Roofline模型中,我们可以发现GEMM内核的效率仍远未达到理论极限。我们将继续优化该内核。
GB200 NVL72:在NVIDIA最新的GB200 NVL72解决方案中,72个Blackwell GPU通过高速NVLink互连。对于MoE架构模型,这是一个非常大的机遇和挑战。
结论
DeepSeek MoE模型的多节点部署实现了稠密LLM通常无法实现的目标:同时提升吞吐量和延迟。通过将专家分布在更多的GPU上,我们减少了每台设备的内存带宽压力,从而实现更快的处理和更高的系统吞吐量。我们的实验显示,与单节点部署相比,EP128配置在相同的输出速度下吞吐量高出5倍。
微批处理等计算-通信重叠技术显著减少了多节点通信开销,我们的实施显示出高达40%的加速。我们的自定义AllToAll通信内核和优化的内核实施使671B参数模型的有效部署成为可能。
随着MoE架构因其强大的能力而受到欢迎,这些部署策略为高效扩展此类模型提供了宝贵的见解。
参考文献
引用
@online{perplexity2025LowerLatencyAndHigherThroughputWithMultiNodeDeepseekDeployment,
author = {{Perplexity} and Lequn Chen and Nandor Licker and Erin McKnight},
title = {{Lower latency and higher throughput with multi-node DeepSeek deployment}},
date = {2025-04-18},
url = {https://perplexity.coolkeypoint.com/hub/blog/lower-latency-and-higher-throughput-with-multi-node-deepseek-deployment}
}