ruvector/npm/packages/diskann
ruvnet 89350f80b5 chore(diskann): sync README + package.json to published 0.1.1
The expanded README and 0.1.1 version were already published to npm by
an earlier release, but never committed back to git. Verified identical
to `npm pack @ruvector/diskann@0.1.1`. Bringing the working tree in sync
so future bumps start from a clean baseline.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-18 16:30:44 -04:00
..
false feat(diskann): Vamana ANN + PQ + NAPI bindings — 14 tests, 1.0 recall, 90µs search (#334) 2026-04-06 17:55:06 -04:00
package.json chore(diskann): sync README + package.json to published 0.1.1 2026-05-18 16:30:44 -04:00
README.md chore(diskann): sync README + package.json to published 0.1.1 2026-05-18 16:30:44 -04:00
test.js feat(diskann): Vamana ANN + PQ + NAPI bindings — 14 tests, 1.0 recall, 90µs search (#334) 2026-04-06 17:55:06 -04:00

@ruvector/diskann

npm License Node

DiskANN / Vamana approximate-nearest-neighbor (ANN) search for Node.js — a Rust core compiled to native .node addons via NAPI-RS for Linux x64/arm64, macOS x64/arm64, and Windows x64.

DiskANN is the SSD-friendly graph index from Microsoft Research that powers billion-scale vector search on a single machine. This package implements the Vamana graph construction with α-robust pruning (NeurIPS 2019) plus optional Product Quantization (PQ) and mmap persistence so working set ≪ dataset size.

Why DiskANN

HNSW (in-memory) DiskANN (this package)
Scale <1M vectors, fully resident in RAM 1M 1B+ vectors, SSD-backed
Memory full vectors in RAM only graph + optional PQ codes in RAM
Insert incremental batch (build once after inserts)
Search sub-ms ~55µs (5K · 128d · k=10, M-series)
Best for real-time routing, small corpora large-corpus RAG, retrieval, embeddings store

Capabilities

  • Vamana graph with two-pass construction (α=1.0 then α=1.2) and α-robust pruning — the published DiskANN algorithm, not a clone of HNSW.
  • Optional Product Quantization (M subspaces × 256 centroids, trained with k-means++ / Lloyd's) for compressed in-memory codes + fast distance tables.
  • Memory-mapped persistencesave() writes a flat slab + graph + (optional) PQ codes; load() mmaps so the OS pages in only touched vectors.
  • Async builds and searches that off-load to a blocking thread pool so the Node event loop stays responsive.
  • Batch insert API for high-throughput ingestion of millions of vectors.
  • Delete support (tombstoned then re-pruned at build).
  • Cache-friendly internals — contiguous FlatVectors, generation-counter VisitedSet (O(1) per-query reset), flat PQ distance tables, 4-accumulator ILP for L2.
  • Optional SimSIMD acceleration (NEON / AVX2 / AVX-512) in the Rust crate; Node bindings ship with the portable build.
  • TypeScript types included.
  • Cross-platform prebuilds for linux-x64-gnu, linux-arm64-gnu, darwin-x64, darwin-arm64, win32-x64-msvc — no toolchain or node-gyp required at install time.

Install

npm install @ruvector/diskann
# or
pnpm add @ruvector/diskann
# or
yarn add @ruvector/diskann

Requires Node ≥ 18. The matching platform binary (@ruvector/diskann-<platform>) is pulled in automatically as an optional dependency — there is no install-time compilation.

Quick Start

const { DiskAnn } = require('@ruvector/diskann');

// 1. Create the index
const index = new DiskAnn({ dim: 128 });

// 2. Insert vectors (string id + Float32Array)
for (let i = 0; i < 10_000; i++) {
  const vec = new Float32Array(128);
  for (let d = 0; d < 128; d++) vec[d] = Math.random();
  index.insert(`vec-${i}`, vec);
}

// 3. Build the Vamana graph (one-time, required before search)
await index.buildAsync();

// 4. Search
const query = new Float32Array(128).fill(0.5);
const results = await index.searchAsync(query, 10);
//   [ { id: 'vec-42', distance: 0.123 }, ... ]

// 5. Persist + reload
index.save('./my-index');
const loaded = DiskAnn.load('./my-index');

With Product Quantization

Trade a small recall hit for far smaller in-memory footprint and faster candidate scoring on millions of vectors:

const index = new DiskAnn({
  dim: 768,
  pqSubspaces: 96,     // 96 bytes per vector instead of 768 × 4 = 3072 B
  pqIterations: 12,
  maxDegree: 64,
  buildBeam: 128,
  searchBeam: 96,
  alpha: 1.2,
});

TypeScript

import { DiskAnn, DiskAnnOptions, DiskAnnSearchResult } from '@ruvector/diskann';

const opts: DiskAnnOptions = { dim: 384, searchBeam: 96 };
const index = new DiskAnn(opts);

const hits: DiskAnnSearchResult[] = index.search(query, 10);

API

new DiskAnn(options)

Option Type Default Meaning
dim number (required) Vector dimensionality
maxDegree number 64 Vamana graph out-degree R
buildBeam number 128 Beam width during construction (L_build)
searchBeam number 64 Beam width at query time (L_search)
alpha number 1.2 α-robust pruning factor (≥ 1.0)
pqSubspaces number 0 PQ subspaces M (0 disables PQ)
pqIterations number 10 k-means iterations for PQ training
storagePath string Optional path used by the mmap layer

Methods

Method Description
insert(id: string, vector: Float32Array): void Insert a single vector
insertBatch(ids: string[], vectors: Float32Array, dim: number): void Insert N vectors packed as a flat Float32Array of length N · dim
build(): void Build the Vamana graph (and train PQ if enabled)
buildAsync(): Promise<void> Same, off-loaded to a blocking thread pool
search(query: Float32Array, k: number): DiskAnnSearchResult[] k-NN search
searchAsync(query, k): Promise<DiskAnnSearchResult[]> Async k-NN search
delete(id: string): boolean Tombstone a vector (effective after next build)
count(): number Number of vectors currently in the index
save(dir: string): void Persist index files into dir
static load(dir: string): DiskAnn Load and mmap an index from dir

Search results are { id: string, distance: number }, where distance is squared-L2.

Benchmarks

Reference measurements on an Apple-silicon M-series laptop, release build, single-thread search. PQ is off unless noted.

Dataset Dim Vectors Build Search (k=10) Recall@10
Synthetic 64 2,000 ~1.4 s ~22 µs 1.000
Synthetic 128 5,000 ~6.2 s ~55 µs 0.998
Synthetic, 50 queries 64 2,000 0.998 avg

Validated by the in-tree Rust test suite (17 tests across distance, PQ, Vamana, and end-to-end index) plus the Node integration test that ships with the package (npm test).

When NOT to use this

  • You have fewer than ~10K vectors and don't need persistence → a brute-force scan is faster and simpler.
  • You need real-time incremental inserts with immediate searchability → use HNSW (see @ruvector/router). DiskANN requires a build pass.
  • You're operating in a browser → this is a native Node addon; use the WASM-based packages in the ruvector family instead.

Algorithm notes (one paragraph)

Insertion appends vectors to a contiguous FlatVectors buffer. build() computes the medoid (point nearest the centroid, parallel via rayon), initializes a bounded-degree random graph, then runs two passes of greedy-search-from-medoid → α-robust-prune → bidirectional-edge-update: pass 1 with α=1.0 (accuracy), pass 2 with α=1.2 (navigability). If pqSubspaces > 0, a Product Quantizer is trained with k-means++ initialization and Lloyd's iterations; per-query, a distance table is precomputed so PQ distance is a sum of M table lookups. Search is greedy beam-search from the medoid with a top-L candidate pool; with PQ enabled, top results are re-ranked with exact L2.

For the full design — including persistence layout, optimization rationale, and trade-off analysis — see ADR-146: DiskANN/Vamana Implementation.

License

MIT