Faiss(14):IndexIVFPQ的CPU search过程分析

2023-05-16

1. 说明

之前分析过了faiss 在GPU中的search过程,这里分析一下IndexIVFPQ在CPU中的search过程,即不将index拷贝到GPU中。

2. 过程分析

2.1 python接口

CPU search的python接口与GPU的完全一致,没有差别。

D, I = gpu_index.search(xq_t[x],top_k)

2.2 faiss core

IndexIVF::search

因为IndexIVFPQ没有override search,所以在实际运行过程中会调用父类IndexIVF中的实现。

void IndexIVF::search (idx_t n, const float *x, idx_t k,
                         float *distances, idx_t *labels) const
{
    std::unique_ptr<idx_t[]> idx(new idx_t[n * nprobe]);
    std::unique_ptr<float[]> coarse_dis(new float[n * nprobe]);

    // 1. quantizer中的粗量搜索
    quantizer->search (n, x, nprobe, coarse_dis.get(), idx.get());

    // 2. prefetch_lists函数为空
    invlists->prefetch_lists (idx.get(), n * nprobe);

    // 3. 预分配搜索
    search_preassigned (n, x, k, idx.get(), coarse_dis.get(),
                        distances, labels, false);
}

search过程主要包含三个部分:

  1. quantizer->search
    量化器搜索聚类中心,该过程在quantizer实例中进行,计算所有(nlist个)聚类中心与每一条搜索向量的距离,并从中找出最近的nprobe个聚类中心,输出到idx和coarse_dis中。
    所以这里的idx和coarse_dis是大小为 nnprobe 的二维数组,分别存放nnprobe个向量label和与原向量的距离。

  2. invlists->prefetch_lists
    这里没有用到这类功能,所以实际调用的该函数为空。

  3. search_preassigned
    经过第一步计算出粗聚类中心向量后,在这里进行二次计算,即在选定的聚类里再次计算出top_k个近邻向量。由于在add时已经对向量进行了预计算形成残差,所以这里只要进行向量和运算就可以了。
    在实际测试时发现,search的主要时间消耗是在这里,占比达到96%以上,所以针对这一过程进行进一步分析。

IndexIVF::search_preassigned

    /** search a set of vectors, that are pre-quantized by the IVF
     *  quantizer. Fill in the corresponding heaps with the query
     *  results. The default implementation uses InvertedListScanners
     *  to do the search.
     *
     * @param n      nb of vectors to query
     * @param x      query vectors, size nx * d
     * @param assign coarse quantization indices, size nx * nprobe
     * @param centroid_dis
     *               distances to coarse centroids, size nx * nprobe
     * @param distance
     *               output distances, size n * k
     * @param labels output labels, size n * k
     * @param store_pairs store inv list index + inv list offset
     *                     instead in upper/lower 32 bit of result,
     *                     instead of ids (used for reranking).
     * @param params used to override the object's search parameters
     */
