Skip to content

Misc. bug: MAX_REPETITION_THRESHOLD (2000) too low for peg-native tool-call grammars with many tools / large context, causing silent grammar-compile fallback to unconstrained generation #87

Description

@h9q2cyxvgm-ui

Name and Version

$./llama-server --version
version: 9595 (79697f23a)
built with AppleClang 17.0.0.17000013 for Darwin arm64

Branch: prism (this fork), commit 79697f23a2c8f3aa2ccb2fd7406095a8dbfbb454.

Operating systems

Mac

Which llama.cpp modules do you know to be affected?

llama-server (peg-native tool-call grammar generation / src/llama-grammar.cpp)

Command line

./build/bin/llama-server -m Ternary-Bonsai-27B-Q2_0.gguf -c 65536 --port 8080 -ngl 99 --temp 0.7 --top-p 0.95 --top-k 20 --jinja

Problem description & steps to reproduce

When serving Ternary-Bonsai-27B-Q2_0.gguf with a moderately large tool set (~26 OpenAI-style tool definitions, sent via /v1/chat/completions tools[], e.g. from an agent client like OpenClaw) and a large context window (-c 65536), grammar compilation for the peg-native tool-call format fails on every request with:

parse: error parsing grammar: number of repetitions exceeds sane defaults, please reduce the number of repetitions

This is MAX_REPETITION_THRESHOLD (src/llama-grammar.cpp:13, currently 2000) being tripped by the check at src/llama-grammar.cpp:652-653.

Root cause: tool parameters with no maxLength (e.g. a plain {"type": "string"} arg — very common, e.g. write's path/content params) get an unbounded until-suffix-style GBNF matcher during PEG→GBNF conversion (common_peg_until_parser, see common/chat-auto-parser-generator.cpp and common/peg-parser.cpp). Since the internal grammar sampler needs a concrete finite bound rather than a true *, this gets converted to a {min,max} repetition where max is derived from the server's context size (confirmed empirically: with -c 65536, the failing check reported min_times=1 max_times=65536 — the value matches the context size exactly, not any schema-declared bound). With 26 tools active, many individual string params hit this same context-sized bound, and each one independently trips the 2000 threshold via the direct (non-multiplicative) check at line 652.

Silent fallback is the dangerous part: when grammar compilation throws, the server does not surface this as a client-facing error — it logs E failed to parse grammar and apparently proceeds without a compiled constraint grammar for that turn. This lets the model free-generate its tool call instead of being constrained to valid peg-native syntax, which then fails a second, unrelated parse step downstream (Failed to parse input at pos N: <tool_call>...) once the (unconstrained, occasionally malformed) completion comes back. So the user-visible symptom is a confusing "failed to parse input" 500 on the response side, when the real root cause is a silent grammar-compile failure on the request side, several steps earlier.

Repro:

  1. Start llama-server with -c 65536 and Ternary-Bonsai-27B-Q2_0.gguf.
  2. Send a /v1/chat/completions request with tools[] containing ~20+ tool definitions where several have unbounded string parameters (no maxLength).
  3. Observe E failed to parse grammar / number of repetitions exceeds sane defaults in the server log, immediately followed by processing proceeding anyway (no compiled grammar constraining output).
  4. The model's completion, if it doesn't happen to match peg-native syntax exactly, then fails the response parser with Failed to parse input at pos N.

Confirmed fix: raising MAX_REPETITION_THRESHOLD from 2000 to 100000 (comfortably above 65536) in src/llama-grammar.cpp and rebuilding resolves it — verified via a full write→read→cron-add→cron-list→cron-remove multi-tool-call test with all 26 tools active, temp 0.7 (per the model's recommended sampling settings), which now completes cleanly with real tool execution and no parse errors.

Suggested fix

A few options, roughly in order of preference:

  1. Raise MAX_REPETITION_THRESHOLD (or make it configurable via CLI flag / env var) — simplest, but a blunt instrument since it's a general sanity cap, not specific to this code path.
  2. Cap the "no maxLength declared" fallback bound to something more reasonable than the full context size (e.g. a fixed constant like 4096 or 8192 characters) when converting common_peg_until_parser to GBNF, rather than deriving it from n_ctx. A tool argument string very rarely needs to be context-length-sized, and this would scale independently of -c.
  3. At minimum, surface grammar-compile failures as a client-facing error (e.g. a 500 with a clear message) instead of silently falling back to unconstrained generation — the current behavior turns a clear, actionable error ("grammar too complex, reduce tool count or param bounds") into a confusing downstream parse failure that looks unrelated to its actual cause.

Relevant log output

Logs
I srv  params_from_: Chat format: peg-native
I slot get_availabl: id  3 | task -1 | selected slot by LCP similarity, sim_best = 0.997 (> 0.100 thold), f_keep = 0.995
parse: error parsing grammar: number of repetitions exceeds sane defaults, please reduce the number of repetitions
E failed to parse grammar
...
[response, several seconds later]
Failed to parse input at pos 237: <tool_call>
<function=write>
<parameter=content>
full tool test

</parameter>
<parameter=path>
/Users/livestream/.openclaw/workspace/fulltooltest.txt
</parameter>
</function>
</tool_call>

Debug instrumentation added locally (not in this repro, but confirms the mechanism) at the failing check printed:

[DEBUG-REPCHECK-B] min_times=1 max_times=65536 has_max=1 threshold=20000

(65536 was the -c value passed at startup; threshold shown was an intermediate value of 20000 while narrowing down the fix, not the default 2000.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions