FANN: Vector Search in 200 Lines of Rust
fennel.ai
fennel.ai
FWIW - Weaviate's pure Go implementation of cosine similarity is probably as fast as you can possibly get in pure go, without SIMD -- https://github.com/weaviate/weaviate/blob/2f44dbee52196656bd...
The classic approach is to keep your k candidates in a max heap ordered by distance (replacing the farthest candidates whenever the heap is full and you find nearer items in the leaves), and visit the far side of the hyperplane after visiting the near side if either the distance to the hyperplane is less than the distance to the farthest item in the heap or you don't yet have k candidates.
A way to visualize it is that your k candidates define a ball (with radius defined by the worst candidate) around the search point that shrinks as you find better candidates. Then you recursively visit all the nodes in the tree that the ball still touches. Any nodes outside the ball can't possibly improve on the candidates within it and so can be excluded.
In graphics rendering, this sort of thing was the approach used by photon mapping [0] to find the closest photons for density estimation.
And it is around 10 lines of code in SQL: https://presentations.clickhouse.com/meetup74/ai/#35
Right at the top...
Obviously, together with bindings for Rust, Python, JavaScript, Java, Objective-C, Swift, and Wolfram it gets slightly larger, but on the bright side, you can pass JIT-compiled functions and store/search elements of different dimensionality, which allows much broader applicability [2].
[1]: https://github.com/unum-cloud/usearch [2]: https://ashvardanian.com/posts/abusing-vector-search
Why? Sorry, but your comment is not very insightful without more explanation, and the rest of your comment is just an advertisement for your (admittedly impressive looking) work.
FWIW exhaustive search is still probably good enough for most use cases. IMO the exhaustive search should use a heap https://github.com/fennel-ai/fann/blob/main/src/main.rs#L12 as you're only looking for top-k, it reduces time and memory complexity greatly. On a relatively unoptimized Golang implementation (though much beefier hardware) I get ~100ms to process 1M vectors of 300 dim. Still quite a bit slower than approximate, of course, but in absolute terms probably good enough for most use cases, especially because many use cases don't have 1M vectors.
Video: https://www.youtube.com/watch?v=hGRNcftpqAk
Presentation: https://presentations.clickhouse.com/meetup74/ai/
PS. It should work at the same speed as "bruteforce-blas" in the ANN benchmarks.
But I didn't evaluate it thoroughly. The threshold for Hamming distance over bit masks was set almost arbitrarily, and it will be interesting to test further.
This idea improves the scaling from linear in dimension to logarithmic when very little loss using ideas from multi-arm bandits
Isn't that just a fancy way of saying a line from O (a tuple of zeroes) to C to infinity?
Now there are various ways to define which hyperplane you speak about. The easiest to reason about is to give a couple points that are on the hyperplane (1 point defines a point, a line can be defined by any two points on that line, a plane by three points on that plane etc.). A slightly weirder but equally useful way is to say "it's orthogonal to this line, and intersects this point". That's easy to reason about in three dimensions, but apparently works in higher dimensions as well.
e.g. https://www.cs.princeton.edu/~chazelle/pubs/FJLT-sicomp09.pd...
For example, see my comment above: https://news.ycombinator.com/item?id=36341754
Given this, around a second to build an index of 10K items sounds about right.
I've now updated the article to clearly mention this and also cite annoy.
Cos(x, y) = x . y / ||x|| * ||y||
If x and y are normalized:
Cos(x, y) = x . y
It also isn't a distance...
Since you can compose any vector in the space out of unit vectors, you can extend the concept. See above comment inner product -> metric space.
What is a proper distance function is (1 - |a*b|). That's proportional to the distance between those points on the plane spanned along the circumference of the circle/sphere/hypersphere as it's projected onto that plane.
If u = v, then u*v = 1, but for two points not equal to zero, the distance between them should always be zero. Remember that u*v = 0 if u and v are perpendicular. If neither u nor v are 0, then for them to be perpendicular, they must certainly not be equal. The dot product does not behave like a distance.