perf(memory): store holographic vectors as float32

This commit is contained in:
JabberELF 2026-05-22 23:41:30 +08:00 committed by kshitij
parent 54eafee30b
commit 958ffd1085
4 changed files with 237 additions and 20 deletions

View File

@ -33,6 +33,7 @@ except ImportError:
logger = logging.getLogger(__name__)
_TWO_PI = 2.0 * math.pi
_FLOAT32_BLOB_PREFIX = b"HRR1"
def _require_numpy() -> None:
@ -40,6 +41,12 @@ def _require_numpy() -> None:
raise RuntimeError("numpy is required for holographic operations")
def _np():
"""Return the numpy module after the runtime availability guard."""
_require_numpy()
return np # type: ignore[name-defined]
def encode_atom(word: str, dim: int = 1024) -> "np.ndarray":
"""Deterministic phase vector via SHA-256 counter blocks.
@ -161,19 +168,63 @@ def encode_fact(content: str, entities: list[str], dim: int = 1024) -> "np.ndarr
def phases_to_bytes(phases: "np.ndarray") -> bytes:
"""Serialize phase vector to bytes. float64 tobytes — 8 KB at dim=1024."""
_require_numpy()
return phases.tobytes()
"""Serialize phase vectors as float32 blobs.
def bytes_to_phases(data: bytes) -> "np.ndarray":
"""Deserialize bytes back to phase vector. Inverse of phases_to_bytes.
The .copy() call is required because frombuffer returns a read-only view
backed by the bytes object; callers expect a mutable array.
float32 halves SQLite BLOB storage versus the legacy float64 format
(4 KB + a 4-byte format prefix instead of 8 KB at dim=1024) while
preserving enough precision for phase-similarity retrieval.
``bytes_to_phases`` keeps reading legacy float64 blobs for backward
compatibility.
"""
_require_numpy()
return np.frombuffer(data, dtype=np.float64).copy()
numpy = _np()
payload = numpy.asarray(phases, dtype=numpy.float32).tobytes()
return _FLOAT32_BLOB_PREFIX + payload
def bytes_to_phases(data: bytes, dim: int | None = None) -> "np.ndarray":
"""Deserialize a phase vector from new float32 or legacy float64 storage.
New float32 blobs carry a small prefix so callers can round-trip without
knowing ``dim``. Legacy float64 blobs are raw NumPy bytes and remain
readable for backward compatibility. The returned array is copied and
promoted to float64 so downstream HRR math keeps the existing numerical
behavior.
"""
numpy = _np()
if dim is not None:
float32_payload_bytes = dim * numpy.dtype(numpy.float32).itemsize
float32_blob_bytes = len(_FLOAT32_BLOB_PREFIX) + float32_payload_bytes
float64_bytes = dim * numpy.dtype(numpy.float64).itemsize
if data.startswith(_FLOAT32_BLOB_PREFIX) and len(data) == float32_blob_bytes:
payload = data[len(_FLOAT32_BLOB_PREFIX):]
return numpy.frombuffer(payload, dtype=numpy.float32).astype(numpy.float64)
if len(data) == float64_bytes:
return numpy.frombuffer(data, dtype=numpy.float64).copy()
if data.startswith(_FLOAT32_BLOB_PREFIX):
payload_len = len(data) - len(_FLOAT32_BLOB_PREFIX)
raise ValueError(
f"HRR vector blob has {len(data)} bytes ({payload_len} payload bytes after "
f"the float32 prefix); expected {float32_blob_bytes} (prefixed float32) "
f"or {float64_bytes} (legacy float64) for dim={dim}"
)
raise ValueError(
f"HRR legacy vector blob has {len(data)} bytes; expected "
f"{float64_bytes} (float64) for dim={dim}"
)
if data.startswith(_FLOAT32_BLOB_PREFIX):
payload = data[len(_FLOAT32_BLOB_PREFIX):]
if len(payload) % numpy.dtype(numpy.float32).itemsize != 0:
raise ValueError(
f"HRR float32 vector blob has invalid payload byte length: {len(payload)}"
)
return numpy.frombuffer(payload, dtype=numpy.float32).astype(numpy.float64)
if len(data) % numpy.dtype(numpy.float64).itemsize != 0:
raise ValueError(f"HRR legacy vector blob has invalid byte length: {len(data)}")
return numpy.frombuffer(data, dtype=numpy.float64).copy()
def snr_estimate(dim: int, n_items: int) -> float:

View File

