Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

按 ID 计算距离

除了 KnnSearch 和 RangeSearch,VSAG 还提供了在已建好索引的向量上按 ID 计算距离的 接口,可用于对外部候选集进行重排、召回核验,或在 VSAG 之上构建自定义检索流水线。

接口分为两种形式:

  • CalcDistanceById — 单个 ID,返回单个距离值。
  • CalcDistancesById — 一批 ID,返回一个包含距离数组的 DatasetPtr。

每种形式都有两个重载:一个接收 const float*(稠密向量),另一个接收 DatasetPtr (稠密或稀疏均可)。

迁移说明。 CalDistanceById 是批量接口的历史拼写。为兼容已有代码,1.1 迁移窗口内 仍保留该弃用别名;新代码应使用 CalcDistancesById。两者语义完全相同。详情参见 issue #2068。

HGraph 还支持配置为稀疏或非 FP32 数据类型。这些配置应使用包含原生 query 字段的 Dataset(SparseVectors、Int8Vectors,以及 FP16/BF16 对应的 Float16Vectors);只有 float32 索引才能使用普通 float* 距离查询。Dataset 表示必须匹配索引配置,不能只检查向量维度。

C++ 实现迁移

CalcDistancesById 是正式的批量虚函数接口,包含完整 topk 参数。公共包装层和内部索引实现直接通过该名称派发;公共弃用别名 CalDistanceById 只转发到正式接口,不再反向依赖旧入口。

自定义索引实现应 override 对应的 CalcDistancesById 重载,不能再依赖仅 override 历史拼写。普通调用旧别名仍保持源码兼容,但仅覆盖旧别名不再构成正式批量接口的实现。此次虚函数接口布局发生变化,不保证与此前编译的 Index 子类二进制兼容;请使用匹配的头文件和库重新编译自定义子类及相关 C++ 组件。

接口概览

// 单个 ID,稠密浮点指针
tl::expected<float, Error>
CalcDistanceById(const float* vector,
                 int64_t id,
                 bool calculate_precise_distance = true) const;

// 单个 ID,DatasetPtr(稠密或稀疏)
tl::expected<float, Error>
CalcDistanceById(const DatasetPtr& vector,
                 int64_t id,
                 bool calculate_precise_distance = true) const;

// 批量 ID,稠密浮点指针
tl::expected<DatasetPtr, Error>
CalcDistancesById(const float* query,
                const int64_t* ids,
                int64_t count,
                bool calculate_precise_distance = true,
                int64_t topk = -1) const;

// 批量 ID,DatasetPtr(稠密或稀疏)
tl::expected<DatasetPtr, Error>
CalcDistancesById(const DatasetPtr& query,
                const int64_t* ids,
                int64_t count,
                bool calculate_precise_distance = true,
                int64_t topk = -1) const;

对于 NumElements() > 1 的 DatasetPtr 查询,请先检查 SUPPORT_BATCH_CALC_DISTANCE_BY_ID。count 表示每个 query 的 ID 数,ids 需要包含 NumElements() * count 个 row-major ID,返回距离使用相同布局。当 topk > 0 时, 每个 query 返回按距离升序排列的 min(topk, count) 个结果,并同时返回对应 ID。

声明位于 include/vsag/index.h。

calculate_precise_distance

  • true(默认)优先使用已保留的高精度/重排表示,不会还原已经丢弃的原始向量。没有独立精确表示时使用存储表示,结果仍可能受量化或剪枝影响。
  • false 使用基础表示。两种模式都是对指定 ID 计算距离,不执行近似候选检索;磁盘存储下两种模式均可能产生 I/O。
  • BruteForce/WARP 和 SIMQ 的两种模式使用同一套存储距离后端。HGraph/Pyramid 优先使用保留的原始向量,其次是重排编码;IVF 使用配置的重排编码/桶;SINDI/SINDI_V2 在配置重排时使用 rerank 向量。

各索引的原生 query 表示

索引单 ID query多 query 批量输入
BruteForce、HGraph、IVF、Pyramid、LazyHGraphfloat*,或含 Float32Vectors 的单行 DatasetPtr含 Float32Vectors 的 DatasetPtr
SINDI、SINDI_V2含 SparseVectors 的单行 DatasetPtr含 SparseVectors 的 DatasetPtr
WARP、SIMQ含 MultiVectors、MultiVectorDim 的单行 DatasetPtr含 MultiVectors、MultiVectorDim 的 DatasetPtr

SINDI immutable 存储也支持单 ID 和批量距离,包括反序列化后的索引。WARP/SIMQ 使用与搜索距离后端一致的多向量聚合,不经过粗排候选选择。普通 float 指针不能表示稀疏或多向量 query。

N 行 query、每行 count 个候选 ID 时,必须按行优先提供 N × count 个 ID(ids[q * count + j]),不会隐式广播一份候选列表。新代码推荐使用 CalcDistancesById;CalDistanceById 保留为兼容别名。

缺失 label 返回 -1,但有效内积距离也可能为负数(包括 -1)。top-k 根据 label 是否有效判断排序优先级,而不是根据距离的符号或数值判断。

IVF PQFS 支持单 ID 和批量距离,复用整桶扫描所用的打包编码查表。单 ID 查询会读取所在的 32 向量数据包并取出对应结果,因此工作量可能高于标量量化的单 ID 查询,磁盘存储下尤其如此。

