Files
digital-drosophila/models/male-cns-lif/runtime-credit-v1/phase4o_a.py
T

203 lines
12 KiB
Python

#!/usr/bin/env python3
"""Read-only Phase 4O-A runtime determinism audit; never writes frozen roots."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import platform
import random
import shutil
import subprocess
import sys
import time
from pathlib import Path
import numpy as np
import pyarrow.parquet as pq
import torch
import yaml
ROOT = Path("/data/digital-drosophila")
EXP = ROOT / "experiments/phase4o-runtime-credit"
MODEL = ROOT / "models/male-cns-lif/runtime-credit-v1"
FROZEN = ROOT / "experiments/phase4c-runtime/regression/reference"
GPU_UUID = "GPU-79f3a85d-adce-7833-7003-6488140ca596"
def save(path: Path, value: object) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True, default=str) + "\n")
def sha(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
for block in iter(lambda: f.read(8 << 20), b""):
h.update(block)
return h.hexdigest()
def digest(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def cmd(args: list[str]) -> dict:
exe = shutil.which(args[0])
if exe is None:
return {"available": False}
p = subprocess.run(args, text=True, capture_output=True)
return {"available": True, "exit_code": p.returncode, "stdout": p.stdout.strip()[:8000], "stderr": p.stderr.strip()[:2000]}
def env_audit() -> None:
if os.environ.get("CUDA_VISIBLE_DEVICES") != GPU_UUID:
raise RuntimeError("CUDA_VISIBLE_DEVICES must select frozen RTX 4090 UUID")
names = ["CUDA_VISIBLE_DEVICES", "CUBLAS_WORKSPACE_CONFIG", "PYTHONHASHSEED", "OMP_NUM_THREADS", "MKL_NUM_THREADS"]
names += sorted(x for x in os.environ if x.startswith("TORCH_"))
gpu = torch.cuda.get_device_properties(0)
e = {"OS_release": platform.platform(), "kernel": platform.release(), "uname": platform.uname()._asdict(),
"python": sys.version, "torch": torch.__version__, "cuda_runtime": torch.version.cuda,
"cudnn": torch.backends.cudnn.version(), "gpu_name": gpu.name, "gpu_uuid": GPU_UUID,
"gpu_total_memory_bytes": gpu.total_memory, "env_allowlist": {x: os.environ.get(x) for x in names},
"nvcc": cmd(["nvcc", "--version"]), "nvidia_smi": cmd(["nvidia-smi", "--query-gpu=name,uuid,driver_version,memory.total", "--format=csv,noheader"]),
"gcc": cmd(["gcc", "--version"]), "gxx": cmd(["g++", "--version"]), "ninja": cmd(["ninja", "--version"]),
"git_model": cmd(["git", "-C", str(ROOT / "models/male-cns-lif"), "status", "--short", "--branch"]),
"git_workspace": cmd(["git", "-C", str(ROOT), "rev-parse", "HEAD"]),
"torch_flags": {"deterministic_algorithms": torch.are_deterministic_algorithms_enabled(),
"cudnn_deterministic": torch.backends.cudnn.deterministic,
"cudnn_benchmark": torch.backends.cudnn.benchmark,
"cuda_matmul_allow_tf32": torch.backends.cuda.matmul.allow_tf32,
"cudnn_allow_tf32": torch.backends.cudnn.allow_tf32,
"matmul_precision": torch.get_float32_matmul_precision()}}
save(EXP / "environment/runtime-environment.json", e)
roots = [ROOT / "models/male-cns-lif/reference-v1", ROOT / "models/male-cns-lif/runtime-v1",
FROZEN, ROOT / "models/male-cns-lif/kcmbon-efficacy-v1",
ROOT / "experiments/phase4n-credit-assignment/compiler-reports/extension-build",
ROOT / "experiments/phase4c-runtime/compiler-reports/extension-build"]
files = []
for root in roots:
if root.exists():
for p in sorted(root.rglob("*")):
if p.is_file() and p.suffix not in (".pyc", ".o"):
files.append({"path": str(p), "bytes": p.stat().st_size, "mtime_ns": p.stat().st_mtime_ns, "sha256": sha(p)})
for p in [ROOT / "models/male-cns-lif/brain_runtime.py", ROOT / "models/male-cns-lif/phase4i.py",
ROOT / "models/male-cns-lif/phase4m.py", ROOT / "models/male-cns-lif/phase4l.py",
ROOT / "models/male-cns-lif/phase4n_events.py"]:
if p.is_file():
files.append({"path": str(p), "bytes": p.stat().st_size, "mtime_ns": p.stat().st_mtime_ns, "sha256": sha(p)})
save(EXP / "binary-audit/execution-file-sha256.json", files)
binary = [x for x in files if x["path"].endswith(".so")]
save(EXP / "binary-audit/runtime-binary-diff-report.json", {
"binary_files": binary, "bytewise_same": len({x["sha256"] for x in binary}) == 1 if len(binary) > 1 else None,
"warning": "A binary hash difference is not itself proof of changed semantics; build paths and toolchain can alter ELF bytes.",
"historical_phase4c_manifest": str(ROOT / "experiments/phase4c-runtime/manifests/manifest.json")})
def rng_hashes(rt=None) -> dict:
py = digest(repr(random.getstate()).encode())
npy = digest(repr(np.random.get_state()).encode())
out = {"python_random": py, "numpy_global": npy, "torch_cpu": digest(torch.get_rng_state().numpy().tobytes()),
"torch_cuda": digest(torch.cuda.get_rng_state(0).cpu().numpy().tobytes())}
if rt is not None and hasattr(rt, "generator"):
out["input_generator"] = digest(rt.generator.get_state().cpu().numpy().tobytes())
return out
def first_event_diff(a_step, a_node, b_step, b_node):
n = min(len(a_step), len(b_step))
mismatched = np.flatnonzero((a_step[:n] != b_step[:n]) | (a_node[:n] != b_node[:n]))
j = int(mismatched[0]) if len(mismatched) else (n if len(a_step) != len(b_step) else None)
if j is None:
return None
ref = None if j >= len(a_step) else {"step": int(a_step[j]), "neuron_idx": int(a_node[j])}
got = None if j >= len(b_step) else {"step": int(b_step[j]), "neuron_idx": int(b_node[j])}
step = min(x["step"] for x in (ref, got) if x is not None)
return {"event_position": j, "first_difference_step_upper_bound": step, "reference_event": ref, "observed_event": got,
"note": "Event-list first difference is not necessarily first state divergence; state-level instrumentation is required."}
def repeat_audit(repeats: int, trial_filter: str | None = None, tag: str = "baseline") -> None:
from brain_runtime import BrainRuntime
from phase4i import poisson_batch
cfgpath = ROOT / "models/male-cns-lif/mb-mechanistic-v1/phase4i.yaml"
rt = BrainRuntime(cfgpath, EXP / "compiler-reports/extension-build")
backend_file = Path(rt.extension.__file__)
identity = {"module": rt.extension.__name__, "binary_path": str(backend_file), "binary_sha256": sha(backend_file),
"runtime_v1_python_sha256": sha(ROOT / "models/male-cns-lif/runtime-v1/phase4c.py"),
"source": "BrainRuntime._hybrid_edges plus GPU scatter_add_", "config": str(cfgpath), "config_sha256": sha(cfgpath)}
save(EXP / "binary-audit/loaded-backend.json", identity)
specs = [(s["name"], s["population"], float(s["rate_hz"]), int(s["seed"])) for s in yaml.safe_load((ROOT / "models/male-cns-lif/runtime-v1/phase4c.yaml").read_text())["reference_trials"]]
if trial_filter:
specs = [x for x in specs if x[0] == trial_filter]
if not specs: raise ValueError(f"unknown frozen trial {trial_filter}")
rows = []
output_dir = EXP / "phase4c-replay" / tag
for name, pop, rate, seed in specs:
refroot = FROZEN / name
table = pq.read_table(refroot / "spike-events.parquet")
rstep = table["step"].to_numpy().astype(np.int32); rnode = table["neuron_idx"].to_numpy().astype(np.int32)
rcount = np.load(refroot / "per-neuron-spike-counts.int32.npy")
rv = np.load(refroot / "final-voltage.float32.npy"); rg = np.load(refroot / "final-g.float32.npy")
targets = np.flatnonzero(rt.population_masks[pop]).astype(np.int64)
for repeat in range(repeats):
before = rng_hashes(rt)
rt.reset(seed)
after_reset = rng_hashes(rt)
before_first = rng_hashes(rt)
t0 = time.perf_counter()
error = None
try:
rt.step(poisson_batch(targets, rate, 1000.0, name), 1000.0)
events = rt.read_spikes()
step = events["step"].astype(np.int32); node = events["neuron_idx"].astype(np.int32)
counts = rt.spike_counts.cpu().numpy().astype(np.int32)
voltage = rt.v.cpu().numpy(); g = rt.g.cpu().numpy()
match_events = bool(np.array_equal(step, rstep) and np.array_equal(node, rnode))
match_counts = bool(np.array_equal(counts, rcount))
row = {"trial": name, "repeat": repeat, "target_count": int(len(targets)),
"spike_events_exact": match_events, "per_neuron_counts_exact": match_counts,
"final_voltage_max_abs_error": float(np.max(np.abs(voltage-rv))),
"final_g_max_abs_error": float(np.max(np.abs(g-rg))),
"event_count": len(node), "event_sha256": digest(step.tobytes()+node.tobytes()),
"counts_sha256": digest(counts.tobytes()), "first_event_difference": first_event_diff(rstep,rnode,step,node),
"wall_seconds": time.perf_counter()-t0,
"rng_before_reset": before, "rng_after_reset": after_reset,
"rng_before_first_timestep": before_first, "rng_after_trial": rng_hashes(rt)}
if not match_events:
output_dir.mkdir(parents=True, exist_ok=True)
pq.write_table(__import__('pyarrow').table({'step':step,'neuron_idx':node}),
output_dir / f"divergent-{name}-repeat-{repeat}-events.parquet",compression='zstd')
np.savez_compressed(output_dir / f"divergent-{name}-repeat-{repeat}-state.npz",
final_voltage=voltage, final_g=g, counts=counts)
except Exception as exc:
error = repr(exc)
row = {"trial": name, "repeat": repeat, "error": error, "wall_seconds": time.perf_counter()-t0,
"rng_before_reset": before, "rng_after_reset": after_reset}
rows.append(row)
save(output_dir / "reproducibility-matrix.json", rows)
print(json.dumps({"trial": name, "repeat": repeat, "event_exact": row.get("spike_events_exact"),
"count_exact": row.get("per_neuron_counts_exact"), "first_diff": row.get("first_event_difference"), "error": error}), flush=True)
save(output_dir / "summary.json", {"repeats_per_trial": repeats, "all_spike_events_exact": all(r.get("spike_events_exact",False) for r in rows),
"all_counts_exact": all(r.get("per_neuron_counts_exact",False) for r in rows),
"trial_summary": {name: {"event_exact": sum(r.get("spike_events_exact",False) for r in rows if r["trial"]==name),
"count_exact": sum(r.get("per_neuron_counts_exact",False) for r in rows if r["trial"]==name)} for name,_,_,_ in specs},
"backend": identity})
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("task", choices=["environment", "repeats"])
ap.add_argument("--repeats", type=int, default=10)
ap.add_argument("--trial")
ap.add_argument("--tag", default="baseline")
args = ap.parse_args()
EXP.mkdir(parents=True, exist_ok=True); MODEL.mkdir(parents=True, exist_ok=True)
if args.task == "environment": env_audit()
else: repeat_audit(args.repeats, args.trial, args.tag)
if __name__ == "__main__":
main()