Skip to content

serve: opt-in recovery from repeated reasoning passages - #869

Closed
huangserva wants to merge 1 commit into
Niko1221:mainfrom
huangserva:serve-reasoning-loop-recovery
Closed

huangserva wants to merge 1 commit into
Niko1221:mainfrom
huangserva:serve-reasoning-loop-recovery

Conversation

@huangserva

@huangserva huangserva commented Oct 5, 2026 •

Copy link
Copy Markdown

A high-effort IQ3_S request can repeat whole reasoning passages until max_tokens is exhausted and return no final answer. The 0.1.39 guard from #606 ends runs of one identical token; it does not detect a repeated sentence or a repeated code-verification pass. This draft adds an opt-in, one-shot recovery policy for those passages. It does not claim to fix numerical corruption or replace #838.

Set "reasoning_loop_recovery": true in the model config (default: false). At clean parser and UTF-8 boundaries every 512 output tokens, the server checks whether at least 25% of the most recent 2,000 word/punctuation units belongs to 12-unit passages seen at least three times in the reasoning history. It stops and drains the current generation and resumes with every generated token ID preserved. Only the native high-effort hint in the initial system message changes to the template's low-effort hint. No final answer, synthetic reasoning, or </think> is inserted; both passes share the original completion limit.

On recovery only, temperature is raised to at least 1.0 and presence penalty to at least 1.5, with top-p 0.95/top-k 20 and the same seed. This deliberately overrides an explicit low-entropy client sampler and is documented as opt-in. An already-matching sampler is passed through unchanged. History and request traces record recovery count and repetition coverage. The 0.1.39 single-token guard stays enabled.

Validation:

  • CPU regressions cover the single-token guard's passage-loop gap, unchanged user task and generated IDs, unchanged matching sampler, old explicit sampler recovery without request mutation, shared output limit, one-shot behavior, cancellation, repeated final text, missing native hint, and batch status cleanup.
  • python -m unittest discover -s serve -p 'test_*.py' on base 6f32ec070f23ced9f50e704d854d775da52591ab: 281 tests run, 274 passed, 7 skipped (no GPU/model fixtures). The optional jsonschema package was installed for schema-validation tests. Includes 13 new recovery tests.
  • v0.1.39 GPU control, built for Linux CUDA sm_89 with pinned llama.cpp 3cf03257f219afbe7334045ff7c6a06ac68c627d: the captured original IQ3_S failure prefix produced 1,487 consecutive repeated passages, reached the 16,384-token cap, and never closed thinking or delivered a final answer. This is a multi-token loop, so the server's 256-identical-token guard does not match it. Recovery enabled on that same loaded engine, input, seed and cap detected coverage 1.0 at 2,048 generated tokens, recovered once, and finished naturally with stop at 9,629 total completion tokens, with a nonempty final answer (SHA-256 5a35536b34032fce0094f6453067efb6952f2ae15a6620cbc59f863c5f188cad). The repeated prefix is retained, including 187 phrase repetitions generated before recovery. This is one diagnostic replay, not a new full-quality benchmark on 0.1.39.
    • STRATA_NO_LARGEPAGES=1 was used in both arms, after the default large-page startup was manually aborted before READY due to slow allocation. This switch changes page backing, not model arithmetic. No engine source was modified.
  • Prior 0.1.38 hardware verification: an actual captured failure prefix with the same loaded engine, seed and 16,384-token cap produced 1,487 consecutive repeated passages without recovery and a native stop/nonempty final answer at 6,655 completion tokens with one recovery. A frozen 31-request cohort then returned 31 complete final answers. Protocol, limitations and public answers.

Limits: this is a heuristic policy and may trigger on useful repeated code/checks. It supports the exact native high-effort hint in the first system message, skips images, and can require re-prefilling the modified prompt. Other templates/effort positions are not validated. A resumed model can still loop or reach the shared output limit. The 31-request cohort was run on 0.1.38; the 0.1.39 GPU check above is one diagnostic pair, not evidence that every 0.1.39 request completes. The draft is open for feedback on the opt-in API, threshold, and sampler policy before broader use.

@blange48

