From e6438d7c51e46b8db76ebdef2a7ad24748dfe15e Mon Sep 17 00:00:00 2001 From: Mayya Sharipova Date: Tue, 21 Oct 2025 10:17:01 -0400 Subject: [PATCH 1/6] Disallow dot_product and max_inner_product for int8_hnsw GPU type For int8_hnsw, during merges we get quantized vectors from Lucene files but dropping for each quantized vector its correction factor. For cosine and euclidean metrics this correction factor is not important, but for dot_product and max_inner_product metrics, they are important. It means that that currently for dot_product and max_inner_product metrics, GPU graph building doesn't work well, and may product bad recall. Thus this PR diallows these metrids. Alternatives: for most datasets (really majority), we can substitute dot_product with cosine. But there are some datasets that require max_inner_product, and for this our "int8_hsnw" will not work, and "hnsw" should be used instead --- .../vectors/DenseVectorFieldMapper.java | 6 +- .../mapper/vectors/VectorsFormatProvider.java | 7 +- .../vectors/DenseVectorFieldTypeTests.java | 9 +++ .../elasticsearch/plugin/gpu/GPUIndexIT.java | 39 +++++++++++ .../plugin/gpu/GPUPluginInitializationIT.java | 65 ++++++++++++++++--- .../elasticsearch/xpack/gpu/GPUPlugin.java | 23 +++++-- .../codec/GPUDenseVectorFieldMapperTests.java | 2 +- 7 files changed, 134 insertions(+), 17 deletions(-) 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 84d32874a17b3..0fcb5a60c95bd 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 @@ -2286,6 +2286,10 @@ public DenseVectorFieldType( this.isSyntheticSource = isSyntheticSource; } + public VectorSimilarity similarity() { + return similarity; + } + @Override public String typeName() { return CONTENT_TYPE; @@ -2890,7 +2894,7 @@ public KnnVectorsFormat getKnnVectorsFormatForField(KnnVectorsFormat defaultForm // if plugins provided alternative KnnVectorsFormat for this indexOptions, use it instead of standard KnnVectorsFormat extraKnnFormat = null; for (VectorsFormatProvider vectorsFormatProvider : extraVectorsFormatProviders) { - extraKnnFormat = vectorsFormatProvider.getKnnVectorsFormat(indexSettings, indexOptions); + extraKnnFormat = vectorsFormatProvider.getKnnVectorsFormat(indexSettings, indexOptions, fieldType().similarity()); if (extraKnnFormat != null) { break; } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/vectors/VectorsFormatProvider.java b/server/src/main/java/org/elasticsearch/index/mapper/vectors/VectorsFormatProvider.java index 4bc338e6680ec..7629b1032b98c 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/vectors/VectorsFormatProvider.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/vectors/VectorsFormatProvider.java @@ -24,7 +24,12 @@ public interface VectorsFormatProvider { * * @param indexSettings The index settings. * @param indexOptions The dense vector index options. + * @param similarity The vector similarity function. * @return A KnnVectorsFormat instance. */ - KnnVectorsFormat getKnnVectorsFormat(IndexSettings indexSettings, DenseVectorFieldMapper.DenseVectorIndexOptions indexOptions); + KnnVectorsFormat getKnnVectorsFormat( + IndexSettings indexSettings, + DenseVectorFieldMapper.DenseVectorIndexOptions indexOptions, + DenseVectorFieldMapper.VectorSimilarity similarity + ); } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldTypeTests.java index 333722a4b438f..74b9f976ed25d 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldTypeTests.java @@ -96,6 +96,15 @@ public static DenseVectorFieldMapper.DenseVectorIndexOptions randomGpuSupportedI ); } + public static DenseVectorFieldMapper.VectorSimilarity randomGPUSupportedSimilarity( + DenseVectorFieldMapper.DenseVectorIndexOptions indexOptions + ) { + if (indexOptions.getType() == DenseVectorFieldMapper.VectorIndexType.INT8_HNSW) { + return randomFrom(VectorSimilarity.L2_NORM, VectorSimilarity.COSINE); + } + return randomFrom(VectorSimilarity.values()); + } + public static DenseVectorFieldMapper.DenseVectorIndexOptions randomIndexOptionsAll() { List options = new ArrayList<>( Arrays.asList( diff --git a/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUIndexIT.java b/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUIndexIT.java index b00d8d83143a9..2742fbb0cea28 100644 --- a/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUIndexIT.java +++ b/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUIndexIT.java @@ -27,6 +27,7 @@ import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailuresAndResponse; +import static org.hamcrest.Matchers.containsString; @LuceneTestCase.SuppressCodecs("*") // use our custom codec public class GPUIndexIT extends ESIntegTestCase { @@ -160,6 +161,44 @@ public void testSearchWithoutGPU() { assertSearch(indexName, randomFloatVector(dims), numDocs); } + public void testInt8HnswDotProductFails() { + String indexName = "index_int8_dot_product_fails"; + final int dims = randomIntBetween(4, 128); + + Settings.Builder settingsBuilder = Settings.builder().put(indexSettings()); + settingsBuilder.put("index.number_of_shards", 1); + settingsBuilder.put("index.vectors.indexing.use_gpu", true); + + String mapping = String.format(Locale.ROOT, """ + { + "properties": { + "my_vector": { + "type": "dense_vector", + "dims": %d, + "similarity": "dot_product", + "index_options": { + "type": "int8_hnsw" + } + } + } + } + """, dims); + + // Index creation should succeed + assertAcked(prepareCreate(indexName).setSettings(settingsBuilder.build()).setMapping(mapping)); + ensureGreen(); + + // Attempt to index a document and expect it to fail + IllegalArgumentException ex = expectThrows( + IllegalArgumentException.class, + () -> client().prepareIndex(indexName).setId("1").setSource("my_vector", randomFloatVector(dims)).get() + ); + assertThat( + ex.getMessage(), + containsString("GPU vector indexing does not support DOT_PRODUCT similarity for [int8_hnsw] index type.") + ); + } + private void createIndex(String indexName, int dims, boolean sorted) { var settings = Settings.builder().put(indexSettings()); settings.put("index.number_of_shards", 1); diff --git a/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUPluginInitializationIT.java b/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUPluginInitializationIT.java index 65d8daf14d31e..dd46a469dfbb0 100644 --- a/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUPluginInitializationIT.java +++ b/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUPluginInitializationIT.java @@ -109,7 +109,7 @@ public void testFFOff() { GPUPlugin gpuPlugin = internalCluster().getInstance(GPUPlugin.class); VectorsFormatProvider vectorsFormatProvider = gpuPlugin.getVectorsFormatProvider(); - var format = vectorsFormatProvider.getKnnVectorsFormat(null, null); + var format = vectorsFormatProvider.getKnnVectorsFormat(null, null, null); assertNull(format); } @@ -136,7 +136,11 @@ public void testFFOffGPUFormatNull() { IndexSettings settings = getIndexSettings(); final var indexOptions = DenseVectorFieldTypeTests.randomGpuSupportedIndexOptions(); - var format = vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions); + var format = vectorsFormatProvider.getKnnVectorsFormat( + settings, + indexOptions, + DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions) + ); assertNull(format); } @@ -151,7 +155,11 @@ public void testIndexSettingOnIndexTypeSupportedGPUSupported() { IndexSettings settings = getIndexSettings(); final var indexOptions = DenseVectorFieldTypeTests.randomGpuSupportedIndexOptions(); - var format = vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions); + var format = vectorsFormatProvider.getKnnVectorsFormat( + settings, + indexOptions, + DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions) + ); assertNotNull(format); } @@ -166,7 +174,14 @@ public void testIndexSettingOnIndexTypeNotSupportedThrows() { IndexSettings settings = getIndexSettings(); final var indexOptions = DenseVectorFieldTypeTests.randomFlatIndexOptions(); - var ex = expectThrows(IllegalArgumentException.class, () -> vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions)); + var ex = expectThrows( + IllegalArgumentException.class, + () -> vectorsFormatProvider.getKnnVectorsFormat( + settings, + indexOptions, + DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions) + ) + ); assertThat(ex.getMessage(), startsWith("[index.vectors.indexing.use_gpu] doesn't support [index_options.type] of")); } @@ -181,7 +196,14 @@ public void testIndexSettingOnGPUNotSupportedThrows() { IndexSettings settings = getIndexSettings(); final var indexOptions = DenseVectorFieldTypeTests.randomGpuSupportedIndexOptions(); - var ex = expectThrows(IllegalArgumentException.class, () -> vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions)); + var ex = expectThrows( + IllegalArgumentException.class, + () -> vectorsFormatProvider.getKnnVectorsFormat( + settings, + indexOptions, + DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions) + ) + ); assertThat( ex.getMessage(), equalTo("[index.vectors.indexing.use_gpu] was set to [true], but GPU resources are not accessible on the node.") @@ -200,7 +222,14 @@ public void testIndexSettingOnGPUSupportThrowsRethrows() { IndexSettings settings = getIndexSettings(); final var indexOptions = DenseVectorFieldTypeTests.randomGpuSupportedIndexOptions(); - var ex = expectThrows(IllegalArgumentException.class, () -> vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions)); + var ex = expectThrows( + IllegalArgumentException.class, + () -> vectorsFormatProvider.getKnnVectorsFormat( + settings, + indexOptions, + DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions) + ) + ); assertThat( ex.getMessage(), equalTo("[index.vectors.indexing.use_gpu] was set to [true], but GPU resources are not accessible on the node.") @@ -218,7 +247,11 @@ public void testIndexSettingAutoIndexTypeSupportedGPUSupported() { IndexSettings settings = getIndexSettings(); final var indexOptions = DenseVectorFieldTypeTests.randomGpuSupportedIndexOptions(); - var format = vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions); + var format = vectorsFormatProvider.getKnnVectorsFormat( + settings, + indexOptions, + DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions) + ); assertNotNull(format); } @@ -233,7 +266,11 @@ public void testIndexSettingAutoGPUNotSupported() { IndexSettings settings = getIndexSettings(); final var indexOptions = DenseVectorFieldTypeTests.randomGpuSupportedIndexOptions(); - var format = vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions); + var format = vectorsFormatProvider.getKnnVectorsFormat( + settings, + indexOptions, + DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions) + ); assertNull(format); } @@ -248,7 +285,11 @@ public void testIndexSettingAutoIndexTypeNotSupported() { IndexSettings settings = getIndexSettings(); final var indexOptions = DenseVectorFieldTypeTests.randomFlatIndexOptions(); - var format = vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions); + var format = vectorsFormatProvider.getKnnVectorsFormat( + settings, + indexOptions, + DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions) + ); assertNull(format); } @@ -263,7 +304,11 @@ public void testIndexSettingOff() { IndexSettings settings = getIndexSettings(); final var indexOptions = DenseVectorFieldTypeTests.randomGpuSupportedIndexOptions(); - var format = vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions); + var format = vectorsFormatProvider.getKnnVectorsFormat( + settings, + indexOptions, + DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions) + ); assertNull(format); } diff --git a/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/GPUPlugin.java b/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/GPUPlugin.java index 96ea11e0c1aff..8b670f86a1c7a 100644 --- a/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/GPUPlugin.java +++ b/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/GPUPlugin.java @@ -62,7 +62,7 @@ public List> getSettings() { @Override public VectorsFormatProvider getVectorsFormatProvider() { - return (indexSettings, indexOptions) -> { + return (indexSettings, indexOptions, similarity) -> { if (GPU_FORMAT.isEnabled()) { GpuMode gpuMode = indexSettings.getValue(VECTORS_INDEXING_USE_GPU_SETTING); if (gpuMode == GpuMode.TRUE) { @@ -76,10 +76,10 @@ public VectorsFormatProvider getVectorsFormatProvider() { "[index.vectors.indexing.use_gpu] was set to [true], but GPU resources are not accessible on the node." ); } - return getVectorsFormat(indexOptions); + return getVectorsFormat(indexOptions, similarity); } if (gpuMode == GpuMode.AUTO && vectorIndexTypeSupported(indexOptions.getType()) && isGpuSupported) { - return getVectorsFormat(indexOptions); + return getVectorsFormat(indexOptions, similarity); } } return null; @@ -90,7 +90,10 @@ private boolean vectorIndexTypeSupported(DenseVectorFieldMapper.VectorIndexType return type == DenseVectorFieldMapper.VectorIndexType.HNSW || type == DenseVectorFieldMapper.VectorIndexType.INT8_HNSW; } - private static KnnVectorsFormat getVectorsFormat(DenseVectorFieldMapper.DenseVectorIndexOptions indexOptions) { + private static KnnVectorsFormat getVectorsFormat( + DenseVectorFieldMapper.DenseVectorIndexOptions indexOptions, + DenseVectorFieldMapper.VectorSimilarity similarity + ) { if (indexOptions.getType() == DenseVectorFieldMapper.VectorIndexType.HNSW) { DenseVectorFieldMapper.HnswIndexOptions hnswIndexOptions = (DenseVectorFieldMapper.HnswIndexOptions) indexOptions; int efConstruction = hnswIndexOptions.efConstruction(); @@ -99,6 +102,18 @@ private static KnnVectorsFormat getVectorsFormat(DenseVectorFieldMapper.DenseVec } return new ES92GpuHnswVectorsFormat(hnswIndexOptions.m(), efConstruction); } else if (indexOptions.getType() == DenseVectorFieldMapper.VectorIndexType.INT8_HNSW) { + if (similarity == DenseVectorFieldMapper.VectorSimilarity.DOT_PRODUCT + || similarity == DenseVectorFieldMapper.VectorSimilarity.MAX_INNER_PRODUCT) { + throw new IllegalArgumentException( + "GPU vector indexing does not support [" + + similarity + + "] similarity for [int8_hnsw] index type. " + + "Instead, consider using [" + + DenseVectorFieldMapper.VectorSimilarity.COSINE + + "] or " + + " [hnsw] index type." + ); + } DenseVectorFieldMapper.Int8HnswIndexOptions int8HnswIndexOptions = (DenseVectorFieldMapper.Int8HnswIndexOptions) indexOptions; int efConstruction = int8HnswIndexOptions.efConstruction(); if (efConstruction == HnswGraphBuilder.DEFAULT_BEAM_WIDTH) { diff --git a/x-pack/plugin/gpu/src/test/java/org/elasticsearch/xpack/gpu/codec/GPUDenseVectorFieldMapperTests.java b/x-pack/plugin/gpu/src/test/java/org/elasticsearch/xpack/gpu/codec/GPUDenseVectorFieldMapperTests.java index 2648691d03eec..d0188fb06d70b 100644 --- a/x-pack/plugin/gpu/src/test/java/org/elasticsearch/xpack/gpu/codec/GPUDenseVectorFieldMapperTests.java +++ b/x-pack/plugin/gpu/src/test/java/org/elasticsearch/xpack/gpu/codec/GPUDenseVectorFieldMapperTests.java @@ -63,7 +63,7 @@ private KnnVectorsFormat getKnnVectorsFormat(String indexOptionsType) throws IOE b.field("type", "dense_vector"); b.field("dims", dims); b.field("index", true); - b.field("similarity", "dot_product"); + b.field("similarity", "cosine"); b.startObject("index_options"); b.field("type", indexOptionsType); b.endObject(); From 79d8e0244d518ee1b958d08dbe8fbd9b2278e7e3 Mon Sep 17 00:00:00 2001 From: Mayya Sharipova Date: Tue, 21 Oct 2025 10:27:39 -0400 Subject: [PATCH 2/6] Update docs/changelog/136881.yaml --- docs/changelog/136881.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/136881.yaml diff --git a/docs/changelog/136881.yaml b/docs/changelog/136881.yaml new file mode 100644 index 0000000000000..d7ccf2ed0f5b3 --- /dev/null +++ b/docs/changelog/136881.yaml @@ -0,0 +1,5 @@ +pr: 136881 +summary: Disallow `dot_product` and `max_inner_product` for int8_hnsw GPU type +area: Search +type: bug +issues: [] From 34ad4cf4c709aae257998e8a254409b9bd8aab70 Mon Sep 17 00:00:00 2001 From: Mayya Sharipova Date: Tue, 21 Oct 2025 12:43:02 -0400 Subject: [PATCH 3/6] Fix errors --- .../java/org/elasticsearch/plugin/gpu/GPUIndexIT.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUIndexIT.java b/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUIndexIT.java index 2742fbb0cea28..626792a400075 100644 --- a/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUIndexIT.java +++ b/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUIndexIT.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Locale; +import static org.apache.lucene.tests.index.BaseKnnVectorsFormatTestCase.randomNormalizedVector; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailuresAndResponse; @@ -191,11 +192,11 @@ public void testInt8HnswDotProductFails() { // Attempt to index a document and expect it to fail IllegalArgumentException ex = expectThrows( IllegalArgumentException.class, - () -> client().prepareIndex(indexName).setId("1").setSource("my_vector", randomFloatVector(dims)).get() + () -> client().prepareIndex(indexName).setId("1").setSource("my_vector", randomNormalizedVector(dims)).get() ); assertThat( ex.getMessage(), - containsString("GPU vector indexing does not support DOT_PRODUCT similarity for [int8_hnsw] index type.") + containsString("GPU vector indexing does not support [dot_product] similarity for [int8_hnsw] index type.") ); } From 60e1dc9d3226320cb4f9f236efd521827dbf2d2a Mon Sep 17 00:00:00 2001 From: Mayya Sharipova Date: Tue, 21 Oct 2025 14:15:18 -0400 Subject: [PATCH 4/6] Substitute dot_product with cosine for int8 --- docs/changelog/136881.yaml | 2 +- .../mapper/vectors/DenseVectorFieldTypeTests.java | 2 +- .../java/org/elasticsearch/plugin/gpu/GPUIndexIT.java | 11 +++++------ .../java/org/elasticsearch/xpack/gpu/GPUPlugin.java | 3 +-- .../xpack/gpu/codec/ES92GpuHnswVectorsWriter.java | 7 ++++++- .../gpu/codec/GPUDenseVectorFieldMapperTests.java | 2 +- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/changelog/136881.yaml b/docs/changelog/136881.yaml index d7ccf2ed0f5b3..6ec0c0f410a22 100644 --- a/docs/changelog/136881.yaml +++ b/docs/changelog/136881.yaml @@ -1,5 +1,5 @@ pr: 136881 -summary: Disallow `dot_product` and `max_inner_product` for int8_hnsw GPU type +summary: Disallow `max_inner_product`, swap `dot_product` for `cosine` for int8_hnsw GPU type area: Search type: bug issues: [] diff --git a/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldTypeTests.java index 74b9f976ed25d..4a191fe0f5679 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldTypeTests.java @@ -100,7 +100,7 @@ public static DenseVectorFieldMapper.VectorSimilarity randomGPUSupportedSimilari DenseVectorFieldMapper.DenseVectorIndexOptions indexOptions ) { if (indexOptions.getType() == DenseVectorFieldMapper.VectorIndexType.INT8_HNSW) { - return randomFrom(VectorSimilarity.L2_NORM, VectorSimilarity.COSINE); + return randomFrom(VectorSimilarity.L2_NORM, VectorSimilarity.COSINE, VectorSimilarity.DOT_PRODUCT); } return randomFrom(VectorSimilarity.values()); } diff --git a/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUIndexIT.java b/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUIndexIT.java index 626792a400075..92d91c7db22c5 100644 --- a/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUIndexIT.java +++ b/x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUIndexIT.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.Locale; -import static org.apache.lucene.tests.index.BaseKnnVectorsFormatTestCase.randomNormalizedVector; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailuresAndResponse; @@ -162,8 +161,8 @@ public void testSearchWithoutGPU() { assertSearch(indexName, randomFloatVector(dims), numDocs); } - public void testInt8HnswDotProductFails() { - String indexName = "index_int8_dot_product_fails"; + public void testInt8HnswMaxInnerProductProductFails() { + String indexName = "index_int8_max_inner_product_fails"; final int dims = randomIntBetween(4, 128); Settings.Builder settingsBuilder = Settings.builder().put(indexSettings()); @@ -176,7 +175,7 @@ public void testInt8HnswDotProductFails() { "my_vector": { "type": "dense_vector", "dims": %d, - "similarity": "dot_product", + "similarity": "max_inner_product", "index_options": { "type": "int8_hnsw" } @@ -192,11 +191,11 @@ public void testInt8HnswDotProductFails() { // Attempt to index a document and expect it to fail IllegalArgumentException ex = expectThrows( IllegalArgumentException.class, - () -> client().prepareIndex(indexName).setId("1").setSource("my_vector", randomNormalizedVector(dims)).get() + () -> client().prepareIndex(indexName).setId("1").setSource("my_vector", randomFloatVector(dims)).get() ); assertThat( ex.getMessage(), - containsString("GPU vector indexing does not support [dot_product] similarity for [int8_hnsw] index type.") + containsString("GPU vector indexing does not support [max_inner_product] similarity for [int8_hnsw] index type.") ); } diff --git a/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/GPUPlugin.java b/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/GPUPlugin.java index 8b670f86a1c7a..4dd1e574b6da8 100644 --- a/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/GPUPlugin.java +++ b/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/GPUPlugin.java @@ -102,8 +102,7 @@ private static KnnVectorsFormat getVectorsFormat( } return new ES92GpuHnswVectorsFormat(hnswIndexOptions.m(), efConstruction); } else if (indexOptions.getType() == DenseVectorFieldMapper.VectorIndexType.INT8_HNSW) { - if (similarity == DenseVectorFieldMapper.VectorSimilarity.DOT_PRODUCT - || similarity == DenseVectorFieldMapper.VectorSimilarity.MAX_INNER_PRODUCT) { + if (similarity == DenseVectorFieldMapper.VectorSimilarity.MAX_INNER_PRODUCT) { throw new IllegalArgumentException( "GPU vector indexing does not support [" + similarity diff --git a/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java b/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java index 6702547314f2d..584fde03cc636 100644 --- a/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java +++ b/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java @@ -314,7 +314,12 @@ private CagraIndex buildGPUIndex( ) throws Throwable { CagraIndexParams.CuvsDistanceType distanceType = switch (similarityFunction) { case EUCLIDEAN -> CagraIndexParams.CuvsDistanceType.L2Expanded; - case DOT_PRODUCT, MAXIMUM_INNER_PRODUCT -> CagraIndexParams.CuvsDistanceType.InnerProduct; + case DOT_PRODUCT, MAXIMUM_INNER_PRODUCT -> { + if (dataType == CuVSMatrix.DataType.BYTE && similarityFunction == VectorSimilarityFunction.DOT_PRODUCT) { + yield CagraIndexParams.CuvsDistanceType.CosineExpanded; + } + yield CagraIndexParams.CuvsDistanceType.InnerProduct; + } case COSINE -> CagraIndexParams.CuvsDistanceType.CosineExpanded; }; diff --git a/x-pack/plugin/gpu/src/test/java/org/elasticsearch/xpack/gpu/codec/GPUDenseVectorFieldMapperTests.java b/x-pack/plugin/gpu/src/test/java/org/elasticsearch/xpack/gpu/codec/GPUDenseVectorFieldMapperTests.java index d0188fb06d70b..2648691d03eec 100644 --- a/x-pack/plugin/gpu/src/test/java/org/elasticsearch/xpack/gpu/codec/GPUDenseVectorFieldMapperTests.java +++ b/x-pack/plugin/gpu/src/test/java/org/elasticsearch/xpack/gpu/codec/GPUDenseVectorFieldMapperTests.java @@ -63,7 +63,7 @@ private KnnVectorsFormat getKnnVectorsFormat(String indexOptionsType) throws IOE b.field("type", "dense_vector"); b.field("dims", dims); b.field("index", true); - b.field("similarity", "cosine"); + b.field("similarity", "dot_product"); b.startObject("index_options"); b.field("type", indexOptionsType); b.endObject(); From 0fac65cf9f7a02e99f90b5d87d4f262412ea0386 Mon Sep 17 00:00:00 2001 From: Mayya Sharipova Date: Wed, 22 Oct 2025 12:19:21 -0400 Subject: [PATCH 5/6] Update x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java Co-authored-by: Benjamin Trent --- .../elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java b/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java index 584fde03cc636..2196f12237ba9 100644 --- a/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java +++ b/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java @@ -315,7 +315,7 @@ private CagraIndex buildGPUIndex( CagraIndexParams.CuvsDistanceType distanceType = switch (similarityFunction) { case EUCLIDEAN -> CagraIndexParams.CuvsDistanceType.L2Expanded; case DOT_PRODUCT, MAXIMUM_INNER_PRODUCT -> { - if (dataType == CuVSMatrix.DataType.BYTE && similarityFunction == VectorSimilarityFunction.DOT_PRODUCT) { + if (dataType == CuVSMatrix.DataType.BYTE) { yield CagraIndexParams.CuvsDistanceType.CosineExpanded; } yield CagraIndexParams.CuvsDistanceType.InnerProduct; From 33909ffa2cb54895e62c348e24a2249ba70684fe Mon Sep 17 00:00:00 2001 From: Mayya Sharipova Date: Wed, 22 Oct 2025 12:34:12 -0400 Subject: [PATCH 6/6] Add assertion --- .../xpack/gpu/codec/ES92GpuHnswVectorsWriter.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java b/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java index 2196f12237ba9..6cccda6333f2b 100644 --- a/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java +++ b/x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java @@ -313,14 +313,18 @@ private CagraIndex buildGPUIndex( CuVSMatrix dataset ) throws Throwable { CagraIndexParams.CuvsDistanceType distanceType = switch (similarityFunction) { + case COSINE -> CagraIndexParams.CuvsDistanceType.CosineExpanded; case EUCLIDEAN -> CagraIndexParams.CuvsDistanceType.L2Expanded; - case DOT_PRODUCT, MAXIMUM_INNER_PRODUCT -> { + case DOT_PRODUCT -> { if (dataType == CuVSMatrix.DataType.BYTE) { yield CagraIndexParams.CuvsDistanceType.CosineExpanded; } yield CagraIndexParams.CuvsDistanceType.InnerProduct; } - case COSINE -> CagraIndexParams.CuvsDistanceType.CosineExpanded; + case MAXIMUM_INNER_PRODUCT -> { + assert dataType != CuVSMatrix.DataType.BYTE; + yield CagraIndexParams.CuvsDistanceType.InnerProduct; + } }; // TODO: expose cagra index params for algorithm, NNDescentNumIterations