ernie-image-mlx
Pure MLX port of Baidu ERNIE-Image — an 8B single-stream Diffusion Transformer for text-to-image generation on Apple Silicon.
Status: Runnable end-to-end. All MLX modules parity-tested against diffusers (fp32 — DiT 3.1e-6, VAE encoder 1.7e-6, VAE decoder 6.7e-6, ResnetBlock2D 1e-5). Six checkpoint variants converted (fp16 / int8 / int4 × SFT / Turbo) and verified to produce clean images. The sample below was rendered by Turbo-q fp16 in 45 s on an M-series Mac with the prompt
一只黑白相间的中华田园犬("a black and white Chinese village dog"):
Variants
Both checkpoints share the same architecture and load through the same class — only the scheduler steps and default guidance differ.
| Variant | HF repo | Steps | Guidance | MLX output size (fp16 / q8 / q4) |
|---|---|---|---|---|
| ERNIE-Image (SFT) | baidu/ERNIE-Image |
50 | ~5 | 22 GB / 12 GB / 6.4 GB |
| ERNIE-Image-Turbo (distilled) | baidu/ERNIE-Image-Turbo |
8 | 1.0 | 22 GB / 12 GB / 6.4 GB |
Install
git clone https://github.com/dgrauet/ernie-image-mlx.git
cd ernie-image-mlx
uv sync # runtime
uv sync --extra parity # + PyTorch / diffusers / transformers for parity tests
Requires macOS with Apple Silicon and Python 3.11+.
Quick start
import os
os.environ["ERNIE_IMAGE_MLX_WEIGHTS_DIR"] = "/path/to/ernie-image-turbo-mlx" # see "Convert weights" below
from ernie_image_core_mlx import ErnieImagePipeline
pipe = ErnieImagePipeline.from_pretrained("baidu/ERNIE-Image-Turbo")
out = pipe(
"a black and white chinese farm dog", # any language works — PE expands short inputs
height=512, width=512, # into rich Chinese visual descriptions
num_inference_steps=8, # turbo is distilled; SFT wants 50 steps
guidance_scale=1.0, # turbo is CFG-distilled; use 4-5 with the SFT variant
negative_prompt=None,
seed=42,
)
print(out.revised_prompts[0]) # the PE-expanded prompt fed to the DiT
out.images[0].save("dog.png")
from_pretrained also loads the Prompt Enhancer from a separate HF repo by
default (dgrauet/ernie-image-pe-mlx-q4, ~1.8 GB). Set pe_repo_id=None to
skip loading it entirely, or pass use_pe=False to the call to keep it loaded
but bypass expansion on a per-call basis.
from_pretrained resolution order: explicit local_dir → ERNIE_IMAGE_MLX_WEIGHTS_DIR env var → huggingface_hub.snapshot_download(repo_id) (only useful once an MLX build is uploaded to HF).
CLI
pip install -e . (or uv sync) exposes a terminal entrypoint. Weights download on first use from dgrauet/ernie-image-turbo-mlx-q8 (12 GB, int8 Turbo — the "ideal balance" row below):
ernie-image-mlx generate -p "一只黑白相间的中华田园犬" -o dog.png
# SFT variant at 50 steps with guidance 5
ernie-image-mlx generate -p "prompt" \
--repo-id dgrauet/ernie-image-sft-mlx-q8 -s 50 -g 5.0 --seed 42
# Use a locally-converted checkpoint (mlx-forge convert ernie-image …)
ernie-image-mlx generate -p "prompt" --local-dir ~/models/ernie-image-turbo-mlx-q8
# Skip the Prompt Enhancer (save ~3 s + ~1.8 GB RAM — useful when the prompt
# is already a detailed Chinese description)
ernie-image-mlx generate -p "一只详细描述..." --no-pe
Defaults match the ErnieImagePipeline: 1024×1024, variant auto-detected from --repo-id, CFG implicitly disabled for Turbo (guidance=1.0). Pass --no-cfg to skip the uncond pass explicitly, or --variant {turbo,sft} to override detection when loading from --local-dir. Pass --seed -1 to draw (and print) a fresh random seed — the integer is echoed to stdout so you can rerun with the exact value for reproducibility.
The Prompt Enhancer is on by default and loads from dgrauet/ernie-image-pe-mlx-q4. Override with --pe-repo-id, point at a local converted dir with --pe-local-dir, or disable entirely with --no-pe. --pe-seed controls PE sampling reproducibility independently of --seed (which controls the image latent).
Convert weights
MLX-native safetensors ship via the sibling mlx-forge CLI:
# Download + convert Turbo at fp16
mlx-forge convert ernie-image --variant turbo
# int8 quantized (12 GB, recommended for 32 GB Macs)
mlx-forge convert ernie-image --variant turbo --quantize --bits 8
# int4 (6.4 GB, recommended for 16-24 GB Macs)
mlx-forge convert ernie-image --variant sft --quantize --bits 4
# Validate
mlx-forge validate ernie-image models/ernie-image-turbo-mlx
# Prompt Enhancer (separate recipe, same tool — 7 GB fp16 or 1.8 GB int4)
mlx-forge convert ernie-image-pe --quantize --bits 4
mlx-forge validate ernie-image-pe models/ernie-image-pe-mlx-q4
Output shape: split per-component safetensors (transformer.safetensors, text_encoder.safetensors, vae.safetensors) plus transformer_config.json, vae_config.json, text_encoder_config.json, and the mistral-community/pixtral-12b tokenizer files bundled automatically (Baidu publishes only tokenizer_config.json, the vocabulary itself is pulled from the upstream Pixtral repo). The PE recipe produces a standalone pe.safetensors + chat-template + tokenizer directory, ready to be uploaded as a sibling repo shared across Turbo and SFT.
Architecture
Extracted from model_index.json + per-component config.json:
| Component | Class | Config highlights |
|---|---|---|
| Transformer (DiT) | ErnieImageTransformer2DModel |
36 layers, hidden 4096, 32 heads (head_dim 128), FFN 12288, qk_layernorm, RoPE axes [32, 48, 48] (θ=256), text_in_dim 3072 |
| VAE | AutoencoderKLFlux2 |
4 down/up blocks [128, 256, 512, 512], latent 32 ch, patch 2×2, GroupNorm, SiLU; top-level BatchNorm2d for latent renormalisation |
| Text encoder | Mistral3Model (text path) |
Ministral3 backbone: 26 layers, hidden 3072, 32 heads / 8 KV heads (GQA), head_dim 128, YaRN RoPE |
| Scheduler | FlowMatchEulerDiscreteScheduler |
mlx_arsenal.diffusion, linear sigma schedule linspace(1, 0, N+1)[:-1] |
| Prompt Enhancer | Ministral3ForCausalLM |
3B CausalLM, 26-layer Ministral3 (shares backbone with text encoder) + tied lm_head; expands user prompts via Chinese chat template — int4 default (~1.8 GB) |
Development
# Smoke suite (no weights, no torch)
uv run pytest tests/smoke
# Full parity suite (needs the [parity] extra; ~5 s total — all random-weight)
uv run pytest tests/parity -m parity
# Optional: install the pre-commit hook so ruff runs on every `git commit`.
uv run pre-commit install
23 tests pass. Parity coverage: RoPE embedder, apply_rotary_emb, single-head and multi-head attention, FFN (GeGLU), shared-AdaLN block, AdaLN-continuous, full 2-layer DiT, ResnetBlock2D, VAE self-attention, full VAE encoder, full VAE decoder. Thresholds: < 1e-5 for layers, < 5e-3 for the full block, < 1e-4 for the small-config full model.
Memory footprint at inference
| Unified RAM | Suggested variant | Notes |
|---|---|---|
| 96 GB+ | fp16 SFT | maximum quality, 50-step CFG |
| 48-64 GB | int8 SFT or fp16 Turbo | ~20 GB peak activations + weights |
| 24-32 GB | int8 Turbo | ideal balance — 12 GB weights, ~6 s / step at 512² |
| 16 GB | int4 Turbo | 6.4 GB weights; may need mx.metal.set_memory_limit for 1024² |
Related projects
mlx-forge— weight-conversion CLI with theernie-imagerecipe.mlx-arsenal— reusable MLX ops (flow-match scheduler,get_timestep_embedding, pixel-shuffle, etc.).claude-skill-mlx-porting— Claude Code skill capturing the workflow used to produce this port, including the two pitfalls (#7checkerboard,#8Tekken tokenizer BOS) that the port surfaced.
License
MIT. ERNIE-Image weights and reference code are released by Baidu under Apache 2.0.
Release files for ernie-image-mlx 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ernie_image_mlx-0.3.1.tar.gz | 31.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ernie_image_mlx-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 70.1 kB
Release files / ernie_image_mlx-0.3.1.tar.gz
| Download URL | ernie_image_mlx-0.3.1.tar.gz |
|---|---|
| Size | 31.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
052250d73c78de094a69d0126c259ff62d1ad77ed736f278390322819f034ed5
|
|
BLAKE2b-256 checksum How to use checksums |
b241f59988af0edc75a07687a1e173c2c0fbb5ac7e2cd4d97567f484d76cafea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Release files / ernie_image_mlx-0.3.1-py3-none-any.whl
| Download URL | ernie_image_mlx-0.3.1-py3-none-any.whl |
|---|---|
| Size | 38.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a96883b20fe593ef68936dda367e1e51b4e6a36146cdf529f3f61f051229a73d
|
|
BLAKE2b-256 checksum How to use checksums |
05684bdae7f6fce613e2120ec8abb0f6376ae216da6f3014d2ffbe1565c19041
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|