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 的差異

面向turbovecFAISS IndexPQ
訓練不需要需要 train()
壓縮Lloyd-Max(數學推導)k-means++(資料驅動)
SIMD手寫 NEON + AVX-512BWAVX-512 VBMI + AVX2
過濾kernel 內部 allowlistpost-filter
語言Rust + PythonC++ + 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 文件 + 整合指南 + 效能圖

關鍵設計決策

  1. OnceLock 快取:rotation matrix、Lloyd-Max centroids 和 SIMD-blocked layout 使用 std::sync::OnceLock 延遲初始化,使 search() 只需 &self,支援多執行緒並行搜尋。

  2. TQ+ 凍結語義:per-coord calibration 在首次 add() 時擬合並凍結,後續 add() 重用同一組 shift/scale。這保證所有向量活在相同的校準座標系中。

  3. swap_remove 刪除:O(1) 刪除,將最後一個向量搬到被刪位置。犧牲排序穩定性換取效能,需要穩定 ID 時用 IdMapIndex

  4. 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")
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)適用場景
216x~0.96大量資料、記憶體受限
48x~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.rssearch.rs,是 SIMD 程式設計的必然需求
subprocess⚠️ 僅限 benchmarkbenchmarks/download_data.py 使用 subprocess.run(["curl", ...]) 下載測試資料,非核心路徑
HTTP URL⚠️ 僅限 benchmarkbenchmarks/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)vs IdMapIndex(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. 進一步閱讀

論文

文件

相關專案

  • FAISS — Meta 的向量搜尋庫(C++ / Python),turbovec 的主要對標
  • turboquant-py — 另一個 TurboQuant 的 Python 實作(純 Python,用於 benchmark 對照)
  • FAISS FastScan wiki — turbovec x86 SIMD kernel 參考的 pack layout 與 nibble-LUT 策略

Benchmark 資料