Skip to content

Training

DiffusionTrainer ties a model + scheduler + optimizer into a compiled training loop. It works for unconditional and class-conditional models.

from mlx_diffuser import DiT, DiTConfig, DiffusionTrainer
from mlx_diffuser.schedulers import FlowMatchEulerScheduler
from mlx_diffuser.training import batch_iterator, min_snr_weights

model = DiT(DiTConfig(in_channels=3, hidden_size=384, depth=12, num_heads=6, num_classes=10))
trainer = DiffusionTrainer(
    model,
    FlowMatchEulerScheduler(),
    lr=1e-4,
    weight_decay=0.0,
    grad_clip=1.0,
    ema_decay=0.999,
    class_dropout_prob=0.1,         # for classifier-free guidance
)

history = trainer.fit(
    batch_iterator((images, labels), batch_size=32),
    steps=10_000,
    log_every=100,
)

How it works

Each step draws noise and timesteps eagerly, then runs a single mx.compile-fused function that fuses forward + backward + optimizer update. Temporaries are released before evaluation to keep peak memory low.

loss = trainer.step(x0, y)   # one batch; returns a scalar loss

Loss weighting

Pass loss_weighting= a callable (scheduler, t) -> weights. The built-in min_snr_weights implements Min-SNR-γ for VP diffusion:

from functools import partial
trainer = DiffusionTrainer(model, scheduler,
                           loss_weighting=partial(min_snr_weights, gamma=5.0))

EMA

Set ema_decay to track an exponential moving average of the weights; copy them in for sampling:

trainer.ema.copy_to(model)
model.save_pretrained("my-model-ema")

Tips

  • Keep batches a constant shape so the compiled step is not retraced.
  • Use dtype="bf16" weights for large models; norms accumulate in higher precision automatically.
  • See Performance for memory and compilation knobs.