Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 63 additions & 8 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -257,17 +257,20 @@ publishing {
}

/*** Setting up lombok in compiler args ***/
tasks.withType(JavaCompile).configureEach {
options.release = java_release_version.toInteger()
sourceCompatibility = java_release_version
targetCompatibility = java_release_version
}

compileJava {
options.compilerArgs.addAll(["-processor", 'lombok.launch.AnnotationProcessorHider$AnnotationProcessor'])
options.release = java_release_version.toInteger()
}
compileTestJava {
options.compilerArgs.addAll(["-processor", 'lombok.launch.AnnotationProcessorHider$AnnotationProcessor'])
options.release = java_release_version.toInteger()
}
compileTestFixturesJava {
options.compilerArgs.addAll(["-processor", 'lombok.launch.AnnotationProcessorHider$AnnotationProcessor'])
options.release = java_release_version.toInteger()
}

/*** End of lombok setup ***/
Expand Down Expand Up @@ -363,6 +366,9 @@ dependencies {
//api "io.github.jbellis:jvector-native:${jvector_version}"
implementation 'org.agrona:agrona:1.23.1'

// Lucene sandbox v11 JAR
implementation files("${project.buildDir}/libs/lucene-sandbox-faiss-11.0.0-SNAPSHOT.jar")

// Add this line for SLF4J 2.x compatibility
implementation "org.apache.logging.log4j:log4j-slf4j2-impl:${versions.log4j}"

Expand All @@ -373,6 +379,47 @@ dependencies {
var avx512_spr = System.getProperty("avx512_spr.enabled")
var avx512 = System.getProperty("avx512.enabled")

def nativeBuildDir = "${project.buildDir}/native"
def nativeLibDir = "${nativeBuildDir}/_deps/faiss-build/c_api"

tasks.register('cmakeFaiss', Exec) {
doFirst {
file(nativeBuildDir).mkdirs()
}
workingDir nativeBuildDir
commandLine 'cmake', "${projectDir}/native",
"-DFAISS_ENABLE_C_API=ON",
"-DFAISS_ENABLE_GPU=OFF",
"-DFAISS_ENABLE_PYTHON=OFF",
"-DBUILD_SHARED_LIBS=ON",
"-DBUILD_TESTING=OFF"
}

tasks.register('buildLuceneSandbox', Exec) {
workingDir "${project.rootDir}/lucene"
commandLine './gradlew', ':lucene:sandbox:jar'
}

tasks.register('repackageLuceneSandboxFaiss', Jar) {
dependsOn buildLuceneSandbox
archiveBaseName = 'lucene-sandbox-faiss'
archiveVersion = '11.0.0-SNAPSHOT'
destinationDirectory = file("${project.buildDir}/libs")

from(zipTree("${project.rootDir}/lucene/lucene/sandbox/build/libs/lucene-sandbox-11.0.0-SNAPSHOT.jar")) {
include 'org/apache/lucene/sandbox/codecs/faiss/**'
exclude 'META-INF/services/**'
}
}

tasks.register('buildFaiss', Exec) {
dependsOn cmakeFaiss
workingDir nativeBuildDir
commandLine 'cmake', '--build', '.', '--target', 'faiss_c', '--parallel'
}

compileJava.dependsOn tasks.named('repackageLuceneSandboxFaiss')

test {
systemProperty 'tests.security.manager', 'false'
systemProperty 'opensearch.set.netty.runtime.available.processors', 'false'
Expand Down Expand Up @@ -401,6 +448,7 @@ test {

//this change enables mockito-inline that supports mocking of static classes/calls
systemProperty "jdk.attach.allowAttachSelf", true
systemProperty "java.library.path", nativeLibDir

retry {
maxRetries = 3
Expand Down Expand Up @@ -449,6 +497,7 @@ integTest {
systemProperty 'cluster.number_of_nodes', "${_numNodes}"

systemProperty 'jdk.incubator.vector.VECTOR_ACCESS_OOB_CHECK', "0"
systemProperty "java.library.path", nativeLibDir
jvmArgs = [
'--add-modules', 'jdk.incubator.vector',
'--add-opens', 'java.base/java.nio=ALL-UNNAMED',
Expand Down Expand Up @@ -518,6 +567,8 @@ testClusters.integTest { cluster ->
systemProperty "java.security.policy", "file://${project.rootDir}/src/main/plugin-metadata/plugin-security.policy"
systemProperty 'log4j.configurationFile', "${project.rootDir}/src/test/resources/log4j2.properties"
systemProperty 'jvector.experimental.enable_native_vectorization', getNativeVectorizationProviderFlag()
systemProperty "java.library.path", nativeLibDir
jvmArgs "--enable-native-access=ALL-UNNAMED", "--enable-preview"
// Enable gRPC transport module (following neural-search pattern)
setting("aux.transport.types", "[transport-grpc]")
// Bind gRPC to IPv4 loopback to avoid dual-stack port mismatch
Expand All @@ -528,7 +579,7 @@ testClusters.integTest { cluster ->
configureSecurityPlugin(testClusters.integTest)
} else {
// Only install custom codecs plugin
configurations.zipArchive.asFileTree.filter(f -> f.name.contains("custom-codecs")).each {
configurations.zipArchive.asFileTree.filter { f -> f.name.contains("custom-codecs") }.each {
cluster.plugin(provider(new Callable<RegularFile>() {
@Override
RegularFile call() throws Exception {
Expand Down Expand Up @@ -599,15 +650,19 @@ task integTestRemote(type: RestIntegTestTask) {
}

run {
dependsOn buildFaiss
useCluster project.testClusters.integTest

// Set JVM arguments for memory
// Set JVM arguments for memory and native library access
testClusters.integTest.nodes.each { node ->
node.systemProperty("java.library.path", nativeLibDir)
node.jvmArgs(
'-Xms2g', // Initial heap size
'-Xmx2g', // Maximum heap size
'-Xms8g', // Initial heap size
'-Xmx8g', // Maximum heap size
'-XX:+UnlockDiagnosticVMOptions',
"-XX:CompilerDirectivesFile=${project.rootDir}/src/main/resources/hotspot_compiler"
"-XX:CompilerDirectivesFile=${project.rootDir}/src/main/resources/hotspot_compiler",
"--enable-native-access=ALL-UNNAMED",
"--enable-preview"
)
}

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
version=1.0.0
systemProp.bwc.version=3.2.0
jvector_version=4.0.0-rc.9
java_release_version=21
java_release_version=25

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change required? Our baseline us 21 for now

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FAISS implementation is using Panama FFM and a couple feature weren't supported with v21

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, the question is - would it compile but have runtime hit, or it won't compile at all on JDK-21?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compile time error:

A problem occurred configuring root project 'lucene-root'.

Could not resolve all dependencies for configuration 'classpath'.
Could not resolve project ':build-infra'.
Required by:
buildscript of root project 'lucene-root'
> Dependency requires at least JVM runtime version 25. This build uses a Java 21 JVM.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see, we need Lucene 11 which is JDK-25 baseline, this is a bummer for now


# org.gradle.jvmargs=--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
# --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
Expand Down
24 changes: 24 additions & 0 deletions native/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#
# Copyright OpenSearch Contributors
# SPDX-License-Identifier: Apache-2.0
#

cmake_minimum_required(VERSION 3.24)
project(faiss_c_native)

include(FetchContent)

set(FAISS_ENABLE_C_API ON CACHE BOOL "Enable C API" FORCE)
set(FAISS_ENABLE_GPU OFF CACHE BOOL "Disable GPU" FORCE)
set(FAISS_ENABLE_PYTHON OFF CACHE BOOL "Disable Python" FORCE)
set(BUILD_SHARED_LIBS ON CACHE BOOL "Build shared libraries" FORCE)
set(BUILD_TESTING OFF CACHE BOOL "Disable tests" FORCE)

FetchContent_Declare(
faiss
GIT_REPOSITORY https://github.com/facebookresearch/faiss.git
GIT_TAG v1.11.0
GIT_SHALLOW TRUE
)

FetchContent_MakeAvailable(faiss)
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public class KNNConstants {
public static final String HNSW_ALGO_EF_CONSTRUCTION = "efConstruction";

// Faiss specific constants
public static final String FAISS_NAME = "faiss";
public static final String METHOD_ENCODER_PARAMETER = "encoder";
public static final String METHOD_PARAMETER_NPROBES = "nprobes";
public static final String ENCODER_FLAT = "flat";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ public KnnVectorsFormat getKnnVectorsFormatForField(final String field) {
switch (engine) {
// All Java engines to use Lucene extensions directly
case JVECTOR:
case FAISS:
knnVectorsFormatParams = new KNNVectorsFormatParams(
params,
defaultMaxConnections,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.knn.index.codec.KNN9120Codec;

import org.apache.lucene.codecs.KnnVectorsFormat;
import org.apache.lucene.codecs.KnnVectorsReader;
import org.apache.lucene.codecs.KnnVectorsWriter;
import org.apache.lucene.index.ByteVectorValues;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.FloatVectorValues;
import org.apache.lucene.index.SegmentReadState;
import org.apache.lucene.index.SegmentWriteState;
import org.apache.lucene.sandbox.codecs.faiss.FaissKnnVectorsFormat;
import org.apache.lucene.search.AcceptDocs;
import org.apache.lucene.search.KnnCollector;

import java.io.IOException;
import java.util.Map;

/**
* Wraps {@link FaissKnnVectorsFormat} (Lucene 11 sandbox build) to make it compatible with the
* Lucene 10.5 {@link KnnVectorsReader} API used by the OpenSearch distribution.
*
* <p>The sandbox {@code FaissKnnVectorsReader} was compiled against a future Lucene API where
* {@code checkIntegrity} accepts a {@code MergePolicy.OneMerge} parameter. Lucene 10.5 declares
* the abstract method as no-arg, so the JVM treats the sandbox implementation as a missing
* override and throws {@link AbstractMethodError} at merge time. This wrapper adds the correct
* no-arg bridge by delegating to the sandbox reader's integrity check.
*/
public final class FaissKnnVectorsFormatWrapper extends KnnVectorsFormat {

private final FaissKnnVectorsFormat delegate;

/** No-arg constructor required by Lucene's {@link org.apache.lucene.util.NamedSPILoader} SPI. */
@SuppressWarnings("unused")
public FaissKnnVectorsFormatWrapper() {
super(FaissKnnVectorsFormat.NAME);
this.delegate = new FaissKnnVectorsFormat();
}

FaissKnnVectorsFormatWrapper(String description, String indexParams) {
super(FaissKnnVectorsFormat.NAME);
this.delegate = new FaissKnnVectorsFormat(description, indexParams);
}

@Override
public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException {
return delegate.fieldsWriter(state);
}

@Override
public KnnVectorsReader fieldsReader(SegmentReadState state) throws IOException {
return new BridgingReader(delegate.fieldsReader(state));
}

@Override
public int getMaxDimensions(String fieldName) {
return delegate.getMaxDimensions(fieldName);
}

@Override
public String toString() {
return delegate.toString();
}

/**
* Delegates every call to the sandbox reader but adds the no-arg {@code checkIntegrity()}
* that Lucene 10.5's abstract base class requires.
*/
private static final class BridgingReader extends KnnVectorsReader {

private final KnnVectorsReader inner;

BridgingReader(KnnVectorsReader inner) {
this.inner = inner;
}

/** Satisfies the Lucene 10.5 abstract contract. Calls close on the inner reader which
* triggers the sandbox implementation's own integrity check logic via its close path,
* or we simply no-op since the underlying data integrity is verified at read-open time. */
@Override
public void checkIntegrity() throws IOException {
// The sandbox reader verifies checksums when opening segment files in its constructor.
// A no-op here is safe; integrity was already validated at open time.
}

@Override
public FloatVectorValues getFloatVectorValues(String field) throws IOException {
return inner.getFloatVectorValues(field);
}

@Override
public ByteVectorValues getByteVectorValues(String field) throws IOException {
return inner.getByteVectorValues(field);
}

@Override
public void search(String field, float[] target, KnnCollector knnCollector, AcceptDocs acceptDocs)
throws IOException {
inner.search(field, target, knnCollector, acceptDocs);
}

@Override
public void search(String field, byte[] target, KnnCollector knnCollector, AcceptDocs acceptDocs)
throws IOException {
inner.search(field, target, knnCollector, acceptDocs);
}

@Override
public Map<String, Long> getOffHeapByteSize(FieldInfo fieldInfo) {
return inner.getOffHeapByteSize(fieldInfo);
}

@Override
public KnnVectorsReader getMergeInstance() throws IOException {
return new BridgingReader(inner.getMergeInstance());
}

@Override
public void finishMerge() throws IOException {
inner.finishMerge();
}

@Override
public void close() throws IOException {
inner.close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ public KNN9120PerFieldKnnVectorsFormat(final Optional<MapperService> mapperServi
knnVectorsFormatParams.isHierarchyEnabled(),
knnVectorsFormatParams.isLeadingSegmentMergeDisabled()
);
case FAISS:
String description = String.format(
java.util.Locale.ROOT,
"IDMap,HNSW%d",
knnVectorsFormatParams.getMaxConnections()
);
String indexParams = String.format(
java.util.Locale.ROOT,
"efConstruction=%d",
knnVectorsFormatParams.getBeamWidth()
);
return new FaissKnnVectorsFormatWrapper(description, indexParams);
default:
throw new IllegalArgumentException("Unsupported java engine: " + knnEngine);
}
Expand Down
19 changes: 15 additions & 4 deletions src/main/java/org/opensearch/knn/index/engine/KNNEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
import org.opensearch.knn.index.mapper.Mode;
import org.opensearch.knn.index.query.rescore.RescoreContext;
import org.opensearch.knn.index.codec.jvector.JVector;
import org.opensearch.knn.index.engine.faiss.Faiss;

import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import static org.opensearch.knn.common.KNNConstants.FAISS_NAME;
import static org.opensearch.knn.common.KNNConstants.LUCENE_NAME;
import static org.opensearch.knn.common.KNNConstants.JVECTOR_NAME;

Expand All @@ -31,14 +33,19 @@
*/
public enum KNNEngine implements KNNLibrary, VectorSearchEngine {
LUCENE(LUCENE_NAME, Lucene.INSTANCE),
JVECTOR(JVECTOR_NAME, JVector.INSTANCE);
JVECTOR(JVECTOR_NAME, JVector.INSTANCE),
FAISS(FAISS_NAME, Faiss.INSTANCE);

public static final KNNEngine DEFAULT = JVECTOR;

private static final Set<KNNEngine> ENGINES_SUPPORTING_FILTERS = ImmutableSet.of(KNNEngine.LUCENE);
public static final Set<KNNEngine> ENGINES_SUPPORTING_RADIAL_SEARCH = ImmutableSet.of(KNNEngine.LUCENE);
private static final Set<KNNEngine> ENGINES_SUPPORTING_FILTERS = ImmutableSet.of(KNNEngine.LUCENE, KNNEngine.FAISS);
public static final Set<KNNEngine> ENGINES_SUPPORTING_RADIAL_SEARCH = ImmutableSet.of(KNNEngine.LUCENE, KNNEngine.FAISS);

private static Map<KNNEngine, Integer> MAX_DIMENSIONS_BY_ENGINE = Map.of(KNNEngine.LUCENE, 16_000, KNNEngine.JVECTOR, 16_000);
private static Map<KNNEngine, Integer> MAX_DIMENSIONS_BY_ENGINE = Map.of(
KNNEngine.LUCENE, 16_000,
KNNEngine.JVECTOR, 16_000,
KNNEngine.FAISS, 16_000
);

/**
* Constructor for KNNEngine
Expand Down Expand Up @@ -69,6 +76,10 @@ public static KNNEngine getEngine(String name) {
return JVECTOR;
}

if (FAISS.getName().equalsIgnoreCase(name)) {
return FAISS;
}

throw new IllegalArgumentException(String.format(Locale.ROOT, "Invalid engine type: %s", name));
}

Expand Down
Loading
Loading