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")

这是 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 值得尝试。

项目地址: https://github.com/RyanCodrai/turbovec

论文: TurboQuant: Data-Oblivious Vector Quantization