Skip to content
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

Add support for Faiss SQFP16 Quantization and enable AVX2 Optimization #1421

Merged
Next Next commit
Add Support for Faiss SQFP16 and enable Faiss AVX2 Optimization
Signed-off-by: Naveen Tatikonda <navtat@amazon.com>
  • Loading branch information
naveentatikonda committed Jan 31, 2024
commit 70ea2206dbde7171644bc03b89707092c5632501
2 changes: 1 addition & 1 deletion jni/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ endif ()
if (${CONFIG_FAISS} STREQUAL ON OR ${CONFIG_ALL} STREQUAL ON OR ${CONFIG_TEST} STREQUAL ON)
set(BUILD_TESTING OFF) # Avoid building faiss tests
set(BLA_STATIC ON) # Statically link BLAS
set(FAISS_OPT_LEVEL generic) # Keep optimization level generic
set(FAISS_OPT_LEVEL avx2) # Keep optimization level as avx2 to improve performance

if (${CMAKE_SYSTEM_NAME} STREQUAL Darwin)
if(CMAKE_C_COMPILER_ID MATCHES "Clang\$")
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/opensearch/knn/common/KNNConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ public class KNNConstants {
public static final String FAISS_IVF_DESCRIPTION = "IVF";
public static final String FAISS_FLAT_DESCRIPTION = "Flat";
public static final String FAISS_PQ_DESCRIPTION = "PQ";
public static final String ENCODER_SQFP16 = "SQfp16";
naveentatikonda marked this conversation as resolved.
Show resolved Hide resolved
public static final String FAISS_SQFP16_DESCRIPTION = "SQfp16";

// Parameter defaults/limits
public static final Integer ENCODER_PARAMETER_PQ_CODE_COUNT_DEFAULT = 1;
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/opensearch/knn/index/util/Faiss.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ class Faiss extends NativeLibrary {
methodComponentContext
).build())
)
.build(),
KNNConstants.ENCODER_SQFP16,
MethodComponent.Builder.builder(KNNConstants.ENCODER_SQFP16)
.setMapGenerator(
((methodComponent, methodComponentContext) -> MethodAsMapBuilder.builder(
KNNConstants.FAISS_SQFP16_DESCRIPTION,
methodComponent,
methodComponentContext
).build())
)
.build()
);

Expand Down
151 changes: 151 additions & 0 deletions src/test/java/org/opensearch/knn/index/FaissIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Floats;
import lombok.SneakyThrows;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.junit.BeforeClass;
import org.opensearch.client.Response;
Expand All @@ -39,10 +40,12 @@

import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M;
import static org.opensearch.knn.common.KNNConstants.ENCODER_PQ;
import static org.opensearch.knn.common.KNNConstants.ENCODER_SQFP16;
import static org.opensearch.knn.common.KNNConstants.FAISS_NAME;
import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE;
import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER;
import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW;
import static org.opensearch.knn.common.KNNConstants.METHOD_IVF;
import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST;
import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE;
import static org.opensearch.knn.common.KNNConstants.MODEL_ID;
Expand Down Expand Up @@ -267,6 +270,115 @@ public void testEndToEnd_whenMethodIsHNSWPQ_thenSucceed() {
fail("Graphs are not getting evicted");
}

@SneakyThrows
public void testEndToEnd_whenMethodIsHNSWSQFP16_thenSucceed() {
naveentatikonda marked this conversation as resolved.
Show resolved Hide resolved
String indexName = "test-index-hnsw-sqfp16";
String fieldName = "test-field-hnsw-sqfp16";

KNNMethod hnswMethod = KNNEngine.FAISS.getMethod(KNNConstants.METHOD_HNSW);
SpaceType spaceType = SpaceType.L2;
naveentatikonda marked this conversation as resolved.
Show resolved Hide resolved

List<Integer> mValues = ImmutableList.of(16, 32, 64, 128);
List<Integer> efConstructionValues = ImmutableList.of(16, 32, 64, 128);
List<Integer> efSearchValues = ImmutableList.of(16, 32, 64, 128);

int dimension = 128;
int numDocs = 100;

// Create an index
XContentBuilder builder = XContentFactory.jsonBuilder()
.startObject()
.startObject("properties")
.startObject(fieldName)
.field("type", "knn_vector")
.field("dimension", dimension)
.startObject(KNNConstants.KNN_METHOD)
.field(KNNConstants.NAME, hnswMethod.getMethodComponent().getName())
.field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue())
.field(KNNConstants.KNN_ENGINE, KNNEngine.FAISS.getName())
.startObject(KNNConstants.PARAMETERS)
.field(KNNConstants.METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size())))
.field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size())))
.field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size())))
.startObject(METHOD_ENCODER_PARAMETER)
.field(NAME, ENCODER_SQFP16)
.startObject(PARAMETERS)
.endObject()
.endObject()
.endObject()
.endObject()
.endObject()
.endObject()
.endObject();