@ -91,7 +91,7 @@ class FactRetriever:
# HRR similarity
if self.hrr_weight > 0 and fact.get("hrr_vector"):
fact_vec = hrr.bytes_to_phases(fact["hrr_vector"])
fact_vec = hrr.bytes_to_phases(fact["hrr_vector"], dim=self.hrr_dim)
if query_vec is None:
query_vec = hrr.encode_text(query, self.hrr_dim)
hrr_sim = (hrr.similarity(query_vec, fact_vec) + 1.0) / 2.0 # shift to [0,1]
@ -154,7 +154,7 @@ class FactRetriever:
(bank_name,),
).fetchone()
if bank_row:
bank_vec = hrr.bytes_to_phases(bank_row["vector"])
bank_vec = hrr.bytes_to_phases(bank_row["vector"], dim=self.hrr_dim)
extracted = hrr.unbind(bank_vec, probe_key)
# Use extracted signal to score individual facts
return self._score_facts_by_vector(
@ -189,7 +189,7 @@ class FactRetriever:
scored = []
for row in rows:
fact = dict(row)
fact_vec = hrr.bytes_to_phases(fact.pop("hrr_vector"))
fact_vec = hrr.bytes_to_phases(fact.pop("hrr_vector"), dim=self.hrr_dim)
# Unbind probe key from fact to see if entity is structurally present
residual = hrr.unbind(fact_vec, probe_key)
# Compare residual against content signal
@ -253,7 +253,7 @@ class FactRetriever:
scored = []
for row in rows:
fact = dict(row)
fact_vec = hrr.bytes_to_phases(fact.pop("hrr_vector"))
fact_vec = hrr.bytes_to_phases(fact.pop("hrr_vector"), dim=self.hrr_dim)
# Check structural similarity: unbind entity from fact
residual = hrr.unbind(fact_vec, entity_vec)
@ -334,7 +334,7 @@ class FactRetriever:
scored = []
for row in rows:
fact = dict(row)
fact_vec = hrr.bytes_to_phases(fact.pop("hrr_vector"))
fact_vec = hrr.bytes_to_phases(fact.pop("hrr_vector"), dim=self.hrr_dim)
entity_scores = []
for probe_key in entity_residuals:
@ -431,8 +431,8 @@ class FactRetriever:
continue # Not enough entity overlap to be contradictory
# Content similarity via HRR vectors
v1 = hrr.bytes_to_phases(f1["hrr_vector"])
v2 = hrr.bytes_to_phases(f2["hrr_vector"])
v1 = hrr.bytes_to_phases(f1["hrr_vector"], dim=self.hrr_dim)
v2 = hrr.bytes_to_phases(f2["hrr_vector"], dim=self.hrr_dim)
content_sim = hrr.similarity(v1, v2)
# High entity overlap + low content similarity = potential contradiction
@ -484,7 +484,7 @@ class FactRetriever:
scored = []
for row in rows:
fact = dict(row)
fact_vec = hrr.bytes_to_phases(fact.pop("hrr_vector"))
fact_vec = hrr.bytes_to_phases(fact.pop("hrr_vector"), dim=self.hrr_dim)
sim = hrr.similarity(target_vec, fact_vec)
fact["score"] = (sim + 1.0) / 2.0 * fact["trust_score"]
scored.append(fact)

View File

@ -561,7 +561,7 @@ class MemoryStore:
self._conn.commit()
return
vectors = [hrr.bytes_to_phases(row["hrr_vector"]) for row in rows]
vectors = [hrr.bytes_to_phases(row["hrr_vector"], dim=self.hrr_dim) for row in rows]
bank_vector = hrr.bundle(*vectors)
fact_count = len(vectors)

View File

@ -0,0 +1,166 @@
"""Storage-size regression tests for holographic HRR vectors."""
from __future__ import annotations
import pytest
np = pytest.importorskip("numpy")
from plugins.memory.holographic import holographic as hrr
from plugins.memory.holographic.retrieval import FactRetriever
from plugins.memory.holographic.store import MemoryStore
pytestmark = pytest.mark.skipif(
not hrr._HAS_NUMPY,
reason="holographic vector storage requires numpy",
)
def _float32_blob_size(dim: int) -> int:
return len(hrr._FLOAT32_BLOB_PREFIX) + dim * np.dtype(np.float32).itemsize
def test_phases_to_bytes_stores_float32_and_round_trips_with_dim() -> None:
dim = 1024
phases = hrr.encode_atom("storage-size-regression", dim=dim)
blob = hrr.phases_to_bytes(phases)
assert len(blob) == _float32_blob_size(dim)
restored = hrr.bytes_to_phases(blob, dim=dim)
assert restored.shape == (dim,)
np.testing.assert_allclose(restored, phases, rtol=0, atol=1e-6)
def test_phases_to_bytes_round_trips_without_dim() -> None:
dim = 1024
phases = hrr.encode_atom("dimensionless-round-trip", dim=dim)
restored = hrr.bytes_to_phases(hrr.phases_to_bytes(phases))
assert restored.shape == (dim,)
np.testing.assert_allclose(restored, phases, rtol=0, atol=1e-6)
def test_phases_to_bytes_round_trips_ambiguous_small_dims_without_dim() -> None:
dim = 2
phases = hrr.encode_atom("ambiguous-small-dimension", dim=dim)
restored = hrr.bytes_to_phases(hrr.phases_to_bytes(phases))
assert restored.shape == (dim,)
np.testing.assert_allclose(restored, phases, rtol=0, atol=1e-6)
def test_bytes_to_phases_rejects_malformed_float32_blobs() -> None:
phases = hrr.encode_atom("malformed-float32-blob", dim=2)
blob = hrr.phases_to_bytes(phases)
with pytest.raises(ValueError, match="expected .* for dim=3"):
hrr.bytes_to_phases(blob, dim=3)
with pytest.raises(ValueError, match="invalid payload byte length"):
hrr.bytes_to_phases(hrr._FLOAT32_BLOB_PREFIX + b"x")
def test_bytes_to_phases_reads_legacy_float64_blobs_with_and_without_dim() -> None:
dim = 1024
phases = hrr.encode_atom("legacy-float64-regression", dim=dim)
legacy_blob = phases.astype(np.float64, copy=False).tobytes()
assert len(legacy_blob) == dim * np.dtype(np.float64).itemsize
restored_with_dim = hrr.bytes_to_phases(legacy_blob, dim=dim)
restored_without_dim = hrr.bytes_to_phases(legacy_blob)
assert restored_with_dim.shape == (dim,)
assert restored_without_dim.shape == (dim,)
np.testing.assert_allclose(restored_with_dim, phases, rtol=0, atol=0)
np.testing.assert_allclose(restored_without_dim, phases, rtol=0, atol=0)
def test_bytes_to_phases_prefers_dim_matched_legacy_float64_on_prefix_collision() -> None:
dim = 4
legacy_blob = hrr._FLOAT32_BLOB_PREFIX + b"\0" * (
dim * np.dtype(np.float64).itemsize - len(hrr._FLOAT32_BLOB_PREFIX)
)
restored = hrr.bytes_to_phases(legacy_blob, dim=dim)
assert restored.shape == (dim,)
np.testing.assert_array_equal(
restored,
np.frombuffer(legacy_blob, dtype=np.float64).copy(),
)
def test_memory_store_reads_legacy_float64_vectors(tmp_path) -> None:
dim = 64
db_path = tmp_path / "legacy_memory_store.db"
with MemoryStore(db_path=db_path, hrr_dim=dim) as store:
fact_id = store.add_fact(
'Bob Stone keeps "legacy HRR vectors" searchable.',
category="compat",
tags="legacy storage",
)
fact_blob = store._conn.execute(
"SELECT hrr_vector FROM facts WHERE fact_id = ?",
(fact_id,),
).fetchone()["hrr_vector"]
bank_blob = store._conn.execute(
"SELECT vector FROM memory_banks WHERE bank_name = ?",
("cat:compat",),
).fetchone()["vector"]
legacy_fact_blob = hrr.bytes_to_phases(fact_blob, dim=dim).astype(np.float64).tobytes()
legacy_bank_blob = hrr.bytes_to_phases(bank_blob, dim=dim).astype(np.float64).tobytes()
store._conn.execute(
"UPDATE facts SET hrr_vector = ? WHERE fact_id = ?",
(legacy_fact_blob, fact_id),
)
store._conn.execute(
"UPDATE memory_banks SET vector = ? WHERE bank_name = ?",
(legacy_bank_blob, "cat:compat"),
)
store._conn.commit()
assert len(legacy_fact_blob) == dim * np.dtype(np.float64).itemsize
assert len(legacy_bank_blob) == dim * np.dtype(np.float64).itemsize
retriever = FactRetriever(store, hrr_dim=dim)
results = retriever.search("legacy HRR vectors", category="compat", limit=1)
assert results
assert results[0]["fact_id"] == fact_id
def test_memory_store_persists_fact_and_bank_vectors_as_float32(tmp_path) -> None:
dim = 64
db_path = tmp_path / "memory_store.db"
with MemoryStore(db_path=db_path, hrr_dim=dim) as store:
fact_id = store.add_fact(
'Alice Smith stores "compact HRR vectors" for Python tests.',
category="perf",
tags="hrr storage",
)
fact_blob = store._conn.execute(
"SELECT hrr_vector FROM facts WHERE fact_id = ?",
(fact_id,),
).fetchone()["hrr_vector"]
bank_blob = store._conn.execute(
"SELECT vector FROM memory_banks WHERE bank_name = ?",
("cat:perf",),
).fetchone()["vector"]
assert len(fact_blob) == _float32_blob_size(dim)
assert len(bank_blob) == _float32_blob_size(dim)
retriever = FactRetriever(store, hrr_dim=dim)
results = retriever.search("compact HRR vectors", category="perf", limit=1)
assert results
assert results[0]["fact_id"] == fact_id