diff options
| -rw-r--r-- | MIRROR_BASELINE.md | 86 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | experiments/analyze_mirror_causal_capture.py | 149 | ||||
| -rw-r--r-- | experiments/mirror_causal_capture_screen.py | 54 |
4 files changed, 291 insertions, 0 deletions
diff --git a/MIRROR_BASELINE.md b/MIRROR_BASELINE.md new file mode 100644 index 0000000..c7ad348 --- /dev/null +++ b/MIRROR_BASELINE.md @@ -0,0 +1,86 @@ +# Normalized response-mirror baseline protocol + +## Status and attribution + +This protocol was frozen before observing any CIFAR-prefix response-mirror +alignment or accuracy endpoint. A mechanics-only pilot on synthetic Gaussian +images was used to choose the bounded mirror-rate grid; it did not load the +frozen CIFAR development split or evaluate validation/test accuracy. + +The method is an inherited strong baseline, not SDIL novelty. It is a +normalized, bias-blocked local system-identification variant of Akrout et al.'s +[weight mirror](https://proceedings.neurips.cc/paper_files/paper/2019/file/f387624df552cea2f369918c5e1e12bc-Paper.pdf). +Gaussian parent probes pass through ordinary local forward synapses and a +separate update receives only the probe and child preactivation response. The +local correlation estimates the forward kernel; Q/R track that estimate by an +exponential delta rule. The task-error path remains the audited hierarchical +FA residual DAG. + +Any BP-like scale obtained here belongs to weight estimation. It can raise the +bar SDIL must beat but cannot raise SDIL novelty or rescue failed Oral-A-v1. +Only a later, separately frozen demonstration that somato-dendritic innovation +is load-bearing on top of this path can improve the paper-level claim. + +## WM-0: mechanics gate + +Before endpoints, the smoke suite must show: + +- one 16-probe local observation has mean feedback/forward cosine above 0.985 + and minimum cosine above 0.95 on a deterministic tiny ResNet; +- every feedback/forward norm ratio is in `[0.90, 1.10]`; +- after observations are fixed, changing all forward parameters changes the + feedback update by exactly zero; +- all hierarchical-gradient and convolutional local-update audits remain + green. + +The gate passed at mean/min cosine `0.988701/0.966066`, norm ratios +`[0.953729,1.044368]`, and update independence error exactly zero. + +## WM-1: frozen-forward CIFAR causal-capture gate + +Use seed-0 ResNet-20, random feedback scale 1, the first 10,000 examples of the +frozen development split, batch 128 for the diagnostic graph, and the fixed +64-example training-prefix exact-gradient audit. Forward weights, readout, +BatchNorm state, and affine parameters remain bitwise fixed. + +Record one fixed-HFA reference. For each `eta_M in {0.03, 0.1, 0.3}`, run 20 +local mirror observations with convolutional probe batch 1, Gaussian noise +standard deviation 1, and mirror seed 3000. No task loss or label is consumed +by mirroring; the final alignment audit alone uses labels. Select maximum +early-third alignment, then all-layer alignment, then lower rate. + +WM-1 passes only if all four records are finite and selected WM: + +1. reaches early-third teaching alignment at least `0.40`; +2. reaches all-layer alignment at least `0.50`; +3. reaches mean feedback/forward cosine at least `0.85`; +4. keeps every feedback/forward norm ratio in `[0.5, 1.5]`; +5. records zero logical task-loss queries during mirror learning. + +No mirror batch, step count, noise scale, rate, covariance estimator, or +per-layer schedule is added after observation. + +## WM-2: bounded short accuracy gate + +Only a WM-1 pass opens a matched 10k-example, 20-epoch ResNet-20 validation +screen. Copy A2b data, augmentation, batch 128, cosine schedule, no warmup, +momentum 0.9, weight decay `1e-4`, and output LR 0.1. Cross hidden LR +`{0.03,0.1}`. Use the selected mirror rate, 20 mirror warmup observations, +then one batch-1 mirror observation every 16 task updates. Select validation +accuracy, then lower total MACs/rate. + +Advance only if both runs are finite, selected WM reaches at least 65%, stays +within 10 points of BP's matched 74.94%, retains early alignment at least 0.30, +uses zero task-loss queries for feedback learning, and costs no more than 1.15x +matched BP estimated MACs. Failure closes the baseline without tuning. + +## WM-3: full validation baseline + +Only a WM-2 pass opens one 200-epoch seed-0 validation run on all 45,000 +development-training examples. Copy its selected hidden rate, mirror settings, +output LR 0.1, and the A1 step drops at epochs 100/150. No recovery branch is +allowed. A finite endpoint at least 88% with early alignment at least 0.30 and +cost at most 1.15x BP is considered a strong inherited baseline. It does not +authorize test access; it instead opens design of a hierarchical innovation +intervention on the same frozen recipe. + @@ -138,6 +138,8 @@ high finite-sample kernel recovery and that changing forward parameters after observation cannot affect the update. Any scale obtained this way is credited to the weight-mirror baseline until innovation residualization is shown to be load-bearing on top of it. +`MIRROR_BASELINE.md` freezes that comparator's CIFAR-prefix capture, short +accuracy, and conditional full-validation gates. The subsequent V3 mechanism estimates the required A/G matrix statistics directly by perturbing the vectorizer parameter subspace. It remains diff --git a/experiments/analyze_mirror_causal_capture.py b/experiments/analyze_mirror_causal_capture.py new file mode 100644 index 0000000..4347385 --- /dev/null +++ b/experiments/analyze_mirror_causal_capture.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +"""Audit and gate normalized response-mirror causal capture.""" +import argparse +import glob +import json +import math +import os + + +SPLIT_HASH = "8328b206a97c420e49e54e3eca4abe3274c4756b084355784ea3fb8059e4515b" +RATES = (0.03, 0.1, 0.3) + + +def load(path): + with open(path) as handle: + record = json.load(handle) + args = record["args"] + mode = args.get("mode") + if mode not in ("hfa", "wm"): + raise ValueError(f"{path}: unexpected method") + expected = { + "depth": 20, "width": 16, "seed": 0, "loader_seed": 0, + "epochs": 0, "train_limit": 10000, "val_examples": 5000, + "split_seed": 2027, "eval_split": "validation", "eval_every": 0, + "normalization": "batchnorm", "a_scale": 1.0, + "alignment_probe": 64, "mirror_batch_size": 1, + "mirror_noise_std": 1.0, "mirror_seed": 3000, + } + for key, value in expected.items(): + if args.get(key) != value: + raise ValueError(f"{path}: {key} drift") + expected_steps = 0 if mode == "hfa" else 20 + if args.get("mirror_warmup_steps") != expected_steps: + raise ValueError(f"{path}: mirror warmup drift") + if mode == "wm" and float(args["mirror_eta"]) not in RATES: + raise ValueError(f"{path}: unregistered mirror rate") + if record["provenance"]["git_tracked_dirty"]: + raise ValueError(f"tracked-dirty result: {path}") + if record["split"]["validation_index_sha256"] != SPLIT_HASH: + raise ValueError(f"split drift: {path}") + if record["evaluation_protocol"]["test_evaluations"]: + raise ValueError(f"test touched: {path}") + expected_space = None if mode == "hfa" else "local_parent_child_response" + if record.get("calibration_metric_space") != expected_space: + raise ValueError(f"metric-space drift: {path}") + diagnostics = record["diagnostics"] + values = diagnostics["teaching_negative_gradient_cosine"] + early_count = max(1, len(values) // 3) + norm_ratios = diagnostics["feedback_forward_norm_ratio"] + feedback_cosines = diagnostics["feedback_forward_cosine"] + metrics = { + "early_third_alignment": sum(values[:early_count]) / early_count, + "all_layer_alignment": sum(values) / len(values), + "mean_feedback_forward_cosine": ( + sum(feedback_cosines) / len(feedback_cosines)), + "min_feedback_forward_norm_ratio": min(norm_ratios), + "max_feedback_forward_norm_ratio": max(norm_ratios), + } + if mode == "wm": + warmup = record.get("mirror_warmup", {}).get("mean") + if warmup is None: + raise ValueError(f"missing mirror aggregate: {path}") + metrics.update({ + "mean_mirror_update_rms": warmup["mirror_update_rms"], + "mean_mirror_estimate_rms": warmup["mirror_estimate_rms"], + }) + finite = (bool(record["final"]["finite"]) + and all(math.isfinite(value) for value in metrics.values())) + return { + "path": path, "mode": mode, + "mirror_eta": (None if mode == "hfa" else float(args["mirror_eta"])), + "metrics": metrics, "finite": finite, + "logical_batch_loss_queries": int( + record["work"]["logical_batch_loss_queries"]), + "mirror_events": int(record["counters"]["mirror_events"]), + "total_macs": int(record["work"]["total_macs_estimate"]), + "source_commit": record["provenance"]["git_commit"], + } + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--input", default="results/mirror_causal_capture") + parser.add_argument( + "--out", default="results/mirror_causal_capture_gate.json") + args = parser.parse_args() + rows = [load(path) for path in sorted( + glob.glob(os.path.join(args.input, "*.json")))] + references = [row for row in rows if row["mode"] == "hfa"] + candidates = [row for row in rows if row["mode"] == "wm"] + if len(references) != 1 or len(candidates) != len(RATES): + raise ValueError("incomplete WM-1 method grid") + if {row["mirror_eta"] for row in candidates} != set(RATES): + raise ValueError("incomplete WM-1 rate grid") + if len({row["source_commit"] for row in rows}) != 1: + raise ValueError("WM-1 source commits differ") + eligible = [row for row in candidates if row["finite"]] + eligible.sort(key=lambda row: ( + -row["metrics"]["early_third_alignment"], + -row["metrics"]["all_layer_alignment"], row["mirror_eta"])) + selected = eligible[0] if eligible else None + checks = { + "all_four_records_finite": all(row["finite"] for row in rows), + "candidate_selected": selected is not None, + } + if selected is None: + checks.update({ + "early_third_at_least_0.40": False, + "all_layer_at_least_0.50": False, + "mean_feedback_forward_cosine_at_least_0.85": False, + "feedback_norm_ratios_in_0.5_to_1.5": False, + "zero_task_loss_queries": False, + }) + else: + metrics = selected["metrics"] + checks.update({ + "early_third_at_least_0.40": ( + metrics["early_third_alignment"] >= 0.40), + "all_layer_at_least_0.50": ( + metrics["all_layer_alignment"] >= 0.50), + "mean_feedback_forward_cosine_at_least_0.85": ( + metrics["mean_feedback_forward_cosine"] >= 0.85), + "feedback_norm_ratios_in_0.5_to_1.5": ( + metrics["min_feedback_forward_norm_ratio"] >= 0.5 + and metrics["max_feedback_forward_norm_ratio"] <= 1.5), + "zero_task_loss_queries": ( + all(row["logical_batch_loss_queries"] == 0 for row in rows)), + }) + output = { + "protocol": "normalized_response_mirror_capture_v1", + "status": "passed" if all(checks.values()) else "failed", + "checks": checks, "rows": rows, "matched_fixed_hfa": references[0], + "selected_wm": selected, "confirmation_test_seeds_touched": False, + "review_score_before": 5, "review_score_after": 5, + "score_change_rule": "inherited baseline capture cannot raise score", + } + os.makedirs(os.path.dirname(os.path.abspath(args.out)), exist_ok=True) + with open(args.out, "w") as handle: + json.dump(output, handle, indent=2, sort_keys=True) + handle.write("\n") + print(json.dumps({ + "status": output["status"], "checks": checks, + "reference": references[0], "selected_wm": selected, + }, indent=2)) + + +if __name__ == "__main__": + main() + diff --git a/experiments/mirror_causal_capture_screen.py b/experiments/mirror_causal_capture_screen.py new file mode 100644 index 0000000..1bf516a --- /dev/null +++ b/experiments/mirror_causal_capture_screen.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Run a shard of the frozen normalized response-mirror capture screen.""" +import argparse +import os +import subprocess +import sys + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--device", default="cuda") + parser.add_argument("--shard_index", type=int, default=0) + parser.add_argument("--num_shards", type=int, default=1) + parser.add_argument("--dry_run", action="store_true") + args = parser.parse_args() + if not 0 <= args.shard_index < args.num_shards: + raise ValueError("invalid shard index") + common = [ + sys.executable, "experiments/conv_run.py", "--device", args.device, + "--depth", "20", "--width", "16", "--seed", "0", + "--loader_seed", "0", "--batch_size", "128", "--epochs", "0", + "--train_limit", "10000", "--val_examples", "5000", + "--split_seed", "2027", "--eval_split", "validation", + "--eval_every", "0", "--augment_train", "1", "--lr", "0.1", + "--output_lr", "0.1", "--lr_schedule", "constant", + "--warmup_epochs", "0", "--momentum", "0.9", + "--weight_decay", "1e-4", "--normalization", "batchnorm", + "--a_scale", "1", "--alignment_probe", "64", + "--mirror_batch_size", "1", "--mirror_noise_std", "1", + "--mirror_seed", "3000", + ] + jobs = [("fixed_hfa", common + [ + "--mode", "hfa", "--out", + "results/mirror_causal_capture/fixed_hfa.json", + ])] + for rate in (0.03, 0.1, 0.3): + tag = f"wm_etaM{rate}" + jobs.append((tag, common + [ + "--mode", "wm", "--mirror_eta", str(rate), + "--mirror_warmup_steps", "20", "--out", + f"results/mirror_causal_capture/{tag}.json", + ])) + os.makedirs("results/mirror_causal_capture", exist_ok=True) + for index, (tag, command) in enumerate(jobs): + if index % args.num_shards != args.shard_index: + continue + print(tag, " ".join(command), flush=True) + if not args.dry_run: + subprocess.run(command, check=True) + + +if __name__ == "__main__": + main() + |
