跳到主要内容

Faiss 向量搜索库

Faiss 是 Meta(Facebook)开源的高效相似度搜索和聚类库,专为大规模向量搜索设计。它支持十亿级向量的搜索,是工业界最常用的向量搜索解决方案之一。

概述

为什么选择 Faiss

特性说明
Meta开源Facebook AI Research 开发,经过大规模验证
极致性能C++实现,支持 GPU 加速,速度极快
算法丰富支持多种 ANN 算法和量化方法
内存优化多种压缩技术,降低内存占用
灵活组合索引可组合,适应不同场景
多语言支持Python、C++、Go、Rust 等绑定

适用场景

  • 大规模搜索:十亿级向量的高效检索
  • 推荐系统:实时相似度计算
  • 图像检索:以图搜图、内容去重
  • 研究实验:快速验证向量搜索算法
  • 嵌入式部署:轻量级部署到边缘设备

快速开始

1. 安装 Faiss

# CPU 版本
pip install faiss-cpu

# GPU 版本 (需要 CUDA)
pip install faiss-gpu

# 从源码编译 (获取最新功能)
git clone https://github.com/facebookresearch/faiss.git
cd faiss
cmake -B build . -DFAISS_ENABLE_GPU=OFF
make -C build -j faiss
make -C build -j swigfaiss

2. 第一个示例

import faiss
import numpy as np

# 生成示例数据 (10000 条 128 维向量)
np.random.seed(42)
dimension = 128
db_size = 10000
query_size = 5

# 随机生成数据库向量 (需要归一化用于余弦相似度)
db_vectors = np.random.random((db_size, dimension)).astype('float32')
db_vectors = db_vectors / np.linalg.norm(db_vectors, axis=1, keepdims=True)

# 随机生成查询向量
query_vectors = np.random.random((query_size, dimension)).astype('float32')
query_vectors = query_vectors / np.linalg.norm(query_vectors, axis=1, keepdims=True)

# 创建索引 (使用 L2 距离)
index = faiss.IndexFlatL2(dimension)

# 添加向量到索引
index.add(db_vectors)
print(f"索引中向量数量: {index.ntotal}")

# 搜索 Top-5
k = 5
distances, indices = index.search(query_vectors, k)

print("查询结果:")
for i in range(query_size):
print(f"查询 {i}: 最近邻索引 {indices[i]}, 距离 {distances[i]}")

核心概念

索引类型

Faiss 提供多种索引类型,适应不同规模和性能需求:

1. 精确搜索索引

# IndexFlatL2 - 欧几里得距离精确搜索
index_l2 = faiss.IndexFlatL2(dimension)

# IndexFlatIP - 内积精确搜索 (余弦相似度需归一化)
index_ip = faiss.IndexFlatIP(dimension)

2. 近似搜索索引 (IVF)

# IVF 索引 (倒排文件)
nlist = 100 # 聚类中心数
quantizer = faiss.IndexFlatL2(dimension)
index_ivf = faiss.IndexIVFFlat(quantizer, dimension, nlist)

# 需要训练
index_ivf.train(db_vectors)
index_ivf.add(db_vectors)

# 搜索时可调整 nprobe (搜索的聚类数)
index_ivf.nprobe = 10 # 默认 1,越大越精确

3. 乘积量化索引 (PQ)

# PQ 索引 (压缩存储)
m = 16 # 子向量数
nbits = 8 # 每个子向量编码位数
index_pq = faiss.IndexPQ(dimension, m, nbits)

index_pq.train(db_vectors)
index_pq.add(db_vectors)

# 原始 128 维 float32 = 512 字节
# PQ 压缩后 = 16 字节 (压缩比 32:1)

4. HNSW 索引

# HNSW 索引 (图索引,搜索速度快)
M = 16 # 每个节点的连接数
index_hnsw = faiss.IndexHNSWFlat(dimension, M)

# 设置构建和搜索参数
index_hnsw.hnsw.efConstruction = 200
index_hnsw.hnsw.efSearch = 64

index_hnsw.add(db_vectors)

5. 组合索引

# IVF + PQ (大规模数据的最佳选择)
quantizer = faiss.IndexFlatL2(dimension)
index_ivfpq = faiss.IndexIVFPQ(quantizer, dimension, nlist, m, nbits)

index_ivfpq.train(db_vectors)
index_ivfpq.add(db_vectors)

# 调整搜索参数
index_ivfpq.nprobe = 10

索引选择指南

数据规模推荐索引内存占用搜索速度精度
< 10万IndexFlatL2100%
10万-100万IndexIVFFlat>95%
100万-1000万IndexIVFPQ较快>90%
> 1000万IndexIVFPQ + GPU很快>85%
内存受限IndexPQ很低中等>80%

数据操作

