向量数据库:faiss的常用三种数据索引方式(IndexFlatL2,IndexIVFFlat,IndexIVFPQ)的使用和持久化+索引融合的实现及库函数解读

对Faiss中IndexFlatL2、IndexIVFFlat、IndexIVFPQ三种索引的总结和选择 由于项目和研究的需要,想要存储并检索大量的embedding,在之前的博客里,我尝试了一种方案:https://blog.csdn.net/qysh123/article/details/113754991 但是感觉太傻瓜了,听从建议,试了一下Faiss。不得不说,虽然Faiss感觉挺强大的,但是文档和说明依然是很垃圾。像这里给出了我们到底应该怎么选index:https://github.com/facebookresearch/faiss/wiki/Guidelines-to-choose-an-in 阅读详情
AI摘要

博客介绍了Faiss中常用的三种索引方式,包括暴力检索L2、倒排索引加速和省空间超快的PQ技术,还提及索引的保存、加载、清空数据和合并等功能,同时指出其不支持动态更新、分布式存储和查询,单机可能内存不足等缺点。

常用的三种索引方式

        Faiss 中有常用的三种索引方式:IndexFlatL2、IndexIVFFlat 和 IndexIVFPQ。

1.IndexFlatL2 - 暴力检索L2:

  • 使用欧氏距离(L2)进行精确检索。
  • 适用于较小规模的数据集,采用暴力检索的方式,即计算查询向量与所有数据库向量之间的距离,然后返回相似度最高的前 k 个向量。
import faiss

d = 200  # 向量维度
index = faiss.IndexFlatL2(d)  # 构建索引
data = ...  # 添加数据
index.add(data)  # 添加数据到索引
k = 500  # 返回结果个数
query = ...  # 查询向量
dis, ind = index.search(query, k)  # 查询相似内容

2. IndexIVFFlat - 倒排索引,加速:

  • 使用倒排索引结构,将数据集划分为多个聚类空间,以加速搜索。
  • 在查询阶段,首先定位到可能包含相似向量的聚类中心,然后在该聚类中心附近进行精确搜索。
import faiss

d = 200  # 向量维度
nlist = 10000  # 聚类空间
k = 500  # 返回结果个数
quantizer = faiss.IndexFlatL2(d)  # 量化器
index = faiss.IndexIVFFlat(quantizer, d, nlist)  # 构建索引
index.nprobe = 20  # 查找聚类中心的个数
index.train(data)  # 训练
index.add(data)  # 添加数据到索引
dis, ind = index.search(query, k)  # 查询相似内容

3. IndexIVFPQ - 省空间超快:

  • 使用 Product Quantization(PQ)技术进行有损压缩,以节省内存。
  • 在查询阶段,返回近似结果。
import faiss

d = 200  # 向量维度
m = 8  # 空间拆分
nlist = 10000  # 聚类空间
k = 500  # 返回结果个数
quantizer = faiss.IndexFlatL2(d)
index = faiss.IndexIVFPQ(quantizer, d, nlist, m, 8)  # 每个向量用8 bits 编码
index.nprobe = 20  # 查找聚类中心的个数
index.train(data)  # 训练
index.add(data)  # 添加数据到索引
dis, ind = index.search(query, k)  # 查询相似内容
// https://github1s.com/facebookresearch/faiss/blob/HEAD/tutorial/python/3-IVFPQ.py#L6-L32
import numpy as np

d = 64                           # dimension
nb = 100000                      # database size
nq = 10000                       # nb of queries
np.random.seed(1234)             # make reproducible
xb = np.random.random((nb, d)).astype('float32')
xb[:, 0] += np.arange(nb) / 1000.
xq = np.random.random((nq, d)).astype('float32')
xq[:, 0] += np.arange(nq) / 1000.

import faiss

nlist = 100
m = 8
k = 4
quantizer = faiss.IndexFlatL2(d)  # this remains the same
index = faiss.IndexIVFPQ(quantizer, d, nlist, m, 8)
                                  # 8 specifies that each sub-vector is encoded as 8 bits
index.train(xb)
index.add(xb)
D, I = index.search(xb[:5], k) # sanity check
print(I)
print(D)
index.nprobe = 10              # make comparable with experiment above
D, I = index.search(xq, k)     # search
print(I[-5:])

这些索引方法在不同场景下有不同的优势,你可以根据数据集大小、内存限制和搜索速度的需求来选择适当的索引类型。

  • Product quantization in Faiss and from scratch
  • 乘积量化背后的主要思想是,将采用高维嵌入(其中每个元素都是浮点数)转换为更小的向量,其元素是无符号整数,具体位数通常是八位或一个字节。        
  • 为了实现这一点,我们首先将向量分成 m 段,然后将每个段映射到某个固定整数。对于 m 个分段中的每个分段都有 m 个单独的估计器,如果我们假设这些估计器已经经过训练,我们可以简单地使用它们将给定分段分配给集群 id,并且该集群 id 是我们将用来表示的数字。

在这里插入图片描述

保存和加载索引

        faiss 提供了保存和加载索引的功能,可以将索引保存为文件以便后续重用。这对于避免重新构建索引,以及在不同的程序之间共享索引非常有用。

import faiss
import numpy as np

# 创建一些随机数据
np.random.seed(42)
data = np.random.rand(10, 5).astype('float32')

# 创建一个平面索引
index = faiss.IndexFlatL2(5)
index.add(data)

# 保存索引到文件
faiss.write_index(index, "my_index.index")
import faiss
import numpy as np
# 加载索引
loaded_index = faiss.read_index("my_index.index")

