276 lines
14 KiB
Python
276 lines
14 KiB
Python
"""Pure synthetic inputs/AST tests; no Torch import, GPU, or brain execution."""
|
|
import ast
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import tempfile
|
|
import types
|
|
import unittest
|
|
import time
|
|
|
|
import numpy as np
|
|
|
|
import adapter
|
|
import overlay
|
|
|
|
|
|
HERE = Path(__file__).absolute().parent
|
|
AJ = HERE.parent / 'apl-timing-lab-v1/gpu_timing.py'
|
|
|
|
|
|
class ArrayTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.ids = np.array([2, 7, 15], np.int64)
|
|
self.base32 = np.array([.275, 1.1, 2.75], np.float32)
|
|
|
|
def plan(self, delta=None):
|
|
if delta is None:
|
|
delta = np.zeros(3, np.float32)
|
|
return overlay.validate_arrays(self.ids, delta, self.ids, self.base32)
|
|
|
|
def test_zero_preserves_unrelated_signed_zero_and_original_bytes(self):
|
|
ids = np.array([0, 2, 7, 15, 17], np.int64)
|
|
base = np.array([-0., .000275, .0011, .00275, -1.], np.float64)
|
|
original = base.tobytes()
|
|
result, mapping = overlay.materialize_numpy(base, ids, np.zeros(5, bool), self.plan())
|
|
self.assertEqual(result.tobytes(), original)
|
|
self.assertEqual(base.tobytes(), original)
|
|
self.assertEqual(mapping['installed_nonzero_count'], 0)
|
|
self.assertEqual(len(mapping['rows']), 0)
|
|
self.assertFalse(np.shares_memory(result, base))
|
|
|
|
def test_negative_zero_delta_does_not_write(self):
|
|
plan = self.plan(np.full(3, -0., np.float32))
|
|
result, mapping = overlay.materialize_numpy(self.base32.astype(np.float64) * .001,
|
|
self.ids, np.zeros(3, bool), plan)
|
|
self.assertEqual(len(mapping['rows']), 0)
|
|
self.assertTrue(np.signbit(plan['plastic_delta_mv']).all())
|
|
|
|
def test_original_float32_floor_then_widen_and_multiply(self):
|
|
delta = np.float32(-.25) * self.base32
|
|
plan = self.plan(delta)
|
|
base_si = np.array([.000275, .0011, .00275], np.float64)
|
|
result, mapping = overlay.materialize_numpy(base_si, self.ids, np.zeros(3, bool), plan)
|
|
reference = np.add(base_si, np.multiply(delta.astype(np.float64), np.float64(.001)))
|
|
self.assertTrue(overlay.bit_equal(result, reference))
|
|
self.assertTrue(np.any(result.view(np.uint64) != (base_si * .75).view(np.uint64)))
|
|
self.assertEqual(mapping['installed_nonzero_count'], 3)
|
|
|
|
def test_global_id_mapping_fixture_intersection_and_missing_nonzero_reject(self):
|
|
plan = self.plan(np.array([0., -.1, 0.], np.float32))
|
|
ids = np.array([1, 7, 20], np.int64)
|
|
result, mapping = overlay.materialize_numpy(np.array([1., .002, -0.], np.float64), ids,
|
|
np.array([True, False, False]), plan)
|
|
np.testing.assert_array_equal(mapping['rows'], np.array([1], np.int64))
|
|
self.assertEqual(mapping['missing_candidate_count'], 2)
|
|
self.assertEqual(result[0].view(np.uint64), 0)
|
|
self.assertTrue(np.signbit(result[2]))
|
|
with self.assertRaises(ValueError):
|
|
overlay.materialize_numpy(np.array([1.], np.float64), np.array([1], np.int64), np.zeros(1, bool), plan)
|
|
|
|
def test_apl_overlap_sign_nonfinite_bad_mapping_fail_closed(self):
|
|
base = self.base32.astype(np.float64) * .001
|
|
for ids, values, apl in ((self.ids, base, np.array([1, 0, 0], bool)),
|
|
(self.ids[::-1], base, np.zeros(3, bool)),
|
|
(self.ids, -base, np.zeros(3, bool)),
|
|
(self.ids, np.array([np.nan, 1., 2.]), np.zeros(3, bool))):
|
|
with self.subTest(ids=ids), self.assertRaises(ValueError):
|
|
overlay.map_installation(values, ids, apl, self.plan())
|
|
|
|
def test_validation_bad_dtype_shape_sign_finite_bound_and_mask(self):
|
|
bad_deltas = [np.zeros(3, np.float64), np.zeros((3, 1), np.float32), np.zeros(2, np.float32),
|
|
np.array([.1, 0, 0], np.float32), np.array([np.nan, 0, 0], np.float32),
|
|
np.array([-np.inf, 0, 0], np.float32), np.array([-1., 0, 0], np.float32)]
|
|
for delta in bad_deltas:
|
|
with self.subTest(delta=delta), self.assertRaises(ValueError):
|
|
self.plan(delta)
|
|
for ids in (self.ids.astype(np.int32), self.ids[::-1], np.array([2, 2, 15], np.int64),
|
|
np.array([-1, 7, 15], np.int64), np.array([2, 7, overlay.EDGE_COUNT], np.int64)):
|
|
with self.subTest(ids=ids), self.assertRaises(ValueError):
|
|
overlay.validate_arrays(ids, np.zeros(3, np.float32), self.ids, self.base32)
|
|
with self.assertRaises(ValueError):
|
|
overlay.validate_arrays(self.ids, np.array([-.01, 0, 0], np.float32), self.ids)
|
|
|
|
def test_positive_original_base_and_bound_bits_readonly(self):
|
|
for base in (np.zeros(3, np.float32), -self.base32, self.base32.astype(np.float64),
|
|
np.array([np.inf, 1., 1.], np.float32)):
|
|
with self.assertRaises(ValueError):
|
|
overlay.validate_arrays(self.ids, np.zeros(3, np.float32), self.ids, base)
|
|
self.ids.flags.writeable = False
|
|
self.base32.flags.writeable = False
|
|
plan = self.plan()
|
|
self.assertFalse(plan['edge_id'].flags.writeable)
|
|
self.assertFalse(plan['plastic_delta_mv'].flags.writeable)
|
|
self.assertFalse(np.shares_memory(plan['base_candidate_mv'], self.base32))
|
|
lower = np.float32(-.25) * self.base32
|
|
too_low = lower.copy(); too_low[0] = np.nextafter(lower[0], np.float32(-np.inf))
|
|
with self.assertRaises(ValueError):
|
|
self.plan(too_low)
|
|
|
|
def test_chunked_expected_hash_matches_materialization(self):
|
|
ids = np.array([0, 2, 7, 15, 20], np.int64)
|
|
base = np.array([-0., .001, .002, .003, -1.], np.float64)
|
|
apl = np.array([False, False, False, False, True])
|
|
plan = self.plan(np.array([-.01, 0, -.2], np.float32))
|
|
result, mapping = overlay.materialize_numpy(base, ids, apl, plan)
|
|
for chunk in (1, 2, 3, 100):
|
|
self.assertEqual(overlay.expected_hashes(base, apl, mapping, chunk),
|
|
(overlay.raw_hash(base), overlay.raw_hash(result)))
|
|
|
|
|
|
class FileTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name)
|
|
self.ids = np.arange(overlay.CANDIDATE_COUNT, dtype=np.int64)
|
|
self.base = np.full(overlay.CANDIDATE_COUNT, .275, np.float32)
|
|
np.save(self.root / 'mask.npy', self.ids)
|
|
np.save(self.root / 'base.npy', self.base)
|
|
|
|
def files(self, name='zero', embedded=False, base=True):
|
|
delta = np.zeros_like(self.base) if name == 'zero' else np.float32(-.25) * self.base
|
|
values = dict(edge_id=self.ids, plastic_delta_mv=delta)
|
|
if embedded:
|
|
values['base_candidate_mv'] = self.base
|
|
np.savez(self.root / 'overlay.npz', **values)
|
|
manifest = dict(schema=overlay.SCHEMA, overlay_name=name,
|
|
overlay_sha256=overlay.file_hash(self.root / 'overlay.npz'),
|
|
mask_path=str(self.root / 'mask.npy'), mask_sha256=overlay.file_hash(self.root / 'mask.npy'),
|
|
base_candidate_path=str(self.root / 'base.npy') if base else None,
|
|
base_candidate_sha256=overlay.file_hash(self.root / 'base.npy') if base else None,
|
|
aj_model_dir=str(self.root))
|
|
(self.root / 'manifest.json').write_text(json.dumps(manifest), encoding='utf-8')
|
|
return self.root / 'overlay.npz', self.root / 'manifest.json'
|
|
|
|
def test_full_mask_zero_optional_base_and_static_floor(self):
|
|
for name, embedded, base in (('zero', False, False), ('zero', True, True), ('static25', True, True)):
|
|
plan = overlay.load_overlay(*self.files(name, embedded, base))
|
|
self.assertEqual(len(plan['edge_id']), overlay.CANDIDATE_COUNT)
|
|
overlay.verify_inputs(plan)
|
|
|
|
def test_hash_tamper_and_wrong_count_reject(self):
|
|
paths = self.files()
|
|
(self.root / 'base.npy').write_bytes(b'not the anchored source')
|
|
with self.assertRaises(ValueError):
|
|
overlay.load_overlay(*paths)
|
|
np.save(self.root / 'base.npy', self.base)
|
|
np.save(self.root / 'mask.npy', self.ids[:-1])
|
|
paths = self.files()
|
|
with self.assertRaises(ValueError):
|
|
overlay.load_overlay(*paths)
|
|
|
|
def test_static25_needs_original_base_and_postload_tampering_is_detected(self):
|
|
with self.assertRaises(ValueError):
|
|
overlay.load_overlay(*self.files('static25', base=False))
|
|
paths = self.files()
|
|
plan = overlay.load_overlay(*paths)
|
|
(self.root / 'manifest.json').write_text('{}', encoding='utf-8')
|
|
with self.assertRaises(ValueError):
|
|
overlay.verify_inputs(plan)
|
|
|
|
|
|
class AstTests(unittest.TestCase):
|
|
def source(self):
|
|
if not AJ.is_file():
|
|
self.skipTest('Pinned AJ static source is not installed at its declared sibling path')
|
|
return AJ.read_bytes()
|
|
|
|
def test_exact_whole_tick_loop_and_only_one_preloop_insertion(self):
|
|
source = self.source()
|
|
transformed, audit = adapter.derive_ast(source)
|
|
original = next(node for node in ast.parse(source).body if isinstance(node, ast.FunctionDef) and node.name == 'run_gpu')
|
|
getloop = lambda function: next(node for node in ast.walk(function) if isinstance(node, ast.For) and
|
|
isinstance(node.target, ast.Name) and node.target.id == 'tick')
|
|
self.assertEqual(ast.dump(getloop(original), include_attributes=False), ast.dump(getloop(transformed), include_attributes=False))
|
|
hooks = [node for node in ast.walk(transformed) if isinstance(node, ast.Call) and
|
|
isinstance(node.func, ast.Name) and node.func.id == adapter.HOOK_NAME]
|
|
self.assertEqual(len(hooks), 1)
|
|
self.assertFalse(any(node is hooks[0] for node in ast.walk(getloop(transformed))))
|
|
self.assertTrue(audit['entire_tick_loop_ast_identical'])
|
|
self.assertEqual(audit['inserted_ast_statements'], 1)
|
|
|
|
def test_mutated_source_fails_even_if_loop_looks_plausible(self):
|
|
with self.assertRaises(ValueError):
|
|
adapter.derive_ast(self.source() + b'\n# unregistered change\n')
|
|
|
|
def test_compilation_new_namespace_no_fake_file_no_old_global_patch(self):
|
|
self.source()
|
|
# Compile only; the function is never called, so no Torch or GPU needed.
|
|
sealed = types.ModuleType('synthetic_sealed_namespace')
|
|
sealed.__file__ = str(AJ)
|
|
sealed.marker = object()
|
|
before = dict(sealed.__dict__)
|
|
hook = lambda *args: None
|
|
function, audit = adapter.compile_derived(sealed, hook)
|
|
self.assertEqual(sealed.__dict__, before)
|
|
self.assertNotIn(adapter.HOOK_NAME, sealed.__dict__)
|
|
self.assertIs(function.__globals__[adapter.HOOK_NAME], hook)
|
|
self.assertIs(function.__globals__['marker'], sealed.marker)
|
|
self.assertEqual(function.__globals__['__file__'], str(Path(adapter.__file__).absolute()))
|
|
self.assertNotEqual(function.__globals__['__file__'], sealed.__file__)
|
|
self.assertFalse(audit['fake_original_file_used'])
|
|
self.assertFalse(audit['original_module_globals_patched'])
|
|
|
|
def test_real_source_dependency_hashes_without_importing_gpu(self):
|
|
self.source()
|
|
proof = adapter.verify_sources(AJ.parent)
|
|
self.assertEqual({key: row['sha256'] for key, row in proof.items()}, adapter.PINS)
|
|
self.assertEqual(hashlib.sha256(AJ.read_bytes()).hexdigest(), adapter.AJ_SHA256)
|
|
|
|
def test_derived_hook_runs_after_apl_zero_and_before_any_state_tick(self):
|
|
self.source()
|
|
|
|
class StopBeforeBrain(BaseException):
|
|
pass
|
|
|
|
class Tensor:
|
|
def __init__(self, array):
|
|
self.array = np.array(array, copy=True)
|
|
|
|
def to(self, *, device, dtype):
|
|
return Tensor(self.array.astype(dtype))
|
|
|
|
def contiguous(self):
|
|
return self
|
|
|
|
def clone(self):
|
|
return Tensor(self.array)
|
|
|
|
def __setitem__(self, item, value):
|
|
self.array[item.array if isinstance(item, Tensor) else item] = value
|
|
|
|
torch_stub = types.SimpleNamespace(from_numpy=Tensor, float64=np.float64, int64=np.int64,
|
|
int32=np.int32, bool=bool, cuda=types.SimpleNamespace(is_available=lambda: True,
|
|
device_count=lambda: 1, get_device_name=lambda: 'synthetic RTX4090', reset_peak_memory_stats=lambda _: None))
|
|
graph = {'weights': np.array([.1, -.2], np.float64), 'nodes': np.array([3, 9], np.int32),
|
|
'edge_id': np.array([4, 12], np.int64), 'ptr': np.array([0, 1, 2], np.int64),
|
|
'dst': np.array([1, 0], np.int32), 'src': np.array([0, 1], np.int32)}
|
|
apl = types.SimpleNamespace(device='no-device', apl_globals=np.array([9], np.int32), metadata={})
|
|
sealed = types.ModuleType('synthetic_before_loop')
|
|
sealed.__file__ = str(AJ)
|
|
sealed.__dict__.update(Path=Path, time=time, np=np, torch=torch_stub, require=overlay.require,
|
|
source_audit=lambda _: {'scope': 'synthetic inherited AJ source'}, write_json=lambda *_: None,
|
|
load_inputs=lambda _: ({'apl_bundle_dir': 'unused', 'dt_ms': .1, 'steps': 2}, graph, {},
|
|
np.empty(0, np.int32), np.empty(0, np.int32), np.empty(0, np.int32), {}),
|
|
sealed_backend=lambda: types.SimpleNamespace(build_extension=lambda _: (None, {})),
|
|
sealed_apl=lambda: types.SimpleNamespace(LegacyAPL=lambda *_: apl))
|
|
observed = []
|
|
|
|
def hook(weights, actual_graph, mask, upload, out_dir, provenance):
|
|
self.assertTrue(overlay.bit_equal(weights.array, np.array([.1, 0.], np.float64)))
|
|
self.assertTrue(overlay.bit_equal(graph['weights'], np.array([.1, -.2], np.float64)))
|
|
np.testing.assert_array_equal(mask, [False, True])
|
|
observed.append(provenance)
|
|
raise StopBeforeBrain()
|
|
|
|
derived, _ = adapter.compile_derived(sealed, hook)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
with self.assertRaises(StopBeforeBrain):
|
|
derived(Path(directory), Path(directory) / 'new-output', Path(directory) / 'new-build')
|
|
self.assertEqual(len(observed), 1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|