unslothai/unsloth · error · ValueError

The LoRA selection changed, but a quantized (int8/fp8) trans

Error message

The LoRA selection changed, but a quantized (int8/fp8) transformer bakes its adapters at load time. Reload the model with the new adapter selection.

What it means

Also from `_adjust_baked_loras`: the quantized load DID bake adapters, but the generate request's adapter set differs from the baked one. Value-level changes are fine -- the same (name, path) set with new weights goes through `set_adapters` (absorbed by torch.compile guards), and disabling all is scale 0. But a different adapter set changes topology, which the baked-and-compiled quantized transformer cannot accept, so it raises a clean ValueError directing a reload.

Source

Thrown at studio/backend/core/inference/diffusion.py:5132

                pipe._unsloth_loras = tuple((n, p, 0.0) for (n, p, _w) in current)
            return
        desired = self._resolve_lora_set(
            specs,
            family = getattr(state.family, "name", None),
            hf_token = state.hf_token,
            cancel = cancel,
        )
        if desired == current:
            return
        if [(n, p) for (n, p, _w) in desired] == [(n, p) for (n, p, _w) in current]:
            # Same adapters, new weights: value-level change on the baked topology.
            pipe.set_adapters(
                [n for (n, _p, _w) in desired],
                adapter_weights = [w for (_n, _p, w) in desired],
            )
            pipe._unsloth_loras = desired
            return
        raise ValueError(
            "The LoRA selection changed, but a quantized (int8/fp8) transformer bakes its "
            "adapters at load time. Reload the model with the new adapter selection."
        )

    @staticmethod
    def _reset_step_cache(pipe: Any) -> None:
        """Clear the transformer's stateful step cache (FBCache) before a forward.

        diffusers keys FBCache residuals by cache context ("cond"/"uncond") on the
        long-lived transformer. The context exit does NOT reset them; the end of a
        pipeline ``__call__`` does, via ``maybe_free_model_hooks()`` -- but only when
        the call RETURNS. A call that raised (an OOM this generate() backs off from, a
        cancelled denoise, a failed prior request) leaves its own batch's residual on
        the resident transformer, and the next forward's first step then compares
        against it: a tensor-shape mismatch when the resolution/batch changed, or a
        stale-cache reuse otherwise. The transformer-level reset entry point is
        ``_reset_stateful_cache`` in diffusers 0.39 (``reset_stateful_hooks`` lives only
        on the HookRegistry, so a getattr for it on the transformer is a silent no-op).

View on GitHub (pinned to 203007d190)

Solutions

  1. Keep the adapter selection identical to what was baked; adjust only weights/scales at generate time.
  2. If a different set is needed, reload the model with the new adapter selection so it bakes correctly.
  3. Track the baked set (pipe._unsloth_loras / load response) and diff against requested specs client-side before calling.

Example fix

# before: baked [lora-a], requested [lora-b] -> raises
diffusion.generate(prompt="...", loras=[("lora-b", 0.8)])
# after: same baked adapter, new weight -- allowed
diffusion.generate(prompt="...", loras=[("lora-a", 0.6)])
Defensive patterns

Strategy: validation

Validate before calling

# Diff requested adapters against the baked set before generate
baked = {(n, p) for (n, p, _w) in state.baked_loras}
requested = [(n, p) for (n, p, _w) in normalize(lora_specs)]
if set(requested) != baked:
    await diffusion.load(model, transformer_quant=state.transformer_quant, loras=lora_specs)  # re-bake

Type guard

def same_adapter_set(desired: list, current: list) -> bool:
    """Baked topology matches: same (name, path) pairs regardless of weights."""
    return [(n, p) for (n, p, _w) in desired] == [(n, p) for (n, p, _w) in current]

Try / catch

try:
    diffusion.generate(prompt=p, loras=loras)
except ValueError as e:
    if "selection changed" in str(e):
        await diffusion.load(model, transformer_quant="int8", loras=loras)
        return diffusion.generate(prompt=p)
    raise

Prevention

When it happens

Trigger: Load-time loras baked as [("lora-a",1.0)] but generate() passes [("lora-b",0.8)], or adds/removes an adapter: the (name, path) list comparison `[(n,p) for desired] == [(n,p) for current]` fails, falling to the raise. Same set with different weights does NOT raise.

Common situations: UI LoRA picker letting users swap adapters on a quantized load; API clients building per-request LoRA lists without consulting what was baked; session state desync between load parameters and generate parameters.

Related errors


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