# 进行相似性搜索
query_vector = np.random.rand(1, 5).astype('float32')
k = 3
D, I = loaded_index.search(query_vector, k)

print("相似度最高的{}个向量的索引: {}".format(k, I))
print("对应的相似度分数: {}".format(D))

清空一个index中的所有数据

import faiss

# 假设你已经创建了一个索引 index
# 这里以 IndexFlatL2 为例,具体类型根据你的实际情况选择
index = faiss.IndexFlatL2(d=128)

# 添加一些数据到索引中
data_to_add = [...]  # 你的数据
index.add(data_to_add)

# 打印添加数据前的索引大小
print("索引大小 (添加数据前):", index.ntotal)

# 清空索引中的所有数据
index.reset()

# 打印清空数据后的索引大小
print("索引大小 (清空数据后):", index.ntotal)

merge

merge_from实现(python)

# 这个例子好像有问题
import faiss

# 创建两个示例 IndexIVFPQ 索引
dimension = 64
nlist = 100
nprobe = 32
code_size = 8

# 进行相似性搜索的设置
query_vector = faiss.rand((1, dimension)).astype('float32')
k = 3

quantizer = faiss.IndexFlatL2(dimension)
index1 = faiss.IndexIVFPQ(quantizer, dimension, nlist, nprobe, 8)  # 每个向量用8 bits 编码
index2 = faiss.IndexIVFPQ(quantizer, dimension, nlist, nprobe, 8)  # 每个向量用8 bits 编码
# 向索引添加一些示例数据
data1 = faiss.rand((10000, dimension)).astype('float32')
data2 = faiss.rand((50000, dimension)).astype('float32')

index1.train(data1)
index1.add(data1)
D, I = index1.search(query_vector, k)
print("index1:相似度最高的{}个向量的索引: {}".format(k, I))
print("index1:对应的相似度分数: {}".format(D))
# print("Retrieved Vectors:", data1[I[0]])

index2.train(data2)
index2.add(data2)
D, I = index2.search(query_vector, k)
print("index2:相似度最高的{}个向量的索引: {}".format(k, I))
print("index2:对应的相似度分数: {}".format(D))
# print("Retrieved Vectors:", data2[I[0]])

# 打印每个索引的总数
print("Index 1 total:", index1.ntotal)
print("Index 2 total:", index2.ntotal)

# 创建一个新的索引,然后将两个索引合并到新的索引中
merged_index = faiss.IndexIVFPQ(quantizer, dimension, nlist, nprobe, 8)
merged_index.merge_from(index1,add_id=True)
merged_index.merge_from(index2,add_id=True)

# 打印合并后的总数
print("Merged Index total:", index1.ntotal)
print("Merged Index total:", index1.ntotal)
print("Merged Index total:", merged_index.ntotal)

D, I = merged_index.search(query_vector, k)
print("merged_index合并后:相似度最高的{}个向量的索引: {}".format(k, I))
print("merged_index合并后:对应的相似度分数: {}".format(D))
# index1:相似度最高的3个向量的索引: [[   0 4722 8480]]
# index1:对应的相似度分数: [[0.02087733 6.411026   7.01804   ]]
# index2:相似度最高的3个向量的索引: [[    0   512 33625]]
# index2:对应的相似度分数: [[0.02118337 5.254285   5.6290326 ]]
# Index 1 total: 10000
# Index 2 total: 50000
# Merged Index total: 0
# Merged Index total: 0
# Merged Index total: 60000
# merged_index合并后:相似度最高的3个向量的索引: [[63 70  1]]
# merged_index合并后:对应的相似度分数: [[4.093991 4.093991 4.093991]]
  • merge_from遍历 PDF 列表。首次创建 Faiss 索引,然后合并其余索引。
// https://stackoverflow.com/questions/76421045/how-to-combine-multiple-faiss-indexes-into-one-to-get-a-single-retriever
pdfs = [help_doc_name, newsletters_doc_name, supportCases_doc_name]

for index, pdf in enumerate(pdfs):
   content = load_pdf(pdf)
   if index == 0:
       faiss_index = FAISS.from_documents(content, OpenAIEmbeddings())
   else:
      faiss_index_i = FAISS.from_documents(content, OpenAIEmbeddings())
      faiss_index.merge_from(faiss_index_i)

faiss_index.save_local(index_path)

retriever = faiss_index.as_retriever(
        search_type="similarity", search_kwargs={"k": 3}
    )
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=retriever,
    verbose=False
)

另一种实现方式(python)

// https://gist.github.com/mdouze/586746666ef493dbc363aef9266bb990
import numpy as np
import faiss

def merge_idmap_flat(a, b): 
    a_flat = faiss.downcast_index(a.index)
    b_flat = faiss.downcast_index(b.index)    
    ab_flat = faiss.IndexFlatL2(a.d)
    faiss.copy_array_to_vector(
        np.hstack((
            faiss.vector_to_array(a_flat.xb), 
            faiss.vector_to_array(b_flat.xb)
        )), 
        ab_flat.xb
    )
    ab = faiss.IndexIDMap(ab_flat)
    ab.referenced = ab_flat # avoid deallocation, not needed in 1.4.0
    faiss.copy_array_to_vector(
        np.hstack((
            faiss.vector_to_array(a.id_map), 
            faiss.vector_to_array(b.id_map)
        )), 
        ab.id_map 
    )
    ab_flat.ntotal = ab.ntotal = a.ntotal + b.ntotal
    return ab

code实现(官方库的代码实现逻辑)

  • 使用python定位到最深的实现为IndexIVF类的merge_from方法。这个函数很可能是 faiss 库中的底层实现,通过 swig 工具将 C/C++ 代码包装成 Python 接口。
    在这里插入图片描述

  • 再TEST宏处进入测试

