One method, one protocol, zero per-task tuning: where does NWA-constrained decoding with an HMM guide work, and why.
NWA = nested-word automaton: a finite automaton augmented with a call/return stack, so it recognizes unbounded balanced nesting (parentheses, JSON, code blocks) that no DFA can. Ctrl-G = constrained decoding where a distilled HMM approximates the LM to score, at each step, the probability mass of constraint-satisfying futures. This project composes the two: hard NWA masks plus HMM future-mass guidance. Benchmarks: countdown (arithmetic target game, equation answer), nested_arith (rewrite a nested expression step by step), planetarium (NL → PDDL problem files), racket (MultiPL-E Racket function completion, unit-test pass), bbeh_dyck (BBEH Dyck bracket-closing), NESTFUL (nested tool-call sequences in JSON).
Every number below is final: 2 models × 6 benchmarks × 5 conditions, plus a 24-cell λ ablation. One method, one protocol, no per-task tuning. Nothing is mid-run.
walker.max_depth was silently ignored by the planetarium and
bbeh walkers — their original C cells were byte-identical duplicates of E
and have been superseded (planetarium C was re-run with a functional cap —
jobs 1910589/1910590, final at 0.000 on both models; bbeh C is n/a — its answer
language has no nesting). countdown and arith
honor the cap but their decoded outputs never nest ≥ 2, so C ≡ E there is
an honest (if uninformative) result. planetarium-Qwen is the real NWA>DFA
separation: depth-2 C = 0.000 (0/256) vs E = 0.324 (83/256), +32.4pp.
racket C is 0.000 on both models too, but racket-Qwen E is also 0.000, so
that lane separates nothing — its outputs are budget-truncated, not
depth-limited. (C shares E's guide DP; only the hard mask is depth-capped.)q(·) is the NWA configuration reached after emitting
a and β(q) is the guide's total probability mass over
accepting completions from q. λ = 1.0 in the main matrix below (the pure
formulation); the λ-ablation section re-runs E at
λ ∈ {0.1, 0.5}. Tokens with β = 0 (no valid completion) are hard-banned at
any λ — which is why the mask is a separate run, not a λ→0 sweep point.
Implementation note: the runtime scans tokens in LM-score order and keeps the
top keep_k=32 walker-valid candidates — argmax-identical to the
full valid set under the greedy (beam-1) decoding used everywhere in this
matrix, but a real truncation for sampling-based uses.
build_offline_cache — fp64, since fp32 future mass
underflows on long completions. Runtime lookup is O(1) per candidate.(thinking-OFF rendered prompt) + answer; at eval the guide's
belief state is initialized by forwarding the same prompt
(hmm_prefix_ids) — byte-identical conditioning between
distillation and decoding. LVD (latent-variable distillation from LM hidden
states) initializes EM; EM trains on the prefixed rows (h=256 suite, h=512
NESTFUL).<think>…</think> freely; a free warm-up runs to the
task's answer marker (token-id-preserving cut); the constraint applies only
from there. Greedy, beam 1, identical budgets across conditions within a task.
Exception: racket and nested_arith define no answer marker, so on Llama
(no thinking mode) their constrained conditions decode under the mask from the
first token — no free prose phase, unlike condition A. Those are also the two
lanes characterized below as "long free-form", so part of that penalty may be
warm-up asymmetry rather than guidance.| id | constraint | guide | isolates |
|---|---|---|---|
| A | none | none | the raw LLM |
| B | NWA walker (viability projection) | none | value of constraint support alone |
| C | walker, depth capped at 2 (= DFA / original-Ctrl-G class); the guide's future-mass DP is shared with E (full NWA) — only the hard mask is depth-restricted | learned HMM, λ=1 | NWA-vs-DFA constraint-class axis (mask side only) |
| D | NWA walker | uniform (unlearned) HMM, λ=1, fp64 | does guide learning matter |
| E | NWA walker | learned HMM, λ=1 | the full method as formulated |
Accuracy (exact task metric), n = full benchmark. Suite cells recomputed from per-example predictions at page build; NESTFUL from the scorer's final log block. Bold = row best. Llama decodes answers directly (no thinking mode).
| task | A zeroshot | B mask | C dfa+guide | D uniform | E full | B−A | E−B | E−D |
|---|---|---|---|---|---|---|---|---|
| countdown | 0.133 | 0.191 | 0.172 | 0.188 | 0.172 | +0.059 | -0.020 | -0.016 |
| nested_arith | 0.195 | 0.201 | 0.005 | 0.000 | 0.005 | +0.005 | -0.195 | +0.005 |
| planetarium | 0.012 | 0.016 | 0.000 | 0.000 | 0.016 | +0.004 | +0.000 | +0.016 |
| racket | 0.199 | 0.199 | 0.000† | 0.000 | 0.025† | +0.000 | -0.174 | +0.025 |
| bbeh_dyck | 0.045 | 0.000 | n/a | 0.050 | 0.000† | -0.045 | +0.000 | -0.050 |
| nestfulacc-combined | 0.183 | 0.260 | n/a | 0.017 | 0.143 | +0.077 | -0.117 | +0.126 |
bold = row best among completed cells · — = not started · … k/n = running, mean suppressed below 25% coverage · n/a = condition undefined for the task · Δ columns computed on raw counts before rounding (may differ from displayed cells by ±0.001) · † = transductively distilled guide (see caveats)
Same protocol, thinking ON (free, never constrained; max_think 4096–8192 per task). Every guide is distilled from this model's own constrained samples. Bold = row best.
| task | A zeroshot | B mask | C dfa+guide | D uniform | E full | B−A | E−B | E−D |
|---|---|---|---|---|---|---|---|---|
| countdown | 0.805 | 0.832 | 0.832 | 0.641 | 0.832 | +0.027 | +0.000 | +0.191 |
| nested_arith | 0.396 | 0.383 | 0.000 | 0.005 | 0.000 | -0.013 | -0.383 | -0.005 |
| planetarium | 0.332 | 0.332 | 0.000 | 0.105 | 0.324 | +0.000 | -0.008 | +0.219 |
| racket | 0.286 | 0.360 | 0.000† | 0.000 | 0.000† | +0.075 | -0.360 | +0.000 |
| bbeh_dyck | 0.155 | 0.295 | n/a | 0.055 | 0.315† | +0.140 | +0.020 | +0.260 |
| nestfulacc-combined | 0.378 | 0.387 | n/a | 0.061 | 0.354 | +0.009 | -0.033 | +0.293 |
bold = row best among completed cells · — = not started · … k/n = running, mean suppressed below 25% coverage · n/a = condition undefined for the task · Δ columns computed on raw counts before rounding (may differ from displayed cells by ±0.001) · † = transductively distilled guide (see caveats)
This is measured formulation behavior, not a bug — condition D exists to expose exactly this. With uniform emissions, every completion of length ℓ has probability V−ℓ (V ≈ 128k–152k vocab), so:
The data shows all three signatures (figures below are Llama unless
noted; Qwen is given where it differs). countdown-Llama (answers ≈ 14
chars): D = 0.188 ≈ B = 0.191 — harmless below the horizon, the clearest
evidence the D implementation is correct. But countdown-Qwen D = 0.641
vs B = 0.832: even short answers lose 19pp once the base model is capable
enough that the tilt competes with real preferences. nested_arith:
outputs collapse to minimal valid shapes ('1. \n7 \n2 \n7…') →
0. racket / planetarium (Llama): long outputs that are almost entirely
comments (penalty-free) with no scored structure → 0;
planetarium-Qwen reaches 0.105, not 0.
NESTFUL-Llama: minimal valid call-JSON (median 76 chars, vs 277
for the mask) — 200/200 schema-valid, 0.017 correct. E−D therefore reads: what does learning
the guide buy, given identical machinery.
accuracy_combined = the benchmark's combined intent+slot+sequencing metric. "schema-valid" = outputs strictly accepted by the call-schema parser as emitted.
| model | cond | acc_combined | f1_intent | f1_slot | schema-valid |
|---|---|---|---|---|---|
| llama | A | 0.183 | 0.704 | 0.432 | 0/200 |
| llama | B | 0.260 | 0.932 | 0.564 | 196/200 |
| llama | D | 0.017 | 0.443 | 0.050 | 200/200 |
| llama | E | 0.143 | 0.564 | 0.280 | 200/200 |
| qwen | A | 0.378 | 0.892 | 0.698 | 111/200 |
| qwen | B | 0.387 | 0.895 | 0.701 | 199/200 |
| qwen | D | 0.061 | 0.295 | 0.168 | 152/200 |
| qwen | E | 0.354 | 0.876 | 0.648 | 200/200 |
The accuracy–guarantee trade at λ=1: B leads accuracy (0.260) with 4/200 schema failures; E gives up ~12pp accuracy for zero schema failures; zeroshot A emits nothing strictly schema-valid (its calls are recovered only by lenient extraction). The earlier exploratory λ-sweep (archived) showed the gap closing as λ → 0.1–0.2 with validity retained — measuring that on the v2 prefix-conditioned guides is the λ-ablation section below, which found λ=0.1 recovers 0.252 (Llama) and 0.386 (Qwen).
The matrix above fixes λ=1 (the pure formulation). Here condition E is re-run at λ ∈ {0.1, 0.5} with everything else identical — same guides, same budgets, greedy, thinking unconstrained. The λ=0 column is condition B shown as the λ→0 limit of the guide term — it is a separate mask-only run, not a sweep point (the β=0 hard ban applies at every λ, so a literal λ=0 guided run is still not the mask). 24 guided cells, all complete at full n.
| task | A zeroshot | λ=0 (mask) | λ=0.1 | λ=0.5 | λ=1 |
|---|---|---|---|---|---|
| countdown | 0.805 | 0.832 | 0.832 | 0.836 | 0.832 |
| nested_arith | 0.396 | 0.383 | 0.130 | 0.201 | 0.000 |
| planetarium | 0.332 | 0.332 | 0.328 | 0.336 | 0.324 |
| racket | 0.286 | 0.360† | 0.000† | 0.000† | 0.000† |
| bbeh_dyck | 0.155 | 0.295† | 0.315† | 0.315† | 0.315† |
| nestfulacc + valid | 0.378— | 0.387199/200 | 0.386200/200 | 0.384200/200 | 0.354200/200 |
bold = best guided-or-mask cell in the row (suppressed when that best is 0.000) · λ=0 column = condition B, a separate mask-only run shown as the λ→0 limit; λ=1 = condition E from the main matrix · NESTFUL cells show accuracy with schema-validity beneath (jobs 1924871-1924874) · † = transductively distilled guide (see caveats)
| task | A zeroshot | λ=0 (mask) | λ=0.1 | λ=0.5 | λ=1 |
|---|---|---|---|---|---|
| countdown | 0.133 | 0.191 | 0.176 | 0.180 | 0.172 |
| nested_arith | 0.195 | 0.201 | 0.109 | 0.104 | 0.005 |
| planetarium | 0.012 | 0.016 | 0.016 | 0.027 | 0.016 |
| racket | 0.199 | 0.199† | 0.130† | 0.075† | 0.025† |
| bbeh_dyck | 0.045 | 0.000† | 0.000† | 0.000† | 0.000† |
| nestfulacc + valid | 0.183— | 0.260196/200 | 0.252195/200 | 0.222198/200 | 0.143200/200 |
bold = best guided-or-mask cell in the row (suppressed when that best is 0.000) · λ=0 column = condition B, a separate mask-only run shown as the λ→0 limit; λ=1 = condition E from the main matrix · NESTFUL cells show accuracy with schema-validity beneath (jobs 1924871-1924874) · † = transductively distilled guide (see caveats)
The matrix and the λ section both measure D2 — what grading the viable set buys. The other half of the decomposition is D1: what reachability look-ahead buys over plain one-step legality. Two arms, both on the same objects as the matrix.
Arm 1 (walker, repair disabled) turned out to be a null instrument: 0 differing answers in 2,063 matched examples. The per-task walkers are viability-complete by construction — their step function refuses any character that would strand the generation — so the decoder never dead-ends and the fallback is unreachable. It measures nothing about look-ahead and is reported here only so the zero is not mistaken for evidence.
Arm 2 (the real one): one-step nwa.step legality vs
1[β>0] from the offline DP, both on the same token NWA.
| lane | n | (a2) local | (b2) viability | D1 | differing | walker mask |
|---|---|---|---|---|---|---|
| countdown / llama | 256 | 0.0117 | 0.0117 | +0.0000 | 0 | 0.1914 |
| countdown / qwen | 202 | 0.1188 | 0.1188 | +0.0000 | 0 | 0.8366 |
| nested_arith / llama | 384 | 0.1172 | 0.1172 | +0.0000 | 1 | 0.2005 |
| nested_arith / qwen | 289 | 0.1730 | 0.1730 | +0.0000 | 5 | 0.5526 |
| planetarium / llama | 256 | 0.0156 | 0.0156 | +0.0000 | 0 | 0.0156 |
| planetarium / qwen | 256 | 0.3281 | 0.3281 | +0.0000 | 2 | 0.3320 |
| racket / llama | 161 | 0.1242 | 0.1242 | +0.0000 | 0 | 0.1988 |
| racket / qwen | 161 | 0.0000 | 0.0000 | +0.0000 | 0 | 0.3602 |
1,965 matched examples · 8 differing answers (0.41%) · D1 = +0.0000 on every lane · countdown/qwen and nested_arith/qwen are partial (202, 289) because their 12 h wall hit — matched-pair comparison still settles them, and they were not resubmitted to reconfirm a structural property on a shared cluster
Every structural NWA in this project is a bracket/balance hull, and on
such a hull every legal prefix extends to an accepting string by appending
closers. So 1[β>0] and 1[legal now] are the same
function, and the offline DP decides nothing the one-step relation had not
already decided. The project's own handoff argued exactly this a priori —
"nesting depth and look-ahead depth are orthogonal; bracket viability is
shallow look-ahead yet needs NWA state" — and this measures it on four
languages and two models. The NWA earns its keep through state (unbounded
nesting a DFA cannot track), not through look-ahead; β's remaining
justification is grading, i.e. D2.
The matrix, the λ ablation and the D1 study all return nulls for the guide. Three further experiments — the ladder from the project's own handoff — were run to establish why, and whether the nulls are about the method or about the benchmarks. They answer it.
What it does. Runs Ctrl-G on the benchmark it was built for, using its
own released assets (gpt2-large_common-gen + the h=4096 CommonGen
HMM) and its own DFA machinery: the constraint is keyword inclusion (every
concept must appear in some inflected form). Two arms, identical except for the
guide — uniform (h=2, uninformative) vs trained (h=4096). n=200.
Metric: constraint satisfaction, plus mean log pLM per generated
token (what rank_generated_ids already ranks by, i.e. the
pipeline's own notion of quality).
Why it matters. The handoff is blunt about it: "without this anchor, a null result anywhere else is uninterpretable." If our apparatus cannot detect a guide's benefit where one is known to exist, every null we report is worthless.
| arm | constraint satisfied | mean log pLM |
|---|---|---|
| uniform (h=2) | 97/200 (48.5%) | −2.217 |
| trained (h=4096) | 200/200 (100%) | −2.054 |
a third "viability" arm was attempted and abandoned after two failed implementations — documented in EXPERIMENT_LOG, excluded here rather than reported
Finding. The trained guide dominates on both axes at once: +51.5pp constraint satisfaction and better fluency. D2 is large and easily detected when it exists. The apparatus works.
What it does. Asks whether β̂ is actually a good forecaster of the thing it claims to predict. Take an in-language partial generation, read β̂ at its automaton config, then roll the base LM forward unconstrained 8× and record whether those rollouts satisfy the constraint. Score β̂ as a probabilistic forecaster of that realized event — Brier and AUC — for the trained guide and a uniform one. 59 live prefixes, stratified across difficulty.
Why it matters. Perplexity measures the guide's content model. Whether the guide predicts the satisfaction event is a different functional, and it is the one Ctrl-G's decoding rule actually consumes.
| guide | Brier (lower better) | AUC |
|---|---|---|
| trained (h=256) | 0.7431 | 0.9402 |
| uniform (h=2) | 0.7458 | 0.9462 |
base rate 0.746 — predicting the base rate alone would score Brier ≈ 0.19
Findings. (1) β is not a probability. Both guides forecast ≈1e−10 where realized satisfaction is 0.75; Brier ≈ 0.74 against ≈0.19 for base-rate prediction. (2) Learning adds nothing to event prediction — uniform is marginally ahead on AUC. Both rank well (≈0.94) using the same structural signal, shortest-completion length, which the uniform guide has by construction. This is the handoff's H4 hypothesis, confirmed.
What it does. Removes guide quality from the equation entirely. At each binding decision point (where more than one token is viable, so grading could change the outcome), it replaces β̂ with a Monte-Carlo estimate from the base LM itself: roll forward 6× per candidate and score the outcomes, then decode with that. It is the ceiling any future-aware scorer could reach.
| condition (matched, n=25 stratified) | accuracy |
|---|---|
| A zeroshot | 0.400 |
| B mask | 0.400 |
| E learned guide (λ=1) | 0.000 |
| T2 correctness-oracle | 0.360 |
oracle − mask = −0.040, one differing example in 25; oracle active at 11.0 decision points per generation on average
Finding. The oracle does not beat the mask. And this is the strong form: our oracle scores rollouts by verified task correctness, not merely by constraint satisfaction — it is more powerful than Ctrl-G's β is entitled to be — and it still buys nothing. By the handoff's own decision rule, nested_arith has no D2 headroom. Not a guide-fidelity problem.
caveats: n=25 with one differing example means "no detectable difference", not "the oracle is worse"; and the oracle is capped at 12 binding points before falling back to mask decoding, making it a lower bound on the true ceiling
ArithChainWalker._MAX_LINES = 128:
279/384 answers sit at the cap at λ=0.1 and 236/384 at λ=0.5, versus 12/384 for
the mask (median 128 lines vs 8). At λ=0.1, 49 of the 50 correct answers are
cap-truncated runs. So the 0.130-vs-0.201 gap measures how much padding
happens before the cap binds — the arith guided cells at every λ are
artifact-dominated and are not evidence about guidance on this benchmark.define, so a valid completion needs one extra
), but 84/161 guided answers are self-balanced standalone
fragments that leave the header open — the assembled program fails with
read-syntax: expected a ')' even though the answer alone parses.
The 2026-07-23 audit's "100% budget-truncated" reading was wrong and is
superseded. Root cause (splicing vs walker vs guide) still open.build_offline_cache (33 GiB C_stack
intermediate) and was deferred pending a loop-form build. Where guidance
underperforms, capacity is a live un-tested lever.results/_superseded/.results/_superseded/2026-07-23_bbeh_no_fix/ holds the full pre-'No'-fix
bbeh grid (Qwen A .200/B .295/C .330/D .115/E .330; Llama A .025/B .005/C
.000/D .005/E .000) and 2026-07-23_pddl_dfa_noop/ holds the two
no-op planetarium C files. The bbeh fix changed the prompt as well as the
walker, so pre/post bbeh numbers are not directly comparable.PLTADDONDIR); per-file labels re-checked offline before
reporting (a per-node package issue once produced all-False labels).| stage | code | notes |
|---|---|---|
| Task adapters (5 suite benchmarks; NESTFUL has its own m14 pipeline) | ctrlg/task_adapter.py |
load / messages / walker / NWA / extract / verify per task |
| NWA constructions | ctrlg/nwa_*.py |
per-task nested-word automata; documented supersets where exact languages are impractical |
| Runtime hard mask | ctrlg/walker_mask.py, ctrlg/nwa_nestful_mask.py |
char-level walkers over token strings; force-EOS on dead ends; mask_only = Outlines-style renormalize |
| Offline DP cache | ctrlg/nwa_backward.py |
build_offline_cache (Tι, Cγ, Rγ, W, β), fp64 |
| Guided processor | ctrlg/nwa_processor.py |
score = log pLM + λ·log β; hmm_prefix_ids conditions the belief on the prompt |
| Guide distillation | scripts/suite_distill.py, scripts/m14_distill_nestful_hmm.py |
constrained temp-1.0 corpus → LVD init → EM (--with_prefix v2 rows) |
| Evaluation | scripts/suite_eval.py, scripts/m14_constrained_eval.py |
modes = conditions; per-example checkpointing, resumable |
| Launcher / protocol | scripts/run_matrix.sh, PROTOCOL.md |
full matrix as Slurm jobs; canonical protocol text |
| task | n | max_think (Qwen) | max_new | guide |
|---|---|---|---|---|
| countdown | 256 | 8192 | 1024 | h256-pfx |
| nested_arith | 384 | 4096 | 1536 | h256-pfx |
| planetarium | 256 | 4096 | 2048 | h256-pfx |
| racket | 161 | 4096 | 384 | h256-pfx (transductive) |
| bbeh_dyck | 200 | 8192 | 256 (Llama 2048) | h256-pfx (transductive) |
| nestful | 200 | 8192 (Qwen only) | 512 | h512-pfx |
results/ at code 497f812 ·
suite cells = mean of per-example correct flags recomputed at
build time · NESTFUL cells = final log blocks of jobs
1896241/1896242/1896243/1898460
(Llama) and
1898686/1903191/1903192/1907241
(Qwen); NESTFUL λ cells = jobs 1924871/1924873 (Llama) and 1924872/1924874
(Qwen) ·
pre-protocol exploratory dashboard (frozen)