<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://migrationassurance.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://migrationassurance.com/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-07-24T09:24:06+00:00</updated><id>https://migrationassurance.com/feed.xml</id><title type="html">Migration Assurance</title><subtitle>Evidence-grade sign-off for model swaps and agent deployments.</subtitle><author><name>Ashish Kumar Singh</name></author><entry><title type="html">I changed one thing about my $40 contract-review model. It gained ten points. My prediction still missed.</title><link href="https://migrationassurance.com/one-change-ten-points/" rel="alternate" type="text/html" title="I changed one thing about my $40 contract-review model. It gained ten points. My prediction still missed." /><published>2026-07-18T00:00:00+00:00</published><updated>2026-07-18T00:00:00+00:00</updated><id>https://migrationassurance.com/one-change-ten-points</id><content type="html" xml:base="https://migrationassurance.com/one-change-ten-points/"><![CDATA[<p>Last week I published <a href="/forty-dollar-specialist-vs-frontier/">an experiment</a>
where a $40 fine-tuned 14B model lost to frontier LLMs at contract clause
extraction by 27 points, and I ended it with a bet, written down before spending
another dollar: reframe the task from generation to extraction, and the same
model clears 0.50 on the official CUAD metric.</p>

<p>The experiment ran. Here is the honest scoreboard first, because that is the
deal with pre-registered predictions: you publish the result you got, not the
one you wanted.</p>

<table>
  <thead>
    <tr>
      <th>Model</th>
      <th style="text-align: right">CUAD AUPR (full test split)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Claude Opus 4.8</td>
      <td style="text-align: right">0.561</td>
    </tr>
    <tr>
      <td>Claude Opus 4.6</td>
      <td style="text-align: right">0.498</td>
    </tr>
    <tr>
      <td>GPT-5.2</td>
      <td style="text-align: right">0.423</td>
    </tr>
    <tr>
      <td>GPT-4o</td>
      <td style="text-align: right">0.421</td>
    </tr>
    <tr>
      <td>nightwing v2 (extractive)</td>
      <td style="text-align: right"><strong>0.389</strong></td>
    </tr>
    <tr>
      <td>nightwing v1 (generative)</td>
      <td style="text-align: right">0.291</td>
    </tr>
  </tbody>
</table>

<p>0.389 is not 0.50. The prediction missed and I will get to why. But look at the
two nightwing rows. Same base model. Same training data. Same one-epoch budget,
about thirty dollars of GPU time. The only difference is what I asked the model
to do with the contract.</p>

<h2 id="the-one-change">The one change</h2>

<p>Version one treated contract review the way everyone treats LLMs: as text
generation. Show the model a contract chunk and a question, train it to write
out the answer clause. Version two treats it as what the benchmark actually is:
span extraction. The model does not retype the clause. It points at where the
clause starts and where it ends.</p>

<p>That is the entire change. Pointing instead of retyping. Ten points.</p>

<p>Generation was paying a double tax. The model had to reproduce long legal spans
verbatim, where one paraphrased word breaks the span-overlap scoring, and it had
to do that from a decoder trained to be fluent rather than faithful. Extraction
removes both failure modes at once: the span it selects is by construction
verbatim, and the scoring threshold stops punishing honest reformulation.</p>

<h2 id="where-the-ten-points-landed">Where the ten points landed</h2>

<p>The headline gap to Claude is still real. What moved is everything underneath
it. Against the GPT frontier models, the specialist now wins a majority of
categories: <strong>25 of 40 against GPT-4o, 22 of 40 against GPT-5.2</strong>. In version
one it won 11. It beats all four frontier models simultaneously in six
categories now, up from two, and the margins got silly: Agreement Date 0.829
against a best frontier score of 0.168.</p>

