RockTalk commited on
Commit
1ecf7d6
Β·
verified Β·
1 Parent(s): 1f73b3d

Initial release: Lance-3B-MLX first MLX port, T2I verified end-to-end

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ samples/orange_cat_chair.png filter=lfs diff=lfs merge=lfs -text
37
+ samples/snowy_peak.png filter=lfs diff=lfs merge=lfs -text
38
+ samples/sunset_mountains.png filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -3,111 +3,163 @@ license: apache-2.0
3
  base_model:
4
  - bytedance-research/Lance
5
  - Qwen/Qwen2.5-VL-3B-Instruct
6
- pipeline_tag: any-to-any
7
  library_name: mlx
8
  tags:
9
  - multimodal
10
  - mlx
11
  - apple-silicon
 
12
  - image-generation
13
  - video-generation
14
- - image-editing
15
- - video-understanding
16
- - any-to-any
 
 
17
  - port
18
  ---
19
 
20
  # Lance-3B-MLX
21
 
22
- A native [MLX](https://github.com/ml-explore/mlx) port of [ByteDance's Lance](https://huggingface.co/bytedance-research/Lance) β€” a 3B-parameter unified multimodal model for image and video generation, editing, and understanding.
23
 
24
- Built on top of the Qwen2.5-VL-3B-Instruct backbone, with Lance's custom multi-task adapters and a Wan 2.2 VAE.
25
 
26
- ## Status
27
 
28
- **Weight conversion is complete** β€” all tensors from the upstream PyTorch
29
- checkpoint are present in MLX safetensors layout, verified bit-exact on
30
- sampled tensors. **Inference wrapper is not yet runnable.**
31
-
32
- | Component | Status |
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  |---|---|
34
- | Weight conversion (PT β†’ MLX safetensors, layout + name remaps) | βœ… DONE β€” bit-exact spot check (40/40 sampled tensors) |
35
- | `modeling_utils` (TimestepEmbedder, PositionEmbedding3D, MLP, sincos tables) | βœ… DONE |
36
- | `vae_wan22` β€” image-mode encode/decode | βœ… DONE |
37
- | `vae_wan22` β€” video streaming feat-cache | ⏳ PENDING |
38
- | `lance.py` adapters (vae2llm, llm2vae, time/pos embed) | ⚠ PARTIAL β€” primitives present; patchify path doesn't match source, needs debug |
39
- | Lance's MoE-gen attention (`q_proj_moe_gen` etc β€” 505 tensors) | ⏳ NOT YET WRAPPED β€” weights bundled, wrapper class still uses bare Qwen2.5-VL |
40
- | Lance's QK-norm extension (`q_norm`/`k_norm` in attention β€” 73 tensors) | ⏳ NOT YET WRAPPED β€” weights bundled |
41
- | Flow-matching sampler (`validation_gen` β€” T2I/T2V/edit) | ⏳ STUB |
42
- | Xβ†’T (understanding) autoregressive loop | ⏳ Phase 2 |
43
- | NaViT variable-resolution image packing | ⏳ Phase 2 |
44
-
45
- **What this repo gives you today:** correctly-converted weights you can load
46
- in MLX, with the bundle keys matching mlx-vlm's Qwen2.5-VL convention plus
47
- Lance's own adapter top-levels (`vae2llm`, `llm2vae`, `time_embedder`,
48
- `latent_pos_embed`, `vae_model.*`). The MoE-gen generation-path weights are
49
- included verbatim so any future MoE-aware wrapper can pick them up.
50
-
51
- **What's missing for end-to-end inference:** the Python wrapper class needs
52
- (1) MoE-routing attention layers with parallel und/gen weight sets, (2)
53
- QK-norm, (3) a corrected patchify path before `vae2llm`, (4) the
54
- flow-matching denoising loop + CFG. The source PT implementation in
55
- `Lance/modeling/lance/qwen2_navit.py` (~1300 lines) and `lance.py` (~1900
56
- lines) is the reference.
57
 
58
  ## Files
59
 
