kvex — pure Erlang approximate k-NN vector search on the BEAM.
Two ETS tables per index:
• vec table — {Id, F32Bin, BinVec} per vector (source of truth)
• flat cache — {flat, F32FlatBin, BvecFlatBin, IdsTuple} rebuilt on
every insert; a single refc-binary per flat, so search
never iterates over individual Erlang terms.
Search path (two sied NIF calls on flat binaries, no Erlang list work):
1. sied:hamming_topk_flat/4 — SIMD POPCNT on BvecFlat, O(N)+O(K logK)
2. sied:dot_product_topk_flat/4 — SIMD dot-product on F32Flat candidates
{ok, Ix} = kvex:new(128),
Vec = [rand:uniform() || _ <- lists:seq(1, 128)],
ok = kvex:add(Ix, 42, Vec),
{ok, Rs} = kvex:search(Ix, Vec, 5),
ok = kvex:delete(Ix).
id() = non_neg_integer() | binary()
abstract datatype: index()
opts() = #{bits => 2 | 3 | 4}
vector() = [float()] | binary()
| add/3 | Inserts a single vector. |
| add_batch/2 | Inserts vectors in batch. |
| cosine_search/3 | |
| delete/1 | |
| new/1 | |
| new/2 | Creates an empty index for vectors of dimension Dim. |
| normalize/1 | |
| search/3 | |
| size/1 | |
| version/0 |
Inserts a single vector. Rebuilds the flat cache — O(N) copy.
Inserts vectors in batch. Builds the flat binary incrementally — O(batch).
cosine_search(Ix::index(), Query::vector(), K::pos_integer()) -> {ok, [{id(), Score::float()}]} | {error, term()}
delete(X1::index()) -> ok
new(Dim::pos_integer()) -> {ok, index()} | {error, term()}
Creates an empty index for vectors of dimension Dim.
normalize(Vec::vector()) -> {ok, [float()]} | {error, term()}
search(X1::index(), Query::vector(), K::pos_integer()) -> {ok, [{id(), Score::float()}]} | {error, term()}
size(X1::index()) -> non_neg_integer()
version() -> binary()
Generated by EDoc