#include <gtest/gtest.h>  // https://github1s.com/facebookresearch/faiss/blob/HEAD/tests/test_merge.cpp#L14
// now use ondisk specific merge https://github1s.com/facebookresearch/faiss/blob/HEAD/tests/test_merge.cpp#L234-L247
TEST(MERGE, merge_flat_ondisk_2) {
    faiss::IndexShards index_shards(d, false, false);
    index_shards.own_indices = true;

    for (int i = 0; i < nindex; i++) {
        index_shards.add_shard(
                new faiss::IndexIVFFlat(&cd.quantizer, d, nlist));
    }
    EXPECT_TRUE(index_shards.is_trained);
    index_shards.add_with_ids(nb, cd.database.data(), cd.ids.data());
    int ndiff = compare_merged(&index_shards, false, false);
    EXPECT_GE(0, ndiff);
}
  • 调用compare_merged
// https://github1s.com/facebookresearch/faiss/blob/HEAD/tests/test_merge.cpp#L88-L144
/// perform a search on shards, then merge and search again and
/// compare results.

// 定义一个函数,用于在索引分片上进行搜索,合并索引,再次搜索,并比较结果。
int compare_merged(
        faiss::IndexShards* index_shards,
        bool shift_ids,
        bool standard_merge = true) {
    // 定义用于存储搜索结果的数组
    std::vector<idx_t> refI(k * nq);
    std::vector<float> refD(k * nq);

    // 在索引分片上执行搜索操作,将结果存储在 refD 和 refI 数组中
    index_shards->search(nq, cd.queries.data(), k, refD.data(), refI.data());
    
    Tempfilename filename;// 创建一个临时文件名对象

    // 定义新的搜索结果数组
    std::vector<idx_t> newI(k * nq);
    std::vector<float> newD(k * nq);

    // 根据 standard_merge 的值,选择标准合并方式还是非标准合并方式
    if (standard_merge) {
        // 标准合并方式
        for (int i = 1; i < nindex; i++) {
            faiss::ivflib::merge_into(// 将所有索引分片合并到第一个分片中
                    index_shards->at(0), index_shards->at(i), shift_ids);
        }

        // 同步索引以确保合并的变化得以反映
        index_shards->syncWithSubIndexes();
    } else {
        // 非标准合并方式
        std::vector<const faiss::InvertedLists*> lists;
        faiss::IndexIVF* index0 = nullptr;
        size_t ntotal = 0;

        // 收集所有分片的倒排列表
        for (int i = 0; i < nindex; i++) {
            auto index_ivf =
                    dynamic_cast<faiss::IndexIVF*>(index_shards->at(i));
            assert(index_ivf);
            if (i == 0) {
                index0 = index_ivf;
            }
            lists.push_back(index_ivf->invlists);
            ntotal += index_ivf->ntotal;
        }

        // 创建一个新的 OnDiskInvertedLists,并将所有倒排列表合并到其中
        auto il = new faiss::OnDiskInvertedLists(
                index0->nlist, index0->code_size, filename.c_str());

        il->merge_from(lists.data(), lists.size());

        // 替换第一个分片的倒排列表
        index0->replace_invlists(il, true);
        index0->ntotal = ntotal;
    }

    // 仅在第一个索引分片上执行搜索操作
    index_shards->at(0)->search(
            nq, cd.queries.data(), k, newD.data(), newI.data());

    // 比较搜索结果,计算不同的数量
    size_t ndiff = 0;
    for (size_t i = 0; i < k * nq; i++) {
        if (refI[i] != newI[i]) {
            ndiff++;
        }
    }
    
    // 返回不同的数量
    return ndiff;
}
void merge_into(faiss::Index* index0, faiss::Index* index1, bool shift_ids) {
    // 检查两个索引是否兼容,如果不兼容将引发异常
    check_compatible_for_merge(index0, index1);

    // 将传入的索引强制转换为 IndexIVF 类型
    IndexIVF* ivf0 = extract_index_ivf(index0);
    IndexIVF* ivf1 = extract_index_ivf(index1);

    // 调用 IndexIVF 类的 merge_from 方法,将 index1 合并到 index0
    ivf0->merge_from(*ivf1, shift_ids ? ivf0->ntotal : 0);

    // 对于 IndexPreTransform 等情况,更新索引总数
    index0->ntotal = ivf0->ntotal;
    index1->ntotal = ivf1->ntotal;
}

  • IndexIVF 的类型定义如下,它继承自两个类:Index 和 IndexIVFInterface。第396行为函数声明:virtual void merge_from(Index& otherIndex, idx_t add_id) override;,它的实际实现是通过
/** Index based on a inverted file (IVF)
 *
 * In the inverted file, the quantizer (an Index instance) provides a
 * quantization index for each vector to be added. The quantization
 * index maps to a list (aka inverted list or posting list), where the
 * id of the vector is stored.
 *
 * The inverted list object is required only after trainng. If none is
 * set externally, an ArrayInvertedLists is used automatically.
 *
 * At search time, the vector to be searched is also quantized, and
 * only the list corresponding to the quantization index is
 * searched. This speeds up the search by making it
 * non-exhaustive. This can be relaxed using multi-probe search: a few
 * (nprobe) quantization indices are selected and several inverted
 * lists are visited.
 *
 * Sub-classes implement a post-filtering of the index that refines
 * the distance estimation from the query to databse vectors.
 */
struct IndexIVF : Index, IndexIVFInterface {
    /// Access to the actual data
    InvertedLists* invlists = nullptr; // #include <faiss/invlists/InvertedLists.h>
    bool own_invlists = false;