60
- - `model.safetensors` β€” Lance 3B image variant LLM + adapters (Qwen2.5-VL language model, vae2llm/llm2vae, time_embedder, latent_pos_embed), MLX-layout, **22.9 GB** (1021 tensors, ~6.19B params incl. embed table)
61
- - `vit.safetensors` β€” Qwen2.5-VL ViT visual encoder, MLX-layout (NTHWC conv weights), **1.25 GB** (390 tensors, ~668M params, fp16 β€” bundled here for offline use; the source ships it as a separate shard)
62
- - `vae.safetensors` β€” Wan 2.2 VAE, MLX-layout, **2.62 GB** (196 tensors, ~705M params). Converted from the upstream `Wan2.2_VAE.pth` pickle.
63
- - `config.json` β€” distilled architecture config + embedded Qwen2.5-VL sub-config
64
- - `vit_config.json` β€” Qwen2.5-VL ViT sub-config
65
- - `tokenizer.json`, `tokenizer_config.json`, `vocab.json`, `merges.txt`, `generation_config.json` β€” copied verbatim from upstream
 
 
66
 
67
- ## Hardware
68
 
69
- Targets Apple Silicon with unified memory. Verified on M3 Ultra (512 GB). Lower-RAM Macs may need to run the LLM forward only (no joint backbone + VAE).
70
 
71
- ## Loading
 
 
72
 
73
  ```python
74
  import mlx.core as mx
75
- import mlx.nn as nn
76
  from lance_mlx.lance import Lance, LanceConfig
77
- from mlx_vlm.models.qwen2_5_vl.config import ModelConfig as Qwen25VLConfig
78
- import json
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
- from lance_mlx import load_lance
81
 
82
- model, cfg = load_lance("./")
83
- # cfg['_loaded_into_model'] reports how many tensors landed in the wrapper.
84
- # cfg['_vae_weights'] holds the Wan VAE keys (load into a separate vae module).
85
- # cfg['_moe_gen_weights'] holds Lance's generation-path weights, parked until
86
- # a MoE-aware wrapper is available.
 
 
 
87
  ```
88
 
89
- ## Citation
90
 