Map<String, Object> mappingMap = xContentBuilderToMap(builder);
String mapping = builder.toString();

createKnnIndex(indexName, mapping);
assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName)));
indexTestData(indexName, fieldName, dimension, numDocs);
queryTestData(indexName, fieldName, dimension, numDocs);
naveentatikonda marked this conversation as resolved.
Show resolved Hide resolved
deleteKNNIndex(indexName);
validateGraphEviction();
}

@SneakyThrows
public void testEndToEnd_whenMethodIsIVFSQFP16_thenSucceed() {

String modelId = "test-model-ivf-sqfp16";
int dimension = 128;
int numDocs = 100;

String trainingIndexName = "train-index-ivf-sqfp16";
String trainingFieldName = "train-field-ivf-sqfp16";

// Add training data
createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension);
int trainingDataCount = 200;
bulkIngestRandomVectors(trainingIndexName, trainingFieldName, trainingDataCount, dimension);

XContentBuilder builder = XContentFactory.jsonBuilder()
.startObject()
.field(NAME, METHOD_IVF)
.field(KNN_ENGINE, FAISS_NAME)
.field(METHOD_PARAMETER_SPACE_TYPE, "l2")
.startObject(PARAMETERS)
.startObject(METHOD_ENCODER_PARAMETER)
.field(NAME, ENCODER_SQFP16)
.startObject(PARAMETERS)
.endObject()
.endObject()
.endObject()
.endObject();
Map<String, Object> method = xContentBuilderToMap(builder);

trainModel(modelId, trainingIndexName, trainingFieldName, dimension, method, "faiss ivf sqfp16 test description");

// Make sure training succeeds after 30 seconds
assertTrainingSucceeds(modelId, 30, 1000);

// Create knn index from model
String fieldName = "test-field-name-ivf-sqfp16";
String indexName = "test-index-name-ivf-sqfp16";
String indexMapping = XContentFactory.jsonBuilder()
.startObject()
.startObject("properties")
.startObject(fieldName)
.field("type", "knn_vector")
.field(MODEL_ID, modelId)
.endObject()
.endObject()
.endObject()
.toString();

createKnnIndex(indexName, getKNNDefaultIndexSettings(), indexMapping);

indexTestData(indexName, fieldName, dimension, numDocs);
queryTestData(indexName, fieldName, dimension, numDocs);
deleteKNNIndex(indexName);
validateGraphEviction();
}

@SneakyThrows
public void testEndToEnd_whenMethodIsHNSWPQAndHyperParametersNotSet_thenSucceed() {
String indexName = "test-index";
Expand Down Expand Up @@ -625,4 +737,43 @@ protected void setupKNNIndexForFilterQuery() throws Exception {

refreshIndex(INDEX_NAME);
}

private void queryTestData(String indexName, String fieldName, int dimension, int numDocs) throws IOException, ParseException {
naveentatikonda marked this conversation as resolved.
Show resolved Hide resolved
float[] queryVector = new float[dimension];
Arrays.fill(queryVector, (float) numDocs);
int k = 10;

Response searchResponse = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName, queryVector, k), k);
List<KNNResult> results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), fieldName);
assertEquals(k, results.size());
for (int i = 0; i < k; i++) {
assertEquals(numDocs - i - 1, Integer.parseInt(results.get(i).getDocId()));
}
}

private void indexTestData(String indexName, String fieldName, int dimension, int numDocs) throws Exception {
naveentatikonda marked this conversation as resolved.
Show resolved Hide resolved
for (int i = 0; i < numDocs; i++) {
float[] indexVector = new float[dimension];
Arrays.fill(indexVector, (float) i);
addKnnDocWithAttributes(indexName, Integer.toString(i), fieldName, indexVector, ImmutableMap.of("rating", String.valueOf(i)));
}

// Assert that all docs are ingested
refreshAllNonSystemIndices();
assertEquals(numDocs, getDocCount(indexName));
}

