File size: 9,290 Bytes
af91a6b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
851cff7
 
 
 
 
 
 
 
 
af91a6b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
851cff7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
af91a6b
 
 
 
 
 
 
 
 
 
 
 
851cff7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
af91a6b
 
 
 
 
 
 
 
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
---
license: other
license_name: ltx-2-community-license-agreement
license_link: LICENSE.md
base_model: Lightricks/LTX-2.5
tags:
- mlx
- ltx-video
- ltx-2.5
- text-to-video
- image-to-video
- audio-video
pipeline_tag: text-to-video
---

# LTX-2.5 — MLX conversion (bf16, split components)

MLX-format conversion of [Lightricks LTX-2.5](https://huggingface.co/Lightricks/LTX-2.5)
(joint audio+video DiT, 22B) for Apple Silicon, in the per-component split layout consumed by
[`ltx-2-mlx`](https://github.com/xocialize/ltx-2-mlx) (branch `ltx-2.5`).

> **Status (2026-08-13): the port is COMPLETE on both consumers.** Python-MLX
> ([`ltx-2-mlx`](https://github.com/xocialize/ltx-2-mlx), branch `ltx-2.5`) and Swift-MLX
> ([`ltx-2-mlx-swift`](https://github.com/xocialize/ltx-2-mlx-swift)) both generate end to end,
> including the DFR pipeline with temporal rounds. Parity-gated per component against the
> PyTorch reference: text encoder 49 states (mean cosine 0.999985), DiT forward, sampler,
> keyframe slots, and the DFR canvas geometry bit-exactly.
>
> Still a **research port** — it is not a supported product, and see *Memory* below before you
> plan a run.

## ⚠️ License — read before use

These weights are **Derivatives of LTX-2.5** and are distributed under the
**LTX-2.x Community License Agreement** (license date 2026-08-11). A complete copy ships in
this repo as [`LICENSE.md`](LICENSE.md), and the Acceptable Use Policy it incorporates by
reference is snapshotted here as
[`ltx-acceptable-use-policy-snapshot-2026-08-12.pdf`](ltx-acceptable-use-policy-snapshot-2026-08-12.pdf)
(the version in effect at your time of use governs — check
[Lightricks' current AUP](https://static.lightricks.com/legal/ltx-acceptable-use-policy.pdf)).

**Transfer notice (Agreement §3.5).** Your use of these weights is subject to the LTX-2.x
Community License Agreement. If you (aggregated across entities under common control) have
annual revenues of **US $10,000,000 or more**, you are a "Commercial Entity" under the
Agreement and **must obtain a paid license from Lightricks before any use** other than the
Agreement's non-commercial-purpose carve-outs (testing, evaluation, non-commercial R&D in
non-production environments). Sub-threshold commercial and production use is royalty-free
under the Agreement's terms.

Further obligations that travel with these weights include (not exhaustive — read the
license): machine-generated content disclosure (Attachment A §5), no removal or circumvention
of any transparency/provenance features (§6), EU AI Act / CA AI Transparency Act
responsibilities for providers/deployers (§6), and the Attachment A acceptable-use terms.

## Modification notice (Agreement §3.3)

Every tensor file here is **modified from the original Lightricks release**: re-serialized to
MLX conventions (channels-last conv layouts, component-split files, renamed keys per the
`ltx-2-mlx` dialect, fused projections split). No weights were trained, fine-tuned, or
numerically altered beyond layout/serialization transforms. Conversion tooling:
[`scripts/convert_ltx25.py`](https://github.com/xocialize/ltx-2-mlx/blob/ltx-2.5/scripts/convert_ltx25.py).

## Components

| File | Contents | Params |
|---|---|---|
| `transformer-distilled.safetensors` | distilled joint-AV DiT (fixed 8-step, CFG=1) | 22B |
| `transformer-dev.safetensors` | dev (full) joint-AV DiT | 22B |
| `gemma4-12b-ltx-v1/` | Lightricks-tuned Gemma-4-unified text encoder, HF layout (loads via `mlx-lm`) | 12B |
| `connector.safetensors` | text-embeddings connectors + aggregate projections | — |
| `vae_encoder.safetensors` / `vae_decoder.safetensors` | conv video VAE (byte-identical to LTX-2.3's) | 726M |
| `vae_diffusion_decoder.safetensors` | DiffVAE 1-step x0 video decoder (NA attention) | 417M |
| `audio_vae.safetensors` / `vocoder.safetensors` | audio VAE + BigVGAN v2 + BWE (byte-identical to 2.3's) | 182M |
| `spatial_upscaler_x2_v1_1.safetensors` | ×2 spatial latent upscaler (byte-identical to 2.3's) | 498M |
| `temporal_upscaler_x2_v1_0.safetensors` | ×2 temporal latent upscaler (byte-identical to 2.3's) | 131M |
| `duration_head.safetensors` | prompt→duration predictor (fused MHA split to q/k/v) | 1.9M |
| `config.json` / `embedded_config.json` | pipeline + transformer configs | — |

## Conversion receipts

- Components LTX-2.5 re-ships byte-identical to LTX-2.3 (conv VAE, audio stack, both
  upscalers) were converted from the 2.5 sources and verified **bit-identical** to the
  established `mlx-forge` 2.3 conversions — validating this converter's conv/layout/rename
  handling against independent tooling.
- DiT template enforced at conversion: 4091 tensors per variant = the 2.3 key template
  − 96 video-FF biases (`ff_bias: false`) + `keyframes_abs_pos_embedding`.
- Text-encoder parity vs the PyTorch reference (transformers `Gemma4Unified`): tokenization
  identical; all 49 tapped hidden states ≥ 0.9997 cosine; pre-connector projections ≥ 0.99996.
- The embedded LTX-2.x license text present in upstream file metadata is preserved.

## Memory — read this before planning a run

Measured on Apple Silicon (128 GB unified), 448×320×9 frames, bf16, via the Swift consumer.
Your numbers will differ with resolution and frame count; the *structure* is what transfers.

| | peak |
|---|---|
| default (text encoder co-resident with the DiT) | **62.40 GB** |
| with the DiT evicted around the encode phase | **40.66 GB** |

**The text encoder is the surprise.** `gemma4-12b-ltx-v1/` is an unquantized **bf16 12B —
23.8 GB on disk, ~24.4 GB resident**. LTX-2.3 used a 4-bit Gemma-3 (~7 GB), so anything you
carry over from a 2.3 setup will badly under-estimate 2.5. The DiT itself is a 37.98 GB
resident floor at bf16.

Two levers, and they stack:

1. **Evict the DiT around the phases that don't need it.** −21.74 GB (−34.8%) with output
   **bit-identical** and wall-clock within run-to-run noise. This is a scheduling change, not
   a quality trade — it is on by default in the Swift consumer.
2. **Quantize the text encoder to int8** (group 64, keeping `embed_tokens` in bf16): encoder
   24.42 → 14.20 GB, end-to-end 62.40 → 52.18 GB. We measured this as **numerically faithful**
   (valid-token cosine 0.999820 against a 0.999879 bf16 floor) and **perceptually neutral** in a
   blinded 6-pair operator A/B (3 ties, 2–1, all "very close").
   ⚠️ **int4 was REJECTED** — 0.996728 on the same metric, consistent with an independent
   in-fleet measurement of a different frozen encoder. Note that several third-party MLX packs
   ship this encoder at 4-bit with no published quality data.
   **No quantized sibling is published here**; the recipe is in the consuming repo.

⚠️ **If you quantize it yourself:** the default `mlx_lm.convert -q` also quantizes
`embed_tokens`, which is hidden state 00 *and* the input to all 48 layers — exclude it. The
`mixed_*` recipes are worse: with tied embeddings they put the embedding table at 3 bits.

## Usage

```bash
git clone -b ltx-2.5 https://github.com/xocialize/ltx-2-mlx
cd ltx-2-mlx && uv sync
uv run ltx-2-mlx generate \
  --model mlx-community/ltx-2.5-mlx \
  --distilled \
  --prompt "a red fox standing in deep snow, closeup wildlife photography, golden hour" \
  -H 512 -W 768 -f 121 --frame-rate 24 -o fox.mp4
```

**Multishot (LTX-2.5 generated keyframe slots)**`--num-generated-keyframes N` places N
invented keyframes at evenly spaced interior positions. Note what this does and does not do:
it relaxes the effective temporal compression at those positions (each slot costs a full
latent frame of tokens to buy one pixel frame). In our measurement it did **not**, on its own,
turn a shot-listed prompt into multiple cut shots — that is the DFR pipeline's job.

**DFR (diffusion fidelity rendering)** is implemented in both consumers. Its *spatial*
detailing pass was preferred by an operator on 4 of 4 matched pairs for roughly ×1.2 time.
Its *temporal rounds* are a parity/quality feature, **not** a speed one: they cost ×2.767
(1 round) / ×3.542 (2 rounds), and generating the same deliverable natively at the target
frame rate was both ~2.8× cheaper and preferred. Use rounds when you want the temporal
density, not to save time.

### Swift

```swift
// https://github.com/xocialize/ltx-2-mlx-swift — MLX-Swift consumer.
// 2.5 is detected from the CHECKPOINT (the in-dir gemma4-12b-ltx-v1/), never a path name,
// so a renamed or relocated copy still resolves correctly.
let pipeline = try await LTX2Pipeline.load(ltxDir: modelDir, gemmaDir: gemma4Dir)
let out = try await pipeline.t2vTwoStage(prompt: prompt, height: 320, width: 448,
                                         numFrames: 25, fps: 24, seed: 4242)
```

## Provenance

Converted from [`Lightricks/LTX-2.5`](https://huggingface.co/Lightricks/LTX-2.5) (comfy split
pack; connectors sourced from the transformer-file bundle, which is what the reference
runtime loads) and [`Lightricks/LTX-2.5-Diffusers`](https://huggingface.co/Lightricks/LTX-2.5-Diffusers)
(diffusion decoder). All credit for the models to Lightricks — see the
[LTX-2 reference implementation](https://github.com/Lightricks/LTX-2) and the
[LTX-2.5 announcement](https://huggingface.co/Lightricks/LTX-2.5).