type Distance = float64 API
Types
DistanceDistance is a score between two entries, in the metric of the index that produced it.
StructDenseIndexDenseIndex is a Vector Search Index over equidimensional dense representations.31
type DenseIndex struct {
DenseIndexOn
// contains filtered or unexported fields
} A handle may be shared across goroutines. 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.
Its verbs come from the embedded DenseIndexOn and run on the calling goroutine; On runs the same verbs blocking on an executor, and Deferred submits them there.
DenseInitRamDenseInitRam creates an empty, mutable index in host memory, one shard per NUMA node memoryDomains names; empty derives every node the process may use.
func DenseInitRam(
options DenseOptions,
memoryDomains []int32,
) (*DenseIndex, error) DenseInitVramDenseInitVram creates an empty, mutable index in device memory, sharded across the CUDA ordinals devices names and never replicated; empty derives one free device.
func DenseInitVram(
options DenseOptions,
devices []int32,
) (*DenseIndex, error) DenseLoadRamDenseLoadRam copies a checkpoint into a new, mutable index in host memory.
func DenseLoadRam(
path string,
config *DenseLoadRamConfig,
) (*DenseIndex, error) The path names a manifest of a sharded checkpoint or a single .safegraphs monolith.
DenseLoadRamBufferDenseLoadRamBuffer copies a monolith from a buffer into a new, mutable index in host memory, as DenseLoadRam does a path.
func DenseLoadRamBuffer(
buffer []byte,
config *DenseLoadRamBufferConfig,
) (*DenseIndex, error) DenseLoadVramDenseLoadVram copies a checkpoint into a new, mutable index in device memory, with the placement rules of DenseLoadRam.
func DenseLoadVram(
path string,
config *DenseLoadVramConfig,
) (*DenseIndex, error) DenseLoadVramBufferDenseLoadVramBuffer copies a monolith from a buffer into a new, mutable index in device memory.
func DenseLoadVramBuffer(
buffer []byte,
config *DenseLoadVramBufferConfig,
) (*DenseIndex, error) DenseViewNvmeDenseViewNvme serves a checkpoint in place from NVMe drives, read through the executor's submission rings, as a read-only index.
func DenseViewNvme(
path string,
config *DenseViewNvmeConfig,
) (*DenseIndex, error) Until NVMe checkpoints carry keys, it reports StatusUnsupported.
DenseViewRamDenseViewRam serves a checkpoint in place through a memory mapping, as a read-only index whose mutating verbs report StatusReadOnly; the files must stay unmodified while it lives.
func DenseViewRam(path string) (*DenseIndex, error) CloseClose releases the index, waiting for every deferred call naming it.
func (index *DenseIndex) Close() error It is idempotent, and a closed handle must not be used again.
DeferredDeferred is the receiver whose verbs submit their calls to executor, each answering a Deferred from the same arguments.
func (index *DenseIndex) Deferred(
executor *Executor,
) DenseIndexDeferredOn Binding-only.
OnOn is the receiver whose verbs block on executor, after the calls submitted there before them.
AbsorbAbsorb moves every donor into this index, one after another, keeping their graphs rather than re-inserting their vectors: each donor's entries append shard by shard, then a refinement links them across the appended part.
Inherited from DenseIndexOn
func (on DenseIndex) Absorb(
donors []*DenseIndex,
config *DenseAbsorbConfig,
) (DenseRefineReport, error) Donors must share the index's residency, scalar, metric and geometry. Readers of a RAM index keep searching throughout; a VRAM one admits no overlapping search.
Each absorbed donor is consumed, and every later verb on it reports StatusUninitialized. A stop between donors, or a failure, leaves every donor not yet absorbed a live handle the caller still owns and closes; until the call is final every donor reports StatusUninitialized.
AttendAttend is softmax attention of every query row over every indexed key its walk scores, folded as Flash Attention folds key tiles, with no top-k cut, writing one row per query into arena, already divided by the partition.
Inherited from DenseIndexOn
func (on DenseIndex) Attend(
queries RowsView,
lookup ValueLookup,
arena *Arena,
config *DenseAttendConfig,
) (DenseVectors, error) The index holds one layer's keys for one key-value head and no values, so lookup resolves every scored key to its value row. Runs on an inner-product index alone, since a softmax needs logits, and on RAM and NVMe handles, since a VRAM walk needs a device lookup no Go function can be.
See https://arxiv.org/abs/2205.14135 - Flash Attention, whose recurrence this reuses.
ClearClear erases all entries, keeping the reserved capacity and the frozen geometry.
Inherited from DenseIndexOn
func (on DenseIndex) Clear() error No other verb may overlap it on the same handle.
ContainsContains reports whether the index holds at least one entry under a key, as a nonzero Count.
Inherited from DenseIndexOn
func (on DenseIndex) Contains(key Key) (bool, error) CountCount reports how many entries are stored under a key; zero means the index holds none.
CursorCursor opens a walk over the current contents.
Inherited from DenseIndexOn
func (on DenseIndex) Cursor(starts ...Key) (*DenseCursor, error) 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.
DescribeDescribe reports the frozen metadata and the current counters in one call.
Inherited from DenseIndexOn
func (on DenseIndex) Describe() (DenseIndexDescription, error) GetGet exports every vector stored under a key into arena, one row per entry sharing the key, in insertion order, converted to the outputsScalar kind - one call, however many there turn out to be.
Inherited from DenseIndexOn
func (on DenseIndex) Get(
key Key,
outputsScalar Scalar,
arena *Arena,
) (DenseVectors, error) The call rewinds arena, and the rows stay readable until it serves another call.
HealthHealth audits how much of the graph a search reaches: forward reachability from as many landmarks as one search draws, swept level by level.
Inherited from DenseIndexOn
func (on DenseIndex) Health() (DenseReachability, error) Read-only, so concurrent insertions merely leave fresh entries unaudited.
InsertInsert adds a matrix of vectors under their keys - the native batch operation, where a single vector is a one-row matrix.
Inherited from DenseIndexOn
func (on DenseIndex) Insert(
keys []Key,
vectors RowsView,
config *DenseInsertConfig,
) error 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 becomes visible at once, since every fallible step runs before the first entry identity is claimed. A VRAM call that fails after its first launch publishes none of its entries, and leaves the handle refusing every later mutation.
MaxsimMaxsim scores multi-row queries against multi-vector documents by late interaction: every query row takes its best match within a key, and those maxima sum to that key's score.
Inherited from DenseIndexOn
func (on DenseIndex) Maxsim(
queries RowsView,
rowsPerQuery, wanted uint,
arena *Arena,
config *DenseMaxsimConfig,
) (DenseProducts, error) A document is one key holding its token rows, which is what ValuesPerKey above one exists for. rowsPerQuery is the token count each query occupies, queries holds them end to end, and wanted counts documents rather than rows, zero keeping every key a query scored. Each query's keys land in arena as one run, largest score first. Runs on an inner-product index alone.
See https://arxiv.org/abs/2004.12832 - ColBERT, which introduced this scoring.
MultiplyMultiply is the approximate sparse product of a matrix against the indexed vectors - the linear algebra sibling of Search, reporting every pair the traversal scored rather than only the nearest.
Inherited from DenseIndexOn
func (on DenseIndex) Multiply(
left RowsView,
arena *Arena,
config *DenseMultiplyConfig,
) (DenseProducts, error) Pairs arrive in traversal order rather than sorted, and the pairs the walk never reaches are the low and negative products. Runs on an inner-product index alone.
How many pairs a walk scores is known only once it finishes, so the products, keys and offsets land in arena, as Get's rows do.
RefineRefine re-selects the neighbors of every stored entry against the finished graph, improving edges chosen when the graph around them was still incomplete.
Inherited from DenseIndexOn
func (on DenseIndex) Refine(
config *DenseRefineConfig,
) (DenseRefineReport, error) A build step rather than a repair: a cheap insertion pass followed by one refinement reaches a better graph, in less total time, than a single expensive insertion pass - so a wide expansion factor belongs here rather than in Insert. Reports the entries re-selected and changed, so a caller loops until Changed falls below whatever rate they consider settled.
On a RAM handle searches may run throughout; insertions may not. A stop is a time budget rather than a failure, so it reports no status.
ReserveReserve claims capacity for a number of incoming vectors ahead of insertion.
Inherited from DenseIndexOn
func (on DenseIndex) Reserve(capacity uint) error The capacity is the desired total, including the current size.
SampleSample draws up to wanted keys around the config's Focus, or across the whole index, each hanging from a shown or earlier draw and placed in three dimensions beside the shown ones, so a view grows one connected sample as it zooms; a larger wanted draws a superset.
Inherited from DenseIndexOn
func (on DenseIndex) Sample(
wanted uint,
arena *Arena,
config *DenseSampleConfig,
) (DenseDraws, error) Only RAM samples yet.
SaveSave writes the index under a path, in the layout its extension picks - one .safegraphs monolith, or a .safegraphs.json manifest beside its shards.
Inherited from DenseIndexOn
func (on DenseIndex) Save(
path string,
config *DenseSaveConfig,
) error Demands a mutation-free window; concurrent readers keep searching throughout.
SaveBufferSaveBuffer serializes the index as one monolith into arena, a size only the call itself can discover.
Inherited from DenseIndexOn
func (on DenseIndex) SaveBuffer(
arena *Arena,
config *DenseSaveBufferConfig,
) (Monolith, error) SearchSearch resolves a matrix of queries into up to wanted nearest matches apiece, written into arena - the native batch path, parallelized internally, and filtered when the config carries a predicate.
Inherited from DenseIndexOn
func (on DenseIndex) Search(
queries RowsView,
wanted uint,
arena *Arena,
config *DenseSearchConfig,
) (MatchesView, error) Never writes to the graph, so any number of these may overlap with concurrent insertions. A zero wanted walks nothing and lands only the counts, every one zero.
SearchStreamSearchStream opens a resumable search for a one-row query, so callers that post-filter aggressively pull until satisfied instead of guessing the wanted count upfront.
Inherited from DenseIndexOn
func (on DenseIndex) SearchStream(
query RowsView,
config *DenseSearchStreamInitConfig,
) (*DenseSearchStream, error) No residency implements the stream yet, so the engine answers StatusUnsupported.
SplitSplit hands this index to the receivers, the inverse of Absorb: graph-local parts keeping their edges, one per receiver, leaving this index empty but live.
Inherited from DenseIndexOn
func (on DenseIndex) Split(
receivers []*DenseIndex,
config *DenseSplitConfig,
) (DenseRefineReport, error) Receivers must be empty and share the index's residency, scalar, class, metric, geometry and shard count, and a refusal moves nothing.
EnumDenseMetricDenseMetric names a distance kind supported out of the box, with SIMD kernels dispatched by the machine's capabilities.1
type DenseMetric int32 All of them derive from a single inner-product kernel with pre-computed magnitudes; DenseMetricHamming and DenseMetricJaccard are bit-level, over the packed sub-byte kinds.
Arbitrary metrics belong to the C++ baseline tier, which forfeits the SIMD kernel chains and the magnitude cache both.
Values
DenseMetricUnknown | DenseMetricUnknown tags a metric the library could not name. | |
|---|---|---|
DenseMetricInner | DenseMetricInner is the inner product. | |
DenseMetricAngular | DenseMetricAngular is the cosine distance. | |
DenseMetricEuclidean | DenseMetricEuclidean is the L2 distance. | |
DenseMetricHamming | DenseMetricHamming counts differing bits. | |
DenseMetricJaccard | DenseMetricJaccard is the bit-level Jaccard distance. |
StringString renders the metric as the library spells it.
func (metric DenseMetric) String() string StructDenseOptionsDenseOptions holds the construction options of a dense index, the fields DenseInitRam and DenseInitVram share.1
type DenseOptions struct {
Dimensions uint
Metric DenseMetric
Quantization Scalar
Connectivity uint
ValuesPerKey uint
CPUClass CPUClass
GPUClass GPUClass
} Fields
Dimensions | uint | Dimensions is the width of the indexed vectors; zero leaves the geometry unfrozen, so the index admits no entries. |
|---|---|---|
Metric | DenseMetric | Metric is the distance kind used between vectors. |
Quantization | Scalar | Quantization is the scalar type vectors are stored and quantized into. |
Connectivity | uint | Connectivity bounds the connections per node in the graph; zero derives. |
ValuesPerKey | uint | ValuesPerKey bounds how many entries may share one key; zero is unbounded. Above one is what late interaction needs, a document being one key holding its token rows. |
CPUClass | CPUClass | CPUClass is the class a host index dispatches as; the zero value derives the CPU's own. |
GPUClass | GPUClass | GPUClass is the class a device index dispatches as; the zero value derives the devices' own. |
DefaultDenseOptionsDefaultDenseOptions is an angular bf16 index, with every optional field derived.
func DefaultDenseOptions() DenseOptions