<p>The part I did not expect: the reasoning-heavy categories recovered too. In
version one I concluded that cheap specialists learn annotation conventions
while real semantics stay with the giants, and the collapse categories backed
that up. Version two took Covenant Not To Sue from 0.107 to 0.539, Joint IP
Ownership from 0.184 to 0.500, Anti-Assignment from 0.224 to 0.497. Those are
not date-format conventions. The generative framing was not just taxing the
easy categories; it was hiding capability the model already had.</p>

<h2 id="the-other-experiment-hiding-inside-this-one">The other experiment hiding inside this one</h2>

<p>Because the pipeline was cheap, I trained the identical recipe at four sizes.
Dev scores: 0.5B gets 0.284, 1.5B gets 0.289, 7B gets 0.300, 14B gets 0.303.</p>

<p>Twenty-eight times more parameters, two points. The half-billion-parameter
model, which trains overnight on a consumer laptop, already matches what my
14B generative version needed an A100 to reach. If you read the first essay,
this should rhyme: GPT-4o and GPT-5.2, a full frontier generation apart, tied
on this task to within two thousandths of a point. Same lesson from the other
side of the fence. On convention-bound extraction work, scale is almost
decorative. Framing is load-bearing.</p>

<h2 id="why-the-prediction-missed">Why the prediction missed</h2>

<p>The 0.50 target was not pulled from air. A 0.9B extractive model from the
original CUAD paper scores about 0.47 published. My 14B extractive scored 0.389.
The difference is that the published number comes from the full training recipe:
multiple epochs, every negative example, tuned hyperparameters. Mine was
deliberately lean, one epoch, negatives subsampled two-to-one, first-guess
learning rate, zero tuning, because the question this round was about framing,
not recipe, and I kept every other variable at its cheapest setting.</p>

<p>That is a real miss against a target I set knowing the reference point, so it
counts as a miss. But it localizes the remaining gap precisely: not
architecture, not scale, recipe. Version three changes only that, and the
prediction carries forward with better information behind it.</p>

<h2 id="what-this-means-if-you-buy-llm-systems">What this means if you buy LLM systems</h2>

<p>The cheapest lever in this entire series was not a bigger model, more data, or
more GPU hours. It was asking the model for the right kind of output. Before
you benchmark vendors against each other, benchmark the task framing against
itself: the same model, asked two different ways, just produced a ten-point
swing that dwarfs the gap between two frontier generations.</p>

<p>And a note on predictions. Writing 0.50 down in public before spending made
this essay harder to write and easier to trust. My model lost to my own
forecast, in print, and the experiment is still the most useful thing this
project has produced. Vendor decks do not work this way. That is worth
remembering when you read them.</p>

<p>Everything is public and re-scorable at
<a href="https://github.com/ashish24142/nightwing">github.com/ashish24142/nightwing</a>:
raw predictions for every model at every size, the full run journal including
the four dependency failures and the two silent bugs that free local testing
caught before they could burn the cloud budget, and the exact runbook that
produced the numbers. The trained model itself is on
<a href="https://huggingface.co/ashish24142/nightwing-14b-cuad-extractive">Hugging Face</a>
if you want to run it on your own contracts.</p>

<hr />

<p><em>I’m <a href="https://www.linkedin.com/in/ashish-kr-singh-ml/">Ashish Kumar Singh</a>, an
engineering leader building enterprise LLM systems, and I still write the code.
The nightwing code and every prediction file are on
<a href="https://github.com/ashish24142/nightwing">GitHub</a>; the trained model is on
<a href="https://huggingface.co/ashish24142/nightwing-14b-cuad-extractive">Hugging Face</a>;
the paper is at <a href="https://doi.org/10.5281/zenodo.21530247">doi.org/10.5281/zenodo.21530247</a>.</em></p>

