Skip to content

Commit 253e09f

Browse files
BaaRaiNiko1221
authored andcommitted
serve: the reasoning loop check also sees a loop written as one long word (#1753)
A periodic loop emitted as digits (a 24k-digit string cycling an 11-digit pattern) is a single word to the word count and never one token twice (the tokenizer has no multi-digit tokens), so both #606 and #728 passed it and the run burned the whole reasoning budget. The reasoning's last 2,048 chars are now checked for a period of at most 64 chars, and a periodic tail reads as full coverage. The opt-in path only: the default path and the word count are unchanged.
1 parent b9cba34 commit 253e09f

3 files changed

Lines changed: 32 additions & 2 deletions

File tree

‎docs/DETAILS.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,10 @@ print(r.choices[0].message.content)
746746
- **Repeated reasoning (opt-in, #728).** The single-token guard above does not see a model that repeats whole
747747
passages. `"reasoning_loop_recovery"` in `strata-<model>.json` is `false` (the default), `"stop"` or `"recover"`
748748
(`true` means `"recover"`). Every 512 output tokens, at a complete character and parser boundary, the reasoning is
749-
measured over its last 2,000 words and punctuation marks (counting passages over the last 30,000 words). If at
749+
measured over its last 2,000 words and punctuation marks (counting passages over the last 30,000 words), and its
750+
last 2,048 characters are checked for a period of at most 64 characters (#1753: a loop written as one long word,
751+
such as a 24k-digit string cycling an 11-digit pattern, is a single word to the count and never repeats one
752+
token); a periodic tail counts as fully repeated. If at
750753
least 25% belong to 12-word passages seen three times, `"stop"` ends the reply there as `"length"` and says so in
751754
the server window. `"recover"` stops and drains that generation, then goes on once from all its generated token
752755
ids with the template's low-effort sentence in place of the xhigh one in the first system message (a splice of

‎serve/server.py‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,23 @@ def write_temporary(path: Path, parts: list) -> None:
157157
LOOP_CHECK_EVERY = 512 # output tokens between two looks at the reasoning (at a clean parser boundary)
158158
LOOP_COVERAGE = 0.25 # the share of the last 2,000 words inside 12-word passages seen three times
159159
LOOP_HISTORY_WORDS = 30000 # how far back the passages are counted (bounds the cost of a look)
160+
LOOP_TAIL_CHARS = 2048 # chars at the end of the reasoning also checked for a short period (#1753)
161+
LOOP_TAIL_PERIOD = 64 # a period at most this long in that tail reads as a fully repeated window
160162

161163

162164
def reasoning_repeat_coverage(text):
163165
"""Coverage of recent words by 12-word passages seen at least three times.
164166
165167
Only reasoning is supplied. The history (the last LOOP_HISTORY_WORDS words) detects repeated verification passes
166-
separated by long code drafts; the recent window excludes old repetitions.
168+
separated by long code drafts; the recent window excludes old repetitions. #1753: a loop can also be one long
169+
word (a 24k-digit string cycling an 11-digit pattern): the word split makes it a single word and no word count
170+
grows, and #606's token run never grows either (the tokenizer has no multi-digit tokens), so before the word
171+
count the last LOOP_TAIL_CHARS chars are checked for a period of at most LOOP_TAIL_PERIOD, and a periodic tail
172+
reads as full coverage: it is all of the recent output, repeated.
167173
"""
174+
tail = text[-LOOP_TAIL_CHARS:].lower()
175+
if len(tail) == LOOP_TAIL_CHARS and any(tail[p:] == tail[:-p] for p in range(1, LOOP_TAIL_PERIOD + 1)):
176+
return 1.0
168177
words = re.findall(r"\w+|[^\w\s]", text.lower())[-LOOP_HISTORY_WORDS:]
169178
width, window = 12, 2000
170179
if len(words) < window:

‎serve/test_reasoning_loop_recovery.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ def test_repetition_and_novel_work(self):
3636
self.assertEqual(reasoning_repeat_coverage(' '.join(str(i) for i in range(4000))), 0)
3737
self.assertGreater(reasoning_repeat_coverage(self.loop), .9)
3838

39+
def test_a_loop_written_as_one_long_word(self):
40+
"""#1753: a 24k-digit string cycling an 11-digit pattern is one word to the split and never one token twice."""
41+
self.assertEqual(reasoning_repeat_coverage('Counting the digits of 9^104. ' + '58747693387' * 3000), 1)
42+
counted = 'Digits: ' + ''.join(str(i) for i in range(1000)) # 2,893 chars that never repeat
43+
self.assertEqual(reasoning_repeat_coverage(counted), 0)
44+
self.assertEqual(reasoning_repeat_coverage('58747693387' * 100), 0) # a tail too short to judge
45+
3946
def test_user_instruction_is_never_rewritten(self):
4047
ids = self.tok.encode('<|im_start|>user\n' + HIGH_EFFORT, parse_special=True)
4148
self.assertIsNone(focused_recovery_prompt(self.tok, ids, []))
@@ -110,6 +117,17 @@ def test_stop_mode_ends_the_reply_at_the_loop(self):
110117
self.assertLess(events[-1][1]['completion_tokens'], 20000)
111118
self.assertEqual(events[-1][1]['reasoning_recoveries'], 0)
112119

120+
def test_stop_mode_ends_a_loop_written_as_one_long_word(self):
121+
"""#1753: the word count never sees the digits, so only the tail's period ends the reply."""
122+
engine = RecordingEngine(self.tok, ['9^104 = ' + '58747693387' * 3000])
123+
service = Service(engine, self.tok, None)
124+
service.reasoning_loop_recovery = "stop"
125+
events = list(service.run(self.ids, True, [], 40000, {}, threading.Event()))
126+
self.assertEqual(len(engine.calls), 1) # no second pass
127+
self.assertEqual(events[-1][1]['finish'], 'length')
128+
self.assertLess(events[-1][1]['completion_tokens'], 5000)
129+
self.assertEqual(events[-1][1]['reasoning_recoveries'], 0)
130+
113131
def test_disabled_is_one_pass(self):
114132
engine, events = self.run_case(False)
115133
self.assertEqual(len(engine.calls), 1)

0 commit comments

Comments
 (0)