fix: .gguf loading stays inside the model folder, ignores a Manager-disabled pack, keeps big fp32 values finite; text-encoder picker hides .gguf without the pack; truthful refusal text (attacker round 10)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-10-15 19:03:32 +03:00
co-authored by Claude Sonnet 5.5
parent 40c3366214
commit b08fba7d58
4 changed files with 46 additions and 10 deletions
@@ -45,8 +45,7 @@ def routes(table, base, web):
async def _encoders(_req):
"""Encoder files and families the Enhance panel can offer (read fresh: files come and go)."""
from ...loaders.clip import nodes as clip_nodes
from ...loaders import gguf_support
files = [f for f in clip_nodes.encoder_files() if gguf_support.backend() == "pack" or not gguf_support.is_gguf(f)] # only the pack can read a text encoder's .gguf
files = clip_nodes.encoder_files()
return web.json_response({"files": files, "families": clip_nodes.clip_types()})
+3 -4
View File
@@ -43,10 +43,9 @@ def clip_types():
def encoder_files():
"""Text encoder files, `.gguf` included. Same reasoning as the diffusion
model loader's own file-list helper."""
return list(folder_paths.get_filename_list("text_encoders")) + \
gguf_support.gguf_names("text_encoders")
"""Text encoder files; `.gguf` ones only when the ComfyUI-GGUF pack is there to read them."""
ggufs = gguf_support.gguf_names("text_encoders") if gguf_support.backend() == "pack" else []
return list(folder_paths.get_filename_list("text_encoders")) + ggufs
class FunPackCLIPLoader(io.ComfyNode):
+11 -4
View File
@@ -115,6 +115,8 @@ def gguf_path(folder: str, name: str) -> Optional[str]:
pass
for root in roots:
cand = os.path.join(root or "", name)
if not os.path.realpath(cand).startswith(os.path.realpath(root or ".") + os.sep):
continue # a name that climbs out of the model folder
if os.path.isfile(cand):
return cand
return None
@@ -176,6 +178,8 @@ def _pack_dir() -> Optional[str]:
if not os.path.isdir(base):
return None
for entry in sorted(os.listdir(base)):
if entry.endswith(".disabled"): # Manager's way of switching a pack off
continue
d = os.path.join(base, entry)
loader = os.path.join(d, "loader.py")
if not os.path.isfile(loader) or not os.path.isfile(os.path.join(d, "ops.py")):
@@ -248,7 +252,8 @@ def _load(path: str, clip: bool) -> tuple[dict, dict, str]:
# Only the pack maps llama.cpp's tensor names back to a text encoder's; the native reader would hand over
# a state dict that looks loaded and is the wrong model.
raise RuntimeError("a text-encoder .gguf needs the ComfyUI-GGUF node pack to be read, and it is not installed. "
"Install it, or use a .safetensors encoder of the same model.")
"Installing it helps only for architectures it handles (not Gemma 4, not vision files); "
"otherwise use a .safetensors encoder of the same model.")
if which is None:
raise RuntimeError(UNAVAILABLE)
if which == "pack":
@@ -281,8 +286,8 @@ def _load(path: str, clip: bool) -> tuple[dict, dict, str]:
# The native reader keeps llama.cpp's tensor names; only the pack maps them back to a text encoder's.
# Loading them anyway gives a model that looks loaded and is the wrong one (a Gemma 4 .gguf did).
raise RuntimeError(f"this text-encoder .gguf could not be loaded as one: {e}. "
"Architectures ComfyUI-GGUF does not know (Gemma 4) and vision files are not supported; "
"if the file should work, update ComfyUI-GGUF. Or use a .safetensors encoder of the same model.") from e
"If the file is intact, its architecture is not one ComfyUI-GGUF handles (Gemma 4, vision files) "
"or ComfyUI-GGUF is out of date. Use a .safetensors encoder of the same model.") from e
if no_gguf:
raise
sd, options, note = _load_native(path, pack=True)
@@ -330,7 +335,9 @@ def _load_native(path: str, pack: bool = False) -> tuple[dict, dict, str]:
f"is not laid out the way the gguf package describes it.")
if shape:
t = t.reshape(shape)
sd[name] = t.to(torch.float16) if t.dtype == torch.float32 else t
if t.dtype == torch.float32 and t.numel(): # fp16 halves the size; a tensor too big for its range goes bf16 instead of inf
t = t.to(torch.float16) if t.abs().max() < 60000 else t.to(torch.bfloat16)
sd[name] = t
note = (f"gguf: dequantized at load ({quantized} quantized tensors expanded) -- the "
f"file loads, but it occupies its full size in VRAM."
+ ("" if pack else " Install ComfyUI-GGUF to keep it quantized."))
@@ -330,3 +330,34 @@ def test_a_pack_whose_gguf_library_is_missing_says_how_to_fix_it_not_an_import_e
gguf_support.load_state_dict("/x.gguf")
with pytest.raises(RuntimeError, match="installed, but the `gguf` package"):
gguf_support.load_clip_state_dict("/x.gguf")
def test_a_name_that_climbs_out_of_the_model_folder_is_not_found(fake_folders):
(fake_folders / "secret.gguf").write_bytes(b"x")
assert gguf_support.gguf_path("text_encoders", "../secret.gguf") is None
assert gguf_support.gguf_path("text_encoders", "gemma-Q5_K.gguf")
def test_a_pack_switched_off_by_manager_is_not_used(fake_folders):
pack = fake_folders / "custom_nodes" / "ComfyUI-GGUF.disabled"
pack.mkdir(parents=True)
(pack / "ops.py").write_text("")
(pack / "loader.py").write_text("def gguf_sd_loader(p): pass\n")
assert gguf_support._pack_dir() is None
(fake_folders / "custom_nodes" / "ComfyUI-GGUF").mkdir()
(fake_folders / "custom_nodes" / "ComfyUI-GGUF" / "ops.py").write_text("")
(fake_folders / "custom_nodes" / "ComfyUI-GGUF" / "loader.py").write_text("def gguf_sd_loader(p): pass\n")
assert gguf_support._pack_dir().endswith("ComfyUI-GGUF")
def test_native_load_keeps_an_f32_value_too_big_for_fp16_finite(tmp_path):
gguf = pytest.importorskip("gguf")
torch = pytest.importorskip("torch")
import numpy as np
f = tmp_path / "t.gguf"
w = gguf.GGUFWriter(str(f), "llama")
w.add_tensor("a", np.array([[70000.0, 1.0]], dtype=np.float32))
w.write_header_to_file(); w.write_kv_data_to_file(); w.write_tensors_to_file(); w.close()
sd, _o, _n = gguf_support._load_native(str(f))
assert torch.isfinite(sd["a"].float()).all()