<p><em>Views my own. Not legal advice, and the model still isn’t a lawyer.</em></p>]]></content><author><name>Ashish Kumar Singh</name></author><summary type="html"><![CDATA[The sequel to the nightwing experiment: swapping generative fine-tuning for an extractive span head moved CUAD AUPR from 0.291 to 0.389 at the same size, data and budget. The model now beats GPT-4o in 25 of 40 categories. The pre-registered 0.50 prediction missed, and the scaling curve from 0.5B to 14B is nearly flat. Every number is public and re-scorable.]]></summary></entry><entry><title type="html">I spent $40 fine-tuning a 14B model to beat frontier LLMs at contract review. It lost by 27 points. Except where it won.</title><link href="https://migrationassurance.com/forty-dollar-specialist-vs-frontier/" rel="alternate" type="text/html" title="I spent $40 fine-tuning a 14B model to beat frontier LLMs at contract review. It lost by 27 points. Except where it won." /><published>2026-07-13T00:00:00+00:00</published><updated>2026-07-13T00:00:00+00:00</updated><id>https://migrationassurance.com/forty-dollar-specialist-vs-frontier</id><content type="html" xml:base="https://migrationassurance.com/forty-dollar-specialist-vs-frontier/"><![CDATA[<p>Everyone in legal AI is paying frontier prices right now. The pitch behind my
latest experiment was simple: contract clause extraction is a narrow, repetitive,
convention-heavy task. Exactly the kind of thing a small specialist model should
be able to learn. So what does $40 of fine-tuning actually buy against models
that cost $165 per evaluation run?</p>

<p>The honest answer: not victory. Something more interesting. The specialist
could not lay a hand on the reigning giant, Claude. But against GPT-5.2 it won
11 of 40 categories outright, and against GPT-4o, another 11.</p>

<h2 id="the-setup">The setup</h2>

<p>The benchmark is <a href="https://www.atticusprojectai.org/cuad">CUAD</a>, 510 real
commercial contracts annotated by lawyers across 41 clause categories: governing
law, non-compete, IP assignment, termination, the clauses a lawyer scans for
first. The task: given a contract and a category, extract the exact supporting
text, or say the clause is absent. Scoring uses CUAD’s official metric (area
under the precision-recall curve), not a metric I invented.</p>

<p>I measured four frontier models first, through one identical pipeline: Claude
Opus 4.8, Claude Opus 4.6, GPT-5.2, and GPT-4o. Then I fine-tuned Qwen3-14B, an
open Apache-licensed model, with LoRA on CUAD’s training split. One A100 for an
afternoon. About $40 of GPU time.</p>

<p>Three rules were fixed before any training started, because a benchmark you can
quietly bend is worthless:</p>

<p><strong>First, the test set is untouchable.</strong> A script proves zero overlap between
training and test contracts, in code, before every run. Checkpoint selection
happened on a held-out dev split. The test set was scored exactly once.</p>

<p><strong>Second, the thresholds were pre-committed.</strong> Within 4 points of frontier:
green. 5 to 8 points below: yellow. Worse than that: red. Written down before
the GPU was rented.</p>

<p><strong>Third, everything is auditable.</strong> Every model’s raw predictions are committed
to the <a href="https://github.com/ashish24142/nightwing">public repo</a>. You can re-score
every number in this essay yourself.</p>

<h2 id="the-scoreboard">The scoreboard</h2>

<table>
  <thead>
    <tr>
      <th>Model</th>
      <th style="text-align: right">CUAD AUPR (full test split)</th>
      <th style="text-align: right">Cost per evaluation</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Claude Opus 4.8</td>
      <td style="text-align: right">0.561</td>
      <td style="text-align: right">~$165</td>
    </tr>
    <tr>
      <td>Claude Opus 4.6</td>
      <td style="text-align: right">0.498</td>
      <td style="text-align: right">~$154</td>
    </tr>
    <tr>
      <td>GPT-5.2</td>
      <td style="text-align: right">0.423</td>
      <td style="text-align: right">~$13</td>
    </tr>
    <tr>
      <td>GPT-4o</td>
      <td style="text-align: right">0.421</td>
      <td style="text-align: right">~$58</td>
    </tr>
    <tr>
      <td>nightwing-14b (my fine-tune)</td>
      <td style="text-align: right">0.291</td>
      <td style="text-align: right">~$0</td>
    </tr>
  </tbody>
