LLM 추론에는 두 단계가 있어요.
Prefill (프리필):
- 입력 프롬프트 전체를 처리
- 연산 집약적 (Compute-bound)
- KV 캐시 생성
- 보통 수백~수천 토큰을 한 번에 처리
Decode (디코드):
- 토큰을 하나씩 생성
- 메모리 집약적 (Memory-bound)
- KV 캐시를 매 스텝마다 읽음
- 요청당 수십~수백 번 반복
전통적인 통합 엔진에서는 이 두 단계가 같은 GPU에서 경쟁해요. 그래서 두 가지 심각한 문제가 생겨요.
문제 1: Prefill 방해(Prefill Interruption)
기존 통합 엔진:
[디코딩 중...토큰 생성 중...]
↑
새 요청 들어옴!
↓
[프리필 처리... (디코딩 멈춤)]
[디코딩 재개...]
[프리필 처리... (또 멈춤)]
→ 토큰 생성이 계속 끊김 → TPOT(토큰 생성 시간) 급증
문제 2: DP 어텐션 불균형
기존 통합 엔진 (DP 어텐션 2개):
DP Worker 0: [프리필 처리 중] ← 연산 집약적
DP Worker 1: [디코딩 처리 중] ← 메모리 집약적
→ 서로 다른 특성 때문에 동기화 대기 발생 → 레이턴시 증가
PD 분리(Disaggregation) 는 이 두 단계를 완전히 다른 서버로 분리해서 각각 최적화해요.
PD 분리 아키텍처:
클라이언트 요청
↓
[로드 밸런서]
↓
[Prefill 서버] → KV 캐시 생성 후 RDMA로 전송 →
[Decode 서버] → 토큰 생성 → 클라이언트
실제 DeepSeek 모델 96 GPU 배포에서 Prefill 3.8배, Decode 4.8배 처리량 향상이 확인됐어요.
PD 분리가 왜 빠른가
Prefill 서버 최적화
Prefill 특성:
- 연산 집약적 → 빠른 GPU 연산 필요
- 배치 처리에 유리 → 여러 요청 한 번에 처리
- KV 캐시 생성 후 바로 다음 요청으로 넘어감
→ 고성능 계산용 GPU에 배치
→ 텐서 병렬 크게 설정
→ 배치 크기 최대화
Decode 서버 최적화
Decode 특성:
- 메모리 집약적 → 큰 KV 캐시 필요
- 매 스텝마다 KV 캐시 읽기
- 연속 토큰 생성
→ 메모리 많은 GPU에 배치
→ 많은 동시 요청 처리
→ KV 캐시 공간 최대화
RDMA 기반 KV 캐시 전송
Prefill이 KV 캐시를 생성하면 Decode로 넘겨줘야 해요. 이 전송이 빠르지 않으면 PD 분리 효과가 없어요. SGLang은 **RDMA(Remote Direct Memory Access)**로 GPU 메모리 간 직접 전송을 해요.
일반 네트워크 전송:
GPU메모리 → CPU메모리 → 네트워크 → CPU메모리 → GPU메모리
(복사 4번, CPU 관여)
RDMA 전송:
GPU메모리 → [InfiniBand/RoCE] → GPU메모리
(직접 전송, CPU 관여 없음, 제로 카피)
요청 흐름 — 단계별 동작
1. 클라이언트 → Decode 서버로 요청 전송
2. Decode → Prefill에 bootstrap_room ID 요청 (KV 캐시 수신 공간 예약)
3. Decode → GPU 메모리 페이지 할당
4. Decode → Prefill로 요청 포워딩
5. Prefill → 프롬프트 전체 처리, KV 캐시 생성
6. Prefill → RDMA로 KV 캐시를 Decode GPU 메모리에 직접 기록
7. Prefill → 완료 신호 전송, 전송 메타데이터 정리
8. Decode → KV 캐시 수신 확인 후 토큰 생성 시작
9. Decode → 생성된 토큰을 스트리밍으로 클라이언트에 전달
설치 — 전송 백엔드 준비
PD 분리에는 KV 캐시 전송 백엔드가 필요해요. Mooncake 또는 NIXL 중 하나를 사용해요.
Mooncake 설치 (AMD GPU 포함 모든 환경 권장)
pip install mooncake-transfer-engine
# 또는 소스에서 빌드
git clone https://github.com/kvcache-ai/Mooncake.git
cd Mooncake
pip install -e .
# 설치 확인
python -c "import mooncake; print(mooncake.__version__)"
NIXL 설치 (NVIDIA GPU 권장, RDMA 최적화)
# pip 설치
pip install nixl
# 소스 빌드 (UCX가 이미 설치된 경우 필요)
git clone https://github.com/ai-dynamo/nixl.git
cd nixl
pip install .
어느 걸 쓸까?
Mooncake:
✅ AMD GPU 포함 모든 GPU 지원
✅ 설치 간단
✅ InfiniBand/RoCE 네트워크 지원
→ AMD MI300X, 멀티벤더 환경
NIXL:
✅ NVIDIA GPU 전용 최적화
✅ CUDA IPC 지원
✅ 더 낮은 전송 레이턴시 (NVIDIA 환경)
→ NVIDIA H100/A100/GB200 순수 환경
단일 노드 PD 분리 (기본 설정)
가장 간단한 구성이에요. 같은 서버에서 Prefill과 Decode를 분리해요.
# Prefill 서버 (GPU 0, 1)
CUDA_VISIBLE_DEVICES=0,1 python -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-8B-Instruct \
--disaggregation-mode prefill \
--disaggregation-transfer-backend mooncake \
--host 0.0.0.0 \
--port 30000 \
--tp 2
# Decode 서버 (GPU 2, 3)
CUDA_VISIBLE_DEVICES=2,3 python -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-8B-Instruct \
--disaggregation-mode decode \
--disaggregation-transfer-backend mooncake \
--host 0.0.0.0 \
--port 30001 \
--tp 2
로드 밸런서 설정
Prefill과 Decode 서버 앞단에 라우터를 놓아요. SGLang 공식 라우터를 사용해요.
# pip 설치
pip install sglang-router
# 라우터 실행
python -m sglang_router.launch_router \
--pd-disaggregation \
--prefill-hosts http://localhost:30000 \
--decode-hosts http://localhost:30001 \
--host 0.0.0.0 \
--port 8080
# 클라이언트에서는 라우터 주소만 사용
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8080/v1",
api_key="EMPTY"
)
response = client.chat.completions.create(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=[{"role": "user", "content": "안녕"}]
)
멀티 노드 PD 분리 (대규모 배포)
여러 노드에 걸쳐 Prefill과 Decode를 분리해요.
Llama 70B — 2노드 PD 분리
# ===== Prefill 노드 (노드 0) =====
python -m sglang.launch_server \
--model-path meta-llama/Llama-3.3-70B-Instruct \
--disaggregation-mode prefill \
--disaggregation-transfer-backend mooncake \
--host 0.0.0.0 \
--port 30000 \
--tp 4 \
--dtype bfloat16 \
--mem-fraction-static 0.85
# ===== Decode 노드 (노드 1) =====
python -m sglang.launch_server \
--model-path meta-llama/Llama-3.3-70B-Instruct \
--disaggregation-mode decode \
--disaggregation-transfer-backend mooncake \
--host 0.0.0.0 \
--port 30001 \
--tp 4 \
--dtype bfloat16 \
--mem-fraction-static 0.88 # Decode는 KV 캐시 공간 더 확보
# ===== 라우터 (별도 노드 또는 마스터 노드) =====
python -m sglang_router.launch_router \
--pd-disaggregation \
--prefill-hosts http://prefill-node-ip:30000 \
--decode-hosts http://decode-node-ip:30001 \
--host 0.0.0.0 \
--port 8080
DeepSeek-V3 — 대규모 PD 분리 (96 GPU)
실제 LMSYS가 공개한 DeepSeek V3 96 GPU 배포 설정이에요.
# ===== Prefill 서버 — 노드 0 (마스터) =====
python -m sglang.launch_server \
--model-path deepseek-ai/DeepSeek-V3-0324 \
--disaggregation-mode prefill \
--disaggregation-transfer-backend mooncake \
--disaggregation-ib-device mlx5_0 # InfiniBand NIC 이름 (ibstat로 확인)
--host ${local_ip} \
--port 30000 \
--trust-remote-code \
--dist-init-addr ${prefill_master_ip}:5000 \
--nnodes 2 \
--node-rank 0 \
--tp 16 \
--dp 8 \
--enable-dp-attention \
--moe-a2a-backend deepep \
--mem-fraction-static 0.80
# ===== Prefill 서버 — 노드 1 (워커) =====
python -m sglang.launch_server \
--model-path deepseek-ai/DeepSeek-V3-0324 \
--disaggregation-mode prefill \
--disaggregation-transfer-backend mooncake \
--disaggregation-ib-device mlx5_0 \
--host ${local_ip} \
--port 30000 \
--trust-remote-code \
--dist-init-addr ${prefill_master_ip}:5000 \
--nnodes 2 \
--node-rank 1 \
--tp 16 \
--dp 8 \
--enable-dp-attention \
--moe-a2a-backend deepep \
--mem-fraction-static 0.80
# ===== Decode 서버 — 노드 2 (마스터) =====
python -m sglang.launch_server \
--model-path deepseek-ai/DeepSeek-V3-0324 \
--disaggregation-mode decode \
--disaggregation-transfer-backend mooncake \
--disaggregation-ib-device mlx5_0 \
--host ${local_ip} \
--port 30001 \
--trust-remote-code \
--dist-init-addr ${decode_master_ip}:5001 \
--nnodes 2 \
--node-rank 0 \
--tp 16 \
--dp 8 \
--enable-dp-attention \
--moe-a2a-backend deepep \
--mem-fraction-static 0.85
# ===== Decode 서버 — 노드 3 (워커) =====
python -m sglang.launch_server \
--model-path deepseek-ai/DeepSeek-V3-0324 \
--disaggregation-mode decode \
--disaggregation-transfer-backend mooncake \
--disaggregation-ib-device mlx5_0 \
--host ${local_ip} \
--port 30001 \
--trust-remote-code \
--dist-init-addr ${decode_master_ip}:5001 \
--nnodes 2 \
--node-rank 1 \
--tp 16 \
--dp 8 \
--enable-dp-attention \
--moe-a2a-backend deepep \
--mem-fraction-static 0.85
PD 분리 전용 파라미터 상세
--disaggregation-mode
서버 역할을 지정해요.
# Prefill 전용 서버
--disaggregation-mode prefill
# Decode 전용 서버
--disaggregation-mode decode
# 설정 안 하면 기존 통합 모드 (기본값)
--disaggregation-transfer-backend
KV 캐시 전송 백엔드예요.
# Mooncake (범용, AMD 포함)
--disaggregation-transfer-backend mooncake
# NIXL (NVIDIA 전용, 더 빠름)
--disaggregation-transfer-backend nixl
--disaggregation-ib-device
InfiniBand NIC 이름이에요. 명시 안 하면 자동 감지.
# InfiniBand NIC 목록 확인
ibstat
# 특정 NIC 지정
--disaggregation-ib-device mlx5_0
# 여러 NIC 사용
--disaggregation-ib-device mlx5_0,mlx5_1
--disaggregation-prefill-dp-size
Prefill 전용 DP 크기예요. Prefill과 Decode가 다른 DP 크기를 가질 수 있어요.
# Decode DP보다 Prefill DP를 더 크게 설정
python -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-70B-Instruct \
--disaggregation-mode prefill \
--tp 4 \
--dp 4 \
--disaggregation-prefill-dp-size 4
환경 변수로 타임아웃 조정
# Bootstrap 타임아웃 (기본 30초, 느린 네트워크에서 늘리기)
export SGLANG_DISAGGREGATION_BOOTSTRAP_TIMEOUT=600 # 10분
# 대기 타임아웃
export SGLANG_DISAGGREGATION_WAITING_TIMEOUT=600
Prefill : Decode 비율 설정
워크로드 특성에 따라 비율을 조절해요.
짧은 프롬프트, 긴 응답 (예: 코드 생성):
Prefill 1 : Decode 3~4
→ Decode 서버가 더 많은 GPU 필요
긴 프롬프트, 짧은 응답 (예: 문서 요약):
Prefill 2~3 : Decode 1
→ Prefill 서버가 더 많은 GPU 필요
균형 잡힌 워크로드 (예: 챗봇):
Prefill 1 : Decode 1~2
# 짧은 프롬프트, 긴 응답 워크로드
# Prefill 1개 서버 (GPU 2개)
CUDA_VISIBLE_DEVICES=0,1 python -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-8B-Instruct \
--disaggregation-mode prefill \
--tp 2 --port 30000
# Decode 3개 서버 (GPU 6개)
CUDA_VISIBLE_DEVICES=2,3 python -m sglang.launch_server \
--disaggregation-mode decode --tp 2 --port 30001
CUDA_VISIBLE_DEVICES=4,5 python -m sglang.launch_server \
--disaggregation-mode decode --tp 2 --port 30002
CUDA_VISIBLE_DEVICES=6,7 python -m sglang.launch_server \
--disaggregation-mode decode --tp 2 --port 30003
# 라우터에서 여러 Decode 서버 등록
python -m sglang_router.launch_router \
--pd-disaggregation \
--prefill-hosts http://localhost:30000 \
--decode-hosts http://localhost:30001 http://localhost:30002 http://localhost:30003 \
--port 8080
Docker Compose로 PD 분리 배포
# docker-compose.pd.yml
version: "3.8"
services:
prefill:
image: lmsysorg/sglang:latest
runtime: nvidia
environment:
- NVIDIA_VISIBLE_DEVICES=0,1
volumes:
- ~/.cache/huggingface:/root/.cache/huggingface
ports:
- "30000:30000"
command: >
python -m sglang.launch_server
--model-path meta-llama/Llama-3.1-8B-Instruct
--disaggregation-mode prefill
--disaggregation-transfer-backend mooncake
--tp 2
--host 0.0.0.0
--port 30000
network_mode: host
decode:
image: lmsysorg/sglang:latest
runtime: nvidia
environment:
- NVIDIA_VISIBLE_DEVICES=2,3
volumes:
- ~/.cache/huggingface:/root/.cache/huggingface
ports:
- "30001:30001"
command: >
python -m sglang.launch_server
--model-path meta-llama/Llama-3.1-8B-Instruct
--disaggregation-mode decode
--disaggregation-transfer-backend mooncake
--tp 2
--host 0.0.0.0
--port 30001
--mem-fraction-static 0.88
network_mode: host
depends_on:
- prefill
router:
image: sglang-router:latest
ports:
- "8080:8080"
command: >
python -m sglang_router.launch_router
--pd-disaggregation
--prefill-hosts http://localhost:30000
--decode-hosts http://localhost:30001
--host 0.0.0.0
--port 8080
network_mode: host
depends_on:
- prefill
- decode
docker compose -f docker-compose.pd.yml up -d
성능 비교 — 통합 모드 vs PD 분리
96 GPU에서 DeepSeek V3 기준 실측값이에요.
지표 통합 모드 PD 분리 향상
| Prefill 처리량 | 기준 | 3.8배 | +280% |
| Decode 처리량 | 기준 | 4.8배 | +380% |
| TTFT (첫 토큰) | 높음 | 낮음 | 대폭 개선 |
| TPOT (토큰 생성) | 불규칙 | 안정적 | 안정화 |
PD 분리 사용해야 할 때 vs 아닐 때
PD 분리를 써야 할 때
✅ GPU 8개 이상 보유
✅ 고트래픽 프로덕션 환경
✅ TTFT와 TPOT SLA가 엄격함
✅ InfiniBand 네트워크 있음
✅ DeepSeek, Llama 70B+ 대형 모델
통합 모드가 더 나을 때
❌ GPU 4개 이하 소규모 환경
❌ 단순 개발/테스트 환경
❌ 네트워크 대역폭 부족 (KV 전송 병목)
❌ 8B 이하 소형 모델 (PD 분리 오버헤드가 이득보다 큼)
자주 발생하는 문제와 해결법
# 문제 1: KV 캐시 전송 타임아웃
# 해결: 타임아웃 늘리기
export SGLANG_DISAGGREGATION_BOOTSTRAP_TIMEOUT=300
# 문제 2: InfiniBand NIC 찾지 못함
# 해결: 수동 지정
ibstat # 사용 가능한 NIC 확인
--disaggregation-ib-device mlx5_0
# 문제 3: Decode 서버 OOM
# 해결: mem-fraction-static 낮추기 (KV 캐시 더 줄이기)
--mem-fraction-static 0.80
# 문제 4: Prefill 서버가 느림
# 해결: max-prefill-tokens 늘리기
--max-prefill-tokens 32768
# 문제 5: 멀티 노드 데드락
# 해결: CUDA 그래프 비활성화
--disable-cuda-graph
# 문제 6: RDMA 연결 실패
# 해결: UCX/NCCL 환경 변수 설정
export NCCL_IB_DISABLE=0
export NCCL_IB_GID_INDEX=3
export UCX_TLS=rc,cuda_copy,cuda_ipc
공식 문서 링크
- PD 분리 가이드: https://docs.sglang.io/advanced_features/pd_disaggregation.html
- SGLang 라우터: https://docs.sglang.io/advanced_features/sgl_model_gateway.html
- 멀티 노드 배포: https://docs.sglang.io/references/multi_node_deployment/multi_node_index.html
마무리
PD 분리를 한 줄로 요약하면 이래요.
"Prefill은 계산이 빠른 GPU에서 몰아치고, Decode는 메모리 많은 GPU에서 여유 있게 처리해라."
GPU가 충분하고 고트래픽 환경이라면 PD 분리는 선택이 아니라 필수예요. DeepSeek-V3 수준의 모델을 프로덕션에서 서빙하려면 PD 분리 없이는 SLA를 맞추기 어려워요. 😄
'LLM' 카테고리의 다른 글
| LoRA / QLoRA 완전 가이드 — LLM 파인튜닝을 저렴하게 하는 법 (0) | 2026.04.09 |
|---|---|
| LLM 양자화 완전 정리 — FP8, AWQ, GPTQ, GGUF 차이와 선택법 (1) | 2026.04.09 |
| SGLang launch_server 파라미터 완전 정리 (0) | 2026.04.09 |
| SGLang 서빙에 대한 모든 것 — 설치부터 프로덕션까지 완전 가이드 (0) | 2026.04.09 |
| 스마트폰에서 AI를 돌리는 법 — 온디바이스 LLM 개발 입문 가이드 (0) | 2026.04.08 |