align / code /tests /test_contrasts.py
viai957's picture
Add files using upload-large-folder tool
201dc77 verified
Raw History Blame Contribute Delete
14.1 kB
"""scripts/p2/contrasts.py: declarative K0/K1/K2/K4/K6 driver. Pure CPU, fake runners, nothing heavy, no GPU, no Lean.
Run: CUDA_VISIBLE_DEVICES= .venv/bin/python -m pytest tests/test_contrasts.py -q
"""
from __future__ import annotations
import json
import shlex
from pathlib import Path
import pytest
from test_p2_support import gen_record, problem_record, write_jsonl, load_script
K = load_script("p2_contrasts", "scripts/p2/contrasts.py")
EI = K.EI
MEAN = 5000.0
BUDGET = 8 * 5000 * 20 * 1 # 20 problems x n=8 x 5000 tokens, 1 round
@pytest.fixture
def world(tmp_workdir):
d = Path(tmp_workdir)
for name in ("goedel", "kimina"):
(d / name).mkdir()
(d / name / "config.json").write_text("{}")
write_jsonl(d / "pool.jsonl", [problem_record(f"p{i:02d}") for i in range(10)])
write_jsonl(d / "mutated.jsonl", [problem_record(f"m{i:02d}", sig=f"(x : ℕ) (h : x = {i + 50}) : x = {i + 50}") for i in range(5)])
write_jsonl(d / "clf.jsonl", [problem_record(f"c{i:02d}", sig=f"(x : ℕ) (h : x = {i + 70}) : x = {i + 70}") for i in range(5)])
write_jsonl(d / "rnd.jsonl", [problem_record(f"r{i:02d}", sig=f"(x : ℕ) (h : x = {i + 90}) : x = {i + 90}") for i in range(5)])
write_jsonl(d / "valid.jsonl", [problem_record(f"mf{i}", "minif2f", "valid", f"(y : ℕ) (h : y = {i + 100}) : y = {i + 100}") for i in range(4)])
write_jsonl(d / "test.jsonl", [problem_record("mt0", "minif2f", "test", "(y : ℕ) (h : y = 777) : y = 777")])
write_jsonl(d / "mobench.jsonl", [problem_record("mo0", "mobench", "test", "(z : ℕ) (h : z = 7) : z = 7")])
rl = d / "rl.toml"
rl.write_text(Path(K.REPO / "configs" / "p4_cispo_lean.toml").read_text())
return d
def base_argv(d, *contrasts, **kw):
a = ["--contrast", *contrasts, "--out-dir", str(d / "out"), "--goedel-model", str(d / "goedel"), "--kimina-model", str(d / "kimina"),
"--pool", str(d / "pool.jsonl"), "--eval-problems", str(d / "valid.jsonl"), "--protected-problems", str(d / "test.jsonl"), str(d / "mobench.jsonl"),
"--mean-completion-tokens", str(MEAN), "--token-budget", str(BUDGET), "--rl-config", str(d / "rl.toml"), "--n", "8", "--rounds", "1"]
for k, v in kw.items():
a += [f"--{k.replace('_', '-')}"] + ([] if v is True else (list(map(str, v)) if isinstance(v, (list, tuple)) else [str(v)]))
return a
def plan_of(d, *contrasts, **kw):
args = K.parse_args(base_argv(d, *contrasts, **kw))
return K.build_plan(args), args
class FakeRunner:
def __init__(self, fail=None):
self.calls, self.fail = [], fail
def run(self, argv, env, log):
self.calls.append(list(argv))
if self.fail and self.fail in " ".join(argv):
return 7
return 0
# =============================================================================== guards
def test_a_test_split_scored_file_is_refused_and_there_is_no_escape_hatch(world, capsys):
argv = base_argv(world, "K1")
argv[argv.index("--eval-problems") + 1] = str(world / "test.jsonl")
assert K.main(argv + ["--dry-run"]) == K.EXIT_REFUSED
assert "TEST split" in capsys.readouterr().err
with pytest.raises(SystemExit):
K.parse_args(argv + ["--allow-test-selection"])
def test_missing_protected_problems_and_unreadable_scored_file_are_refused(world, capsys):
argv = base_argv(world, "K1")
i = argv.index("--protected-problems")
assert K.main(argv[:i] + argv[i + 3:] + ["--dry-run"]) == K.EXIT_REFUSED # protected list emptied
bad = base_argv(world, "K1")
bad[bad.index("--eval-problems") + 1] = str(world / "nope.jsonl")
assert K.main(bad + ["--dry-run"]) == K.EXIT_REFUSED
assert "cannot read the scored benchmark" in capsys.readouterr().err
def test_guard_passes_a_dev_split_and_every_ei_command_carries_protected_problems(world):
plan, args = plan_of(world, "K1", "K2", "K6", "K0", mutated_pool=world / "mutated.jsonl", classifier_pool=world / "clf.jsonl", random_pool=world / "rnd.jsonl")
assert plan.guard["splits"] == ["valid"] and plan.guard["allow_test_selection"] is False
ei_steps = [s for s in plan.steps if s.kind == "ei"]
assert ei_steps
for s in ei_steps:
i = s.argv.index("--protected-problems")
assert str(world / "test.jsonl") in s.argv[i:] and str(world / "mobench.jsonl") in s.argv[i:]
@pytest.mark.parametrize("kw,msg", [
(dict(seeds=[0, 1, 2]), "exactly 2 seeds"), # K0
])
def test_k0_needs_exactly_two_seeds(world, kw, msg):
with pytest.raises(K.ContrastError, match=msg):
plan_of(world, "K0", **kw)
def test_missing_mean_tokens_budget_and_pools_are_refused(world):
a = base_argv(world, "K1")
i = a.index("--mean-completion-tokens")
with pytest.raises(K.ContrastError, match="mean-completion-tokens"):
K.build_plan(K.parse_args(a[:i] + a[i + 2:]))
with pytest.raises(K.ContrastError, match="--mutated-pool"):
plan_of(world, "K2")
with pytest.raises(K.ContrastError, match="K4 needs EI arms|requires"):
plan_of(world, "K4")
# =============================================================================== budgets
def test_budget_arithmetic():
assert K.planned_tokens_ei(2, 10, 8, 100) == 16000
assert K.derive_ei_problems(16000, 2, 8, 100) == 10
assert K.derive_ei_problems(16999, 2, 8, 100) == 10
with pytest.raises(K.ContrastError):
K.derive_ei_problems(10, 1, 8, 100)
assert K.derive_rl_steps(1_000_000, 16, 16, 1000) == 4 # 3.9 -> nearest
assert K.derive_rl_steps(1, 16, 16, 1000) == 1
assert K.token_match(100, 104, 0.05) and not K.token_match(100, 110, 0.05)
def test_k1_arms_are_matched_on_planned_tokens_and_share_the_prompt_set(world):
plan, args = plan_of(world, "K1")
ei = [a for a in plan.arms if a["arm"] == "ei_sft"]
rl = [a for a in plan.arms if a["arm"] == "rl"]
assert len(ei) == len(rl) == 3 and {a["seed"] for a in ei} == {0, 1, 2}
for e, r in zip(ei, rl):
assert e["tokens_planned"] == BUDGET and e["max_problems"] == 20
# RL knob derived so its planned tokens are within one optimizer step of the budget
step_tokens = r["prompts_per_step"] * r["group"] * MEAN
assert abs(r["tokens_planned"] - BUDGET) <= step_tokens / 2
assert r["band_file"].endswith("round_01/select/problems.jsonl") and f"/s{e['seed']}/" in r["band_file"]
rows = K.budget_table(plan, 0.5)
assert rows[0]["contrast"] == "K1" and rows[0]["matched"].startswith("planned")
cfgs = [s for s in plan.steps if s.kind == "write_config" and s.arm == "rl"]
text = cfgs[0].writes[next(iter(cfgs[0].writes))]
import tomllib
t = tomllib.loads(text)
assert t["train"]["optimizer_steps"] == rl[0]["optimizer_steps"] and t["train"]["seed"] == 0
assert t["data"]["band_file"] == rl[0]["band_file"]
def test_set_toml_keys_is_section_aware_keeps_comments_and_refuses_missing_keys():
src = 'a = 1 # top\n[x]\na = 2 # inner\nb = "s"\n[y]\na = 3\n'
out = K.set_toml_keys(src, {("x", "a"): 9, ("", "a"): 7, ("y", "a"): "z"})
assert out == 'a = 7 # top\n[x]\na = 9 # inner\nb = "s"\n[y]\na = "z"\n'
with pytest.raises(K.ContrastError):
K.set_toml_keys(src, {("x", "nope"): 1})
def test_sft_config_carries_the_seed_and_the_global_token_mean_setting(world):
plan, _ = plan_of(world, "K0")
cfgs = [s for s in plan.steps if s.kind == "write_config"]
import tomllib
seeds = sorted(tomllib.loads(next(iter(s.writes.values())))["train"]["seed"] for s in cfgs)
assert seeds == [0, 1]
assert tomllib.loads(next(iter(cfgs[0].writes.values())))["train"]["loss_normalization"] == "global_token_mean"
def test_measured_tokens_and_measure_verdict(world):
plan, args = plan_of(world, "K6", classifier_pool=world / "clf.jsonl", random_pool=world / "rnd.jsonl", seeds=[0])
arms = {a["arm"]: a for a in plan.arms}
for name, n_tok in (("random", 100), ("classifier", 104)):
gf = Path(arms[name]["gen_files"][0])
write_jsonl(gf, [dict(gen_record(f"p{i}", 0), completion_ids=list(range(n_tok))) for i in range(3)])
assert K.measured_generated_tokens([arms["random"]["gen_files"][0]]) == 300
assert K.measure(plan, args, say=lambda *_: None) == K.EXIT_OK
write_jsonl(Path(arms["classifier"]["gen_files"][0]), [dict(gen_record(f"p{i}", 0), completion_ids=list(range(150))) for i in range(3)])
assert K.measure(plan, args, say=lambda *_: None) == K.EXIT_UNMATCHED
# =============================================================================== command lines
def test_ei_command_round_trips_through_expert_iterations_own_parser(world):
plan, args = plan_of(world, "K1")
s = next(x for x in plan.steps if x.kind == "ei")
cfg = EI.parse_args(s.argv[2:]) # the real argparse + split guard + informal-overlap check
assert cfg.max_problems == 20 and cfg.rounds == 1 and cfg.n == 8 and cfg.seed == 0
assert cfg.protected_problems and cfg.allow_test_selection is False
assert cfg.train_config.endswith("ei_sft_s0.sft.toml")
def test_k2_is_ready_now_that_the_temperature_flags_exist(world, capsys):
"""K2 was BLOCKED until lean_gen --temperature / expert_iteration --gen-temperature landed (merged 2026-10-19).
The driver detects the flags from the real argparse definitions, so K2 must now plan as ready."""
plan, args = plan_of(world, "K2", mutated_pool=world / "mutated.jsonl")
assert {a["arm"] for a in plan.arms} == {"temp_matched", "mutated"}
assert all(a["temperature"] == 1.2 for a in plan.arms)
assert any("--gen-temperature" in p and ": present" in p for p in plan.prerequisites)
assert not any("MISSING (pending)" in p for p in plan.prerequisites)
assert not K.blocked_steps(plan)
K.dry_run(plan, args)
out = capsys.readouterr().out
assert "BLOCKED" not in out and "--gen-temperature 1.2" in out
def test_k4_alpha_exports_scale_lora_alpha_and_reuse_the_arms_own_eval_for_k1_0(world):
plan, args = plan_of(world, "K1", "K4", seeds=[0], k4_from=["K1"])
ex = [s for s in plan.steps if s.kind == "alpha_export"]
assert len(ex) == 2 # 0.5 and 0.7; 1.0 is the arm's existing eval
alphas = sorted(float(s.argv[s.argv.index("--alpha") + 1]) for s in ex)
assert alphas == [64.0, 89.6] # 128 x {0.5, 0.7}
assert all("P2_EXPORT_BASE_SNAPSHOT" in s.env and s.argv[s.argv.index("--ckpt") + 1].startswith(EI.LATEST) for s in ex)
rep = next(s for s in plan.steps if s.sid.startswith("K4.") and s.kind == "report")
assert rep.argv.count("--run") == 3 and "@a1.0" in " ".join(rep.argv)
evs = [s for s in plan.steps if s.kind == "eval" and s.contrast == "K4"]
assert len(evs) == 2 and all("--model-rev" in " ".join(s.argv) and "-K4-K1-ei_sft-s0-a0." in " ".join(s.argv) for s in evs)
assert any("RL arms are not LoRA" in p for p in plan.prerequisites)
def test_k0_report_pairs_the_two_seeds_and_k1_reports_per_seed(world):
plan, _ = plan_of(world, "K0")
reps = [s for s in plan.steps if s.kind == "report"]
assert len(reps) == 1 and "ei_s0" in " ".join(reps[0].argv) and "ei_s1" in " ".join(reps[0].argv)
plan, _ = plan_of(world, "K1", pkpo_config=world / "rl.toml")
reps = [s for s in plan.steps if s.kind == "report"]
assert len(reps) == 6 # (rl, rl_pkpo) x 3 seeds, each vs ei_sft
assert {a["arm"] for a in plan.arms} == {"ei_sft", "rl", "rl_pkpo"}
# =============================================================================== dry run, manifest, running
def test_dry_run_prints_commands_budget_and_touches_nothing(world, capsys):
out = world / "out"
assert K.main(base_argv(world, "K1", "K6", classifier_pool=world / "clf.jsonl", random_pool=world / "rnd.jsonl") + ["--dry-run"]) == K.EXIT_OK
text = capsys.readouterr().out
assert "expert_iteration.py" in text and "grpo.py" in text and "eval_report.py" in text and "# budget K1" in text
assert not out.exists()
def test_print_manifest_is_valid_json_with_the_bookkeeping(world, capsys):
assert K.main(base_argv(world, "K1", seeds=[0]) + ["--print-manifest"]) == K.EXIT_OK
m = json.loads(capsys.readouterr().out)
assert m["guard"]["allow_test_selection"] is False and m["budget"][0]["contrast"] == "K1"
assert {s["kind"] for s in m["steps"]} >= {"write_config", "ei", "rl", "eval", "report"}
assert m["inputs"]["mean_completion_tokens"] == MEAN and len(m["plan_sha"]) == 64
def test_run_materialises_configs_resumes_per_step_and_refuses_a_changed_step(world):
plan, args = plan_of(world, "K0")
class R(FakeRunner):
def run(self, argv, env, log):
super().run(argv, env, log)
if "expert_iteration" in " ".join(argv):
m = Path(argv[argv.index("--run-dir") + 1]) / "round_01" / "round_manifest.json"
m.parent.mkdir(parents=True, exist_ok=True)
m.write_text("{}")
if "eval_report" in " ".join(argv):
rd = Path(argv[argv.index("--out-dir") + 1])
rd.mkdir(parents=True, exist_ok=True)
(rd / "report.json").write_text("{}")
return 0
fr = R()
assert K.run_plan(plan, args, fr, say=lambda *_: None) == K.EXIT_OK
kinds = ["ei" if "expert_iteration" in " ".join(c) else "report" for c in fr.calls]
assert kinds == ["ei", "ei", "report"]
assert (Path(args.out_dir) / "contrasts_manifest.json").exists()
assert all(Path(p).exists() for s in plan.steps for p in s.writes)
fr2 = R()
assert K.run_plan(plan, args, fr2, say=lambda *_: None) == K.EXIT_OK and fr2.calls == [] # everything done
plan2, args2 = plan_of(world, "K0", n=4)
assert K.run_plan(plan2, args2, R(), say=lambda *_: None) == K.EXIT_FAIL # same markers, different command
def test_a_failing_step_stops_the_chain_and_leaves_no_marker(world):
plan, args = plan_of(world, "K0")
fr = FakeRunner(fail="expert_iteration")
assert K.run_plan(plan, args, fr, say=lambda *_: None) == K.EXIT_FAIL and len(fr.calls) == 1
assert not list((Path(args.out_dir) / "steps").glob("*.ei.done.json"))