분리 확산 파이프라인
분리 확산 파이프라인 (Disaggregated Diffusion Pipeline)
이 페이지는 단일(monolithic) text-to-video/image 파이프라인을 독립적인 Encoder, Denoiser, Decoder 역할로 분할하는 방법을 설명해요. 각 역할은 자체 GPU에서 실행되고, 중앙의 DiffusionServer가 파이프라인을 통해 요청을 라우팅해요.
출처: 문서
본문
단일(monolithic) text-to-video/image 파이프라인을 독립적인 Encoder, Denoiser, Decoder 역할로 분할하고, 각각 자체 GPU에서 실행해요. 중앙의 DiffusionServer가 파이프라인을 통해 요청을 라우팅해요.
빠른 시작 (Quick Start)
분리는 단일 플래그 --disagg-role로 제어돼요. 각 컴포넌트는 LLM PD disaggregation처럼 독립적으로 실행돼요.
--disagg-role |
What it runs |
|---|---|
monolithic |
(기본값) 표준 단일 서버 모드 |
encoder |
기본 RoleType.ENCODER 친화도를 가진 모든 단계: InputValidationStage, TextEncodingStage(이미지 조건 파이프라인의 ImageEncodingStage / ImageVAEEncodingStage 포함), LatentPreparationStage, TimestepPreparationStage, 그리고 "before denoising" 모델별 단계(예: QwenImageLayeredBeforeDenoisingStage, GlmImageBeforeDenoisingStage). |
denoiser |
DenoisingStage(및 그 하위 클래스: CausalDMDDenoisingStage, DmdDenoisingStage, LTX2AVDenoisingStage, LTX2RefinementStage, Hunyuan3DShapeDenoisingStage, ...) — DiT forward 루프와 그 구동 스케줄러. |
decoder |
DecodingStage(VAE decode) 및 그 하위 클래스(LTX2AVDecodingStage, HeliosDecodingStage, ...). |
server |
DiffusionServer 헤드 노드 + HTTP 서버 (GPU 없음) |
각 단계는
PipelineStage의role_affinity속성으로 자체 역할을 선언해요(기본값ENCODER).--disagg-role이monolithic이 아니면 파이프라인은 친화도가 일치하는 단계만 인스턴스화하므로, 위 표는 각 프로세스에서 실제로 실행되는 것의 진실의 근원이에요.
단일 머신 예시 (검증됨)
다음 명령은 Wan-AI/Wan2.1-T2V-1.3B-Diffusers로 8×H200 머신에서 end-to-end 테스트됐어요. 각 역할은 --base-gpu-id를 통해 별도 GPU에서 실행되며, server 헤드 노드는 GPU가 필요 없어요.
# Terminal 1: Encoder (GPU 0)
sglang serve --model-path Wan-AI/Wan2.1-T2V-1.3B-Diffusers \
--disagg-role encoder \
--disagg-server-addr tcp://127.0.0.1:19655 \
--scheduler-port 19000 \
--num-gpus 1 --base-gpu-id 0
# Terminal 2: Denoiser (GPU 1)
sglang serve --model-path Wan-AI/Wan2.1-T2V-1.3B-Diffusers \
--disagg-role denoiser \
--disagg-server-addr tcp://127.0.0.1:19655 \
--scheduler-port 19001 \
--num-gpus 1 --base-gpu-id 1
# Terminal 3: Decoder (GPU 2)
sglang serve --model-path Wan-AI/Wan2.1-T2V-1.3B-Diffusers \
--disagg-role decoder \
--disagg-server-addr tcp://127.0.0.1:19655 \
--scheduler-port 19002 \
--num-gpus 1 --base-gpu-id 2
# Terminal 4: DiffusionServer head (no GPU, receives HTTP requests)
sglang serve --model-path Wan-AI/Wan2.1-T2V-1.3B-Diffusers \
--disagg-role server \
--encoder-urls "tcp://127.0.0.1:19000" \
--denoiser-urls "tcp://127.0.0.1:19001" \
--decoder-urls "tcp://127.0.0.1:19002" \
--host 0.0.0.0 --port 22000 \
--scheduler-port 19655
# Send request (video generation)
curl http://127.0.0.1:22000/v1/videos \
-H "Content-Type: application/json" \
-d '{"model": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers", "prompt": "A curious raccoon exploring a garden, cinematic", "size": "832x480"}'
테스트 결과 (8×H200): Encoder 2.3초(TextEncoding) → Denoiser 312.8초(50스텝, 레이어 단위 offload) → Decoder 7.1초(VAE decode). 81프레임 1024×1024 비디오에 총 약 ~322초.
Tip:
--base-gpu-id는 역할이 사용하는 물리 GPU를 제어해요. Encoder와 Decoder는 리소스를 아끼기 위해 GPU를 공유할 수 있어요(예: 둘 다--base-gpu-id 0), 하지만 결합 GPU 메모리가 충분한지 확인하세요.
다중 머신 예시
정확히 동일한 CLI 패턴 — 그냥 127.0.0.1을 실제 IP로 바꾸고 직접 전송용 RDMA 플래그를 추가해요:
# Machine A (10.0.0.1): Encoder
sglang serve --model-path Wan-AI/Wan2.1-T2V-14B-Diffusers \
--disagg-role encoder \
--disagg-server-addr tcp://10.0.0.4:19655 \
--scheduler-port 19000 \
--num-gpus 1 \
--disagg-p2p-hostname 10.0.0.1 --disagg-ib-device mlx5_0
# Machine B (10.0.0.2): Denoiser (4 GPUs with SP)
sglang serve --model-path Wan-AI/Wan2.1-T2V-14B-Diffusers \
--disagg-role denoiser \
--disagg-server-addr tcp://10.0.0.4:19655 \
--scheduler-port 19001 \
--num-gpus 4 --denoiser-sp 4 --denoiser-ulysses 2 --denoiser-ring 2 \
--disagg-p2p-hostname 10.0.0.2 --disagg-ib-device mlx5_0
# Machine C (10.0.0.3): Decoder
sglang serve --model-path Wan-AI/Wan2.1-T2V-14B-Diffusers \
--disagg-role decoder \
--disagg-server-addr tcp://10.0.0.4:19655 \
--scheduler-port 19002 \
--num-gpus 1 \
--disagg-p2p-hostname 10.0.0.3 --disagg-ib-device mlx5_0
# Machine D (10.0.0.4): DiffusionServer head
sglang serve --model-path Wan-AI/Wan2.1-T2V-14B-Diffusers \
--disagg-role server \
--encoder-urls "tcp://10.0.0.1:19000" \
--denoiser-urls "tcp://10.0.0.2:19001" \
--decoder-urls "tcp://10.0.0.3:19002" \
--host 0.0.0.0 --port 30000 \
--scheduler-port 19655 \
--disagg-dispatch-policy max_free_slots
ZMQ는 시작 순서를 우아하게 처리해요 — 인스턴스와 헤드가 어떤 순서로든 시작할 수 있어요.
역할당 여러 인스턴스 (Multiple Instances per Role)
--*-urls에 세미콜론을 사용해 여러 인스턴스를 등록해요:
# 2 encoders + 2 denoisers (4-GPU SP each) + 1 decoder
sglang serve --model-path ... --disagg-role server \
--encoder-urls "tcp://10.0.0.1:35000;tcp://10.0.0.2:35000" \
--denoiser-urls "tcp://10.0.0.3:35000;tcp://10.0.0.4:35000" \
--decoder-urls "tcp://10.0.0.5:35000"
GLM-Image 분산 모드
GLM-Image는 헤드에서 AR 생성을 배칭하고 결과 prior 토큰을 분산 denoiser 워커에 디스패치할 수 있어요. 각 워커는 프롬프트/글리프 준비, DiT, VAE 디코딩을 로컬에서 실행하고, 잠재 또는 임베딩 텐서는 전송되지 않아요.
다음 16-디바이스 배포는 디바이스 0-1을 외부 AR 서버에, 디바이스 2-15를 14개의 독립 batch-1 Cache-DiT denoiser에 사용해요.
# Run 14 distributed denoisers
DISAGG_SERVER="tcp://127.0.0.1:19655"
MODEL_PATH="zai-org/GLM-Image"
BASE_MASTER_PORT=29005
export SGLANG_CACHE_DIT_FN=2
export SGLANG_CACHE_DIT_BN=1
export SGLANG_CACHE_DIT_WARMUP=4
export SGLANG_CACHE_DIT_RDT=0.4
export SGLANG_CACHE_DIT_MC=4
export SGLANG_CACHE_DIT_TAYLORSEER=true
export SGLANG_CACHE_DIT_TS_ORDER=2
export SGLANG_CACHE_DIT_ENABLED=true
worker_pids=()
cleanup() {
trap - EXIT
if ((${#worker_pids[@]})); then
kill "${worker_pids[@]}" 2>/dev/null || true
wait "${worker_pids[@]}" 2>/dev/null || true
fi
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
for i in $(seq 2 15); do
scheduler_port=$((19001 + i))
master_port=$((BASE_MASTER_PORT + i))
sglang serve \
--model-path "$MODEL_PATH" \
--disagg-role denoiser \
--disagg-server-addr "$DISAGG_SERVER" \
--srt-encoder-url http://127.0.0.1:30020 \
--scheduler-port "$scheduler_port" \
--master-port "$master_port" \
--num-gpus 1 \
--base-gpu-id "$i" \
--denoiser-sp 1 \
--cfg-parallel-size 1 \
--batching-max-size 1 \
--dit-cpu-offload false \
--attention-backend fa &
worker_pids+=("$!")
done
# Stop all denoisers if any worker exits or fails during startup.
wait -n "${worker_pids[@]}"
외부 AR 서버 실행:
sglang serve \
--model-path ./zai-org/GLM-Image/vision_language_encoder/ \
--tokenizer-path ./zai-org/GLM-Image/processor/ \
--enable-multimodal \
--cuda-graph-max-bs-decode 28 \
--device npu \
--attention-backend ascend \
--disable-fast-image-processor \
--tp-size 2 \
--host 0.0.0.0 \
--port 30020 \
--mem-fraction-static 0.8
공개 헤드 실행. --encoder-urls와 --decoder-urls는 이 토폴로지에 대해 의도적으로 생략돼요.
sglang serve \
--model-path zai-org/GLM-Image \
--disagg-role server \
--srt-encoder-url http://127.0.0.1:30020 \
--srt-encoder-timeout 300 \
--denoiser-urls "tcp://127.0.0.1:19003;tcp://127.0.0.1:19004;tcp://127.0.0.1:19005;tcp://127.0.0.1:19006;tcp://127.0.0.1:19007;tcp://127.0.0.1:19008;tcp://127.0.0.1:19009;tcp://127.0.0.1:19010;tcp://127.0.0.1:19011;tcp://127.0.0.1:19012;tcp://127.0.0.1:19013;tcp://127.0.0.1:19014;tcp://127.0.0.1:19015;tcp://127.0.0.1:19016" \
--batching-mode dynamic \
--batching-max-size 28 \
--batching-delay-ms 30 \
--enable-batching-metrics \
--host 0.0.0.0 \
--port 30052 \
--scheduler-port 19655 \
--output-path ./outputs
워커는 디코딩된 픽셀을 헤드에 반환하며, 헤드는 --output-path에서 최종 파일을 저장하고 서빙해요. PR 벤치마크는 longtext-bench.zip을 사용했어요:
python fetch_images.py \
--base-url http://localhost:30052/v1 \
--model GLM-Image-distributed-test \
--output-dir generated_images \
--max-concurrency 56
포트 규약 (Port Convention)
결과 엔드포인트는 헤드 노드의 --scheduler-port(기본값: 5555)에서 결정적으로 파생돼요:
| Socket | Port |
|---|---|
| DS frontend (ROUTER) | scheduler\_port |
| Encoder result (PULL) | scheduler\_port + 1 |
| Denoiser result (PULL) | scheduler\_port + 2 |
| Decoder result (PULL) | scheduler\_port + 3 |
역할 인스턴스는 --disagg-server-addr에서 결과 엔드포인트를 자동으로 파생해요. 수동 엔드포인트 구성이 필요 없어요.
전송 메커니즘 (Transfer Mechanism)
역할 간 텐서 데이터(encoder→denoiser, denoiser→decoder)는 P2P 전송 엔진으로 전송돼요. DiffusionServer는 경량 제어 메시지(alloc/push/ready)만 라우팅하고, 실제 텐서 데이터는 인스턴스 간에 직접 흘러요.
mooncake-transfer-engine은 분리 확산에 필요해요. GPU 간 직접 데이터 이동을 위한 RDMA를 제공해요.
GLM-Image 분산 모드는 예외예요. ZMQ를 통해 prior 토큰 ID와 요청 메타데이터만 중계하며 Mooncake를 요구하지 않아요.
pip install mooncake-transfer-engine
전송 흐름 (Transfer Flow)
- Sender(encoder/denoiser)가 텐서를 스테이징: 비동기 복사로 전송 버퍼(GPUDirect 지원 여부에 따라 GPU 또는 CPU 핀)에, 메타데이터 JSON 직렬화와 겹쳐서.
- Sender가 DiffusionServer에
transfer_staged제어 메시지 전송(메타데이터만, 텐서 데이터 없음). - DiffusionServer가 receiver에
transfer_alloc전송 → receiver가 버퍼 슬롯 할당 →transfer_allocated응답. - DiffusionServer가 sender의 주소 정보와 함께 receiver에
transfer_push전송. - Receiver가 전송 엔진(Mooncake RDMA 또는 mock)으로 데이터를 pull하고
transfer_ready전송. - Receiver가 전용 전송 스트림에서 텐서를 비동기 로드, 이전 요청의 컴퓨트와 겹쳐서.
Decoder 결과(최종 출력)는 DiffusionServer를 통해 원시 ZMQ 프레임으로 HTTP 클라이언트에 흘러가요.
RDMA 플래그
| Flag | Default | Description |
|---|---|---|
--disagg-p2p-hostname |
127.0.0.1 |
이 인스턴스의 RDMA-도달 가능 호스트명/IP |
--disagg-ib-device |
None |
InfiniBand 디바이스 (예: mlx5\_0, mlx5\_roce0) |
--disagg-transfer-pool-size |
256 MiB | 인스턴스당 핀 메모리 풀 |
각 머신에서 --disagg-p2p-hostname을 실제 IP로 설정해요. 다중 머신에서 --disagg-ib-device가 RDMA NIC를 지정해요.
역할별 병렬 처리 (Per-Role Parallelism)
| Flag | Description |
|---|---|
--encoder-tp |
Encoder tensor parallelism |
--denoiser-tp / --denoiser-sp / --denoiser-ulysses / --denoiser-ring |
Denoiser 병렬 처리 |
--decoder-sp |
Decoder sequence parallelism |
지정되지 않으면 병렬 처리는 --num-gpus에서 자동 파생돼요.
기타 옵션 (Other Options)
| Flag | Default | Description |
|---|---|---|
--disagg-timeout |
600 |
보류 요청에 대한 타임아웃(초) |
--disagg-dispatch-policy |
round\_robin |
round\_robin 또는 max\_free\_slots |
Python API
프로그래매틱 단일 머신 배포를 위해 launch_pool_disagg_server()를 사용할 수 있어요. 이 함수는 워커를 생성하며, 각 자식은 플랫폼을 초기화하기 전에 이 스크립트의 모듈 스코프를 다시 실행하므로 확산 import를 가드 안에 유지해요:
if __name__ == "__main__":
from sglang.multimodal_gen.runtime.launch_server import launch_pool_disagg_server
from sglang.multimodal_gen.runtime.server_args import ServerArgs
server_args = ServerArgs.from_kwargs(
model_path="Wan-AI/Wan2.1-T2V-14B-Diffusers",
denoiser_sp=4, denoiser_ulysses=2, denoiser_ring=2,
disagg_ib_device="mlx5_0",
)
launch_pool_disagg_server(
server_args,
encoder_gpus=[[0]],
denoiser_gpus=[[1, 2, 3, 4], [5, 6, 7, 8]],
decoder_gpus=[[0]],
)
아키텍처 (Architecture)
Client ─── HTTP (port 30000) ──► FastAPI Server
│
▼
DiffusionServer (ROUTER, scheduler_port)
┌───────┼───────┐
PUSH work │ │ │ PUSH work
▼ │ ▼
Encoder[0..N] │ Decoder[0..K]
│ │ ▲
P2P tensor │ │ │ P2P tensor
transfer ▼ │ │ transfer
Denoiser[0..M] ─────┘
│
PULL results ◄────┘ (decoder → DS → client)
요청 상태 머신 (Request State Machine)
PENDING → ENCODER_WAITING → ENCODER_RUNNING → ENCODER_DONE
│
DENOISING_WAITING → DENOISING_RUNNING → DENOISING_DONE
│
DECODER_WAITING → DECODER_RUNNING → DONE
어떤 상태든 FAILED 또는 TIMED_OUT으로 전환할 수 있어요.