HNSW-Sharp

Quick Start

A short tour of HNSW-Sharp: pick parameters, build a graph, run a k-NN query, and serialize the index to disk.

If you haven't installed the package yet, see Installation:

dotnet add package HNSW.Net

1. Pick your parameters

Three knobs cover most use cases. Defaults are reasonable; tweak them once you measure recall and latency on your data.

using HNSW.Net;

var parameters = new SmallWorldParameters
{
    M = 15,                        // graph degree — higher = better recall, more memory
    LevelLambda = 1 / Math.Log(15),// keep in sync with M
    EfSearch = 50,                 // candidates kept at query time
};

For a full description of each parameter — including the ACORN-γ filtering options — see Parameters.


2. Choose a distance function

For unit-normalised float embeddings, CosineDistance.SIMDForUnits is fastest. The library ships four cosine variants; you can also plug in your own Func<TItem, TItem, TDistance>.

var graph = new SmallWorld<float[], float>(
    CosineDistance.SIMDForUnits,
    DefaultRandomGenerator.Instance,
    parameters);

See Distance functions for the trade-offs between the four cosine variants.


3. Build the index

AddItems inserts a batch of items and returns the integer IDs HNSW will use to refer to them in search results.

float[][] vectors = Enumerable.Range(0, 10_000)
    .Select(_ => RandomUnitVector(128))
    .ToArray();

IReadOnlyList<int> ids = graph.AddItems(vectors);
Console.WriteLine($"Indexed {ids.Count} vectors.");

For large indexes, pass an IProgressReporter so you can show progress to a user.


KNNSearch returns the k closest items together with their numeric distance.

float[] query = RandomUnitVector(128);

IList<SmallWorld<float[], float>.KNNSearchResult> top10 =
    graph.KNNSearch(query, k: 10);

foreach (var hit in top10.OrderBy(r => r.Distance))
{
    Console.WriteLine($"id={hit.Id} dist={hit.Distance:F4}");
}

Each result exposes Id, Item, and Distance. The list is not sorted by default — sort by Distance if you need ranked output.


5. Serialize the graph

The first build of a large index can take minutes. Once built, write the graph to a stream and reload it on startup — typically in seconds.

using (var fs = File.Create("index.hnsw"))
{
    graph.SerializeGraph(fs);
}

// Later …
using (var fs = File.OpenRead("index.hnsw"))
{
    var (reloaded, missing) = SmallWorld<float[], float>.DeserializeGraph(
        vectors,                       // same vectors as before
        CosineDistance.SIMDForUnits,   // same distance function
        DefaultRandomGenerator.Instance,
        fs);

    var hits = reloaded.KNNSearch(query, k: 10);
}

The original vectors are not stored in the file — pass them back in on DeserializeGraph. The missing array reports any items the file references that weren't supplied. See Serialization for the full pattern.


6. Put it all together

app/Program.cs
using HNSW.Net;

float[][] vectors = LoadEmbeddings();

var graph = new SmallWorld<float[], float>(
    CosineDistance.SIMDForUnits,
    DefaultRandomGenerator.Instance,
    new SmallWorldParameters { M = 15, EfSearch = 50, LevelLambda = 1 / Math.Log(15) });

graph.AddItems(vectors);

using (var fs = File.Create("index.hnsw"))
{
    graph.SerializeGraph(fs);
}

float[] query = LoadQuery();

foreach (var hit in graph.KNNSearch(query, 10).OrderBy(r => r.Distance))
{
    Console.WriteLine($"id={hit.Id} dist={hit.Distance:F4}");
}

Where next?

Core Concepts

What HNSW is, the role of each parameter, and how distance functions plug in.

Guides

Build, search, serialize, and filter — task by task with full code samples.

Advanced

Performance tuning, custom distance functions, and thread-safety semantics.

Referenced by

© 2026 HNSW-Sharp. All rights reserved.