Lightning-AI/pytorch-lightning · error · ValueError

Invalid mode. Has to be min or max, found

Error message

Invalid mode. Has to be min or max, found {self.mode}

What it means

`SpikeDetection._is_better` compares the current metric against the running mean using directionality given by `mode`, which must be "min" or "max". If mode is anything else (typo, wrong case, None), it raises ValueError listing the offending mode.

Solutions

  1. Set mode to exactly "min" or "max" (lowercase)
  2. If using "minimize"/"maximize" style configs, map them: `{"minimize": "min", "maximize": "max"}[cfg.mode]`
  3. Add a config assertion at startup so invalid modes fail fast

Example fix

# before
SpikeDetection(mode="minimize")  # or "MAX"

# after
SpikeDetection(mode="min")  # loss; use "max" for accuracy-style metrics
Defensive patterns

Strategy: validation

Validate before calling

mode = {"minimize": "min", "maximize": "max"}.get(mode, mode)
assert mode in ("min", "max"), f"bad mode: {mode}"

Prevention

When it happens

Trigger: Constructing `SpikeDetection(mode="minimum")`, `mode="MAX"`, `mode=None`, or mutating `self.mode` after init; any value other than exactly "min"/"max" hits the raise when a spike check is evaluated.

Common situations: Config files using verbose values like "minimize"/"maximize" (common with other libraries' `mode` conventions), YAML casing issues, copying configs from early-stopping setups that accept different strings.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of Lightning-AI/pytorch-lightning@9fed5c27d2 (2026-08-28). Data as JSON: /api/errors/fbf535876a79998e. Report an issue: GitHub.

Appendix: source

Thrown at src/lightning/fabric/utilities/spike.py:140

            with open(self.exclude_batches_path, "w") as f:
                json.dump(self.bad_batches, f, indent=4)

        raise TrainingSpikeException(batch_idx=batch_idx)

    def _check_atol(self, val_a: Union[float, torch.Tensor], val_b: Union[float, torch.Tensor]) -> bool:
        return (self.atol is None) or bool(abs(val_a - val_b) >= abs(self.atol))  # type: ignore

    def _check_rtol(self, val_a: Union[float, torch.Tensor], val_b: Union[float, torch.Tensor]) -> bool:
        return (self.rtol is None) or bool(abs(val_a - val_b) >= abs(self.rtol * val_b))  # type: ignore

    def _is_better(self, diff_val: torch.Tensor) -> bool:
        if self.mode == "min":
            return bool((diff_val <= 0.0).all())
        if self.mode == "max":
            return bool((diff_val >= 0).all())

        raise ValueError(f"Invalid mode. Has to be min or max, found {self.mode}")

    def _update_stats(self, val: torch.Tensor) -> None:
        # only update if finite
        self.running_mean.update(val)
        self.last_val = val

    def state_dict(self) -> dict[str, Any]:
        return {
            "last_val": self.last_val.item() if isinstance(self.last_val, torch.Tensor) else self.last_val,
            "mode": self.mode,
            "warmup": self.warmup,
            "atol": self.atol,
            "rtol": self.rtol,
            "bad_batches": self.bad_batches,
            "bad_batches_path": self.exclude_batches_path,
            "running": self.running_mean.state_dict(),
            "mean": self.running_mean.base_metric.state_dict(),
        }

View on GitHub (pinned to 9fed5c27d2)