OkfVectorStore

Struct OkfVectorStore 

Source
pub struct OkfVectorStore<S: OkfStore> { /* private fields */ }
Expand description

Vectors kept beside the records, in the same store the OKF Markdown uses.

One document per record, holding the text hash and the vector as base64 f16. That encoding is worth explaining, because it is the largest thing this type adds to a deployment and an earlier revision wrote it the obvious way — lowercase hex f32 — at three times the size.

encodingvector bytesper recordat 16,000× the Markdown
hex f32 (what this used to write)6,1446,16198.6 MB4.75×
base64 f324,0964,11365.8 MB3.17×
hex f163,0723,08949.4 MB2.38×
base64 f16 (what ships)2,0482,06533.0 MB1.59×

The Markdown column compares against the records these annotate, which memory_at_scale measures at 20,259 KiB for the same 16,000. Hex f32 made the vectors nearly five times the corpus they describe; base64 f16 makes them about one and a half.

§Why f16 is free here, measured rather than assumed

f16 keeps 10 mantissa bits against f32’s 23, so the obvious worry is that a lossy store quietly degrades retrieval. It does not, and the reason is that PrecomputedSemanticIndex reads these floats for exactly two things with very different sensitivities:

  • The packed scan is sign bits, and f16 preserves sign. Over 920,832 coordinates of a real corpus, zero flipped — so the shortlist the rerank sees is bit-identical, not merely similar. The one way it could flip is a coordinate below f16’s smallest subnormal (2⁻²⁴ ≈ 6e-8) underflowing to -0.0, which the packer reads as non-negative; three coordinates underflowed on that corpus and all three were positive. The probe asserts on the count rather than trusting the argument.
  • The rerank is a dot product, where rounding could reorder candidates. Over 93 questions it did not move the answer’s rank once.

tests/storage_encoding_probe.rs runs both stores through this crate’s own search_vector and reports identical top-1 (64/93), top-5 (71/93) and MRR (0.727), and identical fused numbers against BM25 at 2:1 (65/93, 79/93, 0.760). Worst coordinate movement is 1.2e-4. Five questions reorder within the top five, all among non-answers.

So this is a 3× saving for no measured quality cost — but the measurement is on one 1,199-record corpus at 768 dimensions, and the property it rests on is that embedding coordinates sit comfortably inside f16’s normal range. A model whose vectors are not L2-normalised, or are far wider, deserves a re-run of that probe before the same conclusion is assumed.

§Reading what an older version wrote

A payload tagged f16b64: is base64 f16; anything else is the legacy lowercase-hex f32, and is decoded as such. That matters more than a format flag usually does: without it, upgrading would invalidate every stored vector and re-embedding a 16,000-record corpus is over an hour of wall clock. Existing stores therefore keep their old size until each record is next rewritten, which happens when its text changes.

What all of this buys is the whole reason to pay it: without persistence a restart re-embeds every record at 259 ms each, which is over an hour at this corpus size, on every deploy and every replica.

Implementations§

Source§

impl<S: OkfStore> OkfVectorStore<S>

Source

pub fn new(store: Arc<S>) -> Self

Keep vectors under vectors/ in the given store.

Trait Implementations§

Source§

impl<S: OkfStore> VectorStore for OkfVectorStore<S>

Source§

fn load<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Vec<(MemoryId, String, Vec<f32>)>, MemoryError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Every vector held, as (id, text hash, vector).
Source§

fn save<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, id: &'life1 MemoryId, hash: &'life2 str, vector: &'life3 [f32], ) -> Pin<Box<dyn Future<Output = Result<(), MemoryError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait,

Persist one vector against the hash of the text that produced it.
Source§

fn remove<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 MemoryId, ) -> Pin<Box<dyn Future<Output = Result<(), MemoryError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Forget a record’s vector.

Auto Trait Implementations§

§

impl<S> Freeze for OkfVectorStore<S>

§

impl<S> RefUnwindSafe for OkfVectorStore<S>
where S: RefUnwindSafe,

§

impl<S> Send for OkfVectorStore<S>

§

impl<S> Sync for OkfVectorStore<S>

§

impl<S> Unpin for OkfVectorStore<S>

§

impl<S> UnwindSafe for OkfVectorStore<S>
where S: RefUnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<S> FromSample<S> for S

§

fn from_sample_(s: S) -> S

§

impl<T> FutureExt for T

§

fn with_context(self, otel_cx: Context) -> WithContext<Self>

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
§

fn with_current_context(self) -> WithContext<Self>

Attaches the current Context to this type, returning a WithContext wrapper. Read more
§

impl<T> FutureExt for T

§

fn with_context(self, otel_cx: Context) -> WithContext<Self>

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
§

fn with_current_context(self) -> WithContext<Self>

Attaches the current Context to this type, returning a WithContext wrapper. Read more
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
§

impl<T> IntoRequest<T> for T

§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
§

impl<L> LayerExt<L> for L

§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in [Layered].
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

§

fn to_sample_(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,