Skip to content

Performance on Apple silicon

mlx-diffuser leans on MLX's strengths. Most of it is on by default.

Compilation

Sampling and training steps are compiled. For custom inference loops, compile the model once (per-step shapes are constant, so one graph is reused):

from mlx_diffuser.perf import compile_model
fast_model = compile_model(model)        # params passed as implicit inputs

Parameters are passed as implicit inputs, so a later merge_lora or weight change is picked up without a stale graph. Use shapeless=True only if you understand the shapeless-compile caveats.

Quantization

Weight-only 4/8-bit quantization at load time fits large models in unified memory:

model = DiT.from_pretrained("my-model", quantize=4)         # 4-bit
# or quantize an in-memory model:
from mlx_diffuser import quantize_module
quantize_module(model, bits=8, group_size=64)

Precision

Use dtype="bf16" (or "fp16") for compute; normalization and attention accumulate in higher precision internally, so no manual up/down-casting is needed.

Unified memory

from mlx_diffuser.perf import memory_report, set_memory_limit, clear_cache

set_memory_limit(24)          # soft cap, GB
print(memory_report())        # {'active_gb': ..., 'peak_gb': ..., 'cache_gb': ...}
clear_cache()                 # return cached buffers to the OS

Benchmarking

uv run python examples/benchmark.py --steps 50 --size 32 --batch 4

Reports per-image latency and peak memory, comparing compiled vs eager.

Checklist

  • Keep input shapes stable so compiled graphs aren't retraced.
  • Multiply half-precision arrays by Python scalars (not mx.array) to avoid silent up-casting to float32.
  • Evaluate once per iteration (the trainer and sampler already do this).