<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vaibhav Mittal</title>
    <description>The latest articles on DEV Community by Vaibhav Mittal (@vaibhav_mittal_ac22a2c5d6).</description>
    <link>https://dev.to/vaibhav_mittal_ac22a2c5d6</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4034131%2F105ed466-20be-4540-ab79-cb5f8576a2c1.png</url>
      <title>DEV Community: Vaibhav Mittal</title>
      <link>https://dev.to/vaibhav_mittal_ac22a2c5d6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vaibhav_mittal_ac22a2c5d6"/>
    <language>en</language>
    <item>
      <title>I Fixed a Schema Mismatch. The Negative Result Survived.</title>
      <dc:creator>Vaibhav Mittal</dc:creator>
      <pubDate>Sat, 08 Aug 2026 05:43:13 +0000</pubDate>
      <link>https://dev.to/vaibhav_mittal_ac22a2c5d6/i-fixed-a-schema-mismatch-the-negative-result-survived-192l</link>
      <guid>https://dev.to/vaibhav_mittal_ac22a2c5d6/i-fixed-a-schema-mismatch-the-negative-result-survived-192l</guid>
      <description>&lt;p&gt;An external reviewer found a real mismatch in my experiment.&lt;/p&gt;

&lt;p&gt;I had tested whether an LLM should generate a numeric string directly or generate a&lt;br&gt;
JSON integer that a deterministic transducer converts back to the caller's string&lt;br&gt;
contract.&lt;/p&gt;

&lt;p&gt;The Llama result was negative: the integer representation lost ten net correct&lt;br&gt;
answers on 150 unseen GSM8K items.&lt;/p&gt;

&lt;p&gt;But the two model-facing languages were not exactly equivalent.&lt;/p&gt;

&lt;p&gt;My string control accepted integers, decimals, fractions, comma grouping, and&lt;br&gt;
leading zeros. The compiler transform supported only canonical signed integers.&lt;/p&gt;

&lt;p&gt;That meant I had tested a representation change plus a domain-narrowing change.&lt;/p&gt;

&lt;p&gt;I did not explain the mismatch away. I preregistered one correction, generated one&lt;br&gt;
new 150-row control arm, reused the frozen treatment, audited every disagreement,&lt;br&gt;
and accepted the outcome.&lt;/p&gt;

