Commit 10ad86d7 authored by Sergei Golubchik's avatar Sergei Golubchik

generous_furthest optimization

make generosity depend on

1. M. Keep small M's fast, increase generosity for larger M's to get
   better recall.

2. distance. Keep generosity small when vectors are far from the
   target, increase generosity when the search gets closer. This
   allows to examine more relevant vectors but doesn't waste time
   examining irrelevant vectors. Particularly important with cosine
   metric when the distance is bounded
parent 59e31f85
......@@ -30,7 +30,6 @@ using namespace Eigen;
// Algorithm parameters
static constexpr float alpha = 1.1f;
static constexpr float generosity = 1.1f;
static constexpr uint ef_construction= 10;
static constexpr size_t subdist_part= 192;
static constexpr float subdist_margin= 1.1f;
......@@ -365,6 +364,7 @@ class MHNSW_Context : public Sql_alloc
size_t vec_len= 0;
size_t byte_len= 0;
Atomic_relaxed<double> ef_power{0.6}; // for the bloom filter size heuristic
Atomic_relaxed<float> diameter{0}; // for the generosity heuristic
FVectorNode *start= 0;
Map<MatrixXf> randomizer;
const uint tref_len;
......@@ -1036,6 +1036,17 @@ static int update_second_degree_neighbors(MHNSW_Context *ctx, TABLE *graph,
return 0;
}
static inline float generous_furthest(const Queue<Visited> &q, float maxd, float g)
{
float d0=maxd*g/2;
float d= q.top()->distance_to_target;
float k= 5;
float x= (d-d0)/d0;
float sigmoid= k*x/std::sqrt(1+(k*k-1)*x*x); // or any other sigmoid
return d*(1 + (g - 1)/2 * (1 - sigmoid));
}
static int search_layer(MHNSW_Context *ctx, TABLE *graph, const FVector *target,
Neighborhood *start_nodes, uint result_size,
size_t layer, Neighborhood *result, bool construction)
......@@ -1047,6 +1058,7 @@ static int search_layer(MHNSW_Context *ctx, TABLE *graph, const FVector *target,
Queue<Visited> candidates, best;
bool skip_deleted;
uint ef= result_size;
float generosity= 1.1f + ctx->M/500.0f;
if (construction)
{
......@@ -1070,10 +1082,12 @@ static int search_layer(MHNSW_Context *ctx, TABLE *graph, const FVector *target,
best.init(ef, true, Visited::cmp);
DBUG_ASSERT(start_nodes->num <= result_size);
float max_distance= ctx->diameter;
for (size_t i=0; i < start_nodes->num; i++)
{
auto node= start_nodes->links[i];
Visited *v= visited.create(node, node->distance_to(target));
max_distance= std::max(max_distance, v->distance_to_target);
candidates.push(v);
if (skip_deleted && v->node->deleted)
continue;
......@@ -1081,7 +1095,7 @@ static int search_layer(MHNSW_Context *ctx, TABLE *graph, const FVector *target,
}
float furthest_best= best.is_empty() ? FLT_MAX
: best.top()->distance_to_target * generosity;
: generous_furthest(best, max_distance, generosity);
while (candidates.elements())
{
const Visited &cur= *candidates.pop();
......@@ -1107,11 +1121,12 @@ static int search_layer(MHNSW_Context *ctx, TABLE *graph, const FVector *target,
if (!best.is_full())
{
Visited *v= visited.create(links[i], links[i]->distance_to(target));
max_distance= std::max(max_distance, v->distance_to_target);
candidates.push(v);
if (skip_deleted && v->node->deleted)
continue;
best.push(v);
furthest_best= best.top()->distance_to_target * generosity;
furthest_best= generous_furthest(best, max_distance, generosity);
}
else
{
......@@ -1124,13 +1139,14 @@ static int search_layer(MHNSW_Context *ctx, TABLE *graph, const FVector *target,
if (v->distance_to_target < best.top()->distance_to_target)
{
best.replace_top(v);
furthest_best= best.top()->distance_to_target * generosity;
furthest_best= generous_furthest(best, max_distance, generosity);
}
}
}
}
}
}
set_if_bigger(ctx->diameter, max_distance); // not atomic, but it's ok
if (ef > 1 && visited.count*2 > est_size)
{
double ef_power= std::log(visited.count*2/est_heuristic) / std::log(ef);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment