Python · Linear Algebra

Linear Algebra

from usearch import DenseProducts 3.0.0-rc.1 · 4c3e5023

§ Sparse Multiplication

multiply, maxsim and attend read the walk as arithmetic rather than as retrieval. All of them need an inner-product index, since a product, a maximum over logits, and a softmax are all things only those kinds score.

multiply reports every pair the traversal scored rather than only the nearest: an approximate A × Bᵀ, dense in and sparse out. A walk computes many times the products it reports and each of them is exact, so the caller stops recomputing what the index already knew. How many pairs a walk scores is known only once it ends, so they come back ragged, with Apache Arrow offsets around them:

products = index.on(pool).multiply(rows, 0, arena)  # 0 keeps every pair; a bound keeps the first ones
offsets = np.asarray(products.offsets)  # (rows + 1,): left row `i` spans offsets[i] to offsets[i + 1]
columns = np.asarray(products.keys)  # (pairs, 16): which indexed entry each pair names
scores = np.asarray(products.scores)  # (pairs,): the exact products
products.required  # pairs scored before the bound cut any
row_keys, row_scores = products.query(0)  # one left row's pairs, for each of the len(products) rows

§ Late Interaction

maxsim scores multi-row queries against multi-vector documents, where 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 values_per_key above one exists for, and the result is the ragged run multiply answers, one run of documents per query, largest first. A zero wanted keeps every document a query scored.

tokens = DenseIndex.init_ram(dimensions=128, metric=DenseMetric.INNER, values_per_key=32)
...
documents = tokens.on(pool).maxsim(query_tokens, 32, 10, arena)  # 32 token rows per query, up to 10 documents each
offsets = np.asarray(documents.offsets)  # (queries + 1,): query `i` spans offsets[i] to offsets[i + 1]

§ Attention

attend is Flash Attention folded into the walk: every key the walk scores folds into each head’s running maximum, partition and weighted value sum, with no top-k cut, so the long tail it scores brings the result toward exact dense attention. The index holds one layer’s keys for one key-value head and no values, so row_of names the row of values each scored key reads, the rows as wide as the keys and stored in the index’s quantization. Grouped-query heads share one walk per token: with head_count=8, row t * 8 + h of the queries is head h of token t.

carry = np.zeros((len(queries), 2), dtype=np.float32)
row_of = {key: row for row, key in enumerate(context_keys)}.get  # a scored Key in, its row of `values` out
outputs = index.on(pool).attend(queries, values, row_of, arena, head_count=8, outputs_dtype=Scalar.BF16, carry=carry)

A row_of answering None, or a row outside values, refuses the call with ShapeMismatchError, since a missing row is a wrong softmax rather than a smaller one; so do values of another width or scalar type, which values_dtype= names when the buffer cannot. It re-enters Python from the library’s threads, so it runs under the GIL. A VRAM walk resolves values on the device, where no Python callable runs, so attend with a VRAM arena raises DeviceError. The attended rows come back already divided by the partition, as a DenseVectors over the arena.

Ask for the carry to recombine this branch by partial-softmax rescaling with another over the same queries, such as attention sinks, a dense recent window, or a second index of one context. Keep the recent window out of the index, inserting a token once it leaves the window, so the two branches never count a key twice. Each of its rows holds one query’s largest logit and the softmax denominator taken against it; a maximum without its partition is a number nothing can rescale, which is why they travel as one row rather than as two arrays.

API

ClassDenseProductsThe ragged sparse product multiply wrote into an Arena: per left row, every pair its walk scored.8
lib.pyi:500 ↗
class DenseProducts

maxsim lands its documents alike, one run per query, largest first, required counting those it scored.

queryThe (keys, scores) of left row or query query, viewing the arena.
lib.pyi:528 ↗
def query(query: int, /) -> tuple[_ArenaBuffer, _ArenaBuffer]

Python-only.

offsets(queries + 1,) Apache Arrow offsets.
lib.pyi:509 ↗
offsets: _ArenaBuffer
keys(pairs, 16) identities of the indexed entries.
lib.pyi:514 ↗
keys: _ArenaBuffer
scores(pairs,) float64 products.
lib.pyi:519 ↗
scores: _ArenaBuffer
requiredPairs scored before the bound cut any.
lib.pyi:524 ↗
required: int
__arrow_c_array__Exports host rows as an Arrow schema and array capsule.
lib.pyi:117 ↗

Inherited from _ArrowOutput

def __arrow_c_array__(
    requested_schema: _Capsule | None = None,
) -> tuple[_Capsule, _Capsule]
__arrow_c_device_array__Exports rows wherever they live as an Arrow schema and device array capsule.
lib.pyi:121 ↗

Inherited from _ArrowOutput

def __arrow_c_device_array__(
    requested_schema: _Capsule | None = None,
) -> tuple[_Capsule, _Capsule]
__arrow_c_stream__Exports host rows as an Arrow stream of one chunk.
lib.pyi:125 ↗

Inherited from _ArrowOutput

def __arrow_c_stream__(
    requested_schema: _Capsule | None = None,
) -> _Capsule