summaryrefslogtreecommitdiff
path: root/external/dualprop_patches/0011-experiment-register-plain-CNN-P1-selector-grid.patch
diff options
context:
space:
mode:
Diffstat (limited to 'external/dualprop_patches/0011-experiment-register-plain-CNN-P1-selector-grid.patch')
-rw-r--r--external/dualprop_patches/0011-experiment-register-plain-CNN-P1-selector-grid.patch247
1 files changed, 247 insertions, 0 deletions
diff --git a/external/dualprop_patches/0011-experiment-register-plain-CNN-P1-selector-grid.patch b/external/dualprop_patches/0011-experiment-register-plain-CNN-P1-selector-grid.patch
new file mode 100644
index 0000000..4f79f09
--- /dev/null
+++ b/external/dualprop_patches/0011-experiment-register-plain-CNN-P1-selector-grid.patch
@@ -0,0 +1,247 @@
+From bc3b656233c8731e7b8182087cf74a0079baadbd Mon Sep 17 00:00:00 2001
+From: YurenHao0426 <Blackhao0426@gmail.com>
+Date: Mon, 27 Jul 2026 13:28:00 -0500
+Subject: [PATCH 11/19] experiment: register plain CNN P1 selector grid
+
+---
+ crossover_grid.py | 228 ++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 228 insertions(+)
+ create mode 100644 crossover_grid.py
+
+diff --git a/crossover_grid.py b/crossover_grid.py
+new file mode 100644
+index 0000000..dae3316
+--- /dev/null
++++ b/crossover_grid.py
+@@ -0,0 +1,228 @@
++#!/usr/bin/env python3
++"""Immutable command registry for the plain-CNN crossover stages."""
++import argparse
++import glob
++import hashlib
++import json
++import os
++import subprocess
++import sys
++import time
++
++
++ROOT = os.path.dirname(os.path.abspath(__file__))
++MAIN_ROOT = "/home/yurenh2/sdil"
++PROTOCOL = os.path.join(MAIN_ROOT, "PLAIN_CNN_CROSSOVER.md")
++
++
++def output(*args):
++ return subprocess.run(
++ args, cwd=ROOT, check=True, capture_output=True,
++ text=True).stdout.strip()
++
++
++def sha256(path):
++ digest = hashlib.sha256()
++ with open(path, "rb") as handle:
++ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
++ digest.update(chunk)
++ return digest.hexdigest()
++
++
++def source_report():
++ commit = output("git", "rev-parse", "HEAD")
++ dirty = output(
++ "git", "status", "--porcelain", "--untracked-files=no")
++ if dirty:
++ raise RuntimeError("crossover runs require clean tracked author source")
++ main_commit = subprocess.run(
++ ["git", "rev-parse", "HEAD"], cwd=MAIN_ROOT, check=True,
++ capture_output=True, text=True).stdout.strip()
++ main_dirty = subprocess.run(
++ ["git", "status", "--porcelain", "--untracked-files=no"],
++ cwd=MAIN_ROOT, check=True, capture_output=True,
++ text=True).stdout.strip()
++ if main_dirty:
++ raise RuntimeError("crossover runs require clean tracked main source")
++ return {
++ "author_crossover_commit": commit,
++ "main_commit": main_commit,
++ "protocol_path": PROTOCOL,
++ "protocol_sha256": sha256(PROTOCOL),
++ "python": sys.executable,
++ "cuda_visible_devices": os.environ.get("CUDA_VISIBLE_DEVICES"),
++ }
++
++
++def rate_tag(rate):
++ return f"{rate:g}".replace(".", "p")
++
++
++def p1_jobs():
++ # Expensive jobs are deliberately interleaved across modulo shards.
++ specifications = [
++ ("ep", 0.003),
++ ("dualprop", 0.025),
++ ("ep", 0.01),
++ ("ff", 0.03),
++ ("ep", 0.03),
++ ("pepita", 0.01),
++ ("fa", 0.003),
++ ("bp", 0.025),
++ ("fa", 0.01),
++ ("dfa", 0.003),
++ ("fa", 0.03),
++ ("dfa", 0.01),
++ ("clean_kp", 0.003),
++ ("dfa", 0.03),
++ ("clean_kp", 0.01),
++ ("sdil", 0.003),
++ ("clean_kp", 0.03),
++ ("sdil", 0.01),
++ ("sdil", 0.03),
++ ]
++ return [p1_command(method, rate) for method, rate in specifications]
++
++
++def p1_command(method, rate):
++ cli_method = {
++ "dualprop": "dualprop-lagr-ff",
++ "clean_kp": "clean-kp",
++ }.get(method, method)
++ runner = "train_ff.py" if method == "ff" else "train.py"
++ name = f"plain-p1-{method}-minicnn-lr{rate_tag(rate)}"
++ command = [
++ sys.executable, runner,
++ "--model", "miniCNN",
++ "--dataset", "cifar10",
++ "--num-epochs", "10",
++ "--batch-size", "100",
++ "--learning-rate", str(rate),
++ "--learning-rate-final", str(rate),
++ "--warmup-learning-rate", str(rate),
++ "--warmup-epochs", "0",
++ "--decay-epochs", "10",
++ "--momentum", "0.9",
++ "--weight-decay", "5e-4",
++ "--dtype", "float32",
++ "--param-dtype", "float32",
++ "--percent-train", "90",
++ "--percent-val", "10",
++ "--seeds", "0",
++ "--feedback-seed", "1729",
++ "--gradient-diagnostics", "none",
++ "--spectral-diagnostics", "none",
++ "--test-policy", "none",
++ "--early-stop-policy", "none",
++ "--learning-algorithm", cli_method,
++ "--experiment-name", name,
++ ]
++ if method == "pepita":
++ command.extend([
++ "--optimizer-schedule", "pepita",
++ "--pepita-projection-scale", "0.05",
++ ])
++ else:
++ command.extend(["--optimizer-schedule", "author"])
++ if method == "ff":
++ command.extend([
++ "--ff-threshold", "2.0",
++ "--ff-score-from-layer", "1",
++ ])
++ if method == "ep":
++ command.extend([
++ "--ep-beta", "0.5",
++ "--ep-dt", "0.5",
++ "--ep-free-steps", "20",
++ "--ep-nudge-steps", "4",
++ ])
++ if method == "dualprop":
++ command.extend([
++ "--loss", "sce",
++ "--alpha", "0.0",
++ "--beta", "0.1",
++ "--inference-sequence", "fwK",
++ "--inference-passes-nudged", "16",
++ ])
++ if method == "sdil":
++ command.extend([
++ "--sdil-traffic-ratio", "4",
++ "--sdil-traffic-seed", "4000",
++ "--sdil-calibration-examples", "64",
++ ])
++ return {
++ "stage": "p1",
++ "method": method,
++ "architecture": "minicnn",
++ "rate": rate,
++ "experiment_name": name,
++ "command": command,
++ }
++
++
++def completed_histogram(experiment_name):
++ paths = glob.glob(os.path.join(
++ ROOT, "runs", experiment_name, "*", "hist.npy"))
++ if not paths:
++ return None
++ if len(paths) != 1:
++ raise RuntimeError(
++ f"expected one result for {experiment_name}, found {len(paths)}")
++ return paths[0]
++
++
++def run_job(job, source, dry_run):
++ result = completed_histogram(job["experiment_name"])
++ if result is not None:
++ print(f"preserving {job['experiment_name']} -> {result}", flush=True)
++ return
++ print("RUN", " ".join(job["command"]), flush=True)
++ if dry_run:
++ return
++ started = time.time()
++ subprocess.run(job["command"], cwd=ROOT, check=True)
++ result = completed_histogram(job["experiment_name"])
++ if result is None:
++ raise RuntimeError(f"job produced no histogram: {job['experiment_name']}")
++ manifest = {
++ **job,
++ "source": source,
++ "histogram": result,
++ "histogram_sha256": sha256(result),
++ "driver_wall_seconds": time.time() - started,
++ "completed_unix_time": time.time(),
++ }
++ manifest_path = os.path.join(
++ os.path.dirname(result), "crossover_manifest.json")
++ with open(manifest_path, "w") as handle:
++ json.dump(manifest, handle, indent=2, sort_keys=True)
++ handle.write("\n")
++ print(f"DONE {job['experiment_name']} -> {manifest_path}", flush=True)
++
++
++def main():
++ parser = argparse.ArgumentParser()
++ parser.add_argument("--stage", choices=("p1",), default="p1")
++ parser.add_argument("--shard-index", type=int, default=0)
++ parser.add_argument("--num-shards", type=int, default=1)
++ parser.add_argument("--method")
++ 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")
++ source = source_report()
++ jobs = p1_jobs()
++ if args.method:
++ jobs = [job for job in jobs if job["method"] == args.method]
++ jobs = [
++ job for index, job in enumerate(jobs)
++ if index % args.num_shards == args.shard_index
++ ]
++ if not jobs:
++ raise ValueError("no jobs selected")
++ for job in jobs:
++ run_job(job, source, args.dry_run)
++
++
++if __name__ == "__main__":
++ main()
+--
+2.54.0
+