cuVS 基准测试#
cuVS 基准测试提供了一个可重复的基准测试工具,用于各种近似最近邻(ANN)搜索实现。它特别适合比较 GPU 实现以及将 GPU 与 CPU 进行比较。cuVS 的主要目标之一是为各种重要使用模式捕获理想的索引配置,以便结果可以在不同的硬件环境(如本地和云端)中轻松复现。
该工具提供了多种好处,包括
对索引构建时间进行公平比较
对索引搜索吞吐量和/或延迟进行公平比较
为一系列召回桶找到最佳参数设置
轻松生成用于索引构建和搜索的一致风格图表
分析盲点和算法优化的潜力
研究不同参数设置、索引构建时间和搜索性能之间的关系。
安装基准测试#
预编译基准测试主要有两种分发方式:
Conda 适用于不使用容器但希望安装和使用Python包的用户。计划添加Pip wheels作为无法使用conda且不希望使用容器的用户的替代方案。
Docker 只需要 docker 和 [NVIDIA docker](NVIDIA/nvidia-docker) 即可使用。提供了一个简单的 docker run 命令用于基本数据集基准测试,以及容器内 conda 解决方案的所有功能。
Conda#
conda create --name cuvs_benchmarks
conda activate cuvs_benchmarks
# to install GPU package:
conda install -c rapidsai -c conda-forge -c nvidia cuvs-ann-bench=<rapids_version> cuda-version=11.8*
# to install CPU package for usage in CPU-only systems:
conda install -c rapidsai -c conda-forge cuvs-bench-cpu
如果需要夜间基准测试,可以轻松地将频道 rapidsai 替换为 rapidsai-nightly。当前的CPU包允许运行HNSW基准测试。
请参阅构建说明以从源代码构建基准测试。
Docker#
我们为支持GPU的系统以及没有GPU的系统提供镜像。以下镜像可用:
cuvs-bench: 包含GPU和CPU基准测试,可以运行所有支持的算法。将根据需要下载百万级数据集。最适合那些在基于GPU的系统上偏好较小容器大小的用户。运行GPU算法需要NVIDIA容器工具包,没有它也可以运行CPU算法。cuvs-bench-datasets: 包含GPU和CPU基准测试,容器中已经包含了百万级数据集。最适合那些想要运行镜像中已经包含的多个百万级数据集的用户。cuvs-bench-cpu: 仅包含最小尺寸的CPU基准测试。最适合希望在无GPU系统上复制基准测试的用户使用最小的容器。
夜间构建的镜像位于dockerhub,而发布(稳定)版本则从24.10版本开始位于NGC。
以下命令拉取Python版本3.10、CUDA版本12.0和cuVS版本24.10的夜间容器:
docker pull rapidsai/cuvs-bench:24.10a-cuda12.0-py3.10 #substitute cuvs-bench for the exact desired container.
CUDA 和 Python 版本可以根据支持的值进行更改: - 支持的 CUDA 版本:11.4 和 12.x - 支持的 Python 版本:3.9 和 3.10。
您可以在dockerhub网站上查看确切的版本: - cuVS bench images - cuVS bench with datasets preloaded images - cuVS bench CPU only images
注意: GPU 容器使用容器内的 CUDA 工具包,唯一的要求是在主机上安装支持该版本的驱动程序。例如,CUDA 11.8 容器可以在支持 CUDA 12.x 驱动程序的系统上运行。请注意,使用 Docker 容器内的 GPU 需要安装来自 Nvidia 容器工具包 的 Nvidia-Docker 运行时。
如何运行基准测试#
我们提供了一系列轻量级的Python脚本来运行基准测试。运行基准测试并可视化结果通常有4个步骤。 #. 准备数据集 #. 构建索引并搜索索引 #. 数据导出 #. 绘制结果
步骤1:准备数据集#
脚本 cuvs_bench.get_dataset 将会下载并解压用户提供的目录中的数据集。目前,该脚本仅支持百万级别的数据集。有关更多信息,请参阅 datasets and formats。
此脚本的用法是:
usage: get_dataset.py [-h] [--name NAME] [--dataset-path DATASET_PATH] [--normalize]
options:
-h, --help show this help message and exit
--dataset DATASET dataset to download (default: glove-100-angular)
--dataset-path DATASET_PATH
path to download dataset (default: ${RAPIDS_DATASET_ROOT_DIR})
--normalize normalize cosine distance to inner product (default: False)
当脚本提供了选项normalize时,任何具有余弦距离的数据集将被归一化为内积。因此,例如,数据集glove-100-angular将被写入位置datasets/glove-100-inner/。
步骤2:构建和搜索索引#
脚本 cuvs_bench.run 将为给定的数据集及其指定的配置构建和搜索索引。
脚本 cuvs_bench.run 的用法是:
usage: __main__.py [-h] [--subset-size SUBSET_SIZE] [-k COUNT] [-bs BATCH_SIZE] [--dataset-configuration DATASET_CONFIGURATION] [--configuration CONFIGURATION] [--dataset DATASET]
[--dataset-path DATASET_PATH] [--build] [--search] [--algorithms ALGORITHMS] [--groups GROUPS] [--algo-groups ALGO_GROUPS] [-f] [-m SEARCH_MODE]
options:
-h, --help show this help message and exit
--subset-size SUBSET_SIZE
the number of subset rows of the dataset to build the index (default: None)
-k COUNT, --count COUNT
the number of nearest neighbors to search for (default: 10)
-bs BATCH_SIZE, --batch-size BATCH_SIZE
number of query vectors to use in each query trial (default: 10000)
--dataset-configuration DATASET_CONFIGURATION
path to YAML configuration file for datasets (default: None)
--configuration CONFIGURATION
path to YAML configuration file or directory for algorithms Any run groups found in the specified file/directory will automatically override groups of the same name
present in the default configurations, including `base` (default: None)
--dataset DATASET name of dataset (default: glove-100-inner)
--dataset-path DATASET_PATH
path to dataset folder, by default will look in RAPIDS_DATASET_ROOT_DIR if defined, otherwise a datasets subdirectory from the calling directory (default:
os.getcwd()/datasets/)
--build
--search
--algorithms ALGORITHMS
run only comma separated list of named algorithms. If parameters `groups` and `algo-groups` are both undefined, then group `base` is run by default (default: None)
--groups GROUPS run only comma separated groups of parameters (default: base)
--algo-groups ALGO_GROUPS
add comma separated <algorithm>.<group> to run. Example usage: "--algo-groups=cuvs_cagra.large,hnswlib.large" (default: None)
-f, --force re-run algorithms even if their results already exist (default: False)
-m SEARCH_MODE, --search-mode SEARCH_MODE
run search in 'latency' (measure individual batches) or 'throughput' (pipeline batches and measure end-to-end) mode (default: throughput)
-t SEARCH_THREADS, --search-threads SEARCH_THREADS
specify the number threads to use for throughput benchmark. Single value or a pair of min and max separated by ':'. Example --search-threads=1:4. Power of 2 values between 'min' and 'max' will be used. If only 'min' is
specified, then a single test is run with 'min' threads. By default min=1, max=<num hyper threads>. (default: None)
-r, --dry-run dry-run mode will convert the yaml config for the specified algorithms and datasets to the json format that's consumed by the lower-level c++ binaries and then print the command to run execute the benchmarks but
will not actually execute the command. (default: False)
dataset: 要在`datasets.yaml`_中搜索的数据集名称
dataset-configuration: 可选的用于自定义数据集YAML配置的文件路径,该配置包含一个用于参数dataset的条目
configuration: 可选的YAML配置文件路径,用于单个算法的配置或包含多个算法配置的目录。更多信息请参考`Dataset.yaml config`_。
algorithms: 运行在configuration中找到的YAML配置中的所有算法。默认情况下,只会运行base组。
groups: 仅运行算法的特定参数配置组。组在YAML配置中定义(参见configuration),默认情况下运行base组
algo-groups: 此参数有助于附加任何特定的算法+组组合,以运行基准测试,除了来自algorithms和groups的所有参数。它的格式为,例如,cuvs_cagra.large
对于此脚本运行的每个算法,它会在中输出一个索引构建统计信息的JSON文件,并在中输出一个索引搜索统计信息的JSON文件。注意:如果group = "base",则文件名将不包含“,{group}”。
对于此脚本运行的每个算法,它会在中输出一个索引构建统计信息的JSON文件,并在中输出一个索引搜索统计信息的JSON文件。注意:如果group = "base",则文件名将不包含“,{group}”。
dataset-path :
#. 数据从 读取
#. 索引构建在
#. 构建/搜索结果存储在
build 和 search:如果脚本没有提供这两个参数,则默认两者都为 True。
indices 和 algorithms : 这些参数确保为索引指定的算法在 algos.yaml 中可用且未被禁用,同时具有相关的可执行文件。
步骤3:数据导出#
脚本 cuvs_bench.data_export 将会把由 cuvs_bench.run 生成的中间 JSON 输出转换为更易读的 CSV 文件,这些文件是构建由 cuvs_bench.plot 制作的图表所必需的。
usage: data_export.py [-h] [--dataset DATASET] [--dataset-path DATASET_PATH]
options:
-h, --help show this help message and exit
--dataset DATASET dataset to download (default: glove-100-inner)
--dataset-path DATASET_PATH
path to dataset folder (default: ${RAPIDS_DATASET_ROOT_DIR})
构建统计CSV文件存储在
索引搜索统计CSV文件存储在,其中后缀有三个值:
#. raw:导出所有搜索结果
#. throughput:导出吞吐量结果的帕累托前沿
#. latency:导出延迟结果的帕累托前沿
步骤4:绘制结果#
脚本 cuvs_bench.plot 将绘制在索引搜索统计 CSV 文件 中找到的所有算法的结果。
此脚本的用法是:
usage: [-h] [--dataset DATASET] [--dataset-path DATASET_PATH] [--output-filepath OUTPUT_FILEPATH] [--algorithms ALGORITHMS] [--groups GROUPS] [--algo-groups ALGO_GROUPS]
[-k COUNT] [-bs BATCH_SIZE] [--build] [--search] [--x-scale X_SCALE] [--y-scale {linear,log,symlog,logit}] [--x-start X_START] [--mode {throughput,latency}]
[--time-unit {s,ms,us}] [--raw]
options:
-h, --help show this help message and exit
--dataset DATASET dataset to plot (default: glove-100-inner)
--dataset-path DATASET_PATH
path to dataset folder (default: /home/coder/cuvs/datasets/)
--output-filepath OUTPUT_FILEPATH
directory for PNG to be saved (default: /home/coder/cuvs)
--algorithms ALGORITHMS
plot only comma separated list of named algorithms. If parameters `groups` and `algo-groups are both undefined, then group `base` is plot by default
(default: None)
--groups GROUPS plot only comma separated groups of parameters (default: base)
--algo-groups ALGO_GROUPS, --algo-groups ALGO_GROUPS
add comma separated <algorithm>.<group> to plot. Example usage: "--algo-groups=cuvs_cagra.large,hnswlib.large" (default: None)
-k COUNT, --count COUNT
the number of nearest neighbors to search for (default: 10)
-bs BATCH_SIZE, --batch-size BATCH_SIZE
number of query vectors to use in each query trial (default: 10000)
--build
--search
--x-scale X_SCALE Scale to use when drawing the X-axis. Typically linear, logit or a2 (default: linear)
--y-scale {linear,log,symlog,logit}
Scale to use when drawing the Y-axis (default: linear)
--x-start X_START Recall values to start the x-axis from (default: 0.8)
--mode {throughput,latency}
search mode whose Pareto frontier is used on the y-axis (default: throughput)
--time-unit {s,ms,us}
time unit to plot when mode is latency (default: ms)
--raw Show raw results (not just Pareto frontier) of mode arg (default: False)
mode: 绘制上一步导出的throughput或latency结果的帕累托前沿
algorithms:绘制所有可以为指定的dataset找到结果的算法。默认情况下,只会绘制base组。
groups: 仅绘制算法的特定参数配置组。组在YAML配置中定义(参见configuration),默认情况下运行base组
algo-groups: 此参数有助于在algorithms和groups的所有参数之外,附加任何特定的算法+组组合以绘制结果。它的格式为,例如,cuvs_cagra.large
运行基准测试#
端到端:较小规模的基准测试(小于1M到10M)#
以下步骤演示了如何从Yandex Deep-1B数据集的10M向量子集中下载、安装和运行基准测试。默认情况下,数据集将存储并从RAPIDS_DATASET_ROOT_DIR环境变量指示的文件夹中使用(如果已定义),否则将从调用脚本的位置的数据集子文件夹中使用:
# (1) prepare dataset.
python -m cuvs_bench.get_dataset --dataset deep-image-96-angular --normalize
# (2) build and search index
python -m cuvs_bench.run --dataset deep-image-96-inner --algorithms cuvs_cagra --batch-size 10 -k 10
# (3) export data
python -m cuvs_bench.data_export --dataset deep-image-96-inner
# (4) plot results
python -m cuvs_bench.plot --dataset deep-image-96-inner
数据集名称 |
训练行数 |
列数 |
测试行数 |
距离 |
|
10M |
96 |
10K |
Angular |
|
60K |
784 |
10K |
欧几里得 |
|
1.1M |
50 |
10K |
Angular |
|
1.1M |
100 |
10K |
Angular |
|
60K |
784 |
10K |
欧几里得 |
|
290K |
256 |
10K |
Angular |
|
1M |
128 |
10K |
欧几里得 |
以上所有数据集都包含100个邻居的地面测试数据集。因此,这些数据集的k必须小于或等于100。
端到端:大规模基准测试(>1000万向量)#
cuvs_bench.get_dataset 不能用于下载 `billion-scale datasets`_,因为它们的大小。你应该使用我们的十亿级数据集指南来下载和准备它们。
一旦十亿级数据集被下载,下面提到的所有其他python命令都可以按预期工作。
要下载十亿级数据集,请访问 big-ann-benchmarks
我们还提供了一个名为wiki-all的新数据集,包含8800万个768维向量。该数据集旨在用于大规模基准测试检索增强生成(RAG)/LLM嵌入大小。它还包含1M和10M向量的子集,用于较小规模的实验。有关更多信息并下载数据集,请参阅我们的Wiki-all Dataset Guide。
以下步骤演示了如何从Yandex Deep-1B数据集的100M向量子集中下载、安装和运行基准测试。请注意,建议使用具有较大内存的GPU(如A100或H100)来处理这种规模的数据集。
mkdir -p datasets/deep-1B
# (1) prepare dataset
# download manually "Ground Truth" file of "Yandex DEEP"
# suppose the file name is deep_new_groundtruth.public.10K.bin
python -m cuvs_bench.split_groundtruth --groundtruth datasets/deep-1B/deep_new_groundtruth.public.10K.bin
# two files 'groundtruth.neighbors.ibin' and 'groundtruth.distances.fbin' should be produced
# (2) build and search index
python -m cuvs_bench.run --dataset deep-1B --algorithms cuvs_cagra --batch-size 10 -k 10
# (3) export data
python -m cuvs_bench.data_export --dataset deep-1B
# (4) plot results
python -m cuvs_bench.plot --dataset deep-1B
python -m cuvs_bench.split_groundtruth 的用法是:
使用Docker容器运行#
提供了两种方法用于使用Docker容器运行基准测试。
在GPU上端到端运行#
当没有提供其他入口点时,端到端脚本将运行上述运行基准测试中的所有步骤。
对于支持GPU的系统,DATA_FOLDER变量应该是一个本地文件夹,您希望数据集存储在$DATA_FOLDER/datasets中,结果存储在$DATA_FOLDER/result中(我们强烈建议$DATA_FOLDER是一个专门用于存储容器数据集和结果的文件夹):
export DATA_FOLDER=path/to/store/datasets/and/results
docker run --gpus all --rm -it -u $(id -u) \
-v $DATA_FOLDER:/data/benchmarks \
rapidsai/cuvs-bench:24.10a-cuda11.8-py3.10 \
"--dataset deep-image-96-angular" \
"--normalize" \
"--algorithms cuvs_cagra,cuvs_ivf_pq --batch-size 10 -k 10" \
""
上述命令的用法如下:
参数 |
描述 |
|
要使用的镜像。可以是 |
|
数据集名称 |
|
是否对数据集进行归一化 |
|
传递给 |
|
将传递给 |
*关于用户和文件权限的说明:* 标志 -u $(id -u) 允许容器内的用户与容器外的用户的 uid 匹配,从而使容器能够读取和写入由 $DATA_FOLDER 变量指示的挂载卷。
在CPU上端到端运行#
上一节中的容器参数也可以用于仅CPU的容器,这些容器可以在没有安装GPU的系统上使用。
*注意:* 镜像更改为 cuvs-bench-cpu 容器,并且不再使用 --gpus all 参数:
export DATA_FOLDER=path/to/store/datasets/and/results
docker run --rm -it -u $(id -u) \
-v $DATA_FOLDER:/data/benchmarks \
rapidsai/cuvs-bench-cpu:24.10a-py3.10 \
"--dataset deep-image-96-angular" \
"--normalize" \
"--algorithms hnswlib --batch-size 10 -k 10" \
""
手动在容器内运行脚本#
所有的 cuvs-bench 镜像都包含 Conda 包,因此可以直接登录到容器本身来使用它们:
export DATA_FOLDER=path/to/store/datasets/and/results
docker run --gpus all --rm -it -u $(id -u) \
--entrypoint /bin/bash \
--workdir /data/benchmarks \
-v $DATA_FOLDER:/data/benchmarks \
rapidsai/cuvs-bench:24.10a-cuda11.8-py3.10
这将使您进入容器中的命令行,cuvs-bench Python 包已准备就绪,如上文[运行基准测试](#running-the-benchmarks)部分所述:
(base) root@00b068fbb862:/data/benchmarks# python -m cuvs_bench.get_dataset --dataset deep-image-96-angular --normalize
此外,容器可以在分离模式下运行,没有任何问题。
评估结果#
基准测试捕获了几种不同的测量值。下表描述了索引构建基准测试的每种测量值:
名称 |
描述 |
基准 |
唯一标识基准实例的名称 |
时间 |
训练索引所花费的墙上时间 |
CPU |
训练索引所花费的CPU时间 |
迭代次数 |
迭代次数(通常为1) |
GPU |
GPU 构建所花费的时间 |
index_size |
用于训练索引的向量数量 |
下表描述了索引搜索基准的每个测量值。最重要的测量值包括 Latency、items_per_second 和 end_to_end。
名称 |
描述 |
基准 |
唯一标识基准实例的名称 |
时间 |
单次迭代(批次)的挂钟时间除以线程数。 |
CPU |
平均CPU时间(用户时间 + 系统时间)。这不包括空闲时间(在等待GPU同步时也可能发生)。 |
迭代次数 |
总批次数。这将是 |
GPU |
单个批次的GPU延迟(秒)。在吞吐量模式下,这是在多个线程上平均的。 |
延迟 |
单个批次的延迟(秒),根据挂钟时间计算。在吞吐量模式下,这是在多个线程上平均的。 |
召回率 |
正确邻居与真实邻居的比例。请注意,只有在数据集配置中指定了真实文件时,此列才会出现。 |
items_per_second |
总吞吐量,也称为每秒查询数(QPS)。这大约是 |
k |
每次迭代中查询的邻居数量 |
end_to_end |
运行所有迭代的所有批次所需的总时间 |
n_queries |
每批查询向量的总数 |
total_queries |
所有迭代中的向量查询总数(= |
请注意以下事项:
- 测量Time和end_to_end使用了稍微不同的方法。这就是为什么end_to_end = Time * Iterations仅大致成立。
- 屏幕上显示的实际表格可能会略有不同,因为每个不同的组合进行基准测试时也会显示超参数。
- 召回计算:每次测试处理的查询数量取决于迭代次数。因此,如果处理的邻居数量少于基准测试中可用的数量,召回率可能会显示轻微的波动。
创建和自定义数据集配置#
单一配置通常会定义一组算法,以及相关的索引和搜索参数,这些算法和参数可以跨数据集进行泛化。我们使用YAML来定义数据集特定和算法特定的配置。
默认的 datasets.yaml 由 CUVS 在 ${CUVS_HOME}/python/cuvs-ann-bench/src/cuvs_bench/run/conf 中提供,其中包含多个数据集的配置。以下是一个简单的 sift-128-euclidean 数据集的示例条目:
- name: sift-128-euclidean
base_file: sift-128-euclidean/base.fbin
query_file: sift-128-euclidean/query.fbin
groundtruth_neighbors_file: sift-128-euclidean/groundtruth.neighbors.ibin
dims: 128
distance: euclidean
由cuvs-bench支持的ANN算法的配置文件在${CUVS_HOME}/python/cuvs_bench/cuvs_bench/config/algos中提供。cuvs_cagra算法的配置如下所示:
name: cuvs_cagra
groups:
base:
build:
graph_degree: [32, 64]
intermediate_graph_degree: [64, 96]
graph_build_algo: ["NN_DESCENT"]
search:
itopk: [32, 64, 128]
large:
build:
graph_degree: [32, 64]
search:
itopk: [32, 64, 128]
可以通过为具有base组的算法创建自定义YAML文件来覆盖运行基准测试的默认参数。
上述配置有两个字段:
1. name - 定义为其指定参数的算法的名称。
2. groups - 定义一个具有特定参数集的运行组。每个组帮助创建build和search的所有超参数字段的交叉积。
下表包含了cuVS支持的所有算法。每个独特的算法都将有自己的一套build和search设置。ANN算法参数调优指南包含了为每个支持的算法选择构建和搜索参数的详细说明。
库 |
算法 |
FAISS_GPU |
|
FAISS_CPU |
|
GGNN |
|
HNSWLIB |
|
cuVS |
|
多GPU基准测试#
cuVS实现了单节点多GPU版本的IVF-Flat、IVF-PQ和CAGRA索引。
索引类型 |
多GPU算法名称 |
IVF-Flat |
|
IVF-PQ |
|
CAGRA |
|
添加新的索引算法#
实现和配置#
新算法的实现应该是一个继承自class ANN(定义在cpp/bench/ann/src/ann.h)的C++类,并且实现所有的纯虚函数。
此外,它应该定义两个struct`s 用于构建和搜索参数。 搜索参数类应该继承 `struct ANN。以class HnswLib为例,其定义为:
基准测试程序在配置文件中原生使用JSON格式来指定要构建的索引,以及构建和搜索参数。然而,JSON配置文件过于冗长,并不适合直接使用。相反,Python脚本解析YAML并自动创建这些JSON文件。重要的是要认识到这些JSON对象与build_param的YAML对象对齐,其值是一个JSON对象,而search_param的值是一个JSON对象数组。以HnswLib的JSON配置为例,展示从YAML解析后的JSON:
构建和搜索参数最终以json对象的形式传递给C++层,用于每个参数配置的基准测试。下面的代码展示了如何为Hnswlib解析这些参数:
首先,添加两个函数用于将JSON对象解析为
struct BuildParam和struct SearchParam:
template<typename T>
void parse_build_param(const nlohmann::json& conf,
typename cuann::HnswLib<T>::BuildParam& param) {
param.ef_construction = conf.at("efConstruction");
param.M = conf.at("M");
if (conf.contains("numThreads")) {
param.num_threads = conf.at("numThreads");
}
}
template<typename T>
void parse_search_param(const nlohmann::json& conf,
typename cuann::HnswLib<T>::SearchParam& param) {
param.ef = conf.at("ef");
if (conf.contains("numThreads")) {
param.num_threads = conf.at("numThreads");
}
}
接下来,通过调用解析函数,将相应的
if情况添加到函数create_algo()(在cpp/bench/ann/) 和 `create_search_param()中。if条件语句中的字符串字面量必须与配置文件中algo的值相同。例如,
添加一个Cmake目标#
在cuvs/cpp/bench/ann/CMakeLists.txt中,我们提供了一个CMake函数来配置一个新的Benchmark目标,其签名如下:
要为HNSWLIB添加目标,我们将调用函数如下:
ConfigureAnnBench(
NAME HNSWLIB PATH bench/ann/src/hnswlib/hnswlib_benchmark.cpp INCLUDES
${CMAKE_CURRENT_BINARY_DIR}/_deps/hnswlib-src/hnswlib CXXFLAGS "${HNSW_CXX_FLAGS}"
)
这将创建一个名为HNSWLIB_ANN_BENCH的可执行文件,然后可以用来运行HNSWLIB基准测试。
向algos.yaml添加一个新条目,将算法的名称映射到其二进制可执行文件,并指定该算法是否需要GPU支持。
executable : 指定将构建/搜索索引的二进制文件的名称。假定它在 cuvs/cpp/build/ 中可用。
requires_gpu : 表示算法是否需要GPU来运行。