Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions data.reference/media-models.json
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,87 @@
"reviewedAt": "2026-08-30"
}
},
{
"id": "ltx25_cuda_distilled",
"name": "LTX-2.5 CUDA Distilled (joint video + audio, ~72 GB download, streamed)",
"repo": "Lightricks/LTX-2.5",
"revision": "bf86adedf518142442575d1ce2e767b7d01c8c76",
"repoFiles": [
"diffusion_models/ltx-2.5-22b-distilled-transformer-bf16.safetensors",
"text_encoders/gemma4-12b-with-proj-ltx-2.5-bf16.safetensors",
"vae/ltx-2.5-video-vae-bf16.safetensors",
"vae/ltx-2.5-audio-vae-bf16.safetensors",
"model_patches/ltx-2.5-duration-head-bf16.safetensors",
"latent_upscale_models/ltx-2.5-latent-spatial-upscaler-x2-bf16-1.0.safetensors"
],
"runtime": "ltx25_cuda",
"supportedModes": ["text", "image"],
"defaultFrames": 121,
"resolutionStep": 64,
"fpsOptions": [24],
"steps": 8,
"guidance": 1,
"samplerLocked": true,
"samplerNote": "LTX-2.5 Distilled uses the official fixed 8-step, CFG-free schedule.",
"supportsNegativePrompt": false,
"supportsTiling": false,
"supportsDisableAudio": true,
"requiresHfToken": true,
"hardwareRequirements": {
"minMemoryGb": 64,
"minVramGb": 16,
"minCudaComputeCapability": 8
},
"disclosure": {
"modelCardUrl": "https://huggingface.co/Lightricks/LTX-2.5",
"weightsLicense": {
"name": "LTX-2.x Community License",
"url": "https://github.com/Lightricks/LTX-2/blob/main/LICENSE.md"
},
"runtimeLicense": {
"name": "Apache-2.0",
"url": "https://github.com/Lightricks/LTX-2/blob/v1.2.0/LICENSE"
},
"estimatedDownloadGb": 72.1,
"reviewedAt": "2026-08-30"
}
},
{
"id": "wan22_cuda_ti2v_5b",
"name": "Wan 2.2 TI2V 5B CUDA (high quality, ~34 GB download, text-to-video)",
"repo": "Wan-AI/Wan2.2-TI2V-5B-Diffusers",
"revision": "b8fff7315c768468a5333511427288870b2e9635",
"runtime": "wan22_cuda",
"supportedModes": ["text"],
"defaultWidth": 1280,
"defaultHeight": 704,
"resolutionStep": 16,
"defaultFrames": 121,
"frameStride": 4,
"fpsOptions": [24],
"steps": 50,
"guidance": 5,
"supportsNegativePrompt": true,
"supportsTiling": false,
"hardwareRequirements": {
"minMemoryGb": 32,
"minVramGb": 24,
"minCudaComputeCapability": 8
},
"disclosure": {
"modelCardUrl": "https://huggingface.co/Wan-AI/Wan2.2-TI2V-5B-Diffusers",
"weightsLicense": {
"name": "Apache-2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0"
},
"runtimeLicense": {
"name": "Apache-2.0",
"url": "https://github.com/huggingface/diffusers/blob/main/LICENSE"
},
"estimatedDownloadGb": 34.2,
"reviewedAt": "2026-08-30"
}
},
{
"id": "minimax_h3_cuda",
"name": "MiniMax H3 CUDA int8 (joint video + audio, ~144 GB download, 24 GB VRAM + 96 GB RAM)",
Expand Down
175 changes: 175 additions & 0 deletions scripts/generate_ltx25_cuda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""Cache-only LTX-2.5 distilled video+audio runner for NVIDIA CUDA.

The Video Gen UI owns runtime installation and every Hugging Face download.
This helper resolves the pinned split checkpoint only from the local cache,
streams model blocks from disk on consumer GPUs, and writes one MP4.
"""

from __future__ import annotations

import argparse
import os
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
from _runner_common import emit_runtime_fingerprint, establish_process_group, heartbeat # noqa: E402


MODEL_FILES = {
"transformer": "diffusion_models/ltx-2.5-22b-distilled-transformer-bf16.safetensors",
"text_encoder": "text_encoders/gemma4-12b-with-proj-ltx-2.5-bf16.safetensors",
"video_vae": "vae/ltx-2.5-video-vae-bf16.safetensors",
"audio_vae": "vae/ltx-2.5-audio-vae-bf16.safetensors",
"duration_head": "model_patches/ltx-2.5-duration-head-bf16.safetensors",
"upsampler": "latent_upscale_models/ltx-2.5-latent-spatial-upscaler-x2-bf16-1.0.safetensors",
}


def log(message: str) -> None:
print(message, file=sys.stderr, flush=True)


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--model-repo", required=True)
parser.add_argument("--model-revision", required=True)
parser.add_argument("--repo-file", action="append", default=[])
parser.add_argument("--prompt", required=True)
parser.add_argument("--negative-prompt")
parser.add_argument("--width", type=int, required=True)
parser.add_argument("--height", type=int, required=True)
parser.add_argument("--num-frames", type=int, required=True)
parser.add_argument("--fps", type=float, required=True)
parser.add_argument("--steps", type=int, default=8)
parser.add_argument("--seed", type=int, required=True)
parser.add_argument("--image")
parser.add_argument("--image-strength", type=float, default=1.0)
parser.add_argument("--disable-audio", action="store_true")
parser.add_argument("--output", required=True)
return parser.parse_args()


def validate_args(args: argparse.Namespace) -> None:
missing = sorted(set(MODEL_FILES.values()) - set(args.repo_file))
if missing:
raise SystemExit(f"LTX-2.5 model entry is missing required repo files: {', '.join(missing)}")
if args.width % 64 or args.height % 64:
raise SystemExit("The two-stage LTX-2.5 pipeline requires width and height divisible by 64.")
if args.num_frames < 9 or args.num_frames % 8 != 1:
raise SystemExit("LTX-2.5 num-frames must be at least 9 and satisfy frames % 8 == 1.")
if args.steps != 8:
raise SystemExit("The distilled LTX-2.5 schedule is fixed at 8 steps.")
if not 0 <= args.image_strength <= 1:
raise SystemExit("image-strength must be between 0 and 1.")


def resolve_snapshot(args: argparse.Namespace) -> Path:
from huggingface_hub import snapshot_download

try:
root = snapshot_download(
repo_id=args.model_repo,
revision=args.model_revision,
allow_patterns=args.repo_file,
local_files_only=True,
)
except Exception as exc:
raise RuntimeError(
"The pinned LTX-2.5 files are not complete in the Hugging Face cache. "
"Use Download or Repair on the Video Gen page."
) from exc
snapshot = Path(root)
unresolved = [relative for relative in MODEL_FILES.values() if not (snapshot / relative).is_file()]
if unresolved:
raise RuntimeError(
"The pinned LTX-2.5 snapshot is incomplete: " + ", ".join(unresolved)
)
return snapshot


def main() -> None:
establish_process_group()
args = parse_args()
validate_args(args)
log("STAGE:resolve-cache")
snapshot = resolve_snapshot(args)

# LTX's documented allocator setting reduces fragmentation on cards that
# sit close to the model's supported VRAM floor. It must be set before
# importing torch so CUDA reads it during allocator initialization.
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
import torch
from ltx_core.model.video_vae import AUTO_TILING, get_video_chunks_number
from ltx_core.quantization.fp8_cast import build_policy as build_fp8_cast_policy
from ltx_pipelines.distilled import DistilledPipeline
from ltx_pipelines.utils.args import ImageConditioningInput
from ltx_pipelines.utils.media_io import encode_video
from ltx_pipelines.utils.model_paths import ModelPaths
from ltx_pipelines.utils.types import OffloadMode

if not torch.cuda.is_available():
raise RuntimeError("LTX-2.5 CUDA needs a visible NVIDIA device. Repair the runtime from Video Gen.")
emit_runtime_fingerprint(
"ltx25_cuda",
["torch", "ltx-core", "ltx-pipelines", "transformers", "accelerate", "huggingface-hub"],
)
paths = {key: str(snapshot / relative) for key, relative in MODEL_FILES.items()}
model_paths = ModelPaths.from_split(
transformer_path=paths["transformer"],
text_encoder_path=paths["text_encoder"],
video_vae_path=paths["video_vae"],
audio_vae_path=paths["audio_vae"],
duration_head_path=paths["duration_head"],
)

log("STATUS:LTX-2.5 CUDA disk-streamed loading (FP8 transformer cache)")
log("STAGE:load-pipeline")
with heartbeat("ltx25-cuda-load"):
pipe = DistilledPipeline(
model_paths=model_paths,
spatial_upsampler_path=paths["upsampler"],
loras=[],
device=torch.device("cuda"),
quantization=build_fp8_cast_policy(paths["transformer"]),
# CPU mode pins a model-sized prefetch buffer. The 24 GB VRAM /
# 32 GB system-RAM tier can exhaust that buffer while Gemma is
# resident; DISK is upstream's lowest-memory streaming mode.
offload_mode=OffloadMode.DISK,
)
images = []
if args.image:
images.append(ImageConditioningInput(args.image, 0, args.image_strength))
log("STAGE:inference")
with heartbeat("ltx25-cuda-inference"):
video, audio, resolved_frames, tiling = pipe(
prompt=args.prompt,
seed=args.seed,
height=args.height,
width=args.width,
num_frames=args.num_frames,
frame_rate=args.fps,
images=images,
tiling_config=AUTO_TILING,
)
if args.disable_audio:
audio = None

output = Path(args.output)
output.parent.mkdir(parents=True, exist_ok=True)
log("STAGE:mux")
encode_video(
video=video,
fps=int(args.fps),
audio=audio,
output_path=str(output),
video_chunks_number=int(get_video_chunks_number(resolved_frames, tiling)),
)
if not output.is_file():
raise RuntimeError(f"LTX-2.5 completed but did not write {output}.")
log(f"STATUS:LTX-2.5 saved {output.name} ({resolved_frames} frames)")


if __name__ == "__main__":
main()
87 changes: 87 additions & 0 deletions scripts/generate_wan22_cuda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""Cache-only Wan 2.2 TI2V 5B text-to-video runner for NVIDIA CUDA."""

from __future__ import annotations

import argparse
import os
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
from _runner_common import emit_runtime_fingerprint, establish_process_group, heartbeat # noqa: E402


def log(message: str) -> None:
print(message, file=sys.stderr, flush=True)


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--model-repo", required=True)
parser.add_argument("--model-revision", required=True)
parser.add_argument("--prompt", required=True)
parser.add_argument("--negative-prompt", default="")
parser.add_argument("--width", type=int, required=True)
parser.add_argument("--height", type=int, required=True)
parser.add_argument("--num-frames", type=int, required=True)
parser.add_argument("--fps", type=float, required=True)
parser.add_argument("--steps", type=int, required=True)
parser.add_argument("--guidance", type=float, required=True)
parser.add_argument("--seed", type=int, required=True)
parser.add_argument("--output", required=True)
return parser.parse_args()


def validate_args(args: argparse.Namespace) -> None:
if args.width % 16 or args.height % 16:
raise SystemExit("Wan 2.2 width and height must be divisible by 16.")
if args.num_frames < 5 or (args.num_frames - 1) % 4:
raise SystemExit("Wan 2.2 frame count must satisfy 4n+1.")
if args.steps < 1:
raise SystemExit("Wan 2.2 steps must be positive.")


def main() -> None:
establish_process_group()
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
args = parse_args()
validate_args(args)
from huggingface_hub import snapshot_download
log("STAGE:resolve-cache")
try:
snapshot = snapshot_download(repo_id=args.model_repo, revision=args.model_revision, local_files_only=True)
except Exception as exc:
raise RuntimeError("The pinned Wan 2.2 snapshot is incomplete. Use Download or Repair in Video Gen.") from exc
import torch
from diffusers import AutoencoderKLWan, WanPipeline
from diffusers.utils import export_to_video
if not torch.cuda.is_available():
raise RuntimeError("Wan 2.2 CUDA needs a visible NVIDIA device. Repair the runtime in Video Gen.")
emit_runtime_fingerprint("wan22_cuda", ["torch", "diffusers", "transformers", "accelerate", "huggingface-hub", "hf-xet"])
log("STATUS:Loading Wan 2.2 TI2V 5B with component CPU offload")
log("STAGE:load-pipeline")
with heartbeat("wan22-cuda-load"):
vae = AutoencoderKLWan.from_pretrained(snapshot, subfolder="vae", torch_dtype=torch.float32, local_files_only=True)
pipe = WanPipeline.from_pretrained(snapshot, vae=vae, torch_dtype=torch.bfloat16, local_files_only=True)
# Each component fits independently on a 24 GB card (the largest is
# the ~20 GB transformer). Model-level offload moves each component
# once; sequential offload moved every layer every step and made a
# 3090 needlessly slow without lowering the peak that matters here.
pipe.enable_model_cpu_offload()
pipe.vae.enable_tiling()
generator = torch.Generator(device="cpu").manual_seed(args.seed)
log("STAGE:inference")
with heartbeat("wan22-cuda-inference"):
frames = pipe(prompt=args.prompt, negative_prompt=args.negative_prompt or None, height=args.height, width=args.width, num_frames=args.num_frames, num_inference_steps=args.steps, guidance_scale=args.guidance, generator=generator).frames[0]
output = Path(args.output)
output.parent.mkdir(parents=True, exist_ok=True)
log("STAGE:mux")
export_to_video(frames, str(output), fps=int(args.fps))
if not output.is_file():
raise RuntimeError(f"Wan 2.2 completed but did not write {output}.")
log(f"STATUS:Wan 2.2 saved {output.name} ({len(frames)} frames)")


if __name__ == "__main__":
main()
13 changes: 13 additions & 0 deletions scripts/requirements-ltx25-cuda.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# LTX-2.5 CUDA runtime for ~/.portos/ltx-2.5-cuda/.venv.
# Torch, torchaudio and torchvision are installed separately as a matched stack
# from the host-appropriate CUDA index.

ltx-core @ git+https://github.com/Lightricks/LTX-2.git@v1.2.0#subdirectory=packages/ltx-core
ltx-pipelines @ git+https://github.com/Lightricks/LTX-2.git@v1.2.0#subdirectory=packages/ltx-pipelines
transformers>=5.14.1,<5.15
accelerate>=1.14.0
huggingface_hub>=1.27.0
imageio>=2.37.2
imageio-ffmpeg>=0.6.0
pillow>=12.3.0
av>=18.1.0
11 changes: 11 additions & 0 deletions scripts/requirements-wan22-cuda.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
diffusers==0.36.0
transformers>=4.49,<5
accelerate>=1.2,<2
huggingface-hub>=0.27,<2
hf-xet>=1.1,<2
sentencepiece>=0.2,<1
ftfy>=6.3,<7
imageio>=2.36,<3
imageio-ffmpeg>=0.5,<1
av>=14,<17
Pillow>=10,<13
Loading