[diffusion] fix: further align ltx2.3 accuracy with tp (#24660)
This commit is contained in:
@@ -71,7 +71,7 @@ Other deployment flags:
|
|||||||
- `--lora-weight-name`: Select the exact safetensors file when the LoRA repository contains multiple weight files.
|
- `--lora-weight-name`: Select the exact safetensors file when the LoRA repository contains multiple weight files.
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
For native LTX-2.3 two-stage serving without a user LoRA, `resident` is the fastest high-VRAM path. When you pass `--lora-path`, SGLang still applies the user LoRA during the two-stage switch, so use `resident` on H200-class GPUs for enough VRAM, but do not expect the same premerged-stage2 benefit as the no-user-LoRA path.
|
For native LTX-2.3 two-stage serving without a user LoRA, `resident` is the fastest high-VRAM path. LTX-2 still applies the distilled LoRA during the stage switch, so `--ltx2-two-stage-device-mode` is mainly an LTX-2.3 optimization. When you pass `--lora-path`, SGLang still applies the user LoRA during the two-stage switch, so use `resident` on H200-class GPUs for enough VRAM, but do not expect the same premerged-stage2 benefit as the no-user-LoRA path.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
### 3.3 Fast multi-GPU presets
|
### 3.3 Fast multi-GPU presets
|
||||||
@@ -80,11 +80,12 @@ For latency-oriented LTX serving, prefer CFG parallel over sequence parallelism.
|
|||||||
|
|
||||||
| Target | Recommended server flags | Notes |
|
| Target | Recommended server flags | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 1 high-VRAM GPU | `--ltx2-two-stage-device-mode resident` | Fastest two-stage setup when both DiTs fit. |
|
| LTX-2.3, 1 high-VRAM GPU | `--ltx2-two-stage-device-mode resident` | Fastest two-stage setup when both DiTs fit. |
|
||||||
| 1 standard GPU | `--ltx2-two-stage-device-mode snapshot` | Lower VRAM than `resident`; use this when H100-class memory is tight. |
|
| LTX-2.3, 1 standard GPU | `--ltx2-two-stage-device-mode snapshot` | Lower VRAM than `resident`; use this when H100-class memory is tight. |
|
||||||
| 2 GPUs | `--num-gpus 2 --enable-cfg-parallel --ltx2-two-stage-device-mode resident` | Fastest common 2-GPU setup. |
|
| LTX-2, 2 GPUs | `--num-gpus 2 --enable-cfg-parallel` | Fastest verified 2-GPU setup; keep `--dit-layerwise-offload` disabled unless memory is tight. |
|
||||||
| 4 GPUs | `--num-gpus 4 --tp-size 2 --enable-cfg-parallel --ltx2-two-stage-device-mode resident` | Fastest common 4-GPU layout: TP2 inside each CFG branch. |
|
| LTX-2.3, 2 GPUs | `--num-gpus 2 --enable-cfg-parallel --ltx2-two-stage-device-mode resident` | Fastest common 2-GPU setup. |
|
||||||
| Official comparison | `--ltx2-two-stage-device-mode original` | Use this only when matching the original stage-switch semantics matters. |
|
| LTX-2.3, 4 GPUs | `--num-gpus 4 --tp-size 2 --enable-cfg-parallel --ltx2-two-stage-device-mode resident` | Fastest common 4-GPU layout: TP2 inside each CFG branch. |
|
||||||
|
| Official comparison | `--ltx2-two-stage-device-mode original` | Use this only when matching the original LTX-2.3 stage-switch semantics matters. |
|
||||||
|
|
||||||
Use `--enable-cfg-parallel` for degree-2 CFG parallel. Use `--cfg-parallel-size` only when you explicitly need a different CFG branch count. If `resident` exceeds available VRAM, keep the same parallelism preset and switch only the device mode to `snapshot`.
|
Use `--enable-cfg-parallel` for degree-2 CFG parallel. Use `--cfg-parallel-size` only when you explicitly need a different CFG branch count. If `resident` exceeds available VRAM, keep the same parallelism preset and switch only the device mode to `snapshot`.
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export const LTXDeployment = () => {
|
|||||||
|
|
||||||
let command = `sglang serve \\\n --model-path ${config.repoId} \\\n --pipeline-class-name ${pipelineClass}`;
|
let command = `sglang serve \\\n --model-path ${config.repoId} \\\n --pipeline-class-name ${pipelineClass}`;
|
||||||
command += getParallelFlags();
|
command += getParallelFlags();
|
||||||
if (values.pipeline !== 'one-stage') {
|
if (values.model === 'ltx23' && values.pipeline !== 'one-stage') {
|
||||||
command += ` \\\n --ltx2-two-stage-device-mode ${getDeviceMode()}`;
|
command += ` \\\n --ltx2-two-stage-device-mode ${getDeviceMode()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
from contextlib import nullcontext
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
from torch.nn.attention import SDPBackend, sdpa_kernel
|
||||||
|
|
||||||
from sglang.multimodal_gen.runtime.layers.attention.backends.attention_backend import ( # FlashAttentionMetadata,
|
from sglang.multimodal_gen.runtime.layers.attention.backends.attention_backend import ( # FlashAttentionMetadata,
|
||||||
AttentionBackend,
|
AttentionBackend,
|
||||||
@@ -14,6 +17,13 @@ from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
|||||||
|
|
||||||
logger = init_logger(__name__)
|
logger = init_logger(__name__)
|
||||||
|
|
||||||
|
_PYTORCH_DEFAULT_CUDA_SDP_BACKENDS = [
|
||||||
|
SDPBackend.CUDNN_ATTENTION,
|
||||||
|
SDPBackend.FLASH_ATTENTION,
|
||||||
|
SDPBackend.EFFICIENT_ATTENTION,
|
||||||
|
SDPBackend.MATH,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class SDPABackend(AttentionBackend):
|
class SDPABackend(AttentionBackend):
|
||||||
|
|
||||||
@@ -51,6 +61,7 @@ class SDPAImpl(AttentionImpl):
|
|||||||
self.causal = causal
|
self.causal = causal
|
||||||
self.softmax_scale = softmax_scale
|
self.softmax_scale = softmax_scale
|
||||||
self.dropout = extra_impl_args.get("dropout_p", 0.0)
|
self.dropout = extra_impl_args.get("dropout_p", 0.0)
|
||||||
|
self.allow_cudnn_sdp = bool(extra_impl_args.get("allow_cudnn_sdp", False))
|
||||||
|
|
||||||
def forward(
|
def forward(
|
||||||
self,
|
self,
|
||||||
@@ -71,6 +82,12 @@ class SDPAImpl(AttentionImpl):
|
|||||||
}
|
}
|
||||||
if query.shape[1] != key.shape[1]:
|
if query.shape[1] != key.shape[1]:
|
||||||
attn_kwargs["enable_gqa"] = True
|
attn_kwargs["enable_gqa"] = True
|
||||||
|
sdpa_context = (
|
||||||
|
sdpa_kernel(_PYTORCH_DEFAULT_CUDA_SDP_BACKENDS)
|
||||||
|
if self.allow_cudnn_sdp and query.device.type == "cuda"
|
||||||
|
else nullcontext()
|
||||||
|
)
|
||||||
|
with sdpa_context:
|
||||||
output = torch.nn.functional.scaled_dot_product_attention(
|
output = torch.nn.functional.scaled_dot_product_attention(
|
||||||
query, key, value, **attn_kwargs
|
query, key, value, **attn_kwargs
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
|
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
|
||||||
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
from contextlib import nullcontext
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
import torch.nn as nn
|
import torch.nn as nn
|
||||||
|
from torch.nn.attention import SDPBackend, sdpa_kernel
|
||||||
|
|
||||||
from sglang.multimodal_gen.runtime.distributed.communication_op import (
|
from sglang.multimodal_gen.runtime.distributed.communication_op import (
|
||||||
sequence_model_parallel_all_gather,
|
sequence_model_parallel_all_gather,
|
||||||
@@ -35,6 +37,13 @@ from sglang.multimodal_gen.runtime.managers.forward_context import (
|
|||||||
from sglang.multimodal_gen.runtime.platforms import AttentionBackendEnum
|
from sglang.multimodal_gen.runtime.platforms import AttentionBackendEnum
|
||||||
from sglang.multimodal_gen.utils import get_compute_dtype
|
from sglang.multimodal_gen.utils import get_compute_dtype
|
||||||
|
|
||||||
|
_PYTORCH_DEFAULT_CUDA_SDP_BACKENDS = [
|
||||||
|
SDPBackend.CUDNN_ATTENTION,
|
||||||
|
SDPBackend.FLASH_ATTENTION,
|
||||||
|
SDPBackend.EFFICIENT_ATTENTION,
|
||||||
|
SDPBackend.MATH,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class UlyssesAttention(nn.Module):
|
class UlyssesAttention(nn.Module):
|
||||||
"""Ulysses-style SequenceParallelism attention layer."""
|
"""Ulysses-style SequenceParallelism attention layer."""
|
||||||
@@ -246,6 +255,7 @@ class LocalAttention(nn.Module):
|
|||||||
head_size, dtype, supported_attention_backends=supported_attention_backends
|
head_size, dtype, supported_attention_backends=supported_attention_backends
|
||||||
)
|
)
|
||||||
impl_cls = attn_backend.get_impl_cls()
|
impl_cls = attn_backend.get_impl_cls()
|
||||||
|
self.allow_cudnn_sdp = bool(extra_impl_args.get("allow_cudnn_sdp", False))
|
||||||
self.attn_impl = impl_cls(
|
self.attn_impl = impl_cls(
|
||||||
num_heads=num_heads,
|
num_heads=num_heads,
|
||||||
head_size=head_size,
|
head_size=head_size,
|
||||||
@@ -304,6 +314,12 @@ class LocalAttention(nn.Module):
|
|||||||
mask = mask[:, None, :, :]
|
mask = mask[:, None, :, :]
|
||||||
mask = (mask - 1.0) * torch.finfo(q_.dtype).max
|
mask = (mask - 1.0) * torch.finfo(q_.dtype).max
|
||||||
|
|
||||||
|
sdpa_context = (
|
||||||
|
sdpa_kernel(_PYTORCH_DEFAULT_CUDA_SDP_BACKENDS)
|
||||||
|
if self.allow_cudnn_sdp and q_.device.type == "cuda"
|
||||||
|
else nullcontext()
|
||||||
|
)
|
||||||
|
with sdpa_context:
|
||||||
return torch.nn.functional.scaled_dot_product_attention(
|
return torch.nn.functional.scaled_dot_product_attention(
|
||||||
q_,
|
q_,
|
||||||
k_,
|
k_,
|
||||||
@@ -373,6 +389,7 @@ class USPAttention(nn.Module):
|
|||||||
f"Please ensure your platform supports these backends."
|
f"Please ensure your platform supports these backends."
|
||||||
)
|
)
|
||||||
impl_cls: Type["AttentionImpl"] = attn_backend.get_impl_cls()
|
impl_cls: Type["AttentionImpl"] = attn_backend.get_impl_cls()
|
||||||
|
self.allow_cudnn_sdp = bool(extra_impl_args.get("allow_cudnn_sdp", False))
|
||||||
self.attn_impl = impl_cls(
|
self.attn_impl = impl_cls(
|
||||||
num_heads=num_heads,
|
num_heads=num_heads,
|
||||||
head_size=head_size,
|
head_size=head_size,
|
||||||
@@ -453,6 +470,12 @@ class USPAttention(nn.Module):
|
|||||||
k_ = k.transpose(1, 2)
|
k_ = k.transpose(1, 2)
|
||||||
v_ = v.transpose(1, 2)
|
v_ = v.transpose(1, 2)
|
||||||
mask = _prepare_sdpa_mask(attn_mask, dtype=q_.dtype, device=q_.device)
|
mask = _prepare_sdpa_mask(attn_mask, dtype=q_.dtype, device=q_.device)
|
||||||
|
sdpa_context = (
|
||||||
|
sdpa_kernel(_PYTORCH_DEFAULT_CUDA_SDP_BACKENDS)
|
||||||
|
if self.allow_cudnn_sdp and q_.device.type == "cuda"
|
||||||
|
else nullcontext()
|
||||||
|
)
|
||||||
|
with sdpa_context:
|
||||||
return torch.nn.functional.scaled_dot_product_attention(
|
return torch.nn.functional.scaled_dot_product_attention(
|
||||||
q_,
|
q_,
|
||||||
k_,
|
k_,
|
||||||
@@ -489,6 +512,12 @@ class USPAttention(nn.Module):
|
|||||||
k_ = k.transpose(1, 2)
|
k_ = k.transpose(1, 2)
|
||||||
v_ = v.transpose(1, 2)
|
v_ = v.transpose(1, 2)
|
||||||
mask = _prepare_sdpa_mask(gathered_mask, dtype=q_.dtype, device=q_.device)
|
mask = _prepare_sdpa_mask(gathered_mask, dtype=q_.dtype, device=q_.device)
|
||||||
|
sdpa_context = (
|
||||||
|
sdpa_kernel(_PYTORCH_DEFAULT_CUDA_SDP_BACKENDS)
|
||||||
|
if self.allow_cudnn_sdp and q_.device.type == "cuda"
|
||||||
|
else nullcontext()
|
||||||
|
)
|
||||||
|
with sdpa_context:
|
||||||
out = torch.nn.functional.scaled_dot_product_attention(
|
out = torch.nn.functional.scaled_dot_product_attention(
|
||||||
q_,
|
q_,
|
||||||
k_,
|
k_,
|
||||||
|
|||||||
@@ -4,11 +4,8 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import functools
|
|
||||||
import math
|
|
||||||
from typing import Any, Optional, Tuple, Union
|
from typing import Any, Optional, Tuple, Union
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import torch
|
import torch
|
||||||
import torch.nn as nn
|
import torch.nn as nn
|
||||||
import torch.nn.functional as F
|
import torch.nn.functional as F
|
||||||
@@ -98,17 +95,6 @@ def _ltx2_build_batched_perturbation_states(
|
|||||||
return states
|
return states
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=5)
|
|
||||||
def _ltx2_rope_freq_grid_np(theta: float, num_pos_dims: int, dim: int) -> torch.Tensor:
|
|
||||||
# Official LTX uses NumPy float64 for double-precision RoPE frequencies.
|
|
||||||
n_elem = 2 * num_pos_dims
|
|
||||||
pow_indices = np.power(
|
|
||||||
theta,
|
|
||||||
np.linspace(0.0, 1.0, dim // n_elem, dtype=np.float64),
|
|
||||||
)
|
|
||||||
return torch.tensor(pow_indices * math.pi / 2.0, dtype=torch.float32)
|
|
||||||
|
|
||||||
|
|
||||||
def apply_interleaved_rotary_emb(
|
def apply_interleaved_rotary_emb(
|
||||||
x: torch.Tensor, freqs: Tuple[torch.Tensor, torch.Tensor]
|
x: torch.Tensor, freqs: Tuple[torch.Tensor, torch.Tensor]
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
@@ -351,18 +337,16 @@ class LTX2AudioVideoRotaryPosEmbed(nn.Module):
|
|||||||
).to(device)
|
).to(device)
|
||||||
|
|
||||||
num_rope_elems = num_pos_dims * 2
|
num_rope_elems = num_pos_dims * 2
|
||||||
if self.double_precision:
|
# LTX-2.3 HQ is sensitive to RoPE rounding; keep frequency generation on
|
||||||
freqs = _ltx2_rope_freq_grid_np(self.theta, num_pos_dims, self.dim).to(
|
# the target device instead of caching a CPU/NumPy tensor.
|
||||||
device=device
|
freqs_dtype = torch.float64 if self.double_precision else torch.float32
|
||||||
)
|
|
||||||
else:
|
|
||||||
pow_indices = torch.pow(
|
pow_indices = torch.pow(
|
||||||
self.theta,
|
self.theta,
|
||||||
torch.linspace(
|
torch.linspace(
|
||||||
start=0.0,
|
start=0.0,
|
||||||
end=1.0,
|
end=1.0,
|
||||||
steps=self.dim // num_rope_elems,
|
steps=self.dim // num_rope_elems,
|
||||||
dtype=torch.float32,
|
dtype=freqs_dtype,
|
||||||
device=device,
|
device=device,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -647,6 +631,8 @@ class LTX2Attention(nn.Module):
|
|||||||
causal=False,
|
causal=False,
|
||||||
supported_attention_backends=supported_attention_backends,
|
supported_attention_backends=supported_attention_backends,
|
||||||
prefix=f"{prefix}.attn",
|
prefix=f"{prefix}.attn",
|
||||||
|
# official LTX2 torch_sdpa uses cuDNN; cuda setup disables it
|
||||||
|
allow_cudnn_sdp=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.attn = USPAttention(
|
self.attn = USPAttention(
|
||||||
@@ -658,6 +644,8 @@ class LTX2Attention(nn.Module):
|
|||||||
causal=False,
|
causal=False,
|
||||||
supported_attention_backends=supported_attention_backends,
|
supported_attention_backends=supported_attention_backends,
|
||||||
prefix=f"{prefix}.attn",
|
prefix=f"{prefix}.attn",
|
||||||
|
# official LTX2 torch_sdpa uses cuDNN; cuda setup disables it
|
||||||
|
allow_cudnn_sdp=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def forward(
|
def forward(
|
||||||
@@ -1422,9 +1410,18 @@ class LTX2VideoTransformer3DModel(CachableDiT, OffloadableDiTMixin):
|
|||||||
if hasattr(arch.rope_type, "value")
|
if hasattr(arch.rope_type, "value")
|
||||||
else str(arch.rope_type)
|
else str(arch.rope_type)
|
||||||
)
|
)
|
||||||
rope_double_precision = bool(
|
frequencies_precision = hf_config.get("frequencies_precision")
|
||||||
|
if frequencies_precision is None:
|
||||||
|
frequencies_precision = getattr(arch, "frequencies_precision", None)
|
||||||
|
|
||||||
|
# diffusers/LTX configs use `frequencies_precision` for this RoPE switch
|
||||||
|
rope_double_precision = (
|
||||||
|
str(frequencies_precision) == "float64"
|
||||||
|
if frequencies_precision is not None
|
||||||
|
else bool(
|
||||||
hf_config.get("rope_double_precision", arch.double_precision_rope)
|
hf_config.get("rope_double_precision", arch.double_precision_rope)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
self.quantize_video_rope_coords_to_hidden_dtype = bool(
|
self.quantize_video_rope_coords_to_hidden_dtype = bool(
|
||||||
hf_config.get("quantize_video_rope_coords_to_hidden_dtype", False)
|
hf_config.get("quantize_video_rope_coords_to_hidden_dtype", False)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -146,14 +146,21 @@ class Gemma3Attention(nn.Module):
|
|||||||
prefix=f"{prefix}.o_proj",
|
prefix=f"{prefix}.o_proj",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.layer_type = (
|
layer_types = getattr(config.text_config, "layer_types", None)
|
||||||
config.text_config.layer_types[layer_id]
|
if layer_types:
|
||||||
if hasattr(config.text_config, "layer_types")
|
self.layer_type = layer_types[layer_id]
|
||||||
else None
|
self.is_sliding = self.layer_type == "sliding_attention"
|
||||||
|
else:
|
||||||
|
# official Gemma3 uses sliding_window_pattern when layer_types is absent
|
||||||
|
sliding_window_pattern = getattr(
|
||||||
|
config.text_config, "sliding_window_pattern", None
|
||||||
)
|
)
|
||||||
self.is_sliding = (
|
self.is_sliding = (
|
||||||
config.text_config.layer_types[layer_id] == "sliding_attention"
|
bool((layer_id + 1) % sliding_window_pattern)
|
||||||
|
if sliding_window_pattern
|
||||||
|
else False
|
||||||
)
|
)
|
||||||
|
self.layer_type = "sliding_attention" if self.is_sliding else None
|
||||||
|
|
||||||
rope_parameters = getattr(config.text_config, "rope_parameters", None) or {}
|
rope_parameters = getattr(config.text_config, "rope_parameters", None) or {}
|
||||||
layer_rope_params = {}
|
layer_rope_params = {}
|
||||||
@@ -204,7 +211,7 @@ class Gemma3Attention(nn.Module):
|
|||||||
self.sliding_window = None
|
self.sliding_window = None
|
||||||
self.window_size = (-1, -1)
|
self.window_size = (-1, -1)
|
||||||
|
|
||||||
self.rotary_emb = get_rope(
|
self.rotary_pos_emb = get_rope(
|
||||||
self.head_dim,
|
self.head_dim,
|
||||||
rotary_dim=self.head_dim,
|
rotary_dim=self.head_dim,
|
||||||
max_position=config.text_config.max_position_embeddings,
|
max_position=config.text_config.max_position_embeddings,
|
||||||
@@ -213,12 +220,6 @@ class Gemma3Attention(nn.Module):
|
|||||||
is_neox_style=True,
|
is_neox_style=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.rope_scaling_factor = (
|
|
||||||
float(rope_scaling["factor"])
|
|
||||||
if rope_scaling and rope_scaling.get("factor")
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
|
|
||||||
# Local Attention not support attention mask, we use global attention instead.
|
# Local Attention not support attention mask, we use global attention instead.
|
||||||
# self.attn = LocalAttention(
|
# self.attn = LocalAttention(
|
||||||
# self.num_heads,
|
# self.num_heads,
|
||||||
@@ -238,25 +239,18 @@ class Gemma3Attention(nn.Module):
|
|||||||
dim=self.head_dim, eps=config.text_config.rms_norm_eps
|
dim=self.head_dim, eps=config.text_config.rms_norm_eps
|
||||||
)
|
)
|
||||||
|
|
||||||
def rotary_emb(self, positions, q, k):
|
def _apply_rotary_pos_emb(self, positions, q, k):
|
||||||
"""Apply RoPE using the same device-side inv_freq materialization as LTX."""
|
positions_flat = positions.flatten().to(
|
||||||
positions_flat = positions.flatten().float()
|
device=self.rotary_pos_emb.cos_sin_cache.device, dtype=torch.long
|
||||||
num_tokens = positions_flat.shape[0]
|
|
||||||
|
|
||||||
with torch.autocast(device_type=q.device.type, enabled=False):
|
|
||||||
freq_indices = (
|
|
||||||
torch.arange(
|
|
||||||
0, self.head_dim, 2, dtype=torch.int64, device=q.device
|
|
||||||
).float()
|
|
||||||
/ self.head_dim
|
|
||||||
)
|
)
|
||||||
inv_freq = 1.0 / (self.rope_theta**freq_indices)
|
cos_sin = self.rotary_pos_emb.cos_sin_cache.index_select(0, positions_flat)
|
||||||
if self.rope_scaling_factor is not None:
|
cos, sin = cos_sin.chunk(2, dim=-1)
|
||||||
inv_freq = inv_freq / self.rope_scaling_factor
|
# match HF Gemma3: expand half-dim freqs to full head dim before rotate_half
|
||||||
freqs = torch.outer(positions_flat, inv_freq)
|
cos = torch.cat((cos, cos), dim=-1).to(device=q.device, dtype=q.dtype)
|
||||||
emb = freqs.repeat(1, 2)
|
sin = torch.cat((sin, sin), dim=-1).to(device=q.device, dtype=q.dtype)
|
||||||
cos = emb.cos().to(q.dtype).unsqueeze(1)
|
cos = cos.unsqueeze(1)
|
||||||
sin = emb.sin().to(q.dtype).unsqueeze(1)
|
sin = sin.unsqueeze(1)
|
||||||
|
num_tokens = positions_flat.shape[0]
|
||||||
|
|
||||||
q = q.reshape(num_tokens, -1, self.head_dim)
|
q = q.reshape(num_tokens, -1, self.head_dim)
|
||||||
k = k.reshape(num_tokens, -1, self.head_dim)
|
k = k.reshape(num_tokens, -1, self.head_dim)
|
||||||
@@ -283,7 +277,7 @@ class Gemma3Attention(nn.Module):
|
|||||||
k = self.k_norm(k)
|
k = self.k_norm(k)
|
||||||
|
|
||||||
# Apply RoPE
|
# Apply RoPE
|
||||||
q, k = self.rotary_emb(positions, q, k)
|
q, k = self._apply_rotary_pos_emb(positions, q, k)
|
||||||
q = q.reshape(batch_size, seq_len, self.num_heads, self.head_dim)
|
q = q.reshape(batch_size, seq_len, self.num_heads, self.head_dim)
|
||||||
k = k.reshape(batch_size, seq_len, self.num_kv_heads, self.head_dim)
|
k = k.reshape(batch_size, seq_len, self.num_kv_heads, self.head_dim)
|
||||||
|
|
||||||
@@ -306,7 +300,7 @@ class Gemma3Attention(nn.Module):
|
|||||||
attn_mask = attn_mask.masked_fill(causal, False)
|
attn_mask = attn_mask.masked_fill(causal, False)
|
||||||
if self.is_sliding and self.sliding_window is not None:
|
if self.is_sliding and self.sliding_window is not None:
|
||||||
idx = torch.arange(seq_len, device=hidden_states.device)
|
idx = torch.arange(seq_len, device=hidden_states.device)
|
||||||
dist = idx[None, :] - idx[:, None]
|
dist = idx[:, None] - idx[None, :]
|
||||||
too_far = dist > self.sliding_window
|
too_far = dist > self.sliding_window
|
||||||
attn_mask = attn_mask.masked_fill(too_far, False)
|
attn_mask = attn_mask.masked_fill(too_far, False)
|
||||||
|
|
||||||
|
|||||||
@@ -193,7 +193,6 @@ class LTX2SigmaPreparationStage(PipelineStage):
|
|||||||
int(batch.num_inference_steps),
|
int(batch.num_inference_steps),
|
||||||
number_of_tokens=latent_num_frames * latent_height * latent_width,
|
number_of_tokens=latent_num_frames * latent_height * latent_width,
|
||||||
)
|
)
|
||||||
batch.sigmas.append(0.0011)
|
|
||||||
else:
|
else:
|
||||||
batch.sigmas = build_official_ltx2_sigmas(
|
batch.sigmas = build_official_ltx2_sigmas(
|
||||||
int(batch.num_inference_steps)
|
int(batch.num_inference_steps)
|
||||||
|
|||||||
@@ -82,8 +82,12 @@ class ParallelExecutor(PipelineExecutor):
|
|||||||
|
|
||||||
elif paradigm == StageParallelismType.CFG_PARALLEL:
|
elif paradigm == StageParallelismType.CFG_PARALLEL:
|
||||||
obj_list = [batch] if rank == 0 else []
|
obj_list = [batch] if rank == 0 else []
|
||||||
|
# `dist.broadcast(src=...)` expects a global rank for process groups.
|
||||||
broadcasted_list = broadcast_pyobj(
|
broadcasted_list = broadcast_pyobj(
|
||||||
obj_list, rank=rank, dist_group=cfg_group.cpu_group, src=0
|
obj_list,
|
||||||
|
rank=get_world_rank(),
|
||||||
|
dist_group=cfg_group.cpu_group,
|
||||||
|
src=cfg_group.ranks[0],
|
||||||
)
|
)
|
||||||
if rank != 0:
|
if rank != 0:
|
||||||
batch = broadcasted_list[0]
|
batch = broadcasted_list[0]
|
||||||
|
|||||||
@@ -354,12 +354,11 @@ class LTX2RefinementStage(LTX2AVDenoisingStage):
|
|||||||
|
|
||||||
scheduler = clone_scheduler_runtime(original_batch_scheduler or self.scheduler)
|
scheduler = clone_scheduler_runtime(original_batch_scheduler or self.scheduler)
|
||||||
distilled_device = scheduler.sigmas.device
|
distilled_device = scheduler.sigmas.device
|
||||||
|
num_steps = len(self.distilled_sigmas) - 1
|
||||||
# Inject `0.0011` before the terminal `0.0` to avoid the
|
# Inject `0.0011` before the terminal `0.0` to avoid the
|
||||||
# `sigma_next==0` singularity in res2s' `(sample - denoised) /
|
# `sigma_next==0` singularity in res2s' `(sample - denoised) /
|
||||||
# (sigma - sigma_next)`. Official `res2s_denoising_loop` does this
|
# (sigma - sigma_next)`. This changes the final sigma pair only; it
|
||||||
# exact injection (samplers.py:262); official `euler_denoising_loop`
|
# must not add an extra denoising timestep.
|
||||||
# does NOT — it uses `sigma_next` directly. So gate on the active
|
|
||||||
# sampler, not on the model variant.
|
|
||||||
if self.sampler_name == "res2s" and self.distilled_sigmas[-1].item() == 0.0:
|
if self.sampler_name == "res2s" and self.distilled_sigmas[-1].item() == 0.0:
|
||||||
scheduler_sigmas = torch.cat(
|
scheduler_sigmas = torch.cat(
|
||||||
[
|
[
|
||||||
@@ -372,9 +371,10 @@ class LTX2RefinementStage(LTX2AVDenoisingStage):
|
|||||||
scheduler_sigmas = self.distilled_sigmas
|
scheduler_sigmas = self.distilled_sigmas
|
||||||
|
|
||||||
scheduler.sigmas = scheduler_sigmas
|
scheduler.sigmas = scheduler_sigmas
|
||||||
num_steps = len(scheduler_sigmas) - 1
|
|
||||||
scheduler.num_inference_steps = num_steps
|
scheduler.num_inference_steps = num_steps
|
||||||
scheduler.timesteps = (scheduler_sigmas[:num_steps] * 1000).to(distilled_device)
|
scheduler.timesteps = (self.distilled_sigmas[:num_steps] * 1000).to(
|
||||||
|
distilled_device
|
||||||
|
)
|
||||||
scheduler._step_index = None
|
scheduler._step_index = None
|
||||||
scheduler._begin_index = None
|
scheduler._begin_index = None
|
||||||
|
|
||||||
|
|||||||
@@ -422,45 +422,76 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
return pred * factor
|
return pred * factor
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _ltx2_combine_guided_x0_parallel(
|
def _ltx2_combine_guided_x0_parallel_av(
|
||||||
cls,
|
cls,
|
||||||
*,
|
*,
|
||||||
latents: torch.Tensor,
|
video_latents: torch.Tensor,
|
||||||
local_velocities: dict[str, torch.Tensor],
|
audio_latents: torch.Tensor,
|
||||||
sigma: float | torch.Tensor,
|
local_video_velocities: dict[str, torch.Tensor],
|
||||||
cfg_scale: float,
|
local_audio_velocities: dict[str, torch.Tensor],
|
||||||
stg_scale: float,
|
video_sigma: float | torch.Tensor,
|
||||||
rescale_scale: float,
|
audio_sigma: float | torch.Tensor,
|
||||||
modality_scale: float,
|
video_cfg_scale: float,
|
||||||
) -> torch.Tensor:
|
video_stg_scale: float,
|
||||||
"""Combine stage-1 guidance passes that were split across CFG ranks.
|
video_rescale_scale: float,
|
||||||
|
video_modality_scale: float,
|
||||||
|
audio_cfg_scale: float,
|
||||||
|
audio_stg_scale: float,
|
||||||
|
audio_rescale_scale: float,
|
||||||
|
audio_modality_scale: float,
|
||||||
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||||
|
"""Reconstruct CFG branches once for both modalities before guider math."""
|
||||||
|
first_video_velocity = next(iter(local_video_velocities.values()))
|
||||||
|
first_audio_velocity = next(iter(local_audio_velocities.values()))
|
||||||
|
video_template = cls._ltx2_velocity_to_x0(
|
||||||
|
video_latents, first_video_velocity, video_sigma
|
||||||
|
)
|
||||||
|
audio_template = cls._ltx2_velocity_to_x0(
|
||||||
|
audio_latents, first_audio_velocity, audio_sigma
|
||||||
|
)
|
||||||
|
video_numel = video_template.numel()
|
||||||
|
branches: dict[str, tuple[torch.Tensor, torch.Tensor]] = {}
|
||||||
|
|
||||||
Each pass is one model forward with a different conditioning setup:
|
for name in ("cond", "neg", "perturbed", "modality"):
|
||||||
positive prompt, negative prompt, attention-disabled perturbation, or
|
if name in local_video_velocities:
|
||||||
audio/video cross-attention disabled. A rank only owns some passes, so
|
local_video = cls._ltx2_velocity_to_x0(
|
||||||
it contributes weighted x0 terms for those passes and all-reduce
|
video_latents, local_video_velocities[name], video_sigma
|
||||||
reconstructs the full guided x0 on every rank.
|
)
|
||||||
"""
|
local_audio = cls._ltx2_velocity_to_x0(
|
||||||
coefficients = {
|
audio_latents, local_audio_velocities[name], audio_sigma
|
||||||
"cond": cfg_scale + stg_scale + modality_scale - 1.0,
|
)
|
||||||
"neg": 1.0 - cfg_scale,
|
else:
|
||||||
"perturbed": -stg_scale,
|
local_video = torch.zeros_like(video_template)
|
||||||
"modality": 1.0 - modality_scale,
|
local_audio = torch.zeros_like(audio_template)
|
||||||
}
|
flat = torch.cat((local_video.reshape(-1), local_audio.reshape(-1)))
|
||||||
first_velocity = next(iter(local_velocities.values()))
|
flat = cfg_model_parallel_all_reduce(flat)
|
||||||
template = cls._ltx2_velocity_to_x0(latents, first_velocity, sigma)
|
branches[name] = (
|
||||||
cond_partial = torch.zeros_like(template)
|
flat[:video_numel].reshape_as(video_template),
|
||||||
pred_partial = torch.zeros_like(template)
|
flat[video_numel:].reshape_as(audio_template),
|
||||||
|
)
|
||||||
|
|
||||||
for name, velocity in local_velocities.items():
|
# folding the coefficients changes bf16 rounding and drifts from single-GPU
|
||||||
denoised = cls._ltx2_velocity_to_x0(latents, velocity, sigma)
|
guided_video = cls._ltx2_calculate_guided_x0(
|
||||||
if name == "cond":
|
cond=branches["cond"][0],
|
||||||
cond_partial = cond_partial + denoised
|
uncond_text=branches["neg"][0],
|
||||||
pred_partial = pred_partial + denoised * coefficients[name]
|
uncond_perturbed=branches["perturbed"][0],
|
||||||
|
uncond_modality=branches["modality"][0],
|
||||||
cond = cfg_model_parallel_all_reduce(cond_partial)
|
cfg_scale=video_cfg_scale,
|
||||||
pred = cfg_model_parallel_all_reduce(pred_partial)
|
stg_scale=video_stg_scale,
|
||||||
return cls._ltx2_apply_rescale(cond, pred, rescale_scale)
|
rescale_scale=video_rescale_scale,
|
||||||
|
modality_scale=video_modality_scale,
|
||||||
|
)
|
||||||
|
guided_audio = cls._ltx2_calculate_guided_x0(
|
||||||
|
cond=branches["cond"][1],
|
||||||
|
uncond_text=branches["neg"][1],
|
||||||
|
uncond_perturbed=branches["perturbed"][1],
|
||||||
|
uncond_modality=branches["modality"][1],
|
||||||
|
cfg_scale=audio_cfg_scale,
|
||||||
|
stg_scale=audio_stg_scale,
|
||||||
|
rescale_scale=audio_rescale_scale,
|
||||||
|
modality_scale=audio_modality_scale,
|
||||||
|
)
|
||||||
|
return guided_video, guided_audio
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _ltx2_channelwise_normalize(noise: torch.Tensor) -> torch.Tensor:
|
def _ltx2_channelwise_normalize(noise: torch.Tensor) -> torch.Tensor:
|
||||||
@@ -556,7 +587,9 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
dtype=sliced.dtype,
|
dtype=sliced.dtype,
|
||||||
)
|
)
|
||||||
sliced = torch.cat([sliced, pad], dim=1)
|
sliced = torch.cat([sliced, pad], dim=1)
|
||||||
return sliced.to(dtype=reference_tensor.dtype)
|
return sliced
|
||||||
|
# The native HQ SDE path consumes this through `.float()`; keep the
|
||||||
|
# original downcast boundary before that upcast to preserve the trajectory.
|
||||||
return cls._ltx2_res2s_new_noise(reference_tensor, generator).to(
|
return cls._ltx2_res2s_new_noise(reference_tensor, generator).to(
|
||||||
dtype=reference_tensor.dtype
|
dtype=reference_tensor.dtype
|
||||||
)
|
)
|
||||||
@@ -703,6 +736,8 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
update (midpoint SDE, bongmath anchor refinement, midpoint re-eval,
|
update (midpoint SDE, bongmath anchor refinement, midpoint re-eval,
|
||||||
final RK2 combination with SDE noise). Mirrors the guided stage-1 res2s
|
final RK2 combination with SDE noise). Mirrors the guided stage-1 res2s
|
||||||
math but without CFG/STG (stage-2 HQ uses the simple CFG path).
|
math but without CFG/STG (stage-2 HQ uses the simple CFG path).
|
||||||
|
Raw HQ model timesteps are only inputs to the DiT call; stage-2 res2s
|
||||||
|
math stays in scheduler sigma space.
|
||||||
"""
|
"""
|
||||||
sigma_val = float(sigma.item())
|
sigma_val = float(sigma.item())
|
||||||
sigma_next_val = float(sigma_next.item())
|
sigma_next_val = float(sigma_next.item())
|
||||||
@@ -711,24 +746,10 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
denoised_video = ctx.latents.float()
|
denoised_video = ctx.latents.float()
|
||||||
denoised_audio = ctx.audio_latents.float()
|
denoised_audio = ctx.audio_latents.float()
|
||||||
else:
|
else:
|
||||||
video_sigma_for_x0 = (
|
denoised_video = ctx.latents.float() - sigma * model_video_velocity.float()
|
||||||
model_video_timestep
|
denoised_audio = (
|
||||||
if ctx.use_ltx23_hq_timestep_semantics
|
ctx.audio_latents.float() - sigma * model_audio_velocity.float()
|
||||||
and model_video_timestep is not None
|
|
||||||
else sigma
|
|
||||||
)
|
)
|
||||||
audio_sigma_for_x0 = (
|
|
||||||
model_audio_timestep
|
|
||||||
if ctx.use_ltx23_hq_timestep_semantics
|
|
||||||
and model_audio_timestep is not None
|
|
||||||
else sigma
|
|
||||||
)
|
|
||||||
denoised_video = self._ltx2_velocity_to_x0(
|
|
||||||
ctx.latents, model_video_velocity, video_sigma_for_x0
|
|
||||||
).float()
|
|
||||||
denoised_audio = self._ltx2_velocity_to_x0(
|
|
||||||
ctx.audio_latents, model_audio_velocity, audio_sigma_for_x0
|
|
||||||
).float()
|
|
||||||
|
|
||||||
if sigma_val == 0.0 or sigma_next_val == 0.0:
|
if sigma_val == 0.0 or sigma_next_val == 0.0:
|
||||||
next_video = denoised_video.to(dtype=ctx.latents.dtype)
|
next_video = denoised_video.to(dtype=ctx.latents.dtype)
|
||||||
@@ -738,11 +759,6 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
|
|
||||||
sigma_d = sigma.double()
|
sigma_d = sigma.double()
|
||||||
sigma_next_d = sigma_next.double()
|
sigma_next_d = sigma_next.double()
|
||||||
if ctx.use_ltx23_hq_timestep_semantics:
|
|
||||||
h = self._ltx2_res2s_step_size_scalar(sigma_d, sigma_next_d)
|
|
||||||
a21, b1, b2 = self._ltx2_get_res2s_coefficients_scalar(h)
|
|
||||||
h_value = h
|
|
||||||
else:
|
|
||||||
h = -torch.log(torch.clamp(sigma_next_d / sigma_d, min=1e-12))
|
h = -torch.log(torch.clamp(sigma_next_d / sigma_d, min=1e-12))
|
||||||
a21, b1, b2 = self._ltx2_get_res2s_coefficients(h)
|
a21, b1, b2 = self._ltx2_get_res2s_coefficients(h)
|
||||||
h_value = float(h.item())
|
h_value = float(h.item())
|
||||||
@@ -809,22 +825,8 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
midpoint_video_model_latents, midpoint_audio_model_latents, sub_sigma
|
midpoint_video_model_latents, midpoint_audio_model_latents, sub_sigma
|
||||||
)
|
)
|
||||||
|
|
||||||
mid_video_sigma_for_x0 = (
|
midpoint_denoised_video = midpoint_video_latents.float() - sub_sigma * mid_v
|
||||||
mid_video_timestep
|
midpoint_denoised_audio = midpoint_audio_latents.float() - sub_sigma * mid_a
|
||||||
if ctx.use_ltx23_hq_timestep_semantics and mid_video_timestep is not None
|
|
||||||
else sub_sigma
|
|
||||||
)
|
|
||||||
mid_audio_sigma_for_x0 = (
|
|
||||||
mid_audio_timestep
|
|
||||||
if ctx.use_ltx23_hq_timestep_semantics and mid_audio_timestep is not None
|
|
||||||
else sub_sigma
|
|
||||||
)
|
|
||||||
midpoint_denoised_video = self._ltx2_velocity_to_x0(
|
|
||||||
midpoint_video_latents, mid_v, mid_video_sigma_for_x0
|
|
||||||
).float()
|
|
||||||
midpoint_denoised_audio = self._ltx2_velocity_to_x0(
|
|
||||||
midpoint_audio_latents, mid_a, mid_audio_sigma_for_x0
|
|
||||||
).float()
|
|
||||||
|
|
||||||
eps2_video = midpoint_denoised_video.double() - anchor_video
|
eps2_video = midpoint_denoised_video.double() - anchor_video
|
||||||
eps2_audio = midpoint_denoised_audio.double() - anchor_audio
|
eps2_audio = midpoint_denoised_audio.double() - anchor_audio
|
||||||
@@ -848,23 +850,19 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
ctx.audio_latents, batch
|
ctx.audio_latents, batch
|
||||||
).float()
|
).float()
|
||||||
)
|
)
|
||||||
sde_sigma = sigma if ctx.use_ltx23_hq_timestep_semantics else sigma_d
|
|
||||||
sde_sigma_next = (
|
|
||||||
sigma_next if ctx.use_ltx23_hq_timestep_semantics else sigma_next_d
|
|
||||||
)
|
|
||||||
next_video = self._ltx2_res2s_sde_step(
|
next_video = self._ltx2_res2s_sde_step(
|
||||||
sample=anchor_video,
|
sample=anchor_video,
|
||||||
denoised_sample=next_video_det,
|
denoised_sample=next_video_det,
|
||||||
sigma=sde_sigma,
|
sigma=sigma_d,
|
||||||
sigma_next=sde_sigma_next,
|
sigma_next=sigma_next_d,
|
||||||
noise=step_noise_video,
|
noise=step_noise_video,
|
||||||
terminal=False,
|
terminal=False,
|
||||||
)
|
)
|
||||||
next_audio = self._ltx2_res2s_sde_step(
|
next_audio = self._ltx2_res2s_sde_step(
|
||||||
sample=anchor_audio,
|
sample=anchor_audio,
|
||||||
denoised_sample=next_audio_det,
|
denoised_sample=next_audio_det,
|
||||||
sigma=sde_sigma,
|
sigma=sigma_d,
|
||||||
sigma_next=sde_sigma_next,
|
sigma_next=sigma_next_d,
|
||||||
noise=step_noise_audio,
|
noise=step_noise_audio,
|
||||||
terminal=False,
|
terminal=False,
|
||||||
)
|
)
|
||||||
@@ -1574,9 +1572,19 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
ctx.denoise_mask = ctx.denoise_mask.to(device)
|
ctx.denoise_mask = ctx.denoise_mask.to(device)
|
||||||
if ctx.clean_latent is not None:
|
if ctx.clean_latent is not None:
|
||||||
ctx.clean_latent = ctx.clean_latent.to(device)
|
ctx.clean_latent = ctx.clean_latent.to(device)
|
||||||
|
self._move_ltx2_scheduler_tensors_to_device(ctx.scheduler, device)
|
||||||
|
self._move_ltx2_scheduler_tensors_to_device(ctx.audio_scheduler, device)
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _move_ltx2_scheduler_tensors_to_device(scheduler: object, device) -> None:
|
||||||
|
# cfg-parallel batches are broadcast from rank 0; scheduler state must be local
|
||||||
|
for name in ("sigmas", "timesteps"):
|
||||||
|
value = getattr(scheduler, name, None)
|
||||||
|
if isinstance(value, torch.Tensor):
|
||||||
|
setattr(scheduler, name, value.to(device))
|
||||||
|
|
||||||
def _before_denoising_loop(
|
def _before_denoising_loop(
|
||||||
self, ctx: LTX2DenoisingContext, batch: Req, server_args: ServerArgs
|
self, ctx: LTX2DenoisingContext, batch: Req, server_args: ServerArgs
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -1652,9 +1660,13 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
)
|
)
|
||||||
use_official_cfg_path = stage1_guider_params is None
|
use_official_cfg_path = stage1_guider_params is None
|
||||||
if use_official_cfg_path:
|
if use_official_cfg_path:
|
||||||
cfg_parallel = (
|
do_two_branch_cfg = batch.do_classifier_free_guidance
|
||||||
server_args.enable_cfg_parallel and batch.do_classifier_free_guidance
|
if ctx.stage == "stage2" and is_ltx2_two_stage_pipeline_name(
|
||||||
)
|
server_args.pipeline_class_name
|
||||||
|
):
|
||||||
|
# official two-stage stage 2 is a distilled positive-only denoiser
|
||||||
|
do_two_branch_cfg = False
|
||||||
|
cfg_parallel = server_args.enable_cfg_parallel and do_two_branch_cfg
|
||||||
cfg_rank = get_classifier_free_guidance_rank() if cfg_parallel else 0
|
cfg_rank = get_classifier_free_guidance_rank() if cfg_parallel else 0
|
||||||
|
|
||||||
if cfg_parallel:
|
if cfg_parallel:
|
||||||
@@ -1691,7 +1703,7 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
audio_encoder_hidden_states=batch.audio_prompt_embeds[0],
|
audio_encoder_hidden_states=batch.audio_prompt_embeds[0],
|
||||||
encoder_attention_mask=prompt_attention_mask,
|
encoder_attention_mask=prompt_attention_mask,
|
||||||
)
|
)
|
||||||
if batch.do_classifier_free_guidance:
|
if do_two_branch_cfg:
|
||||||
cfg_batch_size = batch_size * 2
|
cfg_batch_size = batch_size * 2
|
||||||
model_kwargs = self._repeat_ltx2_model_kwargs_batch(
|
model_kwargs = self._repeat_ltx2_model_kwargs_batch(
|
||||||
model_kwargs, cfg_batch_size
|
model_kwargs, cfg_batch_size
|
||||||
@@ -1735,7 +1747,7 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
model_video, model_audio = self._combine_cfg_parallel_av(
|
model_video, model_audio = self._combine_cfg_parallel_av(
|
||||||
model_video, model_audio, float(batch.guidance_scale), cfg_rank
|
model_video, model_audio, float(batch.guidance_scale), cfg_rank
|
||||||
)
|
)
|
||||||
elif batch.do_classifier_free_guidance:
|
elif do_two_branch_cfg:
|
||||||
model_video_uncond, model_video_text = model_video.chunk(2)
|
model_video_uncond, model_video_text = model_video.chunk(2)
|
||||||
model_audio_uncond, model_audio_text = model_audio.chunk(2)
|
model_audio_uncond, model_audio_text = model_audio.chunk(2)
|
||||||
model_video = model_video_uncond + (
|
model_video = model_video_uncond + (
|
||||||
@@ -1780,7 +1792,7 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
audio_encoder_hidden_states=batch.audio_prompt_embeds[0],
|
audio_encoder_hidden_states=batch.audio_prompt_embeds[0],
|
||||||
encoder_attention_mask=prompt_attention_mask,
|
encoder_attention_mask=prompt_attention_mask,
|
||||||
)
|
)
|
||||||
if batch.do_classifier_free_guidance:
|
if do_two_branch_cfg:
|
||||||
cfg_batch_size = batch_size_local * 2
|
cfg_batch_size = batch_size_local * 2
|
||||||
model_kwargs_local = self._repeat_ltx2_model_kwargs_batch(
|
model_kwargs_local = self._repeat_ltx2_model_kwargs_batch(
|
||||||
model_kwargs_local, cfg_batch_size
|
model_kwargs_local, cfg_batch_size
|
||||||
@@ -1827,7 +1839,7 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
|
|
||||||
mid_v = mid_v.float()
|
mid_v = mid_v.float()
|
||||||
mid_a = mid_a.float()
|
mid_a = mid_a.float()
|
||||||
if batch.do_classifier_free_guidance:
|
if do_two_branch_cfg:
|
||||||
mid_v_u, mid_v_t = mid_v.chunk(2)
|
mid_v_u, mid_v_t = mid_v.chunk(2)
|
||||||
mid_a_u, mid_a_t = mid_a.chunk(2)
|
mid_a_u, mid_a_t = mid_a.chunk(2)
|
||||||
mid_v = mid_v_u + batch.guidance_scale * (mid_v_t - mid_v_u)
|
mid_v = mid_v_u + batch.guidance_scale * (mid_v_t - mid_v_u)
|
||||||
@@ -2202,20 +2214,43 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
) * ctx.denoise_mask.squeeze(-1)
|
) * ctx.denoise_mask.squeeze(-1)
|
||||||
|
|
||||||
if stage1_cfg_parallel:
|
if stage1_cfg_parallel:
|
||||||
guided_video = self._ltx2_combine_guided_x0_parallel(
|
guided_video, guided_audio = (
|
||||||
latents=video_latents,
|
self._ltx2_combine_guided_x0_parallel_av(
|
||||||
local_velocities={
|
video_latents=video_latents,
|
||||||
|
audio_latents=audio_latents,
|
||||||
|
local_video_velocities={
|
||||||
name: output[0] for name, output in pass_outputs.items()
|
name: output[0] for name, output in pass_outputs.items()
|
||||||
},
|
},
|
||||||
sigma=video_sigma_for_x0,
|
local_audio_velocities={
|
||||||
cfg_scale=float(stage1_guider_params["video_cfg_scale"]),
|
name: output[1] for name, output in pass_outputs.items()
|
||||||
stg_scale=float(stage1_guider_params["video_stg_scale"]),
|
},
|
||||||
rescale_scale=float(
|
video_sigma=video_sigma_for_x0,
|
||||||
|
audio_sigma=audio_sigma_for_x0,
|
||||||
|
video_cfg_scale=float(
|
||||||
|
stage1_guider_params["video_cfg_scale"]
|
||||||
|
),
|
||||||
|
video_stg_scale=float(
|
||||||
|
stage1_guider_params["video_stg_scale"]
|
||||||
|
),
|
||||||
|
video_rescale_scale=float(
|
||||||
stage1_guider_params["video_rescale_scale"]
|
stage1_guider_params["video_rescale_scale"]
|
||||||
),
|
),
|
||||||
modality_scale=float(
|
video_modality_scale=float(
|
||||||
stage1_guider_params["video_modality_scale"]
|
stage1_guider_params["video_modality_scale"]
|
||||||
),
|
),
|
||||||
|
audio_cfg_scale=float(
|
||||||
|
stage1_guider_params["audio_cfg_scale"]
|
||||||
|
),
|
||||||
|
audio_stg_scale=float(
|
||||||
|
stage1_guider_params["audio_stg_scale"]
|
||||||
|
),
|
||||||
|
audio_rescale_scale=float(
|
||||||
|
stage1_guider_params["audio_rescale_scale"]
|
||||||
|
),
|
||||||
|
audio_modality_scale=float(
|
||||||
|
stage1_guider_params["audio_modality_scale"]
|
||||||
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if video_skip and ctx.last_denoised_video is not None:
|
if video_skip and ctx.last_denoised_video is not None:
|
||||||
denoised_video_local = ctx.last_denoised_video
|
denoised_video_local = ctx.last_denoised_video
|
||||||
@@ -2224,21 +2259,6 @@ class LTX2DenoisingStage(DenoisingStage):
|
|||||||
if update_skip_cache:
|
if update_skip_cache:
|
||||||
ctx.last_denoised_video = guided_video
|
ctx.last_denoised_video = guided_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:
|
if audio_skip and ctx.last_denoised_audio is not None:
|
||||||
denoised_audio_local = ctx.last_denoised_audio
|
denoised_audio_local = ctx.last_denoised_audio
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -614,9 +614,15 @@ class ServerArgs(DisaggArgsMixin):
|
|||||||
if component_name is None:
|
if component_name is None:
|
||||||
continue
|
continue
|
||||||
key = component_name.replace("-", "_")
|
key = component_name.replace("-", "_")
|
||||||
backend = self.component_attention_backends.get(key)
|
fallback_keys = [key]
|
||||||
|
if key.endswith("_2"):
|
||||||
|
# Secondary two-stage components inherit the base component
|
||||||
|
# backend unless explicitly overridden.
|
||||||
|
fallback_keys.append(key[:-2])
|
||||||
|
for backend_key in fallback_keys:
|
||||||
|
backend = self.component_attention_backends.get(backend_key)
|
||||||
if backend is not None:
|
if backend is not None:
|
||||||
return AttentionBackendEnum[backend.upper()], key
|
return AttentionBackendEnum[backend.upper()], backend_key
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
def _adjust_warmup(self):
|
def _adjust_warmup(self):
|
||||||
|
|||||||
@@ -110,15 +110,15 @@
|
|||||||
"mean_abs_diff_threshold": 10.0
|
"mean_abs_diff_threshold": 10.0
|
||||||
},
|
},
|
||||||
"ltx_2.3_one_stage_ti2v": {
|
"ltx_2.3_one_stage_ti2v": {
|
||||||
"clip_threshold": 0.64,
|
"clip_threshold": 0.84,
|
||||||
"ssim_threshold": 0.42,
|
"ssim_threshold": 0.78,
|
||||||
"psnr_threshold": 8.8,
|
"psnr_threshold": 20.5,
|
||||||
"mean_abs_diff_threshold": 59.0
|
"mean_abs_diff_threshold": 11.0
|
||||||
},
|
},
|
||||||
"ltx_2.3_two_stage_t2v_2gpus": {
|
"ltx_2.3_two_stage_t2v_2gpus": {
|
||||||
"clip_threshold": 0.79,
|
"clip_threshold": 0.79,
|
||||||
"ssim_threshold": 0.12,
|
"ssim_threshold": 0.12,
|
||||||
"psnr_threshold": 12.1,
|
"psnr_threshold": 16.0,
|
||||||
"mean_abs_diff_threshold": 51.0
|
"mean_abs_diff_threshold": 51.0
|
||||||
},
|
},
|
||||||
"wan2_1_t2v_1.3b_teacache_enabled": {
|
"wan2_1_t2v_1.3b_teacache_enabled": {
|
||||||
@@ -254,10 +254,10 @@
|
|||||||
"mean_abs_diff_threshold": 45.0
|
"mean_abs_diff_threshold": 45.0
|
||||||
},
|
},
|
||||||
"ltx_2_3_two_stage_ti2v_2gpus": {
|
"ltx_2_3_two_stage_ti2v_2gpus": {
|
||||||
"clip_threshold": 0.92,
|
"clip_threshold": 0.55,
|
||||||
"ssim_threshold": 0.58,
|
"ssim_threshold": 0.29,
|
||||||
"psnr_threshold": 17.5,
|
"psnr_threshold": 11.7,
|
||||||
"mean_abs_diff_threshold": 20.0
|
"mean_abs_diff_threshold": 47.0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"default_clip_threshold_image": 0.92,
|
"default_clip_threshold_image": 0.92,
|
||||||
|
|||||||
@@ -601,8 +601,7 @@ TWO_GPU_CASES = [
|
|||||||
"ltx_2_two_stage_t2v",
|
"ltx_2_two_stage_t2v",
|
||||||
DiffusionServerArgs(
|
DiffusionServerArgs(
|
||||||
model_path="Lightricks/LTX-2",
|
model_path="Lightricks/LTX-2",
|
||||||
ulysses_degree=2,
|
cfg_parallel=True,
|
||||||
dit_layerwise_offload=True,
|
|
||||||
extras=["--pipeline-class-name LTX2TwoStagePipeline"],
|
extras=["--pipeline-class-name LTX2TwoStagePipeline"],
|
||||||
),
|
),
|
||||||
T2V_sampling_params,
|
T2V_sampling_params,
|
||||||
@@ -613,7 +612,7 @@ TWO_GPU_CASES = [
|
|||||||
model_path="Lightricks/LTX-2.3",
|
model_path="Lightricks/LTX-2.3",
|
||||||
cfg_parallel=True,
|
cfg_parallel=True,
|
||||||
extras=[
|
extras=[
|
||||||
"--pipeline-class-name LTX2TwoStagePipeline --ltx2-two-stage-device-mode original"
|
"--pipeline-class-name LTX2TwoStagePipeline --ltx2-two-stage-device-mode original",
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
TI2V_sampling_params,
|
TI2V_sampling_params,
|
||||||
@@ -634,10 +633,10 @@ TWO_GPU_CASES = [
|
|||||||
cfg_parallel=True,
|
cfg_parallel=True,
|
||||||
extras=[
|
extras=[
|
||||||
"--pipeline-class-name LTX2TwoStagePipeline",
|
"--pipeline-class-name LTX2TwoStagePipeline",
|
||||||
"--ltx2-two-stage-device-mode original",
|
"--component-attention-backends transformer=fa",
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
T2V_sampling_params,
|
DiffusionSamplingParams(prompt=T2V_PROMPT, extras={"seed": 42}),
|
||||||
run_component_accuracy_check=False,
|
run_component_accuracy_check=False,
|
||||||
),
|
),
|
||||||
# I2V LoRA test case
|
# I2V LoRA test case
|
||||||
@@ -708,7 +707,7 @@ TWO_GPU_CASES = [
|
|||||||
"ltx_2.3_one_stage_ti2v",
|
"ltx_2.3_one_stage_ti2v",
|
||||||
DiffusionServerArgs(
|
DiffusionServerArgs(
|
||||||
model_path="Lightricks/LTX-2.3",
|
model_path="Lightricks/LTX-2.3",
|
||||||
ulysses_degree=2,
|
cfg_parallel=True,
|
||||||
),
|
),
|
||||||
TI2V_sampling_params,
|
TI2V_sampling_params,
|
||||||
run_component_accuracy_check=False,
|
run_component_accuracy_check=False,
|
||||||
|
|||||||
@@ -1096,68 +1096,70 @@
|
|||||||
},
|
},
|
||||||
"ltx_2_two_stage_t2v": {
|
"ltx_2_two_stage_t2v": {
|
||||||
"stages_ms": {
|
"stages_ms": {
|
||||||
"InputValidationStage": 0.1,
|
"InputValidationStage": 0.03,
|
||||||
"TextEncodingStage": 1830.33,
|
"TextEncodingStage": 1463.27,
|
||||||
"LTX2TextConnectorStage": 9.61,
|
"LTX2TextConnectorStage": 12.85,
|
||||||
"LTX2HalveResolutionStage": 0.06,
|
"LTX2HalveResolutionStage": 0.11,
|
||||||
"LTX2LoRASwitchStage": 11578.48,
|
"LTX2LoRASwitchStage": 325.46,
|
||||||
"LTX2SigmaPreparationStage": 0.18,
|
"LTX2SigmaPreparationStage": 0.25,
|
||||||
"TimestepPreparationStage": 19.37,
|
"TimestepPreparationStage": 7.85,
|
||||||
"LTX2AVLatentPreparationStage": 0.23,
|
"LTX2AVLatentPreparationStage": 0.34,
|
||||||
"LTX2AVDenoisingStage": 53177.49,
|
"LTX2ImageEncodingStage": 0.02,
|
||||||
"LTX2UpsampleStage": 2104.17,
|
"LTX2AVDenoisingStage": 7744.44,
|
||||||
"LTX2RefinementStage": 3947.13,
|
"LTX2UpsampleStage": 2.98,
|
||||||
"LTX2AVDecodingStage": 332.07
|
"LTX2RefinementStage": 666.08,
|
||||||
|
"LTX2AVDecodingStage": 338.87,
|
||||||
|
"per_frame_generation": null
|
||||||
},
|
},
|
||||||
"denoise_step_ms": {
|
"denoise_step_ms": {
|
||||||
"0": 1186.27,
|
"0": 165.1,
|
||||||
"1": 1331.86,
|
"1": 309.19,
|
||||||
"2": 1330.41,
|
"2": 166.63,
|
||||||
"3": 1331.28,
|
"3": 175.53,
|
||||||
"4": 1331.5,
|
"4": 158.77,
|
||||||
"5": 1331.45,
|
"5": 191.1,
|
||||||
"6": 1331.79,
|
"6": 203.59,
|
||||||
"7": 1331.59,
|
"7": 202.98,
|
||||||
"8": 1331.55,
|
"8": 205.09,
|
||||||
"9": 1331.55,
|
"9": 195.92,
|
||||||
"10": 1331.51,
|
"10": 226.96,
|
||||||
"11": 1331.34,
|
"11": 203.85,
|
||||||
"12": 1331.48,
|
"12": 190.75,
|
||||||
"13": 1331.38,
|
"13": 192.07,
|
||||||
"14": 1331.74,
|
"14": 193.85,
|
||||||
"15": 1331.84,
|
"15": 191.34,
|
||||||
"16": 1331.09,
|
"16": 193.56,
|
||||||
"17": 1331.79,
|
"17": 192.05,
|
||||||
"18": 1332.34,
|
"18": 189.53,
|
||||||
"19": 1337.7,
|
"19": 191.61,
|
||||||
"20": 1337.53,
|
"20": 187.52,
|
||||||
"21": 1334.48,
|
"21": 192.57,
|
||||||
"22": 1334.87,
|
"22": 190.67,
|
||||||
"23": 1333.28,
|
"23": 189.47,
|
||||||
"24": 1333.15,
|
"24": 187.38,
|
||||||
"25": 1333.82,
|
"25": 190.22,
|
||||||
"26": 1333.55,
|
"26": 196.7,
|
||||||
"27": 1339.35,
|
"27": 185.05,
|
||||||
"28": 1336.96,
|
"28": 189.59,
|
||||||
"29": 1335.25,
|
"29": 209.85,
|
||||||
"30": 1331.8,
|
"30": 194.47,
|
||||||
"31": 1339.52,
|
"31": 189.43,
|
||||||
"32": 1334.1,
|
"32": 189.58,
|
||||||
"33": 1331.96,
|
"33": 188.41,
|
||||||
"34": 1331.78,
|
"34": 198.11,
|
||||||
"35": 1332.5,
|
"35": 188.45,
|
||||||
"36": 1331.3,
|
"36": 187.06,
|
||||||
"37": 1331.75,
|
"37": 188.65,
|
||||||
"38": 1331.94,
|
"38": 200.22,
|
||||||
"39": 1331.84,
|
"39": 156.46,
|
||||||
"40": 1278.82,
|
"40": 220.06,
|
||||||
"41": 1330.68,
|
"41": 223.51,
|
||||||
"42": 1331.7
|
"42": 221.07
|
||||||
},
|
},
|
||||||
"expected_e2e_ms": 73463.94,
|
"expected_e2e_ms": 10601.1,
|
||||||
"expected_avg_denoise_ms": 1328.22,
|
"expected_avg_denoise_ms": 195.04,
|
||||||
"expected_median_denoise_ms": 1331.79,
|
"expected_median_denoise_ms": 191.34,
|
||||||
"estimated_full_test_time_s": 133.1
|
"estimated_full_test_time_s": 345.4
|
||||||
},
|
},
|
||||||
"wan2_2_ti2v_5b": {
|
"wan2_2_ti2v_5b": {
|
||||||
"stages_ms": {
|
"stages_ms": {
|
||||||
|
|||||||
@@ -169,3 +169,6 @@ def test_save_consistency_failure_artifact(tmp_path, monkeypatch):
|
|||||||
assert artifact_path.suffix == ".png"
|
assert artifact_path.suffix == ".png"
|
||||||
assert (tmp_path / "consistency_failures" / "summary.json").exists()
|
assert (tmp_path / "consistency_failures" / "summary.json").exists()
|
||||||
assert (tmp_path / "consistency_failures" / "index.html").exists()
|
assert (tmp_path / "consistency_failures" / "index.html").exists()
|
||||||
|
assert (
|
||||||
|
tmp_path / "consistency_failures" / "generated" / "unit_image_fail_1gpu.png"
|
||||||
|
).exists()
|
||||||
|
|||||||
@@ -50,12 +50,12 @@ SGL_TEST_FILES_CONSISTENCY_GT_BASES = (
|
|||||||
SGL_TEST_FILES_OFFICIAL_CONSISTENCY_GT_BASE,
|
SGL_TEST_FILES_OFFICIAL_CONSISTENCY_GT_BASE,
|
||||||
SGL_TEST_FILES_SGLANG_CONSISTENCY_GT_BASE,
|
SGL_TEST_FILES_SGLANG_CONSISTENCY_GT_BASE,
|
||||||
)
|
)
|
||||||
# Keep non-comparable LTX CI scenarios on sglang_generated rather than hiding
|
# LTX cases listed here compare against official-generated GT.
|
||||||
# remaining semantic gaps behind very loose official thresholds.
|
|
||||||
SGL_TEST_FILES_OFFICIAL_CONSISTENCY_GT_CASES = frozenset(
|
SGL_TEST_FILES_OFFICIAL_CONSISTENCY_GT_CASES = frozenset(
|
||||||
{
|
{
|
||||||
"ltx_2.3_one_stage_ti2v",
|
"ltx_2.3_one_stage_ti2v",
|
||||||
"ltx_2.3_two_stage_t2v_2gpus",
|
"ltx_2.3_two_stage_t2v_2gpus",
|
||||||
|
"ltx_2_3_two_stage_ti2v_2gpus",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
CONSISTENCY_THRESHOLD_JSON_PATH = (
|
CONSISTENCY_THRESHOLD_JSON_PATH = (
|
||||||
@@ -1458,6 +1458,7 @@ def _consistency_failure_record(
|
|||||||
is_video: bool,
|
is_video: bool,
|
||||||
output_format: str | None,
|
output_format: str | None,
|
||||||
image_name: str,
|
image_name: str,
|
||||||
|
generated_files: list[str],
|
||||||
gt_remote_files: list[tuple[str, str]] | None,
|
gt_remote_files: list[tuple[str, str]] | None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
@@ -1466,6 +1467,7 @@ def _consistency_failure_record(
|
|||||||
"is_video": is_video,
|
"is_video": is_video,
|
||||||
"output_format": output_format,
|
"output_format": output_format,
|
||||||
"comparison_png": image_name,
|
"comparison_png": image_name,
|
||||||
|
"generated_files": generated_files,
|
||||||
"metrics": {
|
"metrics": {
|
||||||
"min_clip_similarity": _json_metric_value(result.min_similarity),
|
"min_clip_similarity": _json_metric_value(result.min_similarity),
|
||||||
"min_ssim": _json_metric_value(result.min_ssim),
|
"min_ssim": _json_metric_value(result.min_ssim),
|
||||||
@@ -1499,6 +1501,36 @@ def _consistency_failure_record(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _save_generated_artifact_images(
|
||||||
|
out_dir: Path,
|
||||||
|
case_id: str,
|
||||||
|
num_gpus: int,
|
||||||
|
output_frames: list[np.ndarray],
|
||||||
|
is_video: bool,
|
||||||
|
output_format: str | None,
|
||||||
|
) -> list[str]:
|
||||||
|
generated_dir = out_dir / "generated"
|
||||||
|
generated_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
safe_case_id = _safe_artifact_name(case_id)
|
||||||
|
if is_video:
|
||||||
|
suffixes = ("frame_0", "frame_mid", "frame_last")
|
||||||
|
filenames = [
|
||||||
|
f"{safe_case_id}_{num_gpus}gpu_{suffix}.png"
|
||||||
|
for suffix in suffixes[: len(output_frames)]
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
ext = output_format_to_ext(output_format)
|
||||||
|
filenames = [f"{safe_case_id}_{num_gpus}gpu.{ext}"]
|
||||||
|
|
||||||
|
generated_files = []
|
||||||
|
for frame, filename in zip(output_frames, filenames):
|
||||||
|
path = generated_dir / filename
|
||||||
|
Image.fromarray(_ensure_rgb_uint8_image(frame)).save(path)
|
||||||
|
generated_files.append(str(path.relative_to(out_dir)))
|
||||||
|
return generated_files
|
||||||
|
|
||||||
|
|
||||||
def _write_consistency_failure_index(
|
def _write_consistency_failure_index(
|
||||||
out_dir: Path,
|
out_dir: Path,
|
||||||
records: list[dict[str, Any]],
|
records: list[dict[str, Any]],
|
||||||
@@ -1508,6 +1540,15 @@ def _write_consistency_failure_index(
|
|||||||
case_id = html.escape(record["case_id"])
|
case_id = html.escape(record["case_id"])
|
||||||
png = html.escape(record["comparison_png"])
|
png = html.escape(record["comparison_png"])
|
||||||
metrics = record["metrics"]
|
metrics = record["metrics"]
|
||||||
|
generated_links = "".join(
|
||||||
|
f'<li><a href="{html.escape(path)}">{html.escape(path)}</a></li>'
|
||||||
|
for path in record.get("generated_files", [])
|
||||||
|
)
|
||||||
|
generated_html = (
|
||||||
|
f"<p>Generated images:</p><ul>{generated_links}</ul>"
|
||||||
|
if generated_links
|
||||||
|
else ""
|
||||||
|
)
|
||||||
sections.append(
|
sections.append(
|
||||||
"<section>"
|
"<section>"
|
||||||
f"<h2>{case_id} ({record['num_gpus']} GPU)</h2>"
|
f"<h2>{case_id} ({record['num_gpus']} GPU)</h2>"
|
||||||
@@ -1518,6 +1559,7 @@ def _write_consistency_failure_index(
|
|||||||
f"mean_abs_diff={metrics['max_mean_abs_diff']}"
|
f"mean_abs_diff={metrics['max_mean_abs_diff']}"
|
||||||
"</p>"
|
"</p>"
|
||||||
f'<img src="{png}" alt="{case_id} comparison">'
|
f'<img src="{png}" alt="{case_id} comparison">'
|
||||||
|
f"{generated_html}"
|
||||||
"</section>"
|
"</section>"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1529,6 +1571,7 @@ def _write_consistency_failure_index(
|
|||||||
"section{margin:0 0 28px;padding:16px;background:white;border:1px solid #ddd;border-radius:6px}"
|
"section{margin:0 0 28px;padding:16px;background:white;border:1px solid #ddd;border-radius:6px}"
|
||||||
"h2{font-size:18px;margin:0 0 8px}"
|
"h2{font-size:18px;margin:0 0 8px}"
|
||||||
"p{margin:0 0 12px;color:#444}"
|
"p{margin:0 0 12px;color:#444}"
|
||||||
|
"ul{margin:0 0 12px;padding-left:20px}"
|
||||||
"img{max-width:100%;height:auto;border:1px solid #ddd}"
|
"img{max-width:100%;height:auto;border:1px solid #ddd}"
|
||||||
"</style></head><body>"
|
"</style></head><body>"
|
||||||
"<h1>Diffusion consistency failures</h1>" + "".join(sections) + "</body></html>"
|
"<h1>Diffusion consistency failures</h1>" + "".join(sections) + "</body></html>"
|
||||||
@@ -1566,6 +1609,15 @@ def save_consistency_failure_artifact(
|
|||||||
)
|
)
|
||||||
comparison.save(image_path)
|
comparison.save(image_path)
|
||||||
|
|
||||||
|
generated_files = _save_generated_artifact_images(
|
||||||
|
out_dir=out_dir,
|
||||||
|
case_id=case_id,
|
||||||
|
num_gpus=num_gpus,
|
||||||
|
output_frames=output_frames,
|
||||||
|
is_video=is_video,
|
||||||
|
output_format=output_format,
|
||||||
|
)
|
||||||
|
|
||||||
record = _consistency_failure_record(
|
record = _consistency_failure_record(
|
||||||
case_id=case_id,
|
case_id=case_id,
|
||||||
num_gpus=num_gpus,
|
num_gpus=num_gpus,
|
||||||
@@ -1573,6 +1625,7 @@ def save_consistency_failure_artifact(
|
|||||||
is_video=is_video,
|
is_video=is_video,
|
||||||
output_format=output_format,
|
output_format=output_format,
|
||||||
image_name=image_name,
|
image_name=image_name,
|
||||||
|
generated_files=generated_files,
|
||||||
gt_remote_files=gt_remote_files,
|
gt_remote_files=gt_remote_files,
|
||||||
)
|
)
|
||||||
case_json_path = out_dir / f"{safe_case_id}.json"
|
case_json_path = out_dir / f"{safe_case_id}.json"
|
||||||
|
|||||||
Reference in New Issue
Block a user