memory and model

turbovec: shrink the RAM a RAG index needs

It compresses vectors to 4 bits and searches them anyway

RAG1 spends RAM2 on vectors. turbovec compresses them to 2 or 4 bits so one machine holds more, with no training step and fast deletes, at the cost of being a scan rather than a graph index.

Requirements and key details
  • Ten million 768-dimensional vectors drop from 31 GB to 4 GB.
  • At 4-bit it runs 3.8 to 5.1 times faster than FAISS.
  • No training step, so a new vector is searchable immediately.
  • Deletes take microseconds.

A vector index eats RAM too

RAG turns documents into embeddings and stores them. As float323, ten million 768-dimensional vectors take 31 GB, which fills RAM before any model is loaded.

turbovec stores those vectors at 2 or 4 bits per dimension and searches the compressed form, so the same ten million fit in 4 GB.

It is a library, not a service: it runs inside your process and persists to one file, so it slots into an existing pipeline4.

Small data blocks passing through a GPU into stacked layers, then splitting into RAM and an SSD
Where the vectors sit and how many decides your RAM budget, and compression is the cheapest lever.

Do the memory arithmetic first

float32 is 4 bytes per dimension, 4-bit is 0.5 and 2-bit is 0.25, so the arithmetic gives 8 and 16 times. The README reports measured factors of 7.8 and 15.5.

At 100K vectors of 1536 dimensions, float32 takes 586 MB, 4-bit 75 MB and 2-bit 38 MB. Budgeting with that ratio avoids running out of RAM.

Scroll sideways to compare all columns.

Memory by bit width
WidthBytes per dimension100K·1536Versus float32
float324 bytes586MB1×
4-bit mode0.5 bytes75MB7.8× smaller
2-bit mode0.25 bytes38MB15.5× smaller

Install it and build an index

Install with pip and pass a NumPy array. Vectors are searchable as soon as they are added, with no training step.

Input must be a 2-D float32 array. Other dtypes raise instead of converting, which catches conversion mistakes early.

The scores are inner products, so normalise vectors before adding if you want cosine similarity.

Building an index
import numpy as np
from turbovec import TurboQuantIndex

vectors = np.load("embeddings.npy").astype(np.float32)  # (n, 1536)
index = TurboQuantIndex(dim=1536, bit_width=4)
index.add(vectors)

scores, slots = index.search(vectors[:1], k=5)
index.sync("index.tv")

Read speed and recall together

4-bit search runs 3.8 to 5.1 times faster than FAISS: 4.0 times on ARM and 4.9 on x86, so both architectures gain.

Recall@1 is 0.959 and 0.981 at 4-bit against FAISS at 0.966 and 0.971, for 1536 and 3072 dimensions.

2-bit drops to 0.901 and 0.931, and low dimensions lose more: GloVe at 200 dimensions reaches 0.572. That is the number that decides the width.

Scroll sideways to compare all columns.

Figures with their conditions
Metric4-bit mode2-bit modeFAISS
Search speed3.8–5.1× faster1.8–2.5× fasterCriterion
recall@1 (1536/3072)0.959 / 0.9810.901 / 0.9310.966 / 0.971
recall@1 (GloVe 200)0.8600.5720.842

Measured on 100K embeddings, k=64, 1,000 queries, median of 5 runs, on GCP c4a-standard-8 (ARM) and c3-standard-8 (x86).

A Mac mini, a dual-GPU tower and a large desktop feeding tokens into one stopwatch
Speed is only comparable with its conditions attached, so record corpus size and core count alongside it.

Frequent adds and deletes favour it

With no training step, an added vector is searchable at once, so a pipeline that keeps ingesting documents never has to reindex.

Deletes take microseconds. FAISS repacks codes on every delete, which runs 0.19 to 1.03 seconds.

Saving writes only the change, so it stays in milliseconds however large the index, and a crash keeps everything up to the last sync.

A filter can ride along with the query

Pass an allowlist5 and the search only returns from it, which lets permissions or a time window live outside the index.

IdMapIndex keeps your own ids instead of slot positions, which makes matching keys from another system straightforward.

Searching with an allowlist
from turbovec import IdMapIndex

index = IdMapIndex(dim=1536, bit_width=4)
index.add_with_ids(vectors, ids)

allowed = np.array([1001, 1003, 1042], dtype=np.uint64)
scores, ids = index.search(query, k=10, allowlist=allowed)
A card catalog with a few drawers pulled out, their contents flowing to a workbench
Passing a filter to the search pulls only allowed entries, so permissions stay outside the index.

It drops into a RAG stack

Store integrations exist for LangChain, LlamaIndex, Haystack and Agno, matching each framework's in-memory store so the swap is a replacement.

With a local embedding model the whole index runs offline, and turbovec itself never leaves the process.

When it is the wrong tool

It is not recommended when you need a graph index: it scans every vector, so cost grows linearly. At 100K a query is 0.13 to 0.25 ms on eight cores, and for hundreds of millions behind strict latency, HNSW or IVF is the right shape.

Exact float results are out of reach: quantization6 is lossy, and at 4-bit the top hit matches the float answer 96 to 98 percent of the time. Check recall on a sample of your own data.

It is not a server either: no network API7, replication or multi-tenancy, it is a component you place inside a stack that has those.

Dimensions must be multiples of 8 and no wider than 16,384, and the only widths are 2 and 4 bits.

Terminology notes

  1. Retrieval-Augmented Generation — A method that retrieves material relevant to a query, adds it to the model input, and generates an answer from that context.

    Back to the text
  2. System RAM — System memory that temporarily holds data while programs run.

    Back to the text
  3. FP32 — A 32-bit floating-point format. It uses more memory per value than BF16 and can represent values more precisely.

    Back to the text
  4. Pipeline — A sequence of processing stages from input to output. Different models or tools may be used at each stage.

    Back to the text
  5. Allowlist — A policy that permits only pre-approved targets, such as tools, commands, users, or network destinations.

    Back to the text
  6. Quantization — A way to represent model weights or values with fewer bits, reducing storage and memory use. The coarser representation can also affect accuracy.

    Back to the text
  7. API — A defined interface that lets other code call a program’s functions.

    Back to the text

Frequently asked questions

What should you check first in this article?
RAG spends RAM on vectors. turbovec compresses them to 2 or 4 bits so one machine holds more, with no training step and fast deletes, at the cost of being a scan rather than a graph index.
When is it better not to choose this?
It is not recommended when you need a graph index: it scans every vector, so cost grows linearly. At 100K a query is 0.13 to 0.25 ms on eight cores, and for hundreds of millions behind strict latency, HNSW or IVF is the right shape.