void IndexIVF::search_preassigned (idx_t n, const float *x, idx_t k,
                                   const idx_t *keys,
                                   const float *coarse_dis ,
                                   float *distances, idx_t *labels,
                                   bool store_pairs,
                                   const IVFSearchParameters *params) const
{
    // max_codes是默认值0
    long nprobe = params ? params->nprobe : this->nprobe;
    long max_codes = params ? params->max_codes : this->max_codes;

    size_t nlistv = 0, ndis = 0, nheap = 0;

    // 根据计算类型定义堆
    using HeapForIP = CMin<float, idx_t>;
    using HeapForL2 = CMax<float, idx_t>;

    bool interrupt = false;

    // don't start parallel section if single query
    bool do_parallel =
        parallel_mode == 0 ? n > 1 :
        parallel_mode == 1 ? nprobe > 1 :
        nprobe * n > 1;

#pragma omp parallel if(do_parallel) reduction(+: nlistv, ndis, nheap)
    {
        InvertedListScanner *scanner = get_InvertedListScanner(store_pairs);
        ScopeDeleter1<InvertedListScanner> del(scanner);

        /****************************************************
         * Actual loops, depending on parallel_mode
         ****************************************************/

        if (parallel_mode == 0) {

#pragma omp for
            for (size_t i = 0; i < n; i++) {

                if (interrupt) {
                    continue;
                }
                
                // 在inverted_list中设置搜索的起始点
                scanner->set_query (x + i * d);
                // 根据i设置distances和labels的地址
                float * simi = distances + i * k;
                idx_t * idxi = labels + i * k;

                init_result (simi, idxi);

                long nscan = 0;

                // 依次在nprobe个聚类中进行搜索
                for (size_t ik = 0; ik < nprobe; ik++) {

                    nscan += scan_one_list (
                         keys [i * nprobe + ik],
                         coarse_dis[i * nprobe + ik],
                         simi, idxi
                    );

                    if (max_codes && nscan >= max_codes) {
                        break;
                    }
                }

                ndis += nscan;
                //对搜索结果进行排序
                reorder_result (simi, idxi);

                if (InterruptCallback::is_interrupted ()) {
                    interrupt = true;
                }

            } // parallel for
        } else if (parallel_mode == 1) {
            std::vector <idx_t> local_idx (k);
            std::vector <float> local_dis (k);

            for (size_t i = 0; i < n; i++) {
                scanner->set_query (x + i * d);
                init_result (local_dis.data(), local_idx.data());

#pragma omp for schedule(dynamic)
                for (size_t ik = 0; ik < nprobe; ik++) {
                    ndis += scan_one_list
                        (keys [i * nprobe + ik],
                         coarse_dis[i * nprobe + ik],
                         local_dis.data(), local_idx.data());

                    // can't do the test on max_codes
                }
                // merge thread-local results

                float * simi = distances + i * k;
                idx_t * idxi = labels + i * k;
#pragma omp single
                init_result (simi, idxi);

#pragma omp barrier
// 将各个线程产生的堆合并到结果堆中,临界访问
#pragma omp critical
                {
                    if (metric_type == METRIC_INNER_PRODUCT) {
                        heap_addn<HeapForIP>
                            (k, simi, idxi,
                             local_dis.data(), local_idx.data(), k);
                    } else {
                        heap_addn<HeapForL2>
                            (k, simi, idxi,
                             local_dis.data(), local_idx.data(), k);
                    }
                }
#pragma omp barrier
#pragma omp single
                reorder_result (simi, idxi);
            }
        } else {
            FAISS_THROW_FMT ("parallel_mode %d not supported\n",
                             parallel_mode);
        }
    } // parallel section

    if (interrupt) {
        FAISS_THROW_MSG ("computation interrupted");
    }

    indexIVF_stats.nq += n;
    indexIVF_stats.nlist += nlistv;
    indexIVF_stats.ndis += ndis;
    indexIVF_stats.nheap_updates += nheap;

}

这里的params使用默认值nullptr.

流程
从代码中可以看出,虽然根据parallel_mode值的不同,程序处理上会有部分差异,但差异主要体现在并行运算的时间点和内容,主要流程是一致的:

  1. 首先根据原向量下标确定要输出的distances和labels的地址;
  2. 在nprobe个倒序列表中进行搜索,并对搜索结果进行排序

堆的使用
Faiss使用堆对搜索结果进行排序,不同的搜索类型可能使用不同的堆,在L2的范式搜索中使用大根堆进行排序。

do_parallel
Faiss可以直接使用OpemMP的并行运算指令,do_parallel是打开并行运算的标志值,在以下三种情况下为1:

  • parallel_mode == 0 && n > 1
  • parallel_mode == 1 && nprobe > 1
  • parallel_mode == 2 && nprobe * n > 1

parallel_mode
该值确定采用何种并行模式进行查询。0表示在查询时开启并行,1表示在inverted_list计算残差时开启并行,2表示在上述两个阶段都使用并行模式。

并行运算
Faiss默认添加了对OpenMP的支持,具体命令待添加

intialize + reorder resule heap
这部分原本是定义在search_preassigned 函数体内的函数,这里把它们单拎出来。顾名思义,这里是分别对堆进行初始化和排序,因为搜索结果通常是两个列表,distances和labels,所以这里也是两个堆。

 auto init_result = [&](float *simi, idx_t *idxi) {
            if (metric_type == METRIC_INNER_PRODUCT) {
                heap_heapify<HeapForIP> (k, simi, idxi);
            } else {
                heap_heapify<HeapForL2> (k, simi, idxi);
            }
        };

auto reorder_result = [&] (float *simi, idx_t *idxi) {
    if (metric_type == METRIC_INNER_PRODUCT) {
        heap_reorder<HeapForIP> (k, simi, idxi);
    } else {
        heap_reorder<HeapForL2> (k, simi, idxi);
    }
};

