From 08550e356955aeb5debb3957db32311db061d430 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Thu, 15 Oct 2026 18:10:57 +0300 Subject: [PATCH] fix: H3 second-pass preset held back; unsupported .gguf text encoders are refused instead of loading as the wrong model; Start shots hint is true for H3; LTX module titled UNTESTED; docs corrected (failure behaviour, Taste key location, install caution, gguf, second pass) (attacker round 5) Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 12 ++++++------ app/ui/features/inspector/project.js | 2 +- docs/how-to-start.md | 14 +++++++------- modules/loaders/gguf_support.py | 5 +++++ modules/loaders/tests/test_gguf_support.py | 15 +++++++++++++++ modules/models/ltx/__init__.py | 2 +- modules/models/minimax_h3/pipeline.py | 7 +++---- .../minimax_h3/tests/test_pipeline_preset.py | 3 +-- 8 files changed, 39 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c4f1380..5037bba0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,18 +8,18 @@ A rewrite from scratch, not an update of 4.x. Same idea (a studio for video insi - **A browser studio at `/funpack/`**: timeline with one clip per scene, Composer (story in one box, cut word, templates, `$variables`, shortcuts), properties, media bin, preview, takes, rating, Export, Render, project wizard and guided tour. - **Models & Pipeline**: the pipeline as cards. Pick files, swap or add nodes (any installed custom node), wire one node's output into another's input, import a ComfyUI workflow. The server refuses what would not build and says why. -- **Modules** (Settings ▸ Modules): each feature switches on or off on its own and shows whether it is proven or experimental. A module that fails during a run is switched off for the rest of it, and the log says why. +- **Modules** (Settings ▸ Modules): each feature switches on or off on its own. A module that fails during a run is left out of it, the log says why, and it stays off until its code is repaired or you turn it back on. - **Learning from ratings**: a Taste key remembers ratings; the guidance, steering and prompt modules that use them start acting after a few liked clips and say so when they need more. ### Models -- **MiniMax H3**: the model v5 is built and tested around. Presets for reference to video; works text-only (no source picture); second-pass preset (needs a compatible H3 latent upscaler file, not verified). +- **MiniMax H3**: the model v5 is built and tested around. Presets for reference to video; works text-only (no source picture). A second-pass preset exists in the code but is not offered until it has been verified. - **Stable Diffusion 1.5**: experimental. -- **LTX 2.x**: **UNTESTED.** Never run on a real LTX model, and nobody plans to test it soon. Presets are marked UNTESTED and every LTX run logs the same notice. You are free to try it; please report problems in Issues. +- **LTX 2.x**: **UNTESTED.** Never run on a real LTX model, and nobody plans to test it soon. Presets and the module are marked UNTESTED, and LTX runs log the same notice. You are free to try it; please report problems in Issues. ### Features -- **Proven**: prompt markup, empty latent, second pass, loaders (diffusion model, CLIP, VAE, LoRA, checkpoint, media), cut opening, decode, save video, anchor de-staticking (ALG), randomizer, DynaShift, first-step seed search, modifiers, sampler, detail. +- **Proven**: prompt markup, empty latent, loaders (diffusion model, CLIP, VAE, LoRA, checkpoint, media), cut opening, decode, save video, anchor de-staticking (ALG), randomizer, DynaShift, first-step seed search, modifiers, sampler, detail. - **Experimental**: shot camera, continuity, identity transfer, negative erase, prompt enhancer, Render, taste modules (slider, guidance, prompt nudge, attention, REINS, early guidance), shot memory, decisiveness, late-branch guidance, camera move, context windows, block influence, block repeat, phrase probe, shadow negative, momentum, velocity bias, video detail, temporal style, massive-activation steering, video-to-audio coupling. - **Prompt enhancer**: a language model rewrites each finished prompt. Has its own encoder pick (Composer ▸ Enhance ▸ Encoder), because H3's own encoder cannot write prose. Verified with Gemma 4. - **Auto Montage**: builds a new cut at the end of the timeline from your rendered clips; originals stay untouched, and the pieces are never regenerated or rated. @@ -35,9 +35,9 @@ A rewrite from scratch, not an update of 4.x. Same idea (a studio for video insi ### Known limits - **LTX 2.x is untested** (see Models). -- **`.gguf` files** load only for architectures ComfyUI-GGUF understands. Gemma 4 `.gguf` does not work; a loader for it may come later. +- **`.gguf` text encoders** work only for architectures ComfyUI-GGUF understands; others are refused with a message. Gemma 4 `.gguf` does not work; a loader for it may come later. - **H3 start frame**: the scene picture sets the video's shape only; image-to-video from it is not implemented. -- **H3 second pass** is not verified. +- **H3 second pass** is held back: not offered until verified. - Turning some modules on switches the model compiler off for the run, which costs sampling time. ### Docs diff --git a/app/ui/features/inspector/project.js b/app/ui/features/inspector/project.js index 20c5835d..ab80cf1a 100644 --- a/app/ui/features/inspector/project.js +++ b/app/ui/features/inspector/project.js @@ -72,7 +72,7 @@ export function projectRows(p, slots, api) { ...custom(p, open), c.field.default({ label: "Final render size", hint: "The size of the exported video: the first clip's, the project's, or any rendered clip's own.", control: c.select.md({ label: "Final render size", options: sizes(open), value: open.export_size_from || "", onChange: (v) => p.setField("export_size_from", v) }) }), - c.field.default({ label: "Start shots", hint: "From an image: a scene starts from its picture, or the last frame of the clip before it. From a prompt: every scene starts without a picture.", control: c.select.md({ label: "Start shots", options: STARTS, value: open.generation_mode === "t2v" ? "t2v" : "i2v", onChange: (v) => p.setField("generation_mode", v) }) })), + c.field.default({ label: "Start shots", hint: "From an image: a scene is shaped by its picture, or the last frame of the clip before it; a model that can start from a picture opens with it (MiniMax H3 uses it for the shape only). From a prompt: every scene starts without a picture.", control: c.select.md({ label: "Start shots", options: STARTS, value: open.generation_mode === "t2v" ? "t2v" : "i2v", onChange: (v) => p.setField("generation_mode", v) }) })), c.label.section({ text: "Prompt" }), card(c.field.default({ label: "Anchor", hint: "Put in front of every scene's prompt.", control: c.textarea.md({ label: "Anchor", value: p.anchor, rows: 2, onInput: (v) => p.setAnchor(v) }) }), c.field.default({ label: "Negative prompt", hint: "What to steer away from. A model that ignores negative prompts (MiniMax H3) uses it only with Shadow negative or Negative erase on.", control: c.textarea.md({ label: "Negative prompt", value: p.negative, rows: 2, onInput: (v) => p.setNegative(v) }) }), diff --git a/docs/how-to-start.md b/docs/how-to-start.md index d8b5cdb1..bedd55ab 100644 --- a/docs/how-to-start.md +++ b/docs/how-to-start.md @@ -10,7 +10,7 @@ Any one of these: - `git clone https://github.com/digital-garbage/ComfyUI-FunPack` inside `ComfyUI/custom_nodes` - ComfyUI-Manager: Custom Nodes Manager, search `ComfyUI-FunPack`, Install. -Then `pip install -r requirements.txt` in the same Python environment ComfyUI uses, and restart ComfyUI. FunPack uses your existing ComfyUI and PyTorch; it never upgrades them on its own. +Then `pip install -r requirements.txt` in the same Python environment ComfyUI uses, and restart ComfyUI. FunPack uses your existing ComfyUI and PyTorch. If pip wants to upgrade a package ComfyUI compiles against (torch, numpy, transformers, comfy-kitchen), read what it will change first: FunPack's own updater only installs what is missing and reports the rest. ## 2. Open FunPack @@ -24,7 +24,7 @@ Start ComfyUI as usual and open `/funpack/` on the same address, for example `ht |---|---| | MiniMax H3 | The model FunPack is built and tested around. | | Stable Diffusion 1.5 | Experimental. | -| LTX 2.x | **UNTESTED.** Never run on a real LTX model, and nobody plans to test it soon. You are free to try it; if something breaks, please report it in [Issues](https://github.com/digital-garbage/ComfyUI-FunPack/issues). | +| LTX 2.x (UNTESTED) | **UNTESTED.** Never run on a real LTX model, and nobody plans to test it soon. You are free to try it; if something breaks, please report it in [Issues](https://github.com/digital-garbage/ComfyUI-FunPack/issues). | ## 4. Pick your files @@ -35,11 +35,11 @@ The model's pipeline starts empty: it needs your files. Put model files where ComfyUI expects them (`models/diffusion_models`, `models/text_encoders`, `models/vae`, `models/loras`). When nothing is missing, Ready to generate? says so. -`.gguf` files load only for the architectures ComfyUI-GGUF understands. Anything else is not supported yet (Gemma 4 `.gguf` is one such case). +`.gguf` text encoders work only for architectures ComfyUI-GGUF understands; any other is refused with a message (Gemma 4 `.gguf` is one such case). Use the `.safetensors` file of the same model. ## 5. Write the story -Click **Composer** and write all scenes in one box, separated by the cut word (see the Cuts tab). Each piece becomes one clip on the timeline. Composer also keeps `$variables`, templates and shortcuts, and has an optional **Enhance** tab: a language model rewrites each finished prompt. On H3, pick a chat encoder (for example Gemma 4) under Enhance ▸ Encoder, because H3's own encoder cannot write prose. +Click **Composer** and write all scenes in one box, separated by the cut word (see the Cuts tab). Each piece becomes one clip on the timeline. Composer also keeps `$variables`, templates and shortcuts, and has an optional **Enhance** tab: a language model rewrites each finished prompt. On H3, pick a chat encoder (for example Gemma 4) under Enhance ▸ Encoder, because H3's own encoder cannot write prose. Leave Encoder family at its default unless your encoder belongs to another family (Gemma 4 worked with the default). ## 6. Generate @@ -55,11 +55,11 @@ Trim and split clips on the timeline. **⚡ Auto Montage** builds a new cut at t ## 8. Teach it your taste (optional) -Rate a result with **Rate scene…** (liked, disliked, bad image, bad composition). Set a **Taste key** (the ⚠ chip on the timeline, or **Settings ▸ Refinement & Taste**) so ratings are remembered. Modules that learn from ratings start acting after a few liked clips and say so when they need more. +Rate a result with **Rate scene…** (liked, disliked, bad image, bad composition). Set a **Taste key** (the ⚠ chip on the timeline points to it: **Settings ▸ Engine ▸ System**) so ratings are remembered. **Settings ▸ Refinement & Taste** lists, exports and deletes keys. Modules that learn from ratings start acting after a few liked clips and say so when they need more. ## 9. Features marked experimental -**Settings ▸ Modules** switches each feature on or off and shows its state. Experimental ones are built and run, but not yet proven over many runs. If one fails it is switched off for the rest of that run and the log says why. +**Settings ▸ Modules** switches each feature on or off. Several features are experimental: built and run, but not yet proven over many runs. If a module fails while generating, the run carries on without it, the Log says why, and the module stays off until its code is repaired or you turn it back on in Settings ▸ Modules. ## If something goes wrong @@ -71,4 +71,4 @@ Rate a result with **Rate scene…** (liked, disliked, bad image, bad compositio - LTX 2.x is untested (see above). - On H3 the scene picture sets the video's shape only; using it as a start frame is not implemented. -- The H3 "second pass" preset needs a compatible H3 latent upscaler file and has not been verified. +- The H3 "second pass" preset is not offered yet: it has not been verified. diff --git a/modules/loaders/gguf_support.py b/modules/loaders/gguf_support.py index cfcdc0d2..5d0a6299 100644 --- a/modules/loaders/gguf_support.py +++ b/modules/loaders/gguf_support.py @@ -257,6 +257,11 @@ def _load(path: str, clip: bool) -> tuple[dict, dict, str]: except Exception as e: # noqa: BLE001 -- any refusal is a reason to fall back if importlib.util.find_spec("gguf") is None: raise + if clip: + # 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 is not supported (ComfyUI-GGUF could not read it: {e}). " + "Use a .safetensors encoder of the same model.") from e sd, options, note = _load_native(path) return sd, options, (f"gguf: ComfyUI-GGUF could not read this file ({e}) -- " f"fell back. {note}") diff --git a/modules/loaders/tests/test_gguf_support.py b/modules/loaders/tests/test_gguf_support.py index 9705b82b..ee43f5ac 100644 --- a/modules/loaders/tests/test_gguf_support.py +++ b/modules/loaders/tests/test_gguf_support.py @@ -246,3 +246,18 @@ def test_gguf_is_a_declared_requirement(): lines = [ln.strip() for ln in reqs.read_text(encoding="utf-8").splitlines() if ln.strip() and not ln.startswith("#")] assert any(ln == "gguf" or ln.startswith("gguf") for ln in lines) + + +def test_a_text_encoder_gguf_the_pack_refuses_is_refused_not_loaded_as_the_wrong_model(tmp_path, monkeypatch): + d = tmp_path / "pack" + d.mkdir() + (d / "ops.py").write_text("class GGMLOps:\n pass\n") + (d / "loader.py").write_text( + "def gguf_clip_loader(path):\n" + " raise ValueError(\"Unexpected text model architecture type in GGUF file: 'gemma4'\")\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: object() if name == "gguf" else None) + monkeypatch.setattr(gguf_support, "_load_native", lambda path: ({"w": "native"}, {}, "native")) + with pytest.raises(RuntimeError, match="not supported"): + gguf_support.load_clip_state_dict("/x.gguf") diff --git a/modules/models/ltx/__init__.py b/modules/models/ltx/__init__.py index 47790f51..360a2667 100644 --- a/modules/models/ltx/__init__.py +++ b/modules/models/ltx/__init__.py @@ -34,7 +34,7 @@ from .pipeline import presets as _pipeline_presets has_block = _traits.has_block ID = "model_ltx" -TITLE = "LTX 2.x" +TITLE = "LTX 2.x (UNTESTED)" STAGE = "load" CATEGORY = "system" STATUS = "experimental" diff --git a/modules/models/minimax_h3/pipeline.py b/modules/models/minimax_h3/pipeline.py index af34458f..f6c8b985 100644 --- a/modules/models/minimax_h3/pipeline.py +++ b/modules/models/minimax_h3/pipeline.py @@ -206,10 +206,9 @@ def h3_reference_to_video_second_pass(): def presets(): return [{"id": "minimax_h3_reference_to_video", "title": "MiniMax H3 · Reference to Video", - "slots": h3_reference_to_video()}, - {"id": "minimax_h3_reference_to_video_second_pass", - "title": "MiniMax H3 · Reference to Video + second pass", - "slots": h3_reference_to_video_second_pass()}] + "slots": h3_reference_to_video()}] + # Held back, not offered: h3_reference_to_video_second_pass() builds clean but was never verified on a GPU + # (it needs a compatible H3 latent upscaler file). Add it back here once it has been. PROVIDES = {"pipeline_presets": presets} diff --git a/modules/models/minimax_h3/tests/test_pipeline_preset.py b/modules/models/minimax_h3/tests/test_pipeline_preset.py index c153ba0a..eb5b2729 100644 --- a/modules/models/minimax_h3/tests/test_pipeline_preset.py +++ b/modules/models/minimax_h3/tests/test_pipeline_preset.py @@ -33,8 +33,7 @@ def registered(comfyui): def test_the_preset_is_offered(registered): from modules.models.minimax_h3.pipeline import presets offered = presets() - assert [p["id"] for p in offered] == [ - "minimax_h3_reference_to_video", "minimax_h3_reference_to_video_second_pass"] + assert [p["id"] for p in offered] == ["minimax_h3_reference_to_video"] # the second-pass preset is held back assert all(p["slots"] for p in offered)