elidedb-qbe / python /elidedb /patches.py
SudharshanR
ElideDB query by example: no text, no model at query time
a1dd5ba
Raw
History Blame Contribute Delete
3.66 kB
"""LATE INTERACTION channel: MaxSim over stored patch sign-codes.
Built by scripts/patch_ingest.py, which carries the measurement that
justifies it. Here is only the scoring, and it has one trick worth
reading.
The codes are one bit per PCA dimension, so a patch is a vector of
+-1 and its score against a query q is sum_d sign_d * q_d. Unpacking
1.15M patches x 256 bits into floats to run that as a matmul costs
294 MB of RAM and throws away the reason for storing bits. Instead,
for each of the 32 code BYTES, precompute the partial sum of the 8 q
dimensions it covers, for all 256 possible byte values: a (32, 256)
table. Scoring is then one gather per byte - 32 lookups per patch, no
unpacking, and the codes stay exactly as they sit on disk. Building
the table costs 32 x 256 adds, once per query.
Query side stays full precision (asymmetric quantization): there is
one query and a million patches, so precision is free where it is
scarce and paid where it is cheap.
"""
from __future__ import annotations
import numpy as np
_PIDX: dict = {}
def _load(store):
"""(codes uint8 (rows, 32*keep), episode row-groups, mu, R)."""
ver = store.table("patch_codes").state().version
key = (str(store.dir), ver)
if key in _PIDX:
return _PIDX[key]
from pathlib import Path
tbl = store.table("patch_codes").scan()
meta = store.table("patch_codes").state().meta or {}
dim = int(meta.get("dim", 256))
keep = int(meta.get("keep", 256))
raw = tbl.column("code").to_pylist()
C = np.frombuffer(b"".join(raw), np.uint8).reshape(
len(raw), keep, dim // 8)
idx: dict = {}
for i, (s, a) in enumerate(zip(tbl.column("stream").to_pylist(),
tbl.column("ts").to_pylist())):
idx.setdefault((str(s), int(a)), []).append(i)
idx = {k: np.asarray(v, np.int64) for k, v in idx.items()}
b = np.load(Path(store.dir) / "_patch_basis.npz")
if len(_PIDX) > 4:
_PIDX.clear()
_PIDX[key] = (C, idx, b["mu"], b["R"], dim)
return _PIDX[key]
def _tables(q, dim):
"""(dim/8, 256) partial sums of q over every possible code byte."""
nb = dim // 8
# bit j of a byte is dimension 8*b + (7 - j) — packbits is MSB first
bits = ((np.arange(256)[:, None] >> np.arange(7, -1, -1)) & 1)
T = np.empty((nb, 256), np.float32)
for b in range(nb):
w = q[b * 8:(b + 1) * 8]
# code bit 1 means +1 on that dimension, 0 means -1
T[b] = bits @ w - (1 - bits) @ w
return T
def patch_lookup(store, texts):
"""lookup(stream, t0, t1) -> MaxSim score for the episode.
`texts` is a list of query atoms. Each atom takes its best patch
over every patch of every frame of the episode (MaxSim), then the
atoms are combined with min — the same soft-AND the other binding
channels use, so a compound query needs each named thing to be
somewhere in the episode rather than one strong match to carry it.
"""
from .sig2 import _text_vec
C, idx, mu, R, dim = _load(store)
nb = dim // 8
Ts = []
for t in texts:
v = (np.asarray(_text_vec(t), np.float32) - mu) @ R
v /= np.linalg.norm(v) + 1e-8
Ts.append(_tables(v, dim))
ar = np.arange(nb)
def lookup(s, a, b):
rows = idx.get((str(s), int(a)))
if rows is None or len(rows) == 0:
return float("nan")
codes = C[rows].reshape(-1, nb) # (frames*patches, nb)
best = None
for T in Ts:
sc = T[ar, codes].sum(1).max() / np.sqrt(dim)
best = sc if best is None else min(best, sc)
return float(best)
return lookup