Seal Phase 4AG author-APL audit and fixed CAL failure
This commit is contained in:
@@ -0,0 +1,431 @@
|
||||
"""Independent extraction/replay of the frozen legacy APL component.
|
||||
|
||||
Only the initializer prefix before ``self.base`` and two original APL update
|
||||
statements are executed. No old simulator is instantiated, no learning code is
|
||||
executed, and no frozen artifact is written. The Torch replay is a component
|
||||
oracle, not an independent CPU whole-brain closed-loop oracle.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import copy
|
||||
from functools import lru_cache
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import types
|
||||
|
||||
import numpy as np
|
||||
import pyarrow.parquet as pq
|
||||
import torch
|
||||
|
||||
|
||||
ROOT = Path('/data/digital-drosophila')
|
||||
CANONICAL = ROOT/'datasets/male-cns-v1.0/processed/neuron-only'
|
||||
SOURCE = ROOT/'models/male-cns-lif/deterministic-runtime-v2/phase4p_revalidate.py'
|
||||
SOURCE_SHA256 = 'c7e693a2da70acdce4f53af5c2853e52e8333acc93f88a46a5e8083eac639346'
|
||||
N, A, K = 166700, 2, 4064
|
||||
BUNDLE_ARRAYS = {
|
||||
'input_matrix': (np.float32, (A, N)), 'output_matrix': (np.float32, (A, K)),
|
||||
'input_raw': (np.float32, (A, N)), 'output_raw': (np.float32, (A, K)),
|
||||
'kc_globals': (np.int32, (K,)), 'apl_globals': (np.int32, (A,)),
|
||||
'pn_globals': (np.int32, None), 'apl_out_ids': (np.int64, (5845,)),
|
||||
}
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def digest(path):
|
||||
h = hashlib.sha256()
|
||||
with Path(path).open('rb') as source:
|
||||
for block in iter(lambda: source.read(8 << 20), b''):
|
||||
h.update(block)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def array_hash(value):
|
||||
return hashlib.sha256(np.ascontiguousarray(value).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def bit_equal(a, b):
|
||||
a, b = np.asarray(a), np.asarray(b)
|
||||
return bool(a.dtype == b.dtype and a.shape == b.shape and
|
||||
np.array_equal(np.ascontiguousarray(a).view(np.uint8),
|
||||
np.ascontiguousarray(b).view(np.uint8)))
|
||||
|
||||
|
||||
def save(path, value):
|
||||
path = Path(path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open('x', encoding='utf-8') as target:
|
||||
json.dump(value, target, indent=2, sort_keys=True, allow_nan=False)
|
||||
target.write('\n')
|
||||
|
||||
|
||||
class ReadOnlyNamespace:
|
||||
"""The extracted prefix can read rt, but cannot mutate or add rt fields."""
|
||||
def __init__(self, **values):
|
||||
object.__setattr__(self, '_values', types.MappingProxyType(values))
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self._values[name]
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
raise AttributeError('Read-only canonical runtime view')
|
||||
|
||||
|
||||
def _code(nodes, filename):
|
||||
module = ast.Module(body=copy.deepcopy(nodes), type_ignores=[])
|
||||
return compile(ast.fix_missing_locations(module), str(filename), 'exec')
|
||||
|
||||
|
||||
def _self_attribute(target, field):
|
||||
return isinstance(target, ast.Attribute) and isinstance(target.value, ast.Name) and \
|
||||
target.value.id == 'self' and target.attr == field
|
||||
|
||||
|
||||
def extract_statements():
|
||||
data = SOURCE.read_bytes()
|
||||
require(hashlib.sha256(data).hexdigest() == SOURCE_SHA256, 'Frozen legacy source SHA-256 mismatch')
|
||||
text = data.decode('utf-8')
|
||||
tree = ast.parse(text, filename=str(SOURCE))
|
||||
classes = [node for node in tree.body if isinstance(node, ast.ClassDef) and node.name == 'DeterministicAPLSim']
|
||||
require(len(classes) == 1, 'Expected exactly one frozen DeterministicAPLSim')
|
||||
methods = {node.name: node for node in classes[0].body if isinstance(node, ast.FunctionDef)}
|
||||
init, run = methods['__init__'], methods['run']
|
||||
cuts = [i for i, node in enumerate(init.body) if isinstance(node, ast.Assign) and
|
||||
any(_self_attribute(target, 'base') for target in node.targets)]
|
||||
require(len(cuts) == 1 and cuts[0] > 0, 'Missing/ambiguous self.base cutoff')
|
||||
prefix = init.body[:cuts[0]]
|
||||
# Executing no import or file-writing call is allowed in the extracted prefix.
|
||||
require(not any(isinstance(node, (ast.Import, ast.ImportFrom, ast.With, ast.AsyncWith))
|
||||
for stmt in prefix for node in ast.walk(stmt)), 'Unexpected side-effect construct in initializer prefix')
|
||||
expressions = [node for node in ast.walk(run) if isinstance(node, ast.Expr) and
|
||||
isinstance(node.value, ast.Call) and isinstance(node.value.func, ast.Attribute) and
|
||||
node.value.func.attr == 'add_' and isinstance(node.value.func.value, ast.Call) and
|
||||
isinstance(node.value.func.value.func, ast.Attribute) and
|
||||
isinstance(node.value.func.value.func.value, ast.Name) and
|
||||
node.value.func.value.func.value.id == 'apl_state' and node.value.func.value.func.attr == 'mul_']
|
||||
inhibition = [node for node in ast.walk(run) if isinstance(node, ast.Assign) and
|
||||
any(isinstance(target, ast.Name) and target.id == 'inhibition' for target in node.targets)]
|
||||
decay = [node for node in ast.walk(run) if isinstance(node, ast.Assign) and
|
||||
any(isinstance(target, ast.Name) and target.id == 'apl_decay' for target in node.targets)]
|
||||
require(len(expressions) == len(inhibition) == len(decay) == 1, 'Ambiguous frozen APL operator extraction')
|
||||
require(expressions[0].lineno < inhibition[0].lineno, 'Unexpected frozen APL update order')
|
||||
rhs = inhibition[0].value
|
||||
require(isinstance(rhs, ast.BinOp) and isinstance(rhs.op, ast.Mult), 'Unexpected inhibition gain expression')
|
||||
gain_expression = ast.Expression(copy.deepcopy(rhs.right))
|
||||
provenance = {'source': str(SOURCE), 'sha256': SOURCE_SHA256,
|
||||
'initializer_line_start': prefix[0].lineno, 'initializer_line_end': prefix[-1].end_lineno,
|
||||
'excluded_self_base_line': init.body[cuts[0]].lineno,
|
||||
'decay_line': decay[0].lineno, 'state_update_line': expressions[0].lineno,
|
||||
'inhibition_line': inhibition[0].lineno,
|
||||
'initializer_source': '\n'.join(ast.get_source_segment(text, node) for node in prefix),
|
||||
'decay_source': ast.get_source_segment(text, decay[0]),
|
||||
'state_update_source': ast.get_source_segment(text, expressions[0]),
|
||||
'inhibition_source': ast.get_source_segment(text, inhibition[0]),
|
||||
'learning_initializer_suffix_executed': False, 'original_run_loop_executed': False}
|
||||
return (_code(prefix, SOURCE), _code(decay, SOURCE),
|
||||
_code([expressions[0], inhibition[0]], SOURCE),
|
||||
compile(ast.fix_missing_locations(gain_expression), str(SOURCE), 'eval'), provenance)
|
||||
|
||||
|
||||
def _canonical_files():
|
||||
return {name: CANONICAL/path for name, path in {
|
||||
'metadata': 'neuron-index/unified-metadata.parquet',
|
||||
'indptr': 'gpu/csr-indptr.int64.npy', 'indices': 'gpu/csr-indices.int32.npy',
|
||||
'raw': 'gpu/csr-weights.int64.npy'}.items()}
|
||||
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
def reconstruct(canonical_hashes, device_name):
|
||||
"""Cache only reconstructions tied to exact canonical bytes and source hash."""
|
||||
files = _canonical_files()
|
||||
require(dict(canonical_hashes) == {name: digest(path) for name, path in files.items()},
|
||||
'Canonical bytes changed before matrix reconstruction')
|
||||
arrays = {name: np.load(files[name], mmap_mode='r', allow_pickle=False)
|
||||
for name in ('indptr', 'indices', 'raw')}
|
||||
require(arrays['indptr'].dtype == np.int64 and arrays['indices'].dtype == np.int32 and
|
||||
arrays['raw'].dtype == np.int64, 'Canonical CSR dtypes changed')
|
||||
require(arrays['indptr'].shape == (N + 1,) and arrays['indptr'][-1] == 25582938 and
|
||||
arrays['indices'].shape == arrays['raw'].shape == (25582938,), 'Canonical graph shape changed')
|
||||
require(int(arrays['raw'].sum()) == 124177617 and bool(np.all(arrays['raw'] > 0)), 'Canonical raw weights changed')
|
||||
table = pq.read_table(files['metadata'])
|
||||
require(table.num_rows == N, 'Canonical metadata count changed')
|
||||
rt = ReadOnlyNamespace(n=N, dt=.1, device=torch.device(device_name), table=table,
|
||||
indptr_np=arrays['indptr'], indices_np=arrays['indices'], raw_np=arrays['raw'])
|
||||
view = types.SimpleNamespace()
|
||||
init_code, decay_code, update_code, gain_code, provenance = extract_statements()
|
||||
scope = {'self': view, 'rt': rt, 'np': np, 'torch': torch}
|
||||
exec(init_code, scope)
|
||||
require(not hasattr(view, 'base') and not hasattr(view, 'identity'), 'Initializer crossed the learning cutoff')
|
||||
expected = {'input_matrix': view.input_gpu.detach().cpu().numpy().copy(),
|
||||
'output_matrix': view.output_gpu.detach().cpu().numpy().copy(),
|
||||
'input_raw': scope['input_matrix'], 'output_raw': scope['output_matrix'],
|
||||
'kc_globals': view.kc, 'apl_globals': view.apl, 'pn_globals': view.pn,
|
||||
'apl_out_ids': view.apl_out_ids}
|
||||
for value in expected.values():
|
||||
value.flags.writeable = False
|
||||
return view, expected, provenance, (decay_code, update_code, gain_code)
|
||||
|
||||
|
||||
class TorchObservationProxy:
|
||||
"""Observe the result of the actual extracted torch.mv call unchanged."""
|
||||
def __init__(self):
|
||||
self.drive = None
|
||||
self.calls = 0
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(torch, name)
|
||||
|
||||
def mv(self, *args, **kwargs):
|
||||
value = torch.mv(*args, **kwargs)
|
||||
self.drive = value
|
||||
self.calls += 1
|
||||
return value
|
||||
|
||||
|
||||
def _up(value):
|
||||
return np.nextafter(np.asarray(value, dtype=np.float64), np.inf)
|
||||
|
||||
|
||||
def _add(a, b):
|
||||
return _up(np.asarray(a, dtype=np.float64) + np.asarray(b, dtype=np.float64))
|
||||
|
||||
|
||||
def _mul(a, b):
|
||||
return _up(np.asarray(a, dtype=np.float64) * np.asarray(b, dtype=np.float64))
|
||||
|
||||
|
||||
def _gamma(count, unit_roundoff):
|
||||
require(count * unit_roundoff < 1, 'Gamma bound is undefined')
|
||||
# Outward rounding on the division; the denominator is rounded downward.
|
||||
numerator = _mul(count, unit_roundoff)
|
||||
denominator = np.nextafter(1.0 - numerator, -np.inf)
|
||||
return _up(numerator / denominator)
|
||||
|
||||
|
||||
def _positive_dot_upper(state, matrix):
|
||||
result = np.zeros(matrix.shape[1], np.float64)
|
||||
for index in range(len(state)):
|
||||
result = _add(result, _mul(state[index], matrix[index]))
|
||||
return result
|
||||
|
||||
|
||||
def _normal_or_zero(value, tiny):
|
||||
value = np.asarray(value)
|
||||
return bool(np.all((value == 0) | (value >= tiny)))
|
||||
|
||||
|
||||
def _new_metric():
|
||||
return {'max_abs_error': 0.0, 'sum_abs_error': 0.0, 'elements': 0,
|
||||
'max_relative_error_nonzero_reference': 0.0, 'zero_reference_nonzero_actual': 0,
|
||||
'max_error_bound': 0.0, 'max_bound_utilization': 0.0, 'bound_violations': 0}
|
||||
|
||||
|
||||
def _accumulate(metric, actual, reference, bound):
|
||||
error = np.abs(np.asarray(actual, np.float64) - reference)
|
||||
error_upper = np.where(error == 0, 0.0, _up(error))
|
||||
nonzero = reference != 0
|
||||
ratio = np.divide(error, np.abs(reference), out=np.zeros_like(error), where=nonzero)
|
||||
usage = np.divide(error_upper, bound, out=np.zeros_like(error), where=bound > 0)
|
||||
metric['max_abs_error'] = max(metric['max_abs_error'], float(error.max(initial=0)))
|
||||
metric['sum_abs_error'] += float(error.sum(dtype=np.float64))
|
||||
metric['elements'] += error.size
|
||||
metric['max_relative_error_nonzero_reference'] = max(metric['max_relative_error_nonzero_reference'], float(ratio.max(initial=0)))
|
||||
metric['zero_reference_nonzero_actual'] += int(np.count_nonzero((~nonzero) & (error != 0)))
|
||||
metric['max_error_bound'] = max(metric['max_error_bound'], float(np.max(bound, initial=0)))
|
||||
metric['max_bound_utilization'] = max(metric['max_bound_utilization'], float(usage.max(initial=0)))
|
||||
metric['bound_violations'] += int(np.count_nonzero(error_upper > bound))
|
||||
|
||||
|
||||
def validate(gpu_case_dir, bundle_dir, out_json):
|
||||
"""Validate one completed fixture; write one exclusive pure-JSON audit."""
|
||||
gpu_case_dir, bundle_dir, out_json = Path(gpu_case_dir), Path(bundle_dir), Path(out_json)
|
||||
require(not out_json.exists(), 'Refusing to overwrite an existing component audit')
|
||||
report = {'pass_all': False, 'scope': 'Frozen APL component extraction and recorded-spike replay only',
|
||||
'independent_cpu_closed_loop_oracle': False, 'brain_or_learning_parameters_changed': False,
|
||||
'candidate_function_called': False, 'gpu_case_dir': str(gpu_case_dir), 'bundle_dir': str(bundle_dir)}
|
||||
try:
|
||||
require(digest(SOURCE) == SOURCE_SHA256, 'Frozen source changed before component validation')
|
||||
require(torch.cuda.is_available() and torch.cuda.device_count() == 1 and
|
||||
'4090' in torch.cuda.get_device_name(), 'Require the one declared RTX4090')
|
||||
environment = {'torch': str(torch.__version__), 'numpy': np.__version__,
|
||||
'gpu': torch.cuda.get_device_name(), 'cuda': torch.version.cuda,
|
||||
'cuda_matmul_allow_tf32': torch.backends.cuda.matmul.allow_tf32,
|
||||
'float32_matmul_precision': torch.get_float32_matmul_precision(),
|
||||
'deterministic_algorithms': torch.are_deterministic_algorithms_enabled(),
|
||||
'precision_flags_changed': False}
|
||||
report['environment'] = environment
|
||||
canonical_hashes = tuple(sorted((name, digest(path)) for name, path in _canonical_files().items()))
|
||||
view, reconstructed, provenance, code = reconstruct(canonical_hashes, 'cuda:0')
|
||||
bundle = {}
|
||||
matrix_checks = {}
|
||||
for name, (dtype, shape) in BUNDLE_ARRAYS.items():
|
||||
value = np.load(bundle_dir/(name+'.npy'), mmap_mode='r', allow_pickle=False)
|
||||
require(value.dtype == dtype and value.ndim == len(shape or (0,)) and
|
||||
(shape is None or value.shape == shape), 'Bundle schema mismatch: '+name)
|
||||
bundle[name] = value
|
||||
matrix_checks[name] = bit_equal(value, reconstructed[name])
|
||||
scales = json.loads((bundle_dir/'scales.json').read_text())
|
||||
for name in ('input_scale', 'output_scale'):
|
||||
matrix_checks[name] = struct.pack('<d', float(scales[name])) == struct.pack('<d', float(getattr(view, name)))
|
||||
report['matrix_checks'] = matrix_checks
|
||||
report['extracted_source'] = provenance
|
||||
report['canonical_sha256'] = dict(canonical_hashes)
|
||||
report['bundle_sha256'] = {name+'.npy': digest(bundle_dir/(name+'.npy')) for name in BUNDLE_ARRAYS}
|
||||
report['bundle_sha256']['scales.json'] = digest(bundle_dir/'scales.json')
|
||||
report['reconstructed_array_sha256'] = {name: array_hash(value) for name, value in reconstructed.items()}
|
||||
bundle_provenance = json.loads((bundle_dir/'provenance.json').read_text())
|
||||
require(Path(bundle_provenance['canonical_root']).resolve() == CANONICAL.resolve(),
|
||||
'Bundle provenance names another canonical dataset')
|
||||
report['bundle_sha256']['provenance.json'] = digest(bundle_dir/'provenance.json')
|
||||
require(all(matrix_checks.values()), 'Bundle differs from the actual frozen initializer prefix')
|
||||
|
||||
traces = {name: np.load(gpu_case_dir/('apl-'+name+'.float32.npy'), mmap_mode='r', allow_pickle=False)
|
||||
for name in ('drive', 'state', 'inhibition')}
|
||||
steps = len(traces['state'])
|
||||
require(steps > 0, 'Empty APL trajectory cannot pass')
|
||||
for name, width in (('drive', A), ('state', A), ('inhibition', K)):
|
||||
require(traces[name].dtype == np.float32 and traces[name].shape == (steps, width) and
|
||||
bool(np.isfinite(traces[name]).all()) and bool(np.all(traces[name] >= 0)), 'Invalid APL trace: '+name)
|
||||
summary = json.loads((gpu_case_dir/'summary.json').read_text())
|
||||
require(summary.get('complete') is True and summary.get('pass_all') is True and
|
||||
summary.get('steps') == steps and summary.get('completed_ticks') == steps,
|
||||
'GPU fixture did not complete its internal execution checks')
|
||||
with np.load(gpu_case_dir/'state.npz', allow_pickle=False) as archive:
|
||||
event_step, event_node = archive['step'].copy(), archive['neuron_idx'].copy()
|
||||
require(event_step.dtype == event_node.dtype == np.int32 and event_step.ndim == 1 and
|
||||
event_step.shape == event_node.shape, 'Invalid sparse actual spike event schema')
|
||||
require(bool(np.all((event_step >= 0) & (event_step < steps))) and
|
||||
bool(np.all((event_node >= 0) & (event_node < N))), 'Sparse event outside its clock/identity range')
|
||||
require(np.array_equal(np.lexsort((event_node, event_step)), np.arange(len(event_step))), 'Actual events not sorted')
|
||||
keys = event_step.astype(np.int64)*N + event_node
|
||||
require(len(keys) < 2 or bool(np.all(np.diff(keys) > 0)), 'Duplicate actual spikes')
|
||||
boundaries = np.searchsorted(event_step, np.arange(steps + 1))
|
||||
|
||||
proxy = TorchObservationProxy()
|
||||
# Reconstructed original matrices are already on the original GPU/dtype.
|
||||
replay_rt = types.SimpleNamespace(spike_u8=torch.zeros(N, dtype=torch.uint8, device=view.device))
|
||||
shadow = types.SimpleNamespace(input_gpu=view.input_gpu, output_gpu=view.output_gpu, rt=replay_rt, dt=.1)
|
||||
scope = {'self': shadow, 'np': np, 'math': math, 'torch': proxy,
|
||||
'apl_state': torch.zeros(A, dtype=torch.float32, device=view.device)}
|
||||
decay_code, update_code, gain_code = code
|
||||
exec(decay_code, scope)
|
||||
decay32 = np.float32(scope['apl_decay'])
|
||||
gain32 = np.float32(eval(gain_code, {'np': np}))
|
||||
require(0 < decay32 < 1 and gain32 > 0, 'Invalid extracted positive APL constants')
|
||||
exact = {name: True for name in traces}
|
||||
first_difference = None
|
||||
I64 = np.asarray(bundle['input_matrix'], np.float64)
|
||||
O64 = np.asarray(bundle['output_matrix'], np.float64)
|
||||
require(bool(np.all(I64 >= 0)) and bool(np.all(O64 >= 0)), 'Positive-sum theory requires nonnegative matrices')
|
||||
d, q = float(decay32), float(gain32)
|
||||
u32, u64 = 2.0**-24, 2.0**-53
|
||||
gd32, gd64 = _gamma(N-1, u32), _gamma(N-1, u64)
|
||||
g232, g264 = _gamma(2, u32), _gamma(2, u64)
|
||||
gh32, gh64 = _gamma(2*A+1, u32), _gamma(2*A+1, u64)
|
||||
state64 = np.zeros(A, np.float64)
|
||||
e32 = np.zeros(A, np.float64)
|
||||
e64 = np.zeros(A, np.float64)
|
||||
previous32 = np.zeros(A, np.float64)
|
||||
statistics = {name: _new_metric() for name in traces}
|
||||
normal_arithmetic = _normal_or_zero(I64, np.finfo(np.float32).tiny) and _normal_or_zero(O64, np.finfo(np.float32).tiny)
|
||||
max_active_terms = 0
|
||||
with torch.no_grad():
|
||||
for tick in range(steps):
|
||||
fired = event_node[boundaries[tick]:boundaries[tick+1]]
|
||||
replay_rt.spike_u8.zero_()
|
||||
if len(fired):
|
||||
replay_rt.spike_u8[torch.as_tensor(fired.astype(np.int64), device=view.device)] = 1
|
||||
before_calls = proxy.calls
|
||||
exec(update_code, scope)
|
||||
require(proxy.calls == before_calls + 1, 'Original update did not issue exactly one mv')
|
||||
actual = {'drive': proxy.drive.detach().cpu().numpy(),
|
||||
'state': scope['apl_state'].detach().cpu().numpy(),
|
||||
'inhibition': scope['inhibition'].detach().cpu().numpy()}
|
||||
for name in traces:
|
||||
same = bit_equal(actual[name], traces[name][tick])
|
||||
exact[name] &= same
|
||||
if not same and first_difference is None:
|
||||
first_difference = {'tick': tick, 'field': name,
|
||||
'max_abs_error': float(np.max(np.abs(actual[name].astype(np.float64)-traces[name][tick])))}
|
||||
# Float64 reference is measurement only and never feeds either brain.
|
||||
selected = I64[:, fired]
|
||||
drive64 = selected.sum(axis=1, dtype=np.float64)
|
||||
max_active_terms = max(max_active_terms, int(np.count_nonzero(selected, axis=1).max(initial=0)))
|
||||
Dupper = _up(drive64 / np.nextafter(1.0-gd64, -np.inf))
|
||||
drive_bound = _add(_mul(gd32, Dupper), _mul(gd64, Dupper))
|
||||
state32 = traces['state'][tick].astype(np.float64)
|
||||
normal_arithmetic &= _normal_or_zero(d*previous32, np.finfo(np.float32).tiny)
|
||||
normal_arithmetic &= _normal_or_zero(O64*state32[:, None], np.finfo(np.float32).tiny)
|
||||
e32 = _add(_mul(d, e32), _add(_mul(g232, _add(_mul(d, previous32), Dupper)),
|
||||
_mul(1.0+u32, _mul(gd32, Dupper))))
|
||||
e64 = _add(_mul(d, e64), _add(_mul(g264, _add(_mul(d, state64), Dupper)),
|
||||
_mul(1.0+u64, _mul(gd64, Dupper))))
|
||||
state64 = d*state64 + drive64
|
||||
inhibition64 = np.matmul(state64, O64)*q
|
||||
h32bound = _add(_mul(q, _positive_dot_upper(e32, O64)),
|
||||
_mul(gh32, _mul(q, _positive_dot_upper(state32, O64))))
|
||||
h64bound = _add(_mul(q, _positive_dot_upper(e64, O64)),
|
||||
_mul(gh64, _mul(q, _positive_dot_upper(state64, O64))))
|
||||
normal_arithmetic &= _normal_or_zero(q*np.matmul(state32, O64), np.finfo(np.float32).tiny)
|
||||
_accumulate(statistics['drive'], traces['drive'][tick], drive64, drive_bound)
|
||||
_accumulate(statistics['state'], traces['state'][tick], state64, _add(e32, e64))
|
||||
_accumulate(statistics['inhibition'], traces['inhibition'][tick], inhibition64, _add(h32bound, h64bound))
|
||||
previous32 = state32
|
||||
for metric in statistics.values():
|
||||
metric['mean_abs_error'] = metric.pop('sum_abs_error') / metric['elements']
|
||||
theory_applicable = bool(normal_arithmetic and not environment['cuda_matmul_allow_tf32'] and
|
||||
environment['float32_matmul_precision'] == 'highest')
|
||||
report.update(steps=steps, actual_spike_events=len(event_step),
|
||||
ast_replay_bitwise_exact=exact, first_ast_difference=first_difference,
|
||||
value_units={'drive': 'legacy normalized-contact spike drive',
|
||||
'state': 'legacy normalized-drive accumulator', 'inhibition': 'legacy mV'},
|
||||
decay_f32_bits=f'{int(decay32.view(np.uint32)):08x}', gain_f32_bits=f'{int(gain32.view(np.uint32)):08x}',
|
||||
float64_measurement=statistics,
|
||||
theoretical_bound={'applicable': theory_applicable, 'normal_or_zero_arithmetic': bool(normal_arithmetic),
|
||||
'gamma_definition': 'gamma_n=(n*u)/(1-n*u), upward-rounded; u32=2^-24,u64=2^-53',
|
||||
'drive_n': N-1, 'max_observed_nonzero_drive_terms': max_active_terms,
|
||||
'gamma_drive_f32': float(gd32), 'gamma_drive_f64': float(gd64),
|
||||
'state_recurrence': 'E_p[t] <= d*E_p[t-1] + gamma_2,p*(d*abs(s_p[t-1])+D_upper) + (1+u_p)*gamma_(N-1),p*D_upper',
|
||||
'output_bound': 'q*sum(B*E_p) + gamma_(2*A+1),p*q*sum(B*abs(s_p)); compare with E32+E64',
|
||||
'assumptions': 'nonnegative f32 matrices, spikes exactly 0/1, fixed stored f32 decay/gain, normal finite RN binary32/64 operations; no reduced-precision matmul',
|
||||
'bound_arithmetic': 'Each positive bound operation is rounded toward +infinity via nextafter; denominator toward -infinity',
|
||||
'tolerance_fitted_to_observed_error': False,
|
||||
'f64_values_used_to_drive_brain': False,
|
||||
'no_cpu_gpu_bitwise_expectation_for_f64_measurement': True},
|
||||
input_sha256={name: digest(gpu_case_dir/name) for name in
|
||||
('state.npz', 'summary.json', 'apl-drive.float32.npy', 'apl-state.float32.npy', 'apl-inhibition.float32.npy')})
|
||||
report['pass_all'] = bool(all(matrix_checks.values()) and all(exact.values()) and theory_applicable and
|
||||
all(value['bound_violations'] == 0 for value in statistics.values()))
|
||||
report['limitations'] = [
|
||||
'AST shadow and candidate use the same original Torch/CUDA primitives; this validates preserved component semantics, not an independent CPU whole-brain oracle.',
|
||||
'Float64 is a non-feedback mathematical measurement with explicit gamma error bounds, not replacement APL dynamics.',
|
||||
'Replay uses the recorded actual spike train; it does not establish equivalence for untested closed-loop inputs.',
|
||||
'The N-1 positive-sum bound is deliberately conservative; passing it does not prove physiological validity or learning.']
|
||||
save(out_json, report)
|
||||
return report
|
||||
except Exception as error:
|
||||
report['error'] = {'type': type(error).__name__, 'message': str(error)}
|
||||
if not out_json.exists():
|
||||
save(out_json, report)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('gpu_case_dir', type=Path)
|
||||
parser.add_argument('bundle_dir', type=Path)
|
||||
parser.add_argument('out_json', type=Path)
|
||||
args = parser.parse_args()
|
||||
result = validate(args.gpu_case_dir, args.bundle_dir, args.out_json)
|
||||
print(json.dumps({'pass_all': result['pass_all'], 'steps': result['steps']}), flush=True)
|
||||
if not result['pass_all']:
|
||||
raise SystemExit(1)
|
||||
Reference in New Issue
Block a user