Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 }
Original file line number Diff line number Diff line change
Expand Up @@ -541,3 +541,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