JavaScript · Linear Algebra

Linear Algebra

import * as usearch from 'usearch';import { DenseProducts } from 'usearch'; 3.0.0-rc.1 · 4c3e5023

§ Sparse Multiplication

multiply is the sparse sibling of search: every left row emits one pair per column the walk scored rather than one ranked match, so the scores are unnormalized logits and the index must hold the Inner kind. How many pairs a walk scores is known only once it ends, so the call answers with a DenseProducts run viewing its arena, laid out with Apache Arrow offsets:

const index = usearch.DenseIndex.initRam({ metric: usearch.DenseMetric.Inner, dimensions: 128 });
const products = index.on(executor).multiply(left, arena, { wanted: 64 }); // Up to 64 pairs per left row
for (let row = 0; row < products.queries; row++) {
  const pairs = products.get(row); // A QueryMatches over `offsets[row]` up to `offsets[row + 1]`
  for (let pair = 0; pair < pairs.count; pair++) accumulate(row, usearch.keyAt(pairs.keys, pair), pairs.scores[pair]);
}
products.required; // Pairs scored across every left row, before `wanted` cut any

Pairs arrive in traversal order rather than sorted, and a wanted bound keeps the first ones each walk scored; leaving it out keeps every pair. The Arrow offsets, one longer than the left rows, land in the arena beside the pairs.

§ Late Interaction

An index whose valuesPerKey is above one holds several token rows per document, and maxsim scores documents rather than rows. It answers the DenseProducts run multiply does, one run of documents per query, largest first, and a zero wanted keeps every document a query scored:

const index = usearch.DenseIndex.initRam({ metric: usearch.DenseMetric.Inner, dimensions: 128, valuesPerKey: 64 });
index.on(executor).insert(documentKeys, tokenRows);

// 32 padded token rows per query, up to 10 documents each, query `i` spanning `offsets[i]` up to `offsets[i + 1]`
const documents = index.on(executor).maxsim(queryTokens, 32, 10, arena);
const best = documents.get(0); // A QueryMatches over the first query's documents

§ Attention

attend is softmax attention over every key a query’s graph walk scores, folded as Flash Attention folds key tiles, with no top-k cut: the long tail the walk scores is what brings it toward exact dense attention. It lands one key-wide row per query in the arena, in the queries’ own kind unless scalar names another. The index stores no values: every key a walk scores, 16 bytes apiece, resolves through rowOf to a row of values, borrowed for the call rather than copied, as wide as the keys and stored in the index’s own scalar kind. Answering with no row of values, or lending values of another width or kind, refuses the call with ShapeMismatch, since a missing row is a wrong softmax rather than a smaller one.

// One value row per indexed key, stored like the keys
const values = usearch.RowsView.of(valueMatrix, dimensions);
// The row of `values` a 16-byte key resolves to
const rowOf = (key) => tokenRows.get(usearch.integerFromKey(key));
// A largest logit and a partition per query row, lent and updated in place
const carry = new Float32Array(queries.rows * 2);
// Row `t * 8 + h` is head `h` of token `t`
const outputs = index.attend(queries, values, rowOf, arena, { headCount: 8, carry, scoreScale });
// One key-wide row per query, `outputs.stride` bytes apart
outputs.data;

One index holds one layer’s one key-value head, so the headCount query heads of grouped-query attention share its keys and one walk per token. The first exception rowOf throws stops every later lookup and surfaces as itself. rowOf runs on the host, so attend serves RAM handles alone: a VRAM walk resolves rows through a device function, which this binding does not build, and a VRAM arena refuses with Status.Device. The carry is what folds one branch into another over the same queries, by the usual partial-softmax rescaling: attention sinks, a dense recent window, or a second index of one context. Its two floats travel in one row because a maximum without its partition is a number nothing can rescale, and omitting it altogether starts the softmax fresh. scoreScale multiplies every logit and derives from the geometry when left out, and a wider expansionFactor walks further, folding more of the tail.

API

Types

DenseMaxsimConfigOptional per-call settings of late-interaction scoring.
usearch.ts:141 ↗
interface DenseMaxsimConfig

Fields

expansionFactor?numberBreadth of the candidates the walk explores; absent lets the engine derive it.
stats?DenseTraversalStatsThe reading this call overwrites with its own telemetry; absent skips reporting.
DenseMultiplyConfigOptional per-call settings of a sparse product.
usearch.ts:155 ↗
interface DenseMultiplyConfig

Fields

wanted?numberPairs kept per left row, the first ones its walk scored; absent keeps every pair.
expansionFactor?numberBreadth of the candidates, which widens the emitted pattern; absent lets the engine derive it.
stats?DenseTraversalStatsThe reading this call overwrites with its own telemetry; absent skips reporting.
DenseAttendConfigOptional per-call settings of attention.
usearch.ts:171 ↗
interface DenseAttendConfig

Fields

headCount?numberQuery heads sharing the index's key-value head, and with it one walk per token; absent is one.
scalar?ScalarThe scalar kind the attended rows land as; absent takes the queries' own.
expansionFactor?numberBreadth of the candidates the walk explores; absent lets the engine derive it.
scoreScale?numberThe logit scale, conventionally the inverse square root of the head dimensionality; absent derives it.
carry?Float32ArrayTwo floats per query row - the largest logit and the partition taken against it.
stats?DenseTraversalStatsThe reading this call overwrites with its own telemetry; absent skips reporting.
ValueRowOfResolves one scored key to its row of the values attend borrows, on the calling thread.
usearch.ts:358 ↗
type ValueRowOf = (key: Key) => number

Anything but the index of a row the values hold refuses the call with Status.ShapeMismatch.

ClassDenseProductsEvery pair a sparse product scored, as a ragged run with Apache Arrow offsets: left row i spans pairs offsets[i] up to offsets[i + 1], in traversal order rather than sorted.7
dense_index.ts:407 ↗
class DenseProducts extends ArenaView

maxsim lands its documents alike, one run per query, largest first.

DenseProductsMade by multiply and maxsim, never directly.
dense_index.ts:415 ↗
new DenseProducts(outputs: CompiledProducts, queries: number)
getBorrows the pairs of one left row or the documents of one query, without copying.
dense_index.ts:443 ↗
get(query: number): QueryMatches

Binding-only.

Parameters

querynumberWhich left row or query to read.

Returns

The keys and products that query's walk scored.
queriesLeft rows the product covers, or queries maxsim scored.
dense_index.ts:409 ↗
readonly queries: number
requiredPairs scored across every left row, or documents across every query, before wanted cut any.
dense_index.ts:411 ↗
readonly required: number
offsetsOne more offset than there are queries, non-decreasing.
dense_index.ts:423 ↗
readonly offsets: BigUint64Array
keysSixteen bytes per pair, naming each pair's indexed entry.
dense_index.ts:428 ↗
readonly keys: Uint8Array
scoresOne product per pair, positionally paired with keys.
dense_index.ts:433 ↗
readonly scores: Float64Array