From fdce76db2eaeab55795fc1ca5a2e237101278f04 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Mon, 27 Jul 2026 09:50:02 +0000 Subject: [PATCH] Do not advertise a family the installed diffusers cannot build The newer families (Z-Image, Krea 2, FLUX.2, LTX-2, HunyuanImage) exist only from diffusers 0.39, and 0.39 cannot be installed on Python 3.9 at all -- diffusers dropped 3.9 in 0.38, so the requirement is conditional or the whole extra becomes unresolvable. On such an environment the picker still offered those rows, every pick failed deterministically, and the error's advice to run pip install -U diffusers could not fix it without also upgrading Python. The cached-repo picker now applies the same availability check validate_load_request does, which is keyed on the pipeline class actually present rather than on the Python version, so it is also right for an intentionally pinned older diffusers on 3.10+. Fails open when diffusers cannot be imported at all: that is a different problem and the load path reports it. --- .../core/inference/diffusion_families.py | 19 +++++++++ studio/backend/routes/models.py | 11 ++++- .../backend/tests/test_cached_gguf_routes.py | 41 +++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/studio/backend/core/inference/diffusion_families.py b/studio/backend/core/inference/diffusion_families.py index 4212ea13cd..b0d7c8658e 100644 --- a/studio/backend/core/inference/diffusion_families.py +++ b/studio/backend/core/inference/diffusion_families.py @@ -641,6 +641,25 @@ def assert_pipeline_class_available(pipeline_class: str, family_name: str) -> No ) +def family_pipeline_available(fam: Optional[DiffusionFamily]) -> bool: + """True when the installed diffusers actually has this family's pipeline class. + + The boolean twin of ``assert_pipeline_class_available``, for the listing routes: the newer + families exist only from diffusers 0.39, and the packaging leaves an older diffusers + installable on Python 3.9 (diffusers dropped 3.9 in 0.38, so the 0.39 floor has to be + conditional or the extra becomes unresolvable). Advertising Z-Image or Krea 2 in the picker + on such an environment offers a pick that can only fail, and no `pip install -U diffusers` + can fix it without also upgrading Python. Fails OPEN (True) when diffusers cannot be + imported at all, so a listing never hides a model over an unrelated import problem.""" + if fam is None: + return False + try: + import diffusers + except Exception: # noqa: BLE001 -- no diffusers here: the load path reports it properly + return True + return hasattr(diffusers, fam.pipeline_class) + + def family_gguf_loadable(fam: DiffusionFamily) -> bool: """True when a GGUF transformer can be assembled for this family. diff --git a/studio/backend/routes/models.py b/studio/backend/routes/models.py index 0f1f2634fe..54f0437e98 100644 --- a/studio/backend/routes/models.py +++ b/studio/backend/routes/models.py @@ -3672,11 +3672,18 @@ def _cached_repo_task(repo_info) -> Optional[str]: # the image families this backend can assemble, so an unsloth-hosted pipeline of an unsupported # class cleared the trust gate, was advertised as text-to-image, and then deterministically # failed validate_load_request, which detects the family the same way. + # The third gate is the installed diffusers: the newer families exist only from 0.39, which + # cannot be installed on Python 3.9 at all (diffusers dropped 3.9 in 0.38), so on such an + # environment a Z-Image / Krea 2 / FLUX.2 row is a pick that can only fail, and the suggested + # `pip install -U diffusers` cannot fix it. validate_load_request refuses the same way. try: from core.inference.diffusion import _is_trusted_diffusion_repo - from core.inference.diffusion_families import detect_family + from core.inference.diffusion_families import detect_family, family_pipeline_available - if not _is_trusted_diffusion_repo(repo_id) or detect_family(repo_id) is None: + fam = detect_family(repo_id) + if not _is_trusted_diffusion_repo(repo_id) or fam is None: + return None + if not family_pipeline_available(fam): return None return "text-to-image" except Exception: # noqa: BLE001 -- an import failure must not hide a usable repo diff --git a/studio/backend/tests/test_cached_gguf_routes.py b/studio/backend/tests/test_cached_gguf_routes.py index 43939054a7..a190e0d2f5 100644 --- a/studio/backend/tests/test_cached_gguf_routes.py +++ b/studio/backend/tests/test_cached_gguf_routes.py @@ -1854,3 +1854,44 @@ def test_cached_repo_task_agrees_with_the_image_loader(monkeypatch): assert ( task == "text-to-image" ) == loader_accepts, f"{repo_id}: picker task={task} but loader accepts={loader_accepts}" + + +def test_cached_picker_hides_a_family_this_diffusers_cannot_build(monkeypatch): + # The newer families exist only from diffusers 0.39, which cannot be installed on Python 3.9 at + # all (diffusers dropped 3.9 in 0.38). Advertising one there is a pick that can only fail, and + # `pip install -U diffusers` cannot fix it without a Python upgrade -- so the picker applies the + # same availability check validate_load_request does. + import types + + import routes.models as models_module + from core.inference.diffusion_families import detect_family, family_pipeline_available + + fam = detect_family("unsloth/Z-Image-Turbo") + assert fam is not None + # Present in this environment's diffusers, so the row is offered. + assert family_pipeline_available(fam) is True + + monkeypatch.setattr(models_module, "_repo_is_diffusers", lambda info: True) + monkeypatch.setattr( + "core.inference.diffusion._is_trusted_diffusion_repo", lambda repo_id: True + ) + info = types.SimpleNamespace(repo_id = "unsloth/Z-Image-Turbo") + assert models_module._cached_repo_task(info) == "text-to-image" + + # An older diffusers without the pipeline class hides the row instead. + monkeypatch.setattr( + "core.inference.diffusion_families.family_pipeline_available", lambda f: False + ) + assert models_module._cached_repo_task(info) is None + + +def test_family_pipeline_available_fails_open_without_diffusers(monkeypatch): + # No diffusers at all is a different problem; the load path reports it properly, and a listing + # must not silently hide every image model over it. + import sys + + from core.inference.diffusion_families import detect_family, family_pipeline_available + + monkeypatch.setitem(sys.modules, "diffusers", None) + assert family_pipeline_available(detect_family("unsloth/Z-Image-Turbo")) is True + assert family_pipeline_available(None) is False