Automated PR - 2026-01-12

This commit is contained in:
sync-bot
2026-01-12 14:14:33 +00:00
parent 628956009c
commit e5a15a4777
14 changed files with 66 additions and 31 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
[![Website](https://img.shields.io/badge/Website-LTX-181717?logo=google-chrome)](https://ltx.io) [![Website](https://img.shields.io/badge/Website-LTX-181717?logo=google-chrome)](https://ltx.io)
[![Model](https://img.shields.io/badge/HuggingFace-Model-orange?logo=huggingface)](https://huggingface.co/Lightricks/LTX-2) [![Model](https://img.shields.io/badge/HuggingFace-Model-orange?logo=huggingface)](https://huggingface.co/Lightricks/LTX-2)
[![Demo](https://img.shields.io/badge/Demo-Try%20Now-brightgreen?logo=vercel)](https://app.ltx.studio/ltx-2-playground/i2v) [![Demo](https://img.shields.io/badge/Demo-Try%20Now-brightgreen?logo=vercel)](https://app.ltx.studio/ltx-2-playground/i2v)
[![Paper](https://img.shields.io/badge/Paper-PDF-EC1C24?logo=adobeacrobatreader&logoColor=white)](https://videos.ltx.io/LTX-2/grants/LTX_2_Technical_Report_compressed.pdf) [![Paper](https://img.shields.io/badge/Paper-PDF-EC1C24?logo=adobeacrobatreader&logoColor=white)](https://arxiv.org/abs/2601.03233)
[![Discord](https://img.shields.io/badge/Join-Discord-5865F2?logo=discord)](https://discord.gg/ltxplatform) [![Discord](https://img.shields.io/badge/Join-Discord-5865F2?logo=discord)](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)