Performance
This page shows how fast Chassis is through Python, and the options that trade build speed, search speed and recall.
On an Apple M5 in Low Power Mode (2026-10-15), through Python, a search over 20,000 random vectors
of 384 dimensions took a median of 0.16 ms, and add_batch of 5,000 of them into an empty index
took 0.57 s. Time your embedding model too: in most apps it, not the index, sets the speed.
- With several worker processes on one machine, limit each model's threads (fastembed's
threads=, PyTorch'storch.set_num_threads) so they don't fight over the cores. - Embed texts of similar length together, since a batch is padded to its longest text.
Chassis searches an HNSW graph (Hierarchical Navigable Small World). IndexOptions sets how it
trades build speed, search speed and recall.
Options
You can pass an IndexOptions object when opening the index.
from chassisdb import VectorIndex, IndexOptions
options = IndexOptions(
max_connections=32, # 'M' in HNSW papers
ef_construction=400, # Build quality
ef_search=100 # Search quality
)
index = VectorIndex("tuned.chassis", dimensions=128, options=options)
Parameters
| Parameter | Description | Default | Impact |
|---|---|---|---|
max_connections |
Max edges per node in the graph. | 16 | Higher = Better recall, higher memory usage. |
ef_construction |
Size of the dynamic candidate list during build. | 200 | Higher = Slower build, higher quality graph. |
ef_search |
Size of the dynamic candidate list during search. | 50 | Higher = Slower search, better recall. |
precision |
"full" keeps 32-bit floats, "half" 16-bit floats. Fixed when the index is created. |
"full" |
"half" halves the vectors' size in the file and in memory, and on an index too large for the CPU's caches searches are a tenth to two thirds faster on most machines measured. Components are rounded to about three decimal digits; recall on the embeddings measured was the same. A component of 65,520 or more in magnitude is refused, and older releases can't open the file. |
huge_pages |
Ask Linux to keep the vectors on 2 MB pages. | False |
On an index too large for the CPU's caches, up to a quarter faster search. Does nothing off Linux, or where the kernel and filesystem don't keep files on huge pages (ext4 on Linux 6.17 does). An index much larger than memory reads 2 MB per miss. |
warm |
Read the index into memory on another thread from the moment it is opened; warm() asks for it later. |
False |
While the file isn't in memory yet, as after a reboot, searches stop waiting for the disk one page at a time. On an Apple M5 the hundredth search over 99,000 vectors of 1,536 dimensions came 0.37 s after the process started, instead of 1.9 s. For an index that fits in memory. Linux 5.14 or later, or macOS; an older Linux reads in only some of the index, and Windows none. |
Smaller files
An index is mostly its vectors: 4 bytes a dimension, 2 with precision="half", and about 160 bytes
a vector for the graph. Besides half precision, the way to shrink it is fewer dimensions. Matryoshka
embeddings are trained so that the first part of a vector works on its own; OpenAI's
text-embedding-3 models take a dimensions argument for it. Keeping 512 of 1,536 dimensions makes
the index about a third of the size. Measure recall on your own queries first: OpenAI reports
text-embedding-3-large at 256 dimensions doing better than ada-002 at 1,536, but a model not trained
this way loses much more. Cut vectors yourself only with the cosine metric, or normalize them
again. Lower max_connections saves less. 12 instead of 16 makes a 128-dimension file in half
precision 7% smaller, and one of 1,536 dimensions 1%. Builds are 7 to 18% faster, but searches for
0.99 recall at 128 and 384 dimensions take up to 14% longer.
Many queries
search_batch(queries, k) searches a (count, dimensions) array in one call and returns the ids
and distances as two (count, k) arrays. search adds 15 to 38 µs a query on the server CPUs
measured (checking the query, buffers, result objects), which search_batch pays once per batch. On
GitHub's server runners, a batch made 1.20 to 1.42 times the queries a second of one call per query
over a million vectors of 128 dimensions, and 1.10 to 1.13 times over 99,000 of 1,536. That is
within 5% of the same searches from Rust (0.95 recall).
Batches
A flush waits for the disk (fsync), so it takes milliseconds. To add many vectors quickly:
- Add vectors with
add_batch, which links them on every core, in chunks of, say, 10,000. - Call
flush()once at the end, or every few chunks to bound what a crash can lose. A flush after everyaddwaits for the disk once per vector.
import numpy as np
from chassisdb import VectorIndex
vectors = np.random.rand(50_000, 384).astype(np.float32) # stands for your embeddings
with VectorIndex("bulk.chassis", dimensions=384) as index:
for start in range(0, len(vectors), 10_000):
index.add_batch(vectors[start : start + 10_000])
index.flush()