전문가 병렬 배포
전문가 병렬 배포 (Expert Parallel Deployment)
vLLM은 Expert Parallelism(EP)을 지원합니다. EP는 MoE(Mixture-of-Experts) 모델의 전문가(Expert)를 별도의 GPU에 배포해 전체적으로 로컬리티·효율·처리량을 높일 수 있게 합니다. EP는 보통 데이터 병렬(DP)과 함께 사용되며, DP는 EP 없이 독립적으로 쓸 수 있지만 EP는 DP와 결합할 때 더 효율적입니다.
출처: 문서
본문
vLLM은 EP를 지원하며, MoE 모델의 전문가를 별도 GPU에 배포해 로컬리티·효율·처리량을 전반적으로 높입니다. EP는 보통 DP와 결합해 씁니다. DP는 EP 없이 독립적으로도 쓸 수 있지만, EP는 DP와 함께 쓸 때 더 효율적입니다. 데이터 병렬에 대한 자세한 내용은 여기에서 읽을 수 있습니다.
사전 준비 (Prerequisites)
EP를 사용하려면 필요한 의존성을 설치해야 합니다. 향후 이를 더 쉽게 만들기 위해 적극적으로 작업 중입니다:
- DeepEP 설치: vLLM의 EP 커널 가이드 여기를 따라 호스트 환경을 설정하세요.
- DeepGEMM 라이브러리 설치: 공식 지침을 따르세요.
- 분리(disaggregated) 서빙용:
install_gdrcopy.sh스크립트를 실행해gdrcopy를 설치하세요(예:install_gdrcopy.sh "${GDRCOPY_OS_VERSION}" "12.8" "x64"). 사용 가능한 OS 버전은 여기에서 찾을 수 있습니다.
NCCL 버전 (CUDA 13+)
deepep_v2 백엔드는 NCCL >= 2.30.4를 요구합니다. PyTorch는 더 오래된 NCCL을 제공하므로, DeepEP를 빌드/실행하기 전에 반드시 업그레이드해야 합니다. 지침은 EP kernels 가이드를 참고하세요.
백엔드 선택 가이드 (Backend Selection Guide)
vLLM은 EP용 여러 통신 백엔드를 제공합니다. --all2all-backend로 선택합니다:
| 백엔드 | 사용 사례 | 특징 | 적합한 용도 |
|---|---|---|---|
allgather_reducescatter |
기본 백엔드 | allgather/reducescatter 프리미티브를 쓰는 표준 all2all | 일반 목적, 모든 EP+DP 구성에서 동작 |
deepep_high_throughput |
멀티 노드 prefill | 연속 레이아웃의 Grouped GEMM, prefill에 최적화 | prefill 중심 워크로드, 고처리량 시나리오 |
deepep_low_latency |
멀티 노드 decode | CUDA graph 지원, masked 레이아웃, decode에 최적화 | decode 중심 워크로드, 저지연 시나리오 |
flashinfer_nvlink_one_sided |
MNNVL 시스템 | 멀티 노드 NVLink용 FlashInfer 단방향 A2A 전략 | 고처리량 워크로드 |
flashinfer_nvlink_two_sided |
MNNVL 시스템 | 멀티 노드 NVLink용 FlashInfer 양방향 A2A 전략 | 노드 간 NVLink가 있는 시스템 |
단일 노드 배포 (Single Node Deployment)
구성
--enable-expert-parallel 플래그를 설정해 EP를 활성화합니다. EP 크기는 자동으로 계산됩니다:
EP_SIZE = TP_SIZE × DP_SIZE
여기서:
TP_SIZE: Tensor parallel 크기DP_SIZE: Data parallel 크기EP_SIZE: Expert parallel 크기 (자동 계산)
EP 활성화 시 레이어 동작 (Layer Behavior with EP Enabled)
EP를 켜면 MoE 모델의 서로 다른 레이어가 다르게 동작합니다:
| 레이어 유형 | 동작 | 사용 병렬화 |
|---|---|---|
| 전문가(MoE) 레이어 | 모든 EP 랭크에 걸쳐 샤딩 | 크기 TP × DP의 EP |
| 어텐션 레이어 | TP 크기에 따라 다름 | 아래 참고 |
어텐션 레이어 병렬화:
TP = 1일 때: 어텐션 가중치가 모든 DP 랭크에 복제됩니다(데이터 병렬)TP > 1일 때: 각 DP 그룹 내 TP 랭크에 걸쳐 tensor parallelism으로 어텐션 가중치가 샤딩됩니다
예를 들어 TP=2, DP=4(총 8 GPU)라면:
- 전문가 레이어는 크기 8의 EP 그룹을 형성하고, 전문가가 모든 GPU에 분산됩니다
- 어텐션 레이어는 4개 DP 그룹 각각에서 TP=2를 사용합니다
데이터 병렬 배포와의 핵심 차이
--enable-expert-parallel이 없으면 MoE 레이어는 밀집 모델과 유사하게 tensor parallelism(크기 TP × DP의 TP 그룹 형성)을 사용합니다. EP를 켜면 전문가 레이어가 expert parallelism으로 전환되어 MoE 모델에 더 나은 효율과 로컬리티를 제공할 수 있습니다.
예시 명령
다음 명령은 DeepSeek-V3-0324 모델을 1-way tensor parallel, 8-way(어텐션) 데이터 병렬, 8-way expert parallel로 서빙합니다. 어텐션 가중치는 모든 GPU에 복제되고, 전문가 가중치는 GPU에 split됩니다. 8개 GPU가 있는 H200(또는 H20) 노드에서 동작합니다. H100이라면 더 작은 모델을 서빙하거나 멀티 노드 배포 섹션을 참고하세요.
# Single node EP deployment
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
멀티 노드 배포 (Multi-Node Deployment)
멀티 노드 배포에는 두 모드 중 하나(위 Backend Selection Guide 참고)로 DeepEP 통신 커널을 사용합니다.
배포 단계
- 노드당 명령 하나 실행 — 각 노드는 자체 실행 명령이 필요합니다
- 네트워킹 구성 — 올바른 IP 주소와 포트를 구성하세요
- 노드 역할 설정 — 첫 노드가 요청을 처리하고, 추가 노드는 headless 모드로 실행됩니다
예시: 2-노드 배포
다음 예시는 deepep_low_latency 모드로 DeepSeek-V3-0324를 2개 노드에 배포합니다:
# Node 1 (Primary - handles incoming requests)
vllm serve deepseek-ai/DeepSeek-V3-0324 \
--all2all-backend deepep_low_latency \
--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 # local ranks is recommended)
# Node 2 (Secondary - headless mode, no API server)
vllm serve deepseek-ai/DeepSeek-V3-0324 \
--all2all-backend deepep_low_latency \
--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 모드: 보조 노드는
--headless플래그로 실행되며, 모든 클라이언트 요청을 기본 노드가 처리합니다 - 랭크 계산:
--data-parallel-start-rank는 이전 노드들의 누적 로컬 DP 크기와 같아야 합니다 - 부하 확장: 기본 노드에서
--api-server-count를 조정해 더 높은 요청 부하를 처리하세요
네트워크 구성
InfiniBand 클러스터
InfiniBand 네트워크 클러스터에서는 초기화 hang을 방지하기 위해 이 환경 변수를 설정하세요:
export GLOO_SOCKET_IFNAME=eth0
이렇게 하면 torch distributed 그룹 발견이 초기 설정에 InfiniBand 대신 Ethernet을 사용합니다.
전문가 병렬 로드밸런서 (EPLB)
MoE 모델은 보통 각 전문가가 비슷한 토큰 수를 받도록 학습되지만, 실제로는 전문가 간 토큰 분포가 크게 치우칠 수 있습니다. vLLM은 Expert Parallel Load Balancer(EPLB)를 제공해 EP 랭크 간 전문가 매핑을 재분배하여 전문가 간 부하를 고르게 합니다.
구성
--enable-eplb 플래그로 활성화합니다.
활성화하면 vLLM은 매 포워드 패스마다 부하 통계를 수집하고 주기적으로 전문가 분포를 재조정합니다.
EPLB 파라미터
--eplb-config 인자로 구성하며, JSON 문자열을 받습니다. 사용 가능한 키와 설명:
| 파라미터 | 설명 | 기본값 |
|---|---|---|
window_size |
재조정 결정을 위해 추적할 엔진 스텝 수 | 1000 |
step_interval |
재조정 빈도 (매 N 엔진 스텝) | 3000 |
log_balancedness |
밸런스드니스 지표 기록 (전문가당 평균 토큰 ÷ 전문가당 최대 토큰) | false |
num_redundant_experts |
균등 분배를 넘어 EP 랭크당 추가되는 전역 전문가 수 | 0 |
use_async |
지연 오버헤드 감소를 위한 non-blocking EPLB 사용 | true |
policy |
전문가 병렬 로드밸런싱 정책 유형 | "default" |
communicator |
전문가 가중치 전송 백엔드: "torch_nccl", "torch_gloo", "pynccl", "nixl", 또는 null(자동) |
null |
예를 들어:
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 랭크는
NUM_TOTAL_EXPERTS ÷ NUM_EP_RANKS개 전문가를 가짐 - 리던던시 포함: 각 EP 랭크는
(NUM_TOTAL_EXPERTS + NUM_REDUNDANT_EXPERTS) ÷ NUM_EP_RANKS개 전문가를 가짐
메모리 오버헤드
EPLB는 GPU 메모리에 들어가야 하는 리던던트 전문가를 사용합니다. 즉 메모리 제약 환경이나 KV cache 공간이 귀중한 환경에는 EPLB가 맞지 않을 수 있습니다.
이 오버헤드는 NUM_MOE_LAYERS * BYTES_PER_EXPERT * (NUM_TOTAL_EXPERTS + NUM_REDUNDANT_EXPERTS) ÷ NUM_EP_RANKS와 같습니다. DeepSeekV3의 경우 EP 랭크당 리던던트 전문가 1개당 약 2.4 GB입니다.
예시 명령
EPLB를 활성화한 단일 노드 배포:
# Single node with EPLB load balancing
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-config '{"window_size":1000,"step_interval":3000,"num_redundant_experts":2,"log_balancedness":true}'
멀티 노드 배포에서는 각 노드의 명령에 EPLB 플래그를 추가하세요. 대규모 사용에서는 --eplb-config '{"num_redundant_experts":32}'로 32를 설정해 인기 전문가가 항상 사용 가능하도록 하는 것을 권장합니다.
고급 구성 (Advanced Configuration)
성능 최적화
- DeepEP 커널:
high_throughput와low_latency커널은 분리(disaggregated) 서빙에 최적화되어 있어 혼합 워크로드에서는 성능이 좋지 않을 수 있습니다 - Dual Batch Overlap:
--enable-dbo로 all-to-all 통신과 컴퓨팅을 겹칩니다. 자세한 내용은 Dual Batch Overlap 참고 - 비동기 스케줄링 (실험적):
--async-scheduling으로 스케줄링을 모델 실행과 겹쳐 보세요
문제 해결
non-zero status: 7 cannot register cq buf: Infiniband/RoCE 사용 시 호스트 VM과 파드의ulimit -l이 "unlimited"인지 확인하세요init failed for transport: IBGDA: InfiniBand GDA 커널 모듈이 없습니다. 각 GPU 노드에서tools/ep_kernels/configure_system_drivers.sh를 실행하고 재부팅하세요.NVSHMEM API called before NVSHMEM initialization has completed오류도 해결합니다- NVSHMEM 피어 연결 끊김: 보통 네트워킹 설정 오류입니다. Kubernetes로 배포한다면 모든 파드가 Infiniband 접근을 위해
hostNetwork: true,securityContext.privileged: true로 실행되는지 확인하세요
벤치마킹
- 시뮬레이터 플래그
VLLM_MOE_ROUTING_SIMULATION_STRATEGY=uniform_random과VLLM_RANDOMIZE_DP_DUMMY_INPUTS=1을 사용해 토큰 라우팅이 EP 랭크 간 균등하게 되게 하세요.
분리 서빙 (Disaggregated Serving, Prefill/Decode 분리)
첫 토큰까지의 시간과 inter-token 지연에 대한 엄격한 SLA 보장이 필요한 프로덕션 배포에서, disaggregated serving은 prefill과 decode 연산을 독립적으로 확장할 수 있게 합니다.
아키텍처 개요
- Prefill 인스턴스: 최적의 prefill 성능을 위해
deepep_high_throughput백엔드 사용 - Decode 인스턴스: 최소 decode 지연을 위해
deepep_low_latency백엔드 사용 - KV Cache 전송: NIXL 또는 다른 KV 커넥터로 인스턴스를 연결
설정 단계
- gdrcopy/ucx/nixl 설치: 최대 성능을 위해
install_gdrcopy.sh스크립트로gdrcopy를 설치하세요(예:install_gdrcopy.sh "${GDRCOPY_OS_VERSION}" "12.8" "x64"). OS 버전은 여기에서 찾을 수 있습니다.gdrcopy가 없어도 일반pip install nixl로 동작은 하지만 성능은 더 낮습니다.nixl과ucx는 pip 의존성으로 설치됩니다. non-cuda 플랫폼에서 non-cuda UCX 빌드로 nixl을 설치하려면install_nixl_from_source_ubuntu.py를 실행하세요. - 양쪽 인스턴스 구성: prefill과 decode 인스턴스 모두에 이 플래그를 추가하세요
--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"]}}' - 클라이언트 오케스트레이션: 아래 클라이언트 측 스크립트로 prefill/decode 연산을 조정하세요. 라우팅 솔루션을 적극적으로 작업 중입니다.
클라이언트 오케스트레이션 예시
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")
벤치마킹
- 분리 서빙의 decode 배포를 시뮬레이션하려면
vllm serve호출에--kv-transfer-config '{"kv_connector":"DecodeBenchConnector","kv_role":"kv_both"}'를 전달하세요. 커넥터가 KV cache를 무작위 값으로 채워 decode를 단독으로 프로파일링하게 합니다 - CUDAGraph 캡처:
--compilation_config '{"cudagraph_mode": "FULL_DECODE_ONLY"}'로 decode 전용 CUDA graph 캡처를 활성화하고 KV cache를 절약하세요.
더 알아보기 (Learn more)
- 데이터 병렬 배포 — DP 배포 개요
- 병렬 처리 확장 — DP/TP/EP 크기 조정 가이드
- Dual Batch Overlap — all2all 통신 겹치기