Skip to main content
Split a monolithic text-to-video/image pipeline into independent Encoder, Denoiser, and Decoder roles, each running on its own GPU(s). A central DiffusionServer routes requests through the pipeline.

Quick Start

Disaggregation is controlled by a single flag: --disagg-role. Each component is launched independently, just like LLM PD disaggregation.
—disagg-roleWhat it runs
monolithic(Default) Standard single-server mode
encoderAll stages with the default RoleType.ENCODER affinity: InputValidationStage, TextEncodingStage (plus ImageEncodingStage / ImageVAEEncodingStage for image-conditioned pipelines), LatentPreparationStage, TimestepPreparationStage, and any model-specific “before denoising” stage (e.g. QwenImageLayeredBeforeDenoisingStage, GlmImageBeforeDenoisingStage).
denoiserDenoisingStage (and its subclasses: CausalDMDDenoisingStage, DmdDenoisingStage, LTX2AVDenoisingStage, LTX2RefinementStage, Hunyuan3DShapeDenoisingStage, …) — the DiT forward loop plus the scheduler stepping it drives.
decoderDecodingStage (VAE decode) and its subclasses (LTX2AVDecodingStage, HeliosDecodingStage, …).
serverDiffusionServer head node + HTTP server (no GPU)
Each stage declares its role via the role_affinity property on PipelineStage (default ENCODER). When --disagg-role is not monolithic, the pipeline only instantiates stages whose affinity matches, so the above table is the source of truth for what actually runs in each process.

Single-Machine Example (Verified)

The following commands have been tested end-to-end on an 8×H200 machine with Wan-AI/Wan2.1-T2V-1.3B-Diffusers. Each role runs on a separate GPU via --base-gpu-id; the server head node requires no GPU.
Tested result (8×H200): Encoder 2.3 s (TextEncoding) → Denoiser 312.8 s (50 steps, layerwise offload) → Decoder 7.1 s (VAE decode). Total ~322 s for 81-frame 1024×1024 video.
Tip: --base-gpu-id controls which physical GPU the role uses. Encoder and Decoder can share a GPU (e.g. both --base-gpu-id 0) to save resources, but make sure the combined GPU memory is sufficient.

Multi-Machine Example

The exact same CLI pattern — just replace 127.0.0.1 with actual IPs and add RDMA flags for direct transfer:
ZMQ handles startup order gracefully — instances and head can start in any order.

Multiple Instances per Role

Use semicolons in --*-urls to register multiple instances:

Port Convention

Result endpoints are derived deterministically from the head node’s --scheduler-port (default: 5555):
SocketPort
DS frontend (ROUTER)scheduler_port
Encoder result (PULL)scheduler_port + 1
Denoiser result (PULL)scheduler_port + 2
Decoder result (PULL)scheduler_port + 3
Role instances derive their result endpoint automatically from --disagg-server-addr. No manual endpoint configuration needed.

Transfer Mechanism

Tensor data between roles (encoder→denoiser, denoiser→decoder) is transferred via a P2P transfer engine. The DiffusionServer only routes lightweight control messages (alloc/push/ready); actual tensor data flows directly between instances. mooncake-transfer-engine is required for disaggregated diffusion. It provides RDMA for direct GPU-to-GPU data movement.

Transfer Flow

  1. Sender (encoder/denoiser) stages tensors: async copy to transfer buffer (GPU or CPU pinned, depending on GPUDirect support), overlapped with metadata JSON serialization.
  2. Sender sends transfer_staged control message to DiffusionServer (metadata only, no tensor data).
  3. DiffusionServer sends transfer_alloc to receiver → receiver allocates buffer slot → replies transfer_allocated.
  4. DiffusionServer sends transfer_push to receiver with sender’s address info.
  5. Receiver pulls data via transfer engine (Mooncake RDMA or mock), sends transfer_ready.
  6. Receiver loads tensors async on a dedicated transfer stream, overlapped with the previous request’s compute.
Decoder results (final output) flow back through DiffusionServer as raw ZMQ frames to the HTTP client.

RDMA Flags

FlagDefaultDescription
—disagg-p2p-hostname127.0.0.1RDMA-reachable hostname/IP of this instance
—disagg-ib-deviceNoneInfiniBand device (e.g., mlx5_0, mlx5_roce0)
—disagg-transfer-pool-size256 MiBPinned memory pool per instance
Set --disagg-p2p-hostname to the actual IP on each machine. For multi-machine, --disagg-ib-device specifies the RDMA NIC.

Per-Role Parallelism

FlagDescription
—encoder-tpEncoder tensor parallelism
—denoiser-tp / —denoiser-sp / —denoiser-ulysses / —denoiser-ringDenoiser parallelism
—decoder-spDecoder sequence parallelism
—decoder-tpDeprecated alias for —decoder-sp
If not specified, parallelism is auto-derived from --num-gpus.

Other Options

FlagDefaultDescription
—disagg-timeout600Timeout (seconds) for pending requests
—disagg-dispatch-policyround_robinround_robin or max_free_slots

Python API

For programmatic single-machine deployment, launch_pool_disagg_server() is available:

Architecture

Request State Machine

Any state can transition to FAILED or TIMED_OUT.