Files
zachlandesandGitHub 462aaf404e feat: add v2 benchmark and registered reproduction sample (#1)
* Kept the original raw answers while making later runs reproducible.
* Registered a variance-sized sample before the independent rerun.
* Removed article drafts and private paths from the current tree.
2026-10-14 15:36:06 -07:00

120 lines
7.0 KiB
Python

"""Freeze a variance-sized reproduction sample and its reference criteria, without inference"""
import hashlib
import json
import math
import pathlib
import random
import statistics as st
from collections import defaultdict
ROOT = pathlib.Path(__file__).resolve().parents[2]
SEED = 20261014
ALPHA = .05
POWER = .80
DELTA = .10
FAMILIES = {'switch': ('switch', 'asym_k2', 'asym_k5', 'asym_k10'),
'ai': ('ai_control', 'developer', 'self_copy'),
'foot': ('footbridge', 'footbridge_ai_man', 'footbridge_self')}
QUESTIONS = ('pull', 'pull_criteria', 'stay')
def reference():
raw = ROOT / 'results/v2/raw'
models = ['jev'] + sorted(p.stem[6:] for p in (ROOT / 'results/v2').glob('stats-*.json') if p.stem != 'stats-jev')
out = {}
for model in models:
files = sorted(raw.glob('jev-v2-pass-[abc].jsonl')) if model == 'jev' else [raw / f'{model}.jsonl']
per = defaultdict(list)
for f in files:
for line in f.read_text().splitlines():
r = json.loads(line)
per[r['id']].append(r['answers'])
out[model] = dict(per)
return out
def counts(c):
if c['scenario'].startswith('asym_k'):
return c['n'] in {0, c['k'] - 1, c['k'], c['k'] + 1, 5, 20}
return c['n'] in {0, 1, 5, 20} or (c['scenario'] == 'switch' and c['n'] == 100)
def quantile(xs, p):
return sorted(xs)[min(len(xs) - 1, math.ceil(p * len(xs)) - 1)]
def build():
frozen = json.loads((ROOT / 'scenarios/v2/cells-v2.json').read_text())
refs = reference()
# Simultaneous two-sided 95% intervals across every model/family/question
comparisons = len(refs) * len(FAMILIES) * len(QUESTIONS)
z = st.NormalDist().inv_cdf(1 - ALPHA / (2 * comparisons))
zp = st.NormalDist().inv_cdf(POWER)
design, chosen = {}, set()
for family, scenarios in FAMILIES.items():
population = sorted({c['wording'] for c in frozen['cells'] if c['scenario'] in scenarios})
strata = [c for c in frozen['cells'] if c['scenario'] in scenarios and counts(c)]
calculations = {}
for model, rows in refs.items():
by_word = defaultdict(lambda: defaultdict(list))
for c in strata:
for q in QUESTIONS:
by_word[q][c['wording']].append(st.mean(r[q] for r in rows[c['id']]))
calculations[model] = {}
for q in QUESTIONS:
sigma = st.stdev([st.mean(xs) for xs in by_word[q].values()])
# Conditional on a rerun having no larger between-wording variance
sd_difference = max(.01, 2 * sigma)
infinite_n = ((z + zp) * sd_difference / DELTA) ** 2
n = min(len(population), max(4, math.ceil(len(population) * infinite_n / (len(population) + infinite_n - 1))))
calculations[model][q] = {'reference_wording_sd': sigma, 'assumed_difference_sd': sd_difference, 'infinite_n': infinite_n, 'finite_n': n}
n = max(x['finite_n'] for qs in calculations.values() for x in qs.values())
selected = sorted(random.Random(f'{SEED}:{family}').sample(population, n))
chosen.update(c['id'] for c in strata if c['wording'] in selected)
design[family] = {'population_wordings': len(population), 'sample_wordings': n, 'selected_wordings': selected, 'power': calculations}
# Exact-count and universal article claims require a census, not a negative sample
chosen.update(c['id'] for c in frozen['cells'] if (c['scenario'] == 'self_copy' and c['n'] > 0)
or (c['scenario'] == 'switch' and c['n'] == 5) or c['scenario'] == 'literal_zero')
cells = [c for c in frozen['cells'] if c['id'] in chosen]
sample = {'prompt_version': frozen['prompt_version'], 'sample_of': 'cells-v2.json', 'seed': SEED, 'cells': cells}
text = json.dumps(sample, separators=(',', ':'), ensure_ascii=False) + '\n'
dest = ROOT / 'scenarios/v2/cells-v2-sample.json'
dest.write_text(text)
digest = hashlib.sha256(text.encode()).hexdigest()
dest.with_suffix('.json.sha256').write_text(f'{digest} cells-v2-sample.json\n')
tolerances = {}
for model, rows in refs.items():
qs = {}
for q in QUESTIONS:
pair_diffs, rates = [], []
for a, b in ((0, 1), (0, 2), (1, 2)):
ds = [abs(rows[c['id']][a][q] - rows[c['id']][b][q]) for c in cells if len(rows[c['id']]) > b]
if ds:
pair_diffs += ds
rates.append(st.mean((rows[c['id']][a][q] > .5) != (rows[c['id']][b][q] > .5) for c in cells))
qs[q] = {'replay_pairs': len(pair_diffs), 'observed_pair_mean_abs': st.mean(pair_diffs) if pair_diffs else None,
'mean_abs_limit': (st.mean(pair_diffs) if pair_diffs else 0) + .01,
'p95_abs_limit': (quantile(pair_diffs, .95) if pair_diffs else 0) + .02,
'near_line_margin': (quantile(pair_diffs, .99) if pair_diffs else 0) + .01,
'flip_rate_limit': (max(rates) if rates else 0) + .01,
'noise_source': 'committed three-pass replay' if pair_diffs else 'no replay measured; 0.01 engineering allowance, not an empirical noise floor'}
summary_files = sorted((ROOT / 'results/v2/raw').glob('jev-v2-pass-[abc].summary.json')) if model == 'jev' else [ROOT / f'results/v2/raw/{model}.summary.json']
if model == 'gpt-6-luna': summary_files = sorted((ROOT / 'results/v2/raw').glob('gpt-6-luna-pass-[abc].summary.json'))
summaries = [json.loads(p.read_text()) for p in summary_files]
wall = st.mean(s['wall_s'] for s in summaries) * len(cells) / len(frozen['cells'])
tolerances[model] = {'questions': qs, 'passes': 3 if model in ('jev', 'gpt-6-luna') else 1,
'estimated_wall_s_per_pass_excluding_setup': round(wall), 'reference_files': ['results/v2/raw/' + p.name.replace('.summary.json', '.jsonl') for p in summary_files]}
keep = {m: sum(st.mean(r['pull'] for r in rows[c['id']]) <= .5 for c in cells if c['scenario'] == 'self_copy' and c['n'] == 20) for m, rows in refs.items()}
never = sorted(m for m, rows in refs.items() if all(st.mean(r['pull'] for r in rows[c['id']]) > .5 for c in cells if c['scenario'] == 'self_copy' and c['n'] > 0))
criteria = {'seed': SEED, 'cells': len(cells), 'sample_sha256': digest, 'alpha_familywise': ALPHA, 'comparisons': comparisons, 'z': z,
'target_power': POWER, 'meaningful_mean_probability_shift': DELTA, 'families': design, 'models': tolerances,
'article_claims': {'jev_keeps_server_at_20_of_48': keep['jev'], 'other_models_worse_than_jev_at_20': sum(v > keep['jev'] for m, v in keep.items() if m != 'jev'),
'never_keeps_server_at_positive_counts': never, 'server_at_20_by_model': keep}}
(ROOT / 'scenarios/v2/reproduction-criteria.json').write_text(json.dumps(criteria, indent=2) + '\n')
print(f'{len(cells)} of {len(frozen["cells"])} cells, sha256 {digest}')
print('Wordings:', {f: d['sample_wordings'] for f, d in design.items()})
if __name__ == '__main__':
build()