Files
digital-drosophila/models/male-cns-lif/phase4b.py
T

1262 lines
63 KiB
Python

#!/usr/bin/env python3
"""MaleCNS Phase 4B: strict LIF dynamics, propagation and performance audit."""
from __future__ import annotations
import argparse
import bisect
import hashlib
import json
import logging
import math
import os
import platform
import random
import resource
import subprocess
import threading
import time
from collections import Counter, defaultdict
from pathlib import Path
from typing import Any
import numpy as np
import pyarrow as pa
import pyarrow.parquet as pq
import torch
import yaml
LOG = logging.getLogger("phase4b")
def sha256(path: Path, chunk: int = 8 << 20) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
while b := f.read(chunk):
h.update(b)
return h.hexdigest()
def write_json(path: Path, value: Any) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8")
def setup_logging(path: Path) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
handlers=[logging.FileHandler(path, encoding="utf-8"), logging.StreamHandler()],
force=True,
)
def classify_nt(value: Any, cfg: dict[str, Any]) -> tuple[str, int]:
policy = cfg["sign_policy"]
if value is None or not str(value).strip():
return "UNKNOWN", int(policy["unknown_sign"])
key = str(value).strip().lower()
if key == "unclear":
return "UNCLEAR", int(policy["unclear_sign"])
if key not in policy["neurotransmitter_sign"]:
if policy["unmapped_policy"] == "error":
raise RuntimeError(f"unmapped neurotransmitter {value!r}")
return f"UNMAPPED:{key}", 0
return key, int(policy["neurotransmitter_sign"][key])
def exact_input_paths(cfg: dict[str, Any]) -> dict[str, Path]:
root = Path(cfg["paths"]["connectome_root"]).resolve()
allowed = Path("/data/digital-drosophila/datasets/male-cns-v1.0/processed/neuron-only").resolve()
if root != allowed:
raise RuntimeError(f"only allowed connectome root is {allowed}; got {root}")
paths = {
"indptr": root / "gpu/csr-indptr.int64.npy",
"indices": root / "gpu/csr-indices.int32.npy",
"raw_weights": root / "gpu/csr-weights.int64.npy",
"metadata": root / "neuron-index/unified-metadata.parquet",
"mapping": root / "neuron-index/body-id-neuron-idx.parquet",
"csr_metadata": root / "gpu/csr-metadata.json",
}
for path in paths.values():
if not path.is_file():
raise FileNotFoundError(path)
return paths
def validate_graph(cfg: dict[str, Any], paths: dict[str, Path]) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
expected = cfg["expected_graph"]
n, nnz, weight_sum = int(expected["neurons"]), int(expected["directed_edges"]), int(expected["synaptic_contact_weight_sum"])
indptr = np.load(paths["indptr"], mmap_mode="r")
indices = np.load(paths["indices"], mmap_mode="r")
raw = np.load(paths["raw_weights"], mmap_mode="r")
if indptr.dtype != np.int64 or indices.dtype != np.int32 or raw.dtype != np.int64:
raise RuntimeError(f"CSR dtype mismatch: {indptr.dtype}, {indices.dtype}, {raw.dtype}")
if indptr.shape != (n + 1,) or indices.shape != (nnz,) or raw.shape != (nnz,):
raise RuntimeError(f"CSR shape mismatch: {indptr.shape}, {indices.shape}, {raw.shape}")
if int(indptr[-1]) != nnz or int(raw.sum(dtype=np.int64)) != weight_sum:
raise RuntimeError("fixed graph invariant failed")
if int(raw.min()) <= 0 or int(indices.min()) < 0 or int(indices.max()) >= n:
raise RuntimeError("invalid CSR range")
return indptr, indices, raw
def metadata_arrays(cfg: dict[str, Any], path: Path) -> dict[str, Any]:
columns = [
"neuron_idx", "body_id", "neurotransmitter", "annotation_type", "annotation_superclass",
"annotation_class", "annotation_subclass",
]
table = pq.read_table(path, columns=columns)
n = int(cfg["expected_graph"]["neurons"])
neuron_idx = table["neuron_idx"].to_numpy(zero_copy_only=False)
if len(table) != n or neuron_idx.dtype != np.int32 or not np.array_equal(neuron_idx, np.arange(n, dtype=np.int32)):
raise RuntimeError("metadata mapping invariant failed")
nt = table["neurotransmitter"].to_pylist()
classes: list[str] = []
signs = np.empty(n, dtype=np.int8)
for i, value in enumerate(nt):
cls, sign = classify_nt(value, cfg)
classes.append(cls)
signs[i] = sign
return {
"table": table,
"body_id": table["body_id"].to_numpy(zero_copy_only=False),
"nt": nt,
"nt_class": np.asarray(classes, dtype=object),
"sign": signs,
"type": np.asarray(table["annotation_type"].to_pylist(), dtype=object),
"superclass": np.asarray(table["annotation_superclass"].to_pylist(), dtype=object),
"class": np.asarray(table["annotation_class"].to_pylist(), dtype=object),
"subclass": np.asarray(table["annotation_subclass"].to_pylist(), dtype=object),
}
def population_masks(cfg: dict[str, Any], meta: dict[str, Any]) -> dict[str, np.ndarray]:
cls, sub = meta["class"], meta["subclass"]
mech = {"mechanosensory", "mechanosensory_tactile", "mechanosensory_proprioceptive"}
masks = {
"visual": cls == "visual",
"mechanosensory": np.asarray([(c in mech) and s != "auditory" for c, s in zip(cls, sub)], dtype=bool),
"olfactory": cls == "olfactory",
"gustatory": cls == "gustatory",
"auditory": sub == "auditory",
}
if set(masks) != set(cfg["sensory_populations"]):
raise RuntimeError("population implementation/config mismatch")
if any(not mask.any() for mask in masks.values()):
raise RuntimeError("empty sensory population")
return masks
def directed_distances(indptr: np.ndarray, indices: np.ndarray, sources: np.ndarray, n: int) -> np.ndarray:
distances = np.full(n, -1, dtype=np.int16)
distances[sources] = 0
frontier = sources.astype(np.int64, copy=False)
depth = 0
while len(frontier):
depth += 1
next_mask = np.zeros(n, dtype=bool)
for src in frontier:
next_mask[indices[int(indptr[src]):int(indptr[src + 1])]] = True
next_mask[distances >= 0] = False
frontier = np.flatnonzero(next_mask).astype(np.int64)
if not len(frontier):
break
if depth >= np.iinfo(np.int16).max:
raise RuntimeError("graph distance overflow")
distances[frontier] = depth
return distances
def audit_populations(
cfg: dict[str, Any], exp: Path, meta: dict[str, Any], masks: dict[str, np.ndarray],
indptr: np.ndarray, indices: np.ndarray, raw: np.ndarray,
) -> tuple[dict[str, Any], dict[str, np.ndarray]]:
output: dict[str, Any] = {}
distances: dict[str, np.ndarray] = {}
pop_dir = exp / "propagation/populations"
pop_dir.mkdir(parents=True, exist_ok=True)
n = int(cfg["expected_graph"]["neurons"])
for name, mask in masks.items():
nodes = np.flatnonzero(mask).astype(np.int32)
starts, stops = indptr[nodes], indptr[nodes + 1]
outgoing_edges = int(np.sum(stops - starts, dtype=np.int64))
outgoing_weight = 0
first = np.zeros(n, dtype=bool)
for start, stop in zip(starts, stops):
outgoing_weight += int(raw[int(start):int(stop)].sum(dtype=np.int64))
first[indices[int(start):int(stop)]] = True
first[mask] = False
d = directed_distances(indptr, indices, nodes, n)
distances[name] = d
membership = pa.table({
"neuron_idx": pa.array(nodes, type=pa.int32()),
"body_id": pa.array(meta["body_id"][nodes], type=pa.uint64()),
"neurotransmitter": pa.array([meta["nt"][i] for i in nodes], type=pa.string()),
})
membership_path = pop_dir / f"{name}.parquet"
pq.write_table(membership, membership_path, compression="zstd")
nt_counts = Counter(meta["nt_class"][nodes].tolist())
record = {
"name": name,
"filter_expression": cfg["sensory_populations"][name]["expression"],
"neuron_count": int(len(nodes)),
"neuron_indices": nodes.tolist(),
"body_ids": [int(x) for x in meta["body_id"][nodes]],
"neurotransmitter_composition": dict(sorted(nt_counts.items())),
"outgoing_edge_count": outgoing_edges,
"outgoing_synapse_weight": outgoing_weight,
"downstream_first_hop_neuron_count": int(first.sum()),
"max_reachable_depth": int(d.max()),
"reachable_neuron_count": int((d >= 0).sum()),
"membership_parquet": str(membership_path),
}
output[name] = record
write_json(pop_dir / f"{name}.json", record)
np.save(pop_dir / f"{name}.distances.int16.npy", d, allow_pickle=False)
LOG.info("population %s neurons=%d outgoing_edges=%d first_hop=%d", name, len(nodes), outgoing_edges, first.sum())
write_json(exp / "propagation/sensory-populations.json", output)
return output, distances
def tiny_numpy(
signs: np.ndarray, raw: np.ndarray, rows: np.ndarray, cols: np.ndarray,
forced: dict[int, list[int]], cfg: dict[str, Any], steps: int,
) -> dict[str, Any]:
n = int(len(signs))
m = cfg["model"]
dt = float(m["dt_ms"])
alpha = dt / float(m["membrane_tau_ms"])
g_decay = 1.0 - dt / float(m["synaptic_tau_ms"])
delay = int(round(float(m["synaptic_delay_ms"]) / dt))
ref_steps = int(round(float(m["refractory_ms"]) / dt))
rest, reset, threshold = float(m["resting_potential_mv"]), float(m["reset_potential_mv"]), float(m["threshold_mv"])
weight = signs[rows].astype(np.float64) * raw.astype(np.float64) * float(m["w_syn_mv_per_anatomical_synapse"])
v = np.full(n, rest, dtype=np.float64)
g = np.zeros(n, dtype=np.float64)
ref = np.zeros(n, dtype=np.int32)
queue = np.zeros((delay + 1, n), dtype=np.float64)
events: list[tuple[int, int]] = []
g_history = np.zeros((steps, n), dtype=np.float64)
rejected = 0
for step in range(steps):
slot = step % len(queue)
g *= g_decay
g += queue[slot]
queue[slot].fill(0)
ref = np.maximum(ref - 1, 0)
available = ref == 0
v[available] += alpha * (g[available] - (v[available] - rest))
v[~available] = reset
spikes = available & (v >= threshold)
for src in forced.get(step, []):
if available[src]:
spikes[src] = True
else:
rejected += 1
sources = np.flatnonzero(spikes)
if len(sources):
v[sources] = reset
ref[sources] = ref_steps
for src in sources:
events.append((step, int(src)))
hit = rows == src
np.add.at(queue[(step + delay) % len(queue)], cols[hit], weight[hit])
g_history[step] = g
return {"v": v, "g": g, "events": events, "g_history": g_history, "rejected": rejected}
def tiny_torch(
signs: np.ndarray, raw: np.ndarray, rows: np.ndarray, cols: np.ndarray,
forced: dict[int, list[int]], cfg: dict[str, Any], steps: int, device: torch.device,
) -> dict[str, Any]:
n = int(len(signs))
m = cfg["model"]
dt = float(m["dt_ms"])
alpha = dt / float(m["membrane_tau_ms"])
g_decay = 1.0 - dt / float(m["synaptic_tau_ms"])
delay = int(round(float(m["synaptic_delay_ms"]) / dt))
ref_steps = int(round(float(m["refractory_ms"]) / dt))
rest, reset, threshold = float(m["resting_potential_mv"]), float(m["reset_potential_mv"]), float(m["threshold_mv"])
rows_t = torch.as_tensor(rows, dtype=torch.int64, device=device)
cols_t = torch.as_tensor(cols, dtype=torch.int64, device=device)
weight = torch.as_tensor(signs[rows].astype(np.float64) * raw.astype(np.float64) * float(m["w_syn_mv_per_anatomical_synapse"]), dtype=torch.float64, device=device)
v = torch.full((n,), rest, dtype=torch.float64, device=device)
g = torch.zeros(n, dtype=torch.float64, device=device)
ref = torch.zeros(n, dtype=torch.int32, device=device)
queue = torch.zeros((delay + 1, n), dtype=torch.float64, device=device)
events: list[tuple[int, int]] = []
history: list[np.ndarray] = []
rejected = 0
for step in range(steps):
slot = step % len(queue)
g.mul_(g_decay).add_(queue[slot])
queue[slot].zero_()
ref.clamp_min_(1).sub_(1)
available = ref == 0
v[available] += alpha * (g[available] - (v[available] - rest))
v[~available] = reset
spikes = available & (v >= threshold)
for src in forced.get(step, []):
if bool(available[src].item()):
spikes[src] = True
else:
rejected += 1
sources = torch.nonzero(spikes, as_tuple=False).flatten()
if sources.numel():
v[sources] = reset
ref[sources] = ref_steps
for src in sources.cpu().tolist():
events.append((step, int(src)))
hit = rows_t == src
queue[(step + delay) % len(queue)].scatter_add_(0, cols_t[hit], weight[hit])
history.append(g.cpu().numpy().copy())
torch.cuda.synchronize(device)
return {"v": v.cpu().numpy(), "g": g.cpu().numpy(), "events": events, "g_history": np.stack(history), "rejected": rejected}
def run_dynamics_unit_tests(cfg: dict[str, Any], exp: Path, device: torch.device) -> dict[str, Any]:
# 0->1 excitatory; 2->3 inhibitory; 4->5 ten contacts; 6->6 self-loop;
# 7->9 and 8->9 synchronous convergent input.
rows = np.array([0, 2, 4, 6, 7, 8], dtype=np.int64)
cols = np.array([1, 3, 5, 6, 9, 9], dtype=np.int64)
raw = np.array([1, 1, 10, 1, 1, 1], dtype=np.int64)
signs = np.array([1, 0, -1, 0, 1, 0, 1, 1, 1, 0], dtype=np.int8)
forced = {0: [0, 2, 4, 6, 7, 8], 1: [0], 22: [0]}
steps = 30
cpu = tiny_numpy(signs, raw, rows, cols, forced, cfg, steps)
gpu = tiny_torch(signs, raw, rows, cols, forced, cfg, steps, device)
delay = int(round(float(cfg["model"]["synaptic_delay_ms"]) / float(cfg["model"]["dt_ms"])))
w = float(cfg["model"]["w_syn_mv_per_anatomical_synapse"])
checks = {
"single_excitatory_edge": math.isclose(cpu["g_history"][delay, 1], w, abs_tol=1e-12),
"single_inhibitory_edge": math.isclose(cpu["g_history"][delay, 3], -w, abs_tol=1e-12),
"multi_synapse_high_weight_edge": math.isclose(cpu["g_history"][delay, 5], 10 * w, abs_tol=1e-12),
"delay_exact_1_8_ms": bool(np.all(cpu["g_history"][:delay, [1, 3, 5, 6, 9]] == 0)),
"refractory_rejects_early_forced_spike": cpu["rejected"] == 1 and (22, 0) in cpu["events"],
"self_loop": math.isclose(cpu["g_history"][delay, 6], w, abs_tol=1e-12),
"synchronous_presynaptic_sum": math.isclose(cpu["g_history"][delay, 9], 2 * w, abs_tol=1e-12),
"g_decays_during_refractory": math.isclose(cpu["g_history"][delay + 1, 6], w * (1 - 0.1 / 5.0), abs_tol=1e-12),
"cpu_gpu_events_equal": cpu["events"] == gpu["events"],
"cpu_gpu_voltage_tolerance": float(np.max(np.abs(cpu["v"] - gpu["v"]))) <= 1e-10,
"cpu_gpu_g_tolerance": float(np.max(np.abs(cpu["g"] - gpu["g"]))) <= 1e-10,
}
result = {
"passed": all(checks.values()),
"checks": checks,
"delay_steps": delay,
"refractory_steps": int(round(float(cfg["model"]["refractory_ms"]) / float(cfg["model"]["dt_ms"]))),
"cpu_events": cpu["events"],
"gpu_events": gpu["events"],
"max_abs_voltage_difference": float(np.max(np.abs(cpu["v"] - gpu["v"]))),
"max_abs_synaptic_state_difference": float(np.max(np.abs(cpu["g"] - gpu["g"]))),
"refractory_g_policy": cfg["model"]["refractory_g_policy"],
}
write_json(exp / "audit/dynamics-unit-tests.json", result)
if not result["passed"]:
raise RuntimeError(f"dynamics unit tests failed: {result}")
return result
class GPUSampler:
def __init__(self, uuid: str, interval: float):
self.uuid = uuid
self.interval = interval
self.samples: list[dict[str, float]] = []
self.stop_event = threading.Event()
self.thread: threading.Thread | None = None
def _run(self) -> None:
while not self.stop_event.is_set():
try:
out = subprocess.check_output(
["nvidia-smi", "--query-gpu=uuid,utilization.gpu,memory.used", "--format=csv,noheader,nounits"],
text=True, timeout=2,
)
for line in out.splitlines():
parts = [x.strip() for x in line.split(",")]
if len(parts) == 3 and parts[0] == self.uuid:
self.samples.append({"gpu_util_percent": float(parts[1]), "memory_used_mib": float(parts[2])})
break
except Exception:
pass
self.stop_event.wait(self.interval)
def start(self) -> None:
self.thread = threading.Thread(target=self._run, daemon=True)
self.thread.start()
def stop(self) -> dict[str, Any]:
self.stop_event.set()
if self.thread:
self.thread.join(timeout=3)
gpu = [x["gpu_util_percent"] for x in self.samples]
mem = [x["memory_used_mib"] for x in self.samples]
return {
"samples": len(self.samples),
"gpu_util_mean_percent": float(np.mean(gpu)) if gpu else None,
"gpu_util_max_percent": float(np.max(gpu)) if gpu else None,
"nvidia_smi_peak_memory_used_mib": float(np.max(mem)) if mem else None,
}
def tensor_record(name: str, tensor: torch.Tensor) -> dict[str, Any]:
return {
"name": name,
"shape": list(tensor.shape),
"dtype": str(tensor.dtype),
"device": str(tensor.device),
"bytes": int(tensor.numel() * tensor.element_size()),
}
def array_record(name: str, array: np.ndarray) -> dict[str, Any]:
return {
"name": name,
"shape": list(array.shape),
"dtype": str(array.dtype),
"device": "cpu",
"bytes": int(array.nbytes),
}
def expand_rows_profiled(indptr: torch.Tensor, sources: torch.Tensor, profile: dict[str, float] | None) -> torch.Tensor:
if sources.numel() == 0:
return torch.empty(0, dtype=torch.int64, device=indptr.device)
starts = indptr[sources]
counts = indptr[sources + 1] - starts
cumulative = torch.cumsum(counts, 0)
sync_start = time.perf_counter()
total = int(cumulative[-1].item()) if cumulative.numel() else 0
if profile is not None:
profile["synchronization"] += time.perf_counter() - sync_start
if total == 0:
return torch.empty(0, dtype=torch.int64, device=indptr.device)
seq = torch.arange(total, dtype=torch.int64, device=indptr.device)
row_position = torch.searchsorted(cumulative, seq, right=True)
previous = cumulative - counts
return starts[row_position] + seq - previous[row_position]
def category_activity(values: np.ndarray, active: np.ndarray, spike_counts: np.ndarray) -> dict[str, Any]:
result: dict[str, dict[str, int]] = {}
for value in sorted({str(x) for x in values if x is not None and str(x).strip()}):
mask = values == value
result[value] = {
"active_neurons": int(np.count_nonzero(active & mask)),
"spikes": int(spike_counts[mask].sum(dtype=np.int64)),
}
null_mask = np.asarray([x is None or not str(x).strip() for x in values], dtype=bool)
result["<null>"] = {
"active_neurons": int(np.count_nonzero(active & null_mask)),
"spikes": int(spike_counts[null_mask].sum(dtype=np.int64)),
}
return result
class FullGraphRuntime:
def __init__(
self, cfg: dict[str, Any], paths: dict[str, Path], meta: dict[str, Any],
indptr_np: np.ndarray, indices_np: np.ndarray, raw_np: np.ndarray, device: torch.device,
):
self.cfg = cfg
self.paths = paths
self.meta = meta
self.device = device
self.n = int(cfg["expected_graph"]["neurons"])
self.indptr_np, self.indices_np, self.raw_np = indptr_np, indices_np, raw_np
self.indptr = torch.as_tensor(np.asarray(indptr_np), dtype=torch.int64, device=device)
self.indices = torch.as_tensor(np.asarray(indices_np), dtype=torch.int32, device=device)
self.sign = torch.as_tensor(meta["sign"], dtype=torch.int8, device=device)
# Exact raw counts fit losslessly in float32 because max is 2591.
signed_raw_np = np.empty(len(raw_np), dtype=np.float32)
for src in range(self.n):
start, stop = int(indptr_np[src]), int(indptr_np[src + 1])
signed_raw_np[start:stop] = raw_np[start:stop].astype(np.float32) * float(meta["sign"][src])
self.signed_raw_np = signed_raw_np
self.effective: torch.Tensor | None = None
def set_weight(self, w_syn: float) -> None:
# Keep exactly one edge-weight tensor on GPU; the rescalable base stays on CPU.
self.effective = None
torch.cuda.empty_cache()
self.effective = torch.as_tensor(self.signed_raw_np, dtype=torch.float32, device=self.device)
self.effective.mul_(float(w_syn))
def persistent_tensor_audit(self) -> list[dict[str, Any]]:
if self.effective is None:
raise RuntimeError("effective weights not initialized")
return [
array_record("source_csr_indptr_readonly_memmap", self.indptr_np),
array_record("source_csr_indices_readonly_memmap", self.indices_np),
array_record("source_raw_synapse_count_int64_readonly_memmap", self.raw_np),
tensor_record("csr_indptr", self.indptr),
tensor_record("csr_indices", self.indices),
tensor_record("presynaptic_sign", self.sign),
array_record("signed_raw_synapse_count_rescale_source", self.signed_raw_np),
tensor_record("runtime_effective_weights", self.effective),
]
def run(
self, population: str, targets_np: np.ndarray, distances: np.ndarray, rate_hz: float,
seed: int, duration_ms: float, w_syn: float, log_events: bool,
event_path: Path | None = None, profile_categories: bool = False,
monitor_resources: bool = False, abort_supercritical: bool | None = None,
) -> tuple[dict[str, Any], np.ndarray]:
self.set_weight(w_syn)
assert self.effective is not None
cfg, m = self.cfg, self.cfg["model"]
dt = float(m["dt_ms"])
steps = int(round(duration_ms / dt))
delay_steps = int(round(float(m["synaptic_delay_ms"]) / dt))
queue_len = delay_steps + 1
refractory_steps = int(round(float(m["refractory_ms"]) / dt))
alpha = dt / float(m["membrane_tau_ms"])
g_decay = 1.0 - dt / float(m["synaptic_tau_ms"])
rest = float(m["resting_potential_mv"])
reset = float(m["reset_potential_mv"])
threshold = float(m["threshold_mv"])
targets = torch.as_tensor(targets_np, dtype=torch.int64, device=self.device)
target_mask_np = np.zeros(self.n, dtype=bool)
target_mask_np[targets_np] = True
probability = float(rate_hz) * dt / 1000.0
generator = torch.Generator(device=self.device)
generator.manual_seed(int(seed))
v = torch.full((self.n,), rest, dtype=torch.float32, device=self.device)
g = torch.zeros(self.n, dtype=torch.float32, device=self.device)
refractory = torch.zeros(self.n, dtype=torch.int32, device=self.device)
delay_queue = torch.zeros((queue_len, self.n), dtype=torch.float32, device=self.device)
spike_counts = torch.zeros(self.n, dtype=torch.int32, device=self.device)
event_steps: list[np.ndarray] = []
event_neurons: list[np.ndarray] = []
stimulated_attempts = 0
stimulated_spikes = 0
peak_step_spikes = 0
total_spikes_running = 0
self_loop_transmissions = 0
self_loop_sources = np.zeros(self.n, dtype=bool)
for src in range(self.n):
if np.any(self.indices_np[int(self.indptr_np[src]):int(self.indptr_np[src + 1])] == src):
self_loop_sources[src] = True
self_loop_sources_t = torch.as_tensor(self_loop_sources, dtype=torch.bool, device=self.device)
numerical_ok = True
termination_reason = None
timing = defaultdict(float) if profile_categories else None
memory_samples: list[int] = []
runtime_tensors = self.persistent_tensor_audit() + [
tensor_record("membrane_voltage", v), tensor_record("synaptic_state_g", g),
tensor_record("refractory_state", refractory), tensor_record("delay_event_buffer", delay_queue),
tensor_record("spike_counts", spike_counts), tensor_record("stimulated_targets", targets),
]
sampler = None
if monitor_resources:
sampler = GPUSampler(cfg["device"]["launcher_visible_gpu_uuid"], float(cfg["performance"]["gpu_sample_interval_seconds"]))
sampler.start()
cpu_before = resource.getrusage(resource.RUSAGE_SELF)
torch.cuda.synchronize(self.device)
torch.cuda.reset_peak_memory_stats(self.device)
started = time.perf_counter()
completed_steps = 0
abort_flag = cfg["classification"]["abort_supercritical"] if abort_supercritical is None else abort_supercritical
def timed_sync_start() -> float:
if profile_categories:
torch.cuda.synchronize(self.device)
return time.perf_counter()
def timed_sync_end(name: str, started_at: float) -> None:
if profile_categories:
torch.cuda.synchronize(self.device)
assert timing is not None
timing[name] += time.perf_counter() - started_at
for step in range(steps):
t0 = timed_sync_start()
slot = step % queue_len
g.mul_(g_decay).add_(delay_queue[slot])
delay_queue[slot].zero_()
timed_sync_end("delay_queue_and_g", t0)
t0 = timed_sync_start()
refractory.clamp_min_(1).sub_(1)
available = refractory == 0
v[available] += alpha * (g[available] - (v[available] - rest))
v[~available] = reset
timed_sync_end("neuron_state_integration", t0)
t0 = timed_sync_start()
natural = available & (v >= threshold)
poisson = torch.rand(targets.numel(), generator=generator, device=self.device) < probability
stimulated_attempts += int(poisson.sum().item())
accepted = poisson & available[targets]
forced_sources = targets[accepted]
stimulated_spikes += int(forced_sources.numel())
spikes = natural
if forced_sources.numel():
spikes[forced_sources] = True
sources = torch.nonzero(spikes, as_tuple=False).flatten()
count = int(sources.numel())
total_spikes_running += count
peak_step_spikes = max(peak_step_spikes, count)
spike_counts.add_(spikes.to(torch.int32))
timed_sync_end("spike_detection_and_poisson", t0)
if count:
v[sources] = reset
refractory[sources] = refractory_steps
self_loop_transmissions += int(self_loop_sources_t[sources].sum().item())
t0 = timed_sync_start()
edge_ids = expand_rows_profiled(self.indptr, sources, timing)
if edge_ids.numel():
posts = self.indices[edge_ids].to(torch.int64)
arrivals = self.effective[edge_ids]
delay_queue[(step + delay_steps) % queue_len].scatter_add_(0, posts, arrivals)
timed_sync_end("csr_fanout_and_accumulation", t0)
if log_events:
t0 = timed_sync_start()
src_np = sources.detach().cpu().numpy().astype(np.int32, copy=False)
event_steps.append(np.full(count, step, dtype=np.int32))
event_neurons.append(src_np.copy())
timed_sync_end("event_memory_transfer_and_logging", t0)
if step % int(cfg["audit"]["finite_check_interval_steps"]) == 0 or step == steps - 1:
finite = torch.isfinite(v).all() & torch.isfinite(g).all() & torch.isfinite(delay_queue).all()
if not bool(finite.item()):
numerical_ok = False
termination_reason = "nan_or_inf"
completed_steps = step + 1
break
memory_samples.append(torch.cuda.memory_allocated(self.device))
completed_steps = step + 1
if abort_flag and (step + 1) % int(cfg["classification"]["check_interval_steps"]) == 0:
elapsed_s = (step + 1) * dt / 1000.0
active_now = int(torch.count_nonzero(spike_counts).item())
mean_rate_now = total_spikes_running / (self.n * elapsed_s)
if peak_step_spikes / self.n >= float(cfg["classification"]["supercritical_peak_step_fraction"]):
termination_reason = "supercritical_peak_synchrony"
elif active_now / self.n >= float(cfg["classification"]["supercritical_active_fraction"]):
termination_reason = "supercritical_active_fraction"
elif mean_rate_now >= float(cfg["classification"]["supercritical_mean_rate_hz"]):
termination_reason = "supercritical_mean_rate"
if termination_reason:
break
torch.cuda.synchronize(self.device)
wall = time.perf_counter() - started
cpu_after = resource.getrusage(resource.RUSAGE_SELF)
resource_result = sampler.stop() if sampler else {"samples": 0, "gpu_util_mean_percent": None, "gpu_util_max_percent": None, "nvidia_smi_peak_memory_used_mib": None}
cpu_seconds = (cpu_after.ru_utime + cpu_after.ru_stime) - (cpu_before.ru_utime + cpu_before.ru_stime)
spike_counts_np = spike_counts.cpu().numpy().astype(np.int32, copy=True)
active = spike_counts_np > 0
simulated_ms = completed_steps * dt
simulated_s = simulated_ms / 1000.0
total_spikes = int(spike_counts_np.sum(dtype=np.int64))
active_outside = active & ~target_mask_np
sign_np = self.meta["sign"]
descending = np.isin(self.meta["superclass"], ["descending_neuron", "descending_neuron_tbc"])
motor = np.isin(self.meta["superclass"], ["vnc_motor", "cb_motor"])
active_distances = distances[active]
max_depth = int(active_distances[active_distances >= 0].max()) if np.any(active_distances >= 0) else 0
unreachable_active = int(np.count_nonzero(active & (distances < 0)))
active_fraction = int(active.sum()) / self.n
mean_rate = total_spikes / (self.n * simulated_s) if simulated_s else 0.0
supercritical = (
peak_step_spikes / self.n >= float(cfg["classification"]["supercritical_peak_step_fraction"])
or active_fraction >= float(cfg["classification"]["supercritical_active_fraction"])
or mean_rate >= float(cfg["classification"]["supercritical_mean_rate_hz"])
)
if supercritical:
region = "supercritical"
elif max_depth >= int(cfg["classification"]["propagating_min_depth"]) and int(active_outside.sum()) > 0:
region = "propagating"
else:
region = "subcritical"
if log_events and event_path is not None:
steps_np = np.concatenate(event_steps) if event_steps else np.empty(0, dtype=np.int32)
neurons_np = np.concatenate(event_neurons) if event_neurons else np.empty(0, dtype=np.int32)
timestamps_np = steps_np.astype(np.float64) * dt
runtime_tensors.extend([
array_record("event_steps", steps_np),
array_record("event_neuron_indices", neurons_np),
array_record("event_timestamps_ms", timestamps_np),
])
event_path.parent.mkdir(parents=True, exist_ok=True)
pq.write_table(pa.table({
"step": pa.array(steps_np, type=pa.int32()),
"timestamp_ms": pa.array(timestamps_np, type=pa.float64()),
"neuron_idx": pa.array(neurons_np, type=pa.int32()),
}), event_path, compression="zstd")
category_seconds = dict(timing) if timing is not None else {}
measured = sum(category_seconds.values())
category_seconds["python_and_unattributed_overhead"] = max(0.0, wall - measured)
category_percent = {k: (100.0 * value / wall if wall else 0.0) for k, value in category_seconds.items()}
peak_allocated = int(torch.cuda.max_memory_allocated(self.device))
peak_reserved = int(torch.cuda.max_memory_reserved(self.device))
result = {
"population": population,
"population_neurons": int(len(targets_np)),
"rate_hz": float(rate_hz),
"seed": int(seed),
"w_syn_mv": float(w_syn),
"requested_duration_ms": float(duration_ms),
"simulated_duration_ms": simulated_ms,
"requested_steps": steps,
"completed_steps": completed_steps,
"terminated_early": completed_steps < steps,
"termination_reason": termination_reason,
"stimulation_mode": cfg["stimulation"]["mode"],
"stimulated_spike_attempts": stimulated_attempts,
"stimulated_spikes": stimulated_spikes,
"total_spikes": total_spikes,
"active_neurons": int(active.sum()),
"active_neurons_outside_stimulated_population": int(active_outside.sum()),
"mean_firing_rate_hz": mean_rate,
"max_firing_rate_hz": float(spike_counts_np.max(initial=0) / simulated_s) if simulated_s else 0.0,
"spikes_per_biological_second": total_spikes / simulated_s if simulated_s else 0.0,
"first_hop_active_neurons": int(np.count_nonzero(active & (distances == 1))),
"second_hop_active_neurons": int(np.count_nonzero(active & (distances == 2))),
"three_or_more_hop_active_neurons": int(np.count_nonzero(active & (distances >= 3))),
"propagation_depth": max_depth,
"unreachable_active_neurons": unreachable_active,
"excitatory_active_neurons": int(np.count_nonzero(active & (sign_np > 0))),
"inhibitory_active_neurons": int(np.count_nonzero(active & (sign_np < 0))),
"zero_sign_active_neurons": int(np.count_nonzero(active & (sign_np == 0))),
"descending_active_neurons": int(np.count_nonzero(active & descending)),
"descending_spikes": int(spike_counts_np[descending].sum(dtype=np.int64)),
"motor_active_neurons": int(np.count_nonzero(active & motor)),
"motor_spikes": int(spike_counts_np[motor].sum(dtype=np.int64)),
"activity_by_superclass": category_activity(self.meta["superclass"], active, spike_counts_np),
"activity_by_class": category_activity(self.meta["class"], active, spike_counts_np),
"peak_spikes_one_step": peak_step_spikes,
"peak_synchronous_fraction": peak_step_spikes / self.n,
"dynamics_region": region,
"nan_or_inf": not numerical_ok,
"self_loop_transmission_events": self_loop_transmissions,
"wall_clock_seconds": wall,
"realtime_factor_wall_over_biological": wall / simulated_s if simulated_s else None,
"biological_realtime_speed": simulated_s / wall if wall else None,
"steps_per_second": completed_steps / wall if wall else None,
"peak_allocated_vram_bytes": peak_allocated,
"peak_reserved_vram_bytes": peak_reserved,
"observed_allocated_growth_bytes": int(max(memory_samples, default=peak_allocated) - min(memory_samples, default=peak_allocated)),
"memory_leak_suspected": int(max(memory_samples, default=peak_allocated) - min(memory_samples, default=peak_allocated)) > int(cfg["audit"]["memory_growth_tolerance_bytes"]),
"cpu_process_seconds": cpu_seconds,
"cpu_process_utilization_percent_of_one_core": 100.0 * cpu_seconds / wall if wall else None,
"resource_sampling": resource_result,
"event_logging": log_events,
"event_file": str(event_path) if event_path else None,
"runtime_tensors": runtime_tensors,
"profile_category_seconds": category_seconds,
"profile_category_percent": category_percent,
}
del v, g, refractory, delay_queue, spike_counts, targets, self_loop_sources_t
torch.cuda.empty_cache()
return result, spike_counts_np
def run_induced_reference(
cfg: dict[str, Any], exp: Path, meta: dict[str, Any], indptr: np.ndarray,
indices: np.ndarray, raw: np.ndarray, device: torch.device,
) -> dict[str, Any]:
spec = cfg["cpu_gpu_reference"]
n = int(cfg["expected_graph"]["neurons"])
size = int(spec["subgraph_neurons"])
rng = np.random.default_rng(int(spec["seed"]))
nodes = np.sort(rng.choice(n, size=size, replace=False)).astype(np.int64)
inverse = np.full(n, -1, dtype=np.int32)
inverse[nodes] = np.arange(size, dtype=np.int32)
rows: list[int] = []
cols: list[int] = []
weights: list[int] = []
for local_src, global_src in enumerate(nodes):
start, stop = int(indptr[global_src]), int(indptr[global_src + 1])
local_targets = inverse[indices[start:stop]]
keep = local_targets >= 0
count = int(keep.sum())
rows.extend([local_src] * count)
cols.extend(local_targets[keep].tolist())
weights.extend(raw[start:stop][keep].tolist())
rows_np = np.asarray(rows, dtype=np.int64)
cols_np = np.asarray(cols, dtype=np.int64)
raw_np = np.asarray(weights, dtype=np.int64)
local_signs = meta["sign"][nodes]
forced = {0: [0, 1, 2, 3], 22: [0, 1, 2, 3], 44: [0, 1, 2, 3]}
steps = int(spec["steps"])
cpu = tiny_numpy(local_signs, raw_np, rows_np, cols_np, forced, cfg, steps)
gpu = tiny_torch(local_signs, raw_np, rows_np, cols_np, forced, cfg, steps, device)
tolerance = float(spec["absolute_tolerance"])
v_diff = float(np.max(np.abs(cpu["v"] - gpu["v"])))
g_diff = float(np.max(np.abs(cpu["g"] - gpu["g"])))
result = {
"selection": "uniform random without replacement using fixed seed; induced directed edges",
"seed": int(spec["seed"]),
"subgraph_neurons": size,
"induced_edges": int(len(rows_np)),
"steps": steps,
"cpu_events": len(cpu["events"]),
"gpu_events": len(gpu["events"]),
"events_exact_match": cpu["events"] == gpu["events"],
"max_abs_voltage_difference": v_diff,
"max_abs_synaptic_state_difference": g_diff,
"absolute_tolerance": tolerance,
"selected_global_neuron_indices": nodes.tolist(),
"passed": cpu["events"] == gpu["events"] and v_diff <= tolerance and g_diff <= tolerance,
}
write_json(exp / "benchmarks/cpu-gpu-induced-reference.json", result)
if not result["passed"]:
raise RuntimeError("CPU/GPU induced reference failed")
return result
def save_trial(exp: Path, category: str, result: dict[str, Any]) -> Path:
path = trial_path(exp, category, result["population"], result["rate_hz"], result["seed"], result["w_syn_mv"])
write_json(path, result)
return path
def trial_path(exp: Path, category: str, population: str, rate: float, seed: int, weight: float) -> Path:
safe = f"{population}-rate{float(rate):g}-seed{int(seed)}-w{float(weight):g}.json"
return exp / f"benchmarks/{category}/{safe}"
def causal_validation(
cfg: dict[str, Any], exp: Path, event_path: Path, targets: np.ndarray,
indptr: np.ndarray, indices: np.ndarray, raw: np.ndarray, signs: np.ndarray, w_syn: float,
) -> dict[str, Any]:
table = pq.read_table(event_path, columns=["step", "neuron_idx"])
steps = table["step"].to_numpy(zero_copy_only=False).astype(np.int32, copy=False)
neurons = table["neuron_idx"].to_numpy(zero_copy_only=False).astype(np.int32, copy=False)
by_neuron: dict[int, np.ndarray] = {}
for neuron in np.unique(neurons):
by_neuron[int(neuron)] = np.sort(steps[neurons == neuron])
active_set = set(by_neuron)
target_set = {int(x) for x in targets}
delay = int(round(float(cfg["model"]["synaptic_delay_ms"]) / float(cfg["model"]["dt_ms"])))
max_edges = int(cfg["causal_validation"]["max_edges_examined"])
parent: dict[int, dict[str, Any]] = {}
inhibitory: list[dict[str, Any]] = []
edges_examined = 0
edge_integrity_failures = 0
sign_failures = 0
active_mask = np.zeros(len(indptr) - 1, dtype=bool)
active_mask[np.fromiter(active_set, dtype=np.int64)] = True
last_spike = np.full(len(indptr) - 1, -1, dtype=np.int32)
for neuron, neuron_steps in by_neuron.items():
last_spike[neuron] = int(neuron_steps[-1])
for pre, pre_steps in by_neuron.items():
start, stop = int(indptr[pre]), int(indptr[pre + 1])
pre_sign = int(signs[pre])
if pre_sign == 0:
continue
row_posts = indices[start:stop]
candidate_offsets = np.flatnonzero(active_mask[row_posts])
if len(candidate_offsets) == 0:
continue
remaining = max_edges - edges_examined
if remaining <= 0:
break
candidate_offsets = candidate_offsets[:remaining]
candidate_posts = row_posts[candidate_offsets].astype(np.int64, copy=False)
valid_temporal = last_spike[candidate_posts] >= int(pre_steps[0]) + delay
candidate_offsets = candidate_offsets[valid_temporal]
candidate_posts = candidate_posts[valid_temporal]
edges_examined += len(candidate_posts)
for offset, post_np in zip(candidate_offsets, candidate_posts):
edge_id = start + int(offset)
post = int(post_np)
if not (start <= edge_id < stop and int(indices[edge_id]) == post):
edge_integrity_failures += 1
effective = pre_sign * int(raw[edge_id]) * float(w_syn)
if (pre_sign > 0 and effective <= 0) or (pre_sign < 0 and effective >= 0):
sign_failures += 1
post_steps = by_neuron[post]
post_pos = int(np.searchsorted(post_steps, int(pre_steps[0]) + delay, side="left"))
if post_pos >= len(post_steps):
continue
post_step = int(post_steps[post_pos])
pre_pos = int(np.searchsorted(pre_steps, post_step - delay, side="right")) - 1
if pre_pos < 0:
continue
pre_step = int(pre_steps[pre_pos])
record = {
"pre_neuron_idx": pre,
"post_neuron_idx": post,
"pre_spike_step": pre_step,
"arrival_step": pre_step + delay,
"post_spike_step": post_step,
"edge_id": edge_id,
"raw_synapse_count": int(raw[edge_id]),
"presynaptic_sign": pre_sign,
"effective_weight_mv": effective,
"delay_steps": delay,
}
if pre_sign < 0 and len(inhibitory) < int(cfg["causal_validation"]["requested_inhibitory_transmissions"]):
inhibitory.append(record)
if pre_sign > 0 and post not in parent:
parent[post] = record
if edges_examined >= max_edges:
break
supported_neurons = set(parent)
outside_active = active_set - target_set
unsupported = sorted(outside_active - supported_neurons)
candidate_keys = [post for post in parent if post not in target_set]
rng = random.Random(int(cfg["causal_validation"]["seed"]))
rng.shuffle(candidate_keys)
requested = int(cfg["causal_validation"]["requested_chains"])
chains: list[dict[str, Any]] = []
for key in candidate_keys:
edges: list[dict[str, Any]] = []
seen: set[int] = set()
current = key
while current in parent and current not in seen and len(edges) < 64:
seen.add(current)
edge = parent[current]
edges.append(edge)
next_neuron = int(edge["pre_neuron_idx"])
previous = parent.get(next_neuron)
if previous is not None and int(previous["post_spike_step"]) > int(edge["pre_spike_step"]):
break
current = next_neuron
if current in target_set:
break
if edges:
edges.reverse()
valid = all(
int(indices[e["edge_id"]]) == e["post_neuron_idx"]
and e["arrival_step"] - e["pre_spike_step"] == delay
and e["presynaptic_sign"] == int(signs[e["pre_neuron_idx"]])
for e in edges
)
chains.append({"edges": edges, "length": len(edges), "valid": valid})
if len(chains) >= requested:
break
inhibitory = inhibitory[: int(cfg["causal_validation"]["requested_inhibitory_transmissions"])]
result = {
"event_file": str(event_path),
"active_neurons": len(active_set),
"active_outside_stimulated": len(outside_active),
"edges_examined": edges_examined,
"edge_scan_truncated": edges_examined >= max_edges,
"requested_chains": requested,
"validated_chains": len(chains),
"all_chain_edges_valid": all(x["valid"] for x in chains),
"maximum_validated_chain_length": max((x["length"] for x in chains), default=0),
"unsupported_active_neuron_count": len(unsupported),
"unsupported_active_neuron_indices": unsupported,
"edge_integrity_failures": edge_integrity_failures,
"sign_failures": sign_failures,
"requested_inhibitory_transmissions": int(cfg["causal_validation"]["requested_inhibitory_transmissions"]),
"validated_inhibitory_transmissions": len(inhibitory),
"all_inhibitory_weights_negative": all(x["effective_weight_mv"] < 0 for x in inhibitory),
"delay_steps": delay,
"delay_ms": delay * float(cfg["model"]["dt_ms"]),
"chains": chains,
"inhibitory_transmissions": inhibitory,
"no_connectionless_transmission": edge_integrity_failures == 0,
"passed": (
len(chains) >= requested and all(x["valid"] for x in chains)
and edge_integrity_failures == 0 and sign_failures == 0
and len(unsupported) == 0
and len(inhibitory) >= int(cfg["causal_validation"]["requested_inhibitory_transmissions"])
and all(x["effective_weight_mv"] < 0 for x in inhibitory)
),
}
write_json(exp / "propagation/causal-validation.json", result)
return result
def compact_trial(result: dict[str, Any]) -> dict[str, Any]:
keys = [
"population", "population_neurons", "rate_hz", "seed", "w_syn_mv",
"requested_duration_ms", "simulated_duration_ms", "terminated_early", "termination_reason",
"stimulated_spikes", "total_spikes", "active_neurons",
"active_neurons_outside_stimulated_population", "mean_firing_rate_hz", "max_firing_rate_hz",
"first_hop_active_neurons", "second_hop_active_neurons", "three_or_more_hop_active_neurons",
"propagation_depth", "unreachable_active_neurons", "excitatory_active_neurons",
"inhibitory_active_neurons", "zero_sign_active_neurons", "descending_active_neurons",
"descending_spikes", "motor_active_neurons", "motor_spikes", "dynamics_region", "nan_or_inf",
"wall_clock_seconds", "realtime_factor_wall_over_biological", "steps_per_second",
"peak_allocated_vram_bytes", "peak_reserved_vram_bytes", "memory_leak_suspected",
]
return {key: result[key] for key in keys}
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--config", required=True, type=Path)
args = parser.parse_args()
cfg = yaml.safe_load(args.config.read_text(encoding="utf-8"))
exp = Path(cfg["paths"]["experiment_root"]).resolve()
for sub in ("configs", "benchmarks", "propagation", "profiler", "events", "audit", "manifests"):
(exp / sub).mkdir(parents=True, exist_ok=True)
setup_logging(exp / "audit/phase4b.log")
LOG.info("Phase 4B start config=%s", args.config)
random.seed(int(cfg["reproducibility"]["global_seed"]))
np.random.seed(int(cfg["reproducibility"]["global_seed"]))
torch.manual_seed(int(cfg["reproducibility"]["global_seed"]))
paths = exact_input_paths(cfg)
hashes_before = {name: sha256(path) for name, path in paths.items()}
indptr, indices, raw = validate_graph(cfg, paths)
meta = metadata_arrays(cfg, paths["metadata"])
if not torch.cuda.is_available():
raise RuntimeError("CUDA unavailable")
if torch.cuda.device_count() != 1:
raise RuntimeError("exactly one visible GPU is required; mixed multi-GPU is forbidden")
device = torch.device("cuda:0")
props = torch.cuda.get_device_properties(device)
if cfg["device"]["required_name_substring"] not in props.name:
raise RuntimeError(f"required RTX 4090, got {props.name}")
masks = population_masks(cfg, meta)
population_audit, distances = audit_populations(cfg, exp, meta, masks, indptr, indices, raw)
unit_tests = run_dynamics_unit_tests(cfg, exp, device)
induced_reference = run_induced_reference(cfg, exp, meta, indptr, indices, raw, device)
runtime = FullGraphRuntime(cfg, paths, meta, indptr, indices, raw, device)
baseline_w = float(cfg["model"]["w_syn_mv_per_anatomical_synapse"])
runtime.set_weight(baseline_w)
assert runtime.effective is not None
sign_path = exp / "audit/synaptic-sign.int8.npy"
np.save(sign_path, meta["sign"], allow_pickle=False)
effective_path = exp / "audit/effective-weights-0.275.float32.npy"
np.save(effective_path, runtime.effective.cpu().numpy(), allow_pickle=False)
sign_classes = Counter(meta["nt_class"].tolist())
sign_audit = {
"source_field": "neurotransmitter",
"classification_counts": dict(sorted(sign_classes.items())),
"sign_counts": {str(k): int(v) for k, v in sorted(Counter(meta["sign"].tolist()).items())},
"unknown_count": int(sign_classes.get("UNKNOWN", 0)),
"unclear_count": int(sign_classes.get("UNCLEAR", 0)),
"formula": "effective_weight(j->i) = sign(j) * raw_synapse_count(j->i) * W_syn",
"baseline_w_syn_mv": baseline_w,
"transform": "strict linear; no sqrt/log1p/normalization/degree normalization/threshold",
"raw_weights_path": str(paths["raw_weights"]),
"raw_weights_sha256": hashes_before["raw_weights"],
"raw_weights_read_only": True,
"effective_weights_path": str(effective_path),
"effective_weights_sha256": sha256(effective_path),
}
write_json(exp / "audit/sign-and-effective-weight.json", sign_audit)
baseline_results: list[dict[str, Any]] = []
total_baseline = len(masks) * len(cfg["baseline_sweep"]["rates_hz"]) * len(cfg["baseline_sweep"]["seeds"])
trial_number = 0
for population, mask in masks.items():
targets = np.flatnonzero(mask).astype(np.int64)
for rate in cfg["baseline_sweep"]["rates_hz"]:
for seed in cfg["baseline_sweep"]["seeds"]:
trial_number += 1
LOG.info("baseline trial %d/%d population=%s rate=%s seed=%s", trial_number, total_baseline, population, rate, seed)
checkpoint = trial_path(exp, "baseline-trials", population, float(rate), int(seed), baseline_w)
if checkpoint.is_file():
result = json.loads(checkpoint.read_text(encoding="utf-8"))
LOG.info("checkpoint reused %s", checkpoint.name)
else:
result, _ = runtime.run(
population, targets, distances[population], float(rate), int(seed),
float(cfg["baseline_sweep"]["duration_ms"]), baseline_w, False,
)
save_trial(exp, "baseline-trials", result)
baseline_results.append(result)
write_json(exp / "benchmarks/baseline-summary.json", {
"trial_count": len(baseline_results),
"expected_trial_count": total_baseline,
"trials": [compact_trial(x) for x in baseline_results],
"region_counts": dict(Counter(x["dynamics_region"] for x in baseline_results)),
"early_termination_count": sum(bool(x["terminated_early"]) for x in baseline_results),
"maximum_propagation_depth": max(x["propagation_depth"] for x in baseline_results),
})
weight_results: list[dict[str, Any]] = []
weight_pop = str(cfg["weight_sweep"]["population"])
weight_targets = np.flatnonzero(masks[weight_pop]).astype(np.int64)
for weight in cfg["weight_sweep"]["values_mv"]:
LOG.info("weight sweep W_syn=%s", weight)
checkpoint = trial_path(
exp, "weight-sweep", weight_pop, float(cfg["weight_sweep"]["rate_hz"]),
int(cfg["weight_sweep"]["seed"]), float(weight),
)
if checkpoint.is_file():
result = json.loads(checkpoint.read_text(encoding="utf-8"))
LOG.info("checkpoint reused %s", checkpoint.name)
else:
result, _ = runtime.run(
weight_pop, weight_targets, distances[weight_pop], float(cfg["weight_sweep"]["rate_hz"]),
int(cfg["weight_sweep"]["seed"]), float(cfg["weight_sweep"]["duration_ms"]), float(weight), False,
)
save_trial(exp, "weight-sweep", result)
weight_results.append(result)
write_json(exp / "benchmarks/weight-sweep-summary.json", {
"controlled_population": weight_pop,
"controlled_rate_hz": cfg["weight_sweep"]["rate_hz"],
"controlled_seed": cfg["weight_sweep"]["seed"],
"classification_thresholds_fixed_before_run": cfg["classification"],
"trials": [compact_trial(x) for x in weight_results],
"regions": {str(x["w_syn_mv"]): x["dynamics_region"] for x in weight_results},
})
perf_pop = str(cfg["performance"]["population"])
perf_targets = np.flatnonzero(masks[perf_pop]).astype(np.int64)
perf_common = dict(
population=perf_pop, targets_np=perf_targets, distances=distances[perf_pop],
rate_hz=float(cfg["performance"]["rate_hz"]), seed=int(cfg["performance"]["seed"]),
duration_ms=float(cfg["performance"]["duration_ms"]), w_syn=baseline_w,
monitor_resources=True,
)
LOG.info("performance benchmark logging disabled")
perf_disabled, counts_disabled = runtime.run(**perf_common, log_events=False)
LOG.info("performance benchmark full event logging")
perf_event_path = exp / "events/performance-full.events.parquet"
perf_full, counts_full = runtime.run(**perf_common, log_events=True, event_path=perf_event_path)
event_consistency = bool(np.array_equal(counts_disabled, counts_full))
performance = {
"logging_disabled": compact_trial(perf_disabled) | {
"resource_sampling": perf_disabled["resource_sampling"],
"cpu_process_utilization_percent_of_one_core": perf_disabled["cpu_process_utilization_percent_of_one_core"],
},
"full_event_logging": compact_trial(perf_full) | {
"resource_sampling": perf_full["resource_sampling"],
"cpu_process_utilization_percent_of_one_core": perf_full["cpu_process_utilization_percent_of_one_core"],
"event_file": perf_full["event_file"],
},
"spike_count_arrays_exact_match": event_consistency,
"logging_disabled_spike_count_sha256": hashlib.sha256(counts_disabled.tobytes()).hexdigest(),
"full_logging_spike_count_sha256": hashlib.sha256(counts_full.tobytes()).hexdigest(),
}
write_json(exp / "benchmarks/performance-logging-comparison.json", performance)
if not event_consistency:
raise RuntimeError("logging changed spike events")
profile_pop = str(cfg["profiler"]["population"])
profile_targets = np.flatnonzero(masks[profile_pop]).astype(np.int64)
LOG.info("synchronized category profiler")
profile_result, profile_counts_disabled = runtime.run(
profile_pop, profile_targets, distances[profile_pop], float(cfg["profiler"]["rate_hz"]),
int(cfg["profiler"]["seed"]), float(cfg["profiler"]["duration_ms"]), baseline_w,
False, profile_categories=True,
)
write_json(exp / "profiler/category-profile.json", profile_result)
LOG.info("synchronized category profiler with full event logging")
profiler_event_path = exp / "events/profiler-full.events.parquet"
profile_full_result, profile_counts_full = runtime.run(
profile_pop, profile_targets, distances[profile_pop], float(cfg["profiler"]["rate_hz"]),
int(cfg["profiler"]["seed"]), float(cfg["profiler"]["duration_ms"]), baseline_w,
True, event_path=profiler_event_path, profile_categories=True,
)
profile_event_consistency = bool(np.array_equal(profile_counts_disabled, profile_counts_full))
write_json(exp / "profiler/category-profile-full-logging.json", profile_full_result)
if not profile_event_consistency:
raise RuntimeError("profile logging changed spike events")
eligible = [x for x in baseline_results if not x["terminated_early"] and x["active_neurons_outside_stimulated_population"] > 0]
candidate_pool = eligible if eligible else baseline_results
selected = max(candidate_pool, key=lambda x: (x["propagation_depth"], x["active_neurons_outside_stimulated_population"], -x["rate_hz"]))
causal_pop = selected["population"]
causal_targets = np.flatnonzero(masks[causal_pop]).astype(np.int64)
causal_event_path = exp / "events/causal-trial.events.parquet"
causal_trial_path = exp / "propagation/causal-trial.json"
causal_audit_path = exp / "propagation/causal-validation.json"
if causal_event_path.is_file() and causal_trial_path.is_file() and causal_audit_path.is_file():
LOG.info("causal trial and validation checkpoints reused")
causal_trial = json.loads(causal_trial_path.read_text(encoding="utf-8"))
causal = json.loads(causal_audit_path.read_text(encoding="utf-8"))
else:
LOG.info("causal trial population=%s rate=%s seed=%s", causal_pop, selected["rate_hz"], selected["seed"])
causal_trial, _ = runtime.run(
causal_pop, causal_targets, distances[causal_pop], float(selected["rate_hz"]), int(selected["seed"]),
float(cfg["baseline_sweep"]["duration_ms"]), baseline_w, True, event_path=causal_event_path,
)
write_json(causal_trial_path, causal_trial)
causal = causal_validation(cfg, exp, causal_event_path, causal_targets, indptr, indices, raw, meta["sign"], baseline_w)
tensor_records = perf_disabled["runtime_tensors"]
gpu_tensor_bytes = sum(x["bytes"] for x in tensor_records if x["device"].startswith("cuda"))
peak_allocated = perf_disabled["peak_allocated_vram_bytes"]
runtime_memory = {
"tensors_and_arrays": tensor_records,
"full_logging_event_arrays": [x for x in perf_full["runtime_tensors"] if x["name"].startswith("event_")],
"listed_gpu_tensor_bytes": gpu_tensor_bytes,
"peak_allocated_vram_bytes": peak_allocated,
"peak_reserved_vram_bytes": perf_disabled["peak_reserved_vram_bytes"],
"peak_minus_listed_tensor_bytes": peak_allocated - gpu_tensor_bytes,
"explanation": "peak minus listed persistent/state tensors is transient fan-out indexing, spike detection, Poisson, and scatter workspace; no padding copies were created",
"phase4a_234_7_mib_explained": abs(peak_allocated / (1024 ** 2) - 234.7) < 32.0,
}
write_json(exp / "audit/runtime-tensor-memory.json", runtime_memory)
hashes_after = {name: sha256(path) for name, path in paths.items()}
if hashes_before != hashes_after:
raise RuntimeError("Phase 3B input changed during Phase 4B")
summary = {
"status": "PASS_WITH_REPORTED_ANOMALIES" if not causal["passed"] else "PASS",
"graph": cfg["expected_graph"],
"device": {"name": props.name, "total_vram_bytes": int(props.total_memory), "visible_gpu_count": 1},
"strict_shiu_baseline": {
"parameters": cfg["model"],
"formula": sign_audit["formula"],
"forbidden_transforms_used": False,
"implementation_semantics": "dv/dt=(g-(v-V_rest))/tau_m; dg/dt=-g/tau_syn; forward Euler dt=0.1 ms; delayed g increment; v reset; g continues during refractory",
},
"population_audit": {name: {k: v for k, v in rec.items() if k not in ("body_ids", "neuron_indices")} for name, rec in population_audit.items()},
"baseline": {
"trials": len(baseline_results),
"region_counts": dict(Counter(x["dynamics_region"] for x in baseline_results)),
"early_termination_count": sum(bool(x["terminated_early"]) for x in baseline_results),
"maximum_propagation_depth": max(x["propagation_depth"] for x in baseline_results),
"maximum_descending_active": max(x["descending_active_neurons"] for x in baseline_results),
"maximum_motor_active": max(x["motor_active_neurons"] for x in baseline_results),
},
"weight_sweep": {str(x["w_syn_mv"]): compact_trial(x) for x in weight_results},
"unit_tests": unit_tests,
"cpu_gpu_induced_reference": induced_reference,
"causal_validation": {k: v for k, v in causal.items() if k not in ("chains", "inhibitory_transmissions", "unsupported_active_neuron_indices")},
"performance": performance,
"profiler": {
"logging_disabled_wall_clock_seconds": profile_result["wall_clock_seconds"],
"full_logging_wall_clock_seconds": profile_full_result["wall_clock_seconds"],
"simulated_duration_ms": profile_result["simulated_duration_ms"],
"logging_disabled_category_seconds": profile_result["profile_category_seconds"],
"logging_disabled_category_percent": profile_result["profile_category_percent"],
"full_logging_category_seconds": profile_full_result["profile_category_seconds"],
"full_logging_category_percent": profile_full_result["profile_category_percent"],
"spike_count_arrays_exact_match": profile_event_consistency,
},
"runtime_memory": runtime_memory,
"input_hashes_unchanged": True,
"no_training_game_web_or_frontend": True,
}
write_json(exp / "benchmarks/summary.json", summary)
LOG.info("PHASE4B_COMPLETE status=%s", summary["status"])
for handler in logging.getLogger().handlers:
handler.flush()
controls = {exp / "manifests/manifest.json", exp / "manifests/checksums.sha256"}
outputs = sorted(path for path in exp.rglob("*") if path.is_file() and path not in controls)
manifest = {
"phase": "4B",
"created_unix_time": time.time(),
"code": {"path": str(Path(__file__).resolve()), "sha256": sha256(Path(__file__).resolve())},
"config": {"path": str(args.config.resolve()), "sha256": sha256(args.config)},
"software": {"python": platform.python_version(), "torch": torch.__version__, "numpy": np.__version__, "pyarrow": pa.__version__},
"inputs": {name: {"path": str(paths[name]), "sha256": digest} for name, digest in hashes_after.items()},
"outputs": [{"path": str(path), "bytes": path.stat().st_size, "sha256": sha256(path)} for path in outputs],
}
manifest_path = exp / "manifests/manifest.json"
write_json(manifest_path, manifest)
targets = outputs + [manifest_path]
(exp / "manifests/checksums.sha256").write_text(
"\n".join(f"{sha256(path)} {path.relative_to(exp)}" for path in targets) + "\n", encoding="utf-8"
)
if __name__ == "__main__":
main()