AI/ML Explained: Same Name, Different Thing — Model Versioning & Eval Reproducibility

Day 56 · 2026-08-30
For: engineers outside AI · Level: methodology / frontier

Referential Drift & Model VersioningReferential Drift

measurementversioning
One-line analogy

A model name is Docker's :latest tag, not a Git commit SHA. It is a mutable pointer, not a content-addressed immutable reference. The "o3" you read about in a paper, the "o3" written into your SLA, and the "o3" the API actually serves six months later can be three different piles of weights. Nobody on your team would use :latest as a rollback baseline — yet the whole industry uses model names as benchmark identity.

The problem it exposes + how it works

Pain point: the minimum requirement for science is referability — "what is the object under study" must be uniquely determinable. Yet benchmark scores, paper conclusions, cost models and SLAs all identify that object with a single string. Once the mapping from name to actual system can change, every conclusion pinned to that name fails silently: no error, no version conflict, just a score that mysteriously drops a few points one day.

Mechanically, one "model name" sits on top of a whole mutable stack, and every layer changes observable behavior:

The mutable stack under a single name (any layer changes → behavior changes, name doesn't)

base checkpoint→distilled / quantized variant
↓
hidden system prompt→safety filter / refusal layer
↓
test-time compute budget→which replica you route to→the output you see

You can only observe the rightmost box; the name anchors only the leftmost one, and loosely at that

This is not hypothetical. In December 2024 ARC Prize verified an unreleased o3 preview (high configuration, very large test-time compute) at 87.5% on ARC-AGI-1. The o3 that actually shipped in 2025 scored roughly 41% at the low setting and 53% at medium on the same benchmark. OpenAI clarified that the production o3 is not the same model as that preview (production integrates multimodal capability, and the compute budget the preview used is not available in the product). Same name, thirty-plus points apart — the cleanest specimen of referential drift we have.

The other direction is codename drift: the internal codename "Orion" was widely expected to ship as GPT-5, and was ultimately released in February 2025 as GPT-4.5. There is not even a stable correspondence between rumored codename performance and measured product-name performance.

Code
import anthropic, hashlib, json
client = anthropic.Anthropic()          # needs ANTHROPIC_API_KEY

REQUESTED = "claude-sonnet-4-5"            # the name you request: a mutable pointer
resp = client.messages.create(
    model=REQUESTED, max_tokens=64, temperature=0,
    messages=[{"role": "user", "content": "Reply OK"}],
)
# Key: the response carries the model id the server actually used. Log that,
# not the string you asked for.
print("requested:", REQUESTED, "| served:", resp.model)

# Fingerprint every eval run: if any dimension changed, old scores are not comparable
run = {"served_model": resp.model, "temperature": 0,
       "max_tokens": 64, "prompt_tmpl": "eval-v3", "harness": "lm-eval@0.4"}
fp = hashlib.sha256(json.dumps(run, sort_keys=True).encode()).hexdigest()[:12]
print("run_fingerprint:", fp)   # use this as the primary key, not the model name
Common misconception + where it applies
"I pinned the model name, so my experiment is reproducible" — no. Pinning a name pins the request, not the service. Use dated/versioned snapshot ids where the API offers them, log the served model id from the response, and alert when the two disagree. Open weights are scientifically harder precisely because they are content-addressed: one checkpoint hash uniquely determines the object under study.
📌 BigCat scenario: whenever your workflow shows the "it was fine last week, now the style changed" symptom, don't start by doubting your prompt — diff the logged resp.model first. Treat it exactly like dependency version drift in a distributed system: pin snapshots, record fingerprints, keep a baseline sample set for regression.
Takeaway + question
💡 A model name is a mutable pointer; a reproducible experiment must be anchored to a content fingerprint, not a string.
🤔 How many of your judgments currently hang on a name that can change? If the weights behind it were swapped tonight, how long until you noticed?

Scaffolding as a ConfounderScaffolding Confound

experiment designcounterintuitive
One-line analogy

Imagine someone hands you a chart reading "MySQL: 18,000 QPS" with no mention of indexes, connection pool, cache hit rate, or hardware. You'd say immediately: that isn't a property of the database, it's a property of the whole system. Agent benchmark scores work the same way — the score belongs to the (model × scaffold × sampling budget) joint system, while the leaderboard shows you a single column: the model name.

The problem it exposes + how it works

Pain point: the leaderboard's data structure is itself misleading. It presents as a map from model name to score, implying the score is an intrinsic property of the model. But on agentic benchmarks (SWE-bench, terminal-style tasks) the model is just a function being called repeatedly; the scaffold around it — which files get retrieved, whether tests are run, how many retries on failure, how candidate patches are ranked — contributes a large share of the score.

The most important confound is collapsing many attempts into one number. Let p be the success rate of a single attempt. If the scaffold allows k independent attempts and can filter the right one using regression tests, the success rate becomes:

pass@k = 1 − (1 − p)k

Intuition: (1−p) is the probability of one failure, so k failures in a row is that raised to the k-th power, and subtracting from 1 gives "succeeds at least once." The consequence is that as long as p>0, the score climbs monotonically with budget — at p=0.3, pass@1≈0.30 but pass@10≈0.97. A leaderboard that reports a score without k has hidden a freely tunable independent variable.

How large is the effect? Anthropic's own published numbers: Claude 3.7 Sonnet scores 62.3% on SWE-bench Verified, and 70.3% with a custom scaffold. Same model, same benchmark, same day — eight points, enough to flip any two adjacent leaderboard ranks.

SWE-bench Verified · Claude 3.7 Sonnet · the only thing that changed is the scaffold

standard 62.3%
custom scaffold 70.3%

score=model×scaffold×budget k×scoring protocol
The leaderboard labels only the first term
Code
import numpy as np

def pass_at_k(n, c, k):
    """Unbiased pass@k estimator (the standard HumanEval formulation):
    c correct out of n samples -> 1 - C(n-c,k)/C(n,k), as a product to avoid overflow."""
    if n - c < k:
        return 1.0          # not enough wrong samples to fill k failures
    return 1.0 - np.prod(1.0 - k / np.arange(n - c + 1, n + 1))

# Same model, same task: single-attempt success fixed at 30% (30 correct out of 100).
# The only thing that varies is how many attempts the scaffold allows.
for k in (1, 2, 5, 10, 40):
    print(f"pass@{k:<2} = {pass_at_k(n=100, c=30, k=k):.3f}")

# pass@1 =0.300 / pass@5 =0.839 / pass@40=1.000
# Model capability did not change by a single bit; only the budget did.
# Reporting a score without k is reporting nothing.
Common misconception + where it applies
"A beats B by 3 points on the leaderboard, so A is stronger" — on agentic benchmarks this carries almost no information, because A and B often ran under different scaffolds and different sampling budgets. Comparing models requires controlling the variables: same harness, same k, same scoring protocol. Otherwise you are comparing two teams' engineering, not two models' capability.
📌 BigCat scenario: when you build a private eval set for your own agent, put the scaffold version and sampling budget into the primary key of your results table. Otherwise, three months later you'll see a rising curve and be unable to tell whether you swapped the model, edited the prompt, or simply bumped retries from 1 to 3.
Takeaway + question
💡 Agent benchmarks measure the "model × scaffold" joint system; a score without the scaffold and k is like a performance benchmark without the hardware.
🤔 If a score can be pushed up monotonically by raising k, at which budget level should "capability" even be defined?
📎 Engineering counterpart → super-individual D6: Eval Engineering

The Harness Is the BenchmarkImplementation as Definition

reproducibilityscoring protocol
One-line analogy

Two teams both say "I implemented JSON parsing," but one accepts trailing commas and the other doesn't — whatever the spec leaves open, the implementation defines. Benchmarks are the same: "MMLU" is not a dataset, it is a four-tuple of dataset + prompt template + scoring method + answer extraction. Change any one of them and the same model reports a score several points apart, with neither side lying.

The problem it exposes + how it works

Pain point: you try to reproduce someone's number, land 4 points off, and spend a week suspecting your environment. The real cause is usually in the scoring protocol, while the paper only said "we evaluated on MMLU." EleutherAI's Lessons from the Trenches on Reproducible Evaluation of Language Models (arXiv 2405.14782) lays this out: model scores are highly sensitive to evaluation setup, which makes cross-method comparison very hard to justify. Their conclusion is that what can genuinely be shared is not a score but the complete evaluation code and configuration — precisely why a harness like lm-eval exists.

Between the dataset and a single number sit five gates, each of which changes the result:

dataset→prompt template
wording / chat format
→few-shot
count / order
→scoring method
generate vs compare logprob
→extraction
regex / normalization
→score

Papers usually publish only the first box and the last one

The most overlooked gate is the scoring method. Multiple choice has two mainstream paths: (a) let the model generate, then regex out "A/B/C/D"; (b) skip generation and compare log P(option | stem) across the four options, taking the max. Path (b) has a further fork — whether to normalize by token count:

scoreraw = Σ log P(ti)   vs   scorenorm = (1/T) Σ log P(ti)

Intuition: every token's log probability is negative, so the longer the option, the more negative the sum. Without normalization you systematically favor short options; dividing by token count T turns it into "how natural per token on average," which flips the bias toward long, fluent options. Both are defensible, and both pick different answers — so "the same MMLU" has two scores, and the gap is often larger than the gap between adjacent leaderboard ranks.

Code
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
tok = AutoTokenizer.from_pretrained("gpt2")
m = AutoModelForCausalLM.from_pretrained("gpt2").eval()

def score(ctx, cont):
    """Return (raw log-likelihood sum, length-normalized mean) for a candidate."""
    ids = tok(ctx + cont, return_tensors="pt").input_ids
    n_ctx = len(tok(ctx).input_ids)
    with torch.no_grad():
        lp = torch.log_softmax(m(ids).logits[0, :-1], dim=-1)
    tgt = ids[0, 1:]                          # actual next token at each position
    s = lp[range(len(tgt)), tgt][n_ctx - 1:]   # keep only the candidate's tokens
    return s.sum().item(), s.mean().item()

q = "The capital of France is"
for c in [" Paris", " the beautiful old city of Paris"]:
    raw, norm = score(q, c)
    print(f"{c!r:34} raw={raw:8.2f}  len-norm={norm:6.2f}")
# raw favors the short option, len-norm favors the long one: ranking can invert.
# One model, one question, two equally "correct" protocols -> two benchmark scores.
Common misconception + where it applies
"A benchmark is a dataset, so anyone running it gets the same thing" — wrong. A benchmark is dataset + protocol, and the protocol lives in code. "We used lm-eval v0.4.x, task mmlu, 5-shot, default template" is a reproducible statement; "we scored X on MMLU" is not. Before reproducing someone's number, ask which harness and which version.
📌 BigCat scenario: manage the eval harness as a dependency — pin the version, put it in the lockfile, archive the config alongside results. Same discipline as handling "the same SQL got a different plan under a new optimizer version": the root cause of incomparable results is usually not the data, it's the execution engine.
Takeaway + question
💡 Scores don't travel, configs do: the smallest reproducible unit you can share is "harness version + full config," not the number.
🤔 If a benchmark score only means something under a given implementation, what exactly are we comparing in those cross-paper, cross-year "progress curves"?

Construct Validity & Error BarsValidity + Uncertainty

measurement theorystatistics
One-line analogy

Using "unit test pass rate" as a proxy for "code quality" hits two independent problems: first, it measures the proxy, not the thing (tests can be weak, and can be gamed by feeding them the answer); second, it is a finite sample and therefore has variance (20 tests and 2,000 tests carry very different confidence). Benchmark scores take both hits identically.

The problem it exposes + how it works

Problem one: construct validity — whether what you measure is what you think you're measuring. Raji et al. 2021, AI and the Everything in the Whole Wide World Benchmark (arXiv 2111.15366), argue systematically that the benchmarks treated as milestones of "general capability" involve fundamentally arbitrary task selection, so reading their scores as progress toward general intelligence has a validity problem at the root.

Two hard pieces of evidence. Contamination: the Scale AI team rebuilt a fresh set of problems matched to GSM8k in distribution and difficulty, called GSM1k (arXiv 2405.00332), and found drops of up to 13 percentage points for some model families, with signs of systematic overfitting (while several frontier families barely moved) — meaning the original score mixed "memorized" with "can do." Weak adjudication: SWE-Bench+ (arXiv 2410.06992) audited patches judged "resolved" on SWE-bench and found roughly 32.7% of passing samples had the solution written directly in the issue or its comments (solution leakage), and about another 31.1% were suspicious because the tests were too weak to verify correctness. A substantial part of that number is not measuring bug-fixing ability.

Problem two: statistical uncertainty. Miller 2024, Adding Error Bars to Evals (arXiv 2411.00640), makes a plain point that is widely ignored: an evaluation is an experiment — the questions are a sample drawn from an unseen super-population, so the score is an estimator and needs error bars. With n questions and accuracy p, the standard error of the mean is:

SE ≈ √( p(1−p) / n )

Intuition: the numerator p(1−p) is the variance of a single Bernoulli right/wrong outcome (largest at p=0.5, smaller as p approaches 0 or 1); dividing by n and taking the square root is "more questions shrink the noise as √n." Plug in n=500, p=0.70: SE≈2.0%, so the 95% confidence interval is roughly ±4 percentage points. The leaderboard gap between 70.3% and 68.9% is therefore usually not significant at 500 questions. And that's the optimistic case — questions from the same repository or template are correlated (clustered), so real variance exceeds the independence assumption.

At n=500, two models "1.4 points apart" have nearly overlapping 95% CIs

model A 66 ├┤ 74  point estimate 70.3%
model B 65 ├┤ 73  point estimate 68.9%

→ Ranking higher ≠ being better. Fix: paired comparison (same question, both models) cancels shared difficulty
Code
import numpy as np
rng = np.random.default_rng(0)
n = 500                                # number of benchmark questions
a = rng.random(n) < 0.703              # model A per-question outcome (true rate 70.3%)
b = rng.random(n) < 0.689              # model B (true rate 68.9%)

def se(x):                             # standard error of the mean = sd / sqrt(n)
    return x.std(ddof=1) / np.sqrt(len(x))

print(f"A = {a.mean():.3f} ± {1.96*se(a):.3f}")   # 95% CI is about ±0.04
print(f"B = {b.mean():.3f} ± {1.96*se(b):.3f}")

# Paired difference: compare A and B on the same question, cancelling the shared
# noise source of "how hard this question is"
d = a.astype(float) - b.astype(float)
print(f"A-B = {d.mean():+.3f} ± {1.96*se(d):.3f}")
# Interval straddles 0 -> not significant. Pairing needs fewer questions than
# independent comparison, so it should be the default for a private eval.
Common misconception + where it applies
"A one-point leaderboard gap is a real difference" — on a few-hundred-question benchmark, one point is usually buried in noise. The converse deserves care too: a flat score does not mean no regression, because an aggregate averages away the degradation on the subset you care about. Beyond the total, always look at your slice.
📌 BigCat scenario: when building a private eval set, prefer 150 questions drawn from your real tasks plus paired comparison over 1,500 generic ones. The former has high construct validity (it really measures your use case) and the paired design suppresses variance; the latter produces a pretty number that carries no information for your decision.
Takeaway + question
💡 For a score to be trusted it must clear three gates at once: it measures the right thing (validity), it isn't contaminated (clean), and the gap exceeds the noise (significant).
🤔 If you added error bars to the "model A is better" judgment you currently rely on, how much of it would survive?
📎 Engineering counterpart → super-individual D6: Eval Engineering

Further ReadingFurther Reading

Deep QuestionsDeep Questions

1. Card 1 says names drift; card 3 says the implementation is the definition. Stacked together, how much information is left in "model A scores X on benchmark B"?
On the surface that sentence is a binary function f(model, benchmark). In reality it is f(weight snapshot, inference config, scaffold, sampling budget, harness version, scoring protocol, extraction rule) — seven independent variables, of which the sentence pins two, and those two are themselves mutable pointers. So it amounts to "one observation of an unspecified system under an unspecified protocol." Not zero information: the order of magnitude still holds (70% vs 20% cannot be explained away by these degrees of freedom), but it cannot support fine-grained ranking — which is exactly what leaderboards are used for. The fix is isomorphic to handling an unreliable external dependency: make hidden variables explicit (log fingerprints, pin versions, report k), and internalize fine-grained comparison (trust only the comparisons you ran on your own fixed harness).
2. Card 2 says a score can be pushed up monotonically by raising the sampling budget k. Can "model capability" be defined independently of budget at all?
Strictly, no — only relative to a capability–cost curve: inference budget (k, or dollars) on the x-axis, success rate on the y-axis, with a single score being one sample on that curve. Different models have different curve shapes: some have low pass@1 but climb steeply with k (diverse generations that a filter can rescue), others have high pass@1 and saturate fast (confidently wrong, so extra attempts don't help). Two such models can tie at some k while suiting opposite situations. This connects back to Day 54's generation–verification gap: the value of k depends entirely on whether you have a verifier stronger than your generator. Without one, raising k just gives you more candidates you can't tell apart — the pass@k curve is not redeemable in practice. Reporting a single-point score not only drops the budget dimension, it silently assumes a free and perfect verifier.
3. Open weights are content-addressed (one hash fixes one model); closed APIs are not. Does that mean only research on open models counts as science?
Too strong a conclusion, but the direction is right. Reproducibility has tiers: open weights give full reproducibility ("same object, same environment"); a closed API gives at best conditional reproducibility ("same snapshot id, same parameters") — conditional on the vendor not having silently swapped the underlying service, which you cannot verify and can only trust. So experiments on closed models resemble observational science (astronomy: the object is outside your control, so you record observation conditions, repeat observations, and report drift) rather than controlled experiments. That demands a different discipline: stamp the observation date, keep raw outputs, and maintain a runnable gold-standard regression set to detect drift. The genuinely dangerous move is writing observational results in the voice of controlled experiments — turning "GPT-X has property P" into a permanent fact when it was the behavior of one service endpoint within one time window.
4. Can the two problems in card 4 (validity and variance) both be solved by making benchmarks bigger and harder?
Only half, and it introduces new problems. Raising n suppresses variance (SE falls as √n) but does nothing for validity — measuring the wrong thing ten thousand times just makes you precisely wrong. The two are also in tension: getting more questions means loosening collection standards or generating them automatically, which increases contamination risk and lets weak adjudication slip in (both the GSM1k and SWE-Bench+ findings point here). "Harder" is no cure either: harder usually means more niche and more skewed, taking the set further from your real use case and lowering validity. Validity is improved by a different class of work: rebuilding fresh distribution-matched problems, hardening adjudication, and stating explicitly what the benchmark claims to measure and what it does not. So handle them separately — statistical design for variance, measurement theory for validity. Conflating them yields an industry that drives noise down to a tenth of a point while still measuring something nobody wanted measured.
5. Putting all four cards together: what kind of model-evaluation conclusion is allowed into a decision document?
A four-item checklist; miss one and it drops to "reference information" rather than "conclusion." (a) Reference: the object under study must be uniquely determinable — log the versioned snapshot id and the served model id from the response, stamp the observation date, and record the checkpoint hash for open models (card 1). (b) Joint system: report the score together with scaffold version, sampling budget k, and whether filtering/reranking was used, and hold all three equal when comparing (card 2). (c) Protocol: archive the harness version, prompt template, few-shot configuration and extraction rules with the results, and put the config file itself under version control (card 3). (d) Statistics and validity: point estimates come with 95% confidence intervals, comparisons prefer paired differences, and you state what the set claims to measure and whether contamination was checked (card 4). Plus one meta-rule: discount every external leaderboard number by one tier before it enters a decision document — it tells you the order of magnitude at most; fine-grained ranking must be reproduced on your own eval stack. The essence matches distributed-systems discipline: an untrusted external dependency isn't unusable, it just has to have its uncertainty bounds declared explicitly, with an observable baseline maintained on your side.