분리 확산 파이프라인

분리 확산 파이프라인 (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 없음)

각 단계는 PipelineStagerole_affinity 속성으로 자체 역할을 선언해요(기본값 ENCODER). --disagg-rolemonolithic이 아니면 파이프라인은 친화도가 일치하는 단계만 인스턴스화하므로, 위 표는 각 프로세스에서 실제로 실행되는 것의 진실의 근원이에요.

단일 머신 예시 (검증됨)

다음 명령은 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)

  1. Sender(encoder/denoiser)가 텐서를 스테이징: 비동기 복사로 전송 버퍼(GPUDirect 지원 여부에 따라 GPU 또는 CPU 핀)에, 메타데이터 JSON 직렬화와 겹쳐서.
  2. Sender가 DiffusionServer에 transfer_staged 제어 메시지 전송(메타데이터만, 텐서 데이터 없음).
  3. DiffusionServer가 receiver에 transfer_alloc 전송 → receiver가 버퍼 슬롯 할당 → transfer_allocated 응답.
  4. DiffusionServer가 sender의 주소 정보와 함께 receiver에 transfer_push 전송.
  5. Receiver가 전송 엔진(Mooncake RDMA 또는 mock)으로 데이터를 pull하고 transfer_ready 전송.
  6. 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으로 전환할 수 있어요.

더 알아보기