Repository navigation
[Perf] diffusion: Shard LTX-2 stage 2 over the CFG ranks as Ulysses - #45965
Merged
Merged
Conversation
LTX-2/2.3 two-stage stage 2 is a distilled refiner without CFG, so under CFG parallel it ran on the main rank while the other CFG rank idled. Every CFG rank already holds the same stage-1 result (the guided combine is an all-reduce and the upsample is replicated), so a stage without CFG can sequence-shard over the CFG ranks instead: - parallel_state builds the CFG ranks as one Ulysses group when cfg > 1 and sp == 1. It is a view over the CFG group's communicators, so it adds no NCCL buffers or process groups. - use_sequence_parallel_group() routes the SP accessors to a group for a scope; latent shards, RoPE offsets and the all-to-alls follow it. - StageParallelismType.SEQUENCE_PARALLEL_OVER_CFG_RANKS runs a stage on every CFG rank inside that scope. LTX2RefinementStage declares it. With SP of their own, CFG ranks keep the previous main-rank path. Stage 2 ends with the SP gather, so every rank holds the full latents for decode and the post-stage batch broadcast is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mickqian
requested review from
AgainstEntropy,
BBuf,
HaiShaw,
kevin-mii,
niehen6174,
ping1jing2 and
yichiche
as code owners
October 23, 2026 04:26
Collaborator
Author
|
/tag-and-rerun-ci |
… GPUs hidden On a 1-GPU runner the SRT MoE-EP group bound rank 1 to cuda:1 and the spawned worker died with an invalid device ordinal. Hide the GPUs and select the CPU platform in the workers, as the MOSS-Image parallel test does, and skip on ROCm where hidden GPUs break the sglang import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 23, 2026
Merged
… is long enough Eager stage 2 is host-bound at short sequences, and Ulysses adds a fixed ~75 ms of host work per step, so on 2xH200 the SP-over-CFG path was slower below ~2.1k video tokens per rank (CI's 768x512x25 refine stage: 590 -> 841 ms). The executor now asks the stage for its parallelism per batch via PipelineStage.parallelism_type_for, and LTX2RefinementStage takes the SP-over-CFG path only from CFG_SP_MIN_TOKENS_PER_RANK (2304) stage-2 tokens per rank, read from the packed stage-2 latents that every rank holds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
|
/tag-and-rerun-ci extra |
4 of 5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
LTX-2/2.3 two-stage runs stage 2, the distilled refiner, without CFG. Under
--enable-cfg-parallel,LTX2RefinementStagewasMAIN_RANK_ONLY, orMAIN_RANK_ONLY_AND_SEND_TO_OTHERSwith parallel VAE decode. So on 2 GPUs one GPU sat idle for all of stage 2. At 768x512x121 on 2xH200 that is 3 steps at 0.394 s each.TensorRT-LLM runs stage 2 as Ulysses 2 on the same ranks. In a 4xB300 benchmark round, sglang's stage 2 took 0.143 s/step x3 under breakable CUDA graphs, while TensorRT-LLM took 0.30 s for the whole of stage 2.
Sharding only pays off once stage 2 is long enough. At CI's 768x512x25 (1536 stage-2 tokens), it made
LTX2RefinementStageslower, so the new path is gated on the stage-2 sequence length.Modifications
At stage-2 entry, every CFG rank already holds the same inputs:
So a stage without CFG can be sequence-sharded over the CFG ranks instead of leaving them idle:
parallel_state.py: whencfg > 1andsp == 1, the CFG ranks also form one pure-Ulysses group (get_cfg_sequence_parallel_group()).SequenceParallelGroupCoordinator.over_groupbuilds it as a view over the CFG group's communicators. It adds no process group or NCCL communicator, and destroying the CFG group releases it.use_sequence_parallel_group()points the SP accessors at that group for one scope. Everything that readsget_sp_group()follows it: latent shard and gather, RoPE offsets, the Ulysses all-to-alls. The exchanges use the usual SP transports: the 2-rank CUDA-IPC all-to-all by default, otherwise NCCL.stages/base.py: newStageParallelismType.SEQUENCE_PARALLEL_OVER_CFG_RANKS.parallel_executor.pyruns such a stage on every CFG rank inside that scope.PipelineStage.parallelism_type_for(batch), which defaults toparallelism_type.LTX2RefinementStage.parallelism_type_forreturns the new type only when the group exists and stage 2 has at leastCFG_SP_MIN_TOKENS_PER_RANK(2304) video tokens per rank. It reads the sequence length of the packed stage-2 latents, which every rank holds with the same shape, so all ranks pick the same path. Shorter requests keep the previous main-rank path, and so do CFG ranks that have SP of their own (sp > 1). A warmup takes the same path as serving at the same shape, so breakable CUDA graphs capture what serving replays.Tests:
test/unit/test_sequence_parallel_over_cfg_ranks.pyruns 2 gloo ranks on CPU with cfg=2, sp=1. It checks:LTX2RefinementStagebatch one token per rank short of the threshold on the main rank only, and the other rank keeps its batch;No files are shared with #45942, #45943, #45944 or #45948, and this PR merges cleanly with all four. With parallel VAE decode, stage 2 now leaves the full latents on both ranks instead of broadcasting the batch. #45942 changes the decode itself, so the two PRs are independent. Stage 2's exchange runs on the 2-rank IPC transport, which #45948 (N-rank transport) does not touch.
Accuracy Tests
2xH200, LTX-2 two-stage 768x512x121,
--enable-cfg-parallel, against main:Speed Tests and Profiling
2xH200, LTX-2 two-stage 768x512x121,
--enable-cfg-parallel. Server benchmark with a fresh server per run, arms interleaved.Sequence-length gate
2xH200, two-stage 768x512,
--enable-cfg-parallel --dit-layerwise-offload false --performance-mode speed, eager. Server benchmark, one warm request per shape, then medians of the measured requests. LTX-2 25/57/89/121 frames ran in 2 interleaved rounds (n≥4); the other shapes ran on one server per arm (n=2–4). The server rounds the frame count so the latent frames split evenly over 2 GPUs, so the table lists the served frames. "Step" is[LTX2RefinementStage] average time per step, and "e2e" is the server's request time.LTX-2:
ltx_2_two_stage_t2vsettings (default serve args, 768x512x25):LTX2RefinementStagetakes 590 ms on main, 841 ms ungated, and 609 ms gated.LTX-2.3 (
Lightricks/LTX-2.3, same flags):Breakable CUDA graphs, with warmup at the serving shape (LTX-2, n=2):
Checklist
🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ✅ Run #39951066058
Latest PR Test (Extra): ✅ Run #39951066122
Latest PR Test (AMD ROCm 10): ❌ Run #39951066039