    size_t code_size = 0; ///< code size per vector in bytes

    /** Parallel mode determines how queries are parallelized with OpenMP
     *
     * 0 (default): split over queries
     * 1: parallelize over inverted lists
     * 2: parallelize over both
     * 3: split over queries with a finer granularity
     *
     * PARALLEL_MODE_NO_HEAP_INIT: binary or with the previous to
     * prevent the heap to be initialized and finalized
     */
    int parallel_mode = 0;
    const int PARALLEL_MODE_NO_HEAP_INIT = 1024;

    /** optional map that maps back ids to invlist entries. This
     *  enables reconstruct() */
    DirectMap direct_map;

    /// do the codes in the invlists encode the vectors relative to the
    /// centroids?
    bool by_residual = true;

    /** The Inverted file takes a quantizer (an Index) on input,
     * which implements the function mapping a vector to a list
     * identifier.
     */
    IndexIVF(
            Index* quantizer,
            size_t d,
            size_t nlist,
            size_t code_size,
            MetricType metric = METRIC_L2);

    void reset() override;

    /// Trains the quantizer and calls train_encoder to train sub-quantizers
    void train(idx_t n, const float* x) override;

    /// Calls add_with_ids with NULL ids
    void add(idx_t n, const float* x) override;

    /// default implementation that calls encode_vectors
    void add_with_ids(idx_t n, const float* x, const idx_t* xids) override;

    /** Implementation of vector addition where the vector assignments are
     * predefined. The default implementation hands over the code extraction to
     * encode_vectors.
     *
     * @param precomputed_idx    quantization indices for the input vectors
     * (size n)
     */
    virtual void add_core(
            idx_t n,
            const float* x,
            const idx_t* xids,
            const idx_t* precomputed_idx);

    /** Encodes a set of vectors as they would appear in the inverted lists
     *
     * @param list_nos   inverted list ids as returned by the
     *                   quantizer (size n). -1s are ignored.
     * @param codes      output codes, size n * code_size
     * @param include_listno
     *                   include the list ids in the code (in this case add
     *                   ceil(log8(nlist)) to the code size)
     */
    virtual void encode_vectors(
            idx_t n,
            const float* x,
            const idx_t* list_nos,
            uint8_t* codes,
            bool include_listno = false) const = 0;

    /** Add vectors that are computed with the standalone codec
     *
     * @param codes  codes to add size n * sa_code_size()
     * @param xids   corresponding ids, size n
     */
    void add_sa_codes(idx_t n, const uint8_t* codes, const idx_t* xids);

    /** Train the encoder for the vectors.
     *
     * If by_residual then it is called with residuals and corresponding assign
     * array, otherwise x is the raw training vectors and assign=nullptr */
    virtual void train_encoder(idx_t n, const float* x, const idx_t* assign);

    /// can be redefined by subclasses to indicate how many training vectors
    /// they need
    virtual idx_t train_encoder_num_vectors() const;

    void search_preassigned(
            idx_t n,
            const float* x,
            idx_t k,
            const idx_t* assign,
            const float* centroid_dis,
            float* distances,
            idx_t* labels,
            bool store_pairs,
            const IVFSearchParameters* params = nullptr,
            IndexIVFStats* stats = nullptr) const override;

    void range_search_preassigned(
            idx_t nx,
            const float* x,
            float radius,
            const idx_t* keys,
            const float* coarse_dis,
            RangeSearchResult* result,
            bool store_pairs = false,
            const IVFSearchParameters* params = nullptr,
            IndexIVFStats* stats = nullptr) const override;

    /** assign the vectors, then call search_preassign */
    void search(
            idx_t n,
            const float* x,
            idx_t k,
            float* distances,
            idx_t* labels,
            const SearchParameters* params = nullptr) const override;

    void range_search(
            idx_t n,
            const float* x,
            float radius,
            RangeSearchResult* result,
            const SearchParameters* params = nullptr) const override;

    /** Get a scanner for this index (store_pairs means ignore labels)
     *
     * The default search implementation uses this to compute the distances
     */
    virtual InvertedListScanner* get_InvertedListScanner(
            bool store_pairs = false,
            const IDSelector* sel = nullptr) const;

    /** reconstruct a vector. Works only if maintain_direct_map is set to 1 or 2
     */
    void reconstruct(idx_t key, float* recons) const override;

    /** Update a subset of vectors.
     *
     * The index must have a direct_map
     *
     * @param nv     nb of vectors to update
     * @param idx    vector indices to update, size nv
     * @param v      vectors of new values, size nv*d
     */
    virtual void update_vectors(int nv, const idx_t* idx, const float* v);

    /** Reconstruct a subset of the indexed vectors.
     *
     * Overrides default implementation to bypass reconstruct() which requires
     * direct_map to be maintained.
     *
     * @param i0     first vector to reconstruct
     * @param ni     nb of vectors to reconstruct
     * @param recons output array of reconstructed vectors, size ni * d
     */
    void reconstruct_n(idx_t i0, idx_t ni, float* recons) const override;

    /** Similar to search, but also reconstructs the stored vectors (or an
     * approximation in the case of lossy coding) for the search results.
     *
     * Overrides default implementation to avoid having to maintain direct_map
     * and instead fetch the code offsets through the `store_pairs` flag in
     * search_preassigned().
     *
     * @param recons      reconstructed vectors size (n, k, d)
     */
    void search_and_reconstruct(
            idx_t n,
            const float* x,
            idx_t k,
            float* distances,
            idx_t* labels,
            float* recons,
            const SearchParameters* params = nullptr) const override;

