{"record":{"id":"f21d4365e7a65342","repo":"unslothai/unsloth","slug":"save-steps-must-be-0-0-disables-periodic-check","errorCode":null,"errorMessage":"save_steps must be >= 0 (0 disables periodic checkpoints)","messagePattern":"save_steps must be >= 0 \\(0 disables periodic checkpoints\\)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"studio/backend/core/training/diffusion_train_common.py","lineNumber":1062,"sourceCode":"            )\n        if str(self.lr_scheduler) not in _LR_SCHEDULERS:\n            raise ValueError(\n                f\"lr_scheduler must be one of {', '.join(sorted(_LR_SCHEDULERS))}; \"\n                f\"got {self.lr_scheduler!r}\"\n            )\n        if not 1 <= int(self.cache_variants) <= 16:\n            raise ValueError(\"cache_variants must be between 1 and 16\")\n        # Checkpointing knobs. Rejected here, before the route evicts resident GPU models, rather than deep in the loop.\n        try:\n            save_steps = int(self.save_steps or 0)\n            save_total_limit = int(self.save_total_limit or 0)\n        except (TypeError, ValueError) as exc:\n            raise ValueError(\n                f\"save_steps / save_total_limit must be whole numbers, got \"\n                f\"{self.save_steps!r} / {self.save_total_limit!r}\"\n            ) from exc\n        if save_steps < 0:\n            raise ValueError(\"save_steps must be >= 0 (0 disables periodic checkpoints)\")\n        if save_total_limit < 0:\n            raise ValueError(\"save_total_limit must be >= 0 (0 keeps every checkpoint)\")\n        # A blank resume path (the Studio default when the field is present but unset) means \"fresh run\", not the outputs root.\n        resume_from_checkpoint = (\n            str(self.resume_from_checkpoint).strip()\n            if self.resume_from_checkpoint is not None\n            else \"\"\n        ) or None\n        # The H3 loop does not checkpoint: it neither writes a resume bundle nor restores one.\n        # Accepting these two silently was the dangerous part -- a caller handing over a resume\n        # bundle got a FRESH optimization that then overwrote the outputs it was meant to\n        # continue, and one asking for periodic saves got none, both discovered only after an\n        # expensive run. Refuse in validation, where it costs nothing, until the loop supports it.\n        if resolved_family in CHECKPOINTLESS_FAMILIES:\n            if resume_from_checkpoint:\n                raise ValueError(\n                    f\"resume_from_checkpoint is not supported for {resolved_family}: its trainer \"\n                    f\"writes no checkpoint bundle, so there is nothing to continue from and the \"","sourceCodeStart":1044,"sourceCodeEnd":1080,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/core/training/diffusion_train_common.py#L1044-L1080","documentation":"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.","triggerScenarios":"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.","commonSituations":"Migrating configs from frameworks where -1 means 'off'; arithmetic that derives save cadence from run length and can go negative for short runs.","solutions":["Use 0 to disable periodic checkpointing — not a negative number.","Set a positive step count (e.g. 500) for periodic saves.","Clamp derived values: save_steps = max(0, computed)."],"exampleFix":"# before\nconfig = TrainConfig(save_steps=-1)\n\n# after\nconfig = TrainConfig(save_steps=0)","handlingStrategy":"validation","validationCode":"def check_save_steps(v) -> int:\n    n = int(v or 0)\n    if n < 0:\n        raise ValueError(f\"save_steps must be >= 0 (0 disables periodic checkpoints), got {v!r}\")\n    return n","typeGuard":"def is_valid_save_steps(v) -> bool:\n    try:\n        return int(v or 0) >= 0\n    except (TypeError, ValueError):\n        return False","tryCatchPattern":"try:\n    session.submit_training(config)\nexcept ValueError as e:\n    if \"save_steps must be >= 0\" in str(e):\n        config.save_steps = 0\n        session.submit_training(config)\n    else:\n        raise","preventionTips":["Use 0 (not -1) as the 'periodic checkpoints off' sentinel here.","Clamp derived cadences: save_steps = max(0, total_steps // n).","Purge -1 sentinels when porting configs from other trainers."],"tags":["training","checkpointing","configuration","validation"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}