Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion docs/docs/en/src/advanced/new_serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,16 @@ IVF writes these streaming blocks in order:
| `ivf_bucket` | bucket datacell payloads for inverted lists | yes |
| `ivf_partition_strategy` | partition strategy state, such as trained centroids | yes |
| `label_table` | external labels and label remap | yes |
| `high_precision_codes` | reorder codes when IVF reorder is enabled | conditional |
| `high_precision_codes` | flat reorder codes when IVF reorder uses the `flat` layout | conditional |
| `ivf_precise_bucket` | bucket-aligned reorder codes when IVF reorder uses the `bucket` layout | conditional |
| `attribute_filter` | optional attribute filter index | conditional |

`DeserializeStreaming` restores the full in-memory IVF index. `Index::Load` can create the IVF
index directly from streaming metadata and currently loads all emitted IVF blocks into memory.
The two precise-code blocks are mutually exclusive and are selected by
`precise_codes_layout`. For file-backed bucket-aligned precise codes, create an IVF destination
with an independent `precise_file_path` and use `DeserializeStreaming`; static `Index::Load` is
rejected until it can accept an independent target path.

## SINDI Blocks

Expand Down
12 changes: 12 additions & 0 deletions docs/docs/en/src/indexes/ivf.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,22 @@ Build-time parameters live under `index_param`. See
| `fast_encode_rabitq_rounds` | int | `6` | CAQ adjustment rounds; allowed range is `[1, 32]` |
| `use_reorder` | bool | `false` | Keep a high-precision copy and re-rank after the coarse scan |
| `precise_quantization_type` | string | `"fp32"` | Quantizer used for reordering (with `use_reorder: true`) |
| `precise_codes_layout` | string | `"flat"` | Storage layout for precise codes: `"flat"` keeps the legacy one-code-per-vector layout; `"bucket"` stores the precise code in the same bucket and offset as its basic posting |
| `base_io_type` | string | `"memory_io"` | Storage backend for coarse codes; supports `uring_io` when built with liburing |
| `precise_io_type` | string | `"block_memory_io"` | Storage backend for precise codes (`memory_io`, `block_memory_io`, `mmap_io`, `buffer_io`, `async_io`, `uring_io`, `reader_io`) |
| `precise_file_path` | string | `""` | File path when the precise IO type is disk-backed |

`precise_codes_layout: "bucket"` requires `use_reorder: true`. It supports
`memory_io`, `block_memory_io`, `buffer_io`, `async_io`, and `uring_io`
(when io_uring is available); `mmap_io`, `reader_io`, and `pqfs` precise
quantization are not supported. The bucket layout currently requires
`buckets_per_data: 1`; configurations that assign one vector to multiple buckets are rejected.

For file-backed bucket-aligned precise codes, `Clone`, `ExportModel`, `Merge`, and static
`Index::Load` are rejected because those operations cannot yet assign an independent target
file. To restore a streaming index on disk, create the destination IVF with a different
`precise_file_path` and call `DeserializeStreaming`.

A rule of thumb for `buckets_count` is `sqrt(N)` to `4 * sqrt(N)` where `N` is the
corpus size.

Expand Down
8 changes: 6 additions & 2 deletions docs/docs/zh/src/advanced/new_serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,15 @@ IVF 按顺序写入以下 streaming blocks:
| `ivf_bucket` | 倒排列表使用的 bucket datacell 数据 | 是 |
| `ivf_partition_strategy` | partition strategy 状态,例如已训练的中心点 | 是 |
| `label_table` | 外部 label 和 label remap | 是 |
| `high_precision_codes` | IVF reorder 开启时的 reorder codes | 条件必需 |
| `high_precision_codes` | IVF reorder 使用 `flat` 布局时的精排 codes | 条件必需 |
| `ivf_precise_bucket` | IVF reorder 使用 `bucket` 布局时按桶对齐的精排 codes | 条件必需 |
| `attribute_filter` | 开启属性过滤时写入的可选属性过滤索引 | 条件必需 |