    /** Similar to search, but also returns the codes corresponding to the
     * stored vectors for the search results.
     *
     * @param codes      codes (n, k, code_size)
     * @param include_listno
     *                   include the list ids in the code (in this case add
     *                   ceil(log8(nlist)) to the code size)
     */
    void search_and_return_codes(
            idx_t n,
            const float* x,
            idx_t k,
            float* distances,
            idx_t* labels,
            uint8_t* recons,
            bool include_listno = false,
            const SearchParameters* params = nullptr) const;

    /** Reconstruct a vector given the location in terms of (inv list index +
     * inv list offset) instead of the id.
     *
     * Useful for reconstructing when the direct_map is not maintained and
     * the inv list offset is computed by search_preassigned() with
     * `store_pairs` set.
     */
    virtual void reconstruct_from_offset(
            int64_t list_no,
            int64_t offset,
            float* recons) const;

    /// Dataset manipulation functions

    size_t remove_ids(const IDSelector& sel) override;

    void check_compatible_for_merge(const Index& otherIndex) const override;

    virtual void merge_from(Index& otherIndex, idx_t add_id) override;

    // returns a new instance of a CodePacker
    virtual CodePacker* get_CodePacker() const;

    /** copy a subset of the entries index to the other index
     * see Invlists::copy_subset_to for the meaning of subset_type
     */
    virtual void copy_subset_to(
            IndexIVF& other,
            InvertedLists::subset_type_t subset_type,
            idx_t a1,
            idx_t a2) const;

    ~IndexIVF() override;

    size_t get_list_size(size_t list_no) const {
        return invlists->list_size(list_no);
    }

    /// are the ids sorted?
    bool check_ids_sorted() const;

    /** initialize a direct map
     *
     * @param new_maintain_direct_map    if true, create a direct map,
     *                                   else clear it
     */
    void make_direct_map(bool new_maintain_direct_map = true);

    void set_direct_map_type(DirectMap::Type type);

    /// replace the inverted lists, old one is deallocated if own_invlists
    void replace_invlists(InvertedLists* il, bool own = false);

    /* The standalone codec interface (except sa_decode that is specific) */
    size_t sa_code_size() const override;
    void sa_encode(idx_t n, const float* x, uint8_t* bytes) const override;

    IndexIVF();
};
merge_from的最终实现
// https://github1s.com/facebookresearch/faiss/blob/HEAD/faiss/invlists/OnDiskInvertedLists.cpp#L569-L636
// 从一组倒排列表中合并数据到当前 OnDiskInvertedLists 对象
size_t OnDiskInvertedLists::merge_from(
        const InvertedLists** ils,
        int n_il,
        bool verbose) {
    // 确保当前 InvertedLists 对象为空
    FAISS_THROW_IF_NOT_MSG(
            totsize == 0, "works only on an empty InvertedLists");

    // 记录每个倒排列表的大小
    std::vector<size_t> sizes(nlist);

    // 遍历所有输入的倒排列表
    for (int i = 0; i < n_il; i++) {
        const InvertedLists* il = ils[i];
        // 确保倒排列表的维度和码的大小与当前对象一致
        FAISS_THROW_IF_NOT(il->nlist == nlist && il->code_size == code_size);

        // 统计每个列表的大小
        for (size_t j = 0; j < nlist; j++) {
            sizes[j] += il->list_size(j);
        }
    }

    size_t cums = 0;
    size_t ntotal = 0;

    // 根据统计的列表大小更新当前对象的结构
    for (size_t j = 0; j < nlist; j++) {
        ntotal += sizes[j];
        lists[j].size = 0;
        lists[j].capacity = sizes[j];
        lists[j].offset = cums;
        cums += lists[j].capacity * (sizeof(idx_t) + code_size);
    }

    // 更新当前对象的总大小
    update_totsize(cums);

    size_t nmerged = 0;
    double t0 = getmillisecs(), last_t = t0;

    // 并行处理每个列表
#pragma omp parallel for // OpenMP在循环中实现并行化的预编译关键字(Preprocessor Directives),表示在其后的 for 循环应该在多个线程中并行执行
    for (size_t j = 0; j < nlist; j++) {
        List& l = lists[j];
        // 遍历所有输入的倒排列表
        for (int i = 0; i < n_il; i++) {
            const InvertedLists* il = ils[i];
            // 获取当前列表在输入倒排列表中的条目数
            size_t n_entry = il->list_size(j);
            // 更新当前列表的大小和内容
            l.size += n_entry;
            update_entries(
                    j,
                    l.size - n_entry,
                    n_entry,
                    ScopedIds(il, j).get(),//根据https://github1s.com/facebookresearch/faiss/blob/HEAD/faiss/invlists/InvertedLists.h#L165-L205的定义看,应该是指向倒排表的指针
                    ScopedCodes(il, j).get());
        }
        // 确保当前列表的大小与容量相等
        assert(l.size == l.capacity);

        // 在 verbose 模式下,输出合并的进度信息
        if (verbose) {
#pragma omp critical
            {
                nmerged++;
                double t1 = getmillisecs();
                if (t1 - last_t > 500) {
                    printf("merged %zd lists in %.3f s\r",
                           nmerged,
                           (t1 - t0) / 1000.0);
                    fflush(stdout);
                    last_t = t1;
                }
            }
        }
    }
    // 输出合并完成的信息
    if (verbose) {
        printf("\n");
    }

    // 返回合并后的总条目数
    return ntotal;
}

