Context
I'm the author of pg_turbovec, a PostgreSQL extension that uses turbovec as the kernel for indexed cosine / inner-product / L2 / L1 ANN search at storage sizes 10–20× smaller than pgvector. It's been carrying a small additive vendor patch on top of turbovec 0.5.0/0.6.0 to handle two needs that database extensions hit but the upstream API doesn't currently support:
-
In-memory deserialisation. When the index payload is already in RAM (read out of a bytea column or a buffer-manager-pinned page), going through IdMapIndex::load(path) requires a tmpfile round-trip — write to /tmp, fsync, re-open, read back, parse. On a 1 M × 384-d × 4-bit index that's ≈195 MB of needless I/O. The internal parsers in src/io.rs are already generic over Read/Write; widening the public surface to expose that internal generality is a 100-line additive change.
-
Constructing an IdMapIndex from already-decoded parts. Database storage backends sometimes have their own page format (e.g. PostgreSQL relfiles) and want to skip the .tvim wire format entirely. Promoting TurboQuantIndex::from_parts (and the small packed_codes() / scales() accessors it pairs with) from pub(crate) to pub lets external embedders construct the index from their own deserialised bytes.
Proposed API additions
All additive; no behavioural change to existing APIs; no wire-format change.
// src/io.rs
pub fn write_to<W: Write>(w: &mut W, …) -> io::Result<()>
pub fn load_from<R: Read>(r: &mut R) -> io::Result<…>
pub fn write_id_map_to<W: Write>(w: &mut W, …) -> io::Result<()>
pub fn load_id_map_from<R: Read>(r: &mut R) -> io::Result<…>
// src/id_map.rs
impl IdMapIndex {
pub fn write_to_writer<W: Write>(&self, w: &mut W) -> io::Result<()>
pub fn load_from_reader<R: Read>(r: &mut R) -> io::Result<Self>
}
// src/lib.rs — three previously-pub(crate) items become pub
pub fn TurboQuantIndex::from_parts(dim, bit_width, n_vectors, packed_codes, scales) -> Self
pub fn TurboQuantIndex::packed_codes(&self) -> &[u8]
pub fn TurboQuantIndex::scales(&self) -> &[f32]
Why this matters in the field
pg_turbovec measured a 21× cold-scan speedup (26 310 ms → 1 256 ms p50, 1 M × 1536-d real OpenAI ada-002 corpus on a single bench host) by combining:
- The
Read/Write API to read TVIM payloads straight from PG's buffer manager (no tmpfile).
pub fn from_parts to construct the IdMapIndex from the buffer-pinned bytes, skipping deserialisation entirely.
- Persisting the SIMD-blocked layout into the relfile at
ambuild time so the per-backend pack::repack (12–15 s on this corpus) becomes amortised constant.
The first two bullets are the API change in this issue; the third lives entirely in pg_turbovec. Without the upstream changes, pg_turbovec carries the vendor patch indefinitely, which is fine but adds a maintenance step every time turbovec releases.
Status of a draft PR
I respect the by-invitation policy in CONTRIBUTING. I've drafted the implementation against v0.6.0 on a branch in my fork — gburd/turbovec in-memory-io-and-pub-from-parts — so if you're amenable to the change and would like to invite a PR, the diff is already ready (3 files, +107 / -11 lines, all upstream cargo test --tests --release passes). Happy to take it on if you prefer; equally happy if you'd rather pick this up yourself or not at all.
Test results on the draft branch
test result: ok. 6 passed tests/codebook
test result: ok. 5 passed tests/concurrent_search
test result: ok. 5 passed tests/distortion
test result: ok. 4 passed tests/encode
test result: ok. 15 passed tests/filtering
test result: ok. 11 passed tests/id_map
test result: ok. 5 passed tests/io_versioning
test result: ok. 6 passed tests/kernel_correctness
test result: ok. 21 passed tests/lazy_init
Context
I'm the author of
pg_turbovec, a PostgreSQL extension that usesturbovecas the kernel for indexed cosine / inner-product / L2 / L1 ANN search at storage sizes 10–20× smaller than pgvector. It's been carrying a small additive vendor patch on top ofturbovec 0.5.0/0.6.0to handle two needs that database extensions hit but the upstream API doesn't currently support:In-memory deserialisation. When the index payload is already in RAM (read out of a
byteacolumn or a buffer-manager-pinned page), going throughIdMapIndex::load(path)requires a tmpfile round-trip — write to/tmp, fsync, re-open, read back, parse. On a 1 M × 384-d × 4-bit index that's ≈195 MB of needless I/O. The internal parsers insrc/io.rsare already generic overRead/Write; widening the public surface to expose that internal generality is a 100-line additive change.Constructing an
IdMapIndexfrom already-decoded parts. Database storage backends sometimes have their own page format (e.g. PostgreSQL relfiles) and want to skip the.tvimwire format entirely. PromotingTurboQuantIndex::from_parts(and the smallpacked_codes()/scales()accessors it pairs with) frompub(crate)topublets external embedders construct the index from their own deserialised bytes.Proposed API additions
All additive; no behavioural change to existing APIs; no wire-format change.
Why this matters in the field
pg_turbovecmeasured a 21× cold-scan speedup (26 310 ms → 1 256 ms p50, 1 M × 1536-d real OpenAI ada-002 corpus on a single bench host) by combining:Read/WriteAPI to read TVIM payloads straight from PG's buffer manager (no tmpfile).pub fn from_partsto construct theIdMapIndexfrom the buffer-pinned bytes, skipping deserialisation entirely.ambuildtime so the per-backendpack::repack(12–15 s on this corpus) becomes amortised constant.The first two bullets are the API change in this issue; the third lives entirely in
pg_turbovec. Without the upstream changes,pg_turboveccarries the vendor patch indefinitely, which is fine but adds a maintenance step every timeturbovecreleases.Status of a draft PR
I respect the by-invitation policy in CONTRIBUTING. I've drafted the implementation against
v0.6.0on a branch in my fork —gburd/turbovecin-memory-io-and-pub-from-parts— so if you're amenable to the change and would like to invite a PR, the diff is already ready (3 files, +107 / -11 lines, all upstreamcargo test --tests --releasepasses). Happy to take it on if you prefer; equally happy if you'd rather pick this up yourself or not at all.Test results on the draft branch