Swift · Inputs and Outputs

Inputs and Outputs

import USearch 3.0.0-rc.1 · 4c3e5023

§ Arenas

Every 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.

§ Rows & Scalars

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.

API

StructScalarA stored or exchanged scalar type, named by width and encoding.17
Types.swift:279 ↗
struct Scalar

Sub-byte kinds pack several dimensions per byte, as their xN suffixes spell.

Conforms to

Hashable, Sendable, CustomStringConvertible, Equatable
unknownNo scalar named - an unset field.
Types.swift:284 ↗
static let unknown: Scalar
f6464-bit double precision.
Types.swift:286 ↗
static let f64: Scalar
f3232-bit single precision.
Types.swift:288 ↗
static let f32: Scalar
bf16The brain floating-point format.
Types.swift:290 ↗
static let bf16: Scalar
f16IEEE half precision.
Types.swift:292 ↗
static let f16: Scalar
e5m28-bit floating point with five exponent bits.
Types.swift:294 ↗
static let e5m2: Scalar
e4m38-bit floating point with four exponent bits.
Types.swift:296 ↗
static let e4m3: Scalar
e3m26-bit floating point with three exponent bits.
Types.swift:298 ↗
static let e3m2: Scalar
e2m36-bit floating point with two exponent bits.
Types.swift:300 ↗
static let e2m3: Scalar
i8An 8-bit signed integer.
Types.swift:302 ↗
static let i8: Scalar
i4x2Two 4-bit signed integers per byte.
Types.swift:304 ↗
static let i4x2: Scalar
i32A 32-bit signed integer, the coordinates of geospatial points.
Types.swift:306 ↗
static let i32: Scalar
u8An 8-bit unsigned integer.
Types.swift:308 ↗
static let u8: Scalar
u4x2Two 4-bit unsigned integers per byte.
Types.swift:310 ↗
static let u4x2: Scalar
u1x8Eight single-bit dimensions per byte.
Types.swift:312 ↗
static let u1x8: Scalar
descriptionThe kind rendered as the library spells it.
Types.swift:315 ↗
var description: String { get }
initReads a spelling back, and nil for one no kind carries.
Types.swift:318 ↗
init?(name: String)
ProtocolScalarValueAn element type a matrix may hold.1
Types.swift:339 ↗
protocol 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.

Conforming types

scalarKindThe scalar kind this element type crosses the boundary as.
Types.swift:341 ↗
static var scalarKind: Scalar { get }
StructRowsViewA 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.6
Types.swift:392 ↗
struct 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.

Conforms to

Sendable
dataThe first row.
Types.swift:394 ↗
let data: UnsafeRawPointer
rowsThe rows the run carries, which the call takes as its batch axis.
Types.swift:396 ↗
let rows: Int
strideBytes between the starts of consecutive rows; zero repeats the first row.
Types.swift:398 ↗
let stride: Int
columnsLogical values per row, wider than a view's columns for the packed sub-byte kinds.
Types.swift:400 ↗
let columns: Int
scalarThe scalar kind the rows hold.
Types.swift:402 ↗
let scalar: Scalar
initBorrows the rows of a NumKong view, which must outlive every call reading them.
Types.swift:407 ↗
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.

StructMatchOne retrieved entry.2
Types.swift:430 ↗
struct Match

Conforms to

Equatable, Sendable
keyThe matched entry's key.
Types.swift:432 ↗
let key: Key
distanceIts distance to the query, in the index's metric.
Types.swift:434 ↗
let distance: Distance
StructMatchesThe ranked matches a search or a stream pull wrote into an arena, one list per query, read in place.4
Types.swift:438 ↗
struct Matches

Conforms to

RandomAccessCollection, BidirectionalCollection, Collection, Sequence
endIndexOne past the last query the call resolved.
Types.swift:444 ↗
let endIndex: Int
startIndexThe first query's position, which a collection walk starts from.
Types.swift:447 ↗
var startIndex: Int { get }
columnsThe columns per query the call wrote, at most its wanted - the "k" in "kANN".
Types.swift:450 ↗
var columns: Int { get }
subscriptOne query's matches, as many as the call wrote.
Types.swift:453 ↗
subscript(query: Int) -> QueryMatches { get }
StructQueryMatchesOne query's matches, or one left row's pairs, read in place from the arena they live in.3
Types.swift:466 ↗
struct QueryMatches

Conforms to

RandomAccessCollection, BidirectionalCollection, Collection, Sequence
startIndexThe first match's position, which a collection walk starts from.
Types.swift:477 ↗
var startIndex: Int { get }
endIndexOne past the last match the call wrote.
Types.swift:480 ↗
var endIndex: Int { get }
subscriptThe match at a position, key and score read from their paired arrays.
Types.swift:483 ↗
subscript(position: Int) -> Match { get }
StructTraversalHow a search reaches the entries it scores.4
Types.swift:904 ↗
struct Traversal

Conforms to

