C · Residency

Residency

#include <usearch/dense_index.h>#include <usearch/geospatial_index.h>#include <usearch/sequence_index.h>#include <usearch/shapes_index.h>#include <usearch/types.h> 3.0.0-rc.1 · 4c3e5023

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.

§ GPUs

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:

#include <cuda_runtime.h>

cudaStream_t stream;
cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking);
int32_t const device = 0;
usearch_executor_t gpu = NULL;
usearch_executor_for_vram(&(usearch_executor_for_vram_t) {
    .executor = &gpu, .outcome = &outcome, .vram = {&device, 1}, .streams = (void* const*)&stream});

usearch_dense_index_t index = NULL;
usearch_dense_init_vram(&(usearch_dense_init_vram_t) {.index = &index,
                                                      .outcome = &outcome,
                                                      .metric = usearch_dense_metric_inner_k,
                                                      .quantization = usearch_scalar_bf16_k,
                                                      .dimensions = 1024,
                                                      .vram = {&device, 1}});
usearch_dense_reserve(&(usearch_dense_reserve_t) {
    .index = index, .executor = gpu, .outcome = &outcome, .capacity = 1000000});

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:

float* rows = NULL;
cudaHostAlloc((void**)&rows, 4096 * 1024 * sizeof(float), cudaHostAllocDefault);
// ... fill 4096 rows ...
usearch_dense_insert(&(usearch_dense_insert_t) {.index = index,
                                                .executor = gpu,
                                                .outcome = &outcome,
                                                .count = 4096,
                                                .keys = {row_keys, sizeof(usearch_key_t)},
                                                .vectors = {rows, 1024 * sizeof(float), 1024, usearch_scalar_f32_k}});

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:

usearch_rows_view_t rows_on_device = {0};
size_t rows_count = 0;
usearch_arena_t arena = NULL;
usearch_arena_init(&(usearch_arena_init_t) {.arena = &arena, .executor = gpu, .outcome = &outcome});
usearch_dense_get(&(usearch_dense_get_t) {.index = index,
                                          .executor = gpu,
                                          .outcome = &outcome,
                                          .key = row_keys[0],
                                          .vectors = &rows_on_device,
                                          .written = &rows_count,
                                          .arena = arena,
                                          .outputs_scalar = usearch_scalar_f32_k});

size_t const table_pitch = 1280 * sizeof(float);
float* table = NULL;
cudaHostAlloc((void**)&table, rows_count* table_pitch, cudaHostAllocDefault);
cudaMemcpy2DAsync(table, table_pitch, rows_on_device.data, rows_on_device.stride, rows_on_device.stride, rows_count,
                  cudaMemcpyDeviceToHost, stream);
cudaStreamSynchronize(stream);
usearch_arena_free(arena);

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.
types.h:631 ↗
enum usearch_gpu_vendor_t

Values

usearch_gpu_vendor_unknown_k0
usearch_gpu_vendor_nvidia_kNVIDIA, through the CUDA driver when one is installed.
usearch_gpu_vendor_amd_kAMD, through the HIP runtime when one is installed.
usearch_gpu_vendor_apple_kApple, through Metal, present on every Apple silicon Mac.
usearch_gpu_device_tOne GPU as USearch numbers it: its position in usearch_gpu_devices is the USearch device ordinal every VRAM call names.
types.h:646 ↗
struct usearch_gpu_device_t

Fields

vendorusearch_gpu_vendor_tThe vendor whose runtime drives the device.
vendor_ordinaluint32_tThe device's ordinal in its vendor's runtime, as nvidia-smi or rocm-smi list it.
gpu_classusearch_gpu_class_tThe class whose kernels run on it.
compute_unitsuint32_tStreaming multiprocessors, compute units or GPU cores.
memory_bytesuint64_tDevice memory in bytes, or the working set Metal recommends for unified memory.
namecharThe 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.
types.h:675 ↗
enum usearch_residency_t

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_k0No residency named, which is what usearch_residency_from_name reads unknown text as.
usearch_residency_ram_k1Volatile-memory index traversed by CPU threads - the concurrent baseline.
usearch_residency_vram_k2Accelerator-memory index traversed by GPU warps on a chosen device.
usearch_residency_nvme_k3Persisted index traversed through pluggable I/O engines, sized past RAM.

