Swift · Linear Algebra

Linear Algebra

import USearch 3.0.0-rc.1 · 4c3e5023

§ Sparse Multiplication

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) }

§ 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 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) }

§ Attention

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.

let lookup = ValueLookup(values: RowsView(valueCache)) { key in rowOf[key] }
let context = try carry.withUnsafeMutableBufferPointer { carry in
    try index.on(executor).attend(
        queries, lookup: lookup, as: Float.self, arena: arena,
        config: DenseAttendConfig(headCount: 8, carry: carry))
}

API

StructDenseSoftmaxStateFlash Attention's running maximum and partition for one query row.3
DenseIndex.swift:222 ↗
struct DenseSoftmaxState

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.
DenseIndex.swift:224 ↗
var maximumScore: Float
partitionThe sum of exponentials taken against that maximum - the recurrence's l.
DenseIndex.swift:226 ↗
var partition: Float
init
DenseIndex.swift:228 ↗
init(maximumScore: Float = 0, partition: Float = 0)
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
DenseIndex.swift:240 ↗
struct ValueLookup

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.
DenseIndex.swift:242 ↗
var values: RowsView
rowThe row of values holding a key's value, or nil for none.
DenseIndex.swift:246 ↗
var row: @Sendable (Key) throws -> Int?

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
DenseIndex.swift:248 ↗
init(values: RowsView, row: @escaping @Sendable (Key) throws -> Int?)
StructDenseMaxsimConfigThe knobs of maxsim.5
DenseIndex.swift:342 ↗
struct DenseMaxsimConfig

Every zeroed field derives.

expansionFactorThe breadth of the candidates; zero derives it from the geometry.
DenseIndex.swift:344 ↗
var expansionFactor: Int
progressCalled with queries resolved.
DenseIndex.swift:346 ↗
var progress: Progress?
progressStepsReports the call makes at most, the last at completion; zero derives to ten.
DenseIndex.swift:348 ↗
var progressSteps: Int
statsThe traversal telemetry to write.
DenseIndex.swift:350 ↗
var stats: DenseTraversalStats?
init
DenseIndex.swift:352 ↗
init(expansionFactor: Int = 0, progress: Progress? = nil, progressSteps: Int = 0, stats: DenseTraversalStats? = nil)
StructDenseMultiplyConfigThe knobs of multiply.6
DenseIndex.swift:366 ↗
struct DenseMultiplyConfig

Every zeroed field derives.

wantedThe bound on pairs kept per left row, the first ones its walk scored; zero keeps every pair.
DenseIndex.swift:368 ↗
var wanted: Int
expansionFactorThe breadth of the candidates, which widens the emitted pattern; zero derives.
DenseIndex.swift:370 ↗
var expansionFactor: Int
progressCalled with rows resolved.
DenseIndex.swift:372 ↗
var progress: Progress?
progressStepsReports the call makes at most, the last at completion; zero derives to ten.
DenseIndex.swift:374 ↗
var progressSteps: Int
statsThe traversal telemetry to write.
DenseIndex.swift:376 ↗
var stats: DenseTraversalStats?
init
DenseIndex.swift:378 ↗
init(wanted: Int = 0, expansionFactor: Int = 0, progress: Progress? = nil, progressSteps: Int = 0, stats: DenseTraversalStats? = nil)
StructDenseAttendConfigThe knobs of attend.8
DenseIndex.swift:394 ↗
struct DenseAttendConfig

Every zeroed field derives.

headCountThe query heads sharing the index's key-value head, and with it one walk per token; it must divide the query rows, and zero means one.
DenseIndex.swift:397 ↗
var headCount: Int
carryThe softmax state to write, one per query row, which must outlive the call; nil skips it.
DenseIndex.swift:399 ↗
var carry: UnsafeMutableBufferPointer<DenseSoftmaxState>?
expansionFactorThe breadth of the candidates; zero derives it from the geometry.
DenseIndex.swift:402 ↗
var expansionFactor: Int

Wider candidates walk further, folding more of the tail.

scoreScaleThe logit scale, conventionally the inverse square root of the head dimensionality; zero derives it from the index's frozen geometry.
DenseIndex.swift:405 ↗
var scoreScale: Float
progressCalled with queries resolved.
DenseIndex.swift:407 ↗
var progress: Progress?
progressStepsReports the call makes at most, the last at completion; zero derives to ten.
DenseIndex.swift:409 ↗
var progressSteps: Int
statsThe traversal telemetry to write.
DenseIndex.swift:411 ↗
var stats: DenseTraversalStats?
init
DenseIndex.swift:413 ↗
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
DenseIndex.swift:815 ↗
struct DenseProducts

Conforms to

RandomAccessCollection, BidirectionalCollection, Collection, Sequence
endIndexOne past the last left row or query.
DenseIndex.swift:821 ↗
let endIndex: Int
requiredPairs the walks scored across every left row, or keys scored across every query, before the wanted bound cut any.
DenseIndex.swift:824 ↗
let required: Int
startIndexThe first query's position, which a collection walk starts from.
DenseIndex.swift:827 ↗
var startIndex: Int { get }
subscriptOne left row's pairs, in traversal order, or one query's keys, largest score first.
DenseIndex.swift:830 ↗
subscript(query: Int) -> QueryMatches { get }