`DeserializeStreaming` 会恢复完整的内存 IVF 索引。`Index::Load` 可以直接从 streaming metadata
创建 IVF 索引对象,当前会把写出的 IVF blocks 都加载到内存中。
创建 IVF 索引对象,当前会把写出的 IVF blocks 都加载到内存中。两种精排 codes block 互斥,
由 `precise_codes_layout` 选择。对于文件型 bucket 精排 codes,请使用独立的
`precise_file_path` 创建 IVF 目标对象并调用 `DeserializeStreaming`;在静态 `Index::Load`
能够接收独立目标路径之前,该组合会被拒绝。

## SINDI Blocks

Expand Down
11 changes: 11 additions & 0 deletions docs/docs/zh/src/indexes/ivf.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,21 @@ auto result = index->KnnSearch(
| `fast_encode_rabitq_rounds` | int | `6` | CAQ 微调轮数,允许范围 `[1, 32]` |
| `use_reorder` | bool | `false` | 是否保留高精度副本用于精排 |
| `precise_quantization_type` | string | `"fp32"` | 精排量化类型(`use_reorder: true` 时使用) |
| `precise_codes_layout` | string | `"flat"` | 精排 codes 的存储布局:`"flat"` 保持旧的一向量一码布局;`"bucket"` 在 basic posting 的相同 bucket 和 offset 保存高精度 code |
| `base_io_type` | string | `"memory_io"` | 粗排向量的存储后端;以 liburing 构建时支持 `uring_io` |
| `precise_io_type` | string | `"block_memory_io"` | 精排向量的存储后端(`memory_io`、`block_memory_io`、`mmap_io`、`buffer_io`、`async_io`、`uring_io`、`reader_io`) |
| `precise_file_path` | string | `""` | 当精排 IO 为磁盘后端时的文件路径 |

`precise_codes_layout: "bucket"` 要求 `use_reorder: true`,支持 `memory_io`、
`block_memory_io`、`buffer_io`、`async_io` 和 `uring_io`
(需要构建环境支持 io_uring),不支持 `mmap_io`、`reader_io` 和 `pqfs`
精排量化。bucket 布局当前要求 `buckets_per_data: 1`;一个向量分配到多个 bucket
的配置会被拒绝。

对于文件型 bucket 精排 codes,暂不支持 `Clone`、`ExportModel`、`Merge` 和静态
`Index::Load`,因为这些操作目前无法为目标索引指定独立文件。若需从 streaming 数据恢复磁盘索引,
请使用不同的 `precise_file_path` 创建目标 IVF,然后调用 `DeserializeStreaming`。

`buckets_count` 的经验值一般为 `sqrt(N)` ~ `4 * sqrt(N)`,其中 `N` 是语料规模。

## 检索参数
Expand Down
134 changes: 134 additions & 0 deletions examples/cpp/326_feature_ivf_precise_bucket.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright 2024-present the vsag project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <vsag/vsag.h>

#include <chrono>
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <random>
#include <string>
#include <utility>
#include <vector>

namespace {

constexpr int64_t NUM_VECTORS = 1000;
constexpr int64_t DIM = 32;

} // namespace

int
main() {
vsag::init();

/******************* Prepare Dataset *****************/
std::vector<int64_t> ids(NUM_VECTORS);
std::vector<float> vectors(NUM_VECTORS * DIM);
std::mt19937 rng(47);
std::uniform_real_distribution<float> distribution;
for (int64_t i = 0; i < NUM_VECTORS; ++i) {
ids[i] = i;
}
for (auto& value : vectors) {
value = distribution(rng);
}
auto base = vsag::Dataset::Make()
->NumElements(NUM_VECTORS)
->Dim(DIM)
->Ids(ids.data())
->Float32Vectors(vectors.data())
->Owner(false);

/******************* Create Disk-Backed Precise Codes *****************/
const auto unique_id = std::chrono::steady_clock::now().time_since_epoch().count();
const auto temp_dir = std::filesystem::temp_directory_path() /
("vsag-ivf-precise-bucket-" + std::to_string(unique_id));
std::filesystem::create_directories(temp_dir);
const auto precise_file_path = (temp_dir / "precise.codes").generic_string();

// precise_codes_layout is the only parameter introduced by bucket-aligned precise storage.
// The existing precise quantizer and IO settings are reused.
const auto build_params = std::string(R"(
{
"dtype": "float32",
"metric_type": "l2",
"dim": 32,
"index_param": {
"buckets_count": 16,
"buckets_per_data": 1,
"base_quantization_type": "sq8",
"partition_strategy_type": "ivf",
"ivf_train_type": "kmeans",
"use_reorder": true,
"precise_quantization_type": "fp32",
"precise_codes_layout": "bucket",
"precise_io_type": "buffer_io",
"precise_file_path": ")") +
precise_file_path + R"("
}
}
)";

auto create_result = vsag::Factory::CreateIndex("ivf", build_params);
if (not create_result.has_value()) {
std::cerr << "Create index failed: " << create_result.error().message << std::endl;
std::filesystem::remove_all(temp_dir);
return EXIT_FAILURE;
}
auto index = std::move(create_result.value());

auto build_result = index->Build(base);
if (not build_result.has_value()) {
std::cerr << "Build failed: " << build_result.error().message << std::endl;
index.reset();
std::filesystem::remove_all(temp_dir);
return EXIT_FAILURE;
}

/******************* Search With Precise Reordering *****************/
auto query = vsag::Dataset::Make()
->NumElements(1)
->Dim(DIM)
->Float32Vectors(vectors.data())
->Owner(false);
const auto search_params = R"(
{
"ivf": {
"scan_buckets_count": 16,
"factor": 4.0
}
}
)";
{
auto search_result = index->KnnSearch(query, 10, search_params);
if (not search_result.has_value()) {
std::cerr << "Search failed: " << search_result.error().message << std::endl;
index.reset();
std::filesystem::remove_all(temp_dir);
return EXIT_FAILURE;
}

std::cout << "Top-" << search_result.value()->GetDim() << " results:" << std::endl;
for (int64_t i = 0; i < search_result.value()->GetDim(); ++i) {
std::cout << " id=" << search_result.value()->GetIds()[i]
<< " dist=" << search_result.value()->GetDistances()[i] << std::endl;
}
}

