C# · Linear Algebra

Linear Algebra

3.0.0-rc.1 · 4c3e5023

§ Sparse Multiplication

DenseProducts products = index.On(pool).Multiply(RowsView.RowsOf<float>(left, dimensions: 3), arena, wanted: 64);
ReadOnlySpan<Key> reached = products.Keys(0);         // Every indexed entry the first walk scored
ReadOnlySpan<double> dots = products.Scores(0);       // Paired with those keys, in traversal order

Every pair a traversal scored is an exact product the caller would otherwise recompute, so Multiply hands them all back, ragged per left row, with their offsets in the arena beside them. It runs on the inner-product kinds alone, and the pairs a walk never reaches are the low and negative products.

§ Late Interaction

DenseOptions colbert = DenseOptions.Default with
{
    Dimensions = 128,
    Metric = DenseMetric.Inner,      // Only the inner-product kinds score logits
    Quantization = Scalar.BF16,
    ValuesPerKey = 32,               // A document is one key holding its token rows
};

DenseProducts scored = index.On(pool).Maxsim(RowsView.RowsOf<float>(queryTokens, dimensions: 128), rowsPerQuery: 32, wanted: 10, arena);
ReadOnlySpan<Key> documents = scored.Keys(0);         // The first query's best documents, largest first
ReadOnlySpan<double> totals = scored.Scores(0);       // Each document's summed maxima

Every query row takes its best match within a key, and those maxima sum to that key’s score, so the wanted count is the “k” over documents rather than over rows. Each query lands a ragged run in the arena, the same DenseProducts that Multiply returns, and a wanted count of zero keeps every document the query scored. That is what a values-per-key bound above one exists for, and a graph frozen under a distance kind refuses with Status.ConfigMismatch.

§ 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 come back already divided by the partition. The index stores no values, so every scored key resolves to a row of a values RowsView the caller lends, as wide as the keys and stored in the index’s own quantization.

DenseSoftmaxState[] carry = new DenseSoftmaxState[queryRows];
DenseVectors attended = index.On(pool).Attend(
    RowsView.RowsOf<float>(queryHeads, 128), RowsView.RowsOf<ushort>(valueCache, 128, Scalar.BF16),
    rowOf: key => positionOf[key],                       // Runs on the executor's threads together
    arena, headCount: 8, carry: carry);                  // One row per query row, as the queries' kind
ReadOnlySpan<float> first = attended.Row<float>(0);

With headCount: 8, row t * 8 + h of the queries is head h of token t, and the heads of one token share its walk. A key resolving outside the values, or values of another kind or width than the library reads, refuses the call with Status.ShapeMismatch, since a missing row is a wrong softmax rather than a smaller one. The carry holds one DenseSoftmaxState per query row, which folds this branch into another over the same queries, such as a recent window kept out of the index; it is state the caller carries between calls, so it stays lent rather than written into the arena. An outputsScalar argument lands the attended rows as another kind than the queries’ own. rowOf is a host function, so it serves RAM handles alone: a VRAM walk resolves rows through a device function, which this binding does not build, and an Attend into a VRAM arena refuses with Status.Device.