Skip to content

Commit 9634fd6

Browse files
Disallow dot_product and max_inner_product for int8_hnsw GPU type (#136881)
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 produce bad recall. This PR does the following: - disallows max_inner_product for int8 - substitutes internally dot_product with cosine 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
1 parent e258b6f commit 9634fd6

File tree

8 files changed

+148
-18
lines changed

8 files changed

+148
-18
lines changed

docs/changelog/136881.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 136881
2+
summary: Disallow `max_inner_product`, swap `dot_product` for `cosine` for int8_hnsw GPU type
3+
area: Search
4+
type: bug
5+
issues: []

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2286,6 +2286,10 @@ public DenseVectorFieldType(
22862286
this.isSyntheticSource = isSyntheticSource;
22872287
}
22882288

2289+
public VectorSimilarity similarity() {
2290+
return similarity;
2291+
}
2292+
22892293
@Override
22902294
public String typeName() {
22912295
return CONTENT_TYPE;
@@ -2890,7 +2894,7 @@ public KnnVectorsFormat getKnnVectorsFormatForField(KnnVectorsFormat defaultForm
28902894
// if plugins provided alternative KnnVectorsFormat for this indexOptions, use it instead of standard
28912895
KnnVectorsFormat extraKnnFormat = null;
28922896
for (VectorsFormatProvider vectorsFormatProvider : extraVectorsFormatProviders) {
2893-
extraKnnFormat = vectorsFormatProvider.getKnnVectorsFormat(indexSettings, indexOptions);
2897+
extraKnnFormat = vectorsFormatProvider.getKnnVectorsFormat(indexSettings, indexOptions, fieldType().similarity());
28942898
if (extraKnnFormat != null) {
28952899
break;
28962900
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ public interface VectorsFormatProvider {
2424
*
2525
* @param indexSettings The index settings.
2626
* @param indexOptions The dense vector index options.
27+
* @param similarity The vector similarity function.
2728
* @return A KnnVectorsFormat instance.
2829
*/
29-
KnnVectorsFormat getKnnVectorsFormat(IndexSettings indexSettings, DenseVectorFieldMapper.DenseVectorIndexOptions indexOptions);
30+
KnnVectorsFormat getKnnVectorsFormat(
31+
IndexSettings indexSettings,
32+
DenseVectorFieldMapper.DenseVectorIndexOptions indexOptions,
33+
DenseVectorFieldMapper.VectorSimilarity similarity
34+
);
3035
}

server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldTypeTests.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@ public static DenseVectorFieldMapper.DenseVectorIndexOptions randomGpuSupportedI
9696
);
9797
}
9898

99+
public static DenseVectorFieldMapper.VectorSimilarity randomGPUSupportedSimilarity(
100+
DenseVectorFieldMapper.DenseVectorIndexOptions indexOptions
101+
) {
102+
if (indexOptions.getType() == DenseVectorFieldMapper.VectorIndexType.INT8_HNSW) {
103+
return randomFrom(VectorSimilarity.L2_NORM, VectorSimilarity.COSINE, VectorSimilarity.DOT_PRODUCT);
104+
}
105+
return randomFrom(VectorSimilarity.values());
106+
}
107+
99108
public static DenseVectorFieldMapper.DenseVectorIndexOptions randomIndexOptionsAll() {
100109
List<DenseVectorFieldMapper.DenseVectorIndexOptions> options = new ArrayList<>(
101110
Arrays.asList(

x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUIndexIT.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
2828
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
2929
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailuresAndResponse;
30+
import static org.hamcrest.Matchers.containsString;
3031

3132
@LuceneTestCase.SuppressCodecs("*") // use our custom codec
3233
public class GPUIndexIT extends ESIntegTestCase {
@@ -160,6 +161,44 @@ public void testSearchWithoutGPU() {
160161
assertSearch(indexName, randomFloatVector(dims), numDocs);
161162
}
162163

164+
public void testInt8HnswMaxInnerProductProductFails() {
165+
String indexName = "index_int8_max_inner_product_fails";
166+
final int dims = randomIntBetween(4, 128);
167+
168+
Settings.Builder settingsBuilder = Settings.builder().put(indexSettings());
169+
settingsBuilder.put("index.number_of_shards", 1);
170+
settingsBuilder.put("index.vectors.indexing.use_gpu", true);
171+
172+
String mapping = String.format(Locale.ROOT, """
173+
{
174+
"properties": {
175+
"my_vector": {
176+
"type": "dense_vector",
177+
"dims": %d,
178+
"similarity": "max_inner_product",
179+
"index_options": {
180+
"type": "int8_hnsw"
181+
}
182+
}
183+
}
184+
}
185+
""", dims);
186+
187+
// Index creation should succeed
188+
assertAcked(prepareCreate(indexName).setSettings(settingsBuilder.build()).setMapping(mapping));
189+
ensureGreen();
190+
191+
// Attempt to index a document and expect it to fail
192+
IllegalArgumentException ex = expectThrows(
193+
IllegalArgumentException.class,
194+
() -> client().prepareIndex(indexName).setId("1").setSource("my_vector", randomFloatVector(dims)).get()
195+
);
196+
assertThat(
197+
ex.getMessage(),
198+
containsString("GPU vector indexing does not support [max_inner_product] similarity for [int8_hnsw] index type.")
199+
);
200+
}
201+
163202
private void createIndex(String indexName, int dims, boolean sorted) {
164203
var settings = Settings.builder().put(indexSettings());
165204
settings.put("index.number_of_shards", 1);

x-pack/plugin/gpu/src/internalClusterTest/java/org/elasticsearch/plugin/gpu/GPUPluginInitializationIT.java

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public void testFFOff() {
109109
GPUPlugin gpuPlugin = internalCluster().getInstance(GPUPlugin.class);
110110
VectorsFormatProvider vectorsFormatProvider = gpuPlugin.getVectorsFormatProvider();
111111

112-
var format = vectorsFormatProvider.getKnnVectorsFormat(null, null);
112+
var format = vectorsFormatProvider.getKnnVectorsFormat(null, null, null);
113113
assertNull(format);
114114
}
115115

@@ -136,7 +136,11 @@ public void testFFOffGPUFormatNull() {
136136
IndexSettings settings = getIndexSettings();
137137
final var indexOptions = DenseVectorFieldTypeTests.randomGpuSupportedIndexOptions();
138138

139-
var format = vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions);
139+
var format = vectorsFormatProvider.getKnnVectorsFormat(
140+
settings,
141+
indexOptions,
142+
DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions)
143+
);
140144
assertNull(format);
141145
}
142146

@@ -151,7 +155,11 @@ public void testIndexSettingOnIndexTypeSupportedGPUSupported() {
151155
IndexSettings settings = getIndexSettings();
152156
final var indexOptions = DenseVectorFieldTypeTests.randomGpuSupportedIndexOptions();
153157

154-
var format = vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions);
158+
var format = vectorsFormatProvider.getKnnVectorsFormat(
159+
settings,
160+
indexOptions,
161+
DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions)
162+
);
155163
assertNotNull(format);
156164
}
157165

@@ -166,7 +174,14 @@ public void testIndexSettingOnIndexTypeNotSupportedThrows() {
166174
IndexSettings settings = getIndexSettings();
167175
final var indexOptions = DenseVectorFieldTypeTests.randomFlatIndexOptions();
168176

169-
var ex = expectThrows(IllegalArgumentException.class, () -> vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions));
177+
var ex = expectThrows(
178+
IllegalArgumentException.class,
179+
() -> vectorsFormatProvider.getKnnVectorsFormat(
180+
settings,
181+
indexOptions,
182+
DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions)
183+
)
184+
);
170185
assertThat(ex.getMessage(), startsWith("[index.vectors.indexing.use_gpu] doesn't support [index_options.type] of"));
171186
}
172187

@@ -181,7 +196,14 @@ public void testIndexSettingOnGPUNotSupportedThrows() {
181196
IndexSettings settings = getIndexSettings();
182197
final var indexOptions = DenseVectorFieldTypeTests.randomGpuSupportedIndexOptions();
183198

184-
var ex = expectThrows(IllegalArgumentException.class, () -> vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions));
199+
var ex = expectThrows(
200+
IllegalArgumentException.class,
201+
() -> vectorsFormatProvider.getKnnVectorsFormat(
202+
settings,
203+
indexOptions,
204+
DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions)
205+
)
206+
);
185207
assertThat(
186208
ex.getMessage(),
187209
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() {
200222
IndexSettings settings = getIndexSettings();
201223
final var indexOptions = DenseVectorFieldTypeTests.randomGpuSupportedIndexOptions();
202224

203-
var ex = expectThrows(IllegalArgumentException.class, () -> vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions));
225+
var ex = expectThrows(
226+
IllegalArgumentException.class,
227+
() -> vectorsFormatProvider.getKnnVectorsFormat(
228+
settings,
229+
indexOptions,
230+
DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions)
231+
)
232+
);
204233
assertThat(
205234
ex.getMessage(),
206235
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() {
218247
IndexSettings settings = getIndexSettings();
219248
final var indexOptions = DenseVectorFieldTypeTests.randomGpuSupportedIndexOptions();
220249

221-
var format = vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions);
250+
var format = vectorsFormatProvider.getKnnVectorsFormat(
251+
settings,
252+
indexOptions,
253+
DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions)
254+
);
222255
assertNotNull(format);
223256
}
224257