</table>

<p>Red band. Twenty-seven points below the best frontier model. If I were selling
you a fine-tuning success story, this essay would end here and you should close
the tab.</p>

<p>Look at the two GPT rows before you scroll on. GPT-4o scores 0.421. GPT-5.2, a
full model generation later, scores 0.423. Two thousandths of a point of
improvement across a generation of frontier progress, on a task worth billions to
the legal industry. Hold that thought.</p>

<p>The per-category table is where it gets interesting.</p>

<h2 id="where-the-40-model-beat-all-three-frontier-models">Where the $40 model beat all three frontier models</h2>

<table>
  <thead>
    <tr>
      <th>Category</th>
      <th style="text-align: right">nightwing-14b</th>
      <th style="text-align: right">Best frontier</th>
      <th style="text-align: right">GPT-5.2</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Agreement Date</td>
      <td style="text-align: right"><strong>0.687</strong></td>
      <td style="text-align: right">0.168</td>
      <td style="text-align: right">0.054</td>
    </tr>
    <tr>
      <td>Effective Date</td>
      <td style="text-align: right"><strong>0.369</strong></td>
      <td style="text-align: right">0.105</td>
      <td style="text-align: right">0.070</td>
    </tr>
  </tbody>
</table>

<p>Not close wins. On Agreement Date the fine-tune scored four times the best
frontier model, and twelve times GPT-5.2. Against GPT-5.2 specifically, the
specialist won 11 of 40 categories outright, including Document Name (0.711 vs
0.471) and Third Party Beneficiary (0.585 vs 0.355).</p>

<h2 id="the-gpt-52-scoreline">The GPT-5.2 scoreline</h2>

<p>Category-by-category against GPT-5.2 alone, the $40 model went 11 for 40:</p>

<table>
  <thead>
    <tr>
      <th>Category</th>
      <th style="text-align: right">nightwing-14b</th>
      <th style="text-align: right">GPT-5.2</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Agreement Date</td>
      <td style="text-align: right"><strong>0.687</strong></td>
      <td style="text-align: right">0.054</td>
    </tr>
    <tr>
      <td>Effective Date</td>
      <td style="text-align: right"><strong>0.369</strong></td>
      <td style="text-align: right">0.070</td>
    </tr>
    <tr>
      <td>Document Name</td>
      <td style="text-align: right"><strong>0.711</strong></td>
      <td style="text-align: right">0.471</td>
    </tr>
    <tr>
      <td>Third Party Beneficiary</td>
      <td style="text-align: right"><strong>0.585</strong></td>
      <td style="text-align: right">0.355</td>
    </tr>
    <tr>
      <td>Expiration Date</td>
      <td style="text-align: right"><strong>0.633</strong></td>
      <td style="text-align: right">0.589</td>
    </tr>
  </tbody>
</table>

<p>(Plus six narrower wins; GPT-5.2 still takes the overall score, 0.423 to 0.291.)
More than a quarter of the categories, against a frontier model, for the price
of a nice dinner. And the same is true of GPT-4o: 11 of 40 categories again,
with even larger margins (Agreement Date 0.687 vs 0.079, Third Party Beneficiary
0.585 vs 0.102). The gap between “frontier” and “specialist” is not a wall. It is
category-shaped.</p>

<p>Now back to that thought I asked you to hold. GPT-4o and GPT-5.2, a generation
apart, land two thousandths of a point apart on this task. A full cycle of
frontier scaling moved the needle essentially zero. That is the tell: contract
clause extraction is not bottlenecked by reasoning power that more scale would
supply. It is bottlenecked by knowing where a lawyer would draw the span, which
is a convention you learn from examples, not a capability you scale into. That is
exactly the seam a $40 specialist reaches through.</p>

