unslothai/unsloth · error · ValueError

ControlNet is not yet supported on the native sd.cpp engine;

Error message

ControlNet is not yet supported on the native sd.cpp engine; run on a GPU (diffusers) for ControlNet conditioning.

What it means

ValueError: ControlNet conditioning is not implemented on the native sd.cpp engine. Note the preceding no-op: controlnet tuples whose strength component (index 3) is None/0/0.0 are silently dropped (matching diffusers semantics), so this fires only for genuinely active ControlNet.

Source

Thrown at studio/backend/core/inference/sd_cpp_backend.py:2074

            or mask_image is not None
            or reference_images
            or (upscale is not None and upscale > 1)
        ):
            raise ValueError(
                "img2img / inpaint / reference / upscale are not yet supported on the native "
                "sd.cpp engine; run on a GPU (diffusers) for image-conditioned workflows."
            )
        if prompts is not None or seeds is not None:
            raise ValueError(
                "Batched prompt/seed lists are not supported on the native sd.cpp engine "
                "(it renders serially); run on a GPU (diffusers) for batched generation, "
                "or use batch_size for a serial native batch."
            )
        # strength 0/None disables ControlNet (matches diffusers), so no-op it rather than 400.
        if controlnet is not None and controlnet[3] in (None, 0, 0.0):
            controlnet = None
        if controlnet is not None:
            raise ValueError(
                "ControlNet is not yet supported on the native sd.cpp engine; run on a GPU "
                "(diffusers) for ControlNet conditioning."
            )

        cancel = threading.Event()
        with self._generate_lock:
            with self._lock:
                state = self._state
                if state is None:
                    raise RuntimeError(DIFFUSION_NOT_LOADED_MSG)
                # A resident server can exit while idle; drop stale state and report not-loaded so the client gets the reload path, not a 500.
                if (
                    state.mode == "server"
                    and state.server is not None
                    and not state.server.is_alive()
                ):
                    self._state = None
                    raise RuntimeError(DIFFUSION_NOT_LOADED_MSG)

View on GitHub (pinned to 203007d190)

Solutions

  1. Run ControlNet workflows on the diffusers engine (GPU).
  2. Set the controlnet strength to 0/None (or omit controlnet) for native generation — it is treated as disabled.

Example fix

# before
backend.generate(prompt='a cat', controlnet=(model, image, None, 0.8))

# after
backend.generate(prompt='a cat', controlnet=(model, image, None, 0.0))  # no-op on native; or use diffusers
Defensive patterns

Strategy: validation

Validate before calling

if controlnet is not None and controlnet[3] not in (None, 0, 0.0):
    route_to_diffusers_engine(request)  # active ControlNet needs diffusers

Type guard

def controlnet_is_active(controlnet) -> bool:
    return controlnet is not None and controlnet[3] not in (None, 0, 0.0)

Prevention

When it happens

Trigger: generate() with controlnet not None and strength not in (None, 0, 0.0) on the native engine.

Common situations: Reusing diffusers-era request payloads that always attach a ControlNet; UI defaulting a control model on.

Related errors


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