unslothai/unsloth · error · ValueError

save_steps must be >= 0 (0 disables periodic checkpoints)

Error message

save_steps must be >= 0 (0 disables periodic checkpoints)

What it means

The validator rejected a negative save_steps. save_steps controls how often periodic checkpoints are written during the loop; 0 is the documented 'disable periodic checkpoints' value, and negatives are meaningless. Like the other checkpoint knobs, it is checked before the route evicts resident GPU models.

Source

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

            )
        if str(self.lr_scheduler) not in _LR_SCHEDULERS:
            raise ValueError(
                f"lr_scheduler must be one of {', '.join(sorted(_LR_SCHEDULERS))}; "
                f"got {self.lr_scheduler!r}"
            )
        if not 1 <= int(self.cache_variants) <= 16:
            raise ValueError("cache_variants must be between 1 and 16")
        # Checkpointing knobs. Rejected here, before the route evicts resident GPU models, rather than deep in the loop.
        try:
            save_steps = int(self.save_steps or 0)
            save_total_limit = int(self.save_total_limit or 0)
        except (TypeError, ValueError) as exc:
            raise ValueError(
                f"save_steps / save_total_limit must be whole numbers, got "
                f"{self.save_steps!r} / {self.save_total_limit!r}"
            ) from exc
        if save_steps < 0:
            raise ValueError("save_steps must be >= 0 (0 disables periodic checkpoints)")
        if save_total_limit < 0:
            raise ValueError("save_total_limit must be >= 0 (0 keeps every checkpoint)")
        # A blank resume path (the Studio default when the field is present but unset) means "fresh run", not the outputs root.
        resume_from_checkpoint = (
            str(self.resume_from_checkpoint).strip()
            if self.resume_from_checkpoint is not None
            else ""
        ) or None
        # The H3 loop does not checkpoint: it neither writes a resume bundle nor restores one.
        # Accepting these two silently was the dangerous part -- a caller handing over a resume
        # bundle got a FRESH optimization that then overwrote the outputs it was meant to
        # continue, and one asking for periodic saves got none, both discovered only after an
        # expensive run. Refuse in validation, where it costs nothing, until the loop supports it.
        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 "

View on GitHub (pinned to 203007d190)

Solutions

  1. Use 0 to disable periodic checkpointing — not a negative number.
  2. Set a positive step count (e.g. 500) for periodic saves.
  3. Clamp derived values: save_steps = max(0, computed).

Example fix

# before
config = TrainConfig(save_steps=-1)

# after
config = TrainConfig(save_steps=0)
Defensive patterns

Strategy: validation

Validate before calling

def check_save_steps(v) -> int:
    n = int(v or 0)
    if n < 0:
        raise ValueError(f"save_steps must be >= 0 (0 disables periodic checkpoints), got {v!r}")
    return n

Type guard

def is_valid_save_steps(v) -> bool:
    try:
        return int(v or 0) >= 0
    except (TypeError, ValueError):
        return False

Try / catch

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

Prevention

When it happens

Trigger: save_steps=-100, or a string like '-1' that int() happily parses. Often from code computing save_steps = total_steps // n where n overshoots and yields a negative remainder, or from configs using -1 as a 'disabled' sentinel from another tool's convention.

Common situations: Migrating configs from frameworks where -1 means 'off'; arithmetic that derives save cadence from run length and can go negative for short runs.

Related errors


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