LTX-2.5 text-to-video locally on Apple Silicon in one command — with audio, no ComfyUI/CUDA. 16x faster than PyTorch-on-MPS (53s vs 853s) via macOS 26 watchdog + eval-guard fixes.
LTX-2.5 on Apple Silicon, one command
Generate video — with audio — locally on your Mac. No ComfyUI, no CUDA. The first run bootstraps everything itself: the MLX runtime, the Python env, and a one-time ~36 GiB weight download.
curl -fsSL https://gist.githubusercontent.com/DanielHauschildt/9ad1d7cef482ed508a121881280b74dd/raw/generate -o generate chmod +x generate ./generate "A wide shot frames a rainy city intersection at dusk, neon signs reflecting on wet asphalt. A young woman in a yellow raincoat walks toward camera, gripping a folded newspaper, while cars hiss past behind her. Soft synth music and distant traffic fill the air. A hard cut transitions to a medium close-up of her face under the hood, raindrops catching the neon as she looks off-screen left; the synth score continues across the cut, traffic muffled. She whispers, “He's late.” Another hard cut jumps to a low-angle shot of a man's scuffed boots stepping into a puddle at the curb; the music drops to a low drone. He lifts his head into frame — short dark hair, soaked jacket — and smiles toward her off-screen as a bus rumbles past." \ --width 768 --height 512 --frames 241 --output rainy_intersection.mp4
LTX-2.5 does multi-shot scenes natively — describe the shots in order and quoted dialogue becomes speech. Give them room: 241 frames ≈ 10 s fits three shots.
Needs: Apple Silicon Mac with 32 GB+ RAM (24 GB works with --low-ram) · brew install uv ffmpeg · ~40 GiB disk.
Rules: width/height divisible by 32 · frames = 8n+1 (33, 65, 97, 121, …) · extra args pass through to the ltx-2-mlx CLI.
Speed (M3 Max, 64 GB): 1.4 s clip in ~1 min · 5 s 768×512 clip in ~5–8 min. That's ~16× faster than PyTorch-on-MPS — two non-obvious fixes are baked into the script, documented in its comments.
Credits
Lightricks LTX-2.5 (weights, LTX-2.x Community License — free for commercial use under $10M ARR) · dgrauet/ltx-2-mlx (MLX runtime) · MrMofer's LTX-2.5 port + q8 conversion. No weights are redistributed here; the script downloads them from HuggingFace under their own license.
| #!/bin/zsh | |
| # LTX-2.5 text-to-video on Apple Silicon — clone-and-run launcher. | |
| # | |
| # First run bootstraps everything: clones the pinned MLX runtime, lets uv | |
| # create the Python env, and downloads the ~36 GiB q8 weight subset from | |
| # HuggingFace (ungated, no login). Subsequent runs go straight to generation. | |
| # | |
| # Usage: | |
| # ./generate "A sailboat crossing a calm sea at golden hour" | |
| # ./generate "prompt" --width 768 --height 512 --frames 121 --output clip.mp4 | |
| # | |
| # Constraints: width/height divisible by 32, frames = 8n+1 (33, 65, 97, 121...). | |
| set -euo pipefail | |
| ROOT="$(cd "$(dirname "$0")" && pwd)" | |
| RUNTIME="$ROOT/.runtime" | |
| WEIGHTS="$ROOT/models/ltx-2.5-mlx-q8" | |
| PROMPT="${1:?usage: generate \"prompt\" [ltx-2-mlx args...]}" | |
| shift | |
| # The MLX runtime with LTX-2.5 support, pinned to a validated commit. | |
| RUNTIME_REPO="https://github.com/MrMoferFRAN/ltx-2-mlx.git" | |
| RUNTIME_COMMIT="57952288076766abe27dda3a774b2c24f7346977" | |
| WEIGHTS_REPO="MrMofer/ltx-2.5-mlx-q8" | |
| for tool in git uv ffmpeg; do | |
| command -v "$tool" >/dev/null || { echo "error: '$tool' is required — install it with: brew install $tool" >&2; exit 1; } | |
| done | |
| if [[ ! -d "$RUNTIME" ]]; then | |
| echo "[bootstrap] Cloning MLX runtime (pinned $RUNTIME_COMMIT) ..." | |
| git clone --quiet "$RUNTIME_REPO" "$RUNTIME" | |
| git -C "$RUNTIME" checkout --quiet "$RUNTIME_COMMIT" | |
| fi | |
| if [[ ! -f "$WEIGHTS/transformer-distilled.safetensors" ]]; then | |
| echo "[bootstrap] Downloading LTX-2.5 q8 weights (~36 GiB, one time) ..." | |
| echo " License: LTX-2.x Community License — https://huggingface.co/$WEIGHTS_REPO" | |
| uv run --project "$RUNTIME" python - "$WEIGHTS_REPO" "$WEIGHTS" <<'PY' | |
| import sys | |
| from huggingface_hub import snapshot_download | |
| repo, dest = sys.argv[1], sys.argv[2] | |
| # Distilled-pipeline subset only: skips the dev transformer and the stage-2 | |
| # LoRA (needed only for CFG modes), saving ~27 GiB. | |
| snapshot_download( | |
| repo, | |
| local_dir=dest, | |
| allow_patterns=[ | |
| "transformer-distilled.safetensors", | |
| "connector.safetensors", | |
| "text_encoder/*", | |
| "vae_encoder.safetensors", | |
| "vae_decoder.safetensors", | |
| "audio_vae.safetensors", | |
| "vocoder.safetensors", | |
| "spatial_upscaler_x2.safetensors", | |
| "temporal_upscaler_x2.safetensors", | |
| "duration_head.safetensors", | |
| "*.json", | |
| "LICENSE.md", | |
| ], | |
| ) | |
| PY | |
| fi | |
| # AGX_RELAX_CDM_CTXSTORE_TIMEOUT works around the macOS 26 + MLX 0.31 GPU | |
| # watchdog stall (dgrauet/ltx-2-mlx#75); the per-step cost is otherwise | |
| # 25-130x higher. With the watchdog relaxed, the runtime's per-block eval | |
| # guards are redundant on >=32 GB machines, so they are disabled too. | |
| exec env \ | |
| AGX_RELAX_CDM_CTXSTORE_TIMEOUT=1 \ | |
| LTX2_DIT_EVAL_EVERY=0 \ | |
| LTX2_GEMMA_EVAL_EVERY=0 \ | |
| uv run --project "$RUNTIME" ltx-2-mlx generate \ | |
| --model "$WEIGHTS" \ | |
| --distilled --frame-rate 24 \ | |
| --prompt "$PROMPT" \ | |
| "$@" |