添加和删除向量

import faiss
import numpy as np

# 创建可增量添加的索引
index = faiss.IndexFlatL2(dimension)

# 批量添加
batch_size = 1000
for i in range(0, len(vectors), batch_size):
batch = vectors[i:i+batch_size]
index.add(batch)

# 带 ID 的索引 (支持删除)
index_with_ids = faiss.IndexFlatL2(dimension)
index_with_ids = faiss.IndexIDMap(index_with_ids)

# 添加时指定 ID
ids = np.arange(len(vectors), dtype='int64')
index_with_ids.add_with_ids(vectors, ids)

# 删除向量 (通过 ID)
ids_to_remove = np.array([1, 3, 5], dtype='int64')
index_with_ids.remove_ids(faiss.IDSelectorBatch(ids_to_remove))

搜索操作

# 基本搜索
k = 10
distances, indices = index.search(query_vectors, k)

# 范围搜索 (返回距离小于阈值的向量)
radius = 0.5
distances, indices = index.range_search(query_vectors, radius)

# 批量搜索优化
# Faiss 自动并行处理批量查询
batch_queries = np.random.random((100, dimension)).astype('float32')
distances, indices = index.search(batch_queries, k)

保存和加载索引

# 保存索引
faiss.write_index(index, "my_index.faiss")

# 加载索引
index = faiss.read_index("my_index.faiss")

# 保存到内存 (用于网络传输)
import io
buffer = io.BytesIO()
faiss.write_index(index, buffer)
index_data = buffer.getvalue()

# 从内存加载
buffer = io.BytesIO(index_data)
index = faiss.read_index(buffer)

GPU 加速

使用 GPU 索引

# 检查 GPU
print(f"可用 GPU 数量: {faiss.get_num_gpus()}")

# 创建 GPU 资源
res = faiss.StandardGpuResources()

# 将 CPU 索引转换为 GPU 索引
gpu_index = faiss.index_cpu_to_gpu(res, 0, index) # 0 是 GPU ID

# 使用 GPU 索引搜索
distances, indices = gpu_index.search(query_vectors, k)

# 多个 GPU
ngpus = faiss.get_num_gpus()
res_list = [faiss.StandardGpuResources() for _ in range(ngpus)]
gpu_index = faiss.index_cpu_to_gpu_multiple_py(res_list, index)

GPU 索引最佳实践

# 大规模数据 GPU 索引配置
co = faiss.GpuClonerOptions()
co.useFloat16 = True # 使用 float16 节省显存
co.indicesOptions = faiss.INDICES_64_BIT

res = faiss.StandardGpuResources()
gpu_index = faiss.index_cpu_to_gpu(res, 0, index, co)

与嵌入模型集成

OpenAI Embedding

from openai import OpenAI
import faiss
import numpy as np

client = OpenAI()

def get_embeddings(texts):
response = client.embeddings.create(
input=texts,
model="text-embedding-3-small"
)
return [item.embedding for item in response.data]

# 准备文档
documents = [
"Faiss 是 Facebook 开源的向量搜索库",
"Python 是一种流行的编程语言",
"机器学习是人工智能的子集"
]

# 生成嵌入
embeddings = get_embeddings(documents)
embeddings = np.array(embeddings).astype('float32')

# 创建索引
dimension = len(embeddings[0])
index = faiss.IndexFlatIP(dimension) # 内积用于余弦相似度
index.add(embeddings)

# 搜索
query = "什么是 Faiss?"
query_embedding = get_embeddings([query])
query_embedding = np.array(query_embedding).astype('float32')

distances, indices = index.search(query_embedding, 3)

for i, idx in enumerate(indices[0]):
print(f"{i+1}. [{distances[0][i]:.4f}] {documents[idx]}")

Sentence Transformers

from sentence_transformers import SentenceTransformer
import faiss

# 加载模型
model = SentenceTransformer('all-MiniLM-L6-v2')

# 编码文档
documents = ["文档1内容", "文档2内容", "文档3内容"]
embeddings = model.encode(documents, convert_to_numpy=True)
embeddings = embeddings.astype('float32')

# 归一化 (用于余弦相似度)
faiss.normalize_L2(embeddings)

# 创建索引
dimension = embeddings.shape[1]
index = faiss.IndexFlatIP(dimension)
index.add(embeddings)

RAG 应用完整示例

import faiss
import numpy as np
from openai import OpenAI
import os

class FaissRAG:
def __init__(self, dimension=1536):
self.dimension = dimension
self.index = faiss.IndexFlatIP(dimension) # 内积索引
self.documents = []
self.openai = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))

def add_documents(self, documents):
"""添加文档到知识库"""
# 生成嵌入
embeddings = self._get_embeddings(documents)

# 归一化
faiss.normalize_L2(embeddings)

