Three verbs, multiply, maxsim and attend, treat the index as a sparse operand rather than as a kNN store, each folding its arithmetic into the graph walk instead of materializing a match list.
All three read logits rather than distances, so they run on an inner-product index alone.
multiply is a sparse matrix product, reporting every pair the traversal scored rather than only the nearest.
How many pairs a walk scores is known only once it ends, so the call returns them as DenseProducts read in place from an arena: one run of pairs per left row, in traversal order, and required counting every pair scored before a non-zero wanted cut any.
let products = try index.on(executor).multiply(left, arena: arena, config: DenseMultiplyConfig(wanted: 64))for pair in products[0] { print(pair.key, pair.distance) }
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 valuesPerKey above one exists for, and the outputs are sized over documents rather than rows.
The call returns the same DenseProducts as multiply, one ragged run per whole query with its documents largest score first, and a zero wanted keeps every document the query scored.
let documents = try index.on(executor).maxsim(tokens, rowsPerQuery: tokensPerQuery, wanted: 10, arena: arena)for document in documents[0] { print(document.key, document.distance) }
attend is softmax attention over every key the graph walk scores, folded as Flash Attention folds key tiles, with outputs already divided by the partition.
Row t · headCount + h of the queries, outputs and carry is head h of token t, so the query heads of grouped-query attention share the index’s one key-value head and one walk per token.
The index stores no values: a ValueLookup names, for every scored key, its row of a values RowsView as wide as the keys and stored in the index’s own quantization.
A key without a row, or values of another width or scalar kind than the library reads, refuses the call with shapeMismatch, since a missing row is a wrong softmax rather than a smaller one.
A VRAM index walks on the device and resolves values through a device function, which a Swift closure cannot be, so attend refuses it with device.
The lookup runs on the executor’s threads together, so it must be @Sendable and safe for concurrent use.
The outputs land in the arena as rows of the scalar kind as: names, and a carry of one DenseSoftmaxState per query row folds this branch into another over the same queries.
The two are one concept: a maximum without its partition is a number nothing can rescale, so
they share an element rather than travelling as two arrays a caller could supply half of.
Carrying them is what folds one attention call into another over the same queries.
Conforms to
Equatable, Sendable
maximumScoreThe largest logit seen so far - the recurrence's m.
StructValueLookupHow attend resolves scored keys to value rows: a key names its row of values, whose rows must be as wide as the keys and stored in the index's own quantization.3
The index stores no values, so only the keys a walk scores are ever resolved. A key without a
row, or values of another width or scalar kind than the library reads, refuses the call with
shapeMismatch, since a missing row is a wrong softmax rather than a smaller one.
Conforms to
Sendable
valuesThe value rows, which must outlive the call.
It runs on the executor's
threads together, so it must be safe for concurrent use; the first error it throws leaves
that key without a row and is what the call throws.
init(headCount: Int = 0, carry: UnsafeMutableBufferPointer<DenseSoftmaxState>? = nil, expansionFactor: Int = 0, scoreScale: Float = 0, progress: Progress? = nil, progressSteps: Int = 0, stats: DenseTraversalStats? = nil)
StructDenseProductsThe ragged sparse product multiply wrote into an arena: per left row, every pair its walk scored, in traversal order; or the keys maxsim kept per query, largest score first; read in place.4