Instructions to use suryatmodulus/PrunaVAED with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use suryatmodulus/PrunaVAED with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("suryatmodulus/PrunaVAED", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - LTX.io
How to use suryatmodulus/PrunaVAED with LTX.io:
# Install the LTX-2 pipelines git clone https://github.com/Lightricks/LTX-2.git cd LTX-2 uv sync --frozen
# Download the weights from this repo, plus the Gemma text encoder hf download suryatmodulus/PrunaVAED --local-dir models/PrunaVAED hf download google/gemma-3-12b-it-qat-q4_0-unquantized --local-dir models/gemma-3-12b
# Fast pipeline (distilled model, no distilled LoRA needed) uv run python -m ltx_pipelines.distilled \ --distilled-checkpoint-path models/PrunaVAED/<distilled-checkpoint>.safetensors \ --spatial-upsampler-path models/PrunaVAED/<spatial-upsampler>.safetensors \ --gemma-root models/gemma-3-12b \ --prompt "A beautiful sunset over the ocean" \ --output-path output.mp4 # For image-to-video, add: --image path/to/image.jpg 0 0.8# HQ pipeline (two-stage, higher quality) uv run python -m ltx_pipelines.ti2vid_two_stages_hq \ --checkpoint-path models/PrunaVAED/<checkpoint>.safetensors \ --distilled-lora models/PrunaVAED/<distilled-lora>.safetensors 0.8 \ --spatial-upsampler-path models/PrunaVAED/<spatial-upsampler>.safetensors \ --gemma-root models/gemma-3-12b \ --prompt "A beautiful sunset over the ocean" \ --output-path output.mp4 # For image-to-video, add: --image path/to/image.jpg 0 0.8 - Notebooks
- Google Colab
- Kaggle
File size: 8,461 Bytes
93293db | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | #!/usr/bin/env python3
"""Quick demo: generate a short LTX-2.3 video latent, then decode it twice.
Compares the stock LTX-2.3 VAE decoder with this repo's pruned decoder
(PrunaVAED) on the *same* latent. Prints decode time (ms) and peak VRAM (GiB),
and writes two mp4s.
Requires a CUDA GPU. From the repo root:
pip install -r requirements-test.txt
python demo/demo_distilled_decode.py
"""
from __future__ import annotations
import statistics
import sys
import time
from pathlib import Path
import imageio.v3 as iio
import torch
from diffusers import LTX2LatentUpsamplePipeline, LTX2Pipeline
from diffusers.models.autoencoders import AutoencoderKLLTX2Video
from diffusers.pipelines.ltx2.latent_upsampler import LTX2LatentUpsamplerModel
from diffusers.pipelines.ltx2.utils import (
DEFAULT_NEGATIVE_PROMPT,
DISTILLED_SIGMA_VALUES,
STAGE_2_DISTILLED_SIGMA_VALUES,
)
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT))
from patch_diffusers import patch_pruna_ltx2_decoder # noqa: E402
torch.backends.cuda.enable_cudnn_sdp(False)
# ---------------------------------------------------------------------------
# Settings (edit these if you want)
# ---------------------------------------------------------------------------
# Official Diffusers checkpoints for the 2-stage distilled recipe.
DISTILLED_MODEL = "diffusers/LTX-2.3-Distilled-Diffusers"
SPATIAL_UPSAMPLER = "dg845/LTX-2.3-Spatial-Upsampler-Diffusers"
LTX23_VAE = "diffusers/LTX-2.3-Diffusers" # stock decoder (baseline)
PRUNED_VAE = str(REPO_ROOT) # this repo's vae/ folder
# ~1080p for 5 s @ 24 fps. Height/width must be multiples of 64 (2-stage).
HEIGHT, WIDTH, NUM_FRAMES, FPS = 1088, 1920, 121, 24.0
SEED = 42
DECODE_WARMUP, DECODE_RUNS = 1, 3 # timing: 1 warm-up + median of 3 runs
PROMPT = (
"The video shows a hockey player in a green jersey and blue helmet skating on the ice with a hockey stick. The player is seen moving around the ice, passing the puck to another player who is also wearing a green jersey and blue helmet. The player in the green jersey is seen skating away from the camera, and then turning around to face the camera. The ice rink is surrounded by boards with advertisements, and there are other players in the background. The player in the green jersey is wearing black gloves and black skates. The player in the green jersey is also seen skating towards the camera and then away from the camera again."
)
DEVICE = "cuda"
DTYPE = torch.bfloat16
OUTPUT_DIR = REPO_ROOT / "outputs" / "demo_distilled"
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def save_mp4(video: torch.Tensor, path: Path, fps: float) -> None:
"""Save a BCTHW tensor in [-1, 1] as an H.264 mp4."""
frames = video[0].permute(1, 2, 3, 0).clamp(-1, 1).float()
frames = ((frames + 1) / 2 * 255).round().byte().cpu().numpy()
path.parent.mkdir(parents=True, exist_ok=True)
iio.imwrite(path, frames, fps=fps, codec="libx264")
def load_vae(model_id: str) -> AutoencoderKLLTX2Video:
"""Load a video VAE decoder on GPU, tiling off so we time full-frame decodes."""
vae = AutoencoderKLLTX2Video.from_pretrained(
model_id, subfolder="vae", torch_dtype=DTYPE
)
vae.disable_tiling()
return vae.to(DEVICE).eval()
@torch.inference_mode()
def timed_decode(
vae: AutoencoderKLLTX2Video, latent: torch.Tensor
) -> tuple[torch.Tensor, float, float]:
"""Decode DECODE_RUNS times after warm-up.
Returns the video on CPU, the median latency in ms and the peak VRAM in GiB.
Deliberately no autocast: weights and latent are already bfloat16, and autocast
would run every PerChannelRMSNorm (``x**2``) in float32, costing time and VRAM.
"""
latent = latent.to(DEVICE, DTYPE)
for _ in range(DECODE_WARMUP):
vae.decode(latent, return_dict=False)
torch.cuda.synchronize()
torch.cuda.reset_peak_memory_stats()
times_ms = []
for _ in range(DECODE_RUNS):
video = None # keep the previous output out of the peak VRAM measurement
torch.cuda.synchronize()
t0 = time.perf_counter()
video = vae.decode(latent, return_dict=False)[0]
torch.cuda.synchronize()
times_ms.append((time.perf_counter() - t0) * 1000)
peak_gib = torch.cuda.max_memory_allocated() / 2**30
return video.cpu(), statistics.median(times_ms), peak_gib
@torch.inference_mode()
def generate_latent(prompt: str) -> torch.Tensor:
"""Run the official 2-stage distilled pipeline and return the final video latent.
Same layout as Lightricks DistilledPipeline:
stage 1 @ half-res → 2× latent upsample → stage 2 @ full-res.
"""
half_h, half_w = HEIGHT // 2, WIDTH // 2
generator = torch.Generator(DEVICE).manual_seed(SEED)
pipe = LTX2Pipeline.from_pretrained(DISTILLED_MODEL, torch_dtype=DTYPE)
pipe.enable_model_cpu_offload(device=DEVICE)
# Stage 1 — cheap draft at half resolution.
print(f"1/3 Stage 1 @ {half_w}×{half_h}")
video_latent, audio_latent = pipe(
prompt=prompt,
negative_prompt=DEFAULT_NEGATIVE_PROMPT,
height=half_h,
width=half_w,
num_frames=NUM_FRAMES,
frame_rate=FPS,
num_inference_steps=len(DISTILLED_SIGMA_VALUES),
sigmas=DISTILLED_SIGMA_VALUES,
guidance_scale=1.0,
generator=generator,
output_type="latent",
return_dict=False,
)
# Upsample — bring the latent to full resolution before refining.
print(f"2/3 Upsample → {WIDTH}×{HEIGHT}")
upsampler = LTX2LatentUpsamplerModel.from_pretrained(
SPATIAL_UPSAMPLER, subfolder="latent_upsampler", torch_dtype=DTYPE
)
upsample_pipe = LTX2LatentUpsamplePipeline(vae=pipe.vae, latent_upsampler=upsampler)
upsample_pipe.enable_model_cpu_offload(device=DEVICE)
video_latent = upsample_pipe(
latents=video_latent[:1],
height=half_h,
width=half_w,
num_frames=NUM_FRAMES,
output_type="latent",
return_dict=False,
)[0]
del upsample_pipe, upsampler
torch.cuda.empty_cache()
# Stage 2 — refine at full resolution (renoises from STAGE_2 schedule).
print(f"3/3 Stage 2 @ {WIDTH}×{HEIGHT}")
video_latent, _ = pipe(
latents=video_latent,
audio_latents=audio_latent,
prompt=prompt,
negative_prompt=DEFAULT_NEGATIVE_PROMPT,
height=HEIGHT,
width=WIDTH,
num_frames=NUM_FRAMES,
frame_rate=FPS,
num_inference_steps=len(STAGE_2_DISTILLED_SIGMA_VALUES),
noise_scale=STAGE_2_DISTILLED_SIGMA_VALUES[0],
sigmas=STAGE_2_DISTILLED_SIGMA_VALUES,
guidance_scale=1.0,
generator=generator,
output_type="latent",
return_dict=False,
)
latent = video_latent.detach().cpu()
del pipe
torch.cuda.empty_cache()
return latent
def compare_decoders(latent: torch.Tensor, out_dir: Path) -> None:
"""Decode the same latent with LTX-2.3 and PrunaVAED; print ms/VRAM and save mp4s."""
# Needed so Diffusers can build the pruned decoder graph correctly.
patch_pruna_ltx2_decoder()
results = {}
for name, model_id in (("ltx23", LTX23_VAE), ("prunavaed", PRUNED_VAE)):
print(f"Decoding with {name} …")
vae = load_vae(model_id)
video, ms, peak_gib = timed_decode(vae, latent)
results[name] = (ms, peak_gib)
path = out_dir / f"{name}.mp4"
save_mp4(video, path, FPS)
print(f" {name}: {ms:.1f} ms · {peak_gib:.2f} GiB peak → {path}")
del vae, video
torch.cuda.empty_cache()
(ltx_ms, ltx_gib), (pruna_ms, pruna_gib) = results["ltx23"], results["prunavaed"]
print(f"Speedup: {ltx_ms / pruna_ms:.2f}× Peak VRAM: {pruna_gib / ltx_gib:.0%} of LTX-2.3")
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
def main() -> None:
if not torch.cuda.is_available():
raise SystemExit("This demo needs a CUDA GPU.")
print(f"Prompt: {PROMPT[:80]}…")
print(f"Output: {OUTPUT_DIR}")
latent = generate_latent(PROMPT)
print(f"Latent shape: {tuple(latent.shape)}")
compare_decoders(latent, OUTPUT_DIR)
if __name__ == "__main__":
main()
|