Hashable, Sendable, CustomStringConvertible, Equatable
walkWalks the graph, scoring the entries the expansion reaches - approximate, the default.
Types.swift:909 ↗
static let walk: Traversal
scanScores every entry published when the query's scan starts - exact, on RAM and NVMe handles.
Types.swift:911 ↗
static let scan: Traversal
descriptionThe traversal rendered as the library spells it.
Types.swift:914 ↗
var description: String { get }
initReads a spelling back, and nil for one no traversal carries.
Types.swift:917 ↗
init?(name: String)
ClassArenaMemory 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.2
Types.swift:1151 ↗
final 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.
Types.swift:1155 ↗
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.
Types.swift:1163 ↗
init(on executor: Executor? = nil) throws(USearchError)
ClassDenseTraversalStatsThe traversal telemetry one call reports, passed in a config and written by the call.4
DenseIndex.swift:197 ↗
final 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.

Conforms to

Sendable
init
DenseIndex.swift:201 ↗
init()
visitedMembersThe graph members the call visited.
DenseIndex.swift:209 ↗
var visitedMembers: Int { get }
computedDistancesThe distances the call computed, diversity re-selections included.
DenseIndex.swift:211 ↗
var computedDistances: Int { get }
restartsThe starvation re-seedings - a graph-connectivity health signal rather than a cost counter.
DenseIndex.swift:214 ↗
var restarts: Int { get }
StructDenseInsertConfigThe knobs of insert.5
DenseIndex.swift:255 ↗
struct 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.
DenseIndex.swift:258 ↗
var expansionFactor: Int
progressCalled with entries admitted; it reports and cannot cancel.
DenseIndex.swift:260 ↗
var progress: Progress?
progressStepsReports the call makes at most, the last at completion; zero derives to ten.
DenseIndex.swift:262 ↗
var progressSteps: Int
statsThe traversal telemetry to write.
DenseIndex.swift:264 ↗
var stats: DenseTraversalStats?
init
DenseIndex.swift:266 ↗
init(expansionFactor: Int = 0, progress: Progress? = nil, progressSteps: Int = 0, stats: DenseTraversalStats? = nil)
StructDenseSearchConfigThe knobs of search.7
DenseIndex.swift:304 ↗
struct DenseSearchConfig

Every zeroed field derives.

expansionFactorThe breadth of the candidates the walk explores; zero derives it from the geometry, and a scan ignores it.
DenseIndex.swift:307 ↗
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.
DenseIndex.swift:310 ↗
var traversal: Traversal
predicateNarrows the matches to the keys it approves.
DenseIndex.swift:316 ↗
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.
DenseIndex.swift:318 ↗
var progress: Progress?
progressStepsReports the call makes at most, the last at completion; zero derives to ten.
DenseIndex.swift:320 ↗
var progressSteps: Int
statsThe traversal telemetry to write.
DenseIndex.swift:322 ↗
var stats: DenseTraversalStats?
init
DenseIndex.swift:324 ↗
init(expansionFactor: Int = 0, traversal: Traversal = .walk, predicate: (@Sendable (Key) throws -> Bool)? = nil, progress: Progress? = nil, progressSteps: Int = 0, stats: DenseTraversalStats? = nil)
StructBFloat161
extension BFloat16 : ScalarValue
scalarKind
Types.swift:351 ↗
static var scalarKind: Scalar { get }
StructE5M21
extension E5M2 : ScalarValue
scalarKind
Types.swift:357 ↗
static var scalarKind: Scalar { get }
StructE4M31
extension E4M3 : ScalarValue
scalarKind
Types.swift:360 ↗
static var scalarKind: Scalar { get }
StructE3M21
extension E3M2 : ScalarValue
scalarKind
Types.swift:363 ↗
static var scalarKind: Scalar { get }
StructE2M31
extension E2M3 : ScalarValue
scalarKind
Types.swift:366 ↗
static var scalarKind: Scalar { get }
StructI4x21
extension I4x2 : ScalarValue
scalarKind
Types.swift:372 ↗
static var scalarKind: Scalar { get }
StructU4x21
extension U4x2 : ScalarValue
scalarKind
Types.swift:378 ↗
static var scalarKind: Scalar { get }
StructU1x81
extension U1x8 : ScalarValue
scalarKind
Types.swift:381 ↗
static var scalarKind: Scalar { get }
StructDouble1
extension Double : ScalarValue
scalarKind
Types.swift:345 ↗
static var scalarKind: Scalar { get }
StructFloat1
extension Float : ScalarValue
scalarKind
Types.swift:348 ↗
static var scalarKind: Scalar { get }
StructFloat161
extension Float16 : ScalarValue
scalarKind
Types.swift:354 ↗
static var scalarKind: Scalar { get }
StructInt81
extension Int8 : ScalarValue
scalarKind
Types.swift:369 ↗
static var scalarKind: Scalar { get }
StructUInt81
extension UInt8 : ScalarValue
scalarKind
Types.swift:375 ↗
static var scalarKind: Scalar { get }