treadon commited on
Commit
90dc3da
·
verified ·
1 Parent(s): 062a7f9

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +91 -73
README.md CHANGED
@@ -17,35 +17,42 @@ pipeline_tag: text-to-image
17
 
18
  An [MLX](https://github.com/ml-explore/mlx) port of [NucleusAI/Nucleus-Image](https://huggingface.co/NucleusAI/Nucleus-Image), a 17B parameter Mixture-of-Experts (MoE) DiT for text-to-image generation. Runs natively on Apple Silicon.
19
 
20
- ## Sample Outputs (512x512, 50 steps, CFG 4.0, 4-bit)
21
 
22
- *Run `generate.py` to produce your own samples in the `samples/` directory.*
 
 
23
 
24
- ## Architecture
25
-
26
- | Component | Details |
27
- |-----------|---------|
28
- | DiT | 17B total params, ~2B active per token |
29
- | Layers | 32 (3 dense + 29 MoE) |
30
- | Experts | 64 routed + 1 shared per MoE layer |
31
- | Routing | Expert-choice (capacity-based) |
32
- | Attention | GQA: 16 query / 4 KV heads, head_dim=128 |
33
- | Text Encoder | Qwen3-VL-8B-Instruct (PyTorch, hybrid) |
34
- | VAE | AutoencoderKLQwenImage, 16-ch latents |
35
- | Scheduler | Flow Matching Euler |
36
 
37
  ## Quick Start
38
 
 
 
 
 
 
 
 
 
 
39
  ```bash
40
  pip install mlx torch transformers huggingface_hub pillow
41
  ```
42
 
43
- ### CLI (recommended)
44
 
45
  ```bash
46
  python generate.py --prompt "A red apple on a white table" --seed 42
 
 
 
 
 
47
 
48
- # More options
49
  python generate.py \
50
  --prompt "A futuristic city skyline at sunset" \
51
  --height 512 --width 512 \
@@ -54,56 +61,29 @@ python generate.py \
54
  --quantize 4
55
  ```
56
 
57
- ### Python API
 
 
 
 
 
 
 
 
 
58
 
59
- ```python
60
- import torch
61
- import mlx.core as mx
62
- from transformers import AutoProcessor, AutoModel
63
- from nucleus_image import NucleusImagePipeline
64
-
65
- # Step 1: Encode text (PyTorch — runs once, then freed)
66
- processor = AutoProcessor.from_pretrained(
67
- "NucleusAI/Nucleus-Image", subfolder="processor", trust_remote_code=True
68
- )
69
- text_model = AutoModel.from_pretrained(
70
- "NucleusAI/Nucleus-Image", subfolder="text_encoder",
71
- dtype=torch.bfloat16, trust_remote_code=True
72
- )
73
- text_model.eval()
74
-
75
- PROMPT = "A red apple on a white table"
76
- SYSTEM = "You are an image generation assistant."
77
- messages = [
78
- {"role": "system", "content": SYSTEM},
79
- {"role": "user", "content": [{"type": "text", "text": PROMPT}]},
80
- ]
81
- formatted = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
82
- inputs = processor(text=[formatted], return_tensors="pt", padding=True)
83
-
84
- with torch.no_grad():
85
- outputs = text_model(
86
- input_ids=inputs["input_ids"],
87
- attention_mask=inputs.get("attention_mask"),
88
- output_hidden_states=True, use_cache=False,
89
- )
90
- hidden = outputs.hidden_states[-8][0] # 8th from last
91
- text_emb = mx.array(hidden.cpu().float().numpy())
92
-
93
- del text_model, processor # Free ~16GB
94
-
95
- # Step 2: Generate image (MLX)
96
- pipe = NucleusImagePipeline.from_pretrained(quantize=4) # 4-bit DiT
97
 
98
- img = pipe.generate(
99
- text_embeddings=mx.expand_dims(text_emb, 0),
100
- height=512, width=512,
101
- num_inference_steps=30,
102
- guidance_scale=4.0,
103
- seed=42,
104
- )
105
- img.save("output.png")
106
- ```
 
107
 
108
  ## Performance (M4 Pro 64GB)
109
 
@@ -118,7 +98,7 @@ img.save("output.png")
118
  The port is a hybrid approach:
119
  - **Text encoder** stays in PyTorch (Qwen3-VL-8B, ~16GB). Loaded once to extract embeddings, then freed.
120
  - **DiT** (17B MoE) runs in MLX with optional 4-bit quantization for attention/modulation layers. Expert weights stay in bfloat16.
121
- - **VAE decoder** runs in MLX (254MB). Conv3d weights converted to Conv2d by extracting the last temporal kernel slice (CausalConv3d).
122
 
123
  ### Key Conversion Details
124
 
@@ -131,21 +111,59 @@ The port is a hybrid approach:
131
  | AdaLayerNormContinuous | LayerNorm(affine=False) + adaptive scale/shift | scale first, shift second |
132
  | Timesteps(scale=1000) | `timestep_embedding(sigma * 1000)` | Pipeline normalizes t/1000 before DiT |
133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  ## Files
135
 
136
  ```
137
- generate.py — CLI entry point (108 lines)
 
138
  nucleus_image/
139
- __init__.py — Package exports
140
- dit.py — 17B MoE DiT (509 lines)
141
- vae.py — VAE decoder with Conv3d->Conv2d (189 lines)
142
- pipeline.py — End-to-end pipeline (184 lines)
143
- scheduler.py — Flow matching Euler scheduler (24 lines)
144
- text_encoder.py — Text encoder wrapper (optional)
145
  ```
146
 
147
  ## Acknowledgments
148
 
149
  - [NucleusAI](https://huggingface.co/NucleusAI) for the original Nucleus-Image model
150
  - [Apple MLX](https://github.com/ml-explore/mlx) framework
151
- - Built by [@treadon](https://twitter.com/treadon)
 
 
17
 
18
  An [MLX](https://github.com/ml-explore/mlx) port of [NucleusAI/Nucleus-Image](https://huggingface.co/NucleusAI/Nucleus-Image), a 17B parameter Mixture-of-Experts (MoE) DiT for text-to-image generation. Runs natively on Apple Silicon.
19
 
20
+ ## Sample Outputs (512x512, 30 steps, CFG 4.0, 4-bit)
21
 
22
+ | "A red apple on a white table" | "A golden retriever puppy playing in autumn leaves" | "A futuristic city skyline at sunset" |
23
+ |:---:|:---:|:---:|
24
+ | <img src="samples/apple.png" width="256"> | <img src="samples/puppy.png" width="256"> | <img src="samples/city.png" width="256"> |
25
 
26
+ | "A steaming cup of coffee on a rainy windowsill" | "An astronaut riding a horse on the moon" |
27
+ |:---:|:---:|
28
+ | <img src="samples/coffee.png" width="256"> | <img src="samples/astronaut.png" width="256"> |
 
 
 
 
 
 
 
 
 
29
 
30
  ## Quick Start
31
 
32
+ ### 1. Clone the repo
33
+
34
+ ```bash
35
+ git clone https://huggingface.co/treadon/mlx-nucleus-image
36
+ cd mlx-nucleus-image
37
+ ```
38
+
39
+ ### 2. Install dependencies
40
+
41
  ```bash
42
  pip install mlx torch transformers huggingface_hub pillow
43
  ```
44
 
45
+ ### 3. Generate an image
46
 
47
  ```bash
48
  python generate.py --prompt "A red apple on a white table" --seed 42
49
+ ```
50
+
51
+ The first run downloads ~34GB of weights (cached for subsequent runs).
52
+
53
+ ### More options
54
 
55
+ ```bash
56
  python generate.py \
57
  --prompt "A futuristic city skyline at sunset" \
58
  --height 512 --width 512 \
 
61
  --quantize 4
62
  ```
63
 
64
+ | Flag | Default | Description |
65
+ |------|---------|-------------|
66
+ | `--prompt` | required | Text prompt |
67
+ | `--height` | 512 | Image height |
68
+ | `--width` | 512 | Image width |
69
+ | `--steps` | 50 | Denoising steps |
70
+ | `--cfg` | 4.0 | Guidance scale |
71
+ | `--seed` | random | Random seed |
72
+ | `--output` | output.png | Output file |
73
+ | `--quantize` | 4 | Quantization bits (4, 8, or None) |
74
 
75
+ ## Architecture
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
+ | Component | Details |
78
+ |-----------|---------|
79
+ | DiT | 17B total params, ~2B active per token |
80
+ | Layers | 32 (3 dense + 29 MoE) |
81
+ | Experts | 64 routed + 1 shared per MoE layer |
82
+ | Routing | Expert-choice (capacity-based) |
83
+ | Attention | GQA: 16 query / 4 KV heads, head_dim=128 |
84
+ | Text Encoder | Qwen3-VL-8B-Instruct (PyTorch, hybrid) |
85
+ | VAE | AutoencoderKLQwenImage, 16-ch latents |
86
+ | Scheduler | Flow Matching Euler |
87
 
88
  ## Performance (M4 Pro 64GB)
89
 
 
98
  The port is a hybrid approach:
99
  - **Text encoder** stays in PyTorch (Qwen3-VL-8B, ~16GB). Loaded once to extract embeddings, then freed.
100
  - **DiT** (17B MoE) runs in MLX with optional 4-bit quantization for attention/modulation layers. Expert weights stay in bfloat16.
101
+ - **VAE decoder** runs in MLX (~50MB converted). Original CausalConv3d weights converted to Conv2d.
102
 
103
  ### Key Conversion Details
104
 
 
111
  | AdaLayerNormContinuous | LayerNorm(affine=False) + adaptive scale/shift | scale first, shift second |
112
  | Timesteps(scale=1000) | `timestep_embedding(sigma * 1000)` | Pipeline normalizes t/1000 before DiT |
113
 
114
+ ## Python API
115
+
116
+ ```python
117
+ import torch
118
+ import mlx.core as mx
119
+ from transformers import AutoProcessor, AutoModel
120
+ from nucleus_image import NucleusImagePipeline
121
+
122
+ # Encode text (PyTorch — loaded once, then freed)
123
+ processor = AutoProcessor.from_pretrained("NucleusAI/Nucleus-Image", subfolder="processor", trust_remote_code=True)
124
+ text_model = AutoModel.from_pretrained("NucleusAI/Nucleus-Image", subfolder="text_encoder", dtype=torch.bfloat16, trust_remote_code=True).eval()
125
+
126
+ SYSTEM = "You are an image generation assistant."
127
+ def encode(prompt):
128
+ messages = [{"role": "system", "content": SYSTEM}, {"role": "user", "content": [{"type": "text", "text": prompt}]}]
129
+ formatted = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
130
+ inputs = processor(text=[formatted], return_tensors="pt", padding=True)
131
+ with torch.no_grad():
132
+ out = text_model(input_ids=inputs["input_ids"], attention_mask=inputs.get("attention_mask"),
133
+ output_hidden_states=True, use_cache=False)
134
+ return mx.array(out.hidden_states[-8][0].cpu().float().numpy())
135
+
136
+ text_emb = encode("A red apple on a white table")
137
+ neg_emb = encode("") # empty string for proper CFG
138
+ del text_model, processor # free ~16GB
139
+
140
+ # Generate (MLX)
141
+ pipe = NucleusImagePipeline.from_pretrained(quantize=4)
142
+ img = pipe.generate(
143
+ text_embeddings=mx.expand_dims(text_emb, 0),
144
+ neg_text_embeddings=mx.expand_dims(neg_emb, 0),
145
+ height=512, width=512, num_inference_steps=30, guidance_scale=4.0, seed=42,
146
+ )
147
+ img.save("output.png")
148
+ ```
149
+
150
  ## Files
151
 
152
  ```
153
+ generate.py — CLI entry point
154
+ samples/ — Pre-generated sample images
155
  nucleus_image/
156
+ dit.py — 17B MoE DiT
157
+ vae.py — VAE decoder (Conv3d→Conv2d)
158
+ pipeline.py — End-to-end pipeline
159
+ scheduler.py — Flow matching Euler scheduler
160
+ dit/ — Pre-converted DiT weights (safetensors)
161
+ vae/ — Pre-converted VAE weights (safetensors)
162
  ```
163
 
164
  ## Acknowledgments
165
 
166
  - [NucleusAI](https://huggingface.co/NucleusAI) for the original Nucleus-Image model
167
  - [Apple MLX](https://github.com/ml-explore/mlx) framework
168
+ - Source code: [github.com/treadon/mlx-nucleus-image](https://github.com/treadon/mlx-nucleus-image)
169
+ - Built by [@treadon](https://x.com/treadon)