diff --git a/docs/reference/elasticsearch/mapping-reference/bbq.md b/docs/reference/elasticsearch/mapping-reference/bbq.md index 923b6310cd9d1..50fdc2411d587 100644 --- a/docs/reference/elasticsearch/mapping-reference/bbq.md +++ b/docs/reference/elasticsearch/mapping-reference/bbq.md @@ -244,12 +244,13 @@ A lower `visit_percentage` can further reduce memory use and speed up queries, w stack: ga 9.4 ``` By default, BBQ performs asymmetric quantization: it performs 1-bit quantization for the indexed vectors and 4-bit quantization for query vectors. -For fields of type `bbq_disk` it is possible to change the level of quantization for indexed vectors by setting the `bits` parameter in `index_options` to `1` (default), `2` or `4`. +For fields of type `bbq_disk` it is possible to change the level of quantization for indexed vectors by setting the `bits` parameter in `index_options` to `1` (default), `2`, `4` or `7`. If no `oversampling_factor` is specified, setting `bits` will automatically adjust that as follows: * `bits = 1` --> `oversampling_factor = 3.0` * `bits = 2` --> `oversampling_factor = 1.5` * `bits = 4` --> `oversampling_factor = 0` (no oversampling) +* `bits = 7` --> `oversampling_factor = 0` (no oversampling) ```console PUT bbq_disk-index/_mapping diff --git a/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java index 2fae309729ced..2ed4edc0cb901 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java @@ -1836,9 +1836,9 @@ public DenseVectorIndexOptions parseIndexOptions( if (indexVersion.onOrAfter(DISK_BBQ_QUANTIZE_BITS) && experimentalFeaturesEnabled) { Object quantizeBitsNode = indexOptionsMap.remove("bits"); quantizeBits = XContentMapValues.nodeIntegerValue(quantizeBitsNode, DEFAULT_BBQ_IVF_QUANTIZE_BITS); - if ((quantizeBits == 1 || quantizeBits == 2 || quantizeBits == 4) == false) { + if ((quantizeBits == 1 || quantizeBits == 2 || quantizeBits == 4 || quantizeBits == 7) == false) { throw new IllegalArgumentException( - "'bits' must be 1, 2 or 4, got: " + quantizeBits + " for field [" + fieldName + "]" + "'bits' must be 1, 2, 4 or 7, got: " + quantizeBits + " for field [" + fieldName + "]" ); } } else { @@ -2562,7 +2562,7 @@ KnnVectorsFormat getVectorsFormat(ElementType elementType, ExecutorService mergi } if (experimentalFeaturesEnabled) { return new ESNextDiskBBQVectorsFormat( - ESNextDiskBBQVectorsFormat.QuantEncoding.fromId(bits >> 1), + ESNextDiskBBQVectorsFormat.QuantEncoding.fromBits((byte) bits), clusterSize, ES920DiskBBQVectorsFormat.DEFAULT_CENTROIDS_PER_PARENT_CLUSTER, elementType, @@ -2653,6 +2653,10 @@ public boolean isOnDiskRescore() { public boolean doPrecondition() { return doPrecondition; } + + public int getBits() { + return bits; + } } public record RescoreVector(float oversample) implements ToXContentObject { diff --git a/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapperTests.java index 3fffb9d201cad..525060da6bf58 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapperTests.java @@ -674,6 +674,25 @@ public void testIVFParsing() throws IOException { assertEquals(-1, indexOptions.getFlatIndexThreshold()); assertEquals(DYNAMIC_VISIT_RATIO, indexOptions.defaultVisitPercentage, 0.0); } + { + DocumentMapper mapperService = createMapperService(experimentalEnabled, fieldMapping(b -> { + b.field("type", "dense_vector"); + b.field("dims", 128); + b.field("index", true); + b.field("similarity", "dot_product"); + b.startObject("index_options"); + b.field("type", "bbq_disk"); + b.field("bits", 7); + b.endObject(); + })).documentMapper(); + + DenseVectorFieldMapper denseVectorFieldMapper = (DenseVectorFieldMapper) mapperService.mappers().getMapper("field"); + DenseVectorFieldMapper.BBQIVFIndexOptions indexOptions = (DenseVectorFieldMapper.BBQIVFIndexOptions) denseVectorFieldMapper + .fieldType() + .getIndexOptions(); + assertEquals(7, indexOptions.bits, 0.0F); + assertNull(indexOptions.rescoreVector); + } { DocumentMapper mapperService = createMapperService(experimentalDisabled, fieldMapping(b -> { b.field("type", "dense_vector"); diff --git a/x-pack/plugin/diskbbq/src/main/java/org/elasticsearch/xpack/diskbbq/DiskBBQPlugin.java b/x-pack/plugin/diskbbq/src/main/java/org/elasticsearch/xpack/diskbbq/DiskBBQPlugin.java index eaee1c18a348b..70e7fbf0087bd 100644 --- a/x-pack/plugin/diskbbq/src/main/java/org/elasticsearch/xpack/diskbbq/DiskBBQPlugin.java +++ b/x-pack/plugin/diskbbq/src/main/java/org/elasticsearch/xpack/diskbbq/DiskBBQPlugin.java @@ -50,7 +50,7 @@ public VectorsFormatProvider getVectorsFormatProvider() { int flatIndexThreshold = diskbbq.getFlatIndexThreshold(); if (Build.current().isSnapshot()) { return new ESNextDiskBBQVectorsFormat( - ESNextDiskBBQVectorsFormat.QuantEncoding.ONE_BIT_4BIT_QUERY, + ESNextDiskBBQVectorsFormat.QuantEncoding.fromBits((byte) diskbbq.getBits()), clusterSize, ES920DiskBBQVectorsFormat.DEFAULT_CENTROIDS_PER_PARENT_CLUSTER, elementType,