Skip to main content

zimgturbo

Z-Image-Turbo text-to-image on Apple Silicon, in a fraction of the time.

A from-scratch int8 inference engine for the 6B-parameter Z-Image-Turbo diffusion transformer, built on custom Metal kernels that drive the M5 tensor units (Metal 4 MetalPerformancePrimitives) and are hosted inside MLX.

1024×1024, the model's native 8 steps, ~13.7 s end to end on an M5 Pro (16-core GPU) — versus ~76 s for the reference PyTorch/MPS pipeline (5.6×) — with no quality regression (pixel cosine 0.986–0.997 vs. the MLX int8 baseline over 10 scenes).

sample


Install & run (one command each)

Requires an Apple Silicon Mac (M-series), macOS 26+, and uv.

# 1. get the weights (downloads a prepared int8 bundle, ~10 GB)
uvx zimgturbo setup            # downloads the int8 bundle (~10 GB) from HuggingFace

# 2. generate
uvx zimgturbo "a fluffy tabby cat on a windowsill, sunset backlight, photorealistic" -o cat.png

That's it. Subsequent images are just the second command.

Don't have a prepared bundle? Convert from the original checkpoint (no PyTorch needed)
# download the original Z-Image-Turbo (from HuggingFace) once, then:
uvx zimgturbo setup --from-original \
  --transformer   /path/to/Z-Image-Turbo/transformer \
  --vae           /path/to/vae.safetensors \
  --vae-config    /path/to/vae_config.json \
  --text-encoder  /path/to/Z-Image-Turbo/text_encoder \
  --tokenizer     /path/to/Z-Image-Turbo/tokenizer

The transformer is quantized to int8 on the fly using the calibration data shipped in the package — no torch, no re-calibration. The resulting bundle lands in ~/.cache/zimgturbo.


Usage

CLI

zimgturbo "a red rose covered in dew, macro"          # bare prompt = generate
zimgturbo generate "a kingfisher" -o bird.png --steps 8 --seed 42
zimgturbo bench                                        # per-stage timings
zimgturbo web  --reference /path/to/Z-Image-Turbo      # side-by-side demo (needs torch)

Options: --steps (default 8, the model's native count), --seed, --height/--width, -n (batch), --vae-dtype {bfloat16,float32} (bf16 is default and 1.85× faster).

Python

from zimgturbo import ZImageTurbo

m = ZImageTurbo("~/.cache/zimgturbo")
r = m.generate("a single origami crane on a dark table, dramatic side light",
               steps=8, seed=1234)
print(r.seconds)      # {'encode': 0.31, 'denoise': 12.6, 'vae': 0.73}
r.save("crane.png")

As a dependency

uv add zimgturbo && uv sync

Performance

M5 Pro (16-core GPU, 48 GB), macOS 26.6, 1024×1024, 8 steps, medians over 5 runs:

pipeline encode denoise s/step VAE total speedup
PyTorch/MPS bf16 (9 steps) ~8.4 76.0 s 1.0×
MLX group-int8 (8 steps) 0.3 ~50 ~6.2 1.7 ~52 s 1.5×
zimgturbo (8 steps) 0.31 12.61 1.575 0.73 13.7 s 5.6×

This is within 4% of the hardware roofline: the transformer needs 62 TFLOP/step and the measured int8 tensor-unit peak is 49 TOPS, so ~1.55 s/step is the floor; we hit 1.575.

Quality

Same prompt and initial noise. Left → right: PyTorch bf16 (9 steps), MLX group-int8 (8), our int8 scheme simulated in MLX (8), our engine (8):

comparison

Over a 10-scene set (portraits, fur, feathers, macro, English/Chinese text, architecture, low light) the engine matches the MLX int8 baseline at pixel cosine 0.986–0.997 and is 0.88 against the original PyTorch — marginally closer to PyTorch than the MLX baseline itself (0.864). The residual is trajectory divergence from int8 rounding, not blur.

How it works (short version)

Seven custom Metal kernels do the heavy lifting; everything else stays in MLX:

kernel what it does measured
fused int8 GEMM int8×int8→int32 with dequant+bias epilogue in registers 49 TOPS (2× fp16)
int8 flash attention scores stay on-chip, vectorized softmax 26 TOPS
5 fused row kernels RMSNorm / modulation / SwiGLU / RoPE / transpose / quantize 0.3–1.2 ms each

Two things that are load-bearing and easy to get wrong:

  • GEMM layout. With transpose_right=true, matmul2d wants the reduction dim K on the x axis and N on the y axis, so plain row-major W[N][K] works with .slice(0, n0). Getting those axes backwards produces a read pattern that looks like a hardware aliasing bug and tempts an elaborate multi-pass workaround. There is none — the single dispatch is bit-exact.
  • Range. The SwiGLU intermediate and the to_out input reach ~2e5–4e5, and the VAE overflows in fp16. Those are kept in fp32/bf16 and quantized straight to int8; never materialized as fp16.

Full write-up: OPTIMIZATION_GUIDE.md (a from-first-principles tour for readers new to perf work) and the technical report.

Limits

  • 8 steps under 5 s is not reachable on this class of hardware: 8 steps need 496 TFLOP and the int8 peak is 49 TOPS, a 10.1 s hard floor. This is a compute limit, not an engineering gap.
  • int4 weights are supported but slower (41 vs 49 TOPS) — this workload is compute bound, not weight-bandwidth bound, so shrinking weights doesn't help. int4 only saves memory.
  • Numbers are specific to the M5 Pro (16-core) and to Z-Image-Turbo's shapes.

For maintainers: publishing a weight bundle

Code lives on GitHub; the ~10 GB int8 bundle lives on the HuggingFace Hub:

zimgturbo publish --repo <your-hf-repo>     # uploads ~/.cache/zimgturbo

Then users just zimgturbo setup. The default bundle is yunfengwang/zimgturbo.

The 6.6 GB weights.bin is published as weights.bin.part.* shards (some networks cannot sustain a single multi-GB upload); setup concatenates them back automatically.

Acknowledgements

Builds on SmoothQuant, FlashAttention, LLM.int8(), the roofline model, Apple's MLX, and Tongyi-MAI/Z-Image-Turbo.

License

Apache-2.0 (code). Model weights are subject to the Z-Image-Turbo license.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zimgturbo-0.1.1.tar.gz (3.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zimgturbo-0.1.1-py3-none-any.whl (2.6 MB view details)

Uploaded Python 3

File details

Details for the file zimgturbo-0.1.1.tar.gz.

File metadata

  • Download URL: zimgturbo-0.1.1.tar.gz
  • Upload date:
  • Size: 3.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for zimgturbo-0.1.1.tar.gz
Algorithm Hash digest
SHA256 1d896b98a14621e10e9aec73fb038eb60398bf3356e90081fc13f30cb6bc7d69
MD5 29a5ff8d48bc696c830ec2a429b31fba
BLAKE2b-256 b5d07f8d7713a0fa93fede3d71ed06687f2fe2bfb1e69bfae7e37b6476ae5c08

See more details on using hashes here.

File details

Details for the file zimgturbo-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: zimgturbo-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 2.6 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for zimgturbo-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 301043ee38b33a22c583d7a7a8fe5b5bd488047ee23039896f85e59d43203cd2
MD5 54610be0b791bef937766ac114d4bca4
BLAKE2b-256 8dfbd716e10a2bb141686311649789b161aa3a87679239057cb027cf50702a24

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page