private void validateGraphEviction() throws Exception {
// Search every 5 seconds 14 times to confirm graph gets evicted
int intervals = 14;
for (int i = 0; i < intervals; i++) {
if (getTotalGraphsInCache() == 0) {
return;
}

Thread.sleep(5 * 1000);
}

fail("Graphs are not getting evicted");
}
}
50 changes: 50 additions & 0 deletions src/test/java/org/opensearch/knn/index/util/FaissTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE;
import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M;
import static org.opensearch.knn.common.KNNConstants.ENCODER_PQ;
import static org.opensearch.knn.common.KNNConstants.ENCODER_SQFP16;
import static org.opensearch.knn.common.KNNConstants.FAISS_NAME;
import static org.opensearch.knn.common.KNNConstants.INDEX_DESCRIPTION_PARAMETER;
import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE;
Expand Down Expand Up @@ -86,6 +87,31 @@ public void testGetMethodAsMap_whenMethodIsHNSWPQ_thenCreateCorrectIndexDescript
assertEquals(expectedIndexDescription, map.get(INDEX_DESCRIPTION_PARAMETER));
}

public void testGetMethodAsMap_whenMethodIsHNSWSQFP16_thenCreateCorrectIndexDescription() throws IOException {
naveentatikonda marked this conversation as resolved.
Show resolved Hide resolved
int hnswMParam = 65;
String expectedIndexDescription = String.format(Locale.ROOT, "HNSW%d,SQfp16", hnswMParam);

XContentBuilder xContentBuilder = XContentFactory.jsonBuilder()
.startObject()
.field(NAME, METHOD_HNSW)
.field(KNN_ENGINE, FAISS_NAME)
.startObject(PARAMETERS)
.field(METHOD_PARAMETER_M, hnswMParam)
.startObject(METHOD_ENCODER_PARAMETER)
.field(NAME, ENCODER_SQFP16)
.endObject()
.endObject()
.endObject();
Map<String, Object> in = xContentBuilderToMap(xContentBuilder);
KNNMethodContext knnMethodContext = KNNMethodContext.parse(in);
knnMethodContext.getMethodComponentContext().setIndexVersion(Version.CURRENT);

Map<String, Object> map = Faiss.INSTANCE.getMethodAsMap(knnMethodContext);

assertTrue(map.containsKey(INDEX_DESCRIPTION_PARAMETER));
assertEquals(expectedIndexDescription, map.get(INDEX_DESCRIPTION_PARAMETER));
}

public void testGetMethodAsMap_whenMethodIsIVFFlat_thenCreateCorrectIndexDescription() throws IOException {
int nlists = 88;
String expectedIndexDescription = String.format(Locale.ROOT, "IVF%d,Flat", nlists);
Expand Down Expand Up @@ -137,6 +163,30 @@ public void testGetMethodAsMap_whenMethodIsIVFPQ_thenCreateCorrectIndexDescripti
assertEquals(expectedIndexDescription, map.get(INDEX_DESCRIPTION_PARAMETER));
}

public void testGetMethodAsMap_whenMethodIsIVFSQFP16_thenCreateCorrectIndexDescription() throws IOException {
naveentatikonda marked this conversation as resolved.
Show resolved Hide resolved
int nlists = 88;
String expectedIndexDescription = String.format(Locale.ROOT, "IVF%d,SQfp16", nlists);

XContentBuilder xContentBuilder = XContentFactory.jsonBuilder()
.startObject()
.field(NAME, METHOD_IVF)
.field(KNN_ENGINE, FAISS_NAME)
.startObject(PARAMETERS)
.field(METHOD_PARAMETER_NLIST, nlists)
.startObject(METHOD_ENCODER_PARAMETER)
.field(NAME, ENCODER_SQFP16)
.endObject()
.endObject()
.endObject();
Map<String, Object> in = xContentBuilderToMap(xContentBuilder);
KNNMethodContext knnMethodContext = KNNMethodContext.parse(in);

Map<String, Object> map = Faiss.INSTANCE.getMethodAsMap(knnMethodContext);

assertTrue(map.containsKey(INDEX_DESCRIPTION_PARAMETER));
assertEquals(expectedIndexDescription, map.get(INDEX_DESCRIPTION_PARAMETER));
}

public void testMethodAsMapBuilder() throws IOException {
String methodName = "test-method";
String methodDescription = "test-description";
Expand Down