Spellings

char const * usearch_residency_name(usearch_residency_t residency);
usearch_residency_t usearch_residency_from_name(
    usearch_bytes_view_t name);
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.
types.h:793 ↗
struct usearch_ram_options_t

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_domainsint32_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_countsize_tNumber of entries in memory_domains; zero derives.
shard_capacitysize_tEntries 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.
cpu_classusearch_cpu_class_tThe 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_limitsize_tBytes 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.
types.h:831 ↗
struct usearch_vram_options_t

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

devicesint32_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_countsize_tNumber of entries in devices; zero derives.
shard_capacitysize_tEntries 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.
gpu_classusearch_gpu_class_tThe 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.
types.h:869 ↗
struct usearch_nvme_options_t

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:PCIe traddr: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.

Fields

pathsusearch_bytes_view_t const *Devices or directories to serve shards from, borrowed for the duration of the call; NULL leaves every shard where its manifest points.
paths_countsize_tNumber of entries in paths; zero derives.
cpu_classusearch_cpu_class_tThe 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.

Functions

usearch_dense_init_ramCreates an empty, mutable index in host memory, its geometry frozen, so it takes vectors without a reservation.
dense_index.h:307 ↗
void usearch_dense_init_ram(usearch_dense_init_ram_t *arguments);

Parameters

indexusearch_dense_index_t *Output for the new index.
outcomeusearch_outcome_t *Output for the outcome, final once its status leaves usearch_status_deferred_k.
metricusearch_dense_metric_tThe metric kind used for distance calculations between vectors.
quantizationusearch_scalar_tThe scalar type vectors are stored & quantized into.
dimensionssize_tThe number of dimensions in the indexed vectors.
connectivitysize_tThe optional connections-per-node bound; zero derives.
values_per_keysize_tUpper bound on entries sharing one key; zero is unbounded.
ramusearch_ram_options_tThe memory domains to place one shard on apiece.

Statuses

usearch_dense_init_vramCreates an empty, mutable index in device memory, its geometry frozen, so it takes vectors without a reservation.
dense_index.h:347 ↗
void usearch_dense_init_vram(usearch_dense_init_vram_t *arguments);

GPU Acceleration & Constraints

Parameters

indexusearch_dense_index_t *Output for the new index.
outcomeusearch_outcome_t *Output for the outcome, final once its status leaves usearch_status_deferred_k.
metricusearch_dense_metric_tThe metric kind used for distance calculations between vectors.
quantizationusearch_scalar_tThe scalar type vectors are stored & quantized into.
dimensionssize_tThe number of dimensions in the indexed vectors.
connectivitysize_tThe optional connections-per-node bound; zero derives.
values_per_keysize_tUpper bound on entries sharing one key; zero is unbounded.
vramusearch_vram_options_tThe 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.
dense_index.h:672 ↗
void usearch_dense_free(usearch_dense_index_t index);
Symbolindex
usearch_dense_freeusearch_dense_index_t
usearch_geospatial_freeusearch_geospatial_index_t
usearch_sequence_freeusearch_sequence_index_t
usearch_shapes_freeusearch_shapes_index_t

Waits until every deferred call naming the handle is final.

Parameters

indexinoutThe 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.
dense_index.h:706 ↗
void usearch_dense_reserve(usearch_dense_reserve_t *arguments);

Parameters

indexusearch_dense_index_tThe handle to be resized.
executorusearch_executor_tThe executor the growth runs on.
outcomeusearch_outcome_t *Output for the outcome, final once its status leaves usearch_status_deferred_k.
completionusearch_completion_tBlocking, the default, or deferred.
capacitysize_tThe desired total capacity, including the current size.

Statuses

usearch_*_clear× 2Erases all entries, keeping the reserved capacity and frozen geometry.
dense_index.h:734 ↗
void usearch_dense_clear(usearch_dense_clear_t *arguments);
Symbolindex
usearch_dense_clearusearch_dense_index_t
usearch_shapes_clearusearch_shapes_index_t

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.

Parameters

indexusearch_dense_index_tThe handle to be cleared.
executorusearch_executor_tThe executor the erasure runs on.
outcomeusearch_outcome_t *Output for the outcome, final once its status leaves usearch_status_deferred_k.
completionusearch_completion_tBlocking, the default, or deferred.

Statuses

