Skip to main content

Crate yo_vector

Crate yo_vector 

Source
Expand description

The vector model: RaBitQ codes under partitions that update in place (10).

A vector index is two decisions, and as of 2026 only one of them is still open. The quantiser is settled: RaBitQ won, and VectorChord, Lucene, which calls it BBQ, CockroachDB, turbopuffer and Zvec all landed on it against five different index structures inside eighteen months. Quantizer is that, and it is what makes a ten million vector collection a 1 GB index rather than a 30 GB one.

The index is decided here by the update path. A graph index tombstones a delete, degrades as the tombstones pile up, and gets better again only when it is rebuilt. Redis shipped HNSW vector sets in 8.0 in May 2025 and they were still beta three minor releases later, which is the vendor’s own evidence about how hard that path is. So this is partitions with the centroids resident and the codes in flat postings, which SPFresh showed can be split, merged and reassigned in place, and there is never a rebuild.

§What is here so far

The quantiser and the rotation it needs. Quantizer::encode writes the searchable form of a vector against the centroid of the partition it belongs to, and Quantizer::query prepares a query once so that measuring it against the codes in a partition is a scan over contiguous bytes.

use yo_vector::{Bits, Quantizer};

let q = Quantizer::new(128, Bits::One, 7);
assert_eq!(q.code_bytes(), 16);

A code is stored as bit planes rather than with each coordinate’s bits next to each other, and the query is quantised and transposed the same way, so measuring one against the other is ANDs and popcounts rather than a float multiply per dimension. That is 20 nanoseconds a vector at 768 dimensions against a whole search budget of a millisecond, and 35 times what the same estimator costs with the query left in floats. benches/rabitq.rs runs both so the ratio is measured rather than remembered.

Partitions is the index over those codes. A vector belongs to the partition whose centroid it is nearest, a partition’s members are a flat run of codes, an insert is an append and a delete moves the last member into the hole. A search ranks the centroids, scans the nearest few postings, and then measures the best handful properly against the full precision vectors. It splits, merges and reassigns in bounded steps as it goes, which is SPFresh’s LIRE, and it is why there is never a rebuild.

The centroids are kept already rotated, which matters more than it sounds like it should. Preparing a query is mostly the rotation and it happens once per partition probed, so rotating the centroids once when they are built turns tens of rotations a search into one.

A filter runs inside the posting scan rather than after it. Every member carries a tag word beside its code, and a scan that can reject a member before it measures one can keep going into further partitions until it has enough that pass. That widening is the whole point: a selective filter means the nearest partitions may hold nothing the caller asked for, and a search that does not go looking is a recall lottery. Signature packs arbitrary attribute values into that one word, and it is allowed to say yes when it should have said no but never the other way round, so the caller’s own predicate stays the authority.

muvera is late interaction retrieval on that same index. A ColBERT style model gives a document one vector per token and scores a query against it with Chamfer similarity, which normally means a second index over every token of every document and a scoring pass on top of it. MUVERA maps a set of token vectors to one fixed length vector whose dot product approximates Chamfer, so it costs an encode at write time, the index that is already here, and muvera::chamfer as the rerank. There is no second index.

hnsw is the compatibility view. Clients pass M, EF_CONSTRUCTION and EF_RUNTIME and expect them to do something, because against Redis and valkey they do, and there is no graph here to point them at. So each one is mapped onto whatever it was actually for: build effort becomes the posting size, search beam becomes the probe and the rerank width, and M is the out degree of a graph that does not exist, so it is echoed back and changes nothing. A client that asked for HNSW and meant it can say so and be refused rather than quietly served something else.

Writing any of this to a .yo file is the rest of M6.

Re-exports§

pub use partition::Any;
pub use partition::Filter;
pub use partition::Hit;
pub use partition::Partitions;
pub use partition::Signature;
pub use partition::Tuning;
pub use partition::Vectors;
pub use rabitq::Bits;
pub use rabitq::Coded;
pub use rabitq::Quantizer;
pub use rabitq::Query;
pub use rotate::Rotation;

Modules§

hnsw
HNSW as a compatibility view, not as an index (10 section 7).
muvera
MUVERA: a set of token vectors as one vector, so late interaction retrieval runs on the index that is already here (10 section 6).
partition
The partition index, and the in place update protocol that means it never has to be rebuilt (10 sections 2, 4 and 5).
rabitq
RaBitQ, the quantiser the searchable form of a vector is written in (10 section 3).
rotate
The random rotation every vector goes through before it is quantised (10 section 3).