You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up from the review of #5739 (fix for #5558), where this was raised three times as out of scope for that fix but worth capturing.
The precondition
JVector's VectorSimilarityFunction.DOT_PRODUCT computes (1 + dotProduct(a, b)) / 2 and documents the precondition in its own javadoc:
NOTE: this similarity is intended as an optimized way to perform cosine similarity. In order to use it, all vectors must be normalized, including both document and query vectors. Using dot product with vectors that are not normalized can result in errors or poor search results.
ArcadeDB does not enforce it. CREATE INDEX ... LSM_VECTOR METADATA {"similarity": "DOT_PRODUCT"} accepts any vectors, and nothing warns when they are not unit length.
With unit vectors dot is in [-1, 1], so the score is in [0, 1] and 0 is the floor. #5739 relies on that: a graph node whose vector can no longer be read is scored at UNREADABLE_NODE_SCORE = 0.0f, on the basis that 0 is at or below every real score for all three metrics.
For non-unit vectors dot is unbounded below, so a real vector can score below 0 and a tombstone scored at exactly 0 would outrank it in the beam. The consequence is bounded - a tombstone can waste beam budget but can never enter a result, because LiveVectorBitsFilter decides membership, not the score - so this is a recall/efficiency issue rather than a correctness one. But it is a precondition the engine currently leaves entirely to the user, on a metric whose whole point is that it is the fast path for cosine on already-normalized data.
It is also a foot-gun independent of #5739: a user who picks DOT_PRODUCT without normalizing gets quietly worse rankings than COSINE would have given, which is the opposite of the reason to pick it.
Options
Warn at index creation / first build when the vectors seen are not unit length (a sampled check during the graph build is nearly free - it already reads every vector).
Normalize on ingest for DOT_PRODUCT indexes. Changes stored data, so it would need to be explicit.
Refuse a non-unit vector on a DOT_PRODUCT index. Strictest, and a breaking change for anyone relying on today's silence.
(1) looks like the right default: it costs nothing on the path that already reads every vector, it turns a silent quality problem into an operator-visible one, and it breaks nobody.
Follow-up from the review of #5739 (fix for #5558), where this was raised three times as out of scope for that fix but worth capturing.
The precondition
JVector's
VectorSimilarityFunction.DOT_PRODUCTcomputes(1 + dotProduct(a, b)) / 2and documents the precondition in its own javadoc:ArcadeDB does not enforce it.
CREATE INDEX ... LSM_VECTOR METADATA {"similarity": "DOT_PRODUCT"}accepts any vectors, and nothing warns when they are not unit length.Why it matters more since #5739
With unit vectors
dotis in[-1, 1], so the score is in[0, 1]and 0 is the floor. #5739 relies on that: a graph node whose vector can no longer be read is scored atUNREADABLE_NODE_SCORE = 0.0f, on the basis that 0 is at or below every real score for all three metrics.For non-unit vectors
dotis unbounded below, so a real vector can score below 0 and a tombstone scored at exactly 0 would outrank it in the beam. The consequence is bounded - a tombstone can waste beam budget but can never enter a result, becauseLiveVectorBitsFilterdecides membership, not the score - so this is a recall/efficiency issue rather than a correctness one. But it is a precondition the engine currently leaves entirely to the user, on a metric whose whole point is that it is the fast path for cosine on already-normalized data.It is also a foot-gun independent of #5739: a user who picks
DOT_PRODUCTwithout normalizing gets quietly worse rankings thanCOSINEwould have given, which is the opposite of the reason to pick it.Options
DOT_PRODUCTindexes. Changes stored data, so it would need to be explicit.DOT_PRODUCTindex. Strictest, and a breaking change for anyone relying on today's silence.UNREADABLE_NODE_SCOREthat the floor claim is precondition-bound (this part is already done in fix(engine) #5558: a vector search inside a deleted region finds the survivors instead of nothing #5739).(1) looks like the right default: it costs nothing on the path that already reads every vector, it turns a silent quality problem into an operator-visible one, and it breaks nobody.