fix(beam-search): make decode terminable, bound the grammar, add repe… - #44
Merged
HabibaTahir merged 1 commit intoSep 12, 2026
Conversation
…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>
|
@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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…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
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)
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.