unslothai/unsloth · error · ValueError

{exc}

Error message

{exc}

What it means

A FileNotFoundError from resolve_controlnet (unknown ControlNet id not in the catalog) is re-raised as ValueError with the original message. The comment in source makes the intent explicit: the route maps ValueError to HTTP 400, so an unknown CN id surfaces as a client error rather than a bare 500.

Source

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

                            transformer_quant = state.transformer_quant,
                        ):
                            raise ValueError(
                                "ControlNet is not supported for this model/quantisation on the "
                                "diffusers engine (needs a bf16 or bnb-4bit load of a family with a "
                                "ControlNet pipeline; not GGUF-via-diffusers or torchao fp8/int8)."
                            )
                        # Decode + preprocess the control image FIRST so a bad image 400s before any CN download, at the OUTPUT size.
                        src = decode_b64_image(cn_image_b64, mode = "RGB")
                        control_pil = diffusion_controlnet.preprocess_control(src, cn_type).resize(
                            (width, height), Image.LANCZOS
                        )
                        try:
                            resolved_cn = diffusion_controlnet.resolve_controlnet(
                                cn_id, family = state.family.name
                            )
                        except FileNotFoundError as exc:
                            # An unknown CN id -> 400, not 500 (the route maps ValueError).
                            raise ValueError(str(exc)) from exc
                        pipe = self._controlnet_pipe(state, resolved_cn, cancel)
                        workflow = "controlnet"
                        cn_scale, cn_gstart, cn_gend = cn_strength, cn_gs, cn_ge
                        # Flux Union CN selects its head by an integer control_mode; map the type.
                        cn_mode = diffusion_controlnet.union_control_mode(cn_id, cn_type)
                # A prompt LIST batches plain text-to-image only: conditioned workflows take one image per call and a silent broadcast would pair every prompt with it.
                if uniform_prompt(jobs) is None and workflow != "txt2img":
                    raise ValueError(
                        "A prompts list is supported for plain text-to-image only; the "
                        f"{workflow} workflow takes one prompt per call (seed lists still work)."
                    )
                # Snap odd-sized inputs (and the mask) to a multiple of 16 where the OUTPUT size comes from the input image.
                if init_pil is not None and workflow in ("img2img", "inpaint", "edit"):
                    # img2img/inpaint take output size from the upload, so bound the longest side to 2048 (a phone photo would OOM).
                    if workflow == "img2img":
                        # ...and bound Transform by the REQUESTED size too, so the Resolution
                        # control caps the output instead of being inert. img2img only: an
                        # inpaint payload is the canvas the mask was painted against (Extend

View on GitHub (pinned to 203007d190)

Solutions

  1. Use a ControlNet id from the current catalog (list available ControlNets via the app's catalog/registry endpoint or diffusion_controlnet catalog helpers).
  2. Check for typos in the repo id / spec id — full repo ids must match a curated entry exactly.
  3. If the CN was local ('source: local'), confirm it is still present on disk and re-registered.

Example fix

# before
controlnet=("xinsir-controlnet-sdxl-canny", img, "canny", 0.8, 0.0, 1.0)  # typo'd id
# after
controlnet=("xinsir-controlnet-v1.1-sdxl-canny", img, "canny", 0.8, 0.0, 1.0)  # exact catalog id
Defensive patterns

Strategy: validation

Validate before calling

from core.inference.diffusion_controlnet import _catalog_by_id  # or a public list endpoint

def valid_cn_id(spec_id: str) -> bool:
    return spec_id in _catalog_by_id() or any(
        e.repo_id == spec_id for e in getattr(__import__("core.inference.diffusion_controlnet", fromlist=["_CURATED"]), "_CURATED")
    )

Try / catch

try:
    out = engine.generate(prompt=p, controlnet=(cn_id, img, t, s, gs, ge))
except ValueError as e:
    if "ControlNet" in str(e) and "no longer present" not in str(e):
        refresh_cn_catalog_and_repick()  # 400-class: fix the id, don't retry the same one
    else:
        raise

Prevention

When it happens

Trigger: Passing a controlnet tuple whose id does not match any curated catalog entry or known repo id — e.g. a typo'd id, a repo that was never registered, or an entry removed from the catalog.

Common situations: Hand-writing API payloads with a ControlNet repo id from memory; catalogs drifting between versions so a previously valid id disappears; stale UI state referencing a deleted curated entry.

Related errors


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