{"record":{"id":"2e94ed357f03e7a5","repo":"unslothai/unsloth","slug":"ema-decay-must-be-a-number-got-self-ema-decay-r","errorCode":null,"errorMessage":"ema_decay must be a number, got {self.ema_decay!r}","messagePattern":"ema_decay must be a number, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"studio/backend/core/training/diffusion_train_common.py","lineNumber":1091,"sourceCode":"        # 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 \"\n                    f\"run would silently start over and overwrite its output. Start a fresh run.\"\n                )\n            if save_steps:\n                raise ValueError(\n                    f\"save_steps is not supported for {resolved_family}: its trainer writes no \"\n                    f\"checkpoint bundle. Leave it at 0; the adapter is still saved at the end.\"\n                )\n        try:\n            ema_decay = float(self.ema_decay or 0.0)\n        except (TypeError, ValueError) as exc:\n            raise ValueError(f\"ema_decay must be a number, got {self.ema_decay!r}\") from exc\n        # 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).\n        if not 0.0 <= ema_decay < 1.0:\n            raise ValueError(\"ema_decay must be in [0, 1); 0 disables the EMA adapter\")\n        # A blank cond_cache_dir (the Studio default when unset) means \"off\", not cwd.\n        cond_cache_dir = (\n            str(self.cond_cache_dir).strip() if self.cond_cache_dir is not None else \"\"\n        ) or None\n        compile_transformer = str(self.compile_transformer or \"auto\").strip().lower()\n        if compile_transformer not in (\"off\", \"on\", \"auto\"):\n            raise ValueError(\"compile_transformer must be one of off / on / auto\")\n        base_precision = str(self.base_precision or \"nf4\").strip().lower()\n        if base_precision not in (\"nf4\", \"bf16\", \"int8\", \"fp8\", \"mxfp8\", \"auto\"):\n            raise ValueError(\"base_precision must be one of nf4 / bf16 / int8 / fp8 / mxfp8 / auto\")\n        # 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.\n        if resolved_family != \"sdxl\" and base_precision in (\"bf16\", \"int8\", \"fp8\", \"mxfp8\"):\n            if repo_is_prequantized(self.base_model):\n                raise ValueError(\n                    f\"base_precision={base_precision!r} needs a dense base repo, but \"","sourceCodeStart":1073,"sourceCodeEnd":1109,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/core/training/diffusion_train_common.py#L1073-L1109","documentation":"Raised when ema_decay cannot be converted with float(): the validator wraps the conversion in try/except (TypeError, ValueError) and echoes the offending value. EMA (exponential moving average of adapter weights) needs a numeric decay; values like a list, dict, or a non-numeric string fail here. Note float('') also raises ValueError, but a falsy value hits the `or 0.0` default first — only truthy non-numeric values land here.","triggerScenarios":"Passing ema_decay='auto', '0.999 ' is fine but 'high' fails, [0.99] (list), {'decay': 0.99} (dict), or an object without __float__. Typically a deserialization mismatch where the field arrives as a nested JSON structure instead of a scalar.","commonSituations":"JSON config schemas that model ema as an object ({'enabled': true, 'decay': 0.99}) forwarded whole; string placeholders from UI; version upgrades that changed the field's expected shape.","solutions":["Pass a plain number, e.g. ema_decay=0.999, or None/0 to disable EMA.","If your config nests EMA options, unwrap the scalar: ema_decay = cfg['ema']['decay'].","The error echoes the value — check its repr to see what actually arrived."],"exampleFix":"# before\nconfig = TrainConfig(ema_decay={'decay': 0.999})\n\n# after\nconfig = TrainConfig(ema_decay=0.999)","handlingStrategy":"validation","validationCode":"def check_ema_decay(v) -> float:\n    if v in (None, \"\", 0, 0.0):\n        return 0.0  # disabled\n    if isinstance(v, (list, tuple, dict, bool)):\n        raise ValueError(f\"ema_decay must be a number, got {v!r}\")\n    return float(v)  # raises for junk strings like 'auto'","typeGuard":"def is_valid_ema_decay(v) -> bool:\n    if v in (None, \"\"):\n        return True\n    if isinstance(v, bool) or not isinstance(v, (int, float, str)):\n        return False\n    try:\n        float(v)\n        return True\n    except ValueError:\n        return False","tryCatchPattern":"try:\n    session.submit_training(config)\nexcept ValueError as e:\n    if \"ema_decay must be a number\" in str(e):\n        config.ema_decay = None  # disable EMA rather than guess the intended value\n        session.submit_training(config)\n    else:\n        raise","preventionTips":["Pass a scalar number (e.g. 0.999) or None; never forward a nested EMA settings object into this one field.","Unwrap structured configs at your boundary: cfg['ema']['decay'], not cfg['ema'].","The error echoes the repr — log it to see what shape actually arrived."],"tags":["training","ema","configuration","validation","type-coercion"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}