跳到内容

专家并行部署

vLLM 支持专家并行 (EP),它允许将混合专家 (MoE) 模型中的专家部署在独立的 GPU 上,从而整体提高局部性、效率和吞吐量。

EP 通常与数据并行 (DP) 结合使用。虽然 DP 可以独立于 EP 使用,但与 DP 结合使用时 EP 更高效。您可以在此处阅读更多关于数据并行性的信息。

先决条件

在使用 EP 之前,您需要安装必要的依赖项。我们正在积极努力使未来的安装过程更简单

  1. 安装 DeepEP 和 pplx-kernels:按照 vLLM 关于 EP 内核的指南设置主机环境此处
  2. 安装 DeepGEMM 库:遵循官方说明
  3. 对于解耦服务:按照脚本安装 UCX 和 NIXL。

后端选择指南

vLLM 为 EP 提供了三种通信后端

后端 用例 特性 最佳应用场景
pplx 单节点 支持分块预填充 开发,最适用于节点内(单节点)部署
deepep_high_throughput 多节点预填充 带连续布局的 grouped GEMM 高吞吐量场景,预填充主导型工作负载
deepep_low_latency 多节点解码 支持 CUDA graph,掩码布局 低延迟场景,解码主导型工作负载

单节点部署

警告

EP 是一项实验性功能。参数名称和默认值未来可能会发生变化。

配置

通过设置 --enable-expert-parallel 标志启用 EP。EP 大小将自动计算为

EP_SIZE = TP_SIZE × DP_SIZE

其中:- TP_SIZE:张量并行大小(目前始终为 1)- DP_SIZE:数据并行大小- EP_SIZE:专家并行大小(自动计算)

示例命令

以下命令将部署一个 DeepSeek-V3-0324 模型,采用 1 路张量并行、8 路(注意力)数据并行和 8 路专家并行。注意力权重在所有 GPU 上复制,而专家权重则在 GPU 之间分割。它将在一个配备 8 块 GPU 的 H200(或 H20)节点上运行。对于 H100,您可以尝试部署一个较小的模型或参考多节点部署部分。

# Single node EP deployment with pplx backend
VLLM_ALL2ALL_BACKEND=pplx VLLM_USE_DEEP_GEMM=1 \
    vllm serve deepseek-ai/DeepSeek-V3-0324 \
    --tensor-parallel-size 1 \      # Tensor parallelism across 1 GPU
    --data-parallel-size 8 \         # Data parallelism across 8 processes
    --enable-expert-parallel         # Enable expert parallelism

多节点部署

对于多节点部署,请使用 DeepEP 通信内核,并选择以下两种模式之一(参见上方的后端选择指南)。

部署步骤

  1. 每个节点运行一个命令 - 每个节点都需要自己的启动命令
  2. 配置网络 - 确保正确的 IP 地址和端口配置
  3. 设置节点角色 - 第一个节点处理请求,其他节点以无头模式运行

示例:2节点部署

以下示例展示了使用 deepep_low_latency 模式在 2 个节点上部署 DeepSeek-V3-0324 模型

# Node 1 (Primary - handles incoming requests)
VLLM_ALL2ALL_BACKEND=deepep_low_latency VLLM_USE_DEEP_GEMM=1 \
    vllm serve deepseek-ai/DeepSeek-V3-0324 \
    --tensor-parallel-size 1 \               # TP size per node
    --enable-expert-parallel \               # Enable EP
    --data-parallel-size 16 \                # Total DP size across all nodes
    --data-parallel-size-local 8 \           # Local DP size on this node (8 GPUs per node)
    --data-parallel-address 192.168.1.100 \  # Replace with actual IP of Node 1
    --data-parallel-rpc-port 13345 \         # RPC communication port, can be any port as long as reachable by all nodes
    --api-server-count=8                     # Number of API servers for load handling (scaling this out to total ranks are recommended)

# Node 2 (Secondary - headless mode, no API server)
VLLM_ALL2ALL_BACKEND=deepep_low_latency VLLM_USE_DEEP_GEMM=1 \
    vllm serve deepseek-ai/DeepSeek-V3-0324 \
    --tensor-parallel-size 1 \               # TP size per node
    --enable-expert-parallel \               # Enable EP
    --data-parallel-size 16 \                # Total DP size across all nodes
    --data-parallel-size-local 8 \           # Local DP size on this node
    --data-parallel-start-rank 8 \           # Starting rank offset for this node
    --data-parallel-address 192.168.1.100 \  # IP of primary node (Node 1)
    --data-parallel-rpc-port 13345 \         # Same RPC port as primary
    --headless                               # No API server, worker only

关键配置说明

  • 无头模式:辅助节点使用 --headless 标志运行,这意味着所有客户端请求都由主节点处理
  • Rank 计算--data-parallel-start-rank 应等于先前节点的累积本地 DP 大小
  • 负载扩容:调整主节点上的 --api-server-count 以处理更高的请求负载

网络配置

InfiniBand 集群

在 InfiniBand 网络集群上,设置此环境变量以防止初始化挂起

export GLOO_SOCKET_IFNAME=eth0
这确保了 torch 分布式组发现使用以太网而非 InfiniBand 进行初始设置。

专家并行负载均衡器 (EPLB)