loop probes分析
每个线程在这一过程中会循环遍历nprobe个聚类,计算残差,以最后得出top个最近邻向量。代码内容如下:

// loop over probes
                for (size_t ik = 0; ik < nprobe; ik++) {
                    nscan += scan_one_list (
                         keys [i * nprobe + ik],
                         coarse_dis[i * nprobe + ik],
                         simi, idxi
                    );

                    if (max_codes && nscan >= max_codes) {
                        break;
                    }
                }

其中scan_one_list是函数内定义的函数,主要完成下列工作:

  1. invlist->list_size:计算倒序列表的大小,为空则直接跳过;
  2. scanner->set_list:根据key值,从coarse_dis_i列表中找到入口地址;
  3. ScopedCodes:根据key值和invlists的内容生成ccode;
  4. sids->get:重置并获取id号;
  5. scanner->scan_codes:扫描一组代码,计算到当前查询的距离,并更新结果堆。

scan_one_list
这个函数的功能是在单个的聚类中进行搜索。

/*
* key: nprobe中的invetred list编号
* coarse_dis_i: key对应的聚类中心的distance
* simi:存放distances结果的堆
* idxi: 存放labels结果的堆
*/
auto scan_one_list = [&] (idx_t key, float coarse_dis_i,
                          float *simi, idx_t *idxi) {

    if (key < 0) {
        // not enough centroids for multiprobe
        return (size_t)0;
    }
    FAISS_THROW_IF_NOT_FMT (key < (idx_t) nlist,
                            "Invalid key=%ld nlist=%ld\n",
                            key, nlist);

    size_t list_size = invlists->list_size(key);

    // don't waste time on empty lists
    if (list_size == 0) {
        return (size_t)0;
    }

    scanner->set_list (key, coarse_dis_i);

    nlistv++;

    InvertedLists::ScopedCodes scodes (invlists, key);

    std::unique_ptr<InvertedLists::ScopedIds> sids;
    const Index::idx_t * ids = nullptr;

    if (!store_pairs)  {
        sids.reset (new InvertedLists::ScopedIds (invlists, key));
        ids = sids->get();
    }

    nheap += scanner->scan_codes (list_size, scodes.get(),
                                  ids, simi, idxi, k);

    return list_size;
}

这个函数的绝大部分时间消耗在scanner->scan_codes内。

scanner
从代码看,scanner是一个搜索引擎的实例,用于在InvertedList中进行搜索的具体实现。值得单独分析。
所有的线程都会单独生成一个自己的scanner。

InvertedListScanner *scanner = get_InvertedListScanner(store_pairs);

scanner->scan_codes
scanner是struct IVFPQScanner结构体的实例,定义在IndexIVFPQ.h中,scan_codes函数内容如下:

size_t scan_codes (size_t ncode,
                       const uint8_t *codes,
                       const idx_t *ids,
                       float *heap_sim, idx_t *heap_ids,
                       size_t k) const override
    {
        KnnSearchResults<C> res = {
            /* key */      this->key,
            /* ids */      this->store_pairs ? nullptr : ids,
            /* k */        k,
            /* heap_sim */ heap_sim,
            /* heap_ids */ heap_ids,
            /* nup */      0
        };

        if (this->polysemous_ht > 0) {
            assert(precompute_mode == 2);
            this->scan_list_polysemous (ncode, codes, res);
        } else if (precompute_mode == 2) {
            this->scan_list_with_table (ncode, codes, res);
        } else if (precompute_mode == 1) {
            this->scan_list_with_pointer (ncode, codes, res);
        } else if (precompute_mode == 0) {
            this->scan_on_the_fly_dist (ncode, codes, res);
        } else {
            FAISS_THROW_MSG("bad precomp mode");
        }
        return res.nup;
    }

实际运行中polysemous_ht为0,precompute_mode为2,故之后调用scan_list_with_table函数。

scan_list_with_table

函数内容如下:

template<class SearchResultType>
    void scan_list_with_table (size_t ncode, const uint8_t *codes,
                               SearchResultType & res) const
    {
        for (size_t j = 0; j < ncode; j++) {
            float dis = dis0;
            const float *tab = sim_table;
            for (size_t m = 0; m < pq.M; m++) {
                dis += tab[*codes++];
                tab += pq.ksub;
            }
            res.add(j, dis);
        }
}

