Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 6 additions & 0 deletions docs/changelog/125599.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 125599
summary: Allow zero for `rescore_vector.oversample` to indicate by-passing oversample
and rescoring
area: Vector Search
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ $$$dense-vector-index-options$$$
: (Optional, object) Functionality in [preview]. An optional section that configures automatic vector rescoring on knn queries for the given field. Only applicable to quantized index types.
:::::{dropdown} Properties of `rescore_vector`
`oversample`
: (required, float) The amount to oversample the search results by. This value should be greater than `1.0` and less than `10.0`. The higher the value, the more vectors will be gathered and rescored with the raw values per shard.
: (required, float) The amount to oversample the search results by. This value should be greater than `1.0` and less than `10.0` or exactly `0` to indicate no oversampling & rescoring should occur. The higher the value, the more vectors will be gathered and rescored with the raw values per shard.
: In case a knn query specifies a `rescore_vector` parameter, the query `rescore_vector` parameter will be used instead.
: See [oversampling and rescoring quantized vectors](docs-content://solutions/search/vector/knn.md#dense-vector-knn-search-rescoring) for details.
:::::
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Rescoring only makes sense for quantized vectors; when [quantization](/reference
* Retrieve `num_candidates` candidates per shard.
* From these candidates, the top `k * oversample` candidates per shard will be rescored using the original vectors.
* The top `k` rescored candidates will be returned.
Must be >= 1f to indicate oversample factor, or exactly `0` to indicate that no oversampling and rescoring should occur.


See [oversampling and rescoring quantized vectors](docs-content://solutions/search/vector/knn.md#dense-vector-knn-search-rescoring) for details.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,58 @@ setup:
- match: { hits.hits.0._score: $rescore_score0 }
- match: { hits.hits.1._score: $rescore_score1 }
- match: { hits.hits.2._score: $rescore_score2 }
---
"Test index configured rescore vector updateable and settable to 0":
- requires:
cluster_features: ["mapper.dense_vector.rescore_zero_vector"]
reason: Needs rescore_zero_vector feature

- do:
indices.create:
index: bbq_rescore_0_hnsw
body:
settings:
index:
number_of_shards: 1
mappings:
properties:
vector:
type: dense_vector
index_options:
type: bbq_hnsw
rescore_vector:
oversample: 0

- do:
indices.create:
index: bbq_rescore_update_hnsw
body:
settings:
index:
number_of_shards: 1
mappings:
properties:
vector:
type: dense_vector
index_options:
type: bbq_hnsw
rescore_vector:
oversample: 1

- do:
indices.put_mapping:
index: bbq_rescore_update_hnsw
body:
properties:
vector:
type: dense_vector
index_options:
type: bbq_hnsw
rescore_vector:
oversample: 0

- do:
indices.get_mapping:
index: bbq_rescore_update_hnsw

- match: { .bbq_rescore_update_hnsw.mappings.properties.vector.index_options.rescore_vector.oversample: 0 }
Original file line number Diff line number Diff line change
Expand Up @@ -700,3 +700,58 @@ setup:
- match: { hits.hits.0._score: $rescore_score0 }
- match: { hits.hits.1._score: $rescore_score1 }
- match: { hits.hits.2._score: $rescore_score2 }
---
"Test index configured rescore vector updateable and settable to 0":
- requires:
cluster_features: ["mapper.dense_vector.rescore_zero_vector"]
reason: Needs rescore_zero_vector feature

- do:
indices.create:
index: int8_rescore_0_hnsw
body:
settings:
index:
number_of_shards: 1
mappings:
properties:
vector:
type: dense_vector
index_options:
type: int8_hnsw
rescore_vector:
oversample: 0

- do:
indices.create:
index: int8_rescore_update_hnsw
body:
settings:
index:
number_of_shards: 1
mappings:
properties:
vector:
type: dense_vector
index_options:
type: int8_hnsw
rescore_vector:
oversample: 1

- do:
indices.put_mapping:
index: int8_rescore_update_hnsw
body:
properties:
vector:
type: dense_vector
index_options:
type: int8_hnsw
rescore_vector:
oversample: 0

- do:
indices.get_mapping:
index: int8_rescore_update_hnsw

- match: { .int8_rescore_update_hnsw.mappings.properties.vector.index_options.rescore_vector.oversample: 0 }
Original file line number Diff line number Diff line change
Expand Up @@ -731,3 +731,58 @@ setup:
- match: { hits.hits.0._score: $rescore_score0 }
- match: { hits.hits.1._score: $rescore_score1 }
- match: { hits.hits.2._score: $rescore_score2 }
---
"Test index configured rescore vector updateable and settable to 0":
- requires:
cluster_features: ["mapper.dense_vector.rescore_zero_vector"]
reason: Needs rescore_zero_vector feature

- do:
indices.create:
index: int4_rescore_0_hnsw
body:
settings:
index:
number_of_shards: 1
mappings:
properties:
vector:
type: dense_vector
index_options:
type: int4_hnsw
rescore_vector:
oversample: 0

- do:
indices.create:
index: int4_rescore_update_hnsw
body:
settings:
index:
number_of_shards: 1
mappings:
properties:
vector:
type: dense_vector
index_options:
type: int4_hnsw
rescore_vector:
oversample: 1

- do:
indices.put_mapping:
index: int4_rescore_update_hnsw
body:
properties:
vector:
type: dense_vector
index_options:
type: int4_hnsw
rescore_vector:
oversample: 0

- do:
indices.get_mapping:
index: int4_rescore_update_hnsw

- match: { .int4_rescore_update_hnsw.mappings.properties.vector.index_options.rescore_vector.oversample: 0 }
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ static TransportVersion def(int id) {
public static final TransportVersion ESQL_AGGREGATE_METRIC_DOUBLE_LITERAL = def(9_035_0_00);
public static final TransportVersion INDEX_METADATA_INCLUDES_RECENT_WRITE_LOAD = def(9_036_0_00);
public static final TransportVersion RERANK_COMMON_OPTIONS_ADDED = def(9_037_0_00);
public static final TransportVersion RESCORE_VECTOR_ALLOW_ZERO = def(9_038_0_00);

/*
* STOP! READ THIS FIRST! No, really,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ private static Version parseUnchecked(String version) {
public static final IndexVersion ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS = def(9_015_0_00, Version.LUCENE_10_1_0);
public static final IndexVersion SYNTHETIC_SOURCE_STORE_ARRAYS_NATIVELY_NUMBER = def(9_016_0_00, Version.LUCENE_10_1_0);
public static final IndexVersion SYNTHETIC_SOURCE_STORE_ARRAYS_NATIVELY_BOOLEAN = def(9_017_0_00, Version.LUCENE_10_1_0);
public static final IndexVersion RESCORE_PARAMS_ALLOW_ZERO_TO_QUANTIZED_VECTORS = def(9_018_0_00, Version.LUCENE_10_1_0);
/*
* STOP! READ THIS FIRST! No, really,
* ____ _____ ___ ____ _ ____ _____ _ ____ _____ _ _ ___ ____ _____ ___ ____ ____ _____ _
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.Set;

import static org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapper.RESCORE_VECTOR_QUANTIZED_VECTOR_MAPPING;
import static org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapper.RESCORE_ZERO_VECTOR_QUANTIZED_VECTOR_MAPPING;

/**
* Spec for mapper-related features.
Expand Down Expand Up @@ -61,7 +62,8 @@ public Set<NodeFeature> getTestFeatures() {
ObjectMapper.SUBOBJECTS_FALSE_MAPPING_UPDATE_FIX,
UKNOWN_FIELD_MAPPING_UPDATE_ERROR_MESSAGE,
DOC_VALUES_SKIPPER,
RESCORE_VECTOR_QUANTIZED_VECTOR_MAPPING
RESCORE_VECTOR_QUANTIZED_VECTOR_MAPPING,
RESCORE_ZERO_VECTOR_QUANTIZED_VECTOR_MAPPING
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,13 @@ public static boolean isNotUnitVector(float magnitude) {
public static final IndexVersion DEFAULT_TO_INT8 = DEFAULT_DENSE_VECTOR_TO_INT8_HNSW;
public static final IndexVersion LITTLE_ENDIAN_FLOAT_STORED_INDEX_VERSION = IndexVersions.V_8_9_0;
public static final IndexVersion ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS = IndexVersions.ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS;
public static final IndexVersion RESCORE_PARAMS_ALLOW_ZERO_TO_QUANTIZED_VECTORS =
IndexVersions.RESCORE_PARAMS_ALLOW_ZERO_TO_QUANTIZED_VECTORS;

public static final NodeFeature RESCORE_VECTOR_QUANTIZED_VECTOR_MAPPING = new NodeFeature("mapper.dense_vector.rescore_vector");
public static final NodeFeature RESCORE_ZERO_VECTOR_QUANTIZED_VECTOR_MAPPING = new NodeFeature(
"mapper.dense_vector.rescore_zero_vector"
);

public static final String CONTENT_TYPE = "dense_vector";
public static final short MAX_DIMS_COUNT = 4096; // maximum allowed number of dimensions
Expand Down Expand Up @@ -1321,7 +1326,7 @@ public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOpti
}
RescoreVector rescoreVector = null;
if (indexVersion.onOrAfter(ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS)) {
rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap);
rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap, indexVersion);
}
MappingParser.checkNoRemainingFields(fieldName, indexOptionsMap);
return new Int8HnswIndexOptions(m, efConstruction, confidenceInterval, rescoreVector);
Expand Down Expand Up @@ -1356,7 +1361,7 @@ public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOpti
}
RescoreVector rescoreVector = null;
if (indexVersion.onOrAfter(ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS)) {
rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap);
rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap, indexVersion);
}
MappingParser.checkNoRemainingFields(fieldName, indexOptionsMap);
return new Int4HnswIndexOptions(m, efConstruction, confidenceInterval, rescoreVector);
Expand Down Expand Up @@ -1399,7 +1404,7 @@ public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOpti
}
RescoreVector rescoreVector = null;
if (indexVersion.onOrAfter(ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS)) {
rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap);
rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap, indexVersion);
}
MappingParser.checkNoRemainingFields(fieldName, indexOptionsMap);
return new Int8FlatIndexOptions(confidenceInterval, rescoreVector);
Expand All @@ -1425,7 +1430,7 @@ public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOpti
}
RescoreVector rescoreVector = null;
if (indexVersion.onOrAfter(ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS)) {
rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap);
rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap, indexVersion);
}
MappingParser.checkNoRemainingFields(fieldName, indexOptionsMap);
return new Int4FlatIndexOptions(confidenceInterval, rescoreVector);
Expand Down Expand Up @@ -1456,7 +1461,7 @@ public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOpti
int efConstruction = XContentMapValues.nodeIntegerValue(efConstructionNode);
RescoreVector rescoreVector = null;
if (indexVersion.onOrAfter(ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS)) {
rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap);
rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap, indexVersion);
}
MappingParser.checkNoRemainingFields(fieldName, indexOptionsMap);
return new BBQHnswIndexOptions(m, efConstruction, rescoreVector);
Expand All @@ -1477,7 +1482,7 @@ public boolean supportsDimension(int dims) {
public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap, IndexVersion indexVersion) {
RescoreVector rescoreVector = null;
if (indexVersion.onOrAfter(ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS)) {
rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap);
rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap, indexVersion);
}
MappingParser.checkNoRemainingFields(fieldName, indexOptionsMap);
return new BBQFlatIndexOptions(rescoreVector);
Expand Down Expand Up @@ -1991,7 +1996,7 @@ record RescoreVector(float oversample) implements ToXContentObject {
static final String NAME = "rescore_vector";
static final String OVERSAMPLE = "oversample";

static RescoreVector fromIndexOptions(Map<String, ?> indexOptionsMap) {
static RescoreVector fromIndexOptions(Map<String, ?> indexOptionsMap, IndexVersion indexVersion) {
Object rescoreVectorNode = indexOptionsMap.remove(NAME);
if (rescoreVectorNode == null) {
return null;
Expand All @@ -2001,16 +2006,16 @@ static RescoreVector fromIndexOptions(Map<String, ?> indexOptionsMap) {
if (oversampleNode == null) {
throw new IllegalArgumentException("Invalid rescore_vector value. Missing required field " + OVERSAMPLE);
}
return new RescoreVector((float) XContentMapValues.nodeDoubleValue(oversampleNode));
}

RescoreVector {
if (oversample < 1) {
float oversampleValue = (float) XContentMapValues.nodeDoubleValue(oversampleNode);
if (oversampleValue == 0 && indexVersion.before(RESCORE_PARAMS_ALLOW_ZERO_TO_QUANTIZED_VECTORS)) {
throw new IllegalArgumentException("oversample must be greater than 1");
}
if (oversample > 10) {
if (oversampleValue < 1 && oversampleValue != 0) {
throw new IllegalArgumentException("oversample must be greater than 1 or exactly 0");
} else if (oversampleValue > 10) {
throw new IllegalArgumentException("oversample must be less than or equal to 10");
}
return new RescoreVector(oversampleValue);
}

@Override
Expand Down Expand Up @@ -2177,7 +2182,7 @@ public Query createKnnQuery(
}

private boolean needsRescore(Float rescoreOversample) {
return rescoreOversample != null && isQuantized();
return rescoreOversample != null && rescoreOversample > 0 && isQuantized();
}

private boolean isQuantized() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@

package org.elasticsearch.search.vectors;

import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.xcontent.ConstructingObjectParser;
import org.elasticsearch.xcontent.ParseField;
import org.elasticsearch.xcontent.ToXContentObject;
Expand All @@ -21,9 +23,12 @@
import java.io.IOException;
import java.util.Objects;

import static org.elasticsearch.TransportVersions.RESCORE_VECTOR_ALLOW_ZERO;

public class RescoreVectorBuilder implements Writeable, ToXContentObject {

public static final ParseField OVERSAMPLE_FIELD = new ParseField("oversample");
public static final float NO_OVERSAMPLE = 0.0F;
public static final float MIN_OVERSAMPLE = 1.0F;
private static final ConstructingObjectParser<RescoreVectorBuilder, Void> PARSER = new ConstructingObjectParser<>(
"rescore_vector",
Expand All @@ -39,8 +44,8 @@ public class RescoreVectorBuilder implements Writeable, ToXContentObject {

public RescoreVectorBuilder(float numCandidatesFactor) {
Objects.requireNonNull(numCandidatesFactor, "[" + OVERSAMPLE_FIELD.getPreferredName() + "] must be set");
if (numCandidatesFactor < MIN_OVERSAMPLE) {
throw new IllegalArgumentException("[" + OVERSAMPLE_FIELD.getPreferredName() + "] must be >= " + MIN_OVERSAMPLE);
if (numCandidatesFactor < MIN_OVERSAMPLE && numCandidatesFactor != NO_OVERSAMPLE) {
throw new IllegalArgumentException("[" + OVERSAMPLE_FIELD.getPreferredName() + "] must be >= " + MIN_OVERSAMPLE + " or 0");
}
this.oversample = numCandidatesFactor;
}
Expand All @@ -51,6 +56,17 @@ public RescoreVectorBuilder(StreamInput in) throws IOException {

@Override
public void writeTo(StreamOutput out) throws IOException {
// We don't want to serialize a `0` oversample to a node that doesn't know what to do with it.
if (oversample == NO_OVERSAMPLE && out.getTransportVersion().before(RESCORE_VECTOR_ALLOW_ZERO)) {
throw new ElasticsearchStatusException(
"[rescore_vector] does not support a 0 for ["
+ OVERSAMPLE_FIELD.getPreferredName()
+ "] before version ["
+ RESCORE_VECTOR_ALLOW_ZERO.toReleaseVersion()
+ "]",
RestStatus.BAD_REQUEST
);
}
out.writeFloat(oversample);
}

Expand Down
Loading
Loading