PQFS 可用于 IVF 基础桶编码,但不能用于 flat 重排编码,因为该存储不维护 PQFS 数据包。非法 PQFS 重排配置会在创建索引时被拒绝;请选用 FP32 等受支持的精确量化类型。

返回值含义

  • 单 ID 重载返回 float 距离值。
  • topk == -1 时,裸指针批量重载返回一行 count 个距离;DatasetPtr 重载返回 NumElements() 行、每行 count 个距离。两者都保持输入顺序,且不返回 ID。
  • topk > 0 时,批量重载每个 query 返回按距离升序排列的最小 min(topk, count) 个 距离,GetIds() 包含对应 ID。缺失 ID(距离为 -1)排在所有有效距离之后,不受有效距离的符号影响,仅在有效 ID 不足时才出现在结果中。
  • 距离的语义由建索引时设置的 metric_type(IP / L2 / cosine)决定,参见 度量语义。

基本用法

#include <vsag/vsag.h>

// 1. 构建 HGraph 索引
auto index = engine.CreateIndex("hgraph", hgraph_build_parameters).value();
index->Build(base);

// 2. 单 ID 距离
auto d = index->CalcDistanceById(query_vector.data(), /*id=*/42);
if (d.has_value()) {
    std::cout << "distance to id 42 = " << d.value() << std::endl;
}

// 3. 批量 ID 距离
std::vector<int64_t> ids = { 1, 2, 3, 4, 5 };
auto result = index->CalcDistancesById(query_vector.data(), ids.data(), ids.size());
if (result.has_value()) {
    const float* dists = result.value()->GetDistances();
    for (size_t i = 0; i < ids.size(); ++i) {
        if (dists[i] == -1.0f) {
            std::cout << ids[i] << " -> 无效 ID" << std::endl;
        } else {
            std::cout << ids[i] << " -> " << dists[i] << std::endl;
        }
    }
}

多查询

当索引公布 SUPPORT_BATCH_CALC_DISTANCE_BY_ID 能力时,DatasetPtr 批量重载可以一次接收 多个 query。候选 ID 和输出都采用 row-major 布局。对于 LazyHGraph,调用会委托给当前生效的 内部索引;即使 wrapper 公布了该能力,实际支持也可能在 phase 切换后改变,因此仍需处理返回错误:

// 两个稠密 query,每个 query 三个候选 ID。
auto queries = vsag::Dataset::Make()
                   ->NumElements(2)
                   ->Dim(dim)
                   ->Float32Vectors(query_vectors.data())
                   ->Owner(false);
std::vector<int64_t> candidate_ids = {
    10, 11, 12,  // query 0 的候选
    20, 21, 22   // query 1 的候选
};

if (index->CheckFeature(vsag::SUPPORT_BATCH_CALC_DISTANCE_BY_ID)) {
    auto result = index->CalcDistancesById(queries, candidate_ids.data(), 3, true, /*topk=*/2);
    if (result.has_value()) {
        auto batch = result.value();
        // batch 的 NumElements() == 2,Dim() == 2。
        for (int64_t q = 0; q < batch->GetNumElements(); ++q) {
            for (int64_t j = 0; j < batch->GetDim(); ++j) {
                const int64_t offset = q * batch->GetDim() + j;
                std::cout << batch->GetIds()[offset] << ": "
                          << batch->GetDistances()[offset] << '\n';
            }
        }
    }
}

topk == -1 时,GetDim() 等于 count,位置 q * count + j 对应输入 ID ids[q * count + j]。topk 为正数时,行跨度为 GetDim();每行先放最近的有效候选, 只有有效 ID 少于 topk 时才会在行尾保留无效 ID。

可运行的完整示例见 examples/cpp/306_feature_calculate_distance_by_id.cpp。

稀疏向量

对于 SINDI 等稀疏向量索引,const float* 重载不适用。需要通过 SparseVectors(...) 把查询 封装为 DatasetPtr,并调用 DatasetPtr 重载:

auto query = vsag::Dataset::Make();
query->NumElements(1)->SparseVectors(&sparse_query)->Owner(false);

auto d = index->CalcDistanceById(query, /*id=*/42);

支持矩阵

索引类型单 ID 稠密重载(const float*)单 ID DatasetPtr多查询 DatasetPtr 批量重载说明
hgraph支持支持公布能力时支持遵循 calculate_precise_distance。
ivf支持支持公布能力时支持可用性取决于是否保留精确存储。
brute_force支持支持公布能力时支持仅稠密单向量索引。
pyramid支持支持支持
lazy_hgraph支持不支持取决于当前生效的内部索引DatasetPtr 批量调用会委托给当前 BruteForce/HGraph;可用性可能在 phase 切换后改变。
sindi不支持支持支持仅稀疏向量。

对于未实现某重载的索引,调用会返回 UNSUPPORTED_INDEX_OPERATION 错误。

注意事项

  • 稠密重载中,查询向量的维度必须与索引维度一致。
  • 批量重载存在默认实现:循环调用单 ID 接口;部分索引会重写以做批量优化。
  • 与 VSAG 其他只读接口一样,这些方法可以与 KnnSearch 等只读操作并发调用。