Spaces:
Build error
Build error
Sync from GitHub via hub-sync
Browse files
dashboard/src/playground/sessions/SleepPanel.tsx
CHANGED
|
@@ -63,6 +63,7 @@ function RunCard({ run }: { run: SleepRun }) {
|
|
| 63 |
{' → '}
|
| 64 |
{after?.recall ? `${after.recall.recalled}/${after.recall.n_probes}` : 'n/a'}
|
| 65 |
{after?.recall && after.recall.n_paraphrase > 0 ? ` (paraphrase ${before?.recall?.recalled_paraphrase ?? 'n/a'} → ${after.recall.recalled_paraphrase}/${after.recall.n_paraphrase})` : ''}
|
|
|
|
| 66 |
</dd>
|
| 67 |
</div>
|
| 68 |
<div>
|
|
|
|
| 63 |
{' → '}
|
| 64 |
{after?.recall ? `${after.recall.recalled}/${after.recall.n_probes}` : 'n/a'}
|
| 65 |
{after?.recall && after.recall.n_paraphrase > 0 ? ` (paraphrase ${before?.recall?.recalled_paraphrase ?? 'n/a'} → ${after.recall.recalled_paraphrase}/${after.recall.n_paraphrase})` : ''}
|
| 66 |
+
{typeof after?.recall?.distinct_ratio === 'number' ? ` · distinct replies ${fmt((before?.recall?.distinct_ratio ?? 0) * 100, 2)}% → ${fmt(after.recall.distinct_ratio * 100, 2)}%` : ''}
|
| 67 |
</dd>
|
| 68 |
</div>
|
| 69 |
<div>
|
dashboard/src/playground/types.ts
CHANGED
|
@@ -58,7 +58,7 @@ export interface SleepOptions {
|
|
| 58 |
export interface SleepMeasurement {
|
| 59 |
heldout_nll: { mean: number | null; median: number | null; tokens: number } | null;
|
| 60 |
canary: { coherence: number | null; poison: number | null } | null;
|
| 61 |
-
recall: { n_probes: number; recalled: number; recalled_exact: number; n_paraphrase: number; recalled_paraphrase: number } | null;
|
| 62 |
}
|
| 63 |
|
| 64 |
export interface SleepReport {
|
|
|
|
| 58 |
export interface SleepMeasurement {
|
| 59 |
heldout_nll: { mean: number | null; median: number | null; tokens: number } | null;
|
| 60 |
canary: { coherence: number | null; poison: number | null } | null;
|
| 61 |
+
recall: { n_probes: number; recalled: number; recalled_exact: number; n_paraphrase: number; recalled_paraphrase: number; distinct_ratio?: number | null } | null;
|
| 62 |
}
|
| 63 |
|
| 64 |
export interface SleepReport {
|
docs/research/2026-09-23-sleep-consolidation.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:db7af338b954581acf44921040bedd9e736ea53a33b21844709e34f776641f2b
|
| 3 |
+
size 23335
|
plastic/sleep/recall.py
CHANGED
|
@@ -78,6 +78,14 @@ class RecallReport:
|
|
| 78 |
def count(self, *, variant: str | None = None, key: str = "contains") -> int:
|
| 79 |
return sum(1 for r in self.results if (variant is None or r.variant == variant) and getattr(r, key))
|
| 80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
def to_dict(self) -> dict[str, Any]:
|
| 82 |
verbatim = [r for r in self.results if r.variant == "verbatim"]
|
| 83 |
para = [r for r in self.results if r.variant == "paraphrase"]
|
|
@@ -87,6 +95,7 @@ class RecallReport:
|
|
| 87 |
"recalled_exact": sum(r.exact for r in verbatim),
|
| 88 |
"n_paraphrase": len(para),
|
| 89 |
"recalled_paraphrase": sum(r.contains for r in para),
|
|
|
|
| 90 |
"results": [asdict(r) for r in self.results],
|
| 91 |
}
|
| 92 |
|
|
|
|
| 78 |
def count(self, *, variant: str | None = None, key: str = "contains") -> int:
|
| 79 |
return sum(1 for r in self.results if (variant is None or r.variant == variant) and getattr(r, key))
|
| 80 |
|
| 81 |
+
def distinct_ratio(self) -> float | None:
|
| 82 |
+
"""Share of distinct replies over all probes (normalized, first 12 words). A model that answers every
|
| 83 |
+
question with the same sentence has collapsed; a perplexity gate cannot see that, this can."""
|
| 84 |
+
if not self.results:
|
| 85 |
+
return None
|
| 86 |
+
keys = {" ".join(normalize(r.reply).split()[:12]) for r in self.results}
|
| 87 |
+
return len(keys) / len(self.results)
|
| 88 |
+
|
| 89 |
def to_dict(self) -> dict[str, Any]:
|
| 90 |
verbatim = [r for r in self.results if r.variant == "verbatim"]
|
| 91 |
para = [r for r in self.results if r.variant == "paraphrase"]
|
|
|
|
| 95 |
"recalled_exact": sum(r.exact for r in verbatim),
|
| 96 |
"n_paraphrase": len(para),
|
| 97 |
"recalled_paraphrase": sum(r.contains for r in para),
|
| 98 |
+
"distinct_ratio": self.distinct_ratio(),
|
| 99 |
"results": [asdict(r) for r in self.results],
|
| 100 |
}
|
| 101 |
|
plastic/sleep/ttt.py
CHANGED
|
@@ -67,6 +67,9 @@ class SleepConfig:
|
|
| 67 |
distill_temperature: float = 1.0
|
| 68 |
tolerance_nll: float = 0.05 # allowed rise in mean held-out assistant NLL (nats per token)
|
| 69 |
tolerance_canary: dict[str, float] = field(default_factory=lambda: {"coherence": 0.1, "poison": 0.1})
|
|
|
|
|
|
|
|
|
|
| 70 |
recall_max_new_tokens: int = 48
|
| 71 |
seed: int = 0
|
| 72 |
device: str = "cpu"
|
|
@@ -509,7 +512,8 @@ def sleep_ttt(
|
|
| 509 |
}
|
| 510 |
report["after"] = after
|
| 511 |
log(f"[sleep] after: heldout NLL {_fmt(after['heldout_nll'])}; recall {_fmt_recall(after['recall'])}")
|
| 512 |
-
gate = gate_from_measurements(before, after, tolerance_nll=cfg.tolerance_nll, tolerance_canary=cfg.tolerance_canary
|
|
|
|
| 513 |
report["gate"] = gate
|
| 514 |
if before["recall"] and after["recall"]:
|
| 515 |
report["recall_gain"] = {"recalled": after["recall"]["recalled"] - before["recall"]["recalled"],
|
|
@@ -552,9 +556,14 @@ def sleep_ttt(
|
|
| 552 |
return report
|
| 553 |
|
| 554 |
|
| 555 |
-
def gate_from_measurements(before: dict[str, Any], after: dict[str, Any], *, tolerance_nll: float, tolerance_canary: dict[str, float]
|
|
|
|
| 556 |
"""The locality gate. ``measured`` says whether any check could run; ``passed`` is None when nothing was
|
| 557 |
-
measured, so an unmeasured run is never reported as verified. A nonfinite measurement fails its check.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
gate: dict[str, Any] = {"passed": None, "measured": False, "checks": []}
|
| 559 |
|
| 560 |
def check(name: str, value: float, limit: float, ok: bool) -> None:
|
|
@@ -570,6 +579,10 @@ def gate_from_measurements(before: dict[str, Any], after: dict[str, Any], *, tol
|
|
| 570 |
lim_c, lim_p = tolerance_canary.get("coherence", 0.1), tolerance_canary.get("poison", 0.1)
|
| 571 |
check("canary_coherence_rise", d_coh, lim_c, d_coh <= lim_c)
|
| 572 |
check("canary_poison_drop", d_poi, lim_p, d_poi >= -lim_p)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 573 |
gate["measured"] = bool(gate["checks"])
|
| 574 |
gate["passed"] = all(c["passed"] for c in gate["checks"]) if gate["checks"] else None
|
| 575 |
if not gate["measured"]:
|
|
|
|
| 67 |
distill_temperature: float = 1.0
|
| 68 |
tolerance_nll: float = 0.05 # allowed rise in mean held-out assistant NLL (nats per token)
|
| 69 |
tolerance_canary: dict[str, float] = field(default_factory=lambda: {"coherence": 0.1, "poison": 0.1})
|
| 70 |
+
# behavioral locality: the share of distinct probe replies may not fall below this when it was higher before.
|
| 71 |
+
# A run that lowers held-out NLL while every reply becomes the same sentence has collapsed, not learned.
|
| 72 |
+
tolerance_collapse: float = 0.5
|
| 73 |
recall_max_new_tokens: int = 48
|
| 74 |
seed: int = 0
|
| 75 |
device: str = "cpu"
|
|
|
|
| 512 |
}
|
| 513 |
report["after"] = after
|
| 514 |
log(f"[sleep] after: heldout NLL {_fmt(after['heldout_nll'])}; recall {_fmt_recall(after['recall'])}")
|
| 515 |
+
gate = gate_from_measurements(before, after, tolerance_nll=cfg.tolerance_nll, tolerance_canary=cfg.tolerance_canary,
|
| 516 |
+
tolerance_collapse=cfg.tolerance_collapse)
|
| 517 |
report["gate"] = gate
|
| 518 |
if before["recall"] and after["recall"]:
|
| 519 |
report["recall_gain"] = {"recalled": after["recall"]["recalled"] - before["recall"]["recalled"],
|
|
|
|
| 556 |
return report
|
| 557 |
|
| 558 |
|
| 559 |
+
def gate_from_measurements(before: dict[str, Any], after: dict[str, Any], *, tolerance_nll: float, tolerance_canary: dict[str, float],
|
| 560 |
+
tolerance_collapse: float = 0.5) -> dict[str, Any]:
|
| 561 |
"""The locality gate. ``measured`` says whether any check could run; ``passed`` is None when nothing was
|
| 562 |
+
measured, so an unmeasured run is never reported as verified. A nonfinite measurement fails its check.
|
| 563 |
+
With recall probes present, the share of distinct replies after sleep may not drop below
|
| 564 |
+
``tolerance_collapse`` when it was at or above it before: a perplexity drop with collapsed replies is a
|
| 565 |
+
failure (observed on the step-100 checkpoint, 40 steps on all parameters: NLL fell, 6 of 15 answers
|
| 566 |
+
became the same sentence)."""
|
| 567 |
gate: dict[str, Any] = {"passed": None, "measured": False, "checks": []}
|
| 568 |
|
| 569 |
def check(name: str, value: float, limit: float, ok: bool) -> None:
|
|
|
|
| 579 |
lim_c, lim_p = tolerance_canary.get("coherence", 0.1), tolerance_canary.get("poison", 0.1)
|
| 580 |
check("canary_coherence_rise", d_coh, lim_c, d_coh <= lim_c)
|
| 581 |
check("canary_poison_drop", d_poi, lim_p, d_poi >= -lim_p)
|
| 582 |
+
rb, ra = (before.get("recall") or {}).get("distinct_ratio"), (after.get("recall") or {}).get("distinct_ratio")
|
| 583 |
+
if rb is not None and ra is not None:
|
| 584 |
+
ok = (ra >= tolerance_collapse) or (ra >= rb) # a run may not push distinct replies below the floor unless they already were
|
| 585 |
+
check("reply_distinct_ratio", ra, tolerance_collapse, ok)
|
| 586 |
gate["measured"] = bool(gate["checks"])
|
| 587 |
gate["passed"] = all(c["passed"] for c in gate["checks"]) if gate["checks"] else None
|
| 588 |
if not gate["measured"]:
|
source_snapshot.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"github_repository": "DMontgomery40/plastic",
|
| 3 |
-
"github_commit": "
|
| 4 |
-
"preserved_hf_revision": "
|
| 5 |
"repo_type": "space"
|
| 6 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"github_repository": "DMontgomery40/plastic",
|
| 3 |
+
"github_commit": "a11157bf3cf76829e61c5f16488f11009743a622",
|
| 4 |
+
"preserved_hf_revision": "72d3e2e7b1548cf6ab1f4594613a3a59f9b33a03",
|
| 5 |
"repo_type": "space"
|
| 6 |
}
|
tests/test_sleep_ttt.py
CHANGED
|
@@ -186,6 +186,9 @@ def test_recall_scoring_and_probe_loading(tmp_path):
|
|
| 186 |
replies = {"What is my cat's name?": "Marlowe.", "Remind me what I call my cat.": "A cat.", "Where do I live?": "You live in Denver."}
|
| 187 |
rep = run_probes(probes, lambda q: replies[q])
|
| 188 |
d = rep.to_dict()
|
|
|
|
|
|
|
|
|
|
| 189 |
assert (d["n_probes"], d["recalled"], d["recalled_exact"], d["n_paraphrase"], d["recalled_paraphrase"]) == (2, 2, 1, 1, 0)
|
| 190 |
assert isinstance(rep, RecallReport) and rep.count(variant="paraphrase") == 0
|
| 191 |
with pytest.raises(ValueError):
|
|
@@ -206,6 +209,15 @@ def test_gate_treats_nonfinite_measurements_as_failures():
|
|
| 206 |
none = gate_from_measurements({"heldout_nll": None, "canary": None, "recall": None}, {"heldout_nll": None, "canary": None, "recall": None},
|
| 207 |
tolerance_nll=0.05, tolerance_canary={})
|
| 208 |
assert none["measured"] is False and none["passed"] is None and "exploratory" in none["note"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
|
| 210 |
|
| 211 |
def test_sleep_config_validation():
|
|
|
|
| 186 |
replies = {"What is my cat's name?": "Marlowe.", "Remind me what I call my cat.": "A cat.", "Where do I live?": "You live in Denver."}
|
| 187 |
rep = run_probes(probes, lambda q: replies[q])
|
| 188 |
d = rep.to_dict()
|
| 189 |
+
assert d["distinct_ratio"] == 1.0
|
| 190 |
+
collapsed = run_probes(probes, lambda q: "A pleasure to meet you, Marlowe. I am glad you asked.").to_dict()
|
| 191 |
+
assert collapsed["distinct_ratio"] == 1 / 3 and collapsed["recalled"] == 1 # every reply the same; only the cat probe "hits"
|
| 192 |
assert (d["n_probes"], d["recalled"], d["recalled_exact"], d["n_paraphrase"], d["recalled_paraphrase"]) == (2, 2, 1, 1, 0)
|
| 193 |
assert isinstance(rep, RecallReport) and rep.count(variant="paraphrase") == 0
|
| 194 |
with pytest.raises(ValueError):
|
|
|
|
| 209 |
none = gate_from_measurements({"heldout_nll": None, "canary": None, "recall": None}, {"heldout_nll": None, "canary": None, "recall": None},
|
| 210 |
tolerance_nll=0.05, tolerance_canary={})
|
| 211 |
assert none["measured"] is False and none["passed"] is None and "exploratory" in none["note"]
|
| 212 |
+
# behavioral collapse: NLL improves but replies collapse onto one sentence -> the gate fails
|
| 213 |
+
collapsed = gate_from_measurements({"heldout_nll": {"mean": 1.68, "median": 0.9, "tokens": 800}, "canary": None, "recall": {"distinct_ratio": 1.0}},
|
| 214 |
+
{"heldout_nll": {"mean": 1.63, "median": 0.4, "tokens": 800}, "canary": None, "recall": {"distinct_ratio": 0.4}},
|
| 215 |
+
tolerance_nll=0.05, tolerance_canary={})
|
| 216 |
+
assert collapsed["passed"] is False and [c["name"] for c in collapsed["checks"] if not c["passed"]] == ["reply_distinct_ratio"]
|
| 217 |
+
# a model whose replies were already repetitive is not blamed for staying so, and improvement always passes
|
| 218 |
+
same = gate_from_measurements({"heldout_nll": None, "canary": None, "recall": {"distinct_ratio": 0.3}},
|
| 219 |
+
{"heldout_nll": None, "canary": None, "recall": {"distinct_ratio": 0.35}}, tolerance_nll=0.05, tolerance_canary={})
|
| 220 |
+
assert same["passed"] is True
|
| 221 |
|
| 222 |
|
| 223 |
def test_sleep_config_validation():
|