Text-to-Video
MLX
Safetensors
lance
multimodal
apple-silicon
text-to-image
image-generation
video-generation
diffusion
flow-matching
Mixture of Experts
qwen2_5_vl
wan
port
Instructions to use RockTalk/Lance-3B-Video-MLX with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use RockTalk/Lance-3B-Video-MLX with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] huggingface-cli download --local-dir Lance-3B-Video-MLX RockTalk/Lance-3B-Video-MLX
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
Update README: remove broken github link, point to bundled code
Browse files
README.md
CHANGED
|
@@ -22,10 +22,87 @@ tags:
|
|
| 22 |
|
| 23 |
# Lance-3B-Video-MLX
|
| 24 |
|
| 25 |
-
Video variant of [Lance-3B-MLX](https://huggingface.co/RockTalk/Lance-3B-MLX). First native [MLX](https://github.com/ml-explore/mlx) port of [ByteDance Research's Lance](https://huggingface.co/bytedance-research/Lance) β a
|
| 26 |
|
| 27 |
The architecture is **Qwen2.5-VL-3B + parallel MoE-gen experts + Wan 2.2 VAE**. Lance uses a "Mixture-of-Tokens" routing: every attention block and MLP has a parallel `*_moe_gen` branch. Text tokens go through normal weights; VAE-latent (generation) tokens go through the `_moe_gen` weights, in the same forward pass.
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
## What works
|
| 30 |
|
| 31 |
| Capability | Status |
|
|
@@ -70,64 +147,25 @@ First-call kernel-compile penalty: ~few seconds per new resolution.
|
|
| 70 |
## Differences vs Lance-3B-MLX
|
| 71 |
|
| 72 |
This is the same architecture as the image variant, with two differences:
|
|
|
|
| 73 |
- `model.safetensors`: 26.5 GB (vs 23 GB) β extra weights for multi-frame attention
|
| 74 |
- `latent_pos_embed.pos_embed`: 31 Γ 64 Γ 64 = 126,976 positions (vs 1 Γ 64 Γ 64 = 4,096) β supports up to 31 latent frames (β 121 video frames @ 4Γ temporal downsample)
|
| 75 |
|
| 76 |
T2I via this checkpoint works the same as Lance-3B-MLX. **T2V is now live** β uses the [Wan 2.2 VAE v0.1.0 streaming cache](https://huggingface.co/RockTalk/Wan2.2-VAE-MLX) under the hood. Pass `latent_shape=(T_lat, H_lat, W_lat)` with `T_lat > 1` to `sample_t2i` to generate a video.
|
| 77 |
|
| 78 |
-
T_lat β output frames: T = (T_lat - 1) Γ 4 + 1.
|
| 79 |
-
- T_lat=1 β 1 frame (image)
|
| 80 |
-
- T_lat=3 β 9 frames
|
| 81 |
-
- T_lat=8 β 29 frames
|
| 82 |
-
- T_lat=31 β 121 frames (max for this checkpoint)
|
| 83 |
-
|
| 84 |
## Files
|
| 85 |
|
| 86 |
| File | Size | Description |
|
| 87 |
|---|---|---|
|
| 88 |
| `model.safetensors` | 26.5 GB | LLM (Qwen2.5-VL with MoE-gen) + Lance adapters + bundled ViT (`vit_model.*` prefix), 1411 tensors total |
|
| 89 |
| `vit.safetensors` | 1.25 GB | Qwen2.5-VL ViT, also extractable from `model.safetensors` |
|
| 90 |
-
| `vae.safetensors` | 2.62 GB | Wan 2.2 VAE (older keying
|
| 91 |
| `config.json` | β | Distilled architecture config |
|
| 92 |
| `tokenizer.json`, `vocab.json`, `merges.txt` | β | Qwen2.5-VL tokenizer, verbatim |
|
| 93 |
| `samples/ocean_wave_*.png` | β | Verified 9-frame T2V outputs |
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
Requires `mlx >= 0.29`, `mlx-vlm >= 0.3`, `numpy`, `einops`, `transformers`, `pillow`, and the [`lance-mlx`](https://github.com/RockTalk/Lance-MLX) companion repo for the `Lance` Python class.
|
| 98 |
-
|
| 99 |
-
```bash
|
| 100 |
-
pip install mlx mlx-vlm numpy einops transformers pillow
|
| 101 |
-
```
|
| 102 |
-
|
| 103 |
-
```python
|
| 104 |
-
import mlx.core as mx
|
| 105 |
-
from lance_mlx.lance import Lance, LanceConfig
|
| 106 |
-
from lance_mlx.vae_wan22 import Wan2_2_VAE
|
| 107 |
-
|
| 108 |
-
# Build + strict-load (see tools/lance_t2i.py in the companion repo for the
|
| 109 |
-
# full builder; LanceConfig takes a Qwen2.5-VL ModelConfig built from
|
| 110 |
-
# config.json).
|
| 111 |
-
model = Lance(lance_cfg)
|
| 112 |
-
model.load_weights(list(mx.load("model.safetensors").items()), strict=True)
|
| 113 |
-
|
| 114 |
-
vae = Wan2_2_VAE(z_dim=48, c_dim=160, dim_mult=(1, 2, 4, 4),
|
| 115 |
-
temperal_downsample=(False, True, True))
|
| 116 |
-
vae.model.load_weights(list(mx.load("vae.safetensors").items()), strict=True)
|
| 117 |
-
|
| 118 |
-
# Sample
|
| 119 |
-
latent = model.sample_t2i(
|
| 120 |
-
prompt_token_ids=text_ids, # (P,) int32 from tokenizer (no specials)
|
| 121 |
-
latent_shape=(1, 32, 32), # (T_lat, H_lat, W_lat) for 512Γ512 image
|
| 122 |
-
special_token_ids={"bos": 151644, "eos": 151645,
|
| 123 |
-
"start_of_image": 151652, "end_of_image": 151653,
|
| 124 |
-
"image_token_id": 151655},
|
| 125 |
-
num_steps=30, timestep_shift=3.5, cfg_scale=4.0, seed=0,
|
| 126 |
-
)
|
| 127 |
-
img = vae.decode(latent) # (1, 1, 512, 512, 3) in [-1, 1]
|
| 128 |
-
```
|
| 129 |
-
|
| 130 |
-
End-to-end script: `tools/lance_t2i.py` in the [companion repo](https://github.com/RockTalk/Lance-MLX).
|
| 131 |
|
| 132 |
## How the MoE-gen routing is implemented in MLX
|
| 133 |
|
|
@@ -149,17 +187,21 @@ For T2I/T2V the sequence layout is:
|
|
| 149 |
β everything else: normal weights
|
| 150 |
```
|
| 151 |
|
| 152 |
-
The MLX port (`qwen2_navit_mlx.py`) routes by slicing the sequence into the latent slab vs the surrounding text, applying the appropriate expert to each slab, and concatenating. mrope position ids continue to flow normally across both slabs (with axis-T/H/W coordinates only varying inside the latent slab).
|
| 153 |
|
| 154 |
## Conversion source
|
| 155 |
|
| 156 |
-
Converted from `bytedance-research/Lance/Lance_3B/*` using
|
| 157 |
|
| 158 |
- Conv weights: PT `(O, I, [T,] H, W)` β MLX `(O, [T,] H, W, I)`
|
| 159 |
- Embedding weights: shape preserved
|
| 160 |
- `lm_head.weight` tied to `embed_tokens.weight` (Qwen default)
|
| 161 |
- All `*_moe_gen.*` keys copied verbatim under the same names
|
| 162 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
## License
|
| 164 |
|
| 165 |
Apache 2.0, inherited from upstream `bytedance-research/Lance`. The Wan 2.2 VAE component is also Apache 2.0 from Alibaba's Wan team.
|
|
@@ -170,6 +212,7 @@ Apache 2.0, inherited from upstream `bytedance-research/Lance`. The Wan 2.2 VAE
|
|
| 170 |
- **Qwen team** β Qwen2.5-VL-3B-Instruct backbone
|
| 171 |
- **Alibaba Wan team** β Wan 2.2 VAE training
|
| 172 |
- **Apple `mlx` and `mlx-vlm` teams** β the underlying frameworks
|
|
|
|
| 173 |
- **This MLX port** β RockTalk
|
| 174 |
|
| 175 |
## Citation
|
|
|
|
| 22 |
|
| 23 |
# Lance-3B-Video-MLX
|
| 24 |
|
| 25 |
+
Video variant of [Lance-3B-MLX](https://huggingface.co/RockTalk/Lance-3B-MLX). First native [MLX](https://github.com/ml-explore/mlx) port of [ByteDance Research's Lance](https://huggingface.co/bytedance-research/Lance) β a 3 B-parameter unified multimodal model for image/video generation, editing, and understanding. Runs natively on Apple Silicon, no CUDA required.
|
| 26 |
|
| 27 |
The architecture is **Qwen2.5-VL-3B + parallel MoE-gen experts + Wan 2.2 VAE**. Lance uses a "Mixture-of-Tokens" routing: every attention block and MLP has a parallel `*_moe_gen` branch. Text tokens go through normal weights; VAE-latent (generation) tokens go through the `_moe_gen` weights, in the same forward pass.
|
| 28 |
|
| 29 |
+
## Quick start (self-contained β no external repo needed)
|
| 30 |
+
|
| 31 |
+
```bash
|
| 32 |
+
# 1. Download the model (one-time, ~30 GB total)
|
| 33 |
+
hf download RockTalk/Lance-3B-Video-MLX --local-dir Lance-3B-Video-MLX
|
| 34 |
+
|
| 35 |
+
# 2. Install runtime deps
|
| 36 |
+
cd Lance-3B-Video-MLX
|
| 37 |
+
pip install -r requirements.txt
|
| 38 |
+
|
| 39 |
+
# 3. Generate a 9-frame video (T_lat=3 β 9 output frames)
|
| 40 |
+
python inference.py --prompt "a calm ocean wave rolling onto a sandy beach"
|
| 41 |
+
|
| 42 |
+
# Longer video (29 frames):
|
| 43 |
+
python inference.py --prompt "..." --t-lat 8
|
| 44 |
+
|
| 45 |
+
# Pure image (T_lat=1):
|
| 46 |
+
python inference.py --prompt "..." --t-lat 1 --size 512 --steps 30
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
First run auto-fetches the companion VAE ([`RockTalk/Wan2.2-VAE-MLX`](https://huggingface.co/RockTalk/Wan2.2-VAE-MLX), ~2.6 GB, cached as `wan22_vae.safetensors`) so all subsequent runs are fully offline.
|
| 50 |
+
|
| 51 |
+
### CLI options
|
| 52 |
+
|
| 53 |
+
```
|
| 54 |
+
python inference.py \
|
| 55 |
+
--prompt "..." \
|
| 56 |
+
--out output.png # frame strip + per-frame PNGs saved next to it
|
| 57 |
+
--size 256 # 256 recommended for T2V
|
| 58 |
+
--t-lat 3 # latent frames; output = (t_lat-1)*4 + 1 frames
|
| 59 |
+
--steps 24 # 24 typical for T2V
|
| 60 |
+
--cfg 4.0
|
| 61 |
+
--seed 0
|
| 62 |
+
--mp4 # also emit output.mp4 (needs `pip install 'imageio[ffmpeg]'`)
|
| 63 |
+
--fps 8
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
Frame-count table: `T_lat=1 β 1`, `T_lat=3 β 9`, `T_lat=8 β 29`, `T_lat=31 β 121` (max).
|
| 67 |
+
|
| 68 |
+
### Programmatic use
|
| 69 |
+
|
| 70 |
+
```python
|
| 71 |
+
from inference import build_lance_config, ensure_vae_weights
|
| 72 |
+
from lance_mlx.lance import Lance, LanceConfig
|
| 73 |
+
from lance_mlx.vae_wan22 import Wan2_2_VAE
|
| 74 |
+
from transformers import AutoTokenizer
|
| 75 |
+
import json, mlx.core as mx
|
| 76 |
+
from pathlib import Path
|
| 77 |
+
|
| 78 |
+
repo = Path(".")
|
| 79 |
+
cfg_json = json.loads((repo / "config.json").read_text())
|
| 80 |
+
lance_cfg = build_lance_config(cfg_json)
|
| 81 |
+
|
| 82 |
+
model = Lance(lance_cfg)
|
| 83 |
+
weights = mx.load("model.safetensors")
|
| 84 |
+
non_vit = {k: v for k, v in weights.items() if not k.startswith("vit_model.")}
|
| 85 |
+
model.load_weights(list(non_vit.items()), strict=True)
|
| 86 |
+
|
| 87 |
+
vae = Wan2_2_VAE(z_dim=48, c_dim=160, dim_mult=(1, 2, 4, 4),
|
| 88 |
+
temperal_downsample=(False, True, True))
|
| 89 |
+
vae.model.load_weights(list(mx.load(str(ensure_vae_weights(repo))).items()), strict=True)
|
| 90 |
+
|
| 91 |
+
tok = AutoTokenizer.from_pretrained(".")
|
| 92 |
+
text_ids = mx.array(tok("a calm ocean wave", add_special_tokens=False,
|
| 93 |
+
return_tensors="np").input_ids[0], dtype=mx.int32)
|
| 94 |
+
|
| 95 |
+
latent = model.sample_t2i(
|
| 96 |
+
prompt_token_ids=text_ids,
|
| 97 |
+
latent_shape=(3, 16, 16), # T_lat=3 β 9 output frames @ 256Γ256
|
| 98 |
+
special_token_ids={"bos": 151644, "eos": 151645,
|
| 99 |
+
"start_of_image": 151652, "end_of_image": 151653,
|
| 100 |
+
"image_token_id": 151655},
|
| 101 |
+
num_steps=24, timestep_shift=3.5, cfg_scale=4.0, seed=0,
|
| 102 |
+
)
|
| 103 |
+
video = vae.decode(latent) # (1, 9, 256, 256, 3) in [-1, 1]
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
## What works
|
| 107 |
|
| 108 |
| Capability | Status |
|
|
|
|
| 147 |
## Differences vs Lance-3B-MLX
|
| 148 |
|
| 149 |
This is the same architecture as the image variant, with two differences:
|
| 150 |
+
|
| 151 |
- `model.safetensors`: 26.5 GB (vs 23 GB) β extra weights for multi-frame attention
|
| 152 |
- `latent_pos_embed.pos_embed`: 31 Γ 64 Γ 64 = 126,976 positions (vs 1 Γ 64 Γ 64 = 4,096) β supports up to 31 latent frames (β 121 video frames @ 4Γ temporal downsample)
|
| 153 |
|
| 154 |
T2I via this checkpoint works the same as Lance-3B-MLX. **T2V is now live** β uses the [Wan 2.2 VAE v0.1.0 streaming cache](https://huggingface.co/RockTalk/Wan2.2-VAE-MLX) under the hood. Pass `latent_shape=(T_lat, H_lat, W_lat)` with `T_lat > 1` to `sample_t2i` to generate a video.
|
| 155 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
## Files
|
| 157 |
|
| 158 |
| File | Size | Description |
|
| 159 |
|---|---|---|
|
| 160 |
| `model.safetensors` | 26.5 GB | LLM (Qwen2.5-VL with MoE-gen) + Lance adapters + bundled ViT (`vit_model.*` prefix), 1411 tensors total |
|
| 161 |
| `vit.safetensors` | 1.25 GB | Qwen2.5-VL ViT, also extractable from `model.safetensors` |
|
| 162 |
+
| `vae.safetensors` | 2.62 GB | Wan 2.2 VAE (older "nested-conv" keying, kept for archival β `inference.py` auto-fetches the cleanly-keyed [RockTalk/Wan2.2-VAE-MLX](https://huggingface.co/RockTalk/Wan2.2-VAE-MLX) instead) |
|
| 163 |
| `config.json` | β | Distilled architecture config |
|
| 164 |
| `tokenizer.json`, `vocab.json`, `merges.txt` | β | Qwen2.5-VL tokenizer, verbatim |
|
| 165 |
| `samples/ocean_wave_*.png` | β | Verified 9-frame T2V outputs |
|
| 166 |
+
| `lance_mlx/` | β | **Bundled MLX implementation** (model + VAE + utils) |
|
| 167 |
+
| `inference.py` | β | **Self-contained T2V/T2I runner** |
|
| 168 |
+
| `requirements.txt` | β | Pip dependencies |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
|
| 170 |
## How the MoE-gen routing is implemented in MLX
|
| 171 |
|
|
|
|
| 187 |
β everything else: normal weights
|
| 188 |
```
|
| 189 |
|
| 190 |
+
The MLX port (`lance_mlx/qwen2_navit_mlx.py`) routes by slicing the sequence into the latent slab vs the surrounding text, applying the appropriate expert to each slab, and concatenating. mrope position ids continue to flow normally across both slabs (with axis-T/H/W coordinates only varying inside the latent slab).
|
| 191 |
|
| 192 |
## Conversion source
|
| 193 |
|
| 194 |
+
Converted from `bytedance-research/Lance/Lance_3B/*` using a local conversion pipeline. Layout transforms:
|
| 195 |
|
| 196 |
- Conv weights: PT `(O, I, [T,] H, W)` β MLX `(O, [T,] H, W, I)`
|
| 197 |
- Embedding weights: shape preserved
|
| 198 |
- `lm_head.weight` tied to `embed_tokens.weight` (Qwen default)
|
| 199 |
- All `*_moe_gen.*` keys copied verbatim under the same names
|
| 200 |
|
| 201 |
+
## Related ports
|
| 202 |
+
|
| 203 |
+
A parallel MLX port exists at [mlx-community/Lance-3B-Video-bf16](https://huggingface.co/mlx-community/Lance-3B-Video-bf16) (Apache-2.0). The two checkpoints have been verified numerically equivalent: remapping this repo's F32 weights into their layout and casting to bf16 produces byte-identical pixel output through their pipeline. Use whichever fits your workflow.
|
| 204 |
+
|
| 205 |
## License
|
| 206 |
|
| 207 |
Apache 2.0, inherited from upstream `bytedance-research/Lance`. The Wan 2.2 VAE component is also Apache 2.0 from Alibaba's Wan team.
|
|
|
|
| 212 |
- **Qwen team** β Qwen2.5-VL-3B-Instruct backbone
|
| 213 |
- **Alibaba Wan team** β Wan 2.2 VAE training
|
| 214 |
- **Apple `mlx` and `mlx-vlm` teams** β the underlying frameworks
|
| 215 |
+
- **mlx-community Lance porters** β parallel bf16 port, numerically cross-checked against this one
|
| 216 |
- **This MLX port** β RockTalk
|
| 217 |
|
| 218 |
## Citation
|