What is a normalization inconsistency?
A normalization inconsistency is when L2 (or other) vector-length handling differs across indexing, querying, or evaluation – some vectors are unit-normalized, others keep raw magnitude – so rankings under cosine or dot product diverge from the geometry your model and metric assume.
Why does vector length change who counts as a neighbor?
An embedding is both a direction and a magnitude. Cosine similarity ignores length and compares only angle; the ordinary (unnormalized) dot product multiplies angle by both lengths, so longer vectors score higher even when their directions are mediocre. Many retrieval models are trained with an explicit normalization step so that cosine and (negative) dot agree; others deliberately leave magnitude free so that length encodes confidence, popularity, or score mass. HNSW does not invent a third geometry: it compares whatever floats you stored under the collection metric. If document vectors were L2-normalized at ingest but query vectors were not – or the reverse – every distance calculation mixes two incompatible conventions. The graph may still return something for each query; the top-k set is simply nearest under a hybrid of rules nobody trained.
That failure is easy to ship because normalization is often an invisible default, not a schema field you stare at every day.
Where do pipelines silently disagree about norms?
Typical splits: the embedding service returns already unit-length vectors for one model family and raw logits-style vectors for another; an ingest job calls a normalize helper while the online query path does not; a batch backfill re-embeds with a client that auto-normalizes while live traffic uses an older SDK that does not; evaluation scripts L2-normalize both sides before computing exact neighbors while production stores raw bring-your-own vectors. Partial migrations are especially nasty – half the collection rewritten to unit length, half left as historical magnitudes – so recall looks fine on new docs and collapses on old ones. Double normalization is subtler: dividing by the L2 norm twice is usually a no-op for true unit vectors, but if the first step was approximate, quantized, or applied after a scale factor intended for a different metric, you have warped the space without a loud error.
The same inconsistency also masquerades as a "metric mismatch" when the real bug is length, not the named distance function.
How do cosine and dot product amplify length mistakes?
Under a true cosine distance, unit-length pairs make cosine and (scaled) inner-product orderings agree. Leave length free and use a raw dot metric, and magnitude is part of relevance – which is correct only if every vector in the index and every query follows the same training convention. Normalize only the query into a unit sphere while documents still carry length, and you reweight the space: short documents look artificially close on angle alone; long documents lose the boost the model expected. Normalize documents but not queries and the opposite skew appears. L2-squared collections care about absolute coordinates, so casually unit-normalizing those vectors before insert changes distances the model never saw during training. Binary or other aggressive quantizers that assume roughly unit, isotropic data will also degrade when half the corpus sits on a sphere and half does not.
Weaviate makes the cosine path explicit about normalization so you can reason about what the engine does versus what your client did.
How does Weaviate treat normalization under different metrics?
When a collection uses cosine distance, Weaviate normalizes vectors to length 1 at read time and computes an efficient dot product under the hood – so client-side double-normalization is usually redundant for that metric, but client-side inconsistent scaling before insert can still interact badly with overflow, quantization, or mixed named vectors. When you choose dot, Weaviate returns the negative dot product as distance and does not erase magnitude for you: whatever length you store participates in ranking. l2-squared likewise uses raw coordinates. Match the metric to the embedding recipe, then make one decision – normalize everywhere that recipe requires, or nowhere that recipe forbids – and apply that decision in ingest, query, and offline ground-truth code. After a model swap, re-check norms with a quick histogram of L2 lengths on a sample of stored vectors and live queries; a bimodal length distribution often marks a partial migration or two embedding paths.
Detection is cheaper than another round of ef tuning that cannot fix geometry.
How should you detect and repair a normalization inconsistency?
Sample stored vectors and compute L2 norms: nearly all near 1.0 suggests unit convention; a wide spread suggests magnitude-aware or mixed data. Compare the same queries with and without explicit query normalization – large rank churn means length is deciding results. Rebuild a tiny exact-neighbor set under three policies (both sides normalized, neither, only one side) and see which policy matches production rankings; that tells you which convention the live index actually encodes. Fix by re-embedding or rewriting vectors under one policy, aligning the query path, and regenerating evaluation ground truth with the same rule. Prefer a new collection or a full reindex when half the corpus is already wrong – patching only new inserts leaves a permanent recall cliff on older objects.
A normalization inconsistency is length disagreement dressed up as bad search quality. Next, read "What is insertion-order recall variance?" when quality seems to depend on when objects were added, or "What is the difference between cosine similarity and cosine distance?" when you need the high-versus-low score intuition straightened out before you change another pipeline flag.