turbovec 完整教學:基於 TurboQuant 的高效向量搜尋引擎
1. 專案定位
這是什麼?
turbovec 是一個以 Rust 撰寫、提供 Python bindings 的向量索引引擎。它實作了 Google Research 在 ICLR 2026 發表的 TurboQuant 演算法(arXiv:2504.19874),能將高維 embedding 向量壓縮到每座標 2-4 bits,同時保持接近 Shannon 理論下界的失真率。
為什麼重要?
在 RAG(Retrieval-Augmented Generation)應用中,向量索引是核心基礎設施。現有方案如 FAISS 需要 codebook 訓練(train 階段),且隨資料量增長需要重建索引。turbovec 的 data-oblivious 特性意味著:
- 零訓練成本:向量加入即索引,無需
index.train()步驟 - 極致壓縮:1000 萬筆 1536 維向量從 31 GB 壓到 4 GB(2-bit)
- 搜尋速度:手寫 NEON/AVX-512BW SIMD 核心,ARM 上比 FAISS 快 12-20%
- 純本地部署:無需雲端服務,適合隱私敏感場景
適用場景
| 場景 | 適合度 | 說明 |
|---|---|---|
| 百萬級 embedding RAG | ★★★★★ | 核心使用場景,壓縮 + 速度雙贏 |
| 隱私敏感的 air-gapped 環境 | ★★★★★ | 無網路依賴,純本地 |
| 需要 filtered search 的多租戶系統 | ★★★★★ | kernel 內部直接支援 allowlist |
| 即時向量流入(streaming ingest) | ★★★★☆ | online ingest 無需 rebuild |
| 小型原型(< 10K 向量) | ★★★☆☆ | 用 numpy brute-force 可能更簡單 |
| 需要 HNSW / IVF 圖索引 | ★★☆☆☆ | turbovec 是 flat scan,非圖索引 |
與 FAISS 的差異
| 面向 | turbovec | FAISS IndexPQ |
|---|---|---|
| 訓練 | 不需要 | 需要 train() |
| 壓縮 | Lloyd-Max(數學推導) | k-means++(資料驅動) |
| SIMD | 手寫 NEON + AVX-512BW | AVX-512 VBMI + AVX2 |
| 過濾 | kernel 內部 allowlist | post-filter |
| 語言 | Rust + Python | C++ + Python |
| Recall | 在 d≥1536 贏 0.4-3.4pp @R1 | 在 d=200 2-bit 贏 1.2pp @R1 |
2. 安裝指南
Python 安裝(推薦)
1# 基本安裝
2pip install turbovec
3
4# 含 framework 整合
5pip install turbovec[langchain] # LangChain
6pip install turbovec[llama-index] # LlamaIndex
7pip install turbovec[haystack] # Haystack
8pip install turbovec[agno] # Agno
9
10# 多個整合同時安裝
11pip install "turbovec[langchain,haystack]"
系統需求:
- Python ≥ 3.9
- numpy ≥ 1.20
- 支援平台:Linux(x86_64, aarch64)、macOS(Apple Silicon / Intel)、Windows
從原始碼建置
1# 前置需求
2pip install maturin
3
4# Clone + 建置
5git clone https://github.com/RyanCodrai/turbovec.git
6cd turbovec/turbovec-python
7maturin build --release
8pip install target/wheels/*.whl
Linux 需要 OpenBLAS:
1sudo apt-get install -y libopenblas-dev pkg-config
Rust 安裝
1cargo add turbovec
或在 Cargo.toml 中:
1[dependencies]
2turbovec = "0.8"
3. 核心架構解析
系統架構圖
graph TB
subgraph Input["輸入層"]
PY["Python API
TurboQuantIndex / IdMapIndex"]
RS["Rust API
turbovec crate"]
end
subgraph Encode["編碼管線"]
NORM["1. Normalize
分離 norm → 單位方向"]
ROT["2. Random Rotation
固定正交矩陣
座標 → Beta 分布"]
TQP["3. TQ+ Calibration
per-coord shift/scale
(首批擬合, 後續凍結)"]
LM["4. Lloyd-Max Quantize
預算分桶邊界
2-bit: 4 buckets / 4-bit: 16 buckets"]
PACK["5. Bit-pack
1536-dim: 6144B → 384B (2-bit)"]
RENORM["6. Length Renormalize
per-vector 校正因子
消除量化偏差"]
end
subgraph Search["搜尋核心"]
QROT["Query Rotation
旋轉到相同 domain"]
SIMD["SIMD Scoring Kernel"]
NEON["NEON
(ARM)"]
AVX["AVX-512BW
(x86)"]
AVX2["AVX2 Fallback
(x86 older)"]
HEAP["Top-K Min-Heap
+ Mask Filter"]
end
subgraph Storage["持久化"]
TV[".tv 格式
positional index"]
TVIM[".tvim 格式
id-mapped index"]
end
PY --> NORM
RS --> NORM
NORM --> ROT --> TQP --> LM --> PACK --> RENORM
RENORM --> QROT
QROT --> SIMD
SIMD --> NEON
SIMD --> AVX
SIMD --> AVX2
NEON --> HEAP
AVX --> HEAP
AVX2 --> HEAP
RENORM --> TV
RENORM --> TVIM
模組結構
1turbovec/ # Rust workspace root
2├── turbovec/ # 核心 crate (v0.8.0)
3│ ├── src/
4│ │ ├── lib.rs # TurboQuantIndex 主結構 + OnceLock 快取
5│ │ ├── encode.rs # 編碼管線 (normalize → rotate → TQ+ → quantize → pack)
6│ │ ├── search.rs # SIMD 搜尋核心 (NEON / AVX-512BW / AVX2 / scalar)
7│ │ ├── codebook.rs # Lloyd-Max codebook 計算
8│ │ ├── rotation.rs # 隨機正交矩陣生成
9│ │ ├── pack.rs # SIMD-blocked 記憶體重排
10│ │ ├── io.rs # .tv 檔案格式讀寫
11│ │ ├── id_map.rs # IdMapIndex (stable u64 ids)
12│ │ └── error.rs # 錯誤類型
13│ └── tests/ # 14 個 Rust 測試模組
14├── turbovec-python/ # Python binding (PyO3 + maturin)
15│ ├── python/turbovec/
16│ │ ├── __init__.py
17│ │ ├── langchain.py # LangChain VectorStore drop-in
18│ │ ├── llama_index.py # LlamaIndex VectorStore drop-in
19│ │ ├── haystack.py # Haystack DocumentStore drop-in
20│ │ └── agno.py # Agno VectorDB drop-in
21│ └── tests/ # Python 測試套件
22├── benchmarks/ # 效能基準測試套件
23│ ├── suite/ # 各 config 獨立測試腳本
24│ └── results/ # JSON 結果 + SVG 圖表
25└── docs/ # API 文件 + 整合指南 + 效能圖
關鍵設計決策
OnceLock 快取:rotation matrix、Lloyd-Max centroids 和 SIMD-blocked layout 使用
std::sync::OnceLock延遲初始化,使search()只需&self,支援多執行緒並行搜尋。TQ+ 凍結語義:per-coord calibration 在首次
add()時擬合並凍結,後續add()重用同一組 shift/scale。這保證所有向量活在相同的校準座標系中。swap_remove 刪除:O(1) 刪除,將最後一個向量搬到被刪位置。犧牲排序穩定性換取效能,需要穩定 ID 時用
IdMapIndex。Lazy dim:索引可在不指定維度的情況下建立,由首次
add()推斷並鎖定。
4. 核心功能詳解
4.1 基本使用:TurboQuantIndex
1import numpy as np
2from turbovec import TurboQuantIndex
3
4# 建立索引(dim=1536, 4-bit 量化)
5index = TurboQuantIndex(dim=1536, bit_width=4)
6
7# 加入向量(可多次呼叫,無需 rebuild)
8vectors = np.random.randn(1000, 1536).astype(np.float32)
9index.add(vectors)
10
11# 再加入更多(online ingest)
12more = np.random.randn(500, 1536).astype(np.float32)
13index.add(more)
14
15# 搜尋
16query = np.random.randn(1, 1536).astype(np.float32)
17scores, indices = index.search(query, k=10)
18# scores.shape == (1, 10), indices.shape == (1, 10)
19
20# 持久化
21index.write("my_index.tv")
22loaded = TurboQuantIndex.load("my_index.tv")
4.2 穩定 ID:IdMapIndex
1import numpy as np
2from turbovec import IdMapIndex
3
4index = IdMapIndex(dim=1536, bit_width=4)
5
6# 加入時指定外部 ID
7ids = np.array([1001, 1002, 1003, 1004, 1005], dtype=np.uint64)
8vectors = np.random.randn(5, 1536).astype(np.float32)
9index.add_with_ids(vectors, ids)
10
11# 搜尋回傳外部 ID
12scores, result_ids = index.search(query, k=3)
13
14# O(1) 刪除
15index.remove(1002)
16
17# 成員檢查
18assert 1003 in index
19assert 1002 not in index
20
21# 持久化
22index.write("my_index.tvim")
4.3 過濾搜尋(Filtered Search)
1# IdMapIndex: 透過 allowlist 過濾
2allowed = np.array([1001, 1003, 1005], dtype=np.uint64)
3scores, ids = index.search(query, k=10, allowlist=allowed)
4# 結果最多 min(k, len(allowed)) 筆
5
6# TurboQuantIndex: 透過 bool mask 過濾
7mask = np.ones(len(tq_index), dtype=bool)
8mask[disabled_slots] = False
9scores, slots = tq_index.search(query, k=10, mask=mask)
過濾在 SIMD kernel 內部執行,以 32-vector block 粒度短路不允許的 block。不是先搜尋再過濾(post-filter),而是搜尋時直接跳過,所以 selective allowlist 能避免大部分 SIMD 計算成本。
4.4 bit_width 選擇指引
| bit_width | 壓縮率 | Recall @1 (d=1536) | 適用場景 |
|---|---|---|---|
| 2 | 16x | ~0.96 | 大量資料、記憶體受限 |
| 4 | 8x | ~0.99+ | 一般用途、recall 優先 |
建議:一般場景用 4-bit;資料量 > 500M 且記憶體有限時考慮 2-bit。
4.5 Lazy 初始化
1# 不指定 dim,由第一次 add 自動推斷
2index = TurboQuantIndex(bit_width=4)
3index.dim # None
4
5index.add(vectors) # locks dim to vectors.shape[1]
6index.dim # 1536
4.6 搜尋快取預熱
1index = TurboQuantIndex.load("large_index.tv")
2index.prepare() # 預先建立 rotation matrix + centroids + blocked layout
3# 後續 search() 不需付出首次初始化成本
5. 應用場景
5.1 隱私優先的 RAG 系統
1from turbovec import IdMapIndex
2from sentence_transformers import SentenceTransformer
3
4model = SentenceTransformer("all-MiniLM-L6-v2") # 384-dim
5index = IdMapIndex(dim=384, bit_width=4)
6
7# Ingest
8texts = ["文件內容1...", "文件內容2...", ...]
9embeddings = model.encode(texts).astype(np.float32)
10ids = np.arange(len(texts), dtype=np.uint64)
11index.add_with_ids(embeddings, ids)
12
13# Search
14query_emb = model.encode(["搜尋問題"]).astype(np.float32)
15scores, doc_ids = index.search(query_emb, k=5)
全程本地執行,embedding + 向量索引都不需要雲端服務。
5.2 LangChain 整合
1from turbovec.langchain import TurboVecVectorStore
2from langchain_openai import OpenAIEmbeddings
3
4store = TurboVecVectorStore(embedding=OpenAIEmbeddings())
5store.add_texts(["doc1", "doc2", "doc3"])
6
7retriever = store.as_retriever(search_kwargs={"k": 5})
8docs = retriever.invoke("查詢問題")
5.3 Haystack 整合
1from turbovec.haystack import TurboVecDocumentStore
2from haystack import Document
3
4store = TurboVecDocumentStore()
5docs = [Document(content="文件內容", embedding=[0.1] * 1536)]
6store.write_documents(docs)
7
8results = store.embedding_retrieval(query_embedding=[0.2] * 1536, top_k=5)
5.4 多租戶過濾搜尋
1# 假設有一個多租戶系統
2import numpy as np
3from turbovec import IdMapIndex
4
5index = IdMapIndex(dim=1536, bit_width=4)
6# ... 加入所有租戶的文件 ...
7
8# 搜尋時只搜當前租戶的文件
9tenant_doc_ids = np.array(
10 db.execute("SELECT doc_id FROM docs WHERE tenant_id=?", (current_tenant,))
11 .fetchall(),
12 dtype=np.uint64
13)
14scores, ids = index.search(query_emb, k=10, allowlist=tenant_doc_ids)
6. 資安掃描報告
掃描範圍
- Rust 核心程式碼(turbovec/src/)
- Python bindings(turbovec-python/)
- 建置腳本與 CI(.github/workflows/)
- 基準測試(benchmarks/)
掃描結果
🟢 綠燈:整體安全等級良好
| 檢查項目 | 結果 | 說明 |
|---|---|---|
| 硬編碼密碼/金鑰 | ✅ 未發現 | 無 .env、credentials、secret 檔案 |
| 網路連線(核心) | ✅ 安全 | 核心 crate 無任何網路呼叫 |
| 危險 Python 函式 | ✅ 未發現 | 無 eval/exec/pickle/import/os.system |
| 供應鏈風險 | ✅ 低風險 | Rust deps 均為知名套件(rayon, ndarray, rand) |
| 輸入驗證 | ✅ 完善 | NaN/Inf/極大值皆在 add/search 入口攔截 |
| CI 安全 | ✅ 適當 | permissions: contents: read, concurrency cancel |
🟡 黃燈:注意事項
| 檢查項目 | 結果 | 說明 |
|---|---|---|
| unsafe Rust | ⚠️ 預期中 | SIMD 核心使用 unsafe(NEON intrinsics, AVX-512BW, AVX2),共約 20 處,全在 encode.rs 和 search.rs,是 SIMD 程式設計的必然需求 |
| subprocess | ⚠️ 僅限 benchmark | benchmarks/download_data.py 使用 subprocess.run(["curl", ...]) 下載測試資料,非核心路徑 |
| HTTP URL | ⚠️ 僅限 benchmark | benchmarks/download_data.py 有一個 HTTP URL 下載 GloVe 資料集 |
🟢 安全亮點
- 輸入驗證:
first_invalid_coord()函式在 add/search 入口檢查每個 float32 值,reject NaN、Inf、超過 1e16 的極大值,防止 f32 norm overflow 導致索引損壞。 - 序列化安全:
.tvim格式使用自訂二進制格式(非 pickle),載入時驗證 duplicate ids 並拒絕損壞檔案。 - 無網路依賴:核心 crate 完全離線執行,適合 air-gapped 部署。
- OnceLock 執行緒安全:search 快取使用
std::sync::OnceLock而非 unsafe static,正確處理多執行緒初始化競態。
結論
🟢 建議採用。核心程式碼安全設計良好,unsafe 用量合理且集中於 SIMD 效能關鍵路徑。無遠端程式碼執行、無序列化漏洞、無機密洩漏風險。
7. FAQ
Q1: turbovec 跟 FAISS 哪個好?
視場景而定。turbovec 在以下場景勝出:(1) 不想管理 train 階段、(2) 需要 online ingest、(3) 需要 kernel 內過濾搜尋、(4) ARM 平台效能關鍵。FAISS 在以下場景仍有優勢:(1) 需要 IVF/HNSW 圖索引、(2) GPU 搜尋、(3) 極低維(d<200)2-bit 場景。
Q2: 支援 GPU 嗎?
不支援。turbovec 專注 CPU SIMD 最佳化(NEON + AVX-512BW + AVX2)。若需 GPU 搜尋,考慮 FAISS GPU 或 cuVS。
Q3: 最大支援多少向量?
理論上受限於記憶體。n_vectors 使用 u32(.tv 格式 header),上限約 42 億。實務上 1 億筆 d=1536 4-bit 向量約需 ~77 GB RAM。
Q4: 可以混合 bit_width 嗎?
不行。一個索引只能使用一種 bit_width(2, 3, 或 4)。若需不同精度,建立多個獨立索引。
Q5: dim 有什麼限制?
dim 必須是 8 的倍數且 ≥ 8。常見 embedding 維度(384, 768, 1024, 1536, 3072)都滿足此條件。
Q6: add() 之後需要 rebuild 嗎?
不需要。這是 turbovec 最大的賣點之一。TurboQuant 是 data-oblivious 的,每次 add() 直接編碼並追加到索引,不影響已有向量。
Q7: 如何從 FAISS IndexPQ 遷移?
無法直接轉換索引檔案。需要從原始向量重新建立 turbovec 索引。但 API 設計刻意接近 FAISS 風格(add, search, write, load),程式碼遷移成本低。
8. 進階技巧
8.1 多執行緒搜尋
1import concurrent.futures
2from turbovec import TurboQuantIndex
3
4index = TurboQuantIndex.load("large_index.tv")
5index.prepare() # 先預熱快取
6
7queries = [...] # 多個查詢批次
8
9with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
10 futures = [executor.submit(index.search, q, 10) for q in queries]
11 results = [f.result() for f in futures]
search() 只需 &self,OnceLock 快取保證執行緒安全。
8.2 批次搜尋效能
1# 比起逐筆搜尋,批次搜尋更有效率
2queries = np.stack([q1, q2, q3, q4]).astype(np.float32) # shape (4, dim)
3scores, indices = index.search(queries, k=10)
4# scores.shape == (4, 10)
8.3 索引檔案備份策略
1# .tv / .tvim 是自包含的二進制檔案,直接複製即可
2import shutil
3shutil.copy("production.tvim", "backup.tvim")
4
5# 或用 rsync 增量同步
6# rsync -av production.tvim backup-server:/backups/
8.4 監控索引大小
1import os
2
3index = TurboQuantIndex(dim=1536, bit_width=4)
4index.add(vectors)
5
6# 預估記憶體用量
7n = len(index)
8dim = index.dim
9bw = index.bit_width
10packed_bytes = n * dim * bw // 8
11scale_bytes = n * 4 # float32 per vector
12total_mb = (packed_bytes + scale_bytes) / 1024 / 1024
13print(f"Index: {n} vectors, ~{total_mb:.1f} MB in memory")
9. 整合進其他工作流
9.1 與 SQLite 搭配的混合檢索
1import sqlite3
2import numpy as np
3from turbovec import IdMapIndex
4
5# Metadata 存 SQLite
6conn = sqlite3.connect("metadata.db")
7conn.execute("""
8 CREATE TABLE IF NOT EXISTS docs (
9 id INTEGER PRIMARY KEY,
10 title TEXT,
11 category TEXT,
12 created_at TEXT
13 )
14""")
15
16# Embeddings 存 turbovec
17index = IdMapIndex(dim=1536, bit_width=4)
18
19# Stage 1: SQL 預過濾
20candidate_ids = np.array(
21 conn.execute(
22 "SELECT id FROM docs WHERE category=? AND created_at > ?",
23 ("research", "2026-01-01")
24 ).fetchall(),
25 dtype=np.uint64
26).flatten()
27
28# Stage 2: 向量搜尋(僅在候選集內)
29scores, ids = index.search(query_emb, k=10, allowlist=candidate_ids)
9.2 與 FastAPI 搭配
1from fastapi import FastAPI
2from turbovec import IdMapIndex
3import numpy as np
4
5app = FastAPI()
6index = IdMapIndex.load("production.tvim")
7index.prepare()
8
9@app.post("/search")
10async def search(query: list[float], k: int = 10):
11 q = np.array([query], dtype=np.float32)
12 scores, ids = index.search(q, k=k)
13 return {"scores": scores[0].tolist(), "ids": ids[0].tolist()}
9.3 定期增量更新
1def daily_ingest(new_vectors, new_ids):
2 index = IdMapIndex.load("production.tvim")
3 index.add_with_ids(new_vectors, new_ids)
4 index.write("production.tvim") # 原子寫入
10. 重點摘要 Checklist
- 核心概念:TurboQuant 是 data-oblivious 量化器,利用隨機旋轉使座標分布可預測,預算 Lloyd-Max codebook
- 兩種索引:
TurboQuantIndex(positional, fast)vsIdMapIndex(stable u64 ids, 適合有刪除需求) - 壓縮率:2-bit = 16x, 4-bit = 8x(相對 float32)
- bit_width 選擇:一般用 4-bit;記憶體極度受限用 2-bit
- dim 限制:必須是 8 的倍數且 ≥ 8
- Online ingest:add() 不需 rebuild,適合串流場景
- 過濾搜尋:IdMapIndex 用 allowlist、TurboQuantIndex 用 mask,kernel 內部短路
- 執行緒安全:search() 是
&self,可多執行緒並行 - prepare():可選的快取預熱,消除首次搜尋延遲
- Framework 整合:LangChain / LlamaIndex / Haystack / Agno 皆有 drop-in replacement
- 安全性:核心無網路、無 pickle、有完善輸入驗證,unsafe 僅限 SIMD 必要路徑
- 檔案格式:
.tv(positional)/.tvim(id-mapped),跨版本穩定
11. 進一步閱讀
論文
- TurboQuant: Online Vector Quantization with Near-optimal Distortion Rate(ICLR 2026)— turbovec 實作的演算法
- RaBitQ: Quantizing High-Dimensional Vectors with a Theoretical Error Bound for Approximate Nearest Neighbor Search(SIGMOD 2024)— per-vector length-renormalization 校正的來源
文件
相關專案
- FAISS — Meta 的向量搜尋庫(C++ / Python),turbovec 的主要對標
- turboquant-py — 另一個 TurboQuant 的 Python 實作(純 Python,用於 benchmark 對照)
- FAISS FastScan wiki — turbovec x86 SIMD kernel 參考的 pack layout 與 nibble-LUT 策略
Comments