Instructions to use treadon/mlx-nucleus-image with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use treadon/mlx-nucleus-image with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] hf download treadon/mlx-nucleus-image --local-dir mlx-nucleus-image
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
Upload README.md with huggingface_hub
Browse files
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,
|
| 21 |
|
| 22 |
-
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
| 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 |
-
###
|
| 44 |
|
| 45 |
```bash
|
| 46 |
python generate.py --prompt "A red apple on a white table" --seed 42
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
-
|
| 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 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 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 (
|
| 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
|
|
|
|
| 138 |
nucleus_image/
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 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 |
-
-
|
|
|
|
|
|
| 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)
|