Skip to content

fix(beam-search): make decode terminable, bound the grammar, add repe… - #44

Merged
HabibaTahir merged 1 commit into
QuantumLogicsLabs:mainfrom
mustfaaaa:dev2/beam-search-repetition-control
Sep 12, 2026
Merged

HabibaTahir merged 1 commit into
QuantumLogicsLabs:mainfrom
mustfaaaa:dev2/beam-search-repetition-control

Conversation

@mustfaaaa

Copy link
Copy Markdown
Contributor

…tition control

DEV 2 scope: inference/beam_search.py, inference/grammar.py.

Root cause of the 0% eval: the decoder had no legal way to terminate. is_valid_prefix() required the parse to consume every token, so every continuation of a completed AST -- [EOS] included -- was rejected. The grammar mask went all -inf the moment a beam produced a correct answer, and beam_search dropped that beam from the candidate list. Only degenerate, still-open beams survived, which is why every prediction returned status="partial" with an identical "reached end of tokens" error.

Verified with an oracle stub that is certain about every gold token: before this change it still looped and never reached a solved status, so the search was broken independently of any checkpoint.

Termination

  • grammar: accept [EOS] as the final token of a complete AST; add is_complete() alongside is_valid_prefix() via a shared _parse_status().
  • beam_search: retire finished beams instead of discarding them; cap topk to the legal-candidate count (it was padding with -inf entries, and ties break by index, so [EOS] got selected and manufactured "finished" beams holding truncated ASTs); strip the terminator before the deserializer, which rejects any token following a closed AST.

Reachability
The grammar rejected 19.1% of real training targets: NODE:GRADIENT had no parser branch, and op-nodes could not carry the optional COEF/EXP/POINT decorators serialize_op_node emits. Because the grammar drives the decode mask those answers were unreachable, not merely mis-scored -- this is why gradient scores 0/50. Acceptance is now 100.00% of all 155,000 targets in data/splits/ (was 80.9%). Also rejects the COEF:OTHER / EXP:OTHER OOV placeholders, which parse as tokens but crash float() in the deserializer.

Repetition control (tasks 1 and 2)

  • soft CTRL-style penalty on raw logits before log_softmax.
  • hard guards: consecutive-run cap and a no-repeat-ngram check.
  • grammar bounds: MAX_OPVARS_PER_OP, MAX_NESTING_DEPTH, MAX_SIBLINGS. Every threshold is derived from the dataset, with margin. Scoping is load-bearing: guards apply to content tokens only and never to STRUCT:CLOSE/[EOS]. 77.4% of real targets repeat some bigram but zero repeat a content-only one, so an unscoped no_repeat_ngram_size=2 would corrupt three quarters of legitimate answers.

Profiling (task 3)
scripts/profile_beam.py + docs/BEAM_SEARCH_PERFORMANCE.md. The validity mask is 4-6% of runtime at beam_size=5, not the bottleneck; the un-batched forward passes are. The grammar bounds cut the pessimistic worst case from 34.58s to 5.94s per problem. Documented bound: run_eval.py should complete in ~15 minutes and must not exceed ~30.

ONNX parity
deployment/onnx_beam_search.py had drifted -- every previous fix landed on the torch side only -- plus a numpy-only defect where np.log(softmax(x) + 1e-12) made masked tokens finite and therefore selectable. Shared policy extracted to inference/decoding.py (pure stdlib) so the paths cannot diverge again, with a parity test and a test asserting the ONNX path never imports torch (that is what keeps the Vercel bundle under the 250MB cap).

Tests: 118 -> 241 passing, 4 xfailed.

Known limitation: bounding the grammar's dimensions does not bound sequence length, so a pathological model can still fill max_len with a legal but incomplete prefix. beam_search falls back to the longest closed prefix, but that only helps when the outermost node closed. A hard guarantee needs a budget-aware closing constraint; documented, not assumed.

…tition control

DEV 2 scope: inference/beam_search.py, inference/grammar.py.

Root cause of the 0% eval: the decoder had no legal way to terminate.
is_valid_prefix() required the parse to consume every token, so every
continuation of a completed AST -- [EOS] included -- was rejected. The
grammar mask went all -inf the moment a beam produced a correct answer,
and beam_search dropped that beam from the candidate list. Only degenerate,
still-open beams survived, which is why every prediction returned
status="partial" with an identical "reached end of tokens" error.

Verified with an oracle stub that is certain about every gold token: before
this change it still looped and never reached a solved status, so the search
was broken independently of any checkpoint.

Termination
- grammar: accept [EOS] as the final token of a complete AST; add
  is_complete() alongside is_valid_prefix() via a shared _parse_status().
- beam_search: retire finished beams instead of discarding them; cap topk
  to the legal-candidate count (it was padding with -inf entries, and ties
  break by index, so [EOS] got selected and manufactured "finished" beams
  holding truncated ASTs); strip the terminator before the deserializer,
  which rejects any token following a closed AST.

Reachability
The grammar rejected 19.1% of real training targets: NODE:GRADIENT had no
parser branch, and op-nodes could not carry the optional COEF/EXP/POINT
decorators serialize_op_node emits. Because the grammar drives the decode
mask those answers were unreachable, not merely mis-scored -- this is why
gradient scores 0/50. Acceptance is now 100.00% of all 155,000 targets in
data/splits/ (was 80.9%). Also rejects the COEF:OTHER / EXP:OTHER OOV
placeholders, which parse as tokens but crash float() in the deserializer.

Repetition control (tasks 1 and 2)
- soft CTRL-style penalty on raw logits before log_softmax.
- hard guards: consecutive-run cap and a no-repeat-ngram check.
- grammar bounds: MAX_OPVARS_PER_OP, MAX_NESTING_DEPTH, MAX_SIBLINGS.
Every threshold is derived from the dataset, with margin. Scoping is
load-bearing: guards apply to content tokens only and never to
STRUCT:CLOSE/[EOS]. 77.4% of real targets repeat some bigram but zero
repeat a content-only one, so an unscoped no_repeat_ngram_size=2 would
corrupt three quarters of legitimate answers.

Profiling (task 3)
scripts/profile_beam.py + docs/BEAM_SEARCH_PERFORMANCE.md. The validity
mask is 4-6% of runtime at beam_size=5, not the bottleneck; the un-batched
forward passes are. The grammar bounds cut the pessimistic worst case from
34.58s to 5.94s per problem. Documented bound: run_eval.py should complete
in ~15 minutes and must not exceed ~30.

ONNX parity
deployment/onnx_beam_search.py had drifted -- every previous fix landed on
the torch side only -- plus a numpy-only defect where
np.log(softmax(x) + 1e-12) made masked tokens finite and therefore
selectable. Shared policy extracted to inference/decoding.py (pure stdlib)
so the paths cannot diverge again, with a parity test and a test asserting
the ONNX path never imports torch (that is what keeps the Vercel bundle
under the 250MB cap).

Tests: 118 -> 241 passing, 4 xfailed.

Known limitation: bounding the grammar's dimensions does not bound sequence
length, so a pathological model can still fill max_len with a legal but
incomplete prefix. beam_search falls back to the longest closed prefix, but
that only helps when the outermost node closed. A hard guarantee needs a
budget-aware closing constraint; documented, not assumed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 12, 2026

Copy link
Copy Markdown

@mustfaaaa is attempting to deploy a commit to the seno-quantum-coder's projects Team on Vercel.

A member of the Team first needs to authorize it.

@HabibaTahir
HabibaTahir merged commit cf95350 into QuantumLogicsLabs:main Sep 12, 2026
1 check failed
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.

2 participants