pub type ValueLookup<'lookup, Value> = dyn Fn(Key) -> Option<&'lookup [Value]> + Sync + 'lookup; § Sparse Multiplication
multiply, maxsim and attend treat the index as a sparse operand rather than as a kNN store, and each folds its arithmetic into the graph walk instead of materializing a match list.
All three need logits rather than distances, so they run on an inner-product index alone.
multiply is a sparse matrix product: every left row is walked against the index, and every pair the walk scored lands in an arena as one ragged run, in traversal order. wanted keeps the first pairs of each row, where zero keeps every pair, and required counts how many there were.
The run’s Apache Arrow offsets land in the arena beside the keys and products.
let mut arena = Arena::new_in(&executor)?;
let products = index.on(&executor).multiply(&rows(&left, 768)?, 32, &mut arena)?;
for row in products.iter()? {
println!("{} pairs", row.keys.len());
}§ Late Interaction
maxsim is late interaction - every query row takes its best match within a key, and those maxima sum to that key’s score.
A document is one key holding its token rows, which is what a values_per_key above one exists for, and the wanted count is over documents rather than rows.
Each query lands its best documents, largest score first, in the same ragged DenseProducts run multiply returns, and a wanted of zero keeps every document the query scored.
let documents = index.on(&executor).maxsim(&rows(&tokens, 768)?, tokens_per_query, 10, &mut arena)?;
for query in documents.iter()? {
println!("best {:?} of {} documents", query.keys.first(), query.keys.len());
}§ Attention
attend is Flash Attention folded into the walk: every key it scores folds into each head’s running maximum, partition and weighted value sum, with no top-k cut, and the outputs land in an arena already divided by the partition, as rows of the queries’ own scalar type.
The index stores no values, so a ValueLookup closure resolves every scored key to its value row, as wide as the keys, stored in the index’s own quantization, and borrowed from storage outliving the call.
A None row, or rows of another scalar type or width than the library reads, refuses the call with Status::ShapeMismatch.
It runs on the executor’s threads together, hence Sync, and a panic inside it refuses the call.
A VRAM walk resolves values on the device, where no Rust closure runs, so an arena from a VRAM executor is refused with Status::Device.
With a head_count of 8, row t * 8 + h of the queries is head h of token t, and the heads of one token share its walk.
A carry of one DenseSoftmaxState per query row, lent by you, folds this branch into another over the same queries, such as a recent window kept out of the index. score_scale is conventionally the inverse square root of the head width, and zero derives it from the frozen geometry.
let value_lookup: &ValueLookup<bf16> = &|key| cache.get(&key).map(Vec::as_slice);
let mut carry = vec![DenseSoftmaxState::default(); queries_count];
let context = index.on(&executor).attend(&rows(&queries, 128)?, value_lookup, DenseAttendConfig {
carry: Some(&mut carry),
..DenseAttendConfig::from(8)
}, &mut arena)?;
println!("{:?}", context.view()?.shape());API
Types
ValueLookupAn attention's value lookup, run on the executor's threads together: it resolves one key to its value row, stored in the index's own quantization and borrowed from storage outliving the call, and None refuses the call, as a panic does before it resurfaces once the call is final.
StructDenseAttendConfigThe heads, carry and knobs of an attention's walk; see DenseVerbs::attend.
pub struct DenseAttendConfig<'config> Fields
head_count | usize | The query heads sharing the index's key-value head, and with it one walk per token: row
t · head_count + h is head h of token t, so it must divide the rows; zero means one. |
|---|---|---|
carry | Option<&'config mut [DenseSoftmaxState]> | Where the online-softmax state lands, one entry per query row, which folds this call into another over the same queries by partial-softmax rescaling. |
expansion_factor | usize | The breadth of the candidates; zero derives it. Wider candidates walk further, folding more of the tail. |
score_scale | f32 | The logit scale, conventionally the inverse square root of the head dimensionality; zero derives it from the index's frozen geometry. |
progress | Option<&'config mut ProgressCallback<'config>> | Reports the queries resolved and stops wherever it says to. |
progress_steps | usize | Reports the call makes at most, the last at completion; zero derives to ten. |
stats | Option<&'config mut DenseTraversalStats> | Where the traversal telemetry lands, overwritten by the call. |
Implements
From<usize>StructDenseMaxsimConfigThe documents a late-interaction search keeps and the knobs of its walk; see DenseVerbs::maxsim.
pub struct DenseMaxsimConfig<'config> Fields
wanted | usize | Documents kept per query, counted over keys rather than entries; zero keeps every key the query scored. |
|---|---|---|
expansion_factor | usize | The breadth of the candidates the walk explores; zero derives it from the geometry. |
progress | Option<&'config mut ProgressCallback<'config>> | Reports the queries resolved and stops wherever it says to. |
progress_steps | usize | Reports the call makes at most, the last at completion; zero derives to ten. |
stats | Option<&'config mut DenseTraversalStats> | Where the traversal telemetry lands, overwritten by the call. |
Implements
From<usize>StructDenseMultiplyConfigThe pairs a sparse product keeps and the knobs of its walk; see DenseVerbs::multiply.
pub struct DenseMultiplyConfig<'config> Fields
wanted | usize | Keeps the first wanted pairs each row's walk scored; zero keeps every pair. |
|---|---|---|
expansion_factor | usize | The breadth of the candidates, which widens the emitted pattern; zero derives it. |
progress | Option<&'config mut ProgressCallback<'config>> | Reports the rows resolved and stops wherever it says to. |
progress_steps | usize | Reports the call makes at most, the last at completion; zero derives to ten. |
stats | Option<&'config mut DenseTraversalStats> | Where the traversal telemetry lands, overwritten by the call. |
Implements
From<usize>StructDenseProductsThe ragged run DenseVerbs::multiply or DenseVerbs::maxsim exported into the arena it borrows: per left row, every pair its walk scored, in traversal order, or per query, its kept keys, largest score first.4
pub struct DenseProducts<'arena> queriesLeft rows the call multiplied, or queries it scored.
pub fn queries(&self) -> usize requiredPairs the walks scored across every left row, or keys scored across every query, before the wanted bound cut any.
pub fn required(&self) -> usize queryThe pairs of one left row, a key naming each indexed entry beside its product, or the keys of one query beside their summed maxima, refused with Status::InvalidArgument when query is not below DenseProducts::queries and with Status::Device over device memory.
pub fn query(&self, query: usize) -> Result<QueryMatches<'arena>> iterEvery query's pairs or keys, in query order, refused with Status::Device over device memory.
pub fn iter(&self) -> Result<impl Iterator<Item = QueryMatches<'arena>>> StructDenseSoftmaxStateThe online-softmax carry of one query row, which folds one attention call into another over the same queries.
pub struct DenseSoftmaxState Fields
maximum_score | f32 | The largest logit seen so far - the recurrence's m. |
|---|---|---|
partition | f32 | The sum of exponentials taken against that maximum - the recurrence's l. |
Implements
Clone, Copy, StructuralPartialEq, PartialEq, Debug, Default