尽管 MoE 模型通常经过训练,使得每个专家接收相似数量的 token,但实际上 token 在专家之间的分布可能高度不均匀。vLLM 提供专家并行负载均衡器 (EPLB) 以在 EP 排名之间重新分配专家映射,从而平衡专家之间的负载。

配置

使用 --enable-eplb 标志启用 EPLB。

模型支持

目前仅支持 DeepSeek V3 架构。

启用后,vLLM 会在每次前向传播时收集负载统计数据,并定期重新平衡专家分布。

EPLB 参数

参数 描述 默认值
--eplb-window-size 用于再平衡决策跟踪的引擎步数 -
--eplb-step-interval 再平衡频率(每 N 个引擎步) -
--eplb-log-balancedness 记录平衡性指标(每个专家的平均 token 数 ÷ 每个专家的最大 token 数) false
--num-redundant-experts 每个 EP 排名除了等量分配之外的额外全局专家数量 0

专家分布公式

  • 默认:每个 EP 排名拥有 NUM_TOTAL_EXPERTS ÷ NUM_EP_RANKS 个专家
  • 带冗余:每个 EP 排名拥有 (NUM_TOTAL_EXPERTS + NUM_REDUNDANT_EXPERTS) ÷ NUM_EP_RANKS 个专家

示例命令

启用 EPLB 的单节点部署

# Single node with EPLB load balancing
VLLM_ALL2ALL_BACKEND=pplx VLLM_USE_DEEP_GEMM=1 vllm serve deepseek-ai/DeepSeek-V3-0324 \
    --tensor-parallel-size 1 \     # Tensor parallelism
    --data-parallel-size 8 \        # Data parallelism  
    --enable-expert-parallel \      # Enable EP
    --enable-eplb \                 # Enable load balancer
    --eplb-log-balancedness \       # Log balancing metrics
    --eplb-window-size 1000 \       # Track last 1000 engine steps
    --eplb-step-interval 3000       # Rebalance every 3000 steps

对于多节点部署,将这些 EPLB 标志添加到每个节点的命令中。我们建议在大规模用例中将 --num-redundant-experts 设置为 32,以便最受欢迎的专家始终可用。

解耦服务(预填充/解码分离)

对于需要严格 SLA 保证首个 token 时间和 token 间延迟的生产部署,解耦服务允许预填充和解码操作独立扩展。

架构概述

  • 预填充实例:使用 deepep_high_throughput 后端以获得最佳预填充性能
  • 解码实例:使用 deepep_low_latency 后端以获得最小解码延迟
  • KV 缓存传输:通过 NIXL 或其他 KV 连接器连接实例

设置步骤

  1. 安装 KV 连接器:使用安装脚本安装 NIXL

  2. 配置两个实例:将此标志添加到预填充和解码实例:--kv-transfer-config '{"kv_connector":"NixlConnector","kv_role":"kv_both"}

  3. 客户端编排:使用下面的客户端脚本协调预填充/解码操作。我们正在积极开发路由解决方案。

客户端编排示例

from openai import OpenAI
import uuid

try:
    # 1: Set up clients for prefill and decode instances
    openai_api_key = "EMPTY"  # vLLM doesn't require a real API key

    # Replace these IP addresses with your actual instance addresses
    prefill_client = OpenAI(
        api_key=openai_api_key,
        base_url="http://192.168.1.100:8000/v1",  # Prefill instance URL
    )
    decode_client = OpenAI(
        api_key=openai_api_key,
        base_url="http://192.168.1.101:8001/v1",  # Decode instance URL  
    )

    # Get model name from prefill instance
    models = prefill_client.models.list()
    model = models.data[0].id
    print(f"Using model: {model}")

    # 2: Prefill Phase
    # Generate unique request ID to link prefill and decode operations
    request_id = str(uuid.uuid4())
    print(f"Request ID: {request_id}")

    prefill_response = prefill_client.completions.create(
        model=model,
        # Prompt must exceed vLLM's block size (16 tokens) for PD to work
        prompt="Write a detailed explanation of Paged Attention for Transformers works including the management of KV cache for multi-turn conversations",
        max_tokens=1,  # Force prefill-only operation
        extra_body={
            "kv_transfer_params": {
                "do_remote_decode": True,     # Enable remote decode
                "do_remote_prefill": False,   # This is the prefill instance
                "remote_engine_id": None,     # Will be populated by vLLM
                "remote_block_ids": None,     # Will be populated by vLLM
                "remote_host": None,          # Will be populated by vLLM
                "remote_port": None           # Will be populated by vLLM
            }
        },
        extra_headers={"X-Request-Id": request_id}
    )

    print("-" * 50)
    print("✓ Prefill completed successfully")
    print(f"Prefill response: {prefill_response.choices[0].text}")

    # 3: Decode Phase
    # Transfer KV cache parameters from prefill to decode instance
    decode_response = decode_client.completions.create(
        model=model,
        prompt="This prompt is ignored during decode",  # Original prompt not needed
        max_tokens=150,  # Generate up to 150 tokens
        extra_body={
            "kv_transfer_params": prefill_response.kv_transfer_params  # Pass KV cache info
        },
        extra_headers={"X-Request-Id": request_id}  # Same request ID
    )

    print("-" * 50)
    print("✓ Decode completed successfully")
    print(f"Final response: {decode_response.choices[0].text}")

except Exception as e:
    print(f"❌ Error during disaggregated serving: {e}")
    print("Check that both prefill and decode instances are running and accessible")