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.
4. Run a k-NN search
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
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}");
}