程序运行到这里已经进入了IndexIVFPQ的聚类里面进行残差计算(浮点加)。

3. 总结

虽然不够细致,但经过本文的梳理,可以大致看出IndexIVF的搜索的过程,这其中最重要的两个步骤分别与上一篇文档中提到的Product Quantizer和Inverted File System对应,所以说只要搞清楚这两个实例的过程,便能完全了解整个search的流程了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Faiss(14):IndexIVFPQ的CPU search过程分析 的相关文章

  • NodeJS CPU 一次飙升至 100%

    我有一个用 NodeJS 编写的 SOCKS5 代理服务器 我正在使用原生net and dgram打开 TCP 和 UDP 套接字的库 它可以正常工作大约 2 天 所有 CPU 的最大利用率约为 30 两天没有重新启动后 一个 CPU 峰
  • 是否可以在VM内使用VMX CPU指令?

    VM guest 内部的进程是否有可能使用 VMX AMD V VT x CPU 指令 然后由外部 VMM 处理而不是直接在 CPU 上处理 Edit 假设外部VM使用VMX本身来管理其虚拟客户机 即它在Ring 1中运行 如果可能的话 是
  • 在数据库中搜索时忽略空文本框

    此代码能够搜索数据并将其加载到DataGridView基于搜索表单文本框中提供的值 如果我将任何文本框留空 则不会有搜索结果 因为 SQL 查询是用 AND 组合的 如何在搜索 从 SQL 查询或 C 代码 时忽略空文本框 private
  • 分支预测器和分支目标缓冲区如何共存?

    我的问题是它们如何在现代 CPU 架构中共存并协同工作 你把它稍微颠倒了 每次获取时 您都会索引到分支预测器 它会告诉您刚刚收到的指令是否will be解码为已采取的分支 如果没有 则获取下一个连续地址 但是 如果您的分支预测器说它将是一个
  • 谷歌如何通过图像进行搜索?

    最近 谷歌推出了图片搜索的新功能 即通过图片搜索 我们可以通过在谷歌搜索框中上传图片来搜索其他图片 这怎么可能 http images google com http images google com Look at WP 基于内容的图像
  • 使用 Fortran 进行数组问题的二分查找

    我正在使用 Schaum 的 Fortran 77 编程概要 一书 其中有一个关于使用括号值组方法进行二分搜索的示例 首先这是代码 INTEGER X 100 INTEGER RANGE INTEGER START FINISH PRINT
  • ArrayList 搜索 .net

    以下是存储在我的数组列表中的数据的格式 A Amsterdam B Brussels C Canada 如此等等 我想通过仅传递前几个字符直到 来搜索我的数组列表 因此 如果我有类似 AA Test 的东西 那么我只想通过 AA 来检查它是
  • grep 查找 Unix 中的特殊字符

    我有一个日志文件 application log 其中可能包含以下多行普通和特殊字符字符串 Q 我想搜索包含这个特殊字符串的行号 grep Q application log 上述命令不返回任何结果 获取行号的正确语法是什么 Tell gr
  • 在休眠搜索中使用现有分析器AnalyzerDiscriminator

    Entity Indexed AnalyzerDefs AnalyzerDef name en tokenizer TokenizerDef factory StandardTokenizerFactory class filters To
  • Twitter Bootstrap 行过滤器/搜索框

    我无法找到有关如何为 Twitter Bootstrap 创建简单搜索查询或行过滤器的教程 我已经尝试了很多 我不确定是否我做错了什么或者插件与 Bootstrap 不兼容 如果可以的话请帮忙 我试过了 document ready fun
  • 快速算法可以快速找到一组范围中某个数字所属的范围?

    场景 我有几个数字范围 这些范围不重叠 由于它们不重叠 逻辑结果是任何时候任何数字都不能属于多个范围 每个范围都是连续的 单个范围内没有空洞 因此范围 8 到 16 将真正包含 8 到 16 之间的所有数字 但两个范围之间可能存在空洞 例如
  • 使用Python获取CPU温度?

    如何使用 Python 检索 CPU 的温度 假设我在Linux上 有一个较新的 sysfs 热区 API http shallowsky com blog linux kernel sysfs thermal zone html 也可以看
  • 如何突出显示 html 文档中文本查询的搜索结果而忽略 html 标签?

    我有一个字符串 其中包含 html 内容 像这样的东西 const text My name is Alan and I span an span div class someClass artist div 我使用以下命令在反应组件中渲染
  • 如何将 UIWebView 中的输入的键盘按钮“返回”更改为“搜索”?

    我有一个简单的 HTML 文件 它将显示在 UIWebView 中 p p
  • (Nand2tetris CPU)每个时钟周期发生(什么/多少)?

    在此基础上Nand2俄罗斯方块 https www coursera org learn build a computer lecture gjhcz unit 5 5 project 5 overviewCPU 如下图 我想了解一下 每个
  • Linux 内核中是否使用了扩展指令集(SSE、MMX)?

    好吧 它们带来 至少应该带来 性能的巨大提升 不是吗 所以 我还没有看到任何 Linux 内核源代码 但很想问 它们是否以某种方式被使用 在这种情况下 对于没有此类指令的系统 必须有一些特殊的 代码上限 SSE 和 MMX 指令集在音频 视
  • 常用姓名别名/昵称数据库

    我参与了一个 SQL NET 项目 该项目将搜索名称列表 我正在寻找一种方法来返回类似名字的人的一些结果 如果搜索 Tom 结果将包括 Thom Thomas 等 这是文件还是 Web 服务并不重要 设计示例 Table Names has
  • Nodejs 异步函数是否使用所有 CPU 核心?

    如果我使用异步函数或带有回调的函数 例如本机 fs 模块 http 等 它们会默认在所有 cpu 核心上运行吗 或者整个系统只使用 1 个核心 Node js 中的一些异步操作 例如文件 I O fsmodule 将通过 libuv 中的线
  • 字符串插值搜索

    对于那些不熟悉插值搜索的人来说 这是一种在排序数组中搜索值的方法 可能比二分搜索更快 您查看第一个和最后一个元素 并 假设数组的内容均匀分布 线性插值以预测位置 例如 我们有一个长度为 100 的数组 其中 array 0 0 和 arra
  • 使用php表单更改href链接

    我正在制作一个带有搜索栏的网站 我想让搜索栏在 搜索 并显示结果后具有交互性 所以我希望 href 根据正在使用的 Id 进行更改 例如 有人搜索 Pinecones 如果它在数据库中 它将有一个 ID 在本例中是 4 一旦他们搜索它 它就

