Disable automatic GGUF finalization retries

This commit is contained in:
Marcel Hofer
2026-06-19 19:14:46 +02:00
parent 07b88246c2
commit 52503f95f8
7 changed files with 237 additions and 20 deletions
+46 -17
View File
@@ -333,6 +333,23 @@ def _resolve_base_dir(base_dir_value: str) -> Path:
return Path(folder_paths.models_dir) / base_dir
def _gguf_search_dirs(base_dir_value: str) -> list[Path]:
search_dirs: list[Path] = [_resolve_base_dir(base_dir_value)]
try:
if "LLM" in folder_paths.folder_names_and_paths:
for llm_path in folder_paths.get_folder_paths("LLM"):
gguf_dir = Path(llm_path) / "GGUF"
if gguf_dir not in search_dirs:
search_dirs.append(gguf_dir)
llm_dir = Path(llm_path)
if llm_dir not in search_dirs:
search_dirs.append(llm_dir)
except Exception:
pass
return search_dirs
def _safe_dirname(value: str) -> str:
value = (value or "").strip()
if not value:
@@ -495,20 +512,7 @@ def _load_gguf_vl_catalog():
# Scan filesystem for locally available models not in JSON config
# Collect all directories to scan: the configured base_dir + any extra LLM paths from ComfyUI
existing_filenames = {Path(e.get("filename", "")).name for e in flattened.values() if e.get("filename")}
scan_dirs: list[Path] = [_resolve_base_dir(base_dir)]
try:
if "LLM" in folder_paths.folder_names_and_paths:
for llm_path in folder_paths.get_folder_paths("LLM"):
gguf_dir = Path(llm_path) / "GGUF"
if gguf_dir not in scan_dirs:
scan_dirs.append(gguf_dir)
# Also scan the LLM path itself (users may put GGUFs directly there)
llm_p = Path(llm_path)
if llm_p not in scan_dirs:
scan_dirs.append(llm_p)
except Exception:
pass
for scan_dir in scan_dirs:
for scan_dir in _gguf_search_dirs(base_dir):
local_models = _scan_local_gguf_models(scan_dir, existing_filenames)
flattened.update(local_models)
# Update existing_filenames so we don't add duplicates across directories
@@ -986,6 +990,14 @@ def _find_existing_local_file(base_dir: Path, filename: str) -> Path | None:
return None
def _find_existing_local_file_in_dirs(search_dirs: list[Path], filename: str) -> Path | None:
for search_dir in search_dirs:
found = _find_existing_local_file(search_dir, filename)
if found is not None:
return found
return None
def _resolve_model_entry(model_name: str) -> GGUFVLResolved:
all_models = GGUF_VL_CATALOG.get("models") or {}
entry = all_models.get(model_name) or {}
@@ -1171,7 +1183,9 @@ class QwenVLGGUFBase:
if mmproj_path is not None and not mmproj_path.exists():
raise FileNotFoundError(f"[QwenVL] Local mmproj not found: {mmproj_path}")
else:
base_dir = _resolve_base_dir(GGUF_VL_CATALOG.get("base_dir") or "llm/GGUF")
base_dir_value = GGUF_VL_CATALOG.get("base_dir") or "llm/GGUF"
search_dirs = _gguf_search_dirs(base_dir_value)
base_dir = search_dirs[0]
author_dir = _safe_dirname(resolved.author or "")
repo_dir = _safe_dirname(resolved.repo_dirname)
@@ -1180,7 +1194,7 @@ class QwenVLGGUFBase:
model_path = target_dir / Path(resolved.model_filename).name
mmproj_path = target_dir / Path(resolved.mmproj_filename).name if resolved.mmproj_filename else None
existing_model = _find_existing_local_file(base_dir, resolved.model_filename)
existing_model = _find_existing_local_file_in_dirs(search_dirs, resolved.model_filename)
if existing_model is not None:
model_path.parent.mkdir(parents=True, exist_ok=True)
if not model_path.exists():
@@ -1192,7 +1206,7 @@ class QwenVLGGUFBase:
model_path = model_path
if mmproj_path is not None:
existing_mmproj = _find_existing_local_file(base_dir, resolved.mmproj_filename)
existing_mmproj = _find_existing_local_file_in_dirs(search_dirs, resolved.mmproj_filename)
if existing_mmproj is not None:
mmproj_path.parent.mkdir(parents=True, exist_ok=True)
if not mmproj_path.exists():
@@ -1427,6 +1441,7 @@ class QwenVLGGUFBase:
model_name: str = "",
stream_to_terminal: bool = False,
enable_thinking: bool = True,
auto_finalization_retry: bool = False,
top_k: int | None = None,
):
"""Returns (cleaned_text, raw_text) tuple."""
@@ -1630,6 +1645,8 @@ class QwenVLGGUFBase:
best_cleaned = cleaned_text
if _answer_output_is_usable(best_cleaned):
return best_cleaned, "\n\n".join(raw_trace_parts)
if not auto_finalization_retry:
return best_cleaned or "", "\n\n".join(raw_trace_parts)
current_raw = raw_text
for attempt_number in range(2, _GGUF_MAX_FINALIZATION_ATTEMPTS + 1):
@@ -1739,6 +1756,7 @@ class QwenVLGGUFBase:
node_class="QwenVLGGUF",
stream_to_terminal=False,
enable_thinking=True,
auto_finalization_retry=False,
hf_token="",
):
print(f"[QwenVL GGUF DEBUG] Starting run with seed={seed}")
@@ -1769,6 +1787,7 @@ class QwenVLGGUFBase:
offload_kqv=bool(offload_kqv),
ctx_checkpoints=ctx_checkpoints,
enable_thinking=bool(enable_thinking),
auto_finalization_retry=bool(auto_finalization_retry),
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
@@ -1918,6 +1937,7 @@ class QwenVLGGUFBase:
model_name=model_name,
stream_to_terminal=stream_to_terminal,
enable_thinking=effective_thinking,
auto_finalization_retry=auto_finalization_retry,
top_k=top_k,
)
if self.last_backend_trace:
@@ -1995,6 +2015,7 @@ class ThinkingLLM_QwenVL_GGUF(QwenVLGGUFBase):
"seed": ("INT", {"default": 1, "min": 1, "max": 2**32 - 1, "tooltip": GGUF_TOOLTIPS["seed"]}),
"stream_tokens_to_terminal": ("BOOLEAN", {"default": False, "tooltip": GGUF_TOOLTIPS["stream_tokens_to_terminal"]}),
"enable_thinking": ("BOOLEAN", {"default": True, "tooltip": "Enable model reasoning/thinking when the backend supports it: True=allow thinking, False=force direct answer. Even when enabled, easy prompts may still get a direct answer, and this node automatically disables thinking when there is not enough output budget left for useful reasoning. For non-Qwen GGUF models this is advisory and may not be honored by the backend."}),
"auto_finalization_retry": ("BOOLEAN", {"default": False, "tooltip": "If enabled, runs an extra LLM completion when the first output is empty or reasoning-only. Disabled by default so one node execution performs one generation pass."}),
"hf_token": ("STRING", {"default": "", "multiline": False, "tooltip": GGUF_TOOLTIPS["hf_token"]}),
},
"optional": {
@@ -2030,6 +2051,7 @@ class ThinkingLLM_QwenVL_GGUF(QwenVLGGUFBase):
extra_pnginfo=None,
stream_tokens_to_terminal=False,
enable_thinking=True,
auto_finalization_retry=False,
hf_token="",
):
result = self.run(
@@ -2065,6 +2087,7 @@ class ThinkingLLM_QwenVL_GGUF(QwenVLGGUFBase):
node_class="ThinkingLLM_QwenVL_GGUF",
stream_to_terminal=stream_tokens_to_terminal,
enable_thinking=enable_thinking,
auto_finalization_retry=auto_finalization_retry,
hf_token=hf_token,
)
hf_token = ""
@@ -2086,6 +2109,7 @@ class ThinkingLLM_Gemma4_Audio_GGUF(QwenVLGGUFBase):
"seed": ("INT", {"default": 1, "min": 1, "max": 2**32 - 1, "tooltip": GGUF_TOOLTIPS["seed"]}),
"stream_tokens_to_terminal": ("BOOLEAN", {"default": False, "tooltip": GGUF_TOOLTIPS["stream_tokens_to_terminal"]}),
"enable_thinking": ("BOOLEAN", {"default": False, "tooltip": "Gemma 4 can reason, but audio transcription and short analysis are usually clearer with thinking disabled."}),
"auto_finalization_retry": ("BOOLEAN", {"default": False, "tooltip": "If enabled, runs an extra LLM completion when the first output is empty or reasoning-only. Disabled by default so one node execution performs one generation pass."}),
"hf_token": ("STRING", {"default": "", "multiline": False, "tooltip": GGUF_TOOLTIPS["hf_token"]}),
},
"optional": {
@@ -2112,6 +2136,7 @@ class ThinkingLLM_Gemma4_Audio_GGUF(QwenVLGGUFBase):
seed,
stream_tokens_to_terminal=False,
enable_thinking=False,
auto_finalization_retry=False,
hf_token="",
audio=None,
audio_file_path="",
@@ -2151,6 +2176,7 @@ class ThinkingLLM_Gemma4_Audio_GGUF(QwenVLGGUFBase):
node_class="ThinkingLLM_Gemma4_Audio_GGUF",
stream_to_terminal=stream_tokens_to_terminal,
enable_thinking=enable_thinking,
auto_finalization_retry=auto_finalization_retry,
hf_token=hf_token,
)
hf_token = ""
@@ -2201,6 +2227,7 @@ class ThinkingLLM_QwenVL_GGUF_Advanced(QwenVLGGUFBase):
"ctx_checkpoints": ("INT", {"default": 0, "min": 0, "max": 32, "tooltip": GGUF_TOOLTIPS["ctx_checkpoints"]}),
"stream_tokens_to_terminal": ("BOOLEAN", {"default": False, "tooltip": GGUF_TOOLTIPS["stream_tokens_to_terminal"]}),
"enable_thinking": ("BOOLEAN", {"default": True, "tooltip": "Enable model reasoning/thinking when the backend supports it: True=allow thinking, False=force direct answer. Even when enabled, easy prompts may still get a direct answer, and this node automatically disables thinking when there is not enough output budget left for useful reasoning. For non-Qwen GGUF models this is advisory."}),
"auto_finalization_retry": ("BOOLEAN", {"default": False, "tooltip": "If enabled, runs an extra LLM completion when the first output is empty or reasoning-only. Disabled by default so one node execution performs one generation pass."}),
"hf_token": ("STRING", {"default": "", "multiline": False, "tooltip": GGUF_TOOLTIPS["hf_token"]}),
},
"optional": {
@@ -2255,6 +2282,7 @@ class ThinkingLLM_QwenVL_GGUF_Advanced(QwenVLGGUFBase):
extra_pnginfo=None,
stream_tokens_to_terminal=False,
enable_thinking=True,
auto_finalization_retry=False,
hf_token="",
):
_ = legacy_seed_mode
@@ -2292,6 +2320,7 @@ class ThinkingLLM_QwenVL_GGUF_Advanced(QwenVLGGUFBase):
node_class="ThinkingLLM_QwenVL_GGUF_Advanced",
stream_to_terminal=stream_tokens_to_terminal,
enable_thinking=enable_thinking,
auto_finalization_retry=auto_finalization_retry,
hf_token=hf_token,
)
hf_token = ""
+9
View File
@@ -79,6 +79,7 @@ GGUF_PROMPT_TOOLTIPS = {
"keep_last_prompt": "Return the saved per-node prompt instead of generating a new one when available.",
"stream_tokens_to_terminal": "Show clean wrapped generated tokens in the ComfyUI terminal. Streaming bypasses fixed-seed reuse for a fresh run.",
"enable_thinking": "Enable model reasoning/thinking when supported. The node still returns the cleaned final prompt, so reasoning may be hidden or empty.",
"auto_finalization_retry": "If enabled, runs an extra LLM completion when the first output is empty or reasoning-only. Disabled by default so one node execution performs one generation pass.",
"hf_token": "Optional Hugging Face access token for private or gated GGUF downloads. It is passed only to the download call, never logged or cached, and the in-memory copy is dropped after the download attempt. Clear this field before saving or sharing workflows.",
}
@@ -481,6 +482,7 @@ class ThinkingLLM_QwenVL_GGUF_PromptEnhancer:
"keep_last_prompt": ("BOOLEAN", {"default": False, "tooltip": GGUF_PROMPT_TOOLTIPS["keep_last_prompt"]}),
"stream_tokens_to_terminal": ("BOOLEAN", {"default": False, "tooltip": GGUF_PROMPT_TOOLTIPS["stream_tokens_to_terminal"]}),
"enable_thinking": ("BOOLEAN", {"default": True, "tooltip": GGUF_PROMPT_TOOLTIPS["enable_thinking"]}),
"auto_finalization_retry": ("BOOLEAN", {"default": False, "tooltip": GGUF_PROMPT_TOOLTIPS["auto_finalization_retry"]}),
"hf_token": ("STRING", {"default": "", "multiline": False, "tooltip": GGUF_PROMPT_TOOLTIPS["hf_token"]}),
},
"hidden": {
@@ -715,6 +717,7 @@ class ThinkingLLM_QwenVL_GGUF_PromptEnhancer:
stream_to_terminal=False,
initial_stage_label="INITIAL GENERATION",
enable_thinking=True,
auto_finalization_retry=False,
):
"""Returns (cleaned_prompt, raw_trace) tuple."""
stream_display = TerminalStreamDisplay("QwenVL GGUF", suppress_planning=True, compact=True) if stream_to_terminal else None
@@ -860,6 +863,8 @@ class ThinkingLLM_QwenVL_GGUF_PromptEnhancer:
best_cleaned = cleaned.strip()
if _prompt_output_is_usable(best_cleaned):
return best_cleaned, "\n\n".join(raw_trace_parts)
if not auto_finalization_retry:
return best_cleaned or "", "\n\n".join(raw_trace_parts)
current_raw = raw
for attempt_number in range(2, _PROMPT_ENHANCER_MAX_FINALIZATION_ATTEMPTS + 1):
@@ -924,6 +929,7 @@ class ThinkingLLM_QwenVL_GGUF_PromptEnhancer:
unique_id=None,
extra_pnginfo=None,
enable_thinking=True,
auto_finalization_retry=False,
hf_token="",
):
node_class = "ThinkingLLM_QwenVL_GGUF_PromptEnhancer"
@@ -935,6 +941,7 @@ class ThinkingLLM_QwenVL_GGUF_PromptEnhancer:
english_output=bool(english_output),
device=device,
enable_thinking=bool(enable_thinking),
auto_finalization_retry=bool(auto_finalization_retry),
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
@@ -1021,6 +1028,7 @@ class ThinkingLLM_QwenVL_GGUF_PromptEnhancer:
stream_to_terminal=stream_tokens_to_terminal,
initial_stage_label="INITIAL GENERATION",
enable_thinking=effective_thinking,
auto_finalization_retry=auto_finalization_retry,
)
full_raw_trace = raw_trace
if english_output:
@@ -1039,6 +1047,7 @@ class ThinkingLLM_QwenVL_GGUF_PromptEnhancer:
stream_to_terminal=stream_tokens_to_terminal,
initial_stage_label="TRANSLATION STAGE",
enable_thinking=effective_thinking,
auto_finalization_retry=auto_finalization_retry,
)
full_raw_trace = f"{raw_trace}\n\n{trans_trace}"
final = clean_model_output(translated, OutputCleanConfig(mode="prompt")) or translated.strip()
+1 -1
View File
@@ -3,7 +3,7 @@
"name": "ComfyUI-ThinkingLLM",
"description": "A multimodal ComfyUI AI node with Qwen3.5, Qwen3-VL, Qwen2.5-VL, Qwen3, and Gemma 4 integrations. Features live thinking in the terminal to see what the LLM is doing in real time.",
"author": "goodguy1963",
"version": "2.3.10",
"version": "2.3.11",
"url": "https://github.com/goodguy1963/ComfyUI-ThinkingLLM",
"category": "image"
}
+1 -1
View File
@@ -9,5 +9,5 @@
"description": "A multimodal ComfyUI AI node with Qwen3.5, Qwen3-VL, Qwen2.5-VL, Qwen3, and Gemma 4 integrations. Features live thinking in the terminal to see what the LLM is doing in real time.",
"category": "image",
"tags": ["vision", "language", "multimodal", "thinkingllm", "qwen", "gguf", "flash_attention", "performance", "wan2.2", "video", "i2v", "t2v", "storyboard", "caching", "fork", "gpl-3.0"],
"version": "2.3.10"
"version": "2.3.11"
}
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "ComfyUI-ThinkingLLM"
description = "A multimodal ComfyUI AI node with Qwen3.5, Qwen3-VL, Qwen2.5-VL, Qwen3, and Gemma 4 integrations. Features live thinking in the terminal to see what the LLM is doing in real time."
version = "2.3.10"
version = "2.3.11"
license = {file = "LICENSE"}
dependencies = ["transformers>=5.2.0", "torch", "huggingface-hub", "hf_xet", "psutil", "numpy", "Pillow", "opencv-python", "bitsandbytes", "accelerate", "sentencepiece", "tiktoken", "faster-whisper", "llama-cpp-python"]
+166
View File
@@ -1068,6 +1068,24 @@ class TestGGUFAdvancedWorkflowCompatibility(unittest.TestCase):
self.assertIn("gemma-4-12b", model_meta["default"].lower())
self.assertIn("Audio", input_types["required"]["custom_prompt"][1]["default"])
def test_gguf_nodes_disable_auto_finalization_retry_by_default(self):
package = load_thinkingllm_loader_subset([
"AILab_QwenVL_GGUF.py",
"AILab_QwenVL_GGUF_PromptEnhancer.py",
])
for node_name in (
"ThinkingLLM_QwenVL_GGUF",
"ThinkingLLM_QwenVL_GGUF_Advanced",
"ThinkingLLM_Gemma4_Audio_GGUF",
"ThinkingLLM_QwenVL_GGUF_PromptEnhancer",
):
with self.subTest(node_name=node_name):
required = package.NODE_CLASS_MAPPINGS[node_name].INPUT_TYPES()["required"]
self.assertIn("auto_finalization_retry", required)
self.assertEqual(required["auto_finalization_retry"][0], "BOOLEAN")
self.assertEqual(required["auto_finalization_retry"][1]["default"], False)
def test_qwen3_asr_node_is_not_registered(self):
package = load_thinkingllm_loader_subset(["AILab_QwenVL_GGUF.py", "AILab_WhisperASR.py"])
@@ -1535,6 +1553,109 @@ class TestLlamaCppInstaller(unittest.TestCase):
class TestGGUFWindllRuntimeFallback(unittest.TestCase):
def test_gguf_loader_reuses_catalog_model_from_extra_llm_path_without_download(self):
calls = []
class DummyChatHandler:
def __init__(self, **kwargs):
calls.append(("chat", kwargs.get("clip_model_path")))
class DummyLlama:
def __init__(self, **kwargs):
calls.append(("llama", kwargs.get("model_path")))
chat_format_module = build_stub_module(
"llama_cpp.llama_chat_format",
Qwen35VLChatHandler=DummyChatHandler,
Qwen3VLChatHandler=DummyChatHandler,
)
installer_module = build_stub_module(
"AILab_LlamaCppInstaller",
ensure_llama_cpp_backend=lambda *args, **kwargs: DummyLlama,
format_llama_cpp_backend_info=lambda info=None: "stub backend",
get_last_llama_cpp_backend_info=lambda: {
"gpu_offload": True,
"vision_handlers": ["Qwen35VLChatHandler"],
},
relax_windows_dll_directory_for_long_paths=contextlib.nullcontext,
)
with tempfile.TemporaryDirectory() as temp_dir:
temp_root = Path(temp_dir)
dev_models = temp_root / "dev" / "models"
shared_llm = temp_root / "shared" / "models" / "LLM"
shared_model_dir = shared_llm / "GGUF" / "HauhauCS" / "Qwen3.5-4B-Uncensored-HauhauCS-Aggressive"
shared_model_dir.mkdir(parents=True)
model_filename = "Qwen3.5-4B-Uncensored-HauhauCS-Aggressive-Q8_0.gguf"
mmproj_filename = "mmproj-Qwen3.5-4B-Uncensored-HauhauCS-Aggressive-BF16.gguf"
(shared_model_dir / model_filename).write_bytes(b"GGUF")
(shared_model_dir / mmproj_filename).write_bytes(b"mmproj")
stubs = build_loader_test_stubs()
stubs.update(
{
"folder_paths": build_stub_module(
"folder_paths",
models_dir=str(dev_models),
folder_names_and_paths={"LLM": ([str(shared_llm)], set())},
get_folder_paths=lambda name: [str(shared_llm)] if name == "LLM" else [],
),
"AILab_LlamaCppInstaller": installer_module,
"llama_cpp": build_stub_module("llama_cpp", llama_chat_format=chat_format_module),
"llama_cpp.llama_chat_format": chat_format_module,
}
)
with mock.patch.dict(sys.modules, stubs, clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gguf_extra_llm_reuse_test",
)
model_name = next(
name
for name in module.GGUF_VL_CATALOG["models"]
if name.startswith(model_filename)
)
loader = module.QwenVLGGUFBase()
with mock.patch.dict(sys.modules, stubs, clear=False), mock.patch.object(
module,
"_download_single_file",
side_effect=AssertionError("download should not be called for shared local GGUF"),
), mock.patch.object(
module,
"_pick_device",
return_value="cpu",
), mock.patch.object(
module,
"read_gguf_architecture",
return_value="qwen35",
):
loader._load_model(
model_name=model_name,
device="cpu",
ctx=128,
n_batch=16,
n_ubatch=None,
gpu_layers=0,
image_max_tokens=64,
top_k=0,
pool_size=1024,
n_threads=None,
n_threads_batch=None,
flash_attn=False,
offload_kqv=False,
ctx_checkpoints=None,
enable_thinking=True,
unique_id=None,
)
self.assertEqual(calls[0][0], "chat")
self.assertTrue(str(calls[0][1]).endswith(mmproj_filename))
self.assertEqual(calls[1][0], "llama")
self.assertTrue(str(calls[1][1]).endswith(model_filename))
def test_gguf_runtime_reuses_windows_dll_relaxation_for_handler_and_llama(self):
enter_events = []
@@ -1754,6 +1875,50 @@ class TestGGUFWindllRuntimeFallback(unittest.TestCase):
self.assertEqual(loader.llm.reset_count, 1)
self.assertEqual(loader.llm.events, ["reset", "completion"])
def test_gguf_invoke_does_not_auto_finalize_by_default(self):
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gguf_no_default_finalization_test",
)
class DummyLlama:
def __init__(self):
self.events = []
self.calls = 0
def reset(self):
self.events.append(("reset", self.calls))
def create_chat_completion(self, **kwargs):
self.calls += 1
self.events.append(("completion", self.calls))
return iter([
{"choices": [{"delta": {"reasoning_content": "hidden reasoning only"}}]},
])
loader = module.QwenVLGGUFBase()
loader.llm = DummyLlama()
text, raw_trace = loader._invoke(
system_prompt="system",
user_prompt="user",
images_b64=[],
audio_b64=[],
max_tokens=8,
temperature=0.0,
top_p=1.0,
repetition_penalty=1.0,
seed=1,
stream_to_terminal=False,
enable_thinking=True,
)
self.assertEqual(text, "")
self.assertIn("[STREAMING]", raw_trace)
self.assertNotIn("FINALIZATION ATTEMPT", raw_trace)
self.assertEqual(loader.llm.events, [("reset", 0), ("completion", 1)])
def test_gguf_invoke_resets_before_finalization_retry_completion(self):
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
module = load_module_from_file(
@@ -1795,6 +1960,7 @@ class TestGGUFWindllRuntimeFallback(unittest.TestCase):
seed=1,
stream_to_terminal=False,
enable_thinking=True,
auto_finalization_retry=True,
)
self.assertEqual(text, "final answer")
+13
View File
@@ -1,5 +1,18 @@
# ComfyUI-QwenVL Update Log
## Version 2.3.11 (2026/06/19) — GGUF Single-Pass Execution Fix
### 2.3.11 Changes
- Disabled automatic GGUF finalization retries by default so one node execution performs one LLM generation pass unless explicitly configured otherwise.
- Added `auto_finalization_retry` to `ThinkingLLM (GGUF)`, `ThinkingLLM Advanced (GGUF)`, `ThinkingLLM Gemma 4 Audio (GGUF)`, and `ThinkingLLM Prompt Enhancer (GGUF)` for users who still want the old empty/reasoning-only retry behavior.
- Fixed catalogued GGUF model reuse across ComfyUI `extra_model_paths.yaml` `LLM` mappings, preventing unnecessary re-downloads when models already exist in a shared `models/LLM/GGUF` directory.
### 2.3.11 Validation
- Added regression coverage for default single-pass GGUF execution, explicit opt-in finalization retry, GGUF node UI defaults, and shared `LLM/GGUF` model reuse.
- Targeted GGUF tests pass.
## Version 2.3.10 (2026/06/19) — ComfyUI 2.0 Advanced GGUF Layout Fix
### 2.3.10 Changes