Pyramid
Pyramid 是 VSAG 的 层级路径分区 图索引。每条向量可以附带零个、一个或多个路径字符串
(例如 "a/d/f"),Pyramid 会按这些路径树为每个节点构建一个子图;查询时提供一个或
多个路径前缀,检索即被限定在相应的子树内。
这种设计非常适合多租户部署、标签分区的物料库,或者任何“一个逻辑索引服务多个群体、 群体之间不允许结果交叉”的场景。
- 源码:
src/algorithm/pyramid.{h,cpp}、src/algorithm/pyramid_zparameters.{h,cpp} - 示例(单层级):
examples/cpp/107_index_pyramid.cpp - 示例(多层级):
examples/cpp/112_index_pyramid_multi_hierarchy.cpp
工作原理
- 路径树。 每条向量在 ID 之外还可以携带多条独立路径,分隔符为
/(例如"tenant_a/lang_en/topic_news")。Pyramid 会为构建期间出现过的每个路径前缀 维护一个子索引。即使向量属于多条路径,向量数据和 ID 也只保存一次。 - 按层构建子图。 默认情况下每一层都会独立构建一张近邻图。可以用
no_build_levels跳过那些太小或太粗、不适合构图的层级——这些层级仍作为透传容器存在,但检索会退化为 线性扫描。 - 图的构建。 每个子图与 HGraph 采用同一套机制:
nsw插入或odescent,并通过graph_iter_turn、neighbor_sample_rate、alpha控制构图剪枝。底层向量按base_quantization_type存储;启用精排时另外保留一份高精度副本。 - 检索。 查询向量同样要附带路径。搜索会顺路径树向下走到最具体匹配查询路径的子图,
然后在该子图内执行图检索(
ef_search;中间层由subindex_ef_search控制)。
快速开始
#include <vsag/vsag.h>
std::string params = R"({
"dtype": "float32",
"metric_type": "l2",
"dim": 128,
"index_param": {
"base_quantization_type": "sq8",
"max_degree": 32,
"alpha": 1.2,
"graph_type": "odescent",
"graph_iter_turn": 15,
"neighbor_sample_rate": 0.2,
"no_build_levels": [0, 1],
"use_reorder": true,
"build_thread_count": 16
}
})";
auto index = vsag::Factory::CreateIndex("pyramid", params).value();
// 构建时为每条向量提供路径。
auto base = vsag::Dataset::Make();
base->NumElements(n)
->Dim(128)
->Ids(ids)
->Paths(paths) // std::string* 长度为 n,例如 "a/d/f"
->Float32Vectors(data)
->Owner(false);
index->Build(base);
// 按路径前缀执行检索。
std::string query_path = "a/d";
auto query = vsag::Dataset::Make();
query->NumElements(1)
->Dim(128)
->Float32Vectors(q)
->Paths(&query_path)
->Owner(false);
auto result = index->KnnSearch(
query, /*topk=*/10,
R"({"pyramid": {"ef_search": 100}})").value();
支持的输入数据类型
当前公开的 Build、Add 和检索路径接收通过 Dataset::Float32Vectors 提供的 FP32 向量,dtype 应设为 "float32"。base_quantization_type 选择的是内部编码和存储,本身不会使 API 接受 FP16、BF16 或 INT8 输入。
构建参数
构建参数放在 index_param 下。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
base_quantization_type | string | — | 底层量化类型(fp32、fp16、bf16、sq8、sq4、sq8_uniform、sq4_uniform、pq、pqfs、rabitq、tq)。各量化器细节见量化章节。 |
tq_chain | string | — | base_quantization_type 为 tq 时使用的变换链,例如 "mrle, rabitq"。 |
mrle_dim | int | 0 | MRLE 保留的前缀维度;0 表示保持输入维度。 |
max_degree | int | 64 | 子图内节点的最大出度 |
graph_type | string | "nsw" | nsw、odescent 或 pipnn。PiPNN 批量构建每棵 hierarchy 中所有实际构建的图,包括 multi_layer 根节点的 routing 层。 |
graph_storage_type | string | "flat" | multi_layer 根节点的底图存储:flat 偏向构建和检索速度,compressed 减少图内存。压缩存储要求 max_degree <= 255。单层根图、路由图和子图仍使用 Sparse。 |
ef_construction | int | 400 | nsw 构图时的候选集大小 |
alpha | float | 1.2 | 构图剪枝系数;PiPNN 要求有限且不小于 1.0 |
graph_iter_turn | int | — | ODescent 迭代轮数(graph_type: "odescent" 时生效) |
neighbor_sample_rate | float | — | ODescent 的邻居采样比率 |
pipnn_max_leaf_size | int | 1024 | PiPNN 分区叶子的最大点数 |
pipnn_min_leaf_size | int | 64 | PiPNN 合并过小叶子时的目标规模 |
pipnn_leader_sample_rate | float | 0.005 | 分区 leader 的采样比率;每个分区使用 2 到 1000 个 leader,且不超过分区点数 |
pipnn_fanout | int[] | [10, 2] | PiPNN 连续分区层级中一个点加入的最近 leader 分区数 |
pipnn_leaf_neighbor_count | int | 5 | 每个点在每个叶子中贡献的最近候选数,是 PiPNN 主要的质量/构建工作量旋钮;过小叶子至少使用 4 |
pipnn_hash_plane_count | int | 12 | 方向 hash 位数,范围 [1, 15];max_degree 不能超过 2 的该次幂 |
pipnn_reservoir_size | int | 64 | 最终剪枝前每个点保留的候选槽数;实际容量至少为 max_degree |
no_build_levels | int[] | [] | 跳过构图的层级(从根节点开始的 0-based 下标) |
use_reorder | bool | false | 是否保留高精度副本用于精排 |
precise_quantization_type | string | "fp32" | 精排使用的量化类型。与 rabitq_bits_per_dim_precise 配合设为 "rabitq" 时,可启用从 base storage 重排的 RaBitQ x+y split。 |
rabitq_bits_per_dim_base | int | 1 | RaBitQ 底库存储码的每维位数。在 x+y split 模式下表示 x,即图遍历使用的 filter bits;范围为 [1, 8]。 |
rabitq_bits_per_dim_precise | int | 未设置 | RaBitQ split 的 y bits。和 base_quantization_type: "rabitq"、precise_quantization_type: "rabitq" 一起设置时,Pyramid 使用 split storage;rabitq_bits_per_dim_base 仍表示 x,且 x + y <= 8。 |
fast_encode_rabitq | bool | true | 对 RaBitQ 底层或精排存储使用多 bit 快速编码器;设为 false 使用精确编码器 |
fast_encode_rabitq_rounds | int | 6 | RaBitQ 快速编码的微调轮数,范围 [1, 32] |
base_io_type / precise_io_type | string | "block_memory_io" | 底层与精排存储后端;以 liburing 构建时可用 uring_io |
base_file_path / precise_file_path | string | — | buffer_io、async_io、uring_io、mmap_io 等磁盘存储必须设置 |
store_raw_vector | bool | false | 保留 FP32 原始向量,用于 GetRawVectorByIds 和精确的按 ID 距离计算 |
store_paths | bool | false | 顶层开关;保留传给 Build 和 Add 的原始路径,使 GetDataByIdsWithFlag 在选择 DATA_FLAG_PATH 时可以返回它们。该开关对所有已配置的 hierarchy 生效,不支持按 hierarchy 覆盖 |
index_min_size | int | 0 | 子索引的最小规模;小于该值的分区会退化为线性扫描 |
root_graph_type | string | "single_layer" | 根图结构:single_layer 保留原有稀疏底图;multi_layer 使用预分配的 Flat 或 Compressed 底图、类似 HGraph 的稀疏路由层以及联合构图流程。multi_layer 支持 graph_type: "nsw" 和 "pipnn"。no_build_levels 禁用第 0 层时不要显式指定此选项。 |
support_duplicate | bool | false | 根据构建编码的 CompareVectors 语义对编码等价、外部标签不同的向量分组;外部标签仍须唯一。 |
build_thread_count | int | 1 | 构建阶段并发线程数 |
hierarchies | array | [] | 命名层级定义。每个元素可以是字符串(继承全部顶层参数)或对象(含 name 及可选覆盖参数:max_degree、ef_construction、alpha、no_build_levels、index_min_size、root_graph_type)。设置后激活多层级模式,每个层级维护独立的路径树。 |
PiPNN 支持 dtype: "float32",并支持 metric_type: "l2"、"ip" 或 "cosine" 的 Pyramid
构建。它批量构建每棵 hierarchy 中所有实际构建的图;设置
root_graph_type: "multi_layer" 时,也包括所有 root routing 层。搜索、增量 Add、删除、
重复处理、精排和序列化保持标准 Pyramid 行为;no_build_levels 中列出的层级仍会跳过构图。
pipnn_* 参数在整个 Pyramid 索引中共享;hierarchy 对象仍可分别覆盖
max_degree 和 alpha,PiPNN 构建该 hierarchy 时会使用这些覆盖值。
ef_construction 不调节 PiPNN。
RaBitQ split 配置
需要同时设置以下五个参数,才能启用 RaBitQ x+y split 存储和精排:
{
"use_reorder": true,
"base_quantization_type": "rabitq",
"precise_quantization_type": "rabitq",
"rabitq_bits_per_dim_base": 3,
"rabitq_bits_per_dim_precise": 5
}
Pyramid 使用 split code 的 code-code 距离完成增量 FLAT→GRAPH 晋升,因此默认不保留
内部 FP32 副本。需要访问原始向量或完整 Analyzer 指标时,可在构建时将
store_raw_vector 设置为 true。
MRLE 与 split RaBitQ
对于使用 Matryoshka Representation Learning 训练的向量,Pyramid 可以先截断维度,再编码 为 split RaBitQ:
{
"base_quantization_type": "tq",
"tq_chain": "mrle, rabitq",
"mrle_dim": 1280,
"precise_quantization_type": "rabitq",
"rabitq_bits_per_dim_base": 3,
"rabitq_bits_per_dim_precise": 5,
"use_reorder": true
}
该配置使用 3-bit filter planes 构图和搜索、5-bit supplement planes 精排;两部分编码的是
同一个截断后向量。Pyramid 使用 split code-code 距离完成构图提升,不保留原始 FP32
向量;除非构建时启用 store_raw_vector,否则依赖可解码向量的 Analyzer 指标会被标记为
不可用。如果 embedding 模型没有针对前缀维度训练,截断可能显著降低召回率。
构建缓存
ExportCache 保存每个层级、每个节点的 NSW 图种子,ImportCache 可在后续 Build 中复用。缓存使用索引缓存 payload 格式,而非 streaming 索引序列化格式。需要复用缓存的索引通过 footer 序列化前应设置 persist_source_id: true,两次构建中的每个向量都须提供唯一的 Dataset::SourceID 和外部标签。缓存预热仅适用于 graph_type: "nsw";ODescent、缺少 source ID、source ID 或标签重复时回退到普通冷构建(保留普通构建拒绝重复标签的行为)。ef_construction 不作为准入条件。输入 source ID 至少 80% 须与缓存重合。未命中成员按正常流程构建;single-layer root 命中成员执行出边修复,multi-layer 节点重建未缓存的 routing overlay。
启用 support_duplicate: true 时,Pyramid 显式保存每个图的组成员及实际邻接行代表,而不是最小标签或内部 ID。预热构建重映射 SourceID,并验证当前图成员关系和构建编码等价性。代表缺失或迁移、组等价性变化、组缓存行不可用时,仅将受影响的组转为 miss,其他合法组仍可复用。仅删除别名不会使其组失效。编码新合并的组保守重建。按哈希组织的有序索引通过比较完整构建编码字节处理冲突,不将哈希相同视为向量相等。每图至少 80% 的成员关系须保持可复用;缺少组元数据(含旧缓存)或复用比例不足时,整个图回退冷构建。缺少有效缓存邻接的全相同向量图仍冷构建。先恢复代表邻接再关联别名,过滤指向失效组的边;别名只计入成员关系,不增加恢复边或路由条目。
并发插入使用临时的图级精确编码代表登记表,仅在相应的准入分片锁内完成代表发布。登记表引用已有构建编码,不复制完整向量;加载索引后在需要时重建。启用 duplicate 的 NSW Build 在冷构建和预热修整结束后,既检查从每个底层图入口出发的正向可达性,也检查各代表返回该入口的可达性。必要时补充有界、按几何距离选择的连接,同时保护正向遍历树边并保持最大度数。这两项检查共同确保图内物理代表之间的强连通性,也适用于路由下降为不同查询选择的底层入口。返回路径检查采用 CSR 反向邻接结构,对于 R 个代表和 E 条边需要 O(R + E) 临时内存,逐图处理并释放。这是 Build 阶段的不变量,不保证任意后续 Add 操作后的连通性。有限搜索预算的近似查询仍可能漏召回整个向量组,不承诺通用的召回率或构建加速。 反序列化后,稀疏图的显式空邻接行仍保留物理代表身份。旧版稠密或压缩存储可能丢失孤立空重复组的历史代表身份;准入初始化优先保留可由邻接行、入边或路由观察到的代表,然后为仍不可观察的已持久化重复组规范化一个成员作为代表,而不改变组内标签。这不等于恢复历史代表身份,也不保证任意增量图的连通性。此一次性旧索引准入扫描需要 O(H + A + E) 遍历工作和 O(H) 访问标记内存,其中 H 为已存储 ID 的高水位,A 为访问的重复成员数,E 为遍历的边数;精确编码有序索引操作另计通常的比较开销。
带重复组的缓存使用 Pyramid 专用版本化格式;新读取器兼容旧普通缓存,旧读取器不能读取新重复组 payload。不含组元数据的普通缓存保留旧格式。HGraph 缓存及索引序列化格式不变。GetStats() 区分向量、图成员关系的命中/未命中计数和恢复边数;图回退计为未命中,不计为已恢复组。
检索参数
检索参数放在 pyramid 子对象下:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
ef_search | int | 100 | 叶子层子图检索的候选集大小 |
factor | float | 未设置 | KNN 重排候选倍率。值 <= 1 时不增加限制;值大于 1 时,Pyramid 保持各子图原有搜索行为,先合并子图结果,再将至多 min(max(ef_search, topk), floor(topk * factor)) 个主图候选送入重排。与 HGraph 一致,RaBitQ lower-bound 安全候选可在该限制后额外并入,reorder_candidate_count 记录实际合并数量。参数必须为有限正数;范围检索或关闭重排时不生效。 |
hops_limit | int | 不限 | 根节点底图及每个非根 GRAPH 的逐图 KNN 跳数上限;不大于 ef_search 时忽略。根节点的稀疏路由层不受限制,FLAT 扫描与范围检索不受影响。 |
subindex_ef_search | int | 50 | 沿路径向下遍历中间子图时的候选集大小 |
hierarchies | string[] | [] | 指定检索哪个层级。空数组表示使用默认(匿名)层级。 |
hierarchy_op | string | "single" | 多层级结果合并方式:single(检索单个层级)、union、intersection。注意: union 和 intersection 尚未实现——设置后 KnnSearch/RangeSearch 会返回错误。 |
rabitq_error_rate | float | 1.9 | 本次搜索使用的正数 lower-bound 误差倍率。默认值 1.9 较大;值越大,精度越高,但搜索速度越慢。 |
auto result = index->KnnSearch(
query, topk,
R"({"pyramid": {"ef_search": 200, "subindex_ef_search": 80}})").value();
多层级支持 (Multi-Hierarchy)
一个 Pyramid 索引可以同时维护多棵独立的路径树,每棵树由名称标识(如
"site"、"category")。所有层级共享向量 ID 和数据——只有路径不同。每个层级可以
选择性地覆盖图构建参数。
当同一组向量需要沿多个维度同时分区时,这个特性非常有用。例如,一个电商平台可能
需要按站点(site-a/lang-en)和按品类(electronics/phones)同时分区,
检索时可以独立选择任一层级。
构建配置
在 index_param 中添加 hierarchies 数组。每个元素可以是:
- 字符串(继承全部顶层参数):
"site" - 对象(含
name和可选的参数覆盖):{"name": "category", "max_degree": 64, "no_build_levels": [0]}
可按层级覆盖的参数:max_degree、ef_construction、alpha、no_build_levels、
index_min_size、root_graph_type。
root_graph_type: "multi_layer" 只改变所选层级的根节点。底图使用顶层
graph_storage_type:默认使用 Flat,也可使用 Compressed,以构建和检索速度换取更低的图
内存;路由图和子图仍使用 Sparse。稀疏路由图先选择更好的入口点,再进入底图检索。NSW Build
与增量 Add 使用类似 HGraph 的 route 与 bottom 联合插入流程;PiPNN Build 则批量构建这两类图。
参数校验会拒绝 multi_layer 与 odescent 的组合。存在独立 precise
storage 时,底图和路由图的边统一使用 precise codes 构建,否则使用 base codes;查询遍历
继续使用 base codes,最终精排使用配置的 reorder source。
{
"dtype": "float32",
"metric_type": "l2",
"dim": 128,
"index_param": {
"base_quantization_type": "sq8",
"max_degree": 32,
"alpha": 1.2,
"graph_type": "odescent",
"graph_iter_turn": 15,
"neighbor_sample_rate": 0.2,
"no_build_levels": [0, 1],
"use_reorder": true,
"build_thread_count": 16,
"hierarchies": [
"site",
{"name": "category", "max_degree": 64, "no_build_levels": [0]}
]
}
}
命名层级的 Dataset API
使用重载方法 Paths(hierarchy_name, paths) 为每个层级设置路径。所有层级共享同一份
Ids() 和 Float32Vectors()。指针重载保留原有的“每条向量一条路径”形式:
auto base = vsag::Dataset::Make();
base->NumElements(n)
->Dim(128)
->Ids(ids)
->Float32Vectors(data)
->Paths("site", site_paths) // std::string* 长度为 n
->Paths("category", category_paths) // 第二个层级的独立路径
->Owner(false);
index->Build(base);
按 ID 取回路径
将顶层构建参数 store_paths 设为 true 后,Pyramid 会保留原始路径,供按 ID
取回。在 GetDataByIdsWithFlag 中选择 DATA_FLAG_PATH 后,返回路径的顺序与请求 ID
的顺序一致。默认匿名 hierarchy 使用 GetPaths(),命名 hierarchy 使用
GetPaths(hierarchy_name):
int64_t requested_ids[] = {product_id_b, product_id_a};
auto data = index->GetDataByIdsWithFlag(
requested_ids, 2, DATA_FLAG_ID | DATA_FLAG_PATH).value();
const std::string* site_paths = data->GetPaths("site");
const std::string* category_paths = data->GetPaths("category");
GetDataByIds 以及未选择 DATA_FLAG_PATH 的 GetDataByIdsWithFlag 都不会附带路径
数组。store_paths 为 false 时选择 DATA_FLAG_PATH 会返回参数错误。开启路径存储
后,只有当所有请求 ID 在某个 hierarchy 中都恰好有一条已记录路径时,这些旧 getter
才会返回该 hierarchy。只要其中一个 ID 没有已记录路径或有多条路径,对应 getter 就返回
nullptr;其他路径完整的 hierarchy 仍会正常返回。单 hierarchy 模式下,
GetPaths() 遵循相同规则。
使用结构化重载可以统一取回旧表示或新表示中的全部路径:
std::vector<std::vector<std::string>> tag_paths;
if (data->GetPaths("tag", tag_paths)) {
// tag_paths[i] 包含 requested_ids[i] 的全部路径。
}
若要让同一条向量在同一个层级中属于多条独立路径,请传入嵌套 vector。外层 vector 与 dataset 元素一一对应,每个内层 vector 可以包含一条或多条路径:
std::vector<std::vector<std::string>> tag_paths = {
{"technology", "military"}, // 向量 0 同时属于两条路径
{"sports"}, // 向量 1 属于一条路径
{""}, // 向量 2 属于层级根节点
};
auto base = vsag::Dataset::Make();
base->NumElements(3)
->Dim(128)
->Ids(ids)
->Float32Vectors(data)
->Paths("tag", std::move(tag_paths))
->Paths("site", site_paths) // 可与旧指针形式共存
->Owner(false);
index->Build(base);
空的内层 vector 非法;只包含 "" 的内层 vector 表示挂到根节点。
重复路径和共享前缀在同一个树节点内只插入一次。Add() 支持相同形式,序列化会保留
最终形成的全部路径归属。
检索指定层级
通过检索参数中的 "hierarchies" 指定要检索的层级。查询 Dataset 也需要在对应的
层级名称上设置路径:
auto query = vsag::Dataset::Make();
query->NumElements(1)
->Dim(128)
->Float32Vectors(q)
->Paths("site", &query_path) // 指向 "site" 层级
->Owner(false);
auto result = index->KnnSearch(
query, /*topk=*/10,
R"({"pyramid": {"ef_search": 100, "hierarchies": ["site"]}})").value();
旧查询形式用 | 表示多条候选路径的并集,例如 "technology|military";同一向量即使
同时可由两条路径到达,在结果中也只出现一次。旧字符串形式中的 | 是保留分隔符,
目前没有转义语法。结构化查询不依赖这个分隔约定,也允许路径段中包含字面量 |:
auto query = vsag::Dataset::Make();
query->NumElements(1)
->Dim(128)
->Float32Vectors(q)
->Paths("tag", std::vector<std::vector<std::string>>{{"technology", "military"}})
->Owner(false);
增量插入 (Add)
Add() 的用法与 Build() 一致——可以提供单路径或结构化命名路径,每条通过校验的向量
都会插入到所有匹配路径:
auto new_data = vsag::Dataset::Make();
new_data->NumElements(count)
->Dim(128)
->Ids(new_ids)
->Float32Vectors(new_vectors)
->Paths("site", new_site_paths)
->Paths("category", new_cat_paths);
index->Add(new_data);
范围检索 (RangeSearch)
RangeSearch 同样支持通过检索参数选择层级:
auto result = index->RangeSearch(
query, /*radius=*/20.0f,
R"({"pyramid": {"ef_search": 100, "hierarchies": ["category"]}})").value();
序列化与反序列化
多 hierarchy 索引的序列化和反序列化完全透明。序列化格式包含所有 hierarchy
名称及其图结构。当 store_paths: true 时,常规序列化和 streaming 序列化还会持久化
已保留的原始路径,因此反序列化后仍可通过 GetDataByIdsWithFlag 获取。使用默认值
false 时,图 hierarchy 会被持久化,但不会保留按 ID 索引的原始路径:
// 序列化
auto binary_set = index->Serialize().value();
// 反序列化到新索引(必须使用相同的构建参数)
auto new_index = vsag::Factory::CreateIndex("pyramid", build_params).value();
new_index->Deserialize(binary_set);
何时选择 Pyramid
- 多租户服务:每个租户只能看到自己分区的结果,且希望避免为每个租户单独维护一份索引。
- 带有层级标签的物料库(语言 / 地域 / 品类),查询永远限定在某个已知的前缀下。
- 小分区非常多的负载:可以用
no_build_levels与index_min_size跳过那些小到不值得 构图的分区。
如果不需要按路径限定查询范围,HGraph 更简洁,性能通常也更高。
可以通过索引分析检查 Pyramid 的树结构、子索引质量、
GetStats() 输出的 base 采样召回率和重复比例。每个 hierarchy 的 root_graphs 会报告
root_graph_type、bottom_graph_storage_type、bottom_graph_node_count、
bottom_graph_size、route_graph_count、route_node_counts 和 route_graph_size。
AnalyzeIndexBySearch 还会输出按路径限定的
query 召回率、距离、耗时,以及开启 reorder 时的量化指标。query 数据集必须包含与
KnnSearch 相同的默认或命名 hierarchy 路径。对于批量数据集,外层路径集合的每一行对应
一条 query:旧重载每行提供一个路径字符串(并用 | 表示并集),结构化重载则可为每行提供
一条或多条原子路径,并允许路径中包含字面量 |。analyze_index 工具当前无法从
dense query 文件加载 hierarchy 路径,因此按路径执行动态分析时请使用 C++ 接口。
标记删除
Pyramid 支持 RemoveMode::MARK_REMOVE。调用 Remove(ids)(默认模式)会为给定的 id
打上删除标记:它们会从后续检索结果中排除,GetNumElements() 相应减少,
GetNumberRemoved() 返回累计删除数量。删除不存在或已删除的 id 不会有任何效果。
RemoveMode::FORCE_REMOVE 不支持,调用会返回错误。
被标记删除的向量在索引重建前仍占用内存,空间不会被物理回收。
标签查询与持久化
标签 0 是合法标签。预留容量不会创建标签:没有向量使用该标签时,CheckIdExist(0)
返回 false,单 ID 的 CalcDistanceById 对不存在或已标记删除的标签返回错误。
Pyramid 仅写入已填充的标签槽位,继续使用现有的带长度前缀的向量格式。 读取包含未使用槽位的旧数据时,根据已存储的向量数量移除未使用的尾部并重建标签映射。 可寻址格式和流式格式均支持此恢复过程。旧版读取器仍能解析新写入的数据, 但不会因此获得修复后的查询行为,因此读取端也需要升级。