mirror of
https://github.com/ruvnet/RuView.git
synced 2026-10-21 22:22:33 -05:00
214 lines
12 KiB
Python
214 lines
12 KiB
Python
"""Frozen nonlinear development search with no support-vector retention."""
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
import time
|
|
|
|
import numpy as np
|
|
from safetensors.numpy import load_file, save_file
|
|
from .espfi_generalize import features, folds, GeneralizationBundle
|
|
from .espfi_train import digest, scores, write_json
|
|
|
|
FAMILIES = ('relative_spectral', 'relative_combined')
|
|
GRID = ([{'family': family, 'kind': 'linear', 'alpha': alpha} for family in FAMILIES for alpha in (1., 10., 100.)]
|
|
+ [{'family': family, 'kind': 'centroid'} for family in FAMILIES]
|
|
+ [{'family': family, 'kind': 'rff', 'dimensions': dimensions, 'gamma_multiplier': gamma, 'alpha': alpha}
|
|
for family in FAMILIES for dimensions in (128, 512) for gamma in (.1, 1., 10.) for alpha in (.01, .1, 1., 10.)])
|
|
CONTRACT = {'seed': 42, 'grid': GRID, 'input': 'nonnegative_amplitude_N52T190',
|
|
'selection': 'mean training-only grouped CV macro F1, then fewer stored scalars, then declaration order',
|
|
'folds': [{'train_room': 1, 'validation_room': 2}, {'train_room': 2, 'validation_room': 1}],
|
|
'train_participants': [1, 2, 3, 4], 'validation_participants': [5, 6],
|
|
'gamma': 'multiplier / 468 after train-only standardization',
|
|
'rff': 'sqrt(2/M) cos(Z @ Gaussian(0,2gamma) + Uniform(0,2pi))',
|
|
'support_vectors': False}
|
|
|
|
|
|
def transformed(state, values):
|
|
values = np.asarray(values, dtype=state['feature_mean'].dtype)
|
|
z = (values - state['feature_mean']) / state['feature_scale']
|
|
if 'projection' in state:
|
|
return np.sqrt(2. / state['phase'].size) * np.cos(z @ state['projection'] + state['phase'])
|
|
return z
|
|
|
|
|
|
def predict_features(state, values):
|
|
z = transformed(state, values) - state['output_mean']
|
|
result = z @ state['weights'] + state['bias']
|
|
if not np.isfinite(result).all():
|
|
raise ValueError('Nonfinite classifier output')
|
|
return result
|
|
|
|
|
|
def fit(values, labels, candidate):
|
|
mean, scale = values.mean(0), np.maximum(values.std(0), 1e-6)
|
|
state = {'feature_mean': mean, 'feature_scale': scale}
|
|
if candidate['kind'] == 'rff':
|
|
rng = np.random.default_rng(42)
|
|
dimensions = candidate['dimensions']
|
|
gamma = candidate['gamma_multiplier'] / values.shape[1]
|
|
state['projection'] = rng.normal(0, np.sqrt(2 * gamma), (values.shape[1], dimensions))
|
|
state['phase'] = rng.uniform(0, 2 * np.pi, dimensions)
|
|
z = transformed(state, values)
|
|
if candidate['kind'] == 'centroid':
|
|
centroids = np.stack([z[labels == label].mean(0) for label in range(7)])
|
|
state.update({'output_mean': np.zeros(z.shape[1]), 'weights': 2 * centroids.T,
|
|
'bias': -np.square(centroids).sum(1)})
|
|
else:
|
|
output_mean = z.mean(0)
|
|
z = z - output_mean
|
|
target = np.eye(7)[labels]
|
|
bias = target.mean(0)
|
|
target -= bias
|
|
if len(z) < z.shape[1]:
|
|
gram = z @ z.T
|
|
gram.flat[::len(gram) + 1] += candidate['alpha']
|
|
weights = z.T @ np.linalg.solve(gram, target)
|
|
else:
|
|
gram = z.T @ z
|
|
gram.flat[::len(gram) + 1] += candidate['alpha']
|
|
weights = np.linalg.solve(gram, z.T @ target)
|
|
state.update({'output_mean': output_mean, 'weights': weights, 'bias': bias})
|
|
if any(not np.isfinite(v).all() for v in state.values()):
|
|
raise ValueError('Nonfinite model state')
|
|
# Canonical layout avoids BLAS reduction-order differences after export.
|
|
return {k: np.ascontiguousarray(v) for k, v in state.items()}
|
|
|
|
|
|
class KernelBundle:
|
|
def __init__(self, directory):
|
|
root = Path(directory)
|
|
for name in ('metrics.json', 'status.json', 'model.safetensors'):
|
|
path = root / name
|
|
if path.is_symlink() or not path.is_file() or path.stat().st_size > 4 * 1024**2:
|
|
raise ValueError('Invalid bounded artifact')
|
|
report = json.loads((root / 'metrics.json').read_text())
|
|
status = json.loads((root / 'status.json').read_text())
|
|
if (status.get('status') != 'complete' or status.get('promotionAuthorized') is not False
|
|
or report.get('status') != 'complete' or report.get('promotionAuthorized') is not False
|
|
or report.get('task') != 'public_amplitude_nonlinear_development' or report.get('contract') != CONTRACT):
|
|
raise ValueError('Invalid completed kernel candidate')
|
|
if report.get('model_sha256') != digest(root / 'model.safetensors'):
|
|
raise ValueError('Model hash mismatch')
|
|
self.candidate = report['selected']['candidate']
|
|
if self.candidate not in GRID:
|
|
raise ValueError('Unknown candidate')
|
|
self.state = load_file(str(root / 'model.safetensors'))
|
|
width = self.candidate.get('dimensions', 468)
|
|
expected = {'feature_mean': (468,), 'feature_scale': (468,), 'output_mean': (width,),
|
|
'weights': (width, 7), 'bias': (7,)}
|
|
if self.candidate['kind'] == 'rff':
|
|
expected.update({'projection': (468, width), 'phase': (width,)})
|
|
if set(self.state) != set(expected) or any(self.state[k].shape != shape or self.state[k].dtype != np.float64 or not np.isfinite(self.state[k]).all() for k, shape in expected.items()) or not (self.state['feature_scale'] > 0).all():
|
|
raise ValueError('Invalid bounded classifier state')
|
|
|
|
def predict(self, amplitudes):
|
|
return predict_features(self.state, features(amplitudes, self.candidate['family']))
|
|
|
|
|
|
def search(parts, provenance, baseline_directory, output, max_seconds=600):
|
|
if set(parts) != {'train', 'validation'} or not 1 <= max_seconds <= 3600:
|
|
raise ValueError('Requires bounded development-only partitions')
|
|
start = time.monotonic()
|
|
baseline = GeneralizationBundle(baseline_directory)
|
|
baseline_report = json.loads((Path(baseline_directory) / 'metrics.json').read_text())
|
|
if baseline_report['provenance']['manifest_sha256'] != provenance['manifest_sha256']:
|
|
raise ValueError('Data provenance differs from incumbent')
|
|
train, validation = parts['train'], parts['validation']
|
|
y = np.asarray(train['y'])
|
|
if y.dtype.kind not in 'iu' or y.shape != (len(train['x']),) or set(y) != set(range(7)):
|
|
raise ValueError('Invalid training labels')
|
|
fold_ids = folds(train['identity'], y)
|
|
ids = np.asarray(validation['identity'])
|
|
if ids.shape != (len(validation['y']), 4) or not np.all(ids[:, 0] == 3) or not np.all(ids[:, 1] == 7) or not np.array_equal(ids[:, 2] - 1, validation['y']):
|
|
raise ValueError('Invalid development identities')
|
|
root = Path(output)
|
|
root.mkdir(exist_ok=False, parents=True)
|
|
write_json(root / 'status.json', {'status': 'running', 'promotionAuthorized': False})
|
|
write_json(root / 'search_contract.json', CONTRACT)
|
|
try:
|
|
cache = {family: features(train['x'], family) for family in FAMILIES}
|
|
results, selected, best = [], None, None
|
|
for candidate in GRID:
|
|
x = cache[candidate['family']]
|
|
evaluations = []
|
|
for fit_ids, eval_ids in fold_ids:
|
|
if time.monotonic() - start > max_seconds:
|
|
raise TimeoutError('Nonlinear search budget exceeded')
|
|
state = fit(x[fit_ids], y[fit_ids], candidate)
|
|
evaluations.append(scores(y[eval_ids], predict_features(state, x[eval_ids]).argmax(1), 7))
|
|
result = {'candidate': candidate, 'folds': evaluations,
|
|
'mean_cv_macro_f1': float(np.mean([e['macro_f1'] for e in evaluations])),
|
|
'stored_scalar_count': sum(v.size for v in state.values())}
|
|
results.append(result)
|
|
rank = (result['mean_cv_macro_f1'], -result['stored_scalar_count'])
|
|
if best is None or rank > best:
|
|
selected, best = result, rank
|
|
# Freeze selection before touching external development values or labels.
|
|
write_json(root / 'selection.json', {'selected': selected, 'candidates': results, 'contract': CONTRACT})
|
|
chosen = selected['candidate']
|
|
state = fit(cache[chosen['family']], y, chosen)
|
|
save_file({k: np.ascontiguousarray(v) for k, v in state.items()}, str(root / 'model.safetensors'))
|
|
dev_features = features(validation['x'], chosen['family'])
|
|
prediction = predict_features(state, dev_features)
|
|
reloaded = load_file(str(root / 'model.safetensors'))
|
|
if not np.array_equal(prediction, predict_features(reloaded, dev_features)):
|
|
raise ValueError('Export changed logits')
|
|
incumbent = scores(validation['y'], baseline.predict(validation['x']).argmax(1), 7)
|
|
measured = scores(validation['y'], prediction.argmax(1), 7)
|
|
# Full same-method inference including feature extraction and input/output checks.
|
|
sample = validation['x'][:1]
|
|
def inference():
|
|
return predict_features(state, features(sample, chosen['family']))
|
|
for _ in range(20):
|
|
inference()
|
|
baseline.predict(sample)
|
|
timings = {'candidate': [], 'incumbent': []}
|
|
for i in range(500):
|
|
pairs = [('candidate', inference), ('incumbent', lambda: baseline.predict(sample))]
|
|
if i % 2:
|
|
pairs.reverse()
|
|
for name, fn in pairs:
|
|
before = time.perf_counter_ns()
|
|
fn()
|
|
timings[name].append((time.perf_counter_ns() - before) / 1e6)
|
|
report = {'schema_version': 1, 'task': 'public_amplitude_nonlinear_development', 'status': 'complete',
|
|
'promotionAuthorized': False, 'contract': CONTRACT, 'selected': selected, 'candidates': results,
|
|
'evidence': 'SYNTHETIC' if provenance.get('evidence') == 'SYNTHETIC' else 'MEASURED',
|
|
'development': measured, 'incumbent_development': incumbent,
|
|
'development_macro_f1_delta': measured['macro_f1'] - incumbent['macro_f1'],
|
|
'development_improvement': measured['macro_f1'] > incumbent['macro_f1'],
|
|
'provenance': provenance, 'implementation_sha256': digest(__file__),
|
|
'feature_implementation_sha256': digest(Path(__file__).with_name('espfi_generalize.py')),
|
|
'baseline_metrics_sha256': digest(Path(baseline_directory) / 'metrics.json'),
|
|
'baseline_model_sha256': digest(Path(baseline_directory) / 'model.safetensors'),
|
|
'model_sha256': digest(root / 'model.safetensors'), 'model_bytes': (root / 'model.safetensors').stat().st_size,
|
|
'stored_scalar_count': sum(v.size for v in state.values()),
|
|
'classifier_scalar_count': state['weights'].size + state['bias'].size,
|
|
'random_projection_scalar_count': sum(state[k].size for k in ('projection', 'phase') if k in state),
|
|
'reload_max_abs_logit_error': 0.0,
|
|
'latency': {name: {'p50_ms': float(np.percentile(t, 50)), 'p95_ms': float(np.percentile(t, 95))} for name, t in timings.items()},
|
|
'timing_scope': '500 paired CPU batch1 calls, input/output checks plus features/classifier; excludes IO and temporal pooling',
|
|
'test_evaluation': 'Final-test arrays not deserialized or predictions computed',
|
|
'limitations': ['Adaptive development result, not independent generalization evidence', 'No comparable SOTA claim; new untouched room/participant required'],
|
|
'elapsed_seconds': time.monotonic() - start, 'max_seconds': max_seconds,
|
|
'gpu_used': False, 'incremental_gpu_spend_usd': 0}
|
|
write_json(root / 'metrics.json', report)
|
|
write_json(root / 'status.json', {'status': 'complete', 'promotionAuthorized': False})
|
|
return report
|
|
except Exception:
|
|
write_json(root / 'status.json', {'status': 'failed', 'promotionAuthorized': False})
|
|
raise
|
|
|
|
|
|
def main(argv=None):
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--manifest', required=True)
|
|
parser.add_argument('--baseline', required=True)
|
|
parser.add_argument('--output', required=True)
|
|
parser.add_argument('--max-seconds', type=int, default=600)
|
|
args = parser.parse_args(argv)
|
|
from .espfi import load_development
|
|
parts, metadata = load_development(args.manifest)
|
|
report = search(parts, metadata, args.baseline, args.output, args.max_seconds)
|
|
print(json.dumps({k: report[k] for k in ('selected', 'development', 'incumbent_development', 'development_macro_f1_delta', 'latency', 'model_bytes')}, indent=2))
|