fix(studio): trim the enhancer by repetition, not by an invented length cap
The 2000-character cap was wrong. H3 has no padding, so every prompt token is a real sequence row and a long prompt is a legitimate thing to want — an enhanced one especially. Cutting at a fixed length contradicted the token limit the user had just set. Length cannot tell "long and detailed" from "stuck". What distinguishes them is that a model which has stopped writing starts repeating itself, so that is what is detected now: three occurrences of the same sentence and the prompt is cut at the first repeat. A long prompt that never repeats is never touched. The only remaining length bound is the one the user's own token limit already implies (~4 chars/token), so there is one control rather than two fighting. Short recurring lines are exempt — "She smiles." legitimately recurs in a shot list. Tests: 1690 passed.
This commit is contained in:
+42
-13
@@ -9211,10 +9211,11 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
max_length=max_length, temperature=temperature, top_p=top_p,
|
||||
min_floor=32, label="Prompt enhancer",
|
||||
)
|
||||
enhanced, overran = self._v2_trim_runaway_prompt(self._v2_clean_enhanced_prompt(raw))
|
||||
enhanced, overran = self._v2_trim_runaway_prompt(
|
||||
self._v2_clean_enhanced_prompt(raw), max_length=max_length)
|
||||
if overran:
|
||||
status += (" The model did not stop on its own and was cut at a sentence — lower "
|
||||
"the length limit, or use a model with a trained chat head.")
|
||||
status += (" The model repeated itself rather than stopping, and was cut at a "
|
||||
"sentence — a model with a trained chat head will not do this.")
|
||||
if not enhanced:
|
||||
return original, (status if "failed" in status or "unavailable" in status
|
||||
else "Prompt enhancer: model returned nothing; prompt unchanged.")
|
||||
@@ -9243,20 +9244,48 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
return str(probe)
|
||||
return "an unreported device"
|
||||
|
||||
#: A model with no trained decoder head may never emit a stop token, so `max_length` stops
|
||||
#: being a ceiling and becomes the actual length. A prompt is a paragraph; anything past
|
||||
#: this many characters is the model failing to stop, not detail worth keeping.
|
||||
V2_ENHANCED_PROMPT_CHAR_CAP = 2000
|
||||
#: Rough chars per token, to turn the user's TOKEN limit into a character bound. There is
|
||||
#: no fixed character cap: H3 has no padding, so every prompt token is a real sequence row
|
||||
#: and a long prompt is a legitimate thing to want. The length the user asked for is the
|
||||
#: length they get — this only stops output that overshoots the tokenizer's own ceiling.
|
||||
V2_ENHANCED_PROMPT_CHARS_PER_TOKEN = 4
|
||||
#: How many times a phrase may repeat before it is a stuck model rather than a style.
|
||||
V2_ENHANCED_PROMPT_REPEAT_LIMIT = 3
|
||||
|
||||
@classmethod
|
||||
def _v2_trim_runaway_prompt(cls, text, cap=None):
|
||||
"""Cut a prompt that never ended back to its last COMPLETE sentence.
|
||||
def _v2_trim_runaway_prompt(cls, text, max_length=None):
|
||||
"""Cut a prompt that never ended — by REPETITION first, length second.
|
||||
|
||||
Cutting mid-word would hand the encoder a fragment; cutting at a sentence keeps the
|
||||
prompt readable and keeps whatever the model got right before it started rambling.
|
||||
A model with no trained decoder head may never emit a stop token, so it fills whatever
|
||||
budget it is given. But length alone cannot tell "long and detailed" from "stuck": on
|
||||
H3 a long prompt is often exactly what is wanted. What distinguishes the two is that a
|
||||
stuck model repeats itself, so that is what is detected — and the length bound is
|
||||
derived from the user's own token limit rather than a number invented here.
|
||||
|
||||
Cutting lands on a complete sentence; a fragment handed to the encoder is worse than a
|
||||
shorter prompt.
|
||||
"""
|
||||
cap = int(cap or cls.V2_ENHANCED_PROMPT_CHAR_CAP)
|
||||
text = str(text or "")
|
||||
text = str(text or "").strip()
|
||||
if not text:
|
||||
return text, False
|
||||
|
||||
# 1. repetition: the honest signal that the model stopped writing and started looping
|
||||
sentences = re.split(r"(?<=[.!?])\s+", text)
|
||||
seen = {}
|
||||
for index, sentence in enumerate(sentences):
|
||||
key = " ".join(sentence.lower().split())
|
||||
if len(key) < 12: # too short to judge; "She smiles." recurs
|
||||
continue
|
||||
seen[key] = seen.get(key, 0) + 1
|
||||
if seen[key] >= cls.V2_ENHANCED_PROMPT_REPEAT_LIMIT:
|
||||
kept = " ".join(sentences[:index]).strip()
|
||||
if kept:
|
||||
return kept, True
|
||||
|
||||
# 2. length: only the ceiling the user's own token limit implies
|
||||
if max_length is None:
|
||||
return text, False
|
||||
cap = max(64, int(max_length) * cls.V2_ENHANCED_PROMPT_CHARS_PER_TOKEN)
|
||||
if len(text) <= cap:
|
||||
return text, False
|
||||
head = text[:cap]
|
||||
|
||||
@@ -233,36 +233,74 @@ def test_the_built_in_instructions_forbid_inventing_speech():
|
||||
# being a ceiling and becomes the actual length. Nothing downstream would notice: it would
|
||||
# just encode 8000 tokens of rambling as the prompt.
|
||||
|
||||
def test_a_prompt_that_never_ended_is_cut(studio):
|
||||
long = "A cat sits. " * 900
|
||||
out, cut = C.FunPackVideoRefinerV2._v2_trim_runaway_prompt(long)
|
||||
def _trim(text, **kw):
|
||||
return C.FunPackVideoRefinerV2._v2_trim_runaway_prompt(text, **kw)
|
||||
|
||||
|
||||
def test_a_looping_model_is_cut_at_the_repeat(studio):
|
||||
"""Repetition, not length, is what distinguishes a stuck model from a detailed one."""
|
||||
out, cut = _trim("A ginger cat sits on the sunlit sill. " * 40)
|
||||
assert cut is True
|
||||
assert len(out) <= C.FunPackVideoRefinerV2.V2_ENHANCED_PROMPT_CHAR_CAP
|
||||
assert out.count("A ginger cat sits on the sunlit sill.") < 3
|
||||
|
||||
|
||||
def test_a_long_prompt_that_never_repeats_survives(studio):
|
||||
"""H3 has no padding — every prompt token is a real sequence row, so a long prompt is a
|
||||
legitimate thing to want and must not be cut for being long."""
|
||||
text = " ".join(f"Shot {i}: a different thing happens in this room." for i in range(200))
|
||||
out, cut = _trim(text)
|
||||
assert cut is False and out == text
|
||||
|
||||
|
||||
def test_length_alone_never_cuts_without_a_token_limit(studio):
|
||||
out, cut = _trim("Unique sentence number %d here. " % 0 + "".join(
|
||||
f"Sentence {i} describes something new. " for i in range(500)))
|
||||
assert cut is False
|
||||
|
||||
|
||||
def test_the_length_bound_comes_from_the_users_token_limit(studio):
|
||||
"""The limit the user set IS the control; no character cap is invented next to it."""
|
||||
text = "".join(f"Sentence {i} describes something new and distinct. " for i in range(500))
|
||||
out, cut = _trim(text, max_length=64)
|
||||
assert cut is True
|
||||
assert len(out) <= 64 * C.FunPackVideoRefinerV2.V2_ENHANCED_PROMPT_CHARS_PER_TOKEN
|
||||
|
||||
|
||||
def test_a_generous_token_limit_keeps_a_generous_prompt(studio):
|
||||
text = "".join(f"Sentence {i} describes something new and distinct. " for i in range(200))
|
||||
_out, cut = _trim(text, max_length=8192)
|
||||
assert cut is False
|
||||
|
||||
|
||||
def test_the_cut_lands_on_a_finished_sentence(studio):
|
||||
long = "A cat sits on the sill. " * 900
|
||||
out, _cut = C.FunPackVideoRefinerV2._v2_trim_runaway_prompt(long)
|
||||
out, _cut = _trim("A ginger cat sits on the sunlit sill. " * 40)
|
||||
assert out.endswith(".")
|
||||
|
||||
|
||||
def test_a_normal_prompt_is_not_cut(studio):
|
||||
text = "A ginger cat sits on a sunlit sill."
|
||||
out, cut = C.FunPackVideoRefinerV2._v2_trim_runaway_prompt(text)
|
||||
out, cut = _trim(text, max_length=400)
|
||||
assert out == text and cut is False
|
||||
|
||||
|
||||
def test_a_short_recurring_line_is_not_treated_as_a_loop(studio):
|
||||
""""She smiles." legitimately recurs in a long shot list."""
|
||||
text = " ".join(f"The {w} moves across the room. She smiles." for w in
|
||||
("cat", "dog", "bird", "fox", "owl"))
|
||||
_out, cut = _trim(text)
|
||||
assert cut is False
|
||||
|
||||
|
||||
def test_a_runaway_never_ends_mid_word(studio):
|
||||
"""A fragment handed to the encoder is worse than a shorter prompt."""
|
||||
out, _cut = C.FunPackVideoRefinerV2._v2_trim_runaway_prompt("supercalifragilistic " * 500)
|
||||
out, _cut = _trim("supercalifragilistic " * 500, max_length=64)
|
||||
assert not out.endswith("supercalifragilis")
|
||||
assert out == out.strip()
|
||||
|
||||
|
||||
def test_the_user_is_told_the_model_did_not_stop(studio):
|
||||
clip = FakeClip(reply="A cat sits. " * 900)
|
||||
def test_the_user_is_told_the_model_repeated_itself(studio):
|
||||
clip = FakeClip(reply="A ginger cat sits on the sunlit sill. " * 40)
|
||||
_out, status = studio._v2_enhance_prompt(clip, "a cat", "sys")
|
||||
assert "did not stop" in status
|
||||
assert "repeated itself" in status
|
||||
|
||||
|
||||
# ── where it ran ────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user