From 0645bf8369bf49ca4c23a9b2c19e4742f174addc Mon Sep 17 00:00:00 2001 From: YurenHao0426 Date: Mon, 27 Jul 2026 13:49:15 -0500 Subject: [PATCH 14/19] experiment: freeze plain CNN P2 registry --- crossover_grid.py | 177 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 175 insertions(+), 2 deletions(-) diff --git a/crossover_grid.py b/crossover_grid.py index 340e174..846a2f1 100644 --- a/crossover_grid.py +++ b/crossover_grid.py @@ -13,6 +13,14 @@ 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") +SELECTOR = os.path.join( + MAIN_ROOT, "results", "plain_cnn_p1_selector.json") +SELECTOR_SHA256 = ( + "3365f4618e0941de7dd7183f69c1f1bdd596a94b60f58de9e4e60ed05c38918c") +P2_METHODS = ( + "bp", "fa", "dfa", "pepita", "ff", "ep", "dualprop", "clean_kp", + "sdil") +P2_ARCHITECTURES = ("minicnn", "vgglike", "vgg16") def output(*args): @@ -58,6 +66,18 @@ def rate_tag(rate): return f"{rate:g}".replace(".", "p") +def selector_report(): + if sha256(SELECTOR) != SELECTOR_SHA256: + raise RuntimeError("P1 selector artifact hash drift") + with open(SELECTOR, encoding="utf-8") as handle: + report = json.load(handle) + if report["gate"] != "pass" or report["num_audited_records"] != 19: + raise RuntimeError("P1 selector gate did not pass") + if set(report["selected"]) != set(P2_METHODS): + raise RuntimeError("P1 selector method registry drift") + return report + + def p1_jobs(): # Expensive jobs are deliberately interleaved across modulo shards. specifications = [ @@ -84,6 +104,159 @@ def p1_jobs(): return [p1_command(method, rate) for method, rate in specifications] +def p2_jobs(): + report = selector_report() + # Expensive largest-model cells start first. Modulo-2 assignment places + # VGG16 EP and DP on different physical GPUs. + schedule = [ + ("ep", "vgg16"), + ("dualprop", "vgg16"), + ("ff", "vgg16"), + ("pepita", "vgg16"), + ("sdil", "vgg16"), + ("clean_kp", "vgg16"), + ("fa", "vgg16"), + ("dfa", "vgg16"), + ("bp", "vgg16"), + ("ep", "vgglike"), + ("dualprop", "vgglike"), + ("ff", "vgglike"), + ("pepita", "vgglike"), + ("sdil", "vgglike"), + ("clean_kp", "vgglike"), + ("fa", "vgglike"), + ("dfa", "vgglike"), + ("bp", "vgglike"), + ("ep", "minicnn"), + ("dualprop", "minicnn"), + ("ff", "minicnn"), + ("pepita", "minicnn"), + ("sdil", "minicnn"), + ("clean_kp", "minicnn"), + ("fa", "minicnn"), + ("dfa", "minicnn"), + ("bp", "minicnn"), + ] + assert len(schedule) == len(P2_METHODS) * len(P2_ARCHITECTURES) + assert set(schedule) == { + (method, architecture) + for method in P2_METHODS for architecture in P2_ARCHITECTURES} + return [ + p2_command( + method, architecture, report["selected"][method]["rate"]) + for method, architecture in schedule + ] + + +def p2_command(method, architecture, rate): + cli_method = { + "bp": "backprop", + "dualprop": "dualprop-lagr-ff", + "clean_kp": "clean-kp", + }.get(method, method) + model = { + "minicnn": "miniCNN", + "vgglike": "VGGlike", + "vgg16": "VGG16", + }[architecture] + runner = "train_ff.py" if method == "ff" else "train.py" + if method in ("bp", "fa", "dfa", "dualprop", "clean_kp", "sdil"): + epochs = 130 + final_rate = 2e-6 + warmup_rate = 0.001 + warmup_epochs = 10 + decay_epochs = 120 + optimizer_schedule = "author" + elif method == "pepita": + epochs = 100 + final_rate = warmup_rate = rate + warmup_epochs = 0 + decay_epochs = epochs + optimizer_schedule = "pepita" + elif method == "ff": + epochs = 40 + final_rate = warmup_rate = rate + warmup_epochs = 0 + decay_epochs = epochs + optimizer_schedule = "author" + elif method == "ep": + epochs = 100 + final_rate = warmup_rate = rate + warmup_epochs = 0 + decay_epochs = epochs + optimizer_schedule = "author" + else: + raise ValueError(method) + name = f"plain-p2-{method}-{architecture}" + command = [ + sys.executable, runner, + "--model", model, + "--dataset", "cifar10", + "--num-epochs", str(epochs), + "--batch-size", "100", + "--learning-rate", str(rate), + "--learning-rate-final", str(final_rate), + "--warmup-learning-rate", str(warmup_rate), + "--warmup-epochs", str(warmup_epochs), + "--decay-epochs", str(decay_epochs), + "--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, + "--optimizer-schedule", optimizer_schedule, + ] + if method == "pepita": + command.extend(["--pepita-projection-scale", "0.05"]) + 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": "p2", + "method": method, + "architecture": architecture, + "rate": rate, + "expected_epochs": epochs, + "experiment_name": name, + "selector_path": SELECTOR, + "selector_sha256": SELECTOR_SHA256, + "timeout_seconds": 48 * 60 * 60, + "command": command, + } + + def p1_command(method, rate): cli_method = { "bp": "backprop", @@ -203,7 +376,7 @@ def run_job(job, source, dry_run): def main(): parser = argparse.ArgumentParser() - parser.add_argument("--stage", choices=("p1",), default="p1") + parser.add_argument("--stage", choices=("p1", "p2"), default="p1") parser.add_argument("--shard-index", type=int, default=0) parser.add_argument("--num-shards", type=int, default=1) parser.add_argument("--method") @@ -212,7 +385,7 @@ def main(): if not 0 <= args.shard_index < args.num_shards: raise ValueError("invalid shard") source = source_report() - jobs = p1_jobs() + jobs = p1_jobs() if args.stage == "p1" else p2_jobs() if args.method: jobs = [job for job in jobs if job["method"] == args.method] jobs = [ -- 2.54.0