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:
co-authored by
Claude Sonnet 5.5
parent
40c3366214
commit
b08fba7d58
@@ -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()})
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user