Skip to content

kNN vector rescoring for quantized vectors #116663

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 61 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
df06716
Use a FunctionScoreQuery to replace scores using a VectorSimilarity b…
carlosdelest Nov 12, 2024
be76444
Change API to use "rescore": {"oversample": 1.0}
carlosdelest Nov 13, 2024
bd920c5
Add tests
carlosdelest Nov 13, 2024
91204a1
Fix inference module
carlosdelest Nov 13, 2024
6c2c1be
Merge branch 'main' into feature/knn-vector-rescore-query
carlosdelest Nov 13, 2024
b44ec48
Fix knn query usage in other modules
carlosdelest Nov 14, 2024
2a9e300
Add rescore vector builder to KnnSearchBuilder
carlosdelest Nov 14, 2024
4497b92
Add vector rescore builder to kNN retriever
carlosdelest Nov 14, 2024
955da1f
Fix refactoring, spotless
carlosdelest Nov 14, 2024
ff2c1e9
Check oversampling is not used for quantized types
carlosdelest Nov 18, 2024
bc1e5c6
Minor refactoring to reuse KnnScoreDocQuery
carlosdelest Nov 20, 2024
a7936da
Use KnnRescoreVectorQuery to perform rescoring and limiting the numbe…
carlosdelest Nov 20, 2024
f5080a6
Small name refactoring, fix adjusting parameters
carlosdelest Nov 21, 2024
39e1676
Add testing
carlosdelest Nov 21, 2024
9946e8d
Add tests for RescoreKnnVectorQuery
carlosdelest Nov 27, 2024
4fbbadd
Spotless
carlosdelest Nov 27, 2024
0dab8ea
Add test for knn retriever
carlosdelest Nov 29, 2024
257b75d
Add tests
carlosdelest Nov 29, 2024
81384f2
Parameterize recore knn vector query tests
carlosdelest Nov 29, 2024
1347d4b
Adds profiling, including a small refactoring of the QueryProfiler in…
carlosdelest Nov 29, 2024
916ac83
Spotless
carlosdelest Nov 29, 2024
229ce2d
Add YAML tests
carlosdelest Dec 2, 2024
90e79db
Merge branch 'main' into feature/knn-vector-rescore-query
carlosdelest Dec 2, 2024
934eedb
Minor documentation / style fixes
carlosdelest Dec 2, 2024
cca6e39
Fix test commpilation
carlosdelest Dec 2, 2024
d95db48
Properly implement advanceExact()
carlosdelest Dec 2, 2024
ee904b0
Fix VectorSimilarityFloatValueSource implementation for advanceExact
carlosdelest Dec 2, 2024
0d77521
Add capability for BwC tests
carlosdelest Dec 2, 2024
095a951
Update docs/changelog/116663.yaml
carlosdelest Dec 3, 2024
732bd7d
Correctly implement profiling. Rename ProfilingQuery to QueryProfiler…
carlosdelest Dec 4, 2024
b5e6309
Fix toString()
carlosdelest Dec 4, 2024
3120c5c
YAML tests do not check doc ordering, just scores
carlosdelest Dec 4, 2024
da018b8
Add tests for rescoring on non-quantized values
carlosdelest Dec 4, 2024
1d5426b
having null index type means no quantization, as INT8 is explicit in …
carlosdelest Dec 4, 2024
2379596
Bytes can't be quantized - remove all infra for byte vectors in resco…
carlosdelest Dec 4, 2024
b0c2221
Add assertion to advanceExact()
carlosdelest Dec 4, 2024
9f4cebd
Parsing / toXContent improvements
carlosdelest Dec 4, 2024
fab7395
private access for field, use getter instead
carlosdelest Dec 4, 2024
e03c8e9
toXContent improvements
carlosdelest Dec 4, 2024
69b5451
Fix toXContent / parsing
carlosdelest Dec 4, 2024
ddc6094
Make rescore parameter mandatory
carlosdelest Dec 5, 2024
3ef07fa
Add index types to vector query builder tests
carlosdelest Dec 5, 2024
c127f6e
Merge branch 'main' into feature/knn-vector-rescore-query
carlosdelest Dec 5, 2024
2280fbd
Merge remote-tracking branch 'carlosdelest/feature/knn-vector-rescore…
carlosdelest Dec 5, 2024
fd9188f
Fix compilation on rrf plugin
carlosdelest Dec 5, 2024
863b6e6
Fix tests for vector query builders to ensure multiple dimensions / i…
carlosdelest Dec 5, 2024
9c9773f
Fix test to use just floats
carlosdelest Dec 5, 2024
7d083b7
Fix test, add coverage for byte element types
carlosdelest Dec 5, 2024
a8633e5
Fix YAML test capabilities, add profile test for similarity
carlosdelest Dec 5, 2024
ef8ac8c
Rename "rescore" to "rescore_vector"
carlosdelest Dec 5, 2024
4c65c8a
Fix sneaky bug on iterator
carlosdelest Dec 5, 2024
9907aad
Use 'num_candidates_factor' parameter and update num_candidates inste…
carlosdelest Dec 9, 2024
978cff3
Add knn retriever YAML tests
carlosdelest Dec 9, 2024
9412be0
Simplify logic for RescoreKnnVectorQuery now that k is not modifiable
carlosdelest Dec 9, 2024
7045500
Limit for rescoring factor is 1.0, so we can't have less rescored doc…
carlosdelest Dec 9, 2024
826fd3b
Merge branch 'main' into feature/knn-vector-rescore-query
carlosdelest Dec 9, 2024
497d8e2
Fix test after merge
carlosdelest Dec 10, 2024
83238a5
Vector similarity needs to wrap the new rescoring query and not the o…
carlosdelest Dec 10, 2024
fa975de
Change similarity to MIP in tests
carlosdelest Dec 10, 2024
74a22f8
Spotless
carlosdelest Dec 10, 2024
94963fc
Merge branch 'main' into feature/knn-vector-rescore-query
carlosdelest Dec 10, 2024
a256de9
Apply suggestions from code review
carlosdelest Dec 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog/116663.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 116663
summary: KNN vector rescoring for quantized vectors
area: Vector Search
type: feature
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -1359,7 +1359,7 @@ public void testKnnQueryNotSupportedInPercolator() throws IOException {
""");
indicesAdmin().prepareCreate("index1").setMapping(mappings).get();
ensureGreen();
QueryBuilder knnVectorQueryBuilder = new KnnVectorQueryBuilder("my_vector", new float[] { 1, 1, 1, 1, 1 }, 10, 10, null);
QueryBuilder knnVectorQueryBuilder = new KnnVectorQueryBuilder("my_vector", new float[] { 1, 1, 1, 1, 1 }, 10, 10, null, null);

IndexRequestBuilder indexRequestBuilder = prepareIndex("index1").setId("knn_query1")
.setSource(jsonBuilder().startObject().field("my_query", knnVectorQueryBuilder).endObject());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ setup:
dims: 5
index: true
index_options:
type: hnsw
type: int8_hnsw
similarity: l2_norm

- do:
Expand Down Expand Up @@ -73,3 +73,59 @@ setup:
- match: {hits.total.value: 1}
- match: {hits.hits.0._id: "3"}
- match: {hits.hits.0.fields.name.0: "rabbit.jpg"}

---
"Vector rescoring has no effect for non-quantized vectors and provides same results as non-rescored knn":
- requires:
reason: 'Quantized vector rescoring is required'
test_runner_features: [capabilities]
capabilities:
- method: GET
path: /_search
capabilities: [knn_quantized_vector_rescore]
- skip:
features: "headers"

# Rescore
- do:
headers:
Content-Type: application/json
search:
rest_total_hits_as_int: true
index: index1
body:
knn:
field: vector
query_vector: [2, 2, 2, 2, 3]
k: 3
num_candidates: 3
rescore_vector:
num_candidates_factor: 1.5

# Get rescoring scores - hit ordering may change depending on how things are distributed
- match: { hits.total: 3 }
- set: { hits.hits.0._score: rescore_score0 }
- set: { hits.hits.1._score: rescore_score1 }
- set: { hits.hits.2._score: rescore_score2 }

# Exact knn via script score
- do:
headers:
Content-Type: application/json
search:
rest_total_hits_as_int: true
index: index1
body:
query:
script_score:
query: {match_all: {} }
script:
source: "1.0 / (1.0 + Math.pow(l2norm(params.query_vector, 'vector'), 2.0))"
params:
query_vector: [2, 2, 2, 2, 3]

# Compare scores as hit IDs may change depending on how things are distributed
- match: { hits.total: 3 }
- match: { hits.hits.0._score: $rescore_score0 }
- match: { hits.hits.1._score: $rescore_score1 }
- match: { hits.hits.2._score: $rescore_score2 }
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
setup:
- requires:
reason: 'Quantized vector rescoring is required'
test_runner_features: [ capabilities ]
capabilities:
- method: GET
path: /_search
capabilities: [ knn_quantized_vector_rescore ]
- skip:
features: "headers"

- do:
indices.create:
index: bbq_hnsw
body:
settings:
index:
number_of_shards: 1
mappings:
properties:
vector:
type: dense_vector
dims: 64
index: true
similarity: max_inner_product
index_options:
type: bbq_hnsw

- do:
index:
index: bbq_hnsw
id: "1"
body:
vector: [0.077, 0.32 , -0.205, 0.63 , 0.032, 0.201, 0.167, -0.313,
0.176, 0.531, -0.375, 0.334, -0.046, 0.078, -0.349, 0.272,
0.307, -0.083, 0.504, 0.255, -0.404, 0.289, -0.226, -0.132,
-0.216, 0.49 , 0.039, 0.507, -0.307, 0.107, 0.09 , -0.265,
-0.285, 0.336, -0.272, 0.369, -0.282, 0.086, -0.132, 0.475,
-0.224, 0.203, 0.439, 0.064, 0.246, -0.396, 0.297, 0.242,
-0.028, 0.321, -0.022, -0.009, -0.001 , 0.031, -0.533, 0.45,
-0.683, 1.331, 0.194, -0.157, -0.1 , -0.279, -0.098, -0.176]
# Flush in order to provoke a merge later
- do:
indices.flush:
index: bbq_hnsw

- do:
index:
index: bbq_hnsw
id: "2"
body:
vector: [0.196, 0.514, 0.039, 0.555, -0.042, 0.242, 0.463, -0.348,
-0.08 , 0.442, -0.067, -0.05 , -0.001, 0.298, -0.377, 0.048,
0.307, 0.159, 0.278, 0.119, -0.057, 0.333, -0.289, -0.438,
-0.014, 0.361, -0.169, 0.292, -0.229, 0.123, 0.031, -0.138,
-0.139, 0.315, -0.216, 0.322, -0.445, -0.059, 0.071, 0.429,
-0.602, -0.142, 0.11 , 0.192, 0.259, -0.241, 0.181, -0.166,
0.082, 0.107, -0.05 , 0.155, 0.011, 0.161, -0.486, 0.569,
-0.489, 0.901, 0.208, 0.011, -0.209, -0.153, -0.27 , -0.013]
# Flush in order to provoke a merge later
- do:
indices.flush:
index: bbq_hnsw

- do:
index:
index: bbq_hnsw
id: "3"
body:
name: rabbit.jpg
vector: [0.139, 0.178, -0.117, 0.399, 0.014, -0.139, 0.347, -0.33 ,
0.139, 0.34 , -0.052, -0.052, -0.249, 0.327, -0.288, 0.049,
0.464, 0.338, 0.516, 0.247, -0.104, 0.259, -0.209, -0.246,
-0.11 , 0.323, 0.091, 0.442, -0.254, 0.195, -0.109, -0.058,
-0.279, 0.402, -0.107, 0.308, -0.273, 0.019, 0.082, 0.399,
-0.658, -0.03 , 0.276, 0.041, 0.187, -0.331, 0.165, 0.017,
0.171, -0.203, -0.198, 0.115, -0.007, 0.337, -0.444, 0.615,
-0.657, 1.285, 0.2 , -0.062, 0.038, 0.089, -0.068, -0.058]
# Flush in order to provoke a merge later
- do:
indices.flush:
index: bbq_hnsw

- do:
indices.forcemerge:
index: bbq_hnsw
max_num_segments: 1
---
"Profile rescored knn search":

- do:
search:
index: bbq_hnsw
body:
profile: true
knn:
field: vector
query_vector: [0.128, 0.067, -0.08 , 0.395, -0.11 , -0.259, 0.473, -0.393,
0.292, 0.571, -0.491, 0.444, -0.288, 0.198, -0.343, 0.015,
0.232, 0.088, 0.228, 0.151, -0.136, 0.236, -0.273, -0.259,
-0.217, 0.359, -0.207, 0.352, -0.142, 0.192, -0.061, -0.17 ,
-0.343, 0.189, -0.221, 0.32 , -0.301, -0.1 , 0.005, 0.232,
-0.344, 0.136, 0.252, 0.157, -0.13 , -0.244, 0.193, -0.034,
-0.12 , -0.193, -0.102, 0.252, -0.185, -0.167, -0.575, 0.582,
-0.426, 0.983, 0.212, 0.204, 0.03 , -0.276, -0.425, -0.158]
k: 3
num_candidates: 3
"rescore_vector":
"num_candidates_factor": 2.0

# We expect the knn search ops + rescoring num_cnaidates (for rescoring) per shard
- match: { profile.shards.0.dfs.knn.0.vector_operations_count: 6 }

# Search with similarity to check number of operations are propagated correctly
- do:
search:
index: bbq_hnsw
body:
profile: true
knn:
field: vector
query_vector: [0.128, 0.067, -0.08 , 0.395, -0.11 , -0.259, 0.473, -0.393,
0.292, 0.571, -0.491, 0.444, -0.288, 0.198, -0.343, 0.015,
0.232, 0.088, 0.228, 0.151, -0.136, 0.236, -0.273, -0.259,
-0.217, 0.359, -0.207, 0.352, -0.142, 0.192, -0.061, -0.17 ,
-0.343, 0.189, -0.221, 0.32 , -0.301, -0.1 , 0.005, 0.232,
-0.344, 0.136, 0.252, 0.157, -0.13 , -0.244, 0.193, -0.034,
-0.12 , -0.193, -0.102, 0.252, -0.185, -0.167, -0.575, 0.582,
-0.426, 0.983, 0.212, 0.204, 0.03 , -0.276, -0.425, -0.158]
k: 3
num_candidates: 3
similarity: 100000
"rescore_vector":
"num_candidates_factor": 2.0

# We expect the knn search ops + rescoring num_cnaidates (for rescoring) per shard
- match: { profile.shards.0.dfs.knn.0.vector_operations_count: 6 }
Comment on lines +136 to +137
Copy link
Member

Choose a reason for hiding this comment

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

❤️

Original file line number Diff line number Diff line change
Expand Up @@ -550,3 +550,58 @@ setup:
num_candidates: 3

- match: { hits.total.value: 0 }
---
"Vector rescoring has no effect for non-quantized vectors and provides same results as non-rescored knn":
- requires:
reason: 'Quantized vector rescoring is required'
test_runner_features: [capabilities]
capabilities:
- method: GET
path: /_search
capabilities: [knn_quantized_vector_rescore]
- skip:
features: "headers"

# Non-rescored knn
- do:
headers:
Content-Type: application/json
search:
rest_total_hits_as_int: true
index: test
body:
fields: [ "name" ]
knn:
field: vector
query_vector: [-0.5, 90.0, -10, 14.8, -156.0]
k: 3
num_candidates: 3

# Get scores - hit ordering may change depending on how things are distributed
- match: { hits.total: 3 }
- set: { hits.hits.0._score: knn_score0 }
- set: { hits.hits.1._score: knn_score1 }
- set: { hits.hits.2._score: knn_score2 }

# Rescored knn
- do:
headers:
Content-Type: application/json
search:
rest_total_hits_as_int: true
index: test
body:
fields: [ "name" ]
knn:
field: vector
query_vector: [-0.5, 90.0, -10, 14.8, -156.0]
k: 3
num_candidates: 3
rescore_vector:
num_candidates_factor: 1.5

# Compare scores as hit IDs may change depending on how things are distributed
- match: { hits.total: 3 }
- match: { hits.hits.0._score: $knn_score0 }
- match: { hits.hits.1._score: $knn_score1 }
- match: { hits.hits.2._score: $knn_score2 }
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,75 @@ setup:
- match: { hits.hits.1._id: "3" }
- match: { hits.hits.2._id: "2" }
---
"Vector rescoring has same scoring as exact search for kNN section":
- requires:
reason: 'Quantized vector rescoring is required'
test_runner_features: [capabilities]
capabilities:
- method: GET
path: /_search
capabilities: [knn_quantized_vector_rescore]
- skip:
features: "headers"

# Rescore
- do:
headers:
Content-Type: application/json
search:
rest_total_hits_as_int: true
index: bbq_hnsw
body:
knn:
field: vector
query_vector: [0.128, 0.067, -0.08 , 0.395, -0.11 , -0.259, 0.473, -0.393,
0.292, 0.571, -0.491, 0.444, -0.288, 0.198, -0.343, 0.015,
0.232, 0.088, 0.228, 0.151, -0.136, 0.236, -0.273, -0.259,
-0.217, 0.359, -0.207, 0.352, -0.142, 0.192, -0.061, -0.17 ,
-0.343, 0.189, -0.221, 0.32 , -0.301, -0.1 , 0.005, 0.232,
-0.344, 0.136, 0.252, 0.157, -0.13 , -0.244, 0.193, -0.034,
-0.12 , -0.193, -0.102, 0.252, -0.185, -0.167, -0.575, 0.582,
-0.426, 0.983, 0.212, 0.204, 0.03 , -0.276, -0.425, -0.158]
k: 3
num_candidates: 3
rescore_vector:
num_candidates_factor: 1.5

# Get rescoring scores - hit ordering may change depending on how things are distributed
- match: { hits.total: 3 }
- set: { hits.hits.0._score: rescore_score0 }
- set: { hits.hits.1._score: rescore_score1 }
- set: { hits.hits.2._score: rescore_score2 }

# Exact knn via script score
- do:
headers:
Content-Type: application/json
search:
rest_total_hits_as_int: true
body:
query:
script_score:
query: {match_all: {} }
script:
source: "double similarity = dotProduct(params.query_vector, 'vector'); return similarity < 0 ? 1 / (1 + -1 * similarity) : similarity + 1"
params:
query_vector: [0.128, 0.067, -0.08 , 0.395, -0.11 , -0.259, 0.473, -0.393,
0.292, 0.571, -0.491, 0.444, -0.288, 0.198, -0.343, 0.015,
0.232, 0.088, 0.228, 0.151, -0.136, 0.236, -0.273, -0.259,
-0.217, 0.359, -0.207, 0.352, -0.142, 0.192, -0.061, -0.17 ,
-0.343, 0.189, -0.221, 0.32 , -0.301, -0.1 , 0.005, 0.232,
-0.344, 0.136, 0.252, 0.157, -0.13 , -0.244, 0.193, -0.034,
-0.12 , -0.193, -0.102, 0.252, -0.185, -0.167, -0.575, 0.582,
-0.426, 0.983, 0.212, 0.204, 0.03 , -0.276, -0.425, -0.158]

# Compare scores as hit IDs may change depending on how things are distributed
- match: { hits.total: 3 }
- match: { hits.hits.0._score: $rescore_score0 }
- match: { hits.hits.1._score: $rescore_score1 }
- match: { hits.hits.2._score: $rescore_score2 }

---
"Test bad quantization parameters":
- do:
catch: bad_request
Expand Down
Loading