From 2bfc93e0cf57441d34d5d187aabf93308887d6aa Mon Sep 17 00:00:00 2001 From: Naveen Tatikonda Date: Fri, 17 Jan 2025 16:28:52 -0600 Subject: [PATCH] Optimize create index from template Signed-off-by: Naveen Tatikonda --- jni/include/faiss_index_service.h | 40 ++++++ jni/include/faiss_wrapper.h | 3 + .../org_opensearch_knn_jni_FaissService.h | 26 ++++ jni/src/faiss_index_service.cpp | 126 ++++++++++++++++++ jni/src/faiss_wrapper.cpp | 43 ++++++ .../org_opensearch_knn_jni_FaissService.cpp | 43 ++++++ .../MemOptimizedNativeIndexBuildStrategy.java | 56 ++++++-- .../codec/nativeindex/NativeIndexWriter.java | 3 +- .../org/opensearch/knn/jni/FaissService.java | 33 +++++ .../org/opensearch/knn/jni/JNIService.java | 33 +++++ 10 files changed, 396 insertions(+), 10 deletions(-) diff --git a/jni/include/faiss_index_service.h b/jni/include/faiss_index_service.h index d96c3e755..c02f065d8 100644 --- a/jni/include/faiss_index_service.h +++ b/jni/include/faiss_index_service.h @@ -69,6 +69,20 @@ class IndexService { */ virtual void writeIndex(faiss::IOWriter* writer, jlong idMapAddress); + /** + * Initialize index from template + * + * @param jniUtil jni util + * @param env jni environment + * @param dim dimension of vectors + * @param numVectors number of vectors + * @param threadCount number of thread count to be used while adding data + * @param templateIndexJ template index + * @return memory address of the native index object + */ + virtual jlong initIndexFromTemplate(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, int dim, int numVectors, int threadCount, jbyteArray templateIndexJ); + + virtual ~IndexService() = default; protected: @@ -132,6 +146,19 @@ class BinaryIndexService final : public IndexService { */ void writeIndex(faiss::IOWriter* writer, jlong idMapAddress) final; + /** + * Initialize index from template + * + * @param jniUtil jni util + * @param env jni environment + * @param dim dimension of vectors + * @param numVectors number of vectors + * @param threadCount number of thread count to be used while adding data + * @param templateIndexJ template index + * @return memory address of the native index object + */ + virtual jlong initIndexFromTemplate(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, int dim, int numVectors, int threadCount, jbyteArray templateIndexJ); + protected: void allocIndex(faiss::Index * index, size_t dim, size_t numVectors) final; }; // class BinaryIndexService @@ -191,6 +218,19 @@ class ByteIndexService final : public IndexService { */ void writeIndex(faiss::IOWriter* writer, jlong idMapAddress) final; + /** + * Initialize index from template + * + * @param jniUtil jni util + * @param env jni environment + * @param dim dimension of vectors + * @param numVectors number of vectors + * @param threadCount number of thread count to be used while adding data + * @param templateIndexJ template index + * @return memory address of the native index object + */ + virtual jlong initIndexFromTemplate(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, int dim, int numVectors, int threadCount, jbyteArray templateIndexJ); + protected: void allocIndex(faiss::Index * index, size_t dim, size_t numVectors) final; }; // class ByteIndexService diff --git a/jni/include/faiss_wrapper.h b/jni/include/faiss_wrapper.h index e48e6faa9..23e3ad6b3 100644 --- a/jni/include/faiss_wrapper.h +++ b/jni/include/faiss_wrapper.h @@ -25,6 +25,9 @@ namespace knn_jni { void WriteIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jobject output, jlong indexAddr, IndexService *indexService); + jlong InitIndexFromTemplate(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jlong numDocs, jint dimJ, jobject parametersJ, jbyteArray templateIndexJ, IndexService *indexService); + + // Create an index with ids and vectors. Instead of creating a new index, this function creates the index // based off of the template index passed in. The index is serialized to indexPathJ. void CreateIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, diff --git a/jni/include/org_opensearch_knn_jni_FaissService.h b/jni/include/org_opensearch_knn_jni_FaissService.h index dce580138..c3b14bbe3 100644 --- a/jni/include/org_opensearch_knn_jni_FaissService.h +++ b/jni/include/org_opensearch_knn_jni_FaissService.h @@ -92,6 +92,32 @@ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeBinaryIndex */ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeByteIndex(JNIEnv *, jclass, jlong, jobject); +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: initIndexFromTemplate + * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initIndexFromTemplate(JNIEnv * env, jclass cls, + jlong numDocs, jint dimJ, + jobject parametersJ, jbyteArray templateIndexJ); +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: initBinaryIndexFromTemplate + * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initBinaryIndexFromTemplate(JNIEnv * env, jclass cls, + jlong numDocs, jint dimJ, + jobject parametersJ, jbyteArray templateIndexJ); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: initByteIndexFromTemplate + * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initByteIndexFromTemplate(JNIEnv * env, jclass cls, + jlong numDocs, jint dimJ, + jobject parametersJ, jbyteArray templateIndexJ); + /* * Class: org_opensearch_knn_jni_FaissService * Method: createIndexFromTemplate diff --git a/jni/src/faiss_index_service.cpp b/jni/src/faiss_index_service.cpp index 4999e3172..5977a95a3 100644 --- a/jni/src/faiss_index_service.cpp +++ b/jni/src/faiss_index_service.cpp @@ -155,6 +155,47 @@ void IndexService::writeIndex( } } +jlong IndexService::initIndexFromTemplate( + knn_jni::JNIUtilInterface * jniUtil, + JNIEnv * env, + int dim, + int numVectors, + int threadCount, + jbyteArray templateIndexJ + ) { + + // Get vector of bytes from jbytearray + int indexBytesCount = jniUtil->GetJavaBytesArrayLength(env, templateIndexJ); + jbyte * indexBytesJ = jniUtil->GetByteArrayElements(env, templateIndexJ, nullptr); + + faiss::VectorIOReader vectorIoReader; + for (int i = 0; i < indexBytesCount; i++) { + vectorIoReader.data.push_back((uint8_t) indexBytesJ[i]); + } + jniUtil->ReleaseByteArrayElements(env, templateIndexJ, indexBytesJ, JNI_ABORT); + + // Create faiss index + std::unique_ptr index; + index.reset(faiss::read_index(&vectorIoReader, 0)); + + // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread + if (threadCount != 0) { + omp_set_num_threads(threadCount); + } + + std::unique_ptr idMap (faissMethods->indexIdMap(index.get())); + //Makes sure the index is deleted when the destructor is called, this cannot be passed in the constructor + idMap->own_fields = true; + + // TODO: allocIndex + allocIndex(dynamic_cast(idMap->index), dim, numVectors); + + //Release the ownership so as to make sure not delete the underlying index that is created. The index is needed later + //in insert and write operations + index.release(); + return reinterpret_cast(idMap.release()); +} + BinaryIndexService::BinaryIndexService(std::unique_ptr _faissMethods) : IndexService(std::move(_faissMethods)) { } @@ -252,6 +293,50 @@ void BinaryIndexService::writeIndex( } } +jlong BinaryIndexService::initIndexFromTemplate( + knn_jni::JNIUtilInterface * jniUtil, + JNIEnv * env, + int dim, + int numVectors, + int threadCount, + jbyteArray templateIndexJ + ) { + if (dim % 8 != 0) { + throw std::runtime_error("Dimensions should be multiple of 8"); + } + + // Get vector of bytes from jbytearray + int indexBytesCount = jniUtil->GetJavaBytesArrayLength(env, templateIndexJ); + jbyte * indexBytesJ = jniUtil->GetByteArrayElements(env, templateIndexJ, nullptr); + + faiss::VectorIOReader vectorIoReader; + for (int i = 0; i < indexBytesCount; i++) { + vectorIoReader.data.push_back((uint8_t) indexBytesJ[i]); + } + jniUtil->ReleaseByteArrayElements(env, templateIndexJ, indexBytesJ, JNI_ABORT); + + // Create faiss index + std::unique_ptr index; + index.reset(faiss::read_index_binary(&vectorIoReader, 0)); + + // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread + if (threadCount != 0) { + omp_set_num_threads(threadCount); + } + + std::unique_ptr idMap (faissMethods->indexBinaryIdMap(index.get())); + //Makes sure the index is deleted when the destructor is called, this cannot be passed in the constructor + idMap->own_fields = true; + + // TODO: allocIndex + allocIndex(dynamic_cast(idMap->index), dim, numVectors); + + //Release the ownership so as to make sure not delete the underlying index that is created. The index is needed later + //in insert and write operations + index.release(); + return reinterpret_cast(idMap.release()); +} + ByteIndexService::ByteIndexService(std::unique_ptr _faissMethods) : IndexService(std::move(_faissMethods)) { } @@ -368,5 +453,46 @@ void ByteIndexService::writeIndex( throw std::runtime_error("Failed to write index to disk"); } } + +jlong ByteIndexService::initIndexFromTemplate( + knn_jni::JNIUtilInterface * jniUtil, + JNIEnv * env, + int dim, + int numVectors, + int threadCount, + jbyteArray templateIndexJ + ) { + + // Get vector of bytes from jbytearray + int indexBytesCount = jniUtil->GetJavaBytesArrayLength(env, templateIndexJ); + jbyte * indexBytesJ = jniUtil->GetByteArrayElements(env, templateIndexJ, nullptr); + + faiss::VectorIOReader vectorIoReader; + for (int i = 0; i < indexBytesCount; i++) { + vectorIoReader.data.push_back((uint8_t) indexBytesJ[i]); + } + jniUtil->ReleaseByteArrayElements(env, templateIndexJ, indexBytesJ, JNI_ABORT); + + // Create faiss index + std::unique_ptr index; + index.reset(faiss::read_index(&vectorIoReader, 0)); + + // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread + if (threadCount != 0) { + omp_set_num_threads(threadCount); + } + + std::unique_ptr idMap (faissMethods->indexIdMap(index.get())); + //Makes sure the index is deleted when the destructor is called, this cannot be passed in the constructor + idMap->own_fields = true; + + // TODO: allocIndex + allocIndex(dynamic_cast(idMap->index), dim, numVectors); + + //Release the ownership so as to make sure not delete the underlying index that is created. The index is needed later + //in insert and write operations + index.release(); + return reinterpret_cast(idMap.release()); +} } // namespace faiss_wrapper } // namesapce knn_jni diff --git a/jni/src/faiss_wrapper.cpp b/jni/src/faiss_wrapper.cpp index c02c410c1..fa2b1068b 100644 --- a/jni/src/faiss_wrapper.cpp +++ b/jni/src/faiss_wrapper.cpp @@ -198,6 +198,49 @@ void knn_jni::faiss_wrapper::WriteIndex(knn_jni::JNIUtilInterface * jniUtil, JNI indexService->writeIndex(&writer, index_ptr); } +jlong knn_jni::faiss_wrapper::InitIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jlong numDocs, jint dimJ, + jobject parametersJ, jbyteArray templateIndexJ, IndexService* indexService) { + + if(dimJ <= 0) { + throw std::runtime_error("Vectors dimensions cannot be less than or equal to 0"); + } + + if (parametersJ == nullptr) { + throw std::runtime_error("Parameters cannot be null"); + } + + if (templateIndexJ == nullptr) { + throw std::runtime_error("Template index cannot be null"); + } + + // parametersJ is a Java Map. ConvertJavaMapToCppMap converts it to a c++ map + // so that it is easier to access. + auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); + + // Thread count + int threadCount = 0; + if(parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { + threadCount = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); + } + jniUtil->DeleteLocalRef(env, parametersJ); + + + // Dimension + int dim = (int)dimJ; + + // Number of docs + int docs = (int)numDocs; + // end parameters to pass + + // Create index + return indexService->initIndexFromTemplate(jniUtil, + env, + dim, + docs, + threadCount, + templateIndexJ); +} + void knn_jni::faiss_wrapper::CreateIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, jlong vectorsAddressJ, jint dimJ, jobject output, jbyteArray templateIndexJ, jobject parametersJ) { diff --git a/jni/src/org_opensearch_knn_jni_FaissService.cpp b/jni/src/org_opensearch_knn_jni_FaissService.cpp index 836774402..99310fb1f 100644 --- a/jni/src/org_opensearch_knn_jni_FaissService.cpp +++ b/jni/src/org_opensearch_knn_jni_FaissService.cpp @@ -166,6 +166,49 @@ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeByteIndex(J } } +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initIndexFromTemplate(JNIEnv * env, jclass cls, + jlong numDocs, jint dimJ, + jobject parametersJ, jbyteArray templateIndexJ) +{ + try { + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::IndexService indexService(std::move(faissMethods)); + return knn_jni::faiss_wrapper::InitIndexFromTemplate(&jniUtil, env, numDocs, dimJ, parametersJ, templateIndexJ, &indexService); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return (jlong)0; +} + +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initBinaryIndexFromTemplate(JNIEnv * env, jclass cls, + jlong numDocs, jint dimJ, + jobject parametersJ, jbyteArray templateIndexJ) +{ + try { + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::BinaryIndexService binaryIndexService(std::move(faissMethods)); + return knn_jni::faiss_wrapper::InitIndexFromTemplate(&jniUtil, env, numDocs, dimJ, parametersJ, templateIndexJ, &binaryIndexService); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return (jlong)0; +} + +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initByteIndexFromTemplate(JNIEnv * env, jclass cls, + jlong numDocs, jint dimJ, + jobject parametersJ, jbyteArray templateIndexJ) +{ + try { + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::ByteIndexService byteIndexService(std::move(faissMethods)); + return knn_jni::faiss_wrapper::InitIndexFromTemplate(&jniUtil, env, numDocs, dimJ, parametersJ, templateIndexJ, &byteIndexService); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return (jlong)0; +} + + JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createIndexFromTemplate(JNIEnv * env, jclass cls, jintArray idsJ, diff --git a/src/main/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategy.java b/src/main/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategy.java index 81f5915a7..5e6f4842a 100644 --- a/src/main/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategy.java +++ b/src/main/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategy.java @@ -7,11 +7,13 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; +import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.codec.nativeindex.model.BuildIndexParams; import org.opensearch.knn.index.codec.transfer.OffHeapVectorTransfer; import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.vectorvalues.KNNVectorValues; import org.opensearch.knn.jni.JNIService; +import org.opensearch.knn.quantization.models.quantizationState.ByteScalarQuantizationState; import java.io.IOException; import java.security.AccessController; @@ -21,6 +23,7 @@ import java.util.Map; import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; import static org.opensearch.knn.common.KNNVectorUtil.intListToArray; import static org.opensearch.knn.common.KNNVectorUtil.iterateVectorValuesOnce; import static org.opensearch.knn.index.codec.transfer.OffHeapVectorTransferFactory.getVectorTransfer; @@ -57,16 +60,31 @@ public void buildAndWriteIndex(final BuildIndexParams indexInfo) throws IOExcept KNNEngine engine = indexInfo.getKnnEngine(); Map indexParameters = indexInfo.getParameters(); IndexBuildSetup indexBuildSetup = QuantizationIndexUtils.prepareIndexBuild(knnVectorValues, indexInfo); + long indexMemoryAddress; - // Initialize the index - long indexMemoryAddress = AccessController.doPrivileged( - (PrivilegedAction) () -> JNIService.initIndex( - indexInfo.getTotalLiveDocs(), - indexBuildSetup.getDimensions(), - indexParameters, - engine - ) - ); + if (isTemplate(indexInfo)) { + // Initialize the index from Template + indexMemoryAddress = AccessController.doPrivileged( + (PrivilegedAction) () -> JNIService.initIndexFromTemplate( + indexInfo.getTotalLiveDocs(), + indexBuildSetup.getDimensions(), + indexParameters, + engine, + getIndexTemplate(indexParameters, indexInfo) + ) + ); + + } else { + // Initialize the index + indexMemoryAddress = AccessController.doPrivileged( + (PrivilegedAction) () -> JNIService.initIndex( + indexInfo.getTotalLiveDocs(), + indexBuildSetup.getDimensions(), + indexParameters, + engine + ) + ); + } try ( final OffHeapVectorTransfer vectorTransfer = getVectorTransfer( @@ -133,4 +151,24 @@ public void buildAndWriteIndex(final BuildIndexParams indexInfo) throws IOExcept ); } } + + private static boolean isTemplate(final BuildIndexParams indexInfo) { + if (indexInfo.getParameters().containsKey(MODEL_ID)) { + return true; + } + + if (indexInfo.getQuantizationState() instanceof ByteScalarQuantizationState) { + return true; + } + return false; + } + + private byte[] getIndexTemplate(Map params, BuildIndexParams indexInfo) { + if (params.containsKey(MODEL_ID)) { + return (byte[]) params.get(KNNConstants.MODEL_BLOB_PARAMETER); + } + + ByteScalarQuantizationState byteSQState = (ByteScalarQuantizationState) indexInfo.getQuantizationState(); + return byteSQState.getIndexTemplate(); + } } diff --git a/src/main/java/org/opensearch/knn/index/codec/nativeindex/NativeIndexWriter.java b/src/main/java/org/opensearch/knn/index/codec/nativeindex/NativeIndexWriter.java index e2c309ad1..af4c4343e 100644 --- a/src/main/java/org/opensearch/knn/index/codec/nativeindex/NativeIndexWriter.java +++ b/src/main/java/org/opensearch/knn/index/codec/nativeindex/NativeIndexWriter.java @@ -313,7 +313,8 @@ private static NativeIndexWriter createWriter( @Nullable final QuantizationState quantizationState ) { final KNNEngine knnEngine = extractKNNEngine(fieldInfo); - boolean iterative = !isTemplate(fieldInfo) && KNNEngine.FAISS == knnEngine; + // boolean iterative = !isTemplate(fieldInfo) && KNNEngine.FAISS == knnEngine; + boolean iterative = KNNEngine.FAISS == knnEngine; NativeIndexBuildStrategy strategy = iterative ? MemOptimizedNativeIndexBuildStrategy.getInstance() : DefaultIndexBuildStrategy.getInstance(); diff --git a/src/main/java/org/opensearch/knn/jni/FaissService.java b/src/main/java/org/opensearch/knn/jni/FaissService.java index dcc7b180d..47fbb8760 100644 --- a/src/main/java/org/opensearch/knn/jni/FaissService.java +++ b/src/main/java/org/opensearch/knn/jni/FaissService.java @@ -154,6 +154,39 @@ class FaissService { */ public static native void writeByteIndex(long indexAddress, IndexOutputWithBuffer output); + /** + * Initialize an index for the native library with a provided template index. Takes in numDocs to + * allocate the correct amount of memory. + * + * @param numDocs number of documents to be added + * @param dim dimension of the vector to be indexed + * @param parameters parameters to build index + * @param templateIndex template index + */ + public static native long initIndexFromTemplate(long numDocs, int dim, Map parameters, byte[] templateIndex); + + /** + * Initialize an index for the native library with a provided template index. Takes in numDocs to + * allocate the correct amount of memory. + * + * @param numDocs number of documents to be added + * @param dim dimension of the vector to be indexed + * @param parameters parameters to build index + * @param templateIndex template index + */ + public static native long initBinaryIndexFromTemplate(long numDocs, int dim, Map parameters, byte[] templateIndex); + + /** + * Initialize a byte index for the native library with a provided template index. Takes in numDocs to + * allocate the correct amount of memory. + * + * @param numDocs number of documents to be added + * @param dim dimension of the vector to be indexed + * @param parameters parameters to build index + * @param templateIndex template index + */ + public static native long initByteIndexFromTemplate(long numDocs, int dim, Map parameters, byte[] templateIndex); + /** * Create an index for the native library with a provided template index * diff --git a/src/main/java/org/opensearch/knn/jni/JNIService.java b/src/main/java/org/opensearch/knn/jni/JNIService.java index b490476eb..c01932d81 100644 --- a/src/main/java/org/opensearch/knn/jni/JNIService.java +++ b/src/main/java/org/opensearch/knn/jni/JNIService.java @@ -146,6 +146,39 @@ public static void createIndex( ); } + /** + * Initialize an index for the native library with a provided template index. Takes in numDocs to + * allocate the correct amount of memory. + * + * @param numDocs number of documents to be added + * @param dim dimension of the vector to be indexed + * @param parameters parameters to build index + * @param knnEngine knn engine + * @param templateIndex template index + * @return address of the index in memory + */ + public static long initIndexFromTemplate( + long numDocs, + int dim, + Map parameters, + KNNEngine knnEngine, + byte[] templateIndex + ) { + if (KNNEngine.FAISS == knnEngine) { + if (IndexUtil.isBinaryIndex(knnEngine, parameters)) { + return FaissService.initBinaryIndexFromTemplate(numDocs, dim, parameters, templateIndex); + } + if (IndexUtil.isByteIndex(parameters)) { + return FaissService.initByteIndexFromTemplate(numDocs, dim, parameters, templateIndex); + } + return FaissService.initIndexFromTemplate(numDocs, dim, parameters, templateIndex); + } + + throw new IllegalArgumentException( + String.format(Locale.ROOT, "initIndexFromTemplate not supported for provided engine : %s", knnEngine.getName()) + ); + } + /** * Create an index for the native library with a provided template index *