Cuts the within-batch semantic link pass to float32 and one reused buffer.
The batch was widened to float64, but PackedEmbedding is array("f") and pgvector's
vector column stores float32, so the extra 32 bits were padding nothing downstream
could read. Dropping to float32 halves the working set and puts BLAS on SGEMM;
normalising in place, deriving validity from the row norms instead of an (n, dim)
isfinite mask, and reusing one similarity buffer across blocks remove three further
copies. Peak transient falls 74-86% (at 5,000 facts, 235 MB -> 48 MB). argpartition
replaces a full sort that existed only to discard all but top_k, and the self-link
mask and score unboxing move out of the Python loop: 1.7-2.2x on a realistic
clustered batch, up to 4.8x when nearly every pair clears the threshold.
Norms are accumulated in float64 via einsum, since a float32 sum of 1536 squares
overflows above ~1e19 and flushes to zero below ~1e-22. The batch is copied with
np.array rather than aliased with asarray, as it is now normalised in place.
Verified against the float64 implementation across 120 randomised batches plus
NaN/inf/zero embeddings, degenerate magnitudes and all-ties: identical link pairs,
scores within 1e-6.
Hindsight Benchmarks
This directory contains benchmark suites for evaluating Hindsight's memory capabilities.
Prerequisites
-
Set up your environment variables in
.envat the project root:cp .env.example .env # Edit .env with your API keys -
Make sure you have
uvinstalled.
Available Benchmarks
LoComo
Tests conversational memory with multi-turn dialogues.
# Run from project root
./scripts/benchmarks/run-locomo.sh
# With options
./scripts/benchmarks/run-locomo.sh --max-conversations 10
./scripts/benchmarks/run-locomo.sh --skip-ingestion # Reuse existing data
./scripts/benchmarks/run-locomo.sh --use-think # Use think API
./scripts/benchmarks/run-locomo.sh --conversation conv-26 # Single conversation
Options:
--max-conversations N- Limit number of conversations--max-questions N- Limit questions per conversation--skip-ingestion- Skip data ingestion, use existing--use-think- Use think API instead of search + LLM--conversation NAME- Run specific conversation only--api-url URL- Custom API URL (default: local memory)--only-failed- Retry only failed questions--only-invalid- Retry only invalid questions
LongMemEval
Tests long-term memory across different categories.
# Run from project root
./scripts/benchmarks/run-longmemeval.sh
# With options
./scripts/benchmarks/run-longmemeval.sh --max-instances 50
./scripts/benchmarks/run-longmemeval.sh --category single-session-user
./scripts/benchmarks/run-longmemeval.sh --parallel 4 # Faster evaluation
Options:
--max-instances N- Limit total questions--max-instances-per-category N- Limit per category--skip-ingestion- Skip data ingestion--category NAME- Filter by category:single-session-usermulti-sessionsingle-session-preferencetemporal-reasoningknowledge-updatesingle-session-assistant
--parallel N- Parallel instances (default: 1)--only-failed- Retry failed questions--fill- Resume interrupted runs
Consolidation Performance
Tests consolidation throughput and identifies bottlenecks.
./scripts/benchmarks/run-consolidation.sh
# With custom memory count
NUM_MEMORIES=200 ./scripts/benchmarks/run-consolidation.sh
System Performance Test
Runs retain throughput and recall latency benchmarks using mock LLM + pg0. No external dependencies needed.
# Run all suites at default (small) scale
./scripts/benchmarks/run-perf-test.sh
# Quick smoke test
./scripts/benchmarks/run-perf-test.sh --scale tiny
# Single suite
./scripts/benchmarks/run-perf-test.sh --suite retain
# Save results
./scripts/benchmarks/run-perf-test.sh --output results.json
Options:
--scale {tiny,small,medium,large}- Test scale (default: small)--suite {retain,recall}- Run specific suite (default: all)--output PATH- Save JSON results to file
See perf/README.md for detailed documentation.
Multimodal Retain
Is the information in the pictures reaching memory? Retains the same KB articles
twice — once with their images inline, once with the images absent — and asks
questions that only the images can answer. See
multimodal_retain/README.md for the corpus, the
metrics and the recorded baseline.
Needs a server with a vision-capable retain LLM; without one the multimodal
arm is refused with 422 and the report says so rather than reporting zeros.
uv run python -m benchmarks.multimodal_retain run --api-url http://localhost:8917 --build my-branch
# re-render a saved artifact, no LLM calls
uv run python -m benchmarks.multimodal_retain report benchmarks/results/multimodal_retain/<artifact>.json
Options:
--article NAME- Run one article (repeatable)--build LABEL- Label recorded in the artifact--out PATH- Where to write the artifact--keep-banks- Leave the benchmark banks behind for inspection
Token Counting (micro)
Measures the token counting recall does per candidate fact, chunk and reranker document — wall time, CPU time (all threads) and peak traced allocation, against a set of cheaper spellings of the same count. No DB, no LLM, no network.
# All workloads
./scripts/benchmarks/run-token-count-bench.sh
# One call site, more repeats, raw results
./scripts/benchmarks/run-token-count-bench.sh --workload facts_200 --repeats 10 --json tok.json
# Against real text rather than the synthetic generator (a small vocabulary
# flatters every BPE implementation)
./scripts/benchmarks/run-token-count-bench.sh --corpus /path/to/text
# On another vocabulary instead of production's o200k_base
./scripts/benchmarks/run-token-count-bench.sh --encoding cl100k_base
# Include the tiktoken baseline (deliberately not a project dependency)
cd hindsight-dev && uv run --with tiktoken token-count-bench
Options:
--workload NAME- Run one workload (repeatable); shaped after a real call site--encoding NAME- Vocabulary to measure (defaulto200k_base, what production uses). A 200k vocabulary is a different amount of work per byte, so a ranking measured on one does not transfer to another for free.--repeats N- Timed repeats per variant, best-of (default: 5)--threads N-num_threadsfor the parallel batch variant--corpus PATH- Slice workload texts out of a real text file--no-conformance- Skip the count-agreement check on adversarial inputs--json PATH- Save raw results
Every variant is checked against the production count first, on inputs that have broken token counting before (special-token literals, unicode, empty). A variant that counts differently — or raises — is reported as such, never as a speedup.
Visualizer
View benchmark results in a web UI:
./scripts/benchmarks/start-visualizer.sh
# Opens at http://localhost:8001
Results
Results are saved in JSON format in each benchmark's results/ directory.