最终的更新实现
void OnDiskInvertedLists::update_entries(
        size_t list_no, // 表示要更新的倒排列表的编号。
        size_t offset, // 表示要更新的列表中的起始位置。
        size_t n_entry,// 表示要更新的条目数。
        const idx_t* ids_in, // 表示输入的新 ids 数组的指针。
        const uint8_t* codes_in) {// 表示输入的新 codes 数组的指针。
    // 检查是否为只读状态,如果是,抛出异常
    FAISS_THROW_IF_NOT(!read_only);

    // 如果要更新的条目数为 0,则直接返回
    if (n_entry == 0)
        return;

    // 获取当前列表的引用
    const List& l = lists[list_no];

    // 确保更新的范围在合理的范围内
    assert(n_entry + offset <= l.size);

    // 获取当前列表的 ids 数组的指针
    idx_t* ids = const_cast<idx_t*>(get_ids(list_no));

    // 将输入的 ids_in 复制到列表的相应位置
    memcpy(ids + offset, ids_in, sizeof(ids_in[0]) * n_entry);

    // 获取当前列表的 codes 数组的指针
    uint8_t* codes = const_cast<uint8_t*>(get_codes(list_no));

    // 将输入的 codes_in 复制到列表的相应位置
    memcpy(codes + offset * code_size, codes_in, code_size * n_entry);
}

更多功能

https://github1s.com/facebookresearch/faiss-main/tests$ ls
CMakeLists.txt            test_binary_io.py           test_cppcontrib_sa_decode.cpp   test_factory.py                  test_index_binary.py     test_local_search_quantizer.py  test_omp_threads_py.py    test_pq_encoding.cpp        test_search_params.py
common_faiss_tests.py     test_build_blocks.py        test_cppcontrib_uintreader.cpp  test_fast_scan_ivf.py            test_index_composite.py  test_lowlevel_ivf.cpp           test_ondisk_ivf.cpp       test_product_quantizer.py   test_simdlib.cpp
test_approx_topk.cpp      test_clone.py               test_dealloc_invlists.cpp       test_fast_scan.py                test_index.py            test_mem_leak.cpp               test_oom_exception.py     test_RCQ_cropping.cpp       test_sliding_ivf.cpp
test_autotune.py          test_clustering.py          test_distances_simd.cpp         test_heap.cpp                    test_io.py               test_merge.cpp                  test_pairs_decoding.cpp   test_referenced_objects.py  test_standalone_codec.py
test_binary_factory.py    test_code_distance.cpp      test_documentation.py           test_hnsw.cpp                    test_ivflib.py           test_merge_index.py             test_params_override.cpp  test_refine.py              test_threaded_index.cpp
test_binary_flat.cpp      test_contrib.py             test_doxygen_documentation.py   test_index_accuracy.py           test_ivfpq_codec.cpp     test_meta_index.py              test_partitioning.cpp     test_residual_quantizer.py  test_transfer_invlists.cpp
test_binary_hashindex.py  test_contrib_with_scipy.py  test_extra_distances.py         test_index_binary_from_float.py  test_ivfpq_indexing.cpp  test_omp_threads.cpp            test_partition.py         test_rowwise_minmax.py      torch_test_contrib.py

缺点

  • 不支持动态更新:FAISS构建的索引不支持动态添加、删除和更新向量。如果数据发生改变,通常需要重新构建整个索引。
  • 不支持分布式存储和查询(有第三方库实现)
  • 单机可能出现内存不足问题:
Traceback (most recent call last):
  File "*****.py", line 199, in <module>
    main()
  File "*****.py", line 93, in main
    train(config, train_loader, model, losses, optimizer, 0, logger)
  File "*****.py", line 164, in train
    index.add(image_embeds.view(1,-1).detach().cpu().numpy())
  File "/home/ubuntu/anaconda3/envs/***/lib/python3.8/site-packages/faiss/__init__.py", line 194, in replacement_add
    self.add_c(n, swig_ptr(x))
  File "/home/ubuntu/anaconda3/envs/***/lib/python3.8/site-packages/faiss/swigfaiss_avx2.py", line 2166, in add
    return _swigfaiss_avx2.IndexFlat_add(self, n, x)
MemoryError: std::bad_alloc

cg

向量数据库Faiss的搭建与使用 向量数据库Faiss是Facebook AI研究院开发的一种高效的相似性搜索和聚类的库。它能够快速处理大规模数据,并且支持在高维空间中进行相似性搜索。本文将介绍如何搭建Faiss环境并提供一个简单的使用示例。 阅读详情

相关推荐

如何实现图像搜索,文搜图,图搜图,CLIP+faiss向量数据库实现图像高效搜索

如何实现图像搜索,文搜图,图搜图,CLIP+faiss向量数据库实现图像高效搜索 这是AIGC的时代,各种GPT大模型生成文本,还有多模态图文并茂大模型, 以及stable diffusion和stable video diffusion 图像生成视频生成等新模型, 层出不穷,如何生成一个图文并貌的文章,怎么在合适的段落加入图像,图像用什么方式获取, 图像可以使用搜索的形式获取,也可以使用sta...

高颜值的杀生丸(此博客转载自我的博客园) 3636

GPU Droplets:RAG检索瓶颈的物理级解耦方案

向量检索是RAG系统的核心瓶颈,其本质是高维空间中的大规模相似度计算,传统CPU方案受限于串行架构,难以满足毫秒级响应需求。GPU凭借数千CUDA核心与高带宽显存,天然适配向量相似度的并行矩阵运算,实现检索性能数量级提升。技术价值在于重构RAG数据流拓扑——支持Embedding、ANN检索、Cross-Encoder重排序在显存内零拷贝接力执行,突破CPU方案的延迟与并发天花板。典型应用场景包括金融知识问答、医疗文档实时检索、法务条款比对等对低延迟与高召回强敏感的业务。本文聚焦GPU Droplets在R