<p><strong>Why dates, of all things?</strong> Because CUAD scores by span overlap against what
human annotators marked, and date questions are annotation-convention questions.
When you ask for an agreement date, where does the “correct” span start and end?
Just the date? The whole sentence? The clause naming the parties and the date?
Frontier models answer correctly in the colloquial sense and still lose, because
they draw different boundaries than the annotators did. The fine-tune never had
to guess. It learned the convention from 368 contracts of examples. No amount of
frontier-model scale fixes this, because the information simply is not in the
prompt.</p>

<p>And where did the specialist collapse? The categories that need actual reasoning
over meaning. Identifying all parties to a contract: 0.247 vs Claude’s 0.954.
Source code escrow, a rare clause it barely saw in training: 0.100 vs 0.800.
The pattern across all 40 categories is clean. Convention-driven fields favor
the cheap specialist. Semantics-driven fields favor scale.</p>

<h2 id="what-this-means-if-you-buy-llm-systems">What this means if you buy LLM systems</h2>

<p>The interesting number is not 0.291. It is the shape of the table. A model that
costs nothing per run beat three frontier models simultaneously on the fields
where learned convention matters, and lost everywhere reasoning matters. Nobody
running “which model should we use” evaluations at the whole-benchmark level
would ever see this. Category-level measurement is the whole game. This is the
same lesson as <a href="https://github.com/ashish24142/signoff"><code class="language-plaintext highlighter-rouge">signoff</code></a>, from the
other direction: aggregate scores hide exactly the behavior differences you are
accountable for.</p>

<p>There is also a warning here about benchmark readings in vendor decks. My RED
result and my two knockouts come from the same table. Pick your rows carefully
enough and either story sells.</p>

<h2 id="whats-next">What’s next</h2>

<p>The 27-point gap is probably not a capacity limit. A 0.9B-parameter extractive
model from the original CUAD paper scores about 0.47 published, nearly GPT-5.2
territory, at one-fifteenth the size of my model. The difference is task
framing: it points at spans instead of generating them. Version two of this
experiment puts an extractive head on the 14B backbone. Pre-registered
prediction, written down before spending: it clears 0.50. The repo, the run
journal with every failure along the way, and all predictions are public at
<a href="https://github.com/ashish24142/nightwing">github.com/ashish24142/nightwing</a>.</p>

<p><em>Update, July 18: version two ran. The prediction missed, and the framing
change still gained ten points. The full result is in
<a href="/one-change-ten-points/">the sequel</a>.</em></p>

<hr />

<p><em>I’m <a href="https://www.linkedin.com/in/ashish-kr-singh-ml/">Ashish Kumar Singh</a>, an
engineering leader building enterprise LLM systems, and I still write the code.
The nightwing code and every prediction file are on
<a href="https://github.com/ashish24142/nightwing">GitHub</a>; the trained model is on
<a href="https://huggingface.co/ashish24142/nightwing-14b-cuad-extractive">Hugging Face</a>;
the paper is at <a href="https://doi.org/10.5281/zenodo.21530247">doi.org/10.5281/zenodo.21530247</a>.</em></p>

<p><em>Views my own. Not legal advice, and the model isn’t a lawyer either.</em></p>]]></content><author><name>Ashish Kumar Singh</name></author><summary type="html"><![CDATA[A $40 fine-tuned Qwen3-14B vs Claude Opus 4.8, Claude Opus 4.6, GPT-5.2 and GPT-4o on CUAD contract clause extraction, scored on the official metric. It loses to Claude overall and wins 11 of 40 categories against both GPT models. GPT-4o and GPT-5.2 tie, a full model generation apart. Every prediction is public and re-scorable.]]></summary></entry><entry><title type="html">Is the new model safe to deploy? A sign-off framework for LLM migrations</title><link href="https://migrationassurance.com/llm-migration-signoff-framework/" rel="alternate" type="text/html" title="Is the new model safe to deploy? A sign-off framework for LLM migrations" /><published>2026-07-11T00:00:00+00:00</published><updated>2026-07-11T00:00:00+00:00</updated><id>https://migrationassurance.com/llm-migration-signoff-framework</id><content type="html" xml:base="https://migrationassurance.com/llm-migration-signoff-framework/"><![CDATA[<p>The email every AI platform team eventually gets arrived on a Tuesday: the model
our document-extraction system had been built on was being deprecated. We had a
few months to move a fleet of production prompts to the successor model. These
prompts extract facts from legal and healthcare documents, where a wrong answer
isn’t an inconvenience. It’s a liability.</p>