blange48 commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Feedback (rig: 4 x RTX 5080 16 GB (PCIe Gen3 x16, sm_120), i9-10920X, Linux, IQ3_S, 262K context, int8 K/V, layer split 12,24,36 with --trim-stage-weights, --batch 8 --batch-groups 4, MTP spec 4): applied on top of 0.1.39 and in production since today, so far without "reasoning_loop_recovery": true.

  • No regression with the option off. The server tests pass (193), and the solo and batch early-disconnect and multi-turn checks pass.
  • Not exercised yet. I haven't turned the option on, so I can't confirm the recovery itself. We have seen the failure you describe: an agent turn stuck repeating a weather lookup in its reasoning. If we turn it on, I'll report whether it fires and what it costs.

@talisp

talisp commented Oct 5, 2026

Copy link
Copy Markdown

A data point before turning this on: our runaway reasoning loops (one reply reached 102,833 tokens) were greedy decoding. The client (Hermes Agent) sent no sampling fields, and the strata-*.json had no sampling block, so the engine default (greedy) applied.

Setting the Qwen thinking defaults in the config removed them: 0 loops in the following ~19 h (details in #980):

"sampling": {"temperature": 1.0, "top_p": 0.95, "top_k": 20, "min_p": 0.0, "presence_penalty": 0.0, "repetition_penalty": 1.0}

An auxiliary vision call the client sends at temperature 0.1 also looped (suspected; it stopped after setting it to 1.0). So it is worth checking what sampling the client really sends before adding a loop guard.

Niko1221 pushed a commit that referenced this pull request Oct 6, 2026
Maintainer: separate stop and recover modes (reasoning_loop_recovery: false|stop|recover), token-level splice of the effort sentence (EFFORT_TEXT), a client's top_p/top_k never touched, checks at the first clean boundary after each 512 tokens, bounded history, tests incl. a literal think tag (#728, #869)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Niko1221

Niko1221 commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Thanks for this. We merged it for 0.1.40 with changes: a separate stop and recover mode ("reasoning_loop_recovery": false, "stop" or "recover"), the effort sentence is swapped as token ids (the prompt is not decoded and encoded again, so a literal stays text), a client's top_p and top_k are never changed, the check runs at the first clean boundary after each 512 tokens, and the history it counts is bounded. Off by default.

@Niko1221 Niko1221 closed this Oct 6, 2026
@Niko1221 Niko1221 mentioned this pull request Oct 6, 2026
@huangserva

huangserva commented Oct 6, 2026 via email

Copy link
Copy Markdown
Author

Niko1221 pushed a commit that referenced this pull request Oct 6, 2026
Maintainer: separate stop and recover modes (reasoning_loop_recovery: false|stop|recover), token-level splice of the effort sentence (EFFORT_TEXT), a client's top_p/top_k never touched, checks at the first clean boundary after each 512 tokens, bounded history, tests incl. a literal think tag (#728, #869)
fragtion added a commit to fragtion/StrataPool that referenced this pull request Oct 6, 2026
fragtion added a commit to fragtion/StrataPool that referenced this pull request Oct 6, 2026
Upstream merged Niko1221#863, Niko1221#732, Niko1221#869, Niko1221#931 and Niko1221#846 (our copies dropped) and took its own form of Niko1221#970 (Niko1221#804).
Kept: Niko1221#851, Niko1221#652, Niko1221#855, Niko1221#813, Niko1221#789. A pool turns off what it cannot run yet: --pipeline-windows, --batch-mtp,
--kv-grow, session files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157fjixQD2nRxvCvtXq66DD
fragtion added a commit to fragtion/StrataPool that referenced this pull request Oct 6, 2026
anon761 pushed a commit to anon761/Strata-upstream that referenced this pull request Oct 6, 2026
Maintainer: separate stop and recover modes (reasoning_loop_recovery: false|stop|recover), token-level splice of the effort sentence (EFFORT_TEXT), a client's top_p/top_k never touched, checks at the first clean boundary after each 512 tokens, bounded history, tests incl. a literal think tag (Niko1221#728, Niko1221#869)
sskver pushed a commit to sskver/Strata that referenced this pull request Oct 7, 2026
Maintainer: separate stop and recover modes (reasoning_loop_recovery: false|stop|recover), token-level splice of the effort sentence (EFFORT_TEXT), a client's top_p/top_k never touched, checks at the first clean boundary after each 512 tokens, bounded history, tests incl. a literal think tag (Niko1221#728, Niko1221#869)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants