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
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ In many cases, a brute-force kNN search is not efficient enough. For this reason

Unmapped array fields of float elements with size between 128 and 4096 are dynamically mapped as `dense_vector` with a default similariy of `cosine`. You can override the default similarity by explicitly mapping the field as `dense_vector` with the desired similarity.

Indexing is enabled by default for dense vector fields and indexed as `int8_hnsw`. When indexing is enabled, you can define the vector similarity to use in kNN search:
Indexing is enabled by default for dense vector fields and indexed as `bbq_hnsw` if dimensions are greater than or equal to 384, otherwise they are indexed as `int8_hnsw`. When indexing is enabled, you can define the vector similarity to use in kNN search:

```console
PUT my-index-2
Expand Down Expand Up @@ -105,7 +105,7 @@ The `dense_vector` type supports quantization to reduce the memory footprint req

When using a quantized format, you may want to oversample and rescore the results to improve accuracy. See [oversampling and rescoring](docs-content://solutions/search/vector/knn.md#dense-vector-knn-search-rescoring) for more information.

To use a quantized index, you can set your index type to `int8_hnsw`, `int4_hnsw`, or `bbq_hnsw`. When indexing `float` vectors, the current default index type is `int8_hnsw`.
To use a quantized index, you can set your index type to `int8_hnsw`, `int4_hnsw`, or `bbq_hnsw`. When indexing `float` vectors, the current default index type is `bbq_hnsw` for vectors with greater than or equal to 384 dimensions, otherwise it's `int8_hnsw`.

Quantized vectors can use [oversampling and rescoring](docs-content://solutions/search/vector/knn.md#dense-vector-knn-search-rescoring) to improve accuracy on approximate kNN search results.

Expand Down Expand Up @@ -255,9 +255,9 @@ $$$dense-vector-index-options$$$
`type`
: (Required, string) The type of kNN algorithm to use. Can be either any of:
* `hnsw` - This utilizes the [HNSW algorithm](https://arxiv.org/abs/1603.09320) for scalable approximate kNN search. This supports all `element_type` values.
* `int8_hnsw` - The default index type for float vectors. This utilizes the [HNSW algorithm](https://arxiv.org/abs/1603.09320) in addition to automatically scalar quantization for scalable approximate kNN search with `element_type` of `float`. This can reduce the memory footprint by 4x at the cost of some accuracy. See [Automatically quantize vectors for kNN search](#dense-vector-quantization).
* `int8_hnsw` - The default index type for float vectors with less than 384 dimensions. This utilizes the [HNSW algorithm](https://arxiv.org/abs/1603.09320) in addition to automatically scalar quantization for scalable approximate kNN search with `element_type` of `float`. This can reduce the memory footprint by 4x at the cost of some accuracy. See [Automatically quantize vectors for kNN search](#dense-vector-quantization).
* `int4_hnsw` - This utilizes the [HNSW algorithm](https://arxiv.org/abs/1603.09320) in addition to automatically scalar quantization for scalable approximate kNN search with `element_type` of `float`. This can reduce the memory footprint by 8x at the cost of some accuracy. See [Automatically quantize vectors for kNN search](#dense-vector-quantization).
* `bbq_hnsw` - This utilizes the [HNSW algorithm](https://arxiv.org/abs/1603.09320) in addition to automatically binary quantization for scalable approximate kNN search with `element_type` of `float`. This can reduce the memory footprint by 32x at the cost of accuracy. See [Automatically quantize vectors for kNN search](#dense-vector-quantization).
* `bbq_hnsw` - The default index type for float vectors with greater than or equal to 384 dimensions. This utilizes the [HNSW algorithm](https://arxiv.org/abs/1603.09320) in addition to automatically binary quantization for scalable approximate kNN search with `element_type` of `float`. This can reduce the memory footprint by 32x at the cost of accuracy. See [Automatically quantize vectors for kNN search](#dense-vector-quantization).
* `flat` - This utilizes a brute-force search algorithm for exact kNN search. This supports all `element_type` values.
* `int8_flat` - This utilizes a brute-force search algorithm in addition to automatically scalar quantization. Only supports `element_type` of `float`.
* `int4_flat` - This utilizes a brute-force search algorithm in addition to automatically half-byte scalar quantization. Only supports `element_type` of `float`.
Expand Down
1 change: 1 addition & 0 deletions rest-api-spec/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ tasks.named("yamlRestCompatTestTransform").configure ({ task ->
task.skipTest("cat.shards/10_basic/Help", "sync_id is removed in 9.0")
task.skipTest("search/500_date_range/from, to, include_lower, include_upper deprecated", "deprecated parameters are removed in 9.0")
task.skipTest("search.highlight/30_max_analyzed_offset/Plain highlighter with max_analyzed_offset < 0 should FAIL", "semantics of test has changed")
task.skipTest("search.vectors/70_dense_vector_telemetry/Field mapping stats with field details", "default dense vector field mapping has changed")
task.skipTest("range/20_synthetic_source/Double range", "_source.mode mapping attribute is no-op since 9.0.0")
task.skipTest("range/20_synthetic_source/Float range", "_source.mode mapping attribute is no-op since 9.0.0")
task.skipTest("range/20_synthetic_source/Integer range", "_source.mode mapping attribute is no-op since 9.0.0")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
setup:
- requires:
cluster_features: [ "gte_v8.4.0" ]
reason: "Cluster mappings stats for indexed dense vector was added in 8.4"
cluster_features: [ "search.vectors.mappers.default_bbq_hnsw" ]
reason: "Test cluster feature 'search.vectors.mappers.default_bbq_hnsw' is required for using bbq as default
indexing for vector fields."
- skip:
features: headers

Expand All @@ -13,7 +14,7 @@ setup:
index.number_of_shards: 2
mappings:
properties:
vector1:
vector_hnsw_explicit:
type: dense_vector
dims: 768
index: true
Expand All @@ -23,12 +24,16 @@ setup:
type: hnsw
m: 16
ef_construction: 100
vector2:
vector_bbq_default:
type: dense_vector
dims: 1024
index: true
similarity: dot_product
vector3:
vector_int8_hnsw_default:
type: dense_vector
dims: 100
index: true
vector_no_index:
type: dense_vector
dims: 100
index: false
Expand All @@ -52,10 +57,10 @@ setup:
- do: { cluster.stats: { } }
- length: { indices.mappings.field_types: 1 }
- match: { indices.mappings.field_types.0.name: dense_vector }
- match: { indices.mappings.field_types.0.count: 4 }
- match: { indices.mappings.field_types.0.count: 5 }
- match: { indices.mappings.field_types.0.index_count: 2 }
- match: { indices.mappings.field_types.0.indexed_vector_count: 3 }
- match: { indices.mappings.field_types.0.indexed_vector_dim_min: 768 }
- match: { indices.mappings.field_types.0.indexed_vector_count: 4 }
- match: { indices.mappings.field_types.0.indexed_vector_dim_min: 100 }
- match: { indices.mappings.field_types.0.indexed_vector_dim_max: 1024 }
---
"Field mapping stats with field details":
Expand All @@ -70,15 +75,16 @@ setup:
- do: { cluster.stats: { } }
- length: { indices.mappings.field_types: 1 }
- match: { indices.mappings.field_types.0.name: dense_vector }
- match: { indices.mappings.field_types.0.count: 4 }
- match: { indices.mappings.field_types.0.count: 5 }
- match: { indices.mappings.field_types.0.index_count: 2 }
- match: { indices.mappings.field_types.0.indexed_vector_count: 3 }
- match: { indices.mappings.field_types.0.indexed_vector_dim_min: 768 }
- match: { indices.mappings.field_types.0.indexed_vector_count: 4 }
- match: { indices.mappings.field_types.0.indexed_vector_dim_min: 100 }
- match: { indices.mappings.field_types.0.indexed_vector_dim_max: 1024 }
- match: { indices.mappings.field_types.0.vector_index_type_count.hnsw: 1 }
- match: { indices.mappings.field_types.0.vector_index_type_count.int8_hnsw: 2 }
- match: { indices.mappings.field_types.0.vector_index_type_count.int8_hnsw: 1 }
- match: { indices.mappings.field_types.0.vector_index_type_count.bbq_hnsw: 2 }
- match: { indices.mappings.field_types.0.vector_index_type_count.not_indexed: 1 }
- match: { indices.mappings.field_types.0.vector_similarity_type_count.l2_norm: 2 }
- match: { indices.mappings.field_types.0.vector_similarity_type_count.dot_product: 1 }
- match: { indices.mappings.field_types.0.vector_element_type_count.float: 3 }
- match: { indices.mappings.field_types.0.vector_element_type_count.float: 4 }
- match: { indices.mappings.field_types.0.vector_element_type_count.byte: 1 }
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_IGNORE_DYNAMIC_BEYOND_LIMIT_SETTING;
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING;
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING;
import static org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapper.BBQ_DIMS_DEFAULT_THRESHOLD;
import static org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapper.MIN_DIMS_FOR_DYNAMIC_FLOAT_MAPPING;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
Expand Down Expand Up @@ -908,6 +909,59 @@ public void testKnnSubObject() throws Exception {
client().index(
new IndexRequest("test").source("obj.vector", Randomness.get().doubles(MIN_DIMS_FOR_DYNAMIC_FLOAT_MAPPING, 0.0, 5.0).toArray())
).get();
}

public void testDenseVectorDynamicMapping() throws Exception {
assertAcked(indicesAdmin().prepareCreate("test").setMapping("""
{
"dynamic": "true"
}
""").get());

client().index(
new IndexRequest("test").source("vector_int8", Randomness.get().doubles(BBQ_DIMS_DEFAULT_THRESHOLD - 1, 0.0, 5.0).toArray())
).get();
client().index(
new IndexRequest("test").source("vector_bbq", Randomness.get().doubles(BBQ_DIMS_DEFAULT_THRESHOLD, 0.0, 5.0).toArray())
).get();
Map<String, Object> mappings = indicesAdmin().prepareGetMappings(TEST_REQUEST_TIMEOUT, "test")
.get()
.mappings()
.get("test")
.sourceAsMap();
assertTrue(new WriteField("properties.vector_int8", () -> mappings).exists());
assertTrue(new WriteField("properties.vector_int8.index_options.type", () -> mappings).get(null).toString().equals("int8_hnsw"));
assertTrue(new WriteField("properties.vector_bbq", () -> mappings).exists());
assertTrue(new WriteField("properties.vector_bbq.index_options.type", () -> mappings).get(null).toString().equals("bbq_hnsw"));
}

public void testBBQDynamicMappingWhenFirstIngestingDoc() throws Exception {
assertAcked(indicesAdmin().prepareCreate("test").setMapping("""
{
"properties": {
"vector": {
"type": "dense_vector"
}
}
}
""").get());

Map<String, Object> mappings = indicesAdmin().prepareGetMappings(TEST_REQUEST_TIMEOUT, "test")
.get()
.mappings()
.get("test")
.sourceAsMap();
assertTrue(new WriteField("properties.vector", () -> mappings).exists());
assertFalse(new WriteField("properties.vector.index_options.type", () -> mappings).exists());

client().index(new IndexRequest("test").source("vector", Randomness.get().doubles(BBQ_DIMS_DEFAULT_THRESHOLD, 0.0, 5.0).toArray()))
.get();
Map<String, Object> updatedMappings = indicesAdmin().prepareGetMappings(TEST_REQUEST_TIMEOUT, "test")
.get()
.mappings()
.get("test")
.sourceAsMap();
assertTrue(new WriteField("properties.vector", () -> updatedMappings).exists());
assertTrue(new WriteField("properties.vector.index_options.type", () -> updatedMappings).get(null).toString().equals("bbq_hnsw"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ private static Version parseUnchecked(String version) {
public static final IndexVersion MAPPER_TEXT_MATCH_ONLY_MULTI_FIELDS_DEFAULT_NOT_STORED = def(9_029_0_00, Version.LUCENE_10_2_1);
public static final IndexVersion UPGRADE_TO_LUCENE_10_2_2 = def(9_030_0_00, Version.LUCENE_10_2_2);
public static final IndexVersion SPARSE_VECTOR_PRUNING_INDEX_OPTIONS_SUPPORT = def(9_031_0_00, Version.LUCENE_10_2_2);
public static final IndexVersion DEFAULT_DENSE_VECTOR_TO_BBQ_HNSW = def(9_032_0_00, Version.LUCENE_10_2_2);

/*
* STOP! READ THIS FIRST! No, really,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,7 @@ private static void postProcessDynamicArrayMapping(DocumentParserContext context
fieldName,
context.indexSettings().getIndexVersionCreated()
);
builder.dimensions(mappers.size());
DenseVectorFieldMapper denseVectorFieldMapper = builder.build(builderContext);
context.updateDynamicMappers(fullFieldName, List.of(denseVectorFieldMapper));
}
Expand Down
Loading
Loading