faiss clib

FAISS vector similarity search library for PascalAI. Efficiently find the K nearest neighbors among millions of embedding vectors — the foundation for RAG pipelines, image deduplication, recommender systems, and semantic search.

ppm install faiss
System dependency: requires libfaiss-dev (Ubuntu/Debian: sudo apt install libfaiss-dev) or build from source at github.com/facebookresearch/faiss

Overview

FAISS (Facebook AI Similarity Search) is Meta's battle-tested library for fast nearest-neighbor search over dense float vectors. It underpins production RAG systems, recommendation engines, and image search at billion-vector scale. The PascalAI binding exposes its full index hierarchy — from exact brute-force search (IndexFlat) to compressed approximate indexes (IVFPQ, HNSW) — through a clean, idiomatic API.

Choose your index by the accuracy/speed/memory tradeoff you need: fitFlat for exact results on small corpora, fitHNSW for fast approximate search with low memory, fitIVFPQ for billion-scale datasets where RAM is constrained.

Quick Start

Exact Search (IndexFlat)

uses faiss;

{ Store 1000 document embeddings (dim=768) }
var idx := NewIndex(768, fitFlat);
{ Assume embeddings is an array of 1000*768 doubles }
Add(idx, embeddings, 1000);
WriteLn('Stored: ' + IntToStr(IndexSize(idx)));

{ Search: find 5 nearest to a query embedding }
var result := SearchOne(idx, queryVec, 5);
for var i := 0 to result.K - 1 do
  WriteLn('ID=' + IntToStr(result.Labels[i]) +
          '  dist=' + FloatToStr(result.Distances[i]));
FreeIndex(idx);

HNSW for Fast Approximate Search

uses faiss;

var idx := NewHNSWIndex(384, 32);  { dim=384, M=32 connections }
AddWithIDs(idx, vectors, docIDs, 50000);

{ Save to disk }
SaveIndex(idx, '/data/docs.faiss');
FreeIndex(idx);

{ Later: load and search }
var idx2 := LoadIndex('/data/docs.faiss');
var r := SearchOne(idx2, queryEmb, 10);

Cosine Similarity via Normalized Vectors

uses faiss;

{ Normalize vectors first for cosine similarity }
NormalizeVectors(myVectors, N, D);

var idx := NewIndex(D, fitFlatIP);  { inner product = cosine after L2-norm }
Add(idx, myVectors, N);
var r := SearchOne(idx, queryNorm, 5);

Index Types

ConstantDescription
fitFlatExact brute-force L2 search. 100% accurate; slow on large corpora. Best for < 100 K vectors.
fitFlatIPExact inner-product search. Use with L2-normalized vectors to get cosine similarity.
fitIVFFlatApproximate IVF (inverted file) with flat quantizer. Requires training. Good accuracy/speed balance.
fitHNSWGraph-based approximate search (Hierarchical Navigable Small World). No training; very fast queries; higher RAM use.
fitPQProduct Quantization — compresses vectors to reduce memory by 4–64× at the cost of some accuracy.
fitIVFPQIVF + PQ combined. Best for billion-scale datasets with limited RAM.
fitLSHLocality-Sensitive Hashing for binary/Hamming distance search.

API Reference

Index Lifecycle

FunctionDescription
NewIndex(dim, kind)Create a new empty index with the given vector dimension and index type constant
NewIVFIndex(dim, nlist)Create an IVF index with nlist Voronoi cells; call Train before Add
NewHNSWIndex(dim, M)Create an HNSW index with M bi-directional links per node (16–64 typical)
FreeIndex(idx)Release all memory held by an index handle
IsTrained(idx)Returns True if the index has been trained and is ready to accept vectors
Train(idx, vecs, n)Train an IVF or PQ index on a representative sample of n vectors

Add Vectors

FunctionDescription
Add(idx, vecs, n)Add n vectors to the index; assigns sequential IDs starting from 0
AddWithIDs(idx, vecs, ids, n)Add n vectors with explicit integer IDs (e.g. database row IDs)
AddOne(idx, vec)Convenience wrapper to add a single vector; returns its assigned ID
IndexSize(idx)Returns the number of vectors currently stored in the index

Search

FunctionDescription
Search(idx, queries, nq, k)Search nq query vectors in one batch; returns TSearchResult with Labels and Distances arrays (shape nq×k)
SearchOne(idx, query, k)Search a single query vector for its k nearest neighbors; returns TSearchResult
RangeSearch(idx, query, radius)Return all vectors within radius distance of the query; result count varies
SetNProbe(idx, nprobe)For IVF indexes: set number of cells to visit per query (higher = more accurate, slower)

Persistence

FunctionDescription
SaveIndex(idx, path)Serialize the index to a binary file at path (can be reloaded across processes)
LoadIndex(path)Deserialize and return an index from a previously saved binary file
IndexToBytes(idx)Serialize the index to an in-memory byte array for storage in a database or object store
IndexFromBytes(data)Deserialize an index from a byte array returned by IndexToBytes

Clustering & Utilities

FunctionDescription
KMeans(vecs, n, k, niter)Run K-means clustering on n vectors for niter iterations; returns centroid array and cluster assignments
NormalizeVectors(vecs, n, d)In-place L2 normalization of n vectors of dimension d; required before using fitFlatIP for cosine similarity
PairwiseDistances(a, b, na, nb, d)Compute the full na×nb L2 distance matrix between two sets of vectors; returns a flat Float array

Key Concepts

Vector Dimensions

All vectors in a single index must have the same dimensionality. Common embedding dimensions: 384 (MiniLM), 768 (BERT-base), 1536 (OpenAI text-embedding-3-small), 3072 (text-embedding-3-large). The dimension is fixed at index creation time and cannot be changed.

L2 vs Cosine Similarity

fitFlat and fitIVFFlat minimize L2 (Euclidean) distance. For cosine similarity, call NormalizeVectors on all vectors before indexing and use fitFlatIP (inner product). After normalization, inner product equals cosine similarity.

Training

IVF and PQ indexes require a training pass before vectors can be added. Pass a representative sample of at least 39 × nlist vectors to Train. HNSW and Flat indexes do not require training. Check IsTrained(idx) before calling Add if using a trainable index type.

nprobe Tuning

For IVF indexes, SetNProbe(idx, nprobe) controls how many Voronoi cells are visited per query. The default is 1 (fastest, least accurate). Set nprobe = nlist for exact results. A value of 8–64 is a practical accuracy/speed sweet spot for most workloads.

Memory

Approximate per-vector memory use: Flat — 4×D bytes (full float32 storage); HNSW — 4×D + ~M×8 bytes per vector for the graph; PQ — roughly D/M bytes per vector after compression (M = number of sub-quantizers). For 10 M vectors at dim=768, a Flat index uses ~29 GB; an IVFPQ index can reduce this to under 1 GB.