fix(h3): weight against the text that was encoded, not the raw one
The spans were measured on funpack_scene_text — the RAW prompt, still holding `$style` and unrolled shortcuts — while the conditioning was built from the resolved string. Different string, so every span lands on the wrong words or the placement guard rejects the run. funpack_encode_text is the one that was encoded; use it. Phrase memory also stores raw text, so a phrase could carry `$style` while the prompt had it resolved and match nothing. Variables are a plain substitution, so both sides are put in the same form. Shortcuts are not peeked: a /trigger rolls per seed and peeking would give a different roll from the encoded one, so an unrolled phrase failing to match is correct.
This commit is contained in:
+4
-1
@@ -32,7 +32,10 @@
|
||||
compounded to an effective x3.4 on one token. A lone span is untouched. And how hard it
|
||||
pushes now comes from Studio's existing machinery rather than a new scale: the ceiling is
|
||||
the same 1.25 `_v2_build_attn2_patch` has used on LTX all along, and the ramp toward it is
|
||||
`_v2_auto_strength`, so one Awful reaches about 1.13 instead of jumping to maximum.
|
||||
`_v2_auto_strength`, so one Awful reaches about 1.13 instead of jumping to maximum. It also
|
||||
measures the text that was ENCODED rather than the raw one, and resolves `$variables` in a
|
||||
phrase before matching it — phrase memory stores raw text, so a phrase could still carry
|
||||
`$style` while the encoded prompt had it resolved.
|
||||
|
||||
### Fixed
|
||||
- **A reference image is no longer thrown away by Studio.** With CLIP and a positive
|
||||
|
||||
+40
-7
@@ -13076,7 +13076,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
guess_mode=guess_mode, guess_direction=guess_direction, guess_range=guess_range,
|
||||
guess_freeze_seed=guess_freeze_seed)
|
||||
return (
|
||||
self._v2_finalize_conditioning(output_conditioning, refinement_key, value_guidance, steer_mode, absolute_strength, spread_cap=_guess_spread_cap, temporal_style=temporal_style, temporal_fallback_text=prompt_to_encode, scene_refinement_keys=scene_refinement_keys, learning_profile=learning_profile, conditioning_plan=_conditioning_plan, clip=clip, encode_cache=encode_cache, phrase_memory=global_state.get("phrase_memory"), axis_feedback=repair_feedback, h3_phrase_emphasis=h3_phrase_emphasis, auto_strength=self._v2_auto_strength(global_state)),
|
||||
self._v2_finalize_conditioning(output_conditioning, refinement_key, value_guidance, steer_mode, absolute_strength, spread_cap=_guess_spread_cap, temporal_style=temporal_style, temporal_fallback_text=prompt_to_encode, scene_refinement_keys=scene_refinement_keys, learning_profile=learning_profile, conditioning_plan=_conditioning_plan, clip=clip, encode_cache=encode_cache, phrase_memory=global_state.get("phrase_memory"), axis_feedback=repair_feedback, h3_phrase_emphasis=h3_phrase_emphasis, auto_strength=self._v2_auto_strength(global_state), variables=_prompt_variables),
|
||||
status,
|
||||
training_info,
|
||||
loss_graph,
|
||||
@@ -13094,7 +13094,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
repair_feedback, current_family_slot, _vf_for_memory, _concept_dir,
|
||||
_concept_strength, _current_final)
|
||||
return (
|
||||
self._v2_finalize_conditioning(output_conditioning, refinement_key, value_guidance, steer_mode, absolute_strength, spread_cap=_guess_spread_cap, temporal_style=temporal_style, temporal_fallback_text=prompt_to_encode, scene_refinement_keys=scene_refinement_keys, learning_profile=learning_profile, conditioning_plan=_conditioning_plan, clip=clip, encode_cache=encode_cache, phrase_memory=global_state.get("phrase_memory"), axis_feedback=repair_feedback, h3_phrase_emphasis=h3_phrase_emphasis, auto_strength=self._v2_auto_strength(global_state)),
|
||||
self._v2_finalize_conditioning(output_conditioning, refinement_key, value_guidance, steer_mode, absolute_strength, spread_cap=_guess_spread_cap, temporal_style=temporal_style, temporal_fallback_text=prompt_to_encode, scene_refinement_keys=scene_refinement_keys, learning_profile=learning_profile, conditioning_plan=_conditioning_plan, clip=clip, encode_cache=encode_cache, phrase_memory=global_state.get("phrase_memory"), axis_feedback=repair_feedback, h3_phrase_emphasis=h3_phrase_emphasis, auto_strength=self._v2_auto_strength(global_state), variables=_prompt_variables),
|
||||
status + enhancement_status,
|
||||
training_info,
|
||||
loss_graph,
|
||||
@@ -13769,7 +13769,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
|
||||
def _v2_apply_h3_token_weights(self, conditioning_list, clip, phrase_memory=None,
|
||||
axis_feedback=None, fallback_text="", enabled=False,
|
||||
auto_strength=None):
|
||||
auto_strength=None, variables=None):
|
||||
"""Tag each scene with the token spans the RATING says deserve more attention.
|
||||
|
||||
Studio has decomposed and scored every phrase, word and n-gram since 2.0
|
||||
@@ -13820,6 +13820,31 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
"the rating's per-phrase emphasis is NOT being applied")
|
||||
return conditioning_list
|
||||
|
||||
def _resolve_phrase_variables(pairs):
|
||||
"""Phrase memory stores the RAW prompt text, so a phrase can still carry `$style`
|
||||
while the encoded prompt has it resolved — and then it matches nothing.
|
||||
Variables are a plain substitution, so both sides can be put in the same form.
|
||||
|
||||
Shortcuts are NOT expanded here. A `/trigger` rolls per seed, and peeking would
|
||||
produce a different roll from the one that was encoded — a phrase carrying an
|
||||
unrolled shortcut failing to match is the honest outcome."""
|
||||
if not variables:
|
||||
return list(pairs)
|
||||
try:
|
||||
try:
|
||||
from .templates import resolve_variables as _rv
|
||||
except ImportError:
|
||||
from templates import resolve_variables as _rv
|
||||
except Exception: # noqa: BLE001
|
||||
return list(pairs)
|
||||
done = []
|
||||
for phrase, weight in pairs:
|
||||
try:
|
||||
done.append((_rv(str(phrase), variables)[0], weight))
|
||||
except Exception: # noqa: BLE001
|
||||
done.append((phrase, weight))
|
||||
return done
|
||||
|
||||
out = []
|
||||
applied = 0
|
||||
for entry in conditioning_list or []:
|
||||
@@ -13827,7 +13852,13 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
out.append(entry)
|
||||
continue
|
||||
cond, meta = entry[0], dict(entry[1])
|
||||
text = str(meta.get("funpack_scene_text") or "").strip() or str(fallback_text or "")
|
||||
# funpack_encode_text is the string that was actually ENCODED — shortcuts rolled
|
||||
# and $variables resolved. funpack_scene_text is the RAW one, and tokenizing that
|
||||
# measures a different string from the one inside the conditioning: every span
|
||||
# lands on the wrong words, or the placement guard rejects the run outright.
|
||||
text = (str(meta.get("funpack_encode_text") or "").strip()
|
||||
or str(meta.get("funpack_scene_text") or "").strip()
|
||||
or str(fallback_text or ""))
|
||||
try:
|
||||
from . import h3_token_weights as _tw
|
||||
except ImportError:
|
||||
@@ -13835,7 +13866,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
# Capped AFTER ranking by weight, so the eight that survive are the eight the
|
||||
# rating cared most about; ordered longest-first only for application, so a
|
||||
# phrase's bias lands before one of its own words adds to it.
|
||||
chosen = _tw.order_for_application(weighted[:8])
|
||||
chosen = _tw.order_for_application(_resolve_phrase_variables(weighted[:8]))
|
||||
spans, prompt_tokens = _tw.locate(tokenizer, text, chosen)
|
||||
if spans and prompt_tokens:
|
||||
entry_meta = {"spans": spans, "prompt_tokens": int(prompt_tokens)}
|
||||
@@ -13865,7 +13896,8 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
scene_refinement_keys=None, learning_profile=None,
|
||||
conditioning_plan=None, clip=None, encode_cache=None,
|
||||
phrase_memory=None, axis_feedback=None,
|
||||
h3_phrase_emphasis=False, auto_strength=None):
|
||||
h3_phrase_emphasis=False, auto_strength=None,
|
||||
variables=None):
|
||||
"""Single output hook for both steering modes. Relative = per-key VF ascend (current
|
||||
behaviour). Absolute = global taste pull. Both = layer them. Finally, if Interactive
|
||||
Guessing has learned a safe-spread ceiling, clamp the output conditioning's video-channel
|
||||
@@ -13883,7 +13915,8 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
axis_feedback=axis_feedback,
|
||||
fallback_text=temporal_fallback_text,
|
||||
enabled=h3_phrase_emphasis,
|
||||
auto_strength=auto_strength)
|
||||
auto_strength=auto_strength,
|
||||
variables=variables)
|
||||
if mode in ("relative", "both"):
|
||||
out = self._v2_apply_scene_refinement_keys(
|
||||
out, scene_refinement_keys, refinement_key,
|
||||
|
||||
@@ -6,6 +6,7 @@ the prompt's key positions in the packed self-attention stream.
|
||||
"""
|
||||
import math
|
||||
import sys
|
||||
import types
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
@@ -536,3 +537,68 @@ def test_a_single_span_is_never_clamped():
|
||||
weaken a weight the user asked for."""
|
||||
bias = tw.build_bias([(0, 4, 2.0)], 4, 4, 20, "cpu", torch.float32)
|
||||
assert bias.max().item() == pytest.approx(math.log(2.0))
|
||||
|
||||
|
||||
# --- the text that was actually encoded ------------------------------------
|
||||
|
||||
@pytest.fixture
|
||||
def refiner():
|
||||
import conditioning
|
||||
return conditioning.FunPackVideoRefinerV2.__new__(conditioning.FunPackVideoRefinerV2)
|
||||
|
||||
|
||||
class _H3Clip:
|
||||
def __init__(self):
|
||||
self.tokenizer = types.SimpleNamespace(
|
||||
qwen3vl_32b=types.SimpleNamespace(tokenizer=_FakeTokenizer()))
|
||||
|
||||
|
||||
def _apply(refiner, monkeypatch, meta, memory, variables=None):
|
||||
import conditioning
|
||||
import minimax_h3
|
||||
monkeypatch.setattr(minimax_h3, "is_h3_clip", lambda c: True)
|
||||
monkeypatch.setattr(conditioning, "_log",
|
||||
types.SimpleNamespace(failed=lambda *a, **k: None,
|
||||
note_on_change=lambda *a, **k: None),
|
||||
raising=False)
|
||||
out = refiner._v2_apply_h3_token_weights(
|
||||
[[torch.zeros(1, 40, 8), dict(meta)]], _H3Clip(), phrase_memory=memory,
|
||||
axis_feedback={"missing_axes": ["details"]}, enabled=True,
|
||||
auto_strength=0.0435, variables=variables)
|
||||
return out[0][1].get("funpack_h3_token_weights")
|
||||
|
||||
|
||||
def test_the_encoded_text_is_measured_not_the_raw_one(refiner, monkeypatch):
|
||||
"""funpack_scene_text still holds `$style`; the conditioning was built from the resolved
|
||||
string. Measuring the raw one puts every span on the wrong words."""
|
||||
memory = {"neon rain": {"kind": "phrase",
|
||||
"effective_category_scores": {"details": 0.9}}}
|
||||
tag = _apply(refiner, monkeypatch,
|
||||
{"funpack_scene_text": "$style neon rain",
|
||||
"funpack_encode_text": "cinematic neon rain"}, memory)
|
||||
|
||||
assert tag is not None
|
||||
assert tag["prompt_tokens"] == len("cinematic neon rain")
|
||||
start, end, _w = tag["spans"][0]
|
||||
assert "cinematic neon rain"[start:end] == "neon rain"
|
||||
|
||||
|
||||
def test_a_phrase_still_carrying_a_variable_is_resolved_before_matching(refiner, monkeypatch):
|
||||
"""Phrase memory stores the RAW text, so `$style` survives in the phrase while the
|
||||
encoded prompt has it resolved — and then it matches nothing."""
|
||||
memory = {"$style rain": {"kind": "phrase",
|
||||
"effective_category_scores": {"details": 0.9}}}
|
||||
tag = _apply(refiner, monkeypatch,
|
||||
{"funpack_encode_text": "cinematic rain"}, memory,
|
||||
variables=[{"name": "style", "value": "cinematic"}])
|
||||
|
||||
assert tag is not None
|
||||
start, end, _w = tag["spans"][0]
|
||||
assert "cinematic rain"[start:end] == "cinematic rain"
|
||||
|
||||
|
||||
def test_a_phrase_that_still_does_not_match_is_simply_skipped(refiner, monkeypatch):
|
||||
memory = {"nothing like it": {"kind": "phrase",
|
||||
"effective_category_scores": {"details": 0.9}}}
|
||||
assert _apply(refiner, monkeypatch, {"funpack_encode_text": "cinematic rain"},
|
||||
memory) is None
|
||||
|
||||
Reference in New Issue
Block a user