Skip to content

Add full precision rescoring for quantized vector search - #1051

Merged
aprudhomme merged 2 commits into
Yelp:mainfrom
aprudhomme:quantized_vector_rescore
Oct 14, 2026
Merged

aprudhomme merged 2 commits into
Yelp:mainfrom
aprudhomme:quantized_vector_rescore

Conversation

@aprudhomme

Copy link
Copy Markdown
Contributor

Summary

Adds the option to rescore quantized KNN vector search hits using the full precision vectors. This is the same idea as Elasticsearch's rescore_vector.

For hnsw_scalar_quantized fields, both the HNSW graph search and the hit scores come from the quantized vectors. The full precision float vectors are still stored in the index. A common way to recover accuracy is to oversample the quantized search, rescore those candidates with the full precision vectors, and return the top k.

Changes

KnnQuery.rescore_vector

New RescoreVector message with an oversample factor:

"knn": [
  {
    "field": "embedding_quantized",
    "k": 10,
    "num_candidates": 100,
    "query_vector": [0.18, 0.24, ...],
    "rescore_vector": { "oversample": 3.0 }
  }
]
  • The vector search collects ceil(k * oversample) hits, capped at the num_candidates limit of 10000. num_candidates is raised to this value if it is lower.
  • Hits are rescored with the full precision vectors, and the top k are returned with full precision similarity scores.
  • oversample must be >= 1.0, or 0 (default) to disable rescoring. A value of 1.0 re-ranks the top k without collecting extra hits.
  • similarityThreshold and boost are applied to the rescored score.
  • Works for both top level knn queries and multi-retriever KnnRetriever, since they share the same query building path.
  • Only supported for float vector fields. Byte fields are not quantized, and requests that use the option on them return an error.
  • For nested vector fields, only the best matching child of each parent is rescored.

Implementation uses Lucene's RescoreTopNQuery with FullPrecisionFloatVectorSimilarityValuesSource. The new VectorRescoreQuery wrapper:

  • exposes the inner query's vector total hits through WithVectorTotalHits, so vector diagnostics keep working
  • preserves MatchNoDocsQuery when the filter matches no documents

ExactVectorQuery.full_precision

ExactVectorQuery scores with FloatVectorValues.scorer(), which uses the quantized vectors when the field has a quantized vector search type. The new full_precision flag switches to FloatVectorValues.rescorer(), which uses the full precision vectors. It defaults to false, so existing behavior is unchanged.

This also allows full precision rescoring of any first pass query through the existing rescorer API:

"rescorers": [
  {
    "windowSize": 100,
    "name": "vector_rescore",
    "queryRescorer": {
      "rescoreQuery": {
        "exactVectorQuery": {
          "field": "embedding_quantized",
          "query_float_vector": [0.18, 0.24, ...],
          "full_precision": true
        }
      },
      "queryWeight": 0.0,
      "rescoreQueryWeight": 1.0
    }
  }
]

Only supported for float vector fields.

Other

  • Updated docs/vector_search.rst with a "Rescoring Quantized Vector Search" section. Also corrected the KnnQuery proto snippet.
  • Regenerated the grpc-gateway files.

Testing

New tests in VectorFieldDefTest, using a 4 bit quantized field:

  • Rescored hits have full precision scores and match the brute force top k.
  • Without rescoring, scores differ from full precision.
  • oversample = 1.0, normalized cosine, boost, similarity threshold, filter, filter matching no documents, and the num_candidates limit cap.
  • Nested vector field rescoring.
  • Validation errors for invalid oversample values and for byte fields.
  • ExactVectorQuery with full_precision matches brute force full precision results. Without it, scores are quantized.
./gradlew :test --tests '*Vector*' --tests '*Knn*' --tests '*MultiRetriever*' --tests '*Retriever*'

Add KnnQuery.rescore_vector.oversample, which collects ceil(k * oversample)
hits using the quantized vector scores, rescores them with the full
precision vectors, and returns the top k. This uses Lucene's
RescoreTopNQuery with FullPrecisionFloatVectorSimilarityValuesSource,
wrapped in VectorRescoreQuery to preserve vector diagnostics.

Add ExactVectorQuery.full_precision to score with the full precision
vectors instead of the quantized vectors, allowing full precision
rescoring through a QueryRescorer.

Both options are only supported for float vector fields.
Add VectorRescoreQueryTest unit tests. Add VectorFieldDefTest coverage for
the oversampled k and num candidates of the built query, rescoring through a
KnnRetriever, and profiled rescore searches. Add NrtKnnFloatVectorQuery.getTopHits
to inspect the oversampled k.

Fix VectorRescoreQuery.toString, which threw a NullPointerException from
FullPrecisionFloatVectorSimilarityValuesSource.toString when the values source
is created without a similarity function. Document why RescoreTopNQuery is
wrapped instead of using createFullPrecisionRescorerQuery.

@vim345 vim345 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat feature.

@aprudhomme
aprudhomme merged commit 9cdc80c into Yelp:main Oct 14, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants