-
Notifications
You must be signed in to change notification settings - Fork 140
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
Partial loading implementation for FAISS HNSW #2405
base: main
Are you sure you want to change the base?
Changes from all commits
1ee43c7
3539b64
6eb7d2e
2051bfc
2c47d2c
ceae9ca
adb2b37
51388da
1539547
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.knn.index; | ||
|
||
import org.apache.lucene.util.VectorUtil; | ||
|
||
public enum KNNVectorDistanceFunction { | ||
EUCLIDEAN { | ||
@Override | ||
public float distance(float[] vec1, float[] vec2) { | ||
return VectorUtil.squareDistance(vec1, vec2); | ||
} | ||
|
||
@Override | ||
public float distance(byte[] vec1, byte[] vec2) { | ||
return VectorUtil.squareDistance(vec1, vec2); | ||
} | ||
}, | ||
DOT_PRODUCT { | ||
@Override | ||
public float distance(float[] vec1, float[] vec2) { | ||
return -VectorUtil.dotProduct(vec1, vec2); | ||
} | ||
|
||
@Override | ||
public float distance(byte[] vec1, byte[] vec2) { | ||
return -VectorUtil.dotProduct(vec1, vec2); | ||
} | ||
}, | ||
COSINE { | ||
@Override | ||
public float distance(float[] vec1, float[] vec2) { | ||
return VectorUtil.cosine(vec1, vec2); | ||
} | ||
|
||
@Override | ||
public float distance(byte[] vec1, byte[] vec2) { | ||
return VectorUtil.cosine(vec1, vec2); | ||
} | ||
}; | ||
|
||
public abstract float distance(float[] vec1, float[] vec2); | ||
|
||
public abstract float distance(byte[] vec1, byte[] vec2); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,11 +22,15 @@ | |
import org.opensearch.knn.index.util.IndexUtil; | ||
import org.opensearch.knn.jni.JNIService; | ||
import org.opensearch.knn.index.engine.KNNEngine; | ||
import org.opensearch.knn.partialloading.PartialLoadingContext; | ||
import org.opensearch.knn.partialloading.faiss.FaissIndex; | ||
import org.opensearch.knn.partialloading.search.PartialLoadingMode; | ||
import org.opensearch.knn.training.TrainingDataConsumer; | ||
import org.opensearch.knn.training.VectorReader; | ||
|
||
import java.io.Closeable; | ||
import java.io.IOException; | ||
import java.io.UnsupportedEncodingException; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
|
||
|
@@ -87,6 +91,15 @@ public NativeMemoryAllocation.IndexAllocation load(NativeMemoryEntryContext.Inde | |
final Directory directory = indexEntryContext.getDirectory(); | ||
final int indexSizeKb = Math.toIntExact(directory.fileLength(vectorFileName) / 1024); | ||
|
||
// TMP | ||
final PartialLoadingMode partialLoadingMode = PartialLoadingMode.DISABLED; | ||
// final PartialLoadingMode partialLoadingMode = PartialLoadingMode.MEMORY_EFFICIENT; | ||
// TMP | ||
|
||
if (partialLoadingMode != PartialLoadingMode.DISABLED) { | ||
return createPartialLoadedIndexAllocation(directory, indexEntryContext, knnEngine, vectorFileName, partialLoadingMode); | ||
} | ||
|
||
// Try to open an index input then pass it down to native engine for loading an index. | ||
try (IndexInput readStream = directory.openInput(vectorFileName, IOContext.READONCE)) { | ||
final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(readStream); | ||
|
@@ -96,6 +109,45 @@ public NativeMemoryAllocation.IndexAllocation load(NativeMemoryEntryContext.Inde | |
} | ||
} | ||
|
||
private NativeMemoryAllocation.IndexAllocation createPartialLoadedIndexAllocation( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we create a separate class which extends This will simplify the code and isolate partialLoading related code ideally under one fork rather than an if fork in each class. Let me know how it turns out? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Decision whether to partial load or not is being made within |
||
Directory directory, | ||
NativeMemoryEntryContext.IndexEntryContext indexEntryContext, | ||
KNNEngine knnEngine, | ||
String vectorFileName, | ||
PartialLoadingMode partialLoadingMode | ||
) throws IOException { | ||
validatePartialLoadingSupported(indexEntryContext, knnEngine); | ||
|
||
// Try to open an index input then pass it down to native engine for loading an index. | ||
FaissIndex faissIndex = null; | ||
try (IndexInput input = directory.openInput(vectorFileName, IOContext.READONCE)) { | ||
faissIndex = FaissIndex.partiallyLoad(input); | ||
} | ||
|
||
// Create partial loading context. | ||
final PartialLoadingContext partialLoadingContext = new PartialLoadingContext(faissIndex, vectorFileName, partialLoadingMode); | ||
|
||
return new NativeMemoryAllocation.IndexAllocation( | ||
executor, | ||
knnEngine, | ||
vectorFileName, | ||
indexEntryContext.getOpenSearchIndexName(), | ||
IndexUtil.isBinaryIndex(knnEngine, indexEntryContext.getParameters()), | ||
partialLoadingContext | ||
); | ||
} | ||
|
||
private void validatePartialLoadingSupported(NativeMemoryEntryContext.IndexEntryContext indexEntryContext, KNNEngine knnEngine) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This validation seems too late, are there any validations like this while creating the mapping? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I agree! My strategy is to have two separate PRs: 1. Core logic for partial loading 2. Extending mapping |
||
throws UnsupportedEncodingException { | ||
if (IndexUtil.isBinaryIndex(knnEngine, indexEntryContext.getParameters())) { | ||
throw new UnsupportedEncodingException("Partial loading search does not support binary index."); | ||
} | ||
|
||
if (IndexUtil.isByteIndex(indexEntryContext.getParameters())) { | ||
throw new UnsupportedEncodingException("Partial loading search does not support byte index."); | ||
} | ||
} | ||
|
||
private NativeMemoryAllocation.IndexAllocation createIndexAllocation( | ||
final NativeMemoryEntryContext.IndexEntryContext indexEntryContext, | ||
final KNNEngine knnEngine, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am little confused between mapping and setting. What was decided on whether to use mapping or setting?
Ideally if the performance and recall is equal we should eventually have an option of deprecating something that is not memory-effecient. Will having a mapping make it a one way door?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The whole point of MEMORY_EFFICIENT is to give an option to users to operate big vector index within a memory constraints environment.