Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
S

sqlite-vector

> 编程语言
Open source

SQLite-Vector is a cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database.

1.1K stars0 likes0 views
WebsiteGitHub

About

SQLite-Vector is a cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database.

Building RAG or semantic search? SQLite-Vector ships as an extension you can drop into any SQLite app. Need it managed with sync and auth? SQLite Cloud free tier gives you 512 MB and 20 connections, no credit card.


SQLite Vector

SQLite Vector is a cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database. It works seamlessly on iOS, Android, Windows, Linux, and macOS, using just 30MB of memory by default. With support for Float32, Float16, BFloat16, Int8, UInt8, 1Bit, and TurboQuant 2/3/4-bit quantization, plus highly optimized distance functions, it's the ideal solution for Edge AI applications.

SQLite-Vector includes TurboQuant, a compact data-oblivious vector quantizer inspired by the Google Research paper TurboQuant: Online Vector Quantization with Near-Optimal Distortion Rate. It stores each vector as low-bit scalar codes plus one scale value, then scores directly from SIMD lookup-table kernels without reconstructing full vectors.

Highlights

  • No virtual tables required – store vectors directly as BLOBs in ordinary tables
  • Blazing fast – optimized C implementation with SIMD acceleration
  • TurboQuant support – SIMD 2-, 3-, and 4-bit quantization scans with qtype=TURBO
  • Low memory footprint – defaults to just 30MB of RAM usage
  • Zero preindexing needed – no long preprocessing or index-building phases
  • Works offline – perfect for on-device, privacy-preserving AI workloads
  • Plug-and-play – drop into existing SQLite workflows with minimal effort
  • Cross-platform – works out of the box on all major OSes

Why Use SQLite-Vector?

Feature SQLite-Vector Traditional Solutions
Works with ordinary tables ✅ ❌ (usually require special virtual tables)
Doesn't need preindexing ✅ ❌ (can take hours for large datasets)
Doesn't need external server ✅ ❌ (often needs Redis/FAISS/Weaviate/etc.)
Memory-efficient ✅ ❌
TurboQuant low-bit scanning ✅ ❌
Easy to use SQL ✅ ❌ (often complex JOINs, subqueries)
Offline/Edge ready ✅ ❌
Cross-platform ✅ ❌

Unlike other vector databases or extensions that require complex setup, SQLite-Vector just works with your existing database schema and tools.

Installation

Pre-built Binaries

Download the appropriate pre-built binary for your platform from the official Releases page:

  • Linux: x86 and ARM
  • macOS: x86 and ARM
  • Windows: x86
  • Android
  • iOS

Loading the Extension

-- In SQLite CLI
.load ./vector

-- In SQL
SELECT load_extension('./vector');

Or embed it directly into your application.

WASM Version

You can download the WebAssembly (WASM) version of SQLite with the SQLite Vector extension enabled from: https://www.npmjs.com/package/@sqliteai/sqlite-wasm

Example Usage

…

Benchmark

To add your machine to the table, one command — pass the CPU name and nothing else:

make benchmark HARDWARE="Apple M5 Pro"

It builds test/benchmark.c at -O3 with the same per-translation-unit SIMD flags the shipped extension uses, runs k=20 over 1,000,000 vectors of dimension 768 with cosine distance and 20 queries reporting the best, and prints two rows ready to paste, unedited, into the table below.

Two things it does so a pasted row cannot be wrong. The backend is appended by the binary from what the build actually selected, not typed by hand, so a row cannot claim AVX512 on a build that fell back to SSE2. And a run whose parameters differ from the ones the table is built on prints an explanation instead of rows, because a row measured on a different workload would sit in that table looking comparable without being comparable.

That second guard exists because the parameters are adjustable, just not for this table:

make benchmark NVECS=100000 DIM=384 K=10 DISTANCE=l2

That run prints the mode table for whatever you asked for, and no paste-ready rows.

Common to every row: the database is a file, never :memory: — an in-memory database puts the whole index in the process no matter how it is configured, which makes any memory figure meaningless. Vectors are uniform random with a fixed seed, so two machines measure the same data. The INT8 index is 740 MB on disk against 2930 MB of raw FLOAT32. Recall is the overlap with the exact FLOAT32 scan, the baseline everything is compared against, 100% by definition; on the reference machine that scan takes 484 ms/query, because it reads 3 GB per query.

The two rows per machine are the two ways the same index gets deployed. Preloaded holds it in the process after vector_quantize_preload(). Streamed walks it through a bounded buffer set by max_memory=30MB, the default, and what a device with less RAM than the index actually does. Max memory is measured, not the parameter echoed back: it is the peak the extension and SQLite had allocated during the scan.

Hardware

Hardware Vectors Index Max memory ms/query Mvec/s Recall@20
Apple M5 Pro - NEON 1,000,000 INT8 preloaded 740 MB 37.6 26.6 99.5%
Apple M5 Pro - NEON 1,000,000 INT8 streamed 30 MB 114.4 8.7 99.5%

Results from other CPUs welcome — run the command above and open a PR adding the two rows it prints.

The trade is 25x less memory for 3x the latency, with recall untouched: both rows read the same index, only how much of it is resident differs.

