diff --git a/modules/loaders/gguf_support.py b/modules/loaders/gguf_support.py index d4107971..a95e20eb 100644 --- a/modules/loaders/gguf_support.py +++ b/modules/loaders/gguf_support.py @@ -256,6 +256,10 @@ def _load(path: str, clip: bool) -> tuple[dict, dict, str]: sd = read(path) if isinstance(sd, tuple): # newer ComfyUI-GGUF: (state dict, {arch_str, metadata}) sd = sd[0] + if clip and any(str(k).startswith(("blk.", "v.", "mm.", "token_embd")) for k in sd): + # llama.cpp's own names came back unmapped: a vision projector (mmproj) or an architecture the pack + # let through without a name map. Built as a text encoder it would be an empty shell that "loaded". + raise ValueError("this .gguf is not a text encoder ComfyUI-GGUF can map (an mmproj / vision file, or an unsupported architecture)") options = {} ggml_ops = getattr(ops, "GGMLOps", None) if ggml_ops is not None: @@ -263,6 +267,8 @@ def _load(path: str, clip: bool) -> tuple[dict, dict, str]: return sd, options, f"gguf: quantized (ComfyUI-GGUF at {os.path.basename(d)})" except Exception as e: # noqa: BLE001 -- any refusal is a reason to fall back if importlib.util.find_spec("gguf") is None: + if isinstance(e, ModuleNotFoundError) and "gguf" in str(e): + raise RuntimeError(UNAVAILABLE) from e # the pack is here, the library it imports is not raise if clip: # The native reader keeps llama.cpp's tensor names; only the pack maps them back to a text encoder's. diff --git a/modules/loaders/tests/test_gguf_support.py b/modules/loaders/tests/test_gguf_support.py index 1495af38..8f119b68 100644 --- a/modules/loaders/tests/test_gguf_support.py +++ b/modules/loaders/tests/test_gguf_support.py @@ -286,3 +286,28 @@ def test_a_text_encoder_gguf_with_no_backend_says_the_pack_not_pip(monkeypatch): monkeypatch.setattr(gguf_support, "backend", lambda: None) with pytest.raises(RuntimeError, match="ComfyUI-GGUF"): gguf_support.load_clip_state_dict("/x.gguf") + + +def _pack(tmp_path, body): + d = tmp_path / "pack" + d.mkdir() + (d / "ops.py").write_text("class GGMLOps:\n pass\n") + (d / "loader.py").write_text(body) + return d + + +def test_a_vision_projector_gguf_the_pack_lets_through_is_refused_as_a_text_encoder(tmp_path, monkeypatch): + d = _pack(tmp_path, "def gguf_clip_loader(path):\n return {'v.blk.0.attn_q.weight': 1}\n") + monkeypatch.setattr(gguf_support, "_pack_dir", lambda: str(d)) + monkeypatch.delitem(sys.modules, gguf_support._PKG_NAME, raising=False) + with pytest.raises(RuntimeError, match="not supported"): + gguf_support.load_clip_state_dict("/mmproj.gguf") + + +def test_a_pack_whose_gguf_library_is_missing_says_how_to_fix_it_not_an_import_error(tmp_path, monkeypatch): + d = _pack(tmp_path, "import gguf_missing_for_this_test\ndef gguf_sd_loader(path):\n return {}\n") + monkeypatch.setattr(gguf_support, "_pack_dir", lambda: str(d)) + monkeypatch.delitem(sys.modules, gguf_support._PKG_NAME, raising=False) + monkeypatch.setattr(gguf_support.importlib.util, "find_spec", lambda name: None) + with pytest.raises(RuntimeError): + gguf_support.load_state_dict("/x.gguf")