&lt;p&gt;The negative result survived.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Exact canonical string control: 92/150 correct. Frozen integer treatment: 82/150.&lt;br&gt;
Paired effect: -6.7 percentage points, with interval [-12.7, -0.7].&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This closes the default optimizer thesis for this project. More importantly, it&lt;br&gt;
shows why model-facing schemas must be treated as semantic context, not transparent&lt;br&gt;
serialization wrappers.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Vaibhav701161" rel="noopener noreferrer"&gt;
        Vaibhav701161
      &lt;/a&gt; / &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab" rel="noopener noreferrer"&gt;
        constrained-decoding-lab
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Constrained Decoding Under Matched Conditions&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A controlled, artifact-validated study of how JSON prompting, grammar-constrained
decoding, and output-field order affect mathematical accuracy and schema compliance.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#principal-results" rel="noopener noreferrer"&gt;Results&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#contract-aligned-internal-representation" rel="noopener noreferrer"&gt;Alignment result&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#corrected-7b-replication" rel="noopener noreferrer"&gt;Corrected replication&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#item-level-and-mechanism-evidence" rel="noopener noreferrer"&gt;Paired evidence&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#study-design" rel="noopener noreferrer"&gt;Study design&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#reproduce-the-evaluation" rel="noopener noreferrer"&gt;Reproduction&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#evidence-map" rel="noopener noreferrer"&gt;Evidence&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#public-kaggle-artifacts" rel="noopener noreferrer"&gt;Public Kaggle artifacts&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#progress-presentation" rel="noopener noreferrer"&gt;Progress presentation&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#technical-articles" rel="noopener noreferrer"&gt;Technical articles&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#scope-and-limitations" rel="noopener noreferrer"&gt;Limitations&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Progress presentation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Watch this presentation for an overview of the project's progress, key findings
and results to date.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://youtu.be/82-3grLsO2M?si=e37WsDqnmG9DbTYY" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a9de11ac5ed9b6e42d3486ebc43586b031ba29c656f455bf630a9b3664637de8/68747470733a2f2f696d672e796f75747562652e636f6d2f76692f38322d3367724c734f324d2f6d617872657364656661756c742e6a7067" alt="Watch the constrained decoding project progress presentation"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://youtu.be/82-3grLsO2M?si=e37WsDqnmG9DbTYY" rel="nofollow noopener noreferrer"&gt;Watch on YouTube&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Central result&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Constrained decoding solved the formatting problem, but it did not preserve all of
the model's recoverable mathematical accuracy. On Qwen2.5-7B, prompt-only JSON
achieved 79.6% recoverable accuracy and 0% schema compliance. Outlines and XGrammar
each achieved 61.2% recoverable accuracy and 100% schema compliance. The paired
semantic effect was -18.4 percentage points for both backends (exact McNemar
&lt;code&gt;p = 0.003906&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This is not a claim that constrained decoding is universally harmful. It is a
controlled…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Vaibhav701161/constrained-decoding-lab" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  The mismatch
&lt;/h2&gt;

&lt;p&gt;The original Llama control used this broad numeric-string grammar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^-?(?:(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?|(?:\d+|\d{1,3}(?:,\d{3})+)/(?:\d+|\d{1,3}(?:,\d{3})+))$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It accepts values such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"001"
"1.0"
"36/2"
"1,000"
"-5.5"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The safe compiler transform accepts only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^-?(?:0|[1-9][0-9]*)$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It maps exactly between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;external JSON string "-12"
        |
        v
internal JSON integer -12
        |
        v
deterministic base-10 stringification
        |
        v
external JSON string "-12"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No rounding, parsing heuristic, sign repair, or second model call is allowed.&lt;/p&gt;

&lt;p&gt;Eight actual broad-control outputs were outside the canonical language: six on the&lt;br&gt;
fresh set and two on the bridge set. Examples included &lt;code&gt;"13.33"&lt;/code&gt;, &lt;code&gt;"55.5"&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;"94.98"&lt;/code&gt;, and &lt;code&gt;"36/2"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All eight were incorrect in both arms. So the mismatch did not directly explain the&lt;br&gt;
ten-answer Llama deficit. It still blocked the strongest claim about the exact&lt;br&gt;
compiler-supported transform.&lt;/p&gt;

&lt;p&gt;That distinction matters. A result can be directionally meaningful and still need a&lt;br&gt;
targeted correction before supporting its most specific interpretation.&lt;/p&gt;
&lt;h2&gt;
  
  
  The only new run I allowed
&lt;/h2&gt;

&lt;p&gt;I froze the correction before generation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Frozen value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;&lt;code&gt;meta-llama/Llama-3.2-3B-Instruct&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revision&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0cb88a4f764b7a12671c53f0838cd831a0843b95&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dataset&lt;/td&gt;
&lt;td&gt;Same 150 previously unseen GSM8K test items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;XGrammar 0.2.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decoding&lt;/td&gt;
&lt;td&gt;Greedy, seed 0, FP32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token budget&lt;/td&gt;
&lt;td&gt;256 maximum new tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt&lt;/td&gt;
&lt;td&gt;Byte-identical raw prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New arm&lt;/td&gt;
&lt;td&gt;Exact canonical signed integer string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Treatment&lt;/td&gt;
&lt;td&gt;Existing frozen integer artifact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exclusions&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The frozen treatment SHA-256 was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;298d1a38ad8d95d89ca97ab1f98d14bef4853342bf388d080f57f06de9c47342
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new run was not allowed to change the model, prompt, backend, package versions,&lt;br&gt;
precision, decoding settings, holdout, or treatment. It was also not allowed to add&lt;br&gt;
a bridge arm or Outlines arm.&lt;/p&gt;

&lt;p&gt;This was a correction, not a search for a favorable result.&lt;/p&gt;
&lt;h2&gt;
  
  
  Semantic-blind canary and resumability
&lt;/h2&gt;

&lt;p&gt;The first five rows were evaluated only for operational integrity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same item IDs and order;&lt;/li&gt;
&lt;li&gt;byte-identical prompts;&lt;/li&gt;
&lt;li&gt;one chat-template application;&lt;/li&gt;
&lt;li&gt;frozen model and tokenizer revisions;&lt;/li&gt;
&lt;li&gt;matching package and GPU environment;&lt;/li&gt;
&lt;li&gt;no duplicate rows;&lt;/li&gt;
&lt;li&gt;no exceptions or cap hits;&lt;/li&gt;
&lt;li&gt;100% internal and external validity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The canary did not inspect correctness before expansion.&lt;/p&gt;

&lt;p&gt;One operational interruption happened after row 23 when the local Modal client lost&lt;br&gt;
its heartbeat. Every completed row had already been committed to the remote evidence&lt;br&gt;
volume. I relaunched in detached resume mode, starting at row 24. No completed row&lt;br&gt;
was regenerated.&lt;/p&gt;

&lt;p&gt;The final new artifact contains 150 rows in the frozen dataset order, with zero&lt;br&gt;
errors, zero cap hits, zero internal-schema failures, and zero external-schema&lt;br&gt;
failures.&lt;/p&gt;
&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjuycm9y4art4my2w45l4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjuycm9y4art4my2w45l4.png" alt="Canonical schema-equivalence correction outcome, paired transitions, and complete discordance audit" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;th&gt;Canonical string control&lt;/th&gt;
&lt;th&gt;Frozen integer treatment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Contract-valid correct&lt;/td&gt;
&lt;td&gt;92/150, 61.3%&lt;/td&gt;
&lt;td&gt;82/150, 54.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic correct&lt;/td&gt;
&lt;td&gt;92/150, 61.3%&lt;/td&gt;
&lt;td&gt;82/150, 54.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final external valid&lt;/td&gt;
&lt;td&gt;150/150, 100.0%&lt;/td&gt;
&lt;td&gt;149/150, 99.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal schema valid&lt;/td&gt;
&lt;td&gt;150/150, 100.0%&lt;/td&gt;
&lt;td&gt;149/150, 99.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Errors&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token-cap hits&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mean generated tokens&lt;/td&gt;
&lt;td&gt;78.2&lt;/td&gt;
&lt;td&gt;78.9&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The paired transition matrix was:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Treatment correct&lt;/th&gt;
&lt;th&gt;Treatment wrong&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Control correct&lt;/td&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control wrong&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So the treatment had six unique wins and sixteen unique losses.&lt;/p&gt;

&lt;p&gt;The treatment-minus-control estimate was &lt;strong&gt;-6.7 percentage points&lt;/strong&gt;. The exact paired&lt;br&gt;
bootstrap 95% interval was &lt;strong&gt;[-12.7, -0.7] points&lt;/strong&gt;. The exact two-sided McNemar test&lt;br&gt;
was &lt;code&gt;p = 0.05248&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Those two uncertainty summaries answer related but different questions. I report&lt;br&gt;
both rather than selecting the one with the cleaner threshold. The deterministic&lt;br&gt;
paired bootstrap interval excludes zero. The exact McNemar result narrowly misses&lt;br&gt;
0.05.&lt;/p&gt;

&lt;p&gt;The preregistered decision did not depend on calling the result statistically&lt;br&gt;
significant. It said that if the exact canonical control still beat the frozen&lt;br&gt;
treatment, the optimizer thesis would close. The control beat it by ten net items.&lt;/p&gt;

&lt;p&gt;The treatment's one cap hit remained in the denominator. That item was wrong in both&lt;br&gt;
arms, so it did not create a discordant pair.&lt;/p&gt;
&lt;h2&gt;
  
  
  What changed relative to the broad control
&lt;/h2&gt;

&lt;p&gt;The canonical grammar did change model behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;134/150 raw outputs were byte-identical to the broad control;&lt;/li&gt;
&lt;li&gt;140/150 normalized final answers were identical;&lt;/li&gt;
&lt;li&gt;16 raw outputs changed;&lt;/li&gt;
&lt;li&gt;one item changed from wrong to correct;&lt;/li&gt;
&lt;li&gt;one item changed from correct to wrong;&lt;/li&gt;
&lt;li&gt;aggregate control correctness remained 92/150.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The six noncanonical fresh-set values disappeared, as required. But canonicalization&lt;br&gt;
did not improve aggregate accuracy or remove the treatment deficit.&lt;/p&gt;

&lt;p&gt;This is an important negative mechanism result. The broad language mismatch was&lt;br&gt;
real, but it was not the cause of the overall direction.&lt;/p&gt;
&lt;h2&gt;
  
  
  I inspected every discordant item
&lt;/h2&gt;

&lt;p&gt;There were 22 discordant pairs after the correction. I inspected all of them against&lt;br&gt;
the question, reference answer, raw control output, and raw treatment output.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Problem-interpretation change&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reasoning and final-answer inconsistency&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arithmetic regression&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arithmetic correction&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sign or lexical-boundary change&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parser or validator issue&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Truncation&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The dominant pattern was not an inability to emit a minus sign.&lt;/p&gt;

&lt;p&gt;For example, in one control-only win both conditions produced the same correct&lt;br&gt;
reasoning that Sam should receive 20 feet of fence. The integer treatment emitted&lt;br&gt;
&lt;code&gt;40&lt;/code&gt; anyway.&lt;/p&gt;

&lt;p&gt;In another, both conditions derived two trays of eggnog. The treatment selected the&lt;br&gt;
intermediate count of ten glasses as its final answer.&lt;/p&gt;

&lt;p&gt;In a treatment-only win, the string control stopped after calculating 4,750&lt;br&gt;
remaining graduation seats. The integer treatment completed the requested division&lt;br&gt;
by 950 graduates and returned five tickets each.&lt;/p&gt;

&lt;p&gt;These are trajectory-level changes. A one-field schema rewrite can alter which&lt;br&gt;
steps are retained, which arithmetic is executed, and which number becomes final.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Contract preservation after generation does not imply behavioral equivalence&lt;br&gt;
during generation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Engineering corrections beyond the run
&lt;/h2&gt;

&lt;p&gt;The review also exposed engineering work worth fixing regardless of the result.&lt;/p&gt;

&lt;p&gt;I made the following changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Defined the canonical signed-integer regex once and generated the experimental
schema through the same compiler rewrite path.&lt;/li&gt;
&lt;li&gt;Added one-command replay that reparses raw outputs, revalidates schemas,
retransduces objects, recomputes task and execution scores, and reconstructs paired
summaries.&lt;/li&gt;
&lt;li&gt;Made integer-to-string rewrites fail closed when &lt;code&gt;enum&lt;/code&gt;, &lt;code&gt;const&lt;/code&gt;, bounds, or
&lt;code&gt;multipleOf&lt;/code&gt; would otherwise be dropped.&lt;/li&gt;
&lt;li&gt;Replaced generic float coercion with exact integer and deliberate decimal
comparison.&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;pyproject.toml&lt;/code&gt;, MIT licensing, dependency extras, and CI on Python 3.11
and 3.12.&lt;/li&gt;
&lt;li&gt;Split lightweight replay dependencies from GPU and backend dependencies.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In a clean lightweight environment, 129 tests passed and eight generation or backend&lt;br&gt;
tests skipped explicitly. The replay command reconstructed 464 earlier GSM8K and&lt;br&gt;
tool-dispatch rows with zero row-score mismatches and zero paired-summary mismatches.&lt;/p&gt;

&lt;p&gt;The full pinned environment currently reports 143 passed and three Llama prompt&lt;br&gt;
parity tests skipped locally because they require the frozen Modal image.&lt;/p&gt;
&lt;h2&gt;
  
  
  The product decision
&lt;/h2&gt;

&lt;p&gt;I started by asking whether caller contracts could be compiled into model-friendlier&lt;br&gt;
internal representations that improve quality.&lt;/p&gt;

&lt;p&gt;The corrected Qwen experiment produced a positive estimate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;18/49 canonical contract-valid correct
        to
24/49 integer + transducer contract-valid correct
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the exact canonical Llama correction went the other way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;92/150 canonical contract-valid correct
        to
82/150 integer + transducer contract-valid correct
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A separate 30-item BFCL-derived deterministic tool-dispatch and post-state pilot also&lt;br&gt;
found no evidence of practical benefit. That pilot did not execute arbitrary&lt;br&gt;
business functions, and its interval was wide, so I do not claim general tool-calling&lt;br&gt;
harm.&lt;/p&gt;

&lt;p&gt;The project now has this primary loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;External contract
        |
        v
Candidate model-facing representations
        |
        v
Frozen paired workload
        |
        v
Matched constrained generations
        |
        v
External validation and deterministic dispatch
        |
        v
Paired correctness, uncertainty, and complete audit
        |
        v
Workload-scoped recommendation or refusal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The product is a &lt;strong&gt;contract-sensitivity evaluation harness&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The linter is secondary. It can say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This contract contains a representation-sensitive boundary.
Measure it before deployment.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It cannot say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Convert this string to an integer; accuracy will improve.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reproduce the evidence without a GPU
&lt;/h2&gt;

&lt;p&gt;The default replay path is intentionally lightweight:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Vaibhav701161/constrained-decoding-lab.git
&lt;span class="nb"&gt;cd &lt;/span&gt;constrained-decoding-lab
git checkout exp/canonical-schema-correction

uv venv .venv &lt;span class="nt"&gt;--python&lt;/span&gt; 3.12
uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--python&lt;/span&gt; .venv/bin/python &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;".[dev]"&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate

python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest
python scripts/replay_artifacts.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scope&lt;/span&gt; all &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--out&lt;/span&gt; /tmp/replay-validation.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete correction record includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/canonical-schema-correction/experiments/canonical-schema-equivalence-correction/protocol.md" rel="noopener noreferrer"&gt;preregistered protocol&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/canonical-schema-correction/experiments/canonical-schema-equivalence-correction/canary-gate.json" rel="noopener noreferrer"&gt;operational canary&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/canonical-schema-correction/experiments/canonical-schema-equivalence-correction/results/xgrammar_json_canonical_integer_string_reasoning_first.jsonl" rel="noopener noreferrer"&gt;raw 150-row control artifact&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/canonical-schema-correction/experiments/canonical-schema-equivalence-correction/artifact-validation.json" rel="noopener noreferrer"&gt;artifact validation&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/canonical-schema-correction/experiments/canonical-schema-equivalence-correction/paired-summary.md" rel="noopener noreferrer"&gt;paired summary&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/canonical-schema-correction/experiments/canonical-schema-equivalence-correction/failure-attribution.jsonl" rel="noopener noreferrer"&gt;complete 22-item audit&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/canonical-schema-correction/experiments/canonical-schema-equivalence-correction/decision-report.md" rel="noopener noreferrer"&gt;final decision report&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final lesson
&lt;/h2&gt;

&lt;p&gt;The compiler utility is still useful. It preserves the supported external contract,&lt;br&gt;
fails closed, and makes representation alternatives executable.&lt;/p&gt;

&lt;p&gt;What failed was the assumption that a model-friendlier representation would be a&lt;br&gt;
portable quality optimization.&lt;/p&gt;

&lt;p&gt;That is the result I am keeping.&lt;/p&gt;

&lt;p&gt;If a schema rewrite changes the tokens available to a model, it can change the whole&lt;br&gt;
generation trajectory. Measure it on the actual model and workload. Preserve every&lt;br&gt;
regression. Refuse to search until the story becomes positive.&lt;/p&gt;

&lt;p&gt;That is a stronger engineering system than an optimizer built from one encouraging&lt;br&gt;
experiment.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The Optimization Worked on Qwen. It Failed on Llama and Tool Calls.</title>
      <dc:creator>Vaibhav Mittal</dc:creator>
      <pubDate>Fri, 07 Aug 2026 19:56:08 +0000</pubDate>
      <link>https://dev.to/vaibhav_mittal_ac22a2c5d6/the-optimization-worked-on-qwen-it-failed-on-llama-and-tool-calls-40oe</link>
      <guid>https://dev.to/vaibhav_mittal_ac22a2c5d6/the-optimization-worked-on-qwen-it-failed-on-llama-and-tool-calls-40oe</guid>
      <description>&lt;blockquote&gt;
&lt;h2&gt;
  
  
  Correction update, 8 Aug 2026
&lt;/h2&gt;

&lt;p&gt;An external review found that the Llama string control described below accepted&lt;br&gt;
decimals, fractions, comma grouping, and leading zeros, while the safe compiler&lt;br&gt;
supported only canonical signed integers. I preregistered one exact canonical&lt;br&gt;
control arm and reused the frozen treatment. The result remained negative: 92/150&lt;br&gt;
versus 82/150, a -6.7 point paired effect with interval [-12.7, -0.7], 6 wins, and&lt;br&gt;
16 losses. This resolves the mismatch and closes the default optimizer thesis. See&lt;br&gt;
the &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/canonical-schema-correction/experiments/canonical-schema-equivalence-correction/decision-report.md" rel="noopener noreferrer"&gt;complete correction decision&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I had a promising compiler result.&lt;/p&gt;

&lt;p&gt;On Qwen2.5-7B, changing one model-facing JSON field from a signed numeric string to a&lt;br&gt;
native integer, then deterministically converting it back to the caller's unchanged&lt;br&gt;
string contract, improved contract-valid GSM8K correctness from 18/49 to 24/49.&lt;/p&gt;

&lt;p&gt;The paired estimate was +12.2 percentage points. The transform preserved 100%&lt;br&gt;
external validity. The implementation had property tests, exact inverse&lt;br&gt;
transduction, fail-closed validation, frozen artifacts, and two grammar backends that&lt;br&gt;
produced byte-identical outputs.&lt;/p&gt;

&lt;p&gt;It looked like the beginning of a contract compiler for LLMs.&lt;/p&gt;

&lt;p&gt;Then I ran the test that mattered:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the improvement survive on a different model family, unseen items, and an&lt;br&gt;
executable tool-call task?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It did not.&lt;/p&gt;

&lt;p&gt;On 150 unseen GSM8K items with Llama 3.2 3B, the same treatment reduced correctness&lt;br&gt;
from 92/150 to 82/150. On a separate 30-case executable primary sample built from&lt;br&gt;
pinned BFCL V4 cases, it reduced exact executable-call success from 26/30 to 24/30.&lt;/p&gt;

&lt;p&gt;The honest conclusion is not that the implementation failed. The transducer and&lt;br&gt;
validator worked perfectly in the practical pilot.&lt;/p&gt;

&lt;p&gt;The conclusion is more important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A contract-preserving representation change is still a semantic intervention on&lt;br&gt;
the model, and a local win is not a portable optimizer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The code, protocols, raw generations, exact hashes, paired statistics, validators,&lt;br&gt;
and complete discordance audits are public.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Vaibhav701161" rel="noopener noreferrer"&gt;
        Vaibhav701161
      &lt;/a&gt; / &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab" rel="noopener noreferrer"&gt;
        constrained-decoding-lab
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Constrained Decoding Under Matched Conditions&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A controlled, artifact-validated study of how JSON prompting, grammar-constrained
decoding, and output-field order affect mathematical accuracy and schema compliance.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#principal-results" rel="noopener noreferrer"&gt;Results&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#contract-aligned-internal-representation" rel="noopener noreferrer"&gt;Alignment result&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#corrected-7b-replication" rel="noopener noreferrer"&gt;Corrected replication&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#item-level-and-mechanism-evidence" rel="noopener noreferrer"&gt;Paired evidence&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#study-design" rel="noopener noreferrer"&gt;Study design&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#reproduce-the-evaluation" rel="noopener noreferrer"&gt;Reproduction&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#evidence-map" rel="noopener noreferrer"&gt;Evidence&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#public-kaggle-artifacts" rel="noopener noreferrer"&gt;Public Kaggle artifacts&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#progress-presentation" rel="noopener noreferrer"&gt;Progress presentation&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#technical-articles" rel="noopener noreferrer"&gt;Technical articles&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#scope-and-limitations" rel="noopener noreferrer"&gt;Limitations&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Progress presentation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Watch this presentation for an overview of the project's progress, key findings
and results to date.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://youtu.be/82-3grLsO2M?si=e37WsDqnmG9DbTYY" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a9de11ac5ed9b6e42d3486ebc43586b031ba29c656f455bf630a9b3664637de8/68747470733a2f2f696d672e796f75747562652e636f6d2f76692f38322d3367724c734f324d2f6d617872657364656661756c742e6a7067" alt="Watch the constrained decoding project progress presentation"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://youtu.be/82-3grLsO2M?si=e37WsDqnmG9DbTYY" rel="nofollow noopener noreferrer"&gt;Watch on YouTube&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Central result&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Constrained decoding solved the formatting problem, but it did not preserve all of
the model's recoverable mathematical accuracy. On Qwen2.5-7B, prompt-only JSON
achieved 79.6% recoverable accuracy and 0% schema compliance. Outlines and XGrammar
each achieved 61.2% recoverable accuracy and 100% schema compliance. The paired
semantic effect was -18.4 percentage points for both backends (exact McNemar
&lt;code&gt;p = 0.003906&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This is not a claim that constrained decoding is universally harmful. It is a
controlled…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Vaibhav701161/constrained-decoding-lab" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  What was being optimized?
&lt;/h2&gt;

&lt;p&gt;Suppose a caller requires this object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adjust_inventory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ABC-14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"quantity_delta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-12"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The externally required &lt;code&gt;quantity_delta&lt;/code&gt; is a canonical signed integer string. Its&lt;br&gt;
language is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^-?(0|[1-9][0-9]*)$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proposed internal contract lets the model emit a native JSON integer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adjust_inventory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ABC-14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"quantity_delta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-12&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runtime then performs an exact inverse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;external JSON Schema
        |
        v
contract IR and applicability analysis
        |
        v
internal JSON Schema
        |
        v
one constrained model generation
        |
        v
deterministic integer-to-string transduction
        |
        v
validation against the original external schema
        |
        v
caller-facing object
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no second model call. There is no sign repair, rounding, regex extraction,&lt;br&gt;
default insertion, key guessing, or fallback coercion. A generated boolean is&lt;br&gt;
rejected even though Python treats booleans as integer subclasses. Unsupported&lt;br&gt;
schemas fail closed.&lt;/p&gt;

&lt;p&gt;This proves a useful engineering property: the internal object can be converted back&lt;br&gt;
without changing the generated sign or magnitude.&lt;/p&gt;

&lt;p&gt;It does not prove a model-quality property: that asking the model for an integer&lt;br&gt;
will make it choose better values.&lt;/p&gt;

&lt;p&gt;That second claim needed replication.&lt;/p&gt;
&lt;h2&gt;
  
  
  The evidence before replication
&lt;/h2&gt;

&lt;p&gt;The project started with a matched Qwen2.5-7B study of constrained JSON generation.&lt;br&gt;
Prompt-only JSON achieved 79.6% recoverable GSM8K accuracy but 0% compliance with a&lt;br&gt;
numeric-string answer schema. Outlines and XGrammar achieved 100% schema compliance&lt;br&gt;
but only 61.2% recoverable accuracy.&lt;/p&gt;

&lt;p&gt;That -18.4 point semantic difference showed that schema compliance and task&lt;br&gt;
correctness were separate outcomes.&lt;/p&gt;

&lt;p&gt;The native-integer transform was a narrow attempt to recover some of the lost&lt;br&gt;
correctness without weakening the caller's contract. After finding and correcting a&lt;br&gt;
double-chat-template risk in the historical runner, I reran both representations&lt;br&gt;
from a frozen source.&lt;/p&gt;

&lt;p&gt;The corrected Qwen result was:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Representation&lt;/th&gt;
&lt;th&gt;Contract-valid correct&lt;/th&gt;
&lt;th&gt;External valid&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Signed numeric string&lt;/td&gt;
&lt;td&gt;18/49 (36.7%)&lt;/td&gt;
&lt;td&gt;49/49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native integer plus transducer&lt;/td&gt;
&lt;td&gt;24/49 (49.0%)&lt;/td&gt;
&lt;td&gt;49/49&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Paired statistics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;difference:                 +12.2 percentage points
exact bootstrap interval:   [0.0, 26.5] points
treatment-only wins:         9
control-only wins:           3
exact McNemar p:             0.145996
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was valid, positive, and uncertain. The interval touched zero. It was enough to&lt;br&gt;
authorize one independent replication, not enough to say the compiler worked in&lt;br&gt;
general.&lt;/p&gt;
&lt;h2&gt;
  
  
  I froze the Llama test before generation
&lt;/h2&gt;

&lt;p&gt;The confirmatory question was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On a non-Qwen model and unseen GSM8K items, does replacing a model-facing signed&lt;br&gt;
numeric string with a native JSON integer improve final external-contract-valid&lt;br&gt;
correctness after deterministic stringification?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The fixed design used:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Frozen value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;&lt;code&gt;meta-llama/Llama-3.2-3B-Instruct&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exact revision&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0cb88a4f764b7a12671c53f0838cd831a0843b95&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary backend&lt;/td&gt;
&lt;td&gt;XGrammar 0.2.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decoding&lt;/td&gt;
&lt;td&gt;Greedy, seed 0, FP32, 256 maximum new tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Confirmatory set&lt;/td&gt;
&lt;td&gt;150 randomly selected, previously unseen GSM8K test items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bridge set&lt;/td&gt;
&lt;td&gt;Existing cleaned 49-item set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary outcome&lt;/td&gt;
&lt;td&gt;Paired contract-valid correctness on the fresh 150&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error policy&lt;/td&gt;
&lt;td&gt;Every error, cap, invalid object, and transduction failure stays in the denominator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Post-launch exclusions&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The unseen-set preparation scanned existing JSONL artifacts under &lt;code&gt;results/&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;experiments/&lt;/code&gt;, and &lt;code&gt;deployment/&lt;/code&gt;, removed every previously used source ID, shuffled&lt;br&gt;
the remaining GSM8K test items with seed &lt;code&gt;20260815&lt;/code&gt;, and selected 150. The manifest&lt;br&gt;
records the excluded set, selected IDs, source, split, seed, and hashes.&lt;/p&gt;

&lt;p&gt;Control and treatment used one shared generation runtime. Model loading, tokenizer&lt;br&gt;
handling, chat templating, grammar compilation, output decoding, token counting,&lt;br&gt;
latency measurement, error handling, manifests, and resumable writing were&lt;br&gt;
identical. Only the model-facing schema, symbolic representation in the prompt, and&lt;br&gt;
treatment inverse transduction differed.&lt;/p&gt;

&lt;p&gt;The first five paired items were an operational canary. Expansion depended on IDs,&lt;br&gt;
ordering, prompt-template depth, outputs, errors, caps, validity, revisions,&lt;br&gt;
environment, signatures, and dataset hashes.&lt;/p&gt;

&lt;p&gt;Semantic wins were explicitly forbidden from influencing expansion.&lt;/p&gt;
&lt;h2&gt;
  
  
  The cross-family result was negative
&lt;/h2&gt;

&lt;p&gt;The complete XGrammar matrix contained 398 generations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fresh set:   150 control + 150 treatment
bridge set:   49 control +  49 treatment
total:       398 generations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fresh confirmatory result was:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;String control&lt;/th&gt;
&lt;th&gt;Integer treatment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Contract-valid correct&lt;/td&gt;
&lt;td&gt;92/150 (61.3%)&lt;/td&gt;
&lt;td&gt;82/150 (54.7%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final external valid&lt;/td&gt;
&lt;td&gt;150/150&lt;/td&gt;
&lt;td&gt;149/150&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal-schema valid&lt;/td&gt;
&lt;td&gt;150/150&lt;/td&gt;
&lt;td&gt;149/150&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generation errors&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token-cap hits&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Paired effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;difference:                 -6.7 percentage points
exact bootstrap interval:   [-12.7, -1.3] points
treatment-only wins:         5
control-only wins:          15
exact McNemar p:             0.041389
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The single treatment cap hit stayed in the denominator. It was not retried or&lt;br&gt;
repaired.&lt;/p&gt;

&lt;p&gt;The bridge set also moved slightly negative:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;control:                    21/49
treatment:                  20/49
difference:                 -2.0 points
interval:                   [-10.2, 6.1] points
wins : losses:               2 : 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The primary interval was entirely below zero. This was not a merely inconclusive&lt;br&gt;
replication. Under the frozen gate it was Red: no cross-family replication.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8g6k03k8diyflbe710o7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8g6k03k8diyflbe710o7.png" alt="Paired effects across the corrected Qwen, unseen Llama, and executable gates" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The failure audit changed the mechanism story
&lt;/h2&gt;

&lt;p&gt;I manually inspected every discordant item, not a sample of them.&lt;/p&gt;

&lt;p&gt;Across the fresh and bridge sets there were 25 discordances:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Problem-interpretation change&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reasoning-to-final inconsistency&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arithmetic regression&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arithmetic correction&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sign or lexical-boundary change&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parser or validator issue&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Truncation among discordants&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The representation change did not simply remove friction at a local quote or minus&lt;br&gt;
boundary. It changed broader generation behavior. Some final answers stopped&lt;br&gt;
matching the model's own reasoning. Other outputs interpreted the same word problem&lt;br&gt;
differently.&lt;/p&gt;

&lt;p&gt;That matters because a compiler analogy can become misleading. A conventional&lt;br&gt;
compiler transform is expected to preserve program semantics. Here, changing the&lt;br&gt;
schema shown to the model changes the probability distribution that produces the&lt;br&gt;
semantics in the first place.&lt;/p&gt;

&lt;p&gt;The inverse transducer can be exact while the generation intervention is not&lt;br&gt;
semantics-preserving at the model level.&lt;/p&gt;
&lt;h2&gt;
  
  
  I checked that XGrammar was not the culprit
&lt;/h2&gt;

&lt;p&gt;After freezing the XGrammar result and discordant set, I ran Outlines only on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;all 20 fresh discordant items; and&lt;/li&gt;
&lt;li&gt;20 concordant items selected with a frozen seed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both representations used the same 40 IDs.&lt;/p&gt;

&lt;p&gt;Results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signed-string outputs matching byte for byte: 40/40
integer outputs matching byte for byte:       40/40
all prompt and scoring fields matching:       80/80
same treatment cap hit reproduced:            yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is implementation-parity evidence, not a second statistical replication. It&lt;br&gt;
shows that the negative result on the audited subset was not specific to XGrammar's&lt;br&gt;
implementation.&lt;/p&gt;
&lt;h2&gt;
  
  
  Red authorized one bounded practical pilot
&lt;/h2&gt;

&lt;p&gt;A negative math replication did not automatically prove the idea useless for tool&lt;br&gt;
calls. The protocol allowed exactly one bounded practical pilot.&lt;/p&gt;

&lt;p&gt;The question became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does contract alignment improve the probability that an LLM emits an externally&lt;br&gt;
valid call that actually executes with the correct arguments and state?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I used pinned BFCL V4 &lt;code&gt;simple_python&lt;/code&gt; cases and official acceptable arguments as the&lt;br&gt;
reference foundation. I did not claim an official BFCL leaderboard evaluation. The&lt;br&gt;
external numeric-string contract was a project-defined adaptation, and deterministic&lt;br&gt;
local wrappers replaced real business functions.&lt;/p&gt;

&lt;p&gt;The wrappers had no external side effects. They checked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exact function selection;&lt;/li&gt;
&lt;li&gt;whole-object JSON validity;&lt;/li&gt;
&lt;li&gt;internal-schema validity;&lt;/li&gt;
&lt;li&gt;deterministic inverse transduction;&lt;/li&gt;
&lt;li&gt;reconstructed external-schema validity;&lt;/li&gt;
&lt;li&gt;exact typed argument semantics;&lt;/li&gt;
&lt;li&gt;dispatch and execution acceptance;&lt;/li&gt;
&lt;li&gt;correct post-execution state; and&lt;/li&gt;
&lt;li&gt;zero heuristic repairs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The executable dataset was selected mechanically
&lt;/h2&gt;

&lt;p&gt;An eligible pinned BFCL case had to contain one turn, one function, an object-like&lt;br&gt;
parameter schema, at least one required integer, a single unambiguous ground-truth&lt;br&gt;
call, and only supported schema constructs.&lt;/p&gt;

&lt;p&gt;The preparation code found 194 eligible cases out of 400.&lt;/p&gt;

&lt;p&gt;The primary pilot selected 30 uniformly without replacement using seed &lt;code&gt;20260817&lt;/code&gt;.&lt;br&gt;
Because negative required integers were rare, all 3 eligible negative cases formed a&lt;br&gt;
separate sign-stress set. They were never pooled into the primary effect.&lt;/p&gt;

&lt;p&gt;The two representations produced 66 total generations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;30 primary + 3 stress = 33 cases
33 control + 33 treatment = 66 generations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three-case canary checked 20 operational invariants without looking at semantic&lt;br&gt;
outcomes. The same files then resumed to the complete set.&lt;/p&gt;
&lt;h2&gt;
  
  
  The practical pilot was also Red
&lt;/h2&gt;

&lt;p&gt;Every one of the 66 calls was valid JSON, internally schema-valid, externally&lt;br&gt;
schema-valid after treatment transduction, and accepted by the deterministic&lt;br&gt;
dispatcher. There were zero generation errors, cap hits, transduction failures,&lt;br&gt;
execution failures, or heuristic repairs.&lt;/p&gt;

&lt;p&gt;That is excellent contract-boundary behavior.&lt;/p&gt;

&lt;p&gt;It still did not improve exact calls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqjfmaclqwzfdf6snbuus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqjfmaclqwzfdf6snbuus.png" alt="Executable pilot component outcomes and paired transition matrix" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Primary metric&lt;/th&gt;
&lt;th&gt;String control&lt;/th&gt;
&lt;th&gt;Integer treatment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Executable-contract success&lt;/td&gt;
&lt;td&gt;26/30 (86.7%)&lt;/td&gt;
&lt;td&gt;24/30 (80.0%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal-schema validity&lt;/td&gt;
&lt;td&gt;30/30&lt;/td&gt;
&lt;td&gt;30/30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External-schema validity&lt;/td&gt;
&lt;td&gt;30/30&lt;/td&gt;
&lt;td&gt;30/30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exact argument semantics&lt;/td&gt;
&lt;td&gt;26/30&lt;/td&gt;
&lt;td&gt;24/30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution acceptance&lt;/td&gt;
&lt;td&gt;30/30&lt;/td&gt;
&lt;td&gt;30/30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct post-execution state&lt;/td&gt;
&lt;td&gt;26/30&lt;/td&gt;
&lt;td&gt;24/30&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Paired statistics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;difference:                 -6.7 percentage points
exact bootstrap interval:   [-20.0, 6.7] points
treatment-only wins:         1
control-only wins:           3
exact McNemar p:             0.625
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sample is small and the interval crosses zero. I am not claiming statistically&lt;br&gt;
proven harm in tool calling generally.&lt;/p&gt;

&lt;p&gt;But the preregistered decision rule was directional. A zero or negative estimate, or&lt;br&gt;
losses greater than or equal to wins, was sufficient for Red. Both occurred.&lt;/p&gt;
&lt;h2&gt;
  
  
  Every practical discordance was inspected
&lt;/h2&gt;

&lt;p&gt;There were five discordant calls across the primary and stress sets.&lt;/p&gt;
&lt;h3&gt;
  
  
  Treatment correction: emissions
&lt;/h3&gt;

&lt;p&gt;Both conditions emitted the correct integer duration, &lt;code&gt;3&lt;/code&gt;. The control used the&lt;br&gt;
wrong non-integer energy type, &lt;code&gt;solar&lt;/code&gt;; the treatment used the accepted value,&lt;br&gt;
&lt;code&gt;renewable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This was a real correction, but not an integer-boundary repair.&lt;/p&gt;
&lt;h3&gt;
  
  
  Treatment regression: magnetic permeability
&lt;/h3&gt;

&lt;p&gt;Both required integer fields remained correct. The treatment changed an optional&lt;br&gt;
floating-point permeability from the pinned accepted value to a more precise physical&lt;br&gt;
constant. It was plausible, structurally valid, and wrong under the frozen call&lt;br&gt;
semantics.&lt;/p&gt;
&lt;h3&gt;
  
  
  Treatment regression: cooking conversion
&lt;/h3&gt;

&lt;p&gt;The user asked to convert &lt;code&gt;2&lt;/code&gt; pounds to ounces. The control passed &lt;code&gt;quantity=2&lt;/code&gt;. The&lt;br&gt;
treatment passed &lt;code&gt;quantity=16&lt;/code&gt;, apparently substituting the conversion result for the&lt;br&gt;
function input.&lt;/p&gt;
&lt;h3&gt;
  
  
  Treatment regression: restaurant threshold
&lt;/h3&gt;

&lt;p&gt;The request required a minimum rating of more than 4, represented by the pinned&lt;br&gt;
argument &lt;code&gt;4&lt;/code&gt;. The treatment emitted &lt;code&gt;5&lt;/code&gt;, strengthening and therefore changing the&lt;br&gt;
requested threshold.&lt;/p&gt;
&lt;h3&gt;
  
  
  Stress-set correction: quadratic roots
&lt;/h3&gt;

&lt;p&gt;Both conditions preserved &lt;code&gt;a=3&lt;/code&gt;, &lt;code&gt;b=-11&lt;/code&gt;, and &lt;code&gt;c=-4&lt;/code&gt; exactly. The treatment won only&lt;br&gt;
because a separate string field changed from &lt;code&gt;root_type="real"&lt;/code&gt; to&lt;br&gt;
&lt;code&gt;root_type="all"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So even the sole sign-stress repair was not a sign repair.&lt;/p&gt;

&lt;p&gt;The complete audit contained two semantic corrections and three semantic&lt;br&gt;
regressions. No discordance came from validation, transduction, dispatch, truncation,&lt;br&gt;
or parsing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why I am stopping the optimizer thesis
&lt;/h2&gt;

&lt;p&gt;The three decision gates now tell a coherent story:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Qwen corrected GSM8K:       +12.2 points, positive and uncertain
Llama unseen GSM8K:          -6.7 points, interval below zero
Llama executable pilot:      -6.7 points, small and uncertain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would be easy to rescue the narrative by pooling the positive three-case stress&lt;br&gt;
set, changing prompts, trying more model families, or emphasizing perfect validity&lt;br&gt;
while hiding incorrect arguments.&lt;/p&gt;

&lt;p&gt;That would be research theater.&lt;/p&gt;

&lt;p&gt;The external contract was preserved. The model's call meaning was not reliably&lt;br&gt;
improved. The broad claim failed twice after the initial Qwen result.&lt;/p&gt;

&lt;p&gt;The optimizing-compiler thesis is closed under the current evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  What remains genuinely useful
&lt;/h2&gt;

&lt;p&gt;The negative result does not make the infrastructure useless. It changes what the&lt;br&gt;
product should be.&lt;/p&gt;

&lt;p&gt;The repository already has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;canonical contract IR and stable hashing;&lt;/li&gt;
&lt;li&gt;deterministic, serializable alignment plans;&lt;/li&gt;
&lt;li&gt;conservative transducers with exact inverses;&lt;/li&gt;
&lt;li&gt;final validation against the original schema;&lt;/li&gt;
&lt;li&gt;typed fail-closed refusals;&lt;/li&gt;
&lt;li&gt;unified matched runners;&lt;/li&gt;
&lt;li&gt;resumable artifact writing;&lt;/li&gt;
&lt;li&gt;source, dataset, environment, and run manifests;&lt;/li&gt;
&lt;li&gt;paired bootstrap intervals and exact McNemar tests;&lt;/li&gt;
&lt;li&gt;deterministic execution receipts; and&lt;/li&gt;
&lt;li&gt;complete item-level discordance audits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those components are the foundation for a different product:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Schema-risk linter
&lt;/h3&gt;

&lt;p&gt;Identify contract features that may create fragile lexical or generation boundaries.&lt;br&gt;
Do not automatically rewrite them. Explain the risk and the evidence scope.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Contract-sensitivity analyzer
&lt;/h3&gt;

&lt;p&gt;Run frozen paired interventions on the user's actual workload. Report validity,&lt;br&gt;
semantic wins, regressions, uncertainty, cap hits, latency, and failure mechanisms&lt;br&gt;
separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Reproducible measurement harness
&lt;/h3&gt;

&lt;p&gt;Preserve prompts, chat templates, model revisions, schemas, environments, raw&lt;br&gt;
outputs, execution state, and exact artifact hashes so that a structured-output&lt;br&gt;
change can be audited before deployment.&lt;/p&gt;

&lt;p&gt;The integer-to-string transducer remains useful as a deterministic utility. It is&lt;br&gt;
not a default quality optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Contract correctness and task correctness are orthogonal
&lt;/h3&gt;

&lt;p&gt;The executable treatment reconstructed 100% valid external calls and executed 100%&lt;br&gt;
of them. It still produced fewer exactly correct calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema changes can perturb untouched fields
&lt;/h3&gt;

&lt;p&gt;Three discordances changed fields whose types were identical across conditions. A&lt;br&gt;
local schema edit can alter the entire autoregressive path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend agreement is not independent replication
&lt;/h3&gt;

&lt;p&gt;Byte-identical XGrammar and Outlines outputs show implementation parity. They do not&lt;br&gt;
turn one model result into two replications.&lt;/p&gt;

&lt;h3&gt;
  
  
  An uncertainty interval does not replace a frozen decision rule
&lt;/h3&gt;

&lt;p&gt;The small practical pilot cannot prove general harm. It can still fail a&lt;br&gt;
preregistered continuation gate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Negative replication is product information
&lt;/h3&gt;

&lt;p&gt;The most valuable result was not another positive chart. It was learning which claim&lt;br&gt;
the system could no longer honestly make.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproducibility status
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Llama primary XGrammar rows:       398/398 validated
Llama discordances audited:         25/25
Outlines parity outputs:            80/80 byte-identical
Executable pilot rows:              66/66 validated
Executable discordances audited:     5/5
Post-launch exclusions:               0
Semantic retries:                     0
Modal billed cost:                 $0.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The canonical reports are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/llama32-second-family/experiments/second-family-replication/decision-report.md" rel="noopener noreferrer"&gt;Llama replication decision&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/llama32-second-family/experiments/second-family-replication/paired-summary.md" rel="noopener noreferrer"&gt;Llama paired summary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/llama32-second-family/experiments/tool-call-gate/decision-report.md" rel="noopener noreferrer"&gt;Executable pilot decision&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/llama32-second-family/experiments/tool-call-gate/failure-attribution.jsonl" rel="noopener noreferrer"&gt;Complete practical discordance audit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/llama32-second-family/docs/evidence-status.md" rel="noopener noreferrer"&gt;Current evidence and product status&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I started this stage trying to validate an optimizing compiler.&lt;/p&gt;

&lt;p&gt;I finished it with a more defensible system: one that can tell you when a contract&lt;br&gt;
change is safe, when it is harmful, and when the evidence is too weak to decide.&lt;/p&gt;

&lt;p&gt;That is less dramatic than claiming a universal optimization.&lt;/p&gt;

&lt;p&gt;It is also much more useful.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Found a Runner Bug, Re-ran 200 Generations, and the Effect Survived</title>
      <dc:creator>Vaibhav Mittal</dc:creator>
      <pubDate>Fri, 07 Aug 2026 13:29:56 +0000</pubDate>
      <link>https://dev.to/vaibhav_mittal_ac22a2c5d6/i-found-a-runner-bug-re-ran-200-generations-and-the-effect-survived-o5c</link>
      <guid>https://dev.to/vaibhav_mittal_ac22a2c5d6/i-found-a-runner-bug-re-ran-200-generations-and-the-effect-survived-o5c</guid>
      <description>&lt;blockquote&gt;
&lt;h2&gt;
  
  
  Update, 8 Aug 2026
&lt;/h2&gt;

&lt;p&gt;The corrected Qwen estimate below remains valid for this frozen model and workload,&lt;br&gt;
but it is not a portable optimizer result. A fresh Llama replication reversed&lt;br&gt;
direction. After an external review found a numeric-language mismatch in that&lt;br&gt;
Llama control, I ran one preregistered exact canonical correction. The canonical&lt;br&gt;
string control still scored 92/150 versus 82/150 for the immutable integer&lt;br&gt;
treatment, with a paired interval of [-12.7, -0.7] points. See the &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/canonical-schema-correction/experiments/canonical-schema-equivalence-correction/decision-report.md" rel="noopener noreferrer"&gt;complete&lt;br&gt;
correction decision&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I had a result I wanted to believe.&lt;/p&gt;

&lt;p&gt;Changing the model-facing JSON answer from a signed numeric string to a native&lt;br&gt;
integer, then deterministically converting it back to the caller's string contract,&lt;br&gt;
appeared to recover 14.3 percentage points of constrained accuracy on Qwen2.5-7B.&lt;/p&gt;

&lt;p&gt;Then an audit found a real problem in the experiment runner.&lt;/p&gt;

&lt;p&gt;The historical Outlines path could receive a prompt after the Qwen chat template had&lt;br&gt;
already been applied, while the Outlines Transformers adapter could apply that&lt;br&gt;
template again. The accepted artifacts remained complete and provenance-valid, but&lt;br&gt;
the control and treatment evidence could no longer support an architecture decision&lt;br&gt;
without a corrected paired run.&lt;/p&gt;

&lt;p&gt;So I froze a new protocol, corrected both sides of the comparison, ran 200 new cloud&lt;br&gt;
generations, downloaded every row, and validated the bundle independently.&lt;/p&gt;

&lt;p&gt;The effect survived in direction and practical size:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Contract-valid correctness increased from 18/49 to 24/49, a paired gain of 12.2&lt;br&gt;
percentage points, while final external-schema validity remained 100%.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But the uncertainty matters. The exact bootstrap interval is &lt;code&gt;[0.0, 26.5]&lt;/code&gt; points&lt;br&gt;
and exact McNemar &lt;code&gt;p = 0.146&lt;/code&gt;. This is evidence for a scoped next stage, not proof of&lt;br&gt;
a general solution.&lt;/p&gt;

&lt;p&gt;The complete code, protocol, raw rows, hashes, traces, validation report, and decision&lt;br&gt;
are public in the repository.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Vaibhav701161" rel="noopener noreferrer"&gt;
        Vaibhav701161
      &lt;/a&gt; / &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab" rel="noopener noreferrer"&gt;
        constrained-decoding-lab
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Constrained Decoding Under Matched Conditions&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A controlled, artifact-validated study of how JSON prompting, grammar-constrained
decoding, and output-field order affect mathematical accuracy and schema compliance.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#principal-results" rel="noopener noreferrer"&gt;Results&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#contract-aligned-internal-representation" rel="noopener noreferrer"&gt;Alignment result&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#corrected-7b-replication" rel="noopener noreferrer"&gt;Corrected replication&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#item-level-and-mechanism-evidence" rel="noopener noreferrer"&gt;Paired evidence&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#study-design" rel="noopener noreferrer"&gt;Study design&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#reproduce-the-evaluation" rel="noopener noreferrer"&gt;Reproduction&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#evidence-map" rel="noopener noreferrer"&gt;Evidence&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#public-kaggle-artifacts" rel="noopener noreferrer"&gt;Public Kaggle artifacts&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#progress-presentation" rel="noopener noreferrer"&gt;Progress presentation&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#technical-articles" rel="noopener noreferrer"&gt;Technical articles&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#scope-and-limitations" rel="noopener noreferrer"&gt;Limitations&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Progress presentation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Watch this presentation for an overview of the project's progress, key findings
and results to date.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://youtu.be/82-3grLsO2M?si=e37WsDqnmG9DbTYY" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a9de11ac5ed9b6e42d3486ebc43586b031ba29c656f455bf630a9b3664637de8/68747470733a2f2f696d672e796f75747562652e636f6d2f76692f38322d3367724c734f324d2f6d617872657364656661756c742e6a7067" alt="Watch the constrained decoding project progress presentation"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://youtu.be/82-3grLsO2M?si=e37WsDqnmG9DbTYY" rel="nofollow noopener noreferrer"&gt;Watch on YouTube&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Central result&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Constrained decoding solved the formatting problem, but it did not preserve all of
the model's recoverable mathematical accuracy. On Qwen2.5-7B, prompt-only JSON
achieved 79.6% recoverable accuracy and 0% schema compliance. Outlines and XGrammar
each achieved 61.2% recoverable accuracy and 100% schema compliance. The paired
semantic effect was -18.4 percentage points for both backends (exact McNemar
&lt;code&gt;p = 0.003906&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This is not a claim that constrained decoding is universally harmful. It is a
controlled…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Vaibhav701161/constrained-decoding-lab" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  The system boundary I am testing
&lt;/h2&gt;

&lt;p&gt;Suppose an API requires this external object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"18000"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The answer must be a canonical signed-integer string. The model, however, may be&lt;br&gt;
better aligned with a native JSON integer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proposed compiler does not weaken the caller's contract. It creates a separate&lt;br&gt;
model-facing contract, generates once under that grammar, applies an exact inverse&lt;br&gt;
transformation, and validates the final object against the original schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;external schema
     |
     v
contract IR and applicability checks
     |
     v
model-facing schema plus replayable transform plan
     |
     v
one constrained generation
     |
     v
deterministic reverse transduction
     |
     v
original external-schema validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this experiment, the only semantic transform is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;external canonical signed-integer string
    -&amp;gt; internal JSON integer
    -&amp;gt; arbitrary-precision base-10 stringification
    -&amp;gt; external validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no second model call, sign repair, rounding, regex guessing, or fallback&lt;br&gt;
coercion. A boolean is rejected even though Python treats it as an integer subtype.&lt;br&gt;
Lexical values such as &lt;code&gt;"+26"&lt;/code&gt;, &lt;code&gt;"00026"&lt;/code&gt;, &lt;code&gt;"26.0"&lt;/code&gt;, and &lt;code&gt;"2.6e1"&lt;/code&gt; are not guessed&lt;br&gt;
into the accepted language. Unsupported cases fail closed.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the audit found
&lt;/h2&gt;

&lt;p&gt;The audit identified three separate defects or inconsistencies.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. A double-chat-template risk
&lt;/h3&gt;

&lt;p&gt;For string inputs, Outlines owns chat templating through its Transformers adapter.&lt;br&gt;
The runner had passed an already formatted chat string into that layer. A Qwen prompt&lt;br&gt;
could therefore be wrapped twice.&lt;/p&gt;

&lt;p&gt;The corrected path passes the raw project prompt to Outlines. Direct Transformers&lt;br&gt;
and XGrammar generation use the same shared formatting helper exactly once. Tests&lt;br&gt;
compare effective token IDs and prove that the nested form is different.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Backend-dependent generated-token counts
&lt;/h3&gt;

&lt;p&gt;Direct generation and XGrammar counted generated tensor IDs. Outlines counted the&lt;br&gt;
visible text after decoding. A backend-only stop token could make those metrics&lt;br&gt;
incomparable.&lt;/p&gt;

&lt;p&gt;The corrected metric retokenizes visible generated content without special tokens&lt;br&gt;
for every backend. This is a measurement fix, not a model behavior change.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Noncanonical whitespace policies
&lt;/h3&gt;

&lt;p&gt;Permissive JSON whitespace loops add legal token paths and can create avoidable&lt;br&gt;
stalls. The corrected runner pins compact separators in XGrammar and an empty&lt;br&gt;
whitespace pattern in Outlines.&lt;/p&gt;

&lt;p&gt;Real pinned-backend tests verify that compact valid JSON is accepted and a long&lt;br&gt;
whitespace prefix is rejected.&lt;/p&gt;

&lt;p&gt;These corrections changed the experimental system. Comparing a corrected treatment&lt;br&gt;
to a historical control would mix runner versions, so I reran both representations.&lt;/p&gt;
&lt;h2&gt;
  
  
  The protocol was frozen before the cloud run
&lt;/h2&gt;

&lt;p&gt;The corrected question was narrow:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Under greedy FP32 decoding on Qwen2.5-7B-Instruct, does a model-facing JSON integer&lt;br&gt;
improve contract-valid correctness over a signed numeric string when both sides use&lt;br&gt;
the same corrected source and the final object must satisfy the unchanged external&lt;br&gt;
contract?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The frozen design used:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Qwen2.5-7B-Instruct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dataset&lt;/td&gt;
&lt;td&gt;Deterministic GSM8K-50 test subset, seed 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clean paired set&lt;/td&gt;
&lt;td&gt;49 items after one previously documented contradictory reference exclusion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decoding&lt;/td&gt;
&lt;td&gt;Greedy, seed 0, FP32, 256 maximum new tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backends&lt;/td&gt;
&lt;td&gt;Outlines 1.3.2 and XGrammar 0.2.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External contract&lt;/td&gt;
&lt;td&gt;Reasoning string followed by signed numeric answer string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Treatment&lt;/td&gt;
&lt;td&gt;Internal integer followed by deterministic stringification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary metric&lt;/td&gt;
&lt;td&gt;Paired delta in contract-valid correctness&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The job first ran the same five deterministic items in all four conditions. Expansion&lt;br&gt;
to 50 was allowed only if row identity, prompt equivalence, schema validity, run&lt;br&gt;
signatures, nonblank output, and cap status passed. The canary reused the same result&lt;br&gt;
files, so it did not create duplicate research rows.&lt;/p&gt;

&lt;p&gt;The Green rule required at least five points of recovery, 100% final external&lt;br&gt;
validity, paired wins exceeding losses, no systematic replacement failure, and&lt;br&gt;
evidence consistent with a representation-boundary mechanism.&lt;/p&gt;

&lt;p&gt;Importantly, semantic correctness was not part of the canary. Stopping or expanding&lt;br&gt;
based on the first five answers would have biased the confirmation.&lt;/p&gt;
&lt;h2&gt;
  
  
  The corrected result
&lt;/h2&gt;

&lt;p&gt;The cloud job produced all 200 expected rows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 backends x 2 representations x 50 items = 200 generations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The clean paired analysis retained 49 items under the audit policy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi69s5dbv72gf3zi7yw7p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi69s5dbv72gf3zi7yw7p.png" alt="Corrected contract-valid accuracy with intervals and artifact integrity" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Representation&lt;/th&gt;
&lt;th&gt;Correct&lt;/th&gt;
&lt;th&gt;Contract-valid accuracy&lt;/th&gt;
&lt;th&gt;Final external validity&lt;/th&gt;
&lt;th&gt;Negative answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Signed numeric string&lt;/td&gt;
&lt;td&gt;18/49&lt;/td&gt;
&lt;td&gt;36.7%&lt;/td&gt;
&lt;td&gt;49/49&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal integer plus transducer&lt;/td&gt;
&lt;td&gt;24/49&lt;/td&gt;
&lt;td&gt;49.0%&lt;/td&gt;
&lt;td&gt;49/49&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Paired effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;net improvement:                  6 items
accuracy delta:                  +12.2 percentage points
exact bootstrap 95% interval:    [0.0, 26.5] points
treatment-only repairs:           9
control-only regressions:         3
both correct:                    15
both wrong:                      22
exact two-sided McNemar p:        0.145996
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft887nc634kfg5z8pm7kf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft887nc634kfg5z8pm7kf.png" alt="Full paired correctness transition matrix" width="800" height="678"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The intervention repaired three times as many items as it broke. It also preserved&lt;br&gt;
the external contract for every clean treatment output.&lt;/p&gt;

&lt;p&gt;The point estimate clears the preregistered continuation threshold. It does not&lt;br&gt;
clear a conventional &lt;code&gt;p &amp;lt; 0.05&lt;/code&gt; threshold, and the interval touches zero. The honest&lt;br&gt;
conclusion is not "the compiler works universally." It is "the corrected evidence is&lt;br&gt;
strong enough to justify one bounded external-validity test."&lt;/p&gt;
&lt;h2&gt;
  
  
  Every item remains inspectable
&lt;/h2&gt;

&lt;p&gt;Aggregate accuracy can hide cherry-picked wins or a concentrated replacement&lt;br&gt;
failure. The item map below shows both outcomes for every clean-analysis item in the&lt;br&gt;
frozen dataset order.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2priomvuqizl6xphuxmk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2priomvuqizl6xphuxmk.png" alt="Item-level correctness for the signed-string control and integer treatment" width="799" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The nine repaired items were:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gsm8k_test_98
gsm8k_test_416
gsm8k_test_712
gsm8k_test_739
gsm8k_test_765
gsm8k_test_1205
gsm8k_test_1232
gsm8k_test_1251
gsm8k_test_1272
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three regressions were:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gsm8k_test_506
gsm8k_test_601
gsm8k_test_996
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The regressions remain in the primary denominator. They are not treated as outliers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two backends became an implementation agreement check
&lt;/h2&gt;

&lt;p&gt;Outlines and XGrammar emitted byte-identical raw output on all 50 signed-string items&lt;br&gt;
and all 50 integer items.&lt;/p&gt;

&lt;p&gt;That is strong evidence that both corrected integrations realized the same canonical&lt;br&gt;
token path in this run. It also changes the statistical interpretation.&lt;/p&gt;

&lt;p&gt;These are not two independent semantic replications. They are two implementations&lt;br&gt;
agreeing on one effective 49-item paired experiment. Drawing separate effect bars and&lt;br&gt;
calling the result replicated twice would overstate the evidence.&lt;/p&gt;

&lt;p&gt;The observed semantic difference is associated with the model-facing representation,&lt;br&gt;
not backend identity, under these matched settings.&lt;/p&gt;
&lt;h2&gt;
  
  
  The mechanism is subtler than "integers forbid negatives"
&lt;/h2&gt;

&lt;p&gt;Both negative signed-string outputs were repaired by the integer treatment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gsm8k_test_712:  -14 -&amp;gt; 14
gsm8k_test_1205:  -1 -&amp;gt; 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But JSON integers allow negative values. The integer grammar did not prohibit the&lt;br&gt;
minus token.&lt;/p&gt;

&lt;p&gt;The XGrammar boundary traces show that the integer grammar removes quote and&lt;br&gt;
structural-space continuations at the answer boundary while leaving minus legal. The&lt;br&gt;
model therefore traverses a different legal token path. That path can affect the&lt;br&gt;
first answer token and, because the answer follows reasoning, earlier generation as&lt;br&gt;
well.&lt;/p&gt;

&lt;p&gt;This is consistent with a representation-boundary mechanism. It is not proof that&lt;br&gt;
every repair came from one local mask decision.&lt;/p&gt;
&lt;h2&gt;
  
  
  A correct benchmark answer can still have unfaithful reasoning
&lt;/h2&gt;

&lt;p&gt;One repaired item is an important counterexample.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;gsm8k_test_712&lt;/code&gt;, the integer output's reasoning still computes &lt;code&gt;-14&lt;/code&gt;, but its&lt;br&gt;
final answer is &lt;code&gt;14&lt;/code&gt;. The benchmark score is correct. The generated explanation is&lt;br&gt;
internally inconsistent.&lt;/p&gt;

&lt;p&gt;So the evidence supports improved final-answer fidelity. It does not establish&lt;br&gt;
improved reasoning faithfulness.&lt;/p&gt;

&lt;p&gt;This distinction matters for any production system that exposes explanations,&lt;br&gt;
executes intermediate values, or relies on chain-of-thought consistency.&lt;/p&gt;
&lt;h2&gt;
  
  
  The artifact validator does not trust the remote summary
&lt;/h2&gt;

&lt;p&gt;After the Kaggle run completed, a separate local validator checked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;200/200 expected rows;&lt;/li&gt;
&lt;li&gt;50 unique items in every condition;&lt;/li&gt;
&lt;li&gt;source, runner, manifest, and result hashes;&lt;/li&gt;
&lt;li&gt;run signatures and frozen item order;&lt;/li&gt;
&lt;li&gt;prompt equivalence within each representation;&lt;/li&gt;
&lt;li&gt;byte equality across the two backends;&lt;/li&gt;
&lt;li&gt;generation errors, cap hits, and blank outputs;&lt;/li&gt;
&lt;li&gt;internal and external schema validity;&lt;/li&gt;
&lt;li&gt;the three preregistered boundary traces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It reported zero failures and zero warnings. The run contained zero generation errors,&lt;br&gt;
zero cap hits, zero blank outputs, and zero schema failures.&lt;/p&gt;

&lt;p&gt;The summary was then regenerated locally. The paired bootstrap interval is computed&lt;br&gt;
exactly from the empirical paired distribution by finite convolution, not estimated&lt;br&gt;
from a random 10,000-resample loop.&lt;/p&gt;
&lt;h2&gt;
  
  
  What exists beyond the experiment
&lt;/h2&gt;

&lt;p&gt;The repository now contains a conservative compiler prototype rather than only a&lt;br&gt;
one-off transducer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a canonical, hashable contract IR;&lt;/li&gt;
&lt;li&gt;explicit unsupported-construct records;&lt;/li&gt;
&lt;li&gt;deterministic, serializable alignment plans;&lt;/li&gt;
&lt;li&gt;backend capability and whitespace requirements;&lt;/li&gt;
&lt;li&gt;integer-string, key-alias, field-order, scratch-field, and whitespace transforms;&lt;/li&gt;
&lt;li&gt;exact reverse transduction;&lt;/li&gt;
&lt;li&gt;final validation against the original schema;&lt;/li&gt;
&lt;li&gt;typed fail-closed refusals;&lt;/li&gt;
&lt;li&gt;property and adversarial tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implementation refuses references and schema features outside its initial&lt;br&gt;
supported subset. That is intentional. A compiler that silently discards contract&lt;br&gt;
semantics would be worse than no compiler.&lt;/p&gt;

&lt;p&gt;The current test suite passes 87 tests, including 1,501 integer property cases and&lt;br&gt;
real pinned-backend grammar probes. Those tests establish deterministic contract&lt;br&gt;
behavior and integration. They do not substitute for external semantic evidence.&lt;/p&gt;
&lt;h2&gt;
  
  
  What changed relative to the historical result
&lt;/h2&gt;

&lt;p&gt;The historical mixed-runner paths reported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signed string:     61.2%
internal integer:  75.5%
delta:             +14.3 points
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The corrected paired paths report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signed string:     36.7%
internal integer:  49.0%
delta:             +12.2 points
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The absolute rates changed substantially. The direction and approximate treatment&lt;br&gt;
effect survived. Historical and corrected rates must not be pooled because prompt&lt;br&gt;
handling and canonical grammar behavior differ between runner versions.&lt;/p&gt;

&lt;p&gt;This is why preserving old artifacts matters. A correction should create a new&lt;br&gt;
evidence layer, not rewrite what happened.&lt;/p&gt;
&lt;h2&gt;
  
  
  The decision: Green, but scoped
&lt;/h2&gt;

&lt;p&gt;The corrected result satisfies the registered continuation rule:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The point recovery exceeds five percentage points.&lt;/li&gt;
&lt;li&gt;Final external validity is 100%.&lt;/li&gt;
&lt;li&gt;Paired wins exceed losses.&lt;/li&gt;
&lt;li&gt;No systematic replacement failure was observed.&lt;/li&gt;
&lt;li&gt;Boundary evidence remains consistent with the representation hypothesis.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The limitations are equally concrete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;only 49 clean paired items;&lt;/li&gt;
&lt;li&gt;interval touching zero and &lt;code&gt;p = 0.146&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;treatment accuracy only 49.0%;&lt;/li&gt;
&lt;li&gt;one model family and one benchmark;&lt;/li&gt;
&lt;li&gt;one correct answer with inconsistent reasoning;&lt;/li&gt;
&lt;li&gt;no independent semantic replication across backends.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next authorized gate is not broad schema expansion. It is either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the same paired contract test on a second model family; or&lt;/li&gt;
&lt;li&gt;an executable tool-call task where contract-valid correctness has direct practical
value.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If neither preserves a useful positive effect, the project should narrow into a&lt;br&gt;
schema-risk linter and measurement system rather than continue claiming a general&lt;br&gt;
compiler direction.&lt;/p&gt;
&lt;h2&gt;
  
  
  Reproduce the evidence
&lt;/h2&gt;

&lt;p&gt;The corrected evidence lives under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;experiments/corrected-replication/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main entry points are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python scripts/validate_corrected_replication.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--run-dir&lt;/span&gt; experiments/corrected-replication/results/qwen2.5-7b-corrected/results/corrected-replication &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dataset&lt;/span&gt; data/gsm8k_50_seed0.jsonl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source-root&lt;/span&gt; deployment/kaggle/corrected-replication/source-snapshot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--kernel-source&lt;/span&gt; deployment/kaggle/corrected-replication/run_kaggle.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--out&lt;/span&gt; /tmp/corrected-artifact-validation.json

python scripts/build_corrected_replication_figures.py

python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the exact artifacts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/master/experiments/corrected-replication/protocol.md" rel="noopener noreferrer"&gt;Frozen protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/master/experiments/corrected-replication/results/qwen2.5-7b-corrected/artifact-validation.json" rel="noopener noreferrer"&gt;Independent validation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/master/experiments/corrected-replication/results/qwen2.5-7b-corrected/paired-summary-exact.md" rel="noopener noreferrer"&gt;Exact paired summary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/master/experiments/corrected-replication/results/qwen2.5-7b-corrected/decision-report.md" rel="noopener noreferrer"&gt;Decision report&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am treating the correction as part of the result, not as an embarrassing footnote.&lt;br&gt;
The point of an evidence pipeline is not to protect a claim. It is to make the claim&lt;br&gt;
survive contact with its own implementation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Constraints Cost 18 Points. Compiling the Schema Recovered 14.</title>
      <dc:creator>Vaibhav Mittal</dc:creator>
      <pubDate>Wed, 05 Aug 2026 13:32:04 +0000</pubDate>
      <link>https://dev.to/vaibhav_mittal_ac22a2c5d6/constraints-cost-18-points-compiling-the-schema-recovered-14-1f72</link>
      <guid>https://dev.to/vaibhav_mittal_ac22a2c5d6/constraints-cost-18-points-compiling-the-schema-recovered-14-1f72</guid>
      <description>&lt;blockquote&gt;
&lt;h2&gt;
  
  
  Update, 8 Aug 2026
&lt;/h2&gt;

&lt;p&gt;This Qwen result remains valid for its frozen setup, but it did not generalize.&lt;br&gt;
A fresh Llama replication was negative, and an external review later found that&lt;br&gt;
the first Llama string control accepted a broader numeric language than the safe&lt;br&gt;
compiler transform. I preregistered an exact canonical correction. The canonical&lt;br&gt;
string control still scored 92/150 versus 82/150 for the frozen integer treatment,&lt;br&gt;
with a paired interval of [-12.7, -0.7] points. The default optimizer thesis is&lt;br&gt;
closed. See the &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/exp/canonical-schema-correction/experiments/canonical-schema-equivalence-correction/decision-report.md" rel="noopener noreferrer"&gt;complete correction decision&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My previous experiment ended with an uncomfortable result.&lt;/p&gt;

&lt;p&gt;On the same 49 audited GSM8K questions, Qwen2.5-7B-Instruct answered 39 correctly&lt;br&gt;
when prompted to produce JSON. When I enforced the declared schema with Outlines or&lt;br&gt;
XGrammar, each backend answered only 30 correctly.&lt;/p&gt;

&lt;p&gt;The constraints fixed compliance and cost 18.4 percentage points of recoverable&lt;br&gt;
mathematical accuracy.&lt;/p&gt;

&lt;p&gt;That result raised a more useful question than "are constraints bad?"&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Was the model failing because it was constrained, or because the external contract&lt;br&gt;
forced it through a poorly aligned lexical representation?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have now completed the follow-up: 72 targeted and 150 full-confirmation generations&lt;br&gt;
on Qwen2.5-7B, plus local integration probes, boundary traces, paired statistics, and&lt;br&gt;
independent artifact validation.&lt;/p&gt;

&lt;p&gt;The short result is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Replacing a model-facing signed numeric string with a native JSON integer, then&lt;br&gt;
deterministically restoring the original string contract, improved both constrained&lt;br&gt;
backends from 30/49 to 37/49 contract-valid correct.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a 14.3 percentage-point recovery while retaining 100% final external-schema&lt;br&gt;
validity.&lt;/p&gt;

&lt;p&gt;This article explains what changed, what stayed frozen, what the trace showed, and&lt;br&gt;
why this is a Green light for a small contract-alignment compiler rather than proof of&lt;br&gt;
a universal solution.&lt;/p&gt;

&lt;p&gt;The first study is here:&lt;br&gt;
&lt;a href="https://dev.to/vaibhav_mittal_ac22a2c5d6/structured-output-fixed-my-json-and-cut-math-accuracy-by-18-points-jm5"&gt;Structured Output Fixed My JSON and Cut Math Accuracy by 18 Points&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;All new code, raw rows, manifests, hashes, traces, and reports are in the repository.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Vaibhav701161" rel="noopener noreferrer"&gt;
        Vaibhav701161
      &lt;/a&gt; / &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab" rel="noopener noreferrer"&gt;
        constrained-decoding-lab
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Constrained Decoding Under Matched Conditions&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A controlled, artifact-validated study of how JSON prompting, grammar-constrained
decoding, and output-field order affect mathematical accuracy and schema compliance.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#principal-results" rel="noopener noreferrer"&gt;Results&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#contract-aligned-internal-representation" rel="noopener noreferrer"&gt;Alignment result&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#corrected-7b-replication" rel="noopener noreferrer"&gt;Corrected replication&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#item-level-and-mechanism-evidence" rel="noopener noreferrer"&gt;Paired evidence&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#study-design" rel="noopener noreferrer"&gt;Study design&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#reproduce-the-evaluation" rel="noopener noreferrer"&gt;Reproduction&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#evidence-map" rel="noopener noreferrer"&gt;Evidence&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#public-kaggle-artifacts" rel="noopener noreferrer"&gt;Public Kaggle artifacts&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#progress-presentation" rel="noopener noreferrer"&gt;Progress presentation&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#technical-articles" rel="noopener noreferrer"&gt;Technical articles&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#scope-and-limitations" rel="noopener noreferrer"&gt;Limitations&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Progress presentation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Watch this presentation for an overview of the project's progress, key findings
and results to date.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://youtu.be/82-3grLsO2M?si=e37WsDqnmG9DbTYY" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a9de11ac5ed9b6e42d3486ebc43586b031ba29c656f455bf630a9b3664637de8/68747470733a2f2f696d672e796f75747562652e636f6d2f76692f38322d3367724c734f324d2f6d617872657364656661756c742e6a7067" alt="Watch the constrained decoding project progress presentation"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://youtu.be/82-3grLsO2M?si=e37WsDqnmG9DbTYY" rel="nofollow noopener noreferrer"&gt;Watch on YouTube&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Central result&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Constrained decoding solved the formatting problem, but it did not preserve all of
the model's recoverable mathematical accuracy. On Qwen2.5-7B, prompt-only JSON
achieved 79.6% recoverable accuracy and 0% schema compliance. Outlines and XGrammar
each achieved 61.2% recoverable accuracy and 100% schema compliance. The paired
semantic effect was -18.4 percentage points for both backends (exact McNemar
&lt;code&gt;p = 0.003906&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This is not a claim that constrained decoding is universally harmful. It is a
controlled…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Vaibhav701161/constrained-decoding-lab" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  The failure was more specific than "constraints hurt reasoning"
&lt;/h2&gt;

&lt;p&gt;The external schema required this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"18000"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model naturally preferred this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prompt-only output was valid JSON and often mathematically correct, but it did&lt;br&gt;
not satisfy the contract because &lt;code&gt;answer&lt;/code&gt; was a JSON number rather than a string.&lt;/p&gt;

&lt;p&gt;Hard constraints solved that type mismatch. They also changed the generated answer.&lt;/p&gt;

&lt;p&gt;In the frozen reasoning-first baseline:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Semantic correctness&lt;/th&gt;
&lt;th&gt;External-schema validity&lt;/th&gt;
&lt;th&gt;Negative answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prompt-only JSON&lt;/td&gt;
&lt;td&gt;39/49 (79.6%)&lt;/td&gt;
&lt;td&gt;0/49&lt;/td&gt;
&lt;td&gt;1/49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outlines signed string&lt;/td&gt;
&lt;td&gt;30/49 (61.2%)&lt;/td&gt;
&lt;td&gt;49/49&lt;/td&gt;
&lt;td&gt;12/49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XGrammar signed string&lt;/td&gt;
&lt;td&gt;30/49 (61.2%)&lt;/td&gt;
&lt;td&gt;49/49&lt;/td&gt;
&lt;td&gt;12/49&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both constrained backends had the same paired transition against prompting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;correct in both:                  30
correct only with prompting:       9
correct only with constraints:     0
wrong in both:                    10
exact paired p:             0.003906
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eight of the nine losses were shared across Outlines and XGrammar. In seven shared&lt;br&gt;
cases, the reasoning contained the correct positive magnitude and the final answer&lt;br&gt;
field emitted its negative.&lt;/p&gt;

&lt;p&gt;For example, the reasoning concluded &lt;code&gt;18000&lt;/code&gt;, then the constrained answer became&lt;br&gt;
&lt;code&gt;-18000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That pattern suggested a representation problem at the answer boundary, not a reason&lt;br&gt;
to build another grammar engine.&lt;/p&gt;
&lt;h2&gt;
  
  
  The hypothesis: compile the contract before generation
&lt;/h2&gt;

&lt;p&gt;The caller's external contract remains authoritative. But the model does not&lt;br&gt;
necessarily need to generate that exact wire representation directly.&lt;/p&gt;

&lt;p&gt;The intervention was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;External contract
{"reasoning": "...", "answer": "18000"}

        compile
           ↓

Internal model-facing contract
{"reasoning": "...", "answer": 18000}

        constrained generation
           ↓

Deterministic transducer
integer 18000 -&amp;gt; canonical string "18000"

        external validation
           ↓

Returned contract
{"reasoning": "...", "answer": "18000"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7o7ottdcdyermzep7kxv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7o7ottdcdyermzep7kxv.png" alt="Engineering diagram of the implemented external contract, safe compiler, model-facing schema, constrained generation, and deterministic validation boundary" width="799" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This design has four important properties:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It uses one model call.&lt;/li&gt;
&lt;li&gt;It does not relax the external schema.&lt;/li&gt;
&lt;li&gt;It does not use an LLM to repair another LLM's output.&lt;/li&gt;
&lt;li&gt;It fails closed on ambiguous or unsupported values.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The core transducer is deliberately boring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;canonical_integer_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;internal answer must be a JSON integer and not a boolean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transduce_integer_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;internal object must contain exactly reasoning and answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;internal reasoning must be a string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;internal_value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;canonical_integer_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After conversion, the rebuilt object is validated against the original signed&lt;br&gt;
numeric-string schema. If parsing, typing, transformation, or external validation&lt;br&gt;
fails, no external object is returned.&lt;/p&gt;

&lt;p&gt;This is not post-hoc answer correction. The sign and magnitude generated by the&lt;br&gt;
model are preserved exactly.&lt;/p&gt;
&lt;h2&gt;
  
  
  I wrote the decision rule before running the new matrix
&lt;/h2&gt;

&lt;p&gt;The gate question was frozen before launching the 7B intervention:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does a native JSON integer as the hard-constrained, model-facing answer&lt;br&gt;
representation recover the semantic losses observed with a signed numeric string,&lt;br&gt;
while deterministic stringification restores validity under the original external&lt;br&gt;
contract?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Green criteria were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At least five percentage points of recovery over the matching constrained
signed-string baseline.&lt;/li&gt;
&lt;li&gt;A majority of the shared sign-loss cases repaired.&lt;/li&gt;
&lt;li&gt;100% final external validity after transduction.&lt;/li&gt;
&lt;li&gt;No new systematic semantic failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model, dataset, item order, chat template, greedy decoding, seed, FP32 precision,&lt;br&gt;
256-token cap, and backend versions remained frozen.&lt;/p&gt;

&lt;p&gt;Relative to the previous prompt, the integer prompt changed only the symbolic answer&lt;br&gt;
representation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signed-string template: "answer": "&amp;lt;final numeric answer&amp;gt;"
integer template:       "answer": &amp;lt;integer&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A regression test compares the new prompt to the first accepted baseline row and&lt;br&gt;
asserts that this replacement is the only change.&lt;/p&gt;
&lt;h2&gt;
  
  
  Layered scoring prevented a misleading result
&lt;/h2&gt;

&lt;p&gt;The experiment reports four separate outcomes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Semantic correctness&lt;/strong&gt;: does the extracted numeric value match the gold answer?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal validity&lt;/strong&gt;: did the model satisfy the model-facing integer schema?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External validity&lt;/strong&gt;: did deterministic transduction produce an object satisfying
the unchanged caller schema?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract-valid correctness&lt;/strong&gt;: is the answer both semantically correct and
externally valid?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This separation matters. A system that emits perfect JSON with the wrong number is&lt;br&gt;
not successful. A mathematically correct response with the wrong API type is not&lt;br&gt;
immediately usable either.&lt;/p&gt;

&lt;p&gt;The primary product metric was contract-valid correctness after transduction.&lt;/p&gt;
&lt;h2&gt;
  
  
  First gate: an 18-item targeted suite
&lt;/h2&gt;

&lt;p&gt;I mechanically derived the targeted suite from the frozen baseline artifacts. It&lt;br&gt;
contained:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The union of all constrained semantic losses.&lt;/li&gt;
&lt;li&gt;Matched controls where prompting, Outlines, and XGrammar were all correct.&lt;/li&gt;
&lt;li&gt;Shared difficult cases where all conditions were wrong.&lt;/li&gt;
&lt;li&gt;A recorded inclusion reason for every item.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The manifest recorded 9 Outlines losses, 9 XGrammar losses, 8 shared losses, and 10&lt;br&gt;
unique losses across the two backends. The resulting suite had 18 items.&lt;/p&gt;

&lt;p&gt;I ran four conditions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prompt-only integer.&lt;/li&gt;
&lt;li&gt;Outlines integer.&lt;/li&gt;
&lt;li&gt;XGrammar integer.&lt;/li&gt;
&lt;li&gt;XGrammar unsigned numeric string, explicitly labeled diagnostic.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The unsigned-string condition was useful because every gold answer in this subset&lt;br&gt;
was positive. But it was not contract-equivalent: the original external schema permits&lt;br&gt;
negative integers, so an unsigned internal language cannot be the general solution.&lt;/p&gt;
&lt;h3&gt;
  
  
  Targeted result
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Semantic correct&lt;/th&gt;
&lt;th&gt;External valid&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prompted integer&lt;/td&gt;
&lt;td&gt;13/18&lt;/td&gt;
&lt;td&gt;18/18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outlines integer&lt;/td&gt;
&lt;td&gt;13/18&lt;/td&gt;
&lt;td&gt;18/18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XGrammar integer&lt;/td&gt;
&lt;td&gt;13/18&lt;/td&gt;
&lt;td&gt;18/18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XGrammar unsigned-string diagnostic&lt;/td&gt;
&lt;td&gt;13/18&lt;/td&gt;
&lt;td&gt;18/18&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The aggregate tie hid useful paired information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Outlines integer repaired 7/8 shared signed-string losses.&lt;/li&gt;
&lt;li&gt;XGrammar integer repaired 8/8.&lt;/li&gt;
&lt;li&gt;Prompted integer, XGrammar integer, and XGrammar unsigned string had the same
item-level correctness set.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result cleared the preregistered threshold to advance. It did not count as final&lt;br&gt;
confirmation because the suite was deliberately enriched for known failures.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the answer-boundary trace showed
&lt;/h2&gt;

&lt;p&gt;I wrapped the XGrammar Hugging Face logits processor and captured compact diagnostics&lt;br&gt;
near the answer boundary. The trace records top pre-mask and post-mask candidates,&lt;br&gt;
selected tokens, known sign and digit scores, and the number of masked vocabulary&lt;br&gt;
entries. It does not store a full-vocabulary tensor.&lt;/p&gt;

&lt;p&gt;Three items were traced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gsm8k_test_173&lt;/code&gt;, a shared sign flip.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gsm8k_test_1216&lt;/code&gt;, a backend-difference case.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gsm8k_test_12&lt;/code&gt;, a matched control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;code&gt;gsm8k_test_173&lt;/code&gt;, the generated reasoning ended with the correct value &lt;code&gt;18000&lt;/code&gt;.&lt;br&gt;
At the integer answer boundary after whitespace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;token "1" pre-mask score:  39.63
token "-" pre-mask score:  -1.33
selected token:             "1"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The integer grammar still permits legitimate negative values. The intervention did&lt;br&gt;
not simply ban the minus sign. Instead, the changed representation exposed a token&lt;br&gt;
path on which the digit was overwhelmingly preferred.&lt;/p&gt;

&lt;p&gt;That is consistent with the hypothesis, but three traces are diagnostic evidence,&lt;br&gt;
not a universal causal proof.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full confirmation: 150 new 7B generations
&lt;/h2&gt;

&lt;p&gt;After the targeted gate passed, I ran the three integer conditions on the complete&lt;br&gt;
frozen 50-item subset:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;50 prompt-only integer generations.&lt;/li&gt;
&lt;li&gt;50 Outlines integer generations.&lt;/li&gt;
&lt;li&gt;50 XGrammar integer generations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One contradictory GSM8K reference remained in every raw artifact and was excluded&lt;br&gt;
only from the predeclared cleaned analysis. No other row was removed.&lt;/p&gt;

&lt;p&gt;Independent validation accepted all 150 rows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;50 unique item IDs in every condition.&lt;/li&gt;
&lt;li&gt;Expected dataset, runner, schema, and source hashes.&lt;/li&gt;
&lt;li&gt;No generation errors.&lt;/li&gt;
&lt;li&gt;No token-cap hits.&lt;/li&gt;
&lt;li&gt;100% final external validity.&lt;/li&gt;
&lt;li&gt;Complete trace coverage.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The complete result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Semantic correct&lt;/th&gt;
&lt;th&gt;Contract-valid correct&lt;/th&gt;
&lt;th&gt;External valid&lt;/th&gt;
&lt;th&gt;Negative answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prompted signed-string baseline&lt;/td&gt;
&lt;td&gt;39/49 (79.6%)&lt;/td&gt;
&lt;td&gt;0/49&lt;/td&gt;
&lt;td&gt;0/49&lt;/td&gt;
&lt;td&gt;1/49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompted integer + transducer&lt;/td&gt;
&lt;td&gt;37/49 (75.5%)&lt;/td&gt;
&lt;td&gt;37/49&lt;/td&gt;
&lt;td&gt;49/49&lt;/td&gt;
&lt;td&gt;0/49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outlines signed-string baseline&lt;/td&gt;
&lt;td&gt;30/49 (61.2%)&lt;/td&gt;
&lt;td&gt;30/49&lt;/td&gt;
&lt;td&gt;49/49&lt;/td&gt;
&lt;td&gt;12/49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outlines integer + transducer&lt;/td&gt;
&lt;td&gt;37/49 (75.5%)&lt;/td&gt;
&lt;td&gt;37/49&lt;/td&gt;
&lt;td&gt;49/49&lt;/td&gt;
&lt;td&gt;0/49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XGrammar signed-string baseline&lt;/td&gt;
&lt;td&gt;30/49 (61.2%)&lt;/td&gt;
&lt;td&gt;30/49&lt;/td&gt;
&lt;td&gt;49/49&lt;/td&gt;
&lt;td&gt;12/49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XGrammar integer + transducer&lt;/td&gt;
&lt;td&gt;37/49 (75.5%)&lt;/td&gt;
&lt;td&gt;37/49&lt;/td&gt;
&lt;td&gt;49/49&lt;/td&gt;
&lt;td&gt;0/49&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgydoq384h51pbx0s9krn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgydoq384h51pbx0s9krn.png" alt="Data-derived diagram showing the 14.3-point recovery for Outlines and XGrammar, paired repairs and regressions, and removal of 12 negative answers per backend" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The architecture figure is generated from the implemented path. The recovery figure&lt;br&gt;
is generated directly from the accepted &lt;code&gt;paired-summary.json&lt;/code&gt;; its counts and rates&lt;br&gt;
are not manually typed into the image.&lt;/p&gt;

&lt;p&gt;Both constrained backends gained 7 net correct answers, or 14.3 percentage points,&lt;br&gt;
while preserving 100% external validity.&lt;/p&gt;

&lt;p&gt;The original constrained gap was 18.4 points. The intervention recovered about 78%&lt;br&gt;
of that gap.&lt;/p&gt;

&lt;p&gt;The negative-answer cluster also disappeared in this matrix. Both constrained&lt;br&gt;
backends moved from 12/49 negative answers to 0/49.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paired effects, including the new failures
&lt;/h2&gt;

&lt;p&gt;Aggregate improvement is not enough. A treatment can repair some items and silently&lt;br&gt;
break others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Outlines
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signed-string correct:                 30/49
integer + transducer correct:          37/49
difference:                      +14.3 points
paired 95% interval:          [+4.1, +26.5]
newly correct / newly wrong:            8 / 1
two-sided exact paired p:              0.0391
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  XGrammar
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signed-string correct:                 30/49
integer + transducer correct:          37/49
difference:                      +14.3 points
paired 95% interval:           [0.0, +28.6]
newly correct / newly wrong:           10 / 3
two-sided exact paired p:              0.0923
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outlines clears the conventional 0.05 threshold. XGrammar has the same point&lt;br&gt;
estimate, but more discordant items, a wider interval touching zero, and a p-value&lt;br&gt;
above 0.05.&lt;/p&gt;

&lt;p&gt;The project threshold was not defined as "obtain p &amp;lt; 0.05 on every backend." It was&lt;br&gt;
defined as meaningful recovery, majority repair, perfect final validity, and no&lt;br&gt;
systematic replacement failure. Both backends passed that rule, but the uncertainty&lt;br&gt;
around XGrammar belongs in the conclusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The incremental constraint cost disappeared under the integer representation
&lt;/h2&gt;

&lt;p&gt;The cleanest comparison is not integer-constrained versus signed-string prompted.&lt;br&gt;
Those conditions use different representations.&lt;/p&gt;

&lt;p&gt;The matched comparison is integer-constrained versus integer-prompted.&lt;/p&gt;

&lt;p&gt;All three integer conditions scored 37/49.&lt;/p&gt;

&lt;p&gt;At the item level:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Outlines versus prompted integer had 3 treatment-only and 3 control-only correct
items, for zero net difference.&lt;/li&gt;
&lt;li&gt;XGrammar and prompted integer had the same correctness vector: 0 treatment-only
and 0 control-only items.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, in this experiment, I no longer observed an aggregate semantic tax from applying&lt;br&gt;
the grammar after the representation was aligned.&lt;/p&gt;

&lt;p&gt;That is the most important engineering result of the follow-up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did not work perfectly
&lt;/h2&gt;

&lt;p&gt;The intervention did not restore every answer.&lt;/p&gt;

&lt;p&gt;Prompted signed-string generation had 39/49 recoverable semantic correctness, while&lt;br&gt;
all integer conditions had 37/49. The prompted representation difference was -4.1&lt;br&gt;
points with a paired interval from -12.2 to +4.1 and exact &lt;code&gt;p = 0.625&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The aligned systems also retained ordinary reasoning errors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;12/49 items were still wrong.&lt;/li&gt;
&lt;li&gt;Outlines repaired 8 baseline errors and newly missed 1.&lt;/li&gt;
&lt;li&gt;XGrammar repaired 10 and newly missed 3.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Representation alignment fixes a representation-associated failure. It does not&lt;br&gt;
turn a 7B model into a perfect arithmetic solver.&lt;/p&gt;

&lt;h2&gt;
  
  
  An integration bug the local smoke test caught
&lt;/h2&gt;

&lt;p&gt;The local Qwen2.5-0.5B smoke test exposed a separate XGrammar issue in my runner.&lt;/p&gt;

&lt;p&gt;I initially reused one stateful Hugging Face logits processor across multiple&lt;br&gt;
generations. The first item passed and later items failed with assertions. The fix was&lt;br&gt;
to build a fresh processor for every generation.&lt;/p&gt;

&lt;p&gt;The failed attempt is preserved as a diagnostic. The corrected five-item XGrammar&lt;br&gt;
smoke passed 5/5 external validity, and the accepted cloud artifacts contain no such&lt;br&gt;
errors.&lt;/p&gt;

&lt;p&gt;This was a useful reminder: a structured-generation benchmark can be invalidated by&lt;br&gt;
state management even when its schema and scoring logic are correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this result proves
&lt;/h2&gt;

&lt;p&gt;Within the declared setup, the evidence supports these statements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The model-facing answer representation was materially associated with the
constrained semantic loss.&lt;/li&gt;
&lt;li&gt;A native integer plus deterministic external transduction recovered 14.3 points
for both tested backends.&lt;/li&gt;
&lt;li&gt;The original external signed-string contract remained 100% valid.&lt;/li&gt;
&lt;li&gt;The observed negative-answer cluster disappeared.&lt;/li&gt;
&lt;li&gt;Under the integer representation, neither backend had an aggregate accuracy loss
relative to matched integer prompting.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What it does not prove
&lt;/h2&gt;

&lt;p&gt;The evidence does not establish that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native integers improve every model or schema.&lt;/li&gt;
&lt;li&gt;Every constrained-decoding failure is a representation problem.&lt;/li&gt;
&lt;li&gt;The mechanism generalizes beyond Qwen2.5-7B, GSM8K, greedy FP32 decoding, and the
tested library versions.&lt;/li&gt;
&lt;li&gt;The result holds for legitimate negative gold answers. The transducer is tested on
negatives, but this GSM8K subset contains positive targets.&lt;/li&gt;
&lt;li&gt;The 49-item confirmation is an independent replication. The intervention was
frozen before the new runs, but the underlying evaluation set also produced the
original failure observation.&lt;/li&gt;
&lt;li&gt;The complete contract compiler already exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The XGrammar confidence interval touching zero is another reason to keep the claim&lt;br&gt;
narrow even though its point recovery matches Outlines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The project decision
&lt;/h2&gt;

&lt;p&gt;The gate is Green.&lt;/p&gt;

&lt;p&gt;The result justifies building a bounded compiler layer with a small, auditable set of&lt;br&gt;
safe transformations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Canonical integer-string representation.&lt;/li&gt;
&lt;li&gt;Field-order restoration.&lt;/li&gt;
&lt;li&gt;Key aliases with exact inverse mapping.&lt;/li&gt;
&lt;li&gt;Canonical whitespace policies.&lt;/li&gt;
&lt;li&gt;Final validation against the unchanged external contract.&lt;/li&gt;
&lt;li&gt;Explicit refusal when a transformation is ambiguous or unsupported.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The compiler should sit above existing engines such as Outlines and XGrammar. The&lt;br&gt;
goal is not to compete with their grammar execution. It is to choose a safer internal&lt;br&gt;
language for the model, prove that the transformation back is sound, and preserve the&lt;br&gt;
caller's contract.&lt;/p&gt;

&lt;p&gt;Before making a general product claim, the next evidence gates are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replication on an independent model family.&lt;/li&gt;
&lt;li&gt;An executable tool-call task.&lt;/li&gt;
&lt;li&gt;A fresh evaluation split.&lt;/li&gt;
&lt;li&gt;Property tests over the supported schema subset.&lt;/li&gt;
&lt;li&gt;Integration through a production inference path.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Inspect or reproduce the evidence
&lt;/h2&gt;

&lt;p&gt;The repository contains the protocol, failure catalogue, schema variants,&lt;br&gt;
transducer, tests, raw JSONL rows, manifests, compact traces, validation reports, and&lt;br&gt;
paired summary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab" rel="noopener noreferrer"&gt;Complete repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/master/docs/representation-alignment-results.md" rel="noopener noreferrer"&gt;Representation-alignment results&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/master/experiments/representation-alignment-gate/protocol.md" rel="noopener noreferrer"&gt;Frozen gate protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/master/experiments/representation-alignment-gate/results/cloud-full/artifact-validation.json" rel="noopener noreferrer"&gt;Full artifact validation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/master/experiments/representation-alignment-gate/results/cloud-full/paired-summary.json" rel="noopener noreferrer"&gt;Machine-readable paired summary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/master/experiments/representation-alignment-gate/results/cloud-full/results/representation-alignment-full/traces/xgrammar-integer-answer-boundary.jsonl" rel="noopener noreferrer"&gt;Compact XGrammar trace&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical lesson is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not assume the caller's wire format is the best language for the model.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes the safest way to preserve the external contract is to compile it into a&lt;br&gt;
different internal representation, generate there, and transform back with code that&lt;br&gt;
is deterministic enough to audit.&lt;/p&gt;

&lt;p&gt;In this experiment, that small change recovered most of the lost accuracy without&lt;br&gt;
weakening the guarantee.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Structured Output Fixed My JSON and Cut Math Accuracy by 18 Points</title>
      <dc:creator>Vaibhav Mittal</dc:creator>
      <pubDate>Tue, 04 Aug 2026 10:43:51 +0000</pubDate>
      <link>https://dev.to/vaibhav_mittal_ac22a2c5d6/structured-output-fixed-my-json-and-cut-math-accuracy-by-18-points-jm5</link>
      <guid>https://dev.to/vaibhav_mittal_ac22a2c5d6/structured-output-fixed-my-json-and-cut-math-accuracy-by-18-points-jm5</guid>
      <description>&lt;p&gt;I expected constrained decoding to change formatting.&lt;/p&gt;

&lt;p&gt;I did not expect it to change which math problems the model could solve.&lt;/p&gt;

&lt;p&gt;In a controlled Qwen2.5-7B experiment, both Outlines and XGrammar raised JSON Schema compliance from 0% to 100%. At the same time, recoverable mathematical accuracy fell from 79.6% to 61.2%.&lt;/p&gt;

&lt;p&gt;That is an 18.4 percentage-point drop on the same questions, with the same prompt text, model, precision, decoding settings, and scoring code.&lt;/p&gt;

&lt;p&gt;The paired result was even harder to dismiss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;30 questions were correct with both prompt-only and constrained generation.&lt;/li&gt;
&lt;li&gt;9 were correct with prompting but wrong with constrained generation.&lt;/li&gt;
&lt;li&gt;0 were rescued by constrained generation.&lt;/li&gt;
&lt;li&gt;10 were wrong in both conditions.&lt;/li&gt;
&lt;li&gt;Two-sided exact McNemar &lt;code&gt;p = 0.003906&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article explains how I reached that result, why "valid JSON" turned out to be an inadequate metric, and why moving one JSON field changed accuracy more than switching constraint backends.&lt;/p&gt;

&lt;p&gt;All source code, raw outputs, validation reports, and accepted cloud runs are public.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Vaibhav701161" rel="noopener noreferrer"&gt;
        Vaibhav701161
      &lt;/a&gt; / &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab" rel="noopener noreferrer"&gt;
        constrained-decoding-lab
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Constrained Decoding Under Matched Conditions&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A controlled, artifact-validated study of how JSON prompting, grammar-constrained
decoding, and output-field order affect mathematical accuracy and schema compliance.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#principal-results" rel="noopener noreferrer"&gt;Results&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#item-level-and-mechanism-evidence" rel="noopener noreferrer"&gt;Paired evidence&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#study-design" rel="noopener noreferrer"&gt;Study design&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#reproduce-the-evaluation" rel="noopener noreferrer"&gt;Reproduction&lt;/a&gt; | &lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#evidence-map" rel="noopener noreferrer"&gt;Evidence&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#public-kaggle-artifacts" rel="noopener noreferrer"&gt;Public Kaggle artifacts&lt;/a&gt; |
&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab#scope-and-limitations" rel="noopener noreferrer"&gt;Limitations&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Central result&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Constrained decoding solved the formatting problem, but it did not preserve all of
the model's recoverable mathematical accuracy. On Qwen2.5-7B, prompt-only JSON
achieved 79.6% recoverable accuracy and 0% schema compliance. Outlines and XGrammar
each achieved 61.2% recoverable accuracy and 100% schema compliance. The paired
semantic effect was -18.4 percentage points for both backends (exact McNemar
&lt;code&gt;p = 0.003906&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This is not a claim that constrained decoding is universally harmful. It is a
controlled reproduction showing that contract compliance and semantic correctness
are separate outcomes, and that a decoder can improve the first while reducing the
second under a specific, matched setup.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Vaibhav701161/constrained-decoding-lab/assets/figures/accuracy-compliance-tradeoff.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FVaibhav701161%2Fconstrained-decoding-lab%2FHEAD%2Fassets%2Ffigures%2Faccuracy-compliance-tradeoff.png" alt="Accuracy and contract compliance across the six Qwen2.5-7B conditions"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Principal results&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The primary 7B…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Vaibhav701161/constrained-decoding-lab" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  The engineering problem
&lt;/h2&gt;

&lt;p&gt;Production applications rarely want an essay from an LLM. They want something a program can consume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"42"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prompting a model to return JSON is not a guarantee. It may add prose, omit a field, use the wrong type, or stop halfway through an object.&lt;/p&gt;

&lt;p&gt;Constrained decoding addresses this at generation time. A grammar engine masks tokens that would make the output invalid, leaving the model only allowed continuations. Libraries such as &lt;a href="https://github.com/dottxt-ai/outlines" rel="noopener noreferrer"&gt;Outlines&lt;/a&gt; and &lt;a href="https://xgrammar.mlc.ai/" rel="noopener noreferrer"&gt;XGrammar&lt;/a&gt; make this practical.&lt;/p&gt;

&lt;p&gt;The obvious expectation is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Constrained decoding changes the syntax, not the answer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But token masking changes the model's available next-token distribution. If reasoning and serialization happen in the same sequence, syntax and semantics may not remain independent.&lt;/p&gt;

&lt;p&gt;That led to the question I wanted to test:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can a decoder guarantee the output contract without changing the model's recoverable mathematical accuracy?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What I measured
&lt;/h2&gt;

&lt;p&gt;I used a deterministic 50-question sample from the GSM8K test split and evaluated Qwen2.5-7B-Instruct under six conditions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Free response.&lt;/li&gt;
&lt;li&gt;Prompt-only JSON, reasoning first.&lt;/li&gt;
&lt;li&gt;Outlines JSON, reasoning first.&lt;/li&gt;
&lt;li&gt;XGrammar JSON, reasoning first.&lt;/li&gt;
&lt;li&gt;Prompt-only JSON, answer first.&lt;/li&gt;
&lt;li&gt;Outlines JSON, answer first.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The final matrix contains 300 validated generations.&lt;/p&gt;

&lt;p&gt;One sampled GSM8K row had a contradictory reference answer. The question implies &lt;code&gt;240&lt;/code&gt;, while its stored reference is &lt;code&gt;150&lt;/code&gt;. I retained that row in the raw results, documented it in a machine-readable audit, and excluded it from the predeclared clean analysis. No other row was excluded.&lt;/p&gt;

&lt;p&gt;The primary analysis therefore contains 49 paired questions per condition.&lt;/p&gt;

&lt;h3&gt;
  
  
  Controls held constant
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Qwen2.5-7B-Instruct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;Deterministic GSM8K test subset, seed 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dataset SHA-256&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3639f2f6def0f50e02086bc91e6f4a45567c85aa9b0f498224cb9421400d812a&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt formatting&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tokenizer.apply_chat_template(..., add_generation_prompt=True)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decoding&lt;/td&gt;
&lt;td&gt;Greedy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random seed&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maximum output&lt;/td&gt;
&lt;td&gt;256 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Precision&lt;/td&gt;
&lt;td&gt;FP32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constraint libraries&lt;/td&gt;
&lt;td&gt;Outlines 1.3.2 and XGrammar 0.2.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary sample&lt;/td&gt;
&lt;td&gt;49 audited paired items per condition&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a matched comparison, the dataset item, prompt text, chat template, model, precision, decoding configuration, and scoring logic were held fixed. Only the treatment being tested changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Valid JSON is not the same as a valid contract
&lt;/h2&gt;

&lt;p&gt;This distinction became central to the study.&lt;/p&gt;

&lt;p&gt;I reported two correctness metrics:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recoverable accuracy&lt;/strong&gt; asks whether the intended numeric answer can be extracted from the response, even if the response violates the schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strict accuracy&lt;/strong&gt; requires the correct value to appear inside a schema-compliant &lt;code&gt;answer&lt;/code&gt; field. This measures output that an application can use immediately without repair.&lt;/p&gt;

&lt;p&gt;Consider this output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"40 + 2 = 42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is valid JSON. It is also mathematically correct. But it violates a schema requiring &lt;code&gt;answer&lt;/code&gt; to be a numeric string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"40 + 2 = 42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"42"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prompt-only reasoning-first condition exposed this exact failure mode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100% valid JSON.&lt;/li&gt;
&lt;li&gt;79.6% recoverable mathematical accuracy.&lt;/li&gt;
&lt;li&gt;0% schema compliance.&lt;/li&gt;
&lt;li&gt;0% strict accuracy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model consistently emitted an unquoted JSON number instead of the required string.&lt;/p&gt;

&lt;p&gt;If I had measured only JSON parse success, I would have called this condition perfect. If I had measured only strict accuracy, I would have called every answer wrong. Both conclusions would have hidden important information.&lt;/p&gt;

&lt;h2&gt;
  
  
  The complete result
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxk3tk85cdsfdkg69h1no.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxk3tk85cdsfdkg69h1no.png" alt="Grouped horizontal bar chart comparing recoverable accuracy, strict accuracy, and schema compliance across six Qwen2.5-7B conditions" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Recoverable accuracy&lt;/th&gt;
&lt;th&gt;Strict accuracy&lt;/th&gt;
&lt;th&gt;Schema compliance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free response&lt;/td&gt;
&lt;td&gt;36/49 (73.5%)&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompted JSON, reasoning first&lt;/td&gt;
&lt;td&gt;39/49 (79.6%)&lt;/td&gt;
&lt;td&gt;0/49 (0.0%)&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outlines, reasoning first&lt;/td&gt;
&lt;td&gt;30/49 (61.2%)&lt;/td&gt;
&lt;td&gt;30/49 (61.2%)&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XGrammar, reasoning first&lt;/td&gt;
&lt;td&gt;30/49 (61.2%)&lt;/td&gt;
&lt;td&gt;30/49 (61.2%)&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompted JSON, answer first&lt;/td&gt;
&lt;td&gt;11/49 (22.4%)&lt;/td&gt;
&lt;td&gt;8/49 (16.3%)&lt;/td&gt;
&lt;td&gt;65.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outlines, answer first&lt;/td&gt;
&lt;td&gt;8/49 (16.3%)&lt;/td&gt;
&lt;td&gt;8/49 (16.3%)&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Constrained decoding clearly solved the output-contract problem. Every Outlines and XGrammar reasoning-first response satisfied the schema.&lt;/p&gt;

&lt;p&gt;But the recoverable view tells a second story. Relative to matched prompt-only JSON, Outlines and XGrammar each lost 18.4 percentage points of mathematical accuracy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fui43cim9akdff9s2gzsv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fui43cim9akdff9s2gzsv.png" alt="Forest plot of paired recoverable-accuracy effects with bootstrap confidence intervals" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each constraint backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;accuracy difference: -18.4 percentage points
paired bootstrap 95% CI: [-30.6, -8.2]
two-sided exact McNemar p: 0.003906
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the frozen strict metric, the interpretation reverses. Prompt-only JSON scored 0%, while both constrained backends scored 61.2%.&lt;/p&gt;

&lt;p&gt;So these statements are simultaneously true:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Constraints greatly improved immediately usable correctness.&lt;/li&gt;
&lt;li&gt;Constraints reduced recoverable mathematical correctness.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Schema compliance and semantic accuracy are separate outcomes. A structured-output evaluation should report both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aggregate accuracy was not enough
&lt;/h2&gt;

&lt;p&gt;Because every condition used the same 49 questions, I could inspect correctness transitions item by item.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7wuzub2wfvy4d8kibrm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7wuzub2wfvy4d8kibrm5.png" alt="Three paired correctness contingency tables comparing prompt-only JSON, Outlines, and XGrammar" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Against prompt-only reasoning-first generation, both constraint backends produced the same contingency table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Paired outcome&lt;/th&gt;
&lt;th&gt;Items&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Correct in both&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct only with prompting&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct only with the constraint&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrong in both&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is more informative than comparing &lt;code&gt;79.6%&lt;/code&gt; and &lt;code&gt;61.2%&lt;/code&gt; as independent proportions. The effect came from nine one-directional losses, not a balanced exchange of wins and losses.&lt;/p&gt;

&lt;p&gt;Outlines and XGrammar also tied at 30/49, but they were not behaviorally identical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;29 items were correct under both.&lt;/li&gt;
&lt;li&gt;One was correct only under Outlines.&lt;/li&gt;
&lt;li&gt;One was correct only under XGrammar.&lt;/li&gt;
&lt;li&gt;18 were wrong under both.&lt;/li&gt;
&lt;li&gt;Only 20/49 raw responses were byte-identical.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An aggregate tie can conceal different generation paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Field order mattered more than backend choice
&lt;/h2&gt;

&lt;p&gt;I initially treated JSON field order as a serialization detail. It was not.&lt;/p&gt;

&lt;p&gt;The reasoning-first schema required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The answer-first schema required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within each paired comparison, only that order changed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff9171cxdrr1lrt4a3t7a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff9171cxdrr1lrt4a3t7a.png" alt="Two-panel line plot showing accuracy and schema compliance under reasoning-first and answer-first field order" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For prompt-only JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reasoning first: 79.6% recoverable accuracy
answer first:    22.4% recoverable accuracy
paired change:  -57.1 percentage points
95% CI:         [-71.4, -40.8]
exact p:         5.77e-8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Outlines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reasoning first: 61.2% recoverable accuracy
answer first:    16.3% recoverable accuracy
paired change:  -44.9 percentage points
95% CI:         [-59.2, -30.6]
exact p:         2.98e-6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outlines maintained 100% schema compliance in both orders. Its 44.9-point decline therefore cannot be explained by an improvement in formatting.&lt;/p&gt;

&lt;p&gt;In this setup, asking the model to commit to the answer before generating its reasoning changed task performance far more than switching between Outlines and XGrammar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trusting the GPU output required its own investigation
&lt;/h2&gt;

&lt;p&gt;The final results ran on two Tesla T4 GPUs, but the first successful-looking runs were not necessarily trustworthy.&lt;/p&gt;

&lt;p&gt;During the environment and precision diagnostics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some 4-bit and FP16 paths produced visibly corrupted tokens.&lt;/li&gt;
&lt;li&gt;BF16 restored much of the structure but damaged digits in answers.&lt;/li&gt;
&lt;li&gt;FP32 produced the first outputs accepted as task evidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These observations are specific to the tested Kaggle software and T4 hardware path. They do not establish that FP16 or BF16 universally fail for Qwen.&lt;/p&gt;

&lt;p&gt;They do establish a broader engineering rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A completed GPU job is not automatically a valid experiment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before accepting a run, the validation pipeline checked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source-file hashes.&lt;/li&gt;
&lt;li&gt;Dataset hash and exact item order.&lt;/li&gt;
&lt;li&gt;Model artifact and environment.&lt;/li&gt;
&lt;li&gt;Prompt and schema version.&lt;/li&gt;
&lt;li&gt;Precision and decoding settings.&lt;/li&gt;
&lt;li&gt;Expected row counts.&lt;/li&gt;
&lt;li&gt;Duplicate item IDs.&lt;/li&gt;
&lt;li&gt;Generation errors.&lt;/li&gt;
&lt;li&gt;Token-cap hits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Failed, corrupted, stale-deployment, and diagnostic runs remain in the repository instead of disappearing from the history.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this result does not prove
&lt;/h2&gt;

&lt;p&gt;This is not evidence that constrained decoding always harms reasoning.&lt;/p&gt;

&lt;p&gt;The accepted evidence covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One deterministic GSM8K subset.&lt;/li&gt;
&lt;li&gt;One final prompt family.&lt;/li&gt;
&lt;li&gt;Two sizes from one model family.&lt;/li&gt;
&lt;li&gt;Greedy decoding.&lt;/li&gt;
&lt;li&gt;One primary precision.&lt;/li&gt;
&lt;li&gt;Two grammar backends.&lt;/li&gt;
&lt;li&gt;A sample of 49 audited paired questions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The smaller Qwen2.5-0.5B experiment did not detect the same semantic constraint cost at its low base accuracy. That alone warns against turning the 7B observation into a universal rule.&lt;/p&gt;

&lt;p&gt;The result also sits within existing research. &lt;a href="https://arxiv.org/abs/2501.10868" rel="noopener noreferrer"&gt;JSONSchemaBench&lt;/a&gt; argues that structured generation should be evaluated across compliance, coverage, efficiency, and output quality. The &lt;a href="https://arxiv.org/abs/2411.15100" rel="noopener noreferrer"&gt;XGrammar paper&lt;/a&gt; focuses on efficient grammar execution. &lt;a href="https://arxiv.org/abs/2502.09061" rel="noopener noreferrer"&gt;CRANE&lt;/a&gt; explicitly studies how restrictive grammars can diminish reasoning and proposes reasoning-augmented constrained generation.&lt;/p&gt;

&lt;p&gt;My contribution here is narrower: a reproducible engineering audit with matched prompt text, paired GSM8K scoring, two independent backends, field-order controls, strict versus recoverable metrics, and preserved failure evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The next experiment I would run
&lt;/h2&gt;

&lt;p&gt;The most useful follow-up is not another copy of the same matrix. It is a mechanism test.&lt;/p&gt;

&lt;p&gt;I would separate reasoning from serialization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;problem
  -&amp;gt; unconstrained reasoning and answer
  -&amp;gt; constrained final JSON serialization
  -&amp;gt; schema-valid application output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That creates three pipelines to compare:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prompt-only, single-stage generation.&lt;/li&gt;
&lt;li&gt;Grammar-constrained, single-stage generation.&lt;/li&gt;
&lt;li&gt;Unconstrained reasoning followed by constrained serialization.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the two-stage approach preserves prompt-only accuracy while achieving 100% compliance, it would provide a practical way to keep the contract guarantee without applying a grammar across the reasoning trajectory.&lt;/p&gt;

&lt;p&gt;After that, I would replicate on another model family, increase the preregistered sample, and add both a harder reasoning benchmark and a schema-centric benchmark.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce or inspect the evidence
&lt;/h2&gt;

&lt;p&gt;The repository contains the deterministic dataset, runners, raw JSONL outputs, summaries, item-level reports, figures, environment records, and validation manifests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab" rel="noopener noreferrer"&gt;Complete GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/master/docs/research-report.md" rel="noopener noreferrer"&gt;Research report&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/master/docs/methodology.md" rel="noopener noreferrer"&gt;Methodology&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab/blob/master/results/qwen2.5-7b/primary/combined/items.md" rel="noopener noreferrer"&gt;Per-item 7B evidence&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kaggle.com/code/vaibhav7011/constrained-decoding-qwen7b-evaluation?scriptVersionId=339899508" rel="noopener noreferrer"&gt;Reasoning-first Kaggle run&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kaggle.com/code/vaibhav7011/constrained-decoding-qwen7b-evaluation?scriptVersionId=339962138" rel="noopener noreferrer"&gt;Answer-first Kaggle run&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kaggle.com/datasets/vaibhav7011/constrained-decoding-day3-source" rel="noopener noreferrer"&gt;Frozen Kaggle source dataset&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core lesson is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not evaluate structured generation with one number.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Measure whether the output parses. Measure whether it satisfies the schema. Measure whether the answer remains correct. Then compare the same items, not just aggregate percentages.&lt;/p&gt;

&lt;p&gt;Constrained decoding can solve the contract problem. That does not make its semantic effect zero.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why “Return Valid JSON” Is Not a Decoding Constraint</title>
      <dc:creator>Vaibhav Mittal</dc:creator>
      <pubDate>Sat, 01 Aug 2026 17:33:53 +0000</pubDate>
      <link>https://dev.to/vaibhav_mittal_ac22a2c5d6/why-return-valid-json-is-not-a-decoding-constraint-2bl8</link>
      <guid>https://dev.to/vaibhav_mittal_ac22a2c5d6/why-return-valid-json-is-not-a-decoding-constraint-2bl8</guid>
      <description>&lt;p&gt;A model can know what valid JSON looks like and still produce a response that your parser rejects.&lt;/p&gt;

&lt;p&gt;For example, each of these responses contains a valid-looking JSON object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here is the result:

```json
{"answer": "72"}
```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"answer": "72"}

The calculation is 48 + 24.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"answer": "72"}
{"answer": "72"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A person can identify the answer immediately.&lt;/p&gt;

&lt;p&gt;A program expecting exactly one JSON value cannot.&lt;/p&gt;

&lt;p&gt;This is the distinction I wanted to understand more concretely:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Prompting changes which outputs are likely. Constrained decoding changes which outputs are allowed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I built a small local lab around Qwen2.5-0.5B-Instruct to inspect that distinction at the tokenizer, generation, validation, and logit-processing levels.&lt;/p&gt;

&lt;p&gt;The repository contains the scripts, raw generations, observations, and JSONL evaluation logs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab" rel="noopener noreferrer"&gt;github.com/Vaibhav701161/constrained-decoding-lab&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The experiment
&lt;/h2&gt;

&lt;p&gt;I compared two generation conditions on three simple grade-school math problems.&lt;/p&gt;

&lt;p&gt;The free-form prompt asked the model to show brief reasoning and end with a numeric answer.&lt;/p&gt;

&lt;p&gt;The structured prompt asked it to return exactly one JSON object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your reasoning here"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"final numeric answer here"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generation was deterministic, using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;do_sample&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For every example, the evaluation script recorded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the prompt and raw output&lt;/li&gt;
&lt;li&gt;the parsed representation&lt;/li&gt;
&lt;li&gt;JSON validity&lt;/li&gt;
&lt;li&gt;schema validity&lt;/li&gt;
&lt;li&gt;the extracted answer&lt;/li&gt;
&lt;li&gt;exact-match correctness&lt;/li&gt;
&lt;li&gt;prompt and generated token counts&lt;/li&gt;
&lt;li&gt;latency&lt;/li&gt;
&lt;li&gt;parsing or validation errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The initial smoke-test result was:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;th&gt;Valid JSON&lt;/th&gt;
&lt;th&gt;Correct&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free form&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;3/3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompted JSON&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0/3&lt;/td&gt;
&lt;td&gt;0/3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This should not be treated as a benchmark.&lt;/p&gt;

&lt;p&gt;Three hand-selected examples are far too few, and the extraction logic is intentionally simple.&lt;/p&gt;

&lt;p&gt;The failure modes were still useful.&lt;/p&gt;

&lt;p&gt;The JSON-prompted outputs included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Markdown-fenced JSON&lt;/li&gt;
&lt;li&gt;valid-looking JSON followed by prose&lt;/li&gt;
&lt;li&gt;multiple JSON objects in one response&lt;/li&gt;
&lt;li&gt;continued generation after a complete object&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In several cases, the answer was visibly present.&lt;/p&gt;

&lt;p&gt;The complete response still failed JSON parsing.&lt;/p&gt;

&lt;p&gt;That is the practical limitation of relying only on instructions such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Return ONLY valid JSON.
No markdown.
No extra text.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The instruction can make compliant output more probable.&lt;/p&gt;

&lt;p&gt;It does not remove non-compliant output from the model’s possible decoding paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation happens after the mistake
&lt;/h2&gt;

&lt;p&gt;A JSON parser can tell us that the output is invalid after generation.&lt;/p&gt;

&lt;p&gt;A schema validator can tell us that the parsed object contains the wrong keys or value types.&lt;/p&gt;

&lt;p&gt;Recovery logic may be able to extract the first complete object from a longer response.&lt;/p&gt;

&lt;p&gt;These are all useful techniques, but they solve a different problem.&lt;/p&gt;

&lt;p&gt;Post-generation validation asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did the model produce something acceptable?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Constrained decoding asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which tokens can the model legally produce next?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first detects failure.&lt;/p&gt;

&lt;p&gt;The second attempts to prevent structural failure.&lt;/p&gt;

&lt;p&gt;This does not mean constrained decoding automatically makes an answer correct.&lt;/p&gt;

&lt;p&gt;It only means the generated sequence is forced to remain inside a defined language.&lt;/p&gt;

&lt;p&gt;A perfectly valid JSON object can still contain the wrong answer.&lt;/p&gt;

&lt;p&gt;Structural validity and semantic correctness are separate evaluation dimensions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Touching the decoding loop
&lt;/h2&gt;

&lt;p&gt;To understand the underlying mechanism, I implemented a small Hugging Face &lt;code&gt;LogitsProcessor&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The processor scans the tokenizer vocabulary and records token IDs whose individually decoded text contains a digit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;token_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;piece&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;token_id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;skip_special_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;banned_token_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At every generation step, it modifies the scores of those tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;banned_token_ids&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After softmax, those tokens receive zero probability.&lt;/p&gt;

&lt;p&gt;The decoder cannot select them.&lt;/p&gt;

&lt;p&gt;This is not a grammar-constrained decoder.&lt;/p&gt;

&lt;p&gt;It is a static token mask used to observe the basic decoding-time operation directly.&lt;/p&gt;

&lt;p&gt;The result was more interesting than simply “the model stopped generating digits.”&lt;/p&gt;

&lt;p&gt;It avoided ordinary ASCII digits, but produced digit-like Unicode characters such as circled numbers.&lt;/p&gt;

&lt;p&gt;The formatting and output quality also degraded.&lt;/p&gt;

&lt;p&gt;That exposed two important problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  A constraint enforces its implementation, not its intention
&lt;/h2&gt;

&lt;p&gt;“Prevent the model from outputting a number” is an informal intention.&lt;/p&gt;

&lt;p&gt;“Set the logits of tokens whose individually decoded strings contain a digit to negative infinity” is an implementation.&lt;/p&gt;

&lt;p&gt;Those are not equivalent.&lt;/p&gt;

&lt;p&gt;A number can be represented through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ASCII digits&lt;/li&gt;
&lt;li&gt;other Unicode digit characters&lt;/li&gt;
&lt;li&gt;number words&lt;/li&gt;
&lt;li&gt;symbols that visually resemble numbers&lt;/li&gt;
&lt;li&gt;token combinations missed by a naive vocabulary scan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The decoder only guarantees the rule that has actually been encoded.&lt;/p&gt;

&lt;p&gt;This is especially important when people say that constrained decoding guarantees valid output.&lt;/p&gt;

&lt;p&gt;The guarantee is relative to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the grammar being enforced&lt;/li&gt;
&lt;li&gt;the tokenizer representation&lt;/li&gt;
&lt;li&gt;the parser state&lt;/li&gt;
&lt;li&gt;byte and Unicode handling&lt;/li&gt;
&lt;li&gt;the definition of an accepting state&lt;/li&gt;
&lt;li&gt;stopping behaviour&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A narrow or incorrect formalisation can provide a strong guarantee for the wrong language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hard constraints can damage output quality
&lt;/h2&gt;

&lt;p&gt;The original model distribution may place most of its probability mass on a small set of natural continuations.&lt;/p&gt;

&lt;p&gt;When those continuations are masked, the decoder must choose from what remains.&lt;/p&gt;

&lt;p&gt;The remaining token may be structurally legal but awkward, unlikely, or semantically poor.&lt;/p&gt;

&lt;p&gt;This is why evaluating constrained decoding only through a validity percentage is incomplete.&lt;/p&gt;

&lt;p&gt;A useful comparison should also measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;task accuracy&lt;/li&gt;
&lt;li&gt;latency overhead&lt;/li&gt;
&lt;li&gt;generated token count&lt;/li&gt;
&lt;li&gt;dead ends&lt;/li&gt;
&lt;li&gt;probability mass removed&lt;/li&gt;
&lt;li&gt;output quality&lt;/li&gt;
&lt;li&gt;recovery behaviour&lt;/li&gt;
&lt;li&gt;behaviour across different schemas and prompt styles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A system can produce 100% syntactically valid JSON and still be worse for the actual task.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real difficulty: grammars operate on bytes, models emit tokens
&lt;/h2&gt;

&lt;p&gt;Setting selected logits to negative infinity is straightforward.&lt;/p&gt;

&lt;p&gt;Determining which logits should be masked is the difficult part.&lt;/p&gt;

&lt;p&gt;A grammar is usually defined over characters or bytes.&lt;/p&gt;

&lt;p&gt;A language model emits tokens.&lt;/p&gt;

&lt;p&gt;One token can contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one character&lt;/li&gt;
&lt;li&gt;several characters&lt;/li&gt;
&lt;li&gt;whitespace and punctuation together&lt;/li&gt;
&lt;li&gt;a complete word&lt;/li&gt;
&lt;li&gt;part of a multibyte character&lt;/li&gt;
&lt;li&gt;a fragment whose behaviour depends on surrounding tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suppose the parser currently allows a number beginning with &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The valid vocabulary might contain tokens representing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0
0.
0.4
0.46
0}
0,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whether each token is legal depends on the grammar and the current parser state.&lt;/p&gt;

&lt;p&gt;It is not enough to ask whether a token equals the next valid character.&lt;/p&gt;

&lt;p&gt;The decoder has to simulate consuming the token’s entire byte sequence.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;current parser state
        ↓
candidate token bytes
        ↓
simulate every parser transition
        ↓
keep the token or mask it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A token should remain available only when its complete byte sequence can be consumed without making the grammar invalid.&lt;/p&gt;

&lt;p&gt;After one token is generated, the parser state changes.&lt;/p&gt;

&lt;p&gt;The valid vocabulary set must therefore be computed again.&lt;/p&gt;

&lt;p&gt;This makes a real constraint state-dependent.&lt;/p&gt;

&lt;p&gt;A static digit ban uses the same mask at every generation step.&lt;/p&gt;

&lt;p&gt;A JSON grammar needs a different mask after:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;than after:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"answer":
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"answer":"72"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The legal continuation depends on where the parser currently is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why checking every token naively is expensive
&lt;/h2&gt;

&lt;p&gt;A modern tokenizer may contain tens or hundreds of thousands of tokens.&lt;/p&gt;

&lt;p&gt;Testing every token against the parser at every generation step would add substantial overhead.&lt;/p&gt;

&lt;p&gt;This is where techniques such as token tries and cached parser transitions become useful.&lt;/p&gt;

&lt;p&gt;A token trie groups vocabulary tokens by shared byte prefixes.&lt;/p&gt;

&lt;p&gt;Instead of independently replaying every token through the parser, the decoder can traverse shared prefixes once and prune entire branches as soon as a prefix becomes invalid.&lt;/p&gt;

&lt;p&gt;The high-level loop becomes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the current parser state.&lt;/li&gt;
&lt;li&gt;Traverse possible token-byte prefixes.&lt;/li&gt;
&lt;li&gt;Prune branches that violate the grammar.&lt;/li&gt;
&lt;li&gt;Collect token IDs ending on viable branches.&lt;/li&gt;
&lt;li&gt;Mask the rest of the vocabulary.&lt;/li&gt;
&lt;li&gt;Generate one token.&lt;/li&gt;
&lt;li&gt;Advance the parser state.&lt;/li&gt;
&lt;li&gt;Repeat until an accepting state is reached.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The implementation becomes more complicated for recursive structures, strings, escaping, numbers, UTF-8, and ambiguous parser states.&lt;/p&gt;

&lt;p&gt;But the central mapping remains:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Parser state → legal token IDs&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What this experiment does not prove
&lt;/h2&gt;

&lt;p&gt;There are several limitations in the current setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  The dataset is tiny
&lt;/h3&gt;

&lt;p&gt;Three examples are enough for a smoke test, not for comparing decoding strategies.&lt;/p&gt;

&lt;h3&gt;
  
  
  The JSON extractor is deliberately strict and naive
&lt;/h3&gt;

&lt;p&gt;It takes the content between the first opening brace and the final closing brace.&lt;/p&gt;

&lt;p&gt;Multiple valid objects therefore become one invalid candidate.&lt;/p&gt;

&lt;p&gt;A stronger evaluation should report separate metrics for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whole-response validity&lt;/li&gt;
&lt;li&gt;first-object recoverability&lt;/li&gt;
&lt;li&gt;schema validity&lt;/li&gt;
&lt;li&gt;semantic correctness&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The free-form extractor is fragile
&lt;/h3&gt;

&lt;p&gt;It selects the final number in the output.&lt;/p&gt;

&lt;p&gt;Extra numeric text after the answer can produce an incorrect score.&lt;/p&gt;

&lt;h3&gt;
  
  
  The model was not prompted through its chat template
&lt;/h3&gt;

&lt;p&gt;The raw prompt was passed directly to an instruction-tuned model.&lt;/p&gt;

&lt;p&gt;Comparing this with the tokenizer’s intended chat template is necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  The token mask is not byte-accurate
&lt;/h3&gt;

&lt;p&gt;Individually decoding each vocabulary token is useful for inspection.&lt;/p&gt;

&lt;p&gt;A production implementation should reason carefully about the underlying token bytes and UTF-8 behaviour.&lt;/p&gt;

&lt;h3&gt;
  
  
  The constraint is static
&lt;/h3&gt;

&lt;p&gt;The digit processor demonstrates logit masking, not parser-driven constrained decoding.&lt;/p&gt;

&lt;p&gt;These limitations are not side notes.&lt;/p&gt;

&lt;p&gt;They define what conclusions can and cannot be drawn from the experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The next implementation step
&lt;/h2&gt;

&lt;p&gt;The next useful milestone is a small state-dependent decoder for a restricted output language, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"answer":"&amp;lt;digits&amp;gt;"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;define the language as an automaton or parser&lt;/li&gt;
&lt;li&gt;map tokenizer IDs to byte sequences&lt;/li&gt;
&lt;li&gt;track the current parser state&lt;/li&gt;
&lt;li&gt;simulate candidate token bytes&lt;/li&gt;
&lt;li&gt;allow only tokens that preserve validity&lt;/li&gt;
&lt;li&gt;stop only in an accepting state&lt;/li&gt;
&lt;li&gt;log mask size and latency at every step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once that works, it becomes possible to compare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prompting only&lt;/li&gt;
&lt;li&gt;parsing and repair&lt;/li&gt;
&lt;li&gt;regex or finite-state constraints&lt;/li&gt;
&lt;li&gt;JSON-schema constraints&lt;/li&gt;
&lt;li&gt;different token-filtering strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The comparison should keep the model, dataset, prompt content, and semantic scoring as controlled as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The useful distinction
&lt;/h2&gt;

&lt;p&gt;“Return only valid JSON” is still a useful instruction.&lt;/p&gt;

&lt;p&gt;It can reduce failures, improve readability, and make the model’s intended output clearer.&lt;/p&gt;

&lt;p&gt;But it is not a decoding constraint.&lt;/p&gt;

&lt;p&gt;Prompting asks the model to follow a structure.&lt;/p&gt;

&lt;p&gt;Validation checks whether it did.&lt;/p&gt;

&lt;p&gt;Constrained decoding removes continuations that violate the structure.&lt;/p&gt;

&lt;p&gt;These approaches can complement each other, but they do not provide the same guarantee.&lt;/p&gt;

&lt;p&gt;The code, raw outputs, and notes for the experiment are available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Vaibhav701161/constrained-decoding-lab" rel="noopener noreferrer"&gt;Constrained Decoding Lab&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>I Expected JSON Grammar Masks to Kill Sampling Diversity. The Prompt Got There First.</title>
      <dc:creator>Vaibhav Mittal</dc:creator>
      <pubDate>Thu, 30 Jul 2026 10:19:41 +0000</pubDate>
      <link>https://dev.to/vaibhav_mittal_ac22a2c5d6/i-expected-json-grammar-masks-to-kill-sampling-diversity-the-prompt-got-there-first-55fj</link>
      <guid>https://dev.to/vaibhav_mittal_ac22a2c5d6/i-expected-json-grammar-masks-to-kill-sampling-diversity-the-prompt-got-there-first-55fj</guid>
      <description>&lt;p&gt;I started this project with a fairly simple suspicion.&lt;/p&gt;

&lt;p&gt;When an LLM is forced to follow a JSON schema, the grammar removes most of&lt;br&gt;
the vocabulary at every step. If I keep using ordinary sampling settings such&lt;br&gt;
as &lt;code&gt;temperature=0.7&lt;/code&gt; and &lt;code&gt;top_p=0.9&lt;/code&gt;, is there enough distribution left for&lt;br&gt;
those settings to matter?&lt;/p&gt;

&lt;p&gt;I expected the grammar mask to make the sampler act almost greedily. After&lt;br&gt;
capturing the logits, checking the masks, and running 6,144 sampled outputs, I&lt;br&gt;
got a less convenient answer.&lt;/p&gt;

&lt;p&gt;For Qwen2.5-1.5B-Instruct, the sampler did collapse to one token very often,&lt;br&gt;
but the grammar mask was not causing it in my main run. The model had already&lt;br&gt;
made up its mind after seeing the full schema in the prompt.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A mask can remove 99% of token IDs while removing almost none of the&lt;br&gt;
model's probability mass.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence explained the first result. Then I withheld the schema from the&lt;br&gt;
prompt and ran the comparison again. Legal probability mass fell from 0.9996&lt;br&gt;
to 0.7962, and the mask started changing nuclei. The prompt had been doing&lt;br&gt;
more work than I had given it credit for.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The short version:&lt;/strong&gt; Qwen's grammar mask changed the nucleus size on 0 of&lt;br&gt;
427 measured free-choice steps when the schema was shown. In a matched&lt;br&gt;
follow-up with the schema withheld, it changed 120 of 556. TinyLlama's mask&lt;br&gt;
changed 38 of 504 in the original run.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  In this article
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Measuring collapse&lt;/li&gt;
&lt;li&gt;Results&lt;/li&gt;
&lt;li&gt;Whole-output diversity&lt;/li&gt;
&lt;li&gt;Order of operations&lt;/li&gt;
&lt;li&gt;Implementation notes&lt;/li&gt;
&lt;li&gt;What this changes in practice&lt;/li&gt;
&lt;li&gt;What I would claim now&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Measuring collapse
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What I counted
&lt;/h3&gt;

&lt;p&gt;Top-p sampling sorts tokens by probability and keeps the smallest prefix whose&lt;br&gt;
total probability reaches &lt;code&gt;p&lt;/code&gt;. Sampling then happens inside that prefix.&lt;/p&gt;

&lt;p&gt;Suppose the next-token probabilities are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;token A: 0.93
token B: 0.04
token C: 0.02
token D: 0.01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At &lt;code&gt;top_p=0.9&lt;/code&gt;, the nucleus contains only token A. The call is technically&lt;br&gt;
sampling, but there is nothing to sample between.&lt;/p&gt;

&lt;p&gt;I called that a collapsed step. More precisely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;collapse = nucleus size is 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is an important exception. If the grammar permits only one token, the&lt;br&gt;
step is forced by syntax. A closing brace or quote should not count as evidence&lt;br&gt;
that top-p removed diversity. I separated those steps before calculating any&lt;br&gt;
headline rate.&lt;/p&gt;

&lt;p&gt;The quantity I cared about was therefore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;collapsed free-choice steps
---------------------------
all free-choice steps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A free-choice step has at least two legal tokens before top-p is applied.&lt;/p&gt;

&lt;h3&gt;
  
  
  The same-logit comparison
&lt;/h3&gt;

&lt;p&gt;The normal generation path is not enough for this question. A constrained&lt;br&gt;
generation and a free generation can choose different tokens early, so their&lt;br&gt;
later logits no longer describe the same prefix.&lt;/p&gt;

&lt;p&gt;Instead, I captured one raw logit vector at each constrained-generation step&lt;br&gt;
and evaluated two distributions from it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the original model distribution&lt;/li&gt;
&lt;li&gt;the same distribution after applying the grammar mask and renormalizing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For each one I recomputed nucleus size across a grid of temperatures and top-p&lt;br&gt;
values. This gave me a same-logit counterfactual. I could ask whether the mask&lt;br&gt;
changed the nucleus without confusing that effect with a different generated&lt;br&gt;
trajectory.&lt;/p&gt;

&lt;p&gt;I also recorded the legal probability mass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Z_t = probability mass assigned to grammar-legal tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;Z_t&lt;/code&gt; is close to one, the model already wants to stay inside the grammar.&lt;br&gt;
When it is small, the mask has to move a lot of probability.&lt;/p&gt;

&lt;p&gt;The main run used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Qwen2.5-1.5B-Instruct&lt;/li&gt;
&lt;li&gt;TinyLlama-1.1B-Chat-v1.0&lt;/li&gt;
&lt;li&gt;six JSON schemas&lt;/li&gt;
&lt;li&gt;two prompts per schema&lt;/li&gt;
&lt;li&gt;temperatures from 0.0 to 1.5&lt;/li&gt;
&lt;li&gt;top-p values from 0.5 to 1.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The schemas covered a tight enum, an integer, a free string, a patterned&lt;br&gt;
string, a mixed object, and a mixed object with a reasoning string first.&lt;/p&gt;

&lt;p&gt;One prompt detail is essential: &lt;code&gt;_prompt_text&lt;/code&gt; appended the complete JSON&lt;br&gt;
schema to the user message. Qwen saw the field names, types, enum values, and&lt;br&gt;
required properties before I measured its legal probability mass. That setup&lt;br&gt;
is common, but it is not the only way structured-output systems are built.&lt;/p&gt;
&lt;h3&gt;
  
  
  Checking the mask
&lt;/h3&gt;

&lt;p&gt;Every result depends on the legal-token mask being right, so I started with a&lt;br&gt;
schema small enough to check by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wrote a ten-state byte-level recognizer for the compact form &lt;code&gt;{"a":12}&lt;/code&gt; and&lt;br&gt;
compared its allowed tokens with XGrammar at every prefix. I also compared&lt;br&gt;
XGrammar with Outlines.&lt;/p&gt;

&lt;p&gt;The first comparison failed by exactly 271 token IDs at every prefix. That&lt;br&gt;
number turned out to be the clue. Qwen's model head has 151,936 rows, while&lt;br&gt;
its tokenizer has 151,665 entries. My checker was treating the padded model&lt;br&gt;
rows as empty byte strings and accidentally allowing them as no-op tokens.&lt;/p&gt;

&lt;p&gt;After excluding those rows, the hand-written recognizer, XGrammar, and&lt;br&gt;
Outlines agreed at every checked prefix.&lt;/p&gt;

&lt;p&gt;TinyLlama exposed another problem. SentencePiece can encode an opening brace&lt;br&gt;
with a token whose grammar bytes include a leading space, even though the&lt;br&gt;
ordinary decoded string looks like the same brace. The original hand check&lt;br&gt;
was using the wrong beginning-of-sequence tokenization.&lt;/p&gt;

&lt;p&gt;That bug was worth finding early. Later, the same leading-space behavior&lt;br&gt;
created a misleading result when I compared generated token IDs with&lt;br&gt;
re-encoded token IDs. All twelve TinyLlama sequences changed their first token&lt;br&gt;
ID on the round trip, but the decoded bytes did not change at all. I now treat&lt;br&gt;
the ID change as a tokenizer diagnostic, not as a changed output.&lt;/p&gt;


&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Qwen: the two histograms are the same
&lt;/h3&gt;

&lt;p&gt;With the full schema in the prompt, at &lt;code&gt;T=0.7&lt;/code&gt; and &lt;code&gt;top_p=0.9&lt;/code&gt;, Qwen had 427&lt;br&gt;
free-choice steps across 12 constrained generations. The nucleus contained&lt;br&gt;
one token on 336 of those steps, a pooled rate of 78.7%.&lt;/p&gt;

&lt;p&gt;The step count is not an independent sample size. Steps inside one generation&lt;br&gt;
share a prompt, a schema, and a prefix. Giving each of the 12 generations&lt;br&gt;
equal weight, the mean collapse rate was 82.5%, with a 10.0 percentage-point&lt;br&gt;
standard deviation and a range from 70.0% to 100%.&lt;/p&gt;

&lt;p&gt;The surprising part was the counterfactual:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Qwen measurement&lt;/th&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pooled collapse rate&lt;/td&gt;
&lt;td&gt;78.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mean legal probability mass&lt;/td&gt;
&lt;td&gt;0.9984&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mask changed nucleus size&lt;/td&gt;
&lt;td&gt;0 of 427 steps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mask changed top token&lt;/td&gt;
&lt;td&gt;0 of 427 steps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The free and constrained histograms sit directly on top of each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frqjnkgzorq382g4gfu7y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frqjnkgzorq382g4gfu7y.png" alt="Overlapping free and constrained nucleus-size histograms for Qwen" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. Free and constrained nucleus sizes at &lt;code&gt;T=0.7&lt;/code&gt; and &lt;code&gt;top_p=0.9&lt;/code&gt;.&lt;br&gt;
The two distributions overlap.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Qwen had the schema in its prompt and was already assigning almost all of its&lt;br&gt;
probability to legal continuations. The mask removed vocabulary entries, but&lt;br&gt;
hardly any probability. Removing low-probability tokens did not change the&lt;br&gt;
top-p prefix.&lt;/p&gt;

&lt;p&gt;The wider grid behaved in the expected direction. Lower temperature and lower&lt;br&gt;
top-p produced more one-token nuclei. At &lt;code&gt;top_p=1.0&lt;/code&gt;, there is no truncation,&lt;br&gt;
so a free-choice step cannot count as collapsed under this definition.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fznt32jzyl4b35vgjfq51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fznt32jzyl4b35vgjfq51.png" alt="Qwen collapse rate across temperature and top-p" width="799" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2. Qwen collapse rate over the complete temperature and top-p grid.&lt;br&gt;
At &lt;code&gt;top_p=1.0&lt;/code&gt;, there is no nucleus truncation.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The prompt got there first
&lt;/h3&gt;

&lt;p&gt;I ran a paired follow-up on the mixed-object schema. It includes strings, an&lt;br&gt;
enum, an integer, a boolean, an array, and a nested object. I used one prompt&lt;br&gt;
that still made sense without the schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Return a plausible record for a failed API request.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model, grammar, sampler settings, and seeds 101 through 108 stayed fixed.&lt;br&gt;
The only change was whether the full schema was appended to the user message.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;measurement&lt;/th&gt;
&lt;th&gt;schema shown&lt;/th&gt;
&lt;th&gt;schema withheld&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;generations&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;prompt tokens&lt;/td&gt;
&lt;td&gt;306&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;free-choice steps&lt;/td&gt;
&lt;td&gt;543&lt;/td&gt;
&lt;td&gt;556&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mean generation &lt;code&gt;Z_t&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0.9996&lt;/td&gt;
&lt;td&gt;0.7962&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SD of generation &lt;code&gt;Z_t&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0.0001&lt;/td&gt;
&lt;td&gt;0.0173&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mask changed nucleus size&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;free counterfactual collapse&lt;/td&gt;
&lt;td&gt;82.9%&lt;/td&gt;
&lt;td&gt;59.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;constrained collapse&lt;/td&gt;
&lt;td&gt;82.9%&lt;/td&gt;
&lt;td&gt;72.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mask-added collapse&lt;/td&gt;
&lt;td&gt;0.0 points&lt;/td&gt;
&lt;td&gt;12.8 points&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyisj03xwdf7mjm926gay.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyisj03xwdf7mjm926gay.png" alt="Legal probability mass and collapse with the schema shown or withheld" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3. Eight matched seeds on the mixed-object schema. Points in the left&lt;br&gt;
panel are generation means; black markers show the mean and standard&lt;br&gt;
deviation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;All 16 constrained generations finished and parsed as JSON. Every matched seed&lt;br&gt;
had lower mean legal mass with the schema withheld. The mean paired drop was&lt;br&gt;
0.2034. In the withheld condition, the mask changed 21.6% of free-choice&lt;br&gt;
nuclei and changed the top token on 106 steps.&lt;/p&gt;

&lt;p&gt;This is the mechanism I had missed. The mask's effect depends partly on how&lt;br&gt;
much the prompt has already concentrated probability on legal tokens. It is&lt;br&gt;
not determined by the model and grammar alone.&lt;/p&gt;
&lt;h3&gt;
  
  
  TinyLlama moved, but not by much
&lt;/h3&gt;

&lt;p&gt;TinyLlama had 504 free-choice steps across 12 constrained generations. Its&lt;br&gt;
pooled collapse rate was 76.2%. The generation-level mean was 76.7%, with an&lt;br&gt;
11.6 percentage-point standard deviation and a range from 54.5% to 90.9%.&lt;/p&gt;

&lt;p&gt;This time the mask had a measurable effect:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TinyLlama measurement&lt;/th&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;free counterfactual collapse rate&lt;/td&gt;
&lt;td&gt;71.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;constrained collapse rate&lt;/td&gt;
&lt;td&gt;76.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mean legal probability mass&lt;/td&gt;
&lt;td&gt;0.9239&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mask changed nucleus size&lt;/td&gt;
&lt;td&gt;38 of 504 steps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mask changed top token&lt;/td&gt;
&lt;td&gt;36 of 504 steps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So the grammar added about five percentage points of collapse for TinyLlama.&lt;br&gt;
That is real in this sample, but it is a much narrower result than saying&lt;br&gt;
structured decoding generally turns top-p into greedy decoding.&lt;/p&gt;

&lt;p&gt;The separate free control for TinyLlama was not clean enough to rescue a&lt;br&gt;
larger claim. None of its 12 free generations parsed as JSON, and both prompts&lt;br&gt;
hit the 128-token cap in four of the six schemas. The same-logit comparison is&lt;br&gt;
the useful one here.&lt;/p&gt;
&lt;h3&gt;
  
  
  Schema shape still mattered
&lt;/h3&gt;

&lt;p&gt;My original ordering prediction mostly survived. Tight enums and patterned&lt;br&gt;
strings collapsed more often than ordinary string values. The gap was smaller&lt;br&gt;
than I expected, and even free strings often had a one-token nucleus.&lt;/p&gt;

&lt;p&gt;For Qwen at the reference setting, the schema-pooled rates ranged from 73.5%&lt;br&gt;
for the free-string schema to 94.4% for the tight enum. TinyLlama ranged from&lt;br&gt;
56.9% to 90.9% on those two schemas.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpyx0n3bfnoltbqmv0c4d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpyx0n3bfnoltbqmv0c4d.png" alt="Collapse rate by schema and grammar position" width="800" height="745"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4. Qwen collapse rates by schema and approximate grammar position.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The position labels in this figure come from the decoded prefix. They are&lt;br&gt;
useful for a rough breakdown, but a token that crosses two grammar positions&lt;br&gt;
or an escaped string can fool the tagger.&lt;/p&gt;
&lt;h3&gt;
  
  
  Constraint pressure did not predict collapse
&lt;/h3&gt;

&lt;p&gt;I expected low legal probability mass to line up with one-token nuclei. It&lt;br&gt;
did not.&lt;/p&gt;

&lt;p&gt;Pearson correlation between &lt;code&gt;Z_t&lt;/code&gt; and the collapse flag was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Qwen:      -0.020
TinyLlama: -0.056
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are effectively zero in this sample.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F206y3l5gyk5m87t0d31p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F206y3l5gyk5m87t0d31p.png" alt="Constraint pressure against the collapse flag for Qwen" width="800" height="669"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 5. Constraint pressure against the collapse flag for Qwen. The&lt;br&gt;
correlation is -0.020.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Qwen plot is mostly a pile of points near zero pressure because &lt;code&gt;Z_t&lt;/code&gt; is&lt;br&gt;
usually close to one. A few unusual steps do not explain the overall collapse&lt;br&gt;
rate.&lt;/p&gt;


&lt;h2&gt;
  
  
  Whole-output diversity
&lt;/h2&gt;

&lt;p&gt;The per-step measurement told me that the nucleus was often one token at&lt;br&gt;
&lt;code&gt;T=0.7&lt;/code&gt;, but it did not tell me whether sampler settings still changed whole&lt;br&gt;
outputs.&lt;/p&gt;

&lt;p&gt;For the second experiment I generated 6,144 Qwen outputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3 schemas
x 2 prompts
x 4 temperatures
x 2 top-p values
x 2 conditions
x 64 seeds
= 6,144 outputs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At &lt;code&gt;top_p=0.9&lt;/code&gt;, the mean constrained distinct-output rate rose with&lt;br&gt;
temperature:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;temperature&lt;/th&gt;
&lt;th&gt;constrained&lt;/th&gt;
&lt;th&gt;free&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.3&lt;/td&gt;
&lt;td&gt;14.3%&lt;/td&gt;
&lt;td&gt;14.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.7&lt;/td&gt;
&lt;td&gt;42.4%&lt;/td&gt;
&lt;td&gt;42.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;49.5%&lt;/td&gt;
&lt;td&gt;51.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.5&lt;/td&gt;
&lt;td&gt;60.7%&lt;/td&gt;
&lt;td&gt;65.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flx1qb61l9vln6rwl897z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flx1qb61l9vln6rwl897z.png" alt="Distinct output rate across temperature and top-p settings" width="799" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 6. Mean distinct-output rate across three schemas and two prompts,&lt;br&gt;
with 64 seeds in each cell.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The constrained curve is not flat. Temperature plainly still did something.&lt;br&gt;
For the free-string and mixed-object schemas, constrained and free distinct&lt;br&gt;
counts matched in every &lt;code&gt;top_p=0.9&lt;/code&gt; cell on both prompts. The visible gap at&lt;br&gt;
high temperature came mainly from the enum schema, where the grammar limits&lt;br&gt;
the field to five values. That gap is the schema doing its job, not evidence&lt;br&gt;
that the sampler settings had been disabled.&lt;/p&gt;

&lt;p&gt;There is a length caveat at &lt;code&gt;T=1.5&lt;/code&gt;. Some outputs hit the 128-token cap, and a&lt;br&gt;
truncated string still counts as distinct. I would not treat the final points&lt;br&gt;
as a clean measure of useful JSON diversity.&lt;/p&gt;

&lt;p&gt;I also compared each step's probability distribution at &lt;code&gt;T=0.7&lt;/code&gt; and &lt;code&gt;T=1.3&lt;/code&gt;&lt;br&gt;
using total variation distance. Many steps barely moved because one token was&lt;br&gt;
already dominant, while a smaller tail of steps remained temperature&lt;br&gt;
sensitive. Once again, Qwen's free and constrained distributions looked&lt;br&gt;
nearly the same.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F26u4z6x94xsr6b4sc8ss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F26u4z6x94xsr6b4sc8ss.png" alt="Per-step sensitivity to a temperature change" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 7. Total variation distance between each Qwen step's distribution at&lt;br&gt;
&lt;code&gt;T=0.7&lt;/code&gt; and &lt;code&gt;T=1.3&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Order of operations can fail completely
&lt;/h2&gt;

&lt;p&gt;I also checked where grammar masks sit in real sampling paths. In the pinned&lt;br&gt;
versions I inspected, Hugging Face Transformers, vLLM, and SGLang all apply&lt;br&gt;
the grammar mask before top-p.&lt;/p&gt;

&lt;p&gt;For the Hugging Face path, the relevant order is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;penalties -&amp;gt; grammar mask -&amp;gt; temperature -&amp;gt; top-p
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I deliberately reversed the last part and ran twelve seeds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mask -&amp;gt; temperature -&amp;gt; top-p
temperature -&amp;gt; top-p -&amp;gt; mask
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mask-first completed all twelve generations with valid JSON. Top-p-first&lt;br&gt;
failed all twelve because the top-p set and the legal-token set had an empty&lt;br&gt;
intersection. Sampling then received an invalid probability row.&lt;/p&gt;

&lt;p&gt;This was not a quality comparison. The reversed path simply failed. It also&lt;br&gt;
confirmed that sampler order is part of the behavior, not an implementation&lt;br&gt;
detail to ignore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation notes
&lt;/h2&gt;

&lt;p&gt;I computed the metric grid while the full logit vector was still in memory.&lt;br&gt;
My first storage plan saved only the top 4,096 logits and tried to reconstruct&lt;br&gt;
everything later. That loses entropy information at high temperature and&lt;br&gt;
cannot represent &lt;code&gt;top_p=1.0&lt;/code&gt;, where the full support matters.&lt;/p&gt;

&lt;p&gt;The first batched diversity run was also too slow because I moved full logits&lt;br&gt;
to the CPU before sorting for top-p. Keeping logits, masks, and seeded&lt;br&gt;
samplers on the GPU reduced the complete E2 run to 37 minutes and 24 seconds&lt;br&gt;
with a batch size of eight.&lt;/p&gt;

&lt;p&gt;I kept raw JSONL runs out of Git and committed the smaller aggregate CSVs,&lt;br&gt;
figures, configs, source, and notes. The full repository is available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Vaibhav701161/nucleus-collapse-constrained-decoding" rel="noopener noreferrer"&gt;nucleus-collapse-constrained-decoding on GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What this changes in practice
&lt;/h2&gt;

&lt;p&gt;If the complete schema is already in the prompt, my Qwen results suggest that&lt;br&gt;
a high one-token nucleus rate may be the model being confident, not the&lt;br&gt;
grammar secretly turning sampling into greedy decoding. Temperature still&lt;br&gt;
changed whole-output diversity in that setup.&lt;/p&gt;

&lt;p&gt;If the schema is enforced by an API or gateway but withheld from the model,&lt;br&gt;
the mask can do much more work. That is the setup where I would measure legal&lt;br&gt;
probability mass and compare free and constrained nuclei on my own prompts&lt;br&gt;
before assuming ordinary sampling settings behave the same way.&lt;/p&gt;

&lt;p&gt;The practical question includes both the grammar engine and how much of the&lt;br&gt;
output contract the model saw before decoding began.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I would claim now
&lt;/h2&gt;

&lt;p&gt;I would not say that JSON grammar masks generally collapse top-p sampling.&lt;br&gt;
Two small models and twelve constrained generations per model are nowhere near&lt;br&gt;
enough for that claim.&lt;/p&gt;

&lt;p&gt;The prompt ablation is smaller still: one model, one schema, one prompt, and&lt;br&gt;
eight matched seeds per condition. I read it as a mechanism check, not an&lt;br&gt;
estimate of what every hidden-schema deployment will do.&lt;/p&gt;

&lt;p&gt;What these runs do show is more specific:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A one-token nucleus can be common even after forced syntax steps are
removed.&lt;/li&gt;
&lt;li&gt;For Qwen2.5-1.5B-Instruct with the full schema in the prompt, the grammar
mask did not cause that collapse at &lt;code&gt;T=0.7&lt;/code&gt; and &lt;code&gt;top_p=0.9&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Withholding one mixed-object schema dropped mean generation &lt;code&gt;Z_t&lt;/code&gt; from
0.9996 to 0.7962 and produced a 12.8-point mask effect on collapse.&lt;/li&gt;
&lt;li&gt;TinyLlama showed a modest, model-dependent mask effect of about five
percentage points.&lt;/li&gt;
&lt;li&gt;Within the original schema-shown runs, constraint mass did not predict
collapse in either model.&lt;/li&gt;
&lt;li&gt;Temperature continued to change whole-output diversity in the larger Qwen
run.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I began with a story about grammar masks silently disabling sampling. The&lt;br&gt;
first measurements did not support it because the prompt had already pushed&lt;br&gt;
Qwen onto legal tokens. When I removed that prompt information, the grammar&lt;br&gt;
became visible in both probability mass and nucleus size.&lt;/p&gt;

&lt;p&gt;That is not the result I planned to write up, but it is the one the logits&lt;br&gt;
gave me.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Grammars are written in characters. Models emit tokens.</title>
      <dc:creator>Vaibhav Mittal</dc:creator>
      <pubDate>Mon, 27 Jul 2026 13:44:55 +0000</pubDate>
      <link>https://dev.to/vaibhav_mittal_ac22a2c5d6/grammars-are-written-in-characters-models-emit-tokens-1k07</link>
      <guid>https://dev.to/vaibhav_mittal_ac22a2c5d6/grammars-are-written-in-characters-models-emit-tokens-1k07</guid>
      <description>&lt;p&gt;I am about to spend several weeks building a constrained decoding engine, so I started by reading a paper from 2019.&lt;/p&gt;

&lt;p&gt;Section 2.2 of the GPT-2 paper is about half a page long. It sits between the training dataset section and the model architecture section, and it is easy to skim past. It is also, as far as I can tell, the origin of the single hardest problem in the field I am about to work in.&lt;/p&gt;

&lt;p&gt;Here is what it says, what I verified by running the tokenizer myself, and the thing I found at the end that I did not expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part everybody already knows
&lt;/h2&gt;

&lt;p&gt;A model computes on numbers. Text is characters. Something has to bridge that, and there are three obvious ways to do it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Word level.&lt;/strong&gt; Map every word to an id. The vocabulary explodes, because every inflection and typo and compound is a new entry, and anything the model never saw during training becomes &lt;code&gt;&amp;lt;UNK&amp;gt;&lt;/code&gt;. The model goes blind on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Character level.&lt;/strong&gt; Map every character to an id. Nothing is ever out of vocabulary, because you can spell anything. But sequences get roughly five times longer, transformer attention cost scales with the square of sequence length, and you have made the model learn spelling before it can learn meaning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subword level.&lt;/strong&gt; Split into pieces. Common words stay whole, rare words break into fragments the model has seen in other contexts. Short sequences, no out of vocabulary hole.&lt;/p&gt;

&lt;p&gt;Byte Pair Encoding is the subword method that won. It came from a 1994 data compression algorithm and got repurposed for NLP by Sennrich et al. in 2015. The training loop is four lines of pseudocode:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with a base vocabulary of every individual symbol in your corpus.&lt;/li&gt;
&lt;li&gt;Count every adjacent pair of symbols.&lt;/li&gt;
&lt;li&gt;Merge the most frequent pair into a new symbol and add it to the vocabulary.&lt;/li&gt;
&lt;li&gt;Go back to step 2 until you hit your target size.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it. If &lt;code&gt;t h&lt;/code&gt; shows up constantly, you get a &lt;code&gt;th&lt;/code&gt; token. Then &lt;code&gt;th e&lt;/code&gt; shows up constantly, so you get &lt;code&gt;the&lt;/code&gt;. Run it 50,000 times and common English words are single tokens while rare ones stay in fragments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Finivgvktq3kncth4c9wv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Finivgvktq3kncth4c9wv.png" alt="Diagram comparing Word-Level, Character-Level, and Subword-Level (BPE) tokenization, highlighting OOV issues, sequence lengths, and computational efficiency trade-offs." width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The property worth holding onto: this is greedy, frequency based, and entirely dependent on the corpus. Nothing about the resulting vocabulary is linguistically principled. It is whatever compressed the training data. That is why different tokenizers split the same word differently and why numbers and code often tokenize in ways that look arbitrary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GPT-2 actually contributed
&lt;/h2&gt;

&lt;p&gt;Standard BPE at the time ran over Unicode code points. Unicode has more than 130,000 of them, so your base vocabulary is enormous before you have merged anything, and you still have holes for anything outside your set.&lt;/p&gt;

&lt;p&gt;GPT-2 runs BPE over UTF-8 bytes instead.&lt;/p&gt;

&lt;p&gt;A byte has 256 possible values. That is your entire base vocabulary. And because every possible Unicode string encodes to some sequence of UTF-8 bytes, there is no out of vocabulary case. Not for any language, not for emoji, not for malformed input. Any string at all.&lt;/p&gt;

&lt;p&gt;This is the actual point of byte level BPE and I think it usually gets described wrong. It is not primarily an efficiency win. It is a coverage guarantee bought with a tiny base vocabulary.&lt;/p&gt;

&lt;p&gt;The paper is also honest about what broke when they first tried it. Applying BPE naively to bytes produced bad merges, because the greedy frequency heuristic learned separate tokens for &lt;code&gt;dog&lt;/code&gt;, &lt;code&gt;dog.&lt;/code&gt;, &lt;code&gt;dog!&lt;/code&gt;, and &lt;code&gt;dog?&lt;/code&gt;. Four vocabulary slots for one word. So they blocked merges across character categories: letters do not merge with punctuation.&lt;/p&gt;

&lt;p&gt;You can check that this worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'dog'   -&amp;gt; ['dog']         [9703]
'dog.'  -&amp;gt; ['dog', '.']    [9703, 13]
'dog!'  -&amp;gt; ['dog', '!']    [9703, 0]
'dog?'  -&amp;gt; ['dog', '?']    [9703, 30]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same &lt;code&gt;dog&lt;/code&gt; token, id 9703, in all four. The punctuation is separate every time.&lt;/p&gt;

&lt;p&gt;Then they carved out an exception for spaces, because blocking every cross category merge fragments ordinary text badly. Spaces get attached to the token that follows them, not the one before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'hello world' -&amp;gt; ['hello', 'Ġworld']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Ġ&lt;/code&gt; is how GPT-2's vocabulary renders a leading space. It is a prefix, which means &lt;code&gt;hello&lt;/code&gt; and &lt;code&gt;hello&lt;/code&gt; are two different tokens with two different ids (31373 and 23748). The same five letters, in the same order, are a different symbol to the model depending on whether a space came first.&lt;/p&gt;

&lt;p&gt;Hold that thought.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhk5s57scy89rg0jzbpe1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhk5s57scy89rg0jzbpe1.png" alt="Diagram illustrating the Byte-Level BPE training loop and GPT-2 specific optimizations like character-category merge blocking and whitespace handling." width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade nobody explains cleanly
&lt;/h2&gt;

&lt;p&gt;Vocabulary size is a knob with a real cost on both sides.&lt;/p&gt;

&lt;p&gt;Bigger vocabulary means shorter sequences for the same text, so fewer decode steps to say the same thing. But your embedding matrix and your final softmax layer both scale with vocabulary size, so you pay in parameters and in output projection compute.&lt;/p&gt;

&lt;p&gt;Smaller vocabulary means a cheaper embedding and softmax, but longer sequences. And since decoding is one sequential forward pass per token, and decoding is the memory bound half of inference, more tokens is directly more wall clock time.&lt;/p&gt;

&lt;p&gt;50,257 was GPT-2's answer. It is not a constant of nature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now the part I actually came for
&lt;/h2&gt;

&lt;p&gt;Everything above is background. Here is why I was reading it.&lt;/p&gt;

&lt;p&gt;Constrained decoding is the technique that forces a model's output to conform to a schema. At every generation step, before sampling, you set the logits of every token that would break the schema to negative infinity. After softmax those tokens have probability exactly zero. The model cannot emit them. Validity stops being something you hope for and becomes something structural.&lt;/p&gt;

&lt;p&gt;To do that, you compile the schema into a state machine. At any state you know which characters are legal next, so you know which tokens are legal next, so you know what to mask.&lt;/p&gt;

&lt;p&gt;Except the schema is defined over characters and the model emits tokens, and those two things do not line up.&lt;/p&gt;

&lt;p&gt;I knew that abstractly before I started. What I did not appreciate is how badly they do not line up. So I ran GPT-2's tokenizer over some JSON.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;
&lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;        &lt;span class="c1"&gt;# [4895]
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;        &lt;span class="c1"&gt;# [1298]
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;        &lt;span class="c1"&gt;# [20662]
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;},{&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;     &lt;span class="c1"&gt;# [11919]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;{"&lt;/code&gt; is one token. The opening brace and the opening quote of the first key are fused into a single symbol, id 4895.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;":&lt;/code&gt; is one token. The closing quote of a key and the colon that follows it, fused, id 1298.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"},{"&lt;/code&gt; is one token. Id 11919. Five characters: close a string, close an object, comma, open an object, open a string. That is five separate transitions in any JSON grammar you would write, and the model emits all of them or none of them, as one indivisible unit.&lt;/p&gt;

&lt;p&gt;A full tool call looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'{"location": "Jaipur", "unit": "celsius"}'

['{"', 'location', '":', 'Ġ"', 'Ja', 'ip', 'ur', '",',
 'Ġ"', 'unit', '":', 'Ġ"', 'cel', 's', 'ius', '"}']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sixteen tokens. Look at where the boundaries fall. Not one of the structural delimiters sits alone. &lt;code&gt;{"&lt;/code&gt;, &lt;code&gt;":&lt;/code&gt;, &lt;code&gt;",&lt;/code&gt;, &lt;code&gt;Ġ"&lt;/code&gt;, &lt;code&gt;"}&lt;/code&gt;. The grammar's alphabet and the model's alphabet are different alphabets, and the model's is coarser in exactly the places the grammar cares about most.&lt;/p&gt;

&lt;p&gt;Out of 50,257 entries in the vocabulary, 50,001 are longer than a single character. That is 99.5 percent. 311 of them contain at least one of &lt;code&gt;{ } [ ] " : ,&lt;/code&gt; and 92 of those also contain alphanumerics, which means there are almost a hundred single tokens that straddle the line between structure and content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this costs you accuracy
&lt;/h2&gt;

&lt;p&gt;Here is the mechanism, as I currently understand it. I want to be clear that this is the field's leading explanation rather than something settled, and testing it is a large part of what I am about to do.&lt;/p&gt;

&lt;p&gt;Say a model is parsing a receipt and the correct total is &lt;code&gt;0.46&lt;/code&gt;. Left alone, it tokenizes like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'0.46' -&amp;gt; ['0', '.', '46']   [15, 13, 3510]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three tokens, and &lt;code&gt;46&lt;/code&gt; is a single one of them, id 3510. That is the path the model has walked thousands of times in training.&lt;/p&gt;

&lt;p&gt;Now constrain it. The grammar is in some state where it expects a number, and depending on how your automaton is built and where the previous token landed you may end up masking in a way that forces the model down a different token path to spell the same string. The output is still valid JSON. It just took a route through the distribution that the model almost never took during training, in this context, and everything it generates after that point is conditioned on a prefix it did not choose.&lt;/p&gt;

&lt;p&gt;BAML published a concrete case of this. On a receipt parsing task, constrained decoding returned &lt;code&gt;1&lt;/code&gt; where the correct value was &lt;code&gt;0.46&lt;/code&gt;. Across the task they measured 91.37 percent accuracy constrained against 93.63 percent with free form parsing.&lt;/p&gt;

&lt;p&gt;The 2 point gap is not the interesting part. The interesting part is that &lt;code&gt;1&lt;/code&gt; is perfectly valid JSON. Your schema validated. Your types checked. Your pipeline reported success. There is no exception, no parse error, no signal anywhere that anything went wrong. The failure is silent by construction, because the entire purpose of the constraint was to make invalid output impossible, and it succeeded.&lt;/p&gt;

&lt;p&gt;At the larger end, Tam et al. measured format restrictions degrading reasoning accuracy on benchmarks by as much as 27 points under strict constraints (EMNLP 2024). That number gets quoted a lot without its conditions attached, so treat it as an upper bound under specific settings rather than a headline, but the direction has since been replicated independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token healing, and why it exists
&lt;/h2&gt;

&lt;p&gt;There is a partial fix for the boundary half of this, and it has been sitting in Guidance for a while.&lt;/p&gt;

&lt;p&gt;When a constraint truncates in the middle of what would naturally have been a single token, you back up to a clean token boundary and re-constrain from there, so the model gets to pick a token that spans the boundary rather than being forced to spell it out piece by piece.&lt;/p&gt;

&lt;p&gt;It is called token healing, and reading GPT-2's section 2.2 is what made me understand why it needs to exist at all. It is not a workaround for a sloppy implementation. It is a direct consequence of a design decision made in 2019 for entirely unrelated reasons: bytes instead of code points, greedy frequency merges, spaces glued to the front of the following word. None of those choices were made with grammars in mind, because grammar constrained generation did not exist yet.&lt;/p&gt;

&lt;p&gt;The XGrammar paper puts the same thing more precisely than I can. Their framing is that tokens are fixed strings which may not correspond to complete semantic units and may split Unicode characters, and that this is the fundamental challenge for structured generation. I found that sentence after I had worked out the problem by hand, which was a useful check that I was looking at the right thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I do not know yet
&lt;/h2&gt;

&lt;p&gt;Two things I want to measure rather than assume.&lt;/p&gt;

&lt;p&gt;First, how much of the observed degradation is actually the boundary problem versus the other candidate mechanism, which is that a schema forces the model to commit to an &lt;code&gt;answer&lt;/code&gt; field before the reasoning that would produce the answer has happened. My prior is that the second dominates on reasoning tasks and the first dominates on extraction tasks, but that is a guess and I have not seen it cleanly separated anywhere.&lt;/p&gt;

&lt;p&gt;Second, whether the degradation is size dependent. If small models are hurt more than large ones, or the reverse, the shape of that curve says something about whether this is a capacity problem or a distributional one.&lt;/p&gt;

&lt;p&gt;I am reproducing all of this across four model sizes over the next couple of weeks and publishing the harness with the numbers. If you have run into a case where structured output gave you a wrong but valid answer, I would like to see it. I am collecting them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;p&gt;Everything above runs in about thirty seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pip install tiktoken
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;
&lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;toks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode_single_token_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;!r:&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;toks&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dog&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dog.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; hello&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;},{&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.46&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;46&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Jaipur&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;celsius&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The GPT-2 paper is &lt;em&gt;Language Models are Unsupervised Multitask Learners&lt;/em&gt;, Radford et al. 2019. Section 2.2 is the one to read, and it is shorter than this post.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>machinelearning</category>
      <category>python</category>
    </item>
    <item>
      <title>Self-hosting SigNoz: notes from a day of actually running it</title>
      <dc:creator>Vaibhav Mittal</dc:creator>
      <pubDate>Fri, 17 Jul 2026 17:33:33 +0000</pubDate>
      <link>https://dev.to/vaibhav_mittal_ac22a2c5d6/self-hosting-signoz-notes-from-a-day-of-actually-running-it-14ph</link>
      <guid>https://dev.to/vaibhav_mittal_ac22a2c5d6/self-hosting-signoz-notes-from-a-day-of-actually-running-it-14ph</guid>
      <description>&lt;p&gt;Last week I self-hosted SigNoz, wired a small app into it, and spent a day poking at traces, logs, dashboards, and alerts. Most of it worked the way the docs said it would. Two things did not, and one of those turned into the most useful thing I learned all day: my logs and my traces were both arriving in SigNoz, but they didn't know about each other.&lt;/p&gt;

&lt;p&gt;This post is a writeup of the whole thing, including the parts where I got stuck.&lt;/p&gt;

&lt;h2&gt;
  
  
  The app
&lt;/h2&gt;

&lt;p&gt;I needed something real to observe, so I built Shelfie, a Flask + SQLite API for tracking books. It has the usual CRUD routes, plus a &lt;code&gt;/books/&amp;lt;id&amp;gt;/recommendations&lt;/code&gt; endpoint that calls the Open Library API live. I also added two endpoints on purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;related-slow&lt;/code&gt; fetches all the book IDs for a subject, then queries each one individually (the classic N+1 pattern)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;related-fast&lt;/code&gt; returns the same data in a single query&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The idea was to have a known performance bug in the app and see how obvious SigNoz could make it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying SigNoz
&lt;/h2&gt;

&lt;p&gt;SigNoz now ships an installer called Foundry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://signoz.io/foundry.sh | bash
foundryctl cast &lt;span class="nt"&gt;-f&lt;/span&gt; casting.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;casting.yaml&lt;/code&gt; is seven lines saying "docker compose, please". That generated and started six containers: ClickHouse, a Postgres metastore, the OTel collector, and the main &lt;code&gt;signoz&lt;/code&gt; server.&lt;/p&gt;

&lt;p&gt;Two things tripped me up here. First, the UI is on port 8080 now. A lot of older tutorials say 3301. Second, and this one cost me more time: the collector would not accept any telemetry at first. Its logs showed the opamp connection failing with &lt;code&gt;"cannot create agent without orgId"&lt;/code&gt;. It turns out that until you create the first admin account, there is no organization in the metastore, and the collector has nowhere to attach your data. I registered via the API (&lt;code&gt;POST /api/v1/register&lt;/code&gt;) and the error went away immediately. So if your fresh SigNoz install seems to be dropping everything silently, check whether you ever finished signup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrumenting Flask
&lt;/h2&gt;

&lt;p&gt;Traces needed no code changes at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;opentelemetry-instrument &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--traces_exporter&lt;/span&gt; otlp &lt;span class="nt"&gt;--metrics_exporter&lt;/span&gt; otlp &lt;span class="nt"&gt;--logs_exporter&lt;/span&gt; otlp &lt;span class="se"&gt;\&lt;/span&gt;
  flask run &lt;span class="nt"&gt;--port&lt;/span&gt; 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added two custom metrics by hand (a counter for books created and a histogram timing the Open Library calls), then ran a small load generator against the app: normal browsing, recommendations, both related endpoints, and a steady trickle of requests for a book ID that doesn't exist, so there would be a real 404 rate.&lt;/p&gt;

&lt;p&gt;One installation problem I would point out : I had pinned &lt;code&gt;opentelemetry-distro==0.48b0&lt;/code&gt;, and &lt;code&gt;opentelemetry-bootstrap&lt;/code&gt; then installed instrumentation packages from 0.65b0. Pip's resolver complained mid-install and the bootstrap failed. Upgrading everything to a matched set (SDK 1.44.0, instrumentation 0.65b0) fixed it. These packages really do need to move together.&lt;/p&gt;

&lt;p&gt;A minute or so after starting the load generator, the Services page showed &lt;code&gt;shelfie&lt;/code&gt; with a P99 of 1749 ms (the live Open Library calls are slow) and a 0.47% error rate from the deliberate 404s:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fguw2jmm8uwwxal2j00wx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fguw2jmm8uwwxal2j00wx.png" alt="SigNoz home showing the shelfie service with live P99, error rate, and ops/sec" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The N+1 in a waterfall
&lt;/h2&gt;

&lt;p&gt;The slowest &lt;code&gt;related-slow&lt;/code&gt; trace had 10 spans and took 74.27 ms: the Flask handler span, a connect, and then a stack of single-row &lt;code&gt;SELECT&lt;/code&gt;s, each with its full SQL in the span attributes. The equivalent &lt;code&gt;related-fast&lt;/code&gt; trace had 5 spans and took 7.94 ms. Same response body, about 9x the latency. You can see the problem in the shape of the waterfall before you read anything:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl4s0ydv0aw0azanocnhy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl4s0ydv0aw0azanocnhy.png" alt="Trace waterfall of the N+1 endpoint: 74.27ms, 10 spans, repeated SELECTs" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All of those spans came from auto-instrumentation (Flask, SQLAlchemy, requests). I wrote none of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The empty Logs tab
&lt;/h2&gt;

&lt;p&gt;Now the part I actually want to tell you about. I opened one of those traces and clicked the Logs tab in the span details panel, expecting to see the request's log lines. Instead: "No logs found for this trace."&lt;/p&gt;

&lt;p&gt;The logs were in SigNoz. I could query them in the Logs explorer. But their &lt;code&gt;trace_id&lt;/code&gt; and &lt;code&gt;span_id&lt;/code&gt; fields were empty, so nothing linked them to the trace. I checked ClickHouse directly to be sure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;trace_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;span_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;127.0.0.1 - - [17/Jul/2026 13:28:33] "GET /books/subjects/romance/related-slow HTTP/1.1" 200 -&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That log line is Werkzeug's access log, and it explains the whole thing. The access log is written after the request finishes. By that point the span has ended and there is no active trace context left to inject, so the exporter ships the line with no IDs. Everything was "working". The correlation just silently wasn't happening.&lt;/p&gt;

&lt;p&gt;The fix was two small changes. I added a normal &lt;code&gt;logger.info(...)&lt;/code&gt; call inside the recommendations handler, so at least one log line is emitted while the span is still open, and I set &lt;code&gt;OTEL_PYTHON_LOG_CORRELATION=true&lt;/code&gt; so the logging instrumentation stamps the active trace context onto each record. After a restart, the new log line had a real trace_id in ClickHouse, and the same trace's Logs tab in the UI now showed it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx3a31enlkhg1tzs1pcpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx3a31enlkhg1tzs1pcpa.png" alt="The same trace's Logs tab now showing the correlated application log line" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This trace-to-logs link ended up being my favorite feature in SigNoz. Being able to open a slow request and see only that request's log lines removes the usual step of grepping logs around a timestamp and hoping. But the lesson from the empty tab stands: a connected pipeline is not the same as correlated data. If your framework logs after the span closes, you get nothing, and no error tells you so. You have to open a trace and check.&lt;/p&gt;

&lt;h2&gt;
  
  
  A dashboard and a real alert
&lt;/h2&gt;

&lt;p&gt;I built a small dashboard with two panels: the custom books-created counter, and a spans-per-minute panel filtered to &lt;code&gt;service.name = 'shelfie'&lt;/code&gt;. One note on the counter: it's a monotonic sum, and with the default Rate aggregation my one burst of seed inserts was a barely visible blip. Switching the panel to Increase made it readable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd2w1hmwn8jqo37b7dwj4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd2w1hmwn8jqo37b7dwj4.png" alt="Dashboard with the custom books-created metric and request-count panel" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For alerting I wanted to see an actual delivery, not just a rule turning red. So I wrote a 15-line Python HTTP server that appends every POST body to a file, ran it on port 9999, and created a SigNoz Webhook notification channel pointing at it. One Docker detail: from inside the SigNoz container, &lt;code&gt;localhost&lt;/code&gt; is the container, not my machine. &lt;code&gt;docker inspect&lt;/code&gt; gave me the bridge gateway address (&lt;code&gt;172.19.0.1&lt;/code&gt;), and I tested it with a &lt;code&gt;wget&lt;/code&gt; from inside the container before configuring anything. The probe showed up in my receiver's log, so I knew the path worked.&lt;/p&gt;

&lt;p&gt;Then I made a trace-based alert rule (span count for &lt;code&gt;shelfie&lt;/code&gt; above 0 over a 5-minute rolling window, a threshold chosen to fire immediately under load) and waited. About ten minutes later my receiver logged this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"receiver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"shelfie-webhook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"firing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"alerts"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"labels"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"alertname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Shelfie traffic alert"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"critical"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"annotations"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"...(current value: 32) crosses the threshold (0)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"related_traces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8080/traces-explorer?..."&lt;/span&gt;&lt;span class="p"&gt;}}]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the rule showed Firing in the UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhv07wmgvjtgmesckkr9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhv07wmgvjtgmesckkr9s.png" alt="Alert rule in Firing state" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;related_traces&lt;/code&gt; field in the payload is a nice touch. It's a link that opens the traces explorer already filtered to the service and the exact time window that fired the alert.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things I'd tell someone doing this tomorrow
&lt;/h2&gt;

&lt;p&gt;Finish the signup before you debug ingestion. No account means no org, and no org means the collector rejects everything with only a vague log line to show for it.&lt;/p&gt;

&lt;p&gt;The UI is on port 8080. Ignore anything that says 3301.&lt;/p&gt;

&lt;p&gt;Log correlation is effectively opt-in. Auto-instrumentation gives you traces for free, but if all your logs come from an access logger that runs after the span ends, none of them will carry trace IDs. Log from inside the handler and set &lt;code&gt;OTEL_PYTHON_LOG_CORRELATION=true&lt;/code&gt;, then verify by opening a trace's Logs tab. Don't just confirm that logs arrive.&lt;/p&gt;

&lt;p&gt;Probe your alert channel before you create the rule. Testing the webhook path with one &lt;code&gt;wget&lt;/code&gt; from inside the container meant that when the alert didn't arrive instantly, I knew the delay was the evaluation window and not Docker networking.&lt;/p&gt;

&lt;p&gt;Keep the OpenTelemetry Python packages on matching versions. Mixed pins fail at install time in a confusing way.&lt;/p&gt;

&lt;p&gt;All of this ran on one WSL2 machine in an afternoon: six containers, one Flask app, and a webhook receiver that now has a real alert payload sitting in its log file.&lt;/p&gt;

&lt;p&gt;References: &lt;a href="https://signoz.io/docs/" rel="noopener noreferrer"&gt;SigNoz docs&lt;/a&gt;, &lt;a href="https://opentelemetry.io/docs/languages/python/" rel="noopener noreferrer"&gt;OpenTelemetry Python&lt;/a&gt;, &lt;a href="https://signoz.io/docs/userguide/alerts-management/" rel="noopener noreferrer"&gt;SigNoz alerting&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>observability</category>
      <category>docker</category>
      <category>python</category>
    </item>
  </channel>
</rss>
