struct Scalar Sub-byte kinds pack several
dimensions per byte, as their xN suffixes spell.
Conforms to
Hashable, Sendable, CustomStringConvertible, EquatableEvery output whose length only the call knows, such as matches, rows, products, monoliths and cursor or stream batches, lands in an Arena you pass, and the verb answers a view over it without copying.
There is one verb per operation: no allocating twin, no caller buffers, and no default arena. Arena(on: executor) places it where that executor computes, decided once, and Arena() makes host pages for the calling thread.
Each call rewinds its arena, so the previous call’s views die and a steady loop allocates once it reaches its high-water mark.
To keep a result past the next call, or to read one while making the next, pass that call another arena.
An arena serves one call at a time, so each deferred call needs its own.
Views stay on the arena’s thread, which the compiler checks, and reading a stale one traps as an out-of-bounds index does.
A view cannot be ~Escapable while its lifetime annotation is still behind an experimental feature flag as of Swift 6.4, so it checks its arena at runtime instead.
An arena from a VRAM executor holds device memory, which host accessors refuse with a trap, and serves only that executor’s calls, keeping the executor alive until the arena goes.
let arena = try Arena(on: executor) // Arena() is host pages for the calling thread
for batch in batches {
let matches = try index.on(executor).search(batch, wanted: 10, arena: arena) // rewinds: last batch's matches are gone
total += matches.joined().reduce(0) { $0 + $1.distance }
}
let found = try Arena(), fetched = try Arena() // two live results, two arenas
for match in try index.search(query, wanted: 10, arena: found)[0] {
rerank(match.key, try index.get(key: match.key, as: Float.self, arena: fetched))
}
let calls = try shards.indices.map { at in
try shards[at].deferred(executors[at]).search(queries, wanted: 10, arena: arenas[at]) // one executor and one arena per submitted call
}
var results: [Matches] = []
for call in calls { results.append(try await call.value) } // or: try call.wait()A result keeps the arena it reads alive, so it outlives the index and the caller’s reference alike.
A RowsView borrows a NumKong MatrixView, or keeps a NumKong Tensor alive.
A matrix view borrows and never owns, so it stays valid only for as long as the storage it points into, which is why a call over one sits inside a withUnsafeBufferPointer.
let padded: [Float] = load() // 768 dimensions pitched at 1024
try padded.withUnsafeBufferPointer { buffer in
let rows = MatrixView(baseAddress: buffer.baseAddress!, rows: 1_000, columns: 768, rowStrideBytes: 1024 * 4)
try index.on(executor).insert(keys: keys, vectors: RowsView(rows))
}The library checks every row view’s width against the index geometry itself, so a mismatch surfaces as its own shapeMismatch.
Sub-byte kinds count logical dimensions rather than stored values, so a U1x8 view one value wide carries eight dimensions.
The scalar type of what you hand the index is independent of the scalar type it stores: the library converts on ingest and on export. Double, Float, Int8, and UInt8 are native, and the low-precision encodings come from NumKong: BFloat16, E5M2, E4M3, E3M2, E2M3 and U1x8, each of which converts from a Float rather than only naming a bit pattern.
let row = [BFloat16(float: 0.3), BFloat16(float: 0.5), BFloat16(float: 1.2)]Scalar, DenseMetric and Mood spell themselves through the library’s own name tables, and Scalar(name: "bf16") reads one back.
A search writes up to wanted matches per query into its arena, ranked nearest first, and answers Matches reading them in place: matches[query] is one query’s QueryMatches, so neither level copies a match out.
A query’s own count stops at the matches the call wrote, however many were wanted.
Both levels are RandomAccessCollection, so a query walks, maps and slices like any other sequence.
let stats = DenseTraversalStats()
let matches = try index.on(executor).search(queries, wanted: 10, arena: arena, config: DenseSearchConfig(
expansionFactor: 64, stats: stats))
for query in 0 ..< matches.count {
for match in matches[query] { print(match.key, match.distance) }
}
print(stats.visitedMembers, stats.computedDistances, stats.restarts)expansionFactor is the breadth of the candidates the walk explores, and zero derives it from the geometry.
The insert, refine, search, maxsim, multiply and attend configs take a DenseTraversalStats, and every call overwrites the stats it is handed rather than accumulating, so a sweep sums them itself.
Insertions and searches overlap freely on one handle: searches read only entries published behind the visibility watermark, while insertions publish in order.
ScalarA stored or exchanged scalar type, named by width and encoding.17struct Scalar Sub-byte kinds pack several
dimensions per byte, as their xN suffixes spell.
Hashable, Sendable, CustomStringConvertible, EquatableunknownNo scalar named - an unset field.static let unknown: Scalar f6464-bit double precision.static let f64: Scalar f3232-bit single precision.static let f32: Scalar bf16The brain floating-point format.static let bf16: Scalar f16IEEE half precision.static let f16: Scalar e5m28-bit floating point with five exponent bits.static let e5m2: Scalar e4m38-bit floating point with four exponent bits.static let e4m3: Scalar e3m26-bit floating point with three exponent bits.static let e3m2: Scalar e2m36-bit floating point with two exponent bits.static let e2m3: Scalar i8An 8-bit signed integer.static let i8: Scalar i4x2Two 4-bit signed integers per byte.static let i4x2: Scalar i32A 32-bit signed integer, the coordinates of geospatial points.static let i32: Scalar u8An 8-bit unsigned integer.static let u8: Scalar u4x2Two 4-bit unsigned integers per byte.static let u4x2: Scalar u1x8Eight single-bit dimensions per byte.static let u1x8: Scalar descriptionThe kind rendered as the library spells it.var description: String { get } initReads a spelling back, and nil for one no kind carries.init?(name: String) ScalarValueAn element type a matrix may hold.1protocol ScalarValue Every conformance below names a type Swift or NumKong
already owns, so a caller converts a Float into any stored kind rather than only naming it,
and this SDK defines no scalar of its own.
scalarKindThe scalar kind this element type crosses the boundary as.static var scalarKind: Scalar { get } RowsViewA borrowed run of rows along a call's batch axis - the input of every verb whose rows share one width - with the row count that sets the call's batch size.6struct RowsView Built over a NumKong MatrixView, it borrows and stays valid only as long as the storage the
view points into, so a memory-mapped dataset reaches the index with no reshape and no copy.
Built over a Tensor, it keeps the tensor alive, which is what a deferred call needs.
SendabledataThe first row.let data: UnsafeRawPointer rowsThe rows the run carries, which the call takes as its batch axis.let rows: Int strideBytes between the starts of consecutive rows; zero repeats the first row.let stride: Int columnsLogical values per row, wider than a view's columns for the packed sub-byte kinds.let columns: Int scalarThe scalar kind the rows hold.let scalar: Scalar initBorrows the rows of a NumKong view, which must outlive every call reading them.init<Element>(_ matrix: MatrixView<Element>) where Element : ScalarValue init<Element>(_ tensor: Tensor<Element>) where Element : ScalarValue Views the rows of a NumKong tensor, keeping it alive as long as the view is.
MatchOne retrieved entry.2keyThe matched entry's key.let key: Key distanceIts distance to the query, in the index's metric.let distance: Distance MatchesThe ranked matches a search or a stream pull wrote into an arena, one list per query, read in place.4struct Matches RandomAccessCollection, BidirectionalCollection, Collection, SequenceendIndexOne past the last query the call resolved.let endIndex: Int startIndexThe first query's position, which a collection walk starts from.var startIndex: Int { get } columnsThe columns per query the call wrote, at most its wanted - the "k" in "kANN".var columns: Int { get } subscriptOne query's matches, as many as the call wrote.subscript(query: Int) -> QueryMatches { get } QueryMatchesOne query's matches, or one left row's pairs, read in place from the arena they live in.3struct QueryMatches RandomAccessCollection, BidirectionalCollection, Collection, SequencestartIndexThe first match's position, which a collection walk starts from.var startIndex: Int { get } endIndexOne past the last match the call wrote.var endIndex: Int { get } subscriptThe match at a position, key and score read from their paired arrays.subscript(position: Int) -> Match { get } TraversalHow a search reaches the entries it scores.4struct Traversal Hashable, Sendable, CustomStringConvertible, EquatablewalkWalks the graph, scoring the entries the expansion reaches - approximate, the default.static let walk: Traversal scanScores every entry published when the query's scan starts - exact, on RAM and NVMe handles.static let scan: Traversal descriptionThe traversal rendered as the library spells it.var description: String { get } initReads a spelling back, and nil for one no traversal carries.init?(name: String) ArenaMemory the library writes every output whose length scales with a call into - matches, rows, products, monoliths, cursor and stream batches - reused across calls so a steady loop allocates once.2final class Arena Every such verb requires one and answers a view reading it in place. Each call given the arena
rewinds it, so the views an earlier call answered stop reading: any read of them traps, as an
out-of-bounds index does. To keep a result past the next call, give that call another arena.
Every call borrows the arena, deferred ones included, and one naming it while another call
writes it is refused with contended. An arena serves one call at a time and is not Sendable.
One in GPU memory serves only calls on the executor that made it, and keeps that executor alive.
residencyWhere its memory lives, which the executor that made it decided.let residency: Residency initMakes an arena where executor computes, host pages or its GPU's memory, decided once; nil makes host pages for calls on the calling thread.init(on executor: Executor? = nil) throws(USearchError) DenseTraversalStatsThe traversal telemetry one call reports, passed in a config and written by the call.4final class DenseTraversalStats Every call overwrites it rather than accumulating, so a sweep over many batches sums them itself, and it belongs to one call until that call is final.
Sendableinitinit() visitedMembersThe graph members the call visited.var visitedMembers: Int { get } computedDistancesThe distances the call computed, diversity re-selections included.var computedDistances: Int { get } restartsThe starvation re-seedings - a graph-connectivity health signal rather than a cost counter.var restarts: Int { get } DenseInsertConfigThe knobs of insert.5struct DenseInsertConfig Every zeroed field derives, which is what almost every caller wants.
expansionFactorThe breadth of the candidates the insertion walk searches for neighbors with; zero derives it from the geometry.var expansionFactor: Int progressCalled with entries admitted; it reports and cannot cancel.var progress: Progress? progressStepsReports the call makes at most, the last at completion; zero derives to ten.var progressSteps: Int statsThe traversal telemetry to write.var stats: DenseTraversalStats? initinit(expansionFactor: Int = 0, progress: Progress? = nil, progressSteps: Int = 0, stats: DenseTraversalStats? = nil) DenseSearchConfigThe knobs of search.7expansionFactorThe breadth of the candidates the walk explores; zero derives it from the geometry, and a scan ignores it.var expansionFactor: Int traversalWalks the graph by default; .scan scores every entry exactly, on RAM and NVMe handles, and a VRAM handle refuses it with unsupported.var traversal: Traversal predicateNarrows the matches to the keys it approves.var predicate: (@Sendable (Key) throws -> Bool)? Non-matching entries still route the
traversal, so recall does not collapse at low selectivity. It runs on the executor's
threads together, so it must be safe for concurrent use; an error it throws never unwinds
into the engine, but rejects that key, and the first one is what the call throws. A VRAM
handle refuses one with device, since host code cannot narrow a device walk.
progressCalled with queries resolved.var progress: Progress? progressStepsReports the call makes at most, the last at completion; zero derives to ten.var progressSteps: Int statsThe traversal telemetry to write.var stats: DenseTraversalStats? initinit(expansionFactor: Int = 0, traversal: Traversal = .walk, predicate: (@Sendable (Key) throws -> Bool)? = nil, progress: Progress? = nil, progressSteps: Int = 0, stats: DenseTraversalStats? = nil) BFloat161extension BFloat16 : ScalarValue scalarKindstatic var scalarKind: Scalar { get } E5M21extension E5M2 : ScalarValue scalarKindstatic var scalarKind: Scalar { get } E4M31extension E4M3 : ScalarValue scalarKindstatic var scalarKind: Scalar { get } E3M21extension E3M2 : ScalarValue scalarKindstatic var scalarKind: Scalar { get } E2M31extension E2M3 : ScalarValue scalarKindstatic var scalarKind: Scalar { get } I4x21extension I4x2 : ScalarValue scalarKindstatic var scalarKind: Scalar { get } U4x21extension U4x2 : ScalarValue scalarKindstatic var scalarKind: Scalar { get } U1x81extension U1x8 : ScalarValue scalarKindstatic var scalarKind: Scalar { get } Double1extension Double : ScalarValue scalarKindstatic var scalarKind: Scalar { get } Float1extension Float : ScalarValue scalarKindstatic var scalarKind: Scalar { get } Float161extension Float16 : ScalarValue scalarKindstatic var scalarKind: Scalar { get } Int81extension Int8 : ScalarValue scalarKindstatic var scalarKind: Scalar { get } UInt81extension UInt8 : ScalarValue scalarKindstatic var scalarKind: Scalar { get }