{"record":{"id":"d0eb236195b875c6","repo":"hiyouga/LlamaFactory","slug":"save-epochs-is-not-supported-with-dynamic-batch","errorCode":null,"errorMessage":"`save_epochs` is not supported with `dynamic_batching`; use `save_steps` instead.","messagePattern":"`save_epochs` is not supported with `dynamic_batching`; use `save_steps` instead\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/llamafactory/v1/config/training_args.py","lineNumber":198,"sourceCode":"        except ImportError:\n            pass\n\n        # The optimizer learning rate has a single source of truth: ``learning_rate``.\n        # Propagate it into ``optim_config[\"lr\"]`` so optimizer plugins (e.g. Muon) pick it up\n        # via ``optim_config.get(\"lr\")`` without each plugin needing a separate ``learning_rate`` arg.\n        if self.optim_config is not None:\n            if \"lr\" in self.optim_config:\n                logger.warning_rank0(\n                    \"`optim_config.lr` is overridden by `learning_rate`; set the learning rate via \"\n                    \"`learning_rate` instead and remove `lr` from `optim_config`.\"\n                )\n            self.optim_config[\"lr\"] = self.learning_rate\n\n        if str(self.batching_strategy) == str(BatchingStrategy.DYNAMIC_BATCHING):\n            if self.max_steps is None or self.max_steps <= 0:\n                raise ValueError(\"`dynamic_batching` requires `max_steps` because it is step-driven.\")\n            if self.save_epochs is not None:\n                raise ValueError(\"`save_epochs` is not supported with `dynamic_batching`; use `save_steps` instead.\")\n","sourceCodeStart":180,"sourceCodeEnd":199,"githubUrl":"https://github.com/hiyouga/LlamaFactory/blob/f28afaf6355af515454dfb16c97d728307c93897/src/llamafactory/v1/config/training_args.py#L180-L199","documentation":"v1 `TrainingArguments.__post_init__` rejects `save_epochs` together with `batching_strategy: dynamic_batching`. Dynamic batching is step-driven and never computes epoch boundaries, so epoch-conditional checkpointing is meaningless; `save_steps` must be used for periodic checkpoints instead.","triggerScenarios":"Configuring `batching_strategy: dynamic_batching` plus a non-null `save_epochs` in the v1 training args.","commonSituations":"Copying a checkpointing block (`save_epochs: 1`) from an epoch-based config into a dynamic-batching run.","solutions":["Delete `save_epochs` and set `save_steps` (e.g. `save_steps: 500`)","If epoch checkpoints are a hard requirement, switch back to a non-dynamic batching strategy and use `max_steps`/`epochs` normally"],"exampleFix":"# before (yaml)\ntraining:\n  batching_strategy: dynamic_batching\n  max_steps: 1000\n  save_epochs: 1\n\n# after (yaml)\ntraining:\n  batching_strategy: dynamic_batching\n  max_steps: 1000\n  save_steps: 500","handlingStrategy":"validation","validationCode":"def validate_checkpointing(cfg: dict) -> None:\n    if str(cfg.get(\"batching_strategy\", \"\")) == \"dynamic_batching\" and cfg.get(\"save_epochs\") is not None:\n        raise SystemExit(\"use save_steps with dynamic_batching, not save_epochs\")","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Standardize on save_steps for all step-driven runs","Config lint: forbid save_epochs whenever batching_strategy is dynamic_batching","When porting epoch-based configs, audit every `*_epochs` key"],"tags":["v1","configuration","checkpointing","batching"],"backgroundTag":null,"analyzedSha":"f28afaf6355af515454dfb16c97d728307c93897","analyzedAt":"2026-08-14T21:57:28.298Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}