Skip to content

Quick Start

Install

Jasper's Python bindings wrap a CUDA C++ core via tvm-ffi. You'll need a CUDA toolchain (nvcc + CMake) and an NVIDIA GPU to build it.

# Install tvm_ffi
pip install apache-tvm-ffi

# Build the FFI shared library
# CMAKE_CUDA_ARCHITECTURES defaults to 120; override for your target GPU
cmake -B build -DJASPER_BUILD_FFI=ON -DJASPER_BUILD_CMD=ON -DCMAKE_CUDA_ARCHITECTURES="90;120"
cmake --build build -j
cmake --install build
pip install -e python/

Run the test suite:

cmake -B build -DJASPER_BUILD_TESTS=ON
cmake --build build
cd build && ctest --output-on-failure

Runnable end-to-end scripts live under example/: graph.py builds and searches in one pass, query.py loads a saved index and sweeps beam widths, and groundtruth.py generates exact k-NN ground truth for recall evaluation.

import torch
import jasper

vectors = torch.randn(1_000_000, 128, dtype=torch.float16, device="cuda")
# vectors: torch.Tensor, shape [n_vectors, dim], dtype=torch.float16, CUDA

g = jasper.Graph.build(
    vectors,          # torch.Tensor  [n_vectors, dim], dtype=torch.float16
    n_neighbors=64,    # int    — max neighbors per node (R)
    distance="l2",     # str | DistanceFunc — "l2" or "ip"
    alpha=1.2,          # float  — pruning factor (1.0=strict, >1.0=longer hops)
)
# g: jasper.Graph

queries = torch.randn(100, 128, dtype=torch.float16, device="cuda")
# queries: torch.Tensor, shape [n_queries, dim], dtype=torch.float16, CUDA

indices, distances = g.search(
    queries,       # torch.Tensor  [n_queries, dim], dtype=torch.float16, CUDA
    k=10,           # int    — number of neighbors to return
    beam_width=64,   # int    — search beam width
)
# indices:   torch.Tensor, int32,   shape [n_queries, k]
# distances: torch.Tensor, float32, shape [n_queries, k]

Save and load a graph

g.save("sift1m.graph")

g = jasper.Graph.load(
    "sift1m.graph",  # str  — path to graph binary file
    dim=128,          # int  — vector dimensionality
    n_neighbors=32,    # int  — max neighbors per node (R), must match file
)

Insert and delete vectors

Jasper supports updating a live graph without a full rebuild.

# Insert: wires new vectors into the existing graph and assigns stable ids
new_vectors = torch.randn(10_000, 128, dtype=torch.float16, device="cuda")
ids = g.append(new_vectors, alpha=1.2)
# ids: torch.Tensor, int32, shape [n] — assigned stable ids, in input order

# Delete: soft-delete hides vectors immediately; consolidate/compact repair the graph
g.mark_deleted(ids)          # hidden from search right away
g.consolidate(alpha=1.2)     # repairs edges, clears tombstones
g.compact()                  # reclaims space, preserves stable ids

Stable ids are the ids returned by search(); they're never reused and are unchanged by consolidate/compact.

Directional search (LSH / PQ estimators)

A directional graph stores extra per-edge estimator artifacts alongside the plain graph, enabling beam search scored by a cheaper proxy distance instead of the exact one.

# LSH estimator
g = jasper.Graph.build(
    vectors, n_neighbors=64, distance="l2", alpha=1.2,
    build_lsh=True,   # populate LSH edges, enables directional_search()
    k_ranks=4,         # LSH rank count (4 or 16)
)
indices, distances = g.directional_search(queries, k=10, beam_width=64)

# PQ estimator
g = jasper.Graph.build(
    vectors, n_neighbors=64, distance="l2", alpha=1.2,
    build_pq=True,    # populate PQ edges + exact norms, enables pq_search()
    k_ranks=4,         # PQ subquantizer count (4 or 16)
)
indices, distances = g.pq_search(queries, k=10, beam_width=64)

build_lsh and build_pq can be enabled together on the same graph and queried independently. A graph built with either flag is directional: plain g.search() raises — use directional_search() / pq_search() instead.

Evaluate recall

gt_ids, gt_dists = jasper.read_groundtruth("sift1m_groundtruth.bin", k=10)

recall = jasper.get_recall(gt_ids, indices.cpu(), k=10, n_queries=indices.shape[0])
# recall: float — value in [0, 1]

See the Python API reference for the full set of methods, including reading raw vector files, generating your own ground truth, and inspecting graph/deletion state.