Image-to-Video
Diffusers
text-to-video
video-to-video
image-text-to-video
audio-to-video
text-to-audio
video-to-audio
audio-to-audio
text-to-audio-video
image-to-audio-video
image-text-to-audio-video
ltx-2
ltx-2-3
ltx-video
ltxv
lightricks
Instructions to use ibyteohdear/ltx-2.3-packages with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use ibyteohdear/ltx-2.3-packages with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline from diffusers.utils import load_image, export_to_video # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("ibyteohdear/ltx-2.3-packages", torch_dtype=torch.bfloat16, device_map="cuda") pipe.to("cuda") prompt = "A man with short gray hair plays a red electric guitar." image = load_image( "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/guitar-man.png" ) output = pipe(image=image, prompt=prompt).frames[0] export_to_video(output, "output.mp4") - Notebooks
- Google Colab
- Kaggle
Upload folder using huggingface_hub (part 2)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- packages/ltx-trainer/configs/a2v_lora.yaml +344 -0
- packages/ltx-trainer/configs/accelerate/ddp.yaml +16 -0
- packages/ltx-trainer/configs/accelerate/ddp_compile.yaml +21 -0
- packages/ltx-trainer/configs/accelerate/fsdp.yaml +29 -0
- packages/ltx-trainer/configs/accelerate/fsdp_compile.yaml +34 -0
- packages/ltx-trainer/configs/audio_extend_lora.yaml +343 -0
- packages/ltx-trainer/configs/audio_inpainting_lora.yaml +324 -0
- packages/ltx-trainer/configs/audio_suffix_lora.yaml +343 -0
- packages/ltx-trainer/configs/av2av_ic_lora.yaml +342 -0
- packages/ltx-trainer/configs/i2v_lora.yaml +348 -0
- packages/ltx-trainer/configs/t2a_lora.yaml +317 -0
- packages/ltx-trainer/configs/t2v_lora.yaml +327 -0
- packages/ltx-trainer/configs/t2v_lora_low_vram.yaml +339 -0
- packages/ltx-trainer/configs/v2a_lora.yaml +349 -0
- packages/ltx-trainer/configs/v2v_ic_lora.yaml +359 -0
- packages/ltx-trainer/configs/video_extend_lora.yaml +355 -0
- packages/ltx-trainer/configs/video_inpainting_lora.yaml +343 -0
- packages/ltx-trainer/configs/video_outpainting_lora.yaml +341 -0
- packages/ltx-trainer/configs/video_suffix_lora.yaml +355 -0
- packages/ltx-trainer/docs/configuration-reference.md +464 -0
- packages/ltx-trainer/docs/custom-training-strategies.md +515 -0
- packages/ltx-trainer/docs/dataset-preparation.md +489 -0
- packages/ltx-trainer/docs/quick-start.md +160 -0
- packages/ltx-trainer/docs/training-guide.md +215 -0
- packages/ltx-trainer/docs/training-modes.md +600 -0
- packages/ltx-trainer/docs/troubleshooting.md +314 -0
- packages/ltx-trainer/docs/utility-scripts.md +239 -0
- packages/ltx-trainer/pyproject.toml +98 -0
- packages/ltx-trainer/scripts/caption_videos.py +536 -0
- packages/ltx-trainer/scripts/compute_reference.py +304 -0
- packages/ltx-trainer/scripts/decode_latents.py +369 -0
- packages/ltx-trainer/scripts/process_captions.py +497 -0
- packages/ltx-trainer/scripts/process_dataset.py +432 -0
- packages/ltx-trainer/scripts/process_videos.py +1474 -0
- packages/ltx-trainer/scripts/serve_captioner.py +203 -0
- packages/ltx-trainer/scripts/split_scenes.py +417 -0
- packages/ltx-trainer/scripts/train.py +63 -0
- packages/ltx-trainer/src/ltx_trainer/__init__.py +44 -0
- packages/ltx-trainer/src/ltx_trainer/captioning.py +455 -0
- packages/ltx-trainer/src/ltx_trainer/config.py +833 -0
- packages/ltx-trainer/src/ltx_trainer/config_display.py +156 -0
- packages/ltx-trainer/src/ltx_trainer/datasets.py +315 -0
- packages/ltx-trainer/src/ltx_trainer/gemma_8bit.py +104 -0
- packages/ltx-trainer/src/ltx_trainer/gpu_utils.py +90 -0
- packages/ltx-trainer/src/ltx_trainer/hf_hub_utils.py +212 -0
- packages/ltx-trainer/src/ltx_trainer/model_loader.py +378 -0
- packages/ltx-trainer/src/ltx_trainer/progress.py +182 -0
- packages/ltx-trainer/src/ltx_trainer/quantization.py +195 -0
- packages/ltx-trainer/src/ltx_trainer/sigma_tracker.py +59 -0
- packages/ltx-trainer/src/ltx_trainer/timestep_samplers.py +160 -0
packages/ltx-trainer/configs/a2v_lora.yaml
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 Audio-to-Video LoRA Training Configuration
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This configuration is for training LoRA adapters on the LTX-2 model for
|
| 6 |
+
# audio-to-video generation. The model learns to generate videos conditioned
|
| 7 |
+
# on a frozen audio signal via the transformer's built-in cross-modal attention.
|
| 8 |
+
#
|
| 9 |
+
# In this mode, audio is provided as a frozen (clean, no noise, no loss)
|
| 10 |
+
# conditioning signal. The video modality is the only generated output.
|
| 11 |
+
# Audio influences video generation through the transformer's audio-to-video
|
| 12 |
+
# cross-attention mechanism.
|
| 13 |
+
#
|
| 14 |
+
# Use this configuration when you want to:
|
| 15 |
+
# - Generate videos driven by audio content (e.g., music visualizations)
|
| 16 |
+
# - Train audio-reactive video generation
|
| 17 |
+
# - Create models that synchronize video with given audio
|
| 18 |
+
#
|
| 19 |
+
# Dataset structure:
|
| 20 |
+
# preprocessed_data_root/
|
| 21 |
+
# ├── latents/ # Video latents (VAE-encoded videos)
|
| 22 |
+
# ├── conditions/ # Text embeddings for each video
|
| 23 |
+
# └── audio_latents/ # Audio latents (frozen conditioning input)
|
| 24 |
+
#
|
| 25 |
+
# =============================================================================
|
| 26 |
+
|
| 27 |
+
# -----------------------------------------------------------------------------
|
| 28 |
+
# Model Configuration
|
| 29 |
+
# -----------------------------------------------------------------------------
|
| 30 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 31 |
+
model:
|
| 32 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 33 |
+
# This should be a local path to your downloaded model
|
| 34 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 35 |
+
|
| 36 |
+
# Path to the text encoder model directory
|
| 37 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 38 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 39 |
+
|
| 40 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 41 |
+
# LoRA is recommended for most use cases (faster, less memory, prevents overfitting)
|
| 42 |
+
training_mode: "lora"
|
| 43 |
+
|
| 44 |
+
# Optional: Path to resume training from a checkpoint
|
| 45 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 46 |
+
load_checkpoint: null
|
| 47 |
+
|
| 48 |
+
# -----------------------------------------------------------------------------
|
| 49 |
+
# LoRA Configuration
|
| 50 |
+
# -----------------------------------------------------------------------------
|
| 51 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 52 |
+
lora:
|
| 53 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 54 |
+
# Typical values: 8, 16, 32, 64. Start with 32 for general fine-tuning.
|
| 55 |
+
rank: 32
|
| 56 |
+
|
| 57 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 58 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 59 |
+
alpha: 32
|
| 60 |
+
|
| 61 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 62 |
+
# Can help with regularization if overfitting occurs
|
| 63 |
+
dropout: 0.0
|
| 64 |
+
|
| 65 |
+
# Which transformer modules to apply LoRA to
|
| 66 |
+
# The LTX-2 transformer has separate attention and FFN blocks for video and audio:
|
| 67 |
+
#
|
| 68 |
+
# VIDEO MODULES:
|
| 69 |
+
# - attn1.to_k, attn1.to_q, attn1.to_v, attn1.to_out.0 (video self-attention)
|
| 70 |
+
# - attn2.to_k, attn2.to_q, attn2.to_v, attn2.to_out.0 (video cross-attention to text)
|
| 71 |
+
# - ff.net.0.proj, ff.net.2 (video feed-forward)
|
| 72 |
+
#
|
| 73 |
+
# AUDIO MODULES:
|
| 74 |
+
# - audio_attn1.to_k, audio_attn1.to_q, audio_attn1.to_v, audio_attn1.to_out.0 (audio self-attention)
|
| 75 |
+
# - audio_attn2.to_k, audio_attn2.to_q, audio_attn2.to_v, audio_attn2.to_out.0 (audio cross-attention to text)
|
| 76 |
+
# - audio_ff.net.0.proj, audio_ff.net.2 (audio feed-forward)
|
| 77 |
+
#
|
| 78 |
+
# AUDIO-VIDEO CROSS-ATTENTION MODULES (for cross-modal interaction):
|
| 79 |
+
# - audio_to_video_attn.to_k, audio_to_video_attn.to_q, audio_to_video_attn.to_v, audio_to_video_attn.to_out.0
|
| 80 |
+
# (Q from video, K/V from audio - allows video to attend to audio features)
|
| 81 |
+
# - video_to_audio_attn.to_k, video_to_audio_attn.to_q, video_to_audio_attn.to_v, video_to_audio_attn.to_out.0
|
| 82 |
+
# (Q from audio, K/V from video - allows audio to attend to video features)
|
| 83 |
+
#
|
| 84 |
+
# Using short patterns like "to_k" matches ALL attention modules (video, audio, and cross-modal).
|
| 85 |
+
# For audio-video training, this is the recommended approach.
|
| 86 |
+
target_modules:
|
| 87 |
+
# Attention layers (matches both video and audio branches)
|
| 88 |
+
- "to_k"
|
| 89 |
+
- "to_q"
|
| 90 |
+
- "to_v"
|
| 91 |
+
- "to_out.0"
|
| 92 |
+
# Uncomment below to also train feed-forward layers (can increase the LoRA's capacity):
|
| 93 |
+
# - "ff.net.0.proj"
|
| 94 |
+
# - "ff.net.2"
|
| 95 |
+
# - "audio_ff.net.0.proj"
|
| 96 |
+
# - "audio_ff.net.2"
|
| 97 |
+
|
| 98 |
+
# -----------------------------------------------------------------------------
|
| 99 |
+
# Training Strategy Configuration
|
| 100 |
+
# -----------------------------------------------------------------------------
|
| 101 |
+
# Defines the audio-to-video training approach using the unified flexible strategy.
|
| 102 |
+
# Audio is frozen (no noise, sigma=0, excluded from loss) and conditions video
|
| 103 |
+
# generation via the transformer's built-in cross-modal attention.
|
| 104 |
+
training_strategy:
|
| 105 |
+
# Strategy name: "flexible" for the unified conditioning framework
|
| 106 |
+
# Supports all training modes (T2V, I2V, V2V, A2V, V2A, etc.) through
|
| 107 |
+
# modality-specific configuration blocks.
|
| 108 |
+
name: "flexible"
|
| 109 |
+
|
| 110 |
+
# Video modality configuration
|
| 111 |
+
# Video is the generated (denoised) output
|
| 112 |
+
video:
|
| 113 |
+
# Whether the model generates video (true) or uses it as frozen conditioning (false)
|
| 114 |
+
is_generated: true
|
| 115 |
+
# Directory name (within preprocessed_data_root) containing video latents
|
| 116 |
+
latents_dir: "latents"
|
| 117 |
+
|
| 118 |
+
# Audio modality configuration
|
| 119 |
+
# Audio is frozen — it acts as conditioning for video generation
|
| 120 |
+
# Frozen modalities get sigma=0, timestep=0, no noise, and no loss
|
| 121 |
+
audio:
|
| 122 |
+
# Whether the model generates audio (true) or uses it as frozen conditioning (false)
|
| 123 |
+
# When false, audio is passed through the transformer clean and influences
|
| 124 |
+
# video via cross-modal attention
|
| 125 |
+
is_generated: false
|
| 126 |
+
# Directory name (within preprocessed_data_root) containing audio latents
|
| 127 |
+
latents_dir: "audio_latents"
|
| 128 |
+
|
| 129 |
+
# -----------------------------------------------------------------------------
|
| 130 |
+
# Optimization Configuration
|
| 131 |
+
# -----------------------------------------------------------------------------
|
| 132 |
+
# Controls the training optimization parameters.
|
| 133 |
+
optimization:
|
| 134 |
+
# Learning rate for the optimizer
|
| 135 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 136 |
+
learning_rate: 1e-4
|
| 137 |
+
|
| 138 |
+
# Total number of training steps
|
| 139 |
+
steps: 2000
|
| 140 |
+
|
| 141 |
+
# Batch size per GPU
|
| 142 |
+
# Reduce if running out of memory
|
| 143 |
+
batch_size: 1
|
| 144 |
+
|
| 145 |
+
# Number of gradient accumulation steps
|
| 146 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 147 |
+
gradient_accumulation_steps: 1
|
| 148 |
+
|
| 149 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 150 |
+
max_grad_norm: 1.0
|
| 151 |
+
|
| 152 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 153 |
+
optimizer_type: "adamw"
|
| 154 |
+
|
| 155 |
+
# Learning rate scheduler type
|
| 156 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 157 |
+
scheduler_type: "linear"
|
| 158 |
+
|
| 159 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 160 |
+
scheduler_params: { }
|
| 161 |
+
|
| 162 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 163 |
+
# Recommended for training with limited GPU memory
|
| 164 |
+
enable_gradient_checkpointing: true
|
| 165 |
+
|
| 166 |
+
# -----------------------------------------------------------------------------
|
| 167 |
+
# Acceleration Configuration
|
| 168 |
+
# -----------------------------------------------------------------------------
|
| 169 |
+
# Hardware acceleration and memory optimization settings.
|
| 170 |
+
acceleration:
|
| 171 |
+
# Mixed precision training mode
|
| 172 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 173 |
+
mixed_precision_mode: "bf16"
|
| 174 |
+
|
| 175 |
+
# Model quantization for reduced memory usage
|
| 176 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 177 |
+
quantization: null
|
| 178 |
+
|
| 179 |
+
# Load text encoder in 8-bit precision to save memory
|
| 180 |
+
# Useful when GPU memory is limited
|
| 181 |
+
load_text_encoder_in_8bit: false
|
| 182 |
+
|
| 183 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 184 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 185 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 186 |
+
offload_optimizer_during_validation: false
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
# -----------------------------------------------------------------------------
|
| 190 |
+
# Data Configuration
|
| 191 |
+
# -----------------------------------------------------------------------------
|
| 192 |
+
# Specifies the training data location and loading parameters.
|
| 193 |
+
data:
|
| 194 |
+
# Root directory containing preprocessed training data
|
| 195 |
+
# Should contain: latents/, audio_latents/, and conditions/
|
| 196 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 197 |
+
|
| 198 |
+
# Number of worker processes for data loading
|
| 199 |
+
# Used for parallel data loading to speed up data loading
|
| 200 |
+
num_dataloader_workers: 2
|
| 201 |
+
|
| 202 |
+
# -----------------------------------------------------------------------------
|
| 203 |
+
# Validation Configuration
|
| 204 |
+
# -----------------------------------------------------------------------------
|
| 205 |
+
# Controls validation sampling during training.
|
| 206 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 207 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 208 |
+
validation:
|
| 209 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 210 |
+
# Use 'conditions' to add conditioning (first_frame, prefix, suffix, reference, video_to_audio, etc.)
|
| 211 |
+
# See docs/configuration-reference.md#validation-condition-types for the full list of condition types.
|
| 212 |
+
samples:
|
| 213 |
+
- prompt: >-
|
| 214 |
+
A musician plays an acoustic guitar in a dimly lit recording studio, fingers moving
|
| 215 |
+
across the fretboard with practiced ease. The warm amber light from a desk lamp
|
| 216 |
+
illuminates the wooden guitar body. Sound-absorbing panels line the walls, and a
|
| 217 |
+
microphone stands nearby on a boom arm.
|
| 218 |
+
conditions:
|
| 219 |
+
- type: audio_to_video
|
| 220 |
+
audio: "/path/to/conditioning_audio_1.wav"
|
| 221 |
+
- prompt: >-
|
| 222 |
+
Rain falls steadily on a cobblestone street in a European town at dusk. Puddles form
|
| 223 |
+
between the stones, creating ripples as new drops land. Old brick buildings line both
|
| 224 |
+
sides of the narrow street, their facades glistening with moisture under warm
|
| 225 |
+
streetlights.
|
| 226 |
+
conditions:
|
| 227 |
+
- type: audio_to_video
|
| 228 |
+
audio: "/path/to/conditioning_audio_2.wav"
|
| 229 |
+
|
| 230 |
+
# Negative prompt to avoid unwanted artifacts
|
| 231 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 232 |
+
|
| 233 |
+
# Output video dimensions [width, height, frames]
|
| 234 |
+
# Width and height must be divisible by 32
|
| 235 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 236 |
+
video_dims: [ 576, 576, 89 ]
|
| 237 |
+
|
| 238 |
+
# Frame rate for generated videos
|
| 239 |
+
frame_rate: 25.0
|
| 240 |
+
|
| 241 |
+
# Random seed for reproducible validation outputs
|
| 242 |
+
seed: 42
|
| 243 |
+
|
| 244 |
+
# Number of denoising steps for validation inference
|
| 245 |
+
# Higher values = better quality but slower generation
|
| 246 |
+
inference_steps: 30
|
| 247 |
+
|
| 248 |
+
# Generate validation videos every N training steps
|
| 249 |
+
# Set to null to disable validation during training
|
| 250 |
+
interval: 100
|
| 251 |
+
|
| 252 |
+
# Classifier-free guidance scale
|
| 253 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 254 |
+
guidance_scale: 4.0
|
| 255 |
+
|
| 256 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 257 |
+
# STG is combined with CFG for better temporal coherence
|
| 258 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 259 |
+
stg_blocks: [29] # Recommended: single block 29
|
| 260 |
+
stg_mode: "stg_v" # "stg_av" perturbs both audio and video, "stg_v" video only
|
| 261 |
+
|
| 262 |
+
# Whether to generate audio in validation samples
|
| 263 |
+
# Independent of training_strategy.audio.is_generated - you can generate audio
|
| 264 |
+
# in validation even when not training the audio branch
|
| 265 |
+
# For this audio-to-video config, false matches frozen audio conditioning (video-only synthesis).
|
| 266 |
+
generate_audio: false
|
| 267 |
+
|
| 268 |
+
# Generate video from the frozen audio condition
|
| 269 |
+
generate_video: true
|
| 270 |
+
|
| 271 |
+
# Skip validation at the beginning of training (step 0)
|
| 272 |
+
skip_initial_validation: false
|
| 273 |
+
|
| 274 |
+
# -----------------------------------------------------------------------------
|
| 275 |
+
# Checkpoint Configuration
|
| 276 |
+
# -----------------------------------------------------------------------------
|
| 277 |
+
# Controls model checkpoint saving during training.
|
| 278 |
+
checkpoints:
|
| 279 |
+
# Save a checkpoint every N steps
|
| 280 |
+
# Set to null to disable intermediate checkpoints
|
| 281 |
+
interval: 250
|
| 282 |
+
|
| 283 |
+
# Number of most recent checkpoints to keep
|
| 284 |
+
# Set to -1 to keep all checkpoints
|
| 285 |
+
keep_last_n: -1
|
| 286 |
+
|
| 287 |
+
# Precision to use when saving checkpoint weights
|
| 288 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 289 |
+
precision: "bfloat16"
|
| 290 |
+
|
| 291 |
+
# -----------------------------------------------------------------------------
|
| 292 |
+
# Flow Matching Configuration
|
| 293 |
+
# -----------------------------------------------------------------------------
|
| 294 |
+
# Parameters for the flow matching training objective.
|
| 295 |
+
flow_matching:
|
| 296 |
+
# Timestep sampling mode
|
| 297 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 298 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 299 |
+
|
| 300 |
+
# Additional parameters for timestep sampling
|
| 301 |
+
timestep_sampling_params: { }
|
| 302 |
+
|
| 303 |
+
# -----------------------------------------------------------------------------
|
| 304 |
+
# Hugging Face Hub Configuration
|
| 305 |
+
# -----------------------------------------------------------------------------
|
| 306 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 307 |
+
hub:
|
| 308 |
+
# Whether to push the trained model to the Hub
|
| 309 |
+
push_to_hub: false
|
| 310 |
+
|
| 311 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-lora-model")
|
| 312 |
+
# Required if push_to_hub is true
|
| 313 |
+
hub_model_id: null
|
| 314 |
+
|
| 315 |
+
# -----------------------------------------------------------------------------
|
| 316 |
+
# Weights & Biases Configuration
|
| 317 |
+
# -----------------------------------------------------------------------------
|
| 318 |
+
# Settings for experiment tracking with W&B.
|
| 319 |
+
wandb:
|
| 320 |
+
# Enable W&B logging
|
| 321 |
+
enabled: false
|
| 322 |
+
|
| 323 |
+
# W&B project name
|
| 324 |
+
project: "ltx-2-trainer"
|
| 325 |
+
|
| 326 |
+
# W&B username or team (null uses default account)
|
| 327 |
+
entity: null
|
| 328 |
+
|
| 329 |
+
# Tags to help organize runs
|
| 330 |
+
tags: [ "ltx2", "lora", "a2v" ]
|
| 331 |
+
|
| 332 |
+
# Log validation media (video/audio) to W&B
|
| 333 |
+
log_validation_videos: true
|
| 334 |
+
|
| 335 |
+
# -----------------------------------------------------------------------------
|
| 336 |
+
# General Configuration
|
| 337 |
+
# -----------------------------------------------------------------------------
|
| 338 |
+
# Global settings for the training run.
|
| 339 |
+
|
| 340 |
+
# Random seed for reproducibility
|
| 341 |
+
seed: 42
|
| 342 |
+
|
| 343 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 344 |
+
output_dir: "outputs/a2v_lora"
|
packages/ltx-trainer/configs/accelerate/ddp.yaml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
compute_environment: LOCAL_MACHINE
|
| 2 |
+
debug: false
|
| 3 |
+
distributed_type: MULTI_GPU
|
| 4 |
+
downcast_bf16: 'no'
|
| 5 |
+
enable_cpu_affinity: false
|
| 6 |
+
machine_rank: 0
|
| 7 |
+
main_training_function: main
|
| 8 |
+
mixed_precision: bf16
|
| 9 |
+
num_machines: 1
|
| 10 |
+
num_processes: 4
|
| 11 |
+
rdzv_backend: static
|
| 12 |
+
same_network: true
|
| 13 |
+
tpu_env: []
|
| 14 |
+
tpu_use_cluster: false
|
| 15 |
+
tpu_use_sudo: false
|
| 16 |
+
use_cpu: false
|
packages/ltx-trainer/configs/accelerate/ddp_compile.yaml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
compute_environment: LOCAL_MACHINE
|
| 2 |
+
dynamo_config:
|
| 3 |
+
dynamo_backend: INDUCTOR
|
| 4 |
+
dynamo_mode: default
|
| 5 |
+
dynamo_use_fullgraph: false
|
| 6 |
+
dynamo_use_dynamic: true
|
| 7 |
+
debug: false
|
| 8 |
+
distributed_type: MULTI_GPU
|
| 9 |
+
downcast_bf16: 'no'
|
| 10 |
+
enable_cpu_affinity: false
|
| 11 |
+
machine_rank: 0
|
| 12 |
+
main_training_function: main
|
| 13 |
+
mixed_precision: bf16
|
| 14 |
+
num_machines: 1
|
| 15 |
+
num_processes: 4
|
| 16 |
+
rdzv_backend: static
|
| 17 |
+
same_network: true
|
| 18 |
+
tpu_env: [ ]
|
| 19 |
+
tpu_use_cluster: false
|
| 20 |
+
tpu_use_sudo: false
|
| 21 |
+
use_cpu: false
|
packages/ltx-trainer/configs/accelerate/fsdp.yaml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
compute_environment: LOCAL_MACHINE
|
| 2 |
+
debug: false
|
| 3 |
+
distributed_type: FSDP
|
| 4 |
+
downcast_bf16: 'no'
|
| 5 |
+
enable_cpu_affinity: false
|
| 6 |
+
fsdp_config:
|
| 7 |
+
fsdp_activation_checkpointing: false
|
| 8 |
+
fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
|
| 9 |
+
fsdp_backward_prefetch: BACKWARD_PRE
|
| 10 |
+
fsdp_cpu_ram_efficient_loading: true
|
| 11 |
+
fsdp_forward_prefetch: false
|
| 12 |
+
fsdp_offload_params: false
|
| 13 |
+
fsdp_reshard_after_forward: FULL_SHARD
|
| 14 |
+
fsdp_state_dict_type: SHARDED_STATE_DICT
|
| 15 |
+
fsdp_sync_module_states: true
|
| 16 |
+
fsdp_transformer_layer_cls_to_wrap: BasicAVTransformerBlock
|
| 17 |
+
fsdp_use_orig_params: true
|
| 18 |
+
fsdp_version: 1
|
| 19 |
+
machine_rank: 0
|
| 20 |
+
main_training_function: main
|
| 21 |
+
mixed_precision: bf16
|
| 22 |
+
num_machines: 1
|
| 23 |
+
num_processes: 4
|
| 24 |
+
rdzv_backend: static
|
| 25 |
+
same_network: true
|
| 26 |
+
tpu_env: []
|
| 27 |
+
tpu_use_cluster: false
|
| 28 |
+
tpu_use_sudo: false
|
| 29 |
+
use_cpu: false
|
packages/ltx-trainer/configs/accelerate/fsdp_compile.yaml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
compute_environment: LOCAL_MACHINE
|
| 2 |
+
debug: false
|
| 3 |
+
distributed_type: FSDP
|
| 4 |
+
downcast_bf16: 'no'
|
| 5 |
+
dynamo_config:
|
| 6 |
+
dynamo_backend: INDUCTOR
|
| 7 |
+
dynamo_mode: default
|
| 8 |
+
dynamo_use_fullgraph: false
|
| 9 |
+
dynamo_use_dynamic: true
|
| 10 |
+
enable_cpu_affinity: false
|
| 11 |
+
fsdp_config:
|
| 12 |
+
fsdp_activation_checkpointing: false
|
| 13 |
+
fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
|
| 14 |
+
fsdp_backward_prefetch: BACKWARD_PRE
|
| 15 |
+
fsdp_cpu_ram_efficient_loading: true
|
| 16 |
+
fsdp_forward_prefetch: false
|
| 17 |
+
fsdp_offload_params: false
|
| 18 |
+
fsdp_reshard_after_forward: FULL_SHARD
|
| 19 |
+
fsdp_state_dict_type: SHARDED_STATE_DICT
|
| 20 |
+
fsdp_sync_module_states: true
|
| 21 |
+
fsdp_transformer_layer_cls_to_wrap: BasicAVTransformerBlock
|
| 22 |
+
fsdp_use_orig_params: true
|
| 23 |
+
fsdp_version: 1
|
| 24 |
+
machine_rank: 0
|
| 25 |
+
main_training_function: main
|
| 26 |
+
mixed_precision: bf16
|
| 27 |
+
num_machines: 1
|
| 28 |
+
num_processes: 4
|
| 29 |
+
rdzv_backend: static
|
| 30 |
+
same_network: true
|
| 31 |
+
tpu_env: []
|
| 32 |
+
tpu_use_cluster: false
|
| 33 |
+
tpu_use_sudo: false
|
| 34 |
+
use_cpu: false
|
packages/ltx-trainer/configs/audio_extend_lora.yaml
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 Audio Extension (Forward) LoRA Training Configuration
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This configuration is for training LoRA adapters on the LTX-2 model for
|
| 6 |
+
# forward audio extension. The model learns to continue audio forward in time
|
| 7 |
+
# by conditioning on a prefix of existing audio latent timesteps.
|
| 8 |
+
#
|
| 9 |
+
# Prefix conditioning works by providing the first N audio latent timesteps as
|
| 10 |
+
# clean conditioning signals during training — they receive no noise,
|
| 11 |
+
# timestep=0, and are excluded from the loss. The model learns to generate
|
| 12 |
+
# audio that seamlessly continues from the given prefix.
|
| 13 |
+
#
|
| 14 |
+
# This is an audio-only training mode — no video modality is configured.
|
| 15 |
+
#
|
| 16 |
+
# Use this configuration when you want to:
|
| 17 |
+
# - Train the model to extend/continue existing audio
|
| 18 |
+
# - Fine-tune audio temporal continuation capabilities
|
| 19 |
+
# - Create seamless audio extension models
|
| 20 |
+
#
|
| 21 |
+
# Dataset structure:
|
| 22 |
+
# preprocessed_data_root/
|
| 23 |
+
# ├── conditions/ # Text embeddings for each sample
|
| 24 |
+
# └── audio_latents/ # Audio latents (VAE-encoded audio)
|
| 25 |
+
#
|
| 26 |
+
# =============================================================================
|
| 27 |
+
|
| 28 |
+
# -----------------------------------------------------------------------------
|
| 29 |
+
# Model Configuration
|
| 30 |
+
# -----------------------------------------------------------------------------
|
| 31 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 32 |
+
model:
|
| 33 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 34 |
+
# This should be a local path to your downloaded model
|
| 35 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 36 |
+
|
| 37 |
+
# Path to the text encoder model directory
|
| 38 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 39 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 40 |
+
|
| 41 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 42 |
+
# LoRA is recommended for most use cases (faster, less memory, prevents overfitting)
|
| 43 |
+
training_mode: "lora"
|
| 44 |
+
|
| 45 |
+
# Optional: Path to resume training from a checkpoint
|
| 46 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 47 |
+
load_checkpoint: null
|
| 48 |
+
|
| 49 |
+
# -----------------------------------------------------------------------------
|
| 50 |
+
# LoRA Configuration
|
| 51 |
+
# -----------------------------------------------------------------------------
|
| 52 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 53 |
+
lora:
|
| 54 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 55 |
+
# Typical values: 8, 16, 32, 64. Start with 32 for audio extension LoRA training.
|
| 56 |
+
rank: 32
|
| 57 |
+
|
| 58 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 59 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 60 |
+
alpha: 32
|
| 61 |
+
|
| 62 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 63 |
+
# Can help with regularization if overfitting occurs
|
| 64 |
+
dropout: 0.0
|
| 65 |
+
|
| 66 |
+
# Which transformer modules to apply LoRA to
|
| 67 |
+
# The LTX-2 transformer has separate attention and FFN blocks for video and audio:
|
| 68 |
+
#
|
| 69 |
+
# VIDEO MODULES (not used for audio-only modes):
|
| 70 |
+
# - attn1.to_k, attn1.to_q, attn1.to_v, attn1.to_out.0 (video self-attention)
|
| 71 |
+
# - attn2.to_k, attn2.to_q, attn2.to_v, attn2.to_out.0 (video cross-attention to text)
|
| 72 |
+
# - ff.net.0.proj, ff.net.2 (video feed-forward)
|
| 73 |
+
#
|
| 74 |
+
# AUDIO MODULES:
|
| 75 |
+
# - audio_attn1.to_k, audio_attn1.to_q, audio_attn1.to_v, audio_attn1.to_out.0 (audio self-attention)
|
| 76 |
+
# - audio_attn2.to_k, audio_attn2.to_q, audio_attn2.to_v, audio_attn2.to_out.0 (audio cross-attention to text)
|
| 77 |
+
# - audio_ff.net.0.proj, audio_ff.net.2 (audio feed-forward)
|
| 78 |
+
#
|
| 79 |
+
# AUDIO-VIDEO CROSS-ATTENTION MODULES (not used for audio-only modes):
|
| 80 |
+
# - audio_to_video_attn.to_k, audio_to_video_attn.to_q, audio_to_video_attn.to_v, audio_to_video_attn.to_out.0
|
| 81 |
+
# (Q from video, K/V from audio - allows video to attend to audio features)
|
| 82 |
+
# - video_to_audio_attn.to_k, video_to_audio_attn.to_q, video_to_audio_attn.to_v, video_to_audio_attn.to_out.0
|
| 83 |
+
# (Q from audio, K/V from video - allows audio to attend to video features)
|
| 84 |
+
#
|
| 85 |
+
# For audio-only extension, we explicitly target audio modules.
|
| 86 |
+
# Including audio FFN layers can increase the LoRA's capacity.
|
| 87 |
+
target_modules:
|
| 88 |
+
# Audio self-attention
|
| 89 |
+
- "audio_attn1.to_k"
|
| 90 |
+
- "audio_attn1.to_q"
|
| 91 |
+
- "audio_attn1.to_v"
|
| 92 |
+
- "audio_attn1.to_out.0"
|
| 93 |
+
# Audio cross-attention to text
|
| 94 |
+
- "audio_attn2.to_k"
|
| 95 |
+
- "audio_attn2.to_q"
|
| 96 |
+
- "audio_attn2.to_v"
|
| 97 |
+
- "audio_attn2.to_out.0"
|
| 98 |
+
# Audio feed-forward (often improves transformation quality)
|
| 99 |
+
- "audio_ff.net.0.proj"
|
| 100 |
+
- "audio_ff.net.2"
|
| 101 |
+
|
| 102 |
+
# -----------------------------------------------------------------------------
|
| 103 |
+
# Training Strategy Configuration
|
| 104 |
+
# -----------------------------------------------------------------------------
|
| 105 |
+
# Defines the audio extension training approach using the unified flexible
|
| 106 |
+
# strategy. Prefix conditioning provides the first N audio latent timesteps as
|
| 107 |
+
# clean conditioning, teaching the model to generate temporal continuations.
|
| 108 |
+
training_strategy:
|
| 109 |
+
name: "flexible"
|
| 110 |
+
|
| 111 |
+
# Audio modality configuration (audio-only, no video)
|
| 112 |
+
audio:
|
| 113 |
+
# Whether the model generates audio (true) or uses it as frozen conditioning (false)
|
| 114 |
+
is_generated: true
|
| 115 |
+
# Directory name (within preprocessed_data_root) containing audio latents
|
| 116 |
+
latents_dir: "audio_latents"
|
| 117 |
+
|
| 118 |
+
# Conditions applied to the audio modality during training
|
| 119 |
+
conditions:
|
| 120 |
+
- type: prefix
|
| 121 |
+
# Number of audio latent timesteps to use as conditioning prefix
|
| 122 |
+
# Each audio latent timestep = 1 patchified token
|
| 123 |
+
temporal_boundary: 8
|
| 124 |
+
# Probability of applying prefix conditioning per training sample
|
| 125 |
+
# At 1.0, all training samples use audio extension mode
|
| 126 |
+
probability: 1.0
|
| 127 |
+
|
| 128 |
+
# -----------------------------------------------------------------------------
|
| 129 |
+
# Optimization Configuration
|
| 130 |
+
# -----------------------------------------------------------------------------
|
| 131 |
+
# Controls the training optimization parameters.
|
| 132 |
+
optimization:
|
| 133 |
+
# Learning rate for the optimizer
|
| 134 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 135 |
+
learning_rate: 2e-4
|
| 136 |
+
|
| 137 |
+
# Total number of training steps
|
| 138 |
+
steps: 3000
|
| 139 |
+
|
| 140 |
+
# Batch size per GPU
|
| 141 |
+
# Reduce if running out of memory
|
| 142 |
+
batch_size: 1
|
| 143 |
+
|
| 144 |
+
# Number of gradient accumulation steps
|
| 145 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 146 |
+
gradient_accumulation_steps: 1
|
| 147 |
+
|
| 148 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 149 |
+
max_grad_norm: 1.0
|
| 150 |
+
|
| 151 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 152 |
+
optimizer_type: "adamw"
|
| 153 |
+
|
| 154 |
+
# Learning rate scheduler type
|
| 155 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 156 |
+
scheduler_type: "linear"
|
| 157 |
+
|
| 158 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 159 |
+
scheduler_params: { }
|
| 160 |
+
|
| 161 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 162 |
+
# Recommended for training with limited GPU memory
|
| 163 |
+
enable_gradient_checkpointing: true
|
| 164 |
+
|
| 165 |
+
# -----------------------------------------------------------------------------
|
| 166 |
+
# Acceleration Configuration
|
| 167 |
+
# -----------------------------------------------------------------------------
|
| 168 |
+
# Hardware acceleration and memory optimization settings.
|
| 169 |
+
acceleration:
|
| 170 |
+
# Mixed precision training mode
|
| 171 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 172 |
+
mixed_precision_mode: "bf16"
|
| 173 |
+
|
| 174 |
+
# Model quantization for reduced memory usage
|
| 175 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 176 |
+
quantization: null
|
| 177 |
+
|
| 178 |
+
# Load text encoder in 8-bit precision to save memory
|
| 179 |
+
# Useful when GPU memory is limited
|
| 180 |
+
load_text_encoder_in_8bit: false
|
| 181 |
+
|
| 182 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 183 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 184 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 185 |
+
offload_optimizer_during_validation: false
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
# -----------------------------------------------------------------------------
|
| 189 |
+
# Data Configuration
|
| 190 |
+
# -----------------------------------------------------------------------------
|
| 191 |
+
# Specifies the training data location and loading parameters.
|
| 192 |
+
data:
|
| 193 |
+
# Root directory containing preprocessed training data
|
| 194 |
+
# Should contain: conditions/ and audio_latents/ subdirectories
|
| 195 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 196 |
+
|
| 197 |
+
# Number of worker processes for data loading
|
| 198 |
+
# Used for parallel data loading to speed up data loading
|
| 199 |
+
num_dataloader_workers: 2
|
| 200 |
+
|
| 201 |
+
# -----------------------------------------------------------------------------
|
| 202 |
+
# Validation Configuration
|
| 203 |
+
# -----------------------------------------------------------------------------
|
| 204 |
+
# Controls validation sampling during training.
|
| 205 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 206 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 207 |
+
validation:
|
| 208 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 209 |
+
# Each sample includes an audio prefix condition for forward audio extension.
|
| 210 |
+
samples:
|
| 211 |
+
- prompt: >-
|
| 212 |
+
A warm and soothing piano melody with soft ambient textures, reminiscent of a quiet
|
| 213 |
+
evening by the fireplace. Gentle reverb creates a sense of intimate space.
|
| 214 |
+
conditions:
|
| 215 |
+
- type: prefix
|
| 216 |
+
audio: "/path/to/prefix_audio_1.wav"
|
| 217 |
+
# Duration is in seconds; choose a value that covers a comparable audio context
|
| 218 |
+
# to the training temporal_boundary measured in audio latent timesteps.
|
| 219 |
+
duration: 1.0
|
| 220 |
+
- prompt: >-
|
| 221 |
+
Energetic electronic beats with pulsing synthesizer leads and crisp hi-hat patterns.
|
| 222 |
+
Deep bass tones provide a driving rhythm underneath bright melodic arpeggios.
|
| 223 |
+
conditions:
|
| 224 |
+
- type: prefix
|
| 225 |
+
audio: "/path/to/prefix_audio_2.wav"
|
| 226 |
+
# Duration is in seconds; choose a value that covers a comparable audio context
|
| 227 |
+
# to the training temporal_boundary measured in audio latent timesteps.
|
| 228 |
+
duration: 1.0
|
| 229 |
+
|
| 230 |
+
# Negative prompt to avoid unwanted artifacts
|
| 231 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 232 |
+
|
| 233 |
+
# Generation length control [width, height, frames]
|
| 234 |
+
# With generate_video=false, width/height are unused; frames and frame_rate set audio duration.
|
| 235 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 236 |
+
video_dims: [ 512, 512, 81 ]
|
| 237 |
+
|
| 238 |
+
# Frame rate for generated videos
|
| 239 |
+
frame_rate: 25.0
|
| 240 |
+
|
| 241 |
+
# Random seed for reproducible validation outputs
|
| 242 |
+
seed: 42
|
| 243 |
+
|
| 244 |
+
# Number of denoising steps for validation inference
|
| 245 |
+
# Higher values = better quality but slower generation
|
| 246 |
+
inference_steps: 30
|
| 247 |
+
|
| 248 |
+
# Generate validation videos every N training steps
|
| 249 |
+
# Set to null to disable validation during training
|
| 250 |
+
interval: 100
|
| 251 |
+
|
| 252 |
+
# Classifier-free guidance scale
|
| 253 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 254 |
+
guidance_scale: 4.0
|
| 255 |
+
|
| 256 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 257 |
+
# STG is combined with CFG for better temporal coherence
|
| 258 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 259 |
+
stg_blocks: [29] # Recommended: single block 29
|
| 260 |
+
stg_mode: "stg_av" # "stg_av" skips both audio and video self-attention (suited to audio validation)
|
| 261 |
+
|
| 262 |
+
# Whether to generate audio in validation samples
|
| 263 |
+
# Enabled because this audio-only config generates audio
|
| 264 |
+
generate_audio: true
|
| 265 |
+
|
| 266 |
+
# Whether to generate video in validation samples
|
| 267 |
+
# Disabled because no video modality is configured
|
| 268 |
+
generate_video: false
|
| 269 |
+
|
| 270 |
+
# Skip validation at the beginning of training (step 0)
|
| 271 |
+
skip_initial_validation: false
|
| 272 |
+
|
| 273 |
+
# -----------------------------------------------------------------------------
|
| 274 |
+
# Checkpoint Configuration
|
| 275 |
+
# -----------------------------------------------------------------------------
|
| 276 |
+
# Controls model checkpoint saving during training.
|
| 277 |
+
checkpoints:
|
| 278 |
+
# Save a checkpoint every N steps
|
| 279 |
+
# Set to null to disable intermediate checkpoints
|
| 280 |
+
interval: 250
|
| 281 |
+
|
| 282 |
+
# Number of most recent checkpoints to keep
|
| 283 |
+
# Set to -1 to keep all checkpoints
|
| 284 |
+
keep_last_n: 3
|
| 285 |
+
|
| 286 |
+
# Precision to use when saving checkpoint weights
|
| 287 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 288 |
+
precision: "bfloat16"
|
| 289 |
+
|
| 290 |
+
# -----------------------------------------------------------------------------
|
| 291 |
+
# Flow Matching Configuration
|
| 292 |
+
# -----------------------------------------------------------------------------
|
| 293 |
+
# Parameters for the flow matching training objective.
|
| 294 |
+
flow_matching:
|
| 295 |
+
# Timestep sampling mode
|
| 296 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 297 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 298 |
+
|
| 299 |
+
# Additional parameters for timestep sampling
|
| 300 |
+
timestep_sampling_params: { }
|
| 301 |
+
|
| 302 |
+
# -----------------------------------------------------------------------------
|
| 303 |
+
# Hugging Face Hub Configuration
|
| 304 |
+
# -----------------------------------------------------------------------------
|
| 305 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 306 |
+
hub:
|
| 307 |
+
# Whether to push the trained model to the Hub
|
| 308 |
+
push_to_hub: false
|
| 309 |
+
|
| 310 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-ic-lora-model")
|
| 311 |
+
# Required if push_to_hub is true
|
| 312 |
+
hub_model_id: null
|
| 313 |
+
|
| 314 |
+
# -----------------------------------------------------------------------------
|
| 315 |
+
# Weights & Biases Configuration
|
| 316 |
+
# -----------------------------------------------------------------------------
|
| 317 |
+
# Settings for experiment tracking with W&B.
|
| 318 |
+
wandb:
|
| 319 |
+
# Enable W&B logging
|
| 320 |
+
enabled: false
|
| 321 |
+
|
| 322 |
+
# W&B project name
|
| 323 |
+
project: "ltx-2-trainer"
|
| 324 |
+
|
| 325 |
+
# W&B username or team (null uses default account)
|
| 326 |
+
entity: null
|
| 327 |
+
|
| 328 |
+
# Tags to help organize runs
|
| 329 |
+
tags: [ "ltx2", "lora", "audio-extension", "audio-only" ]
|
| 330 |
+
|
| 331 |
+
# Log validation media (video/audio) to W&B
|
| 332 |
+
log_validation_videos: true
|
| 333 |
+
|
| 334 |
+
# -----------------------------------------------------------------------------
|
| 335 |
+
# General Configuration
|
| 336 |
+
# -----------------------------------------------------------------------------
|
| 337 |
+
# Global settings for the training run.
|
| 338 |
+
|
| 339 |
+
# Random seed for reproducibility
|
| 340 |
+
seed: 42
|
| 341 |
+
|
| 342 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 343 |
+
output_dir: "outputs/audio_extend_lora"
|
packages/ltx-trainer/configs/audio_inpainting_lora.yaml
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 Audio Inpainting LoRA Training Configuration
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This configuration is for training LoRA adapters on the LTX-2 model for
|
| 6 |
+
# audio inpainting. The model learns to fill in masked regions of audio
|
| 7 |
+
# using per-sample binary masks that define which regions are conditioning
|
| 8 |
+
# (provided clean) and which regions the model must generate.
|
| 9 |
+
#
|
| 10 |
+
# Mask conditioning works by loading per-sample binary masks from disk.
|
| 11 |
+
# Masked regions receive clean latents (no noise, timestep=0) and are excluded
|
| 12 |
+
# from the loss. Unmasked regions are noised and trained normally.
|
| 13 |
+
#
|
| 14 |
+
# This is an audio-only training mode — no video modality is configured.
|
| 15 |
+
#
|
| 16 |
+
# Use this configuration when you want to:
|
| 17 |
+
# - Train the model to fill in or replace regions of existing audio
|
| 18 |
+
# - Fine-tune audio inpainting capabilities on custom datasets
|
| 19 |
+
# - Create audio restoration or editing models
|
| 20 |
+
#
|
| 21 |
+
# Dataset structure:
|
| 22 |
+
# preprocessed_data_root/
|
| 23 |
+
# ├── conditions/ # Text embeddings for each sample
|
| 24 |
+
# ├── audio_latents/ # Audio latents (VAE-encoded audio)
|
| 25 |
+
# └── audio_masks/ # Per-sample binary masks defining conditioning regions
|
| 26 |
+
#
|
| 27 |
+
# Dataset metadata columns: audio, audio_mask, caption
|
| 28 |
+
#
|
| 29 |
+
# =============================================================================
|
| 30 |
+
|
| 31 |
+
# -----------------------------------------------------------------------------
|
| 32 |
+
# Model Configuration
|
| 33 |
+
# -----------------------------------------------------------------------------
|
| 34 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 35 |
+
model:
|
| 36 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 37 |
+
# This should be a local path to your downloaded model
|
| 38 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 39 |
+
|
| 40 |
+
# Path to the text encoder model directory
|
| 41 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 42 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 43 |
+
|
| 44 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 45 |
+
# LoRA is recommended for most use cases (faster, less memory, prevents overfitting)
|
| 46 |
+
training_mode: "lora"
|
| 47 |
+
|
| 48 |
+
# Optional: Path to resume training from a checkpoint
|
| 49 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 50 |
+
load_checkpoint: null
|
| 51 |
+
|
| 52 |
+
# -----------------------------------------------------------------------------
|
| 53 |
+
# LoRA Configuration
|
| 54 |
+
# -----------------------------------------------------------------------------
|
| 55 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 56 |
+
lora:
|
| 57 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 58 |
+
# Typical values: 8, 16, 32, 64. Start with 32 for audio inpainting LoRA training.
|
| 59 |
+
rank: 32
|
| 60 |
+
|
| 61 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 62 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 63 |
+
alpha: 32
|
| 64 |
+
|
| 65 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 66 |
+
# Can help with regularization if overfitting occurs
|
| 67 |
+
dropout: 0.0
|
| 68 |
+
|
| 69 |
+
# For audio-only inpainting, we explicitly target audio modules.
|
| 70 |
+
# Including audio FFN layers can increase the LoRA's capacity.
|
| 71 |
+
target_modules:
|
| 72 |
+
# Audio self-attention
|
| 73 |
+
- "audio_attn1.to_k"
|
| 74 |
+
- "audio_attn1.to_q"
|
| 75 |
+
- "audio_attn1.to_v"
|
| 76 |
+
- "audio_attn1.to_out.0"
|
| 77 |
+
# Audio cross-attention to text
|
| 78 |
+
- "audio_attn2.to_k"
|
| 79 |
+
- "audio_attn2.to_q"
|
| 80 |
+
- "audio_attn2.to_v"
|
| 81 |
+
- "audio_attn2.to_out.0"
|
| 82 |
+
# Audio feed-forward (often improves transformation quality)
|
| 83 |
+
- "audio_ff.net.0.proj"
|
| 84 |
+
- "audio_ff.net.2"
|
| 85 |
+
|
| 86 |
+
# -----------------------------------------------------------------------------
|
| 87 |
+
# Training Strategy Configuration
|
| 88 |
+
# -----------------------------------------------------------------------------
|
| 89 |
+
# Defines the audio inpainting training approach using the unified flexible
|
| 90 |
+
# strategy. Per-sample binary masks define which audio regions are provided
|
| 91 |
+
# as clean conditioning and which regions the model must learn to generate.
|
| 92 |
+
training_strategy:
|
| 93 |
+
name: "flexible"
|
| 94 |
+
|
| 95 |
+
# Audio modality configuration (audio-only, no video)
|
| 96 |
+
audio:
|
| 97 |
+
# Whether the model generates audio (true) or uses it as frozen conditioning (false)
|
| 98 |
+
is_generated: true
|
| 99 |
+
# Directory name (within preprocessed_data_root) containing audio latents
|
| 100 |
+
latents_dir: "audio_latents"
|
| 101 |
+
|
| 102 |
+
# Conditions applied to the audio modality during training
|
| 103 |
+
conditions:
|
| 104 |
+
- type: mask
|
| 105 |
+
# Directory name (within preprocessed_data_root) containing binary masks
|
| 106 |
+
# Each mask file corresponds to a training sample and defines the
|
| 107 |
+
# conditioning region (mask=1 means conditioning, mask=0 means generate)
|
| 108 |
+
mask_dir: "audio_masks"
|
| 109 |
+
# Probability of applying mask conditioning per training sample
|
| 110 |
+
# At 1.0, all training samples use inpainting mode
|
| 111 |
+
probability: 1.0
|
| 112 |
+
|
| 113 |
+
# -----------------------------------------------------------------------------
|
| 114 |
+
# Optimization Configuration
|
| 115 |
+
# -----------------------------------------------------------------------------
|
| 116 |
+
# Controls the training optimization parameters.
|
| 117 |
+
optimization:
|
| 118 |
+
# Learning rate for the optimizer
|
| 119 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 120 |
+
learning_rate: 2e-4
|
| 121 |
+
|
| 122 |
+
# Total number of training steps
|
| 123 |
+
steps: 3000
|
| 124 |
+
|
| 125 |
+
# Batch size per GPU
|
| 126 |
+
# Reduce if running out of memory
|
| 127 |
+
batch_size: 1
|
| 128 |
+
|
| 129 |
+
# Number of gradient accumulation steps
|
| 130 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 131 |
+
gradient_accumulation_steps: 1
|
| 132 |
+
|
| 133 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 134 |
+
max_grad_norm: 1.0
|
| 135 |
+
|
| 136 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 137 |
+
optimizer_type: "adamw"
|
| 138 |
+
|
| 139 |
+
# Learning rate scheduler type
|
| 140 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 141 |
+
scheduler_type: "linear"
|
| 142 |
+
|
| 143 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 144 |
+
scheduler_params: { }
|
| 145 |
+
|
| 146 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 147 |
+
# Recommended for training with limited GPU memory
|
| 148 |
+
enable_gradient_checkpointing: true
|
| 149 |
+
|
| 150 |
+
# -----------------------------------------------------------------------------
|
| 151 |
+
# Acceleration Configuration
|
| 152 |
+
# -----------------------------------------------------------------------------
|
| 153 |
+
# Hardware acceleration and memory optimization settings.
|
| 154 |
+
acceleration:
|
| 155 |
+
# Mixed precision training mode
|
| 156 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 157 |
+
mixed_precision_mode: "bf16"
|
| 158 |
+
|
| 159 |
+
# Model quantization for reduced memory usage
|
| 160 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 161 |
+
quantization: null
|
| 162 |
+
|
| 163 |
+
# Load text encoder in 8-bit precision to save memory
|
| 164 |
+
# Useful when GPU memory is limited
|
| 165 |
+
load_text_encoder_in_8bit: false
|
| 166 |
+
|
| 167 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 168 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 169 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 170 |
+
offload_optimizer_during_validation: false
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
# -----------------------------------------------------------------------------
|
| 174 |
+
# Data Configuration
|
| 175 |
+
# -----------------------------------------------------------------------------
|
| 176 |
+
# Specifies the training data location and loading parameters.
|
| 177 |
+
data:
|
| 178 |
+
# Root directory containing preprocessed training data
|
| 179 |
+
# Should contain: conditions/, audio_latents/, and audio_masks/ subdirectories
|
| 180 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 181 |
+
|
| 182 |
+
# Number of worker processes for data loading
|
| 183 |
+
# Used for parallel data loading to speed up data loading
|
| 184 |
+
num_dataloader_workers: 2
|
| 185 |
+
|
| 186 |
+
# -----------------------------------------------------------------------------
|
| 187 |
+
# Validation Configuration
|
| 188 |
+
# -----------------------------------------------------------------------------
|
| 189 |
+
# Controls validation sampling during training.
|
| 190 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 191 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 192 |
+
validation:
|
| 193 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 194 |
+
# For audio inpainting, each sample includes mask conditioning (audio + mask paths).
|
| 195 |
+
samples:
|
| 196 |
+
- prompt: >-
|
| 197 |
+
A warm and soothing piano melody with soft ambient textures, reminiscent of a quiet
|
| 198 |
+
evening by the fireplace. Gentle reverb creates a sense of intimate space.
|
| 199 |
+
conditions:
|
| 200 |
+
- type: mask
|
| 201 |
+
audio: "/path/to/inpainting_audio_1.wav"
|
| 202 |
+
mask: "/path/to/inpainting_mask_1.pt"
|
| 203 |
+
- prompt: >-
|
| 204 |
+
Energetic electronic beats with pulsing synthesizer leads and crisp hi-hat patterns.
|
| 205 |
+
Deep bass tones provide a driving rhythm underneath bright melodic arpeggios.
|
| 206 |
+
conditions:
|
| 207 |
+
- type: mask
|
| 208 |
+
audio: "/path/to/inpainting_audio_2.wav"
|
| 209 |
+
mask: "/path/to/inpainting_mask_2.pt"
|
| 210 |
+
|
| 211 |
+
# Negative prompt to avoid unwanted artifacts
|
| 212 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 213 |
+
|
| 214 |
+
# Generation length control [width, height, frames]
|
| 215 |
+
# With generate_video=false, width/height are unused; frames and frame_rate set audio duration.
|
| 216 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 217 |
+
video_dims: [ 512, 512, 81 ]
|
| 218 |
+
|
| 219 |
+
# Frame rate for generated videos
|
| 220 |
+
frame_rate: 25.0
|
| 221 |
+
|
| 222 |
+
# Random seed for reproducible validation outputs
|
| 223 |
+
seed: 42
|
| 224 |
+
|
| 225 |
+
# Number of denoising steps for validation inference
|
| 226 |
+
# Higher values = better quality but slower generation
|
| 227 |
+
inference_steps: 30
|
| 228 |
+
|
| 229 |
+
# Generate validation videos every N training steps
|
| 230 |
+
# Set to null to disable validation during training
|
| 231 |
+
interval: 100
|
| 232 |
+
|
| 233 |
+
# Classifier-free guidance scale
|
| 234 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 235 |
+
guidance_scale: 4.0
|
| 236 |
+
|
| 237 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 238 |
+
# STG is combined with CFG for better temporal coherence
|
| 239 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 240 |
+
stg_blocks: [29] # Recommended: single block 29
|
| 241 |
+
stg_mode: "stg_av" # "stg_av" skips both audio and video self-attention (suited to audio validation)
|
| 242 |
+
|
| 243 |
+
# Whether to generate audio in validation samples
|
| 244 |
+
# Enabled because this audio-only config generates audio
|
| 245 |
+
generate_audio: true
|
| 246 |
+
|
| 247 |
+
# Whether to generate video in validation samples
|
| 248 |
+
# Disabled because no video modality is configured
|
| 249 |
+
generate_video: false
|
| 250 |
+
|
| 251 |
+
# Skip validation at the beginning of training (step 0)
|
| 252 |
+
skip_initial_validation: false
|
| 253 |
+
|
| 254 |
+
# -----------------------------------------------------------------------------
|
| 255 |
+
# Checkpoint Configuration
|
| 256 |
+
# -----------------------------------------------------------------------------
|
| 257 |
+
# Controls model checkpoint saving during training.
|
| 258 |
+
checkpoints:
|
| 259 |
+
# Save a checkpoint every N steps
|
| 260 |
+
# Set to null to disable intermediate checkpoints
|
| 261 |
+
interval: 250
|
| 262 |
+
|
| 263 |
+
# Number of most recent checkpoints to keep
|
| 264 |
+
# Set to -1 to keep all checkpoints
|
| 265 |
+
keep_last_n: 3
|
| 266 |
+
|
| 267 |
+
# Precision to use when saving checkpoint weights
|
| 268 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 269 |
+
precision: "bfloat16"
|
| 270 |
+
|
| 271 |
+
# -----------------------------------------------------------------------------
|
| 272 |
+
# Flow Matching Configuration
|
| 273 |
+
# -----------------------------------------------------------------------------
|
| 274 |
+
# Parameters for the flow matching training objective.
|
| 275 |
+
flow_matching:
|
| 276 |
+
# Timestep sampling mode
|
| 277 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 278 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 279 |
+
|
| 280 |
+
# Additional parameters for timestep sampling
|
| 281 |
+
timestep_sampling_params: { }
|
| 282 |
+
|
| 283 |
+
# -----------------------------------------------------------------------------
|
| 284 |
+
# Hugging Face Hub Configuration
|
| 285 |
+
# -----------------------------------------------------------------------------
|
| 286 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 287 |
+
hub:
|
| 288 |
+
# Whether to push the trained model to the Hub
|
| 289 |
+
push_to_hub: false
|
| 290 |
+
|
| 291 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-ic-lora-model")
|
| 292 |
+
# Required if push_to_hub is true
|
| 293 |
+
hub_model_id: null
|
| 294 |
+
|
| 295 |
+
# -----------------------------------------------------------------------------
|
| 296 |
+
# Weights & Biases Configuration
|
| 297 |
+
# -----------------------------------------------------------------------------
|
| 298 |
+
# Settings for experiment tracking with W&B.
|
| 299 |
+
wandb:
|
| 300 |
+
# Enable W&B logging
|
| 301 |
+
enabled: false
|
| 302 |
+
|
| 303 |
+
# W&B project name
|
| 304 |
+
project: "ltx-2-trainer"
|
| 305 |
+
|
| 306 |
+
# W&B username or team (null uses default account)
|
| 307 |
+
entity: null
|
| 308 |
+
|
| 309 |
+
# Tags to help organize runs
|
| 310 |
+
tags: [ "ltx2", "lora", "audio-inpainting", "audio-only" ]
|
| 311 |
+
|
| 312 |
+
# Log validation media (video/audio) to W&B
|
| 313 |
+
log_validation_videos: true
|
| 314 |
+
|
| 315 |
+
# -----------------------------------------------------------------------------
|
| 316 |
+
# General Configuration
|
| 317 |
+
# -----------------------------------------------------------------------------
|
| 318 |
+
# Global settings for the training run.
|
| 319 |
+
|
| 320 |
+
# Random seed for reproducibility
|
| 321 |
+
seed: 42
|
| 322 |
+
|
| 323 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 324 |
+
output_dir: "outputs/audio_inpainting_lora"
|
packages/ltx-trainer/configs/audio_suffix_lora.yaml
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 Audio Extension (Backward) LoRA Training Configuration
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This configuration is for training LoRA adapters on the LTX-2 model for
|
| 6 |
+
# backward audio extension. The model learns to generate audio content leading into existing audio
|
| 7 |
+
# by conditioning on a suffix of existing audio latent timesteps.
|
| 8 |
+
#
|
| 9 |
+
# Suffix conditioning works by providing the last N audio latent timesteps as
|
| 10 |
+
# clean conditioning signals during training — they receive no noise,
|
| 11 |
+
# timestep=0, and are excluded from the loss. The model learns to generate
|
| 12 |
+
# audio that seamlessly leads into the given suffix.
|
| 13 |
+
#
|
| 14 |
+
# This is an audio-only training mode — no video modality is configured.
|
| 15 |
+
#
|
| 16 |
+
# Use this configuration when you want to:
|
| 17 |
+
# - Train the model to generate preceding content for existing audio
|
| 18 |
+
# - Fine-tune audio temporal continuation capabilities
|
| 19 |
+
# - Create seamless audio extension models
|
| 20 |
+
#
|
| 21 |
+
# Dataset structure:
|
| 22 |
+
# preprocessed_data_root/
|
| 23 |
+
# ├── conditions/ # Text embeddings for each sample
|
| 24 |
+
# └── audio_latents/ # Audio latents (VAE-encoded audio)
|
| 25 |
+
#
|
| 26 |
+
# =============================================================================
|
| 27 |
+
|
| 28 |
+
# -----------------------------------------------------------------------------
|
| 29 |
+
# Model Configuration
|
| 30 |
+
# -----------------------------------------------------------------------------
|
| 31 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 32 |
+
model:
|
| 33 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 34 |
+
# This should be a local path to your downloaded model
|
| 35 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 36 |
+
|
| 37 |
+
# Path to the text encoder model directory
|
| 38 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 39 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 40 |
+
|
| 41 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 42 |
+
# LoRA is recommended for most use cases (faster, less memory, prevents overfitting)
|
| 43 |
+
training_mode: "lora"
|
| 44 |
+
|
| 45 |
+
# Optional: Path to resume training from a checkpoint
|
| 46 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 47 |
+
load_checkpoint: null
|
| 48 |
+
|
| 49 |
+
# -----------------------------------------------------------------------------
|
| 50 |
+
# LoRA Configuration
|
| 51 |
+
# -----------------------------------------------------------------------------
|
| 52 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 53 |
+
lora:
|
| 54 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 55 |
+
# Typical values: 8, 16, 32, 64. Start with 32 for audio extension LoRA training.
|
| 56 |
+
rank: 32
|
| 57 |
+
|
| 58 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 59 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 60 |
+
alpha: 32
|
| 61 |
+
|
| 62 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 63 |
+
# Can help with regularization if overfitting occurs
|
| 64 |
+
dropout: 0.0
|
| 65 |
+
|
| 66 |
+
# Which transformer modules to apply LoRA to
|
| 67 |
+
# The LTX-2 transformer has separate attention and FFN blocks for video and audio:
|
| 68 |
+
#
|
| 69 |
+
# VIDEO MODULES (not used for audio-only modes):
|
| 70 |
+
# - attn1.to_k, attn1.to_q, attn1.to_v, attn1.to_out.0 (video self-attention)
|
| 71 |
+
# - attn2.to_k, attn2.to_q, attn2.to_v, attn2.to_out.0 (video cross-attention to text)
|
| 72 |
+
# - ff.net.0.proj, ff.net.2 (video feed-forward)
|
| 73 |
+
#
|
| 74 |
+
# AUDIO MODULES:
|
| 75 |
+
# - audio_attn1.to_k, audio_attn1.to_q, audio_attn1.to_v, audio_attn1.to_out.0 (audio self-attention)
|
| 76 |
+
# - audio_attn2.to_k, audio_attn2.to_q, audio_attn2.to_v, audio_attn2.to_out.0 (audio cross-attention to text)
|
| 77 |
+
# - audio_ff.net.0.proj, audio_ff.net.2 (audio feed-forward)
|
| 78 |
+
#
|
| 79 |
+
# AUDIO-VIDEO CROSS-ATTENTION MODULES (not used for audio-only modes):
|
| 80 |
+
# - audio_to_video_attn.to_k, audio_to_video_attn.to_q, audio_to_video_attn.to_v, audio_to_video_attn.to_out.0
|
| 81 |
+
# (Q from video, K/V from audio - allows video to attend to audio features)
|
| 82 |
+
# - video_to_audio_attn.to_k, video_to_audio_attn.to_q, video_to_audio_attn.to_v, video_to_audio_attn.to_out.0
|
| 83 |
+
# (Q from audio, K/V from video - allows audio to attend to video features)
|
| 84 |
+
#
|
| 85 |
+
# For audio-only extension, we explicitly target audio modules.
|
| 86 |
+
# Including audio FFN layers can increase the LoRA's capacity.
|
| 87 |
+
target_modules:
|
| 88 |
+
# Audio self-attention
|
| 89 |
+
- "audio_attn1.to_k"
|
| 90 |
+
- "audio_attn1.to_q"
|
| 91 |
+
- "audio_attn1.to_v"
|
| 92 |
+
- "audio_attn1.to_out.0"
|
| 93 |
+
# Audio cross-attention to text
|
| 94 |
+
- "audio_attn2.to_k"
|
| 95 |
+
- "audio_attn2.to_q"
|
| 96 |
+
- "audio_attn2.to_v"
|
| 97 |
+
- "audio_attn2.to_out.0"
|
| 98 |
+
# Audio feed-forward (often improves transformation quality)
|
| 99 |
+
- "audio_ff.net.0.proj"
|
| 100 |
+
- "audio_ff.net.2"
|
| 101 |
+
|
| 102 |
+
# -----------------------------------------------------------------------------
|
| 103 |
+
# Training Strategy Configuration
|
| 104 |
+
# -----------------------------------------------------------------------------
|
| 105 |
+
# Defines the backward audio extension training approach using the unified flexible
|
| 106 |
+
# strategy. Suffix conditioning provides the last N audio latent timesteps as
|
| 107 |
+
# clean conditioning, teaching the model to generate content leading into existing audio.
|
| 108 |
+
training_strategy:
|
| 109 |
+
name: "flexible"
|
| 110 |
+
|
| 111 |
+
# Audio modality configuration (audio-only, no video)
|
| 112 |
+
audio:
|
| 113 |
+
# Whether the model generates audio (true) or uses it as frozen conditioning (false)
|
| 114 |
+
is_generated: true
|
| 115 |
+
# Directory name (within preprocessed_data_root) containing audio latents
|
| 116 |
+
latents_dir: "audio_latents"
|
| 117 |
+
|
| 118 |
+
# Conditions applied to the audio modality during training
|
| 119 |
+
conditions:
|
| 120 |
+
- type: suffix
|
| 121 |
+
# Number of audio latent timesteps to use as conditioning suffix
|
| 122 |
+
# Each audio latent timestep = 1 patchified token
|
| 123 |
+
temporal_boundary: 8
|
| 124 |
+
# Probability of applying suffix conditioning per training sample
|
| 125 |
+
# At 1.0, all training samples use audio extension mode
|
| 126 |
+
probability: 1.0
|
| 127 |
+
|
| 128 |
+
# -----------------------------------------------------------------------------
|
| 129 |
+
# Optimization Configuration
|
| 130 |
+
# -----------------------------------------------------------------------------
|
| 131 |
+
# Controls the training optimization parameters.
|
| 132 |
+
optimization:
|
| 133 |
+
# Learning rate for the optimizer
|
| 134 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 135 |
+
learning_rate: 2e-4
|
| 136 |
+
|
| 137 |
+
# Total number of training steps
|
| 138 |
+
steps: 3000
|
| 139 |
+
|
| 140 |
+
# Batch size per GPU
|
| 141 |
+
# Reduce if running out of memory
|
| 142 |
+
batch_size: 1
|
| 143 |
+
|
| 144 |
+
# Number of gradient accumulation steps
|
| 145 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 146 |
+
gradient_accumulation_steps: 1
|
| 147 |
+
|
| 148 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 149 |
+
max_grad_norm: 1.0
|
| 150 |
+
|
| 151 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 152 |
+
optimizer_type: "adamw"
|
| 153 |
+
|
| 154 |
+
# Learning rate scheduler type
|
| 155 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 156 |
+
scheduler_type: "linear"
|
| 157 |
+
|
| 158 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 159 |
+
scheduler_params: { }
|
| 160 |
+
|
| 161 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 162 |
+
# Recommended for training with limited GPU memory
|
| 163 |
+
enable_gradient_checkpointing: true
|
| 164 |
+
|
| 165 |
+
# -----------------------------------------------------------------------------
|
| 166 |
+
# Acceleration Configuration
|
| 167 |
+
# -----------------------------------------------------------------------------
|
| 168 |
+
# Hardware acceleration and memory optimization settings.
|
| 169 |
+
acceleration:
|
| 170 |
+
# Mixed precision training mode
|
| 171 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 172 |
+
mixed_precision_mode: "bf16"
|
| 173 |
+
|
| 174 |
+
# Model quantization for reduced memory usage
|
| 175 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 176 |
+
quantization: null
|
| 177 |
+
|
| 178 |
+
# Load text encoder in 8-bit precision to save memory
|
| 179 |
+
# Useful when GPU memory is limited
|
| 180 |
+
load_text_encoder_in_8bit: false
|
| 181 |
+
|
| 182 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 183 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 184 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 185 |
+
offload_optimizer_during_validation: false
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
# -----------------------------------------------------------------------------
|
| 189 |
+
# Data Configuration
|
| 190 |
+
# -----------------------------------------------------------------------------
|
| 191 |
+
# Specifies the training data location and loading parameters.
|
| 192 |
+
data:
|
| 193 |
+
# Root directory containing preprocessed training data
|
| 194 |
+
# Should contain: conditions/ and audio_latents/ subdirectories
|
| 195 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 196 |
+
|
| 197 |
+
# Number of worker processes for data loading
|
| 198 |
+
# Used for parallel data loading to speed up data loading
|
| 199 |
+
num_dataloader_workers: 2
|
| 200 |
+
|
| 201 |
+
# -----------------------------------------------------------------------------
|
| 202 |
+
# Validation Configuration
|
| 203 |
+
# -----------------------------------------------------------------------------
|
| 204 |
+
# Controls validation sampling during training.
|
| 205 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 206 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 207 |
+
validation:
|
| 208 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 209 |
+
# Each sample includes an audio suffix condition for backward audio extension.
|
| 210 |
+
samples:
|
| 211 |
+
- prompt: >-
|
| 212 |
+
A warm and soothing piano melody with soft ambient textures, reminiscent of a quiet
|
| 213 |
+
evening by the fireplace. Gentle reverb creates a sense of intimate space.
|
| 214 |
+
conditions:
|
| 215 |
+
- type: suffix
|
| 216 |
+
audio: "/path/to/suffix_audio_1.wav"
|
| 217 |
+
# Duration is in seconds; choose a value that covers a comparable audio context
|
| 218 |
+
# to the training temporal_boundary measured in audio latent timesteps.
|
| 219 |
+
duration: 1.0
|
| 220 |
+
- prompt: >-
|
| 221 |
+
Energetic electronic beats with pulsing synthesizer leads and crisp hi-hat patterns.
|
| 222 |
+
Deep bass tones provide a driving rhythm underneath bright melodic arpeggios.
|
| 223 |
+
conditions:
|
| 224 |
+
- type: suffix
|
| 225 |
+
audio: "/path/to/suffix_audio_2.wav"
|
| 226 |
+
# Duration is in seconds; choose a value that covers a comparable audio context
|
| 227 |
+
# to the training temporal_boundary measured in audio latent timesteps.
|
| 228 |
+
duration: 1.0
|
| 229 |
+
|
| 230 |
+
# Negative prompt to avoid unwanted artifacts
|
| 231 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 232 |
+
|
| 233 |
+
# Generation length control [width, height, frames]
|
| 234 |
+
# With generate_video=false, width/height are unused; frames and frame_rate set audio duration.
|
| 235 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 236 |
+
video_dims: [ 512, 512, 81 ]
|
| 237 |
+
|
| 238 |
+
# Frame rate for generated videos
|
| 239 |
+
frame_rate: 25.0
|
| 240 |
+
|
| 241 |
+
# Random seed for reproducible validation outputs
|
| 242 |
+
seed: 42
|
| 243 |
+
|
| 244 |
+
# Number of denoising steps for validation inference
|
| 245 |
+
# Higher values = better quality but slower generation
|
| 246 |
+
inference_steps: 30
|
| 247 |
+
|
| 248 |
+
# Generate validation videos every N training steps
|
| 249 |
+
# Set to null to disable validation during training
|
| 250 |
+
interval: 100
|
| 251 |
+
|
| 252 |
+
# Classifier-free guidance scale
|
| 253 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 254 |
+
guidance_scale: 4.0
|
| 255 |
+
|
| 256 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 257 |
+
# STG is combined with CFG for better temporal coherence
|
| 258 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 259 |
+
stg_blocks: [29] # Recommended: single block 29
|
| 260 |
+
stg_mode: "stg_av" # "stg_av" skips both audio and video self-attention (suited to audio validation)
|
| 261 |
+
|
| 262 |
+
# Whether to generate audio in validation samples
|
| 263 |
+
# Enabled because this audio-only config generates audio
|
| 264 |
+
generate_audio: true
|
| 265 |
+
|
| 266 |
+
# Whether to generate video in validation samples
|
| 267 |
+
# Disabled because no video modality is configured
|
| 268 |
+
generate_video: false
|
| 269 |
+
|
| 270 |
+
# Skip validation at the beginning of training (step 0)
|
| 271 |
+
skip_initial_validation: false
|
| 272 |
+
|
| 273 |
+
# -----------------------------------------------------------------------------
|
| 274 |
+
# Checkpoint Configuration
|
| 275 |
+
# -----------------------------------------------------------------------------
|
| 276 |
+
# Controls model checkpoint saving during training.
|
| 277 |
+
checkpoints:
|
| 278 |
+
# Save a checkpoint every N steps
|
| 279 |
+
# Set to null to disable intermediate checkpoints
|
| 280 |
+
interval: 250
|
| 281 |
+
|
| 282 |
+
# Number of most recent checkpoints to keep
|
| 283 |
+
# Set to -1 to keep all checkpoints
|
| 284 |
+
keep_last_n: 3
|
| 285 |
+
|
| 286 |
+
# Precision to use when saving checkpoint weights
|
| 287 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 288 |
+
precision: "bfloat16"
|
| 289 |
+
|
| 290 |
+
# -----------------------------------------------------------------------------
|
| 291 |
+
# Flow Matching Configuration
|
| 292 |
+
# -----------------------------------------------------------------------------
|
| 293 |
+
# Parameters for the flow matching training objective.
|
| 294 |
+
flow_matching:
|
| 295 |
+
# Timestep sampling mode
|
| 296 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 297 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 298 |
+
|
| 299 |
+
# Additional parameters for timestep sampling
|
| 300 |
+
timestep_sampling_params: { }
|
| 301 |
+
|
| 302 |
+
# -----------------------------------------------------------------------------
|
| 303 |
+
# Hugging Face Hub Configuration
|
| 304 |
+
# -----------------------------------------------------------------------------
|
| 305 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 306 |
+
hub:
|
| 307 |
+
# Whether to push the trained model to the Hub
|
| 308 |
+
push_to_hub: false
|
| 309 |
+
|
| 310 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-ic-lora-model")
|
| 311 |
+
# Required if push_to_hub is true
|
| 312 |
+
hub_model_id: null
|
| 313 |
+
|
| 314 |
+
# -----------------------------------------------------------------------------
|
| 315 |
+
# Weights & Biases Configuration
|
| 316 |
+
# -----------------------------------------------------------------------------
|
| 317 |
+
# Settings for experiment tracking with W&B.
|
| 318 |
+
wandb:
|
| 319 |
+
# Enable W&B logging
|
| 320 |
+
enabled: false
|
| 321 |
+
|
| 322 |
+
# W&B project name
|
| 323 |
+
project: "ltx-2-trainer"
|
| 324 |
+
|
| 325 |
+
# W&B username or team (null uses default account)
|
| 326 |
+
entity: null
|
| 327 |
+
|
| 328 |
+
# Tags to help organize runs
|
| 329 |
+
tags: [ "ltx2", "lora", "audio-suffix", "audio-only" ]
|
| 330 |
+
|
| 331 |
+
# Log validation media (video/audio) to W&B
|
| 332 |
+
log_validation_videos: true
|
| 333 |
+
|
| 334 |
+
# -----------------------------------------------------------------------------
|
| 335 |
+
# General Configuration
|
| 336 |
+
# -----------------------------------------------------------------------------
|
| 337 |
+
# Global settings for the training run.
|
| 338 |
+
|
| 339 |
+
# Random seed for reproducibility
|
| 340 |
+
seed: 42
|
| 341 |
+
|
| 342 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 343 |
+
output_dir: "outputs/audio_suffix_lora"
|
packages/ltx-trainer/configs/av2av_ic_lora.yaml
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 AV2AV IC-LoRA Training Configuration
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This configuration is for training In-Context LoRA (IC-LoRA) adapters that
|
| 6 |
+
# enable joint audio-video-to-audio-video transformations. IC-LoRA learns to
|
| 7 |
+
# apply transformations to both the video and audio modalities simultaneously
|
| 8 |
+
# by conditioning on paired reference video and audio.
|
| 9 |
+
#
|
| 10 |
+
# Both modalities use reference conditioning: pre-encoded reference latents
|
| 11 |
+
# are concatenated to each modality's target sequence. Reference tokens
|
| 12 |
+
# participate in bidirectional self-attention but receive no noise and are
|
| 13 |
+
# excluded from the loss.
|
| 14 |
+
#
|
| 15 |
+
# Key differences from video-only IC-LoRA (v2v_ic_lora.yaml):
|
| 16 |
+
# - Both video AND audio have reference conditions
|
| 17 |
+
# - Requires preprocessed reference latents for BOTH modalities
|
| 18 |
+
# - LoRA targets all modules (video, audio, and cross-modal attention)
|
| 19 |
+
# - Validation uses both video and audio reference conditions
|
| 20 |
+
#
|
| 21 |
+
# Dataset structure:
|
| 22 |
+
# preprocessed_data_root/
|
| 23 |
+
# ├── latents/ # Target video latents
|
| 24 |
+
# ├── audio_latents/ # Target audio latents
|
| 25 |
+
# ├── conditions/ # Text embeddings
|
| 26 |
+
# ├── reference_latents/ # Reference video latents (conditioning input)
|
| 27 |
+
# └── reference_audio_latents/ # Reference audio latents (conditioning input)
|
| 28 |
+
#
|
| 29 |
+
# Dataset metadata columns: video, audio, reference_video, reference_audio, caption
|
| 30 |
+
#
|
| 31 |
+
# =============================================================================
|
| 32 |
+
|
| 33 |
+
# -----------------------------------------------------------------------------
|
| 34 |
+
# Model Configuration
|
| 35 |
+
# -----------------------------------------------------------------------------
|
| 36 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 37 |
+
model:
|
| 38 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 39 |
+
# This should be a local path to your downloaded model
|
| 40 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 41 |
+
|
| 42 |
+
# Path to the text encoder model directory
|
| 43 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 44 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 45 |
+
|
| 46 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 47 |
+
# IC-LoRA reference conditioning is intended for LoRA adapter training.
|
| 48 |
+
training_mode: "lora"
|
| 49 |
+
|
| 50 |
+
# Optional: Path to resume training from a checkpoint
|
| 51 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 52 |
+
load_checkpoint: null
|
| 53 |
+
|
| 54 |
+
# -----------------------------------------------------------------------------
|
| 55 |
+
# LoRA Configuration
|
| 56 |
+
# -----------------------------------------------------------------------------
|
| 57 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 58 |
+
lora:
|
| 59 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 60 |
+
# Typical values: 8, 16, 32, 64. Start with 16-32 for IC-LoRA.
|
| 61 |
+
rank: 32
|
| 62 |
+
|
| 63 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 64 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 65 |
+
alpha: 32
|
| 66 |
+
|
| 67 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 68 |
+
# Can help with regularization if overfitting occurs
|
| 69 |
+
dropout: 0.0
|
| 70 |
+
|
| 71 |
+
# For AV2AV IC-LoRA, we target ALL modules — video, audio, and cross-modal attention.
|
| 72 |
+
# Using short patterns matches all branches simultaneously.
|
| 73 |
+
target_modules:
|
| 74 |
+
# Attention layers (matches video, audio, and cross-modal branches)
|
| 75 |
+
- "to_k"
|
| 76 |
+
- "to_q"
|
| 77 |
+
- "to_v"
|
| 78 |
+
- "to_out.0"
|
| 79 |
+
|
| 80 |
+
# -----------------------------------------------------------------------------
|
| 81 |
+
# Training Strategy Configuration
|
| 82 |
+
# -----------------------------------------------------------------------------
|
| 83 |
+
# Defines the AV2AV IC-LoRA training approach using the unified flexible
|
| 84 |
+
# strategy. Both video and audio modalities have reference conditioning,
|
| 85 |
+
# enabling joint audiovisual transformations.
|
| 86 |
+
training_strategy:
|
| 87 |
+
name: "flexible"
|
| 88 |
+
|
| 89 |
+
# Video modality configuration
|
| 90 |
+
video:
|
| 91 |
+
# Whether the model generates video (true) or uses it as frozen conditioning (false)
|
| 92 |
+
is_generated: true
|
| 93 |
+
# Directory name (within preprocessed_data_root) containing target video latents
|
| 94 |
+
latents_dir: "latents"
|
| 95 |
+
|
| 96 |
+
# Conditions applied to the video modality during training
|
| 97 |
+
conditions:
|
| 98 |
+
# Reference conditioning (IC-LoRA): concatenates pre-encoded reference video
|
| 99 |
+
# latents to the target sequence
|
| 100 |
+
- type: reference
|
| 101 |
+
latents_dir: "reference_latents"
|
| 102 |
+
probability: 1.0
|
| 103 |
+
|
| 104 |
+
# Audio modality configuration
|
| 105 |
+
audio:
|
| 106 |
+
# Whether the model generates audio (true) or uses it as frozen conditioning (false)
|
| 107 |
+
is_generated: true
|
| 108 |
+
# Directory name (within preprocessed_data_root) containing target audio latents
|
| 109 |
+
latents_dir: "audio_latents"
|
| 110 |
+
|
| 111 |
+
# Conditions applied to the audio modality during training
|
| 112 |
+
conditions:
|
| 113 |
+
# Reference conditioning (IC-LoRA): concatenates pre-encoded reference audio
|
| 114 |
+
# latents to the target sequence
|
| 115 |
+
- type: reference
|
| 116 |
+
latents_dir: "reference_audio_latents"
|
| 117 |
+
probability: 1.0
|
| 118 |
+
|
| 119 |
+
# -----------------------------------------------------------------------------
|
| 120 |
+
# Optimization Configuration
|
| 121 |
+
# -----------------------------------------------------------------------------
|
| 122 |
+
# Controls the training optimization parameters.
|
| 123 |
+
optimization:
|
| 124 |
+
# Learning rate for the optimizer
|
| 125 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 126 |
+
learning_rate: 2e-4
|
| 127 |
+
|
| 128 |
+
# Total number of training steps
|
| 129 |
+
steps: 3000
|
| 130 |
+
|
| 131 |
+
# Batch size per GPU
|
| 132 |
+
# Reduce if running out of memory
|
| 133 |
+
batch_size: 1
|
| 134 |
+
|
| 135 |
+
# Number of gradient accumulation steps
|
| 136 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 137 |
+
gradient_accumulation_steps: 1
|
| 138 |
+
|
| 139 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 140 |
+
max_grad_norm: 1.0
|
| 141 |
+
|
| 142 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 143 |
+
optimizer_type: "adamw"
|
| 144 |
+
|
| 145 |
+
# Learning rate scheduler type
|
| 146 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 147 |
+
scheduler_type: "linear"
|
| 148 |
+
|
| 149 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 150 |
+
scheduler_params: { }
|
| 151 |
+
|
| 152 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 153 |
+
# Recommended for training with limited GPU memory
|
| 154 |
+
enable_gradient_checkpointing: true
|
| 155 |
+
|
| 156 |
+
# -----------------------------------------------------------------------------
|
| 157 |
+
# Acceleration Configuration
|
| 158 |
+
# -----------------------------------------------------------------------------
|
| 159 |
+
# Hardware acceleration and memory optimization settings.
|
| 160 |
+
acceleration:
|
| 161 |
+
# Mixed precision training mode
|
| 162 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 163 |
+
mixed_precision_mode: "bf16"
|
| 164 |
+
|
| 165 |
+
# Model quantization for reduced memory usage
|
| 166 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 167 |
+
quantization: null
|
| 168 |
+
|
| 169 |
+
# Load text encoder in 8-bit precision to save memory
|
| 170 |
+
# Useful when GPU memory is limited
|
| 171 |
+
load_text_encoder_in_8bit: false
|
| 172 |
+
|
| 173 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 174 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 175 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 176 |
+
offload_optimizer_during_validation: false
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
# -----------------------------------------------------------------------------
|
| 180 |
+
# Data Configuration
|
| 181 |
+
# -----------------------------------------------------------------------------
|
| 182 |
+
# Specifies the training data location and loading parameters.
|
| 183 |
+
data:
|
| 184 |
+
# Root directory containing preprocessed training data
|
| 185 |
+
# Should contain: latents/, audio_latents/, conditions/, reference_latents/, and reference_audio_latents/
|
| 186 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 187 |
+
|
| 188 |
+
# Number of worker processes for data loading
|
| 189 |
+
# Used for parallel data loading to speed up data loading
|
| 190 |
+
num_dataloader_workers: 2
|
| 191 |
+
|
| 192 |
+
# -----------------------------------------------------------------------------
|
| 193 |
+
# Validation Configuration
|
| 194 |
+
# -----------------------------------------------------------------------------
|
| 195 |
+
# Controls validation sampling during training.
|
| 196 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 197 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 198 |
+
validation:
|
| 199 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 200 |
+
# For AV2AV IC-LoRA, each sample includes reference video and reference audio conditions.
|
| 201 |
+
samples:
|
| 202 |
+
- prompt: >-
|
| 203 |
+
A man in a casual blue jacket walks along a winding path through a lush green park on a
|
| 204 |
+
bright sunny afternoon. Tall oak trees line the pathway, their leaves rustling gently in
|
| 205 |
+
the breeze. Dappled sunlight creates shifting patterns on the ground as he strolls at a
|
| 206 |
+
relaxed pace, occasionally looking up at the scenery around him. The audio captures
|
| 207 |
+
footsteps on gravel, birds singing in the trees, distant children playing, and the soft
|
| 208 |
+
whisper of wind through the foliage.
|
| 209 |
+
conditions:
|
| 210 |
+
- type: reference
|
| 211 |
+
video: "/path/to/reference_video_1.mp4"
|
| 212 |
+
downscale_factor: 1
|
| 213 |
+
temporal_scale_factor: 1
|
| 214 |
+
include_in_output: true
|
| 215 |
+
- type: reference
|
| 216 |
+
audio: "/path/to/reference_audio_1.wav"
|
| 217 |
+
- prompt: >-
|
| 218 |
+
A fluffy orange tabby cat sits perfectly still on a wooden windowsill, its green eyes
|
| 219 |
+
intently tracking small birds hopping on a branch just outside the glass. The cat's ears
|
| 220 |
+
twitch and rotate, following every movement. Warm afternoon light illuminates its fur,
|
| 221 |
+
creating a soft golden glow. Behind the cat, a cozy living room with a bookshelf and
|
| 222 |
+
houseplants is visible. The audio features gentle purring, occasional soft meows, muffled
|
| 223 |
+
bird chirps through the window, and quiet ambient room sounds.
|
| 224 |
+
conditions:
|
| 225 |
+
- type: reference
|
| 226 |
+
video: "/path/to/reference_video_2.mp4"
|
| 227 |
+
downscale_factor: 1
|
| 228 |
+
temporal_scale_factor: 1
|
| 229 |
+
include_in_output: true
|
| 230 |
+
- type: reference
|
| 231 |
+
audio: "/path/to/reference_audio_2.wav"
|
| 232 |
+
|
| 233 |
+
# Negative prompt to avoid unwanted artifacts
|
| 234 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 235 |
+
|
| 236 |
+
# Output video dimensions [width, height, frames]
|
| 237 |
+
# Width and height must be divisible by 32
|
| 238 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 239 |
+
video_dims: [ 512, 512, 81 ]
|
| 240 |
+
|
| 241 |
+
# Frame rate for generated videos
|
| 242 |
+
frame_rate: 25.0
|
| 243 |
+
|
| 244 |
+
# Random seed for reproducible validation outputs
|
| 245 |
+
seed: 42
|
| 246 |
+
|
| 247 |
+
# Number of denoising steps for validation inference
|
| 248 |
+
# Higher values = better quality but slower generation
|
| 249 |
+
inference_steps: 30
|
| 250 |
+
|
| 251 |
+
# Generate validation videos every N training steps
|
| 252 |
+
# Set to null to disable validation during training
|
| 253 |
+
interval: 100
|
| 254 |
+
|
| 255 |
+
# Classifier-free guidance scale
|
| 256 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 257 |
+
guidance_scale: 4.0
|
| 258 |
+
|
| 259 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 260 |
+
# STG is combined with CFG for better temporal coherence
|
| 261 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 262 |
+
stg_blocks: [29] # Recommended: single block 29
|
| 263 |
+
stg_mode: "stg_av" # Both video and audio modalities are trained
|
| 264 |
+
|
| 265 |
+
# Whether to generate audio in validation samples
|
| 266 |
+
# Can be enabled even when not training the audio branch
|
| 267 |
+
generate_audio: true
|
| 268 |
+
|
| 269 |
+
# Skip validation at the beginning of training (step 0)
|
| 270 |
+
skip_initial_validation: false
|
| 271 |
+
|
| 272 |
+
# -----------------------------------------------------------------------------
|
| 273 |
+
# Checkpoint Configuration
|
| 274 |
+
# -----------------------------------------------------------------------------
|
| 275 |
+
# Controls model checkpoint saving during training.
|
| 276 |
+
checkpoints:
|
| 277 |
+
# Save a checkpoint every N steps
|
| 278 |
+
# Set to null to disable intermediate checkpoints
|
| 279 |
+
interval: 250
|
| 280 |
+
|
| 281 |
+
# Number of most recent checkpoints to keep
|
| 282 |
+
# Set to -1 to keep all checkpoints
|
| 283 |
+
keep_last_n: 3
|
| 284 |
+
|
| 285 |
+
# Precision to use when saving checkpoint weights
|
| 286 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 287 |
+
precision: "bfloat16"
|
| 288 |
+
|
| 289 |
+
# -----------------------------------------------------------------------------
|
| 290 |
+
# Flow Matching Configuration
|
| 291 |
+
# -----------------------------------------------------------------------------
|
| 292 |
+
# Parameters for the flow matching training objective.
|
| 293 |
+
flow_matching:
|
| 294 |
+
# Timestep sampling mode
|
| 295 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 296 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 297 |
+
|
| 298 |
+
# Additional parameters for timestep sampling
|
| 299 |
+
timestep_sampling_params: { }
|
| 300 |
+
|
| 301 |
+
# -----------------------------------------------------------------------------
|
| 302 |
+
# Hugging Face Hub Configuration
|
| 303 |
+
# -----------------------------------------------------------------------------
|
| 304 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 305 |
+
hub:
|
| 306 |
+
# Whether to push the trained model to the Hub
|
| 307 |
+
push_to_hub: false
|
| 308 |
+
|
| 309 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-ic-lora-model")
|
| 310 |
+
# Required if push_to_hub is true
|
| 311 |
+
hub_model_id: null
|
| 312 |
+
|
| 313 |
+
# -----------------------------------------------------------------------------
|
| 314 |
+
# Weights & Biases Configuration
|
| 315 |
+
# -----------------------------------------------------------------------------
|
| 316 |
+
# Settings for experiment tracking with W&B.
|
| 317 |
+
wandb:
|
| 318 |
+
# Enable W&B logging
|
| 319 |
+
enabled: false
|
| 320 |
+
|
| 321 |
+
# W&B project name
|
| 322 |
+
project: "ltx-2-trainer"
|
| 323 |
+
|
| 324 |
+
# W&B username or team (null uses default account)
|
| 325 |
+
entity: null
|
| 326 |
+
|
| 327 |
+
# Tags to help organize runs
|
| 328 |
+
tags: [ "ltx2", "ic-lora", "av2av" ]
|
| 329 |
+
|
| 330 |
+
# Log validation media (video/audio) to W&B
|
| 331 |
+
log_validation_videos: true
|
| 332 |
+
|
| 333 |
+
# -----------------------------------------------------------------------------
|
| 334 |
+
# General Configuration
|
| 335 |
+
# -----------------------------------------------------------------------------
|
| 336 |
+
# Global settings for the training run.
|
| 337 |
+
|
| 338 |
+
# Random seed for reproducibility
|
| 339 |
+
seed: 42
|
| 340 |
+
|
| 341 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 342 |
+
output_dir: "outputs/av2av_ic_lora"
|
packages/ltx-trainer/configs/i2v_lora.yaml
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 Image-to-Video LoRA Training Configuration
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This configuration is for training LoRA adapters on the LTX-2 model for
|
| 6 |
+
# image-to-video generation. The model learns to generate videos conditioned
|
| 7 |
+
# on a starting image (first frame), with optional audio generation.
|
| 8 |
+
#
|
| 9 |
+
# First-frame conditioning works by providing the first frame as a clean
|
| 10 |
+
# conditioning signal during training — it receives no noise, timestep=0,
|
| 11 |
+
# and is excluded from the loss. This teaches the model to animate from
|
| 12 |
+
# a given image.
|
| 13 |
+
#
|
| 14 |
+
# Use this configuration when you want to:
|
| 15 |
+
# - Train the model to generate videos starting from a given image
|
| 16 |
+
# - Fine-tune image-to-video capabilities on custom datasets
|
| 17 |
+
# - Create image animation models with optional audio
|
| 18 |
+
#
|
| 19 |
+
# Dataset structure:
|
| 20 |
+
# preprocessed_data_root/
|
| 21 |
+
# ├── latents/ # Video latents (VAE-encoded videos)
|
| 22 |
+
# ├── conditions/ # Text embeddings for each video
|
| 23 |
+
# └── audio_latents/ # Audio latents (VAE-encoded audio)
|
| 24 |
+
#
|
| 25 |
+
# =============================================================================
|
| 26 |
+
|
| 27 |
+
# -----------------------------------------------------------------------------
|
| 28 |
+
# Model Configuration
|
| 29 |
+
# -----------------------------------------------------------------------------
|
| 30 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 31 |
+
model:
|
| 32 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 33 |
+
# This should be a local path to your downloaded model
|
| 34 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 35 |
+
|
| 36 |
+
# Path to the text encoder model directory
|
| 37 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 38 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 39 |
+
|
| 40 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 41 |
+
# LoRA is recommended for most use cases (faster, less memory, prevents overfitting)
|
| 42 |
+
training_mode: "lora"
|
| 43 |
+
|
| 44 |
+
# Optional: Path to resume training from a checkpoint
|
| 45 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 46 |
+
load_checkpoint: null
|
| 47 |
+
|
| 48 |
+
# -----------------------------------------------------------------------------
|
| 49 |
+
# LoRA Configuration
|
| 50 |
+
# -----------------------------------------------------------------------------
|
| 51 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 52 |
+
lora:
|
| 53 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 54 |
+
# Typical values: 8, 16, 32, 64. Start with 32 for general fine-tuning.
|
| 55 |
+
rank: 32
|
| 56 |
+
|
| 57 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 58 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 59 |
+
alpha: 32
|
| 60 |
+
|
| 61 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 62 |
+
# Can help with regularization if overfitting occurs
|
| 63 |
+
dropout: 0.0
|
| 64 |
+
|
| 65 |
+
# Which transformer modules to apply LoRA to
|
| 66 |
+
# The LTX-2 transformer has separate attention and FFN blocks for video and audio:
|
| 67 |
+
#
|
| 68 |
+
# VIDEO MODULES:
|
| 69 |
+
# - attn1.to_k, attn1.to_q, attn1.to_v, attn1.to_out.0 (video self-attention)
|
| 70 |
+
# - attn2.to_k, attn2.to_q, attn2.to_v, attn2.to_out.0 (video cross-attention to text)
|
| 71 |
+
# - ff.net.0.proj, ff.net.2 (video feed-forward)
|
| 72 |
+
#
|
| 73 |
+
# AUDIO MODULES:
|
| 74 |
+
# - audio_attn1.to_k, audio_attn1.to_q, audio_attn1.to_v, audio_attn1.to_out.0 (audio self-attention)
|
| 75 |
+
# - audio_attn2.to_k, audio_attn2.to_q, audio_attn2.to_v, audio_attn2.to_out.0 (audio cross-attention to text)
|
| 76 |
+
# - audio_ff.net.0.proj, audio_ff.net.2 (audio feed-forward)
|
| 77 |
+
#
|
| 78 |
+
# AUDIO-VIDEO CROSS-ATTENTION MODULES (for cross-modal interaction):
|
| 79 |
+
# - audio_to_video_attn.to_k, audio_to_video_attn.to_q, audio_to_video_attn.to_v, audio_to_video_attn.to_out.0
|
| 80 |
+
# (Q from video, K/V from audio - allows video to attend to audio features)
|
| 81 |
+
# - video_to_audio_attn.to_k, video_to_audio_attn.to_q, video_to_audio_attn.to_v, video_to_audio_attn.to_out.0
|
| 82 |
+
# (Q from audio, K/V from video - allows audio to attend to video features)
|
| 83 |
+
#
|
| 84 |
+
# Using short patterns like "to_k" matches ALL attention modules (video, audio, and cross-modal).
|
| 85 |
+
# For audio-video training, this is the recommended approach.
|
| 86 |
+
target_modules:
|
| 87 |
+
# Attention layers (matches both video and audio branches)
|
| 88 |
+
- "to_k"
|
| 89 |
+
- "to_q"
|
| 90 |
+
- "to_v"
|
| 91 |
+
- "to_out.0"
|
| 92 |
+
# Uncomment below to also train feed-forward layers (can increase the LoRA's capacity):
|
| 93 |
+
# - "ff.net.0.proj"
|
| 94 |
+
# - "ff.net.2"
|
| 95 |
+
# - "audio_ff.net.0.proj"
|
| 96 |
+
# - "audio_ff.net.2"
|
| 97 |
+
|
| 98 |
+
# -----------------------------------------------------------------------------
|
| 99 |
+
# Training Strategy Configuration
|
| 100 |
+
# -----------------------------------------------------------------------------
|
| 101 |
+
# Defines the image-to-video training approach using the unified flexible strategy.
|
| 102 |
+
# First-frame conditioning provides the first frame as clean conditioning signal
|
| 103 |
+
# during training, teaching the model to animate from a given image.
|
| 104 |
+
training_strategy:
|
| 105 |
+
# Strategy name: "flexible" for the unified conditioning framework
|
| 106 |
+
# Supports all training modes (T2V, I2V, V2V, A2V, V2A, etc.) through
|
| 107 |
+
# modality-specific configuration blocks.
|
| 108 |
+
name: "flexible"
|
| 109 |
+
|
| 110 |
+
# Video modality configuration
|
| 111 |
+
video:
|
| 112 |
+
# Whether the model generates video (true) or uses it as frozen conditioning (false)
|
| 113 |
+
is_generated: true
|
| 114 |
+
# Directory name (within preprocessed_data_root) containing video latents
|
| 115 |
+
latents_dir: "latents"
|
| 116 |
+
|
| 117 |
+
# Conditions applied to the video modality during training
|
| 118 |
+
# First-frame conditioning: the first frame of each video is provided as a clean
|
| 119 |
+
# conditioning signal (no noise, timestep=0, excluded from loss)
|
| 120 |
+
conditions:
|
| 121 |
+
- type: first_frame
|
| 122 |
+
# Probability of applying first-frame conditioning per training sample
|
| 123 |
+
# At 0.5, half the training samples use I2V mode, half use pure T2V
|
| 124 |
+
# Higher values improve I2V quality but may reduce T2V diversity
|
| 125 |
+
probability: 0.5
|
| 126 |
+
|
| 127 |
+
# Audio modality configuration
|
| 128 |
+
audio:
|
| 129 |
+
# Whether the model generates audio (true) or uses it as frozen conditioning (false)
|
| 130 |
+
is_generated: true
|
| 131 |
+
# Directory name (within preprocessed_data_root) containing audio latents
|
| 132 |
+
latents_dir: "audio_latents"
|
| 133 |
+
|
| 134 |
+
# -----------------------------------------------------------------------------
|
| 135 |
+
# Optimization Configuration
|
| 136 |
+
# -----------------------------------------------------------------------------
|
| 137 |
+
# Controls the training optimization parameters.
|
| 138 |
+
optimization:
|
| 139 |
+
# Learning rate for the optimizer
|
| 140 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 141 |
+
learning_rate: 1e-4
|
| 142 |
+
|
| 143 |
+
# Total number of training steps
|
| 144 |
+
steps: 2000
|
| 145 |
+
|
| 146 |
+
# Batch size per GPU
|
| 147 |
+
# Reduce if running out of memory
|
| 148 |
+
batch_size: 1
|
| 149 |
+
|
| 150 |
+
# Number of gradient accumulation steps
|
| 151 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 152 |
+
gradient_accumulation_steps: 1
|
| 153 |
+
|
| 154 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 155 |
+
max_grad_norm: 1.0
|
| 156 |
+
|
| 157 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 158 |
+
optimizer_type: "adamw"
|
| 159 |
+
|
| 160 |
+
# Learning rate scheduler type
|
| 161 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 162 |
+
scheduler_type: "linear"
|
| 163 |
+
|
| 164 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 165 |
+
scheduler_params: { }
|
| 166 |
+
|
| 167 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 168 |
+
# Recommended for training with limited GPU memory
|
| 169 |
+
enable_gradient_checkpointing: true
|
| 170 |
+
|
| 171 |
+
# -----------------------------------------------------------------------------
|
| 172 |
+
# Acceleration Configuration
|
| 173 |
+
# -----------------------------------------------------------------------------
|
| 174 |
+
# Hardware acceleration and memory optimization settings.
|
| 175 |
+
acceleration:
|
| 176 |
+
# Mixed precision training mode
|
| 177 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 178 |
+
mixed_precision_mode: "bf16"
|
| 179 |
+
|
| 180 |
+
# Model quantization for reduced memory usage
|
| 181 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 182 |
+
quantization: null
|
| 183 |
+
|
| 184 |
+
# Load text encoder in 8-bit precision to save memory
|
| 185 |
+
# Useful when GPU memory is limited
|
| 186 |
+
load_text_encoder_in_8bit: false
|
| 187 |
+
|
| 188 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 189 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 190 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 191 |
+
offload_optimizer_during_validation: false
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
# -----------------------------------------------------------------------------
|
| 195 |
+
# Data Configuration
|
| 196 |
+
# -----------------------------------------------------------------------------
|
| 197 |
+
# Specifies the training data location and loading parameters.
|
| 198 |
+
data:
|
| 199 |
+
# Root directory containing preprocessed training data
|
| 200 |
+
# Should contain: latents/, conditions/, and audio_latents/
|
| 201 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 202 |
+
|
| 203 |
+
# Number of worker processes for data loading
|
| 204 |
+
# Used for parallel data loading to speed up data loading
|
| 205 |
+
num_dataloader_workers: 2
|
| 206 |
+
|
| 207 |
+
# -----------------------------------------------------------------------------
|
| 208 |
+
# Validation Configuration
|
| 209 |
+
# -----------------------------------------------------------------------------
|
| 210 |
+
# Controls validation sampling during training.
|
| 211 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 212 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 213 |
+
validation:
|
| 214 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 215 |
+
# Use 'conditions' to add conditioning (first_frame, prefix, suffix, reference, video_to_audio, etc.)
|
| 216 |
+
# See docs/configuration-reference.md#validation-condition-types for the full list of condition types.
|
| 217 |
+
samples:
|
| 218 |
+
- prompt: >-
|
| 219 |
+
A woman with long brown hair sits at a wooden desk in a cozy home office, typing on a
|
| 220 |
+
laptop while occasionally glancing at notes beside her. Soft natural light streams through
|
| 221 |
+
a large window, casting warm shadows across the room. She pauses to take a sip from a
|
| 222 |
+
ceramic mug, then continues working with focused concentration. The audio captures the
|
| 223 |
+
gentle clicking of keyboard keys, the soft rustle of papers, and ambient room tone with
|
| 224 |
+
occasional distant bird chirps from outside.
|
| 225 |
+
conditions:
|
| 226 |
+
- type: first_frame
|
| 227 |
+
image_or_video: "/path/to/conditioning_image_1.png"
|
| 228 |
+
- prompt: >-
|
| 229 |
+
A chef in a white uniform stands in a professional kitchen, carefully plating a gourmet
|
| 230 |
+
dish with precise movements. Steam rises from freshly cooked vegetables as he arranges
|
| 231 |
+
them with tweezers. The stainless steel surfaces gleam under bright overhead lights, and
|
| 232 |
+
various pots simmer on the stove behind him. The audio features the sizzling of pans,
|
| 233 |
+
the clinking of utensils against plates, and the ambient hum of kitchen ventilation.
|
| 234 |
+
conditions:
|
| 235 |
+
- type: first_frame
|
| 236 |
+
image_or_video: "/path/to/conditioning_image_2.png"
|
| 237 |
+
|
| 238 |
+
# Negative prompt to avoid unwanted artifacts
|
| 239 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 240 |
+
|
| 241 |
+
# Output video dimensions [width, height, frames]
|
| 242 |
+
# Width and height must be divisible by 32
|
| 243 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 244 |
+
video_dims: [ 576, 576, 89 ]
|
| 245 |
+
|
| 246 |
+
# Frame rate for generated videos
|
| 247 |
+
frame_rate: 25.0
|
| 248 |
+
|
| 249 |
+
# Random seed for reproducible validation outputs
|
| 250 |
+
seed: 42
|
| 251 |
+
|
| 252 |
+
# Number of denoising steps for validation inference
|
| 253 |
+
# Higher values = better quality but slower generation
|
| 254 |
+
inference_steps: 30
|
| 255 |
+
|
| 256 |
+
# Generate validation videos every N training steps
|
| 257 |
+
# Set to null to disable validation during training
|
| 258 |
+
interval: 100
|
| 259 |
+
|
| 260 |
+
# Classifier-free guidance scale
|
| 261 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 262 |
+
guidance_scale: 4.0
|
| 263 |
+
|
| 264 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 265 |
+
# STG is combined with CFG for better temporal coherence
|
| 266 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 267 |
+
stg_blocks: [29] # Recommended: single block 29
|
| 268 |
+
stg_mode: "stg_av" # "stg_av" perturbs both audio and video, "stg_v" video only
|
| 269 |
+
|
| 270 |
+
# Whether to generate audio in validation samples
|
| 271 |
+
# Independent of training_strategy.audio.is_generated - you can generate audio
|
| 272 |
+
# in validation even when not training the audio branch
|
| 273 |
+
generate_audio: true
|
| 274 |
+
|
| 275 |
+
# Skip validation at the beginning of training (step 0)
|
| 276 |
+
skip_initial_validation: false
|
| 277 |
+
|
| 278 |
+
# -----------------------------------------------------------------------------
|
| 279 |
+
# Checkpoint Configuration
|
| 280 |
+
# -----------------------------------------------------------------------------
|
| 281 |
+
# Controls model checkpoint saving during training.
|
| 282 |
+
checkpoints:
|
| 283 |
+
# Save a checkpoint every N steps
|
| 284 |
+
# Set to null to disable intermediate checkpoints
|
| 285 |
+
interval: 250
|
| 286 |
+
|
| 287 |
+
# Number of most recent checkpoints to keep
|
| 288 |
+
# Set to -1 to keep all checkpoints
|
| 289 |
+
keep_last_n: -1
|
| 290 |
+
|
| 291 |
+
# Precision to use when saving checkpoint weights
|
| 292 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 293 |
+
precision: "bfloat16"
|
| 294 |
+
|
| 295 |
+
# -----------------------------------------------------------------------------
|
| 296 |
+
# Flow Matching Configuration
|
| 297 |
+
# -----------------------------------------------------------------------------
|
| 298 |
+
# Parameters for the flow matching training objective.
|
| 299 |
+
flow_matching:
|
| 300 |
+
# Timestep sampling mode
|
| 301 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 302 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 303 |
+
|
| 304 |
+
# Additional parameters for timestep sampling
|
| 305 |
+
timestep_sampling_params: { }
|
| 306 |
+
|
| 307 |
+
# -----------------------------------------------------------------------------
|
| 308 |
+
# Hugging Face Hub Configuration
|
| 309 |
+
# -----------------------------------------------------------------------------
|
| 310 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 311 |
+
hub:
|
| 312 |
+
# Whether to push the trained model to the Hub
|
| 313 |
+
push_to_hub: false
|
| 314 |
+
|
| 315 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-lora-model")
|
| 316 |
+
# Required if push_to_hub is true
|
| 317 |
+
hub_model_id: null
|
| 318 |
+
|
| 319 |
+
# -----------------------------------------------------------------------------
|
| 320 |
+
# Weights & Biases Configuration
|
| 321 |
+
# -----------------------------------------------------------------------------
|
| 322 |
+
# Settings for experiment tracking with W&B.
|
| 323 |
+
wandb:
|
| 324 |
+
# Enable W&B logging
|
| 325 |
+
enabled: false
|
| 326 |
+
|
| 327 |
+
# W&B project name
|
| 328 |
+
project: "ltx-2-trainer"
|
| 329 |
+
|
| 330 |
+
# W&B username or team (null uses default account)
|
| 331 |
+
entity: null
|
| 332 |
+
|
| 333 |
+
# Tags to help organize runs
|
| 334 |
+
tags: [ "ltx2", "lora", "i2v" ]
|
| 335 |
+
|
| 336 |
+
# Log validation media (video/audio) to W&B
|
| 337 |
+
log_validation_videos: true
|
| 338 |
+
|
| 339 |
+
# -----------------------------------------------------------------------------
|
| 340 |
+
# General Configuration
|
| 341 |
+
# -----------------------------------------------------------------------------
|
| 342 |
+
# Global settings for the training run.
|
| 343 |
+
|
| 344 |
+
# Random seed for reproducibility
|
| 345 |
+
seed: 42
|
| 346 |
+
|
| 347 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 348 |
+
output_dir: "outputs/i2v_lora"
|
packages/ltx-trainer/configs/t2a_lora.yaml
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 Text-to-Audio LoRA Training Configuration
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This configuration is for training LoRA adapters on the LTX-2 model for
|
| 6 |
+
# text-to-audio generation. The model learns to generate audio from text
|
| 7 |
+
# prompts without any additional conditioning.
|
| 8 |
+
#
|
| 9 |
+
# This is the simplest audio-only training mode — no reference audio, no
|
| 10 |
+
# video modality. Only the audio branch of the transformer is trained.
|
| 11 |
+
#
|
| 12 |
+
# Use this configuration when you want to:
|
| 13 |
+
# - Fine-tune audio generation for specific sound styles or domains
|
| 14 |
+
# - Train custom audio generation capabilities
|
| 15 |
+
# - Create audio LoRAs that can be combined with video LoRAs
|
| 16 |
+
#
|
| 17 |
+
# Dataset structure:
|
| 18 |
+
# preprocessed_data_root/
|
| 19 |
+
# ├── conditions/ # Text embeddings for each sample
|
| 20 |
+
# └── audio_latents/ # Audio latents (VAE-encoded audio)
|
| 21 |
+
#
|
| 22 |
+
# =============================================================================
|
| 23 |
+
|
| 24 |
+
# -----------------------------------------------------------------------------
|
| 25 |
+
# Model Configuration
|
| 26 |
+
# -----------------------------------------------------------------------------
|
| 27 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 28 |
+
model:
|
| 29 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 30 |
+
# This should be a local path to your downloaded model
|
| 31 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 32 |
+
|
| 33 |
+
# Path to the text encoder model directory
|
| 34 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 35 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 36 |
+
|
| 37 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 38 |
+
# LoRA is recommended for most use cases (faster, less memory, prevents overfitting)
|
| 39 |
+
training_mode: "lora"
|
| 40 |
+
|
| 41 |
+
# Optional: Path to resume training from a checkpoint
|
| 42 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 43 |
+
load_checkpoint: null
|
| 44 |
+
|
| 45 |
+
# -----------------------------------------------------------------------------
|
| 46 |
+
# LoRA Configuration
|
| 47 |
+
# -----------------------------------------------------------------------------
|
| 48 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 49 |
+
lora:
|
| 50 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 51 |
+
# Typical values: 8, 16, 32, 64. Start with 32 for general audio LoRA training.
|
| 52 |
+
rank: 32
|
| 53 |
+
|
| 54 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 55 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 56 |
+
alpha: 32
|
| 57 |
+
|
| 58 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 59 |
+
# Can help with regularization if overfitting occurs
|
| 60 |
+
dropout: 0.0
|
| 61 |
+
|
| 62 |
+
# Which transformer modules to apply LoRA to
|
| 63 |
+
# The LTX-2 transformer has separate attention and FFN blocks for video and audio:
|
| 64 |
+
#
|
| 65 |
+
# VIDEO MODULES (not used for audio-only modes):
|
| 66 |
+
# - attn1.to_k, attn1.to_q, attn1.to_v, attn1.to_out.0 (video self-attention)
|
| 67 |
+
# - attn2.to_k, attn2.to_q, attn2.to_v, attn2.to_out.0 (video cross-attention to text)
|
| 68 |
+
# - ff.net.0.proj, ff.net.2 (video feed-forward)
|
| 69 |
+
#
|
| 70 |
+
# AUDIO MODULES:
|
| 71 |
+
# - audio_attn1.to_k, audio_attn1.to_q, audio_attn1.to_v, audio_attn1.to_out.0 (audio self-attention)
|
| 72 |
+
# - audio_attn2.to_k, audio_attn2.to_q, audio_attn2.to_v, audio_attn2.to_out.0 (audio cross-attention to text)
|
| 73 |
+
# - audio_ff.net.0.proj, audio_ff.net.2 (audio feed-forward)
|
| 74 |
+
#
|
| 75 |
+
# AUDIO-VIDEO CROSS-ATTENTION MODULES (not used for audio-only modes):
|
| 76 |
+
# - audio_to_video_attn.to_k, audio_to_video_attn.to_q, audio_to_video_attn.to_v, audio_to_video_attn.to_out.0
|
| 77 |
+
# (Q from video, K/V from audio - allows video to attend to audio features)
|
| 78 |
+
# - video_to_audio_attn.to_k, video_to_audio_attn.to_q, video_to_audio_attn.to_v, video_to_audio_attn.to_out.0
|
| 79 |
+
# (Q from audio, K/V from video - allows audio to attend to video features)
|
| 80 |
+
#
|
| 81 |
+
# For audio-only training, we explicitly target audio modules.
|
| 82 |
+
# Including audio FFN layers can increase the LoRA's capacity.
|
| 83 |
+
target_modules:
|
| 84 |
+
# Audio self-attention
|
| 85 |
+
- "audio_attn1.to_k"
|
| 86 |
+
- "audio_attn1.to_q"
|
| 87 |
+
- "audio_attn1.to_v"
|
| 88 |
+
- "audio_attn1.to_out.0"
|
| 89 |
+
# Audio cross-attention to text
|
| 90 |
+
- "audio_attn2.to_k"
|
| 91 |
+
- "audio_attn2.to_q"
|
| 92 |
+
- "audio_attn2.to_v"
|
| 93 |
+
- "audio_attn2.to_out.0"
|
| 94 |
+
# Audio feed-forward (often improves transformation quality)
|
| 95 |
+
- "audio_ff.net.0.proj"
|
| 96 |
+
- "audio_ff.net.2"
|
| 97 |
+
|
| 98 |
+
# -----------------------------------------------------------------------------
|
| 99 |
+
# Training Strategy Configuration
|
| 100 |
+
# -----------------------------------------------------------------------------
|
| 101 |
+
# Defines the text-to-audio training approach using the unified flexible
|
| 102 |
+
# strategy. Audio-only training with no additional conditions — the model
|
| 103 |
+
# learns to generate audio purely from text prompts.
|
| 104 |
+
training_strategy:
|
| 105 |
+
name: "flexible"
|
| 106 |
+
|
| 107 |
+
# Audio modality configuration (audio-only, no video)
|
| 108 |
+
audio:
|
| 109 |
+
# Whether the model generates audio (true) or uses it as frozen conditioning (false)
|
| 110 |
+
is_generated: true
|
| 111 |
+
# Directory name (within preprocessed_data_root) containing audio latents
|
| 112 |
+
latents_dir: "audio_latents"
|
| 113 |
+
|
| 114 |
+
# -----------------------------------------------------------------------------
|
| 115 |
+
# Optimization Configuration
|
| 116 |
+
# -----------------------------------------------------------------------------
|
| 117 |
+
# Controls the training optimization parameters.
|
| 118 |
+
optimization:
|
| 119 |
+
# Learning rate for the optimizer
|
| 120 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 121 |
+
learning_rate: 2e-4
|
| 122 |
+
|
| 123 |
+
# Total number of training steps
|
| 124 |
+
steps: 3000
|
| 125 |
+
|
| 126 |
+
# Batch size per GPU
|
| 127 |
+
# Reduce if running out of memory
|
| 128 |
+
batch_size: 1
|
| 129 |
+
|
| 130 |
+
# Number of gradient accumulation steps
|
| 131 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 132 |
+
gradient_accumulation_steps: 1
|
| 133 |
+
|
| 134 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 135 |
+
max_grad_norm: 1.0
|
| 136 |
+
|
| 137 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 138 |
+
optimizer_type: "adamw"
|
| 139 |
+
|
| 140 |
+
# Learning rate scheduler type
|
| 141 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 142 |
+
scheduler_type: "linear"
|
| 143 |
+
|
| 144 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 145 |
+
scheduler_params: { }
|
| 146 |
+
|
| 147 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 148 |
+
# Recommended for training with limited GPU memory
|
| 149 |
+
enable_gradient_checkpointing: true
|
| 150 |
+
|
| 151 |
+
# -----------------------------------------------------------------------------
|
| 152 |
+
# Acceleration Configuration
|
| 153 |
+
# -----------------------------------------------------------------------------
|
| 154 |
+
# Hardware acceleration and memory optimization settings.
|
| 155 |
+
acceleration:
|
| 156 |
+
# Mixed precision training mode
|
| 157 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 158 |
+
mixed_precision_mode: "bf16"
|
| 159 |
+
|
| 160 |
+
# Model quantization for reduced memory usage
|
| 161 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 162 |
+
quantization: null
|
| 163 |
+
|
| 164 |
+
# Load text encoder in 8-bit precision to save memory
|
| 165 |
+
# Useful when GPU memory is limited
|
| 166 |
+
load_text_encoder_in_8bit: false
|
| 167 |
+
|
| 168 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 169 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 170 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 171 |
+
offload_optimizer_during_validation: false
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
# -----------------------------------------------------------------------------
|
| 175 |
+
# Data Configuration
|
| 176 |
+
# -----------------------------------------------------------------------------
|
| 177 |
+
# Specifies the training data location and loading parameters.
|
| 178 |
+
data:
|
| 179 |
+
# Root directory containing preprocessed training data
|
| 180 |
+
# Should contain: conditions/ and audio_latents/ subdirectories
|
| 181 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 182 |
+
|
| 183 |
+
# Number of worker processes for data loading
|
| 184 |
+
# Used for parallel data loading to speed up data loading
|
| 185 |
+
num_dataloader_workers: 2
|
| 186 |
+
|
| 187 |
+
# -----------------------------------------------------------------------------
|
| 188 |
+
# Validation Configuration
|
| 189 |
+
# -----------------------------------------------------------------------------
|
| 190 |
+
# Controls validation sampling during training.
|
| 191 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 192 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 193 |
+
validation:
|
| 194 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 195 |
+
# Text-to-audio validation samples do not need additional conditions.
|
| 196 |
+
samples:
|
| 197 |
+
- prompt: >-
|
| 198 |
+
A warm and soothing piano melody with soft ambient textures, reminiscent of a quiet
|
| 199 |
+
evening by the fireplace. Gentle reverb creates a sense of intimate space.
|
| 200 |
+
- prompt: >-
|
| 201 |
+
Energetic electronic beats with pulsing synthesizer leads and crisp hi-hat patterns.
|
| 202 |
+
Deep bass tones provide a driving rhythm underneath bright melodic arpeggios.
|
| 203 |
+
|
| 204 |
+
# Negative prompt to avoid unwanted artifacts
|
| 205 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 206 |
+
|
| 207 |
+
# Generation length control [width, height, frames]
|
| 208 |
+
# With generate_video=false, width/height are unused; frames and frame_rate set audio duration.
|
| 209 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 210 |
+
video_dims: [ 512, 512, 81 ]
|
| 211 |
+
|
| 212 |
+
# Frame rate for generated videos
|
| 213 |
+
frame_rate: 25.0
|
| 214 |
+
|
| 215 |
+
# Random seed for reproducible validation outputs
|
| 216 |
+
seed: 42
|
| 217 |
+
|
| 218 |
+
# Number of denoising steps for validation inference
|
| 219 |
+
# Higher values = better quality but slower generation
|
| 220 |
+
inference_steps: 30
|
| 221 |
+
|
| 222 |
+
# Generate validation videos every N training steps
|
| 223 |
+
# Set to null to disable validation during training
|
| 224 |
+
interval: 100
|
| 225 |
+
|
| 226 |
+
# Classifier-free guidance scale
|
| 227 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 228 |
+
guidance_scale: 4.0
|
| 229 |
+
|
| 230 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 231 |
+
# STG is combined with CFG for better temporal coherence
|
| 232 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 233 |
+
stg_blocks: [29] # Recommended: single block 29
|
| 234 |
+
stg_mode: "stg_av" # "stg_av" skips both audio and video self-attention (suited to audio validation)
|
| 235 |
+
|
| 236 |
+
# Whether to generate audio in validation samples
|
| 237 |
+
# Enabled because this audio-only config generates audio
|
| 238 |
+
generate_audio: true
|
| 239 |
+
|
| 240 |
+
# Whether to generate video in validation samples
|
| 241 |
+
# Disabled because no video modality is configured
|
| 242 |
+
generate_video: false
|
| 243 |
+
|
| 244 |
+
# Skip validation at the beginning of training (step 0)
|
| 245 |
+
skip_initial_validation: false
|
| 246 |
+
|
| 247 |
+
# -----------------------------------------------------------------------------
|
| 248 |
+
# Checkpoint Configuration
|
| 249 |
+
# -----------------------------------------------------------------------------
|
| 250 |
+
# Controls model checkpoint saving during training.
|
| 251 |
+
checkpoints:
|
| 252 |
+
# Save a checkpoint every N steps
|
| 253 |
+
# Set to null to disable intermediate checkpoints
|
| 254 |
+
interval: 250
|
| 255 |
+
|
| 256 |
+
# Number of most recent checkpoints to keep
|
| 257 |
+
# Set to -1 to keep all checkpoints
|
| 258 |
+
keep_last_n: 3
|
| 259 |
+
|
| 260 |
+
# Precision to use when saving checkpoint weights
|
| 261 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 262 |
+
precision: "bfloat16"
|
| 263 |
+
|
| 264 |
+
# -----------------------------------------------------------------------------
|
| 265 |
+
# Flow Matching Configuration
|
| 266 |
+
# -----------------------------------------------------------------------------
|
| 267 |
+
# Parameters for the flow matching training objective.
|
| 268 |
+
flow_matching:
|
| 269 |
+
# Timestep sampling mode
|
| 270 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 271 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 272 |
+
|
| 273 |
+
# Additional parameters for timestep sampling
|
| 274 |
+
timestep_sampling_params: { }
|
| 275 |
+
|
| 276 |
+
# -----------------------------------------------------------------------------
|
| 277 |
+
# Hugging Face Hub Configuration
|
| 278 |
+
# -----------------------------------------------------------------------------
|
| 279 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 280 |
+
hub:
|
| 281 |
+
# Whether to push the trained model to the Hub
|
| 282 |
+
push_to_hub: false
|
| 283 |
+
|
| 284 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-ic-lora-model")
|
| 285 |
+
# Required if push_to_hub is true
|
| 286 |
+
hub_model_id: null
|
| 287 |
+
|
| 288 |
+
# -----------------------------------------------------------------------------
|
| 289 |
+
# Weights & Biases Configuration
|
| 290 |
+
# -----------------------------------------------------------------------------
|
| 291 |
+
# Settings for experiment tracking with W&B.
|
| 292 |
+
wandb:
|
| 293 |
+
# Enable W&B logging
|
| 294 |
+
enabled: false
|
| 295 |
+
|
| 296 |
+
# W&B project name
|
| 297 |
+
project: "ltx-2-trainer"
|
| 298 |
+
|
| 299 |
+
# W&B username or team (null uses default account)
|
| 300 |
+
entity: null
|
| 301 |
+
|
| 302 |
+
# Tags to help organize runs
|
| 303 |
+
tags: [ "ltx2", "lora", "t2a", "audio-only" ]
|
| 304 |
+
|
| 305 |
+
# Log validation media (video/audio) to W&B
|
| 306 |
+
log_validation_videos: true
|
| 307 |
+
|
| 308 |
+
# -----------------------------------------------------------------------------
|
| 309 |
+
# General Configuration
|
| 310 |
+
# -----------------------------------------------------------------------------
|
| 311 |
+
# Global settings for the training run.
|
| 312 |
+
|
| 313 |
+
# Random seed for reproducibility
|
| 314 |
+
seed: 42
|
| 315 |
+
|
| 316 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 317 |
+
output_dir: "outputs/t2a_lora"
|
packages/ltx-trainer/configs/t2v_lora.yaml
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 Text-to-Video LoRA Training Configuration
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This configuration is for training LoRA adapters on the LTX-2 model for
|
| 6 |
+
# text-to-video generation with joint audio-video support.
|
| 7 |
+
#
|
| 8 |
+
# Use this configuration when you want to:
|
| 9 |
+
# - Fine-tune LTX-2 on your own video dataset
|
| 10 |
+
# - Train joint audio-video generation from text prompts
|
| 11 |
+
# - Create custom video generation styles or audiovisual concepts
|
| 12 |
+
#
|
| 13 |
+
# Dataset structure:
|
| 14 |
+
# preprocessed_data_root/
|
| 15 |
+
# ├── latents/ # Video latents (VAE-encoded videos)
|
| 16 |
+
# ├── conditions/ # Text embeddings for each video
|
| 17 |
+
# └── audio_latents/ # Audio latents (VAE-encoded audio)
|
| 18 |
+
#
|
| 19 |
+
# =============================================================================
|
| 20 |
+
|
| 21 |
+
# -----------------------------------------------------------------------------
|
| 22 |
+
# Model Configuration
|
| 23 |
+
# -----------------------------------------------------------------------------
|
| 24 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 25 |
+
model:
|
| 26 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 27 |
+
# This should be a local path to your downloaded model
|
| 28 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 29 |
+
|
| 30 |
+
# Path to the text encoder model directory
|
| 31 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 32 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 33 |
+
|
| 34 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 35 |
+
# LoRA is recommended for most use cases (faster, less memory, prevents overfitting)
|
| 36 |
+
training_mode: "lora"
|
| 37 |
+
|
| 38 |
+
# Optional: Path to resume training from a checkpoint
|
| 39 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 40 |
+
load_checkpoint: null
|
| 41 |
+
|
| 42 |
+
# -----------------------------------------------------------------------------
|
| 43 |
+
# LoRA Configuration
|
| 44 |
+
# -----------------------------------------------------------------------------
|
| 45 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 46 |
+
lora:
|
| 47 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 48 |
+
# Typical values: 8, 16, 32, 64. Start with 32 for general fine-tuning.
|
| 49 |
+
rank: 32
|
| 50 |
+
|
| 51 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 52 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 53 |
+
alpha: 32
|
| 54 |
+
|
| 55 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 56 |
+
# Can help with regularization if overfitting occurs
|
| 57 |
+
dropout: 0.0
|
| 58 |
+
|
| 59 |
+
# Which transformer modules to apply LoRA to
|
| 60 |
+
# The LTX-2 transformer has separate attention and FFN blocks for video and audio:
|
| 61 |
+
#
|
| 62 |
+
# VIDEO MODULES:
|
| 63 |
+
# - attn1.to_k, attn1.to_q, attn1.to_v, attn1.to_out.0 (video self-attention)
|
| 64 |
+
# - attn2.to_k, attn2.to_q, attn2.to_v, attn2.to_out.0 (video cross-attention to text)
|
| 65 |
+
# - ff.net.0.proj, ff.net.2 (video feed-forward)
|
| 66 |
+
#
|
| 67 |
+
# AUDIO MODULES:
|
| 68 |
+
# - audio_attn1.to_k, audio_attn1.to_q, audio_attn1.to_v, audio_attn1.to_out.0 (audio self-attention)
|
| 69 |
+
# - audio_attn2.to_k, audio_attn2.to_q, audio_attn2.to_v, audio_attn2.to_out.0 (audio cross-attention to text)
|
| 70 |
+
# - audio_ff.net.0.proj, audio_ff.net.2 (audio feed-forward)
|
| 71 |
+
#
|
| 72 |
+
# AUDIO-VIDEO CROSS-ATTENTION MODULES (for cross-modal interaction):
|
| 73 |
+
# - audio_to_video_attn.to_k, audio_to_video_attn.to_q, audio_to_video_attn.to_v, audio_to_video_attn.to_out.0
|
| 74 |
+
# (Q from video, K/V from audio - allows video to attend to audio features)
|
| 75 |
+
# - video_to_audio_attn.to_k, video_to_audio_attn.to_q, video_to_audio_attn.to_v, video_to_audio_attn.to_out.0
|
| 76 |
+
# (Q from audio, K/V from video - allows audio to attend to video features)
|
| 77 |
+
#
|
| 78 |
+
# Using short patterns like "to_k" matches ALL attention modules (video, audio, and cross-modal).
|
| 79 |
+
# For audio-video training, this is the recommended approach.
|
| 80 |
+
target_modules:
|
| 81 |
+
# Attention layers (matches both video and audio branches)
|
| 82 |
+
- "to_k"
|
| 83 |
+
- "to_q"
|
| 84 |
+
- "to_v"
|
| 85 |
+
- "to_out.0"
|
| 86 |
+
# Uncomment below to also train feed-forward layers (can increase the LoRA's capacity):
|
| 87 |
+
# - "ff.net.0.proj"
|
| 88 |
+
# - "ff.net.2"
|
| 89 |
+
# - "audio_ff.net.0.proj"
|
| 90 |
+
# - "audio_ff.net.2"
|
| 91 |
+
|
| 92 |
+
# -----------------------------------------------------------------------------
|
| 93 |
+
# Training Strategy Configuration
|
| 94 |
+
# -----------------------------------------------------------------------------
|
| 95 |
+
# Defines the training approach using the unified flexible strategy.
|
| 96 |
+
# This configuration trains both video and audio generation from text prompts.
|
| 97 |
+
training_strategy:
|
| 98 |
+
# Strategy name: "flexible" for the unified conditioning framework
|
| 99 |
+
# Supports all training modes (T2V, I2V, V2V, A2V, V2A, etc.) through
|
| 100 |
+
# modality-specific configuration blocks.
|
| 101 |
+
name: "flexible"
|
| 102 |
+
|
| 103 |
+
# Video modality configuration
|
| 104 |
+
# When is_generated is true, the model learns to generate (denoise) video
|
| 105 |
+
video:
|
| 106 |
+
# Whether the model generates video (true) or uses it as frozen conditioning (false)
|
| 107 |
+
is_generated: true
|
| 108 |
+
# Directory name (within preprocessed_data_root) containing video latents
|
| 109 |
+
latents_dir: "latents"
|
| 110 |
+
|
| 111 |
+
# Audio modality configuration
|
| 112 |
+
# When is_generated is true, the model learns to generate (denoise) audio
|
| 113 |
+
audio:
|
| 114 |
+
# Whether the model generates audio (true) or uses it as frozen conditioning (false)
|
| 115 |
+
is_generated: true
|
| 116 |
+
# Directory name (within preprocessed_data_root) containing audio latents
|
| 117 |
+
latents_dir: "audio_latents"
|
| 118 |
+
|
| 119 |
+
# -----------------------------------------------------------------------------
|
| 120 |
+
# Optimization Configuration
|
| 121 |
+
# -----------------------------------------------------------------------------
|
| 122 |
+
# Controls the training optimization parameters.
|
| 123 |
+
optimization:
|
| 124 |
+
# Learning rate for the optimizer
|
| 125 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 126 |
+
learning_rate: 1e-4
|
| 127 |
+
|
| 128 |
+
# Total number of training steps
|
| 129 |
+
steps: 2000
|
| 130 |
+
|
| 131 |
+
# Batch size per GPU
|
| 132 |
+
# Reduce if running out of memory
|
| 133 |
+
batch_size: 1
|
| 134 |
+
|
| 135 |
+
# Number of gradient accumulation steps
|
| 136 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 137 |
+
gradient_accumulation_steps: 1
|
| 138 |
+
|
| 139 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 140 |
+
max_grad_norm: 1.0
|
| 141 |
+
|
| 142 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 143 |
+
optimizer_type: "adamw"
|
| 144 |
+
|
| 145 |
+
# Learning rate scheduler type
|
| 146 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 147 |
+
scheduler_type: "linear"
|
| 148 |
+
|
| 149 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 150 |
+
scheduler_params: { }
|
| 151 |
+
|
| 152 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 153 |
+
# Recommended for training with limited GPU memory
|
| 154 |
+
enable_gradient_checkpointing: true
|
| 155 |
+
|
| 156 |
+
# -----------------------------------------------------------------------------
|
| 157 |
+
# Acceleration Configuration
|
| 158 |
+
# -----------------------------------------------------------------------------
|
| 159 |
+
# Hardware acceleration and memory optimization settings.
|
| 160 |
+
acceleration:
|
| 161 |
+
# Mixed precision training mode
|
| 162 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 163 |
+
mixed_precision_mode: "bf16"
|
| 164 |
+
|
| 165 |
+
# Model quantization for reduced memory usage
|
| 166 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 167 |
+
quantization: null
|
| 168 |
+
|
| 169 |
+
# Load text encoder in 8-bit precision to save memory
|
| 170 |
+
# Useful when GPU memory is limited
|
| 171 |
+
load_text_encoder_in_8bit: false
|
| 172 |
+
|
| 173 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 174 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 175 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 176 |
+
offload_optimizer_during_validation: false
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
# -----------------------------------------------------------------------------
|
| 180 |
+
# Data Configuration
|
| 181 |
+
# -----------------------------------------------------------------------------
|
| 182 |
+
# Specifies the training data location and loading parameters.
|
| 183 |
+
data:
|
| 184 |
+
# Root directory containing preprocessed training data
|
| 185 |
+
# Should contain: latents/, conditions/, and audio_latents/
|
| 186 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 187 |
+
|
| 188 |
+
# Number of worker processes for data loading
|
| 189 |
+
# Used for parallel data loading to speed up data loading
|
| 190 |
+
num_dataloader_workers: 2
|
| 191 |
+
|
| 192 |
+
# -----------------------------------------------------------------------------
|
| 193 |
+
# Validation Configuration
|
| 194 |
+
# -----------------------------------------------------------------------------
|
| 195 |
+
# Controls validation sampling during training.
|
| 196 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 197 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 198 |
+
validation:
|
| 199 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 200 |
+
# Use 'conditions' to add conditioning (first_frame, prefix, suffix, reference, video_to_audio, etc.)
|
| 201 |
+
# See docs/configuration-reference.md#validation-condition-types for the full list of condition types.
|
| 202 |
+
samples:
|
| 203 |
+
- prompt: >-
|
| 204 |
+
A woman with long brown hair sits at a wooden desk in a cozy home office, typing on a
|
| 205 |
+
laptop while occasionally glancing at notes beside her. Soft natural light streams through
|
| 206 |
+
a large window, casting warm shadows across the room. She pauses to take a sip from a
|
| 207 |
+
ceramic mug, then continues working with focused concentration. The audio captures the
|
| 208 |
+
gentle clicking of keyboard keys, the soft rustle of papers, and ambient room tone with
|
| 209 |
+
occasional distant bird chirps from outside.
|
| 210 |
+
- prompt: >-
|
| 211 |
+
A chef in a white uniform stands in a professional kitchen, carefully plating a gourmet
|
| 212 |
+
dish with precise movements. Steam rises from freshly cooked vegetables as he arranges
|
| 213 |
+
them with tweezers. The stainless steel surfaces gleam under bright overhead lights, and
|
| 214 |
+
various pots simmer on the stove behind him. The audio features the sizzling of pans,
|
| 215 |
+
the clinking of utensils against plates, and the ambient hum of kitchen ventilation.
|
| 216 |
+
|
| 217 |
+
# Negative prompt to avoid unwanted artifacts
|
| 218 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 219 |
+
|
| 220 |
+
# Output video dimensions [width, height, frames]
|
| 221 |
+
# Width and height must be divisible by 32
|
| 222 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 223 |
+
video_dims: [ 576, 576, 89 ]
|
| 224 |
+
|
| 225 |
+
# Frame rate for generated videos
|
| 226 |
+
frame_rate: 25.0
|
| 227 |
+
|
| 228 |
+
# Random seed for reproducible validation outputs
|
| 229 |
+
seed: 42
|
| 230 |
+
|
| 231 |
+
# Number of denoising steps for validation inference
|
| 232 |
+
# Higher values = better quality but slower generation
|
| 233 |
+
inference_steps: 30
|
| 234 |
+
|
| 235 |
+
# Generate validation videos every N training steps
|
| 236 |
+
# Set to null to disable validation during training
|
| 237 |
+
interval: 100
|
| 238 |
+
|
| 239 |
+
# Classifier-free guidance scale
|
| 240 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 241 |
+
guidance_scale: 4.0
|
| 242 |
+
|
| 243 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 244 |
+
# STG is combined with CFG for better temporal coherence
|
| 245 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 246 |
+
stg_blocks: [29] # Recommended: single block 29
|
| 247 |
+
stg_mode: "stg_av" # "stg_av" perturbs both audio and video, "stg_v" video only
|
| 248 |
+
|
| 249 |
+
# Whether to generate audio in validation samples
|
| 250 |
+
# Independent of training_strategy.audio.is_generated - you can generate audio
|
| 251 |
+
# in validation even when not training the audio branch
|
| 252 |
+
generate_audio: true
|
| 253 |
+
|
| 254 |
+
# Skip validation at the beginning of training (step 0)
|
| 255 |
+
skip_initial_validation: false
|
| 256 |
+
|
| 257 |
+
# -----------------------------------------------------------------------------
|
| 258 |
+
# Checkpoint Configuration
|
| 259 |
+
# -----------------------------------------------------------------------------
|
| 260 |
+
# Controls model checkpoint saving during training.
|
| 261 |
+
checkpoints:
|
| 262 |
+
# Save a checkpoint every N steps
|
| 263 |
+
# Set to null to disable intermediate checkpoints
|
| 264 |
+
interval: 250
|
| 265 |
+
|
| 266 |
+
# Number of most recent checkpoints to keep
|
| 267 |
+
# Set to -1 to keep all checkpoints
|
| 268 |
+
keep_last_n: -1
|
| 269 |
+
|
| 270 |
+
# Precision to use when saving checkpoint weights
|
| 271 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 272 |
+
precision: "bfloat16"
|
| 273 |
+
|
| 274 |
+
# -----------------------------------------------------------------------------
|
| 275 |
+
# Flow Matching Configuration
|
| 276 |
+
# -----------------------------------------------------------------------------
|
| 277 |
+
# Parameters for the flow matching training objective.
|
| 278 |
+
flow_matching:
|
| 279 |
+
# Timestep sampling mode
|
| 280 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 281 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 282 |
+
|
| 283 |
+
# Additional parameters for timestep sampling
|
| 284 |
+
timestep_sampling_params: { }
|
| 285 |
+
|
| 286 |
+
# -----------------------------------------------------------------------------
|
| 287 |
+
# Hugging Face Hub Configuration
|
| 288 |
+
# -----------------------------------------------------------------------------
|
| 289 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 290 |
+
hub:
|
| 291 |
+
# Whether to push the trained model to the Hub
|
| 292 |
+
push_to_hub: false
|
| 293 |
+
|
| 294 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-lora-model")
|
| 295 |
+
# Required if push_to_hub is true
|
| 296 |
+
hub_model_id: null
|
| 297 |
+
|
| 298 |
+
# -----------------------------------------------------------------------------
|
| 299 |
+
# Weights & Biases Configuration
|
| 300 |
+
# -----------------------------------------------------------------------------
|
| 301 |
+
# Settings for experiment tracking with W&B.
|
| 302 |
+
wandb:
|
| 303 |
+
# Enable W&B logging
|
| 304 |
+
enabled: false
|
| 305 |
+
|
| 306 |
+
# W&B project name
|
| 307 |
+
project: "ltx-2-trainer"
|
| 308 |
+
|
| 309 |
+
# W&B username or team (null uses default account)
|
| 310 |
+
entity: null
|
| 311 |
+
|
| 312 |
+
# Tags to help organize runs
|
| 313 |
+
tags: [ "ltx2", "lora", "t2v" ]
|
| 314 |
+
|
| 315 |
+
# Log validation media (video/audio) to W&B
|
| 316 |
+
log_validation_videos: true
|
| 317 |
+
|
| 318 |
+
# -----------------------------------------------------------------------------
|
| 319 |
+
# General Configuration
|
| 320 |
+
# -----------------------------------------------------------------------------
|
| 321 |
+
# Global settings for the training run.
|
| 322 |
+
|
| 323 |
+
# Random seed for reproducibility
|
| 324 |
+
seed: 42
|
| 325 |
+
|
| 326 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 327 |
+
output_dir: "outputs/t2v_lora"
|
packages/ltx-trainer/configs/t2v_lora_low_vram.yaml
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 Text-to-Video LoRA Training Configuration (Low VRAM)
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This is a memory-optimized variant of the standard text-to-video LoRA config.
|
| 6 |
+
# It uses 8-bit optimizer, int8 quantization, and reduced LoRA rank to minimize
|
| 7 |
+
# GPU memory usage while maintaining good training quality.
|
| 8 |
+
#
|
| 9 |
+
# Memory optimizations applied:
|
| 10 |
+
# - 8-bit AdamW optimizer (reduces optimizer state memory by ~75%)
|
| 11 |
+
# - INT8 model quantization (reduces model memory by ~50%)
|
| 12 |
+
# - Lower LoRA rank (16 vs 32, reduces trainable parameters)
|
| 13 |
+
# - Gradient checkpointing enabled
|
| 14 |
+
#
|
| 15 |
+
# Recommended for GPUs with 32GB VRAM (e.g., RTX 5090).
|
| 16 |
+
#
|
| 17 |
+
# Use this configuration when you want to:
|
| 18 |
+
# - Fine-tune LTX-2 on your own video dataset with limited GPU memory
|
| 19 |
+
# - Train joint audio-video generation from text prompts
|
| 20 |
+
# - Create custom video generation styles or audiovisual concepts
|
| 21 |
+
#
|
| 22 |
+
# Dataset structure:
|
| 23 |
+
# preprocessed_data_root/
|
| 24 |
+
# ├── latents/ # Video latents (VAE-encoded videos)
|
| 25 |
+
# ├── conditions/ # Text embeddings for each video
|
| 26 |
+
# └── audio_latents/ # Audio latents (VAE-encoded audio)
|
| 27 |
+
#
|
| 28 |
+
# =============================================================================
|
| 29 |
+
|
| 30 |
+
# -----------------------------------------------------------------------------
|
| 31 |
+
# Model Configuration
|
| 32 |
+
# -----------------------------------------------------------------------------
|
| 33 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 34 |
+
model:
|
| 35 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 36 |
+
# This should be a local path to your downloaded model
|
| 37 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 38 |
+
|
| 39 |
+
# Path to the text encoder model directory
|
| 40 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 41 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 42 |
+
|
| 43 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 44 |
+
# LoRA is recommended for most use cases (faster, less memory, prevents overfitting)
|
| 45 |
+
training_mode: "lora"
|
| 46 |
+
|
| 47 |
+
# Optional: Path to resume training from a checkpoint
|
| 48 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 49 |
+
load_checkpoint: null
|
| 50 |
+
|
| 51 |
+
# -----------------------------------------------------------------------------
|
| 52 |
+
# LoRA Configuration
|
| 53 |
+
# -----------------------------------------------------------------------------
|
| 54 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 55 |
+
# Using a lower rank (16) to reduce trainable parameters and memory usage.
|
| 56 |
+
# This still provides good capacity for many fine-tuning tasks.
|
| 57 |
+
lora:
|
| 58 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 59 |
+
# Typical values: 8, 16, 32, 64. Using 16 for low VRAM configuration.
|
| 60 |
+
rank: 16
|
| 61 |
+
|
| 62 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 63 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 64 |
+
alpha: 16
|
| 65 |
+
|
| 66 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 67 |
+
# Can help with regularization if overfitting occurs
|
| 68 |
+
dropout: 0.0
|
| 69 |
+
|
| 70 |
+
# Which transformer modules to apply LoRA to
|
| 71 |
+
# The LTX-2 transformer has separate attention and FFN blocks for video and audio:
|
| 72 |
+
#
|
| 73 |
+
# VIDEO MODULES:
|
| 74 |
+
# - attn1.to_k, attn1.to_q, attn1.to_v, attn1.to_out.0 (video self-attention)
|
| 75 |
+
# - attn2.to_k, attn2.to_q, attn2.to_v, attn2.to_out.0 (video cross-attention to text)
|
| 76 |
+
# - ff.net.0.proj, ff.net.2 (video feed-forward)
|
| 77 |
+
#
|
| 78 |
+
# AUDIO MODULES:
|
| 79 |
+
# - audio_attn1.to_k, audio_attn1.to_q, audio_attn1.to_v, audio_attn1.to_out.0 (audio self-attention)
|
| 80 |
+
# - audio_attn2.to_k, audio_attn2.to_q, audio_attn2.to_v, audio_attn2.to_out.0 (audio cross-attention to text)
|
| 81 |
+
# - audio_ff.net.0.proj, audio_ff.net.2 (audio feed-forward)
|
| 82 |
+
#
|
| 83 |
+
# AUDIO-VIDEO CROSS-ATTENTION MODULES (for cross-modal interaction):
|
| 84 |
+
# - audio_to_video_attn.to_k, audio_to_video_attn.to_q, audio_to_video_attn.to_v, audio_to_video_attn.to_out.0
|
| 85 |
+
# (Q from video, K/V from audio - allows video to attend to audio features)
|
| 86 |
+
# - video_to_audio_attn.to_k, video_to_audio_attn.to_q, video_to_audio_attn.to_v, video_to_audio_attn.to_out.0
|
| 87 |
+
# (Q from audio, K/V from video - allows audio to attend to video features)
|
| 88 |
+
#
|
| 89 |
+
# Using short patterns like "to_k" matches ALL attention modules (video, audio, and cross-modal).
|
| 90 |
+
# For audio-video training, this is the recommended approach.
|
| 91 |
+
target_modules:
|
| 92 |
+
# Attention layers (matches both video and audio branches)
|
| 93 |
+
- "to_k"
|
| 94 |
+
- "to_q"
|
| 95 |
+
- "to_v"
|
| 96 |
+
- "to_out.0"
|
| 97 |
+
# Uncomment below to also train feed-forward layers (can increase the LoRA's capacity):
|
| 98 |
+
# - "ff.net.0.proj"
|
| 99 |
+
# - "ff.net.2"
|
| 100 |
+
# - "audio_ff.net.0.proj"
|
| 101 |
+
# - "audio_ff.net.2"
|
| 102 |
+
|
| 103 |
+
# -----------------------------------------------------------------------------
|
| 104 |
+
# Training Strategy Configuration
|
| 105 |
+
# -----------------------------------------------------------------------------
|
| 106 |
+
# Defines the training approach using the unified flexible strategy.
|
| 107 |
+
# This configuration trains both video and audio generation from text prompts.
|
| 108 |
+
training_strategy:
|
| 109 |
+
# Strategy name: "flexible" for the unified conditioning framework
|
| 110 |
+
# Supports all training modes (T2V, I2V, V2V, A2V, V2A, etc.) through
|
| 111 |
+
# modality-specific configuration blocks.
|
| 112 |
+
name: "flexible"
|
| 113 |
+
|
| 114 |
+
# Video modality configuration
|
| 115 |
+
# When is_generated is true, the model learns to generate (denoise) video
|
| 116 |
+
video:
|
| 117 |
+
# Whether the model generates video (true) or uses it as frozen conditioning (false)
|
| 118 |
+
is_generated: true
|
| 119 |
+
# Directory name (within preprocessed_data_root) containing video latents
|
| 120 |
+
latents_dir: "latents"
|
| 121 |
+
|
| 122 |
+
# Audio modality configuration
|
| 123 |
+
# When is_generated is true, the model learns to generate (denoise) audio
|
| 124 |
+
audio:
|
| 125 |
+
# Whether the model generates audio (true) or uses it as frozen conditioning (false)
|
| 126 |
+
is_generated: true
|
| 127 |
+
# Directory name (within preprocessed_data_root) containing audio latents
|
| 128 |
+
latents_dir: "audio_latents"
|
| 129 |
+
|
| 130 |
+
# -----------------------------------------------------------------------------
|
| 131 |
+
# Optimization Configuration
|
| 132 |
+
# -----------------------------------------------------------------------------
|
| 133 |
+
# Controls the training optimization parameters.
|
| 134 |
+
optimization:
|
| 135 |
+
# Learning rate for the optimizer
|
| 136 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 137 |
+
learning_rate: 1e-4
|
| 138 |
+
|
| 139 |
+
# Total number of training steps
|
| 140 |
+
steps: 2000
|
| 141 |
+
|
| 142 |
+
# Batch size per GPU
|
| 143 |
+
# Reduce if running out of memory
|
| 144 |
+
batch_size: 1
|
| 145 |
+
|
| 146 |
+
# Number of gradient accumulation steps
|
| 147 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 148 |
+
gradient_accumulation_steps: 1
|
| 149 |
+
|
| 150 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 151 |
+
max_grad_norm: 1.0
|
| 152 |
+
|
| 153 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 154 |
+
# Using 8-bit AdamW to reduce optimizer state memory by ~75%
|
| 155 |
+
optimizer_type: "adamw8bit"
|
| 156 |
+
|
| 157 |
+
# Learning rate scheduler type
|
| 158 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 159 |
+
scheduler_type: "linear"
|
| 160 |
+
|
| 161 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 162 |
+
scheduler_params: { }
|
| 163 |
+
|
| 164 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 165 |
+
# Recommended for training with limited GPU memory
|
| 166 |
+
enable_gradient_checkpointing: true
|
| 167 |
+
|
| 168 |
+
# -----------------------------------------------------------------------------
|
| 169 |
+
# Acceleration Configuration
|
| 170 |
+
# -----------------------------------------------------------------------------
|
| 171 |
+
# Hardware acceleration and memory optimization settings.
|
| 172 |
+
acceleration:
|
| 173 |
+
# Mixed precision training mode
|
| 174 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 175 |
+
mixed_precision_mode: "bf16"
|
| 176 |
+
|
| 177 |
+
# Model quantization for reduced memory usage
|
| 178 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 179 |
+
# Using INT8 quantization to reduce base model memory consumption by ~50%
|
| 180 |
+
quantization: "int8-quanto"
|
| 181 |
+
|
| 182 |
+
# Load text encoder in 8-bit precision to save memory
|
| 183 |
+
# Useful when GPU memory is limited
|
| 184 |
+
load_text_encoder_in_8bit: true
|
| 185 |
+
|
| 186 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 187 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 188 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 189 |
+
offload_optimizer_during_validation: true
|
| 190 |
+
|
| 191 |
+
# -----------------------------------------------------------------------------
|
| 192 |
+
# Data Configuration
|
| 193 |
+
# -----------------------------------------------------------------------------
|
| 194 |
+
# Specifies the training data location and loading parameters.
|
| 195 |
+
data:
|
| 196 |
+
# Root directory containing preprocessed training data
|
| 197 |
+
# Should contain: latents/, conditions/, and audio_latents/
|
| 198 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 199 |
+
|
| 200 |
+
# Number of worker processes for data loading
|
| 201 |
+
# Used for parallel data loading to speed up data loading
|
| 202 |
+
num_dataloader_workers: 2
|
| 203 |
+
|
| 204 |
+
# -----------------------------------------------------------------------------
|
| 205 |
+
# Validation Configuration
|
| 206 |
+
# -----------------------------------------------------------------------------
|
| 207 |
+
# Controls validation sampling during training.
|
| 208 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 209 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 210 |
+
validation:
|
| 211 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 212 |
+
# Use 'conditions' to add conditioning (first_frame, prefix, suffix, reference, video_to_audio, etc.)
|
| 213 |
+
# See docs/configuration-reference.md#validation-condition-types for the full list of condition types.
|
| 214 |
+
samples:
|
| 215 |
+
- prompt: >-
|
| 216 |
+
A woman with long brown hair sits at a wooden desk in a cozy home office, typing on a
|
| 217 |
+
laptop while occasionally glancing at notes beside her. Soft natural light streams through
|
| 218 |
+
a large window, casting warm shadows across the room. She pauses to take a sip from a
|
| 219 |
+
ceramic mug, then continues working with focused concentration. The audio captures the
|
| 220 |
+
gentle clicking of keyboard keys, the soft rustle of papers, and ambient room tone with
|
| 221 |
+
occasional distant bird chirps from outside.
|
| 222 |
+
- prompt: >-
|
| 223 |
+
A chef in a white uniform stands in a professional kitchen, carefully plating a gourmet
|
| 224 |
+
dish with precise movements. Steam rises from freshly cooked vegetables as he arranges
|
| 225 |
+
them with tweezers. The stainless steel surfaces gleam under bright overhead lights, and
|
| 226 |
+
various pots simmer on the stove behind him. The audio features the sizzling of pans,
|
| 227 |
+
the clinking of utensils against plates, and the ambient hum of kitchen ventilation.
|
| 228 |
+
|
| 229 |
+
# Negative prompt to avoid unwanted artifacts
|
| 230 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 231 |
+
|
| 232 |
+
# Output video dimensions [width, height, frames]
|
| 233 |
+
# Width and height must be divisible by 32
|
| 234 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 235 |
+
video_dims: [ 576, 576, 49 ]
|
| 236 |
+
|
| 237 |
+
# Frame rate for generated videos
|
| 238 |
+
frame_rate: 25.0
|
| 239 |
+
|
| 240 |
+
# Random seed for reproducible validation outputs
|
| 241 |
+
seed: 42
|
| 242 |
+
|
| 243 |
+
# Number of denoising steps for validation inference
|
| 244 |
+
# Higher values = better quality but slower generation
|
| 245 |
+
inference_steps: 30
|
| 246 |
+
|
| 247 |
+
# Generate validation videos every N training steps
|
| 248 |
+
# Set to null to disable validation during training
|
| 249 |
+
interval: 100
|
| 250 |
+
|
| 251 |
+
# Classifier-free guidance scale
|
| 252 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 253 |
+
guidance_scale: 4.0
|
| 254 |
+
|
| 255 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 256 |
+
# STG is combined with CFG for better temporal coherence
|
| 257 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 258 |
+
stg_blocks: [ 29 ] # Recommended: single block 29
|
| 259 |
+
stg_mode: "stg_av" # "stg_av" perturbs both audio and video, "stg_v" video only
|
| 260 |
+
|
| 261 |
+
# Whether to generate audio in validation samples
|
| 262 |
+
# Independent of training_strategy audio modality settings — you can generate audio
|
| 263 |
+
# in validation even when not training the audio branch
|
| 264 |
+
generate_audio: true
|
| 265 |
+
|
| 266 |
+
# Skip validation at the beginning of training (step 0)
|
| 267 |
+
skip_initial_validation: false
|
| 268 |
+
|
| 269 |
+
# -----------------------------------------------------------------------------
|
| 270 |
+
# Checkpoint Configuration
|
| 271 |
+
# -----------------------------------------------------------------------------
|
| 272 |
+
# Controls model checkpoint saving during training.
|
| 273 |
+
checkpoints:
|
| 274 |
+
# Save a checkpoint every N steps
|
| 275 |
+
# Set to null to disable intermediate checkpoints
|
| 276 |
+
interval: 250
|
| 277 |
+
|
| 278 |
+
# Number of most recent checkpoints to keep
|
| 279 |
+
# Set to -1 to keep all checkpoints
|
| 280 |
+
keep_last_n: -1
|
| 281 |
+
|
| 282 |
+
# Precision to use when saving checkpoint weights
|
| 283 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 284 |
+
precision: "bfloat16"
|
| 285 |
+
|
| 286 |
+
# -----------------------------------------------------------------------------
|
| 287 |
+
# Flow Matching Configuration
|
| 288 |
+
# -----------------------------------------------------------------------------
|
| 289 |
+
# Parameters for the flow matching training objective.
|
| 290 |
+
flow_matching:
|
| 291 |
+
# Timestep sampling mode
|
| 292 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 293 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 294 |
+
|
| 295 |
+
# Additional parameters for timestep sampling
|
| 296 |
+
timestep_sampling_params: { }
|
| 297 |
+
|
| 298 |
+
# -----------------------------------------------------------------------------
|
| 299 |
+
# Hugging Face Hub Configuration
|
| 300 |
+
# -----------------------------------------------------------------------------
|
| 301 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 302 |
+
hub:
|
| 303 |
+
# Whether to push the trained model to the Hub
|
| 304 |
+
push_to_hub: false
|
| 305 |
+
|
| 306 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-lora-model")
|
| 307 |
+
# Required if push_to_hub is true
|
| 308 |
+
hub_model_id: null
|
| 309 |
+
|
| 310 |
+
# -----------------------------------------------------------------------------
|
| 311 |
+
# Weights & Biases Configuration
|
| 312 |
+
# -----------------------------------------------------------------------------
|
| 313 |
+
# Settings for experiment tracking with W&B.
|
| 314 |
+
wandb:
|
| 315 |
+
# Enable W&B logging
|
| 316 |
+
enabled: false
|
| 317 |
+
|
| 318 |
+
# W&B project name
|
| 319 |
+
project: "ltx-2-trainer"
|
| 320 |
+
|
| 321 |
+
# W&B username or team (null uses default account)
|
| 322 |
+
entity: null
|
| 323 |
+
|
| 324 |
+
# Tags to help organize runs
|
| 325 |
+
tags: [ "ltx2", "lora", "t2v", "low-vram" ]
|
| 326 |
+
|
| 327 |
+
# Log validation media (video/audio) to W&B
|
| 328 |
+
log_validation_videos: true
|
| 329 |
+
|
| 330 |
+
# -----------------------------------------------------------------------------
|
| 331 |
+
# General Configuration
|
| 332 |
+
# -----------------------------------------------------------------------------
|
| 333 |
+
# Global settings for the training run.
|
| 334 |
+
|
| 335 |
+
# Random seed for reproducibility
|
| 336 |
+
seed: 42
|
| 337 |
+
|
| 338 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 339 |
+
output_dir: "outputs/t2v_lora_low_vram"
|
packages/ltx-trainer/configs/v2a_lora.yaml
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 Video-to-Audio (Foley) LoRA Training Configuration
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This configuration is for training LoRA adapters on the LTX-2 model for
|
| 6 |
+
# video-to-audio (Foley) generation. The model learns to generate audio
|
| 7 |
+
# conditioned on a frozen video signal via the transformer's built-in
|
| 8 |
+
# cross-modal attention.
|
| 9 |
+
#
|
| 10 |
+
# In this mode, video is provided as a frozen (clean, no noise, no loss)
|
| 11 |
+
# conditioning signal. The audio modality is the only generated output.
|
| 12 |
+
# Video influences audio generation through the transformer's video-to-audio
|
| 13 |
+
# cross-attention mechanism.
|
| 14 |
+
#
|
| 15 |
+
# Use this configuration when you want to:
|
| 16 |
+
# - Generate sound effects (Foley) for existing videos
|
| 17 |
+
# - Train audio generation conditioned on visual content
|
| 18 |
+
# - Create models that produce audio matching video content
|
| 19 |
+
#
|
| 20 |
+
# Dataset structure:
|
| 21 |
+
# preprocessed_data_root/
|
| 22 |
+
# ├── latents/ # Video latents (frozen conditioning input)
|
| 23 |
+
# ├── conditions/ # Text embeddings for each video
|
| 24 |
+
# └── audio_latents/ # Audio latents (VAE-encoded audio, generated output)
|
| 25 |
+
#
|
| 26 |
+
# =============================================================================
|
| 27 |
+
|
| 28 |
+
# -----------------------------------------------------------------------------
|
| 29 |
+
# Model Configuration
|
| 30 |
+
# -----------------------------------------------------------------------------
|
| 31 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 32 |
+
model:
|
| 33 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 34 |
+
# This should be a local path to your downloaded model
|
| 35 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 36 |
+
|
| 37 |
+
# Path to the text encoder model directory
|
| 38 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 39 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 40 |
+
|
| 41 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 42 |
+
# LoRA is recommended for most use cases (faster, less memory, prevents overfitting)
|
| 43 |
+
training_mode: "lora"
|
| 44 |
+
|
| 45 |
+
# Optional: Path to resume training from a checkpoint
|
| 46 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 47 |
+
load_checkpoint: null
|
| 48 |
+
|
| 49 |
+
# -----------------------------------------------------------------------------
|
| 50 |
+
# LoRA Configuration
|
| 51 |
+
# -----------------------------------------------------------------------------
|
| 52 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 53 |
+
lora:
|
| 54 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 55 |
+
# Typical values: 8, 16, 32, 64. Start with 32 for general fine-tuning.
|
| 56 |
+
rank: 32
|
| 57 |
+
|
| 58 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 59 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 60 |
+
alpha: 32
|
| 61 |
+
|
| 62 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 63 |
+
# Can help with regularization if overfitting occurs
|
| 64 |
+
dropout: 0.0
|
| 65 |
+
|
| 66 |
+
# Which transformer modules to apply LoRA to
|
| 67 |
+
# The LTX-2 transformer has separate attention and FFN blocks for video and audio:
|
| 68 |
+
#
|
| 69 |
+
# VIDEO MODULES:
|
| 70 |
+
# - attn1.to_k, attn1.to_q, attn1.to_v, attn1.to_out.0 (video self-attention)
|
| 71 |
+
# - attn2.to_k, attn2.to_q, attn2.to_v, attn2.to_out.0 (video cross-attention to text)
|
| 72 |
+
# - ff.net.0.proj, ff.net.2 (video feed-forward)
|
| 73 |
+
#
|
| 74 |
+
# AUDIO MODULES:
|
| 75 |
+
# - audio_attn1.to_k, audio_attn1.to_q, audio_attn1.to_v, audio_attn1.to_out.0 (audio self-attention)
|
| 76 |
+
# - audio_attn2.to_k, audio_attn2.to_q, audio_attn2.to_v, audio_attn2.to_out.0 (audio cross-attention to text)
|
| 77 |
+
# - audio_ff.net.0.proj, audio_ff.net.2 (audio feed-forward)
|
| 78 |
+
#
|
| 79 |
+
# AUDIO-VIDEO CROSS-ATTENTION MODULES (for cross-modal interaction):
|
| 80 |
+
# - audio_to_video_attn.to_k, audio_to_video_attn.to_q, audio_to_video_attn.to_v, audio_to_video_attn.to_out.0
|
| 81 |
+
# (Q from video, K/V from audio - allows video to attend to audio features)
|
| 82 |
+
# - video_to_audio_attn.to_k, video_to_audio_attn.to_q, video_to_audio_attn.to_v, video_to_audio_attn.to_out.0
|
| 83 |
+
# (Q from audio, K/V from video - allows audio to attend to video features)
|
| 84 |
+
#
|
| 85 |
+
# For video-to-audio training, we target audio modules and the cross-modal
|
| 86 |
+
# attention that allows audio to attend to video features.
|
| 87 |
+
target_modules:
|
| 88 |
+
# Audio self-attention
|
| 89 |
+
- "audio_attn1.to_k"
|
| 90 |
+
- "audio_attn1.to_q"
|
| 91 |
+
- "audio_attn1.to_v"
|
| 92 |
+
- "audio_attn1.to_out.0"
|
| 93 |
+
# Audio cross-attention to text
|
| 94 |
+
- "audio_attn2.to_k"
|
| 95 |
+
- "audio_attn2.to_q"
|
| 96 |
+
- "audio_attn2.to_v"
|
| 97 |
+
- "audio_attn2.to_out.0"
|
| 98 |
+
# Audio feed-forward
|
| 99 |
+
- "audio_ff.net.0.proj"
|
| 100 |
+
- "audio_ff.net.2"
|
| 101 |
+
# Cross-modal attention: allows audio to attend to video features
|
| 102 |
+
- "video_to_audio_attn.to_k"
|
| 103 |
+
- "video_to_audio_attn.to_q"
|
| 104 |
+
- "video_to_audio_attn.to_v"
|
| 105 |
+
- "video_to_audio_attn.to_out.0"
|
| 106 |
+
|
| 107 |
+
# -----------------------------------------------------------------------------
|
| 108 |
+
# Training Strategy Configuration
|
| 109 |
+
# -----------------------------------------------------------------------------
|
| 110 |
+
# Defines the video-to-audio (Foley) training approach using the unified
|
| 111 |
+
# flexible strategy. Video is frozen (no noise, sigma=0, excluded from loss)
|
| 112 |
+
# and conditions audio generation via the transformer's cross-modal attention.
|
| 113 |
+
training_strategy:
|
| 114 |
+
# Strategy name: "flexible" for the unified conditioning framework
|
| 115 |
+
# Supports all training modes (T2V, I2V, V2V, A2V, V2A, etc.) through
|
| 116 |
+
# modality-specific configuration blocks.
|
| 117 |
+
name: "flexible"
|
| 118 |
+
|
| 119 |
+
# Video modality configuration
|
| 120 |
+
# Video is frozen — it acts as conditioning for audio generation
|
| 121 |
+
# Frozen modalities get sigma=0, timestep=0, no noise, and no loss
|
| 122 |
+
video:
|
| 123 |
+
# Whether the model generates video (true) or uses it as frozen conditioning (false)
|
| 124 |
+
# When false, video is passed through the transformer clean and influences
|
| 125 |
+
# audio via cross-modal attention
|
| 126 |
+
is_generated: false
|
| 127 |
+
# Directory name (within preprocessed_data_root) containing video latents
|
| 128 |
+
latents_dir: "latents"
|
| 129 |
+
|
| 130 |
+
# Audio modality configuration
|
| 131 |
+
# Audio is the generated (denoised) output
|
| 132 |
+
audio:
|
| 133 |
+
# Whether the model generates audio (true) or uses it as frozen conditioning (false)
|
| 134 |
+
is_generated: true
|
| 135 |
+
# Directory name (within preprocessed_data_root) containing audio latents
|
| 136 |
+
latents_dir: "audio_latents"
|
| 137 |
+
|
| 138 |
+
# -----------------------------------------------------------------------------
|
| 139 |
+
# Optimization Configuration
|
| 140 |
+
# -----------------------------------------------------------------------------
|
| 141 |
+
# Controls the training optimization parameters.
|
| 142 |
+
optimization:
|
| 143 |
+
# Learning rate for the optimizer
|
| 144 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 145 |
+
learning_rate: 1e-4
|
| 146 |
+
|
| 147 |
+
# Total number of training steps
|
| 148 |
+
steps: 2000
|
| 149 |
+
|
| 150 |
+
# Batch size per GPU
|
| 151 |
+
# Reduce if running out of memory
|
| 152 |
+
batch_size: 1
|
| 153 |
+
|
| 154 |
+
# Number of gradient accumulation steps
|
| 155 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 156 |
+
gradient_accumulation_steps: 1
|
| 157 |
+
|
| 158 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 159 |
+
max_grad_norm: 1.0
|
| 160 |
+
|
| 161 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 162 |
+
optimizer_type: "adamw"
|
| 163 |
+
|
| 164 |
+
# Learning rate scheduler type
|
| 165 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 166 |
+
scheduler_type: "linear"
|
| 167 |
+
|
| 168 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 169 |
+
scheduler_params: { }
|
| 170 |
+
|
| 171 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 172 |
+
# Recommended for training with limited GPU memory
|
| 173 |
+
enable_gradient_checkpointing: true
|
| 174 |
+
|
| 175 |
+
# -----------------------------------------------------------------------------
|
| 176 |
+
# Acceleration Configuration
|
| 177 |
+
# -----------------------------------------------------------------------------
|
| 178 |
+
# Hardware acceleration and memory optimization settings.
|
| 179 |
+
acceleration:
|
| 180 |
+
# Mixed precision training mode
|
| 181 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 182 |
+
mixed_precision_mode: "bf16"
|
| 183 |
+
|
| 184 |
+
# Model quantization for reduced memory usage
|
| 185 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 186 |
+
quantization: null
|
| 187 |
+
|
| 188 |
+
# Load text encoder in 8-bit precision to save memory
|
| 189 |
+
# Useful when GPU memory is limited
|
| 190 |
+
load_text_encoder_in_8bit: false
|
| 191 |
+
|
| 192 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 193 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 194 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 195 |
+
offload_optimizer_during_validation: false
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
# -----------------------------------------------------------------------------
|
| 199 |
+
# Data Configuration
|
| 200 |
+
# -----------------------------------------------------------------------------
|
| 201 |
+
# Specifies the training data location and loading parameters.
|
| 202 |
+
data:
|
| 203 |
+
# Root directory containing preprocessed training data
|
| 204 |
+
# Should contain: latents/, audio_latents/, and conditions/
|
| 205 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 206 |
+
|
| 207 |
+
# Number of worker processes for data loading
|
| 208 |
+
# Used for parallel data loading to speed up data loading
|
| 209 |
+
num_dataloader_workers: 2
|
| 210 |
+
|
| 211 |
+
# -----------------------------------------------------------------------------
|
| 212 |
+
# Validation Configuration
|
| 213 |
+
# -----------------------------------------------------------------------------
|
| 214 |
+
# Controls validation sampling during training.
|
| 215 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 216 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 217 |
+
validation:
|
| 218 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 219 |
+
# Use 'conditions' to add conditioning (first_frame, prefix, suffix, reference, video_to_audio, etc.)
|
| 220 |
+
# See docs/configuration-reference.md#validation-condition-types for the full list of condition types.
|
| 221 |
+
samples:
|
| 222 |
+
- prompt: >-
|
| 223 |
+
The sound of ocean waves crashing against rocky cliffs, with seagulls calling in the
|
| 224 |
+
distance and wind whistling through coastal grass.
|
| 225 |
+
conditions:
|
| 226 |
+
- type: video_to_audio
|
| 227 |
+
video: "/path/to/conditioning_video_1.mp4"
|
| 228 |
+
- prompt: >-
|
| 229 |
+
Footsteps echo in a marble hallway as a person walks steadily, with the distant hum of
|
| 230 |
+
air conditioning and occasional door closing sounds.
|
| 231 |
+
conditions:
|
| 232 |
+
- type: video_to_audio
|
| 233 |
+
video: "/path/to/conditioning_video_2.mp4"
|
| 234 |
+
|
| 235 |
+
# Negative prompt to avoid unwanted artifacts
|
| 236 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 237 |
+
|
| 238 |
+
# Validation dimensions [width, height, frames]
|
| 239 |
+
# Width/height resize the frozen video condition; frames and frame_rate set audio duration.
|
| 240 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 241 |
+
video_dims: [ 576, 576, 89 ]
|
| 242 |
+
|
| 243 |
+
# Frame rate for generated videos
|
| 244 |
+
frame_rate: 25.0
|
| 245 |
+
|
| 246 |
+
# Random seed for reproducible validation outputs
|
| 247 |
+
seed: 42
|
| 248 |
+
|
| 249 |
+
# Number of denoising steps for validation inference
|
| 250 |
+
# Higher values = better quality but slower generation
|
| 251 |
+
inference_steps: 30
|
| 252 |
+
|
| 253 |
+
# Generate validation videos every N training steps
|
| 254 |
+
# Set to null to disable validation during training
|
| 255 |
+
interval: 100
|
| 256 |
+
|
| 257 |
+
# Classifier-free guidance scale
|
| 258 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 259 |
+
guidance_scale: 4.0
|
| 260 |
+
|
| 261 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 262 |
+
# STG is combined with CFG for better temporal coherence
|
| 263 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 264 |
+
stg_blocks: [29] # Recommended: single block 29
|
| 265 |
+
stg_mode: "stg_av" # "stg_av" perturbs both audio and video, "stg_v" video only
|
| 266 |
+
|
| 267 |
+
# Whether to generate audio in validation samples
|
| 268 |
+
# Independent of training_strategy.audio.is_generated - you can generate audio
|
| 269 |
+
# in validation even when not training the audio branch
|
| 270 |
+
generate_audio: true
|
| 271 |
+
|
| 272 |
+
# Whether to generate video in validation samples
|
| 273 |
+
# Disabled for V2A since video is frozen conditioning, not generated
|
| 274 |
+
generate_video: false
|
| 275 |
+
|
| 276 |
+
# Skip validation at the beginning of training (step 0)
|
| 277 |
+
skip_initial_validation: false
|
| 278 |
+
|
| 279 |
+
# -----------------------------------------------------------------------------
|
| 280 |
+
# Checkpoint Configuration
|
| 281 |
+
# -----------------------------------------------------------------------------
|
| 282 |
+
# Controls model checkpoint saving during training.
|
| 283 |
+
checkpoints:
|
| 284 |
+
# Save a checkpoint every N steps
|
| 285 |
+
# Set to null to disable intermediate checkpoints
|
| 286 |
+
interval: 250
|
| 287 |
+
|
| 288 |
+
# Number of most recent checkpoints to keep
|
| 289 |
+
# Set to -1 to keep all checkpoints
|
| 290 |
+
keep_last_n: -1
|
| 291 |
+
|
| 292 |
+
# Precision to use when saving checkpoint weights
|
| 293 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 294 |
+
precision: "bfloat16"
|
| 295 |
+
|
| 296 |
+
# -----------------------------------------------------------------------------
|
| 297 |
+
# Flow Matching Configuration
|
| 298 |
+
# -----------------------------------------------------------------------------
|
| 299 |
+
# Parameters for the flow matching training objective.
|
| 300 |
+
flow_matching:
|
| 301 |
+
# Timestep sampling mode
|
| 302 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 303 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 304 |
+
|
| 305 |
+
# Additional parameters for timestep sampling
|
| 306 |
+
timestep_sampling_params: { }
|
| 307 |
+
|
| 308 |
+
# -----------------------------------------------------------------------------
|
| 309 |
+
# Hugging Face Hub Configuration
|
| 310 |
+
# -----------------------------------------------------------------------------
|
| 311 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 312 |
+
hub:
|
| 313 |
+
# Whether to push the trained model to the Hub
|
| 314 |
+
push_to_hub: false
|
| 315 |
+
|
| 316 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-lora-model")
|
| 317 |
+
# Required if push_to_hub is true
|
| 318 |
+
hub_model_id: null
|
| 319 |
+
|
| 320 |
+
# -----------------------------------------------------------------------------
|
| 321 |
+
# Weights & Biases Configuration
|
| 322 |
+
# -----------------------------------------------------------------------------
|
| 323 |
+
# Settings for experiment tracking with W&B.
|
| 324 |
+
wandb:
|
| 325 |
+
# Enable W&B logging
|
| 326 |
+
enabled: false
|
| 327 |
+
|
| 328 |
+
# W&B project name
|
| 329 |
+
project: "ltx-2-trainer"
|
| 330 |
+
|
| 331 |
+
# W&B username or team (null uses default account)
|
| 332 |
+
entity: null
|
| 333 |
+
|
| 334 |
+
# Tags to help organize runs
|
| 335 |
+
tags: [ "ltx2", "lora", "v2a", "foley" ]
|
| 336 |
+
|
| 337 |
+
# Log validation media (video/audio) to W&B
|
| 338 |
+
log_validation_videos: true
|
| 339 |
+
|
| 340 |
+
# -----------------------------------------------------------------------------
|
| 341 |
+
# General Configuration
|
| 342 |
+
# -----------------------------------------------------------------------------
|
| 343 |
+
# Global settings for the training run.
|
| 344 |
+
|
| 345 |
+
# Random seed for reproducibility
|
| 346 |
+
seed: 42
|
| 347 |
+
|
| 348 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 349 |
+
output_dir: "outputs/v2a_lora"
|
packages/ltx-trainer/configs/v2v_ic_lora.yaml
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 Video-to-Video (IC-LoRA) Training Configuration
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This configuration is for training In-Context LoRA (IC-LoRA) adapters that
|
| 6 |
+
# enable video-to-video transformations. IC-LoRA learns to apply visual
|
| 7 |
+
# transformations (e.g., depth-to-video, pose control, style transfer, etc.)
|
| 8 |
+
# by conditioning on reference videos.
|
| 9 |
+
#
|
| 10 |
+
# Key differences from text-to-video LoRA:
|
| 11 |
+
# - Uses reference videos as conditioning input alongside text prompts
|
| 12 |
+
# - Requires preprocessed reference latents in addition to target latents
|
| 13 |
+
# - Validation requires reference videos to demonstrate the transformation
|
| 14 |
+
#
|
| 15 |
+
# Dataset structure for IC-LoRA training:
|
| 16 |
+
# preprocessed_data_root/
|
| 17 |
+
# ├── latents/ # Target video latents (what the model learns to generate)
|
| 18 |
+
# ├── conditions/ # Text embeddings for each video
|
| 19 |
+
# └── reference_latents/ # Reference video latents (conditioning input)
|
| 20 |
+
#
|
| 21 |
+
# Dataset metadata columns: video, reference_video, caption
|
| 22 |
+
#
|
| 23 |
+
# =============================================================================
|
| 24 |
+
|
| 25 |
+
# -----------------------------------------------------------------------------
|
| 26 |
+
# Model Configuration
|
| 27 |
+
# -----------------------------------------------------------------------------
|
| 28 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 29 |
+
model:
|
| 30 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 31 |
+
# This should be a local path to your downloaded model
|
| 32 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 33 |
+
|
| 34 |
+
# Path to the text encoder model directory
|
| 35 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 36 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 37 |
+
|
| 38 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 39 |
+
# IC-LoRA reference conditioning is intended for LoRA adapter training.
|
| 40 |
+
training_mode: "lora"
|
| 41 |
+
|
| 42 |
+
# Optional: Path to resume training from a checkpoint
|
| 43 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 44 |
+
load_checkpoint: null
|
| 45 |
+
|
| 46 |
+
# -----------------------------------------------------------------------------
|
| 47 |
+
# LoRA Configuration
|
| 48 |
+
# -----------------------------------------------------------------------------
|
| 49 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 50 |
+
lora:
|
| 51 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 52 |
+
# Typical values: 8, 16, 32, 64. Start with 16-32 for IC-LoRA.
|
| 53 |
+
rank: 32
|
| 54 |
+
|
| 55 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 56 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 57 |
+
alpha: 32
|
| 58 |
+
|
| 59 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 60 |
+
# Can help with regularization if overfitting occurs
|
| 61 |
+
dropout: 0.0
|
| 62 |
+
|
| 63 |
+
# Which transformer modules to apply LoRA to
|
| 64 |
+
# The LTX-2 transformer has separate attention and FFN blocks for video and audio:
|
| 65 |
+
#
|
| 66 |
+
# VIDEO MODULES:
|
| 67 |
+
# - attn1.to_k, attn1.to_q, attn1.to_v, attn1.to_out.0 (video self-attention)
|
| 68 |
+
# - attn2.to_k, attn2.to_q, attn2.to_v, attn2.to_out.0 (video cross-attention to text)
|
| 69 |
+
# - ff.net.0.proj, ff.net.2 (video feed-forward)
|
| 70 |
+
#
|
| 71 |
+
# AUDIO MODULES (not used for video-only IC-LoRA):
|
| 72 |
+
# - audio_attn1.to_k, audio_attn1.to_q, audio_attn1.to_v, audio_attn1.to_out.0 (audio self-attention)
|
| 73 |
+
# - audio_attn2.to_k, audio_attn2.to_q, audio_attn2.to_v, audio_attn2.to_out.0 (audio cross-attention to text)
|
| 74 |
+
# - audio_ff.net.0.proj, audio_ff.net.2 (audio feed-forward)
|
| 75 |
+
#
|
| 76 |
+
# AUDIO-VIDEO CROSS-ATTENTION MODULES (for cross-modal interaction, not used for video-only IC-LoRA):
|
| 77 |
+
# - audio_to_video_attn.to_k, audio_to_video_attn.to_q, audio_to_video_attn.to_v, audio_to_video_attn.to_out.0
|
| 78 |
+
# (Q from video, K/V from audio - allows video to attend to audio features)
|
| 79 |
+
# - video_to_audio_attn.to_k, video_to_audio_attn.to_q, video_to_audio_attn.to_v, video_to_audio_attn.to_out.0
|
| 80 |
+
# (Q from audio, K/V from video - allows audio to attend to video features)
|
| 81 |
+
#
|
| 82 |
+
# For IC-LoRA (video-only), we explicitly target video modules.
|
| 83 |
+
# Including FFN layers often improves transformation quality.
|
| 84 |
+
target_modules:
|
| 85 |
+
# Video self-attention
|
| 86 |
+
- "attn1.to_k"
|
| 87 |
+
- "attn1.to_q"
|
| 88 |
+
- "attn1.to_v"
|
| 89 |
+
- "attn1.to_out.0"
|
| 90 |
+
# Video cross-attention
|
| 91 |
+
- "attn2.to_k"
|
| 92 |
+
- "attn2.to_q"
|
| 93 |
+
- "attn2.to_v"
|
| 94 |
+
- "attn2.to_out.0"
|
| 95 |
+
# Video feed-forward (often improves transformation quality)
|
| 96 |
+
- "ff.net.0.proj"
|
| 97 |
+
- "ff.net.2"
|
| 98 |
+
|
| 99 |
+
# -----------------------------------------------------------------------------
|
| 100 |
+
# Training Strategy Configuration
|
| 101 |
+
# -----------------------------------------------------------------------------
|
| 102 |
+
# Defines the video-to-video (IC-LoRA) training approach using the unified
|
| 103 |
+
# flexible strategy. Reference conditioning concatenates pre-encoded reference
|
| 104 |
+
# video latents to the target sequence. Reference tokens participate in
|
| 105 |
+
# bidirectional self-attention but receive no noise and are excluded from loss.
|
| 106 |
+
training_strategy:
|
| 107 |
+
# Strategy name: "flexible" for the unified conditioning framework
|
| 108 |
+
# Supports all training modes (T2V, I2V, V2V, A2V, V2A, etc.) through
|
| 109 |
+
# modality-specific configuration blocks.
|
| 110 |
+
name: "flexible"
|
| 111 |
+
|
| 112 |
+
# Video modality configuration
|
| 113 |
+
video:
|
| 114 |
+
# Whether the model generates video (true) or uses it as frozen conditioning (false)
|
| 115 |
+
is_generated: true
|
| 116 |
+
# Directory name (within preprocessed_data_root) containing target video latents
|
| 117 |
+
latents_dir: "latents"
|
| 118 |
+
|
| 119 |
+
# Conditions applied to the video modality during training
|
| 120 |
+
conditions:
|
| 121 |
+
# Reference conditioning (IC-LoRA): concatenates pre-encoded reference video
|
| 122 |
+
# latents to the target sequence. The model learns to transform the reference
|
| 123 |
+
# into the target video based on the text prompt.
|
| 124 |
+
- type: reference
|
| 125 |
+
# Directory name (within preprocessed_data_root) containing reference video latents
|
| 126 |
+
# These are the conditioning inputs that guide the transformation
|
| 127 |
+
latents_dir: "reference_latents"
|
| 128 |
+
# Probability of applying reference conditioning per training sample
|
| 129 |
+
probability: 1.0
|
| 130 |
+
|
| 131 |
+
# Optional first-frame conditioning to improve I2V capabilities
|
| 132 |
+
# At low probability, this teaches the model to also accept first-frame input
|
| 133 |
+
- type: first_frame
|
| 134 |
+
probability: 0.2
|
| 135 |
+
|
| 136 |
+
# -----------------------------------------------------------------------------
|
| 137 |
+
# Optimization Configuration
|
| 138 |
+
# -----------------------------------------------------------------------------
|
| 139 |
+
# Controls the training optimization parameters.
|
| 140 |
+
optimization:
|
| 141 |
+
# Learning rate for the optimizer
|
| 142 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 143 |
+
learning_rate: 2e-4
|
| 144 |
+
|
| 145 |
+
# Total number of training steps
|
| 146 |
+
steps: 3000
|
| 147 |
+
|
| 148 |
+
# Batch size per GPU
|
| 149 |
+
# Reduce if running out of memory
|
| 150 |
+
batch_size: 1
|
| 151 |
+
|
| 152 |
+
# Number of gradient accumulation steps
|
| 153 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 154 |
+
gradient_accumulation_steps: 1
|
| 155 |
+
|
| 156 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 157 |
+
max_grad_norm: 1.0
|
| 158 |
+
|
| 159 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 160 |
+
optimizer_type: "adamw"
|
| 161 |
+
|
| 162 |
+
# Learning rate scheduler type
|
| 163 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 164 |
+
scheduler_type: "linear"
|
| 165 |
+
|
| 166 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 167 |
+
scheduler_params: { }
|
| 168 |
+
|
| 169 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 170 |
+
# Recommended for training with limited GPU memory
|
| 171 |
+
enable_gradient_checkpointing: true
|
| 172 |
+
|
| 173 |
+
# -----------------------------------------------------------------------------
|
| 174 |
+
# Acceleration Configuration
|
| 175 |
+
# -----------------------------------------------------------------------------
|
| 176 |
+
# Hardware acceleration and memory optimization settings.
|
| 177 |
+
acceleration:
|
| 178 |
+
# Mixed precision training mode
|
| 179 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 180 |
+
mixed_precision_mode: "bf16"
|
| 181 |
+
|
| 182 |
+
# Model quantization for reduced memory usage
|
| 183 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 184 |
+
quantization: null
|
| 185 |
+
|
| 186 |
+
# Load text encoder in 8-bit precision to save memory
|
| 187 |
+
# Useful when GPU memory is limited
|
| 188 |
+
load_text_encoder_in_8bit: false
|
| 189 |
+
|
| 190 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 191 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 192 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 193 |
+
offload_optimizer_during_validation: false
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
# -----------------------------------------------------------------------------
|
| 197 |
+
# Data Configuration
|
| 198 |
+
# -----------------------------------------------------------------------------
|
| 199 |
+
# Specifies the training data location and loading parameters.
|
| 200 |
+
data:
|
| 201 |
+
# Root directory containing preprocessed training data
|
| 202 |
+
# Should contain: latents/, conditions/, and reference_latents/ subdirectories
|
| 203 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 204 |
+
|
| 205 |
+
# Number of worker processes for data loading
|
| 206 |
+
# Used for parallel data loading to speed up data loading
|
| 207 |
+
num_dataloader_workers: 2
|
| 208 |
+
|
| 209 |
+
# -----------------------------------------------------------------------------
|
| 210 |
+
# Validation Configuration
|
| 211 |
+
# -----------------------------------------------------------------------------
|
| 212 |
+
# Controls validation sampling during training.
|
| 213 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 214 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 215 |
+
validation:
|
| 216 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 217 |
+
# For IC-LoRA, each sample includes a reference condition pointing to the conditioning video.
|
| 218 |
+
samples:
|
| 219 |
+
- prompt: >-
|
| 220 |
+
A man in a casual blue jacket walks along a winding path through a lush green park on a
|
| 221 |
+
bright sunny afternoon. Tall oak trees line the pathway, their leaves rustling gently in
|
| 222 |
+
the breeze. Dappled sunlight creates shifting patterns on the ground as he strolls at a
|
| 223 |
+
relaxed pace, occasionally looking up at the scenery around him. The audio captures
|
| 224 |
+
footsteps on gravel, birds singing in the trees, distant children playing, and the soft
|
| 225 |
+
whisper of wind through the foliage.
|
| 226 |
+
conditions:
|
| 227 |
+
- type: reference
|
| 228 |
+
video: "/path/to/reference_video_1.mp4"
|
| 229 |
+
# Set these to match --reference-downscale-factor / --reference-temporal-scale-factor
|
| 230 |
+
# if reference latents were preprocessed at reduced spatial or temporal resolution.
|
| 231 |
+
downscale_factor: 1
|
| 232 |
+
temporal_scale_factor: 1
|
| 233 |
+
include_in_output: true
|
| 234 |
+
- prompt: >-
|
| 235 |
+
A fluffy orange tabby cat sits perfectly still on a wooden windowsill, its green eyes
|
| 236 |
+
intently tracking small birds hopping on a branch just outside the glass. The cat's ears
|
| 237 |
+
twitch and rotate, following every movement. Warm afternoon light illuminates its fur,
|
| 238 |
+
creating a soft golden glow. Behind the cat, a cozy living room with a bookshelf and
|
| 239 |
+
houseplants is visible. The audio features gentle purring, occasional soft meows, muffled
|
| 240 |
+
bird chirps through the window, and quiet ambient room sounds.
|
| 241 |
+
conditions:
|
| 242 |
+
- type: reference
|
| 243 |
+
video: "/path/to/reference_video_2.mp4"
|
| 244 |
+
# Set these to match --reference-downscale-factor / --reference-temporal-scale-factor
|
| 245 |
+
# if reference latents were preprocessed at reduced spatial or temporal resolution.
|
| 246 |
+
downscale_factor: 1
|
| 247 |
+
temporal_scale_factor: 1
|
| 248 |
+
include_in_output: true
|
| 249 |
+
|
| 250 |
+
# Negative prompt to avoid unwanted artifacts
|
| 251 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 252 |
+
|
| 253 |
+
# Output video dimensions [width, height, frames]
|
| 254 |
+
# Width and height must be divisible by 32
|
| 255 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 256 |
+
video_dims: [ 512, 512, 81 ]
|
| 257 |
+
|
| 258 |
+
# Frame rate for generated videos
|
| 259 |
+
frame_rate: 25.0
|
| 260 |
+
|
| 261 |
+
# Random seed for reproducible validation outputs
|
| 262 |
+
seed: 42
|
| 263 |
+
|
| 264 |
+
# Number of denoising steps for validation inference
|
| 265 |
+
# Higher values = better quality but slower generation
|
| 266 |
+
inference_steps: 30
|
| 267 |
+
|
| 268 |
+
# Generate validation videos every N training steps
|
| 269 |
+
# Set to null to disable validation during training
|
| 270 |
+
interval: 100
|
| 271 |
+
|
| 272 |
+
# Classifier-free guidance scale
|
| 273 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 274 |
+
guidance_scale: 4.0
|
| 275 |
+
|
| 276 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 277 |
+
# STG is combined with CFG for better temporal coherence
|
| 278 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 279 |
+
stg_blocks: [29] # Recommended: single block 29
|
| 280 |
+
stg_mode: "stg_v" # "stg_v" for video-only (no audio training)
|
| 281 |
+
|
| 282 |
+
# Whether to generate audio in validation samples
|
| 283 |
+
# Can be enabled even when not training the audio branch
|
| 284 |
+
generate_audio: false
|
| 285 |
+
|
| 286 |
+
# Skip validation at the beginning of training (step 0)
|
| 287 |
+
skip_initial_validation: false
|
| 288 |
+
|
| 289 |
+
# -----------------------------------------------------------------------------
|
| 290 |
+
# Checkpoint Configuration
|
| 291 |
+
# -----------------------------------------------------------------------------
|
| 292 |
+
# Controls model checkpoint saving during training.
|
| 293 |
+
checkpoints:
|
| 294 |
+
# Save a checkpoint every N steps
|
| 295 |
+
# Set to null to disable intermediate checkpoints
|
| 296 |
+
interval: 250
|
| 297 |
+
|
| 298 |
+
# Number of most recent checkpoints to keep
|
| 299 |
+
# Set to -1 to keep all checkpoints
|
| 300 |
+
keep_last_n: 3
|
| 301 |
+
|
| 302 |
+
# Precision to use when saving checkpoint weights
|
| 303 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 304 |
+
precision: "bfloat16"
|
| 305 |
+
|
| 306 |
+
# -----------------------------------------------------------------------------
|
| 307 |
+
# Flow Matching Configuration
|
| 308 |
+
# -----------------------------------------------------------------------------
|
| 309 |
+
# Parameters for the flow matching training objective.
|
| 310 |
+
flow_matching:
|
| 311 |
+
# Timestep sampling mode
|
| 312 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 313 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 314 |
+
|
| 315 |
+
# Additional parameters for timestep sampling
|
| 316 |
+
timestep_sampling_params: { }
|
| 317 |
+
|
| 318 |
+
# -----------------------------------------------------------------------------
|
| 319 |
+
# Hugging Face Hub Configuration
|
| 320 |
+
# -----------------------------------------------------------------------------
|
| 321 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 322 |
+
hub:
|
| 323 |
+
# Whether to push the trained model to the Hub
|
| 324 |
+
push_to_hub: false
|
| 325 |
+
|
| 326 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-ic-lora-model")
|
| 327 |
+
# Required if push_to_hub is true
|
| 328 |
+
hub_model_id: null
|
| 329 |
+
|
| 330 |
+
# -----------------------------------------------------------------------------
|
| 331 |
+
# Weights & Biases Configuration
|
| 332 |
+
# -----------------------------------------------------------------------------
|
| 333 |
+
# Settings for experiment tracking with W&B.
|
| 334 |
+
wandb:
|
| 335 |
+
# Enable W&B logging
|
| 336 |
+
enabled: false
|
| 337 |
+
|
| 338 |
+
# W&B project name
|
| 339 |
+
project: "ltx-2-trainer"
|
| 340 |
+
|
| 341 |
+
# W&B username or team (null uses default account)
|
| 342 |
+
entity: null
|
| 343 |
+
|
| 344 |
+
# Tags to help organize runs
|
| 345 |
+
tags: [ "ltx2", "ic-lora", "v2v" ]
|
| 346 |
+
|
| 347 |
+
# Log validation media (video/audio) to W&B
|
| 348 |
+
log_validation_videos: true
|
| 349 |
+
|
| 350 |
+
# -----------------------------------------------------------------------------
|
| 351 |
+
# General Configuration
|
| 352 |
+
# -----------------------------------------------------------------------------
|
| 353 |
+
# Global settings for the training run.
|
| 354 |
+
|
| 355 |
+
# Random seed for reproducibility
|
| 356 |
+
seed: 42
|
| 357 |
+
|
| 358 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 359 |
+
output_dir: "outputs/v2v_ic_lora"
|
packages/ltx-trainer/configs/video_extend_lora.yaml
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 Video Extension LoRA Training Configuration
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This configuration is for training LoRA adapters on the LTX-2 model for
|
| 6 |
+
# video extension (temporal continuation). The model learns to extend a video
|
| 7 |
+
# forward in time by conditioning on a prefix of existing frames.
|
| 8 |
+
#
|
| 9 |
+
# Prefix conditioning works by providing the first N temporal units as clean
|
| 10 |
+
# conditioning signals during training — they receive no noise, timestep=0,
|
| 11 |
+
# and are excluded from the loss. The model learns to generate continuation
|
| 12 |
+
# frames that seamlessly follow the given prefix.
|
| 13 |
+
#
|
| 14 |
+
# Use this configuration when you want to:
|
| 15 |
+
# - Train the model to extend/continue existing videos
|
| 16 |
+
# - Fine-tune temporal continuation capabilities on custom datasets
|
| 17 |
+
# - Create seamless video extension models with optional audio
|
| 18 |
+
#
|
| 19 |
+
# Dataset structure:
|
| 20 |
+
# preprocessed_data_root/
|
| 21 |
+
# ├── latents/ # Video latents (VAE-encoded videos)
|
| 22 |
+
# ├── conditions/ # Text embeddings for each video
|
| 23 |
+
# └── audio_latents/ # Audio latents (VAE-encoded audio)
|
| 24 |
+
#
|
| 25 |
+
# =============================================================================
|
| 26 |
+
|
| 27 |
+
# -----------------------------------------------------------------------------
|
| 28 |
+
# Model Configuration
|
| 29 |
+
# -----------------------------------------------------------------------------
|
| 30 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 31 |
+
model:
|
| 32 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 33 |
+
# This should be a local path to your downloaded model
|
| 34 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 35 |
+
|
| 36 |
+
# Path to the text encoder model directory
|
| 37 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 38 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 39 |
+
|
| 40 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 41 |
+
# LoRA is recommended for most use cases (faster, less memory, prevents overfitting)
|
| 42 |
+
training_mode: "lora"
|
| 43 |
+
|
| 44 |
+
# Optional: Path to resume training from a checkpoint
|
| 45 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 46 |
+
load_checkpoint: null
|
| 47 |
+
|
| 48 |
+
# -----------------------------------------------------------------------------
|
| 49 |
+
# LoRA Configuration
|
| 50 |
+
# -----------------------------------------------------------------------------
|
| 51 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 52 |
+
lora:
|
| 53 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 54 |
+
# Typical values: 8, 16, 32, 64. Start with 32 for general fine-tuning.
|
| 55 |
+
rank: 32
|
| 56 |
+
|
| 57 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 58 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 59 |
+
alpha: 32
|
| 60 |
+
|
| 61 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 62 |
+
# Can help with regularization if overfitting occurs
|
| 63 |
+
dropout: 0.0
|
| 64 |
+
|
| 65 |
+
# Which transformer modules to apply LoRA to
|
| 66 |
+
# The LTX-2 transformer has separate attention and FFN blocks for video and audio:
|
| 67 |
+
#
|
| 68 |
+
# VIDEO MODULES:
|
| 69 |
+
# - attn1.to_k, attn1.to_q, attn1.to_v, attn1.to_out.0 (video self-attention)
|
| 70 |
+
# - attn2.to_k, attn2.to_q, attn2.to_v, attn2.to_out.0 (video cross-attention to text)
|
| 71 |
+
# - ff.net.0.proj, ff.net.2 (video feed-forward)
|
| 72 |
+
#
|
| 73 |
+
# AUDIO MODULES:
|
| 74 |
+
# - audio_attn1.to_k, audio_attn1.to_q, audio_attn1.to_v, audio_attn1.to_out.0 (audio self-attention)
|
| 75 |
+
# - audio_attn2.to_k, audio_attn2.to_q, audio_attn2.to_v, audio_attn2.to_out.0 (audio cross-attention to text)
|
| 76 |
+
# - audio_ff.net.0.proj, audio_ff.net.2 (audio feed-forward)
|
| 77 |
+
#
|
| 78 |
+
# AUDIO-VIDEO CROSS-ATTENTION MODULES (for cross-modal interaction):
|
| 79 |
+
# - audio_to_video_attn.to_k, audio_to_video_attn.to_q, audio_to_video_attn.to_v, audio_to_video_attn.to_out.0
|
| 80 |
+
# (Q from video, K/V from audio - allows video to attend to audio features)
|
| 81 |
+
# - video_to_audio_attn.to_k, video_to_audio_attn.to_q, video_to_audio_attn.to_v, video_to_audio_attn.to_out.0
|
| 82 |
+
# (Q from audio, K/V from video - allows audio to attend to video features)
|
| 83 |
+
#
|
| 84 |
+
# Using short patterns like "to_k" matches ALL attention modules (video, audio, and cross-modal).
|
| 85 |
+
# For audio-video training, this is the recommended approach.
|
| 86 |
+
target_modules:
|
| 87 |
+
# Attention layers (matches both video and audio branches)
|
| 88 |
+
- "to_k"
|
| 89 |
+
- "to_q"
|
| 90 |
+
- "to_v"
|
| 91 |
+
- "to_out.0"
|
| 92 |
+
# Uncomment below to also train feed-forward layers (can increase the LoRA's capacity):
|
| 93 |
+
# - "ff.net.0.proj"
|
| 94 |
+
# - "ff.net.2"
|
| 95 |
+
# - "audio_ff.net.0.proj"
|
| 96 |
+
# - "audio_ff.net.2"
|
| 97 |
+
|
| 98 |
+
# -----------------------------------------------------------------------------
|
| 99 |
+
# Training Strategy Configuration
|
| 100 |
+
# -----------------------------------------------------------------------------
|
| 101 |
+
# Defines the video extension training approach using the unified flexible strategy.
|
| 102 |
+
# Prefix conditioning provides the first N latent frames as clean conditioning,
|
| 103 |
+
# teaching the model to generate temporally coherent continuations.
|
| 104 |
+
training_strategy:
|
| 105 |
+
# Strategy name: "flexible" for the unified conditioning framework
|
| 106 |
+
# Supports all training modes (T2V, I2V, V2V, A2V, V2A, etc.) through
|
| 107 |
+
# modality-specific configuration blocks.
|
| 108 |
+
name: "flexible"
|
| 109 |
+
|
| 110 |
+
# Video modality configuration
|
| 111 |
+
video:
|
| 112 |
+
# Whether the model generates video (true) or uses it as frozen conditioning (false)
|
| 113 |
+
is_generated: true
|
| 114 |
+
# Directory name (within preprocessed_data_root) containing video latents
|
| 115 |
+
latents_dir: "latents"
|
| 116 |
+
|
| 117 |
+
# Conditions applied to the video modality during training
|
| 118 |
+
# Prefix conditioning: the first N temporal units are provided as clean
|
| 119 |
+
# conditioning signals (no noise, timestep=0, excluded from loss)
|
| 120 |
+
conditions:
|
| 121 |
+
- type: prefix
|
| 122 |
+
# Number of latent frames to use as conditioning prefix
|
| 123 |
+
# For prefix conditioning, N latent frames correspond to (N - 1) * 8 + 1 pixel frames.
|
| 124 |
+
# temporal_boundary=8 means 57 pixel frames are used as prefix.
|
| 125 |
+
temporal_boundary: 8
|
| 126 |
+
# Probability of applying prefix conditioning per training sample
|
| 127 |
+
# At 1.0, all training samples use video extension mode
|
| 128 |
+
probability: 1.0
|
| 129 |
+
|
| 130 |
+
# Audio modality configuration
|
| 131 |
+
audio:
|
| 132 |
+
# Whether the model generates audio (true) or uses it as frozen conditioning (false)
|
| 133 |
+
is_generated: true
|
| 134 |
+
# Directory name (within preprocessed_data_root) containing audio latents
|
| 135 |
+
latents_dir: "audio_latents"
|
| 136 |
+
|
| 137 |
+
# -----------------------------------------------------------------------------
|
| 138 |
+
# Optimization Configuration
|
| 139 |
+
# -----------------------------------------------------------------------------
|
| 140 |
+
# Controls the training optimization parameters.
|
| 141 |
+
optimization:
|
| 142 |
+
# Learning rate for the optimizer
|
| 143 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 144 |
+
learning_rate: 1e-4
|
| 145 |
+
|
| 146 |
+
# Total number of training steps
|
| 147 |
+
steps: 2000
|
| 148 |
+
|
| 149 |
+
# Batch size per GPU
|
| 150 |
+
# Reduce if running out of memory
|
| 151 |
+
batch_size: 1
|
| 152 |
+
|
| 153 |
+
# Number of gradient accumulation steps
|
| 154 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 155 |
+
gradient_accumulation_steps: 1
|
| 156 |
+
|
| 157 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 158 |
+
max_grad_norm: 1.0
|
| 159 |
+
|
| 160 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 161 |
+
optimizer_type: "adamw"
|
| 162 |
+
|
| 163 |
+
# Learning rate scheduler type
|
| 164 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 165 |
+
scheduler_type: "linear"
|
| 166 |
+
|
| 167 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 168 |
+
scheduler_params: { }
|
| 169 |
+
|
| 170 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 171 |
+
# Recommended for training with limited GPU memory
|
| 172 |
+
enable_gradient_checkpointing: true
|
| 173 |
+
|
| 174 |
+
# -----------------------------------------------------------------------------
|
| 175 |
+
# Acceleration Configuration
|
| 176 |
+
# -----------------------------------------------------------------------------
|
| 177 |
+
# Hardware acceleration and memory optimization settings.
|
| 178 |
+
acceleration:
|
| 179 |
+
# Mixed precision training mode
|
| 180 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 181 |
+
mixed_precision_mode: "bf16"
|
| 182 |
+
|
| 183 |
+
# Model quantization for reduced memory usage
|
| 184 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 185 |
+
quantization: null
|
| 186 |
+
|
| 187 |
+
# Load text encoder in 8-bit precision to save memory
|
| 188 |
+
# Useful when GPU memory is limited
|
| 189 |
+
load_text_encoder_in_8bit: false
|
| 190 |
+
|
| 191 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 192 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 193 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 194 |
+
offload_optimizer_during_validation: false
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
# -----------------------------------------------------------------------------
|
| 198 |
+
# Data Configuration
|
| 199 |
+
# -----------------------------------------------------------------------------
|
| 200 |
+
# Specifies the training data location and loading parameters.
|
| 201 |
+
data:
|
| 202 |
+
# Root directory containing preprocessed training data
|
| 203 |
+
# Should contain: latents/, conditions/, and audio_latents/
|
| 204 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 205 |
+
|
| 206 |
+
# Number of worker processes for data loading
|
| 207 |
+
# Used for parallel data loading to speed up data loading
|
| 208 |
+
num_dataloader_workers: 2
|
| 209 |
+
|
| 210 |
+
# -----------------------------------------------------------------------------
|
| 211 |
+
# Validation Configuration
|
| 212 |
+
# -----------------------------------------------------------------------------
|
| 213 |
+
# Controls validation sampling during training.
|
| 214 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 215 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 216 |
+
validation:
|
| 217 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 218 |
+
# Use 'conditions' to add conditioning (first_frame, prefix, suffix, reference, video_to_audio, etc.)
|
| 219 |
+
# See docs/configuration-reference.md#validation-condition-types for the full list of condition types.
|
| 220 |
+
samples:
|
| 221 |
+
- prompt: >-
|
| 222 |
+
A woman with long brown hair sits at a wooden desk in a cozy home office, typing on a
|
| 223 |
+
laptop while occasionally glancing at notes beside her. Soft natural light streams through
|
| 224 |
+
a large window, casting warm shadows across the room. She pauses to take a sip from a
|
| 225 |
+
ceramic mug, then continues working with focused concentration. The audio captures the
|
| 226 |
+
gentle clicking of keyboard keys, the soft rustle of papers, and ambient room tone with
|
| 227 |
+
occasional distant bird chirps from outside.
|
| 228 |
+
conditions:
|
| 229 |
+
- type: prefix
|
| 230 |
+
video: "/path/to/prefix_video_1.mp4"
|
| 231 |
+
# Matches temporal_boundary=8 during training: (8 - 1) * 8 + 1 = 57 frames
|
| 232 |
+
num_frames: 57
|
| 233 |
+
- prompt: >-
|
| 234 |
+
A chef in a white uniform stands in a professional kitchen, carefully plating a gourmet
|
| 235 |
+
dish with precise movements. Steam rises from freshly cooked vegetables as he arranges
|
| 236 |
+
them with tweezers. The stainless steel surfaces gleam under bright overhead lights, and
|
| 237 |
+
various pots simmer on the stove behind him. The audio features the sizzling of pans,
|
| 238 |
+
the clinking of utensils against plates, and the ambient hum of kitchen ventilation.
|
| 239 |
+
conditions:
|
| 240 |
+
- type: prefix
|
| 241 |
+
video: "/path/to/prefix_video_2.mp4"
|
| 242 |
+
# Matches temporal_boundary=8 during training: (8 - 1) * 8 + 1 = 57 frames
|
| 243 |
+
num_frames: 57
|
| 244 |
+
|
| 245 |
+
# Negative prompt to avoid unwanted artifacts
|
| 246 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 247 |
+
|
| 248 |
+
# Output video dimensions [width, height, frames]
|
| 249 |
+
# Width and height must be divisible by 32
|
| 250 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 251 |
+
video_dims: [ 576, 576, 89 ]
|
| 252 |
+
|
| 253 |
+
# Frame rate for generated videos
|
| 254 |
+
frame_rate: 25.0
|
| 255 |
+
|
| 256 |
+
# Random seed for reproducible validation outputs
|
| 257 |
+
seed: 42
|
| 258 |
+
|
| 259 |
+
# Number of denoising steps for validation inference
|
| 260 |
+
# Higher values = better quality but slower generation
|
| 261 |
+
inference_steps: 30
|
| 262 |
+
|
| 263 |
+
# Generate validation videos every N training steps
|
| 264 |
+
# Set to null to disable validation during training
|
| 265 |
+
interval: 100
|
| 266 |
+
|
| 267 |
+
# Classifier-free guidance scale
|
| 268 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 269 |
+
guidance_scale: 4.0
|
| 270 |
+
|
| 271 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 272 |
+
# STG is combined with CFG for better temporal coherence
|
| 273 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 274 |
+
stg_blocks: [29] # Recommended: single block 29
|
| 275 |
+
stg_mode: "stg_av" # "stg_av" perturbs both audio and video, "stg_v" video only
|
| 276 |
+
|
| 277 |
+
# Whether to generate audio in validation samples
|
| 278 |
+
# Independent of training_strategy.audio.is_generated - you can generate audio
|
| 279 |
+
# in validation even when not training the audio branch
|
| 280 |
+
generate_audio: true
|
| 281 |
+
|
| 282 |
+
# Skip validation at the beginning of training (step 0)
|
| 283 |
+
skip_initial_validation: false
|
| 284 |
+
|
| 285 |
+
# -----------------------------------------------------------------------------
|
| 286 |
+
# Checkpoint Configuration
|
| 287 |
+
# -----------------------------------------------------------------------------
|
| 288 |
+
# Controls model checkpoint saving during training.
|
| 289 |
+
checkpoints:
|
| 290 |
+
# Save a checkpoint every N steps
|
| 291 |
+
# Set to null to disable intermediate checkpoints
|
| 292 |
+
interval: 250
|
| 293 |
+
|
| 294 |
+
# Number of most recent checkpoints to keep
|
| 295 |
+
# Set to -1 to keep all checkpoints
|
| 296 |
+
keep_last_n: -1
|
| 297 |
+
|
| 298 |
+
# Precision to use when saving checkpoint weights
|
| 299 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 300 |
+
precision: "bfloat16"
|
| 301 |
+
|
| 302 |
+
# -----------------------------------------------------------------------------
|
| 303 |
+
# Flow Matching Configuration
|
| 304 |
+
# -----------------------------------------------------------------------------
|
| 305 |
+
# Parameters for the flow matching training objective.
|
| 306 |
+
flow_matching:
|
| 307 |
+
# Timestep sampling mode
|
| 308 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 309 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 310 |
+
|
| 311 |
+
# Additional parameters for timestep sampling
|
| 312 |
+
timestep_sampling_params: { }
|
| 313 |
+
|
| 314 |
+
# -----------------------------------------------------------------------------
|
| 315 |
+
# Hugging Face Hub Configuration
|
| 316 |
+
# -----------------------------------------------------------------------------
|
| 317 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 318 |
+
hub:
|
| 319 |
+
# Whether to push the trained model to the Hub
|
| 320 |
+
push_to_hub: false
|
| 321 |
+
|
| 322 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-lora-model")
|
| 323 |
+
# Required if push_to_hub is true
|
| 324 |
+
hub_model_id: null
|
| 325 |
+
|
| 326 |
+
# -----------------------------------------------------------------------------
|
| 327 |
+
# Weights & Biases Configuration
|
| 328 |
+
# -----------------------------------------------------------------------------
|
| 329 |
+
# Settings for experiment tracking with W&B.
|
| 330 |
+
wandb:
|
| 331 |
+
# Enable W&B logging
|
| 332 |
+
enabled: false
|
| 333 |
+
|
| 334 |
+
# W&B project name
|
| 335 |
+
project: "ltx-2-trainer"
|
| 336 |
+
|
| 337 |
+
# W&B username or team (null uses default account)
|
| 338 |
+
entity: null
|
| 339 |
+
|
| 340 |
+
# Tags to help organize runs
|
| 341 |
+
tags: [ "ltx2", "lora", "video-extension" ]
|
| 342 |
+
|
| 343 |
+
# Log validation media (video/audio) to W&B
|
| 344 |
+
log_validation_videos: true
|
| 345 |
+
|
| 346 |
+
# -----------------------------------------------------------------------------
|
| 347 |
+
# General Configuration
|
| 348 |
+
# -----------------------------------------------------------------------------
|
| 349 |
+
# Global settings for the training run.
|
| 350 |
+
|
| 351 |
+
# Random seed for reproducibility
|
| 352 |
+
seed: 42
|
| 353 |
+
|
| 354 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 355 |
+
output_dir: "outputs/video_extend_lora"
|
packages/ltx-trainer/configs/video_inpainting_lora.yaml
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 Video Inpainting LoRA Training Configuration
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This configuration is for training LoRA adapters on the LTX-2 model for
|
| 6 |
+
# video inpainting. The model learns to fill in masked regions of a video
|
| 7 |
+
# using per-sample binary masks that define which regions are conditioning
|
| 8 |
+
# (provided clean) and which regions the model must generate.
|
| 9 |
+
#
|
| 10 |
+
# Mask conditioning works by loading per-sample binary masks from disk.
|
| 11 |
+
# Masked regions receive clean latents (no noise, timestep=0) and are excluded
|
| 12 |
+
# from the loss. Unmasked regions are noised and trained normally.
|
| 13 |
+
#
|
| 14 |
+
# Use this configuration when you want to:
|
| 15 |
+
# - Train the model to fill in or replace regions of existing videos
|
| 16 |
+
# - Fine-tune video inpainting capabilities on custom datasets
|
| 17 |
+
# - Create object removal or region editing models
|
| 18 |
+
#
|
| 19 |
+
# Dataset structure:
|
| 20 |
+
# preprocessed_data_root/
|
| 21 |
+
# ├── latents/ # Video latents (VAE-encoded videos)
|
| 22 |
+
# ├── conditions/ # Text embeddings for each video
|
| 23 |
+
# └── video_masks/ # Per-sample binary masks defining conditioning regions
|
| 24 |
+
#
|
| 25 |
+
# Dataset metadata columns: video, video_mask, caption
|
| 26 |
+
#
|
| 27 |
+
# =============================================================================
|
| 28 |
+
|
| 29 |
+
# -----------------------------------------------------------------------------
|
| 30 |
+
# Model Configuration
|
| 31 |
+
# -----------------------------------------------------------------------------
|
| 32 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 33 |
+
model:
|
| 34 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 35 |
+
# This should be a local path to your downloaded model
|
| 36 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 37 |
+
|
| 38 |
+
# Path to the text encoder model directory
|
| 39 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 40 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 41 |
+
|
| 42 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 43 |
+
# LoRA is recommended for most use cases (faster, less memory, prevents overfitting)
|
| 44 |
+
training_mode: "lora"
|
| 45 |
+
|
| 46 |
+
# Optional: Path to resume training from a checkpoint
|
| 47 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 48 |
+
load_checkpoint: null
|
| 49 |
+
|
| 50 |
+
# -----------------------------------------------------------------------------
|
| 51 |
+
# LoRA Configuration
|
| 52 |
+
# -----------------------------------------------------------------------------
|
| 53 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 54 |
+
lora:
|
| 55 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 56 |
+
# Typical values: 8, 16, 32, 64. Start with 32 for general fine-tuning.
|
| 57 |
+
rank: 32
|
| 58 |
+
|
| 59 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 60 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 61 |
+
alpha: 32
|
| 62 |
+
|
| 63 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 64 |
+
# Can help with regularization if overfitting occurs
|
| 65 |
+
dropout: 0.0
|
| 66 |
+
|
| 67 |
+
# Which transformer modules to apply LoRA to
|
| 68 |
+
# The LTX-2 transformer has separate attention and FFN blocks for video and audio:
|
| 69 |
+
#
|
| 70 |
+
# VIDEO MODULES:
|
| 71 |
+
# - attn1.to_k, attn1.to_q, attn1.to_v, attn1.to_out.0 (video self-attention)
|
| 72 |
+
# - attn2.to_k, attn2.to_q, attn2.to_v, attn2.to_out.0 (video cross-attention to text)
|
| 73 |
+
# - ff.net.0.proj, ff.net.2 (video feed-forward)
|
| 74 |
+
#
|
| 75 |
+
# AUDIO MODULES:
|
| 76 |
+
# - audio_attn1.to_k, audio_attn1.to_q, audio_attn1.to_v, audio_attn1.to_out.0 (audio self-attention)
|
| 77 |
+
# - audio_attn2.to_k, audio_attn2.to_q, audio_attn2.to_v, audio_attn2.to_out.0 (audio cross-attention to text)
|
| 78 |
+
# - audio_ff.net.0.proj, audio_ff.net.2 (audio feed-forward)
|
| 79 |
+
#
|
| 80 |
+
# AUDIO-VIDEO CROSS-ATTENTION MODULES (for cross-modal interaction):
|
| 81 |
+
# - audio_to_video_attn.to_k, audio_to_video_attn.to_q, audio_to_video_attn.to_v, audio_to_video_attn.to_out.0
|
| 82 |
+
# (Q from video, K/V from audio - allows video to attend to audio features)
|
| 83 |
+
# - video_to_audio_attn.to_k, video_to_audio_attn.to_q, video_to_audio_attn.to_v, video_to_audio_attn.to_out.0
|
| 84 |
+
# (Q from audio, K/V from video - allows audio to attend to video features)
|
| 85 |
+
#
|
| 86 |
+
# This is a video-only config, so explicitly target video attention modules.
|
| 87 |
+
target_modules:
|
| 88 |
+
- "attn1.to_k"
|
| 89 |
+
- "attn1.to_q"
|
| 90 |
+
- "attn1.to_v"
|
| 91 |
+
- "attn1.to_out.0"
|
| 92 |
+
- "attn2.to_k"
|
| 93 |
+
- "attn2.to_q"
|
| 94 |
+
- "attn2.to_v"
|
| 95 |
+
- "attn2.to_out.0"
|
| 96 |
+
# Uncomment below to also train feed-forward layers (can increase the LoRA's capacity):
|
| 97 |
+
# - "ff.net.0.proj"
|
| 98 |
+
# - "ff.net.2"
|
| 99 |
+
|
| 100 |
+
# -----------------------------------------------------------------------------
|
| 101 |
+
# Training Strategy Configuration
|
| 102 |
+
# -----------------------------------------------------------------------------
|
| 103 |
+
# Defines the video inpainting training approach using the unified flexible
|
| 104 |
+
# strategy. Per-sample binary masks define which regions are provided as clean
|
| 105 |
+
# conditioning and which regions the model must learn to generate.
|
| 106 |
+
training_strategy:
|
| 107 |
+
# Strategy name: "flexible" for the unified conditioning framework
|
| 108 |
+
# Supports all training modes (T2V, I2V, V2V, A2V, V2A, etc.) through
|
| 109 |
+
# modality-specific configuration blocks.
|
| 110 |
+
name: "flexible"
|
| 111 |
+
|
| 112 |
+
# Video modality configuration
|
| 113 |
+
video:
|
| 114 |
+
# Whether the model generates video (true) or uses it as frozen conditioning (false)
|
| 115 |
+
is_generated: true
|
| 116 |
+
# Directory name (within preprocessed_data_root) containing video latents
|
| 117 |
+
latents_dir: "latents"
|
| 118 |
+
|
| 119 |
+
# Conditions applied to the video modality during training
|
| 120 |
+
# Mask conditioning: per-sample binary masks loaded from disk define which
|
| 121 |
+
# tokens are conditioning (clean, timestep=0, no loss) vs generated
|
| 122 |
+
conditions:
|
| 123 |
+
- type: mask
|
| 124 |
+
# Directory name (within preprocessed_data_root) containing binary masks
|
| 125 |
+
# Each mask file corresponds to a training sample and defines the
|
| 126 |
+
# conditioning region (mask=1 means conditioning, mask=0 means generate)
|
| 127 |
+
mask_dir: "video_masks"
|
| 128 |
+
# Probability of applying mask conditioning per training sample
|
| 129 |
+
# At 1.0, all training samples use inpainting mode
|
| 130 |
+
probability: 1.0
|
| 131 |
+
|
| 132 |
+
# -----------------------------------------------------------------------------
|
| 133 |
+
# Optimization Configuration
|
| 134 |
+
# -----------------------------------------------------------------------------
|
| 135 |
+
# Controls the training optimization parameters.
|
| 136 |
+
optimization:
|
| 137 |
+
# Learning rate for the optimizer
|
| 138 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 139 |
+
learning_rate: 1e-4
|
| 140 |
+
|
| 141 |
+
# Total number of training steps
|
| 142 |
+
steps: 2000
|
| 143 |
+
|
| 144 |
+
# Batch size per GPU
|
| 145 |
+
# Reduce if running out of memory
|
| 146 |
+
batch_size: 1
|
| 147 |
+
|
| 148 |
+
# Number of gradient accumulation steps
|
| 149 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 150 |
+
gradient_accumulation_steps: 1
|
| 151 |
+
|
| 152 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 153 |
+
max_grad_norm: 1.0
|
| 154 |
+
|
| 155 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 156 |
+
optimizer_type: "adamw"
|
| 157 |
+
|
| 158 |
+
# Learning rate scheduler type
|
| 159 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 160 |
+
scheduler_type: "linear"
|
| 161 |
+
|
| 162 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 163 |
+
scheduler_params: { }
|
| 164 |
+
|
| 165 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 166 |
+
# Recommended for training with limited GPU memory
|
| 167 |
+
enable_gradient_checkpointing: true
|
| 168 |
+
|
| 169 |
+
# -----------------------------------------------------------------------------
|
| 170 |
+
# Acceleration Configuration
|
| 171 |
+
# -----------------------------------------------------------------------------
|
| 172 |
+
# Hardware acceleration and memory optimization settings.
|
| 173 |
+
acceleration:
|
| 174 |
+
# Mixed precision training mode
|
| 175 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 176 |
+
mixed_precision_mode: "bf16"
|
| 177 |
+
|
| 178 |
+
# Model quantization for reduced memory usage
|
| 179 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 180 |
+
quantization: null
|
| 181 |
+
|
| 182 |
+
# Load text encoder in 8-bit precision to save memory
|
| 183 |
+
# Useful when GPU memory is limited
|
| 184 |
+
load_text_encoder_in_8bit: false
|
| 185 |
+
|
| 186 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 187 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 188 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 189 |
+
offload_optimizer_during_validation: false
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
# -----------------------------------------------------------------------------
|
| 193 |
+
# Data Configuration
|
| 194 |
+
# -----------------------------------------------------------------------------
|
| 195 |
+
# Specifies the training data location and loading parameters.
|
| 196 |
+
data:
|
| 197 |
+
# Root directory containing preprocessed training data
|
| 198 |
+
# Should contain: latents/, conditions/, and video_masks/
|
| 199 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 200 |
+
|
| 201 |
+
# Number of worker processes for data loading
|
| 202 |
+
# Used for parallel data loading to speed up data loading
|
| 203 |
+
num_dataloader_workers: 2
|
| 204 |
+
|
| 205 |
+
# -----------------------------------------------------------------------------
|
| 206 |
+
# Validation Configuration
|
| 207 |
+
# -----------------------------------------------------------------------------
|
| 208 |
+
# Controls validation sampling during training.
|
| 209 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 210 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 211 |
+
validation:
|
| 212 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 213 |
+
# Use 'conditions' to add conditioning (first_frame, prefix, suffix, reference, video_to_audio, etc.)
|
| 214 |
+
# See docs/configuration-reference.md#validation-condition-types for the full list of condition types.
|
| 215 |
+
samples:
|
| 216 |
+
- prompt: >-
|
| 217 |
+
A woman with long brown hair sits at a wooden desk in a cozy home office, typing on a
|
| 218 |
+
laptop while occasionally glancing at notes beside her. Soft natural light streams through
|
| 219 |
+
a large window, casting warm shadows across the room. She pauses to take a sip from a
|
| 220 |
+
ceramic mug, then continues working with focused concentration.
|
| 221 |
+
conditions:
|
| 222 |
+
- type: mask
|
| 223 |
+
video: "/path/to/inpainting_video_1.mp4"
|
| 224 |
+
mask: "/path/to/inpainting_mask_1.mp4"
|
| 225 |
+
- prompt: >-
|
| 226 |
+
A chef in a white uniform stands in a professional kitchen, carefully plating a gourmet
|
| 227 |
+
dish with precise movements. Steam rises from freshly cooked vegetables as he arranges
|
| 228 |
+
them with tweezers. The stainless steel surfaces gleam under bright overhead lights, and
|
| 229 |
+
various pots simmer on the stove behind him.
|
| 230 |
+
conditions:
|
| 231 |
+
- type: mask
|
| 232 |
+
video: "/path/to/inpainting_video_2.mp4"
|
| 233 |
+
mask: "/path/to/inpainting_mask_2.mp4"
|
| 234 |
+
|
| 235 |
+
# Negative prompt to avoid unwanted artifacts
|
| 236 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 237 |
+
|
| 238 |
+
# Output video dimensions [width, height, frames]
|
| 239 |
+
# Width and height must be divisible by 32
|
| 240 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 241 |
+
video_dims: [ 576, 576, 89 ]
|
| 242 |
+
|
| 243 |
+
# Frame rate for generated videos
|
| 244 |
+
frame_rate: 25.0
|
| 245 |
+
|
| 246 |
+
# Random seed for reproducible validation outputs
|
| 247 |
+
seed: 42
|
| 248 |
+
|
| 249 |
+
# Number of denoising steps for validation inference
|
| 250 |
+
# Higher values = better quality but slower generation
|
| 251 |
+
inference_steps: 30
|
| 252 |
+
|
| 253 |
+
# Generate validation videos every N training steps
|
| 254 |
+
# Set to null to disable validation during training
|
| 255 |
+
interval: 100
|
| 256 |
+
|
| 257 |
+
# Classifier-free guidance scale
|
| 258 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 259 |
+
guidance_scale: 4.0
|
| 260 |
+
|
| 261 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 262 |
+
# STG is combined with CFG for better temporal coherence
|
| 263 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 264 |
+
stg_blocks: [29] # Recommended: single block 29
|
| 265 |
+
stg_mode: "stg_v" # "stg_v" for video-only validation
|
| 266 |
+
|
| 267 |
+
# Video inpainting trains video only; do not generate validation audio.
|
| 268 |
+
generate_audio: false
|
| 269 |
+
|
| 270 |
+
# Skip validation at the beginning of training (step 0)
|
| 271 |
+
skip_initial_validation: false
|
| 272 |
+
|
| 273 |
+
# -----------------------------------------------------------------------------
|
| 274 |
+
# Checkpoint Configuration
|
| 275 |
+
# -----------------------------------------------------------------------------
|
| 276 |
+
# Controls model checkpoint saving during training.
|
| 277 |
+
checkpoints:
|
| 278 |
+
# Save a checkpoint every N steps
|
| 279 |
+
# Set to null to disable intermediate checkpoints
|
| 280 |
+
interval: 250
|
| 281 |
+
|
| 282 |
+
# Number of most recent checkpoints to keep
|
| 283 |
+
# Set to -1 to keep all checkpoints
|
| 284 |
+
keep_last_n: -1
|
| 285 |
+
|
| 286 |
+
# Precision to use when saving checkpoint weights
|
| 287 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 288 |
+
precision: "bfloat16"
|
| 289 |
+
|
| 290 |
+
# -----------------------------------------------------------------------------
|
| 291 |
+
# Flow Matching Configuration
|
| 292 |
+
# -----------------------------------------------------------------------------
|
| 293 |
+
# Parameters for the flow matching training objective.
|
| 294 |
+
flow_matching:
|
| 295 |
+
# Timestep sampling mode
|
| 296 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 297 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 298 |
+
|
| 299 |
+
# Additional parameters for timestep sampling
|
| 300 |
+
timestep_sampling_params: { }
|
| 301 |
+
|
| 302 |
+
# -----------------------------------------------------------------------------
|
| 303 |
+
# Hugging Face Hub Configuration
|
| 304 |
+
# -----------------------------------------------------------------------------
|
| 305 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 306 |
+
hub:
|
| 307 |
+
# Whether to push the trained model to the Hub
|
| 308 |
+
push_to_hub: false
|
| 309 |
+
|
| 310 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-lora-model")
|
| 311 |
+
# Required if push_to_hub is true
|
| 312 |
+
hub_model_id: null
|
| 313 |
+
|
| 314 |
+
# -----------------------------------------------------------------------------
|
| 315 |
+
# Weights & Biases Configuration
|
| 316 |
+
# -----------------------------------------------------------------------------
|
| 317 |
+
# Settings for experiment tracking with W&B.
|
| 318 |
+
wandb:
|
| 319 |
+
# Enable W&B logging
|
| 320 |
+
enabled: false
|
| 321 |
+
|
| 322 |
+
# W&B project name
|
| 323 |
+
project: "ltx-2-trainer"
|
| 324 |
+
|
| 325 |
+
# W&B username or team (null uses default account)
|
| 326 |
+
entity: null
|
| 327 |
+
|
| 328 |
+
# Tags to help organize runs
|
| 329 |
+
tags: [ "ltx2", "lora", "inpainting" ]
|
| 330 |
+
|
| 331 |
+
# Log validation outputs to W&B
|
| 332 |
+
log_validation_videos: true
|
| 333 |
+
|
| 334 |
+
# -----------------------------------------------------------------------------
|
| 335 |
+
# General Configuration
|
| 336 |
+
# -----------------------------------------------------------------------------
|
| 337 |
+
# Global settings for the training run.
|
| 338 |
+
|
| 339 |
+
# Random seed for reproducibility
|
| 340 |
+
seed: 42
|
| 341 |
+
|
| 342 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 343 |
+
output_dir: "outputs/video_inpainting_lora"
|
packages/ltx-trainer/configs/video_outpainting_lora.yaml
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 Video Outpainting LoRA Training Configuration
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This configuration is for training LoRA adapters on the LTX-2 model for
|
| 6 |
+
# video outpainting (spatial extension). The model learns to generate content
|
| 7 |
+
# surrounding a known rectangular region of the video.
|
| 8 |
+
#
|
| 9 |
+
# Spatial crop conditioning works by providing a rectangular pixel region as
|
| 10 |
+
# clean conditioning during training — those tokens receive no noise,
|
| 11 |
+
# timestep=0, and are excluded from the loss. The model learns to generate
|
| 12 |
+
# the content outside the given region, seamlessly extending the scene.
|
| 13 |
+
#
|
| 14 |
+
# Use this configuration when you want to:
|
| 15 |
+
# - Extend video content beyond its original boundaries
|
| 16 |
+
# - Train spatial outpainting capabilities on custom datasets
|
| 17 |
+
# - Create video aspect ratio conversion models
|
| 18 |
+
#
|
| 19 |
+
# Dataset structure:
|
| 20 |
+
# preprocessed_data_root/
|
| 21 |
+
# ├── latents/ # Video latents (VAE-encoded videos)
|
| 22 |
+
# └── conditions/ # Text embeddings for each video
|
| 23 |
+
#
|
| 24 |
+
# =============================================================================
|
| 25 |
+
|
| 26 |
+
# -----------------------------------------------------------------------------
|
| 27 |
+
# Model Configuration
|
| 28 |
+
# -----------------------------------------------------------------------------
|
| 29 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 30 |
+
model:
|
| 31 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 32 |
+
# This should be a local path to your downloaded model
|
| 33 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 34 |
+
|
| 35 |
+
# Path to the text encoder model directory
|
| 36 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 37 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 38 |
+
|
| 39 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 40 |
+
# LoRA is recommended for most use cases (faster, less memory, prevents overfitting)
|
| 41 |
+
training_mode: "lora"
|
| 42 |
+
|
| 43 |
+
# Optional: Path to resume training from a checkpoint
|
| 44 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 45 |
+
load_checkpoint: null
|
| 46 |
+
|
| 47 |
+
# -----------------------------------------------------------------------------
|
| 48 |
+
# LoRA Configuration
|
| 49 |
+
# -----------------------------------------------------------------------------
|
| 50 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 51 |
+
lora:
|
| 52 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 53 |
+
# Typical values: 8, 16, 32, 64. Start with 32 for general fine-tuning.
|
| 54 |
+
rank: 32
|
| 55 |
+
|
| 56 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 57 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 58 |
+
alpha: 32
|
| 59 |
+
|
| 60 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 61 |
+
# Can help with regularization if overfitting occurs
|
| 62 |
+
dropout: 0.0
|
| 63 |
+
|
| 64 |
+
# Which transformer modules to apply LoRA to
|
| 65 |
+
# The LTX-2 transformer has separate attention and FFN blocks for video and audio:
|
| 66 |
+
#
|
| 67 |
+
# VIDEO MODULES:
|
| 68 |
+
# - attn1.to_k, attn1.to_q, attn1.to_v, attn1.to_out.0 (video self-attention)
|
| 69 |
+
# - attn2.to_k, attn2.to_q, attn2.to_v, attn2.to_out.0 (video cross-attention to text)
|
| 70 |
+
# - ff.net.0.proj, ff.net.2 (video feed-forward)
|
| 71 |
+
#
|
| 72 |
+
# AUDIO MODULES:
|
| 73 |
+
# - audio_attn1.to_k, audio_attn1.to_q, audio_attn1.to_v, audio_attn1.to_out.0 (audio self-attention)
|
| 74 |
+
# - audio_attn2.to_k, audio_attn2.to_q, audio_attn2.to_v, audio_attn2.to_out.0 (audio cross-attention to text)
|
| 75 |
+
# - audio_ff.net.0.proj, audio_ff.net.2 (audio feed-forward)
|
| 76 |
+
#
|
| 77 |
+
# AUDIO-VIDEO CROSS-ATTENTION MODULES (for cross-modal interaction):
|
| 78 |
+
# - audio_to_video_attn.to_k, audio_to_video_attn.to_q, audio_to_video_attn.to_v, audio_to_video_attn.to_out.0
|
| 79 |
+
# (Q from video, K/V from audio - allows video to attend to audio features)
|
| 80 |
+
# - video_to_audio_attn.to_k, video_to_audio_attn.to_q, video_to_audio_attn.to_v, video_to_audio_attn.to_out.0
|
| 81 |
+
# (Q from audio, K/V from video - allows audio to attend to video features)
|
| 82 |
+
#
|
| 83 |
+
# This is a video-only config, so explicitly target video attention modules.
|
| 84 |
+
target_modules:
|
| 85 |
+
- "attn1.to_k"
|
| 86 |
+
- "attn1.to_q"
|
| 87 |
+
- "attn1.to_v"
|
| 88 |
+
- "attn1.to_out.0"
|
| 89 |
+
- "attn2.to_k"
|
| 90 |
+
- "attn2.to_q"
|
| 91 |
+
- "attn2.to_v"
|
| 92 |
+
- "attn2.to_out.0"
|
| 93 |
+
# Uncomment below to also train feed-forward layers (can increase the LoRA's capacity):
|
| 94 |
+
# - "ff.net.0.proj"
|
| 95 |
+
# - "ff.net.2"
|
| 96 |
+
|
| 97 |
+
# -----------------------------------------------------------------------------
|
| 98 |
+
# Training Strategy Configuration
|
| 99 |
+
# -----------------------------------------------------------------------------
|
| 100 |
+
# Defines the video outpainting training approach using the unified flexible
|
| 101 |
+
# strategy. Spatial crop conditioning provides a rectangular pixel region as
|
| 102 |
+
# clean conditioning, teaching the model to generate surrounding content.
|
| 103 |
+
training_strategy:
|
| 104 |
+
# Strategy name: "flexible" for the unified conditioning framework
|
| 105 |
+
# Supports all training modes (T2V, I2V, V2V, A2V, V2A, etc.) through
|
| 106 |
+
# modality-specific configuration blocks.
|
| 107 |
+
name: "flexible"
|
| 108 |
+
|
| 109 |
+
# Video modality configuration
|
| 110 |
+
video:
|
| 111 |
+
# Whether the model generates video (true) or uses it as frozen conditioning (false)
|
| 112 |
+
is_generated: true
|
| 113 |
+
# Directory name (within preprocessed_data_root) containing video latents
|
| 114 |
+
latents_dir: "latents"
|
| 115 |
+
|
| 116 |
+
# Conditions applied to the video modality during training
|
| 117 |
+
# Spatial crop conditioning provides a rectangular region as clean context;
|
| 118 |
+
# the model learns to generate the surrounding video tokens.
|
| 119 |
+
conditions:
|
| 120 |
+
- type: spatial_crop
|
| 121 |
+
# Rectangular pixel region provided as clean conditioning (y1, x1, y2, x2)
|
| 122 |
+
# Pixels within this region are conditioning (clean, timestep=0, no loss)
|
| 123 |
+
# Pixels outside this region are generated by the model
|
| 124 |
+
# Coordinates are in pixel space — automatically converted to latent space
|
| 125 |
+
spatial_region: [0, 0, 288, 576]
|
| 126 |
+
# Probability of applying spatial crop conditioning per training sample
|
| 127 |
+
# At 1.0, all training samples use outpainting mode
|
| 128 |
+
probability: 1.0
|
| 129 |
+
|
| 130 |
+
# -----------------------------------------------------------------------------
|
| 131 |
+
# Optimization Configuration
|
| 132 |
+
# -----------------------------------------------------------------------------
|
| 133 |
+
# Controls the training optimization parameters.
|
| 134 |
+
optimization:
|
| 135 |
+
# Learning rate for the optimizer
|
| 136 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 137 |
+
learning_rate: 1e-4
|
| 138 |
+
|
| 139 |
+
# Total number of training steps
|
| 140 |
+
steps: 2000
|
| 141 |
+
|
| 142 |
+
# Batch size per GPU
|
| 143 |
+
# Reduce if running out of memory
|
| 144 |
+
batch_size: 1
|
| 145 |
+
|
| 146 |
+
# Number of gradient accumulation steps
|
| 147 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 148 |
+
gradient_accumulation_steps: 1
|
| 149 |
+
|
| 150 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 151 |
+
max_grad_norm: 1.0
|
| 152 |
+
|
| 153 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 154 |
+
optimizer_type: "adamw"
|
| 155 |
+
|
| 156 |
+
# Learning rate scheduler type
|
| 157 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 158 |
+
scheduler_type: "linear"
|
| 159 |
+
|
| 160 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 161 |
+
scheduler_params: { }
|
| 162 |
+
|
| 163 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 164 |
+
# Recommended for training with limited GPU memory
|
| 165 |
+
enable_gradient_checkpointing: true
|
| 166 |
+
|
| 167 |
+
# -----------------------------------------------------------------------------
|
| 168 |
+
# Acceleration Configuration
|
| 169 |
+
# -----------------------------------------------------------------------------
|
| 170 |
+
# Hardware acceleration and memory optimization settings.
|
| 171 |
+
acceleration:
|
| 172 |
+
# Mixed precision training mode
|
| 173 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 174 |
+
mixed_precision_mode: "bf16"
|
| 175 |
+
|
| 176 |
+
# Model quantization for reduced memory usage
|
| 177 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 178 |
+
quantization: null
|
| 179 |
+
|
| 180 |
+
# Load text encoder in 8-bit precision to save memory
|
| 181 |
+
# Useful when GPU memory is limited
|
| 182 |
+
load_text_encoder_in_8bit: false
|
| 183 |
+
|
| 184 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 185 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 186 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 187 |
+
offload_optimizer_during_validation: false
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
# -----------------------------------------------------------------------------
|
| 191 |
+
# Data Configuration
|
| 192 |
+
# -----------------------------------------------------------------------------
|
| 193 |
+
# Specifies the training data location and loading parameters.
|
| 194 |
+
data:
|
| 195 |
+
# Root directory containing preprocessed training data
|
| 196 |
+
# Should contain: latents/ and conditions/
|
| 197 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 198 |
+
|
| 199 |
+
# Number of worker processes for data loading
|
| 200 |
+
# Used for parallel data loading to speed up data loading
|
| 201 |
+
num_dataloader_workers: 2
|
| 202 |
+
|
| 203 |
+
# -----------------------------------------------------------------------------
|
| 204 |
+
# Validation Configuration
|
| 205 |
+
# -----------------------------------------------------------------------------
|
| 206 |
+
# Controls validation sampling during training.
|
| 207 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 208 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 209 |
+
validation:
|
| 210 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 211 |
+
# Use 'conditions' to add conditioning (first_frame, prefix, suffix, reference, video_to_audio, etc.)
|
| 212 |
+
# See docs/configuration-reference.md#validation-condition-types for the full list of condition types.
|
| 213 |
+
samples:
|
| 214 |
+
- prompt: >-
|
| 215 |
+
A woman with long brown hair sits at a wooden desk in a cozy home office, typing on a
|
| 216 |
+
laptop while occasionally glancing at notes beside her. Soft natural light streams through
|
| 217 |
+
a large window, casting warm shadows across the room. She pauses to take a sip from a
|
| 218 |
+
ceramic mug, then continues working with focused concentration.
|
| 219 |
+
conditions:
|
| 220 |
+
- type: spatial_crop
|
| 221 |
+
video: "/path/to/outpainting_video_1.mp4"
|
| 222 |
+
spatial_region: [0, 0, 288, 576]
|
| 223 |
+
- prompt: >-
|
| 224 |
+
A chef in a white uniform stands in a professional kitchen, carefully plating a gourmet
|
| 225 |
+
dish with precise movements. Steam rises from freshly cooked vegetables as he arranges
|
| 226 |
+
them with tweezers. The stainless steel surfaces gleam under bright overhead lights, and
|
| 227 |
+
various pots simmer on the stove behind him.
|
| 228 |
+
conditions:
|
| 229 |
+
- type: spatial_crop
|
| 230 |
+
video: "/path/to/outpainting_video_2.mp4"
|
| 231 |
+
spatial_region: [0, 0, 288, 576]
|
| 232 |
+
|
| 233 |
+
# Negative prompt to avoid unwanted artifacts
|
| 234 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 235 |
+
|
| 236 |
+
# Output video dimensions [width, height, frames]
|
| 237 |
+
# Width and height must be divisible by 32
|
| 238 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 239 |
+
video_dims: [ 576, 576, 89 ]
|
| 240 |
+
|
| 241 |
+
# Frame rate for generated videos
|
| 242 |
+
frame_rate: 25.0
|
| 243 |
+
|
| 244 |
+
# Random seed for reproducible validation outputs
|
| 245 |
+
seed: 42
|
| 246 |
+
|
| 247 |
+
# Number of denoising steps for validation inference
|
| 248 |
+
# Higher values = better quality but slower generation
|
| 249 |
+
inference_steps: 30
|
| 250 |
+
|
| 251 |
+
# Generate validation videos every N training steps
|
| 252 |
+
# Set to null to disable validation during training
|
| 253 |
+
interval: 100
|
| 254 |
+
|
| 255 |
+
# Classifier-free guidance scale
|
| 256 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 257 |
+
guidance_scale: 4.0
|
| 258 |
+
|
| 259 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 260 |
+
# STG is combined with CFG for better temporal coherence
|
| 261 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 262 |
+
stg_blocks: [29] # Recommended: single block 29
|
| 263 |
+
stg_mode: "stg_v" # "stg_v" for video-only validation
|
| 264 |
+
|
| 265 |
+
# Video outpainting trains video only; do not generate validation audio.
|
| 266 |
+
generate_audio: false
|
| 267 |
+
|
| 268 |
+
# Skip validation at the beginning of training (step 0)
|
| 269 |
+
skip_initial_validation: false
|
| 270 |
+
|
| 271 |
+
# -----------------------------------------------------------------------------
|
| 272 |
+
# Checkpoint Configuration
|
| 273 |
+
# -----------------------------------------------------------------------------
|
| 274 |
+
# Controls model checkpoint saving during training.
|
| 275 |
+
checkpoints:
|
| 276 |
+
# Save a checkpoint every N steps
|
| 277 |
+
# Set to null to disable intermediate checkpoints
|
| 278 |
+
interval: 250
|
| 279 |
+
|
| 280 |
+
# Number of most recent checkpoints to keep
|
| 281 |
+
# Set to -1 to keep all checkpoints
|
| 282 |
+
keep_last_n: -1
|
| 283 |
+
|
| 284 |
+
# Precision to use when saving checkpoint weights
|
| 285 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 286 |
+
precision: "bfloat16"
|
| 287 |
+
|
| 288 |
+
# -----------------------------------------------------------------------------
|
| 289 |
+
# Flow Matching Configuration
|
| 290 |
+
# -----------------------------------------------------------------------------
|
| 291 |
+
# Parameters for the flow matching training objective.
|
| 292 |
+
flow_matching:
|
| 293 |
+
# Timestep sampling mode
|
| 294 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 295 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 296 |
+
|
| 297 |
+
# Additional parameters for timestep sampling
|
| 298 |
+
timestep_sampling_params: { }
|
| 299 |
+
|
| 300 |
+
# -----------------------------------------------------------------------------
|
| 301 |
+
# Hugging Face Hub Configuration
|
| 302 |
+
# -----------------------------------------------------------------------------
|
| 303 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 304 |
+
hub:
|
| 305 |
+
# Whether to push the trained model to the Hub
|
| 306 |
+
push_to_hub: false
|
| 307 |
+
|
| 308 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-lora-model")
|
| 309 |
+
# Required if push_to_hub is true
|
| 310 |
+
hub_model_id: null
|
| 311 |
+
|
| 312 |
+
# -----------------------------------------------------------------------------
|
| 313 |
+
# Weights & Biases Configuration
|
| 314 |
+
# -----------------------------------------------------------------------------
|
| 315 |
+
# Settings for experiment tracking with W&B.
|
| 316 |
+
wandb:
|
| 317 |
+
# Enable W&B logging
|
| 318 |
+
enabled: false
|
| 319 |
+
|
| 320 |
+
# W&B project name
|
| 321 |
+
project: "ltx-2-trainer"
|
| 322 |
+
|
| 323 |
+
# W&B username or team (null uses default account)
|
| 324 |
+
entity: null
|
| 325 |
+
|
| 326 |
+
# Tags to help organize runs
|
| 327 |
+
tags: [ "ltx2", "lora", "outpainting", "spatial-crop" ]
|
| 328 |
+
|
| 329 |
+
# Log validation outputs to W&B
|
| 330 |
+
log_validation_videos: true
|
| 331 |
+
|
| 332 |
+
# -----------------------------------------------------------------------------
|
| 333 |
+
# General Configuration
|
| 334 |
+
# -----------------------------------------------------------------------------
|
| 335 |
+
# Global settings for the training run.
|
| 336 |
+
|
| 337 |
+
# Random seed for reproducibility
|
| 338 |
+
seed: 42
|
| 339 |
+
|
| 340 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 341 |
+
output_dir: "outputs/video_outpainting_lora"
|
packages/ltx-trainer/configs/video_suffix_lora.yaml
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# LTX-2 Video Backward Extension (Suffix) LoRA Training Configuration
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# This configuration is for training LoRA adapters on the LTX-2 model for
|
| 6 |
+
# backward video extension. The model learns to generate video content that
|
| 7 |
+
# leads into a given suffix of existing frames.
|
| 8 |
+
#
|
| 9 |
+
# Suffix conditioning works by providing the last N temporal units as clean
|
| 10 |
+
# conditioning signals during training — they receive no noise, timestep=0,
|
| 11 |
+
# and are excluded from the loss. The model learns to generate preceding
|
| 12 |
+
# frames that seamlessly lead into the given suffix.
|
| 13 |
+
#
|
| 14 |
+
# Use this configuration when you want to:
|
| 15 |
+
# - Train the model to generate "prequel" content for existing videos
|
| 16 |
+
# - Fine-tune backward temporal continuation capabilities
|
| 17 |
+
# - Create video backward extension models with optional audio
|
| 18 |
+
#
|
| 19 |
+
# Dataset structure:
|
| 20 |
+
# preprocessed_data_root/
|
| 21 |
+
# ├── latents/ # Video latents (VAE-encoded videos)
|
| 22 |
+
# ├── conditions/ # Text embeddings for each video
|
| 23 |
+
# └── audio_latents/ # Audio latents (VAE-encoded audio)
|
| 24 |
+
#
|
| 25 |
+
# =============================================================================
|
| 26 |
+
|
| 27 |
+
# -----------------------------------------------------------------------------
|
| 28 |
+
# Model Configuration
|
| 29 |
+
# -----------------------------------------------------------------------------
|
| 30 |
+
# Specifies the base model to fine-tune and the training mode.
|
| 31 |
+
model:
|
| 32 |
+
# Path to the LTX-2 model checkpoint (.safetensors file)
|
| 33 |
+
# This should be a local path to your downloaded model
|
| 34 |
+
model_path: "path/to/ltx-2-model.safetensors"
|
| 35 |
+
|
| 36 |
+
# Path to the text encoder model directory
|
| 37 |
+
# For LTX-2, this is typically the Gemma-based text encoder
|
| 38 |
+
text_encoder_path: "path/to/gemma-text-encoder"
|
| 39 |
+
|
| 40 |
+
# Training mode: "lora" for efficient adapter training, "full" for full fine-tuning
|
| 41 |
+
# LoRA is recommended for most use cases (faster, less memory, prevents overfitting)
|
| 42 |
+
training_mode: "lora"
|
| 43 |
+
|
| 44 |
+
# Optional: Path to resume training from a checkpoint
|
| 45 |
+
# Can be a checkpoint file (.safetensors) or directory (uses latest checkpoint)
|
| 46 |
+
load_checkpoint: null
|
| 47 |
+
|
| 48 |
+
# -----------------------------------------------------------------------------
|
| 49 |
+
# LoRA Configuration
|
| 50 |
+
# -----------------------------------------------------------------------------
|
| 51 |
+
# Controls the Low-Rank Adaptation parameters for efficient fine-tuning.
|
| 52 |
+
lora:
|
| 53 |
+
# Rank of the LoRA matrices (higher = more capacity but more parameters)
|
| 54 |
+
# Typical values: 8, 16, 32, 64. Start with 32 for general fine-tuning.
|
| 55 |
+
rank: 32
|
| 56 |
+
|
| 57 |
+
# Alpha scaling factor (usually set equal to rank)
|
| 58 |
+
# The effective scaling is alpha/rank, so alpha=rank means scaling of 1.0
|
| 59 |
+
alpha: 32
|
| 60 |
+
|
| 61 |
+
# Dropout probability for LoRA layers (0.0 = no dropout)
|
| 62 |
+
# Can help with regularization if overfitting occurs
|
| 63 |
+
dropout: 0.0
|
| 64 |
+
|
| 65 |
+
# Which transformer modules to apply LoRA to
|
| 66 |
+
# The LTX-2 transformer has separate attention and FFN blocks for video and audio:
|
| 67 |
+
#
|
| 68 |
+
# VIDEO MODULES:
|
| 69 |
+
# - attn1.to_k, attn1.to_q, attn1.to_v, attn1.to_out.0 (video self-attention)
|
| 70 |
+
# - attn2.to_k, attn2.to_q, attn2.to_v, attn2.to_out.0 (video cross-attention to text)
|
| 71 |
+
# - ff.net.0.proj, ff.net.2 (video feed-forward)
|
| 72 |
+
#
|
| 73 |
+
# AUDIO MODULES:
|
| 74 |
+
# - audio_attn1.to_k, audio_attn1.to_q, audio_attn1.to_v, audio_attn1.to_out.0 (audio self-attention)
|
| 75 |
+
# - audio_attn2.to_k, audio_attn2.to_q, audio_attn2.to_v, audio_attn2.to_out.0 (audio cross-attention to text)
|
| 76 |
+
# - audio_ff.net.0.proj, audio_ff.net.2 (audio feed-forward)
|
| 77 |
+
#
|
| 78 |
+
# AUDIO-VIDEO CROSS-ATTENTION MODULES (for cross-modal interaction):
|
| 79 |
+
# - audio_to_video_attn.to_k, audio_to_video_attn.to_q, audio_to_video_attn.to_v, audio_to_video_attn.to_out.0
|
| 80 |
+
# (Q from video, K/V from audio - allows video to attend to audio features)
|
| 81 |
+
# - video_to_audio_attn.to_k, video_to_audio_attn.to_q, video_to_audio_attn.to_v, video_to_audio_attn.to_out.0
|
| 82 |
+
# (Q from audio, K/V from video - allows audio to attend to video features)
|
| 83 |
+
#
|
| 84 |
+
# Using short patterns like "to_k" matches ALL attention modules (video, audio, and cross-modal).
|
| 85 |
+
# For audio-video training, this is the recommended approach.
|
| 86 |
+
target_modules:
|
| 87 |
+
# Attention layers (matches both video and audio branches)
|
| 88 |
+
- "to_k"
|
| 89 |
+
- "to_q"
|
| 90 |
+
- "to_v"
|
| 91 |
+
- "to_out.0"
|
| 92 |
+
# Uncomment below to also train feed-forward layers (can increase the LoRA's capacity):
|
| 93 |
+
# - "ff.net.0.proj"
|
| 94 |
+
# - "ff.net.2"
|
| 95 |
+
# - "audio_ff.net.0.proj"
|
| 96 |
+
# - "audio_ff.net.2"
|
| 97 |
+
|
| 98 |
+
# -----------------------------------------------------------------------------
|
| 99 |
+
# Training Strategy Configuration
|
| 100 |
+
# -----------------------------------------------------------------------------
|
| 101 |
+
# Defines the backward video extension training approach using the unified
|
| 102 |
+
# flexible strategy. Suffix conditioning provides the last N latent frames as
|
| 103 |
+
# clean conditioning, teaching the model to generate content leading into them.
|
| 104 |
+
training_strategy:
|
| 105 |
+
# Strategy name: "flexible" for the unified conditioning framework
|
| 106 |
+
# Supports all training modes (T2V, I2V, V2V, A2V, V2A, etc.) through
|
| 107 |
+
# modality-specific configuration blocks.
|
| 108 |
+
name: "flexible"
|
| 109 |
+
|
| 110 |
+
# Video modality configuration
|
| 111 |
+
video:
|
| 112 |
+
# Whether the model generates video (true) or uses it as frozen conditioning (false)
|
| 113 |
+
is_generated: true
|
| 114 |
+
# Directory name (within preprocessed_data_root) containing video latents
|
| 115 |
+
latents_dir: "latents"
|
| 116 |
+
|
| 117 |
+
# Conditions applied to the video modality during training
|
| 118 |
+
# Suffix conditioning: the last N temporal units are provided as clean
|
| 119 |
+
# conditioning signals (no noise, timestep=0, excluded from loss)
|
| 120 |
+
conditions:
|
| 121 |
+
- type: suffix
|
| 122 |
+
# Number of latent frames to use as conditioning suffix
|
| 123 |
+
# For suffix conditioning, N latent frames correspond to N * 8 pixel frames.
|
| 124 |
+
# temporal_boundary=8 means the last 64 pixel frames are used as suffix.
|
| 125 |
+
temporal_boundary: 8
|
| 126 |
+
# Probability of applying suffix conditioning per training sample
|
| 127 |
+
# At 1.0, all training samples use backward extension mode
|
| 128 |
+
probability: 1.0
|
| 129 |
+
|
| 130 |
+
# Audio modality configuration
|
| 131 |
+
audio:
|
| 132 |
+
# Whether the model generates audio (true) or uses it as frozen conditioning (false)
|
| 133 |
+
is_generated: true
|
| 134 |
+
# Directory name (within preprocessed_data_root) containing audio latents
|
| 135 |
+
latents_dir: "audio_latents"
|
| 136 |
+
|
| 137 |
+
# -----------------------------------------------------------------------------
|
| 138 |
+
# Optimization Configuration
|
| 139 |
+
# -----------------------------------------------------------------------------
|
| 140 |
+
# Controls the training optimization parameters.
|
| 141 |
+
optimization:
|
| 142 |
+
# Learning rate for the optimizer
|
| 143 |
+
# Typical range for LoRA: 1e-5 to 1e-4
|
| 144 |
+
learning_rate: 1e-4
|
| 145 |
+
|
| 146 |
+
# Total number of training steps
|
| 147 |
+
steps: 2000
|
| 148 |
+
|
| 149 |
+
# Batch size per GPU
|
| 150 |
+
# Reduce if running out of memory
|
| 151 |
+
batch_size: 1
|
| 152 |
+
|
| 153 |
+
# Number of gradient accumulation steps
|
| 154 |
+
# Effective batch size = batch_size * gradient_accumulation_steps * num_gpus
|
| 155 |
+
gradient_accumulation_steps: 1
|
| 156 |
+
|
| 157 |
+
# Maximum gradient norm for clipping (helps training stability)
|
| 158 |
+
max_grad_norm: 1.0
|
| 159 |
+
|
| 160 |
+
# Optimizer type: "adamw" (standard) or "adamw8bit" (memory-efficient)
|
| 161 |
+
optimizer_type: "adamw"
|
| 162 |
+
|
| 163 |
+
# Learning rate scheduler type
|
| 164 |
+
# Options: "constant", "linear", "cosine", "cosine_with_restarts", "polynomial"
|
| 165 |
+
scheduler_type: "linear"
|
| 166 |
+
|
| 167 |
+
# Additional scheduler parameters (depends on scheduler_type)
|
| 168 |
+
scheduler_params: { }
|
| 169 |
+
|
| 170 |
+
# Enable gradient checkpointing to reduce memory usage
|
| 171 |
+
# Recommended for training with limited GPU memory
|
| 172 |
+
enable_gradient_checkpointing: true
|
| 173 |
+
|
| 174 |
+
# -----------------------------------------------------------------------------
|
| 175 |
+
# Acceleration Configuration
|
| 176 |
+
# -----------------------------------------------------------------------------
|
| 177 |
+
# Hardware acceleration and memory optimization settings.
|
| 178 |
+
acceleration:
|
| 179 |
+
# Mixed precision training mode
|
| 180 |
+
# Options: "no" (fp32), "fp16" (half precision), "bf16" (bfloat16, recommended)
|
| 181 |
+
mixed_precision_mode: "bf16"
|
| 182 |
+
|
| 183 |
+
# Model quantization for reduced memory usage
|
| 184 |
+
# Options: null (none), "int8-quanto", "int4-quanto", "int2-quanto", "fp8-quanto", "fp8uz-quanto"
|
| 185 |
+
quantization: null
|
| 186 |
+
|
| 187 |
+
# Load text encoder in 8-bit precision to save memory
|
| 188 |
+
# Useful when GPU memory is limited
|
| 189 |
+
load_text_encoder_in_8bit: false
|
| 190 |
+
|
| 191 |
+
# Offload optimizer state to CPU during validation video sampling and restore it after.
|
| 192 |
+
# Frees VRAM for the VAE decoder when optimizer state is large (full fine-tune, high-rank
|
| 193 |
+
# LoRA). No effect under FSDP (sharded state).
|
| 194 |
+
offload_optimizer_during_validation: false
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
# -----------------------------------------------------------------------------
|
| 198 |
+
# Data Configuration
|
| 199 |
+
# -----------------------------------------------------------------------------
|
| 200 |
+
# Specifies the training data location and loading parameters.
|
| 201 |
+
data:
|
| 202 |
+
# Root directory containing preprocessed training data
|
| 203 |
+
# Should contain: latents/, conditions/, and audio_latents/
|
| 204 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 205 |
+
|
| 206 |
+
# Number of worker processes for data loading
|
| 207 |
+
# Used for parallel data loading to speed up data loading
|
| 208 |
+
num_dataloader_workers: 2
|
| 209 |
+
|
| 210 |
+
# -----------------------------------------------------------------------------
|
| 211 |
+
# Validation Configuration
|
| 212 |
+
# -----------------------------------------------------------------------------
|
| 213 |
+
# Controls validation sampling during training.
|
| 214 |
+
# NOTE: Validation sampling use simplified inference pipelines and prioritizes speed over
|
| 215 |
+
# maximum quality. For production-quality inference, use `packages/ltx-pipelines`.
|
| 216 |
+
validation:
|
| 217 |
+
# Validation samples — each sample describes a self-contained generation request.
|
| 218 |
+
# Use 'conditions' to add conditioning (first_frame, prefix, suffix, reference, video_to_audio, etc.)
|
| 219 |
+
# See docs/configuration-reference.md#validation-condition-types for the full list of condition types.
|
| 220 |
+
samples:
|
| 221 |
+
- prompt: >-
|
| 222 |
+
A woman with long brown hair sits at a wooden desk in a cozy home office, typing on a
|
| 223 |
+
laptop while occasionally glancing at notes beside her. Soft natural light streams through
|
| 224 |
+
a large window, casting warm shadows across the room. She pauses to take a sip from a
|
| 225 |
+
ceramic mug, then continues working with focused concentration. The audio captures the
|
| 226 |
+
gentle clicking of keyboard keys, the soft rustle of papers, and ambient room tone with
|
| 227 |
+
occasional distant bird chirps from outside.
|
| 228 |
+
conditions:
|
| 229 |
+
- type: suffix
|
| 230 |
+
video: "/path/to/suffix_video_1.mp4"
|
| 231 |
+
# Matches temporal_boundary=8 during training: 8 * 8 = 64 frames
|
| 232 |
+
num_frames: 64
|
| 233 |
+
- prompt: >-
|
| 234 |
+
A chef in a white uniform stands in a professional kitchen, carefully plating a gourmet
|
| 235 |
+
dish with precise movements. Steam rises from freshly cooked vegetables as he arranges
|
| 236 |
+
them with tweezers. The stainless steel surfaces gleam under bright overhead lights, and
|
| 237 |
+
various pots simmer on the stove behind him. The audio features the sizzling of pans,
|
| 238 |
+
the clinking of utensils against plates, and the ambient hum of kitchen ventilation.
|
| 239 |
+
conditions:
|
| 240 |
+
- type: suffix
|
| 241 |
+
video: "/path/to/suffix_video_2.mp4"
|
| 242 |
+
# Matches temporal_boundary=8 during training: 8 * 8 = 64 frames
|
| 243 |
+
num_frames: 64
|
| 244 |
+
|
| 245 |
+
# Negative prompt to avoid unwanted artifacts
|
| 246 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 247 |
+
|
| 248 |
+
# Output video dimensions [width, height, frames]
|
| 249 |
+
# Width and height must be divisible by 32
|
| 250 |
+
# Frames must satisfy: frames % 8 == 1 (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, ...)
|
| 251 |
+
video_dims: [ 576, 576, 89 ]
|
| 252 |
+
|
| 253 |
+
# Frame rate for generated videos
|
| 254 |
+
frame_rate: 25.0
|
| 255 |
+
|
| 256 |
+
# Random seed for reproducible validation outputs
|
| 257 |
+
seed: 42
|
| 258 |
+
|
| 259 |
+
# Number of denoising steps for validation inference
|
| 260 |
+
# Higher values = better quality but slower generation
|
| 261 |
+
inference_steps: 30
|
| 262 |
+
|
| 263 |
+
# Generate validation videos every N training steps
|
| 264 |
+
# Set to null to disable validation during training
|
| 265 |
+
interval: 100
|
| 266 |
+
|
| 267 |
+
# Classifier-free guidance scale
|
| 268 |
+
# Higher values = stronger adherence to prompt but may introduce artifacts
|
| 269 |
+
guidance_scale: 4.0
|
| 270 |
+
|
| 271 |
+
# STG (Spatio-Temporal Guidance) parameters for improved video quality
|
| 272 |
+
# STG is combined with CFG for better temporal coherence
|
| 273 |
+
stg_scale: 1.0 # Recommended: 1.0 (0.0 disables STG)
|
| 274 |
+
stg_blocks: [29] # Recommended: single block 29
|
| 275 |
+
stg_mode: "stg_av" # "stg_av" perturbs both audio and video, "stg_v" video only
|
| 276 |
+
|
| 277 |
+
# Whether to generate audio in validation samples
|
| 278 |
+
# Independent of training_strategy.audio.is_generated - you can generate audio
|
| 279 |
+
# in validation even when not training the audio branch
|
| 280 |
+
generate_audio: true
|
| 281 |
+
|
| 282 |
+
# Skip validation at the beginning of training (step 0)
|
| 283 |
+
skip_initial_validation: false
|
| 284 |
+
|
| 285 |
+
# -----------------------------------------------------------------------------
|
| 286 |
+
# Checkpoint Configuration
|
| 287 |
+
# -----------------------------------------------------------------------------
|
| 288 |
+
# Controls model checkpoint saving during training.
|
| 289 |
+
checkpoints:
|
| 290 |
+
# Save a checkpoint every N steps
|
| 291 |
+
# Set to null to disable intermediate checkpoints
|
| 292 |
+
interval: 250
|
| 293 |
+
|
| 294 |
+
# Number of most recent checkpoints to keep
|
| 295 |
+
# Set to -1 to keep all checkpoints
|
| 296 |
+
keep_last_n: -1
|
| 297 |
+
|
| 298 |
+
# Precision to use when saving checkpoint weights
|
| 299 |
+
# Options: "bfloat16" (default, smaller files) or "float32" (full precision)
|
| 300 |
+
precision: "bfloat16"
|
| 301 |
+
|
| 302 |
+
# -----------------------------------------------------------------------------
|
| 303 |
+
# Flow Matching Configuration
|
| 304 |
+
# -----------------------------------------------------------------------------
|
| 305 |
+
# Parameters for the flow matching training objective.
|
| 306 |
+
flow_matching:
|
| 307 |
+
# Timestep sampling mode
|
| 308 |
+
# "shifted_logit_normal" is recommended for LTX-2 models
|
| 309 |
+
timestep_sampling_mode: "shifted_logit_normal"
|
| 310 |
+
|
| 311 |
+
# Additional parameters for timestep sampling
|
| 312 |
+
timestep_sampling_params: { }
|
| 313 |
+
|
| 314 |
+
# -----------------------------------------------------------------------------
|
| 315 |
+
# Hugging Face Hub Configuration
|
| 316 |
+
# -----------------------------------------------------------------------------
|
| 317 |
+
# Settings for uploading trained models to the Hugging Face Hub.
|
| 318 |
+
hub:
|
| 319 |
+
# Whether to push the trained model to the Hub
|
| 320 |
+
push_to_hub: false
|
| 321 |
+
|
| 322 |
+
# Repository ID on Hugging Face Hub (e.g., "username/my-lora-model")
|
| 323 |
+
# Required if push_to_hub is true
|
| 324 |
+
hub_model_id: null
|
| 325 |
+
|
| 326 |
+
# -----------------------------------------------------------------------------
|
| 327 |
+
# Weights & Biases Configuration
|
| 328 |
+
# -----------------------------------------------------------------------------
|
| 329 |
+
# Settings for experiment tracking with W&B.
|
| 330 |
+
wandb:
|
| 331 |
+
# Enable W&B logging
|
| 332 |
+
enabled: false
|
| 333 |
+
|
| 334 |
+
# W&B project name
|
| 335 |
+
project: "ltx-2-trainer"
|
| 336 |
+
|
| 337 |
+
# W&B username or team (null uses default account)
|
| 338 |
+
entity: null
|
| 339 |
+
|
| 340 |
+
# Tags to help organize runs
|
| 341 |
+
tags: [ "ltx2", "lora", "video-suffix", "backward-extension" ]
|
| 342 |
+
|
| 343 |
+
# Log validation media (video/audio) to W&B
|
| 344 |
+
log_validation_videos: true
|
| 345 |
+
|
| 346 |
+
# -----------------------------------------------------------------------------
|
| 347 |
+
# General Configuration
|
| 348 |
+
# -----------------------------------------------------------------------------
|
| 349 |
+
# Global settings for the training run.
|
| 350 |
+
|
| 351 |
+
# Random seed for reproducibility
|
| 352 |
+
seed: 42
|
| 353 |
+
|
| 354 |
+
# Directory to save outputs (checkpoints, validation videos, logs)
|
| 355 |
+
output_dir: "outputs/video_suffix_lora"
|
packages/ltx-trainer/docs/configuration-reference.md
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Configuration Reference
|
| 2 |
+
|
| 3 |
+
The trainer uses structured Pydantic models for configuration, making it easy to customize training parameters.
|
| 4 |
+
This guide covers all available configuration options and their usage.
|
| 5 |
+
|
| 6 |
+
## 📋 Overview
|
| 7 |
+
|
| 8 |
+
The main configuration class is [`LtxTrainerConfig`](../src/ltx_trainer/config.py), which includes the following
|
| 9 |
+
sub-configurations:
|
| 10 |
+
|
| 11 |
+
- **ModelConfig**: Base model and training mode settings
|
| 12 |
+
- **LoraConfig**: LoRA training parameters
|
| 13 |
+
- **TrainingStrategyConfig**: Training strategy settings (flexible conditioning framework)
|
| 14 |
+
- **OptimizationConfig**: Learning rate, batch sizes, and scheduler settings
|
| 15 |
+
- **AccelerationConfig**: Mixed precision and quantization settings
|
| 16 |
+
- **DataConfig**: Data loading parameters
|
| 17 |
+
- **ValidationConfig**: Validation and inference settings
|
| 18 |
+
- **CheckpointsConfig**: Checkpoint saving frequency and retention settings
|
| 19 |
+
- **HubConfig**: Hugging Face Hub integration settings
|
| 20 |
+
- **WandbConfig**: Weights & Biases logging settings
|
| 21 |
+
- **FlowMatchingConfig**: Timestep sampling parameters
|
| 22 |
+
|
| 23 |
+
## 📄 Example Configuration Files
|
| 24 |
+
|
| 25 |
+
Check out our example configurations in the `configs` directory:
|
| 26 |
+
|
| 27 |
+
- 📄 [Text-to-Video LoRA](../configs/t2v_lora.yaml) - Text-to-video LoRA training
|
| 28 |
+
- 📄 [Image-to-Video LoRA](../configs/i2v_lora.yaml) - Image-to-video LoRA training
|
| 29 |
+
- 📄 [IC-LoRA Video-to-Video](../configs/v2v_ic_lora.yaml) - IC-LoRA video-to-video training
|
| 30 |
+
- 📄 [Audio-to-Video LoRA](../configs/a2v_lora.yaml) - Audio-to-video LoRA training
|
| 31 |
+
- 📄 [Video-to-Audio LoRA](../configs/v2a_lora.yaml) - Video-to-audio (Foley) LoRA training
|
| 32 |
+
- 📄 [Video Extension LoRA](../configs/video_extend_lora.yaml) - Video extension (forward) LoRA training
|
| 33 |
+
- 📄 [Video Suffix LoRA](../configs/video_suffix_lora.yaml) - Video extension (backward) LoRA training
|
| 34 |
+
- 📄 [Video Inpainting LoRA](../configs/video_inpainting_lora.yaml) - Video inpainting LoRA training
|
| 35 |
+
- 📄 [Video Outpainting LoRA](../configs/video_outpainting_lora.yaml) - Video outpainting (spatial crop) LoRA training
|
| 36 |
+
- 📄 [Text-to-Audio LoRA](../configs/t2a_lora.yaml) - Text-to-audio LoRA training
|
| 37 |
+
- 📄 [Audio Extension LoRA](../configs/audio_extend_lora.yaml) - Audio extension (forward) LoRA training
|
| 38 |
+
- 📄 [Audio Suffix LoRA](../configs/audio_suffix_lora.yaml) - Audio extension (backward) LoRA training
|
| 39 |
+
- 📄 [Audio Inpainting LoRA](../configs/audio_inpainting_lora.yaml) - Audio inpainting LoRA training
|
| 40 |
+
- 📄 [Audio-to-Audio IC-LoRA](../configs/a2a_ic_lora.yaml) - Audio IC-LoRA transformation training
|
| 41 |
+
- 📄 [AV2AV IC-LoRA](../configs/av2av_ic_lora.yaml) - Audio+video IC-LoRA transformation training
|
| 42 |
+
- 📄 [T2V LoRA (Low VRAM)](../configs/t2v_lora_low_vram.yaml) - Memory-optimized config for 32GB GPUs
|
| 43 |
+
|
| 44 |
+
## ⚙️ Configuration Sections
|
| 45 |
+
|
| 46 |
+
> [!NOTE]
|
| 47 |
+
> The YAML snippets below show **recommended starting values**, not necessarily the code defaults.
|
| 48 |
+
> Fields you omit from your config file will use the code defaults from [`config.py`](../src/ltx_trainer/config.py).
|
| 49 |
+
|
| 50 |
+
### ModelConfig
|
| 51 |
+
|
| 52 |
+
Controls the base model and training mode settings.
|
| 53 |
+
|
| 54 |
+
```yaml
|
| 55 |
+
model:
|
| 56 |
+
model_path: "/path/to/ltx-2-model.safetensors" # Local path to model checkpoint
|
| 57 |
+
text_encoder_path: "/path/to/gemma-model" # Path to Gemma text encoder directory
|
| 58 |
+
training_mode: "lora" # "lora" or "full"
|
| 59 |
+
load_checkpoint: null # Path to checkpoint to resume from
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
**Key parameters:**
|
| 63 |
+
|
| 64 |
+
| Parameter | Description |
|
| 65 |
+
|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
| 66 |
+
| `model_path` | **Required.** Local path to the LTX-2 model checkpoint (`.safetensors` file). URLs are not supported. |
|
| 67 |
+
| `text_encoder_path` | **Required.** Path to the Gemma text encoder model directory. Download from [HuggingFace](https://huggingface.co/google/gemma-3-12b-it-qat-q4_0-unquantized/). |
|
| 68 |
+
| `training_mode` | Training approach - `"lora"` for LoRA training or `"full"` for full-rank fine-tuning. |
|
| 69 |
+
| `load_checkpoint` | Optional path to resume training from a checkpoint file or directory. |
|
| 70 |
+
|
| 71 |
+
> [!NOTE]
|
| 72 |
+
> LTX-2 requires both a model checkpoint and a Gemma text encoder. Both must be local paths.
|
| 73 |
+
|
| 74 |
+
### LoraConfig
|
| 75 |
+
|
| 76 |
+
LoRA-specific fine-tuning parameters (only used when `training_mode: "lora"`).
|
| 77 |
+
|
| 78 |
+
```yaml
|
| 79 |
+
lora:
|
| 80 |
+
rank: 32 # LoRA rank (higher = more parameters)
|
| 81 |
+
alpha: 32 # LoRA alpha scaling factor
|
| 82 |
+
dropout: 0.0 # Dropout probability (0.0-1.0)
|
| 83 |
+
target_modules: # Modules to apply LoRA to
|
| 84 |
+
- "to_k"
|
| 85 |
+
- "to_q"
|
| 86 |
+
- "to_v"
|
| 87 |
+
- "to_out.0"
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
**Key parameters:**
|
| 91 |
+
|
| 92 |
+
| Parameter | Description |
|
| 93 |
+
|------------------|---------------------------------------------------------------------------------|
|
| 94 |
+
| `rank` | LoRA rank - higher values mean more trainable parameters (typical range: 8-128) |
|
| 95 |
+
| `alpha` | Alpha scaling factor - typically set equal to rank |
|
| 96 |
+
| `dropout` | Dropout probability for regularization |
|
| 97 |
+
| `target_modules` | List of transformer modules to apply LoRA adapters to (see below) |
|
| 98 |
+
|
| 99 |
+
#### Understanding Target Modules
|
| 100 |
+
|
| 101 |
+
The LTX-2 transformer has separate attention and feed-forward blocks for video and audio, as well as cross-attention
|
| 102 |
+
modules that enable the two modalities to exchange information. Choosing the right `target_modules` is critical for
|
| 103 |
+
achieving good results, especially when training with audio.
|
| 104 |
+
|
| 105 |
+
**Video-only modules:**
|
| 106 |
+
|
| 107 |
+
| Module Pattern | Description |
|
| 108 |
+
|------------------------------------------------------------|---------------------------------|
|
| 109 |
+
| `attn1.to_k`, `attn1.to_q`, `attn1.to_v`, `attn1.to_out.0` | Video self-attention |
|
| 110 |
+
| `attn2.to_k`, `attn2.to_q`, `attn2.to_v`, `attn2.to_out.0` | Video cross-attention (to text) |
|
| 111 |
+
| `ff.net.0.proj`, `ff.net.2` | Video feed-forward network |
|
| 112 |
+
|
| 113 |
+
**Audio-only modules:**
|
| 114 |
+
|
| 115 |
+
| Module Pattern | Description |
|
| 116 |
+
|------------------------------------------------------------------------------------|---------------------------------|
|
| 117 |
+
| `audio_attn1.to_k`, `audio_attn1.to_q`, `audio_attn1.to_v`, `audio_attn1.to_out.0` | Audio self-attention |
|
| 118 |
+
| `audio_attn2.to_k`, `audio_attn2.to_q`, `audio_attn2.to_v`, `audio_attn2.to_out.0` | Audio cross-attention (to text) |
|
| 119 |
+
| `audio_ff.net.0.proj`, `audio_ff.net.2` | Audio feed-forward network |
|
| 120 |
+
|
| 121 |
+
**Audio-video cross-attention modules:**
|
| 122 |
+
|
| 123 |
+
These modules enable bidirectional information flow between the audio and video modalities:
|
| 124 |
+
|
| 125 |
+
| Module Pattern | Description |
|
| 126 |
+
|--------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------|
|
| 127 |
+
| `audio_to_video_attn.to_k`, `audio_to_video_attn.to_q`, `audio_to_video_attn.to_v`, `audio_to_video_attn.to_out.0` | Video attends to audio (Q from video, K/V from audio) |
|
| 128 |
+
| `video_to_audio_attn.to_k`, `video_to_audio_attn.to_q`, `video_to_audio_attn.to_v`, `video_to_audio_attn.to_out.0` | Audio attends to video (Q from audio, K/V from video) |
|
| 129 |
+
|
| 130 |
+
**Recommended configurations:**
|
| 131 |
+
|
| 132 |
+
For **video-only training**, target the video attention layers:
|
| 133 |
+
|
| 134 |
+
```yaml
|
| 135 |
+
target_modules:
|
| 136 |
+
- "attn1.to_k"
|
| 137 |
+
- "attn1.to_q"
|
| 138 |
+
- "attn1.to_v"
|
| 139 |
+
- "attn1.to_out.0"
|
| 140 |
+
- "attn2.to_k"
|
| 141 |
+
- "attn2.to_q"
|
| 142 |
+
- "attn2.to_v"
|
| 143 |
+
- "attn2.to_out.0"
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
For **audio-video training**, use patterns that match both branches:
|
| 147 |
+
|
| 148 |
+
```yaml
|
| 149 |
+
target_modules:
|
| 150 |
+
- "to_k"
|
| 151 |
+
- "to_q"
|
| 152 |
+
- "to_v"
|
| 153 |
+
- "to_out.0"
|
| 154 |
+
```
|
| 155 |
+
|
| 156 |
+
> [!NOTE]
|
| 157 |
+
> Using shorter patterns like `"to_k"` will match all attention modules including `attn1.to_k`, `audio_attn1.to_k`,
|
| 158 |
+
> `audio_to_video_attn.to_k`, and `video_to_audio_attn.to_k`, effectively training video, audio, and cross-modal
|
| 159 |
+
> attention branches together.
|
| 160 |
+
|
| 161 |
+
> [!TIP]
|
| 162 |
+
> You can also target the feed-forward (FFN) modules (`ff.net.0.proj`, `ff.net.2` for video,
|
| 163 |
+
> `audio_ff.net.0.proj`, `audio_ff.net.2` for audio) to increase the LoRA's capacity and potentially
|
| 164 |
+
> help it capture the target distribution better.
|
| 165 |
+
|
| 166 |
+
### TrainingStrategyConfig
|
| 167 |
+
|
| 168 |
+
Configures the training strategy. The recommended strategy is `"flexible"`, which supports all conditioning scenarios through configuration.
|
| 169 |
+
|
| 170 |
+
#### Flexible Strategy
|
| 171 |
+
|
| 172 |
+
The flexible strategy provides a unified conditioning framework. Each modality (video, audio) is configured
|
| 173 |
+
independently with its own latents directory, generation flag, and list of conditions.
|
| 174 |
+
|
| 175 |
+
```yaml
|
| 176 |
+
training_strategy:
|
| 177 |
+
name: "flexible"
|
| 178 |
+
video:
|
| 179 |
+
is_generated: true # Video is denoised during training
|
| 180 |
+
latents_dir: "latents" # Directory containing precomputed video latents
|
| 181 |
+
conditions:
|
| 182 |
+
- type: first_frame # Use first frame as conditioning
|
| 183 |
+
probability: 0.5 # Apply this condition 50% of the time
|
| 184 |
+
audio:
|
| 185 |
+
is_generated: true # Audio is denoised during training
|
| 186 |
+
latents_dir: "audio_latents" # Directory containing precomputed audio latents
|
| 187 |
+
conditions: [] # No additional audio conditions (text-only)
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
**ModalityConfig parameters:**
|
| 191 |
+
|
| 192 |
+
| Parameter | Description |
|
| 193 |
+
|----------------|------------------------------------------------------------------------------------------------------------------|
|
| 194 |
+
| `is_generated` | `true` = modality is denoised (contributes to loss). `false` = frozen conditioning (sigma=0, no loss). |
|
| 195 |
+
| `latents_dir` | Directory name within `preprocessed_data_root` containing precomputed latents for this modality. |
|
| 196 |
+
| `conditions` | List of conditioning configs applied during training (see condition types below). Text conditioning is implicit. |
|
| 197 |
+
|
| 198 |
+
**Condition types:**
|
| 199 |
+
|
| 200 |
+
| Type | Parameters | Description |
|
| 201 |
+
|----------------|-----------------------------------------------------|---------------------------------------------------------------------------------------|
|
| 202 |
+
| `first_frame` | `probability` | First latent frame is clean, excluded from loss. **Video only.** |
|
| 203 |
+
| `prefix` | `temporal_boundary`, `probability` | First N latent temporal units are clean. For extension forward. |
|
| 204 |
+
| `suffix` | `temporal_boundary`, `probability` | Last N latent temporal units are clean. For extension backward. |
|
| 205 |
+
| `spatial_crop` | `spatial_region` (y1, x1, y2, x2 in px), `probability` | Rectangular region is clean, excluded from loss. For outpainting. **Video only.** |
|
| 206 |
+
| `mask` | `mask_dir`, `probability` | Per-sample mask directory. Masks are thresholded at `0.5`; `1` means conditioning, `0` means generate. |
|
| 207 |
+
| `reference` | `latents_dir`, `probability` | IC-LoRA style concatenation. Reference tokens are prepended, clean (timestep=0), no loss. |
|
| 208 |
+
|
| 209 |
+
> [!NOTE]
|
| 210 |
+
> The `prefix`, `suffix`, `mask`, and `reference` condition types work on both video and audio modalities —
|
| 211 |
+
> place them in the `video.conditions` or `audio.conditions` list as appropriate.
|
| 212 |
+
> `first_frame` and `spatial_crop` are video-only conditions.
|
| 213 |
+
|
| 214 |
+
> [!NOTE]
|
| 215 |
+
> Training conditions reference **directories** of precomputed data (within `preprocessed_data_root`),
|
| 216 |
+
> while validation conditions reference **individual files** (images, videos, masks) that are encoded
|
| 217 |
+
> on-the-fly during validation. The condition `type` names are the same, but the fields differ.
|
| 218 |
+
|
| 219 |
+
> [!NOTE]
|
| 220 |
+
> The legacy `text_to_video` and `video_to_video` strategies are deprecated but remain forward-compatible.
|
| 221 |
+
> New configs should use `name: "flexible"`.
|
| 222 |
+
|
| 223 |
+
### OptimizationConfig
|
| 224 |
+
|
| 225 |
+
Training optimization parameters including learning rates, batch sizes, and schedulers.
|
| 226 |
+
|
| 227 |
+
```yaml
|
| 228 |
+
optimization:
|
| 229 |
+
learning_rate: 1e-4 # Learning rate
|
| 230 |
+
steps: 2000 # Total training steps
|
| 231 |
+
batch_size: 1 # Batch size per GPU
|
| 232 |
+
gradient_accumulation_steps: 1 # Steps to accumulate gradients
|
| 233 |
+
max_grad_norm: 1.0 # Gradient clipping threshold
|
| 234 |
+
optimizer_type: "adamw" # "adamw" or "adamw8bit"
|
| 235 |
+
scheduler_type: "linear" # Scheduler type
|
| 236 |
+
scheduler_params: { } # Additional scheduler parameters
|
| 237 |
+
enable_gradient_checkpointing: true # Memory optimization
|
| 238 |
+
```
|
| 239 |
+
|
| 240 |
+
**Key parameters:**
|
| 241 |
+
|
| 242 |
+
| Parameter | Description |
|
| 243 |
+
|---------------------------------|----------------------------------------------------------------------------------------------|
|
| 244 |
+
| `learning_rate` | Learning rate for optimization (typical range: 1e-5 to 1e-3) |
|
| 245 |
+
| `steps` | Total number of training steps |
|
| 246 |
+
| `batch_size` | Batch size per GPU (reduce if running out of memory) |
|
| 247 |
+
| `gradient_accumulation_steps` | Accumulate gradients over multiple steps |
|
| 248 |
+
| `scheduler_type` | LR scheduler: `"constant"`, `"linear"`, `"cosine"`, `"cosine_with_restarts"`, `"polynomial"`, `"step"` |
|
| 249 |
+
| `enable_gradient_checkpointing` | Trade training speed for GPU memory savings (recommended for large models) |
|
| 250 |
+
|
| 251 |
+
### AccelerationConfig
|
| 252 |
+
|
| 253 |
+
Hardware acceleration and compute optimization settings.
|
| 254 |
+
|
| 255 |
+
```yaml
|
| 256 |
+
acceleration:
|
| 257 |
+
mixed_precision_mode: "bf16" # "no", "fp16", or "bf16"
|
| 258 |
+
quantization: null # Quantization options
|
| 259 |
+
load_text_encoder_in_8bit: false # Load text encoder in 8-bit
|
| 260 |
+
offload_optimizer_during_validation: false # Offload optimizer state to CPU during validation
|
| 261 |
+
```
|
| 262 |
+
|
| 263 |
+
**Key parameters:**
|
| 264 |
+
|
| 265 |
+
| Parameter | Description |
|
| 266 |
+
|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
| 267 |
+
| `mixed_precision_mode` | Precision mode - `"bf16"` recommended for modern GPUs |
|
| 268 |
+
| `quantization` | Model quantization: `null`, `"int8-quanto"`, `"int4-quanto"`, `"int2-quanto"`, `"fp8-quanto"`, or `"fp8uz-quanto"` |
|
| 269 |
+
| `load_text_encoder_in_8bit` | Load the Gemma text encoder in 8-bit to save GPU memory |
|
| 270 |
+
| `offload_optimizer_during_validation` | Move optimizer state to CPU before validation video sampling and back afterwards. Useful when validation OOMs because VAE decoder + transformer + optimizer state can't coexist on the GPU (full fine-tune, high-rank LoRA). No effect for FSDP. |
|
| 271 |
+
|
| 272 |
+
### DataConfig
|
| 273 |
+
|
| 274 |
+
Data loading and processing configuration.
|
| 275 |
+
|
| 276 |
+
```yaml
|
| 277 |
+
data:
|
| 278 |
+
preprocessed_data_root: "/path/to/preprocessed/data" # Path to precomputed dataset
|
| 279 |
+
num_dataloader_workers: 2 # Background data loading workers
|
| 280 |
+
```
|
| 281 |
+
|
| 282 |
+
**Key parameters:**
|
| 283 |
+
|
| 284 |
+
| Parameter | Description |
|
| 285 |
+
|--------------------------|--------------------------------------------------------------------------------------------|
|
| 286 |
+
| `preprocessed_data_root` | Path to your preprocessed dataset directory produced by `process_dataset.py` (contains `latents/`, `conditions/`, etc.) |
|
| 287 |
+
| `num_dataloader_workers` | Number of parallel data loading processes (0 = synchronous loading, useful when debugging) |
|
| 288 |
+
|
| 289 |
+
### ValidationConfig
|
| 290 |
+
|
| 291 |
+
Validation and inference settings for monitoring training progress. Validation samples use a self-describing
|
| 292 |
+
format where each sample specifies its own prompt and conditions.
|
| 293 |
+
|
| 294 |
+
```yaml
|
| 295 |
+
validation:
|
| 296 |
+
samples:
|
| 297 |
+
- prompt: "A cat playing with a ball"
|
| 298 |
+
conditions:
|
| 299 |
+
- type: first_frame
|
| 300 |
+
image_or_video: "/path/to/image.png"
|
| 301 |
+
- prompt: "A dog running in a field"
|
| 302 |
+
video_dims: [576, 576, 89] # Output dimensions: [width, height, frames]
|
| 303 |
+
negative_prompt: "worst quality, inconsistent motion, blurry, jittery, distorted" # Negative prompt for all samples
|
| 304 |
+
frame_rate: 25.0 # Output video frame rate (fps)
|
| 305 |
+
seed: 42 # Random seed for reproducibility
|
| 306 |
+
inference_steps: 30 # Number of denoising steps
|
| 307 |
+
interval: 100 # Run validation every N steps (null to disable)
|
| 308 |
+
guidance_scale: 4.0 # CFG scale (higher = stronger prompt adherence)
|
| 309 |
+
stg_scale: 1.0 # STG scale (0.0 to disable)
|
| 310 |
+
stg_blocks: [29] # Transformer blocks to apply STG perturbation
|
| 311 |
+
stg_mode: "stg_av" # STG mode: "stg_av" (audio+video) or "stg_v" (video only)
|
| 312 |
+
generate_audio: true # Whether to generate audio during validation
|
| 313 |
+
generate_video: true # Whether to generate video during validation
|
| 314 |
+
skip_initial_validation: false # Skip validation at step 0
|
| 315 |
+
```
|
| 316 |
+
|
| 317 |
+
**Key parameters:**
|
| 318 |
+
|
| 319 |
+
| Parameter | Description |
|
| 320 |
+
|--------------------------|--------------------------------------------------------------------------------------------------------------------------|
|
| 321 |
+
| `samples` | List of `ValidationSample` objects (see below). Replaces the legacy `prompts`/`images`/`reference_videos` fields. |
|
| 322 |
+
| `video_dims` | Output dimensions `[width, height, frames]`. Width/height must be divisible by 32, frames must satisfy `frames % 8 == 1` |
|
| 323 |
+
| `interval` | Steps between validation runs (set to `null` to disable) |
|
| 324 |
+
| `guidance_scale` | CFG (Classifier-Free Guidance) scale. Recommended: 4.0 |
|
| 325 |
+
| `stg_scale` | STG (Spatio-Temporal Guidance) scale. 0.0 disables STG. Recommended: 1.0 |
|
| 326 |
+
| `stg_blocks` | Transformer blocks to perturb for STG. Recommended: `[29]` (single block) |
|
| 327 |
+
| `stg_mode` | STG mode: `"stg_av"` perturbs both audio and video, `"stg_v"` perturbs video only |
|
| 328 |
+
| `generate_audio` | Whether to generate audio in validation samples |
|
| 329 |
+
| `generate_video` | Whether to generate video in validation samples. Set to `false` for V2A (video-to-audio) validation. Default: `true` |
|
| 330 |
+
| `skip_initial_validation`| Skip validation video sampling at step 0 (beginning of training) |
|
| 331 |
+
|
| 332 |
+
#### ValidationSample
|
| 333 |
+
|
| 334 |
+
Each sample in the `samples` list has:
|
| 335 |
+
|
| 336 |
+
| Field | Description |
|
| 337 |
+
|--------------|-------------------------------------------------------------------------------------------------|
|
| 338 |
+
| `prompt` | Text prompt for this validation sample. |
|
| 339 |
+
| `conditions` | List of validation conditions (see types below). Empty list = text-only generation. |
|
| 340 |
+
| `video_dims` | Optional per-sample override for `(width, height, frames)`. Inherits from `ValidationConfig` if not set. |
|
| 341 |
+
| `seed` | Optional per-sample override for random seed. Inherits from `ValidationConfig` if not set. |
|
| 342 |
+
|
| 343 |
+
#### Validation Condition Types
|
| 344 |
+
|
| 345 |
+
| Type | Parameters | Description |
|
| 346 |
+
|------------------|------------------------------------------------------------|-------------------------------------------------------------------------|
|
| 347 |
+
| `first_frame` | `image_or_video` (path) | Use the first frame of the image/video as conditioning. |
|
| 348 |
+
| `prefix` | `video` or `audio` (path), optional `num_frames`/`duration`| Use a video/audio clip as temporal prefix (for extension forward). |
|
| 349 |
+
| `suffix` | `video` or `audio` (path), optional `num_frames`/`duration`| Use a video/audio clip as temporal suffix (for extension backward). |
|
| 350 |
+
| `spatial_crop` | `video` (path), `spatial_region` (y1, x1, y2, x2) | Provide spatial context for outpainting. Video only. |
|
| 351 |
+
| `mask` | `video` or `audio` (path), `mask` (path) | Mask-based inpainting with a binary mask file. |
|
| 352 |
+
| `reference` | `video` or `audio` (path), optional video-reference `downscale_factor`, `temporal_scale_factor`, `include_in_output` | IC-LoRA style reference conditioning. |
|
| 353 |
+
| `video_to_audio` | `video` (path) | Freeze video, generate audio. For Foley/V2A tasks. |
|
| 354 |
+
| `audio_to_video` | `audio` (path) | Freeze audio, generate video. For audio-driven generation. |
|
| 355 |
+
|
| 356 |
+
For video `reference` validation conditions, `downscale_factor` is the spatial reference scale and
|
| 357 |
+
`temporal_scale_factor` is the temporal reference scale. Set both to match the factors used when
|
| 358 |
+
preprocessing video reference latents for training; validation media is encoded on the fly and cannot infer
|
| 359 |
+
those factors from the training dataset.
|
| 360 |
+
|
| 361 |
+
> [!NOTE]
|
| 362 |
+
> The legacy fields `prompts`, `images`, and `reference_videos` are deprecated but auto-converted to `samples`
|
| 363 |
+
> internally. New configs should use the `samples` format.
|
| 364 |
+
|
| 365 |
+
### CheckpointsConfig
|
| 366 |
+
|
| 367 |
+
Model checkpointing configuration.
|
| 368 |
+
|
| 369 |
+
```yaml
|
| 370 |
+
checkpoints:
|
| 371 |
+
interval: 250 # Steps between checkpoint saves (null = disabled)
|
| 372 |
+
keep_last_n: 3 # Number of recent checkpoints to retain
|
| 373 |
+
precision: bfloat16 # Precision for saved weights (bfloat16 or float32)
|
| 374 |
+
no_resume: false # Ignore saved state, start from step 0
|
| 375 |
+
save_training_state: "minimal" # "full", "minimal", or "off"
|
| 376 |
+
```
|
| 377 |
+
|
| 378 |
+
**Key parameters:**
|
| 379 |
+
|
| 380 |
+
| Parameter | Description |
|
| 381 |
+
|---------------|-------------------------------------------------------------------------------|
|
| 382 |
+
| `interval` | Steps between intermediate checkpoint saves (set to `null` to disable) |
|
| 383 |
+
| `keep_last_n` | Number of most recent checkpoints to keep (-1 = keep all) |
|
| 384 |
+
| `precision` | Precision for saved checkpoint weights: `"bfloat16"` (default) or `"float32"` |
|
| 385 |
+
| `no_resume` | When `true`, ignore saved training state and start from step 0. Model weights from `load_checkpoint` are still loaded. |
|
| 386 |
+
| `save_training_state` | Save training state for resume: `"full"` (optimizer + scheduler + RNG), `"minimal"` (scheduler + RNG only, sufficient for LoRA), `"off"` (no resume). |
|
| 387 |
+
|
| 388 |
+
### HubConfig
|
| 389 |
+
|
| 390 |
+
Hugging Face Hub integration for automatic model uploads.
|
| 391 |
+
|
| 392 |
+
```yaml
|
| 393 |
+
hub:
|
| 394 |
+
push_to_hub: false # Enable Hub uploading
|
| 395 |
+
hub_model_id: "username/model-name" # Hub repository ID
|
| 396 |
+
```
|
| 397 |
+
|
| 398 |
+
**Key parameters:**
|
| 399 |
+
|
| 400 |
+
| Parameter | Description |
|
| 401 |
+
|----------------|------------------------------------------------------------------|
|
| 402 |
+
| `push_to_hub` | Whether to automatically push trained models to Hugging Face Hub |
|
| 403 |
+
| `hub_model_id` | Repository ID in format `"username/repository-name"` |
|
| 404 |
+
|
| 405 |
+
### WandbConfig
|
| 406 |
+
|
| 407 |
+
Weights & Biases logging configuration.
|
| 408 |
+
|
| 409 |
+
```yaml
|
| 410 |
+
wandb:
|
| 411 |
+
enabled: false # Enable W&B logging
|
| 412 |
+
project: "ltx-2-trainer" # W&B project name
|
| 413 |
+
entity: null # W&B username or team
|
| 414 |
+
tags: [ ] # Tags for the run
|
| 415 |
+
log_validation_videos: true # Log validation videos to W&B
|
| 416 |
+
```
|
| 417 |
+
|
| 418 |
+
**Key parameters:**
|
| 419 |
+
|
| 420 |
+
| Parameter | Description |
|
| 421 |
+
|-------------------------|--------------------------------------------------|
|
| 422 |
+
| `enabled` | Whether to enable W&B logging |
|
| 423 |
+
| `project` | W&B project name |
|
| 424 |
+
| `entity` | W&B username or team (null uses default account) |
|
| 425 |
+
| `log_validation_videos` | Whether to log validation videos to W&B |
|
| 426 |
+
|
| 427 |
+
### FlowMatchingConfig
|
| 428 |
+
|
| 429 |
+
Flow matching training configuration for timestep sampling.
|
| 430 |
+
|
| 431 |
+
```yaml
|
| 432 |
+
flow_matching:
|
| 433 |
+
timestep_sampling_mode: "shifted_logit_normal" # Timestep sampling strategy
|
| 434 |
+
timestep_sampling_params: { } # Additional sampling parameters
|
| 435 |
+
```
|
| 436 |
+
|
| 437 |
+
**Key parameters:**
|
| 438 |
+
|
| 439 |
+
| Parameter | Description |
|
| 440 |
+
|----------------------------|------------------------------------------------------------|
|
| 441 |
+
| `timestep_sampling_mode` | Sampling strategy: `"uniform"` or `"shifted_logit_normal"` |
|
| 442 |
+
| `timestep_sampling_params` | Additional parameters for the sampling strategy |
|
| 443 |
+
|
| 444 |
+
### General Configuration
|
| 445 |
+
|
| 446 |
+
Top-level settings for the training run.
|
| 447 |
+
|
| 448 |
+
```yaml
|
| 449 |
+
seed: 42 # Random seed for reproducibility
|
| 450 |
+
output_dir: "outputs/my_training_run" # Directory for outputs (checkpoints, validation videos, logs)
|
| 451 |
+
```
|
| 452 |
+
|
| 453 |
+
| Parameter | Description |
|
| 454 |
+
|--------------|----------------------------------------------------------|
|
| 455 |
+
| `seed` | Random seed for reproducibility (default: `42`) |
|
| 456 |
+
| `output_dir` | Directory to save outputs (default: `"outputs"`) |
|
| 457 |
+
|
| 458 |
+
## 🚀 Next Steps
|
| 459 |
+
|
| 460 |
+
Once you've configured your training parameters:
|
| 461 |
+
|
| 462 |
+
- Set up your dataset using [Dataset Preparation](dataset-preparation.md)
|
| 463 |
+
- Choose your training approach in [Training Modes](training-modes.md)
|
| 464 |
+
- Start training with the [Training Guide](training-guide.md)
|
packages/ltx-trainer/docs/custom-training-strategies.md
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Implementing Custom Training Strategies
|
| 2 |
+
|
| 3 |
+
This guide explains how to implement your own training strategy for specialized recipes that cannot be expressed with
|
| 4 |
+
the built-in `flexible` strategy.
|
| 5 |
+
|
| 6 |
+
## 📋 Overview
|
| 7 |
+
|
| 8 |
+
The trainer uses the **Strategy Pattern** to separate training logic from the core training loop. Each strategy defines:
|
| 9 |
+
|
| 10 |
+
1. **What data is needed** - Which preprocessed data directories to load
|
| 11 |
+
2. **How to prepare inputs** - Transform batch data into model inputs
|
| 12 |
+
3. **How to compute loss** - Calculate the training objective
|
| 13 |
+
|
| 14 |
+
This architecture lets you implement new training modes without modifying the core trainer code.
|
| 15 |
+
|
| 16 |
+
### When You Need a Custom Strategy
|
| 17 |
+
|
| 18 |
+
> [!NOTE]
|
| 19 |
+
> The built-in `flexible` strategy already supports most conditioning scenarios out of the box:
|
| 20 |
+
> first-frame conditioning, video extension (prefix/suffix), spatial crop (outpainting),
|
| 21 |
+
> mask-based inpainting, IC-LoRA reference conditioning, and frozen modality cross-conditioning
|
| 22 |
+
> (audio-to-video, video-to-audio). Only implement a custom strategy if your use case requires
|
| 23 |
+
> fundamentally different training logic that cannot be expressed through the flexible strategy's
|
| 24 |
+
> configuration.
|
| 25 |
+
|
| 26 |
+
Consider implementing a custom strategy when you need:
|
| 27 |
+
|
| 28 |
+
- **Custom loss computation** (e.g., weighted losses, auxiliary losses, perceptual losses)
|
| 29 |
+
- **Non-standard noise application** (e.g., noise schedules different from flow matching)
|
| 30 |
+
- **Novel conditioning mechanisms** not covered by the flexible strategy's condition types
|
| 31 |
+
- **Additional model outputs** beyond the standard video/audio predictions
|
| 32 |
+
|
| 33 |
+
## 🏗️ Architecture Overview
|
| 34 |
+
|
| 35 |
+
### How Strategies Fit Into the Trainer
|
| 36 |
+
|
| 37 |
+
The trainer delegates all training-mode-specific logic to the strategy:
|
| 38 |
+
|
| 39 |
+
1. **Initialization** — The trainer calls `config.get_data_sources()` to determine which preprocessed data directories to load
|
| 40 |
+
2. **Each training step:**
|
| 41 |
+
- Calls `prepare_training_inputs()` to transform the raw batch into model-ready inputs
|
| 42 |
+
- Runs the transformer forward pass
|
| 43 |
+
- Calls `compute_loss()` to compute the training objective
|
| 44 |
+
|
| 45 |
+
The trainer handles everything else: optimization, checkpointing, validation, and distributed training.
|
| 46 |
+
|
| 47 |
+
### Key Components
|
| 48 |
+
|
| 49 |
+
| Component | Purpose |
|
| 50 |
+
|-----------------------------------------------------------------------------------------|--------------------------------------------------------------|
|
| 51 |
+
| [`TrainingStrategyConfigBase`](../src/ltx_trainer/training_strategies/base_strategy.py) | Base class for strategy configuration (Pydantic model) |
|
| 52 |
+
| [`TrainingStrategy`](../src/ltx_trainer/training_strategies/base_strategy.py) | Abstract base class defining the strategy interface |
|
| 53 |
+
| [`ModelInputs`](../src/ltx_trainer/training_strategies/base_strategy.py) | Dataclass containing prepared inputs for the transformer |
|
| 54 |
+
| [`Modality`](../../ltx-core/src/ltx_core/model/transformer/modality.py) | ltx-core dataclass representing video or audio modality data |
|
| 55 |
+
|
| 56 |
+
## 📝 Step-by-Step Implementation
|
| 57 |
+
|
| 58 |
+
### Step 1: Plan Your Strategy
|
| 59 |
+
|
| 60 |
+
Before writing code, answer these questions:
|
| 61 |
+
|
| 62 |
+
1. **What additional data does your strategy need?**
|
| 63 |
+
- Example: A perceptual-loss strategy may need auxiliary feature targets
|
| 64 |
+
- Example: A novel conditioning mechanism may need an additional precomputed directory
|
| 65 |
+
|
| 66 |
+
2. **What does conditioning look like?**
|
| 67 |
+
- Which tokens should be noised vs. kept clean?
|
| 68 |
+
- How should conditioning tokens be structured (e.g., first frame, reference video, mask)?
|
| 69 |
+
|
| 70 |
+
3. **How should loss be computed?**
|
| 71 |
+
- Which tokens contribute to the loss?
|
| 72 |
+
- Are there multiple loss terms to combine?
|
| 73 |
+
|
| 74 |
+
### Step 2: Extend Data Preprocessing (If Needed)
|
| 75 |
+
|
| 76 |
+
If your strategy requires additional preprocessed data beyond video latents, audio latents, and text embeddings, you'll
|
| 77 |
+
need to extend the preprocessing pipeline.
|
| 78 |
+
|
| 79 |
+
#### Option A: Modify `process_dataset.py`
|
| 80 |
+
|
| 81 |
+
For integrated preprocessing, add new arguments and processing steps to the main script. For example, to add mask
|
| 82 |
+
preprocessing:
|
| 83 |
+
|
| 84 |
+
```python
|
| 85 |
+
# In process_dataset.py, add a new argument
|
| 86 |
+
@app.command()
|
| 87 |
+
def main(
|
| 88 |
+
# ... existing arguments ...
|
| 89 |
+
mask_column: str | None = typer.Option(
|
| 90 |
+
default=None,
|
| 91 |
+
help="Column name containing mask video paths (for inpainting)",
|
| 92 |
+
),
|
| 93 |
+
) -> None:
|
| 94 |
+
# ... existing processing ...
|
| 95 |
+
|
| 96 |
+
# Process masks if provided
|
| 97 |
+
if mask_column:
|
| 98 |
+
logger.info("Processing mask videos for inpainting training...")
|
| 99 |
+
mask_latents_dir = output_base / "mask_latents"
|
| 100 |
+
|
| 101 |
+
compute_latents(
|
| 102 |
+
dataset_file=dataset_path,
|
| 103 |
+
video_column=mask_column,
|
| 104 |
+
resolution_buckets=parsed_resolution_buckets,
|
| 105 |
+
output_dir=str(mask_latents_dir),
|
| 106 |
+
model_path=model_path,
|
| 107 |
+
# ... other args ...
|
| 108 |
+
)
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
#### Option B: Create a Standalone Script
|
| 112 |
+
|
| 113 |
+
For complex preprocessing that doesn't fit naturally into the existing pipeline, create a dedicated script
|
| 114 |
+
(e.g., `scripts/process_masks.py`). Use [`scripts/compute_reference.py`](../scripts/compute_reference.py) as a
|
| 115 |
+
template - it shows how to process paired data and update the dataset JSON.
|
| 116 |
+
|
| 117 |
+
#### Expected Output Structure
|
| 118 |
+
|
| 119 |
+
Your preprocessing should create a directory structure that the strategy can reference:
|
| 120 |
+
|
| 121 |
+
```
|
| 122 |
+
preprocessed_data_root/
|
| 123 |
+
├── latents/ # Video latents (standard)
|
| 124 |
+
├── conditions/ # Text embeddings (standard)
|
| 125 |
+
├── audio_latents/ # Audio latents (if with_audio)
|
| 126 |
+
├── mask_latents/ # Your custom data directory
|
| 127 |
+
└── reference_latents/ # Reference videos (for IC-LoRA)
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
### Step 3: Create the Strategy Configuration
|
| 131 |
+
|
| 132 |
+
Create a new file for your strategy (e.g., `src/ltx_trainer/training_strategies/inpainting.py`):
|
| 133 |
+
|
| 134 |
+
```python
|
| 135 |
+
"""Inpainting training strategy.
|
| 136 |
+
|
| 137 |
+
This strategy implements video inpainting training where:
|
| 138 |
+
- Mask latents indicate which regions to inpaint
|
| 139 |
+
- Loss is computed only on masked (inpainted) regions
|
| 140 |
+
"""
|
| 141 |
+
|
| 142 |
+
from typing import Any, Literal
|
| 143 |
+
|
| 144 |
+
import torch
|
| 145 |
+
from pydantic import Field
|
| 146 |
+
from torch import Tensor
|
| 147 |
+
|
| 148 |
+
from ltx_core.model.transformer.modality import Modality
|
| 149 |
+
from ltx_trainer.timestep_samplers import TimestepSampler
|
| 150 |
+
from ltx_trainer.training_strategies.base_strategy import (
|
| 151 |
+
ModelInputs,
|
| 152 |
+
TrainingStrategy,
|
| 153 |
+
TrainingStrategyConfigBase,
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
class InpaintingConfig(TrainingStrategyConfigBase):
|
| 158 |
+
"""Configuration for inpainting training strategy."""
|
| 159 |
+
|
| 160 |
+
# The 'name' field acts as a discriminator for the config union
|
| 161 |
+
name: Literal["inpainting"] = "inpainting"
|
| 162 |
+
|
| 163 |
+
mask_latents_dir: str = Field(
|
| 164 |
+
default="mask_latents",
|
| 165 |
+
description="Directory name for mask latents",
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
# Add any strategy-specific parameters
|
| 169 |
+
mask_threshold: float = Field(
|
| 170 |
+
default=0.5,
|
| 171 |
+
description="Threshold for binary mask conversion",
|
| 172 |
+
ge=0.0,
|
| 173 |
+
le=1.0,
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
def get_data_sources(self) -> dict[str, str]:
|
| 177 |
+
"""Define which data directories to load.
|
| 178 |
+
|
| 179 |
+
Returns a mapping of directory names (under preprocessed_data_root) to
|
| 180 |
+
batch keys. The trainer loads .pt files from each directory and exposes
|
| 181 |
+
them in the batch under the specified key. The trainer also uses this
|
| 182 |
+
mapping to validate that all required directories exist.
|
| 183 |
+
"""
|
| 184 |
+
return {
|
| 185 |
+
"latents": "latents", # -> batch["latents"]
|
| 186 |
+
"conditions": "conditions", # -> batch["conditions"]
|
| 187 |
+
self.mask_latents_dir: "masks", # -> batch["masks"]
|
| 188 |
+
}
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
**Key points:**
|
| 192 |
+
|
| 193 |
+
- Inherit from `TrainingStrategyConfigBase`
|
| 194 |
+
- Use `Literal["your_strategy_name"]` for the `name` field - this enables automatic strategy selection
|
| 195 |
+
- Use Pydantic `Field` for validation and documentation
|
| 196 |
+
- Implement `get_data_sources()` on the config — it's the single source of truth for data directories (used for both dataset wiring and existence validation)
|
| 197 |
+
|
| 198 |
+
### Step 4: Implement the Strategy Class
|
| 199 |
+
|
| 200 |
+
```python
|
| 201 |
+
class InpaintingStrategy(TrainingStrategy):
|
| 202 |
+
"""Inpainting training strategy.
|
| 203 |
+
|
| 204 |
+
Trains the model to fill in masked regions of videos while
|
| 205 |
+
keeping unmasked regions as conditioning.
|
| 206 |
+
"""
|
| 207 |
+
|
| 208 |
+
config: InpaintingConfig
|
| 209 |
+
|
| 210 |
+
def __init__(self, config: InpaintingConfig):
|
| 211 |
+
super().__init__(config)
|
| 212 |
+
|
| 213 |
+
def prepare_training_inputs(
|
| 214 |
+
self,
|
| 215 |
+
batch: dict[str, Any],
|
| 216 |
+
timestep_sampler: TimestepSampler,
|
| 217 |
+
) -> ModelInputs:
|
| 218 |
+
"""Transform batch data into model inputs.
|
| 219 |
+
|
| 220 |
+
This is where the core training logic lives:
|
| 221 |
+
1. Extract and patchify latents
|
| 222 |
+
2. Sample noise and apply it appropriately
|
| 223 |
+
3. Create conditioning masks
|
| 224 |
+
4. Build Modality objects for the transformer
|
| 225 |
+
"""
|
| 226 |
+
# Get video latents [B, C, F, H, W]
|
| 227 |
+
latents_data = batch["latents"]
|
| 228 |
+
video_latents = latents_data["latents"]
|
| 229 |
+
|
| 230 |
+
# Get dimensions
|
| 231 |
+
num_frames = latents_data["num_frames"][0].item()
|
| 232 |
+
height = latents_data["height"][0].item()
|
| 233 |
+
width = latents_data["width"][0].item()
|
| 234 |
+
|
| 235 |
+
# Patchify: [B, C, F, H, W] -> [B, seq_len, C]
|
| 236 |
+
video_latents = self._video_patchifier.patchify(video_latents)
|
| 237 |
+
|
| 238 |
+
batch_size, seq_len, _ = video_latents.shape
|
| 239 |
+
device = video_latents.device
|
| 240 |
+
dtype = video_latents.dtype
|
| 241 |
+
|
| 242 |
+
# Get mask latents and process them
|
| 243 |
+
mask_data = batch["masks"]
|
| 244 |
+
mask_latents = mask_data["latents"]
|
| 245 |
+
mask_latents = self._video_patchifier.patchify(mask_latents)
|
| 246 |
+
|
| 247 |
+
# Create binary mask: True = inpaint this region, False = keep original
|
| 248 |
+
inpaint_mask = mask_latents.mean(dim=-1) > self.config.mask_threshold
|
| 249 |
+
|
| 250 |
+
# Sample noise and sigmas
|
| 251 |
+
sigmas = timestep_sampler.sample_for(video_latents)
|
| 252 |
+
noise = torch.randn_like(video_latents)
|
| 253 |
+
|
| 254 |
+
# Apply noise only to inpaint regions
|
| 255 |
+
sigmas_expanded = sigmas.view(-1, 1, 1)
|
| 256 |
+
noisy_latents = (1 - sigmas_expanded) * video_latents + sigmas_expanded * noise
|
| 257 |
+
|
| 258 |
+
# Keep original latents for non-inpaint regions (conditioning)
|
| 259 |
+
inpaint_mask_expanded = inpaint_mask.unsqueeze(-1)
|
| 260 |
+
noisy_latents = torch.where(inpaint_mask_expanded, noisy_latents, video_latents)
|
| 261 |
+
|
| 262 |
+
# Create per-token timesteps
|
| 263 |
+
# Conditioning tokens (non-inpaint) get timestep=0
|
| 264 |
+
# Inpaint tokens get the sampled sigma
|
| 265 |
+
timesteps = self._create_per_token_timesteps(~inpaint_mask, sigmas.squeeze())
|
| 266 |
+
|
| 267 |
+
# Compute targets (velocity prediction: noise - clean)
|
| 268 |
+
targets = noise - video_latents
|
| 269 |
+
|
| 270 |
+
# Get text embeddings
|
| 271 |
+
conditions = batch["conditions"]
|
| 272 |
+
video_prompt_embeds = conditions["video_prompt_embeds"]
|
| 273 |
+
prompt_attention_mask = conditions["prompt_attention_mask"]
|
| 274 |
+
|
| 275 |
+
# Generate position embeddings
|
| 276 |
+
positions = self._get_video_positions(
|
| 277 |
+
num_frames=num_frames,
|
| 278 |
+
height=height,
|
| 279 |
+
width=width,
|
| 280 |
+
batch_size=batch_size,
|
| 281 |
+
fps=24.0, # Or get from latents_data
|
| 282 |
+
device=device,
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
# Create video Modality
|
| 286 |
+
video_modality = Modality(
|
| 287 |
+
enabled=True,
|
| 288 |
+
latent=noisy_latents,
|
| 289 |
+
sigma=sigmas,
|
| 290 |
+
timesteps=timesteps,
|
| 291 |
+
positions=positions,
|
| 292 |
+
context=video_prompt_embeds,
|
| 293 |
+
context_mask=prompt_attention_mask,
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
# Loss mask: only compute loss on inpaint regions
|
| 297 |
+
loss_mask = inpaint_mask
|
| 298 |
+
|
| 299 |
+
return ModelInputs(
|
| 300 |
+
video=video_modality,
|
| 301 |
+
audio=None,
|
| 302 |
+
video_targets=targets,
|
| 303 |
+
audio_targets=None,
|
| 304 |
+
video_loss_mask=loss_mask,
|
| 305 |
+
audio_loss_mask=None,
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
def compute_loss(
|
| 309 |
+
self,
|
| 310 |
+
video_pred: Tensor,
|
| 311 |
+
audio_pred: Tensor | None,
|
| 312 |
+
inputs: ModelInputs,
|
| 313 |
+
) -> Tensor:
|
| 314 |
+
"""Compute training loss on inpaint regions only. Returns [B,]."""
|
| 315 |
+
# MSE loss
|
| 316 |
+
loss = (video_pred - inputs.video_targets).pow(2)
|
| 317 |
+
|
| 318 |
+
# Apply loss mask and reduce to per-element [B,]
|
| 319 |
+
loss_mask = inputs.video_loss_mask.unsqueeze(-1).float()
|
| 320 |
+
masked = loss.mul(loss_mask)
|
| 321 |
+
return masked.mean(dim=[-2, -1]) / loss_mask.mean(dim=[-2, -1]).clamp(min=1e-8)
|
| 322 |
+
```
|
| 323 |
+
|
| 324 |
+
### Step 5: Register the Strategy
|
| 325 |
+
|
| 326 |
+
You need to register your strategy in two places:
|
| 327 |
+
|
| 328 |
+
**1. Update [`src/ltx_trainer/training_strategies/__init__.py`](../src/ltx_trainer/training_strategies/__init__.py):**
|
| 329 |
+
|
| 330 |
+
```python
|
| 331 |
+
# Add import for your strategy
|
| 332 |
+
from ltx_trainer.training_strategies.inpainting import InpaintingConfig, InpaintingStrategy
|
| 333 |
+
|
| 334 |
+
# Add to the TrainingStrategyConfig type alias
|
| 335 |
+
TrainingStrategyConfig = TextToVideoConfig | VideoToVideoConfig | FlexibleStrategyConfig | InpaintingConfig
|
| 336 |
+
|
| 337 |
+
# Add to __all__
|
| 338 |
+
__all__ = [
|
| 339 |
+
# ... existing exports ...
|
| 340 |
+
"InpaintingConfig",
|
| 341 |
+
"InpaintingStrategy",
|
| 342 |
+
]
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
# Add case in get_training_strategy()
|
| 346 |
+
def get_training_strategy(config: TrainingStrategyConfig) -> TrainingStrategy:
|
| 347 |
+
match config:
|
| 348 |
+
# ... existing cases ...
|
| 349 |
+
case InpaintingConfig():
|
| 350 |
+
strategy = InpaintingStrategy(config)
|
| 351 |
+
```
|
| 352 |
+
|
| 353 |
+
**2. Update [`src/ltx_trainer/config.py`](../src/ltx_trainer/config.py):**
|
| 354 |
+
|
| 355 |
+
```python
|
| 356 |
+
# Add import
|
| 357 |
+
from ltx_trainer.training_strategies.inpainting import InpaintingConfig
|
| 358 |
+
|
| 359 |
+
# Add to the TrainingStrategyConfig union with a Tag matching your strategy name
|
| 360 |
+
TrainingStrategyConfig = Annotated[
|
| 361 |
+
Annotated[TextToVideoConfig, Tag("text_to_video")]
|
| 362 |
+
| Annotated[VideoToVideoConfig, Tag("video_to_video")]
|
| 363 |
+
| Annotated[FlexibleStrategyConfig, Tag("flexible")]
|
| 364 |
+
| Annotated[InpaintingConfig, Tag("inpainting")],
|
| 365 |
+
Discriminator(_get_strategy_discriminator),
|
| 366 |
+
]
|
| 367 |
+
```
|
| 368 |
+
|
| 369 |
+
### Step 6: Create a Configuration File
|
| 370 |
+
|
| 371 |
+
Create an example config in `configs/`:
|
| 372 |
+
|
| 373 |
+
```yaml
|
| 374 |
+
# configs/custom_inpainting_lora.yaml
|
| 375 |
+
|
| 376 |
+
model:
|
| 377 |
+
model_path: "/path/to/ltx2.safetensors"
|
| 378 |
+
text_encoder_path: "/path/to/gemma"
|
| 379 |
+
training_mode: "lora"
|
| 380 |
+
|
| 381 |
+
training_strategy:
|
| 382 |
+
name: "inpainting" # Must match your Literal type
|
| 383 |
+
mask_latents_dir: "mask_latents"
|
| 384 |
+
mask_threshold: 0.5
|
| 385 |
+
|
| 386 |
+
lora:
|
| 387 |
+
rank: 32
|
| 388 |
+
alpha: 32
|
| 389 |
+
target_modules:
|
| 390 |
+
- "to_k"
|
| 391 |
+
- "to_q"
|
| 392 |
+
- "to_v"
|
| 393 |
+
- "to_out.0"
|
| 394 |
+
|
| 395 |
+
data:
|
| 396 |
+
preprocessed_data_root: "/path/to/preprocessed/dataset"
|
| 397 |
+
|
| 398 |
+
optimization:
|
| 399 |
+
learning_rate: 1e-4
|
| 400 |
+
steps: 2000
|
| 401 |
+
batch_size: 1
|
| 402 |
+
|
| 403 |
+
# ... other config sections ...
|
| 404 |
+
```
|
| 405 |
+
|
| 406 |
+
## 🔧 Helper Methods Reference
|
| 407 |
+
|
| 408 |
+
The base `TrainingStrategy` class provides these helper methods:
|
| 409 |
+
|
| 410 |
+
| Method | Purpose |
|
| 411 |
+
|----------------------------------------------|-------------------------------------------------|
|
| 412 |
+
| `_video_patchifier.patchify(latents)` | Convert `[B, C, F, H, W]` → `[B, seq_len, C]` |
|
| 413 |
+
| `_audio_patchifier.patchify(latents)` | Convert `[B, C, T, F]` → `[B, T, C*F]` |
|
| 414 |
+
| `_get_video_positions(...)` | Generate position embeddings for video |
|
| 415 |
+
| `_get_audio_positions(...)` | Generate position embeddings for audio |
|
| 416 |
+
| `_create_per_token_timesteps(conditioning_mask, sampled_sigma)` | Create timesteps with 0 for conditioning tokens |
|
| 417 |
+
| `_create_first_frame_conditioning_mask(...)` | Create mask for first-frame conditioning |
|
| 418 |
+
|
| 419 |
+
## 📊 Understanding ModelInputs
|
| 420 |
+
|
| 421 |
+
The `ModelInputs` dataclass contains everything needed for the forward pass and loss computation:
|
| 422 |
+
|
| 423 |
+
```python
|
| 424 |
+
@dataclass
|
| 425 |
+
class ModelInputs:
|
| 426 |
+
video: Modality | None # Video modality data
|
| 427 |
+
audio: Modality | None # Audio modality data
|
| 428 |
+
|
| 429 |
+
video_targets: Tensor | None # Target values for video loss (velocity)
|
| 430 |
+
audio_targets: Tensor | None # Target values for audio loss (velocity)
|
| 431 |
+
|
| 432 |
+
video_loss_mask: Tensor | None # Boolean loss mask for video tokens
|
| 433 |
+
audio_loss_mask: Tensor | None # Boolean loss mask for audio tokens
|
| 434 |
+
```
|
| 435 |
+
|
| 436 |
+
## 📊 Understanding Modality
|
| 437 |
+
|
| 438 |
+
The `Modality` dataclass (from ltx-core) represents a single modality's data:
|
| 439 |
+
|
| 440 |
+
```python
|
| 441 |
+
@dataclass(frozen=True)
|
| 442 |
+
class Modality:
|
| 443 |
+
latent: Tensor # [B, T, D] — patchified latent tokens
|
| 444 |
+
sigma: Tensor # [B,] — per-batch noise level (for cross-attn conditioning)
|
| 445 |
+
timesteps: Tensor # [B, T] — per-token timestep embeddings
|
| 446 |
+
positions: Tensor # [B, 3, T, 2] for video, [B, 1, T, 2] for audio — positional bounds
|
| 447 |
+
context: Tensor # text conditioning embeddings
|
| 448 |
+
enabled: bool = True
|
| 449 |
+
context_mask: Tensor | None = None # attention mask for text context
|
| 450 |
+
attention_mask: Tensor | None = None # optional 2D self-attention mask [B, T, T]
|
| 451 |
+
```
|
| 452 |
+
|
| 453 |
+
> [!NOTE]
|
| 454 |
+
> **Per-token timesteps:** Each token in the sequence has its own timestep. Conditioning tokens—those that should remain
|
| 455 |
+
> un-noised—must have `timestep=0`. This is how the model distinguishes clean reference tokens from tokens to denoise. Use
|
| 456 |
+
> `_create_per_token_timesteps(conditioning_mask, sampled_sigma)` to set this up correctly.
|
| 457 |
+
|
| 458 |
+
> [!NOTE]
|
| 459 |
+
> `Modality` is immutable (frozen dataclass). Use `dataclasses.replace()` to create modified copies.
|
| 460 |
+
|
| 461 |
+
## ✅ Testing Your Strategy
|
| 462 |
+
|
| 463 |
+
1. **Verify your training configuration is valid:**
|
| 464 |
+
```bash
|
| 465 |
+
uv run python -c "
|
| 466 |
+
from ltx_trainer.config import LtxTrainerConfig
|
| 467 |
+
import yaml
|
| 468 |
+
|
| 469 |
+
with open('configs/custom_inpainting_lora.yaml') as f:
|
| 470 |
+
config = LtxTrainerConfig(**yaml.safe_load(f))
|
| 471 |
+
print(f'Strategy: {config.training_strategy.name}')
|
| 472 |
+
"
|
| 473 |
+
```
|
| 474 |
+
|
| 475 |
+
2. **Test strategy instantiation:**
|
| 476 |
+
```bash
|
| 477 |
+
uv run python -c "
|
| 478 |
+
from ltx_trainer.training_strategies import get_training_strategy
|
| 479 |
+
from ltx_trainer.training_strategies.inpainting import InpaintingConfig
|
| 480 |
+
|
| 481 |
+
config = InpaintingConfig()
|
| 482 |
+
strategy = get_training_strategy(config)
|
| 483 |
+
print(f'Data sources: {config.get_data_sources()}')
|
| 484 |
+
"
|
| 485 |
+
```
|
| 486 |
+
|
| 487 |
+
3. **Run a short training test:**
|
| 488 |
+
```bash
|
| 489 |
+
uv run python scripts/train.py configs/custom_inpainting_lora.yaml
|
| 490 |
+
```
|
| 491 |
+
|
| 492 |
+
## 💡 Tips and Best Practices
|
| 493 |
+
|
| 494 |
+
### Debugging
|
| 495 |
+
|
| 496 |
+
- Set `data.num_dataloader_workers: 0` to get clearer error messages
|
| 497 |
+
- Use a small dataset and few steps for initial testing
|
| 498 |
+
- Check tensor shapes at each step with print statements
|
| 499 |
+
|
| 500 |
+
## 🔗 Related Documentation
|
| 501 |
+
|
| 502 |
+
- [Training Modes](training-modes.md) - Overview of built-in training modes
|
| 503 |
+
- [Configuration Reference](configuration-reference.md) - All configuration options
|
| 504 |
+
- [Dataset Preparation](dataset-preparation.md) - Preprocessing workflow
|
| 505 |
+
- [ltx-core Documentation](../../ltx-core/README.md) - Core model components
|
| 506 |
+
|
| 507 |
+
## 📚 Reference: Existing Strategies
|
| 508 |
+
|
| 509 |
+
Study these implementations for guidance:
|
| 510 |
+
|
| 511 |
+
| Strategy | Complexity | Key Features |
|
| 512 |
+
|----------|------------|--------------|
|
| 513 |
+
| [`FlexibleStrategy`](../src/ltx_trainer/training_strategies/flexible.py) | Medium | Unified conditioning framework — supports all built-in modes |
|
| 514 |
+
| [`TextToVideoStrategy`](../src/ltx_trainer/training_strategies/text_to_video.py) | Simple | First-frame conditioning, optional audio (deprecated) |
|
| 515 |
+
| [`VideoToVideoStrategy`](../src/ltx_trainer/training_strategies/video_to_video.py) | Medium | Reference video concatenation, split loss mask (deprecated) |
|
packages/ltx-trainer/docs/dataset-preparation.md
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dataset Preparation Guide
|
| 2 |
+
|
| 3 |
+
This guide covers the complete workflow for preparing and preprocessing your dataset for training.
|
| 4 |
+
|
| 5 |
+
## 📋 Overview
|
| 6 |
+
|
| 7 |
+
The general dataset preparation workflow is:
|
| 8 |
+
|
| 9 |
+
1. **(Optional)** Split long videos into scenes using `split_scenes.py`
|
| 10 |
+
2. **(Optional)** Generate captions for your videos using `caption_videos.py`
|
| 11 |
+
3. **Preprocess your dataset** using `process_dataset.py` to compute and cache video/audio latents and text embeddings
|
| 12 |
+
4. **Run the trainer** with your preprocessed dataset
|
| 13 |
+
|
| 14 |
+
## 🎬 Step 1: Split Scenes
|
| 15 |
+
|
| 16 |
+
If you're starting with raw, long-form videos (e.g., downloaded from YouTube), you should first split them into shorter, coherent scenes.
|
| 17 |
+
|
| 18 |
+
```bash
|
| 19 |
+
uv run python scripts/split_scenes.py input.mp4 scenes_output_dir/ \
|
| 20 |
+
--filter-shorter-than 5s
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
This will create multiple video clips in `scenes_output_dir`.
|
| 24 |
+
These clips will be the input for the captioning step, if you choose to use it.
|
| 25 |
+
|
| 26 |
+
The script supports many configuration options for scene detection (detector algorithms, thresholds, minimum scene lengths, etc.):
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
uv run python scripts/split_scenes.py --help
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
## 📝 Step 2: Caption Videos
|
| 33 |
+
|
| 34 |
+
If your dataset doesn't include captions, you can automatically generate them using multimodal models that understand both video and audio.
|
| 35 |
+
|
| 36 |
+
The default `qwen_omni` backend talks to a local vLLM server, which you launch once in a separate terminal:
|
| 37 |
+
|
| 38 |
+
```bash
|
| 39 |
+
# Terminal 1: start the captioner server (stays running)
|
| 40 |
+
uv run python scripts/serve_captioner.py
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
```bash
|
| 44 |
+
# Terminal 2: caption your videos
|
| 45 |
+
uv run python scripts/caption_videos.py scenes_output_dir/ \
|
| 46 |
+
--output scenes_output_dir/dataset.json
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
This will create a `dataset.json` file containing video paths and their captions.
|
| 50 |
+
|
| 51 |
+
**Captioning options:**
|
| 52 |
+
|
| 53 |
+
| Option | Description |
|
| 54 |
+
| ------------------ | --------------------------------------------------------------- |
|
| 55 |
+
| `--captioner-type` | `qwen_omni` (default, local vLLM server) or `gemini_flash` (API) |
|
| 56 |
+
| `--vllm-url` | Base URL of the vLLM server (default `http://127.0.0.1:8001/v1`) |
|
| 57 |
+
| `--override` | Re-caption files that already have captions |
|
| 58 |
+
| `--api-key` | Gemini API key (else `GEMINI_API_KEY`/`GOOGLE_API_KEY`; with no key, uses gcloud/Vertex AI auth) |
|
| 59 |
+
|
| 60 |
+
**Caption format:**
|
| 61 |
+
|
| 62 |
+
Each caption is a single, detailed paragraph describing both the visual content and the audio (speech, music, ambient sounds) of the clip. See the [Utility Scripts Reference](utility-scripts.md#automatic-video-captioning) for backend setup and the full list of options.
|
| 63 |
+
|
| 64 |
+
> [!NOTE]
|
| 65 |
+
> The automatically generated captions may contain inaccuracies or hallucinated content.
|
| 66 |
+
> We recommend reviewing and correcting the generated captions in your `dataset.json` file before proceeding to preprocessing.
|
| 67 |
+
|
| 68 |
+
## ⚡ Step 3: Dataset Preprocessing
|
| 69 |
+
|
| 70 |
+
This step preprocesses your video dataset by:
|
| 71 |
+
|
| 72 |
+
1. Resizing and cropping videos to fit specified resolution buckets
|
| 73 |
+
2. Computing and caching video latent representations
|
| 74 |
+
3. Computing and caching text embeddings for captions
|
| 75 |
+
4. Extracting and caching audio latents from videos (automatic, use `--skip-audio` to disable)
|
| 76 |
+
|
| 77 |
+
> [!WARNING]
|
| 78 |
+
> Very large videos (especially high spatial resolution and/or many frames) can cause GPU out-of-memory (OOM)
|
| 79 |
+
> during preprocessing/encoding.
|
| 80 |
+
> The simplest fix is to reduce the target resolution (spatially: width/height) and/or the number of frames
|
| 81 |
+
> (temporally) by using `--resolution-buckets` with smaller dimensions (lower width/height and/or fewer frames).
|
| 82 |
+
|
| 83 |
+
### Basic Usage
|
| 84 |
+
|
| 85 |
+
```bash
|
| 86 |
+
uv run python scripts/process_dataset.py dataset.json \
|
| 87 |
+
--resolution-buckets "960x544x49" \
|
| 88 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 89 |
+
--text-encoder-path /path/to/gemma-model
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
Audio latents are automatically extracted from video files — no extra flag is needed. Use `--skip-audio`
|
| 93 |
+
to disable this. For standalone audio files (`.wav`), use the `audio` column in your dataset instead
|
| 94 |
+
(see [Convention-Based Column Detection](#convention-based-column-detection) below).
|
| 95 |
+
|
| 96 |
+
### 🚀 Multi-GPU Preprocessing
|
| 97 |
+
|
| 98 |
+
Preprocessing large datasets can take a while. To run it across multiple GPUs in parallel, wrap the command with
|
| 99 |
+
`accelerate launch` (for example `--num_processes 4`). Each process handles an interleaved slice of the dataset.
|
| 100 |
+
The same approach applies to `process_videos.py` and `process_captions.py` when you run them standalone.
|
| 101 |
+
|
| 102 |
+
```bash
|
| 103 |
+
uv run accelerate launch --num_processes 4 scripts/process_dataset.py dataset.json \
|
| 104 |
+
--resolution-buckets "960x544x49" \
|
| 105 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 106 |
+
--text-encoder-path /path/to/gemma-model
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
Outputs are written atomically (via a per-process temporary file, then renamed), so an interrupted run leaves no
|
| 110 |
+
corrupt files. By default a rerun **resumes** — items whose output `.pt` already exists are skipped.
|
| 111 |
+
|
| 112 |
+
> [!IMPORTANT]
|
| 113 |
+
> Pass **`--overwrite`** when rerunning with changed parameters (different model checkpoint, resolution buckets,
|
| 114 |
+
> text encoder, `--lora-trigger`, etc.). Without it the script keeps the stale outputs from the previous run.
|
| 115 |
+
>
|
| 116 |
+
> ```bash
|
| 117 |
+
> uv run accelerate launch --num_processes 4 scripts/process_dataset.py dataset.json \
|
| 118 |
+
> --resolution-buckets "960x544x49" \
|
| 119 |
+
> --model-path /path/to/ltx-2.3-model.safetensors \
|
| 120 |
+
> --text-encoder-path /path/to/gemma-model \
|
| 121 |
+
> --overwrite
|
| 122 |
+
> ```
|
| 123 |
+
|
| 124 |
+
### 📊 Dataset Format
|
| 125 |
+
|
| 126 |
+
The trainer supports videos, single images, or a mix of both in the same dataset.
|
| 127 |
+
|
| 128 |
+
> [!TIP]
|
| 129 |
+
> **Image Datasets:** When using images, follow the same preprocessing steps and format requirements as with videos,
|
| 130 |
+
> but use `1` for the frame count in the resolution bucket (e.g., `960x544x1`).
|
| 131 |
+
|
| 132 |
+
> [!NOTE]
|
| 133 |
+
> **Mixed image + video datasets:** Mixing stills and videos in a single dataset is supported, but requires some care:
|
| 134 |
+
>
|
| 135 |
+
> - Preprocess with **multiple resolution buckets** covering both frame counts — e.g.
|
| 136 |
+
> `--resolution-buckets "960x544x1;960x544x49"`. Images are automatically assigned to the `F=1` bucket and
|
| 137 |
+
> videos to an `F>1` bucket.
|
| 138 |
+
> - You **must** set `optimization.batch_size: 1` in your training config (see the warning under
|
| 139 |
+
> [Resolution Buckets](#resolution-buckets)), since samples with different shapes cannot be collated into a
|
| 140 |
+
> single batch. Use `gradient_accumulation_steps` if you need a larger effective batch.
|
| 141 |
+
> - Per-step cost differs substantially between a single-frame sample and a many-frame sample, which can lead to
|
| 142 |
+
> uneven gradient magnitudes across steps. Consider weighting the two subsets or tuning the learning rate if
|
| 143 |
+
> you observe instability.
|
| 144 |
+
> - If you prefer a fully officially-supported path, train two separate LoRAs (one on stills, one on video) and
|
| 145 |
+
> stack them at inference.
|
| 146 |
+
|
| 147 |
+
The dataset must be a CSV, JSON, or JSONL metadata file with columns for captions and media paths.
|
| 148 |
+
|
| 149 |
+
#### Convention-Based Column Detection
|
| 150 |
+
|
| 151 |
+
The preprocessing script automatically detects and processes columns based on their names. The following columns are recognized:
|
| 152 |
+
|
| 153 |
+
| Column | Output Dir | Description |
|
| 154 |
+
|--------|-----------|-------------|
|
| 155 |
+
| `video` (or legacy `media_path`) | `latents/` | Target video to encode |
|
| 156 |
+
| `audio` | `audio_latents/` | Explicit audio file (overrides auto-extraction from video) |
|
| 157 |
+
| `caption` | `conditions/` | Text caption for the sample |
|
| 158 |
+
| `reference_video` (or legacy `ref_media_path`) | `reference_latents/` | IC-LoRA reference video |
|
| 159 |
+
| `reference_audio` | `reference_audio_latents/` | IC-LoRA reference audio |
|
| 160 |
+
| `video_mask` | `video_masks/` | Binary mask for video inpainting |
|
| 161 |
+
| `audio_mask` | `audio_masks/` | Binary mask for audio inpainting |
|
| 162 |
+
|
| 163 |
+
> [!NOTE]
|
| 164 |
+
> **Legacy column names:** `media_path` and `ref_media_path` are accepted as aliases for `video` and `reference_video` respectively. Existing datasets using these names will continue to work without modification.
|
| 165 |
+
|
| 166 |
+
**JSON format example:**
|
| 167 |
+
|
| 168 |
+
```json
|
| 169 |
+
[
|
| 170 |
+
{
|
| 171 |
+
"caption": "A cat playing with a ball of yarn",
|
| 172 |
+
"video": "videos/cat_playing.mp4"
|
| 173 |
+
},
|
| 174 |
+
{
|
| 175 |
+
"caption": "A dog running in the park",
|
| 176 |
+
"video": "videos/dog_running.mp4"
|
| 177 |
+
}
|
| 178 |
+
]
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
**JSONL format example:**
|
| 182 |
+
|
| 183 |
+
```jsonl
|
| 184 |
+
{"caption": "A cat playing with a ball of yarn", "video": "videos/cat_playing.mp4"}
|
| 185 |
+
{"caption": "A dog running in the park", "video": "videos/dog_running.mp4"}
|
| 186 |
+
```
|
| 187 |
+
|
| 188 |
+
**CSV format example:**
|
| 189 |
+
|
| 190 |
+
```csv
|
| 191 |
+
caption,video
|
| 192 |
+
"A cat playing with a ball of yarn","videos/cat_playing.mp4"
|
| 193 |
+
"A dog running in the park","videos/dog_running.mp4"
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
**Additional dataset format examples:**
|
| 197 |
+
|
| 198 |
+
Audio-only dataset:
|
| 199 |
+
```json
|
| 200 |
+
{"audio": "song.wav", "caption": "piano melody"}
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
V2V IC-LoRA with reference video:
|
| 204 |
+
```json
|
| 205 |
+
{"video": "clip.mp4", "reference_video": "depth.mp4", "caption": "depth to video"}
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
A2A IC-LoRA with reference audio:
|
| 209 |
+
```json
|
| 210 |
+
{"video": "clip.mp4", "reference_audio": "ref.wav", "caption": "match this style"}
|
| 211 |
+
```
|
| 212 |
+
This form auto-extracts the target audio from `clip.mp4`. For pure audio datasets, use `audio` plus
|
| 213 |
+
`reference_audio` columns and preprocess with `--audio-durations`.
|
| 214 |
+
|
| 215 |
+
Video inpainting with mask:
|
| 216 |
+
```json
|
| 217 |
+
{"video": "clip.mp4", "video_mask": "mask.mp4", "caption": "fill the sky"}
|
| 218 |
+
```
|
| 219 |
+
|
| 220 |
+
### 📐 Resolution Buckets
|
| 221 |
+
|
| 222 |
+
Videos are organized into "buckets" of specific dimensions (width × height × frames).
|
| 223 |
+
Each video is assigned to the nearest matching bucket.
|
| 224 |
+
You can preprocess with one or multiple resolution buckets.
|
| 225 |
+
When training with multiple resolution buckets, you must use a batch size of 1.
|
| 226 |
+
|
| 227 |
+
The dimensions of each bucket must follow these constraints due to LTX-2's VAE architecture:
|
| 228 |
+
|
| 229 |
+
- **Spatial dimensions** (width and height) must be multiples of 32
|
| 230 |
+
- **Number of frames** must satisfy `frames % 8 == 1` (e.g., 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, 97, 121, etc.)
|
| 231 |
+
|
| 232 |
+
**Guidelines for choosing training resolution:**
|
| 233 |
+
|
| 234 |
+
- For high-quality, detailed videos: use larger spatial dimensions (e.g. 768x448) with fewer frames (e.g. 89)
|
| 235 |
+
- For longer, motion-focused videos: use smaller spatial dimensions (512×512) with more frames (121)
|
| 236 |
+
- Memory usage increases with both spatial and temporal dimensions
|
| 237 |
+
|
| 238 |
+
**Example usage:**
|
| 239 |
+
|
| 240 |
+
```bash
|
| 241 |
+
uv run python scripts/process_dataset.py dataset.json \
|
| 242 |
+
--resolution-buckets "960x544x49" \
|
| 243 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 244 |
+
--text-encoder-path /path/to/gemma-model
|
| 245 |
+
```
|
| 246 |
+
|
| 247 |
+
Multiple buckets are supported by separating entries with `;`:
|
| 248 |
+
|
| 249 |
+
```bash
|
| 250 |
+
uv run python scripts/process_dataset.py dataset.json \
|
| 251 |
+
--resolution-buckets "960x544x49;512x512x49" \
|
| 252 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 253 |
+
--text-encoder-path /path/to/gemma-model
|
| 254 |
+
```
|
| 255 |
+
|
| 256 |
+
**Video processing workflow:**
|
| 257 |
+
|
| 258 |
+
1. Videos are **resized** maintaining aspect ratio until either width or height matches the target
|
| 259 |
+
2. The larger dimension is **center cropped** to match the bucket's dimensions
|
| 260 |
+
3. Only the **first X frames are taken** to match the bucket's frame count, remaining frames are ignored
|
| 261 |
+
|
| 262 |
+
> [!NOTE]
|
| 263 |
+
> The sequence length processed by the transformer model can be calculated as:
|
| 264 |
+
>
|
| 265 |
+
> ```
|
| 266 |
+
> sequence_length = (H/32) * (W/32) * ((F-1)/8 + 1)
|
| 267 |
+
> ```
|
| 268 |
+
>
|
| 269 |
+
> Where:
|
| 270 |
+
>
|
| 271 |
+
> - H = Height of video
|
| 272 |
+
> - W = Width of video
|
| 273 |
+
> - F = Number of frames
|
| 274 |
+
> - 32 = VAE's spatial downsampling factor
|
| 275 |
+
> - 8 = VAE's temporal downsampling factor
|
| 276 |
+
>
|
| 277 |
+
> For example, a 768×448×89 video would have sequence length:
|
| 278 |
+
>
|
| 279 |
+
> ```
|
| 280 |
+
> (768/32) * (448/32) * ((89-1)/8 + 1) = 24 * 14 * 12 = 4,032
|
| 281 |
+
> ```
|
| 282 |
+
>
|
| 283 |
+
> Keep this in mind when choosing video dimensions, as longer sequences require more GPU memory.
|
| 284 |
+
|
| 285 |
+
> [!WARNING]
|
| 286 |
+
> When training with multiple resolution buckets, you must use a batch size of 1
|
| 287 |
+
> (i.e., set `optimization.batch_size: 1` in your training config).
|
| 288 |
+
|
| 289 |
+
### 📁 Output Structure
|
| 290 |
+
|
| 291 |
+
The preprocessed data is saved in a `.precomputed` directory:
|
| 292 |
+
|
| 293 |
+
```
|
| 294 |
+
dataset/
|
| 295 |
+
└── .precomputed/
|
| 296 |
+
├── latents/ # Video latents
|
| 297 |
+
├── conditions/ # Text embeddings
|
| 298 |
+
├── audio_latents/ # Audio latents (auto-extracted or explicit)
|
| 299 |
+
├── reference_latents/ # Reference video latents (IC-LoRA)
|
| 300 |
+
├── reference_audio_latents/ # Reference audio latents (audio IC-LoRA)
|
| 301 |
+
├── video_masks/ # Video masks (inpainting)
|
| 302 |
+
└── audio_masks/ # Audio masks (audio inpainting)
|
| 303 |
+
```
|
| 304 |
+
|
| 305 |
+
Set `data.preprocessed_data_root` in your training config to this `.precomputed` directory — the parent directory that
|
| 306 |
+
contains `latents/`, `conditions/`, and any mode-specific audio/reference/mask directories.
|
| 307 |
+
|
| 308 |
+
## 🔊 Audio-Only Dataset Preprocessing
|
| 309 |
+
|
| 310 |
+
For datasets containing only audio files (no `video` column), use `--audio-durations` to specify duration buckets:
|
| 311 |
+
|
| 312 |
+
```bash
|
| 313 |
+
uv run python scripts/process_dataset.py dataset.json \
|
| 314 |
+
--audio-durations "2.0;4.0;8.0" \
|
| 315 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 316 |
+
--text-encoder-path /path/to/gemma-model
|
| 317 |
+
```
|
| 318 |
+
|
| 319 |
+
The `--audio-durations` flag provides duration buckets (in seconds) for audio-only datasets. Since there is no video column to derive timing from, explicit duration buckets are required.
|
| 320 |
+
|
| 321 |
+
## 🪄 IC-LoRA Reference Video Preprocessing
|
| 322 |
+
|
| 323 |
+
For IC-LoRA training, you need to preprocess datasets that include reference videos.
|
| 324 |
+
Reference videos provide the conditioning input while target videos represent the desired transformed output.
|
| 325 |
+
|
| 326 |
+
### Dataset Format with Reference Videos
|
| 327 |
+
|
| 328 |
+
The `reference_video` column is automatically detected by convention — no extra CLI flags are needed.
|
| 329 |
+
|
| 330 |
+
**JSON format:**
|
| 331 |
+
|
| 332 |
+
```json
|
| 333 |
+
[
|
| 334 |
+
{
|
| 335 |
+
"caption": "A cat playing with a ball of yarn",
|
| 336 |
+
"video": "videos/cat_playing.mp4",
|
| 337 |
+
"reference_video": "references/cat_playing_depth.mp4"
|
| 338 |
+
}
|
| 339 |
+
]
|
| 340 |
+
```
|
| 341 |
+
|
| 342 |
+
**JSONL format:**
|
| 343 |
+
|
| 344 |
+
```jsonl
|
| 345 |
+
{"caption": "A cat playing with a ball of yarn", "video": "videos/cat_playing.mp4", "reference_video": "references/cat_playing_depth.mp4"}
|
| 346 |
+
{"caption": "A dog running in the park", "video": "videos/dog_running.mp4", "reference_video": "references/dog_running_depth.mp4"}
|
| 347 |
+
```
|
| 348 |
+
|
| 349 |
+
### Preprocessing with Reference Videos
|
| 350 |
+
|
| 351 |
+
Convention-based detection means you just need the `reference_video` column in your dataset, and `process_dataset.py` will automatically detect and process it. No `--reference-column` flag is needed:
|
| 352 |
+
|
| 353 |
+
```bash
|
| 354 |
+
uv run python scripts/process_dataset.py dataset.json \
|
| 355 |
+
--resolution-buckets "960x544x49" \
|
| 356 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 357 |
+
--text-encoder-path /path/to/gemma-model \
|
| 358 |
+
--reference-downscale-factor 2 \
|
| 359 |
+
--reference-temporal-scale-factor 1
|
| 360 |
+
```
|
| 361 |
+
|
| 362 |
+
This will create an additional `reference_latents/` directory containing the preprocessed reference video latents.
|
| 363 |
+
Use `--reference-downscale-factor` for spatial subsampling and `--reference-temporal-scale-factor` for temporal
|
| 364 |
+
subsampling. Validation reference conditions should use matching `downscale_factor` and `temporal_scale_factor` values.
|
| 365 |
+
|
| 366 |
+
> [!NOTE]
|
| 367 |
+
> **Legacy column names:** If your dataset uses `ref_media_path`, it is accepted as an alias for `reference_video`.
|
| 368 |
+
|
| 369 |
+
### Generating Reference Videos
|
| 370 |
+
|
| 371 |
+
**Dataset Requirements for IC-LoRA:**
|
| 372 |
+
|
| 373 |
+
- Your dataset must contain paired videos where each target video has a corresponding reference video
|
| 374 |
+
- Reference and target videos should cover the same content. Reference videos can optionally be lower spatial
|
| 375 |
+
resolution or temporally subsampled (see Scaled Reference Conditioning in [Training Modes](training-modes.md)).
|
| 376 |
+
- Both reference and target videos should be preprocessed together using the same target resolution buckets, plus any
|
| 377 |
+
reference scale factors you choose.
|
| 378 |
+
|
| 379 |
+
We provide an example script, `[scripts/compute_reference.py](../scripts/compute_reference.py)`, to generate reference
|
| 380 |
+
videos for a given dataset. The default implementation generates Canny edge reference videos.
|
| 381 |
+
|
| 382 |
+
```bash
|
| 383 |
+
uv run python scripts/compute_reference.py scenes_output_dir/ \
|
| 384 |
+
--output scenes_output_dir/dataset.json
|
| 385 |
+
```
|
| 386 |
+
|
| 387 |
+
The script accepts a JSON file as the dataset configuration and updates it in-place by adding the filenames of the generated reference videos.
|
| 388 |
+
|
| 389 |
+
> [!NOTE]
|
| 390 |
+
> `compute_reference.py` writes generated references to the `reference_video` column, which `process_dataset.py`
|
| 391 |
+
> detects automatically. The legacy `ref_media_path` column is also accepted.
|
| 392 |
+
|
| 393 |
+
If you want to generate a different type of condition (depth maps, pose skeletons, etc.), modify or replace the `compute_reference()` function within this script.
|
| 394 |
+
|
| 395 |
+
### Example Dataset
|
| 396 |
+
|
| 397 |
+
For reference, see our **[Canny Control Dataset](https://huggingface.co/datasets/Lightricks/Canny-Control-Dataset)** which demonstrates proper IC-LoRA dataset structure with paired videos and Canny edge maps.
|
| 398 |
+
|
| 399 |
+
## 🎭 Mask Preprocessing for Inpainting
|
| 400 |
+
|
| 401 |
+
For inpainting training with the `mask` condition type, provide `video_mask` or `audio_mask` columns in your dataset
|
| 402 |
+
metadata. These columns point to mask media files (for example a mask image/video for video inpainting, or a waveform or
|
| 403 |
+
`.pt` tensor for audio inpainting). `process_dataset.py` downsamples and thresholds them into per-sample `.pt` tensors
|
| 404 |
+
under `video_masks/` or `audio_masks/`.
|
| 405 |
+
|
| 406 |
+
### Processed Video Mask Format
|
| 407 |
+
|
| 408 |
+
If you create masks manually instead of using `process_dataset.py`, save them as `.pt` files with the key `"mask"`
|
| 409 |
+
containing a tensor of shape `[F, H, W]` where:
|
| 410 |
+
|
| 411 |
+
- `F` = number of latent frames (temporal dimension)
|
| 412 |
+
- `H` = latent height (pixel height / 32)
|
| 413 |
+
- `W` = latent width (pixel width / 32)
|
| 414 |
+
- Values are thresholded at `0.5`: values `> 0.5` are conditioning tokens (clean, excluded from loss),
|
| 415 |
+
and values `<= 0.5` are generated tokens (noised, contributes to loss).
|
| 416 |
+
|
| 417 |
+
### Audio Mask Format
|
| 418 |
+
|
| 419 |
+
Audio masks follow the same thresholding pattern as video masks but with shape `[T]` (temporal dimension only), where `T` is the number of audio latent frames. They are stored in `audio_masks/`.
|
| 420 |
+
|
| 421 |
+
### Directory Structure
|
| 422 |
+
|
| 423 |
+
Place masks in a directory within your preprocessed data root:
|
| 424 |
+
|
| 425 |
+
```
|
| 426 |
+
preprocessed_data_root/
|
| 427 |
+
├── latents/ # Video latents
|
| 428 |
+
├── conditions/ # Text embeddings
|
| 429 |
+
├── video_masks/ # Video masks (one .pt per sample, matching latent filenames)
|
| 430 |
+
└── audio_masks/ # Audio masks (one .pt per sample, matching latent filenames)
|
| 431 |
+
```
|
| 432 |
+
|
| 433 |
+
Then reference the mask directory in your training config:
|
| 434 |
+
|
| 435 |
+
```yaml
|
| 436 |
+
training_strategy:
|
| 437 |
+
name: "flexible"
|
| 438 |
+
video:
|
| 439 |
+
is_generated: true
|
| 440 |
+
latents_dir: "latents"
|
| 441 |
+
conditions:
|
| 442 |
+
- type: mask
|
| 443 |
+
mask_dir: "video_masks"
|
| 444 |
+
```
|
| 445 |
+
|
| 446 |
+
## 🎯 LoRA Trigger Words
|
| 447 |
+
|
| 448 |
+
When training a LoRA, you can specify a trigger token that will be prepended to all captions:
|
| 449 |
+
|
| 450 |
+
```bash
|
| 451 |
+
uv run python scripts/process_dataset.py dataset.json \
|
| 452 |
+
--resolution-buckets "960x544x49" \
|
| 453 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 454 |
+
--text-encoder-path /path/to/gemma-model \
|
| 455 |
+
--lora-trigger "MYTRIGGER"
|
| 456 |
+
```
|
| 457 |
+
|
| 458 |
+
This acts as a trigger word that activates the LoRA during inference when you include the same token in your prompts.
|
| 459 |
+
|
| 460 |
+
> [!NOTE]
|
| 461 |
+
> There is no need to manually insert the trigger word into your dataset JSON/JSONL/CSV file.
|
| 462 |
+
> The trigger word specified with `--lora-trigger` is automatically prepended to each caption during preprocessing.
|
| 463 |
+
|
| 464 |
+
## 🔍 Decoding Videos for Verification
|
| 465 |
+
|
| 466 |
+
If you add the `--decode` flag, the script will VAE-decode the precomputed video latents and save the resulting videos
|
| 467 |
+
in `.precomputed/decoded_videos`. Reference video latents are decoded to `.precomputed/decoded_reference_videos` when
|
| 468 |
+
present. To inspect audio latents, run `scripts/decode_latents.py` with `--with-audio`.
|
| 469 |
+
|
| 470 |
+
```bash
|
| 471 |
+
uv run python scripts/process_dataset.py dataset.json \
|
| 472 |
+
--resolution-buckets "960x544x49" \
|
| 473 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 474 |
+
--text-encoder-path /path/to/gemma-model \
|
| 475 |
+
--decode
|
| 476 |
+
```
|
| 477 |
+
|
| 478 |
+
For single-frame images, the decoded latents will be saved as PNG files rather than MP4 videos.
|
| 479 |
+
|
| 480 |
+
## 🚀 Next Steps
|
| 481 |
+
|
| 482 |
+
Once your dataset is preprocessed, you can proceed to:
|
| 483 |
+
|
| 484 |
+
- Configure your training parameters in [Configuration Reference](configuration-reference.md)
|
| 485 |
+
- Choose your training approach in [Training Modes](training-modes.md)
|
| 486 |
+
- Start training with the [Training Guide](training-guide.md)
|
| 487 |
+
|
| 488 |
+
> [!TIP]
|
| 489 |
+
> The `flexible` strategy supports masks for inpainting (`mask` condition type) and spatial crop regions for outpainting (`spatial_crop` condition type) out of the box. For other custom preprocessing needs, see [Custom Training Strategies](custom-training-strategies.md).
|
packages/ltx-trainer/docs/quick-start.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Quick Start Guide
|
| 2 |
+
|
| 3 |
+
Get up and running with LTX-2 training in just a few steps!
|
| 4 |
+
|
| 5 |
+
## 📋 Prerequisites
|
| 6 |
+
|
| 7 |
+
Before you begin, ensure you have:
|
| 8 |
+
|
| 9 |
+
1. **LTX-2 Model Checkpoint** - A local `.safetensors` file containing the LTX-2 model weights.
|
| 10 |
+
Download `ltx-2.3-22b-dev.safetensors` from: [HuggingFace Hub](https://huggingface.co/Lightricks/LTX-2.3)
|
| 11 |
+
The trainer supports LTX-2 and LTX-2.3 checkpoints through the same configuration API; version-specific components
|
| 12 |
+
are detected from the checkpoint.
|
| 13 |
+
2. **Gemma Text Encoder** - A local directory containing the Gemma model (required for LTX-2).
|
| 14 |
+
Download from: [HuggingFace Hub](https://huggingface.co/google/gemma-3-12b-it-qat-q4_0-unquantized/)
|
| 15 |
+
3. **Linux with CUDA** - The trainer requires `triton` which is Linux-only; CUDA 13+ is recommended
|
| 16 |
+
4. **GPU with sufficient VRAM** - 80GB recommended for the standard config. For GPUs with 32GB VRAM (e.g., RTX 5090),
|
| 17 |
+
use the [low VRAM config](../configs/t2v_lora_low_vram.yaml) which enables INT8 quantization and other
|
| 18 |
+
memory optimizations
|
| 19 |
+
|
| 20 |
+
## ⚡ Installation
|
| 21 |
+
|
| 22 |
+
First, install [uv](https://docs.astral.sh/uv/getting-started/installation/) if you haven't already.
|
| 23 |
+
Then clone the repository and install the dependencies:
|
| 24 |
+
|
| 25 |
+
```bash
|
| 26 |
+
git clone https://github.com/Lightricks/LTX-2
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
The `ltx-trainer` package is part of the `LTX-2` monorepo. Install the dependencies from the repository root,
|
| 30 |
+
then navigate to the trainer package:
|
| 31 |
+
|
| 32 |
+
```bash
|
| 33 |
+
# From the repository root
|
| 34 |
+
uv sync
|
| 35 |
+
cd packages/ltx-trainer
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
> [!NOTE]
|
| 39 |
+
> The trainer depends on [`ltx-core`](../../ltx-core/) and [`ltx-pipelines`](../../ltx-pipelines/)
|
| 40 |
+
> packages which are automatically installed from the monorepo.
|
| 41 |
+
|
| 42 |
+
## 🏋 Training Workflow
|
| 43 |
+
|
| 44 |
+
If you are using an agent-enabled environment with repository skills, you can ask for the
|
| 45 |
+
[`train-model`](../../../.claude/skills/train-model/SKILL.md) skill to run this workflow with you.
|
| 46 |
+
It creates a run workspace, confirms the training mode, prepares data, preprocesses latents,
|
| 47 |
+
launches training, and monitors the run while stopping for approval before expensive steps.
|
| 48 |
+
|
| 49 |
+
### 1. Choose a Training Mode
|
| 50 |
+
|
| 51 |
+
Start with [`t2v_lora.yaml`](../configs/t2v_lora.yaml) for a first run with videos and captions. For modes such as
|
| 52 |
+
IC-LoRA, inpainting, or outpainting, check [Training Modes](training-modes.md) first because your metadata needs extra
|
| 53 |
+
columns such as `reference_video`, `video_mask`, or `audio_mask` before preprocessing.
|
| 54 |
+
|
| 55 |
+
### 2. Prepare Your Dataset
|
| 56 |
+
|
| 57 |
+
Organize your videos and captions, then preprocess them:
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
# Split long videos into scenes (optional)
|
| 61 |
+
uv run python scripts/split_scenes.py input.mp4 scenes_output_dir/ --filter-shorter-than 5s
|
| 62 |
+
|
| 63 |
+
# Generate captions for videos (optional)
|
| 64 |
+
uv run python scripts/caption_videos.py scenes_output_dir/ --output dataset.json
|
| 65 |
+
|
| 66 |
+
# Preprocess the dataset (compute latents and embeddings)
|
| 67 |
+
uv run python scripts/process_dataset.py dataset.json \
|
| 68 |
+
--resolution-buckets "960x544x49" \
|
| 69 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 70 |
+
--text-encoder-path /path/to/gemma-model
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
By default, preprocessing writes to `.precomputed/`. Use that directory as `data.preprocessed_data_root`
|
| 74 |
+
in your training config.
|
| 75 |
+
|
| 76 |
+
See [Dataset Preparation](dataset-preparation.md) for detailed instructions.
|
| 77 |
+
|
| 78 |
+
### 3. Configure Training
|
| 79 |
+
|
| 80 |
+
Create or modify a configuration YAML file. Start with one of the example configs:
|
| 81 |
+
|
| 82 |
+
- [`configs/t2v_lora.yaml`](../configs/t2v_lora.yaml) - Text-to-video LoRA
|
| 83 |
+
- [`configs/t2v_lora_low_vram.yaml`](../configs/t2v_lora_low_vram.yaml) - Same as above, tuned for ~32GB VRAM (INT8 quantization and memory optimizations)
|
| 84 |
+
- [`configs/v2v_ic_lora.yaml`](../configs/v2v_ic_lora.yaml) - IC-LoRA video-to-video
|
| 85 |
+
|
| 86 |
+
Key settings to update:
|
| 87 |
+
|
| 88 |
+
```yaml
|
| 89 |
+
model:
|
| 90 |
+
model_path: "/path/to/ltx-2-model.safetensors"
|
| 91 |
+
text_encoder_path: "/path/to/gemma-model"
|
| 92 |
+
|
| 93 |
+
data:
|
| 94 |
+
preprocessed_data_root: "/path/to/preprocessed/data"
|
| 95 |
+
|
| 96 |
+
output_dir: "outputs/my_training_run"
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
See [Configuration Reference](configuration-reference.md) for all available options.
|
| 100 |
+
|
| 101 |
+
### 4. Start Training
|
| 102 |
+
|
| 103 |
+
```bash
|
| 104 |
+
uv run python scripts/train.py configs/t2v_lora.yaml
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
For multi-GPU training:
|
| 108 |
+
|
| 109 |
+
```bash
|
| 110 |
+
uv run accelerate launch scripts/train.py configs/t2v_lora.yaml
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
See [Training Guide](training-guide.md) for distributed training and advanced options.
|
| 114 |
+
|
| 115 |
+
## 🎯 Training Modes
|
| 116 |
+
|
| 117 |
+
> [!TIP]
|
| 118 |
+
> **First time?** Start with [`t2v_lora.yaml`](../configs/t2v_lora.yaml) — it's the simplest mode
|
| 119 |
+
> and only requires videos with captions. You can explore other modes once you've confirmed your
|
| 120 |
+
> setup works.
|
| 121 |
+
|
| 122 |
+
The trainer supports several training modes:
|
| 123 |
+
|
| 124 |
+
| Mode | Description | Example Config |
|
| 125 |
+
|-----------------------|--------------------------------------------|-------------------------------------------------------------------|
|
| 126 |
+
| **Text-to-Video** | Generate video+audio from text prompts | [`t2v_lora.yaml`](../configs/t2v_lora.yaml) |
|
| 127 |
+
| **Image-to-Video** | Animate from a starting image | [`i2v_lora.yaml`](../configs/i2v_lora.yaml) |
|
| 128 |
+
| **Video Extension** | Extend videos temporally (forward/backward)| [`video_extend_lora.yaml`](../configs/video_extend_lora.yaml), [`video_suffix_lora.yaml`](../configs/video_suffix_lora.yaml) |
|
| 129 |
+
| **IC-LoRA (V2V)** | Video-to-video transformations | [`v2v_ic_lora.yaml`](../configs/v2v_ic_lora.yaml) |
|
| 130 |
+
| **Audio-to-Video** | Generate video conditioned on audio | [`a2v_lora.yaml`](../configs/a2v_lora.yaml) |
|
| 131 |
+
| **Video-to-Audio** | Generate audio/foley from video | [`v2a_lora.yaml`](../configs/v2a_lora.yaml) |
|
| 132 |
+
| **Video Inpainting** | Fill in masked regions of video | [`video_inpainting_lora.yaml`](../configs/video_inpainting_lora.yaml) |
|
| 133 |
+
| **Video Outpainting** | Extend video spatially | [`video_outpainting_lora.yaml`](../configs/video_outpainting_lora.yaml) |
|
| 134 |
+
| **Text-to-Audio** | Generate audio from text prompts | [`t2a_lora.yaml`](../configs/t2a_lora.yaml) |
|
| 135 |
+
| **Audio Extension** | Extend audio temporally | [`audio_extend_lora.yaml`](../configs/audio_extend_lora.yaml), [`audio_suffix_lora.yaml`](../configs/audio_suffix_lora.yaml) |
|
| 136 |
+
| **Audio Inpainting** | Fill in masked regions of audio | [`audio_inpainting_lora.yaml`](../configs/audio_inpainting_lora.yaml) |
|
| 137 |
+
| **IC-LoRA (A2A)** | Audio-to-audio transformations | [`a2a_ic_lora.yaml`](../configs/a2a_ic_lora.yaml) |
|
| 138 |
+
| **AV2AV IC-LoRA** | Audio+video IC-LoRA transformations | [`av2av_ic_lora.yaml`](../configs/av2av_ic_lora.yaml) |
|
| 139 |
+
| **Full Fine-tuning** | Full model training (any mode above) | Set `model.training_mode: "full"` |
|
| 140 |
+
|
| 141 |
+
See [Training Modes](training-modes.md) for detailed explanations of each mode.
|
| 142 |
+
|
| 143 |
+
## Next Steps
|
| 144 |
+
|
| 145 |
+
Once you've completed your first training run, you can:
|
| 146 |
+
|
| 147 |
+
- **Use your trained LoRA for inference** - The [`ltx-pipelines`](../../ltx-pipelines/) package provides
|
| 148 |
+
production-ready inference
|
| 149 |
+
pipelines for various use cases (T2V, I2V, IC-LoRA, etc.). See the package documentation for details.
|
| 150 |
+
- Learn more about [Dataset Preparation](dataset-preparation.md) for advanced preprocessing
|
| 151 |
+
- Explore different [Training Modes](training-modes.md)
|
| 152 |
+
- Dive deeper into [Training Configuration](configuration-reference.md)
|
| 153 |
+
- Understand the model architecture in [LTX-Core Documentation](../../ltx-core/README.md)
|
| 154 |
+
|
| 155 |
+
## Need Help?
|
| 156 |
+
|
| 157 |
+
If you run into issues at any step, see the [Troubleshooting Guide](troubleshooting.md) for solutions to common
|
| 158 |
+
problems.
|
| 159 |
+
|
| 160 |
+
Join our [Discord community](https://discord.gg/ltxplatform) for real-time help and discussion!
|
packages/ltx-trainer/docs/training-guide.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Training Guide
|
| 2 |
+
|
| 3 |
+
This guide covers how to run training jobs, from basic single-GPU training to advanced distributed setups and automatic
|
| 4 |
+
model uploads.
|
| 5 |
+
|
| 6 |
+
## ⚡ Basic Training (Single GPU)
|
| 7 |
+
|
| 8 |
+
After preprocessing your dataset and preparing a configuration file, you can start training using the trainer script:
|
| 9 |
+
|
| 10 |
+
```bash
|
| 11 |
+
uv run python scripts/train.py configs/t2v_lora.yaml
|
| 12 |
+
```
|
| 13 |
+
|
| 14 |
+
The trainer will:
|
| 15 |
+
|
| 16 |
+
1. **Load your configuration** and validate all parameters
|
| 17 |
+
2. **Initialize models** and apply optimizations
|
| 18 |
+
3. **Run the training loop** with progress tracking
|
| 19 |
+
4. **Generate validation videos** (if configured)
|
| 20 |
+
5. **Save the trained weights** in your output directory
|
| 21 |
+
|
| 22 |
+
### Agent-Assisted Training
|
| 23 |
+
|
| 24 |
+
If your environment supports repository skills, the
|
| 25 |
+
[`train-model`](../../../.claude/skills/train-model/SKILL.md) skill provides an end-to-end
|
| 26 |
+
orchestrator for this package. It asks what you want the model to learn, maps that intent to
|
| 27 |
+
one of the documented [training modes](training-modes.md), probes your filesystem and GPU,
|
| 28 |
+
prepares/preprocesses the dataset, writes a run-specific config, launches training, and
|
| 29 |
+
monitors the job. It uses the trainer docs as its source of truth and stops for approval before
|
| 30 |
+
captioning, preprocessing, or starting expensive training work.
|
| 31 |
+
|
| 32 |
+
### Output Files
|
| 33 |
+
|
| 34 |
+
**For LoRA training:**
|
| 35 |
+
|
| 36 |
+
- `checkpoints/lora_weights_step_00000.safetensors` - LoRA checkpoint weights, with the current step in the filename
|
| 37 |
+
- `training_config.yaml` - Copy of training configuration
|
| 38 |
+
- `samples/` - Generated validation samples (if enabled)
|
| 39 |
+
- `checkpoints/training_state_step_00000.pt` - Optional resume state, depending on `checkpoints.save_training_state`
|
| 40 |
+
|
| 41 |
+
**For full model fine-tuning:**
|
| 42 |
+
|
| 43 |
+
- `checkpoints/model_weights_step_00000.safetensors` - Full model checkpoint weights, with the current step in the filename
|
| 44 |
+
- `training_config.yaml` - Copy of training configuration
|
| 45 |
+
- `samples/` - Generated validation samples (if enabled)
|
| 46 |
+
- `checkpoints/training_state_step_00000.pt` - Optional resume state, depending on `checkpoints.save_training_state`
|
| 47 |
+
|
| 48 |
+
## 🖥️ Distributed / Multi-GPU Training
|
| 49 |
+
|
| 50 |
+
We use Hugging Face 🤗 [Accelerate](https://huggingface.co/docs/accelerate/index) for multi-GPU DDP and FSDP.
|
| 51 |
+
|
| 52 |
+
### Configure Accelerate
|
| 53 |
+
|
| 54 |
+
Run the interactive wizard once to set up your environment (DDP / FSDP, GPU count, etc.):
|
| 55 |
+
|
| 56 |
+
```bash
|
| 57 |
+
uv run accelerate config
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
This stores your preferences in `~/.cache/huggingface/accelerate/default_config.yaml`.
|
| 61 |
+
|
| 62 |
+
### Use the Provided Accelerate Configs (Recommended)
|
| 63 |
+
|
| 64 |
+
We include ready-to-use Accelerate config files in `configs/accelerate/`:
|
| 65 |
+
|
| 66 |
+
- [ddp.yaml](../configs/accelerate/ddp.yaml) — Standard DDP
|
| 67 |
+
- [ddp_compile.yaml](../configs/accelerate/ddp_compile.yaml) — DDP with `torch.compile` (Inductor)
|
| 68 |
+
- [fsdp.yaml](../configs/accelerate/fsdp.yaml) — Standard FSDP (auto-wraps `BasicAVTransformerBlock`)
|
| 69 |
+
- [fsdp_compile.yaml](../configs/accelerate/fsdp_compile.yaml) — FSDP with `torch.compile` (Inductor)
|
| 70 |
+
|
| 71 |
+
Launch with a specific config using `--config_file`:
|
| 72 |
+
|
| 73 |
+
```bash
|
| 74 |
+
# DDP (2 GPUs shown as example)
|
| 75 |
+
CUDA_VISIBLE_DEVICES=0,1 \
|
| 76 |
+
uv run accelerate launch --config_file configs/accelerate/ddp.yaml \
|
| 77 |
+
scripts/train.py configs/t2v_lora.yaml
|
| 78 |
+
|
| 79 |
+
# DDP + torch.compile
|
| 80 |
+
CUDA_VISIBLE_DEVICES=0,1 \
|
| 81 |
+
uv run accelerate launch --config_file configs/accelerate/ddp_compile.yaml \
|
| 82 |
+
scripts/train.py configs/t2v_lora.yaml
|
| 83 |
+
|
| 84 |
+
# FSDP (4 GPUs shown as example)
|
| 85 |
+
CUDA_VISIBLE_DEVICES=0,1,2,3 \
|
| 86 |
+
uv run accelerate launch --config_file configs/accelerate/fsdp.yaml \
|
| 87 |
+
scripts/train.py configs/t2v_lora.yaml
|
| 88 |
+
|
| 89 |
+
# FSDP + torch.compile
|
| 90 |
+
CUDA_VISIBLE_DEVICES=0,1,2,3 \
|
| 91 |
+
uv run accelerate launch --config_file configs/accelerate/fsdp_compile.yaml \
|
| 92 |
+
scripts/train.py configs/t2v_lora.yaml
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
**Notes:**
|
| 96 |
+
|
| 97 |
+
- The number of processes is taken from the Accelerate config (`num_processes`). Override with `--num_processes X` or
|
| 98 |
+
restrict GPUs with `CUDA_VISIBLE_DEVICES`.
|
| 99 |
+
- The compile variants enable `torch.compile` with the Inductor backend via Accelerate's `dynamo_config`.
|
| 100 |
+
- FSDP configs auto-wrap the transformer blocks (`fsdp_transformer_layer_cls_to_wrap: BasicAVTransformerBlock`).
|
| 101 |
+
|
| 102 |
+
### Launch with Your Default Accelerate Config
|
| 103 |
+
|
| 104 |
+
If you prefer to use your default Accelerate profile:
|
| 105 |
+
|
| 106 |
+
```bash
|
| 107 |
+
# Use settings from your default accelerate config
|
| 108 |
+
uv run accelerate launch scripts/train.py configs/t2v_lora.yaml
|
| 109 |
+
|
| 110 |
+
# Override number of processes on the fly (e.g., 2 GPUs)
|
| 111 |
+
uv run accelerate launch --num_processes 2 scripts/train.py configs/t2v_lora.yaml
|
| 112 |
+
|
| 113 |
+
# Select specific GPUs
|
| 114 |
+
CUDA_VISIBLE_DEVICES=0,1 uv run accelerate launch scripts/train.py configs/t2v_lora.yaml
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
> [!TIP]
|
| 118 |
+
> You can disable the in-terminal progress bars with `--disable-progress-bars` flag in the trainer CLI if desired.
|
| 119 |
+
|
| 120 |
+
### Benefits of Distributed Training
|
| 121 |
+
|
| 122 |
+
- **Faster training**: Distribute workload across multiple GPUs
|
| 123 |
+
- **Larger effective batch sizes**: Combine gradients from multiple GPUs
|
| 124 |
+
- **Memory efficiency**: Each GPU handles a portion of the batch
|
| 125 |
+
|
| 126 |
+
> [!NOTE]
|
| 127 |
+
> Distributed training requires that all GPUs have sufficient memory for the model and batch size. The effective batch
|
| 128 |
+
> size becomes `batch_size × num_processes`.
|
| 129 |
+
|
| 130 |
+
## 🤗 Pushing Models to Hugging Face Hub
|
| 131 |
+
|
| 132 |
+
You can automatically push your trained models to the Hugging Face Hub by adding the following to your configuration:
|
| 133 |
+
|
| 134 |
+
```yaml
|
| 135 |
+
hub:
|
| 136 |
+
push_to_hub: true
|
| 137 |
+
hub_model_id: "your-username/your-model-name"
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
### Prerequisites
|
| 141 |
+
|
| 142 |
+
Before pushing, make sure you:
|
| 143 |
+
|
| 144 |
+
1. **Have a Hugging Face account** - Sign up at [huggingface.co](https://huggingface.co)
|
| 145 |
+
2. **Are logged in** via `huggingface-cli login` or have set the `HUGGING_FACE_HUB_TOKEN` environment variable
|
| 146 |
+
3. **Have write access** to the specified repository (it will be created if it doesn't exist)
|
| 147 |
+
|
| 148 |
+
### Login Options
|
| 149 |
+
|
| 150 |
+
**Option 1: Interactive login**
|
| 151 |
+
|
| 152 |
+
```bash
|
| 153 |
+
uv run huggingface-cli login
|
| 154 |
+
```
|
| 155 |
+
|
| 156 |
+
**Option 2: Environment variable**
|
| 157 |
+
|
| 158 |
+
```bash
|
| 159 |
+
export HUGGING_FACE_HUB_TOKEN="your_token_here"
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
### What Gets Uploaded
|
| 163 |
+
|
| 164 |
+
The trainer will automatically:
|
| 165 |
+
|
| 166 |
+
- **Create a model card** with training details and sample outputs
|
| 167 |
+
- **Upload model weights**
|
| 168 |
+
- **Push sample videos as GIFs** in the model card
|
| 169 |
+
- **Include training configuration and prompts**
|
| 170 |
+
|
| 171 |
+
## 📊 Weights & Biases Logging
|
| 172 |
+
|
| 173 |
+
Enable experiment tracking with W&B by adding to your configuration:
|
| 174 |
+
|
| 175 |
+
```yaml
|
| 176 |
+
wandb:
|
| 177 |
+
enabled: true
|
| 178 |
+
project: "ltx-2-trainer"
|
| 179 |
+
entity: null # Your W&B username or team
|
| 180 |
+
tags: [ "ltx2", "lora" ]
|
| 181 |
+
log_validation_videos: true
|
| 182 |
+
```
|
| 183 |
+
|
| 184 |
+
This will log:
|
| 185 |
+
|
| 186 |
+
- Training loss and learning rate
|
| 187 |
+
- Validation videos
|
| 188 |
+
- Model configuration
|
| 189 |
+
- Training progress
|
| 190 |
+
|
| 191 |
+
## 🚀 Next Steps
|
| 192 |
+
|
| 193 |
+
After training completes:
|
| 194 |
+
|
| 195 |
+
- **Run inference with your trained LoRA** - The [`ltx-pipelines`](../../ltx-pipelines/) package provides
|
| 196 |
+
production-ready inference
|
| 197 |
+
pipelines that support loading custom LoRAs. Available pipelines include text-to-video, image-to-video,
|
| 198 |
+
IC-LoRA video-to-video, and more. See the [`ltx-pipelines`](../../ltx-pipelines/) package for usage details.
|
| 199 |
+
- **Test your model** with validation prompts
|
| 200 |
+
- **Iterate and improve** based on validation results
|
| 201 |
+
- **Share your results** by pushing to Hugging Face Hub
|
| 202 |
+
|
| 203 |
+
## 💡 Tips for Successful Training
|
| 204 |
+
|
| 205 |
+
- **Start small**: Begin with a small dataset and a few hundred steps to verify everything works
|
| 206 |
+
- **Monitor validation**: Keep an eye on validation samples to catch overfitting
|
| 207 |
+
- **Adjust learning rate**: Lower learning rates often produce better results
|
| 208 |
+
- **Use gradient checkpointing**: Essential for training with limited GPU memory
|
| 209 |
+
- **Save checkpoints**: Regular checkpoints help recover from interruptions
|
| 210 |
+
|
| 211 |
+
## Need Help?
|
| 212 |
+
|
| 213 |
+
If you encounter issues during training, see the [Troubleshooting Guide](troubleshooting.md).
|
| 214 |
+
|
| 215 |
+
Join our [Discord community](https://discord.gg/ltxplatform) for real-time help!
|
packages/ltx-trainer/docs/training-modes.md
ADDED
|
@@ -0,0 +1,600 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Training Modes Guide
|
| 2 |
+
|
| 3 |
+
The trainer uses the **flexible** training strategy (`name: "flexible"`) — a unified conditioning framework that
|
| 4 |
+
supports all training modes through configuration. Every scenario is expressed by setting `is_generated` on each
|
| 5 |
+
modality and adding optional conditions, rather than choosing a separate strategy class.
|
| 6 |
+
|
| 7 |
+
## Key Concepts
|
| 8 |
+
|
| 9 |
+
Before diving into individual modes, here are the core ideas behind the flexible strategy:
|
| 10 |
+
|
| 11 |
+
- **`is_generated: true`** — the modality is denoised during training and contributes to the loss. This is the
|
| 12 |
+
modality the model learns to generate.
|
| 13 |
+
- **`is_generated: false`** — the modality is frozen (sigma=0, no noise, no loss). It passes through the transformer
|
| 14 |
+
clean and acts as cross-modal conditioning for the generated modality.
|
| 15 |
+
- **At least one modality must have `is_generated: true`.**
|
| 16 |
+
- **Conditions** are per-modality and can be composed (e.g., `reference` + `first_frame` together on the video
|
| 17 |
+
modality).
|
| 18 |
+
- Audio does **not** support `first_frame` or `spatial_crop` conditions — only `prefix`, `suffix`, `mask`,
|
| 19 |
+
and `reference`.
|
| 20 |
+
|
| 21 |
+
> [!TIP]
|
| 22 |
+
> If you are using an agent-enabled environment with repository skills and are unsure which mode to choose,
|
| 23 |
+
> ask for the [`train-model`](../../../.claude/skills/train-model/SKILL.md) skill. It maps your intent to one of
|
| 24 |
+
> these configs and walks through dataset preparation, preprocessing, launch, and monitoring.
|
| 25 |
+
|
| 26 |
+
## 📊 Quick Reference
|
| 27 |
+
|
| 28 |
+
| Mode | Video | Audio | Conditions | Config |
|
| 29 |
+
|-----------------------|-----------|-----------|---------------------|--------|
|
| 30 |
+
| **T2V** | Generated | Generated | — | [`t2v_lora`](../configs/t2v_lora.yaml) |
|
| 31 |
+
| **I2V** | Generated | Generated | `first_frame` | [`i2v_lora`](../configs/i2v_lora.yaml) |
|
| 32 |
+
| **Video Extension** | Generated | Generated | `prefix`/`suffix` | [`video_extend_lora`](../configs/video_extend_lora.yaml) |
|
| 33 |
+
| **V2V IC-LoRA** | Generated | — | `reference` | [`v2v_ic_lora`](../configs/v2v_ic_lora.yaml) |
|
| 34 |
+
| **A2V** | Generated | Frozen | — | [`a2v_lora`](../configs/a2v_lora.yaml) |
|
| 35 |
+
| **V2A (Foley)** | Frozen | Generated | — | [`v2a_lora`](../configs/v2a_lora.yaml) |
|
| 36 |
+
| **Video Inpainting** | Generated | — | `mask` | [`video_inpainting_lora`](../configs/video_inpainting_lora.yaml) |
|
| 37 |
+
| **Video Outpainting** | Generated | — | `spatial_crop` | [`video_outpainting_lora`](../configs/video_outpainting_lora.yaml) |
|
| 38 |
+
| **T2A** | — | Generated | — | [`t2a_lora`](../configs/t2a_lora.yaml) |
|
| 39 |
+
| **Audio Extension** | — | Generated | `prefix`/`suffix` | [`audio_extend_lora`](../configs/audio_extend_lora.yaml) |
|
| 40 |
+
| **Audio Inpainting** | — | Generated | `mask` | [`audio_inpainting_lora`](../configs/audio_inpainting_lora.yaml) |
|
| 41 |
+
| **A2A IC-LoRA** | — | Generated | `reference` | [`a2a_ic_lora`](../configs/a2a_ic_lora.yaml) |
|
| 42 |
+
| **AV2AV IC-LoRA** | Generated | Generated | `reference` (both) | [`av2av_ic_lora`](../configs/av2av_ic_lora.yaml) |
|
| 43 |
+
|
| 44 |
+
---
|
| 45 |
+
|
| 46 |
+
## 🎯 Text-to-Video (T2V)
|
| 47 |
+
|
| 48 |
+
Generate video and audio from text prompts. Both modalities are denoised with no additional conditions.
|
| 49 |
+
|
| 50 |
+
```yaml
|
| 51 |
+
training_strategy:
|
| 52 |
+
name: "flexible"
|
| 53 |
+
video:
|
| 54 |
+
is_generated: true
|
| 55 |
+
latents_dir: "latents"
|
| 56 |
+
audio:
|
| 57 |
+
is_generated: true
|
| 58 |
+
latents_dir: "audio_latents"
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
**Example config:** 📄 [t2v_lora.yaml](../configs/t2v_lora.yaml)
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
## 🖼️ Image-to-Video (I2V)
|
| 66 |
+
|
| 67 |
+
Generate video conditioned on a starting image. The first frame is provided as a clean conditioning signal — no noise,
|
| 68 |
+
timestep=0, excluded from loss. The `probability` parameter controls how often first-frame conditioning is applied;
|
| 69 |
+
remaining samples train in pure T2V mode.
|
| 70 |
+
|
| 71 |
+
```yaml
|
| 72 |
+
training_strategy:
|
| 73 |
+
name: "flexible"
|
| 74 |
+
video:
|
| 75 |
+
is_generated: true
|
| 76 |
+
latents_dir: "latents"
|
| 77 |
+
conditions:
|
| 78 |
+
- type: first_frame
|
| 79 |
+
probability: 0.5
|
| 80 |
+
audio:
|
| 81 |
+
is_generated: true
|
| 82 |
+
latents_dir: "audio_latents"
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
**Example config:** 📄 [i2v_lora.yaml](../configs/i2v_lora.yaml)
|
| 86 |
+
|
| 87 |
+
---
|
| 88 |
+
|
| 89 |
+
## ⏩ Video Extension
|
| 90 |
+
|
| 91 |
+
Extend a video forward (or backward) in time. Prefix or suffix conditioning provides a span of existing latent frames
|
| 92 |
+
as clean conditioning. The `temporal_boundary` sets the number of **latent frames** used as context (each latent frame
|
| 93 |
+
= 8 pixel frames due to temporal compression).
|
| 94 |
+
|
| 95 |
+
```yaml
|
| 96 |
+
training_strategy:
|
| 97 |
+
name: "flexible"
|
| 98 |
+
video:
|
| 99 |
+
is_generated: true
|
| 100 |
+
latents_dir: "latents"
|
| 101 |
+
conditions:
|
| 102 |
+
- type: prefix # or "suffix" for backward extension
|
| 103 |
+
temporal_boundary: 8 # 8 latent frames = 64 pixel frames
|
| 104 |
+
probability: 1.0
|
| 105 |
+
audio:
|
| 106 |
+
is_generated: true
|
| 107 |
+
latents_dir: "audio_latents"
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
> [!NOTE]
|
| 111 |
+
> The `prefix` and `suffix` conditions also work on the audio modality for audio extension.
|
| 112 |
+
> Set `temporal_boundary` on the audio modality's conditions list to condition on a prefix or suffix
|
| 113 |
+
> of the audio latents.
|
| 114 |
+
|
| 115 |
+
**Example configs:** 📄 [video_extend_lora.yaml](../configs/video_extend_lora.yaml) (forward), 📄 [video_suffix_lora.yaml](../configs/video_suffix_lora.yaml) (backward)
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
## 🔄 IC-LoRA / Video-to-Video (V2V)
|
| 120 |
+
|
| 121 |
+
In-Context LoRA learns transformations from paired videos. Pre-encoded reference latents are concatenated to the target
|
| 122 |
+
sequence — reference tokens participate in bidirectional self-attention but receive no noise and are excluded from loss.
|
| 123 |
+
This enables control adapters (depth, pose), style transfer, deblurring, colorization, and more.
|
| 124 |
+
|
| 125 |
+
```yaml
|
| 126 |
+
training_strategy:
|
| 127 |
+
name: "flexible"
|
| 128 |
+
video:
|
| 129 |
+
is_generated: true
|
| 130 |
+
latents_dir: "latents"
|
| 131 |
+
conditions:
|
| 132 |
+
- type: reference
|
| 133 |
+
latents_dir: "reference_latents"
|
| 134 |
+
probability: 1.0
|
| 135 |
+
- type: first_frame # optional — composable with reference
|
| 136 |
+
probability: 0.2
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
> [!NOTE]
|
| 140 |
+
> IC-LoRA is video-only by default (no audio modality block). Conditions can be composed — the example above also
|
| 141 |
+
> applies first-frame conditioning with 20% probability alongside the reference.
|
| 142 |
+
> Use [AV2AV IC-LoRA](#av2av-ic-lora) when both video and audio references should be trained jointly.
|
| 143 |
+
|
| 144 |
+
**Example config:** 📄 [v2v_ic_lora.yaml](../configs/v2v_ic_lora.yaml)
|
| 145 |
+
|
| 146 |
+
### Dataset Requirements
|
| 147 |
+
|
| 148 |
+
- **Paired videos** — each target video has a corresponding reference video
|
| 149 |
+
- **Same frame count** between reference and target
|
| 150 |
+
- Reference videos can optionally be at **lower spatial resolution** (see [Scaled Reference](#scaled-reference-conditioning) below)
|
| 151 |
+
- Both must be **preprocessed** before training
|
| 152 |
+
|
| 153 |
+
**Dataset structure:**
|
| 154 |
+
|
| 155 |
+
```
|
| 156 |
+
preprocessed_data_root/
|
| 157 |
+
├── latents/ # Target video latents
|
| 158 |
+
├── conditions/ # Text embeddings
|
| 159 |
+
└── reference_latents/ # Reference video latents (conditioning input)
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
### Generating Reference Videos
|
| 163 |
+
|
| 164 |
+
Use the `compute_reference.py` script to generate reference videos (e.g., Canny edge maps) for a dataset:
|
| 165 |
+
|
| 166 |
+
```bash
|
| 167 |
+
uv run python scripts/compute_reference.py scenes_output_dir/ \
|
| 168 |
+
--output scenes_output_dir/dataset.json
|
| 169 |
+
```
|
| 170 |
+
|
| 171 |
+
To compute a different condition (depth maps, pose skeletons, etc.), modify the `compute_reference()` function in the
|
| 172 |
+
script.
|
| 173 |
+
|
| 174 |
+
> [!NOTE]
|
| 175 |
+
> `compute_reference.py` writes generated references to the `reference_video` column, which
|
| 176 |
+
> `process_dataset.py` detects automatically. The legacy `ref_media_path` column is also accepted.
|
| 177 |
+
|
| 178 |
+
### Scaled Reference Conditioning
|
| 179 |
+
|
| 180 |
+
For more efficient training and inference, use **downscaled reference videos** while keeping targets at full
|
| 181 |
+
resolution. During training, the strategy infers the spatial and temporal scale factors from the preprocessed
|
| 182 |
+
reference and target latents and adjusts positional encodings accordingly. This reduces conditioning tokens, leading to:
|
| 183 |
+
|
| 184 |
+
- **Faster training** — shorter sequence lengths
|
| 185 |
+
- **Faster inference** — reduced memory usage
|
| 186 |
+
- **Same aspect ratio** maintained between reference and target
|
| 187 |
+
|
| 188 |
+
Preprocess with the `--reference-downscale-factor` option:
|
| 189 |
+
|
| 190 |
+
```bash
|
| 191 |
+
uv run python scripts/process_dataset.py dataset.json \
|
| 192 |
+
--resolution-buckets 768x768x25 \
|
| 193 |
+
--model-path /path/to/ltx2.safetensors \
|
| 194 |
+
--text-encoder-path /path/to/gemma \
|
| 195 |
+
--reference-downscale-factor 2
|
| 196 |
+
```
|
| 197 |
+
|
| 198 |
+
> [!NOTE]
|
| 199 |
+
> The `reference_video` column is auto-detected by convention — no `--reference-column` flag needed.
|
| 200 |
+
|
| 201 |
+
Validation encodes reference media on the fly, so set `downscale_factor` and `temporal_scale_factor`
|
| 202 |
+
on each `reference` validation condition to match the preprocessing factors:
|
| 203 |
+
|
| 204 |
+
```yaml
|
| 205 |
+
validation:
|
| 206 |
+
samples:
|
| 207 |
+
- prompt: "..."
|
| 208 |
+
conditions:
|
| 209 |
+
- type: reference
|
| 210 |
+
video: "/path/to/reference.mp4"
|
| 211 |
+
downscale_factor: 2
|
| 212 |
+
temporal_scale_factor: 1
|
| 213 |
+
include_in_output: true
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
> [!NOTE]
|
| 217 |
+
> The scale factor must be a positive integer, and all dimensions must be divisible by 32.
|
| 218 |
+
> Common values are 1 (no scaling), 2 (half resolution), or 4 (quarter resolution).
|
| 219 |
+
|
| 220 |
+
---
|
| 221 |
+
|
| 222 |
+
## 🔊 Audio-to-Video (A2V)
|
| 223 |
+
|
| 224 |
+
Generate video conditioned on frozen audio. Audio passes through the transformer clean (sigma=0) and influences video
|
| 225 |
+
via the built-in cross-modal attention. Only video is denoised.
|
| 226 |
+
|
| 227 |
+
```yaml
|
| 228 |
+
training_strategy:
|
| 229 |
+
name: "flexible"
|
| 230 |
+
video:
|
| 231 |
+
is_generated: true
|
| 232 |
+
latents_dir: "latents"
|
| 233 |
+
audio:
|
| 234 |
+
is_generated: false
|
| 235 |
+
latents_dir: "audio_latents"
|
| 236 |
+
```
|
| 237 |
+
|
| 238 |
+
**Example config:** 📄 [a2v_lora.yaml](../configs/a2v_lora.yaml)
|
| 239 |
+
|
| 240 |
+
---
|
| 241 |
+
|
| 242 |
+
## 🎵 Video-to-Audio / Foley (V2A)
|
| 243 |
+
|
| 244 |
+
Generate audio (Foley) conditioned on frozen video. Video passes through the transformer clean (sigma=0) and
|
| 245 |
+
conditions audio via cross-modal attention. Only audio is denoised.
|
| 246 |
+
|
| 247 |
+
```yaml
|
| 248 |
+
training_strategy:
|
| 249 |
+
name: "flexible"
|
| 250 |
+
video:
|
| 251 |
+
is_generated: false
|
| 252 |
+
latents_dir: "latents"
|
| 253 |
+
audio:
|
| 254 |
+
is_generated: true
|
| 255 |
+
latents_dir: "audio_latents"
|
| 256 |
+
```
|
| 257 |
+
|
| 258 |
+
**Example config:** 📄 [v2a_lora.yaml](../configs/v2a_lora.yaml)
|
| 259 |
+
|
| 260 |
+
---
|
| 261 |
+
|
| 262 |
+
## 🎭 Video Inpainting
|
| 263 |
+
|
| 264 |
+
Fill in masked regions of a video. Per-sample masks loaded from disk define which tokens are conditioning and which
|
| 265 |
+
must be generated. Masks are thresholded at `0.5` to match validation/inference: tokens with `mask > 0.5` receive clean
|
| 266 |
+
latents and timestep=0 and are excluded from loss; tokens with `mask <= 0.5` are denoised normally and contribute to
|
| 267 |
+
loss.
|
| 268 |
+
|
| 269 |
+
```yaml
|
| 270 |
+
training_strategy:
|
| 271 |
+
name: "flexible"
|
| 272 |
+
video:
|
| 273 |
+
is_generated: true
|
| 274 |
+
latents_dir: "latents"
|
| 275 |
+
conditions:
|
| 276 |
+
- type: mask
|
| 277 |
+
mask_dir: "video_masks"
|
| 278 |
+
probability: 1.0
|
| 279 |
+
```
|
| 280 |
+
|
| 281 |
+
**Dataset structure:**
|
| 282 |
+
|
| 283 |
+
```
|
| 284 |
+
preprocessed_data_root/
|
| 285 |
+
├── latents/ # Video latents
|
| 286 |
+
├── conditions/ # Text embeddings
|
| 287 |
+
└── video_masks/ # Per-sample binary masks (1 → conditioning, 0 → generate)
|
| 288 |
+
```
|
| 289 |
+
|
| 290 |
+
In dataset metadata, provide mask media via the `video_mask` column; preprocessing converts it into `video_masks/`.
|
| 291 |
+
|
| 292 |
+
**Example config:** 📄 [video_inpainting_lora.yaml](../configs/video_inpainting_lora.yaml)
|
| 293 |
+
|
| 294 |
+
---
|
| 295 |
+
|
| 296 |
+
## 🌅 Video Outpainting
|
| 297 |
+
|
| 298 |
+
Extend a video spatially beyond its original boundaries. A rectangular pixel region is provided as clean conditioning
|
| 299 |
+
(no noise, timestep=0, excluded from loss) — the model learns to generate the surrounding content. The `spatial_region`
|
| 300 |
+
is specified in pixel coordinates `[y1, x1, y2, x2]` and automatically converted to latent space.
|
| 301 |
+
|
| 302 |
+
```yaml
|
| 303 |
+
training_strategy:
|
| 304 |
+
name: "flexible"
|
| 305 |
+
video:
|
| 306 |
+
is_generated: true
|
| 307 |
+
latents_dir: "latents"
|
| 308 |
+
conditions:
|
| 309 |
+
- type: spatial_crop
|
| 310 |
+
spatial_region: [0, 0, 288, 576] # y1, x1, y2, x2 in pixels
|
| 311 |
+
probability: 1.0
|
| 312 |
+
```
|
| 313 |
+
|
| 314 |
+
> [!NOTE]
|
| 315 |
+
> `spatial_crop` is a video-only condition — it is not supported on the audio modality.
|
| 316 |
+
|
| 317 |
+
**Example config:** 📄 [video_outpainting_lora.yaml](../configs/video_outpainting_lora.yaml)
|
| 318 |
+
|
| 319 |
+
---
|
| 320 |
+
|
| 321 |
+
## 🔈 Text-to-Audio (T2A)
|
| 322 |
+
|
| 323 |
+
Generate audio from text prompts with no video modality. Only the audio branch of the transformer is denoised. Since
|
| 324 |
+
no video modality is configured, this mode uses **audio-only LoRA targets** — explicitly targeting `audio_attn1`,
|
| 325 |
+
`audio_attn2`, and `audio_ff` modules.
|
| 326 |
+
|
| 327 |
+
```yaml
|
| 328 |
+
training_strategy:
|
| 329 |
+
name: "flexible"
|
| 330 |
+
audio:
|
| 331 |
+
is_generated: true
|
| 332 |
+
latents_dir: "audio_latents"
|
| 333 |
+
```
|
| 334 |
+
|
| 335 |
+
> [!NOTE]
|
| 336 |
+
> With no `video` block in the strategy, the trainer only loads audio latents and text embeddings. LoRA adapters
|
| 337 |
+
> should explicitly target audio modules (e.g., `audio_attn1.to_k`) rather than short patterns like `to_k` which
|
| 338 |
+
> would also match video modules. See [LoRA Target Modules Guidance](#lora-target-modules-guidance) below.
|
| 339 |
+
|
| 340 |
+
**Example config:** 📄 [t2a_lora.yaml](../configs/t2a_lora.yaml)
|
| 341 |
+
|
| 342 |
+
---
|
| 343 |
+
|
| 344 |
+
## 🔊 Audio Extension
|
| 345 |
+
|
| 346 |
+
Extend audio forward (prefix) or backward (suffix) in time — the audio equivalent of Video Extension. A span of
|
| 347 |
+
existing audio latent frames is provided as clean conditioning, and the model generates the continuation. The
|
| 348 |
+
`temporal_boundary` sets the number of latent frames used as context. This mode uses **audio-only LoRA targets**.
|
| 349 |
+
|
| 350 |
+
```yaml
|
| 351 |
+
training_strategy:
|
| 352 |
+
name: "flexible"
|
| 353 |
+
audio:
|
| 354 |
+
is_generated: true
|
| 355 |
+
latents_dir: "audio_latents"
|
| 356 |
+
conditions:
|
| 357 |
+
- type: prefix # or "suffix" for backward extension
|
| 358 |
+
temporal_boundary: 8
|
| 359 |
+
probability: 1.0
|
| 360 |
+
```
|
| 361 |
+
|
| 362 |
+
**Example configs:** 📄 [audio_extend_lora.yaml](../configs/audio_extend_lora.yaml), 📄 [audio_suffix_lora.yaml](../configs/audio_suffix_lora.yaml)
|
| 363 |
+
|
| 364 |
+
---
|
| 365 |
+
|
| 366 |
+
## 🎭 Audio Inpainting
|
| 367 |
+
|
| 368 |
+
Fill in masked regions of audio. Per-sample masks loaded from disk define which audio tokens are conditioning and
|
| 369 |
+
which must be generated — the audio equivalent of Video Inpainting. Masks are thresholded at `0.5` with the same
|
| 370 |
+
binary semantics as video inpainting. This mode uses **audio-only LoRA targets**.
|
| 371 |
+
|
| 372 |
+
```yaml
|
| 373 |
+
training_strategy:
|
| 374 |
+
name: "flexible"
|
| 375 |
+
audio:
|
| 376 |
+
is_generated: true
|
| 377 |
+
latents_dir: "audio_latents"
|
| 378 |
+
conditions:
|
| 379 |
+
- type: mask
|
| 380 |
+
mask_dir: "audio_masks"
|
| 381 |
+
probability: 1.0
|
| 382 |
+
```
|
| 383 |
+
|
| 384 |
+
**Dataset structure:**
|
| 385 |
+
|
| 386 |
+
```
|
| 387 |
+
preprocessed_data_root/
|
| 388 |
+
├── conditions/ # Text embeddings
|
| 389 |
+
├── audio_latents/ # Audio latents
|
| 390 |
+
└── audio_masks/ # Per-sample binary masks (1 → conditioning, 0 → generate)
|
| 391 |
+
```
|
| 392 |
+
|
| 393 |
+
In dataset metadata, provide mask media via the `audio_mask` column; preprocessing converts it into `audio_masks/`.
|
| 394 |
+
|
| 395 |
+
**Example config:** 📄 [audio_inpainting_lora.yaml](../configs/audio_inpainting_lora.yaml)
|
| 396 |
+
|
| 397 |
+
---
|
| 398 |
+
|
| 399 |
+
## 🔄 IC-LoRA / Audio-to-Audio (A2A)
|
| 400 |
+
|
| 401 |
+
In-Context LoRA for audio-to-audio transformations. Pre-encoded reference audio latents are concatenated to the target
|
| 402 |
+
sequence — reference tokens participate in bidirectional self-attention but receive no noise and are excluded from loss.
|
| 403 |
+
This enables audio style transfer, voice conversion, sound effect transformation, and more. This mode uses
|
| 404 |
+
**audio-only LoRA targets**.
|
| 405 |
+
|
| 406 |
+
```yaml
|
| 407 |
+
training_strategy:
|
| 408 |
+
name: "flexible"
|
| 409 |
+
audio:
|
| 410 |
+
is_generated: true
|
| 411 |
+
latents_dir: "audio_latents"
|
| 412 |
+
conditions:
|
| 413 |
+
- type: reference
|
| 414 |
+
latents_dir: "reference_audio_latents"
|
| 415 |
+
probability: 1.0
|
| 416 |
+
```
|
| 417 |
+
|
| 418 |
+
**Dataset structure:**
|
| 419 |
+
|
| 420 |
+
```
|
| 421 |
+
preprocessed_data_root/
|
| 422 |
+
├── conditions/ # Text embeddings
|
| 423 |
+
├── audio_latents/ # Target audio latents
|
| 424 |
+
└── reference_audio_latents/ # Reference audio latents (conditioning input)
|
| 425 |
+
```
|
| 426 |
+
|
| 427 |
+
**Example config:** 📄 [a2a_ic_lora.yaml](../configs/a2a_ic_lora.yaml)
|
| 428 |
+
|
| 429 |
+
---
|
| 430 |
+
|
| 431 |
+
## 🔄 AV2AV IC-LoRA
|
| 432 |
+
|
| 433 |
+
Joint audio-video In-Context LoRA — both modalities have reference conditioning. Pre-encoded reference latents are
|
| 434 |
+
concatenated to each modality's target sequence independently. This enables joint audiovisual transformations such as
|
| 435 |
+
synchronized style transfer across both video and audio.
|
| 436 |
+
|
| 437 |
+
```yaml
|
| 438 |
+
training_strategy:
|
| 439 |
+
name: "flexible"
|
| 440 |
+
video:
|
| 441 |
+
is_generated: true
|
| 442 |
+
latents_dir: "latents"
|
| 443 |
+
conditions:
|
| 444 |
+
- type: reference
|
| 445 |
+
latents_dir: "reference_latents"
|
| 446 |
+
probability: 1.0
|
| 447 |
+
audio:
|
| 448 |
+
is_generated: true
|
| 449 |
+
latents_dir: "audio_latents"
|
| 450 |
+
conditions:
|
| 451 |
+
- type: reference
|
| 452 |
+
latents_dir: "reference_audio_latents"
|
| 453 |
+
probability: 1.0
|
| 454 |
+
```
|
| 455 |
+
|
| 456 |
+
> [!NOTE]
|
| 457 |
+
> Unlike audio-only IC-LoRA (A2A), AV2AV uses short LoRA target patterns like `"to_k"` to match all branches
|
| 458 |
+
> (video, audio, and cross-modal attention), since both modalities are trained.
|
| 459 |
+
|
| 460 |
+
**Dataset structure:**
|
| 461 |
+
|
| 462 |
+
```
|
| 463 |
+
preprocessed_data_root/
|
| 464 |
+
├── latents/ # Target video latents
|
| 465 |
+
├── audio_latents/ # Target audio latents
|
| 466 |
+
├── conditions/ # Text embeddings
|
| 467 |
+
├── reference_latents/ # Reference video latents (conditioning input)
|
| 468 |
+
└── reference_audio_latents/ # Reference audio latents (conditioning input)
|
| 469 |
+
```
|
| 470 |
+
|
| 471 |
+
**Example config:** 📄 [av2av_ic_lora.yaml](../configs/av2av_ic_lora.yaml)
|
| 472 |
+
|
| 473 |
+
---
|
| 474 |
+
|
| 475 |
+
## 🔥 Full Model Fine-tuning
|
| 476 |
+
|
| 477 |
+
All modes above default to `training_mode: "lora"`. For full fine-tuning, set `training_mode: "full"` — this updates
|
| 478 |
+
all model parameters rather than adding LoRA adapters.
|
| 479 |
+
|
| 480 |
+
```yaml
|
| 481 |
+
model:
|
| 482 |
+
training_mode: "full"
|
| 483 |
+
|
| 484 |
+
training_strategy:
|
| 485 |
+
name: "flexible"
|
| 486 |
+
video:
|
| 487 |
+
is_generated: true
|
| 488 |
+
latents_dir: "latents"
|
| 489 |
+
audio:
|
| 490 |
+
is_generated: true
|
| 491 |
+
latents_dir: "audio_latents"
|
| 492 |
+
```
|
| 493 |
+
|
| 494 |
+
> [!IMPORTANT]
|
| 495 |
+
> Full fine-tuning requires multiple high-end GPUs (e.g., 4-8× H100 80GB) and distributed training with FSDP.
|
| 496 |
+
> See [Training Guide](training-guide.md) for multi-GPU setup instructions.
|
| 497 |
+
|
| 498 |
+
---
|
| 499 |
+
|
| 500 |
+
## 🎛️ LoRA Target Modules Guidance
|
| 501 |
+
|
| 502 |
+
The `target_modules` configuration determines which transformer modules receive LoRA adapters. The right choice depends
|
| 503 |
+
on whether your training involves cross-modal (audio ↔ video) interaction.
|
| 504 |
+
|
| 505 |
+
**For T2V, I2V, A2V, V2A, or any mode involving both modalities** — use short patterns to match all branches
|
| 506 |
+
(video, audio, and cross-modal attention):
|
| 507 |
+
|
| 508 |
+
```yaml
|
| 509 |
+
target_modules:
|
| 510 |
+
- "to_k"
|
| 511 |
+
- "to_q"
|
| 512 |
+
- "to_v"
|
| 513 |
+
- "to_out.0"
|
| 514 |
+
```
|
| 515 |
+
|
| 516 |
+
> [!IMPORTANT]
|
| 517 |
+
> Short patterns like `"to_k"` match video modules (`attn1.to_k`, `attn2.to_k`), audio modules
|
| 518 |
+
> (`audio_attn1.to_k`, `audio_attn2.to_k`), and cross-modal modules (`audio_to_video_attn.to_k`,
|
| 519 |
+
> `video_to_audio_attn.to_k`). The cross-modal attention modules enable bidirectional information flow between
|
| 520 |
+
> audio and video, which is critical for synchronized audiovisual generation.
|
| 521 |
+
> See [Understanding Target Modules](configuration-reference.md#understanding-target-modules) for detailed guidance.
|
| 522 |
+
|
| 523 |
+
**For video-only IC-LoRA** — explicitly target video modules (including FFN layers for better transformation quality):
|
| 524 |
+
|
| 525 |
+
```yaml
|
| 526 |
+
target_modules:
|
| 527 |
+
- "attn1.to_k"
|
| 528 |
+
- "attn1.to_q"
|
| 529 |
+
- "attn1.to_v"
|
| 530 |
+
- "attn1.to_out.0"
|
| 531 |
+
- "attn2.to_k"
|
| 532 |
+
- "attn2.to_q"
|
| 533 |
+
- "attn2.to_v"
|
| 534 |
+
- "attn2.to_out.0"
|
| 535 |
+
- "ff.net.0.proj"
|
| 536 |
+
- "ff.net.2"
|
| 537 |
+
```
|
| 538 |
+
|
| 539 |
+
**For audio-only modes (T2A, Audio Extension, Audio Inpainting, A2A IC-LoRA)** — explicitly target audio modules:
|
| 540 |
+
|
| 541 |
+
```yaml
|
| 542 |
+
target_modules:
|
| 543 |
+
- "audio_attn1.to_k"
|
| 544 |
+
- "audio_attn1.to_q"
|
| 545 |
+
- "audio_attn1.to_v"
|
| 546 |
+
- "audio_attn1.to_out.0"
|
| 547 |
+
- "audio_attn2.to_k"
|
| 548 |
+
- "audio_attn2.to_q"
|
| 549 |
+
- "audio_attn2.to_v"
|
| 550 |
+
- "audio_attn2.to_out.0"
|
| 551 |
+
- "audio_ff.net.0.proj"
|
| 552 |
+
- "audio_ff.net.2"
|
| 553 |
+
```
|
| 554 |
+
|
| 555 |
+
> [!NOTE]
|
| 556 |
+
> Audio-only modes have no `video` block in the strategy, so there is no need to train video or cross-modal
|
| 557 |
+
> attention modules. Targeting only `audio_*` modules keeps the LoRA small and focused.
|
| 558 |
+
|
| 559 |
+
---
|
| 560 |
+
|
| 561 |
+
## 🎬 Using Trained Models for Inference
|
| 562 |
+
|
| 563 |
+
After training, use the [`ltx-pipelines`](../../ltx-pipelines/) package for production inference with your trained
|
| 564 |
+
LoRAs:
|
| 565 |
+
|
| 566 |
+
| Training Mode | Recommended Pipeline |
|
| 567 |
+
|-------------------------|-------------------------------------------------------|
|
| 568 |
+
| T2V / I2V / A2V / Extension / Inpainting / Outpainting | `TI2VidOneStagePipeline` or `TI2VidTwoStagesPipeline` |
|
| 569 |
+
| IC-LoRA (V2V / A2A / AV2AV) | `ICLoraPipeline` |
|
| 570 |
+
| V2A (Foley) / T2A / Audio Extension / Audio Inpainting | `TI2VidOneStagePipeline` or `TI2VidTwoStagesPipeline` |
|
| 571 |
+
|
| 572 |
+
All pipelines support loading custom LoRAs via the `loras` parameter. See the [`ltx-pipelines`](../../ltx-pipelines/)
|
| 573 |
+
package documentation for detailed usage instructions.
|
| 574 |
+
|
| 575 |
+
> [!NOTE]
|
| 576 |
+
> You can generate audio during validation even if you're not training the audio branch.
|
| 577 |
+
> Set `validation.generate_audio: true` independently of whether audio has `is_generated: true`.
|
| 578 |
+
|
| 579 |
+
---
|
| 580 |
+
|
| 581 |
+
## 🔄 Migration from Legacy Strategies
|
| 582 |
+
|
| 583 |
+
Legacy `text_to_video` and `video_to_video` strategy configs are forward-compatible and will continue to work (with a
|
| 584 |
+
deprecation warning). We recommend migrating to `flexible` for access to all conditioning modes.
|
| 585 |
+
|
| 586 |
+
---
|
| 587 |
+
|
| 588 |
+
## 🚀 Next Steps
|
| 589 |
+
|
| 590 |
+
Once you've chosen your training mode:
|
| 591 |
+
|
| 592 |
+
- Set up your dataset using [Dataset Preparation](dataset-preparation.md)
|
| 593 |
+
- Configure your training parameters in [Configuration Reference](configuration-reference.md)
|
| 594 |
+
- Start training with the [Training Guide](training-guide.md)
|
| 595 |
+
|
| 596 |
+
> [!TIP]
|
| 597 |
+
> Need a training mode that's not covered here?
|
| 598 |
+
> First check whether it can be expressed by composing existing `flexible` conditions. Use
|
| 599 |
+
> [Implementing Custom Training Strategies](custom-training-strategies.md) only for custom losses,
|
| 600 |
+
> noising rules, model outputs, or preprocessing that cannot be represented by configuration.
|
packages/ltx-trainer/docs/troubleshooting.md
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Troubleshooting Guide
|
| 2 |
+
|
| 3 |
+
This guide covers common issues and solutions when training with the LTX-2 trainer.
|
| 4 |
+
|
| 5 |
+
## 🔧 VRAM and Memory Issues
|
| 6 |
+
|
| 7 |
+
Memory management is crucial for successful training with LTX-2.
|
| 8 |
+
|
| 9 |
+
> [!TIP]
|
| 10 |
+
> For GPUs with 32GB VRAM, use the pre-configured low VRAM config:
|
| 11 |
+
> [`configs/t2v_lora_low_vram.yaml`](../configs/t2v_lora_low_vram.yaml)
|
| 12 |
+
> which combines 8-bit optimizer, INT8 quantization, and reduced LoRA rank.
|
| 13 |
+
|
| 14 |
+
### Memory Optimization Techniques
|
| 15 |
+
|
| 16 |
+
#### 1. Enable Gradient Checkpointing
|
| 17 |
+
|
| 18 |
+
Gradient checkpointing trades training speed for memory savings. **Highly recommended** for most training runs:
|
| 19 |
+
|
| 20 |
+
```yaml
|
| 21 |
+
optimization:
|
| 22 |
+
enable_gradient_checkpointing: true
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
#### 2. Enable 8-bit Text Encoder
|
| 26 |
+
|
| 27 |
+
Load the Gemma text encoder in 8-bit precision to save GPU memory:
|
| 28 |
+
|
| 29 |
+
```yaml
|
| 30 |
+
acceleration:
|
| 31 |
+
load_text_encoder_in_8bit: true
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
#### 3. Reduce Batch Size
|
| 35 |
+
|
| 36 |
+
Lower the batch size if you encounter out-of-memory errors:
|
| 37 |
+
|
| 38 |
+
```yaml
|
| 39 |
+
optimization:
|
| 40 |
+
batch_size: 1 # Start with 1 and increase gradually
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
Use gradient accumulation to maintain a larger effective batch size:
|
| 44 |
+
|
| 45 |
+
```yaml
|
| 46 |
+
optimization:
|
| 47 |
+
batch_size: 1
|
| 48 |
+
gradient_accumulation_steps: 4 # Effective batch size = 4
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
#### 4. Use Lower Resolution
|
| 52 |
+
|
| 53 |
+
Reduce spatial or temporal dimensions to save memory:
|
| 54 |
+
|
| 55 |
+
```bash
|
| 56 |
+
# Smaller spatial resolution
|
| 57 |
+
uv run python scripts/process_dataset.py dataset.json \
|
| 58 |
+
--resolution-buckets "512x512x49" \
|
| 59 |
+
--model-path /path/to/model.safetensors \
|
| 60 |
+
--text-encoder-path /path/to/gemma
|
| 61 |
+
|
| 62 |
+
# Fewer frames
|
| 63 |
+
uv run python scripts/process_dataset.py dataset.json \
|
| 64 |
+
--resolution-buckets "960x544x25" \
|
| 65 |
+
--model-path /path/to/model.safetensors \
|
| 66 |
+
--text-encoder-path /path/to/gemma
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
#### 5. Enable Model Quantization
|
| 70 |
+
|
| 71 |
+
Use quantization to reduce memory usage:
|
| 72 |
+
|
| 73 |
+
```yaml
|
| 74 |
+
acceleration:
|
| 75 |
+
quantization: "int8-quanto" # Options: int8-quanto, int4-quanto, fp8-quanto
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
#### 6. Use 8-bit Optimizer
|
| 79 |
+
|
| 80 |
+
The 8-bit AdamW optimizer uses less memory:
|
| 81 |
+
|
| 82 |
+
```yaml
|
| 83 |
+
optimization:
|
| 84 |
+
optimizer_type: "adamw8bit"
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
#### 7. Offload Optimizer State During Validation
|
| 88 |
+
|
| 89 |
+
If you OOM specifically during validation video sampling — typically in
|
| 90 |
+
full fine-tunes or high-rank LoRA runs where AdamW state and the VAE decoder
|
| 91 |
+
can't coexist on the GPU — offload optimizer state to CPU during sampling:
|
| 92 |
+
|
| 93 |
+
```yaml
|
| 94 |
+
acceleration:
|
| 95 |
+
offload_optimizer_during_validation: true
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
The offload + reload happens once per validation interval, not per step.
|
| 99 |
+
No effect for FSDP (sharded state).
|
| 100 |
+
|
| 101 |
+
---
|
| 102 |
+
|
| 103 |
+
## ⚠️ Common Usage Issues
|
| 104 |
+
|
| 105 |
+
### Issue: "No module named 'ltx_trainer'" Error
|
| 106 |
+
|
| 107 |
+
**Solution:**
|
| 108 |
+
Ensure you've installed the dependencies and are using `uv run` to execute scripts:
|
| 109 |
+
|
| 110 |
+
```bash
|
| 111 |
+
# From the repository root
|
| 112 |
+
uv sync
|
| 113 |
+
cd packages/ltx-trainer
|
| 114 |
+
uv run python scripts/train.py configs/t2v_lora.yaml
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
> [!TIP]
|
| 118 |
+
> Always use `uv run` to execute Python scripts. This automatically uses the correct virtual environment
|
| 119 |
+
> without requiring manual activation.
|
| 120 |
+
|
| 121 |
+
### Issue: "Gemma model path is not a directory" Error
|
| 122 |
+
|
| 123 |
+
**Solution:**
|
| 124 |
+
The `text_encoder_path` must point to a directory containing the Gemma model, not a file:
|
| 125 |
+
|
| 126 |
+
```yaml
|
| 127 |
+
model:
|
| 128 |
+
model_path: "/path/to/ltx-2-model.safetensors" # File path
|
| 129 |
+
text_encoder_path: "/path/to/gemma-model/" # Directory path
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
### Issue: "Model path does not exist" Error
|
| 133 |
+
|
| 134 |
+
**Solution:**
|
| 135 |
+
LTX-2 requires local model paths. URLs are not supported:
|
| 136 |
+
|
| 137 |
+
```yaml
|
| 138 |
+
# ✅ Correct - local path
|
| 139 |
+
model:
|
| 140 |
+
model_path: "/path/to/ltx-2-model.safetensors"
|
| 141 |
+
|
| 142 |
+
# ❌ Wrong - URL not supported
|
| 143 |
+
model:
|
| 144 |
+
model_path: "https://huggingface.co/..."
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
### Issue: "Frames must satisfy frames % 8 == 1" Error
|
| 148 |
+
|
| 149 |
+
**Solution:**
|
| 150 |
+
LTX-2 requires the number of frames to satisfy `frames % 8 == 1`:
|
| 151 |
+
|
| 152 |
+
- ✅ Valid: 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, 97, 121
|
| 153 |
+
- ❌ Invalid: 24, 32, 48, 64, 100
|
| 154 |
+
|
| 155 |
+
### Issue: Slow Training Speed
|
| 156 |
+
|
| 157 |
+
**Optimizations:**
|
| 158 |
+
|
| 159 |
+
1. **Disable gradient checkpointing** (if you have enough VRAM):
|
| 160 |
+
|
| 161 |
+
```yaml
|
| 162 |
+
optimization:
|
| 163 |
+
enable_gradient_checkpointing: false
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
2. **Use torch.compile** via Accelerate:
|
| 168 |
+
|
| 169 |
+
```bash
|
| 170 |
+
uv run accelerate launch --config_file configs/accelerate/ddp_compile.yaml \
|
| 171 |
+
scripts/train.py configs/t2v_lora.yaml
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
### Issue: Poor Quality Validation Outputs
|
| 175 |
+
|
| 176 |
+
**Solutions:**
|
| 177 |
+
|
| 178 |
+
1. **Use conditioned validation:** For more reliable validation, use image-to-video (first-frame conditioning) rather than pure text-to-video:
|
| 179 |
+
|
| 180 |
+
```yaml
|
| 181 |
+
validation:
|
| 182 |
+
samples:
|
| 183 |
+
- prompt: "a professional portrait video of a person"
|
| 184 |
+
conditions:
|
| 185 |
+
- type: first_frame
|
| 186 |
+
image_or_video: "/path/to/first_frame.png"
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
2. **Increase inference steps:**
|
| 190 |
+
|
| 191 |
+
```yaml
|
| 192 |
+
validation:
|
| 193 |
+
inference_steps: 30
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
3. **Adjust guidance settings:**
|
| 197 |
+
|
| 198 |
+
```yaml
|
| 199 |
+
validation:
|
| 200 |
+
guidance_scale: 4.0 # CFG scale (recommended: 4.0)
|
| 201 |
+
stg_scale: 1.0 # STG scale for temporal coherence (recommended: 1.0)
|
| 202 |
+
stg_blocks: [29] # Transformer block to perturb
|
| 203 |
+
```
|
| 204 |
+
|
| 205 |
+
4. **Check caption quality:**
|
| 206 |
+
Review and manually edit captions for accuracy if using auto-generated captions.
|
| 207 |
+
LTX-2 prefers long, detailed captions that describe both visual content and audio (e.g., ambient sounds, speech,
|
| 208 |
+
music).
|
| 209 |
+
|
| 210 |
+
5. **Check target modules:**
|
| 211 |
+
Ensure your `target_modules` configuration matches your training goals. For audio-video training,
|
| 212 |
+
use patterns that match both branches (e.g., `"to_k"` instead of `"attn1.to_k"`).
|
| 213 |
+
See [Understanding Target Modules](configuration-reference.md#understanding-target-modules) for details.
|
| 214 |
+
|
| 215 |
+
6. **Adjust LoRA rank:**
|
| 216 |
+
Try higher values for more capacity:
|
| 217 |
+
|
| 218 |
+
```yaml
|
| 219 |
+
lora:
|
| 220 |
+
rank: 64 # Or 128 for more capacity
|
| 221 |
+
```
|
| 222 |
+
|
| 223 |
+
7. **Increase training steps:**
|
| 224 |
+
|
| 225 |
+
```yaml
|
| 226 |
+
optimization:
|
| 227 |
+
steps: 3000
|
| 228 |
+
```
|
| 229 |
+
|
| 230 |
+
---
|
| 231 |
+
|
| 232 |
+
## 🔍 Debugging Tools
|
| 233 |
+
|
| 234 |
+
### Monitor GPU Memory Usage
|
| 235 |
+
|
| 236 |
+
Track memory usage during training:
|
| 237 |
+
|
| 238 |
+
```bash
|
| 239 |
+
# Watch GPU memory in real-time
|
| 240 |
+
watch -n 1 nvidia-smi
|
| 241 |
+
|
| 242 |
+
# Log memory usage to file
|
| 243 |
+
nvidia-smi --query-gpu=memory.used,memory.total --format=csv --loop=5 > memory_log.csv
|
| 244 |
+
```
|
| 245 |
+
|
| 246 |
+
### Verify Preprocessed Data
|
| 247 |
+
|
| 248 |
+
Decode latents to visualize the preprocessed videos:
|
| 249 |
+
|
| 250 |
+
```bash
|
| 251 |
+
uv run python scripts/decode_latents.py dataset/.precomputed/latents debug_output \
|
| 252 |
+
--model-path /path/to/model.safetensors
|
| 253 |
+
```
|
| 254 |
+
|
| 255 |
+
To also decode audio latents, add the `--with-audio` flag:
|
| 256 |
+
|
| 257 |
+
```bash
|
| 258 |
+
uv run python scripts/decode_latents.py dataset/.precomputed/latents debug_output \
|
| 259 |
+
--model-path /path/to/model.safetensors \
|
| 260 |
+
--with-audio
|
| 261 |
+
```
|
| 262 |
+
|
| 263 |
+
Compare decoded videos and audio with originals to ensure quality.
|
| 264 |
+
|
| 265 |
+
---
|
| 266 |
+
|
| 267 |
+
## 💡 Best Practices
|
| 268 |
+
|
| 269 |
+
### Before Training
|
| 270 |
+
|
| 271 |
+
- [ ] Test preprocessing with a small subset first
|
| 272 |
+
- [ ] Verify all video files are accessible
|
| 273 |
+
- [ ] Check available GPU memory
|
| 274 |
+
- [ ] Review configuration against hardware capabilities
|
| 275 |
+
- [ ] Ensure model and text encoder paths are correct
|
| 276 |
+
|
| 277 |
+
### During Training
|
| 278 |
+
|
| 279 |
+
- [ ] Monitor GPU memory usage
|
| 280 |
+
- [ ] Check loss convergence regularly
|
| 281 |
+
- [ ] Review validation samples periodically
|
| 282 |
+
- [ ] Save checkpoints frequently
|
| 283 |
+
|
| 284 |
+
### After Training
|
| 285 |
+
|
| 286 |
+
- [ ] Test trained model with diverse prompts
|
| 287 |
+
- [ ] Document training parameters and results
|
| 288 |
+
- [ ] Archive training data and configs
|
| 289 |
+
|
| 290 |
+
## 🆘 Getting Help
|
| 291 |
+
|
| 292 |
+
If you're still experiencing issues:
|
| 293 |
+
|
| 294 |
+
1. **Check logs:** Review console output for error details
|
| 295 |
+
2. **Search issues:** Look through GitHub issues for similar problems
|
| 296 |
+
3. **Provide details:** When reporting issues, include:
|
| 297 |
+
- Hardware specifications (GPU model, VRAM)
|
| 298 |
+
- Configuration file used
|
| 299 |
+
- Complete error message
|
| 300 |
+
- Steps to reproduce the issue
|
| 301 |
+
|
| 302 |
+
---
|
| 303 |
+
|
| 304 |
+
## 🤝 Join the Community
|
| 305 |
+
|
| 306 |
+
Have questions, want to share your results, or need real-time help?
|
| 307 |
+
Join our [community Discord server](https://discord.gg/ltxplatform)
|
| 308 |
+
to connect with other users and the development team!
|
| 309 |
+
|
| 310 |
+
- Get troubleshooting help
|
| 311 |
+
- Share your training results and workflows
|
| 312 |
+
- Stay up to date with announcements and updates
|
| 313 |
+
|
| 314 |
+
We look forward to seeing you there!
|
packages/ltx-trainer/docs/utility-scripts.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Utility Scripts Reference
|
| 2 |
+
|
| 3 |
+
This guide covers the various utility scripts available for preprocessing, conversion, and debugging tasks.
|
| 4 |
+
|
| 5 |
+
## 🎬 Dataset Processing Scripts
|
| 6 |
+
|
| 7 |
+
### Video Scene Splitting
|
| 8 |
+
|
| 9 |
+
The `scripts/split_scenes.py` script automatically splits long videos into shorter, coherent scenes.
|
| 10 |
+
|
| 11 |
+
```bash
|
| 12 |
+
# Basic scene splitting
|
| 13 |
+
uv run python scripts/split_scenes.py input.mp4 output_dir/ --filter-shorter-than 5s
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
**Key features:**
|
| 17 |
+
|
| 18 |
+
- **Automatic scene detection**: Uses PySceneDetect for intelligent splitting
|
| 19 |
+
- **Multiple algorithms**: Content-based, adaptive, threshold, and histogram detection
|
| 20 |
+
- **Filtering options**: Remove scenes shorter than specified duration
|
| 21 |
+
- **Customizable parameters**: Thresholds, window sizes, and detection modes
|
| 22 |
+
|
| 23 |
+
**Common options:**
|
| 24 |
+
|
| 25 |
+
```bash
|
| 26 |
+
# See all available options
|
| 27 |
+
uv run python scripts/split_scenes.py --help
|
| 28 |
+
|
| 29 |
+
# Use adaptive detection with custom threshold
|
| 30 |
+
uv run python scripts/split_scenes.py video.mp4 scenes/ --detector adaptive --threshold 30.0
|
| 31 |
+
|
| 32 |
+
# Limit to maximum number of scenes
|
| 33 |
+
uv run python scripts/split_scenes.py video.mp4 scenes/ --max-scenes 50
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
### Automatic Video Captioning
|
| 37 |
+
|
| 38 |
+
The `scripts/caption_videos.py` script generates a single, detailed combined audio-visual
|
| 39 |
+
caption per video as a continuous paragraph of prose. Two backends are available:
|
| 40 |
+
|
| 41 |
+
- **`qwen_omni` (default)** — Qwen3-Omni-30B-A3B-Thinking served via a local
|
| 42 |
+
[vLLM](https://docs.vllm.ai/) HTTP server (~1-3 s/video on H100). Highest quality, runs
|
| 43 |
+
fully offline once the model is downloaded.
|
| 44 |
+
- **`gemini_flash`** — Google Gemini (cloud, `gemini-3.5-flash`). No GPU required. Auth is
|
| 45 |
+
automatic: set `GEMINI_API_KEY` (or `GOOGLE_API_KEY`) for the Developer API, or just have
|
| 46 |
+
Google Cloud credentials available (`gcloud auth` / an attached service account) and it
|
| 47 |
+
uses Vertex AI with no extra setup.
|
| 48 |
+
|
| 49 |
+
**Step 1 — launch the captioner server** (`qwen_omni` only, one-time).
|
| 50 |
+
|
| 51 |
+
`scripts/serve_captioner.py` runs vLLM in an isolated environment via `uvx`, so vLLM's heavy
|
| 52 |
+
CUDA dependencies never touch the trainer's venv. It defaults to dynamic FP8 quantization
|
| 53 |
+
(~31 GiB weights, fits on 40 GB GPUs, same speed as BF16 on H100):
|
| 54 |
+
|
| 55 |
+
```bash
|
| 56 |
+
# Terminal 1 - stays running
|
| 57 |
+
uv run python packages/ltx-trainer/scripts/serve_captioner.py
|
| 58 |
+
|
| 59 |
+
# Useful variants:
|
| 60 |
+
# --print-cmd show the vLLM command without running it
|
| 61 |
+
# --quantization bf16 use BF16 instead (needs ~66 GiB free VRAM)
|
| 62 |
+
# --hf-home /mnt/disk override where the ~65 GB model is downloaded
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
**Step 2 — caption your videos.**
|
| 66 |
+
|
| 67 |
+
```bash
|
| 68 |
+
# Terminal 2 - default backend talks to the server above
|
| 69 |
+
uv run python packages/ltx-trainer/scripts/caption_videos.py videos_dir/ --output dataset.json
|
| 70 |
+
|
| 71 |
+
# Remote server: --vllm-url http://other-host:8001/v1
|
| 72 |
+
# Gemini (gemini-3.5-flash): --captioner-type gemini_flash (uses GEMINI_API_KEY, else gcloud/Vertex)
|
| 73 |
+
# Gemini, parallel calls: --captioner-type gemini_flash --num-workers 5
|
| 74 |
+
# Re-caption everything: --override
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
Captioning is incremental (already-captioned files are skipped, progress saves every 5 videos)
|
| 78 |
+
and writes JSON, JSONL, CSV, or TXT based on the output extension.
|
| 79 |
+
|
| 80 |
+
Qwen3-Omni-Thinking can optionally emit a `<think>...</think>` chain-of-thought before the
|
| 81 |
+
caption (`--enable-thinking`). It is off by default, which is recommended for bulk captioning
|
| 82 |
+
(thinking is slower as it generates the reasoning trace first).
|
| 83 |
+
|
| 84 |
+
For Gemini, keep `--num-workers` at 3-5 (higher values may hit API rate limits).
|
| 85 |
+
|
| 86 |
+
### Dataset Preprocessing
|
| 87 |
+
|
| 88 |
+
The `scripts/process_dataset.py` script processes videos and caches latents for training.
|
| 89 |
+
|
| 90 |
+
```bash
|
| 91 |
+
# Basic preprocessing
|
| 92 |
+
uv run python scripts/process_dataset.py dataset.json \
|
| 93 |
+
--resolution-buckets "960x544x49" \
|
| 94 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 95 |
+
--text-encoder-path /path/to/gemma-model
|
| 96 |
+
|
| 97 |
+
# With video decoding for verification
|
| 98 |
+
uv run python scripts/process_dataset.py dataset.json \
|
| 99 |
+
--resolution-buckets "960x544x49" \
|
| 100 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 101 |
+
--text-encoder-path /path/to/gemma-model \
|
| 102 |
+
--decode
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
Multiple resolution buckets can be specified, separated by `;`:
|
| 106 |
+
|
| 107 |
+
```bash
|
| 108 |
+
uv run python scripts/process_dataset.py dataset.json \
|
| 109 |
+
--resolution-buckets "960x544x49;512x512x81" \
|
| 110 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 111 |
+
--text-encoder-path /path/to/gemma-model
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
> [!NOTE]
|
| 115 |
+
> When training with multiple resolution buckets, set `optimization.batch_size: 1`.
|
| 116 |
+
|
| 117 |
+
**Multi-GPU preprocessing.** Launch with `accelerate launch` to shard the dataset across processes. Reruns resume
|
| 118 |
+
by default (existing `.pt` outputs are skipped); writes are atomic so interrupted runs are safe. Pass `--overwrite`
|
| 119 |
+
when rerunning with changed parameters (different model, resolution buckets, text encoder, `--lora-trigger`, etc.)
|
| 120 |
+
so stale outputs are replaced. Use the same `accelerate launch` pattern (and `--overwrite` when needed) with
|
| 121 |
+
`process_videos.py` or `process_captions.py` when you run those scripts standalone.
|
| 122 |
+
|
| 123 |
+
```bash
|
| 124 |
+
# Multi-GPU preprocessing
|
| 125 |
+
uv run accelerate launch --num_processes 4 scripts/process_dataset.py dataset.json \
|
| 126 |
+
--resolution-buckets "960x544x49" \
|
| 127 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 128 |
+
--text-encoder-path /path/to/gemma-model
|
| 129 |
+
|
| 130 |
+
# Force re-encoding of all items (e.g. after switching model or resolution)
|
| 131 |
+
uv run accelerate launch --num_processes 4 scripts/process_dataset.py dataset.json \
|
| 132 |
+
--resolution-buckets "960x544x49" \
|
| 133 |
+
--model-path /path/to/ltx-2.3-model.safetensors \
|
| 134 |
+
--text-encoder-path /path/to/gemma-model \
|
| 135 |
+
--overwrite
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
For detailed usage, see the [Dataset Preparation Guide](dataset-preparation.md).
|
| 139 |
+
|
| 140 |
+
### Reference Video Generation
|
| 141 |
+
|
| 142 |
+
The `scripts/compute_reference.py` script provides a template for creating reference videos needed for IC-LoRA training.
|
| 143 |
+
The default implementation generates Canny edge reference videos.
|
| 144 |
+
|
| 145 |
+
```bash
|
| 146 |
+
# Generate Canny edge reference videos
|
| 147 |
+
uv run python scripts/compute_reference.py videos_dir/ --output dataset.json
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
**Key features:**
|
| 151 |
+
|
| 152 |
+
- **Canny edge detection**: Creates edge-based reference videos
|
| 153 |
+
- **In-place editing**: Updates existing dataset JSON files
|
| 154 |
+
- **Customizable**: Modify the `compute_reference()` function for different conditions (depth, pose, etc.)
|
| 155 |
+
|
| 156 |
+
> [!TIP]
|
| 157 |
+
> You can edit this script to generate other types of reference videos for IC-LoRA training,
|
| 158 |
+
> such as depth maps, segmentation masks, or any custom video transformation.
|
| 159 |
+
|
| 160 |
+
> [!NOTE]
|
| 161 |
+
> `compute_reference.py` writes generated references to the `reference_video` column, which
|
| 162 |
+
> `process_dataset.py` detects automatically.
|
| 163 |
+
|
| 164 |
+
## 🔍 Debugging and Verification Scripts
|
| 165 |
+
|
| 166 |
+
### Latents Decoding
|
| 167 |
+
|
| 168 |
+
The `scripts/decode_latents.py` script decodes precomputed video latents back into video files for visual inspection.
|
| 169 |
+
|
| 170 |
+
```bash
|
| 171 |
+
# Basic usage
|
| 172 |
+
uv run python scripts/decode_latents.py /path/to/latents/dir \
|
| 173 |
+
--output-dir /path/to/output \
|
| 174 |
+
--model-path /path/to/ltx-2-model.safetensors
|
| 175 |
+
|
| 176 |
+
# With VAE tiling for large videos
|
| 177 |
+
uv run python scripts/decode_latents.py /path/to/latents/dir \
|
| 178 |
+
--output-dir /path/to/output \
|
| 179 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 180 |
+
--vae-tiling
|
| 181 |
+
|
| 182 |
+
# Decode both video and audio latents
|
| 183 |
+
uv run python scripts/decode_latents.py /path/to/latents/dir \
|
| 184 |
+
--output-dir /path/to/output \
|
| 185 |
+
--model-path /path/to/ltx-2-model.safetensors \
|
| 186 |
+
--with-audio
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
**The script will:**
|
| 190 |
+
|
| 191 |
+
1. **Load the VAE model** from the specified path
|
| 192 |
+
2. **Process all `.pt` latent files** in the input directory
|
| 193 |
+
3. **Decode each latent** back into a video using the VAE
|
| 194 |
+
4. **Save resulting videos** as MP4 files in the output directory
|
| 195 |
+
|
| 196 |
+
**When to use:**
|
| 197 |
+
|
| 198 |
+
- **Verify preprocessing quality**: Check that your videos were encoded correctly
|
| 199 |
+
- **Debug training data**: Visualize what the model actually sees during training
|
| 200 |
+
- **Quality assessment**: Ensure latent encoding preserves important visual details
|
| 201 |
+
|
| 202 |
+
### Inference with Trained Models
|
| 203 |
+
|
| 204 |
+
For inference with trained LoRAs, use the [`ltx-pipelines`](../../ltx-pipelines/) package which provides
|
| 205 |
+
production-ready pipelines:
|
| 206 |
+
|
| 207 |
+
- **Text/Image-to-Video**: `TI2VidOneStagePipeline`, `TI2VidTwoStagesPipeline`
|
| 208 |
+
- **Distilled (fast) inference**: `DistilledPipeline`
|
| 209 |
+
- **IC-LoRA video-to-video**: `ICLoraPipeline`
|
| 210 |
+
- **Keyframe interpolation**: `KeyframeInterpolationPipeline`
|
| 211 |
+
|
| 212 |
+
All pipelines support loading custom LoRAs trained with this trainer.
|
| 213 |
+
|
| 214 |
+
## 🚀 Training Scripts
|
| 215 |
+
|
| 216 |
+
### Basic and Distributed Training
|
| 217 |
+
|
| 218 |
+
Use `scripts/train.py` for both single GPU and multi-GPU runs:
|
| 219 |
+
|
| 220 |
+
```bash
|
| 221 |
+
# Single-GPU training
|
| 222 |
+
uv run python scripts/train.py configs/t2v_lora.yaml
|
| 223 |
+
|
| 224 |
+
# Multi-GPU (uses your accelerate config)
|
| 225 |
+
uv run accelerate launch scripts/train.py configs/t2v_lora.yaml
|
| 226 |
+
|
| 227 |
+
# Override number of processes
|
| 228 |
+
uv run accelerate launch --num_processes 4 scripts/train.py configs/t2v_lora.yaml
|
| 229 |
+
```
|
| 230 |
+
|
| 231 |
+
For detailed usage, see the [Training Guide](training-guide.md).
|
| 232 |
+
|
| 233 |
+
## 💡 Tips for Using Utility Scripts
|
| 234 |
+
|
| 235 |
+
- **Start with `--help`**: Always check available options for each script
|
| 236 |
+
- **Test on small datasets**: Verify workflows with a few files before processing large datasets
|
| 237 |
+
- **Use decode verification**: Always decode a few samples to verify preprocessing quality
|
| 238 |
+
- **Monitor VRAM usage**: Reach for quantization or lower-memory settings (e.g. FP8 for the captioner server) when running into memory issues
|
| 239 |
+
- **Keep backups**: Make copies of important dataset files before running conversion scripts
|
packages/ltx-trainer/pyproject.toml
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "ltx-trainer"
|
| 3 |
+
version = "1.1.7"
|
| 4 |
+
description = "LTX-2 training, democratized."
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
authors = [
|
| 7 |
+
{ name = "Matan Ben-Yosef", email = "mbyosef@lightricks.com" }
|
| 8 |
+
]
|
| 9 |
+
requires-python = ">=3.10"
|
| 10 |
+
dependencies = [
|
| 11 |
+
"ltx-core",
|
| 12 |
+
"accelerate>=1.2.1",
|
| 13 |
+
"av>=14.2.1",
|
| 14 |
+
"bitsandbytes >=0.45.2; sys_platform == 'linux'",
|
| 15 |
+
"google-genai>=2.0",
|
| 16 |
+
"huggingface-hub[hf-xet]>=0.31.4",
|
| 17 |
+
"imageio>=2.37.0",
|
| 18 |
+
"imageio-ffmpeg>=0.6.0",
|
| 19 |
+
"openai>=2.0",
|
| 20 |
+
"opencv-python>=4.11.0.86",
|
| 21 |
+
"optimum-quanto>=0.2.6",
|
| 22 |
+
"pandas>=2.2.3",
|
| 23 |
+
"peft>=0.14.0",
|
| 24 |
+
"pillow-heif>=0.21.0",
|
| 25 |
+
"pydantic>=2.10.4",
|
| 26 |
+
"rich>=13.9.4",
|
| 27 |
+
"safetensors>=0.5.0",
|
| 28 |
+
"scenedetect>=0.6.5.2",
|
| 29 |
+
"sentencepiece>=0.2.0",
|
| 30 |
+
"soundfile>=0.12.1",
|
| 31 |
+
"torch>=2.6.0",
|
| 32 |
+
"torchaudio>=2.7.0",
|
| 33 |
+
# torchcodec must match the torch version (it ships a torch-ABI C++ extension and declares
|
| 34 |
+
# no torch pin of its own); the 0.9 line matches torch 2.9. torchaudio>=2.9 routes
|
| 35 |
+
# torchaudio.load() through torchcodec, so audio preprocessing needs it installed.
|
| 36 |
+
"torchcodec>=0.8.1,<0.10",
|
| 37 |
+
"torchvision>=0.21.0",
|
| 38 |
+
"typer>=0.15.1",
|
| 39 |
+
"wandb>=0.27.0",
|
| 40 |
+
"setuptools>=79.0.0",
|
| 41 |
+
]
|
| 42 |
+
|
| 43 |
+
[dependency-groups]
|
| 44 |
+
dev = [
|
| 45 |
+
"pre-commit>=4.0.1",
|
| 46 |
+
"ruff>=0.8.6",
|
| 47 |
+
]
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
[build-system]
|
| 51 |
+
requires = ["hatchling"]
|
| 52 |
+
build-backend = "hatchling.build"
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
[tool.ruff]
|
| 57 |
+
target-version = "1.1.7"
|
| 58 |
+
line-length = 120
|
| 59 |
+
# Restrict isort first-party detection to src/ so stray dirs (e.g. wandb/ run output)
|
| 60 |
+
# next to pyproject.toml don't get classified as first-party packages. See ruff#10519.
|
| 61 |
+
src = ["src"]
|
| 62 |
+
|
| 63 |
+
[tool.ruff.lint]
|
| 64 |
+
select = [
|
| 65 |
+
"E", # pycodestyle
|
| 66 |
+
"F", # pyflakes
|
| 67 |
+
"W", # pycodestyle (warnings)
|
| 68 |
+
"I", # isort
|
| 69 |
+
"N", # pep8-naming
|
| 70 |
+
"ANN", # flake8-annotations
|
| 71 |
+
"B", # flake8-bugbear
|
| 72 |
+
"A", # flake8-builtins
|
| 73 |
+
"COM", # flake8-commas
|
| 74 |
+
"C4", # flake8-comprehensions
|
| 75 |
+
"DTZ", # flake8-datetimez
|
| 76 |
+
"EXE", # flake8-executable
|
| 77 |
+
"PIE", # flake8-pie
|
| 78 |
+
"T20", # flake8-print
|
| 79 |
+
"PT", # flake8-pytest
|
| 80 |
+
"SIM", # flake8-simplify
|
| 81 |
+
"ARG", # flake8-unused-arguments
|
| 82 |
+
"PTH", # flake8--use-pathlib
|
| 83 |
+
"ERA", # flake8-eradicate
|
| 84 |
+
"RUF", # ruff specific rules
|
| 85 |
+
"PL", # pylint
|
| 86 |
+
]
|
| 87 |
+
ignore = [
|
| 88 |
+
"ANN002", # Missing type annotation for *args
|
| 89 |
+
"ANN003", # Missing type annotation for **kwargs
|
| 90 |
+
"ANN204", # Missing type annotation for special method
|
| 91 |
+
"COM812", # Missing trailing comma
|
| 92 |
+
"PTH123", # `open()` should be replaced by `Path.open()`
|
| 93 |
+
"PLR2004", # Magic value used in comparison, consider replacing with a constant variable
|
| 94 |
+
]
|
| 95 |
+
[tool.ruff.lint.pylint]
|
| 96 |
+
max-args = 10
|
| 97 |
+
[tool.ruff.lint.isort]
|
| 98 |
+
known-first-party = ["ltx_trainer", "ltx_core", "ltx_pipelines"]
|
packages/ltx-trainer/scripts/caption_videos.py
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
Auto-caption videos with audio using multimodal models.
|
| 5 |
+
Backends:
|
| 6 |
+
- Qwen3-Omni-30B-A3B-Thinking via a local vLLM HTTP server (default,
|
| 7 |
+
``qwen_omni``). Launch the server once with ``scripts/serve_captioner.py``.
|
| 8 |
+
- Gemini Flash 3.5 via Google's API (``gemini_flash``).
|
| 9 |
+
The paths in the output file are RELATIVE to the output file's directory,
|
| 10 |
+
making the dataset portable.
|
| 11 |
+
Basic usage:
|
| 12 |
+
# Launch the captioner server once (separate terminal)
|
| 13 |
+
uv run python scripts/serve_captioner.py
|
| 14 |
+
# Caption a directory
|
| 15 |
+
caption_videos.py videos_dir/ --output captions.json
|
| 16 |
+
# Caption a single video with a custom prompt
|
| 17 |
+
caption_videos.py video.mp4 --output cap.json --instruction "Describe in detail."
|
| 18 |
+
Advanced usage:
|
| 19 |
+
# Use Gemini Flash 3.5 (cloud, requires GEMINI_API_KEY)
|
| 20 |
+
caption_videos.py videos_dir/ --captioner-type gemini_flash
|
| 21 |
+
# Gemini with parallel workers
|
| 22 |
+
caption_videos.py videos_dir/ --captioner-type gemini_flash --num-workers 5
|
| 23 |
+
# Talk to a remote vLLM server
|
| 24 |
+
caption_videos.py videos_dir/ --vllm-url http://192.168.1.10:8001/v1
|
| 25 |
+
# Enable Qwen3 chain-of-thought (slower, more detail)
|
| 26 |
+
caption_videos.py videos_dir/ --enable-thinking
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
import csv
|
| 30 |
+
import json
|
| 31 |
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
| 32 |
+
from enum import Enum
|
| 33 |
+
from pathlib import Path
|
| 34 |
+
|
| 35 |
+
import typer
|
| 36 |
+
from rich.console import Console
|
| 37 |
+
from rich.progress import (
|
| 38 |
+
BarColumn,
|
| 39 |
+
MofNCompleteColumn,
|
| 40 |
+
Progress,
|
| 41 |
+
SpinnerColumn,
|
| 42 |
+
TextColumn,
|
| 43 |
+
TimeElapsedColumn,
|
| 44 |
+
TimeRemainingColumn,
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
from ltx_trainer.captioning import (
|
| 48 |
+
DEFAULT_QWEN_MODEL,
|
| 49 |
+
DEFAULT_VLLM_BASE_URL,
|
| 50 |
+
CaptionerType,
|
| 51 |
+
MediaCaptioningModel,
|
| 52 |
+
create_captioner,
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
VIDEO_EXTENSIONS = ["mp4", "avi", "mov", "mkv", "webm"]
|
| 56 |
+
IMAGE_EXTENSIONS = ["jpg", "jpeg", "png"]
|
| 57 |
+
MEDIA_EXTENSIONS = VIDEO_EXTENSIONS + IMAGE_EXTENSIONS
|
| 58 |
+
SAVE_INTERVAL = 5
|
| 59 |
+
|
| 60 |
+
console = Console()
|
| 61 |
+
app = typer.Typer(
|
| 62 |
+
pretty_exceptions_enable=False,
|
| 63 |
+
no_args_is_help=True,
|
| 64 |
+
help="Auto-caption videos with audio using multimodal models.",
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class OutputFormat(str, Enum):
|
| 69 |
+
"""Available output formats for captions."""
|
| 70 |
+
|
| 71 |
+
TXT = "txt" # Separate files for captions and video paths, one caption / video path per line
|
| 72 |
+
CSV = "csv" # CSV file with video path and caption columns
|
| 73 |
+
JSON = "json" # JSON file with video paths as keys and captions as values
|
| 74 |
+
JSONL = "jsonl" # JSON Lines file with one JSON object per line
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def caption_media(
|
| 78 |
+
input_path: Path,
|
| 79 |
+
output_path: Path,
|
| 80 |
+
captioner: MediaCaptioningModel,
|
| 81 |
+
extensions: list[str],
|
| 82 |
+
recursive: bool,
|
| 83 |
+
fps: int,
|
| 84 |
+
output_format: OutputFormat,
|
| 85 |
+
override: bool,
|
| 86 |
+
num_workers: int = 1,
|
| 87 |
+
) -> None:
|
| 88 |
+
"""Caption videos and images using the provided captioning model.
|
| 89 |
+
Args:
|
| 90 |
+
input_path: Path to input video file or directory
|
| 91 |
+
output_path: Path to output caption file
|
| 92 |
+
captioner: Media captioning model
|
| 93 |
+
extensions: List of media file extensions to include
|
| 94 |
+
recursive: Whether to search subdirectories recursively
|
| 95 |
+
fps: Frames per second to sample from videos (ignored for images)
|
| 96 |
+
output_format: Format to save the captions in
|
| 97 |
+
override: Whether to override existing captions
|
| 98 |
+
num_workers: Number of parallel workers (only for cloud-based captioners like Gemini)
|
| 99 |
+
"""
|
| 100 |
+
|
| 101 |
+
# Get list of media files to process
|
| 102 |
+
media_files = _get_media_files(input_path, extensions, recursive)
|
| 103 |
+
|
| 104 |
+
if not media_files:
|
| 105 |
+
console.print("[bold yellow]No media files found to process.[/]")
|
| 106 |
+
return
|
| 107 |
+
|
| 108 |
+
console.print(f"Found [bold]{len(media_files)}[/] media files to process.")
|
| 109 |
+
|
| 110 |
+
# Load existing captions and determine which files need processing
|
| 111 |
+
base_dir = output_path.parent.resolve()
|
| 112 |
+
existing_captions = _load_existing_captions(output_path, output_format)
|
| 113 |
+
existing_abs_paths = {str((base_dir / p).resolve()) for p in existing_captions}
|
| 114 |
+
|
| 115 |
+
if override:
|
| 116 |
+
media_to_process = media_files
|
| 117 |
+
else:
|
| 118 |
+
media_to_process = [f for f in media_files if str(f.resolve()) not in existing_abs_paths]
|
| 119 |
+
if skipped := len(media_files) - len(media_to_process):
|
| 120 |
+
console.print(f"[bold yellow]Skipping {skipped} media that already have captions.[/]")
|
| 121 |
+
|
| 122 |
+
if not media_to_process:
|
| 123 |
+
console.print("[bold yellow]All media already have captions. Use --override to recaption.[/]")
|
| 124 |
+
return
|
| 125 |
+
|
| 126 |
+
if num_workers > 1:
|
| 127 |
+
console.print(f"Running with [bold cyan]{num_workers}[/] parallel workers.")
|
| 128 |
+
|
| 129 |
+
captions = existing_captions.copy()
|
| 130 |
+
successfully_captioned = 0
|
| 131 |
+
completed_since_save = 0
|
| 132 |
+
|
| 133 |
+
progress = Progress(
|
| 134 |
+
SpinnerColumn(),
|
| 135 |
+
TextColumn("{task.description}"),
|
| 136 |
+
BarColumn(bar_width=40),
|
| 137 |
+
MofNCompleteColumn(),
|
| 138 |
+
TimeElapsedColumn(),
|
| 139 |
+
TextColumn("•"),
|
| 140 |
+
TimeRemainingColumn(),
|
| 141 |
+
console=console,
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
def process_one(media_file: Path) -> tuple[str, str]:
|
| 145 |
+
"""Caption a single media file and return (relative_path, caption)."""
|
| 146 |
+
caption = captioner.caption(
|
| 147 |
+
path=media_file,
|
| 148 |
+
fps=fps,
|
| 149 |
+
)
|
| 150 |
+
# Don't resolve the file itself, so a symlinked clip keeps its logical path under the
|
| 151 |
+
# dataset dir instead of jumping to its (possibly external) link target.
|
| 152 |
+
rel_path = str((media_file.parent.resolve() / media_file.name).relative_to(base_dir))
|
| 153 |
+
return rel_path, caption
|
| 154 |
+
|
| 155 |
+
with progress:
|
| 156 |
+
task = progress.add_task(
|
| 157 |
+
f"Captioning (workers: {num_workers})" if num_workers > 1 else "Captioning",
|
| 158 |
+
total=len(media_to_process),
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
with ThreadPoolExecutor(max_workers=num_workers) as executor:
|
| 162 |
+
futures = {executor.submit(process_one, f): f for f in media_to_process}
|
| 163 |
+
|
| 164 |
+
for future in as_completed(futures):
|
| 165 |
+
media_file = futures[future]
|
| 166 |
+
progress.update(task, description=f"Captioning [bold blue]{media_file.name}[/]")
|
| 167 |
+
|
| 168 |
+
try:
|
| 169 |
+
rel_path, caption = future.result()
|
| 170 |
+
|
| 171 |
+
captions[rel_path] = caption
|
| 172 |
+
successfully_captioned += 1
|
| 173 |
+
completed_since_save += 1
|
| 174 |
+
|
| 175 |
+
if completed_since_save >= SAVE_INTERVAL:
|
| 176 |
+
_save_captions(captions, output_path, output_format)
|
| 177 |
+
completed_since_save = 0
|
| 178 |
+
|
| 179 |
+
except Exception as e:
|
| 180 |
+
console.print(f"[bold red]Error captioning {media_file.name}: {e}[/]")
|
| 181 |
+
|
| 182 |
+
progress.advance(task)
|
| 183 |
+
|
| 184 |
+
# Final save with everything accumulated
|
| 185 |
+
_save_captions(captions, output_path, output_format)
|
| 186 |
+
|
| 187 |
+
# Print summary
|
| 188 |
+
console.print(
|
| 189 |
+
f"[bold green]✓[/] Captioned [bold]{successfully_captioned}/{len(media_to_process)}[/] media successfully.",
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def _get_media_files(
|
| 194 |
+
input_path: Path,
|
| 195 |
+
extensions: list[str] = MEDIA_EXTENSIONS,
|
| 196 |
+
recursive: bool = False,
|
| 197 |
+
) -> list[Path]:
|
| 198 |
+
"""Get all media files from the input path."""
|
| 199 |
+
input_path = Path(input_path)
|
| 200 |
+
# Normalize extensions to lowercase without dots
|
| 201 |
+
extensions_set = {ext.lower().lstrip(".") for ext in extensions}
|
| 202 |
+
|
| 203 |
+
if input_path.is_file():
|
| 204 |
+
# If input is a file, check if it has a valid extension
|
| 205 |
+
if input_path.suffix.lstrip(".").lower() in extensions_set:
|
| 206 |
+
return [input_path]
|
| 207 |
+
else:
|
| 208 |
+
typer.echo(f"Warning: {input_path} is not a recognized media file. Skipping.")
|
| 209 |
+
return []
|
| 210 |
+
elif input_path.is_dir():
|
| 211 |
+
# Find all files and filter by extension case-insensitively
|
| 212 |
+
glob_pattern = "**/*" if recursive else "*"
|
| 213 |
+
media_files = [
|
| 214 |
+
f for f in input_path.glob(glob_pattern) if f.is_file() and f.suffix.lstrip(".").lower() in extensions_set
|
| 215 |
+
]
|
| 216 |
+
return sorted(media_files)
|
| 217 |
+
else:
|
| 218 |
+
typer.echo(f"Error: {input_path} does not exist.")
|
| 219 |
+
raise typer.Exit(code=1)
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def _save_captions(
|
| 223 |
+
captions: dict[str, str],
|
| 224 |
+
output_path: Path,
|
| 225 |
+
format_type: OutputFormat,
|
| 226 |
+
) -> None:
|
| 227 |
+
"""Save captions to a file in the specified format.
|
| 228 |
+
Args:
|
| 229 |
+
captions: Dictionary mapping media paths to captions
|
| 230 |
+
output_path: Path to save the output file
|
| 231 |
+
format_type: Format to save the captions in
|
| 232 |
+
"""
|
| 233 |
+
# Create parent directories if they don't exist
|
| 234 |
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 235 |
+
|
| 236 |
+
console.print("[bold blue]Saving captions...[/]")
|
| 237 |
+
|
| 238 |
+
match format_type:
|
| 239 |
+
case OutputFormat.TXT:
|
| 240 |
+
# Create two separate files for captions and media paths
|
| 241 |
+
captions_file = output_path.with_stem(f"{output_path.stem}_captions")
|
| 242 |
+
paths_file = output_path.with_stem(f"{output_path.stem}_paths")
|
| 243 |
+
|
| 244 |
+
with captions_file.open("w", encoding="utf-8") as f:
|
| 245 |
+
for caption in captions.values():
|
| 246 |
+
f.write(f"{caption}\n")
|
| 247 |
+
|
| 248 |
+
with paths_file.open("w", encoding="utf-8") as f:
|
| 249 |
+
for media_path in captions:
|
| 250 |
+
f.write(f"{media_path}\n")
|
| 251 |
+
|
| 252 |
+
console.print(f"[bold green]✓[/] Captions saved to [cyan]{captions_file}[/]")
|
| 253 |
+
console.print(f"[bold green]✓[/] Media paths saved to [cyan]{paths_file}[/]")
|
| 254 |
+
|
| 255 |
+
case OutputFormat.CSV:
|
| 256 |
+
with output_path.open("w", encoding="utf-8", newline="") as f:
|
| 257 |
+
writer = csv.writer(f)
|
| 258 |
+
writer.writerow(["caption", "media_path"])
|
| 259 |
+
for media_path, caption in captions.items():
|
| 260 |
+
writer.writerow([caption, media_path])
|
| 261 |
+
|
| 262 |
+
console.print(f"[bold green]✓[/] Captions saved to [cyan]{output_path}[/]")
|
| 263 |
+
|
| 264 |
+
case OutputFormat.JSON:
|
| 265 |
+
# Format as list of dictionaries with caption and media_path keys
|
| 266 |
+
json_data = [{"caption": caption, "media_path": media_path} for media_path, caption in captions.items()]
|
| 267 |
+
|
| 268 |
+
with output_path.open("w", encoding="utf-8") as f:
|
| 269 |
+
json.dump(json_data, f, indent=2, ensure_ascii=False)
|
| 270 |
+
|
| 271 |
+
console.print(f"[bold green]✓[/] Captions saved to [cyan]{output_path}[/]")
|
| 272 |
+
|
| 273 |
+
case OutputFormat.JSONL:
|
| 274 |
+
with output_path.open("w", encoding="utf-8") as f:
|
| 275 |
+
for media_path, caption in captions.items():
|
| 276 |
+
f.write(json.dumps({"caption": caption, "media_path": media_path}, ensure_ascii=False) + "\n")
|
| 277 |
+
|
| 278 |
+
console.print(f"[bold green]✓[/] Captions saved to [cyan]{output_path}[/]")
|
| 279 |
+
|
| 280 |
+
case _:
|
| 281 |
+
raise ValueError(f"Unsupported output format: {format_type}")
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def _load_existing_captions( # noqa: PLR0912
|
| 285 |
+
output_path: Path,
|
| 286 |
+
format_type: OutputFormat,
|
| 287 |
+
) -> dict[str, str]:
|
| 288 |
+
"""Load existing captions from a file.
|
| 289 |
+
Args:
|
| 290 |
+
output_path: Path to the captions file
|
| 291 |
+
format_type: Format of the captions file
|
| 292 |
+
Returns:
|
| 293 |
+
Dictionary mapping media paths to captions, or empty dict if file doesn't exist
|
| 294 |
+
"""
|
| 295 |
+
if not output_path.exists():
|
| 296 |
+
return {}
|
| 297 |
+
|
| 298 |
+
console.print(f"[bold blue]Loading existing captions from [cyan]{output_path}[/]...[/]")
|
| 299 |
+
|
| 300 |
+
existing_captions = {}
|
| 301 |
+
|
| 302 |
+
try:
|
| 303 |
+
match format_type:
|
| 304 |
+
case OutputFormat.TXT:
|
| 305 |
+
# For TXT format, we have two separate files
|
| 306 |
+
captions_file = output_path.with_stem(f"{output_path.stem}_captions")
|
| 307 |
+
paths_file = output_path.with_stem(f"{output_path.stem}_paths")
|
| 308 |
+
|
| 309 |
+
if captions_file.exists() and paths_file.exists():
|
| 310 |
+
captions = captions_file.read_text(encoding="utf-8").splitlines()
|
| 311 |
+
paths = paths_file.read_text(encoding="utf-8").splitlines()
|
| 312 |
+
|
| 313 |
+
if len(captions) == len(paths):
|
| 314 |
+
existing_captions = dict(zip(paths, captions, strict=False))
|
| 315 |
+
|
| 316 |
+
case OutputFormat.CSV:
|
| 317 |
+
with output_path.open("r", encoding="utf-8", newline="") as f:
|
| 318 |
+
reader = csv.reader(f)
|
| 319 |
+
# Skip header
|
| 320 |
+
next(reader, None)
|
| 321 |
+
for row in reader:
|
| 322 |
+
if len(row) >= 2:
|
| 323 |
+
caption, media_path = row[0], row[1]
|
| 324 |
+
existing_captions[media_path] = caption
|
| 325 |
+
|
| 326 |
+
case OutputFormat.JSON:
|
| 327 |
+
with output_path.open("r", encoding="utf-8") as f:
|
| 328 |
+
json_data = json.load(f)
|
| 329 |
+
for item in json_data:
|
| 330 |
+
if "caption" in item and "media_path" in item:
|
| 331 |
+
existing_captions[item["media_path"]] = item["caption"]
|
| 332 |
+
|
| 333 |
+
case OutputFormat.JSONL:
|
| 334 |
+
with output_path.open("r", encoding="utf-8") as f:
|
| 335 |
+
for line in f:
|
| 336 |
+
item = json.loads(line)
|
| 337 |
+
if "caption" in item and "media_path" in item:
|
| 338 |
+
existing_captions[item["media_path"]] = item["caption"]
|
| 339 |
+
|
| 340 |
+
case _:
|
| 341 |
+
raise ValueError(f"Unsupported output format: {format_type}")
|
| 342 |
+
|
| 343 |
+
console.print(f"[bold green]✓[/] Loaded [bold]{len(existing_captions)}[/] existing captions")
|
| 344 |
+
return existing_captions
|
| 345 |
+
|
| 346 |
+
except Exception as e:
|
| 347 |
+
console.print(f"[bold yellow]Warning: Could not load existing captions: {e}[/]")
|
| 348 |
+
return {}
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
@app.command()
|
| 352 |
+
def main( # noqa: PLR0913
|
| 353 |
+
input_path: Path = typer.Argument( # noqa: B008
|
| 354 |
+
...,
|
| 355 |
+
help="Path to input video/image file or directory containing media files",
|
| 356 |
+
exists=True,
|
| 357 |
+
),
|
| 358 |
+
output: Path | None = typer.Option( # noqa: B008
|
| 359 |
+
None,
|
| 360 |
+
"--output",
|
| 361 |
+
"-o",
|
| 362 |
+
help="Path to output file for captions. Format determined by file extension.",
|
| 363 |
+
),
|
| 364 |
+
captioner_type: CaptionerType = typer.Option( # noqa: B008
|
| 365 |
+
CaptionerType.QWEN_OMNI,
|
| 366 |
+
"--captioner-type",
|
| 367 |
+
"-c",
|
| 368 |
+
help="Type of captioner to use. Valid values: 'qwen_omni' (local), 'gemini_flash' (API)",
|
| 369 |
+
case_sensitive=False,
|
| 370 |
+
),
|
| 371 |
+
vllm_url: str = typer.Option(
|
| 372 |
+
DEFAULT_VLLM_BASE_URL,
|
| 373 |
+
"--vllm-url",
|
| 374 |
+
help=(
|
| 375 |
+
"Base URL of the vLLM OpenAI-compatible server (qwen_omni only). "
|
| 376 |
+
"Launch the server with `uv run python scripts/serve_captioner.py`."
|
| 377 |
+
),
|
| 378 |
+
),
|
| 379 |
+
vllm_model: str = typer.Option(
|
| 380 |
+
DEFAULT_QWEN_MODEL,
|
| 381 |
+
"--vllm-model",
|
| 382 |
+
help="Served model identifier on the vLLM server (qwen_omni only).",
|
| 383 |
+
),
|
| 384 |
+
enable_thinking: bool = typer.Option(
|
| 385 |
+
False,
|
| 386 |
+
"--enable-thinking/--no-thinking",
|
| 387 |
+
help=(
|
| 388 |
+
"Let Qwen3-Omni produce a <think>...</think> chain-of-thought before the caption. "
|
| 389 |
+
"Off by default: ~5x slower with marginal quality benefit and occasional hallucinations."
|
| 390 |
+
),
|
| 391 |
+
),
|
| 392 |
+
max_tokens: int = typer.Option(
|
| 393 |
+
4096,
|
| 394 |
+
"--max-tokens",
|
| 395 |
+
help="Maximum new tokens to generate per caption (qwen_omni only).",
|
| 396 |
+
),
|
| 397 |
+
instruction: str | None = typer.Option(
|
| 398 |
+
None,
|
| 399 |
+
"--instruction",
|
| 400 |
+
"-i",
|
| 401 |
+
help="Custom instruction for the captioning model. If not provided, uses an appropriate default.",
|
| 402 |
+
),
|
| 403 |
+
extensions: str = typer.Option(
|
| 404 |
+
",".join(MEDIA_EXTENSIONS),
|
| 405 |
+
"--extensions",
|
| 406 |
+
"-e",
|
| 407 |
+
help="Comma-separated list of media file extensions to process",
|
| 408 |
+
),
|
| 409 |
+
recursive: bool = typer.Option(
|
| 410 |
+
False,
|
| 411 |
+
"--recursive",
|
| 412 |
+
"-r",
|
| 413 |
+
help="Search for media files in subdirectories recursively",
|
| 414 |
+
),
|
| 415 |
+
fps: int = typer.Option(
|
| 416 |
+
2,
|
| 417 |
+
"--fps",
|
| 418 |
+
"-f",
|
| 419 |
+
help=(
|
| 420 |
+
"Frames per second to sample from videos. 2 is a typical default; "
|
| 421 |
+
"lower values use less compute per video. Ignored for images and for the "
|
| 422 |
+
"Gemini backend (which decides its own sampling rate)."
|
| 423 |
+
),
|
| 424 |
+
),
|
| 425 |
+
override: bool = typer.Option(
|
| 426 |
+
False,
|
| 427 |
+
"--override",
|
| 428 |
+
help="Whether to override existing captions for media",
|
| 429 |
+
),
|
| 430 |
+
api_key: str | None = typer.Option(
|
| 431 |
+
None,
|
| 432 |
+
"--api-key",
|
| 433 |
+
envvar=["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
| 434 |
+
help="API key for Gemini Flash (can also use GOOGLE_API_KEY or GEMINI_API_KEY env var)",
|
| 435 |
+
),
|
| 436 |
+
num_workers: int = typer.Option(
|
| 437 |
+
1,
|
| 438 |
+
"--num-workers",
|
| 439 |
+
"-w",
|
| 440 |
+
min=1,
|
| 441 |
+
max=10,
|
| 442 |
+
help=(
|
| 443 |
+
"Number of parallel workers for captioning (1-10). "
|
| 444 |
+
"Values above 1 are only supported for cloud-based captioners (gemini_flash). "
|
| 445 |
+
"Using multiple workers with a local model will raise an error."
|
| 446 |
+
),
|
| 447 |
+
),
|
| 448 |
+
) -> None:
|
| 449 |
+
"""Auto-caption videos with audio using multimodal models.
|
| 450 |
+
Backends:
|
| 451 |
+
- ``qwen_omni`` (default): Qwen3-Omni-30B-A3B-Thinking via a local vLLM
|
| 452 |
+
HTTP server. Launch the server once in a separate terminal with
|
| 453 |
+
``uv run python scripts/serve_captioner.py``. The server stays loaded
|
| 454 |
+
across script invocations.
|
| 455 |
+
- ``gemini_flash``: Google Gemini (``gemini-3.5-flash``) via the google-genai SDK.
|
| 456 |
+
Auth is automatic -- ``GEMINI_API_KEY``/``GOOGLE_API_KEY`` for the Developer API,
|
| 457 |
+
or Google Cloud credentials (gcloud / service account) for Vertex AI with no env vars.
|
| 458 |
+
The paths in the output file will be relative to the output file's directory.
|
| 459 |
+
Examples:
|
| 460 |
+
# Caption videos using the local vLLM server (default)
|
| 461 |
+
caption_videos.py videos_dir/ -o captions.json
|
| 462 |
+
# Point at a remote vLLM server
|
| 463 |
+
caption_videos.py videos_dir/ -o captions.json --vllm-url http://other-host:8001/v1
|
| 464 |
+
# Caption using Gemini Flash 3.5
|
| 465 |
+
caption_videos.py videos_dir/ -o captions.json -c gemini_flash
|
| 466 |
+
# Caption with custom instruction
|
| 467 |
+
caption_videos.py video.mp4 -o captions.json -i "Describe this video in detail"
|
| 468 |
+
"""
|
| 469 |
+
|
| 470 |
+
# Parallel workers are only supported for the cloud Gemini backend; qwen_omni
|
| 471 |
+
# drives a single shared vLLM server and is captioned serially from here.
|
| 472 |
+
if num_workers > 1 and captioner_type != CaptionerType.GEMINI_FLASH:
|
| 473 |
+
console.print(
|
| 474 |
+
"[bold red]Error:[/] --num-workers > 1 is only supported with "
|
| 475 |
+
"[bold]--captioner-type gemini_flash[/]. Use --num-workers 1 (default) "
|
| 476 |
+
"for the qwen_omni backend."
|
| 477 |
+
)
|
| 478 |
+
raise typer.Exit(code=1)
|
| 479 |
+
|
| 480 |
+
# Parse extensions
|
| 481 |
+
ext_list = [ext.strip() for ext in extensions.split(",")]
|
| 482 |
+
|
| 483 |
+
# Determine output path and format
|
| 484 |
+
if output is None:
|
| 485 |
+
output_format = OutputFormat.JSON
|
| 486 |
+
if input_path.is_file(): # noqa: SIM108
|
| 487 |
+
# Default to a JSON file with the same name as the input media
|
| 488 |
+
output = input_path.with_suffix(".dataset.json")
|
| 489 |
+
else:
|
| 490 |
+
# Default to a JSON file in the input directory
|
| 491 |
+
output = input_path / "dataset.json"
|
| 492 |
+
else:
|
| 493 |
+
# Determine format from file extension
|
| 494 |
+
output_format = OutputFormat(Path(output).suffix.lstrip(".").lower())
|
| 495 |
+
|
| 496 |
+
# Ensure output path is absolute
|
| 497 |
+
output = Path(output).resolve()
|
| 498 |
+
console.print(f"Output will be saved to [bold blue]{output}[/]")
|
| 499 |
+
|
| 500 |
+
with console.status("Initializing captioner...", spinner="dots"):
|
| 501 |
+
if captioner_type == CaptionerType.QWEN_OMNI:
|
| 502 |
+
captioner = create_captioner(
|
| 503 |
+
captioner_type=captioner_type,
|
| 504 |
+
base_url=vllm_url,
|
| 505 |
+
model=vllm_model,
|
| 506 |
+
instruction=instruction,
|
| 507 |
+
max_tokens=max_tokens,
|
| 508 |
+
enable_thinking=enable_thinking,
|
| 509 |
+
)
|
| 510 |
+
elif captioner_type == CaptionerType.GEMINI_FLASH:
|
| 511 |
+
captioner = create_captioner(
|
| 512 |
+
captioner_type=captioner_type,
|
| 513 |
+
api_key=api_key,
|
| 514 |
+
instruction=instruction,
|
| 515 |
+
)
|
| 516 |
+
else:
|
| 517 |
+
raise ValueError(f"Unsupported captioner type: {captioner_type}")
|
| 518 |
+
|
| 519 |
+
console.print(f"[bold green]✓[/] {captioner_type.value} captioner ready")
|
| 520 |
+
|
| 521 |
+
# Caption media files
|
| 522 |
+
caption_media(
|
| 523 |
+
input_path=input_path,
|
| 524 |
+
output_path=output,
|
| 525 |
+
captioner=captioner,
|
| 526 |
+
extensions=ext_list,
|
| 527 |
+
recursive=recursive,
|
| 528 |
+
fps=fps,
|
| 529 |
+
output_format=output_format,
|
| 530 |
+
override=override,
|
| 531 |
+
num_workers=num_workers,
|
| 532 |
+
)
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
if __name__ == "__main__":
|
| 536 |
+
app()
|
packages/ltx-trainer/scripts/compute_reference.py
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Compute reference videos for IC-LoRA training.
|
| 3 |
+
This script provides a command-line interface for generating reference videos to be used for IC-LoRA training.
|
| 4 |
+
Note that it reads and writes to the same file (the output of caption_videos.py),
|
| 5 |
+
where it adds the "reference_video" field to the JSON.
|
| 6 |
+
Basic usage:
|
| 7 |
+
# Compute reference videos for all videos in a directory
|
| 8 |
+
compute_reference.py videos_dir/ --output videos_dir/captions.json
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
# Standard library imports
|
| 12 |
+
import json
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
from typing import Any
|
| 15 |
+
|
| 16 |
+
# Third-party imports
|
| 17 |
+
import cv2
|
| 18 |
+
import torch
|
| 19 |
+
import torchvision.transforms.functional as TF # noqa: N812
|
| 20 |
+
import typer
|
| 21 |
+
from rich.console import Console
|
| 22 |
+
from rich.progress import (
|
| 23 |
+
BarColumn,
|
| 24 |
+
MofNCompleteColumn,
|
| 25 |
+
Progress,
|
| 26 |
+
SpinnerColumn,
|
| 27 |
+
TextColumn,
|
| 28 |
+
TimeElapsedColumn,
|
| 29 |
+
TimeRemainingColumn,
|
| 30 |
+
)
|
| 31 |
+
from transformers.utils.logging import disable_progress_bar
|
| 32 |
+
|
| 33 |
+
# Local imports
|
| 34 |
+
from ltx_trainer.video_utils import read_video, save_video
|
| 35 |
+
|
| 36 |
+
# Initialize console and disable progress bars
|
| 37 |
+
console = Console()
|
| 38 |
+
disable_progress_bar()
|
| 39 |
+
|
| 40 |
+
VIDEO_COLUMNS = ("video", "media_path")
|
| 41 |
+
REFERENCE_VIDEO_COLUMN = "reference_video"
|
| 42 |
+
LEGACY_REFERENCE_COLUMN = "reference_path"
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def compute_reference(
|
| 46 |
+
images: torch.Tensor,
|
| 47 |
+
) -> torch.Tensor:
|
| 48 |
+
"""Compute Canny edge detection on a batch of images.
|
| 49 |
+
Args:
|
| 50 |
+
images: Batch of images tensor of shape [B, C, H, W]
|
| 51 |
+
Returns:
|
| 52 |
+
Binary edge masks tensor of shape [B, H, W]
|
| 53 |
+
"""
|
| 54 |
+
# Convert to grayscale if needed
|
| 55 |
+
if images.shape[1] == 3:
|
| 56 |
+
images = TF.rgb_to_grayscale(images)
|
| 57 |
+
|
| 58 |
+
# Ensure images are in [0, 1] range
|
| 59 |
+
if images.max() > 1.0:
|
| 60 |
+
images = images / 255.0
|
| 61 |
+
|
| 62 |
+
# Compute Canny edges
|
| 63 |
+
edge_masks = []
|
| 64 |
+
for image in images:
|
| 65 |
+
# Convert to numpy for OpenCV
|
| 66 |
+
image_np = (image.squeeze().cpu().numpy() * 255).astype("uint8")
|
| 67 |
+
|
| 68 |
+
# Apply Canny edge detection
|
| 69 |
+
edges = cv2.Canny(
|
| 70 |
+
image_np,
|
| 71 |
+
threshold1=100,
|
| 72 |
+
threshold2=200,
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
# Convert back to tensor
|
| 76 |
+
edge_mask = torch.from_numpy(edges).float()
|
| 77 |
+
edge_masks.append(edge_mask)
|
| 78 |
+
|
| 79 |
+
edges = torch.stack(edge_masks)
|
| 80 |
+
edges = torch.stack([edges] * 3, dim=1) # Convert to 3-channel
|
| 81 |
+
return edges
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def _get_meta_data(
|
| 85 |
+
output_path: Path,
|
| 86 |
+
) -> list[dict[str, Any]]:
|
| 87 |
+
"""Get set of existing reference video paths without loading the actual files.
|
| 88 |
+
Args:
|
| 89 |
+
output_path: Path to the reference video paths file
|
| 90 |
+
Returns:
|
| 91 |
+
Dataset rows with media paths and captions
|
| 92 |
+
"""
|
| 93 |
+
if not output_path.exists():
|
| 94 |
+
return []
|
| 95 |
+
|
| 96 |
+
console.print(f"[bold blue]Reading meta data from [cyan]{output_path}[/]...[/]")
|
| 97 |
+
|
| 98 |
+
try:
|
| 99 |
+
with output_path.open("r", encoding="utf-8") as f:
|
| 100 |
+
json_data = json.load(f)
|
| 101 |
+
return json_data
|
| 102 |
+
|
| 103 |
+
except Exception as e:
|
| 104 |
+
console.print(f"[bold yellow]Warning: Could not check meta data: {e}[/]")
|
| 105 |
+
return []
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def _get_media_path(item: dict[str, Any]) -> str:
|
| 109 |
+
for column in VIDEO_COLUMNS:
|
| 110 |
+
if column in item:
|
| 111 |
+
return item[column]
|
| 112 |
+
raise KeyError(f"Dataset row must contain one of {VIDEO_COLUMNS}")
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def _save_dataset_json(
|
| 116 |
+
reference_paths: dict[str, str],
|
| 117 |
+
output_path: Path,
|
| 118 |
+
) -> None:
|
| 119 |
+
"""Save dataset json with reference video paths.
|
| 120 |
+
Args:
|
| 121 |
+
reference_paths: Dictionary mapping media paths to reference video paths
|
| 122 |
+
output_path: Path to save the output file
|
| 123 |
+
"""
|
| 124 |
+
|
| 125 |
+
with output_path.open("r", encoding="utf-8") as f:
|
| 126 |
+
json_data = json.load(f)
|
| 127 |
+
new_json_data = json_data.copy()
|
| 128 |
+
for i, item in enumerate(json_data):
|
| 129 |
+
media_path = _get_media_path(item)
|
| 130 |
+
reference_path = reference_paths[media_path]
|
| 131 |
+
new_json_data[i].pop(LEGACY_REFERENCE_COLUMN, None)
|
| 132 |
+
new_json_data[i][REFERENCE_VIDEO_COLUMN] = reference_path
|
| 133 |
+
|
| 134 |
+
with output_path.open("w", encoding="utf-8") as f:
|
| 135 |
+
json.dump(new_json_data, f, indent=2, ensure_ascii=False)
|
| 136 |
+
|
| 137 |
+
console.print(f"[bold green]✓[/] Reference video paths saved to [cyan]{output_path}[/]")
|
| 138 |
+
console.print("[bold yellow]Note:[/] Reference videos were written to the '[cyan]reference_video[/]' column.")
|
| 139 |
+
console.print(" [cyan]process_dataset.py[/] detects this column automatically for IC-LoRA preprocessing.")
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def process_media(
|
| 143 |
+
input_path: Path,
|
| 144 |
+
output_path: Path,
|
| 145 |
+
override: bool,
|
| 146 |
+
batch_size: int = 100,
|
| 147 |
+
) -> None:
|
| 148 |
+
"""Process videos and images to compute condition on videos.
|
| 149 |
+
Args:
|
| 150 |
+
input_path: Path to input video/image file or directory
|
| 151 |
+
output_path: Path to output reference video file
|
| 152 |
+
override: Whether to override existing reference video files
|
| 153 |
+
"""
|
| 154 |
+
if not output_path.exists():
|
| 155 |
+
raise FileNotFoundError(
|
| 156 |
+
f"Output file does not exist: {output_path}. This is also the input file for the dataset."
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
# Check for existing reference video files
|
| 160 |
+
meta_data = _get_meta_data(output_path)
|
| 161 |
+
|
| 162 |
+
base_dir = input_path.resolve()
|
| 163 |
+
console.print(f"Using [bold blue]{base_dir}[/] as base directory for relative paths")
|
| 164 |
+
|
| 165 |
+
# Filter media files
|
| 166 |
+
media_to_process = []
|
| 167 |
+
skipped_media = []
|
| 168 |
+
|
| 169 |
+
def media_path_to_reference_path(media_file: Path) -> Path:
|
| 170 |
+
return media_file.parent / (media_file.stem + "_reference" + media_file.suffix)
|
| 171 |
+
|
| 172 |
+
media_files = [base_dir / Path(_get_media_path(sample)) for sample in meta_data]
|
| 173 |
+
for media_file in media_files:
|
| 174 |
+
reference_path = media_path_to_reference_path(media_file)
|
| 175 |
+
media_to_process.append(media_file)
|
| 176 |
+
|
| 177 |
+
console.print(f"Processing [bold]{len(media_to_process)}[/] media.")
|
| 178 |
+
|
| 179 |
+
# Initialize progress tracking
|
| 180 |
+
progress = Progress(
|
| 181 |
+
SpinnerColumn(),
|
| 182 |
+
TextColumn("{task.description}"),
|
| 183 |
+
BarColumn(bar_width=40),
|
| 184 |
+
MofNCompleteColumn(),
|
| 185 |
+
TimeElapsedColumn(),
|
| 186 |
+
TextColumn("•"),
|
| 187 |
+
TimeRemainingColumn(),
|
| 188 |
+
console=console,
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
# Process media files
|
| 192 |
+
media_paths = [_get_media_path(item) for item in meta_data]
|
| 193 |
+
reference_paths = {rel_path: str(media_path_to_reference_path(Path(rel_path))) for rel_path in media_paths}
|
| 194 |
+
|
| 195 |
+
with progress:
|
| 196 |
+
task = progress.add_task("Computing condition on videos", total=len(media_to_process))
|
| 197 |
+
|
| 198 |
+
for media_file, rel_path in zip(media_to_process, media_paths, strict=True):
|
| 199 |
+
progress.update(task, description=f"Processing [bold blue]{media_file.name}[/]")
|
| 200 |
+
|
| 201 |
+
# Key by the original media-path string (matches the dict seeded above). Avoid
|
| 202 |
+
# resolve()/relative_to here — they crash on symlinked or absolute media paths.
|
| 203 |
+
reference_path = media_path_to_reference_path(media_file)
|
| 204 |
+
try:
|
| 205 |
+
ref_stored = str(reference_path.relative_to(base_dir))
|
| 206 |
+
except ValueError:
|
| 207 |
+
ref_stored = str(reference_path) # absolute/out-of-tree: keep it next to the source
|
| 208 |
+
reference_paths[rel_path] = ref_stored
|
| 209 |
+
|
| 210 |
+
if not reference_path.resolve().exists() or override:
|
| 211 |
+
try:
|
| 212 |
+
video, fps = read_video(media_file)
|
| 213 |
+
|
| 214 |
+
# Process frames in batches
|
| 215 |
+
condition_frames = []
|
| 216 |
+
|
| 217 |
+
for i in range(0, len(video), batch_size):
|
| 218 |
+
batch = video[i : i + batch_size]
|
| 219 |
+
condition_batch = compute_reference(batch)
|
| 220 |
+
condition_frames.append(condition_batch)
|
| 221 |
+
|
| 222 |
+
# Concatenate all edge frames
|
| 223 |
+
all_condition = torch.cat(condition_frames, dim=0)
|
| 224 |
+
|
| 225 |
+
# Save the edge video
|
| 226 |
+
save_video(all_condition, reference_path.resolve(), fps=fps)
|
| 227 |
+
|
| 228 |
+
except Exception as e:
|
| 229 |
+
console.print(f"[bold red]Error processing [bold blue]{media_file}[/]: {e}[/]")
|
| 230 |
+
reference_paths.pop(rel_path)
|
| 231 |
+
else:
|
| 232 |
+
skipped_media.append(media_file)
|
| 233 |
+
|
| 234 |
+
progress.advance(task)
|
| 235 |
+
|
| 236 |
+
# Save results
|
| 237 |
+
_save_dataset_json(reference_paths, output_path)
|
| 238 |
+
|
| 239 |
+
# Print summary
|
| 240 |
+
total_to_process = len(media_files) - len(skipped_media)
|
| 241 |
+
console.print(
|
| 242 |
+
f"[bold green]✓[/] Processed [bold]{total_to_process}/{len(media_files)}[/] media successfully.",
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
app = typer.Typer(
|
| 247 |
+
pretty_exceptions_enable=False,
|
| 248 |
+
no_args_is_help=True,
|
| 249 |
+
help="Compute reference videos for IC-LoRA training.",
|
| 250 |
+
)
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
@app.command()
|
| 254 |
+
def main(
|
| 255 |
+
input_path: Path = typer.Argument( # noqa: B008
|
| 256 |
+
...,
|
| 257 |
+
help="Path to input video/image file or directory containing media files",
|
| 258 |
+
exists=True,
|
| 259 |
+
),
|
| 260 |
+
output: Path = typer.Option( # noqa: B008
|
| 261 |
+
...,
|
| 262 |
+
"--output",
|
| 263 |
+
"-o",
|
| 264 |
+
help="Path to json output file for reference video paths. "
|
| 265 |
+
"This is also the input file for the dataset, the output of compute_captions.py.",
|
| 266 |
+
),
|
| 267 |
+
override: bool = typer.Option(
|
| 268 |
+
False,
|
| 269 |
+
"--override",
|
| 270 |
+
help="Whether to override existing reference video files",
|
| 271 |
+
),
|
| 272 |
+
batch_size: int = typer.Option(
|
| 273 |
+
100,
|
| 274 |
+
"--batch-size",
|
| 275 |
+
help="Batch size for processing videos",
|
| 276 |
+
),
|
| 277 |
+
) -> None:
|
| 278 |
+
"""Compute reference videos for IC-LoRA training.
|
| 279 |
+
This script generates reference videos (e.g., Canny edge maps) for given videos.
|
| 280 |
+
The paths in the output file will be relative to the output file's directory.
|
| 281 |
+
Examples:
|
| 282 |
+
# Process all videos in a directory
|
| 283 |
+
compute_reference.py videos_dir/ -o videos_dir/captions.json
|
| 284 |
+
"""
|
| 285 |
+
|
| 286 |
+
# Ensure output path is absolute
|
| 287 |
+
output = Path(output).resolve()
|
| 288 |
+
console.print(f"Output will be saved to [bold blue]{output}[/]")
|
| 289 |
+
|
| 290 |
+
# Verify output path exists
|
| 291 |
+
if not output.exists():
|
| 292 |
+
raise FileNotFoundError(f"Output file does not exist: {output}. This is also the input file for the dataset.")
|
| 293 |
+
|
| 294 |
+
# Process media files
|
| 295 |
+
process_media(
|
| 296 |
+
input_path=input_path,
|
| 297 |
+
output_path=output,
|
| 298 |
+
override=override,
|
| 299 |
+
batch_size=batch_size,
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
if __name__ == "__main__":
|
| 304 |
+
app()
|
packages/ltx-trainer/scripts/decode_latents.py
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
Decode precomputed video latents back into videos using the VAE.
|
| 5 |
+
This script loads latent files saved during preprocessing and decodes them
|
| 6 |
+
back into video clips using the same VAE model.
|
| 7 |
+
Basic usage:
|
| 8 |
+
python scripts/decode_latents.py /path/to/latents/dir /path/to/output \
|
| 9 |
+
--model-source /path/to/ltx2.safetensors
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
import torchaudio
|
| 16 |
+
import torchvision.utils
|
| 17 |
+
import typer
|
| 18 |
+
from einops import rearrange
|
| 19 |
+
from rich.console import Console
|
| 20 |
+
from rich.progress import (
|
| 21 |
+
BarColumn,
|
| 22 |
+
MofNCompleteColumn,
|
| 23 |
+
Progress,
|
| 24 |
+
SpinnerColumn,
|
| 25 |
+
TextColumn,
|
| 26 |
+
TimeElapsedColumn,
|
| 27 |
+
TimeRemainingColumn,
|
| 28 |
+
)
|
| 29 |
+
from transformers.utils.logging import disable_progress_bar
|
| 30 |
+
|
| 31 |
+
from ltx_core.model.video_vae import SpatialTilingConfig, TemporalTilingConfig, TilingConfig
|
| 32 |
+
from ltx_trainer import logger
|
| 33 |
+
from ltx_trainer.model_loader import load_audio_vae_decoder, load_video_vae_decoder, load_vocoder
|
| 34 |
+
from ltx_trainer.video_utils import save_video
|
| 35 |
+
|
| 36 |
+
DEFAULT_TILE_SIZE_PIXELS = 512 # Spatial tile size in pixels (must be ≥64 and divisible by 32)
|
| 37 |
+
DEFAULT_TILE_OVERLAP_PIXELS = 128 # Spatial tile overlap in pixels (must be divisible by 32)
|
| 38 |
+
DEFAULT_TILE_SIZE_FRAMES = 128 # Temporal tile size in frames (must be ≥16 and divisible by 8)
|
| 39 |
+
DEFAULT_TILE_OVERLAP_FRAMES = 24 # Temporal tile overlap in frames (must be divisible by 8)
|
| 40 |
+
|
| 41 |
+
disable_progress_bar()
|
| 42 |
+
console = Console()
|
| 43 |
+
app = typer.Typer(
|
| 44 |
+
pretty_exceptions_enable=False,
|
| 45 |
+
no_args_is_help=True,
|
| 46 |
+
help="Decode precomputed video latents back into videos using the VAE.",
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class LatentsDecoder:
|
| 51 |
+
def __init__(
|
| 52 |
+
self,
|
| 53 |
+
model_path: str,
|
| 54 |
+
device: str = "cuda",
|
| 55 |
+
vae_tiling: bool = False,
|
| 56 |
+
with_audio: bool = False,
|
| 57 |
+
):
|
| 58 |
+
"""Initialize the decoder with model configuration.
|
| 59 |
+
Args:
|
| 60 |
+
model_path: Path to LTX-2 checkpoint (.safetensors)
|
| 61 |
+
device: Device to use for computation
|
| 62 |
+
vae_tiling: Whether to enable VAE tiling for larger video resolutions
|
| 63 |
+
with_audio: Whether to load audio VAE for audio decoding
|
| 64 |
+
"""
|
| 65 |
+
self.device = torch.device(device)
|
| 66 |
+
self.model_path = model_path
|
| 67 |
+
self.vae = None
|
| 68 |
+
self.audio_vae = None
|
| 69 |
+
self.vocoder = None
|
| 70 |
+
self.vae_tiling = vae_tiling
|
| 71 |
+
|
| 72 |
+
self._load_model(model_path, with_audio)
|
| 73 |
+
|
| 74 |
+
def _load_model(self, model_path: str, with_audio: bool = False) -> None:
|
| 75 |
+
"""Initialize and load the VAE model(s)."""
|
| 76 |
+
with console.status(f"[bold]Loading video VAE decoder from {model_path}...", spinner="dots"):
|
| 77 |
+
self.vae = load_video_vae_decoder(model_path, device=self.device, dtype=torch.bfloat16)
|
| 78 |
+
|
| 79 |
+
if with_audio:
|
| 80 |
+
with console.status(f"[bold]Loading audio VAE decoder from {model_path}...", spinner="dots"):
|
| 81 |
+
self.audio_vae = load_audio_vae_decoder(model_path, device=self.device, dtype=torch.bfloat16)
|
| 82 |
+
|
| 83 |
+
with console.status(f"[bold]Loading vocoder from {model_path}...", spinner="dots"):
|
| 84 |
+
self.vocoder = load_vocoder(model_path, device=self.device)
|
| 85 |
+
|
| 86 |
+
@torch.inference_mode()
|
| 87 |
+
def decode(self, latents_dir: Path, output_dir: Path, seed: int | None = None) -> None:
|
| 88 |
+
"""Decode all latent files in the directory recursively.
|
| 89 |
+
Args:
|
| 90 |
+
latents_dir: Directory containing latent files (.pt)
|
| 91 |
+
output_dir: Directory to save decoded videos
|
| 92 |
+
seed: Optional random seed for noise generation
|
| 93 |
+
"""
|
| 94 |
+
# Find all .pt files recursively
|
| 95 |
+
latent_files = list(latents_dir.rglob("*.pt"))
|
| 96 |
+
|
| 97 |
+
if not latent_files:
|
| 98 |
+
logger.warning(f"No .pt files found in {latents_dir}")
|
| 99 |
+
return
|
| 100 |
+
|
| 101 |
+
logger.info(f"Found {len(latent_files):,} latent files to decode")
|
| 102 |
+
|
| 103 |
+
# Process files with progress bar
|
| 104 |
+
with Progress(
|
| 105 |
+
SpinnerColumn(),
|
| 106 |
+
TextColumn("[progress.description]{task.description}"),
|
| 107 |
+
BarColumn(),
|
| 108 |
+
MofNCompleteColumn(),
|
| 109 |
+
TimeElapsedColumn(),
|
| 110 |
+
TimeRemainingColumn(),
|
| 111 |
+
console=console,
|
| 112 |
+
) as progress:
|
| 113 |
+
task = progress.add_task("Decoding latents", total=len(latent_files))
|
| 114 |
+
|
| 115 |
+
for latent_file in latent_files:
|
| 116 |
+
# Calculate relative path to maintain directory structure
|
| 117 |
+
rel_path = latent_file.relative_to(latents_dir)
|
| 118 |
+
output_subdir = output_dir / rel_path.parent
|
| 119 |
+
output_subdir.mkdir(parents=True, exist_ok=True)
|
| 120 |
+
|
| 121 |
+
try:
|
| 122 |
+
self._process_file(latent_file, output_subdir, seed)
|
| 123 |
+
except Exception as e:
|
| 124 |
+
logger.error(f"Error processing {latent_file}: {e}")
|
| 125 |
+
continue
|
| 126 |
+
|
| 127 |
+
progress.advance(task)
|
| 128 |
+
|
| 129 |
+
logger.info(f"Decoding complete! Videos saved to {output_dir}")
|
| 130 |
+
|
| 131 |
+
@torch.inference_mode()
|
| 132 |
+
def decode_audio(self, latents_dir: Path, output_dir: Path) -> None:
|
| 133 |
+
"""Decode all audio latent files in the directory recursively.
|
| 134 |
+
Args:
|
| 135 |
+
latents_dir: Directory containing audio latent files (.pt)
|
| 136 |
+
output_dir: Directory to save decoded audio files
|
| 137 |
+
"""
|
| 138 |
+
# Check if audio VAE is loaded
|
| 139 |
+
if self.audio_vae is None or self.vocoder is None:
|
| 140 |
+
logger.warning("Audio VAE or vocoder not loaded. Skipping audio decoding.")
|
| 141 |
+
return
|
| 142 |
+
|
| 143 |
+
# Find all .pt files recursively
|
| 144 |
+
latent_files = list(latents_dir.rglob("*.pt"))
|
| 145 |
+
|
| 146 |
+
if not latent_files:
|
| 147 |
+
logger.warning(f"No .pt files found in {latents_dir}")
|
| 148 |
+
return
|
| 149 |
+
|
| 150 |
+
logger.info(f"Found {len(latent_files):,} audio latent files to decode")
|
| 151 |
+
|
| 152 |
+
# Process files with progress bar
|
| 153 |
+
with Progress(
|
| 154 |
+
SpinnerColumn(),
|
| 155 |
+
TextColumn("[progress.description]{task.description}"),
|
| 156 |
+
BarColumn(),
|
| 157 |
+
MofNCompleteColumn(),
|
| 158 |
+
TimeElapsedColumn(),
|
| 159 |
+
TimeRemainingColumn(),
|
| 160 |
+
console=console,
|
| 161 |
+
) as progress:
|
| 162 |
+
task = progress.add_task("Decoding audio latents", total=len(latent_files))
|
| 163 |
+
|
| 164 |
+
for latent_file in latent_files:
|
| 165 |
+
# Calculate relative path to maintain directory structure
|
| 166 |
+
rel_path = latent_file.relative_to(latents_dir)
|
| 167 |
+
output_subdir = output_dir / rel_path.parent
|
| 168 |
+
output_subdir.mkdir(parents=True, exist_ok=True)
|
| 169 |
+
|
| 170 |
+
try:
|
| 171 |
+
self._process_audio_file(latent_file, output_subdir)
|
| 172 |
+
except Exception as e:
|
| 173 |
+
logger.error(f"Error processing audio {latent_file}: {e}")
|
| 174 |
+
continue
|
| 175 |
+
|
| 176 |
+
progress.advance(task)
|
| 177 |
+
|
| 178 |
+
logger.info(f"Audio decoding complete! Audio files saved to {output_dir}")
|
| 179 |
+
|
| 180 |
+
def _process_file(self, latent_file: Path, output_dir: Path, seed: int | None) -> None:
|
| 181 |
+
"""Process a single latent file."""
|
| 182 |
+
# Load the latent data
|
| 183 |
+
data = torch.load(latent_file, map_location=self.device, weights_only=False)
|
| 184 |
+
|
| 185 |
+
# Get latents - handle both old patchified [seq_len, C] and new [C, F, H, W] formats
|
| 186 |
+
latents = data["latents"]
|
| 187 |
+
num_frames = data["num_frames"]
|
| 188 |
+
height = data["height"]
|
| 189 |
+
width = data["width"]
|
| 190 |
+
|
| 191 |
+
# Check if latents need reshaping (old patchified format)
|
| 192 |
+
if latents.dim() == 2:
|
| 193 |
+
# Old format: [seq_len, C] -> reshape to [C, F, H, W]
|
| 194 |
+
latents = rearrange(latents, "(f h w) c -> c f h w", f=num_frames, h=height, w=width)
|
| 195 |
+
|
| 196 |
+
# Add batch dimension: [C, F, H, W] -> [1, C, F, H, W]
|
| 197 |
+
latents = latents.unsqueeze(0).to(device=self.device, dtype=torch.bfloat16)
|
| 198 |
+
|
| 199 |
+
# Create generator only if seed is provided
|
| 200 |
+
generator = None
|
| 201 |
+
if seed is not None:
|
| 202 |
+
generator = torch.Generator(device=self.device)
|
| 203 |
+
generator.manual_seed(seed)
|
| 204 |
+
|
| 205 |
+
# Decode the video
|
| 206 |
+
video = self._decode_video(latents, generator)
|
| 207 |
+
|
| 208 |
+
# Determine output format and save
|
| 209 |
+
is_image = video.shape[0] == 1
|
| 210 |
+
if is_image:
|
| 211 |
+
# Save as PNG for single frame
|
| 212 |
+
output_path = output_dir / f"{latent_file.stem}.png"
|
| 213 |
+
torchvision.utils.save_image(
|
| 214 |
+
video[0], # [C, H, W] in [0, 1]
|
| 215 |
+
str(output_path),
|
| 216 |
+
)
|
| 217 |
+
else:
|
| 218 |
+
# Save as MP4 for video using PyAV-based save_video
|
| 219 |
+
output_path = output_dir / f"{latent_file.stem}.mp4"
|
| 220 |
+
fps = data.get("fps", 24) # Use stored FPS or default to 24
|
| 221 |
+
save_video(
|
| 222 |
+
video_tensor=video, # [F, C, H, W] in [0, 1]
|
| 223 |
+
output_path=output_path,
|
| 224 |
+
fps=fps,
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
def _decode_video(self, latents: torch.Tensor, generator: torch.Generator | None = None) -> torch.Tensor:
|
| 228 |
+
"""Decode latents to video frames."""
|
| 229 |
+
if self.vae_tiling:
|
| 230 |
+
# Use tiled decoding for reduced VRAM
|
| 231 |
+
tiling_config = TilingConfig(
|
| 232 |
+
spatial_config=SpatialTilingConfig(
|
| 233 |
+
tile_size_in_pixels=DEFAULT_TILE_SIZE_PIXELS,
|
| 234 |
+
tile_overlap_in_pixels=DEFAULT_TILE_OVERLAP_PIXELS,
|
| 235 |
+
),
|
| 236 |
+
temporal_config=TemporalTilingConfig(
|
| 237 |
+
tile_size_in_frames=DEFAULT_TILE_SIZE_FRAMES,
|
| 238 |
+
tile_overlap_in_frames=DEFAULT_TILE_OVERLAP_FRAMES,
|
| 239 |
+
),
|
| 240 |
+
)
|
| 241 |
+
chunks = list(
|
| 242 |
+
self.vae.tiled_decode(
|
| 243 |
+
latents,
|
| 244 |
+
tiling_config=tiling_config,
|
| 245 |
+
generator=generator,
|
| 246 |
+
)
|
| 247 |
+
)
|
| 248 |
+
# Concatenate along temporal dimension
|
| 249 |
+
video = torch.cat(chunks, dim=2) # [B, C, F, H, W]
|
| 250 |
+
else:
|
| 251 |
+
# Standard full decoding
|
| 252 |
+
video = self.vae(latents, generator=generator) # [B, C, F, H, W]
|
| 253 |
+
|
| 254 |
+
# Convert to [F, C, H, W] format and normalize to [0, 1]
|
| 255 |
+
video = rearrange(video, "1 c f h w -> f c h w")
|
| 256 |
+
video = (video + 1) / 2 # Denormalize from [-1, 1] to [0, 1]
|
| 257 |
+
video = video.clamp(0, 1)
|
| 258 |
+
|
| 259 |
+
return video
|
| 260 |
+
|
| 261 |
+
def _process_audio_file(self, latent_file: Path, output_dir: Path) -> None:
|
| 262 |
+
"""Process a single audio latent file."""
|
| 263 |
+
# Load the latent data
|
| 264 |
+
data = torch.load(latent_file, map_location=self.device, weights_only=False)
|
| 265 |
+
|
| 266 |
+
latents = data["latents"].to(device=self.device, dtype=torch.float32)
|
| 267 |
+
num_time_steps = data["num_time_steps"]
|
| 268 |
+
freq_bins = data["frequency_bins"]
|
| 269 |
+
|
| 270 |
+
# Handle both old patchified [seq_len, C] and new [C, T, F] formats
|
| 271 |
+
if latents.dim() == 2:
|
| 272 |
+
# Old format: [seq_len, channels] where seq_len = time * freq
|
| 273 |
+
# Reshape to [C, T, F]
|
| 274 |
+
latents = rearrange(latents, "(t f) c -> c t f", t=num_time_steps, f=freq_bins)
|
| 275 |
+
|
| 276 |
+
# Add batch dimension: [C, T, F] -> [1, C, T, F]
|
| 277 |
+
latents = latents.unsqueeze(0)
|
| 278 |
+
|
| 279 |
+
# Set correct dtype for audio VAE
|
| 280 |
+
latents = latents.to(dtype=torch.bfloat16)
|
| 281 |
+
|
| 282 |
+
# Decode audio using audio VAE decoder (produces mel spectrogram)
|
| 283 |
+
mel_spectrogram = self.audio_vae(latents)
|
| 284 |
+
|
| 285 |
+
# Convert mel spectrogram to waveform using vocoder
|
| 286 |
+
waveform = self.vocoder(mel_spectrogram)
|
| 287 |
+
|
| 288 |
+
# Save as WAV
|
| 289 |
+
output_path = output_dir / f"{latent_file.stem}.wav"
|
| 290 |
+
sample_rate = self.vocoder.output_sampling_rate
|
| 291 |
+
torchaudio.save(str(output_path), waveform[0].cpu(), sample_rate)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
@app.command()
|
| 295 |
+
def main(
|
| 296 |
+
latents_dir: str = typer.Argument(
|
| 297 |
+
...,
|
| 298 |
+
help="Directory containing the precomputed latent files (searched recursively)",
|
| 299 |
+
),
|
| 300 |
+
output_dir: str = typer.Argument(
|
| 301 |
+
...,
|
| 302 |
+
help="Directory to save the decoded videos (maintains same folder hierarchy as input)",
|
| 303 |
+
),
|
| 304 |
+
model_path: str = typer.Option(
|
| 305 |
+
...,
|
| 306 |
+
help="Path to LTX-2 checkpoint (.safetensors file)",
|
| 307 |
+
),
|
| 308 |
+
device: str = typer.Option(
|
| 309 |
+
default="cuda",
|
| 310 |
+
help="Device to use for computation",
|
| 311 |
+
),
|
| 312 |
+
vae_tiling: bool = typer.Option(
|
| 313 |
+
default=True,
|
| 314 |
+
help="Enable VAE tiling for larger video resolutions",
|
| 315 |
+
),
|
| 316 |
+
seed: int | None = typer.Option(
|
| 317 |
+
default=None,
|
| 318 |
+
help="Random seed for noise generation during decoding",
|
| 319 |
+
),
|
| 320 |
+
with_audio: bool = typer.Option(
|
| 321 |
+
default=False,
|
| 322 |
+
help="Also decode audio latents (requires audio_latents directory)",
|
| 323 |
+
),
|
| 324 |
+
audio_latents_dir: str | None = typer.Option(
|
| 325 |
+
default=None,
|
| 326 |
+
help="Directory containing audio latent files (defaults to 'audio_latents' sibling of latents_dir)",
|
| 327 |
+
),
|
| 328 |
+
) -> None:
|
| 329 |
+
"""Decode precomputed video latents back into videos using the VAE.
|
| 330 |
+
This script recursively searches for .pt latent files in the input directory
|
| 331 |
+
and decodes them to videos, maintaining the same folder hierarchy in the output.
|
| 332 |
+
Examples:
|
| 333 |
+
# Basic usage
|
| 334 |
+
python scripts/decode_latents.py /path/to/latents /path/to/videos \\
|
| 335 |
+
--model-path /path/to/ltx2.safetensors
|
| 336 |
+
# With VAE tiling for large videos
|
| 337 |
+
python scripts/decode_latents.py /path/to/latents /path/to/videos \\
|
| 338 |
+
--model-path /path/to/ltx2.safetensors --vae-tiling
|
| 339 |
+
# With audio decoding
|
| 340 |
+
python scripts/decode_latents.py /path/to/latents /path/to/videos \\
|
| 341 |
+
--model-path /path/to/ltx2.safetensors --with-audio
|
| 342 |
+
"""
|
| 343 |
+
latents_path = Path(latents_dir)
|
| 344 |
+
output_path = Path(output_dir)
|
| 345 |
+
|
| 346 |
+
if not latents_path.exists() or not latents_path.is_dir():
|
| 347 |
+
raise typer.BadParameter(f"Latents directory does not exist: {latents_path}")
|
| 348 |
+
|
| 349 |
+
decoder = LatentsDecoder(
|
| 350 |
+
model_path=model_path,
|
| 351 |
+
device=device,
|
| 352 |
+
vae_tiling=vae_tiling,
|
| 353 |
+
with_audio=with_audio,
|
| 354 |
+
)
|
| 355 |
+
decoder.decode(latents_path, output_path, seed=seed)
|
| 356 |
+
|
| 357 |
+
# Decode audio if requested
|
| 358 |
+
if with_audio:
|
| 359 |
+
audio_path = Path(audio_latents_dir) if audio_latents_dir else latents_path.parent / "audio_latents"
|
| 360 |
+
|
| 361 |
+
if audio_path.exists():
|
| 362 |
+
audio_output_path = output_path.parent / "decoded_audio"
|
| 363 |
+
decoder.decode_audio(audio_path, audio_output_path)
|
| 364 |
+
else:
|
| 365 |
+
logger.warning(f"Audio latents directory not found: {audio_path}")
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
if __name__ == "__main__":
|
| 369 |
+
app()
|
packages/ltx-trainer/scripts/process_captions.py
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
Compute text embeddings for video generation training.
|
| 5 |
+
This module provides functionality for processing text captions, including:
|
| 6 |
+
- Loading captions from various file formats (CSV, JSON, JSONL)
|
| 7 |
+
- Cleaning and preprocessing text (removing LLM prefixes, adding ID tokens)
|
| 8 |
+
- CaptionsDataset for caption-only preprocessing workflows
|
| 9 |
+
Can be used as a standalone script:
|
| 10 |
+
python scripts/process_captions.py dataset.json --output-dir /path/to/output \
|
| 11 |
+
--model-source /path/to/ltx2.safetensors --text-encoder-path /path/to/gemma
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
import json
|
| 15 |
+
import os
|
| 16 |
+
from collections.abc import Callable
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
from typing import Any
|
| 19 |
+
|
| 20 |
+
import pandas as pd
|
| 21 |
+
import torch
|
| 22 |
+
import typer
|
| 23 |
+
from accelerate import PartialState
|
| 24 |
+
from rich.console import Console
|
| 25 |
+
from rich.progress import (
|
| 26 |
+
BarColumn,
|
| 27 |
+
MofNCompleteColumn,
|
| 28 |
+
Progress,
|
| 29 |
+
SpinnerColumn,
|
| 30 |
+
TaskProgressColumn,
|
| 31 |
+
TextColumn,
|
| 32 |
+
TimeElapsedColumn,
|
| 33 |
+
TimeRemainingColumn,
|
| 34 |
+
)
|
| 35 |
+
from torch.utils.data import DataLoader, Dataset, Subset
|
| 36 |
+
from transformers.utils.logging import disable_progress_bar
|
| 37 |
+
|
| 38 |
+
from ltx_trainer import logger
|
| 39 |
+
from ltx_trainer.model_loader import load_embeddings_processor, load_text_encoder
|
| 40 |
+
|
| 41 |
+
# Disable tokenizers parallelism to avoid warnings
|
| 42 |
+
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
| 43 |
+
|
| 44 |
+
disable_progress_bar()
|
| 45 |
+
|
| 46 |
+
# Common phrases that LLMs often add to captions that we might want to remove
|
| 47 |
+
COMMON_BEGINNING_PHRASES: tuple[str, ...] = (
|
| 48 |
+
"This video",
|
| 49 |
+
"The video",
|
| 50 |
+
"This clip",
|
| 51 |
+
"The clip",
|
| 52 |
+
"The animation",
|
| 53 |
+
"This image",
|
| 54 |
+
"The image",
|
| 55 |
+
"This picture",
|
| 56 |
+
"The picture",
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
COMMON_CONTINUATION_WORDS: tuple[str, ...] = (
|
| 60 |
+
"shows",
|
| 61 |
+
"depicts",
|
| 62 |
+
"features",
|
| 63 |
+
"captures",
|
| 64 |
+
"highlights",
|
| 65 |
+
"introduces",
|
| 66 |
+
"presents",
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
COMMON_LLM_START_PHRASES: tuple[str, ...] = (
|
| 70 |
+
"In the video,",
|
| 71 |
+
"In this video,",
|
| 72 |
+
"In this video clip,",
|
| 73 |
+
"In the clip,",
|
| 74 |
+
"Caption:",
|
| 75 |
+
*(
|
| 76 |
+
f"{beginning} {continuation}"
|
| 77 |
+
for beginning in COMMON_BEGINNING_PHRASES
|
| 78 |
+
for continuation in COMMON_CONTINUATION_WORDS
|
| 79 |
+
),
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
app = typer.Typer(
|
| 83 |
+
pretty_exceptions_enable=False,
|
| 84 |
+
no_args_is_help=True,
|
| 85 |
+
help="Process text captions and save embeddings for video generation training.",
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
class CaptionsDataset(Dataset):
|
| 90 |
+
"""
|
| 91 |
+
Dataset for processing text captions only.
|
| 92 |
+
This dataset is designed for caption preprocessing workflows where you only need
|
| 93 |
+
to process text without loading videos. Useful for:
|
| 94 |
+
- Precomputing text embeddings
|
| 95 |
+
- Caption cleaning and preprocessing
|
| 96 |
+
- Text-only preprocessing pipelines
|
| 97 |
+
"""
|
| 98 |
+
|
| 99 |
+
def __init__(
|
| 100 |
+
self,
|
| 101 |
+
dataset_file: str | Path,
|
| 102 |
+
caption_column: str,
|
| 103 |
+
media_column: str = "media_path",
|
| 104 |
+
lora_trigger: str | None = None,
|
| 105 |
+
remove_llm_prefixes: bool = False,
|
| 106 |
+
) -> None:
|
| 107 |
+
"""
|
| 108 |
+
Initialize the captions dataset.
|
| 109 |
+
Args:
|
| 110 |
+
dataset_file: Path to CSV/JSON/JSONL metadata file
|
| 111 |
+
caption_column: Column name for captions in the metadata file
|
| 112 |
+
media_column: Column name for media paths (used for output naming)
|
| 113 |
+
lora_trigger: Optional trigger word to prepend to each caption
|
| 114 |
+
remove_llm_prefixes: Whether to remove common LLM-generated prefixes
|
| 115 |
+
"""
|
| 116 |
+
super().__init__()
|
| 117 |
+
|
| 118 |
+
self.dataset_file = Path(dataset_file)
|
| 119 |
+
self.caption_column = caption_column
|
| 120 |
+
self.media_column = media_column
|
| 121 |
+
self.lora_trigger = f"{lora_trigger.strip()} " if lora_trigger else ""
|
| 122 |
+
|
| 123 |
+
# Load captions with their corresponding output embedding paths
|
| 124 |
+
self.caption_data = self._load_caption_data()
|
| 125 |
+
|
| 126 |
+
# Convert to lists for indexing
|
| 127 |
+
self.output_paths = list(self.caption_data.keys())
|
| 128 |
+
self.prompts = list(self.caption_data.values())
|
| 129 |
+
|
| 130 |
+
# Clean LLM start phrases if requested
|
| 131 |
+
if remove_llm_prefixes:
|
| 132 |
+
self._clean_llm_prefixes()
|
| 133 |
+
|
| 134 |
+
def __len__(self) -> int:
|
| 135 |
+
return len(self.prompts)
|
| 136 |
+
|
| 137 |
+
def __getitem__(self, index: int) -> dict[str, Any]:
|
| 138 |
+
"""Get a single caption with optional trigger word prepended and output path."""
|
| 139 |
+
prompt = self.lora_trigger + self.prompts[index]
|
| 140 |
+
return {
|
| 141 |
+
"prompt": prompt,
|
| 142 |
+
"output_path": self.output_paths[index],
|
| 143 |
+
"index": index,
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
def _load_caption_data(self) -> dict[str, str]:
|
| 147 |
+
"""Load captions and compute their output embedding paths."""
|
| 148 |
+
if self.dataset_file.suffix == ".csv":
|
| 149 |
+
return self._load_caption_data_from_csv()
|
| 150 |
+
elif self.dataset_file.suffix == ".json":
|
| 151 |
+
return self._load_caption_data_from_json()
|
| 152 |
+
elif self.dataset_file.suffix == ".jsonl":
|
| 153 |
+
return self._load_caption_data_from_jsonl()
|
| 154 |
+
else:
|
| 155 |
+
raise ValueError("Expected `dataset_file` to be a path to a CSV, JSON, or JSONL file.")
|
| 156 |
+
|
| 157 |
+
def _embedding_output_path(self, media_path: Path) -> str:
|
| 158 |
+
"""Output `.pt` path relative to the dataset dir; mirrors `process_videos._output_relative`
|
| 159 |
+
so caption keys match video/audio latent keys (and absolute paths don't escape output_dir)."""
|
| 160 |
+
data_root = self.dataset_file.parent
|
| 161 |
+
resolved = data_root / media_path # pathlib: an absolute media_path overrides data_root
|
| 162 |
+
try:
|
| 163 |
+
rel = resolved.relative_to(data_root)
|
| 164 |
+
except ValueError:
|
| 165 |
+
rel = Path(*resolved.parts[1:]) if resolved.is_absolute() else resolved
|
| 166 |
+
return str(rel.with_suffix(".pt"))
|
| 167 |
+
|
| 168 |
+
def _load_caption_data_from_csv(self) -> dict[str, str]:
|
| 169 |
+
"""Load captions from a CSV file and compute output embedding paths."""
|
| 170 |
+
df = pd.read_csv(self.dataset_file)
|
| 171 |
+
|
| 172 |
+
if self.caption_column not in df.columns:
|
| 173 |
+
raise ValueError(f"Column '{self.caption_column}' not found in CSV file")
|
| 174 |
+
if self.media_column not in df.columns:
|
| 175 |
+
raise ValueError(f"Column '{self.media_column}' not found in CSV file")
|
| 176 |
+
|
| 177 |
+
caption_data = {}
|
| 178 |
+
for _, row in df.iterrows():
|
| 179 |
+
media_path = Path(row[self.media_column].strip())
|
| 180 |
+
output_path = self._embedding_output_path(media_path)
|
| 181 |
+
caption_data[output_path] = row[self.caption_column]
|
| 182 |
+
|
| 183 |
+
return caption_data
|
| 184 |
+
|
| 185 |
+
def _load_caption_data_from_json(self) -> dict[str, str]:
|
| 186 |
+
"""Load captions from a JSON file and compute output embedding paths."""
|
| 187 |
+
with open(self.dataset_file, "r", encoding="utf-8") as file:
|
| 188 |
+
data = json.load(file)
|
| 189 |
+
|
| 190 |
+
if not isinstance(data, list):
|
| 191 |
+
raise ValueError("JSON file must contain a list of objects")
|
| 192 |
+
|
| 193 |
+
caption_data = {}
|
| 194 |
+
for entry in data:
|
| 195 |
+
if self.caption_column not in entry:
|
| 196 |
+
raise ValueError(f"Key '{self.caption_column}' not found in JSON entry: {entry}")
|
| 197 |
+
if self.media_column not in entry:
|
| 198 |
+
raise ValueError(f"Key '{self.media_column}' not found in JSON entry: {entry}")
|
| 199 |
+
|
| 200 |
+
media_path = Path(entry[self.media_column].strip())
|
| 201 |
+
output_path = self._embedding_output_path(media_path)
|
| 202 |
+
caption_data[output_path] = entry[self.caption_column]
|
| 203 |
+
|
| 204 |
+
return caption_data
|
| 205 |
+
|
| 206 |
+
def _load_caption_data_from_jsonl(self) -> dict[str, str]:
|
| 207 |
+
"""Load captions from a JSONL file and compute output embedding paths."""
|
| 208 |
+
caption_data = {}
|
| 209 |
+
with open(self.dataset_file, "r", encoding="utf-8") as file:
|
| 210 |
+
for line in file:
|
| 211 |
+
entry = json.loads(line)
|
| 212 |
+
if self.caption_column not in entry:
|
| 213 |
+
raise ValueError(f"Key '{self.caption_column}' not found in JSONL entry: {entry}")
|
| 214 |
+
if self.media_column not in entry:
|
| 215 |
+
raise ValueError(f"Key '{self.media_column}' not found in JSONL entry: {entry}")
|
| 216 |
+
|
| 217 |
+
media_path = Path(entry[self.media_column].strip())
|
| 218 |
+
output_path = self._embedding_output_path(media_path)
|
| 219 |
+
caption_data[output_path] = entry[self.caption_column]
|
| 220 |
+
|
| 221 |
+
return caption_data
|
| 222 |
+
|
| 223 |
+
def _clean_llm_prefixes(self) -> None:
|
| 224 |
+
"""Remove common LLM-generated prefixes from captions."""
|
| 225 |
+
for i in range(len(self.prompts)):
|
| 226 |
+
self.prompts[i] = self.prompts[i].strip()
|
| 227 |
+
for phrase in COMMON_LLM_START_PHRASES:
|
| 228 |
+
if self.prompts[i].startswith(phrase):
|
| 229 |
+
self.prompts[i] = self.prompts[i].removeprefix(phrase).strip()
|
| 230 |
+
break
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def compute_captions_embeddings( # noqa: PLR0913
|
| 234 |
+
dataset_file: str | Path,
|
| 235 |
+
output_dir: str,
|
| 236 |
+
model_path: str,
|
| 237 |
+
text_encoder_path: str,
|
| 238 |
+
caption_column: str = "caption",
|
| 239 |
+
media_column: str = "media_path",
|
| 240 |
+
lora_trigger: str | None = None,
|
| 241 |
+
remove_llm_prefixes: bool = False,
|
| 242 |
+
batch_size: int = 8,
|
| 243 |
+
device: str = "cuda",
|
| 244 |
+
load_in_8bit: bool = False,
|
| 245 |
+
overwrite: bool = False,
|
| 246 |
+
) -> None:
|
| 247 |
+
"""
|
| 248 |
+
Process captions and save text embeddings.
|
| 249 |
+
Under ``accelerate launch``, each process handles an interleaved shard of
|
| 250 |
+
the dataset (rank/world read from ``accelerate.PartialState``). Already-
|
| 251 |
+
computed ``.pt`` outputs are skipped unless ``overwrite=True``; writes are
|
| 252 |
+
atomic so an interrupted run is safe to resume.
|
| 253 |
+
Args:
|
| 254 |
+
dataset_file: Path to metadata file (CSV/JSON/JSONL) containing captions and media paths
|
| 255 |
+
output_dir: Directory to save embeddings
|
| 256 |
+
model_path: Path to LTX-2 checkpoint (.safetensors)
|
| 257 |
+
text_encoder_path: Path to Gemma text encoder directory
|
| 258 |
+
caption_column: Column name containing captions in the metadata file
|
| 259 |
+
media_column: Column name containing media paths (used for output naming)
|
| 260 |
+
lora_trigger: Optional trigger word to prepend to each caption
|
| 261 |
+
remove_llm_prefixes: Whether to remove common LLM-generated prefixes
|
| 262 |
+
batch_size: Batch size for processing
|
| 263 |
+
device: Device to use for computation
|
| 264 |
+
load_in_8bit: Whether to load the Gemma text encoder in 8-bit precision
|
| 265 |
+
overwrite: Re-encode every item even if its output exists. Use when rerunning with
|
| 266 |
+
changed parameters (different text encoder, lora_trigger, etc.) so stale
|
| 267 |
+
outputs are replaced.
|
| 268 |
+
"""
|
| 269 |
+
console = Console()
|
| 270 |
+
|
| 271 |
+
dataset = CaptionsDataset(
|
| 272 |
+
dataset_file=dataset_file,
|
| 273 |
+
caption_column=caption_column,
|
| 274 |
+
media_column=media_column,
|
| 275 |
+
lora_trigger=lora_trigger,
|
| 276 |
+
remove_llm_prefixes=remove_llm_prefixes,
|
| 277 |
+
)
|
| 278 |
+
logger.info(f"Loaded {len(dataset):,} captions")
|
| 279 |
+
|
| 280 |
+
output_path = Path(output_dir)
|
| 281 |
+
output_path.mkdir(parents=True, exist_ok=True)
|
| 282 |
+
|
| 283 |
+
# TODO(batch-tokenization): The current Gemma tokenizer doesn't support batched tokenization.
|
| 284 |
+
if batch_size > 1:
|
| 285 |
+
logger.warning(
|
| 286 |
+
"Batch size greater than 1 is not currently supported with the Gemma tokenizer. "
|
| 287 |
+
"Overriding batch_size to 1. This will be fixed in a future update."
|
| 288 |
+
)
|
| 289 |
+
batch_size = 1
|
| 290 |
+
|
| 291 |
+
dataloader = _build_sharded_dataloader(
|
| 292 |
+
dataset,
|
| 293 |
+
batch_size=batch_size,
|
| 294 |
+
num_workers=2,
|
| 295 |
+
is_done=lambda idx: (output_path / dataset.output_paths[idx]).is_file(),
|
| 296 |
+
overwrite=overwrite,
|
| 297 |
+
)
|
| 298 |
+
if dataloader is None:
|
| 299 |
+
return
|
| 300 |
+
|
| 301 |
+
# Load text encoder and embeddings processor
|
| 302 |
+
with console.status("[bold]Loading Gemma text encoder...", spinner="dots"):
|
| 303 |
+
text_encoder = load_text_encoder(
|
| 304 |
+
text_encoder_path,
|
| 305 |
+
device=device,
|
| 306 |
+
dtype=torch.bfloat16,
|
| 307 |
+
load_in_8bit=load_in_8bit,
|
| 308 |
+
)
|
| 309 |
+
embeddings_processor = load_embeddings_processor(
|
| 310 |
+
model_path,
|
| 311 |
+
device=device,
|
| 312 |
+
dtype=torch.bfloat16,
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
logger.info("Text encoder and embeddings processor loaded successfully")
|
| 316 |
+
logger.info(f"Processing captions in {len(dataloader):,} batches...")
|
| 317 |
+
|
| 318 |
+
with Progress(
|
| 319 |
+
SpinnerColumn(),
|
| 320 |
+
TextColumn("[progress.description]{task.description}"),
|
| 321 |
+
BarColumn(),
|
| 322 |
+
TaskProgressColumn(),
|
| 323 |
+
MofNCompleteColumn(),
|
| 324 |
+
TimeElapsedColumn(),
|
| 325 |
+
TimeRemainingColumn(),
|
| 326 |
+
console=console,
|
| 327 |
+
) as progress:
|
| 328 |
+
task = progress.add_task("Processing captions", total=len(dataloader))
|
| 329 |
+
for batch in dataloader:
|
| 330 |
+
# Encode prompts using text_encoder.encode() + feature_extractor
|
| 331 |
+
# (returns video/audio features before connector).
|
| 332 |
+
# The connector is applied during training via embeddings_processor
|
| 333 |
+
with torch.inference_mode():
|
| 334 |
+
# TODO(batch-tokenization): When tokenizer supports batching, encode all prompts at once.
|
| 335 |
+
# For now, process one at a time:
|
| 336 |
+
for i in range(len(batch["prompt"])):
|
| 337 |
+
encoded = text_encoder.encode([batch["prompt"][i]], padding_side="left")
|
| 338 |
+
hidden_states, prompt_attention_mask = encoded[0]
|
| 339 |
+
video_prompt_embeds, audio_prompt_embeds = embeddings_processor.feature_extractor(
|
| 340 |
+
hidden_states, prompt_attention_mask, "left"
|
| 341 |
+
)
|
| 342 |
+
|
| 343 |
+
output_rel_path = Path(batch["output_path"][i])
|
| 344 |
+
|
| 345 |
+
# Create output directory maintaining structure
|
| 346 |
+
output_dir_path = output_path / output_rel_path.parent
|
| 347 |
+
output_dir_path.mkdir(parents=True, exist_ok=True)
|
| 348 |
+
|
| 349 |
+
embedding_data = {
|
| 350 |
+
"video_prompt_embeds": video_prompt_embeds[0].cpu().contiguous(),
|
| 351 |
+
"prompt_attention_mask": prompt_attention_mask[0].cpu().contiguous(),
|
| 352 |
+
}
|
| 353 |
+
if audio_prompt_embeds is not None:
|
| 354 |
+
embedding_data["audio_prompt_embeds"] = audio_prompt_embeds[0].cpu().contiguous()
|
| 355 |
+
|
| 356 |
+
output_file = output_path / output_rel_path
|
| 357 |
+
_atomic_save(embedding_data, output_file)
|
| 358 |
+
|
| 359 |
+
progress.advance(task)
|
| 360 |
+
|
| 361 |
+
logger.info(f"Processed {len(dataloader.dataset):,} captions -> {output_path}") # type: ignore[arg-type]
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
def _atomic_save(data: Any, out: Path) -> None: # noqa: ANN401
|
| 365 |
+
"""Save to ``out`` atomically via per-PID temp file + replace.
|
| 366 |
+
Crash mid-write leaves an orphan ``.tmp.<pid>`` file that the skip logic
|
| 367 |
+
ignores. The per-PID suffix makes concurrent writes from multiple ranks
|
| 368 |
+
collision-free.
|
| 369 |
+
"""
|
| 370 |
+
tmp = out.with_suffix(f"{out.suffix}.tmp.{os.getpid()}")
|
| 371 |
+
torch.save(data, tmp)
|
| 372 |
+
tmp.replace(out)
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
def _build_sharded_dataloader(
|
| 376 |
+
dataset: Dataset,
|
| 377 |
+
*,
|
| 378 |
+
batch_size: int,
|
| 379 |
+
num_workers: int,
|
| 380 |
+
is_done: Callable[[int], bool],
|
| 381 |
+
overwrite: bool,
|
| 382 |
+
) -> DataLoader | None:
|
| 383 |
+
"""Return a DataLoader over this rank's interleaved shard of ``dataset``.
|
| 384 |
+
When ``overwrite`` is False, items whose outputs already exist (per
|
| 385 |
+
``is_done``) are filtered out. Returns ``None`` if this rank has nothing
|
| 386 |
+
to do, so the caller can early-return without loading any models.
|
| 387 |
+
"""
|
| 388 |
+
state = PartialState()
|
| 389 |
+
todo = [i for i in range(state.process_index, len(dataset), state.num_processes) if overwrite or not is_done(i)]
|
| 390 |
+
if not todo:
|
| 391 |
+
logger.info(f"Rank {state.process_index}/{state.num_processes}: nothing to do")
|
| 392 |
+
return None
|
| 393 |
+
logger.info(f"Rank {state.process_index}/{state.num_processes}: processing {len(todo):,} of {len(dataset):,} items")
|
| 394 |
+
return DataLoader(Subset(dataset, todo), batch_size=batch_size, shuffle=False, num_workers=num_workers)
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
@app.command()
|
| 398 |
+
def main( # noqa: PLR0913
|
| 399 |
+
dataset_file: str = typer.Argument(
|
| 400 |
+
...,
|
| 401 |
+
help="Path to metadata file (CSV/JSON/JSONL) containing captions and media paths",
|
| 402 |
+
),
|
| 403 |
+
output_dir: str = typer.Option(
|
| 404 |
+
...,
|
| 405 |
+
help="Output directory to save text embeddings",
|
| 406 |
+
),
|
| 407 |
+
model_path: str = typer.Option(
|
| 408 |
+
...,
|
| 409 |
+
help="Path to LTX-2 checkpoint (.safetensors file)",
|
| 410 |
+
),
|
| 411 |
+
text_encoder_path: str = typer.Option(
|
| 412 |
+
...,
|
| 413 |
+
help="Path to Gemma text encoder directory",
|
| 414 |
+
),
|
| 415 |
+
caption_column: str = typer.Option(
|
| 416 |
+
default="caption",
|
| 417 |
+
help="Column name containing captions in the dataset JSON/JSONL/CSV file",
|
| 418 |
+
),
|
| 419 |
+
media_column: str = typer.Option(
|
| 420 |
+
default="media_path",
|
| 421 |
+
help="Column name in the dataset JSON/JSONL/CSV file containing media paths "
|
| 422 |
+
"(used for output file naming and folder structure)",
|
| 423 |
+
),
|
| 424 |
+
batch_size: int = typer.Option(
|
| 425 |
+
default=8,
|
| 426 |
+
help="Batch size for processing",
|
| 427 |
+
),
|
| 428 |
+
device: str = typer.Option(
|
| 429 |
+
default="cuda",
|
| 430 |
+
help="Device to use for computation",
|
| 431 |
+
),
|
| 432 |
+
lora_trigger: str | None = typer.Option(
|
| 433 |
+
default=None,
|
| 434 |
+
help="Optional trigger word to prepend to each caption (activates the LoRA during inference)",
|
| 435 |
+
),
|
| 436 |
+
remove_llm_prefixes: bool = typer.Option(
|
| 437 |
+
default=False,
|
| 438 |
+
help="Remove common LLM-generated prefixes from captions",
|
| 439 |
+
),
|
| 440 |
+
load_text_encoder_in_8bit: bool = typer.Option(
|
| 441 |
+
default=False,
|
| 442 |
+
help="Load the Gemma text encoder in 8-bit precision to save GPU memory (requires bitsandbytes)",
|
| 443 |
+
),
|
| 444 |
+
overwrite: bool = typer.Option(
|
| 445 |
+
default=False,
|
| 446 |
+
help="Re-encode every caption even if its output exists. Use when rerunning with "
|
| 447 |
+
"changed parameters (different text encoder, lora_trigger, etc.) so stale outputs are replaced.",
|
| 448 |
+
),
|
| 449 |
+
) -> None:
|
| 450 |
+
"""Process text captions and save embeddings for video generation training.
|
| 451 |
+
For multi-GPU preprocessing, invoke under ``accelerate launch`` - each process
|
| 452 |
+
will handle an interleaved shard of the dataset.
|
| 453 |
+
This script processes captions from metadata files and saves text embeddings
|
| 454 |
+
that can be used for training video generation models. The output embeddings
|
| 455 |
+
will maintain the same folder structure and naming as the corresponding media files.
|
| 456 |
+
Note: This script is designed for LTX-2 models which use the Gemma text encoder.
|
| 457 |
+
Examples:
|
| 458 |
+
# Process captions with LTX-2 model
|
| 459 |
+
python scripts/process_captions.py dataset.json --output-dir ./embeddings \\
|
| 460 |
+
--model-path /path/to/ltx2_checkpoint.safetensors \\
|
| 461 |
+
--text-encoder-path /path/to/gemma
|
| 462 |
+
# Add a trigger word for LoRA training
|
| 463 |
+
python scripts/process_captions.py dataset.json --output-dir ./embeddings \\
|
| 464 |
+
--model-path /path/to/ltx2.safetensors --text-encoder-path /path/to/gemma \\
|
| 465 |
+
--lora-trigger "mytoken"
|
| 466 |
+
# Remove LLM-generated prefixes from captions
|
| 467 |
+
python scripts/process_captions.py dataset.json --output-dir ./embeddings \\
|
| 468 |
+
--model-path /path/to/ltx2.safetensors --text-encoder-path /path/to/gemma \\
|
| 469 |
+
--remove-llm-prefixes
|
| 470 |
+
"""
|
| 471 |
+
|
| 472 |
+
# Validate dataset file
|
| 473 |
+
if not Path(dataset_file).is_file():
|
| 474 |
+
raise typer.BadParameter(f"Dataset file not found: {dataset_file}")
|
| 475 |
+
|
| 476 |
+
if lora_trigger:
|
| 477 |
+
logger.info(f'LoRA trigger word "{lora_trigger}" will be prepended to all captions')
|
| 478 |
+
|
| 479 |
+
# Process embeddings
|
| 480 |
+
compute_captions_embeddings(
|
| 481 |
+
dataset_file=dataset_file,
|
| 482 |
+
output_dir=output_dir,
|
| 483 |
+
model_path=model_path,
|
| 484 |
+
text_encoder_path=text_encoder_path,
|
| 485 |
+
caption_column=caption_column,
|
| 486 |
+
media_column=media_column,
|
| 487 |
+
lora_trigger=lora_trigger,
|
| 488 |
+
remove_llm_prefixes=remove_llm_prefixes,
|
| 489 |
+
batch_size=batch_size,
|
| 490 |
+
device=device,
|
| 491 |
+
load_in_8bit=load_text_encoder_in_8bit,
|
| 492 |
+
overwrite=overwrite,
|
| 493 |
+
)
|
| 494 |
+
|
| 495 |
+
|
| 496 |
+
if __name__ == "__main__":
|
| 497 |
+
app()
|
packages/ltx-trainer/scripts/process_dataset.py
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
Preprocess a media dataset for LTX-2 training.
|
| 5 |
+
Automatically detects dataset columns and processes each according to a convention table.
|
| 6 |
+
Column names determine what gets encoded and where outputs go — no per-role CLI flags needed.
|
| 7 |
+
Convention table:
|
| 8 |
+
video → Video VAE → latents/
|
| 9 |
+
audio → Audio VAE → audio_latents/
|
| 10 |
+
reference_video → Video VAE → reference_latents/
|
| 11 |
+
reference_audio → Audio VAE → reference_audio_latents/
|
| 12 |
+
video_mask → (downsample) → video_masks/
|
| 13 |
+
audio_mask → (downsample) → audio_masks/
|
| 14 |
+
caption → Text encoder → conditions/
|
| 15 |
+
Legacy aliases: media_path → video, ref_media_path → reference_video
|
| 16 |
+
Basic usage:
|
| 17 |
+
python scripts/process_dataset.py /path/to/dataset.json --resolution-buckets 768x768x49 \\
|
| 18 |
+
--model-path /path/to/ltx2.safetensors --text-encoder-path /path/to/gemma
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
from pathlib import Path
|
| 22 |
+
|
| 23 |
+
import typer
|
| 24 |
+
from decode_latents import LatentsDecoder
|
| 25 |
+
from process_captions import compute_captions_embeddings
|
| 26 |
+
from process_videos import (
|
| 27 |
+
compute_audio_latents,
|
| 28 |
+
compute_audio_masks,
|
| 29 |
+
compute_latents,
|
| 30 |
+
compute_scaled_resolution_buckets,
|
| 31 |
+
compute_video_masks,
|
| 32 |
+
detect_dataset_columns,
|
| 33 |
+
parse_resolution_buckets,
|
| 34 |
+
)
|
| 35 |
+
from rich.console import Console
|
| 36 |
+
|
| 37 |
+
from ltx_trainer import logger
|
| 38 |
+
from ltx_trainer.gpu_utils import free_gpu_memory_context
|
| 39 |
+
|
| 40 |
+
console = Console()
|
| 41 |
+
|
| 42 |
+
app = typer.Typer(
|
| 43 |
+
pretty_exceptions_enable=False,
|
| 44 |
+
no_args_is_help=True,
|
| 45 |
+
help="Preprocess a media dataset for LTX-2 training. "
|
| 46 |
+
"Automatically detects columns (video, audio, reference_video, reference_audio, caption) "
|
| 47 |
+
"and processes each with the appropriate encoder.",
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
_KNOWN_ROLES = {"video", "audio", "reference_video", "reference_audio", "video_mask", "audio_mask", "caption"}
|
| 51 |
+
_LEGACY_ALIASES = {"media_path": "video", "ref_media_path": "reference_video"}
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def preprocess_dataset( # noqa: PLR0912, PLR0913, PLR0915
|
| 55 |
+
dataset_file: str,
|
| 56 |
+
resolution_buckets: list[tuple[int, int, int]] | None,
|
| 57 |
+
model_path: str,
|
| 58 |
+
text_encoder_path: str,
|
| 59 |
+
device: str,
|
| 60 |
+
output_dir: str | None = None,
|
| 61 |
+
video_column: str | None = None,
|
| 62 |
+
caption_column: str | None = None,
|
| 63 |
+
batch_size: int = 1,
|
| 64 |
+
lora_trigger: str | None = None,
|
| 65 |
+
vae_tiling: bool = False,
|
| 66 |
+
decode: bool = False,
|
| 67 |
+
remove_llm_prefixes: bool = False,
|
| 68 |
+
reference_downscale_factor: int = 1,
|
| 69 |
+
reference_temporal_scale_factor: int = 1,
|
| 70 |
+
skip_audio: bool = False,
|
| 71 |
+
audio_durations: list[float] | None = None,
|
| 72 |
+
load_text_encoder_in_8bit: bool = False,
|
| 73 |
+
overwrite: bool = False,
|
| 74 |
+
) -> None:
|
| 75 |
+
"""Run the preprocessing pipeline with convention-based column detection."""
|
| 76 |
+
_validate_dataset_file(dataset_file)
|
| 77 |
+
|
| 78 |
+
# Detect columns and resolve roles
|
| 79 |
+
dataset_columns = detect_dataset_columns(dataset_file)
|
| 80 |
+
roles = _resolve_columns(dataset_columns, video_column, caption_column)
|
| 81 |
+
|
| 82 |
+
# Log detected roles
|
| 83 |
+
for role, col in sorted(roles.items()):
|
| 84 |
+
alias_note = f" (alias for '{role}')" if col != role else ""
|
| 85 |
+
logger.info(f"Detected column '{col}'{alias_note} → {role}")
|
| 86 |
+
|
| 87 |
+
# Validate: need at least caption
|
| 88 |
+
if "caption" not in roles:
|
| 89 |
+
raise ValueError(
|
| 90 |
+
f"No caption column found. Dataset has columns: {dataset_columns}. "
|
| 91 |
+
f"Expected 'caption' or use --caption-column to specify."
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
# Validate: need video or audio
|
| 95 |
+
has_video = "video" in roles
|
| 96 |
+
has_audio = "audio" in roles
|
| 97 |
+
if not has_video and not has_audio:
|
| 98 |
+
raise ValueError(
|
| 99 |
+
f"No media column found. Dataset has columns: {dataset_columns}. "
|
| 100 |
+
f"Expected 'video', 'audio', or 'media_path' (legacy)."
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
# Validate: video modes need resolution buckets
|
| 104 |
+
if has_video and not resolution_buckets:
|
| 105 |
+
raise ValueError("--resolution-buckets is required when the dataset has a video column.")
|
| 106 |
+
|
| 107 |
+
output_base = Path(output_dir) if output_dir else Path(dataset_file).parent / ".precomputed"
|
| 108 |
+
|
| 109 |
+
if lora_trigger:
|
| 110 |
+
logger.info(f'LoRA trigger word "{lora_trigger}" will be prepended to all captions')
|
| 111 |
+
|
| 112 |
+
# --- Phase 1: Text encoder ---
|
| 113 |
+
with free_gpu_memory_context():
|
| 114 |
+
compute_captions_embeddings(
|
| 115 |
+
dataset_file=dataset_file,
|
| 116 |
+
output_dir=str(output_base / "conditions"),
|
| 117 |
+
model_path=model_path,
|
| 118 |
+
text_encoder_path=text_encoder_path,
|
| 119 |
+
caption_column=roles["caption"],
|
| 120 |
+
media_column=roles.get("video") or roles.get("audio") or roles["caption"],
|
| 121 |
+
lora_trigger=lora_trigger,
|
| 122 |
+
remove_llm_prefixes=remove_llm_prefixes,
|
| 123 |
+
batch_size=batch_size,
|
| 124 |
+
device=device,
|
| 125 |
+
load_in_8bit=load_text_encoder_in_8bit,
|
| 126 |
+
overwrite=overwrite,
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
# --- Phase 2: Video VAE (video, reference_video) ---
|
| 130 |
+
if has_video and resolution_buckets:
|
| 131 |
+
# Determine if audio should be auto-extracted from video files
|
| 132 |
+
auto_audio = not skip_audio and "audio" not in roles
|
| 133 |
+
|
| 134 |
+
audio_latents_dir = str(output_base / "audio_latents") if auto_audio else None
|
| 135 |
+
if auto_audio:
|
| 136 |
+
logger.info("Audio will be auto-extracted from video files (use --skip-audio to disable)")
|
| 137 |
+
|
| 138 |
+
with free_gpu_memory_context():
|
| 139 |
+
compute_latents(
|
| 140 |
+
dataset_file=dataset_file,
|
| 141 |
+
video_column=roles["video"],
|
| 142 |
+
resolution_buckets=resolution_buckets,
|
| 143 |
+
output_dir=str(output_base / "latents"),
|
| 144 |
+
model_path=model_path,
|
| 145 |
+
batch_size=batch_size,
|
| 146 |
+
device=device,
|
| 147 |
+
vae_tiling=vae_tiling,
|
| 148 |
+
with_audio=auto_audio,
|
| 149 |
+
audio_output_dir=audio_latents_dir,
|
| 150 |
+
overwrite=overwrite,
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
# Process reference video if present
|
| 154 |
+
if "reference_video" in roles:
|
| 155 |
+
if reference_downscale_factor > 1 and len(resolution_buckets) > 1:
|
| 156 |
+
raise ValueError(
|
| 157 |
+
"When using --reference-downscale-factor > 1, only a single resolution bucket is supported."
|
| 158 |
+
)
|
| 159 |
+
if reference_temporal_scale_factor > 1 and len(resolution_buckets) > 1:
|
| 160 |
+
raise ValueError(
|
| 161 |
+
"When using --reference-temporal-scale-factor > 1, only a single resolution bucket is supported."
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
reference_buckets = compute_scaled_resolution_buckets(resolution_buckets, reference_downscale_factor)
|
| 165 |
+
if reference_downscale_factor > 1:
|
| 166 |
+
logger.info(f"Processing reference videos at 1/{reference_downscale_factor} resolution...")
|
| 167 |
+
if reference_temporal_scale_factor > 1:
|
| 168 |
+
logger.info(
|
| 169 |
+
f"Temporally subsampling reference videos by {reference_temporal_scale_factor}x "
|
| 170 |
+
f"(VAE-aligned pattern)..."
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
with free_gpu_memory_context():
|
| 174 |
+
compute_latents(
|
| 175 |
+
dataset_file=dataset_file,
|
| 176 |
+
main_media_column=roles["video"],
|
| 177 |
+
video_column=roles["reference_video"],
|
| 178 |
+
resolution_buckets=reference_buckets,
|
| 179 |
+
output_dir=str(output_base / "reference_latents"),
|
| 180 |
+
model_path=model_path,
|
| 181 |
+
batch_size=batch_size,
|
| 182 |
+
device=device,
|
| 183 |
+
vae_tiling=vae_tiling,
|
| 184 |
+
overwrite=overwrite,
|
| 185 |
+
temporal_subsample_factor=reference_temporal_scale_factor,
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
# --- Phase 2b: Masks (video_mask, audio_mask) — processed after video latents for alignment ---
|
| 189 |
+
if "video_mask" in roles and has_video:
|
| 190 |
+
compute_video_masks(
|
| 191 |
+
dataset_file=dataset_file,
|
| 192 |
+
mask_column=roles["video_mask"],
|
| 193 |
+
latents_dir=str(output_base / "latents"),
|
| 194 |
+
output_dir=str(output_base / "video_masks"),
|
| 195 |
+
main_media_column=roles["video"],
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
# --- Phase 3: Audio VAE (audio, reference_audio) ---
|
| 199 |
+
audio_roles_to_process = [
|
| 200 |
+
("audio", "audio_latents"),
|
| 201 |
+
("reference_audio", "reference_audio_latents"),
|
| 202 |
+
]
|
| 203 |
+
active_audio_roles = [(role, subdir) for role, subdir in audio_roles_to_process if role in roles]
|
| 204 |
+
|
| 205 |
+
if active_audio_roles:
|
| 206 |
+
# Determine audio duration constraint: video bucket → max_duration, or explicit buckets
|
| 207 |
+
max_audio_duration = None
|
| 208 |
+
audio_duration_buckets = None
|
| 209 |
+
if has_video and resolution_buckets:
|
| 210 |
+
max_audio_duration = max(f for f, _h, _w in resolution_buckets) / 25.0
|
| 211 |
+
elif audio_durations:
|
| 212 |
+
audio_duration_buckets = audio_durations
|
| 213 |
+
|
| 214 |
+
for role, output_subdir in active_audio_roles:
|
| 215 |
+
with free_gpu_memory_context():
|
| 216 |
+
compute_audio_latents(
|
| 217 |
+
dataset_file=dataset_file,
|
| 218 |
+
audio_column=roles[role],
|
| 219 |
+
output_dir=str(output_base / output_subdir),
|
| 220 |
+
model_path=model_path,
|
| 221 |
+
main_media_column=roles.get("video"),
|
| 222 |
+
max_duration=max_audio_duration,
|
| 223 |
+
duration_buckets=audio_duration_buckets,
|
| 224 |
+
device=device,
|
| 225 |
+
overwrite=overwrite,
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
# --- Phase 4: Audio masks (after audio latents exist for temporal alignment) ---
|
| 229 |
+
if "audio_mask" in roles:
|
| 230 |
+
audio_latents_source = output_base / "audio_latents"
|
| 231 |
+
if audio_latents_source.exists():
|
| 232 |
+
compute_audio_masks(
|
| 233 |
+
dataset_file=dataset_file,
|
| 234 |
+
mask_column=roles["audio_mask"],
|
| 235 |
+
audio_latents_dir=str(audio_latents_source),
|
| 236 |
+
output_dir=str(output_base / "audio_masks"),
|
| 237 |
+
main_media_column=roles.get("video") or roles.get("audio"),
|
| 238 |
+
)
|
| 239 |
+
else:
|
| 240 |
+
logger.warning("audio_mask column found but no audio_latents/ — run with audio first")
|
| 241 |
+
|
| 242 |
+
# --- Decode for verification ---
|
| 243 |
+
if decode:
|
| 244 |
+
logger.info("Decoding latents for verification...")
|
| 245 |
+
decoder = LatentsDecoder(model_path=model_path, device=device, vae_tiling=vae_tiling, with_audio=has_audio)
|
| 246 |
+
if has_video:
|
| 247 |
+
decoder.decode(output_base / "latents", output_base / "decoded_videos")
|
| 248 |
+
if "reference_video" in roles and (output_base / "reference_latents").exists():
|
| 249 |
+
decoder.decode(output_base / "reference_latents", output_base / "decoded_reference_videos")
|
| 250 |
+
|
| 251 |
+
# --- Summary ---
|
| 252 |
+
logger.info(f"Dataset preprocessing complete! Results saved to {output_base}")
|
| 253 |
+
produced = [d.name for d in output_base.iterdir() if d.is_dir() and not d.name.startswith("decoded")]
|
| 254 |
+
logger.info(f"Output directories: {', '.join(sorted(produced))}")
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
def _validate_dataset_file(dataset_path: str) -> None:
|
| 258 |
+
"""Validate that the dataset file exists and has the correct format."""
|
| 259 |
+
dataset_file = Path(dataset_path)
|
| 260 |
+
if not dataset_file.exists():
|
| 261 |
+
raise FileNotFoundError(f"Dataset file does not exist: {dataset_file}")
|
| 262 |
+
if not dataset_file.is_file():
|
| 263 |
+
raise ValueError(f"Dataset path must be a file, not a directory: {dataset_file}")
|
| 264 |
+
if dataset_file.suffix.lower() not in [".csv", ".json", ".jsonl"]:
|
| 265 |
+
raise ValueError(f"Dataset file must be CSV, JSON, or JSONL format: {dataset_file}")
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def _resolve_columns(
|
| 269 |
+
dataset_columns: set[str],
|
| 270 |
+
video_column_override: str | None = None,
|
| 271 |
+
caption_column_override: str | None = None,
|
| 272 |
+
) -> dict[str, str]:
|
| 273 |
+
"""Map canonical role names to actual dataset column names.
|
| 274 |
+
Returns a dict of role → column_name for recognized roles found in the dataset.
|
| 275 |
+
"""
|
| 276 |
+
roles: dict[str, str] = {}
|
| 277 |
+
for col in dataset_columns:
|
| 278 |
+
role = _LEGACY_ALIASES.get(col, col)
|
| 279 |
+
if role in _KNOWN_ROLES:
|
| 280 |
+
roles[role] = col
|
| 281 |
+
|
| 282 |
+
if video_column_override and video_column_override in dataset_columns:
|
| 283 |
+
roles["video"] = video_column_override
|
| 284 |
+
if caption_column_override and caption_column_override in dataset_columns:
|
| 285 |
+
roles["caption"] = caption_column_override
|
| 286 |
+
|
| 287 |
+
return roles
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
@app.command()
|
| 291 |
+
def main( # noqa: PLR0913
|
| 292 |
+
dataset_path: str = typer.Argument(
|
| 293 |
+
...,
|
| 294 |
+
help="Path to metadata file (CSV/JSON/JSONL) with columns matching the convention table",
|
| 295 |
+
),
|
| 296 |
+
resolution_buckets: str | None = typer.Option(
|
| 297 |
+
default=None,
|
| 298 |
+
help='Resolution buckets in format "WxHxF;WxHxF;..." (e.g. "768x768x25"). '
|
| 299 |
+
"Required when dataset has a video column.",
|
| 300 |
+
),
|
| 301 |
+
model_path: str = typer.Option(
|
| 302 |
+
...,
|
| 303 |
+
help="Path to LTX-2 checkpoint (.safetensors file)",
|
| 304 |
+
),
|
| 305 |
+
text_encoder_path: str = typer.Option(
|
| 306 |
+
...,
|
| 307 |
+
help="Path to Gemma text encoder directory",
|
| 308 |
+
),
|
| 309 |
+
caption_column: str | None = typer.Option(
|
| 310 |
+
default=None,
|
| 311 |
+
help="Override: treat this column as 'caption' (default: auto-detect 'caption')",
|
| 312 |
+
),
|
| 313 |
+
video_column: str | None = typer.Option(
|
| 314 |
+
default=None,
|
| 315 |
+
help="Override: treat this column as 'video' (default: auto-detect 'video' or 'media_path')",
|
| 316 |
+
),
|
| 317 |
+
batch_size: int = typer.Option(
|
| 318 |
+
default=1,
|
| 319 |
+
help="Batch size for preprocessing",
|
| 320 |
+
),
|
| 321 |
+
device: str = typer.Option(
|
| 322 |
+
default="cuda",
|
| 323 |
+
help="Device to use for computation",
|
| 324 |
+
),
|
| 325 |
+
vae_tiling: bool = typer.Option(
|
| 326 |
+
default=False,
|
| 327 |
+
help="Enable VAE tiling for larger video resolutions",
|
| 328 |
+
),
|
| 329 |
+
output_dir: str | None = typer.Option(
|
| 330 |
+
default=None,
|
| 331 |
+
help="Output directory (defaults to .precomputed in dataset directory)",
|
| 332 |
+
),
|
| 333 |
+
lora_trigger: str | None = typer.Option(
|
| 334 |
+
default=None,
|
| 335 |
+
help="Optional trigger word to prepend to each caption",
|
| 336 |
+
),
|
| 337 |
+
decode: bool = typer.Option(
|
| 338 |
+
default=False,
|
| 339 |
+
help="Decode and save latents after encoding for verification",
|
| 340 |
+
),
|
| 341 |
+
remove_llm_prefixes: bool = typer.Option(
|
| 342 |
+
default=False,
|
| 343 |
+
help="Remove LLM prefixes from captions",
|
| 344 |
+
),
|
| 345 |
+
skip_audio: bool = typer.Option(
|
| 346 |
+
default=False,
|
| 347 |
+
help="Don't extract audio from video files (audio extraction is on by default)",
|
| 348 |
+
),
|
| 349 |
+
audio_durations: str | None = typer.Option(
|
| 350 |
+
default=None,
|
| 351 |
+
help='Audio duration buckets in seconds for audio-only datasets (e.g. "2.0;4.0;8.0"). '
|
| 352 |
+
"When set, audio files are trimmed to the best matching duration. "
|
| 353 |
+
"Not needed when a video column is present (audio duration derived from video bucket).",
|
| 354 |
+
),
|
| 355 |
+
with_audio: bool = typer.Option(
|
| 356 |
+
default=False,
|
| 357 |
+
hidden=True,
|
| 358 |
+
help="[DEPRECATED: audio is now on by default, use --skip-audio to disable]",
|
| 359 |
+
),
|
| 360 |
+
load_text_encoder_in_8bit: bool = typer.Option(
|
| 361 |
+
default=False,
|
| 362 |
+
help="Load the Gemma text encoder in 8-bit precision to save GPU memory",
|
| 363 |
+
),
|
| 364 |
+
reference_downscale_factor: int = typer.Option(
|
| 365 |
+
default=1,
|
| 366 |
+
help="Downscale factor for reference video resolution (e.g., 2 = half resolution for IC-LoRA)",
|
| 367 |
+
),
|
| 368 |
+
reference_temporal_scale_factor: int = typer.Option(
|
| 369 |
+
default=1,
|
| 370 |
+
help="Temporal subsampling factor for reference videos (e.g., 2 = half frame rate, "
|
| 371 |
+
"VAE-aligned: keeps frame 0, then every Nth frame from frame 1 onwards)",
|
| 372 |
+
),
|
| 373 |
+
overwrite: bool = typer.Option(
|
| 374 |
+
default=False,
|
| 375 |
+
help="Re-compute every item even if its output exists. Use when rerunning with "
|
| 376 |
+
"changed parameters (different model, resolution, etc.) so stale outputs are replaced.",
|
| 377 |
+
),
|
| 378 |
+
) -> None:
|
| 379 |
+
"""Preprocess a media dataset for LTX-2 training.
|
| 380 |
+
See module docstring for the convention table. Audio is auto-extracted from
|
| 381 |
+
video files by default — use --skip-audio to disable.
|
| 382 |
+
For multi-GPU preprocessing, invoke under ``accelerate launch`` -- each process
|
| 383 |
+
will handle an interleaved shard of the dataset.
|
| 384 |
+
"""
|
| 385 |
+
# Handle deprecated --with-audio flag
|
| 386 |
+
if with_audio:
|
| 387 |
+
logger.warning(
|
| 388 |
+
"--with-audio is deprecated. Audio extraction is now on by default. Use --skip-audio to disable."
|
| 389 |
+
)
|
| 390 |
+
|
| 391 |
+
parsed_buckets = parse_resolution_buckets(resolution_buckets) if resolution_buckets else None
|
| 392 |
+
|
| 393 |
+
if parsed_buckets and len(parsed_buckets) > 1:
|
| 394 |
+
logger.warning("Using multiple resolution buckets. Training batch size must be 1.")
|
| 395 |
+
|
| 396 |
+
if reference_downscale_factor < 1:
|
| 397 |
+
raise typer.BadParameter("--reference-downscale-factor must be >= 1")
|
| 398 |
+
|
| 399 |
+
if reference_temporal_scale_factor < 1:
|
| 400 |
+
raise typer.BadParameter("--reference-temporal-scale-factor must be >= 1")
|
| 401 |
+
|
| 402 |
+
parsed_audio_durations = None
|
| 403 |
+
if audio_durations:
|
| 404 |
+
parsed_audio_durations = [float(d) for d in audio_durations.split(";")]
|
| 405 |
+
if any(d <= 0 for d in parsed_audio_durations):
|
| 406 |
+
raise typer.BadParameter("All audio durations must be positive")
|
| 407 |
+
|
| 408 |
+
preprocess_dataset(
|
| 409 |
+
dataset_file=dataset_path,
|
| 410 |
+
resolution_buckets=parsed_buckets,
|
| 411 |
+
model_path=model_path,
|
| 412 |
+
text_encoder_path=text_encoder_path,
|
| 413 |
+
device=device,
|
| 414 |
+
output_dir=output_dir,
|
| 415 |
+
video_column=video_column,
|
| 416 |
+
caption_column=caption_column,
|
| 417 |
+
batch_size=batch_size,
|
| 418 |
+
lora_trigger=lora_trigger,
|
| 419 |
+
vae_tiling=vae_tiling,
|
| 420 |
+
decode=decode,
|
| 421 |
+
remove_llm_prefixes=remove_llm_prefixes,
|
| 422 |
+
reference_downscale_factor=reference_downscale_factor,
|
| 423 |
+
reference_temporal_scale_factor=reference_temporal_scale_factor,
|
| 424 |
+
skip_audio=skip_audio,
|
| 425 |
+
audio_durations=parsed_audio_durations,
|
| 426 |
+
load_text_encoder_in_8bit=load_text_encoder_in_8bit,
|
| 427 |
+
overwrite=overwrite,
|
| 428 |
+
)
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
if __name__ == "__main__":
|
| 432 |
+
app()
|
packages/ltx-trainer/scripts/process_videos.py
ADDED
|
@@ -0,0 +1,1474 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
Compute latent representations for video generation training.
|
| 5 |
+
This module provides functionality for processing video and image files, including:
|
| 6 |
+
- Loading videos/images from various file formats (CSV, JSON, JSONL)
|
| 7 |
+
- Resizing, cropping, and transforming media
|
| 8 |
+
- MediaDataset for video-only preprocessing workflows
|
| 9 |
+
- BucketSampler for grouping videos by resolution
|
| 10 |
+
Can be used as a standalone script:
|
| 11 |
+
python scripts/process_videos.py dataset.csv --resolution-buckets 768x768x25 \
|
| 12 |
+
--output-dir /path/to/output --model-source /path/to/ltx2.safetensors
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import json
|
| 16 |
+
import math
|
| 17 |
+
import os
|
| 18 |
+
from collections.abc import Callable
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
from typing import Any
|
| 21 |
+
|
| 22 |
+
import numpy as np
|
| 23 |
+
import pandas as pd
|
| 24 |
+
import torch
|
| 25 |
+
import torchaudio
|
| 26 |
+
import typer
|
| 27 |
+
from accelerate import PartialState
|
| 28 |
+
from pillow_heif import register_heif_opener
|
| 29 |
+
from rich.console import Console
|
| 30 |
+
from rich.progress import (
|
| 31 |
+
BarColumn,
|
| 32 |
+
MofNCompleteColumn,
|
| 33 |
+
Progress,
|
| 34 |
+
SpinnerColumn,
|
| 35 |
+
TaskProgressColumn,
|
| 36 |
+
TextColumn,
|
| 37 |
+
TimeElapsedColumn,
|
| 38 |
+
TimeRemainingColumn,
|
| 39 |
+
)
|
| 40 |
+
from torch.utils.data import DataLoader, Dataset, Subset
|
| 41 |
+
from torchvision import transforms
|
| 42 |
+
from torchvision.transforms import InterpolationMode
|
| 43 |
+
from torchvision.transforms.functional import crop, resize, to_tensor
|
| 44 |
+
from torchvision.transforms.functional import resize as tv_resize
|
| 45 |
+
from transformers.utils.logging import disable_progress_bar
|
| 46 |
+
|
| 47 |
+
from ltx_core.model.audio_vae import AudioProcessor
|
| 48 |
+
from ltx_core.types import Audio
|
| 49 |
+
from ltx_trainer import logger
|
| 50 |
+
from ltx_trainer.model_loader import load_audio_vae_encoder, load_video_vae_encoder
|
| 51 |
+
from ltx_trainer.utils import open_image_as_srgb
|
| 52 |
+
from ltx_trainer.video_utils import get_video_frame_count, read_video
|
| 53 |
+
|
| 54 |
+
disable_progress_bar()
|
| 55 |
+
|
| 56 |
+
# Register HEIF/HEIC support
|
| 57 |
+
register_heif_opener()
|
| 58 |
+
|
| 59 |
+
# Constants for validation
|
| 60 |
+
VAE_SPATIAL_FACTOR = 32
|
| 61 |
+
VAE_TEMPORAL_FACTOR = 8
|
| 62 |
+
|
| 63 |
+
# Audio constants
|
| 64 |
+
AUDIO_LATENT_CHANNELS = 8
|
| 65 |
+
AUDIO_FREQUENCY_BINS = 16
|
| 66 |
+
|
| 67 |
+
DEFAULT_TILE_SIZE = 512 # Spatial tile size in pixels (must be ≥64 and divisible by 32)
|
| 68 |
+
DEFAULT_TILE_OVERLAP = 128 # Spatial tile overlap in pixels (must be divisible by 32)
|
| 69 |
+
|
| 70 |
+
app = typer.Typer(
|
| 71 |
+
pretty_exceptions_enable=False,
|
| 72 |
+
no_args_is_help=True,
|
| 73 |
+
help="Process videos/images and save latent representations for video generation training.",
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def _clamp_01(x: torch.Tensor) -> torch.Tensor:
|
| 78 |
+
return x.clamp_(0, 1)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class MediaDataset(Dataset):
|
| 82 |
+
"""
|
| 83 |
+
Dataset for processing video and image files.
|
| 84 |
+
This dataset is designed for media preprocessing workflows where you need to:
|
| 85 |
+
- Load and preprocess videos/images
|
| 86 |
+
- Apply resizing and cropping transformations
|
| 87 |
+
- Handle different resolution buckets
|
| 88 |
+
- Filter out invalid media files
|
| 89 |
+
- Optionally extract audio from video files
|
| 90 |
+
"""
|
| 91 |
+
|
| 92 |
+
def __init__(
|
| 93 |
+
self,
|
| 94 |
+
dataset_file: str | Path,
|
| 95 |
+
main_media_column: str,
|
| 96 |
+
video_column: str,
|
| 97 |
+
resolution_buckets: list[tuple[int, int, int]],
|
| 98 |
+
reshape_mode: str = "center",
|
| 99 |
+
with_audio: bool = False,
|
| 100 |
+
temporal_subsample_factor: int = 1,
|
| 101 |
+
) -> None:
|
| 102 |
+
"""
|
| 103 |
+
Initialize the media dataset.
|
| 104 |
+
Args:
|
| 105 |
+
dataset_file: Path to CSV/JSON/JSONL metadata file
|
| 106 |
+
video_column: Column name for video paths in the metadata file
|
| 107 |
+
resolution_buckets: List of (frames, height, width) tuples
|
| 108 |
+
reshape_mode: How to crop videos ("center", "random")
|
| 109 |
+
with_audio: Whether to extract audio from video files
|
| 110 |
+
temporal_subsample_factor: Factor for VAE-aligned temporal subsampling.
|
| 111 |
+
When > 1, keeps frame 0 then takes every Nth frame from frame 1 onwards.
|
| 112 |
+
"""
|
| 113 |
+
super().__init__()
|
| 114 |
+
|
| 115 |
+
self.dataset_file = Path(dataset_file)
|
| 116 |
+
self.main_media_column = main_media_column
|
| 117 |
+
self.resolution_buckets = resolution_buckets
|
| 118 |
+
self.reshape_mode = reshape_mode
|
| 119 |
+
self.with_audio = with_audio
|
| 120 |
+
self.temporal_subsample_factor = temporal_subsample_factor
|
| 121 |
+
|
| 122 |
+
# First load main media paths
|
| 123 |
+
self.main_media_paths = self._load_video_paths(main_media_column)
|
| 124 |
+
|
| 125 |
+
# Then load reference video paths
|
| 126 |
+
self.video_paths = self._load_video_paths(video_column)
|
| 127 |
+
|
| 128 |
+
# Filter out videos with insufficient frames
|
| 129 |
+
self._filter_valid_videos()
|
| 130 |
+
|
| 131 |
+
self.max_target_frames = max(self.resolution_buckets, key=lambda x: x[0])[0]
|
| 132 |
+
|
| 133 |
+
# Set up video transforms
|
| 134 |
+
self.transforms = transforms.Compose(
|
| 135 |
+
[
|
| 136 |
+
transforms.Lambda(_clamp_01),
|
| 137 |
+
transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True),
|
| 138 |
+
]
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
def __len__(self) -> int:
|
| 142 |
+
return len(self.video_paths)
|
| 143 |
+
|
| 144 |
+
def __getitem__(self, index: int) -> dict[str, Any]:
|
| 145 |
+
"""Get a single video/image with metadata, and optionally audio."""
|
| 146 |
+
if isinstance(index, list):
|
| 147 |
+
# Special case for BucketSampler - return cached data
|
| 148 |
+
return index
|
| 149 |
+
|
| 150 |
+
video_path: Path = self.video_paths[index]
|
| 151 |
+
|
| 152 |
+
# Compute relative path of the video
|
| 153 |
+
data_root = self.dataset_file.parent
|
| 154 |
+
relative_path = str(_output_relative(video_path, data_root))
|
| 155 |
+
media_relative_path = str(_output_relative(self.main_media_paths[index], data_root))
|
| 156 |
+
|
| 157 |
+
if video_path.suffix.lower() in [".png", ".jpg", ".jpeg"]:
|
| 158 |
+
media_tensor = self._preprocess_image(video_path)
|
| 159 |
+
fps = 1.0
|
| 160 |
+
audio_data = None # Images don't have audio
|
| 161 |
+
else:
|
| 162 |
+
media_tensor, fps = self._preprocess_video(video_path)
|
| 163 |
+
|
| 164 |
+
# Extract audio if enabled
|
| 165 |
+
if self.with_audio:
|
| 166 |
+
# Calculate target duration from the processed video frames
|
| 167 |
+
# This ensures audio is trimmed to match the exact video duration
|
| 168 |
+
# media_tensor is [C, F, H, W] so shape[1] is num_frames
|
| 169 |
+
target_duration = media_tensor.shape[1] / fps
|
| 170 |
+
audio_data = self._extract_audio(video_path, target_duration)
|
| 171 |
+
else:
|
| 172 |
+
audio_data = None
|
| 173 |
+
|
| 174 |
+
# media_tensor is [C, F, H, W] format for VAE compatibility
|
| 175 |
+
_, num_frames, height, width = media_tensor.shape
|
| 176 |
+
|
| 177 |
+
result = {
|
| 178 |
+
"video": media_tensor,
|
| 179 |
+
"relative_path": relative_path,
|
| 180 |
+
"main_media_relative_path": media_relative_path,
|
| 181 |
+
"video_metadata": {
|
| 182 |
+
"num_frames": num_frames,
|
| 183 |
+
"height": height,
|
| 184 |
+
"width": width,
|
| 185 |
+
"fps": fps,
|
| 186 |
+
},
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
# Add audio data if available
|
| 190 |
+
if audio_data is not None:
|
| 191 |
+
result["audio"] = audio_data
|
| 192 |
+
|
| 193 |
+
return result
|
| 194 |
+
|
| 195 |
+
@staticmethod
|
| 196 |
+
def _extract_audio(video_path: Path, target_duration: float) -> dict[str, torch.Tensor | int] | None:
|
| 197 |
+
"""Extract audio track from a video file, trimmed/padded to match video duration."""
|
| 198 |
+
audio = _load_audio_from_file(video_path, max_duration=target_duration)
|
| 199 |
+
if audio is None:
|
| 200 |
+
return None
|
| 201 |
+
|
| 202 |
+
# Pad if shorter than target (_load_audio_from_file only trims, doesn't pad)
|
| 203 |
+
target_samples = int(target_duration * audio.sampling_rate)
|
| 204 |
+
if audio.waveform.shape[-1] < target_samples:
|
| 205 |
+
padding = target_samples - audio.waveform.shape[-1]
|
| 206 |
+
waveform = torch.nn.functional.pad(audio.waveform, (0, padding))
|
| 207 |
+
logger.warning(f"Padded audio to {target_duration:.2f} seconds for {video_path}")
|
| 208 |
+
else:
|
| 209 |
+
waveform = audio.waveform
|
| 210 |
+
|
| 211 |
+
return {"waveform": waveform, "sample_rate": audio.sampling_rate}
|
| 212 |
+
|
| 213 |
+
def _load_video_paths(self, column: str) -> list[Path]:
|
| 214 |
+
"""Load video paths from the specified data source, validating existence."""
|
| 215 |
+
paths = _load_paths_from_dataset(self.dataset_file, column)
|
| 216 |
+
invalid = [p for p in paths if not p.is_file()]
|
| 217 |
+
if invalid:
|
| 218 |
+
raise ValueError(f"Found {len(invalid)} invalid paths in '{column}'. First few: {invalid[:5]}")
|
| 219 |
+
return paths
|
| 220 |
+
|
| 221 |
+
def _filter_valid_videos(self) -> None:
|
| 222 |
+
"""Filter out videos with insufficient frames."""
|
| 223 |
+
original_length = len(self.video_paths)
|
| 224 |
+
valid_video_paths = []
|
| 225 |
+
valid_main_media_paths = []
|
| 226 |
+
min_frames_required = min(self.resolution_buckets, key=lambda x: x[0])[0]
|
| 227 |
+
|
| 228 |
+
for i, video_path in enumerate(self.video_paths):
|
| 229 |
+
if video_path.suffix.lower() in [".png", ".jpg", ".jpeg"]:
|
| 230 |
+
valid_video_paths.append(video_path)
|
| 231 |
+
valid_main_media_paths.append(self.main_media_paths[i])
|
| 232 |
+
continue
|
| 233 |
+
|
| 234 |
+
try:
|
| 235 |
+
frame_count = get_video_frame_count(video_path)
|
| 236 |
+
|
| 237 |
+
if frame_count >= min_frames_required:
|
| 238 |
+
valid_video_paths.append(video_path)
|
| 239 |
+
valid_main_media_paths.append(self.main_media_paths[i])
|
| 240 |
+
else:
|
| 241 |
+
logger.warning(
|
| 242 |
+
f"Skipping video at {video_path} - has {frame_count} frames, "
|
| 243 |
+
f"which is less than the minimum required frames ({min_frames_required})"
|
| 244 |
+
)
|
| 245 |
+
except Exception as e:
|
| 246 |
+
logger.warning(f"Failed to read video at {video_path}: {e!s}")
|
| 247 |
+
|
| 248 |
+
# Update both path lists to maintain synchronization
|
| 249 |
+
self.video_paths = valid_video_paths
|
| 250 |
+
self.main_media_paths = valid_main_media_paths
|
| 251 |
+
|
| 252 |
+
if len(self.video_paths) < original_length:
|
| 253 |
+
logger.warning(
|
| 254 |
+
f"Filtered out {original_length - len(self.video_paths)} videos with insufficient frames. "
|
| 255 |
+
f"Proceeding with {len(self.video_paths)} valid videos."
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
def _preprocess_image(self, path: Path) -> torch.Tensor:
|
| 259 |
+
"""Preprocess a single image by resizing and applying transforms."""
|
| 260 |
+
image = open_image_as_srgb(path)
|
| 261 |
+
image = to_tensor(image)
|
| 262 |
+
image = image.unsqueeze(0) # Add frame dimension [1, C, H, W] for bucket selection
|
| 263 |
+
|
| 264 |
+
# Find nearest resolution bucket and resize
|
| 265 |
+
nearest_bucket = self._get_resolution_bucket_for_item(image)
|
| 266 |
+
_, target_height, target_width = nearest_bucket
|
| 267 |
+
image_resized = self._resize_and_crop(image, target_height, target_width)
|
| 268 |
+
# _resize_and_crop returns [C, H, W] for single-frame input (squeeze removes dim 0)
|
| 269 |
+
|
| 270 |
+
# Apply transforms
|
| 271 |
+
image = self.transforms(image_resized) # [C, H, W] -> [C, H, W]
|
| 272 |
+
|
| 273 |
+
# Add frame dimension in VAE format: [C, H, W] -> [C, 1, H, W]
|
| 274 |
+
image = image.unsqueeze(1)
|
| 275 |
+
return image
|
| 276 |
+
|
| 277 |
+
def _preprocess_video(self, path: Path) -> tuple[torch.Tensor, float]:
|
| 278 |
+
"""Preprocess a video by loading, resizing, and applying transforms.
|
| 279 |
+
Returns:
|
| 280 |
+
Tuple of (video tensor in [C, F, H, W] format, fps)
|
| 281 |
+
"""
|
| 282 |
+
# Load video frames up to max_target_frames
|
| 283 |
+
video, fps = read_video(path, max_frames=self.max_target_frames)
|
| 284 |
+
|
| 285 |
+
nearest_bucket = self._get_resolution_bucket_for_item(video)
|
| 286 |
+
target_num_frames, target_height, target_width = nearest_bucket
|
| 287 |
+
frames_resized = self._resize_and_crop(video, target_height, target_width)
|
| 288 |
+
|
| 289 |
+
# Trim video to target number of frames
|
| 290 |
+
frames_resized = frames_resized[:target_num_frames]
|
| 291 |
+
|
| 292 |
+
# VAE-aligned temporal subsampling: keep frame 0, then every Nth frame
|
| 293 |
+
if self.temporal_subsample_factor > 1:
|
| 294 |
+
indices = _compute_temporal_subsample_indices(target_num_frames, self.temporal_subsample_factor)
|
| 295 |
+
frames_resized = frames_resized[indices]
|
| 296 |
+
|
| 297 |
+
# Apply transforms to each frame and stack
|
| 298 |
+
video = torch.stack([self.transforms(frame) for frame in frames_resized], dim=0)
|
| 299 |
+
|
| 300 |
+
# Permute [F,C,H,W] -> [C,F,H,W] for VAE compatibility
|
| 301 |
+
# After DataLoader batching, this becomes [B,C,F,H,W] which VAE expects
|
| 302 |
+
video = video.permute(1, 0, 2, 3).contiguous()
|
| 303 |
+
|
| 304 |
+
return video, fps
|
| 305 |
+
|
| 306 |
+
def _get_resolution_bucket_for_item(self, media_tensor: torch.Tensor) -> tuple[int, int, int]:
|
| 307 |
+
"""Get the nearest resolution bucket for the given media tensor."""
|
| 308 |
+
num_frames, _, height, width = media_tensor.shape
|
| 309 |
+
|
| 310 |
+
def distance(bucket: tuple[int, int, int]) -> tuple:
|
| 311 |
+
bucket_num_frames, bucket_height, bucket_width = bucket
|
| 312 |
+
# Lexicographic key:
|
| 313 |
+
# 1) minimize aspect-ratio diff (in log-scale, for invariance to shorter/longer ARs)
|
| 314 |
+
# 2) prefer buckets with more frames (by using negative)
|
| 315 |
+
# 3) prefer buckets with larger spatial area (by using negative)
|
| 316 |
+
return (
|
| 317 |
+
abs(math.log(width / height) - math.log(bucket_width / bucket_height)),
|
| 318 |
+
-bucket_num_frames,
|
| 319 |
+
-(bucket_height * bucket_width),
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
# Keep only buckets with <= available frames
|
| 323 |
+
relevant_buckets = [b for b in self.resolution_buckets if b[0] <= num_frames]
|
| 324 |
+
if not relevant_buckets:
|
| 325 |
+
raise ValueError(f"No resolution buckets have <= {num_frames} frames. Available: {self.resolution_buckets}")
|
| 326 |
+
|
| 327 |
+
# Find the bucket with the minimal distance (according to the function above) to the media item's shape.
|
| 328 |
+
nearest_bucket = min(relevant_buckets, key=distance)
|
| 329 |
+
|
| 330 |
+
return nearest_bucket
|
| 331 |
+
|
| 332 |
+
def _resize_and_crop(self, media_tensor: torch.Tensor, target_height: int, target_width: int) -> torch.Tensor:
|
| 333 |
+
"""Resize and crop tensor to target size."""
|
| 334 |
+
# Get current dimensions
|
| 335 |
+
current_height, current_width = media_tensor.shape[2], media_tensor.shape[3]
|
| 336 |
+
|
| 337 |
+
# Calculate aspect ratios to determine which dimension to resize first
|
| 338 |
+
current_aspect = current_width / current_height
|
| 339 |
+
target_aspect = target_width / target_height
|
| 340 |
+
|
| 341 |
+
# Resize while maintaining aspect ratio - scale to make the smaller dimension fit
|
| 342 |
+
if current_aspect > target_aspect:
|
| 343 |
+
# Current is wider than target, so scale by height
|
| 344 |
+
new_width = int(current_width * target_height / current_height)
|
| 345 |
+
media_tensor = resize(
|
| 346 |
+
media_tensor,
|
| 347 |
+
size=[target_height, new_width], # type: ignore
|
| 348 |
+
interpolation=InterpolationMode.BICUBIC,
|
| 349 |
+
)
|
| 350 |
+
else:
|
| 351 |
+
# Current is taller than target, so scale by width
|
| 352 |
+
new_height = int(current_height * target_width / current_width)
|
| 353 |
+
media_tensor = resize(
|
| 354 |
+
media_tensor,
|
| 355 |
+
size=[new_height, target_width],
|
| 356 |
+
interpolation=InterpolationMode.BICUBIC,
|
| 357 |
+
)
|
| 358 |
+
|
| 359 |
+
# Update dimensions after resize
|
| 360 |
+
current_height, current_width = media_tensor.shape[2], media_tensor.shape[3]
|
| 361 |
+
media_tensor = media_tensor.squeeze(0)
|
| 362 |
+
|
| 363 |
+
# Calculate how much we need to crop from each dimension
|
| 364 |
+
delta_h = current_height - target_height
|
| 365 |
+
delta_w = current_width - target_width
|
| 366 |
+
|
| 367 |
+
# Determine crop position based on reshape mode
|
| 368 |
+
if self.reshape_mode == "random":
|
| 369 |
+
# Random crop position
|
| 370 |
+
top = np.random.randint(0, delta_h + 1)
|
| 371 |
+
left = np.random.randint(0, delta_w + 1)
|
| 372 |
+
elif self.reshape_mode == "center":
|
| 373 |
+
# Center crop
|
| 374 |
+
top, left = delta_h // 2, delta_w // 2
|
| 375 |
+
else:
|
| 376 |
+
raise ValueError(f"Unsupported reshape mode: {self.reshape_mode}")
|
| 377 |
+
|
| 378 |
+
# Perform the final crop to exact target dimensions
|
| 379 |
+
media_tensor = crop(media_tensor, top=top, left=left, height=target_height, width=target_width)
|
| 380 |
+
return media_tensor
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def _compute_temporal_subsample_indices(num_frames: int, factor: int) -> list[int]:
|
| 384 |
+
"""Compute VAE-aligned temporal subsample indices.
|
| 385 |
+
Keeps frame 0 (the VAE's standalone first-frame latent), then takes every
|
| 386 |
+
``factor``-th frame from frame 1 onwards. This ensures each resulting
|
| 387 |
+
8-frame VAE group spans ``factor`` groups of the original video.
|
| 388 |
+
"""
|
| 389 |
+
if factor == 1:
|
| 390 |
+
return list(range(num_frames))
|
| 391 |
+
return [0, *list(range(1, num_frames, factor))]
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
def compute_latents( # noqa: PLR0912, PLR0913, PLR0915
|
| 395 |
+
dataset_file: str | Path,
|
| 396 |
+
video_column: str,
|
| 397 |
+
resolution_buckets: list[tuple[int, int, int]],
|
| 398 |
+
output_dir: str,
|
| 399 |
+
model_path: str,
|
| 400 |
+
main_media_column: str | None = None,
|
| 401 |
+
reshape_mode: str = "center",
|
| 402 |
+
batch_size: int = 1,
|
| 403 |
+
device: str = "cuda",
|
| 404 |
+
vae_tiling: bool = False,
|
| 405 |
+
with_audio: bool = False,
|
| 406 |
+
audio_output_dir: str | None = None,
|
| 407 |
+
num_dataloader_workers: int = 4,
|
| 408 |
+
overwrite: bool = False,
|
| 409 |
+
temporal_subsample_factor: int = 1,
|
| 410 |
+
) -> None:
|
| 411 |
+
"""
|
| 412 |
+
Process videos and save latent representations.
|
| 413 |
+
Under ``accelerate launch``, each process handles an interleaved shard of
|
| 414 |
+
the dataset (rank/world read from ``accelerate.PartialState``). Already-
|
| 415 |
+
computed ``.pt`` outputs are skipped unless ``overwrite=True``; writes are
|
| 416 |
+
atomic so an interrupted run is safe to resume.
|
| 417 |
+
Args:
|
| 418 |
+
dataset_file: Path to metadata file (CSV/JSON/JSONL) containing video paths
|
| 419 |
+
video_column: Column name for video paths in the metadata file
|
| 420 |
+
resolution_buckets: List of (frames, height, width) tuples
|
| 421 |
+
output_dir: Directory to save video latents
|
| 422 |
+
model_path: Path to LTX-2 checkpoint (.safetensors)
|
| 423 |
+
reshape_mode: How to crop videos ("center", "random")
|
| 424 |
+
main_media_column: Column name for main media paths (if different from video_column)
|
| 425 |
+
batch_size: Batch size for processing
|
| 426 |
+
device: Device to use for computation
|
| 427 |
+
vae_tiling: Whether to enable VAE tiling
|
| 428 |
+
with_audio: Whether to extract and encode audio from videos
|
| 429 |
+
audio_output_dir: Directory to save audio latents (required if with_audio=True)
|
| 430 |
+
num_dataloader_workers: Number of DataLoader worker processes (0 for in-process loading)
|
| 431 |
+
overwrite: Re-process every item even if its output exists. Use when rerunning with
|
| 432 |
+
changed parameters (different model, resolution, etc.) so stale outputs are replaced.
|
| 433 |
+
temporal_subsample_factor: Factor for VAE-aligned temporal subsampling of reference videos
|
| 434 |
+
"""
|
| 435 |
+
# Validate temporal subsampling compatibility with resolution buckets
|
| 436 |
+
if temporal_subsample_factor > 1:
|
| 437 |
+
for frames, _h, _w in resolution_buckets:
|
| 438 |
+
pixel_frames_minus_one = frames - 1
|
| 439 |
+
if pixel_frames_minus_one % temporal_subsample_factor != 0:
|
| 440 |
+
raise ValueError(
|
| 441 |
+
f"Frame count {frames} is not compatible with "
|
| 442 |
+
f"temporal_subsample_factor={temporal_subsample_factor}. "
|
| 443 |
+
f"(frames - 1) must be divisible by the factor."
|
| 444 |
+
)
|
| 445 |
+
subsampled = 1 + pixel_frames_minus_one // temporal_subsample_factor
|
| 446 |
+
if (subsampled - 1) % VAE_TEMPORAL_FACTOR != 0:
|
| 447 |
+
raise ValueError(
|
| 448 |
+
f"After temporal subsampling {frames} → {subsampled} frames, "
|
| 449 |
+
f"result does not satisfy (frames - 1) % {VAE_TEMPORAL_FACTOR} == 0."
|
| 450 |
+
)
|
| 451 |
+
|
| 452 |
+
if with_audio and audio_output_dir is None:
|
| 453 |
+
raise ValueError("audio_output_dir must be provided when with_audio=True")
|
| 454 |
+
|
| 455 |
+
console = Console()
|
| 456 |
+
torch_device = torch.device(device)
|
| 457 |
+
|
| 458 |
+
dataset = MediaDataset(
|
| 459 |
+
dataset_file=dataset_file,
|
| 460 |
+
main_media_column=main_media_column or video_column,
|
| 461 |
+
video_column=video_column,
|
| 462 |
+
resolution_buckets=resolution_buckets,
|
| 463 |
+
reshape_mode=reshape_mode,
|
| 464 |
+
with_audio=with_audio,
|
| 465 |
+
temporal_subsample_factor=temporal_subsample_factor,
|
| 466 |
+
)
|
| 467 |
+
logger.info(f"Loaded {len(dataset)} valid media files")
|
| 468 |
+
|
| 469 |
+
output_path = Path(output_dir)
|
| 470 |
+
output_path.mkdir(parents=True, exist_ok=True)
|
| 471 |
+
|
| 472 |
+
audio_output_path: Path | None = None
|
| 473 |
+
if with_audio:
|
| 474 |
+
audio_output_path = Path(audio_output_dir)
|
| 475 |
+
audio_output_path.mkdir(parents=True, exist_ok=True)
|
| 476 |
+
|
| 477 |
+
# Audio processing requires batch_size=1; must be applied before the dataloader is built.
|
| 478 |
+
if with_audio and batch_size > 1:
|
| 479 |
+
logger.warning("Audio processing requires batch_size=1. Overriding batch_size to 1.")
|
| 480 |
+
batch_size = 1
|
| 481 |
+
|
| 482 |
+
data_root = Path(dataset_file).parent
|
| 483 |
+
|
| 484 |
+
def _is_done(idx: int) -> bool:
|
| 485 |
+
rel = _output_relative(dataset.main_media_paths[idx], data_root).with_suffix(".pt")
|
| 486 |
+
if not (output_path / rel).is_file():
|
| 487 |
+
return False
|
| 488 |
+
return audio_output_path is None or (audio_output_path / rel).is_file()
|
| 489 |
+
|
| 490 |
+
dataloader = _build_sharded_dataloader(
|
| 491 |
+
dataset,
|
| 492 |
+
batch_size=batch_size,
|
| 493 |
+
num_workers=num_dataloader_workers,
|
| 494 |
+
is_done=_is_done,
|
| 495 |
+
overwrite=overwrite,
|
| 496 |
+
)
|
| 497 |
+
if dataloader is None:
|
| 498 |
+
return
|
| 499 |
+
|
| 500 |
+
with console.status(f"[bold]Loading video VAE encoder from [cyan]{model_path}[/]...", spinner="dots"):
|
| 501 |
+
vae = load_video_vae_encoder(model_path, device=torch_device, dtype=torch.bfloat16)
|
| 502 |
+
|
| 503 |
+
audio_vae_encoder = None
|
| 504 |
+
audio_processor = None
|
| 505 |
+
if with_audio:
|
| 506 |
+
with console.status(f"[bold]Loading audio VAE encoder from [cyan]{model_path}[/]...", spinner="dots"):
|
| 507 |
+
audio_vae_encoder = load_audio_vae_encoder(
|
| 508 |
+
checkpoint_path=model_path,
|
| 509 |
+
device=torch_device,
|
| 510 |
+
dtype=torch.float32, # Audio VAE needs float32 for quality. TODO: re-test with bfloat16.
|
| 511 |
+
)
|
| 512 |
+
audio_processor = AudioProcessor(
|
| 513 |
+
target_sample_rate=audio_vae_encoder.sample_rate,
|
| 514 |
+
mel_bins=audio_vae_encoder.mel_bins,
|
| 515 |
+
mel_hop_length=audio_vae_encoder.mel_hop_length,
|
| 516 |
+
n_fft=audio_vae_encoder.n_fft,
|
| 517 |
+
).to(torch_device)
|
| 518 |
+
|
| 519 |
+
# Track audio statistics
|
| 520 |
+
audio_success_count = 0
|
| 521 |
+
audio_skip_count = 0
|
| 522 |
+
|
| 523 |
+
# Process batches
|
| 524 |
+
with Progress(
|
| 525 |
+
SpinnerColumn(),
|
| 526 |
+
TextColumn("[progress.description]{task.description}"),
|
| 527 |
+
BarColumn(),
|
| 528 |
+
TaskProgressColumn(),
|
| 529 |
+
MofNCompleteColumn(),
|
| 530 |
+
TimeElapsedColumn(),
|
| 531 |
+
TimeRemainingColumn(),
|
| 532 |
+
console=console,
|
| 533 |
+
) as progress:
|
| 534 |
+
task = progress.add_task("Processing videos", total=len(dataloader))
|
| 535 |
+
|
| 536 |
+
for batch in dataloader:
|
| 537 |
+
# Get video tensor - shape is [B, F, C, H, W] from DataLoader
|
| 538 |
+
video = batch["video"]
|
| 539 |
+
|
| 540 |
+
# Encode video
|
| 541 |
+
with torch.inference_mode():
|
| 542 |
+
video_latent_data = _encode_video(vae=vae, video=video, use_tiling=vae_tiling)
|
| 543 |
+
|
| 544 |
+
# Save latents for each item in batch
|
| 545 |
+
for i in range(len(batch["relative_path"])):
|
| 546 |
+
output_rel_path = Path(batch["main_media_relative_path"][i]).with_suffix(".pt")
|
| 547 |
+
output_file = output_path / output_rel_path
|
| 548 |
+
|
| 549 |
+
# Create output directory maintaining structure
|
| 550 |
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
| 551 |
+
|
| 552 |
+
# Store the latent's effective fps (= source_fps / subsample factor).
|
| 553 |
+
# Downstream position math expects the rate the saved latents actually have.
|
| 554 |
+
effective_fps = batch["video_metadata"]["fps"][i].item() / temporal_subsample_factor
|
| 555 |
+
latent_data = {
|
| 556 |
+
"latents": video_latent_data["latents"][i].cpu().contiguous(), # [C, F', H', W']
|
| 557 |
+
"num_frames": video_latent_data["num_frames"],
|
| 558 |
+
"height": video_latent_data["height"],
|
| 559 |
+
"width": video_latent_data["width"],
|
| 560 |
+
"fps": effective_fps,
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
_atomic_save(latent_data, output_file)
|
| 564 |
+
|
| 565 |
+
# Process audio if enabled (audio is already extracted by the dataset)
|
| 566 |
+
if with_audio:
|
| 567 |
+
audio_batch = batch.get("audio")
|
| 568 |
+
if audio_batch is not None:
|
| 569 |
+
# Extract the i-th item from batched audio data
|
| 570 |
+
# DataLoader collates [channels, samples] -> [batch, channels, samples]
|
| 571 |
+
audio_data = Audio(
|
| 572 |
+
waveform=audio_batch["waveform"][i],
|
| 573 |
+
sampling_rate=audio_batch["sample_rate"][i].item(),
|
| 574 |
+
)
|
| 575 |
+
|
| 576 |
+
# Encode audio
|
| 577 |
+
with torch.inference_mode():
|
| 578 |
+
audio_latents = _encode_audio(audio_vae_encoder, audio_processor, audio_data)
|
| 579 |
+
|
| 580 |
+
# Save audio latents
|
| 581 |
+
audio_output_file = audio_output_path / output_rel_path
|
| 582 |
+
audio_output_file.parent.mkdir(parents=True, exist_ok=True)
|
| 583 |
+
|
| 584 |
+
audio_save_data = {
|
| 585 |
+
"latents": audio_latents["latents"].cpu().contiguous(),
|
| 586 |
+
"num_time_steps": audio_latents["num_time_steps"],
|
| 587 |
+
"frequency_bins": audio_latents["frequency_bins"],
|
| 588 |
+
"duration": audio_latents["duration"],
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
_atomic_save(audio_save_data, audio_output_file)
|
| 592 |
+
audio_success_count += 1
|
| 593 |
+
else:
|
| 594 |
+
# Video has no audio track
|
| 595 |
+
audio_skip_count += 1
|
| 596 |
+
|
| 597 |
+
progress.advance(task)
|
| 598 |
+
|
| 599 |
+
logger.info(f"Processed {len(dataloader.dataset)} videos -> {output_path}") # type: ignore[arg-type]
|
| 600 |
+
if with_audio:
|
| 601 |
+
logger.info(
|
| 602 |
+
f"Audio processing: {audio_success_count} videos with audio, "
|
| 603 |
+
f"{audio_skip_count} videos without audio (skipped)"
|
| 604 |
+
)
|
| 605 |
+
|
| 606 |
+
|
| 607 |
+
def _encode_video(
|
| 608 |
+
vae: torch.nn.Module,
|
| 609 |
+
video: torch.Tensor,
|
| 610 |
+
dtype: torch.dtype | None = None,
|
| 611 |
+
use_tiling: bool = False,
|
| 612 |
+
tile_size: int = DEFAULT_TILE_SIZE,
|
| 613 |
+
tile_overlap: int = DEFAULT_TILE_OVERLAP,
|
| 614 |
+
) -> dict[str, torch.Tensor | int]:
|
| 615 |
+
"""Encode video into non-patchified latent representation.
|
| 616 |
+
Args:
|
| 617 |
+
vae: Video VAE encoder model
|
| 618 |
+
video: Input tensor of shape [B, C, F, H, W] (batch, channels, frames, height, width)
|
| 619 |
+
This is the format expected by the VAE encoder.
|
| 620 |
+
dtype: Target dtype for output latents
|
| 621 |
+
use_tiling: Whether to use spatial tiling for memory efficiency
|
| 622 |
+
tile_size: Tile size in pixels (must be divisible by 32)
|
| 623 |
+
tile_overlap: Overlap between tiles in pixels (must be divisible by 32)
|
| 624 |
+
Returns:
|
| 625 |
+
Dict containing non-patchified latents and shape information:
|
| 626 |
+
{
|
| 627 |
+
"latents": Tensor[B, C, F', H', W'], # Non-patchified format with batch dim
|
| 628 |
+
"num_frames": int, # Latent frame count
|
| 629 |
+
"height": int, # Latent height
|
| 630 |
+
"width": int, # Latent width
|
| 631 |
+
}
|
| 632 |
+
"""
|
| 633 |
+
device = next(vae.parameters()).device
|
| 634 |
+
vae_dtype = next(vae.parameters()).dtype
|
| 635 |
+
|
| 636 |
+
# Add batch dimension if needed
|
| 637 |
+
if video.ndim == 4:
|
| 638 |
+
video = video.unsqueeze(0) # [C, F, H, W] -> [B, C, F, H, W]
|
| 639 |
+
|
| 640 |
+
video = video.to(device=device, dtype=vae_dtype)
|
| 641 |
+
|
| 642 |
+
# Choose encoding method based on tiling flag
|
| 643 |
+
if use_tiling:
|
| 644 |
+
latents = _tiled_encode_video(
|
| 645 |
+
vae=vae,
|
| 646 |
+
video=video,
|
| 647 |
+
tile_size=tile_size,
|
| 648 |
+
tile_overlap=tile_overlap,
|
| 649 |
+
)
|
| 650 |
+
else:
|
| 651 |
+
# Encode video - VAE expects [B, C, F, H, W], returns [B, C, F', H', W']
|
| 652 |
+
latents = vae(video)
|
| 653 |
+
|
| 654 |
+
if dtype is not None:
|
| 655 |
+
latents = latents.to(dtype=dtype)
|
| 656 |
+
|
| 657 |
+
_, _, num_frames, height, width = latents.shape
|
| 658 |
+
|
| 659 |
+
return {
|
| 660 |
+
"latents": latents, # [B, C, F', H', W']
|
| 661 |
+
"num_frames": num_frames,
|
| 662 |
+
"height": height,
|
| 663 |
+
"width": width,
|
| 664 |
+
}
|
| 665 |
+
|
| 666 |
+
|
| 667 |
+
def _tiled_encode_video( # noqa: PLR0912, PLR0915
|
| 668 |
+
vae: torch.nn.Module,
|
| 669 |
+
video: torch.Tensor,
|
| 670 |
+
tile_size: int = DEFAULT_TILE_SIZE,
|
| 671 |
+
tile_overlap: int = DEFAULT_TILE_OVERLAP,
|
| 672 |
+
) -> torch.Tensor:
|
| 673 |
+
"""Encode video using spatial tiling for memory efficiency.
|
| 674 |
+
Splits the video into overlapping spatial tiles, encodes each tile separately,
|
| 675 |
+
and blends the results using linear feathering in the overlap regions.
|
| 676 |
+
Args:
|
| 677 |
+
vae: Video VAE encoder model
|
| 678 |
+
video: Input tensor of shape [B, C, F, H, W]
|
| 679 |
+
tile_size: Tile size in pixels (must be divisible by 32)
|
| 680 |
+
tile_overlap: Overlap between tiles in pixels (must be divisible by 32)
|
| 681 |
+
Returns:
|
| 682 |
+
Encoded latent tensor [B, C_latent, F_latent, H_latent, W_latent]
|
| 683 |
+
"""
|
| 684 |
+
batch, _channels, frames, height, width = video.shape
|
| 685 |
+
device = video.device
|
| 686 |
+
dtype = video.dtype
|
| 687 |
+
|
| 688 |
+
# Validate tile parameters
|
| 689 |
+
if tile_size % VAE_SPATIAL_FACTOR != 0:
|
| 690 |
+
raise ValueError(f"tile_size must be divisible by {VAE_SPATIAL_FACTOR}, got {tile_size}")
|
| 691 |
+
if tile_overlap % VAE_SPATIAL_FACTOR != 0:
|
| 692 |
+
raise ValueError(f"tile_overlap must be divisible by {VAE_SPATIAL_FACTOR}, got {tile_overlap}")
|
| 693 |
+
if tile_overlap >= tile_size:
|
| 694 |
+
raise ValueError(f"tile_overlap ({tile_overlap}) must be less than tile_size ({tile_size})")
|
| 695 |
+
|
| 696 |
+
# If video fits in a single tile, use regular encoding
|
| 697 |
+
if height <= tile_size and width <= tile_size:
|
| 698 |
+
return vae(video)
|
| 699 |
+
|
| 700 |
+
# Calculate output dimensions
|
| 701 |
+
# VAE compresses: H -> H/32, W -> W/32, F -> 1 + (F-1)/8
|
| 702 |
+
output_height = height // VAE_SPATIAL_FACTOR
|
| 703 |
+
output_width = width // VAE_SPATIAL_FACTOR
|
| 704 |
+
output_frames = 1 + (frames - 1) // VAE_TEMPORAL_FACTOR
|
| 705 |
+
|
| 706 |
+
# Latent channels (128 for LTX-2)
|
| 707 |
+
# Get from a small test encode or assume 128
|
| 708 |
+
latent_channels = 128
|
| 709 |
+
|
| 710 |
+
# Initialize output and weight tensors
|
| 711 |
+
output = torch.zeros(
|
| 712 |
+
(batch, latent_channels, output_frames, output_height, output_width),
|
| 713 |
+
device=device,
|
| 714 |
+
dtype=dtype,
|
| 715 |
+
)
|
| 716 |
+
weights = torch.zeros(
|
| 717 |
+
(batch, 1, output_frames, output_height, output_width),
|
| 718 |
+
device=device,
|
| 719 |
+
dtype=dtype,
|
| 720 |
+
)
|
| 721 |
+
|
| 722 |
+
# Calculate tile positions with overlap
|
| 723 |
+
# Step size is tile_size - tile_overlap
|
| 724 |
+
step_h = tile_size - tile_overlap
|
| 725 |
+
step_w = tile_size - tile_overlap
|
| 726 |
+
|
| 727 |
+
h_positions = list(range(0, max(1, height - tile_overlap), step_h))
|
| 728 |
+
w_positions = list(range(0, max(1, width - tile_overlap), step_w))
|
| 729 |
+
|
| 730 |
+
# Ensure last tile covers the edge
|
| 731 |
+
if h_positions[-1] + tile_size < height:
|
| 732 |
+
h_positions.append(height - tile_size)
|
| 733 |
+
if w_positions[-1] + tile_size < width:
|
| 734 |
+
w_positions.append(width - tile_size)
|
| 735 |
+
|
| 736 |
+
# Remove duplicates and sort
|
| 737 |
+
h_positions = sorted(set(h_positions))
|
| 738 |
+
w_positions = sorted(set(w_positions))
|
| 739 |
+
|
| 740 |
+
# Overlap in latent space
|
| 741 |
+
overlap_out_h = tile_overlap // VAE_SPATIAL_FACTOR
|
| 742 |
+
overlap_out_w = tile_overlap // VAE_SPATIAL_FACTOR
|
| 743 |
+
|
| 744 |
+
# Process each tile
|
| 745 |
+
for h_pos in h_positions:
|
| 746 |
+
for w_pos in w_positions:
|
| 747 |
+
# Calculate tile boundaries in input space
|
| 748 |
+
h_start = max(0, h_pos)
|
| 749 |
+
w_start = max(0, w_pos)
|
| 750 |
+
h_end = min(h_start + tile_size, height)
|
| 751 |
+
w_end = min(w_start + tile_size, width)
|
| 752 |
+
|
| 753 |
+
# Ensure tile dimensions are divisible by VAE_SPATIAL_FACTOR
|
| 754 |
+
tile_h = ((h_end - h_start) // VAE_SPATIAL_FACTOR) * VAE_SPATIAL_FACTOR
|
| 755 |
+
tile_w = ((w_end - w_start) // VAE_SPATIAL_FACTOR) * VAE_SPATIAL_FACTOR
|
| 756 |
+
|
| 757 |
+
if tile_h < VAE_SPATIAL_FACTOR or tile_w < VAE_SPATIAL_FACTOR:
|
| 758 |
+
continue
|
| 759 |
+
|
| 760 |
+
# Adjust end positions
|
| 761 |
+
h_end = h_start + tile_h
|
| 762 |
+
w_end = w_start + tile_w
|
| 763 |
+
|
| 764 |
+
# Extract tile
|
| 765 |
+
tile = video[:, :, :, h_start:h_end, w_start:w_end]
|
| 766 |
+
|
| 767 |
+
# Encode tile
|
| 768 |
+
encoded_tile = vae(tile)
|
| 769 |
+
|
| 770 |
+
# Get actual encoded dimensions
|
| 771 |
+
_, _, tile_out_frames, tile_out_height, tile_out_width = encoded_tile.shape
|
| 772 |
+
|
| 773 |
+
# Calculate output positions
|
| 774 |
+
out_h_start = h_start // VAE_SPATIAL_FACTOR
|
| 775 |
+
out_w_start = w_start // VAE_SPATIAL_FACTOR
|
| 776 |
+
out_h_end = min(out_h_start + tile_out_height, output_height)
|
| 777 |
+
out_w_end = min(out_w_start + tile_out_width, output_width)
|
| 778 |
+
|
| 779 |
+
# Trim encoded tile if necessary
|
| 780 |
+
actual_tile_h = out_h_end - out_h_start
|
| 781 |
+
actual_tile_w = out_w_end - out_w_start
|
| 782 |
+
encoded_tile = encoded_tile[:, :, :, :actual_tile_h, :actual_tile_w]
|
| 783 |
+
|
| 784 |
+
# Create blending mask with linear feathering at edges
|
| 785 |
+
mask = torch.ones(
|
| 786 |
+
(1, 1, tile_out_frames, actual_tile_h, actual_tile_w),
|
| 787 |
+
device=device,
|
| 788 |
+
dtype=dtype,
|
| 789 |
+
)
|
| 790 |
+
|
| 791 |
+
# Apply feathering at edges (linear blend in overlap regions)
|
| 792 |
+
# Left edge
|
| 793 |
+
if h_pos > 0 and overlap_out_h > 0 and overlap_out_h < actual_tile_h:
|
| 794 |
+
fade_in = torch.linspace(0.0, 1.0, overlap_out_h + 2, device=device, dtype=dtype)[1:-1]
|
| 795 |
+
mask[:, :, :, :overlap_out_h, :] *= fade_in.view(1, 1, 1, -1, 1)
|
| 796 |
+
|
| 797 |
+
# Right edge (bottom in height dimension)
|
| 798 |
+
if h_end < height and overlap_out_h > 0 and overlap_out_h < actual_tile_h:
|
| 799 |
+
fade_out = torch.linspace(1.0, 0.0, overlap_out_h + 2, device=device, dtype=dtype)[1:-1]
|
| 800 |
+
mask[:, :, :, -overlap_out_h:, :] *= fade_out.view(1, 1, 1, -1, 1)
|
| 801 |
+
|
| 802 |
+
# Top edge (left in width dimension)
|
| 803 |
+
if w_pos > 0 and overlap_out_w > 0 and overlap_out_w < actual_tile_w:
|
| 804 |
+
fade_in = torch.linspace(0.0, 1.0, overlap_out_w + 2, device=device, dtype=dtype)[1:-1]
|
| 805 |
+
mask[:, :, :, :, :overlap_out_w] *= fade_in.view(1, 1, 1, 1, -1)
|
| 806 |
+
|
| 807 |
+
# Bottom edge (right in width dimension)
|
| 808 |
+
if w_end < width and overlap_out_w > 0 and overlap_out_w < actual_tile_w:
|
| 809 |
+
fade_out = torch.linspace(1.0, 0.0, overlap_out_w + 2, device=device, dtype=dtype)[1:-1]
|
| 810 |
+
mask[:, :, :, :, -overlap_out_w:] *= fade_out.view(1, 1, 1, 1, -1)
|
| 811 |
+
|
| 812 |
+
# Accumulate weighted results
|
| 813 |
+
output[:, :, :, out_h_start:out_h_end, out_w_start:out_w_end] += encoded_tile * mask
|
| 814 |
+
weights[:, :, :, out_h_start:out_h_end, out_w_start:out_w_end] += mask
|
| 815 |
+
|
| 816 |
+
# Normalize by weights (avoid division by zero)
|
| 817 |
+
output = output / (weights + 1e-8)
|
| 818 |
+
|
| 819 |
+
return output
|
| 820 |
+
|
| 821 |
+
|
| 822 |
+
def _encode_audio(
|
| 823 |
+
audio_vae_encoder: torch.nn.Module,
|
| 824 |
+
audio_processor: torch.nn.Module,
|
| 825 |
+
audio: Audio,
|
| 826 |
+
) -> dict[str, torch.Tensor | int | float]:
|
| 827 |
+
"""Encode audio waveform into latent representation.
|
| 828 |
+
Args:
|
| 829 |
+
audio_vae_encoder: Audio VAE encoder model from ltx-core
|
| 830 |
+
audio_processor: AudioProcessor for waveform-to-spectrogram conversion
|
| 831 |
+
audio: Audio container with waveform tensor and sampling rate.
|
| 832 |
+
Returns:
|
| 833 |
+
Dict containing audio latents and shape information:
|
| 834 |
+
{
|
| 835 |
+
"latents": Tensor[C, T, F], # Non-patchified format
|
| 836 |
+
"num_time_steps": int,
|
| 837 |
+
"frequency_bins": int,
|
| 838 |
+
"duration": float,
|
| 839 |
+
}
|
| 840 |
+
"""
|
| 841 |
+
device = next(audio_vae_encoder.parameters()).device
|
| 842 |
+
dtype = next(audio_vae_encoder.parameters()).dtype
|
| 843 |
+
|
| 844 |
+
waveform = audio.waveform.to(device=device, dtype=dtype)
|
| 845 |
+
|
| 846 |
+
# Add batch dimension if needed: [channels, samples] -> [batch, channels, samples]
|
| 847 |
+
if waveform.dim() == 2:
|
| 848 |
+
waveform = waveform.unsqueeze(0)
|
| 849 |
+
|
| 850 |
+
# Convert to stereo if needed (audio VAE expects 2 channels)
|
| 851 |
+
# Channel order for surround: 5.1=[L,R,C,LFE,Ls,Rs], 7.1=[L,R,C,LFE,Ls,Rs,Lb,Rb]
|
| 852 |
+
num_channels = waveform.shape[1]
|
| 853 |
+
if num_channels == 1:
|
| 854 |
+
# Mono to stereo: duplicate the channel
|
| 855 |
+
waveform = waveform.repeat(1, 2, 1)
|
| 856 |
+
elif num_channels == 6:
|
| 857 |
+
# 5.1 downmix with normalized weights (sum to 1.0)
|
| 858 |
+
# Original: L = L + 0.707*C + 0.707*Ls, weights sum = 2.414
|
| 859 |
+
w_main = 1.0 / 2.414 # ~0.414
|
| 860 |
+
w_other = 0.707 / 2.414 # ~0.293
|
| 861 |
+
left = w_main * waveform[:, 0, :] + w_other * waveform[:, 2, :] + w_other * waveform[:, 4, :]
|
| 862 |
+
right = w_main * waveform[:, 1, :] + w_other * waveform[:, 2, :] + w_other * waveform[:, 5, :]
|
| 863 |
+
waveform = torch.stack([left, right], dim=1)
|
| 864 |
+
elif num_channels == 8:
|
| 865 |
+
# 7.1 downmix with normalized weights (sum to 1.0)
|
| 866 |
+
# Original: L = L + 0.707*C + 0.707*Ls + 0.707*Lb, weights sum = 3.121
|
| 867 |
+
w_main = 1.0 / 3.121 # ~0.320
|
| 868 |
+
w_other = 0.707 / 3.121 # ~0.227
|
| 869 |
+
center = waveform[:, 2, :]
|
| 870 |
+
left = w_main * waveform[:, 0, :] + w_other * (center + waveform[:, 4, :] + waveform[:, 6, :])
|
| 871 |
+
right = w_main * waveform[:, 1, :] + w_other * (center + waveform[:, 5, :] + waveform[:, 7, :])
|
| 872 |
+
waveform = torch.stack([left, right], dim=1)
|
| 873 |
+
elif num_channels > 2:
|
| 874 |
+
# Unknown layout: average all channels to mono, then duplicate to stereo
|
| 875 |
+
logger.warning(f"Unknown audio channel layout ({num_channels} channels), using mean downmix")
|
| 876 |
+
mono = waveform.mean(dim=1, keepdim=True)
|
| 877 |
+
waveform = mono.repeat(1, 2, 1)
|
| 878 |
+
|
| 879 |
+
# Calculate duration
|
| 880 |
+
duration = waveform.shape[-1] / audio.sampling_rate
|
| 881 |
+
|
| 882 |
+
# Convert waveform to mel spectrogram using AudioProcessor
|
| 883 |
+
mel_spectrogram = audio_processor.waveform_to_mel(Audio(waveform=waveform, sampling_rate=audio.sampling_rate))
|
| 884 |
+
mel_spectrogram = mel_spectrogram.to(dtype=dtype)
|
| 885 |
+
|
| 886 |
+
# Encode mel spectrogram to latents
|
| 887 |
+
latents = audio_vae_encoder(mel_spectrogram)
|
| 888 |
+
|
| 889 |
+
# latents shape: [batch, channels, time, freq] = [1, 8, T, 16]
|
| 890 |
+
_, _channels, time_steps, freq_bins = latents.shape
|
| 891 |
+
|
| 892 |
+
return {
|
| 893 |
+
"latents": latents.squeeze(0), # [C, T, F] - remove batch dim
|
| 894 |
+
"num_time_steps": time_steps,
|
| 895 |
+
"frequency_bins": freq_bins,
|
| 896 |
+
"duration": duration,
|
| 897 |
+
}
|
| 898 |
+
|
| 899 |
+
|
| 900 |
+
AUDIO_FILE_EXTENSIONS = {".wav", ".mp3", ".flac", ".ogg", ".aac", ".m4a"}
|
| 901 |
+
VIDEO_FILE_EXTENSIONS = {".mp4", ".avi", ".mov", ".mkv", ".webm"}
|
| 902 |
+
IMAGE_FILE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".heic", ".heif", ".bmp", ".tiff", ".webp"}
|
| 903 |
+
|
| 904 |
+
|
| 905 |
+
def compute_video_masks(
|
| 906 |
+
dataset_file: str | Path,
|
| 907 |
+
mask_column: str,
|
| 908 |
+
latents_dir: str,
|
| 909 |
+
output_dir: str,
|
| 910 |
+
main_media_column: str | None = None,
|
| 911 |
+
overwrite: bool = False,
|
| 912 |
+
) -> None:
|
| 913 |
+
"""Preprocess video mask files to latent-space binary masks.
|
| 914 |
+
For each sample, loads the mask video/image, applies the same spatial
|
| 915 |
+
resize/crop as the target video (read from saved latent metadata), downsamples
|
| 916 |
+
to latent dimensions, binarizes, and saves as a .pt tensor.
|
| 917 |
+
Args:
|
| 918 |
+
dataset_file: Path to metadata file (CSV/JSON/JSONL).
|
| 919 |
+
mask_column: Column name containing mask video/image paths.
|
| 920 |
+
latents_dir: Directory containing the target video latents (for reading
|
| 921 |
+
spatial/temporal metadata to ensure mask alignment).
|
| 922 |
+
output_dir: Directory to save mask .pt files.
|
| 923 |
+
main_media_column: Column for output file naming (defaults to mask_column).
|
| 924 |
+
"""
|
| 925 |
+
dataset_path = Path(dataset_file)
|
| 926 |
+
data_root = dataset_path.parent
|
| 927 |
+
latents_path = Path(latents_dir)
|
| 928 |
+
output_path = Path(output_dir)
|
| 929 |
+
output_path.mkdir(parents=True, exist_ok=True)
|
| 930 |
+
|
| 931 |
+
naming_column = main_media_column or mask_column
|
| 932 |
+
mask_paths = _load_paths_from_dataset(dataset_path, mask_column)
|
| 933 |
+
naming_paths = _load_paths_from_dataset(dataset_path, naming_column) if naming_column != mask_column else mask_paths
|
| 934 |
+
|
| 935 |
+
success = 0
|
| 936 |
+
for mask_file, naming_file in zip(mask_paths, naming_paths, strict=True):
|
| 937 |
+
rel_path = _output_relative(naming_file, data_root)
|
| 938 |
+
latent_file = latents_path / rel_path.with_suffix(".pt")
|
| 939 |
+
out_file = output_path / rel_path.with_suffix(".pt")
|
| 940 |
+
|
| 941 |
+
if not latent_file.exists():
|
| 942 |
+
logger.warning(f"No target latent found at {latent_file}, skipping mask {mask_file}")
|
| 943 |
+
continue
|
| 944 |
+
|
| 945 |
+
if not overwrite and out_file.is_file():
|
| 946 |
+
continue
|
| 947 |
+
|
| 948 |
+
target_meta = torch.load(latent_file, map_location="cpu", weights_only=True)
|
| 949 |
+
latent_f = target_meta["num_frames"]
|
| 950 |
+
latent_h = target_meta["height"]
|
| 951 |
+
latent_w = target_meta["width"]
|
| 952 |
+
pixel_h = latent_h * VAE_SPATIAL_FACTOR
|
| 953 |
+
pixel_w = latent_w * VAE_SPATIAL_FACTOR
|
| 954 |
+
pixel_f = (latent_f - 1) * VAE_TEMPORAL_FACTOR + 1
|
| 955 |
+
|
| 956 |
+
# Load mask as video or image
|
| 957 |
+
if mask_file.suffix.lower() in IMAGE_FILE_EXTENSIONS:
|
| 958 |
+
img = to_tensor(open_image_as_srgb(mask_file)).mean(dim=0, keepdim=True) # [1, H, W]
|
| 959 |
+
img = tv_resize(img.unsqueeze(0), [pixel_h, pixel_w]).squeeze(0) # [1, H, W]
|
| 960 |
+
mask_pixels = img.expand(pixel_f, -1, -1) # tile across frames → [F, H, W]
|
| 961 |
+
else:
|
| 962 |
+
frames, _ = read_video(str(mask_file), max_frames=pixel_f) # [F, C, H, W]
|
| 963 |
+
frames = frames[:pixel_f].mean(dim=1) # grayscale → [F, H, W]
|
| 964 |
+
frames = torch.nn.functional.interpolate(
|
| 965 |
+
frames.unsqueeze(1), size=(pixel_h, pixel_w), mode="nearest"
|
| 966 |
+
).squeeze(1) # [F, H, W]
|
| 967 |
+
mask_pixels = frames
|
| 968 |
+
|
| 969 |
+
# Downsample to latent dims: [F, H, W] → [F', H', W']
|
| 970 |
+
mask_latent = torch.nn.functional.avg_pool2d(mask_pixels.unsqueeze(1), kernel_size=VAE_SPATIAL_FACTOR).squeeze(
|
| 971 |
+
1
|
| 972 |
+
) # [F, H', W'] → spatial done
|
| 973 |
+
# Temporal: max-pool over groups of VAE_TEMPORAL_FACTOR frames (any masked frame masks the group)
|
| 974 |
+
f_spatial = mask_latent.shape[0]
|
| 975 |
+
pad_f = (VAE_TEMPORAL_FACTOR - f_spatial % VAE_TEMPORAL_FACTOR) % VAE_TEMPORAL_FACTOR
|
| 976 |
+
if pad_f > 0:
|
| 977 |
+
mask_latent = torch.nn.functional.pad(mask_latent, (0, 0, 0, 0, 0, pad_f))
|
| 978 |
+
h_prime, w_prime = mask_latent.shape[1], mask_latent.shape[2]
|
| 979 |
+
mask_latent = mask_latent.reshape(-1, VAE_TEMPORAL_FACTOR, h_prime, w_prime).amax(dim=1)[:latent_f]
|
| 980 |
+
|
| 981 |
+
# Binarize
|
| 982 |
+
mask_latent = (mask_latent > 0.5).float()
|
| 983 |
+
|
| 984 |
+
out_file.parent.mkdir(parents=True, exist_ok=True)
|
| 985 |
+
_atomic_save({"mask": mask_latent}, out_file)
|
| 986 |
+
success += 1
|
| 987 |
+
|
| 988 |
+
logger.info(f"Mask preprocessing complete: {success} masks saved to {output_path}")
|
| 989 |
+
|
| 990 |
+
|
| 991 |
+
def compute_audio_masks(
|
| 992 |
+
dataset_file: str | Path,
|
| 993 |
+
mask_column: str,
|
| 994 |
+
audio_latents_dir: str,
|
| 995 |
+
output_dir: str,
|
| 996 |
+
main_media_column: str | None = None,
|
| 997 |
+
overwrite: bool = False,
|
| 998 |
+
) -> None:
|
| 999 |
+
"""Preprocess audio mask files to latent-space binary masks.
|
| 1000 |
+
For each sample, loads the mask (a 1D waveform-like signal or a simple tensor),
|
| 1001 |
+
resamples it to match the target audio latent temporal length, binarizes, and saves.
|
| 1002 |
+
Args:
|
| 1003 |
+
dataset_file: Path to metadata file (CSV/JSON/JSONL).
|
| 1004 |
+
mask_column: Column name containing mask file paths (.wav or .pt).
|
| 1005 |
+
audio_latents_dir: Directory containing the target audio latents (for reading
|
| 1006 |
+
temporal metadata to ensure mask alignment).
|
| 1007 |
+
output_dir: Directory to save mask .pt files.
|
| 1008 |
+
main_media_column: Column for output file naming (defaults to mask_column).
|
| 1009 |
+
"""
|
| 1010 |
+
dataset_path = Path(dataset_file)
|
| 1011 |
+
data_root = dataset_path.parent
|
| 1012 |
+
audio_latents_path = Path(audio_latents_dir)
|
| 1013 |
+
output_path = Path(output_dir)
|
| 1014 |
+
output_path.mkdir(parents=True, exist_ok=True)
|
| 1015 |
+
|
| 1016 |
+
naming_column = main_media_column or mask_column
|
| 1017 |
+
mask_paths = _load_paths_from_dataset(dataset_path, mask_column)
|
| 1018 |
+
naming_paths = _load_paths_from_dataset(dataset_path, naming_column) if naming_column != mask_column else mask_paths
|
| 1019 |
+
|
| 1020 |
+
success = 0
|
| 1021 |
+
for mask_file, naming_file in zip(mask_paths, naming_paths, strict=True):
|
| 1022 |
+
rel_path = _output_relative(naming_file, data_root)
|
| 1023 |
+
latent_file = audio_latents_path / rel_path.with_suffix(".pt")
|
| 1024 |
+
out_file = output_path / rel_path.with_suffix(".pt")
|
| 1025 |
+
|
| 1026 |
+
if not latent_file.exists():
|
| 1027 |
+
logger.warning(f"No target audio latent found at {latent_file}, skipping mask {mask_file}")
|
| 1028 |
+
continue
|
| 1029 |
+
|
| 1030 |
+
if not overwrite and out_file.is_file():
|
| 1031 |
+
continue
|
| 1032 |
+
|
| 1033 |
+
target_meta = torch.load(latent_file, map_location="cpu", weights_only=True)
|
| 1034 |
+
latent_t = target_meta["num_time_steps"]
|
| 1035 |
+
|
| 1036 |
+
# Load mask: .pt file (raw tensor) or .wav (use amplitude envelope)
|
| 1037 |
+
if mask_file.suffix == ".pt":
|
| 1038 |
+
raw_mask = torch.load(mask_file, map_location="cpu", weights_only=True)
|
| 1039 |
+
if isinstance(raw_mask, dict):
|
| 1040 |
+
raw_mask = raw_mask.get("mask", next(iter(raw_mask.values())))
|
| 1041 |
+
raw_mask = raw_mask.float().flatten()
|
| 1042 |
+
else:
|
| 1043 |
+
audio = _load_audio_from_file(mask_file)
|
| 1044 |
+
if audio is None:
|
| 1045 |
+
logger.warning(f"Could not load audio mask from {mask_file}")
|
| 1046 |
+
continue
|
| 1047 |
+
raw_mask = audio.waveform.abs().mean(dim=0) # mono amplitude envelope
|
| 1048 |
+
|
| 1049 |
+
# Resample to target audio latent length
|
| 1050 |
+
mask_resampled = torch.nn.functional.interpolate(
|
| 1051 |
+
raw_mask.unsqueeze(0).unsqueeze(0), size=latent_t, mode="nearest"
|
| 1052 |
+
).squeeze() # [latent_t]
|
| 1053 |
+
|
| 1054 |
+
mask_binary = (mask_resampled > 0.5).float()
|
| 1055 |
+
|
| 1056 |
+
out_file.parent.mkdir(parents=True, exist_ok=True)
|
| 1057 |
+
_atomic_save({"mask": mask_binary}, out_file)
|
| 1058 |
+
success += 1
|
| 1059 |
+
|
| 1060 |
+
logger.info(f"Audio mask preprocessing complete: {success} masks saved to {output_path}")
|
| 1061 |
+
|
| 1062 |
+
|
| 1063 |
+
def compute_audio_latents( # noqa: PLR0915
|
| 1064 |
+
dataset_file: str | Path,
|
| 1065 |
+
audio_column: str,
|
| 1066 |
+
output_dir: str,
|
| 1067 |
+
model_path: str,
|
| 1068 |
+
main_media_column: str | None = None,
|
| 1069 |
+
max_duration: float | None = None,
|
| 1070 |
+
duration_buckets: list[float] | None = None,
|
| 1071 |
+
device: str = "cuda",
|
| 1072 |
+
overwrite: bool = False,
|
| 1073 |
+
) -> None:
|
| 1074 |
+
"""Encode audio files into latent representations.
|
| 1075 |
+
Supports standalone audio files (.wav, .mp3, etc.) and audio tracks
|
| 1076 |
+
extracted from video files (.mp4, etc.).
|
| 1077 |
+
Args:
|
| 1078 |
+
dataset_file: Path to metadata file (CSV/JSON/JSONL).
|
| 1079 |
+
audio_column: Column name containing audio file paths.
|
| 1080 |
+
output_dir: Directory to save audio latents.
|
| 1081 |
+
model_path: Path to LTX-2 checkpoint (.safetensors).
|
| 1082 |
+
main_media_column: Column for output file naming (defaults to audio_column).
|
| 1083 |
+
Ensures alignment with other latent directories.
|
| 1084 |
+
max_duration: Maximum audio duration in seconds. Audio is trimmed if longer.
|
| 1085 |
+
Mutually exclusive with duration_buckets.
|
| 1086 |
+
duration_buckets: List of allowed durations in seconds (e.g. [2.0, 4.0, 8.0]).
|
| 1087 |
+
Each audio file is matched to the largest bucket that fits its duration,
|
| 1088 |
+
then trimmed to exactly that length. Files shorter than the smallest
|
| 1089 |
+
bucket are skipped. Ensures uniform lengths for batched training.
|
| 1090 |
+
device: Device to use for computation.
|
| 1091 |
+
"""
|
| 1092 |
+
console = Console()
|
| 1093 |
+
torch_device = torch.device(device)
|
| 1094 |
+
|
| 1095 |
+
dataset_path = Path(dataset_file)
|
| 1096 |
+
data_root = dataset_path.parent
|
| 1097 |
+
output_path = Path(output_dir)
|
| 1098 |
+
output_path.mkdir(parents=True, exist_ok=True)
|
| 1099 |
+
|
| 1100 |
+
naming_column = main_media_column or audio_column
|
| 1101 |
+
audio_paths = _load_paths_from_dataset(dataset_path, audio_column)
|
| 1102 |
+
naming_paths = (
|
| 1103 |
+
_load_paths_from_dataset(dataset_path, naming_column) if naming_column != audio_column else audio_paths
|
| 1104 |
+
)
|
| 1105 |
+
|
| 1106 |
+
with console.status(f"[bold]Loading audio VAE encoder from [cyan]{model_path}[/]...", spinner="dots"):
|
| 1107 |
+
audio_vae_encoder = load_audio_vae_encoder(
|
| 1108 |
+
checkpoint_path=model_path,
|
| 1109 |
+
device=torch_device,
|
| 1110 |
+
dtype=torch.float32,
|
| 1111 |
+
)
|
| 1112 |
+
audio_processor = AudioProcessor(
|
| 1113 |
+
target_sample_rate=audio_vae_encoder.sample_rate,
|
| 1114 |
+
mel_bins=audio_vae_encoder.mel_bins,
|
| 1115 |
+
mel_hop_length=audio_vae_encoder.mel_hop_length,
|
| 1116 |
+
n_fft=audio_vae_encoder.n_fft,
|
| 1117 |
+
).to(torch_device)
|
| 1118 |
+
|
| 1119 |
+
sorted_buckets = sorted(duration_buckets, reverse=True) if duration_buckets else None
|
| 1120 |
+
success_count = 0
|
| 1121 |
+
skip_count = 0
|
| 1122 |
+
|
| 1123 |
+
with Progress(
|
| 1124 |
+
SpinnerColumn(),
|
| 1125 |
+
TextColumn("[progress.description]{task.description}"),
|
| 1126 |
+
BarColumn(),
|
| 1127 |
+
TaskProgressColumn(),
|
| 1128 |
+
MofNCompleteColumn(),
|
| 1129 |
+
TimeElapsedColumn(),
|
| 1130 |
+
TimeRemainingColumn(),
|
| 1131 |
+
console=console,
|
| 1132 |
+
) as progress:
|
| 1133 |
+
task = progress.add_task("Encoding audio", total=len(audio_paths))
|
| 1134 |
+
|
| 1135 |
+
for audio_path, naming_path in zip(audio_paths, naming_paths, strict=True):
|
| 1136 |
+
rel_path = _output_relative(naming_path, data_root)
|
| 1137 |
+
output_file = output_path / rel_path.with_suffix(".pt")
|
| 1138 |
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
| 1139 |
+
|
| 1140 |
+
if not overwrite and output_file.is_file():
|
| 1141 |
+
success_count += 1
|
| 1142 |
+
progress.advance(task)
|
| 1143 |
+
continue
|
| 1144 |
+
|
| 1145 |
+
# Load audio (no trimming yet — need full duration for bucket matching)
|
| 1146 |
+
audio = _load_audio_from_file(audio_path)
|
| 1147 |
+
if audio is None:
|
| 1148 |
+
skip_count += 1
|
| 1149 |
+
progress.advance(task)
|
| 1150 |
+
continue
|
| 1151 |
+
|
| 1152 |
+
file_duration = audio.waveform.shape[-1] / audio.sampling_rate
|
| 1153 |
+
|
| 1154 |
+
# Determine target duration: bucket matching, max_duration cap, or full file
|
| 1155 |
+
target_duration = file_duration
|
| 1156 |
+
if sorted_buckets:
|
| 1157 |
+
bucket = next((b for b in sorted_buckets if b <= file_duration), None)
|
| 1158 |
+
if bucket is None:
|
| 1159 |
+
logger.warning(
|
| 1160 |
+
f"Skipping {audio_path.name} ({file_duration:.1f}s) — shorter than "
|
| 1161 |
+
f"smallest bucket ({sorted_buckets[-1]:.1f}s)"
|
| 1162 |
+
)
|
| 1163 |
+
skip_count += 1
|
| 1164 |
+
progress.advance(task)
|
| 1165 |
+
continue
|
| 1166 |
+
target_duration = bucket
|
| 1167 |
+
elif max_duration is not None:
|
| 1168 |
+
target_duration = min(file_duration, max_duration)
|
| 1169 |
+
|
| 1170 |
+
# Trim to target duration
|
| 1171 |
+
target_samples = int(target_duration * audio.sampling_rate)
|
| 1172 |
+
trimmed_waveform = audio.waveform[:, :target_samples]
|
| 1173 |
+
audio = Audio(waveform=trimmed_waveform, sampling_rate=audio.sampling_rate)
|
| 1174 |
+
|
| 1175 |
+
with torch.inference_mode():
|
| 1176 |
+
audio_latents = _encode_audio(audio_vae_encoder, audio_processor, audio)
|
| 1177 |
+
|
| 1178 |
+
_atomic_save(
|
| 1179 |
+
{
|
| 1180 |
+
"latents": audio_latents["latents"].cpu().contiguous(),
|
| 1181 |
+
"num_time_steps": audio_latents["num_time_steps"],
|
| 1182 |
+
"frequency_bins": audio_latents["frequency_bins"],
|
| 1183 |
+
"duration": audio_latents["duration"],
|
| 1184 |
+
},
|
| 1185 |
+
output_file,
|
| 1186 |
+
)
|
| 1187 |
+
success_count += 1
|
| 1188 |
+
progress.advance(task)
|
| 1189 |
+
|
| 1190 |
+
logger.info(f"Audio encoding complete: {success_count} encoded, {skip_count} skipped. Saved to {output_path}")
|
| 1191 |
+
|
| 1192 |
+
|
| 1193 |
+
def _output_relative(path: Path, data_root: Path) -> Path:
|
| 1194 |
+
"""Relative path used to name a sample's cached output, mirroring the input layout.
|
| 1195 |
+
Normally media lives under the dataset directory and this is just the path relative to it.
|
| 1196 |
+
If a media path is absolute or otherwise outside the dataset directory (e.g. a one-off
|
| 1197 |
+
metadata file that references media elsewhere), mirror its absolute structure under the
|
| 1198 |
+
output directory instead of raising, so out-of-tree media stays collision-free.
|
| 1199 |
+
"""
|
| 1200 |
+
try:
|
| 1201 |
+
return path.relative_to(data_root)
|
| 1202 |
+
except ValueError:
|
| 1203 |
+
return Path(*path.parts[1:]) if path.is_absolute() else path
|
| 1204 |
+
|
| 1205 |
+
|
| 1206 |
+
def _load_paths_from_dataset(dataset_file: Path, column: str) -> list[Path]:
|
| 1207 |
+
"""Load file paths from a dataset column, resolving relative to the dataset file's directory."""
|
| 1208 |
+
data_root = dataset_file.parent
|
| 1209 |
+
|
| 1210 |
+
if dataset_file.suffix == ".csv":
|
| 1211 |
+
df = pd.read_csv(dataset_file)
|
| 1212 |
+
if column not in df.columns:
|
| 1213 |
+
raise ValueError(f"Column '{column}' not found in CSV file")
|
| 1214 |
+
return [data_root / Path(str(v).strip()) for v in df[column].tolist()]
|
| 1215 |
+
|
| 1216 |
+
if dataset_file.suffix == ".json":
|
| 1217 |
+
with open(dataset_file, encoding="utf-8") as f:
|
| 1218 |
+
data = json.load(f)
|
| 1219 |
+
if not isinstance(data, list):
|
| 1220 |
+
raise ValueError("JSON file must contain a list of objects")
|
| 1221 |
+
return [data_root / Path(entry[column].strip()) for entry in data]
|
| 1222 |
+
|
| 1223 |
+
if dataset_file.suffix == ".jsonl":
|
| 1224 |
+
paths = []
|
| 1225 |
+
with open(dataset_file, encoding="utf-8") as f:
|
| 1226 |
+
for line in f:
|
| 1227 |
+
entry = json.loads(line)
|
| 1228 |
+
paths.append(data_root / Path(entry[column].strip()))
|
| 1229 |
+
return paths
|
| 1230 |
+
|
| 1231 |
+
raise ValueError(f"Unsupported dataset format: {dataset_file.suffix}")
|
| 1232 |
+
|
| 1233 |
+
|
| 1234 |
+
def _load_audio_from_file(audio_path: Path, max_duration: float | None = None) -> Audio | None:
|
| 1235 |
+
"""Load audio from an audio or video file, optionally trimming to max_duration."""
|
| 1236 |
+
try:
|
| 1237 |
+
waveform, sample_rate = torchaudio.load(str(audio_path))
|
| 1238 |
+
except Exception:
|
| 1239 |
+
logger.debug(f"Could not load audio from {audio_path}")
|
| 1240 |
+
return None
|
| 1241 |
+
|
| 1242 |
+
if max_duration is not None:
|
| 1243 |
+
max_samples = int(max_duration * sample_rate)
|
| 1244 |
+
if waveform.shape[-1] > max_samples:
|
| 1245 |
+
waveform = waveform[:, :max_samples]
|
| 1246 |
+
|
| 1247 |
+
return Audio(waveform=waveform, sampling_rate=sample_rate)
|
| 1248 |
+
|
| 1249 |
+
|
| 1250 |
+
def detect_dataset_columns(dataset_file: str | Path) -> set[str]:
|
| 1251 |
+
"""Read column names from a dataset file without loading all data."""
|
| 1252 |
+
path = Path(dataset_file)
|
| 1253 |
+
if path.suffix == ".csv":
|
| 1254 |
+
df = pd.read_csv(path, nrows=0)
|
| 1255 |
+
return set(df.columns)
|
| 1256 |
+
if path.suffix == ".json":
|
| 1257 |
+
with open(path, encoding="utf-8") as f:
|
| 1258 |
+
data = json.load(f)
|
| 1259 |
+
return set(data[0].keys()) if isinstance(data, list) and data else set()
|
| 1260 |
+
if path.suffix == ".jsonl":
|
| 1261 |
+
with open(path, encoding="utf-8") as f:
|
| 1262 |
+
return set(json.loads(f.readline()).keys())
|
| 1263 |
+
return set()
|
| 1264 |
+
|
| 1265 |
+
|
| 1266 |
+
def parse_resolution_buckets(resolution_buckets_str: str) -> list[tuple[int, int, int]]:
|
| 1267 |
+
"""Parse resolution buckets from string format to list of tuples (frames, height, width)"""
|
| 1268 |
+
resolution_buckets = []
|
| 1269 |
+
for bucket_str in resolution_buckets_str.split(";"):
|
| 1270 |
+
w, h, f = map(int, bucket_str.split("x"))
|
| 1271 |
+
|
| 1272 |
+
if w % VAE_SPATIAL_FACTOR != 0 or h % VAE_SPATIAL_FACTOR != 0:
|
| 1273 |
+
raise typer.BadParameter(
|
| 1274 |
+
f"Width and height must be multiples of {VAE_SPATIAL_FACTOR}, got {w}x{h}",
|
| 1275 |
+
param_hint="resolution-buckets",
|
| 1276 |
+
)
|
| 1277 |
+
|
| 1278 |
+
if f % VAE_TEMPORAL_FACTOR != 1:
|
| 1279 |
+
raise typer.BadParameter(
|
| 1280 |
+
f"Number of frames must be a multiple of {VAE_TEMPORAL_FACTOR} plus 1, got {f}",
|
| 1281 |
+
param_hint="resolution-buckets",
|
| 1282 |
+
)
|
| 1283 |
+
|
| 1284 |
+
resolution_buckets.append((f, h, w))
|
| 1285 |
+
return resolution_buckets
|
| 1286 |
+
|
| 1287 |
+
|
| 1288 |
+
def compute_scaled_resolution_buckets(
|
| 1289 |
+
resolution_buckets: list[tuple[int, int, int]],
|
| 1290 |
+
scale_factor: int,
|
| 1291 |
+
) -> list[tuple[int, int, int]]:
|
| 1292 |
+
"""Compute scaled resolution buckets and validate the results."""
|
| 1293 |
+
if scale_factor == 1:
|
| 1294 |
+
return resolution_buckets
|
| 1295 |
+
|
| 1296 |
+
scaled_buckets = []
|
| 1297 |
+
for frames, height, width in resolution_buckets:
|
| 1298 |
+
# Validate that scale factor evenly divides the dimensions
|
| 1299 |
+
if height % scale_factor != 0:
|
| 1300 |
+
raise ValueError(
|
| 1301 |
+
f"Height {height} is not evenly divisible by scale factor {scale_factor}. "
|
| 1302 |
+
f"Choose a scale factor that divides {height} evenly."
|
| 1303 |
+
)
|
| 1304 |
+
if width % scale_factor != 0:
|
| 1305 |
+
raise ValueError(
|
| 1306 |
+
f"Width {width} is not evenly divisible by scale factor {scale_factor}. "
|
| 1307 |
+
f"Choose a scale factor that divides {width} evenly."
|
| 1308 |
+
)
|
| 1309 |
+
|
| 1310 |
+
scaled_height = height // scale_factor
|
| 1311 |
+
scaled_width = width // scale_factor
|
| 1312 |
+
|
| 1313 |
+
# Validate scaled dimensions are divisible by VAE spatial factor
|
| 1314 |
+
if scaled_height % VAE_SPATIAL_FACTOR != 0:
|
| 1315 |
+
raise ValueError(
|
| 1316 |
+
f"Scaled height {scaled_height} (from {height} / {scale_factor}) "
|
| 1317 |
+
f"is not divisible by {VAE_SPATIAL_FACTOR}. "
|
| 1318 |
+
f"Choose a different scale factor or adjust your resolution buckets."
|
| 1319 |
+
)
|
| 1320 |
+
if scaled_width % VAE_SPATIAL_FACTOR != 0:
|
| 1321 |
+
raise ValueError(
|
| 1322 |
+
f"Scaled width {scaled_width} (from {width} / {scale_factor}) "
|
| 1323 |
+
f"is not divisible by {VAE_SPATIAL_FACTOR}. "
|
| 1324 |
+
f"Choose a different scale factor or adjust your resolution buckets."
|
| 1325 |
+
)
|
| 1326 |
+
|
| 1327 |
+
scaled_buckets.append((frames, scaled_height, scaled_width))
|
| 1328 |
+
|
| 1329 |
+
return scaled_buckets
|
| 1330 |
+
|
| 1331 |
+
|
| 1332 |
+
def _atomic_save(data: Any, out: Path) -> None: # noqa: ANN401
|
| 1333 |
+
"""Save to ``out`` atomically via per-PID temp file + replace.
|
| 1334 |
+
Crash mid-write leaves an orphan ``.tmp.<pid>`` file that the skip logic
|
| 1335 |
+
ignores. The per-PID suffix makes concurrent writes from multiple ranks
|
| 1336 |
+
collision-free.
|
| 1337 |
+
"""
|
| 1338 |
+
tmp = out.with_suffix(f"{out.suffix}.tmp.{os.getpid()}")
|
| 1339 |
+
torch.save(data, tmp)
|
| 1340 |
+
tmp.replace(out)
|
| 1341 |
+
|
| 1342 |
+
|
| 1343 |
+
def _build_sharded_dataloader(
|
| 1344 |
+
dataset: Dataset,
|
| 1345 |
+
*,
|
| 1346 |
+
batch_size: int,
|
| 1347 |
+
num_workers: int,
|
| 1348 |
+
is_done: Callable[[int], bool],
|
| 1349 |
+
overwrite: bool,
|
| 1350 |
+
) -> DataLoader | None:
|
| 1351 |
+
"""Return a DataLoader over this rank's interleaved shard of ``dataset``.
|
| 1352 |
+
When ``overwrite`` is False, items whose outputs already exist (per
|
| 1353 |
+
``is_done``) are filtered out. Returns ``None`` if this rank has nothing
|
| 1354 |
+
to do, so the caller can early-return without loading any models.
|
| 1355 |
+
"""
|
| 1356 |
+
state = PartialState()
|
| 1357 |
+
todo = [i for i in range(state.process_index, len(dataset), state.num_processes) if overwrite or not is_done(i)]
|
| 1358 |
+
if not todo:
|
| 1359 |
+
logger.info(f"Rank {state.process_index}/{state.num_processes}: nothing to do")
|
| 1360 |
+
return None
|
| 1361 |
+
logger.info(f"Rank {state.process_index}/{state.num_processes}: processing {len(todo):,} of {len(dataset):,} items")
|
| 1362 |
+
return DataLoader(Subset(dataset, todo), batch_size=batch_size, shuffle=False, num_workers=num_workers)
|
| 1363 |
+
|
| 1364 |
+
|
| 1365 |
+
@app.command()
|
| 1366 |
+
def main( # noqa: PLR0913
|
| 1367 |
+
dataset_file: str = typer.Argument(
|
| 1368 |
+
...,
|
| 1369 |
+
help="Path to metadata file (CSV/JSON/JSONL) containing video paths",
|
| 1370 |
+
),
|
| 1371 |
+
resolution_buckets: str = typer.Option(
|
| 1372 |
+
...,
|
| 1373 |
+
help='Resolution buckets in format "WxHxF;WxHxF;..." (e.g. "768x768x25;512x512x49")',
|
| 1374 |
+
),
|
| 1375 |
+
output_dir: str = typer.Option(
|
| 1376 |
+
...,
|
| 1377 |
+
help="Output directory to save video latents",
|
| 1378 |
+
),
|
| 1379 |
+
model_path: str = typer.Option(
|
| 1380 |
+
...,
|
| 1381 |
+
help="Path to LTX-2 checkpoint (.safetensors file)",
|
| 1382 |
+
),
|
| 1383 |
+
video_column: str = typer.Option(
|
| 1384 |
+
default="media_path",
|
| 1385 |
+
help="Column name in the dataset JSON/JSONL/CSV file containing video paths",
|
| 1386 |
+
),
|
| 1387 |
+
batch_size: int = typer.Option(
|
| 1388 |
+
default=1,
|
| 1389 |
+
help="Batch size for processing",
|
| 1390 |
+
),
|
| 1391 |
+
device: str = typer.Option(
|
| 1392 |
+
default="cuda",
|
| 1393 |
+
help="Device to use for computation",
|
| 1394 |
+
),
|
| 1395 |
+
vae_tiling: bool = typer.Option(
|
| 1396 |
+
default=False,
|
| 1397 |
+
help="Enable VAE tiling for larger video resolutions",
|
| 1398 |
+
),
|
| 1399 |
+
reshape_mode: str = typer.Option(
|
| 1400 |
+
default="center",
|
| 1401 |
+
help="How to crop videos: 'center' or 'random'",
|
| 1402 |
+
),
|
| 1403 |
+
with_audio: bool = typer.Option(
|
| 1404 |
+
default=False,
|
| 1405 |
+
help="Extract and encode audio from video files",
|
| 1406 |
+
),
|
| 1407 |
+
audio_output_dir: str | None = typer.Option(
|
| 1408 |
+
default=None,
|
| 1409 |
+
help="Output directory for audio latents (required if --with-audio is set)",
|
| 1410 |
+
),
|
| 1411 |
+
overwrite: bool = typer.Option(
|
| 1412 |
+
default=False,
|
| 1413 |
+
help="Re-encode every item even if its output exists. Use when rerunning with "
|
| 1414 |
+
"changed parameters (different model, resolution, etc.) so stale outputs are replaced.",
|
| 1415 |
+
),
|
| 1416 |
+
) -> None:
|
| 1417 |
+
"""Process videos/images and save latent representations for video generation training.
|
| 1418 |
+
This script processes videos and images from metadata files and saves latent representations
|
| 1419 |
+
that can be used for training video generation models. The output latents will maintain
|
| 1420 |
+
the same folder structure and naming as the corresponding media files.
|
| 1421 |
+
For multi-GPU preprocessing, invoke under ``accelerate launch`` -- each process
|
| 1422 |
+
will handle an interleaved shard of the dataset.
|
| 1423 |
+
Examples:
|
| 1424 |
+
# Process videos from a CSV file
|
| 1425 |
+
python scripts/process_videos.py dataset.csv --resolution-buckets 768x768x25 \\
|
| 1426 |
+
--output-dir ./latents --model-path /path/to/ltx2.safetensors
|
| 1427 |
+
# Process videos from a JSON file with custom video column
|
| 1428 |
+
python scripts/process_videos.py dataset.json --resolution-buckets 768x768x25 \\
|
| 1429 |
+
--output-dir ./latents --model-path /path/to/ltx2.safetensors --video-column "video_path"
|
| 1430 |
+
# Enable VAE tiling to save GPU VRAM
|
| 1431 |
+
python scripts/process_videos.py dataset.csv --resolution-buckets 1024x1024x25 \\
|
| 1432 |
+
--output-dir ./latents --model-path /path/to/ltx2.safetensors --vae-tiling
|
| 1433 |
+
# Process videos with audio
|
| 1434 |
+
python scripts/process_videos.py dataset.csv --resolution-buckets 768x768x25 \\
|
| 1435 |
+
--output-dir ./latents --model-path /path/to/ltx2.safetensors \\
|
| 1436 |
+
--with-audio --audio-output-dir ./audio_latents
|
| 1437 |
+
"""
|
| 1438 |
+
|
| 1439 |
+
# Validate dataset file exists
|
| 1440 |
+
if not Path(dataset_file).is_file():
|
| 1441 |
+
raise typer.BadParameter(f"Dataset file not found: {dataset_file}")
|
| 1442 |
+
|
| 1443 |
+
# Validate audio parameters
|
| 1444 |
+
if with_audio and audio_output_dir is None:
|
| 1445 |
+
raise typer.BadParameter("--audio-output-dir is required when --with-audio is set")
|
| 1446 |
+
|
| 1447 |
+
# Parse resolution buckets
|
| 1448 |
+
parsed_resolution_buckets = parse_resolution_buckets(resolution_buckets)
|
| 1449 |
+
|
| 1450 |
+
if len(parsed_resolution_buckets) > 1:
|
| 1451 |
+
logger.warning(
|
| 1452 |
+
"Using multiple resolution buckets. "
|
| 1453 |
+
"When training with multiple resolution buckets, you must use a batch size of 1."
|
| 1454 |
+
)
|
| 1455 |
+
|
| 1456 |
+
# Process latents
|
| 1457 |
+
compute_latents(
|
| 1458 |
+
dataset_file=dataset_file,
|
| 1459 |
+
video_column=video_column,
|
| 1460 |
+
resolution_buckets=parsed_resolution_buckets,
|
| 1461 |
+
output_dir=output_dir,
|
| 1462 |
+
model_path=model_path,
|
| 1463 |
+
reshape_mode=reshape_mode,
|
| 1464 |
+
batch_size=batch_size,
|
| 1465 |
+
device=device,
|
| 1466 |
+
vae_tiling=vae_tiling,
|
| 1467 |
+
with_audio=with_audio,
|
| 1468 |
+
audio_output_dir=audio_output_dir,
|
| 1469 |
+
overwrite=overwrite,
|
| 1470 |
+
)
|
| 1471 |
+
|
| 1472 |
+
|
| 1473 |
+
if __name__ == "__main__":
|
| 1474 |
+
app()
|
packages/ltx-trainer/scripts/serve_captioner.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
"""Launch a vLLM server for Qwen3-Omni captioning.
|
| 4 |
+
Runs the actual server via ``uvx`` so that vLLM and its CUDA-tied
|
| 5 |
+
dependencies live in their own isolated environment (no impact on this
|
| 6 |
+
package's dependency tree).
|
| 7 |
+
The captioning script (``caption_videos.py``) talks to the server over its
|
| 8 |
+
OpenAI-compatible HTTP API. Once the server is up it stays loaded across
|
| 9 |
+
captioning runs; no per-script model warmup cost.
|
| 10 |
+
Typical usage::
|
| 11 |
+
# Default: dynamic FP8 quantization, listen on 127.0.0.1:8001
|
| 12 |
+
uv run python scripts/serve_captioner.py
|
| 13 |
+
# Just print the chosen `uvx vllm serve ...` command without running it
|
| 14 |
+
uv run python scripts/serve_captioner.py --print-cmd
|
| 15 |
+
# Use full bf16 on a GPU with >= 66 GiB free VRAM (slightly more reliable
|
| 16 |
+
# numerics but 2x the weight memory)
|
| 17 |
+
uv run python scripts/serve_captioner.py --quantization bf16
|
| 18 |
+
# Use a different port or expose on all interfaces
|
| 19 |
+
uv run python scripts/serve_captioner.py --port 9000 --host 0.0.0.0
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
import os
|
| 23 |
+
import shutil
|
| 24 |
+
import subprocess
|
| 25 |
+
import sys
|
| 26 |
+
from pathlib import Path
|
| 27 |
+
|
| 28 |
+
import typer
|
| 29 |
+
from rich.console import Console
|
| 30 |
+
|
| 31 |
+
console = Console()
|
| 32 |
+
|
| 33 |
+
# Model identifier we serve. The captioner client must use the same string.
|
| 34 |
+
DEFAULT_MODEL = "Qwen/Qwen3-Omni-30B-A3B-Thinking"
|
| 35 |
+
|
| 36 |
+
# Pinned vLLM version known to support Qwen3-Omni on CUDA 12.x.
|
| 37 |
+
# vLLM 0.20+ requires CUDA 13. Update both as the environment evolves.
|
| 38 |
+
# The ``[audio]`` extra is required for Qwen3-Omni to decode audio at all.
|
| 39 |
+
DEFAULT_VLLM_SPEC = "vllm[audio]==0.11.2"
|
| 40 |
+
|
| 41 |
+
# Approximate disk needed for the model download (HF cache structure).
|
| 42 |
+
MODEL_DISK_GIB = 65.0
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
app = typer.Typer(
|
| 46 |
+
pretty_exceptions_enable=False,
|
| 47 |
+
no_args_is_help=False,
|
| 48 |
+
help="Launch a local vLLM server for Qwen3-Omni captioning.",
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _query_disk_free_gib(path: Path) -> float:
|
| 53 |
+
return shutil.disk_usage(str(path)).free / 1024**3
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _build_vllm_args(
|
| 57 |
+
*,
|
| 58 |
+
model: str,
|
| 59 |
+
host: str,
|
| 60 |
+
port: int,
|
| 61 |
+
quantization: str,
|
| 62 |
+
max_model_len: int,
|
| 63 |
+
gpu_memory_utilization: float,
|
| 64 |
+
extra_args: list[str],
|
| 65 |
+
) -> list[str]:
|
| 66 |
+
"""Construct the `vllm serve ...` argv."""
|
| 67 |
+
args = [
|
| 68 |
+
"vllm",
|
| 69 |
+
"serve",
|
| 70 |
+
model,
|
| 71 |
+
"--host",
|
| 72 |
+
host,
|
| 73 |
+
"--port",
|
| 74 |
+
str(port),
|
| 75 |
+
"--dtype",
|
| 76 |
+
"bfloat16",
|
| 77 |
+
"--max-model-len",
|
| 78 |
+
str(max_model_len),
|
| 79 |
+
"--gpu-memory-utilization",
|
| 80 |
+
str(gpu_memory_utilization),
|
| 81 |
+
# Let the server accept ``file://`` URLs pointing at local videos.
|
| 82 |
+
"--allowed-local-media-path",
|
| 83 |
+
"/",
|
| 84 |
+
# The model is a multimodal MoE; cap each input to one of each
|
| 85 |
+
# modality to match what our captioner sends.
|
| 86 |
+
"--limit-mm-per-prompt",
|
| 87 |
+
'{"image": 1, "video": 1, "audio": 1}',
|
| 88 |
+
# Small concurrent-sequence cap so KV cache headroom isn't fragmented.
|
| 89 |
+
"--max-num-seqs",
|
| 90 |
+
"4",
|
| 91 |
+
]
|
| 92 |
+
if quantization == "fp8":
|
| 93 |
+
args += ["--quantization", "fp8"]
|
| 94 |
+
args += extra_args
|
| 95 |
+
return args
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
@app.command()
|
| 99 |
+
def main(
|
| 100 |
+
model: str = typer.Option(DEFAULT_MODEL, "--model", help="Model identifier to serve."),
|
| 101 |
+
host: str = typer.Option("127.0.0.1", "--host", help="Listen address. Use 0.0.0.0 for remote access."),
|
| 102 |
+
port: int = typer.Option(8001, "--port", help="HTTP port."),
|
| 103 |
+
quantization: str = typer.Option(
|
| 104 |
+
"fp8",
|
| 105 |
+
"--quantization",
|
| 106 |
+
"-q",
|
| 107 |
+
help=(
|
| 108 |
+
"Weight precision. 'fp8' (default, dynamic FP8 -- ~31 GiB weights) is "
|
| 109 |
+
"the recommended choice; it fits on 40 GiB GPUs and runs at the same "
|
| 110 |
+
"speed as bf16 on H100. 'bf16' uses ~60 GiB of weights -- pick it if "
|
| 111 |
+
"you have abundant VRAM and want minimal numerical drift."
|
| 112 |
+
),
|
| 113 |
+
),
|
| 114 |
+
max_model_len: int = typer.Option(
|
| 115 |
+
32768,
|
| 116 |
+
"--max-model-len",
|
| 117 |
+
help="Maximum context length the server accepts (must fit input video tokens + max_tokens).",
|
| 118 |
+
),
|
| 119 |
+
gpu_memory_utilization: float = typer.Option(
|
| 120 |
+
0.9,
|
| 121 |
+
"--gpu-memory-utilization",
|
| 122 |
+
help="Fraction of GPU memory vLLM may reserve (model + KV cache).",
|
| 123 |
+
),
|
| 124 |
+
hf_home: Path | None = typer.Option( # noqa: B008
|
| 125 |
+
None,
|
| 126 |
+
"--hf-home",
|
| 127 |
+
help=(
|
| 128 |
+
"Override HF_HOME (where the model is downloaded). The model is ~65 GB; "
|
| 129 |
+
"by default this follows your environment's HF_HOME or HuggingFace's default."
|
| 130 |
+
),
|
| 131 |
+
),
|
| 132 |
+
vllm_spec: str = typer.Option(
|
| 133 |
+
DEFAULT_VLLM_SPEC,
|
| 134 |
+
"--vllm-spec",
|
| 135 |
+
help="pip-style spec passed to `uvx --from`. Pin a version that matches your CUDA.",
|
| 136 |
+
),
|
| 137 |
+
print_cmd: bool = typer.Option(
|
| 138 |
+
False,
|
| 139 |
+
"--print-cmd",
|
| 140 |
+
help="Print the chosen command without running it.",
|
| 141 |
+
),
|
| 142 |
+
extra_args: list[str] | None = typer.Argument( # noqa: B008
|
| 143 |
+
None,
|
| 144 |
+
help="Additional args passed through to `vllm serve` after `--`.",
|
| 145 |
+
),
|
| 146 |
+
) -> None:
|
| 147 |
+
"""Launch the vLLM server for Qwen3-Omni."""
|
| 148 |
+
extra = extra_args or []
|
| 149 |
+
|
| 150 |
+
if quantization not in ("bf16", "fp8"):
|
| 151 |
+
console.print(f"[red]--quantization must be 'bf16' or 'fp8'; got {quantization!r}.[/]")
|
| 152 |
+
raise typer.Exit(code=1)
|
| 153 |
+
|
| 154 |
+
# Disk check (only meaningful before first download).
|
| 155 |
+
cache_root = hf_home or Path(os.environ.get("HF_HOME", str(Path.home() / ".cache" / "huggingface")))
|
| 156 |
+
cache_root.mkdir(parents=True, exist_ok=True)
|
| 157 |
+
free_disk = _query_disk_free_gib(cache_root)
|
| 158 |
+
if free_disk < MODEL_DISK_GIB:
|
| 159 |
+
console.print(
|
| 160 |
+
f"[yellow]\u26a0 Only {free_disk:.1f} GiB free on disk under {cache_root} but the "
|
| 161 |
+
f"model needs ~{MODEL_DISK_GIB:.0f} GiB. Either free up space, set --hf-home "
|
| 162 |
+
f"to a larger volume, or expect the download to fail mid-way.[/]"
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
vllm_args = _build_vllm_args(
|
| 166 |
+
model=model,
|
| 167 |
+
host=host,
|
| 168 |
+
port=port,
|
| 169 |
+
quantization=quantization,
|
| 170 |
+
max_model_len=max_model_len,
|
| 171 |
+
gpu_memory_utilization=gpu_memory_utilization,
|
| 172 |
+
extra_args=extra,
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
# Use ``uvx --from vllm==...`` so vLLM lives in its own throwaway venv
|
| 176 |
+
# (or a cached tool venv). The `--` separates uvx args from the command's.
|
| 177 |
+
uvx_cmd = ["uvx", "--from", vllm_spec, *vllm_args]
|
| 178 |
+
|
| 179 |
+
env = os.environ.copy()
|
| 180 |
+
# vLLM 0.11.x requires the V0 engine for Qwen3-Omni's multimodal pipeline.
|
| 181 |
+
env.setdefault("VLLM_USE_V1", "0")
|
| 182 |
+
if hf_home is not None:
|
| 183 |
+
env["HF_HOME"] = str(hf_home)
|
| 184 |
+
|
| 185 |
+
console.print("\n[bold]Command:[/]")
|
| 186 |
+
console.print(" " + " ".join(uvx_cmd))
|
| 187 |
+
if hf_home is not None:
|
| 188 |
+
console.print(f" [dim](with HF_HOME={hf_home})[/]")
|
| 189 |
+
|
| 190 |
+
if print_cmd:
|
| 191 |
+
return
|
| 192 |
+
|
| 193 |
+
console.print("\n[dim]Launching... (first run downloads the model -- ~5 min on a fast link)[/]\n")
|
| 194 |
+
try:
|
| 195 |
+
completed = subprocess.run(uvx_cmd, env=env, check=False)
|
| 196 |
+
except KeyboardInterrupt:
|
| 197 |
+
console.print("\n[yellow]Interrupted.[/]")
|
| 198 |
+
return
|
| 199 |
+
sys.exit(completed.returncode)
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
if __name__ == "__main__":
|
| 203 |
+
app()
|
packages/ltx-trainer/scripts/split_scenes.py
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
Split video into scenes using PySceneDetect.
|
| 5 |
+
This script provides a command-line interface for splitting videos into scenes using various detection algorithms.
|
| 6 |
+
It supports multiple detection methods, preview image generation, and customizable parameters for fine-tuning
|
| 7 |
+
the scene detection process.
|
| 8 |
+
Basic usage:
|
| 9 |
+
# Split video using default content-based detection
|
| 10 |
+
scenes_split.py input.mp4 output_dir/
|
| 11 |
+
# Save 3 preview images per scene
|
| 12 |
+
scenes_split.py input.mp4 output_dir/ --save-images 3
|
| 13 |
+
# Process specific duration and filter short scenes
|
| 14 |
+
scenes_split.py input.mp4 output_dir/ --duration 60s --filter-shorter-than 2s
|
| 15 |
+
Advanced usage:
|
| 16 |
+
# Content detection with minimum scene length and frame skip
|
| 17 |
+
scenes_split.py input.mp4 output_dir/ --detector content --min-scene-length 30 --frame-skip 2
|
| 18 |
+
# Use adaptive detection with custom detector and detector parameters
|
| 19 |
+
scenes_split.py input.mp4 output_dir/ --detector adaptive --threshold 3.0 --adaptive-window 10
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
from enum import Enum
|
| 23 |
+
from pathlib import Path
|
| 24 |
+
from typing import List, Optional, Tuple
|
| 25 |
+
|
| 26 |
+
import typer
|
| 27 |
+
from scenedetect import (
|
| 28 |
+
AdaptiveDetector,
|
| 29 |
+
ContentDetector,
|
| 30 |
+
HistogramDetector,
|
| 31 |
+
SceneManager,
|
| 32 |
+
ThresholdDetector,
|
| 33 |
+
open_video,
|
| 34 |
+
)
|
| 35 |
+
from scenedetect.frame_timecode import FrameTimecode
|
| 36 |
+
from scenedetect.scene_manager import SceneDetector, write_scene_list_html
|
| 37 |
+
from scenedetect.scene_manager import save_images as save_scene_images
|
| 38 |
+
from scenedetect.stats_manager import StatsManager
|
| 39 |
+
from scenedetect.video_splitter import split_video_ffmpeg
|
| 40 |
+
|
| 41 |
+
app = typer.Typer(no_args_is_help=True, help="Split video into scenes using PySceneDetect.")
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class DetectorType(str, Enum):
|
| 45 |
+
"""Available scene detection algorithms."""
|
| 46 |
+
|
| 47 |
+
CONTENT = "content" # Detects fast cuts using HSV color space
|
| 48 |
+
ADAPTIVE = "adaptive" # Detects fast two-phase cuts
|
| 49 |
+
THRESHOLD = "threshold" # Detects fast cuts/slow fades in from and out to a given threshold level
|
| 50 |
+
HISTOGRAM = "histogram" # Detects based on YUV histogram differences in adjacent frames
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def create_detector(
|
| 54 |
+
detector_type: DetectorType,
|
| 55 |
+
threshold: Optional[float] = None,
|
| 56 |
+
min_scene_len: Optional[int] = None,
|
| 57 |
+
luma_only: Optional[bool] = None,
|
| 58 |
+
adaptive_window: Optional[int] = None,
|
| 59 |
+
fade_bias: Optional[float] = None,
|
| 60 |
+
) -> SceneDetector:
|
| 61 |
+
"""Create a scene detector based on the specified type and parameters.
|
| 62 |
+
Args:
|
| 63 |
+
detector_type: Type of detector to create
|
| 64 |
+
threshold: Detection threshold (meaning varies by detector)
|
| 65 |
+
min_scene_len: Minimum scene length in frames
|
| 66 |
+
luma_only: If True, only use brightness for content detection
|
| 67 |
+
adaptive_window: Window size for adaptive detection
|
| 68 |
+
fade_bias: Bias for fade in/out detection (-1.0 to 1.0)
|
| 69 |
+
Note: Parameters set to None will use the detector's built-in default values.
|
| 70 |
+
Returns:
|
| 71 |
+
Configured scene detector instance
|
| 72 |
+
"""
|
| 73 |
+
# Set common arguments
|
| 74 |
+
kwargs = {}
|
| 75 |
+
if threshold is not None:
|
| 76 |
+
kwargs["threshold"] = threshold
|
| 77 |
+
|
| 78 |
+
if min_scene_len is not None:
|
| 79 |
+
kwargs["min_scene_len"] = min_scene_len
|
| 80 |
+
|
| 81 |
+
match detector_type:
|
| 82 |
+
case DetectorType.CONTENT:
|
| 83 |
+
if luma_only is not None:
|
| 84 |
+
kwargs["luma_only"] = luma_only
|
| 85 |
+
return ContentDetector(**kwargs)
|
| 86 |
+
case DetectorType.ADAPTIVE:
|
| 87 |
+
if adaptive_window is not None:
|
| 88 |
+
kwargs["window_width"] = adaptive_window
|
| 89 |
+
if luma_only is not None:
|
| 90 |
+
kwargs["luma_only"] = luma_only
|
| 91 |
+
if "threshold" in kwargs:
|
| 92 |
+
# Special case for adaptive detector which uses different param name
|
| 93 |
+
kwargs["adaptive_threshold"] = kwargs.pop("threshold")
|
| 94 |
+
return AdaptiveDetector(**kwargs)
|
| 95 |
+
case DetectorType.THRESHOLD:
|
| 96 |
+
if fade_bias is not None:
|
| 97 |
+
kwargs["fade_bias"] = fade_bias
|
| 98 |
+
return ThresholdDetector(**kwargs)
|
| 99 |
+
case DetectorType.HISTOGRAM:
|
| 100 |
+
return HistogramDetector(**kwargs)
|
| 101 |
+
case _:
|
| 102 |
+
raise ValueError(f"Unknown detector type: {detector_type}")
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def validate_output_dir(output_dir: str) -> Path:
|
| 106 |
+
"""Validate and create output directory if it doesn't exist.
|
| 107 |
+
Args:
|
| 108 |
+
output_dir: Path to the output directory
|
| 109 |
+
Returns:
|
| 110 |
+
Path object of the validated output directory
|
| 111 |
+
"""
|
| 112 |
+
path = Path(output_dir)
|
| 113 |
+
|
| 114 |
+
if path.exists() and not path.is_dir():
|
| 115 |
+
raise typer.BadParameter(f"{output_dir} exists but is not a directory")
|
| 116 |
+
|
| 117 |
+
return path
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def parse_timecode(video: any, time_str: Optional[str]) -> Optional[FrameTimecode]:
|
| 121 |
+
"""Parse a timecode string into a FrameTimecode object.
|
| 122 |
+
Supports formats:
|
| 123 |
+
- Frames: '123'
|
| 124 |
+
- Seconds: '123s' or '123.45s'
|
| 125 |
+
- Timecode: '00:02:03' or '00:02:03.456'
|
| 126 |
+
Args:
|
| 127 |
+
video: Video object to get framerate from
|
| 128 |
+
time_str: String to parse, or None
|
| 129 |
+
Returns:
|
| 130 |
+
FrameTimecode object or None if input is None
|
| 131 |
+
"""
|
| 132 |
+
if time_str is None:
|
| 133 |
+
return None
|
| 134 |
+
|
| 135 |
+
try:
|
| 136 |
+
if time_str.endswith("s"):
|
| 137 |
+
# Seconds format
|
| 138 |
+
seconds = float(time_str[:-1])
|
| 139 |
+
return FrameTimecode(timecode=seconds, fps=video.frame_rate)
|
| 140 |
+
elif ":" in time_str:
|
| 141 |
+
# Timecode format
|
| 142 |
+
return FrameTimecode(timecode=time_str, fps=video.frame_rate)
|
| 143 |
+
else:
|
| 144 |
+
# Frame number format
|
| 145 |
+
return FrameTimecode(timecode=int(time_str), fps=video.frame_rate)
|
| 146 |
+
except ValueError as e:
|
| 147 |
+
raise typer.BadParameter(
|
| 148 |
+
f"Invalid timecode format: {time_str}. Use frames (123), "
|
| 149 |
+
f"seconds (123s/123.45s), or timecode (HH:MM:SS[.nnn])",
|
| 150 |
+
) from e
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def detect_and_split_scenes( # noqa: PLR0913
|
| 154 |
+
video_path: str,
|
| 155 |
+
output_dir: Path,
|
| 156 |
+
detector_type: DetectorType,
|
| 157 |
+
threshold: Optional[float] = None,
|
| 158 |
+
min_scene_len: Optional[int] = None,
|
| 159 |
+
max_scenes: Optional[int] = None,
|
| 160 |
+
filter_shorter_than: Optional[str] = None,
|
| 161 |
+
skip_start: Optional[int] = None, # noqa: ARG001
|
| 162 |
+
skip_end: Optional[int] = None, # noqa: ARG001
|
| 163 |
+
save_images_per_scene: int = 0,
|
| 164 |
+
stats_file: Optional[str] = None,
|
| 165 |
+
luma_only: bool = False,
|
| 166 |
+
adaptive_window: Optional[int] = None,
|
| 167 |
+
fade_bias: Optional[float] = None,
|
| 168 |
+
downscale_factor: Optional[int] = None,
|
| 169 |
+
frame_skip: int = 0,
|
| 170 |
+
duration: Optional[str] = None,
|
| 171 |
+
) -> List[Tuple[FrameTimecode, FrameTimecode]]:
|
| 172 |
+
"""Detect and split scenes in a video using the specified parameters.
|
| 173 |
+
Args:
|
| 174 |
+
video_path: Path to input video.
|
| 175 |
+
output_dir: Directory to save output split scenes.
|
| 176 |
+
detector_type: Type of scene detector to use.
|
| 177 |
+
threshold: Detection threshold.
|
| 178 |
+
min_scene_len: Minimum scene length in frames.
|
| 179 |
+
max_scenes: Maximum number of scenes to detect.
|
| 180 |
+
filter_shorter_than: Filter out scenes shorter than this duration (frames/seconds/timecode)
|
| 181 |
+
skip_start: Number of frames to skip at start.
|
| 182 |
+
skip_end: Number of frames to skip at end.
|
| 183 |
+
save_images_per_scene: Number of images to save per scene (0 to disable).
|
| 184 |
+
stats_file: Path to save detection statistics (optional).
|
| 185 |
+
luma_only: Only use brightness for content detection.
|
| 186 |
+
adaptive_window: Window size for adaptive detection.
|
| 187 |
+
fade_bias: Bias for fade detection (-1.0 to 1.0).
|
| 188 |
+
downscale_factor: Factor to downscale frames by during detection.
|
| 189 |
+
frame_skip: Number of frames to skip (i.e. process every 1 in N+1 frames,
|
| 190 |
+
where N is frame_skip, processing only 1/N+1 percent of the video,
|
| 191 |
+
speeding up the detection time at the expense of accuracy).
|
| 192 |
+
frame_skip must be 0 (the default) when using a StatsManager.
|
| 193 |
+
duration: How much of the video to process from start position.
|
| 194 |
+
Can be specified as frames (123), seconds (123s/123.45s),
|
| 195 |
+
or timecode (HH:MM:SS[.nnn]).
|
| 196 |
+
Returns:
|
| 197 |
+
List of detected scenes as (start, end) FrameTimecode pairs.
|
| 198 |
+
"""
|
| 199 |
+
# Create video stream
|
| 200 |
+
video = open_video(video_path, backend="opencv")
|
| 201 |
+
|
| 202 |
+
# Parse duration if specified
|
| 203 |
+
duration_tc = parse_timecode(video, duration)
|
| 204 |
+
|
| 205 |
+
# Parse filter_shorter_than if specified
|
| 206 |
+
filter_shorter_than_tc = parse_timecode(video, filter_shorter_than)
|
| 207 |
+
|
| 208 |
+
# Initialize scene manager with optional stats manager
|
| 209 |
+
stats_manager = StatsManager() if stats_file else None
|
| 210 |
+
scene_manager = SceneManager(stats_manager)
|
| 211 |
+
|
| 212 |
+
# Configure scene manager
|
| 213 |
+
if downscale_factor:
|
| 214 |
+
scene_manager.auto_downscale = False
|
| 215 |
+
scene_manager.downscale = downscale_factor
|
| 216 |
+
|
| 217 |
+
# Create and add detector
|
| 218 |
+
detector = create_detector(
|
| 219 |
+
detector_type=detector_type,
|
| 220 |
+
threshold=threshold,
|
| 221 |
+
min_scene_len=min_scene_len,
|
| 222 |
+
luma_only=luma_only,
|
| 223 |
+
adaptive_window=adaptive_window,
|
| 224 |
+
fade_bias=fade_bias,
|
| 225 |
+
)
|
| 226 |
+
scene_manager.add_detector(detector)
|
| 227 |
+
|
| 228 |
+
# Detect scenes
|
| 229 |
+
typer.echo("Detecting scenes...")
|
| 230 |
+
scene_manager.detect_scenes(
|
| 231 |
+
video=video,
|
| 232 |
+
show_progress=True,
|
| 233 |
+
frame_skip=frame_skip,
|
| 234 |
+
duration=duration_tc,
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
# Get scene list
|
| 238 |
+
scenes = scene_manager.get_scene_list()
|
| 239 |
+
|
| 240 |
+
# Filter out scenes that are too short if filter_shorter_than is specified
|
| 241 |
+
if filter_shorter_than_tc:
|
| 242 |
+
original_count = len(scenes)
|
| 243 |
+
scenes = [
|
| 244 |
+
(start, end)
|
| 245 |
+
for start, end in scenes
|
| 246 |
+
if (end.get_frames() - start.get_frames()) >= filter_shorter_than_tc.get_frames()
|
| 247 |
+
]
|
| 248 |
+
if len(scenes) < original_count:
|
| 249 |
+
typer.echo(
|
| 250 |
+
f"Filtered out {original_count - len(scenes)} scenes shorter "
|
| 251 |
+
f"than {filter_shorter_than_tc.get_seconds():.1f} seconds "
|
| 252 |
+
f"({filter_shorter_than_tc.get_frames()} frames)",
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
# Apply max scenes limit if specified
|
| 256 |
+
if max_scenes and len(scenes) > max_scenes:
|
| 257 |
+
typer.echo(f"Dropping last {len(scenes) - max_scenes} scenes to meet max_scenes ({max_scenes}) limit")
|
| 258 |
+
scenes = scenes[:max_scenes]
|
| 259 |
+
|
| 260 |
+
# Print scene information
|
| 261 |
+
typer.echo(f"Found {len(scenes)} scenes:")
|
| 262 |
+
for i, (start, end) in enumerate(scenes, 1):
|
| 263 |
+
typer.echo(
|
| 264 |
+
f"Scene {i}: {start.get_timecode()} to {end.get_timecode()} "
|
| 265 |
+
f"({end.get_frames() - start.get_frames()} frames)",
|
| 266 |
+
)
|
| 267 |
+
|
| 268 |
+
# Save stats if requested
|
| 269 |
+
if stats_file:
|
| 270 |
+
typer.echo(f"Saving detection stats to {stats_file}")
|
| 271 |
+
stats_manager.save_to_csv(stats_file)
|
| 272 |
+
|
| 273 |
+
# Split video into scenes
|
| 274 |
+
typer.echo("Splitting video into scenes...")
|
| 275 |
+
try:
|
| 276 |
+
split_video_ffmpeg(
|
| 277 |
+
input_video_path=video_path,
|
| 278 |
+
scene_list=scenes,
|
| 279 |
+
output_dir=output_dir,
|
| 280 |
+
show_progress=True,
|
| 281 |
+
)
|
| 282 |
+
typer.echo(f"Scenes have been saved to: {output_dir}")
|
| 283 |
+
except Exception as e:
|
| 284 |
+
raise typer.BadParameter(f"Error splitting video: {e}") from e
|
| 285 |
+
|
| 286 |
+
# Save preview images if requested
|
| 287 |
+
if save_images_per_scene > 0:
|
| 288 |
+
typer.echo(f"Saving {save_images_per_scene} preview images per scene...")
|
| 289 |
+
image_filenames = save_scene_images(
|
| 290 |
+
scene_list=scenes,
|
| 291 |
+
video=video,
|
| 292 |
+
num_images=save_images_per_scene,
|
| 293 |
+
output_dir=str(output_dir),
|
| 294 |
+
show_progress=True,
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
# Generate HTML report with scene information and previews
|
| 298 |
+
html_path = output_dir / "scene_report.html"
|
| 299 |
+
write_scene_list_html(
|
| 300 |
+
output_html_filename=str(html_path),
|
| 301 |
+
scene_list=scenes,
|
| 302 |
+
image_filenames=image_filenames,
|
| 303 |
+
)
|
| 304 |
+
typer.echo(f"Scene report saved to: {html_path}")
|
| 305 |
+
|
| 306 |
+
return scenes
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
@app.command()
|
| 310 |
+
def main( # noqa: PLR0913
|
| 311 |
+
video_path: Path = typer.Argument( # noqa: B008
|
| 312 |
+
...,
|
| 313 |
+
help="Path to the input video file",
|
| 314 |
+
exists=True,
|
| 315 |
+
dir_okay=False,
|
| 316 |
+
),
|
| 317 |
+
output_dir: str = typer.Argument(
|
| 318 |
+
...,
|
| 319 |
+
help="Directory where split scenes will be saved",
|
| 320 |
+
),
|
| 321 |
+
detector: DetectorType = typer.Option( # noqa: B008
|
| 322 |
+
DetectorType.CONTENT,
|
| 323 |
+
help="Scene detection algorithm to use",
|
| 324 |
+
),
|
| 325 |
+
threshold: Optional[float] = typer.Option(
|
| 326 |
+
None,
|
| 327 |
+
help="Detection threshold (meaning varies by detector)",
|
| 328 |
+
),
|
| 329 |
+
max_scenes: Optional[int] = typer.Option(
|
| 330 |
+
None,
|
| 331 |
+
help="Maximum number of scenes to produce",
|
| 332 |
+
),
|
| 333 |
+
min_scene_length: Optional[int] = typer.Option(
|
| 334 |
+
None,
|
| 335 |
+
help="Minimum scene length during detection. Forces the detector to make scenes at least this many frames. "
|
| 336 |
+
"This affects scene detection behavior but does not filter out short scenes.",
|
| 337 |
+
),
|
| 338 |
+
filter_shorter_than: Optional[str] = typer.Option(
|
| 339 |
+
None,
|
| 340 |
+
help="Filter out scenes shorter than this duration. Can be specified as frames (123), "
|
| 341 |
+
"seconds (123s/123.45s), or timecode (HH:MM:SS[.nnn]). These scenes will be detected but not saved.",
|
| 342 |
+
),
|
| 343 |
+
skip_start: Optional[int] = typer.Option(
|
| 344 |
+
None,
|
| 345 |
+
help="Number of frames to skip at the start of the video",
|
| 346 |
+
),
|
| 347 |
+
skip_end: Optional[int] = typer.Option(
|
| 348 |
+
None,
|
| 349 |
+
help="Number of frames to skip at the end of the video",
|
| 350 |
+
),
|
| 351 |
+
duration: Optional[str] = typer.Option(
|
| 352 |
+
None,
|
| 353 |
+
"-d",
|
| 354 |
+
help="How much of the video to process. Can be specified as frames (123), "
|
| 355 |
+
"seconds (123s/123.45s), or timecode (HH:MM:SS[.nnn])",
|
| 356 |
+
),
|
| 357 |
+
save_images: int = typer.Option(
|
| 358 |
+
0,
|
| 359 |
+
help="Number of preview images to save per scene (0 to disable)",
|
| 360 |
+
),
|
| 361 |
+
stats_file: Optional[str] = typer.Option(
|
| 362 |
+
None,
|
| 363 |
+
help="Path to save detection statistics CSV",
|
| 364 |
+
),
|
| 365 |
+
luma_only: bool = typer.Option(
|
| 366 |
+
False,
|
| 367 |
+
help="Only use brightness for content detection",
|
| 368 |
+
),
|
| 369 |
+
adaptive_window: Optional[int] = typer.Option(
|
| 370 |
+
None,
|
| 371 |
+
help="Window size for adaptive detection",
|
| 372 |
+
),
|
| 373 |
+
fade_bias: Optional[float] = typer.Option(
|
| 374 |
+
None,
|
| 375 |
+
help="Bias for fade detection (-1.0 to 1.0)",
|
| 376 |
+
),
|
| 377 |
+
downscale: Optional[int] = typer.Option(
|
| 378 |
+
None,
|
| 379 |
+
help="Factor to downscale frames by during detection",
|
| 380 |
+
),
|
| 381 |
+
frame_skip: int = typer.Option(
|
| 382 |
+
0,
|
| 383 |
+
help="Number of frames to skip during processing",
|
| 384 |
+
),
|
| 385 |
+
) -> None:
|
| 386 |
+
"""Split video into scenes using PySceneDetect."""
|
| 387 |
+
if skip_start or skip_end:
|
| 388 |
+
typer.echo("Skipping start and end frames is not supported yet.")
|
| 389 |
+
return
|
| 390 |
+
|
| 391 |
+
# Validate output directory
|
| 392 |
+
output_path = validate_output_dir(output_dir)
|
| 393 |
+
|
| 394 |
+
# Detect and split scenes
|
| 395 |
+
detect_and_split_scenes(
|
| 396 |
+
video_path=str(video_path),
|
| 397 |
+
output_dir=output_path,
|
| 398 |
+
detector_type=detector,
|
| 399 |
+
threshold=threshold,
|
| 400 |
+
min_scene_len=min_scene_length,
|
| 401 |
+
max_scenes=max_scenes,
|
| 402 |
+
filter_shorter_than=filter_shorter_than,
|
| 403 |
+
skip_start=skip_start,
|
| 404 |
+
skip_end=skip_end,
|
| 405 |
+
duration=duration,
|
| 406 |
+
save_images_per_scene=save_images,
|
| 407 |
+
stats_file=stats_file,
|
| 408 |
+
luma_only=luma_only,
|
| 409 |
+
adaptive_window=adaptive_window,
|
| 410 |
+
fade_bias=fade_bias,
|
| 411 |
+
downscale_factor=downscale,
|
| 412 |
+
frame_skip=frame_skip,
|
| 413 |
+
)
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
if __name__ == "__main__":
|
| 417 |
+
app()
|
packages/ltx-trainer/scripts/train.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
Train LTXV models using configuration from YAML files.
|
| 5 |
+
This script provides a command-line interface for training LTXV models using
|
| 6 |
+
either LoRA fine-tuning or full model fine-tuning. It loads configuration from
|
| 7 |
+
a YAML file and passes it to the trainer.
|
| 8 |
+
Basic usage:
|
| 9 |
+
python scripts/train.py CONFIG_PATH [--disable-progress-bars]
|
| 10 |
+
Resume is automatic when a training state file exists next to the loaded checkpoint.
|
| 11 |
+
To start fresh, set `checkpoints.no_resume: true` in the YAML config.
|
| 12 |
+
For multi-GPU/FSDP training, configure and launch via Accelerate:
|
| 13 |
+
accelerate config
|
| 14 |
+
accelerate launch scripts/train.py CONFIG_PATH
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
|
| 19 |
+
import typer
|
| 20 |
+
import yaml
|
| 21 |
+
from rich.console import Console
|
| 22 |
+
|
| 23 |
+
from ltx_trainer.config import LtxTrainerConfig
|
| 24 |
+
from ltx_trainer.trainer import LtxvTrainer
|
| 25 |
+
|
| 26 |
+
console = Console()
|
| 27 |
+
app = typer.Typer(
|
| 28 |
+
pretty_exceptions_enable=False,
|
| 29 |
+
no_args_is_help=True,
|
| 30 |
+
help="Train LTXV models using configuration from YAML files.",
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@app.command()
|
| 35 |
+
def main(
|
| 36 |
+
config_path: str = typer.Argument(..., help="Path to YAML configuration file"),
|
| 37 |
+
disable_progress_bars: bool = typer.Option(
|
| 38 |
+
False,
|
| 39 |
+
"--disable-progress-bars",
|
| 40 |
+
help="Disable progress bars (useful for multi-process runs)",
|
| 41 |
+
),
|
| 42 |
+
) -> None:
|
| 43 |
+
"""Train the model using the provided configuration file."""
|
| 44 |
+
config_path = Path(config_path)
|
| 45 |
+
if not config_path.exists():
|
| 46 |
+
typer.echo(f"Error: Configuration file {config_path} does not exist.")
|
| 47 |
+
raise typer.Exit(code=1)
|
| 48 |
+
|
| 49 |
+
with open(config_path, "r") as file:
|
| 50 |
+
config_data = yaml.safe_load(file)
|
| 51 |
+
|
| 52 |
+
try:
|
| 53 |
+
trainer_config = LtxTrainerConfig(**config_data)
|
| 54 |
+
except Exception as e:
|
| 55 |
+
typer.echo(f"Error: Invalid configuration data: {e}")
|
| 56 |
+
raise typer.Exit(code=1) from e
|
| 57 |
+
|
| 58 |
+
trainer = LtxvTrainer(trainer_config)
|
| 59 |
+
trainer.train(disable_progress_bars=disable_progress_bars)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
if __name__ == "__main__":
|
| 63 |
+
app()
|
packages/ltx-trainer/src/ltx_trainer/__init__.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
from logging import getLogger
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from rich.logging import RichHandler
|
| 8 |
+
|
| 9 |
+
# Get the process rank
|
| 10 |
+
IS_MULTI_GPU = os.environ.get("LOCAL_RANK") is not None
|
| 11 |
+
RANK = int(os.environ.get("LOCAL_RANK", "0"))
|
| 12 |
+
|
| 13 |
+
# Configure with Rich
|
| 14 |
+
logging.basicConfig(
|
| 15 |
+
level="INFO",
|
| 16 |
+
format=f"\\[rank {RANK}] %(message)s" if IS_MULTI_GPU else "%(message)s",
|
| 17 |
+
handlers=[
|
| 18 |
+
RichHandler(
|
| 19 |
+
rich_tracebacks=True,
|
| 20 |
+
show_time=False,
|
| 21 |
+
markup=True,
|
| 22 |
+
)
|
| 23 |
+
],
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
# Get the logger and configure it
|
| 27 |
+
logger = getLogger("ltxv_trainer")
|
| 28 |
+
logger.setLevel(logging.DEBUG)
|
| 29 |
+
logger.propagate = True
|
| 30 |
+
|
| 31 |
+
# Set level based on process
|
| 32 |
+
if RANK != 0:
|
| 33 |
+
logger.setLevel(logging.WARNING)
|
| 34 |
+
|
| 35 |
+
# Expose common logging functions directly
|
| 36 |
+
debug = logger.debug
|
| 37 |
+
info = logger.info
|
| 38 |
+
warning = logger.warning
|
| 39 |
+
error = logger.error
|
| 40 |
+
critical = logger.critical
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
# Add the root directory to the Python path so we can import from scripts.
|
| 44 |
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
packages/ltx-trainer/src/ltx_trainer/captioning.py
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Audio-visual media captioning using multimodal models.
|
| 3 |
+
This module provides captioning capabilities for videos with audio using:
|
| 4 |
+
- Qwen3-Omni via a local vLLM server (default)
|
| 5 |
+
- Gemini Flash 3.5 (cloud API)
|
| 6 |
+
Both produce a single combined English caption per video as a single
|
| 7 |
+
continuous paragraph of prose.
|
| 8 |
+
The Qwen3-Omni backend runs in a separately-launched vLLM server rather than
|
| 9 |
+
in-process, so vLLM's heavy CUDA dependencies stay out of this package. The
|
| 10 |
+
captioner talks to it over the OpenAI-compatible HTTP API.
|
| 11 |
+
Launch the server once (in an isolated environment) with:
|
| 12 |
+
.. code-block:: bash
|
| 13 |
+
uv run python scripts/serve_captioner.py
|
| 14 |
+
That helper picks BF16 vs FP8 dynamic quantization based on the GPU's free
|
| 15 |
+
memory and forwards everything else to ``vllm serve``. To check the recommended
|
| 16 |
+
command without running it, pass ``--print-cmd``.
|
| 17 |
+
To use Gemini instead, install ``google-genai`` and either set ``GEMINI_API_KEY``
|
| 18 |
+
(Gemini Developer API) or have Google Cloud credentials available (gcloud / an
|
| 19 |
+
attached service account), in which case it uses Vertex AI automatically.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
import json
|
| 23 |
+
import os
|
| 24 |
+
import re
|
| 25 |
+
import subprocess
|
| 26 |
+
import tempfile
|
| 27 |
+
from abc import ABC, abstractmethod
|
| 28 |
+
from enum import Enum
|
| 29 |
+
from pathlib import Path
|
| 30 |
+
from typing import ClassVar
|
| 31 |
+
|
| 32 |
+
DEFAULT_VIDEO_CAPTION_INSTRUCTION = """\
|
| 33 |
+
Analyze this video and produce a single detailed caption covering both its visual content and its audio. Be \
|
| 34 |
+
detailed enough that someone reading the caption could form an accurate mental picture of what happens on screen \
|
| 35 |
+
and what can be heard. Be exhaustive: include every meaningful detail you can see and hear, including small \
|
| 36 |
+
objects, textures, secondary movements, and minor background sounds.
|
| 37 |
+
|
| 38 |
+
Begin the caption directly with the action or visual detail; do not preface it with phrases like \
|
| 39 |
+
"The video opens with...", "The scene shows...", "We see...", or "There is...".
|
| 40 |
+
|
| 41 |
+
For every shot, include:
|
| 42 |
+
- The shot type and framing (extreme wide / wide / medium / medium close-up / close-up / extreme close-up) and any \
|
| 43 |
+
camera motion.
|
| 44 |
+
- Characters' clothing, appearance, posture, and movement (direction, speed, quality).
|
| 45 |
+
- The environment's materials, textures, lighting, and colors.
|
| 46 |
+
- All audio: spoken dialogue (quoted exactly in the original language), tone of voice, music (style, mood, \
|
| 47 |
+
volume changes), and environmental sounds. If a category is absent -- for example no music is playing, or no one is \
|
| 48 |
+
speaking -- state that explicitly. Do not invent specific instruments, music genres, moods, or ambient sounds \
|
| 49 |
+
that are not actually present.
|
| 50 |
+
- Any on-screen text (signs, titles, labels).
|
| 51 |
+
|
| 52 |
+
Describe only what is visible or audible. Do not infer emotions, intentions, or anything outside the segment. \
|
| 53 |
+
Refer to people descriptively (e.g., "the man in the blue jacket"). Narrate strictly in chronological order; if \
|
| 54 |
+
the video contains multiple shots, describe each one in turn.
|
| 55 |
+
|
| 56 |
+
Write everything as a single continuous paragraph of prose. Do not use section headers, bullet points, or labels \
|
| 57 |
+
like "Audio:" / "Visual:" / "Shot:". Integrate visual and audio details naturally within the same sentences.
|
| 58 |
+
|
| 59 |
+
Return a JSON object with exactly one key:
|
| 60 |
+
|
| 61 |
+
{"combined_caption_english": "<your caption here>"}"""
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
DEFAULT_IMAGE_CAPTION_INSTRUCTION = """\
|
| 65 |
+
Analyze this image and produce a single detailed caption of its visual content. Be detailed enough that \
|
| 66 |
+
someone reading the caption could form an accurate mental picture of the image. Be thorough: include every meaningful \
|
| 67 |
+
detail that is actually present, including small objects, textures, and background elements.
|
| 68 |
+
|
| 69 |
+
Begin the caption directly with the main subject or a visual detail; do not preface it with phrases like \
|
| 70 |
+
"The image shows...", "This is a photo of...", "We see...", or "There is...".
|
| 71 |
+
|
| 72 |
+
Include:
|
| 73 |
+
- The framing and composition (close-up / medium / wide / overhead, etc.) and the vantage point.
|
| 74 |
+
- The medium or style if distinctive (photograph, illustration, 3D render, painting).
|
| 75 |
+
- People's clothing, appearance, and posture, and what they are doing.
|
| 76 |
+
- The setting's materials, textures, lighting, and colors.
|
| 77 |
+
- Transcribe any visible text verbatim (signs, labels, titles, captions).
|
| 78 |
+
|
| 79 |
+
Describe only what is visible. Do not infer emotions or intentions, and do not describe sounds, motion, or \
|
| 80 |
+
events before or after the moment shown -- this is a single still image. When something is ambiguous, describe \
|
| 81 |
+
the visible cue (e.g., "warm low-angle light") rather than guessing the underlying fact (e.g., "sunrise"). \
|
| 82 |
+
Refer to people descriptively (e.g., "the man in the blue jacket").
|
| 83 |
+
|
| 84 |
+
Only describe what is present. Never state that something is absent or missing -- do not write phrases like \
|
| 85 |
+
"there is no text", "no people are present", or "no other objects". If a category such as people or text does \
|
| 86 |
+
not appear, simply leave it out.
|
| 87 |
+
|
| 88 |
+
Write everything as a single continuous paragraph of prose. Do not use section headers, bullet points, or \
|
| 89 |
+
labels.
|
| 90 |
+
|
| 91 |
+
Return a JSON object with exactly one key:
|
| 92 |
+
|
| 93 |
+
{"combined_caption_english": "<your caption here>"}"""
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
# Default model served by ``scripts/serve_captioner.py``. The captioner does not
|
| 97 |
+
# download or load this model itself -- it just sends requests to the vLLM
|
| 98 |
+
# server, which already has the model loaded.
|
| 99 |
+
DEFAULT_QWEN_MODEL = "Qwen/Qwen3-Omni-30B-A3B-Thinking"
|
| 100 |
+
DEFAULT_VLLM_BASE_URL = "http://127.0.0.1:8001/v1"
|
| 101 |
+
|
| 102 |
+
# Key the combined-caption prompt asks the model to return its caption under.
|
| 103 |
+
_CAPTION_JSON_KEY = "combined_caption_english"
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
class CaptionerType(str, Enum):
|
| 107 |
+
"""Enum for different types of media captioners."""
|
| 108 |
+
|
| 109 |
+
QWEN_OMNI = "qwen_omni" # Qwen3-Omni via local vLLM HTTP server
|
| 110 |
+
GEMINI_FLASH = "gemini_flash" # Gemini Flash 3.5 cloud API
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def create_captioner(captioner_type: CaptionerType, **kwargs) -> "MediaCaptioningModel":
|
| 114 |
+
"""Factory function to create a media captioner."""
|
| 115 |
+
match captioner_type:
|
| 116 |
+
case CaptionerType.QWEN_OMNI:
|
| 117 |
+
return QwenOmniCaptioner(**kwargs)
|
| 118 |
+
case CaptionerType.GEMINI_FLASH:
|
| 119 |
+
return GeminiFlashCaptioner(**kwargs)
|
| 120 |
+
case _:
|
| 121 |
+
raise ValueError(f"Unsupported captioner type: {captioner_type}")
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
class MediaCaptioningModel(ABC):
|
| 125 |
+
"""Abstract base class for audio-visual media captioning models."""
|
| 126 |
+
|
| 127 |
+
instruction: str | None = None
|
| 128 |
+
|
| 129 |
+
@abstractmethod
|
| 130 |
+
def caption(self, path: str | Path, **kwargs) -> str:
|
| 131 |
+
"""Generate a caption for the given video or image."""
|
| 132 |
+
|
| 133 |
+
def _resolve_instruction(self, path: str | Path) -> str:
|
| 134 |
+
"""Return the custom instruction, or the image/video default for this input."""
|
| 135 |
+
if self.instruction is not None:
|
| 136 |
+
return self.instruction
|
| 137 |
+
return DEFAULT_IMAGE_CAPTION_INSTRUCTION if self._is_image_file(path) else DEFAULT_VIDEO_CAPTION_INSTRUCTION
|
| 138 |
+
|
| 139 |
+
@staticmethod
|
| 140 |
+
def _is_image_file(path: str | Path) -> bool:
|
| 141 |
+
return str(path).lower().endswith((".png", ".jpg", ".jpeg", ".heic", ".heif", ".webp"))
|
| 142 |
+
|
| 143 |
+
@staticmethod
|
| 144 |
+
def _is_video_file(path: str | Path) -> bool:
|
| 145 |
+
return str(path).lower().endswith((".mp4", ".avi", ".mov", ".mkv", ".webm"))
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
class QwenOmniCaptioner(MediaCaptioningModel):
|
| 149 |
+
"""Audio-visual captioning via a local vLLM server running Qwen3-Omni.
|
| 150 |
+
The vLLM server must already be running. See ``scripts/serve_captioner.py``
|
| 151 |
+
for a helper that launches one in an isolated environment (no impact on
|
| 152 |
+
this package's dependency tree).
|
| 153 |
+
The captioner uses the OpenAI-compatible chat completions API. It sends
|
| 154 |
+
a ``file://`` URL pointing at the local video, the default combined-caption
|
| 155 |
+
prompt, and parses the JSON-wrapped response.
|
| 156 |
+
"""
|
| 157 |
+
|
| 158 |
+
def __init__(
|
| 159 |
+
self,
|
| 160 |
+
base_url: str = DEFAULT_VLLM_BASE_URL,
|
| 161 |
+
model: str = DEFAULT_QWEN_MODEL,
|
| 162 |
+
api_key: str = "EMPTY",
|
| 163 |
+
instruction: str | None = None,
|
| 164 |
+
max_tokens: int = 4096,
|
| 165 |
+
enable_thinking: bool = False,
|
| 166 |
+
timeout_s: float = 600.0,
|
| 167 |
+
):
|
| 168 |
+
"""Initialize the Qwen3-Omni captioner.
|
| 169 |
+
Args:
|
| 170 |
+
base_url: Base URL of the vLLM OpenAI-compatible server (default
|
| 171 |
+
``http://127.0.0.1:8001/v1``).
|
| 172 |
+
model: Model identifier the server is serving. Must match the
|
| 173 |
+
server's ``--served-model-name`` (defaults to the HuggingFace
|
| 174 |
+
model ID).
|
| 175 |
+
api_key: Token sent in the ``Authorization`` header. vLLM accepts
|
| 176 |
+
any value by default.
|
| 177 |
+
instruction: Custom instruction prompt. If ``None``, uses the
|
| 178 |
+
default combined-caption prompt.
|
| 179 |
+
max_tokens: Maximum new tokens to generate per caption. 4096 leaves
|
| 180 |
+
comfortable headroom for both ``enable_thinking`` modes.
|
| 181 |
+
enable_thinking: Whether to let the Thinking model produce a
|
| 182 |
+
``<think>...</think>`` chain-of-thought before the caption.
|
| 183 |
+
Off by default: it makes captioning ~5x slower with little
|
| 184 |
+
quality benefit and occasionally introduces hallucinations
|
| 185 |
+
(e.g., inventing dialogue or background music).
|
| 186 |
+
timeout_s: Per-request HTTP timeout.
|
| 187 |
+
"""
|
| 188 |
+
from openai import OpenAI # noqa: PLC0415
|
| 189 |
+
|
| 190 |
+
self.model = model
|
| 191 |
+
self.instruction = instruction
|
| 192 |
+
self.max_tokens = max_tokens
|
| 193 |
+
self.enable_thinking = enable_thinking
|
| 194 |
+
self._client = OpenAI(base_url=base_url, api_key=api_key, timeout=timeout_s)
|
| 195 |
+
|
| 196 |
+
def caption(
|
| 197 |
+
self,
|
| 198 |
+
path: str | Path,
|
| 199 |
+
fps: int = 2,
|
| 200 |
+
) -> str:
|
| 201 |
+
"""Generate a caption for the given video or image.
|
| 202 |
+
Args:
|
| 203 |
+
path: Path to the video/image file to caption.
|
| 204 |
+
fps: Frames per second to sample from the video. Passed through to
|
| 205 |
+
vLLM's multimodal processor (``mm_processor_kwargs.fps``).
|
| 206 |
+
Default 2 is a typical choice for video MLLMs at this resolution.
|
| 207 |
+
Ignored for image inputs.
|
| 208 |
+
Returns:
|
| 209 |
+
The extracted caption string.
|
| 210 |
+
"""
|
| 211 |
+
path = Path(path)
|
| 212 |
+
is_image = self._is_image_file(path)
|
| 213 |
+
is_video = self._is_video_file(path)
|
| 214 |
+
if not (is_image or is_video):
|
| 215 |
+
raise ValueError(f"Unsupported media file: {path}")
|
| 216 |
+
|
| 217 |
+
instruction = self._resolve_instruction(path)
|
| 218 |
+
|
| 219 |
+
if is_image:
|
| 220 |
+
content = [
|
| 221 |
+
{"type": "image_url", "image_url": {"url": f"file://{path.resolve()}"}},
|
| 222 |
+
{"type": "text", "text": instruction},
|
| 223 |
+
]
|
| 224 |
+
return _parse_caption_response(self._chat(content)).strip()
|
| 225 |
+
|
| 226 |
+
return self._caption_video(path, instruction, fps)
|
| 227 |
+
|
| 228 |
+
def _chat(self, content: list[dict], mm_kwargs: dict | None = None) -> str:
|
| 229 |
+
"""Send one chat-completions request and return the raw response text."""
|
| 230 |
+
extra_body: dict = {
|
| 231 |
+
"repetition_penalty": 1.05,
|
| 232 |
+
"chat_template_kwargs": {"enable_thinking": self.enable_thinking},
|
| 233 |
+
}
|
| 234 |
+
if mm_kwargs:
|
| 235 |
+
extra_body["mm_processor_kwargs"] = mm_kwargs
|
| 236 |
+
response = self._client.chat.completions.create(
|
| 237 |
+
model=self.model,
|
| 238 |
+
messages=[{"role": "user", "content": content}],
|
| 239 |
+
max_tokens=self.max_tokens,
|
| 240 |
+
temperature=0.0,
|
| 241 |
+
extra_body=extra_body,
|
| 242 |
+
)
|
| 243 |
+
return response.choices[0].message.content or ""
|
| 244 |
+
|
| 245 |
+
def _caption_video(self, path: Path, instruction: str, fps: int) -> str:
|
| 246 |
+
"""Caption a video, sending its audio track as a separate modality.
|
| 247 |
+
vLLM does not extract a video's audio on its own (and its
|
| 248 |
+
``use_audio_in_video`` path is broken server-side), so we pull the audio
|
| 249 |
+
into a 16 kHz mono WAV and send it alongside the video -- otherwise the
|
| 250 |
+
model only sees frames and fabricates any spoken content.
|
| 251 |
+
"""
|
| 252 |
+
with tempfile.TemporaryDirectory(prefix="qwencap_") as tmp:
|
| 253 |
+
work = Path(tmp)
|
| 254 |
+
|
| 255 |
+
# Best-effort: ffmpeg fails (and we send video only) if there's no audio.
|
| 256 |
+
audio_url: str | None = None
|
| 257 |
+
try:
|
| 258 |
+
wav = work / "audio.wav"
|
| 259 |
+
_extract_audio_wav(path, wav)
|
| 260 |
+
audio_url = f"file://{wav.resolve()}"
|
| 261 |
+
except subprocess.CalledProcessError:
|
| 262 |
+
pass
|
| 263 |
+
|
| 264 |
+
def content(video: Path) -> list[dict]:
|
| 265 |
+
parts: list[dict] = [{"type": "video_url", "video_url": {"url": f"file://{video.resolve()}"}}]
|
| 266 |
+
if audio_url:
|
| 267 |
+
parts.append({"type": "audio_url", "audio_url": {"url": audio_url}})
|
| 268 |
+
parts.append({"type": "text", "text": instruction})
|
| 269 |
+
return parts
|
| 270 |
+
|
| 271 |
+
mm_kwargs = {"fps": fps}
|
| 272 |
+
try:
|
| 273 |
+
raw = self._chat(content(path), mm_kwargs)
|
| 274 |
+
except Exception as e:
|
| 275 |
+
# Raw / variable-frame-rate videos over-report their frame count, which
|
| 276 |
+
# breaks the server's frame sampler ("... frames from video"). Re-encode
|
| 277 |
+
# to a constant frame rate and retry once.
|
| 278 |
+
if "frames from video" not in str(e):
|
| 279 |
+
raise
|
| 280 |
+
cfr = work / "video_cfr.mp4"
|
| 281 |
+
_transcode_cfr(path, cfr)
|
| 282 |
+
raw = self._chat(content(cfr), mm_kwargs)
|
| 283 |
+
|
| 284 |
+
return _parse_caption_response(raw).strip()
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
class GeminiFlashCaptioner(MediaCaptioningModel):
|
| 288 |
+
"""Audio-visual captioning using Google's Gemini via the Google Gen AI SDK.
|
| 289 |
+
Uses the ``google-genai`` package (the current SDK; ``google-generativeai``
|
| 290 |
+
is deprecated). Auth is resolved automatically:
|
| 291 |
+
1. If an API key is given (``api_key`` argument, or ``GEMINI_API_KEY`` /
|
| 292 |
+
``GOOGLE_API_KEY`` in the environment) -> the Gemini Developer API (AI Studio).
|
| 293 |
+
2. Otherwise, if Google Cloud Application Default Credentials are available
|
| 294 |
+
(an attached service account or ``gcloud auth application-default login``)
|
| 295 |
+
-> Vertex AI. The project comes from ADC (or ``GOOGLE_CLOUD_PROJECT``) and
|
| 296 |
+
the location defaults to ``global`` (override with ``GOOGLE_CLOUD_LOCATION``).
|
| 297 |
+
This means it "just works" on a gcloud-authed GCP VM with no env vars.
|
| 298 |
+
If neither is available, a clear error explains how to authenticate.
|
| 299 |
+
Media is sent inline (``Part.from_bytes``), which works on both backends.
|
| 300 |
+
"""
|
| 301 |
+
|
| 302 |
+
MODEL_ID = "gemini-3.5-flash"
|
| 303 |
+
|
| 304 |
+
_MIME_TYPES: ClassVar[dict[str, str]] = {
|
| 305 |
+
".jpg": "image/jpeg",
|
| 306 |
+
".jpeg": "image/jpeg",
|
| 307 |
+
".png": "image/png",
|
| 308 |
+
".webp": "image/webp",
|
| 309 |
+
".heic": "image/heic",
|
| 310 |
+
".heif": "image/heif",
|
| 311 |
+
".mp4": "video/mp4",
|
| 312 |
+
".mov": "video/quicktime",
|
| 313 |
+
".avi": "video/x-msvideo",
|
| 314 |
+
".mkv": "video/x-matroska",
|
| 315 |
+
".webm": "video/webm",
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
def __init__(
|
| 319 |
+
self,
|
| 320 |
+
api_key: str | None = None,
|
| 321 |
+
instruction: str | None = None,
|
| 322 |
+
model: str | None = None,
|
| 323 |
+
):
|
| 324 |
+
"""Initialize the Gemini captioner.
|
| 325 |
+
Args:
|
| 326 |
+
api_key: Gemini Developer API key. If ``None``, falls back to
|
| 327 |
+
``GEMINI_API_KEY`` / ``GOOGLE_API_KEY``; if no key is set at all,
|
| 328 |
+
uses Vertex AI via Application Default Credentials.
|
| 329 |
+
instruction: Custom instruction prompt. If ``None``, uses the default
|
| 330 |
+
image or video prompt depending on the input.
|
| 331 |
+
model: Override the served model id (defaults to ``MODEL_ID``).
|
| 332 |
+
"""
|
| 333 |
+
self.instruction = instruction
|
| 334 |
+
self.model = model or self.MODEL_ID
|
| 335 |
+
self._client = self._make_client(api_key)
|
| 336 |
+
|
| 337 |
+
def caption(
|
| 338 |
+
self,
|
| 339 |
+
path: str | Path,
|
| 340 |
+
fps: int = 2, # noqa: ARG002 - kept for API compatibility
|
| 341 |
+
) -> str:
|
| 342 |
+
from google.genai import types # noqa: PLC0415
|
| 343 |
+
|
| 344 |
+
path = Path(path)
|
| 345 |
+
instruction = self._resolve_instruction(path)
|
| 346 |
+
media = types.Part.from_bytes(data=path.read_bytes(), mime_type=self._mime_type(path))
|
| 347 |
+
response = self._client.models.generate_content(
|
| 348 |
+
model=self.model,
|
| 349 |
+
contents=[media, instruction],
|
| 350 |
+
config=types.GenerateContentConfig(temperature=0.0),
|
| 351 |
+
)
|
| 352 |
+
|
| 353 |
+
# Gemini may also return JSON if it followed our prompt format.
|
| 354 |
+
return _parse_caption_response(response.text or "").strip()
|
| 355 |
+
|
| 356 |
+
@classmethod
|
| 357 |
+
def _mime_type(cls, path: Path) -> str:
|
| 358 |
+
try:
|
| 359 |
+
return cls._MIME_TYPES[path.suffix.lower()]
|
| 360 |
+
except KeyError:
|
| 361 |
+
raise ValueError(f"Unsupported media type for Gemini: {path.suffix}") from None
|
| 362 |
+
|
| 363 |
+
def _make_client(self, api_key: str | None): # noqa: ANN202 - genai.Client type is lazy-imported
|
| 364 |
+
from google import genai # noqa: PLC0415
|
| 365 |
+
|
| 366 |
+
# 1. API key (explicit arg or env) -> Gemini Developer API.
|
| 367 |
+
key = api_key or os.environ.get("GEMINI_API_KEY") or os.environ.get("GOOGLE_API_KEY")
|
| 368 |
+
if key:
|
| 369 |
+
return genai.Client(api_key=key)
|
| 370 |
+
|
| 371 |
+
# 2. No key -> Vertex AI via Application Default Credentials (gcloud / service account).
|
| 372 |
+
import google.auth # noqa: PLC0415
|
| 373 |
+
|
| 374 |
+
try:
|
| 375 |
+
_, adc_project = google.auth.default()
|
| 376 |
+
except Exception as e:
|
| 377 |
+
raise ValueError(
|
| 378 |
+
"No Gemini credentials found. Provide an API key (--api-key, or "
|
| 379 |
+
"GEMINI_API_KEY / GOOGLE_API_KEY), or set up Google Cloud credentials "
|
| 380 |
+
"for Vertex AI (e.g. `gcloud auth application-default login` or an "
|
| 381 |
+
"attached service account)."
|
| 382 |
+
) from e
|
| 383 |
+
|
| 384 |
+
project = os.environ.get("GOOGLE_CLOUD_PROJECT") or adc_project
|
| 385 |
+
location = os.environ.get("GOOGLE_CLOUD_LOCATION", "global")
|
| 386 |
+
return genai.Client(vertexai=True, project=project, location=location)
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
def _parse_caption_response(raw: str) -> str:
|
| 390 |
+
"""Extract the caption text from a model response.
|
| 391 |
+
Backend-agnostic: works for any model that follows the combined-caption
|
| 392 |
+
prompt. Handles the formats a model may produce:
|
| 393 |
+
- Plain caption text
|
| 394 |
+
- JSON ``{"combined_caption_english": "..."}``
|
| 395 |
+
- ``<think>...</think>`` chain-of-thought followed by either of the above
|
| 396 |
+
- Truncated JSON (when generation hits a token limit mid-string)
|
| 397 |
+
"""
|
| 398 |
+
text = re.sub(r"<think>[\s\S]*?</think>", "", raw).strip()
|
| 399 |
+
|
| 400 |
+
# Thinking models (e.g. Qwen3-Omni-*-Thinking) emit the reasoning trace
|
| 401 |
+
# without an opening ``<think>`` tag, because the chat template injects it
|
| 402 |
+
# for them -- so the response starts mid-thought and is terminated by a lone
|
| 403 |
+
# ``</think>`` before the real answer. Drop everything up to that closer.
|
| 404 |
+
if "</think>" in text:
|
| 405 |
+
text = text.rsplit("</think>", 1)[1].strip()
|
| 406 |
+
|
| 407 |
+
if not text:
|
| 408 |
+
return raw.strip()
|
| 409 |
+
|
| 410 |
+
try:
|
| 411 |
+
parsed = json.loads(text)
|
| 412 |
+
if isinstance(parsed, dict) and _CAPTION_JSON_KEY in parsed:
|
| 413 |
+
return parsed[_CAPTION_JSON_KEY]
|
| 414 |
+
except (json.JSONDecodeError, ValueError):
|
| 415 |
+
pass
|
| 416 |
+
|
| 417 |
+
match = re.search(rf"\{{[^{{}}]*\"{_CAPTION_JSON_KEY}\"[^{{}}]*\}}", text)
|
| 418 |
+
if match:
|
| 419 |
+
try:
|
| 420 |
+
parsed = json.loads(match.group())
|
| 421 |
+
if isinstance(parsed, dict) and _CAPTION_JSON_KEY in parsed:
|
| 422 |
+
return parsed[_CAPTION_JSON_KEY]
|
| 423 |
+
except (json.JSONDecodeError, ValueError):
|
| 424 |
+
pass
|
| 425 |
+
|
| 426 |
+
# Truncated JSON: extract the string value even if the closing quote/brace is missing.
|
| 427 |
+
match = re.search(rf'"{_CAPTION_JSON_KEY}"\s*:\s*"((?:[^"\\]|\\.)*)', text)
|
| 428 |
+
if match:
|
| 429 |
+
try:
|
| 430 |
+
return json.loads('"' + match.group(1) + '"')
|
| 431 |
+
except (json.JSONDecodeError, ValueError):
|
| 432 |
+
return match.group(1)
|
| 433 |
+
|
| 434 |
+
return text
|
| 435 |
+
|
| 436 |
+
|
| 437 |
+
def _run_ffmpeg(args: list[str]) -> None:
|
| 438 |
+
"""Run the ffmpeg binary bundled with ``imageio-ffmpeg`` (a dependency)."""
|
| 439 |
+
import imageio_ffmpeg # noqa: PLC0415
|
| 440 |
+
|
| 441 |
+
cmd = [imageio_ffmpeg.get_ffmpeg_exe(), "-y", "-loglevel", "error", *args]
|
| 442 |
+
subprocess.run(cmd, check=True, capture_output=True)
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
def _extract_audio_wav(src: Path, dest: Path) -> None:
|
| 446 |
+
"""Extract the audio track to a 16 kHz mono PCM WAV (matches pretraining).
|
| 447 |
+
Raises ``CalledProcessError`` when the video has no audio stream.
|
| 448 |
+
"""
|
| 449 |
+
_run_ffmpeg(["-i", str(src), "-vn", "-ac", "1", "-ar", "16000", "-c:a", "pcm_s16le", str(dest)])
|
| 450 |
+
|
| 451 |
+
|
| 452 |
+
def _transcode_cfr(src: Path, dest: Path) -> None:
|
| 453 |
+
"""Re-encode the video to a constant frame rate so the server's frame sampler can
|
| 454 |
+
read every requested index (raw / variable-frame-rate videos over-report frames)."""
|
| 455 |
+
_run_ffmpeg(["-i", str(src), "-fps_mode", "cfr", "-c:v", "libx264", "-pix_fmt", "yuv420p", "-an", str(dest)])
|
packages/ltx-trainer/src/ltx_trainer/config.py
ADDED
|
@@ -0,0 +1,833 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
from typing import Annotated, Literal, Union
|
| 3 |
+
|
| 4 |
+
from pydantic import BaseModel, ConfigDict, Discriminator, Field, Tag, ValidationInfo, field_validator, model_validator
|
| 5 |
+
|
| 6 |
+
from ltx_trainer.quantization import QuantizationOptions
|
| 7 |
+
from ltx_trainer.training_strategies.base_strategy import TrainingStrategyConfigBase
|
| 8 |
+
from ltx_trainer.training_strategies.flexible import FlexibleStrategyConfig
|
| 9 |
+
from ltx_trainer.training_strategies.text_to_video import TextToVideoConfig
|
| 10 |
+
from ltx_trainer.training_strategies.video_to_video import VideoToVideoConfig
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class ConfigBaseModel(BaseModel):
|
| 14 |
+
model_config = ConfigDict(extra="forbid")
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
# =============================================================================
|
| 18 |
+
# Validation Condition Types
|
| 19 |
+
# =============================================================================
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class FirstFrameConditionConfig(ConfigBaseModel):
|
| 23 |
+
"""First-frame conditioning (intrinsic, latent_idx=0). Always targets video.
|
| 24 |
+
If image_or_video points to a video file, the first frame is automatically extracted.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
type: Literal["first_frame"] = "first_frame"
|
| 28 |
+
image_or_video: str | Path
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class PrefixConditionConfig(ConfigBaseModel):
|
| 32 |
+
"""Prefix conditioning for temporal extension (intrinsic). Exactly one of video/audio must be set."""
|
| 33 |
+
|
| 34 |
+
type: Literal["prefix"] = "prefix"
|
| 35 |
+
video: str | None = None
|
| 36 |
+
audio: str | None = None
|
| 37 |
+
num_frames: int | None = Field(
|
| 38 |
+
default=None,
|
| 39 |
+
ge=1,
|
| 40 |
+
description="Number of pixel frames for video prefix. Must satisfy num_frames %% 8 == 1.",
|
| 41 |
+
)
|
| 42 |
+
duration: float | None = Field(default=None, gt=0, description="Duration in seconds for audio prefix")
|
| 43 |
+
|
| 44 |
+
@model_validator(mode="after")
|
| 45 |
+
def validate_exactly_one_modality(self) -> "PrefixConditionConfig":
|
| 46 |
+
if (self.video is None) == (self.audio is None):
|
| 47 |
+
raise ValueError("Exactly one of 'video' or 'audio' must be set for prefix condition")
|
| 48 |
+
return self
|
| 49 |
+
|
| 50 |
+
@model_validator(mode="after")
|
| 51 |
+
def validate_num_frames_constraint(self) -> "PrefixConditionConfig":
|
| 52 |
+
if self.video is not None and self.num_frames is not None and self.num_frames % 8 != 1:
|
| 53 |
+
raise ValueError(
|
| 54 |
+
f"num_frames ({self.num_frames}) must satisfy num_frames % 8 == 1 "
|
| 55 |
+
f"for video prefix (e.g., 1, 9, 17, 25, ...)"
|
| 56 |
+
)
|
| 57 |
+
return self
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class SuffixConditionConfig(ConfigBaseModel):
|
| 61 |
+
"""Suffix conditioning for temporal extension (intrinsic). Exactly one of video/audio must be set."""
|
| 62 |
+
|
| 63 |
+
type: Literal["suffix"] = "suffix"
|
| 64 |
+
video: str | None = None
|
| 65 |
+
audio: str | None = None
|
| 66 |
+
num_frames: int | None = Field(
|
| 67 |
+
default=None,
|
| 68 |
+
ge=1,
|
| 69 |
+
description="Number of pixel frames for video suffix. Must satisfy num_frames %% 8 == 0.",
|
| 70 |
+
)
|
| 71 |
+
duration: float | None = Field(default=None, gt=0, description="Duration in seconds for audio suffix")
|
| 72 |
+
|
| 73 |
+
@model_validator(mode="after")
|
| 74 |
+
def validate_exactly_one_modality(self) -> "SuffixConditionConfig":
|
| 75 |
+
if (self.video is None) == (self.audio is None):
|
| 76 |
+
raise ValueError("Exactly one of 'video' or 'audio' must be set for suffix condition")
|
| 77 |
+
return self
|
| 78 |
+
|
| 79 |
+
@model_validator(mode="after")
|
| 80 |
+
def validate_num_frames_constraint(self) -> "SuffixConditionConfig":
|
| 81 |
+
if self.video is not None and self.num_frames is not None and self.num_frames % 8 != 0:
|
| 82 |
+
raise ValueError(
|
| 83 |
+
f"num_frames ({self.num_frames}) must satisfy num_frames % 8 == 0 "
|
| 84 |
+
f"for video suffix (e.g., 8, 16, 24, 32, ...)"
|
| 85 |
+
)
|
| 86 |
+
return self
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
class SpatialCropConditionConfig(ConfigBaseModel):
|
| 90 |
+
"""Spatial crop conditioning for outpainting (intrinsic, video only)."""
|
| 91 |
+
|
| 92 |
+
type: Literal["spatial_crop"] = "spatial_crop"
|
| 93 |
+
video: str
|
| 94 |
+
spatial_region: tuple[int, int, int, int] = Field(
|
| 95 |
+
..., description="Spatial crop region as (y1, x1, y2, x2) in pixel coordinates"
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
class MaskConditionConfig(ConfigBaseModel):
|
| 100 |
+
"""Mask-based conditioning for inpainting (intrinsic). Exactly one of video/audio must be set."""
|
| 101 |
+
|
| 102 |
+
type: Literal["mask"] = "mask"
|
| 103 |
+
video: str | None = None
|
| 104 |
+
audio: str | None = None
|
| 105 |
+
mask: str
|
| 106 |
+
|
| 107 |
+
@model_validator(mode="after")
|
| 108 |
+
def validate_exactly_one_modality(self) -> "MaskConditionConfig":
|
| 109 |
+
if (self.video is None) == (self.audio is None):
|
| 110 |
+
raise ValueError("Exactly one of 'video' or 'audio' must be set for mask condition")
|
| 111 |
+
return self
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class ReferenceConditionConfig(ConfigBaseModel):
|
| 115 |
+
"""Reference conditioning (IC-LoRA style concatenation). Exactly one of video/audio must be set."""
|
| 116 |
+
|
| 117 |
+
type: Literal["reference"] = "reference"
|
| 118 |
+
video: str | None = None
|
| 119 |
+
audio: str | None = None
|
| 120 |
+
downscale_factor: int = Field(default=1, ge=1)
|
| 121 |
+
temporal_scale_factor: int = Field(default=1, ge=1)
|
| 122 |
+
include_in_output: bool = False
|
| 123 |
+
|
| 124 |
+
@model_validator(mode="after")
|
| 125 |
+
def validate_exactly_one_modality(self) -> "ReferenceConditionConfig":
|
| 126 |
+
if (self.video is None) == (self.audio is None):
|
| 127 |
+
raise ValueError("Exactly one of 'video' or 'audio' must be set for reference condition")
|
| 128 |
+
return self
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
class VideoToAudioConditionConfig(ConfigBaseModel):
|
| 132 |
+
"""Video-to-audio — video is provided as frozen cross-modal conditioning.
|
| 133 |
+
The video is kept clean (sigma=0) and influences audio generation via cross-modal attention.
|
| 134 |
+
"""
|
| 135 |
+
|
| 136 |
+
type: Literal["video_to_audio"] = "video_to_audio"
|
| 137 |
+
video: str
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
class AudioToVideoConditionConfig(ConfigBaseModel):
|
| 141 |
+
"""Audio-to-video — audio is provided as frozen cross-modal conditioning.
|
| 142 |
+
The audio is kept clean (sigma=0) and influences video generation via cross-modal attention.
|
| 143 |
+
"""
|
| 144 |
+
|
| 145 |
+
type: Literal["audio_to_video"] = "audio_to_video"
|
| 146 |
+
audio: str
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
ValidationCondition = Annotated[
|
| 150 |
+
Union[
|
| 151 |
+
FirstFrameConditionConfig,
|
| 152 |
+
PrefixConditionConfig,
|
| 153 |
+
SuffixConditionConfig,
|
| 154 |
+
SpatialCropConditionConfig,
|
| 155 |
+
MaskConditionConfig,
|
| 156 |
+
ReferenceConditionConfig,
|
| 157 |
+
VideoToAudioConditionConfig,
|
| 158 |
+
AudioToVideoConditionConfig,
|
| 159 |
+
],
|
| 160 |
+
Field(discriminator="type"),
|
| 161 |
+
]
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def _condition_targets_video(cond: ValidationCondition) -> bool:
|
| 165 |
+
"""Check if a validation condition targets the video modality."""
|
| 166 |
+
if cond.type in ("first_frame", "spatial_crop", "video_to_audio"):
|
| 167 |
+
return True
|
| 168 |
+
if cond.type in ("prefix", "suffix", "mask", "reference"):
|
| 169 |
+
return getattr(cond, "video", None) is not None
|
| 170 |
+
return False
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def _condition_targets_audio(cond: ValidationCondition) -> bool:
|
| 174 |
+
"""Check if a validation condition targets the audio modality."""
|
| 175 |
+
if cond.type == "audio_to_video":
|
| 176 |
+
return True
|
| 177 |
+
if cond.type in ("prefix", "suffix", "mask", "reference"):
|
| 178 |
+
return getattr(cond, "audio", None) is not None
|
| 179 |
+
return False
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
class ValidationSample(ConfigBaseModel):
|
| 183 |
+
"""Configuration for a single validation sample — fully self-describing."""
|
| 184 |
+
|
| 185 |
+
prompt: str
|
| 186 |
+
conditions: list[ValidationCondition] = Field(default_factory=list)
|
| 187 |
+
|
| 188 |
+
video_dims: tuple[int, int, int] | None = Field(
|
| 189 |
+
default=None,
|
| 190 |
+
description="Per-sample override for (width, height, frames). None = inherit from ValidationConfig.",
|
| 191 |
+
)
|
| 192 |
+
seed: int | None = Field(
|
| 193 |
+
default=None,
|
| 194 |
+
description="Per-sample override for random seed. None = inherit from ValidationConfig.",
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
@field_validator("video_dims")
|
| 198 |
+
@classmethod
|
| 199 |
+
def validate_video_dims(cls, v: tuple[int, int, int] | None) -> tuple[int, int, int] | None:
|
| 200 |
+
if v is None:
|
| 201 |
+
return v
|
| 202 |
+
width, height, frames = v
|
| 203 |
+
if width % 32 != 0:
|
| 204 |
+
raise ValueError(f"Width ({width}) must be divisible by 32")
|
| 205 |
+
if height % 32 != 0:
|
| 206 |
+
raise ValueError(f"Height ({height}) must be divisible by 32")
|
| 207 |
+
if frames % 8 != 1:
|
| 208 |
+
raise ValueError(f"Frames ({frames}) must satisfy frames % 8 == 1 for LTX-2 (e.g., 1, 9, 17, 25, ...)")
|
| 209 |
+
return v
|
| 210 |
+
|
| 211 |
+
@model_validator(mode="after")
|
| 212 |
+
def validate_frozen_modality_conflicts(self) -> "ValidationSample":
|
| 213 |
+
frozen_types = {c.type for c in self.conditions if c.type in ("video_to_audio", "audio_to_video")}
|
| 214 |
+
|
| 215 |
+
if "video_to_audio" in frozen_types and "audio_to_video" in frozen_types:
|
| 216 |
+
raise ValueError(
|
| 217 |
+
"Cannot have both video_to_audio and audio_to_video conditions — nothing would be generated"
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
if "video_to_audio" in frozen_types:
|
| 221 |
+
for c in self.conditions:
|
| 222 |
+
if c.type != "video_to_audio" and _condition_targets_video(c):
|
| 223 |
+
raise ValueError(
|
| 224 |
+
f"Cannot use video-targeting '{c.type}' condition when video is frozen (video_to_audio)"
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
if "audio_to_video" in frozen_types:
|
| 228 |
+
for c in self.conditions:
|
| 229 |
+
if c.type != "audio_to_video" and _condition_targets_audio(c):
|
| 230 |
+
raise ValueError(
|
| 231 |
+
f"Cannot use audio-targeting '{c.type}' condition when audio is frozen (audio_to_video)"
|
| 232 |
+
)
|
| 233 |
+
|
| 234 |
+
return self
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
class ModelConfig(ConfigBaseModel):
|
| 238 |
+
"""Configuration for the base model and training mode"""
|
| 239 |
+
|
| 240 |
+
model_path: str | Path = Field(
|
| 241 |
+
...,
|
| 242 |
+
description="Model path - local path to safetensors checkpoint file",
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
+
text_encoder_path: str | Path | None = Field(
|
| 246 |
+
default=None,
|
| 247 |
+
description="Path to text encoder (required for LTX-2/Gemma models, optional for LTXV/T5 models)",
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
training_mode: Literal["lora", "full"] = Field(
|
| 251 |
+
default="lora",
|
| 252 |
+
description="Training mode - either LoRA fine-tuning or full model fine-tuning",
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
load_checkpoint: str | Path | None = Field(
|
| 256 |
+
default=None,
|
| 257 |
+
description="Path to a checkpoint file or directory to load from. "
|
| 258 |
+
"If a directory is provided, the latest checkpoint will be used.",
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
@field_validator("model_path")
|
| 262 |
+
@classmethod
|
| 263 |
+
def validate_model_path(cls, v: str | Path) -> str | Path:
|
| 264 |
+
"""Validate that model_path is either a valid URL or an existing local path."""
|
| 265 |
+
is_url = str(v).startswith(("http://", "https://"))
|
| 266 |
+
|
| 267 |
+
if is_url:
|
| 268 |
+
raise ValueError(f"Model path cannot be a URL: {v}")
|
| 269 |
+
|
| 270 |
+
if not Path(v).exists():
|
| 271 |
+
raise ValueError(f"Model path does not exist: {v}")
|
| 272 |
+
|
| 273 |
+
return v
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
class LoraConfig(ConfigBaseModel):
|
| 277 |
+
"""Configuration for LoRA fine-tuning"""
|
| 278 |
+
|
| 279 |
+
rank: int = Field(
|
| 280 |
+
default=64,
|
| 281 |
+
description="Rank of LoRA adaptation",
|
| 282 |
+
ge=2,
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
alpha: int = Field(
|
| 286 |
+
default=64,
|
| 287 |
+
description="Alpha scaling factor for LoRA",
|
| 288 |
+
ge=1,
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
dropout: float = Field(
|
| 292 |
+
default=0.0,
|
| 293 |
+
description="Dropout probability for LoRA layers",
|
| 294 |
+
ge=0.0,
|
| 295 |
+
le=1.0,
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
target_modules: list[str] = Field(
|
| 299 |
+
default=["to_k", "to_q", "to_v", "to_out.0"],
|
| 300 |
+
description="List of modules to target with LoRA",
|
| 301 |
+
)
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def _get_strategy_discriminator(v: dict | TrainingStrategyConfigBase) -> str:
|
| 305 |
+
"""Discriminator function for strategy config union."""
|
| 306 |
+
if isinstance(v, dict):
|
| 307 |
+
return v.get("name", "text_to_video")
|
| 308 |
+
return v.name
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
# Union type for all strategy configs with discriminator
|
| 312 |
+
TrainingStrategyConfig = Annotated[
|
| 313 |
+
Annotated[TextToVideoConfig, Tag("text_to_video")]
|
| 314 |
+
| Annotated[VideoToVideoConfig, Tag("video_to_video")]
|
| 315 |
+
| Annotated[FlexibleStrategyConfig, Tag("flexible")],
|
| 316 |
+
Discriminator(_get_strategy_discriminator),
|
| 317 |
+
]
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
class OptimizationConfig(ConfigBaseModel):
|
| 321 |
+
"""Configuration for optimization parameters"""
|
| 322 |
+
|
| 323 |
+
learning_rate: float = Field(
|
| 324 |
+
default=5e-4,
|
| 325 |
+
description="Learning rate for optimization",
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
steps: int = Field(
|
| 329 |
+
default=3000,
|
| 330 |
+
description="Number of training steps",
|
| 331 |
+
)
|
| 332 |
+
|
| 333 |
+
batch_size: int = Field(
|
| 334 |
+
default=2,
|
| 335 |
+
description="Batch size for training",
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
gradient_accumulation_steps: int = Field(
|
| 339 |
+
default=1,
|
| 340 |
+
description="Number of steps to accumulate gradients",
|
| 341 |
+
)
|
| 342 |
+
|
| 343 |
+
max_grad_norm: float = Field(
|
| 344 |
+
default=1.0,
|
| 345 |
+
description="Maximum gradient norm for clipping",
|
| 346 |
+
)
|
| 347 |
+
|
| 348 |
+
optimizer_type: Literal["adamw", "adamw8bit"] = Field(
|
| 349 |
+
default="adamw",
|
| 350 |
+
description="Type of optimizer to use for training",
|
| 351 |
+
)
|
| 352 |
+
|
| 353 |
+
scheduler_type: Literal[
|
| 354 |
+
"constant",
|
| 355 |
+
"linear",
|
| 356 |
+
"cosine",
|
| 357 |
+
"cosine_with_restarts",
|
| 358 |
+
"polynomial",
|
| 359 |
+
"step",
|
| 360 |
+
] = Field(
|
| 361 |
+
default="linear",
|
| 362 |
+
description="Type of scheduler to use for training",
|
| 363 |
+
)
|
| 364 |
+
|
| 365 |
+
scheduler_params: dict = Field(
|
| 366 |
+
default_factory=dict,
|
| 367 |
+
description="Parameters for the scheduler",
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
enable_gradient_checkpointing: bool = Field(
|
| 371 |
+
default=False,
|
| 372 |
+
description="Enable gradient checkpointing to save memory at the cost of slower training",
|
| 373 |
+
)
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
class AccelerationConfig(ConfigBaseModel):
|
| 377 |
+
"""Configuration for hardware acceleration and compute optimization"""
|
| 378 |
+
|
| 379 |
+
mixed_precision_mode: Literal["no", "fp16", "bf16"] | None = Field(
|
| 380 |
+
default="bf16",
|
| 381 |
+
description="Mixed precision training mode",
|
| 382 |
+
)
|
| 383 |
+
|
| 384 |
+
quantization: QuantizationOptions | None = Field(
|
| 385 |
+
default=None,
|
| 386 |
+
description="Quantization precision to use",
|
| 387 |
+
)
|
| 388 |
+
|
| 389 |
+
load_text_encoder_in_8bit: bool = Field(
|
| 390 |
+
default=False,
|
| 391 |
+
description="Whether to load the text encoder in 8-bit precision to save memory",
|
| 392 |
+
)
|
| 393 |
+
|
| 394 |
+
offload_optimizer_during_validation: bool = Field(
|
| 395 |
+
default=False,
|
| 396 |
+
description="Offload optimizer state to CPU before validation video sampling and reload "
|
| 397 |
+
"it afterwards, to free VRAM for inference. Useful when optimizer state is large "
|
| 398 |
+
"(e.g. AdamW for full fine-tuning or high-rank LoRA) and validation OOMs because the "
|
| 399 |
+
"VAE decoder + transformer + optimizer state cannot coexist on the GPU. Has no effect "
|
| 400 |
+
"for FSDP (sharded state). Disabled by default.",
|
| 401 |
+
)
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
class DataConfig(ConfigBaseModel):
|
| 405 |
+
"""Configuration for data loading and processing"""
|
| 406 |
+
|
| 407 |
+
preprocessed_data_root: str = Field(
|
| 408 |
+
description="Path to folder containing preprocessed training data",
|
| 409 |
+
)
|
| 410 |
+
|
| 411 |
+
num_dataloader_workers: int = Field(
|
| 412 |
+
default=2,
|
| 413 |
+
description="Number of background processes for data loading (0 means synchronous loading)",
|
| 414 |
+
ge=0,
|
| 415 |
+
)
|
| 416 |
+
|
| 417 |
+
@field_validator("preprocessed_data_root")
|
| 418 |
+
@classmethod
|
| 419 |
+
def validate_preprocessed_data_root(cls, v: str) -> str:
|
| 420 |
+
"""Validate that preprocessed_data_root exists."""
|
| 421 |
+
path = Path(v).expanduser().resolve()
|
| 422 |
+
if not path.exists():
|
| 423 |
+
raise ValueError(f"Dataset path does not exist: {v}")
|
| 424 |
+
if not path.is_dir():
|
| 425 |
+
raise ValueError(f"Dataset path is not a directory: {v}")
|
| 426 |
+
return str(path)
|
| 427 |
+
|
| 428 |
+
|
| 429 |
+
class ValidationConfig(ConfigBaseModel):
|
| 430 |
+
"""Configuration for validation during training"""
|
| 431 |
+
|
| 432 |
+
# Per-sample configuration (new format — preferred)
|
| 433 |
+
samples: list[ValidationSample] = Field(
|
| 434 |
+
default_factory=list,
|
| 435 |
+
description="List of validation samples. Each sample is fully self-describing with its own "
|
| 436 |
+
"prompt, conditions, and optional overrides. Replaces prompts/images/reference_videos.",
|
| 437 |
+
)
|
| 438 |
+
|
| 439 |
+
# Legacy fields (deprecated — converted to samples internally via convert_legacy_format)
|
| 440 |
+
prompts: list[str] = Field(
|
| 441 |
+
default_factory=list,
|
| 442 |
+
description="[DEPRECATED: use 'samples' instead] List of prompts to use for validation",
|
| 443 |
+
)
|
| 444 |
+
|
| 445 |
+
negative_prompt: str = Field(
|
| 446 |
+
default="worst quality, inconsistent motion, blurry, jittery, distorted",
|
| 447 |
+
description="Negative prompt to use for validation examples",
|
| 448 |
+
)
|
| 449 |
+
|
| 450 |
+
images: list[str] | None = Field(
|
| 451 |
+
default=None,
|
| 452 |
+
description="[DEPRECATED: use 'samples' with first_frame conditions] "
|
| 453 |
+
"List of image paths to use for validation. "
|
| 454 |
+
"One image path must be provided for each validation prompt",
|
| 455 |
+
)
|
| 456 |
+
|
| 457 |
+
reference_videos: list[str] | None = Field(
|
| 458 |
+
default=None,
|
| 459 |
+
description="[DEPRECATED: use 'samples' with reference conditions] "
|
| 460 |
+
"List of reference video paths to use for validation. "
|
| 461 |
+
"One video path must be provided for each validation prompt",
|
| 462 |
+
)
|
| 463 |
+
|
| 464 |
+
reference_downscale_factor: int = Field(
|
| 465 |
+
default=1,
|
| 466 |
+
description="[DEPRECATED: use downscale_factor on ReferenceCondition] "
|
| 467 |
+
"Downscale factor for reference videos in IC-LoRA validation. "
|
| 468 |
+
"When > 1, reference videos are processed at 1/n resolution (e.g., 2 means half resolution). "
|
| 469 |
+
"Must match the factor used during dataset preprocessing.",
|
| 470 |
+
ge=1,
|
| 471 |
+
)
|
| 472 |
+
|
| 473 |
+
video_dims: tuple[int, int, int] = Field(
|
| 474 |
+
default=(960, 544, 97),
|
| 475 |
+
description="Dimensions of validation videos (width, height, frames). "
|
| 476 |
+
"Width and height must be divisible by 32. Frames must satisfy frames % 8 == 1 for LTX-2.",
|
| 477 |
+
)
|
| 478 |
+
|
| 479 |
+
@field_validator("video_dims")
|
| 480 |
+
@classmethod
|
| 481 |
+
def validate_video_dims(cls, v: tuple[int, int, int]) -> tuple[int, int, int]:
|
| 482 |
+
"""Validate video dimensions for LTX-2 compatibility."""
|
| 483 |
+
width, height, frames = v
|
| 484 |
+
|
| 485 |
+
if width % 32 != 0:
|
| 486 |
+
raise ValueError(f"Width ({width}) must be divisible by 32")
|
| 487 |
+
if height % 32 != 0:
|
| 488 |
+
raise ValueError(f"Height ({height}) must be divisible by 32")
|
| 489 |
+
if frames % 8 != 1:
|
| 490 |
+
raise ValueError(f"Frames ({frames}) must satisfy frames % 8 == 1 for LTX-2 (e.g., 1, 9, 17, 25, ...)")
|
| 491 |
+
|
| 492 |
+
return v
|
| 493 |
+
|
| 494 |
+
frame_rate: float = Field(
|
| 495 |
+
default=25.0,
|
| 496 |
+
description="Frame rate for validation videos",
|
| 497 |
+
gt=0,
|
| 498 |
+
)
|
| 499 |
+
|
| 500 |
+
seed: int = Field(
|
| 501 |
+
default=42,
|
| 502 |
+
description="Random seed used when sampling validation videos",
|
| 503 |
+
)
|
| 504 |
+
|
| 505 |
+
inference_steps: int = Field(
|
| 506 |
+
default=50,
|
| 507 |
+
description="Number of inference steps for validation",
|
| 508 |
+
gt=0,
|
| 509 |
+
)
|
| 510 |
+
|
| 511 |
+
interval: int | None = Field(
|
| 512 |
+
default=100,
|
| 513 |
+
description="Number of steps between validation runs. If None, validation is disabled.",
|
| 514 |
+
gt=0,
|
| 515 |
+
)
|
| 516 |
+
|
| 517 |
+
guidance_scale: float = Field(
|
| 518 |
+
default=4.0,
|
| 519 |
+
description="CFG guidance scale to use during validation",
|
| 520 |
+
ge=1.0,
|
| 521 |
+
)
|
| 522 |
+
|
| 523 |
+
stg_scale: float = Field(
|
| 524 |
+
default=1.0,
|
| 525 |
+
description="STG (Spatio-Temporal Guidance) scale. 0.0 disables STG. "
|
| 526 |
+
"Recommended value is 1.0. STG is combined with CFG for improved video quality.",
|
| 527 |
+
ge=0.0,
|
| 528 |
+
)
|
| 529 |
+
|
| 530 |
+
stg_blocks: list[int] | None = Field(
|
| 531 |
+
default=[29],
|
| 532 |
+
description="Which transformer blocks to perturb for STG. "
|
| 533 |
+
"None means all blocks are perturbed. Recommended for LTX-2: [29].",
|
| 534 |
+
)
|
| 535 |
+
|
| 536 |
+
stg_mode: Literal["stg_av", "stg_v"] = Field(
|
| 537 |
+
default="stg_av",
|
| 538 |
+
description="STG mode: 'stg_av' skips both audio and video self-attention, "
|
| 539 |
+
"'stg_v' skips only video self-attention.",
|
| 540 |
+
)
|
| 541 |
+
|
| 542 |
+
generate_audio: bool = Field(
|
| 543 |
+
default=True,
|
| 544 |
+
description="Whether to generate audio in validation samples. "
|
| 545 |
+
"Independent of training strategy setting - you can generate audio "
|
| 546 |
+
"in validation even when not training the audio branch.",
|
| 547 |
+
)
|
| 548 |
+
|
| 549 |
+
generate_video: bool = Field(
|
| 550 |
+
default=True,
|
| 551 |
+
description="Whether to generate video in validation samples. "
|
| 552 |
+
"Set to False for audio-only or v2a validation to save VRAM by skipping video VAE decoder loading. "
|
| 553 |
+
"When False, validation will only generate audio (requires generate_audio=True).",
|
| 554 |
+
)
|
| 555 |
+
|
| 556 |
+
skip_initial_validation: bool = Field(
|
| 557 |
+
default=False,
|
| 558 |
+
description="Skip validation video sampling at step 0 (beginning of training)",
|
| 559 |
+
)
|
| 560 |
+
|
| 561 |
+
include_reference_in_output: bool = Field(
|
| 562 |
+
default=False,
|
| 563 |
+
description="[DEPRECATED: use include_in_output on ReferenceCondition] "
|
| 564 |
+
"For video-to-video training: concatenate the original reference video side-by-side "
|
| 565 |
+
"with the generated output. The reference comes from the input video, not from the model's output.",
|
| 566 |
+
)
|
| 567 |
+
|
| 568 |
+
@field_validator("images")
|
| 569 |
+
@classmethod
|
| 570 |
+
def validate_images(cls, v: list[str] | None, info: ValidationInfo) -> list[str] | None:
|
| 571 |
+
"""Validate that number of images (if provided) matches number of prompts."""
|
| 572 |
+
if v is None:
|
| 573 |
+
return None
|
| 574 |
+
|
| 575 |
+
num_prompts = len(info.data.get("prompts", []))
|
| 576 |
+
if v is not None and len(v) != num_prompts:
|
| 577 |
+
raise ValueError(f"Number of images ({len(v)}) must match number of prompts ({num_prompts})")
|
| 578 |
+
|
| 579 |
+
for image_path in v:
|
| 580 |
+
if not Path(image_path).exists():
|
| 581 |
+
raise ValueError(f"Image path '{image_path}' does not exist")
|
| 582 |
+
|
| 583 |
+
return v
|
| 584 |
+
|
| 585 |
+
@field_validator("reference_videos")
|
| 586 |
+
@classmethod
|
| 587 |
+
def validate_reference_videos(cls, v: list[str] | None, info: ValidationInfo) -> list[str] | None:
|
| 588 |
+
"""Validate that number of reference videos (if provided) matches number of prompts."""
|
| 589 |
+
if v is None:
|
| 590 |
+
return None
|
| 591 |
+
|
| 592 |
+
num_prompts = len(info.data.get("prompts", []))
|
| 593 |
+
if v is not None and len(v) != num_prompts:
|
| 594 |
+
raise ValueError(f"Number of reference videos ({len(v)}) must match number of prompts ({num_prompts})")
|
| 595 |
+
|
| 596 |
+
for video_path in v:
|
| 597 |
+
if not Path(video_path).exists():
|
| 598 |
+
raise ValueError(f"Reference video path '{video_path}' does not exist")
|
| 599 |
+
|
| 600 |
+
return v
|
| 601 |
+
|
| 602 |
+
@model_validator(mode="after")
|
| 603 |
+
def convert_legacy_format(self) -> "ValidationConfig":
|
| 604 |
+
"""Convert deprecated prompts/images/reference_videos to the new samples format."""
|
| 605 |
+
if self.prompts and not self.samples:
|
| 606 |
+
samples = []
|
| 607 |
+
for i, prompt in enumerate(self.prompts):
|
| 608 |
+
conditions: list[ValidationCondition] = []
|
| 609 |
+
if self.images and i < len(self.images):
|
| 610 |
+
conditions.append(FirstFrameConditionConfig(image_or_video=self.images[i]))
|
| 611 |
+
if self.reference_videos and i < len(self.reference_videos):
|
| 612 |
+
conditions.append(
|
| 613 |
+
ReferenceConditionConfig(
|
| 614 |
+
video=self.reference_videos[i],
|
| 615 |
+
downscale_factor=self.reference_downscale_factor,
|
| 616 |
+
include_in_output=self.include_reference_in_output,
|
| 617 |
+
)
|
| 618 |
+
)
|
| 619 |
+
samples.append(ValidationSample(prompt=prompt, conditions=conditions))
|
| 620 |
+
self.samples = samples
|
| 621 |
+
return self
|
| 622 |
+
|
| 623 |
+
@model_validator(mode="after")
|
| 624 |
+
def validate_scaled_reference_dimensions(self) -> "ValidationConfig":
|
| 625 |
+
"""Validate that scaled reference dimensions are valid when reference_downscale_factor > 1."""
|
| 626 |
+
if self.reference_downscale_factor > 1:
|
| 627 |
+
width, height, _frames = self.video_dims
|
| 628 |
+
|
| 629 |
+
if width % self.reference_downscale_factor != 0:
|
| 630 |
+
raise ValueError(
|
| 631 |
+
f"Width {width} is not evenly divisible by reference_downscale_factor "
|
| 632 |
+
f"{self.reference_downscale_factor}. Choose a downscale factor that divides {width} evenly."
|
| 633 |
+
)
|
| 634 |
+
if height % self.reference_downscale_factor != 0:
|
| 635 |
+
raise ValueError(
|
| 636 |
+
f"Height {height} is not evenly divisible by reference_downscale_factor "
|
| 637 |
+
f"{self.reference_downscale_factor}. Choose a downscale factor that divides {height} evenly."
|
| 638 |
+
)
|
| 639 |
+
|
| 640 |
+
scaled_width = width // self.reference_downscale_factor
|
| 641 |
+
scaled_height = height // self.reference_downscale_factor
|
| 642 |
+
|
| 643 |
+
if scaled_width % 32 != 0:
|
| 644 |
+
raise ValueError(
|
| 645 |
+
f"Scaled reference width {scaled_width} (from {width} / {self.reference_downscale_factor}) "
|
| 646 |
+
f"is not divisible by 32. Choose a different downscale factor or adjust video_dims."
|
| 647 |
+
)
|
| 648 |
+
if scaled_height % 32 != 0:
|
| 649 |
+
raise ValueError(
|
| 650 |
+
f"Scaled reference height {scaled_height} (from {height} / {self.reference_downscale_factor}) "
|
| 651 |
+
f"is not divisible by 32. Choose a different downscale factor or adjust video_dims."
|
| 652 |
+
)
|
| 653 |
+
|
| 654 |
+
return self
|
| 655 |
+
|
| 656 |
+
@model_validator(mode="after")
|
| 657 |
+
def validate_output_modality_requirements(self) -> "ValidationConfig":
|
| 658 |
+
"""Validate output modality settings when validation is configured."""
|
| 659 |
+
has_validation = bool(self.prompts) or bool(self.samples)
|
| 660 |
+
if has_validation and not self.generate_video and not self.generate_audio:
|
| 661 |
+
raise ValueError(
|
| 662 |
+
"At least one of generate_video or generate_audio must be True when validation is configured."
|
| 663 |
+
)
|
| 664 |
+
return self
|
| 665 |
+
|
| 666 |
+
|
| 667 |
+
class CheckpointsConfig(ConfigBaseModel):
|
| 668 |
+
"""Configuration for model checkpointing during training"""
|
| 669 |
+
|
| 670 |
+
interval: int | None = Field(
|
| 671 |
+
default=None,
|
| 672 |
+
description="Number of steps between checkpoint saves. If None, intermediate checkpoints are disabled.",
|
| 673 |
+
gt=0,
|
| 674 |
+
)
|
| 675 |
+
|
| 676 |
+
keep_last_n: int = Field(
|
| 677 |
+
default=1,
|
| 678 |
+
description="Number of most recent checkpoints to keep. Set to -1 to keep all checkpoints.",
|
| 679 |
+
ge=-1,
|
| 680 |
+
)
|
| 681 |
+
|
| 682 |
+
precision: Literal["bfloat16", "float32"] = Field(
|
| 683 |
+
default="bfloat16",
|
| 684 |
+
description="Precision to use when saving checkpoint weights. Options: 'bfloat16' or 'float32'.",
|
| 685 |
+
)
|
| 686 |
+
|
| 687 |
+
no_resume: bool = Field(
|
| 688 |
+
default=False,
|
| 689 |
+
description="When True, ignore any saved training state and start from step 0. "
|
| 690 |
+
"Model weights from load_checkpoint are still loaded, but optimizer/scheduler "
|
| 691 |
+
"state and step counter are reset.",
|
| 692 |
+
)
|
| 693 |
+
|
| 694 |
+
save_training_state: Literal["full", "minimal", "off"] = Field(
|
| 695 |
+
default="minimal",
|
| 696 |
+
description="Save training state alongside checkpoints for resume. "
|
| 697 |
+
"'full': optimizer + scheduler + RNG + step (~800MB for LoRA, much larger for full fine-tuning). "
|
| 698 |
+
"'minimal': scheduler + RNG + step only (~few KB, sufficient for LoRA). "
|
| 699 |
+
"'off': nothing saved, resume not possible.",
|
| 700 |
+
)
|
| 701 |
+
|
| 702 |
+
|
| 703 |
+
class HubConfig(ConfigBaseModel):
|
| 704 |
+
"""Configuration for Hugging Face Hub integration"""
|
| 705 |
+
|
| 706 |
+
push_to_hub: bool = Field(default=False, description="Whether to push the model weights to the Hugging Face Hub")
|
| 707 |
+
hub_model_id: str | None = Field(
|
| 708 |
+
default=None, description="Hugging Face Hub repository ID (e.g., 'username/repo-name')"
|
| 709 |
+
)
|
| 710 |
+
|
| 711 |
+
@model_validator(mode="after")
|
| 712 |
+
def validate_hub_config(self) -> "HubConfig":
|
| 713 |
+
"""Validate that hub_model_id is not None when push_to_hub is True."""
|
| 714 |
+
if self.push_to_hub and not self.hub_model_id:
|
| 715 |
+
raise ValueError("hub_model_id must be specified when push_to_hub is True")
|
| 716 |
+
return self
|
| 717 |
+
|
| 718 |
+
|
| 719 |
+
class WandbConfig(ConfigBaseModel):
|
| 720 |
+
"""Configuration for Weights & Biases logging"""
|
| 721 |
+
|
| 722 |
+
enabled: bool = Field(
|
| 723 |
+
default=False,
|
| 724 |
+
description="Whether to enable W&B logging",
|
| 725 |
+
)
|
| 726 |
+
|
| 727 |
+
project: str = Field(
|
| 728 |
+
default="ltxv-trainer",
|
| 729 |
+
description="W&B project name",
|
| 730 |
+
)
|
| 731 |
+
|
| 732 |
+
entity: str | None = Field(
|
| 733 |
+
default=None,
|
| 734 |
+
description="W&B username or team",
|
| 735 |
+
)
|
| 736 |
+
|
| 737 |
+
tags: list[str] = Field(
|
| 738 |
+
default_factory=list,
|
| 739 |
+
description="Tags to add to the W&B run",
|
| 740 |
+
)
|
| 741 |
+
|
| 742 |
+
log_validation_videos: bool = Field(
|
| 743 |
+
default=True,
|
| 744 |
+
description="Whether to log validation videos to W&B",
|
| 745 |
+
)
|
| 746 |
+
|
| 747 |
+
|
| 748 |
+
class FlowMatchingConfig(ConfigBaseModel):
|
| 749 |
+
"""Configuration for flow matching training"""
|
| 750 |
+
|
| 751 |
+
timestep_sampling_mode: Literal["uniform", "shifted_logit_normal"] = Field(
|
| 752 |
+
default="shifted_logit_normal",
|
| 753 |
+
description="Mode to use for timestep sampling",
|
| 754 |
+
)
|
| 755 |
+
|
| 756 |
+
timestep_sampling_params: dict = Field(
|
| 757 |
+
default_factory=dict,
|
| 758 |
+
description="Parameters for timestep sampling",
|
| 759 |
+
)
|
| 760 |
+
|
| 761 |
+
|
| 762 |
+
class LtxTrainerConfig(ConfigBaseModel):
|
| 763 |
+
"""Unified configuration for LTXV training"""
|
| 764 |
+
|
| 765 |
+
# Sub-configurations
|
| 766 |
+
model: ModelConfig = Field(default_factory=ModelConfig)
|
| 767 |
+
lora: LoraConfig | None = Field(default=None)
|
| 768 |
+
training_strategy: TrainingStrategyConfig = Field(
|
| 769 |
+
default_factory=TextToVideoConfig,
|
| 770 |
+
description="Training strategy configuration. Determines the training mode and its parameters.",
|
| 771 |
+
)
|
| 772 |
+
optimization: OptimizationConfig = Field(default_factory=OptimizationConfig)
|
| 773 |
+
acceleration: AccelerationConfig = Field(default_factory=AccelerationConfig)
|
| 774 |
+
data: DataConfig
|
| 775 |
+
validation: ValidationConfig = Field(default_factory=ValidationConfig)
|
| 776 |
+
checkpoints: CheckpointsConfig = Field(default_factory=CheckpointsConfig)
|
| 777 |
+
hub: HubConfig = Field(default_factory=HubConfig)
|
| 778 |
+
flow_matching: FlowMatchingConfig = Field(default_factory=FlowMatchingConfig)
|
| 779 |
+
wandb: WandbConfig = Field(default_factory=WandbConfig)
|
| 780 |
+
|
| 781 |
+
# General configuration
|
| 782 |
+
seed: int = Field(
|
| 783 |
+
default=42,
|
| 784 |
+
description="Random seed for reproducibility",
|
| 785 |
+
)
|
| 786 |
+
|
| 787 |
+
output_dir: str = Field(
|
| 788 |
+
default="outputs",
|
| 789 |
+
description="Directory to save model outputs",
|
| 790 |
+
)
|
| 791 |
+
|
| 792 |
+
# noinspection PyNestedDecorators
|
| 793 |
+
@field_validator("output_dir")
|
| 794 |
+
@classmethod
|
| 795 |
+
def expand_output_path(cls, v: str) -> str:
|
| 796 |
+
"""Expand user home directory in output path."""
|
| 797 |
+
return str(Path(v).expanduser().resolve())
|
| 798 |
+
|
| 799 |
+
def _validate_data_dirs_exist(self) -> None:
|
| 800 |
+
"""Verify that every directory declared by the training strategy exists under the data root."""
|
| 801 |
+
data_root = Path(self.data.preprocessed_data_root)
|
| 802 |
+
for dir_name in self.training_strategy.get_data_sources():
|
| 803 |
+
dir_path = data_root / dir_name
|
| 804 |
+
if not dir_path.is_dir():
|
| 805 |
+
raise ValueError(
|
| 806 |
+
f"Required data directory '{dir_name}' does not exist under preprocessed_data_root: {dir_path}"
|
| 807 |
+
)
|
| 808 |
+
|
| 809 |
+
@model_validator(mode="after")
|
| 810 |
+
def validate_strategy_compatibility(self) -> "LtxTrainerConfig":
|
| 811 |
+
"""Validate that training strategy and other configurations are compatible."""
|
| 812 |
+
self._validate_data_dirs_exist()
|
| 813 |
+
|
| 814 |
+
# Check that reference videos are provided when using video_to_video strategy
|
| 815 |
+
if self.training_strategy.name == "video_to_video" and self.validation.interval:
|
| 816 |
+
has_reference = bool(self.validation.reference_videos) or any(
|
| 817 |
+
cond.type == "reference" for sample in self.validation.samples for cond in sample.conditions
|
| 818 |
+
)
|
| 819 |
+
if not has_reference:
|
| 820 |
+
raise ValueError(
|
| 821 |
+
"reference_videos or samples with reference conditions must be provided "
|
| 822 |
+
"in validation config when using video_to_video strategy"
|
| 823 |
+
)
|
| 824 |
+
|
| 825 |
+
# Check that LoRA config is provided when training mode is lora
|
| 826 |
+
if self.model.training_mode == "lora" and self.lora is None:
|
| 827 |
+
raise ValueError("LoRA configuration must be provided when training_mode is 'lora'")
|
| 828 |
+
|
| 829 |
+
# Check that LoRA config is provided when using video_to_video strategy
|
| 830 |
+
if self.training_strategy.name == "video_to_video" and self.model.training_mode != "lora":
|
| 831 |
+
raise ValueError("Training mode must be 'lora' when using video_to_video strategy")
|
| 832 |
+
|
| 833 |
+
return self
|
packages/ltx-trainer/src/ltx_trainer/config_display.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Display utilities for training configuration.
|
| 2 |
+
This module provides formatted console output for LtxTrainerConfig.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from rich import box
|
| 6 |
+
from rich.console import Console
|
| 7 |
+
from rich.table import Table
|
| 8 |
+
|
| 9 |
+
from ltx_trainer.config import LtxTrainerConfig
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def print_config(config: LtxTrainerConfig) -> None:
|
| 13 |
+
"""Print configuration as a nicely formatted table with sections."""
|
| 14 |
+
|
| 15 |
+
def fmt(v: object, max_len: int = 55) -> str:
|
| 16 |
+
"""Format any value for display."""
|
| 17 |
+
if v is None:
|
| 18 |
+
return "[dim]—[/]"
|
| 19 |
+
if isinstance(v, bool):
|
| 20 |
+
return "[green]✓[/]" if v else "[dim]✗[/]"
|
| 21 |
+
if isinstance(v, (list, tuple)):
|
| 22 |
+
if not v:
|
| 23 |
+
return "[dim]—[/]"
|
| 24 |
+
return ", ".join(str(x) for x in v)
|
| 25 |
+
s = str(v)
|
| 26 |
+
return s[: max_len - 3] + "..." if len(s) > max_len else s
|
| 27 |
+
|
| 28 |
+
cfg = config
|
| 29 |
+
opt = cfg.optimization
|
| 30 |
+
val = cfg.validation
|
| 31 |
+
accel = cfg.acceleration
|
| 32 |
+
|
| 33 |
+
# Build sections: list of (section_title, [(key, value), ...])
|
| 34 |
+
sections: list[tuple[str, list[tuple[str, str]]]] = [
|
| 35 |
+
(
|
| 36 |
+
"🎬 Model",
|
| 37 |
+
[
|
| 38 |
+
("Base", fmt(cfg.model.model_path)),
|
| 39 |
+
("Text Encoder", fmt(cfg.model.text_encoder_path) or "[dim]Built-in[/]"),
|
| 40 |
+
("Training Mode", f"[bold green]{cfg.model.training_mode.upper()}[/]"),
|
| 41 |
+
("Load Checkpoint", fmt(cfg.model.load_checkpoint) if cfg.model.load_checkpoint else "[dim]—[/]"),
|
| 42 |
+
],
|
| 43 |
+
),
|
| 44 |
+
]
|
| 45 |
+
|
| 46 |
+
if cfg.lora:
|
| 47 |
+
sections.append(
|
| 48 |
+
(
|
| 49 |
+
"🔗 LoRA",
|
| 50 |
+
[
|
| 51 |
+
("Rank / Alpha", f"{cfg.lora.rank} / {cfg.lora.alpha}"),
|
| 52 |
+
("Dropout", str(cfg.lora.dropout)),
|
| 53 |
+
("Target Modules", fmt(cfg.lora.target_modules)),
|
| 54 |
+
],
|
| 55 |
+
)
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
# Strategy section - include strategy-specific fields
|
| 59 |
+
strategy_items: list[tuple[str, str]] = [("Name", cfg.training_strategy.name)]
|
| 60 |
+
if hasattr(cfg.training_strategy, "with_audio"):
|
| 61 |
+
strategy_items.append(("Audio", fmt(cfg.training_strategy.with_audio)))
|
| 62 |
+
if hasattr(cfg.training_strategy, "first_frame_conditioning_p"):
|
| 63 |
+
strategy_items.append(("First Frame Cond P", str(cfg.training_strategy.first_frame_conditioning_p)))
|
| 64 |
+
|
| 65 |
+
sections.append(("🎯 Strategy", strategy_items))
|
| 66 |
+
|
| 67 |
+
sections.extend(
|
| 68 |
+
[
|
| 69 |
+
(
|
| 70 |
+
"⚡ Optimization",
|
| 71 |
+
[
|
| 72 |
+
("Steps", f"[bold]{opt.steps:,}[/]"),
|
| 73 |
+
("Learning Rate", f"{opt.learning_rate:.2e}"),
|
| 74 |
+
("Batch Size", str(opt.batch_size)),
|
| 75 |
+
("Grad Accumulation", str(opt.gradient_accumulation_steps)),
|
| 76 |
+
("Optimizer", opt.optimizer_type),
|
| 77 |
+
("Scheduler", opt.scheduler_type),
|
| 78 |
+
("Max Grad Norm", str(opt.max_grad_norm)),
|
| 79 |
+
("Grad Checkpointing", fmt(opt.enable_gradient_checkpointing)),
|
| 80 |
+
],
|
| 81 |
+
),
|
| 82 |
+
(
|
| 83 |
+
"🚀 Acceleration",
|
| 84 |
+
[
|
| 85 |
+
("Mixed Precision", accel.mixed_precision_mode or "[dim]—[/]"),
|
| 86 |
+
("Quantization", str(accel.quantization) if accel.quantization else "[dim]—[/]"),
|
| 87 |
+
("Text Encoder 8bit", fmt(accel.load_text_encoder_in_8bit)),
|
| 88 |
+
("Optimizer CPU Offload", fmt(accel.offload_optimizer_during_validation)),
|
| 89 |
+
],
|
| 90 |
+
),
|
| 91 |
+
(
|
| 92 |
+
"🎥 Validation",
|
| 93 |
+
[
|
| 94 |
+
("Prompts", f"{len(val.prompts)} prompt(s)" if val.prompts else "[dim]—[/]"),
|
| 95 |
+
("Interval", f"Every {val.interval} steps" if val.interval else "[dim]Disabled[/]"),
|
| 96 |
+
("Video Dims", f"{val.video_dims[0]}x{val.video_dims[1]}, {val.video_dims[2]} frames"),
|
| 97 |
+
("Frame Rate", f"{val.frame_rate} fps"),
|
| 98 |
+
("Inference Steps", str(val.inference_steps)),
|
| 99 |
+
("CFG Scale", str(val.guidance_scale)),
|
| 100 |
+
(
|
| 101 |
+
"STG",
|
| 102 |
+
f"scale={val.stg_scale}; blocks={fmt(val.stg_blocks)}; mode={val.stg_mode}"
|
| 103 |
+
if val.stg_scale > 0
|
| 104 |
+
else "[dim]Disabled[/]",
|
| 105 |
+
),
|
| 106 |
+
("Seed", str(val.seed)),
|
| 107 |
+
],
|
| 108 |
+
),
|
| 109 |
+
(
|
| 110 |
+
"📂 Data & Output",
|
| 111 |
+
[
|
| 112 |
+
("Dataset", fmt(cfg.data.preprocessed_data_root)),
|
| 113 |
+
("Dataloader Workers", str(cfg.data.num_dataloader_workers)),
|
| 114 |
+
("Output Dir", fmt(cfg.output_dir)),
|
| 115 |
+
("Seed", str(cfg.seed)),
|
| 116 |
+
],
|
| 117 |
+
),
|
| 118 |
+
(
|
| 119 |
+
"🔌 Integrations",
|
| 120 |
+
[
|
| 121 |
+
(
|
| 122 |
+
"Checkpoints",
|
| 123 |
+
f"Every {cfg.checkpoints.interval} steps (keep {cfg.checkpoints.keep_last_n})"
|
| 124 |
+
if cfg.checkpoints.interval
|
| 125 |
+
else "[dim]Disabled[/]",
|
| 126 |
+
),
|
| 127 |
+
("W&B", f"{cfg.wandb.project}" if cfg.wandb.enabled else "[dim]Disabled[/]"),
|
| 128 |
+
("HF Hub", cfg.hub.hub_model_id if cfg.hub.push_to_hub else "[dim]Disabled[/]"),
|
| 129 |
+
],
|
| 130 |
+
),
|
| 131 |
+
]
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
# Build table with section headers
|
| 135 |
+
table = Table(
|
| 136 |
+
title="[bold]⚙️ Training Configuration[/]",
|
| 137 |
+
show_header=False,
|
| 138 |
+
box=box.ROUNDED,
|
| 139 |
+
border_style="bright_blue",
|
| 140 |
+
padding=(0, 1),
|
| 141 |
+
title_style="bold bright_blue",
|
| 142 |
+
)
|
| 143 |
+
table.add_column("Key", style="white", width=20)
|
| 144 |
+
table.add_column("Value", style="cyan")
|
| 145 |
+
|
| 146 |
+
for i, (section_title, items) in enumerate(sections):
|
| 147 |
+
if i > 0:
|
| 148 |
+
table.add_row("", "") # Blank line between sections
|
| 149 |
+
table.add_row(f"[bold yellow]{section_title}[/]", "")
|
| 150 |
+
for key, value in items:
|
| 151 |
+
table.add_row(f" {key}", value)
|
| 152 |
+
|
| 153 |
+
console = Console()
|
| 154 |
+
console.print()
|
| 155 |
+
console.print(table)
|
| 156 |
+
console.print()
|
packages/ltx-trainer/src/ltx_trainer/datasets.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from einops import rearrange
|
| 6 |
+
from torch import Tensor
|
| 7 |
+
from torch.utils.data import Dataset
|
| 8 |
+
|
| 9 |
+
from ltx_trainer import logger
|
| 10 |
+
|
| 11 |
+
# Constants for precomputed data directories
|
| 12 |
+
PRECOMPUTED_DIR_NAME = ".precomputed"
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class DummyDataset(Dataset):
|
| 16 |
+
"""Produce random latents and prompt embeddings. For minimal demonstration and benchmarking purposes"""
|
| 17 |
+
|
| 18 |
+
def __init__(
|
| 19 |
+
self,
|
| 20 |
+
width: int = 1024,
|
| 21 |
+
height: int = 1024,
|
| 22 |
+
num_frames: int = 25,
|
| 23 |
+
fps: int = 24,
|
| 24 |
+
dataset_length: int = 200,
|
| 25 |
+
latent_dim: int = 128,
|
| 26 |
+
latent_spatial_compression_ratio: int = 32,
|
| 27 |
+
latent_temporal_compression_ratio: int = 8,
|
| 28 |
+
prompt_embed_dim: int = 4096,
|
| 29 |
+
prompt_sequence_length: int = 256,
|
| 30 |
+
) -> None:
|
| 31 |
+
if width % 32 != 0:
|
| 32 |
+
raise ValueError(f"Width must be divisible by 32, got {width=}")
|
| 33 |
+
|
| 34 |
+
if height % 32 != 0:
|
| 35 |
+
raise ValueError(f"Height must be divisible by 32, got {height=}")
|
| 36 |
+
|
| 37 |
+
if num_frames % 8 != 1:
|
| 38 |
+
raise ValueError(f"Number of frames must have a remainder of 1 when divided by 8, got {num_frames=}")
|
| 39 |
+
|
| 40 |
+
self.width = width
|
| 41 |
+
self.height = height
|
| 42 |
+
self.num_frames = num_frames
|
| 43 |
+
self.fps = fps
|
| 44 |
+
self.dataset_length = dataset_length
|
| 45 |
+
self.latent_dim = latent_dim
|
| 46 |
+
self.num_latent_frames = (num_frames - 1) // latent_temporal_compression_ratio + 1
|
| 47 |
+
self.latent_height = height // latent_spatial_compression_ratio
|
| 48 |
+
self.latent_width = width // latent_spatial_compression_ratio
|
| 49 |
+
self.latent_sequence_length = self.num_latent_frames * self.latent_height * self.latent_width
|
| 50 |
+
self.prompt_embed_dim = prompt_embed_dim
|
| 51 |
+
self.prompt_sequence_length = prompt_sequence_length
|
| 52 |
+
|
| 53 |
+
def __len__(self) -> int:
|
| 54 |
+
return self.dataset_length
|
| 55 |
+
|
| 56 |
+
def __getitem__(self, idx: int) -> dict[str, dict[str, Tensor]]:
|
| 57 |
+
return {
|
| 58 |
+
"latent_conditions": {
|
| 59 |
+
"latents": torch.randn(
|
| 60 |
+
self.latent_dim,
|
| 61 |
+
self.num_latent_frames,
|
| 62 |
+
self.latent_height,
|
| 63 |
+
self.latent_width,
|
| 64 |
+
),
|
| 65 |
+
"num_frames": self.num_latent_frames,
|
| 66 |
+
"height": self.latent_height,
|
| 67 |
+
"width": self.latent_width,
|
| 68 |
+
"fps": self.fps,
|
| 69 |
+
},
|
| 70 |
+
"text_conditions": {
|
| 71 |
+
"video_prompt_embeds": torch.randn(
|
| 72 |
+
self.prompt_sequence_length,
|
| 73 |
+
self.prompt_embed_dim,
|
| 74 |
+
),
|
| 75 |
+
"audio_prompt_embeds": torch.randn(
|
| 76 |
+
self.prompt_sequence_length,
|
| 77 |
+
self.prompt_embed_dim,
|
| 78 |
+
),
|
| 79 |
+
"prompt_attention_mask": torch.ones(
|
| 80 |
+
self.prompt_sequence_length,
|
| 81 |
+
dtype=torch.bool,
|
| 82 |
+
),
|
| 83 |
+
},
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
class PrecomputedDataset(Dataset):
|
| 88 |
+
def __init__(self, data_root: str, data_sources: dict[str, str] | list[str] | None = None) -> None:
|
| 89 |
+
"""
|
| 90 |
+
Generic dataset for loading precomputed data from multiple sources.
|
| 91 |
+
Args:
|
| 92 |
+
data_root: Root directory containing preprocessed data
|
| 93 |
+
data_sources: Either:
|
| 94 |
+
- Dict mapping directory names to output keys
|
| 95 |
+
- List of directory names (keys will equal values)
|
| 96 |
+
- None (defaults to ["latents", "conditions"])
|
| 97 |
+
Example:
|
| 98 |
+
# Standard mode (list)
|
| 99 |
+
dataset = PrecomputedDataset("data/", ["latents", "conditions"])
|
| 100 |
+
# Standard mode (dict)
|
| 101 |
+
dataset = PrecomputedDataset("data/", {"latents": "latent_conditions", "conditions": "text_conditions"})
|
| 102 |
+
# IC-LoRA mode
|
| 103 |
+
dataset = PrecomputedDataset("data/", ["latents", "conditions", "reference_latents"])
|
| 104 |
+
Note:
|
| 105 |
+
Latents are always returned in non-patchified format [C, F, H, W].
|
| 106 |
+
Legacy patchified format [seq_len, C] is automatically converted.
|
| 107 |
+
"""
|
| 108 |
+
super().__init__()
|
| 109 |
+
|
| 110 |
+
self.data_root = self._setup_data_root(data_root)
|
| 111 |
+
self.data_sources = self._normalize_data_sources(data_sources)
|
| 112 |
+
self.source_paths = self._setup_source_paths()
|
| 113 |
+
self.sample_files = self._discover_samples()
|
| 114 |
+
self._validate_setup()
|
| 115 |
+
|
| 116 |
+
@staticmethod
|
| 117 |
+
def _setup_data_root(data_root: str) -> Path:
|
| 118 |
+
"""Setup and validate the data root directory."""
|
| 119 |
+
data_root = Path(data_root).expanduser().resolve()
|
| 120 |
+
|
| 121 |
+
if not data_root.exists():
|
| 122 |
+
raise FileNotFoundError(f"Data root directory does not exist: {data_root}")
|
| 123 |
+
|
| 124 |
+
# If the given path is the dataset root, use the precomputed subdirectory
|
| 125 |
+
if (data_root / PRECOMPUTED_DIR_NAME).exists():
|
| 126 |
+
data_root = data_root / PRECOMPUTED_DIR_NAME
|
| 127 |
+
|
| 128 |
+
return data_root
|
| 129 |
+
|
| 130 |
+
@staticmethod
|
| 131 |
+
def _normalize_data_sources(data_sources: dict[str, str] | list[str] | None) -> dict[str, str]:
|
| 132 |
+
"""Normalize data_sources input to a consistent dict format."""
|
| 133 |
+
if data_sources is None:
|
| 134 |
+
# Default sources
|
| 135 |
+
return {"latents": "latent_conditions", "conditions": "text_conditions"}
|
| 136 |
+
elif isinstance(data_sources, list):
|
| 137 |
+
# Convert list to dict where keys equal values
|
| 138 |
+
return {source: source for source in data_sources}
|
| 139 |
+
elif isinstance(data_sources, dict):
|
| 140 |
+
return data_sources.copy()
|
| 141 |
+
else:
|
| 142 |
+
raise TypeError(f"data_sources must be dict, list, or None, got {type(data_sources)}")
|
| 143 |
+
|
| 144 |
+
def _setup_source_paths(self) -> dict[str, Path]:
|
| 145 |
+
"""Map data source names to their actual directory paths."""
|
| 146 |
+
source_paths = {}
|
| 147 |
+
|
| 148 |
+
for dir_name in self.data_sources:
|
| 149 |
+
source_path = self.data_root / dir_name
|
| 150 |
+
source_paths[dir_name] = source_path
|
| 151 |
+
|
| 152 |
+
# Check that all sources exist.
|
| 153 |
+
if not source_path.exists():
|
| 154 |
+
raise FileNotFoundError(f"Required {dir_name} directory does not exist: {source_path}")
|
| 155 |
+
|
| 156 |
+
return source_paths
|
| 157 |
+
|
| 158 |
+
def _discover_samples(self) -> dict[str, list[Path]]:
|
| 159 |
+
"""Discover all valid sample files across all data sources.
|
| 160 |
+
Uses a fast two-pass approach: first globs all sources in parallel to build
|
| 161 |
+
full-path sets in memory, then checks expected paths via set membership.
|
| 162 |
+
This avoids O(N * num_sources) stat calls on networked filesystems while
|
| 163 |
+
correctly handling path remapping (e.g. latent_X.pt -> condition_X.pt).
|
| 164 |
+
"""
|
| 165 |
+
if not self.data_sources:
|
| 166 |
+
raise ValueError("No data sources configured")
|
| 167 |
+
|
| 168 |
+
data_key = "latents" if "latents" in self.data_sources else next(iter(self.data_sources.keys()))
|
| 169 |
+
data_path = self.source_paths[data_key]
|
| 170 |
+
|
| 171 |
+
# Pass 1: Glob all sources in parallel, build full-path sets
|
| 172 |
+
def _glob_source(dir_name: str) -> tuple[list[Path], set[str]]:
|
| 173 |
+
source_path = self.source_paths[dir_name]
|
| 174 |
+
paths = list(source_path.glob("**/*.pt"))
|
| 175 |
+
path_set = {str(p) for p in paths}
|
| 176 |
+
return paths, path_set
|
| 177 |
+
|
| 178 |
+
with ThreadPoolExecutor(max_workers=len(self.data_sources)) as executor:
|
| 179 |
+
glob_results = dict(
|
| 180 |
+
zip(
|
| 181 |
+
self.data_sources.keys(),
|
| 182 |
+
executor.map(_glob_source, self.data_sources.keys()),
|
| 183 |
+
strict=True,
|
| 184 |
+
)
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
# Get primary source files (cached from glob, no second scan)
|
| 188 |
+
data_files, _ = glob_results[data_key]
|
| 189 |
+
if not data_files:
|
| 190 |
+
raise ValueError(f"No data files found in {data_path}")
|
| 191 |
+
data_files.sort()
|
| 192 |
+
|
| 193 |
+
# Log source sizes
|
| 194 |
+
for dir_name, (paths, _) in glob_results.items():
|
| 195 |
+
logger.debug(f"Source {dir_name}: {len(paths)} files")
|
| 196 |
+
|
| 197 |
+
# Build path sets for non-primary sources
|
| 198 |
+
other_path_sets = {
|
| 199 |
+
dir_name: path_set for dir_name, (_, path_set) in glob_results.items() if dir_name != data_key
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
# Pass 2: For each primary file, check if expected paths exist in other sources' sets
|
| 203 |
+
sample_files: dict[str, list[Path]] = {output_key: [] for output_key in self.data_sources.values()}
|
| 204 |
+
valid_count = 0
|
| 205 |
+
|
| 206 |
+
for data_file in data_files:
|
| 207 |
+
rel_path = data_file.relative_to(data_path)
|
| 208 |
+
|
| 209 |
+
# Check all other sources via set lookup (O(1) per source, no stat calls)
|
| 210 |
+
all_exist = True
|
| 211 |
+
for dir_name, path_set in other_path_sets.items():
|
| 212 |
+
expected = self._get_expected_file_path(dir_name, data_file, rel_path)
|
| 213 |
+
if str(expected) not in path_set:
|
| 214 |
+
logger.debug(f"Skipping {data_file.name}: no matching {dir_name} file at {expected}")
|
| 215 |
+
all_exist = False
|
| 216 |
+
break
|
| 217 |
+
|
| 218 |
+
if all_exist:
|
| 219 |
+
self._fill_sample_data_files(data_file, rel_path, sample_files)
|
| 220 |
+
valid_count += 1
|
| 221 |
+
|
| 222 |
+
skipped = len(data_files) - valid_count
|
| 223 |
+
if skipped > 0:
|
| 224 |
+
logger.info(f"Fast index: {valid_count} valid samples from {len(data_files)} total ({skipped} skipped)")
|
| 225 |
+
else:
|
| 226 |
+
logger.debug(f"Fast index: {valid_count} valid samples from {len(data_files)} total")
|
| 227 |
+
|
| 228 |
+
return sample_files
|
| 229 |
+
|
| 230 |
+
def _get_expected_file_path(self, dir_name: str, data_file: Path, rel_path: Path) -> Path:
|
| 231 |
+
"""Get the expected file path for a given data source."""
|
| 232 |
+
source_path = self.source_paths[dir_name]
|
| 233 |
+
|
| 234 |
+
# For conditions, handle legacy naming where latent_X.pt maps to condition_X.pt
|
| 235 |
+
if dir_name == "conditions" and data_file.name.startswith("latent_"):
|
| 236 |
+
return source_path / f"condition_{data_file.stem[7:]}.pt"
|
| 237 |
+
|
| 238 |
+
return source_path / rel_path
|
| 239 |
+
|
| 240 |
+
def _fill_sample_data_files(self, data_file: Path, rel_path: Path, sample_files: dict[str, list[Path]]) -> None:
|
| 241 |
+
"""Add a valid sample to the sample_files tracking."""
|
| 242 |
+
for dir_name, output_key in self.data_sources.items():
|
| 243 |
+
expected_path = self._get_expected_file_path(dir_name, data_file, rel_path)
|
| 244 |
+
sample_files[output_key].append(expected_path.relative_to(self.source_paths[dir_name]))
|
| 245 |
+
|
| 246 |
+
def _validate_setup(self) -> None:
|
| 247 |
+
"""Validate that the dataset setup is correct."""
|
| 248 |
+
sample_counts = {key: len(files) for key, files in self.sample_files.items()}
|
| 249 |
+
if not sample_counts or all(count == 0 for count in sample_counts.values()):
|
| 250 |
+
raise ValueError(
|
| 251 |
+
f"No valid samples found in {self.data_root} - all configured data sources "
|
| 252 |
+
f"({list(self.data_sources)}) must have matching files (per-source counts: {sample_counts})"
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
# Verify all output keys have the same number of samples
|
| 256 |
+
if len(set(sample_counts.values())) > 1:
|
| 257 |
+
raise ValueError(f"Mismatched sample counts across sources: {sample_counts}")
|
| 258 |
+
|
| 259 |
+
def __len__(self) -> int:
|
| 260 |
+
# Use the first output key as reference count
|
| 261 |
+
first_key = next(iter(self.sample_files.keys()))
|
| 262 |
+
return len(self.sample_files[first_key])
|
| 263 |
+
|
| 264 |
+
def __getitem__(self, index: int) -> dict[str, torch.Tensor]:
|
| 265 |
+
result = {}
|
| 266 |
+
|
| 267 |
+
for dir_name, output_key in self.data_sources.items():
|
| 268 |
+
source_path = self.source_paths[dir_name]
|
| 269 |
+
file_rel_path = self.sample_files[output_key][index]
|
| 270 |
+
file_path = source_path / file_rel_path
|
| 271 |
+
|
| 272 |
+
try:
|
| 273 |
+
data = torch.load(file_path, map_location="cpu", weights_only=True)
|
| 274 |
+
|
| 275 |
+
# Normalize video latent format if this is a latent source
|
| 276 |
+
if "latent" in dir_name.lower():
|
| 277 |
+
data = self._normalize_video_latents(data)
|
| 278 |
+
|
| 279 |
+
result[output_key] = data
|
| 280 |
+
except Exception as e:
|
| 281 |
+
raise RuntimeError(f"Failed to load {output_key} from {file_path}: {e}") from e
|
| 282 |
+
|
| 283 |
+
# Add index for debugging
|
| 284 |
+
result["idx"] = index
|
| 285 |
+
return result
|
| 286 |
+
|
| 287 |
+
@staticmethod
|
| 288 |
+
def _normalize_video_latents(data: dict) -> dict:
|
| 289 |
+
"""
|
| 290 |
+
Normalize video latents to non-patchified format [C, F, H, W].
|
| 291 |
+
Used for keeping backward compatibility with legacy datasets.
|
| 292 |
+
"""
|
| 293 |
+
latents = data["latents"]
|
| 294 |
+
|
| 295 |
+
# Check if latents are in legacy patchified format [seq_len, C]
|
| 296 |
+
if latents.dim() == 2:
|
| 297 |
+
# Legacy format: [seq_len, C] where seq_len = F * H * W
|
| 298 |
+
num_frames = data["num_frames"]
|
| 299 |
+
height = data["height"]
|
| 300 |
+
width = data["width"]
|
| 301 |
+
|
| 302 |
+
# Unpatchify: [seq_len, C] -> [C, F, H, W]
|
| 303 |
+
latents = rearrange(
|
| 304 |
+
latents,
|
| 305 |
+
"(f h w) c -> c f h w",
|
| 306 |
+
f=num_frames,
|
| 307 |
+
h=height,
|
| 308 |
+
w=width,
|
| 309 |
+
)
|
| 310 |
+
|
| 311 |
+
# Update the data dict with unpatchified latents
|
| 312 |
+
data = data.copy()
|
| 313 |
+
data["latents"] = latents
|
| 314 |
+
|
| 315 |
+
return data
|
packages/ltx-trainer/src/ltx_trainer/gemma_8bit.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ruff: noqa: PLC0415
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
8-bit Gemma text encoder loading utilities.
|
| 5 |
+
This module provides functionality for loading the Gemma text encoder in 8-bit precision
|
| 6 |
+
using bitsandbytes, which significantly reduces GPU memory usage.
|
| 7 |
+
Example usage:
|
| 8 |
+
from ltx_trainer.gemma_8bit import load_8bit_gemma
|
| 9 |
+
text_encoder = load_8bit_gemma(gemma_model_path="/path/to/gemma")
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import logging
|
| 15 |
+
import os
|
| 16 |
+
from collections.abc import Generator
|
| 17 |
+
from contextlib import contextmanager
|
| 18 |
+
from pathlib import Path
|
| 19 |
+
|
| 20 |
+
import torch
|
| 21 |
+
|
| 22 |
+
from ltx_core.text_encoders.gemma.encoders.base_encoder import GemmaTextEncoder
|
| 23 |
+
from ltx_core.text_encoders.gemma.tokenizer import LTXVGemmaTokenizer
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def load_8bit_gemma(
|
| 27 |
+
gemma_model_path: str | Path,
|
| 28 |
+
dtype: torch.dtype = torch.bfloat16,
|
| 29 |
+
device: torch.device | str | int | None = None,
|
| 30 |
+
) -> GemmaTextEncoder:
|
| 31 |
+
"""Load the Gemma text encoder in 8-bit precision using bitsandbytes.
|
| 32 |
+
Only the Gemma LLM backbone is loaded here. The embeddings processor
|
| 33 |
+
(feature extractor + connectors) should be loaded separately via
|
| 34 |
+
:func:`ltx_trainer.model_loader.load_embeddings_processor`.
|
| 35 |
+
Args:
|
| 36 |
+
gemma_model_path: Path to Gemma model directory
|
| 37 |
+
dtype: Data type for non-quantized model weights
|
| 38 |
+
device: Device to place the quantized model on. When ``None`` (default),
|
| 39 |
+
the device is inferred from ``LOCAL_RANK`` if CUDA is available, so
|
| 40 |
+
multi-process launches put each rank's encoder on its own GPU
|
| 41 |
+
instead of all colliding on ``cuda:0``.
|
| 42 |
+
Returns:
|
| 43 |
+
GemmaTextEncoder with 8-bit quantized Gemma backbone
|
| 44 |
+
Raises:
|
| 45 |
+
ImportError: If bitsandbytes is not installed
|
| 46 |
+
FileNotFoundError: If required model files are not found
|
| 47 |
+
"""
|
| 48 |
+
try:
|
| 49 |
+
from transformers import BitsAndBytesConfig, Gemma3ForConditionalGeneration
|
| 50 |
+
except ImportError as e:
|
| 51 |
+
raise ImportError(
|
| 52 |
+
"8-bit text encoder loading requires bitsandbytes. Install it with: uv pip install bitsandbytes"
|
| 53 |
+
) from e
|
| 54 |
+
|
| 55 |
+
gemma_path = _find_gemma_subpath(gemma_model_path, "model*.safetensors")
|
| 56 |
+
tokenizer_path = _find_gemma_subpath(gemma_model_path, "tokenizer.model")
|
| 57 |
+
|
| 58 |
+
# Pin the entire model to a single device. `device_map="auto"` collides on cuda:0
|
| 59 |
+
# in multi-process launches because every rank picks the same default device.
|
| 60 |
+
device_map: str | dict[str, int | str | torch.device]
|
| 61 |
+
if device is not None:
|
| 62 |
+
device_map = {"": device}
|
| 63 |
+
elif torch.cuda.is_available():
|
| 64 |
+
device_map = {"": int(os.environ.get("LOCAL_RANK", "0"))}
|
| 65 |
+
else:
|
| 66 |
+
device_map = "auto"
|
| 67 |
+
|
| 68 |
+
quantization_config = BitsAndBytesConfig(load_in_8bit=True)
|
| 69 |
+
with _suppress_accelerate_memory_warnings():
|
| 70 |
+
gemma_model = Gemma3ForConditionalGeneration.from_pretrained(
|
| 71 |
+
gemma_path,
|
| 72 |
+
quantization_config=quantization_config,
|
| 73 |
+
torch_dtype=torch.bfloat16,
|
| 74 |
+
device_map=device_map,
|
| 75 |
+
local_files_only=True,
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
tokenizer = LTXVGemmaTokenizer(tokenizer_path, 1024)
|
| 79 |
+
|
| 80 |
+
return GemmaTextEncoder(
|
| 81 |
+
tokenizer=tokenizer,
|
| 82 |
+
model=gemma_model,
|
| 83 |
+
dtype=dtype,
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def _find_gemma_subpath(root_path: str | Path, pattern: str) -> str:
|
| 88 |
+
"""Find a file matching a glob pattern and return its parent directory."""
|
| 89 |
+
matches = list(Path(root_path).rglob(pattern))
|
| 90 |
+
if not matches:
|
| 91 |
+
raise FileNotFoundError(f"No files matching pattern '{pattern}' found under {root_path}")
|
| 92 |
+
return str(matches[0].parent)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
@contextmanager
|
| 96 |
+
def _suppress_accelerate_memory_warnings() -> Generator[None, None, None]:
|
| 97 |
+
"""Temporarily suppress INFO warnings from accelerate about memory allocation."""
|
| 98 |
+
accelerate_logger = logging.getLogger("accelerate.utils.modeling")
|
| 99 |
+
old_level = accelerate_logger.level
|
| 100 |
+
accelerate_logger.setLevel(logging.WARNING)
|
| 101 |
+
try:
|
| 102 |
+
yield
|
| 103 |
+
finally:
|
| 104 |
+
accelerate_logger.setLevel(old_level)
|
packages/ltx-trainer/src/ltx_trainer/gpu_utils.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""GPU memory management utilities for training and inference."""
|
| 2 |
+
|
| 3 |
+
import functools
|
| 4 |
+
import gc
|
| 5 |
+
import subprocess
|
| 6 |
+
from typing import Callable, TypeVar
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
from ltx_trainer import logger
|
| 11 |
+
|
| 12 |
+
F = TypeVar("F", bound=Callable)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def free_gpu_memory(log: bool = False) -> None:
|
| 16 |
+
"""Free GPU memory by running garbage collection and emptying CUDA cache.
|
| 17 |
+
Args:
|
| 18 |
+
log: If True, log memory stats after clearing
|
| 19 |
+
"""
|
| 20 |
+
gc.collect()
|
| 21 |
+
if torch.cuda.is_available():
|
| 22 |
+
torch.cuda.empty_cache()
|
| 23 |
+
if log:
|
| 24 |
+
allocated = torch.cuda.memory_allocated() / 1024**3
|
| 25 |
+
reserved = torch.cuda.memory_reserved() / 1024**3
|
| 26 |
+
logger.debug(f"GPU memory freed. Allocated: {allocated:.2f}GB, Reserved: {reserved:.2f}GB")
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class free_gpu_memory_context: # noqa: N801
|
| 30 |
+
"""Context manager and decorator to free GPU memory before and/or after execution.
|
| 31 |
+
Can be used as a decorator:
|
| 32 |
+
@free_gpu_memory_context(after=True)
|
| 33 |
+
def my_function():
|
| 34 |
+
...
|
| 35 |
+
Or as a context manager:
|
| 36 |
+
with free_gpu_memory_context():
|
| 37 |
+
heavy_operation()
|
| 38 |
+
Args:
|
| 39 |
+
before: Free memory before execution (default: False)
|
| 40 |
+
after: Free memory after execution (default: True)
|
| 41 |
+
log: Log memory stats when freeing (default: False)
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
def __init__(self, *, before: bool = False, after: bool = True, log: bool = False) -> None:
|
| 45 |
+
self.before = before
|
| 46 |
+
self.after = after
|
| 47 |
+
self.log = log
|
| 48 |
+
|
| 49 |
+
def __enter__(self) -> "free_gpu_memory_context":
|
| 50 |
+
if self.before:
|
| 51 |
+
free_gpu_memory(log=self.log)
|
| 52 |
+
return self
|
| 53 |
+
|
| 54 |
+
def __exit__(self, exc_type: type | None, exc_val: Exception | None, exc_tb: object) -> None:
|
| 55 |
+
if self.after:
|
| 56 |
+
free_gpu_memory(log=self.log)
|
| 57 |
+
|
| 58 |
+
def __call__(self, func: F) -> F:
|
| 59 |
+
@functools.wraps(func)
|
| 60 |
+
def wrapper(*args, **kwargs) -> object:
|
| 61 |
+
with self:
|
| 62 |
+
return func(*args, **kwargs)
|
| 63 |
+
|
| 64 |
+
return wrapper # type: ignore
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def get_gpu_memory_gb(device: torch.device) -> float:
|
| 68 |
+
"""Get current GPU memory usage in GB using nvidia-smi.
|
| 69 |
+
Args:
|
| 70 |
+
device: torch.device to get memory usage for
|
| 71 |
+
Returns:
|
| 72 |
+
Current GPU memory usage in GB
|
| 73 |
+
"""
|
| 74 |
+
try:
|
| 75 |
+
device_id = device.index if device.index is not None else 0
|
| 76 |
+
result = subprocess.check_output(
|
| 77 |
+
[
|
| 78 |
+
"nvidia-smi",
|
| 79 |
+
"--query-gpu=memory.used",
|
| 80 |
+
"--format=csv,nounits,noheader",
|
| 81 |
+
"-i",
|
| 82 |
+
str(device_id),
|
| 83 |
+
],
|
| 84 |
+
encoding="utf-8",
|
| 85 |
+
)
|
| 86 |
+
return float(result.strip()) / 1024 # Convert MB to GB
|
| 87 |
+
except (subprocess.CalledProcessError, FileNotFoundError, ValueError) as e:
|
| 88 |
+
logger.error(f"Failed to get GPU memory from nvidia-smi: {e}")
|
| 89 |
+
# Fallback to torch
|
| 90 |
+
return torch.cuda.memory_allocated(device) / 1024**3
|
packages/ltx-trainer/src/ltx_trainer/hf_hub_utils.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import shutil
|
| 2 |
+
import tempfile
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import List, Optional, Union
|
| 5 |
+
|
| 6 |
+
import imageio
|
| 7 |
+
from huggingface_hub import HfApi, create_repo
|
| 8 |
+
from huggingface_hub.utils import are_progress_bars_disabled, disable_progress_bars, enable_progress_bars
|
| 9 |
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
| 10 |
+
|
| 11 |
+
from ltx_trainer import logger
|
| 12 |
+
from ltx_trainer.config import LtxTrainerConfig
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def push_to_hub(
|
| 16 |
+
weights_path: Path,
|
| 17 |
+
sampled_videos_paths: Optional[List[Path]],
|
| 18 |
+
config: LtxTrainerConfig,
|
| 19 |
+
) -> None:
|
| 20 |
+
"""Push the trained LoRA weights to HuggingFace Hub."""
|
| 21 |
+
if not config.hub.hub_model_id:
|
| 22 |
+
logger.warning("⚠️ HuggingFace hub_model_id not specified, skipping push to hub")
|
| 23 |
+
return
|
| 24 |
+
|
| 25 |
+
api = HfApi()
|
| 26 |
+
|
| 27 |
+
# Save original progress bar state
|
| 28 |
+
original_progress_state = are_progress_bars_disabled()
|
| 29 |
+
disable_progress_bars() # Disable during our custom progress tracking
|
| 30 |
+
|
| 31 |
+
try:
|
| 32 |
+
# Try to create repo if it doesn't exist
|
| 33 |
+
try:
|
| 34 |
+
repo = create_repo(
|
| 35 |
+
repo_id=config.hub.hub_model_id,
|
| 36 |
+
repo_type="model",
|
| 37 |
+
exist_ok=True, # Don't raise error if repo exists
|
| 38 |
+
)
|
| 39 |
+
repo_id = repo.repo_id
|
| 40 |
+
logger.info(f"🤗 Successfully created HuggingFace model repository at: {repo.url}")
|
| 41 |
+
except Exception as e:
|
| 42 |
+
logger.error(f"❌ Failed to create HuggingFace model repository: {e}")
|
| 43 |
+
return
|
| 44 |
+
|
| 45 |
+
# Create a single temporary directory for all files
|
| 46 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
| 47 |
+
temp_path = Path(temp_dir)
|
| 48 |
+
|
| 49 |
+
with Progress(
|
| 50 |
+
SpinnerColumn(),
|
| 51 |
+
TextColumn("[progress.description]{task.description}"),
|
| 52 |
+
transient=True,
|
| 53 |
+
) as progress:
|
| 54 |
+
try:
|
| 55 |
+
# Copy weights
|
| 56 |
+
task_copy = progress.add_task("Copying weights...", total=None)
|
| 57 |
+
weights_dest = temp_path / weights_path.name
|
| 58 |
+
shutil.copy2(weights_path, weights_dest)
|
| 59 |
+
progress.update(task_copy, description="✓ Weights copied")
|
| 60 |
+
|
| 61 |
+
# Create model card and save samples
|
| 62 |
+
task_card = progress.add_task("Creating model card and samples...", total=None)
|
| 63 |
+
_create_model_card(
|
| 64 |
+
output_dir=temp_path,
|
| 65 |
+
videos=sampled_videos_paths,
|
| 66 |
+
config=config,
|
| 67 |
+
)
|
| 68 |
+
progress.update(task_card, description="✓ Model card and samples created")
|
| 69 |
+
|
| 70 |
+
# Upload everything at once
|
| 71 |
+
task_upload = progress.add_task("Pushing files to HuggingFace Hub...", total=None)
|
| 72 |
+
api.upload_folder(
|
| 73 |
+
folder_path=str(temp_path),
|
| 74 |
+
repo_id=repo_id,
|
| 75 |
+
repo_type="model",
|
| 76 |
+
)
|
| 77 |
+
progress.update(task_upload, description="✓ Files pushed to HuggingFace Hub")
|
| 78 |
+
logger.info("✅ Successfully pushed files to HuggingFace Hub")
|
| 79 |
+
|
| 80 |
+
except Exception as e:
|
| 81 |
+
logger.error(f"❌ Failed to process and push files to HuggingFace Hub: {e}")
|
| 82 |
+
raise # Re-raise to handle in outer try block
|
| 83 |
+
|
| 84 |
+
finally:
|
| 85 |
+
# Restore original progress bar state
|
| 86 |
+
if not original_progress_state:
|
| 87 |
+
enable_progress_bars()
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def convert_video_to_gif(video_path: Path, output_path: Path) -> None:
|
| 91 |
+
"""Convert a video file to GIF format."""
|
| 92 |
+
try:
|
| 93 |
+
# Read the video file
|
| 94 |
+
reader = imageio.get_reader(str(video_path))
|
| 95 |
+
fps = reader.get_meta_data()["fps"]
|
| 96 |
+
|
| 97 |
+
# Write GIF file with infinite loop
|
| 98 |
+
writer = imageio.get_writer(
|
| 99 |
+
str(output_path),
|
| 100 |
+
fps=min(fps, 15), # Cap FPS at 15 for reasonable file size
|
| 101 |
+
loop=0, # 0 means infinite loop
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
for frame in reader:
|
| 105 |
+
writer.append_data(frame)
|
| 106 |
+
|
| 107 |
+
writer.close()
|
| 108 |
+
reader.close()
|
| 109 |
+
except Exception as e:
|
| 110 |
+
logger.error(f"Failed to convert video to GIF: {e}")
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def _create_model_card(
|
| 114 |
+
output_dir: Union[str, Path],
|
| 115 |
+
videos: Optional[List[Path]],
|
| 116 |
+
config: LtxTrainerConfig,
|
| 117 |
+
) -> Path:
|
| 118 |
+
"""Generate and save a model card for the trained model."""
|
| 119 |
+
|
| 120 |
+
repo_id = config.hub.hub_model_id
|
| 121 |
+
pretrained_model_name_or_path = config.model.model_path
|
| 122 |
+
validation_prompts = config.validation.prompts
|
| 123 |
+
output_dir = Path(output_dir)
|
| 124 |
+
template_path = Path(__file__).parent.parent.parent / "templates" / "model_card.md"
|
| 125 |
+
|
| 126 |
+
# Read the template
|
| 127 |
+
template = template_path.read_text()
|
| 128 |
+
|
| 129 |
+
# Get model name from repo_id
|
| 130 |
+
model_name = repo_id.split("/")[-1]
|
| 131 |
+
|
| 132 |
+
# Get base model information
|
| 133 |
+
base_model_link = str(pretrained_model_name_or_path)
|
| 134 |
+
model_path_str = str(pretrained_model_name_or_path)
|
| 135 |
+
is_url = model_path_str.startswith(("http://", "https://"))
|
| 136 |
+
|
| 137 |
+
# For URLs, extract the filename from the URL. For local paths, use the filename stem
|
| 138 |
+
base_model_name = model_path_str.split("/")[-1] if is_url else Path(pretrained_model_name_or_path).name
|
| 139 |
+
|
| 140 |
+
# Format validation prompts and create grid layout
|
| 141 |
+
prompts_text = ""
|
| 142 |
+
sample_grid = []
|
| 143 |
+
|
| 144 |
+
if validation_prompts and videos:
|
| 145 |
+
prompts_text = "Example prompts used during validation:\n\n"
|
| 146 |
+
|
| 147 |
+
# Create samples directory
|
| 148 |
+
samples_dir = output_dir / "samples"
|
| 149 |
+
samples_dir.mkdir(exist_ok=True, parents=True)
|
| 150 |
+
|
| 151 |
+
# Process videos and create cells
|
| 152 |
+
cells = []
|
| 153 |
+
for i, (prompt, video) in enumerate(zip(validation_prompts, videos, strict=False)):
|
| 154 |
+
if video.exists():
|
| 155 |
+
# Add prompt to text section
|
| 156 |
+
prompts_text += f"- `{prompt}`\n"
|
| 157 |
+
|
| 158 |
+
# Convert video to GIF
|
| 159 |
+
gif_path = samples_dir / f"sample_{i}.gif"
|
| 160 |
+
try:
|
| 161 |
+
convert_video_to_gif(video, gif_path)
|
| 162 |
+
|
| 163 |
+
# Create grid cell with collapsible description
|
| 164 |
+
cell = (
|
| 165 |
+
f""
|
| 166 |
+
"<br>"
|
| 167 |
+
'<details style="max-width: 300px; margin: auto;">'
|
| 168 |
+
f"<summary>Prompt</summary>"
|
| 169 |
+
f"{prompt}"
|
| 170 |
+
"</details>"
|
| 171 |
+
)
|
| 172 |
+
cells.append(cell)
|
| 173 |
+
except Exception as e:
|
| 174 |
+
logger.error(f"Failed to process video {video}: {e}")
|
| 175 |
+
|
| 176 |
+
# Calculate optimal grid dimensions
|
| 177 |
+
num_cells = len(cells)
|
| 178 |
+
if num_cells > 0:
|
| 179 |
+
# Aim for a roughly square grid, with max 4 columns
|
| 180 |
+
num_cols = min(4, num_cells)
|
| 181 |
+
num_rows = (num_cells + num_cols - 1) // num_cols # Ceiling division
|
| 182 |
+
|
| 183 |
+
# Create grid rows
|
| 184 |
+
for row in range(num_rows):
|
| 185 |
+
start_idx = row * num_cols
|
| 186 |
+
end_idx = min(start_idx + num_cols, num_cells)
|
| 187 |
+
row_cells = cells[start_idx:end_idx]
|
| 188 |
+
# Properly format the row with table markers and exact number of cells
|
| 189 |
+
formatted_row = "| " + " | ".join(row_cells) + " |"
|
| 190 |
+
sample_grid.append(formatted_row)
|
| 191 |
+
|
| 192 |
+
# Join grid rows with just the content, no headers needed
|
| 193 |
+
grid_text = "\n".join(sample_grid) if sample_grid else ""
|
| 194 |
+
|
| 195 |
+
# Fill in the template
|
| 196 |
+
model_card_content = template.format(
|
| 197 |
+
base_model=base_model_name,
|
| 198 |
+
base_model_link=base_model_link,
|
| 199 |
+
model_name=model_name,
|
| 200 |
+
training_type="LoRA fine-tuning" if config.model.training_mode == "lora" else "Full model fine-tuning",
|
| 201 |
+
training_steps=config.optimization.steps,
|
| 202 |
+
learning_rate=config.optimization.learning_rate,
|
| 203 |
+
batch_size=config.optimization.batch_size,
|
| 204 |
+
validation_prompts=prompts_text,
|
| 205 |
+
sample_grid=grid_text,
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
# Save the model card directly
|
| 209 |
+
model_card_path = output_dir / "README.md"
|
| 210 |
+
model_card_path.write_text(model_card_content)
|
| 211 |
+
|
| 212 |
+
return model_card_path
|
packages/ltx-trainer/src/ltx_trainer/model_loader.py
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ruff: noqa: PLC0415
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
Model loader for LTX-2 trainer using the new ltx-core package.
|
| 5 |
+
This module provides a unified interface for loading LTX-2 model components
|
| 6 |
+
for training, using SingleGPUModelBuilder from ltx-core.
|
| 7 |
+
Example usage:
|
| 8 |
+
# Load individual components
|
| 9 |
+
vae_encoder = load_video_vae_encoder("/path/to/checkpoint.safetensors", device="cuda")
|
| 10 |
+
vae_decoder = load_video_vae_decoder("/path/to/checkpoint.safetensors", device="cuda")
|
| 11 |
+
text_encoder = load_text_encoder("/path/to/gemma", device="cuda")
|
| 12 |
+
# Load all components at once
|
| 13 |
+
components = load_model("/path/to/checkpoint.safetensors", text_encoder_path="/path/to/gemma")
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
from dataclasses import dataclass
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
from typing import TYPE_CHECKING
|
| 21 |
+
|
| 22 |
+
import torch
|
| 23 |
+
|
| 24 |
+
from ltx_trainer import logger
|
| 25 |
+
|
| 26 |
+
# Type alias for device specification
|
| 27 |
+
Device = str | torch.device
|
| 28 |
+
|
| 29 |
+
# Type checking imports (not loaded at runtime)
|
| 30 |
+
if TYPE_CHECKING:
|
| 31 |
+
from ltx_core.components.schedulers import LTX2Scheduler
|
| 32 |
+
from ltx_core.model.audio_vae import AudioDecoder, AudioEncoder, Vocoder
|
| 33 |
+
from ltx_core.model.transformer import LTXModel
|
| 34 |
+
from ltx_core.model.video_vae import VideoDecoder, VideoEncoder
|
| 35 |
+
from ltx_core.text_encoders.gemma import GemmaTextEncoder
|
| 36 |
+
from ltx_core.text_encoders.gemma.embeddings_processor import EmbeddingsProcessor
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _to_torch_device(device: Device) -> torch.device:
|
| 40 |
+
"""Convert device specification to torch.device."""
|
| 41 |
+
return torch.device(device) if isinstance(device, str) else device
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
# =============================================================================
|
| 45 |
+
# Individual Component Loaders
|
| 46 |
+
# =============================================================================
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def load_transformer(
|
| 50 |
+
checkpoint_path: str | Path,
|
| 51 |
+
device: Device = "cpu",
|
| 52 |
+
dtype: torch.dtype = torch.bfloat16,
|
| 53 |
+
) -> "LTXModel":
|
| 54 |
+
"""Load the LTX transformer model.
|
| 55 |
+
Args:
|
| 56 |
+
checkpoint_path: Path to the safetensors checkpoint file
|
| 57 |
+
device: Device to load model on
|
| 58 |
+
dtype: Data type for model weights
|
| 59 |
+
Returns:
|
| 60 |
+
Loaded LTXModel transformer
|
| 61 |
+
"""
|
| 62 |
+
from ltx_core.loader.single_gpu_model_builder import SingleGPUModelBuilder
|
| 63 |
+
from ltx_core.model.transformer.model_configurator import (
|
| 64 |
+
LTXV_MODEL_COMFY_RENAMING_MAP,
|
| 65 |
+
LTXModelConfigurator,
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
return SingleGPUModelBuilder(
|
| 69 |
+
model_path=str(checkpoint_path),
|
| 70 |
+
model_class_configurator=LTXModelConfigurator,
|
| 71 |
+
model_sd_ops=LTXV_MODEL_COMFY_RENAMING_MAP,
|
| 72 |
+
).build(device=_to_torch_device(device), dtype=dtype)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def load_video_vae_encoder(
|
| 76 |
+
checkpoint_path: str | Path,
|
| 77 |
+
device: Device = "cpu",
|
| 78 |
+
dtype: torch.dtype = torch.bfloat16,
|
| 79 |
+
) -> "VideoEncoder":
|
| 80 |
+
"""Load the video VAE encoder (for preprocessing).
|
| 81 |
+
Args:
|
| 82 |
+
checkpoint_path: Path to the safetensors checkpoint file
|
| 83 |
+
device: Device to load model on
|
| 84 |
+
dtype: Data type for model weights
|
| 85 |
+
Returns:
|
| 86 |
+
Loaded VideoEncoder
|
| 87 |
+
"""
|
| 88 |
+
from ltx_core.loader.single_gpu_model_builder import SingleGPUModelBuilder
|
| 89 |
+
from ltx_core.model.video_vae import VAE_ENCODER_COMFY_KEYS_FILTER, VideoEncoderConfigurator
|
| 90 |
+
|
| 91 |
+
return SingleGPUModelBuilder(
|
| 92 |
+
model_path=str(checkpoint_path),
|
| 93 |
+
model_class_configurator=VideoEncoderConfigurator,
|
| 94 |
+
model_sd_ops=VAE_ENCODER_COMFY_KEYS_FILTER,
|
| 95 |
+
).build(device=_to_torch_device(device), dtype=dtype)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def load_video_vae_decoder(
|
| 99 |
+
checkpoint_path: str | Path,
|
| 100 |
+
device: Device = "cpu",
|
| 101 |
+
dtype: torch.dtype = torch.bfloat16,
|
| 102 |
+
) -> "VideoDecoder":
|
| 103 |
+
"""Load the video VAE decoder (for inference/validation).
|
| 104 |
+
Args:
|
| 105 |
+
checkpoint_path: Path to the safetensors checkpoint file
|
| 106 |
+
device: Device to load model on
|
| 107 |
+
dtype: Data type for model weights
|
| 108 |
+
Returns:
|
| 109 |
+
Loaded VideoDecoder
|
| 110 |
+
"""
|
| 111 |
+
from ltx_core.loader.single_gpu_model_builder import SingleGPUModelBuilder
|
| 112 |
+
from ltx_core.model.video_vae import VAE_DECODER_COMFY_KEYS_FILTER, VideoDecoderConfigurator
|
| 113 |
+
|
| 114 |
+
return SingleGPUModelBuilder(
|
| 115 |
+
model_path=str(checkpoint_path),
|
| 116 |
+
model_class_configurator=VideoDecoderConfigurator,
|
| 117 |
+
model_sd_ops=VAE_DECODER_COMFY_KEYS_FILTER,
|
| 118 |
+
).build(device=_to_torch_device(device), dtype=dtype)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def load_audio_vae_encoder(
|
| 122 |
+
checkpoint_path: str | Path,
|
| 123 |
+
device: Device = "cpu",
|
| 124 |
+
dtype: torch.dtype = torch.bfloat16,
|
| 125 |
+
) -> "AudioEncoder":
|
| 126 |
+
"""Load the audio VAE encoder (for preprocessing).
|
| 127 |
+
Args:
|
| 128 |
+
checkpoint_path: Path to the safetensors checkpoint file
|
| 129 |
+
device: Device to load model on
|
| 130 |
+
dtype: Data type for model weights (default bfloat16, but float32 recommended for quality)
|
| 131 |
+
Returns:
|
| 132 |
+
Loaded AudioEncoder
|
| 133 |
+
"""
|
| 134 |
+
from ltx_core.loader import SingleGPUModelBuilder
|
| 135 |
+
from ltx_core.model.audio_vae import AUDIO_VAE_ENCODER_COMFY_KEYS_FILTER, AudioEncoderConfigurator
|
| 136 |
+
|
| 137 |
+
return SingleGPUModelBuilder(
|
| 138 |
+
model_path=str(checkpoint_path),
|
| 139 |
+
model_class_configurator=AudioEncoderConfigurator,
|
| 140 |
+
model_sd_ops=AUDIO_VAE_ENCODER_COMFY_KEYS_FILTER,
|
| 141 |
+
).build(device=_to_torch_device(device), dtype=dtype)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def load_audio_vae_decoder(
|
| 145 |
+
checkpoint_path: str | Path,
|
| 146 |
+
device: Device = "cpu",
|
| 147 |
+
dtype: torch.dtype = torch.bfloat16,
|
| 148 |
+
) -> "AudioDecoder":
|
| 149 |
+
"""Load the audio VAE decoder.
|
| 150 |
+
Args:
|
| 151 |
+
checkpoint_path: Path to the safetensors checkpoint file
|
| 152 |
+
device: Device to load model on
|
| 153 |
+
dtype: Data type for model weights
|
| 154 |
+
Returns:
|
| 155 |
+
Loaded AudioDecoder
|
| 156 |
+
"""
|
| 157 |
+
from ltx_core.loader import SingleGPUModelBuilder
|
| 158 |
+
from ltx_core.model.audio_vae import AUDIO_VAE_DECODER_COMFY_KEYS_FILTER, AudioDecoderConfigurator
|
| 159 |
+
|
| 160 |
+
return SingleGPUModelBuilder(
|
| 161 |
+
model_path=str(checkpoint_path),
|
| 162 |
+
model_class_configurator=AudioDecoderConfigurator,
|
| 163 |
+
model_sd_ops=AUDIO_VAE_DECODER_COMFY_KEYS_FILTER,
|
| 164 |
+
).build(device=_to_torch_device(device), dtype=dtype)
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def load_vocoder(
|
| 168 |
+
checkpoint_path: str | Path,
|
| 169 |
+
device: Device = "cpu",
|
| 170 |
+
dtype: torch.dtype = torch.bfloat16,
|
| 171 |
+
) -> "Vocoder":
|
| 172 |
+
"""Load the vocoder (for audio waveform generation).
|
| 173 |
+
Args:
|
| 174 |
+
checkpoint_path: Path to the safetensors checkpoint file
|
| 175 |
+
device: Device to load model on
|
| 176 |
+
dtype: Data type for model weights
|
| 177 |
+
Returns:
|
| 178 |
+
Loaded Vocoder
|
| 179 |
+
"""
|
| 180 |
+
from ltx_core.loader import SingleGPUModelBuilder
|
| 181 |
+
from ltx_core.model.audio_vae import VOCODER_COMFY_KEYS_FILTER, VocoderConfigurator
|
| 182 |
+
|
| 183 |
+
return SingleGPUModelBuilder(
|
| 184 |
+
model_path=str(checkpoint_path),
|
| 185 |
+
model_class_configurator=VocoderConfigurator,
|
| 186 |
+
model_sd_ops=VOCODER_COMFY_KEYS_FILTER,
|
| 187 |
+
).build(device=_to_torch_device(device), dtype=dtype)
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def load_text_encoder(
|
| 191 |
+
gemma_model_path: str | Path,
|
| 192 |
+
device: Device = "cpu",
|
| 193 |
+
dtype: torch.dtype = torch.bfloat16,
|
| 194 |
+
load_in_8bit: bool = False,
|
| 195 |
+
) -> "GemmaTextEncoder":
|
| 196 |
+
"""Load the Gemma text encoder.
|
| 197 |
+
Args:
|
| 198 |
+
gemma_model_path: Path to Gemma model directory
|
| 199 |
+
device: Device to load model on
|
| 200 |
+
dtype: Data type for model weights
|
| 201 |
+
load_in_8bit: Whether to load the Gemma model in 8-bit precision using bitsandbytes.
|
| 202 |
+
Returns:
|
| 203 |
+
Loaded GemmaTextEncoder
|
| 204 |
+
"""
|
| 205 |
+
if not Path(gemma_model_path).is_dir():
|
| 206 |
+
raise ValueError(f"Gemma model path is not a directory: {gemma_model_path}")
|
| 207 |
+
|
| 208 |
+
# Use 8-bit loading path if requested
|
| 209 |
+
if load_in_8bit:
|
| 210 |
+
from ltx_trainer.gemma_8bit import load_8bit_gemma
|
| 211 |
+
|
| 212 |
+
return load_8bit_gemma(gemma_model_path, dtype, device=device)
|
| 213 |
+
|
| 214 |
+
# Standard loading path
|
| 215 |
+
from ltx_core.loader.single_gpu_model_builder import SingleGPUModelBuilder
|
| 216 |
+
from ltx_core.text_encoders.gemma import (
|
| 217 |
+
GEMMA_LLM_KEY_OPS,
|
| 218 |
+
GEMMA_MODEL_OPS,
|
| 219 |
+
GemmaTextEncoderConfigurator,
|
| 220 |
+
module_ops_from_gemma_root,
|
| 221 |
+
)
|
| 222 |
+
from ltx_core.utils import find_matching_file
|
| 223 |
+
|
| 224 |
+
torch_device = _to_torch_device(device)
|
| 225 |
+
|
| 226 |
+
gemma_model_folder = find_matching_file(str(gemma_model_path), "model*.safetensors").parent
|
| 227 |
+
gemma_weight_paths = [str(p) for p in gemma_model_folder.rglob("*.safetensors")]
|
| 228 |
+
|
| 229 |
+
text_encoder = SingleGPUModelBuilder(
|
| 230 |
+
model_path=tuple(gemma_weight_paths),
|
| 231 |
+
model_class_configurator=GemmaTextEncoderConfigurator,
|
| 232 |
+
model_sd_ops=GEMMA_LLM_KEY_OPS,
|
| 233 |
+
module_ops=(GEMMA_MODEL_OPS, *module_ops_from_gemma_root(str(gemma_model_path))),
|
| 234 |
+
).build(device=torch_device, dtype=dtype)
|
| 235 |
+
|
| 236 |
+
return text_encoder
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
def load_embeddings_processor(
|
| 240 |
+
checkpoint_path: str | Path,
|
| 241 |
+
device: Device = "cpu",
|
| 242 |
+
dtype: torch.dtype = torch.bfloat16,
|
| 243 |
+
) -> "EmbeddingsProcessor":
|
| 244 |
+
"""Load the embeddings processor (feature extractor + video/audio connectors).
|
| 245 |
+
Args:
|
| 246 |
+
checkpoint_path: Path to the LTX-2 safetensors checkpoint file
|
| 247 |
+
device: Device to load model on
|
| 248 |
+
dtype: Data type for model weights
|
| 249 |
+
Returns:
|
| 250 |
+
Loaded EmbeddingsProcessor with feature extractor and connectors
|
| 251 |
+
"""
|
| 252 |
+
from ltx_core.loader.single_gpu_model_builder import SingleGPUModelBuilder
|
| 253 |
+
from ltx_core.text_encoders.gemma import (
|
| 254 |
+
EMBEDDINGS_PROCESSOR_KEY_OPS,
|
| 255 |
+
EmbeddingsProcessorConfigurator,
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
torch_device = _to_torch_device(device)
|
| 259 |
+
|
| 260 |
+
return SingleGPUModelBuilder(
|
| 261 |
+
model_path=str(checkpoint_path),
|
| 262 |
+
model_class_configurator=EmbeddingsProcessorConfigurator,
|
| 263 |
+
model_sd_ops=EMBEDDINGS_PROCESSOR_KEY_OPS,
|
| 264 |
+
).build(device=torch_device, dtype=dtype)
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
# =============================================================================
|
| 268 |
+
# Combined Component Loader
|
| 269 |
+
# =============================================================================
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
@dataclass
|
| 273 |
+
class LtxModelComponents:
|
| 274 |
+
"""Container for all LTX-2 model components."""
|
| 275 |
+
|
| 276 |
+
transformer: "LTXModel"
|
| 277 |
+
video_vae_encoder: "VideoEncoder | None" = None
|
| 278 |
+
video_vae_decoder: "VideoDecoder | None" = None
|
| 279 |
+
audio_vae_decoder: "AudioDecoder | None" = None
|
| 280 |
+
vocoder: "Vocoder | None" = None
|
| 281 |
+
text_encoder: "GemmaTextEncoder | None" = None
|
| 282 |
+
scheduler: "LTX2Scheduler | None" = None
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def load_model(
|
| 286 |
+
checkpoint_path: str | Path,
|
| 287 |
+
text_encoder_path: str | Path | None = None,
|
| 288 |
+
device: Device = "cpu",
|
| 289 |
+
dtype: torch.dtype = torch.bfloat16,
|
| 290 |
+
with_video_vae_encoder: bool = False,
|
| 291 |
+
with_video_vae_decoder: bool = True,
|
| 292 |
+
with_audio_vae_decoder: bool = True,
|
| 293 |
+
with_vocoder: bool = True,
|
| 294 |
+
with_text_encoder: bool = True,
|
| 295 |
+
) -> LtxModelComponents:
|
| 296 |
+
"""
|
| 297 |
+
Load LTX-2 model components from a safetensors checkpoint.
|
| 298 |
+
This is a convenience function that loads multiple components at once.
|
| 299 |
+
For loading individual components, use the dedicated functions:
|
| 300 |
+
- load_transformer()
|
| 301 |
+
- load_video_vae_encoder()
|
| 302 |
+
- load_video_vae_decoder()
|
| 303 |
+
- load_audio_vae_decoder()
|
| 304 |
+
- load_vocoder()
|
| 305 |
+
- load_text_encoder()
|
| 306 |
+
Args:
|
| 307 |
+
checkpoint_path: Path to the safetensors checkpoint file
|
| 308 |
+
text_encoder_path: Path to Gemma model directory (required if with_text_encoder=True)
|
| 309 |
+
device: Device to load models on ("cuda", "cpu", etc.)
|
| 310 |
+
dtype: Data type for model weights
|
| 311 |
+
with_video_vae_encoder: Whether to load the video VAE encoder (for preprocessing)
|
| 312 |
+
with_video_vae_decoder: Whether to load the video VAE decoder (for inference/validation)
|
| 313 |
+
with_audio_vae_decoder: Whether to load the audio VAE decoder
|
| 314 |
+
with_vocoder: Whether to load the vocoder
|
| 315 |
+
with_text_encoder: Whether to load the text encoder
|
| 316 |
+
Returns:
|
| 317 |
+
LtxModelComponents containing all loaded model components
|
| 318 |
+
"""
|
| 319 |
+
from ltx_core.components.schedulers import LTX2Scheduler
|
| 320 |
+
|
| 321 |
+
checkpoint_path = Path(checkpoint_path)
|
| 322 |
+
|
| 323 |
+
# Validate checkpoint exists
|
| 324 |
+
if not checkpoint_path.exists():
|
| 325 |
+
raise FileNotFoundError(f"Checkpoint not found: {checkpoint_path}")
|
| 326 |
+
|
| 327 |
+
logger.info(f"Loading LTX-2 model from {checkpoint_path}")
|
| 328 |
+
|
| 329 |
+
torch_device = _to_torch_device(device)
|
| 330 |
+
|
| 331 |
+
# Load transformer
|
| 332 |
+
logger.debug("Loading transformer...")
|
| 333 |
+
transformer = load_transformer(checkpoint_path, torch_device, dtype)
|
| 334 |
+
|
| 335 |
+
# Load video VAE encoder
|
| 336 |
+
video_vae_encoder = None
|
| 337 |
+
if with_video_vae_encoder:
|
| 338 |
+
logger.debug("Loading video VAE encoder...")
|
| 339 |
+
video_vae_encoder = load_video_vae_encoder(checkpoint_path, torch_device, dtype)
|
| 340 |
+
|
| 341 |
+
# Load video VAE decoder
|
| 342 |
+
video_vae_decoder = None
|
| 343 |
+
if with_video_vae_decoder:
|
| 344 |
+
logger.debug("Loading video VAE decoder...")
|
| 345 |
+
video_vae_decoder = load_video_vae_decoder(checkpoint_path, torch_device, dtype)
|
| 346 |
+
|
| 347 |
+
# Load audio VAE decoder
|
| 348 |
+
audio_vae_decoder = None
|
| 349 |
+
if with_audio_vae_decoder:
|
| 350 |
+
logger.debug("Loading audio VAE decoder...")
|
| 351 |
+
audio_vae_decoder = load_audio_vae_decoder(checkpoint_path, torch_device, dtype)
|
| 352 |
+
|
| 353 |
+
# Load vocoder
|
| 354 |
+
vocoder = None
|
| 355 |
+
if with_vocoder:
|
| 356 |
+
logger.debug("Loading vocoder...")
|
| 357 |
+
vocoder = load_vocoder(checkpoint_path, torch_device, dtype)
|
| 358 |
+
|
| 359 |
+
# Load text encoder
|
| 360 |
+
text_encoder = None
|
| 361 |
+
if with_text_encoder:
|
| 362 |
+
if text_encoder_path is None:
|
| 363 |
+
raise ValueError("text_encoder_path must be provided when with_text_encoder=True")
|
| 364 |
+
logger.debug("Loading Gemma text encoder...")
|
| 365 |
+
text_encoder = load_text_encoder(text_encoder_path, torch_device, dtype)
|
| 366 |
+
|
| 367 |
+
# Create scheduler (stateless, no loading needed)
|
| 368 |
+
scheduler = LTX2Scheduler()
|
| 369 |
+
|
| 370 |
+
return LtxModelComponents(
|
| 371 |
+
transformer=transformer,
|
| 372 |
+
video_vae_encoder=video_vae_encoder,
|
| 373 |
+
video_vae_decoder=video_vae_decoder,
|
| 374 |
+
audio_vae_decoder=audio_vae_decoder,
|
| 375 |
+
vocoder=vocoder,
|
| 376 |
+
text_encoder=text_encoder,
|
| 377 |
+
scheduler=scheduler,
|
| 378 |
+
)
|
packages/ltx-trainer/src/ltx_trainer/progress.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Progress tracking for LTX training.
|
| 2 |
+
This module provides a unified progress display for training and validation sampling,
|
| 3 |
+
encapsulating all Rich progress bar logic in one place.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from rich.progress import (
|
| 7 |
+
BarColumn,
|
| 8 |
+
Progress,
|
| 9 |
+
TaskID,
|
| 10 |
+
TextColumn,
|
| 11 |
+
TimeElapsedColumn,
|
| 12 |
+
TimeRemainingColumn,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class SamplingContext:
|
| 17 |
+
"""Context for validation sampling progress tracking.
|
| 18 |
+
Provides a unified progress display showing current video and denoising step.
|
| 19 |
+
Display format: "Sampling X/Y [████████████] step Z/W"
|
| 20 |
+
The progress bar shows the denoising progress for the current video.
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
def __init__(self, progress: Progress | None, task: TaskID | None, num_prompts: int, num_steps: int):
|
| 24 |
+
self._progress = progress
|
| 25 |
+
self._task = task
|
| 26 |
+
self._num_prompts = num_prompts
|
| 27 |
+
self._num_steps = num_steps
|
| 28 |
+
|
| 29 |
+
def start_video(self, video_idx: int) -> None:
|
| 30 |
+
"""Start tracking a new video (resets step progress)."""
|
| 31 |
+
if self._progress is None or self._task is None:
|
| 32 |
+
return
|
| 33 |
+
# Reset task for new video: completed=0, total=num_steps
|
| 34 |
+
self._progress.reset(self._task, total=self._num_steps)
|
| 35 |
+
self._progress.update(
|
| 36 |
+
self._task,
|
| 37 |
+
completed=0,
|
| 38 |
+
video=f"{video_idx + 1}/{self._num_prompts}",
|
| 39 |
+
info=f"step 0/{self._num_steps}",
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
def advance_step(self) -> None:
|
| 43 |
+
"""Advance the denoising step by one."""
|
| 44 |
+
if self._progress is None or self._task is None:
|
| 45 |
+
return
|
| 46 |
+
self._progress.advance(self._task)
|
| 47 |
+
completed = int(self._progress.tasks[self._task].completed)
|
| 48 |
+
self._progress.update(self._task, info=f"step {completed}/{self._num_steps}")
|
| 49 |
+
|
| 50 |
+
def cleanup(self) -> None:
|
| 51 |
+
"""Hide sampling task when done."""
|
| 52 |
+
if self._progress is None or self._task is None:
|
| 53 |
+
return
|
| 54 |
+
self._progress.update(self._task, visible=False)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
class TrainingProgress:
|
| 58 |
+
"""Manages Rich progress display for training and validation.
|
| 59 |
+
This class encapsulates all progress bar logic, providing a clean interface
|
| 60 |
+
for the trainer to update progress without dealing with Rich internals.
|
| 61 |
+
Usage:
|
| 62 |
+
with TrainingProgress(enabled=True, total_steps=1000) as progress:
|
| 63 |
+
for step in range(1000):
|
| 64 |
+
# ... training step ...
|
| 65 |
+
progress.update_training(loss=0.1, lr=1e-4, step_time=0.5)
|
| 66 |
+
if should_validate:
|
| 67 |
+
sampling_ctx = progress.start_sampling(num_prompts=3, num_steps=30)
|
| 68 |
+
sampler = ValidationSampler(..., sampling_context=sampling_ctx)
|
| 69 |
+
for prompt_idx, prompt in enumerate(prompts):
|
| 70 |
+
sampling_ctx.start_video(prompt_idx)
|
| 71 |
+
sampler.generate(...)
|
| 72 |
+
sampling_ctx.cleanup()
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
def __init__(self, enabled: bool, total_steps: int):
|
| 76 |
+
"""Initialize progress tracking.
|
| 77 |
+
Args:
|
| 78 |
+
enabled: Whether to display progress bars (False for non-main processes)
|
| 79 |
+
total_steps: Total number of training steps
|
| 80 |
+
"""
|
| 81 |
+
self._enabled = enabled
|
| 82 |
+
self._total_steps = total_steps
|
| 83 |
+
self._train_task: TaskID | None = None
|
| 84 |
+
|
| 85 |
+
if not enabled:
|
| 86 |
+
self._progress = None
|
| 87 |
+
return
|
| 88 |
+
|
| 89 |
+
# Single Progress instance with flexible columns
|
| 90 |
+
self._progress = Progress(
|
| 91 |
+
TextColumn("[progress.description]{task.description}"),
|
| 92 |
+
TextColumn("{task.fields[video]}", style="magenta"),
|
| 93 |
+
BarColumn(bar_width=40, style="blue"),
|
| 94 |
+
TextColumn("{task.fields[info]}", style="cyan"),
|
| 95 |
+
TimeElapsedColumn(),
|
| 96 |
+
TextColumn("ETA:"),
|
| 97 |
+
TimeRemainingColumn(compact=True),
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
def __enter__(self) -> "TrainingProgress":
|
| 101 |
+
"""Enter the progress context, starting the live display."""
|
| 102 |
+
if self._progress is not None:
|
| 103 |
+
self._progress.__enter__()
|
| 104 |
+
self._train_task = self._progress.add_task(
|
| 105 |
+
"Training",
|
| 106 |
+
total=self._total_steps,
|
| 107 |
+
video=f"0/{self._total_steps}",
|
| 108 |
+
info="Starting...",
|
| 109 |
+
)
|
| 110 |
+
return self
|
| 111 |
+
|
| 112 |
+
def __exit__(self, *args) -> None:
|
| 113 |
+
"""Exit the progress context, stopping the live display."""
|
| 114 |
+
if self._progress is not None:
|
| 115 |
+
self._progress.__exit__(*args)
|
| 116 |
+
|
| 117 |
+
@property
|
| 118 |
+
def enabled(self) -> bool:
|
| 119 |
+
"""Whether progress display is enabled."""
|
| 120 |
+
return self._enabled
|
| 121 |
+
|
| 122 |
+
def update_training(
|
| 123 |
+
self,
|
| 124 |
+
*,
|
| 125 |
+
loss: float,
|
| 126 |
+
lr: float,
|
| 127 |
+
step_time: float,
|
| 128 |
+
advance: bool = True,
|
| 129 |
+
) -> None:
|
| 130 |
+
"""Update the training progress display.
|
| 131 |
+
Args:
|
| 132 |
+
loss: Current training loss
|
| 133 |
+
lr: Current learning rate
|
| 134 |
+
step_time: Time taken for this step in seconds
|
| 135 |
+
advance: Whether to advance the progress by one step
|
| 136 |
+
"""
|
| 137 |
+
if self._progress is None or self._train_task is None:
|
| 138 |
+
return
|
| 139 |
+
|
| 140 |
+
info = f"Loss: {loss:.4f} | LR: {lr:.2e} | {step_time:.2f}s/step"
|
| 141 |
+
self._progress.update(
|
| 142 |
+
self._train_task,
|
| 143 |
+
advance=1 if advance else 0,
|
| 144 |
+
info=info,
|
| 145 |
+
)
|
| 146 |
+
# Update step count in video column
|
| 147 |
+
completed = int(self._progress.tasks[self._train_task].completed)
|
| 148 |
+
self._progress.update(self._train_task, video=f"{completed}/{self._total_steps}")
|
| 149 |
+
|
| 150 |
+
def start_sampling(self, num_prompts: int, num_steps: int) -> SamplingContext:
|
| 151 |
+
"""Start validation sampling progress tracking.
|
| 152 |
+
Creates a task that shows current video and denoising step progress.
|
| 153 |
+
Format: "Sampling X/Y [████████████] step Z/W"
|
| 154 |
+
Args:
|
| 155 |
+
num_prompts: Number of validation prompts to sample
|
| 156 |
+
num_steps: Number of denoising steps per sample
|
| 157 |
+
Returns:
|
| 158 |
+
SamplingContext for tracking progress (no-op if progress is disabled)
|
| 159 |
+
"""
|
| 160 |
+
if self._progress is None:
|
| 161 |
+
# Return a no-op context when progress is disabled
|
| 162 |
+
return SamplingContext(
|
| 163 |
+
progress=None,
|
| 164 |
+
task=None,
|
| 165 |
+
num_prompts=num_prompts,
|
| 166 |
+
num_steps=num_steps,
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
task = self._progress.add_task(
|
| 170 |
+
"Sampling",
|
| 171 |
+
total=num_steps,
|
| 172 |
+
completed=0,
|
| 173 |
+
video=f"0/{num_prompts}",
|
| 174 |
+
info=f"step 0/{num_steps}",
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
return SamplingContext(
|
| 178 |
+
progress=self._progress,
|
| 179 |
+
task=task,
|
| 180 |
+
num_prompts=num_prompts,
|
| 181 |
+
num_steps=num_steps,
|
| 182 |
+
)
|
packages/ltx-trainer/src/ltx_trainer/quantization.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from: https://github.com/bghira/SimpleTuner
|
| 2 |
+
# With improvements from: https://github.com/ostris/ai-toolkit
|
| 3 |
+
from typing import Literal
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
|
| 7 |
+
|
| 8 |
+
from ltx_trainer import logger
|
| 9 |
+
|
| 10 |
+
QuantizationOptions = Literal[
|
| 11 |
+
"int8-quanto",
|
| 12 |
+
"int4-quanto",
|
| 13 |
+
"int2-quanto",
|
| 14 |
+
"fp8-quanto",
|
| 15 |
+
"fp8uz-quanto",
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
# Modules to exclude from quantization.
|
| 19 |
+
# These are glob patterns passed to quanto's `exclude` parameter.
|
| 20 |
+
# When quantizing the full model at once, these patterns match against full module paths.
|
| 21 |
+
# When quantizing block-by-block, we also use SKIP_ROOT_MODULES for top-level modules.
|
| 22 |
+
EXCLUDE_PATTERNS = [
|
| 23 |
+
# Input/output projection layers
|
| 24 |
+
"patchify_proj",
|
| 25 |
+
"audio_patchify_proj",
|
| 26 |
+
"proj_out",
|
| 27 |
+
"audio_proj_out",
|
| 28 |
+
# Timestep embedding layers - int4 tinygemm requires strict bfloat16 input
|
| 29 |
+
# and these receive float32 sinusoidal embeddings that are cast to bfloat16
|
| 30 |
+
"*adaln*",
|
| 31 |
+
"time_proj",
|
| 32 |
+
"timestep_embedder*",
|
| 33 |
+
# Caption/text projection layers
|
| 34 |
+
"caption_projection*",
|
| 35 |
+
"audio_caption_projection*",
|
| 36 |
+
# Normalization layers (usually excluded from quantization)
|
| 37 |
+
"*norm*",
|
| 38 |
+
]
|
| 39 |
+
|
| 40 |
+
# Top-level modules to skip entirely during block-by-block quantization.
|
| 41 |
+
# These are exact matches against model.named_children() names.
|
| 42 |
+
# (Needed because quanto's exclude patterns don't work when calling quantize() directly on a module)
|
| 43 |
+
SKIP_ROOT_MODULES = {
|
| 44 |
+
"patchify_proj",
|
| 45 |
+
"audio_patchify_proj",
|
| 46 |
+
"proj_out",
|
| 47 |
+
"audio_proj_out",
|
| 48 |
+
"audio_caption_projection",
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def quantize_model(
|
| 53 |
+
model: torch.nn.Module,
|
| 54 |
+
precision: QuantizationOptions,
|
| 55 |
+
quantize_activations: bool = False,
|
| 56 |
+
device: torch.device | str | None = None,
|
| 57 |
+
) -> torch.nn.Module:
|
| 58 |
+
"""
|
| 59 |
+
Quantize a model using optimum-quanto.
|
| 60 |
+
For large models with transformer_blocks, this function quantizes block-by-block
|
| 61 |
+
on GPU then moves back to CPU, which is much faster than quantizing on CPU and
|
| 62 |
+
uses less peak VRAM than loading the entire model to GPU at once.
|
| 63 |
+
Args:
|
| 64 |
+
model: The model to quantize.
|
| 65 |
+
precision: The quantization precision (e.g. "int8-quanto", "fp8-quanto").
|
| 66 |
+
quantize_activations: Whether to quantize activations in addition to weights.
|
| 67 |
+
device: Device to use for quantization. If None, uses CUDA if available, else CPU.
|
| 68 |
+
Returns:
|
| 69 |
+
The quantized model.
|
| 70 |
+
"""
|
| 71 |
+
from optimum.quanto import freeze, quantize # noqa: PLC0415
|
| 72 |
+
|
| 73 |
+
if device is None:
|
| 74 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 75 |
+
elif isinstance(device, str):
|
| 76 |
+
device = torch.device(device)
|
| 77 |
+
|
| 78 |
+
weight_quant = _get_quanto_dtype(precision)
|
| 79 |
+
|
| 80 |
+
if quantize_activations:
|
| 81 |
+
logger.debug("Quantizing model weights and activations")
|
| 82 |
+
activations_quant = weight_quant
|
| 83 |
+
else:
|
| 84 |
+
activations_quant = None
|
| 85 |
+
|
| 86 |
+
# Remember original device to restore after quantization
|
| 87 |
+
original_device = next(model.parameters()).device
|
| 88 |
+
|
| 89 |
+
# Check if model has transformer_blocks for block-by-block quantization
|
| 90 |
+
if hasattr(model, "transformer_blocks"):
|
| 91 |
+
logger.debug("Quantizing model using block-by-block approach for memory efficiency")
|
| 92 |
+
_quantize_blockwise(
|
| 93 |
+
model,
|
| 94 |
+
weight_quant=weight_quant,
|
| 95 |
+
activations_quant=activations_quant,
|
| 96 |
+
device=device,
|
| 97 |
+
)
|
| 98 |
+
else:
|
| 99 |
+
# Fallback: quantize entire model at once
|
| 100 |
+
model.to(device)
|
| 101 |
+
quantize(model, weights=weight_quant, activations=activations_quant, exclude=EXCLUDE_PATTERNS)
|
| 102 |
+
freeze(model)
|
| 103 |
+
|
| 104 |
+
# Restore model to original device
|
| 105 |
+
model.to(original_device)
|
| 106 |
+
|
| 107 |
+
return model
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def _quantize_blockwise(
|
| 111 |
+
model: torch.nn.Module,
|
| 112 |
+
weight_quant: torch.dtype,
|
| 113 |
+
activations_quant: torch.dtype | None,
|
| 114 |
+
device: torch.device,
|
| 115 |
+
) -> None:
|
| 116 |
+
"""Quantize a model block-by-block using optimum-quanto.
|
| 117 |
+
This approach:
|
| 118 |
+
1. Moves each transformer block to GPU
|
| 119 |
+
2. Quantizes on GPU (fast!)
|
| 120 |
+
3. Freezes the quantized weights
|
| 121 |
+
4. Moves back to CPU
|
| 122 |
+
This is much faster than quantizing on CPU and uses less peak VRAM
|
| 123 |
+
than loading the entire model to GPU.
|
| 124 |
+
"""
|
| 125 |
+
from optimum.quanto import freeze, quantize # noqa: PLC0415
|
| 126 |
+
|
| 127 |
+
original_dtype = next(model.parameters()).dtype
|
| 128 |
+
transformer_blocks = list(model.transformer_blocks)
|
| 129 |
+
|
| 130 |
+
with Progress(
|
| 131 |
+
SpinnerColumn(),
|
| 132 |
+
TextColumn("[progress.description]{task.description}"),
|
| 133 |
+
BarColumn(),
|
| 134 |
+
TaskProgressColumn(),
|
| 135 |
+
transient=True,
|
| 136 |
+
) as progress:
|
| 137 |
+
task = progress.add_task("Quantizing transformer blocks", total=len(transformer_blocks))
|
| 138 |
+
|
| 139 |
+
for block in transformer_blocks:
|
| 140 |
+
# Move block to GPU
|
| 141 |
+
block.to(device, dtype=original_dtype, non_blocking=True)
|
| 142 |
+
|
| 143 |
+
# Quantize on GPU
|
| 144 |
+
quantize(block, weights=weight_quant, activations=activations_quant, exclude=EXCLUDE_PATTERNS)
|
| 145 |
+
freeze(block)
|
| 146 |
+
|
| 147 |
+
# Move back to CPU to free up VRAM for next block
|
| 148 |
+
block.to("cpu", non_blocking=True)
|
| 149 |
+
|
| 150 |
+
progress.advance(task)
|
| 151 |
+
|
| 152 |
+
# Quantize remaining non-transformer-block modules (e.g., embeddings, timestep projections)
|
| 153 |
+
# Skip modules that should not be quantized (patchify_proj, proj_out, etc.)
|
| 154 |
+
logger.debug("Quantizing remaining model components")
|
| 155 |
+
|
| 156 |
+
for name, module in model.named_children():
|
| 157 |
+
if name == "transformer_blocks":
|
| 158 |
+
continue # Already quantized
|
| 159 |
+
|
| 160 |
+
if name in SKIP_ROOT_MODULES:
|
| 161 |
+
logger.debug(f"Skipping quantization for module: {name}")
|
| 162 |
+
continue # Don't quantize these modules
|
| 163 |
+
|
| 164 |
+
# Move to device, quantize, freeze, move back
|
| 165 |
+
module.to(device, dtype=original_dtype, non_blocking=True)
|
| 166 |
+
quantize(module, weights=weight_quant, activations=activations_quant, exclude=EXCLUDE_PATTERNS)
|
| 167 |
+
freeze(module)
|
| 168 |
+
module.to("cpu", non_blocking=True)
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def _get_quanto_dtype(precision: QuantizationOptions) -> torch.dtype:
|
| 172 |
+
"""Map precision string to quanto dtype."""
|
| 173 |
+
from optimum.quanto import ( # noqa: PLC0415
|
| 174 |
+
qfloat8,
|
| 175 |
+
qfloat8_e4m3fnuz,
|
| 176 |
+
qint2,
|
| 177 |
+
qint4,
|
| 178 |
+
qint8,
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
if precision == "int2-quanto":
|
| 182 |
+
return qint2
|
| 183 |
+
elif precision == "int4-quanto":
|
| 184 |
+
return qint4
|
| 185 |
+
elif precision == "int8-quanto":
|
| 186 |
+
return qint8
|
| 187 |
+
elif precision in ("fp8-quanto", "fp8uz-quanto"):
|
| 188 |
+
if torch.backends.mps.is_available():
|
| 189 |
+
raise ValueError("FP8 quantization is not supported on MPS devices. Use int2, int4, or int8 instead.")
|
| 190 |
+
if precision == "fp8-quanto":
|
| 191 |
+
return qfloat8
|
| 192 |
+
elif precision == "fp8uz-quanto":
|
| 193 |
+
return qfloat8_e4m3fnuz
|
| 194 |
+
|
| 195 |
+
raise ValueError(f"Invalid quantization precision: {precision}")
|
packages/ltx-trainer/src/ltx_trainer/sigma_tracker.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Sigma-bucketed loss tracking.
|
| 2 |
+
Maps each training step's per-element sigmas and losses to buckets.
|
| 3 |
+
Smoothing is left to wandb's UI.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import bisect
|
| 7 |
+
from collections import defaultdict
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class SigmaBucketTracker:
|
| 11 |
+
"""Map per-element sigma values to named buckets for per-bucket loss logging.
|
| 12 |
+
By default, partitions [0, 1] into four equal-width buckets.
|
| 13 |
+
Custom boundaries can be provided for non-uniform bucketing.
|
| 14 |
+
Each call to update() receives per-element sigmas and losses (both [B,]),
|
| 15 |
+
buckets each element, and computes the mean loss per bucket. This gives
|
| 16 |
+
accurate per-sigma loss tracking even for batch_size > 1.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
def __init__(
|
| 20 |
+
self,
|
| 21 |
+
bucket_boundaries: list[float] | None = None,
|
| 22 |
+
) -> None:
|
| 23 |
+
if bucket_boundaries is None:
|
| 24 |
+
bucket_boundaries = [0.0, 0.25, 0.5, 0.75, 1.0]
|
| 25 |
+
if len(bucket_boundaries) < 2:
|
| 26 |
+
raise ValueError("bucket_boundaries must have at least 2 elements")
|
| 27 |
+
if any(bucket_boundaries[i] >= bucket_boundaries[i + 1] for i in range(len(bucket_boundaries) - 1)):
|
| 28 |
+
raise ValueError("bucket_boundaries must be strictly increasing")
|
| 29 |
+
self._boundaries = list(bucket_boundaries)
|
| 30 |
+
self._num_buckets = len(bucket_boundaries) - 1
|
| 31 |
+
self._bucket_labels = [
|
| 32 |
+
f"{bucket_boundaries[i]:.2f}-{bucket_boundaries[i + 1]:.2f}" for i in range(self._num_buckets)
|
| 33 |
+
]
|
| 34 |
+
self._last_metrics: dict[str, float] = {}
|
| 35 |
+
|
| 36 |
+
def _get_bucket_index(self, sigma: float) -> int:
|
| 37 |
+
"""Map sigma value to bucket index."""
|
| 38 |
+
idx = bisect.bisect_right(self._boundaries, sigma) - 1
|
| 39 |
+
return max(0, min(idx, self._num_buckets - 1))
|
| 40 |
+
|
| 41 |
+
def update(self, sigmas: list[float], losses: list[float]) -> None:
|
| 42 |
+
"""Record per-element losses into their sigma buckets.
|
| 43 |
+
Args:
|
| 44 |
+
sigmas: Per-element sigma values, one per batch element.
|
| 45 |
+
losses: Per-element losses, one per batch element.
|
| 46 |
+
"""
|
| 47 |
+
if not sigmas:
|
| 48 |
+
self._last_metrics = {}
|
| 49 |
+
return
|
| 50 |
+
bucket_losses: dict[int, list[float]] = defaultdict(list)
|
| 51 |
+
for sigma, loss in zip(sigmas, losses, strict=True):
|
| 52 |
+
bucket_losses[self._get_bucket_index(sigma)].append(loss)
|
| 53 |
+
self._last_metrics = {self._bucket_labels[b]: sum(vals) / len(vals) for b, vals in bucket_losses.items()}
|
| 54 |
+
|
| 55 |
+
def get_metrics(self, prefix: str = "train") -> dict[str, float]:
|
| 56 |
+
"""Return the mean loss for each bucket hit on the last update.
|
| 57 |
+
Wandb handles smoothing in the UI.
|
| 58 |
+
"""
|
| 59 |
+
return {f"{prefix}/loss_sigma_{label}": loss for label, loss in self._last_metrics.items()}
|
packages/ltx-trainer/src/ltx_trainer/timestep_samplers.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class TimestepSampler:
|
| 5 |
+
"""Base class for timestep samplers.
|
| 6 |
+
Timestep samplers are used to sample timesteps for diffusion models.
|
| 7 |
+
They should implement both sample() and sample_for() methods.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
def sample(self, batch_size: int, seq_length: int | None = None, device: torch.device = None) -> torch.Tensor:
|
| 11 |
+
"""Sample timesteps for a batch.
|
| 12 |
+
Args:
|
| 13 |
+
batch_size: Number of timesteps to sample
|
| 14 |
+
seq_length: (optional) Length of the sequence being processed
|
| 15 |
+
device: Device to place the samples on
|
| 16 |
+
Returns:
|
| 17 |
+
Tensor of shape (batch_size,) containing timesteps
|
| 18 |
+
"""
|
| 19 |
+
raise NotImplementedError
|
| 20 |
+
|
| 21 |
+
def sample_for(self, batch: torch.Tensor) -> torch.Tensor:
|
| 22 |
+
"""Sample timesteps for a specific batch tensor.
|
| 23 |
+
Args:
|
| 24 |
+
batch: Input tensor of shape (batch_size, seq_length, ...)
|
| 25 |
+
Returns:
|
| 26 |
+
Tensor of shape (batch_size,) containing timesteps
|
| 27 |
+
"""
|
| 28 |
+
raise NotImplementedError
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class UniformTimestepSampler(TimestepSampler):
|
| 32 |
+
"""Samples timesteps uniformly between min_value and max_value (default 0 and 1)."""
|
| 33 |
+
|
| 34 |
+
def __init__(self, min_value: float = 0.0, max_value: float = 1.0):
|
| 35 |
+
self.min_value = min_value
|
| 36 |
+
self.max_value = max_value
|
| 37 |
+
|
| 38 |
+
def sample(self, batch_size: int, seq_length: int | None = None, device: torch.device = None) -> torch.Tensor: # noqa: ARG002
|
| 39 |
+
return torch.rand(batch_size, device=device) * (self.max_value - self.min_value) + self.min_value
|
| 40 |
+
|
| 41 |
+
def sample_for(self, batch: torch.Tensor) -> torch.Tensor:
|
| 42 |
+
if batch.ndim != 3:
|
| 43 |
+
raise ValueError(f"Batch should have 3 dimensions, got {batch.ndim}")
|
| 44 |
+
|
| 45 |
+
return self.sample(batch.shape[0], device=batch.device)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class ShiftedLogitNormalTimestepSampler(TimestepSampler):
|
| 49 |
+
"""
|
| 50 |
+
Samples timesteps from a stretched shifted logit-normal distribution,
|
| 51 |
+
where the shift is determined by the sequence length.
|
| 52 |
+
The stretching normalizes samples between percentile bounds to ensure
|
| 53 |
+
the distribution covers [0, 1] more evenly. A uniform fallback prevents
|
| 54 |
+
collapse at high token counts.
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
def __init__(self, std: float = 1.0, eps: float = 1e-3, uniform_prob: float = 0.1):
|
| 58 |
+
self.std = std
|
| 59 |
+
self.eps = eps
|
| 60 |
+
self.uniform_prob = uniform_prob
|
| 61 |
+
# Percentile values for stretching (scaled by std)
|
| 62 |
+
# 99.9th percentile of standard normal ≈ 3.0902
|
| 63 |
+
# 0.5th percentile of standard normal ≈ -2.5758
|
| 64 |
+
self.normal_999_percentile = 3.0902 * std
|
| 65 |
+
self.normal_005_percentile = -2.5758 * std
|
| 66 |
+
|
| 67 |
+
def sample(self, batch_size: int, seq_length: int, device: torch.device = None) -> torch.Tensor:
|
| 68 |
+
"""Sample timesteps for a batch from a stretched shifted logit-normal distribution.
|
| 69 |
+
Args:
|
| 70 |
+
batch_size: Number of timesteps to sample
|
| 71 |
+
seq_length: Length of the sequence being processed, used to determine the shift
|
| 72 |
+
device: Device to place the samples on
|
| 73 |
+
Returns:
|
| 74 |
+
Tensor of shape (batch_size,) containing timesteps sampled from a stretched
|
| 75 |
+
shifted logit-normal distribution, where the shift is determined by seq_length
|
| 76 |
+
"""
|
| 77 |
+
mu = self._get_shift_for_sequence_length(seq_length)
|
| 78 |
+
|
| 79 |
+
# Sample from shifted logit-normal
|
| 80 |
+
normal_samples = torch.randn((batch_size,), device=device) * self.std + mu
|
| 81 |
+
logitnormal_samples = torch.sigmoid(normal_samples)
|
| 82 |
+
|
| 83 |
+
# Compute percentile bounds for stretching
|
| 84 |
+
percentile_999 = torch.sigmoid(torch.tensor(mu + self.normal_999_percentile, device=device))
|
| 85 |
+
percentile_005 = torch.sigmoid(torch.tensor(mu + self.normal_005_percentile, device=device))
|
| 86 |
+
|
| 87 |
+
# Stretch to [0, 1] range by normalizing between percentiles
|
| 88 |
+
zero_terminal_raw = (logitnormal_samples - percentile_005) / (percentile_999 - percentile_005)
|
| 89 |
+
|
| 90 |
+
# Reflect small values around eps for numerical stability
|
| 91 |
+
stretched_logit = torch.where(
|
| 92 |
+
zero_terminal_raw >= self.eps,
|
| 93 |
+
zero_terminal_raw,
|
| 94 |
+
2 * self.eps - zero_terminal_raw,
|
| 95 |
+
)
|
| 96 |
+
stretched_logit = torch.clamp(stretched_logit, 0, 1)
|
| 97 |
+
|
| 98 |
+
# Mix with uniform samples (uniform_prob of the time)
|
| 99 |
+
uniform = (1 - self.eps) * torch.rand((batch_size,), device=device) + self.eps
|
| 100 |
+
prob = torch.rand((batch_size,), device=device)
|
| 101 |
+
|
| 102 |
+
return torch.where(prob > self.uniform_prob, stretched_logit, uniform)
|
| 103 |
+
|
| 104 |
+
def sample_for(self, batch: torch.Tensor) -> torch.Tensor:
|
| 105 |
+
"""Sample timesteps for a specific batch tensor.
|
| 106 |
+
Args:
|
| 107 |
+
batch: Input tensor of shape (batch_size, seq_length, ...)
|
| 108 |
+
Returns:
|
| 109 |
+
Tensor of shape (batch_size,) containing timesteps sampled from a shifted
|
| 110 |
+
logit-normal distribution, where the shift is determined by the sequence length
|
| 111 |
+
of the input batch
|
| 112 |
+
Raises:
|
| 113 |
+
ValueError: If the input batch does not have 3 dimensions
|
| 114 |
+
"""
|
| 115 |
+
if batch.ndim != 3:
|
| 116 |
+
raise ValueError(f"Batch should have 3 dimensions, got {batch.ndim}")
|
| 117 |
+
|
| 118 |
+
batch_size, seq_length, _ = batch.shape
|
| 119 |
+
return self.sample(batch_size, seq_length, device=batch.device)
|
| 120 |
+
|
| 121 |
+
@staticmethod
|
| 122 |
+
def _get_shift_for_sequence_length(
|
| 123 |
+
seq_length: int,
|
| 124 |
+
min_tokens: int = 1024,
|
| 125 |
+
max_tokens: int = 4096,
|
| 126 |
+
min_shift: float = 0.95,
|
| 127 |
+
max_shift: float = 2.05,
|
| 128 |
+
) -> float:
|
| 129 |
+
# Calculate the shift value for a given sequence length using linear interpolation
|
| 130 |
+
# between min_shift and max_shift based on sequence length.
|
| 131 |
+
m = (max_shift - min_shift) / (max_tokens - min_tokens) # Calculate slope
|
| 132 |
+
b = min_shift - m * min_tokens # Calculate y-intercept
|
| 133 |
+
shift = m * seq_length + b # Apply linear equation y = mx + b
|
| 134 |
+
return shift
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
SAMPLERS = {
|
| 138 |
+
"uniform": UniformTimestepSampler,
|
| 139 |
+
"shifted_logit_normal": ShiftedLogitNormalTimestepSampler,
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def example() -> None:
|
| 144 |
+
# noinspection PyUnresolvedReferences
|
| 145 |
+
import matplotlib.pyplot as plt # noqa: PLC0415
|
| 146 |
+
|
| 147 |
+
sampler = ShiftedLogitNormalTimestepSampler()
|
| 148 |
+
for seq_length in [1024, 2048, 4096, 8192]:
|
| 149 |
+
samples = sampler.sample(batch_size=1_000_000, seq_length=seq_length)
|
| 150 |
+
|
| 151 |
+
# plot the histogram of the samples
|
| 152 |
+
plt.hist(samples.numpy(), bins=100, density=True)
|
| 153 |
+
plt.title(f"Timestep Samples for Sequence Length {seq_length}")
|
| 154 |
+
plt.xlabel("Timestep")
|
| 155 |
+
plt.ylabel("Density")
|
| 156 |
+
plt.show()
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
if __name__ == "__main__":
|
| 160 |
+
example()
|