diaoqu4574的博客 421

2. 数据类型、向量、向量索引、向量修改、向量运算

向量、向量索引、向量修改、向量运算

你说的白是什么白_ 2201

深度学习--FAISS向量数据库

faiss 是相似度检索方案中的佼佼者,是来自 Meta AI(原 Facebook Research)的开源项目,也是目前最流行的、效率比较高的相似度检索方案之一。虽然它和相似度检索这门技术颇受欢迎,在出现在了各种我们所熟知的“大厂”应用的功能中,但毕竟属于小众场景,有着不低的掌握门槛和复杂性。

积木的笔记 2万+

Faiss:快速、高效的向量数据库构建与使用

Faiss(Facebook AI Similarity Search)是由Facebook AI Research团队开发的一款用于快速、高效的向量数据库构建和相似性搜索的开源库。它提供了一系列的算法和数据结构,适用于各种规模和维度的向量数据集。IVF(Inverted File with Vocabulary)索引是一种基于向量量化的索引结构,适用于大规模的向量数据集。Flat索引是最简单的索引结构,适用于小规模的向量数据集。其中,D是查询向量与最近邻的距离数组,I是最近邻的索引数组。

BitNetT的博客 1938

Nvidia Tesla A100 驱动安装方法

Nvidia Tesla A100 驱动安装方法 遇到的问题:以前安装tesla系列、geforce系列等gpu 驱动的时候,只需执行nvidiaxxxx.run驱动包或者安装nvidiaxxxx.rpm包即可,但遇到新的GPU Nvidia Tesla A100的时候,安装完驱动似乎不起作用,找了半天资料,原来需要以下步骤才可以使用;

qq_24343177的博客 2万+

faiss 三种基础索引方式

faiss 三个最基础的 index. 分别是IndexFlatL2,IndexIVFFlat,IndexIVFPQ 一、IndexFlatL2 - 最基础的Index IndexFlatL2索引的结果是精确的,可以用来作为其他索引测试中准确性程度的参考. 搜索时,可以以查询向量为中心,返回距离在一定范围内的结果,如返回数据库中与查询向量距离小于0.3的结果。支持IndexFlat, IndexIVFFlat,只支持在CPU使用。 import numpy as np imp...

xian0710830114的专栏 8786

手把手教你用运放搭建方波三角波发生器(附Multisim仿真文件)

本文详细解析了使用运算放大器搭建方波和三角波发生器的核心原理与设计步骤。通过剖析迟滞比较器与积分器的联动机制,指导读者进行电路设计、元件选型,并借助Multisim仿真进行参数调优与波形优化。文章提供了从理论分析、虚拟仿真到实物焊接调试的完整实践指南,并附有仿真文件,帮助硬件爱好者与初学者深入掌握这一经典模拟电路设计。

weixin_29165725的博客 638

Faiss向量数据库

这些操作通过简单的函数调用实现,例如index.add()用于向索引中添加数据,index.remove_ids()用于从索引中删除指定的数据,而index.search()则用于执行实际的相似性搜索并返回结果。这种索引适用于小到中等规模的数据集,因为它采用暴力检索的方式,即计算查询向量与所有数据库向量之间的距离,然后返回相似度最高的前k个向量。总的来说,IndexFlatL2是理解Faiss其他更复杂索引方式的基础,提供了简洁、准确的检索功能,适用于小规模数据集的精确检索任务。

WuqianZhi_123的博客 1169

HDMI协议介绍(二)--DataIsland Packets

前文简单介绍了HDMI传输的三个周期:控制周期、DataIsland周期和Video周期。DataIsland传输音频和辅助数据,这些数据以Packet的形式传输。本文简单介绍DataIsland Packet。

SlamDunk的博客 5671

STM32F407+LAN8720A+LwIP实战:FreeModbus TCP从机功能开发指南

本文详细介绍了基于STM32F407和LAN8720A硬件平台,结合LwIP协议栈实现FreeModbus TCP从机功能的开发指南。内容涵盖硬件连接、LwIP移植、FreeModbus TCP从机实现、任务调度及调试技巧,为工业控制领域的以太网通信开发提供实用参考。

weixin_29190651的博客 546

faiss的使用

1.faiss.IndexFlatL2 IndexFlatL2索引方式 为向量集构建IndexFlatL2索引,它是最简单的索引类型,只执行强力L2距离搜索 index = faiss.IndexFlatL2(d) # build the index import mkl import math^M import time^M import faiss^M import numpy as np^M ^M d = 768 # 向量维数^M ^M data = [[i] * d for i in ra

qq_34595138的博客 1151

YOLOv8高级应用实战:多目标跟踪+实例分割+小样本检测(工业/安防场景落地指南)

安防监控中,需要跟踪多个行人的运动轨迹,而不只是框出“这是行人”;工业质检时,要精确分割出零件的缺陷区域(像素级),而不是只给一个粗略的矩形框;罕见场景下(如特殊故障检测、稀有动物识别),只能收集到几十张样本,普通训练方法精度惨不忍睹。我在落地工业安防和质检项目时,把YOLOv8的高级功能摸了个遍,踩过“多目标跟踪遮挡丢帧”“实例分割边缘模糊”“小样本检测过拟合”等一系列坑,最终总结出这套可直接复用的实战方案。多目标跟踪(ByteTrack)、实例分割、小样本检测。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 750

faiss-7: 基础索引类型

数据准备 import faiss import numpy as np d = 512 # 维数 # 向量集合 n_data = 2000 np.random.seed(0) data = [] mu = 3 sigma = 0.1 for i in range(n_data): data.append(np.random.normal(mu, sigma, d)) data = np.array(data).astype('float32') # query 向