usearch_geospatial_init_ramCreates an empty, mutable geospatial index in host memory.
geospatial_index.h:281 ↗
void usearch_geospatial_init_ram(
    usearch_geospatial_init_ram_t *arguments);

Parameters

indexusearch_geospatial_index_t *Output for the new index.
outcomeusearch_outcome_t *Output for the outcome, final once its status leaves usearch_status_deferred_k.
terrainusearch_geospatial_terrain_tThe optional terrain lines of sight are tested over; a zeroed one means none.
connectivitysize_tThe optional connections-per-node bound; zero derives to twelve - eight tangent-plane cones and four long-range links.
values_per_keysize_tUpper bound on observations sharing one key; zero is unbounded.
ramusearch_ram_options_tThe memory domains to place one shard on apiece.

Statuses

usearch_geospatial_init_vramCreates an empty, mutable geospatial index in device memory.
geospatial_index.h:319 ↗
void usearch_geospatial_init_vram(
    usearch_geospatial_init_vram_t *arguments);

Parameters

indexusearch_geospatial_index_t *Output for the new index.
outcomeusearch_outcome_t *Output for the outcome, final once its status leaves usearch_status_deferred_k.
terrainusearch_geospatial_terrain_tThe optional terrain lines of sight are tested over; a zeroed one means none.
connectivitysize_tThe optional connections-per-node bound; zero derives to twelve.
values_per_keysize_tUpper bound on observations sharing one key; zero is unbounded.
vramusearch_vram_options_tThe devices to place one shard on apiece.

Statuses

usearch_geospatial_reserveGrows the shards ahead of incoming entries - a capacity hint, since insertions grow them on demand.
geospatial_index.h:651 ↗
void usearch_geospatial_reserve(
    usearch_geospatial_reserve_t *arguments);

Parameters

indexusearch_geospatial_index_tThe handle to be resized.
executorusearch_executor_tThe executor the growth runs on.
outcomeusearch_outcome_t *Output for the outcome, final once its status leaves usearch_status_deferred_k.
completionusearch_completion_tBlocking, the default, or deferred.
entriessize_tThe desired total entry capacity, including the current size.

Statuses

usearch_*_clear× 2Erases all entries, keeping the reserved capacity.
geospatial_index.h:677 ↗
void usearch_geospatial_clear(usearch_geospatial_clear_t *arguments);
Symbolindex
usearch_geospatial_clearusearch_geospatial_index_t
usearch_sequence_clearusearch_sequence_index_t

Parameters

indexusearch_geospatial_index_tThe handle to be cleared.
executorusearch_executor_tThe executor the erasure runs on.
outcomeusearch_outcome_t *Output for the outcome, final once its status leaves usearch_status_deferred_k.
completionusearch_completion_tBlocking, the default, or deferred.

Statuses

usearch_sequence_init_ramCreates an empty, mutable sequence index in host memory, for the uniform edit distances or the weighted alignments alike.
sequence_index.h:242 ↗
void usearch_sequence_init_ram(usearch_sequence_init_ram_t *arguments);

Parameters

indexusearch_sequence_index_t *Output for the new index.
outcomeusearch_outcome_t *Output for the outcome, final once its status leaves usearch_status_deferred_k.
metricusearch_sequence_metric_tThe scoring kind.
weightsusearch_sequence_weights_t const *The weights an alignment kind needs; NULL for the Levenshtein kinds, which refuse any.
window_widthssize_t const *Window widths in bytes for the overlap kind, which needs at least one; every other kind refuses any.
window_widths_countsize_tNumber of entries in window_widths.
connectivitysize_tThe optional connections-per-node bound; zero derives.
values_per_keysize_tUpper bound on entries sharing one key; zero is unbounded.
ramusearch_ram_options_tThe memory domains to place one shard on apiece.

Statuses

usearch_sequence_init_vramCreates an empty, mutable sequence index in device memory.
sequence_index.h:297 ↗
void usearch_sequence_init_vram(
    usearch_sequence_init_vram_t *arguments);

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.

Parameters

