Zero-dependency core. Exact + HNSW search. Disk persistence.

Vector similarity
in pure Go

Six metrics. Exact packed search or optional HNSW. Text embedding. Atomic gob/JSON persistence. Zero allocations on distance computation. All pure Go.

main.go
package main

import "github.com/BackendStack21/go-vector/pkg/vector"

func main() {
    store := vector.NewStore(vector.CosineDistance)

    store.Add("cat", vector.Vector{1.0, 0.8, 0.1})
    store.Add("dog", vector.Vector{0.9, 0.7, 0.1})
    store.Add("car", vector.Vector{0.1, 0.0, 0.9})

    query  := vector.Vector{1.0, 0.9, 0.1}
    results := store.Search(query, 2)

    for _, r := range results {
        fmt.Printf("%s: %.4f\n", r.ID, r.Distance)
    }
    // cat: 0.0005
    // dog: 0.0026
}
0
dependencies
6
distance metrics
0
allocations per distance
v1.3
gob files still load

Everything you need for
vector similarity

Ten vector operations, six distance metrics, and a production-ready in-memory vector store. All zero-allocation on read paths.

Optional packages add an HNSW approximate index, int8 quantization, and a mutex-wrapped store. The core pkg/vector API stays stdlib-only and backward compatible with v1.3 gob/JSON files.

Vector Algebra

Dot product, norm, normalize, add, subtract, scale. Full suite of vector operations with zero-overhead []float32.

Distance Metrics

Cosine, Euclidean, Manhattan, Dot Product, Chebyshev, Hamming. Single-pass computation. Choose lower-is-better or higher-is-better sorting.

Vector Store

Add, upsert, metadata, filters, radius, and top-k. Cloned outputs — no accidental mutation. LockedStore when writers share the index.

Text Embedding

Built-in Random Projections embedder (Johnson-Lindenstrauss). Bring your own OpenAI or Ollama adapter via the Embedder interface.

Disk Persistence

Gob or JSON, path or io.Writer. SaveAtomic for temp+rename. v1.3 files still load. ~60MB per 10K vectors at 1536d.

Minimal Surface

Standard library only. No CGo, no BLAS, no syscalls, no I/O. Compiles anywhere Go runs.

HNSW

Pure-Go approximate index in pkg/hnsw. Same Vector type. Exact Store stays brute-force.

Int8 Quantization

pkg/quantize packs vectors to int8 without changing float32 Store precision. Optional sibling, not a silent mode flag.

Filtered Search

SearchOpts adds predicates, metadata equality, score thresholds, parallel scan, and ID-only results that skip cloning.

From text to vectors
in one call

Built-in Random Projections embedder using the Johnson-Lindenstrauss lemma. Deterministic, zero-dependency, ~10µs per document. Or bring your own via the Embedder interface.

Built-in: Random Projections

Sparse random projection matrix — each vocabulary token contributes to ~33% of output dimensions. Vocabulary built from your corpus. Output always L2-normalized.

rp := vector.NewRandomProjections(256)
rp.Fit(corpus)

v, _ := rp.Embed("machine learning")
// v is a 256-dim normalized Vector

Swap backends anytime

Implement Embedder for a custom backend. Batching is a separate BatchEmbedder so existing implementations keep compiling. Built-in HTTP talks to OpenAI, Ollama, LM Studio, and Azure-style paths.

type Embedder interface {
    Embed(text string) (Vector, error)
    Dims() int
}

e := vector.NewHTTPEmbedder(url, model, 0,
    vector.WithRetry(3))

Exact first.
Approximate when you need it.

Uniform-length stores pack into a row-major array with cached norms. When n outgrows a linear scan, switch to HNSW without changing embedders or Vector.

HNSW graph

Sibling package, stdlib plus pkg/vector only. Own gob format. RecallAgainst compares to an exact store. Re-rank candidates with vector.Rerank when you want exact scores on a shortlist.

idx := hnsw.New(384, vector.CosineDistance)
idx.Add("doc", vec)
hits := idx.Search(query, 10)
// optional exact rescore
hits = vector.Rerank(query, hits, vector.CosineDistance, 5)

Int8 store

Quantize for memory. Int8Store is a new type so float32 search cannot silently lose precision. Reconstruct or keep a float32 store for exact ranking.

q, scale := quantize.ToInt8(vec)
qs := quantize.NewInt8Store(vector.CosineDistance)
qs.Add("doc", vec)
qs.Search(query, 10)

Save. Load. Repeat.

Gob-encode your entire store to a file. Restore with metric, metadata, and all data intact. JSON export for debugging. v1.3 files still load.

Gob — compact and atomic

Binary encoding via encoding/gob. SaveAtomic writes a temp file, fsyncs, then renames. Streaming WriteTo / ReadFrom for any io.Writer. Corrupt length mismatches are rejected.

store.SaveAtomic("/data/vectors.db")

restored := vector.NewStore(...)
restored.Load("/data/vectors.db")
// all data + metric restored

JSON — human-readable

Export as indented JSON for debugging, inspection, or cross-language interop. Same roundtrip guarantees.

store.SaveJSON("/data/vectors.json")
restored.LoadJSON("/data/vectors.json")

Embedder state persistence

Save and restore the RandomProjections embedder — vocabulary, projection matrix, dimensions. No refitting needed across restarts.

rp.SaveEmbedder("/data/embedder.gob")

restored, _ := vector.LoadEmbedder("/data/embedder.gob")
// Same text → identical vector

Zero-allocation distance
computation

All benchmarks at 1536 dimensions (typical embedding size) on AMD EPYC. Single-pass Cosine and Euclidean — no intermediate vector allocations.

OperationLatencyAllocs
Dot7.5 µs0
Cosine9.4 µs0
Euclidean8.7 µs0
Manhattan9.4 µs0
Search 100 vectors3.2 ms63 KB
Search 1,000 vectors28.5 ms74 KB
Search 10,000 vectors315 ms185 KB

Brute-force search is O(n·d). Suitable for datasets up to ~100K vectors. Use pkg/hnsw beyond that, or SearchIDs when you do not need cloned vectors.

Minimal attack surface

Pure float32 math in the core. No CGo, no syscalls. Query paths return zero/nil instead of panicking. Persistence validates structure before replacing in-memory state.

SAFE Attack surface Pure float32 operations — no injection, no deserialization, no file access
SAFE Panic-free All edge cases (mismatched lengths, zero vectors, empty stores) return zero/nil
SAFE Memory hygiene Store outputs are always cloned. Internal state never leaks.
AWARE Float32 overflow Dot products of large vectors (>10⁵ dims with large magnitudes) can overflow. Normalize first.
SAFE Load validation Gob/JSON with mismatched IDs, vectors, metadata, or HNSW edges is rejected. Existing state is left unchanged.
AWARE Thread safety Store is read-safe but not write-safe. Use LockedStore or an external mutex when writers are involved.

Install

One import. Zero-dependency core. Optional HNSW, ONNX, and int8 siblings.

$ go get github.com/BackendStack21/go-vector
$ go test github.com/BackendStack21/go-vector/pkg/vector/ -cover