uncle_ll的博客 1010

PD3.0详解 第十一章 策略层 电源协商(三)(sink端)

前面大师匈介绍了Source端的内容,今天再看一下sink端的内容。大师匈还是想把标准文件的状态机拿出来说一下。大师匈把一张图分成俩部分,大家凑合看一下,一张图实在看不清内容。 Sink 端电源协商状态机。基于sink端哦!!! 上图先!!! 上面俩张图就是整个SINK端口的状态机了。。。介绍一下这个流程: 1、收到hardreset或者刚上电的进入PE_SNK_Startup开始状态。 2、在PE_SNK_Startup阶段后进入PE_SNK_Discovery,并等待Vbus的到来。 3、当Vbus

大师匈的博客 4131

11.1华为OD机试真题 新系统 - 整理学生编号 (Java/Py/C/C++/Js/Go)

11.1华为OD机试真题 新系统 - 整理学生编号 (Java/Py/C/C++/Js/Go)

算法大师! 362

Github 15K! 亿级向量相似度检索库Faiss 原理+应用

作者|Chilia整理|NewBeeNLP最近在使用ColBERT双塔结构进行文本召回,其中必然要涉及到向量相似度查询,如果只用brute-force方法的复杂度实在太高,...

Kaiyuan_sjtu的博客 9689

嵌入式编程模型 | 原型模式

本文介绍了原型模式在嵌入式传感器网络中的应用。原型模式通过复制现有对象创建新实例,避免了重复初始化开销,适用于设备配置克隆、状态复制等场景。文章以传感器网络为例,展示了原型模式的结构(抽象原型类、具体原型类、客户端)和实现方式,通过C语言代码演示了温度/湿度传感器的克隆过程。该模式能有效提升嵌入式系统资源复用效率,实现快速部署相似配置的传感器节点,同时保持内存优化和配置灵活性。

嵌入式大杂烩 146

相似向量检索库-Faiss-简介及原理

前言 由于项目需要,需要对某些种子用户进行look-alike,找到相似用户,所以近期对相似向量检索库Faiss进行一定的了解,接下来,结合相关资料,把我对这个库的了解记录在这里,也希望对你有所帮助! 一:Faiss简介 Faiss全称(Facebook AI Similarity Search)是Facebook AI团队开源的针对聚类和相似性搜索库,为稠密向量提供高效相似度搜索和聚类,支持十亿级别向量的搜索,是目前较成熟的近似近邻搜索库。 它包含多种搜索任意大小向量集(备注:向量集大小由RAM内存决定)

Catcher 3万+

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

向量数据库Faiss(Facebook AI Similarity Search)

向量数据库Faiss(Facebook AI Similarity Search)是Facebook AI Research开发的一款高效且可扩展的相似性搜索和聚类库,专门用于处理大规模向量数据的搜索和检索任务。Faiss以其出色的性能和灵活性,在图像检索、文本搜索、推荐系统等多个领域得到了广泛应用。以下将详细介绍Faiss的搭建与使用过程,包括安装、基本使用、索引类型选择、性能优化及应用场景等方面。

shiming8879的博客 1979

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Faiss索引

1、faiss有两种索引构建模式,一种是全量构建,二是增量的索引构建,也就是在原来的基础上添加向量。第一次构建索引时需要经过Train和Add两个操作,后续添加新embedding就直接执行Add就是增量构建了。2、Faiss提供了三种索引类型精确索引-IndexFlatL2、indexFlatIP、倒排快速索引-IndexIVFFlat、乘积量化索引-IndexIVFPQ, 精确索引是一种暴力无误差模式,检索库数据量不大时,优先追求精度时可以用这个。检索库数据量较大时,推荐使用乘积量化索引。

weixin_61785507的博客 4990

Faiss库

图像 / 视频检索(如以图搜图、视频帧匹配);自然语言处理(如语义搜索、文本聚类、问答系统);推荐系统(如用户 / 物品向量匹配、相似商品推荐);人脸识别 / 生物特征匹配;大规模 embedding 数据的聚类或异常检测。

2501_91906624的博客 1705

faiss的基本使用

【代码】faiss的基本使用。

cy10203089的博客 455

解读向量索引

向量嵌入是从图像、文本和音频等数据源转换而来的数字表示,旨在通过为每个项目创建一个数学向量来捕捉其语义或特征。这种表示方式使得计算系统更容易理解这些数据,并且与机器学习模型兼容,从而能够识别不同项之间的关系和相似性。通常,用于存储这些向量嵌入的专门数据库被称为向量数据库。这些数据库利用了嵌入的数学特性,即能够将相似的项聚集在一起存储。向量数据库采用不同的向量索引技术,可以将相似的向量放置在一起,而...

我相信...... 2468

[python] 向量检索库Faiss使用指北

Faiss是一个由facebook开发以用于高效相似性搜索和密集向量聚类的库。它能够在任意大小的向量集中进行搜索。它还包含用于评估和参数调整的支持代码。Faiss是用C++编写的,带有Python的完整接口。一些最有用的算法是在GPU上实现的。。所谓相似性搜索是指通过比较多维空间中数据之间的相似性来搜索与输入数据最相似的目标数据。例如人脸识别中,通过比较人脸向量之前的距离来识别当前人脸与哪张人脸相似。因此,该技术被广泛应用于信息检索、计算机视觉、数据分析等领域。。...

You and Me 2万+
上一篇: ESP32 002 wokwi在线仿真点亮小灯
下一篇: 向量数据库:faiss的IndexPQ中PQ的图解+实现质心表的融合
FakeOccupational
博客等级 码龄6年 2万+粉丝 1727原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值