blob: a24b0844f6c3c2564fb45848d61644f08a913e50 [file] [view]
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you 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.
-->
# Vector Index Storage Format
This document describes the v1 on-disk formats written by the Rust core
library. Version 1 is the first release format. Pre-release layouts are not part
of the compatibility contract.
## Compatibility Policy
- All multi-byte integers and `f32` values are little-endian.
- The unified reader dispatches by the first 4-byte magic value.
- Magic names below show the `u32` constants in human-readable big-endian form.
Because the fields are little-endian, the raw file bytes for those constants
appear in reverse ASCII order.
- Readers reject unknown magic values, unknown versions, unknown required flags,
non-zero reserved bytes, invalid section sizes, negative counts, and malformed
list payload metadata.
- Incompatible on-disk changes require a new format version. Version 1 readers
do not attempt to read future versions.
- Reserved bytes are written as zero and must be read back as zero. Future
extensions must use flags or a new format version rather than repurposing
non-zero reserved bytes within v1.
- Index files have no outer container, footer, checksum, compression envelope,
or schema registry. The complete file starts at byte offset 0 with one of the
headers below.
- File integrity, including length and checksum validation, is guaranteed by
the outer Paimon file/manifest layer rather than by an embedded index footer.
- Roaring row-id filters are a query-time API payload. They are not embedded in
any index file format.
## Common Encodings
### Delta-Varint IDs
IVF-PQ, IVF-FLAT, and IVF-RQ v1 sort each non-empty list by signed row id before writing.
The first id is stored as `base_id: i64`. The id stream then stores one unsigned
LEB128 varint per id, including the first id's zero delta. Each delta is computed
with wrapping unsigned subtraction from the previous signed id. Readers reject a
decoded sequence that is not monotonically non-decreasing in signed order.
### HNSW Graph Section
IVF-HNSW-FLAT and IVF-HNSW-SQ store one graph section per non-empty list. The
section starts with a fixed header, followed by a contiguous sequence of
unsigned LEB128 varints. Neighbor ids within each adjacency group are sorted by
local vector id and stored as unsigned deltas from the previous neighbor id,
with an initial previous id of `0`:
| Field | Count |
| --- | --- |
| `graph_magic` | 1 little-endian `u32`, `HWGR` (`0x48574752`) |
| `graph_version` | 1 little-endian `u32`, currently `1` |
| `graph_flags` | 1 little-endian `u32`; bit 0 delta-varint adjacency is required |
| `graph_count` | 1 varint |
| `entry_point` | 1 varint |
| `max_observed_level` | 1 varint |
| `level[node]` | `graph_count` varints |
| `degree[node][level]` followed by neighbor id deltas | one group for each node level |
Each node has levels `0..=level[node]`. A level-0 node may have at most `2 * m`
neighbors, and higher levels may have at most `m` neighbors.
## IVF-PQ v1
Magic: `IVPQ` (`0x49565051`). Version: `1`. Header size: 64 bytes.
| Offset | Size | Type | Field |
| ---: | ---: | --- | --- |
| 0 | 4 | `u32` | magic |
| 4 | 4 | `u32` | version |
| 8 | 4 | `i32` | dimension `d` |
| 12 | 4 | `i32` | IVF list count `nlist` |
| 16 | 4 | `i32` | PQ subquantizer count `m` |
| 20 | 4 | `i32` | centroid count per subquantizer `ksub` |
| 24 | 4 | `i32` | subvector dimension `dsub` |
| 28 | 4 | `u32` | metric (`0=L2`, `1=InnerProduct`, `2=Cosine`) |
| 32 | 8 | `i64` | total vector count |
| 40 | 4 | `u32` | flags |
| 44 | 20 | bytes | reserved |
Flags:
| Bit | Meaning |
| ---: | --- |
| 0 | OPQ rotation matrix is present |
| 1 | PQ codes are trained/stored by residual |
| 2 | delta-varint ids are used; required in v1 |
| 3 | PQ codes are transposed by subquantizer; required in v1 |
Sections after the header:
1. Optional OPQ rotation matrix: `d * d` `f32` values when flag bit 0 is set.
2. IVF coarse centroids: `nlist * d` `f32` values.
3. PQ centroids: `m * ksub * dsub` `f32` values.
4. Offset table: `nlist` entries of `(offset: i64, count: i32, id_bytes_len: i32)`.
5. List payloads.
For each non-empty list payload:
| Field | Type | Notes |
| --- | --- | --- |
| `base_id` | `i64` | first sorted row id |
| `id_bytes_len` | `i32` | byte length of encoded id stream |
| `id_bytes` | bytes | delta-varint ids |
| `codes` | bytes | transposed PQ codes |
For 8-bit PQ, each vector has `m` code bytes and the stored code layout is
`codes[sub][vector]`. For 4-bit PQ, each byte stores two subquantizers and the
stored layout is `codes[pair][vector]`.
## IVF-FLAT v1
Magic: `IVFL` (`0x4956464C`). Version: `1`. Header size: 64 bytes.
| Offset | Size | Type | Field |
| ---: | ---: | --- | --- |
| 0 | 4 | `u32` | magic |
| 4 | 4 | `u32` | version |
| 8 | 4 | `i32` | dimension `d` |
| 12 | 4 | `i32` | IVF list count `nlist` |
| 16 | 4 | `u32` | metric (`0=L2`, `1=InnerProduct`, `2=Cosine`) |
| 20 | 8 | `i64` | total vector count |
| 28 | 4 | `u32` | flags |
| 32 | 32 | bytes | reserved |
Flags:
| Bit | Meaning |
| ---: | --- |
| 0 | delta-varint ids are used; required in v1 |
Sections after the header:
1. IVF coarse centroids: `nlist * d` `f32` values.
2. Offset table: `nlist` entries of `(offset: i64, count: i32, id_bytes_len: i32)`.
3. List payloads.
For each non-empty list payload:
| Field | Type | Notes |
| --- | --- | --- |
| `base_id` | `i64` | first sorted row id |
| `id_bytes_len` | `i32` | byte length of encoded id stream |
| `id_bytes` | bytes | delta-varint ids |
| `vectors` | `count * d` `f32` | raw stored vectors |
## IVF-RQ v1
Magic: `IVRQ` (`0x49565251`). Version: `1`. Header size: 64 bytes.
| Offset | Size | Type | Field |
| ---: | ---: | --- | --- |
| 0 | 4 | `u32` | magic |
| 4 | 4 | `u32` | version |
| 8 | 4 | `i32` | dimension `d` |
| 12 | 4 | `i32` | IVF list count `nlist` |
| 16 | 4 | `u32` | metric (`0=L2`, `1=InnerProduct`, `2=Cosine`) |
| 20 | 4 | `u32` | flags |
| 24 | 8 | `i64` | total vector count |
| 32 | 8 | `u64` | deterministic rotation seed |
| 40 | 4 | `u32` | deterministic rotation rounds |
| 44 | 4 | `i32` | bytes per primary sign-code plane, `ceil(d / 8)` |
| 48 | 4 | `u32` | RQ `num_bits`; currently `1`, reserved values `2`, `4`, and `8` |
| 52 | 4 | `u32` | `rotation_type`; currently `1` for deterministic Kac rotation |
| 56 | 4 | `u32` | `factor_layout`; currently `1` for three RaBitQ `f32` factors |
| 60 | 4 | `u32` | RQ format flags |
Flags:
| Bit | Meaning |
| ---: | --- |
| 0 | delta-varint ids are used; required in v1 |
RQ format flags:
| Bit | Meaning |
| ---: | --- |
| 0 | optional `ex_codes` section is present |
| 1 | optional `error_factor` section is present |
Sections after the header:
1. IVF coarse centroids: `nlist * d` `f32` values.
2. Offset table: `nlist` entries of `(offset: i64, count: i32, id_bytes_len: i32)`.
3. List payloads.
For each non-empty list payload:
| Field | Type | Notes |
| --- | --- | --- |
| `base_id` | `i64` | first sorted row id |
| `id_bytes_len` | `i32` | byte length of encoded id stream |
| `id_bytes` | bytes | delta-varint ids |
| `codes` | `count * ceil(d / 8)` bytes | primary RaBitQ sign-code plane over rotated residuals |
| `ex_codes` | optional bytes | reserved for additional code planes when `num_bits > 1`; present when RQ format flag bit 0 is set |
| `factors` | `count * 3` `f32` | per-vector `(residual_norm_sqr, vector_norm_sqr, dp_multiplier)` correction factors |
| `error_factor` | optional `count` `f32` | reserved per-vector refinement factor; present when RQ format flag bit 1 is set |
The deterministic rotation is derived from `(d, rotation_seed, rotation_rounds)`
and applied to both indexed residuals and query residuals. The rotation parameters
are stored in the header so independently written files can use the same
distance estimator after being read by any v1 reader.
Current writers emit `num_bits=1`, `rotation_type=1`, `factor_layout=1`, and no
optional RQ sections. Current readers validate these format fields and reject
reserved multi-bit or optional-section encodings until the corresponding scanner
is implemented.
`query_bits` is a search-time IVF-RQ parameter and is not serialized. `0` uses
the default float-query byte-LUT estimator; `4` and `8` quantize the rotated
query residual into sign and magnitude bit planes for bitwise/popcount scanning.
## IVF-HNSW-FLAT v1
Magic: `IHFL` (`0x4948464C`). Version: `1`. Header size: 64 bytes.
| Offset | Size | Type | Field |
| ---: | ---: | --- | --- |
| 0 | 4 | `u32` | magic |
| 4 | 4 | `u32` | version |
| 8 | 4 | `i32` | dimension `d` |
| 12 | 4 | `i32` | IVF list count `nlist` |
| 16 | 4 | `u32` | metric (`0=L2`, `1=InnerProduct`, `2=Cosine`) |
| 20 | 8 | `i64` | total vector count |
| 28 | 4 | `i32` | HNSW `m` |
| 32 | 4 | `i32` | HNSW `ef_construction` |
| 36 | 4 | `i32` | HNSW `max_level` |
| 40 | 4 | `u32` | flags |
| 44 | 20 | bytes | reserved |
Flags:
| Bit | Meaning |
| ---: | --- |
| 0 | sorted delta-varint ids are stored; required in v1 |
| 1 | HNSW graph section uses the v1 delta-varint graph encoding; required in v1 |
Sections after the header:
1. IVF coarse centroids: `nlist * d` `f32` values.
2. Offset table: `nlist` entries of
`(offset: i64, count: i32, graph_bytes_len: i32, payload_bytes_len: i64)`.
3. List payloads.
For each non-empty list payload:
| Field | Type | Notes |
| --- | --- | --- |
| `base_id` | `i64` | first sorted row id |
| `id_bytes_len` | `i32` | byte length of encoded id stream |
| `id_bytes` | bytes | delta-varint ids |
| `vectors` | `count * d` `f32` | raw stored vectors |
| `graph` | bytes | HNSW graph section |
## IVF-HNSW-SQ v1
Magic: `IHSQ` (`0x49485351`). Version: `1`. Header size: 64 bytes.
| Offset | Size | Type | Field |
| ---: | ---: | --- | --- |
| 0 | 4 | `u32` | magic |
| 4 | 4 | `u32` | version |
| 8 | 4 | `i32` | dimension `d` |
| 12 | 4 | `i32` | IVF list count `nlist` |
| 16 | 4 | `u32` | metric (`0=L2`, `1=InnerProduct`, `2=Cosine`) |
| 20 | 8 | `i64` | total vector count |
| 28 | 4 | `i32` | HNSW `m` |
| 32 | 4 | `i32` | HNSW `ef_construction` |
| 36 | 4 | `i32` | HNSW `max_level` |
| 40 | 4 | `f32` | global minimum SQ bound summary |
| 44 | 4 | `f32` | global maximum SQ bound summary |
| 48 | 4 | `u32` | flags |
| 52 | 12 | bytes | reserved |
Flags:
| Bit | Meaning |
| ---: | --- |
| 0 | sorted delta-varint ids are stored; required in v1 |
| 1 | HNSW graph section uses the v1 delta-varint graph encoding; required in v1 |
Sections after the header:
1. Global SQ min bounds: `d` `f32` values.
2. Global SQ max bounds: `d` `f32` values.
3. Per-list SQ bounds: for each list, `d` min `f32` values followed by `d`
max `f32` values.
4. IVF coarse centroids: `nlist * d` `f32` values.
5. Offset table: `nlist` entries of
`(offset: i64, count: i32, graph_bytes_len: i32, payload_bytes_len: i64)`.
6. List payloads.
For each non-empty list payload:
| Field | Type | Notes |
| --- | --- | --- |
| `base_id` | `i64` | first sorted row id |
| `id_bytes_len` | `i32` | byte length of encoded id stream |
| `id_bytes` | bytes | delta-varint ids |
| `codes` | bytes | scalar quantized residual codes, `count * d` bytes |
| `graph` | bytes | HNSW graph section over decoded vectors |