Skip to content

Commit a72883e

Browse files
authored
Default new semantic_text fields to use BBQ when models are compatible (#126629)
* Default new semantic_text fields to use BBQ when models are compatible * Update docs/changelog/126629.yaml * Gate default BBQ by IndexVersion * Cleanup from PR feedback * PR feedback * Fix test * Fix test * PR feedback * Update test to test correct options * Hack alert: Fix issue where mapper service was always being created with current index version
1 parent 0d41e9a commit a72883e

File tree

7 files changed

+218
-37
lines changed

7 files changed

+218
-37
lines changed

docs/changelog/126629.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 126629
2+
summary: Default new `semantic_text` fields to use BBQ when models are compatible
3+
area: Relevance
4+
type: enhancement
5+
issues: []

server/src/main/java/org/elasticsearch/index/IndexVersions.java

+1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ private static Version parseUnchecked(String version) {
160160
public static final IndexVersion SYNTHETIC_SOURCE_STORE_ARRAYS_NATIVELY_SCALED_FLOAT = def(9_020_0_00, Version.LUCENE_10_1_0);
161161
public static final IndexVersion USE_LUCENE101_POSTINGS_FORMAT = def(9_021_0_00, Version.LUCENE_10_1_0);
162162
public static final IndexVersion UPGRADE_TO_LUCENE_10_2_0 = def(9_022_00_0, Version.LUCENE_10_2_0);
163+
public static final IndexVersion SEMANTIC_TEXT_DEFAULTS_TO_BBQ = def(9_023_0_00, Version.LUCENE_10_2_0);
163164
/*
164165
* STOP! READ THIS FIRST! No, really,
165166
* ____ _____ ___ ____ _ ____ _____ _ ____ _____ _ _ ___ ____ _____ ___ ____ ____ _____ _

server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java

+51-20
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@ public Builder elementType(ElementType elementType) {
290290
return this;
291291
}
292292

293+
public Builder indexOptions(IndexOptions indexOptions) {
294+
this.indexOptions.setValue(indexOptions);
295+
return this;
296+
}
297+
293298
@Override
294299
public DenseVectorFieldMapper build(MapperBuilderContext context) {
295300
// Validate again here because the dimensions or element type could have been set programmatically,
@@ -1221,7 +1226,7 @@ public final String toString() {
12211226
public abstract VectorSimilarityFunction vectorSimilarityFunction(IndexVersion indexVersion, ElementType elementType);
12221227
}
12231228

1224-
abstract static class IndexOptions implements ToXContent {
1229+
public abstract static class IndexOptions implements ToXContent {
12251230
final VectorIndexType type;
12261231

12271232
IndexOptions(VectorIndexType type) {
@@ -1230,21 +1235,36 @@ abstract static class IndexOptions implements ToXContent {
12301235

12311236
abstract KnnVectorsFormat getVectorsFormat(ElementType elementType);
12321237

1233-
final void validateElementType(ElementType elementType) {
1234-
if (type.supportsElementType(elementType) == false) {
1238+
public boolean validate(ElementType elementType, int dim, boolean throwOnError) {
1239+
return validateElementType(elementType, throwOnError) && validateDimension(dim, throwOnError);
1240+
}
1241+
1242+
public boolean validateElementType(ElementType elementType) {
1243+
return validateElementType(elementType, true);
1244+
}
1245+
1246+
final boolean validateElementType(ElementType elementType, boolean throwOnError) {
1247+
boolean validElementType = type.supportsElementType(elementType);
1248+
if (throwOnError && validElementType == false) {
12351249
throw new IllegalArgumentException(
12361250
"[element_type] cannot be [" + elementType.toString() + "] when using index type [" + type + "]"
12371251
);
12381252
}
1253+
return validElementType;
12391254
}
12401255

12411256
abstract boolean updatableTo(IndexOptions update);
12421257

1243-
public void validateDimension(int dim) {
1244-
if (type.supportsDimension(dim)) {
1245-
return;
1258+
public boolean validateDimension(int dim) {
1259+
return validateDimension(dim, true);
1260+
}
1261+
1262+
public boolean validateDimension(int dim, boolean throwOnError) {
1263+
boolean supportsDimension = type.supportsDimension(dim);
1264+
if (throwOnError && supportsDimension == false) {
1265+
throw new IllegalArgumentException(type.name + " only supports even dimensions; provided=" + dim);
12461266
}
1247-
throw new IllegalArgumentException(type.name + " only supports even dimensions; provided=" + dim);
1267+
return supportsDimension;
12481268
}
12491269

12501270
abstract boolean doEquals(IndexOptions other);
@@ -1747,12 +1767,12 @@ boolean updatableTo(IndexOptions update) {
17471767

17481768
}
17491769

1750-
static class Int8HnswIndexOptions extends QuantizedIndexOptions {
1770+
public static class Int8HnswIndexOptions extends QuantizedIndexOptions {
17511771
private final int m;
17521772
private final int efConstruction;
17531773
private final Float confidenceInterval;
17541774

1755-
Int8HnswIndexOptions(int m, int efConstruction, Float confidenceInterval, RescoreVector rescoreVector) {
1775+
public Int8HnswIndexOptions(int m, int efConstruction, Float confidenceInterval, RescoreVector rescoreVector) {
17561776
super(VectorIndexType.INT8_HNSW, rescoreVector);
17571777
this.m = m;
17581778
this.efConstruction = efConstruction;
@@ -1890,11 +1910,11 @@ public String toString() {
18901910
}
18911911
}
18921912

1893-
static class BBQHnswIndexOptions extends QuantizedIndexOptions {
1913+
public static class BBQHnswIndexOptions extends QuantizedIndexOptions {
18941914
private final int m;
18951915
private final int efConstruction;
18961916

1897-
BBQHnswIndexOptions(int m, int efConstruction, RescoreVector rescoreVector) {
1917+
public BBQHnswIndexOptions(int m, int efConstruction, RescoreVector rescoreVector) {
18981918
super(VectorIndexType.BBQ_HNSW, rescoreVector);
18991919
this.m = m;
19001920
this.efConstruction = efConstruction;
@@ -1936,11 +1956,14 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
19361956
}
19371957

19381958
@Override
1939-
public void validateDimension(int dim) {
1940-
if (type.supportsDimension(dim)) {
1941-
return;
1959+
public boolean validateDimension(int dim, boolean throwOnError) {
1960+
boolean supportsDimension = type.supportsDimension(dim);
1961+
if (throwOnError && supportsDimension == false) {
1962+
throw new IllegalArgumentException(
1963+
type.name + " does not support dimensions fewer than " + BBQ_MIN_DIMS + "; provided=" + dim
1964+
);
19421965
}
1943-
throw new IllegalArgumentException(type.name + " does not support dimensions fewer than " + BBQ_MIN_DIMS + "; provided=" + dim);
1966+
return supportsDimension;
19441967
}
19451968
}
19461969

@@ -1984,15 +2007,19 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
19842007
}
19852008

19862009
@Override
1987-
public void validateDimension(int dim) {
1988-
if (type.supportsDimension(dim)) {
1989-
return;
2010+
public boolean validateDimension(int dim, boolean throwOnError) {
2011+
boolean supportsDimension = type.supportsDimension(dim);
2012+
if (throwOnError && supportsDimension == false) {
2013+
throw new IllegalArgumentException(
2014+
type.name + " does not support dimensions fewer than " + BBQ_MIN_DIMS + "; provided=" + dim
2015+
);
19902016
}
1991-
throw new IllegalArgumentException(type.name + " does not support dimensions fewer than " + BBQ_MIN_DIMS + "; provided=" + dim);
2017+
return supportsDimension;
19922018
}
2019+
19932020
}
19942021

1995-
record RescoreVector(float oversample) implements ToXContentObject {
2022+
public record RescoreVector(float oversample) implements ToXContentObject {
19962023
static final String NAME = "rescore_vector";
19972024
static final String OVERSAMPLE = "oversample";
19982025

@@ -2311,6 +2338,10 @@ int getVectorDimensions() {
23112338
ElementType getElementType() {
23122339
return elementType;
23132340
}
2341+
2342+
public IndexOptions getIndexOptions() {
2343+
return indexOptions;
2344+
}
23142345
}
23152346

23162347
private final IndexOptions indexOptions;

test/framework/src/main/java/org/elasticsearch/index/mapper/MapperServiceTestCase.java

+7
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@ protected final MapperService createMapperService(Settings settings, String mapp
207207
return mapperService;
208208
}
209209

210+
protected final MapperService createMapperService(IndexVersion indexVersion, Settings settings, XContentBuilder mappings)
211+
throws IOException {
212+
MapperService mapperService = createMapperService(indexVersion, settings, () -> true, mappings);
213+
merge(mapperService, mappings);
214+
return mapperService;
215+
}
216+
210217
protected final MapperService createMapperService(IndexVersion version, XContentBuilder mapping) throws IOException {
211218
return createMapperService(version, getIndexSettings(), () -> true, mapping);
212219
}

x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapper.java

+22
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import org.apache.logging.log4j.LogManager;
1111
import org.apache.logging.log4j.Logger;
12+
import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat;
1213
import org.apache.lucene.index.FieldInfos;
1314
import org.apache.lucene.index.LeafReaderContext;
1415
import org.apache.lucene.search.DocIdSetIterator;
@@ -95,6 +96,7 @@
9596
import java.util.function.Function;
9697
import java.util.function.Supplier;
9798

99+
import static org.elasticsearch.index.IndexVersions.SEMANTIC_TEXT_DEFAULTS_TO_BBQ;
98100
import static org.elasticsearch.inference.TaskType.SPARSE_EMBEDDING;
99101
import static org.elasticsearch.inference.TaskType.TEXT_EMBEDDING;
100102
import static org.elasticsearch.search.SearchService.DEFAULT_SIZE;
@@ -133,6 +135,8 @@ public class SemanticTextFieldMapper extends FieldMapper implements InferenceFie
133135
public static final String CONTENT_TYPE = "semantic_text";
134136
public static final String DEFAULT_ELSER_2_INFERENCE_ID = DEFAULT_ELSER_ID;
135137

138+
public static final float DEFAULT_RESCORE_OVERSAMPLE = 3.0f;
139+
136140
public static final TypeParser parser(Supplier<ModelRegistry> modelRegistry) {
137141
return new TypeParser(
138142
(n, c) -> new Builder(n, c::bitSetProducer, c.getIndexSettings(), modelRegistry.get()),
@@ -1054,12 +1058,30 @@ private static Mapper.Builder createEmbeddingsField(
10541058
denseVectorMapperBuilder.dimensions(modelSettings.dimensions());
10551059
denseVectorMapperBuilder.elementType(modelSettings.elementType());
10561060

1061+
DenseVectorFieldMapper.IndexOptions defaultIndexOptions = null;
1062+
if (indexVersionCreated.onOrAfter(SEMANTIC_TEXT_DEFAULTS_TO_BBQ)) {
1063+
defaultIndexOptions = defaultSemanticDenseIndexOptions();
1064+
}
1065+
if (defaultIndexOptions != null
1066+
&& defaultIndexOptions.validate(modelSettings.elementType(), modelSettings.dimensions(), false)) {
1067+
denseVectorMapperBuilder.indexOptions(defaultIndexOptions);
1068+
}
1069+
10571070
yield denseVectorMapperBuilder;
10581071
}
10591072
default -> throw new IllegalArgumentException("Invalid task_type in model_settings [" + modelSettings.taskType().name() + "]");
10601073
};
10611074
}
10621075

1076+
static DenseVectorFieldMapper.IndexOptions defaultSemanticDenseIndexOptions() {
1077+
// As embedding models for text perform better with BBQ, we aggressively default semantic_text fields to use optimized index
1078+
// options outside of dense_vector defaults
1079+
int m = Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN;
1080+
int efConstruction = Lucene99HnswVectorsFormat.DEFAULT_BEAM_WIDTH;
1081+
DenseVectorFieldMapper.RescoreVector rescoreVector = new DenseVectorFieldMapper.RescoreVector(DEFAULT_RESCORE_OVERSAMPLE);
1082+
return new DenseVectorFieldMapper.BBQHnswIndexOptions(m, efConstruction, rescoreVector);
1083+
}
1084+
10631085
private static boolean canMergeModelSettings(MinimalServiceSettings previous, MinimalServiceSettings current, Conflicts conflicts) {
10641086
if (previous != null && current != null && previous.canMergeWith(current)) {
10651087
return true;

x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticInferenceMetadataFieldsMapperTests.java

+11-7
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ public void testIsEnabled() {
3737
assertFalse(InferenceMetadataFieldsMapper.isEnabled(settings));
3838

3939
settings = Settings.builder()
40-
.put(IndexMetadata.SETTING_INDEX_VERSION_CREATED.getKey(), getRandomCompatibleIndexVersion(true))
40+
.put(
41+
IndexMetadata.SETTING_INDEX_VERSION_CREATED.getKey(),
42+
getRandomCompatibleIndexVersion(true, IndexVersionUtils.getPreviousVersion(IndexVersions.INFERENCE_METADATA_FIELDS))
43+
)
4144
.put(InferenceMetadataFieldsMapper.USE_LEGACY_SEMANTIC_TEXT_FORMAT.getKey(), false)
4245
.build();
4346
assertFalse(InferenceMetadataFieldsMapper.isEnabled(settings));
@@ -114,18 +117,18 @@ public MappedFieldType getMappedFieldType() {
114117
}
115118

116119
static IndexVersion getRandomCompatibleIndexVersion(boolean useLegacyFormat) {
120+
return getRandomCompatibleIndexVersion(useLegacyFormat, IndexVersion.current());
121+
}
122+
123+
static IndexVersion getRandomCompatibleIndexVersion(boolean useLegacyFormat, IndexVersion maxVersion) {
117124
if (useLegacyFormat) {
118125
if (randomBoolean()) {
119-
return IndexVersionUtils.randomVersionBetween(
120-
random(),
121-
IndexVersions.UPGRADE_TO_LUCENE_10_0_0,
122-
IndexVersionUtils.getPreviousVersion(IndexVersions.INFERENCE_METADATA_FIELDS)
123-
);
126+
return IndexVersionUtils.randomVersionBetween(random(), IndexVersions.UPGRADE_TO_LUCENE_10_0_0, maxVersion);
124127
}
125128
return IndexVersionUtils.randomPreviousCompatibleVersion(random(), IndexVersions.INFERENCE_METADATA_FIELDS_BACKPORT);
126129
} else {
127130
if (randomBoolean()) {
128-
return IndexVersionUtils.randomVersionBetween(random(), IndexVersions.INFERENCE_METADATA_FIELDS, IndexVersion.current());
131+
return IndexVersionUtils.randomVersionBetween(random(), IndexVersions.INFERENCE_METADATA_FIELDS, maxVersion);
129132
}
130133
return IndexVersionUtils.randomVersionBetween(
131134
random(),
@@ -134,4 +137,5 @@ static IndexVersion getRandomCompatibleIndexVersion(boolean useLegacyFormat) {
134137
);
135138
}
136139
}
140+
137141
}

0 commit comments

Comments
 (0)