Skip to content
Open
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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ Three ~5s segments, chained and merged:
- **RAM:** longer clips / higher resolution need more unified memory; q8/q4 weights help on smaller Macs.
- **No GPT rewrite** on this MLX server—optimize prompts yourself.
- **ltx-2-mlx** version pinned in repo (see `requirements.txt` / `ltx_mlx_backend.py`); install matching MLX packages after pulls.
- **Training (optional):** Web UI `/train` — install `ltx-trainer-mlx` (see `README.md` / `TRAIN.md`). Do not run training and generation concurrently (MLX lock). Prefer `ltx_generate_*` for inference after registering a trained LoRA.
- **Weights:** **MLX only** — `dgrauet/ltx-2.3-mlx*` repos or local MLX snapshots; **never** `Lightricks/LTX-2.3` or other standard LTX checkpoints (see [MLX model weights only](#mlx-model-weights-only-mandatory)).

---
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Generate text-to-video, image-to-video, audio-to-video, retake, extend, and mult
| **CLI client** | Scriptable batch runs, autocontinue, PyAV merge (`autoconcat`) |
| **MCP tools** | Drive generation from Cursor, Claude, or other MCP clients |
| **LoRA** | Per-request or server-wide style adapters (optional) |
| **LoRA training** | `/train` wizard — T2V, AV, and IC-LoRA presets (optional `ltx-trainer-mlx`; see below) |

**Agent docs:** [`DIRECTOR.md`](DIRECTOR.md) (prompting & shot planning), [`AGENTS.md`](AGENTS.md) (MCP & pipelines), [`CLAUDE.md`](CLAUDE.md) (pointer to agent guides).

Expand Down Expand Up @@ -57,6 +58,17 @@ For gated Hugging Face models, set [`HF_TOKEN`](https://huggingface.co/docs/hugg

Classic **venv + pip**: use `python3.12 -m venv .venv`, `pip install -r requirements.txt`, and the same two `ltx-2-mlx` package lines above.

### Optional: LoRA training (`/train`)

Training is **not** required for generation. To use the **Train LoRA** tab in the Web UI:

```bash
uv pip install \
"ltx-trainer-mlx @ git+https://github.com/dgrauet/ltx-2-mlx.git@v0.14.12#subdirectory=packages/ltx-trainer"
```

Then rebuild the Web UI if needed (`cd web && npm run build`), start `python server.py --web-ui` (or embedded `--web-ui`), and open **Train LoRA** in the header. Jobs and artifacts live under `web_outputs/train/<job_id>/`. See [`TRAIN.md`](TRAIN.md) for presets, IC-LoRA pairing, and the Phase 5 backlog (checkpoint resume, W&B, MCP).

---

## Quick start
Expand Down
572 changes: 572 additions & 0 deletions TRAIN.md

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions ltx_mlx_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,50 @@ def _model_snapshot_present(dest: Path) -> bool:
return bool(has_config and has_weights)


def _hf_hub_cache_roots() -> list[Path]:
"""Candidate Hugging Face hub cache roots (``HF_HOME``, ``HUGGINGFACE_HUB_CACHE``, defaults)."""
candidates: list[Path] = []
hub_cache = os.environ.get("HUGGINGFACE_HUB_CACHE", "").strip()
if hub_cache:
candidates.append(Path(hub_cache).expanduser().resolve())
hf_home = os.environ.get("HF_HOME", "").strip()
if hf_home:
candidates.append(Path(hf_home).expanduser().resolve())
xdg = os.environ.get("XDG_CACHE_HOME", "").strip()
if xdg:
candidates.append((Path(xdg).expanduser() / "huggingface").resolve())
candidates.append((Path.home() / ".cache" / "huggingface").resolve())
seen: set[Path] = set()
unique: list[Path] = []
for path in candidates:
if path not in seen:
seen.add(path)
unique.append(path)
return unique


def find_hf_hub_snapshot(repo_id: str) -> Path | None:
"""Return the newest materialized weights tree under the HF hub cache, if any."""
slug = repo_id.strip().replace("/", "--")
best: Path | None = None
best_mtime = -1.0
for cache_root in _hf_hub_cache_roots():
snaps_dir = cache_root / "hub" / f"models--{slug}" / "snapshots"
if not snaps_dir.is_dir():
continue
try:
for snap in snaps_dir.iterdir():
if not snap.is_dir() or not _model_snapshot_present(snap):
continue
mtime = snap.stat().st_mtime
if mtime >= best_mtime:
best_mtime = mtime
best = snap.resolve()
except OSError:
continue
return best


def hf_local_weights_directory(repo_id: str, explicit_model_dir: str | None) -> Path:
"""
Directory where we store a full ``snapshot_download`` for ``repo_id``.
Expand Down Expand Up @@ -426,6 +470,10 @@ def resolve_mlx_weights_directory(model: str, explicit_model_dir: str | None) ->
"Install with: pip install huggingface_hub\n"
"Or use a local directory for --model."
) from e
hub_snap = find_hf_hub_snapshot(raw)
if hub_snap is not None:
log.info("Using Hugging Face hub cache snapshot for %r at %s", raw, hub_snap)
return str(hub_snap)
dest = hf_local_weights_directory(raw, explicit_model_dir)
dest.mkdir(parents=True, exist_ok=True)
if _model_snapshot_present(dest):
Expand Down
Loading