@@ -233,7 +266,11 @@ public void testIndexSettingAutoGPUNotSupported() {
233266
IndexSettings settings = getIndexSettings();
234267
final var indexOptions = DenseVectorFieldTypeTests.randomGpuSupportedIndexOptions();
235268

236-
var format = vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions);
269+
var format = vectorsFormatProvider.getKnnVectorsFormat(
270+
settings,
271+
indexOptions,
272+
DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions)
273+
);
237274
assertNull(format);
238275
}
239276

@@ -248,7 +285,11 @@ public void testIndexSettingAutoIndexTypeNotSupported() {
248285
IndexSettings settings = getIndexSettings();
249286
final var indexOptions = DenseVectorFieldTypeTests.randomFlatIndexOptions();
250287

251-
var format = vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions);
288+
var format = vectorsFormatProvider.getKnnVectorsFormat(
289+
settings,
290+
indexOptions,
291+
DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions)
292+
);
252293
assertNull(format);
253294
}
254295

@@ -263,7 +304,11 @@ public void testIndexSettingOff() {
263304
IndexSettings settings = getIndexSettings();
264305
final var indexOptions = DenseVectorFieldTypeTests.randomGpuSupportedIndexOptions();
265306

266-
var format = vectorsFormatProvider.getKnnVectorsFormat(settings, indexOptions);
307+
var format = vectorsFormatProvider.getKnnVectorsFormat(
308+
settings,
309+
indexOptions,
310+
DenseVectorFieldTypeTests.randomGPUSupportedSimilarity(indexOptions)
311+
);
267312
assertNull(format);
268313
}
269314

