Skip to main content
SGLang Diffusion ships several parallelism strategies. Each one splits a different dimension of the DiT forward pass, which is exactly why they can be combined: the total GPU count is the product of the degrees,
This page is the map — what each axis does, which combinations are legal, and how to pick one. Per-axis depth lives in Sequence Parallelism, Encoder Parallelism (text/image encoders are a separate axis with their own knob), and the CLI reference.

The axes

Two strategies compose when they split different dimensions. TP and Ulysses both touch heads but compose serially — TP splits the projection weights, then Ulysses splits the activations of the TP-local heads. Ring and Ulysses compose because ring splits rows while Ulysses splits heads. Ring has no composition with a K/V-all-gather style of attention parallelism: both answer the same question (how a rank’s query rows see remote K/V), so they are alternatives for one slot, not complements.

What happens to the shapes

For tp_size = T, ulysses_degree = U, ring_degree = R, one attention runs:
The ring merge (online softmax) requires every rank in a ring group to hold the same heads over different rows; the group construction guarantees this. A K/V-gather (CP-style) variant fills the same slot differently: instead of R−1 overlapped hops it all-gathers K/V once and computes the local Q rows against the full sequence in one shot — fewer, larger transfers, paid for by holding the whole K/V per rank. Like ring it splits rows, so it adds no head constraint. When no SP degree is set explicitly, sp_degree=2 defaults to kv_gather_degree=2 — its measured-win zone — and higher degrees default to Ulysses. Ulysses groups are laid out on contiguous ranks and ring groups on strided ranks, so with a node-major rank mapping, Ulysses traffic stays on intra-node NVLink (all-to-all needs full-bisection bandwidth) while ring hops cross the slower interconnect where neighbor-only transfers overlap with compute. A mis-mapped layout stays numerically correct and silently loses the performance — worth checking when a sharded run is unexpectedly slow.

Constraints

  • num_attention_heads % tp_size == 0 — TP splits heads at the projections.
  • (num_attention_heads / tp_size) % ulysses_degree == 0 — Ulysses splits the TP-local heads. H % U == 0 alone is not sufficient: 56 heads pass with tp=2, ulysses=4 (28 % 4) and fail with tp=4, ulysses=4 (14 % 4).
  • Ring adds no head constraint (it splits rows), but the sequence — including any model-specific packing alignment — must divide by ulysses × ring, since ring adds an outer row split on top of Ulysses’s inner one.
  • Ring requires an attention backend that declares supports_ring_rotation() — the per-hop merge needs the kernel’s softmax LSE. fa and sage_attn declare it; the launcher auto-selects fa when unset.
  • USPAttention’s masked/tail-padded text path and its replicated-prefix, -suffix, and -kv-prefix paths all support ring: the sharded K/V rotates through the ring while the tail-pad or replicated portion is attended locally once and combined into the ring result with the same online-softmax merge. This covers the joint text+image attention most models use (flux, flux_2, qwen_image, zimage, glm_image, ernie_image, and others).
  • What still raises NotImplementedError under ring: USPAttention’s generic varlen path (multiple packed segments per row, as used by HunyuanVideo — no ring-aware rotation for it yet), and the legacy stacked-QKV UlyssesAttention layer, now down to one user (Wan’s VSA sparse-attention variant) that has no softmax LSE to merge and can’t gain ring support without a different kernel.
  • The launcher validates num_gpus against the product of the degrees and fails fast on any mismatch.

The Ulysses transport

The all-to-alls normally run over NCCL. On exactly 2 GPUs with peer-to-peer access, a CUDA-IPC transport replaces them by default: each rank writes its half directly into the peer’s mapped staging buffer, with GPU-side sequence counters instead of a NCCL rendezvous. An all-to-all is a permutation, never a reduction, so the transport cannot change results — outputs are bitwise identical to the NCCL path. Independently of the transport, the default path already packs the three Q/K/V input exchanges into one destination-major collective. A handful of models (e.g. LTX-2) instead opt into enable_packed_qkv_input_a2a, which pipelines three separate exchanges over a dedicated stream rather than merging them into one payload — a different trade-off, not a strict upgrade over the default.

Which axes tolerate crossing nodes

Each axis has a fixed communication pattern, and the pattern — volume per step, how often it fires, and whether it can hide behind compute — decides whether the axis survives the drop from NVLink to the inter-node fabric. Ordered from most to least cross-node friendly: Two caveats keep this a map rather than a promise. Cross-node launch (--nnodes/--node-rank/--dist-init-addr) is merged, and per-model ring support is broad now (see the constraints above) — but those two facts together still don’t add up to “any model, any node count.” The only configuration actually run end-to-end across nodes is the H3 recipe; other models’ ring support is same-node-validated so far, which means crossing nodes with them is untested, not disallowed. And the data-parallel row describes the design: the current implementation binds each replica’s ingress on the local host, so replicas spanning hosts additionally need per-replica host addressing before --dp-size can place one replica per node.

Choosing a configuration

Measured guidance rather than rules — the right combination depends on the model’s communication profile and the hardware topology, and legal does not mean profitable:
  • Multi-branch (true-CFG) models: CFG parallelism first. Branches run the whole DiT independently and combine once per step, avoiding per-layer communication entirely.
  • Single-branch image models on 2 GPUs: Ulysses and TP trade places by model. Communication-heavy DiTs measured faster with Ulysses; smaller DiTs with TP. Measure both; do not copy a winner across models.
  • Long video / packed sequences: Ulysses up to the head-divisibility limit, then ring for the remaining factor — sequence length scales past the head count where Ulysses alone cannot.
  • When Ulysses’s head divisibility blocks the degree you need (H/T not divisible by the target U): the row-splitting slot sidesteps it — ring today, or --kv-gather-degree (it splits rows, so it adds no head constraint either).
  • TP beyond 2 ranks rarely improves image-DiT latency: the per-block all-reduce grows with rank count faster than the GEMM savings.

Data parallelism

--dp-size N runs N full engine replicas on num_gpus / N GPUs each, every replica with its own ingress. Generation requests round-robin across replicas, realtime sessions stick to the replica holding their state, and control operations (weights, LoRA, memory occupation, shutdown) apply to every replica; replicas exchange nothing on the request path. Monolithic serving only, and the ingress currently binds on the local host — one replica per node needs per-replica host addressing first.