unslothai/unsloth · warning · ValueError

fraction must be a number, not a boolean

Error message

fraction must be a number, not a boolean

What it means

Pydantic validation failure (HTTP 422) from a mode='before' validator on VramBudgetPayload.fraction. Because bool subclasses int in Python, non-strict pydantic parsing would silently coerce JSON true to 1.0 and store the maximum VRAM budget; the validator runs before coercion and rejects any boolean value explicitly. Valid floats must also satisfy ge=VRAM_FRACTION_MIN and le=VRAM_FRACTION_MAX.

Source

Thrown at studio/backend/routes/settings.py:586

    # that residency will not fully pin a model larger than this. None means
    # unlimited (macOS) or not applicable (Windows).
    memlock_limit_bytes: Optional[int] = None


class VramBudgetPayload(BaseModel):
    # None clears the stored budget so env/default applies again; it cannot also
    # mean "leave untouched" as the model-memory switches do, since there is one
    # field. Hence required, not defaulted: with a default, {} would mean "clear it"
    # and a client that dropped the field would silently discard the stored budget.
    fraction: Optional[float] = Field(ge = VRAM_FRACTION_MIN, le = VRAM_FRACTION_MAX)

    @field_validator("fraction", mode = "before")
    @classmethod
    def _reject_bool(cls, value: object) -> object:
        # bool subclasses int, so non-strict parsing turns True into 1.0 and stores
        # the max budget instead of 422; pydantic coerces before the util's guard.
        if isinstance(value, bool):
            raise ValueError("fraction must be a number, not a boolean")
        return value


class VramBudgetResponse(BaseModel):
    fraction: float
    # False when inherited from UNSLOTH_VRAM_FRACTION or the default, so the UI
    # knows whether clearing it would change anything.
    is_stored: bool
    default_fraction: float = VRAM_FRACTION_DEFAULT
    min_fraction: float = VRAM_FRACTION_MIN
    max_fraction: float = VRAM_FRACTION_MAX
    # Read when a load sizes itself, so a change cannot reach a running child.
    reload_required: bool


class HuggingFaceCachePayload(BaseModel):
    cache_home: Optional[str] = Field(default = None, max_length = 4096)

View on GitHub (pinned to 203007d190)

Solutions

  1. Send an explicit float within [VRAM_FRACTION_MIN, VRAM_FRACTION_MAX], e.g. 0.8; send null to clear the stored budget.
  2. Type-check client-side: reject boolean before submitting.
  3. If you generate payloads from schemas, ensure fraction is a number type, never a bool.

Example fix

// before
await api.put('/settings/vram-budget', { fraction: true }); // 422

// after
if (typeof fraction !== 'number') throw new TypeError('fraction must be a number');
await api.put('/settings/vram-budget', { fraction: 0.8 });
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof fraction !== 'number' && fraction !== null) {
  throw new TypeError('fraction must be a number or null');
}
await api.put('/settings/vram-budget', { fraction });

Type guard

function isVramFraction(v: unknown): v is number {
  return typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 1;
}

Try / catch

try { await api.put('/settings/vram-budget', { fraction }); }
catch (e) {
  if (e.status === 422 && /boolean/.test(e.detail?.toString() ?? '')) { fixPayloadType('fraction'); return; }
  throw e;
}

Prevention

When it happens

Trigger: PUT the VRAM budget endpoint with fraction: true or false in the JSON body; also any truthy value a client serializes as a JS boolean (e.g. fraction: isMax) instead of the intended number.

Common situations: JS frontend binding a checkbox/toggle state to fraction; YAML/JSON config generated from a template where a number became a boolean; API consumers testing with literal true.

Related errors


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