Using pg_turbovec

Cookbook-style examples. For the full reference see the source comments in src/distance.rs, src/aggregate.rs, src/cast.rs, and src/knn.rs, or read the generated sql/pg_turbovec--<version>.sql.

1. Install and load

CREATE EXTENSION pg_turbovec;
SET search_path = public, turbovec;

All extension objects live in the turbovec schema. Set search_path once per session — or qualify references with turbovec. — and the rest of these examples work as written.

2. Define a column

CREATE TABLE docs (
    id        bigserial PRIMARY KEY,
    body      text,
    embedding turbovec.vector
);

vector accepts any dimension from 1 to 16 000. A single index fixes the dimension at build time, so keep your column homogeneous — mixed-dim rows are stored fine, but the ANN function will skip rows with a mismatched dim.

The TurboQuant kernel additionally requires that dim be a multiple of 8. Pad your embeddings to the next multiple of 8 if your model emits an awkward dimension.

3. Insert and read

INSERT INTO docs (body, embedding) VALUES
  ('hello',  '[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]'),
  ('world',  '[0.2, 0.1, 0.4, 0.3, 0.6, 0.5, 0.8, 0.7]');

-- Casting from a Rust / Python emitter that produces an array literal:
INSERT INTO docs (body, embedding)
VALUES ('greeting',
        ARRAY[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8]::real[]::vector);

4. Exact (brute-force) similarity search

For corpora up to ~10 000 rows, brute-force scan with the cosine operator beats ANN on latency and is exact:

SELECT id, body, embedding <=> $1 AS distance
FROM   docs
ORDER  BY embedding <=> $1
LIMIT  10;

Operators:

Op Meaning
<-> Euclidean (L2) distance
<#> Negative inner product
<=> Cosine distance
<+> Taxicab (L1) distance

<#> is negated deliberately: under ORDER BY ... ASC, the most- similar row sorts first — same convention as pgvector.

5. ANN search via turbovec.knn()

For larger corpora the function-driven ANN beats brute force by 1–2 orders of magnitude:

SELECT k.id, k.score, d.body
FROM   turbovec.knn(
         'docs'::regclass,
         'id', 'embedding',
         '[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8]'::vector,
         10
       ) k
JOIN   docs d ON d.id = k.id
ORDER  BY k.score DESC;

Higher score means more similar (raw inner product on unit vectors). The function is STABLE PARALLEL SAFE and rebuilds the in-memory index on the first call in a backend, then caches it (turbovec.cache_size_mb) for subsequent calls in that backend.

5.1 Tuning bit width

SELECT * FROM turbovec.knn(
    'docs'::regclass, 'id', 'embedding',
    '[...]'::vector, 10,
    bit_width => 2);   -- 2-bit, 32x compression vs FP32

Choices: 2 (most compressed, slight recall loss), 3, or 4 (default; near-FP32 recall).

5.2 Disabling implicit normalisation

TurboQuant assumes unit-norm inputs. By default we normalise both the corpus and the query inside knn(). If your upstream emits already-unit vectors and you want to skip the work:

SET turbovec.normalize_on_insert = off;

If you turn this off and feed non-unit vectors, recall will drop.

6. Aggregates

SELECT avg(embedding) FROM docs;     -- element-wise mean (centroid)
SELECT sum(embedding) FROM docs WHERE topic = 'pets';

Both aggregates use f64 accumulators internally and merge in parallel-safe combinefuncs, so they run cleanly under max_parallel_workers_per_gather > 0.

7. Element-wise arithmetic

-- Difference of centroids — the classic "is X more like A or B?"
-- composition.
SELECT k.id
FROM   turbovec.knn(
         'docs'::regclass, 'id', 'embedding',
         (SELECT avg(embedding) FROM docs WHERE topic = 'cats')
           - (SELECT avg(embedding) FROM docs WHERE topic = 'dogs'),
         5
       ) k;

Operators on vector:

Op Function Result
+ vec_add element-wise sum
- vec_sub element-wise difference
* vec_mul Hadamard (element-wise) product

8. Configuration GUCs

GUC Type Default Effect
turbovec.bit_width_default int 4 default bit_width for indexes built without an explicit reloption
turbovec.cache_size_mb int 256 per-backend cache cap for materialised indexes
turbovec.warn_on_rebuild bool true NOTICE on rematerialisation
turbovec.search_concurrency int 1 rayon fan-out cap inside a single batched search
turbovec.normalize_on_insert bool true unit-normalise on ingestion / query

These are the GUCs most relevant to the SQL knn()/type surface; all are USERSET (settable per-session). pg_turbovec registers 20 GUCs in total — the index-scan tuning knobs (probes, search_k, oversample, iterative_scan, out_of_core, hi_dim_rerank, build_parallelism, …) are listed in the README Configuration table and in src/guc.rs. Note they are registered by the shared library, so pg_turbovec must be in shared_preload_libraries for any turbovec.* GUC to exist.

9. Coexisting with pgvector

pg_turbovec and pgvector can coexist in the same database. They own different types (turbovec.vector vs public.vector) and their distance operators dispatch by operand type — no collisions.

To migrate from a pgvector.vector column to turbovec.vector:

-- Phase 1: add a parallel column.
ALTER TABLE docs ADD COLUMN embedding_tv turbovec.vector;

UPDATE docs
SET    embedding_tv = embedding::real[]::turbovec.vector;

-- Phase 2: drop the old column at your leisure.
ALTER TABLE docs DROP COLUMN embedding;
ALTER TABLE docs RENAME COLUMN embedding_tv TO embedding;

10. Diagnostics

SELECT turbovec.turbovec_version();          -- e.g. '2.10.2' (matches the installed extension)
SELECT turbovec.vector_dims(emb) FROM docs LIMIT 1;
SELECT turbovec.vector_norm(emb) FROM docs LIMIT 1;
SELECT turbovec.turbovec_self_score(turbovec.vec_normalize(emb), 4)
  FROM docs LIMIT 1;

turbovec_self_score round-trips a vector through the upstream turbovec::IdMapIndex and reports the inner-product self-score — useful for verifying the SIMD kernel is producing sane answers on your hardware.