unslothai/unsloth · error · ValueError

ema_decay must be in [0, 1); 0 disables the EMA adapter

Error message

ema_decay must be in [0, 1); 0 disables the EMA adapter

What it means

The validator rejected ema_decay outside [0.0, 1.0). The EMA update is shadow = shadow * decay + param * (1 - decay); decay = 1.0 makes the second term zero, freezing the shadow at its initialization forever, and decay > 1 diverges. 0 is the documented 'disable the EMA adapter' value. The upper bound is exclusive by design.

Source

Thrown at studio/backend/core/training/diffusion_train_common.py:1094

        if resolved_family in CHECKPOINTLESS_FAMILIES:
            if resume_from_checkpoint:
                raise ValueError(
                    f"resume_from_checkpoint is not supported for {resolved_family}: its trainer "
                    f"writes no checkpoint bundle, so there is nothing to continue from and the "
                    f"run would silently start over and overwrite its output. Start a fresh run."
                )
            if save_steps:
                raise ValueError(
                    f"save_steps is not supported for {resolved_family}: its trainer writes no "
                    f"checkpoint bundle. Leave it at 0; the adapter is still saved at the end."
                )
        try:
            ema_decay = float(self.ema_decay or 0.0)
        except (TypeError, ValueError) as exc:
            raise ValueError(f"ema_decay must be a number, got {self.ema_decay!r}") from exc
        # decay = 1.0 would freeze the shadow at its init forever; the update is shadow * decay + param * (1 - decay), so valid decays live in [0, 1).
        if not 0.0 <= ema_decay < 1.0:
            raise ValueError("ema_decay must be in [0, 1); 0 disables the EMA adapter")
        # A blank cond_cache_dir (the Studio default when unset) means "off", not cwd.
        cond_cache_dir = (
            str(self.cond_cache_dir).strip() if self.cond_cache_dir is not None else ""
        ) or None
        compile_transformer = str(self.compile_transformer or "auto").strip().lower()
        if compile_transformer not in ("off", "on", "auto"):
            raise ValueError("compile_transformer must be one of off / on / auto")
        base_precision = str(self.base_precision or "nf4").strip().lower()
        if base_precision not in ("nf4", "bf16", "int8", "fp8", "mxfp8", "auto"):
            raise ValueError("base_precision must be one of nf4 / bf16 / int8 / fp8 / mxfp8 / auto")
        # base_precision is a DiT-only lever, so the dense-mode gates apply only to the DiT families. The mode-name check above still runs for every family.
        if resolved_family != "sdxl" and base_precision in ("bf16", "int8", "fp8", "mxfp8"):
            if repo_is_prequantized(self.base_model):
                raise ValueError(
                    f"base_precision={base_precision!r} needs a dense base repo, but "
                    f"'{self.base_model}' is already bitsandbytes-quantized. Pick the "
                    f"family's dense (bf16) base repo for this mode, or use nf4/auto."
                )

View on GitHub (pinned to 203007d190)

Solutions

  1. Use a decay strictly below 1.0 — typical values are 0.999 or 0.9995.
  2. Use ema_decay=0 (or None/omitted) to disable EMA entirely.
  3. Make slider/sweep endpoints exclusive at 1.0: max bound 0.9999.

Example fix

# before
config = TrainConfig(ema_decay=1.0)

# after
config = TrainConfig(ema_decay=0.999)
Defensive patterns

Strategy: validation

Validate before calling

def check_ema_decay(v) -> float:
    d = float(v or 0.0)
    if not 0.0 <= d < 1.0:
        raise ValueError(f"ema_decay must be in [0, 1); 1.0 freezes the EMA shadow forever, got {v!r}")
    return d

Type guard

def is_valid_ema_decay_range(v) -> bool:
    try:
        return 0.0 <= float(v or 0.0) < 1.0
    except (TypeError, ValueError):
        return False

Try / catch

try:
    session.submit_training(config)
except ValueError as e:
    if "ema_decay must be in" in str(e):
        config.ema_decay = 0.999
        session.submit_training(config)
    else:
        raise

Prevention

When it happens

Trigger: Passing ema_decay=1.0 ('perfect' averaging intuition), 1.0 via float rounding, or values > 1. Often from configs that treat EMA decay as a probability-style 0..1 inclusive range, or sweeps that include the endpoint 1.0.

Common situations: Copying momentum-style 0.9999 values (fine) but also sweeping to 1.0; assuming 1.0 means 'full average'; UI sliders with an inclusive 0..1 range.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/a4eefafa0817dac9. Report an issue: GitHub.