SQLite-Vector is now licensed under Apache 2.0 SQLite-Vector, a cross-platform SQLite extension from SQLite AI that adds vector search to embedded databases, is now licensed under Apache 2.0. The extension supports Float32, Float16, BFloat16, Int8, UInt8, 1Bit, and TurboQuant 2/3/4-bit quantization, runs on iOS, Android, Windows, Linux, and macOS with a default 30MB memory footprint, and requires no virtual tables or preindexing. SQLite Cloud's free tier offers 512 MB and 20 connections for managed deployments. Production-grade vector search inside SQLite. Exact search, SIMD distance kernels, and SIMD 2/3/4-bit TurboQuant scans — runs anywhere SQLite runs: mobile, browser, edge, server. Free managed instance → https://dashboard.sqlitecloud.io/auth/sign-in · Docs https://docs.sqlitecloud.io/docs/ai-overview · Website https://sqlite.ai · Blog https://blog.sqlite.ai Data: Vector https://github.com/sqliteai/sqlite-vector · Sync https://github.com/sqliteai/sqlite-sync · Columnar https://github.com/sqliteai/sqlite-columnar · JS https://github.com/sqliteai/sqlite-js AI: AI https://github.com/sqliteai/sqlite-ai · Agent https://github.com/sqliteai/sqlite-agent · Memory https://github.com/sqliteai/sqlite-memory · MCP https://github.com/sqliteai/sqlite-mcp 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 https://dashboard.sqlitecloud.io/auth/sign-in gives you 512 MB and 20 connections, no credit card. 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 https://arxiv.org/abs/2504.19874 . 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. - No virtual tables required – store vectors directly as BLOB s 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 | 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. Download the appropriate pre-built binary for your platform from the official Releases https://github.com/sqliteai/sqlite-vector/releases page: - Linux: x86 and ARM - macOS: x86 and ARM - Windows: x86 - Android - iOS -- In SQLite CLI .load ./vector -- In SQL SELECT load extension './vector' ; Or embed it directly into your application. You can download the WebAssembly WASM version of SQLite with the SQLite Vector extension enabled from: https://www.npmjs.com/package/@sqliteai/sqlite-wasm https://www.npmjs.com/package/@sqliteai/sqlite-wasm -- Create a regular SQLite table CREATE TABLE images id INTEGER PRIMARY KEY, embedding BLOB, -- store Float32/UInt8/etc. label TEXT ; -- Insert a BLOB vector Float32, 384 dimensions using bindings INSERT INTO images embedding, label VALUES ?, 'cat' ; -- Insert a JSON vector Float32, 384 dimensions INSERT INTO images embedding, label VALUES vector as f32 ' 0.3, 1.0, 0.9, 3.2, 1.4,... ' , 'dog' ; -- Initialize the vector. By default, the distance function is L2. -- To use a different metric, specify one of the following options: -- distance=L1, distance=COSINE, distance=DOT, distance=SQUARED L2, or distance=HAMMING. SELECT vector init 'images', 'embedding', 'type=FLOAT32,dimension=384' ; -- If your embeddings are already unit length, say so: FLOAT32 cosine scans then compute -- 1 - dot instead of the full cosine, with the same results. -- SELECT vector init 'images', 'embedding', 'type=FLOAT32,dimension=384,distance=COSINE,normalized=1' ; -- Quantize vector SELECT vector quantize 'images', 'embedding' ; -- Or use TurboQuant for compact 2/3/4-bit quantization SELECT vector quantize 'images', 'embedding', 'qtype=TURBO,qbits=4' ; -- Optional preload quantized version in memory for a 4x/5x speedup SELECT vector quantize preload 'images', 'embedding' ; -- Run a nearest neighbor query on the quantized version returns top 20 closest vectors SELECT e.id, v.distance FROM images AS e JOIN vector quantize scan 'images', 'embedding', ?, 20 AS v ON e.id = v.rowid; -- Streaming mode: omit k to get rows progressively, use SQL to filter and limit SELECT e.id, v.distance FROM images AS e JOIN vector quantize scan 'images', 'embedding', ? AS v ON e.id = v.rowid WHERE e.label = 'cat' LIMIT 10; 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 | 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. 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 https://github.com/sqliteai/sqlite-vector/issues/57 . Reach for it when the memory budget is what binds. 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 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. You can add this repository as a package dependency to your Swift project https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app Add-a-package-dependency . After adding the package, you'll need to set up SQLite with extension loading by following steps 4 and 5 of this guide https://github.com/sqliteai/sqlite-extensions-guide/blob/main/platforms/ios.md 4-set-up-sqlite-with-extension-loading . Here's an example of how to use the package: python import vector ... var db: OpaquePointer? sqlite3 open ":memory:", &db sqlite3 enable load extension db, 1 var errMsg: UnsafeMutablePointer