Skip to content

Default new semantic_text fields to use BBQ when models are compatible #126629

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 17, 2025
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
5 changes: 5 additions & 0 deletions docs/changelog/126629.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 126629
summary: Default new `semantic_text` fields to use BBQ when models are compatible
area: Relevance
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ private static Version parseUnchecked(String version) {
public static final IndexVersion SYNTHETIC_SOURCE_STORE_ARRAYS_NATIVELY_SCALED_FLOAT = def(9_020_0_00, Version.LUCENE_10_1_0);
public static final IndexVersion USE_LUCENE101_POSTINGS_FORMAT = def(9_021_0_00, Version.LUCENE_10_1_0);
public static final IndexVersion UPGRADE_TO_LUCENE_10_2_0 = def(9_022_00_0, Version.LUCENE_10_2_0);
public static final IndexVersion SEMANTIC_TEXT_DEFAULTS_TO_BBQ = def(9_023_0_00, Version.LUCENE_10_2_0);
/*
* STOP! READ THIS FIRST! No, really,
* ____ _____ ___ ____ _ ____ _____ _ ____ _____ _ _ ___ ____ _____ ___ ____ ____ _____ _
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ public Builder elementType(ElementType elementType) {
return this;
}

public Builder indexOptions(IndexOptions indexOptions) {
this.indexOptions.setValue(indexOptions);
return this;
}

@Override
public DenseVectorFieldMapper build(MapperBuilderContext context) {
// Validate again here because the dimensions or element type could have been set programmatically,
Expand Down Expand Up @@ -1221,7 +1226,7 @@ public final String toString() {
public abstract VectorSimilarityFunction vectorSimilarityFunction(IndexVersion indexVersion, ElementType elementType);
}

abstract static class IndexOptions implements ToXContent {
public abstract static class IndexOptions implements ToXContent {
final VectorIndexType type;

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

abstract KnnVectorsFormat getVectorsFormat(ElementType elementType);

final void validateElementType(ElementType elementType) {
if (type.supportsElementType(elementType) == false) {
public boolean validate(ElementType elementType, int dim, boolean throwOnError) {
return validateElementType(elementType, throwOnError) && validateDimension(dim, throwOnError);
}

public boolean validateElementType(ElementType elementType) {
return validateElementType(elementType, true);
}

final boolean validateElementType(ElementType elementType, boolean throwOnError) {
boolean validElementType = type.supportsElementType(elementType);
if (throwOnError && validElementType == false) {
throw new IllegalArgumentException(
"[element_type] cannot be [" + elementType.toString() + "] when using index type [" + type + "]"
);
}
return validElementType;
}

abstract boolean updatableTo(IndexOptions update);

public void validateDimension(int dim) {
if (type.supportsDimension(dim)) {
return;
public boolean validateDimension(int dim) {
return validateDimension(dim, true);
}

public boolean validateDimension(int dim, boolean throwOnError) {
boolean supportsDimension = type.supportsDimension(dim);
if (throwOnError && supportsDimension == false) {
throw new IllegalArgumentException(type.name + " only supports even dimensions; provided=" + dim);
}
throw new IllegalArgumentException(type.name + " only supports even dimensions; provided=" + dim);
return supportsDimension;
}

abstract boolean doEquals(IndexOptions other);
Expand Down Expand Up @@ -1747,12 +1767,12 @@ boolean updatableTo(IndexOptions update) {

}

static class Int8HnswIndexOptions extends QuantizedIndexOptions {
public static class Int8HnswIndexOptions extends QuantizedIndexOptions {
private final int m;
private final int efConstruction;
private final Float confidenceInterval;

Int8HnswIndexOptions(int m, int efConstruction, Float confidenceInterval, RescoreVector rescoreVector) {
public Int8HnswIndexOptions(int m, int efConstruction, Float confidenceInterval, RescoreVector rescoreVector) {
super(VectorIndexType.INT8_HNSW, rescoreVector);
this.m = m;
this.efConstruction = efConstruction;
Expand Down Expand Up @@ -1890,11 +1910,11 @@ public String toString() {
}
}

static class BBQHnswIndexOptions extends QuantizedIndexOptions {
public static class BBQHnswIndexOptions extends QuantizedIndexOptions {
private final int m;
private final int efConstruction;

BBQHnswIndexOptions(int m, int efConstruction, RescoreVector rescoreVector) {
public BBQHnswIndexOptions(int m, int efConstruction, RescoreVector rescoreVector) {
super(VectorIndexType.BBQ_HNSW, rescoreVector);
this.m = m;
this.efConstruction = efConstruction;
Expand Down Expand Up @@ -1936,11 +1956,14 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
}

@Override
public void validateDimension(int dim) {
if (type.supportsDimension(dim)) {
return;
public boolean validateDimension(int dim, boolean throwOnError) {
boolean supportsDimension = type.supportsDimension(dim);
if (throwOnError && supportsDimension == false) {
throw new IllegalArgumentException(
type.name + " does not support dimensions fewer than " + BBQ_MIN_DIMS + "; provided=" + dim
);
}
throw new IllegalArgumentException(type.name + " does not support dimensions fewer than " + BBQ_MIN_DIMS + "; provided=" + dim);
return supportsDimension;
}
}

Expand Down Expand Up @@ -1984,15 +2007,19 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
}

@Override
public void validateDimension(int dim) {
if (type.supportsDimension(dim)) {
return;
public boolean validateDimension(int dim, boolean throwOnError) {
boolean supportsDimension = type.supportsDimension(dim);
if (throwOnError && supportsDimension == false) {
throw new IllegalArgumentException(
type.name + " does not support dimensions fewer than " + BBQ_MIN_DIMS + "; provided=" + dim
);
}
throw new IllegalArgumentException(type.name + " does not support dimensions fewer than " + BBQ_MIN_DIMS + "; provided=" + dim);
return supportsDimension;
}

}

record RescoreVector(float oversample) implements ToXContentObject {
public record RescoreVector(float oversample) implements ToXContentObject {
static final String NAME = "rescore_vector";
static final String OVERSAMPLE = "oversample";

Expand Down Expand Up @@ -2311,6 +2338,10 @@ int getVectorDimensions() {
ElementType getElementType() {
return elementType;
}

public IndexOptions getIndexOptions() {
return indexOptions;
}
}

private final IndexOptions indexOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,13 @@ protected final MapperService createMapperService(Settings settings, String mapp
return mapperService;
}

protected final MapperService createMapperService(IndexVersion indexVersion, Settings settings, XContentBuilder mappings)
throws IOException {
MapperService mapperService = createMapperService(indexVersion, settings, () -> true, mappings);
merge(mapperService, mappings);
return mapperService;
}

protected final MapperService createMapperService(IndexVersion version, XContentBuilder mapping) throws IOException {
return createMapperService(version, getIndexSettings(), () -> true, mapping);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat;
import org.apache.lucene.index.FieldInfos;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.DocIdSetIterator;
Expand Down Expand Up @@ -95,6 +96,7 @@
import java.util.function.Function;
import java.util.function.Supplier;

import static org.elasticsearch.index.IndexVersions.SEMANTIC_TEXT_DEFAULTS_TO_BBQ;
import static org.elasticsearch.inference.TaskType.SPARSE_EMBEDDING;
import static org.elasticsearch.inference.TaskType.TEXT_EMBEDDING;
import static org.elasticsearch.search.SearchService.DEFAULT_SIZE;
Expand Down Expand Up @@ -133,6 +135,8 @@ public class SemanticTextFieldMapper extends FieldMapper implements InferenceFie
public static final String CONTENT_TYPE = "semantic_text";
public static final String DEFAULT_ELSER_2_INFERENCE_ID = DEFAULT_ELSER_ID;

public static final float DEFAULT_RESCORE_OVERSAMPLE = 3.0f;

public static final TypeParser parser(Supplier<ModelRegistry> modelRegistry) {
return new TypeParser(
(n, c) -> new Builder(n, c::bitSetProducer, c.getIndexSettings(), modelRegistry.get()),
Expand Down Expand Up @@ -1054,12 +1058,30 @@ private static Mapper.Builder createEmbeddingsField(
denseVectorMapperBuilder.dimensions(modelSettings.dimensions());
denseVectorMapperBuilder.elementType(modelSettings.elementType());

DenseVectorFieldMapper.IndexOptions defaultIndexOptions = null;
if (indexVersionCreated.onOrAfter(SEMANTIC_TEXT_DEFAULTS_TO_BBQ)) {
defaultIndexOptions = defaultSemanticDenseIndexOptions();
}
if (defaultIndexOptions != null
&& defaultIndexOptions.validate(modelSettings.elementType(), modelSettings.dimensions(), false)) {
denseVectorMapperBuilder.indexOptions(defaultIndexOptions);
}

yield denseVectorMapperBuilder;
}
default -> throw new IllegalArgumentException("Invalid task_type in model_settings [" + modelSettings.taskType().name() + "]");
};
}

static DenseVectorFieldMapper.IndexOptions defaultSemanticDenseIndexOptions() {
// As embedding models for text perform better with BBQ, we aggressively default semantic_text fields to use optimized index
// options outside of dense_vector defaults
int m = Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN;
int efConstruction = Lucene99HnswVectorsFormat.DEFAULT_BEAM_WIDTH;
DenseVectorFieldMapper.RescoreVector rescoreVector = new DenseVectorFieldMapper.RescoreVector(DEFAULT_RESCORE_OVERSAMPLE);
return new DenseVectorFieldMapper.BBQHnswIndexOptions(m, efConstruction, rescoreVector);
}

private static boolean canMergeModelSettings(MinimalServiceSettings previous, MinimalServiceSettings current, Conflicts conflicts) {
if (previous != null && current != null && previous.canMergeWith(current)) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ public void testIsEnabled() {
assertFalse(InferenceMetadataFieldsMapper.isEnabled(settings));

settings = Settings.builder()
.put(IndexMetadata.SETTING_INDEX_VERSION_CREATED.getKey(), getRandomCompatibleIndexVersion(true))
.put(
IndexMetadata.SETTING_INDEX_VERSION_CREATED.getKey(),
getRandomCompatibleIndexVersion(true, IndexVersionUtils.getPreviousVersion(IndexVersions.INFERENCE_METADATA_FIELDS))
)
.put(InferenceMetadataFieldsMapper.USE_LEGACY_SEMANTIC_TEXT_FORMAT.getKey(), false)
.build();
assertFalse(InferenceMetadataFieldsMapper.isEnabled(settings));
Expand Down Expand Up @@ -114,18 +117,18 @@ public MappedFieldType getMappedFieldType() {
}

static IndexVersion getRandomCompatibleIndexVersion(boolean useLegacyFormat) {
return getRandomCompatibleIndexVersion(useLegacyFormat, IndexVersion.current());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on your changes in SemanticTextFieldMapperTests, I don't think we need any changes to getRandomCompatibleIndexVersion? These changes create some usability challenges:

  • maxVersion is only selectively used, which could be confusing to future devs.
  • What if maxVersion is less than IndexVersions.UPGRADE_TO_LUCENE_10_0_0 or IndexVersions.INFERENCE_METADATA_FIELDS? This will result in an error, and one that is thrown based on a randomization factor as well, which will be very confusing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this because otherwise one of the cases in SemanticInferenceMetadataFieldMapperTests#isEnabled will fail as it depends on the old, less inclusive legacy format logic. You can see the modifications to the test for this here: 6cfe39a

I could have removed the test, but that didn't feel right. I think this is OK, if we need to extend it later for additional tests we can do so as needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I'm confused about is why reverting changes to both getRandomCompatibleIndexVersion and testIsEnabled isn't an option. This should work fine as this is the state of the test before this PR, no?

And that approach should work with your changes in SemanticTextFieldMapperTests, so I see it as the best path for the moment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have some followup work on this test, why don't we roll that into the scope?

}

static IndexVersion getRandomCompatibleIndexVersion(boolean useLegacyFormat, IndexVersion maxVersion) {
if (useLegacyFormat) {
if (randomBoolean()) {
return IndexVersionUtils.randomVersionBetween(
random(),
IndexVersions.UPGRADE_TO_LUCENE_10_0_0,
IndexVersionUtils.getPreviousVersion(IndexVersions.INFERENCE_METADATA_FIELDS)
);
return IndexVersionUtils.randomVersionBetween(random(), IndexVersions.UPGRADE_TO_LUCENE_10_0_0, maxVersion);
}
return IndexVersionUtils.randomPreviousCompatibleVersion(random(), IndexVersions.INFERENCE_METADATA_FIELDS_BACKPORT);
} else {
if (randomBoolean()) {
return IndexVersionUtils.randomVersionBetween(random(), IndexVersions.INFERENCE_METADATA_FIELDS, IndexVersion.current());
return IndexVersionUtils.randomVersionBetween(random(), IndexVersions.INFERENCE_METADATA_FIELDS, maxVersion);
}
return IndexVersionUtils.randomVersionBetween(
random(),
Expand All @@ -134,4 +137,5 @@ static IndexVersion getRandomCompatibleIndexVersion(boolean useLegacyFormat) {
);
}
}

}
Loading
Loading