An index lives in RAM, VRAM or on NVMe, and every lifecycle verb names its action and its residency instead of taking a tag a residency might not support:
init starts empty and mutable: usearch_dense_init_ram, usearch_dense_init_vram.
load copies a checkpoint in: usearch_dense_load_ram, usearch_dense_load_vram, and their _buffer forms.
view serves one in place, read-only: usearch_dense_view_ram, usearch_dense_view_nvme.
The options name where an index lives, never who walks it. usearch_ram_options_t names NUMA nodes, one shard per node, so a dual-socket box gets two graphs whose walks stay local; payload blocks claim huge pages where a pool is provisioned. usearch_vram_options_t names device ordinals, one shard per device and never a replica, so an index outgrowing them fails rather than paging to the host.
Both take a shard_capacity, the entries one shard may hold, zero meaning the 32-bit ID space: an insertion past it opens another generation of shards on the same nodes or devices, and only the newest generation takes insertions. usearch_nvme_options_t names paths: a filesystem path selects a kernel I/O engine, a transport identifier like trtype:PCIe traddr:0000:5e:00.0 selects SPDK in a -D USEARCH_WITH_SPDK=1 build, and a FUSE mount over a bucket reads as an ordinary path.
Memory follows residency: an index keeps its payload where its lifecycle verb put it, an executor keeps its scratch next to its threads or devices, and an arena lives where the executor that created it computes.
So outputs come back in RAM from a RAM executor and in VRAM from a VRAM executor, and moving them elsewhere is the caller’s own copy.
Callers lend memory, never a way to allocate it: no allocator callback crosses the interface, because large systems hold too many kinds of memory for one ABI to describe them all.
Every dense verb is exported for every residency, and the engine answers what a residency lacks with usearch_status_unsupported_k:
RAM answers every verb but usearch_dense_view_ram and usearch_dense_search_stream_init, and exact scans serve multiply and attend too.
VRAM answers insertion, refinement, absorbing, counting, get, search, multiply, maxsim, attention and the serialization family, save_buffer included, while cursors answer usearch_status_unsupported_k, as GPUs explains.
NVIDIA devices answer exact scans, while AMD and Apple ones refuse them with usearch_status_unsupported_k.
Apple’s Metal answers attention usearch_status_unsupported_k too, since its kernels cannot call the caller’s value lookup.
A sequence, shape or geospatial VRAM index answers save_buffer with usearch_status_unsupported_k for now, and those modalities load from memory into RAM alone, through load_ram_buffer.
NVMe answers every read verb as RAM does, sample and health included, refuses every mutating one with usearch_status_read_only_k, and refuses save and save_buffer, since a view already is its checkpoint.
A VRAM index lives on the devices its vram options name, one shard per device, and a VRAM executor drives it through one stream per device.
Passing your own streams lets USearch work join the rest of your pipeline, though the engine refuses borrowed streams with usearch_status_unsupported_k for now:
The library classifies every pointer it is given through the driver, so inputs may live anywhere:
rows already on the device are read in place, pinned host rows upload directly, and only pageable host rows pass through the executor’s pinned bounce buffer.
Pin the rows you upload often, and the bounce disappears:
An arena lives where the executor that created it computes, so on a VRAM executor every output an arena holds is device memory.
Feed it straight into your next kernel, or bring it back yourself, into the layout you want.
Here the packed rows get returns land in a pinned host table with a wider row pitch, in one strided copy:
A search’s matches land in its arena too, so on a VRAM executor they are device memory: every shard ranks its own entries and the first device merges those rankings into the arena, so on NVIDIA and AMD no match crosses through the host, while on Apple the host merges them in the unified memory both share.
A device that runs out of memory for an arena reports usearch_status_out_of_memory_k rather than falling back to the host, so where an output lands never depends on how full a device is.
Free a device arena once your own device work on its outputs has finished, and before the executor that made it.
API
Types
usearch_gpu_vendor_tThe GPU vendors one binary drives at once, each through a runtime loaded on first use.
Streaming multiprocessors, compute units or GPU cores.
memory_bytes
uint64_t
Device memory in bytes, or the working set Metal recommends for unified memory.
name
char
The vendor's own name for the device, NUL-terminated and truncated to fit.
usearch_residency_tWhere an index's payload lives and which engine traverses it - the vocabulary the lifecycle verbs spell in their names, as usearch_dense_init_vram does.
Each modality names a verb per action and residency rather than taking this as a tag, so a residency a modality lacks has no verb to call. The enumerators remain for the name tables bindings parse user text with.
Values
usearch_residency_unknown_k
0
No residency named, which is what usearch_residency_from_name reads unknown text as.
usearch_residency_ram_k
1
Volatile-memory index traversed by CPU threads - the concurrent baseline.
usearch_residency_vram_k
2
Accelerator-memory index traversed by GPU warps on a chosen device.
usearch_residency_nvme_k
3
Persisted index traversed through pluggable I/O engines, sized past RAM.
usearch_ram_options_tThe host memory domains a RAM-resident index places its shards in - one shard per named NUMA node, so a dual-socket box names two and gets two graphs whose traversals stay local to the cores that walk them.
Names where the index lives, never who walks it: threads belong to the executor the verbs take. Every modality's RAM lifecycle verbs and the RAM executor take this struct.
The library's own payload blocks of 1 GiB or more claim 1 GiB huge pages while the host's 1 GiB pool covers them, and every other block base pages; 2 MiB pages are never claimed.
Fields
memory_domains
int32_t const *
NUMA node IDs to place shards on, borrowed for the duration of the call; NULL derives every node the process may allocate on.
memory_domains_count
size_t
Number of entries in memory_domains; zero derives.
shard_capacity
size_t
Entries one shard may hold before the index opens another shard generation on the same domains; zero, or anything past the 32-bit ID space, means 2^32 - 2.
The class to dispatch as if the CPU were of it, admitting every rung whose features it holds; usearch_cpu_class_unknown_k derives the CPU's own.
memory_limit
size_t
Bytes an NVMe view's key tables may hold on each named domain; zero sets no ceiling.
usearch_vram_options_tThe devices a VRAM-resident index places its shards on - one shard per named ordinal, sharded and never replicated, so eight devices host eight slices of one graph rather than eight copies of it.
Names where the index lives, never who walks it: streams belong to the executor the verbs take, one per device. A checkpoint of more parts than named devices keeps one shard per part, part p on device p modulo their count, and shards sharing a device queue on its stream in order.
Flat matrices are device-resident, never migratable: an index outgrowing the named devices fails rather than paging to the host, and the answer is to name more devices.
One index spans one vendor and one class, so naming devices of two vendors or two generations refuses with usearch_status_config_mismatch_k; separate indexes may sit on different vendors in one process.
Fields
devices
int32_t const *
USearch device ordinals to place shards on, NVIDIA devices first, then AMD, then Apple, as usearch_gpu_devices lists them, borrowed for the call; NULL picks device zero.
devices_count
size_t
Number of entries in devices; zero derives.
shard_capacity
size_t
Entries one shard may hold before the index opens another shard generation on the same devices, while their memory lasts; zero, or anything past the 32-bit ID space, means 2^32 - 2.
The class to dispatch as if every named device were of it, admitting only a class the devices run; usearch_gpu_class_unknown_k derives the devices' own.
usearch_nvme_options_tThe devices an NVMe-resident index serves its shards from - one shard per named path, so striping across four drives names four.
Names where the index lives, never who walks it: submission depth belongs to the executor the verbs take. Naming nothing serves every shard where its manifest already points.
A path is whatever identifies a device to the engine that can reach it, so the string is the extension point rather than a field per transport. A filesystem path names a kernel device; a transport identifier like trtype:PCIetraddr:0000:5e:00.0 names a namespace the kernel no longer owns and selects a userspace engine. A FUSE mount is just a path too, which is how object storage stages in without an HTTP stack in this library - point mountpoint-s3 at a bucket and the ordinary engines read it.
Transfer geometry is never an input: alignment is probed from the device, and the record granularity a checkpoint was written at is read back from its own header.
Reads always bypass the page cache where the filesystem admits O_DIRECT.
The devices to place one shard on apiece, by USearch ordinal.
Statuses
usearch_status_config_mismatch_k Zero dimensions, a connectivity that is not a power of two, or on NVIDIA and AMD a geometry whose walks cannot fit the device's shared memory.
usearch_*_free× 4Frees the resources associated with the index; NULL is safe, and it cannot fail, so takes no outcome.
Waits until every deferred call naming the handle is final.
Parameters
index
inout
The handle to be freed; invalid afterwards.
usearch_dense_reserveGrows the shards ahead of a number of incoming vectors - a capacity hint, since insertions grow them on demand and init fixed the geometry already.
An insertion in flight, or a refinement, absorb, compaction, split or load holding the handle, refuses it with usearch_status_contended_k; a search it reaches under repeats or reports the same.
usearch_status_config_mismatch_kweights for a kind other than Needleman-Wunsch, Needleman-Wunsch without them, window widths missing for the overlap kind or given to another, or weights folding into more than 32 classes, a negative gap or a pair above its mean self-score.
Insertions and searches on it run on an executor driving its device, and a search writes its matches in device memory in an arena made for that executor, or on host pages in a host one.
usearch_status_config_mismatch_k Weights for a kind that does not align, an alignment kind without weights, window widths for any kind but overlap or none for overlap, or devices of two classes.
usearch_status_unsupported_k A kind other than Chamfer or Hausdorff, a quantization other than f16 or f32, a values_per_key bound, or devices that do not run the pinned class.
usearch_gpu_devicesLists every GPU this process can drive, NVIDIA devices first, then AMD, then Apple, so the position of a device is the USearch ordinal usearch_vram_options_t names it by.
Each vendor's runtime loads on first use, and a vendor whose runtime is absent lists no devices, so the call succeeds with a zero count on a machine without GPUs. The listing is taken once per process and never changes after.