604 lines
36 KiB
Python
604 lines
36 KiB
Python
"""Exact, read-only Phase 4AM output comparison; never a learning gate.
|
|
|
|
The sealed AJ comparator is loaded from verified bytes without modifying its
|
|
globals. Full-graph queue identities and refractory acceptance are additionally
|
|
reconstructed from actual events, including zero-weight edges. Only declared
|
|
performance fields may differ. New AM provenance is checked separately, not
|
|
mistaken for the inherited AJ implementation provenance.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import ast
|
|
import copy
|
|
from functools import lru_cache
|
|
import hashlib
|
|
import importlib.util
|
|
import json
|
|
import math
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
|
|
|
|
N = 166700
|
|
E = 25582938
|
|
K = 4064
|
|
CANDIDATES = 14232
|
|
STEPS = 1000
|
|
AJ_SHA = 'af1975ba5e5e700208c0a598d3cbe648a3cf10885c6231239b6e4b28f76d173b'
|
|
HELPER_SHA = '9010f070fba9b7c9cf2d86187ee2abc57f3ac606ebc08a42baad4f3922b51734'
|
|
CPP_SHA = '208a937d17bbb041b9bf6b25b89264ee566830b60609e58491b44583d8541f15'
|
|
CUDA_SHA = '669f166e18ce9a3f63fc723db8b0a8b74f7fa4757194b1bd65b4cf6008a7787e'
|
|
SEALED = {
|
|
'ag_gpu': '69f5d893f47dafff4ef7c3186c3df0b9014666b32c8f4e943b57016bec92b18a',
|
|
'ag_worker': '21ceec65fe42581dc55872010e0025d660e80358905f1cf4df1dadf341a3b291',
|
|
'ae_backend': '03758d07572f4b44d272059d9308fac534268ad201b57bdd77d69fee95e67278',
|
|
'af_queue': 'd72003d98f0f1624b12126eff50bd361c43d888f4a64d90ba9247a58976f1961',
|
|
}
|
|
ADAPTER_PINS = {
|
|
'apl-timing-lab-v1/gpu_timing.py': AJ_SHA,
|
|
'apl-timing-lab-v1/timing_queue.py': '54df6d4818e2a0c26e96fb0aaf5d9b71cd964981706da937562d48af79aca14e',
|
|
'author-apl-lab-v2/gpu_apl.py': SEALED['ag_gpu'],
|
|
'author-apl-lab-v2/legacy_apl_worker.py': SEALED['ag_worker'],
|
|
'author-runtime-lab-v1/gpu_backend.py': SEALED['ae_backend'],
|
|
'author-runtime-lab-v1/author_ext.cpp': CPP_SHA,
|
|
'author-runtime-lab-v1/author_ext.cu': CUDA_SHA,
|
|
'author-fullgraph-lab-v1/oracle_full.py': SEALED['af_queue'],
|
|
}
|
|
HOOK_CODE = '_phase4am_install_overlay(weights, graph, apl_out_mask, upload, out_dir, source_provenance)'
|
|
PERFORMANCE = frozenset(('wall_seconds', 'diagnostic_simulation_seconds', 'build_or_cache_seconds',
|
|
'peak_allocated_vram_bytes', 'peak_reserved_vram_bytes'))
|
|
BRIDGE = 'baseSI64 + (delta32 widened to float64) * float64(0.001); nonzero rows only'
|
|
SCHEDULE = 'before tick 0; after fast APL zeroing'
|
|
GRAPH_FIELDS = ('nodes', 'edge_id', 'src', 'dst', 'ptr', 'signed_raw', 'weights')
|
|
_WEIGHT_CACHE = {}
|
|
_BASE_RAW_CACHE = {}
|
|
|
|
|
|
def require(condition, message):
|
|
if not condition:
|
|
raise ValueError(message)
|
|
|
|
|
|
def _fingerprint(path):
|
|
path = Path(path).resolve()
|
|
stat = path.stat()
|
|
require(path.is_file(), 'Not a regular evidence file: ' + str(path))
|
|
return str(path), stat.st_dev, stat.st_ino, stat.st_size, stat.st_mtime_ns, stat.st_ctime_ns
|
|
|
|
|
|
def _digest_fingerprint(identity):
|
|
# Do not cache byte hashes by stat metadata. Same-size writes can share
|
|
# inode, mtime_ns and ctime_ns even on the actual server filesystem.
|
|
value = hashlib.sha256()
|
|
with Path(identity[0]).open('rb') as source:
|
|
for block in iter(lambda: source.read(8 << 20), b''):
|
|
value.update(block)
|
|
require(_fingerprint(identity[0]) == identity, 'Evidence changed while hashing')
|
|
return value.hexdigest()
|
|
|
|
|
|
def file_hash(path):
|
|
return _digest_fingerprint(_fingerprint(path))
|
|
|
|
|
|
def raw_hash(array):
|
|
return hashlib.sha256(np.ascontiguousarray(array).tobytes()).hexdigest()
|
|
|
|
|
|
@lru_cache(maxsize=2)
|
|
def _helper_from_bytes(identity):
|
|
require(file_hash(identity[0]) == HELPER_SHA, 'Sealed AJ comparator hash changed')
|
|
spec = importlib.util.spec_from_file_location('phase4am_readonly_sealed_aj_compare', identity[0])
|
|
module = importlib.util.module_from_spec(spec)
|
|
exec(compile(Path(identity[0]).read_bytes(), identity[0], 'exec'), module.__dict__)
|
|
return module
|
|
|
|
|
|
def _helper(used):
|
|
path = Path(__file__).resolve().parent.parent / 'apl-timing-lab-v1/compare_timing.py'
|
|
used.add(path)
|
|
sha = file_hash(path)
|
|
require(sha == HELPER_SHA, 'Sealed AJ comparator hash changed')
|
|
return _helper_from_bytes(_fingerprint(path) + (sha,))
|
|
|
|
|
|
def _json(path, used):
|
|
path = Path(path)
|
|
used.add(path)
|
|
def unique(pairs):
|
|
result = {}
|
|
for key, value in pairs:
|
|
require(key not in result, 'Duplicate JSON key: ' + key)
|
|
result[key] = value
|
|
return result
|
|
return json.loads(path.read_text(encoding='utf-8'), object_pairs_hook=unique,
|
|
parse_constant=lambda value: (_ for _ in ()).throw(ValueError('Nonfinite JSON: ' + value)))
|
|
|
|
|
|
def _npz(path, used):
|
|
path = Path(path)
|
|
used.add(path)
|
|
with np.load(path, allow_pickle=False) as data:
|
|
require(len(data.files) == len(set(data.files)), 'Duplicate NPZ key')
|
|
return {key: data[key].copy() for key in data.files}
|
|
|
|
|
|
@lru_cache(maxsize=2)
|
|
def _static_arrays(identities):
|
|
arrays = {Path(identity[0]).name: np.load(identity[0], mmap_mode='r', allow_pickle=False)
|
|
for identity in identities}
|
|
require(all(_fingerprint(identity[0]) == identity[:-1] and file_hash(identity[0]) == identity[-1]
|
|
for identity in identities), 'Static inputs changed while loading')
|
|
return arrays
|
|
|
|
|
|
def _load_case(case_dir, used):
|
|
spec = _json(case_dir / 'spec.json', used)
|
|
require(type(spec['steps']) is int and spec['steps'] == STEPS and spec['dt_ms'] == .1 and
|
|
spec['duration_ms'] == 100. and spec['record_history'] is False and
|
|
type(spec['apl_delay_steps']) is int and spec['apl_delay_steps'] == 18,
|
|
'Phase 4AM comparison requires the unchanged full 100-ms D18 case')
|
|
model = spec['model']
|
|
for key, value in (('synaptic_delay_ms', 1.8), ('refractory_ms', 2.2),
|
|
('w_syn_mv_per_anatomical_synapse', .275), ('resting_potential_mv', -52.),
|
|
('reset_potential_mv', -52.), ('threshold_mv', -45.)):
|
|
require(type(model[key]) in (int, float) and model[key] == value, 'Frozen model field changed: ' + key)
|
|
graph_dir, bundle_dir = Path(spec['graph_dir']), Path(spec['apl_bundle_dir'])
|
|
require(graph_dir.is_absolute() and bundle_dir.is_absolute(), 'Static input paths must be absolute')
|
|
paths = [graph_dir / (key + '.npy') for key in GRAPH_FIELDS]
|
|
paths += [bundle_dir / key for key in ('input_matrix.npy', 'output_matrix.npy', 'kc_globals.npy', 'apl_globals.npy')]
|
|
used.update(paths)
|
|
# Caches for parsed/derived immutable graph data are keyed by FRESH content
|
|
# SHA, not merely a filesystem timestamp. Re-read hashes on every call.
|
|
token = tuple(_fingerprint(p) + (file_hash(p),) for p in paths)
|
|
arrays = _static_arrays(token)
|
|
graph = {key: arrays[key + '.npy'] for key in GRAPH_FIELDS}
|
|
bundle = {key: arrays[key] for key in ('input_matrix.npy', 'output_matrix.npy', 'kc_globals.npy', 'apl_globals.npy')}
|
|
_validate_static(graph, bundle, token)
|
|
requests = _npz(case_dir / 'requests.npz', used)
|
|
require(set(requests) == {'tick', 'neuron_idx'}, 'Unexpected request arrays')
|
|
ticks, nodes = requests['tick'], requests['neuron_idx']
|
|
require(ticks.dtype == nodes.dtype == np.int32 and ticks.ndim == 1 and ticks.shape == nodes.shape and
|
|
np.all((ticks >= 0) & (ticks < STEPS)) and np.all((nodes >= 0) & (nodes < N)), 'Invalid sparse requests')
|
|
keys = ticks.astype(np.int64) * N + nodes
|
|
require(np.all(keys[1:] > keys[:-1]), 'Requests must be sorted unique; no silently deduplicated requests')
|
|
coeff_path = Path(spec['coefficients_path'])
|
|
require(coeff_path.is_absolute(), 'Coefficient path must be absolute')
|
|
coeff = _json(coeff_path, used)
|
|
for key in ('gd', 'c0', 'cg', 'cv', 'threshold', 'reset', 'seed_volt'):
|
|
require(type(coeff[key]) in (int, float) and math.isfinite(coeff[key]), 'Invalid author coefficient: ' + key)
|
|
require(coeff['threshold'] == model['threshold_mv'] * .001 and
|
|
coeff['reset'] == model['reset_potential_mv'] * .001 and coeff['seed_volt'] > coeff['threshold'],
|
|
'Author coefficient units/threshold mismatch')
|
|
input_hashes = {'spec.json': file_hash(case_dir / 'spec.json'),
|
|
'requests.npz': file_hash(case_dir / 'requests.npz'), 'base_weights.npy': file_hash(graph_dir / 'weights.npy'),
|
|
'coefficients.json': file_hash(coeff_path)}
|
|
if (case_dir / 'arrays.npz').exists():
|
|
used.add(case_dir / 'arrays.npz')
|
|
input_hashes['arrays.npz'] = file_hash(case_dir / 'arrays.npz')
|
|
return dict(spec=spec, graph=graph, bundle=bundle, token=token, input_hashes=input_hashes,
|
|
requested=list(zip(map(int, ticks), map(int, nodes))), graph_dir=graph_dir, bundle_dir=bundle_dir)
|
|
|
|
|
|
_VALIDATED_STATIC = set()
|
|
|
|
|
|
def _validate_static(graph, bundle, token):
|
|
if token in _VALIDATED_STATIC:
|
|
return
|
|
for key, dtype, shape in (('nodes', np.int32, (N,)), ('ptr', np.int64, (N + 1,)),
|
|
('edge_id', np.int64, (E,)), ('src', np.int32, (E,)), ('dst', np.int32, (E,)),
|
|
('signed_raw', np.int64, (E,)), ('weights', np.float64, (E,))):
|
|
require(graph[key].dtype == dtype and graph[key].shape == shape, 'Canonical graph schema changed: ' + key)
|
|
require(np.array_equal(graph['nodes'], np.arange(N, dtype=np.int32)), 'Global neuron identity changed')
|
|
ptr = graph['ptr']
|
|
require(ptr[0] == 0 and ptr[-1] == E and np.all(np.diff(ptr) >= 0), 'Invalid canonical CSR')
|
|
for start in range(0, E, 1 << 20):
|
|
stop = min(E, start + (1 << 20))
|
|
expected_ids = np.arange(start, stop, dtype=np.int64)
|
|
require(np.array_equal(graph['edge_id'][start:stop], expected_ids), 'Original edge identity changed')
|
|
require(np.array_equal(graph['src'][start:stop], np.searchsorted(ptr[1:], expected_ids, side='right')),
|
|
'CSR source order changed')
|
|
require(np.all((graph['dst'][start:stop] >= 0) & (graph['dst'][start:stop] < N)), 'Invalid edge targets')
|
|
original = graph['weights'][start:stop]
|
|
expected = graph['signed_raw'][start:stop] * (.275 * .001)
|
|
require(np.isfinite(original).all() and raw_hash(original) == raw_hash(expected), 'Original signed SI weights changed')
|
|
for name, dtype, shape in (('kc_globals.npy', np.int32, (K,)), ('apl_globals.npy', np.int32, (2,)),
|
|
('input_matrix.npy', np.float32, (2, N)), ('output_matrix.npy', np.float32, (2, K))):
|
|
a = bundle[name]
|
|
require(a.dtype == dtype and a.shape == shape and np.isfinite(a).all(), 'Invalid APL bundle ' + name)
|
|
if dtype == np.int32:
|
|
require(np.all((a >= 0) & (a < N)) and np.all(a[1:] > a[:-1]), 'Invalid APL/KC IDs')
|
|
else:
|
|
require(np.all(a >= 0), 'Negative normalized APL matrix')
|
|
require(not np.intersect1d(bundle['kc_globals.npy'], bundle['apl_globals.npy']).size, 'APL/KC overlap')
|
|
_VALIDATED_STATIC.add(token)
|
|
|
|
|
|
def _effective_hash(case, ids=None, delta=None):
|
|
"""Independent static bridge, cached per immutable graph/delta identity."""
|
|
key = (case['token'], None if ids is None else raw_hash(ids), None if delta is None else raw_hash(delta))
|
|
if key in _WEIGHT_CACHE:
|
|
return _WEIGHT_CACHE[key]
|
|
graph, apl = case['graph'], case['bundle']['apl_globals.npy']
|
|
baseline, effective = hashlib.sha256(), hashlib.sha256()
|
|
masked = 0
|
|
for start in range(0, len(graph['edge_id']), 1 << 20):
|
|
stop = min(len(graph['edge_id']), start + (1 << 20))
|
|
value = graph['weights'][start:stop].copy()
|
|
fast = np.isin(graph['src'][start:stop], apl)
|
|
masked += int(fast.sum())
|
|
value[fast] = np.float64(0)
|
|
baseline.update(value.astype('<f8', copy=False).tobytes())
|
|
if ids is not None:
|
|
lo, hi = np.searchsorted(ids, (start, stop))
|
|
ix, change = ids[lo:hi] - start, delta[lo:hi]
|
|
require(not fast[ix].any(), 'Plastic mask overlaps fast APL outputs')
|
|
nz = change != 0
|
|
value[ix[nz]] = np.add(value[ix[nz]], np.multiply(change[nz].astype(np.float64), np.float64(.001)))
|
|
require(np.isfinite(value[ix[nz]]).all() and np.all(value[ix[nz]] > 0), 'Static overlay changes candidate sign')
|
|
effective.update(value.astype('<f8', copy=False).tobytes())
|
|
result = (baseline.hexdigest(), effective.hexdigest(), masked)
|
|
_WEIGHT_CACHE[key] = result
|
|
return result
|
|
|
|
|
|
def _base_raw_hash(case):
|
|
key = case['token']
|
|
if key not in _BASE_RAW_CACHE:
|
|
h = hashlib.sha256()
|
|
values = case['graph']['weights']
|
|
for start in range(0, len(values), 1 << 20):
|
|
h.update(values[start:start + (1 << 20)].astype('<f8', copy=False).tobytes())
|
|
_BASE_RAW_CACHE[key] = h.hexdigest()
|
|
return _BASE_RAW_CACHE[key]
|
|
|
|
|
|
def _self_consistency(run, graph, requested, steps, helper, role, checks, differences):
|
|
"""Reconstruct every queue hash, request gate, and ref hash from events.
|
|
|
|
No edge weight, claimed queue digest, or stored PASS determines the result.
|
|
Expansion touches only the current delayed spike sources, not all edges.
|
|
"""
|
|
state, ptr, nodes = run['state'], graph['ptr'], graph['nodes']
|
|
n = len(nodes)
|
|
local = np.searchsorted(nodes, state['neuron_idx'])
|
|
require(np.all(local < n) and np.array_equal(nodes[local], state['neuron_idx']), 'Spike outside graph')
|
|
keys = state['step'].astype(np.int64) * N + state['neuron_idx']
|
|
require(np.all(keys[1:] > keys[:-1]), 'Duplicate or unordered actual events')
|
|
require(np.array_equal(np.bincount(local, minlength=n), state['counts']), 'Event counts do not match per-neuron counts')
|
|
require([(r['tick'], r['neuron_idx']) for r in run['requests']] == requested, 'Request input identity differs')
|
|
bounds = np.searchsorted(state['step'], np.arange(steps + 1))
|
|
rq_bounds = np.searchsorted([r['tick'] for r in run['requests']], np.arange(steps + 1))
|
|
last = np.full(n, -1_000_000_000, np.int64)
|
|
failures = {}
|
|
expected_due_total = expected_accepted_total = 0
|
|
for tick in range(steps):
|
|
fired = local[bounds[tick]:bounds[tick + 1]]
|
|
if np.any(tick - last[fired] < 22):
|
|
failures.setdefault('refractory_spike_violation', tick)
|
|
for row in run['requests'][rq_bounds[tick]:rq_bounds[tick + 1]]:
|
|
target = int(np.searchsorted(nodes, row['neuron_idx']))
|
|
require(target < n and nodes[target] == row['neuron_idx'], 'Request outside canonical graph')
|
|
accepted = tick - last[target] >= 22
|
|
if row['accepted'] != int(accepted) or row['rejected'] != int(not accepted):
|
|
failures.setdefault('request_refractory_gate', tick)
|
|
if bool(np.any(fired == target)) != bool(accepted):
|
|
failures.setdefault('request_actual_spike', tick)
|
|
last[fired] = tick
|
|
ref = np.maximum(22 - (tick - last), 0).astype(np.int32)
|
|
if helper.raw_hash(ref, '<i4') != run['hashes'][tick]['ref_sha256']:
|
|
failures.setdefault('all_tick_ref_hash', tick)
|
|
sources = local[bounds[tick - 18]:bounds[tick - 17]] if tick >= 18 else np.empty(0, np.int64)
|
|
pieces = [np.arange(ptr[source], ptr[source + 1], dtype=np.int64) for source in sources]
|
|
rows = np.concatenate(pieces) if pieces else np.empty(0, np.int64)
|
|
accepted = (ref[graph['dst'][rows]] == 0).astype(np.uint8)
|
|
expected = helper._queue_digest(tick, graph['edge_id'][rows], accepted)
|
|
expected_due_total += len(rows)
|
|
expected_accepted_total += int(accepted.sum())
|
|
if expected != run['queues'][tick]:
|
|
failures.setdefault('full_queue_edge_order_and_acceptance', tick)
|
|
if not np.array_equal(ref, state['ref']):
|
|
failures['final_ref_from_events'] = steps - 1
|
|
summary = run['summary']
|
|
if summary.get('arriving_edges') != expected_due_total or summary.get('accepted_arriving_edges') != expected_accepted_total:
|
|
failures['queue_summary_counts'] = 'mismatch'
|
|
checks[role + '.full_event_queue_request_accounting'] = not failures
|
|
if failures:
|
|
differences[role + '.independent_accounting'] = failures
|
|
|
|
|
|
def _overlay(run, case, used, zero_required=False):
|
|
folder = run['directory']
|
|
audit = _json(folder / 'overlay-audit.json', used)
|
|
require(audit['schema'] == 'phase4am-overlay-audit-v1' and audit['complete'] is True, 'Incomplete overlay audit')
|
|
source = audit['input']
|
|
inputs = {}
|
|
for label in ('overlay', 'manifest', 'mask', 'base_candidate'):
|
|
filename, sha = source[label + '_file'], source[label + '_sha256']
|
|
if filename is None:
|
|
require(label == 'base_candidate' and sha is None, 'Required overlay input absent')
|
|
inputs[label] = None
|
|
continue
|
|
path = Path(filename)
|
|
require(path.is_absolute(), 'Overlay provenance input must be absolute')
|
|
used.add(path)
|
|
require(file_hash(path) == sha, 'Overlay input hash mismatch: ' + label)
|
|
inputs[label] = path
|
|
manifest = _json(inputs['manifest'], used)
|
|
require(manifest['schema'] == 'phase4am-overlay-v1' and manifest['overlay_sha256'] == source['overlay_sha256'] and
|
|
manifest['mask_sha256'] == source['mask_sha256'] and Path(manifest['mask_path']).resolve() == inputs['mask'].resolve(),
|
|
'Overlay manifest linkage mismatch')
|
|
require(manifest['base_candidate_sha256'] == source['base_candidate_sha256'] and
|
|
(manifest['base_candidate_path'] is None if inputs['base_candidate'] is None else
|
|
Path(manifest['base_candidate_path']).resolve() == inputs['base_candidate'].resolve()), 'Candidate floor provenance mismatch')
|
|
require(Path(manifest['aj_model_dir']).resolve() == Path(__file__).resolve().parent.parent / 'apl-timing-lab-v1',
|
|
'Overlay is not bound to the sealed AJ model')
|
|
require(audit['copied_overlay_file'] == 'overlay-input.npz' and audit['copied_manifest_file'] == 'overlay-manifest.json',
|
|
'Unexpected copied overlay filenames')
|
|
for label, name in (('overlay', 'overlay-input.npz'), ('manifest', 'overlay-manifest.json')):
|
|
used.add(folder / name)
|
|
require(file_hash(folder / name) == source[label + '_sha256'], 'Copied input mismatch')
|
|
arrays = _npz(inputs['overlay'], used)
|
|
require(set(arrays) in ({'edge_id', 'plastic_delta_mv'}, {'edge_id', 'plastic_delta_mv', 'base_candidate_mv'}), 'Overlay NPZ schema changed')
|
|
ids, delta = arrays['edge_id'], arrays['plastic_delta_mv']
|
|
require(ids.dtype == np.int64 and ids.shape == (CANDIDATES,) and np.all(ids[1:] > ids[:-1]) and
|
|
np.all((ids >= 0) & (ids < len(case['graph']['edge_id']))), 'Invalid original plastic edge IDs')
|
|
mask = np.load(inputs['mask'], allow_pickle=False)
|
|
require(mask.dtype == np.int64 and mask.shape == ids.shape and np.array_equal(mask, ids), 'Frozen plastic mask identity mismatch')
|
|
require(delta.dtype == np.float32 and delta.shape == ids.shape and np.isfinite(delta).all() and np.all(delta <= 0), 'Invalid LTD delta')
|
|
audit_arrays = dict(arrays)
|
|
name = manifest['overlay_name']
|
|
require(name in ('zero', 'static25') and audit['overlay_name'] == name, 'Unknown overlay intervention')
|
|
if zero_required:
|
|
require(name == 'zero', 'Zero-regression cannot use a nonzero intervention')
|
|
if name == 'zero':
|
|
require(not np.any(delta != 0), 'Zero overlay contains nonzero delta')
|
|
else:
|
|
require(inputs['base_candidate'] is not None, 'Static25 requires its frozen float32 mV floor')
|
|
if inputs['base_candidate'] is not None:
|
|
base = np.load(inputs['base_candidate'], allow_pickle=False)
|
|
require(base.dtype == np.float32 and base.shape == ids.shape and np.isfinite(base).all() and np.all(base > 0), 'Invalid old float32 base')
|
|
floor = np.multiply(base, np.float32(-.25))
|
|
require(np.all(delta >= floor), 'Delta exceeds the existing depression bound')
|
|
if 'base_candidate_mv' in arrays:
|
|
require(arrays['base_candidate_mv'].dtype == np.float32 and raw_hash(arrays['base_candidate_mv']) == raw_hash(base), 'Embedded old base differs')
|
|
if name == 'static25':
|
|
require(raw_hash(delta) == raw_hash(floor), 'Static25 is not the declared old float32 floor')
|
|
audit_arrays['base_candidate_mv'] = base
|
|
else:
|
|
require('base_candidate_mv' not in arrays, 'Embedded unanchored old base is forbidden')
|
|
expected_arrays = {key: dict(dtype=str(value.dtype), shape=list(value.shape), raw_sha256=raw_hash(value))
|
|
for key, value in audit_arrays.items()}
|
|
require(audit['arrays'] == expected_arrays, 'Overlay array audit differs from actual bytes')
|
|
require(np.isin(case['graph']['src'][ids], case['bundle']['kc_globals.npy']).all() and
|
|
np.all(case['graph']['weights'][ids] > 0), 'Plastic edge source/sign changed')
|
|
baseline_sha, effective_sha, apl_count = _effective_hash(case, ids, delta)
|
|
graph = case['graph']
|
|
for key, value in {'candidate_count': CANDIDATES, 'nonzero_delta_count': int(np.count_nonzero(delta)),
|
|
'mapped_candidate_count': CANDIDATES, 'missing_candidate_count': 0,
|
|
'installed_nonzero_count': int(np.count_nonzero(delta)), 'delta_dtype': 'float32', 'delta_units': 'mV',
|
|
'state_dtype': 'float64', 'bridge': BRIDGE, 'installation_schedule': SCHEDULE,
|
|
'weights_updated_during_run': False, 'learning': False, 'zero_delta_no_write': True,
|
|
'frozen_mask_exact': True, 'bounds_pass': True, 'apl_overlap_count': 0,
|
|
'apl_fast_edges_zeroed': apl_count, 'base_graph_files_modified': False,
|
|
'untouched_weights_bitwise_preserved': True,
|
|
'base_weights_file_sha256': file_hash(case['graph_dir'] / 'weights.npy'),
|
|
'base_weights_raw_sha256': _base_raw_hash(case),
|
|
'effective_weights_raw_sha256': effective_sha, 'expected_effective_weights_raw_sha256': effective_sha}.items():
|
|
require(type(audit.get(key)) is type(value) and audit[key] == value, 'Overlay audit mismatch: ' + key)
|
|
require(run['summary']['effective_weights_raw_sha256'] == effective_sha, 'Installed effective-weight hash differs from independent bridge')
|
|
if zero_required:
|
|
require(effective_sha == baseline_sha, 'Zero overlay changed the baseline effective weights')
|
|
require(audit['inherited_aj_source_provenance'] == run['summary']['source_provenance'] and
|
|
audit['inherited_source_audit_scope'] == 'AJ source provenance only; AM overlay semantics are recorded separately' and
|
|
audit['static25_is_exact_fraction_of_float64_base_claimed'] is False and
|
|
audit['arrival_weight_sampling'] == 'arrival time; delta fixed for entire trial', 'Overlay semantic scope mismatch')
|
|
return audit
|
|
|
|
|
|
def _expected_ast(source_path, adapter_path):
|
|
"""Independently derive just the declared Expr insertion; no adapter import."""
|
|
tree = ast.parse(Path(source_path).read_bytes())
|
|
original = [node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == 'run_gpu']
|
|
require(len(original) == 1, 'Pinned AJ run_gpu not unique')
|
|
original = original[0]
|
|
adapted = copy.deepcopy(original)
|
|
tries = [node for node in adapted.body if isinstance(node, ast.Try)]
|
|
require(len(tries) == 1, 'Pinned run_gpu try not unique')
|
|
statements = tries[0].body
|
|
dump = lambda node: ast.dump(node, annotate_fields=True, include_attributes=False)
|
|
anchor = dump(ast.parse('effective_hash = hashlib.sha256()').body[0])
|
|
indices = [i for i, node in enumerate(statements) if dump(node) == anchor]
|
|
require(len(indices) == 1, 'Pinned hook anchor not unique')
|
|
index = indices[0]
|
|
previous = ast.parse('if bool(np.any(apl_out_mask)):\n weights[upload(apl_out_mask, torch.bool)] = 0.0').body[0]
|
|
require(index > 0 and dump(statements[index - 1]) == dump(previous), 'Hook is not immediately after APL zeroing')
|
|
statements.insert(index, ast.parse(HOOK_CODE).body[0])
|
|
loops = lambda node: [item for item in ast.walk(node) if isinstance(item, ast.For) and
|
|
isinstance(item.target, ast.Name) and item.target.id == 'tick']
|
|
before, after = loops(original), loops(adapted)
|
|
require(len(before) == len(after) == 1 and dump(before[0]) == dump(after[0]), 'Hook changed loop AST')
|
|
sha = lambda node: hashlib.sha256(dump(node).encode('utf-8')).hexdigest()
|
|
return dict(original_run_ast_sha256=sha(original), adapted_run_ast_sha256=sha(adapted),
|
|
loop_ast_sha256=sha(before[0]), entire_tick_loop_ast_identical=True, only_ast_change=HOOK_CODE,
|
|
inserted_ast_statements=1, hook_after_apl_zero_before_effective_hash=True,
|
|
derived_namespace_file=str(adapter_path.absolute()), original_module_file=str(Path(source_path).absolute()),
|
|
original_module_globals_patched=False, fake_original_file_used=False)
|
|
|
|
|
|
def _adapter_receipt(run, used):
|
|
folder = run['directory']
|
|
require(not (folder / 'adapter-error.json').exists(), 'Adapter recorded an error')
|
|
receipt = _json(folder / 'adapter-provenance.json', used)
|
|
require(receipt['schema'] == 'phase4am-adapter-provenance-v1' and receipt['complete'] is True,
|
|
'Completed inherited AJ summary is insufficient without completed AM receipt')
|
|
model = Path(__file__).resolve().parent
|
|
for label, filename in (('adapter', 'adapter.py'), ('overlay_module', 'overlay.py')):
|
|
path = Path(receipt[label + '_file'])
|
|
require(path.is_absolute() and path.resolve() == model / filename, 'Unexpected new wrapper source path')
|
|
used.add(path)
|
|
require(file_hash(path) == receipt[label + '_sha256'], 'Wrapper source hash changed')
|
|
source_rows = {}
|
|
for relative, sha in ADAPTER_PINS.items():
|
|
path = model.parent / relative
|
|
used.add(path)
|
|
require(file_hash(path) == sha, 'Frozen source changed: ' + relative)
|
|
source_rows[relative] = {'path': str(path.absolute()), 'sha256': sha}
|
|
require(receipt['sealed_sources'] == source_rows, 'Wrapper sealed-source inventory differs')
|
|
require(receipt['ast'] == _expected_ast(model.parent / 'apl-timing-lab-v1/gpu_timing.py', model / 'adapter.py'),
|
|
'Wrapper AST does not match independently reconstructed single pre-loop hook')
|
|
for key, value in dict(installation_calls=1, weights_updated_during_run=False, learning=False, apl_delay_steps=18,
|
|
original_module_globals_patched=False, delta_constant_for_entire_trial=True,
|
|
inherited_source_audit_describes_AM_semantics=False).items():
|
|
require(type(receipt.get(key)) is type(value) and receipt[key] == value, 'Wrapper receipt mismatch: ' + key)
|
|
require(receipt['scope'] == 'constant sparse overlay installation only; no within-trial plasticity or learning', 'Wrapper scope changed')
|
|
require(receipt['overlay_audit_sha256'] == file_hash(folder / 'overlay-audit.json') and
|
|
receipt['inherited_summary_sha256'] == file_hash(folder / 'summary.json') and
|
|
receipt['effective_weights_raw_sha256'] == run['summary']['effective_weights_raw_sha256'], 'Wrapper receipt not bound to output')
|
|
return {key: value for key, value in receipt.items() if key != 'inherited_summary_sha256'}
|
|
|
|
|
|
def _provenance(run, case, helper, used):
|
|
summary, component = run['summary'], run['component']
|
|
require(summary['input_sha256'] == case['input_hashes'], 'Actual case/weight/coefficient input hash differs')
|
|
require(summary['all_finite'] is True and summary['counts_match_sparse_events'] is True and
|
|
summary['learning'] is False and summary['state_units'] == 'SI volts', 'Invalid finite/state/learning declaration')
|
|
require(summary['sealed_ae_backend_sha256'] == SEALED['ae_backend'], 'Frozen backend changed')
|
|
build = summary['build']
|
|
require(build['sources'] == {'author_ext.cpp': CPP_SHA, 'author_ext.cu': CUDA_SHA} and
|
|
build['cuda_flags'] == ['-O2', '--fmad=false', '--ftz=false', '--prec-div=true', '--prec-sqrt=true'] and
|
|
build['arch_list'] == '8.9', 'Build arithmetic/source contract changed')
|
|
source = _json(run['directory'] / 'source-audit.json', used)
|
|
require(source == summary['source_provenance'] and source['sealed_source_sha256'] == SEALED and
|
|
source['candidate_sha256'] == AJ_SHA and source['module_globals_patched'] is False, 'Inherited AJ source audit changed')
|
|
diff = run['directory'] / 'sealed-gpu-source.diff'
|
|
used.add(diff)
|
|
require(file_hash(diff) == source['source_diff_sha256'], 'Inherited source diff hash mismatch')
|
|
bundle = case['bundle']
|
|
require(component['files_sha256'] == {name: file_hash(case['bundle_dir'] / name) for name in bundle} and
|
|
component['array_sha256'] == {name: raw_hash(value) for name, value in bundle.items()}, 'Actual APL bundle bytes changed')
|
|
require(component['ok'] is True and component['protocol'] == 'phase4ag-apl-v1' and
|
|
component['n_neurons'] == N and component['n_apl'] == 2 and component['n_kc'] == K and
|
|
component['dt_ms'] == .1, 'APL component dimensions/clock changed')
|
|
for key, value in (('decay_f32_bits', np.float32(math.exp(-.1 / 20.))), ('gain_f32_bits', np.float32(.01))):
|
|
require(component[key] == f'{int(value.view(np.uint32)):08x}', 'APL constant changed: ' + key)
|
|
env = component['environment']
|
|
require(env['cuda_matmul_allow_tf32'] is False and env['float32_matmul_precision'] == 'highest' and
|
|
env['precision_configuration_changed_by_component'] is False and '4090' in env['device_name'],
|
|
'GPU precision environment changed')
|
|
require(summary['apl_schedule'] == 'end, after threshold/reset and guarded arrivals, unconditional all KC' and
|
|
summary['apl_si_conversion'] == 'applied h32 -> float64 -> multiply float64 0.001 -> separate float64 subtraction',
|
|
'APL scheduling or SI conversion changed')
|
|
for key in PERFORMANCE:
|
|
require(type(summary[key]) in (int, float) and math.isfinite(summary[key]) and summary[key] >= 0, 'Invalid performance field: ' + key)
|
|
|
|
|
|
def _summary_pair(left, right, checks, differences):
|
|
a = {k: v for k, v in left.items() if k not in PERFORMANCE}
|
|
b = {k: v for k, v in right.items() if k not in PERFORMANCE}
|
|
checks['summary.nonperformance_exact'] = a == b
|
|
if a != b:
|
|
differences['summary'] = {key: {'left': a.get(key), 'right': b.get(key)}
|
|
for key in sorted(set(a) | set(b)) if a.get(key) != b.get(key) or (key in a) != (key in b)}
|
|
|
|
|
|
def compare_runs(case_dir, left, right, output_path=None, mode='zero'):
|
|
"""Zero-vs-sealed-AK or AM repeat equality; failures return a failed report.
|
|
|
|
``output_path=None`` is read-only. A provided report is created exclusively,
|
|
outside both run directories and the input case. Parent protocol must bind
|
|
the case and old manifests; this comparator reports exactly the bytes read.
|
|
"""
|
|
case_dir, left, right = (Path(p).resolve() for p in (case_dir, left, right))
|
|
if output_path is not None:
|
|
output_path = Path(output_path).resolve()
|
|
require(not output_path.exists(), 'Refusing existing comparison output')
|
|
require(not any(output_path.is_relative_to(p) for p in (case_dir, left, right)), 'Comparison must not write into input evidence')
|
|
checks, differences, used = {}, {}, {Path(__file__).resolve()}
|
|
report = dict(schema='phase4am-comparison-v1', mode=mode, case_dir=str(case_dir), left=str(left), right=str(right),
|
|
pass_all=False, learning_achieved=False, candidate_generated=False, tolerance=None,
|
|
scope='Exact declared trajectory and static overlay integrity; no physiological or learning PASS',
|
|
explicit_exclusions=sorted(PERFORMANCE), independent_cpu_wholebrain_oracle_claim=False)
|
|
try:
|
|
require(mode in ('zero', 'repeat', 'self'), 'Unknown comparison mode')
|
|
require((left == right) == (mode == 'self'), 'Only explicit single-run validation may use one directory')
|
|
helper = _helper(used)
|
|
case = _load_case(case_dir, used)
|
|
spec, graph = case['spec'], case['graph']
|
|
n, edges, steps = len(graph['nodes']), len(graph['edge_id']), spec['steps']
|
|
sides = ((left, 'left_gpu'),) if mode == 'self' else ((left, 'left_gpu'), (right, 'right_gpu'))
|
|
runs = [helper._load_run(path, role, n, edges, steps, False, spec['name'], 18, used, checks)
|
|
for path, role in sides]
|
|
for role, run in zip(('left_gpu', 'right_gpu'), runs):
|
|
_provenance(run, case, helper, used)
|
|
require(all(np.isfinite(a).all() for a in run['state'].values()), 'Nonfinite final state/events')
|
|
_self_consistency(run, graph, case['requested'], steps, helper, role, checks, differences)
|
|
helper._timing_sequence_checks(run['apl']['inhibition'], run['apl']['applied'], run['pending'], 18, role, checks, differences)
|
|
helper._timing_metadata_checks(run, role, 18, steps, checks)
|
|
require(not (run['directory'] / 'first-failure-state.npz').exists(), 'Recorded first numerical failure exists')
|
|
if mode != 'self':
|
|
helper._pair_outputs(*runs, checks, differences, n, steps, False)
|
|
require(runs[0]['timing'] == runs[1]['timing'], 'APL timing metadata differs')
|
|
_summary_pair(runs[0]['summary'], runs[1]['summary'], checks, differences)
|
|
baseline_sha, _, apl_count = _effective_hash(case)
|
|
for run in runs:
|
|
require(run['summary']['apl_fast_edges_zeroed'] == apl_count and run['summary']['kc_targets_present'] == K,
|
|
'Fast APL or KC coverage changed')
|
|
if mode == 'zero':
|
|
require(not (left / 'overlay-audit.json').exists(), 'Zero reference must be the unwrapped sealed AK run')
|
|
require(runs[0]['summary']['effective_weights_raw_sha256'] == baseline_sha, 'Sealed reference effective weights changed')
|
|
audit = _overlay(runs[1], case, used, zero_required=True)
|
|
_adapter_receipt(runs[1], used)
|
|
report['overlay_name'] = audit['overlay_name']
|
|
elif mode == 'repeat':
|
|
a, b = [_overlay(run, case, used) for run in runs]
|
|
require(a == b, 'Repeated overlay provenance/identity differs')
|
|
receipts = [_adapter_receipt(run, used) for run in runs]
|
|
require(receipts[0] == receipts[1], 'Repeated wrapper provenance differs beyond bound walltime-bearing summary')
|
|
report['overlay_name'] = a['overlay_name']
|
|
else:
|
|
report['overlay_name'] = _overlay(runs[0], case, used)['overlay_name']
|
|
_adapter_receipt(runs[0], used)
|
|
report['scope'] = 'Single-run event/queue/input/static-overlay self-consistency; no repeat, equivalence, physiology or learning PASS'
|
|
checks['input_overlay_and_source_provenance'] = True
|
|
report.update(nodes=n, edges=edges, steps=steps, apl_delay_steps=18,
|
|
full_queue_reconstructed=True, total_spikes=len(runs[0]['state']['step']))
|
|
except Exception as error:
|
|
report['error'] = {'type': type(error).__name__, 'message': str(error)}
|
|
manifest = []
|
|
try:
|
|
for path in sorted(used):
|
|
manifest.append(dict(path=str(path.resolve()), bytes=path.stat().st_size, sha256=file_hash(path)))
|
|
except Exception as error:
|
|
report['read_manifest_error'] = {'type': type(error).__name__, 'message': str(error)}
|
|
report.update(checks={key: bool(value) for key, value in checks.items()}, differences=differences,
|
|
read_manifest=manifest, failed_checks=sorted(key for key, value in checks.items() if not value))
|
|
report['pass_all'] = bool(checks) and all(checks.values()) and 'error' not in report and 'read_manifest_error' not in report
|
|
if output_path is not None:
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
with output_path.open('x', encoding='utf-8') as target:
|
|
json.dump(report, target, indent=2, sort_keys=True, allow_nan=False)
|
|
target.write('\n')
|
|
return report
|
|
|
|
|
|
def validate_run(case_dir, output_dir, output_path=None):
|
|
"""Single-run static-overlay integrity only; not numerical equivalence."""
|
|
return compare_runs(case_dir, output_dir, output_dir, output_path, mode='self')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--case', required=True)
|
|
parser.add_argument('--left', required=True)
|
|
parser.add_argument('--right', required=True)
|
|
parser.add_argument('--output', required=True)
|
|
parser.add_argument('--mode', choices=('zero', 'repeat', 'self'), required=True)
|
|
args = parser.parse_args()
|
|
result = compare_runs(args.case, args.left, args.right, args.output, args.mode)
|
|
print(json.dumps({'pass_all': result['pass_all'], 'failed_checks': result['failed_checks'], 'error': result.get('error')}, allow_nan=False))
|
|
raise SystemExit(0 if result['pass_all'] else 1)
|