diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c5dffd1..7b608df7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,8 +22,10 @@ connected, so Studio re-encoded from text and discarded what the node built — which on an i2v/r2v graph is not the same tensor, because Studio's encode never sees the reference image (nothing wires `h3_references` or `source_image`). CLIP keeps the negative, the - references and phrase classification. "Let CLIP re-encode a wired conditioning" restores - the old order. Replaces "Skip Studio's positive processing". + references and phrase classification. No setting selects between them — wiring the input + is the instruction, the same way a wired negative CONDITIONING has always won. "Skip + Studio's positive processing" is gone; it was a switch for something the graph already + said. - **Rating-driven phrase emphasis is OFF by default.** It changes what the model sees on every rated run; unvalidated work that does that is something to turn on, not to discover. Overlapping spans are also bounded — their biases add, and three overlapping x1.5 spans diff --git a/conditioning.py b/conditioning.py index 34739d7d..374a6c85 100644 --- a/conditioning.py +++ b/conditioning.py @@ -7092,8 +7092,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): return f"stand-in tokenizer: {source}" if source else "stand-in tokenizer loaded" def _v2_conditioning_source(self, clip, prompt_text, positive_conditioning, encode_cache=None, - reference_image=None, h3_references=None, - clip_owns_prompt=False): + reference_image=None, h3_references=None): # A wired positive CONDITIONING OWNS the prompt. CLIP used to win here, on the theory # that a graph with both connected meant CLIP — but wiring a conditioning is an # explicit act, and discarding it in favour of a re-encode is the surprising half. @@ -7106,8 +7105,9 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): # re-encode is a different tensor, and an i2v/r2v sampler gets handed a text-only one. # # CLIP keeps every OTHER job — the negative, the references, phrase classification — - # which is why both stay connected. `clip_owns_prompt` restores the old precedence. - if positive_conditioning is not None and not clip_owns_prompt: + # which is why both stay connected. No setting selects between them: WIRING the input + # is the instruction. Wanting CLIP to own the prompt means not wiring a conditioning. + if positive_conditioning is not None: clip_note = ("positive CONDITIONING is wired and owns the prompt" + ("; CLIP still encodes the negative and references" if clip is not None @@ -12051,7 +12051,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): split_by_transitions=False, split_transition_placement="start", reference_injection=False, value_guidance=True, latent=None, seed_output_connected=False, steer_mode="relative", absolute_strength=0.6, - clip_owns_prompt=False, h3_phrase_emphasis=False, + h3_phrase_emphasis=False, _seed=None, _seed_source="fresh seed", _scene_seeds=None, _velocity_keys=None, batch_variants=1, guess_mode=False, guess_direction="up", guess_range=1.0, guess_freeze_seed=True, movie_editor_scene_ratings=None, scene_segments=None, @@ -12510,7 +12510,6 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): encode_cache=encode_cache, reference_image=source_image, h3_references=_h3_references, - clip_owns_prompt=clip_owns_prompt, ) fallback_graph = render_refinement_loss_graph(refinement_key, "v2", "clip", 0, 0.0, []) if not isinstance(cond, torch.Tensor): @@ -15130,7 +15129,6 @@ class FunPackStudio: user_intent_prompt=effective_intent, refinement_key_input=refinement_key_input, positive_conditioning=positive_conditioning, - clip_owns_prompt=bool(rf.get("clip_owns_prompt", False)), h3_phrase_emphasis=bool(rf.get("h3_phrase_emphasis", False)), clip_vision_output=clip_vision_output, source_image=source_image if vision_conditioning else None, diff --git a/movie_editor/frontend/engine_settings.js b/movie_editor/frontend/engine_settings.js index de388c77..173da524 100644 --- a/movie_editor/frontend/engine_settings.js +++ b/movie_editor/frontend/engine_settings.js @@ -112,8 +112,6 @@ { name: "negative_erase_renorm", label: "Keep prompt strength", kind: "bool", default: true, dependsOn: "negative_erase", dependsVals: [true], hint: "Puts each word back to its original strength after the change, so the result reads as 'less of that thing' rather than 'quieter prompt'. Off is the raw result." }, - { name: "clip_owns_prompt", label: "Let CLIP re-encode a wired conditioning", kind: "bool", default: false, - hint: "OFF: a wired positive CONDITIONING owns the prompt and Studio steers it — the node that built it may have shown Qwen a reference image, which Studio has no way to reproduce. ON restores the old behaviour, where CLIP re-encodes from text and the wired conditioning is discarded. Text-only scenes lose Studio's per-scene split either way; shortcuts and $variables still apply." }, { name: "h3_phrase_emphasis", label: "Rating-driven phrase emphasis (H3)", kind: "bool", default: false, hint: "EXPERIMENTAL, unvalidated. Boosts the attention paid to phrases the rating said were MISSING, by biasing their attention logits in H3's packed stream. Needs a rated run first. Turn it off if generations drift away from the prompt — and note it forces SLA to run dense." } ]; diff --git a/tests/test_minimax_h3_studio.py b/tests/test_minimax_h3_studio.py index 8993ad2d..3da951cc 100644 --- a/tests/test_minimax_h3_studio.py +++ b/tests/test_minimax_h3_studio.py @@ -168,7 +168,7 @@ class _StubClip: pass -def _source(node, clip, wired, monkeypatch, clip_owns_prompt=False): +def _source(node, clip, wired, monkeypatch): monkeypatch.setattr( node, "_v2_encode_prompt", lambda *a, **k: (torch.ones(1, 4, 8), {"pooled_output": None}, "encoded"), @@ -177,8 +177,7 @@ def _source(node, clip, wired, monkeypatch, clip_owns_prompt=False): node, "_v2_extract_conditioning", lambda c: (torch.zeros(1, 4, 8), {"pooled_output": None}), raising=False) monkeypatch.setattr(node, "_v2_text_tokenizer_status", lambda: "tokenizer ok", raising=False) - return node._v2_conditioning_source(clip, "a prompt", wired, - clip_owns_prompt=clip_owns_prompt) + return node._v2_conditioning_source(clip, "a prompt", wired) def test_a_wired_conditioning_owns_the_prompt_even_with_clip_connected(monkeypatch, capsys): @@ -194,16 +193,6 @@ def test_a_wired_conditioning_owns_the_prompt_even_with_clip_connected(monkeypat assert "CLIP still encodes the negative" in status -def test_clip_still_owns_the_prompt_when_asked_to(monkeypatch): - """The old precedence, on request rather than by default.""" - node = FunPackVideoRefinerV2() - wired = [[torch.zeros(1, 4, 8), {}]] - cond, _meta, _status, owner = _source(node, _StubClip(), wired, monkeypatch, - clip_owns_prompt=True) - assert owner == "CLIP-owned" - assert torch.equal(cond, torch.ones(1, 4, 8)) - - def test_the_ownership_note_is_said_once_not_once_per_scene(monkeypatch, capsys): node = FunPackVideoRefinerV2() wired = [[torch.zeros(1, 4, 8), {}]] diff --git a/tests/test_reference_conditioning_ownership.py b/tests/test_reference_conditioning_ownership.py index e08638aa..b9dfc7e6 100644 --- a/tests/test_reference_conditioning_ownership.py +++ b/tests/test_reference_conditioning_ownership.py @@ -60,11 +60,13 @@ def test_clip_keeps_every_other_job(studio): assert "CLIP still encodes the negative and references" in note -def test_clip_owns_the_prompt_when_asked(studio): - cond, _meta, _note, owner = studio._v2_conditioning_source( - object(), "a cat", _wired(), clip_owns_prompt=True) - assert owner == "CLIP-owned" - assert torch.equal(cond, torch.ones(1, 4, 8)) +def test_no_setting_selects_between_them(studio): + """The wire IS the instruction. Wanting CLIP to own the prompt means not wiring a + conditioning, so there is nothing here to configure.""" + import inspect + params = inspect.signature(studio._v2_conditioning_source).parameters + assert "clip_owns_prompt" not in params + assert "prefer_wired" not in params def test_no_conditioning_wired_still_uses_clip(studio): diff --git a/web/funpack_studio.js b/web/funpack_studio.js index 31424c3c..5e65505f 100644 --- a/web/funpack_studio.js +++ b/web/funpack_studio.js @@ -85,7 +85,7 @@ function defaultSettings() { return { refinement_key: "", overrides: { refinement_key: false, feedback_prompt: false, user_intent_prompt: false, negative_prompt: false }, - refiner: { mode: "Refine", advisor_mode: "Off", advisor_thinking: true, im_feeling_lucky: false, reset_session: false, feedback_prompt: "", user_intent_prompt_override: "", negative_prompt: "", temporal_style: "natural", split_by_transitions: false, split_transition_placement: "start", reference_injection: false, vision_conditioning: true, value_guidance: true, steer_mode: "relative", absolute_strength: 0.6, negative_erase: false, negative_erase_strength: 0.5, negative_erase_mode: "project", negative_erase_renorm: true, clip_owns_prompt: false, h3_phrase_emphasis: false }, + refiner: { mode: "Refine", advisor_mode: "Off", advisor_thinking: true, im_feeling_lucky: false, reset_session: false, feedback_prompt: "", user_intent_prompt_override: "", negative_prompt: "", temporal_style: "natural", split_by_transitions: false, split_transition_placement: "start", reference_injection: false, vision_conditioning: true, value_guidance: true, steer_mode: "relative", absolute_strength: 0.6, negative_erase: false, negative_erase_strength: 0.5, negative_erase_mode: "project", negative_erase_renorm: true, h3_phrase_emphasis: false }, advisor_llm: { enabled: false, model_path: "huihui-ai/Huihui-Qwen3-8B-abliterated-v2", dtype: "bfloat16" }, loras: [], loras_config: { mode: "ltx2", per_block: false }, @@ -1132,19 +1132,6 @@ function openPanel(node) { body.append(row("Keep prompt strength", negRe)); } - // Who owns the positive when both CLIP and a CONDITIONING are wired. - const clipOwns = el("input"); clipOwns.type = "checkbox"; - clipOwns.checked = !!settings.refiner.clip_owns_prompt; - clipOwns.addEventListener("change", () => { - settings.refiner.clip_owns_prompt = clipOwns.checked; - }); - body.append(row("Let CLIP re-encode a wired conditioning", clipOwns)); - body.append(el("div", "funpack-studio-hint", - "Off: a wired positive CONDITIONING owns the prompt and Studio steers it — the node " - + "that built it may have shown Qwen a reference image, which Studio cannot reproduce. " - + "On restores the old behaviour, where CLIP re-encodes from text and the wired " - + "conditioning is discarded.")); - const phraseEmph = el("input"); phraseEmph.type = "checkbox"; phraseEmph.checked = !!settings.refiner.h3_phrase_emphasis; phraseEmph.addEventListener("change", () => {