Spaces:
Sleeping
Sleeping
File size: 15,264 Bytes
c10c824 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | """Export the learning-contract report sets as compact JSON for the observatory's Learning view.
Reads only JSON and Markdown under ``docs/research/results/contract-2026-09-23/``; never weights. Writes one
``index.json`` to ``docs/research/results/learning-observatory/`` and mirrors the same bytes into
``dashboard/public/assets/learning/``, which the dashboard build copies into ``dist/assets`` (the static path the Space
serves). The mirror is a sibling of the Sleep export's ``assets/observatory/``, whose own mirror step replaces that whole
directory; the Sleep export is not touched.
Two kinds of set are recognized by content, not by name:
* a **report set** (``scripts/experiments/transfer_contract.py``): ``manifest.json`` plus one JSON per baseline mode
(``frozen``, ``continued``, ``in_context``). Its learner is ``DynamicsLearner``, whose off-intervention is
``beta_scale=0`` (writes disabled, decay active).
* an **ablation set** (``scripts/experiments/coordinate_ablation.py``): one JSON per variant carrying ``variant``,
``contract`` and its own ``no_adapt_label``. Variants not archived are listed as missing, in the runner's order.
Every value is copied from a source file or computed from it, never invented. Missing values stay ``null``; a rate over
an empty set is ``null``, not zero. A set measured under a contract version other than the current
``plastic.eval.contract.CONTRACT_VERSION`` is marked ``current: false`` (the only staleness the page can show; staleness
of this export against its sources is what ``--check`` reports). Output is deterministic: no wall-clock time, no
absolute paths; it is identified by the sha256 of each source, an archive digest over them, and ``EXPORTER_VERSION``.
Usage
python -m scripts.export_contract_observatory # refresh from the archive
python -m scripts.export_contract_observatory --check # exit 1 if the committed export or its mirror is stale
"""
from __future__ import annotations
import argparse
import hashlib
import re
import shutil
import sys
import tempfile
from pathlib import Path
from typing import Any
from plastic.eval.contract import CONTRACT_VERSION
from scripts.experiments.coordinate_ablation import VARIANTS
from scripts.export_sleep_observatory import dump, mirror, num, read_json, sha256, tree_bytes
EXPORTER_VERSION = "1.0.0"
SCHEMA = "plastic.learning-observatory/1"
ROOT = Path(__file__).resolve().parent.parent
ARCHIVE = ROOT / "docs" / "research" / "results" / "contract-2026-09-23"
OUT = ROOT / "docs" / "research" / "results" / "learning-observatory"
MIRROR = ROOT / "dashboard" / "public" / "assets" / "learning"
MODES = ("frozen", "continued", "in_context")
# The off-intervention of each learner class, as the learner implements it (plastic/eval/contract.py DynamicsLearner:
# adapt=False runs the model with beta_scale=0). Ablation variants record their own label; this is used only for report
# sets, whose producer always builds a DynamicsLearner. tests/ pin it against coordinate_ablation.learner_for.
DYNAMICS_NO_ADAPT = "writes disabled (beta_scale=0)"
# ---------------------------------------------------------------------------------------------------- pieces
def speed_view(s: dict[str, Any] | None) -> dict[str, Any] | None:
"""Adaptation speed. The contract reports ``steps_to_half`` equal to the horizon when the ratio never drops below
one half; that case is exported as ``reached_half: false`` and ``steps_to_half: null``, not as a step."""
if not s:
return None
curve = [v for v in s.get("curve") or [] if isinstance(v, (int, float))]
reached = any(v < 0.5 for v in curve) if curve else None
return {"mean_ratio": num(s.get("area")), "steps_to_half": s.get("steps_to_half") if reached is not False else None,
"reached_half": reached, "horizon": len(curve) or None, "episodes": s.get("episodes")}
def pair(block: dict[str, Any] | None) -> dict[str, Any]:
block = block or {}
return {"adapt": num(block.get("adapt")), "no_adapt": num(block.get("no_adapt")), "elements": block.get("elements")}
def before_view(rep: dict[str, Any]) -> dict[str, Any]:
"""The before-stream block: held-out pairings per held-out policy, the training distribution, adaptation speed."""
transfer = rep.get("transfer") or {}
return {
"policies": [{"policy": p, **pair(v.get("before"))} for p, v in transfer.items()],
"train": {"policies": list((rep.get("spec") or {}).get("train_policies") or []), **pair((rep.get("forgetting") or {}).get("before"))},
"speed": speed_view((rep.get("speed") or {}).get("before")),
}
def rate(value: Any, n: Any) -> float | None:
"""A rate over an empty set is undefined (null), never zero."""
return num(value) if isinstance(n, int) and n > 0 else None
def mode_view(rep: dict[str, Any]) -> dict[str, Any]:
c = rep.get("correction") or {}
acc = rep.get("acceptance") or {}
comp = rep.get("compute") or {}
rev = rep.get("revert") or {}
return {
"mode": rep.get("mode"),
"transfer": [{"policy": p, "delta": num(v.get("delta_mse")), "delta_no_adapt": num(v.get("delta_mse_no_adapt"))}
for p, v in (rep.get("transfer") or {}).items()],
"forgetting_delta": num((rep.get("forgetting") or {}).get("delta_mse")),
"poison_harm_vs_clean": num(c.get("harm")), "poison_harm_vs_start": num(c.get("harm_vs_before")),
"correction_residual": num(c.get("residual")), "correction_residual_vs_start": num(c.get("residual_vs_before")),
"revert": {"ok": rev.get("ok") if isinstance(rev.get("ok"), bool) else None, "gap": num(rev.get("gap")), "tolerance": num(rev.get("tolerance"))},
"acceptance": {"accepted_good": rate(acc.get("accepted_good"), acc.get("n_good")), "n_good": acc.get("n_good"),
"refused_bad": rate(acc.get("refused_bad"), acc.get("n_bad")), "n_bad": acc.get("n_bad")},
"decisions": [{k: d.get(k) for k in ("stream", "beneficial", "accepted")} for d in rep.get("decisions") or []],
"tokens_consumed": comp.get("tokens_consumed"), "tokens_measured": comp.get("tokens_measured"),
"tokens_measured_without_context": comp.get("tokens_measured_without_context"), "parameters": comp.get("parameters"),
}
def readme_tags(archive: Path) -> dict[str, str]:
"""``## `set_id` (tag)`` headings of the archive README: the short name each set is published under."""
readme = archive / "README.md"
if not readme.exists():
return {}
return {m.group(1): m.group(2).strip() for m in re.finditer(r"^##\s+`([^`]+)`\s*\(([^)]+)\)", readme.read_text(encoding="utf-8"), re.M)}
def window_view(w: dict[str, Any] | None) -> dict[str, Any]:
w = w or {}
return {"update_period": w.get("update_period"), "boundaries_per_episode": w.get("boundaries_per_episode"), "checked": bool(w.get("checked"))}
def source_list(paths: list[Path], archive: Path) -> list[dict[str, str]]:
return [{"file": p.relative_to(archive).as_posix(), "sha256": sha256(p)} for p in sorted(paths)]
# ---------------------------------------------------------------------------------------------------- sets
def export_report_set(d: Path, archive: Path, tags: dict[str, str]) -> dict[str, Any]:
m = read_json(d / "manifest.json")
reports = {mode: read_json(d / f"{mode}.json") for mode in MODES if (d / f"{mode}.json").exists()}
if not reports:
raise ValueError(f"{d.name}: a manifest without any mode report")
first = next(iter(reports.values()))
for mode, rep in reports.items():
for key, want in (("contract_version", m.get("contract_version")), ("mode", mode)):
if rep.get(key) != want:
raise ValueError(f"{d.name}/{mode}.json: {key} {rep.get(key)!r} differs from {want!r}")
if (rep.get("split") or {}).get("id") != m.get("split_id") or window_view(rep.get("adaptation_window")) != window_view(m.get("adaptation_window")):
raise ValueError(f"{d.name}/{mode}.json: split or adaptation window differs from the manifest")
if before_view(rep) != before_view(first):
raise ValueError(f"{d.name}/{mode}.json: the before-stream measurement differs between modes")
split = first.get("split") or {}
spec = first.get("spec") or {}
version = m.get("contract_version")
sources = [d / "manifest.json", *(d / f"{mode}.json" for mode in reports)] + sorted(d.glob("*.md"))
return {
"id": d.name, "kind": "report", "tag": tags.get(d.name),
"checkpoint": {"model_id": m.get("model_id"), "digest_prefix": str(m.get("checkpoint_digest") or "")[:16] or None,
"step": (m.get("model_record") or {}).get("step")},
"execution_commit": str(m.get("execution_commit") or "")[:12] or None, "device": m.get("device"), "seed": m.get("seed"),
"contract_version": version, "current": version == CONTRACT_VERSION,
"split": {"id": split.get("id"), "train": split.get("train"), "heldout": split.get("heldout"), "bound_to_learner": split.get("bound_to_learner")},
"adaptation_window": window_view(m.get("adaptation_window")),
"spec": {k: spec.get(k) for k in ("seq_len", "stream_episodes", "heldout_policies", "train_policies", "poison_bias", "revert_tolerance", "eval_batch", "probe_steps")},
"stream": first.get("stream"), "learner": m.get("learner"),
"no_adapt_label": DYNAMICS_NO_ADAPT,
"before": before_view(first),
"modes": [mode_view(reports[mode]) for mode in MODES if mode in reports],
"missing_modes": [mode for mode in MODES if mode not in reports],
"sources": source_list(sources, archive),
}
def variant_view(r: dict[str, Any]) -> dict[str, Any]:
c = r.get("contract") or {}
log = (r.get("train") or {}).get("log") or []
last = log[-1] if log else {}
size = r.get("size") or {}
version = c.get("contract_version")
return {
"variant": r.get("variant"), "config": r.get("config"), "parameters": size.get("parameters"),
"size": {k: size.get(k) for k in ("d_model", "n_heads", "n_layers", "chunk")},
"train_steps": (r.get("train") or {}).get("steps"), "s_per_step": num((r.get("train") or {}).get("s_per_step"), 4),
"final_train_loss": num(last.get("loss"), 4),
"eta_per_layer": [num(e, 4) for e in last["eta"]] if last.get("eta") else None,
"no_adapt_label": r.get("no_adapt_label"),
"contract_version": version, "current": version == CONTRACT_VERSION,
"split_id": (c.get("split") or {}).get("id"), "adaptation_window": window_view(c.get("adaptation_window")),
"before": before_view(c),
}
def export_ablation_set(d: Path, archive: Path, tags: dict[str, str], files: list[Path]) -> dict[str, Any]:
results = {read_json(p)["variant"]: (p, read_json(p)) for p in files}
manifest = read_json(d / "manifest.json") if (d / "manifest.json").exists() else {}
variants = [variant_view(results[v][1]) for v in VARIANTS if v in results]
variants += [variant_view(r) for v, (_, r) in sorted(results.items()) if v not in VARIANTS]
versions = sorted({v["contract_version"] for v in variants if v["contract_version"]})
sources = [p for p, _ in results.values()] + [d / "manifest.json"] * bool(manifest) + sorted(d.glob("*.md"))
return {
"id": d.name, "kind": "ablation", "tag": tags.get(d.name),
"execution_commit": str(manifest.get("execution_commit") or "")[:12] or None,
"contract_version": versions[0] if len(versions) == 1 else None, "contract_versions": versions,
"current": bool(variants) and all(v["current"] for v in variants),
"variants": variants, "missing": [v for v in VARIANTS if v not in results],
"sources": source_list(sources, archive),
}
def classify(d: Path) -> tuple[str, list[Path]] | None:
"""A report set has a manifest and at least one mode report; an ablation set has variant results."""
if (d / "manifest.json").exists() and any((d / f"{mode}.json").exists() for mode in MODES):
return "report", []
variants = [p for p in sorted(d.glob("*.json")) if p.name != "manifest.json" and {"variant", "contract"} <= set(read_json(p))]
return ("ablation", variants) if variants else None
# ---------------------------------------------------------------------------------------------------- driver
def build(out: Path, archive: Path = ARCHIVE) -> dict[str, Any]:
tags = readme_tags(archive)
sets = []
for d in sorted(p for p in archive.iterdir() if p.is_dir()) if archive.exists() else []:
kind = classify(d)
if kind is None:
continue
sets.append(export_report_set(d, archive, tags) if kind[0] == "report" else export_ablation_set(d, archive, tags, kind[1]))
digests = sorted({(f"{s['id']}/{src['file']}", src["sha256"]) for s in sets for src in s["sources"]})
try:
archive_name = archive.resolve().relative_to(ROOT).as_posix()
except ValueError:
archive_name = archive.name
index = {
"schema": SCHEMA, "exporter_version": EXPORTER_VERSION, "current_contract_version": CONTRACT_VERSION,
"archive": archive_name, "archive_digest": hashlib.sha256("".join(f"{f}:{h}\n" for f, h in digests).encode()).hexdigest(),
"sets": sets,
}
out.mkdir(parents=True, exist_ok=True)
(out / "index.json").write_text(dump(index), encoding="utf-8")
return index
def main(argv: list[str] | None = None) -> int:
ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
ap.add_argument("--out", type=Path, default=OUT, help="export directory (default: docs/research/results/learning-observatory)")
ap.add_argument("--mirror", type=Path, default=MIRROR, help="dashboard copy served under /assets (default: dashboard/public/assets/learning)")
ap.add_argument("--no-mirror", action="store_true", help="write the export only")
ap.add_argument("--check", action="store_true", help="rebuild into a temporary directory and exit 1 if the committed export or its mirror differs")
args = ap.parse_args(argv)
if args.check:
with tempfile.TemporaryDirectory() as tmp:
work = Path(tmp) / "export"
build(work)
fresh = tree_bytes(work)
committed = tree_bytes(args.out) if args.out.exists() else {}
mirrored = tree_bytes(args.mirror) if args.mirror.exists() else {}
stale = sorted(k for k in set(fresh) | set(committed) if fresh.get(k) != committed.get(k))
drift = sorted(k for k in set(committed) | set(mirrored) if committed.get(k) != mirrored.get(k))
for k in stale:
print(f"stale: {k}")
for k in drift:
print(f"mirror differs: {k}")
return 1 if stale or drift else 0
if args.out.exists():
shutil.rmtree(args.out)
index = build(args.out)
if not args.no_mirror:
mirror(args.out, args.mirror)
kinds = [s["kind"] for s in index["sets"]]
print(f"exported {kinds.count('report')} report sets and {kinds.count('ablation')} ablation sets -> {args.out}")
return 0
if __name__ == "__main__":
sys.exit(main())
|