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>
This commit is contained in:
ruvnet 2026-05-18 16:30:44 -04:00
parent c5c7e7f26e
commit 89350f80b5
2 changed files with 137 additions and 20 deletions

View file

@ -1,54 +1,171 @@
# @ruvector/diskann
DiskANN/Vamana approximate nearest neighbor search — built in Rust, runs on all platforms.
[![npm](https://img.shields.io/npm/v/@ruvector/diskann.svg)](https://www.npmjs.com/package/@ruvector/diskann)
[![License](https://img.shields.io/npm/l/@ruvector/diskann.svg)](https://github.com/ruvnet/ruvector/blob/main/LICENSE)
[![Node](https://img.shields.io/node/v/@ruvector/diskann.svg)](https://nodejs.org)
Implements the Vamana graph algorithm from ["DiskANN: Fast Accurate Billion-point Nearest Neighbor Search on a Single Node" (NeurIPS 2019)](https://proceedings.neurips.cc/paper/2019/hash/09853c7fb1d3f8ee67a61b6bf4a7f8e6-Abstract.html).
**DiskANN / Vamana** approximate-nearest-neighbor (ANN) search for Node.js — a Rust core compiled to native `.node` addons via [NAPI-RS](https://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](https://proceedings.neurips.cc/paper/2019/hash/09853c7fb1d3f8ee67a61b6bf4a7f8e6-Abstract.html)) 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 persistence**`save()` 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
```bash
npm install @ruvector/diskann
# or
pnpm add @ruvector/diskann
# or
yarn add @ruvector/diskann
```
## Usage
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
```javascript
const { DiskAnn } = require('@ruvector/diskann');
// 1. Create the index
const index = new DiskAnn({ dim: 128 });
// Insert vectors
for (let i = 0; i < 1000; i++) {
// 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);
}
// Build Vamana graph
index.build();
// 3. Build the Vamana graph (one-time, required before search)
await index.buildAsync();
// Search
// 4. Search
const query = new Float32Array(128).fill(0.5);
const results = index.search(query, 10);
console.log(results); // [{ id: 'vec-42', distance: 0.123 }, ...]
const results = await index.searchAsync(query, 10);
// [ { id: 'vec-42', distance: 0.123 }, ... ]
// Persist
// 5. Persist + reload
index.save('./my-index');
const loaded = DiskAnn.load('./my-index');
```
## Performance
### With Product Quantization
| Metric | Value |
|--------|-------|
| Search latency | **55µs** (5K vectors, 128d, k=10) |
| Recall@10 | **0.998** |
| Build | ~6s for 5K vectors |
Trade a small recall hit for far smaller in-memory footprint and faster candidate scoring on millions of vectors:
```javascript
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
```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
See full documentation at [github.com/ruvnet/ruvector](https://github.com/ruvnet/ruvector).
### `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](https://github.com/ruvnet/ruvector/blob/main/docs/adr/ADR-146-diskann-vamana-implementation.md).
## Related packages
- [`@ruvector/router`](https://www.npmjs.com/package/@ruvector/router) — in-memory HNSW router (sub-millisecond, small/medium corpora)
- [`ruvector`](https://www.npmjs.com/package/ruvector) — umbrella package; lazily wraps DiskANN when this addon is installed
- Rust crate: [`ruvector-diskann`](https://crates.io/crates/ruvector-diskann)
## Links
- Repository: <https://github.com/ruvnet/ruvector>
- Issues: <https://github.com/ruvnet/ruvector/issues>
- DiskANN paper (NeurIPS 2019): <https://proceedings.neurips.cc/paper/2019/hash/09853c7fb1d3f8ee67a61b6bf4a7f8e6-Abstract.html>
## License
MIT
[MIT](https://github.com/ruvnet/ruvector/blob/main/LICENSE)

View file

@ -1,6 +1,6 @@
{
"name": "@ruvector/diskann",
"version": "0.1.0",
"version": "0.1.1",
"description": "DiskANN/Vamana — SSD-friendly billion-scale approximate nearest neighbor search with product quantization",
"main": "index.js",
"types": "index.d.ts",