summaryrefslogtreecommitdiff
path: root/external/dualprop_patches/0017-analysis-freeze-complete-P2-audit-gate.patch
diff options
context:
space:
mode:
Diffstat (limited to 'external/dualprop_patches/0017-analysis-freeze-complete-P2-audit-gate.patch')
-rw-r--r--external/dualprop_patches/0017-analysis-freeze-complete-P2-audit-gate.patch248
1 files changed, 248 insertions, 0 deletions
diff --git a/external/dualprop_patches/0017-analysis-freeze-complete-P2-audit-gate.patch b/external/dualprop_patches/0017-analysis-freeze-complete-P2-audit-gate.patch
new file mode 100644
index 0000000..2119d99
--- /dev/null
+++ b/external/dualprop_patches/0017-analysis-freeze-complete-P2-audit-gate.patch
@@ -0,0 +1,248 @@
+From 52f1b56e3a3fc80cc25b9ac9efc935f8d135961e Mon Sep 17 00:00:00 2001
+From: YurenHao0426 <Blackhao0426@gmail.com>
+Date: Mon, 27 Jul 2026 13:51:49 -0500
+Subject: [PATCH 17/19] analysis: freeze complete P2 audit gate
+
+---
+ analyze_p2.py | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 229 insertions(+)
+ create mode 100644 analyze_p2.py
+
+diff --git a/analyze_p2.py b/analyze_p2.py
+new file mode 100644
+index 0000000..d997ec5
+--- /dev/null
++++ b/analyze_p2.py
+@@ -0,0 +1,229 @@
++#!/usr/bin/env python3
++"""Audit the complete frozen 27-cell plain-CNN P2 validation panel."""
++import argparse
++import glob
++import hashlib
++import json
++import math
++import os
++
++import numpy as np
++
++import crossover_grid
++
++
++ROOT = os.path.dirname(os.path.abspath(__file__))
++STATUSES = ("completed", "nonzero_exit", "timeout", "missing_histogram")
++
++
++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 scalar(value):
++ return float(np.asarray(value))
++
++
++def all_nan(values):
++ return all(math.isnan(scalar(value)) for value in values)
++
++
++def history_metrics(path, job):
++ history = np.load(path, allow_pickle=True).item()
++ method = job["method"]
++ expected_epochs = job["expected_epochs"]
++ if method == "ff":
++ assert history["method"] == "ff"
++ assert history["epochs_per_layer"] == expected_epochs
++ assert len(history["layers"]) == history["num_layers"]
++ layer_epochs = [
++ len(layer["epochs"]) for layer in history["layers"]]
++ assert all(count == expected_epochs for count in layer_epochs)
++ final = history["final"]
++ assert all_nan((final["test_accuracy"], final["test_time"]))
++ layer_losses = [
++ float(epoch["loss"]) for layer in history["layers"]
++ for epoch in layer["epochs"]]
++ nonfinite = [
++ index + 1 for index, value in enumerate(layer_losses)
++ if not math.isfinite(value)]
++ validation_accuracy = float(final["validation_accuracy"])
++ return {
++ "outcome": "nonfinite" if nonfinite else "finite",
++ "best_validation_accuracy": validation_accuracy,
++ "final_validation_accuracy": validation_accuracy,
++ "final_validation_loss": None,
++ "best_epoch": None,
++ "epochs_completed": sum(layer_epochs),
++ "first_nonfinite_step": nonfinite[0] if nonfinite else None,
++ "train_seconds": sum(
++ float(epoch["runtime"]) for layer in history["layers"]
++ for epoch in layer["epochs"]),
++ "validation_seconds": float(final["validation_time"]),
++ "test_metrics_all_nan": True,
++ }
++ completed = int(history["epochs_completed"])
++ assert 1 <= completed <= expected_epochs
++ validation_accuracy = np.asarray(
++ history["val_accuracy"], dtype=float)[:completed]
++ validation_loss = np.asarray(
++ history["val_loss"], dtype=float)[:completed]
++ train_loss = np.asarray(history["train_loss"], dtype=float)[:completed]
++ assert np.asarray(history["val_accuracy"]).shape == (expected_epochs,)
++ assert np.asarray(history["train_loss"]).shape == (expected_epochs,)
++ assert all_nan((
++ history["test_accuracy"], history["test_loss"],
++ history["test_top5accuracy"], history["test_time"]))
++ finite_accuracy = validation_accuracy[np.isfinite(validation_accuracy)]
++ best_accuracy = (
++ float(np.max(finite_accuracy)) if finite_accuracy.size else None)
++ nonfinite_mask = (
++ ~np.isfinite(validation_accuracy)
++ | ~np.isfinite(validation_loss)
++ | ~np.isfinite(train_loss))
++ nonfinite_indices = np.flatnonzero(nonfinite_mask)
++ first_nonfinite = (
++ int(nonfinite_indices[0]) + 1 if nonfinite_indices.size else None)
++ if first_nonfinite is None and completed != expected_epochs:
++ outcome = "premature_finite_stop"
++ elif first_nonfinite is None:
++ outcome = "finite"
++ else:
++ outcome = "nonfinite"
++ if best_accuracy is not None:
++ assert math.isclose(
++ best_accuracy, scalar(history["best_validation_accuracy"]),
++ rel_tol=0, abs_tol=1e-5)
++ return {
++ "outcome": outcome,
++ "best_validation_accuracy": best_accuracy,
++ "final_validation_accuracy": float(validation_accuracy[-1]),
++ "final_validation_loss": float(validation_loss[-1]),
++ "best_epoch": int(history["best_epoch"]),
++ "epochs_completed": completed,
++ "first_nonfinite_step": first_nonfinite,
++ "train_seconds": float(
++ np.sum(np.asarray(history["train_time"])[:completed])),
++ "validation_seconds": float(
++ np.sum(np.asarray(history["val_time"])[:completed])),
++ "test_metrics_all_nan": True,
++ }
++
++
++def record_manifest(experiment_name):
++ paths = glob.glob(os.path.join(
++ ROOT, "runs", experiment_name, "*", "crossover_manifest.json"))
++ assert len(paths) == 1, (
++ f"{experiment_name}: expected one manifest, found {len(paths)}")
++ return paths[0]
++
++
++def audit_record(job, launch):
++ manifest_path = record_manifest(job["experiment_name"])
++ with open(manifest_path, encoding="utf-8") as handle:
++ manifest = json.load(handle)
++ for key in (
++ "stage", "method", "architecture", "rate", "expected_epochs",
++ "experiment_name", "selector_path", "selector_sha256",
++ "timeout_seconds", "command"):
++ assert manifest[key] == job[key], (
++ f"{job['experiment_name']}: registry drift in {key}")
++ assert manifest["source"] == launch["source"]
++ assert manifest["status"] in STATUSES
++ assert manifest["selector_sha256"] == crossover_grid.SELECTOR_SHA256
++ assert manifest["work"] == crossover_grid.p2_work_report(job)
++ hardware = manifest["hardware"]
++ assert hardware["physical_index"] in (5, 7)
++ assert hardware["name"] == "NVIDIA GeForce GTX 1080"
++ assert hardware["peak_process_gpu_memory_mib"] >= 0
++ histogram = manifest["histogram"]
++ if histogram is None:
++ assert manifest["histogram_sha256"] is None
++ metrics = {
++ "outcome": manifest["status"],
++ "best_validation_accuracy": None,
++ "final_validation_accuracy": None,
++ "final_validation_loss": None,
++ "best_epoch": None,
++ "epochs_completed": 0,
++ "first_nonfinite_step": None,
++ "train_seconds": None,
++ "validation_seconds": None,
++ "test_metrics_all_nan": None,
++ }
++ else:
++ assert os.path.isfile(histogram)
++ assert manifest["histogram_sha256"] == sha256(histogram)
++ metrics = history_metrics(histogram, job)
++ return {
++ "method": job["method"],
++ "architecture": job["architecture"],
++ "rate": job["rate"],
++ "status": manifest["status"],
++ "manifest": os.path.relpath(manifest_path, ROOT),
++ "histogram_sha256": manifest["histogram_sha256"],
++ "driver_wall_seconds": manifest["driver_wall_seconds"],
++ "physical_gpu": hardware,
++ "work": manifest["work"],
++ **metrics,
++ }
++
++
++def main():
++ parser = argparse.ArgumentParser()
++ parser.add_argument("--output")
++ parser.add_argument("--allow-partial", action="store_true")
++ args = parser.parse_args()
++ launch_path = os.path.join(ROOT, "runs", "plain-p2-launch.json")
++ with open(launch_path, encoding="utf-8") as handle:
++ launch = json.load(handle)
++ jobs = crossover_grid.p2_jobs()
++ assert launch["stage"] == "p2"
++ assert launch["num_registered_cells"] == 27
++ assert launch["registry_sha256"] == crossover_grid.p2_registry_sha256(
++ jobs)
++ assert launch["jobs"] == jobs
++ assert launch["selector_sha256"] == crossover_grid.SELECTOR_SHA256
++ assert launch["source"]["cifar10_tfds_tree"]["num_files"] == 5
++ expected = {job["experiment_name"]: job for job in jobs}
++ actual = {
++ os.path.basename(path)
++ for path in glob.glob(os.path.join(ROOT, "runs", "plain-p2-*"))
++ if os.path.isdir(path) and glob.glob(
++ os.path.join(path, "*", "crossover_manifest.json"))
++ }
++ unexpected = actual - set(expected)
++ missing = set(expected) - actual
++ assert not unexpected, f"unexpected P2 cells: {sorted(unexpected)}"
++ if missing and not args.allow_partial:
++ raise AssertionError(f"missing P2 cells: {sorted(missing)}")
++ records = [
++ audit_record(job, launch) for job in jobs
++ if job["experiment_name"] in actual]
++ report = {
++ "gate": "pass" if not missing else "partial",
++ "stage": "plain_cnn_p2",
++ "launch_record": os.path.relpath(launch_path, ROOT),
++ "source": launch["source"],
++ "selector_sha256": launch["selector_sha256"],
++ "registry_sha256": launch["registry_sha256"],
++ "num_expected_records": len(jobs),
++ "num_audited_records": len(records),
++ "missing_experiments": sorted(missing),
++ "test_policy": "none",
++ "records": records,
++ }
++ encoded = json.dumps(report, indent=2, sort_keys=True) + "\n"
++ if args.output:
++ with open(args.output, "w", encoding="utf-8") as handle:
++ handle.write(encoded)
++ else:
++ print(encoded, end="")
++
++
++if __name__ == "__main__":
++ main()
+--
+2.54.0
+