| |
| """Re-run Q25/dense PPL and controlled semantic confirmation.""" |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| from collections import Counter |
| import json |
| import math |
| from pathlib import Path |
| import sys |
|
|
| import numpy as np |
| import torch |
|
|
|
|
| ROOT = Path(__file__).resolve().parent |
| sys.path.insert(0, str(ROOT / "source/src")) |
| sys.path.insert(0, str(ROOT / "source/scripts")) |
|
|
| import run_strata_headquotient_v1_1_frontier as frontier |
| from strata.eval.head_quotient import bucket_loss_sums, dense_bucket_loss_sums |
| from strata.eval.head_quotient_causal import ( |
| evaluate_scoped_variants, |
| prepare_causal_cases, |
| ) |
| from strata.experiments.compose_rf import load_dense_base |
| from strata.training.lm_data import PackedLMDataset |
|
|
|
|
| BUCKETS = ("0-2048", "2048-4096", "4096-8192") |
| LANGUAGES = ("en", "zh", "de", "es", "ar") |
|
|
|
|
| def parse_args() -> argparse.Namespace: |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument("--device", default="cuda:0" if torch.cuda.is_available() else "cpu") |
| parser.add_argument("--documents", type=int, default=470) |
| parser.add_argument("--semantic-examples", type=int, default=2000) |
| parser.add_argument("--bootstrap-samples", type=int, default=10000) |
| parser.add_argument("--output", type=Path, default=ROOT / "reproduced_evaluation.json") |
| return parser.parse_args() |
|
|
|
|
| def loss_row(row: dict[str, tuple[float, int]]) -> tuple[dict[str, float], float]: |
| buckets = {key: total / count for key, (total, count) in row.items()} |
| return buckets, sum(value[0] for value in row.values()) / sum( |
| value[1] for value in row.values() |
| ) |
|
|
|
|
| def interval(values: np.ndarray, samples: int, seed: int) -> dict[str, float | int]: |
| rng = np.random.default_rng(seed) |
| draws = np.empty(samples, dtype=np.float64) |
| for start in range(0, samples, 1000): |
| stop = min(samples, start + 1000) |
| indices = rng.integers(0, values.size, size=(stop - start, values.size)) |
| draws[start:stop] = values[indices].mean(axis=1) |
| lower, upper = np.quantile(draws, (0.025, 0.975)) |
| mean = float(values.mean()) |
| return { |
| "documents": int(values.size), |
| "nll_difference": mean, |
| "ppl_ratio": math.exp(mean), |
| "ppl_ratio_lower": math.exp(float(lower)), |
| "ppl_ratio_upper": math.exp(float(upper)), |
| } |
|
|
|
|
| def main() -> None: |
| args = parse_args() |
| if not 1 <= args.documents <= 470: |
| raise ValueError("--documents must be between 1 and 470") |
| if not 1 <= args.semantic_examples <= 2000: |
| raise ValueError("--semantic-examples must be between 1 and 2000") |
| device = torch.device(args.device) |
| if device.type == "cuda": |
| torch.cuda.set_device(device) |
| torch.manual_seed(20260721) |
|
|
| config = json.loads((ROOT / "config/headquotient.json").read_text(encoding="utf-8")) |
| config["dense_checkpoint"] = str(ROOT / "checkpoints/dense_model.pt") |
| config["model_config"] = "config/model.json" |
| config["heldout_corpus"] = str(ROOT / "evaluation/heldout-5lang-1m") |
| plan = json.loads((ROOT / "selection/Q25/plan.json").read_text(encoding="utf-8")) |
| scoped = json.loads((ROOT / "scoped_adapter/result.json").read_text(encoding="utf-8")) |
| scoped["adapter"]["path"] = str(ROOT / "scoped_adapter/adapter.pt") |
| frontier.ROOT = ROOT |
|
|
| dense, _ = load_dense_base( |
| ROOT / "config/model.json", ROOT / "checkpoints/dense_model.pt", device |
| ) |
| dense.eval() |
| model, _model_config, _selected, _graph_groups, _coverage, _compaction = ( |
| frontier.construct_export(config, plan, scoped, device) |
| ) |
| model.load_state_dict( |
| torch.load(ROOT / "checkpoints/q25_export.pt", map_location=device, weights_only=True), |
| strict=True, |
| ) |
| model.eval() |
|
|
| with np.load(ROOT / "evaluation/q25_470_documents.npz") as bundle: |
| tokens = bundle["tokens"][: args.documents] |
| languages = bundle["languages"][: args.documents] |
| rows = [] |
| with torch.inference_mode(): |
| for index, values in enumerate(tokens): |
| ids = torch.from_numpy(values.astype(np.int64)).unsqueeze(0).to(device) |
| dense_buckets, dense_all = loss_row(dense_bucket_loss_sums(dense, ids)) |
| q25_buckets, q25_all = loss_row(bucket_loss_sums(model, ids)) |
| rows.append({ |
| "language": str(languages[index]), |
| "difference": q25_all - dense_all, |
| "buckets": { |
| key: q25_buckets[key] - dense_buckets[key] for key in BUCKETS |
| }, |
| }) |
| if (index + 1) % 10 == 0: |
| print(json.dumps({"evaluated_documents": index + 1}), flush=True) |
|
|
| differences = np.asarray([row["difference"] for row in rows], dtype=np.float64) |
| overall = interval(differences, args.bootstrap_samples, 20260722) |
| position = { |
| bucket: interval( |
| np.asarray([row["buckets"][bucket] for row in rows]), |
| args.bootstrap_samples, |
| 20260822 + offset, |
| ) |
| for offset, bucket in enumerate(BUCKETS) |
| } |
| language = {} |
| for offset, name in enumerate(LANGUAGES): |
| values = np.asarray([ |
| row["difference"] for row in rows if row["language"] == name |
| ], dtype=np.float64) |
| if values.size: |
| language[name] = interval(values, args.bootstrap_samples, 20260922 + offset) |
|
|
| semantic_dataset = PackedLMDataset(config["heldout_corpus"], seq_len=64) |
| semantic_cases = prepare_causal_cases( |
| model, semantic_dataset, args.semantic_examples, device, start=8192 |
| ) |
| semantic, _margins, null_exact = evaluate_scoped_variants( |
| model, semantic_cases, batch_size=32 |
| ) |
| payload = { |
| "documents": len(rows), |
| "languages": dict(Counter(str(item) for item in languages[: len(rows)])), |
| "ppl": overall, |
| "position_buckets": position, |
| "per_language": language, |
| "semantic_examples": args.semantic_examples, |
| "semantic": semantic, |
| "null_controls_exact": null_exact, |
| "noninferiority_pass": float(overall["ppl_ratio_upper"]) < 1.03, |
| } |
| args.output.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") |
| print(json.dumps(payload, indent=2, sort_keys=True)) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|