[diffusion] refactor: refactor cfg parallelism framework to support multi-branch CFG for LTX2 (#23736)
Co-authored-by: Mick <mickjagger19@icloud.com>
This commit is contained in:
+20
-2
@@ -251,6 +251,24 @@ MODELS = {
|
||||
],
|
||||
},
|
||||
# 13. Skill-only extra preset
|
||||
"ltx23-two-stage-cfg-parallel": {
|
||||
"path": "Lightricks/LTX-2.3",
|
||||
"prompt": "A beautiful sunset over the ocean",
|
||||
"negative_prompt": "shaky, glitchy, low quality, worst quality, deformed, distorted, disfigured, motion smear, motion artifacts, fused fingers, bad anatomy, weird hand, ugly, transition, static.",
|
||||
"seed": 1234,
|
||||
"extra_args": [
|
||||
"--pipeline-class-name=LTX2TwoStagePipeline",
|
||||
"--width=1536",
|
||||
"--height=1024",
|
||||
"--num-frames=121",
|
||||
"--fps=24",
|
||||
"--num-inference-steps=30",
|
||||
"--guidance-scale=3.0",
|
||||
"--num-gpus=2",
|
||||
"--cfg-parallel-size=2",
|
||||
],
|
||||
},
|
||||
# 14. Skill-only extra preset
|
||||
"hunyuanvideo": {
|
||||
"path": "hunyuanvideo-community/HunyuanVideo",
|
||||
"prompt": "A cat and a dog baking a cake together in a kitchen. The cat is carefully measuring flour, while the dog is stirring the batter with a wooden spoon. The kitchen is cozy, with sunlight streaming through the window.",
|
||||
@@ -263,7 +281,7 @@ MODELS = {
|
||||
"--num-inference-steps=30",
|
||||
],
|
||||
},
|
||||
# 14. Skill-only extra preset
|
||||
# 15. Skill-only extra preset
|
||||
# Requires: <repo>/inputs/diffusion_benchmark/figs/mova_single_person.jpg
|
||||
"mova-720p": {
|
||||
"path": "OpenMOSS-Team/MOVA-720p",
|
||||
@@ -279,7 +297,7 @@ MODELS = {
|
||||
"--num-inference-steps=2",
|
||||
],
|
||||
},
|
||||
# 15. Skill-only extra preset
|
||||
# 16. Skill-only extra preset
|
||||
"helios": {
|
||||
"path": "BestWishYsh/Helios-Base",
|
||||
"prompt": "A curious raccoon",
|
||||
|
||||
@@ -24,6 +24,7 @@ from sglang.multimodal_gen.configs.models.encoders import BaseEncoderOutput
|
||||
from sglang.multimodal_gen.configs.models.encoders.t5 import T5Config
|
||||
from sglang.multimodal_gen.configs.sample.sampling_params import DataType
|
||||
from sglang.multimodal_gen.configs.utils import update_config_from_args
|
||||
from sglang.multimodal_gen.runtime.distributed.cfg_policy import CFGPolicy
|
||||
from sglang.multimodal_gen.runtime.distributed.communication_op import (
|
||||
sequence_model_parallel_all_gather,
|
||||
)
|
||||
@@ -207,6 +208,7 @@ class PipelineConfig:
|
||||
# controls the timestep embedding generation
|
||||
should_use_guidance: bool = True
|
||||
embedded_cfg_scale: float = 6.0
|
||||
cfg_policy: CFGPolicy = field(default_factory=CFGPolicy)
|
||||
generator_device: str | None = None
|
||||
flow_shift: float | None = None
|
||||
disable_autocast: bool = False
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from typing import TYPE_CHECKING, Callable
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
|
||||
from sglang.multimodal_gen.runtime.distributed.cfg_policy import (
|
||||
_apply_cfg_postprocess,
|
||||
_unwrap,
|
||||
_wrap,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.distributed.communication_op import (
|
||||
cfg_model_parallel_all_gather,
|
||||
cfg_model_parallel_all_reduce,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.distributed.parallel_state import (
|
||||
get_cfg_group,
|
||||
get_classifier_free_guidance_rank,
|
||||
get_classifier_free_guidance_world_size,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.multimodal_gen.runtime.distributed.cfg_policy import (
|
||||
CFGBranch,
|
||||
CFGPolicy,
|
||||
)
|
||||
|
||||
# Tracks (n_branches, cfg_world_size, cfg_rank) tuples already logged so the
|
||||
# dispatch table is printed once per unique configuration, not once per step.
|
||||
_logged_dispatch_keys: set[tuple[int, int, int]] = set()
|
||||
|
||||
|
||||
def _run(
|
||||
predict_fn: Callable[["CFGBranch"], "torch.Tensor | tuple[torch.Tensor, ...]"],
|
||||
bid: int,
|
||||
branches,
|
||||
) -> tuple[torch.Tensor, ...]:
|
||||
branch = branches[bid]
|
||||
device = get_local_torch_device()
|
||||
local_branch = dataclasses.replace(
|
||||
branch,
|
||||
kwargs={
|
||||
k: v.to(device) if isinstance(v, torch.Tensor) else v
|
||||
for k, v in branch.kwargs.items()
|
||||
},
|
||||
)
|
||||
raw = predict_fn(local_branch)
|
||||
return _wrap(raw)
|
||||
|
||||
|
||||
def run_cfg_parallel(
|
||||
policy: "CFGPolicy",
|
||||
predict_fn: Callable[["CFGBranch"], "torch.Tensor | tuple[torch.Tensor, ...]"],
|
||||
) -> "list[torch.Tensor | tuple[torch.Tensor, ...]]":
|
||||
"""Dispatch CFG branches across ranks, all-gather results, return in branch order.
|
||||
|
||||
``predict_fn`` is a closure capturing all step-varying state
|
||||
(latent_model_input, timestep, model, etc.). It is called with each
|
||||
assigned ``CFGBranch`` and must return the raw ``_predict_noise`` output.
|
||||
|
||||
Idle ranks (cfg_world_size > n_branches) run branch 0 as a dummy forward
|
||||
to obtain tensor shapes for the all-gather.
|
||||
|
||||
Returns a list indexed to match ``policy.branches``, identical on every rank.
|
||||
"""
|
||||
|
||||
cfg_rank = get_classifier_free_guidance_rank()
|
||||
cfg_world_size = get_classifier_free_guidance_world_size()
|
||||
branches = policy.branches
|
||||
n_branches = len(branches)
|
||||
assignments = dispatch_branches(n_branches, cfg_world_size)
|
||||
branches_assigned_to_local_rank = assignments[cfg_rank]
|
||||
max_num_branches_per_rank = max(len(a) for a in assignments)
|
||||
|
||||
if cfg_world_size > n_branches:
|
||||
logger.warning_once(
|
||||
"cfg_parallel_size=%d > n_branches=%d; %d GPU(s) will be idle for CFG",
|
||||
cfg_world_size,
|
||||
n_branches,
|
||||
cfg_world_size - n_branches,
|
||||
)
|
||||
|
||||
dispatch_key = (n_branches, cfg_world_size, cfg_rank)
|
||||
if dispatch_key not in _logged_dispatch_keys:
|
||||
_logged_dispatch_keys.add(dispatch_key)
|
||||
branch_names = (
|
||||
[branches[i].name for i in branches_assigned_to_local_rank]
|
||||
if branches_assigned_to_local_rank
|
||||
else ["(idle)"]
|
||||
)
|
||||
logger.info(
|
||||
"CFG parallel dispatch: rank %d/%d -> [%s]",
|
||||
cfg_rank,
|
||||
cfg_world_size,
|
||||
", ".join(branch_names),
|
||||
)
|
||||
|
||||
# perform the forward for local branches
|
||||
predicts_from_local_branches: list[tuple[torch.Tensor, ...]] = [
|
||||
_run(predict_fn, bid, branches) for bid in branches_assigned_to_local_rank
|
||||
]
|
||||
|
||||
if not predicts_from_local_branches: # idle rank: run branch 0 for tensor shapes
|
||||
predicts_from_local_branches.append(_run(predict_fn, 0, branches))
|
||||
|
||||
# pad the predicts to the length of max_num_branches_per_rank, to prepare for the all-gather later
|
||||
ref = predicts_from_local_branches[0]
|
||||
while len(predicts_from_local_branches) < max_num_branches_per_rank:
|
||||
# TODO: cache this zero
|
||||
predicts_from_local_branches.append(tuple(torch.zeros_like(t) for t in ref))
|
||||
|
||||
# All-gather each slot and output element with separate_tensors=True.
|
||||
# all_slots[slot][elem] = list[Tensor] indexed by CFG rank; no reshape.
|
||||
all_slots: list[list[list[torch.Tensor]]] = [
|
||||
[
|
||||
cfg_model_parallel_all_gather(p, dim=0, separate_tensors=True)
|
||||
for p in slot_pred
|
||||
]
|
||||
for slot_pred in predicts_from_local_branches
|
||||
]
|
||||
|
||||
# reorder the results in branch order: branch bid -> owner rank, slot.
|
||||
n_elems = len(ref)
|
||||
final: list[torch.Tensor | tuple[torch.Tensor, ...]] = []
|
||||
for bid in range(n_branches):
|
||||
owner = bid % cfg_world_size
|
||||
slot = bid // cfg_world_size
|
||||
elems = tuple(all_slots[slot][ei][owner] for ei in range(n_elems))
|
||||
final.append(_unwrap(elems))
|
||||
return final
|
||||
|
||||
|
||||
def run_two_branch_cfg_parallel(
|
||||
policy: "CFGPolicy",
|
||||
predict_fn: Callable[["CFGBranch"], "torch.Tensor | tuple[torch.Tensor, ...]"],
|
||||
cfg_scale: float,
|
||||
batch,
|
||||
pipeline_config,
|
||||
) -> "torch.Tensor | tuple[torch.Tensor, ...]":
|
||||
"""Run standard two-pass CFG with the old all-reduce combine.
|
||||
|
||||
This keeps the existing WAN baselines: it avoids gathering both branch
|
||||
predictions, and it preserves the bf16 arithmetic order used before the
|
||||
multi-branch CFG dispatcher was added.
|
||||
"""
|
||||
|
||||
cfg_rank = get_classifier_free_guidance_rank()
|
||||
pred_t = _run(predict_fn, cfg_rank, policy.branches)
|
||||
|
||||
if cfg_rank == 0:
|
||||
partial = tuple(cfg_scale * p for p in pred_t)
|
||||
cond_t = pred_t
|
||||
else:
|
||||
partial = tuple((1 - cfg_scale) * p for p in pred_t)
|
||||
cond_t = tuple(torch.empty_like(p) for p in pred_t)
|
||||
|
||||
results = [cfg_model_parallel_all_reduce(p) for p in partial]
|
||||
cond_t = tuple(get_cfg_group().broadcast(p, src=0) for p in cond_t)
|
||||
results[0] = _apply_cfg_postprocess(results[0], cond_t[0], batch, pipeline_config)
|
||||
return _unwrap(tuple(results))
|
||||
|
||||
|
||||
def dispatch_branches(n_branches: int, n_ranks: int) -> list[list[int]]:
|
||||
"""Assign branches to ranks in Round-robin fashion
|
||||
|
||||
Returns a list of length ``n_ranks`` where element ``r`` contains the
|
||||
branch indices assigned to rank ``r``. Branch ``i`` goes to rank
|
||||
``i % n_ranks``.
|
||||
|
||||
Example: 4 passes, 2 GPUs:
|
||||
rank 0 -> [0, 2], rank 1 -> [1, 3]
|
||||
"""
|
||||
assignments: list[list[int]] = [[] for _ in range(n_ranks)]
|
||||
for i in range(n_branches):
|
||||
assignments[i % n_ranks].append(i)
|
||||
return assignments
|
||||
@@ -0,0 +1,159 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import torch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
|
||||
|
||||
|
||||
@dataclass
|
||||
class CFGBranch:
|
||||
"""Immutable specification of one CFG branch forward pass.
|
||||
|
||||
Built once before the denoising loop; read-only across all steps.
|
||||
"""
|
||||
|
||||
name: str
|
||||
is_conditional: bool
|
||||
kwargs: dict[str, Any]
|
||||
|
||||
def configure_batch(self, batch: "Req") -> None:
|
||||
"""Set batch state before this branch's forward pass.
|
||||
|
||||
Override for richer per-branch context (e.g. a branch index instead of
|
||||
a single boolean) when a model needs more than two guidance modes.
|
||||
"""
|
||||
batch.is_cfg_negative = not self.is_conditional
|
||||
|
||||
|
||||
@dataclass
|
||||
class CFGPolicy:
|
||||
"""Owns the CFG branches for one generation run and combines their predictions.
|
||||
|
||||
Built once before the denoising loop via ``build()``, then used read-only
|
||||
across all steps. Subclass and override ``build()`` / ``combine()`` for
|
||||
custom CFG schemes (N-branch, multi-output, etc.).
|
||||
|
||||
The default implementation handles standard 2-branch CFG. With a single
|
||||
branch (CFG disabled) ``combine()`` returns the prediction unchanged.
|
||||
"""
|
||||
|
||||
branches: list[CFGBranch] = field(default_factory=list)
|
||||
|
||||
def build(
|
||||
self,
|
||||
batch: "Req",
|
||||
image_kwargs: dict[str, Any],
|
||||
pos_cond_kwargs: dict[str, Any],
|
||||
neg_cond_kwargs: dict[str, Any],
|
||||
) -> "CFGPolicy":
|
||||
"""Return a new policy with branches populated.
|
||||
|
||||
Called once before the denoising loop. The returned policy is
|
||||
immutable for the lifetime of the run. Override to declare N branches.
|
||||
"""
|
||||
branches = [CFGBranch("conditional", True, {**image_kwargs, **pos_cond_kwargs})]
|
||||
if batch.do_classifier_free_guidance:
|
||||
branches.append(
|
||||
CFGBranch("unconditional", False, {**image_kwargs, **neg_cond_kwargs})
|
||||
)
|
||||
return dataclasses.replace(self, branches=branches)
|
||||
|
||||
def combine(
|
||||
self,
|
||||
predictions: list[torch.Tensor | tuple[torch.Tensor, ...]],
|
||||
batch: "Req",
|
||||
cfg_scale: float,
|
||||
pipeline_config: Any,
|
||||
*,
|
||||
cfg_parallel: bool = False,
|
||||
) -> torch.Tensor | tuple[torch.Tensor, ...]:
|
||||
"""Combine branch predictions into the final noise estimate.
|
||||
|
||||
Default: standard 2-branch CFG formula applied element-wise, followed
|
||||
by normalization / rescale / model-specific postprocess.
|
||||
Single-branch (CFG disabled): returns the prediction unchanged.
|
||||
Override for N-branch or multi-output models.
|
||||
"""
|
||||
if len(predictions) == 1:
|
||||
return predictions[0]
|
||||
pos_t = _wrap(predictions[0])
|
||||
neg_t = _wrap(predictions[1])
|
||||
if cfg_parallel:
|
||||
# Match the old CFG-parallel calculation: multiply the positive
|
||||
# prediction by cfg_scale and the negative prediction by
|
||||
# (1 - cfg_scale) before adding them. The serial CFG formula is
|
||||
# mathematically equivalent, but bf16 rounding changes WAN outputs.
|
||||
results = [
|
||||
cfg_scale * p + (1 - cfg_scale) * n for p, n in zip(pos_t, neg_t)
|
||||
]
|
||||
else:
|
||||
results = [n + cfg_scale * (p - n) for p, n in zip(pos_t, neg_t)]
|
||||
results[0] = _apply_cfg_postprocess(
|
||||
results[0], pos_t[0], batch, pipeline_config
|
||||
)
|
||||
return _unwrap(tuple(results))
|
||||
|
||||
|
||||
# Helpers used by CFGPolicy and run_cfg_parallel.
|
||||
|
||||
|
||||
def _wrap(
|
||||
pred: torch.Tensor | tuple[torch.Tensor, ...],
|
||||
) -> tuple[torch.Tensor, ...]:
|
||||
return pred if isinstance(pred, tuple) else (pred,)
|
||||
|
||||
|
||||
def _unwrap(
|
||||
pred: tuple[torch.Tensor, ...],
|
||||
) -> torch.Tensor | tuple[torch.Tensor, ...]:
|
||||
return pred[0] if len(pred) == 1 else pred
|
||||
|
||||
|
||||
def _apply_cfg_postprocess(
|
||||
noise_pred: torch.Tensor,
|
||||
noise_pred_cond: torch.Tensor,
|
||||
batch: "Req",
|
||||
pipeline_config: Any,
|
||||
) -> torch.Tensor:
|
||||
if batch.cfg_normalization and float(batch.cfg_normalization) > 0:
|
||||
noise_pred = _apply_cfg_normalization(
|
||||
noise_pred, noise_pred_cond, float(batch.cfg_normalization)
|
||||
)
|
||||
if batch.guidance_rescale > 0.0:
|
||||
noise_pred = _rescale_noise_cfg(
|
||||
noise_pred, noise_pred_cond, guidance_rescale=batch.guidance_rescale
|
||||
)
|
||||
return pipeline_config.postprocess_cfg_noise(batch, noise_pred, noise_pred_cond)
|
||||
|
||||
|
||||
def _apply_cfg_normalization(
|
||||
noise_pred: torch.Tensor,
|
||||
noise_pred_cond: torch.Tensor,
|
||||
cfg_normalization: float,
|
||||
) -> torch.Tensor:
|
||||
cond_f = noise_pred_cond.float()
|
||||
pred_f = noise_pred.float()
|
||||
ori_norm = torch.linalg.vector_norm(cond_f)
|
||||
new_norm = torch.linalg.vector_norm(pred_f)
|
||||
max_norm = ori_norm * cfg_normalization
|
||||
if new_norm > max_norm:
|
||||
noise_pred = noise_pred * (max_norm / new_norm)
|
||||
return noise_pred
|
||||
|
||||
|
||||
def _rescale_noise_cfg(
|
||||
noise_cfg: torch.Tensor,
|
||||
noise_pred_text: torch.Tensor,
|
||||
guidance_rescale: float = 0.0,
|
||||
) -> torch.Tensor:
|
||||
std_text = noise_pred_text.std(
|
||||
dim=list(range(1, noise_pred_text.ndim)), keepdim=True
|
||||
)
|
||||
std_cfg = noise_cfg.std(dim=list(range(1, noise_cfg.ndim)), keepdim=True)
|
||||
noise_pred_rescaled = noise_cfg * (std_text / std_cfg)
|
||||
return guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_cfg
|
||||
@@ -467,7 +467,7 @@ def get_dp_rank() -> int:
|
||||
def maybe_init_distributed_environment_and_model_parallel(
|
||||
tp_size: int,
|
||||
sp_size: int,
|
||||
enable_cfg_parallel: bool,
|
||||
cfg_degree: int = 1,
|
||||
ulysses_degree: int = 1,
|
||||
ring_degree: int = 1,
|
||||
dp_size: int = 1,
|
||||
@@ -508,7 +508,7 @@ def maybe_init_distributed_environment_and_model_parallel(
|
||||
)
|
||||
initialize_model_parallel(
|
||||
data_parallel_size=dp_size,
|
||||
classifier_free_guidance_degree=2 if enable_cfg_parallel else 1,
|
||||
classifier_free_guidance_degree=cfg_degree,
|
||||
tensor_parallel_degree=tp_size,
|
||||
ulysses_degree=ulysses_degree,
|
||||
ring_degree=ring_degree,
|
||||
|
||||
@@ -127,7 +127,7 @@ class GPUWorker:
|
||||
# initialize the distributed environment
|
||||
maybe_init_distributed_environment_and_model_parallel(
|
||||
tp_size=self.server_args.tp_size,
|
||||
enable_cfg_parallel=self.server_args.enable_cfg_parallel,
|
||||
cfg_degree=self.server_args.cfg_parallel_degree or 1,
|
||||
ulysses_degree=self.server_args.ulysses_degree,
|
||||
ring_degree=self.server_args.ring_degree,
|
||||
sp_size=self.server_args.sp_degree,
|
||||
|
||||
@@ -37,7 +37,6 @@ from sglang.multimodal_gen.runtime.cache.cache_dit_integration import (
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.disaggregation.roles import RoleType
|
||||
from sglang.multimodal_gen.runtime.distributed import (
|
||||
cfg_model_parallel_all_reduce,
|
||||
get_local_torch_device,
|
||||
get_sp_group,
|
||||
get_sp_world_size,
|
||||
@@ -45,12 +44,20 @@ from sglang.multimodal_gen.runtime.distributed import (
|
||||
get_world_group,
|
||||
get_world_size,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.distributed.cfg_parallel_utils import (
|
||||
run_cfg_parallel,
|
||||
run_two_branch_cfg_parallel,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.distributed.cfg_policy import (
|
||||
CFGPolicy,
|
||||
_unwrap,
|
||||
_wrap,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.distributed.communication_op import (
|
||||
sequence_model_parallel_all_gather,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.distributed.parallel_state import (
|
||||
get_cfg_group,
|
||||
get_classifier_free_guidance_rank,
|
||||
get_classifier_free_guidance_world_size,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.layers.attention.selector import get_attn_backend
|
||||
from sglang.multimodal_gen.runtime.layers.attention.STA_configuration import (
|
||||
@@ -118,6 +125,7 @@ class DenoisingContext:
|
||||
seq_len: int | None
|
||||
guidance: torch.Tensor
|
||||
is_warmup: bool
|
||||
cfg_policy: CFGPolicy | None = None
|
||||
trajectory_timesteps: list[torch.Tensor] = field(default_factory=list)
|
||||
trajectory_latents: list[torch.Tensor] = field(default_factory=list)
|
||||
extra: dict[str, Any] = field(default_factory=dict)
|
||||
@@ -733,6 +741,10 @@ class DenoisingStage(PipelineStage, RolloutDenoisingMixin):
|
||||
else:
|
||||
neg_cond_kwargs = {}
|
||||
|
||||
cfg_policy = server_args.pipeline_config.cfg_policy.build(
|
||||
batch, image_kwargs, pos_cond_kwargs, neg_cond_kwargs
|
||||
)
|
||||
|
||||
return DenoisingContext(
|
||||
scheduler=scheduler,
|
||||
extra_step_kwargs=extra_step_kwargs,
|
||||
@@ -751,6 +763,7 @@ class DenoisingStage(PipelineStage, RolloutDenoisingMixin):
|
||||
seq_len=seq_len,
|
||||
guidance=guidance,
|
||||
is_warmup=batch.is_warmup,
|
||||
cfg_policy=cfg_policy,
|
||||
)
|
||||
|
||||
def _before_denoising_loop(
|
||||
@@ -912,9 +925,7 @@ class DenoisingStage(PipelineStage, RolloutDenoisingMixin):
|
||||
attn_metadata=step.attn_metadata,
|
||||
target_dtype=ctx.target_dtype,
|
||||
current_guidance_scale=step.current_guidance_scale,
|
||||
image_kwargs=ctx.image_kwargs,
|
||||
pos_cond_kwargs=ctx.pos_cond_kwargs,
|
||||
neg_cond_kwargs=ctx.neg_cond_kwargs,
|
||||
cfg_policy=ctx.cfg_policy,
|
||||
server_args=server_args,
|
||||
guidance=ctx.guidance,
|
||||
latents=ctx.latents,
|
||||
@@ -1344,190 +1355,72 @@ class DenoisingStage(PipelineStage, RolloutDenoisingMixin):
|
||||
disable = local_rank != 0
|
||||
return tqdm(iterable=iterable, total=total, disable=disable)
|
||||
|
||||
def _rescale_noise_cfg(
|
||||
self, noise_cfg, noise_pred_text, guidance_rescale=0.0
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
Rescale noise prediction according to guidance_rescale.
|
||||
|
||||
Based on findings of "Common Diffusion Noise Schedules and Sample Steps are Flawed"
|
||||
(https://arxiv.org/pdf/2305.08891.pdf), Section 3.4.
|
||||
|
||||
Args:
|
||||
noise_cfg: The noise prediction with guidance.
|
||||
noise_pred_text: The text-conditioned noise prediction.
|
||||
guidance_rescale: The guidance rescale factor.
|
||||
|
||||
Returns:
|
||||
The rescaled noise prediction.
|
||||
"""
|
||||
std_text = noise_pred_text.std(
|
||||
dim=list(range(1, noise_pred_text.ndim)), keepdim=True
|
||||
)
|
||||
std_cfg = noise_cfg.std(dim=list(range(1, noise_cfg.ndim)), keepdim=True)
|
||||
# Rescale the results from guidance (fixes overexposure)
|
||||
noise_pred_rescaled = noise_cfg * (std_text / std_cfg)
|
||||
# Mix with the original results from guidance by factor guidance_rescale
|
||||
noise_cfg = (
|
||||
guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_cfg
|
||||
)
|
||||
return noise_cfg
|
||||
|
||||
def _apply_cfg_normalization(
|
||||
self,
|
||||
noise_pred: torch.Tensor,
|
||||
noise_pred_cond: torch.Tensor,
|
||||
cfg_normalization: float,
|
||||
) -> torch.Tensor:
|
||||
factor = float(cfg_normalization)
|
||||
cond_f = noise_pred_cond.float()
|
||||
pred_f = noise_pred.float()
|
||||
ori_norm = torch.linalg.vector_norm(cond_f)
|
||||
new_norm = torch.linalg.vector_norm(pred_f)
|
||||
max_norm = ori_norm * factor
|
||||
|
||||
if new_norm > max_norm:
|
||||
noise_pred = noise_pred * (max_norm / new_norm)
|
||||
return noise_pred
|
||||
|
||||
def _apply_cfg_normalization_parallel(
|
||||
self,
|
||||
noise_pred: torch.Tensor,
|
||||
noise_pred_cond: torch.Tensor | None,
|
||||
cfg_normalization: float,
|
||||
cfg_rank: int,
|
||||
) -> torch.Tensor:
|
||||
# In cfg-parallel mode, only rank 0 has the conditional branch locally,
|
||||
# so the reference norm has to be broadcast to the other ranks
|
||||
factor = float(cfg_normalization)
|
||||
pred_f = noise_pred.float()
|
||||
new_norm = torch.linalg.vector_norm(pred_f)
|
||||
if cfg_rank == 0:
|
||||
assert noise_pred_cond is not None
|
||||
ori_norm = torch.linalg.vector_norm(noise_pred_cond.float())
|
||||
else:
|
||||
ori_norm = torch.empty_like(new_norm)
|
||||
ori_norm = get_cfg_group().broadcast(ori_norm, src=0)
|
||||
max_norm = ori_norm * factor
|
||||
|
||||
if new_norm > max_norm:
|
||||
noise_pred = noise_pred * (max_norm / new_norm)
|
||||
return noise_pred
|
||||
|
||||
def _apply_guidance_rescale_parallel(
|
||||
self,
|
||||
noise_pred: torch.Tensor,
|
||||
noise_pred_cond: torch.Tensor | None,
|
||||
guidance_rescale: float,
|
||||
cfg_rank: int,
|
||||
) -> torch.Tensor:
|
||||
# Guidance rescale is still defined against the conditional branch, so
|
||||
# cfg-parallel needs to broadcast that statistic to every rank
|
||||
std_cfg = noise_pred.std(dim=list(range(1, noise_pred.ndim)), keepdim=True)
|
||||
if cfg_rank == 0:
|
||||
assert noise_pred_cond is not None
|
||||
std_text = noise_pred_cond.std(
|
||||
dim=list(range(1, noise_pred_cond.ndim)), keepdim=True
|
||||
)
|
||||
else:
|
||||
std_text = torch.empty_like(std_cfg)
|
||||
std_text = get_cfg_group().broadcast(std_text, src=0)
|
||||
noise_pred_rescaled = noise_pred * (std_text / std_cfg)
|
||||
return (
|
||||
guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_pred
|
||||
)
|
||||
|
||||
def _apply_model_specific_cfg_postprocess(
|
||||
def _predict_noise_with_cfg(
|
||||
self,
|
||||
current_model: nn.Module,
|
||||
latent_model_input: torch.Tensor,
|
||||
timestep,
|
||||
batch: Req,
|
||||
noise_pred: torch.Tensor,
|
||||
noise_pred_cond: torch.Tensor | None,
|
||||
cfg_rank: int,
|
||||
) -> torch.Tensor:
|
||||
# keep model-specific CFG behavior out of the main denoising loop
|
||||
# for cfg-parallel, broadcast cond noise first so the hook sees the same
|
||||
# inputs as the serial path.
|
||||
if cfg_rank == 0:
|
||||
assert noise_pred_cond is not None
|
||||
cond_noise = noise_pred_cond
|
||||
timestep_index: int,
|
||||
attn_metadata,
|
||||
target_dtype,
|
||||
current_guidance_scale,
|
||||
cfg_policy: CFGPolicy,
|
||||
server_args: ServerArgs,
|
||||
guidance: torch.Tensor,
|
||||
latents: torch.Tensor,
|
||||
) -> "torch.Tensor | tuple[torch.Tensor, ...]":
|
||||
"""Run all CFG branch forward passes and combine into the final noise estimate."""
|
||||
cfg_scale = server_args.pipeline_config.get_classifier_free_guidance_scale(
|
||||
batch, current_guidance_scale
|
||||
)
|
||||
|
||||
def predict_fn(branch):
|
||||
branch.configure_batch(batch)
|
||||
with set_forward_context(
|
||||
current_timestep=timestep_index,
|
||||
attn_metadata=attn_metadata,
|
||||
forward_batch=batch,
|
||||
):
|
||||
raw = self._predict_noise(
|
||||
current_model=current_model,
|
||||
latent_model_input=latent_model_input,
|
||||
timestep=timestep,
|
||||
target_dtype=target_dtype,
|
||||
guidance=guidance,
|
||||
**branch.kwargs,
|
||||
)
|
||||
pred_t = _wrap(raw)
|
||||
if len(pred_t) == 1:
|
||||
pred_t = (
|
||||
server_args.pipeline_config.slice_noise_pred(pred_t[0], latents),
|
||||
)
|
||||
return _unwrap(pred_t)
|
||||
|
||||
if server_args.enable_cfg_parallel:
|
||||
if (
|
||||
len(cfg_policy.branches) == 2
|
||||
and get_classifier_free_guidance_world_size() == 2
|
||||
):
|
||||
return run_two_branch_cfg_parallel(
|
||||
cfg_policy,
|
||||
predict_fn,
|
||||
cfg_scale,
|
||||
batch,
|
||||
server_args.pipeline_config,
|
||||
)
|
||||
# perform cfg branches in parallel, following the cfg policy
|
||||
predictions = run_cfg_parallel(cfg_policy, predict_fn)
|
||||
else:
|
||||
# TODO: cache this?
|
||||
cond_noise = torch.empty_like(noise_pred)
|
||||
cond_noise = get_cfg_group().broadcast(cond_noise, src=0)
|
||||
# perform cfg branches one-by-one locally
|
||||
predictions = [predict_fn(branch) for branch in cfg_policy.branches]
|
||||
|
||||
# qwen-image uses true_cfg_scale, match the per-token norm back to the conditional branch
|
||||
return self.server_args.pipeline_config.postprocess_cfg_noise(
|
||||
batch, noise_pred, cond_noise
|
||||
)
|
||||
|
||||
def _combine_cfg_parallel(
|
||||
self,
|
||||
batch: Req,
|
||||
noise_pred_cond: torch.Tensor | None,
|
||||
noise_pred_uncond: torch.Tensor | None,
|
||||
cfg_scale: float,
|
||||
cfg_rank: int,
|
||||
) -> torch.Tensor:
|
||||
# cfg-parallel splits cond / uncond across ranks and reconstructs the
|
||||
# final CFG result with an all-reduce.
|
||||
if cfg_rank == 0:
|
||||
assert noise_pred_cond is not None
|
||||
partial = cfg_scale * noise_pred_cond
|
||||
else:
|
||||
assert noise_pred_uncond is not None
|
||||
partial = (1 - cfg_scale) * noise_pred_uncond
|
||||
|
||||
noise_pred = cfg_model_parallel_all_reduce(partial)
|
||||
|
||||
if batch.cfg_normalization and float(batch.cfg_normalization) > 0:
|
||||
noise_pred = self._apply_cfg_normalization_parallel(
|
||||
noise_pred,
|
||||
noise_pred_cond,
|
||||
batch.cfg_normalization,
|
||||
cfg_rank,
|
||||
)
|
||||
|
||||
if batch.guidance_rescale > 0.0:
|
||||
noise_pred = self._apply_guidance_rescale_parallel(
|
||||
noise_pred,
|
||||
noise_pred_cond,
|
||||
batch.guidance_rescale,
|
||||
cfg_rank,
|
||||
)
|
||||
|
||||
return self._apply_model_specific_cfg_postprocess(
|
||||
batch, noise_pred, noise_pred_cond, cfg_rank
|
||||
)
|
||||
|
||||
def _combine_cfg_serial(
|
||||
self,
|
||||
batch: Req,
|
||||
noise_pred_cond: torch.Tensor,
|
||||
noise_pred_uncond: torch.Tensor,
|
||||
cfg_scale: float,
|
||||
) -> torch.Tensor:
|
||||
# Serial CFG keeps both branches local and is the reference path that
|
||||
# model-specific postprocessing hooks should match.
|
||||
noise_pred = noise_pred_uncond + cfg_scale * (
|
||||
noise_pred_cond - noise_pred_uncond
|
||||
)
|
||||
|
||||
if batch.cfg_normalization and float(batch.cfg_normalization) > 0:
|
||||
noise_pred = self._apply_cfg_normalization(
|
||||
noise_pred,
|
||||
noise_pred_cond,
|
||||
batch.cfg_normalization,
|
||||
)
|
||||
|
||||
if batch.guidance_rescale > 0.0:
|
||||
noise_pred = self._rescale_noise_cfg(
|
||||
noise_pred,
|
||||
noise_pred_cond,
|
||||
guidance_rescale=batch.guidance_rescale,
|
||||
)
|
||||
|
||||
return self.server_args.pipeline_config.postprocess_cfg_noise(
|
||||
batch, noise_pred, noise_pred_cond
|
||||
return cfg_policy.combine(
|
||||
predictions,
|
||||
batch,
|
||||
cfg_scale,
|
||||
server_args.pipeline_config,
|
||||
cfg_parallel=server_args.enable_cfg_parallel,
|
||||
)
|
||||
|
||||
def _build_attn_metadata(
|
||||
@@ -1691,110 +1584,6 @@ class DenoisingStage(PipelineStage, RolloutDenoisingMixin):
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def _predict_noise_with_cfg(
|
||||
self,
|
||||
current_model: nn.Module,
|
||||
latent_model_input: torch.Tensor,
|
||||
timestep,
|
||||
batch: Req,
|
||||
timestep_index: int,
|
||||
attn_metadata,
|
||||
target_dtype,
|
||||
current_guidance_scale,
|
||||
image_kwargs: dict[str, Any],
|
||||
pos_cond_kwargs: dict[str, Any],
|
||||
neg_cond_kwargs: dict[str, Any],
|
||||
server_args,
|
||||
guidance,
|
||||
latents,
|
||||
):
|
||||
"""
|
||||
Predict the noise residual with classifier-free guidance.
|
||||
|
||||
Args:
|
||||
current_model: The transformer model to use for the current step.
|
||||
latent_model_input: The input latents for the model.
|
||||
timestep: The expanded timestep tensor.
|
||||
batch: The current batch information.
|
||||
timestep_index: The current timestep index.
|
||||
attn_metadata: Attention metadata for custom backends.
|
||||
target_dtype: The target data type for autocasting.
|
||||
current_guidance_scale: The guidance scale for the current step.
|
||||
image_kwargs: Keyword arguments for image conditioning.
|
||||
pos_cond_kwargs: Keyword arguments for positive prompt conditioning.
|
||||
neg_cond_kwargs: Keyword arguments for negative prompt conditioning.
|
||||
|
||||
Returns:
|
||||
The predicted noise.
|
||||
"""
|
||||
noise_pred_cond: torch.Tensor | None = None
|
||||
noise_pred_uncond: torch.Tensor | None = None
|
||||
cfg_rank = get_classifier_free_guidance_rank()
|
||||
# positive pass
|
||||
if not (server_args.enable_cfg_parallel and cfg_rank != 0):
|
||||
batch.is_cfg_negative = False
|
||||
with set_forward_context(
|
||||
current_timestep=timestep_index,
|
||||
attn_metadata=attn_metadata,
|
||||
forward_batch=batch,
|
||||
):
|
||||
noise_pred_cond = self._predict_noise(
|
||||
current_model=current_model,
|
||||
latent_model_input=latent_model_input,
|
||||
timestep=timestep,
|
||||
target_dtype=target_dtype,
|
||||
guidance=guidance,
|
||||
**image_kwargs,
|
||||
**pos_cond_kwargs,
|
||||
)
|
||||
# TODO: can it be moved to after _predict_noise_with_cfg?
|
||||
noise_pred_cond = server_args.pipeline_config.slice_noise_pred(
|
||||
noise_pred_cond, latents
|
||||
)
|
||||
if not batch.do_classifier_free_guidance:
|
||||
return noise_pred_cond
|
||||
|
||||
# negative pass
|
||||
if not server_args.enable_cfg_parallel or cfg_rank != 0:
|
||||
batch.is_cfg_negative = True
|
||||
with set_forward_context(
|
||||
current_timestep=timestep_index,
|
||||
attn_metadata=attn_metadata,
|
||||
forward_batch=batch,
|
||||
):
|
||||
noise_pred_uncond = self._predict_noise(
|
||||
current_model=current_model,
|
||||
latent_model_input=latent_model_input,
|
||||
timestep=timestep,
|
||||
target_dtype=target_dtype,
|
||||
guidance=guidance,
|
||||
**image_kwargs,
|
||||
**neg_cond_kwargs,
|
||||
)
|
||||
noise_pred_uncond = server_args.pipeline_config.slice_noise_pred(
|
||||
noise_pred_uncond, latents
|
||||
)
|
||||
cfg_scale = server_args.pipeline_config.get_classifier_free_guidance_scale(
|
||||
batch, current_guidance_scale
|
||||
)
|
||||
|
||||
if server_args.enable_cfg_parallel:
|
||||
return self._combine_cfg_parallel(
|
||||
batch,
|
||||
noise_pred_cond,
|
||||
noise_pred_uncond,
|
||||
cfg_scale,
|
||||
cfg_rank,
|
||||
)
|
||||
|
||||
assert noise_pred_cond is not None and noise_pred_uncond is not None
|
||||
return self._combine_cfg_serial(
|
||||
batch,
|
||||
noise_pred_cond,
|
||||
noise_pred_uncond,
|
||||
cfg_scale,
|
||||
)
|
||||
|
||||
def prepare_sta_param(self, batch: Req, server_args: ServerArgs):
|
||||
"""
|
||||
Prepare Sliding Tile Attention (STA) parameters and settings.
|
||||
|
||||
@@ -7,6 +7,9 @@ from sglang.multimodal_gen.runtime.pipelines_core.diffusion_scheduler_utils impo
|
||||
clone_scheduler_runtime,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.base import (
|
||||
StageParallelismType,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.ltx_2_denoising import (
|
||||
LTX2DenoisingStage,
|
||||
)
|
||||
@@ -138,6 +141,14 @@ class LTX2RefinementStage(LTX2AVDenoisingStage):
|
||||
)
|
||||
]
|
||||
|
||||
@property
|
||||
def parallelism_type(self) -> StageParallelismType:
|
||||
# Stage 2 is distilled and always runs with CFG disabled, so non-main
|
||||
# CFG ranks should wait at a barrier rather than run a redundant forward.
|
||||
if self.server_args.enable_cfg_parallel:
|
||||
return StageParallelismType.MAIN_RANK_ONLY
|
||||
return StageParallelismType.REPLICATED
|
||||
|
||||
@staticmethod
|
||||
def _randn_like_with_batch_generators(
|
||||
reference_tensor: torch.Tensor, batch: Req
|
||||
|
||||
@@ -8,12 +8,29 @@ from diffusers.utils.torch_utils import randn_tensor
|
||||
from sglang.multimodal_gen.configs.pipeline_configs.ltx_2 import (
|
||||
is_ltx23_native_variant,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.distributed import get_sp_world_size
|
||||
from sglang.multimodal_gen.runtime.distributed import (
|
||||
get_local_torch_device,
|
||||
get_sp_world_size,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.distributed.cfg_parallel_utils import (
|
||||
dispatch_branches,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.distributed.communication_op import (
|
||||
cfg_model_parallel_all_gather,
|
||||
cfg_model_parallel_all_reduce,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.distributed.parallel_state import (
|
||||
get_classifier_free_guidance_rank,
|
||||
get_classifier_free_guidance_world_size,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.forward_context import set_forward_context
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.diffusion_scheduler_utils import (
|
||||
clone_scheduler_runtime,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.base import (
|
||||
StageParallelismType,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.denoising import (
|
||||
DenoisingContext,
|
||||
DenoisingStage,
|
||||
@@ -144,6 +161,179 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
dtype=reference_tensor.dtype,
|
||||
)
|
||||
|
||||
@property
|
||||
def parallelism_type(self) -> StageParallelismType:
|
||||
if self.server_args.enable_cfg_parallel:
|
||||
return StageParallelismType.CFG_PARALLEL
|
||||
return StageParallelismType.REPLICATED
|
||||
|
||||
@staticmethod
|
||||
def _combine_cfg_parallel_av(
|
||||
video: torch.Tensor,
|
||||
audio: torch.Tensor,
|
||||
guidance_scale: float,
|
||||
cfg_rank: int,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
"""All-reduce video and audio predictions across CFG ranks.
|
||||
|
||||
Rank 0 (cond) contributes ``guidance_scale * pred``.
|
||||
Rank 1 (uncond) contributes ``(1 - guidance_scale) * pred``.
|
||||
Higher CFG ranks, if configured for multi-pass guidance, contribute
|
||||
zeros on the two-branch path.
|
||||
The sum reconstructs ``uncond + guidance_scale * (cond - uncond)``.
|
||||
"""
|
||||
if cfg_rank == 0:
|
||||
video_partial = guidance_scale * video
|
||||
audio_partial = guidance_scale * audio
|
||||
elif cfg_rank == 1:
|
||||
video_partial = (1.0 - guidance_scale) * video
|
||||
audio_partial = (1.0 - guidance_scale) * audio
|
||||
else:
|
||||
video_partial = torch.zeros_like(video)
|
||||
audio_partial = torch.zeros_like(audio)
|
||||
return (
|
||||
cfg_model_parallel_all_reduce(video_partial),
|
||||
cfg_model_parallel_all_reduce(audio_partial),
|
||||
)
|
||||
|
||||
def _run_legacy_one_stage_multi_branch_cfg_parallel(
|
||||
self,
|
||||
*,
|
||||
base_model_kwargs: dict[str, object],
|
||||
ctx: "LTX2DenoisingContext",
|
||||
step: "DenoisingStepState",
|
||||
encoder_hidden_states: torch.Tensor,
|
||||
audio_encoder_hidden_states: torch.Tensor,
|
||||
encoder_attention_mask: torch.Tensor | None,
|
||||
negative_encoder_hidden_states: torch.Tensor,
|
||||
negative_audio_encoder_hidden_states: torch.Tensor,
|
||||
negative_encoder_attention_mask: torch.Tensor | None,
|
||||
need_perturbed: bool,
|
||||
need_modality: bool,
|
||||
stage1_guider_params: dict[str, object],
|
||||
) -> dict[str, tuple[torch.Tensor, torch.Tensor]]:
|
||||
"""Multi-branch CFG parallel for the legacy LTX-2.3 one-stage path.
|
||||
|
||||
Distributes up to 4 forward passes (cond, neg, perturbed, modality)
|
||||
across CFG ranks via round-robin. Each rank runs only its assigned
|
||||
passes, then an all-gather collects every output so all ranks can
|
||||
compute the guidance combination locally.
|
||||
"""
|
||||
cfg_rank = get_classifier_free_guidance_rank()
|
||||
cfg_world_size = get_classifier_free_guidance_world_size()
|
||||
|
||||
# Build kwargs for every pass in canonical order.
|
||||
all_passes: list[tuple[str, dict[str, object]]] = [
|
||||
(
|
||||
"cond",
|
||||
self._build_ltx2_model_kwargs(
|
||||
ctx,
|
||||
base_model_kwargs,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
audio_encoder_hidden_states=audio_encoder_hidden_states,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
),
|
||||
),
|
||||
(
|
||||
"neg",
|
||||
self._build_ltx2_model_kwargs(
|
||||
ctx,
|
||||
base_model_kwargs,
|
||||
encoder_hidden_states=negative_encoder_hidden_states,
|
||||
audio_encoder_hidden_states=negative_audio_encoder_hidden_states,
|
||||
encoder_attention_mask=negative_encoder_attention_mask,
|
||||
),
|
||||
),
|
||||
]
|
||||
if need_perturbed:
|
||||
all_passes.append(
|
||||
(
|
||||
"perturbed",
|
||||
self._build_ltx2_model_kwargs(
|
||||
ctx,
|
||||
base_model_kwargs,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
audio_encoder_hidden_states=audio_encoder_hidden_states,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
skip_video_self_attn_blocks=tuple(
|
||||
stage1_guider_params["video_stg_blocks"]
|
||||
),
|
||||
skip_audio_self_attn_blocks=tuple(
|
||||
stage1_guider_params["audio_stg_blocks"]
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
if need_modality:
|
||||
all_passes.append(
|
||||
(
|
||||
"modality",
|
||||
self._build_ltx2_model_kwargs(
|
||||
ctx,
|
||||
base_model_kwargs,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
audio_encoder_hidden_states=audio_encoder_hidden_states,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
disable_a2v_cross_attn=True,
|
||||
disable_v2a_cross_attn=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
pass_names = [name for name, _ in all_passes]
|
||||
n_passes = len(pass_names)
|
||||
assignments = dispatch_branches(n_passes, cfg_world_size)
|
||||
my_indices = assignments[cfg_rank]
|
||||
max_local = max(len(a) for a in assignments)
|
||||
|
||||
local_videos: list[torch.Tensor] = []
|
||||
local_audios: list[torch.Tensor] = []
|
||||
|
||||
indices_to_run = my_indices if my_indices else [0]
|
||||
with set_forward_context(
|
||||
current_timestep=step.step_index, attn_metadata=step.attn_metadata
|
||||
):
|
||||
for idx in indices_to_run:
|
||||
_, kwargs = all_passes[idx]
|
||||
v, a = step.current_model(**kwargs)
|
||||
local_videos.append(v.float())
|
||||
local_audios.append(a.float())
|
||||
|
||||
if not my_indices:
|
||||
# This rank has no real branch, but it still needs tensor shapes for all-gather.
|
||||
# The dummy branch above provides the shapes; zeros keep this rank from contributing.
|
||||
local_videos = [torch.zeros_like(local_videos[0])]
|
||||
local_audios = [torch.zeros_like(local_audios[0])]
|
||||
|
||||
# Pad to max_local for unbalanced cases (n_passes not divisible by n_ranks).
|
||||
while len(local_videos) < max_local:
|
||||
local_videos.append(torch.zeros_like(local_videos[0]))
|
||||
local_audios.append(torch.zeros_like(local_audios[0]))
|
||||
|
||||
# Stack -> [max_local, B, ...], flatten to [max_local*B, ...] for all-gather.
|
||||
local_v = torch.stack(local_videos, dim=0)
|
||||
local_a = torch.stack(local_audios, dim=0)
|
||||
B = local_v.shape[1]
|
||||
local_v_flat = local_v.reshape(max_local * B, *local_v.shape[2:])
|
||||
local_a_flat = local_a.reshape(max_local * B, *local_a.shape[2:])
|
||||
|
||||
# All-gather along batch dim -> [cfg_world_size * max_local * B, ...].
|
||||
all_v_flat = cfg_model_parallel_all_gather(local_v_flat, dim=0)
|
||||
all_a_flat = cfg_model_parallel_all_gather(local_a_flat, dim=0)
|
||||
|
||||
# Reshape to [cfg_world_size, max_local, B, ...].
|
||||
all_v = all_v_flat.reshape(cfg_world_size, max_local, B, *all_v_flat.shape[1:])
|
||||
all_a = all_a_flat.reshape(cfg_world_size, max_local, B, *all_a_flat.shape[1:])
|
||||
|
||||
# Branch i was run by rank (i % cfg_world_size) at slot (i // cfg_world_size).
|
||||
return {
|
||||
name: (
|
||||
all_v[i % cfg_world_size, i // cfg_world_size],
|
||||
all_a[i % cfg_world_size, i // cfg_world_size],
|
||||
)
|
||||
for i, name in enumerate(pass_names)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_video_latent_num_frames_for_model(
|
||||
batch: Req, server_args: ServerArgs, latents: torch.Tensor
|
||||
@@ -200,7 +390,7 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
return latents[:, :orig_s, :].contiguous()
|
||||
|
||||
def _maybe_enable_cache_dit(self, num_inference_steps: int, batch: Req) -> None:
|
||||
"""Disable cache-dit for TI2V-style requests (image-conditioned), to avoid stale activations.
|
||||
"""Disable cache-dit for TI2V-style requests to avoid stale activations.
|
||||
|
||||
NOTE: base denoising stage calls this hook with (num_inference_steps, batch).
|
||||
"""
|
||||
@@ -231,6 +421,47 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
factor = rescale_scale * factor + (1.0 - rescale_scale)
|
||||
return pred * factor
|
||||
|
||||
@classmethod
|
||||
def _ltx2_combine_guided_x0_parallel(
|
||||
cls,
|
||||
*,
|
||||
latents: torch.Tensor,
|
||||
local_velocities: dict[str, torch.Tensor],
|
||||
sigma: float | torch.Tensor,
|
||||
cfg_scale: float,
|
||||
stg_scale: float,
|
||||
rescale_scale: float,
|
||||
modality_scale: float,
|
||||
) -> torch.Tensor:
|
||||
"""Combine stage-1 guidance passes that were split across CFG ranks.
|
||||
|
||||
Each pass is one model forward with a different conditioning setup:
|
||||
positive prompt, negative prompt, attention-disabled perturbation, or
|
||||
audio/video cross-attention disabled. A rank only owns some passes, so
|
||||
it contributes weighted x0 terms for those passes and all-reduce
|
||||
reconstructs the full guided x0 on every rank.
|
||||
"""
|
||||
coefficients = {
|
||||
"cond": cfg_scale + stg_scale + modality_scale - 1.0,
|
||||
"neg": 1.0 - cfg_scale,
|
||||
"perturbed": -stg_scale,
|
||||
"modality": 1.0 - modality_scale,
|
||||
}
|
||||
first_velocity = next(iter(local_velocities.values()))
|
||||
template = cls._ltx2_velocity_to_x0(latents, first_velocity, sigma)
|
||||
cond_partial = torch.zeros_like(template)
|
||||
pred_partial = torch.zeros_like(template)
|
||||
|
||||
for name, velocity in local_velocities.items():
|
||||
denoised = cls._ltx2_velocity_to_x0(latents, velocity, sigma)
|
||||
if name == "cond":
|
||||
cond_partial = cond_partial + denoised
|
||||
pred_partial = pred_partial + denoised * coefficients[name]
|
||||
|
||||
cond = cfg_model_parallel_all_reduce(cond_partial)
|
||||
pred = cfg_model_parallel_all_reduce(pred_partial)
|
||||
return cls._ltx2_apply_rescale(cond, pred, rescale_scale)
|
||||
|
||||
@staticmethod
|
||||
def _ltx2_channelwise_normalize(noise: torch.Tensor) -> torch.Tensor:
|
||||
return noise.sub_(noise.mean(dim=(-2, -1), keepdim=True)).div_(
|
||||
@@ -274,6 +505,8 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
ctx: LTX2DenoisingContext,
|
||||
*,
|
||||
substep: bool,
|
||||
batch: Req | None = None,
|
||||
is_audio: bool = False,
|
||||
) -> torch.Tensor:
|
||||
generator = (
|
||||
ctx.res2s_substep_noise_generator
|
||||
@@ -282,7 +515,51 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
)
|
||||
if generator is None:
|
||||
raise ValueError("LTX-2 res2s noise generator was not initialized.")
|
||||
return cls._ltx2_res2s_new_noise(reference_tensor, generator)
|
||||
if batch is not None and get_sp_world_size() > 1 and reference_tensor.ndim == 3:
|
||||
full_shape = (
|
||||
getattr(batch, "raw_audio_latent_shape", None)
|
||||
if is_audio
|
||||
else getattr(batch, "raw_latent_shape", None)
|
||||
)
|
||||
did_shard = (
|
||||
getattr(batch, "did_sp_shard_audio_latents", False)
|
||||
if is_audio
|
||||
else getattr(batch, "did_sp_shard_latents", False)
|
||||
)
|
||||
if full_shape is not None and did_shard:
|
||||
# HQ res2s normalizes SDE noise over the complete latent. If
|
||||
# each SP rank normalizes only its local slice, the sampler
|
||||
# follows a different trajectory. Recreate the same full noise
|
||||
# on every rank, then keep the time slice owned by this rank.
|
||||
full_noise = cls._ltx2_res2s_new_noise(
|
||||
torch.empty(
|
||||
tuple(int(dim) for dim in full_shape),
|
||||
device=reference_tensor.device,
|
||||
dtype=reference_tensor.dtype,
|
||||
),
|
||||
generator,
|
||||
)
|
||||
if is_audio:
|
||||
start = int(batch.sp_audio_start_frame)
|
||||
end = start + int(batch.sp_audio_latent_num_frames)
|
||||
else:
|
||||
start = int(batch.sp_video_start_frame) * int(
|
||||
batch.sp_video_tokens_per_frame
|
||||
)
|
||||
end = start + int(reference_tensor.shape[1])
|
||||
sliced = full_noise[:, start : min(end, int(full_noise.shape[1])), :]
|
||||
if int(sliced.shape[1]) < int(reference_tensor.shape[1]):
|
||||
pad_len = int(reference_tensor.shape[1]) - int(sliced.shape[1])
|
||||
pad = torch.zeros(
|
||||
(sliced.shape[0], pad_len, sliced.shape[2]),
|
||||
device=sliced.device,
|
||||
dtype=sliced.dtype,
|
||||
)
|
||||
sliced = torch.cat([sliced, pad], dim=1)
|
||||
return sliced.to(dtype=reference_tensor.dtype)
|
||||
return cls._ltx2_res2s_new_noise(reference_tensor, generator).to(
|
||||
dtype=reference_tensor.dtype
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _ltx2_apply_clean_latent_mask(
|
||||
@@ -480,12 +757,16 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
midpoint_audio_det = anchor_audio + h * a21 * eps1_audio
|
||||
|
||||
sub_noise_video = (
|
||||
self._ltx2_res2s_noise_like(ctx.latents, ctx, substep=True)
|
||||
self._ltx2_res2s_noise_like(
|
||||
ctx.latents, ctx, substep=True, batch=batch
|
||||
).float()
|
||||
if ctx.use_native_hq_res2s_sde_noise
|
||||
else self._randn_like_with_batch_generators(ctx.latents, batch).float()
|
||||
)
|
||||
sub_noise_audio = (
|
||||
self._ltx2_res2s_noise_like(ctx.audio_latents, ctx, substep=True)
|
||||
self._ltx2_res2s_noise_like(
|
||||
ctx.audio_latents, ctx, substep=True, batch=batch, is_audio=True
|
||||
).float()
|
||||
if ctx.use_native_hq_res2s_sde_noise
|
||||
else self._randn_like_with_batch_generators(
|
||||
ctx.audio_latents, batch
|
||||
@@ -552,12 +833,16 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
next_audio_det = anchor_audio + h * (b1 * eps1_audio + b2 * eps2_audio)
|
||||
|
||||
step_noise_video = (
|
||||
self._ltx2_res2s_noise_like(ctx.latents, ctx, substep=False)
|
||||
self._ltx2_res2s_noise_like(
|
||||
ctx.latents, ctx, substep=False, batch=batch
|
||||
).float()
|
||||
if ctx.use_native_hq_res2s_sde_noise
|
||||
else self._randn_like_with_batch_generators(ctx.latents, batch).float()
|
||||
)
|
||||
step_noise_audio = (
|
||||
self._ltx2_res2s_noise_like(ctx.audio_latents, ctx, substep=False)
|
||||
self._ltx2_res2s_noise_like(
|
||||
ctx.audio_latents, ctx, substep=False, batch=batch, is_audio=True
|
||||
).float()
|
||||
if ctx.use_native_hq_res2s_sde_noise
|
||||
else self._randn_like_with_batch_generators(
|
||||
ctx.audio_latents, batch
|
||||
@@ -746,7 +1031,8 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
return tensor
|
||||
if tensor.shape[0] <= 0 or int(target_batch_size) % int(tensor.shape[0]) != 0:
|
||||
raise ValueError(
|
||||
f"Cannot repeat tensor with batch={tensor.shape[0]} to target_batch_size={target_batch_size}"
|
||||
"Cannot repeat tensor with batch="
|
||||
f"{tensor.shape[0]} to target_batch_size={target_batch_size}"
|
||||
)
|
||||
repeat_factor = int(target_batch_size) // int(tensor.shape[0])
|
||||
return tensor.repeat(repeat_factor, *([1] * (tensor.ndim - 1)))
|
||||
@@ -1048,6 +1334,12 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
kwargs["disable_v2a_cross_attn"] = True
|
||||
if perturbation_configs is not None:
|
||||
kwargs["perturbation_configs"] = perturbation_configs
|
||||
if self.server_args.enable_cfg_parallel:
|
||||
device = get_local_torch_device()
|
||||
return {
|
||||
k: v.to(device) if isinstance(v, torch.Tensor) else v
|
||||
for k, v in kwargs.items()
|
||||
}
|
||||
return kwargs
|
||||
|
||||
@staticmethod
|
||||
@@ -1269,6 +1561,23 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
clean_latent_background=clean_latent_background,
|
||||
)
|
||||
)
|
||||
|
||||
# Batch tensors are broadcast from CFG rank 0 and remain on its device.
|
||||
# Move every context tensor that will be used in model forward passes or
|
||||
# scheduler steps to the local device once here, before the loop begins.
|
||||
if server_args.enable_cfg_parallel:
|
||||
device = get_local_torch_device()
|
||||
ctx.latents = ctx.latents.to(device)
|
||||
ctx.timesteps = ctx.timesteps.to(device)
|
||||
if ctx.audio_latents is not None:
|
||||
ctx.audio_latents = ctx.audio_latents.to(device)
|
||||
if ctx.guidance is not None:
|
||||
ctx.guidance = ctx.guidance.to(device)
|
||||
if ctx.denoise_mask is not None:
|
||||
ctx.denoise_mask = ctx.denoise_mask.to(device)
|
||||
if ctx.clean_latent is not None:
|
||||
ctx.clean_latent = ctx.clean_latent.to(device)
|
||||
|
||||
return ctx
|
||||
|
||||
def _before_denoising_loop(
|
||||
@@ -1346,53 +1655,90 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
)
|
||||
use_official_cfg_path = stage1_guider_params is None
|
||||
if use_official_cfg_path:
|
||||
model_kwargs = self._build_ltx2_model_kwargs(
|
||||
ctx,
|
||||
base_model_kwargs,
|
||||
encoder_hidden_states=batch.prompt_embeds[0],
|
||||
audio_encoder_hidden_states=batch.audio_prompt_embeds[0],
|
||||
encoder_attention_mask=prompt_attention_mask,
|
||||
cfg_parallel = (
|
||||
server_args.enable_cfg_parallel and batch.do_classifier_free_guidance
|
||||
)
|
||||
if batch.do_classifier_free_guidance:
|
||||
cfg_batch_size = batch_size * 2
|
||||
model_kwargs = self._repeat_ltx2_model_kwargs_batch(
|
||||
model_kwargs, cfg_batch_size
|
||||
cfg_rank = get_classifier_free_guidance_rank() if cfg_parallel else 0
|
||||
|
||||
if cfg_parallel:
|
||||
if cfg_rank == 0:
|
||||
encoder_hidden_states = batch.prompt_embeds[0]
|
||||
audio_encoder_hidden_states = batch.audio_prompt_embeds[0]
|
||||
encoder_attention_mask = prompt_attention_mask
|
||||
elif cfg_rank == 1:
|
||||
encoder_hidden_states = batch.negative_prompt_embeds[0]
|
||||
audio_encoder_hidden_states = batch.negative_audio_prompt_embeds[0]
|
||||
encoder_attention_mask = self._get_ltx_prompt_attention_mask(
|
||||
batch,
|
||||
is_ltx23_variant=(
|
||||
ctx.is_ltx23_variant and not ctx.use_ltx23_legacy_one_stage
|
||||
),
|
||||
negative=True,
|
||||
)
|
||||
else:
|
||||
encoder_hidden_states = batch.prompt_embeds[0]
|
||||
audio_encoder_hidden_states = batch.audio_prompt_embeds[0]
|
||||
encoder_attention_mask = prompt_attention_mask
|
||||
model_kwargs = self._build_ltx2_model_kwargs(
|
||||
ctx,
|
||||
base_model_kwargs,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
audio_encoder_hidden_states=audio_encoder_hidden_states,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
)
|
||||
model_kwargs["encoder_hidden_states"] = torch.cat(
|
||||
[batch.negative_prompt_embeds[0], batch.prompt_embeds[0]], dim=0
|
||||
else:
|
||||
model_kwargs = self._build_ltx2_model_kwargs(
|
||||
ctx,
|
||||
base_model_kwargs,
|
||||
encoder_hidden_states=batch.prompt_embeds[0],
|
||||
audio_encoder_hidden_states=batch.audio_prompt_embeds[0],
|
||||
encoder_attention_mask=prompt_attention_mask,
|
||||
)
|
||||
model_kwargs["audio_encoder_hidden_states"] = torch.cat(
|
||||
[
|
||||
batch.negative_audio_prompt_embeds[0],
|
||||
batch.audio_prompt_embeds[0],
|
||||
],
|
||||
dim=0,
|
||||
)
|
||||
if self._should_pass_ltx2_text_attention_mask(ctx):
|
||||
repeated_attention_mask = self._cat_or_none(
|
||||
if batch.do_classifier_free_guidance:
|
||||
cfg_batch_size = batch_size * 2
|
||||
model_kwargs = self._repeat_ltx2_model_kwargs_batch(
|
||||
model_kwargs, cfg_batch_size
|
||||
)
|
||||
model_kwargs["encoder_hidden_states"] = torch.cat(
|
||||
[batch.negative_prompt_embeds[0], batch.prompt_embeds[0]],
|
||||
dim=0,
|
||||
)
|
||||
model_kwargs["audio_encoder_hidden_states"] = torch.cat(
|
||||
[
|
||||
self._get_ltx_prompt_attention_mask(
|
||||
batch,
|
||||
is_ltx23_variant=(
|
||||
ctx.is_ltx23_variant
|
||||
and not ctx.use_ltx23_legacy_one_stage
|
||||
batch.negative_audio_prompt_embeds[0],
|
||||
batch.audio_prompt_embeds[0],
|
||||
],
|
||||
dim=0,
|
||||
)
|
||||
if self._should_pass_ltx2_text_attention_mask(ctx):
|
||||
repeated_attention_mask = self._cat_or_none(
|
||||
[
|
||||
self._get_ltx_prompt_attention_mask(
|
||||
batch,
|
||||
is_ltx23_variant=(
|
||||
ctx.is_ltx23_variant
|
||||
and not ctx.use_ltx23_legacy_one_stage
|
||||
),
|
||||
negative=True,
|
||||
),
|
||||
negative=True,
|
||||
),
|
||||
prompt_attention_mask,
|
||||
]
|
||||
)
|
||||
model_kwargs["encoder_attention_mask"] = repeated_attention_mask
|
||||
model_kwargs["audio_encoder_attention_mask"] = (
|
||||
repeated_attention_mask
|
||||
)
|
||||
prompt_attention_mask,
|
||||
]
|
||||
)
|
||||
model_kwargs["encoder_attention_mask"] = repeated_attention_mask
|
||||
model_kwargs["audio_encoder_attention_mask"] = (
|
||||
repeated_attention_mask
|
||||
)
|
||||
|
||||
with self._ltx2_model_forward_context(ctx, step):
|
||||
model_video, model_audio = step.current_model(**model_kwargs)
|
||||
|
||||
model_video = model_video.float()
|
||||
model_audio = model_audio.float()
|
||||
if batch.do_classifier_free_guidance:
|
||||
if cfg_parallel:
|
||||
model_video, model_audio = self._combine_cfg_parallel_av(
|
||||
model_video, model_audio, float(batch.guidance_scale), cfg_rank
|
||||
)
|
||||
elif batch.do_classifier_free_guidance:
|
||||
model_video_uncond, model_video_text = model_video.chunk(2)
|
||||
model_audio_uncond, model_audio_text = model_audio.chunk(2)
|
||||
model_video = model_video_uncond + (
|
||||
@@ -1554,6 +1900,15 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
float(stage1_guider_params["video_modality_scale"]) != 1.0
|
||||
or float(stage1_guider_params["audio_modality_scale"]) != 1.0
|
||||
)
|
||||
stage1_cfg_parallel = (
|
||||
server_args.enable_cfg_parallel and not ctx.use_ltx23_legacy_one_stage
|
||||
)
|
||||
stage1_cfg_rank = (
|
||||
get_classifier_free_guidance_rank() if stage1_cfg_parallel else 0
|
||||
)
|
||||
stage1_cfg_world_size = (
|
||||
get_classifier_free_guidance_world_size() if stage1_cfg_parallel else 1
|
||||
)
|
||||
# NOTE: this flag must be identical across all SP ranks so that every
|
||||
# rank executes the same number of model-forward calls (each of which
|
||||
# contains NCCL collectives).
|
||||
@@ -1722,8 +2077,21 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
)
|
||||
)
|
||||
|
||||
num_passes = len(pass_specs)
|
||||
expanded_batch_size = batch_size_local * num_passes
|
||||
execution_pass_specs = (
|
||||
[
|
||||
pass_spec
|
||||
for index, pass_spec in enumerate(pass_specs)
|
||||
if index % stage1_cfg_world_size == stage1_cfg_rank
|
||||
]
|
||||
if stage1_cfg_parallel
|
||||
else pass_specs
|
||||
)
|
||||
num_execution_passes = len(execution_pass_specs)
|
||||
if num_execution_passes == 0:
|
||||
raise ValueError(
|
||||
"LTX2 stage-1 CFG parallel degree exceeds guidance pass count."
|
||||
)
|
||||
expanded_batch_size = batch_size_local * num_execution_passes
|
||||
batched_model_kwargs = self._repeat_ltx2_model_kwargs_batch(
|
||||
base_model_kwargs_local, expanded_batch_size
|
||||
)
|
||||
@@ -1733,21 +2101,21 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
encoder_hidden_states=torch.cat(
|
||||
[
|
||||
pass_spec.encoder_hidden_states
|
||||
for pass_spec in pass_specs
|
||||
for pass_spec in execution_pass_specs
|
||||
],
|
||||
dim=0,
|
||||
),
|
||||
audio_encoder_hidden_states=torch.cat(
|
||||
[
|
||||
pass_spec.audio_encoder_hidden_states
|
||||
for pass_spec in pass_specs
|
||||
for pass_spec in execution_pass_specs
|
||||
],
|
||||
dim=0,
|
||||
),
|
||||
encoder_attention_mask=self._cat_or_none(
|
||||
[
|
||||
pass_spec.encoder_attention_mask
|
||||
for pass_spec in pass_specs
|
||||
for pass_spec in execution_pass_specs
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -1755,14 +2123,14 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
split_sizes = [1] * expanded_batch_size
|
||||
split_pass_specs = tuple(
|
||||
pass_spec
|
||||
for pass_spec in pass_specs
|
||||
for pass_spec in execution_pass_specs
|
||||
for _ in range(batch_size_local)
|
||||
)
|
||||
split_perturbation_configs = (
|
||||
()
|
||||
if use_split_pass_kwargs
|
||||
else self._build_ltx2_guidance_perturbation_configs(
|
||||
pass_specs, batch_size_local
|
||||
execution_pass_specs, batch_size_local
|
||||
)
|
||||
)
|
||||
batched_video_chunks = []
|
||||
@@ -1796,7 +2164,7 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
else:
|
||||
perturbation_configs = (
|
||||
self._build_ltx2_guidance_perturbation_configs(
|
||||
pass_specs, batch_size_local
|
||||
execution_pass_specs, batch_size_local
|
||||
)
|
||||
)
|
||||
with self._ltx2_model_forward_context(ctx, step):
|
||||
@@ -1813,18 +2181,20 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
audio_chunk,
|
||||
)
|
||||
for pass_spec, video_chunk, audio_chunk in zip(
|
||||
pass_specs,
|
||||
batched_video.chunk(num_passes, dim=0),
|
||||
batched_audio.chunk(num_passes, dim=0),
|
||||
execution_pass_specs,
|
||||
batched_video.chunk(num_execution_passes, dim=0),
|
||||
batched_audio.chunk(num_execution_passes, dim=0),
|
||||
strict=True,
|
||||
)
|
||||
}
|
||||
v_pos, a_v_pos = pass_outputs["cond"]
|
||||
v_neg, a_v_neg = pass_outputs["neg"]
|
||||
v_ptb, a_v_ptb = pass_outputs.get("perturbed", (None, None))
|
||||
v_mod, a_v_mod = pass_outputs.get("modality", (None, None))
|
||||
if not stage1_cfg_parallel:
|
||||
v_pos, a_v_pos = pass_outputs["cond"]
|
||||
v_neg, a_v_neg = pass_outputs["neg"]
|
||||
v_ptb, a_v_ptb = pass_outputs.get("perturbed", (None, None))
|
||||
v_mod, a_v_mod = pass_outputs.get("modality", (None, None))
|
||||
|
||||
sigma_value_float = float(sigma_value.item())
|
||||
video_sigma_for_x0: float | torch.Tensor = sigma_value_float
|
||||
audio_sigma_for_x0: float | torch.Tensor = sigma_value_float
|
||||
if ctx.use_ltx23_hq_timestep_semantics:
|
||||
video_sigma_for_x0 = model_inputs_local.timestep_video
|
||||
@@ -1833,99 +2203,150 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
video_sigma_for_x0 = sigma_value.to(
|
||||
device=video_latents.device, dtype=torch.float32
|
||||
) * ctx.denoise_mask.squeeze(-1)
|
||||
else:
|
||||
video_sigma_for_x0 = sigma_value_float
|
||||
|
||||
denoised_video_local = self._ltx2_velocity_to_x0(
|
||||
video_latents, v_pos, video_sigma_for_x0
|
||||
)
|
||||
denoised_audio_local = self._ltx2_velocity_to_x0(
|
||||
audio_latents, a_v_pos, audio_sigma_for_x0
|
||||
)
|
||||
denoised_video_neg = self._ltx2_velocity_to_x0(
|
||||
video_latents, v_neg, video_sigma_for_x0
|
||||
)
|
||||
denoised_audio_neg = self._ltx2_velocity_to_x0(
|
||||
audio_latents, a_v_neg, audio_sigma_for_x0
|
||||
)
|
||||
denoised_video_perturbed = (
|
||||
None
|
||||
if v_ptb is None
|
||||
else self._ltx2_velocity_to_x0(
|
||||
video_latents, v_ptb, video_sigma_for_x0
|
||||
if stage1_cfg_parallel:
|
||||
guided_video = self._ltx2_combine_guided_x0_parallel(
|
||||
latents=video_latents,
|
||||
local_velocities={
|
||||
name: output[0] for name, output in pass_outputs.items()
|
||||
},
|
||||
sigma=video_sigma_for_x0,
|
||||
cfg_scale=float(stage1_guider_params["video_cfg_scale"]),
|
||||
stg_scale=float(stage1_guider_params["video_stg_scale"]),
|
||||
rescale_scale=float(
|
||||
stage1_guider_params["video_rescale_scale"]
|
||||
),
|
||||
modality_scale=float(
|
||||
stage1_guider_params["video_modality_scale"]
|
||||
),
|
||||
)
|
||||
)
|
||||
denoised_audio_perturbed = (
|
||||
None
|
||||
if a_v_ptb is None
|
||||
else self._ltx2_velocity_to_x0(
|
||||
audio_latents, a_v_ptb, audio_sigma_for_x0
|
||||
)
|
||||
)
|
||||
denoised_video_modality = (
|
||||
None
|
||||
if v_mod is None
|
||||
else self._ltx2_velocity_to_x0(
|
||||
video_latents, v_mod, video_sigma_for_x0
|
||||
)
|
||||
)
|
||||
denoised_audio_modality = (
|
||||
None
|
||||
if a_v_mod is None
|
||||
else self._ltx2_velocity_to_x0(
|
||||
audio_latents, a_v_mod, audio_sigma_for_x0
|
||||
)
|
||||
)
|
||||
if video_skip and ctx.last_denoised_video is not None:
|
||||
denoised_video_local = ctx.last_denoised_video
|
||||
else:
|
||||
denoised_video_local = guided_video
|
||||
if update_skip_cache:
|
||||
ctx.last_denoised_video = guided_video
|
||||
|
||||
guided_video = self._ltx2_calculate_guided_x0(
|
||||
cond=denoised_video_local,
|
||||
uncond_text=denoised_video_neg,
|
||||
uncond_perturbed=(
|
||||
denoised_video_perturbed
|
||||
if denoised_video_perturbed is not None
|
||||
else 0.0
|
||||
),
|
||||
uncond_modality=(
|
||||
denoised_video_modality
|
||||
if denoised_video_modality is not None
|
||||
else 0.0
|
||||
),
|
||||
cfg_scale=float(stage1_guider_params["video_cfg_scale"]),
|
||||
stg_scale=float(stage1_guider_params["video_stg_scale"]),
|
||||
rescale_scale=float(stage1_guider_params["video_rescale_scale"]),
|
||||
modality_scale=float(stage1_guider_params["video_modality_scale"]),
|
||||
)
|
||||
if video_skip and ctx.last_denoised_video is not None:
|
||||
denoised_video_local = ctx.last_denoised_video
|
||||
guided_audio = self._ltx2_combine_guided_x0_parallel(
|
||||
latents=audio_latents,
|
||||
local_velocities={
|
||||
name: output[1] for name, output in pass_outputs.items()
|
||||
},
|
||||
sigma=audio_sigma_for_x0,
|
||||
cfg_scale=float(stage1_guider_params["audio_cfg_scale"]),
|
||||
stg_scale=float(stage1_guider_params["audio_stg_scale"]),
|
||||
rescale_scale=float(
|
||||
stage1_guider_params["audio_rescale_scale"]
|
||||
),
|
||||
modality_scale=float(
|
||||
stage1_guider_params["audio_modality_scale"]
|
||||
),
|
||||
)
|
||||
if audio_skip and ctx.last_denoised_audio is not None:
|
||||
denoised_audio_local = ctx.last_denoised_audio
|
||||
else:
|
||||
denoised_audio_local = guided_audio
|
||||
if update_skip_cache:
|
||||
ctx.last_denoised_audio = guided_audio
|
||||
else:
|
||||
denoised_video_local = guided_video
|
||||
if update_skip_cache:
|
||||
ctx.last_denoised_video = guided_video
|
||||
denoised_video_local = self._ltx2_velocity_to_x0(
|
||||
video_latents, v_pos, video_sigma_for_x0
|
||||
)
|
||||
denoised_audio_local = self._ltx2_velocity_to_x0(
|
||||
audio_latents, a_v_pos, audio_sigma_for_x0
|
||||
)
|
||||
denoised_video_neg = self._ltx2_velocity_to_x0(
|
||||
video_latents, v_neg, video_sigma_for_x0
|
||||
)
|
||||
denoised_audio_neg = self._ltx2_velocity_to_x0(
|
||||
audio_latents, a_v_neg, audio_sigma_for_x0
|
||||
)
|
||||
denoised_video_perturbed = (
|
||||
None
|
||||
if v_ptb is None
|
||||
else self._ltx2_velocity_to_x0(
|
||||
video_latents, v_ptb, video_sigma_for_x0
|
||||
)
|
||||
)
|
||||
denoised_audio_perturbed = (
|
||||
None
|
||||
if a_v_ptb is None
|
||||
else self._ltx2_velocity_to_x0(
|
||||
audio_latents, a_v_ptb, audio_sigma_for_x0
|
||||
)
|
||||
)
|
||||
denoised_video_modality = (
|
||||
None
|
||||
if v_mod is None
|
||||
else self._ltx2_velocity_to_x0(
|
||||
video_latents, v_mod, video_sigma_for_x0
|
||||
)
|
||||
)
|
||||
denoised_audio_modality = (
|
||||
None
|
||||
if a_v_mod is None
|
||||
else self._ltx2_velocity_to_x0(
|
||||
audio_latents, a_v_mod, audio_sigma_for_x0
|
||||
)
|
||||
)
|
||||
|
||||
guided_audio = self._ltx2_calculate_guided_x0(
|
||||
cond=denoised_audio_local,
|
||||
uncond_text=denoised_audio_neg,
|
||||
uncond_perturbed=(
|
||||
denoised_audio_perturbed
|
||||
if denoised_audio_perturbed is not None
|
||||
else 0.0
|
||||
),
|
||||
uncond_modality=(
|
||||
denoised_audio_modality
|
||||
if denoised_audio_modality is not None
|
||||
else 0.0
|
||||
),
|
||||
cfg_scale=float(stage1_guider_params["audio_cfg_scale"]),
|
||||
stg_scale=float(stage1_guider_params["audio_stg_scale"]),
|
||||
rescale_scale=float(stage1_guider_params["audio_rescale_scale"]),
|
||||
modality_scale=float(stage1_guider_params["audio_modality_scale"]),
|
||||
)
|
||||
if audio_skip and ctx.last_denoised_audio is not None:
|
||||
denoised_audio_local = ctx.last_denoised_audio
|
||||
else:
|
||||
denoised_audio_local = guided_audio
|
||||
if update_skip_cache:
|
||||
ctx.last_denoised_audio = guided_audio
|
||||
guided_video = self._ltx2_calculate_guided_x0(
|
||||
cond=denoised_video_local,
|
||||
uncond_text=denoised_video_neg,
|
||||
uncond_perturbed=(
|
||||
denoised_video_perturbed
|
||||
if denoised_video_perturbed is not None
|
||||
else 0.0
|
||||
),
|
||||
uncond_modality=(
|
||||
denoised_video_modality
|
||||
if denoised_video_modality is not None
|
||||
else 0.0
|
||||
),
|
||||
cfg_scale=float(stage1_guider_params["video_cfg_scale"]),
|
||||
stg_scale=float(stage1_guider_params["video_stg_scale"]),
|
||||
rescale_scale=float(
|
||||
stage1_guider_params["video_rescale_scale"]
|
||||
),
|
||||
modality_scale=float(
|
||||
stage1_guider_params["video_modality_scale"]
|
||||
),
|
||||
)
|
||||
if video_skip and ctx.last_denoised_video is not None:
|
||||
denoised_video_local = ctx.last_denoised_video
|
||||
else:
|
||||
denoised_video_local = guided_video
|
||||
if update_skip_cache:
|
||||
ctx.last_denoised_video = guided_video
|
||||
|
||||
guided_audio = self._ltx2_calculate_guided_x0(
|
||||
cond=denoised_audio_local,
|
||||
uncond_text=denoised_audio_neg,
|
||||
uncond_perturbed=(
|
||||
denoised_audio_perturbed
|
||||
if denoised_audio_perturbed is not None
|
||||
else 0.0
|
||||
),
|
||||
uncond_modality=(
|
||||
denoised_audio_modality
|
||||
if denoised_audio_modality is not None
|
||||
else 0.0
|
||||
),
|
||||
cfg_scale=float(stage1_guider_params["audio_cfg_scale"]),
|
||||
stg_scale=float(stage1_guider_params["audio_stg_scale"]),
|
||||
rescale_scale=float(
|
||||
stage1_guider_params["audio_rescale_scale"]
|
||||
),
|
||||
modality_scale=float(
|
||||
stage1_guider_params["audio_modality_scale"]
|
||||
),
|
||||
)
|
||||
if audio_skip and ctx.last_denoised_audio is not None:
|
||||
denoised_audio_local = ctx.last_denoised_audio
|
||||
else:
|
||||
denoised_audio_local = guided_audio
|
||||
if update_skip_cache:
|
||||
ctx.last_denoised_audio = guided_audio
|
||||
|
||||
denoised_video_local = self._ltx2_apply_clean_latent_mask(
|
||||
denoised_video_local, ctx
|
||||
@@ -1968,14 +2389,22 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
midpoint_audio_deterministic = anchor_audio + h * a21 * eps1_audio
|
||||
|
||||
substep_video_noise = (
|
||||
self._ltx2_res2s_noise_like(ctx.latents, ctx, substep=True)
|
||||
self._ltx2_res2s_noise_like(
|
||||
ctx.latents, ctx, substep=True, batch=batch
|
||||
).float()
|
||||
if ctx.use_native_hq_res2s_sde_noise
|
||||
else self._randn_like_with_batch_generators(
|
||||
ctx.latents, batch
|
||||
).float()
|
||||
)
|
||||
substep_audio_noise = (
|
||||
self._ltx2_res2s_noise_like(ctx.audio_latents, ctx, substep=True)
|
||||
self._ltx2_res2s_noise_like(
|
||||
ctx.audio_latents,
|
||||
ctx,
|
||||
substep=True,
|
||||
batch=batch,
|
||||
is_audio=True,
|
||||
).float()
|
||||
if ctx.use_native_hq_res2s_sde_noise
|
||||
else self._randn_like_with_batch_generators(
|
||||
ctx.audio_latents, batch
|
||||
@@ -2035,14 +2464,22 @@ class LTX2DenoisingStage(DenoisingStage):
|
||||
)
|
||||
|
||||
step_video_noise = (
|
||||
self._ltx2_res2s_noise_like(ctx.latents, ctx, substep=False)
|
||||
self._ltx2_res2s_noise_like(
|
||||
ctx.latents, ctx, substep=False, batch=batch
|
||||
).float()
|
||||
if ctx.use_native_hq_res2s_sde_noise
|
||||
else self._randn_like_with_batch_generators(
|
||||
ctx.latents, batch
|
||||
).float()
|
||||
)
|
||||
step_audio_noise = (
|
||||
self._ltx2_res2s_noise_like(ctx.audio_latents, ctx, substep=False)
|
||||
self._ltx2_res2s_noise_like(
|
||||
ctx.audio_latents,
|
||||
ctx,
|
||||
substep=False,
|
||||
batch=batch,
|
||||
is_audio=True,
|
||||
).float()
|
||||
if ctx.use_native_hq_res2s_sde_noise
|
||||
else self._randn_like_with_batch_generators(
|
||||
ctx.audio_latents, batch
|
||||
|
||||
@@ -160,6 +160,8 @@ class ServerArgs(DisaggArgsMixin):
|
||||
dp_degree: int = 1
|
||||
# cfg parallel (None = auto-decide based on num_gpus)
|
||||
enable_cfg_parallel: Optional[bool] = None
|
||||
# number of GPUs in each CFG parallel group (None = auto, 1 = disabled, N > 1 = enabled)
|
||||
cfg_parallel_degree: Optional[int] = None
|
||||
|
||||
hsdp_replicate_dim: int = 1
|
||||
hsdp_shard_dim: Optional[int] = None
|
||||
@@ -671,6 +673,14 @@ class ServerArgs(DisaggArgsMixin):
|
||||
if self.tp_size is None:
|
||||
self.tp_size = 1
|
||||
|
||||
# --cfg-parallel-size takes precedence over --enable-cfg-parallel bool.
|
||||
if self.cfg_parallel_degree is not None:
|
||||
if self.cfg_parallel_degree == 1:
|
||||
self.enable_cfg_parallel = False
|
||||
elif self.cfg_parallel_degree > 1:
|
||||
self.enable_cfg_parallel = True
|
||||
cfg_unspecified = False
|
||||
|
||||
# Auto-enable CFG parallel when user hasn't set any parallelism flags
|
||||
# and there are enough GPUs. Only auto-enable for models whose default
|
||||
# SamplingParams use classifier-free guidance (negative_prompt is not None),
|
||||
@@ -695,11 +705,15 @@ class ServerArgs(DisaggArgsMixin):
|
||||
else:
|
||||
self.enable_cfg_parallel = False
|
||||
|
||||
# Resolve cfg_parallel_degree to a concrete int now that enable_cfg_parallel is settled.
|
||||
if self.cfg_parallel_degree is None:
|
||||
self.cfg_parallel_degree = 2 if self.enable_cfg_parallel else 1
|
||||
|
||||
# adjust sp_degree: allocate all remaining GPUs after TP and DP
|
||||
if self.sp_degree is None:
|
||||
num_gpus_per_group = self.dp_size * self.tp_size
|
||||
if self.enable_cfg_parallel:
|
||||
num_gpus_per_group *= 2
|
||||
num_gpus_per_group *= self.cfg_parallel_degree
|
||||
if self.num_gpus % num_gpus_per_group == 0:
|
||||
self.sp_degree = self.num_gpus // num_gpus_per_group
|
||||
else:
|
||||
@@ -739,10 +753,6 @@ class ServerArgs(DisaggArgsMixin):
|
||||
return False
|
||||
default_params = model_info.sampling_param_cls()
|
||||
|
||||
# for ltx2.3, cfg-parallel performs worse than ulysses-sp
|
||||
is_ltx = "ltx" in type(default_params).__name__.lower()
|
||||
if is_ltx:
|
||||
return False
|
||||
return (
|
||||
getattr(default_params, "negative_prompt", None) is not None
|
||||
and getattr(default_params, "guidance_scale", 0) > 1.0
|
||||
@@ -976,7 +986,18 @@ class ServerArgs(DisaggArgsMixin):
|
||||
"--enable-cfg-parallel",
|
||||
action="store_true",
|
||||
default=None,
|
||||
help="Enable cfg parallel. Auto-enabled when num_gpus >= 2 and no SP flags are set.",
|
||||
help="Enable cfg parallel at degree 2. Auto-enabled when num_gpus >= 2 and no SP flags are set.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cfg-parallel-size",
|
||||
dest="cfg_parallel_degree",
|
||||
type=int,
|
||||
default=None,
|
||||
help=(
|
||||
"Number of GPUs per CFG parallel group (1 = disabled, N > 1 = enabled at degree N). "
|
||||
"Supersedes --enable-cfg-parallel. Allows 4-branch CFG parallel (e.g., --cfg-parallel-size 4) "
|
||||
"for models with cond + neg + perturbed + modality branches."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--data-parallel-size",
|
||||
@@ -1627,11 +1648,13 @@ class ServerArgs(DisaggArgsMixin):
|
||||
|
||||
num_gpus_per_group = self.dp_size * self.tp_size
|
||||
if self.enable_cfg_parallel:
|
||||
num_gpus_per_group *= 2
|
||||
num_gpus_per_group *= self.cfg_parallel_degree
|
||||
|
||||
if self.num_gpus % num_gpus_per_group != 0:
|
||||
raise ValueError(
|
||||
f"num_gpus ({self.num_gpus}) must be divisible by (dp_size * tp_size{' * 2' if self.enable_cfg_parallel else ''}) = {num_gpus_per_group}"
|
||||
f"num_gpus ({self.num_gpus}) must be divisible by (dp_size * tp_size"
|
||||
f"{f' * {self.cfg_parallel_degree}' if self.enable_cfg_parallel else ''}"
|
||||
f") = {num_gpus_per_group}"
|
||||
)
|
||||
|
||||
if self.sp_degree != self.ring_degree * self.ulysses_degree:
|
||||
|
||||
@@ -86,7 +86,6 @@ STANDALONE_FILES = {
|
||||
"1-gpu": [
|
||||
"../cli/test_generate_t2i_perf.py",
|
||||
"test_update_weights_from_disk.py",
|
||||
"test_tracing.py",
|
||||
],
|
||||
"2-gpu": [
|
||||
"test_disagg_server.py",
|
||||
@@ -100,7 +99,6 @@ STANDALONE_FILE_EST_TIMES = {
|
||||
"1-gpu": {
|
||||
"../cli/test_generate_t2i_perf.py": 240.0,
|
||||
"test_update_weights_from_disk.py": 480.0,
|
||||
"test_tracing.py": 120.0,
|
||||
},
|
||||
"2-gpu": {
|
||||
# Two disagg clusters × (~3 min startup + ~1 min generate) ≈ 8 min.
|
||||
|
||||
@@ -14,6 +14,7 @@ from torch.distributed.tensor import distribute_tensor
|
||||
|
||||
from sglang.multimodal_gen.runtime.distributed.parallel_state import (
|
||||
destroy_model_parallel,
|
||||
get_classifier_free_guidance_world_size,
|
||||
get_data_parallel_world_size,
|
||||
get_sequence_parallel_world_size,
|
||||
get_tensor_model_parallel_world_size,
|
||||
@@ -284,7 +285,7 @@ def initialize_parallel_runtime(sgl_args: ServerArgs) -> None:
|
||||
ulysses_degree = sgl_args.ulysses_degree
|
||||
ring_degree = sgl_args.ring_degree
|
||||
dp_size = sgl_args.dp_size
|
||||
enable_cfg_parallel = bool(sgl_args.enable_cfg_parallel)
|
||||
cfg_degree = sgl_args.cfg_parallel_degree or 1
|
||||
|
||||
if (
|
||||
tp_size is None
|
||||
@@ -305,7 +306,13 @@ def initialize_parallel_runtime(sgl_args: ServerArgs) -> None:
|
||||
current_tp = get_tensor_model_parallel_world_size()
|
||||
current_sp = get_sequence_parallel_world_size()
|
||||
current_dp = get_data_parallel_world_size()
|
||||
if current_tp == tp_size and current_sp == sp_degree and current_dp == dp_size:
|
||||
current_cfg = get_classifier_free_guidance_world_size()
|
||||
if (
|
||||
current_tp == tp_size
|
||||
and current_sp == sp_degree
|
||||
and current_dp == dp_size
|
||||
and current_cfg == cfg_degree
|
||||
):
|
||||
return
|
||||
if torch.distributed.is_initialized():
|
||||
torch.distributed.barrier()
|
||||
@@ -316,7 +323,7 @@ def initialize_parallel_runtime(sgl_args: ServerArgs) -> None:
|
||||
maybe_init_distributed_environment_and_model_parallel(
|
||||
tp_size=tp_size,
|
||||
sp_size=sp_degree,
|
||||
enable_cfg_parallel=enable_cfg_parallel,
|
||||
cfg_degree=cfg_degree,
|
||||
ulysses_degree=ulysses_degree,
|
||||
ring_degree=ring_degree,
|
||||
dp_size=dp_size,
|
||||
|
||||
@@ -564,7 +564,7 @@ TWO_GPU_CASES = [
|
||||
"ltx_2_3_two_stage_ti2v_2gpus",
|
||||
DiffusionServerArgs(
|
||||
model_path="Lightricks/LTX-2.3",
|
||||
ulysses_degree=2,
|
||||
cfg_parallel=True,
|
||||
extras=[
|
||||
"--pipeline-class-name LTX2TwoStagePipeline --ltx2-two-stage-device-mode original"
|
||||
],
|
||||
@@ -584,7 +584,7 @@ TWO_GPU_CASES = [
|
||||
"ltx_2.3_two_stage_t2v_2gpus",
|
||||
DiffusionServerArgs(
|
||||
model_path="Lightricks/LTX-2.3",
|
||||
ulysses_degree=2,
|
||||
cfg_parallel=True,
|
||||
extras=[
|
||||
"--pipeline-class-name LTX2TwoStagePipeline",
|
||||
"--ltx2-two-stage-device-mode original",
|
||||
|
||||
@@ -2424,113 +2424,117 @@
|
||||
},
|
||||
"ltx_2.3_two_stage_t2v_2gpus": {
|
||||
"stages_ms": {
|
||||
"InputValidationStage": 0.06,
|
||||
"TextEncodingStage": 1789.48,
|
||||
"LTX2TextConnectorStage": 28.01,
|
||||
"LTX2HalveResolutionStage": 0.07,
|
||||
"LTX2LoRASwitchStage": 143.36,
|
||||
"LTX2SigmaPreparationStage": 0.37,
|
||||
"TimestepPreparationStage": 24.63,
|
||||
"LTX2AVLatentPreparationStage": 0.16,
|
||||
"LTX2AVDenoisingStage": 12652.72,
|
||||
"LTX2UpsampleStage": 779.33,
|
||||
"LTX2RefinementStage": 762.78,
|
||||
"LTX2AVDecodingStage": 436.15
|
||||
"InputValidationStage": 0.07,
|
||||
"TextEncodingStage": 1784.13,
|
||||
"LTX2TextConnectorStage": 29.83,
|
||||
"LTX2HalveResolutionStage": 0.06,
|
||||
"LTX2LoRASwitchStage": 153.74,
|
||||
"LTX2SigmaPreparationStage": 0.63,
|
||||
"TimestepPreparationStage": 31.17,
|
||||
"LTX2AVLatentPreparationStage": 0.26,
|
||||
"LTX2ImageEncodingStage": 0.02,
|
||||
"LTX2AVDenoisingStage": 5460.05,
|
||||
"LTX2UpsampleStage": 3.31,
|
||||
"LTX2RefinementStage": 615.01,
|
||||
"LTX2AVDecodingStage": 240.26,
|
||||
"per_frame_generation": null
|
||||
},
|
||||
"denoise_step_ms": {
|
||||
"0": 4787.44,
|
||||
"1": 241.83,
|
||||
"2": 301.68,
|
||||
"3": 311.37,
|
||||
"4": 287.91,
|
||||
"5": 277.65,
|
||||
"6": 246.68,
|
||||
"7": 250.9,
|
||||
"8": 260.28,
|
||||
"9": 249.61,
|
||||
"10": 242.41,
|
||||
"11": 241.84,
|
||||
"12": 266.58,
|
||||
"13": 292.73,
|
||||
"14": 284.87,
|
||||
"15": 277.67,
|
||||
"16": 272.33,
|
||||
"17": 290.47,
|
||||
"18": 289.31,
|
||||
"19": 273.62,
|
||||
"20": 263.3,
|
||||
"21": 280.54,
|
||||
"22": 297.45,
|
||||
"23": 241.16,
|
||||
"24": 275.2,
|
||||
"25": 264.26,
|
||||
"26": 255.94,
|
||||
"27": 277.13,
|
||||
"28": 277.31,
|
||||
"29": 268.01,
|
||||
"30": 223.06,
|
||||
"31": 262.81,
|
||||
"32": 271.16
|
||||
"0": 179.26,
|
||||
"1": 283.15,
|
||||
"2": 193.3,
|
||||
"3": 166.82,
|
||||
"4": 200.16,
|
||||
"5": 196.51,
|
||||
"6": 178.52,
|
||||
"7": 168.41,
|
||||
"8": 182.67,
|
||||
"9": 166.79,
|
||||
"10": 163.28,
|
||||
"11": 170.97,
|
||||
"12": 176.57,
|
||||
"13": 189.92,
|
||||
"14": 162.76,
|
||||
"15": 179.89,
|
||||
"16": 172.41,
|
||||
"17": 184.39,
|
||||
"18": 161.55,
|
||||
"19": 176.63,
|
||||
"20": 200.48,
|
||||
"21": 188.38,
|
||||
"22": 161.98,
|
||||
"23": 172.25,
|
||||
"24": 167.45,
|
||||
"25": 189.81,
|
||||
"26": 176.08,
|
||||
"27": 170.88,
|
||||
"28": 188.96,
|
||||
"29": 183.21,
|
||||
"30": 238.92,
|
||||
"31": 187.04,
|
||||
"32": 184.4
|
||||
},
|
||||
"expected_e2e_ms": 22417.99,
|
||||
"expected_avg_denoise_ms": 406.2,
|
||||
"expected_median_denoise_ms": 272.33,
|
||||
"estimated_full_test_time_s": 216.7
|
||||
"expected_e2e_ms": 18039.38,
|
||||
"expected_avg_denoise_ms": 183.75,
|
||||
"expected_median_denoise_ms": 179.26,
|
||||
"estimated_full_test_time_s": 160.0
|
||||
},
|
||||
"ltx_2_3_two_stage_ti2v_2gpus": {
|
||||
"stages_ms": {
|
||||
"InputValidationStage": 4.02,
|
||||
"TextEncodingStage": 1681.03,
|
||||
"LTX2TextConnectorStage": 29.48,
|
||||
"LTX2HalveResolutionStage": 0.06,
|
||||
"LTX2LoRASwitchStage": 123.34,
|
||||
"LTX2SigmaPreparationStage": 0.48,
|
||||
"TimestepPreparationStage": 30.17,
|
||||
"LTX2AVLatentPreparationStage": 0.23,
|
||||
"LTX2AVDenoisingStage": 28796.44,
|
||||
"LTX2UpsampleStage": 1062.28,
|
||||
"LTX2RefinementStage": 1335.05,
|
||||
"LTX2AVDecodingStage": 482.26
|
||||
"InputValidationStage": 3.26,
|
||||
"TextEncodingStage": 1789.58,
|
||||
"LTX2TextConnectorStage": 30.1,
|
||||
"LTX2HalveResolutionStage": 0.05,
|
||||
"LTX2LoRASwitchStage": 127.56,
|
||||
"LTX2SigmaPreparationStage": 0.54,
|
||||
"TimestepPreparationStage": 22.06,
|
||||
"LTX2AVLatentPreparationStage": 0.16,
|
||||
"LTX2ImageEncodingStage": 27.81,
|
||||
"LTX2AVDenoisingStage": 8517.54,
|
||||
"LTX2UpsampleStage": 2.6,
|
||||
"LTX2RefinementStage": 412.39,
|
||||
"LTX2AVDecodingStage": 225.41,
|
||||
"per_frame_generation": null
|
||||
},
|
||||
"denoise_step_ms": {
|
||||
"0": 1205.82,
|
||||
"1": 1208.39,
|
||||
"2": 1269.93,
|
||||
"3": 998.3,
|
||||
"4": 921.32,
|
||||
"5": 1047.42,
|
||||
"6": 948.17,
|
||||
"7": 941.85,
|
||||
"8": 885.76,
|
||||
"9": 904.27,
|
||||
"10": 898.02,
|
||||
"11": 887.85,
|
||||
"12": 898.61,
|
||||
"13": 891.13,
|
||||
"14": 954.07,
|
||||
"15": 896.09,
|
||||
"16": 1001.81,
|
||||
"17": 881.83,
|
||||
"18": 882.7,
|
||||
"19": 920.33,
|
||||
"20": 971.85,
|
||||
"21": 891.13,
|
||||
"22": 884.23,
|
||||
"23": 870.44,
|
||||
"24": 890.55,
|
||||
"25": 869.53,
|
||||
"26": 861.77,
|
||||
"27": 873.08,
|
||||
"28": 934.75,
|
||||
"29": 865.63,
|
||||
"30": 357.92,
|
||||
"31": 315.56,
|
||||
"32": 345.67
|
||||
"0": 283.67,
|
||||
"1": 332.51,
|
||||
"2": 287.41,
|
||||
"3": 288.1,
|
||||
"4": 286.66,
|
||||
"5": 286.86,
|
||||
"6": 285.56,
|
||||
"7": 285.01,
|
||||
"8": 281.79,
|
||||
"9": 276.41,
|
||||
"10": 279.14,
|
||||
"11": 306.21,
|
||||
"12": 280.42,
|
||||
"13": 279.25,
|
||||
"14": 282.39,
|
||||
"15": 283.28,
|
||||
"16": 286.48,
|
||||
"17": 286.48,
|
||||
"18": 276.96,
|
||||
"19": 282.12,
|
||||
"20": 275.69,
|
||||
"21": 278.08,
|
||||
"22": 278.56,
|
||||
"23": 275.05,
|
||||
"24": 301.15,
|
||||
"25": 273.35,
|
||||
"26": 275.35,
|
||||
"27": 274.09,
|
||||
"28": 274.32,
|
||||
"29": 270.66,
|
||||
"30": 140.67,
|
||||
"31": 134.03,
|
||||
"32": 132.51
|
||||
},
|
||||
"expected_e2e_ms": 44740.03,
|
||||
"expected_avg_denoise_ms": 890.17,
|
||||
"expected_median_denoise_ms": 896.09,
|
||||
"estimated_full_test_time_s": 155.3
|
||||
"expected_e2e_ms": 19149.77,
|
||||
"expected_avg_denoise_ms": 270.31,
|
||||
"expected_median_denoise_ms": 280.42,
|
||||
"estimated_full_test_time_s": 170.0
|
||||
},
|
||||
"ltx_2_3_hq_pipeline": {
|
||||
"stages_ms": {
|
||||
|
||||
@@ -543,10 +543,9 @@ class TestDisaggZImageTracing(_DisaggTestBase):
|
||||
# actual regression guard for this PR: it proves the W3C carrier
|
||||
# survives encoder→denoiser→decoder JSON hops (via ``_trace_state``).
|
||||
# The HTTP-level carrier extraction (root Req parented under the
|
||||
# client's span_id) is already covered by ``test_tracing.py`` in
|
||||
# monolithic mode and asserting it here is flaky — the server head's
|
||||
# BatchSpanProcessor may not flush the Req span before role spans
|
||||
# reach the collector, since the role spans close first.
|
||||
# client's span_id) is intentionally not asserted here: the server
|
||||
# head's BatchSpanProcessor may not flush the Req span before role
|
||||
# spans reach the collector, since the role spans close first.
|
||||
trace_ids = {_as_hex(s.trace_id) for s in spans}
|
||||
self.assertEqual(
|
||||
trace_ids,
|
||||
|
||||
@@ -33,7 +33,7 @@ if TYPE_CHECKING:
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
SGL_TEST_FILES_CI_DATA_REVISION = "6e7b99e16b857c98285277fe3b4ffef30559bde9"
|
||||
SGL_TEST_FILES_CI_DATA_REVISION = "3ca3bad088ecc9ef80947d85c551cd335c75b87f"
|
||||
SGL_TEST_FILES_CONSISTENCY_GT_ROOT = (
|
||||
"https://raw.githubusercontent.com/"
|
||||
f"sgl-project/ci-data/{SGL_TEST_FILES_CI_DATA_REVISION}/"
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import unittest
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.multimodal_gen.runtime.distributed.cfg_policy import CFGPolicy
|
||||
|
||||
|
||||
class TestCFGPolicyCombine(unittest.TestCase):
|
||||
def test_cfg_parallel_uses_parallel_arithmetic_order(self):
|
||||
policy = CFGPolicy()
|
||||
req = MagicMock()
|
||||
req.cfg_normalization = 0
|
||||
req.guidance_rescale = 0
|
||||
|
||||
pipeline_config = MagicMock()
|
||||
pipeline_config.postprocess_cfg_noise.side_effect = lambda _, noise, __: noise
|
||||
|
||||
pos = torch.tensor([1.0], dtype=torch.bfloat16)
|
||||
neg = torch.tensor([0.1], dtype=torch.bfloat16)
|
||||
|
||||
serial = policy.combine([pos, neg], req, 7.0, pipeline_config)
|
||||
parallel = policy.combine(
|
||||
[pos, neg], req, 7.0, pipeline_config, cfg_parallel=True
|
||||
)
|
||||
|
||||
self.assertTrue(torch.equal(serial, neg + 7.0 * (pos - neg)))
|
||||
self.assertTrue(torch.equal(parallel, 7.0 * pos + (1 - 7.0) * neg))
|
||||
self.assertFalse(torch.equal(serial, parallel))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user