index.reset();
std::filesystem::remove_all(temp_dir);
return EXIT_SUCCESS;
}
3 changes: 3 additions & 0 deletions examples/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,6 @@ target_link_libraries(406_feature_read_cache vsag)

add_executable(407_feature_ivf_read_cache 407_feature_ivf_read_cache.cpp)
target_link_libraries(407_feature_ivf_read_cache vsag)

add_executable(326_feature_ivf_precise_bucket 326_feature_ivf_precise_bucket.cpp)
target_link_libraries(326_feature_ivf_precise_bucket vsag)
1 change: 1 addition & 0 deletions examples/cpp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ together when the directory is listed:
| [`320_feature_extra_info.cpp`](320_feature_extra_info.cpp) | Attach per-vector extra info / payload. |
| [`322_feature_hgraph_brute_force_threshold.cpp`](322_feature_hgraph_brute_force_threshold.cpp) | HGraph search-time `brute_force_threshold`: automatically switch to an exact scan under highly selective filters. |
| [`324_feature_lazy_hgraph_extra_info.cpp`](324_feature_lazy_hgraph_extra_info.cpp) | LazyHGraph `extra_info` filtering across flat and graph phases. |
| [`326_feature_ivf_precise_bucket.cpp`](326_feature_ivf_precise_bucket.cpp) | Disk-backed IVF precise codes using the bucket-aligned layout. |

### Persistence (`4xx`)

Expand Down
3 changes: 3 additions & 0 deletions include/vsag/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ extern const char* const IVF_PRECISE_CACHE_TOTAL_SIZE;
extern const char* const IVF_PRECISE_QUANTIZATION_TYPE;
extern const char* const IVF_PRECISE_IO_TYPE;
extern const char* const IVF_PRECISE_FILE_PATH;
extern const char* const IVF_PRECISE_CODES_LAYOUT;
extern const char* const IVF_PRECISE_CODES_LAYOUT_FLAT;
extern const char* const IVF_PRECISE_CODES_LAYOUT_BUCKET;
extern const char* const USE_ATTRIBUTE_FILTER;
extern const char* const IVF_THREAD_COUNT;

Expand Down
Loading
Loading