TECHNOLOGY

pgvector cosine and inner product: normalization and ORDER BY must agree

In this article
THE QUICK TAKE

The pgvector <#> operator returns negative inner product, so ascending distance order differs from displaying positive similarity.

Cosine distance and inner product can produce compatible rankings under particular normalization conditions, but their SQL operators do not have the same sign or meaning. Audit representation and ordering together before interpreting a changed pgvector result as a model-quality problem.

Read the operator rather than its label

The pgvector reference states that <#> returns negative inner product because PostgreSQL index scans use ascending order for these operators. An application showing a positive similarity may transform that result for display. Copying the displayed value into a distance threshold or reversing the query order can therefore change behavior. Preserve the original SQL quantity separately from the user-facing score.

Establish whether normalization is guaranteed

Unit normalization is a property to verify for the selected encoder and processing path. It cannot be inferred from vector dimensions or a model family name. Compare query and document generation, including any truncation or extra pooling. An unnormalized replacement encoder can invalidate assumptions that previously made cosine and inner-product rankings comparable. Reusing an index name does not preserve those assumptions when its stored contents have changed.

Inspect the available functions

The extension definitions distinguish distance, inner-product and normalization functions for supported representations. Use those definitions to identify the actual operation rather than treating every returned float as a probability. A near-duplicate fixture can reveal an accidental sign inversion: a document expected near the query appears at the wrong end of the list even though the embedding files are unchanged. Check ordering before changing the model.

QuantityOperational distinction
Similarity shown to usersMay be transformed for display
SQL distance/operator outputDetermines query ordering
Normalized vectorA representation condition to verify

Keep expression and index aligned

An expression index concerns the expression PostgreSQL actually evaluates. Adding a cast or normalization function in a query can create another expression from the one indexed. Inspect the plan and operation together. Avoid treating a successful sequential evaluation as proof that the intended approximate index was used. Correct ordering and the desired access path are separate acceptance questions.

Compare against one exact fixture

Save several labeled query-document relationships, their original vectors and the chosen metric. Compute the declared exact ordering on that small set, then compare the application output. Include a case whose vector norm differs if the production contract permits such inputs. This exposes assumptions hidden by an all-unit test set. Preserve missing or invalid values as explicit input failures instead of silently assigning them a convenient score.

Change one contract at a time

When migrating from cosine to inner product, first establish the normalization condition and expected ordering. Then change the query and index configuration deliberately, retaining the old control. A final threshold needs its own validation on labeled examples. The resulting record should explain score interpretation, representation and access path, so a later frontend change cannot silently reverse relevance or turn a distance into an unsupported probability claim.

Related reading: PostgreSQL and pgvector for RAG on an existing server; Batch text embeddings on your own GPU.

← All articles