Seal AO interface failure and verified isolated postprocessing diagnosis
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
"""UNREGISTERED input consumer; no I/O, brain execution, or training.
|
||||
|
||||
The frozen L/AK interface is discrete float32 Bernoulli (one request per PN
|
||||
and tick), not a continuous-time Poisson sampler. PN/rate mapping is supplied
|
||||
and externally authenticated by the caller; this module never selects odors,
|
||||
rebuilds mappings, rescales rates, clips probabilities, or resets brain RNGs.
|
||||
|
||||
generate_cs_requests lazily uses the original CUDA Torch random operation.
|
||||
CPU mocks test its contract only, not CUDA sequence equivalence. A full
|
||||
(steps, PN-columns) matrix is drawn once, then the explicit half-open CS
|
||||
window masks requests. A 100 ms CS in a 120 ms epoch is NOT the full old L
|
||||
120 ms input. Different draw shapes need not share identical random prefixes.
|
||||
The caller must register source/rate authority, device/environment, budgets,
|
||||
stream aliases, and a separate bounded CUDA equivalence test before real use.
|
||||
Returned request/source arrays are owned read-only copies, not immutable
|
||||
capabilities; the consumer must recheck their recorded hashes before use.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import re
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
N_NEURONS = 166700
|
||||
L_SOURCE_SHA = 'e97d58d8c1f44920d0e953f8f5831ddc86fa0198784e86460e315de4116ac553'
|
||||
AK_ENCODER_SHA = '0061689abb1832e963718bd836bc8a50009ee38b258143a753b4d59e63414925'
|
||||
|
||||
|
||||
def require(value, message):
|
||||
if not value:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def array_identity(value):
|
||||
return dict(dtype=value.dtype.str, shape=list(value.shape), order='C-logical-bytes',
|
||||
sha256=hashlib.sha256(value.tobytes(order='C')).hexdigest())
|
||||
|
||||
|
||||
def _vector(value, dtype, name):
|
||||
require(isinstance(value, np.ndarray) and value.dtype == np.dtype(dtype) and value.ndim == 1,
|
||||
name + ' must be an exact one-dimensional ' + np.dtype(dtype).name + ' array')
|
||||
|
||||
|
||||
def _targets(value):
|
||||
_vector(value, np.int32, 'targets')
|
||||
require(np.all((value >= 0) & (value < N_NEURONS)), 'Target outside canonical global neuron range')
|
||||
require(len(np.unique(value)) == len(value), 'Duplicate PN columns rejected; never merge rates or deduplicate RNG columns')
|
||||
|
||||
|
||||
def _clock(steps, start, stop):
|
||||
require(type(steps) is int and 0 < steps <= np.iinfo(np.int32).max, 'steps must be a positive int32-range integer')
|
||||
require(type(start) is type(stop) is int and 0 <= start <= stop <= steps, 'Invalid explicit half-open CS tick window')
|
||||
|
||||
|
||||
def probabilities_f32(targets, rates_hz, *, dt_ms=0.1):
|
||||
"""Preserve the L expression's separate float32 multiply then divide."""
|
||||
_targets(targets); _vector(rates_hz, np.float32, 'rates_hz')
|
||||
require(rates_hz.shape == targets.shape and np.all(np.isfinite(rates_hz)) and np.all(rates_hz >= 0), 'Invalid PN rate vector')
|
||||
require(type(dt_ms) in (int, float) and dt_ms == 0.1, 'Only the frozen dt_ms=0.1 contract is supported')
|
||||
probability = np.divide(np.multiply(rates_hz, np.float32(dt_ms), dtype=np.float32), np.float32(1000.0), dtype=np.float32)
|
||||
original = rates_hz * dt_ms / 1000.0
|
||||
require(original.dtype == np.float32 and original.tobytes() == probability.tobytes(), 'Installed NumPy changed the frozen rate expression')
|
||||
require(np.all(np.isfinite(probability)) and np.all((probability >= 0) & (probability <= 1)), 'Probability outside [0,1]; no clipping or rate adjustment')
|
||||
return probability
|
||||
|
||||
|
||||
def forced_from_uniforms(uniforms, probability):
|
||||
"""Pure arithmetic fixture helper, NOT a replacement CUDA generator."""
|
||||
_vector(probability, np.float32, 'probability')
|
||||
require(isinstance(uniforms, np.ndarray) and uniforms.dtype == np.float32 and uniforms.ndim == 2 and
|
||||
uniforms.shape[1] == len(probability), 'Uniform fixture requires exact float32 [ticks,columns]')
|
||||
require(np.all(np.isfinite(uniforms)) and np.all((uniforms >= 0) & (uniforms < 1)) and
|
||||
np.all(np.isfinite(probability)) and np.all((probability >= 0) & (probability <= 1)), 'Invalid uniform/probability fixture')
|
||||
return uniforms < probability[None, :]
|
||||
|
||||
|
||||
def requests_from_forced(forced, targets, *, cs_start_tick, cs_stop_tick):
|
||||
"""Sort resulting requests only; never reorder the pre-sampling columns."""
|
||||
_targets(targets)
|
||||
require(isinstance(forced, np.ndarray) and forced.dtype == np.bool_ and forced.ndim == 2 and
|
||||
forced.shape[1] == len(targets), 'Forced samples must be Boolean [steps,original PN columns]')
|
||||
steps = forced.shape[0]; _clock(steps, cs_start_tick, cs_stop_tick)
|
||||
ticks, columns = np.nonzero(forced[cs_start_tick:cs_stop_tick])
|
||||
ticks = ticks.astype(np.int32) + np.int32(cs_start_tick)
|
||||
neurons = targets[columns].copy()
|
||||
order = np.lexsort((neurons, ticks))
|
||||
requests = {'tick': ticks[order], 'neuron_idx': neurons[order]}
|
||||
validate_requests(requests, steps)
|
||||
for value in requests.values(): value.setflags(write=False)
|
||||
generated = int(np.count_nonzero(forced)); retained = len(ticks)
|
||||
return dict(requests=requests, audit=dict(steps=steps, CS_window=[cs_start_tick, cs_stop_tick],
|
||||
full_matrix_boolean_hits=generated, retained_CS_requests=retained,
|
||||
outside_CS_boolean_hits_masked=generated - retained,
|
||||
original_PN_column_order_preserved=True, sorted_only_after_sampling=True,
|
||||
duplicate_requests_removed=0, probabilities_clipped=False,
|
||||
returned_requests_read_only=True, consumer_must_recheck_array_hashes=True,
|
||||
forced_identity=array_identity(forced), targets_identity=array_identity(targets),
|
||||
requests_identity={k: array_identity(v) for k, v in requests.items()}))
|
||||
|
||||
|
||||
def _load_torch():
|
||||
import torch
|
||||
return torch
|
||||
|
||||
|
||||
def generate_cs_requests(targets, rates_hz, *, seed, steps, cs_start_tick, cs_stop_tick,
|
||||
device, max_uniform_draws, dt_ms=0.1):
|
||||
"""Future explicit CUDA input generation; no implicit CPU fallback.
|
||||
|
||||
``seed`` is the caller's full semantic seed (Python int in [0,2**63)).
|
||||
The generator is private to this call. No offset/modulo/truncation or
|
||||
global/manual brain seed is used. Aliases must reuse the same complete
|
||||
sampling descriptor, including steps/column order/window, not seed alone.
|
||||
The caller owns authorization, resource admission, and external rate pins.
|
||||
``device`` and ``max_uniform_draws`` must be explicitly supplied. The
|
||||
latter bounds steps * PN columns, not total process/GPU memory. Retaining
|
||||
initial_seed proves only integer delivery, not CUDA high-bit influence.
|
||||
"""
|
||||
require(type(seed) is int and 0 <= seed < 2**63, 'Exact full 63-bit Python integer seed required')
|
||||
_clock(steps, cs_start_tick, cs_stop_tick)
|
||||
probability = probabilities_f32(targets, rates_hz, dt_ms=dt_ms)
|
||||
require(type(device) is str and re.fullmatch('cuda:[0-9]+', device), 'Explicit indexed CUDA device required; no CPU fallback')
|
||||
require(type(max_uniform_draws) is int and max_uniform_draws > 0, 'Explicit positive integer uniform-draw budget required')
|
||||
uniform_draws = steps * len(targets)
|
||||
require(uniform_draws <= max_uniform_draws, 'Full matrix exceeds caller uniform-draw budget; no chunking or clipping')
|
||||
torch = _load_torch()
|
||||
require(torch.cuda.is_available() and torch.get_default_dtype() == torch.float32, 'Original CUDA/default-float32 sampler environment required')
|
||||
actual_device = torch.device(device)
|
||||
generator = torch.Generator(device=actual_device)
|
||||
generator.manual_seed(seed)
|
||||
require(generator.initial_seed() == seed, 'Generator did not retain the complete semantic seed')
|
||||
p_gpu = torch.as_tensor(probability, dtype=torch.float32, device=actual_device)
|
||||
# Exactly one complete draw. No chunking, per-PN loop, or tail-dependent redraw.
|
||||
uniforms = torch.rand((steps, len(targets)), generator=generator, device=actual_device)
|
||||
require(uniforms.dtype == torch.float32 and str(uniforms.device) == str(actual_device), 'Sampler dtype/device changed')
|
||||
forced = (uniforms < p_gpu[None, :]).cpu().numpy()
|
||||
result = requests_from_forced(forced, targets, cs_start_tick=cs_start_tick, cs_stop_tick=cs_stop_tick)
|
||||
# Optional identifiers are recorded only when the installed Torch exposes
|
||||
# them. An indexed device name is not presented as a UUID or external pin.
|
||||
identity = {'uuid': None, 'pci_bus_id': None, 'query_error': None}
|
||||
try:
|
||||
properties = torch.cuda.get_device_properties(actual_device)
|
||||
for key in ('uuid', 'pci_bus_id'):
|
||||
value = getattr(properties, key, None)
|
||||
if value is not None: identity[key] = str(value)
|
||||
except (AttributeError, RuntimeError) as exc:
|
||||
identity['query_error'] = type(exc).__name__ + ': ' + str(exc)
|
||||
result['audit'].update(schema='phase4an-conditioning-input-v1-unregistered',
|
||||
seed=seed, seed_bits=63, seed_hex=f'{seed:016x}', generator_initial_seed=generator.initial_seed(),
|
||||
seed_conversion='exact integer; no truncation/modulo/offset', private_generator=True,
|
||||
sampler='Torch CUDA rand default float32; strict U < float32 probability',
|
||||
generator_type=type(generator).__module__ + '.' + type(generator).__qualname__,
|
||||
numpy_version=str(np.__version__), torch_version=str(torch.__version__), torch_cuda_version=torch.version.cuda,
|
||||
execution_device=str(uniforms.device), device_name=str(torch.cuda.get_device_name(actual_device)),
|
||||
device_capability=list(torch.cuda.get_device_capability(actual_device)),
|
||||
device_identity=identity, device_identity_verified_against_external_pin=False,
|
||||
matrix_draw_calls=1, uniform_matrix_shape=[steps, len(targets)], uniform_scalar_draws=uniform_draws,
|
||||
max_uniform_draws=max_uniform_draws, uniform_matrix_bytes=uniform_draws * np.dtype(np.float32).itemsize,
|
||||
budget_scope='Uniform scalar count only, not total process or GPU memory',
|
||||
sampled_before_window_mask=True, dt_ms=dt_ms, dt_float32_identity=array_identity(np.asarray([dt_ms], np.float32)),
|
||||
rate_units='Hz', probability_identity=array_identity(probability), rates_identity=array_identity(rates_hz),
|
||||
input_mapping_authority_verified_by_this_module=False,
|
||||
reference_L_source_sha256=L_SOURCE_SHA, reference_AK_encoder_source_sha256=AK_ENCODER_SHA,
|
||||
full_old_L_input_identity_claimed=False, CUDA_sequence_equivalence_tested_here=False,
|
||||
CUDA_high_seed_bit_sequence_influence_tested_here=False,
|
||||
timing_scope='Full epoch matrix drawn once, then explicit CS window; old L supplied PN requests over its full duration',
|
||||
RNG_prefix_invariance_across_shapes_claimed=False, continuous_time_Poisson_claimed=False,
|
||||
brain_reset_called=False, brain_executed=False, training_executed=False, protocol_registered=False)
|
||||
return result
|
||||
|
||||
|
||||
def validate_requests(requests, steps):
|
||||
_clock(steps, 0, steps)
|
||||
require(type(requests) is dict and set(requests) == {'tick', 'neuron_idx'}, 'Exact sparse request schema required')
|
||||
for key, value in requests.items(): _vector(value, np.int32, key)
|
||||
tick, neuron = requests['tick'], requests['neuron_idx']
|
||||
require(tick.shape == neuron.shape and np.all((tick >= 0) & (tick < steps)) and
|
||||
np.all((neuron >= 0) & (neuron < N_NEURONS)), 'Requests outside local clock/global neuron range')
|
||||
keys = tick.astype(np.int64) * N_NEURONS + neuron
|
||||
require(len(keys) < 2 or np.all(keys[1:] > keys[:-1]), 'Requests must already be sorted unique; no silent sorting/deduplication')
|
||||
return keys
|
||||
|
||||
|
||||
class RequestConflictError(ValueError):
|
||||
def __init__(self, audit):
|
||||
super().__init__('CS/US request overlap rejected; explicit boolean_union is required to combine it')
|
||||
self.audit = audit
|
||||
|
||||
|
||||
def merge_requests(cs, us, *, steps, conflict_policy='error'):
|
||||
"""Compose two valid streams, recording every cross-source collision.
|
||||
|
||||
boolean_union explicitly reproduces a single Boolean request per neuron /
|
||||
tick, not two deliveries. Separate source attempts are retained in audit
|
||||
and uint8 source_mask (CS=1, US=2, overlap=3). This is not refractory
|
||||
acceptance, actual spiking, an artificial D value, or an update rule.
|
||||
"""
|
||||
require(conflict_policy in ('error', 'boolean_union'), 'Unknown explicit conflict policy')
|
||||
cs_keys, us_keys = validate_requests(cs, steps), validate_requests(us, steps)
|
||||
overlap = np.intersect1d(cs_keys, us_keys, assume_unique=True)
|
||||
conflicts = [dict(tick=int(k // N_NEURONS), neuron_idx=int(k % N_NEURONS), CS_attempts=1, US_attempts=1,
|
||||
combined_boolean_requests=1 if conflict_policy == 'boolean_union' else None) for k in overlap]
|
||||
audit = dict(schema='phase4an-request-composition-v1-unregistered', policy=conflict_policy,
|
||||
CS_attempts=len(cs_keys), US_attempts=len(us_keys), cross_source_conflict_count=len(overlap), conflicts=conflicts,
|
||||
duplicate_within_source_allowed=False, refractory_acceptance_evaluated=False, actual_spikes_evaluated=False,
|
||||
input_identity={name: {k: array_identity(v) for k, v in values.items()} for name, values in (('CS', cs), ('US', us))})
|
||||
if len(overlap) and conflict_policy == 'error': raise RequestConflictError(audit)
|
||||
keys = np.union1d(cs_keys, us_keys)
|
||||
sources = np.zeros(len(keys), np.uint8)
|
||||
sources[np.searchsorted(keys, cs_keys)] |= np.uint8(1)
|
||||
sources[np.searchsorted(keys, us_keys)] |= np.uint8(2)
|
||||
requests = {'tick': (keys // N_NEURONS).astype(np.int32), 'neuron_idx': (keys % N_NEURONS).astype(np.int32)}
|
||||
validate_requests(requests, steps)
|
||||
for value in (*requests.values(), sources): value.setflags(write=False)
|
||||
audit.update(output_requests=len(keys), explicit_boolean_overlap_collapses=len(overlap),
|
||||
requests_identity={k: array_identity(v) for k, v in requests.items()}, source_mask_identity=array_identity(sources),
|
||||
returned_arrays_read_only=True, consumer_must_recheck_array_hashes=True,
|
||||
learning_or_D_command_defined=False, brain_executed=False, protocol_registered=False)
|
||||
return dict(requests=requests, source_mask=sources, audit=audit)
|
||||
Reference in New Issue
Block a user