typealias Distance = Double § Dense Indexes
A DenseIndex holds equidimensional vectors under keys, and its verbs below take every receiver above.
API
Types
DistanceA score between two entries, in the metric of the index that produced it.
StructDenseMetricA distance kind supported out of the box, with SIMD kernels dispatched by the machine's capabilities.8
struct DenseMetric All of them derive from a single inner-product kernel with pre-computed magnitudes; hamming and jaccard are bit-level, over the packed sub-byte kinds.
Arbitrary metrics belong to the baseline tier, which forfeits the SIMD kernel chains and the magnitude cache both.
Conforms to
Hashable, Sendable, CustomStringConvertible, EquatableunknownNo kind named - an unset field, refused by every constructor.
static let unknown: DenseMetric innerThe inner product.
static let inner: DenseMetric angularThe cosine distance.
static let angular: DenseMetric euclideanThe L2 distance.
static let euclidean: DenseMetric hammingThe count of differing bits.
static let hamming: DenseMetric jaccardThe bit-level jaccard distance.
static let jaccard: DenseMetric descriptionThe metric rendered as the library spells it.
var description: String { get } initReads a spelling back, and nil for one no metric carries.
init?(name: String) StructDenseOptionsThe construction options of a dense index, as initRam and initVram freeze them.8
struct DenseOptions Stored vectors always round up to the SIMD step quantum, so they stay cache-line aligned and every kernel runs tail-free; the arena is slightly larger for it, and that is not a knob.
Conforms to
SendabledimensionsThe width of the indexed vectors; zero leaves the geometry unfrozen, so the index admits no entries.
var dimensions: Int metricThe distance kind used between vectors.
var metric: DenseMetric quantizationThe scalar type vectors are stored and quantized into.
var quantization: Scalar connectivityThe bound on connections per node in the graph; zero derives.
var connectivity: Int valuesPerKeyThe bound on entries sharing one key; zero is unbounded.
var valuesPerKey: Int cpuClassThe class a host index dispatches as; nil derives the CPU's own.
var cpuClass: CPUClass? gpuClassThe class a device index dispatches as; nil derives the devices' own.
var gpuClass: GPUClass? initAn angular bf16 index, with every optional field derived.
init(dimensions: Int = 0, metric: DenseMetric = .angular, quantization: Scalar = .bf16, connectivity: Int = 0, valuesPerKey: Int = 0, cpuClass: CPUClass? = nil, gpuClass: GPUClass? = nil) ClassDenseIndexA Vector Search Index over equidimensional dense representations.32
final class DenseIndex A handle may be shared across threads, so no method here takes a lock. On a RAM handle, insertions and searches overlap freely: searches read only entries published behind the visibility watermark and never write to the graph, while insertions synchronize through per-node locks and publish in order. Serialization is the one exception, demanding a mutation-free window, though concurrent readers keep searching throughout.
Every verb called on the index runs on the calling thread and blocks until its outcome is
final; on(_:) runs the same verbs on an executor, and deferred(_:) submits them there.
Conforms to
SendableinitRamInitializes an empty, mutable index in host memory, one shard per memory domain; an empty list derives every node the process may allocate on.
static func initRam(
_ options: DenseOptions,
memoryDomains: [Int32] = []
) throws(USearchError) -> DenseIndex initVramInitializes an empty, mutable index in device memory, sharded across the named devices and never replicated; an empty list derives one free device.
static func initVram(
_ options: DenseOptions,
devices: [Int32] = []
) throws(USearchError) -> DenseIndex loadRamCopies a checkpoint into a new, mutable index in host memory.
static func loadRam(
_ path: String,
config: DenseLoadRamConfig = DenseLoadRamConfig()
) throws(USearchError) -> DenseIndex The geometry and metric come
from the checkpoint's own headers, and part p lands whole on shard p modulo the shard
count, wherever it was written from.
A cancelled or failed load creates no index.
loadVramCopies a checkpoint into a new, mutable index in device memory, with the placement rules of loadRam.
static func loadVram(
_ path: String,
config: DenseLoadVramConfig = DenseLoadVramConfig()
) throws(USearchError) -> DenseIndex loadRamBufferCopies a monolith from a buffer into a new index in host memory, as loadRam does a path.
static func loadRamBuffer(
_ buffer: RawSpan,
config: DenseLoadRamBufferConfig = DenseLoadRamBufferConfig()
) throws(USearchError) -> DenseIndex loadVramBufferCopies a monolith from a buffer into a new index in device memory, as loadVram does a path.
static func loadVramBuffer(
_ buffer: RawSpan,
config: DenseLoadVramBufferConfig = DenseLoadVramBufferConfig()
) throws(USearchError) -> DenseIndex viewRamServes a checkpoint in place through a memory mapping, as a read-only index whose mutating verbs report readOnly; the files must stay unmodified while it lives.
static func viewRam(_ path: String) throws(USearchError) -> DenseIndex viewNvmeServes a checkpoint in place from NVMe drives, read through the executor's submission rings, as a read-only index.
static func viewNvme(
_ path: String,
config: DenseViewNvmeConfig = DenseViewNvmeConfig()
) throws(USearchError) -> DenseIndex Until NVMe checkpoints carry keys, it reports unsupported.
metadataReadReads a checkpoint's geometry and metric without loading it - from the manifest's summary when the path names one, so no shard is opened.
static func metadataRead(
_ path: String,
executor: Executor? = nil
) throws(USearchError) -> DenseMetadata metadataReadBufferReads geometry and metric metadata from a serialized monolith buffer.
static func metadataReadBuffer(
_ buffer: RawSpan,
executor: Executor? = nil
) throws(USearchError) -> DenseMetadata onThe same verbs, blocking on executor after every call submitted there before them.
func on(_ executor: Executor) -> DenseIndexOn Binding-only: names the executor every call passes.
deferredThe same verbs, submitted to executor and answered at once as a Deferred.
func deferred(_ executor: Executor) -> DenseIndexDeferredOn Binding-only: names the executor and the deferred completion every call passes.
reserveReserves capacity for a total number of entries, the current size included.
func reserve(_ capacity: Int) throws(USearchError) clearErases all entries, keeping the reserved capacity and the frozen geometry.
func clear() throws(USearchError) describeReports the frozen metadata and the current counters in one call.
func describe() throws(USearchError) -> DenseIndexDescription countThe number of entries stored under a key; zero means the index holds none.
func count(key: Key) throws(USearchError) -> Int containsWhether the index holds at least one entry under a key.
func contains(key: Key) throws(USearchError) -> Bool Binding-only: calls count.
getRetrieves every vector stored under a key, one row per entry sharing the key, in insertion order, converted to Value; an absent key yields no rows.
func get<Value>(
key: Key,
as value: Value.Type,
arena: Arena
) throws(USearchError) -> DenseVectors<Value> where Value : ScalarValue The rows land in arena, read in
place by the result until the arena's next call.
insertInserts rows of vectors under their keys - the native batch operation, where a single vector is a one-row matrix.
func insert(
keys: [Key],
vectors: RowsView,
config: DenseInsertConfig = DenseInsertConfig()
) throws(USearchError) Overlaps safely with concurrent searches on the same handle.
The input scalar type is independent of the stored quantization; the library converts on ingest. A batch is all-or-nothing, and its entries become visible at once: every fallible step runs before the first entry identity is claimed, so a thrown failure leaves the index untouched. Every entry is claimed before the handler's first report, so it reports but cannot cancel.
refineRe-selects the neighbors of every stored entry against the finished graph, reporting the entries re-selected and changed and the islands healed; zero changed means settled.
@discardableResult func refine(
config: DenseRefineConfig = DenseRefineConfig()
) throws(USearchError) -> DenseRefineReport A build step rather than a repair: a cheap insertion pass followed by one refinement
reaches a better graph, in less total time, than one expensive insertion pass - so a wide
expansion factor belongs here rather than in insert. Searches may run throughout.
healthAudits how much of the graph a search can reach from its landmarks; entries it misses stay unfound until a refinement re-links them.
func health() throws(USearchError) -> DenseReachability searchResolves rows of queries into up to wanted matches apiece, written in arena - the native batch operation, parallelized internally on an executor.
func search(
_ queries: RowsView,
wanted: Int,
arena: Arena,
config: DenseSearchConfig = DenseSearchConfig()
) throws(USearchError) -> Matches The result reads the arena in place until its next call. Never writes to the graph, so any number of these may overlap with concurrent insertions.
maxsimLate-interaction scoring of multi-row queries against multi-vector documents: every query row takes its best match within a key, and those maxima sum to that key's score.
func maxsim(
_ queries: RowsView,
rowsPerQuery: Int,
wanted: Int,
arena: Arena,
config: DenseMaxsimConfig = DenseMaxsimConfig()
) throws(USearchError) -> DenseProducts A document is one key holding its token rows, which is what a valuesPerKey above one
exists for. rowsPerQuery is the token count each query occupies, queries holds them end
to end, and up to wanted documents land in arena as one ragged run, one run per whole
query rather than per token, largest score first; a zero wanted keeps every document the
query scored. Approximate downward only: a token whose neighborhood the walk never entered
contributes no maximum, so a key can score below its exact value but never above it. Runs on
an inner-product index alone.
See https://arxiv.org/abs/2004.12832 - ColBERT, which introduced this scoring.
multiplyThe approximate sparse product of rows against the indexed vectors - the linear-algebra sibling of search, reporting every pair the traversal scored rather than only the nearest of them.
func multiply(
_ left: RowsView,
arena: Arena,
config: DenseMultiplyConfig = DenseMultiplyConfig()
) throws(USearchError) -> DenseProducts Pairs arrive in traversal order rather than sorted, and carry no duplicates. A non-zero
wanted keeps the first pairs each row's walk scored, and required says how many there
were. The keys, products and row offsets land in arena as one ragged run. Runs on an
inner-product index alone, since a product needs logits rather than distances.
attendSoftmax attention of a run of query heads over every indexed key their graph walks score, folded as Flash Attention folds key tiles, with no top-k cut.
func attend<Value>(
_ queries: RowsView,
lookup: ValueLookup,
as value: Value.Type,
arena: Arena,
config: DenseAttendConfig = DenseAttendConfig()
) throws(USearchError) -> DenseVectors<Value> where Value : ScalarValue Row t · headCount + h of queries, outputs and carry is head h of token t: the
headCount query heads of grouped-query attention share the index's one key-value head and
one walk per token. The index stores no values, so lookup resolves the keys of every
scored batch to rows of its values.
The outputs land in arena as Value rows, and come back divided by the partition. Passing
a carry asks for the softmax state, one entry per query row, which folds this branch into
another over the same queries by partial-softmax rescaling. Runs on an inner-product index
alone, and refuses a VRAM one with device, whose walk needs a device function as its lookup.
See https://arxiv.org/abs/2205.14135 - Flash Attention, whose recurrence this reuses.
absorbAbsorbs every donor into this index, keeping their graphs rather than re-inserting their vectors.
@discardableResult func absorb(
_ donors: [DenseIndex],
config: DenseAbsorbConfig = DenseAbsorbConfig()
) throws(USearchError) -> DenseRefineReport Every donor absorbed is consumed, and every later verb on one reports
uninitialized; an absorb stopped early hands the rest back to their indexes once final. A
donor named twice refuses with invalidArgument, and the receiver itself with
configMismatch, as the library does.
splitSplits this index into receivers, the inverse of absorb: graph-local parts keeping their edges, one per receiver, leaving this index empty but live.
@discardableResult func split(
into receivers: [DenseIndex],
config: DenseSplitConfig = DenseSplitConfig()
) throws(USearchError) -> DenseRefineReport Receivers must be empty and
share the index's residency, scalar, class, metric, geometry and shard count, or the call
answers configMismatch, and a refusal moves nothing.
sampleDraws up to wanted keys around config.focus, or across the whole index, each hanging from a shown or earlier draw and placed in three dimensions beside the shown ones, written in arena; a larger wanted draws a superset.
func sample(
wanted: Int,
arena: Arena,
config: DenseSampleConfig = DenseSampleConfig()
) throws(USearchError) -> DenseDraws Only RAM samples yet.
saveWrites the index under a path, in the layout its extension picks - one .safegraphs monolith, or a .safegraphs.json manifest beside its shards.
func save(path: String, config: DenseSaveConfig = DenseSaveConfig()) throws(USearchError) Demands a mutation-free window; concurrent readers keep searching throughout.
saveBufferWrites the index as one monolith, the same bytes save(path:) writes to a .safegraphs path, into arena.
func saveBuffer(
arena: Arena,
config: DenseSaveBufferConfig = DenseSaveBufferConfig()
) throws(USearchError) -> Monolith cursorOpens a walk over the current contents, for export or migration.
func cursor(starts: [Key] = []) throws(USearchError) -> DenseCursor Each shard's publication
watermark is read as the walk enters it, so an entry once passed is never revisited. Without
starts it yields every entry as stored; with them, the start keys' own entries first in
order, then the entries those link to breadth-first, each once.
searchStreamOpens a resumable nearest-neighbor stream over one query row, so a caller that post-filters aggressively pulls until satisfied rather than guessing a wanted count upfront.
func searchStream(
_ query: RowsView,
config: DenseSearchStreamInitConfig = DenseSearchStreamInitConfig()
) throws(USearchError) -> DenseSearchStream