# 添加到索引
self.index.add(embeddings)
self.documents.extend(documents)

print(f"已添加 {len(documents)} 篇文档,总计 {len(self.documents)} 篇")

def _get_embeddings(self, texts):
"""获取文本嵌入"""
response = self.openai.embeddings.create(
input=texts,
model="text-embedding-3-small"
)
embeddings = [item.embedding for item in response.data]
return np.array(embeddings).astype('float32')

def search(self, query, top_k=5):
"""搜索相关文档"""
# 生成查询嵌入
query_embedding = self._get_embeddings([query])
faiss.normalize_L2(query_embedding)

# 搜索
distances, indices = self.index.search(query_embedding, top_k)

results = []
for i, idx in enumerate(indices[0]):
if idx < len(self.documents):
results.append({
"content": self.documents[idx],
"score": float(distances[0][i]),
"index": int(idx)
})

return results

def answer(self, question, top_k=3):
"""生成回答"""
# 检索相关文档
docs = self.search(question, top_k)

# 构建上下文
context = "\n\n".join([
f"[文档 {i+1}] {doc['content']}"
for i, doc in enumerate(docs)
])

# 调用 LLM
prompt = f"""基于以下文档回答问题。如果文档中没有相关信息,请说明。

文档:
{context}

问题:{question}

回答:"""

response = self.openai.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "你是一个基于文档回答问题的助手。"},
{"role": "user", "content": prompt}
]
)

return {
"answer": response.choices[0].message.content,
"sources": docs
}

def save(self, path):
"""保存索引和文档"""
faiss.write_index(self.index, f"{path}.faiss")
np.save(f"{path}_docs.npy", self.documents)

def load(self, path):
"""加载索引和文档"""
self.index = faiss.read_index(f"{path}.faiss")
self.documents = np.load(f"{path}_docs.npy", allow_pickle=True).tolist()

# 使用示例
rag = FaissRAG()

docs = [
"Faiss 是 Facebook AI Research 开发的高效相似度搜索库",
"向量数据库用于存储和检索高维向量数据",
"RAG 通过检索增强生成提升 LLM 回答质量"
]
rag.add_documents(docs)

result = rag.answer("什么是 Faiss?")
print(f"回答:{result['answer']}")

性能优化

批量处理

# 批量添加比逐条添加快得多
batch_size = 10000
for i in range(0, len(vectors), batch_size):
batch = vectors[i:i+batch_size]
index.add(batch)

# 批量查询自动并行
# Faiss 内部使用 OpenMP 并行处理批量查询

内存映射

# 大索引使用内存映射
index = faiss.read_index("large_index.faiss", faiss.IO_FLAG_MMAP)

# 只加载部分到内存
index = faiss.read_index("large_index.faiss", faiss.IO_FLAG_READ_ONLY)

量化优化

# OPQ 预处理 + PQ 量化 (精度更高)
opq = faiss.OPQMatrix(dimension, m)
opq.train(db_vectors)

db_vectors_transformed = opq.apply_py(db_vectors)

quantizer = faiss.IndexFlatL2(dimension)
index = faiss.IndexIVFPQ(quantizer, dimension, nlist, m, nbits)
index.train(db_vectors_transformed)
index.add(db_vectors_transformed)

常见问题

Q: Faiss 与专用向量数据库的区别?

特性Faiss专用向量数据库
类型算法库完整数据库系统
持久化需自行实现内置
分布式需自行实现通常支持
功能核心搜索完整的数据管理
适用嵌入式/研究生产环境

Q: 如何处理十亿级向量?

# 1. 使用 IVF + PQ 索引
nlist = 65536 # 更多聚类中心
m = 64 # 更多子向量
index = faiss.IndexIVFPQ(quantizer, dimension, nlist, m, 8)

# 2. 使用 GPU
res = faiss.StandardGpuResources()
gpu_index = faiss.index_cpu_to_gpu(res, 0, index)

# 3. 分片存储
# 将数据分成多个索引,分别搜索后合并结果

Q: 如何评估搜索质量?

# 计算召回率
def compute_recall(index, query_vectors, ground_truth, k=10):
"""计算召回率"""
distances, indices = index.search(query_vectors, k)

recall = 0
for i in range(len(query_vectors)):
# 计算交集
correct = len(set(indices[i]) & set(ground_truth[i]))
recall += correct / len(ground_truth[i])

return recall / len(query_vectors)

# 生成 ground truth (使用精确搜索)
index_flat = faiss.IndexFlatL2(dimension)
index_flat.add(db_vectors)
_, ground_truth = index_flat.search(query_vectors, k)

# 评估近似索引
recall = compute_recall(index_ivf, query_vectors, ground_truth)
print(f"召回率: {recall:.4f}")

资源链接