mirror of
https://github.com/ruvnet/RuView.git
synced 2026-10-22 06:32:35 -05:00
perf(training): halve seeded RFF model state with verified float32 inference
This commit is contained in:
1 parent
1f3039ef26
commit
d8c4ff9032
4 files changed
+92
-11
No files matched your search
@@ -333,3 +333,38 @@ or with the prior float32 spectral deployment artifact.
|
||||
The earlier security workflow fails on preexisting Rust dependency alerts in
|
||||
unchanged `v2/Cargo.lock`; the Python checks pass. That remains a release gate,
|
||||
alongside independent room/person confirmation. No SOTA or promotion claim.
|
||||
|
||||
|
||||
## Measured float32 seeded inference
|
||||
|
||||
The optional float32 export converts the fitted tensors and regenerated map
|
||||
at load. The RFF normalization constant now follows the state dtype, avoiding
|
||||
an unintended NumPy float64 promotion. Existing float64 metadata without a
|
||||
precision field remains supported. Unsupported precision values are rejected.
|
||||
|
||||
```python
|
||||
from csi_training.espfi_seeded import export_seeded, SeededKernelBundle
|
||||
baseline = SeededKernelBundle('/private/espfi/seeded-001')
|
||||
export_seeded(baseline.state, '/private/espfi/seeded-002', precision='float32')
|
||||
candidate = SeededKernelBundle('/private/espfi/seeded-002')
|
||||
```
|
||||
|
||||
MEASURED: all 70 development class predictions are unchanged. Maximum absolute
|
||||
logit error is 1.119643e-6, below the predeclared 1e-4 tolerance. Reload is exact.
|
||||
Weights occupy 8,220 bytes plus 1,069 metadata bytes. Retained model tensors use
|
||||
247,996 bytes instead of 495,992, a 50% reduction. Feature extraction and the
|
||||
temporary map generated during loading remain float64, so total process memory
|
||||
and peak loading memory are not halved. This precision conversion is not
|
||||
bitwise lossless and establishes no new accuracy or final-test result.
|
||||
|
||||
A 1,000-pair benchmark alternated call order and cycled all 70 development
|
||||
records. Float64 p50/p95 were 0.401590/0.568780 ms; float32 were
|
||||
0.390610/0.556713 ms. The 2.73% median difference is too small for a reliable
|
||||
speed claim on a shared host. Float32 median load time across 30 loads was
|
||||
1.633650 ms. Input checks, feature extraction and classification are included;
|
||||
acquisition, temporal pooling and I/O are excluded. NumPy 2.4.4, Python 3.12.3,
|
||||
x86_64 and one BLAS thread were used.
|
||||
|
||||
Decision: retain the verified precision option for smaller model state and
|
||||
storage. Keep float64 for numerical comparison. Generalization weaknesses,
|
||||
independent room/person evaluation and the existing Rust security gate remain.
|
||||
@@ -27,7 +27,7 @@ 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 np.asarray(np.sqrt(2. / state['phase'].size), dtype=z.dtype) * np.cos(z @ state['projection'] + state['phase'])
|
||||
return z
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Lossless seed encoding for the fixed 128-dimensional CSI RFF candidate."""
|
||||
"""Seed encoding with optional measured float32 conversion for fixed CSI RFF."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
@@ -18,33 +18,38 @@ def random_map():
|
||||
'phase': rng.uniform(0, 2*np.pi, 128)}
|
||||
def tensor_hash(value):
|
||||
return hashlib.sha256(np.ascontiguousarray(value).tobytes()).hexdigest()
|
||||
def validate(state):
|
||||
def validate(state, dtype=np.float64):
|
||||
if set(state) != set(SHAPES) or any(
|
||||
state[k].shape != shape or state[k].dtype != np.float64 or
|
||||
state[k].shape != shape or state[k].dtype != dtype or
|
||||
not np.isfinite(state[k]).all() for k, shape in SHAPES.items()
|
||||
) or not (state['feature_scale'] > 0).all():
|
||||
raise ValueError('Invalid compact state')
|
||||
|
||||
def export_seeded(state, output):
|
||||
def export_seeded(state, output, precision='float64'):
|
||||
if precision not in ('float64', 'float32'):
|
||||
raise ValueError('Unsupported precision')
|
||||
dtype = np.dtype(precision)
|
||||
generated = random_map()
|
||||
if set(state) != set(SHAPES) | set(generated) or any(
|
||||
not np.array_equal(state[k], generated[k]) for k in generated
|
||||
):
|
||||
raise ValueError('Projection does not match frozen seed recipe')
|
||||
compact = {k: np.ascontiguousarray(state[k]) for k in SHAPES}
|
||||
validate(compact)
|
||||
compact = {k: np.ascontiguousarray(state[k], dtype=dtype) for k in SHAPES}
|
||||
validate(compact, dtype)
|
||||
root = Path(output)
|
||||
root.mkdir(exist_ok=False, parents=True)
|
||||
save_file(compact, str(root/'model.safetensors'))
|
||||
report = {'status': 'complete', 'promotionAuthorized': False, 'spec': SPEC,
|
||||
'model_sha256': digest(root/'model.safetensors'),
|
||||
'precision': precision, 'model_sha256': digest(root/'model.safetensors'),
|
||||
'model_bytes': (root/'model.safetensors').stat().st_size,
|
||||
'random_map_sha256': {k: tensor_hash(v) for k,v in generated.items()},
|
||||
'numpy_version': np.__version__, 'implementation_sha256': digest(__file__),
|
||||
'feature_implementation_sha256': digest(Path(__file__).with_name('espfi_generalize.py')),
|
||||
'stored_scalars': sum(v.size for v in compact.values()),
|
||||
'runtime_scalars': sum(v.size for v in state.values()),
|
||||
'scope': 'Lossless on-disk encoding only; projection regenerated at load, runtime memory unchanged'}
|
||||
'runtime_state_bytes': sum(v.size for v in state.values()) * dtype.itemsize,
|
||||
'kernel_implementation_sha256': digest(Path(__file__).with_name('espfi_kernel.py')),
|
||||
'scope': 'Seed encoded map; float32 requires measured parity, float64 preserves original state'}
|
||||
write_json(root/'metadata.json', report)
|
||||
return report
|
||||
|
||||
@@ -60,12 +65,16 @@ class SeededKernelBundle:
|
||||
raise ValueError('Invalid compact contract')
|
||||
if digest(root/'model.safetensors') != report.get('model_sha256'):
|
||||
raise ValueError('Compact model hash mismatch')
|
||||
precision = report.get('precision', 'float64')
|
||||
if precision not in ('float64', 'float32'):
|
||||
raise ValueError('Unsupported precision')
|
||||
dtype = np.dtype(precision)
|
||||
state = load_file(str(root/'model.safetensors'))
|
||||
validate(state)
|
||||
validate(state, dtype)
|
||||
generated = random_map()
|
||||
if {k:tensor_hash(v) for k,v in generated.items()} != report.get('random_map_sha256'):
|
||||
raise ValueError('Runtime random generator differs from export')
|
||||
state.update(generated)
|
||||
state.update({k: np.ascontiguousarray(v, dtype=dtype) for k, v in generated.items()})
|
||||
self.state = state
|
||||
|
||||
def predict(self, amplitudes):
|
||||
|
||||
@@ -34,3 +34,40 @@ class SeededTests(unittest.TestCase):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
with self.assertRaisesRegex(ValueError,'seed recipe'):
|
||||
export_seeded(state,Path(tmp)/'model')
|
||||
|
||||
def test_float32_export_uses_half_runtime_state(self):
|
||||
amplitudes = np.exp(np.random.default_rng(3).normal(size=(14,52,190)))
|
||||
x = features(amplitudes, SPEC['family'])
|
||||
state = fit(x, np.arange(14)%7, dict(SPEC, kind='rff'))
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
report = export_seeded(state, root/'single', precision='float32')
|
||||
model = SeededKernelBundle(root/'single')
|
||||
self.assertEqual(model.predict(amplitudes).dtype, np.float32)
|
||||
self.assertTrue(all(v.dtype == np.float32 for v in model.state.values()))
|
||||
self.assertEqual(report['runtime_state_bytes'], 61999*4)
|
||||
np.testing.assert_allclose(predict_features(state,x),model.predict(amplitudes),atol=1e-5,rtol=1e-5)
|
||||
np.testing.assert_array_equal(predict_features(state,x).argmax(1),model.predict(amplitudes).argmax(1))
|
||||
import json
|
||||
p = root/'single/metadata.json'
|
||||
metadata = json.loads(p.read_text())
|
||||
metadata['precision'] = 'float16'
|
||||
p.write_text(json.dumps(metadata))
|
||||
with self.assertRaisesRegex(ValueError,'Unsupported precision'):
|
||||
SeededKernelBundle(root/'single')
|
||||
|
||||
def test_legacy_float64_metadata_still_loads(self):
|
||||
x = np.random.default_rng(4).normal(size=(14,468))
|
||||
state = fit(x,np.arange(14)%7,dict(SPEC,kind='rff'))
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path=Path(tmp)/'model'
|
||||
export_seeded(state,path)
|
||||
import json
|
||||
p=path/'metadata.json'
|
||||
metadata=json.loads(p.read_text())
|
||||
del metadata['precision']
|
||||
p.write_text(json.dumps(metadata))
|
||||
loaded=SeededKernelBundle(path)
|
||||
self.assertTrue(all(v.dtype==np.float64 for v in loaded.state.values()))
|
||||
with self.assertRaisesRegex(ValueError,'Unsupported precision'):
|
||||
export_seeded(state,Path(tmp)/'invalid',precision='float16')
|
||||
Reference in new issue
Block a user