91
- ```bibtex
92
- @article{lance2026,
93
- title = {Lance: Unified Multimodal Modeling by Multi-Task Synergy},
94
- author = {Fu, Fengyi and Huang, Mengqi and Wu, Shaojin and Jiang, Yunsheng and Huo, Yufei and Guo, Jianzhu and others},
95
- journal = {arXiv preprint arXiv:2605.18678},
96
- year = {2026},
97
- url = {http://arxiv.org/abs/2605.18678}
98
- }
99
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  ## License
102
 
103
- Apache-2.0, inherited from upstream `bytedance-research/Lance`.
104
 
105
- ## Acknowledgments
106
 
107
- - ByteDance Research for the original Lance training and PyTorch release
108
- - The `mlx` and `mlx-vlm` teams at Apple
109
- - Qwen team for Qwen2.5-VL-3B-Instruct
 
 
110
 
111
- ---
112
 
113
- **Port status reporting honestly:** this repo currently provides MLX-format weights with verified-loading scaffolding. Inference sampling (T2I/T2V) is a follow-up release; the building blocks are in place but the diffusion loop has not been parity-validated end-to-end yet. Pull requests welcome.
 
 
 
 
 
 
 
 
3
  base_model:
4
  - bytedance-research/Lance
5
  - Qwen/Qwen2.5-VL-3B-Instruct
6
+ pipeline_tag: text-to-image
7
  library_name: mlx
8
  tags:
9
  - multimodal
10
  - mlx
11
  - apple-silicon
12
+ - text-to-image
13
  - image-generation
14
  - video-generation
15
+ - diffusion
16
+ - flow-matching
17
+ - moe
18
+ - qwen2_5_vl
19
+ - wan
20
  - port
21
  ---
22
 
23
  # Lance-3B-MLX
24
 
25
+ First native [MLX](https://github.com/ml-explore/mlx) port of [ByteDance Research's Lance](https://huggingface.co/bytedance-research/Lance) β€” a 3B-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
+ ## What works
30
 
31
+ | Capability | Status |
32
+ |---|---|
33
+ | Text-to-image (T2I), single image, CFG | βœ… Working, verified |
34
+ | Strict load of all 1021 LLM/adapter tensors | βœ… Working |
35
+ | Wan 2.2 VAE encode/decode (T=1) | βœ… Working (uses [RockTalk/Wan2.2-VAE-MLX](https://huggingface.co/RockTalk/Wan2.2-VAE-MLX)) |
36
+ | Flow-matching denoising loop | βœ… Working |
37
+ | Classifier-free guidance | βœ… Working |
38
+ | 3D mrope position embeddings | βœ… Working |
39
+ | MoE-gen routing (per-token attention + MLP + layernorm) | βœ… Working |
40
+ | Text-to-video (T2V) | ⏳ Needs VAE T>1 streaming cache + larger pos embed |
41
+ | Image/video editing (TI2I, TIV2V) | ⏳ Phase 2 β€” needs ViT integration |
42
+ | Xβ†’T (image/video understanding) | ⏳ Phase 2 β€” needs AR sampling loop + KV cache |
43
+
44
+ ## Sample generations
45
+
46
+ Verified on M4 Studio (128 GB). 30 steps, CFG=4, 512Γ—512:
47
+
48
+ | Prompt | Output |
49
  |---|---|
50
+ | *"a photo of a sunset over mountains"* | ![sunset](samples/sunset_mountains.png) |
51
+ | *"a fluffy orange cat sitting on a wooden chair, photorealistic"* | ![cat](samples/orange_cat_chair.png) |
52
+ | *"a majestic snowy mountain peak with a dramatic blue sky and clouds"* | ![mountain](samples/snowy_peak.png) |
53
+
54
+ ## Performance
55
+
56
+ Measured on M4 Studio (128 GB) at CFG=4 (one conditional + one unconditional forward per step):
57
+
58
+ | Resolution | Steps | Per-step | Total sample | VAE decode |
59
+ |---|---|---|---|---|
60
+ | 256Γ—256 | 24 | ~400 ms | ~9.6 s | ~0.1 s |
61
+ | 512Γ—512 | 30 | ~1.2 s | ~36 s | ~0.5 s |
62
+
63
+ First-call kernel-compile penalty: ~few seconds per new resolution.
 
 
 
 
 
 
 
 
 
64
 
65
  ## Files
66
 
67
+ | File | Size | Description |
68
+ |---|---|---|
69
+ | `model.safetensors` | 23 GB | LLM (Qwen2.5-VL with MoE-gen) + Lance adapters, 1021 tensors |
70
+ | `vit.safetensors` | 1.25 GB | Qwen2.5-VL ViT (for understanding mode β€” Phase 2) |
71
+ | `vae.safetensors` | 2.62 GB | Wan 2.2 VAE (older keying β€” for compatibility; the standalone [RockTalk/Wan2.2-VAE-MLX](https://huggingface.co/RockTalk/Wan2.2-VAE-MLX) uses cleaner keys and is recommended) |
72
+ | `config.json` | β€” | Distilled architecture config |
73
+ | `tokenizer.json`, `vocab.json`, `merges.txt` | β€” | Qwen2.5-VL tokenizer, verbatim |
74
+ | `samples/*.png` | β€” | Verified T2I outputs from this checkpoint |
75
 
76
+ ## Usage
77
 
78
+ 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.
79
 
80
+ ```bash
81
+ pip install mlx mlx-vlm numpy einops transformers pillow
82
+ ```
83
 
84
  ```python
85
  import mlx.core as mx
 
86
  from lance_mlx.lance import Lance, LanceConfig
87
+ from lance_mlx.vae_wan22 import Wan2_2_VAE
88
+
89
+ # Build + strict-load (see tools/lance_t2i.py in the companion repo for the
90
+ # full builder; LanceConfig takes a Qwen2.5-VL ModelConfig built from
91
+ # config.json).
92
+ model = Lance(lance_cfg)
93
+ model.load_weights(list(mx.load("model.safetensors").items()), strict=True)
94
+
95
+ vae = Wan2_2_VAE(z_dim=48, c_dim=160, dim_mult=(1, 2, 4, 4),
96
+ temperal_downsample=(False, True, True))
97
+ vae.model.load_weights(list(mx.load("vae.safetensors").items()), strict=True)
98
+
99
+ # Sample
100
+ latent = model.sample_t2i(
101
+ prompt_token_ids=text_ids, # (P,) int32 from tokenizer (no specials)
102
+ latent_shape=(1, 32, 32), # (T_lat, H_lat, W_lat) for 512Γ—512 image
103
+ special_token_ids={"bos": 151644, "eos": 151645,
104
+ "start_of_image": 151652, "end_of_image": 151653,
105
+ "image_token_id": 151655},
106
+ num_steps=30, timestep_shift=3.5, cfg_scale=4.0, seed=0,
107
+ )
108
+ img = vae.decode(latent) # (1, 1, 512, 512, 3) in [-1, 1]
109
+ ```
110
+
111
+ End-to-end script: `tools/lance_t2i.py` in the [companion repo](https://github.com/RockTalk/Lance-MLX).
112
 
113
+ ## How the MoE-gen routing is implemented in MLX
114
 
115
+ Lance's checkpoint contains *two* sets of weights per Qwen2 block:
116
+
117
+ ```
118
+ self_attn.{q,k,v,o}_proj self_attn.{q,k,v,o}_proj_moe_gen
119
+ self_attn.{q,k}_norm self_attn.{q,k}_norm_moe_gen
120
+ mlp.{gate,down,up}_proj mlp_moe_gen.{gate,down,up}_proj
121
+ input_layernorm input_layernorm_moe_gen
122
+ post_attention_layernorm post_attention_layernorm_moe_gen
123
  ```
124
 
125
+ For T2I/T2V the sequence layout is:
126
 
 
 
 
 
 
 
 
 
127
  ```
128
+ <|im_start|> [prompt tokens] <|im_end|> <|vision_start|> [N latent placeholders] <|vision_end|>
129
+ └──── routed through moe_gen β”€β”€β”€β”€β”˜
130
+ ↑ everything else: normal weights
131
+ ```
132
+
133
+ 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).
134
+
135
+ ## Conversion source
136
+
137
+ Converted from `bytedance-research/Lance/Lance_3B/*` using the open-source pipeline at https://github.com/RockTalk/Lance-MLX (`tools/convert_weights.py`). Layout transforms:
138
+
139
+ - Conv weights: PT `(O, I, [T,] H, W)` β†’ MLX `(O, [T,] H, W, I)`
140
+ - Embedding weights: shape preserved
141
+ - `lm_head.weight` tied to `embed_tokens.weight` (Qwen default)
142
+ - All `*_moe_gen.*` keys copied verbatim under the same names
143
 
144
  ## License
145
 
146
+ 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.
147
 
148
+ ## Acknowledgements
149
 
150
+ - **ByteDance Research** β€” original Lance training + PT release
151
+ - **Qwen team** β€” Qwen2.5-VL-3B-Instruct backbone
152
+ - **Alibaba Wan team** β€” Wan 2.2 VAE training
153
+ - **Apple `mlx` and `mlx-vlm` teams** β€” the underlying frameworks
154
+ - **This MLX port** β€” RockTalk
155
 
156
+ ## Citation
157
 
158
+ ```bibtex
159
+ @misc{lance_mlx,
160
+ title = {Lance-3B-MLX β€” First MLX port of ByteDance's Lance},
161
+ author = {RockTalk},
162
+ year = {2026},
163
+ url = {https://huggingface.co/RockTalk/Lance-3B-MLX}
164
+ }
165
+ ```
config.json CHANGED
@@ -67,14 +67,15 @@
67
  },
68
  "latent_patch_size": [
69
  1,
70
- 2,
71
- 2
72
  ],
73
- "max_latent_size": 32,
74
- "max_num_frames": 25,
75
  "latent_channel": 48,
76
  "vae_downsample_spatial": 16,
77
  "vae_downsample_temporal": 4,
78
  "connector_act": "gelu_pytorch_tanh",
79
- "timestep_shift": 3.5
 
80
  }
 
67
  },
68
  "latent_patch_size": [
69
  1,
70
+ 1,
71
+ 1
72
  ],
73
+ "max_latent_size": 64,
74
+ "max_num_frames": 0,
75
  "latent_channel": 48,
76
  "vae_downsample_spatial": 16,
77
  "vae_downsample_temporal": 4,
78
  "connector_act": "gelu_pytorch_tanh",
79
+ "timestep_shift": 3.5,
80
+ "max_num_latent_frames": 1
81
  }
samples/orange_cat_chair.png ADDED

Git LFS Details

  • SHA256: 573e45a55bb406aebd81a3b15a66a217cccae86c171bb7688502bca53ea4c169
  • Pointer size: 131 Bytes
  • Size of remote file: 353 kB
samples/snowy_peak.png ADDED

Git LFS Details

  • SHA256: 9ee9dc37a32b0025c910fa99de4fee4ceedf25cc963d61c5925ccb242b512c58
  • Pointer size: 131 Bytes
  • Size of remote file: 339 kB
samples/sunset_mountains.png ADDED

Git LFS Details

  • SHA256: cd52958dbe6117c4c68e7fe56a8a2db97925542f74c5e84cead9343e73a558b6
  • Pointer size: 131 Bytes
  • Size of remote file: 277 kB