<p>The obvious question from leadership was simple: <strong>“Is the new model safe to deploy?”</strong></p>

<p>The honest answer, at that moment, was: <em>“We don’t know, and we don’t even have a
way of knowing.”</em> This essay is about the framework we built to answer that
question properly, and why the standard approach (run a few examples, eyeball
the outputs, ship it) would have quietly hurt us.</p>

<h2 id="why-eyeballing-fails">Why eyeballing fails</h2>

<p>Our system extracts dozens of attributes from documents: governing law,
effective dates, party names, monetary amounts, renewal terms. A migration
“spot check” looks like this: run twenty documents through the new model, diff
the answers, see mostly agreement, call it a day.</p>

<p>Three problems.</p>

<p><strong>First, the sample is too small to see rare regressions.</strong> If the new model breaks
one attribute on a few percent of documents, twenty samples will miss it more often
than not. And a few percent of a production document flow is a lot of wrong
answers with a compliance label on them.</p>

<p><strong>Second, “the same” is not a well-defined concept for LLM output.</strong> Is
<code class="language-plaintext highlighter-rouge">$250,000,000</code> the same as <code class="language-plaintext highlighter-rouge">250000000.00 USD</code>? Is <code class="language-plaintext highlighter-rouge">State of New York</code> the same
as <code class="language-plaintext highlighter-rouge">New York</code>? Naive exact-match scoring will tell you the new model “failed” on
a large share of cases that any human reviewer would score as identical. Scoring
is a pipeline, not a string comparison.</p>

<p><strong>Third, and this is the one almost everyone misses: the old model doesn’t agree
with itself.</strong> Run the <em>same</em> prompt on the <em>same</em> document with the <em>same</em> model
five times, and on some attributes you get different answers on different runs.
On our worst prompts, repeated runs disagreed with each other not occasionally
but routinely. It’s the kind of instability UAT teams file as “the system changed
its mind on refresh.” A single-run comparison between old and new models is
therefore partly measuring <em>luck</em>. You cannot tell a regression from a coin flip.</p>

<p>That third problem has a name, nondeterminism, and it reshaped the whole
validation design. Before you can compare two models, you have to measure how
unstable each one is on its own.</p>

<h2 id="the-framework-n-runs-layered-matching-and-a-statistical-verdict">The framework: N runs, layered matching, and a statistical verdict</h2>

<p>Here is the shape of what we ended up building. None of it is exotic; the value
is in the discipline of doing all of it, every time.</p>

<p><strong>1. A frozen validation suite.</strong> Real (anonymized) documents with ground-truth
answers for every attribute, agreed with the business owners <em>before</em> migration
testing started. If you negotiate what “correct” means after you’ve seen the new
model’s outputs, you will negotiate your way into shipping it.</p>

<p><strong>2. N runs per prompt per model, not one.</strong> We ran every case multiple times on
the old model and the new. From those runs, two numbers per case:</p>

<ul>
  <li>the <strong>majority answer</strong>, what the model “really” says;</li>
  <li>the <strong>flip rate</strong>, how often runs disagree with that majority.</li>
</ul>

<p>The flip rate is the diagnostic gold. A case that changed answers between models
but is unstable on the <em>old</em> model too isn’t a migration regression. It’s a
prompt that was never reliable to begin with. Our “regressions” bucket shrank
substantially once we could separate the two, and the unstable-prompt bucket
became its own workstream (fix the prompt, not the model).</p>