x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/GPUPlugin.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public List<Setting<?>> getSettings() {
6262

6363
@Override
6464
public VectorsFormatProvider getVectorsFormatProvider() {
65-
return (indexSettings, indexOptions) -> {
65+
return (indexSettings, indexOptions, similarity) -> {
6666
if (GPU_FORMAT.isEnabled()) {
6767
GpuMode gpuMode = indexSettings.getValue(VECTORS_INDEXING_USE_GPU_SETTING);
6868
if (gpuMode == GpuMode.TRUE) {
@@ -76,10 +76,10 @@ public VectorsFormatProvider getVectorsFormatProvider() {
7676
"[index.vectors.indexing.use_gpu] was set to [true], but GPU resources are not accessible on the node."
7777
);
7878
}
79-
return getVectorsFormat(indexOptions);
79+
return getVectorsFormat(indexOptions, similarity);
8080
}
8181
if (gpuMode == GpuMode.AUTO && vectorIndexTypeSupported(indexOptions.getType()) && isGpuSupported) {
82-
return getVectorsFormat(indexOptions);
82+
return getVectorsFormat(indexOptions, similarity);
8383
}
8484
}
8585
return null;
@@ -90,7 +90,10 @@ private boolean vectorIndexTypeSupported(DenseVectorFieldMapper.VectorIndexType
9090
return type == DenseVectorFieldMapper.VectorIndexType.HNSW || type == DenseVectorFieldMapper.VectorIndexType.INT8_HNSW;
9191
}
9292

93-
private static KnnVectorsFormat getVectorsFormat(DenseVectorFieldMapper.DenseVectorIndexOptions indexOptions) {
93+
private static KnnVectorsFormat getVectorsFormat(
94+
DenseVectorFieldMapper.DenseVectorIndexOptions indexOptions,
95+
DenseVectorFieldMapper.VectorSimilarity similarity
96+
) {
9497
if (indexOptions.getType() == DenseVectorFieldMapper.VectorIndexType.HNSW) {
9598
DenseVectorFieldMapper.HnswIndexOptions hnswIndexOptions = (DenseVectorFieldMapper.HnswIndexOptions) indexOptions;
9699
int efConstruction = hnswIndexOptions.efConstruction();
@@ -99,6 +102,17 @@ private static KnnVectorsFormat getVectorsFormat(DenseVectorFieldMapper.DenseVec
99102
}
100103
return new ES92GpuHnswVectorsFormat(hnswIndexOptions.m(), efConstruction);
101104
} else if (indexOptions.getType() == DenseVectorFieldMapper.VectorIndexType.INT8_HNSW) {
105+
if (similarity == DenseVectorFieldMapper.VectorSimilarity.MAX_INNER_PRODUCT) {
106+
throw new IllegalArgumentException(
107+
"GPU vector indexing does not support ["
108+
+ similarity
109+
+ "] similarity for [int8_hnsw] index type. "
110+
+ "Instead, consider using ["
111+
+ DenseVectorFieldMapper.VectorSimilarity.COSINE
112+
+ "] or "
113+
+ " [hnsw] index type."
114+
);
115+
}
102116
DenseVectorFieldMapper.Int8HnswIndexOptions int8HnswIndexOptions = (DenseVectorFieldMapper.Int8HnswIndexOptions) indexOptions;
103117
int efConstruction = int8HnswIndexOptions.efConstruction();
104118
if (efConstruction == HnswGraphBuilder.DEFAULT_BEAM_WIDTH) {

x-pack/plugin/gpu/src/main/java/org/elasticsearch/xpack/gpu/codec/ES92GpuHnswVectorsWriter.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,18 @@ private CagraIndex buildGPUIndex(
313313
CuVSMatrix dataset
314314
) throws Throwable {
315315
CagraIndexParams.CuvsDistanceType distanceType = switch (similarityFunction) {
316-
case EUCLIDEAN -> CagraIndexParams.CuvsDistanceType.L2Expanded;
317-
case DOT_PRODUCT, MAXIMUM_INNER_PRODUCT -> CagraIndexParams.CuvsDistanceType.InnerProduct;
318316
case COSINE -> CagraIndexParams.CuvsDistanceType.CosineExpanded;
317+
case EUCLIDEAN -> CagraIndexParams.CuvsDistanceType.L2Expanded;
318+
case DOT_PRODUCT -> {
319+
if (dataType == CuVSMatrix.DataType.BYTE) {
320+
yield CagraIndexParams.CuvsDistanceType.CosineExpanded;
321+
}
322+
yield CagraIndexParams.CuvsDistanceType.InnerProduct;
323+
}
324+
case MAXIMUM_INNER_PRODUCT -> {
325+
assert dataType != CuVSMatrix.DataType.BYTE;
326+
yield CagraIndexParams.CuvsDistanceType.InnerProduct;
327+
}
319328
};
320329

321330
// TODO: expose cagra index params for algorithm, NNDescentNumIterations

0 commit comments

Comments
 (0)