Automated PR - 2026-01-12
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
[](https://ltx.io)
|
[](https://ltx.io)
|
||||||
[](https://huggingface.co/Lightricks/LTX-2)
|
[](https://huggingface.co/Lightricks/LTX-2)
|
||||||
[](https://app.ltx.studio/ltx-2-playground/i2v)
|
[](https://app.ltx.studio/ltx-2-playground/i2v)
|
||||||
[](https://videos.ltx.io/LTX-2/grants/LTX_2_Technical_Report_compressed.pdf)
|
[](https://arxiv.org/abs/2601.03233)
|
||||||
[](https://discord.gg/ltxplatform)
|
[](https://discord.gg/ltxplatform)
|
||||||
|
|
||||||
**LTX-2** is the first DiT-based audio-video foundation model that contains all core capabilities of modern video generation in one model: synchronized audio and video, high fidelity, multiple performance modes, production-ready outputs, API access, and open access.
|
**LTX-2** is the first DiT-based audio-video foundation model that contains all core capabilities of modern video generation in one model: synchronized audio and video, high fidelity, multiple performance modes, production-ready outputs, API access, and open access.
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class ResnetBlock3D(nn.Module):
|
|||||||
self.timestep_conditioning = timestep_conditioning
|
self.timestep_conditioning = timestep_conditioning
|
||||||
|
|
||||||
if timestep_conditioning:
|
if timestep_conditioning:
|
||||||
self.scale_shift_table = nn.Parameter(torch.randn(4, in_channels) / in_channels**0.5)
|
self.scale_shift_table = nn.Parameter(torch.zeros(4, in_channels))
|
||||||
|
|
||||||
def _feed_spatial_noise(
|
def _feed_spatial_noise(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from dataclasses import replace
|
from dataclasses import replace
|
||||||
from typing import Any, Callable, Iterator, List, Optional, Tuple
|
from typing import Any, Callable, Iterator, List, Tuple
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
from einops import rearrange
|
from einops import rearrange
|
||||||
@@ -521,12 +521,11 @@ class VideoDecoder(nn.Module):
|
|||||||
)
|
)
|
||||||
self.last_scale_shift_table = nn.Parameter(torch.empty(2, feature_channels))
|
self.last_scale_shift_table = nn.Parameter(torch.empty(2, feature_channels))
|
||||||
|
|
||||||
# def forward(self, sample: torch.Tensor, target_shape) -> torch.Tensor:
|
|
||||||
def forward(
|
def forward(
|
||||||
self,
|
self,
|
||||||
sample: torch.Tensor,
|
sample: torch.Tensor,
|
||||||
timestep: Optional[torch.Tensor] = None,
|
timestep: torch.Tensor | None = None,
|
||||||
generator: Optional[torch.Generator] = None,
|
generator: torch.Generator | None = None,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
r"""
|
r"""
|
||||||
Decode latent representation into video frames.
|
Decode latent representation into video frames.
|
||||||
@@ -651,8 +650,8 @@ class VideoDecoder(nn.Module):
|
|||||||
self,
|
self,
|
||||||
latent: torch.Tensor,
|
latent: torch.Tensor,
|
||||||
tiling_config: TilingConfig | None = None,
|
tiling_config: TilingConfig | None = None,
|
||||||
timestep: Optional[torch.Tensor] = None,
|
timestep: torch.Tensor | None = None,
|
||||||
generator: Optional[torch.Generator] = None,
|
generator: torch.Generator | None = None,
|
||||||
) -> Iterator[torch.Tensor]:
|
) -> Iterator[torch.Tensor]:
|
||||||
"""
|
"""
|
||||||
Decode a latent tensor into video frames using tiled processing.
|
Decode a latent tensor into video frames using tiled processing.
|
||||||
@@ -769,8 +768,8 @@ class VideoDecoder(nn.Module):
|
|||||||
group_tiles: List[Tile],
|
group_tiles: List[Tile],
|
||||||
buffer: torch.Tensor,
|
buffer: torch.Tensor,
|
||||||
latent: torch.Tensor,
|
latent: torch.Tensor,
|
||||||
timestep: Optional[torch.Tensor],
|
timestep: torch.Tensor | None,
|
||||||
generator: Optional[torch.Generator],
|
generator: torch.Generator | None,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
"""
|
"""
|
||||||
Decode and accumulate all tiles of a temporal group into a local buffer.
|
Decode and accumulate all tiles of a temporal group into a local buffer.
|
||||||
@@ -815,6 +814,7 @@ def decode_video(
|
|||||||
latent: torch.Tensor,
|
latent: torch.Tensor,
|
||||||
video_decoder: VideoDecoder,
|
video_decoder: VideoDecoder,
|
||||||
tiling_config: TilingConfig | None = None,
|
tiling_config: TilingConfig | None = None,
|
||||||
|
generator: torch.Generator | None = None,
|
||||||
) -> Iterator[torch.Tensor]:
|
) -> Iterator[torch.Tensor]:
|
||||||
"""
|
"""
|
||||||
Decode a video latent tensor with the given decoder.
|
Decode a video latent tensor with the given decoder.
|
||||||
@@ -822,6 +822,7 @@ def decode_video(
|
|||||||
latent: Tensor [c, f, h, w]
|
latent: Tensor [c, f, h, w]
|
||||||
video_decoder: Decoder module.
|
video_decoder: Decoder module.
|
||||||
tiling_config: Optional tiling settings.
|
tiling_config: Optional tiling settings.
|
||||||
|
generator: Optional random generator for deterministic decoding.
|
||||||
Yields:
|
Yields:
|
||||||
Decoded chunk [f, h, w, c], uint8 in [0, 255].
|
Decoded chunk [f, h, w, c], uint8 in [0, 255].
|
||||||
"""
|
"""
|
||||||
@@ -832,10 +833,10 @@ def decode_video(
|
|||||||
return frames
|
return frames
|
||||||
|
|
||||||
if tiling_config is not None:
|
if tiling_config is not None:
|
||||||
for frames in video_decoder.tiled_decode(latent, tiling_config):
|
for frames in video_decoder.tiled_decode(latent, tiling_config, generator=generator):
|
||||||
yield convert_to_uint8(frames)
|
yield convert_to_uint8(frames)
|
||||||
else:
|
else:
|
||||||
decoded_video = video_decoder(latent)
|
decoded_video = video_decoder(latent, generator=generator)
|
||||||
yield convert_to_uint8(decoded_video)
|
yield convert_to_uint8(decoded_video)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ class GemmaTextEncoderModelBase(torch.nn.Module):
|
|||||||
dtype: torch.dtype = torch.bfloat16,
|
dtype: torch.dtype = torch.bfloat16,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._gemma_root = None
|
|
||||||
self.tokenizer = tokenizer
|
self.tokenizer = tokenizer
|
||||||
self.model = model
|
self.model = model
|
||||||
self.processor = img_processor
|
self.processor = img_processor
|
||||||
@@ -73,12 +72,6 @@ class GemmaTextEncoderModelBase(torch.nn.Module):
|
|||||||
)
|
)
|
||||||
return projected, attention_mask
|
return projected, attention_mask
|
||||||
|
|
||||||
def _init_image_processor(self) -> None:
|
|
||||||
img_processor = AutoImageProcessor.from_pretrained(self._gemma_root, local_files_only=True)
|
|
||||||
if not self.tokenizer:
|
|
||||||
raise ValueError("Tokenizer is not loaded, cannot load image processor")
|
|
||||||
self.processor = Gemma3Processor(image_processor=img_processor, tokenizer=self.tokenizer.tokenizer)
|
|
||||||
|
|
||||||
def _enhance(
|
def _enhance(
|
||||||
self,
|
self,
|
||||||
messages: list[dict[str, str]],
|
messages: list[dict[str, str]],
|
||||||
@@ -86,8 +79,6 @@ class GemmaTextEncoderModelBase(torch.nn.Module):
|
|||||||
max_new_tokens: int = 512,
|
max_new_tokens: int = 512,
|
||||||
seed: int = 42,
|
seed: int = 42,
|
||||||
) -> str:
|
) -> str:
|
||||||
if self.processor is None:
|
|
||||||
self._init_image_processor()
|
|
||||||
text = self.processor.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
text = self.processor.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
||||||
|
|
||||||
model_inputs = self.processor(
|
model_inputs = self.processor(
|
||||||
@@ -242,17 +233,23 @@ def _find_matching_dir(root_path: str, pattern: str) -> str:
|
|||||||
def module_ops_from_gemma_root(gemma_root: str) -> tuple[ModuleOps, ...]:
|
def module_ops_from_gemma_root(gemma_root: str) -> tuple[ModuleOps, ...]:
|
||||||
gemma_path = _find_matching_dir(gemma_root, "model*.safetensors")
|
gemma_path = _find_matching_dir(gemma_root, "model*.safetensors")
|
||||||
tokenizer_path = _find_matching_dir(gemma_root, "tokenizer.model")
|
tokenizer_path = _find_matching_dir(gemma_root, "tokenizer.model")
|
||||||
|
processor_path = _find_matching_dir(gemma_root, "preprocessor_config.json")
|
||||||
|
|
||||||
def load_gemma(module: GemmaTextEncoderModelBase) -> GemmaTextEncoderModelBase:
|
def load_gemma(module: GemmaTextEncoderModelBase) -> GemmaTextEncoderModelBase:
|
||||||
module.model = Gemma3ForConditionalGeneration.from_pretrained(
|
module.model = Gemma3ForConditionalGeneration.from_pretrained(
|
||||||
gemma_path, local_files_only=True, torch_dtype=torch.bfloat16
|
gemma_path, local_files_only=True, torch_dtype=torch.bfloat16
|
||||||
)
|
)
|
||||||
module._gemma_root = module._gemma_root or gemma_root
|
|
||||||
return module
|
return module
|
||||||
|
|
||||||
def load_tokenizer(module: GemmaTextEncoderModelBase) -> GemmaTextEncoderModelBase:
|
def load_tokenizer(module: GemmaTextEncoderModelBase) -> GemmaTextEncoderModelBase:
|
||||||
module.tokenizer = LTXVGemmaTokenizer(tokenizer_path, 1024)
|
module.tokenizer = LTXVGemmaTokenizer(tokenizer_path, 1024)
|
||||||
module._gemma_root = module._gemma_root or gemma_root
|
return module
|
||||||
|
|
||||||
|
def load_processor(module: GemmaTextEncoderModelBase) -> GemmaTextEncoderModelBase:
|
||||||
|
image_processor = AutoImageProcessor.from_pretrained(processor_path, local_files_only=True)
|
||||||
|
if not module.tokenizer:
|
||||||
|
raise ValueError("Tokenizer model operation must be performed before processor model operation")
|
||||||
|
module.processor = Gemma3Processor(image_processor=image_processor, tokenizer=module.tokenizer.tokenizer)
|
||||||
return module
|
return module
|
||||||
|
|
||||||
gemma_load_ops = ModuleOps(
|
gemma_load_ops = ModuleOps(
|
||||||
@@ -265,7 +262,12 @@ def module_ops_from_gemma_root(gemma_root: str) -> tuple[ModuleOps, ...]:
|
|||||||
matcher=lambda module: isinstance(module, GemmaTextEncoderModelBase) and module.tokenizer is None,
|
matcher=lambda module: isinstance(module, GemmaTextEncoderModelBase) and module.tokenizer is None,
|
||||||
mutator=load_tokenizer,
|
mutator=load_tokenizer,
|
||||||
)
|
)
|
||||||
return (gemma_load_ops, tokenizer_load_ops)
|
processor_load_ops = ModuleOps(
|
||||||
|
"ProcessorLoad",
|
||||||
|
matcher=lambda module: isinstance(module, GemmaTextEncoderModelBase) and module.processor is None,
|
||||||
|
mutator=load_processor,
|
||||||
|
)
|
||||||
|
return (gemma_load_ops, tokenizer_load_ops, processor_load_ops)
|
||||||
|
|
||||||
|
|
||||||
def encode_text(text_encoder: GemmaTextEncoderModelBase, prompts: list[str]) -> list[tuple[torch.Tensor, torch.Tensor]]:
|
def encode_text(text_encoder: GemmaTextEncoderModelBase, prompts: list[str]) -> list[tuple[torch.Tensor, torch.Tensor]]:
|
||||||
|
|||||||
@@ -185,7 +185,9 @@ class DistilledPipeline:
|
|||||||
del video_encoder
|
del video_encoder
|
||||||
cleanup_memory()
|
cleanup_memory()
|
||||||
|
|
||||||
decoded_video = vae_decode_video(video_state.latent, self.model_ledger.video_decoder(), tiling_config)
|
decoded_video = vae_decode_video(
|
||||||
|
video_state.latent, self.model_ledger.video_decoder(), tiling_config, generator
|
||||||
|
)
|
||||||
decoded_audio = vae_decode_audio(
|
decoded_audio = vae_decode_audio(
|
||||||
audio_state.latent, self.model_ledger.audio_decoder(), self.model_ledger.vocoder()
|
audio_state.latent, self.model_ledger.audio_decoder(), self.model_ledger.vocoder()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -223,7 +223,9 @@ class ICLoraPipeline:
|
|||||||
del video_encoder
|
del video_encoder
|
||||||
cleanup_memory()
|
cleanup_memory()
|
||||||
|
|
||||||
decoded_video = vae_decode_video(video_state.latent, self.stage_2_model_ledger.video_decoder(), tiling_config)
|
decoded_video = vae_decode_video(
|
||||||
|
video_state.latent, self.stage_2_model_ledger.video_decoder(), tiling_config, generator
|
||||||
|
)
|
||||||
decoded_audio = vae_decode_audio(
|
decoded_audio = vae_decode_audio(
|
||||||
audio_state.latent, self.stage_2_model_ledger.audio_decoder(), self.stage_2_model_ledger.vocoder()
|
audio_state.latent, self.stage_2_model_ledger.audio_decoder(), self.stage_2_model_ledger.vocoder()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -223,7 +223,9 @@ class KeyframeInterpolationPipeline:
|
|||||||
del video_encoder
|
del video_encoder
|
||||||
cleanup_memory()
|
cleanup_memory()
|
||||||
|
|
||||||
decoded_video = vae_decode_video(video_state.latent, self.stage_2_model_ledger.video_decoder(), tiling_config)
|
decoded_video = vae_decode_video(
|
||||||
|
video_state.latent, self.stage_2_model_ledger.video_decoder(), tiling_config, generator
|
||||||
|
)
|
||||||
decoded_audio = vae_decode_audio(
|
decoded_audio = vae_decode_audio(
|
||||||
audio_state.latent, self.stage_2_model_ledger.audio_decoder(), self.stage_2_model_ledger.vocoder()
|
audio_state.latent, self.stage_2_model_ledger.audio_decoder(), self.stage_2_model_ledger.vocoder()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class TI2VidOneStagePipeline:
|
|||||||
del transformer
|
del transformer
|
||||||
cleanup_memory()
|
cleanup_memory()
|
||||||
|
|
||||||
decoded_video = vae_decode_video(video_state.latent, self.model_ledger.video_decoder())
|
decoded_video = vae_decode_video(video_state.latent, self.model_ledger.video_decoder(), generator=generator)
|
||||||
decoded_audio = vae_decode_audio(
|
decoded_audio = vae_decode_audio(
|
||||||
audio_state.latent, self.model_ledger.audio_decoder(), self.model_ledger.vocoder()
|
audio_state.latent, self.model_ledger.audio_decoder(), self.model_ledger.vocoder()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -225,7 +225,9 @@ class TI2VidTwoStagesPipeline:
|
|||||||
del video_encoder
|
del video_encoder
|
||||||
cleanup_memory()
|
cleanup_memory()
|
||||||
|
|
||||||
decoded_video = vae_decode_video(video_state.latent, self.stage_2_model_ledger.video_decoder(), tiling_config)
|
decoded_video = vae_decode_video(
|
||||||
|
video_state.latent, self.stage_2_model_ledger.video_decoder(), tiling_config, generator
|
||||||
|
)
|
||||||
decoded_audio = vae_decode_audio(
|
decoded_audio = vae_decode_audio(
|
||||||
audio_state.latent, self.stage_2_model_ledger.audio_decoder(), self.stage_2_model_ledger.vocoder()
|
audio_state.latent, self.stage_2_model_ledger.audio_decoder(), self.stage_2_model_ledger.vocoder()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -253,6 +253,10 @@ checkpoints:
|
|||||||
# Set to -1 to keep all checkpoints
|
# Set to -1 to keep all checkpoints
|
||||||
keep_last_n: -1
|
keep_last_n: -1
|
||||||
|
|
||||||
|
# Precision to use when saving checkpoint weights
|
||||||
|
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
||||||
|
precision: "bfloat16"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Flow Matching Configuration
|
# Flow Matching Configuration
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -263,6 +263,10 @@ checkpoints:
|
|||||||
# Set to -1 to keep all checkpoints
|
# Set to -1 to keep all checkpoints
|
||||||
keep_last_n: 3
|
keep_last_n: 3
|
||||||
|
|
||||||
|
# Precision to use when saving checkpoint weights
|
||||||
|
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
||||||
|
precision: "bfloat16"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Flow Matching Configuration
|
# Flow Matching Configuration
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -292,8 +292,9 @@ Model checkpointing configuration.
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
checkpoints:
|
checkpoints:
|
||||||
interval: 250 # Steps between checkpoint saves (null = disabled)
|
interval: 250 # Steps between checkpoint saves (null = disabled)
|
||||||
keep_last_n: 3 # Number of recent checkpoints to retain
|
keep_last_n: 3 # Number of recent checkpoints to retain
|
||||||
|
precision: bfloat16 # Precision for saved weights (bfloat16 or float32)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Key parameters:**
|
**Key parameters:**
|
||||||
@@ -302,6 +303,7 @@ checkpoints:
|
|||||||
|---------------|------------------------------------------------------------------------|
|
|---------------|------------------------------------------------------------------------|
|
||||||
| `interval` | Steps between intermediate checkpoint saves (set to `null` to disable) |
|
| `interval` | Steps between intermediate checkpoint saves (set to `null` to disable) |
|
||||||
| `keep_last_n` | Number of most recent checkpoints to keep (-1 = keep all) |
|
| `keep_last_n` | Number of most recent checkpoints to keep (-1 = keep all) |
|
||||||
|
| `precision` | Precision for saved checkpoint weights: `"bfloat16"` (default) or `"float32"` |
|
||||||
|
|
||||||
### HubConfig
|
### HubConfig
|
||||||
|
|
||||||
|
|||||||
@@ -350,6 +350,11 @@ class CheckpointsConfig(ConfigBaseModel):
|
|||||||
ge=-1,
|
ge=-1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
precision: Literal["bfloat16", "float32"] = Field(
|
||||||
|
default="bfloat16",
|
||||||
|
description="Precision to use when saving checkpoint weights. Options: 'bfloat16' or 'float32'.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class HubConfig(ConfigBaseModel):
|
class HubConfig(ConfigBaseModel):
|
||||||
"""Configuration for Hugging Face Hub integration"""
|
"""Configuration for Hugging Face Hub integration"""
|
||||||
|
|||||||
@@ -873,6 +873,9 @@ class LtxvTrainer:
|
|||||||
|
|
||||||
save_dir.mkdir(exist_ok=True, parents=True)
|
save_dir.mkdir(exist_ok=True, parents=True)
|
||||||
|
|
||||||
|
# Determine save precision
|
||||||
|
save_dtype = torch.bfloat16 if self._config.checkpoints.precision == "bfloat16" else torch.float32
|
||||||
|
|
||||||
# For LoRA: extract only adapter weights; for full: use as-is
|
# For LoRA: extract only adapter weights; for full: use as-is
|
||||||
if is_lora:
|
if is_lora:
|
||||||
unwrapped = self._accelerator.unwrap_model(self._transformer, keep_torch_compile=False)
|
unwrapped = self._accelerator.unwrap_model(self._transformer, keep_torch_compile=False)
|
||||||
@@ -885,9 +888,15 @@ class LtxvTrainer:
|
|||||||
# Convert to ComfyUI-compatible format (add "diffusion_model." prefix)
|
# Convert to ComfyUI-compatible format (add "diffusion_model." prefix)
|
||||||
state_dict = {f"diffusion_model.{k}": v for k, v in state_dict.items()}
|
state_dict = {f"diffusion_model.{k}": v for k, v in state_dict.items()}
|
||||||
|
|
||||||
|
# Cast to configured precision
|
||||||
|
state_dict = {k: v.to(save_dtype) if isinstance(v, Tensor) else v for k, v in state_dict.items()}
|
||||||
|
|
||||||
# Save to disk
|
# Save to disk
|
||||||
save_file(state_dict, saved_weights_path)
|
save_file(state_dict, saved_weights_path)
|
||||||
else:
|
else:
|
||||||
|
# Cast to configured precision
|
||||||
|
full_state_dict = {k: v.to(save_dtype) if isinstance(v, Tensor) else v for k, v in full_state_dict.items()}
|
||||||
|
|
||||||
# Save to disk
|
# Save to disk
|
||||||
self._accelerator.save(full_state_dict, saved_weights_path)
|
self._accelerator.save(full_state_dict, saved_weights_path)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user