<p><strong>3. Layered matching instead of exact match.</strong> Each answer is scored through
escalating layers: exact match first, then normalized (case, whitespace,
punctuation), then numeric equivalence (<code class="language-plaintext highlighter-rouge">$1,000</code> vs <code class="language-plaintext highlighter-rouge">1000</code>), and finally a
semantic or LLM-judge comparison for the genuinely fuzzy cases. Every match
records <em>which layer</em> passed, and the judge layer is used sparingly and audited
by humans on a sample, because “a model said the model is right” is not, by
itself, evidence.</p>

<p><strong>4. A paired statistical test, not a vibes delta.</strong> Suppose the new model scores
94% on your suite and the old one 93%. Improvement, or noise? With per-case
paired results you can run an exact sign test on the discordant pairs and get a
p-value instead of an argument. Our rule: the migration <strong>fails</strong> sign-off only
if the new model is worse <em>and</em> the regression is statistically significant.
Everything else is either a pass or a named, bounded exception.</p>

<p><strong>5. A report a risk committee can read.</strong> The deliverable was never a Jupyter
notebook. It was a short report: verdict up front, per-attribute table (accuracy
old/new, flip rates, match layers used), the list of every regressed case with
its documents, and the methodology in an appendix. The methodology section is
what turns “the team says it’s fine” into <em>evidence</em>, the thing you can hand an
auditor when they ask, a year later, why you approved the swap.</p>

<h2 id="what-the-framework-changes">What the framework changes</h2>

<p>Once it was in place, the migration verdict came back genuinely mixed, which is
exactly why the framework matters:</p>

<ul>
  <li>Most attributes cleared cleanly: new model equal or better, sign-off unambiguous.</li>
  <li>A meaningful share of <em>apparent</em> regressions turned out to be instability, with
high flip rates on both models. Those became prompt fixes, not migration blockers.</li>
  <li>And a small number of real, statistically significant regressions were caught
before production. Those are the cases an eyeball check would have shipped.</li>
</ul>

<p>That last bucket is the entire ROI of the framework. One real regression caught
before deployment pays for the whole validation effort. In domains like ours,
several times over.</p>

<h2 id="the-tooling-open-sourced">The tooling, open-sourced</h2>

<p>I’ve generalized the core of this framework into <strong><a href="https://github.com/ashish24142/signoff">signoff</a></strong>,
an open-source CLI that runs a YAML-defined suite on two models, N runs each,
scores through layered matching, computes flip rates and the sign test, and emits
an HTML + JSON report. It’s vendor-neutral by design: it judges an OpenAI to
Anthropic migration with exactly the same rigor as the reverse.</p>

<p>If you’re facing a forced migration this year (and with current model
deprecation schedules, you probably are), the checklist version of this essay is:</p>

<ol>
  <li>Freeze a ground-truth suite before you start.</li>
  <li>Run everything N times; measure flip rates before you interpret diffs.</li>
  <li>Score with layered matching; log which layer matched.</li>
  <li>Demand statistical significance before calling something a regression, or a win.</li>
  <li>Write the report for the person who has to defend the decision, not the
person who ran the notebook.</li>
</ol>

<p>“Is the new model safe to deploy?” deserves a better answer than “probably.”
It deserves a sign-off.</p>

<hr />

<p><em>I’m <a href="https://www.linkedin.com/in/ashish-kr-singh-ml/">Ashish Kumar Singh</a>, an engineering
leader building enterprise LLM systems (contract intelligence today, a decade of clinical AI
before that), and I still write the code. Migration validation, prompt regression testing, and
document-extraction evals are my daily work. This site is about migration assurance;
<a href="https://github.com/ashish24142/signoff">signoff</a> is its toolkit. All views are my own,
drawn from personal experiments and open-source work, never from client or employer data.</em></p>]]></content><author><name>Ashish Kumar Singh</name></author><summary type="html"><![CDATA[A statistical sign-off framework for LLM model migrations: N-run A/B equivalence testing, flip-rate measurement, and an audit-ready report, built after migrating production document-extraction prompts.]]></summary></entry></feed>