Two things the timings do not show. They are best-of-20, so the file is in the operating system's page cache by then — that cache lives outside the process and is evicted under pressure, so it is not in the memory column, but it is why the streamed row is not paying for storage reads. On a device where the index genuinely does not fit in RAM, add index size / storage bandwidth to the streamed number. And the preloaded row is almost unaffected by where the database lives, because after the one-time preload the scan reads the extension's own buffer and never goes back to SQLite.

Recall is repeated on every row on purpose: it depends on the data, not the hardware, so a row that disagrees with the others is a sign that machine selected a different SIMD backend than it should have.

Choosing a mode

INT8 is in the table above because it is the mode to reach for first. The others, same machine, same data, all preloaded:

Mode Index ms/query vs exact Recall@20
FLOAT32 exact 2930 MB 484.4 1.0x 100.0%
UINT8 740 MB 37.3 13.0x 33.8%
INT8 740 MB 37.6 12.9x 99.5%
1BIT 99 MB 2.5 195x 10.0%
TURBO2 195 MB 48.0 10.1x 45.2%
TURBO4 378 MB 151.5 3.2x 81.8%

The data is uniform random, the worst case for every quantizer: real embeddings have structure quantization exploits, so recall on your own vectors will be higher, often much higher. Read that column as a floor and a way to rank the modes, not as a prediction.

Three things are worth knowing before choosing.

For cosine, use INT8, not UINT8. Same size, same speed, 33.8% recall against 99.5%. Unsigned quantization subtracts the dataset minimum before scaling, and cosine measures angle, which that shift destroys. UINT8 is right for L2, where a common translation cancels. If you omit qtype the extension picks UINT8 for non-negative data — correct for L2, wrong for cosine — so set it explicitly when you use cosine.

1BIT is a filter, not an answer. 195x faster than exact and 30x smaller, at 10% recall here. It earns its place as a first pass whose survivors you re-rank at full precision.

TurboQuant buys memory against INT8, not speed. TURBO4 is 3.2x faster than the exact scan, so it is a real win over brute force — but INT8 is 4x faster again at twice the size, and TURBO2 is both smaller and faster than TURBO4 if 45% recall is enough. TurboQuant's lookup scan is one table gather per row: at dimension 768 that is 384 gathers into a 384 KB table per vector, already about one lookup per cycle, so its current storage layout has no headroom left (#57). Reach for it when the memory budget is what binds.

TurboQuant Benchmark and Recall

TurboQuant can be selected with qtype=TURBO,qbits=N, where N is 2, 3, or 4. Shorthand aliases are also available: TURBO2, TURBO3, and TURBO4.

-- Highest recall TurboQuant mode currently recommended as the default
SELECT vector_quantize('images', 'embedding', 'qtype=TURBO,qbits=4');

-- Smaller edge-oriented representation
SELECT vector_quantize('images', 'embedding', 'qtype=TURBO2');

An earlier synthetic benchmark on this dataset reported speedups of 15x for 4-bit and 38x for 2-bit against vector_full_scan(), with DOT distance and k=10. The Benchmark section above measures the same shape with cosine and k=20 and lands lower — 3.2x for 4-bit, 10.2x for 2-bit — mostly because the distance kernels have since been rewritten, which made the full-precision baseline it is compared against substantially faster. The direction is the same: TurboQuant beats brute force, and INT8 beats TurboQuant on speed while costing twice the memory.

For comparison, the raw FLOAT32 vectors alone are about 3.07 GB for 1M x 768 before SQLite row/page overhead. TurboQuant 4-bit reduces the scan representation to about 13% of that raw vector payload, TurboQuant 3-bit to about 10%, and TurboQuant 2-bit to about 7%. Actual resident memory depends on whether the database is in-memory or file-backed, SQLite cache settings, preloading, page cache behavior, and the host allocator.

The TurboQuant scan backend can be checked separately from the regular distance backend:

SELECT vector_backend(), vector_turboquant_backend();

For edge deployments, vector_quantize_memory(table, column) estimates the quantized scan representation. TurboQuant stores each row as rowid + scale + packed_codes, roughly rows * (8 + 4 + ceil(dim * qbits / 8)) bytes before allocator and SQLite cache overhead. The synthetic benchmark in test/benchmark_turboquant.c also supports PRELOAD=0 to compare the lower-RAM, non-preloaded path.

Real-dataset recall can be reproduced with test/recall_turboquant_real.py, which downloads Fashion-MNIST in the ANN-Benchmarks HDF5 format and compares TurboQuant against vector_full_scan() using L2 distance. Example run on macOS ARM64/NEON with 10,000 base vectors, 50 queries, and k=10:

Mode Quantized storage Full scan / query TurboQuant / query Speedup Recall@10
TurboQuant 4-bit 4.04 MB 16.32 ms 4.80 ms 3.40x 0.948
TurboQuant 3-bit 3.06 MB 16.32 ms 8.28 ms 1.97x 0.868
TurboQuant 2-bit 2.08 MB 16.32 ms 1.86 ms 8.78x 0.596

qbits=4 is the recommended starting point when recall matters. qbits=2 is useful for tighter edge memory budgets, but should be validated on the target embeddings because recall can drop significantly depending on the dataset.

Swift Package

You can [add this repository as a package dependency to your Swift project](https://

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

C

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言