unslothai/unsloth · error · ValueError

learning_rate must be a number, got {self.learning_rate!r}

Error message

learning_rate must be a number, got {self.learning_rate!r}

What it means

learning_rate is coerced with float() because the Studio config path can deliver it as a string ('1e-4'). If the value is not numeric and not a numeric string, the coercion raises TypeError/ValueError and the config re-raises with the original value echoed.

Source

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

                    "flow_shift must be a finite number > 0 (1.0 disables the shift), or 'auto'"
                )
        try:
            cfg_dropout = float(self.cfg_dropout or 0.0)
        except (TypeError, ValueError) as exc:
            raise ValueError(f"cfg_dropout must be a number, got {self.cfg_dropout!r}") from exc
        if not 0.0 <= cfg_dropout <= 1.0:
            raise ValueError("cfg_dropout must be between 0 and 1")
        weighting_scheme = str(self.weighting_scheme or "none").strip().lower()
        if weighting_scheme not in ("none", "bell"):
            raise ValueError("weighting_scheme must be one of none / bell")
        # A zero/negative gamma would zero out (or invert) the min-SNR weight and silently train on a degenerate loss; None is the documented disable.
        if self.snr_gamma is not None and float(self.snr_gamma) <= 0:
            raise ValueError("snr_gamma must be > 0, or null to disable min-SNR weighting")
        # learning_rate can arrive as a string ("1e-4") from the Studio config path, so coerce it before AdamW sees it.
        try:
            learning_rate = float(self.learning_rate)
        except (TypeError, ValueError) as exc:
            raise ValueError(f"learning_rate must be a number, got {self.learning_rate!r}") from exc
        if learning_rate <= 0:
            raise ValueError("learning_rate must be > 0")
        alpha = self.lora_alpha if self.lora_alpha is not None else self.lora_rank
        targets = tuple(self.lora_target_modules) or DEFAULT_LORA_TARGETS
        # A blank Hub token (the Studio default when none is configured) must load anonymously, not as an explicit empty credential.
        token = self.hf_token.strip() if isinstance(self.hf_token, str) else self.hf_token
        from core.inference.diffusion_families import (
            _is_local_path,
            mirror_repo,
            prefer_ungated_mirror,
            upstream_is_gated,
        )

        if resolved_family == "sdxl":
            fetch_base_model = self.base_model
        else:
            fetch_base_model = prefer_ungated_mirror(self.base_model, token or None)
            # For a GATED upstream and no token, the cache preference has to be overridden: the

View on GitHub (pinned to 203007d190)

Solutions

  1. Pass a plain number (0.0001) or a clean numeric string ('1e-4').
  2. Sanitize free-text input: strip whitespace and reject anything float() cannot parse before it reaches the config.
  3. Check for stray characters pasted alongside the value (units, commas, currency symbols).

Example fix

# before
cfg = DiffusionLoraConfig(learning_rate='0.0001 ')  # ok, but '1e-4 lr' fails
# after
cfg = DiffusionLoraConfig(learning_rate='1e-4')  # or 0.0001
Defensive patterns

Strategy: validation

Validate before calling

def coerce_lr(v):
    if isinstance(v, str):
        v = v.strip().replace(',', '')  # tolerate '1e-4 ' / '0,0001'
    lr = float(v)
    if lr <= 0:
        raise ValueError('learning_rate must be > 0')
    return lr

Type guard

def is_valid_learning_rate(v) -> bool:
    try:
        return float(v) > 0
    except (TypeError, ValueError):
        return False

Prevention

When it happens

Trigger: learning_rate='lr', '1e-4x', a list, or None handled incorrectly at the call site (None raises TypeError here, unlike cfg_dropout's 'or' fallback).

Common situations: Hand-edited YAML/JSON with a typo; a UI field left with placeholder text like 'e.g. 0.0001'; unit-suffixed strings like '4e-5lr'.

Related errors


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