vLLM 专家并行(EP)部署完全指南:从 DeepEP 内核到 EPLB 负载均衡与 PD 分离

发布时间:2026/9/7 1:49:21
vLLM 专家并行(EP)部署完全指南:从 DeepEP 内核到 EPLB 负载均衡与 PD 分离 vLLM 专家并行EP部署完全指南从 DeepEP 内核到 EPLB 负载均衡与 PD 分离【免费下载链接】vllmA high-throughput and memory-efficient inference and serving engine for LLMs项目地址: https://gitcode.com/GitHub_Trending/vl/vllm本文是 vLLM 中 Expert ParallelEP专家并行部署的实战指南。EP 允许将 Mixture-of-ExpertsMoE模型中的各个专家部署到不同的 GPU 上从而显著提升 MoE 模型推理的局部性、效率与整体吞吐。文章覆盖前置依赖安装、单/多节点部署命令、Backend 选择、EPLB 负载均衡与基于 EP 的 Prefill/Decode 分离部署并结合 vLLM 源码配置定义与分布式 EPLB 实现讲解底层原理。读完本文你将能够独立配置并启动一套单节点或多节点的 EP 推理服务并能针对负载倾斜、跨节点通信与低延迟场景进行调优。什么是专家并行EP为何 MoE 模型需要它vLLM 支持 Expert ParallelismEP核心思想是把 MoE 模型中的专家权重切分到不同的 GPU 上。对于 DeepSeek、Qwen3-MoE、Mixtral 这类专家众多、每 token 只激活少数专家的稀疏模型EP 相比把所有专家权重复制到每张卡上能大幅节约显存每个 EP rank 只保存一部分专家并通过专家间通信换取算力局部性。在 数据并行部署指南 中 vLLM 明确说明默认情况下未开启 EPMoE 专家层会构成一个大小为DP × TP的张量并行组而开启--enable-expert-parallel后专家层改用真正的专家并行。EP 通常与数据并行DP配合使用DP 可以独立于 EP 单独存在但 EP 与 DP 结合时才最高效。原因在于 EP 只切分了专家层注意力层尤其对于 DeepSeek 这类 MLA 架构更适合用 DP 复制权重、各自处理不同的请求批次从而最大化吞吐。两者配合还能让 DP rank 之间在每次 forward 时同步专家通信保证推理结果一致。前置条件环境与依赖安装使用 EP 之前需要安装必要的依赖。仓库目前通过 tools/ep_kernels 提供一套分步安装脚本依赖分为 Python 库与系统驱动两个层面后者需要 root 权限。安装 DeepEP 与 DeepGEMM安装 DeepEP参照 EP 内核安装指南 配置宿主环境。建议用法如下Hopper 架构H100/H200 等TORCH_CUDA_ARCH_LIST9.0 bash install_python_libraries.shBlackwell 架构TORCH_CUDA_ARCH_LIST10.0 bash install_python_libraries.sh该脚本install_python_libraries.sh会自动完成下载并解压指定版本 NVSHMEM默认3.3.24同时支持 CUDA 12 与 13见 install_python_libraries.sh从 DeepSeek 官方仓库克隆并 checkout 到固定 commit 的 DeepEP 源码再做本地安装或打成 wheel。脚本默认把工作目录建在$(pwd)/ep_kernels_workspace也支持--mode wheel等参数。安装 DeepGEMM 库DeepEP 依赖 DeepGEMM 的轻量级 GEMM 能力需按其官方安装说明单独安装。多节点/分离部署如使用 gdrcopyGPU Direct RDMA 加速 KV 传输运行仓库提供的 install_gdrcopy.sh 脚本例如install_gdrcopy.sh ${GDRCOPY_OS_VERSION} 12.8 x64脚本需 root 权限uuarch参数只接受x64或aarch64内部会按 OS 版本与 CUDA 版本拼接 NVIDIA 官方软件源 URL 并安装libgdrapi的 deb 包。可用 OS 版本需要以12.8对应的 CUDA redist 目录列表为准。NCCL 版本要求CUDA 13deepep_v2后端依赖 NCCL 2.30.4。PyTorch 自带/传递依赖的 NCCL 往往更旧例如 PyTorch 2.11 会 pinnvidia-nccl-cu132.28.9因此必须在编译 DeepEP 之前升级 NCCL 并在运行时保持使用 uv写入 override 文件后export UV_OVERRIDE/tmp/nccl-override.txt使用 pippip install vllm后再执行pip install nvidia-nccl-cu132.30.4 --no-deps。升级后可用python -c from vllm.utils.import_utils import has_deep_ep_v2; print(has_deep_ep_v2())验证详见 EP 内核指南。多节点系统驱动配置需要 root多节点部署还需要第二步在每个 GPU 节点上以 root 运行tools/ep_kernels/configure_system_drivers.sh配置 NVIDIA 驱动以启用 IBGDA随后reboot 重启节点update-initramfs可能要花几分钟。注意该步骤只能作用于宿主机无法打包进 Python 发行包这也是 DeepEP 依赖被拆成两步安装的根本原因。Backend 选择指南用--all2all-backend切换 EP 通信内核EP 的专家间通信本质是 All-to-AllA2AvLLM 提供了多个通信后端通过--all2all-backend选择。从源码看可选的枚举定义在 parallel.py 的All2AllBackend字面量中除了文档表格列出的四个还包含deepep_v2、mori_high_throughput、mori_low_latency、nixl_ep以及naive、pplx等内部/别名后端。文档给出的核心推荐矩阵如下Backend适用场景特性最适合allgather_reducescatter默认后端基于 allgather/reducescatter 原语的通用 A2A通用场景兼容任意 EPDP 组合deepep_high_throughput多节点 prefillGrouped GEMM 连续布局针对 prefill 优化prefill 密集、追求高吞吐deepep_low_latency多节点 decode支持 CUDA graph、masked 布局针对 decode 优化decode 密集、追求低时延flashinfer_nvlink_one_sidedMNNVL 系统FlashInfer 单边 A2A 策略跨节点 NVLink高吞吐负载flashinfer_nvlink_two_sidedMNNVL 系统FlashInfer 双边 A2A 策略节点间有 NVLink 互连的系统在 ParallelConfig 中all2all_backend默认值是allgather_reducescatter。若显式传入了pplx/naive等旧值配置校验会把它们规整回allgather_reducescatter。单节点 EP 部署基本配置与 EP_SIZE 计算单节点上启用 EP 只需加--enable-expert-parallel开关。EP 大小由 vLLM 自动按以下公式计算EP_SIZE TP_SIZE × DP_SIZE其中TP_SIZE张量并行大小--tensor-parallel-sizeDP_SIZE数据并行大小--data-parallel-sizeEP_SIZE专家并行大小自动计算。在源码层面ParallelConfig.enable_expert_parallel的注释写得很直白Use expert parallelism instead of tensor parallelism for MoE layers对 MoE 层使用专家并行而非张量并行见 parallel.py 中对应字段。开启 EP 后各层的行为差异启用 EP 后MoE 模型的不同层会走不同并行策略层类型行为采用的并行专家MoE层在所有 EP rank 上切分大小为TP × DP的 EP注意力层行为取决于 TP 大小见下方说明注意力层的并行细节当TP 1时注意力权重在所有 DP rank 间复制纯数据并行每个 DP rank 独立处理一批请求当TP 1时注意力权重在每个 DP 组内部按张量并行在 TP rank 间切分。例如TP2, DP4共 8 张 GPU时专家层构成大小为 8 的 EP 组专家均匀分布在全部 8 张 GPU 上注意力层则在 4 个 DP 组内各自使用 TP2 切分。与数据并行部署的关键区别若不开启--enable-expert-parallelMoE 层会像稠密模型一样使用张量并行构成大小为TP × DP的 TP 组开启后专家层切换为专家并行对 MoE 模型可获得更好的效率与局部性。值得注意data_parallel_size参数本身也影响着 MoE 层的切分在 ParallelConfig 的 docstring 中明确 MoE layers will be sharded according to the product of the tensor, prefill-context, and data parallel sizes即 MoE 层按TP × PCP × DP的总乘积切分。示例命令下面的命令在单机 8 卡上以 1 路张量并行、8 路注意力数据并行、8 路专家并行服务DeepSeek-V3-0324注意力权重在所有 GPU 上复制专家权重在 GPU 间切分。该配置适合 8 卡的 H200或 H20节点若用 H100建议换更小模型或参考下面多节点章节。# 单节点 EP 部署 vllm serve deepseek-ai/DeepSeek-V3-0324 \ --tensor-parallel-size 1 \ # TP1不做张量并行 --data-parallel-size 8 \ # DP88 个数据并行进程 --enable-expert-parallel # 开启专家并行上例中的#注释仅为解释参数实际执行前请将其移除\续行符后不能跟注释。多节点 EP 部署多节点场景下专家通信跨越节点边界此时应使用 DeepEP 通信内核并按负载特征选择deepep_high_throughputprefill 密集或deepep_low_latencydecode 密集模式见上文 Backend 选择指南。部署步骤每个节点各自执行一次启动命令——多节点 EP 没有统一的中心化启动器每个节点都要跑一个vllm serve配置好网络——确保节点间 IP 与端口可达端口建议使用未被占用的高位端口设置节点角色——第一个节点主节点处理外部请求其余节点以--headless无头模式运行、只承担 worker 角色。2 节点部署示例以下示例用deepep_low_latency模式把DeepSeek-V3-0324部署到 2 个节点每节点 8 卡、共 16 个 DP rank# 节点 1主节点 —— 接收并处理请求 vllm serve deepseek-ai/DeepSeek-V3-0324 \ --all2all-backend deepep_low_latency \ --tensor-parallel-size 1 \ # 每节点 TP1 --enable-expert-parallel \ # 开启 EP --data-parallel-size 16 \ # 全集群 DP 总量为 16 --data-parallel-size-local 8 \ # 本节点本地 DP8每节点 8 卡 --data-parallel-address 192.168.1.100 \ # 主节点实际 IP --data-parallel-rpc-port 13345 \ # DP 间 RPC 端口所有节点需可达 --api-server-count8 # API server 数量建议对齐本地 rank 数 # 节点 2从节点 —— 无头模式不挂 API server vllm serve deepseek-ai/DeepSeek-V3-0324 \ --all2all-backend deepep_low_latency \ --tensor-parallel-size 1 \ # 每节点 TP1 --enable-expert-parallel \ # 开启 EP --data-parallel-size 16 \ # 全集群 DP 总量为 16 --data-parallel-size-local 8 \ # 本节点本地 DP8 --data-parallel-start-rank 8 \ # 本节点起始 rank前序节点本地 DP 累加值 --data-parallel-address 192.168.1.100 \ # 指向主节点 IP --data-parallel-rpc-port 13345 \ # 与主节点相同 RPC 端口 --headless # 无头仅 worker不提供 API关键配置说明无头模式从节点带--headless所有客户端请求统一由主节点处理rank 计算--data-parallel-start-rank应等于前序所有节点本地 DP 大小之和本例节点 1 本地 DP8故节点 2 从 rank 8 开始负载扩容主节点上调大--api-server-count建议对齐本地 rank 数即 8可分担更高请求负载。API server 可多进程扩展但仅限主节点内部。网络配置注意事项InfiniBand 集群在 IB 网络环境中为防止初始化阶段挂起务必设置export GLOO_SOCKET_IFNAMEeth0该变量强制 torch distributed 的组发现走以太网而非 InfiniBand从而避免建立初始控制面时卡死。Expert Parallel Load BalancerEPLB应对专家负载倾斜MoE 模型虽然按训练目标每个专家接收大致等量的 token但实际推理中 token 在不同专家上的分布可能高度倾斜——热门专家拥塞、冷门专家闲置导致 EP 组内某些 rank 成为瓶颈。vLLM 提供 Expert Parallel Load BalancerEPLB通过重映射专家到 EP rank 的分布来均衡专家负载。EPLB 的实现位于 vLLM 的分布式模块如 vllm/distributed/eplb核心状态机EplbState见 eplb_state.py负责统计各层负载、计算新的专家映射compute_logical_maps、同步权重并触发重排rearrange支持同步与异步两种执行路径。启用与工作方式通过--enable-eplb开启。开启后 vLLM每次 forward 都会收集负载统计并周期性对专家分布做再均衡。注意 EPLB 只有在enable_expert_parallelTrue时才有效配置校验会显式报错enable_expert_parallel must be True to use EPLB见 parallel.py。EPLB 参数表EPLB 用--eplb-config传入一段 JSON 字符串配置其字段定义对应源码中的EPLBConfigpydantic 模型默认值与校验逻辑见 parallel.py 中EPLBConfig类。各参数含义如下参数说明默认值window_size记录专家负载的滑动窗口用于做均衡决策的引擎步数须 01000step_interval再均衡执行频率每 N 个引擎步触发一次须 03000log_balancedness是否记录均衡度指标每个专家平均 token ÷ 最大 token 数开启会带来通信开销故默认关闭falsenum_redundant_experts在均分之外、每个 EP rank 额外持有的全局冗余专家数0use_async使用非阻塞 EPLB 以降低时延开销truepolicy均衡策略类型目前仅defaultdefaultcommunicator专家权重传输后端torch_nccl、torch_gloo、pynccl、nixl或null自动选择null源码层面 EPLBConfig 还带一些重要约束模型校验器_validate_eplb_config会拦截非法组合use_asyncTrue时只支持defaultpolicyuse_asyncTrue时不能用torch_nccl/pynccl通信器会与 NCCL 多流冲突报错应改用torch_gloo或nixl或不指定以自动选择communicatorNone时自动选择优先nixl若环境可用否则回退到pynccl/torch_gloo根据是否有 CUDA 等条件推断。示例用 JSON 配置vllm serve Qwen/Qwen3-30B-A3B \ --enable-eplb \ --eplb-config {window_size:1000,step_interval:3000,num_redundant_experts:2,log_balancedness:true}如果你更倾向使用单个参数而非整段 JSON也可以逐项指定vllm serve Qwen/Qwen3-30B-A3B \ --enable-eplb \ --eplb-config.window_size 1000 \ --eplb-config.step_interval 3000 \ --eplb-config.num_redundant_experts 2 \ --eplb-config.log_balancedness true专家分布公式默认无冗余每个 EP rank 持有NUM_TOTAL_EXPERTS ÷ NUM_EP_RANKS个专家开启冗余后每个 EP rank 持有(NUM_TOTAL_EXPERTS NUM_REDUNDANT_EXPERTS) ÷ NUM_EP_RANKS个专家。冗余专家相当于给热门专家预留下的备用副本EPLB 可以随时把某个专家迁到负载更低的 rank 上。从源码看冗余专家还支持跨层共享张量的传播逻辑_propagate_shared_tensors实现上会复用专家映射管理见vllm/model_executor/layers/fused_moe/expert_map_manager.py等相关文件。显存开销预算EPLB 的冗余专家必须放进 GPU 显存因此不适合显存吃紧或 KV cache 空间宝贵的环境。其显存增量可按以下公式估算NUM_MOE_LAYERS × BYTES_PER_EXPERT × (NUM_TOTAL_EXPERTS NUM_REDUNDANT_EXPERTS) ÷ NUM_EP_RANKS对 DeepSeek-V3 而言每个 EP rank 额外挂 1 个冗余专家的开销约为2.4 GB。这也是 EPLB 参数建议要保守的原因——num_redundant_experts每加 1都会按公式放大全集群显存占用。完整示例命令单节点启用 EPLB# 单节点 EPLB 负载均衡 vllm serve deepseek-ai/DeepSeek-V3-0324 \ --tensor-parallel-size 1 \ # TP1 --data-parallel-size 8 \ # DP8 --enable-expert-parallel \ # 开启 EP --enable-eplb \ # 开启负载均衡 --eplb-config {window_size:1000,step_interval:3000,num_redundant_experts:2,log_balancedness:true}多节点部署时把上述 EPLB 参数原样加到每个节点的启动命令即可。大规模场景下官方建议把num_redundant_experts设到 32--eplb-config {num_redundant_experts:32}这样最热门的专家几乎总是有冗余副本可用避免重排期间的性能抖动。高级配置与排障性能优化DeepEP 内核的取舍deepep_high_throughput与deepep_low_latency是针对分离部署prefill/decode 分开分别优化的对混合负载同一批里既有 prefill 又有 decode可能表现不佳需按负载构成选择Dual Batch OverlapDBO加--enable-dbo让 All-to-All 通信与计算重叠通过将 batch 拆成微批次流水化。详见 Dual Batch Overlap 设计文档。对应ParallelConfig.enable_dbo及ubatch_size、dbo_decode_token_threshold默认 32、dbo_prefill_token_threshold默认 512等参数异步调度实验性可尝试--async-scheduling让调度与模型执行重叠。常见错误排查报错信息成因与对策non-zero status: 7 cannot register cq buf使用 InfiniBand/RoCE 时宿主机与 Pod 的ulimit -l锁内存上限未设为 unlimitedinit failed for transport: IBGDA缺少 InfiniBand GDA 内核模块。在每个 GPU 节点执行 configure_system_drivers.sh 后重启该操作同时修复NVSHMEM API called before NVSHMEM initialization has completed错误NVSHMEM peer disconnect通常是网络配置问题。若走 Kubernetes需确保每个 Pod 设置hostNetwork: true与securityContext.privileged: true以访问 InfiniBandBenchmarking 技巧EP 基准测试中最怕路由倾斜导致结果不可复现。可用两个模拟器开关让 token 均匀路由到各 EP rankVLLM_MOE_ROUTING_SIMULATION_STRATEGYuniform_randomVLLM_RANDOMIZE_DP_DUMMY_INPUTS1Disaggregated ServingPrefill/Decode 分离对于需要严格 SLATTFT 与 ITL的生产环境分离式服务允许 prefill 与 decode 各自独立扩缩容——这是 DeepEPhigh_throughput/low_latency双内核设计的主要使用场景。架构概览Prefill 实例使用deepep_high_throughput后端追求 prefill 吞吐Decode 实例使用deepep_low_latency后端追求 decode 低时延KV Cache 传输实例间通过 NIXL 或其他 KV Connector 连接传递 KV cacheNixlConnector。搭建步骤安装 gdrcopy/ucx/nixl为获得最大性能用 install_gdrcopy.sh 装 gdrcopy例如install_gdrcopy.sh ${GDRCOPY_OS_VERSION} 12.8 x64。不装 gdrcopy 也能工作——只要pip install nixl只是 KV 传输性能更低。nixl与ucx会作为 pip 依赖自动安装非 CUDA 平台若要编译非 CUDA 版 UCX 的 nixl可运行 install_nixl_from_source_ubuntu.py 脚本。配置两侧实例prefill 与 decode 实例都要加 KV 传输配置--kv-transfer-config {kv_connector:NixlConnector,kv_role:kv_both}也可以指定一个或多个 NIXL_Backend例如--kv-transfer-config {kv_connector:NixlConnector,kv_role:kv_both, kv_connector_extra_config:{backends:[UCX, GDS]}}客户端编排当前 vLLM 尚未内置自动路由官方正在推进 routing 方案现阶段由客户端脚本负责串联 prefill 与 decode 两个阶段。客户端编排示例下面的 Python 脚本演示了标准的 PD 分离客户端流程先请求 prefill 实例完成 prefill 并拿到kv_transfer_params再把同一request_id的 KV 信息交给 decode 实例续写生成。要点如下prefill 阶段max_tokens1强制只做 prefill通过extra_headers{X-Request-Id: request_id}把两次请求关联到同一逻辑请求kv_transfer_params中的remote_engine_id、remote_block_ids、remote_host/port由 vLLM 在 prefill 响应中自动填充decode 阶段的 prompt 会被忽略KV 已从 prefill 端取回只需传回prefill_response.kv_transfer_params。from openai import OpenAI import uuid try: # 1: 分别创建 prefill 与 decode 实例的客户端 openai_api_key EMPTY # vLLM 不需要真实 API key # 将 IP 替换为实际实例地址 prefill_client OpenAI( api_keyopenai_api_key, base_urlhttp://192.168.1.100:8000/v1, # Prefill 实例地址 ) decode_client OpenAI( api_keyopenai_api_key, base_urlhttp://192.168.1.101:8001/v1, # Decode 实例地址 ) # 从 prefill 实例获取模型名 models prefill_client.models.list() model models.data[0].id print(fUsing model: {model}) # 2: Prefill 阶段 # 生成唯一 request id用于串联 prefill 与 decode request_id str(uuid.uuid4()) print(fRequest ID: {request_id}) prefill_response prefill_client.completions.create( modelmodel, # prompt 长度必须超过 vLLM 的 block size16 tokensPD 才能生效 promptWrite a detailed explanation of Paged Attention for Transformers works including the management of KV cache for multi-turn conversations, max_tokens1, # 只做 prefill extra_body{ kv_transfer_params: { do_remote_decode: True, # 启用远端 decode do_remote_prefill: False, # 本实例扮演 prefill remote_engine_id: None, # 由 vLLM 自动填充 remote_block_ids: None, # 由 vLLM 自动填充 remote_host: None, # 由 vLLM 自动填充 remote_port: None, # 由 vLLM 自动填充 } }, extra_headers{X-Request-Id: request_id}, ) print(- * 50) print(✓ Prefill completed successfully) print(fPrefill response: {prefill_response.choices[0].text}) # 3: Decode 阶段 # 把 KV cache 信息从 prefill 传给 decode 实例 decode_response decode_client.completions.create( modelmodel, promptThis prompt is ignored during decode, # decode 阶段无需原始 prompt max_tokens150, # 最多生成 150 个 token extra_body{ kv_transfer_params: prefill_response.kv_transfer_params # 回传 KV cache 信息 }, extra_headers{X-Request-Id: request_id}, # 使用同一 request id ) print(- * 50) print(✓ Decode completed successfully) print(fFinal 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)注意文档中标注的示例 base_url如http://192.168.1.100:8000/v1需替换为你实际部署的 prefill/decode 实例地址。分离部署的 Benchmarking 技巧隔离测 decode向vllm serve传--kv-transfer-config {kv_connector:DecodeBenchConnector,kv_role:kv_both}该专用 connector 会用随机值填充 KV cache使 decode 可以被单独 profile而不必依赖真实 prefill 产出的 KVCUDAGraph 捕获加--compilation_config {cudagraph_mode: FULL_DECODE_ONLY}只为 decode 开启 CUDA graph 捕获并节省 KV cache 空间。小结专家并行是 vLLM 服务大规模稀疏 MoE 模型的关键路径通过--enable-expert-parallel将专家层按TP × DP切分配合--data-parallel-size让注意力层走数据并行即可在保持注意力层吞吐的同时降低专家权重的单卡显存与访存压力。多节点场景用--all2all-backend在 DeepEP / FlashInfer 等 A2A 后端间取舍负载倾斜由 EPLB--enable-eplb--eplb-config自动再均衡代价是每 rank 需预留冗余专家显存需要严格 SLA 的生产环境则可将 prefill 与 decode 拆分到独立实例借助 NixlConnector 与 KV 传输参数实现分离部署。所有相关命令行参数与约束都可在 vllm/config/parallel.py 的ParallelConfig与EPLBConfig中找到对应的类型定义、默认值与运行时校验逻辑动手前对照源码阅读能帮助你避免踩到诸如异步 EPLB 与 NCCL 通信器互斥EPLB 必须依赖 EP 开启之类的隐性约束。【免费下载链接】vllmA high-throughput and memory-efficient inference and serving engine for LLMs项目地址: https://gitcode.com/GitHub_Trending/vl/vllm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考