Skip to content

Commit 825b39a

Browse files
committed
Mark bbq indices as GA and add rolling upgrade integration tests
1 parent 8c3d9e1 commit 825b39a

File tree

2 files changed

+183
-4
lines changed

2 files changed

+183
-4
lines changed

docs/reference/mapping/types/dense-vector.asciidoc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ The three following quantization strategies are supported:
118118

119119
* `int8` - Quantizes each dimension of the vector to 1-byte integers. This reduces the memory footprint by 75% (or 4x) at the cost of some accuracy.
120120
* `int4` - Quantizes each dimension of the vector to half-byte integers. This reduces the memory footprint by 87% (or 8x) at the cost of accuracy.
121-
* `bbq` - experimental:[] Better binary quantization which reduces each dimension to a single bit precision. This reduces the memory footprint by 96% (or 32x) at a larger cost of accuracy. Generally, oversampling during query time and reranking can help mitigate the accuracy loss.
121+
* `bbq` - Better binary quantization which reduces each dimension to a single bit precision. This reduces the memory footprint by 96% (or 32x) at a larger cost of accuracy. Generally, oversampling during query time and reranking can help mitigate the accuracy loss.
122122

123123

124124
When using a quantized format, you may want to oversample and rescore the results to improve accuracy. See <<dense-vector-knn-search-rescoring, oversampling and rescoring>> for more information.
@@ -133,7 +133,7 @@ This means disk usage will increase by ~25% for `int8`, ~12.5% for `int4`, and ~
133133

134134
NOTE: `int4` quantization requires an even number of vector dimensions.
135135

136-
NOTE: experimental:[] `bbq` quantization only supports vector dimensions that are greater than 64.
136+
NOTE: `bbq` quantization only supports vector dimensions that are greater than 64.
137137

138138
Here is an example of how to create a byte-quantized index:
139139

@@ -325,15 +325,15 @@ by 4x at the cost of some accuracy. See <<dense-vector-quantization, Automatical
325325
* `int4_hnsw` - This utilizes the https://arxiv.org/abs/1603.09320[HNSW algorithm] in addition to automatically scalar
326326
quantization for scalable approximate kNN search with `element_type` of `float`. This can reduce the memory footprint
327327
by 8x at the cost of some accuracy. See <<dense-vector-quantization, Automatically quantize vectors for kNN search>>.
328-
* experimental:[] `bbq_hnsw` - This utilizes the https://arxiv.org/abs/1603.09320[HNSW algorithm] in addition to automatically binary
328+
* `bbq_hnsw` - This utilizes the https://arxiv.org/abs/1603.09320[HNSW algorithm] in addition to automatically binary
329329
quantization for scalable approximate kNN search with `element_type` of `float`. This can reduce the memory footprint
330330
by 32x at the cost of accuracy. See <<dense-vector-quantization, Automatically quantize vectors for kNN search>>.
331331
* `flat` - This utilizes a brute-force search algorithm for exact kNN search. This supports all `element_type` values.
332332
* `int8_flat` - This utilizes a brute-force search algorithm in addition to automatically scalar quantization. Only supports
333333
`element_type` of `float`.
334334
* `int4_flat` - This utilizes a brute-force search algorithm in addition to automatically half-byte scalar quantization. Only supports
335335
`element_type` of `float`.
336-
* experimental:[] `bbq_flat` - This utilizes a brute-force search algorithm in addition to automatically binary quantization. Only supports
336+
* `bbq_flat` - This utilizes a brute-force search algorithm in addition to automatically binary quantization. Only supports
337337
`element_type` of `float`.
338338
--
339339
`m`:::

qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/VectorSearchIT.java

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,14 @@ public VectorSearchIT(@Name("upgradedNodes") int upgradedNodes) {
3333
private static final String SCRIPT_BYTE_INDEX_NAME = "script_byte_vector_index";
3434
private static final String BYTE_INDEX_NAME = "byte_vector_index";
3535
private static final String QUANTIZED_INDEX_NAME = "quantized_vector_index";
36+
private static final String BBQ_INDEX_NAME = "bbq_vector_index";
3637
private static final String FLAT_QUANTIZED_INDEX_NAME = "flat_quantized_vector_index";
38+
private static final String FLAT_BBQ_INDEX_NAME = "flat_bbq_vector_index";
3739
private static final String FLOAT_VECTOR_SEARCH_VERSION = "8.4.0";
3840
private static final String BYTE_VECTOR_SEARCH_VERSION = "8.6.0";
3941
private static final String QUANTIZED_VECTOR_SEARCH_VERSION = "8.12.1";
4042
private static final String FLAT_QUANTIZED_VECTOR_SEARCH_VERSION = "8.13.0";
43+
private static final String BBQ_VECTOR_SEARCH_VERSION = "8.18.0";
4144

4245
public void testScriptByteVectorSearch() throws Exception {
4346
assumeTrue("byte vector search is not supported on this version", getOldClusterTestVersion().onOrAfter(BYTE_VECTOR_SEARCH_VERSION));
@@ -429,6 +432,182 @@ public void testFlatQuantizedVectorSearch() throws Exception {
429432
assertThat((double) hits.get(0).get("_score"), closeTo(0.9934857, 0.005));
430433
}
431434

435+
public void testBBQVectorSearch() throws Exception {
436+
assumeTrue(
437+
"Quantized vector search is not supported on this version",
438+
getOldClusterTestVersion().onOrAfter(BBQ_VECTOR_SEARCH_VERSION)
439+
);
440+
if (isOldCluster()) {
441+
String mapping = """
442+
{
443+
"properties": {
444+
"vector": {
445+
"type": "dense_vector",
446+
"dims": 64,
447+
"index": true,
448+
"similarity": "cosine",
449+
"index_options": {
450+
"type": "bbq_hnsw",
451+
"ef_construction": 100,
452+
"m": 16
453+
}
454+
}
455+
}
456+
}
457+
""";
458+
// create index and index 10 random floating point vectors
459+
createIndex(BBQ_INDEX_NAME, Settings.EMPTY, mapping);
460+
index64DimVectors(BBQ_INDEX_NAME);
461+
// force merge the index
462+
client().performRequest(new Request("POST", "/" + BBQ_INDEX_NAME + "/_forcemerge?max_num_segments=1"));
463+
}
464+
Request searchRequest = new Request("POST", "/" + BBQ_INDEX_NAME + "/_search");
465+
searchRequest.setJsonEntity("""
466+
{
467+
"query": {
468+
"script_score": {
469+
"query": {
470+
"exists": {
471+
"field": "vector"
472+
}
473+
},
474+
"script": {
475+
"source": "cosineSimilarity(params.query, 'vector') + 1.0",
476+
"params": {
477+
"query": [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
478+
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6]
479+
}
480+
}
481+
}
482+
}
483+
}
484+
""");
485+
Map<String, Object> response = search(searchRequest);
486+
assertThat(extractValue(response, "hits.total.value"), equalTo(7));
487+
List<Map<String, Object>> hits = extractValue(response, "hits.hits");
488+
assertThat(hits.get(0).get("_id"), equalTo("0"));
489+
assertThat((double) hits.get(0).get("_score"), closeTo(1.9869276, 0.0001));
490+
491+
// search with knn
492+
searchRequest = new Request("POST", "/" + BBQ_INDEX_NAME + "/_search");
493+
searchRequest.setJsonEntity("""
494+
{
495+
"knn": {
496+
"field": "vector",
497+
"query_vector": [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
498+
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
499+
"k": 2,
500+
"num_candidates": 5
501+
}
502+
}
503+
""");
504+
response = search(searchRequest);
505+
assertThat(extractValue(response, "hits.total.value"), equalTo(2));
506+
hits = extractValue(response, "hits.hits");
507+
assertThat(hits.get(0).get("_id"), equalTo("0"));
508+
assertThat((double) hits.get(0).get("_score"), closeTo(0.9934857, 0.005));
509+
}
510+
511+
public void testFlatBBQVectorSearch() throws Exception {
512+
assumeTrue(
513+
"Quantized vector search is not supported on this version",
514+
getOldClusterTestVersion().onOrAfter(BBQ_VECTOR_SEARCH_VERSION)
515+
);
516+
if (isOldCluster()) {
517+
String mapping = """
518+
{
519+
"properties": {
520+
"vector": {
521+
"type": "dense_vector",
522+
"dims": 64,
523+
"index": true,
524+
"similarity": "cosine",
525+
"index_options": {
526+
"type": "bbq_flat"
527+
}
528+
}
529+
}
530+
}
531+
""";
532+
// create index and index 10 random floating point vectors
533+
createIndex(FLAT_BBQ_INDEX_NAME, Settings.EMPTY, mapping);
534+
index64DimVectors(FLAT_BBQ_INDEX_NAME);
535+
// force merge the index
536+
client().performRequest(new Request("POST", "/" + FLAT_BBQ_INDEX_NAME + "/_forcemerge?max_num_segments=1"));
537+
}
538+
Request searchRequest = new Request("POST", "/" + FLAT_BBQ_INDEX_NAME + "/_search");
539+
searchRequest.setJsonEntity("""
540+
{
541+
"query": {
542+
"script_score": {
543+
"query": {
544+
"exists": {
545+
"field": "vector"
546+
}
547+
},
548+
"script": {
549+
"source": "cosineSimilarity(params.query, 'vector') + 1.0",
550+
"params": {
551+
"query": [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
552+
5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6]
553+
}
554+
}
555+
}
556+
}
557+
}
558+
""");
559+
Map<String, Object> response = search(searchRequest);
560+
assertThat(extractValue(response, "hits.total.value"), equalTo(7));
561+
List<Map<String, Object>> hits = extractValue(response, "hits.hits");
562+
assertThat(hits.get(0).get("_id"), equalTo("0"));
563+
assertThat((double) hits.get(0).get("_score"), closeTo(1.9869276, 0.0001));
564+
565+
// search with knn
566+
searchRequest = new Request("POST", "/" + FLAT_BBQ_INDEX_NAME + "/_search");
567+
searchRequest.setJsonEntity("""
568+
{
569+
"knn": {
570+
"field": "vector",
571+
"query_vector": [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
572+
5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
573+
"k": 2,
574+
"num_candidates": 5
575+
}
576+
}
577+
""");
578+
response = search(searchRequest);
579+
assertThat(extractValue(response, "hits.total.value"), equalTo(2));
580+
hits = extractValue(response, "hits.hits");
581+
assertThat(hits.get(0).get("_id"), equalTo("0"));
582+
assertThat((double) hits.get(0).get("_score"), closeTo(0.9934857, 0.005));
583+
}
584+
585+
private void index64DimVectors(String indexName) throws Exception {
586+
String[] vectors = new String[] {
587+
"{\"vector\":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, "
588+
+ "1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}",
589+
"{\"vector\":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, "
590+
+ "1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]}",
591+
"{\"vector\":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, "
592+
+ "1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]}",
593+
"{\"vector\":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, "
594+
+ "2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}",
595+
"{\"vector\":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, "
596+
+ "3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}",
597+
"{\"vector\":[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, "
598+
+ "1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}",
599+
"{\"vector\":[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, "
600+
+ "1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}",
601+
"{}" };
602+
for (int i = 0; i < vectors.length; i++) {
603+
Request indexRequest = new Request("PUT", "/" + indexName + "/_doc/" + i);
604+
indexRequest.setJsonEntity(vectors[i]);
605+
assertOK(client().performRequest(indexRequest));
606+
}
607+
// always refresh to ensure the data is visible
608+
refresh(indexName);
609+
}
610+
432611
private void indexVectors(String indexName) throws Exception {
433612
String[] vectors = new String[] {
434613
"{\"vector\":[1, 1, 1]}",

0 commit comments

Comments
 (0)