indexusearch_sequence_index_t *Output for the new index.
outcomeusearch_outcome_t *Output for the outcome, final once its status leaves usearch_status_deferred_k.
metricusearch_sequence_metric_tThe scoring kind; byte and UTF-8 Levenshtein, window overlap and Needleman-Wunsch serve a GPU.
weightsusearch_sequence_weights_t const *The weights an alignment kind needs; NULL for the Levenshtein kinds, which refuse any.
window_widthssize_t const *Window widths in bytes for the overlap kind; every other kind refuses any.
window_widths_countsize_tNumber of entries in window_widths.
connectivitysize_tThe optional connections-per-node bound; zero derives.
values_per_keysize_tZero alone: a GPU index keeps no key lookup, so it bounds no key's entries and refuses any bound it is given.
vramusearch_vram_options_tThe devices to place one shard on apiece; naming none picks device zero.

Statuses

usearch_sequence_reserveGrows the shards ahead of incoming entries - a capacity hint, since insertions grow them on demand.
sequence_index.h:650 ↗
void usearch_sequence_reserve(usearch_sequence_reserve_t *arguments);

Parameters

indexusearch_sequence_index_tThe handle to be resized.
executorusearch_executor_tThe executor the growth runs on.
outcomeusearch_outcome_t *Output for the outcome, final once its status leaves usearch_status_deferred_k.
completionusearch_completion_tBlocking, the default, or deferred.
entriessize_tThe desired total entry capacity, including the current size.
payload_bytessize_tExpected total payload volume; zero lets the payload storage grow lazily.

Statuses

usearch_shapes_init_ramCreates an empty, mutable shape index in host memory.
shapes_index.h:269 ↗
void usearch_shapes_init_ram(usearch_shapes_init_ram_t *arguments);

Parameters

indexusearch_shapes_index_t *Output for the new index.
outcomeusearch_outcome_t *Output for the outcome, final once its status leaves usearch_status_deferred_k.
metricusearch_shapes_metric_tThe kind comparing two shapes - usearch_shapes_metric_wasserstein_procrustes_k for conformers and pockets, usearch_shapes_metric_chamfer_k where speed comes first.
quantizationusearch_scalar_tThe scalar type points are stored in: f16 or f32, any other refused with usearch_status_unsupported_k.
pointssize_tThe frozen point count, when every entry has one - a fixed-length backbone, a landmark set.
connectivitysize_tThe optional connections-per-node bound; zero derives.
values_per_keysize_tUpper bound on entries sharing one key; zero is unbounded.
ramusearch_ram_options_tThe memory domains to place one shard on apiece.

Statuses

usearch_shapes_init_vramCreates an empty, mutable shape index in device memory.
shapes_index.h:312 ↗
void usearch_shapes_init_vram(usearch_shapes_init_vram_t *arguments);

Parameters

indexusearch_shapes_index_t *Output for the new index.
outcomeusearch_outcome_t *Output for the outcome, final once its status leaves usearch_status_deferred_k.
metricusearch_shapes_metric_tThe kind comparing two shapes.
quantizationusearch_scalar_tThe scalar type points are stored in: f16 or f32.
pointssize_tThe frozen point count, when every entry has one; zero admits any count.
connectivitysize_tThe optional connections-per-node bound; zero derives.
values_per_keysize_tUpper bound on entries sharing one key; zero is unbounded.
vramusearch_vram_options_tThe devices to place one shard on apiece.

Statuses

usearch_shapes_reserveGrows the shards ahead of incoming entries - a capacity hint, since insertions grow them on demand.
shapes_index.h:635 ↗
void usearch_shapes_reserve(usearch_shapes_reserve_t *arguments);

Parameters

indexusearch_shapes_index_tThe handle to be resized.
executorusearch_executor_tThe executor the growth runs on.
outcomeusearch_outcome_t *Output for the outcome, final once its status leaves usearch_status_deferred_k.
completionusearch_completion_tBlocking, the default, or deferred.
entriessize_tThe desired total entry capacity, including the current size.
pointssize_tExpected total point volume across all entries; zero grows lazily.

Statuses

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.
types.h:1173 ↗
void usearch_gpu_devices(usearch_gpu_devices_t *arguments);

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.

Parameters

outcomeusearch_outcome_t *Output for the outcome, final when the call returns.
devicesusearch_gpu_device_t *Output for up to devices_capacity devices, in USearch ordinal order; may be NULL.
devices_capacitysize_tEntries devices holds.
devices_countsize_t *Output for every device this process can drive, which may exceed devices_capacity.