随机推荐

  • 使用 aptitude解决ubuntu下apt-get install g++依赖问题

    问题描述 xff1a ubuntu下运行C 43 43 程序 xff0c 给出了如下错误提示 程序 g 43 43 尚未安装 使用以下命令安装 xff1a sudo apt get install g 43 43 执行 得出如下错误 正在读
  • 学习笔记-Raspberry Pi Zero W-4:串口(UART)的配置和使用

    4 1 开启UART 据官方所言 xff08 https www raspberrypi org documentation configuration uart md xff09 xff1a 树莓派CPU内部有两个串口 xff0c 一个P
  • CAAnimation——基本动画,关键帧动画和贝塞尔路径

    概述 在做对于图层的动画效果时 xff0c 往往直接改变属性或者使用隐式动画是不能满足我们的需求的 xff0c 所以我们就用到了显式动画 xff0c CAAnimation 它可以管理重复动画 准确的控制时间和步调 xff0c 并且能设定图
  • IOS详解TableView——性能优化及手工绘制UITableViewCell

    提高表视图的性能 UITableView作为应用中最常用的视图 xff0c 它的性能优化问题几乎是经常提及 下面对在非网络访问情况下的表视图性能优化进行了主要的几点说明 xff1a 1 自定义类或XIB文件时 在系统提供的样式不能满足我们的
  • IOS详解TableView——实现九宫格效果

    根据需求九宫格的效果可以有很多种 九宫格效果应用比较广泛 xff0c 实现也多种多样 xff0c 比如选项抽屉效果 这里写了一个在UITableView上显示九宫格效果的Demo 思路 xff1a 在Cell上初始化自定义按钮 xff0c
  • IOS详解TableView——内置刷新,EGO,以及搜索显示控制器

    这几天因为住的地方的网出了一点问题 xff0c 除了能上Q xff0c 上微博以外其他的网页全都无法登陆 博客也就没有跟进 今天恢复了 xff0c 所以继续更新博客 也希望大家能继续评论或私自给我一些建议或者交流 今天找到了以前一个Tabl
  • Linux设备驱动基础01:Linux设备驱动概述

    目录 1 设备驱动的作用 2 有无操作系统时的设备驱动 2 1 无操作系统 2 1 1 硬件 驱动和应用程序的关系 2 1 2 单任务软件典型架构 2 2 有操作系统 2 2 1 硬件 驱动 操作系统和应用软件的关系 3 Linux设备分类
  • IOS回调机制——代理,通知中心以及Block

    Xcode5 0正式版 IOS7和Xcode5正式版在昨天正式可以下载 IOS7不多说了 xff0c 交互设计 xff0c 界面风格 xff0c 操作的简化程度都属于比较领先的水平 这里来说说Xcode5正式版 xff0c 和以前的Xcod
  • IOS飞机大战OC版

    前一阵子看到了很多版本的打飞机游戏 xff0c 有Java版的C 43 43 版本的还有C语言版的 这几天闲着的时候写了一个OC版的 xff0c 也正好是因为答应朋友写这个游戏来把飞机都换成他照片 没有用Cocos2d框架 xff0c 用的
  • Swift的可选链,类型转换和扩展

    可选链 Optional Chaining 可选链是一种请求或调用属性 xff0c 方法 xff0c 子脚本的过程 可选性体现于请求或调用的目标当前可能为nil 若不为nil则成功调用 xff0c 否则返回nil并将链失效 调用可选链的返回
  • iOS小米遥控器的手势监听及UI实现

    这篇文章通过实例实现了一个类似小米手势遥控器的功能页面 效果图如下所示 xff1a 触摸事件的响应通过对系统的触摸实践监听来进行 通过一个数组来对点的集合进行缓存和分析 void touchesBegan NSSet touches wit
  • 博客搬家至Github

    为了使用Markdown写作更方便一些 xff0c 以后将使用github pages来管理博客 地址 xff1a Rannie s Page 欢迎来访
  • C++使用http向服务器发送json数据

    span class token macro property span class token directive hash span span class token directive keyword include span spa
  • 如何使用Git将Github项目拉到本地

    如何使用Git将Github项目拉到本地 前言 因为国内访问GIthub速度比较慢 xff0c 复制粘贴代码又慢效率也低 xff0c 所以建议下载Git工具 xff0c 直接把Github的项目整个下载到本地的文件夹 安装配置git 步骤如
  • 笔记本 - 数据分析百宝箱

    Numpy 一 基本操作 xff1a 属性 xff1a improt numpy as np 生成数组 xff1a array 61 np array 1 2 3 2 3 4 xff0c dtype 61 np int float arra
  • Faiss(5):IndexIVFPQ原理

    说明 原本想尝试自己从头写 xff0c 但看了下网上的各位前辈的博客后 xff0c 感觉自己还是才疏学浅 xff0c 没有理解透彻 xff0c 所以在这里做个搬运工 xff0c 偶尔加些个人的理解在里面 原文链接 xff1a https b
  • cmake(3):编译库和链接可执行文件

    1 说明 在实际开发的过程当中 xff0c 我们会经常需要将部分程序编译成静态或动态库的形式 xff0c 供其他应用程序调用而不是将所有文件一次编译为一个可执行文件 这篇笔记就记录使用cmake编译动态和静态库以及将库链接到可执行文件中的过
  • RTOS原理与实现02:基本任务切换实现

    目录 1 任务定义与切换原理 1 1 任务是什么 1 1 1 任务的外观 1 1 2 任务的内在 1 2 任务切换原理 1 2 1 任务切换的本质 1 2 2 要保存哪些任务运行状态 1 2 3 任务运行状态保存方案 1 3 设计实现 1
  • cmake(5):选择编译器及设置编译器选项

    1 说明 在实际的项目平台中可能安装有多个版本的编译器 xff0c 同时由于不同的功能可能会需要设置不同的编译参数 xff0c 这篇笔记就记录如何选择指定的编译器和配置参数 2 选择编译器 2 1 初始状态 我使用的开发平台默认安装的gcc
  • Faiss(14):IndexIVFPQ的CPU search过程分析

    1 说明 之前分析过了faiss 在GPU中的search过程 xff0c 这里分析一下IndexIVFPQ在CPU中的search过程 xff0c 即不将index拷贝到GPU中 2 过程分析 2 1 python接口 CPU searc