# 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.

> Source: <https://gist.github.com/DanielHauschildt/9ad1d7cef482ed508a121881280b74dd>
> Published: 2026-08-14 17:26:37+00:00

|
#!/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" \ |
|
"$@" |
