TurboVec 是什么?
TurboVec 是一个用 Rust 编写、提供 Python 绑定的高性能向量索引库,基于 Google Research 的 TurboQuant 量化算法构建。它的核心目标是解决 RAG(检索增强生成)系统中的两大痛点:内存占用和搜索速度。
为什么需要 TurboVec?
在传统的向量搜索场景中,如果你有一个包含 1000 万条文档的语料库,使用 float32 格式存储向量需要约 31 GB 的 RAM。而 TurboVec 通过数据无关量化(data-oblivious quantization)技术,将同样的数据集压缩到仅 4 GB —— 内存占用降低了 87%,同时搜索速度还超越了 FAISS。
TurboVec vs FAISS
| 特性 | FAISS | TurboVec |
|---|---|---|
| 内存占用 | 高(float32 或 PQ 量化) | 极低(TurboQuant 量化) |
| 是否需要训练 | ✅ 需要训练阶段 | ❌ 无需训练,即插即用 |
| 在线添加向量 | ⚠️ 需重建索引 | ✅ 实时添加,无需重建 |
| 过滤搜索 | 需后处理 | ✅ 内核级支持,无性能损失 |
| SIMD 优化 | 有 | 手写 NEON (ARM) + AVX-512BW (x86) |
| 纯本地运行 | ✅ | ✅ |
| Python 绑定 | ✅ | ✅ |
| Rust 原生支持 | ❌ | ✅ |
核心技术:TurboQuant 算法
TurboQuant 是 Google Research 在 2025 年提出的新型量化算法,关键特点包括:
- 数据无关(Data-Oblivious):不需要针对特定数据集训练码本(codebook),避免了传统 PQ(Product Quantization)的训练开销
- 逼近香农下界:在失真度上接近理论最优值
- 位宽灵活:支持 2-bit、4-bit、8-bit 等多种量化精度,平衡召回率和内存占用
在基准测试中,TurboVec 在 ARM 架构上比 FAISS IndexPQFastScan 快 12-20%,在 x86 架构上持平或略优。
安装 TurboVec
TurboVec 提供了 Python 和 Rust 两种语言的接口。
Python 安装
pip install turbovec
如果需要与 LangChain、LlamaIndex 等框架集成,可以安装对应的额外依赖:
# LangChain 集成
pip install turbovec[langchain]
# LlamaIndex 集成
pip install turbovec[llama-index]
# Haystack 集成
pip install turbovec[haystack]
# Agno 集成
pip install turbovec[agno]
Rust 安装
在 Cargo.toml 中添加依赖:
[dependencies]
turbovec = "0.1"
快速上手:Python 基础用法
创建索引并添加向量
import numpy as np
from turbovec import TurboQuantIndex
# 创建索引:维度 1536(OpenAI embedding 默认维度),4-bit 量化
index = TurboQuantIndex(dim=1536, bit_width=4)
# 生成示例向量(实际使用中替换为你的 embedding 向量)
vectors = np.random.rand(10000, 1536).astype(np.float32)
# 添加向量到索引
index.add(vectors)
# 可以继续添加更多向量,无需重建索引
more_vectors = np.random.rand(5000, 1536).astype(np.float32)
index.add(more_vectors)
print(f"索引中共有 {len(index)} 个向量")
执行搜索
# 生成查询向量
query = np.random.rand(1536).astype(np.float32)
# 搜索最相似的 10 个向量
scores, indices = index.search(query, k=10)
print("相似度分数:", scores)
print("向量索引:", indices)
持久化保存与加载
# 保存索引到磁盘
index.write("my_index.tq")
# 从磁盘加载索引
loaded_index = TurboQuantIndex.load("my_index.tq")
# 验证加载成功
scores, indices = loaded_index.search(query, k=10)
进阶功能一:使用外部 ID 映射
在实际应用中,你通常需要将向量索引与你数据库中的文档 ID 关联起来。TurboVec 提供了 IdMapIndex 来支持这一需求。
添加带 ID 的向量
import numpy as np
from turbovec import IdMapIndex
# 创建支持外部 ID 的索引
index = IdMapIndex(dim=1536, bit_width=4)
# 假设你有 3 个向量,对应的外部 ID 为 1001, 1002, 1003
vectors = np.random.rand(3, 1536).astype(np.float32)
external_ids = np.array([1001, 1002, 1003], dtype=np.uint64)
# 添加向量和对应的外部 ID
index.add_with_ids(vectors, external_ids)
# 搜索返回的是外部 ID,而非内部索引
query = np.random.rand(1536).astype(np.float32)
scores, ids = index.search(query, k=10)
print("返回的外部 ID:", ids) # [1001, 1003, 1002, ...]
删除向量
IdMapIndex 支持通过外部 ID 直接删除向量,时间复杂度为 O(1):
# 删除 ID 为 1002 的向量
index.remove(1002)
# 再次搜索,1002 不会再出现在结果中
scores, ids = index.search(query, k=10)
print("删除后的 ID:", ids) # 不再包含 1002
持久化带 ID 的索引
# 保存
index.write("my_index.tvim")
# 加载
loaded_index = IdMapIndex.load("my_index.tvim")
进阶功能二:过滤搜索(Filter-at-Search)
这是 TurboVec 的核心亮点之一。在传统向量数据库中,如果你想限制搜索结果只来自某个租户或某个时间范围,通常需要先搜索出大量候选结果,然后在应用层进行过滤 —— 这会导致召回率下降和性能浪费。
TurboVec 在内核级别支持过滤,通过 allowlist 参数传入允许的 ID 列表,SIMD 内核会直接在计算过程中跳过不允许的槽位。
场景:多租户 RAG 系统
假设你有一个多租户的 RAG 系统,每个租户只能访问自己的文档:
import numpy as np
from turbovec import IdMapIndex
# 创建索引
idx = IdMapIndex(dim=1536, bit_width=4)
# 假设有 10000 个向量,每个向量对应一个文档 ID
vectors = np.random.rand(10000, 1536).astype(np.float32)
doc_ids = np.arange(1, 10001, dtype=np.uint64)
idx.add_with_ids(vectors, doc_ids)
# 模拟数据库查询:获取租户 A 的文档 ID 列表
# 实际场景中,这会是从 PostgreSQL / MySQL 查询的结果
tenant_a_docs = np.array([1, 5, 10, 15, 20, 25, 30, 35, 40, 45], dtype=np.uint64)
# 在租户 A 的文档范围内进行搜索
query = np.random.rand(1536).astype(np.float32)
scores, ids = idx.search(query, k=5, allowlist=tenant_a_docs)
print("租户 A 的最相关文档:", ids)
# 输出只会包含 tenant_a_docs 中的 ID
性能优势
过滤发生在 SIMD 内核内部,采用 32 向量块粒度的短路机制:
- 如果某个块中没有任何允许的槽位,直接跳过整个块的 LUT 查找和评分计算
- 如果块中有部分允许的槽位,只对允许的槽位进行评分
- 对于选择性很强的过滤条件(允许的 ID 占总数比例很小),可以避免大部分 SIMD 计算开销
输出结果长度为 min(k, len(allowlist)),当允许列表小于 k 时,返回恰好 len(allowlist) 个结果,而不是用不相关的结果填充。
与主流 RAG 框架集成
TurboVec 提供了对 LangChain、LlamaIndex、Haystack 和 Agno 的无缝集成,只需替换导入语句即可。
LangChain 集成
from langchain_community.vectorstores import TurboVec
from langchain_openai import OpenAIEmbeddings
# 初始化 embeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 创建 TurboVec 向量存储
vector_store = TurboVec.from_documents(
documents=documents, # 你的 Document 列表
embedding=embeddings,
bit_width=4 # 4-bit 量化
)
# 相似性搜索
results = vector_store.similarity_search("你的问题", k=5)
# 持久化
vector_store.save_local("turbovec_index")
# 加载
loaded_store = TurboVec.load_local("turbovec_index", embeddings)
LlamaIndex 集成
from llama_index.vector_stores.turbovec import TurboVecVectorStore
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
# 加载文档
documents = SimpleDirectoryReader("./data").load_data()
# 创建 TurboVec 向量存储
vector_store = TurboVecVectorStore(dim=1536, bit_width=4)
# 创建索引
index = VectorStoreIndex.from_documents(
documents,
vector_store=vector_store
)
# 查询引擎
query_engine = index.as_query_engine()
response = query_engine.query("你的问题")
print(response)
Haystack 集成
from haystack_integrations.document_stores.turbovec import TurboVecDocumentStore
from haystack.components.embedders import SentenceTransformersDocumentEmbedder
from haystack import Pipeline
# 创建文档存储
document_store = TurboVecDocumentStore(dim=768, bit_width=4)
# 嵌入器
embedder = SentenceTransformersDocumentEmbedder(model="sentence-transformers/all-MiniLM-L6-v2")
# 构建管道
pipeline = Pipeline()
pipeline.add_component("embedder", embedder)
# ... 继续添加其他组件
Rust 原生用法
如果你正在用 Rust 构建高性能后端服务,可以直接使用 TurboVec 的原生 Rust API。
基本用法
use turbovec::TurboQuantIndex;
use ndarray::Array2;
fn main() {
// 创建索引:1536 维,4-bit 量化
let mut index = TurboQuantIndex::new(1536, 4);
// 准备向量数据(这里用随机数示例)
let vectors = Array2::<f32>::random((10000, 1536), &mut rand::thread_rng());
// 添加向量
index.add(&vectors);
// 准备查询向量
let query = Array1::<f32>::random(1536, &mut rand::thread_rng());
// 搜索最相似的 10 个向量
let results = index.search(&query, 10);
println!("Top 10 结果: {:?}", results);
// 持久化
index.write("index.tv").unwrap();
// 加载
let loaded = TurboQuantIndex::load("index.tv").unwrap();
}
带外部 ID 的索引
use turbovec::IdMapIndex;
fn main() {
let mut index = IdMapIndex::new(1536, 4);
let vectors = Array2::<f32>::random((100, 1536), &mut rand::thread_rng());
let ids = vec![1001u64, 1002, 1003, /* ... */];
index.add_with_ids(&vectors, &ids);
let query = Array1::<f32>::random(1536, &mut rand::thread_rng());
let (scores, returned_ids) = index.search(&query, 10);
println!("返回的外部 ID: {:?}", returned_ids);
// 删除
index.remove(1002);
// 持久化
index.write("index.tvim").unwrap();
let loaded = IdMapIndex::load("index.tvim").unwrap();
}
性能基准测试
根据官方提供的基准测试数据,TurboVec 在不同数据集和位宽下的表现如下:
召回率对比(TurboQuant vs FAISS IndexPQ)
测试条件:100K 向量,k=64
| 数据集 | 位宽 | TurboVec R@1 | FAISS R@1 | 优势 |
|---|---|---|---|---|
| GloVe d=200 | 4-bit | +0.3 pts | 基准 | TurboVec 更高 |
| OpenAI d=1536 | 2-bit | +0.4 pts | 基准 | TurboVec 更高 |
| OpenAI d=1536 | 4-bit | +1.2 pts | 基准 | TurboVec 更高 |
| OpenAI d=3072 | 4-bit | +3.4 pts | 基准 | TurboVec 显著更高 |
在所有测试中,两者在 k=4 时都收敛到召回率 1.0。
速度对比
- ARM (NEON):TurboVec 比 FAISS IndexPQFastScan 快 12-20%
- x86 (AVX-512BW):TurboVec 与 FAISS 持平或略优
内存占用
| 向量数量 | 维度 | float32 内存 | TurboVec (4-bit) 内存 | 节省 |
|---|---|---|---|---|
| 1000 万 | 1536 | ~31 GB | ~4 GB | 87% |
| 100 万 | 1536 | ~3.1 GB | ~400 MB | 87% |
| 10 万 | 1536 | ~310 MB | ~40 MB | 87% |
实战案例:构建本地 RAG 系统
下面是一个完整的示例,展示如何使用 TurboVec 构建一个完全本地的 RAG 系统,无需任何云服务。
环境准备
pip install turbovec sentence-transformers langchain-community langchain-openai
完整代码
import numpy as np
from turbovec import IdMapIndex
from sentence_transformers import SentenceTransformer
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 1. 加载嵌入模型(本地运行,无需 API Key)
print("加载嵌入模型...")
model = SentenceTransformer('all-MiniLM-L6-v2') # 384 维
dim = 384
# 2. 加载和分割文档
print("加载文档...")
loader = TextLoader('./data/my_documents.txt', encoding='utf-8')
documents = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
chunks = splitter.split_documents(documents)
print(f"分割成 {len(chunks)} 个文本块")
# 3. 生成向量并构建索引
print("生成向量并构建索引...")
index = IdMapIndex(dim=dim, bit_width=4)
texts = [chunk.page_content for chunk in chunks]
metadata_list = [chunk.metadata for chunk in chunks]
# 批量生成向量
embeddings = model.encode(texts, show_progress_bar=True)
# 添加向量和元数据(用索引作为外部 ID)
external_ids = np.arange(len(texts), dtype=np.uint64)
index.add_with_ids(embeddings.astype(np.float32), external_ids)
# 保存索引
index.write("rag_index.tvim")
print("索引已保存")
# 4. 搜索函数
def search(query: str, k: int = 5):
"""搜索最相关的文本块"""
# 加载索引
idx = IdMapIndex.load("rag_index.tvim")
# 生成查询向量
query_embedding = model.encode([query])[0].astype(np.float32)
# 搜索
scores, ids = idx.search(query_embedding, k=k)
# 返回结果
results = []
for score, doc_id in zip(scores, ids):
doc_id = int(doc_id)
results.append({
'text': texts[doc_id],
'score': float(score),
'metadata': metadata_list[doc_id]
})
return results
# 5. 测试搜索
if __name__ == "__main__":
query = "什么是 TurboVec?"
results = search(query, k=3)
print("\n=== 搜索结果 ===")
for i, result in enumerate(results, 1):
print(f"\n[{i}] 相似度: {result['score']:.4f}")
print(f"内容: {result['text'][:200]}...")
添加过滤功能
如果你的文档有分类标签,可以在搜索时进行过滤:
def search_with_filter(query: str, category: str, k: int = 5):
"""带分类过滤的搜索"""
idx = IdMapIndex.load("rag_index.tvim")
# 找出属于指定分类的文档 ID
allowed_ids = np.array([
i for i, meta in enumerate(metadata_list)
if meta.get('category') == category
], dtype=np.uint64)
if len(allowed_ids) == 0:
return []
# 生成查询向量
query_embedding = model.encode([query])[0].astype(np.float32)
# 带过滤的搜索
scores, ids = idx.search(query_embedding, k=k, allowlist=allowed_ids)
results = []
for score, doc_id in zip(scores, ids):
doc_id = int(doc_id)
results.append({
'text': texts[doc_id],
'score': float(score),
'metadata': metadata_list[doc_id]
})
return results
# 只搜索"技术"分类的文档
results = search_with_filter("如何安装?", category="技术", k=3)
常见问题
Q1: TurboVec 适合什么场景?
- 内存受限的环境:需要在有限 RAM 中存储大规模向量索引
- 隐私敏感场景:数据不能离开本地或 VPC
- 动态增长的语料库:需要频繁添加新向量,无法承受重建索引的开销
- 多租户 RAG:需要在搜索时进行细粒度的权限过滤
Q2: TurboVec 不适合什么场景?
- 超大规模分布式搜索:如果需要跨多台机器分布索引,可能需要考虑 Milvus、Weaviate 等分布式向量数据库
- 需要复杂元数据过滤:TurboVec 目前只支持基于 ID 的过滤,复杂的元数据查询需要在应用层处理
Q3: 如何选择 bit_width?
- 4-bit:推荐默认值,在召回率和内存之间取得良好平衡
- 2-bit:极致压缩,召回率略有下降,适合内存极度受限的场景
- 8-bit:最高精度,内存占用约为 4-bit 的两倍,召回率接近 float32
Q4: TurboVec 与 FAISS 可以同时使用吗?
可以。你可以用 FAISS 做粗召回(coarse search),然后用 TurboVec 做精排(rerank),或者反过来。两者的 API 设计思路不同,可以互补使用。
总结
TurboVec 是一个值得关注的新兴向量搜索库,它通过 TurboQuant 算法在内存占用、搜索速度和易用性三个方面取得了很好的平衡:
- ✅ 内存降低 87%:1000 万向量从 31GB 降到 4GB
- ✅ 速度超越 FAISS:ARM 上快 12-20%,x86 上持平
- ✅ 无需训练:即插即用,支持在线添加向量
- ✅ 内核级过滤:多租户 RAG 的理想选择
- ✅ 生态集成:LangChain、LlamaIndex、Haystack 无缝对接
如果你的 RAG 项目面临内存瓶颈或需要纯本地部署,TurboVec 值得尝试。