unslothai/unsloth · error · ValueError

Unsloth MLX: max_grad_value={max_grad_value} must be finite

Error message

Unsloth MLX: max_grad_value={max_grad_value} must be finite and >= 0 (0 or None disables elementwise clipping).

What it means

Raised by the Unsloth MLX training worker when the config value max_grad_value is negative or non-finite (NaN or +/-inf). max_grad_value controls elementwise gradient clipping in the MLX trainer; 0 or None disables it, so any other value must be a finite float >= 0. This is a re-validation in the worker because the worker can be called directly, bypassing the normalization that training.py performs on the main path.

Source

Thrown at studio/backend/core/training/worker.py:3014

    # ── 6. Create trainer ──
    raw_eval_steps = config.get("eval_steps", 0)
    if evaluation_enabled(raw_eval_steps):
        eval_steps_value = float(raw_eval_steps)
    else:
        eval_steps_value = 0.0
    if 0 < eval_steps_value < 1:
        eval_steps_val = max(1, int(eval_steps_value * max_steps))
    else:
        eval_steps_val = int(eval_steps_value)

    # Re-validate for direct worker callers; training.py normalizes the main path.
    max_grad_norm = _resolve_mlx_max_grad_norm(config.get("max_grad_norm"))
    max_grad_value = config.get("max_grad_value")
    if max_grad_value is not None:
        max_grad_value = float(max_grad_value)
        if max_grad_value < 0 or not math.isfinite(max_grad_value):
            raise ValueError(
                f"Unsloth MLX: max_grad_value={max_grad_value} must be finite and >= 0 "
                "(0 or None disables elementwise clipping)."
            )
    max_grad_leaf_norm = config.get("max_grad_leaf_norm")
    if max_grad_leaf_norm is not None:
        max_grad_leaf_norm = float(max_grad_leaf_norm)
        if max_grad_leaf_norm < 0 or not math.isfinite(max_grad_leaf_norm):
            raise ValueError(
                f"Unsloth MLX: max_grad_leaf_norm={max_grad_leaf_norm} must be finite and >= 0 "
                "(0 or None disables proportional leaf-norm clipping)."
            )
    weight_decay = config.get("weight_decay", 0.001)
    weight_decay = 0.001 if weight_decay is None else float(weight_decay)

    mlx_config_kwargs = dict(
        per_device_train_batch_size = batch_size,
        gradient_accumulation_steps = grad_accum,
        max_steps = max_steps,

View on GitHub (pinned to 203007d190)

Solutions

  1. Set max_grad_value to 0 or remove it (None) if you want elementwise clipping disabled, or to a finite positive float like 1.0.
  2. If you call the worker directly, normalize the value before dispatch (mirror what training.py does) so NaN/negative sentinels are converted to None.
  3. Check the upstream config source (UI, YAML, API payload) for NaN-inducing values such as .nan, float('inf'), or -1 sentinels and fix them there.

Example fix

# before
config = {"max_grad_value": -1}  # raises ValueError

# after
config = {"max_grad_value": None}  # disables elementwise clipping
Defensive patterns

Strategy: validation

Validate before calling

import math

def valid_max_grad_value(value):
    return value is None or (isinstance(value, (int, float)) and math.isfinite(value) and value >= 0)

if not valid_max_grad_value(config.get("max_grad_value")):
    config["max_grad_value"] = None  # or raise your own clearer error early

Type guard

def is_valid_max_grad_value(v) -> bool:
    return v is None or (isinstance(v, (int, float)) and not isinstance(v, bool) and math.isfinite(v) and v >= 0)

Try / catch

try:
    run_training(config)
except ValueError as e:
    if "max_grad_value" in str(e):
        config["max_grad_value"] = None
        run_training(config)
    else:
        raise

Prevention

When it happens

Trigger: Calling the MLX training worker with config['max_grad_value'] set to a negative number, NaN, or inf (e.g. from a math expression that overflowed, a string parsed later, or -1 used as a 'disable' sentinel). Passing 0 or None does NOT trigger it; passing a finite positive float does not either.

Common situations: Users copy a PyTorch-style config where -1 or 1e9 means 'off'; a UI/API forwards an empty string that converts to NaN; a YAML config has .nan or .inf; hyperparameter sweeps generate out-of-range values.

Related errors


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